diff options
author | Ben Bridle <bridle.benjamin@gmail.com> | 2024-10-19 18:20:09 +1300 |
---|---|---|
committer | Ben Bridle <bridle.benjamin@gmail.com> | 2024-10-19 18:21:48 +1300 |
commit | 990f2b310bfecf2e04a8a462f6939833080c62bf (patch) | |
tree | bbbbb65a3cfcbaec71935b581e7162a28947f960 /src/phosphor.rs | |
parent | 824483fc95ccbc4627d07371bac8ed4da44c83e7 (diff) | |
download | phosphor-990f2b310bfecf2e04a8a462f6939833080c62bf.zip |
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.
Diffstat (limited to 'src/phosphor.rs')
-rw-r--r-- | src/phosphor.rs | 199 |
1 files changed, 199 insertions, 0 deletions
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<WindowBuilder>, +} + +impl Phosphor { + pub fn new() -> Result<Self, ()> { + 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<WindowBuilder>, + windows: HashMap<WindowId, PhosphorWindow>, + 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); + } +} |