summaryrefslogtreecommitdiff
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
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.
-rw-r--r--Cargo.toml9
-rw-r--r--src/events.rs115
-rw-r--r--src/lib.rs63
-rw-r--r--src/phosphor.rs199
-rw-r--r--src/render.rs67
-rw-r--r--src/window.rs397
-rw-r--r--src/window_builder.rs30
-rw-r--r--src/window_controller.rs49
-rw-r--r--src/window_manager.rs139
-rw-r--r--src/window_program.rs13
10 files changed, 573 insertions, 508 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 3a45b37..35cbfaa 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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,
+ }
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 3255b0a..f856775 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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};
-
-pub struct Window {
- pub controller: Box<dyn WindowController>,
- window: winit::window::Window,
- pixel_scale: u32,
- buffer: Buffer,
- surface: softbuffer::Surface,
+use buffer::{Dimensions, HasDimensions};
+use event_queue::EventQueue;
+
+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 });
+ }
- 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;
- }
- 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);
- }
+ pub fn scale(&self) -> u32 {
+ self.scale
}
- /// 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_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);
}
}
- 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);
+ pub fn set_fullscreen(&mut self, fullscreen: bool) {
+ self.winit.set_fullscreen(
+ match fullscreen {
+ true => Some(Fullscreen::Borderless(None)),
+ false => None,
+ }
+ );
+ }
+
+ 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 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();
+ /// 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;
+
+ // 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);
+}