diff --git a/src/default/mod.rs b/src/default/mod.rs index 0c61a86..cf25d56 100644 --- a/src/default/mod.rs +++ b/src/default/mod.rs @@ -235,8 +235,7 @@ impl AppState for DefaultApp { 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 diff --git a/src/default/sense.rs b/src/default/sense.rs index d1d9838..14d5b3a 100644 --- a/src/default/sense.rs +++ b/src/default/sense.rs @@ -27,10 +27,7 @@ pub struct CursorSenses(Vec); 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; + type Global = Hovered; fn should_run<'a>(&self, data: &Self::Data<'a>) -> Option> { 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, + now: Vec, +} + 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::().active); - let was = std::mem::take(&mut rsc.events_mut().get_type::().global); + let mut hovered = std::mem::take(&mut rsc.events_mut().get_type::().global); + hovered.now.clear(); let position_only = cursor.position_only(); - let mut now: Vec = 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::(); senses.active = active; - senses.global = now; + senses.global = hovered; } } diff --git a/src/harness.rs b/src/harness.rs index f575df6..8c947ba 100644 --- a/src/harness.rs +++ b/src/harness.rs @@ -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(); } } diff --git a/tests/pointer_routing.rs b/tests/pointer_routing.rs index ac4d548..d59e406 100644 --- a/tests/pointer_routing.rs +++ b/tests/pointer_routing.rs @@ -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, -} - -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.events - } - fn events_mut(&mut self) -> &mut EventManager { - &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: (), +/// A widget filling whatever it is given, recording the senses it is sent. +fn listener(h: &mut Harness, senses: impl Into) -> (WeakWidget, Fired) { + let fired = Fired::default(); + let record = fired.clone(); + let id = rect(Color::WHITE) + .on(senses.into(), move |ctx, _| { + record.0.borrow_mut().push(ctx.data.sense) + }) + .add(&mut h.rsc); + (id, fired) } -impl Ui { - fn new() -> Self { - Self { - rsc: SenseRsc { - ui: UiData::default(), - events: EventManager::default(), - }, - render: UiRenderState::new(), - state: (), - } - } - - fn listen( - &mut self, - widget: &StrongWidget, - senses: impl Into, - ) -> 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) { - let root = self - .rsc - .ui - .widgets - .add_strong(Stack { - children, - size: StackSize::default(), - }) - .any(); - self.render.resize((WINDOW, WINDOW)); - self.render.update(&root, &mut self.rsc); - } - - fn cursor(&mut self, at: (f32, f32)) -> CursorState { - CursorState { - pos: at.into(), - exists: true, - buttons: Default::default(), - scroll_delta: Vec2::ZERO, - } - } - - fn run(&mut self, cursor: CursorState) { - self.render.run_sensors( - &mut self.rsc, - &mut self.state, - cursor, - (WINDOW, WINDOW).into(), - ); - } +/// A widget with no senses of its own, to leave a gap beside one that has. +fn blank(h: &mut Harness) -> WeakWidget { + rect(Color::WHITE).add(&mut h.rsc) } -fn full(ui: &mut Ui) -> StrongWidget { - 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],