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
+209
-6
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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> {
|
||||
|
||||
@@ -92,7 +92,7 @@ impl TextEdit {
|
||||
}
|
||||
|
||||
impl Widget for TextEdit {
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||
fn draw(&mut self, painter: &mut Painter) {
|
||||
let base = painter.layer;
|
||||
painter.child_layer();
|
||||
let used = self.view.draw(painter);
|
||||
@@ -100,7 +100,8 @@ impl Widget for TextEdit {
|
||||
let region = self.region();
|
||||
|
||||
let Some(selection) = self.selection else {
|
||||
return used;
|
||||
painter.set_size(used);
|
||||
return;
|
||||
};
|
||||
let layout = self.view.buf.layout();
|
||||
|
||||
@@ -122,7 +123,7 @@ impl Widget for TextEdit {
|
||||
RectPrimitive::color(Color::WHITE),
|
||||
size.align(Align::TOP_LEFT).offset(top_left).within(®ion),
|
||||
);
|
||||
used
|
||||
painter.set_size(used);
|
||||
}
|
||||
|
||||
fn requires_exact_region(&self) -> bool {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user