diff --git a/src/context.rs b/src/context.rs index fa0d0be..266f2bd 100644 --- a/src/context.rs +++ b/src/context.rs @@ -468,7 +468,17 @@ impl<'context, 'window, 'clip, 'gfx, 'pass> GraphicsContext<'context, 'window, ' /// Invokes [`Widget::redraw()`](crate::widget::Widget::redraw) on this /// context's widget. + /// + /// # Panics + /// + /// This function panics if the widget being drawn has no layout set (via + /// [`LayoutContext::set_child_layout()`]). pub fn redraw(&mut self) { + assert!( + self.last_layout().is_some(), + "redraw called without set_widget_layout" + ); + self.current_node .tree .note_widget_rendered(self.current_node.id()); diff --git a/src/value.rs b/src/value.rs index a759ad4..3bb70b7 100644 --- a/src/value.rs +++ b/src/value.rs @@ -264,17 +264,7 @@ impl DynamicData { let mut changed = true; let result = map(&mut state.wrapped.value, &mut changed); if changed { - state.wrapped.generation = state.wrapped.generation.next(); - - for callback in &mut state.callbacks { - callback.update(&state.wrapped); - } - for window in state.windows.drain(..) { - window.redraw(); - } - for waker in state.wakers.drain(..) { - waker.wake(); - } + state.note_changed(); } result @@ -321,6 +311,22 @@ struct State { readers: usize, } +impl State { + fn note_changed(&mut self) { + self.wrapped.generation = self.wrapped.generation.next(); + + for callback in &mut self.callbacks { + callback.update(&self.wrapped); + } + for window in self.windows.drain(..) { + window.redraw(); + } + for waker in self.wakers.drain(..) { + waker.wake(); + } + } +} + impl Debug for State where T: Debug, @@ -353,6 +359,9 @@ struct GenerationalValue { } /// An exclusive reference to the contents of a [`Dynamic`]. +/// +/// If the contents are accessed through [`DerefMut`], all obververs will be +/// notified of a change when this guard is dropped. #[derive(Debug)] pub struct DynamicGuard<'a, T> { guard: MutexGuard<'a, State>, @@ -377,7 +386,7 @@ impl<'a, T> DerefMut for DynamicGuard<'a, T> { impl Drop for DynamicGuard<'_, T> { fn drop(&mut self) { if self.accessed_mut { - todo!("trigger callbacks") + self.guard.note_changed(); } } } diff --git a/src/widget.rs b/src/widget.rs index 78ed448..cb1c95f 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -12,7 +12,7 @@ use kludgine::app::winit::event::{ DeviceId, Ime, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase, }; use kludgine::figures::units::{Px, UPx}; -use kludgine::figures::{Point, Rect, Size}; +use kludgine::figures::{IntoSigned, IntoUnsigned, Point, Rect, Size}; use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext}; use crate::styles::components::VisualOrder; @@ -167,6 +167,263 @@ where } } +/// A [`Widget`] that contains a single child. +pub trait WrapperWidget: Debug + Send + UnwindSafe + 'static { + /// Returns the child widget. + fn child(&mut self) -> &mut WidgetRef; + + /// Returns the rectangle that the child widget should occupy given + /// `available_space`. + #[allow(unused_variables)] + fn layout_child( + &mut self, + available_space: Size, + context: &mut LayoutContext<'_, '_, '_, '_, '_>, + ) -> Rect { + let child = self.child().mounted(&mut context.as_event_context()); + + context + .for_other(&child) + .layout(available_space) + .into_signed() + .into() + } + + /// The widget has been mounted into a parent widget. + #[allow(unused_variables)] + fn mounted(&mut self, context: &mut EventContext<'_, '_>) {} + + /// The widget has been removed from its parent widget. + #[allow(unused_variables)] + fn unmounted(&mut self, context: &mut EventContext<'_, '_>) {} + + /// Returns true if this widget should respond to mouse input at `location`. + #[allow(unused_variables)] + fn hit_test(&mut self, location: Point, context: &mut EventContext<'_, '_>) -> bool { + false + } + + /// The widget is currently has a cursor hovering it at `location`. + #[allow(unused_variables)] + fn hover(&mut self, location: Point, context: &mut EventContext<'_, '_>) {} + + /// The widget is no longer being hovered. + #[allow(unused_variables)] + fn unhover(&mut self, context: &mut EventContext<'_, '_>) {} + + /// This widget has been targeted to be focused. If this function returns + /// true, the widget will be focused. If false, Gooey will continue + /// searching for another focus target. + #[allow(unused_variables)] + fn accept_focus(&mut self, context: &mut EventContext<'_, '_>) -> bool { + false + } + + /// The widget has received focus for user input. + #[allow(unused_variables)] + fn focus(&mut self, context: &mut EventContext<'_, '_>) {} + + /// The widget is no longer focused for user input. + #[allow(unused_variables)] + fn blur(&mut self, context: &mut EventContext<'_, '_>) {} + + /// The widget has become the active widget. + #[allow(unused_variables)] + fn activate(&mut self, context: &mut EventContext<'_, '_>) {} + + /// The widget is no longer active. + #[allow(unused_variables)] + fn deactivate(&mut self, context: &mut EventContext<'_, '_>) {} + + /// A mouse button event has occurred at `location`. Returns whether the + /// event has been handled or not. + /// + /// If an event is handled, the widget will receive callbacks for + /// [`mouse_drag`](Self::mouse_drag) and [`mouse_up`](Self::mouse_up). + #[allow(unused_variables)] + fn mouse_down( + &mut self, + location: Point, + device_id: DeviceId, + button: MouseButton, + context: &mut EventContext<'_, '_>, + ) -> EventHandling { + IGNORED + } + + /// A mouse button is being held down as the cursor is moved across the + /// widget. + #[allow(unused_variables)] + fn mouse_drag( + &mut self, + location: Point, + device_id: DeviceId, + button: MouseButton, + context: &mut EventContext<'_, '_>, + ) { + } + + /// A mouse button is no longer being pressed. + #[allow(unused_variables)] + fn mouse_up( + &mut self, + location: Option>, + device_id: DeviceId, + button: MouseButton, + context: &mut EventContext<'_, '_>, + ) { + } + + /// A keyboard event has been sent to this widget. Returns whether the event + /// has been handled or not. + #[allow(unused_variables)] + fn keyboard_input( + &mut self, + device_id: DeviceId, + input: KeyEvent, + is_synthetic: bool, + context: &mut EventContext<'_, '_>, + ) -> EventHandling { + IGNORED + } + + /// An input manager event has been sent to this widget. Returns whether the + /// event has been handled or not. + #[allow(unused_variables)] + fn ime(&mut self, ime: Ime, context: &mut EventContext<'_, '_>) -> EventHandling { + IGNORED + } + + /// A mouse wheel event has been sent to this widget. Returns whether the + /// event has been handled or not. + #[allow(unused_variables)] + fn mouse_wheel( + &mut self, + device_id: DeviceId, + delta: MouseScrollDelta, + phase: TouchPhase, + context: &mut EventContext<'_, '_>, + ) -> EventHandling { + IGNORED + } +} + +impl Widget for T +where + T: WrapperWidget, +{ + fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) { + let child = self.child().mounted(&mut context.as_event_context()); + context.for_other(&child).redraw(); + } + + fn layout( + &mut self, + available_space: Size, + context: &mut LayoutContext<'_, '_, '_, '_, '_>, + ) -> Size { + let child = self.child().mounted(&mut context.as_event_context()); + + let layout = self.layout_child(available_space, context); + context.set_child_layout(&child, layout); + layout.size.into_unsigned() + } + + fn mounted(&mut self, context: &mut EventContext<'_, '_>) { + T::mounted(self, context); + } + + fn unmounted(&mut self, context: &mut EventContext<'_, '_>) { + T::unmounted(self, context); + } + + fn hit_test(&mut self, location: Point, context: &mut EventContext<'_, '_>) -> bool { + T::hit_test(self, location, context) + } + + fn hover(&mut self, location: Point, context: &mut EventContext<'_, '_>) { + T::hover(self, location, context); + } + + fn unhover(&mut self, context: &mut EventContext<'_, '_>) { + T::unhover(self, context); + } + + fn accept_focus(&mut self, context: &mut EventContext<'_, '_>) -> bool { + T::accept_focus(self, context) + } + + fn focus(&mut self, context: &mut EventContext<'_, '_>) { + T::focus(self, context); + } + + fn blur(&mut self, context: &mut EventContext<'_, '_>) { + T::blur(self, context); + } + + fn activate(&mut self, context: &mut EventContext<'_, '_>) { + T::activate(self, context); + } + + fn deactivate(&mut self, context: &mut EventContext<'_, '_>) { + T::deactivate(self, context); + } + + fn mouse_down( + &mut self, + location: Point, + device_id: DeviceId, + button: MouseButton, + context: &mut EventContext<'_, '_>, + ) -> EventHandling { + T::mouse_down(self, location, device_id, button, context) + } + + fn mouse_drag( + &mut self, + location: Point, + device_id: DeviceId, + button: MouseButton, + context: &mut EventContext<'_, '_>, + ) { + T::mouse_drag(self, location, device_id, button, context); + } + + fn mouse_up( + &mut self, + location: Option>, + device_id: DeviceId, + button: MouseButton, + context: &mut EventContext<'_, '_>, + ) { + T::mouse_up(self, location, device_id, button, context); + } + + fn keyboard_input( + &mut self, + device_id: DeviceId, + input: KeyEvent, + is_synthetic: bool, + context: &mut EventContext<'_, '_>, + ) -> EventHandling { + T::keyboard_input(self, device_id, input, is_synthetic, context) + } + + fn ime(&mut self, ime: Ime, context: &mut EventContext<'_, '_>) -> EventHandling { + T::ime(self, ime, context) + } + + fn mouse_wheel( + &mut self, + device_id: DeviceId, + delta: MouseScrollDelta, + phase: TouchPhase, + context: &mut EventContext<'_, '_>, + ) -> EventHandling { + T::mouse_wheel(self, device_id, delta, phase, context) + } +} + /// A type that can create a [`WidgetInstance`]. pub trait MakeWidget: Sized { /// Returns a new widget. diff --git a/src/widgets.rs b/src/widgets.rs index 031d801..c35cd4a 100644 --- a/src/widgets.rs +++ b/src/widgets.rs @@ -8,6 +8,7 @@ mod input; mod label; mod resize; pub mod scroll; +mod space; pub mod stack; mod style; mod tilemap; @@ -20,6 +21,7 @@ pub use input::Input; pub use label::Label; pub use resize::Resize; pub use scroll::Scroll; +pub use space::Space; pub use stack::Stack; pub use style::Style; pub use tilemap::TileMap; diff --git a/src/widgets/align.rs b/src/widgets/align.rs index ae23734..79969cb 100644 --- a/src/widgets/align.rs +++ b/src/widgets/align.rs @@ -3,10 +3,10 @@ use std::fmt::Debug; use kludgine::figures::units::UPx; use kludgine::figures::{Fraction, IntoSigned, IntoUnsigned, Point, Rect, ScreenScale, Size}; -use crate::context::{AsEventContext, GraphicsContext, LayoutContext}; +use crate::context::{AsEventContext, LayoutContext}; use crate::styles::{Dimension, Edges, FlexibleDimension}; use crate::value::{IntoValue, Value}; -use crate::widget::{MakeWidget, Widget, WidgetRef}; +use crate::widget::{MakeWidget, WidgetRef, WrapperWidget}; use crate::ConstraintLimit; /// A widget aligns its contents to its container's boundaries. @@ -145,31 +145,31 @@ impl FrameInfo { } } -impl Widget for Align { - fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) { - let child = self.child.mounted(&mut context.as_event_context()); - context.for_other(&child).redraw(); +impl WrapperWidget for Align { + fn child(&mut self) -> &mut WidgetRef { + &mut self.child } - fn layout( + fn layout_child( &mut self, available_space: Size, context: &mut LayoutContext<'_, '_, '_, '_, '_>, - ) -> Size { - let child = self.child.mounted(&mut context.as_event_context()); + ) -> Rect { let layout = self.measure(available_space, context); - context.set_child_layout( - &child, - Rect::new( - Point::new( - layout.margin.left.into_signed(), - layout.margin.top.into_signed(), - ), - layout.content.into_signed(), - ), - ); - layout.size().into_unsigned() + Rect::new( + Point::new( + layout.margin.left.into_signed(), + layout.margin.top.into_signed(), + ), + layout.content.into_signed(), + ) + } +} + +impl AsMut for Align { + fn as_mut(&mut self) -> &mut WidgetRef { + &mut self.child } } @@ -178,9 +178,3 @@ struct Layout { margin: Edges, content: Size, } - -impl Layout { - pub fn size(&self) -> Size { - self.margin.size() + self.content - } -} diff --git a/src/widgets/expand.rs b/src/widgets/expand.rs index efe6d9e..6759263 100644 --- a/src/widgets/expand.rs +++ b/src/widgets/expand.rs @@ -1,8 +1,9 @@ -use kludgine::figures::units::UPx; +use kludgine::figures::units::{Px, UPx}; use kludgine::figures::{IntoSigned, Rect, Size}; -use crate::context::{AsEventContext, GraphicsContext, LayoutContext}; -use crate::widget::{MakeWidget, Widget, WidgetRef}; +use crate::context::{AsEventContext, LayoutContext}; +use crate::widget::{MakeWidget, WidgetRef, WrapperWidget}; +use crate::widgets::Space; use crate::ConstraintLimit; /// A widget that expands its child widget to fill the parent. @@ -14,7 +15,7 @@ pub struct Expand { /// The weight to use when splitting available space with multiple /// [`Expand`] widgets. pub weight: u8, - child: Option, + child: WidgetRef, } impl Default for Expand { @@ -28,7 +29,7 @@ impl Expand { #[must_use] pub fn new(child: impl MakeWidget) -> Self { Self { - child: Some(WidgetRef::new(child)), + child: WidgetRef::new(child), weight: 1, } } @@ -37,7 +38,7 @@ impl Expand { #[must_use] pub fn empty() -> Self { Self { - child: None, + child: WidgetRef::new(Space), weight: 1, } } @@ -49,57 +50,44 @@ impl Expand { #[must_use] pub fn weighted(weight: u8, child: impl MakeWidget) -> Self { Self { - child: Some(WidgetRef::new(child)), + child: WidgetRef::new(child), weight, } } /// Returns a reference to the child widget. #[must_use] - pub fn child(&self) -> Option<&WidgetRef> { - self.child.as_ref() + pub const fn child(&self) -> &WidgetRef { + &self.child } } -impl Widget for Expand { - fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) { - let Some(child) = &mut self.child else { return }; - let child = child.mounted(&mut context.as_event_context()); - context.for_other(&child).redraw(); +impl WrapperWidget for Expand { + fn child(&mut self) -> &mut WidgetRef { + &mut self.child } - fn layout( + fn layout_child( &mut self, available_space: Size, context: &mut LayoutContext<'_, '_, '_, '_, '_>, - ) -> Size { + ) -> Rect { let available_space = Size::new( ConstraintLimit::Known(available_space.width.max()), ConstraintLimit::Known(available_space.height.max()), ); - let child = self - .child - .as_mut() - .map(|child| child.mounted(&mut context.as_event_context())); - let size = if let Some(child) = &child { - context.for_other(child).layout(available_space) - } else { - Size::default() - }; + let child = self.child.mounted(&mut context.as_event_context()); + let size = context.for_other(&child).layout(available_space); - let expanded_size = Size::new( + Size::::new( available_space .width .fit_measured(size.width, context.graphics.scale()), available_space .height .fit_measured(size.height, context.graphics.scale()), - ); - - if let Some(child) = child { - context.set_child_layout(&child, Rect::from(expanded_size.into_signed())); - } - - expanded_size + ) + .into_signed() + .into() } } diff --git a/src/widgets/resize.rs b/src/widgets/resize.rs index 0fbb274..2824959 100644 --- a/src/widgets/resize.rs +++ b/src/widgets/resize.rs @@ -1,9 +1,8 @@ -use kludgine::figures::units::UPx; use kludgine::figures::{Fraction, IntoSigned, IntoUnsigned, Rect, ScreenScale, Size}; -use crate::context::{AsEventContext, GraphicsContext, LayoutContext}; +use crate::context::{AsEventContext, LayoutContext}; use crate::styles::Dimension; -use crate::widget::{MakeWidget, Widget, WidgetRef}; +use crate::widget::{MakeWidget, WidgetRef, WrapperWidget}; use crate::ConstraintLimit; /// A widget that resizes its contained widget to an explicit size. @@ -57,17 +56,16 @@ impl Resize { } } -impl Widget for Resize { - fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) { - let child = self.child.mounted(&mut context.as_event_context()); - context.for_other(&child).redraw(); +impl WrapperWidget for Resize { + fn child(&mut self) -> &mut WidgetRef { + &mut self.child } - fn layout( + fn layout_child( &mut self, available_space: Size, context: &mut LayoutContext<'_, '_, '_, '_, '_>, - ) -> Size { + ) -> Rect { let child = self.child.mounted(&mut context.as_event_context()); let size = if let (Some(width), Some(height)) = (self.width, self.height) { Size::new( @@ -85,8 +83,7 @@ impl Widget for Resize { ); context.for_other(&child).layout(available_space) }; - context.set_child_layout(&child, Rect::from(size.into_signed())); - size + Rect::from(size.into_signed()) } } diff --git a/src/widgets/space.rs b/src/widgets/space.rs new file mode 100644 index 0000000..aae3647 --- /dev/null +++ b/src/widgets/space.rs @@ -0,0 +1,22 @@ +use kludgine::figures::units::UPx; +use kludgine::figures::Size; + +use crate::context::{GraphicsContext, LayoutContext}; +use crate::widget::Widget; +use crate::ConstraintLimit; + +/// A widget that does nothing and draws nothing. +#[derive(Debug, Clone)] +pub struct Space; + +impl Widget for Space { + fn redraw(&mut self, _context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {} + + fn layout( + &mut self, + _available_space: Size, + _context: &mut LayoutContext<'_, '_, '_, '_, '_>, + ) -> Size { + Size::default() + } +} diff --git a/src/widgets/stack.rs b/src/widgets/stack.rs index 7971f63..87e82d9 100644 --- a/src/widgets/stack.rs +++ b/src/widgets/stack.rs @@ -1,5 +1,5 @@ -//! A widget that combines an array of [`Widgets`] into one. -// TODO on scale change, all Lp children need to resize +//! A widget that combines a collection of [`Children`] widgets into one. +// TODO on scale change, all `Lp` children need to resize use std::ops::Deref; @@ -11,10 +11,10 @@ use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContex use crate::styles::Dimension; use crate::value::{Generation, IntoValue, Value}; use crate::widget::{Children, ManagedWidget, Widget, WidgetRef}; -use crate::widgets::{Expand, Label, Resize}; +use crate::widgets::{Expand, Resize}; use crate::ConstraintLimit; -/// A widget that displays a collection of [`Widgets`] in a +/// A widget that displays a collection of [`Children`] widgets in a /// [direction](StackDirection). #[derive(Debug)] pub struct Stack { @@ -87,21 +87,12 @@ impl Stack { let guard = widget.lock(); let (mut widget, dimension) = if let Some(expand) = guard.downcast_ref::() { - if let Some(child) = expand.child() { - ( - child.clone(), - StackDimension::Fractional { - weight: expand.weight, - }, - ) - } else { - ( - WidgetRef::new(Label::new("")), // TODO this should be an empty widget. - StackDimension::Fractional { - weight: expand.weight, - }, - ) - } + ( + expand.child().clone(), + StackDimension::Fractional { + weight: expand.weight, + }, + ) } else if let Some((child, size)) = guard.downcast_ref::().and_then(|r| { match self.layout.orientation.orientation { diff --git a/src/widgets/style.rs b/src/widgets/style.rs index 4b1d257..546fe48 100644 --- a/src/widgets/style.rs +++ b/src/widgets/style.rs @@ -1,10 +1,6 @@ -use kludgine::figures::units::UPx; -use kludgine::figures::Size; - -use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext}; +use crate::context::EventContext; use crate::styles::Styles; -use crate::widget::{MakeWidget, Widget, WidgetRef}; -use crate::ConstraintLimit; +use crate::widget::{MakeWidget, WidgetRef, WrapperWidget}; /// A widget that applies a set of [`Styles`] to all contained widgets. #[derive(Debug)] @@ -24,22 +20,12 @@ impl Style { } } -impl Widget for Style { +impl WrapperWidget for Style { + fn child(&mut self) -> &mut WidgetRef { + &mut self.child + } + fn mounted(&mut self, context: &mut EventContext<'_, '_>) { context.attach_styles(self.styles.clone()); } - - fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) { - let child = self.child.mounted(&mut context.as_event_context()); - context.for_other(&child).redraw(); - } - - fn layout( - &mut self, - available_space: Size, - context: &mut LayoutContext<'_, '_, '_, '_, '_>, - ) -> Size { - let child = self.child.mounted(&mut context.as_event_context()); - context.for_other(&child).layout(available_space) - } }