//! A delayed focus request must still own the live parent tree when executed. //! Collect first: a missing target must not unfocus unrelated fields. use super::Context; use iced::advanced::widget::{Id, Operation, Tree, operation}; use iced::advanced::{Clipboard, Layout, Shell, Widget, layout, overlay, renderer}; use iced::{Element, Event, Length, Rectangle, Size, Task, Theme, Vector, mouse}; use oa_core::Id as ModelId; use std::collections::BTreeMap; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) struct Stamp { document: ModelId, owner: ModelId, revision: u64, session: ModelId, mount: ModelId, } impl Stamp { pub(crate) fn new(context: Context, session: ModelId, mount: ModelId) -> Self { Self { document: context.document, owner: context.scenario, revision: context.revision, session, mount, } } } pub(crate) fn name_id(stamp: Stamp) -> Id { Id::from(format!( "{}:{}:{}:{}:{}:{}", super::NAME_INPUT, stamp.document, stamp.owner, stamp.revision, stamp.session, stamp.mount )) } struct Owner(Option); struct Root<'a, Message, R: renderer::Renderer> { content: Element<'a, Message, Theme, R>, owner: Option, } pub(crate) fn root<'a, Message: 'a, R: renderer::Renderer + 'a>( content: impl Into>, owner: Option, ) -> Element<'a, Message, Theme, R> { Element::new(Root { content: content.into(), owner, }) } impl Widget for Root<'_, Message, R> { fn children(&self) -> Vec { vec![Tree::new(self.content.as_widget())] } fn diff(&self, tree: &mut Tree) { tree.diff_children(std::slice::from_ref(&self.content)); } fn size(&self) -> Size { self.content.as_widget().size() } fn layout(&mut self, tree: &mut 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 Tree, layout: Layout<'_>, renderer: &R, op: &mut dyn Operation, ) { // This is computed from the current App view, never retained widget state. op.custom(None, layout.bounds(), &mut Owner(self.owner)); self.content .as_widget_mut() .operate(&mut tree.children[0], layout, renderer, op); } fn update( &mut self, tree: &mut Tree, event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &R, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) { self.content.as_widget_mut().update( &mut tree.children[0], event, layout, cursor, renderer, clipboard, shell, viewport, ); } fn draw( &self, tree: &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: &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 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, ) } } #[derive(Clone)] struct Scroll { index: usize, bounds: Rectangle, capacity: Vector, offset: Vector, } struct Collect { stamp: Stamp, valid: bool, translation: Vector, scrolls: Vec, pending: Option, index: usize, target: Option<(Rectangle, Vec)>, } pub(crate) fn task(stamp: Stamp) -> Task { iced::advanced::widget::operate(Collect { stamp, valid: false, translation: Vector::ZERO, scrolls: vec![], pending: None, index: 0, target: None, }) .discard() } impl Operation for Collect { fn custom(&mut self, _: Option<&Id>, _: Rectangle, state: &mut dyn std::any::Any) { if let Some(owner) = state.downcast_ref::() { self.valid = owner.0 == Some(self.stamp); } } fn traverse(&mut self, visit: &mut dyn FnMut(&mut dyn Operation)) { let previous = (self.translation, self.scrolls.len()); if let Some(scroll) = self.pending.take() { self.translation += scroll.offset; self.scrolls.push(scroll); } visit(self); self.translation = previous.0; self.scrolls.truncate(previous.1); } fn scrollable( &mut self, _: Option<&Id>, bounds: Rectangle, content: Rectangle, offset: Vector, _: &mut dyn operation::Scrollable, ) { self.pending = Some(Scroll { index: self.index, bounds: translated(bounds, self.translation), capacity: Vector::new( (content.width - bounds.width).max(0.), (content.height - bounds.height).max(0.), ), offset, }); self.index += 1; } fn focusable(&mut self, id: Option<&Id>, bounds: Rectangle, _: &mut dyn operation::Focusable) { if id == Some(&name_id(self.stamp)) { self.target = Some((translated(bounds, self.translation), self.scrolls.clone())); } } fn finish(&self) -> operation::Outcome<()> { if !self.valid { return operation::Outcome::None; } let Some((mut bounds, ancestors)) = self.target.clone() else { return operation::Outcome::None; }; let mut shifts = BTreeMap::new(); for ancestor in ancestors.iter().rev() { let dx = reveal( bounds.x, bounds.width, ancestor.bounds.x, ancestor.bounds.width, ); let dy = reveal( bounds.y, bounds.height, ancestor.bounds.y, ancestor.bounds.height, ); // Drawing offsets are rounded; an already-visible axis must not // acquire a scroll delta from a fractional capacity alone. let delta = Vector::new( if dx == 0. { 0. } else { (ancestor.offset.x + dx).clamp(0., ancestor.capacity.x) - ancestor.offset.x }, if dy == 0. { 0. } else { (ancestor.offset.y + dy).clamp(0., ancestor.capacity.y) - ancestor.offset.y }, ); if delta != Vector::ZERO { shifts.insert(ancestor.index, delta); } bounds = translated(bounds, delta); } operation::Outcome::Chain(Box::new(Apply { stamp: self.stamp, valid: false, shifts, index: 0, })) } } fn translated(bounds: Rectangle, delta: Vector) -> Rectangle { Rectangle { x: bounds.x - delta.x, y: bounds.y - delta.y, ..bounds } } fn reveal(start: f32, length: f32, viewport: f32, size: f32) -> f32 { if start < viewport || length > size { start - viewport } else { (start + length - viewport - size).max(0.) } } struct Apply { stamp: Stamp, valid: bool, shifts: BTreeMap, index: usize, } impl Operation for Apply { fn custom(&mut self, _: Option<&Id>, _: Rectangle, state: &mut dyn std::any::Any) { if let Some(owner) = state.downcast_ref::() { self.valid = owner.0 == Some(self.stamp); } } fn traverse(&mut self, visit: &mut dyn FnMut(&mut dyn Operation)) { visit(self); } fn focusable(&mut self, id: Option<&Id>, _: Rectangle, state: &mut dyn operation::Focusable) { if self.valid { if id == Some(&name_id(self.stamp)) { state.focus(); } else { state.unfocus(); } } } fn scrollable( &mut self, _: Option<&Id>, bounds: Rectangle, content: Rectangle, _: Vector, state: &mut dyn operation::Scrollable, ) { if self.valid && let Some(delta) = self.shifts.get(&self.index) { state.scroll_by( operation::scrollable::AbsoluteOffset { x: delta.x, y: delta.y, }, bounds, content, ); } self.index += 1; } }