//! Keep overlay Escape ahead of modal cancellation in the ordinary widget tree. //! //! Iced skips root.update when an overlay captures an event. Keeping this //! wrapper present even when inactive preserves the child's widget state. A //! current-overlay preflight also handles pickers opened mid-batch. Passive //! overlays may see an ignored Escape again: the raw-input observer only makes //! pending focus requests stale; it does not publish modal cancellation. use super::{App, Message, Page, architecture_authoring}; use iced::advanced::{Clipboard, Layout, Shell, Widget, layout, overlay, renderer, widget}; use iced::{Element, Event, Length, Rectangle, Size, Theme, Vector, keyboard, mouse}; pub(crate) fn root<'a, R: renderer::Renderer + 'a>( app: &App, content: Element<'a, Message, Theme, R>, ) -> Element<'a, Message, Theme, R> { let modal = app.text_workspace.menu().is_none() && (app.behavior.creation.has_pending() || app.diagram_usages.is_some() || app.behavior.source_active() || (app.page == Page::Text && (app.text_workspace.drawer().is_some() || app.source_navigation.open || app.source_rename.open()) && app.architecture.is_none())); Element::new(Bridge { content, messages: [ architecture_authoring::captured_escape_message(app), modal.then_some(Message::Escape), ], }) } struct Bridge<'a, R> { content: Element<'a, Message, Theme, R>, messages: [Option; 2], } impl Widget for Bridge<'_, R> { fn children(&self) -> Vec { vec![widget::Tree::new(self.content.as_widget())] } fn diff(&self, tree: &mut widget::Tree) { tree.diff_children(std::slice::from_ref(&self.content)); } fn size(&self) -> Size { self.content.as_widget().size() } fn size_hint(&self) -> Size { self.content.as_widget().size_hint() } fn layout( &mut self, tree: &mut widget::Tree, renderer: &R, limits: &layout::Limits, ) -> layout::Node { self.content .as_widget_mut() .layout(&mut tree.children[0], renderer, limits) } fn operate( &mut self, tree: &mut widget::Tree, layout: Layout<'_>, renderer: &R, operation: &mut dyn widget::Operation, ) { self.content .as_widget_mut() .operate(&mut tree.children[0], layout, renderer, operation); } fn update( &mut self, tree: &mut widget::Tree, event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &R, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) { let escape = matches!( event, Event::Keyboard(keyboard::Event::KeyPressed { key: keyboard::Key::Named(keyboard::key::Named::Escape), .. }) ); // Iced collects overlays before processing a batch of base events. A // preceding click in that batch can therefore open a picker whose // Escape would otherwise reach focused fields underneath it. Offer only // Escape to the current overlay before updating the ordinary tree. if escape { if let Some(element) = self.content.as_widget_mut().overlay( &mut tree.children[0], layout, renderer, viewport, Vector::ZERO, ) { let mut overlay = overlay::Nested::new(element); let node = overlay.layout(renderer, viewport.size()); overlay.update( event, Layout::new(&node), cursor, renderer, clipboard, shell, ); } if shell.is_event_captured() { return; } } self.content.as_widget_mut().update( &mut tree.children[0], event, layout, cursor, renderer, clipboard, shell, viewport, ); if shell.is_event_captured() && escape { for message in self.messages.iter().flatten() { shell.publish(message.clone()); } } } fn draw( &self, tree: &widget::Tree, renderer: &mut R, theme: &Theme, style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, ) { self.content.as_widget().draw( &tree.children[0], renderer, theme, style, layout, cursor, viewport, ); } fn mouse_interaction( &self, tree: &widget::Tree, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, renderer: &R, ) -> mouse::Interaction { self.content.as_widget().mouse_interaction( &tree.children[0], layout, cursor, viewport, renderer, ) } fn overlay<'b>( &'b mut self, tree: &'b mut widget::Tree, layout: Layout<'b>, renderer: &R, viewport: &Rectangle, translation: Vector, ) -> Option> { self.content.as_widget_mut().overlay( &mut tree.children[0], layout, renderer, viewport, translation, ) } } #[cfg(test)] pub(crate) mod tests;