From 5b0468e5352561f53c1ec7808f3d19f1756fbe6a Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 19 Mar 2025 23:11:48 -0400 Subject: [PATCH] refactor: include virtual offset in Layout --- core/src/layout.rs | 16 +++++++++ core/src/overlay.rs | 4 +-- widget/src/button.rs | 33 ++++++++++++----- widget/src/column.rs | 53 +++++++++++++++++++-------- widget/src/container.rs | 38 ++++++++++++++++---- widget/src/keyed/column.rs | 26 ++++++++------ widget/src/pane_grid.rs | 44 ++++++++++++++++------- widget/src/pane_grid/title_bar.rs | 50 +++++++++++++++++--------- widget/src/row.rs | 26 ++++++++------ widget/src/scrollable.rs | 59 +++++++++++++++++++++++-------- 10 files changed, 251 insertions(+), 98 deletions(-) diff --git a/core/src/layout.rs b/core/src/layout.rs index b04d27dc..868e0184 100644 --- a/core/src/layout.rs +++ b/core/src/layout.rs @@ -12,6 +12,9 @@ use crate::{Length, Padding, Point, Rectangle, Size, Vector}; /// The bounds of a [`Node`] and its children, using absolute coordinates. #[derive(Debug, Clone, Copy)] pub struct Layout<'a> { + /// The virtual offset of the layout. + /// May represent the scroll positions in pixels of a scrollable, for example. + virtual_offset: Vector, position: Point, node: &'a Node, } @@ -28,16 +31,29 @@ impl<'a> Layout<'a> { let bounds = node.bounds(); Self { + virtual_offset: Vector::new(0., 0.), position: Point::new(bounds.x, bounds.y) + offset, node, } } + /// Returns a new layout with the virtual offset + pub fn with_virtual_offset(mut self, virtual_offset: Vector) -> Self { + self.virtual_offset = virtual_offset; + self + } + /// Returns the position of the [`Layout`]. pub fn position(&self) -> Point { self.position } + /// The virtual offset of the layout. + /// May represent the scroll positions in pixels of a scrollable, for example. + pub fn virtual_offset(&self) -> Vector { + self.virtual_offset + } + /// Returns the bounds of the [`Layout`]. /// /// The returned [`Rectangle`] describes the position and size of a diff --git a/core/src/overlay.rs b/core/src/overlay.rs index ed513cb3..41739e27 100644 --- a/core/src/overlay.rs +++ b/core/src/overlay.rs @@ -120,10 +120,10 @@ where .iter_mut() .zip(&mut tree.children) .zip(layout.children()) - .filter_map(|((child, state), layout)| { + .filter_map(|((child, state), c_layout)| { child.as_widget_mut().overlay( state, - layout, + c_layout.with_virtual_offset(layout.virtual_offset()), renderer, viewport, translation, diff --git a/widget/src/button.rs b/widget/src/button.rs index 74490da8..f137221e 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -331,7 +331,11 @@ where operation.traverse(&mut |operation| { self.content.as_widget_mut().operate( &mut tree.children[0], - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), renderer, operation, ); @@ -352,7 +356,11 @@ where self.content.as_widget_mut().update( &mut tree.children[0], event, - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), cursor, renderer, clipboard, @@ -472,7 +480,11 @@ where viewport: &Rectangle, ) { let bounds = layout.bounds(); - let content_layout = layout.children().next().unwrap(); + let content_layout = layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()); let style = theme.style(&self.class, self.status.unwrap_or(Status::Disabled)); @@ -543,7 +555,11 @@ where ) -> Option> { self.content.as_widget_mut().overlay( &mut tree.children[0], - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), renderer, viewport, translation, @@ -565,10 +581,11 @@ where let child_layout = layout.children().next().unwrap(); let child_tree = &state.children[0]; - let child_tree = - self.content - .as_widget() - .a11y_nodes(child_layout, child_tree, p); + let child_tree = self.content.as_widget().a11y_nodes( + child_layout.with_virtual_offset(layout.virtual_offset()), + child_tree, + p, + ); let Rectangle { x, diff --git a/widget/src/column.rs b/widget/src/column.rs index bb55954d..ae4beed2 100644 --- a/widget/src/column.rs +++ b/widget/src/column.rs @@ -250,10 +250,13 @@ where .iter_mut() .zip(&mut tree.children) .zip(layout.children()) - .for_each(|((child, state), layout)| { - child - .as_widget_mut() - .operate(state, layout, renderer, operation); + .for_each(|((child, state), c_layout)| { + child.as_widget_mut().operate( + state, + c_layout.with_virtual_offset(layout.virtual_offset()), + renderer, + operation, + ); }); }); } @@ -269,14 +272,20 @@ where shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) { - for ((child, tree), layout) in self + for ((child, tree), c_layout) in self .children .iter_mut() .zip(&mut tree.children) .zip(layout.children()) { child.as_widget_mut().update( - tree, event, layout, cursor, renderer, clipboard, shell, + tree, + event, + c_layout.with_virtual_offset(layout.virtual_offset()), + cursor, + renderer, + clipboard, + shell, viewport, ); } @@ -294,10 +303,14 @@ where .iter() .zip(&tree.children) .zip(layout.children()) - .map(|((child, tree), layout)| { - child - .as_widget() - .mouse_interaction(tree, layout, cursor, viewport, renderer) + .map(|((child, tree), c_layout)| { + child.as_widget().mouse_interaction( + tree, + c_layout.with_virtual_offset(layout.virtual_offset()), + cursor, + viewport, + renderer, + ) }) .max() .unwrap_or_default() @@ -320,7 +333,7 @@ where viewport }; - for ((child, tree), layout) in self + for ((child, tree), c_layout) in self .children .iter() .zip(&tree.children) @@ -328,7 +341,13 @@ where .filter(|(_, layout)| layout.bounds().intersects(viewport)) { child.as_widget().draw( - tree, renderer, theme, style, layout, cursor, viewport, + tree, + renderer, + theme, + style, + c_layout.with_virtual_offset(layout.virtual_offset()), + cursor, + viewport, ); } } @@ -367,7 +386,11 @@ where .zip(layout.children()) .zip(state.children.iter()) .map(|((c, c_layout), state)| { - c.as_widget().a11y_nodes(c_layout, state, cursor) + c.as_widget().a11y_nodes( + c_layout.with_virtual_offset(layout.virtual_offset()), + state, + cursor, + ) }), ) } @@ -379,7 +402,7 @@ where renderer: &Renderer, dnd_rectangles: &mut crate::core::clipboard::DndDestinationRectangles, ) { - for ((e, layout), state) in self + for ((e, c_layout), state) in self .children .iter() .zip(layout.children()) @@ -387,7 +410,7 @@ where { e.as_widget().drag_destinations( state, - layout, + c_layout.with_virtual_offset(layout.virtual_offset()), renderer, dnd_rectangles, ); diff --git a/widget/src/container.rs b/widget/src/container.rs index 6f3280ce..c9a300c9 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -290,7 +290,11 @@ where operation.traverse(&mut |operation| { self.content.as_widget_mut().operate( tree, - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), renderer, operation, ); @@ -311,7 +315,11 @@ where self.content.as_widget_mut().update( tree, event, - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), cursor, renderer, clipboard, @@ -330,7 +338,11 @@ where ) -> mouse::Interaction { self.content.as_widget().mouse_interaction( tree, - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), cursor, viewport, renderer, @@ -366,7 +378,11 @@ where .unwrap_or(renderer_style.text_color), scale_factor: renderer_style.scale_factor, }, - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), cursor, if self.clip { &clipped_viewport @@ -387,7 +403,11 @@ where ) -> Option> { self.content.as_widget_mut().overlay( tree, - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), renderer, viewport, translation, @@ -404,7 +424,11 @@ where ) -> iced_accessibility::A11yTree { let c_layout = layout.children().next().unwrap(); - self.content.as_widget().a11y_nodes(c_layout, state, cursor) + self.content.as_widget().a11y_nodes( + c_layout.with_virtual_offset(layout.virtual_offset()), + state, + cursor, + ) } fn drag_destinations( @@ -417,7 +441,7 @@ where if let Some(l) = layout.children().next() { self.content.as_widget().drag_destinations( state, - l, + l.with_virtual_offset(layout.virtual_offset()), renderer, dnd_rectangles, ); diff --git a/widget/src/keyed/column.rs b/widget/src/keyed/column.rs index 99087e68..baaee5c9 100644 --- a/widget/src/keyed/column.rs +++ b/widget/src/keyed/column.rs @@ -292,10 +292,10 @@ where .iter_mut() .zip(&mut tree.children) .zip(layout.children()) - .for_each(|((child, state), layout)| { + .for_each(|((child, state), c_layout)| { child .as_widget_mut() - .operate(state, layout, renderer, operation); + .operate(state, c_layout.with_virtual_offset(layout.virtual_offset()), renderer, operation); }); }); } @@ -311,14 +311,14 @@ where shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) { - for ((child, tree), layout) in self + for ((child, tree), c_layout) in self .children .iter_mut() .zip(&mut tree.children) .zip(layout.children()) { child.as_widget_mut().update( - tree, event, layout, cursor, renderer, clipboard, shell, + tree, event, c_layout.with_virtual_offset(layout.virtual_offset()), cursor, renderer, clipboard, shell, viewport, ); } @@ -336,10 +336,10 @@ where .iter() .zip(&tree.children) .zip(layout.children()) - .map(|((child, tree), layout)| { + .map(|((child, tree), c_layout)| { child .as_widget() - .mouse_interaction(tree, layout, cursor, viewport, renderer) + .mouse_interaction(tree, c_layout.with_virtual_offset(layout.virtual_offset()), cursor, viewport, renderer) }) .max() .unwrap_or_default() @@ -355,15 +355,21 @@ where cursor: mouse::Cursor, viewport: &Rectangle, ) { - for ((child, state), layout) in self + for ((child, state), c_layout) in self .children .iter() .zip(&tree.children) .zip(layout.children()) { - child - .as_widget() - .draw(state, renderer, theme, style, layout, cursor, viewport); + child.as_widget().draw( + state, + renderer, + theme, + style, + c_layout.with_virtual_offset(layout.virtual_offset()), + cursor, + viewport, + ); } } diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index 85a335b5..00fc0855 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -480,8 +480,13 @@ where .maximized() .is_none_or(|maximized| **pane == maximized) }) - .for_each(|(((_, content), state), layout)| { - content.operate(state, layout, renderer, operation); + .for_each(|(((_, content), state), c_layout)| { + content.operate( + state, + c_layout.with_virtual_offset(layout.virtual_offset()), + renderer, + operation, + ); }); }); } @@ -700,9 +705,11 @@ where .maximized() .is_none_or(|maximized| **pane == maximized) }) - .find_map(|((_pane, content), layout)| { + .find_map(|((_pane, content), c_layout)| { content.grid_interaction( - layout, + c_layout.with_virtual_offset( + layout.virtual_offset(), + ), cursor, on_drag.is_some(), ) @@ -747,10 +754,10 @@ where .maximized() .is_none_or(|maximized| *pane == maximized) }) - .map(|(((_, content), tree), layout)| { + .map(|(((_, content), tree), c_layout)| { content.mouse_interaction( tree, - layout, + c_layout.with_virtual_offset(layout.virtual_offset()), cursor, viewport, renderer, @@ -874,7 +881,8 @@ where renderer, theme, defaults, - pane_layout, + pane_layout + .with_virtual_offset(layout.virtual_offset()), pane_cursor, viewport, ); @@ -904,7 +912,8 @@ where renderer, theme, defaults, - pane_layout, + pane_layout + .with_virtual_offset(layout.virtual_offset()), pane_cursor, viewport, ); @@ -1000,7 +1009,7 @@ where .zip(&mut self.contents) .zip(&mut tree.children) .zip(layout.children()) - .filter_map(|(((pane, content), state), layout)| { + .filter_map(|(((pane, content), state), c_layout)| { if self .internal .maximized() @@ -1009,7 +1018,13 @@ where return None; } - content.overlay(state, layout, renderer, viewport, translation) + content.overlay( + state, + c_layout.with_virtual_offset(layout.virtual_offset()), + renderer, + viewport, + translation, + ) }) .collect::>(); @@ -1066,15 +1081,18 @@ fn click_pane<'a, Message, T>( { let mut clicked_region = contents .zip(layout.children()) - .filter(|(_, layout)| layout.bounds().contains(cursor_position)); + .filter(|(_, c_layout)| layout.bounds().contains(cursor_position)); - if let Some(((pane, content), layout)) = clicked_region.next() { + if let Some(((pane, content), c_layout)) = clicked_region.next() { if let Some(on_click) = &on_click { shell.publish(on_click(pane)); } if let Some(on_drag) = &on_drag - && content.can_be_dragged_at(layout, cursor_position) + && content.can_be_dragged_at( + c_layout.with_virtual_offset(layout.virtual_offset()), + cursor_position, + ) { *action = state::Action::Dragging { pane, diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs index 4181f24d..112b06f8 100644 --- a/widget/src/pane_grid/title_bar.rs +++ b/widget/src/pane_grid/title_bar.rs @@ -190,7 +190,8 @@ where renderer, theme, &inherited_style, - compact_layout, + compact_layout + .with_virtual_offset(layout.virtual_offset()), cursor, viewport, ); @@ -202,7 +203,8 @@ where renderer, theme, &inherited_style, - controls_layout, + controls_layout + .with_virtual_offset(layout.virtual_offset()), cursor, viewport, ); @@ -213,7 +215,8 @@ where renderer, theme, &inherited_style, - controls_layout, + controls_layout + .with_virtual_offset(layout.virtual_offset()), cursor, viewport, ); @@ -226,7 +229,7 @@ where renderer, theme, &inherited_style, - title_layout, + title_layout.with_virtual_offset(layout.virtual_offset()), cursor, viewport, ); @@ -394,7 +397,8 @@ where compact.as_widget_mut().operate( &mut tree.children[2], - compact_layout, + compact_layout + .with_virtual_offset(layout.virtual_offset()), renderer, operation, ); @@ -403,7 +407,8 @@ where controls.full.as_widget_mut().operate( &mut tree.children[1], - controls_layout, + controls_layout + .with_virtual_offset(layout.virtual_offset()), renderer, operation, ); @@ -411,7 +416,8 @@ where } else { controls.full.as_widget_mut().operate( &mut tree.children[1], - controls_layout, + controls_layout + .with_virtual_offset(layout.virtual_offset()), renderer, operation, ); @@ -458,7 +464,8 @@ where compact.as_widget_mut().update( &mut tree.children[2], event, - compact_layout, + compact_layout + .with_virtual_offset(layout.virtual_offset()), cursor, renderer, clipboard, @@ -471,7 +478,8 @@ where controls.full.as_widget_mut().update( &mut tree.children[1], event, - controls_layout, + controls_layout + .with_virtual_offset(layout.virtual_offset()), cursor, renderer, clipboard, @@ -483,7 +491,8 @@ where controls.full.as_widget_mut().update( &mut tree.children[1], event, - controls_layout, + controls_layout + .with_virtual_offset(layout.virtual_offset()), cursor, renderer, clipboard, @@ -497,7 +506,7 @@ where self.content.as_widget_mut().update( &mut tree.children[0], event, - title_layout, + title_layout.with_virtual_offset(layout.virtual_offset()), cursor, renderer, clipboard, @@ -523,7 +532,7 @@ where let title_interaction = self.content.as_widget().mouse_interaction( &tree.children[0], - title_layout, + title_layout.with_virtual_offset(layout.virtual_offset()), cursor, viewport, renderer, @@ -534,7 +543,8 @@ where let controls_interaction = controls.full.as_widget().mouse_interaction( &tree.children[1], - controls_layout, + controls_layout + .with_virtual_offset(layout.virtual_offset()), cursor, viewport, renderer, @@ -548,7 +558,8 @@ where let compact_interaction = compact.as_widget().mouse_interaction( &tree.children[2], - compact_layout, + compact_layout + .with_virtual_offset(layout.virtual_offset()), cursor, viewport, renderer, @@ -605,7 +616,9 @@ where compact.as_widget_mut().overlay( compact_state, - compact_layout, + compact_layout.with_virtual_offset( + layout.virtual_offset(), + ), renderer, viewport, translation, @@ -613,7 +626,9 @@ where } else { controls.full.as_widget_mut().overlay( controls_state, - controls_layout, + controls_layout.with_virtual_offset( + layout.virtual_offset(), + ), renderer, viewport, translation, @@ -622,7 +637,8 @@ where } else { controls.full.as_widget_mut().overlay( controls_state, - controls_layout, + controls_layout + .with_virtual_offset(layout.virtual_offset()), renderer, viewport, translation, diff --git a/widget/src/row.rs b/widget/src/row.rs index 2178f15d..cc7e5620 100644 --- a/widget/src/row.rs +++ b/widget/src/row.rs @@ -239,10 +239,10 @@ where .iter_mut() .zip(&mut tree.children) .zip(layout.children()) - .for_each(|((child, state), layout)| { + .for_each(|((child, state), c_layout)| { child .as_widget_mut() - .operate(state, layout, renderer, operation); + .operate(state, c_layout.with_virtual_offset(layout.virtual_offset()), renderer, operation); }); }); } @@ -258,14 +258,14 @@ where shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) { - for ((child, tree), layout) in self + for ((child, tree), c_layout) in self .children .iter_mut() .zip(&mut tree.children) .zip(layout.children()) { child.as_widget_mut().update( - tree, event, layout, cursor, renderer, clipboard, shell, + tree, event, c_layout.with_virtual_offset(layout.virtual_offset()), cursor, renderer, clipboard, shell, viewport, ); } @@ -283,10 +283,10 @@ where .iter() .zip(&tree.children) .zip(layout.children()) - .map(|((child, tree), layout)| { + .map(|((child, tree), c_layout)| { child .as_widget() - .mouse_interaction(tree, layout, cursor, viewport, renderer) + .mouse_interaction(tree, c_layout.with_virtual_offset(layout.virtual_offset()), cursor, viewport, renderer) }) .max() .unwrap_or_default() @@ -309,7 +309,7 @@ where viewport }; - for ((child, tree), layout) in self + for ((child, tree), c_layout) in self .children .iter() .zip(&tree.children) @@ -317,7 +317,7 @@ where .filter(|(_, layout)| layout.bounds().intersects(viewport)) { child.as_widget().draw( - tree, renderer, theme, style, layout, cursor, viewport, + tree, renderer, theme, style, c_layout.with_virtual_offset(layout.virtual_offset()), cursor, viewport, ); } } @@ -356,7 +356,11 @@ where .zip(layout.children()) .zip(state.children.iter()) .map(|((c, c_layout), state)| { - c.as_widget().a11y_nodes(c_layout, state, cursor) + c.as_widget().a11y_nodes( + c_layout.with_virtual_offset(layout.virtual_offset()), + state, + cursor, + ) }), ) } @@ -368,7 +372,7 @@ where renderer: &Renderer, dnd_rectangles: &mut crate::core::clipboard::DndDestinationRectangles, ) { - for ((e, layout), state) in self + for ((e, c_layout), state) in self .children .iter() .zip(layout.children()) @@ -376,7 +380,7 @@ where { e.as_widget().drag_destinations( state, - layout, + c_layout.with_virtual_offset(layout.virtual_offset()), renderer, dnd_rectangles, ); diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 51b083ad..1213692d 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -616,7 +616,11 @@ where operation.traverse(&mut |operation| { self.content.as_widget_mut().operate( &mut tree.children[0], - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(translation + layout.virtual_offset()), renderer, operation, ); @@ -1278,7 +1282,9 @@ where renderer, theme, defaults, - content_layout, + content_layout.with_virtual_offset( + translation + layout.virtual_offset(), + ), cursor, &Rectangle { y: visible_bounds.y + translation.y, @@ -1382,7 +1388,7 @@ where renderer, theme, defaults, - content_layout, + content_layout.with_virtual_offset(layout.virtual_offset()), cursor, &Rectangle { x: visible_bounds.x + translation.x, @@ -1432,7 +1438,7 @@ where self.content.as_widget().mouse_interaction( &tree.children[0], - content_layout, + content_layout.with_virtual_offset(layout.virtual_offset()), cursor, &Rectangle { y: bounds.y + translation.y, @@ -1460,7 +1466,11 @@ where let overlay = self.content.as_widget_mut().overlay( &mut tree.children[0], - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(translation + layout.virtual_offset()), renderer, &visible_bounds, translation - offset, @@ -1503,15 +1513,9 @@ where A11yId, A11yNode, A11yTree, accesskit::{Node, NodeId, Rect, Role}, }; - - let child_layout = layout.children().next().unwrap(); - let child_tree = &state.children[0]; - let child_tree = self.content.as_widget().a11y_nodes( - child_layout, - &child_tree, - cursor, - ); - + if !matches!(state.state, tree::State::Some(_)) { + return A11yTree::default(); + } let window = layout.bounds(); let is_hovered = cursor.is_over(window); let Rectangle { @@ -1520,6 +1524,25 @@ where width, height, } = window; + + let my_state = state.state.downcast_ref::(); + let content = layout.children().next().unwrap(); + let content_bounds = content.bounds(); + + let translation = my_state.translation( + self.direction, + layout.bounds(), + content_bounds, + ); + + let child_layout = layout.children().next().unwrap(); + let child_tree = &state.children[0]; + let child_tree = self.content.as_widget().a11y_nodes( + child_layout + .with_virtual_offset(translation + layout.virtual_offset()), + &child_tree, + cursor, + ); let bounds = Rect::new( x as f64, y as f64, @@ -1646,9 +1669,15 @@ where layout.children().zip(tree.children.iter()).next() { let mut my_dnd_rectangles = DndDestinationRectangles::new(); + let translation = my_state.translation( + self.direction, + layout.bounds(), + c_layout.bounds(), + ); self.content.as_widget().drag_destinations( c_state, - c_layout, + c_layout + .with_virtual_offset(translation + layout.virtual_offset()), renderer, &mut my_dnd_rectangles, );