use std;
use std::convert::TryFrom;
use std::time;
use log;
use nsys::{self, gl, math::Vector2};
use nsys::gl::glium::{self, glutin};
use nsys::gl::winit;
use crate::prelude::*;
use super::{Graphics, Presentation};
pub mod remote;
pub mod render;
pub use self::remote::Remote;
pub const RESOURCE_TEXTURES_16X16 : u16 = 0;
pub const RESOURCE_TEXTURES_64X64 : u16 = 1;
pub const RESOURCE_TEXTURES_ANYSIZE : u16 = 2;
pub const POINTER_TEXTURE_INDEX : u16 = 0;
pub struct Opengl {
pub draw_crosshair : bool,
pub inner : (winit::event_loop::EventLoop <()>, gl::Render),
input_state : InputState,
tileset_id : Option <gl::render::resource::DefaultTilesetId>,
glium_frame : Option <glium::Frame>,
frame : u64,
screen_tiles : Option <NodeId>
}
pub (crate) struct InputState {
pub dimensions : dimensions::Pixel,
pub modifiers : winit::keyboard::ModifiersState,
pub pointer_hv : (f32, f32),
pub pointer_sensitivity : f32
}
pub (crate) struct InputHandler <'a> {
pub input_buffer : &'a mut Vec <Input>,
pub input_state : &'a mut InputState,
}
pub fn log_glium_info (
glium_display : &glium::Display <glutin::surface::WindowSurface>
) {
let glium_info = gl::info::Glium::new (glium_display);
log::info!("{glium_info:#?}");
}
impl Opengl {
#[inline]
pub const fn inner (&self) -> &(winit::event_loop::EventLoop <()>, gl::Render) {
&self.inner
}
#[inline]
pub const fn inner_mut (&mut self)
-> &mut (winit::event_loop::EventLoop <()>, gl::Render)
{
&mut self.inner
}
#[inline]
pub const fn frame (&self) -> u64 {
self.frame
}
#[inline]
pub const fn screen_tiles_id (&self) -> &NodeId {
self.screen_tiles.as_ref().unwrap()
}
#[inline]
pub fn dimensions (&self) -> dimensions::Pixel {
let inner_size : (u32, u32) =
self.inner.1.window.inner_size().into();
Vector2::from (inner_size).into()
}
pub const fn pointer_sentivity (&self) -> f32 {
self.input_state.pointer_sensitivity
}
pub const fn set_pointer_sensitivity (&mut self, pointer_sensitivity : f32) {
self.input_state.pointer_sensitivity = pointer_sensitivity
}
pub fn load_pointer (&mut self,
key : gl::render::resource::PointerTextureIndexRepr,
bytes : &[u8],
offset : Vector2 <i16>
) {
let render = &mut self.inner_mut().1;
render::load_pointer (render, key, bytes, offset);
}
pub fn load_textures (&mut self,
textures_16x16 : &[&'static str],
textures_64x64 : &[&'static str],
textures_anysize : &[&'static str]
) {
render::load_textures (
&mut self.inner.1, textures_16x16, textures_64x64, textures_anysize)
}
}
impl std::fmt::Debug for Opengl {
fn fmt (&self, f : &mut std::fmt::Formatter) -> Result <(), std::fmt::Error> {
write!(f, "Opengl {{ inner: ({:?}, Render), frame: {} }}",
self.inner.0, self.frame)
}
}
impl Default for Opengl {
fn default() -> Self {
let tileset_id = gl::render::resource::DefaultTilesetId::EasciiAcorn128;
let input_state = InputState::new();
let inner = {
let (event_loop, window, _config, display) =
gl::init::glium_init_gl33core ("Gooey Opengl Window");
window.set_cursor_visible (false);
window.set_cursor_grab (winit::window::CursorGrabMode::Confined).unwrap();
let render = gl::Render::<gl::render::resource::Default>::new (display, window);
let [tile_width, tile_height] = render.resource.tile_dimensions (tileset_id);
unsafe {
std::env::set_var ("GOOEY_TILE_WIDTH", tile_width.to_string());
std::env::set_var ("GOOEY_TILE_HEIGHT", tile_height.to_string());
}
(event_loop, render)
};
Opengl {
inner, input_state,
tileset_id: Some (tileset_id),
glium_frame: None,
frame: 0,
screen_tiles: None,
draw_crosshair: false
}
}
}
impl Graphics for Opengl { }
impl Presentation for Opengl {
fn with_root (root : View, _id : NodeId) -> Self {
use gl::render::resource::{draw2d, MAIN_VIEWPORT};
let mut opengl = Self::default();
{
let draw_crosshair = opengl.draw_crosshair;
let render = &mut opengl.inner_mut().1;
render::set_screen_view (render, &root);
let resources = draw2d::ViewportResources {
draw_crosshair, .. Default::default()
};
render.resource.draw2d.viewport_resources_set (MAIN_VIEWPORT, resources);
}
log::debug!("opengl with root: {opengl:?}");
opengl
}
fn make_interface <A : Application> () -> Interface <A, Self> {
let screen = {
let mut screen = frame::screen::PixelBuilder::<A>::new()
.name ("screen".to_string())
.anchor (Alignment::pixel())
.build_element();
let canvas = Canvas::try_ref_mut (&mut screen.view.component).unwrap();
canvas.coordinates.modify_dimensions_horizontal (1);
canvas.coordinates.modify_dimensions_vertical (1);
screen
};
let mut interface = Interface::<A, Opengl>::with_root (screen);
let screen_id = interface.root_id().clone();
let screen_tiles = frame::free::Builder::<A>::new (interface.elements(), &screen_id)
.name ("screen-tiles".to_string())
.layout (layout::Free { size: Size::fill(), .. layout::Free::default_tile() })
.clear_color (canvas::ClearColor::Fixed (None))
.coord_kind_override (coordinates::Kind::Tile)
.build_element();
let (_, Event::Create (_, tiles_id, _)) = interface
.action (&screen_id, Action::create_singleton (screen_tiles, CreateOrder::Append))
.next().unwrap() else { unreachable!() };
interface.presentation.screen_tiles = Some (tiles_id);
interface
}
fn get_input (&mut self, input_buffer : &mut Vec <Input>) {
use winit::platform::pump_events::EventLoopExtPumpEvents;
log::trace!("get input...");
self.inner.0.pump_app_events (
Some (time::Duration::ZERO),
&mut InputHandler { input_buffer, input_state: &mut self.input_state });
log::trace!("...get input");
}
fn display_view <V : AsRef <View>> (&mut self,
view_tree : &Tree <V>,
_display_values : std::vec::Drain <(NodeId, Display)>
) {
let tileset_id = self.tileset_id;
let draw_crosshair = self.draw_crosshair;
render::update (&mut self.inner_mut().1, tileset_id, view_tree,
draw_crosshair);
self.inner.1.do_frame (self.glium_frame.as_mut());
self.frame += 1;
log::trace!("...display view");
}
}
impl InputHandler <'_> {
fn handle_event (&mut self, event : winit::event::Event <()>) {
match self.input_state.try_convert_winit_event (event) {
Ok (input) => {
log::debug!("input: {input:?}");
self.input_buffer.extend (input)
}
Err (event) => log::trace!("ignored winit event: {event:?}")
}
}
}
impl InputState {
fn new() -> Self {
InputState {
dimensions: Default::default(),
modifiers: Default::default(),
pointer_hv: (-1.0, -1.0),
pointer_sensitivity: 1.0
}
}
#[expect(clippy::result_large_err)] fn try_convert_winit_event <T> (&mut self, event : winit::event::Event <T>)
-> Result <Vec <Input>, winit::event::Event <T>>
where T : std::fmt::Debug
{
use winit::event::{self, Event};
use view::input;
log::trace!("winit event: {event:?}");
let winit_event = event; #[expect(clippy::match_same_arms)]
let input = match &winit_event {
Event::NewEvents (_) => return Err (winit_event),
Event::WindowEvent { event, .. } => match event {
event::WindowEvent::ModifiersChanged (modifiers) => {
self.modifiers = modifiers.state();
return Err (winit_event)
}
event::WindowEvent::AxisMotion {..} => return Err (winit_event),
event::WindowEvent::CloseRequested => input::System::Close.into(),
event::WindowEvent::CursorEntered {..} => input::System::CursorEntered.into(),
event::WindowEvent::CursorLeft {..} => input::System::CursorLeft.into(),
event::WindowEvent::CursorMoved {..} => return Err (winit_event),
event::WindowEvent::RedrawRequested => input::System::Redraw.into(),
event::WindowEvent::Destroyed => input::System::Destroyed.into(),
event::WindowEvent::Focused (focused) =>
input::System::Focused (*focused).into(),
event::WindowEvent::KeyboardInput { event, is_synthetic: false, .. } => {
let input = (event, self.modifiers).into();
if event.state == event::ElementState::Pressed &&
let Some (mut text) = event.text.clone().map (|text| text.to_string())
{
let text_input = if text.len() > 1 {
input::Text::String (text)
} else {
debug_assert_eq!(text.len(), 1);
input::Text::Char (text.pop().unwrap())
}.into();
return Ok (vec![input, text_input])
}
input
}
event::WindowEvent::KeyboardInput { is_synthetic: true, .. } =>
return Err (winit_event),
event::WindowEvent::MouseInput { button, state, .. } =>
mouse_button (*button, self.modifiers, *state),
event::WindowEvent::MouseWheel { delta, .. } =>
mouse_wheel (*delta, self.modifiers),
event::WindowEvent::Resized (logical_size) => {
let (width, height) = (*logical_size).into();
let mut out = vec![input::System::Resized { width, height }.into()];
self.dimensions = dimensions::Pixel::new_wh (width, height);
if self.pointer_hv == (-1.0, -1.0) {
self.pointer_hv.0 = width as f32 / 2.0;
self.pointer_hv.1 = height as f32 / 2.0;
out.push (self.pointer().into());
} else {
self.clip_pointer().map (|pointer| out.push (pointer.into()));
}
return Ok (out)
}
event::WindowEvent::Ime (_) => input::System::InputMethod ().into(),
event::WindowEvent::Occluded (occluded) =>
input::System::Occluded (*occluded).into(),
event::WindowEvent::Moved (_) => return Err (winit_event),
_ => {
log::error!("TODO: unhandled glutin window event: {event:?}");
unimplemented!("TODO: unhandled glutin window event: {:?}", event)
}
}
Event::DeviceEvent { event, .. } => match event {
event::DeviceEvent::Button {..} => return Err (winit_event),
event::DeviceEvent::Key (_) => return Err (winit_event),
event::DeviceEvent::Motion { axis, value } =>
input::Axis { axis: *axis, value: value.round() as i32 }.into(),
event::DeviceEvent::MouseMotion { delta: (x, y) } => {
let motion = input::Motion (x.round() as i32, y.round() as i32).into();
let pointer = {
self.pointer_hv.0 += (x.abs().powf (self.pointer_sensitivity as f64)
* x.signum()) as f32;
self.pointer_hv.1 -= (y.abs().powf (self.pointer_sensitivity as f64)
* y.signum()) as f32;
self.clip_pointer().unwrap_or_else (|| self.pointer()).into()
};
return Ok (vec![motion, pointer])
}
event::DeviceEvent::MouseWheel {..} => return Err (winit_event),
event::DeviceEvent::Added => return Err (winit_event),
event::DeviceEvent::Removed => {
log::error!("TODO: unhandled glutin device event: {event:?}");
unimplemented!("TODO: unhandled glutin device event: {:?}", event)
}
}
Event::Resumed => return Err (winit_event),
Event::AboutToWait => return Err (winit_event),
_ => {
log::error!("TODO: unhandled winit event: {winit_event:?}");
unimplemented!("TODO: unhandld winit event: {:?}", winit_event)
}
};
Ok (vec![input])
}
fn pointer (&self) -> input::Pointer {
input::Pointer {
position_horizontal: self.pointer_hv.0,
position_vertical: self.pointer_hv.1,
modifiers: self.modifiers.into()
}
}
fn clip_pointer (&mut self) -> Option <input::Pointer> {
let hv_before = self.pointer_hv;
self.pointer_hv.0 = f32::min (self.pointer_hv.0, self.dimensions.width() as f32);
self.pointer_hv.0 = f32::max (self.pointer_hv.0, 0.0);
self.pointer_hv.1 = f32::min (self.pointer_hv.1, self.dimensions.height() as f32);
self.pointer_hv.1 = f32::max (self.pointer_hv.1, 0.0);
if hv_before != self.pointer_hv {
Some (self.pointer())
} else {
None
}
}
}
impl winit::application::ApplicationHandler for InputHandler <'_> {
fn resumed (&mut self, _event_loop : &winit::event_loop::ActiveEventLoop) {
self.handle_event (winit::event::Event::Resumed)
}
fn window_event (&mut self,
_event_loop : &winit::event_loop::ActiveEventLoop,
window_id : winit::window::WindowId,
event : winit::event::WindowEvent
) {
self.handle_event (winit::event::Event::WindowEvent { window_id, event })
}
fn new_events (&mut self,
_event_loop : &winit::event_loop::ActiveEventLoop,
cause : winit::event::StartCause
) {
self.handle_event (winit::event::Event::NewEvents (cause))
}
fn user_event (&mut self,
_event_loop : &winit::event_loop::ActiveEventLoop,
event : ()
) {
self.handle_event (winit::event::Event::UserEvent (event))
}
fn device_event (&mut self,
_event_loop : &winit::event_loop::ActiveEventLoop,
device_id : winit::event::DeviceId,
event : winit::event::DeviceEvent
) {
self.handle_event (winit::event::Event::DeviceEvent { device_id, event })
}
fn about_to_wait (&mut self, _event_loop : &winit::event_loop::ActiveEventLoop) {
self.handle_event (winit::event::Event::AboutToWait)
}
fn suspended (&mut self, _event_loop : &winit::event_loop::ActiveEventLoop) {
self.handle_event (winit::event::Event::Suspended)
}
fn exiting (&mut self, _event_loop : &winit::event_loop::ActiveEventLoop) {
self.handle_event (winit::event::Event::LoopExiting)
}
fn memory_warning (&mut self, _event_loop : &winit::event_loop::ActiveEventLoop) {
self.handle_event (winit::event::Event::MemoryWarning)
}
}
impl From <winit::event::MouseButton> for input::button::Mouse {
fn from (button : winit::event::MouseButton) -> input::button::Mouse {
use winit::event::MouseButton;
use view::input::button::Mouse;
match button {
MouseButton::Left => Mouse::Mouse1,
MouseButton::Right => Mouse::Mouse2,
MouseButton::Middle => Mouse::Mouse3,
MouseButton::Other (4) => Mouse::Mouse4,
MouseButton::Other (5) => Mouse::Mouse5,
button => {
log::error!("unhandled glutin mouse button: {button:?}");
unimplemented!()
}
}
}
}
impl From <(&winit::event::KeyEvent, winit::keyboard::ModifiersState)> for Input {
fn from (
(input, modifiers) :
(&winit::event::KeyEvent, winit::keyboard::ModifiersState)
) -> Input {
use std::str::FromStr;
use view::{input, Input};
use winit::keyboard;
let state = input.state.into();
let button = {
let variant = match &input.logical_key {
keyboard::Key::Named (named_key) =>
input::button::Keycode::from ((*named_key, input.location)),
keyboard::Key::Character (ch) => input::button::Keycode::try_from (
char::from_str (ch.as_str()).unwrap()
).unwrap(),
keyboard::Key::Unidentified (k) =>
unimplemented!("TODO: unidentified key: {:?}", k),
keyboard::Key::Dead (k) =>
unimplemented!("TODO: dead key: {:?}", k)
}.into();
input::Button { variant, modifiers: modifiers.into() }
};
Input::Button (button, state)
}
}
impl From <winit::event::ElementState> for input::button::State {
fn from (state : winit::event::ElementState) -> Self {
use winit::event::ElementState;
use view::input::button::State;
match state {
ElementState::Pressed => State::Pressed,
ElementState::Released => State::Released
}
}
}
impl From <winit::keyboard::ModifiersState> for input::Modifiers {
fn from (modifiers : winit::keyboard::ModifiersState) -> Self {
use view::input::Modifiers;
let mut out = Modifiers::empty();
out.set (Modifiers::SHIFT, modifiers.shift_key());
out.set (Modifiers::CTRL, modifiers.control_key());
out.set (Modifiers::ALT, modifiers.alt_key());
out.set (Modifiers::SUPER, modifiers.super_key());
out
}
}
fn mouse_button (
button : winit::event::MouseButton,
modifiers : winit::keyboard::ModifiersState,
state : winit::event::ElementState
) -> Input {
let state = state.into();
let button = {
let variant = input::button::Mouse::from (button).into();
let modifiers = modifiers.into();
input::Button { variant, modifiers }
};
Input::Button (button, state)
}
fn mouse_wheel (
delta : winit::event::MouseScrollDelta,
modifiers : winit::keyboard::ModifiersState
) -> Input {
let delta = match delta {
winit::event::MouseScrollDelta::LineDelta (x, y)
=> (x.round() as i32, y.round() as i32),
winit::event::MouseScrollDelta::PixelDelta (
winit::dpi::PhysicalPosition { x, y }
) => (x.round() as i32, y.round() as i32)
};
let modifiers = modifiers.into();
input::Wheel { delta, modifiers }.into()
}