Swap two hover buffers, and drive the tests from the harness
The set of hovered widgets is two vectors that trade places, so an input allocates nothing once they have grown, instead of building a fresh one each time. `run_sensors` no longer takes a `window_size`: `UiRenderState` already holds the output size, and passing it back in was one more thing that could disagree. `tests/pointer_routing.rs` drops its own `Rsc`, event manager and layer scaffolding for `iris::harness`, which is what it was standing in for. Presses now arrive as a move and then a press, and a scroll after the hover that precedes it, because that is what the harness delivers and what a window does.
This commit is contained in:
1 parent
e865467a3f
commit
7dc7614ae6
4 files changed
+114
-250
No files matched your search
+1
-2
@@ -235,8 +235,7 @@ impl<State: DefaultAppState> AppState for DefaultApp<State> {
|
||||
ui_state.focus = None;
|
||||
}
|
||||
if input_changed {
|
||||
let window_size = ui_state.window_size();
|
||||
render.run_sensors(rsc, state, cursor_state, window_size);
|
||||
render.run_sensors(rsc, state, cursor_state);
|
||||
}
|
||||
let ui_state = state.default_state_mut();
|
||||
if old != ui_state.focus
|
||||
|
||||
+22
-14
@@ -27,10 +27,7 @@ pub struct CursorSenses(Vec<CursorSense>);
|
||||
|
||||
impl Event for CursorSenses {
|
||||
type Data<'a> = CursorData<'a>;
|
||||
/// Who the cursor was inside on the last input, which is what says whose
|
||||
/// hover has ended -- including a widget a higher layer has since covered,
|
||||
/// which this walk never reaches.
|
||||
type Global = Vec<WidgetId>;
|
||||
type Global = Hovered;
|
||||
fn should_run<'a>(&self, data: &Self::Data<'a>) -> Option<Self::Data<'a>> {
|
||||
if let Some(sense) = should_run(self, &data.cursor, data.hover) {
|
||||
let mut data = data.clone();
|
||||
@@ -48,6 +45,18 @@ impl Event for CursorSenses {
|
||||
}
|
||||
}
|
||||
|
||||
/// Who the cursor was inside, before and after an input. The difference is
|
||||
/// whose hover has ended -- including a widget a higher layer has covered,
|
||||
/// which the walk stops before reaching.
|
||||
///
|
||||
/// Two buffers that swap rather than one rebuilt, so an input allocates
|
||||
/// nothing once they have grown.
|
||||
#[derive(Default)]
|
||||
pub struct Hovered {
|
||||
was: Vec<WidgetId>,
|
||||
now: Vec<WidgetId>,
|
||||
}
|
||||
|
||||
impl CursorSense {
|
||||
pub fn click() -> Self {
|
||||
Self::PressStart(CursorButton::Left)
|
||||
@@ -163,7 +172,6 @@ pub trait SensorUi {
|
||||
rsc: &mut Rsc,
|
||||
state: &mut Rsc::State,
|
||||
cursor: CursorState,
|
||||
window_size: Vec2,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -173,17 +181,16 @@ impl SensorUi for UiRenderState {
|
||||
rsc: &mut Rsc,
|
||||
state: &mut Rsc::State,
|
||||
cursor: CursorState,
|
||||
window_size: Vec2,
|
||||
) {
|
||||
// in order to remove this take, need to store active list in UiRenderState somehow
|
||||
// this would probably be done through a generic parameter that adds yet another rsc /
|
||||
// state like thing, but local to render state, and is passed to UiRsc events so you can
|
||||
// update it there?
|
||||
let active = std::mem::take(&mut rsc.events_mut().get_type::<CursorSense>().active);
|
||||
let was = std::mem::take(&mut rsc.events_mut().get_type::<CursorSense>().global);
|
||||
let mut hovered = std::mem::take(&mut rsc.events_mut().get_type::<CursorSense>().global);
|
||||
hovered.now.clear();
|
||||
let position_only = cursor.position_only();
|
||||
let mut now: Vec<WidgetId> = Vec::new();
|
||||
let region_of = |id| Some(self.active.get(&id)?.region.to_px(window_size));
|
||||
let region_of = |id| self.window_region(&id);
|
||||
|
||||
for layer in self.layers.indices().rev() {
|
||||
let mut consumed = false;
|
||||
@@ -194,8 +201,8 @@ impl SensorUi for UiRenderState {
|
||||
if !cursor.exists || !region.contains(cursor.pos) {
|
||||
continue;
|
||||
}
|
||||
now.push(id);
|
||||
let hover = match was.contains(&id) {
|
||||
hovered.now.push(id);
|
||||
let hover = match hovered.was.contains(&id) {
|
||||
true => ActivationState::On,
|
||||
false => ActivationState::Start,
|
||||
};
|
||||
@@ -216,17 +223,18 @@ impl SensorUi for UiRenderState {
|
||||
// Whatever the cursor was inside and is not now, whether it left or a
|
||||
// layer above took the input before the walk reached it. A widget that
|
||||
// stopped being drawn has no region to report and is simply dropped.
|
||||
for id in was {
|
||||
if !now.contains(&id)
|
||||
for &id in &hovered.was {
|
||||
if !hovered.now.contains(&id)
|
||||
&& let Some(region) = region_of(id)
|
||||
{
|
||||
deliver(self, rsc, state, id, ActivationState::End, &cursor, region);
|
||||
}
|
||||
}
|
||||
std::mem::swap(&mut hovered.was, &mut hovered.now);
|
||||
|
||||
let senses = rsc.events_mut().get_type::<CursorSense>();
|
||||
senses.active = active;
|
||||
senses.global = now;
|
||||
senses.global = hovered;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-2
@@ -173,9 +173,8 @@ impl Harness {
|
||||
/// window delivers input against too.
|
||||
fn sense(&mut self) {
|
||||
let cursor = self.cursor.clone();
|
||||
let size = self.render.output_size();
|
||||
self.render
|
||||
.run_sensors(&mut self.rsc, &mut self.state, cursor, size);
|
||||
.run_sensors(&mut self.rsc, &mut self.state, cursor);
|
||||
self.cursor.end_frame();
|
||||
}
|
||||
}
|
||||
+90
-232
@@ -1,45 +1,10 @@
|
||||
//! Input across layers: what stops at a layer, what passes through it, and
|
||||
//! where hovering stops. These drive `run_sensors` directly, which needs no
|
||||
//! GPU and no window.
|
||||
//! where hovering stops.
|
||||
|
||||
use iris::prelude::*;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
struct SenseRsc {
|
||||
ui: UiData,
|
||||
events: EventManager<SenseRsc>,
|
||||
}
|
||||
|
||||
impl UiRsc for SenseRsc {
|
||||
fn ui(&self) -> &UiData {
|
||||
&self.ui
|
||||
}
|
||||
fn ui_mut(&mut self) -> &mut UiData {
|
||||
&mut self.ui
|
||||
}
|
||||
fn on_draw(&mut self, active: &ActiveData) {
|
||||
self.events.draw(active);
|
||||
}
|
||||
fn on_undraw(&mut self, active: &ActiveData) {
|
||||
self.events.undraw(active);
|
||||
}
|
||||
fn on_remove(&mut self, id: WidgetId) {
|
||||
self.events.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
impl HasState for SenseRsc {
|
||||
type State = ();
|
||||
}
|
||||
|
||||
impl HasEvents for SenseRsc {
|
||||
fn events(&self) -> &EventManager<Self> {
|
||||
&self.events
|
||||
}
|
||||
fn events_mut(&mut self) -> &mut EventManager<Self> {
|
||||
&mut self.events
|
||||
}
|
||||
}
|
||||
use iris::harness::Harness;
|
||||
use iris::prelude::*;
|
||||
|
||||
const WINDOW: f32 = 100.0;
|
||||
|
||||
@@ -53,88 +18,36 @@ impl Fired {
|
||||
}
|
||||
}
|
||||
|
||||
struct Ui {
|
||||
rsc: SenseRsc,
|
||||
render: UiRenderState,
|
||||
state: (),
|
||||
}
|
||||
|
||||
impl Ui {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
rsc: SenseRsc {
|
||||
ui: UiData::default(),
|
||||
events: EventManager::default(),
|
||||
},
|
||||
render: UiRenderState::new(),
|
||||
state: (),
|
||||
}
|
||||
}
|
||||
|
||||
fn listen<W: Widget + ?core::marker::Sized + 'static>(
|
||||
&mut self,
|
||||
widget: &StrongWidget<W>,
|
||||
senses: impl Into<CursorSenses>,
|
||||
) -> Fired {
|
||||
/// A widget filling whatever it is given, recording the senses it is sent.
|
||||
fn listener(h: &mut Harness, senses: impl Into<CursorSenses>) -> (WeakWidget<Rect>, Fired) {
|
||||
let fired = Fired::default();
|
||||
let sink = fired.clone();
|
||||
self.rsc
|
||||
.register_event(widget.weak(), senses.into(), move |ctx, _rsc| {
|
||||
sink.0.borrow_mut().push(ctx.data.sense)
|
||||
});
|
||||
fired
|
||||
}
|
||||
|
||||
/// Stacks the widgets bottom first, each on its own layer, and lays them
|
||||
/// out in a square window.
|
||||
fn stack(&mut self, children: Vec<StrongWidget>) {
|
||||
let root = self
|
||||
.rsc
|
||||
.ui
|
||||
.widgets
|
||||
.add_strong(Stack {
|
||||
children,
|
||||
size: StackSize::default(),
|
||||
let record = fired.clone();
|
||||
let id = rect(Color::WHITE)
|
||||
.on(senses.into(), move |ctx, _| {
|
||||
record.0.borrow_mut().push(ctx.data.sense)
|
||||
})
|
||||
.any();
|
||||
self.render.resize((WINDOW, WINDOW));
|
||||
self.render.update(&root, &mut self.rsc);
|
||||
.add(&mut h.rsc);
|
||||
(id, fired)
|
||||
}
|
||||
|
||||
fn cursor(&mut self, at: (f32, f32)) -> CursorState {
|
||||
CursorState {
|
||||
pos: at.into(),
|
||||
exists: true,
|
||||
buttons: Default::default(),
|
||||
scroll_delta: Vec2::ZERO,
|
||||
}
|
||||
/// A widget with no senses of its own, to leave a gap beside one that has.
|
||||
fn blank(h: &mut Harness) -> WeakWidget<Rect> {
|
||||
rect(Color::WHITE).add(&mut h.rsc)
|
||||
}
|
||||
|
||||
fn run(&mut self, cursor: CursorState) {
|
||||
self.render.run_sensors(
|
||||
&mut self.rsc,
|
||||
&mut self.state,
|
||||
cursor,
|
||||
(WINDOW, WINDOW).into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn full(ui: &mut Ui) -> StrongWidget<Rect> {
|
||||
rect(UiColor::WHITE).add_strong(&mut ui.rsc)
|
||||
fn harness() -> Harness {
|
||||
Harness::new((WINDOW, WINDOW))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_stops_at_the_topmost_widget() {
|
||||
let ui = &mut Ui::new();
|
||||
let (bottom, middle, top) = (full(ui), full(ui), full(ui));
|
||||
let bottom_hover = ui.listen(&bottom, CursorSense::HoverStart);
|
||||
let middle_hover = ui.listen(&middle, CursorSense::HoverStart);
|
||||
let top_hover = ui.listen(&top, CursorSense::HoverStart);
|
||||
ui.stack(vec![bottom.any(), middle.any(), top.any()]);
|
||||
let mut h = harness();
|
||||
let (bottom, bottom_hover) = listener(&mut h, CursorSense::HoverStart);
|
||||
let (middle, middle_hover) = listener(&mut h, CursorSense::HoverStart);
|
||||
let (top, top_hover) = listener(&mut h, CursorSense::HoverStart);
|
||||
h.set_root((bottom, middle, top).stack());
|
||||
|
||||
let cursor = ui.cursor((50.0, 50.0));
|
||||
ui.run(cursor);
|
||||
h.move_to((50, 50));
|
||||
|
||||
assert_eq!(top_hover.take(), [CursorSense::HoverStart]);
|
||||
assert_eq!(
|
||||
@@ -147,16 +60,14 @@ fn hover_stops_at_the_topmost_widget() {
|
||||
|
||||
#[test]
|
||||
fn a_scroll_passes_through_every_widget_that_does_not_want_it() {
|
||||
let ui = &mut Ui::new();
|
||||
let (list, button, overlay) = (full(ui), full(ui), full(ui));
|
||||
let scrolled = ui.listen(&list, CursorSense::Scroll);
|
||||
let clicked = ui.listen(&button, CursorSense::click());
|
||||
let overlay_clicked = ui.listen(&overlay, CursorSense::click());
|
||||
ui.stack(vec![list.any(), button.any(), overlay.any()]);
|
||||
let mut h = harness();
|
||||
let (list, scrolled) = listener(&mut h, CursorSense::Scroll);
|
||||
let (button, clicked) = listener(&mut h, CursorSense::click());
|
||||
let (overlay, overlay_clicked) = listener(&mut h, CursorSense::click());
|
||||
h.set_root((list, button, overlay).stack());
|
||||
|
||||
let mut cursor = ui.cursor((50.0, 50.0));
|
||||
cursor.scroll_delta = (0.0, 10.0).into();
|
||||
ui.run(cursor);
|
||||
h.move_to((50, 50));
|
||||
h.scroll((0, 10));
|
||||
|
||||
assert_eq!(
|
||||
scrolled.take(),
|
||||
@@ -168,16 +79,32 @@ fn a_scroll_passes_through_every_widget_that_does_not_want_it() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_the_topmost_listener_takes_a_press() {
|
||||
let ui = &mut Ui::new();
|
||||
let (below, above) = (full(ui), full(ui));
|
||||
let below_clicked = ui.listen(&below, CursorSense::click());
|
||||
let above_clicked = ui.listen(&above, CursorSense::click());
|
||||
ui.stack(vec![below.any(), above.any()]);
|
||||
fn hovering_a_button_above_does_not_stop_a_later_scroll() {
|
||||
let mut h = harness();
|
||||
let (list, scrolled) = listener(&mut h, CursorSense::Scroll);
|
||||
let (button, _clicked) = listener(&mut h, CursorSense::click());
|
||||
h.set_root((list, button).stack());
|
||||
|
||||
let mut cursor = ui.cursor((50.0, 50.0));
|
||||
cursor.buttons.left = ActivationState::Start;
|
||||
ui.run(cursor);
|
||||
// The hover arrives in its own frame, as a window delivers it.
|
||||
h.move_to((50, 50));
|
||||
assert_eq!(scrolled.take(), []);
|
||||
|
||||
h.scroll((0, 10));
|
||||
assert_eq!(
|
||||
scrolled.take(),
|
||||
[CursorSense::Scroll],
|
||||
"a hover already resting on the button must not consume the wheel"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_the_topmost_listener_takes_a_press() {
|
||||
let mut h = harness();
|
||||
let (below, below_clicked) = listener(&mut h, CursorSense::click());
|
||||
let (above, above_clicked) = listener(&mut h, CursorSense::click());
|
||||
h.set_root((below, above).stack());
|
||||
|
||||
h.click((50, 50));
|
||||
|
||||
assert_eq!(above_clicked.take(), [CursorSense::click()]);
|
||||
assert_eq!(below_clicked.take(), [], "one press goes to one widget");
|
||||
@@ -185,29 +112,18 @@ fn only_the_topmost_listener_takes_a_press() {
|
||||
|
||||
#[test]
|
||||
fn a_press_beside_the_button_reaches_the_layer_below() {
|
||||
let ui = &mut Ui::new();
|
||||
let list = full(ui);
|
||||
let mut h = harness();
|
||||
let (list, list_clicked) = listener(&mut h, CursorSense::click());
|
||||
// The row above the list covers it, but only its left half is the button.
|
||||
let button = full(ui);
|
||||
let gap = full(ui);
|
||||
let list_clicked = ui.listen(&list, CursorSense::click());
|
||||
let button_clicked = ui.listen(&button, CursorSense::click());
|
||||
let row = ui.rsc.ui.widgets.add_strong(Span {
|
||||
children: vec![button.any(), gap.any()],
|
||||
dir: Dir::RIGHT,
|
||||
gap: 0.0,
|
||||
});
|
||||
ui.stack(vec![list.any(), row.any()]);
|
||||
let (button, button_clicked) = listener(&mut h, CursorSense::click());
|
||||
let row = (button, blank(&mut h)).span(Dir::RIGHT).add(&mut h.rsc);
|
||||
h.set_root((list, row).stack());
|
||||
|
||||
let mut on_button = ui.cursor((20.0, 50.0));
|
||||
on_button.buttons.left = ActivationState::Start;
|
||||
ui.run(on_button);
|
||||
h.click((20, 50));
|
||||
assert_eq!(button_clicked.take(), [CursorSense::click()]);
|
||||
assert_eq!(list_clicked.take(), []);
|
||||
|
||||
let mut beside_it = ui.cursor((80.0, 50.0));
|
||||
beside_it.buttons.left = ActivationState::Start;
|
||||
ui.run(beside_it);
|
||||
h.click((80, 50));
|
||||
assert_eq!(button_clicked.take(), [], "the cursor is not on the button");
|
||||
assert_eq!(
|
||||
list_clicked.take(),
|
||||
@@ -218,44 +134,32 @@ fn a_press_beside_the_button_reaches_the_layer_below() {
|
||||
|
||||
#[test]
|
||||
fn leaving_a_widget_still_ends_its_hover() {
|
||||
let ui = &mut Ui::new();
|
||||
let widget = full(ui);
|
||||
let hover = ui.listen(&widget, CursorSense::HoverStart | CursorSense::HoverEnd);
|
||||
ui.stack(vec![widget.any()]);
|
||||
let mut h = harness();
|
||||
let (widget, hover) = listener(&mut h, CursorSense::HoverStart | CursorSense::HoverEnd);
|
||||
h.set_root(widget);
|
||||
|
||||
let cursor = ui.cursor((50.0, 50.0));
|
||||
ui.run(cursor);
|
||||
h.move_to((50, 50));
|
||||
assert_eq!(hover.take(), [CursorSense::HoverStart]);
|
||||
|
||||
let mut gone = ui.cursor((50.0, 50.0));
|
||||
gone.exists = false;
|
||||
ui.run(gone);
|
||||
h.leave();
|
||||
assert_eq!(hover.take(), [CursorSense::HoverEnd]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn leaving_a_widget_does_not_block_the_layer_below() {
|
||||
let ui = &mut Ui::new();
|
||||
let below = full(ui);
|
||||
let mut h = harness();
|
||||
let (below, below_hover) = listener(&mut h, CursorSense::HoverStart);
|
||||
// Only the left half of the layer above is a widget, so the cursor can
|
||||
// leave it without leaving the one underneath.
|
||||
let (above, gap) = (full(ui), full(ui));
|
||||
let below_hover = ui.listen(&below, CursorSense::HoverStart);
|
||||
let above_hover = ui.listen(&above, CursorSense::HoverStart | CursorSense::HoverEnd);
|
||||
let row = ui.rsc.ui.widgets.add_strong(Span {
|
||||
children: vec![above.any(), gap.any()],
|
||||
dir: Dir::RIGHT,
|
||||
gap: 0.0,
|
||||
});
|
||||
ui.stack(vec![below.any(), row.any()]);
|
||||
let (above, above_hover) = listener(&mut h, CursorSense::HoverStart | CursorSense::HoverEnd);
|
||||
let row = (above, blank(&mut h)).span(Dir::RIGHT).add(&mut h.rsc);
|
||||
h.set_root((below, row).stack());
|
||||
|
||||
let on_above = ui.cursor((20.0, 50.0));
|
||||
ui.run(on_above);
|
||||
h.move_to((20, 50));
|
||||
assert_eq!(above_hover.take(), [CursorSense::HoverStart]);
|
||||
assert_eq!(below_hover.take(), [], "the layer above is over it");
|
||||
|
||||
let beside_it = ui.cursor((80.0, 50.0));
|
||||
ui.run(beside_it);
|
||||
h.move_to((80, 50));
|
||||
assert_eq!(above_hover.take(), [CursorSense::HoverEnd]);
|
||||
assert_eq!(
|
||||
below_hover.take(),
|
||||
@@ -264,53 +168,18 @@ fn leaving_a_widget_does_not_block_the_layer_below() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hovering_a_button_above_does_not_stop_a_later_scroll() {
|
||||
let ui = &mut Ui::new();
|
||||
let (list, button) = (full(ui), full(ui));
|
||||
let scrolled = ui.listen(&list, CursorSense::Scroll);
|
||||
let clicked = ui.listen(&button, CursorSense::click());
|
||||
ui.stack(vec![list.any(), button.any()]);
|
||||
|
||||
// The hover arrives in its own frame, as a window delivers it.
|
||||
let hover = ui.cursor((50.0, 50.0));
|
||||
ui.run(hover);
|
||||
assert_eq!(scrolled.take(), []);
|
||||
|
||||
let mut wheel = ui.cursor((50.0, 50.0));
|
||||
wheel.scroll_delta = (0.0, 10.0).into();
|
||||
ui.run(wheel);
|
||||
|
||||
assert_eq!(
|
||||
scrolled.take(),
|
||||
[CursorSense::Scroll],
|
||||
"a hover already resting on the button must not consume the wheel"
|
||||
);
|
||||
assert_eq!(clicked.take(), []);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn covering_a_widget_ends_its_hover() {
|
||||
let ui = &mut Ui::new();
|
||||
let below = full(ui);
|
||||
// Only the left half of the layer above is a widget, so the cursor can
|
||||
// start beside it and then move onto it.
|
||||
let (above, gap) = (full(ui), full(ui));
|
||||
let below_hover = ui.listen(&below, CursorSense::HoverStart | CursorSense::HoverEnd);
|
||||
let above_hover = ui.listen(&above, CursorSense::HoverStart);
|
||||
let row = ui.rsc.ui.widgets.add_strong(Span {
|
||||
children: vec![above.any(), gap.any()],
|
||||
dir: Dir::RIGHT,
|
||||
gap: 0.0,
|
||||
});
|
||||
ui.stack(vec![below.any(), row.any()]);
|
||||
let mut h = harness();
|
||||
let (below, below_hover) = listener(&mut h, CursorSense::HoverStart | CursorSense::HoverEnd);
|
||||
let (above, above_hover) = listener(&mut h, CursorSense::HoverStart);
|
||||
let row = (above, blank(&mut h)).span(Dir::RIGHT).add(&mut h.rsc);
|
||||
h.set_root((below, row).stack());
|
||||
|
||||
let beside_it = ui.cursor((80.0, 50.0));
|
||||
ui.run(beside_it);
|
||||
h.move_to((80, 50));
|
||||
assert_eq!(below_hover.take(), [CursorSense::HoverStart]);
|
||||
|
||||
let onto_above = ui.cursor((20.0, 50.0));
|
||||
ui.run(onto_above);
|
||||
h.move_to((20, 50));
|
||||
assert_eq!(above_hover.take(), [CursorSense::HoverStart]);
|
||||
assert_eq!(
|
||||
below_hover.take(),
|
||||
@@ -318,8 +187,7 @@ fn covering_a_widget_ends_its_hover() {
|
||||
"a widget covered by one that took the input is no longer hovered"
|
||||
);
|
||||
|
||||
let off_again = ui.cursor((80.0, 50.0));
|
||||
ui.run(off_again);
|
||||
h.move_to((80, 50));
|
||||
assert_eq!(
|
||||
below_hover.take(),
|
||||
[CursorSense::HoverStart],
|
||||
@@ -329,36 +197,26 @@ fn covering_a_widget_ends_its_hover() {
|
||||
|
||||
#[test]
|
||||
fn hover_starts_and_ends_once_each() {
|
||||
let ui = &mut Ui::new();
|
||||
let mut h = harness();
|
||||
// Only the left half is the widget, so the cursor can leave it without
|
||||
// leaving the window.
|
||||
let (widget, gap) = (full(ui), full(ui));
|
||||
let hover = ui.listen(&widget, CursorSense::HoverStart | CursorSense::HoverEnd);
|
||||
let row = ui.rsc.ui.widgets.add_strong(Span {
|
||||
children: vec![widget.any(), gap.any()],
|
||||
dir: Dir::RIGHT,
|
||||
gap: 0.0,
|
||||
});
|
||||
ui.stack(vec![row.any()]);
|
||||
let (widget, hover) = listener(&mut h, CursorSense::HoverStart | CursorSense::HoverEnd);
|
||||
let row = (widget, blank(&mut h)).span(Dir::RIGHT).add(&mut h.rsc);
|
||||
h.set_root(row);
|
||||
|
||||
let inside = ui.cursor((20.0, 50.0));
|
||||
ui.run(inside);
|
||||
h.move_to((20, 50));
|
||||
assert_eq!(hover.take(), [CursorSense::HoverStart]);
|
||||
|
||||
let further_in = ui.cursor((30.0, 50.0));
|
||||
ui.run(further_in);
|
||||
h.move_to((30, 50));
|
||||
assert_eq!(hover.take(), [], "staying inside is not a second start");
|
||||
|
||||
let outside = ui.cursor((80.0, 50.0));
|
||||
ui.run(outside);
|
||||
h.move_to((80, 50));
|
||||
assert_eq!(hover.take(), [CursorSense::HoverEnd]);
|
||||
|
||||
let further_out = ui.cursor((90.0, 50.0));
|
||||
ui.run(further_out);
|
||||
h.move_to((90, 50));
|
||||
assert_eq!(hover.take(), [], "an ended hover does not end again");
|
||||
|
||||
let back_inside = ui.cursor((20.0, 50.0));
|
||||
ui.run(back_inside);
|
||||
h.move_to((20, 50));
|
||||
assert_eq!(
|
||||
hover.take(),
|
||||
[CursorSense::HoverStart],
|
||||
|
||||
Reference in new issue
Block a user