diff --git a/src/context.rs b/src/context.rs index 8cae0e4..79bd7ff 100644 --- a/src/context.rs +++ b/src/context.rs @@ -15,7 +15,6 @@ use kludgine::shapes::{Shape, StrokeOptions}; use kludgine::{Color, Kludgine}; use crate::animation::ZeroToOne; -use crate::context::sealed::WindowHandle; use crate::graphics::Graphics; use crate::styles::components::{ CornerRadius, FontFamily, FontStyle, FontWeight, HighlightColor, LayoutOrder, LineHeight, @@ -850,7 +849,6 @@ impl<'window> AsEventContext<'window> for GraphicsContext<'_, 'window, '_, '_, ' pub struct WidgetContext<'context, 'window> { current_node: MountedWidget, pub(crate) tree: Tree, - redraw_status: &'context InvalidationStatus, window: &'context mut RunningWindow<'window>, theme: Cow<'context, ThemePair>, cursor: &'context mut CursorState, @@ -862,16 +860,12 @@ pub struct WidgetContext<'context, 'window> { impl<'context, 'window> WidgetContext<'context, 'window> { pub(crate) fn new( current_node: MountedWidget, - redraw_status: &'context InvalidationStatus, theme: &'context ThemePair, window: &'context mut RunningWindow<'window>, theme_mode: ThemeMode, cursor: &'context mut CursorState, ) -> Self { - let enabled = current_node.enabled(&WindowHandle { - kludgine: window.handle(), - redraw_status: redraw_status.clone(), - }); + let enabled = current_node.enabled(&window.handle()); let tree = current_node.tree(); Self { pending_state: PendingState::Owned(PendingWidgetState { @@ -891,7 +885,6 @@ impl<'context, 'window> WidgetContext<'context, 'window> { }, cursor, current_node, - redraw_status, theme: Cow::Borrowed(theme), window, } @@ -902,7 +895,6 @@ impl<'context, 'window> WidgetContext<'context, 'window> { WidgetContext { tree: self.tree.clone(), current_node: self.current_node.clone(), - redraw_status: self.redraw_status, window: &mut *self.window, theme: Cow::Borrowed(self.theme.as_ref()), pending_state: self.pending_state.borrowed(), @@ -941,7 +933,6 @@ impl<'context, 'window> WidgetContext<'context, 'window> { }, current_node, tree: self.tree.clone(), - redraw_status: self.redraw_status, window: &mut *self.window, theme, pending_state: self.pending_state.borrowed(), @@ -1157,13 +1148,6 @@ impl<'context, 'window> WidgetContext<'context, 'window> { self.effective_styles.try_get(query, self) } - pub(crate) fn handle(&self) -> WindowHandle { - WindowHandle { - kludgine: self.window.handle(), - redraw_status: self.redraw_status.clone(), - } - } - /// Returns the window containing this widget. #[must_use] pub fn window(&self) -> &RunningWindow<'window> { @@ -1297,6 +1281,19 @@ impl InvalidationStatus { } } +impl Eq for InvalidationStatus {} + +impl PartialEq for InvalidationStatus { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.invalidated, &other.invalidated) + } +} +impl std::hash::Hash for InvalidationStatus { + fn hash(&self, state: &mut H) { + Arc::as_ptr(&self.invalidated).hash(state); + } +} + /// A type chat can convert to a [`MountedWidget`] through a [`WidgetContext`]. pub trait ManageWidget { /// The managed type, which can be `Option` or @@ -1392,52 +1389,11 @@ pub trait Trackable: sealed::Trackable {} impl Trackable for T where T: sealed::Trackable {} pub(crate) mod sealed { - use std::hash::{Hash, Hasher}; - use std::sync::Arc; - - use crate::context::InvalidationStatus; use crate::widget::WidgetId; - use crate::window::sealed::WindowCommand; + use crate::window::WindowHandle; pub trait Trackable { fn redraw_when_changed(&self, handle: WindowHandle); fn invalidate_when_changed(&self, handle: WindowHandle, id: WidgetId); } - - #[derive(Clone)] - pub struct WindowHandle { - pub(crate) kludgine: kludgine::app::WindowHandle, - pub(crate) redraw_status: InvalidationStatus, - } - - impl Eq for WindowHandle {} - - impl PartialEq for WindowHandle { - fn eq(&self, other: &Self) -> bool { - Arc::ptr_eq( - &self.redraw_status.invalidated, - &other.redraw_status.invalidated, - ) - } - } - - impl Hash for WindowHandle { - fn hash(&self, state: &mut H) { - Arc::as_ptr(&self.redraw_status.invalidated).hash(state); - } - } - - impl WindowHandle { - pub fn redraw(&self) { - if self.redraw_status.should_send_refresh() { - let _result = self.kludgine.send(WindowCommand::Redraw); - } - } - - pub fn invalidate(&self, widget: WidgetId) { - if self.redraw_status.invalidate(widget) { - self.redraw(); - } - } - } } diff --git a/src/tree.rs b/src/tree.rs index 1a03005..f46ae60 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -6,12 +6,11 @@ use alot::{LotId, Lots}; use kludgine::figures::units::{Px, UPx}; use kludgine::figures::{Point, Rect, Size}; -use crate::context::sealed::WindowHandle; use crate::styles::{Styles, ThemePair, VisualOrder}; use crate::utils::IgnorePoison; use crate::value::Value; use crate::widget::{MountedWidget, WidgetId, WidgetInstance}; -use crate::window::ThemeMode; +use crate::window::{ThemeMode, WindowHandle}; use crate::ConstraintLimit; #[derive(Clone, Default)] diff --git a/src/value.rs b/src/value.rs index 6a159fd..e58e5fd 100644 --- a/src/value.rs +++ b/src/value.rs @@ -17,11 +17,11 @@ use intentional::Assert; use kempt::{Map, Sort}; use crate::animation::{AnimationHandle, DynamicTransition, IntoAnimate, LinearInterpolate, Spawn}; -use crate::context::sealed::WindowHandle; use crate::context::{self, WidgetContext}; use crate::utils::{run_in_bg, IgnorePoison, WithClone}; use crate::widget::{Children, MakeWidget, MakeWidgetWithTag, WidgetId, WidgetInstance}; use crate::widgets::{Radio, Select, Space, Switcher}; +use crate::window::WindowHandle; /// An instance of a value that provides APIs to observe and react to its /// contents. diff --git a/src/widget.rs b/src/widget.rs index 205df55..9c58b39 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -19,7 +19,6 @@ use kludgine::figures::{IntoSigned, IntoUnsigned, Point, Rect, Size}; use kludgine::Color; use crate::app::{Application, Open, PendingApp, Run}; -use crate::context::sealed::WindowHandle; use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext, WidgetContext}; use crate::styles::components::{ FontFamily, FontStyle, FontWeight, Heading1FontFamily, Heading1Style, Heading1Weight, @@ -43,7 +42,7 @@ use crate::widgets::{ Align, Button, Checkbox, Collapse, Container, Expand, Layers, Resize, Scroll, Space, Stack, Style, Themed, ThemedMode, Validated, Wrap, }; -use crate::window::{RunningWindow, ThemeMode, Window, WindowBehavior}; +use crate::window::{RunningWindow, ThemeMode, Window, WindowBehavior, WindowHandle}; use crate::ConstraintLimit; /// A type that makes up a graphical user interface. diff --git a/src/window.rs b/src/window.rs index ecdfed0..2e1dd71 100644 --- a/src/window.rs +++ b/src/window.rs @@ -2,6 +2,7 @@ use std::cell::RefCell; use std::ffi::OsStr; +use std::hash::Hash; use std::ops::{Deref, DerefMut, Not}; use std::path::Path; use std::string::ToString; @@ -17,7 +18,7 @@ use kludgine::app::winit::event::{ use kludgine::app::winit::keyboard::{Key, NamedKey}; use kludgine::app::winit::window; use kludgine::app::WindowBehavior as _; -use kludgine::cosmic_text::{Family, FamilyOwned}; +use kludgine::cosmic_text::{fontdb, Family, FamilyOwned}; use kludgine::figures::units::{Px, UPx}; use kludgine::figures::{IntoSigned, IntoUnsigned, Point, Ranged, Rect, ScreenScale, Size, Zero}; use kludgine::render::Drawing; @@ -46,6 +47,7 @@ use crate::{initialize_tracing, ConstraintLimit}; /// A currently running Gooey window. pub struct RunningWindow<'window> { window: kludgine::app::Window<'window, WindowCommand>, + invalidation_status: InvalidationStatus, gooey: Gooey, focused: Dynamic, occluded: Dynamic, @@ -55,6 +57,7 @@ pub struct RunningWindow<'window> { impl<'window> RunningWindow<'window> { pub(crate) fn new( window: kludgine::app::Window<'window, WindowCommand>, + invalidation_status: &InvalidationStatus, gooey: &Gooey, focused: &Dynamic, occluded: &Dynamic, @@ -62,6 +65,7 @@ impl<'window> RunningWindow<'window> { ) -> Self { Self { window, + invalidation_status: invalidation_status.clone(), gooey: gooey.clone(), focused: focused.clone(), occluded: occluded.clone(), @@ -83,6 +87,20 @@ impl<'window> RunningWindow<'window> { &self.occluded } + /// Request that the window closes. + /// + /// A window may disallow itself from being closed by customizing + /// [`WindowBehavior::close_requested`]. + pub fn request_close(&self) { + self.handle().request_close(); + } + + /// Returns a handle to this window. + #[must_use] + pub fn handle(&self) -> WindowHandle { + WindowHandle::new(self.window.handle(), self.invalidation_status.clone()) + } + /// Returns a dynamic that is synchronized with this window's inner size. /// /// Whenever the window is resized, this dynamic will be updated with the @@ -332,12 +350,14 @@ where App: Application, { let gooey = app.gooey().clone(); + let redraw_status = InvalidationStatus::default(); let handle = GooeyWindow::::open_with( app, sealed::Context { user: self.context, settings: RefCell::new(sealed::WindowSettings { gooey, + redraw_status: redraw_status.clone(), on_closed: self.on_closed, transparent: self.attributes.transparent, attributes: Some(self.attributes), @@ -356,7 +376,7 @@ where }, )?; - Ok(handle.map(WindowHandle::from)) + Ok(handle.map(|handle| WindowHandle::new(handle, redraw_status))) } fn run_in(self, app: PendingApp) -> crate::Result { @@ -455,7 +475,6 @@ where EventContext::new( WidgetContext::new( previously_active, - &self.redraw_status, &self.current_theme, window, self.theme_mode.get(), @@ -468,7 +487,6 @@ where EventContext::new( WidgetContext::new( default.clone(), - &self.redraw_status, &self.current_theme, window, self.theme_mode.get(), @@ -487,7 +505,6 @@ where EventContext::new( WidgetContext::new( keyboard_activated, - &self.redraw_status, &self.current_theme, window, self.theme_mode.get(), @@ -517,7 +534,6 @@ where let mut context = EventContext::new( WidgetContext::new( managed, - &self.redraw_status, &self.current_theme, window, self.theme_mode.get(), @@ -591,37 +607,11 @@ where root_mode.unwrap_or(RootMode::Fit) } -} -#[derive(Clone, Copy, Eq, PartialEq, Debug)] -enum RootMode { - Fit, - Expand, - Align, -} - -impl kludgine::app::WindowBehavior for GooeyWindow -where - T: WindowBehavior, -{ - type Context = sealed::Context; - - fn initialize( - window: kludgine::app::Window<'_, WindowCommand>, - graphics: &mut kludgine::Graphics<'_>, - context: Self::Context, - ) -> Self { - let mut settings = context.settings.borrow_mut(); - let gooey = settings.gooey.clone(); - let occluded = settings.occluded.take().unwrap_or_default(); - let focused = settings.focused.take().unwrap_or_default(); - let theme = settings.theme.take().expect("theme always present"); - let inner_size = settings.inner_size.take().unwrap_or_default(); - let on_closed = settings.on_closed.take(); - - inner_size.set(window.inner_size()); - - let fontdb = graphics.font_system().db_mut(); + fn load_fonts( + settings: &mut sealed::WindowSettings, + fontdb: &mut fontdb::Database, + ) -> FontState { for font_to_load in settings.font_data_to_load.drain(..) { fontdb.load_font_data(font_to_load); } @@ -668,6 +658,39 @@ where || default_family(Family::Cursive), |name| fontdb.set_cursive_family(name), ); + fonts + } +} + +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +enum RootMode { + Fit, + Expand, + Align, +} + +impl kludgine::app::WindowBehavior for GooeyWindow +where + T: WindowBehavior, +{ + type Context = sealed::Context; + + fn initialize( + window: kludgine::app::Window<'_, WindowCommand>, + graphics: &mut kludgine::Graphics<'_>, + context: Self::Context, + ) -> Self { + let mut settings = context.settings.borrow_mut(); + let gooey = settings.gooey.clone(); + let occluded = settings.occluded.take().unwrap_or_default(); + let focused = settings.focused.take().unwrap_or_default(); + let theme = settings.theme.take().expect("theme always present"); + let inner_size = settings.inner_size.take().unwrap_or_default(); + let on_closed = settings.on_closed.take(); + + inner_size.set(window.inner_size()); + + let fonts = Self::load_fonts(&mut settings, graphics.font_system().db_mut()); let theme_mode = match settings.theme_mode.take() { Some(Value::Dynamic(dynamic)) => { @@ -678,8 +701,16 @@ where None => Value::dynamic(window.theme().into()), }; let transparent = settings.transparent; + let redraw_status = settings.redraw_status.clone(); let mut behavior = T::initialize( - &mut RunningWindow::new(window, &gooey, &focused, &occluded, &inner_size), + &mut RunningWindow::new( + window, + &redraw_status, + &gooey, + &focused, + &occluded, + &inner_size, + ), context.user, ); let tree = Tree::default(); @@ -701,7 +732,7 @@ where widget: None, }, mouse_buttons: AHashMap::default(), - redraw_status: InvalidationStatus::default(), + redraw_status, initial_frame: true, occluded, focused, @@ -740,6 +771,7 @@ where let resizable = window.winit().is_resizable(); let mut window = RunningWindow::new( window, + &self.redraw_status, &self.gooey, &self.focused, &self.occluded, @@ -752,7 +784,6 @@ where let mut context = GraphicsContext { widget: WidgetContext::new( self.root.clone(), - &self.redraw_status, &self.current_theme, &mut window, self.theme_mode.get(), @@ -876,6 +907,7 @@ where &mut self.behavior, &mut RunningWindow::new( window, + &self.redraw_status, &self.gooey, &self.focused, &self.occluded, @@ -949,6 +981,7 @@ where }; let mut window = RunningWindow::new( window, + &self.redraw_status, &self.gooey, &self.focused, &self.occluded, @@ -957,7 +990,6 @@ where let mut target = EventContext::new( WidgetContext::new( target, - &self.redraw_status, &self.current_theme, &mut window, self.theme_mode.get(), @@ -994,7 +1026,6 @@ where let mut target = EventContext::new( WidgetContext::new( target, - &self.redraw_status, &self.current_theme, &mut window, self.theme_mode.get(), @@ -1055,6 +1086,7 @@ where let mut window = RunningWindow::new( window, + &self.redraw_status, &self.gooey, &self.focused, &self.occluded, @@ -1063,7 +1095,6 @@ where let mut widget = EventContext::new( WidgetContext::new( widget, - &self.redraw_status, &self.current_theme, &mut window, self.theme_mode.get(), @@ -1091,6 +1122,7 @@ where .unwrap_or_else(|| self.tree.widget(self.root.id()).expect("missing widget")); let mut window = RunningWindow::new( window, + &self.redraw_status, &self.gooey, &self.focused, &self.occluded, @@ -1099,7 +1131,6 @@ where let mut target = EventContext::new( WidgetContext::new( widget, - &self.redraw_status, &self.current_theme, &mut window, self.theme_mode.get(), @@ -1124,6 +1155,7 @@ where let mut window = RunningWindow::new( window, + &self.redraw_status, &self.gooey, &self.focused, &self.occluded, @@ -1133,7 +1165,6 @@ where EventContext::new( WidgetContext::new( self.root.clone(), - &self.redraw_status, &self.current_theme, &mut window, self.theme_mode.get(), @@ -1152,7 +1183,6 @@ where let mut context = EventContext::new( WidgetContext::new( handler.clone(), - &self.redraw_status, &self.current_theme, &mut window, self.theme_mode.get(), @@ -1177,6 +1207,7 @@ where if self.cursor.widget.take().is_some() { let mut window = RunningWindow::new( window, + &self.redraw_status, &self.gooey, &self.focused, &self.occluded, @@ -1185,7 +1216,6 @@ where let mut context = EventContext::new( WidgetContext::new( self.root.clone(), - &self.redraw_status, &self.current_theme, &mut window, self.theme_mode.get(), @@ -1207,6 +1237,7 @@ where ) { let mut window = RunningWindow::new( window, + &self.redraw_status, &self.gooey, &self.focused, &self.occluded, @@ -1217,7 +1248,6 @@ where EventContext::new( WidgetContext::new( self.root.clone(), - &self.redraw_status, &self.current_theme, &mut window, self.theme_mode.get(), @@ -1236,7 +1266,6 @@ where &mut EventContext::new( WidgetContext::new( hovered.clone(), - &self.redraw_status, &self.current_theme, &mut window, self.theme_mode.get(), @@ -1276,7 +1305,6 @@ where let mut context = EventContext::new( WidgetContext::new( handler, - &self.redraw_status, &self.current_theme, &mut window, self.theme_mode.get(), @@ -1321,6 +1349,7 @@ where WindowCommand::RequestClose => { let mut window = RunningWindow::new( window, + &self.redraw_status, &self.gooey, &self.focused, &self.occluded, @@ -1367,6 +1396,7 @@ pub(crate) mod sealed { use kludgine::figures::Size; use crate::app::Gooey; + use crate::context::InvalidationStatus; use crate::styles::{FontFamilyList, ThemePair}; use crate::value::{Dynamic, Value}; use crate::widget::OnceCallback; @@ -1379,6 +1409,7 @@ pub(crate) mod sealed { pub struct WindowSettings { pub gooey: Gooey, + pub redraw_status: InvalidationStatus, pub attributes: Option, pub occluded: Option>, pub focused: Option>, @@ -1501,20 +1532,56 @@ fn default_family(query: Family<'_>) -> Option { } /// A handle to an open Gooey window. -pub struct WindowHandle(kludgine::app::WindowHandle); +#[derive(Clone)] +pub struct WindowHandle { + pub(crate) kludgine: kludgine::app::WindowHandle, + pub(crate) redraw_status: InvalidationStatus, +} impl WindowHandle { + pub(crate) fn new( + kludgine: kludgine::app::WindowHandle, + redraw_status: InvalidationStatus, + ) -> Self { + Self { + kludgine, + redraw_status, + } + } + /// Request that the window closes. /// /// A window may disallow itself from being closed by customizing /// [`WindowBehavior::close_requested`]. pub fn request_close(&self) { - let _result = self.0.send(sealed::WindowCommand::RequestClose); + let _result = self.kludgine.send(sealed::WindowCommand::RequestClose); + } + + /// Requests that the window redraws. + pub fn redraw(&self) { + if self.redraw_status.should_send_refresh() { + let _result = self.kludgine.send(WindowCommand::Redraw); + } + } + + /// Marks `widget` as invalidated, and if needed, refreshes the window. + pub fn invalidate(&self, widget: WidgetId) { + if self.redraw_status.invalidate(widget) { + self.redraw(); + } } } -impl From> for WindowHandle { - fn from(handle: kludgine::app::WindowHandle) -> Self { - WindowHandle(handle) +impl Eq for WindowHandle {} + +impl PartialEq for WindowHandle { + fn eq(&self, other: &Self) -> bool { + self.redraw_status == other.redraw_status + } +} + +impl Hash for WindowHandle { + fn hash(&self, state: &mut H) { + self.redraw_status.hash(state); } }