use crate::*; use window::{PhosphorWindow, PointerState}; 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 add_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 { // Allow marked windows to create new windows. 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) { if let Some(window) = self.windows.get_mut(&id) { macro_rules! writer { () => { &mut window.requests.as_writer() } } match event { WindowEvent::RedrawRequested => window.redraw(), WindowEvent::CloseRequested => window.program.handle_event(Event::CloseRequest, writer!()), WindowEvent::Destroyed => window.program.handle_event(Event::Close, writer!()), WindowEvent::Resized( PhysicalSize { width, height } ) => window.update_buffer_size(Dimensions::new(width, height)), WindowEvent::Focused(focused) => window.program.handle_event(Event::FocusChange(focused), writer!()), WindowEvent::CursorEntered { .. } => { window.pointer_state = PointerState::In; window.program.handle_event(Event::CursorEnter, writer!()); } WindowEvent::CursorLeft { .. } => { if window.mouse_buttons.iter().all(|b| !b) { window.pointer_state = PointerState::Out; window.program.handle_event(Event::CursorExit, writer!()); } else { window.pointer_state = PointerState::PendingOut; } } 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); window.program.handle_event(Event::CursorMove(pointer), writer!()); } } WindowEvent::MouseWheel { delta, .. } => match delta { MouseScrollDelta::LineDelta(x, y) => { if x != 0.0 { let axis = Axis::Horizontal; let distance = -x; window.program.handle_event(Event::ScrollLines { axis, distance }, writer!()); } if y != 0.0 { let axis = Axis::Vertical; let distance = -y; window.program.handle_event(Event::ScrollLines { axis, distance }, writer!()); } } MouseScrollDelta::PixelDelta(PhysicalPosition {x, y}) => { if x != 0.0 { let axis = Axis::Horizontal; let distance = -x as f32; window.program.handle_event(Event::ScrollPixels { axis, distance }, writer!()); } if y != 0.0 { let axis = Axis::Vertical; let distance = -y as f32; window.program.handle_event(Event::ScrollPixels { axis, distance }, writer!()); } } } WindowEvent::MouseInput { state, button, .. } => { let action = state.into(); match button { WinitMouseButton::Left => window.program.handle_event(Event::MouseButton { button: MouseButton::Left, action }, writer!()), WinitMouseButton::Middle => window.program.handle_event(Event::MouseButton { button: MouseButton::Middle, action }, writer!()), WinitMouseButton::Right => window.program.handle_event(Event::MouseButton { button: MouseButton::Right, action }, writer!()), WinitMouseButton::Back => window.program.handle_event(Event::MouseButton { button: MouseButton::Back, action }, writer!()), WinitMouseButton::Forward => window.program.handle_event(Event::MouseButton { button: MouseButton::Forward, action }, writer!()), _ => (), } // Record mouse button hold states, and mark cursor as out when all are released. let button_index = match button { WinitMouseButton::Left => 0, WinitMouseButton::Middle => 1, WinitMouseButton::Right => 2, WinitMouseButton::Back => 3, WinitMouseButton::Forward => 4, _ => return, }; window.mouse_buttons[button_index] = action.is_pressed(); if window.pointer_state == PointerState::PendingOut && window.mouse_buttons.iter().all(|b| !b) { window.pointer_state = PointerState::Out; window.program.handle_event(Event::CursorExit, writer!()); } } WindowEvent::KeyboardInput { event, .. } => { if let Some(smol_str) = event.text { if event.state.is_pressed() { for c in smol_str.chars() { window.program.handle_event(Event::CharacterInput(c), writer!()); } } } if let PhysicalKey::Code(code) = event.physical_key { let key = code; let action = event.state.into(); window.program.handle_event(Event::KeyboardInput { key, action }, writer!()); } } WindowEvent::ModifiersChanged(modifiers) => { window.program.handle_event(Event::ModifierChange(modifiers.state()), writer!()); } WindowEvent::DroppedFile(path) => window.program.handle_event(Event::DropFile(path), writer!()), WindowEvent::HoveredFile(path) => window.program.handle_event(Event::HoverFile(path), writer!()), WindowEvent::HoveredFileCancelled => window.program.handle_event(Event::HoverFileCancel, writer!()), _ => (), } } } } 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 { event_loop.set_control_flow(ControlFlow::Poll); self.handle_builders_and_destructors(event_loop); } // Ensure a minimum duration between frames. const MINIMUM_WAIT: Duration = Duration::from_micros(10); std::thread::sleep(MINIMUM_WAIT.saturating_sub(self.frame_start.elapsed())); self.frame_start = Instant::now(); } 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); } }