From f1a2a711ff27a0add010a0a4aeff843736b5f7b1 Mon Sep 17 00:00:00 2001 From: Jonathan Johnson Date: Thu, 21 Dec 2023 14:57:29 -0800 Subject: [PATCH] Multi-window support Closes #91 There's some details to still figure out, which are in new issues: - #109: When opening a window, no handle is returned that gives access to the window from the opener. Technically this can all be wired up manually, with exception of requeesting the window close. - #107: How can a window close itself? Once we have a handle type, we still need a mechanism to allow a button on a window request that the window closes gracefully. The examples that currently close the window call exit instad. --- CHANGELOG.md | 22 +++++ Cargo.lock | 164 +++++++++++++++++++++++++++------- Cargo.toml | 5 ++ examples/containers.rs | 4 +- examples/multi-window.rs | 57 ++++++++++++ examples/theme.rs | 10 +-- examples/window-properties.rs | 3 +- gooey-macros/README.md | 2 +- src/app.rs | 116 +++++++++++++++++++++--- src/lib.rs | 10 +-- src/widget.rs | 32 ++++--- src/window.rs | 108 +++++++++++++++------- 12 files changed, 431 insertions(+), 102 deletions(-) create mode 100644 examples/multi-window.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index fd1e602..1e6410f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Many bounds required `UnwindSafe` due to a misunderstanding on how to handle this trait in `appit`. All requirements for `UnwindSafe` have been removed. +- `Gooey` no longer implements default. To gain access to a `Gooey` instance, + create a `PendingApp` or get a reference to the running `App`. +- `Window::new` no longer accepts a `Gooey` parameter. The window now adopts the + `Gooey` from the application it is opened within. +- `MakeWidget::into_window()` no longer takes any parameters. ### Changed @@ -30,6 +35,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 validations. This was already available on `when` conditioned validations. - `Dynamic::[try_]compare_swap` allows swapping the contents of a dynamic after verifying the current contents. +- [#91][91]: Multi-window support has been implemented. `PendingApp` allows + opening one or more windows before starting the program. `App` is a handle to + the running application that can be used to open additional windows at + runtime. + + `Open` is a new trait that allows various types to open as a window given a + reference to an application. This trait is implemented for all types that + implemented `Run`, which means any type that was previously able to be run as + a standalone executable can now be opened as a window within a multi-window + application. + + The `multi-window` example demonstates using this feature to open multiple + windows before starting Gooey as well as dynamically opening windows at + runtime. +- `Window::on_close` sets a callback to be invoked when the window has closed. + +[91]: https://github.com/khonsulabs/gooey/issues/91 ## v0.1.3 (2023-12-19) diff --git a/Cargo.lock b/Cargo.lock index ca35007..81fb15b 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" @@ -53,9 +69,9 @@ checksum = "b072fc284b73a3e4154e2decdbaad711daca0e8fedfceb0d7b1cbe2dffb00e2b" [[package]] name = "android-activity" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052ad56e336bcc615a214bffbeca6c181ee9550acec193f0327e0b103b033a4d" +checksum = "39b801912a977c3fd52d80511fe1c0c8480c6f957f21ae2ce1b92ffe970cf4b9" dependencies = [ "android-properties", "bitflags 2.4.1", @@ -90,7 +106,7 @@ 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", ] @@ -120,9 +136,15 @@ dependencies = [ "parking_lot", "thiserror", "winapi", - "x11rb", + "x11rb 0.12.0", ] +[[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" @@ -805,6 +827,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "gethostname" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets 0.48.5", +] + [[package]] name = "getrandom" version = "0.2.11" @@ -1147,7 +1179,7 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "kludgine" version = "0.6.1" -source = "git+https://github.com/khonsulabs/kludgine#bb1a2cc237b353ebd91bef62109b9bb8e3cf32e7" +source = "git+https://github.com/khonsulabs/kludgine#76a378ca124b2f965049e65aada1b0e839b37aff" dependencies = [ "ahash", "alot", @@ -1452,8 +1484,7 @@ dependencies = [ "log", "ndk-sys", "num_enum", - "raw-window-handle 0.5.2", - "raw-window-handle 0.6.0", + "raw-window-handle", "thiserror", ] @@ -1637,6 +1668,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[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 = "palette" version = "0.7.3" @@ -1827,9 +1867,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.70" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8" dependencies = [ "unicode-ident", ] @@ -1961,12 +2001,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" -[[package]] -name = "raw-window-handle" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a9830a0e1b9fb145ebb365b8bc4ccd75f290f98c0247deafbbe2c75cefb544" - [[package]] name = "rayon" version = "1.8.0" @@ -2127,6 +2161,19 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sctk-adwaita" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b2eaf3a5b264a521b988b2e73042e742df700c4f962cde845d1541adb46550" +dependencies = [ + "ab_glyph", + "log", + "memmap2 0.9.3", + "smithay-client-toolkit", + "tiny-skia", +] + [[package]] name = "self_cell" version = "1.0.3" @@ -2269,6 +2316,12 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" +[[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" @@ -2287,9 +2340,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.41" +version = "2.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" +checksum = "5b7d0a2c048d661a1a59fcd7355baa232f7ed34e0ee4df2eef3c1c1c0d3852d8" dependencies = [ "proc-macro2", "quote", @@ -2355,6 +2408,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" @@ -2763,7 +2841,7 @@ dependencies = [ "naga", "parking_lot", "profiling", - "raw-window-handle 0.5.2", + "raw-window-handle", "smallvec", "static_assertions", "wasm-bindgen", @@ -2788,7 +2866,7 @@ dependencies = [ "naga", "parking_lot", "profiling", - "raw-window-handle 0.5.2", + "raw-window-handle", "rustc-hash", "smallvec", "thiserror", @@ -2829,7 +2907,7 @@ dependencies = [ "parking_lot", "profiling", "range-alloc", - "raw-window-handle 0.5.2", + "raw-window-handle", "renderdoc-sys", "rustc-hash", "smallvec", @@ -3116,9 +3194,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winit" -version = "0.29.4" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25d662bb83b511acd839534bb2d88521b0bbc81440969cb077d23c4db9e62c7" +checksum = "2cc935117ef48caed8822a893efef723e07ad868f668dfc4d244aea8873b07f9" dependencies = [ "ahash", "android-activity", @@ -3141,9 +3219,10 @@ dependencies = [ "once_cell", "orbclient", "percent-encoding", - "raw-window-handle 0.5.2", + "raw-window-handle", "redox_syscall 0.3.5", "rustix", + "sctk-adwaita", "smithay-client-toolkit", "smol_str", "unicode-segmentation", @@ -3157,7 +3236,7 @@ dependencies = [ "web-time", "windows-sys 0.48.0", "x11-dl", - "x11rb", + "x11rb 0.13.0", "xkbcommon-dl", ] @@ -3187,15 +3266,26 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1641b26d4dec61337c35a1b1aaf9e3cba8f46f0b43636c609ab0291a648040a" dependencies = [ - "as-raw-xcb-connection", - "gethostname", - "libc", - "libloading 0.7.4", + "gethostname 0.3.0", "nix", - "once_cell", "winapi", "winapi-wsapoll", - "x11rb-protocol", + "x11rb-protocol 0.12.0", +] + +[[package]] +name = "x11rb" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8f25ead8c7e4cba123243a6367da5d3990e0d3affa708ea19dce96356bd9f1a" +dependencies = [ + "as-raw-xcb-connection", + "gethostname 0.4.3", + "libc", + "libloading 0.8.1", + "once_cell", + "rustix", + "x11rb-protocol 0.13.0", ] [[package]] @@ -3207,6 +3297,12 @@ dependencies = [ "nix", ] +[[package]] +name = "x11rb-protocol" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e63e71c4b8bd9ffec2c963173a4dc4cbde9ee96961d4fcb4429db9929b606c34" + [[package]] name = "xcursor" version = "0.3.5" @@ -3267,18 +3363,18 @@ checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697" [[package]] name = "zerocopy" -version = "0.7.31" +version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c4061bedbb353041c12f413700357bec76df2c7e2ca8e4df8bac24c6bf68e3d" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.31" +version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 3a61aa9..a399635 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,11 @@ unicode-segmentation = "1.10.1" # alot = { git = "https://github.com/khonsulabs/alot" } # kempt = { path = "../objectmap" } +# [patch."https://github.com/khonsulabs/kludgine"] +# kludgine = { path = "../kludgine" } +# [patch."https://github.com/khonsulabs/appit"] +# appit = { path = "../appit" } + [profile.dev.package."*"] opt-level = 2 diff --git a/examples/containers.rs b/examples/containers.rs index 2ed377e..9b30d60 100644 --- a/examples/containers.rs +++ b/examples/containers.rs @@ -2,7 +2,7 @@ use gooey::value::Dynamic; use gooey::widget::{MakeWidget, WidgetInstance}; use gooey::widgets::container::ContainerShadow; use gooey::window::ThemeMode; -use gooey::{Gooey, Run}; +use gooey::Run; use kludgine::figures::units::Lp; use kludgine::figures::Point; @@ -10,7 +10,7 @@ fn main() -> gooey::Result { let theme_mode = Dynamic::default(); set_of_containers(3, theme_mode.clone()) .centered() - .into_window(Gooey::default()) + .into_window() .themed_mode(theme_mode) .run() } diff --git a/examples/multi-window.rs b/examples/multi-window.rs new file mode 100644 index 0000000..1687610 --- /dev/null +++ b/examples/multi-window.rs @@ -0,0 +1,57 @@ +use gooey::value::{Dynamic, MapEach}; +use gooey::widget::MakeWidget; +use gooey::{Application, Open, PendingApp, Run}; + +fn main() -> gooey::Result { + let app = PendingApp::default(); + + let open_windows = Dynamic::new(0_usize); + let counter = Dynamic::new(0_usize); + + (&open_windows, &counter) + .map_each(|(open, counter)| { + format!( + "There are {open} other window(s) open. {counter} total windows have been opened" + ) + }) + .and(open_window_button(&app, &open_windows, &counter)) + .into_rows() + .centered() + .open(&app)?; + + app.run() +} + +fn open_window_button( + app: &impl Application, + open_windows: &Dynamic, + counter: &Dynamic, +) -> impl MakeWidget { + let app = app.as_app(); + let open_windows = open_windows.clone(); + let counter = counter.clone(); + "Open Another Window".into_button().on_click(move |()| { + open_another_window(&app, &open_windows, &counter); + }) +} + +fn open_another_window( + app: &impl Application, + open_windows: &Dynamic, + counter: &Dynamic, +) { + let my_number = counter.map_mut(|count| { + *count += 1; + *count + }); + let open_windows = open_windows.clone(); + open_windows.map_mut(|open_windows| *open_windows += 1); + format!("This is window {my_number}") + .and(open_window_button(app, &open_windows, counter)) + .into_rows() + .centered() + .into_window() + .on_close(move || open_windows.map_mut(|open_windows| *open_windows -= 1)) + .open(app) + .expect("error opening another window"); +} diff --git a/examples/theme.rs b/examples/theme.rs index d57e004..5093462 100644 --- a/examples/theme.rs +++ b/examples/theme.rs @@ -13,13 +13,13 @@ use gooey::widgets::input::InputValue; use gooey::widgets::slider::Slidable; use gooey::widgets::Space; use gooey::window::ThemeMode; -use gooey::{Gooey, Run}; +use gooey::{Open, PendingApp}; use kludgine::figures::units::Lp; use kludgine::Color; use palette::OklabHue; fn main() -> gooey::Result { - let gooey = Gooey::default(); + let app = PendingApp::default(); let (theme_mode, theme_switcher) = dark_mode_picker(); @@ -79,7 +79,7 @@ fn main() -> gooey::Result { .and(editors.neutral.1) .and(editors.neutral_variant.1) .and("Copy to Clipboard".into_button().on_click({ - let gooey = gooey.clone(); + let gooey = app.gooey().clone(); move |()| { if let Some(mut clipboard) = gooey.clipboard_guard() { let builder = color_scheme_builder.get(); @@ -115,9 +115,9 @@ fn main() -> gooey::Result { .themed(theme) .pad() .expand() - .into_window(gooey) + .into_window() .themed_mode(theme_mode) - .run() + .run_in(app) } struct Scheme { diff --git a/examples/window-properties.rs b/examples/window-properties.rs index e0fd174..46bf8c3 100644 --- a/examples/window-properties.rs +++ b/examples/window-properties.rs @@ -8,7 +8,8 @@ fn main() -> gooey::Result { let occluded = Dynamic::new(false); let inner_size = Dynamic::new(Size::default()); - let widgets = focused.map_each(|v| format!("focused: {:?}", v)) + let widgets = focused + .map_each(|v| format!("focused: {:?}", v)) .and(occluded.map_each(|v| format!("occluded: {:?}", v))) .and(inner_size.map_each(|v| format!("inner_size: {:?}", v))) .into_rows() diff --git a/gooey-macros/README.md b/gooey-macros/README.md index 16ceb6e..d7d5d65 100644 --- a/gooey-macros/README.md +++ b/gooey-macros/README.md @@ -1,5 +1,5 @@ # gooey-macros -This crate contains procedural macros that [`Gooey`][gooey] exposes. +This crate contains procedural macros that [Gooey][gooey] exposes. [gooey]: https://github.com/khonsulabs/gooey diff --git a/src/app.rs b/src/app.rs index e610d30..ec3e599 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,24 +1,55 @@ use std::sync::{Arc, Mutex, MutexGuard}; use arboard::Clipboard; +use kludgine::app::{AppEvent, AsApplication}; use crate::utils::IgnorePoison; +use crate::window::sealed::WindowCommand; -/// A GUI application. +/// A Gooey application that has not started running yet. +pub struct PendingApp { + app: kludgine::app::PendingApp, + gooey: Gooey, +} + +impl PendingApp { + /// The shared resources this application utilizes. + pub const fn gooey(&self) -> &Gooey { + &self.gooey + } +} + +impl Run for PendingApp { + fn run(self) -> crate::Result { + self.app.run() + } +} + +impl Default for PendingApp { + fn default() -> Self { + Self { + app: kludgine::app::PendingApp::default(), + gooey: Gooey { + clipboard: Clipboard::new() + .ok() + .map(|clipboard| Arc::new(Mutex::new(clipboard))), + }, + } + } +} + +impl AsApplication> for PendingApp { + fn as_application(&self) -> &dyn kludgine::app::Application> { + self.app.as_application() + } +} + +/// Shared resources for a GUI application. #[derive(Clone)] pub struct Gooey { pub(crate) clipboard: Option>>, } -impl Default for Gooey { - fn default() -> Self { - Self { - clipboard: Clipboard::new() - .ok() - .map(|clipboard| Arc::new(Mutex::new(clipboard))), - } - } -} impl Gooey { /// Returns a locked mutex guard to the OS's clipboard, if one was able to be /// initialized when the window opened. @@ -29,3 +60,68 @@ impl Gooey { .map(|mutex| mutex.lock().ignore_poison()) } } + +/// A type that is a Gooey application. +pub trait Application: AsApplication> { + /// Returns the shared resources for the application. + fn gooey(&self) -> &Gooey; + /// Returns this type as an [`App`] handle. + fn as_app(&self) -> App; +} + +impl Application for PendingApp { + fn gooey(&self) -> &Gooey { + &self.gooey + } + + fn as_app(&self) -> App { + App { + app: self.app.as_app(), + gooey: self.gooey.clone(), + } + } +} + +/// A handle to a Gooey application. +#[derive(Clone)] +pub struct App { + app: kludgine::app::App, + gooey: Gooey, +} + +impl Application for App { + fn gooey(&self) -> &Gooey { + &self.gooey + } + + fn as_app(&self) -> App { + self.clone() + } +} + +impl AsApplication> for App { + fn as_application(&self) -> &dyn kludgine::app::Application> { + self.app.as_application() + } +} + +/// A type that can be run as an application. +pub trait Run: Sized { + /// Runs the provided type, returning `Ok(())` upon successful execution and + /// program exit. Note that this function may not ever return on some + /// platforms. + fn run(self) -> crate::Result; +} + +/// A type that can be opened as a window in an application. +pub trait Open: Sized { + /// Opens the provided type as a window inside of `app`. + fn open(self, app: &App) -> crate::Result + where + App: Application; + + /// Runs the provided type inside of the pending `app`, returning `Ok(())` + /// upon successful execution and program exit. Note that this function may + /// not ever return on some platforms. + fn run_in(self, app: PendingApp) -> crate::Result; +} diff --git a/src/lib.rs b/src/lib.rs index 65a7bce..2dd36af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,7 +23,7 @@ pub mod widgets; pub mod window; use std::ops::Sub; -pub use app::Gooey; +pub use app::{App, Application, Gooey, Open, PendingApp, Run}; pub use kludgine; use kludgine::app::winit::error::EventLoopError; use kludgine::figures::units::UPx; @@ -117,14 +117,6 @@ impl Sub for ConstraintLimit { /// this crate. pub type Result = std::result::Result; -/// A type that can be run as an application. -pub trait Run: Sized { - /// Runs the provided type, returning `Ok(())` upon successful execution and - /// program exit. Note that this function may not ever return on some - /// platforms. - fn run(self) -> crate::Result; -} - /// Counts the number of expressions passed to it. /// /// This is used inside of Gooey macros to preallocate collections. diff --git a/src/widget.rs b/src/widget.rs index da10b4e..d1d3ecf 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -18,7 +18,7 @@ use kludgine::figures::units::{Px, UPx}; use kludgine::figures::{IntoSigned, IntoUnsigned, Point, Rect, Size}; use kludgine::Color; -use crate::app::Gooey; +use crate::app::{Application, Open, PendingApp, Run}; use crate::context::sealed::WindowHandle; use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext, WidgetContext}; use crate::styles::components::{ @@ -44,7 +44,7 @@ use crate::widgets::{ Style, Themed, ThemedMode, Validated, Wrap, }; use crate::window::{RunningWindow, ThemeMode, Window, WindowBehavior}; -use crate::{ConstraintLimit, Run}; +use crate::ConstraintLimit; /// A type that makes up a graphical user interface. /// @@ -459,7 +459,24 @@ where T: MakeWidget, { fn run(self) -> crate::Result { - self.make_widget().run() + Window::::new(self.make_widget()).run() + } +} + +impl Open for T +where + T: MakeWidget, +{ + fn open(self, app: &App) -> crate::Result + where + App: Application, + { + Window::::new(self.make_widget()).open(app) + } + + fn run_in(self, app: PendingApp) -> crate::Result { + Window::::new(self.make_widget()).open(&app)?; + app.run() } } @@ -900,8 +917,8 @@ pub trait MakeWidget: Sized { fn make_widget(self) -> WidgetInstance; /// Returns a new window containing `self` as the root widget. - fn into_window(self, gooey: Gooey) -> Window { - Window::new(self.make_widget(), gooey.clone()) + fn into_window(self) -> Window { + Window::new(self.make_widget()) } /// Associates `styles` with this widget. @@ -1512,11 +1529,6 @@ impl WidgetInstance { WidgetGuard(self.data.widget.lock().ignore_poison()) } - /// Runs this widget instance as an application. - pub fn run(self) -> crate::Result { - Window::::new(self, Gooey::default()).run() - } - /// Returns the id of the widget that should receive focus after this /// widget. /// diff --git a/src/window.rs b/src/window.rs index 5eda6f4..9a88e05 100644 --- a/src/window.rs +++ b/src/window.rs @@ -26,7 +26,7 @@ use kludgine::Kludgine; use tracing::Level; use crate::animation::{LinearInterpolate, PercentBetween, ZeroToOne}; -use crate::app::Gooey; +use crate::app::{Application, Gooey, Open, PendingApp, Run}; use crate::context::{ AsEventContext, EventContext, Exclusive, GraphicsContext, InvalidationStatus, LayoutContext, WidgetContext, @@ -37,10 +37,11 @@ use crate::tree::Tree; use crate::utils::ModifiersExt; use crate::value::{Dynamic, DynamicReader, Generation, IntoDynamic, IntoValue, Value}; use crate::widget::{ - EventHandling, MountedWidget, RootBehavior, Widget, WidgetId, WidgetInstance, HANDLED, IGNORED, + EventHandling, MountedWidget, OnceCallback, RootBehavior, Widget, WidgetId, WidgetInstance, + HANDLED, IGNORED, }; use crate::window::sealed::WindowCommand; -use crate::{initialize_tracing, ConstraintLimit, Run}; +use crate::{initialize_tracing, ConstraintLimit}; /// A currently running Gooey window. pub struct RunningWindow<'window> { @@ -125,7 +126,6 @@ where Behavior: WindowBehavior, { context: Behavior::Context, - gooey: Gooey, /// The attributes of this window. pub attributes: WindowAttributes, /// The colors to use to theme the user interface. @@ -152,6 +152,7 @@ where /// during drawing operations. pub font_data_to_load: Vec>, + on_closed: Option, inner_size: Option>>, occluded: Option>, focused: Option>, @@ -164,8 +165,7 @@ where Behavior::Context: Default, { fn default() -> Self { - let context = Behavior::Context::default(); - Self::new(context, Gooey::default()) + Self::new(Behavior::Context::default()) } } @@ -175,7 +175,7 @@ impl Window { where W: Widget, { - Self::new(WidgetInstance::new(widget), Gooey::default()) + Self::new(WidgetInstance::new(widget)) } /// Sets `focused` to be the dynamic updated when this window's focus status @@ -252,6 +252,15 @@ impl Window { self.font_data_to_load.push(font_data); self } + + /// Invokes `on_close` when this window is closed. + pub fn on_close(mut self, on_close: Function) -> Self + where + Function: FnOnce() + Send + 'static, + { + self.on_closed = Some(OnceCallback::new(|()| on_close())); + self + } } impl Window @@ -260,7 +269,7 @@ where { /// Returns a new instance using `context` to initialize the window upon /// opening. - pub fn new(context: Behavior::Context, gooey: Gooey) -> Self { + pub fn new(context: Behavior::Context) -> Self { static EXECUTABLE_NAME: OnceLock = OnceLock::new(); let title = EXECUTABLE_NAME @@ -281,7 +290,7 @@ where title, ..WindowAttributes::default() }, - gooey, + on_closed: None, context, load_system_fonts: true, theme: Value::default(), @@ -308,25 +317,51 @@ where { fn run(self) -> crate::Result { initialize_tracing(); - GooeyWindow::::run_with(sealed::Context { - user: self.context, - settings: RefCell::new(sealed::WindowSettings { - gooey: self.gooey, - transparent: self.attributes.transparent, - attributes: Some(self.attributes), - occluded: self.occluded, - focused: self.focused, - inner_size: self.inner_size, - theme: Some(self.theme), - theme_mode: self.theme_mode, - font_data_to_load: self.font_data_to_load, - serif_font_family: self.serif_font_family, - sans_serif_font_family: self.sans_serif_font_family, - fantasy_font_family: self.fantasy_font_family, - monospace_font_family: self.monospace_font_family, - cursive_font_family: self.cursive_font_family, - }), - }) + let app = PendingApp::default(); + self.open(&app)?; + app.run() + } +} + +impl Open for Window +where + Behavior: WindowBehavior, +{ + fn open(self, app: &App) -> crate::Result + where + App: Application, + { + let gooey = app.gooey().clone(); + let _handle = GooeyWindow::::open_with( + app, + sealed::Context { + user: self.context, + settings: RefCell::new(sealed::WindowSettings { + gooey, + on_closed: self.on_closed, + transparent: self.attributes.transparent, + attributes: Some(self.attributes), + occluded: self.occluded, + focused: self.focused, + inner_size: self.inner_size, + theme: Some(self.theme), + theme_mode: self.theme_mode, + font_data_to_load: self.font_data_to_load, + serif_font_family: self.serif_font_family, + sans_serif_font_family: self.sans_serif_font_family, + fantasy_font_family: self.fantasy_font_family, + monospace_font_family: self.monospace_font_family, + cursive_font_family: self.cursive_font_family, + }), + }, + )?; + + Ok(()) + } + + fn run_in(self, app: PendingApp) -> crate::Result { + self.open(&app)?; + app.run() } } @@ -359,7 +394,7 @@ pub trait WindowBehavior: Sized + 'static { /// Runs this behavior as an application, initialized with `context`. fn run_with(context: Self::Context) -> crate::Result { - Window::::new(context, Gooey::default()).run() + Window::::new(context).run() } } @@ -385,6 +420,7 @@ struct GooeyWindow { transparent: bool, fonts: FontState, gooey: Gooey, + on_closed: Option, } impl GooeyWindow @@ -580,6 +616,7 @@ where let focused = settings.focused.take().unwrap_or_default(); let theme = settings.theme.take().expect("theme always present"); let inner_size = settings.inner_size.take().unwrap_or_default(); + let on_closed = settings.on_closed.take(); inner_size.set(window.inner_size()); @@ -676,6 +713,7 @@ where transparent, fonts, gooey, + on_closed, } } @@ -1298,6 +1336,14 @@ where } } +impl Drop for GooeyWindow { + fn drop(&mut self) { + if let Some(on_closed) = self.on_closed.take() { + on_closed.invoke(()); + } + } +} + fn recursively_handle_event( context: &mut EventContext<'_, '_>, mut each_widget: impl FnMut(&mut EventContext<'_, '_>) -> EventHandling, @@ -1322,10 +1368,11 @@ pub(crate) mod sealed { use kludgine::figures::units::UPx; use kludgine::figures::Size; + use crate::app::Gooey; use crate::styles::{FontFamilyList, ThemePair}; use crate::value::{Dynamic, Value}; + use crate::widget::OnceCallback; use crate::window::{ThemeMode, WindowAttributes}; - use crate::Gooey; pub struct Context { pub user: C, @@ -1347,6 +1394,7 @@ pub(crate) mod sealed { pub monospace_font_family: FontFamilyList, pub cursive_font_family: FontFamilyList, pub font_data_to_load: Vec>, + pub on_closed: Option, } #[derive(Clone)]