#[cfg(feature = "layout-diagnostics")] use crate::layout_diagnostics::{self as diag, Counter, ReuseOutcome, TimerKind}; use crate::ui::painter::{declared_lens, placement, rel_base_and_region}; use crate::{ ActiveData, Axis, DrawLayers, IdLike, LayoutHolds, LayoutLen, Len, MaskIdx, MoveIdx, Moves, Painter, Part, PixelRegion, Place, PxVec2, Rel, Size, StrongWidget, UiRegion, UiRsc, UiSpan, UiVec2, Weight, WidgetId, Widgets, util::{HashMap, Vec2}, }; const AXES: [Axis; 2] = [Axis::X, Axis::Y]; /// 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 where it was asked, as parts of the /// parent's box. See [`Place`]. The two are one ask's place until the /// parent puts the answer somewhere else. pub placed: [Place; 2], pub asked: [Place; 2], /// A rel base 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; 2], /// Whether the parent already asked about this widget in this draw. pub re_asked: bool, /// 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 region: UiRegion, pub rel_base: UiVec2, pub window: PxVec2, pub depth: usize, pub move_idx: MoveIdx, pub mask: MaskIdx, } 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::BTreeSet<(usize, WidgetId)>, 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(), 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(|(_, 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); } } /// The root is asked about in the output. Its own rules narrow both its /// 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, depth: 1, parent_move: MoveIdx::NONE, region_node: false, mask: MaskIdx::NONE, rel_base, region, placed: [Place::Within(Part::WHOLE); 2], asked: [Place::Within(Part::WHOLE); 2], narrow: [None; 2], re_asked: false, px, } } 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:#?}" ); } 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 (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 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) { rel_base_and_region( UiRegion::FULL, UiVec2::FULL_SIZE, [Place::Within(Part::WHOLE); 2], [None; 2], declared_lens(widgets, id), widgets.alignment(id), ) } pub(super) fn draw_inner( &mut self, id: WidgetId, info: DrawInfo, mut old: Option, rsc: &mut dyn UiRsc, ) -> (Size, LayoutHolds, LayoutHolds) { 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, info.px, info.region_node); } let align = rsc.widgets().alignment(id); let declared = declared_lens(rsc.widgets(), 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 = placement(region, answer.0, declared, info.fill(), 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, 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 = placement(region, answer.0, declared, info.fill(), 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.narrow = info.narrow; 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); } (answer.0, answer.1, 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, ) -> (Size, LayoutHolds) { 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(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, 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; 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: Vec::new(), primitives: Vec::new(), mask_region: None, mask_slot, children: Vec::new(), size_deps: Vec::new(), own: LayoutHolds::ANY, under: Vec::new(), 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, size_deps, 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 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.rel_base.axis(axis).rel, px: info.rel_base.axis(axis).px, leftover: Weight::ZERO, }, Some(len) => len.within_len(info.rel_base.axis(axis)), }; let size = Size { x: ruled(Axis::X, size.x), y: ruled(Axis::Y, size.y), }; // 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 || AXES .into_iter() .all(|axis| within_box(size, region, self.output_size, axis)), "'{}' ({id:?}) clips to {px:?} and reports {size}", rsc.widgets().label(id), ); 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); } // 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. 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.rel_base.axis(axis)), false => own.rel_base[axis as usize], } }); 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.rel_base, region), "'{}' ({id:?}) drew in {px:?}, outside the ranges it reported: {holds:?}", rsc.widgets().label(id), ); // 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: [Place::Within(Part::WHOLE); 2], asked: [Place::Within(Part::WHOLE); 2], narrow: [None; 2], re_asked: false, px, }, rsc, ); rsc.widgets_mut().needs_redraw.remove(&dep); } } let active = ActiveData { id, placement: region, rel_base: info.rel_base, narrow: info.narrow, 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, size_deps, declared: declared_lens(rsc.widgets(), id), 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); (size, 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<(Size, LayoutHolds)> { let active = self.active.get(&id)?; let has_region_node = active.move_idx != active.parent_move; if !active.drawn || has_region_node != info.region_node || active.parent_move != info.parent_move { return None; } let answer = active.answer?; answer .1 .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, ) -> Option<()> { #[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 None; } let active = self.active.get(&id)?; if !active.drawn { #[cfg(feature = "layout-diagnostics")] diag::reuse(id, ReuseOutcome::Undrawn); return None; } let has_region_node = active.move_idx != active.parent_move; if has_region_node != info.region_node { #[cfg(feature = "layout-diagnostics")] diag::bump(Counter::ReuseWrongNode); return None; } // 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 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 { #[cfg(feature = "layout-diagnostics")] { diag::bump(Counter::ReuseWrongParent); diag::reuse(id, ReuseOutcome::WrongParent); } return None; } // 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")] { // 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.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.rel_base[n] .is_some_and(|pinned| pinned != info.rel_base.axis(axis)) { diag::bump(Counter::OutsideRelBase); } if !holds.region[n] .contains(region.axis(axis).len().to_px(self.output_size.axis(axis))) { diag::bump(Counter::OutsideRegion); } } diag::bump(Counter::ReuseOutside); diag::reuse(id, ReuseOutcome::Outside); } return None; } 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, 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 has_region_node = active.move_idx != active.parent_move; let local = match has_region_node { true => local_region(placed), false => placed, }; let moved = active.placement != local; let slot = active.move_idx; if has_region_node { 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.rel_base = info.rel_base; active.placed = info.placed; #[cfg(feature = "layout-diagnostics")] { match (moved, has_region_node) { (true, true) => diag::bump(Counter::ReuseMoved), (true, false) => diag::bump(Counter::ReuseRemapped), (false, _) => diag::bump(Counter::ReuseExact), } diag::reuse( id, if moved { if has_region_node { ReuseOutcome::Moved } else { ReuseOutcome::Remapped } } else { ReuseOutcome::Exact }, ); } } /// 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: [Place; 2], rsc: &mut dyn UiRsc, ) { let active = &self.active[&child]; 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), active.own_align, ); let info = DrawInfo { layer: active.layer, parent: Some(at.id), depth: at.depth + 1, parent_move: at.move_idx, region_node: active.move_idx != active.parent_move, mask: at.mask, rel_base, region, placed: place, asked: active.asked, narrow: active.narrow, re_asked: active.re_asked, px: rel_base.to_px(at.window), }; 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, so both are resolved against that /// parent's rel base again exactly as the first ask resolved them. fn ask_again(active: &ActiveData, at: &Placing, place: [Place; 2]) -> (UiVec2, UiRegion) { rel_base_and_region( at.region, at.rel_base, place, active.narrow, 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, region: placed, rel_base: info.rel_base, window: self.output_size, depth: info.depth, move_idx: active.move_idx, mask: active.mask, }; let children = active.children.len(); for index in 0..children { let child = self.active[&id].children[index]; self.place_child(child, &at, rsc); } } /// A reused subtree keeps its shape, so every widget in it moves by the /// same amount -- and where the top of it did not move, none of it 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; let children = active.children.len(); for index in 0..children { let child = self.active[&id].children[index]; self.redepth(child, depth + 1); } } fn hints_agree(id: WidgetId, size: Size, rsc: &dyn UiRsc) -> bool { let Some(widget) = rsc.widgets().get_dyn(id) else { return true; }; AXES.into_iter().all(|axis| { widget .size_hint(axis) .is_none_or(|hint| hint == size.axis(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 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.size_deps.clear(); 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, narrow: [None; 2], placed: [Place::Within(Part::WHOLE); 2], asked: [Place::Within(Part::WHOLE); 2], 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(), size_deps: Vec::new(), move_idx: info.parent_move, declared: [None; 2], 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) { 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); } 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); // 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. // The queue is that set, ordered: a mark made while the walk runs // queues itself through `mark`. What ends the walk is still the set // being spent, not the queue, so a mark that reached it another way // cannot be left for the next frame. loop { for &id in rsc.widgets().needs_redraw.iter() { if !self.deferred.contains(&id) { let depth = self.depth(id); self.pending.insert((depth, id)); } } if self.pending.is_empty() { break; } while let Some((depth, id)) = self.pending.pop_last() { // 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.insert((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.insert((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; }; // Its parent resolved its declared lengths into its box 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. let declared_changed = declared_lens(rsc.widgets(), id) != active.declared; let alignment_changed = rsc.widgets().alignment(id) != active.own_align; if let Some(parent) = active.parent && (declared_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; } // Nothing above the root resolved its rules or its alignment, so its // box is its own to work out again against the output. Every other // widget was given one. let Some(parent) = active.parent else { let (rel_base, region) = Self::root_layout(id, rsc.widgets()); let info = DrawInfo { mask: active.parent_mask, ..self.root_info(rel_base, region) }; #[cfg(feature = "layout-diagnostics")] diag::bump(Counter::LocalRedraws); let old = self.remove(id, false, rsc); self.draw_inner(id, info, old, rsc); return true; }; let (was_answer, was_holds, was_place) = (active.answer, active.holds, active.placed); // The question its parent asked, asked again: the same place of the // 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 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, depth: active.depth, parent_move: active.parent_move, region_node: rsc.widgets().is_region_node(id), mask: active.parent_mask, rel_base, region, placed: active.asked, asked: active.asked, narrow: active.narrow, re_asked: false, px: rel_base.to_px(self.output_size), }; #[cfg(feature = "layout-diagnostics")] diag::bump(Counter::LocalRedraws); let old = self.remove(id, false, rsc); let answer = self.draw_inner(id, info, old, rsc); let active = self.active.get_mut(&id).unwrap(); // A wider contract does not invalidate the guarantee the parent kept. // Retain that guarantee so widening and narrowing back do not churn it. if let Some((size, holds)) = was_answer && answer.0 == size && answer.1.covers(holds) { active.answer = was_answer; } if active.holds.covers(was_holds) && was_holds.contains(self.output_size, active.rel_base, active.placement) { active.holds = was_holds; } if active.answer != was_answer || active.holds != was_holds { // The parent retains both the answer and the drawing's validity; // even an unchanged size can narrow the range safe for a resize. #[cfg(feature = "layout-diagnostics")] { diag::bump(Counter::SizeChanges); diag::bump(Counter::ReaderEdges); } self.mark(parent, rsc.widgets_mut()); } else { // 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].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, region, rel_base: active.rel_base, window: self.output_size, depth: active.depth, move_idx: active.move_idx, mask: active.mask, } } } /// 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(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) <= region.axis(axis).len().to_px(window) } /// A box in a fresh region node keeps its window-unit length and starts at /// that node's origin. fn local_region(region: UiRegion) -> UiRegion { let size = region.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 translation(region: UiRegion) -> UiRegion { UiRegion { x: UiSpan::new(region.x.start, region.x.start + Len::FULL), y: UiSpan::new(region.y.start, region.y.start + Len::FULL), } } impl Default for UiRenderState { fn default() -> Self { Self::new() } }