From 15480ba68be47bca3395e182dbcff019a99a09f2 Mon Sep 17 00:00:00 2001 From: Jonathan Johnson Date: Wed, 22 Nov 2023 15:46:58 -0800 Subject: [PATCH] Added Collapse widget Also tweaked progress bar animation --- examples/collapse.rs | 26 +++++++++ src/tree.rs | 2 +- src/widget.rs | 21 +++++++- src/widgets.rs | 2 + src/widgets/collapse.rs | 113 ++++++++++++++++++++++++++++++++++++++++ src/widgets/progress.rs | 16 +++--- 6 files changed, 169 insertions(+), 11 deletions(-) create mode 100644 examples/collapse.rs create mode 100644 src/widgets/collapse.rs diff --git a/examples/collapse.rs b/examples/collapse.rs new file mode 100644 index 0000000..33fc8ae --- /dev/null +++ b/examples/collapse.rs @@ -0,0 +1,26 @@ +use gooey::value::Dynamic; +use gooey::widget::MakeWidget; +use gooey::widgets::checkbox::Checkable; +use gooey::Run; + +const EXPLANATION: &str = + "The collapse widget allows showing and hiding another widget based on a Dynamic."; + +fn main() -> gooey::Result { + let collapse = Dynamic::new(false); + + collapse + .clone() + .into_checkbox("Collapse") + .and( + "Content Above" + .contain() + .and(EXPLANATION.collapse_vertically(collapse)) + .and("Content Below".contain()) + .into_rows(), + ) + .into_columns() + .centered() + .expand() + .run() +} diff --git a/src/tree.rs b/src/tree.rs index 7dff39b..afbca75 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -505,7 +505,7 @@ impl TreeData { fn invalidate(&mut self, id: LotId, include_hierarchy: bool) { let mut node = &mut self.nodes[id]; - while node.layout.is_some() { + loop { node.layout = None; node.last_layout_query = None; diff --git a/src/widget.rs b/src/widget.rs index 43ff166..519b598 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -28,8 +28,8 @@ use crate::utils::IgnorePoison; use crate::value::{IntoDynamic, IntoValue, Value}; use crate::widgets::checkbox::{Checkable, CheckboxState}; use crate::widgets::{ - Align, Button, Checkbox, Container, Expand, Resize, Scroll, Space, Stack, Style, Themed, - ThemedMode, + Align, Button, Checkbox, Collapse, Container, Expand, Resize, Scroll, Space, Stack, Style, + Themed, ThemedMode, }; use crate::window::{RunningWindow, ThemeMode, Window, WindowBehavior}; use crate::{ConstraintLimit, Run}; @@ -865,6 +865,23 @@ pub trait MakeWidget: Sized { fn themed_mode(self, mode: impl IntoValue) -> ThemedMode { ThemedMode::new(mode, self) } + + /// Returns a widget that collapses `self` horizontally based on the dynamic boolean value. + /// + /// This widget will be collapsed when the dynamic contains `true`, and + /// revealed when the dynamic contains `false`. + fn collapse_horizontally(self, collapse_when: impl IntoDynamic) -> Collapse { + Collapse::horizontal(collapse_when, self) + } + + /// Returns a widget that collapses `self` vertically based on the dynamic + /// boolean value. + /// + /// This widget will be collapsed when the dynamic contains `true`, and + /// revealed when the dynamic contains `false`. + fn collapse_vertically(self, collapse_when: impl IntoDynamic) -> Collapse { + Collapse::vertical(collapse_when, self) + } } /// A type that can create a [`WidgetInstance`] with a preallocated diff --git a/src/widgets.rs b/src/widgets.rs index f28c313..9a74c06 100644 --- a/src/widgets.rs +++ b/src/widgets.rs @@ -4,6 +4,7 @@ mod align; pub mod button; mod canvas; pub mod checkbox; +mod collapse; pub mod container; mod custom; mod data; @@ -27,6 +28,7 @@ pub use align::Align; pub use button::Button; pub use canvas::Canvas; pub use checkbox::Checkbox; +pub use collapse::Collapse; pub use container::Container; pub use custom::Custom; pub use data::Data; diff --git a/src/widgets/collapse.rs b/src/widgets/collapse.rs new file mode 100644 index 0000000..0662479 --- /dev/null +++ b/src/widgets/collapse.rs @@ -0,0 +1,113 @@ +use std::time::Duration; + +use kludgine::figures::units::Px; +use kludgine::figures::Size; + +use crate::animation::easings::{EaseInQuadradic, EaseOutQuadradic}; +use crate::animation::{AnimationHandle, AnimationTarget, EasingFunction, Spawn}; +use crate::context::LayoutContext; +use crate::value::{Dynamic, IntoDynamic}; +use crate::widget::{MakeWidget, WidgetRef, WrappedLayout, WrapperWidget}; +use crate::ConstraintLimit; + +/// A widget that collapses/hides its contents based on a [`Dynamic`]. +#[derive(Debug)] +pub struct Collapse { + child: WidgetRef, + collapse: Dynamic, + size: Dynamic, + collapse_animation: Option, + vertical: bool, +} + +impl Collapse { + /// Returns a widget that collapses `child` vertically based on the dynamic + /// boolean value. + /// + /// This widget will be collapsed when the dynamic contains `true`, and + /// revealed when the dynamic contains `false`. + pub fn vertical(collapse_when: impl IntoDynamic, child: impl MakeWidget) -> Self { + Self { + collapse: collapse_when.into_dynamic(), + child: WidgetRef::new(child), + size: Dynamic::default(), + vertical: true, + collapse_animation: None, + } + } + + /// Returns a widget that collapses `child` horizontally based on the + /// dynamic boolean value. + /// + /// This widget will be collapsed when the dynamic contains `true`, and + /// revealed when the dynamic contains `false`. + pub fn horizontal(collapse_when: impl IntoDynamic, child: impl MakeWidget) -> Self { + Self { + collapse: collapse_when.into_dynamic(), + child: WidgetRef::new(child), + size: Dynamic::default(), + vertical: false, + collapse_animation: None, + } + } + + fn note_child_size(&mut self, size: Px, context: &mut LayoutContext<'_, '_, '_, '_, '_>) { + let (easing, target) = if self.collapse.get_tracking_invalidate(context) { + (EasingFunction::from(EaseOutQuadradic), Px::ZERO) + } else { + (EasingFunction::from(EaseInQuadradic), size) + }; + match &self.collapse_animation { + Some(state) if state.target == target => {} + _ => { + // If this is our first setup, immediately give the child the + // space they request. + let duration = if self.collapse_animation.is_some() { + Duration::from_millis(250) + } else { + Duration::ZERO + }; + self.collapse_animation = Some(CollapseAnimation { + target, + _handle: self + .size + .transition_to(target) + .over(duration) + .with_easing(easing) + .spawn(), + }); + } + } + } +} + +impl WrapperWidget for Collapse { + fn child_mut(&mut self) -> &mut WidgetRef { + &mut self.child + } + + fn position_child( + &mut self, + size: Size, + _available_space: Size, + context: &mut LayoutContext<'_, '_, '_, '_, '_>, + ) -> WrappedLayout { + let clip_size = self.size.get_tracking_invalidate(context); + if self.vertical { + self.note_child_size(size.height, context); + + Size::new(size.width, clip_size) + } else { + self.note_child_size(size.width, context); + + Size::new(clip_size, size.height) + } + .into() + } +} + +#[derive(Debug)] +struct CollapseAnimation { + target: Px, + _handle: AnimationHandle, +} diff --git a/src/widgets/progress.rs b/src/widgets/progress.rs index 92fca29..d3f9a1f 100644 --- a/src/widgets/progress.rs +++ b/src/widgets/progress.rs @@ -5,7 +5,7 @@ use std::time::Duration; use kludgine::figures::Ranged; -use crate::animation::easings::EaseInOutQuadradic; +use crate::animation::easings::{EaseInOutQuadradic, EaseOutQuadradic}; use crate::animation::{ AnimationHandle, AnimationTarget, IntoAnimate, PercentBetween, Spawn, ZeroToOne, }; @@ -90,25 +90,25 @@ fn update_progress_bar( ) .immediately() .and_then( - end.transition_to(ZeroToOne::new(0.66)) + end.transition_to(ZeroToOne::new(0.75)) .over(Duration::from_millis(500)) - .with_easing(EaseInOutQuadradic), + .with_easing(EaseOutQuadradic), ) .and_then( start - .transition_to(ZeroToOne::new(0.33)) + .transition_to(ZeroToOne::new(0.25)) .over(Duration::from_millis(500)) - .with_easing(EaseInOutQuadradic), + .with_easing(EaseOutQuadradic), ) .and_then( end.transition_to(ZeroToOne::ONE) - .over(Duration::from_millis(500)) - .with_easing(EaseInOutQuadradic), + .over(Duration::from_millis(250)) + .with_easing(EaseOutQuadradic), ) .and_then( start .transition_to(ZeroToOne::ONE) - .over(Duration::from_millis(500)) + .over(Duration::from_millis(250)) .with_easing(EaseInOutQuadradic), ) .cycle()