Make Iris layout dependencies explicit

This commit is contained in:
iris committed 2026-09-09 22:35:03 -04:00
1 parent 2bc0ff1866
commit 3ae034a47b
25 files changed
+609 -186

No files matched your search

+5 -1
View File
@@ -11,12 +11,16 @@ pub struct ActiveData {
pub textures: Vec<TextureHandle>,
pub primitives: Vec<PrimitiveHandle>,
pub children: Vec<WidgetId>,
/// 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<WidgetId>,
/// 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,
+1 -1
View File
@@ -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)]
+92 -20
View File
@@ -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<std::vec::IntoIter<PrimitiveHandle>>,
pub(super) children: Vec<WidgetId>,
pub(super) reuse_child_sizes: bool,
pub(super) size_dependencies: Vec<WidgetId>,
pub(super) size: Option<Size>,
/// 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<P: Primitive>(&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<W: ?Sized>(&mut self, id: &StrongWidget<W>) -> 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<W>) -> 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<W: ?Sized>(&mut self, id: &StrongWidget<W>, region: UiRegion) -> Size {
pub fn widget_within<'p, W: ?Sized>(
&'p mut self,
id: &StrongWidget<W>,
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<W: ?Sized>(&self, id: &StrongWidget<W>, axis: Axis) -> Option<Len> {
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<W: ?Sized>(&mut self, id: &StrongWidget<W>, axis: Axis) -> Option<Len> {
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<W: ?Sized>(&mut self, id: &StrongWidget<W>, region: UiRegion) -> Size {
fn widget_at<'p, W: ?Sized>(
&'p mut self,
id: &StrongWidget<W>,
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<W: ?Sized>(&mut self, id: &StrongWidget<W>, region: UiRegion) -> Size {
pub fn place<'p, W: ?Sized>(
&'p mut self,
id: &StrongWidget<W>,
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<W>,
used: Size,
within: UiRegion,
) -> Size {
) -> DrawResult<'_, 'a> {
let region = self.fit_region(used, within);
self.place(id, region)
}
+101 -64
View File
@@ -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<Option<&'a StrongWidget>>) -> 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<WidgetId>, 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))
}
}
+3 -3
View File
@@ -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<Len> {
@@ -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 {
+209 -6
View File
@@ -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<RefCell<Vec<&'static str>>>,
}
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<RefCell<Vec<&'static str>>>,
}
struct CountedLeaf {
height: f32,
draws: Rc<Cell<u32>>,
}
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());
}
}
+116 -25
View File
@@ -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<CursorSense>);
pub struct CursorSenses {
senses: Vec<CursorSense>,
drag_axis: Option<Axis>,
}
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<Axis>,
/// 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<WidgetId>,
pressed: Vec<WidgetId>,
press_origin: Option<Vec2>,
drag_axis: Option<Axis>,
}
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::<CursorSense>().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::<CursorSense>()
.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::<CursorSense>()
.registered(*id)
.any(|senses| senses.consumes(&data, momentary_active));
if consumes {
sensed = true;
}
rsc.run_event::<CursorSense>(*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::<CursorSense>().global = pointer;
}
}
@@ -625,6 +697,8 @@ fn deliver_cancel<Rsc: HasEvents>(
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<Axis>,
captured: bool,
) -> Option<CursorSense> {
// 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<CursorSense>;
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<CursorSense> 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<CursorSense> for CursorSenses {
type Output = Self;
fn bitor(mut self, rhs: CursorSense) -> Self::Output {
self.0.push(rhs);
self.senses.push(rhs);
self
}
}
+17 -10
View File
@@ -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();
+2 -2
View File
@@ -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<Len> {
+3 -3
View File
@@ -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 {
+3 -3
View File
@@ -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);
}
}
+3 -3
View File
@@ -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);
}
}
+6 -5
View File
@@ -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<Len> {
+3 -3
View File
@@ -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);
}
}
+3 -3
View File
@@ -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);
}
}
+3 -3
View File
@@ -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 {
+8 -3
View File
@@ -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);
}
}
+2 -2
View File
@@ -502,11 +502,11 @@ where
W: Widget + Scrollable,
WL: WidgetLike<Rsc, Tag, Widget = W>,
{
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
+3 -3
View File
@@ -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<Len> {
+5 -5
View File
@@ -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));
}
}
+6 -6
View File
@@ -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<Len> {
+5 -4
View File
@@ -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 {
+2 -2
View File
@@ -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<Len> {
+4 -3
View File
@@ -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(&region),
);
used
painter.set_size(used);
}
fn requires_exact_region(&self) -> bool {
+4 -3
View File
@@ -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 {