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.
This commit is contained in:
Jonathan Johnson 2023-12-21 14:57:29 -08:00
parent 4c9e2d5989
commit f1a2a711ff
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
12 changed files with 431 additions and 102 deletions

View file

@ -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 - Many bounds required `UnwindSafe` due to a misunderstanding on how to handle
this trait in `appit`. All requirements for `UnwindSafe` have been removed. 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 ### 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. validations. This was already available on `when` conditioned validations.
- `Dynamic::[try_]compare_swap` allows swapping the contents of a dynamic after - `Dynamic::[try_]compare_swap` allows swapping the contents of a dynamic after
verifying the current contents. 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) ## v0.1.3 (2023-12-19)

164
Cargo.lock generated
View file

@ -2,6 +2,22 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 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]] [[package]]
name = "addr2line" name = "addr2line"
version = "0.21.0" version = "0.21.0"
@ -53,9 +69,9 @@ checksum = "b072fc284b73a3e4154e2decdbaad711daca0e8fedfceb0d7b1cbe2dffb00e2b"
[[package]] [[package]]
name = "android-activity" name = "android-activity"
version = "0.5.0" version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "052ad56e336bcc615a214bffbeca6c181ee9550acec193f0327e0b103b033a4d" checksum = "39b801912a977c3fd52d80511fe1c0c8480c6f957f21ae2ce1b92ffe970cf4b9"
dependencies = [ dependencies = [
"android-properties", "android-properties",
"bitflags 2.4.1", "bitflags 2.4.1",
@ -90,7 +106,7 @@ dependencies = [
[[package]] [[package]]
name = "appit" name = "appit"
version = "0.1.1" version = "0.1.1"
source = "git+https://github.com/khonsulabs/appit#36a413865b6ac93e04b8a32023397714a165304d" source = "git+https://github.com/khonsulabs/appit#1e162ed8df4470522d6dbfb1567b54df74ba911f"
dependencies = [ dependencies = [
"winit", "winit",
] ]
@ -120,9 +136,15 @@ dependencies = [
"parking_lot", "parking_lot",
"thiserror", "thiserror",
"winapi", "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]] [[package]]
name = "arrayvec" name = "arrayvec"
version = "0.7.4" version = "0.7.4"
@ -805,6 +827,16 @@ dependencies = [
"winapi", "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]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.11" version = "0.2.11"
@ -1147,7 +1179,7 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
[[package]] [[package]]
name = "kludgine" name = "kludgine"
version = "0.6.1" version = "0.6.1"
source = "git+https://github.com/khonsulabs/kludgine#bb1a2cc237b353ebd91bef62109b9bb8e3cf32e7" source = "git+https://github.com/khonsulabs/kludgine#76a378ca124b2f965049e65aada1b0e839b37aff"
dependencies = [ dependencies = [
"ahash", "ahash",
"alot", "alot",
@ -1452,8 +1484,7 @@ dependencies = [
"log", "log",
"ndk-sys", "ndk-sys",
"num_enum", "num_enum",
"raw-window-handle 0.5.2", "raw-window-handle",
"raw-window-handle 0.6.0",
"thiserror", "thiserror",
] ]
@ -1637,6 +1668,15 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 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]] [[package]]
name = "palette" name = "palette"
version = "0.7.3" version = "0.7.3"
@ -1827,9 +1867,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.70" version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@ -1961,12 +2001,6 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9"
[[package]]
name = "raw-window-handle"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42a9830a0e1b9fb145ebb365b8bc4ccd75f290f98c0247deafbbe2c75cefb544"
[[package]] [[package]]
name = "rayon" name = "rayon"
version = "1.8.0" version = "1.8.0"
@ -2127,6 +2161,19 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 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]] [[package]]
name = "self_cell" name = "self_cell"
version = "1.0.3" version = "1.0.3"
@ -2269,6 +2316,12 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"
[[package]]
name = "strict-num"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
[[package]] [[package]]
name = "svg_fmt" name = "svg_fmt"
version = "0.4.1" version = "0.4.1"
@ -2287,9 +2340,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.41" version = "2.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" checksum = "5b7d0a2c048d661a1a59fcd7355baa232f7ed34e0ee4df2eef3c1c1c0d3852d8"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2355,6 +2408,31 @@ dependencies = [
"weezl", "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]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.6.0" version = "1.6.0"
@ -2763,7 +2841,7 @@ dependencies = [
"naga", "naga",
"parking_lot", "parking_lot",
"profiling", "profiling",
"raw-window-handle 0.5.2", "raw-window-handle",
"smallvec", "smallvec",
"static_assertions", "static_assertions",
"wasm-bindgen", "wasm-bindgen",
@ -2788,7 +2866,7 @@ dependencies = [
"naga", "naga",
"parking_lot", "parking_lot",
"profiling", "profiling",
"raw-window-handle 0.5.2", "raw-window-handle",
"rustc-hash", "rustc-hash",
"smallvec", "smallvec",
"thiserror", "thiserror",
@ -2829,7 +2907,7 @@ dependencies = [
"parking_lot", "parking_lot",
"profiling", "profiling",
"range-alloc", "range-alloc",
"raw-window-handle 0.5.2", "raw-window-handle",
"renderdoc-sys", "renderdoc-sys",
"rustc-hash", "rustc-hash",
"smallvec", "smallvec",
@ -3116,9 +3194,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]] [[package]]
name = "winit" name = "winit"
version = "0.29.4" version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d25d662bb83b511acd839534bb2d88521b0bbc81440969cb077d23c4db9e62c7" checksum = "2cc935117ef48caed8822a893efef723e07ad868f668dfc4d244aea8873b07f9"
dependencies = [ dependencies = [
"ahash", "ahash",
"android-activity", "android-activity",
@ -3141,9 +3219,10 @@ dependencies = [
"once_cell", "once_cell",
"orbclient", "orbclient",
"percent-encoding", "percent-encoding",
"raw-window-handle 0.5.2", "raw-window-handle",
"redox_syscall 0.3.5", "redox_syscall 0.3.5",
"rustix", "rustix",
"sctk-adwaita",
"smithay-client-toolkit", "smithay-client-toolkit",
"smol_str", "smol_str",
"unicode-segmentation", "unicode-segmentation",
@ -3157,7 +3236,7 @@ dependencies = [
"web-time", "web-time",
"windows-sys 0.48.0", "windows-sys 0.48.0",
"x11-dl", "x11-dl",
"x11rb", "x11rb 0.13.0",
"xkbcommon-dl", "xkbcommon-dl",
] ]
@ -3187,15 +3266,26 @@ version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1641b26d4dec61337c35a1b1aaf9e3cba8f46f0b43636c609ab0291a648040a" checksum = "b1641b26d4dec61337c35a1b1aaf9e3cba8f46f0b43636c609ab0291a648040a"
dependencies = [ dependencies = [
"as-raw-xcb-connection", "gethostname 0.3.0",
"gethostname",
"libc",
"libloading 0.7.4",
"nix", "nix",
"once_cell",
"winapi", "winapi",
"winapi-wsapoll", "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]] [[package]]
@ -3207,6 +3297,12 @@ dependencies = [
"nix", "nix",
] ]
[[package]]
name = "x11rb-protocol"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e63e71c4b8bd9ffec2c963173a4dc4cbde9ee96961d4fcb4429db9929b606c34"
[[package]] [[package]]
name = "xcursor" name = "xcursor"
version = "0.3.5" version = "0.3.5"
@ -3267,18 +3363,18 @@ checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697"
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.7.31" version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c4061bedbb353041c12f413700357bec76df2c7e2ca8e4df8bac24c6bf68e3d" checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
dependencies = [ dependencies = [
"zerocopy-derive", "zerocopy-derive",
] ]
[[package]] [[package]]
name = "zerocopy-derive" name = "zerocopy-derive"
version = "0.7.31" version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a" checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View file

@ -46,6 +46,11 @@ unicode-segmentation = "1.10.1"
# alot = { git = "https://github.com/khonsulabs/alot" } # alot = { git = "https://github.com/khonsulabs/alot" }
# kempt = { path = "../objectmap" } # kempt = { path = "../objectmap" }
# [patch."https://github.com/khonsulabs/kludgine"]
# kludgine = { path = "../kludgine" }
# [patch."https://github.com/khonsulabs/appit"]
# appit = { path = "../appit" }
[profile.dev.package."*"] [profile.dev.package."*"]
opt-level = 2 opt-level = 2

View file

@ -2,7 +2,7 @@ use gooey::value::Dynamic;
use gooey::widget::{MakeWidget, WidgetInstance}; use gooey::widget::{MakeWidget, WidgetInstance};
use gooey::widgets::container::ContainerShadow; use gooey::widgets::container::ContainerShadow;
use gooey::window::ThemeMode; use gooey::window::ThemeMode;
use gooey::{Gooey, Run}; use gooey::Run;
use kludgine::figures::units::Lp; use kludgine::figures::units::Lp;
use kludgine::figures::Point; use kludgine::figures::Point;
@ -10,7 +10,7 @@ fn main() -> gooey::Result {
let theme_mode = Dynamic::default(); let theme_mode = Dynamic::default();
set_of_containers(3, theme_mode.clone()) set_of_containers(3, theme_mode.clone())
.centered() .centered()
.into_window(Gooey::default()) .into_window()
.themed_mode(theme_mode) .themed_mode(theme_mode)
.run() .run()
} }

57
examples/multi-window.rs Normal file
View file

@ -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<usize>,
counter: &Dynamic<usize>,
) -> 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<usize>,
counter: &Dynamic<usize>,
) {
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");
}

View file

@ -13,13 +13,13 @@ use gooey::widgets::input::InputValue;
use gooey::widgets::slider::Slidable; use gooey::widgets::slider::Slidable;
use gooey::widgets::Space; use gooey::widgets::Space;
use gooey::window::ThemeMode; use gooey::window::ThemeMode;
use gooey::{Gooey, Run}; use gooey::{Open, PendingApp};
use kludgine::figures::units::Lp; use kludgine::figures::units::Lp;
use kludgine::Color; use kludgine::Color;
use palette::OklabHue; use palette::OklabHue;
fn main() -> gooey::Result { fn main() -> gooey::Result {
let gooey = Gooey::default(); let app = PendingApp::default();
let (theme_mode, theme_switcher) = dark_mode_picker(); let (theme_mode, theme_switcher) = dark_mode_picker();
@ -79,7 +79,7 @@ fn main() -> gooey::Result {
.and(editors.neutral.1) .and(editors.neutral.1)
.and(editors.neutral_variant.1) .and(editors.neutral_variant.1)
.and("Copy to Clipboard".into_button().on_click({ .and("Copy to Clipboard".into_button().on_click({
let gooey = gooey.clone(); let gooey = app.gooey().clone();
move |()| { move |()| {
if let Some(mut clipboard) = gooey.clipboard_guard() { if let Some(mut clipboard) = gooey.clipboard_guard() {
let builder = color_scheme_builder.get(); let builder = color_scheme_builder.get();
@ -115,9 +115,9 @@ fn main() -> gooey::Result {
.themed(theme) .themed(theme)
.pad() .pad()
.expand() .expand()
.into_window(gooey) .into_window()
.themed_mode(theme_mode) .themed_mode(theme_mode)
.run() .run_in(app)
} }
struct Scheme<Primary, Other = Primary> { struct Scheme<Primary, Other = Primary> {

View file

@ -8,7 +8,8 @@ fn main() -> gooey::Result {
let occluded = Dynamic::new(false); let occluded = Dynamic::new(false);
let inner_size = Dynamic::new(Size::default()); 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(occluded.map_each(|v| format!("occluded: {:?}", v)))
.and(inner_size.map_each(|v| format!("inner_size: {:?}", v))) .and(inner_size.map_each(|v| format!("inner_size: {:?}", v)))
.into_rows() .into_rows()

View file

@ -1,5 +1,5 @@
# gooey-macros # 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 [gooey]: https://github.com/khonsulabs/gooey

View file

@ -1,24 +1,55 @@
use std::sync::{Arc, Mutex, MutexGuard}; use std::sync::{Arc, Mutex, MutexGuard};
use arboard::Clipboard; use arboard::Clipboard;
use kludgine::app::{AppEvent, AsApplication};
use crate::utils::IgnorePoison; 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<WindowCommand>,
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<AppEvent<WindowCommand>> for PendingApp {
fn as_application(&self) -> &dyn kludgine::app::Application<AppEvent<WindowCommand>> {
self.app.as_application()
}
}
/// Shared resources for a GUI application.
#[derive(Clone)] #[derive(Clone)]
pub struct Gooey { pub struct Gooey {
pub(crate) clipboard: Option<Arc<Mutex<Clipboard>>>, pub(crate) clipboard: Option<Arc<Mutex<Clipboard>>>,
} }
impl Default for Gooey {
fn default() -> Self {
Self {
clipboard: Clipboard::new()
.ok()
.map(|clipboard| Arc::new(Mutex::new(clipboard))),
}
}
}
impl Gooey { impl Gooey {
/// Returns a locked mutex guard to the OS's clipboard, if one was able to be /// Returns a locked mutex guard to the OS's clipboard, if one was able to be
/// initialized when the window opened. /// initialized when the window opened.
@ -29,3 +60,68 @@ impl Gooey {
.map(|mutex| mutex.lock().ignore_poison()) .map(|mutex| mutex.lock().ignore_poison())
} }
} }
/// A type that is a Gooey application.
pub trait Application: AsApplication<AppEvent<WindowCommand>> {
/// 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<WindowCommand>,
gooey: Gooey,
}
impl Application for App {
fn gooey(&self) -> &Gooey {
&self.gooey
}
fn as_app(&self) -> App {
self.clone()
}
}
impl AsApplication<AppEvent<WindowCommand>> for App {
fn as_application(&self) -> &dyn kludgine::app::Application<AppEvent<WindowCommand>> {
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<App>(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;
}

View file

@ -23,7 +23,7 @@ pub mod widgets;
pub mod window; pub mod window;
use std::ops::Sub; use std::ops::Sub;
pub use app::Gooey; pub use app::{App, Application, Gooey, Open, PendingApp, Run};
pub use kludgine; pub use kludgine;
use kludgine::app::winit::error::EventLoopError; use kludgine::app::winit::error::EventLoopError;
use kludgine::figures::units::UPx; use kludgine::figures::units::UPx;
@ -117,14 +117,6 @@ impl Sub<UPx> for ConstraintLimit {
/// this crate. /// this crate.
pub type Result<T = (), E = EventLoopError> = std::result::Result<T, E>; pub type Result<T = (), E = EventLoopError> = std::result::Result<T, E>;
/// 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. /// Counts the number of expressions passed to it.
/// ///
/// This is used inside of Gooey macros to preallocate collections. /// This is used inside of Gooey macros to preallocate collections.

View file

@ -18,7 +18,7 @@ use kludgine::figures::units::{Px, UPx};
use kludgine::figures::{IntoSigned, IntoUnsigned, Point, Rect, Size}; use kludgine::figures::{IntoSigned, IntoUnsigned, Point, Rect, Size};
use kludgine::Color; use kludgine::Color;
use crate::app::Gooey; use crate::app::{Application, Open, PendingApp, Run};
use crate::context::sealed::WindowHandle; use crate::context::sealed::WindowHandle;
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext, WidgetContext}; use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext, WidgetContext};
use crate::styles::components::{ use crate::styles::components::{
@ -44,7 +44,7 @@ use crate::widgets::{
Style, Themed, ThemedMode, Validated, Wrap, Style, Themed, ThemedMode, Validated, Wrap,
}; };
use crate::window::{RunningWindow, ThemeMode, Window, WindowBehavior}; use crate::window::{RunningWindow, ThemeMode, Window, WindowBehavior};
use crate::{ConstraintLimit, Run}; use crate::ConstraintLimit;
/// A type that makes up a graphical user interface. /// A type that makes up a graphical user interface.
/// ///
@ -459,7 +459,24 @@ where
T: MakeWidget, T: MakeWidget,
{ {
fn run(self) -> crate::Result { fn run(self) -> crate::Result {
self.make_widget().run() Window::<WidgetInstance>::new(self.make_widget()).run()
}
}
impl<T> Open for T
where
T: MakeWidget,
{
fn open<App>(self, app: &App) -> crate::Result
where
App: Application,
{
Window::<WidgetInstance>::new(self.make_widget()).open(app)
}
fn run_in(self, app: PendingApp) -> crate::Result {
Window::<WidgetInstance>::new(self.make_widget()).open(&app)?;
app.run()
} }
} }
@ -900,8 +917,8 @@ pub trait MakeWidget: Sized {
fn make_widget(self) -> WidgetInstance; fn make_widget(self) -> WidgetInstance;
/// Returns a new window containing `self` as the root widget. /// Returns a new window containing `self` as the root widget.
fn into_window(self, gooey: Gooey) -> Window<WidgetInstance> { fn into_window(self) -> Window<WidgetInstance> {
Window::new(self.make_widget(), gooey.clone()) Window::new(self.make_widget())
} }
/// Associates `styles` with this widget. /// Associates `styles` with this widget.
@ -1512,11 +1529,6 @@ impl WidgetInstance {
WidgetGuard(self.data.widget.lock().ignore_poison()) WidgetGuard(self.data.widget.lock().ignore_poison())
} }
/// Runs this widget instance as an application.
pub fn run(self) -> crate::Result {
Window::<WidgetInstance>::new(self, Gooey::default()).run()
}
/// Returns the id of the widget that should receive focus after this /// Returns the id of the widget that should receive focus after this
/// widget. /// widget.
/// ///

View file

@ -26,7 +26,7 @@ use kludgine::Kludgine;
use tracing::Level; use tracing::Level;
use crate::animation::{LinearInterpolate, PercentBetween, ZeroToOne}; use crate::animation::{LinearInterpolate, PercentBetween, ZeroToOne};
use crate::app::Gooey; use crate::app::{Application, Gooey, Open, PendingApp, Run};
use crate::context::{ use crate::context::{
AsEventContext, EventContext, Exclusive, GraphicsContext, InvalidationStatus, LayoutContext, AsEventContext, EventContext, Exclusive, GraphicsContext, InvalidationStatus, LayoutContext,
WidgetContext, WidgetContext,
@ -37,10 +37,11 @@ use crate::tree::Tree;
use crate::utils::ModifiersExt; use crate::utils::ModifiersExt;
use crate::value::{Dynamic, DynamicReader, Generation, IntoDynamic, IntoValue, Value}; use crate::value::{Dynamic, DynamicReader, Generation, IntoDynamic, IntoValue, Value};
use crate::widget::{ 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::window::sealed::WindowCommand;
use crate::{initialize_tracing, ConstraintLimit, Run}; use crate::{initialize_tracing, ConstraintLimit};
/// A currently running Gooey window. /// A currently running Gooey window.
pub struct RunningWindow<'window> { pub struct RunningWindow<'window> {
@ -125,7 +126,6 @@ where
Behavior: WindowBehavior, Behavior: WindowBehavior,
{ {
context: Behavior::Context, context: Behavior::Context,
gooey: Gooey,
/// The attributes of this window. /// The attributes of this window.
pub attributes: WindowAttributes, pub attributes: WindowAttributes,
/// The colors to use to theme the user interface. /// The colors to use to theme the user interface.
@ -152,6 +152,7 @@ where
/// during drawing operations. /// during drawing operations.
pub font_data_to_load: Vec<Vec<u8>>, pub font_data_to_load: Vec<Vec<u8>>,
on_closed: Option<OnceCallback>,
inner_size: Option<Dynamic<Size<UPx>>>, inner_size: Option<Dynamic<Size<UPx>>>,
occluded: Option<Dynamic<bool>>, occluded: Option<Dynamic<bool>>,
focused: Option<Dynamic<bool>>, focused: Option<Dynamic<bool>>,
@ -164,8 +165,7 @@ where
Behavior::Context: Default, Behavior::Context: Default,
{ {
fn default() -> Self { fn default() -> Self {
let context = Behavior::Context::default(); Self::new(Behavior::Context::default())
Self::new(context, Gooey::default())
} }
} }
@ -175,7 +175,7 @@ impl Window<WidgetInstance> {
where where
W: Widget, 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 /// Sets `focused` to be the dynamic updated when this window's focus status
@ -252,6 +252,15 @@ impl Window<WidgetInstance> {
self.font_data_to_load.push(font_data); self.font_data_to_load.push(font_data);
self self
} }
/// Invokes `on_close` when this window is closed.
pub fn on_close<Function>(mut self, on_close: Function) -> Self
where
Function: FnOnce() + Send + 'static,
{
self.on_closed = Some(OnceCallback::new(|()| on_close()));
self
}
} }
impl<Behavior> Window<Behavior> impl<Behavior> Window<Behavior>
@ -260,7 +269,7 @@ where
{ {
/// Returns a new instance using `context` to initialize the window upon /// Returns a new instance using `context` to initialize the window upon
/// opening. /// opening.
pub fn new(context: Behavior::Context, gooey: Gooey) -> Self { pub fn new(context: Behavior::Context) -> Self {
static EXECUTABLE_NAME: OnceLock<String> = OnceLock::new(); static EXECUTABLE_NAME: OnceLock<String> = OnceLock::new();
let title = EXECUTABLE_NAME let title = EXECUTABLE_NAME
@ -281,7 +290,7 @@ where
title, title,
..WindowAttributes::default() ..WindowAttributes::default()
}, },
gooey, on_closed: None,
context, context,
load_system_fonts: true, load_system_fonts: true,
theme: Value::default(), theme: Value::default(),
@ -308,25 +317,51 @@ where
{ {
fn run(self) -> crate::Result { fn run(self) -> crate::Result {
initialize_tracing(); initialize_tracing();
GooeyWindow::<Behavior>::run_with(sealed::Context { let app = PendingApp::default();
user: self.context, self.open(&app)?;
settings: RefCell::new(sealed::WindowSettings { app.run()
gooey: self.gooey, }
transparent: self.attributes.transparent, }
attributes: Some(self.attributes),
occluded: self.occluded, impl<Behavior> Open for Window<Behavior>
focused: self.focused, where
inner_size: self.inner_size, Behavior: WindowBehavior,
theme: Some(self.theme), {
theme_mode: self.theme_mode, fn open<App>(self, app: &App) -> crate::Result
font_data_to_load: self.font_data_to_load, where
serif_font_family: self.serif_font_family, App: Application,
sans_serif_font_family: self.sans_serif_font_family, {
fantasy_font_family: self.fantasy_font_family, let gooey = app.gooey().clone();
monospace_font_family: self.monospace_font_family, let _handle = GooeyWindow::<Behavior>::open_with(
cursive_font_family: self.cursive_font_family, 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`. /// Runs this behavior as an application, initialized with `context`.
fn run_with(context: Self::Context) -> crate::Result { fn run_with(context: Self::Context) -> crate::Result {
Window::<Self>::new(context, Gooey::default()).run() Window::<Self>::new(context).run()
} }
} }
@ -385,6 +420,7 @@ struct GooeyWindow<T> {
transparent: bool, transparent: bool,
fonts: FontState, fonts: FontState,
gooey: Gooey, gooey: Gooey,
on_closed: Option<OnceCallback>,
} }
impl<T> GooeyWindow<T> impl<T> GooeyWindow<T>
@ -580,6 +616,7 @@ where
let focused = settings.focused.take().unwrap_or_default(); let focused = settings.focused.take().unwrap_or_default();
let theme = settings.theme.take().expect("theme always present"); let theme = settings.theme.take().expect("theme always present");
let inner_size = settings.inner_size.take().unwrap_or_default(); let inner_size = settings.inner_size.take().unwrap_or_default();
let on_closed = settings.on_closed.take();
inner_size.set(window.inner_size()); inner_size.set(window.inner_size());
@ -676,6 +713,7 @@ where
transparent, transparent,
fonts, fonts,
gooey, gooey,
on_closed,
} }
} }
@ -1298,6 +1336,14 @@ where
} }
} }
impl<Behavior> Drop for GooeyWindow<Behavior> {
fn drop(&mut self) {
if let Some(on_closed) = self.on_closed.take() {
on_closed.invoke(());
}
}
}
fn recursively_handle_event( fn recursively_handle_event(
context: &mut EventContext<'_, '_>, context: &mut EventContext<'_, '_>,
mut each_widget: impl FnMut(&mut EventContext<'_, '_>) -> EventHandling, mut each_widget: impl FnMut(&mut EventContext<'_, '_>) -> EventHandling,
@ -1322,10 +1368,11 @@ pub(crate) mod sealed {
use kludgine::figures::units::UPx; use kludgine::figures::units::UPx;
use kludgine::figures::Size; use kludgine::figures::Size;
use crate::app::Gooey;
use crate::styles::{FontFamilyList, ThemePair}; use crate::styles::{FontFamilyList, ThemePair};
use crate::value::{Dynamic, Value}; use crate::value::{Dynamic, Value};
use crate::widget::OnceCallback;
use crate::window::{ThemeMode, WindowAttributes}; use crate::window::{ThemeMode, WindowAttributes};
use crate::Gooey;
pub struct Context<C> { pub struct Context<C> {
pub user: C, pub user: C,
@ -1347,6 +1394,7 @@ pub(crate) mod sealed {
pub monospace_font_family: FontFamilyList, pub monospace_font_family: FontFamilyList,
pub cursive_font_family: FontFamilyList, pub cursive_font_family: FontFamilyList,
pub font_data_to_load: Vec<Vec<u8>>, pub font_data_to_load: Vec<Vec<u8>>,
pub on_closed: Option<OnceCallback>,
} }
#[derive(Clone)] #[derive(Clone)]