From dd4c544ba6dc6a84da9ff7524f0331f70d2ce3dc Mon Sep 17 00:00:00 2001 From: Jonathan Johnson Date: Sun, 8 Sep 2024 11:29:29 -0700 Subject: [PATCH] Window sync edge cases - synchronize_platform_window is now called prior to the first redraw. This allows the `visible` attribute to be changed from false to true. - Some window attributes are automatically set based on the incoming dynamic. - Some initial window values are delayed until after the first layout to minimze "noisy" values. All of these changes now allow a window that is resize_to_fit to be initially hidden and show itself centered after being initially resized without any flashing or on-screen movement/resizing. --- CHANGELOG.md | 8 +++ examples/window-properties.rs | 2 +- src/value.rs | 7 +++ src/window.rs | 109 ++++++++++++++++++++++++++-------- 4 files changed, 99 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a9c848dc..fb7e961c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 `PlatformWindowImplementation` parameter. - `PlatformWindowImplementation::position` has been renamed to `PlatformWindowImplementation::outer_position`. +- `Window::position` has been renamed to `Window::outer_position` and takes an + additional parameter controlling whether to position the window with the + initial value of the dynamic or whether to let the operating system perform + the initial positioning. ### Fixed @@ -29,6 +33,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 together. - `Switcher` now unmounts child widgets in all windows it is mounted in. Fixes [#139][139]. +- `inner_size` and `outer_size` are now initialized after the first layout is + performed. This ensures that when `resize_to_fit` is used, the first observed + values will be the resized values. ### Added @@ -89,6 +96,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `outer_size` - `#[cushy::main]` is a new attribute proc-macro that simplifies initializing and running multi-window applications. +- `Window::on_open` executes a callback when the window is initially opened. [139]: https://github.com/khonsulabs/cushy/issues/139 diff --git a/examples/window-properties.rs b/examples/window-properties.rs index f76a0706e..cbc39a069 100644 --- a/examples/window-properties.rs +++ b/examples/window-properties.rs @@ -38,7 +38,7 @@ fn main(app: &mut App) { .inner_size(inner_size) .outer_size(outer_size) .inner_position(inner_position) - .outer_position(outer_position) + .outer_position(outer_position, true) .maximized(maximized) .minimized(minimized) .icon(Some(icon.into_rgba8())) diff --git a/src/value.rs b/src/value.rs index c765ef475..14e008595 100644 --- a/src/value.rs +++ b/src/value.rs @@ -3895,6 +3895,13 @@ where } } + /// Marks the initial value as read and returns self. + #[must_use] + pub fn ignoring_first(mut self) -> Self { + self.unread = false; + self + } + /// Updates this tracked instance's cached value from the source. /// /// Returns true if a value hasn't been read yet. diff --git a/src/window.rs b/src/window.rs index 72561c4c9..ee7789159 100644 --- a/src/window.rs +++ b/src/window.rs @@ -528,6 +528,7 @@ where pub resize_to_fit: Value, on_closed: Option, + on_open: Option>, inner_size: Option>>, zoom: Option>, occluded: Option>, @@ -601,6 +602,7 @@ where pending, title: Value::Constant(title), attributes: WindowAttributes::default(), + on_open: None, on_closed: None, context, load_system_fonts: true, @@ -680,7 +682,12 @@ where /// the dynamic is updated with a new value, a resize request will be made /// with the new inner size. pub fn inner_size(mut self, inner_size: impl IntoDynamic>) -> Self { - self.inner_size = Some(inner_size.into_dynamic()); + let inner_size = inner_size.into_dynamic(); + let initial_size = inner_size.get(); + if initial_size.width > 0 && initial_size.height > 0 { + self.attributes.inner_size = Some(winit::dpi::Size::Physical(initial_size.into())); + } + self.inner_size = Some(inner_size); self } @@ -698,11 +705,29 @@ where /// Sets `position` to be a dynamic synchronized with this window's outer /// position. /// + /// If `automatic_layout` is true, the initial value of `position` will be + /// ignored and the window server will control the window's initial + /// position. + /// /// When the window is moved, this dynamic will contain its new position. /// Setting this dynamic will attempt to move the window to the provided /// location. - pub fn outer_position(mut self, position: impl IntoDynamic>) -> Self { - self.outer_position = Some(position.into_dynamic()); + pub fn outer_position( + mut self, + position: impl IntoValue>, + automatic_layout: bool, + ) -> Self { + let position = position.into_value(); + + if let Some(initial_position) = automatic_layout.then(|| position.get()) { + self.attributes.position = + Some(winit::dpi::Position::Physical(initial_position.into())); + } + + if let Value::Dynamic(position) = position { + self.outer_position = Some(position); + } + self } @@ -725,7 +750,9 @@ where /// Prevents the window contents from being captured by other apps. pub fn content_protected(mut self, protected: impl IntoValue) -> Self { - self.content_protected = Some(protected.into_value()); + let protected = protected.into_value(); + self.attributes.content_protected = protected.get(); + self.content_protected = Some(protected); self } @@ -753,13 +780,17 @@ where /// Controls whether window decorations are shown around this window. pub fn decorated(mut self, decorated: impl IntoValue) -> Self { - self.decorated = Some(decorated.into_value()); + let decorated = decorated.into_value(); + self.attributes.decorations = decorated.get(); + self.decorated = Some(decorated); self } /// Controls the level of this window. pub fn window_level(mut self, window_level: impl IntoValue) -> Self { - self.window_level = Some(window_level.into_value()); + let window_level = window_level.into_value(); + self.attributes.window_level = window_level.get(); + self.window_level = Some(window_level); self } @@ -773,13 +804,17 @@ where /// Provides a dynamic that is updated with the maximized status of this /// window. pub fn maximized(mut self, maximized: impl IntoDynamic) -> Self { - self.maximized = Some(maximized.into_dynamic()); + let maximized = maximized.into_dynamic(); + self.attributes.maximized = maximized.get(); + self.maximized = Some(maximized); self } /// Controls whether the window is resizable by the user or not. pub fn resizable(mut self, resizable: impl IntoValue) -> Self { - self.resizable = Some(resizable.into_value()); + let resizable = resizable.into_value(); + self.attributes.resizable = resizable.get(); + self.resizable = Some(resizable); self } @@ -797,7 +832,9 @@ where /// Controls the visibility of this window. pub fn visible(mut self, visible: impl IntoDynamic) -> Self { - self.visible = Some(visible.into_dynamic()); + let visible = visible.into_dynamic(); + self.attributes.visible = visible.get(); + self.visible = Some(visible); self } @@ -843,6 +880,16 @@ where self } + /// Invokes `on_open` when this window is first opened, even if it is not + /// visible. + pub fn on_open(mut self, on_open: Function) -> Self + where + Function: FnOnce(WindowHandle) + Send + 'static, + { + self.on_open = Some(OnceCallback::new(on_open)); + self + } + /// Invokes `on_close` when this window is closed. pub fn on_close(mut self, on_close: Function) -> Self where @@ -907,6 +954,7 @@ where 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), @@ -1377,7 +1425,7 @@ where #[allow(clippy::needless_pass_by_value)] fn new( mut behavior: T, - window: W, + mut window: W, graphics: &mut kludgine::Graphics<'_>, mut settings: sealed::WindowSettings, ) -> Self @@ -1401,12 +1449,9 @@ where graphics.font_system().db_mut(), ); + let dpi_scale = Dynamic::new(graphics.dpi_scale()); settings.inner_position.set(window.inner_position()); settings.outer_position.set(window.outer_position()); - settings.inner_size.set(window.inner_size()); - settings.outer_size.set(window.outer_size()); - - let dpi_scale = Dynamic::new(graphics.dpi_scale()); let theme_mode = match settings.theme_mode.take() { Some(Value::Dynamic(dynamic)) => { @@ -1426,7 +1471,12 @@ where Value::Dynamic(dynamic) => (dynamic.get(), Some(dynamic.into_reader())), }; - Self { + if let Some(on_open) = settings.on_open { + let handle = window.handle(redraw_status.clone()); + on_open.invoke(handle); + } + + let mut this = Self { behavior, root, tree, @@ -1441,7 +1491,7 @@ where initial_frame: true, occluded: settings.occluded, focused: settings.focused, - inner_size: Tracked::from(settings.inner_size), + inner_size: Tracked::from(settings.inner_size).ignoring_first(), keyboard_activated: None, min_inner_size: None, max_inner_size: None, @@ -1457,22 +1507,27 @@ where close_requested: settings.close_requested, dpi_scale, zoom: Tracked::from(settings.zoom), - content_protected: Tracked::from(settings.content_protected), + content_protected: Tracked::from(settings.content_protected).ignoring_first(), cursor_hittest: Tracked::from(settings.cursor_hittest), cursor_visible: Tracked::from(settings.cursor_visible), cursor_position: Tracked::from(settings.cursor_position), - window_level: Tracked::from(settings.window_level), - decorated: Tracked::from(settings.decorated), + window_level: Tracked::from(settings.window_level).ignoring_first(), + decorated: Tracked::from(settings.decorated).ignoring_first(), maximized: Tracked::from(settings.maximized), minimized: Tracked::from(settings.minimized), - resizable: Tracked::from(settings.resizable), + resizable: Tracked::from(settings.resizable).ignoring_first(), resize_increments: Tracked::from(settings.resize_increments), - visible: Tracked::from(settings.visible), + visible: Tracked::from(settings.visible).ignoring_first(), outer_size: settings.outer_size, inner_position: settings.inner_position, - outer_position: Tracked::from(settings.outer_position), + outer_position: Tracked::from(settings.outer_position).ignoring_first(), window_icon: Tracked::from(settings.window_icon), - } + }; + + this.synchronize_platform_window(&mut window); + this.prepare(window, graphics); + + this } fn new_frame(&mut self, graphics: &mut kludgine::Graphics<'_>) { @@ -1561,7 +1616,7 @@ where layout_size }; let render_size = actual_size.min(window_size); - layout_context.redraw_when_changed(&self.inner_size); + layout_context.invalidate_when_changed(&self.inner_size); layout_context.invalidate_when_changed(&self.resize_to_fit); if let Some(new_size) = self.inner_size.updated() { layout_context.request_inner_size(*new_size); @@ -1655,6 +1710,7 @@ where where W: PlatformWindowImplementation, { + self.redraw_status.sync_received(); self.update_ized(window); if let Some(winit) = window.winit() { let mut redraw = false; @@ -2375,7 +2431,6 @@ where } WindowCommand::Sync => { self.synchronize_platform_window(&mut window); - self.redraw_status.sync_received(); } WindowCommand::RequestClose => { let mut window = RunningWindow::new( @@ -2501,7 +2556,7 @@ pub(crate) mod sealed { use kludgine::Color; use parking_lot::Mutex; - use super::PendingWindow; + use super::{PendingWindow, WindowHandle}; use crate::app::Cushy; use crate::context::sealed::InvalidationStatus; use crate::fonts::FontCollection; @@ -2534,6 +2589,7 @@ pub(crate) mod sealed { pub monospace_font_family: FontFamilyList, pub cursive_font_family: FontFamilyList, pub font_data_to_load: FontCollection, + pub on_open: Option>, pub on_closed: Option, pub vsync: bool, pub multisample_count: NonZeroU32, @@ -3144,6 +3200,7 @@ impl StandaloneWindowBuilder { monospace_font_family: FontFamilyList::default(), cursive_font_family: FontFamilyList::default(), font_data_to_load: FontCollection::default(), + on_open: None, on_closed: None, vsync: false, multisample_count: self.multisample_count,