summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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);
+}