summaryrefslogtreecommitdiff
path: root/src/phosphor.rs
diff options
context:
space:
mode:
authorBen Bridle <bridle.benjamin@gmail.com>2024-10-19 18:20:09 +1300
committerBen Bridle <bridle.benjamin@gmail.com>2024-10-19 18:21:48 +1300
commit990f2b310bfecf2e04a8a462f6939833080c62bf (patch)
treebbbbb65a3cfcbaec71935b581e7162a28947f960 /src/phosphor.rs
parent824483fc95ccbc4627d07371bac8ed4da44c83e7 (diff)
downloadphosphor-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.rs199
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);
+ }
+}