uni_app/
native_app.rs

1mod native_keycode;
2
3use glutin;
4use glutin::dpi::LogicalSize;
5use glutin::event::ElementState;
6use glutin::event::Event;
7use glutin::event::KeyboardInput;
8use glutin::event::ModifiersState;
9use glutin::event::MouseButton;
10use glutin::event::VirtualKeyCode;
11use glutin::event::WindowEvent;
12use glutin::event_loop::EventLoop;
13use glutin::monitor::VideoMode;
14use glutin::window::Fullscreen;
15use glutin::window::WindowBuilder;
16use glutin::Context;
17use glutin::PossiblyCurrent;
18use glutin::WindowedContext;
19use std::cell::RefCell;
20use std::env;
21use std::os::raw::c_void;
22use std::process;
23use std::rc::Rc;
24use time;
25
26use crate::AppConfig;
27use crate::AppEvent;
28
29use self::native_keycode::{translate_scan_code, translate_virtual_key};
30use crate::events;
31use crate::{File, FileSystem};
32
33enum WindowContext {
34    Headless(Context<PossiblyCurrent>),
35    Normal(WindowedContext<PossiblyCurrent>),
36}
37
38impl WindowContext {
39    fn hidpi_factor(&self) -> f32 {
40        match self {
41            WindowContext::Normal(ref w) => w.window().scale_factor() as f32,
42            _ => 1.0,
43        }
44    }
45
46    fn window(&self) -> &WindowedContext<PossiblyCurrent> {
47        match self {
48            WindowContext::Normal(ref w) => w,
49            _ => unimplemented!(),
50        }
51    }
52
53    fn context(&self) -> &Context<PossiblyCurrent> {
54        match self {
55            WindowContext::Normal(w) => w.context(),
56            WindowContext::Headless(w) => w,
57        }
58    }
59
60    fn swap_buffers(&self) -> Result<(), glutin::ContextError> {
61        match self {
62            WindowContext::Normal(ref w) => w.swap_buffers(),
63            WindowContext::Headless(_) => Ok(()),
64        }
65    }
66}
67
68/// the main application struct
69pub struct App {
70    window: WindowContext,
71    events_loop: Option<EventLoop<()>>,
72    intercept_close_request: bool,
73    modifiers_state: ModifiersState,
74    pub events: Rc<RefCell<Vec<AppEvent>>>,
75    dropped_files: Vec<File>,
76    fullscreen_resolution: VideoMode,
77}
78
79fn get_virtual_key(input: KeyboardInput) -> String {
80    match input.virtual_keycode {
81        Some(k) => {
82            let mut s = translate_virtual_key(k).into();
83            if s == "" {
84                s = format!("{:?}", k);
85            }
86            s
87        }
88        None => "".into(),
89    }
90}
91
92fn get_scan_code(input: KeyboardInput) -> events::ScanCode {
93    translate_scan_code(input.scancode & 0xFF)
94}
95
96fn translate_event(e: Event<()>, modifiers: &ModifiersState) -> Option<AppEvent> {
97    if let Event::WindowEvent {
98        event: winevent, ..
99    } = e
100    {
101        match winevent {
102            WindowEvent::MouseInput { state, button, .. } => {
103                let mouse_button = match button {
104                    MouseButton::Left => events::MouseButton::Left,
105                    MouseButton::Middle => events::MouseButton::Middle,
106                    MouseButton::Right => events::MouseButton::Right,
107                    MouseButton::Other(val) => events::MouseButton::Other(val as usize),
108                };
109                let event = events::MouseButtonEvent { button: mouse_button };
110                match state {
111                    ElementState::Pressed => Some(AppEvent::MouseDown(event)),
112                    ElementState::Released => Some(AppEvent::MouseUp(event)),
113                }
114            }
115            WindowEvent::CursorMoved { position, .. } => Some(AppEvent::MousePos(position.into())),
116            WindowEvent::KeyboardInput { input, .. } => match input.state {
117                ElementState::Pressed => Some(AppEvent::KeyDown(events::KeyDownEvent {
118                    key: get_virtual_key(input),
119                    code: get_scan_code(input),
120                    shift: modifiers.shift(),
121                    alt: modifiers.alt(),
122                    ctrl: modifiers.ctrl(),
123                })),
124                ElementState::Released => Some(AppEvent::KeyUp(events::KeyUpEvent {
125                    key: get_virtual_key(input),
126                    code: get_scan_code(input),
127                    shift: modifiers.shift(),
128                    alt: modifiers.alt(),
129                    ctrl: modifiers.ctrl(),
130                })),
131            },
132            WindowEvent::ReceivedCharacter(c) => Some(AppEvent::CharEvent(c)),
133            WindowEvent::Resized(size) => Some(AppEvent::Resized(size.into())),
134            WindowEvent::CloseRequested => Some(AppEvent::CloseRequested),
135            WindowEvent::DroppedFile(path) => {
136                Some(AppEvent::FileDropped(path.to_str().unwrap().to_owned()))
137            }
138            _ => None,
139        }
140    } else {
141        None
142    }
143}
144
145impl App {
146    /// create a new game window
147    pub fn new(config: AppConfig) -> App {
148        use glutin::*;
149        let events_loop = EventLoop::new();
150        let gl_req = GlRequest::GlThenGles {
151            opengl_version: (3, 2),
152            opengles_version: (2, 0),
153        };
154        let fullscreen_resolution = events_loop
155            .available_monitors()
156            .nth(0)
157            .unwrap()
158            .video_modes()
159            .nth(0)
160            .unwrap();
161        let window = if config.headless {
162            let headless_context = ContextBuilder::new()
163                .with_gl(gl_req)
164                .with_gl_profile(GlProfile::Core)
165                .build_headless(&events_loop, (config.size.0, config.size.1).into())
166                .unwrap();
167
168            WindowContext::Headless(unsafe { headless_context.make_current().unwrap() })
169        } else {
170            let window_builder = WindowBuilder::new()
171                .with_title(config.title)
172                .with_fullscreen(if config.fullscreen {
173                    Some(Fullscreen::Exclusive(fullscreen_resolution.clone()))
174                } else {
175                    None
176                })
177                .with_resizable(config.resizable)
178                .with_inner_size(LogicalSize::new(config.size.0, config.size.1))
179                .with_window_icon(
180                    if let Some((w,h,data)) = config.icon {
181                        window::Icon::from_rgba(data, w, h).ok()
182                    } else { None }
183                );
184
185            let windowed_context = ContextBuilder::new()
186                .with_vsync(config.vsync)
187                .with_gl(gl_req)
188                .with_gl_profile(GlProfile::Core)
189                .build_windowed(window_builder, &events_loop)
190                .unwrap();
191
192            windowed_context
193                .window()
194                .set_cursor_visible(config.show_cursor);
195
196            WindowContext::Normal(unsafe { windowed_context.make_current().unwrap() })
197        };
198
199        App {
200            window,
201            events_loop: Some(events_loop),
202            intercept_close_request: config.intercept_close_request,
203            events: Rc::new(RefCell::new(Vec::new())),
204            dropped_files: Vec::new(),
205            modifiers_state: ModifiersState::default(),
206            fullscreen_resolution,
207        }
208    }
209
210    /// return the screen resolution in physical pixels
211    pub fn get_screen_resolution(&self) -> (u32, u32) {
212        if let WindowContext::Normal(ref glwindow) = self.window {
213            if let Some(ref monitor) = glwindow.window().current_monitor() {
214                return monitor.size().into();
215            }
216        }
217        (0, 0)
218    }
219
220    /// return the command line / URL parameters
221    pub fn get_params() -> Vec<String> {
222        let mut params: Vec<String> = env::args().collect();
223        params.remove(0);
224        params
225    }
226
227    /// activate or deactivate fullscreen. only works on native target
228    pub fn set_fullscreen(&mut self, b: bool) {
229        if let WindowContext::Normal(ref glwindow) = self.window {
230            if b {
231                glwindow.window().set_fullscreen(Some(Fullscreen::Exclusive(
232                    self.fullscreen_resolution.clone(),
233                )));
234            } else {
235                glwindow.window().set_fullscreen(None);
236            }
237        }
238    }
239
240    /// print a message on standard output (native) or js console (web)
241    pub fn print<T: Into<String>>(msg: T) {
242        println!("{}", msg.into());
243    }
244
245    /// exit current process (close the game window). On web target, this does nothing.
246    pub fn exit() {
247        process::exit(0);
248    }
249
250    /// returns the HiDPI factor for current screen
251    pub fn hidpi_factor(&self) -> f32 {
252        self.window.hidpi_factor()
253    }
254
255    fn get_proc_address(&self, name: &str) -> *const c_void {
256        self.window.context().get_proc_address(name) as *const c_void
257    }
258
259    /// return the opengl context for this window
260    pub fn canvas<'p>(&'p self) -> Box<dyn 'p + FnMut(&str) -> *const c_void> {
261        Box::new(move |name| self.get_proc_address(name))
262    }
263
264    fn handle_event(&mut self, event: Event<()>) -> (bool, bool) {
265        let mut running = true;
266        let mut next_frame = false;
267        match event {
268            Event::RedrawRequested(_) => {}
269            Event::MainEventsCleared => {
270                next_frame = true;
271            }
272            Event::WindowEvent { ref event, .. } => match event {
273                WindowEvent::CloseRequested => {
274                    if !self.intercept_close_request {
275                        running = false;
276                    }
277                }
278                WindowEvent::Resized(size) => {
279                    // Fixed for Windows which minimized to emit a Resized(0,0) event
280                    if size.width != 0 && size.height != 0 {
281                        self.window.window().resize(*size);
282                    }
283                }
284                WindowEvent::ModifiersChanged(new_state) => {
285                    self.modifiers_state = *new_state;
286                }
287                WindowEvent::KeyboardInput { input, .. } => {
288                    // issue tracked in https://github.com/tomaka/winit/issues/41
289                    // Right now we handle it manually.
290                    if cfg!(target_os = "macos") {
291                        if let Some(keycode) = input.virtual_keycode {
292                            if keycode == VirtualKeyCode::Q && self.modifiers_state.logo() {
293                                running = false;
294                            }
295                        }
296                    }
297                }
298                WindowEvent::DroppedFile(ref path) => {
299                    let filepath = path.to_str().unwrap();
300                    self.dropped_files.push(FileSystem::open(filepath).unwrap());
301                }
302                _ => (),
303            },
304            _ => (),
305        };
306
307        if let Some(app_event) = translate_event(event, &self.modifiers_state) {
308            self.events.borrow_mut().push(app_event);
309        }
310
311        (running, next_frame)
312    }
313
314    pub fn get_dropped_file(&mut self) -> Option<File> {
315        self.dropped_files.pop()
316    }
317
318    /// start the game loop, calling provided callback every frame
319    pub fn run<'a, F>(mut self, mut callback: F)
320    where
321        F: 'static + FnMut(&mut Self) -> (),
322    {
323        let events_loop = self.events_loop.take().unwrap();
324        events_loop.run(move |event, _, control_flow| {
325            control_flow.set_poll();
326            let (running, next_frame) = self.handle_event(event);
327            if !running {
328                control_flow.set_exit();
329            }
330            if next_frame {
331                callback(&mut self);
332                self.events.borrow_mut().clear();
333                self.window.swap_buffers().unwrap();
334            }
335        });
336    }
337}
338
339/// return the time since the start of the program in seconds
340pub fn now() -> f64 {
341    // precise_time_s() is in second
342    // https://doc.rust-lang.org/time/time/fn.precise_time_s.html
343    time::precise_time_s()
344}
345