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.
This commit is contained in:
Jonathan Johnson 2023-11-09 10:04:09 -08:00
parent 85928675ab
commit a2e28cb522
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
6 changed files with 163 additions and 48 deletions

View file

@ -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()

View file

@ -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
}
}

View file

@ -117,6 +117,32 @@ impl<T> Dynamic<T> {
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<T>
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.

View file

@ -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<W>(&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<W>(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<W> FromIterator<W> 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 {

View file

@ -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
}

View file

@ -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::<UPx>::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())