1#![deny(missing_docs)]
2extern crate gl;
5extern crate input;
6extern crate sdl2;
7extern crate shader_version;
8extern crate window;
9
10use input::HatState as PistonHat;
12use input::{
13 keyboard, Button, ButtonArgs, ButtonState, CloseArgs, ControllerAxisArgs, ControllerButton,
14 ControllerHat, Event, Input, Motion, MouseButton, ResizeArgs, TimeStamp, Touch, TouchArgs,
15};
16use sdl2::joystick::HatState;
17use window::{
18 AdvancedWindow, Api, BuildFromWindowSettings, OpenGLWindow, Position, ProcAddress, Size,
19 UnsupportedGraphicsApiError, Window, WindowSettings,
20};
21
22use std::error::Error;
23use std::time::Duration;
24use std::vec::Vec;
25
26pub use shader_version::OpenGL;
27
28struct JoystickState {
29 joysticks: Vec<sdl2::joystick::Joystick>,
30 subsystem: sdl2::JoystickSubsystem,
31}
32
33impl JoystickState {
34 fn new(subsystem: sdl2::JoystickSubsystem) -> Self {
35 JoystickState {
36 joysticks: Vec::new(),
37 subsystem,
38 }
39 }
40}
41
42pub struct Sdl2Window {
44 pub window: sdl2::video::Window,
46 #[allow(dead_code)]
49 pub context: sdl2::video::GLContext,
50 pub sdl_context: sdl2::Sdl,
52 pub video_subsystem: sdl2::VideoSubsystem,
54 joystick_state: Option<JoystickState>,
55 should_close: bool,
56 automatic_close: bool,
57 mouse_relative: Option<(f64, f64, TimeStamp)>,
59 is_capturing_cursor: bool,
61 ignore_relative_event: Option<(i32, i32)>,
64 exit_on_esc: bool,
65 title: String,
66}
67
68impl Sdl2Window {
69 pub fn new(settings: &WindowSettings) -> Result<Self, Box<dyn Error>> {
72 let sdl = sdl2::init()?;
73 let video_subsystem = sdl.video()?;
74 Self::with_subsystem(video_subsystem, settings)
75 }
76
77 pub fn with_subsystem(
79 video_subsystem: sdl2::VideoSubsystem,
80 settings: &WindowSettings,
81 ) -> Result<Self, Box<dyn Error>> {
82 use sdl2::video::GLProfile;
83
84 let sdl_context = video_subsystem.sdl();
85 let api = settings
86 .get_maybe_graphics_api()
87 .unwrap_or(Api::opengl(3, 2));
88 if api.api != "OpenGL" {
89 return Err(UnsupportedGraphicsApiError {
90 found: api.api,
91 expected: vec!["OpenGL".into()],
92 }
93 .into());
94 }
95
96 {
97 let gl_attr = video_subsystem.gl_attr();
98
99 gl_attr.set_red_size(8);
101 gl_attr.set_green_size(8);
102 gl_attr.set_blue_size(8);
103 gl_attr.set_alpha_size(8);
104 gl_attr.set_stencil_size(8);
105 gl_attr.set_context_version(api.major as u8, api.minor as u8);
106 gl_attr.set_framebuffer_srgb_compatible(settings.get_srgb());
107 }
108
109 if api >= Api::opengl(3, 2) {
110 video_subsystem
111 .gl_attr()
112 .set_context_profile(GLProfile::Core);
113 }
114 if settings.get_samples() != 0 {
115 let gl_attr = video_subsystem.gl_attr();
116 gl_attr.set_multisample_buffers(1);
117 gl_attr.set_multisample_samples(settings.get_samples());
118 }
119
120 let mut window_builder = video_subsystem.window(
121 &settings.get_title(),
122 settings.get_size().width as u32,
123 settings.get_size().height as u32,
124 );
125
126 let window_builder = window_builder.position_centered().opengl();
127
128 let window_builder = if settings.get_resizable() {
129 window_builder.resizable()
130 } else {
131 window_builder
132 };
133
134 let window_builder = if settings.get_decorated() {
135 window_builder
136 } else {
137 window_builder.borderless()
138 };
139
140 let window_builder = if settings.get_fullscreen() {
141 window_builder.fullscreen()
142 } else {
143 window_builder
144 };
145
146 let window = window_builder.build();
147
148 let window = match window {
149 Ok(w) => w,
150 Err(_) => {
151 if settings.get_samples() != 0 {
152 let gl_attr = video_subsystem.gl_attr();
154 gl_attr.set_multisample_buffers(0);
155 gl_attr.set_multisample_samples(0);
156 window_builder.build().map_err(|e| format!("{}", e))?
157 } else {
158 window.map_err(|e| format!("{}", e))?
159 }
160 }
161 };
162
163 video_subsystem.text_input().start();
165
166 let context = window.gl_create_context()?;
167
168 gl::load_with(|name| video_subsystem.gl_get_proc_address(name) as *const _);
170
171 if settings.get_vsync() {
172 video_subsystem.gl_set_swap_interval(1)?;
173 } else {
174 video_subsystem.gl_set_swap_interval(0)?;
175 }
176
177 let mut window = Sdl2Window {
178 exit_on_esc: settings.get_exit_on_esc(),
179 should_close: false,
180 automatic_close: settings.get_automatic_close(),
181 is_capturing_cursor: false,
182 ignore_relative_event: None,
183 window,
184 context,
185 sdl_context,
186 video_subsystem,
187 joystick_state: None,
188 mouse_relative: None,
189 title: settings.get_title(),
190 };
191 if settings.get_controllers() {
192 window.init_joysticks()?;
193 }
194 if settings.get_transparent() {
195 let _ = window.window.set_opacity(0.0);
196 }
197 Ok(window)
198 }
199
200 pub fn init_joysticks(&mut self) -> Result<u32, String> {
203 let subsystem = self.sdl_context.joystick()?;
204 let mut state = JoystickState::new(subsystem);
205 let available = state.subsystem.num_joysticks()?;
206
207 for id in 0..available {
209 match state.subsystem.open(id) {
210 Ok(c) => state.joysticks.push(c),
211 Err(e) => return Err(format!("{}", e)),
212 }
213 }
214
215 self.joystick_state = Some(state);
216
217 Ok(available)
218 }
219
220 fn wait_event(&mut self) -> Event {
221 loop {
222 if let Some(event) = self.check_pending_event() {
223 return event;
224 };
225 let sdl_event = self.sdl_context.event_pump().unwrap().wait_event();
226 let mut unknown = false;
227 if let Some(event) = self.handle_event(Some(sdl_event), &mut unknown) {
228 return event;
229 }
230 }
231 }
232
233 fn wait_event_timeout(&mut self, timeout: Duration) -> Option<Event> {
234 let event = self.check_pending_event();
235 if event.is_some() {
236 return event;
237 };
238
239 let timeout_ms = timeout.as_millis() as u32;
240 let sdl_event = self
241 .sdl_context
242 .event_pump()
243 .unwrap()
244 .wait_event_timeout(timeout_ms);
245
246 let mut unknown = false;
247 let event = self.handle_event(sdl_event, &mut unknown);
248 if unknown {
249 self.poll_event()
250 } else {
251 event
252 }
253 }
254
255 fn poll_event(&mut self) -> Option<Event> {
256 loop {
258 let event = self.check_pending_event();
259 if event.is_some() {
260 return event;
261 };
262
263 let sdl_event = self.sdl_context.event_pump().unwrap().poll_event();
267 let mut unknown = false;
268 let event = self.handle_event(sdl_event, &mut unknown);
269 if unknown {
270 continue;
271 };
272 return event;
273 }
274 }
275
276 fn check_pending_event(&mut self) -> Option<Event> {
277 if let Some((x, y, timestamp)) = self.mouse_relative {
279 self.mouse_relative = None;
280 return Some(input::Event::Input(
281 Input::Move(Motion::MouseRelative([x, y])),
282 Some(timestamp),
283 ));
284 }
285 None
286 }
287
288 fn handle_event(
289 &mut self,
290 sdl_event: Option<sdl2::event::Event>,
291 unknown: &mut bool,
292 ) -> Option<Event> {
293 use sdl2::event::{Event, WindowEvent};
294 let event = match sdl_event {
295 Some(ev) => {
296 if let Event::MouseMotion { xrel, yrel, .. } = ev {
297 if Some((xrel, yrel)) == self.ignore_relative_event {
301 self.ignore_relative_event = None;
302 return None;
303 }
304 }
305 ev
306 }
307 None => {
308 if self.is_capturing_cursor {
311 self.fake_capture();
312 }
313 return None;
314 }
315 };
316 match event {
317 Event::Quit { timestamp, .. } => {
318 if self.automatic_close {
319 self.should_close = true;
320 }
321 return Some(input::Event::Input(
322 Input::Close(CloseArgs),
323 Some(timestamp),
324 ));
325 }
326 Event::TextInput {
327 text, timestamp, ..
328 } => {
329 return Some(input::Event::Input(Input::Text(text), Some(timestamp)));
330 }
331 Event::KeyDown {
332 keycode: Some(key),
333 repeat,
334 scancode,
335 timestamp,
336 ..
337 } => {
338 if repeat {
341 return self.poll_event();
342 }
343
344 if self.exit_on_esc && key == sdl2::keyboard::Keycode::Escape {
345 self.should_close = true;
346 } else {
347 return Some(input::Event::Input(
348 Input::Button(ButtonArgs {
349 state: ButtonState::Press,
350 button: Button::Keyboard(sdl2_map_key(key)),
351 scancode: scancode.map(|scode| scode as i32),
352 }),
353 Some(timestamp),
354 ));
355 }
356 }
357 Event::KeyUp {
358 keycode: Some(key),
359 repeat,
360 scancode,
361 timestamp,
362 ..
363 } => {
364 if repeat {
365 return self.poll_event();
366 }
367 return Some(input::Event::Input(
368 Input::Button(ButtonArgs {
369 state: ButtonState::Release,
370 button: Button::Keyboard(sdl2_map_key(key)),
371 scancode: scancode.map(|scode| scode as i32),
372 }),
373 Some(timestamp),
374 ));
375 }
376 Event::MouseButtonDown {
377 mouse_btn: button,
378 timestamp,
379 ..
380 } => {
381 return Some(input::Event::Input(
382 Input::Button(ButtonArgs {
383 state: ButtonState::Press,
384 button: Button::Mouse(sdl2_map_mouse(button)),
385 scancode: None,
386 }),
387 Some(timestamp),
388 ));
389 }
390 Event::MouseButtonUp {
391 mouse_btn: button,
392 timestamp,
393 ..
394 } => {
395 return Some(input::Event::Input(
396 Input::Button(ButtonArgs {
397 state: ButtonState::Release,
398 button: Button::Mouse(sdl2_map_mouse(button)),
399 scancode: None,
400 }),
401 Some(timestamp),
402 ));
403 }
404 Event::MouseMotion {
405 x,
406 y,
407 xrel: dx,
408 yrel: dy,
409 timestamp,
410 ..
411 } => {
412 if self.is_capturing_cursor {
413 return Some(input::Event::Input(
415 Input::Move(Motion::MouseRelative([dx as f64, dy as f64])),
416 Some(timestamp),
417 ));
418 }
419 self.mouse_relative = Some((dx as f64, dy as f64, timestamp));
421 return Some(input::Event::Input(
422 Input::Move(Motion::MouseCursor([x as f64, y as f64])),
423 Some(timestamp),
424 ));
425 }
426 Event::MouseWheel {
427 x, y, timestamp, ..
428 } => {
429 return Some(input::Event::Input(
430 Input::Move(Motion::MouseScroll([x as f64, y as f64])),
431 Some(timestamp),
432 ));
433 }
434 Event::JoyAxisMotion {
435 which,
436 axis_idx,
437 value: val,
438 timestamp,
439 ..
440 } => {
441 let normalized_value = val as f64 / (i16::MAX) as f64;
444 return Some(input::Event::Input(
445 Input::Move(Motion::ControllerAxis(ControllerAxisArgs::new(
446 which,
447 axis_idx,
448 normalized_value,
449 ))),
450 Some(timestamp),
451 ));
452 }
453 Event::JoyButtonDown {
454 which,
455 button_idx,
456 timestamp,
457 ..
458 } => {
459 return Some(input::Event::Input(
460 Input::Button(ButtonArgs {
461 state: ButtonState::Press,
462 button: Button::Controller(ControllerButton::new(which, button_idx)),
463 scancode: None,
464 }),
465 Some(timestamp),
466 ))
467 }
468 Event::JoyButtonUp {
469 which,
470 button_idx,
471 timestamp,
472 ..
473 } => {
474 return Some(input::Event::Input(
475 Input::Button(ButtonArgs {
476 state: ButtonState::Release,
477 button: Button::Controller(ControllerButton::new(which, button_idx)),
478 scancode: None,
479 }),
480 Some(timestamp),
481 ))
482 }
483 Event::JoyHatMotion {
484 which,
485 hat_idx,
486 state,
487 timestamp,
488 ..
489 } => {
490 let state = match state {
491 HatState::Centered => PistonHat::Centered,
492 HatState::Up => PistonHat::Up,
493 HatState::Right => PistonHat::Right,
494 HatState::Down => PistonHat::Down,
495 HatState::Left => PistonHat::Left,
496 HatState::RightUp => PistonHat::RightUp,
497 HatState::RightDown => PistonHat::RightDown,
498 HatState::LeftUp => PistonHat::LeftUp,
499 HatState::LeftDown => PistonHat::LeftDown,
500 };
501 return Some(input::Event::Input(
502 Input::Button(ButtonArgs {
503 state: ButtonState::Release,
504 button: Button::Hat(ControllerHat::new(which, hat_idx, state)),
505 scancode: None,
506 }),
507 Some(timestamp),
508 ));
509 }
510 Event::FingerDown {
511 touch_id,
512 finger_id,
513 x,
514 y,
515 pressure,
516 timestamp,
517 ..
518 } => {
519 return Some(input::Event::Input(
520 Input::Move(Motion::Touch(TouchArgs::new(
521 touch_id,
522 finger_id,
523 [x as f64, y as f64],
524 pressure as f64,
525 Touch::Start,
526 ))),
527 Some(timestamp),
528 ))
529 }
530 Event::FingerMotion {
531 touch_id,
532 finger_id,
533 x,
534 y,
535 pressure,
536 timestamp,
537 ..
538 } => {
539 return Some(input::Event::Input(
540 Input::Move(Motion::Touch(TouchArgs::new(
541 touch_id,
542 finger_id,
543 [x as f64, y as f64],
544 pressure as f64,
545 Touch::Move,
546 ))),
547 Some(timestamp),
548 ))
549 }
550 Event::FingerUp {
551 touch_id,
552 finger_id,
553 x,
554 y,
555 pressure,
556 timestamp,
557 ..
558 } => {
559 return Some(input::Event::Input(
560 Input::Move(Motion::Touch(TouchArgs::new(
561 touch_id,
562 finger_id,
563 [x as f64, y as f64],
564 pressure as f64,
565 Touch::End,
566 ))),
567 Some(timestamp),
568 ))
569 }
570 Event::Window {
571 win_event: sdl2::event::WindowEvent::Resized(w, h),
572 timestamp,
573 ..
574 } => {
575 let draw_size = self.draw_size();
576 return Some(input::Event::Input(
577 Input::Resize(ResizeArgs {
578 window_size: [w as f64, h as f64],
579 draw_size: draw_size.into(),
580 }),
581 Some(timestamp),
582 ));
583 }
584 Event::Window {
585 win_event: WindowEvent::FocusGained,
586 timestamp,
587 ..
588 } => {
589 return Some(input::Event::Input(Input::Focus(true), Some(timestamp)));
590 }
591 Event::Window {
592 win_event: WindowEvent::FocusLost,
593 timestamp,
594 ..
595 } => {
596 return Some(input::Event::Input(Input::Focus(false), Some(timestamp)));
597 }
598 Event::Window {
599 win_event: WindowEvent::Enter,
600 timestamp,
601 ..
602 } => {
603 return Some(input::Event::Input(Input::Cursor(true), Some(timestamp)));
604 }
605 Event::Window {
606 win_event: WindowEvent::Leave,
607 timestamp,
608 ..
609 } => {
610 return Some(input::Event::Input(Input::Cursor(false), Some(timestamp)));
611 }
612 _ => {
613 *unknown = true;
614 return None;
615 }
616 }
617 None
618 }
619
620 fn fake_capture(&mut self) {
621 let (w, h) = self.window.size();
623 let cx = (w / 2) as i32;
624 let cy = (h / 2) as i32;
625 let s = self.sdl_context.event_pump().unwrap().mouse_state();
626 let dx = cx - s.x();
627 let dy = cy - s.y();
628 if dx != 0 || dy != 0 {
629 self.ignore_relative_event = Some((dx, dy));
630 self.sdl_context
631 .mouse()
632 .warp_mouse_in_window(&self.window, cx, cy);
633 }
634 }
635}
636
637impl BuildFromWindowSettings for Sdl2Window {
638 fn build_from_window_settings(settings: &WindowSettings) -> Result<Self, Box<dyn Error>> {
639 Sdl2Window::new(settings)
640 }
641}
642
643impl Drop for Sdl2Window {
644 fn drop(&mut self) {
645 self.set_capture_cursor(false);
646 }
647}
648
649impl Window for Sdl2Window {
650 fn should_close(&self) -> bool {
651 self.should_close
652 }
653 fn set_should_close(&mut self, value: bool) {
654 self.should_close = value;
655 }
656 fn swap_buffers(&mut self) {
657 self.window.gl_swap_window();
658 }
659 fn size(&self) -> Size {
660 let (w, h) = self.window.size();
661 Size {
662 width: w as f64,
663 height: h as f64,
664 }
665 }
666 fn wait_event(&mut self) -> Event {
667 self.wait_event()
668 }
669 fn wait_event_timeout(&mut self, timeout: Duration) -> Option<Event> {
670 self.wait_event_timeout(timeout)
671 }
672 fn poll_event(&mut self) -> Option<Event> {
673 self.poll_event()
674 }
675 fn draw_size(&self) -> Size {
676 let (w, h) = self.window.drawable_size();
677 Size {
678 width: w as f64,
679 height: h as f64,
680 }
681 }
682}
683
684impl AdvancedWindow for Sdl2Window {
685 fn get_title(&self) -> String {
686 self.title.clone()
687 }
688 fn set_title(&mut self, value: String) {
689 let _ = self.window.set_title(&value);
690 self.title = value
691 }
692 fn get_automatic_close(&self) -> bool {
693 self.automatic_close
694 }
695 fn set_automatic_close(&mut self, value: bool) {
696 self.automatic_close = value;
697 }
698 fn get_exit_on_esc(&self) -> bool {
699 self.exit_on_esc
700 }
701 fn set_exit_on_esc(&mut self, value: bool) {
702 self.exit_on_esc = value;
703 }
704 fn set_capture_cursor(&mut self, value: bool) {
705 self.is_capturing_cursor = value;
710 self.sdl_context.mouse().show_cursor(!value);
711 if value {
712 self.fake_capture();
715 }
716 }
717 fn show(&mut self) {
718 self.window.show();
719 }
720 fn hide(&mut self) {
721 self.window.hide();
722 }
723 fn get_position(&self) -> Option<Position> {
724 let (x, y) = self.window.position();
725 Some(Position { x, y })
726 }
727 fn set_position<P: Into<Position>>(&mut self, pos: P) {
728 use sdl2::video::WindowPos;
729
730 let pos: Position = pos.into();
731 self.window
732 .set_position(WindowPos::Positioned(pos.x), WindowPos::Positioned(pos.y));
733 }
734 fn set_size<S: Into<Size>>(&mut self, size: S) {
735 let size: Size = size.into();
736 let _ = self.window.set_size(size.width as u32, size.height as u32);
737 }
738}
739
740impl OpenGLWindow for Sdl2Window {
741 fn get_proc_address(&mut self, proc_name: &str) -> ProcAddress {
742 self.video_subsystem.gl_get_proc_address(proc_name) as *const _
743 }
744
745 fn is_current(&self) -> bool {
746 self.context.is_current()
747 }
748
749 fn make_current(&mut self) {
750 self.window.gl_make_current(&self.context).unwrap();
751 }
752}
753
754pub fn sdl2_map_key(keycode: sdl2::keyboard::Keycode) -> keyboard::Key {
756 let keycode = keycode.into_i32();
757 use std::convert::TryInto;
758 let keycode: u32 = keycode
759 .try_into()
760 .expect("sdl keycode purely uses positive numbers");
761 keycode.into()
762}
763
764pub fn sdl2_map_mouse(button: sdl2::mouse::MouseButton) -> MouseButton {
766 use sdl2::mouse::MouseButton as MB;
767
768 match button {
769 MB::Left => MouseButton::Left,
770 MB::Right => MouseButton::Right,
771 MB::Middle => MouseButton::Middle,
772 MB::X1 => MouseButton::X1,
773 MB::X2 => MouseButton::X2,
774 MB::Unknown => MouseButton::Unknown,
775 }
776}