From a2e28cb52236b8e4df69e2015c17856bcdb965ec Mon Sep 17 00:00:00 2001 From: Jonathan Johnson Date: Thu, 9 Nov 2023 10:04:09 -0800 Subject: [PATCH] Dynamic::take, align helpers, scroll fix Scroll was previously taking the graphics region as its control size as opposed to the constraints. This was due to this code originally living in redraw. This fixes scroll areas being able to scroll their contents fully when sharing window space with other widgts. --- examples/login.rs | 67 +++++++++++++++++++------------------------ src/styles.rs | 14 +++++++-- src/value.rs | 26 +++++++++++++++++ src/widget.rs | 54 +++++++++++++++++++++++++++++++++- src/widgets/align.rs | 46 ++++++++++++++++++++++++----- src/widgets/scroll.rs | 4 ++- 6 files changed, 163 insertions(+), 48 deletions(-) diff --git a/examples/login.rs b/examples/login.rs index 51af3832e..96cf1221d 100644 --- a/examples/login.rs +++ b/examples/login.rs @@ -13,46 +13,39 @@ fn main() -> gooey::Result { let valid = (&username, &password).map_each(|(username, password)| validate(username, password)); + // TODO this should be a grid layout to ensure proper visual alignment. + let username_row = Stack::columns( + Label::new("Username").and(Input::new(username.clone()).fit_horizontally().expand()), + ); + + let password_row = Stack::columns(Label::new("Password").and( + // TODO secure input + Input::new(password.clone()).fit_horizontally().expand(), + )); + + let buttons = Stack::columns( + Button::new("Cancel") + .on_click(|_| { + eprintln!("Login cancelled"); + exit(0) + }) + .into_escape() + .and(Expand::empty()) + .and( + Button::new("Log In") + .enabled(valid) + .on_click(move |_| { + println!("Welcome, {}", username.get()); + exit(0); + }) + .into_default(), + ), + ); + Resize::width( // TODO We need a min/max range for the Resize widget Lp::points(400), - Stack::rows( - Stack::columns( - Label::new("Username").and( - Input::new(username.clone()) - .centered() - .fit_horizontally() - .expand(), - ), - ) - .and(Stack::columns( - Label::new("Password").and( - // TODO secure input - Input::new(password.clone()) - .centered() - .fit_horizontally() - .expand(), - ), - )) - .and(Stack::columns( - Button::new("Cancel") - .on_click(|_| { - eprintln!("Login cancelled"); - exit(0) - }) - .into_escape() - .and(Expand::empty()) - .and( - Button::new("Log In") - .enabled(valid) - .on_click(move |_| { - println!("Welcome, {}", username.get()); - exit(0); - }) - .into_default(), - ), - )), - ), + Stack::rows(username_row.and(password_row).and(buttons)), ) .centered() .expand() diff --git a/src/styles.rs b/src/styles.rs index 573e927cf..39a3db729 100644 --- a/src/styles.rs +++ b/src/styles.rs @@ -235,9 +235,14 @@ pub enum FlexibleDimension { Dimension(Dimension), } +impl FlexibleDimension { + /// A dimension of 0 pixels. + pub const ZERO: Self = Self::Dimension(Dimension::ZERO); +} + impl Default for FlexibleDimension { fn default() -> Self { - Self::Dimension(Dimension::default()) + Self::ZERO } } @@ -268,9 +273,14 @@ pub enum Dimension { Lp(Lp), } +impl Dimension { + /// A dimension of 0 pixels. + pub const ZERO: Self = Self::Px(Px(0)); +} + impl Default for Dimension { fn default() -> Self { - Self::Px(Px(0)) + Self::ZERO } } diff --git a/src/value.rs b/src/value.rs index b93a5404a..ce5a776a5 100644 --- a/src/value.rs +++ b/src/value.rs @@ -117,6 +117,32 @@ impl Dynamic { self.0.get().value } + /// Returns the currently stored value, replacing the current contents with + /// `T::default()`. + #[must_use] + pub fn take(&self) -> T + where + T: Default, + { + std::mem::take(&mut self.lock()) + } + + /// Checks if the currently stored value is different than `T::default()`, + /// and if so, returns `Some(self.take())`. + #[must_use] + pub fn take_if_not_default(&self) -> Option + where + T: Default + PartialEq, + { + let default = T::default(); + let mut guard = self.lock(); + if *guard == default { + None + } else { + Some(std::mem::replace(&mut guard, default)) + } + } + /// Replaces the contents with `new_value`, returning the previous contents. /// Before returning from this function, all observers will be notified that /// the contents have been updated. diff --git a/src/widget.rs b/src/widget.rs index d38980400..6b9d3f189 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -3,7 +3,7 @@ use std::any::Any; use std::clone::Clone; use std::fmt::Debug; -use std::ops::{ControlFlow, Deref}; +use std::ops::{ControlFlow, Deref, DerefMut}; use std::panic::UnwindSafe; use std::sync::atomic::{self, AtomicU64}; use std::sync::{Arc, Mutex, MutexGuard, PoisonError}; @@ -514,6 +514,36 @@ pub trait MakeWidget: Sized { Align::centered(self) } + /// Aligns `self` to the left. + fn align_left(self) -> Align { + self.centered().align_left() + } + + /// Aligns `self` to the right. + fn align_right(self) -> Align { + self.centered().align_right() + } + + /// Aligns `self` to the top. + fn align_top(self) -> Align { + self.centered().align_top() + } + + /// Aligns `self` to the bottom. + fn align_bottom(self) -> Align { + self.centered().align_bottom() + } + + /// Fits `self` horizontally within its parent. + fn fit_horizontally(self) -> Align { + self.centered().fit_horizontally() + } + + /// Fits `self` vertically within its parent. + fn fit_vertically(self) -> Align { + self.centered().fit_vertically() + } + /// Allows scrolling `self` both vertically and horizontally. #[must_use] fn scroll(self) -> Scroll { @@ -1002,6 +1032,14 @@ impl Children { self.ordered.push(widget.make_widget()); } + /// Inserts `widget` into the list at `index`. + pub fn insert(&mut self, index: usize, widget: W) + where + W: MakeWidget, + { + self.ordered.insert(index, widget.make_widget()); + } + /// Adds `widget` to self and returns the updated list. pub fn and(mut self, widget: W) -> Self where @@ -1022,6 +1060,14 @@ impl Children { pub fn is_empty(&self) -> bool { self.ordered.is_empty() } + + /// Truncates the collection of children to `length`. + /// + /// If this collection is already smaller or the same size as `length`, this + /// function does nothing. + pub fn truncate(&mut self, length: usize) { + self.ordered.truncate(length); + } } impl FromIterator for Children @@ -1043,6 +1089,12 @@ impl Deref for Children { } } +impl DerefMut for Children { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.ordered + } +} + /// A child widget #[derive(Debug, Clone)] pub enum WidgetRef { diff --git a/src/widgets/align.rs b/src/widgets/align.rs index 79969cb6e..8705988cc 100644 --- a/src/widgets/align.rs +++ b/src/widgets/align.rs @@ -4,7 +4,7 @@ use kludgine::figures::units::UPx; use kludgine::figures::{Fraction, IntoSigned, IntoUnsigned, Point, Rect, ScreenScale, Size}; use crate::context::{AsEventContext, LayoutContext}; -use crate::styles::{Dimension, Edges, FlexibleDimension}; +use crate::styles::{Edges, FlexibleDimension}; use crate::value::{IntoValue, Value}; use crate::widget::{MakeWidget, WidgetRef, WrapperWidget}; use crate::ConstraintLimit; @@ -32,22 +32,54 @@ impl Align { Self::new(FlexibleDimension::Auto, widget) } - /// Sets the left and right edges to 0 and returns self. + /// Sets the left edge of alignment to 0 and returns self. + #[must_use] + pub fn align_left(mut self) -> Self { + self.edges + .map_mut(|edges| edges.left = FlexibleDimension::ZERO); + self + } + + /// Sets the top edge of alignment to 0 and returns self. + #[must_use] + pub fn align_top(mut self) -> Self { + self.edges + .map_mut(|edges| edges.top = FlexibleDimension::ZERO); + self + } + + /// Sets the bottom edge of alignment to 0 and returns self. + #[must_use] + pub fn align_bottom(mut self) -> Self { + self.edges + .map_mut(|edges| edges.bottom = FlexibleDimension::ZERO); + self + } + + /// Sets the right edge of alignment to 0 and returns self. + #[must_use] + pub fn align_right(mut self) -> Self { + self.edges + .map_mut(|edges| edges.right = FlexibleDimension::ZERO); + self + } + + /// Sets the left and right edges of alignment to 0 and returns self. #[must_use] pub fn fit_horizontally(mut self) -> Self { self.edges.map_mut(|edges| { - edges.left = FlexibleDimension::Dimension(Dimension::default()); - edges.right = FlexibleDimension::Dimension(Dimension::default()); + edges.left = FlexibleDimension::ZERO; + edges.right = FlexibleDimension::ZERO; }); self } - /// Sets the top and bottom edges to 0 and returns self. + /// Sets the top and bottom edges of alignment to 0 and returns self. #[must_use] pub fn fit_vertically(mut self) -> Self { self.edges.map_mut(|edges| { - edges.top = FlexibleDimension::Dimension(Dimension::default()); - edges.bottom = FlexibleDimension::Dimension(Dimension::default()); + edges.top = FlexibleDimension::ZERO; + edges.bottom = FlexibleDimension::ZERO; }); self } diff --git a/src/widgets/scroll.rs b/src/widgets/scroll.rs index 91cefb2dd..8125d0ddd 100644 --- a/src/widgets/scroll.rs +++ b/src/widgets/scroll.rs @@ -186,7 +186,9 @@ impl Widget for Scroll { let (mut scroll, current_max_scroll) = self.constrain_scroll(); - let control_size = context.graphics.region().size; + let control_size = + Size::::new(available_space.width.max(), available_space.height.max()) + .into_signed(); let max_extents = Size::new( if self.enabled.x { ConstraintLimit::ClippedAfter(UPx::MAX - scroll.x.into_unsigned())