//! Input across layers: what stops at a layer, what passes through it, and //! where hovering stops. use std::{cell::RefCell, rc::Rc}; use iris::harness::Harness; use iris::prelude::*; const WINDOW: f32 = 100.0; /// Every sense that has fired on one widget since it was last read. #[derive(Default, Clone)] struct Fired(Rc>>); impl Fired { fn take(&self) -> Vec { std::mem::take(&mut self.0.borrow_mut()) } } /// 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) } /// 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 harness() -> Harness { Harness::new((WINDOW, WINDOW)) } #[test] fn hover_stops_at_the_topmost_widget() { 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()); h.move_to((50, 50)); assert_eq!(top_hover.take(), [CursorSense::HoverStart]); assert_eq!( middle_hover.take(), [], "hover is not shared with a layer below" ); assert_eq!(bottom_hover.take(), []); } #[test] fn a_scroll_passes_through_every_widget_that_does_not_want_it() { 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()); h.move_to((50, 50)); h.scroll((0, 10)); assert_eq!( scrolled.take(), [CursorSense::Scroll], "two layers of click-only widgets do not stop a scroll" ); assert_eq!(clicked.take(), []); assert_eq!(overlay_clicked.take(), []); } #[test] 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()); // 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"); } #[test] fn a_press_beside_the_button_reaches_the_layer_below() { 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, 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()); h.click((20, 50)); assert_eq!(button_clicked.take(), [CursorSense::click()]); assert_eq!(list_clicked.take(), []); h.click((80, 50)); assert_eq!(button_clicked.take(), [], "the cursor is not on the button"); assert_eq!( list_clicked.take(), [CursorSense::click()], "a press beside the button belongs to what is under it" ); } #[test] fn leaving_a_widget_still_ends_its_hover() { let mut h = harness(); let (widget, hover) = listener(&mut h, CursorSense::HoverStart | CursorSense::HoverEnd); h.set_root(widget); h.move_to((50, 50)); assert_eq!(hover.take(), [CursorSense::HoverStart]); h.leave(); assert_eq!(hover.take(), [CursorSense::HoverEnd]); } #[test] fn leaving_a_widget_does_not_block_the_layer_below() { 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, 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()); h.move_to((20, 50)); assert_eq!(above_hover.take(), [CursorSense::HoverStart]); assert_eq!(below_hover.take(), [], "the layer above is over it"); h.move_to((80, 50)); assert_eq!(above_hover.take(), [CursorSense::HoverEnd]); assert_eq!( below_hover.take(), [CursorSense::HoverStart], "ending a hover above must not stop the hover below" ); } #[test] fn covering_a_widget_ends_its_hover() { 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()); h.move_to((80, 50)); assert_eq!(below_hover.take(), [CursorSense::HoverStart]); h.move_to((20, 50)); assert_eq!(above_hover.take(), [CursorSense::HoverStart]); assert_eq!( below_hover.take(), [CursorSense::HoverEnd], "a widget covered by one that took the input is no longer hovered" ); h.move_to((80, 50)); assert_eq!( below_hover.take(), [CursorSense::HoverStart], "uncovering it hovers it again" ); } #[test] fn hover_starts_and_ends_once_each() { let mut h = harness(); // Only the left half is the widget, so the cursor can leave it without // leaving the window. 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); h.move_to((20, 50)); assert_eq!(hover.take(), [CursorSense::HoverStart]); h.move_to((30, 50)); assert_eq!(hover.take(), [], "staying inside is not a second start"); h.move_to((80, 50)); assert_eq!(hover.take(), [CursorSense::HoverEnd]); h.move_to((90, 50)); assert_eq!(hover.take(), [], "an ended hover does not end again"); h.move_to((20, 50)); assert_eq!( hover.take(), [CursorSense::HoverStart], "re-entering starts it" ); }