use super::*; use iced::advanced::widget::operation::{Focusable, Outcome}; use iced::widget::{column, text_input}; use iced::{Point, Theme}; #[derive(Default)] struct FocusMap(Vec<(Option, Rectangle, bool)>); impl widget::Operation for FocusMap { fn traverse(&mut self, visit: &mut dyn FnMut(&mut dyn widget::Operation)) { visit(self); } fn focusable(&mut self, id: Option<&widget::Id>, bounds: Rectangle, state: &mut dyn Focusable) { self.0.push((id.cloned(), bounds, state.is_focused())); } } struct Mounted { element: Element<'static, (), Theme, ()>, tree: widget::Tree, node: layout::Node, } impl Mounted { fn new(clock: Arc) -> Self { let content = column![ text_input("Editor", "source") .id("editor") .on_input(|_| ()) .padding(12), text_input("Find", "") .id("find") .on_input(|_| ()) .padding(12), ] .spacing(10) .into(); let mut element = observe(content, clock); let mut tree = widget::Tree::new(element.as_widget()); let node = element.as_widget_mut().layout( &mut tree, &(), &layout::Limits::new(Size::ZERO, Size::new(400., 200.)), ); Self { element, tree, node, } } fn pass(&mut self, operation: &mut dyn widget::Operation) { self.element.as_widget_mut().operate( &mut self.tree, Layout::new(&self.node), &(), operation, ); } fn run(&mut self, operation: impl widget::Operation + 'static) { let mut next: Box = Box::new(operation); loop { self.pass(next.as_mut()); match next.finish() { Outcome::Chain(chained) => next = chained, Outcome::None => break, Outcome::Some(()) => panic!("focus must not publish an application message"), } } } fn focus(&mut self) -> FocusMap { let mut focus = FocusMap::default(); self.pass(&mut focus); focus } fn click(&mut self, id: &str) { let bounds = self .focus() .0 .into_iter() .find(|(found, _, _)| found.as_ref() == Some(&widget::Id::from(id.to_owned()))) .unwrap() .1; let mut messages = Vec::new(); let mut shell = Shell::new(&mut messages); self.element.as_widget_mut().update( &mut self.tree, &Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)), Layout::new(&self.node), mouse::Cursor::Available(bounds.center()), &(), &mut iced::advanced::clipboard::Null, &mut shell, &Rectangle::with_size(Size::new(400., 200.)), ); assert!(shell.is_event_captured()); assert!( messages.is_empty(), "focus-only click must not rely on App messages" ); } fn focused(&mut self, id: &str) -> bool { self.focus() .0 .into_iter() .find(|(found, _, _)| found.as_ref() == Some(&widget::Id::from(id.to_owned()))) .unwrap() .2 } } #[test] fn captured_find_click_invalidates_without_app_message_and_preserves_focus() { let clock = Arc::new(AtomicU64::new(0)); let mut ui = Mounted::new(clock.clone()); ui.run(GuardedFocus::new("editor".into(), clock.clone(), 0)); assert!(ui.focused("editor")); ui.click("find"); assert!(!is_current(&clock, 0)); assert!(ui.focused("find")); ui.run(GuardedFocus::new("editor".into(), clock.clone(), 0)); assert!(ui.focused("find")); // Even a matching clock cannot authorize stealing another widget's focus. ui.run(GuardedFocus::new( "editor".into(), clock.clone(), clock.load(Ordering::Acquire), )); assert!(ui.focused("find")); assert!(!ui.focused("editor")); } #[test] fn focus_rechecks_clock_between_inspection_and_application_and_missing_target_is_inert() { let clock = Arc::new(AtomicU64::new(0)); let mut ui = Mounted::new(clock.clone()); let mut operation = GuardedFocus::new("editor".into(), clock.clone(), 0); ui.pass(&mut operation); let Outcome::Chain(mut apply) = >::finish(&operation) else { panic!("mounted, unopposed target must be eligible"); }; clock.store(1, Ordering::Release); ui.pass(apply.as_mut()); assert!(!ui.focused("editor")); ui.run(GuardedFocus::new("editor".into(), clock.clone(), 1)); assert!(ui.focused("editor")); ui.run(GuardedFocus::new("removed-editor".into(), clock, 1)); assert!(ui.focused("editor")); } #[test] fn raw_navigation_inputs_advance_but_passive_geometry_does_not_and_exhaustion_refuses() { let clock = AtomicU64::new(0); let events = [ Event::Keyboard(keyboard::Event::KeyPressed { key: keyboard::Key::Named(keyboard::key::Named::ArrowLeft), modified_key: keyboard::Key::Named(keyboard::key::Named::ArrowLeft), physical_key: keyboard::key::Physical::Code(keyboard::key::Code::ArrowLeft), location: keyboard::Location::Standard, modifiers: keyboard::Modifiers::empty(), text: None, repeat: false, }), Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)), Event::Mouse(mouse::Event::WheelScrolled { delta: mouse::ScrollDelta::Lines { x: 0., y: 1. }, }), Event::Touch(touch::Event::FingerPressed { id: touch::Finger(1), position: Point::ORIGIN, }), Event::Window(iced::window::Event::Unfocused), ]; for (index, event) in events.iter().enumerate() { invalidate(&clock, event); assert_eq!(clock.load(Ordering::Acquire), index as u64 + 1); } invalidate( &clock, &Event::Mouse(mouse::Event::CursorMoved { position: Point::ORIGIN, }), ); invalidate(&clock, &Event::Window(iced::window::Event::Focused)); assert!(is_current(&clock, 5)); clock.store(u64::MAX - 1, Ordering::Release); invalidate(&clock, &events[0]); assert!(!is_current(&clock, u64::MAX)); invalidate(&clock, &events[0]); assert_eq!(clock.load(Ordering::Acquire), u64::MAX); assert!(!is_current(&clock, 0)); }