diff --git a/CHANGELOG.md b/CHANGELOG.md index fb7e961..5370531 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 initial value of the dynamic or whether to let the operating system perform the initial positioning. +### Changed + +- `Open` is now implemented for most types via a blanket implementation for a + new trait, `MakeWindow`. `MakeWindow` splits the process of creating a + `Window` from the process of opening a window. + + The new `MakeWindow` trait adds some new functionality: + `open_centered`/`run_centered`/`run_centered_in`. These functions present a + window centered on the screen where the window initially is shown. + ### Fixed - `Collapse`, `OverlayLayer`, and `Progress` all honor the theme components diff --git a/src/debug.rs b/src/debug.rs index 151af90..1560f59 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -7,8 +7,7 @@ use alot::OrderedLots; use crate::value::{Dynamic, DynamicReader, ForEach, Source, WeakDynamic}; use crate::widget::{MakeWidget, WidgetInstance, WidgetList}; use crate::widgets::grid::{Grid, GridWidgets}; -use crate::window::Window; -use crate::{Application, Open, PendingApp}; +use crate::window::{MakeWindow, Window}; /// A widget that can provide extra information when debugging. #[derive(Clone, Default)] @@ -96,14 +95,6 @@ impl DebugContext { Self { section } } - fn into_window(self) -> Window { - self.section - .map_ref(|section| section.widget.clone()) - // .vertical_scroll() - .into_window() - .titled("Cushy Debugger") - } - /// Returns true if this debug context has no child sections or observed /// values. #[must_use] @@ -114,16 +105,15 @@ impl DebugContext { } } -impl Open for DebugContext { - fn open(self, app: &mut App) -> crate::Result - where - App: Application + ?Sized, - { - self.into_window().open(app) - } +impl MakeWindow for DebugContext { + type Behavior = WidgetInstance; - fn run_in(self, app: PendingApp) -> crate::Result { - self.into_window().run_in(app) + fn make_window(self) -> Window { + self.section + .map_ref(|section| section.widget.clone()) + // .vertical_scroll() + .make_window() + .titled("Cushy Debugger") } } diff --git a/src/value.rs b/src/value.rs index 14e0085..c15ccb8 100644 --- a/src/value.rs +++ b/src/value.rs @@ -550,7 +550,6 @@ pub trait Destination { /// /// This function panics if this value is already locked by the current /// thread. - #[must_use] fn take(&self) -> T where Self: Source, @@ -1919,17 +1918,29 @@ where } } +#[derive(Default)] +struct ChangeCallbacksExecutor { + thread: Option, + callbacks_to_remove: Vec, +} + #[derive(Default)] struct ChangeCallbacksData { callbacks: Mutex, - currently_executing: Mutex>, + currently_executing: Mutex, sync: Condvar, } impl CallbackCollection for ChangeCallbacksData { fn remove(&self, id: LotId) { - let mut data = self.callbacks.lock(); - data.callbacks.remove(id); + let mut currently_executing = self.currently_executing.lock(); + if currently_executing.thread == Some(thread::current().id()) { + currently_executing.callbacks_to_remove.push(id); + } else { + drop(currently_executing); + let mut data = self.callbacks.lock(); + data.callbacks.remove(id); + } } } @@ -1957,12 +1968,12 @@ impl Drop for ChangeCallbacks { let mut currently_executing = self.data.currently_executing.lock(); let current_thread = thread::current().id(); loop { - match &*currently_executing { + match ¤tly_executing.thread { None => { // No other thread is executing these callbacks. Set this // thread as the current executor so that we can prevent // infinite cycles. - *currently_executing = Some(current_thread); + currently_executing.thread = Some(current_thread); drop(currently_executing); // Invoke the callbacks @@ -1978,12 +1989,15 @@ impl Drop for ChangeCallbacks { .callbacks .drain_filter(|callback| callback.changed().is_err()); } - drop(state); // Remove ourselves as the current executor, notifying any // other threads that are waiting. currently_executing = self.data.currently_executing.lock(); - *currently_executing = None; + currently_executing.thread = None; + for callback in currently_executing.callbacks_to_remove.drain(..) { + state.callbacks.remove(callback); + } + drop(state); drop(currently_executing); self.data.sync.notify_all(); diff --git a/src/widget.rs b/src/widget.rs index 2f20b9a..d7b9198 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -17,7 +17,7 @@ use kludgine::app::winit::window::CursorIcon; use kludgine::Color; use parking_lot::{Mutex, MutexGuard}; -use crate::app::{Application, Open, PendingApp, Run}; +use crate::app::Run; use crate::context::sealed::Trackable as _; use crate::context::{ AsEventContext, EventContext, GraphicsContext, LayoutContext, ManageWidget, WidgetContext, @@ -46,7 +46,7 @@ use crate::widgets::{ }; use crate::window::sealed::WindowCommand; use crate::window::{ - DeviceId, KeyEvent, Rgb8, RunningWindow, StandaloneWindowBuilder, ThemeMode, + DeviceId, KeyEvent, MakeWindow, Rgb8, RunningWindow, StandaloneWindowBuilder, ThemeMode, VirtualRecorderBuilder, Window, WindowBehavior, WindowHandle, WindowLocal, }; use crate::ConstraintLimit; @@ -470,23 +470,6 @@ where } // ANCHOR_END: run -impl Open for T -where - T: MakeWidget, -{ - fn open(self, app: &mut App) -> crate::Result - where - App: Application + ?Sized, - { - Window::::new(self.make_widget()).open(app) - } - - fn run_in(self, mut app: PendingApp) -> crate::Result { - Window::::new(self.make_widget()).open(&mut app)?; - app.run() - } -} - /// A behavior that should be applied to a root widget. #[derive(Debug, Clone, Copy)] pub enum RootBehavior { @@ -918,8 +901,8 @@ pub trait MakeWidget: Sized { fn make_widget(self) -> WidgetInstance; /// Returns a new window containing `self` as the root widget. - fn into_window(self) -> Window { - Window::new(self.make_widget()) + fn into_window(self) -> Window { + self.make_window() } /// Returns a builder for a standalone window. @@ -1611,7 +1594,7 @@ impl WidgetInstance { where Self: Clone, { - self.clone().into_window() + self.clone().make_window() } } diff --git a/src/window.rs b/src/window.rs index ee77891..7469cc6 100644 --- a/src/window.rs +++ b/src/window.rs @@ -562,7 +562,7 @@ where } } -impl Window { +impl Window { /// Returns a new instance using `widget` as its contents. pub fn for_widget(widget: W) -> Self where @@ -645,6 +645,63 @@ where } } + /// Returns the handle to this window. + pub const fn handle(&self) -> &WindowHandle { + &self.pending.0 + } + + /// Opens `self` in the center of the monitor the window initially appears + /// on. + pub fn open_centered(mut self, app: &mut App) -> crate::Result + where + App: Application + ?Sized, + { + // We want to ensure that if the user has customized any of these + // properties that we keep their dynamic. + let outer_position = self.outer_position.clone().unwrap_or_else(|| { + let outer_position = Dynamic::new(Point::default()); + self.outer_position = Some(outer_position.clone()); + outer_position + }); + let outer_size = self.outer_size.clone().unwrap_or_else(|| { + let outer_size = Dynamic::new(Size::default()); + self.outer_size = Some(outer_size.clone()); + outer_size + }); + let visible = self.visible.clone().unwrap_or_else(|| { + let visible = Dynamic::new(false); + self.visible = Some(visible.clone()); + visible + }); + visible.set(false); + + let callback_handle = Dynamic::new(None); + callback_handle.set(Some(outer_size.for_each_subsequent({ + let visible = visible.clone(); + let app = app.as_app(); + let callback_handle = callback_handle.clone(); + move |new_size| { + if let Some(monitor) = app.monitors().and_then(|monitors| { + let initial_position = outer_position.get(); + monitors + .available + .into_iter() + .find(|m| m.region().contains(initial_position)) + .or(monitors.primary) + }) { + let region = monitor.region(); + let margin = region.size - new_size.into_signed(); + outer_position.set(region.origin + margin / 2); + } + visible.set(true); + // Uninstall this callback to ensure it doesn't fire again. + let _ = callback_handle.take(); + } + }))); + + self.open(app) + } + /// Sets `focused` to be the dynamic updated when this window's focus status /// is changed. /// @@ -936,61 +993,62 @@ where } } -impl Open for Window +impl Open for T where - Behavior: WindowBehavior, + T: MakeWindow, { fn open(self, app: &mut App) -> crate::Result where App: Application + ?Sized, { + let this = self.make_window(); let cushy = app.cushy().clone(); - let handle = self.pending.handle(); - OpenWindow::::open_with( + let handle = this.pending.handle(); + OpenWindow::::open_with( app, sealed::Context { - user: self.context, + user: this.context, settings: RefCell::new(sealed::WindowSettings { cushy, - title: self.title, - redraw_status: self.pending.0.redraw_status.clone(), - on_open: self.on_open, - on_closed: self.on_closed, - transparent: self.attributes.transparent, - attributes: Some(self.attributes), - occluded: self.occluded.unwrap_or_default(), - focused: self.focused.unwrap_or_default(), - inner_size: self.inner_size.unwrap_or_default(), - theme: Some(self.theme), - theme_mode: self.theme_mode, - font_data_to_load: self.fonts, - 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, - vsync: self.vsync, - multisample_count: self.multisample_count, - close_requested: self.close_requested.map(|cb| Arc::new(Mutex::new(cb))), - zoom: self.zoom.unwrap_or_else(|| Dynamic::new(Fraction::ONE)), - resize_to_fit: self.resize_to_fit, - content_protected: self.content_protected.unwrap_or_default(), - cursor_hittest: self.cursor_hittest.unwrap_or_else(|| Value::Constant(true)), - cursor_visible: self.cursor_visible.unwrap_or_else(|| Value::Constant(true)), - cursor_position: self.cursor_position.unwrap_or_default(), - window_level: self.window_level.unwrap_or_default(), - decorated: self.decorated.unwrap_or_else(|| Value::Constant(true)), - maximized: self.maximized.unwrap_or_default(), - minimized: self.minimized.unwrap_or_default(), - resizable: self.resizable.unwrap_or_else(|| Value::Constant(true)), - resize_increments: self.resize_increments.unwrap_or_default(), - visible: self.visible.unwrap_or_default(), - inner_position: self.inner_position.unwrap_or_default(), - outer_position: self.outer_position.unwrap_or_default(), - outer_size: self.outer_size.unwrap_or_default(), - window_icon: self.icon.unwrap_or_default(), + title: this.title, + redraw_status: this.pending.0.redraw_status.clone(), + on_open: this.on_open, + on_closed: this.on_closed, + transparent: this.attributes.transparent, + attributes: Some(this.attributes), + occluded: this.occluded.unwrap_or_default(), + focused: this.focused.unwrap_or_default(), + inner_size: this.inner_size.unwrap_or_default(), + theme: Some(this.theme), + theme_mode: this.theme_mode, + font_data_to_load: this.fonts, + serif_font_family: this.serif_font_family, + sans_serif_font_family: this.sans_serif_font_family, + fantasy_font_family: this.fantasy_font_family, + monospace_font_family: this.monospace_font_family, + cursive_font_family: this.cursive_font_family, + vsync: this.vsync, + multisample_count: this.multisample_count, + close_requested: this.close_requested.map(|cb| Arc::new(Mutex::new(cb))), + zoom: this.zoom.unwrap_or_else(|| Dynamic::new(Fraction::ONE)), + resize_to_fit: this.resize_to_fit, + content_protected: this.content_protected.unwrap_or_default(), + cursor_hittest: this.cursor_hittest.unwrap_or_else(|| Value::Constant(true)), + cursor_visible: this.cursor_visible.unwrap_or_else(|| Value::Constant(true)), + cursor_position: this.cursor_position.unwrap_or_default(), + window_level: this.window_level.unwrap_or_default(), + decorated: this.decorated.unwrap_or_else(|| Value::Constant(true)), + maximized: this.maximized.unwrap_or_default(), + minimized: this.minimized.unwrap_or_default(), + resizable: this.resizable.unwrap_or_else(|| Value::Constant(true)), + resize_increments: this.resize_increments.unwrap_or_default(), + visible: this.visible.unwrap_or_default(), + inner_position: this.inner_position.unwrap_or_default(), + outer_position: this.outer_position.unwrap_or_default(), + outer_size: this.outer_size.unwrap_or_default(), + window_icon: this.icon.unwrap_or_default(), }), - pending: self.pending, + pending: this.pending, }, )?; @@ -1003,6 +1061,66 @@ where } } +/// A type that can be made into a [`Window`]. +pub trait MakeWindow { + /// The behavior associated with this window. + type Behavior: WindowBehavior; + + /// Returns a new window from `self`. + fn make_window(self) -> Window; + + /// Opens `self` in the center of the monitor the window initially appears + /// on. + fn open_centered(self, app: &mut App) -> crate::Result + where + Self: Sized, + App: Application + ?Sized, + { + self.make_window().open_centered(app) + } + + /// Runs `self` in the center of the monitor the window + /// initially appears on. + fn run_centered(self) -> crate::Result + where + Self: Sized, + { + self.make_window().run() + } + + /// Runs `app` after opening `self` in the center of the monitor the window + /// initially appears on. + fn run_centered_in(self, mut app: PendingApp) -> crate::Result + where + Self: Sized, + { + self.make_window().open_centered(&mut app)?; + app.run() + } +} + +impl MakeWindow for Window +where + Behavior: WindowBehavior, +{ + type Behavior = Behavior; + + fn make_window(self) -> Window { + self + } +} + +impl MakeWindow for T +where + T: MakeWidget, +{ + type Behavior = WidgetInstance; + + fn make_window(self) -> Window { + Window::for_widget(self.make_widget()) + } +} + /// The behavior of a Cushy window. pub trait WindowBehavior: Sized + 'static { /// The type that is provided when initializing this window. @@ -2863,7 +2981,7 @@ impl PendingWindow { } /// Returns a [`Window`] containing `root`. - pub fn with_root(self, root: impl MakeWidget) -> Window { + pub fn with_root(self, root: impl MakeWidget) -> Window { Window::new_with_pending(root.make_widget(), self) }