diff --git a/core/src/ui/active.rs b/core/src/ui/active.rs index 0050fe4..d90d6c7 100644 --- a/core/src/ui/active.rs +++ b/core/src/ui/active.rs @@ -11,12 +11,16 @@ pub struct ActiveData { pub textures: Vec, pub primitives: Vec, pub children: Vec, + /// Direct children whose reported size this widget used during its + /// latest draw. Dirtiness propagates across these edges before layout + /// starts, so the resulting draw still travels only parent to child. + pub size_dependencies: Vec, /// The inherited mask, not `own_mask`. pub mask: MaskIdx, /// The widget's retained mask slot, or `MaskIdx::NONE`. pub own_mask: MaskIdx, pub layer: LayerId, - /// The last `Widget::draw` result. + /// The size recorded by the last `Widget::draw` through its painter. pub size: Size, /// Retained so descendants' parent links stay valid across redraws. pub move_slot: MoveIdx, diff --git a/core/src/ui/mod.rs b/core/src/ui/mod.rs index e922019..ee5d340 100644 --- a/core/src/ui/mod.rs +++ b/core/src/ui/mod.rs @@ -9,7 +9,7 @@ mod render_state; pub use access::*; pub use active::*; -pub use painter::Painter; +pub use painter::{DrawResult, Painter}; pub use render_state::*; #[derive(Default)] diff --git a/core/src/ui/painter.rs b/core/src/ui/painter.rs index c9ef46b..1aee453 100644 --- a/core/src/ui/painter.rs +++ b/core/src/ui/painter.rs @@ -25,12 +25,46 @@ pub struct Painter<'a> { /// Previous handles, consumed in draw order and freed if left over. pub(super) recycle: std::iter::Peekable>, pub(super) children: Vec, - pub(super) reuse_child_sizes: bool, + pub(super) size_dependencies: Vec, + pub(super) size: Option, + /// Whether a retained child's length on each axis is still valid. A + /// child's length may change when the parent's orthogonal extent changes + /// (most importantly, wrapped text gets taller when it gets narrower), + /// but not merely because a content-sized parent grew along that same + /// axis around one of its siblings. + pub(super) reuse_child_sizes: [bool; 2], pub layer: usize, pub(super) id: WidgetId, } +/// A child draw whose size has not necessarily been observed by its parent. +/// Holding this value keeps the painter borrowed, so `.size()` can only name +/// the child from the immediately preceding draw. +pub struct DrawResult<'p, 'a> { + painter: &'p mut Painter<'a>, + child: WidgetId, +} + +impl DrawResult<'_, '_> { + /// Return the child's reported size and record the layout dependency. + pub fn size(self) -> Size { + if !self.painter.size_dependencies.contains(&self.child) { + self.painter.size_dependencies.push(self.child); + } + self.painter.state.active[&self.child].size + } +} + impl<'a> Painter<'a> { + /// Record the size this widget used. Every `Widget::draw` calls this + /// exactly once; parents observe it through [`DrawResult::size`]. + pub fn set_size(&mut self, size: Size) { + assert!( + self.size.replace(size).is_none(), + "a widget set its size more than once during one draw" + ); + } + fn primitive_at(&mut self, primitive: P, region: UiRegion) { self.write_primitive(primitive, region, Drawn::Yes); } @@ -207,15 +241,19 @@ impl<'a> Painter<'a> { self.mask = self.own_mask; } - /// Draws a widget within this widget's region, returning the size it - /// reported using. - pub fn widget(&mut self, id: &StrongWidget) -> Size { + /// Draw a widget within this widget's region. Reading the result's size + /// records that this widget's layout depends on the child. + pub fn widget<'p, W: ?Sized>(&'p mut self, id: &StrongWidget) -> DrawResult<'p, 'a> { self.widget_at(id, self.region) } /// Draws a widget somewhere within this one. /// Useful for drawing child widgets in select areas. - pub fn widget_within(&mut self, id: &StrongWidget, region: UiRegion) -> Size { + pub fn widget_within<'p, W: ?Sized>( + &'p mut self, + id: &StrongWidget, + region: UiRegion, + ) -> DrawResult<'p, 'a> { self.widget_at(id, region.within(&self.region)) } @@ -260,17 +298,29 @@ impl<'a> Painter<'a> { } } - pub fn known_len(&self, id: &StrongWidget, axis: Axis) -> Option { - if let Some(len) = self.rsc.widgets().get_dyn(id.id())?.size_hint(axis) { - return Some(len.fold_dp(self.density())); + pub fn known_len(&mut self, id: &StrongWidget, axis: Axis) -> Option { + let len = if let Some(len) = self.rsc.widgets().get_dyn(id.id())?.size_hint(axis) { + Some(len.fold_dp(self.density())) + } else if !self.reuse_child_sizes[match axis { + Axis::X => 0, + Axis::Y => 1, + }] || self.rsc.widgets().needs_redraw.contains(&id.id()) + { + None + } else { + self.state.active.get(&id.id()).map(|a| a.size.axis(axis)) + }; + if len.is_some() && !self.size_dependencies.contains(&id.id()) { + self.size_dependencies.push(id.id()); } - if !self.reuse_child_sizes || self.rsc.widgets().needs_redraw.contains(&id.id()) { - return None; - } - self.state.active.get(&id.id()).map(|a| a.size.axis(axis)) + len } - fn widget_at(&mut self, id: &StrongWidget, region: UiRegion) -> Size { + fn widget_at<'p, W: ?Sized>( + &'p mut self, + id: &StrongWidget, + region: UiRegion, + ) -> DrawResult<'p, 'a> { self.children.push(id.id()); // Passed directly rather than looked up from `self.active`: this // widget's own `ActiveData` (which would carry its `move_slot`) is @@ -289,19 +339,26 @@ impl<'a> Painter<'a> { self.mask, Retained::default(), self.rsc, - ) + ); + DrawResult { + painter: self, + child: id.id(), + } } /// Place an already-drawn child's used area, redrawing only if its size changes. - pub fn place(&mut self, id: &StrongWidget, region: UiRegion) -> Size { + pub fn place<'p, W: ?Sized>( + &'p mut self, + id: &StrongWidget, + region: UiRegion, + ) -> DrawResult<'p, 'a> { let region = region.within(&self.region); let retained = self .state .active .get(&id.id()) .map(|active| (active.layer, active.mask)); - if let Some(size) = self.state.place(id.id(), region, self.rsc) { - size + if self.state.place(id.id(), region, self.rsc).is_some() { } else if let Some((layer, mask)) = retained { self.children.push(id.id()); self.rsc.widgets_mut().needs_redraw.insert(id.id()); @@ -315,9 +372,24 @@ impl<'a> Painter<'a> { mask, Retained::default(), self.rsc, - ) + ); } else { - self.widget_at(id, region) + self.children.push(id.id()); + let parent_move_slot = self.child_move_slot.unwrap_or(self.move_slot); + self.state.draw_inner( + self.layer, + id.id(), + region, + Some(self.id), + parent_move_slot.idx() as u32, + self.mask, + Retained::default(), + self.rsc, + ); + } + DrawResult { + painter: self, + child: id.id(), } } @@ -326,7 +398,7 @@ impl<'a> Painter<'a> { id: &StrongWidget, used: Size, within: UiRegion, - ) -> Size { + ) -> DrawResult<'_, 'a> { let region = self.fit_region(used, within); self.place(id, region) } diff --git a/core/src/ui/render_state.rs b/core/src/ui/render_state.rs index d5f5d21..a0521a2 100644 --- a/core/src/ui/render_state.rs +++ b/core/src/ui/render_state.rs @@ -535,8 +535,16 @@ impl UiRenderState { let move_slot = Self::move_slot_for(old_move_slot, parent_move_slot, rsc); let inherited_mask = mask; - let reuse_child_sizes = - old_region.is_some_and(|old| Self::same_size(old, region, self.output_size)); + // `Painter::layer` is a cursor widgets advance while assigning + // layers to their children. Retain the layer this widget itself was + // entered on, not wherever that cursor finishes after `draw`. + let inherited_layer = layer; + let reuse_child_sizes = old_region.map_or([false; 2], |old| { + [ + Self::same_axis_size(old, region, Axis::Y, self.output_size), + Self::same_axis_size(old, region, Axis::X, self.output_size), + ] + }); let mut painter = Painter { state: self, region, @@ -550,6 +558,8 @@ impl UiRenderState { primitives: Vec::new(), recycle: recycle.into_iter().peekable(), children: Vec::new(), + size_dependencies: Vec::new(), + size: None, reuse_child_sizes, rsc, }; @@ -561,7 +571,13 @@ impl UiRenderState { widget.size_hint(Axis::Y).map(|len| len.fold_dp(density)), ]; painter.state.draw_count += 1; - let size = widget.draw(&mut painter); + widget.draw(&mut painter); + let size = painter.size.unwrap_or_else(|| { + panic!( + "widget '{}' ({id:?}) did not set its size during draw", + painter.rsc.widgets().label(id) + ) + }); debug_assert!( size.x.dp == 0.0 && size.y.dp == 0.0, "widget {id:?} reported an unresolved `dp` size ({size:?}); \ @@ -593,8 +609,10 @@ impl UiRenderState { primitives, recycle, children, + size_dependencies, + size: _, reuse_child_sizes: _, - layer, + layer: _, id, } = painter; @@ -615,8 +633,9 @@ impl UiRenderState { textures, primitives, children, + size_dependencies, mask: inherited_mask, - layer, + layer: inherited_layer, size, move_slot, child_move_slot, @@ -690,9 +709,13 @@ impl UiRenderState { } fn same_size(a: UiRegion, b: UiRegion, output: Vec2) -> bool { - let a = a.size().to_abs(output); - let b = b.size().to_abs(output); - (a.x - b.x).abs() < 0.01 && (a.y - b.y).abs() < 0.01 + Self::same_axis_size(a, b, Axis::X, output) && Self::same_axis_size(a, b, Axis::Y, output) + } + + fn same_axis_size(mut a: UiRegion, mut b: UiRegion, axis: Axis, output: Vec2) -> bool { + let a = a.axis(axis).len().to_abs(output.axis(axis)); + let b = b.axis(axis).len().to_abs(output.axis(axis)); + (a - b).abs() < 0.01 } pub(super) fn place( @@ -920,12 +943,76 @@ impl UiRenderState { } pub fn redraw_updates(&mut self, rsc: &mut dyn UiRsc) { - while let Some(&id) = rsc.widgets().needs_redraw.iter().next() { - self.redraw(id, rsc); + while rsc.widgets().has_updates() { + // Expand size dependencies before drawing anything. The parent + // links are the retained widget tree already used by hit testing + // and removal; only the direct-child dependency list is new. + let pending: Vec<_> = rsc.widgets().needs_redraw.iter().copied().collect(); + for mut child in pending { + for _ in 0..PARENT_CHAIN_LIMIT { + // An exact hint is the child's current answer without a + // draw. If both axes still match the retained size, no + // parent can observe a size change from this mutation. + if self.size_matches_hints(child, rsc) { + break; + } + let Some(parent) = self.active.get(&child).and_then(|active| active.parent) + else { + break; + }; + let depends = self + .active + .get(&parent) + .is_some_and(|active| active.size_dependencies.contains(&child)); + if !depends { + break; + } + rsc.widgets_mut().needs_redraw.insert(parent); + child = parent; + } + } + + // A dirty ancestor draws its dirty descendants on the way down; + // starting those descendants separately would duplicate work. + let dirty: Vec<_> = rsc.widgets().needs_redraw.iter().copied().collect(); + let mut roots = Vec::new(); + for id in dirty { + let mut ancestor = self.active.get(&id).and_then(|active| active.parent); + let mut covered = false; + for _ in 0..PARENT_CHAIN_LIMIT { + let Some(parent) = ancestor else { break }; + if rsc.widgets().needs_redraw.contains(&parent) { + covered = true; + break; + } + ancestor = self.active.get(&parent).and_then(|active| active.parent); + } + if !covered { + roots.push(id); + } + } + for id in roots { + self.redraw(id, rsc); + } } rsc.free(); } + fn size_matches_hints(&self, id: WidgetId, rsc: &dyn UiRsc) -> bool { + let Some(active) = self.active.get(&id) else { + return false; + }; + let Some(widget) = rsc.widgets().get_dyn(id) else { + return false; + }; + [Axis::X, Axis::Y].into_iter().all(|axis| { + widget + .size_hint(axis) + .map(|hint| hint.fold_dp(self.density)) + == Some(active.size.axis(axis)) + }) + } + pub fn root_changed<'a>(&self, root: impl Into>) -> bool { root.into().map(|r| r.id()) != self.old_root } @@ -1207,61 +1294,18 @@ impl UiRenderState { /// redraws a widget that's currently active (drawn) pub fn redraw(&mut self, id: WidgetId, rsc: &mut dyn UiRsc) { - self.redraw_and_settle(id, rsc); - } - - /// Measure a changed branch toward the root, then revisit each widget - /// whose reported size changed on the way back down. The upward pass gives - /// every parent the new child size; the downward pass is what lets those - /// children draw inside the final boxes their parents chose. Without it a - /// newly grown subtree can retain the provisional (even inverted) region - /// it was measured in until an unrelated later update redraws it. - fn redraw_and_settle(&mut self, id: WidgetId, rsc: &mut dyn UiRsc) { - let Some((parent, changed)) = self.redraw_once(id, rsc) else { - return; - }; - if changed { - if let Some(pid) = parent { - self.redraw_and_settle(pid, rsc); - } - // The parent pass above has now placed this widget in its final - // region. Draw it once more there; unchanged descendants still - // take draw_inner's retained fast path. This is deliberately one - // redraw rather than another settling pass: feeding its size - // back into the same upward walk can alternate between the - // provisional and final regions forever (a text edit first did - // that when an Android IME committed a space), overflowing the - // native thread's stack before Rust can report a panic. - let settled_size = self.active.get(&id).map(|active| active.size); - let _ = self.redraw_once(id, rsc); - debug_assert_eq!( - self.active.get(&id).map(|active| active.size), - settled_size, - "a widget changed size after its parent settled its final region" - ); - } - } - - /// Redraw `id` exactly once, returning its parent and whether the size it - /// reports changed. [`Self::redraw_and_settle`] owns any propagation; in - /// particular, its final downward redraw must not start another upward - /// pass through the same branch. - fn redraw_once( - &mut self, - id: WidgetId, - rsc: &mut dyn UiRsc, - ) -> Option<(Option, bool)> { rsc.widgets_mut().needs_redraw.remove(&id); // An ancestor is drawing this widget right now, and that draw is // about to write fresh primitives for it. Drawing it a second time // here would leave one of the two copies on screen with nothing // owning it -- see `draw_started`'s own doc. if self.draw_started.contains(&id) { - return None; + return; } - let active = self.remove(id, false, true, rsc)?; - let old_size = active.size; + let Some(active) = self.remove(id, false, true, rsc) else { + return; + }; let parent = active.parent; // `old_move_slot` being `Some` below means the slot is reused in // place rather than freshly parented, so this is only reached for @@ -1285,13 +1329,6 @@ impl UiRenderState { }, rsc, ); - // If this widget's own reported size changed, its parent's layout - // (which placed it using the old size) is now stale and needs to - // relay out too. Checked after the real draw, not before it -- - // there is no query left that answers "what size would this be" - // without actually drawing (LAYOUT.md section 5). - let changed = self.active.get(&id).map(|a| a.size) != Some(old_size); - Some((parent, changed)) } } diff --git a/core/src/widget/mod.rs b/core/src/widget/mod.rs index d22e4b1..56dbd0b 100644 --- a/core/src/widget/mod.rs +++ b/core/src/widget/mod.rs @@ -16,7 +16,7 @@ pub use view::*; pub use widgets::*; pub trait Widget: Any { - fn draw(&mut self, painter: &mut Painter) -> Size; + fn draw(&mut self, painter: &mut Painter); /// An exact, context-free length known without drawing or inspecting children. fn size_hint(&self, _axis: Axis) -> Option { @@ -45,8 +45,8 @@ pub trait Widget: Any { } impl Widget for () { - fn draw(&mut self, _: &mut Painter) -> Size { - Size::ZERO + fn draw(&mut self, painter: &mut Painter) { + painter.set_size(Size::ZERO); } fn is_size_independent(&self) -> bool { diff --git a/src/layout_tests.rs b/src/layout_tests.rs index beaad8d..dfb8782 100644 --- a/src/layout_tests.rs +++ b/src/layout_tests.rs @@ -5,6 +5,7 @@ //! CPU-side layout/move machinery LAYOUT.md is about. use crate::prelude::*; +use std::{cell::Cell, cell::RefCell, rc::Rc}; /// The minimal `UiRsc` a test needs: just the shared `UiData`, none of the /// event/window/state plumbing `DefaultRsc` carries. `pub(crate)` so @@ -26,14 +27,14 @@ impl UiRsc for TestRsc { struct FixedRect(f32); impl Widget for FixedRect { - fn draw(&mut self, painter: &mut Painter) -> Size { + fn draw(&mut self, painter: &mut Painter) { let size = Size::from_axis(Axis::Y, Len::abs(self.0), Len::REST); painter.primitive_within( RectPrimitive::color(UiColor::WHITE), size.to_uivec2(painter.density()) .align(RegionAlign::TOP_LEFT), ); - size + painter.set_size(size); } } @@ -43,12 +44,214 @@ struct ChildOffset { } impl Widget for ChildOffset { - fn draw(&mut self, painter: &mut Painter) -> Size { + fn draw(&mut self, painter: &mut Painter) { painter.set_child_offset(self.offset); - painter.widget(&self.child) + let size = painter.widget(&self.child).size(); + painter.set_size(size); } } +struct TracedLeaf { + height: f32, + trace: Rc>>, +} + +impl Widget for TracedLeaf { + fn draw(&mut self, painter: &mut Painter) { + self.trace.borrow_mut().push("child"); + painter.set_size(Size::from_axis(Axis::Y, Len::abs(self.height), Len::REST)); + } +} + +struct TracedParent { + child: StrongWidget, + reads_child_size: bool, + trace: Rc>>, +} + +struct CountedLeaf { + height: f32, + draws: Rc>, +} + +impl Widget for CountedLeaf { + fn draw(&mut self, painter: &mut Painter) { + self.draws.set(self.draws.get() + 1); + painter.set_size(Size::from_axis(Axis::Y, Len::abs(self.height), Len::REST)); + } +} + +impl Widget for TracedParent { + fn draw(&mut self, painter: &mut Painter) { + self.trace.borrow_mut().push("parent"); + let child = painter.widget(&self.child); + let size = if self.reads_child_size { + child.size() + } else { + Size::REST + }; + painter.set_size(size); + } +} + +/// Minimal reproduction for a container's child-layer cursor being retained +/// as though it were the layer on which the container itself was entered. +/// +/// `Stack` is drawn by `Sized` on layer 0. It advances its painter to layers +/// 1 and 2 for its two children. The retained `ActiveData` must still say the +/// stack itself is on layer 0; otherwise an ordinary redraw of `Sized` asks +/// for the stack on 0 again and turns the invented 2 -> 0 change into a full +/// redraw of the stack and both children. +#[test] +fn a_widget_retains_its_entry_layer_not_its_child_cursor() { + let mut rsc = TestRsc { + ui: UiData::default(), + }; + let back = rsc.ui.widgets.add_strong(Rect::new(UiColor::WHITE)); + let front = rsc.ui.widgets.add_strong(Rect::new(UiColor::RED)); + let stack = rsc.ui.widgets.add_strong(Stack { + children: vec![back.any(), front.any()], + size: StackSize::Default, + }); + let stack_id = stack.id(); + let outer = rsc.ui.widgets.add_strong(Sized { + inner: stack.any(), + x: None, + y: None, + }); + let outer_weak = outer.weak(); + let root = outer.any(); + let mut render = UiRenderState::new(); + render.resize((100.0, 100.0)); + render.update(&root, &mut rsc); + + assert_eq!( + render.active[&stack_id].layer, 0, + "Stack entered on layer 0; layers 1 and 2 belong only to its children" + ); + + render.take_counters(); + rsc.ui.widgets.get_mut(&outer_weak).unwrap().x = None; + render.update(&root, &mut rsc); + assert_eq!( + render.take_counters().0, + 1, + "redrawing the parent should retain the unchanged Stack subtree" + ); +} + +#[test] +fn a_size_dependent_parent_is_invalidated_before_layout_runs_downward() { + let mut rsc = TestRsc { + ui: UiData::default(), + }; + let trace = Rc::new(RefCell::new(Vec::new())); + let child = rsc.ui.widgets.add_strong(TracedLeaf { + height: 20.0, + trace: trace.clone(), + }); + let child_weak = child.weak(); + let parent = rsc.ui.widgets.add_strong(TracedParent { + child: child.any(), + reads_child_size: true, + trace: trace.clone(), + }); + let parent_weak = parent.weak(); + let root = parent.any(); + let mut render = UiRenderState::new(); + render.resize((100.0, 100.0)); + render.update(&root, &mut rsc); + trace.borrow_mut().clear(); + + rsc.ui.widgets.get_mut(&child_weak).unwrap().height = 40.0; + render.update(&root, &mut rsc); + + assert_eq!(&*trace.borrow(), &["parent", "child"]); + assert_eq!(render.active[&parent_weak.id()].size.y, Len::abs(40.0)); +} + +#[test] +fn a_parent_that_ignores_child_size_is_not_invalidated_with_it() { + let mut rsc = TestRsc { + ui: UiData::default(), + }; + let trace = Rc::new(RefCell::new(Vec::new())); + let child = rsc.ui.widgets.add_strong(TracedLeaf { + height: 20.0, + trace: trace.clone(), + }); + let child_weak = child.weak(); + let parent = rsc.ui.widgets.add_strong(TracedParent { + child: child.any(), + reads_child_size: false, + trace: trace.clone(), + }); + let root = parent.any(); + let mut render = UiRenderState::new(); + render.resize((100.0, 100.0)); + render.update(&root, &mut rsc); + trace.borrow_mut().clear(); + + rsc.ui.widgets.get_mut(&child_weak).unwrap().height = 40.0; + render.update(&root, &mut rsc); + + assert_eq!(&*trace.borrow(), &["child"]); +} + +/// A content-sized vertical container grows vertically when one child grows, +/// but that does not invalidate another child's retained height. Its width is +/// the context that could change that height (for example through wrapping), +/// and that stayed fixed. +#[test] +fn a_span_reuses_unchanged_sibling_sizes_when_only_its_along_extent_changes() { + let mut rsc = TestRsc { + ui: UiData::default(), + }; + let changed_draws = Rc::new(Cell::new(0)); + let sibling_draws = Rc::new(Cell::new(0)); + let changed = rsc.ui.widgets.add_strong(CountedLeaf { + height: 20.0, + draws: changed_draws.clone(), + }); + let changed_weak = changed.weak(); + let sibling = rsc.ui.widgets.add_strong(CountedLeaf { + height: 20.0, + draws: sibling_draws.clone(), + }); + let span = rsc.ui.widgets.add_strong(Span { + children: vec![changed.any(), sibling.any()], + dir: Dir::DOWN, + gap: Len::ZERO, + }); + // `Sized` settles its child from the full offered box into the content + // height, reproducing the retained-region change a nested content-sized + // row sees when one of its children grows. + let root = rsc + .ui + .widgets + .add_strong(Sized { + inner: span.any(), + x: None, + y: None, + }) + .any(); + let mut render = UiRenderState::new(); + render.resize((100.0, 100.0)); + render.update(&root, &mut rsc); + changed_draws.set(0); + sibling_draws.set(0); + + rsc.ui.widgets.get_mut(&changed_weak).unwrap().height = 40.0; + render.update(&root, &mut rsc); + + assert!(changed_draws.get() > 0, "the changed child was not redrawn"); + assert_eq!( + sibling_draws.get(), + 0, + "a vertical size change redrew a sibling whose width stayed fixed" + ); +} + #[test] fn a_child_coordinate_offset_moves_only_the_child_subtree() { let mut rsc = TestRsc { @@ -721,7 +924,7 @@ struct MoveThenPlace { } impl Widget for MoveThenPlace { - fn draw(&mut self, painter: &mut Painter) -> Size { + fn draw(&mut self, painter: &mut Painter) { let offer = UiRegion::new( UiSpan::FULL, UiSpan::new( @@ -738,7 +941,7 @@ impl Widget for MoveThenPlace { ), ); painter.place(&self.inner, place); - Size::default() + painter.set_size(Size::default()); } } diff --git a/src/sense.rs b/src/sense.rs index a49539e..9f3729a 100644 --- a/src/sense.rs +++ b/src/sense.rs @@ -21,7 +21,7 @@ pub enum CursorSense { HoverStart, Hovering, HoverEnd, - Scroll, + Scroll(Axis), /// Delivered exactly once, in place of `PressEnd`, to whichever widget /// currently holds pointer capture (`UiRenderState::capture_pointer`) /// when the button lifts -- see `iris::sense`'s pointer-capture doc @@ -47,7 +47,10 @@ pub enum CursorSense { } #[derive(Clone)] -pub struct CursorSenses(Vec); +pub struct CursorSenses { + senses: Vec, + drag_axis: Option, +} impl Event for CursorSenses { type Data<'a> = CursorData<'a>; @@ -74,7 +77,13 @@ impl Event for CursorSenses { if data.sense == CursorSense::Drop || data.sense == CursorSense::Cancel { return self.contains(&data.sense).then(|| data.clone()); } - if let Some(sense) = should_run(self, &data.cursor, data.hover) { + if let Some(sense) = should_run( + self, + &data.cursor, + data.hover, + data.drag_axis, + data.captured, + ) { let mut data = data.clone(); data.sense = sense; Some(data) @@ -84,6 +93,30 @@ impl Event for CursorSenses { } } +impl CursorSenses { + fn consumes(&self, data: &CursorData<'_>, momentary_active: bool) -> bool { + if !momentary_active { + return true; + } + let Some(sense) = should_run( + self, + &data.cursor, + data.hover, + data.drag_axis, + data.captured, + ) else { + return false; + }; + match (self.drag_axis, sense) { + // Directional drags are candidates until movement chooses an + // axis. Only the matching one consumes its visual layer. + (Some(axis), CursorSense::Pressing(_)) => data.captured || data.drag_axis == Some(axis), + (Some(_), CursorSense::PressStart(_) | CursorSense::PressEnd(_)) => false, + _ => sense.is_momentary(), + } + } +} + impl CursorSense { pub fn click() -> Self { Self::PressStart(CursorButton::Left) @@ -109,6 +142,16 @@ impl CursorSense { pub fn drag_senses() -> CursorSenses { Self::click_or_drag() | Self::unclick() | Self::Drop | Self::Cancel } + + /// The frames of a drag along one axis. Before the gesture crosses + /// [`DRAG_SLOP`], directional listeners observe without consuming a + /// visual layer; once its direction is known, only listeners for that + /// axis receive and consume it. + pub fn drag(axis: Axis) -> CursorSenses { + let mut senses = Self::drag_senses(); + senses.drag_axis = Some(axis); + senses + } pub fn is_dragging(&self) -> bool { matches!(self, CursorSense::Pressing(CursorButton::Left)) } @@ -261,6 +304,11 @@ pub struct CursorData<'a> { pub scroll_delta: Vec2, pub hover: ActivationState, pub cursor: CursorState, + /// The direction selected after this press crossed [`DRAG_SLOP`]. + /// `None` while the gesture is still only a press. + pub drag_axis: Option, + /// Whether this sample bypassed hit testing for the pointer holder. + pub captured: bool, /// the first sense that triggered this pub sense: CursorSense, pub render: &'a UiRenderState, @@ -294,6 +342,8 @@ pub struct CursorData<'a> { pub struct PointerInput { captured: Option, pressed: Vec, + press_origin: Option, + drag_axis: Option, } impl PointerInput { @@ -393,6 +443,22 @@ impl SensorUi for UiRenderState { holder: std::cell::Cell::new(pointer.captured), }; let button_down = cursor.buttons.select(&CursorButton::Left).is_on(); + if cursor.buttons.left.is_start() { + pointer.press_origin = Some(cursor.pos); + pointer.drag_axis = None; + } else if button_down + && pointer.drag_axis.is_none() + && let Some(origin) = pointer.press_origin + { + let moved = cursor.pos - origin; + if moved.x.abs().max(moved.y.abs()) > DRAG_SLOP { + pointer.drag_axis = Some(if moved.x.abs() > moved.y.abs() { + Axis::X + } else { + Axis::Y + }); + } + } // The platform took the gesture away (`CursorState::cancelled`). // Everybody still tracking this press hears about it -- the @@ -410,6 +476,8 @@ impl SensorUi for UiRenderState { } } requests.release(); + pointer.press_origin = None; + pointer.drag_axis = None; for id in told { deliver_cancel(self, rsc, state, id, &cursor, window_size, &requests); } @@ -458,6 +526,8 @@ impl SensorUi for UiRenderState { scroll_delta: cursor.scroll_delta, hover: ActivationState::On, cursor: cursor.clone(), + drag_axis: pointer.drag_axis, + captured: true, sense, render: self, pointer: &requests, @@ -466,6 +536,8 @@ impl SensorUi for UiRenderState { if !button_down { requests.release(); pointer.pressed.clear(); + pointer.press_origin = None; + pointer.drag_axis = None; } pointer.captured = requests.holder(); rsc.events_mut().get_type::().global = pointer; @@ -524,20 +596,6 @@ impl SensorUi for UiRenderState { // widget that registered a matching non-hover sense // consumes it -- a button that only registered `click()` // must not block a scroll meant for the list behind it. - let consumed = if momentary_active { - rsc.events_mut() - .get_type::() - .registered(*id) - .any(|senses| { - matches!(should_run(senses, &cursor, sensor.hover), Some(s) if s.is_momentary()) - }) - } else { - true - }; - if consumed { - sensed = true; - } - let cursor = cursor.clone(); let data = CursorData { @@ -546,12 +604,22 @@ impl SensorUi for UiRenderState { scroll_delta: cursor.scroll_delta, hover: sensor.hover, cursor, + drag_axis: pointer.drag_axis, + captured: false, // this does not have any meaning; // might wanna set up Event to have a prepare stage sense: CursorSense::Hovering, render: self, pointer: &requests, }; + let consumes = rsc + .events_mut() + .get_type::() + .registered(*id) + .any(|senses| senses.consumes(&data, momentary_active)); + if consumes { + sensed = true; + } rsc.run_event::(*id, data, state); // Anything handed a frame while the button is down may // have opened a gesture on it, and is owed a `Cancel` if @@ -563,7 +631,7 @@ impl SensorUi for UiRenderState { pointer.pressed.push(*id); } } - if sensed { + if sensed || requests.holder().is_some() { break; } } @@ -597,6 +665,10 @@ impl SensorUi for UiRenderState { // A cancel handler may itself have captured (a widget deciding // the gesture is now its own); `requests` is still the truth. pointer.captured = requests.holder(); + if !button_down { + pointer.press_origin = None; + pointer.drag_axis = None; + } rsc.events_mut().get_type::().global = pointer; } } @@ -625,6 +697,8 @@ fn deliver_cancel( scroll_delta: cursor.scroll_delta, hover: ActivationState::On, cursor: cursor.clone(), + drag_axis: None, + captured: false, sense: CursorSense::Cancel, render, pointer, @@ -636,6 +710,8 @@ pub fn should_run( senses: &CursorSenses, cursor: &CursorState, hover: ActivationState, + drag_axis: Option, + captured: bool, ) -> Option { // Every sense below that is about the *pointer* rather than about // hovering needs the pointer to actually be on this widget, and @@ -670,7 +746,12 @@ pub fn should_run( CursorSense::HoverStart => hover.is_start(), CursorSense::Hovering => hover.is_on(), CursorSense::HoverEnd => hover.is_end(), - CursorSense::Scroll => on_this && cursor.scroll_delta != Vec2::ZERO, + CursorSense::Scroll(axis) => { + on_this + && cursor.scroll_delta.axis(*axis) != 0.0 + && cursor.scroll_delta.axis(*axis).abs() + >= cursor.scroll_delta.axis(!*axis).abs() + } // Never derived here -- `Drop` only ever fires through // `CursorSenses::should_run`'s own special case, ahead of this // loop, for the one widget `run_sensors`' capture branch is @@ -683,7 +764,11 @@ pub fn should_run( // note above; both are set by `run_sensors` alone, for the one // widget it is delivering to this frame. CursorSense::Drop | CursorSense::Cancel => false, - } { + } && (captured + || !matches!(sense, CursorSense::Pressing(_)) + || senses.drag_axis.is_none() + || senses.drag_axis == drag_axis) + { return Some(*sense); } } @@ -744,19 +829,22 @@ impl Deref for CursorSenses { type Target = Vec; fn deref(&self) -> &Self::Target { - &self.0 + &self.senses } } impl DerefMut for CursorSenses { fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 + &mut self.senses } } impl From for CursorSenses { fn from(val: CursorSense) -> Self { - CursorSenses(vec![val]) + CursorSenses { + senses: vec![val], + drag_axis: None, + } } } @@ -764,7 +852,10 @@ impl BitOr for CursorSense { type Output = CursorSenses; fn bitor(self, rhs: Self) -> Self::Output { - CursorSenses(vec![self, rhs]) + CursorSenses { + senses: vec![self, rhs], + drag_axis: None, + } } } @@ -772,7 +863,7 @@ impl BitOr for CursorSenses { type Output = Self; fn bitor(mut self, rhs: CursorSense) -> Self::Output { - self.0.push(rhs); + self.senses.push(rhs); self } } diff --git a/src/sense_tests.rs b/src/sense_tests.rs index d8f893b..353379c 100644 --- a/src/sense_tests.rs +++ b/src/sense_tests.rs @@ -73,9 +73,13 @@ fn a_button_over_a_list_scrolls_the_list_and_still_clicks() { let clicked = Rc::new(Cell::new(false)); { let scrolled = scrolled.clone(); - rsc.register_event(list_weak, CursorSense::Scroll, move |_ctx, _rsc| { - scrolled.set(true); - }); + rsc.register_event( + list_weak, + CursorSense::Scroll(Axis::Y), + move |_ctx, _rsc| { + scrolled.set(true); + }, + ); } { let clicked = clicked.clone(); @@ -461,10 +465,10 @@ fn taking_the_pointer_cancels_everyone_else_tracking_the_press() { events: EventManager::default(), }; - // The bystander *contains* the capturer, which is the real shape: a - // transcript's `LazySpan` and one row's own text both track the same - // press, and a `Stack`'s siblings would be on separate layers where - // only the topmost is dispatched to at all. + // The bystander contains the capturer on a lower visual layer: the + // shape of a vertical transcript scroller with a higher horizontal + // scroller inside one row. Both observe the undecided press, then only + // the recognizer matching its direction may consume it. let capturer = rsc.ui.widgets.add_strong(Rect::new(UiColor::WHITE)); let capturer_weak = capturer.weak(); let bystander = rsc.ui.widgets.add_strong(Stack { @@ -478,7 +482,7 @@ fn taking_the_pointer_cancels_everyone_else_tracking_the_press() { let capturer_saw = capturer_saw.clone(); rsc.register_event( capturer_weak, - CursorSense::drag_senses(), + CursorSense::drag(Axis::X), move |ctx, _rsc| { capturer_saw.set(capturer_saw.get() + 1); if matches!(ctx.data.sense, CursorSense::Pressing(_)) { @@ -493,7 +497,7 @@ fn taking_the_pointer_cancels_everyone_else_tracking_the_press() { let (cancelled, ended) = (cancelled.clone(), ended.clone()); rsc.register_event( bystander_weak, - CursorSense::drag_senses(), + CursorSense::drag(Axis::Y), move |ctx, _rsc| match ctx.data.sense { CursorSense::Cancel => cancelled.set(cancelled.get() + 1), CursorSense::PressEnd(_) | CursorSense::Drop => ended.set(ended.get() + 1), @@ -515,7 +519,7 @@ fn taking_the_pointer_cancels_everyone_else_tracking_the_press() { render.update(&root, &mut rsc); assert_eq!(cancelled.get(), 0, "nothing has captured yet"); - let mut moved = cursor_at((50.0, 20.0).into()); + let mut moved = cursor_at((80.0, 50.0).into()); moved.buttons.left = ActivationState::On; render.run_sensors(&mut rsc, &mut state, moved, win); render.update(&root, &mut rsc); @@ -590,6 +594,9 @@ fn a_drag_pans_whichever_nested_scroll_area_owns_its_axis() { record.set(Some(id)); id }) + // The horizontal area is visually above the vertical one, as + // it is when a raised transcript row contains sideways content. + .layer_offset(1) .scrollable(Axis::Y, Pin::Start) .add_strong(&mut rsc); let inner = seen.get().unwrap(); diff --git a/src/widget/image.rs b/src/widget/image.rs index 615a5c8..64fa4be 100644 --- a/src/widget/image.rs +++ b/src/widget/image.rs @@ -6,10 +6,10 @@ pub struct Image { } impl Widget for Image { - fn draw(&mut self, painter: &mut Painter) -> Size { + fn draw(&mut self, painter: &mut Painter) { let size = self.handle.size(); painter.texture_within(&self.handle, size.align(Align::TOP_LEFT)); - Size::abs(size) + painter.set_size(Size::abs(size)); } fn size_hint(&self, axis: Axis) -> Option { diff --git a/src/widget/mask.rs b/src/widget/mask.rs index 241921f..144c0d3 100644 --- a/src/widget/mask.rs +++ b/src/widget/mask.rs @@ -6,7 +6,7 @@ pub struct Masked { } impl Widget for Masked { - fn draw(&mut self, painter: &mut Painter) -> Size { + fn draw(&mut self, painter: &mut Painter) { match &self.shape { Some(shape) => { painter.child_layer(); @@ -16,9 +16,9 @@ impl Widget for Masked { } None => painter.set_mask(painter.region()), } - let used = painter.widget(&self.inner); + let used = painter.widget(&self.inner).size(); painter.place_used(&self.inner, used, UiRegion::FULL); - used + painter.set_size(used); } fn requires_exact_region(&self) -> bool { diff --git a/src/widget/position/align.rs b/src/widget/position/align.rs index 69625ce..fe166a5 100644 --- a/src/widget/position/align.rs +++ b/src/widget/position/align.rs @@ -6,8 +6,8 @@ pub struct Aligned { } impl Widget for Aligned { - fn draw(&mut self, painter: &mut Painter) -> Size { - let used = painter.widget(&self.inner); + fn draw(&mut self, painter: &mut Painter) { + let used = painter.widget(&self.inner).size(); let density = painter.density(); let (x, y) = self.align.tuple(); let region = UiRegion::new( @@ -19,6 +19,6 @@ impl Widget for Aligned { .align(y.unwrap_or(AxisAlign::Neg)), ); painter.place(&self.inner, region); - used + painter.set_size(used); } } diff --git a/src/widget/position/layer.rs b/src/widget/position/layer.rs index e3d63c2..47cf790 100644 --- a/src/widget/position/layer.rs +++ b/src/widget/position/layer.rs @@ -6,12 +6,12 @@ pub struct LayerOffset { } impl Widget for LayerOffset { - fn draw(&mut self, painter: &mut Painter) -> Size { + fn draw(&mut self, painter: &mut Painter) { for _ in 0..self.offset { painter.next_layer(); } - let used = painter.widget(&self.inner); + let used = painter.widget(&self.inner).size(); painter.place_used(&self.inner, used, UiRegion::FULL); - used + painter.set_size(used); } } diff --git a/src/widget/position/lazy_span.rs b/src/widget/position/lazy_span.rs index 552975d..6be7b8f 100644 --- a/src/widget/position/lazy_span.rs +++ b/src/widget/position/lazy_span.rs @@ -939,7 +939,7 @@ impl LazySpan { (Some(h), None) => { let (lead, trail) = placement.edges(h); let region = self.row_region(lead, trail); - let used = painter.widget_within(self.slot_widget(slot), region); + let used = painter.widget_within(self.slot_widget(slot), region).size(); let height = resolve(used); if height != h { let (new_lead, new_trail) = placement.edges(height); @@ -957,7 +957,7 @@ impl LazySpan { Placement::Trailing(_) => 0.0, }; let first = self.row_region(measure_from, measure_from + GENEROUS_PADDING); - let height = resolve(painter.widget_within(self.slot_widget(slot), first)); + let height = resolve(painter.widget_within(self.slot_widget(slot), first).size()); let (lead, trail) = placement.edges(height); if is_anchor { self.stabilize_lead(painter, measure_from, lead); @@ -1072,7 +1072,7 @@ impl Widget for LazySpan { self.tick_fling(now) } - fn draw(&mut self, painter: &mut Painter) -> Size { + fn draw(&mut self, painter: &mut Painter) { let axis = self.dir.axis; let output_len = painter.output_size().axis(axis); self.viewport_len = painter.region().axis(axis).len().to_abs(output_len); @@ -1081,7 +1081,8 @@ impl Widget for LazySpan { self.repair_anchor(); if self.anchor.is_none() { self.extents.clear(); - return Size::REST; + painter.set_size(Size::REST); + return; } // What a wheel, a drag or a fling asked for since the last frame, @@ -1154,7 +1155,7 @@ impl Widget for LazySpan { self.rehome_anchor(); self.update_snap_end(); self.ctl.set_travel(self.travel()); - Size::REST + painter.set_size(Size::REST); } fn size_hint(&self, _axis: Axis) -> Option { diff --git a/src/widget/position/max_size.rs b/src/widget/position/max_size.rs index f1df2f2..f9d243e 100644 --- a/src/widget/position/max_size.rs +++ b/src/widget/position/max_size.rs @@ -35,7 +35,7 @@ impl MaxSize { } impl Widget for MaxSize { - fn draw(&mut self, painter: &mut Painter) -> Size { + fn draw(&mut self, painter: &mut Painter) { let output = painter.output_size(); let density = painter.density(); let offered = painter.px_size(); @@ -43,12 +43,12 @@ impl Widget for MaxSize { x: Self::clamp_region(offered.x, self.x, output.x, density), y: Self::clamp_region(offered.y, self.y, output.y, density), }; - let used = painter.widget_within(&self.inner, region); + let used = painter.widget_within(&self.inner, region).size(); let size = Size { x: Self::clamp(used.x, self.x, output.x, density), y: Self::clamp(used.y, self.y, output.y, density), }; painter.place_used(&self.inner, size, UiRegion::FULL); - size + painter.set_size(size); } } diff --git a/src/widget/position/offset.rs b/src/widget/position/offset.rs index 6e746fa..3765838 100644 --- a/src/widget/position/offset.rs +++ b/src/widget/position/offset.rs @@ -6,10 +6,10 @@ pub struct Offset { } impl Widget for Offset { - fn draw(&mut self, painter: &mut Painter) -> Size { + fn draw(&mut self, painter: &mut Painter) { let region = UiRegion::FULL.offset(self.amt); - let used = painter.widget_within(&self.inner, region); + let used = painter.widget_within(&self.inner, region).size(); painter.place_used(&self.inner, used, region); - used + painter.set_size(used); } } diff --git a/src/widget/position/pad.rs b/src/widget/position/pad.rs index 30161c8..7e1f39a 100644 --- a/src/widget/position/pad.rs +++ b/src/widget/position/pad.rs @@ -8,11 +8,11 @@ pub struct Pad { } impl Widget for Pad { - fn draw(&mut self, painter: &mut Painter) -> Size { + fn draw(&mut self, painter: &mut Painter) { let density = painter.density(); let offered = painter.px_size(); let region = self.padding.region(density); - let used = painter.widget_within(&self.inner, region); + let used = painter.widget_within(&self.inner, region).size(); painter.place_used(&self.inner, used, region); let width = self.padding.left.apply_rest(density).abs + self.padding.right.apply_rest(density).abs; @@ -26,7 +26,7 @@ impl Widget for Pad { if needed.x <= offered.x + 0.01 && needed.y <= offered.y + 0.01 { self.exact_region = false; } - size + painter.set_size(size); } fn requires_exact_region(&self) -> bool { diff --git a/src/widget/position/scroll_area.rs b/src/widget/position/scroll_area.rs index e3cf6c1..963cbec 100644 --- a/src/widget/position/scroll_area.rs +++ b/src/widget/position/scroll_area.rs @@ -30,7 +30,7 @@ impl Widget for ScrollArea { self.tick_fling(now) } - fn draw(&mut self, painter: &mut Painter) -> Size { + fn draw(&mut self, painter: &mut Painter) { let axis = self.ctl.axis(); let container_len = painter.px_size().axis(axis); self.container_len = container_len; @@ -41,7 +41,9 @@ impl Widget for ScrollArea { self.ctl.set_amt(travelled); let hint = self.content_len.unwrap_or(container_len); - let used = painter.widget_within(&self.inner, self.child_region(hint)); + let used = painter + .widget_within(&self.inner, self.child_region(hint)) + .size(); let measured = used .axis(axis) @@ -62,7 +64,10 @@ impl Widget for ScrollArea { fwd: range - amt, }); - painter.place(&self.inner, self.child_region(measured)) + let size = painter + .place(&self.inner, self.child_region(measured)) + .size(); + painter.set_size(size); } } diff --git a/src/widget/position/scrollable.rs b/src/widget/position/scrollable.rs index 5d0fac9..b1021e1 100644 --- a/src/widget/position/scrollable.rs +++ b/src/widget/position/scrollable.rs @@ -502,11 +502,11 @@ where W: Widget + Scrollable, WL: WidgetLike, { - w.on(CursorSense::Scroll, move |ctx, rsc| { + w.on(CursorSense::Scroll(axis), move |ctx, rsc| { let delta = ctx.data.scroll_delta.axis(axis) * 50.0; ctx.widget(rsc).scroll(delta); }) - .on(CursorSense::drag_senses(), |ctx, rsc: &mut Rsc| { + .on(CursorSense::drag(axis), |ctx, rsc: &mut Rsc| { let id = ctx.widget.id(); let (sense, pos) = (ctx.data.sense, ctx.data.cursor.pos); let flung = ctx diff --git a/src/widget/position/sized.rs b/src/widget/position/sized.rs index f34ff80..c648a2a 100644 --- a/src/widget/position/sized.rs +++ b/src/widget/position/sized.rs @@ -7,7 +7,7 @@ pub struct Sized { } impl Widget for Sized { - fn draw(&mut self, painter: &mut Painter) -> Size { + fn draw(&mut self, painter: &mut Painter) { let density = painter.density(); let mut region = UiRegion::FULL; if let Some(x) = self.x { @@ -16,13 +16,13 @@ impl Widget for Sized { if let Some(y) = self.y { region.y = y.apply_rest(density).align(AxisAlign::Neg); } - let used = painter.widget_within(&self.inner, region); + let used = painter.widget_within(&self.inner, region).size(); let size = Size { x: self.x.map(|x| x.fold_dp(density)).unwrap_or(used.x), y: self.y.map(|y| y.fold_dp(density)).unwrap_or(used.y), }; painter.place_used(&self.inner, size, UiRegion::FULL); - size + painter.set_size(size); } fn size_hint(&self, axis: Axis) -> Option { diff --git a/src/widget/position/span.rs b/src/widget/position/span.rs index 2040186..38b73e1 100644 --- a/src/widget/position/span.rs +++ b/src/widget/position/span.rs @@ -8,7 +8,7 @@ pub struct Span { } impl Widget for Span { - fn draw(&mut self, painter: &mut Painter) -> Size { + fn draw(&mut self, painter: &mut Painter) { let axis = self.dir.axis; let gap = self.gap.apply_rest(painter.density()).abs; @@ -29,7 +29,7 @@ impl Widget for Span { slot.flip(); } let region = UiRegion::from_axis(axis, slot, UiSpan::FULL); - let len = painter.widget_within(child, region).axis(axis); + let len = painter.widget_within(child, region).size().axis(axis); lens[i] = Some(len); drawn[i] = true; len @@ -65,9 +65,9 @@ impl Widget for Span { child_region.flip(axis); } let used = if drawn[i] { - painter.place(child, child_region) + painter.place(child, child_region).size() } else { - painter.widget_within(child, child_region) + painter.widget_within(child, child_region).size() }; placed.push(child_region); start.abs += gap; @@ -97,7 +97,7 @@ impl Widget for Span { Len::default() }; - Size::from_axis(axis, along, ortho_len) + painter.set_size(Size::from_axis(axis, along, ortho_len)); } } diff --git a/src/widget/position/stack.rs b/src/widget/position/stack.rs index 6c4fd40..90fe230 100644 --- a/src/widget/position/stack.rs +++ b/src/widget/position/stack.rs @@ -8,7 +8,7 @@ pub struct Stack { } impl Widget for Stack { - fn draw(&mut self, painter: &mut Painter) -> Size { + fn draw(&mut self, painter: &mut Painter) { let density = painter.density(); let known = match self.size { StackSize::Default => Some(Size::REST), @@ -25,15 +25,15 @@ impl Widget for Stack { if let Some(child) = iter.next() { painter.child_layer(); used.push(match region { - Some(region) => painter.widget_within(child, region), - None => painter.widget(child), + Some(region) => painter.widget_within(child, region).size(), + None => painter.widget(child).size(), }); } for child in iter { painter.next_layer(); used.push(match region { - Some(region) => painter.widget_within(child, region), - None => painter.widget(child), + Some(region) => painter.widget_within(child, region).size(), + None => painter.widget(child).size(), }); } let size = match self.size { @@ -50,7 +50,7 @@ impl Widget for Stack { painter.place(child, child_region); } } - size + painter.set_size(size); } fn size_hint(&self, _axis: Axis) -> Option { diff --git a/src/widget/ptr.rs b/src/widget/ptr.rs index c016c2c..ce3b625 100644 --- a/src/widget/ptr.rs +++ b/src/widget/ptr.rs @@ -6,14 +6,15 @@ pub struct WidgetPtr { } impl Widget for WidgetPtr { - fn draw(&mut self, painter: &mut Painter) -> Size { - if let Some(id) = &self.inner { - let used = painter.widget(id); + fn draw(&mut self, painter: &mut Painter) { + let size = if let Some(id) = &self.inner { + let used = painter.widget(id).size(); painter.place_used(id, used, UiRegion::FULL); used } else { Size::ZERO - } + }; + painter.set_size(size); } fn is_size_independent(&self) -> bool { diff --git a/src/widget/rect.rs b/src/widget/rect.rs index 68fe5fe..588adfd 100644 --- a/src/widget/rect.rs +++ b/src/widget/rect.rs @@ -28,14 +28,14 @@ impl Rect { } impl Widget for Rect { - fn draw(&mut self, painter: &mut Painter) -> Size { + fn draw(&mut self, painter: &mut Painter) { painter.primitive(RectPrimitive { color: self.color, radius: self.radius.fold_dp(painter.density()).abs, thickness: self.thickness, inner_radius: self.inner_radius, }); - Size::REST + painter.set_size(Size::REST); } fn size_hint(&self, _axis: Axis) -> Option { diff --git a/src/widget/text/edit.rs b/src/widget/text/edit.rs index 12eb8cf..6ca9709 100644 --- a/src/widget/text/edit.rs +++ b/src/widget/text/edit.rs @@ -92,7 +92,7 @@ impl TextEdit { } impl Widget for TextEdit { - fn draw(&mut self, painter: &mut Painter) -> Size { + fn draw(&mut self, painter: &mut Painter) { let base = painter.layer; painter.child_layer(); let used = self.view.draw(painter); @@ -100,7 +100,8 @@ impl Widget for TextEdit { let region = self.region(); let Some(selection) = self.selection else { - return used; + painter.set_size(used); + return; }; let layout = self.view.buf.layout(); @@ -122,7 +123,7 @@ impl Widget for TextEdit { RectPrimitive::color(Color::WHITE), size.align(Align::TOP_LEFT).offset(top_left).within(®ion), ); - used + painter.set_size(used); } fn requires_exact_region(&self) -> bool { diff --git a/src/widget/text/mod.rs b/src/widget/text/mod.rs index dc44f12..685c95c 100644 --- a/src/widget/text/mod.rs +++ b/src/widget/text/mod.rs @@ -111,7 +111,7 @@ impl TextView { if self.is_blank() && let Some(hint) = &self.hint { - return painter.widget(hint); + return painter.widget(hint).size(); } let region = tex.size.align(self.align); let within = region.within(&painter.region()); @@ -141,9 +141,10 @@ impl Text { } impl Widget for Text { - fn draw(&mut self, painter: &mut Painter) -> Size { + fn draw(&mut self, painter: &mut Painter) { self.update_buf(); - self.view.draw(painter) + let size = self.view.draw(painter); + painter.set_size(size); } fn requires_exact_region(&self) -> bool {