Make alignment a widget property

This commit is contained in:
iris-ai committed 2026-09-15 23:12:16 -04:00
1 parent 8220a78d4a
commit d3b0ebf90c
21 files changed
+823 -300

No files matched your search

-43
View File
@@ -1,43 +0,0 @@
use crate::prelude::*;
pub struct Aligned {
pub inner: StrongWidget,
pub align: Align,
}
impl Widget for Aligned {
fn draw(&mut self, painter: &mut Painter) -> Size {
let known = match self.align.tuple() {
(Some(_), Some(_)) => painter
.known_len(&self.inner, Axis::X, UiRegion::FULL)
.zip(painter.known_len(&self.inner, Axis::Y, UiRegion::FULL))
.map(|(x, y)| Size { x, y }),
(Some(_), None) => painter
.known_len(&self.inner, Axis::X, UiRegion::FULL)
.map(|x| Size {
x,
y: Len::LEFTOVER,
}),
(None, Some(_)) => painter
.known_len(&self.inner, Axis::Y, UiRegion::FULL)
.map(|y| Size {
x: Len::LEFTOVER,
y,
}),
(None, None) => Some(Size::LEFTOVER),
};
// Drawn where it may be too big only when the aligned axes are not
// already known, then given its aligned box once its size is known.
let had_size = known.is_some();
let size =
known.unwrap_or_else(|| painter.widget_within(&self.inner, UiRegion::FULL).size());
let region = match self.align.tuple() {
(Some(x), Some(y)) => size.to_uivec2().align(RegionAlign { x, y }),
(Some(x), None) => UiRegion::new(size.x.apply_leftover().align(x), UiSpan::FULL),
(None, Some(y)) => UiRegion::new(UiSpan::FULL, size.y.apply_leftover().align(y)),
(None, None) => UiRegion::FULL,
};
let placed = painter.widget_within(&self.inner, region).size();
if had_size { placed } else { size }
}
}
-2
View File
@@ -1,4 +1,3 @@
mod align;
mod layer;
mod offset;
mod pad;
@@ -6,7 +5,6 @@ mod scroll;
mod span;
mod stack;
pub use align::*;
pub use layer::*;
pub use offset::*;
pub use pad::*;
+1 -1
View File
@@ -8,7 +8,7 @@ pub struct Pad {
impl Widget for Pad {
fn draw(&mut self, painter: &mut Painter) -> Size {
let inner = painter
.widget_within(&self.inner, self.padding.region())
.widget_aligned(&self.inner, self.padding.region(), RegionAlign::NEAR)
.size();
Size {
x: Len {
+23 -15
View File
@@ -14,13 +14,9 @@ impl Widget for Scroll {
let container_len = painter.px_len(self.axis);
// Draw in the whole container only when its scrolling-axis length is
// not already known, then draw it at the scrolled offset.
let (answer_len, measured) = match painter.known_len(&self.inner, self.axis, UiRegion::FULL)
{
Some(len) => (len, None),
None => {
let size = painter.widget_within(&self.inner, UiRegion::FULL).size();
(size.axis(self.axis), Some(size))
}
let answer_len = match painter.known_len(&self.inner, self.axis, UiRegion::FULL) {
Some(len) => len,
None => painter.widget(&self.inner).size().axis(self.axis),
};
let content = answer_len.apply_leftover();
self.container_len = container_len;
@@ -30,21 +26,33 @@ impl Widget for Scroll {
self.amt = self.content_len - self.container_len;
}
self.update_amt();
// Content of a fixed length that fits sits at the start of any box
// it fits in; one scrolled part way sits where it is until the box
// shrinks past what is left of it. Kept to the end, it moves with
// every length.
if content.rel == 0.0 && self.content_len <= self.container_len {
let align = painter.alignment().axis(self.axis);
// Content of a fixed length that fits sits at the start of any box it
// fits in -- but only anchored there. Anywhere else it is a part of
// the room left over, so it moves with every length the box takes and
// the drawing holds for that length alone. One scrolled part way sits
// where it is until the box shrinks past what is left of it. Kept to
// the end, it moves with every length.
if content.rel == 0.0 && self.content_len <= self.container_len && align == AxisAlign::NEG {
painter.holds(self.axis, self.content_len..=f32::INFINITY);
} else if content.rel == 0.0 && !self.snap_end {
let left = self.content_len - self.amt;
painter.holds(self.axis, f32::NEG_INFINITY..=left);
}
let mut region = UiRegion::FULL.offset(Vec2::from_axis(self.axis, -self.amt, 0.0));
// Content shorter than the viewport has room to sit in, and where it
// sits is this widget's own alignment -- the same property that would
// have placed the whole scroll in a box longer than it.
let slack = (self.container_len - self.content_len).max(0.0);
let anchor = slack * align.rel();
let mut region = UiRegion::FULL.offset(Vec2::from_axis(self.axis, anchor - self.amt, 0.0));
region.axis_mut(self.axis).end = region.axis(self.axis).start.offset(self.content_len);
let placed = painter.widget_within(&self.inner, region).size();
measured.unwrap_or_else(|| Size::from_axis(self.axis, answer_len, placed.axis(!self.axis)))
painter.widget_aligned(&self.inner, region, RegionAlign::NEAR);
// What it occupies is its box, on both axes: it clips its content to
// that box, so it can neither take less of one nor honestly ask for
// more. The content's length is what it scrolls through, not what it
// is.
Size::LEFTOVER
}
}
+9 -7
View File
@@ -13,18 +13,20 @@ impl Widget for Stack {
StackSize::Default => None,
StackSize::Child(i) => Some(i),
};
let mut size = Size::default();
// Whichever child sizes the stack decides the box every child gets.
// The stack reports that size, so a child given a longer box would
// draw outside what the stack says it occupies.
let size = match sizing.and_then(|i| self.children.get(i)) {
Some(child) => painter.widget(child).size(),
None => Size::LEFTOVER,
};
let region = painter.box_of(size);
for (i, child) in self.children.iter().enumerate() {
match i {
0 => painter.child_layer(),
_ => painter.next_layer(),
}
let drawn = painter.widget(child);
// Only the child that sizes the stack is read, so the others
// changing size does not redraw it.
if sizing == Some(i) {
size = drawn.size();
}
painter.widget_aligned(child, region, RegionAlign::NEAR);
}
size
}
+14 -5
View File
@@ -12,14 +12,23 @@ widget_trait! {
}
}
fn align(self, align: impl Into<Align>) -> impl WidgetFn<Rsc, Aligned> {
move |state| Aligned {
inner: self.add_strong(state),
align: align.into(),
fn align(self, align: impl Into<Align>) -> impl WidgetIdFn<Rsc, WL::Widget> {
// An axis left out keeps whatever it had, which is centered unless
// something else set it.
let align = align.into();
move |state| {
let id = self.add(state);
let widgets = &mut state.ui_mut().widgets;
for (axis, align) in [(Axis::X, align.x), (Axis::Y, align.y)] {
if let Some(align) = align {
widgets.set_alignment(id, axis, align);
}
}
id
}
}
fn center(self) -> impl WidgetFn<Rsc, Aligned> {
fn center(self) -> impl WidgetIdFn<Rsc, WL::Widget> {
self.align(Align::CENTER)
}