From 990f2b310bfecf2e04a8a462f6939833080c62bf Mon Sep 17 00:00:00 2001 From: Ben Bridle Date: Sat, 19 Oct 2024 18:20:09 +1300 Subject: Complete rewrite of Phosphor The previous version of the library passed events to an implementation of a WindowController trait by calling the trait method associated with each event, and received requests by calling different trait methods and reading the returned values. This had the downside of requiring that any data received from one event had to be stored in the type so that it could be passed back to Phosphor when a request method was called. The new library structure uses a single handle_event method on a trait, which is passed data representing any single event when it is called. Data is returned via a passed mutable reference to an EventQueue, meaning that any number of responses for any event can be immediately returned to Phosphor without having to wait in storage. --- src/phosphor.rs | 199 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 src/phosphor.rs (limited to 'src/phosphor.rs') diff --git a/src/phosphor.rs b/src/phosphor.rs new file mode 100644 index 0000000..1e50735 --- /dev/null +++ b/src/phosphor.rs @@ -0,0 +1,199 @@ +use crate::*; + +use winit::application::ApplicationHandler; +use winit::dpi::{PhysicalPosition, PhysicalSize}; +use winit::event::{MouseButton as WinitMouseButton, MouseScrollDelta, StartCause, WindowEvent}; +use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; +use winit::keyboard::PhysicalKey; +use winit::window::WindowId; +use winit::error::EventLoopError; + +use std::collections::HashMap; +use std::time::{Duration, Instant}; + + +pub struct Phosphor { + event_loop: EventLoop<()>, + builders: Vec, +} + +impl Phosphor { + pub fn new() -> Result { + if let Ok(event_loop) = EventLoop::new() { + Ok( Self { + event_loop, + builders: Vec::new(), + } ) + } else { + Err(()) + } + } + + pub fn create_window(&mut self, window: WindowBuilder) { + self.builders.push(window); + } + + pub fn run(self) -> Result<(), EventLoopError> { + let mut application = PhosphorApplication { + builders: self.builders, + windows: HashMap::new(), + frame_start: Instant::now(), + }; + self.event_loop.run_app(&mut application) + } +} + + +struct PhosphorApplication { + builders: Vec, + windows: HashMap, + frame_start: Instant, +} + +impl PhosphorApplication { + pub fn handle_builders_and_destructors(&mut self, event_loop: &ActiveEventLoop) { + // Find marked windows, handle final requests and destroy. + let mut marked_ids = Vec::new(); + for (id, window) in &mut self.windows { + if window.marked_for_destruction { + window.handle_requests(&mut self.builders); + marked_ids.push(id.clone()); + } + } + for id in marked_ids { + self.windows.remove(&id); + } + + // Build any newly-requested windows. + while !self.builders.is_empty() { + for builder in std::mem::take(&mut self.builders) { + let mut window = PhosphorWindow::from_builder(builder, event_loop); + window.program.handle_event(Event::Initialise, &mut window.requests.as_writer()); + window.handle_requests(&mut self.builders); + self.windows.insert(window.winit.id(), window); + } + } + + // If all windows have been destroyed, close the event loop. + if self.windows.is_empty() { + event_loop.exit(); + } + } + + pub fn handle_window_event(&mut self, event: WindowEvent, id: WindowId) { + let window = match self.windows.get_mut(&id) { + Some(w) => w, + None => return, + }; + macro_rules! handle { ($event:expr) => { + window.program.handle_event($event, &mut window.requests.as_writer()) + }; } + + match event { + WindowEvent::Resized( PhysicalSize { width, height } ) => { + window.update_buffer_size(Dimensions::new(width, height)); + } + WindowEvent::CloseRequested => { + handle!(Event::CloseRequest); + } + WindowEvent::Destroyed => { + handle!(Event::Close); + } + WindowEvent::DroppedFile(path) => { + handle!(Event::FileDrop(path)); + } + WindowEvent::Focused(focused) => { + handle!(Event::FocusChange(focused)); + } + WindowEvent::KeyboardInput { event, .. } => { + if let Some(smol_str) = event.text { + if event.state.is_pressed() { + for c in smol_str.chars() { + handle!(Event::CharacterInput(c)); + } + } + } + if let PhysicalKey::Code(code) = event.physical_key { + handle!(Event::KeyboardInput { + key: code, + action: event.state.into(), + }); + } + } + WindowEvent::ModifiersChanged(modifiers) => { + handle!(Event::ModifierChange(modifiers.state())); + } + WindowEvent::CursorMoved { position, .. } => { + let pointer = Position::new( + position.x as i32 / window.scale() as i32, + position.y as i32 / window.scale() as i32, + ); + if window.pointer != Some(pointer) { + window.pointer = Some(pointer); + handle!(Event::CursorMove(pointer)); + } + } + WindowEvent::CursorEntered { .. } => { + handle!(Event::CursorEnter); + } + WindowEvent::CursorLeft { .. } => { + handle!(Event::CursorExit); + } + WindowEvent::MouseWheel { delta, .. } => match delta { + MouseScrollDelta::LineDelta(x, y) => { + if x != 0.0 { handle!(Event::ScrollLines { axis: Axis::Horizontal, distance: -x }); } + if y != 0.0 { handle!(Event::ScrollLines { axis: Axis::Vertical, distance: -y }); } + } + MouseScrollDelta::PixelDelta(PhysicalPosition {x, y}) => { + if x != 0.0 { handle!(Event::ScrollPixels { axis: Axis::Horizontal, distance: -x as f32 }); } + if y != 0.0 { handle!(Event::ScrollPixels { axis: Axis::Vertical, distance: -y as f32 }); } + } + } + WindowEvent::MouseInput { state, button, .. } => { + let action = state.into(); + match button { + WinitMouseButton::Left => handle!(Event::MouseButton { button: MouseButton::Left, action }), + WinitMouseButton::Middle => handle!(Event::MouseButton { button: MouseButton::Middle, action }), + WinitMouseButton::Right => handle!(Event::MouseButton { button: MouseButton::Right, action }), + _ => (), + } + } + WindowEvent::RedrawRequested => { + window.redraw(); + }, + _ => (), + } + } +} + +impl ApplicationHandler for PhosphorApplication { + fn resumed(&mut self, _event_loop: &ActiveEventLoop) {} + + fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) { + if let StartCause::Init = cause { + // Ensure a minimum duration between frames. + const MINIMUM_WAIT: Duration = Duration::from_millis(1); + std::thread::sleep(MINIMUM_WAIT.saturating_sub(self.frame_start.elapsed())); + self.frame_start = Instant::now(); + + event_loop.set_control_flow(ControlFlow::Poll); + self.handle_builders_and_destructors(event_loop); + } + } + + fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { + for (_, window) in &mut self.windows { + window.program.process(&mut window.requests.as_writer()); + window.handle_requests(&mut self.builders); + } + self.handle_builders_and_destructors(event_loop); + } + + fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) { + self.handle_window_event(event, id); + if let Some(window) = self.windows.get_mut(&id) { + window.handle_requests(&mut self.builders); + } + self.handle_builders_and_destructors(event_loop); + } +} -- cgit v1.2.3-70-g09d2