#[cfg(feature = "layout-diagnostics")] use crate::layout_diagnostics::{self as diag, Counter, ReuseOutcome, TimerKind}; use crate::{ ActiveData, Answer, Axis, Bounds, Declared, DrawLayers, IdLike, LayoutHolds, LayoutLen, Len, MaskIdx, MoveIdx, Moves, Painter, PixelRegion, PlaceDesc, PxVec2, Size, StrongWidget, UiRegion, UiRsc, UiSpan, UiVec2, Weight, WidgetId, Widgets, ui::painter::Ask, util::{HashMap, Vec2}, }; /// Where a widget is drawn: what its parent decides about the draw besides /// the boxes themselves. #[derive(Clone, Copy)] pub(super) struct DrawInfo { pub layer: usize, pub parent: Option, pub depth: usize, pub parent_move: MoveIdx, pub region_node: bool, pub mask: MaskIdx, /// What a fraction declared or reported under this widget is a fraction /// of, as a length of the window. pub rel_base: UiVec2, /// The box the widget is asked in, in its parent region node's /// coordinates. pub region: UiRegion, /// Where the widget is put, and what its parent offered it, as parts of /// the parent's box. See [`PlaceDesc`]. The two are one place until a /// rule of the widget's own takes it past the offer, or the parent puts /// the answer somewhere else. pub placed: PlaceDesc, pub asked: PlaceDesc, /// What the ask made of the widget's own rules. See [`Ask::declared`] /// and [`Ask::bounds`]. pub declared: Declared, pub bounds: Bounds, /// What the ask that gave it those two holds for. See [`Ask::holds`]. pub ask_holds: LayoutHolds, /// Whether the parent already asked about this widget in this draw. pub re_asked: bool, } /// What one draw of a widget came to: the answer it gave, and the boxes and /// windows the drawing that gave it holds for. The two are separate ranges -- /// a drawing can be invalid where its answer still stands. pub(super) struct Drawn { pub answer: Answer, pub drawing_holds: LayoutHolds, } /// 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 { /// The widget whose box this is, and nothing for the window. pub id: Option, pub region: UiRegion, pub rel_base: UiVec2, pub depth: usize, pub move_idx: MoveIdx, pub mask: MaskIdx, } impl Placing { /// The window, which is what the root is placed within. Nothing above the /// root narrowed a box or chose where it goes, so it is asked in the whole /// output and its fractions are of the whole output -- an ordinary ask, /// from the one box nobody drew. pub const WINDOW: Self = Self { id: None, region: UiRegion::FULL, rel_base: UiVec2::FULL_SIZE, depth: 0, move_idx: MoveIdx::NONE, mask: MaskIdx::NONE, }; } pub struct UiRenderState { pub active: HashMap, pub layers: DrawLayers, pub(super) output_size: PxVec2, old_root: Option, /// Whether the output has changed since the last update. A frame is /// owed for that whether or not anything has to be drawn again: every /// fraction becomes pixels against the output, in the shader's uniform /// as well as here. resized: bool, /// A widget's move slot, which outlives any one `ActiveData`: a redraw /// replaces that while its children go on pointing at the slot. slots: HashMap, /// Widgets waiting for an ancestor to draw them, so the walk down the /// depths does not pick one up again at its own depth. deferred: crate::util::HashSet, /// What the walk has left to settle, deepest last. Ordered rather than /// searched for, so finding the next one is not a pass over the marks. pending: std::collections::BinaryHeap<(usize, WidgetId)>, pub(super) requests: crate::RequestArena, changed: Vec, request_readers: HashMap>, pub moves: Moves, } impl UiRenderState { pub fn new() -> Self { Self { active: Default::default(), layers: Default::default(), output_size: PxVec2::ZERO, old_root: None, slots: Default::default(), deferred: Default::default(), pending: Default::default(), requests: Default::default(), changed: Vec::new(), request_readers: Default::default(), moves: Default::default(), resized: false, } } /// The window, in whatever the platform measures it in, onto the grid /// everything below it is decided on. No move entry holds it: a chain /// bottoms out in `MoveIdx::NONE`, which is the window, and the window's /// size is applied where a fraction becomes pixels -- here in `to_px`, /// and in the shader by its uniform. A resize therefore rewrites no /// retained entry at all. /// /// The root is the only widget a resize marks, and only where the new /// output invalidates its answer or its drawing. The latter includes /// children whose size it never read. Where either fails, the ordinary walk /// draws the root, and each widget's own range decides how far down the /// new length reaches. pub fn resize(&mut self, size: impl Into, widgets: &mut Widgets) { let size = PxVec2::from_f32(size.into()); if size == self.output_size { return; } self.output_size = size; self.resized = true; 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 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(|answer| answer.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); } } /// The root's first draw: the ask [`Placing::WINDOW`] answered, with the /// bookkeeping a widget with no parent carries. fn root_info(&self, ask: &Ask, region_node: bool) -> DrawInfo { DrawInfo { layer: 0, parent: None, depth: Placing::WINDOW.depth + 1, parent_move: MoveIdx::NONE, region_node, mask: MaskIdx::NONE, rel_base: ask.rel_base, region: ask.region, placed: ask.place, asked: PlaceDesc::WHOLE, declared: ask.declared, bounds: ask.bounds, ask_holds: ask.holds, re_asked: false, } } pub fn output_size(&self) -> PxVec2 { self.output_size } pub fn update<'a>(&mut self, root: impl Into>, rsc: &mut dyn UiRsc) { #[cfg(feature = "layout-diagnostics")] diag::bump(Counter::Updates); #[cfg(feature = "layout-diagnostics")] let _update = diag::timer(TimerKind::Update); // safety mechanism for memory leaks; might wanna return a result instead so user can // decide whether to panic or not if !rsc.widgets().waiting.is_empty() { let widgets = rsc.widgets(); let len = widgets.waiting.len(); let all: Vec<_> = widgets .waiting .iter() .map(|&w| format!("'{}' ({w:?})", widgets.label(w))) .collect(); panic!( "{len} widget(s) were never upgraded\n\ this is likely a memory leak; consider upgrading to strong if you plan on using it later\n\ weak widgets: {all:#?}" ); } self.requests.reset(); let root = root.into(); if self.root_changed(root) { self.redraw_all(root, rsc); self.old_root = root.map(|r| r.id()); } self.resized = false; if rsc.widgets().has_updates() { self.redraw_updates(rsc); } self.free(rsc); } fn redraw_all(&mut self, root: Option<&StrongWidget>, rsc: &mut dyn UiRsc) { #[cfg(feature = "layout-diagnostics")] let _layout = diag::timer(TimerKind::FullLayout); self.clear(rsc); if let Some(id) = root { let ask = Placing::WINDOW.ask( rsc.widgets(), &mut self.requests, self.output_size, id.id(), PlaceDesc::WHOLE, ); let info = self.root_info(&ask, rsc.widgets().is_region_node(id.id())); self.draw_inner(id.id(), info, None, rsc); } } pub(super) fn draw_inner( &mut self, id: WidgetId, info: DrawInfo, mut old: Option, rsc: &mut dyn UiRsc, ) -> Drawn { 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, region, region.to_px(self.output_size).size(), info.region_node, ); } let align = rsc.widgets().alignment(id); // Nothing this widget measured can be dirty while it draws: layout is // one bottom-up walk, so anything deeper has settled or deferred to // its own parent, and a deferred one leaves that parent marked. let stale = rsc.widgets().needs_redraw.contains(&id); // The widget draws once, in the box it is asked in, and its answer // is placed inside that box by re-expressing the drawing. The box the // answer chose is never a question: nothing is drawn again in it, so // 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, region, info)) .flatten() .and_then(|answer| { let placed = info.placed.placement(region, answer.size, align); self.try_reuse(id, region, placed, info, rsc) .then_some(answer) }); let answer = reused.unwrap_or_else(|| { if old.is_none() { old = self.remove(id, false, 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 placed = info.placed.placement(region, answer.size, 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 rel base it stated, // and what of its own box it asked in. A local redraw asks the same // question again from these. active.rel_base = info.rel_base; active.re_asked = info.re_asked; active.answer = Some(answer); active.asked = info.asked; active.region = region; active.placed = info.placed; active.own_align = align; // 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) { old_parent.children.retain(|child| *child != id); } Drawn { answer, drawing_holds, } } /// Calls a widget's `draw` and keeps what it drew in `region`. fn draw_at( &mut self, id: WidgetId, region: UiRegion, info: DrawInfo, old: Option, rsc: &mut dyn UiRsc, ) -> Answer { 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, region.as_translation()), region.at_origin(), 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, region, self.slots.remove(&id)), }; let mask_slot = old .as_ref() .and_then(|old| old.mask_region.map(|_| old.mask)); let (mut old_children, textures, primitives, request_deps, mut scratch) = match old { Some(old) => ( old.children, old.textures, old.primitives, old.request_deps, old.scratch, ), None => Default::default(), }; // Every one of these is a buffer this widget's last draw filled and // `remove` emptied, kept for its capacity alone. A drawing whose // primitives were still in it would record them twice. debug_assert!( textures.is_empty() && primitives.is_empty() && request_deps.is_empty(), "'{}' ({id:?}) was drawn again over what its last draw left", rsc.widgets().label(id) ); let children = std::mem::take(&mut scratch.children); let size_deps = std::mem::take(&mut scratch.size_deps); let under = std::mem::take(&mut scratch.under); rsc.widgets_mut().needs_redraw.remove(&id); let window = self.output_size; let mut painter = Painter { state: self, rel_base, region, window, mask: info.mask, layer: info.layer, own_layer: info.layer, id, textures, primitives, mask_region: None, mask_slot, children, size_deps, request_deps, scratch, own: LayoutHolds::ANY, under, answer_under: LayoutHolds::ANY, depth: info.depth, move_idx, rsc, }; #[cfg(feature = "layout-diagnostics")] { diag::bump(Counter::WidgetDraws); diag::draw_widget(id, painter.rsc.widgets().label(id)); } let mut widget = painter.rsc.widgets().get_dyn_dynamic(id); let size = widget.draw(&mut painter); drop(widget); #[cfg(feature = "layout-diagnostics")] diag::size_reported(id, size); let Painter { state: _, rsc: _, rel_base: _, region: _, window: _, mask, textures, primitives, mask_region, mask_slot, own, answer_under, children, mut size_deps, request_deps, mut scratch, mut under, move_idx, layer, own_layer: _, depth: _, id, } = painter; debug_assert!( Self::hints_agree(id, size, rsc), "'{}' ({id:?}) drew a size its size_hint disagrees with", rsc.widgets().label(id) ); // 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 // rel base is the answer wherever the ask declared a length: it was // resolved into the rel base when the widget 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| { if rules[axis].deferred().is_some() { return info.rel_base[axis].into(); } match rules[axis].exact() { None => reported, Some(len) if len.leftover == Weight::ZERO => LayoutLen { rel: info.rel_base[axis].rel, px: info.rel_base[axis].px, leftover: Weight::ZERO, }, Some(len) => len.within_len(info.rel_base[axis]), } }; let mut size = Size { x: ruled(Axis::X, size.x), y: ruled(Axis::Y, size.y), }; // A bound is a promise about the length as well as about the box: a // widget that drew past the box it was given -- a text too tall for // it, an image at its own size under a cap -- is still held to what // its rule allows. // // Held here rather than taken from the box, even where the bound // decided that box. What a widget answers is its own, and a bound // that replaced the answer would make a share into a fixed length // the moment a box was long enough -- which is a length the span // dividing that box decided from this answer, so the two would // choose each other. A share is left alone here for the same reason: // it is a length only to whoever divides one, and the box that // divider gives is a box this widget is asked in, where the bound is // applied to it. // Widgets may widen their own read ranges, but not the ask's constraints. let mut own_holds = own.and(info.ask_holds); for axis in Axis::BOTH { let answer = size[axis]; if answer.leftover != Weight::ZERO { continue; } let (held, kept) = info.bounds[axis].outside(answer.without_leftover(), window[axis]); own_holds[axis].window = own_holds[axis].window.and(kept); if let Some(held) = held { size[axis] = held.into(); } } // 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 rel base took for its drawing. A bound counts: which side of it // the box fell was decided against this rel base, and the same box of // a different one can fall on the other. for axis in Axis::BOTH { if rules[axis].has_fraction() { own_holds[axis].rel_base = Some(info.rel_base[axis]); } } // A widget that clipped its contents to its box drew nothing outside // it, so reporting more than the box asks to be placed at a length it // does not occupy -- and its parent would place the part it cut off. // Overflowing is otherwise ordinary: a text too tall for the box it // was offered reports the height it needs. debug_assert!( mask == info.mask || Axis::BOTH.into_iter().all(|axis| size.within_box( region, self.output_size, axis )), "'{}' ({id:?}) clips to {} and reports {size}", rsc.widgets().label(id), region.to_px(window), ); for c in &old_children { if !children.contains(c) { 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); } let answer_holds = own_holds.and(answer_under); let holds = under .iter() .fold(answer_holds, |holds, (_, child)| holds.and(*child)); debug_assert!( holds.contains(self.output_size, info.rel_base, region), "'{}' ({id:?}) drew in {}, outside the ranges it reported: {holds:?}", rsc.widgets().label(id), region.to_px(window), ); // What it asked about and did not draw is still something it asked, // and a change there has to reach it. Asking answered whatever mark // it had: a hint is read live, and a drawing is not kept past one. for &dep in &size_deps { if !children.contains(&dep) { self.asked( dep, DrawInfo { layer, parent: Some(id), depth: info.depth + 1, parent_move: move_idx, region_node: false, mask, rel_base: UiVec2::FULL_SIZE, region: UiRegion::FULL, placed: PlaceDesc::WHOLE, asked: PlaceDesc::WHOLE, declared: Declared::NONE, bounds: Bounds::ANY, ask_holds: LayoutHolds::ANY, re_asked: false, }, rsc, ); rsc.widgets_mut().needs_redraw.remove(&dep); } } for &dep in &request_deps { self.request_readers.entry(dep).or_default().insert(id); } old_children.clear(); size_deps.clear(); under.clear(); scratch.children = old_children; scratch.size_deps = size_deps; scratch.under = under; let active = ActiveData { id, placement: region, rel_base: info.rel_base, placed: info.placed, asked: info.asked, region, // Whoever asked writes the answer. answer: None, re_asked: info.re_asked, size, holds, drawn: true, parent: info.parent, depth: info.depth, textures, primitives, mask_region, children, request_deps, scratch, declared: info.declared, bounds: info.bounds, own_align: rsc.widgets().alignment(id), move_idx, parent_move: info.parent_move, mask, parent_mask: info.mask, layer: info.layer, }; rsc.on_draw(&active); self.active.insert(id, active); Answer { size, holds: answer_holds, } } /// Keeps a region node's entry across redraws because descendants retain /// its index. fn move_slot(&mut self, id: WidgetId, parent: MoveIdx, region: UiRegion) -> MoveIdx { if let Some(&idx) = self.slots.get(&id) { self.moves.set_parent(idx, parent); self.moves.set(idx, region); return idx; } let idx = self.moves.push(parent, region); self.slots.insert(id, idx); idx } /// Removes a region node only after its descendants stop naming it. fn drop_slot(&mut self, id: WidgetId) { if let Some(idx) = self.slots.remove(&id) { self.moves.remove(idx); } } /// The answer to an ask can be retained independently of where its /// drawing ended up. Alignment is exactly that case: the first box is the /// question and the smaller placed box holds the drawing. Whether the /// answer is stale at all is its caller's question, asked once there. fn retained_answer(&self, id: WidgetId, region: UiRegion, info: DrawInfo) -> Option { let active = self.active.get(&id)?; if !active.drawn || active.is_region_node() != info.region_node || active.parent_move != info.parent_move || active.bounds != info.bounds { return None; } let answer = active.answer?; answer .holds .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 `placed`, where the answer places it. fn try_reuse( &mut self, id: WidgetId, region: UiRegion, placed: UiRegion, info: DrawInfo, rsc: &mut dyn UiRsc, ) -> bool { #[cfg(feature = "layout-diagnostics")] diag::bump(Counter::ReuseAttempts); if rsc.widgets().needs_redraw.contains(&id) { #[cfg(feature = "layout-diagnostics")] { diag::bump(Counter::ReuseDirty); diag::reuse(id, ReuseOutcome::Dirty); } return false; } let Some(active) = self.active.get(&id) else { return false; }; if !active.drawn { #[cfg(feature = "layout-diagnostics")] { diag::bump(Counter::ReuseUndrawn); diag::reuse(id, ReuseOutcome::Undrawn); } return false; } if active.is_region_node() != info.region_node { #[cfg(feature = "layout-diagnostics")] { diag::bump(Counter::ReuseWrongNode); diag::reuse(id, ReuseOutcome::WrongNode); } return false; } // Drawn on another layer: the drawing sits in that layer's list and // paints at its moment, which no amount of geometry says. A container // that measures a child by drawing it and then draws it again where // it belongs -- `Stack`, over its background -- asks the second time // on a layer the first answer is not good for. if active.layer != info.layer { #[cfg(feature = "layout-diagnostics")] { diag::bump(Counter::ReuseWrongLayer); diag::reuse(id, ReuseOutcome::WrongLayer); } return false; } // Its primitives name the mask it inherited, and a masking parent // that redrew pushed another: keeping them would clip them by one // nothing updates again. if active.parent_mask != info.mask { #[cfg(feature = "layout-diagnostics")] { diag::bump(Counter::ReuseWrongMask); diag::reuse(id, ReuseOutcome::WrongMask); } return false; } // 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 { #[cfg(feature = "layout-diagnostics")] { diag::bump(Counter::ReuseWrongParent); diag::reuse(id, ReuseOutcome::WrongParent); } return false; } // 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.rel_base, region) { #[cfg(feature = "layout-diagnostics")] diag::outside(id, active.holds, region, info.rel_base, self.output_size); return false; } self.relocate(id, placed, info, rsc); true } /// 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, placed: UiRegion, info: DrawInfo, rsc: &mut dyn UiRsc) { let active = &self.active[&id]; debug_assert!( !rsc.widgets().needs_redraw.contains(&id), "'{}' ({id:?}) placed while marked to draw", rsc.widgets().label(id) ); let is_region_node = active.is_region_node(); let local = match is_region_node { true => placed.at_origin(), false => placed, }; let moved = active.placement != local; let slot = active.move_idx; if is_region_node { self.moves.set(slot, placed.as_translation()); } if moved { self.reposition(id, local, info, rsc); } self.redepth(id, info.depth); let active = self.active.get_mut(&id).unwrap(); active.rel_base = info.rel_base; active.placed = info.placed; // What the ask made of its rules, which a re-place decides again. active.declared = info.declared; active.bounds = info.bounds; #[cfg(feature = "layout-diagnostics")] { let (counter, outcome) = match (moved, is_region_node) { (true, true) => (Counter::ReuseMoved, ReuseOutcome::Moved), (true, false) => (Counter::ReuseRemapped, ReuseOutcome::Remapped), (false, _) => (Counter::ReuseExact, ReuseOutcome::Exact), }; diag::bump(counter); diag::reuse(id, outcome); } } /// Places one child of `at.id` where that widget's own box now has it. fn place_child(&mut self, child: WidgetId, at: &Placing, rsc: &mut dyn UiRsc) { let place = self.active[&child].placed; self.place_in(child, at, place, rsc); } /// Puts a child of `at.id` in `place` of that widget's box: its answer /// placed inside that part where the place leaves the axis open, the /// drawing re-expressed there. pub(super) fn place_in( &mut self, child: WidgetId, at: &Placing, place: PlaceDesc, rsc: &mut dyn UiRsc, ) { let active = &self.active[&child]; let (rel_base, region) = Self::ask_again(active, at, place); let placed = place.placement( region, active.measured().unwrap_or(active.size), active.own_align, ); let info = DrawInfo { layer: active.layer, parent: at.id, depth: at.depth + 1, parent_move: at.move_idx, region_node: active.is_region_node(), mask: at.mask, rel_base, region, placed: place, asked: active.asked, declared: active.declared, bounds: active.bounds, // Placing decides no box: this is the one the ask already gave. ask_holds: LayoutHolds::ANY, re_asked: active.re_asked, }; self.relocate(child, placed, info, rsc); } /// 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. Declared lengths stay as the ask /// resolved them; the destination only decides where they sit. fn ask_again(active: &ActiveData, at: &Placing, place: PlaceDesc) -> (UiVec2, UiRegion) { place.rel_base_and_region(at.region, at.rel_base, active.declared, active.own_align) } /// Re-places everything inside a widget whose own box moved. Every child /// 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, placed: UiRegion, info: DrawInfo, rsc: &mut dyn UiRsc) { let active = self.active.get_mut(&id).unwrap(); active.placement = placed; for primitive in &active.primitives { let handle = &primitive.handle; *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(&placed); } let at = Placing { id: Some(id), region: placed, rel_base: info.rel_base, depth: info.depth, move_idx: active.move_idx, mask: active.mask, }; // Taken out and put back so that placing a child can borrow the state // it needs; nothing on that path reads this widget's own child list. let children = std::mem::take(&mut self.active.get_mut(&id).unwrap().children); for &child in &children { self.place_child(child, &at, rsc); } self.active.get_mut(&id).unwrap().children = children; } /// A reused subtree keeps its shape, so each widget in it keeps its depth /// under the top -- and where the top's own depth did not change, none of /// them did, which is what makes this free in the ordinary case. fn redepth(&mut self, id: WidgetId, depth: usize) { let Some(active) = self.active.get_mut(&id) else { return; }; if active.depth == depth { return; } active.depth = depth; // Taken out and put back so the walk can borrow the state it needs; // it only ever goes further down, so it reads no list but its own. let children = std::mem::take(&mut active.children); for &child in &children { self.redepth(child, depth + 1); } self.active.get_mut(&id).unwrap().children = children; } fn hints_agree(id: WidgetId, size: Size, rsc: &dyn UiRsc) -> bool { let Some(widget) = rsc.widgets().get_dyn(id) else { return true; }; Axis::BOTH .into_iter() .all(|axis| widget.size_hint(axis).is_none_or(|hint| hint == size[axis])) } /// Takes a widget's record out and frees what it drew. fn remove(&mut self, id: WidgetId, undraw: bool, rsc: &mut dyn UiRsc) -> Option { let mut active = self.active.remove(&id); if let Some(active) = &mut active { for dep in active.request_deps.drain(..) { if let Some(readers) = self.request_readers.get_mut(&dep) { readers.remove(&id); } } for primitive in &active.primitives { let mask = self.layers.free(&primitive.handle); if mask != MaskIdx::NONE { 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(); if undraw && active.drawn { rsc.on_undraw(active); } } active } /// Stops drawing a widget and everything under it, keeping the record /// of who asked about it so that a change to it still reaches them. pub(super) fn undraw_rec(&mut self, id: WidgetId, rsc: &mut dyn UiRsc) { let Some(mut active) = self.remove(id, true, rsc) else { return; }; for c in std::mem::take(&mut active.children) { self.undraw_rec(c, rsc); } // After the descendants, whose slots name this one as their parent. self.drop_slot(id); active.drawn = false; self.active.insert(id, active); } /// Records that `info.parent` asked about a widget it does not draw. fn asked(&mut self, id: WidgetId, info: DrawInfo, rsc: &mut dyn UiRsc) { if let Some(active) = self.active.get_mut(&id) { debug_assert!(!active.drawn, "asked about a widget it drew"); active.parent = info.parent; active.depth = info.depth; return; } // Never drawn, so there is no drawing to hold anything; what its // parent read was its hint. let widget = rsc.widgets().get_dyn(id); let size = Size { x: widget .and_then(|w| w.size_hint(Axis::X)) .unwrap_or(LayoutLen::ZERO), y: widget .and_then(|w| w.size_hint(Axis::Y)) .unwrap_or(LayoutLen::ZERO), }; self.active.insert( id, ActiveData { id, placement: UiRegion::FULL, rel_base: UiVec2::FULL_SIZE, placed: PlaceDesc::WHOLE, asked: PlaceDesc::WHOLE, region: UiRegion::FULL, answer: None, re_asked: false, size, holds: LayoutHolds::ANY, drawn: false, parent: info.parent, depth: info.depth, textures: Vec::new(), primitives: Vec::new(), mask_region: None, children: Vec::new(), request_deps: Vec::new(), scratch: Default::default(), move_idx: info.parent_move, declared: Declared::NONE, bounds: Bounds::ANY, own_align: rsc.widgets().alignment(id), parent_move: info.parent_move, mask: info.mask, parent_mask: info.mask, layer: info.layer, }, ); } fn clear(&mut self, rsc: &mut dyn UiRsc) { self.request_readers.clear(); for (_, active) in self.active.drain() { if active.drawn { rsc.on_undraw(&active); } } self.slots.clear(); self.moves.clear(); self.layers.clear(); rsc.ui_mut().masks = Default::default(); rsc.widgets_mut().needs_redraw.clear(); self.free(rsc); } /// Frees the widgets nothing holds any more, and the records kept of /// them: an id is handed on to the next widget made. fn free(&mut self, rsc: &mut dyn UiRsc) { while let Some(id) = rsc.widgets_mut().free_next() { rsc.on_remove(id); self.remove(id, true, rsc); self.drop_slot(id); self.request_readers.remove(&id); } rsc.ui_mut().textures.free(); } pub fn redraw_updates(&mut self, rsc: &mut dyn UiRsc) { #[cfg(feature = "layout-diagnostics")] let _layout = diag::timer(TimerKind::IncrementalLayout); self.changed.clear(); self.changed .extend(rsc.widgets().needs_redraw.iter().copied()); while let Some(id) = self.changed.pop() { if let Some(readers) = self.request_readers.get(&id) { for &reader in readers { if rsc.widgets_mut().needs_redraw.insert(reader) { self.changed.push(reader); } } } } // Deepest first, and strictly: a widget that cannot settle where it // is defers to its parent rather than drawing the parent from // inside itself. It marks the parent, stays marked, and waits here // until the walk reaches its parent's depth. // // What that buys is that nothing shallower is ever drawn while // anything deeper is still dirty. A parent drawing can therefore // trust every answer it reads without descending to check whether // something below is about to change it -- which is the whole class // of defect where a widget settles inside its parent's draw, clears // its mark there, and tells nobody its answer moved. // A mark made while the walk runs queues itself through `mark`. What // ends the walk is the marks being spent rather than the queue being // empty, so a mark that reached the queue twice, or that was settled // another way, costs a pop and nothing else. loop { for &id in rsc.widgets().needs_redraw.iter() { if !self.deferred.contains(&id) { let depth = self.depth(id); self.pending.push((depth, id)); } } if self.pending.is_empty() { break; } while let Some((depth, id)) = self.pending.pop() { // Settled inside an ancestor's draw, or deferred to one, // since the mark that queued it. if self.deferred.contains(&id) || !rsc.widgets().needs_redraw.contains(&id) { continue; } // A subtree that changed hands takes its descendants' depths // with it, so an entry queued before that move names the // depth it had under the parent it left. let now = self.depth(id); if now != depth { self.pending.push((now, id)); continue; } #[cfg(feature = "layout-diagnostics")] diag::bump(Counter::QueuePops); if !self.redraw(id, rsc) { self.deferred.insert(id); } } } self.deferred.clear(); } /// Marks a widget for the walk to settle, and queues it at its depth. fn mark(&mut self, id: WidgetId, widgets: &mut Widgets) { if widgets.needs_redraw.insert(id) && !self.deferred.contains(&id) { let depth = self.depth(id); self.pending.push((depth, id)); } } fn depth(&self, id: WidgetId) -> usize { #[cfg(feature = "layout-diagnostics")] diag::bump(Counter::DepthReads); let depth = match self.active.get(&id) { Some(active) if active.drawn => active.depth, // Nothing keeps an undrawn widget's current, and it only has to // reach whoever asked. Some(_) => return self.walked_depth(id), None => 1, }; debug_assert_eq!( depth, self.walked_depth(id), "a widget's kept depth is not the one its ancestry says" ); depth } /// What the kept depth is checked against, and the only thing that reads /// the ancestry to find one. fn walked_depth(&self, id: WidgetId) -> usize { let mut depth = 0; let mut at = Some(id); while let Some(id) = at { at = self.active.get(&id).and_then(|active| active.parent); depth += 1; } depth } pub fn root_changed<'a>(&self, root: impl Into>) -> bool { root.into().map(|r| r.id()) != self.old_root } pub fn needs_redraw<'a>( &self, root: impl Into>, widgets: &Widgets, ) -> bool { self.root_changed(root) || self.resized || widgets.has_updates() } pub fn active_widgets(&self) -> usize { self.active.values().filter(|active| active.drawn).count() } pub fn debug(&self, widgets: &Widgets, label: &str) -> impl Iterator { self.active.iter().filter_map(move |(&id, inst)| { let l = widgets.label(id); if l == label { Some(inst) } else { None } }) } pub fn debug_layers(&self) { for ((idx, depth), draws) in self.layers.iter_depth() { let indent = " ".repeat(depth * 2); let counts: Vec = draws .primitives() .iter() .map(|l| l.as_ref().map_or(0, |l| l.instances().len()).to_string()) .collect(); println!("{indent}{idx}: [{}]", counts.join(", ")); } } /// Where a widget is on screen: its box composed through the boxes it /// sits within, the same walk the vertex shader does. `None` for one that /// is not drawn. /// /// This is for asking where a drawing landed: hit testing, and a test /// reading a box back. Layout decides on the lengths threaded down the /// draw instead, and a position is not one of its inputs. pub fn window_region(&self, id: &impl IdLike) -> Option { let active = self.active.get(&id.id())?; active.drawn.then(|| { self.moves .resolve(active.move_idx, active.placement) .to_px(self.output_size) }) } /// Settles a dirty widget: asks it again where its parent asked, and /// tells the parent if the answer changed. `false` where the question is /// its parent's rather than its own, which leaves it marked for the /// parent to draw when the walk reaches that depth. pub fn redraw(&mut self, id: WidgetId, rsc: &mut dyn UiRsc) -> bool { rsc.widgets_mut().needs_redraw.remove(&id); let Some(active) = self.active.get(&id) else { return true; }; // Asked where its parent asked it, which is what says whether the // question is still this widget's own: its parent resolved its // declared lengths into its box -- a bound of its own that the box // falls outside is one of them -- and decided whether to draw it at // all, so a change to either is the parent's to draw, with the mark // left on so the parent draws it rather than keeping it. So is a // widget the parent asked twice: its layout rests on an answer this // widget cannot give again alone. The root's parent is the window, // which no draw made and no answer can move. let at = match active.parent { Some(parent) => self.placing_of(parent, self.active[&parent].region), None => Placing::WINDOW, }; let ask = at.ask( rsc.widgets(), &mut self.requests, self.output_size, id, active.asked, ); let active = &self.active[&id]; // Even an inactive bound changes what the parent must track about its offer. let constraints_changed = ask.declared != active.declared || ask.bounds != active.bounds; let alignment_changed = rsc.widgets().alignment(id) != active.own_align; if let Some(parent) = active.parent && (constraints_changed || alignment_changed || active.re_asked || !active.drawn || active.answer.is_none()) { // Both stay marked: the parent because it has this to draw, and // this because the parent must draw it rather than keep what it // has. The mark comes off in `draw_at`, where the parent draws. self.mark(id, rsc.widgets_mut()); self.mark(parent, rsc.widgets_mut()); return false; } if !active.drawn { return true; } let (was_answer, was_holds, was_place) = (active.answer, active.holds, active.placed); // The place the ask above came to: the same place of the box the // parent was asked in, which is the box the parent's own 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 (rel_base, region) = (ask.rel_base, ask.region); let info = DrawInfo { layer: active.layer, parent: active.parent, depth: active.depth, parent_move: active.parent_move, region_node: rsc.widgets().is_region_node(id), mask: active.parent_mask, rel_base, region, placed: ask.place, asked: active.asked, declared: ask.declared, bounds: ask.bounds, ask_holds: ask.holds, re_asked: false, }; #[cfg(feature = "layout-diagnostics")] diag::bump(Counter::LocalRedraws); let old = self.remove(id, false, rsc); let drawn = self.draw_inner(id, info, old, rsc); let active = self.active.get_mut(&id).unwrap(); // Against the box it was asked in, which is what both contracts are // about. Where the answer put the drawing is shorter than that // wherever the widget reported less than it was offered. let (window, rel_base, region) = (self.output_size, active.rel_base, active.region); // A wider contract does not invalidate the guarantee the parent kept. // Retain that guarantee so widening and narrowing back do not churn // it -- but only where the narrower range still holds here: one this // window is outside is refused by the parent's next ask, and refusing // it throws away the drawing this one just made. if let Some(was) = was_answer && drawn.answer.size == was.size && drawn.answer.holds.covers(was.holds) && was.holds.contains(window, rel_base, region) { active.answer = was_answer; } if active.holds.covers(was_holds) && was_holds.contains(window, rel_base, region) { active.holds = was_holds; } let changed = active.answer != was_answer || active.holds != was_holds; // Nothing above the root retained either, so there is nobody to tell // and nowhere else the drawing has to go back to. if let Some(parent) = active.parent { match changed { // The parent retains both the answer and the drawing's // validity; even an unchanged size can narrow the range safe // for a resize. true => { #[cfg(feature = "layout-diagnostics")] { diag::bump(Counter::SizeChanges); diag::bump(Counter::ReaderEdges); } self.mark(parent, rsc.widgets_mut()); } // 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. false => { 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 /// `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, region: UiRegion) -> Placing { let active = &self.active[&id]; Placing { id: Some(id), region, rel_base: active.rel_base, depth: active.depth, move_idx: active.move_idx, mask: active.mask, } } } impl Size { /// Whether what a widget reports along `axis` is inside the box it drew /// in. 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(&self, region: UiRegion, window: PxVec2, axis: Axis) -> bool { let len = self[axis]; let window = window[axis]; len.leftover != Weight::ZERO || len.without_leftover().to_px(window) <= region[axis].len().to_px(window) } } impl UiRegion { /// A box in a fresh region node keeps its window-unit length and starts /// at that node's origin. fn at_origin(self) -> UiRegion { let size = self.size(); UiRegion::new( UiSpan::new(Len::ZERO, size.x), UiSpan::new(Len::ZERO, size.y), ) } /// A region node changes only the origin. A full relative span anchored at /// the box start composes as that translation in both the CPU and shader. fn as_translation(self) -> UiRegion { UiRegion { x: UiSpan::new(self.x.start, self.x.start + Len::FULL), y: UiSpan::new(self.y.start, self.y.start + Len::FULL), } } } impl Default for UiRenderState { fn default() -> Self { Self::new() } }