From bf78da333d74a7a3ed9b9292a77efe39fde6705b Mon Sep 17 00:00:00 2001 From: Jonathan Johnson Date: Fri, 6 Sep 2024 11:50:35 -0700 Subject: [PATCH] App::monitors + run()/on_startup() Closes #163 --- CHANGELOG.md | 8 +++++++ Cargo.lock | 6 ++--- Cargo.toml | 4 +++- examples/monitors.rs | 53 ++++++++++++++++++++++++++++++++++++++++++++ src/app.rs | 38 ++++++++++++++++++++++++++++++- src/lib.rs | 11 +++++++++ 6 files changed, 114 insertions(+), 6 deletions(-) create mode 100644 examples/monitors.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a08f7a..6af5ce2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Window::resize_increments` - `Window::transparent` - `Window::visible` +- `run(&mut App)` is a new function that can provide a more concise way of + executing applications that would normally require using `PendingApp`. +- `PendingApp::on_startup` allows executing a function once the application's + event loop has begun. +- `App::monitors()` returns a snapshot of the currently configured monitors + attached to the device. A new example demonstrating this API is available at + `examples/monitors.rs`. + [139]: https://github.com/khonsulabs/cushy/issues/139 diff --git a/Cargo.lock b/Cargo.lock index 5644045..bedd204 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -124,8 +124,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "appit" version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a158c9d2660ce603c741d513b44cdd97a4553e0749d4e49e45b9480fc72c162" +source = "git+https://github.com/khonsulabs/appit#4cab6aedd10d179ed730397dc48ca6b23af6a431" dependencies = [ "winit", ] @@ -1310,8 +1309,7 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "kludgine" version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bc8757e03860f7a9a15e9616060ed620a12500a61c9ff9b457cccfc5f20af1f" +source = "git+https://github.com/khonsulabs/kludgine#a468845f84cbee812778f91fe55cf02716efebd8" dependencies = [ "ahash", "alot", diff --git a/Cargo.toml b/Cargo.toml index 21fa6a7..d96b3fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,9 @@ tokio = ["dep:tokio"] tokio-multi-thread = ["tokio", "tokio/rt-multi-thread"] [dependencies] -kludgine = { version = "0.10.0", features = ["app"] } +kludgine = { git = "https://github.com/khonsulabs/kludgine", features = [ + "app", +] } figures = { version = "0.4.0" } alot = "0.3" interner = "0.2.1" diff --git a/examples/monitors.rs b/examples/monitors.rs new file mode 100644 index 0000000..c4bf33a --- /dev/null +++ b/examples/monitors.rs @@ -0,0 +1,53 @@ +use cushy::widget::{MakeWidget, WidgetInstance, WidgetList}; +use cushy::{Application, Open, PendingApp}; +use kludgine::app::{Monitor, Monitors}; + +fn main() -> cushy::Result { + // Monitor information is only available through winit after the application + // has started up. + assert!(PendingApp::default().as_app().monitors().is_none()); + + cushy::run(|app| { + let monitors = app.monitors(); + + "Monitors" + .h1() + .and(list_monitors(monitors)) + .into_rows() + .vertical_scroll() + .expand() + .open(app) + .expect("app running"); + }) +} + +fn list_monitors(monitors: Option) -> WidgetInstance { + if let Some(monitors) = monitors { + monitors + .available + .into_iter() + .enumerate() + .map(|(index, monitor)| monitor_info(index, monitor, monitors.primary.as_ref())) + .collect::() + .into_rows() + .make_widget() + } else { + "No monitor information available".make_widget() + } +} + +fn monitor_info(index: usize, monitor: Monitor, primary: Option<&Monitor>) -> impl MakeWidget { + let mut name = monitor + .name() + .unwrap_or_else(|| format!("Monitor {}", index + 1)); + if primary.map_or(false, |primary| primary == &monitor) { + name.push_str(" (Primary)"); + } + let region = monitor.region(); + let region = format!( + "{},{} @ {}x{}", + region.origin.x, region.origin.y, region.size.width, region.size.height + ); + + name.h3().and(region).into_rows().contain() +} diff --git a/src/app.rs b/src/app.rs index 862ef64..b700baa 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,8 +1,9 @@ use std::marker::PhantomData; use std::sync::Arc; +use std::thread; use arboard::Clipboard; -use kludgine::app::{AppEvent, AsApplication}; +use kludgine::app::{AppEvent, AsApplication, Monitors}; use parking_lot::{Mutex, MutexGuard}; use crate::animation; @@ -30,6 +31,30 @@ impl PendingApp { pub const fn cushy(&self) -> &Cushy { &self.cushy } + + /// Executes `on_startup` once the application event loop has begun. + /// + /// Some APIs are not available until after the application has started + /// running. For example, `App::monitors` requires the event loop to have + /// been started. + pub fn on_startup(&mut self, on_startup: F) + where + F: FnOnce(&mut App) + Send + 'static, + { + let mut app = self.as_app(); + self.app.on_startup(move |_app| { + // Accessing some information from this closure needs to use `_app` + // instead of `App`. For example, accessing monitor information + // requires the window thread to respond to a message. Trying to do + // that in this closure would cause the thread to block. So, we + // execute our on_startup callbacks in their own thread. + thread::spawn(move || { + let cushy = app.cushy.clone(); + let _guard = cushy.enter_runtime(); + on_startup(&mut app); + }); + }); + } } impl Run for PendingApp { @@ -293,6 +318,17 @@ pub struct App { cushy: Cushy, } +impl App { + /// Returns a snapshot of information about the monitors connected to this + /// device. + /// + /// Returns None if the app is not currently running. + #[must_use] + pub fn monitors(&self) -> Option { + self.app.as_ref().and_then(kludgine::app::App::monitors) + } +} + impl Application for App { fn cushy(&self) -> &Cushy { &self.cushy diff --git a/src/lib.rs b/src/lib.rs index ba49f43..5287ad1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,6 +45,17 @@ pub use {figures, kludgine}; pub use self::graphics::Graphics; pub use self::tick::{InputState, Tick}; +/// Starts running a Cushy application, invoking `app_init` after the event loop +/// has started. +pub fn run(app_init: F) -> Result +where + F: FnOnce(&mut App) + Send + 'static, +{ + let mut app = PendingApp::default(); + app.on_startup(app_init); + app.run() +} + /// A limit used when measuring a widget. #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum ConstraintLimit {