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
68pub 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 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 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 pub fn get_params() -> Vec<String> {
222 let mut params: Vec<String> = env::args().collect();
223 params.remove(0);
224 params
225 }
226
227 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 pub fn print<T: Into<String>>(msg: T) {
242 println!("{}", msg.into());
243 }
244
245 pub fn exit() {
247 process::exit(0);
248 }
249
250 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 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 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 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 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
339pub fn now() -> f64 {
341 time::precise_time_s()
344}
345