diff options
| -rw-r--r-- | Cargo.toml | 9 | ||||
| -rw-r--r-- | src/events.rs | 115 | ||||
| -rw-r--r-- | src/lib.rs | 63 | ||||
| -rw-r--r-- | src/phosphor.rs | 199 | ||||
| -rw-r--r-- | src/render.rs | 67 | ||||
| -rw-r--r-- | src/window.rs | 393 | ||||
| -rw-r--r-- | src/window_builder.rs | 30 | ||||
| -rw-r--r-- | src/window_controller.rs | 49 | ||||
| -rw-r--r-- | src/window_manager.rs | 139 | ||||
| -rw-r--r-- | src/window_program.rs | 13 | 
10 files changed, 571 insertions, 506 deletions
| @@ -1,6 +1,6 @@  [package]  name = "phosphor" -version = "2.0.0" +version = "3.0.0-rc1"  authors = ["Ben Bridle"]  edition = "2021"  description = "Window management library" @@ -8,11 +8,12 @@ description = "Window management library"  # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html  [dependencies] -buffer = { git = "git://benbridle.com/buffer", "tag" = "v1.0.0" } +buffer = { git = "git://benbridle.com/buffer", "tag" = "v1.1.0" } +event_queue = { git = "git://benbridle.com/event_queue", "tag" = "v1.1.0" }  geometry = { git = "git://benbridle.com/geometry", "tag" = "v1.0.0" } -softbuffer = "0.3.1" -winit = { version = "0.28.1", default-features=false } +softbuffer = "0.4.6" +winit = { version = "0.30.5", default-features=false, features=["rwh_06"] }  [features]  default = ["x11", "wayland"] diff --git a/src/events.rs b/src/events.rs new file mode 100644 index 0000000..b738e5b --- /dev/null +++ b/src/events.rs @@ -0,0 +1,115 @@ +use crate::*; + +use winit::dpi::PhysicalSize; +use winit::event::ElementState; +use winit::keyboard::KeyCode; +use winit::window::CursorIcon; + +use std::path::PathBuf; + + +pub enum Request { +    SetTitle(String), +    SetSize(Dimensions), +    SetSizeBounds(SizeBounds), +    SetResizable(bool), +    SetFullscreen(bool), +    SetVisible(bool), +    SetPixelScale(u32), +    SetCursor(Option<CursorIcon>), +    Redraw, +    CreateWindow(WindowBuilder), +    CloseWindow, +}  + + +#[derive(Debug)] +pub enum Event { +    Initialise, +    CloseRequest, +    Close, +    Resize(Dimensions), +    FocusChange(bool), +    CursorEnter, +    CursorExit, +    CursorMove(Position), +    ScrollLines { axis: Axis, distance: f32 }, +    ScrollPixels { axis: Axis, distance: f32 }, +    MouseButton { button: MouseButton, action: Action }, +    KeyboardInput { key: KeyCode, action: Action }, +    CharacterInput(char), +    ModifierChange(ModifiersState), +    FileDrop(PathBuf), +}  + + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Action { +    Pressed, +    Released +} + +impl Action { +    pub fn is_pressed(self) -> bool { +        self == Action::Pressed +    } +} + +impl From<ElementState> for Action { +    fn from(value: ElementState) -> Self { +        match value { +            ElementState::Pressed => Action::Pressed, +            ElementState::Released => Action::Released, +        } +    } +} + + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Axis { +    Horizontal, +    Vertical, +} + + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum MouseButton { +    Left, +    Middle, +    Right, +} + + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct SizeBounds { +    pub min_width: Option<u32>, +    pub max_width: Option<u32>, +    pub min_height: Option<u32>, +    pub max_height: Option<u32>, +} + +impl SizeBounds { +    pub fn as_min_max_size(&self, scale: u32) -> (PhysicalSize<u32>, PhysicalSize<u32>) { +        ( +            PhysicalSize { +                width:  self.min_width.unwrap_or(0).saturating_mul(scale), +                height: self.min_height.unwrap_or(0).saturating_mul(scale), +            }, +            PhysicalSize { +                width:  self.max_width.unwrap_or(u32::MAX).saturating_mul(scale), +                height: self.max_height.unwrap_or(u32::MAX).saturating_mul(scale), +            }, +        ) +    } +} + +impl Default for SizeBounds { +    fn default() -> Self { +        Self { +            min_width: None, +            max_width: None, +            min_height: None, +            max_height: None, +        } +    } +} @@ -1,56 +1,19 @@ -mod render; +mod events;  mod window; -mod window_controller; -mod window_manager; +mod window_builder; +mod window_program; +mod phosphor; -pub use render::*; -pub use window::*; -pub use window_controller::*; -pub use window_manager::*; +pub use events::{Request, Event, Action, Axis, MouseButton, SizeBounds}; +pub(crate) use window::PhosphorWindow; +pub use window_builder::WindowBuilder; +pub use window_program::WindowProgram;  pub use buffer::*; -pub use winit::{ -    event::{ModifiersState, ElementState}, -    event::VirtualKeyCode as KeyCode, -    window::CursorIcon, -}; -pub use std::num::NonZeroU32; +pub use phosphor::Phosphor; +pub use event_queue::EventWriter; -// ----------------------------------------------------------------------------- +pub use winit::keyboard::{KeyCode, ModifiersState}; -#[derive(Copy, Clone)] -pub struct KeyboardInput { -    pub action: Action, -    pub key: KeyCode, -} - -impl TryFrom<winit::event::KeyboardInput> for KeyboardInput { -    type Error = (); - -    fn try_from(input: winit::event::KeyboardInput) -> Result<Self, ()> { -        if let Some(key) = input.virtual_keycode { -            Ok( Self { action: input.state.into(), key } ) -        } else { -            Err(()) -        } -    } -} - -// ----------------------------------------------------------------------------- - -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum Action { Pressed, Released } - -impl Action { -    pub fn is_pressed(&self)  -> bool { *self == Self::Pressed  } -    pub fn is_released(&self) -> bool { *self == Self::Released } -} - -impl From<ElementState> for Action { -    fn from(value: ElementState) -> Self { -        match value { -            ElementState::Pressed => Action::Pressed, -            ElementState::Released => Action::Released, -        } -    } -} +pub type Position = geometry::Point<i32>; +pub type Dimensions = geometry::Dimensions<u32>; 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); +    } +} diff --git a/src/render.rs b/src/render.rs deleted file mode 100644 index e7ec02d..0000000 --- a/src/render.rs +++ /dev/null @@ -1,67 +0,0 @@ -/// Tells a window controller whether the previous render state is intact and -/// can be updated, or was destroyed and needs to be fully redrawn. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum RenderHint { -    /// The buffer content is unchanged, only updates are required. -    Update, -    /// The buffer content has been destroyed, a full redraw is required. -    Redraw, -} - -impl RenderHint { -    pub fn is_update(&self) -> bool { *self == RenderHint::Update } -    pub fn is_redraw(&self) -> bool { *self == RenderHint::Redraw } -} - -impl std::ops::BitAnd for RenderHint { -    type Output = RenderHint; -    fn bitand(self, other: RenderHint) -> Self::Output { -        if self == RenderHint::Redraw || other == RenderHint::Redraw { -            RenderHint::Redraw -        } else { -            RenderHint::Update -        } -    } -} - -impl std::ops::BitAndAssign for RenderHint { -    fn bitand_assign(&mut self, other: RenderHint) { -        *self = *self & other; -    } -} - -/// A request to the window manager for a render pass to be run. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum RenderRequest { -    None, -    Render(RenderHint), -} - -impl RenderRequest { -    pub const NONE:   RenderRequest = RenderRequest::None; -    pub const UPDATE: RenderRequest = RenderRequest::Render(RenderHint::Update); -    pub const REDRAW: RenderRequest = RenderRequest::Render(RenderHint::Redraw); - -    pub fn is_none(&self)   -> bool { *self == RenderRequest::None } -    pub fn is_some(&self)   -> bool { *self != RenderRequest::None } -    pub fn is_update(&self) -> bool { *self == RenderRequest::UPDATE } -    pub fn is_redraw(&self) -> bool { *self == RenderRequest::REDRAW } -} - -impl std::ops::BitAnd for RenderRequest { -    type Output = RenderRequest; -    fn bitand(self, other: RenderRequest) -> Self::Output { -        use RenderRequest::*; -        match (self, other) { -            (None, req)  => req, -            (req, None)  => req, -            (Render(a), Render(b)) => Render(a & b), -        } -    } -} - -impl std::ops::BitAndAssign for RenderRequest { -    fn bitand_assign(&mut self, other: RenderRequest) { -        *self = *self & other; -    } -} diff --git a/src/window.rs b/src/window.rs index 261af36..377d460 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,251 +1,250 @@  use crate::*; -use buffer::HasDimensions; -use winit::dpi::{Size, PhysicalSize}; -use winit::event_loop::EventLoopWindowTarget; -use winit::window::{Fullscreen, WindowId}; +use buffer::{Dimensions, HasDimensions}; +use event_queue::EventQueue; -pub struct Window { -    pub controller: Box<dyn WindowController>, -    window: winit::window::Window, -    pixel_scale: u32, -    buffer: Buffer, -    surface: softbuffer::Surface, +use winit::dpi::PhysicalSize; +use winit::event_loop::ActiveEventLoop; +use winit::window::{CursorIcon, Fullscreen, Window}; + +use std::rc::Rc; +use std::cmp::{min, max}; + +fn to_physical_size(dimensions: Dimensions, scale: u32) -> PhysicalSize<u32> { +    PhysicalSize { +        width:  max(1, dimensions.width  * scale), +        height: max(1, dimensions.height * scale), +    } +} + + +pub(crate) struct PhosphorWindow { +    pub winit: Rc<Window>, +    pub program: Box<dyn WindowProgram>, +    pub requests: EventQueue<Request>, +    pub pointer: Option<Position>, +    pub marked_for_destruction: bool, +    pub size_bounds: SizeBounds,  // logical dimensions + +    scale: u32, +    buffer: buffer::Buffer, +    surface: softbuffer::Surface<Rc<Window>, Rc<Window>>,      surface_dimensions: Dimensions, -    #[allow(dead_code)] context: softbuffer::Context, -    render_request: RenderRequest, -    previous_cursor_position: Option<Point>, -    previous_cursor: Option<CursorIcon>, -    previous_title: String, +    redraw_full: bool,  } -impl Window { -    pub fn new(event_loop: &EventLoopWindowTarget<()>, controller: Box<dyn WindowController>) -> Self { -        let pixel_scale = controller.pixel_scale().get(); -        let increment = PhysicalSize { width: pixel_scale, height: pixel_scale }; -        let mut builder = winit::window::WindowBuilder::new() -            .with_title(controller.title()) -            .with_resize_increments(increment); -        if let Some(exact_dimensions) = controller.exact_size() { -            let exact_size = dim_to_size(exact_dimensions * pixel_scale); -            builder = builder -                .with_resizable(false) -                .with_inner_size(exact_size) -                .with_min_inner_size(exact_size) -                .with_max_inner_size(exact_size); -        } else if let Some(initial_dimensions) = controller.initial_size() { -            builder = builder.with_inner_size(dim_to_size(initial_dimensions * pixel_scale)); -            if let Some(min_dimensions) = controller.minimum_size() { -                builder = builder.with_min_inner_size(dim_to_size(min_dimensions * pixel_scale)); -            } -            if let Some(max_dimensions) = controller.maximum_size() { -                builder = builder.with_max_inner_size(dim_to_size(max_dimensions * pixel_scale)); -            } + +impl PhosphorWindow { +    pub fn from_builder(builder: WindowBuilder, event_loop: &ActiveEventLoop) -> Self { +        let mut attr = Window::default_attributes(); +        let scale = builder.scale; +        let size_bounds = builder.size_bounds.unwrap_or(SizeBounds::default()); + +        if let Some(size_bounds) = builder.size_bounds { +            let (min_size, max_size) = size_bounds.as_min_max_size(scale); +            attr = attr.with_min_inner_size(min_size); +            attr = attr.with_max_inner_size(max_size); +        } +        if let Some(dimensions) = builder.dimensions { +            attr = attr.with_inner_size(to_physical_size(dimensions, scale)); +        } +        match builder.fullscreen { +            true  => attr = attr.with_fullscreen(Some(Fullscreen::Borderless(None))), +            false => attr = attr.with_fullscreen(None),          }; -        let window = builder.build(event_loop).unwrap(); -        let context = unsafe { softbuffer::Context::new(&window) }.unwrap(); -        let surface = unsafe { softbuffer::Surface::new(&context, &window) }.unwrap(); +        let increment = PhysicalSize { width: scale, height: scale }; +        attr = attr.with_resize_increments(increment); + +        attr = attr.with_title(match builder.title { +            Some(title) => title, +            None => String::from("Untitled"), +        }); + +        let window = Rc::new(event_loop.create_window(attr).unwrap()); +        let context = softbuffer::Context::new(window.clone()).unwrap(); +        let surface = softbuffer::Surface::new(&context, window.clone()).unwrap(); + +        if let Some(cursor) = builder.cursor { +            window.set_cursor(cursor); +            window.set_cursor_visible(true); +        } else { +            window.set_cursor_visible(false); +        }          Self { -            controller, -            window, -            pixel_scale, -            buffer: Buffer::new(Dimensions::ZERO), +            winit: window, +            program: builder.program, +            requests: EventQueue::new(), +            pointer: None, +            marked_for_destruction: false, +            size_bounds, + +            scale, +            buffer: buffer::Buffer::new(Dimensions::ZERO),              surface,              surface_dimensions: Dimensions::ZERO, -            context, -            render_request: RenderRequest::REDRAW, -            previous_cursor_position: None, -            previous_cursor: Some(CursorIcon::Default), -            previous_title: String::new(), +            redraw_full: true,          }      } -    pub fn id(&self) -> WindowId { -        self.window.id() +    pub fn handle_requests(&mut self, builders: &mut Vec<WindowBuilder>) { +        for request in self.requests.drain() { +            match request { +                Request::CreateWindow(builder) => builders.push(builder), +                Request::SetTitle(title) => self.winit.set_title(&title), +                Request::SetSize(dimensions) => self.set_size(dimensions), +                Request::SetSizeBounds(bounds) => self.set_size_bounds(bounds), +                Request::SetResizable(resizable) => self.winit.set_resizable(resizable), +                Request::SetFullscreen(fullscreen) => self.set_fullscreen(fullscreen), +                Request::SetVisible(visible) => self.winit.set_visible(visible), +                Request::SetPixelScale(scale) => self.set_scale(scale), +                Request::SetCursor(cursor) => self.set_cursor(cursor), +                Request::Redraw => self.winit.request_redraw(), +                Request::CloseWindow => { +                    self.marked_for_destruction = true; +                    self.program.handle_event(Event::Close, &mut self.requests.as_writer()); +                }, +            } +        }      } -    pub fn update_title(&mut self) { -        let title = self.controller.title(); -        if title != self.previous_title { -            self.window.set_title(&title); -            self.previous_title = title; -        } +    pub fn set_size(&mut self, dimensions: Dimensions) { +        let size = to_physical_size(dimensions, self.scale); +        let _ = self.winit.request_inner_size(size);      } -    pub fn update_cursor(&mut self) { -        let cursor = self.controller.cursor(); -        if cursor != self.previous_cursor { -            self.window.set_cursor_visible(cursor.is_some()); -            if let Some(icon) = cursor { -                self.window.set_cursor_icon(icon); -            } -            self.previous_cursor = cursor; +    pub fn set_size_bounds(&mut self, bounds: SizeBounds) { +        if self.size_bounds != bounds { +            self.size_bounds = bounds; +            self.update_size_bounds();          }      } -    /// This method only requests that the window size be changed; it does not -    /// alter any buffer sizes or struct fields. When the window size eventually -    /// changes, the state updates are handled by `resize_buffer_and_surface`. -    pub fn update_window_size(&mut self) { -        let fullscreen = self.controller.fullscreen(); -        if fullscreen != self.window.fullscreen().is_some() { -            if fullscreen { -                if let Some(monitor) = self.window.current_monitor() { -                    if let Some(mode) = monitor.video_modes().nth(0) { -                        self.window.set_fullscreen(Some(Fullscreen::Exclusive(mode))); -                    } -                }; -            } else { -                self.window.set_fullscreen(None); -            } +    pub fn update_size_bounds(&mut self) { +        // Set the minimum and maximum dimensions of the window. +        let (min_size, max_size) = self.size_bounds.as_min_max_size(self.scale); +        self.winit.set_min_inner_size(Some(min_size)); +        self.winit.set_max_inner_size(Some(max_size)); + +        // Resize the window to fit the new size bounds. +        let current_size = self.winit.inner_size(); +        let width = min(max(current_size.width, min_size.width), max_size.width); +        let height = min(max(current_size.height, min_size.height), max_size.height); +        let _ = self.winit.request_inner_size(PhysicalSize { width, height }); +        self.update_buffer_size(Dimensions { width, height }); +    } + +    pub fn scale(&self) -> u32 { +        self.scale +    } + +    pub fn set_scale(&mut self, scale: u32) { +        if self.scale != scale { +            self.scale = max(1, scale); +            let increment = PhysicalSize { width: scale, height: scale }; +            self.winit.set_resize_increments(Some(increment)); +            self.update_size_bounds(); +            self.requests.write(Request::Redraw);          } +    } -        let pixel_scale = self.controller.pixel_scale().get(); -        if let Some(dimensions) = self.controller.exact_size() { -            // Prevent the window properties from being modified every frame -            // and interrupting the actions of the window manager. -            if pixel_scale == self.pixel_scale -            && dimensions * pixel_scale == self.surface_dimensions { -                return; +    pub fn set_fullscreen(&mut self, fullscreen: bool) { +        self.winit.set_fullscreen( +            match fullscreen { +                true => Some(Fullscreen::Borderless(None)), +                false => None,              } -            self.window.set_resizable(false); -            let size = dim_to_size(dimensions * pixel_scale); -            self.window.set_min_inner_size(Some(size)); -            self.window.set_max_inner_size(Some(size)); -            self.window.set_inner_size(size); -            self.render_request = RenderRequest::REDRAW; -            self.resize_buffer_and_surface(dimensions * pixel_scale); -        } else { -            // Prevent the window properties from being modified every frame -            // and interrupting the actions of the window manager. -            if pixel_scale == self.pixel_scale { -                return; -            } -            self.window.set_resizable(true); -            let size = self.controller.minimum_size().map(|d| dim_to_size(d * pixel_scale)); -            self.window.set_min_inner_size(size); -            let size = self.controller.maximum_size().map(|d| dim_to_size(d * pixel_scale)); -            self.window.set_max_inner_size(size); -            let increment = PhysicalSize { width: pixel_scale, height: pixel_scale }; -            self.window.set_resize_increments(Some(increment)); -            self.render_request = RenderRequest::REDRAW; -            self.resize_buffer_and_surface(self.surface_dimensions); -        } +        );      } -    /// Resize the frame buffer and screen surface to the new size of the window. -    pub fn resize_buffer_and_surface(&mut self, physical_dimensions: Dimensions) { -        if let Some((width, height)) = dim_to_nonzero_size(physical_dimensions) { -            self.surface.resize(width, height).unwrap(); -            self.surface_dimensions = physical_dimensions; -        }; -        self.pixel_scale = self.controller.pixel_scale().get(); -        let logical_dimensions = physical_dimensions / self.pixel_scale; -        if logical_dimensions != self.buffer.dimensions() { -            self.buffer.resize(logical_dimensions); -            self.controller.on_resize(logical_dimensions); -            self.render_request = RenderRequest::REDRAW; +    pub fn set_cursor(&mut self, cursor: Option<CursorIcon>) { +        if let Some(cursor) = cursor { +            self.winit.set_cursor(cursor); +            self.winit.set_cursor_visible(true); +        } else { +            self.winit.set_cursor_visible(false);          }      } -    pub fn move_cursor(&mut self, position: Point) { -        // Convert cursor position from physical position to logical position. -        let pixel_scale = self.controller.pixel_scale().get() as i32; -        let logical_position = Point::new(position.x / pixel_scale, position.y / pixel_scale); -        // The cursor position is rounded to i32 from f64, so we need to ignore -        // duplicate consecutive cursor positions. -        if self.previous_cursor_position != Some(logical_position) { -            self.previous_cursor_position = Some(logical_position); -            self.controller.on_cursor_move(logical_position); +    /// Called with new physical dimensions when window is resized. +    pub fn update_buffer_size(&mut self, dimensions: Dimensions) { +        if dimensions.is_zero() { +            return;          } -    } +        // Set surface to physical dimensions. +        unsafe { +            self.surface.resize( +                std::num::NonZeroU32::new_unchecked(dimensions.width), +                std::num::NonZeroU32::new_unchecked(dimensions.height), +            ).unwrap(); +        } +        self.surface_dimensions = dimensions; -    pub fn handle_render_request(&mut self) { -        if let RenderRequest::Render(hint) = self.controller.render_request() { -            self.render_request &= RenderRequest::Render(hint); -            self.window.request_redraw(); -        } else if let RenderRequest::Render(_) = self.render_request { -            self.window.request_redraw(); +        // Set buffer to logical dimensions. +        let logical = dimensions / self.scale; +        if logical != self.buffer.dimensions() { +            self.buffer.resize(logical); +            self.program.handle_event(Event::Resize(logical), &mut self.requests.as_writer()); +            self.redraw_full = true;          }      } -    pub fn render(&mut self) { -        // If the surface dimensions are zero, the first resize event hasn't yet -        // come through, and the Surface object will panic when `.present()` is -        // called on it later in this method. We will instead skip rendering for -        // this frame, knowing that the pending resize will trigger a re-render. +    pub fn redraw(&mut self) { +        // The surface dimensions will be zero until the first resize event +        // arrives, which would cause a panic when calling `surface.present()`.          if self.surface_dimensions.is_zero() {              return;          } -        let scale = self.pixel_scale as usize; -        let physical_dim = self.surface_dimensions; -        let logical_dim = self.buffer.dimensions(); -        if let RenderRequest::Render(hint) = self.render_request { -            self.controller.on_render(&mut self.buffer, hint); -        } else { -            // TODO: Look into the cause of reduntant unrequested render -            // requests under Windows. -            self.controller.on_render(&mut self.buffer, RenderHint::Redraw); -        } +        // Render the program to the buffer. +        self.program.render(&mut self.buffer, self.redraw_full); +        self.redraw_full = false; +        // Render the scaled buffer to the surface.          let buffer = self.buffer.as_u32_slice();          let mut surface = self.surface.buffer_mut().unwrap(); -        if scale == 1 { -            let overlap = std::cmp::min(buffer.len(), surface.len()); -            surface[..overlap].copy_from_slice(&buffer[..overlap]); +        if self.scale == 1 && self.buffer.dimensions() == self.surface_dimensions { +            let length = min(buffer.len(), surface.len()); +            surface[..length].copy_from_slice(&buffer[..length]);          } else { -            let logical_width  = logical_dim.width as usize; -            let logical_height = logical_dim.height as usize; -            let physical_width = physical_dim.width as usize; -            let physical_content_width = logical_dim.width as usize * scale; -            let row_excess = physical_width.saturating_sub(physical_content_width); +            let scale          = self.scale as usize; +            let surface_width  = self.surface_dimensions.width  as usize; +            let surface_height = self.surface_dimensions.height  as usize; +            let buffer_width   = self.buffer.dimensions().width  as usize; +            let buffer_height  = self.buffer.dimensions().height as usize; +            let blank_width    = surface_width.saturating_sub(buffer_width * scale); +            let min_width      = min(buffer_width, surface_width / scale); +            let min_height     = min(buffer_height, surface_height / scale); +              let mut bi: usize = 0;              let mut si: usize = 0; -            let mut row_start: usize = 0; -            let mut row_end:   usize = 0; -            for _y in 0..logical_height { -                for py in 0..scale { -                    if py == 0 { -                        row_start = si; -                        for _x in 0..logical_width { -                            surface[si..si+scale].fill(buffer[bi]); -                            si += scale; -                            bi += 1; -                        } -                        // Fill the excess space on the right edge with black. -                        surface[si..si+row_excess].fill(0); -                        si += row_excess; -                        row_end = si; -                    } else { -                        surface.copy_within(row_start..row_end, si); -                        si += physical_width; -                    } +            for _ in 0..min_height { +                // Horizontally scale a buffer row. +                let row_start = si; +                for _ in 0..min_width { +                    surface[si..si+scale].fill(buffer[bi]); +                    si += scale; +                    bi += 1; +                } +                // Fill fractional right edge with black. +                surface[si..si+blank_width].fill(0); +                si += blank_width; +                let row_end = si; +                // Vertically scale the buffer row. +                for _ in 0..scale.saturating_sub(1) { +                    surface.copy_within(row_start..row_end, si); +                    si += surface_width;                  }              } -            // Fill the excess space on the bottom edge with black. -            let excess = surface.len().saturating_sub(si); -            surface[si..si+excess].fill(0); +            // Fill fractional bottom edge with black. +            surface[si..].fill(0);          } -        surface.present().unwrap(); -        // Reset render_request back to the lowest variant for the next frame. -        self.render_request = RenderRequest::None; +        surface.present().unwrap();      }  } -fn dim_to_size(dimensions: Dimensions) -> Size { -    Size::Physical( PhysicalSize { -        width:  std::cmp::max(1, dimensions.width), -        height: std::cmp::max(1, dimensions.height), -    }) -} -fn dim_to_nonzero_size(dimensions: Dimensions) -> Option<(NonZeroU32, NonZeroU32)> { -    let width = NonZeroU32::new(dimensions.width)?; -    let height = NonZeroU32::new(dimensions.height)?; -    return Some((width, height)); -} + diff --git a/src/window_builder.rs b/src/window_builder.rs new file mode 100644 index 0000000..55c99f2 --- /dev/null +++ b/src/window_builder.rs @@ -0,0 +1,30 @@ +use crate::*; + +use winit::window::{Cursor, Icon}; + + +pub struct WindowBuilder { +    pub program: Box<dyn WindowProgram>, +    pub size_bounds: Option<SizeBounds>, +    pub dimensions: Option<Dimensions>, +    pub scale: u32, +    pub title: Option<String>, +    pub icon: Option<Icon>, +    pub cursor: Option<Cursor>, +    pub fullscreen: bool, +} + +impl WindowBuilder { +    pub fn new(program: Box<dyn WindowProgram>) -> Self { +        Self { +            program, +            size_bounds: None, +            dimensions: None, +            scale: 1, +            title: None, +            icon: None, +            cursor: None, +            fullscreen: false, +        } +    } +} diff --git a/src/window_controller.rs b/src/window_controller.rs deleted file mode 100644 index 848730d..0000000 --- a/src/window_controller.rs +++ /dev/null @@ -1,49 +0,0 @@ -use crate::*; -use std::num::NonZeroU32; -use std::path::Path; - -const NON_ZERO_ONE: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(1) }; - -pub trait WindowController { -    fn title(&self) -> String { String::from("Phosphor") } -    fn initial_size(&self) -> Option<Dimensions> { None } -    fn minimum_size(&self) -> Option<Dimensions> { None } -    fn maximum_size(&self) -> Option<Dimensions> { None } -    fn exact_size(&self) -> Option<Dimensions> { None } -    fn fullscreen(&self) -> bool { false } -    fn pixel_scale(&self) -> NonZeroU32 { NON_ZERO_ONE } - -    fn cursor(&mut self) -> Option<CursorIcon> { Some(CursorIcon::Default) } -    fn render_request(&mut self) -> RenderRequest { RenderRequest::None } - -    fn is_visible(&self) -> bool { true } - -    fn on_init(&mut self) {} -    fn on_resize(&mut self, _size: Dimensions) {} -    fn on_move(&mut self, _position: Point) {} -    fn on_focus_change(&mut self, _is_focused: bool) {} -    fn on_process(&mut self) {} -    fn on_render(&mut self, _buffer: &mut buffer::Buffer, _hint: RenderHint) {} -    fn on_close_request(&mut self) {} -    fn on_close(&mut self) {} - -    fn on_cursor_enter(&mut self) {} -    fn on_cursor_exit(&mut self) {} -    fn on_cursor_move(&mut self, _position: Point) {} - -    fn on_left_mouse_button(&mut self, _action: Action) {} -    fn on_middle_mouse_button(&mut self, _action: Action) {} -    fn on_right_mouse_button(&mut self, _action: Action) {} - -    fn on_line_scroll_horizontal(&mut self, _delta: f64) {} -    fn on_line_scroll_vertical(&mut self, _delta: f64) {} -    fn on_pixel_scroll_horizontal(&mut self, _delta: f64) {} -    fn on_pixel_scroll_vertical(&mut self, _delta: f64) {} - -    fn on_keyboard_input(&mut self, _input: KeyboardInput) {} -    fn on_keyboard_modifier_change(&mut self, _modifiers: ModifiersState) {} -    fn on_character_input(&mut self, _input: char) {} -    fn on_file_hover(&mut self, _path: &Path) {} -    fn on_file_hover_cancel(&mut self) {} -    fn on_file_drop(&mut self, _path: &Path) {} -} diff --git a/src/window_manager.rs b/src/window_manager.rs deleted file mode 100644 index 8999bcf..0000000 --- a/src/window_manager.rs +++ /dev/null @@ -1,139 +0,0 @@ -use crate::*; -use std::collections::HashMap; -use std::time::Duration; -use winit::{event::*, event_loop::{EventLoop, ControlFlow}, window::WindowId}; - -pub struct WindowManager { -    event_loop: EventLoop<()>, -    windows: HashMap<WindowId, Window>, -    min_frame_duration: Duration, -} - -impl WindowManager { -    pub fn new() -> Self { -        Self { -            event_loop: EventLoop::new(), -            windows: HashMap::new(), -            min_frame_duration: Duration::ZERO, -        } -    } - -    pub fn with_frame_limit(mut self, limit: u64) -> Self { -        self.min_frame_duration = match limit { -            0 => Duration::ZERO, -            _ => Duration::from_nanos(1_000_000_000 / limit), -        }; -        return self; -    } - -    /// Add a window to the window manager before the event loop begins. -    pub fn add_window(&mut self, controller: Box<dyn WindowController>) { -        let window = Window::new(&self.event_loop, controller); -        self.windows.insert(window.id(), window); -    } - -    /// Start the event loop, passing program control to the window manager. -    pub fn run(mut self) -> ! { -        self.event_loop.run(move |event, _window_target, control_flow| { -            match event { -                // Called when the event loop is first initialized. -                Event::NewEvents(StartCause::Init) => (), - -                Event::NewEvents(..) => { -                    control_flow.set_wait_timeout(self.min_frame_duration); -                } - -                // Called when an application suspends on a mobile platform. -                Event::Suspended => (), - -                // Called when an application resumes, or after initialization on non-mobile platforms. -                Event::Resumed => (), - -                Event::WindowEvent { window_id, event } => { -                    if let Some(window) = self.windows.get_mut(&window_id) { -                        use WindowEvent::*; - -                        match event { -                            Resized(dim) => window.resize_buffer_and_surface(Dimensions::new(dim.width, dim.height)), -                            Moved(position) => window.controller.on_move(Point::new(position.x, position.y)), -                            Focused(is_focused) => window.controller.on_focus_change(is_focused), - -                            CursorEntered { .. } => window.controller.on_cursor_enter(), -                            CursorLeft { .. } => window.controller.on_cursor_exit(), -                            CursorMoved { position: p, .. } => { window.move_cursor(Point::new(p.x as i32, p.y as i32)) } - -                            MouseWheel {delta, ..} => match delta { -                                MouseScrollDelta::LineDelta(x, y) => { -                                    let (x, y) = (-x as f64, -y as f64); -                                    if x != 0.0 {window.controller.on_line_scroll_horizontal(x)} -                                    if y != 0.0 {window.controller.on_line_scroll_vertical(y)} -                                } -                                MouseScrollDelta::PixelDelta(point) => { -                                    let (x, y) = (-point.x, -point.y); -                                    if x != 0.0 {window.controller.on_pixel_scroll_horizontal(x)} -                                    if y != 0.0 {window.controller.on_pixel_scroll_vertical(y)} -                                } -                            } -                            MouseInput { state, button, .. } => match button { -                                MouseButton::Left => window.controller.on_left_mouse_button(state.into()), -                                MouseButton::Middle => window.controller.on_middle_mouse_button(state.into()), -                                MouseButton::Right => window.controller.on_right_mouse_button(state.into()), -                                MouseButton::Other(_) => (), -                            } - -                            KeyboardInput { input, .. } => if let Ok(input) = input.try_into() { window.controller.on_keyboard_input(input)}, -                            ModifiersChanged(state) => window.controller.on_keyboard_modifier_change(state), -                            ReceivedCharacter(character) => window.controller.on_character_input(character), -                            HoveredFile(path) => window.controller.on_file_hover(&path), -                            HoveredFileCancelled => window.controller.on_file_hover_cancel(), -                            DroppedFile(path) => window.controller.on_file_drop(&path), - -                            CloseRequested => { -                                window.controller.on_close_request(); -                                *control_flow = ControlFlow::Exit; -                            }, -                            Destroyed => window.controller.on_close(), - -                            Ime(_) => (), -                            AxisMotion { .. } => (), -                            Touch(_) => (), -                            TouchpadRotate { .. } => (), -                            TouchpadPressure { .. } => (), -                            TouchpadMagnify { .. } => (), -                            SmartMagnify { .. } => (), -                            ScaleFactorChanged { .. } => (), -                            ThemeChanged(_) => (), -                            Occluded(_) => (), -                        } -                    } -                } - -                // Called before any render events are called. -                Event::MainEventsCleared => { -                    for window in self.windows.values_mut() { -                        window.controller.on_process(); -                        window.update_title(); -                        window.update_window_size(); -                        window.update_cursor(); -                        window.handle_render_request(); -                    } -                } - -                // Called if a window has requested to be rendered. -                Event::RedrawRequested(window_id) => { -                    if let Some(window) = self.windows.get_mut(&window_id) { -                        window.render(); -                    } -                } - -                // Called after all rendering has completed, or if there were no render requests. -                Event::RedrawEventsCleared => (), - -                // Called before the program closes. -                Event::LoopDestroyed => (), - -                _ => (), -            } -        }) -    } -} diff --git a/src/window_program.rs b/src/window_program.rs new file mode 100644 index 0000000..9a5df2d --- /dev/null +++ b/src/window_program.rs @@ -0,0 +1,13 @@ +use crate::*; + +use buffer::Buffer; +use event_queue::*; + + +pub trait WindowProgram { +    fn handle_event(&mut self, event: Event, requests: &mut EventWriter<Request>); + +    fn process(&mut self, requests: &mut EventWriter<Request>); + +    fn render(&mut self, buffer: &mut Buffer, full: bool); +} | 
