//! Raw input invalidation and focus checks at widget-operation execution time. use iced::advanced::{Clipboard, Layout, Shell, Widget, layout, overlay, renderer, widget}; use iced::{Element, Event, Length, Rectangle, Size, Task, Vector, keyboard, mouse, touch}; use std::sync::{ Arc, atomic::{AtomicU64, Ordering}, }; /// Exhaustion fails closed: a saturated clock can never authorize navigation. /// Replace the clock with a new workspace/controller, rather than wrapping it. pub fn is_current(clock: &AtomicU64, expected: u64) -> bool { expected != u64::MAX && clock.load(Ordering::Acquire) == expected } fn invalidate(clock: &AtomicU64, event: &Event) { if matches!( event, Event::Keyboard(keyboard::Event::KeyPressed { .. }) | Event::Mouse(mouse::Event::ButtonPressed(_) | mouse::Event::WheelScrolled { .. }) | Event::Touch(touch::Event::FingerPressed { .. }) | Event::Window(iced::window::Event::Unfocused) ) { let _ = clock.fetch_update(Ordering::AcqRel, Ordering::Acquire, |value| { Some(value.saturating_add(1)) }); } } /// Transparent, including overlays: invalidate before the child can capture input. pub fn observe<'a, Message: 'a, Theme: 'a, Renderer: renderer::Renderer + 'a>( content: Element<'a, Message, Theme, Renderer>, clock: Arc, ) -> Element<'a, Message, Theme, Renderer> { Element::new(Observed { content, clock }) } struct Observed<'a, Message, Theme, Renderer> { content: Element<'a, Message, Theme, Renderer>, clock: Arc, } impl Widget for Observed<'_, Message, Theme, Renderer> { fn size(&self) -> Size { self.content.as_widget().size() } fn size_hint(&self) -> Size { self.content.as_widget().size_hint() } fn tag(&self) -> widget::tree::Tag { self.content.as_widget().tag() } fn state(&self) -> widget::tree::State { self.content.as_widget().state() } fn children(&self) -> Vec { self.content.as_widget().children() } fn diff(&self, tree: &mut widget::Tree) { self.content.as_widget().diff(tree); } fn layout( &mut self, tree: &mut widget::Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { self.content.as_widget_mut().layout(tree, renderer, limits) } fn draw( &self, tree: &widget::Tree, renderer: &mut Renderer, theme: &Theme, style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, ) { self.content .as_widget() .draw(tree, renderer, theme, style, layout, cursor, viewport); } fn operate( &mut self, tree: &mut widget::Tree, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn widget::Operation, ) { self.content .as_widget_mut() .operate(tree, layout, renderer, operation); } fn update( &mut self, tree: &mut widget::Tree, event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) { invalidate(&self.clock, event); self.content.as_widget_mut().update( tree, event, layout, cursor, renderer, clipboard, shell, viewport, ); } fn mouse_interaction( &self, tree: &widget::Tree, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, renderer: &Renderer, ) -> mouse::Interaction { self.content .as_widget() .mouse_interaction(tree, layout, cursor, viewport, renderer) } fn overlay<'a>( &'a mut self, tree: &'a mut widget::Tree, layout: Layout<'a>, renderer: &Renderer, viewport: &Rectangle, translation: Vector, ) -> Option> { let clock = self.clock.clone(); self.content .as_widget_mut() .overlay(tree, layout, renderer, viewport, translation) .map(|content| overlay::Element::new(Box::new(ObservedOverlay { content, clock }))) } } struct ObservedOverlay<'a, Message, Theme, Renderer> { content: overlay::Element<'a, Message, Theme, Renderer>, clock: Arc, } impl overlay::Overlay for ObservedOverlay<'_, Message, Theme, Renderer> { fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node { self.content.as_overlay_mut().layout(renderer, bounds) } fn draw( &self, renderer: &mut Renderer, theme: &Theme, style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, ) { self.content .as_overlay() .draw(renderer, theme, style, layout, cursor); } fn operate( &mut self, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn widget::Operation, ) { self.content .as_overlay_mut() .operate(layout, renderer, operation); } fn update( &mut self, event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, ) { invalidate(&self.clock, event); self.content .as_overlay_mut() .update(event, layout, cursor, renderer, clipboard, shell); } fn mouse_interaction( &self, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, ) -> mouse::Interaction { self.content .as_overlay() .mouse_interaction(layout, cursor, renderer) } fn overlay<'a>( &'a mut self, layout: Layout<'a>, renderer: &Renderer, ) -> Option> { let clock = self.clock.clone(); self.content .as_overlay_mut() .overlay(layout, renderer) .map(|content| overlay::Element::new(Box::new(ObservedOverlay { content, clock }))) } fn index(&self) -> f32 { self.content.as_overlay().index() } } /// Inspect all focus owners, then focus only the mounted target. Iced executes /// Operation::Chain synchronously, without an intervening input event. Both /// traversals recheck the raw clock; no result message authorizes later focus. pub fn guarded_focus( target: widget::Id, clock: Arc, expected: u64, ) -> Task { widget::operate(GuardedFocus::new(target, clock, expected)) } struct GuardedFocus { target: widget::Id, clock: Arc, expected: u64, target_count: usize, foreign_focus: bool, apply: bool, } impl GuardedFocus { fn new(target: widget::Id, clock: Arc, expected: u64) -> Self { Self { target, clock, expected, target_count: 0, foreign_focus: false, apply: false, } } } impl widget::Operation for GuardedFocus { fn traverse(&mut self, visit: &mut dyn FnMut(&mut dyn widget::Operation)) { if is_current(&self.clock, self.expected) { visit(self); } } fn focusable( &mut self, id: Option<&widget::Id>, _: Rectangle, state: &mut dyn widget::operation::Focusable, ) { if !is_current(&self.clock, self.expected) { return; } if id == Some(&self.target) { self.target_count += 1; if self.apply { state.focus(); } } else if state.is_focused() { self.foreign_focus = true; } } fn finish(&self) -> widget::operation::Outcome { if !self.apply && self.target_count == 1 && !self.foreign_focus && is_current(&self.clock, self.expected) { widget::operation::Outcome::Chain(Box::new(Self { apply: true, ..Self::new(self.target.clone(), self.clock.clone(), self.expected) })) } else { widget::operation::Outcome::None } } } #[cfg(all(test, debug_assertions))] mod tests;