Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de1eb7e406 | ||
|
|
76aaf06c0b |
No files matched your search
@@ -23,6 +23,14 @@ pub struct LayoutLen {
|
|||||||
pub leftover: Weight,
|
pub leftover: Weight,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A bare number is pixels, which is the one length that needs no box to be
|
||||||
|
/// read in.
|
||||||
|
impl<N: UiNum> From<N> for Len {
|
||||||
|
fn from(value: N) -> Self {
|
||||||
|
Len::px(value.to_f32())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<N: UiNum> From<N> for LayoutLen {
|
impl<N: UiNum> From<N> for LayoutLen {
|
||||||
fn from(value: N) -> Self {
|
fn from(value: N) -> Self {
|
||||||
LayoutLen::px(value.to_f32())
|
LayoutLen::px(value.to_f32())
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
Declared, LayerId, LayoutHolds, MaskIdx, MoveIdx, PlaceDesc, RegionAlign, RetainedPrimitive,
|
Bounds, Declared, LayerId, LayoutHolds, MaskIdx, MoveIdx, PlaceDesc, RegionAlign,
|
||||||
Size, TextureHandle, UiRegion, UiVec2, WidgetId,
|
RetainedPrimitive, Size, TextureHandle, UiRegion, UiVec2, WidgetId,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// What is kept of a widget its parent has asked about. `drawn` says whether
|
/// What is kept of a widget its parent has asked about. `drawn` says whether
|
||||||
@@ -58,6 +58,10 @@ pub struct ActiveData {
|
|||||||
/// A change to one moves a box this widget cannot fix by drawing again,
|
/// A change to one moves a box this widget cannot fix by drawing again,
|
||||||
/// and comparing them is what says so.
|
/// and comparing them is what says so.
|
||||||
pub declared: Declared,
|
pub declared: Declared,
|
||||||
|
/// Its bounds, resolved the same way. The answer is held to these where
|
||||||
|
/// the box was not, so a change to one changes what it answers even
|
||||||
|
/// where its declared lengths stand.
|
||||||
|
pub bounds: Bounds,
|
||||||
/// Its alignment when it was last drawn, which a change to the property
|
/// Its alignment when it was last drawn, which a change to the property
|
||||||
/// is found against.
|
/// is found against.
|
||||||
pub own_align: RegionAlign,
|
pub own_align: RegionAlign,
|
||||||
|
|||||||
+39
-1
@@ -1,4 +1,4 @@
|
|||||||
use crate::{Len, Px, REL_SHIFT, fixed::div_toward, fixed::narrow};
|
use crate::{Bound, Len, Outside, Px, REL_SHIFT, fixed::div_toward, fixed::narrow};
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
/// The lengths of a box, in pixels, that one drawing of a widget holds for:
|
/// The lengths of a box, in pixels, that one drawing of a widget holds for:
|
||||||
@@ -39,6 +39,44 @@ impl Len {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Bound {
|
||||||
|
/// Which end of this bound `len` falls outside, and the windows that
|
||||||
|
/// answer holds for. Nothing where it is inside, which is the answer
|
||||||
|
/// wherever there is no bound at all.
|
||||||
|
///
|
||||||
|
/// `len` and this bound are lengths of the same thing, whichever that
|
||||||
|
/// is: a box in window lengths wants the bound resolved, and a length a
|
||||||
|
/// widget declares of its rel base wants it as the rule wrote it. Both
|
||||||
|
/// comparisons are in pixels, so each is a question about this window,
|
||||||
|
/// and the box is decided again on the other side of a crossing.
|
||||||
|
pub fn outside(&self, len: Len, window: Px) -> (Option<Outside>, Holds) {
|
||||||
|
let mut outside = None;
|
||||||
|
let mut holds = Holds::ANY;
|
||||||
|
let mut held = len;
|
||||||
|
if let Some(min) = self.min {
|
||||||
|
let (shorter, kept) = min.longer_than(held, window);
|
||||||
|
holds = holds.and(kept);
|
||||||
|
if shorter {
|
||||||
|
outside = Some(Outside::Shorter);
|
||||||
|
held = min;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(max) = self.max {
|
||||||
|
let (longer, kept) = held.longer_than(max, window);
|
||||||
|
holds = holds.and(kept);
|
||||||
|
if longer {
|
||||||
|
debug_assert!(
|
||||||
|
outside.is_none(),
|
||||||
|
"a floor of {:?} over a cap of {max:?} bounds nothing",
|
||||||
|
self.min,
|
||||||
|
);
|
||||||
|
outside = Some(Outside::Longer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(outside, holds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Holds {
|
impl Holds {
|
||||||
pub const ANY: Self = Self {
|
pub const ANY: Self = Self {
|
||||||
lo: Px::MIN,
|
lo: Px::MIN,
|
||||||
|
|||||||
+32
-10
@@ -1,8 +1,8 @@
|
|||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
use crate::layout_diagnostics::{self as diag, Counter};
|
use crate::layout_diagnostics::{self as diag, Counter};
|
||||||
use crate::{
|
use crate::{
|
||||||
Axis, Declared, Holds, LayoutHolds, LayoutLen, Len, PlaceDesc, Px, PxVec2, RegionAlign, Rel,
|
Axis, Bounds, Declared, Holds, LayoutHolds, LayoutLen, Len, PlaceDesc, Px, PxVec2, RegionAlign,
|
||||||
RenderedText, RetainedPrimitive, Size, StrongWidget, TextAttrs, TextBuffer, TextureHandle,
|
Rel, RenderedText, RetainedPrimitive, Size, StrongWidget, TextAttrs, TextBuffer, TextureHandle,
|
||||||
UiRegion, UiRenderState, UiRsc, UiVec2, Weight, WidgetId, Widgets,
|
UiRegion, UiRenderState, UiRsc, UiVec2, Weight, WidgetId, Widgets,
|
||||||
render::{
|
render::{
|
||||||
GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveInst, PrimitiveKind,
|
GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveInst, PrimitiveKind,
|
||||||
@@ -193,6 +193,7 @@ impl<'a> Painter<'a> {
|
|||||||
region,
|
region,
|
||||||
place,
|
place,
|
||||||
declared,
|
declared,
|
||||||
|
bounds,
|
||||||
holds: ask_holds,
|
holds: ask_holds,
|
||||||
} = self
|
} = self
|
||||||
.placing()
|
.placing()
|
||||||
@@ -221,6 +222,8 @@ impl<'a> Painter<'a> {
|
|||||||
region,
|
region,
|
||||||
placed: place,
|
placed: place,
|
||||||
asked: offer,
|
asked: offer,
|
||||||
|
declared,
|
||||||
|
bounds,
|
||||||
ask_holds,
|
ask_holds,
|
||||||
re_asked,
|
re_asked,
|
||||||
},
|
},
|
||||||
@@ -689,7 +692,14 @@ pub(super) struct Ask {
|
|||||||
/// The place the ask came to, which a rule of the widget's own can take
|
/// The place the ask came to, which a rule of the widget's own can take
|
||||||
/// past the box its parent offered.
|
/// past the box its parent offered.
|
||||||
pub place: PlaceDesc,
|
pub place: PlaceDesc,
|
||||||
|
/// What the widget's box is on each axis where something says so
|
||||||
|
/// outright: its rule or its hint, or a bound of its own that the box it
|
||||||
|
/// was offered falls outside -- a bound that binds is a declaration, and
|
||||||
|
/// the same one the widget answers with.
|
||||||
pub declared: Declared,
|
pub declared: Declared,
|
||||||
|
/// Its bounds, resolved against the rel base its rules were resolved
|
||||||
|
/// against, for the answer to be held to where the box was not.
|
||||||
|
pub bounds: Bounds,
|
||||||
/// What the ask itself holds for, kept on the widget asked about: a rule
|
/// What the ask itself holds for, kept on the widget asked about: a rule
|
||||||
/// compared against the offer in pixels holds only for the windows on its
|
/// compared against the offer in pixels holds only for the windows on its
|
||||||
/// side of the crossing, and that range reaches whoever asked through the
|
/// side of the crossing, and that range reaches whoever asked through the
|
||||||
@@ -714,21 +724,32 @@ impl Placing {
|
|||||||
mut place: PlaceDesc,
|
mut place: PlaceDesc,
|
||||||
) -> Ask {
|
) -> Ask {
|
||||||
let align = widgets.alignment(id);
|
let align = widgets.alignment(id);
|
||||||
|
let rules = widgets.size_rules(id);
|
||||||
let mut holds = LayoutHolds::ANY;
|
let mut holds = LayoutHolds::ANY;
|
||||||
// A share fills what the pixels and fraction beside it leave of the
|
let declared = widgets.declared_lens(id);
|
||||||
// box and overflows where they are longer, which is the rule a span
|
let mut bounds = Bounds::ANY;
|
||||||
// follows with one child. Only the overflow is a box of the child's
|
|
||||||
// own: a share that fits is the box it was given, which is what this
|
|
||||||
// place already says.
|
|
||||||
for axis in Axis::BOTH {
|
for axis in Axis::BOTH {
|
||||||
let (len, kept) =
|
let base = place.base(axis, self.rel_base);
|
||||||
|
// A share fills what the pixels and fraction beside it leave of
|
||||||
|
// the box and overflows where they are longer, which is the rule
|
||||||
|
// a span follows with one child. Only the overflow is a box of
|
||||||
|
// the widget's own: a share that fits is the box it was given,
|
||||||
|
// which is what this place already says.
|
||||||
|
let (share, kept) =
|
||||||
self.share_past_the_offer(widgets, window[axis], id, place, align, axis);
|
self.share_past_the_offer(widgets, window[axis], id, place, align, axis);
|
||||||
holds[axis].window = holds[axis].window.and(kept);
|
holds[axis].window = holds[axis].window.and(kept);
|
||||||
if let Some(len) = len {
|
if let Some(len) = share {
|
||||||
place[axis] = len.as_desc().fills();
|
place[axis] = len.as_desc().fills();
|
||||||
}
|
}
|
||||||
|
// A bound holds what the widget answers, not the box it is asked
|
||||||
|
// in: the box it is given is whoever asked's to decide, and a
|
||||||
|
// rule that read it would be decided again by every path that
|
||||||
|
// hands the widget a box -- including the ones that never ask it
|
||||||
|
// anything. Resolved here because only the ask knows the rel base
|
||||||
|
// a fraction in it is of. `MaxSize` is the box version, and it is
|
||||||
|
// a widget because a widget is drawn again when its box changes.
|
||||||
|
bounds[axis] = rules[axis].bound().within_len(base);
|
||||||
}
|
}
|
||||||
let declared = widgets.declared_lens(id);
|
|
||||||
let (rel_base, region) =
|
let (rel_base, region) =
|
||||||
place.rel_base_and_region(self.region, self.rel_base, declared, align);
|
place.rel_base_and_region(self.region, self.rel_base, declared, align);
|
||||||
Ask {
|
Ask {
|
||||||
@@ -736,6 +757,7 @@ impl Placing {
|
|||||||
region,
|
region,
|
||||||
place,
|
place,
|
||||||
declared,
|
declared,
|
||||||
|
bounds,
|
||||||
holds,
|
holds,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+78
-29
@@ -1,8 +1,8 @@
|
|||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
use crate::layout_diagnostics::{self as diag, Counter, ReuseOutcome, TimerKind};
|
use crate::layout_diagnostics::{self as diag, Counter, ReuseOutcome, TimerKind};
|
||||||
use crate::{
|
use crate::{
|
||||||
ActiveData, Answer, Axis, Declared, DrawLayers, IdLike, LayoutHolds, LayoutLen, Len, MaskIdx,
|
ActiveData, Answer, Axis, Bounds, Declared, DrawLayers, IdLike, LayoutHolds, LayoutLen, Len,
|
||||||
MoveIdx, Moves, Painter, PixelRegion, PlaceDesc, PxVec2, Rel, Size, StrongWidget, UiRegion,
|
MaskIdx, MoveIdx, Moves, Painter, PixelRegion, PlaceDesc, PxVec2, Size, StrongWidget, UiRegion,
|
||||||
UiRsc, UiSpan, UiVec2, Weight, WidgetId, Widgets,
|
UiRsc, UiSpan, UiVec2, Weight, WidgetId, Widgets,
|
||||||
ui::painter::Ask,
|
ui::painter::Ask,
|
||||||
util::{HashMap, Vec2},
|
util::{HashMap, Vec2},
|
||||||
@@ -30,6 +30,10 @@ pub(super) struct DrawInfo {
|
|||||||
/// the answer somewhere else.
|
/// the answer somewhere else.
|
||||||
pub placed: PlaceDesc,
|
pub placed: PlaceDesc,
|
||||||
pub asked: PlaceDesc,
|
pub asked: PlaceDesc,
|
||||||
|
/// What the ask made of the widget's own rules. See [`Ask::declared`]
|
||||||
|
/// and [`Ask::bounds`].
|
||||||
|
pub declared: Declared,
|
||||||
|
pub bounds: Bounds,
|
||||||
/// What the ask that gave it those two holds for. See [`Ask::holds`].
|
/// What the ask that gave it those two holds for. See [`Ask::holds`].
|
||||||
pub ask_holds: LayoutHolds,
|
pub ask_holds: LayoutHolds,
|
||||||
/// Whether the parent already asked about this widget in this draw.
|
/// Whether the parent already asked about this widget in this draw.
|
||||||
@@ -158,6 +162,8 @@ impl UiRenderState {
|
|||||||
region: ask.region,
|
region: ask.region,
|
||||||
placed: ask.place,
|
placed: ask.place,
|
||||||
asked: PlaceDesc::WHOLE,
|
asked: PlaceDesc::WHOLE,
|
||||||
|
declared: ask.declared,
|
||||||
|
bounds: ask.bounds,
|
||||||
ask_holds: ask.holds,
|
ask_holds: ask.holds,
|
||||||
re_asked: false,
|
re_asked: false,
|
||||||
}
|
}
|
||||||
@@ -236,7 +242,7 @@ impl UiRenderState {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
let align = rsc.widgets().alignment(id);
|
let align = rsc.widgets().alignment(id);
|
||||||
let declared = rsc.widgets().declared_lens(id);
|
let declared = info.declared;
|
||||||
// Nothing this widget measured can be dirty while it draws: layout is
|
// Nothing this widget measured can be dirty while it draws: layout is
|
||||||
// one bottom-up walk, so anything deeper has settled or deferred to
|
// one bottom-up walk, so anything deeper has settled or deferred to
|
||||||
// its own parent, and a deferred one leaves that parent marked.
|
// its own parent, and a deferred one leaves that parent marked.
|
||||||
@@ -392,9 +398,9 @@ impl UiRenderState {
|
|||||||
// A rule wins on the axis it names, and the draw answers the rest.
|
// A rule wins on the axis it names, and the draw answers the rest.
|
||||||
// Applied here so it is one place rather than every widget that could
|
// Applied here so it is one place rather than every widget that could
|
||||||
// carry one, and so the widget under a rule never learns of it. The
|
// carry one, and so the widget under a rule never learns of it. The
|
||||||
// rel base is the answer where the rule gave a length outright: it was
|
// rel base is the answer wherever the ask declared a length: it was
|
||||||
// resolved into the rel base when the child was asked, and resolving it
|
// resolved into the rel base when the widget was asked, and resolving
|
||||||
// again here would take the fraction of a fraction.
|
// it again here would take the fraction of a fraction.
|
||||||
let rules = rsc.widgets().size_rules(id);
|
let rules = rsc.widgets().size_rules(id);
|
||||||
let ruled = |axis: Axis, reported: LayoutLen| match rules[axis].exact() {
|
let ruled = |axis: Axis, reported: LayoutLen| match rules[axis].exact() {
|
||||||
None => reported,
|
None => reported,
|
||||||
@@ -405,10 +411,37 @@ impl UiRenderState {
|
|||||||
},
|
},
|
||||||
Some(len) => len.within_len(info.rel_base[axis]),
|
Some(len) => len.within_len(info.rel_base[axis]),
|
||||||
};
|
};
|
||||||
let size = Size {
|
let mut size = Size {
|
||||||
x: ruled(Axis::X, size.x),
|
x: ruled(Axis::X, size.x),
|
||||||
y: ruled(Axis::Y, size.y),
|
y: ruled(Axis::Y, size.y),
|
||||||
};
|
};
|
||||||
|
// A bound is a promise about the length as well as about the box: a
|
||||||
|
// widget that drew past the box it was given -- a text too tall for
|
||||||
|
// it, an image at its own size under a cap -- is still held to what
|
||||||
|
// its rule allows.
|
||||||
|
//
|
||||||
|
// Held here rather than taken from the box, even where the bound
|
||||||
|
// decided that box. What a widget answers is its own, and a bound
|
||||||
|
// that replaced the answer would make a share into a fixed length
|
||||||
|
// the moment a box was long enough -- which is a length the span
|
||||||
|
// dividing that box decided from this answer, so the two would
|
||||||
|
// choose each other. A share is left alone here for the same reason:
|
||||||
|
// it is a length only to whoever divides one, and the box that
|
||||||
|
// divider gives is a box this widget is asked in, where the bound is
|
||||||
|
// applied to it.
|
||||||
|
let mut bounded = LayoutHolds::ANY;
|
||||||
|
for axis in Axis::BOTH {
|
||||||
|
let answer = size[axis];
|
||||||
|
if answer.leftover != Weight::ZERO {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let (outside, kept) =
|
||||||
|
info.bounds[axis].outside(answer.without_leftover(), window[axis]);
|
||||||
|
bounded[axis].window = kept;
|
||||||
|
if let Some(outside) = outside {
|
||||||
|
size[axis] = info.bounds[axis].at(outside).into();
|
||||||
|
}
|
||||||
|
}
|
||||||
// A widget that clipped its contents to its box drew nothing outside
|
// A widget that clipped its contents to its box drew nothing outside
|
||||||
// it, so reporting more than the box asks to be placed at a length it
|
// it, so reporting more than the box asks to be placed at a length it
|
||||||
// does not occupy -- and its parent would place the part it cut off.
|
// does not occupy -- and its parent would place the part it cut off.
|
||||||
@@ -439,11 +472,12 @@ impl UiRenderState {
|
|||||||
// A rule that is a fraction of the rel base is answered with the
|
// A rule that is a fraction of the rel base is answered with the
|
||||||
// rel base's own length, so the answer is that rel base's and not just
|
// rel base's own length, so the answer is that rel base's and not just
|
||||||
// that many pixels of this window -- the same pin a widget that read
|
// that many pixels of this window -- the same pin a widget that read
|
||||||
// its rel base took for its drawing.
|
// its rel base took for its drawing. A bound counts: which side of it
|
||||||
let mut own_holds = own;
|
// the box fell was decided against this rel base, and the same box of
|
||||||
|
// a different one can fall on the other.
|
||||||
|
let mut own_holds = own.and(bounded);
|
||||||
for axis in Axis::BOTH {
|
for axis in Axis::BOTH {
|
||||||
let fraction = rules[axis].exact().is_some_and(|len| len.rel != Rel::ZERO);
|
if rules[axis].has_fraction() {
|
||||||
if fraction {
|
|
||||||
own_holds[axis].rel_base = Some(info.rel_base[axis]);
|
own_holds[axis].rel_base = Some(info.rel_base[axis]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -475,6 +509,8 @@ impl UiRenderState {
|
|||||||
region: UiRegion::FULL,
|
region: UiRegion::FULL,
|
||||||
placed: PlaceDesc::WHOLE,
|
placed: PlaceDesc::WHOLE,
|
||||||
asked: PlaceDesc::WHOLE,
|
asked: PlaceDesc::WHOLE,
|
||||||
|
declared: Declared::NONE,
|
||||||
|
bounds: Bounds::ANY,
|
||||||
ask_holds: LayoutHolds::ANY,
|
ask_holds: LayoutHolds::ANY,
|
||||||
re_asked: false,
|
re_asked: false,
|
||||||
},
|
},
|
||||||
@@ -503,7 +539,8 @@ impl UiRenderState {
|
|||||||
primitives,
|
primitives,
|
||||||
mask_region,
|
mask_region,
|
||||||
children,
|
children,
|
||||||
declared: rsc.widgets().declared_lens(id),
|
declared: info.declared,
|
||||||
|
bounds: info.bounds,
|
||||||
own_align: rsc.widgets().alignment(id),
|
own_align: rsc.widgets().alignment(id),
|
||||||
move_idx,
|
move_idx,
|
||||||
parent_move: info.parent_move,
|
parent_move: info.parent_move,
|
||||||
@@ -673,6 +710,9 @@ impl UiRenderState {
|
|||||||
let active = self.active.get_mut(&id).unwrap();
|
let active = self.active.get_mut(&id).unwrap();
|
||||||
active.rel_base = info.rel_base;
|
active.rel_base = info.rel_base;
|
||||||
active.placed = info.placed;
|
active.placed = info.placed;
|
||||||
|
// What the ask made of its rules, which a re-place decides again.
|
||||||
|
active.declared = info.declared;
|
||||||
|
active.bounds = info.bounds;
|
||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
{
|
{
|
||||||
let (counter, outcome) = match (moved, is_region_node) {
|
let (counter, outcome) = match (moved, is_region_node) {
|
||||||
@@ -720,6 +760,8 @@ impl UiRenderState {
|
|||||||
region,
|
region,
|
||||||
placed: place,
|
placed: place,
|
||||||
asked: active.asked,
|
asked: active.asked,
|
||||||
|
declared: active.declared,
|
||||||
|
bounds: active.bounds,
|
||||||
// Placing decides no box: this is the one the ask already gave.
|
// Placing decides no box: this is the one the ask already gave.
|
||||||
ask_holds: LayoutHolds::ANY,
|
ask_holds: LayoutHolds::ANY,
|
||||||
re_asked: active.re_asked,
|
re_asked: active.re_asked,
|
||||||
@@ -874,6 +916,7 @@ impl UiRenderState {
|
|||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
move_idx: info.parent_move,
|
move_idx: info.parent_move,
|
||||||
declared: Declared::NONE,
|
declared: Declared::NONE,
|
||||||
|
bounds: Bounds::ANY,
|
||||||
own_align: rsc.widgets().alignment(id),
|
own_align: rsc.widgets().alignment(id),
|
||||||
parent_move: info.parent_move,
|
parent_move: info.parent_move,
|
||||||
mask: info.mask,
|
mask: info.mask,
|
||||||
@@ -1058,12 +1101,22 @@ impl UiRenderState {
|
|||||||
let Some(active) = self.active.get(&id) else {
|
let Some(active) = self.active.get(&id) else {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
// Its parent resolved its declared lengths into its box and decided
|
// Asked where its parent asked it, which is what says whether the
|
||||||
// whether to draw it at all, so a change to either is the parent's
|
// question is still this widget's own: its parent resolved its
|
||||||
// to draw -- with the mark left on, so the parent draws it rather
|
// declared lengths into its box -- a bound of its own that the box
|
||||||
// than keeping it. So is a widget the parent asked twice: its
|
// falls outside is one of them -- and decided whether to draw it at
|
||||||
// layout rests on an answer this widget cannot give again alone.
|
// all, so a change to either is the parent's to draw, with the mark
|
||||||
let declared_changed = rsc.widgets().declared_lens(id) != active.declared;
|
// left on so the parent draws it rather than keeping it. So is a
|
||||||
|
// widget the parent asked twice: its layout rests on an answer this
|
||||||
|
// widget cannot give again alone. The root's parent is the window,
|
||||||
|
// which no draw made and no answer can move.
|
||||||
|
let at = match active.parent {
|
||||||
|
Some(parent) => self.placing_of(parent, self.active[&parent].region),
|
||||||
|
None => Placing::WINDOW,
|
||||||
|
};
|
||||||
|
let ask = at.ask(rsc.widgets(), self.output_size, id, active.asked);
|
||||||
|
let active = &self.active[&id];
|
||||||
|
let declared_changed = ask.declared != active.declared;
|
||||||
let alignment_changed = rsc.widgets().alignment(id) != active.own_align;
|
let alignment_changed = rsc.widgets().alignment(id) != active.own_align;
|
||||||
if let Some(parent) = active.parent
|
if let Some(parent) = active.parent
|
||||||
&& (declared_changed
|
&& (declared_changed
|
||||||
@@ -1083,17 +1136,11 @@ impl UiRenderState {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
let (was_answer, was_holds, was_place) = (active.answer, active.holds, active.placed);
|
let (was_answer, was_holds, was_place) = (active.answer, active.holds, active.placed);
|
||||||
// The question its parent asked, asked again: the same place of the
|
// The place the ask above came to: the same place of the box the
|
||||||
// box the parent was asked in, which is the box the parent's own
|
// parent was asked in, which is the box the parent's own draw ran in
|
||||||
// draw ran in and what its children's parts are of. Where the
|
// and what its children's parts are of. Where the parent's answer put
|
||||||
// parent's answer put its own drawing is not a question anybody
|
// its own drawing is not a question anybody asked, and nothing is
|
||||||
// asked, and nothing is asked in it here either. The root's parent is
|
// asked in it here either.
|
||||||
// the window, which no draw made and no answer can move.
|
|
||||||
let at = match active.parent {
|
|
||||||
Some(parent) => self.placing_of(parent, self.active[&parent].region),
|
|
||||||
None => Placing::WINDOW,
|
|
||||||
};
|
|
||||||
let ask = at.ask(rsc.widgets(), self.output_size, id, active.asked);
|
|
||||||
let (rel_base, region) = (ask.rel_base, ask.region);
|
let (rel_base, region) = (ask.rel_base, ask.region);
|
||||||
let info = DrawInfo {
|
let info = DrawInfo {
|
||||||
layer: active.layer,
|
layer: active.layer,
|
||||||
@@ -1106,6 +1153,8 @@ impl UiRenderState {
|
|||||||
region,
|
region,
|
||||||
placed: ask.place,
|
placed: ask.place,
|
||||||
asked: active.asked,
|
asked: active.asked,
|
||||||
|
declared: ask.declared,
|
||||||
|
bounds: ask.bounds,
|
||||||
ask_holds: ask.holds,
|
ask_holds: ask.holds,
|
||||||
re_asked: false,
|
re_asked: false,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::util::impl_axis_index;
|
use crate::util::impl_axis_index;
|
||||||
use crate::{Axis, LayoutLen, Len};
|
use crate::{Axis, LayoutLen, Len, Rel};
|
||||||
|
|
||||||
/// What a widget's length on one axis is, as a rule its parent applies where
|
/// What a widget's length on one axis is, as a rule its parent applies where
|
||||||
/// it draws it rather than an answer the widget gives about itself.
|
/// it draws it rather than an answer the widget gives about itself.
|
||||||
@@ -9,6 +9,10 @@ use crate::{Axis, LayoutLen, Len};
|
|||||||
/// with no rule. That is what lets a span divide its space around a length
|
/// with no rule. That is what lets a span divide its space around a length
|
||||||
/// nobody has drawn yet, and it is why a rule lives beside the widget rather
|
/// nobody has drawn yet, and it is why a rule lives beside the widget rather
|
||||||
/// than inside it -- the widget under the rule never has to know about it.
|
/// than inside it -- the widget under the rule never has to know about it.
|
||||||
|
///
|
||||||
|
/// A rule gives a length or bounds one, never both: a share that is also
|
||||||
|
/// capped wants two widgets, one taking the share and one capping what is
|
||||||
|
/// inside it.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||||
pub enum SizeRule {
|
pub enum SizeRule {
|
||||||
/// Whatever the widget reports from drawing.
|
/// Whatever the widget reports from drawing.
|
||||||
@@ -16,9 +20,72 @@ pub enum SizeRule {
|
|||||||
Free,
|
Free,
|
||||||
/// This length, whatever the widget reports.
|
/// This length, whatever the widget reports.
|
||||||
Exact(LayoutLen),
|
Exact(LayoutLen),
|
||||||
|
/// At least this long, and otherwise whatever the box gives it.
|
||||||
|
Min(Len),
|
||||||
|
/// At most this long.
|
||||||
|
Max(Len),
|
||||||
|
/// Between the two.
|
||||||
|
Clamp { min: Len, max: Len },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SizeRule {
|
impl SizeRule {
|
||||||
|
/// What this rule allows the length to be where it does not give one
|
||||||
|
/// outright.
|
||||||
|
pub fn bound(&self) -> Bound {
|
||||||
|
match *self {
|
||||||
|
Self::Free | Self::Exact(_) => Bound::ANY,
|
||||||
|
Self::Min(min) => Bound {
|
||||||
|
min: Some(min),
|
||||||
|
max: None,
|
||||||
|
},
|
||||||
|
Self::Max(max) => Bound {
|
||||||
|
min: None,
|
||||||
|
max: Some(max),
|
||||||
|
},
|
||||||
|
Self::Clamp { min, max } => Bound {
|
||||||
|
min: Some(min),
|
||||||
|
max: Some(max),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether what this rule says is a fraction of the rel base, so that
|
||||||
|
/// the same rule against a different one is a different length.
|
||||||
|
pub fn has_fraction(&self) -> bool {
|
||||||
|
let bound = self.bound();
|
||||||
|
self.exact().is_some_and(|len| len.rel != Rel::ZERO)
|
||||||
|
|| [bound.min, bound.max]
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.any(|len| len.rel != Rel::ZERO)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This rule with a floor under it, which is the whole of it where there
|
||||||
|
/// was no rule.
|
||||||
|
pub fn at_least(&self, min: Len) -> Self {
|
||||||
|
match *self {
|
||||||
|
Self::Free | Self::Min(_) => Self::Min(min),
|
||||||
|
Self::Max(max) | Self::Clamp { max, .. } => Self::Clamp { min, max },
|
||||||
|
Self::Exact(len) => {
|
||||||
|
debug_assert!(false, "{len:?} is a length, so bounding it says nothing");
|
||||||
|
Self::Min(min)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This rule with a cap over it, which is the whole of it where there was
|
||||||
|
/// no rule.
|
||||||
|
pub fn at_most(&self, max: Len) -> Self {
|
||||||
|
match *self {
|
||||||
|
Self::Free | Self::Max(_) => Self::Max(max),
|
||||||
|
Self::Min(min) | Self::Clamp { min, .. } => Self::Clamp { min, max },
|
||||||
|
Self::Exact(len) => {
|
||||||
|
debug_assert!(false, "{len:?} is a length, so bounding it says nothing");
|
||||||
|
Self::Max(max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The length this rule gives without the widget being drawn, if it can
|
/// The length this rule gives without the widget being drawn, if it can
|
||||||
/// give one.
|
/// give one.
|
||||||
pub fn declared(&self) -> Option<Len> {
|
pub fn declared(&self) -> Option<Len> {
|
||||||
@@ -32,12 +99,84 @@ impl SizeRule {
|
|||||||
/// that give a box directly.
|
/// that give a box directly.
|
||||||
pub fn exact(&self) -> Option<LayoutLen> {
|
pub fn exact(&self) -> Option<LayoutLen> {
|
||||||
match self {
|
match self {
|
||||||
Self::Free => None,
|
|
||||||
Self::Exact(len) => Some(*len),
|
Self::Exact(len) => Some(*len),
|
||||||
|
Self::Free | Self::Min(_) | Self::Max(_) | Self::Clamp { .. } => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// What a rule allows a length to be where it does not give one outright: a
|
||||||
|
/// floor, a cap, or both. Each is a length of the rel base the widget is
|
||||||
|
/// asked with, which is the base a declared length is a fraction of too, and
|
||||||
|
/// a bound that binds is a declaration -- the box comes to what it says.
|
||||||
|
///
|
||||||
|
/// A bound is a [`Len`] and never a share. Which of a fixed and a relative
|
||||||
|
/// child is longer, asked at the length the cap is itself deciding, admits
|
||||||
|
/// several self-sizing fixed points, so a cap containing `leftover` has no
|
||||||
|
/// one answer: see `docs/LAYOUT.md` under the failed hypotheses.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||||
|
pub struct Bound {
|
||||||
|
pub min: Option<Len>,
|
||||||
|
pub max: Option<Len>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Which end of a bound a length fell outside.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum Outside {
|
||||||
|
Shorter,
|
||||||
|
Longer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bound {
|
||||||
|
/// Every length.
|
||||||
|
pub const ANY: Self = Self {
|
||||||
|
min: None,
|
||||||
|
max: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The end [`Outside`] names, which is the length a widget outside it
|
||||||
|
/// gets instead of its own.
|
||||||
|
pub fn at(&self, outside: Outside) -> Len {
|
||||||
|
let end = match outside {
|
||||||
|
Outside::Shorter => self.min,
|
||||||
|
Outside::Longer => self.max,
|
||||||
|
};
|
||||||
|
end.expect("an end nothing is outside of")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This bound as lengths of the window, from lengths of a rel base that
|
||||||
|
/// long.
|
||||||
|
pub fn within_len(&self, len: Len) -> Self {
|
||||||
|
Self {
|
||||||
|
min: self.min.map(|min| min.within_len(len)),
|
||||||
|
max: self.max.map(|max| max.within_len(len)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One bound per axis, as [`SizeRules`] is one rule per axis.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||||
|
pub struct Bounds {
|
||||||
|
pub x: Bound,
|
||||||
|
pub y: Bound,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bounds {
|
||||||
|
pub const ANY: Self = Self {
|
||||||
|
x: Bound::ANY,
|
||||||
|
y: Bound::ANY,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn from_axes(f: impl Fn(Axis) -> Bound) -> Self {
|
||||||
|
Self {
|
||||||
|
x: f(Axis::X),
|
||||||
|
y: f(Axis::Y),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_axis_index!(Bounds => Bound);
|
||||||
|
|
||||||
impl From<LayoutLen> for SizeRule {
|
impl From<LayoutLen> for SizeRule {
|
||||||
fn from(len: LayoutLen) -> Self {
|
fn from(len: LayoutLen) -> Self {
|
||||||
Self::Exact(len)
|
Self::Exact(len)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use std::sync::mpsc::{Receiver, Sender, channel};
|
use std::sync::mpsc::{Receiver, Sender, channel};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Axis, AxisAlign, IdLike, RegionAlign, SizeRule, SizeRules, StrongWidget, WeakWidget, Widget,
|
Axis, AxisAlign, IdLike, Len, RegionAlign, SizeRule, SizeRules, StrongWidget, WeakWidget,
|
||||||
WidgetData, WidgetId,
|
Widget, WidgetData, WidgetId,
|
||||||
util::{DynBorrower, HashSet, SlotVec, forget_mut, to_mut},
|
util::{DynBorrower, HashSet, SlotVec, forget_mut, to_mut},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -145,6 +145,22 @@ impl Widgets {
|
|||||||
self.needs_redraw.insert(id);
|
self.needs_redraw.insert(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Puts a floor under this widget's length on one axis, keeping a cap it
|
||||||
|
/// already had. See [`SizeRule::at_least`].
|
||||||
|
pub fn set_min_len(&mut self, id: impl IdLike, axis: Axis, min: Len) {
|
||||||
|
let id = id.id();
|
||||||
|
let rule = self.size_rules(id)[axis].at_least(min);
|
||||||
|
self.set_size_rule(id, axis, rule);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Puts a cap over it, keeping a floor it already had. See
|
||||||
|
/// [`SizeRule::at_most`].
|
||||||
|
pub fn set_max_len(&mut self, id: impl IdLike, axis: Axis, max: Len) {
|
||||||
|
let id = id.id();
|
||||||
|
let rule = self.size_rules(id)[axis].at_most(max);
|
||||||
|
self.set_size_rule(id, axis, rule);
|
||||||
|
}
|
||||||
|
|
||||||
/// Where this widget sits in a box longer than the length it takes.
|
/// Where this widget sits in a box longer than the length it takes.
|
||||||
pub fn alignment(&self, id: impl IdLike) -> RegionAlign {
|
pub fn alignment(&self, id: impl IdLike) -> RegionAlign {
|
||||||
self.data(id).unwrap().align
|
self.data(id).unwrap().align
|
||||||
|
|||||||
+10
-3
@@ -18,6 +18,7 @@ struct Input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct InputFn {
|
struct InputFn {
|
||||||
|
attrs: Vec<Attribute>,
|
||||||
sig: Signature,
|
sig: Signature,
|
||||||
body: Block,
|
body: Block,
|
||||||
}
|
}
|
||||||
@@ -32,9 +33,10 @@ impl Parse for Input {
|
|||||||
input.parse::<Token![;]>()?;
|
input.parse::<Token![;]>()?;
|
||||||
let mut fns = Vec::new();
|
let mut fns = Vec::new();
|
||||||
while !input.is_empty() {
|
while !input.is_empty() {
|
||||||
|
let attrs = input.call(Attribute::parse_outer)?;
|
||||||
let sig = input.parse()?;
|
let sig = input.parse()?;
|
||||||
let body = input.parse()?;
|
let body = input.parse()?;
|
||||||
fns.push(InputFn { sig, body })
|
fns.push(InputFn { attrs, sig, body })
|
||||||
}
|
}
|
||||||
if !input.is_empty() {
|
if !input.is_empty() {
|
||||||
input.error("function expected");
|
input.error("function expected");
|
||||||
@@ -59,10 +61,15 @@ pub fn widget_trait(input: TokenStream) -> TokenStream {
|
|||||||
fns,
|
fns,
|
||||||
} = parse_macro_input!(input as Input);
|
} = parse_macro_input!(input as Input);
|
||||||
|
|
||||||
let sigs: Vec<_> = fns.iter().map(|f| f.sig.clone()).collect();
|
// What a method says about itself belongs on the trait, where a reader
|
||||||
|
// looks it up; the implementation is the same text and says it again.
|
||||||
|
let sigs: Vec<_> = fns
|
||||||
|
.iter()
|
||||||
|
.map(|InputFn { attrs, sig, .. }| quote! { #(#attrs)* #sig })
|
||||||
|
.collect();
|
||||||
let impls: Vec<_> = fns
|
let impls: Vec<_> = fns
|
||||||
.iter()
|
.iter()
|
||||||
.map(|InputFn { sig, body }| quote! { #sig #body })
|
.map(|InputFn { attrs, sig, body }| quote! { #(#attrs)* #sig #body })
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let Some(GenericParam::Type(state)) = generics.params.first() else {
|
let Some(GenericParam::Type(state)) = generics.params.first() else {
|
||||||
|
|||||||
+41
-7
@@ -644,11 +644,45 @@ impl Sow<'_> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn len(&mut self) -> Option<LayoutLen> {
|
fn len(&mut self) -> LayoutLen {
|
||||||
match self.rng.below(4) {
|
LayoutLen::px(20.0 + self.rng.below(180) as f32)
|
||||||
0 => Some(LayoutLen::px(20.0 + self.rng.below(180) as f32)),
|
}
|
||||||
1 => Some(LayoutLen::LEFTOVER),
|
|
||||||
_ => None,
|
/// A length of a box rather than a length of the window, which is what a
|
||||||
|
/// bound is.
|
||||||
|
///
|
||||||
|
/// Pixels only, for now. A fraction in a bound is resolved against the rel
|
||||||
|
/// base the widget was asked with, and `place_at` hands a parent a
|
||||||
|
/// retained answer without checking that the answer still holds for the
|
||||||
|
/// rel base this place gives -- so a fraction resolved against one rel
|
||||||
|
/// base survives into another. Seeds 4 (shuffle-all-but-first) and 196
|
||||||
|
/// (resize-size) at depth 5 are where that showed; both pass with pixels.
|
||||||
|
/// The hole is older than bounds -- an `Exact` rule that is a fraction
|
||||||
|
/// can reach it too -- and closing it is a check at the re-place site.
|
||||||
|
fn bound(&mut self) -> Len {
|
||||||
|
Len::px(20.0 + self.rng.below(180) as f32)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rule(&mut self) -> SizeRule {
|
||||||
|
match self.rng.below(8) {
|
||||||
|
0 | 1 => self.len().into(),
|
||||||
|
2 => LayoutLen::LEFTOVER.into(),
|
||||||
|
3 => SizeRule::Min(self.bound()),
|
||||||
|
4 => SizeRule::Max(self.bound()),
|
||||||
|
// Both in pixels, so one can be put under the other: a floor and
|
||||||
|
// a cap that change sides with the window bound nothing, which
|
||||||
|
// is a caller's bug rather than a tree to grow.
|
||||||
|
5 => {
|
||||||
|
let (a, b) = (
|
||||||
|
Px::from_f32(20.0 + self.rng.below(180) as f32),
|
||||||
|
Px::from_f32(20.0 + self.rng.below(180) as f32),
|
||||||
|
);
|
||||||
|
SizeRule::Clamp {
|
||||||
|
min: Len::px(a.min(b).to_f32()),
|
||||||
|
max: Len::px(a.max(b).to_f32()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => SizeRule::Free,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -674,8 +708,8 @@ impl Sow<'_> {
|
|||||||
fn sized(&mut self, inner: &mut Plan) {
|
fn sized(&mut self, inner: &mut Plan) {
|
||||||
let take = self.rng.chance();
|
let take = self.rng.chance();
|
||||||
let lens = SizeRules {
|
let lens = SizeRules {
|
||||||
x: self.len().into(),
|
x: self.rule(),
|
||||||
y: self.len().into(),
|
y: self.rule(),
|
||||||
};
|
};
|
||||||
if !take || inner.size.is_some() {
|
if !take || inner.size.is_some() {
|
||||||
return;
|
return;
|
||||||
|
|||||||
+1
-1
@@ -17,7 +17,7 @@ impl Widget for Image {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Image {
|
impl Image {
|
||||||
/// One texture already uploaded, for a caller holding its handle: [`image`]
|
/// One texture already uploaded, for a caller holding its handle: [`image()`]
|
||||||
/// uploads what it is given, and several widgets showing one picture want
|
/// uploads what it is given, and several widgets showing one picture want
|
||||||
/// one upload and one slot between them.
|
/// one upload and one slot between them.
|
||||||
pub fn new(handle: TextureHandle) -> Self {
|
pub fn new(handle: TextureHandle) -> Self {
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
/// Asks its child in the shorter of a cap and the box this widget was given,
|
||||||
|
/// and answers what the child used, held to the same cap.
|
||||||
|
///
|
||||||
|
/// A cap on the box is a widget rather than a [`SizeRule`] because a box is
|
||||||
|
/// whoever asked's to decide: a rule that read the box it was given would be
|
||||||
|
/// decided again by every path that hands a widget one, including the ones
|
||||||
|
/// that re-place a drawing without asking it anything, and the decision would
|
||||||
|
/// then depend on which path arrived last. A widget is drawn again whenever
|
||||||
|
/// its own box changes, so the comparison is made where the answer can be
|
||||||
|
/// kept -- `longer_than` narrows the windows this drawing holds for, and
|
||||||
|
/// `holds` says the box lengths.
|
||||||
|
///
|
||||||
|
/// The box is what a text wraps at and what a scroll takes its viewport from,
|
||||||
|
/// which is why capping the answer alone is not the same thing.
|
||||||
|
pub struct MaxSize {
|
||||||
|
pub inner: StrongWidget,
|
||||||
|
pub x: Option<Len>,
|
||||||
|
pub y: Option<Len>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MaxSize {
|
||||||
|
fn max(&self, axis: Axis) -> Option<Len> {
|
||||||
|
match axis {
|
||||||
|
Axis::X => self.x,
|
||||||
|
Axis::Y => self.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for MaxSize {
|
||||||
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
|
let align = painter.alignment();
|
||||||
|
let mut region = UiRegion::FULL;
|
||||||
|
for axis in Axis::BOTH {
|
||||||
|
let Some(max) = self.max(axis) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let own = painter.region_len(axis);
|
||||||
|
if painter.longer_than(own, max, axis) {
|
||||||
|
region[axis] = max.align(align[axis]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut size = painter.widget_at(&self.inner, region).size();
|
||||||
|
for axis in Axis::BOTH {
|
||||||
|
// The child may draw past the box it was given -- a text too tall
|
||||||
|
// for it -- and the cap is a promise about the length as well. A
|
||||||
|
// share passes through: it is a length only to whoever divides
|
||||||
|
// one, and that is this widget's parent rather than this widget,
|
||||||
|
// which has already given the share the box the cap allows.
|
||||||
|
if let Some(max) = self.max(axis)
|
||||||
|
&& painter.longer_than(size[axis].without_leftover(), max, axis)
|
||||||
|
{
|
||||||
|
size[axis] = max.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
size
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
mod layer;
|
mod layer;
|
||||||
|
mod max_size;
|
||||||
mod offset;
|
mod offset;
|
||||||
mod pad;
|
mod pad;
|
||||||
mod scroll;
|
mod scroll;
|
||||||
@@ -6,6 +7,7 @@ mod span;
|
|||||||
mod stack;
|
mod stack;
|
||||||
|
|
||||||
pub use layer::*;
|
pub use layer::*;
|
||||||
|
pub use max_size::*;
|
||||||
pub use offset::*;
|
pub use offset::*;
|
||||||
pub use pad::*;
|
pub use pad::*;
|
||||||
pub use scroll::*;
|
pub use scroll::*;
|
||||||
|
|||||||
@@ -71,6 +71,50 @@ widget_trait! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Answers at least this wide, whatever it drew: a rule beside the
|
||||||
|
/// widget, so what a row gives it is at least this even where the widget
|
||||||
|
/// itself wanted less. The box it draws in is untouched -- for that, see
|
||||||
|
/// [`MaxSize`].
|
||||||
|
fn min_width(self, len: impl Into<Len>) -> impl WidgetIdFn<Rsc, WL::Widget> {
|
||||||
|
let len = len.into();
|
||||||
|
move |state| {
|
||||||
|
let id = self.add(state);
|
||||||
|
state.ui_mut().widgets.set_min_len(id, Axis::X, len);
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn min_height(self, len: impl Into<Len>) -> impl WidgetIdFn<Rsc, WL::Widget> {
|
||||||
|
let len = len.into();
|
||||||
|
move |state| {
|
||||||
|
let id = self.add(state);
|
||||||
|
state.ui_mut().widgets.set_min_len(id, Axis::Y, len);
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Puts this in a [`MaxSize`]: it is asked in the shorter of the cap and
|
||||||
|
/// the box that widget was given, and is as long as it used, held to the
|
||||||
|
/// cap. A widget rather than a rule because the box is whoever asked's to
|
||||||
|
/// decide -- see [`MaxSize`].
|
||||||
|
fn max_width(self, len: impl Into<Len>) -> impl WidgetFn<Rsc, MaxSize> {
|
||||||
|
let len = len.into();
|
||||||
|
move |state| MaxSize {
|
||||||
|
inner: self.add_strong(state),
|
||||||
|
x: Some(len),
|
||||||
|
y: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn max_height(self, len: impl Into<Len>) -> impl WidgetFn<Rsc, MaxSize> {
|
||||||
|
let len = len.into();
|
||||||
|
move |state| MaxSize {
|
||||||
|
inner: self.add_strong(state),
|
||||||
|
x: None,
|
||||||
|
y: Some(len),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn height(self, len: impl Into<LayoutLen>) -> impl WidgetIdFn<Rsc, WL::Widget> {
|
fn height(self, len: impl Into<LayoutLen>) -> impl WidgetIdFn<Rsc, WL::Widget> {
|
||||||
let len = len.into();
|
let len = len.into();
|
||||||
move |state| {
|
move |state| {
|
||||||
|
|||||||
+119
-7
@@ -296,11 +296,11 @@ impl Asked {
|
|||||||
fn a_share_is_a_minimum_wherever_nothing_divides_it() {
|
fn a_share_is_a_minimum_wherever_nothing_divides_it() {
|
||||||
for (rule, want) in [
|
for (rule, want) in [
|
||||||
(LayoutLen::LEFTOVER, 400),
|
(LayoutLen::LEFTOVER, 400),
|
||||||
(LayoutLen::px(50) + LayoutLen::LEFTOVER, 400),
|
(LayoutLen::px(50.0) + LayoutLen::LEFTOVER, 400),
|
||||||
(LayoutLen::px(500) + LayoutLen::LEFTOVER, 500),
|
(LayoutLen::px(500.0) + LayoutLen::LEFTOVER, 500),
|
||||||
(LayoutLen::rel(0.5) + LayoutLen::LEFTOVER, 400),
|
(LayoutLen::rel(0.5) + LayoutLen::LEFTOVER, 400),
|
||||||
(LayoutLen::rel(2.0) + LayoutLen::LEFTOVER, 800),
|
(LayoutLen::rel(2.0) + LayoutLen::LEFTOVER, 800),
|
||||||
(LayoutLen::px(500), 500),
|
(LayoutLen::px(500.0), 500),
|
||||||
] {
|
] {
|
||||||
let want = Px::from_int(want);
|
let want = Px::from_int(want);
|
||||||
for asked in Asked::ALL {
|
for asked in Asked::ALL {
|
||||||
@@ -323,7 +323,7 @@ fn a_share_past_the_box_is_decided_again_on_either_side_of_the_crossing() {
|
|||||||
for wrapped in [false, true] {
|
for wrapped in [false, true] {
|
||||||
let mut h = Harness::new((400, 200));
|
let mut h = Harness::new((400, 200));
|
||||||
let probe = rect(Color::RED).add(&mut h.rsc);
|
let probe = rect(Color::RED).add(&mut h.rsc);
|
||||||
h.set_len(probe, Axis::X, LayoutLen::px(500) + LayoutLen::LEFTOVER);
|
h.set_len(probe, Axis::X, LayoutLen::px(500.0) + LayoutLen::LEFTOVER);
|
||||||
match wrapped {
|
match wrapped {
|
||||||
true => h.set_root(probe.wrapper()),
|
true => h.set_root(probe.wrapper()),
|
||||||
false => h.set_root(probe),
|
false => h.set_root(probe),
|
||||||
@@ -339,11 +339,11 @@ fn a_share_past_the_box_is_decided_again_on_either_side_of_the_crossing() {
|
|||||||
h.frame();
|
h.frame();
|
||||||
assert_eq!(width(&h), Px::from_int(500), "wrapped: {wrapped}");
|
assert_eq!(width(&h), Px::from_int(500), "wrapped: {wrapped}");
|
||||||
|
|
||||||
h.set_len(probe, Axis::X, LayoutLen::px(50) + LayoutLen::LEFTOVER);
|
h.set_len(probe, Axis::X, LayoutLen::px(50.0) + LayoutLen::LEFTOVER);
|
||||||
h.frame();
|
h.frame();
|
||||||
assert_eq!(width(&h), Px::from_int(400), "wrapped: {wrapped}");
|
assert_eq!(width(&h), Px::from_int(400), "wrapped: {wrapped}");
|
||||||
|
|
||||||
h.set_len(probe, Axis::X, LayoutLen::px(500) + LayoutLen::LEFTOVER);
|
h.set_len(probe, Axis::X, LayoutLen::px(500.0) + LayoutLen::LEFTOVER);
|
||||||
h.frame();
|
h.frame();
|
||||||
assert_eq!(width(&h), Px::from_int(500), "wrapped: {wrapped}");
|
assert_eq!(width(&h), Px::from_int(500), "wrapped: {wrapped}");
|
||||||
}
|
}
|
||||||
@@ -756,7 +756,7 @@ fn only_a_pure_leftover_child_disappears_when_nothing_is_left() {
|
|||||||
let mut h = Harness::new((100, 20));
|
let mut h = Harness::new((100, 20));
|
||||||
let fixed = rect(Color::RED).width(100).add(&mut h.rsc);
|
let fixed = rect(Color::RED).width(100).add(&mut h.rsc);
|
||||||
let mixed = rect(Color::BLUE)
|
let mixed = rect(Color::BLUE)
|
||||||
.width(LayoutLen::px(20) + LayoutLen::LEFTOVER)
|
.width(LayoutLen::px(20.0) + LayoutLen::LEFTOVER)
|
||||||
.add(&mut h.rsc);
|
.add(&mut h.rsc);
|
||||||
h.set_root((fixed, mixed).span(Dir::RIGHT));
|
h.set_root((fixed, mixed).span(Dir::RIGHT));
|
||||||
|
|
||||||
@@ -1004,3 +1004,115 @@ fn a_region_node_root_is_a_region_node() {
|
|||||||
h.frame();
|
h.frame();
|
||||||
assert_eq!(h.region(&probe).unwrap().size().x, Px::from_int(900));
|
assert_eq!(h.region(&probe).unwrap().size().x, Px::from_int(900));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A bound is a rule about what a widget answers: it holds the length that
|
||||||
|
/// reaches whoever asked and leaves the box alone. Here the content is 400
|
||||||
|
/// wide in a 250 window, so a cap cuts what the row reports and a floor
|
||||||
|
/// raises it, while the rects inside stay where the 250 box put them.
|
||||||
|
#[test]
|
||||||
|
fn a_bound_holds_what_a_widget_answers() {
|
||||||
|
let row = |rule: SizeRule| {
|
||||||
|
let mut h = Harness::new((250, 200));
|
||||||
|
let left = rect(Color::RED).width(200).add(&mut h.rsc);
|
||||||
|
let right = rect(Color::BLUE).width(200).add(&mut h.rsc);
|
||||||
|
let row = (left, right).span(Dir::RIGHT).add(&mut h.rsc);
|
||||||
|
h.rsc.widgets_mut().set_size_rule(row, Axis::X, rule);
|
||||||
|
h.set_root(row);
|
||||||
|
(
|
||||||
|
h.region(&row).unwrap().size().x,
|
||||||
|
h.region(&left).unwrap().size().x,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let (capped, left) = row(SizeRule::Max(Len::px(300.0)));
|
||||||
|
assert_eq!(capped, Px::from_int(300), "the cap, not the 400 drawn");
|
||||||
|
assert_eq!(left, Px::from_int(200), "the box the children were given");
|
||||||
|
|
||||||
|
let (floored, _) = row(SizeRule::Min(Len::px(600.0)));
|
||||||
|
assert_eq!(floored, Px::from_int(600), "the floor, not the 400 drawn");
|
||||||
|
|
||||||
|
let (free, _) = row(SizeRule::Free);
|
||||||
|
assert_eq!(free, Px::from_int(400), "what it drew");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A cap on the box is `MaxSize`, which asks its child in the shorter of the
|
||||||
|
/// cap and its own box. That is the box a text wraps at and a scroll takes
|
||||||
|
/// its viewport from, so it cannot be had by holding the answer.
|
||||||
|
#[test]
|
||||||
|
fn a_cap_widget_asks_its_child_in_the_shorter_box() {
|
||||||
|
let mut h = Harness::new((400, 200));
|
||||||
|
// A fraction of its box, so it says what box it was asked in.
|
||||||
|
let fills = rect(Color::RED).width(rel(1.0)).add(&mut h.rsc);
|
||||||
|
let capped = fills.max_width(300).add(&mut h.rsc);
|
||||||
|
h.set_root(capped);
|
||||||
|
|
||||||
|
assert_eq!(h.region(&fills).unwrap().size().x, Px::from_int(300));
|
||||||
|
assert_eq!(
|
||||||
|
h.region(&capped).unwrap().size().x,
|
||||||
|
Px::from_int(300),
|
||||||
|
"as long as its child used"
|
||||||
|
);
|
||||||
|
|
||||||
|
// A child that asked for a share takes the box the cap allows, and the
|
||||||
|
// share itself passes up: whoever divides one is this widget's parent.
|
||||||
|
let mut h = Harness::new((400, 200));
|
||||||
|
let share = rect(Color::RED).add(&mut h.rsc);
|
||||||
|
let capped = share.max_width(300).add(&mut h.rsc);
|
||||||
|
h.set_root(capped);
|
||||||
|
|
||||||
|
assert_eq!(h.region(&share).unwrap().size().x, Px::from_int(300));
|
||||||
|
assert_eq!(h.region(&capped).unwrap().size().x, Px::from_int(400));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Which of the cap and the box is shorter is a question in pixels, so it is
|
||||||
|
/// asked again wherever the answer can change -- and the widget asking it is
|
||||||
|
/// drawn again whenever its own box is, which is what keeps the two in step.
|
||||||
|
#[test]
|
||||||
|
fn a_cap_widget_is_decided_again_on_either_side_of_the_crossing() {
|
||||||
|
let mut h = Harness::new((400, 200));
|
||||||
|
let probe = rect(Color::RED).add(&mut h.rsc);
|
||||||
|
h.set_root(probe.max_width(300));
|
||||||
|
let width = |h: &Harness| h.region(&probe).unwrap().size().x;
|
||||||
|
assert_eq!(width(&h), Px::from_int(300));
|
||||||
|
|
||||||
|
h.resize((250, 200));
|
||||||
|
h.frame();
|
||||||
|
assert_eq!(
|
||||||
|
width(&h),
|
||||||
|
Px::from_int(250),
|
||||||
|
"its box, which is under the cap"
|
||||||
|
);
|
||||||
|
|
||||||
|
h.resize((400, 200));
|
||||||
|
h.frame();
|
||||||
|
assert_eq!(width(&h), Px::from_int(300));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A fraction in a cap is a fraction of the box the widget capping it was
|
||||||
|
/// given, which is the box a declared length of its own would be a fraction
|
||||||
|
/// of -- not of the window, and not of what the cap itself decided.
|
||||||
|
#[test]
|
||||||
|
fn a_cap_is_a_fraction_of_the_box_it_was_given() {
|
||||||
|
let mut h = Harness::new((400, 200));
|
||||||
|
let probe = rect(Color::RED).add(&mut h.rsc);
|
||||||
|
h.set_root(probe.max_width(Len::rel(0.5)).pad(Padding::uniform(50)));
|
||||||
|
|
||||||
|
// Half of the 300 left by the padding, not half of the window.
|
||||||
|
assert_eq!(h.region(&probe).unwrap().size().x, Px::from_int(150));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A cap is a promise about the length as well as the box: a widget whose
|
||||||
|
/// content is longer than the box it was given reports what it drew, and the
|
||||||
|
/// cap holds that down even though it never decided the box.
|
||||||
|
#[test]
|
||||||
|
fn a_cap_holds_an_answer_that_overflowed_its_box() {
|
||||||
|
let mut h = Harness::new((250, 200));
|
||||||
|
let left = rect(Color::RED).width(200).add(&mut h.rsc);
|
||||||
|
let right = rect(Color::BLUE).width(200).add(&mut h.rsc);
|
||||||
|
let row = (left, right).span(Dir::RIGHT).add(&mut h.rsc);
|
||||||
|
h.rsc.widgets_mut().set_max_len(row, Axis::X, 300.into());
|
||||||
|
h.set_root(row);
|
||||||
|
|
||||||
|
// The box is the 250 window, which the cap of 300 leaves alone, and the
|
||||||
|
// row draws 400 of it. Its answer is the cap, and the window centres it.
|
||||||
|
assert_corners!(h, row, (-25, 0), (275, 200));
|
||||||
|
}
|
||||||
@@ -161,3 +161,26 @@ fn content_that_fits_is_placed_in_the_viewport_and_not_in_the_window() {
|
|||||||
assert_corners!(h, scroll, (0, 100), (400, 400));
|
assert_corners!(h, scroll, (0, 100), (400, 400));
|
||||||
assert_corners!(h, inner, (0, 225), (400, 275));
|
assert_corners!(h, inner, (0, 225), (400, 275));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A cap narrows the box the widget is asked in, which is what a scroll
|
||||||
|
/// measures its viewport from: the content scrolls within the cap rather than
|
||||||
|
/// within the room the cap was cut from.
|
||||||
|
#[test]
|
||||||
|
fn a_capped_scroll_takes_its_viewport_from_the_cap() {
|
||||||
|
let mut h = Harness::new((400, 200));
|
||||||
|
let top = rect(Color::RED).height(200).add(&mut h.rsc);
|
||||||
|
let bottom = rect(Color::BLUE).height(200).add(&mut h.rsc);
|
||||||
|
let scroll = (top, bottom).span(Dir::DOWN).scrollable().add(&mut h.rsc);
|
||||||
|
let capped = scroll.max_height(100).add(&mut h.rsc);
|
||||||
|
h.set_root(capped);
|
||||||
|
h.move_to((200, 50));
|
||||||
|
|
||||||
|
// 400 of content in a viewport of 100, so 300 to scroll and the end
|
||||||
|
// showing: the top is 300 above the box, which the window centres.
|
||||||
|
assert_eq!(h.region(&scroll).unwrap().size().y, Px::from_int(100));
|
||||||
|
assert_corners!(h, top, (0, -250), (400, -50));
|
||||||
|
|
||||||
|
h.scroll((0, 1));
|
||||||
|
h.frame();
|
||||||
|
assert_corners!(h, top, (0, -200), (400, 0));
|
||||||
|
}
|
||||||
+22
-7
@@ -194,14 +194,22 @@ fn mark(warm: &mut Harness, tree: &Tree, step: usize) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn a_len(rng: &mut Rng) -> Option<LayoutLen> {
|
/// A length in pixels, or a cap over one: a rule that reads the box it is
|
||||||
Some(LayoutLen::px(20.0 + rng.below(180) as f32))
|
/// given is the one a resize can change the effect of without changing the
|
||||||
|
/// rule, so a tree that never grows one leaves that unexercised.
|
||||||
|
fn a_rule(rng: &mut Rng) -> SizeRule {
|
||||||
|
let len = Len::px(20.0 + rng.below(180) as f32);
|
||||||
|
match rng.below(4) {
|
||||||
|
0 => SizeRule::Max(len),
|
||||||
|
1 => SizeRule::Min(len),
|
||||||
|
_ => LayoutLen::from(len).into(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resize_one(warm: &mut Harness, tree: &Tree, idx: usize, rng: &mut Rng) -> SizeRules {
|
fn resize_one(warm: &mut Harness, tree: &Tree, idx: usize, rng: &mut Rng) -> SizeRules {
|
||||||
let lens = SizeRules {
|
let lens = SizeRules {
|
||||||
x: a_len(rng).into(),
|
x: a_rule(rng),
|
||||||
y: a_len(rng).into(),
|
y: a_rule(rng),
|
||||||
};
|
};
|
||||||
warm.rsc
|
warm.rsc
|
||||||
.widgets_mut()
|
.widgets_mut()
|
||||||
@@ -349,9 +357,16 @@ fn change(case: Case, warm: &mut Harness, tree: &mut Tree, plan: &Plan, rng: &mu
|
|||||||
/// buildable from what the failure printed.
|
/// buildable from what the failure printed.
|
||||||
fn describe(id: WidgetId, h: &Harness) -> String {
|
fn describe(id: WidgetId, h: &Harness) -> String {
|
||||||
let rules = h.rsc.widgets().size_rules(id);
|
let rules = h.rsc.widgets().size_rules(id);
|
||||||
let rule = |r: SizeRule| match r.exact() {
|
// A bound prints as itself: a failure is reproduced from what it printed,
|
||||||
Some(len) => format!("{len}"),
|
// and a rule shown as "no rule" cannot be written out again.
|
||||||
None => "-".into(),
|
let rule = |r: SizeRule| match r {
|
||||||
|
SizeRule::Free => "-".into(),
|
||||||
|
SizeRule::Exact(len) => format!("{len}"),
|
||||||
|
SizeRule::Min(min) => format!(">{}", LayoutLen::from(min)),
|
||||||
|
SizeRule::Max(max) => format!("<{}", LayoutLen::from(max)),
|
||||||
|
SizeRule::Clamp { min, max } => {
|
||||||
|
format!(">{}<{}", LayoutLen::from(min), LayoutLen::from(max))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let align = h.rsc.widgets().alignment(id);
|
let align = h.rsc.widgets().alignment(id);
|
||||||
let side = |a: AxisAlign| {
|
let side = |a: AxisAlign| {
|
||||||
|
|||||||
Reference in new issue
Block a user