Make Iris layout dependencies explicit
This commit is contained in:
1 parent
2bc0ff1866
commit
3ae034a47b
25 files changed
+609
-186
No files matched your search
@@ -11,12 +11,16 @@ pub struct ActiveData {
|
|||||||
pub textures: Vec<TextureHandle>,
|
pub textures: Vec<TextureHandle>,
|
||||||
pub primitives: Vec<PrimitiveHandle>,
|
pub primitives: Vec<PrimitiveHandle>,
|
||||||
pub children: Vec<WidgetId>,
|
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`.
|
/// The inherited mask, not `own_mask`.
|
||||||
pub mask: MaskIdx,
|
pub mask: MaskIdx,
|
||||||
/// The widget's retained mask slot, or `MaskIdx::NONE`.
|
/// The widget's retained mask slot, or `MaskIdx::NONE`.
|
||||||
pub own_mask: MaskIdx,
|
pub own_mask: MaskIdx,
|
||||||
pub layer: LayerId,
|
pub layer: LayerId,
|
||||||
/// The last `Widget::draw` result.
|
/// The size recorded by the last `Widget::draw` through its painter.
|
||||||
pub size: Size,
|
pub size: Size,
|
||||||
/// Retained so descendants' parent links stay valid across redraws.
|
/// Retained so descendants' parent links stay valid across redraws.
|
||||||
pub move_slot: MoveIdx,
|
pub move_slot: MoveIdx,
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@ mod render_state;
|
|||||||
|
|
||||||
pub use access::*;
|
pub use access::*;
|
||||||
pub use active::*;
|
pub use active::*;
|
||||||
pub use painter::Painter;
|
pub use painter::{DrawResult, Painter};
|
||||||
pub use render_state::*;
|
pub use render_state::*;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|||||||
+92
-20
@@ -25,12 +25,46 @@ pub struct Painter<'a> {
|
|||||||
/// Previous handles, consumed in draw order and freed if left over.
|
/// Previous handles, consumed in draw order and freed if left over.
|
||||||
pub(super) recycle: std::iter::Peekable<std::vec::IntoIter<PrimitiveHandle>>,
|
pub(super) recycle: std::iter::Peekable<std::vec::IntoIter<PrimitiveHandle>>,
|
||||||
pub(super) children: Vec<WidgetId>,
|
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 layer: usize,
|
||||||
pub(super) id: WidgetId,
|
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> {
|
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) {
|
fn primitive_at<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
|
||||||
self.write_primitive(primitive, region, Drawn::Yes);
|
self.write_primitive(primitive, region, Drawn::Yes);
|
||||||
}
|
}
|
||||||
@@ -207,15 +241,19 @@ impl<'a> Painter<'a> {
|
|||||||
self.mask = self.own_mask;
|
self.mask = self.own_mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draws a widget within this widget's region, returning the size it
|
/// Draw a widget within this widget's region. Reading the result's size
|
||||||
/// reported using.
|
/// records that this widget's layout depends on the child.
|
||||||
pub fn widget<W: ?Sized>(&mut self, id: &StrongWidget<W>) -> Size {
|
pub fn widget<'p, W: ?Sized>(&'p mut self, id: &StrongWidget<W>) -> DrawResult<'p, 'a> {
|
||||||
self.widget_at(id, self.region)
|
self.widget_at(id, self.region)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draws a widget somewhere within this one.
|
/// Draws a widget somewhere within this one.
|
||||||
/// Useful for drawing child widgets in select areas.
|
/// 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))
|
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> {
|
pub fn known_len<W: ?Sized>(&mut self, id: &StrongWidget<W>, axis: Axis) -> Option<Len> {
|
||||||
if let Some(len) = self.rsc.widgets().get_dyn(id.id())?.size_hint(axis) {
|
let len = if let Some(len) = self.rsc.widgets().get_dyn(id.id())?.size_hint(axis) {
|
||||||
return Some(len.fold_dp(self.density()));
|
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()) {
|
len
|
||||||
return None;
|
|
||||||
}
|
|
||||||
self.state.active.get(&id.id()).map(|a| a.size.axis(axis))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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());
|
self.children.push(id.id());
|
||||||
// Passed directly rather than looked up from `self.active`: this
|
// Passed directly rather than looked up from `self.active`: this
|
||||||
// widget's own `ActiveData` (which would carry its `move_slot`) is
|
// widget's own `ActiveData` (which would carry its `move_slot`) is
|
||||||
@@ -289,19 +339,26 @@ impl<'a> Painter<'a> {
|
|||||||
self.mask,
|
self.mask,
|
||||||
Retained::default(),
|
Retained::default(),
|
||||||
self.rsc,
|
self.rsc,
|
||||||
)
|
);
|
||||||
|
DrawResult {
|
||||||
|
painter: self,
|
||||||
|
child: id.id(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Place an already-drawn child's used area, redrawing only if its size changes.
|
/// 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 region = region.within(&self.region);
|
||||||
let retained = self
|
let retained = self
|
||||||
.state
|
.state
|
||||||
.active
|
.active
|
||||||
.get(&id.id())
|
.get(&id.id())
|
||||||
.map(|active| (active.layer, active.mask));
|
.map(|active| (active.layer, active.mask));
|
||||||
if let Some(size) = self.state.place(id.id(), region, self.rsc) {
|
if self.state.place(id.id(), region, self.rsc).is_some() {
|
||||||
size
|
|
||||||
} else if let Some((layer, mask)) = retained {
|
} else if let Some((layer, mask)) = retained {
|
||||||
self.children.push(id.id());
|
self.children.push(id.id());
|
||||||
self.rsc.widgets_mut().needs_redraw.insert(id.id());
|
self.rsc.widgets_mut().needs_redraw.insert(id.id());
|
||||||
@@ -315,9 +372,24 @@ impl<'a> Painter<'a> {
|
|||||||
mask,
|
mask,
|
||||||
Retained::default(),
|
Retained::default(),
|
||||||
self.rsc,
|
self.rsc,
|
||||||
)
|
);
|
||||||
} else {
|
} 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>,
|
id: &StrongWidget<W>,
|
||||||
used: Size,
|
used: Size,
|
||||||
within: UiRegion,
|
within: UiRegion,
|
||||||
) -> Size {
|
) -> DrawResult<'_, 'a> {
|
||||||
let region = self.fit_region(used, within);
|
let region = self.fit_region(used, within);
|
||||||
self.place(id, region)
|
self.place(id, region)
|
||||||
}
|
}
|
||||||
|
|||||||
+101
-64
@@ -535,8 +535,16 @@ impl UiRenderState {
|
|||||||
let move_slot = Self::move_slot_for(old_move_slot, parent_move_slot, rsc);
|
let move_slot = Self::move_slot_for(old_move_slot, parent_move_slot, rsc);
|
||||||
|
|
||||||
let inherited_mask = mask;
|
let inherited_mask = mask;
|
||||||
let reuse_child_sizes =
|
// `Painter::layer` is a cursor widgets advance while assigning
|
||||||
old_region.is_some_and(|old| Self::same_size(old, region, self.output_size));
|
// 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 {
|
let mut painter = Painter {
|
||||||
state: self,
|
state: self,
|
||||||
region,
|
region,
|
||||||
@@ -550,6 +558,8 @@ impl UiRenderState {
|
|||||||
primitives: Vec::new(),
|
primitives: Vec::new(),
|
||||||
recycle: recycle.into_iter().peekable(),
|
recycle: recycle.into_iter().peekable(),
|
||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
|
size_dependencies: Vec::new(),
|
||||||
|
size: None,
|
||||||
reuse_child_sizes,
|
reuse_child_sizes,
|
||||||
rsc,
|
rsc,
|
||||||
};
|
};
|
||||||
@@ -561,7 +571,13 @@ impl UiRenderState {
|
|||||||
widget.size_hint(Axis::Y).map(|len| len.fold_dp(density)),
|
widget.size_hint(Axis::Y).map(|len| len.fold_dp(density)),
|
||||||
];
|
];
|
||||||
painter.state.draw_count += 1;
|
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!(
|
debug_assert!(
|
||||||
size.x.dp == 0.0 && size.y.dp == 0.0,
|
size.x.dp == 0.0 && size.y.dp == 0.0,
|
||||||
"widget {id:?} reported an unresolved `dp` size ({size:?}); \
|
"widget {id:?} reported an unresolved `dp` size ({size:?}); \
|
||||||
@@ -593,8 +609,10 @@ impl UiRenderState {
|
|||||||
primitives,
|
primitives,
|
||||||
recycle,
|
recycle,
|
||||||
children,
|
children,
|
||||||
|
size_dependencies,
|
||||||
|
size: _,
|
||||||
reuse_child_sizes: _,
|
reuse_child_sizes: _,
|
||||||
layer,
|
layer: _,
|
||||||
id,
|
id,
|
||||||
} = painter;
|
} = painter;
|
||||||
|
|
||||||
@@ -615,8 +633,9 @@ impl UiRenderState {
|
|||||||
textures,
|
textures,
|
||||||
primitives,
|
primitives,
|
||||||
children,
|
children,
|
||||||
|
size_dependencies,
|
||||||
mask: inherited_mask,
|
mask: inherited_mask,
|
||||||
layer,
|
layer: inherited_layer,
|
||||||
size,
|
size,
|
||||||
move_slot,
|
move_slot,
|
||||||
child_move_slot,
|
child_move_slot,
|
||||||
@@ -690,9 +709,13 @@ impl UiRenderState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn same_size(a: UiRegion, b: UiRegion, output: Vec2) -> bool {
|
fn same_size(a: UiRegion, b: UiRegion, output: Vec2) -> bool {
|
||||||
let a = a.size().to_abs(output);
|
Self::same_axis_size(a, b, Axis::X, output) && Self::same_axis_size(a, b, Axis::Y, output)
|
||||||
let b = b.size().to_abs(output);
|
}
|
||||||
(a.x - b.x).abs() < 0.01 && (a.y - b.y).abs() < 0.01
|
|
||||||
|
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(
|
pub(super) fn place(
|
||||||
@@ -920,12 +943,76 @@ impl UiRenderState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn redraw_updates(&mut self, rsc: &mut dyn UiRsc) {
|
pub fn redraw_updates(&mut self, rsc: &mut dyn UiRsc) {
|
||||||
while let Some(&id) = rsc.widgets().needs_redraw.iter().next() {
|
while rsc.widgets().has_updates() {
|
||||||
self.redraw(id, rsc);
|
// 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();
|
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 {
|
pub fn root_changed<'a>(&self, root: impl Into<Option<&'a StrongWidget>>) -> bool {
|
||||||
root.into().map(|r| r.id()) != self.old_root
|
root.into().map(|r| r.id()) != self.old_root
|
||||||
}
|
}
|
||||||
@@ -1207,61 +1294,18 @@ impl UiRenderState {
|
|||||||
|
|
||||||
/// redraws a widget that's currently active (drawn)
|
/// redraws a widget that's currently active (drawn)
|
||||||
pub fn redraw(&mut self, id: WidgetId, rsc: &mut dyn UiRsc) {
|
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);
|
rsc.widgets_mut().needs_redraw.remove(&id);
|
||||||
// An ancestor is drawing this widget right now, and that draw is
|
// An ancestor is drawing this widget right now, and that draw is
|
||||||
// about to write fresh primitives for it. Drawing it a second time
|
// about to write fresh primitives for it. Drawing it a second time
|
||||||
// here would leave one of the two copies on screen with nothing
|
// here would leave one of the two copies on screen with nothing
|
||||||
// owning it -- see `draw_started`'s own doc.
|
// owning it -- see `draw_started`'s own doc.
|
||||||
if self.draw_started.contains(&id) {
|
if self.draw_started.contains(&id) {
|
||||||
return None;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let active = self.remove(id, false, true, rsc)?;
|
let Some(active) = self.remove(id, false, true, rsc) else {
|
||||||
let old_size = active.size;
|
return;
|
||||||
|
};
|
||||||
let parent = active.parent;
|
let parent = active.parent;
|
||||||
// `old_move_slot` being `Some` below means the slot is reused in
|
// `old_move_slot` being `Some` below means the slot is reused in
|
||||||
// place rather than freshly parented, so this is only reached for
|
// place rather than freshly parented, so this is only reached for
|
||||||
@@ -1285,13 +1329,6 @@ impl UiRenderState {
|
|||||||
},
|
},
|
||||||
rsc,
|
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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ pub use view::*;
|
|||||||
pub use widgets::*;
|
pub use widgets::*;
|
||||||
|
|
||||||
pub trait Widget: Any {
|
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.
|
/// An exact, context-free length known without drawing or inspecting children.
|
||||||
fn size_hint(&self, _axis: Axis) -> Option<Len> {
|
fn size_hint(&self, _axis: Axis) -> Option<Len> {
|
||||||
@@ -45,8 +45,8 @@ pub trait Widget: Any {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for () {
|
impl Widget for () {
|
||||||
fn draw(&mut self, _: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
Size::ZERO
|
painter.set_size(Size::ZERO);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_size_independent(&self) -> bool {
|
fn is_size_independent(&self) -> bool {
|
||||||
|
|||||||
+209
-6
@@ -5,6 +5,7 @@
|
|||||||
//! CPU-side layout/move machinery LAYOUT.md is about.
|
//! CPU-side layout/move machinery LAYOUT.md is about.
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use std::{cell::Cell, cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
/// The minimal `UiRsc` a test needs: just the shared `UiData`, none of the
|
/// The minimal `UiRsc` a test needs: just the shared `UiData`, none of the
|
||||||
/// event/window/state plumbing `DefaultRsc` carries. `pub(crate)` so
|
/// event/window/state plumbing `DefaultRsc` carries. `pub(crate)` so
|
||||||
@@ -26,14 +27,14 @@ impl UiRsc for TestRsc {
|
|||||||
struct FixedRect(f32);
|
struct FixedRect(f32);
|
||||||
|
|
||||||
impl Widget for FixedRect {
|
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);
|
let size = Size::from_axis(Axis::Y, Len::abs(self.0), Len::REST);
|
||||||
painter.primitive_within(
|
painter.primitive_within(
|
||||||
RectPrimitive::color(UiColor::WHITE),
|
RectPrimitive::color(UiColor::WHITE),
|
||||||
size.to_uivec2(painter.density())
|
size.to_uivec2(painter.density())
|
||||||
.align(RegionAlign::TOP_LEFT),
|
.align(RegionAlign::TOP_LEFT),
|
||||||
);
|
);
|
||||||
size
|
painter.set_size(size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,12 +44,214 @@ struct ChildOffset {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for 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.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]
|
#[test]
|
||||||
fn a_child_coordinate_offset_moves_only_the_child_subtree() {
|
fn a_child_coordinate_offset_moves_only_the_child_subtree() {
|
||||||
let mut rsc = TestRsc {
|
let mut rsc = TestRsc {
|
||||||
@@ -721,7 +924,7 @@ struct MoveThenPlace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for MoveThenPlace {
|
impl Widget for MoveThenPlace {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
let offer = UiRegion::new(
|
let offer = UiRegion::new(
|
||||||
UiSpan::FULL,
|
UiSpan::FULL,
|
||||||
UiSpan::new(
|
UiSpan::new(
|
||||||
@@ -738,7 +941,7 @@ impl Widget for MoveThenPlace {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
painter.place(&self.inner, place);
|
painter.place(&self.inner, place);
|
||||||
Size::default()
|
painter.set_size(Size::default());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+116
-25
@@ -21,7 +21,7 @@ pub enum CursorSense {
|
|||||||
HoverStart,
|
HoverStart,
|
||||||
Hovering,
|
Hovering,
|
||||||
HoverEnd,
|
HoverEnd,
|
||||||
Scroll,
|
Scroll(Axis),
|
||||||
/// Delivered exactly once, in place of `PressEnd`, to whichever widget
|
/// Delivered exactly once, in place of `PressEnd`, to whichever widget
|
||||||
/// currently holds pointer capture (`UiRenderState::capture_pointer`)
|
/// currently holds pointer capture (`UiRenderState::capture_pointer`)
|
||||||
/// when the button lifts -- see `iris::sense`'s pointer-capture doc
|
/// when the button lifts -- see `iris::sense`'s pointer-capture doc
|
||||||
@@ -47,7 +47,10 @@ pub enum CursorSense {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CursorSenses(Vec<CursorSense>);
|
pub struct CursorSenses {
|
||||||
|
senses: Vec<CursorSense>,
|
||||||
|
drag_axis: Option<Axis>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Event for CursorSenses {
|
impl Event for CursorSenses {
|
||||||
type Data<'a> = CursorData<'a>;
|
type Data<'a> = CursorData<'a>;
|
||||||
@@ -74,7 +77,13 @@ impl Event for CursorSenses {
|
|||||||
if data.sense == CursorSense::Drop || data.sense == CursorSense::Cancel {
|
if data.sense == CursorSense::Drop || data.sense == CursorSense::Cancel {
|
||||||
return self.contains(&data.sense).then(|| data.clone());
|
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();
|
let mut data = data.clone();
|
||||||
data.sense = sense;
|
data.sense = sense;
|
||||||
Some(data)
|
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 {
|
impl CursorSense {
|
||||||
pub fn click() -> Self {
|
pub fn click() -> Self {
|
||||||
Self::PressStart(CursorButton::Left)
|
Self::PressStart(CursorButton::Left)
|
||||||
@@ -109,6 +142,16 @@ impl CursorSense {
|
|||||||
pub fn drag_senses() -> CursorSenses {
|
pub fn drag_senses() -> CursorSenses {
|
||||||
Self::click_or_drag() | Self::unclick() | Self::Drop | Self::Cancel
|
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 {
|
pub fn is_dragging(&self) -> bool {
|
||||||
matches!(self, CursorSense::Pressing(CursorButton::Left))
|
matches!(self, CursorSense::Pressing(CursorButton::Left))
|
||||||
}
|
}
|
||||||
@@ -261,6 +304,11 @@ pub struct CursorData<'a> {
|
|||||||
pub scroll_delta: Vec2,
|
pub scroll_delta: Vec2,
|
||||||
pub hover: ActivationState,
|
pub hover: ActivationState,
|
||||||
pub cursor: CursorState,
|
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
|
/// the first sense that triggered this
|
||||||
pub sense: CursorSense,
|
pub sense: CursorSense,
|
||||||
pub render: &'a UiRenderState,
|
pub render: &'a UiRenderState,
|
||||||
@@ -294,6 +342,8 @@ pub struct CursorData<'a> {
|
|||||||
pub struct PointerInput {
|
pub struct PointerInput {
|
||||||
captured: Option<WidgetId>,
|
captured: Option<WidgetId>,
|
||||||
pressed: Vec<WidgetId>,
|
pressed: Vec<WidgetId>,
|
||||||
|
press_origin: Option<Vec2>,
|
||||||
|
drag_axis: Option<Axis>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PointerInput {
|
impl PointerInput {
|
||||||
@@ -393,6 +443,22 @@ impl SensorUi for UiRenderState {
|
|||||||
holder: std::cell::Cell::new(pointer.captured),
|
holder: std::cell::Cell::new(pointer.captured),
|
||||||
};
|
};
|
||||||
let button_down = cursor.buttons.select(&CursorButton::Left).is_on();
|
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`).
|
// The platform took the gesture away (`CursorState::cancelled`).
|
||||||
// Everybody still tracking this press hears about it -- the
|
// Everybody still tracking this press hears about it -- the
|
||||||
@@ -410,6 +476,8 @@ impl SensorUi for UiRenderState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
requests.release();
|
requests.release();
|
||||||
|
pointer.press_origin = None;
|
||||||
|
pointer.drag_axis = None;
|
||||||
for id in told {
|
for id in told {
|
||||||
deliver_cancel(self, rsc, state, id, &cursor, window_size, &requests);
|
deliver_cancel(self, rsc, state, id, &cursor, window_size, &requests);
|
||||||
}
|
}
|
||||||
@@ -458,6 +526,8 @@ impl SensorUi for UiRenderState {
|
|||||||
scroll_delta: cursor.scroll_delta,
|
scroll_delta: cursor.scroll_delta,
|
||||||
hover: ActivationState::On,
|
hover: ActivationState::On,
|
||||||
cursor: cursor.clone(),
|
cursor: cursor.clone(),
|
||||||
|
drag_axis: pointer.drag_axis,
|
||||||
|
captured: true,
|
||||||
sense,
|
sense,
|
||||||
render: self,
|
render: self,
|
||||||
pointer: &requests,
|
pointer: &requests,
|
||||||
@@ -466,6 +536,8 @@ impl SensorUi for UiRenderState {
|
|||||||
if !button_down {
|
if !button_down {
|
||||||
requests.release();
|
requests.release();
|
||||||
pointer.pressed.clear();
|
pointer.pressed.clear();
|
||||||
|
pointer.press_origin = None;
|
||||||
|
pointer.drag_axis = None;
|
||||||
}
|
}
|
||||||
pointer.captured = requests.holder();
|
pointer.captured = requests.holder();
|
||||||
rsc.events_mut().get_type::<CursorSense>().global = pointer;
|
rsc.events_mut().get_type::<CursorSense>().global = pointer;
|
||||||
@@ -524,20 +596,6 @@ impl SensorUi for UiRenderState {
|
|||||||
// widget that registered a matching non-hover sense
|
// widget that registered a matching non-hover sense
|
||||||
// consumes it -- a button that only registered `click()`
|
// consumes it -- a button that only registered `click()`
|
||||||
// must not block a scroll meant for the list behind it.
|
// 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 cursor = cursor.clone();
|
||||||
|
|
||||||
let data = CursorData {
|
let data = CursorData {
|
||||||
@@ -546,12 +604,22 @@ impl SensorUi for UiRenderState {
|
|||||||
scroll_delta: cursor.scroll_delta,
|
scroll_delta: cursor.scroll_delta,
|
||||||
hover: sensor.hover,
|
hover: sensor.hover,
|
||||||
cursor,
|
cursor,
|
||||||
|
drag_axis: pointer.drag_axis,
|
||||||
|
captured: false,
|
||||||
// this does not have any meaning;
|
// this does not have any meaning;
|
||||||
// might wanna set up Event to have a prepare stage
|
// might wanna set up Event to have a prepare stage
|
||||||
sense: CursorSense::Hovering,
|
sense: CursorSense::Hovering,
|
||||||
render: self,
|
render: self,
|
||||||
pointer: &requests,
|
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);
|
rsc.run_event::<CursorSense>(*id, data, state);
|
||||||
// Anything handed a frame while the button is down may
|
// Anything handed a frame while the button is down may
|
||||||
// have opened a gesture on it, and is owed a `Cancel` if
|
// have opened a gesture on it, and is owed a `Cancel` if
|
||||||
@@ -563,7 +631,7 @@ impl SensorUi for UiRenderState {
|
|||||||
pointer.pressed.push(*id);
|
pointer.pressed.push(*id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if sensed {
|
if sensed || requests.holder().is_some() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -597,6 +665,10 @@ impl SensorUi for UiRenderState {
|
|||||||
// A cancel handler may itself have captured (a widget deciding
|
// A cancel handler may itself have captured (a widget deciding
|
||||||
// the gesture is now its own); `requests` is still the truth.
|
// the gesture is now its own); `requests` is still the truth.
|
||||||
pointer.captured = requests.holder();
|
pointer.captured = requests.holder();
|
||||||
|
if !button_down {
|
||||||
|
pointer.press_origin = None;
|
||||||
|
pointer.drag_axis = None;
|
||||||
|
}
|
||||||
rsc.events_mut().get_type::<CursorSense>().global = pointer;
|
rsc.events_mut().get_type::<CursorSense>().global = pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -625,6 +697,8 @@ fn deliver_cancel<Rsc: HasEvents>(
|
|||||||
scroll_delta: cursor.scroll_delta,
|
scroll_delta: cursor.scroll_delta,
|
||||||
hover: ActivationState::On,
|
hover: ActivationState::On,
|
||||||
cursor: cursor.clone(),
|
cursor: cursor.clone(),
|
||||||
|
drag_axis: None,
|
||||||
|
captured: false,
|
||||||
sense: CursorSense::Cancel,
|
sense: CursorSense::Cancel,
|
||||||
render,
|
render,
|
||||||
pointer,
|
pointer,
|
||||||
@@ -636,6 +710,8 @@ pub fn should_run(
|
|||||||
senses: &CursorSenses,
|
senses: &CursorSenses,
|
||||||
cursor: &CursorState,
|
cursor: &CursorState,
|
||||||
hover: ActivationState,
|
hover: ActivationState,
|
||||||
|
drag_axis: Option<Axis>,
|
||||||
|
captured: bool,
|
||||||
) -> Option<CursorSense> {
|
) -> Option<CursorSense> {
|
||||||
// Every sense below that is about the *pointer* rather than about
|
// Every sense below that is about the *pointer* rather than about
|
||||||
// hovering needs the pointer to actually be on this widget, and
|
// 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::HoverStart => hover.is_start(),
|
||||||
CursorSense::Hovering => hover.is_on(),
|
CursorSense::Hovering => hover.is_on(),
|
||||||
CursorSense::HoverEnd => hover.is_end(),
|
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
|
// Never derived here -- `Drop` only ever fires through
|
||||||
// `CursorSenses::should_run`'s own special case, ahead of this
|
// `CursorSenses::should_run`'s own special case, ahead of this
|
||||||
// loop, for the one widget `run_sensors`' capture branch is
|
// 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
|
// note above; both are set by `run_sensors` alone, for the one
|
||||||
// widget it is delivering to this frame.
|
// widget it is delivering to this frame.
|
||||||
CursorSense::Drop | CursorSense::Cancel => false,
|
CursorSense::Drop | CursorSense::Cancel => false,
|
||||||
} {
|
} && (captured
|
||||||
|
|| !matches!(sense, CursorSense::Pressing(_))
|
||||||
|
|| senses.drag_axis.is_none()
|
||||||
|
|| senses.drag_axis == drag_axis)
|
||||||
|
{
|
||||||
return Some(*sense);
|
return Some(*sense);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -744,19 +829,22 @@ impl Deref for CursorSenses {
|
|||||||
type Target = Vec<CursorSense>;
|
type Target = Vec<CursorSense>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.0
|
&self.senses
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DerefMut for CursorSenses {
|
impl DerefMut for CursorSenses {
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
&mut self.0
|
&mut self.senses
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<CursorSense> for CursorSenses {
|
impl From<CursorSense> for CursorSenses {
|
||||||
fn from(val: CursorSense) -> Self {
|
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;
|
type Output = CursorSenses;
|
||||||
|
|
||||||
fn bitor(self, rhs: Self) -> Self::Output {
|
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;
|
type Output = Self;
|
||||||
|
|
||||||
fn bitor(mut self, rhs: CursorSense) -> Self::Output {
|
fn bitor(mut self, rhs: CursorSense) -> Self::Output {
|
||||||
self.0.push(rhs);
|
self.senses.push(rhs);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-10
@@ -73,9 +73,13 @@ fn a_button_over_a_list_scrolls_the_list_and_still_clicks() {
|
|||||||
let clicked = Rc::new(Cell::new(false));
|
let clicked = Rc::new(Cell::new(false));
|
||||||
{
|
{
|
||||||
let scrolled = scrolled.clone();
|
let scrolled = scrolled.clone();
|
||||||
rsc.register_event(list_weak, CursorSense::Scroll, move |_ctx, _rsc| {
|
rsc.register_event(
|
||||||
scrolled.set(true);
|
list_weak,
|
||||||
});
|
CursorSense::Scroll(Axis::Y),
|
||||||
|
move |_ctx, _rsc| {
|
||||||
|
scrolled.set(true);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let clicked = clicked.clone();
|
let clicked = clicked.clone();
|
||||||
@@ -461,10 +465,10 @@ fn taking_the_pointer_cancels_everyone_else_tracking_the_press() {
|
|||||||
events: EventManager::default(),
|
events: EventManager::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// The bystander *contains* the capturer, which is the real shape: a
|
// The bystander contains the capturer on a lower visual layer: the
|
||||||
// transcript's `LazySpan` and one row's own text both track the same
|
// shape of a vertical transcript scroller with a higher horizontal
|
||||||
// press, and a `Stack`'s siblings would be on separate layers where
|
// scroller inside one row. Both observe the undecided press, then only
|
||||||
// only the topmost is dispatched to at all.
|
// the recognizer matching its direction may consume it.
|
||||||
let capturer = rsc.ui.widgets.add_strong(Rect::new(UiColor::WHITE));
|
let capturer = rsc.ui.widgets.add_strong(Rect::new(UiColor::WHITE));
|
||||||
let capturer_weak = capturer.weak();
|
let capturer_weak = capturer.weak();
|
||||||
let bystander = rsc.ui.widgets.add_strong(Stack {
|
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();
|
let capturer_saw = capturer_saw.clone();
|
||||||
rsc.register_event(
|
rsc.register_event(
|
||||||
capturer_weak,
|
capturer_weak,
|
||||||
CursorSense::drag_senses(),
|
CursorSense::drag(Axis::X),
|
||||||
move |ctx, _rsc| {
|
move |ctx, _rsc| {
|
||||||
capturer_saw.set(capturer_saw.get() + 1);
|
capturer_saw.set(capturer_saw.get() + 1);
|
||||||
if matches!(ctx.data.sense, CursorSense::Pressing(_)) {
|
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());
|
let (cancelled, ended) = (cancelled.clone(), ended.clone());
|
||||||
rsc.register_event(
|
rsc.register_event(
|
||||||
bystander_weak,
|
bystander_weak,
|
||||||
CursorSense::drag_senses(),
|
CursorSense::drag(Axis::Y),
|
||||||
move |ctx, _rsc| match ctx.data.sense {
|
move |ctx, _rsc| match ctx.data.sense {
|
||||||
CursorSense::Cancel => cancelled.set(cancelled.get() + 1),
|
CursorSense::Cancel => cancelled.set(cancelled.get() + 1),
|
||||||
CursorSense::PressEnd(_) | CursorSense::Drop => ended.set(ended.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);
|
render.update(&root, &mut rsc);
|
||||||
assert_eq!(cancelled.get(), 0, "nothing has captured yet");
|
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;
|
moved.buttons.left = ActivationState::On;
|
||||||
render.run_sensors(&mut rsc, &mut state, moved, win);
|
render.run_sensors(&mut rsc, &mut state, moved, win);
|
||||||
render.update(&root, &mut rsc);
|
render.update(&root, &mut rsc);
|
||||||
@@ -590,6 +594,9 @@ fn a_drag_pans_whichever_nested_scroll_area_owns_its_axis() {
|
|||||||
record.set(Some(id));
|
record.set(Some(id));
|
||||||
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)
|
.scrollable(Axis::Y, Pin::Start)
|
||||||
.add_strong(&mut rsc);
|
.add_strong(&mut rsc);
|
||||||
let inner = seen.get().unwrap();
|
let inner = seen.get().unwrap();
|
||||||
|
|||||||
+2
-2
@@ -6,10 +6,10 @@ pub struct Image {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for 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();
|
let size = self.handle.size();
|
||||||
painter.texture_within(&self.handle, size.align(Align::TOP_LEFT));
|
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> {
|
fn size_hint(&self, axis: Axis) -> Option<Len> {
|
||||||
|
|||||||
+3
-3
@@ -6,7 +6,7 @@ pub struct Masked {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Masked {
|
impl Widget for Masked {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
match &self.shape {
|
match &self.shape {
|
||||||
Some(shape) => {
|
Some(shape) => {
|
||||||
painter.child_layer();
|
painter.child_layer();
|
||||||
@@ -16,9 +16,9 @@ impl Widget for Masked {
|
|||||||
}
|
}
|
||||||
None => painter.set_mask(painter.region()),
|
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);
|
painter.place_used(&self.inner, used, UiRegion::FULL);
|
||||||
used
|
painter.set_size(used);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn requires_exact_region(&self) -> bool {
|
fn requires_exact_region(&self) -> bool {
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ pub struct Aligned {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Aligned {
|
impl Widget for Aligned {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
let used = painter.widget(&self.inner);
|
let used = painter.widget(&self.inner).size();
|
||||||
let density = painter.density();
|
let density = painter.density();
|
||||||
let (x, y) = self.align.tuple();
|
let (x, y) = self.align.tuple();
|
||||||
let region = UiRegion::new(
|
let region = UiRegion::new(
|
||||||
@@ -19,6 +19,6 @@ impl Widget for Aligned {
|
|||||||
.align(y.unwrap_or(AxisAlign::Neg)),
|
.align(y.unwrap_or(AxisAlign::Neg)),
|
||||||
);
|
);
|
||||||
painter.place(&self.inner, region);
|
painter.place(&self.inner, region);
|
||||||
used
|
painter.set_size(used);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,12 +6,12 @@ pub struct LayerOffset {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for 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 {
|
for _ in 0..self.offset {
|
||||||
painter.next_layer();
|
painter.next_layer();
|
||||||
}
|
}
|
||||||
let used = painter.widget(&self.inner);
|
let used = painter.widget(&self.inner).size();
|
||||||
painter.place_used(&self.inner, used, UiRegion::FULL);
|
painter.place_used(&self.inner, used, UiRegion::FULL);
|
||||||
used
|
painter.set_size(used);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -939,7 +939,7 @@ impl LazySpan {
|
|||||||
(Some(h), None) => {
|
(Some(h), None) => {
|
||||||
let (lead, trail) = placement.edges(h);
|
let (lead, trail) = placement.edges(h);
|
||||||
let region = self.row_region(lead, trail);
|
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);
|
let height = resolve(used);
|
||||||
if height != h {
|
if height != h {
|
||||||
let (new_lead, new_trail) = placement.edges(height);
|
let (new_lead, new_trail) = placement.edges(height);
|
||||||
@@ -957,7 +957,7 @@ impl LazySpan {
|
|||||||
Placement::Trailing(_) => 0.0,
|
Placement::Trailing(_) => 0.0,
|
||||||
};
|
};
|
||||||
let first = self.row_region(measure_from, measure_from + GENEROUS_PADDING);
|
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);
|
let (lead, trail) = placement.edges(height);
|
||||||
if is_anchor {
|
if is_anchor {
|
||||||
self.stabilize_lead(painter, measure_from, lead);
|
self.stabilize_lead(painter, measure_from, lead);
|
||||||
@@ -1072,7 +1072,7 @@ impl Widget for LazySpan {
|
|||||||
self.tick_fling(now)
|
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 axis = self.dir.axis;
|
||||||
let output_len = painter.output_size().axis(axis);
|
let output_len = painter.output_size().axis(axis);
|
||||||
self.viewport_len = painter.region().axis(axis).len().to_abs(output_len);
|
self.viewport_len = painter.region().axis(axis).len().to_abs(output_len);
|
||||||
@@ -1081,7 +1081,8 @@ impl Widget for LazySpan {
|
|||||||
self.repair_anchor();
|
self.repair_anchor();
|
||||||
if self.anchor.is_none() {
|
if self.anchor.is_none() {
|
||||||
self.extents.clear();
|
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,
|
// 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.rehome_anchor();
|
||||||
self.update_snap_end();
|
self.update_snap_end();
|
||||||
self.ctl.set_travel(self.travel());
|
self.ctl.set_travel(self.travel());
|
||||||
Size::REST
|
painter.set_size(Size::REST);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn size_hint(&self, _axis: Axis) -> Option<Len> {
|
fn size_hint(&self, _axis: Axis) -> Option<Len> {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ impl MaxSize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for 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 output = painter.output_size();
|
||||||
let density = painter.density();
|
let density = painter.density();
|
||||||
let offered = painter.px_size();
|
let offered = painter.px_size();
|
||||||
@@ -43,12 +43,12 @@ impl Widget for MaxSize {
|
|||||||
x: Self::clamp_region(offered.x, self.x, output.x, density),
|
x: Self::clamp_region(offered.x, self.x, output.x, density),
|
||||||
y: Self::clamp_region(offered.y, self.y, output.y, 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 {
|
let size = Size {
|
||||||
x: Self::clamp(used.x, self.x, output.x, density),
|
x: Self::clamp(used.x, self.x, output.x, density),
|
||||||
y: Self::clamp(used.y, self.y, output.y, density),
|
y: Self::clamp(used.y, self.y, output.y, density),
|
||||||
};
|
};
|
||||||
painter.place_used(&self.inner, size, UiRegion::FULL);
|
painter.place_used(&self.inner, size, UiRegion::FULL);
|
||||||
size
|
painter.set_size(size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,10 +6,10 @@ pub struct Offset {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for 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 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);
|
painter.place_used(&self.inner, used, region);
|
||||||
used
|
painter.set_size(used);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,11 +8,11 @@ pub struct Pad {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for 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 density = painter.density();
|
||||||
let offered = painter.px_size();
|
let offered = painter.px_size();
|
||||||
let region = self.padding.region(density);
|
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);
|
painter.place_used(&self.inner, used, region);
|
||||||
let width =
|
let width =
|
||||||
self.padding.left.apply_rest(density).abs + self.padding.right.apply_rest(density).abs;
|
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 {
|
if needed.x <= offered.x + 0.01 && needed.y <= offered.y + 0.01 {
|
||||||
self.exact_region = false;
|
self.exact_region = false;
|
||||||
}
|
}
|
||||||
size
|
painter.set_size(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn requires_exact_region(&self) -> bool {
|
fn requires_exact_region(&self) -> bool {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ impl Widget for ScrollArea {
|
|||||||
self.tick_fling(now)
|
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 axis = self.ctl.axis();
|
||||||
let container_len = painter.px_size().axis(axis);
|
let container_len = painter.px_size().axis(axis);
|
||||||
self.container_len = container_len;
|
self.container_len = container_len;
|
||||||
@@ -41,7 +41,9 @@ impl Widget for ScrollArea {
|
|||||||
self.ctl.set_amt(travelled);
|
self.ctl.set_amt(travelled);
|
||||||
|
|
||||||
let hint = self.content_len.unwrap_or(container_len);
|
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
|
let measured = used
|
||||||
.axis(axis)
|
.axis(axis)
|
||||||
@@ -62,7 +64,10 @@ impl Widget for ScrollArea {
|
|||||||
fwd: range - amt,
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -502,11 +502,11 @@ where
|
|||||||
W: Widget + Scrollable,
|
W: Widget + Scrollable,
|
||||||
WL: WidgetLike<Rsc, Tag, Widget = W>,
|
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;
|
let delta = ctx.data.scroll_delta.axis(axis) * 50.0;
|
||||||
ctx.widget(rsc).scroll(delta);
|
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 id = ctx.widget.id();
|
||||||
let (sense, pos) = (ctx.data.sense, ctx.data.cursor.pos);
|
let (sense, pos) = (ctx.data.sense, ctx.data.cursor.pos);
|
||||||
let flung = ctx
|
let flung = ctx
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ pub struct Sized {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for 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 density = painter.density();
|
||||||
let mut region = UiRegion::FULL;
|
let mut region = UiRegion::FULL;
|
||||||
if let Some(x) = self.x {
|
if let Some(x) = self.x {
|
||||||
@@ -16,13 +16,13 @@ impl Widget for Sized {
|
|||||||
if let Some(y) = self.y {
|
if let Some(y) = self.y {
|
||||||
region.y = y.apply_rest(density).align(AxisAlign::Neg);
|
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 {
|
let size = Size {
|
||||||
x: self.x.map(|x| x.fold_dp(density)).unwrap_or(used.x),
|
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),
|
y: self.y.map(|y| y.fold_dp(density)).unwrap_or(used.y),
|
||||||
};
|
};
|
||||||
painter.place_used(&self.inner, size, UiRegion::FULL);
|
painter.place_used(&self.inner, size, UiRegion::FULL);
|
||||||
size
|
painter.set_size(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn size_hint(&self, axis: Axis) -> Option<Len> {
|
fn size_hint(&self, axis: Axis) -> Option<Len> {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ pub struct Span {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for 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 axis = self.dir.axis;
|
||||||
let gap = self.gap.apply_rest(painter.density()).abs;
|
let gap = self.gap.apply_rest(painter.density()).abs;
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ impl Widget for Span {
|
|||||||
slot.flip();
|
slot.flip();
|
||||||
}
|
}
|
||||||
let region = UiRegion::from_axis(axis, slot, UiSpan::FULL);
|
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);
|
lens[i] = Some(len);
|
||||||
drawn[i] = true;
|
drawn[i] = true;
|
||||||
len
|
len
|
||||||
@@ -65,9 +65,9 @@ impl Widget for Span {
|
|||||||
child_region.flip(axis);
|
child_region.flip(axis);
|
||||||
}
|
}
|
||||||
let used = if drawn[i] {
|
let used = if drawn[i] {
|
||||||
painter.place(child, child_region)
|
painter.place(child, child_region).size()
|
||||||
} else {
|
} else {
|
||||||
painter.widget_within(child, child_region)
|
painter.widget_within(child, child_region).size()
|
||||||
};
|
};
|
||||||
placed.push(child_region);
|
placed.push(child_region);
|
||||||
start.abs += gap;
|
start.abs += gap;
|
||||||
@@ -97,7 +97,7 @@ impl Widget for Span {
|
|||||||
Len::default()
|
Len::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
Size::from_axis(axis, along, ortho_len)
|
painter.set_size(Size::from_axis(axis, along, ortho_len));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ pub struct Stack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for 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 density = painter.density();
|
||||||
let known = match self.size {
|
let known = match self.size {
|
||||||
StackSize::Default => Some(Size::REST),
|
StackSize::Default => Some(Size::REST),
|
||||||
@@ -25,15 +25,15 @@ impl Widget for Stack {
|
|||||||
if let Some(child) = iter.next() {
|
if let Some(child) = iter.next() {
|
||||||
painter.child_layer();
|
painter.child_layer();
|
||||||
used.push(match region {
|
used.push(match region {
|
||||||
Some(region) => painter.widget_within(child, region),
|
Some(region) => painter.widget_within(child, region).size(),
|
||||||
None => painter.widget(child),
|
None => painter.widget(child).size(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
for child in iter {
|
for child in iter {
|
||||||
painter.next_layer();
|
painter.next_layer();
|
||||||
used.push(match region {
|
used.push(match region {
|
||||||
Some(region) => painter.widget_within(child, region),
|
Some(region) => painter.widget_within(child, region).size(),
|
||||||
None => painter.widget(child),
|
None => painter.widget(child).size(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let size = match self.size {
|
let size = match self.size {
|
||||||
@@ -50,7 +50,7 @@ impl Widget for Stack {
|
|||||||
painter.place(child, child_region);
|
painter.place(child, child_region);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
size
|
painter.set_size(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn size_hint(&self, _axis: Axis) -> Option<Len> {
|
fn size_hint(&self, _axis: Axis) -> Option<Len> {
|
||||||
|
|||||||
+5
-4
@@ -6,14 +6,15 @@ pub struct WidgetPtr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for WidgetPtr {
|
impl Widget for WidgetPtr {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
if let Some(id) = &self.inner {
|
let size = if let Some(id) = &self.inner {
|
||||||
let used = painter.widget(id);
|
let used = painter.widget(id).size();
|
||||||
painter.place_used(id, used, UiRegion::FULL);
|
painter.place_used(id, used, UiRegion::FULL);
|
||||||
used
|
used
|
||||||
} else {
|
} else {
|
||||||
Size::ZERO
|
Size::ZERO
|
||||||
}
|
};
|
||||||
|
painter.set_size(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_size_independent(&self) -> bool {
|
fn is_size_independent(&self) -> bool {
|
||||||
|
|||||||
+2
-2
@@ -28,14 +28,14 @@ impl Rect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Rect {
|
impl Widget for Rect {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
painter.primitive(RectPrimitive {
|
painter.primitive(RectPrimitive {
|
||||||
color: self.color,
|
color: self.color,
|
||||||
radius: self.radius.fold_dp(painter.density()).abs,
|
radius: self.radius.fold_dp(painter.density()).abs,
|
||||||
thickness: self.thickness,
|
thickness: self.thickness,
|
||||||
inner_radius: self.inner_radius,
|
inner_radius: self.inner_radius,
|
||||||
});
|
});
|
||||||
Size::REST
|
painter.set_size(Size::REST);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn size_hint(&self, _axis: Axis) -> Option<Len> {
|
fn size_hint(&self, _axis: Axis) -> Option<Len> {
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ impl TextEdit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for TextEdit {
|
impl Widget for TextEdit {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
let base = painter.layer;
|
let base = painter.layer;
|
||||||
painter.child_layer();
|
painter.child_layer();
|
||||||
let used = self.view.draw(painter);
|
let used = self.view.draw(painter);
|
||||||
@@ -100,7 +100,8 @@ impl Widget for TextEdit {
|
|||||||
let region = self.region();
|
let region = self.region();
|
||||||
|
|
||||||
let Some(selection) = self.selection else {
|
let Some(selection) = self.selection else {
|
||||||
return used;
|
painter.set_size(used);
|
||||||
|
return;
|
||||||
};
|
};
|
||||||
let layout = self.view.buf.layout();
|
let layout = self.view.buf.layout();
|
||||||
|
|
||||||
@@ -122,7 +123,7 @@ impl Widget for TextEdit {
|
|||||||
RectPrimitive::color(Color::WHITE),
|
RectPrimitive::color(Color::WHITE),
|
||||||
size.align(Align::TOP_LEFT).offset(top_left).within(®ion),
|
size.align(Align::TOP_LEFT).offset(top_left).within(®ion),
|
||||||
);
|
);
|
||||||
used
|
painter.set_size(used);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn requires_exact_region(&self) -> bool {
|
fn requires_exact_region(&self) -> bool {
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ impl TextView {
|
|||||||
if self.is_blank()
|
if self.is_blank()
|
||||||
&& let Some(hint) = &self.hint
|
&& let Some(hint) = &self.hint
|
||||||
{
|
{
|
||||||
return painter.widget(hint);
|
return painter.widget(hint).size();
|
||||||
}
|
}
|
||||||
let region = tex.size.align(self.align);
|
let region = tex.size.align(self.align);
|
||||||
let within = region.within(&painter.region());
|
let within = region.within(&painter.region());
|
||||||
@@ -141,9 +141,10 @@ impl Text {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Text {
|
impl Widget for Text {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
self.update_buf();
|
self.update_buf();
|
||||||
self.view.draw(painter)
|
let size = self.view.draw(painter);
|
||||||
|
painter.set_size(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn requires_exact_region(&self) -> bool {
|
fn requires_exact_region(&self) -> bool {
|
||||||
|
|||||||
Reference in new issue
Block a user