diff --git a/core/src/event/manager.rs b/core/src/event/manager.rs index 6b4b969..cb13893 100644 --- a/core/src/event/manager.rs +++ b/core/src/event/manager.rs @@ -79,6 +79,7 @@ type EventData = (E, Rc EventFn::Data<'a>> pub struct TypeEventManager { // TODO: reduce visiblity!! pub active: HashMap>, + pub global: E::Global, map: HashMap>>, } @@ -107,6 +108,7 @@ impl Default for TypeEventManager { fn default() -> Self { Self { active: Default::default(), + global: Default::default(), map: Default::default(), } } diff --git a/core/src/event/mod.rs b/core/src/event/mod.rs index c3ce00a..bc80c9d 100644 --- a/core/src/event/mod.rs +++ b/core/src/event/mod.rs @@ -9,6 +9,9 @@ pub use rsc::*; pub trait Event: Sized + 'static + Clone { type Data<'a>: Clone = (); type State: Default = (); + /// State the whole event type keeps, as opposed to `State`, which each + /// widget keeps its own of. + type Global: Default = (); #[allow(unused_variables)] fn should_run<'a>(&self, data: &Self::Data<'a>) -> Option> { Some(data.clone()) diff --git a/src/default/sense.rs b/src/default/sense.rs index 980cd5f..d1d9838 100644 --- a/src/default/sense.rs +++ b/src/default/sense.rs @@ -27,7 +27,10 @@ pub struct CursorSenses(Vec); impl Event for CursorSenses { type Data<'a> = CursorData<'a>; - type State = SensorState; + /// 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; 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(); @@ -141,11 +144,6 @@ pub struct Sensor { pub type SenseShape = UiRegion; -#[derive(Default, Debug)] -pub struct SensorState { - pub hover: ActivationState, -} - #[derive(Clone)] pub struct CursorData<'a> { /// where this widget was hit @@ -181,51 +179,81 @@ impl SensorUi for UiRenderState { // 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 mut active = std::mem::take(&mut rsc.events_mut().get_type::().active); + 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 position_only = cursor.position_only(); - let mut consumed = false; + let mut now: Vec = Vec::new(); + let region_of = |id| Some(self.active.get(&id)?.region.to_px(window_size)); + for layer in self.layers.indices().rev() { - let mut consumed_here = false; - for (id, sensor) in active.get_mut(&layer).into_flat_iter() { - let shape = self.active.get(id).unwrap().region; - let region = shape.to_px(window_size); - // Once a layer above has taken the input, everything under it - // is covered rather than skipped: it is not hovered, so a - // widget that was gets to end its hover. - let in_shape = !consumed && cursor.exists && region.contains(cursor.pos); - sensor.hover.update(in_shape); - if sensor.hover == ActivationState::Off { + let mut consumed = false; + for id in active.get(&layer).into_flat_iter().map(|(id, _)| *id) { + let Some(region) = region_of(id) else { + continue; + }; + if !cursor.exists || !region.contains(cursor.pos) { continue; } - let cursor = cursor.clone(); - - let data = CursorData { - pos: cursor.pos - region.top_left, - size: region.bot_right - region.top_left, - scroll_delta: cursor.scroll_delta, - hover: sensor.hover, - cursor, - // this does not have any meaning; - // might wanna set up Event to have a prepare stage - sense: CursorSense::Hovering, - render: self, + now.push(id); + let hover = match was.contains(&id) { + true => ActivationState::On, + false => ActivationState::Start, }; // A press or a scroll stops where something answered it, so a - // button over a list does not swallow the list's scrolling. A - // cursor doing neither stops at the widget it is over, so - // hovering does not reach through one -- but not at a widget - // it has just left, which is here only to end its hover. - let answered = rsc.run_event::(*id, data, state); - consumed_here |= answered || (position_only && in_shape); + // button over a list does not swallow the list's scrolling. + consumed |= deliver(self, rsc, state, id, hover, &cursor, region); + // A cursor doing neither stops at whatever it is over, so + // hovering does not reach through. + consumed |= position_only; + } + // Applied after the layer, never during it: senses on one layer do + // not block each other. + if consumed { + break; } - // Applied after the layer, never during it: senses on one layer - // do not block each other. - consumed |= consumed_here; } - rsc.events_mut().get_type::().active = active; + + // 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) + && let Some(region) = region_of(id) + { + deliver(self, rsc, state, id, ActivationState::End, &cursor, region); + } + } + + let senses = rsc.events_mut().get_type::(); + senses.active = active; + senses.global = now; } } +/// Runs one widget's cursor senses, and says whether they used up the input. +fn deliver( + render: &UiRenderState, + rsc: &mut Rsc, + state: &mut Rsc::State, + id: WidgetId, + hover: ActivationState, + cursor: &CursorState, + region: PixelRegion, +) -> bool { + let data = CursorData { + pos: cursor.pos - region.top_left, + size: region.bot_right - region.top_left, + scroll_delta: cursor.scroll_delta, + hover, + cursor: cursor.clone(), + // this does not have any meaning; + // might wanna set up Event to have a prepare stage + sense: CursorSense::Hovering, + render, + }; + rsc.run_event::(id, data, state) +} + pub fn should_run( senses: &CursorSenses, cursor: &CursorState, diff --git a/tests/pointer_routing.rs b/tests/pointer_routing.rs index 1a653f5..ac4d548 100644 --- a/tests/pointer_routing.rs +++ b/tests/pointer_routing.rs @@ -317,4 +317,51 @@ fn covering_a_widget_ends_its_hover() { [CursorSense::HoverEnd], "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); + assert_eq!( + below_hover.take(), + [CursorSense::HoverStart], + "uncovering it hovers it again" + ); +} + +#[test] +fn hover_starts_and_ends_once_each() { + let ui = &mut Ui::new(); + // 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 inside = ui.cursor((20.0, 50.0)); + ui.run(inside); + assert_eq!(hover.take(), [CursorSense::HoverStart]); + + let further_in = ui.cursor((30.0, 50.0)); + ui.run(further_in); + assert_eq!(hover.take(), [], "staying inside is not a second start"); + + let outside = ui.cursor((80.0, 50.0)); + ui.run(outside); + assert_eq!(hover.take(), [CursorSense::HoverEnd]); + + let further_out = ui.cursor((90.0, 50.0)); + ui.run(further_out); + assert_eq!(hover.take(), [], "an ended hover does not end again"); + + let back_inside = ui.cursor((20.0, 50.0)); + ui.run(back_inside); + assert_eq!( + hover.take(), + [CursorSense::HoverStart], + "re-entering starts it" + ); }