WIP: a widget's region stays put and its placement moves in it
The protocol split: `region` is the box a parent gives a widget -- what a fraction it declares or reports is a fraction of, and the coordinates every region it writes composes within -- and it is the same box on the ask that measures and the ask that places. `placement` is what of that region the drawing takes, chosen by the parent per axis or by the widget's own answer and alignment. That is what stops a fraction being resolved twice: the placing ask no longer hands the widget its own answer as its box, so nothing under it re-resolves against a box that came from its own report. `reports_of` and `decided` are gone, folded into the two regions; `box_of` is gone; `declared_box` becomes `ask_box`, which gives a rule the region's length and takes the position from the placement. 84 of 92 suite tests pass. Five text and region-node cases still diverge warm against cold, and three count a second widget draw where a span's measuring ask and its placing ask give different placements.
This commit is contained in:
1 parent
e44dea34b4
commit
5fcace1bfa
11 files changed
+346
-206
No files matched your search
+88
-51
@@ -1,6 +1,6 @@
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
use crate::layout_diagnostics::{self as diag, Counter, ReuseOutcome, TimerKind};
|
||||
use crate::ui::painter::{declared_box, declared_lens, placed_box, placed_lens};
|
||||
use crate::ui::painter::{ask_box, declared_lens, placed_box, placed_lens};
|
||||
use crate::{
|
||||
ActiveData, Axis, DrawLayers, Holds, IdLike, LayoutLen, Len, MaskIdx, MoveIdx, Moves, Painter,
|
||||
PixelRegion, Px, PxVec2, Rel, Size, StrongWidget, UiRegion, UiRsc, UiSpan, UiVec2, Weight,
|
||||
@@ -30,10 +30,27 @@ pub(super) struct DrawInfo {
|
||||
/// parent's own, which is where every pixel length in layout comes from.
|
||||
pub px: PxVec2,
|
||||
pub offered_px: PxVec2,
|
||||
/// The axes along which the parent chose this box from the widget's own
|
||||
/// answer, so the answer is not placed inside it again. See
|
||||
/// [`Painter::widget_at`].
|
||||
pub decided: [bool; 2],
|
||||
/// What of that region the parent chose to put the drawing in, per axis.
|
||||
/// `None` leaves the axis to the widget's own answer and its alignment.
|
||||
/// See [`Painter::widget_at`].
|
||||
pub placement: [Option<UiSpan>; 2],
|
||||
}
|
||||
|
||||
impl DrawInfo {
|
||||
/// The axes the parent chose the placement on, which are the axes the
|
||||
/// answer is not placed inside its region again.
|
||||
fn decided(&self) -> [bool; 2] {
|
||||
self.placement.map(|span| span.is_some())
|
||||
}
|
||||
|
||||
/// The placement to draw in before the answer is known: what the parent
|
||||
/// chose, and the whole region on any axis it left open.
|
||||
fn offered_placement(&self) -> UiRegion {
|
||||
UiRegion {
|
||||
x: self.placement[0].unwrap_or(UiSpan::FULL),
|
||||
y: self.placement[1].unwrap_or(UiSpan::FULL),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UiRenderState {
|
||||
@@ -127,7 +144,7 @@ impl UiRenderState {
|
||||
offer_len: UiVec2::FULL_SIZE,
|
||||
px,
|
||||
offered_px: px,
|
||||
decided: [false; 2],
|
||||
placement: [None; 2],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,11 +198,13 @@ impl UiRenderState {
|
||||
}
|
||||
|
||||
fn root_region(id: WidgetId, widgets: &Widgets) -> UiRegion {
|
||||
declared_box(
|
||||
ask_box(
|
||||
UiRegion::FULL,
|
||||
declared_lens(widgets, id),
|
||||
widgets.alignment(id),
|
||||
[None; 2],
|
||||
)
|
||||
.0
|
||||
}
|
||||
|
||||
pub(super) fn draw_inner(
|
||||
@@ -217,41 +236,41 @@ impl UiRenderState {
|
||||
if old.is_none() {
|
||||
old = self.remove(id, false, rsc);
|
||||
}
|
||||
self.draw_at(id, region, info, old.take(), rsc)
|
||||
self.draw_at(id, region, info.offered_placement(), info, old.take(), rsc)
|
||||
});
|
||||
|
||||
let declared = declared_lens(rsc.widgets(), id);
|
||||
// The second, final ask is in a box chosen from the answer on both
|
||||
// axes, which is also what makes it terminate.
|
||||
let lens = placed_lens(answer.0, declared, info.decided);
|
||||
let placed = placed_box(region, lens, align);
|
||||
let placed_info = DrawInfo {
|
||||
px: lens.to_px(info.px),
|
||||
decided: [true; 2],
|
||||
..info
|
||||
// Where the drawing goes, in the region's own coordinates: what the
|
||||
// parent chose, and on any axis it left open, what the answer took of
|
||||
// the region placed by the widget's alignment. The region itself does
|
||||
// not change, so nothing under it resolves a fraction a second time.
|
||||
let lens = placed_lens(answer.0, declared, info.decided());
|
||||
let own = placed_box(UiRegion::FULL, lens, align);
|
||||
let placement = UiRegion {
|
||||
x: info.placement[0].unwrap_or(own.x),
|
||||
y: info.placement[1].unwrap_or(own.y),
|
||||
};
|
||||
self.place(id, placed, placed_info, rsc);
|
||||
self.place(id, region, placement, info, rsc);
|
||||
|
||||
// The answer is only reusable while both parts of the operation are:
|
||||
// what the widget reported in the box it was asked in, and what it
|
||||
// drew in the box its report selected. Express the latter's contract
|
||||
// back in terms of the box asked in before handing it to the parent.
|
||||
// what the widget reported, and what it drew once placed. Both are
|
||||
// ranges of the region's own lengths, since that is the box neither
|
||||
// ask changes.
|
||||
let drawing_holds = self.active[&id].holds;
|
||||
let mut settled = answer;
|
||||
for axis in AXES {
|
||||
settled.1[axis as usize] =
|
||||
settled.1[axis as usize].and(drawing_holds[axis as usize].through(lens.axis(axis)));
|
||||
settled.1[axis as usize] = settled.1[axis as usize].and(drawing_holds[axis as usize]);
|
||||
}
|
||||
|
||||
let active = self.active.get_mut(&id).unwrap();
|
||||
// Whoever asked owns how the box was reached: the box it stated, and
|
||||
// what of that box the answer then took. A local redraw asks the
|
||||
// same question again from these.
|
||||
active.given = region;
|
||||
active.region = region;
|
||||
active.given_len = info.given_len;
|
||||
active.offer_len = info.offer_len;
|
||||
active.answer = settled;
|
||||
active.decided = info.decided;
|
||||
active.decided = info.decided();
|
||||
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
|
||||
@@ -268,17 +287,33 @@ impl UiRenderState {
|
||||
settled
|
||||
}
|
||||
|
||||
/// Draws a widget in the final box its answer chose, reusing the drawing
|
||||
/// already there where its retained contract holds for that box. The
|
||||
/// symbolic box can be unchanged while the box it sits in changed pixel
|
||||
/// length, so what reuse checks is the box in pixels.
|
||||
fn place(&mut self, id: WidgetId, placed: UiRegion, info: DrawInfo, rsc: &mut dyn UiRsc) {
|
||||
if self.try_reuse(id, placed, info, rsc).is_none() {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::PlaceRedraws);
|
||||
let old = self.remove(id, false, rsc);
|
||||
self.draw_at(id, placed, info, old, rsc);
|
||||
/// Puts the drawing where the answer says it goes. A drawing that never
|
||||
/// read its placement is the same drawing wherever it is put, so all that
|
||||
/// changes is what box the widget is recorded as occupying; one that read
|
||||
/// it is drawn again, and only where the placement it read has moved.
|
||||
fn place(
|
||||
&mut self,
|
||||
id: WidgetId,
|
||||
region: UiRegion,
|
||||
placement: UiRegion,
|
||||
info: DrawInfo,
|
||||
rsc: &mut dyn UiRsc,
|
||||
) {
|
||||
let active = &self.active[&id];
|
||||
// A drawing that never read its placement is the same drawing
|
||||
// wherever it is put, so only what the widget is recorded as
|
||||
// occupying changes; one that read it stands only for the placement
|
||||
// it read. Either way the drawing still has to be where the region
|
||||
// now is, which is what a retained answer on its own does not do.
|
||||
let stands = !active.reads_placement || active.placement == placement;
|
||||
if stands && self.try_reuse(id, region, info, rsc).is_some() {
|
||||
self.active.get_mut(&id).unwrap().placement = placement;
|
||||
return;
|
||||
}
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::PlaceRedraws);
|
||||
let old = self.remove(id, false, rsc);
|
||||
self.draw_at(id, region, placement, info, old, rsc);
|
||||
}
|
||||
|
||||
/// Calls a widget's `draw` and keeps what it drew in `region`.
|
||||
@@ -286,6 +321,7 @@ impl UiRenderState {
|
||||
&mut self,
|
||||
id: WidgetId,
|
||||
region: UiRegion,
|
||||
placement: UiRegion,
|
||||
info: DrawInfo,
|
||||
old: Option<ActiveData>,
|
||||
rsc: &mut dyn UiRsc,
|
||||
@@ -317,6 +353,8 @@ impl UiRenderState {
|
||||
let mut painter = Painter {
|
||||
state: self,
|
||||
region: local,
|
||||
placement,
|
||||
reads_placement: false,
|
||||
px,
|
||||
mask: info.mask,
|
||||
layer: info.layer,
|
||||
@@ -351,6 +389,8 @@ impl UiRenderState {
|
||||
state: _,
|
||||
rsc: _,
|
||||
region: _,
|
||||
placement: _,
|
||||
reads_placement,
|
||||
px: _,
|
||||
mask,
|
||||
textures,
|
||||
@@ -425,7 +465,7 @@ impl UiRenderState {
|
||||
offer_len: UiVec2::FULL_SIZE,
|
||||
px,
|
||||
offered_px: px,
|
||||
decided: [false; 2],
|
||||
placement: [None; 2],
|
||||
},
|
||||
rsc,
|
||||
);
|
||||
@@ -436,10 +476,8 @@ impl UiRenderState {
|
||||
let active = ActiveData {
|
||||
id,
|
||||
region,
|
||||
// The box a placing ask draws in is a part of the one its parent
|
||||
// gave, which `draw_inner` writes back over these once the
|
||||
// placement is done.
|
||||
given: region,
|
||||
placement,
|
||||
reads_placement,
|
||||
given_len: info.given_len,
|
||||
offer_len: info.offer_len,
|
||||
// Whoever asked writes the answer, if this was the asking.
|
||||
@@ -454,7 +492,7 @@ impl UiRenderState {
|
||||
children,
|
||||
size_deps,
|
||||
declared: declared_lens(rsc.widgets(), id),
|
||||
decided: info.decided,
|
||||
decided: info.decided(),
|
||||
own_align: rsc.widgets().alignment(id),
|
||||
move_idx,
|
||||
parent_move: info.parent_move,
|
||||
@@ -540,11 +578,7 @@ impl UiRenderState {
|
||||
// Nothing above the root: the window is where a fraction becomes
|
||||
// pixels, which is also the whole of the box the root is given.
|
||||
let (parent_px, parent_offer) = match active.parent.and_then(|p| self.active.get(&p)) {
|
||||
Some(parent) => {
|
||||
let (given, offer) = self.asked_px(parent.id);
|
||||
let lens = placed_lens(parent.answer.0, parent.declared, parent.decided);
|
||||
(lens.to_px(given), offer)
|
||||
}
|
||||
Some(parent) => self.asked_px(parent.id),
|
||||
None => (self.output_size, self.output_size),
|
||||
};
|
||||
let px = active.given_len.to_px(parent_px);
|
||||
@@ -639,7 +673,6 @@ impl UiRenderState {
|
||||
self.redepth(id, info.depth);
|
||||
let active = self.active.get_mut(&id).unwrap();
|
||||
active.region = region;
|
||||
active.given = region;
|
||||
active.given_len = info.given_len;
|
||||
active.offer_len = info.offer_len;
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
@@ -694,7 +727,6 @@ impl UiRenderState {
|
||||
rsc: &mut dyn UiRsc,
|
||||
) {
|
||||
let active = self.active.get_mut(&id).unwrap();
|
||||
active.given = remap.apply(active.given);
|
||||
if active.move_idx != parent_move {
|
||||
let region = remap.apply(active.region);
|
||||
active.region = region;
|
||||
@@ -792,7 +824,8 @@ impl UiRenderState {
|
||||
ActiveData {
|
||||
id,
|
||||
region: UiRegion::FULL,
|
||||
given: UiRegion::FULL,
|
||||
placement: UiRegion::FULL,
|
||||
reads_placement: false,
|
||||
given_len: UiVec2::FULL_SIZE,
|
||||
offer_len: UiVec2::FULL_SIZE,
|
||||
answer: (size, [Holds::ANY; 2]),
|
||||
@@ -951,8 +984,9 @@ impl UiRenderState {
|
||||
pub fn window_region(&self, id: &impl IdLike) -> Option<PixelRegion> {
|
||||
let active = self.active.get(&id.id())?;
|
||||
active.drawn.then(|| {
|
||||
let placed = active.placement.within(&active.region);
|
||||
self.moves
|
||||
.resolve(active.parent_move, active.region)
|
||||
.resolve(active.parent_move, placed)
|
||||
.to_px(self.output_size)
|
||||
})
|
||||
}
|
||||
@@ -1031,9 +1065,12 @@ impl UiRenderState {
|
||||
offer_len: active.offer_len,
|
||||
px: given_px,
|
||||
offered_px,
|
||||
decided: active.decided,
|
||||
// The same question its parent asked: the axes its parent chose
|
||||
// the placement on, put back where they were.
|
||||
placement: AXES
|
||||
.map(|axis| active.decided[axis as usize].then(|| *active.placement.axis(axis))),
|
||||
};
|
||||
let (given, was_answer) = (active.given, active.answer);
|
||||
let (given, was_answer) = (active.region, active.answer);
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::LocalRedraws);
|
||||
|
||||
|
||||
Reference in new issue
Block a user