From 76a378ca124b2f965049e65aada1b0e839b37aff Mon Sep 17 00:00:00 2001 From: Jonathan Johnson Date: Thu, 21 Dec 2023 14:55:42 -0800 Subject: [PATCH] Finished multi-window support --- CHANGELOG.md | 9 ++ Cargo.lock | 78 +++++++++++++++- Cargo.toml | 3 + src/app.rs | 254 ++++++++++++++++++++++++++++++++++----------------- 4 files changed, 260 insertions(+), 84 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f175c11db..6005f10b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 and various types may or may no longer implmement `UnwindSafe`. The underlying requirement for this has been removed from `appit`. +## Added + +- `app::PendingApp` is a type that allows opening one or more windows before + running an application. +- `app::App` is a handle to a running application. +- `app::Window::app()` returns a handle to the application of the window. +- `WindowBehavior::open[_with]()` are new functions that allow opening a window + into a reference of an `App` or `PendingApp`. + ## v0.6.1 (2023-12-19) ### Fixed diff --git a/Cargo.lock b/Cargo.lock index f5b680874..a82965d4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,22 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ab_glyph" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80179d7dd5d7e8c285d67c4a1e652972a92de7475beddfb92028c76463b13225" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" + [[package]] name = "addr2line" version = "0.21.0" @@ -81,11 +97,17 @@ dependencies = [ [[package]] name = "appit" version = "0.1.1" -source = "git+https://github.com/khonsulabs/appit#36a413865b6ac93e04b8a32023397714a165304d" +source = "git+https://github.com/khonsulabs/appit#1e162ed8df4470522d6dbfb1567b54df74ba911f" dependencies = [ "winit", ] +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + [[package]] name = "arrayvec" version = "0.7.4" @@ -1311,6 +1333,15 @@ dependencies = [ "libredox", ] +[[package]] +name = "owned_ttf_parser" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4586edfe4c648c71797a74c84bacb32b52b212eff5dfe2bb9f2c599844023e7" +dependencies = [ + "ttf-parser 0.20.0", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -1589,6 +1620,19 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sctk-adwaita" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729a30a469de249c6effc17ec8d039b0aa29b3af79b819b7f51cb6ab8046a90" +dependencies = [ + "ab_glyph", + "log", + "memmap2 0.9.3", + "smithay-client-toolkit", + "tiny-skia", +] + [[package]] name = "self_cell" version = "1.0.3" @@ -1704,6 +1748,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" + [[package]] name = "svg_fmt" version = "0.4.1" @@ -1780,6 +1830,31 @@ dependencies = [ "weezl", ] +[[package]] +name = "tiny-skia" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6a067b809476893fce6a254cf285850ff69c847e6cfbade6a20b655b6c7e80d" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de35e8a90052baaaf61f171680ac2f8e925a1e43ea9d2e3a00514772250e541" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -2518,6 +2593,7 @@ dependencies = [ "raw-window-handle 0.5.2", "redox_syscall 0.3.5", "rustix", + "sctk-adwaita", "smithay-client-toolkit", "smol_str", "unicode-segmentation", diff --git a/Cargo.toml b/Cargo.toml index a4858aaa6..52171269f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,9 @@ image = { version = "0.24.6", features = ["png"] } # intentional = { path = "../intentional" } # appit = { path = "../appit" } +# [patch."https://github.com/khonsulabs/appit"] +# appit = { path = "../appit" } + # [patch."https://github.com/khonsulabs/figures"] # figures = { path = "../figures" } diff --git a/src/app.rs b/src/app.rs index 7160eef0e..92109c08c 100644 --- a/src/app.rs +++ b/src/app.rs @@ -6,15 +6,15 @@ use std::sync::{Arc, OnceLock}; use std::time::{Duration, Instant}; use appit::winit::dpi::{PhysicalPosition, PhysicalSize}; -use appit::winit::error::EventLoopError; +use appit::winit::error::{EventLoopError, OsError}; use appit::winit::event::{ AxisId, DeviceId, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta, Touch, TouchPhase, }; use appit::winit::keyboard::PhysicalKey; use appit::winit::window::{ImePurpose, Theme, WindowId}; -pub use appit::{winit, Message, WindowAttributes}; -use appit::{Application, PendingApp, RunningWindow, WindowBehavior as _}; +pub use appit::{winit, Application, AsApplication, Message, WindowAttributes}; +use appit::{RunningWindow, WindowBehavior as _}; use figures::units::{Px, UPx}; use figures::{Point, Rect, Size}; use intentional::{Assert, Cast}; @@ -28,12 +28,87 @@ fn shared_wgpu() -> Arc { SHARED_WGPU.get_or_init(Arc::default).clone() } +/// A `Kludgine` application that enables opening multiple windows. +pub struct PendingApp(appit::PendingApp>) +where + AppEvent: Message; + +impl Default for PendingApp +where + AppEvent: Message, +{ + fn default() -> Self { + Self::new() + } +} + +impl AsApplication> for PendingApp +where + AppEvent: Message, +{ + fn as_application(&self) -> &dyn Application> + where + AppEvent: Message, + { + &self.0 + } +} + +impl PendingApp +where + AppEvent: Message, +{ + /// Creates a new Kludgine application. + #[allow(unsafe_code)] + #[must_use] + #[allow(clippy::missing_panics_doc)] // The panics are in a closure that happens only after the app is running. + pub fn new() -> Self { + Self(appit::PendingApp::new_with_event_callback( + |request: AppEvent, windows: &appit::Windows| { + let window = windows.get(request.0.window).expect("window not found"); + // SAFETY: The winit window is valid and open when it is + // contained in the windows collection. The surface being + // created is stored on the KludgineWindow, which is dropped + // prior to the appit/winit window closing. + unsafe { + shared_wgpu() + .create_surface(&*window) + .expect("error creating surface") + } + }, + )) + } + + /// Returns a handle to the application that will be run. + #[must_use] + pub fn as_app(&self) -> App { + self.0.app() + } + + /// Begins running the application. + /// + /// On some platforms, this function may never return. If it does return, it + /// is after the application has been shut down. + /// + /// # Errors + /// + /// Returns an [`EventLoopError`] upon the loop exiting due to an error. See + /// [`EventLoop::run`](winit::event_loop::EventLoop::run) for more + /// information. + pub fn run(self) -> Result<(), EventLoopError> { + self.0.run() + } +} + +/// A handle to a running Kludgine application. +pub type App = appit::App>; + /// An open window. pub struct Window<'window, WindowEvent = ()> where WindowEvent: Send + 'static, { - window: &'window mut RunningWindow>, + window: &'window mut RunningWindow>, elapsed: Duration, last_frame_rendered_in: Duration, } @@ -43,7 +118,7 @@ where WindowEvent: Send + 'static, { fn new( - window: &'window mut RunningWindow>, + window: &'window mut RunningWindow>, elapsed: Duration, last_frame_rendered_in: Duration, ) -> Self { @@ -61,6 +136,15 @@ where WindowHandle(self.window.handle()) } + /// Returns a handle to the application. + /// + /// This is useful for opening additional windows in a multi-window + /// application. + #[must_use] + pub fn app(&self) -> App { + self.window.app() + } + /// Returns the current position of the window. #[must_use] pub fn position(&self) -> Point { @@ -333,28 +417,56 @@ where /// Returns an [`EventLoopError`] upon the loop exiting due to an error. See /// [`EventLoop::run`](appit::winit::event_loop::EventLoop::run) for more /// information. - #[allow(unsafe_code)] fn run_with(context: Self::Context) -> Result<(), EventLoopError> { let window_attributes = Self::initial_window_attributes(&context); - let app = PendingApp::new_with_event_callback( - |request: CreateSurfaceRequest, windows: &appit::Windows| { - let window = windows.get(request.window).expect("window not found"); - // SAFETY: The winit window is valid and open when it is - // contained in the windows collection. The surface being - // created is stored on the KludgineWindow, which is dropped - // prior to the appit/winit window closing. - unsafe { - shared_wgpu() - .create_surface(&*window) - .expect("error creating surface") - } - }, - ); - let mut window = KludgineWindow::::build_with(&app, context); + let app = PendingApp::new(); + let mut window = KludgineWindow::::build_with(&app.0, context); *window = window_attributes; window.open().expect("error opening initial window"); - app.run() + app.0.run() + } + + /// Opens a new window with a default instance of this behavior's + /// [`Context`](Self::Context). The events of the window will be processed + /// in a thread spawned by this function. + /// + /// If the application has shut down, this function returns None. + /// + /// # Errors + /// + /// The only errors this funciton can return arise from + /// [`winit::window::WindowBuilder::build`]. + fn open(app: &App) -> Result>, OsError> + where + App: AsApplication>, + Self::Context: Default, + { + KludgineWindow::::build(app) + .open() + .map(|opt| opt.map(WindowHandle)) + } + + /// Opens a new window with the provided [`Context`](Self::Context). The + /// events of the window will be processed in a thread spawned by this + /// function. + /// + /// If the application has shut down, this function returns None. + /// + /// # Errors + /// + /// The only errors this funciton can return arise from + /// [`winit::window::WindowBuilder::build`]. + fn open_with( + app: &App, + context: Self::Context, + ) -> Result>, OsError> + where + App: AsApplication>, + { + KludgineWindow::::build_with(app, context) + .open() + .map(|opt| opt.map(WindowHandle)) } /// The window has been requested to be closed. This can happen as a result @@ -580,12 +692,15 @@ where } } +/// A Kludgine application event. +pub struct AppEvent(CreateSurfaceRequest); + struct CreateSurfaceRequest { window: WindowId, data: PhantomData, } -impl Message for CreateSurfaceRequest +impl Message for AppEvent where User: Send + 'static, { @@ -608,22 +723,19 @@ struct KludgineWindow { _adapter: wgpu::Adapter, } -impl appit::WindowBehavior> for KludgineWindow +impl appit::WindowBehavior> for KludgineWindow where T: WindowBehavior + 'static, User: Send + 'static, { type Context = T::Context; - fn initialize( - window: &mut RunningWindow>, - context: Self::Context, - ) -> Self { + fn initialize(window: &mut RunningWindow>, context: Self::Context) -> Self { let surface = window - .send(CreateSurfaceRequest { + .send(AppEvent(CreateSurfaceRequest { window: window.winit().id(), data: PhantomData, - }) + })) .expect("app not running"); let wgpu = shared_wgpu(); let adapter = pollster::block_on(wgpu.request_adapter(&wgpu::RequestAdapterOptions { @@ -699,7 +811,7 @@ where } } - fn redraw(&mut self, window: &mut RunningWindow>) { + fn redraw(&mut self, window: &mut RunningWindow>) { let surface = loop { match self.surface.get_current_texture() { Ok(frame) => break frame, @@ -713,10 +825,10 @@ where } wgpu::SurfaceError::Lost => { self.surface = window - .send(CreateSurfaceRequest { + .send(AppEvent(CreateSurfaceRequest { window: window.winit().id(), data: PhantomData, - }) + })) .expect("app not running"); self.surface.configure(&self.device, &self.config); } @@ -809,7 +921,7 @@ where } } - fn close_requested(&mut self, window: &mut RunningWindow>) -> bool { + fn close_requested(&mut self, window: &mut RunningWindow>) -> bool { self.behavior.close_requested( Window::new( window, @@ -820,7 +932,7 @@ where ) } - fn focus_changed(&mut self, window: &mut RunningWindow>) { + fn focus_changed(&mut self, window: &mut RunningWindow>) { self.behavior.focus_changed( Window::new( window, @@ -831,7 +943,7 @@ where ); } - fn occlusion_changed(&mut self, window: &mut RunningWindow>) { + fn occlusion_changed(&mut self, window: &mut RunningWindow>) { self.behavior.occlusion_changed( Window::new( window, @@ -842,7 +954,7 @@ where ); } - fn resized(&mut self, window: &mut RunningWindow>) { + fn resized(&mut self, window: &mut RunningWindow>) { self.config.width = window.inner_size().width; self.config.height = window.inner_size().height; self.surface.configure(&self.device, &self.config); @@ -862,7 +974,7 @@ where ); } - fn scale_factor_changed(&mut self, window: &mut RunningWindow>) { + fn scale_factor_changed(&mut self, window: &mut RunningWindow>) { self.behavior.scale_factor_changed( Window::new( window, @@ -873,7 +985,7 @@ where ); } - fn theme_changed(&mut self, window: &mut RunningWindow>) { + fn theme_changed(&mut self, window: &mut RunningWindow>) { self.behavior.theme_changed( Window::new( window, @@ -884,11 +996,7 @@ where ); } - fn dropped_file( - &mut self, - window: &mut RunningWindow>, - path: PathBuf, - ) { + fn dropped_file(&mut self, window: &mut RunningWindow>, path: PathBuf) { self.behavior.dropped_file( Window::new( window, @@ -900,11 +1008,7 @@ where ); } - fn hovered_file( - &mut self, - window: &mut RunningWindow>, - path: PathBuf, - ) { + fn hovered_file(&mut self, window: &mut RunningWindow>, path: PathBuf) { self.behavior.hovered_file( Window::new( window, @@ -916,7 +1020,7 @@ where ); } - fn hovered_file_cancelled(&mut self, window: &mut RunningWindow>) { + fn hovered_file_cancelled(&mut self, window: &mut RunningWindow>) { self.behavior.hovered_file_cancelled( Window::new( window, @@ -927,11 +1031,7 @@ where ); } - fn received_character( - &mut self, - window: &mut RunningWindow>, - char: char, - ) { + fn received_character(&mut self, window: &mut RunningWindow>, char: char) { self.behavior.received_character( Window::new( window, @@ -945,7 +1045,7 @@ where fn keyboard_input( &mut self, - window: &mut RunningWindow>, + window: &mut RunningWindow>, device_id: DeviceId, event: KeyEvent, is_synthetic: bool, @@ -963,7 +1063,7 @@ where ); } - fn modifiers_changed(&mut self, window: &mut RunningWindow>) { + fn modifiers_changed(&mut self, window: &mut RunningWindow>) { self.behavior.modifiers_changed( Window::new( window, @@ -974,7 +1074,7 @@ where ); } - fn ime(&mut self, window: &mut RunningWindow>, ime: Ime) { + fn ime(&mut self, window: &mut RunningWindow>, ime: Ime) { self.behavior.ime( Window::new( window, @@ -988,7 +1088,7 @@ where fn cursor_moved( &mut self, - window: &mut RunningWindow>, + window: &mut RunningWindow>, device_id: DeviceId, position: PhysicalPosition, ) { @@ -1004,11 +1104,7 @@ where ); } - fn cursor_entered( - &mut self, - window: &mut RunningWindow>, - device_id: DeviceId, - ) { + fn cursor_entered(&mut self, window: &mut RunningWindow>, device_id: DeviceId) { self.behavior.cursor_entered( Window::new( window, @@ -1020,11 +1116,7 @@ where ); } - fn cursor_left( - &mut self, - window: &mut RunningWindow>, - device_id: DeviceId, - ) { + fn cursor_left(&mut self, window: &mut RunningWindow>, device_id: DeviceId) { self.behavior.cursor_left( Window::new( window, @@ -1038,7 +1130,7 @@ where fn mouse_wheel( &mut self, - window: &mut RunningWindow>, + window: &mut RunningWindow>, device_id: DeviceId, delta: MouseScrollDelta, phase: TouchPhase, @@ -1058,7 +1150,7 @@ where fn mouse_input( &mut self, - window: &mut RunningWindow>, + window: &mut RunningWindow>, device_id: DeviceId, state: ElementState, button: MouseButton, @@ -1078,7 +1170,7 @@ where fn touchpad_pressure( &mut self, - window: &mut RunningWindow>, + window: &mut RunningWindow>, device_id: DeviceId, pressure: f32, stage: i64, @@ -1098,7 +1190,7 @@ where fn axis_motion( &mut self, - window: &mut RunningWindow>, + window: &mut RunningWindow>, device_id: DeviceId, axis: AxisId, value: f64, @@ -1116,7 +1208,7 @@ where ); } - fn touch(&mut self, window: &mut RunningWindow>, touch: Touch) { + fn touch(&mut self, window: &mut RunningWindow>, touch: Touch) { self.behavior.touch( Window::new( window, @@ -1130,7 +1222,7 @@ where fn touchpad_magnify( &mut self, - window: &mut RunningWindow>, + window: &mut RunningWindow>, device_id: DeviceId, delta: f64, phase: TouchPhase, @@ -1148,11 +1240,7 @@ where ); } - fn smart_magnify( - &mut self, - window: &mut RunningWindow>, - device_id: DeviceId, - ) { + fn smart_magnify(&mut self, window: &mut RunningWindow>, device_id: DeviceId) { self.behavior.smart_magnify( Window::new( window, @@ -1166,7 +1254,7 @@ where fn touchpad_rotate( &mut self, - window: &mut RunningWindow>, + window: &mut RunningWindow>, device_id: DeviceId, delta: f32, phase: TouchPhase, @@ -1186,8 +1274,8 @@ where fn event( &mut self, - window: &mut RunningWindow>, - event: as Message>::Window, + window: &mut RunningWindow>, + event: as Message>::Window, ) { self.behavior.event( Window::new(