From 288119a8310a1b4db3d4939944092ef6a46a257e Mon Sep 17 00:00:00 2001 From: Jonathan Johnson Date: Tue, 5 Dec 2023 08:51:55 -0800 Subject: [PATCH] Added Layers --- examples/layers.rs | 12 ++++ src/context.rs | 15 ++++- src/widget.rs | 35 ++++++++-- src/widgets.rs | 2 + src/widgets/layers.rs | 153 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 211 insertions(+), 6 deletions(-) create mode 100644 examples/layers.rs create mode 100644 src/widgets/layers.rs diff --git a/examples/layers.rs b/examples/layers.rs new file mode 100644 index 0000000..8b6db69 --- /dev/null +++ b/examples/layers.rs @@ -0,0 +1,12 @@ +use gooey::widget::MakeWidget; +use gooey::widgets::Space; +use gooey::Run; +use kludgine::Color; + +fn main() -> gooey::Result { + Space::colored(Color::RED) + .and("Layers stack widgets on top of each other") + .into_layers() + .centered() + .run() +} diff --git a/src/context.rs b/src/context.rs index ed72737..09643cf 100644 --- a/src/context.rs +++ b/src/context.rs @@ -23,7 +23,9 @@ use crate::styles::components::{ use crate::styles::{ComponentDefinition, Styles, Theme, ThemePair}; use crate::utils::IgnorePoison; use crate::value::{IntoValue, Value}; -use crate::widget::{EventHandling, ManagedWidget, WidgetId, WidgetInstance, WidgetRef}; +use crate::widget::{ + EventHandling, ManagedWidget, RootBehavior, WidgetId, WidgetInstance, WidgetRef, +}; use crate::window::{CursorState, RunningWindow, ThemeMode}; use crate::ConstraintLimit; @@ -461,6 +463,17 @@ impl<'context, 'window> EventContext<'context, 'window> { // sets it to `true` explicitly. self.pending_state.focus_is_advancing = advance; } + + /// Invokes + /// [`Widget::root_behavior()`](crate::widget::Widget::root_behavior) on + /// this context's widget and returns the result. + pub fn root_behavior(&mut self) -> Option<(RootBehavior, WidgetInstance)> { + self.current_node + .clone() + .lock() + .as_widget() + .root_behavior(self) + } } impl<'context, 'window> Deref for EventContext<'context, 'window> { diff --git a/src/widget.rs b/src/widget.rs index 777f6cd..989919b 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -5,6 +5,7 @@ use std::clone::Clone; use std::fmt::{self, Debug}; use std::ops::{ControlFlow, Deref, DerefMut}; use std::panic::UnwindSafe; +use std::slice; use std::sync::atomic::{self, AtomicU64}; use std::sync::{Arc, Mutex, MutexGuard}; @@ -38,8 +39,8 @@ use crate::utils::IgnorePoison; use crate::value::{Dynamic, IntoDynamic, IntoValue, Validation, Value}; use crate::widgets::checkbox::{Checkable, CheckboxState}; use crate::widgets::{ - Align, Button, Checkbox, Collapse, Container, Expand, Resize, Scroll, Space, Stack, Style, - Themed, ThemedMode, Validated, + Align, Button, Checkbox, Collapse, Container, Expand, Layers, Resize, Scroll, Space, Stack, + Style, Themed, ThemedMode, Validated, }; use crate::window::{RunningWindow, ThemeMode, Window, WindowBehavior}; use crate::{ConstraintLimit, Run}; @@ -222,7 +223,7 @@ pub trait Widget: Send + UnwindSafe + Debug + 'static { fn root_behavior( &mut self, context: &mut EventContext<'_, '_>, - ) -> Option<(RootBehavior, &WidgetInstance)> { + ) -> Option<(RootBehavior, WidgetInstance)> { None } } @@ -531,8 +532,9 @@ where fn root_behavior( &mut self, context: &mut EventContext<'_, '_>, - ) -> Option<(RootBehavior, &WidgetInstance)> { - T::root_behavior(self, context).map(|behavior| (behavior, T::child_mut(self).widget())) + ) -> Option<(RootBehavior, WidgetInstance)> { + T::root_behavior(self, context) + .map(|behavior| (behavior, T::child_mut(self).widget().clone())) } fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) { @@ -1675,6 +1677,13 @@ impl Children { pub fn into_columns(self) -> Stack { Stack::columns(self) } + + /// Returns `self` as [`Layers`], with the widgets being stacked in the Z + /// direction. + #[must_use] + pub fn into_layers(self) -> Layers { + Layers::new(self) + } } impl Debug for Children { @@ -1695,6 +1704,13 @@ impl Dynamic { pub fn into_columns(self) -> Stack { Stack::columns(self) } + + /// Returns `self` as [`Layers`], with the widgets being stacked in the Z + /// direction. + #[must_use] + pub fn into_layers(self) -> Layers { + Layers::new(self) + } } impl FromIterator for Children @@ -1722,6 +1738,15 @@ impl DerefMut for Children { } } +impl<'a> IntoIterator for &'a Children { + type IntoIter = slice::Iter<'a, WidgetInstance>; + type Item = &'a WidgetInstance; + + fn into_iter(self) -> Self::IntoIter { + self.ordered.iter() + } +} + /// A child widget #[derive(Clone)] pub enum WidgetRef { diff --git a/src/widgets.rs b/src/widgets.rs index 1c44e92..069415e 100644 --- a/src/widgets.rs +++ b/src/widgets.rs @@ -12,6 +12,7 @@ mod expand; pub mod grid; pub mod input; pub mod label; +mod layers; mod mode_switch; pub mod progress; mod radio; @@ -38,6 +39,7 @@ pub use data::Data; pub use expand::Expand; pub use input::Input; pub use label::Label; +pub use layers::Layers; pub use mode_switch::ThemedMode; pub use progress::ProgressBar; pub use radio::Radio; diff --git a/src/widgets/layers.rs b/src/widgets/layers.rs new file mode 100644 index 0000000..2b8ca75 --- /dev/null +++ b/src/widgets/layers.rs @@ -0,0 +1,153 @@ +use std::fmt; + +use gooey::widget::{RootBehavior, WidgetInstance}; +use kludgine::figures::units::UPx; +use kludgine::figures::{IntoSigned, Rect, Size, Zero}; + +use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext}; +use crate::value::{Generation, IntoValue, Value}; +use crate::widget::{Children, ManagedWidget, Widget}; +use crate::ConstraintLimit; + +/// A Z-direction stack of widgets. +#[derive(Debug)] +pub struct Layers { + /// The children that are laid out as layers with index 0 being the lowest (bottom). + pub children: Value, + mounted: Vec, + mounted_generation: Option, +} + +impl Layers { + /// Returns a new instance that lays out `children` as layers. + pub fn new(children: impl IntoValue) -> Self { + Self { + children: children.into_value(), + mounted: Vec::new(), + mounted_generation: None, + } + } + + fn synchronize_children(&mut self, context: &mut EventContext<'_, '_>) { + let current_generation = self.children.generation(); + self.children.invalidate_when_changed(context); + if current_generation.map_or_else( + || self.children.map(Children::len) != self.mounted.len(), + |gen| Some(gen) != self.mounted_generation, + ) { + self.mounted_generation = self.children.generation(); + self.children.map(|children| { + for (index, widget) in children.iter().enumerate() { + if self + .mounted + .get(index) + .map_or(true, |child| child != widget) + { + // These entries do not match. See if we can find the + // new id somewhere else, if so we can swap the entries. + if let Some((swap_index, _)) = self + .mounted + .iter() + .enumerate() + .skip(index + 1) + .find(|(_, child)| *child == widget) + { + self.mounted.swap(index, swap_index); + } else { + // This is a brand new child. + self.mounted + .insert(index, context.push_child(widget.clone())); + } + } + } + + // Any children remaining at the end of this process are ones + // that have been removed. + for removed in self.mounted.drain(children.len()..) { + context.remove_child(&removed); + } + }); + } + } +} + +impl Widget for Layers { + fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) { + self.synchronize_children(&mut context.as_event_context()); + + for child in &self.mounted { + context.for_other(child).redraw(); + } + } + + fn summarize(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.children.map(|children| { + let mut f = f.debug_tuple("Layered"); + for child in children { + f.field(child); + } + + f.finish() + }) + } + + fn layout( + &mut self, + available_space: Size, + context: &mut LayoutContext<'_, '_, '_, '_, '_>, + ) -> Size { + self.synchronize_children(&mut context.as_event_context()); + + let mut size = Size::ZERO; + + for child in &self.mounted { + size = size.max( + context + .for_other(child) + .as_temporary() + .layout(available_space), + ); + } + + // Now we know the size of the widget, we can request the widgets fill + // the allocated space. + let layout = Rect::from(size).into_signed(); + for child in &self.mounted { + context + .for_other(child) + .layout(size.map(ConstraintLimit::Fill)); + context.set_child_layout(child, layout); + } + + size + } + + fn mounted(&mut self, context: &mut EventContext<'_, '_>) { + self.synchronize_children(context); + } + + fn unmounted(&mut self, context: &mut EventContext<'_, '_>) { + for child in self.mounted.drain(..) { + context.remove_child(&child); + } + self.mounted_generation = None; + } + + fn root_behavior( + &mut self, + context: &mut EventContext<'_, '_>, + ) -> Option<(RootBehavior, WidgetInstance)> { + self.synchronize_children(context); + + for child in &self.mounted { + let Some((child_behavior, next_in_chain)) = context.for_other(child).root_behavior() + else { + continue; + }; + + return Some((child_behavior, next_in_chain)); + } + + None + } +}