refactor: include virtual offset in Layout

This commit is contained in:
Ashley Wulber 2025-03-19 23:11:48 -04:00
parent 7120db60ba
commit 5b0468e535
No known key found for this signature in database
GPG key ID: 5216D4F46A90A820
10 changed files with 251 additions and 98 deletions

View file

@ -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

View file

@ -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,

View file

@ -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<overlay::Element<'b, Message, Theme, Renderer>> {
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,

View file

@ -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,
);

View file

@ -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<overlay::Element<'b, Message, Theme, Renderer>> {
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,
);

View file

@ -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,
);
}
}

View file

@ -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::<Vec<_>>();
@ -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,

View file

@ -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,

View file

@ -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,
);

View file

@ -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::<State>();
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,
);