gooey/presentation/opengl/
mod.rs

1//! OpenGL backend
2
3use std;
4use std::convert::TryFrom;
5use std::time;
6use log;
7
8use nsys::{self, gl, math::Vector2};
9use nsys::gl::glium::{self, glutin};
10use nsys::gl::winit;
11
12use crate::prelude::*;
13use super::{Graphics, Presentation};
14
15pub mod remote;
16pub mod render;
17
18pub use self::remote::Remote;
19
20pub const RESOURCE_TEXTURES_16X16   : u16 = 0;
21pub const RESOURCE_TEXTURES_64X64   : u16 = 1;
22pub const RESOURCE_TEXTURES_ANYSIZE : u16 = 2;
23
24pub const POINTER_TEXTURE_INDEX : u16 = 0;
25
26/// An OpenGL presentation backend
27pub struct Opengl {
28  pub draw_crosshair : bool,
29  pub inner     : (winit::event_loop::EventLoop <()>, gl::Render),
30  input_state   : InputState,
31  tileset_id    : Option <gl::render::resource::DefaultTilesetId>,
32  glium_frame   : Option <glium::Frame>,
33  frame         : u64,
34  screen_tiles  : Option <NodeId>
35}
36
37pub (crate) struct InputState {
38  pub dimensions         : view::dimensions::Pixel,
39  // winit doesn't include modifiers state in key or mouse events so we have to
40  // track it
41  pub modifiers          : winit::keyboard::ModifiersState,
42  // due to the implementation of winit, CursorMoved events are not generated on
43  // windows when the mouse is hidden and grabbed so we need to create a virtual
44  // pointer position and update it based on device MouseMotion events
45  pub pointer_hv         : (f32, f32),
46  pub pointer_sensitivity : f32
47}
48
49pub (crate) struct InputHandler <'a> {
50  pub input_buffer : &'a mut Vec <view::Input>,
51  pub input_state  : &'a mut InputState,
52}
53
54/// Log glium info
55pub fn log_glium_info (
56  glium_display : &glium::Display <glutin::surface::WindowSurface>
57) {
58  let glium_info = gl::info::Glium::new (glium_display);
59  log::info!("{:#?}", glium_info);
60}
61
62impl Opengl {
63  #[inline]
64  pub fn inner (&self) -> &(winit::event_loop::EventLoop <()>, gl::Render) {
65    &self.inner
66  }
67  #[inline]
68  pub fn inner_mut (&mut self)
69    -> &mut (winit::event_loop::EventLoop <()>, gl::Render)
70  {
71    &mut self.inner
72  }
73  #[inline]
74  pub fn frame (&self) -> u64 {
75    self.frame
76  }
77  #[inline]
78  pub fn screen_tiles_id (&self) -> &NodeId {
79    self.screen_tiles.as_ref().unwrap()
80  }
81  #[inline]
82  pub fn dimensions (&self) -> view::dimensions::Pixel {
83    let inner_size : (u32, u32) =
84      self.inner.1.window.inner_size().into();
85    Vector2::from (inner_size).into()
86  }
87  pub fn pointer_sentivity (&self) -> f32 {
88    self.input_state.pointer_sensitivity
89  }
90  pub fn set_pointer_sensitivity (&mut self, pointer_sensitivity : f32) {
91    self.input_state.pointer_sensitivity = pointer_sensitivity
92  }
93  pub fn load_pointer (&mut self,
94    key    : gl::render::resource::PointerTextureIndexRepr,
95    bytes  : &[u8],
96    offset : Vector2 <i16>
97  ) {
98    let render  = &mut self.inner_mut().1;
99    render::load_pointer (render, key, bytes, offset);
100  }
101  pub fn load_textures (&mut self,
102    textures_16x16   : &[&'static str],
103    textures_64x64   : &[&'static str],
104    textures_anysize : &[&'static str]
105  ) {
106    render::load_textures (
107      &mut self.inner.1, textures_16x16, textures_64x64, textures_anysize)
108  }
109}
110
111impl std::fmt::Debug for Opengl {
112  fn fmt (&self, f : &mut std::fmt::Formatter) -> Result <(), std::fmt::Error> {
113    write!(f, "Opengl {{ inner: ({:?}, Render), frame: {} }}",
114      self.inner.0, self.frame)
115  }
116}
117
118impl Default for Opengl {
119  fn default() -> Self {
120    // TODO: tileset parameter ?
121    let tileset_id =
122      Some (gl::render::resource::DefaultTilesetId::EasciiAcorn128);
123    let input_state = InputState::new();
124    let inner = {
125      let (event_loop, window, _config, display) =
126        gl::init::glium_init_gl33core ("Gooey Opengl Window");
127      // hide & grab cursor
128      window.set_cursor_visible (false);
129      window.set_cursor_grab (winit::window::CursorGrabMode::Confined).unwrap();
130      let render =
131        gl::Render::<gl::render::resource::Default>::new (display, window);
132      // set the tile dimensions
133      let [tile_width, tile_height] =
134        render.resource.tile_dimensions (tileset_id.unwrap_or_default());
135      unsafe {
136        std::env::set_var ("GOOEY_TILE_WIDTH", tile_width.to_string());
137        std::env::set_var ("GOOEY_TILE_HEIGHT", tile_height.to_string());
138      }
139      (event_loop, render)
140    };
141    Opengl {
142      inner, input_state, tileset_id,
143      glium_frame:    None,
144      frame:          0,
145      screen_tiles:   None,
146      draw_crosshair: false
147    }
148  }
149}
150
151impl Graphics     for Opengl { }
152impl Presentation for Opengl {
153  /// Expects a root Canvas component
154  fn with_root (root : View, _id : NodeId) -> Self {
155    use gl::render::resource::{draw2d, MAIN_VIEWPORT};
156    let mut opengl = Self::default();
157    {
158      let draw_crosshair = opengl.draw_crosshair;
159      let render = &mut opengl.inner_mut().1;
160      render::set_screen_view (render, &root);
161      let resources = draw2d::ViewportResources {
162        draw_crosshair, .. Default::default()
163      };
164      render.resource.draw2d.viewport_resources_set (MAIN_VIEWPORT, resources);
165    }
166    log::debug!("opengl with root: {:?}", opengl);
167    opengl
168  }
169
170  /// Create a new interface with a root screen pixel frame element and child
171  /// screen tile frame element
172  fn make_interface <A : Application> () -> Interface <A, Self> {
173    let screen = {
174      // create a root screen frame and rely on the resize control function to
175      // update the dimensions
176      let mut screen = frame::screen::PixelBuilder::<A>::new()
177        .anchor (Alignment::pixel())
178        .build_element();
179      // NOTE: viewport width/height must be non-zero
180      let canvas = Canvas::try_ref_mut (&mut screen.view.component).unwrap();
181      canvas.coordinates.modify_dimensions_horizontal (1);
182      canvas.coordinates.modify_dimensions_vertical (1);
183      screen
184    };
185    // create interface with screen root
186    let mut interface = Interface::<A, Opengl>::with_root (screen);
187    // create child tile frame
188    let screen_id     = interface.root_id().clone();
189    let screen_tiles  = frame::free::Builder::<A>::new (
190      interface.elements(), &screen_id
191    ) .layout (layout::Free {
192        size: Size::fill(), .. layout::Free::default_tile()
193      }.into())
194      .clear_color (canvas::ClearColor::Fixed (None))
195      .coord_kind_override (coordinates::Kind::Tile)
196      .build_element();
197    let tiles_id =
198      match interface.action (&screen_id,
199        Action::create_singleton (screen_tiles, CreateOrder::Append)
200      ).next().unwrap() {
201        (_, Event::Create (_, id, _)) => id,
202        _ => unreachable!()
203      };
204    interface.presentation.screen_tiles = Some (tiles_id);
205    interface
206  }
207
208  fn get_input (&mut self, input_buffer : &mut Vec <view::Input>) {
209    use winit::platform::pump_events::EventLoopExtPumpEvents;
210    log::trace!("get input...");
211    // TODO: we may want to ignore some extra winit events, e.g. window axis
212    // motion events or keyboard device events on linux
213    self.inner.0.pump_app_events (
214      Some (time::Duration::ZERO),
215      &mut InputHandler { input_buffer, input_state: &mut self.input_state });
216    log::trace!("...get input");
217  }
218
219  fn display_view <V : AsRef <View>> (&mut self,
220    view_tree      : &Tree <V>,
221    _display_values : std::vec::Drain <(NodeId, view::Display)>
222  ) {
223    // update the renderer with the current view
224    let tileset_id     = self.tileset_id;
225    let draw_crosshair = self.draw_crosshair;
226    render::update (&mut self.inner_mut().1, tileset_id, view_tree,
227      draw_crosshair);
228    // render the frame
229    self.inner.1.do_frame (self.glium_frame.as_mut());
230    self.frame += 1;
231    log::trace!("...display view");
232  }
233}
234
235impl <'a> InputHandler <'a> {
236  fn handle_event (&mut self, event : winit::event::Event <()>) {
237    match self.input_state.try_convert_winit_event (event) {
238      Ok (input) => {
239        log::debug!("input: {:?}", input);
240        self.input_buffer.extend (input)
241      }
242      Err (event) => log::trace!("ignored winit event: {:?}", event)
243    }
244  }
245}
246
247impl InputState {
248  fn new() -> Self {
249    InputState {
250      dimensions:          Default::default(),
251      modifiers:           Default::default(),
252      // pointer is initialized to (-1,-1) until the first resize event is
253      // received at which point it will be moved to the center of the screen
254      pointer_hv:          (-1.0, -1.0),
255      pointer_sensitivity: 1.0
256    }
257  }
258  // NOTE: we possibly return multiple input events because winit no longer
259  // emits separate "text" events, so a key event can generate both a button
260  // event and a text event
261  fn try_convert_winit_event <T> (&mut self, event : winit::event::Event <T>)
262    -> Result <Vec <view::Input>, winit::event::Event <T>>
263    where T : std::fmt::Debug
264  {
265    use winit::event::{self, Event};
266    use view::input;
267    log::trace!("winit event: {:?}", event);
268    let winit_event = event; // rename to prevent shadowing
269    let input = match &winit_event {
270      Event::NewEvents (_) => return Err (winit_event),
271      Event::WindowEvent { event, .. } => match event {
272        event::WindowEvent::ModifiersChanged (modifiers) => {
273          self.modifiers = modifiers.state();
274          return Err (winit_event)
275        }
276        event::WindowEvent::AxisMotion {..}    => return Err (winit_event),
277        event::WindowEvent::CloseRequested     => input::System::Close.into(),
278        event::WindowEvent::CursorEntered {..} => input::System::CursorEntered
279          .into(),
280        event::WindowEvent::CursorLeft {..}    => input::System::CursorLeft
281          .into(),
282        // NOTE: due to the implementation of winit, CursorMoved events are not
283        // generated on windows when the mouse is hidden and grabbed so we need
284        // to create a virtual pointer position and update it based on device
285        // MouseMotion events
286        event::WindowEvent::CursorMoved {..}   => return Err (winit_event),
287        event::WindowEvent::RedrawRequested => input::System::Redraw.into(),
288        event::WindowEvent::Destroyed => input::System::Destroyed.into(),
289        event::WindowEvent::Focused (focused) =>
290          input::System::Focused (*focused).into(),
291        event::WindowEvent::KeyboardInput { event, is_synthetic: false, .. } => {
292          let input = (event, self.modifiers).into();
293          if event.state == event::ElementState::Pressed {
294            if let Some (mut text) = event.text.clone()
295              .map (|text| text.to_string())
296            {
297              let text_input = if text.len() > 1 {
298                input::Text::String (text)
299              } else {
300                debug_assert_eq!(text.len(), 1);
301                input::Text::Char (text.pop().unwrap())
302              }.into();
303              return Ok (vec![input, text_input])
304            }
305          }
306          input
307        }
308        event::WindowEvent::KeyboardInput { is_synthetic: true, .. } =>
309          return Err (winit_event),
310        // NOTE: allowing use of deprecated modifiers until winit issue #1824 is
311        // resolved
312        #[allow(deprecated)]
313        event::WindowEvent::MouseInput { button, state, .. } =>
314          mouse_button (*button, self.modifiers, *state),
315        // NOTE: allowing use of deprecated modifiers until winit issue #1824 is
316        // resolved
317        #[allow(deprecated)]
318        event::WindowEvent::MouseWheel { delta, .. } =>
319          mouse_wheel (*delta, self.modifiers),
320        event::WindowEvent::Resized (logical_size) => {
321          // TODO: convert logical coordinates to physical coordinates ?
322          let (width, height) = (*logical_size).into();
323          let mut out = vec![input::System::Resized { width, height }.into()];
324          self.dimensions =
325            view::coordinates::dimensions::Pixel::new_wh (width, height);
326          if self.pointer_hv == (-1.0, -1.0) {
327            self.pointer_hv.0 = width  as f32 / 2.0;
328            self.pointer_hv.1 = height as f32 / 2.0;
329            out.push (self.pointer().into());
330          } else {
331            self.clip_pointer().map (|pointer| out.push (pointer.into()));
332          }
333          return Ok (out)
334        }
335        // "input method event" see winit documentation
336        event::WindowEvent::Ime (_) =>
337          input::System::InputMethod (/*TODO*/).into(),
338        event::WindowEvent::Occluded (occluded) =>
339          input::System::Occluded (*occluded).into(),
340        event::WindowEvent::Moved (_) => return Err (winit_event),
341        _ => {
342          log::error!("TODO: unhandled glutin window event: {:?}", event);
343          unimplemented!("TODO: unhandled glutin window event: {:?}", event)
344        }
345      }
346      Event::DeviceEvent { event, .. } => match event {
347        // TODO: joystick/gamepad buttons? note this also returns mouse buttons
348        event::DeviceEvent::Button {..} => return Err (winit_event),
349        event::DeviceEvent::Key (_)     => return Err (winit_event),
350        // TODO: always round away from zero ?
351        event::DeviceEvent::Motion { axis, value } =>
352          input::Axis { axis: *axis, value: value.round() as i32 }.into(),
353        // TODO: always round away from zero ?
354        event::DeviceEvent::MouseMotion { delta: (x, y) } => {
355          let motion = input::Motion (x.round() as i32, y.round() as i32).into();
356          let pointer = {
357            self.pointer_hv.0 += (x.abs().powf (self.pointer_sensitivity as f64)
358              * x.signum()) as f32;
359            self.pointer_hv.1 -= (y.abs().powf (self.pointer_sensitivity as f64)
360              * y.signum()) as f32;
361            self.clip_pointer().unwrap_or_else (|| self.pointer()).into()
362          };
363          return Ok (vec![motion, pointer])
364        }
365        event::DeviceEvent::MouseWheel {..} => return Err (winit_event),
366        event::DeviceEvent::Added {..} => return Err (winit_event),
367        _ => {
368          log::error!("TODO: unhandled glutin device event: {:?}", event);
369          unimplemented!("TODO: unhandled glutin device event: {:?}", event)
370        }
371      }
372      // NOTE: resume is spammed so we are not handling it for now
373      Event::Resumed     => return Err (winit_event),
374      Event::AboutToWait => return Err (winit_event),
375      _ => {
376        log::error!("TODO: unhandled winit event: {:?}", winit_event);
377        unimplemented!("TODO: unhandld winit event: {:?}", winit_event)
378      }
379    };
380    Ok (vec![input])
381  }
382
383  fn pointer (&self) -> view::input::Pointer {
384    view::input::Pointer {
385      position_horizontal: self.pointer_hv.0,
386      position_vertical:   self.pointer_hv.1,
387      modifiers:           self.modifiers.into()
388    }
389  }
390
391  /// Return a pointer input event if position was clipped
392  fn clip_pointer (&mut self) -> Option <view::input::Pointer> {
393    let hv_before = self.pointer_hv;
394    self.pointer_hv.0 = f32::min (self.pointer_hv.0, self.dimensions.width()
395      as f32);
396    self.pointer_hv.0 = f32::max (self.pointer_hv.0, 0.0);
397    self.pointer_hv.1 = f32::min (self.pointer_hv.1, self.dimensions.height()
398      as f32);
399    self.pointer_hv.1 = f32::max (self.pointer_hv.1, 0.0);
400    if hv_before != self.pointer_hv {
401      Some (self.pointer())
402    } else {
403      None
404    }
405  }
406}
407
408impl <'a> winit::application::ApplicationHandler for InputHandler <'a> {
409  // required
410  fn resumed (&mut self, _event_loop : &winit::event_loop::ActiveEventLoop) {
411    self.handle_event (winit::event::Event::Resumed)
412  }
413  fn window_event (&mut self,
414    _event_loop : &winit::event_loop::ActiveEventLoop,
415    window_id   : winit::window::WindowId,
416    event       : winit::event::WindowEvent
417  ) {
418    self.handle_event (winit::event::Event::WindowEvent { window_id, event })
419  }
420  // optional
421  fn new_events (&mut self,
422    _event_loop : &winit::event_loop::ActiveEventLoop,
423    cause       : winit::event::StartCause
424  ) {
425    self.handle_event (winit::event::Event::NewEvents (cause))
426  }
427  fn user_event (&mut self,
428    _event_loop : &winit::event_loop::ActiveEventLoop,
429    event       : ()
430  ) {
431    self.handle_event (winit::event::Event::UserEvent (event))
432  }
433  fn device_event (&mut self,
434    _event_loop : &winit::event_loop::ActiveEventLoop,
435    device_id   : winit::event::DeviceId,
436    event       : winit::event::DeviceEvent
437  ) {
438    self.handle_event (winit::event::Event::DeviceEvent { device_id, event })
439  }
440  fn about_to_wait (&mut self,
441    _event_loop : &winit::event_loop::ActiveEventLoop
442  ) {
443    self.handle_event (winit::event::Event::AboutToWait)
444  }
445  fn suspended (&mut self,
446    _event_loop : &winit::event_loop::ActiveEventLoop
447  ) {
448    self.handle_event (winit::event::Event::Suspended)
449  }
450  fn exiting (&mut self, _event_loop : &winit::event_loop::ActiveEventLoop) {
451    self.handle_event (winit::event::Event::LoopExiting)
452  }
453  fn memory_warning (&mut self,
454    _event_loop : &winit::event_loop::ActiveEventLoop
455  ) {
456    self.handle_event (winit::event::Event::MemoryWarning)
457  }
458}
459
460impl From <winit::event::MouseButton> for view::input::button::Mouse {
461  fn from (button : winit::event::MouseButton) -> view::input::button::Mouse {
462    use winit::event::MouseButton;
463    use view::input::button::Mouse;
464    match button {
465      MouseButton::Left      => Mouse::Mouse1,
466      MouseButton::Right     => Mouse::Mouse2,
467      MouseButton::Middle    => Mouse::Mouse3,
468      MouseButton::Other (4) => Mouse::Mouse4,
469      MouseButton::Other (5) => Mouse::Mouse5,
470      button => {
471        log::error!("unhandled glutin mouse button: {:?}", button);
472        unimplemented!()
473      }
474    }
475  }
476}
477
478impl From <(&winit::event::KeyEvent, winit::keyboard::ModifiersState)>
479  for view::Input
480{
481  fn from (
482    (input, modifiers) :
483    (&winit::event::KeyEvent, winit::keyboard::ModifiersState)
484  ) -> view::Input {
485    use std::str::FromStr;
486    use view::{input, Input};
487    use winit::keyboard;
488    let state  = input.state.into();
489    let button = {
490      let variant = match &input.logical_key {
491        keyboard::Key::Named (named_key) =>
492          input::button::Keycode::from ((*named_key, input.location)),
493        keyboard::Key::Character (ch) => input::button::Keycode::try_from (
494          char::from_str (ch.as_str()).unwrap()
495        ).unwrap(),
496        //  input::button::Keycode::from_scancode (input.scancode),
497        keyboard::Key::Unidentified (k) =>
498          unimplemented!("TODO: unidentified key: {:?}", k),
499        keyboard::Key::Dead (k) =>
500          unimplemented!("TODO: dead key: {:?}", k)
501      }.into();
502      input::Button { variant, modifiers: modifiers.into() }
503    };
504    Input::Button (button, state)
505  }
506}
507
508impl From <winit::event::ElementState> for view::input::button::State {
509  fn from (state : winit::event::ElementState) -> Self {
510    use winit::event::ElementState;
511    use view::input::button::State;
512    match state {
513      ElementState::Pressed  => State::Pressed,
514      ElementState::Released => State::Released
515    }
516  }
517}
518
519impl From <winit::keyboard::ModifiersState> for view::input::Modifiers {
520  fn from (modifiers : winit::keyboard::ModifiersState) -> Self {
521    use view::input::Modifiers;
522    let mut out = Modifiers::empty();
523    out.set (Modifiers::SHIFT, modifiers.shift_key());
524    out.set (Modifiers::CTRL,  modifiers.control_key());
525    out.set (Modifiers::ALT,   modifiers.alt_key());
526    out.set (Modifiers::SUPER, modifiers.super_key());
527    out
528  }
529}
530
531/// Convert a back-end mouse+modifier+element state event into an interface
532/// button input
533fn mouse_button (
534  button    : winit::event::MouseButton,
535  modifiers : winit::keyboard::ModifiersState,
536  state     : winit::event::ElementState
537) -> view::Input {
538  let state  = state.into();
539  let button = {
540    let variant   = view::input::button::Mouse::from (button).into();
541    let modifiers = modifiers.into();
542    view::input::Button { variant, modifiers }
543  };
544  view::Input::Button (button, state)
545}
546
547/// Convert a back-end mouse wheel delta+modifier event into an interface wheel
548/// input
549fn mouse_wheel (
550  delta     : winit::event::MouseScrollDelta,
551  modifiers : winit::keyboard::ModifiersState
552) -> view::Input {
553  let delta = match delta {
554    winit::event::MouseScrollDelta::LineDelta (x, y)
555      => (x.round() as i32, y.round() as i32),
556    winit::event::MouseScrollDelta::PixelDelta (
557      winit::dpi::PhysicalPosition { x, y }
558    ) => (x.round() as i32, y.round() as i32)
559  };
560  let modifiers = modifiers.into();
561  view::input::Wheel { delta, modifiers }.into()
562}