//! A widget takes only what it answers to: a button over a list takes the //! click and leaves the scroll. These drive `run_sensors` directly, which //! needs no GPU and no window. use iris::prelude::*; use std::{cell::Cell, 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 } } fn cursor_at(pos: Vec2) -> CursorState { CursorState { pos, exists: true, buttons: Default::default(), scroll_delta: Vec2::ZERO, } } /// A button covering a list, on the layer above it: the list scrolls, the /// button clicks, and the returned flags say which fired. fn button_over_list() -> (UiRenderState, SenseRsc, Rc>, Rc>) { let mut rsc = SenseRsc { ui: UiData::default(), events: EventManager::default(), }; // Both cover the whole window: the button "sitting over" the list. let list = rsc.ui.widgets.add_strong(Rect::new(UiColor::WHITE)); let list_weak = list.weak(); let button = rsc.ui.widgets.add_strong(Rect::new(UiColor::RED)); let button_weak = button.weak(); let scrolled = Rc::new(Cell::new(false)); let clicked = Rc::new(Cell::new(false)); { let scrolled = scrolled.clone(); rsc.register_event(list_weak, CursorSense::Scroll, move |_ctx, _rsc| { scrolled.set(true); }); } { let clicked = clicked.clone(); rsc.register_event(button_weak, CursorSense::click(), move |_ctx, _rsc| { clicked.set(true); }); } // A Stack draws each child on its own layer, in order. let root = rsc .ui .widgets .add_strong(Stack { children: vec![list.any(), button.any()], size: StackSize::default(), }) .any(); let mut render = UiRenderState::new(); render.resize((100.0, 100.0)); render.update(&root, &mut rsc); (render, rsc, scrolled, clicked) } #[test] fn a_button_over_a_list_scrolls_the_list_and_still_clicks() { let (render, mut rsc, scrolled, clicked) = button_over_list(); let mut state = (); let mut scroll = cursor_at((50.0, 50.0).into()); scroll.scroll_delta = (0.0, 10.0).into(); render.run_sensors(&mut rsc, &mut state, scroll, (100.0, 100.0).into()); assert!( scrolled.get(), "a scroll over the button must still reach the list underneath it" ); assert!( !clicked.get(), "a scroll is not a click; the button must not have fired" ); let mut click = cursor_at((50.0, 50.0).into()); click.buttons.left = ActivationState::Start; render.run_sensors(&mut rsc, &mut state, click, (100.0, 100.0).into()); assert!( clicked.get(), "the button on top must still receive an actual click" ); } #[test] fn a_click_and_a_scroll_in_one_frame_go_to_different_widgets() { let (render, mut rsc, scrolled, clicked) = button_over_list(); let mut state = (); let mut both = cursor_at((50.0, 50.0).into()); both.scroll_delta = (0.0, 10.0).into(); both.buttons.left = ActivationState::Start; render.run_sensors(&mut rsc, &mut state, both, (100.0, 100.0).into()); assert!( clicked.get(), "the button takes the click it registered for" ); assert!( scrolled.get(), "taking the click must not take the scroll with it" ); } #[test] fn leaving_a_widget_still_ends_its_hover() { let mut rsc = SenseRsc { ui: UiData::default(), events: EventManager::default(), }; let widget = rsc.ui.widgets.add_strong(Rect::new(UiColor::WHITE)); let ended = Rc::new(Cell::new(false)); { let ended = ended.clone(); rsc.register_event(widget.weak(), CursorSense::HoverEnd, move |_ctx, _rsc| { ended.set(true); }); } let root = widget.any(); let mut render = UiRenderState::new(); render.resize((100.0, 100.0)); render.update(&root, &mut rsc); let mut state = (); render.run_sensors( &mut rsc, &mut state, cursor_at((50.0, 50.0).into()), (100.0, 100.0).into(), ); assert!(!ended.get(), "the cursor is still on it"); let mut gone = cursor_at((50.0, 50.0).into()); gone.exists = false; render.run_sensors(&mut rsc, &mut state, gone, (100.0, 100.0).into()); assert!(ended.get(), "leaving a widget ends its hover"); }