From f9d0203ff5a89caaa25a10d1190c5f9c6706bd44 Mon Sep 17 00:00:00 2001 From: Marli Frost Date: Tue, 19 Dec 2023 13:51:28 +0000 Subject: [PATCH 1/2] Add API for tracking checked to the inner_size of a window This matched the apis used for focused and occluded properties. I've added an example to demonstrate usage. --- examples/window-properties.rs | 22 +++++++++++++++ src/window.rs | 52 ++++++++++++++++++++++++++++------- 2 files changed, 64 insertions(+), 10 deletions(-) create mode 100644 examples/window-properties.rs diff --git a/examples/window-properties.rs b/examples/window-properties.rs new file mode 100644 index 0000000..e0fd174 --- /dev/null +++ b/examples/window-properties.rs @@ -0,0 +1,22 @@ +use gooey::value::Dynamic; +use gooey::widget::{MakeWidget, WidgetInstance}; +use gooey::Run; +use kludgine::figures::Size; + +fn main() -> gooey::Result { + let focused = Dynamic::new(false); + let occluded = Dynamic::new(false); + let inner_size = Dynamic::new(Size::default()); + + 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() + .centered(); + + gooey::window::Window::::for_widget(widgets) + .focused(focused) + .occluded(occluded) + .inner_size(inner_size) + .run() +} diff --git a/src/window.rs b/src/window.rs index e669b01..ad5e06b 100644 --- a/src/window.rs +++ b/src/window.rs @@ -49,6 +49,7 @@ pub struct RunningWindow<'window> { gooey: Gooey, focused: Dynamic, occluded: Dynamic, + inner_size: Dynamic>, } impl<'window> RunningWindow<'window> { @@ -57,12 +58,14 @@ impl<'window> RunningWindow<'window> { gooey: &Gooey, focused: &Dynamic, occluded: &Dynamic, + inner_size: &Dynamic>, ) -> Self { Self { window, gooey: gooey.clone(), focused: focused.clone(), occluded: occluded.clone(), + inner_size: inner_size.clone(), } } @@ -80,6 +83,13 @@ impl<'window> RunningWindow<'window> { &self.occluded } + /// Returns a dynamic that is updated whenever this window's inner size + /// changes. + #[must_use] + pub const fn inner_size(&self) -> &Dynamic> { + &self.inner_size + } + /// Returns a locked mutex guard to the OS's clipboard, if one was able to be /// initialized when the window opened. #[must_use] @@ -139,6 +149,7 @@ where /// during drawing operations. pub font_data_to_load: Vec>, + inner_size: Option>>, occluded: Option>, focused: Option>, theme_mode: Option>, @@ -194,6 +205,17 @@ impl Window { self } + /// Sets `inner_size` to be the dynamic updated when this window's inner size + /// is changed. + /// + /// When the window is resized, the dynamic will contain its new size. + /// `true`. + pub fn inner_size(mut self, inner_size: impl IntoDynamic>) -> Self { + let inner_size = inner_size.into_dynamic(); + self.inner_size = Some(inner_size); + self + } + /// Sets the [`ThemeMode`] for this window. /// /// If a [`ThemeMode`] is provided, the window will be set to this theme @@ -262,6 +284,7 @@ where occluded: None, focused: None, theme_mode: None, + inner_size: None, serif_font_family: FontFamilyList::default(), sans_serif_font_family: FontFamilyList::default(), fantasy_font_family: FontFamilyList::default(), @@ -289,6 +312,7 @@ where 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, @@ -346,6 +370,7 @@ struct GooeyWindow { initial_frame: bool, occluded: Dynamic, focused: Dynamic, + inner_size: Dynamic>, keyboard_activated: Option, min_inner_size: Option>, max_inner_size: Option>, @@ -548,6 +573,7 @@ where let gooey = settings.gooey.clone(); let occluded = settings.occluded.take().unwrap_or_default(); let focused = settings.focused.take().unwrap_or_default(); + let inner_size = settings.inner_size.take().unwrap_or_default(); let theme = settings.theme.take().expect("theme always present"); let fontdb = graphics.font_system().db_mut(); @@ -616,7 +642,7 @@ where }; let transparent = settings.transparent; let mut behavior = T::initialize( - &mut RunningWindow::new(window, &gooey, &focused, &occluded), + &mut RunningWindow::new(window, &gooey, &focused, &occluded, &inner_size), context.user, ); let root = Tree::default().push_boxed(behavior.make_root(), None); @@ -640,6 +666,7 @@ where initial_frame: true, occluded, focused, + inner_size, keyboard_activated: None, min_inner_size: None, max_inner_size: None, @@ -671,7 +698,7 @@ where .new_frame(self.redraw_status.invalidations().drain()); let resizable = window.winit().is_resizable(); - let mut window = RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded); + let mut window = RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded, &self.inner_size); let root_mode = self.constrain_window_resizing(resizable, &mut window, graphics); self.fonts.next_frame(); @@ -798,7 +825,7 @@ where Self::request_close( &mut self.should_close, &mut self.behavior, - &mut RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded), + &mut RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded, &self.inner_size), ) } @@ -834,9 +861,10 @@ where fn resized( &mut self, - _window: kludgine::app::Window<'_, WindowCommand>, + window: kludgine::app::Window<'_, WindowCommand>, _kludgine: &mut Kludgine, ) { + self.inner_size.set(window.inner_size()); self.root.invalidate(); } @@ -862,7 +890,7 @@ where let Some(target) = self.root.tree.widget_from_node(target) else { return; }; - let mut window = RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded); + let mut window = RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded, &self.inner_size); let mut target = EventContext::new( WidgetContext::new( target, @@ -972,7 +1000,7 @@ where .expect("missing widget") }); - let mut window = RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded); + let mut window = RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded, &self.inner_size); let mut widget = EventContext::new( WidgetContext::new( widget, @@ -1008,7 +1036,7 @@ where .widget(self.root.id()) .expect("missing widget") }); - let mut window = RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded); + let mut window = RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded, &self.inner_size); let mut target = EventContext::new( WidgetContext::new( widget, @@ -1035,7 +1063,7 @@ where let location = Point::::from(position); self.cursor.location = Some(location); - let mut window = RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded); + let mut window = RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded, &self.inner_size); EventContext::new( WidgetContext::new( @@ -1082,7 +1110,7 @@ where _device_id: DeviceId, ) { if self.cursor.widget.take().is_some() { - let mut window = RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded); + let mut window = RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded, &self.inner_size); let mut context = EventContext::new( WidgetContext::new( self.root.clone(), @@ -1106,7 +1134,7 @@ where state: ElementState, button: MouseButton, ) { - let mut window = RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded); + let mut window = RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded, &self.inner_size); match state { ElementState::Pressed => { EventContext::new( @@ -1238,6 +1266,9 @@ pub(crate) struct CursorState { pub(crate) mod sealed { use std::cell::RefCell; + use kludgine::figures::Size; + use kludgine::figures::units::UPx; + use crate::styles::{FontFamilyList, ThemePair}; use crate::value::{Dynamic, Value}; use crate::window::{ThemeMode, WindowAttributes}; @@ -1253,6 +1284,7 @@ pub(crate) mod sealed { pub attributes: Option, pub occluded: Option>, pub focused: Option>, + pub inner_size: Option>>, pub theme: Option>, pub theme_mode: Option>, pub transparent: bool, From 873e6d6f160f10f28277e0c5f23828fd0fe77628 Mon Sep 17 00:00:00 2001 From: Jonathan Johnson Date: Tue, 19 Dec 2023 13:33:45 -0800 Subject: [PATCH 2/2] Added inner_size syncing This adds upon the work in #94 by allowing the dynamic's value to be set and it cause the window size to change. I've ordered the resizing operations so that changes to the property are prioritized over automatic adjustments. This doesn't change the behavior for automatic adjustments in any way. --- src/graphics.rs | 13 ++++ src/window.rs | 188 +++++++++++++++++++++++++++++++----------------- 2 files changed, 136 insertions(+), 65 deletions(-) diff --git a/src/graphics.rs b/src/graphics.rs index f3cd6d3..2628dd5 100644 --- a/src/graphics.rs +++ b/src/graphics.rs @@ -369,4 +369,17 @@ impl FontState { }) .cloned() } + + pub fn apply_font_family_list( + &self, + family: &FontFamilyList, + fallback: impl FnOnce() -> Option, + apply: impl FnOnce(String), + ) { + if let Some(FamilyOwned::Name(name)) = + self.find_available_font_family(family).or_else(fallback) + { + apply(name); + } + } } diff --git a/src/window.rs b/src/window.rs index ad5e06b..76b539c 100644 --- a/src/window.rs +++ b/src/window.rs @@ -36,7 +36,7 @@ use crate::graphics::{FontState, Graphics}; use crate::styles::{Edges, FontFamilyList, ThemePair}; use crate::tree::Tree; use crate::utils::ModifiersExt; -use crate::value::{Dynamic, DynamicReader, IntoDynamic, IntoValue, Value}; +use crate::value::{Dynamic, DynamicReader, Generation, IntoDynamic, IntoValue, Value}; use crate::widget::{ EventHandling, MountedWidget, RootBehavior, Widget, WidgetId, WidgetInstance, HANDLED, IGNORED, }; @@ -79,12 +79,16 @@ impl<'window> RunningWindow<'window> { /// Returns a dynamic that is updated whenever this window's occlusion /// status changes. #[must_use] - pub fn occluded(&self) -> &Dynamic { + pub const fn occluded(&self) -> &Dynamic { &self.occluded } - /// Returns a dynamic that is updated whenever this window's inner size - /// changes. + /// Returns a dynamic that is synchronized with this window's inner size. + /// + /// Whenever the window is resized, this dynamic will be updated with the + /// new inner size. Setting a new value will request the new size from the + /// operating system, but resize requests may be altered or ignored by the + /// operating system. #[must_use] pub const fn inner_size(&self) -> &Dynamic> { &self.inner_size @@ -205,11 +209,12 @@ impl Window { self } - /// Sets `inner_size` to be the dynamic updated when this window's inner size - /// is changed. + /// Sets `inner_size` to be the dynamic syncrhonized with this window's + /// inner size. /// - /// When the window is resized, the dynamic will contain its new size. - /// `true`. + /// When the window is resized, the dynamic will contain its new size. When + /// 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 { let inner_size = inner_size.into_dynamic(); self.inner_size = Some(inner_size); @@ -371,6 +376,7 @@ struct GooeyWindow { occluded: Dynamic, focused: Dynamic, inner_size: Dynamic>, + inner_size_generation: Generation, keyboard_activated: Option, min_inner_size: Option>, max_inner_size: Option>, @@ -573,8 +579,10 @@ where let gooey = settings.gooey.clone(); let occluded = settings.occluded.take().unwrap_or_default(); let focused = settings.focused.take().unwrap_or_default(); - let inner_size = settings.inner_size.take().unwrap_or_default(); let theme = settings.theme.take().expect("theme always present"); + let inner_size = settings.inner_size.take().unwrap_or_default(); + + inner_size.set(window.inner_size()); let fontdb = graphics.font_system().db_mut(); for font_to_load in settings.font_data_to_load.drain(..) { @@ -582,55 +590,47 @@ where } let fonts = FontState::new(fontdb); + fonts.apply_font_family_list( + &settings.serif_font_family, + || default_family(Family::Serif), + |name| fontdb.set_serif_family(name), + ); - if let Some(FamilyOwned::Name(name)) = fonts - .find_available_font_family(&settings.serif_font_family) - .or_else(|| default_family(Family::Serif)) - { - fontdb.set_serif_family(name); - } - - let bundled_font_name; - #[cfg(feature = "roboto-flex")] - { - bundled_font_name = Some(String::from("Roboto Flex")); - } - #[cfg(not(feature = "roboto-flex"))] - { - bundled_font_name = None; - } - - if let Some(FamilyOwned::Name(name)) = fonts - .find_available_font_family(&settings.sans_serif_font_family) - .or_else(|| { - if let Some(name) = bundled_font_name { - Some(FamilyOwned::Name(name)) - } else { - default_family(Family::SansSerif) + fonts.apply_font_family_list( + &settings.sans_serif_font_family, + || { + let bundled_font_name; + #[cfg(feature = "roboto-flex")] + { + bundled_font_name = Some(String::from("Roboto Flex")); + } + #[cfg(not(feature = "roboto-flex"))] + { + bundled_font_name = None; } - }) - { - fontdb.set_sans_serif_family(name); - } - if let Some(FamilyOwned::Name(name)) = fonts - .find_available_font_family(&settings.fantasy_font_family) - .or_else(|| default_family(Family::Fantasy)) - { - fontdb.set_fantasy_family(name); - } - if let Some(FamilyOwned::Name(name)) = fonts - .find_available_font_family(&settings.monospace_font_family) - .or_else(|| default_family(Family::Monospace)) - { - fontdb.set_monospace_family(name); - } - if let Some(FamilyOwned::Name(name)) = fonts - .find_available_font_family(&settings.cursive_font_family) - .or_else(|| default_family(Family::Cursive)) - { - fontdb.set_cursive_family(name); - } + bundled_font_name.map_or_else( + || default_family(Family::SansSerif), + |name| Some(FamilyOwned::Name(name)), + ) + }, + |name| fontdb.set_sans_serif_family(name), + ); + fonts.apply_font_family_list( + &settings.fantasy_font_family, + || default_family(Family::Fantasy), + |name| fontdb.set_fantasy_family(name), + ); + fonts.apply_font_family_list( + &settings.monospace_font_family, + || default_family(Family::Monospace), + |name| fontdb.set_monospace_family(name), + ); + fonts.apply_font_family_list( + &settings.cursive_font_family, + || default_family(Family::Cursive), + |name| fontdb.set_cursive_family(name), + ); let theme_mode = match settings.theme_mode.take() { Some(Value::Dynamic(dynamic)) => { @@ -666,6 +666,7 @@ where initial_frame: true, occluded, focused, + inner_size_generation: inner_size.generation(), inner_size, keyboard_activated: None, min_inner_size: None, @@ -698,7 +699,13 @@ where .new_frame(self.redraw_status.invalidations().drain()); let resizable = window.winit().is_resizable(); - let mut window = RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded, &self.inner_size); + let mut window = RunningWindow::new( + window, + &self.gooey, + &self.focused, + &self.occluded, + &self.inner_size, + ); let root_mode = self.constrain_window_resizing(resizable, &mut window, graphics); self.fonts.next_frame(); @@ -741,7 +748,14 @@ where layout_size }; let render_size = actual_size.min(window_size); - if actual_size != window_size && !resizable { + layout_context.redraw_when_changed(&self.inner_size); + let inner_size_generation = self.inner_size.generation(); + if self.inner_size_generation != inner_size_generation { + let _ = layout_context + .winit() + .request_inner_size(PhysicalSize::from(self.inner_size.get())); + self.inner_size_generation = inner_size_generation; + } else if actual_size != window_size && !resizable { let mut new_size = actual_size; if let Some(min_size) = self.min_inner_size { new_size = new_size.max(min_size); @@ -825,7 +839,13 @@ where Self::request_close( &mut self.should_close, &mut self.behavior, - &mut RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded, &self.inner_size), + &mut RunningWindow::new( + window, + &self.gooey, + &self.focused, + &self.occluded, + &self.inner_size, + ), ) } @@ -865,6 +885,8 @@ where _kludgine: &mut Kludgine, ) { self.inner_size.set(window.inner_size()); + // We want to prevent a resize request for this resized event. + self.inner_size_generation = self.inner_size.generation(); self.root.invalidate(); } @@ -890,7 +912,13 @@ where let Some(target) = self.root.tree.widget_from_node(target) else { return; }; - let mut window = RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded, &self.inner_size); + let mut window = RunningWindow::new( + window, + &self.gooey, + &self.focused, + &self.occluded, + &self.inner_size, + ); let mut target = EventContext::new( WidgetContext::new( target, @@ -1000,7 +1028,13 @@ where .expect("missing widget") }); - let mut window = RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded, &self.inner_size); + let mut window = RunningWindow::new( + window, + &self.gooey, + &self.focused, + &self.occluded, + &self.inner_size, + ); let mut widget = EventContext::new( WidgetContext::new( widget, @@ -1036,7 +1070,13 @@ where .widget(self.root.id()) .expect("missing widget") }); - let mut window = RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded, &self.inner_size); + let mut window = RunningWindow::new( + window, + &self.gooey, + &self.focused, + &self.occluded, + &self.inner_size, + ); let mut target = EventContext::new( WidgetContext::new( widget, @@ -1063,7 +1103,13 @@ where let location = Point::::from(position); self.cursor.location = Some(location); - let mut window = RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded, &self.inner_size); + let mut window = RunningWindow::new( + window, + &self.gooey, + &self.focused, + &self.occluded, + &self.inner_size, + ); EventContext::new( WidgetContext::new( @@ -1110,7 +1156,13 @@ where _device_id: DeviceId, ) { if self.cursor.widget.take().is_some() { - let mut window = RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded, &self.inner_size); + let mut window = RunningWindow::new( + window, + &self.gooey, + &self.focused, + &self.occluded, + &self.inner_size, + ); let mut context = EventContext::new( WidgetContext::new( self.root.clone(), @@ -1134,7 +1186,13 @@ where state: ElementState, button: MouseButton, ) { - let mut window = RunningWindow::new(window, &self.gooey, &self.focused, &self.occluded, &self.inner_size); + let mut window = RunningWindow::new( + window, + &self.gooey, + &self.focused, + &self.occluded, + &self.inner_size, + ); match state { ElementState::Pressed => { EventContext::new( @@ -1266,8 +1324,8 @@ pub(crate) struct CursorState { pub(crate) mod sealed { use std::cell::RefCell; - use kludgine::figures::Size; use kludgine::figures::units::UPx; + use kludgine::figures::Size; use crate::styles::{FontFamilyList, ThemePair}; use crate::value::{Dynamic, Value};