Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58ce74dd7d | ||
|
|
c55be21761 | ||
|
|
beb138632a | ||
|
|
aeb60e50f5 | ||
|
|
a904cf4f36 | ||
|
|
5642f2010a | ||
|
|
84dad211f5 | ||
|
|
add6774980 | ||
|
|
cadfba05dd | ||
|
|
38b3a81053 |
No files matched your search
@@ -14,3 +14,13 @@ WidgetRef<W> or smth instead of Id
|
||||
vecs for each widget type?
|
||||
|
||||
POTENTIAL BUG: closures that store IDs will not decrement the id!!! need to not increment id if moved into closure somehow??? wait no, need to decrement ID every time an event fn is added...... only if the id is used in it..??
|
||||
|
||||
transforms on a move entry (scale + rotation)
|
||||
an entry is a translation today; composing through one scales the rel
|
||||
part and passes px through untouched, so fixed-size content and glyphs
|
||||
do not follow a shortened entry
|
||||
want a real transform per entry, resolved in resolve_move the way the
|
||||
translation already is, so a whole subtree transforms with one buffer
|
||||
write and no redraw
|
||||
wanted for compose-style stretch at the end of a scroll area, and for
|
||||
rotation generally
|
||||
@@ -53,12 +53,12 @@ pub(crate) enum Counter {
|
||||
TextBreaks,
|
||||
GlyphPlacements,
|
||||
OutsidePinnedLen,
|
||||
OutsideFrame,
|
||||
OutsideExtent,
|
||||
OutsideRelBase,
|
||||
OutsideRegion,
|
||||
}
|
||||
|
||||
impl Counter {
|
||||
const COUNT: usize = Self::OutsideExtent as usize + 1;
|
||||
const COUNT: usize = Self::OutsideRegion as usize + 1;
|
||||
|
||||
const NAMES: [&'static str; Self::COUNT] = [
|
||||
"updates",
|
||||
@@ -89,8 +89,8 @@ impl Counter {
|
||||
"text line breaks",
|
||||
"glyph placements",
|
||||
"reuse outside: the length it was pinned to",
|
||||
"reuse outside: a frame length",
|
||||
"reuse outside: an extent length",
|
||||
"reuse outside: a rel base",
|
||||
"reuse outside: a region length",
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
+18
-22
@@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
LayerId, LayoutHolds, LayoutLen, Len, MaskIdx, MoveIdx, Place, RegionAlign, RetainedPrimitive,
|
||||
LayerId, LayoutHolds, LayoutLen, MaskIdx, MoveIdx, PlaceDesc, RegionAlign, RetainedPrimitive,
|
||||
Size, TextureHandle, UiRegion, UiVec2, WidgetId,
|
||||
};
|
||||
|
||||
@@ -10,26 +10,21 @@ use crate::{
|
||||
pub struct ActiveData {
|
||||
pub id: WidgetId,
|
||||
/// Where its drawing goes, in its region node's coordinates.
|
||||
pub extent: UiRegion,
|
||||
pub placement: UiRegion,
|
||||
/// What a fraction declared or reported under this widget is a fraction
|
||||
/// of, as a length of the window.
|
||||
pub frame: UiVec2,
|
||||
/// A frame its parent decided for it on each axis -- a row's slot, or
|
||||
/// padding's frame less its pixels -- as a length of the window. `None`
|
||||
/// forwards the parent's frame. What it declared is kept separately in
|
||||
/// `declared` and is a fraction of whichever of the two reached it.
|
||||
pub narrow: [Option<Len>; 2],
|
||||
/// Where its drawing was put, and where it was asked, each as a part of
|
||||
/// its parent's box. The two differ where a container asks in one place
|
||||
/// and puts the answer in another -- a row measures from its cursor and
|
||||
/// puts the child in its slot. A part is a length from the box's start,
|
||||
/// so a box that moved re-places every child by re-adding that start.
|
||||
pub placed: [Place; 2],
|
||||
pub asked: [Place; 2],
|
||||
pub rel_base: UiVec2,
|
||||
/// Where its drawing was put, and where it was asked. The two differ
|
||||
/// where a container asks in one place and puts the answer in another --
|
||||
/// a row measures from its cursor and puts the child in its slot. Each
|
||||
/// carries the rel base that ask stated, so asking again from either is
|
||||
/// the same question it was.
|
||||
pub placed: PlaceDesc,
|
||||
pub asked: PlaceDesc,
|
||||
/// The box it was asked in, in the parent's region-node coordinates: the
|
||||
/// box its drawing was made in and the one its contract is about. Its
|
||||
/// drawing is placed elsewhere by re-expression, never by asking again.
|
||||
pub part: UiRegion,
|
||||
pub region: UiRegion,
|
||||
/// The measured answer and its dependencies. A hint-only dependency or
|
||||
/// a widget first encountered during placement has no measurement yet.
|
||||
pub answer: Option<(Size, LayoutHolds)>,
|
||||
@@ -40,8 +35,8 @@ pub struct ActiveData {
|
||||
pub re_asked: bool,
|
||||
/// What the widget reported, in window-unit lengths.
|
||||
pub size: Size,
|
||||
/// The window and extent reads that this drawing holds for, and the
|
||||
/// frame and box it pinned.
|
||||
/// The window and region reads that this drawing holds for, and the
|
||||
/// rel base and region it pinned.
|
||||
pub holds: LayoutHolds,
|
||||
pub drawn: bool,
|
||||
pub parent: Option<WidgetId>,
|
||||
@@ -51,8 +46,9 @@ pub struct ActiveData {
|
||||
pub depth: usize,
|
||||
pub textures: Vec<TextureHandle>,
|
||||
/// Its primitives, each keeping the box it was written in -- in this
|
||||
/// widget's extent coordinates, which is what a move recomposes from.
|
||||
/// widget's placement coordinates, which is what a move recomposes from.
|
||||
pub primitives: Vec<RetainedPrimitive>,
|
||||
/// An owned mask holds one reference independently of its primitives.
|
||||
pub mask_region: Option<UiRegion>,
|
||||
pub children: Vec<WidgetId>,
|
||||
/// The children whose size this widget read while drawing.
|
||||
@@ -60,15 +56,15 @@ pub struct ActiveData {
|
||||
/// The movable region its primitives are positioned through: its own when
|
||||
/// opted in, otherwise the nearest ancestor's.
|
||||
pub move_idx: MoveIdx,
|
||||
/// The declared lengths whoever drew this widget resolved into its frame.
|
||||
/// The declared lengths whoever drew this widget resolved into its rel base.
|
||||
/// A change to one moves a box this widget cannot fix by drawing again,
|
||||
/// and comparing them is what says so.
|
||||
pub declared: [Option<LayoutLen>; 2],
|
||||
/// Its alignment when it was last drawn, which a change to the property
|
||||
/// is found against.
|
||||
pub own_align: RegionAlign,
|
||||
/// The movable region whose coordinates `extent` uses when this widget
|
||||
/// does not own a region node.
|
||||
/// The movable region whose coordinates its placement is in when this
|
||||
/// widget does not own a region node.
|
||||
pub parent_move: MoveIdx,
|
||||
/// The mask its drawing is clipped to: one it set itself, or the one it
|
||||
/// inherited from whoever drew it.
|
||||
|
||||
@@ -73,21 +73,6 @@ impl Holds {
|
||||
}
|
||||
}
|
||||
|
||||
/// What a box has to be for a part of it, this many pixels shorter, to
|
||||
/// stay in this range: the range moved by that much, an end that was
|
||||
/// unbounded staying so.
|
||||
pub const fn longer_by(self, px: Px) -> Self {
|
||||
let lo = match self.lo.raw() == Px::MIN.raw() {
|
||||
true => self.lo,
|
||||
false => self.lo.add(px),
|
||||
};
|
||||
let hi = match self.hi.raw() == Px::MAX.raw() {
|
||||
true => self.hi,
|
||||
false => self.hi.add(px),
|
||||
};
|
||||
Self { lo, hi }
|
||||
}
|
||||
|
||||
const fn raws(lo: i64, hi: i64) -> Self {
|
||||
Self {
|
||||
lo: Px::from_raw(narrow(lo)),
|
||||
|
||||
+30
-30
@@ -4,53 +4,53 @@ const AXES: [Axis; 2] = [Axis::X, Axis::Y];
|
||||
|
||||
/// What one evaluation of a widget depends on: the window lengths its reads
|
||||
/// hold for, the pixel lengths of its own box, and the symbolic lengths of
|
||||
/// that box and of its frame where either one is what it was expressed in.
|
||||
/// that box and of its rel base where either one is what it was expressed in.
|
||||
///
|
||||
/// The symbolic lengths are pins rather than ranges: a container places its
|
||||
/// children as lengths of its frame measured from where its own box starts,
|
||||
/// children as lengths of its rel base measured from where its own box starts,
|
||||
/// so what it draws turns on that box's length and on nothing about where it
|
||||
/// is. A box pin reaches the parent only where the box it pinned is the
|
||||
/// parent's own; anywhere else the parent chose that length itself, and a
|
||||
/// widget pinned this way is checked when it is re-placed.
|
||||
///
|
||||
/// A frame pin says the answer or the drawing is a fraction of the frame,
|
||||
/// which is a different length wherever the frame is a different one -- at
|
||||
/// A rel base pin says the answer or the drawing is a fraction of the rel base,
|
||||
/// which is a different length wherever the rel base is a different one -- at
|
||||
/// the same window size, so no range of window pixels can say it. A length
|
||||
/// of the frame that is only pixels is not one: it is that many pixels
|
||||
/// whatever the frame turns out to be.
|
||||
/// of the rel base that is only pixels is not one: it is that many pixels
|
||||
/// whatever the rel base turns out to be.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct LayoutHolds {
|
||||
pub window: [Holds; 2],
|
||||
pub frame_len: [Option<Len>; 2],
|
||||
pub extent: [Holds; 2],
|
||||
pub extent_len: [Option<Len>; 2],
|
||||
pub rel_base: [Option<Len>; 2],
|
||||
pub region: [Holds; 2],
|
||||
pub region_len: [Option<Len>; 2],
|
||||
}
|
||||
|
||||
impl LayoutHolds {
|
||||
pub const ANY: Self = Self {
|
||||
window: [Holds::ANY; 2],
|
||||
frame_len: [None; 2],
|
||||
extent: [Holds::ANY; 2],
|
||||
extent_len: [None; 2],
|
||||
rel_base: [None; 2],
|
||||
region: [Holds::ANY; 2],
|
||||
region_len: [None; 2],
|
||||
};
|
||||
|
||||
pub fn and(self, other: Self) -> Self {
|
||||
let mut result = Self::ANY;
|
||||
for n in 0..2 {
|
||||
result.window[n] = self.window[n].and(other.window[n]);
|
||||
result.extent[n] = self.extent[n].and(other.extent[n]);
|
||||
result.region[n] = self.region[n].and(other.region[n]);
|
||||
debug_assert!(
|
||||
self.extent_len[n].is_none()
|
||||
|| other.extent_len[n].is_none()
|
||||
|| self.extent_len[n] == other.extent_len[n]
|
||||
self.region_len[n].is_none()
|
||||
|| other.region_len[n].is_none()
|
||||
|| self.region_len[n] == other.region_len[n]
|
||||
);
|
||||
debug_assert!(
|
||||
self.frame_len[n].is_none()
|
||||
|| other.frame_len[n].is_none()
|
||||
|| self.frame_len[n] == other.frame_len[n]
|
||||
self.rel_base[n].is_none()
|
||||
|| other.rel_base[n].is_none()
|
||||
|| self.rel_base[n] == other.rel_base[n]
|
||||
);
|
||||
result.extent_len[n] = self.extent_len[n].or(other.extent_len[n]);
|
||||
result.frame_len[n] = self.frame_len[n].or(other.frame_len[n]);
|
||||
result.region_len[n] = self.region_len[n].or(other.region_len[n]);
|
||||
result.rel_base[n] = self.rel_base[n].or(other.rel_base[n]);
|
||||
}
|
||||
result
|
||||
}
|
||||
@@ -59,21 +59,21 @@ impl LayoutHolds {
|
||||
(0..2).all(|n| {
|
||||
self.window[n].lo <= other.window[n].lo
|
||||
&& self.window[n].hi >= other.window[n].hi
|
||||
&& self.extent[n].lo <= other.extent[n].lo
|
||||
&& self.extent[n].hi >= other.extent[n].hi
|
||||
&& self.extent_len[n].is_none_or(|len| other.extent_len[n] == Some(len))
|
||||
&& self.frame_len[n].is_none_or(|len| other.frame_len[n] == Some(len))
|
||||
&& self.region[n].lo <= other.region[n].lo
|
||||
&& self.region[n].hi >= other.region[n].hi
|
||||
&& self.region_len[n].is_none_or(|len| other.region_len[n] == Some(len))
|
||||
&& self.rel_base[n].is_none_or(|len| other.rel_base[n] == Some(len))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn contains(self, window: PxVec2, frame: UiVec2, extent: UiRegion) -> bool {
|
||||
pub fn contains(self, window: PxVec2, rel_base: UiVec2, region: UiRegion) -> bool {
|
||||
AXES.into_iter().all(|axis| {
|
||||
let n = axis as usize;
|
||||
let len = extent.axis(axis).len();
|
||||
let len = region.axis(axis).len();
|
||||
self.window[n].contains(window.axis(axis))
|
||||
&& self.frame_len[n].is_none_or(|pinned| pinned == frame.axis(axis))
|
||||
&& self.extent[n].contains(len.to_px(window.axis(axis)))
|
||||
&& self.extent_len[n].is_none_or(|pinned| pinned == len)
|
||||
&& self.rel_base[n].is_none_or(|pinned| pinned == rel_base.axis(axis))
|
||||
&& self.region[n].contains(len.to_px(window.axis(axis)))
|
||||
&& self.region_len[n].is_none_or(|pinned| pinned == len)
|
||||
})
|
||||
}
|
||||
}
|
||||
+205
-208
@@ -1,7 +1,7 @@
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
use crate::layout_diagnostics::{self as diag, Counter};
|
||||
use crate::{
|
||||
Axis, Holds, LayoutHolds, LayoutLen, Len, Part, Place, Px, PxVec2, RegionAlign, Rel,
|
||||
Axis, Holds, LayoutHolds, LayoutLen, Len, PlaceDesc, Px, PxVec2, RegionAlign, Rel,
|
||||
RenderedText, RetainedPrimitive, Size, StrongWidget, TextAttrs, TextBuffer, TextData,
|
||||
TextureHandle, UiRegion, UiRenderState, UiRsc, UiSpan, UiVec2, Weight, WidgetId, Widgets,
|
||||
render::{
|
||||
@@ -17,17 +17,14 @@ pub struct Painter<'a> {
|
||||
pub(super) state: &'a mut UiRenderState,
|
||||
pub(super) rsc: &'a mut dyn UiRsc,
|
||||
|
||||
/// This widget's frame, per axis: a length of the window, and what a
|
||||
/// This widget's rel base, per axis: a length of the window, and what a
|
||||
/// fraction it or anything under it declares or reports is a fraction
|
||||
/// of. A length rather than a box, so padding can take from both the
|
||||
/// frame and the box without either becoming the other.
|
||||
pub(super) frame: UiVec2,
|
||||
/// Where this widget's drawing goes, in its region node's coordinates.
|
||||
pub(super) extent: UiRegion,
|
||||
/// The extent's symbolic length where this draw read it, which makes the
|
||||
/// drawing one that holds for that length alone -- the way reading a
|
||||
/// length in pixels makes it hold for that number of pixels.
|
||||
pub(super) extent_len: [Option<Len>; 2],
|
||||
/// rel base and the box without either becoming the other.
|
||||
pub(super) rel_base: UiVec2,
|
||||
/// The box this widget was asked in, in its region node's coordinates:
|
||||
/// what it draws in, and what its children's places are parts of.
|
||||
pub(super) region: UiRegion,
|
||||
/// The window in pixels. Frames and boxes become pixels against this one
|
||||
/// unit, regardless of region-node boundaries.
|
||||
pub(super) window: PxVec2,
|
||||
@@ -35,19 +32,19 @@ pub struct Painter<'a> {
|
||||
pub(super) textures: Vec<TextureHandle>,
|
||||
pub(super) primitives: Vec<RetainedPrimitive>,
|
||||
pub(super) mask_region: Option<UiRegion>,
|
||||
/// The previous drawing's owned mask, available for this draw to reclaim.
|
||||
pub(super) mask_slot: Option<MaskIdx>,
|
||||
/// Only children whose answers were read constrain this widget's answer.
|
||||
pub(super) answer_under: LayoutHolds,
|
||||
pub(super) children: Vec<WidgetId>,
|
||||
/// The children whose size this widget read while drawing.
|
||||
pub(super) size_deps: Vec<WidgetId>,
|
||||
/// What this draw itself read of the window in pixels, per axis: every
|
||||
/// window until it reads one, then that one, unless it says otherwise.
|
||||
pub(super) window_own: [Holds; 2],
|
||||
/// Its frame's symbolic length where this draw read it, which makes the
|
||||
/// drawing one that holds for that frame alone.
|
||||
pub(super) frame_own_len: [Option<Len>; 2],
|
||||
/// The window reads' equivalent for its own box.
|
||||
pub(super) extent_own: [Holds; 2],
|
||||
/// What this draw itself reads, as against what its children's drawings
|
||||
/// hold for: every window and every length of its own region until it
|
||||
/// reads one, then that one unless it says otherwise, and the rel base or
|
||||
/// region length it read symbolically, each of which makes the drawing
|
||||
/// hold for that length alone.
|
||||
pub(super) own: LayoutHolds,
|
||||
/// What each child's drawing depends on. Asking a child again replaces
|
||||
/// its drawing, so it replaces this too rather than narrowing it.
|
||||
pub(super) under: Vec<(WidgetId, LayoutHolds)>,
|
||||
@@ -73,10 +70,10 @@ impl<'a> Painter<'a> {
|
||||
self.write_resolved(kind, primitive, region, self.resolve(region));
|
||||
}
|
||||
|
||||
/// A box in this widget's extent coordinates, composed into its region
|
||||
/// node's coordinates.
|
||||
/// A box in this widget's region, composed into its region node's
|
||||
/// coordinates.
|
||||
fn resolve(&self, region: UiRegion) -> UiRegion {
|
||||
region.within(&self.extent)
|
||||
region.within(&self.region)
|
||||
}
|
||||
|
||||
fn write_resolved<P: Primitive>(
|
||||
@@ -104,7 +101,6 @@ impl<'a> Painter<'a> {
|
||||
|
||||
fn push_primitive(&mut self, h: RetainedPrimitive) {
|
||||
if self.mask != MaskIdx::NONE {
|
||||
// TODO: I have no clue if this works at all :joy:
|
||||
self.rsc.ui_mut().masks.push_ref(self.mask);
|
||||
}
|
||||
self.primitives.push(h);
|
||||
@@ -129,58 +125,79 @@ impl<'a> Painter<'a> {
|
||||
assert!(self.mask == MaskIdx::NONE);
|
||||
let resolved = self.resolve(region);
|
||||
let move_idx = self.move_idx;
|
||||
self.mask = self.rsc.ui_mut().masks.push(Mask {
|
||||
let mask = Mask {
|
||||
region: resolved,
|
||||
move_idx,
|
||||
});
|
||||
};
|
||||
let masks = &mut self.rsc.ui_mut().masks;
|
||||
self.mask = match self.mask_slot.take() {
|
||||
Some(idx) => {
|
||||
*masks.get_mut(idx) = mask;
|
||||
idx
|
||||
}
|
||||
None => {
|
||||
let idx = masks.push(mask);
|
||||
// The owner keeps the slot alive even with no primitives.
|
||||
masks.push_ref(idx);
|
||||
idx
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Draws a widget in the whole of this widget's own box, with the frame
|
||||
/// forwarded unchanged: what a container that is only a wrapper around
|
||||
/// one child wants, and what every transparent container passes for the
|
||||
/// frame.
|
||||
/// Draws a widget in the whole of this widget's own box, with the rel
|
||||
/// base forwarded unchanged: what a container that is only a wrapper
|
||||
/// around one child wants.
|
||||
pub fn widget<'s, W: ?Sized>(&'s mut self, id: &'s StrongWidget<W>) -> DrawResult<'s, 'a, W> {
|
||||
self.widget_at(id, [None; 2], [Place::Within(Part::All); 2])
|
||||
self.widget_at(id, UiRegion::FULL)
|
||||
}
|
||||
|
||||
/// Resolves what the place says about the child's rel base into a length,
|
||||
/// where that is this widget's own narrowed the way the region is. An
|
||||
/// axis the region leaves whole is not read at all, so a wrapper that
|
||||
/// only moves its child does not pin its drawing to a rel base.
|
||||
fn state_rel_base(&mut self, mut place: PlaceDesc) -> PlaceDesc {
|
||||
for axis in AXES {
|
||||
if let Some(span) = place.axis(axis).narrows_rel_base() {
|
||||
let len = span.len();
|
||||
let stated = (len != Len::FULL).then(|| len.within_len(self.rel_base(axis)));
|
||||
*place.axis_mut(axis) = place.axis(axis).with_rel_base(stated);
|
||||
}
|
||||
}
|
||||
place
|
||||
}
|
||||
|
||||
/// Asks a child, saying what its fractions are of and where it is asked.
|
||||
///
|
||||
/// `narrow` is a length this widget decided for the child's frame, per
|
||||
/// axis, as a length of this widget's own frame: a resolved share, or a
|
||||
/// box a sibling's answer decided. `None` forwards this widget's frame,
|
||||
/// which is what a container that only divides room passes, so a
|
||||
/// fraction under it means the same wherever it sits and however deeply
|
||||
/// it is nested. A declared length narrows the frame here whatever the
|
||||
/// caller says. A narrowed frame is placed in the part by the child's
|
||||
/// alignment and is the box the child is asked in.
|
||||
/// `place` says where the child goes and what its fractions are of:
|
||||
/// see [`PlaceDesc`]. A `UiRegion` converts into the common case, which
|
||||
/// is a box of this widget's own with the answer placed inside it.
|
||||
///
|
||||
/// `place` is where the child is asked, per axis, as a part of this
|
||||
/// widget's box: see [`Place`]. The child draws once, in that box, and
|
||||
/// its answer is placed inside it by re-expressing the drawing. Nothing
|
||||
/// is drawn again in a box an answer chose; a container that puts the
|
||||
/// The child draws once, in the region that comes of it, and its answer
|
||||
/// is placed inside that region by re-expressing the drawing. Nothing is
|
||||
/// drawn again in a box an answer chose; a container that puts the
|
||||
/// answer somewhere else says so with [`Self::place_at`].
|
||||
pub fn widget_at<'s, W: ?Sized>(
|
||||
&'s mut self,
|
||||
id: &'s StrongWidget<W>,
|
||||
narrow: [Option<Len>; 2],
|
||||
place: [Place; 2],
|
||||
place: impl Into<PlaceDesc>,
|
||||
) -> DrawResult<'s, 'a, W> {
|
||||
let place = self.state_rel_base(place.into());
|
||||
let region_node = self.rsc.widgets().is_region_node(id.id());
|
||||
let declared = self.declared_lens(id);
|
||||
let align = self.rsc.widgets().alignment(id.id());
|
||||
let (frame, extent) =
|
||||
frame_and_extent(self.extent, self.frame, place, narrow, declared, align);
|
||||
let (rel_base, region) =
|
||||
rel_base_and_region(self.region, self.rel_base, place, declared, align);
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
if region_node {
|
||||
diag::bump(Counter::RegionNodeDraws);
|
||||
diag::region_node(id.id(), self.id, extent);
|
||||
diag::region_node(id.id(), self.id, region);
|
||||
}
|
||||
// A child listed twice would be moved twice.
|
||||
let re_asked = self.children.contains(&id.id());
|
||||
if !re_asked {
|
||||
self.children.push(id.id());
|
||||
}
|
||||
let px = frame.to_px(self.window);
|
||||
let px = rel_base.to_px(self.window);
|
||||
let (size, answer_holds, holds) = self.state.draw_inner(
|
||||
id.id(),
|
||||
DrawInfo {
|
||||
@@ -190,19 +207,18 @@ impl<'a> Painter<'a> {
|
||||
parent_move: self.move_idx,
|
||||
region_node,
|
||||
mask: self.mask,
|
||||
frame,
|
||||
part: extent,
|
||||
rel_base,
|
||||
region,
|
||||
placed: place,
|
||||
asked: place,
|
||||
narrow,
|
||||
re_asked,
|
||||
px,
|
||||
},
|
||||
None,
|
||||
self.rsc,
|
||||
);
|
||||
let holds = self.in_parent(holds, extent, place, narrow, declared);
|
||||
let answer_holds = self.in_parent(answer_holds, extent, place, narrow, declared);
|
||||
let holds = self.in_parent(holds, region, place, declared);
|
||||
let answer_holds = self.in_parent(answer_holds, region, place, declared);
|
||||
match self.under.iter_mut().find(|(child, _)| *child == id.id()) {
|
||||
Some((_, kept)) => *kept = holds,
|
||||
None => self.under.push((id.id(), holds)),
|
||||
@@ -224,26 +240,47 @@ impl<'a> Painter<'a> {
|
||||
self.state.undraw_rec(id.id(), self.rsc);
|
||||
}
|
||||
|
||||
/// Puts a child asked about in this draw somewhere else in this
|
||||
/// widget's box: its answer, placed in this part instead. The drawing
|
||||
/// is re-expressed there rather than made again -- what a row does once
|
||||
/// it knows every slot, having measured each child from its cursor.
|
||||
pub fn place_at<W: ?Sized>(&mut self, id: &StrongWidget<W>, place: [Place; 2]) {
|
||||
debug_assert!(
|
||||
self.children.contains(&id.id()),
|
||||
"'{}' placed a child it did not ask about in this draw",
|
||||
self.label()
|
||||
);
|
||||
/// Puts a child in `place` of this widget's box, where that box is the
|
||||
/// answer the child already gave: the drawing is re-expressed there
|
||||
/// rather than made again -- what a row does once it knows every slot,
|
||||
/// having measured each child from its cursor.
|
||||
///
|
||||
/// A child this draw has not asked about, and one whose rel base this
|
||||
/// narrows, is asked here instead: there is no answer to re-express, or
|
||||
/// the question has changed. So a container that places every child the
|
||||
/// same way says it once, and which of the two happens is this widget's
|
||||
/// business rather than the caller's.
|
||||
pub fn place_at<'s, W: ?Sized>(
|
||||
&'s mut self,
|
||||
id: &'s StrongWidget<W>,
|
||||
place: impl Into<PlaceDesc>,
|
||||
) -> DrawResult<'s, 'a, W> {
|
||||
let place = self.state_rel_base(place.into());
|
||||
let states_rel_base = AXES
|
||||
.iter()
|
||||
.any(|&axis| place.axis(axis).stated_rel_base().is_some());
|
||||
if states_rel_base || !self.children.contains(&id.id()) {
|
||||
return self.widget_at(id, place);
|
||||
}
|
||||
let at = self.placing();
|
||||
self.state.place_in(id.id(), &at, place, self.rsc);
|
||||
let active = &self.state.active[&id.id()];
|
||||
let size = active.measured().unwrap_or(active.size);
|
||||
DrawResult {
|
||||
child: id,
|
||||
painter: self,
|
||||
size,
|
||||
// Read where it was asked; moving it is not a second answer.
|
||||
answer_holds: LayoutHolds::ANY,
|
||||
}
|
||||
}
|
||||
|
||||
/// This widget as the thing its children are placed within.
|
||||
fn placing(&self) -> Placing {
|
||||
Placing {
|
||||
id: self.id,
|
||||
extent: self.extent,
|
||||
frame: self.frame,
|
||||
region: self.region,
|
||||
rel_base: self.rel_base,
|
||||
window: self.window,
|
||||
depth: self.depth,
|
||||
move_idx: self.move_idx,
|
||||
@@ -252,7 +289,7 @@ impl<'a> Painter<'a> {
|
||||
}
|
||||
|
||||
/// What a widget's rules declare its lengths to be, which whoever draws
|
||||
/// it resolves into its frame. Reading them depends on nothing -- the box
|
||||
/// it resolves into its rel base. Reading them depends on nothing -- the box
|
||||
/// that comes of them is kept on the child, and `redraw` compares it
|
||||
/// there.
|
||||
fn declared_lens<W: ?Sized>(&self, id: &StrongWidget<W>) -> [Option<LayoutLen>; 2] {
|
||||
@@ -261,42 +298,38 @@ impl<'a> Painter<'a> {
|
||||
|
||||
/// What a child says its length is without being drawn, if it can say,
|
||||
/// as the length its draw would report: a fraction in it is resolved
|
||||
/// against this widget's frame, which is the frame a child asked with
|
||||
/// against this widget's rel base, which is the rel base a child asked with
|
||||
/// nothing narrowed gets. Asking counts as reading its size.
|
||||
pub fn size_hint<W: ?Sized>(&mut self, id: &StrongWidget<W>, axis: Axis) -> Option<LayoutLen> {
|
||||
let widgets = self.rsc.widgets();
|
||||
// A rule is the answer where there is one: it wins over whatever the
|
||||
// widget would draw, so it has to win over what the widget says too.
|
||||
let hint = widgets
|
||||
.size_rules(id.id())
|
||||
.axis(axis)
|
||||
.exact()
|
||||
.or_else(|| {
|
||||
widgets
|
||||
.get_dyn(id.id())
|
||||
.and_then(|widget| widget.size_hint(axis))
|
||||
})
|
||||
.map(|hint| hint.within_len(self.frame.axis(axis)));
|
||||
let hint = widgets.size_rules(id.id()).axis(axis).exact().or_else(|| {
|
||||
widgets
|
||||
.get_dyn(id.id())
|
||||
.and_then(|widget| widget.size_hint(axis))
|
||||
});
|
||||
let rel_base = self.rel_base.axis(axis);
|
||||
let resolved = hint.map(|hint| hint.within_len(rel_base));
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::hint_read(id.id(), self.id, axis, hint);
|
||||
match hint {
|
||||
Some(hint) => {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::HintHits);
|
||||
self.depend_on(id);
|
||||
// A fraction was just resolved against this frame, so what
|
||||
// this draw does with it is a function of the frame's length.
|
||||
if hint.rel != Rel::ZERO {
|
||||
self.frame_own_len[axis as usize] = Some(self.frame.axis(axis));
|
||||
}
|
||||
Some(hint)
|
||||
}
|
||||
None => {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::HintMisses);
|
||||
None
|
||||
{
|
||||
diag::hint_read(id.id(), self.id, axis, resolved);
|
||||
diag::bump(match resolved {
|
||||
Some(_) => Counter::HintHits,
|
||||
None => Counter::HintMisses,
|
||||
});
|
||||
}
|
||||
if let Some(hint) = hint {
|
||||
self.depend_on(id);
|
||||
// Resolving a fraction against this rel base makes this draw a
|
||||
// function of the rel base's length. The fraction to ask about is
|
||||
// the child's own: resolved against a rel base of pixels, none is
|
||||
// left to see it by.
|
||||
if hint.rel != Rel::ZERO {
|
||||
self.own.rel_base[axis as usize] = Some(rel_base);
|
||||
}
|
||||
}
|
||||
resolved
|
||||
}
|
||||
|
||||
fn depend_on<W: ?Sized>(&mut self, child: &StrongWidget<W>) {
|
||||
@@ -317,11 +350,11 @@ impl<'a> Painter<'a> {
|
||||
ui.text.render(buffer, attrs, width)
|
||||
}
|
||||
|
||||
/// Writes glyphs in the selected frame or extent coordinates.
|
||||
/// Writes glyphs in the selected rel base or region coordinates.
|
||||
// TODO: merge the text methods into the primitive ones.
|
||||
pub fn glyphs(&mut self, text: &RenderedText, origin: UiRegion) {
|
||||
// Glyph offsets and sizes are pixels, which compose additively.
|
||||
// Only the shared origin needs composing through the extent.
|
||||
// Only the shared origin needs composing through the region.
|
||||
let resolved = self.resolve(origin);
|
||||
let kind = self.rsc.ui_mut().primitives.kind::<GlyphPrimitive>();
|
||||
for glyph in text.glyphs.iter() {
|
||||
@@ -353,25 +386,25 @@ impl<'a> Painter<'a> {
|
||||
}
|
||||
|
||||
/// The symbolic length of this widget's own box along one axis, in the
|
||||
/// lengths of its frame that it places its children in. Reading it pins
|
||||
/// lengths of its rel base that it places its children in. Reading it pins
|
||||
/// the drawing to that length -- and to nothing about where the box
|
||||
/// starts, which is what lets a container move without being drawn
|
||||
/// again. One axis at a time, because a container that divides one axis
|
||||
/// holds for any length of the other.
|
||||
pub fn extent_len(&mut self, axis: Axis) -> Len {
|
||||
let len = self.extent.axis(axis).len();
|
||||
self.extent_len[axis as usize] = Some(len);
|
||||
pub fn region_len(&mut self, axis: Axis) -> Len {
|
||||
let len = self.region.axis(axis).len();
|
||||
self.own.region_len[axis as usize] = Some(len);
|
||||
len
|
||||
}
|
||||
|
||||
/// The symbolic length of this widget's frame along one axis: what a
|
||||
/// fraction it or anything under it declares is a fraction of. A
|
||||
/// container reads it to hand a length of it down -- padding, which
|
||||
/// takes its pixels off. Reading it pins the drawing to that frame, the
|
||||
/// way [`Self::extent_len`] pins it to the box.
|
||||
pub fn frame_len(&mut self, axis: Axis) -> Len {
|
||||
let len = self.frame.axis(axis);
|
||||
self.frame_own_len[axis as usize] = Some(len);
|
||||
/// This widget's rel base along one axis: what a fraction it or anything
|
||||
/// under it declares or reports is a fraction of. A container reads it
|
||||
/// to hand a length of it down -- padding, which takes its pixels off.
|
||||
/// Reading it pins the drawing to that rel base, the way
|
||||
/// [`Self::region_len`] pins it to the box.
|
||||
pub fn rel_base(&mut self, axis: Axis) -> Len {
|
||||
let len = self.rel_base.axis(axis);
|
||||
self.own.rel_base[axis as usize] = Some(len);
|
||||
len
|
||||
}
|
||||
|
||||
@@ -409,13 +442,13 @@ impl<'a> Painter<'a> {
|
||||
/// One axis of this widget's own box in pixels. Prefer this to
|
||||
/// [`Self::px_size`] when the other axis cannot affect the drawing.
|
||||
pub fn px_len(&mut self, axis: Axis) -> Px {
|
||||
let part = self.extent.axis(axis).len();
|
||||
let len = part.to_px(self.window.axis(axis));
|
||||
let own = &mut self.extent_own[axis as usize];
|
||||
let len = self.region.axis(axis).len();
|
||||
let px = len.to_px(self.window.axis(axis));
|
||||
let own = &mut self.own.region[axis as usize];
|
||||
if *own == Holds::ANY {
|
||||
*own = Holds::at(len);
|
||||
*own = Holds::at(px);
|
||||
}
|
||||
len
|
||||
px
|
||||
}
|
||||
|
||||
/// The lengths of this widget's own box on `axis` that what it is drawing
|
||||
@@ -423,15 +456,15 @@ impl<'a> Painter<'a> {
|
||||
/// of the box, and the same reported size. A widget that read its length
|
||||
/// in pixels holds for that one alone until it says otherwise.
|
||||
pub fn holds(&mut self, axis: Axis, holds: impl Into<Holds>) {
|
||||
let part = self.extent.axis(axis).len();
|
||||
let len = self.region.axis(axis).len();
|
||||
let holds = holds.into();
|
||||
debug_assert!(
|
||||
holds.contains(part.to_px(self.window.axis(axis))),
|
||||
holds.contains(len.to_px(self.window.axis(axis))),
|
||||
"'{}' ({:?}) says its drawing holds for lengths that leave out its own box",
|
||||
self.label(),
|
||||
self.id
|
||||
);
|
||||
self.extent_own[axis as usize] = holds;
|
||||
self.own.region[axis as usize] = holds;
|
||||
}
|
||||
|
||||
/// A window length in pixels, which is what every length in layout is
|
||||
@@ -441,7 +474,7 @@ impl<'a> Painter<'a> {
|
||||
pub fn to_px(&mut self, len: Len, axis: Axis) -> Px {
|
||||
let window = self.window.axis(axis);
|
||||
if len.rel != Rel::ZERO {
|
||||
let own = &mut self.window_own[axis as usize];
|
||||
let own = &mut self.own.window[axis as usize];
|
||||
if *own == Holds::ANY {
|
||||
*own = Holds::at(window);
|
||||
}
|
||||
@@ -461,7 +494,7 @@ impl<'a> Painter<'a> {
|
||||
self.label(),
|
||||
self.id
|
||||
);
|
||||
self.window_own[axis as usize] = holds;
|
||||
self.own.window[axis as usize] = holds;
|
||||
}
|
||||
|
||||
pub fn text_data(&mut self) -> &mut TextData {
|
||||
@@ -554,22 +587,21 @@ impl PrimitiveLike for &TextureHandle {
|
||||
/// only what the child was asked with.
|
||||
impl Painter<'_> {
|
||||
/// Window ranges are already about the one unit and combine directly.
|
||||
/// A frame pin becomes this widget's own frame wherever a length of it
|
||||
/// A rel base pin becomes this widget's own rel base wherever a length of it
|
||||
/// is what reached the child; where only pixels did, no length of this
|
||||
/// frame can change the child's and the pin stops here.
|
||||
/// rel base can change the child's and the pin stops here.
|
||||
///
|
||||
/// Extent validity maps back through the part of this widget's box,
|
||||
/// A child's validity maps back through the part of this widget's box,
|
||||
/// where the box the child was asked in is that part; a declared length
|
||||
/// places the box inside the part instead, and then only that length
|
||||
/// reaches the child. A narrowed frame is not one of these: it decides
|
||||
/// reaches the child. A narrowed rel base is not one of these: it decides
|
||||
/// what fractions under the child mean and leaves the box the part it
|
||||
/// was given.
|
||||
fn in_parent(
|
||||
&self,
|
||||
holds: LayoutHolds,
|
||||
extent: UiRegion,
|
||||
place: [Place; 2],
|
||||
narrow: [Option<Len>; 2],
|
||||
region: UiRegion,
|
||||
place: PlaceDesc,
|
||||
declared: [Option<LayoutLen>; 2],
|
||||
) -> LayoutHolds {
|
||||
let mut result = LayoutHolds::ANY;
|
||||
@@ -578,52 +610,34 @@ impl Painter<'_> {
|
||||
// Every read became pixels against the window, so a range on
|
||||
// it is already in this widget's terms.
|
||||
result.window[n] = holds.window[n];
|
||||
let reaches = narrow[n].is_none()
|
||||
&& !matches!(place[n].part(), Part::Sized(_))
|
||||
let at = *place.axis(axis);
|
||||
let reaches = at.stated_rel_base().is_none()
|
||||
&& !at.is_sized()
|
||||
&& declared[n].is_none_or(|len| len.rel != Rel::ZERO);
|
||||
result.frame_len[n] = holds.frame_len[n].and(reaches.then(|| self.frame.axis(axis)));
|
||||
match (place[n].part(), declared[n].is_some()) {
|
||||
// Its box is this widget's own, or a part of it in that
|
||||
// box's own lengths: so what it holds for is a range on this
|
||||
// widget's own box, which is what lets that box move without
|
||||
// a redraw. A length it pinned is this widget's length
|
||||
// wherever the part is the whole of it, and pins the same
|
||||
// way.
|
||||
(Part::All, false) => {
|
||||
result.extent[n] = holds.extent[n];
|
||||
result.extent_len[n] = holds.extent_len[n];
|
||||
}
|
||||
// Its box is this widget's own less the inset. Where that is
|
||||
// pixels, its box is exactly that many shorter in any window,
|
||||
// so what it holds for is a range on this widget's box moved
|
||||
// by them, and a length it pinned is this widget's length
|
||||
// less them. An inset with a fraction in it is a different
|
||||
// number of pixels in each window, and taking it off a length
|
||||
// rounds once more than taking it off pixels does: there the
|
||||
// child's box is a fixed expression of this one, so this
|
||||
// widget's length is pinned and the range goes on the window
|
||||
// through the child's box, the way a slot's does.
|
||||
(Part::Inset { lead, trail }, false) => {
|
||||
let inset = lead + trail;
|
||||
match inset.rel == Rel::ZERO {
|
||||
true => {
|
||||
result.extent[n] = holds.extent[n].longer_by(inset.px);
|
||||
result.extent_len[n] = holds.extent_len[n].map(|pinned| pinned + inset);
|
||||
}
|
||||
false => {
|
||||
result.window[n] = result.window[n]
|
||||
.and(holds.extent[n].through(extent.axis(axis).len()));
|
||||
result.extent_len[n] = Some(self.extent.axis(axis).len());
|
||||
}
|
||||
}
|
||||
result.rel_base[n] = holds.rel_base[n].and(reaches.then(|| self.rel_base.axis(axis)));
|
||||
match (at.within_span(), declared[n].is_some()) {
|
||||
// Its box is a part of this widget's own box, in that box's
|
||||
// own lengths, so what it holds for maps back through that
|
||||
// part into a range on this widget's box. A length it pinned
|
||||
// is this widget's length less the part's pixels where the
|
||||
// part is the whole of the box less pixels, which is the one
|
||||
// shape that inverts exactly; any other part pins this
|
||||
// widget's own length.
|
||||
(Some(span), false) => {
|
||||
let part_len = span.len();
|
||||
result.region[n] = holds.region[n].through(part_len);
|
||||
result.region_len[n] = holds.region_len[n].map(|pinned| match part_len.rel {
|
||||
Rel::ONE => pinned - Len::from_parts(Rel::ZERO, part_len.px),
|
||||
_ => self.region.axis(axis).len(),
|
||||
});
|
||||
}
|
||||
// Its box is a length this widget decided, from its own
|
||||
// frame or from a sibling's answer: no length of this
|
||||
// rel base or from a sibling's answer: no length of this
|
||||
// widget's box reaches it, so what it holds for is a range
|
||||
// on the window and none of it on that box.
|
||||
_ => {
|
||||
result.window[n] =
|
||||
result.window[n].and(holds.extent[n].through(extent.axis(axis).len()));
|
||||
result.window[n].and(holds.region[n].through(region.axis(axis).len()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -666,23 +680,23 @@ pub(crate) fn fills(reported: LayoutLen, declared: Option<LayoutLen>, decided: b
|
||||
/// it reported, on the side of the part its alignment says, and the whole
|
||||
/// part wherever the answer fills it.
|
||||
///
|
||||
/// The length it reported is a length of its frame, and the part is one too,
|
||||
/// The length it reported is a length of its rel base, and the part is one too,
|
||||
/// so this takes one from the other rather than composing it into the part.
|
||||
/// That is what makes a fraction the same fraction wherever the part it is
|
||||
/// placed in sits and however long it is -- the fraction is resolved once,
|
||||
/// here, against the frame it was reported of.
|
||||
pub(crate) fn placed_extent(
|
||||
part: UiRegion,
|
||||
/// here, against the rel base it was reported of.
|
||||
pub(crate) fn placement(
|
||||
region: UiRegion,
|
||||
size: Size,
|
||||
declared: [Option<LayoutLen>; 2],
|
||||
fill: [bool; 2],
|
||||
place: PlaceDesc,
|
||||
align: RegionAlign,
|
||||
) -> UiRegion {
|
||||
let mut placed = part;
|
||||
let mut placed = region;
|
||||
for axis in AXES {
|
||||
let n = axis as usize;
|
||||
let reported = size.axis(axis);
|
||||
if fills(reported, declared[n], fill[n]) {
|
||||
if fills(reported, declared[n], place.axis(axis).does_fill()) {
|
||||
continue;
|
||||
}
|
||||
let len = Len::from_parts(reported.rel, reported.px);
|
||||
@@ -693,58 +707,41 @@ pub(crate) fn placed_extent(
|
||||
placed
|
||||
}
|
||||
|
||||
/// The frame length and the box a child is asked in, in the coordinates the
|
||||
/// The rel base length and the box a child is asked in, in the coordinates the
|
||||
/// widget asking draws in.
|
||||
///
|
||||
/// `own` is that widget's own box, and `place` what of it the child is
|
||||
/// given. `narrow` is a frame the container decided for the child -- a row's
|
||||
/// slot, or padding's frame less its pixels -- and [`Part::Sized`] one a
|
||||
/// sibling's answer decided; both are window lengths, like every other
|
||||
/// length here, since a slot of a row is not a fraction of anything the row
|
||||
/// can name. The child's declaration is a fraction of whichever reached it,
|
||||
/// and is the only one of the three that also places the box: a box the
|
||||
/// caller decided is what `place` names.
|
||||
pub(crate) fn frame_and_extent(
|
||||
/// given, including any rel base it states -- a row's slot, or padding's rel
|
||||
/// base less its pixels. That is a window length, like every other length
|
||||
/// here, since a slot of a row is not a fraction of anything the row can
|
||||
/// name. The child's declaration is a fraction of whichever reached it, and
|
||||
/// is the only one that also places the box: a box the caller decided is
|
||||
/// what `place` names.
|
||||
pub(crate) fn rel_base_and_region(
|
||||
own: UiRegion,
|
||||
parent_frame: UiVec2,
|
||||
place: [Place; 2],
|
||||
narrow: [Option<Len>; 2],
|
||||
parent_rel_base: UiVec2,
|
||||
place: PlaceDesc,
|
||||
declared: [Option<LayoutLen>; 2],
|
||||
align: RegionAlign,
|
||||
) -> (UiVec2, UiRegion) {
|
||||
let part = part_of(own, place, align);
|
||||
let mut frame = parent_frame;
|
||||
let mut extent = part;
|
||||
let given = place.of(own, align);
|
||||
let mut rel_base = parent_rel_base;
|
||||
let mut region = given;
|
||||
for axis in AXES {
|
||||
let n = axis as usize;
|
||||
let sized = match place[n].part() {
|
||||
Part::Sized(len) => Some(len),
|
||||
_ => None,
|
||||
};
|
||||
let base = sized
|
||||
.or(narrow[n])
|
||||
.unwrap_or_else(|| parent_frame.axis(axis));
|
||||
let base = place
|
||||
.axis(axis)
|
||||
.stated_rel_base()
|
||||
.unwrap_or_else(|| parent_rel_base.axis(axis));
|
||||
let len = declared[n]
|
||||
.map(|len| Len::from_parts(len.rel, len.px).within_len(base))
|
||||
.unwrap_or(base);
|
||||
*frame.axis_mut(axis) = len;
|
||||
*rel_base.axis_mut(axis) = len;
|
||||
if declared[n].is_some() {
|
||||
let slot = part.axis(axis);
|
||||
let slot = given.axis(axis);
|
||||
let start = slot.start + (slot.len() - len).scale(align.axis(axis).rel());
|
||||
*extent.axis_mut(axis) = UiSpan::new(start, start + len);
|
||||
*region.axis_mut(axis) = UiSpan::new(start, start + len);
|
||||
}
|
||||
}
|
||||
(frame, extent)
|
||||
}
|
||||
|
||||
/// The part of a widget's own box a `place` names, in the coordinates that
|
||||
/// box is in.
|
||||
fn part_of(extent: UiRegion, place: [Place; 2], align: RegionAlign) -> UiRegion {
|
||||
let mut part = extent;
|
||||
for axis in AXES {
|
||||
*part.axis_mut(axis) = place[axis as usize]
|
||||
.part()
|
||||
.of(*extent.axis(axis), align.axis(axis));
|
||||
}
|
||||
part
|
||||
(rel_base, region)
|
||||
}
|
||||
+226
-51
@@ -1,67 +1,242 @@
|
||||
use crate::{AxisAlign, Len, PrimitiveHandle, UiRegion, UiSpan};
|
||||
use crate::{Axis, AxisAlign, Len, PrimitiveHandle, RegionAlign, UiRegion, UiSpan};
|
||||
|
||||
/// What of a widget's own box a child is given, along one axis.
|
||||
/// How a child's region along one axis comes from the region of the widget
|
||||
/// asking, and what its fractions are of.
|
||||
///
|
||||
/// The three ways of saying a region are the three the geometry already has:
|
||||
/// a span composed into the caller's box, a span shifted to where that box
|
||||
/// starts, and a length placed in it by alignment. Which one is meant cannot
|
||||
/// be read off the numbers, since two of them take the same span and apply
|
||||
/// it differently, so it is said here.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum Part {
|
||||
/// The whole of it.
|
||||
All,
|
||||
/// Window lengths from where the box starts, which is what a container
|
||||
/// dividing room among its children speaks: a child's report is a window
|
||||
/// length, so the cursor that sums those reports is one too. A moved box
|
||||
/// re-places every child by re-adding its start, exactly. A fraction
|
||||
/// here is a fraction of the window and not of the box -- the whole of a
|
||||
/// box is [`Self::All`], not a `rel(1.0)` span.
|
||||
From(UiSpan),
|
||||
/// The box less a window length at each end, which is what a container
|
||||
/// that insets one speaks -- padding, or a row asking a child in the
|
||||
/// room left from its cursor. Neither end names the box's length, so a
|
||||
/// container can say "from here to my end" without reading how long it
|
||||
/// is, and a box chosen from its own answer does not feed back into the
|
||||
/// answer.
|
||||
Inset { lead: Len, trail: Len },
|
||||
/// A box of this length, wherever in the parent's box the child's own
|
||||
/// alignment puts it, and that same length as its frame. Unlike `From`,
|
||||
/// it is a length decided from above rather than a place along a
|
||||
/// container's cursor -- what a stack's sizing child decides for the
|
||||
/// rest.
|
||||
pub struct PlaceDescAxis {
|
||||
span: PlaceSpan,
|
||||
fills: bool,
|
||||
rel_base: RelBase,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
enum PlaceSpan {
|
||||
Within(UiSpan),
|
||||
Shifted(UiSpan),
|
||||
Sized(Len),
|
||||
}
|
||||
|
||||
impl Part {
|
||||
/// Where it lands in the coordinates `extent` is in.
|
||||
pub(crate) fn of(self, extent: UiSpan, align: AxisAlign) -> UiSpan {
|
||||
match self {
|
||||
Self::All => extent,
|
||||
Self::From(span) => UiSpan::new(extent.start + span.start, extent.start + span.end),
|
||||
Self::Inset { lead, trail } => UiSpan::new(extent.start + lead, extent.end - trail),
|
||||
Self::Sized(len) => {
|
||||
let start = extent.start + (extent.len() - len).scale(align.rel());
|
||||
/// What a child's fractions are of, where the caller has not named a length.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
enum RelBase {
|
||||
/// The caller's own, unchanged.
|
||||
Inherit,
|
||||
/// The caller's own, narrowed the way the region is.
|
||||
WithRegion,
|
||||
/// This length of the window.
|
||||
Len(Len),
|
||||
}
|
||||
|
||||
impl PlaceDescAxis {
|
||||
/// The whole of the caller's box.
|
||||
pub const WHOLE: Self = Self::within(UiSpan::FULL);
|
||||
|
||||
/// `span` composed into the caller's own box, so it moves and scales
|
||||
/// with it: [`UiSpan::within`], which is what a container that insets
|
||||
/// one speaks. Taking eleven pixels off the end needs no length, where
|
||||
/// saying the same thing in window lengths would make the container read
|
||||
/// its own box -- and a box chosen from its own answer then feeds back
|
||||
/// into the answer.
|
||||
///
|
||||
/// The child's rel base is narrowed the same way, so padding takes its
|
||||
/// pixels off both and `rel(1)` under it fills the caller rather than
|
||||
/// overflowing it.
|
||||
pub const fn within(span: UiSpan) -> Self {
|
||||
Self {
|
||||
span: PlaceSpan::Within(span),
|
||||
fills: false,
|
||||
rel_base: RelBase::WithRegion,
|
||||
}
|
||||
}
|
||||
|
||||
/// `span` shifted to where the caller's own box starts: window lengths
|
||||
/// along a cursor, which is what a container dividing room among its
|
||||
/// children speaks. A child's report is a window length, so the cursor
|
||||
/// that sums those reports is one too, and a moved box re-places every
|
||||
/// child by re-adding its start, exactly.
|
||||
///
|
||||
/// The child's rel base passes through: how far along the cursor a child
|
||||
/// sits says nothing about what a fraction under it is of.
|
||||
pub const fn shifted(span: UiSpan) -> Self {
|
||||
Self {
|
||||
span: PlaceSpan::Shifted(span),
|
||||
fills: false,
|
||||
rel_base: RelBase::Inherit,
|
||||
}
|
||||
}
|
||||
|
||||
/// A box this long, placed in the caller's own by the child's alignment:
|
||||
/// the rule that places an answer, with the length given from above
|
||||
/// rather than reported. What a stack's sizing child decides for the
|
||||
/// rest. It is the child's rel base too.
|
||||
pub const fn sized(len: Len) -> Self {
|
||||
Self {
|
||||
span: PlaceSpan::Sized(len),
|
||||
fills: false,
|
||||
rel_base: RelBase::Len(len),
|
||||
}
|
||||
}
|
||||
|
||||
/// This region is the child's placement: its answer is not placed inside
|
||||
/// it again. A container uses it where it hands back exactly what the
|
||||
/// child asked for -- a row placing a child at the length it reported.
|
||||
pub const fn fills(mut self) -> Self {
|
||||
self.fills = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// What the child's fractions are of, as a length of the window: a
|
||||
/// resolved share, or a box a sibling's answer decided.
|
||||
pub const fn rel_base(mut self, len: Len) -> Self {
|
||||
self.rel_base = RelBase::Len(len);
|
||||
self
|
||||
}
|
||||
|
||||
/// Whether the region is the placement outright, rather than a box the
|
||||
/// answer is placed inside.
|
||||
pub(crate) const fn does_fill(self) -> bool {
|
||||
self.fills
|
||||
}
|
||||
|
||||
/// Where it lands in the coordinates `own` is in.
|
||||
pub(crate) fn of(self, own: UiSpan, align: AxisAlign) -> UiSpan {
|
||||
match self.span {
|
||||
PlaceSpan::Within(span) => span.within(&own),
|
||||
PlaceSpan::Shifted(mut span) => {
|
||||
span.shift(own.start);
|
||||
span
|
||||
}
|
||||
PlaceSpan::Sized(len) => {
|
||||
let start = own.start + (own.len() - len).scale(align.rel());
|
||||
UiSpan::new(start, start + len)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Where a child goes along one axis, as a part of this widget's box.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum Place {
|
||||
/// The child's answer, aligned inside the part by the child's alignment.
|
||||
Within(Part),
|
||||
/// Exactly the part; the answer is not placed inside it again.
|
||||
Fill(Part),
|
||||
}
|
||||
|
||||
impl Place {
|
||||
pub(crate) fn part(self) -> Part {
|
||||
match self {
|
||||
Self::Within(part) | Self::Fill(part) => part,
|
||||
/// The child's rel base, where this says one outright. `None` forwards
|
||||
/// the caller's own, and [`RelBase::WithRegion`] is resolved by whoever
|
||||
/// can read that rel base, so it does not reach here.
|
||||
pub(crate) const fn stated_rel_base(self) -> Option<Len> {
|
||||
match self.rel_base {
|
||||
RelBase::Len(len) => Some(len),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the part is the drawing's box outright, rather than the box
|
||||
/// the answer is placed inside.
|
||||
pub(crate) fn fills(self) -> bool {
|
||||
matches!(self, Self::Fill(_))
|
||||
/// The length this narrows the caller's rel base by, where it does.
|
||||
/// `None` leaves that rel base alone, and reading it is then a
|
||||
/// dependency the caller does not take.
|
||||
pub(crate) const fn narrows_rel_base(self) -> Option<UiSpan> {
|
||||
match (self.rel_base, self.span) {
|
||||
(RelBase::WithRegion, PlaceSpan::Within(span)) => Some(span),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The span it composes into the caller's box, where that is what it
|
||||
/// does: the one case whose validity maps back through the part.
|
||||
pub(crate) const fn within_span(self) -> Option<UiSpan> {
|
||||
match self.span {
|
||||
PlaceSpan::Within(span) => Some(span),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the caller decided this length rather than a place along its
|
||||
/// own box, which is what stops its length reaching the child at all.
|
||||
pub(crate) const fn is_sized(self) -> bool {
|
||||
matches!(self.span, PlaceSpan::Sized(_))
|
||||
}
|
||||
|
||||
/// The same, with its rel base stated outright.
|
||||
pub(crate) const fn with_rel_base(mut self, len: Option<Len>) -> Self {
|
||||
self.rel_base = match len {
|
||||
Some(len) => RelBase::Len(len),
|
||||
None => RelBase::Inherit,
|
||||
};
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Where a child is asked, on both axes. A [`UiRegion`] converts into the
|
||||
/// common case: that box of the caller's own, the answer placed inside it.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct PlaceDesc {
|
||||
pub x: PlaceDescAxis,
|
||||
pub y: PlaceDescAxis,
|
||||
}
|
||||
|
||||
impl PlaceDesc {
|
||||
/// The whole of the caller's box, on both axes.
|
||||
pub const WHOLE: Self = Self::splat(PlaceDescAxis::WHOLE);
|
||||
|
||||
pub const fn new(x: PlaceDescAxis, y: PlaceDescAxis) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
|
||||
/// The same on both axes.
|
||||
pub const fn splat(place: PlaceDescAxis) -> Self {
|
||||
Self { x: place, y: place }
|
||||
}
|
||||
|
||||
/// `aligned` on `axis` and `ortho` on the other, which is how a
|
||||
/// container that divides one axis says what it is doing.
|
||||
pub fn from_axis(axis: Axis, aligned: PlaceDescAxis, ortho: PlaceDescAxis) -> Self {
|
||||
match axis {
|
||||
Axis::X => Self::new(aligned, ortho),
|
||||
Axis::Y => Self::new(ortho, aligned),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn axis(&self, axis: Axis) -> &PlaceDescAxis {
|
||||
match axis {
|
||||
Axis::X => &self.x,
|
||||
Axis::Y => &self.y,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn axis_mut(&mut self, axis: Axis) -> &mut PlaceDescAxis {
|
||||
match axis {
|
||||
Axis::X => &mut self.x,
|
||||
Axis::Y => &mut self.y,
|
||||
}
|
||||
}
|
||||
|
||||
/// Both regions are the child's placement. See [`PlaceDescAxis::fills`].
|
||||
pub const fn fills(self) -> Self {
|
||||
Self::new(self.x.fills(), self.y.fills())
|
||||
}
|
||||
|
||||
/// The child's rel base on one axis. See [`PlaceDescAxis::rel_base`].
|
||||
pub const fn rel_base(mut self, axis: Axis, len: Len) -> Self {
|
||||
*self.axis_mut(axis) = self.axis(axis).rel_base(len);
|
||||
self
|
||||
}
|
||||
|
||||
/// The box each axis names, in the coordinates `own` is in.
|
||||
pub(crate) fn of(self, own: UiRegion, align: RegionAlign) -> UiRegion {
|
||||
UiRegion::new(self.x.of(own.x, align.x), self.y.of(own.y, align.y))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UiRegion> for PlaceDesc {
|
||||
fn from(region: UiRegion) -> Self {
|
||||
Self::new(
|
||||
PlaceDescAxis::within(region.x),
|
||||
PlaceDescAxis::within(region.y),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PlaceDescAxis> for PlaceDesc {
|
||||
fn from(place: PlaceDescAxis) -> Self {
|
||||
Self::splat(place)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+163
-173
@@ -1,10 +1,10 @@
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
use crate::layout_diagnostics::{self as diag, Counter, ReuseOutcome, TimerKind};
|
||||
use crate::ui::painter::{declared_lens, frame_and_extent, placed_extent};
|
||||
use crate::ui::painter::{declared_lens, placement, rel_base_and_region};
|
||||
use crate::{
|
||||
ActiveData, Axis, DrawLayers, Holds, IdLike, LayoutHolds, LayoutLen, Len, MaskIdx, MoveIdx,
|
||||
Moves, Painter, Part, PixelRegion, Place, PxVec2, Rel, Size, StrongWidget, UiRegion, UiRsc,
|
||||
UiSpan, UiVec2, Weight, WidgetId, Widgets,
|
||||
ActiveData, Axis, DrawLayers, IdLike, LayoutHolds, LayoutLen, Len, MaskIdx, MoveIdx, Moves,
|
||||
Painter, PixelRegion, PlaceDesc, PxVec2, Rel, Size, StrongWidget, UiRegion, UiRsc, UiSpan,
|
||||
UiVec2, Weight, WidgetId, Widgets,
|
||||
util::{HashMap, Vec2},
|
||||
};
|
||||
|
||||
@@ -22,38 +22,27 @@ pub(super) struct DrawInfo {
|
||||
pub mask: MaskIdx,
|
||||
/// What a fraction declared or reported under this widget is a fraction
|
||||
/// of, as a length of the window.
|
||||
pub frame: UiVec2,
|
||||
pub rel_base: UiVec2,
|
||||
/// The box the widget is asked in, in its parent region node's
|
||||
/// coordinates.
|
||||
pub part: UiRegion,
|
||||
pub region: UiRegion,
|
||||
/// Where the widget is put, and where it was asked, as parts of the
|
||||
/// parent's box. See [`Place`]. The two are one ask's place until the
|
||||
/// parent's box. See [`PlaceDesc`]. The two are one ask's place until the
|
||||
/// parent puts the answer somewhere else.
|
||||
pub placed: [Place; 2],
|
||||
pub asked: [Place; 2],
|
||||
/// A frame the parent decided for it on each axis, as a length of the
|
||||
/// window, which the widget's own declaration is a fraction of.
|
||||
pub narrow: [Option<Len>; 2],
|
||||
pub placed: PlaceDesc,
|
||||
pub asked: PlaceDesc,
|
||||
/// Whether the parent already asked about this widget in this draw.
|
||||
pub re_asked: bool,
|
||||
/// The frame in pixels, resolved once against the window.
|
||||
/// The rel base in pixels, resolved once against the window.
|
||||
pub px: PxVec2,
|
||||
}
|
||||
|
||||
impl DrawInfo {
|
||||
/// The axes where the part is the drawing's box outright, which are the
|
||||
/// axes the answer is not placed inside it again.
|
||||
fn fill(&self) -> [bool; 2] {
|
||||
self.placed.map(Place::fills)
|
||||
}
|
||||
}
|
||||
|
||||
/// What a widget's children are placed in: its own box, the coordinates its
|
||||
/// drawing is in, and what else one ask of a child is decided from.
|
||||
pub(super) struct Placing {
|
||||
pub id: WidgetId,
|
||||
pub extent: UiRegion,
|
||||
pub frame: UiVec2,
|
||||
pub region: UiRegion,
|
||||
pub rel_base: UiVec2,
|
||||
pub window: PxVec2,
|
||||
pub depth: usize,
|
||||
pub move_idx: MoveIdx,
|
||||
@@ -120,13 +109,13 @@ impl UiRenderState {
|
||||
let Some(root) = self.old_root else { return };
|
||||
let stands = self.active.get(&root).is_some_and(|active| {
|
||||
// Nothing above the root chose anything, so the box it was first
|
||||
// asked about is the whole of its frame. Both its answer and its
|
||||
// asked about is the whole of its rel base. Both its answer and its
|
||||
// drawing have to stand in the new window, since nothing above
|
||||
// it will ask either again.
|
||||
let answer = active
|
||||
.answer
|
||||
.is_some_and(|(_, holds)| holds.contains(size, active.frame, active.part));
|
||||
answer && active.holds.contains(size, active.frame, active.part)
|
||||
.is_some_and(|(_, holds)| holds.contains(size, active.rel_base, active.region));
|
||||
answer && active.holds.contains(size, active.rel_base, active.region)
|
||||
});
|
||||
if !stands {
|
||||
widgets.needs_redraw.insert(root);
|
||||
@@ -134,9 +123,9 @@ impl UiRenderState {
|
||||
}
|
||||
|
||||
/// The root is asked about in the output. Its own rules narrow both its
|
||||
/// frame and box; nothing above it chose a different one.
|
||||
fn root_info(&self, frame: UiVec2, extent: UiRegion) -> DrawInfo {
|
||||
let px = frame.to_px(self.output_size);
|
||||
/// rel base and box; nothing above it chose a different one.
|
||||
fn root_info(&self, rel_base: UiVec2, region: UiRegion) -> DrawInfo {
|
||||
let px = rel_base.to_px(self.output_size);
|
||||
DrawInfo {
|
||||
layer: 0,
|
||||
parent: None,
|
||||
@@ -144,11 +133,10 @@ impl UiRenderState {
|
||||
parent_move: MoveIdx::NONE,
|
||||
region_node: false,
|
||||
mask: MaskIdx::NONE,
|
||||
frame,
|
||||
part: extent,
|
||||
placed: [Place::Within(Part::All); 2],
|
||||
asked: [Place::Within(Part::All); 2],
|
||||
narrow: [None; 2],
|
||||
rel_base,
|
||||
region,
|
||||
placed: PlaceDesc::WHOLE,
|
||||
asked: PlaceDesc::WHOLE,
|
||||
re_asked: false,
|
||||
px,
|
||||
}
|
||||
@@ -196,21 +184,20 @@ impl UiRenderState {
|
||||
let _layout = diag::timer(TimerKind::FullLayout);
|
||||
self.clear(rsc);
|
||||
if let Some(id) = root {
|
||||
let (frame, extent) = Self::root_layout(id.id(), rsc.widgets());
|
||||
let info = self.root_info(frame, extent);
|
||||
let (rel_base, region) = Self::root_layout(id.id(), rsc.widgets());
|
||||
let info = self.root_info(rel_base, region);
|
||||
self.draw_inner(id.id(), info, None, rsc);
|
||||
}
|
||||
}
|
||||
|
||||
/// The root's frame and box: the window, taken in by the root's own
|
||||
/// The root's rel base and box: the window, taken in by the root's own
|
||||
/// rules. Nothing above it narrowed anything or chose where it goes, so
|
||||
/// its declaration is the whole of what decides either.
|
||||
fn root_layout(id: WidgetId, widgets: &Widgets) -> (UiVec2, UiRegion) {
|
||||
frame_and_extent(
|
||||
rel_base_and_region(
|
||||
UiRegion::FULL,
|
||||
UiVec2::FULL_SIZE,
|
||||
[Place::Within(Part::All); 2],
|
||||
[None; 2],
|
||||
PlaceDesc::WHOLE,
|
||||
declared_lens(widgets, id),
|
||||
widgets.alignment(id),
|
||||
)
|
||||
@@ -223,11 +210,15 @@ impl UiRenderState {
|
||||
mut old: Option<ActiveData>,
|
||||
rsc: &mut dyn UiRsc,
|
||||
) -> (Size, LayoutHolds, LayoutHolds) {
|
||||
let part = info.part;
|
||||
let old_parent = old
|
||||
.as_ref()
|
||||
.or_else(|| self.active.get(&id))
|
||||
.and_then(|a| a.parent);
|
||||
let region = info.region;
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
{
|
||||
diag::bump(Counter::DrawRequests);
|
||||
diag::draw_request(id, info.parent, part, info.px, info.region_node);
|
||||
diag::draw_request(id, info.parent, region, info.px, info.region_node);
|
||||
}
|
||||
let align = rsc.widgets().alignment(id);
|
||||
let declared = declared_lens(rsc.widgets(), id);
|
||||
@@ -241,46 +232,43 @@ impl UiRenderState {
|
||||
// an answer is kept only with the drawing that gave it, and both
|
||||
// have to hold for the box asked about.
|
||||
let reused = (!stale)
|
||||
.then(|| self.retained_answer(id, part, info))
|
||||
.then(|| self.retained_answer(id, region, info))
|
||||
.flatten()
|
||||
.and_then(|answer| {
|
||||
let extent = placed_extent(part, answer.0, declared, info.fill(), align);
|
||||
self.try_reuse(id, part, extent, info, rsc).map(|()| answer)
|
||||
let placed = placement(region, answer.0, declared, info.placed, align);
|
||||
self.try_reuse(id, region, placed, info, rsc)
|
||||
.map(|()| answer)
|
||||
});
|
||||
let answer = reused.unwrap_or_else(|| {
|
||||
if old.is_none() {
|
||||
old = self.remove(id, false, rsc);
|
||||
}
|
||||
let answer = self.draw_at(id, part, info, old.take(), rsc);
|
||||
let answer = self.draw_at(id, region, info, old.take(), rsc);
|
||||
// Where the drawing goes: the part its parent gave it, with the
|
||||
// answer placed inside that part on any axis the parent left
|
||||
// open.
|
||||
let extent = placed_extent(part, answer.0, declared, info.fill(), align);
|
||||
if extent != part {
|
||||
self.relocate(id, extent, info, rsc);
|
||||
let placed = placement(region, answer.0, declared, info.placed, align);
|
||||
if placed != region {
|
||||
self.relocate(id, placed, info, rsc);
|
||||
}
|
||||
answer
|
||||
});
|
||||
|
||||
let drawing_holds = self.active[&id].holds;
|
||||
let active = self.active.get_mut(&id).unwrap();
|
||||
// Whoever asked owns how the boxes were reached: the frame it stated,
|
||||
// Whoever asked owns how the boxes were reached: the rel base it stated,
|
||||
// and what of its own box it asked in. A local redraw asks the same
|
||||
// question again from these.
|
||||
active.frame = info.frame;
|
||||
active.narrow = info.narrow;
|
||||
active.rel_base = info.rel_base;
|
||||
active.re_asked = info.re_asked;
|
||||
active.answer = Some(answer);
|
||||
active.asked = info.asked;
|
||||
active.part = part;
|
||||
active.region = region;
|
||||
active.placed = info.placed;
|
||||
active.own_align = align;
|
||||
// A subtree can be reused whole under a different parent -- same box,
|
||||
// same layer, same region node -- and nothing in the drawing says it
|
||||
// changed hands. Two things read who its parent is: a deferral, which
|
||||
// marks whoever has it to draw, and the old parent's list of children,
|
||||
// which its next draw undraws whatever is missing from.
|
||||
let old_parent = std::mem::replace(&mut active.parent, info.parent);
|
||||
// The previous parent must stop owning the subtree before it can
|
||||
// undraw it, whether changing hands reused the drawing or replaced it.
|
||||
active.parent = info.parent;
|
||||
if old_parent != info.parent
|
||||
&& let Some(old_parent) = old_parent
|
||||
&& let Some(old_parent) = self.active.get_mut(&old_parent)
|
||||
@@ -290,28 +278,31 @@ impl UiRenderState {
|
||||
(answer.0, answer.1, drawing_holds)
|
||||
}
|
||||
|
||||
/// Calls a widget's `draw` and keeps what it drew in `extent` of `frame`.
|
||||
/// Calls a widget's `draw` and keeps what it drew in `region`.
|
||||
fn draw_at(
|
||||
&mut self,
|
||||
id: WidgetId,
|
||||
extent: UiRegion,
|
||||
region: UiRegion,
|
||||
info: DrawInfo,
|
||||
old: Option<ActiveData>,
|
||||
rsc: &mut dyn UiRsc,
|
||||
) -> (Size, LayoutHolds) {
|
||||
let frame = info.frame;
|
||||
let (move_idx, extent, retired_move) = match info.region_node {
|
||||
let rel_base = info.rel_base;
|
||||
let (move_idx, region, retired_move) = match info.region_node {
|
||||
// A node entry is only a translation. Its local box keeps the
|
||||
// same window-unit length as the box in its parent's node.
|
||||
true => (
|
||||
self.move_slot(id, info.parent_move, translation(extent)),
|
||||
local_region(extent),
|
||||
self.move_slot(id, info.parent_move, translation(region)),
|
||||
local_region(region),
|
||||
None,
|
||||
),
|
||||
// Keep the old entry alive until every descendant has migrated.
|
||||
// Reusing its index sooner could make an old parent look current.
|
||||
false => (info.parent_move, extent, self.slots.remove(&id)),
|
||||
false => (info.parent_move, region, self.slots.remove(&id)),
|
||||
};
|
||||
let mask_slot = old
|
||||
.as_ref()
|
||||
.and_then(|old| old.mask_region.map(|_| old.mask));
|
||||
let old_children = old.map_or_else(Vec::new, |old| old.children);
|
||||
rsc.widgets_mut().needs_redraw.remove(&id);
|
||||
let px = info.px;
|
||||
@@ -319,9 +310,8 @@ impl UiRenderState {
|
||||
let window = self.output_size;
|
||||
let mut painter = Painter {
|
||||
state: self,
|
||||
frame,
|
||||
extent,
|
||||
extent_len: [None; 2],
|
||||
rel_base,
|
||||
region,
|
||||
window,
|
||||
mask: info.mask,
|
||||
layer: info.layer,
|
||||
@@ -330,12 +320,11 @@ impl UiRenderState {
|
||||
textures: Vec::new(),
|
||||
primitives: Vec::new(),
|
||||
mask_region: None,
|
||||
mask_slot,
|
||||
children: Vec::new(),
|
||||
size_deps: Vec::new(),
|
||||
window_own: [Holds::ANY; 2],
|
||||
frame_own_len: [None; 2],
|
||||
own: LayoutHolds::ANY,
|
||||
under: Vec::new(),
|
||||
extent_own: [Holds::ANY; 2],
|
||||
answer_under: LayoutHolds::ANY,
|
||||
depth: info.depth,
|
||||
move_idx,
|
||||
@@ -356,20 +345,18 @@ impl UiRenderState {
|
||||
let Painter {
|
||||
state: _,
|
||||
rsc: _,
|
||||
frame: _,
|
||||
extent: _,
|
||||
rel_base: _,
|
||||
region: _,
|
||||
window: _,
|
||||
mask,
|
||||
textures,
|
||||
primitives,
|
||||
mask_region,
|
||||
extent_own,
|
||||
extent_len,
|
||||
mask_slot,
|
||||
own,
|
||||
answer_under,
|
||||
children,
|
||||
size_deps,
|
||||
window_own,
|
||||
frame_own_len,
|
||||
under,
|
||||
move_idx,
|
||||
layer,
|
||||
@@ -386,18 +373,18 @@ impl UiRenderState {
|
||||
// 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
|
||||
// carry one, and so the widget under a rule never learns of it. The
|
||||
// frame is the answer where the rule gave a length outright: it was
|
||||
// resolved into the frame when the child was asked, and resolving it
|
||||
// rel base is the answer where the rule gave a length outright: it was
|
||||
// resolved into the rel base when the child was asked, and resolving it
|
||||
// again here would take the fraction of a fraction.
|
||||
let rules = rsc.widgets().size_rules(id);
|
||||
let ruled = |axis: Axis, reported: LayoutLen| match rules.axis(axis).exact() {
|
||||
None => reported,
|
||||
Some(len) if len.leftover == Weight::ZERO => LayoutLen {
|
||||
rel: info.frame.axis(axis).rel,
|
||||
px: info.frame.axis(axis).px,
|
||||
rel: info.rel_base.axis(axis).rel,
|
||||
px: info.rel_base.axis(axis).px,
|
||||
leftover: Weight::ZERO,
|
||||
},
|
||||
Some(len) => len.within_len(info.frame.axis(axis)),
|
||||
Some(len) => len.within_len(info.rel_base.axis(axis)),
|
||||
};
|
||||
let size = Size {
|
||||
x: ruled(Axis::X, size.x),
|
||||
@@ -412,7 +399,7 @@ impl UiRenderState {
|
||||
mask == info.mask
|
||||
|| AXES
|
||||
.into_iter()
|
||||
.all(|axis| within_box(size, extent, self.output_size, axis)),
|
||||
.all(|axis| within_box(size, region, self.output_size, axis)),
|
||||
"'{}' ({id:?}) clips to {px:?} and reports {size}",
|
||||
rsc.widgets().label(id),
|
||||
);
|
||||
@@ -421,35 +408,33 @@ impl UiRenderState {
|
||||
self.undraw_rec(*c, rsc);
|
||||
}
|
||||
}
|
||||
if let Some(idx) = mask_slot {
|
||||
rsc.ui_mut().masks.remove(idx);
|
||||
}
|
||||
if let Some(idx) = retired_move {
|
||||
self.moves.remove(idx);
|
||||
}
|
||||
// A rule that is a fraction of the frame is answered with the
|
||||
// frame's own length, so the answer is that frame's and not just
|
||||
// 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
|
||||
// that many pixels of this window -- the same pin a widget that read
|
||||
// its frame took for its drawing.
|
||||
let frame_len = AXES.map(|axis| {
|
||||
// its rel base took for its drawing.
|
||||
let rel_base = AXES.map(|axis| {
|
||||
let fraction = rules
|
||||
.axis(axis)
|
||||
.exact()
|
||||
.is_some_and(|len| len.rel != Rel::ZERO);
|
||||
match fraction {
|
||||
true => Some(info.frame.axis(axis)),
|
||||
false => frame_own_len[axis as usize],
|
||||
true => Some(info.rel_base.axis(axis)),
|
||||
false => own.rel_base[axis as usize],
|
||||
}
|
||||
});
|
||||
let own_holds = LayoutHolds {
|
||||
window: window_own,
|
||||
frame_len,
|
||||
extent: extent_own,
|
||||
extent_len,
|
||||
};
|
||||
let own_holds = LayoutHolds { rel_base, ..own };
|
||||
let answer_holds = own_holds.and(answer_under);
|
||||
let holds = under
|
||||
.into_iter()
|
||||
.fold(answer_holds, |holds, (_, child)| holds.and(child));
|
||||
debug_assert!(
|
||||
holds.contains(self.output_size, info.frame, extent),
|
||||
holds.contains(self.output_size, info.rel_base, region),
|
||||
"'{}' ({id:?}) drew in {px:?}, outside the ranges it reported: {holds:?}",
|
||||
rsc.widgets().label(id),
|
||||
);
|
||||
@@ -467,11 +452,10 @@ impl UiRenderState {
|
||||
parent_move: move_idx,
|
||||
region_node: false,
|
||||
mask,
|
||||
frame: UiVec2::FULL_SIZE,
|
||||
part: UiRegion::FULL,
|
||||
placed: [Place::Within(Part::All); 2],
|
||||
asked: [Place::Within(Part::All); 2],
|
||||
narrow: [None; 2],
|
||||
rel_base: UiVec2::FULL_SIZE,
|
||||
region: UiRegion::FULL,
|
||||
placed: PlaceDesc::WHOLE,
|
||||
asked: PlaceDesc::WHOLE,
|
||||
re_asked: false,
|
||||
px,
|
||||
},
|
||||
@@ -483,12 +467,11 @@ impl UiRenderState {
|
||||
|
||||
let active = ActiveData {
|
||||
id,
|
||||
extent,
|
||||
frame: info.frame,
|
||||
narrow: info.narrow,
|
||||
placement: region,
|
||||
rel_base: info.rel_base,
|
||||
placed: info.placed,
|
||||
asked: info.asked,
|
||||
part: extent,
|
||||
region,
|
||||
// Whoever asked writes the answer.
|
||||
answer: None,
|
||||
re_asked: info.re_asked,
|
||||
@@ -542,7 +525,7 @@ impl UiRenderState {
|
||||
fn retained_answer(
|
||||
&self,
|
||||
id: WidgetId,
|
||||
part: UiRegion,
|
||||
region: UiRegion,
|
||||
info: DrawInfo,
|
||||
) -> Option<(Size, LayoutHolds)> {
|
||||
let active = self.active.get(&id)?;
|
||||
@@ -556,17 +539,17 @@ impl UiRenderState {
|
||||
let answer = active.answer?;
|
||||
answer
|
||||
.1
|
||||
.contains(self.output_size, info.frame, part)
|
||||
.contains(self.output_size, info.rel_base, region)
|
||||
.then_some(answer)
|
||||
}
|
||||
|
||||
/// Keeps the retained drawing if its contract holds for `part`, the box
|
||||
/// asked about, and puts it at `extent`, where the answer places it.
|
||||
/// asked about, and puts it at `placed`, where the answer places it.
|
||||
fn try_reuse(
|
||||
&mut self,
|
||||
id: WidgetId,
|
||||
part: UiRegion,
|
||||
extent: UiRegion,
|
||||
region: UiRegion,
|
||||
placed: UiRegion,
|
||||
info: DrawInfo,
|
||||
rsc: &mut dyn UiRsc,
|
||||
) -> Option<()> {
|
||||
@@ -605,6 +588,9 @@ impl UiRenderState {
|
||||
}
|
||||
return None;
|
||||
}
|
||||
if active.parent_mask != info.mask {
|
||||
return None;
|
||||
}
|
||||
// Drawn somewhere else in the tree: its box is in coordinates it no
|
||||
// longer sits in, and its slot names the wrong parent.
|
||||
if active.parent_move != info.parent_move {
|
||||
@@ -618,27 +604,31 @@ impl UiRenderState {
|
||||
// In pixels, because the box is a fraction of the window and that
|
||||
// may be what changed -- an unchanged fraction of a window half the
|
||||
// size is half the widget.
|
||||
if !active.holds.contains(self.output_size, info.frame, part) {
|
||||
if !active
|
||||
.holds
|
||||
.contains(self.output_size, info.rel_base, region)
|
||||
{
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
{
|
||||
// Which of the three said no, so a frame that redraws more
|
||||
// Which of the three said no, so a rel base that redraws more
|
||||
// than it should says where to look. They overlap: a drawing
|
||||
// can be outside two of them at once.
|
||||
let holds = active.holds;
|
||||
for axis in AXES {
|
||||
let n = axis as usize;
|
||||
if holds.extent_len[n].is_some_and(|pinned| pinned != part.axis(axis).len()) {
|
||||
if holds.region_len[n].is_some_and(|pinned| pinned != region.axis(axis).len()) {
|
||||
diag::bump(Counter::OutsidePinnedLen);
|
||||
}
|
||||
if !holds.window[n].contains(self.output_size.axis(axis))
|
||||
|| holds.frame_len[n].is_some_and(|pinned| pinned != info.frame.axis(axis))
|
||||
|| holds.rel_base[n]
|
||||
.is_some_and(|pinned| pinned != info.rel_base.axis(axis))
|
||||
{
|
||||
diag::bump(Counter::OutsideFrame);
|
||||
diag::bump(Counter::OutsideRelBase);
|
||||
}
|
||||
if !holds.extent[n]
|
||||
.contains(part.axis(axis).len().to_px(self.output_size.axis(axis)))
|
||||
if !holds.region[n]
|
||||
.contains(region.axis(axis).len().to_px(self.output_size.axis(axis)))
|
||||
{
|
||||
diag::bump(Counter::OutsideExtent);
|
||||
diag::bump(Counter::OutsideRegion);
|
||||
}
|
||||
}
|
||||
diag::bump(Counter::ReuseOutside);
|
||||
@@ -646,14 +636,14 @@ impl UiRenderState {
|
||||
}
|
||||
return None;
|
||||
}
|
||||
self.relocate(id, extent, info, rsc);
|
||||
self.relocate(id, placed, info, rsc);
|
||||
Some(())
|
||||
}
|
||||
|
||||
/// Puts a retained drawing where its parent now has it, without drawing:
|
||||
/// a widget with a node of its own writes that node's translation, and
|
||||
/// one without re-expresses its own drawing and everything inside it.
|
||||
fn relocate(&mut self, id: WidgetId, extent: UiRegion, info: DrawInfo, rsc: &mut dyn UiRsc) {
|
||||
fn relocate(&mut self, id: WidgetId, placed: UiRegion, info: DrawInfo, rsc: &mut dyn UiRsc) {
|
||||
let active = &self.active[&id];
|
||||
debug_assert!(
|
||||
!rsc.widgets().needs_redraw.contains(&id),
|
||||
@@ -662,20 +652,20 @@ impl UiRenderState {
|
||||
);
|
||||
let has_region_node = active.move_idx != active.parent_move;
|
||||
let local = match has_region_node {
|
||||
true => local_region(extent),
|
||||
false => extent,
|
||||
true => local_region(placed),
|
||||
false => placed,
|
||||
};
|
||||
let moved = active.extent != local;
|
||||
let moved = active.placement != local;
|
||||
let slot = active.move_idx;
|
||||
if has_region_node {
|
||||
self.moves.set(slot, translation(extent));
|
||||
self.moves.set(slot, translation(placed));
|
||||
}
|
||||
if moved {
|
||||
self.reposition(id, local, info, rsc);
|
||||
}
|
||||
self.redepth(id, info.depth);
|
||||
let active = self.active.get_mut(&id).unwrap();
|
||||
active.frame = info.frame;
|
||||
active.rel_base = info.rel_base;
|
||||
active.placed = info.placed;
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
{
|
||||
@@ -712,16 +702,16 @@ impl UiRenderState {
|
||||
&mut self,
|
||||
child: WidgetId,
|
||||
at: &Placing,
|
||||
place: [Place; 2],
|
||||
place: PlaceDesc,
|
||||
rsc: &mut dyn UiRsc,
|
||||
) {
|
||||
let active = &self.active[&child];
|
||||
let (frame, part) = Self::ask_again(active, at, place);
|
||||
let extent = placed_extent(
|
||||
part,
|
||||
let (rel_base, region) = Self::ask_again(active, at, place);
|
||||
let placed = placement(
|
||||
region,
|
||||
active.measured().unwrap_or(active.size),
|
||||
active.declared,
|
||||
place.map(Place::fills),
|
||||
place,
|
||||
active.own_align,
|
||||
);
|
||||
let info = DrawInfo {
|
||||
@@ -731,27 +721,25 @@ impl UiRenderState {
|
||||
parent_move: at.move_idx,
|
||||
region_node: active.move_idx != active.parent_move,
|
||||
mask: at.mask,
|
||||
frame,
|
||||
part,
|
||||
rel_base,
|
||||
region,
|
||||
placed: place,
|
||||
asked: active.asked,
|
||||
narrow: active.narrow,
|
||||
re_asked: active.re_asked,
|
||||
px: frame.to_px(at.window),
|
||||
px: rel_base.to_px(at.window),
|
||||
};
|
||||
self.relocate(child, extent, info, rsc);
|
||||
self.relocate(child, placed, info, rsc);
|
||||
}
|
||||
|
||||
/// The frame and the box a widget already drawn is given at `place` of
|
||||
/// the box its parent is being taken as. What narrowed its frame and what
|
||||
/// The rel base and the box a widget already drawn is given at `place` of
|
||||
/// the box its parent is being taken as. What narrowed its rel base and what
|
||||
/// it declared are its own record's, so both are resolved against that
|
||||
/// parent's frame again exactly as the first ask resolved them.
|
||||
fn ask_again(active: &ActiveData, at: &Placing, place: [Place; 2]) -> (UiVec2, UiRegion) {
|
||||
frame_and_extent(
|
||||
at.extent,
|
||||
at.frame,
|
||||
/// parent's rel base again exactly as the first ask resolved them.
|
||||
fn ask_again(active: &ActiveData, at: &Placing, place: PlaceDesc) -> (UiVec2, UiRegion) {
|
||||
rel_base_and_region(
|
||||
at.region,
|
||||
at.rel_base,
|
||||
place,
|
||||
active.narrow,
|
||||
active.declared,
|
||||
active.own_align,
|
||||
)
|
||||
@@ -761,20 +749,20 @@ impl UiRenderState {
|
||||
/// is placed as a part of that box, so each one's new box is its retained
|
||||
/// part re-added to the new start -- and a child whose own box then did
|
||||
/// not change is not touched at all.
|
||||
fn reposition(&mut self, id: WidgetId, extent: UiRegion, info: DrawInfo, rsc: &mut dyn UiRsc) {
|
||||
fn reposition(&mut self, id: WidgetId, placed: UiRegion, info: DrawInfo, rsc: &mut dyn UiRsc) {
|
||||
let active = self.active.get_mut(&id).unwrap();
|
||||
active.extent = extent;
|
||||
active.placement = placed;
|
||||
for primitive in &active.primitives {
|
||||
let handle = &primitive.handle;
|
||||
*self.layers[handle.layer].region_mut(handle) = primitive.region.within(&extent);
|
||||
*self.layers[handle.layer].region_mut(handle) = primitive.region.within(&placed);
|
||||
}
|
||||
if let Some(mask_region) = active.mask_region {
|
||||
rsc.ui_mut().masks.get_mut(active.mask).region = mask_region.within(&extent);
|
||||
rsc.ui_mut().masks.get_mut(active.mask).region = mask_region.within(&placed);
|
||||
}
|
||||
let at = Placing {
|
||||
id,
|
||||
extent,
|
||||
frame: info.frame,
|
||||
region: placed,
|
||||
rel_base: info.rel_base,
|
||||
window: self.output_size,
|
||||
depth: info.depth,
|
||||
move_idx: active.move_idx,
|
||||
@@ -826,6 +814,9 @@ impl UiRenderState {
|
||||
rsc.ui_mut().masks.remove(mask);
|
||||
}
|
||||
}
|
||||
if undraw && active.mask_region.take().is_some() {
|
||||
rsc.ui_mut().masks.remove(active.mask);
|
||||
}
|
||||
active.primitives.clear();
|
||||
active.textures.clear();
|
||||
rsc.ui_mut().textures.free();
|
||||
@@ -875,12 +866,11 @@ impl UiRenderState {
|
||||
id,
|
||||
ActiveData {
|
||||
id,
|
||||
extent: UiRegion::FULL,
|
||||
frame: UiVec2::FULL_SIZE,
|
||||
narrow: [None; 2],
|
||||
placed: [Place::Within(Part::All); 2],
|
||||
asked: [Place::Within(Part::All); 2],
|
||||
part: UiRegion::FULL,
|
||||
placement: UiRegion::FULL,
|
||||
rel_base: UiVec2::FULL_SIZE,
|
||||
placed: PlaceDesc::WHOLE,
|
||||
asked: PlaceDesc::WHOLE,
|
||||
region: UiRegion::FULL,
|
||||
answer: None,
|
||||
re_asked: false,
|
||||
size,
|
||||
@@ -913,6 +903,7 @@ impl UiRenderState {
|
||||
self.slots.clear();
|
||||
self.moves.clear();
|
||||
self.layers.clear();
|
||||
rsc.ui_mut().masks = Default::default();
|
||||
rsc.widgets_mut().needs_redraw.clear();
|
||||
self.free(rsc);
|
||||
}
|
||||
@@ -1064,7 +1055,7 @@ impl UiRenderState {
|
||||
let active = self.active.get(&id.id())?;
|
||||
active.drawn.then(|| {
|
||||
self.moves
|
||||
.resolve(active.move_idx, active.extent)
|
||||
.resolve(active.move_idx, active.placement)
|
||||
.to_px(self.output_size)
|
||||
})
|
||||
}
|
||||
@@ -1106,10 +1097,10 @@ impl UiRenderState {
|
||||
// box is its own to work out again against the output. Every other
|
||||
// widget was given one.
|
||||
let Some(parent) = active.parent else {
|
||||
let (frame, extent) = Self::root_layout(id, rsc.widgets());
|
||||
let (rel_base, region) = Self::root_layout(id, rsc.widgets());
|
||||
let info = DrawInfo {
|
||||
mask: active.parent_mask,
|
||||
..self.root_info(frame, extent)
|
||||
..self.root_info(rel_base, region)
|
||||
};
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::LocalRedraws);
|
||||
@@ -1123,8 +1114,8 @@ impl UiRenderState {
|
||||
// draw ran in and what its children's parts are of. Where the
|
||||
// parent's answer put its own drawing is not a question anybody
|
||||
// asked, and nothing is asked in it here either.
|
||||
let parent_at = self.placing_of(parent, self.active[&parent].part);
|
||||
let (frame, part) = Self::ask_again(active, &parent_at, active.asked);
|
||||
let parent_at = self.placing_of(parent, self.active[&parent].region);
|
||||
let (rel_base, region) = Self::ask_again(active, &parent_at, active.asked);
|
||||
let info = DrawInfo {
|
||||
layer: active.layer,
|
||||
parent: active.parent,
|
||||
@@ -1132,13 +1123,12 @@ impl UiRenderState {
|
||||
parent_move: active.parent_move,
|
||||
region_node: rsc.widgets().is_region_node(id),
|
||||
mask: active.parent_mask,
|
||||
frame,
|
||||
part,
|
||||
rel_base,
|
||||
region,
|
||||
placed: active.asked,
|
||||
asked: active.asked,
|
||||
narrow: active.narrow,
|
||||
re_asked: false,
|
||||
px: frame.to_px(self.output_size),
|
||||
px: rel_base.to_px(self.output_size),
|
||||
};
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::LocalRedraws);
|
||||
@@ -1155,7 +1145,7 @@ impl UiRenderState {
|
||||
active.answer = was_answer;
|
||||
}
|
||||
if active.holds.covers(was_holds)
|
||||
&& was_holds.contains(self.output_size, active.frame, active.extent)
|
||||
&& was_holds.contains(self.output_size, active.rel_base, active.placement)
|
||||
{
|
||||
active.holds = was_holds;
|
||||
}
|
||||
@@ -1172,21 +1162,21 @@ impl UiRenderState {
|
||||
// The answer stands, so where the parent put it stands: the
|
||||
// fresh drawing goes back there -- the same place, of the box
|
||||
// the parent's answer chose rather than the one it was asked in.
|
||||
let at = self.placing_of(parent, self.active[&parent].extent);
|
||||
let at = self.placing_of(parent, self.active[&parent].placement);
|
||||
self.place_in(id, &at, was_place, rsc);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// A drawn widget as the thing its children are placed within, with
|
||||
/// `extent` as the box their parts are of: the box it was asked in for
|
||||
/// `region` as the box their parts are of: the box it was asked in for
|
||||
/// asking one of them again, the box its answer chose for placing one.
|
||||
fn placing_of(&self, id: WidgetId, extent: UiRegion) -> Placing {
|
||||
fn placing_of(&self, id: WidgetId, region: UiRegion) -> Placing {
|
||||
let active = &self.active[&id];
|
||||
Placing {
|
||||
id,
|
||||
extent,
|
||||
frame: active.frame,
|
||||
region,
|
||||
rel_base: active.rel_base,
|
||||
window: self.output_size,
|
||||
depth: active.depth,
|
||||
move_idx: active.move_idx,
|
||||
@@ -1199,11 +1189,11 @@ impl UiRenderState {
|
||||
/// Both are lengths of the window, so the comparison is in its pixels. A
|
||||
/// share is a length only to whoever divides one, so it is not a claim about
|
||||
/// this box and cannot exceed it.
|
||||
fn within_box(size: Size, extent: UiRegion, window: PxVec2, axis: Axis) -> bool {
|
||||
fn within_box(size: Size, region: UiRegion, window: PxVec2, axis: Axis) -> bool {
|
||||
let len = size.axis(axis);
|
||||
let window = window.axis(axis);
|
||||
len.leftover != Weight::ZERO
|
||||
|| Len::from_parts(len.rel, len.px).to_px(window) <= extent.axis(axis).len().to_px(window)
|
||||
|| Len::from_parts(len.rel, len.px).to_px(window) <= region.axis(axis).len().to_px(window)
|
||||
}
|
||||
|
||||
/// A box in a fresh region node keeps its window-unit length and starts at
|
||||
|
||||
+6
-6
@@ -118,9 +118,9 @@ pub struct Branch {
|
||||
impl Widget for Branch {
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||
let cut = Len::from_parts(Rel::ZERO, Px::from_int(40));
|
||||
let top = Place::Within(Part::From(UiSpan::new(Len::ZERO, cut)));
|
||||
let top = PlaceDescAxis::shifted(UiSpan::new(Len::ZERO, cut));
|
||||
let measured = painter
|
||||
.widget_at(&self.probe, [None; 2], [Place::Within(Part::All), top])
|
||||
.widget_at(&self.probe, PlaceDesc::new(PlaceDescAxis::WHOLE, top))
|
||||
.len(Axis::X);
|
||||
let len = measured.apply_leftover();
|
||||
let px = painter.to_px(len, Axis::X);
|
||||
@@ -134,11 +134,11 @@ impl Widget for Branch {
|
||||
};
|
||||
painter.window_holds(Axis::X, holds.through(len));
|
||||
|
||||
let below = Place::Within(Part::From(UiSpan::new(cut, painter.extent_len(Axis::Y))));
|
||||
let place = [Place::Within(Part::All), below];
|
||||
let below = PlaceDescAxis::shifted(UiSpan::new(cut, painter.region_len(Axis::Y)));
|
||||
let place = PlaceDesc::new(PlaceDescAxis::WHOLE, below);
|
||||
match px > threshold {
|
||||
true => painter.widget_at(&self.wide, [None; 2], place),
|
||||
false => painter.widget_at(&self.narrow, [None; 2], place),
|
||||
true => painter.widget_at(&self.wide, place),
|
||||
false => painter.widget_at(&self.narrow, place),
|
||||
};
|
||||
Size::LEFTOVER
|
||||
}
|
||||
|
||||
@@ -7,14 +7,8 @@ pub struct Offset {
|
||||
|
||||
impl Widget for Offset {
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||
// The whole of this widget's box, moved: the frame passes through, so
|
||||
// what the child declares or reports means the same as it would
|
||||
// without the offset.
|
||||
let moved = |len: Len, amt: Len| Place::Within(Part::From(UiSpan::new(amt, len + amt)));
|
||||
let place = [
|
||||
moved(painter.extent_len(Axis::X), self.amt.x),
|
||||
moved(painter.extent_len(Axis::Y), self.amt.y),
|
||||
];
|
||||
painter.widget_at(&self.inner, [None; 2], place).size()
|
||||
painter
|
||||
.widget_at(&self.inner, UiRegion::FULL.offset(self.amt))
|
||||
.size()
|
||||
}
|
||||
}
|
||||
@@ -14,31 +14,12 @@ impl Widget for Pad {
|
||||
// widget -- the slack is the inner's to sit in, and forcing the near
|
||||
// edge pinned it to a corner it had not asked for.
|
||||
//
|
||||
// Padding is an inset of both: it comes off the frame, so `rel(1)`
|
||||
// Padding is an inset of both: it comes off the rel base, so `rel(1)`
|
||||
// under it fills this widget rather than overflowing it by the
|
||||
// padding, and it comes off the box, so what is drawn sits inside.
|
||||
// The two stay distinct -- the box can be narrower still, where a row
|
||||
// asked this widget in the room left, and a text wraps at that.
|
||||
let inset = |lead: Px, trail: Px| {
|
||||
Place::Within(Part::Inset {
|
||||
lead: Len::from_parts(Rel::ZERO, lead),
|
||||
trail: Len::from_parts(Rel::ZERO, trail),
|
||||
})
|
||||
};
|
||||
let place = [
|
||||
inset(self.padding.left, self.padding.right),
|
||||
inset(self.padding.top, self.padding.bottom),
|
||||
];
|
||||
// Read from this widget's own frame rather than written as a
|
||||
// fraction of it: a frame is a length of the window like everything
|
||||
// else here, and taking the padding off is the whole of what this
|
||||
// widget does to it.
|
||||
let narrow = [
|
||||
(Axis::X, self.padding.left + self.padding.right),
|
||||
(Axis::Y, self.padding.top + self.padding.bottom),
|
||||
]
|
||||
.map(|(axis, pixels)| Some(painter.frame_len(axis) - Len::from_parts(Rel::ZERO, pixels)));
|
||||
let inner = painter.widget_at(&self.inner, narrow, place).size();
|
||||
let inner = painter.widget_at(&self.inner, self.padding.region()).size();
|
||||
Size {
|
||||
x: LayoutLen {
|
||||
px: inner.x.px + self.padding.left + self.padding.right,
|
||||
|
||||
@@ -14,7 +14,7 @@ impl Widget for Scroll {
|
||||
let container_len = painter.px_len(self.axis);
|
||||
// Asked in the whole viewport, then put at the scrolled offset.
|
||||
let answer_len = painter
|
||||
.widget_at(&self.inner, [None; 2], [Place::Fill(Part::All); 2])
|
||||
.widget_at(&self.inner, PlaceDesc::WHOLE.fills())
|
||||
.len(self.axis);
|
||||
let fixed = painter.to_px(Len::from_parts(answer_len.rel, answer_len.px), self.axis);
|
||||
self.container_len = container_len;
|
||||
@@ -54,17 +54,17 @@ impl Widget for Scroll {
|
||||
let content = match moved || self.content_len != self.container_len {
|
||||
true => {
|
||||
let start = Len::from_parts(Rel::ZERO, anchor - self.amt);
|
||||
Part::From(UiSpan::new(start, start.offset(self.content_len)))
|
||||
PlaceDescAxis::shifted(UiSpan::new(start, start.offset(self.content_len)))
|
||||
}
|
||||
false => Part::All,
|
||||
false => PlaceDescAxis::WHOLE,
|
||||
};
|
||||
// The viewport is the inner's frame, so a fraction it declares or
|
||||
// The viewport is the inner's rel base, so a fraction it declares or
|
||||
// reports is a fraction of what is on screen rather than of the
|
||||
// content box its own answer decided. Where it goes is the content
|
||||
// box, scrolled: its drawing moved there, not made again there.
|
||||
painter.place_at(
|
||||
&self.inner,
|
||||
self.axis.pair(Place::Fill(content), Place::Fill(Part::All)),
|
||||
PlaceDesc::from_axis(self.axis, content.fills(), PlaceDescAxis::WHOLE.fills()),
|
||||
);
|
||||
// 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
|
||||
|
||||
+24
-61
@@ -10,51 +10,36 @@ pub struct Span {
|
||||
impl Widget for Span {
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||
let axis = self.dir.axis;
|
||||
// The room left from the cursor to the row's end, said without the
|
||||
// row's length: a child measured in it does not make this drawing
|
||||
// depend on how long the row is.
|
||||
let room_from = |cursor: Len| match self.dir.sign {
|
||||
Sign::Pos => Part::Inset {
|
||||
lead: cursor,
|
||||
trail: Len::ZERO,
|
||||
},
|
||||
Sign::Neg => Part::Inset {
|
||||
lead: Len::ZERO,
|
||||
trail: cursor,
|
||||
},
|
||||
// The row: this span's own box, as a length of the rel base its children
|
||||
// are laid out against. Its start is nothing's business -- a slot is
|
||||
// a length from it -- so what this reads is the length alone.
|
||||
let far = painter.region_len(axis);
|
||||
let along = |from: Len, to: Len| match self.dir.sign {
|
||||
Sign::Pos => UiSpan::new(from, to),
|
||||
Sign::Neg => UiSpan::new(far - to, far - from),
|
||||
};
|
||||
// Across itself the child sits where its own alignment says, in the
|
||||
// whole of the row: a span is what contains its children there, and
|
||||
// nothing divides that axis.
|
||||
let across = Place::Within(Part::All);
|
||||
let across = PlaceDescAxis::WHOLE;
|
||||
// A length for every child before their final slots are chosen: from
|
||||
// a hint where one says, and from drawing otherwise. The frame passes
|
||||
// a hint where one says, and from drawing otherwise. The rel base passes
|
||||
// through unchanged, so `rel(0.5)` is half the area this span was
|
||||
// given whatever else is in it and wherever this child sits among
|
||||
// them; what a drawn child is asked in is the room left from the
|
||||
// cursor, because a text has to wrap at the width actually there.
|
||||
// This is the one ask a drawn fixed child gets: its slot is its
|
||||
// answer, and the drawing is moved there once the shares are known.
|
||||
// A hinted child is asked once, in its slot.
|
||||
let mut cursor = Len::rel_min();
|
||||
let mut lens = Vec::with_capacity(self.children.len());
|
||||
let mut measured = Vec::with_capacity(self.children.len());
|
||||
for child in &self.children {
|
||||
let size = match painter.size_hint(child, axis) {
|
||||
Some(len) => {
|
||||
measured.push(None);
|
||||
len
|
||||
}
|
||||
let len = match painter.size_hint(child, axis) {
|
||||
Some(len) => len,
|
||||
None => {
|
||||
let room = Place::Within(room_from(cursor));
|
||||
let size = painter
|
||||
.widget_at(child, [None; 2], axis.pair(room, across))
|
||||
.size();
|
||||
measured.push(Some(size));
|
||||
size.axis(axis)
|
||||
let room = PlaceDescAxis::shifted(along(cursor, far));
|
||||
painter
|
||||
.widget_at(child, PlaceDesc::from_axis(axis, room, across))
|
||||
.len(axis)
|
||||
}
|
||||
};
|
||||
let len = size;
|
||||
cursor.px += len.px + self.gap;
|
||||
cursor.rel += len.rel;
|
||||
lens.push(len);
|
||||
@@ -71,25 +56,9 @@ impl Widget for Span {
|
||||
|sum, len| sum + *len,
|
||||
);
|
||||
|
||||
// The row: this span's own box as a length of the window, read only
|
||||
// where a slot depends on it -- shares divide what is left of it,
|
||||
// and a negative row counts from its end. Reading it pins the
|
||||
// drawing to this length; a positive row of fixed children is not
|
||||
// pinned and holds for any length its children do. Its start is
|
||||
// nothing's business: a slot is a length from it.
|
||||
let far = (total.leftover > Weight::ZERO || self.dir.sign == Sign::Neg)
|
||||
.then(|| painter.extent_len(axis));
|
||||
let along = |from: Len, to: Len| match self.dir.sign {
|
||||
Sign::Pos => UiSpan::new(from, to),
|
||||
Sign::Neg => {
|
||||
let far = far.expect("a negative row reads its length");
|
||||
UiSpan::new(far - to, far - from)
|
||||
}
|
||||
};
|
||||
// What is left for the shares to divide: the row less everything
|
||||
// fixed, as a length of the frame rather than a number of pixels.
|
||||
// Nothing where there are no shares, and nothing reads it there.
|
||||
let room = far.map_or(Len::ZERO, |far| far - Len::from_parts(total.rel, total.px));
|
||||
// fixed, as a length of the rel base rather than a number of pixels.
|
||||
let room = far - Len::from_parts(total.rel, total.px);
|
||||
// Whether anything is left over is a question in pixels: `rel(0.5)`
|
||||
// beside 300 px is full at 600 and overfull at 400. Asked of `room`
|
||||
// itself, and answered back through the same expression, so the
|
||||
@@ -125,8 +94,7 @@ impl Widget for Span {
|
||||
let mut taken = Weight::ZERO;
|
||||
let mut start = Len::rel_min();
|
||||
let mut ortho = LayoutLen::ZERO;
|
||||
for ((child, len), measured) in self.children.iter().zip(&lens).zip(&measured) {
|
||||
let len = *len;
|
||||
for (child, &len) in self.children.iter().zip(&lens) {
|
||||
// A child asking for nothing but a part of what is left over,
|
||||
// when nothing is, is not drawn at all. One that also asked for
|
||||
// pixels or a fraction keeps those and overflows.
|
||||
@@ -134,6 +102,7 @@ impl Widget for Span {
|
||||
{
|
||||
painter.undraw(child);
|
||||
fixed.px += self.gap;
|
||||
start = shared(fixed, taken, total.leftover, room);
|
||||
continue;
|
||||
}
|
||||
let from = start;
|
||||
@@ -146,23 +115,17 @@ impl Widget for Span {
|
||||
// Along the row the span says where the child goes, and that slot
|
||||
// is the child's box outright rather than something to place an
|
||||
// answer inside again. A share is decided here and nowhere
|
||||
// else: its slot narrows its frame, and the child is asked in
|
||||
// else: its slot narrows its rel base, and the child is asked in
|
||||
// it, since a text wraps at the width it is actually given. A
|
||||
// fixed child's slot is its own answer, so a drawing made in the
|
||||
// room is put there as it is, and one not made yet is made here.
|
||||
let slot = along(from, start);
|
||||
let place = axis.pair(Place::Fill(Part::From(slot)), across);
|
||||
let mut narrow = [None; 2];
|
||||
let slot_place = PlaceDescAxis::shifted(slot).fills();
|
||||
let mut place = PlaceDesc::from_axis(axis, slot_place, across);
|
||||
if len.leftover > Weight::ZERO && shares {
|
||||
narrow[axis as usize] = Some(slot.len());
|
||||
place = place.rel_base(axis, slot.len());
|
||||
}
|
||||
let used = match (measured, narrow[axis as usize]) {
|
||||
(Some(size), None) => {
|
||||
painter.place_at(child, place);
|
||||
size.axis(!axis)
|
||||
}
|
||||
_ => painter.widget_at(child, narrow, place).len(!axis),
|
||||
};
|
||||
let used = painter.place_at(child, place).len(!axis);
|
||||
if shrinks {
|
||||
// Choosing between a fixed and a relative length from the
|
||||
// span's own eventual width admits multiple fixed points.
|
||||
|
||||
@@ -22,9 +22,7 @@ impl Widget for Stack {
|
||||
// drawing belongs to the layer it was made on.
|
||||
Some((i, child)) => {
|
||||
painter.child_layer_at(i);
|
||||
painter
|
||||
.widget_at(child, [None; 2], [Place::Fill(Part::All); 2])
|
||||
.size()
|
||||
painter.widget_at(child, PlaceDesc::WHOLE.fills()).size()
|
||||
}
|
||||
None => Size::LEFTOVER,
|
||||
};
|
||||
@@ -33,19 +31,20 @@ impl Widget for Stack {
|
||||
// fraction under them is a fraction of it. A share leaves the axis
|
||||
// to whoever gave the stack its box. Where a child sits in a box
|
||||
// bigger than itself is its own business.
|
||||
let place = [Axis::X, Axis::Y].map(|axis| {
|
||||
let on = |axis| {
|
||||
let len = size.axis(axis);
|
||||
match len.leftover == Weight::ZERO {
|
||||
true => Place::Fill(Part::Sized(Len::from_parts(len.rel, len.px))),
|
||||
false => Place::Within(Part::All),
|
||||
true => PlaceDescAxis::sized(Len::from_parts(len.rel, len.px)).fills(),
|
||||
false => PlaceDescAxis::WHOLE,
|
||||
}
|
||||
});
|
||||
};
|
||||
let place = PlaceDesc::new(on(Axis::X), on(Axis::Y));
|
||||
for (i, child) in self.children.iter().enumerate() {
|
||||
if sizing == Some(i) {
|
||||
continue;
|
||||
}
|
||||
painter.child_layer_at(i);
|
||||
painter.widget_at(child, [None; 2], place);
|
||||
painter.widget_at(child, place);
|
||||
}
|
||||
size
|
||||
}
|
||||
|
||||
@@ -22,17 +22,17 @@ struct BranchesOnMeasurement {
|
||||
impl Widget for BranchesOnMeasurement {
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||
let cut = Len::from_parts(Rel::ZERO, Px::from_int(40));
|
||||
let top = Place::Within(Part::From(UiSpan::new(Len::ZERO, cut)));
|
||||
let top = PlaceDescAxis::shifted(UiSpan::new(Len::ZERO, cut));
|
||||
let measured = painter
|
||||
.widget_at(&self.probe, [None; 2], [Place::Within(Part::All), top])
|
||||
.widget_at(&self.probe, PlaceDesc::new(PlaceDescAxis::WHOLE, top))
|
||||
.len(Axis::X);
|
||||
let px = painter.to_px(measured.apply_leftover(), Axis::X);
|
||||
|
||||
let below = Place::Within(Part::From(UiSpan::new(cut, painter.extent_len(Axis::Y))));
|
||||
let place = [Place::Within(Part::All), below];
|
||||
let below = PlaceDescAxis::shifted(UiSpan::new(cut, painter.region_len(Axis::Y)));
|
||||
let place = PlaceDesc::new(PlaceDescAxis::WHOLE, below);
|
||||
match px > Px::from_f32(self.threshold) {
|
||||
true => painter.widget_at(&self.wide, [None; 2], place),
|
||||
false => painter.widget_at(&self.narrow, [None; 2], place),
|
||||
true => painter.widget_at(&self.wide, place),
|
||||
false => painter.widget_at(&self.narrow, place),
|
||||
};
|
||||
Size::LEFTOVER
|
||||
}
|
||||
|
||||
+44
-7
@@ -108,7 +108,7 @@ const PARAGRAPH: &str = "Wrapping shapes one source into as many lines as the bo
|
||||
/// the icon's width, while a wrapping text beside it is asked in the room
|
||||
/// left, 900 - 24 - 32, and wraps there.
|
||||
#[test]
|
||||
fn padding_keeps_the_frame_distinct_from_the_room_left_in_a_row() {
|
||||
fn padding_keeps_the_rel_base_distinct_from_the_room_left_in_a_row() {
|
||||
let mut h = Harness::new((900, 200));
|
||||
let icon = rect(Color::RED).width(24).add(&mut h.rsc);
|
||||
let fill = rect(Color::GREEN).width(rel(1.0)).add(&mut h.rsc);
|
||||
@@ -124,8 +124,8 @@ fn padding_keeps_the_frame_distinct_from_the_room_left_in_a_row() {
|
||||
h.set_root((icon, padded).span(Dir::RIGHT).width(rel(1.0)));
|
||||
let active = &h.render.active[&text.id()];
|
||||
let window = h.render.output_size().x;
|
||||
let asked = active.part.x.len().to_px(window);
|
||||
assert_eq!(active.frame.x.to_px(window), Px::from_int(868));
|
||||
let asked = active.region.x.len().to_px(window);
|
||||
assert_eq!(active.rel_base.x.to_px(window), Px::from_int(868));
|
||||
assert_eq!(asked, Px::from_int(844));
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ fn a_share_inside_padding_fills_the_slot_it_was_given() {
|
||||
/// The same padding in a share instead: the slot is 450, so both the
|
||||
/// fraction and the wrap are the slot less the padding, and the two agree.
|
||||
#[test]
|
||||
fn padding_narrows_both_frame_and_box_inside_a_share() {
|
||||
fn padding_narrows_both_rel_base_and_box_inside_a_share() {
|
||||
let mut h = Harness::new((900, 200));
|
||||
let fill = rect(Color::GREEN).width(rel(1.0)).add(&mut h.rsc);
|
||||
let padded = fill.pad(16).width(leftover(1)).add(&mut h.rsc);
|
||||
@@ -170,8 +170,8 @@ fn padding_narrows_both_frame_and_box_inside_a_share() {
|
||||
h.set_root((padded, other).span(Dir::RIGHT).width(rel(1.0)));
|
||||
let active = &h.render.active[&text.id()];
|
||||
let window = h.render.output_size().x;
|
||||
assert_eq!(active.frame.x.to_px(window), Px::from_int(418));
|
||||
assert_eq!(active.part.x.len().to_px(window), Px::from_int(418));
|
||||
assert_eq!(active.rel_base.x.to_px(window), Px::from_int(418));
|
||||
assert_eq!(active.region.x.len().to_px(window), Px::from_int(418));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -500,7 +500,7 @@ fn a_row_of_equal_shares_fills_it_exactly() {
|
||||
/// a step of. Kept in step with `snap_floor` in `prelude.wgsl`.
|
||||
fn drawn_edges(h: &Harness, id: WidgetId, axis: Axis) -> (f32, f32) {
|
||||
let active = &h.render.active[&id];
|
||||
let region = h.render.moves.resolve(active.move_idx, active.extent);
|
||||
let region = h.render.moves.resolve(active.move_idx, active.placement);
|
||||
let dim = h.size().axis(axis);
|
||||
let snap = |v: f32| (v + Px::STEP.to_f32() * 0.5).floor();
|
||||
let edge = |s: Len| snap(s.rel.to_f32() * dim + s.px.to_f32());
|
||||
@@ -821,3 +821,40 @@ fn a_root_with_a_fraction_rule_is_that_fraction_of_the_window() {
|
||||
h.set_root(root);
|
||||
assert_eq!(h.region(&root).unwrap().size().x, Px::from_int(450));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_collapsed_share_keeps_the_gaps_before_the_next_slot() {
|
||||
for dir in [Dir::RIGHT, Dir::LEFT, Dir::DOWN, Dir::UP] {
|
||||
for collapsed in [1, 2] {
|
||||
let mut h = Harness::new((400, 400));
|
||||
let head = rect(Color::RED).add(&mut h.rsc);
|
||||
h.set_len(head, dir.axis, 200);
|
||||
let tail = rect(Color::BLUE).add(&mut h.rsc);
|
||||
let tail_len = 200 - 10 * (collapsed + 1);
|
||||
h.set_len(tail, dir.axis, tail_len);
|
||||
let mut children: Vec<StrongWidget> = vec![head.add_strong(&mut h.rsc)];
|
||||
let mut shares = Vec::new();
|
||||
for _ in 0..collapsed {
|
||||
let share = rect(Color::GREEN).add(&mut h.rsc);
|
||||
shares.push(share);
|
||||
children.push(share.add_strong(&mut h.rsc));
|
||||
}
|
||||
children.push(tail.add_strong(&mut h.rsc));
|
||||
h.set_root(Span {
|
||||
children,
|
||||
dir,
|
||||
gap: Px::from_int(10),
|
||||
});
|
||||
for share in shares {
|
||||
assert!(h.region(&share).is_none());
|
||||
}
|
||||
let region = h.region(&tail).unwrap();
|
||||
let (from, to) = match dir.sign {
|
||||
Sign::Pos => (400 - tail_len, 400),
|
||||
Sign::Neg => (0, tail_len),
|
||||
};
|
||||
assert_eq!(region.top_left.axis(dir.axis), Px::from_int(from));
|
||||
assert_eq!(region.bot_right.axis(dir.axis), Px::from_int(to));
|
||||
}
|
||||
}
|
||||
}
|
||||
+199
-53
@@ -216,8 +216,7 @@ impl Widget for FromHint {
|
||||
let top = UiSpan::new(Len::ZERO, Len::from_parts(Rel::ZERO, len.px));
|
||||
painter.widget_at(
|
||||
&self.inner,
|
||||
[None; 2],
|
||||
[Place::Within(Part::All), Place::Within(Part::From(top))],
|
||||
PlaceDesc::new(PlaceDescAxis::WHOLE, PlaceDescAxis::shifted(top)),
|
||||
);
|
||||
Size::LEFTOVER
|
||||
}
|
||||
@@ -797,7 +796,7 @@ fn primitive_bounds(h: &Harness, id: WidgetId) -> Vec<PixelRegion> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn changing_an_inherited_extent_keeps_the_original_measurement_offer() {
|
||||
fn changing_an_inherited_region_keeps_the_original_measurement_offer() {
|
||||
fn build(h: &mut Harness, width: i32, text: &str) -> (WeakWidget<Text>, WeakWidget<Rect>) {
|
||||
let first = rect(Color::RED).width(width).add(&mut h.rsc);
|
||||
let words = wtext(text).size(20).wrap(true).add(&mut h.rsc);
|
||||
@@ -877,11 +876,10 @@ fn resizing_a_fixed_frame_recomposes_its_contents_without_drawing_them() {
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||
painter.widget_at(
|
||||
&self.child,
|
||||
[None; 2],
|
||||
[
|
||||
Place::Within(Part::From(self.region.x)),
|
||||
Place::Within(Part::From(self.region.y)),
|
||||
],
|
||||
PlaceDesc::new(
|
||||
PlaceDescAxis::shifted(self.region.x),
|
||||
PlaceDescAxis::shifted(self.region.y),
|
||||
),
|
||||
);
|
||||
Size::LEFTOVER
|
||||
}
|
||||
@@ -959,18 +957,18 @@ fn glyph_origins_compose_identically_when_drawn_and_when_retained() {
|
||||
}
|
||||
struct Frame {
|
||||
child: StrongWidget,
|
||||
frame: UiRegion,
|
||||
region: UiRegion,
|
||||
extent: UiRegion,
|
||||
}
|
||||
impl Widget for Frame {
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||
painter.widget_at(
|
||||
&self.child,
|
||||
[Some(self.region.x.len()), None],
|
||||
[
|
||||
Place::Fill(Part::From(self.extent.x)),
|
||||
Place::Fill(Part::From(self.extent.y)),
|
||||
],
|
||||
PlaceDesc::new(
|
||||
PlaceDescAxis::shifted(self.region.x).fills(),
|
||||
PlaceDescAxis::shifted(self.region.y).fills(),
|
||||
)
|
||||
.rel_base(Axis::X, self.frame.x.len()),
|
||||
);
|
||||
Size::LEFTOVER
|
||||
}
|
||||
@@ -986,15 +984,15 @@ fn glyph_origins_compose_identically_when_drawn_and_when_retained() {
|
||||
h.rsc.widgets_mut().set_region_node(text, node);
|
||||
let root = Frame {
|
||||
child: text.add_strong(&mut h.rsc),
|
||||
frame: UiRegion::FULL,
|
||||
region: UiRegion::FULL,
|
||||
extent: UiRegion::FULL,
|
||||
}
|
||||
.add(&mut h.rsc);
|
||||
h.set_root(root);
|
||||
for (start, end) in [(0.13, 0.83), (-0.17, 1.23), (0.31, 0.67)] {
|
||||
let before = draws.get();
|
||||
h.rsc[root].region.x = UiSpan::new(Len::px(13.125), Len::px(287.375));
|
||||
h.rsc[root].extent = UiRegion::new(
|
||||
h.rsc[root].frame.x = UiSpan::new(Len::px(13.125), Len::px(287.375));
|
||||
h.rsc[root].region = UiRegion::new(
|
||||
UiSpan::new(Len::rel(start), Len::rel(end)),
|
||||
UiSpan::new(Len::px(7.25), Len::rel(end)),
|
||||
);
|
||||
@@ -1117,7 +1115,7 @@ fn widening_and_restoring_a_contract_does_not_invalidate_its_reader() {
|
||||
assert_eq!(leaf_draws.get(), settled + 1);
|
||||
}
|
||||
#[test]
|
||||
fn padding_and_stack_boxes_follow_the_extent_without_drawing_again() {
|
||||
fn padding_and_stack_boxes_follow_the_region_without_drawing_again() {
|
||||
struct Observed<W> {
|
||||
widget: W,
|
||||
draws: Rc<Cell<usize>>,
|
||||
@@ -1130,23 +1128,22 @@ fn padding_and_stack_boxes_follow_the_extent_without_drawing_again() {
|
||||
}
|
||||
struct Frame {
|
||||
child: StrongWidget,
|
||||
extent: UiRegion,
|
||||
region: UiRegion,
|
||||
}
|
||||
impl Widget for Frame {
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||
painter.widget_at(
|
||||
&self.child,
|
||||
[None; 2],
|
||||
[
|
||||
Place::Fill(Part::From(self.extent.x)),
|
||||
Place::Fill(Part::From(self.extent.y)),
|
||||
],
|
||||
PlaceDesc::new(
|
||||
PlaceDescAxis::shifted(self.region.x).fills(),
|
||||
PlaceDescAxis::shifted(self.region.y).fills(),
|
||||
),
|
||||
);
|
||||
Size::LEFTOVER
|
||||
}
|
||||
}
|
||||
for node in [false, true] {
|
||||
let plant = |h: &mut Harness, extent| {
|
||||
let plant = |h: &mut Harness, region| {
|
||||
let draws = Rc::new(Cell::new(0));
|
||||
let leaf = rect(Color::BLUE).masked().add(&mut h.rsc);
|
||||
h.rsc.widgets_mut().set_region_node(leaf, node);
|
||||
@@ -1167,7 +1164,7 @@ fn padding_and_stack_boxes_follow_the_extent_without_drawing_again() {
|
||||
draws: draws.clone(),
|
||||
}
|
||||
.add_strong(&mut h.rsc);
|
||||
let root = Frame { child: pad, extent }.add(&mut h.rsc);
|
||||
let root = Frame { child: pad, region }.add(&mut h.rsc);
|
||||
h.set_root(root);
|
||||
(root, leaf, fixed, draws)
|
||||
};
|
||||
@@ -1182,13 +1179,13 @@ fn padding_and_stack_boxes_follow_the_extent_without_drawing_again() {
|
||||
let mut warm = Harness::new((403, 211));
|
||||
let (root, leaf, fixed, draws) = plant(&mut warm, at(0.13));
|
||||
for start in [0.13, -0.17, 0.31] {
|
||||
let extent = at(start);
|
||||
let region = at(start);
|
||||
let before = draws.get();
|
||||
warm.rsc[root].extent = extent;
|
||||
warm.rsc[root].region = region;
|
||||
warm.frame();
|
||||
assert_eq!(draws.get(), before);
|
||||
let mut cold = Harness::new((403, 211));
|
||||
let (_, other, other_fixed, _) = plant(&mut cold, extent);
|
||||
let (_, other, other_fixed, _) = plant(&mut cold, region);
|
||||
for (a, b) in [(leaf.id(), other.id()), (fixed.id(), other_fixed.id())] {
|
||||
assert_eq!(warm.region(&a), cold.region(&b));
|
||||
assert_eq!(primitive_bounds(&warm, a), primitive_bounds(&cold, b));
|
||||
@@ -1207,7 +1204,7 @@ fn padding_and_stack_boxes_follow_the_extent_without_drawing_again() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moving_an_extent_child_preserves_the_slot_chosen_from_its_measurement() {
|
||||
fn moving_a_childs_region_preserves_the_slot_chosen_from_its_measurement() {
|
||||
struct Measured;
|
||||
impl Widget for Measured {
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||
@@ -1224,14 +1221,14 @@ fn moving_an_extent_child_preserves_the_slot_chosen_from_its_measurement() {
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||
painter.widget_at(
|
||||
&self.child,
|
||||
[None; 2],
|
||||
[
|
||||
Place::Fill(Part::From(UiSpan::new(
|
||||
PlaceDesc::new(
|
||||
PlaceDescAxis::shifted(UiSpan::new(
|
||||
Len::px(self.start),
|
||||
Len::px(self.start + 200.0),
|
||||
))),
|
||||
Place::Fill(Part::From(UiSpan::FULL)),
|
||||
],
|
||||
))
|
||||
.fills(),
|
||||
PlaceDescAxis::shifted(UiSpan::FULL).fills(),
|
||||
),
|
||||
);
|
||||
Size::LEFTOVER
|
||||
}
|
||||
@@ -1256,7 +1253,7 @@ fn moving_an_extent_child_preserves_the_slot_chosen_from_its_measurement() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extent_frames_keep_fractional_reports_and_numeric_dependencies_valid() {
|
||||
fn changing_regions_keep_fractional_reports_and_numeric_dependencies_valid() {
|
||||
struct Container {
|
||||
child: StrongWidget,
|
||||
region: UiRegion,
|
||||
@@ -1266,18 +1263,17 @@ fn extent_frames_keep_fractional_reports_and_numeric_dependencies_valid() {
|
||||
painter
|
||||
.widget_at(
|
||||
&self.child,
|
||||
[None; 2],
|
||||
[
|
||||
Place::Within(Part::From(self.region.x)),
|
||||
Place::Within(Part::From(self.region.y)),
|
||||
],
|
||||
PlaceDesc::new(
|
||||
PlaceDescAxis::shifted(self.region.x),
|
||||
PlaceDescAxis::shifted(self.region.y),
|
||||
),
|
||||
)
|
||||
.size()
|
||||
}
|
||||
}
|
||||
struct Frame {
|
||||
child: StrongWidget,
|
||||
extent: UiRegion,
|
||||
region: UiRegion,
|
||||
answer: Rc<Cell<Size>>,
|
||||
}
|
||||
impl Widget for Frame {
|
||||
@@ -1286,11 +1282,10 @@ fn extent_frames_keep_fractional_reports_and_numeric_dependencies_valid() {
|
||||
painter
|
||||
.widget_at(
|
||||
&self.child,
|
||||
[None; 2],
|
||||
[
|
||||
Place::Fill(Part::From(self.extent.x)),
|
||||
Place::Fill(Part::From(self.extent.y)),
|
||||
],
|
||||
PlaceDesc::new(
|
||||
PlaceDescAxis::shifted(self.region.x).fills(),
|
||||
PlaceDescAxis::shifted(self.region.y).fills(),
|
||||
),
|
||||
)
|
||||
.size(),
|
||||
);
|
||||
@@ -1302,7 +1297,7 @@ fn extent_frames_keep_fractional_reports_and_numeric_dependencies_valid() {
|
||||
UiRegion::FULL,
|
||||
UiRegion::new(UiSpan::new(Len::rel(0.13), Len::rel(0.79)), UiSpan::FULL),
|
||||
] {
|
||||
let plant = |h: &mut Harness, extent| {
|
||||
let plant = |h: &mut Harness, outer| {
|
||||
let size = if fractional {
|
||||
Size {
|
||||
x: rel(0.5),
|
||||
@@ -1320,7 +1315,7 @@ fn extent_frames_keep_fractional_reports_and_numeric_dependencies_valid() {
|
||||
let answer = Rc::new(Cell::new(Size::ZERO));
|
||||
let root = Frame {
|
||||
child,
|
||||
extent,
|
||||
region: outer,
|
||||
answer: answer.clone(),
|
||||
}
|
||||
.add(&mut h.rsc);
|
||||
@@ -1330,15 +1325,166 @@ fn extent_frames_keep_fractional_reports_and_numeric_dependencies_valid() {
|
||||
let mut warm = Harness::new((403, 211));
|
||||
let (root, leaf, answer) = plant(&mut warm, UiRegion::FULL);
|
||||
for width in [191.125, 297.25, 83.75] {
|
||||
let extent =
|
||||
let region =
|
||||
UiRegion::new(UiSpan::new(Len::px(13.125), Len::px(width)), UiSpan::FULL);
|
||||
warm.rsc[root].extent = extent;
|
||||
warm.rsc[root].region = region;
|
||||
warm.frame();
|
||||
let mut cold = Harness::new((403, 211));
|
||||
let (_, other, other_answer) = plant(&mut cold, extent);
|
||||
let (_, other, other_answer) = plant(&mut cold, region);
|
||||
assert_eq!(answer.get(), other_answer.get());
|
||||
assert_eq!(warm.region(&leaf), cold.region(&other));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct OptionalMask {
|
||||
inner: StrongWidget,
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
impl Widget for OptionalMask {
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||
if self.enabled {
|
||||
painter.set_mask(UiRegion::FULL);
|
||||
}
|
||||
painter.widget(&self.inner);
|
||||
Size::LEFTOVER
|
||||
}
|
||||
}
|
||||
|
||||
fn primitive_masks(h: &Harness, id: WidgetId) -> Vec<MaskIdx> {
|
||||
h.render.active[&id]
|
||||
.primitives
|
||||
.iter()
|
||||
.map(|primitive| {
|
||||
let handle = &primitive.handle;
|
||||
h.render.layers[handle.layer].primitives()[handle.kind as usize]
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.instances()[handle.inst_idx]
|
||||
.mask_idx
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_redrawn_mask_keeps_reused_primitives_clipped_when_it_moves() {
|
||||
for node in [false, true] {
|
||||
let mut h = Harness::new((400, 200));
|
||||
let first = rect(Color::RED).height(50).add(&mut h.rsc);
|
||||
let inner = rect(Color::BLUE).add(&mut h.rsc);
|
||||
let draws = Rc::new(Cell::new(0));
|
||||
let child = Stretchy {
|
||||
inner: inner.add_strong(&mut h.rsc),
|
||||
draws: draws.clone(),
|
||||
}
|
||||
.add(&mut h.rsc);
|
||||
let masked = child.masked().add(&mut h.rsc);
|
||||
h.rsc.widgets_mut().set_region_node(masked, node);
|
||||
h.set_root((first, masked).span(Dir::DOWN));
|
||||
let mask = h.render.active[&masked.id()].mask;
|
||||
let settled = draws.get();
|
||||
h.rsc.widgets_mut().get_dyn_mut(masked.id());
|
||||
h.frame();
|
||||
assert_eq!(primitive_masks(&h, inner.id()), vec![mask]);
|
||||
assert_eq!(draws.get(), settled, "a mask repaint must reuse its child");
|
||||
assert_eq!(h.render.active[&masked.id()].mask, mask);
|
||||
h.set_len(first, Axis::Y, 10);
|
||||
h.frame();
|
||||
let clip = h.rsc.ui().masks[mask.idx()];
|
||||
let clip = h
|
||||
.render
|
||||
.moves
|
||||
.resolve(clip.move_idx, clip.region)
|
||||
.to_px(h.render.output_size());
|
||||
assert_eq!(clip, h.region(&masked).unwrap());
|
||||
assert_corners!(h, inner, (0, 10), (400, 200));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adding_and_removing_a_mask_updates_existing_primitives() {
|
||||
let mut h = Harness::new((400, 200));
|
||||
let inner = rect(Color::BLUE).add(&mut h.rsc);
|
||||
let masked = OptionalMask {
|
||||
inner: inner.add_strong(&mut h.rsc),
|
||||
enabled: false,
|
||||
}
|
||||
.add(&mut h.rsc);
|
||||
h.set_root(masked);
|
||||
for enabled in [true, false, true, false] {
|
||||
h.rsc[masked].enabled = enabled;
|
||||
h.frame();
|
||||
let mask = h.render.active[&masked.id()].mask;
|
||||
assert_eq!(mask == MaskIdx::NONE, !enabled);
|
||||
assert_eq!(primitive_masks(&h, inner.id()), vec![mask]);
|
||||
}
|
||||
assert_eq!(h.rsc.ui().masks.len(), 1, "retired slots must be reusable");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_empty_masks_slot_is_released_when_the_mask_is_removed_or_undrawn() {
|
||||
let mut h = Harness::new((400, 200));
|
||||
let (inner, _) = counted(&mut h, Size::LEFTOVER, false);
|
||||
let masked = OptionalMask {
|
||||
inner: inner.add_strong(&mut h.rsc),
|
||||
enabled: true,
|
||||
}
|
||||
.add(&mut h.rsc);
|
||||
let row = (masked,).span(Dir::DOWN).add(&mut h.rsc);
|
||||
h.set_root(row);
|
||||
for _ in 0..3 {
|
||||
h.rsc[masked].enabled = false;
|
||||
h.frame();
|
||||
h.rsc[masked].enabled = true;
|
||||
h.frame();
|
||||
let child = h.rsc[row].pop().unwrap();
|
||||
h.frame();
|
||||
h.rsc[row].push(child);
|
||||
h.frame();
|
||||
}
|
||||
assert_eq!(h.rsc.ui().masks.len(), 1);
|
||||
}
|
||||
|
||||
struct SharedChild(Rc<StrongWidget>);
|
||||
|
||||
impl Widget for SharedChild {
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||
painter.widget(self.0.as_ref()).size()
|
||||
}
|
||||
}
|
||||
|
||||
struct SwitchParent {
|
||||
choices: [StrongWidget; 2],
|
||||
choice: usize,
|
||||
}
|
||||
|
||||
impl Widget for SwitchParent {
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||
painter.widget(&self.choices[self.choice]).size()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_redrawn_subtree_is_not_undrawn_by_the_parent_it_left() {
|
||||
for node in [false, true] {
|
||||
let mut h = Harness::new((400, 200));
|
||||
let leaf = rect(Color::RED).width(40).add(&mut h.rsc);
|
||||
let held: StrongWidget = leaf.add_strong(&mut h.rsc);
|
||||
let shared = Rc::new(held);
|
||||
let first = SharedChild(shared.clone()).add_strong(&mut h.rsc);
|
||||
let second = SharedChild(shared).add_strong(&mut h.rsc);
|
||||
h.rsc.widgets_mut().set_region_node(&second, node);
|
||||
let root = SwitchParent {
|
||||
choices: [first, second],
|
||||
choice: 0,
|
||||
}
|
||||
.add(&mut h.rsc);
|
||||
h.set_root(root);
|
||||
let before = h.region(&leaf);
|
||||
h.rsc[root].choice = 1;
|
||||
h.frame();
|
||||
assert_eq!(h.region(&leaf), before);
|
||||
}
|
||||
}
|
||||
+8
-4
@@ -14,7 +14,7 @@
|
||||
#[path = "scenario/mod.rs"]
|
||||
mod scenario;
|
||||
|
||||
use iris::random::{Edits, plan};
|
||||
use iris::random::{Edits, Plan, plan};
|
||||
use scenario::{ALL, Case, diverges, env, over_seeds};
|
||||
|
||||
/// How deep the generator branches. The generator widens two to four ways per
|
||||
@@ -32,8 +32,11 @@ fn depth() -> usize {
|
||||
const SEEDS: [u64; 10] = [1, 2, 3, 5, 8, 10, 13, 20, 86, 98];
|
||||
|
||||
fn check(seed: u64, depth: usize, case: Case) {
|
||||
let grown = plan(seed, depth, &Edits::default());
|
||||
if let Some(how) = diverges(&grown, case, seed) {
|
||||
check_plan(&plan(seed, depth, &Edits::default()), seed, depth, case);
|
||||
}
|
||||
|
||||
fn check_plan(grown: &Plan, seed: u64, depth: usize, case: Case) {
|
||||
if let Some(how) = diverges(grown, case, seed) {
|
||||
panic!(
|
||||
"seed {seed} at depth {depth} differs after {}: {how}\n\
|
||||
reduce it with SHRINK_SEED={seed} SHRINK_DEPTH={depth} \
|
||||
@@ -119,8 +122,9 @@ fn a_long_run_of_seeds_agrees() {
|
||||
None => (1..=env("IRIS_GENERATED_SEEDS", 100_u64)).collect(),
|
||||
};
|
||||
over_seeds(seeds, |seed| {
|
||||
let grown = plan(seed, depth, &Edits::default());
|
||||
for case in ALL {
|
||||
check(seed, depth, case);
|
||||
check_plan(&grown, seed, depth, case);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -406,8 +406,8 @@ fn describe_widget(id: WidgetId, h: &Harness) -> String {
|
||||
fn record(id: WidgetId, h: &Harness) -> String {
|
||||
let active = &h.render.active[&id];
|
||||
format!(
|
||||
"frame {} ask {} box {} size {}",
|
||||
active.frame, active.part, active.extent, active.size,
|
||||
"rel_base {} region {} placement {} size {}",
|
||||
active.rel_base, active.region, active.placement, active.size,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -444,12 +444,6 @@ pub fn diverges(plan: &Plan, case: Case, seed: u64) -> Option<String> {
|
||||
cold.state.root = Some(root);
|
||||
cold.frame();
|
||||
|
||||
let places: HashMap<WidgetId, usize> = tree
|
||||
.ids
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, &id)| (id, i))
|
||||
.collect();
|
||||
let mut drawn = 0;
|
||||
for (i, (&w, &c)) in tree.ids.iter().zip(&cold_tree.ids).enumerate() {
|
||||
let (got, want) = (warm.region(&w), cold.region(&c));
|
||||
@@ -457,6 +451,12 @@ pub fn diverges(plan: &Plan, case: Case, seed: u64) -> Option<String> {
|
||||
if got == want {
|
||||
continue;
|
||||
}
|
||||
let places: HashMap<WidgetId, usize> = tree
|
||||
.ids
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, &id)| (id, i))
|
||||
.collect();
|
||||
// Where two trees disagree is rarely where the cause is, so the
|
||||
// ancestry comes with it, marking the widgets that own a region.
|
||||
let mut chain = Vec::new();
|
||||
|
||||
Reference in new issue
Block a user