Skip to content

Commit

Permalink
refactor(core): 💡 Remove the Layouter, render widget perform layout…
Browse files Browse the repository at this point in the history
… more flexibly

We no longer need to perform child layout one by one, as the structure
of the widget tree is immutable during the layout phase.

BREAKING CHANGE: 🧨 render widget needs to adjust the APIs used accordingly.
  • Loading branch information
M-Adoo committed Sep 15, 2024
1 parent cc0aad8 commit 19ca5eb
Show file tree
Hide file tree
Showing 21 changed files with 200 additions and 295 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he

## [@Unreleased] - @ReleaseDate

### Changed

- **core**: Refactor the `LayoutCtx` to eliminate the need for performing layout based on children order. (#625 @M-Adoo)

### Breaking

- **core**: The `Layouter` has been removed, so the render widget needs to adjust the APIs used accordingly. (#625, @M-Adoo)

## [0.4.0-alpha.8] - 2024-09-11

### Features
Expand Down
12 changes: 6 additions & 6 deletions core/src/builtin_widgets/align.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,11 @@ impl Render for HAlignWidget {
} else {
clamp.min.width = 0.;
}
let child = ctx.assert_single_child();
let child_size = ctx.perform_child_layout(child, clamp);
let box_width = clamp.max.width;
let mut layouter = ctx.assert_single_child_layouter();
let child_size = layouter.perform_widget_layout(clamp);
let x = align.align_value(child_size.width, box_width);
layouter.update_position(Point::new(x, 0.));
ctx.update_position(child, Point::new(x, 0.));
Size::new(box_width, child_size.height)
}

Expand All @@ -103,17 +103,17 @@ impl Render for HAlignWidget {

impl Render for VAlignWidget {
fn perform_layout(&self, mut clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size {
let mut layouter = ctx.assert_single_child_layouter();
let align: Align = self.v_align.into();
if align == Align::Stretch {
clamp.min.height = clamp.max.height;
} else {
clamp.min.height = 0.;
}
let child = ctx.assert_single_child();
let child_size = ctx.perform_child_layout(child, clamp);
let box_height = clamp.max.height;
let child_size = layouter.perform_widget_layout(clamp);
let y = align.align_value(child_size.height, box_height);
layouter.update_position(Point::new(0., y));
ctx.update_position(child, Point::new(0., y));
Size::new(child_size.width, box_height)
}

Expand Down
6 changes: 3 additions & 3 deletions core/src/builtin_widgets/anchor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,8 @@ impl Declare for RelativeAnchor {

impl Render for RelativeAnchor {
fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size {
let mut layouter = ctx.assert_single_child_layouter();
let child_size = layouter.perform_widget_layout(clamp);
let child = ctx.assert_single_child();
let child_size = ctx.perform_child_layout(child, clamp);

let Anchor { x, y } = self.anchor;
let x = x
Expand All @@ -185,7 +185,7 @@ impl Render for RelativeAnchor {
})
.unwrap_or_default();

layouter.update_position(Point::new(x, y));
ctx.update_position(child, Point::new(x, y));
child_size
}

Expand Down
7 changes: 2 additions & 5 deletions core/src/builtin_widgets/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,8 @@ pub struct Container {

impl Render for Container {
fn perform_layout(&self, mut clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size {
if let Some(mut l) = ctx.single_child_layouter() {
clamp.max = clamp.max.min(self.size);
clamp.min = clamp.max.min(clamp.min);
l.perform_widget_layout(clamp);
};
clamp.max = clamp.max.min(self.size);
ctx.perform_single_child_layout(clamp);
self.size
}

Expand Down
6 changes: 3 additions & 3 deletions core/src/builtin_widgets/margin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ impl Render for Margin {
let max = (clamp.max - thickness).max(zero);
let child_clamp = BoxClamp { min, max };

let mut layouter = ctx.assert_single_child_layouter();
let size = layouter.perform_widget_layout(child_clamp);
layouter.update_position(Point::new(self.margin.left, self.margin.top));
let child = ctx.assert_single_child();
let size = ctx.perform_child_layout(child, child_clamp);
ctx.update_position(child, Point::new(self.margin.left, self.margin.top));

size + thickness
}
Expand Down
19 changes: 9 additions & 10 deletions core/src/builtin_widgets/padding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,23 @@ impl Render for Padding {
// Shrink the clamp of child.
let child_clamp = BoxClamp { min, max };
ctx.force_child_relayout(child);
let mut child_layouter = ctx.assert_single_child_layouter();
let child = ctx.assert_single_child();

let mut size = child_layouter.perform_widget_layout(child_clamp);
if child_layouter.has_child() {
let mut size = ctx.perform_child_layout(child, child_clamp);
if child.first_child(ctx.tree).is_some() {
// Expand the size, so the child have padding.
size = clamp.clamp(size + thickness);
child_layouter.update_size(child, size);
ctx.update_size(child, size);

// Update child's children position, let they have a correct position after
// expanded with padding. padding.
let mut grandson_layouter = child_layouter.into_first_child_layouter();
while let Some(mut l) = grandson_layouter {
if let Some(pos) = l.box_pos() {
let mut ctx = LayoutCtx { id: child, tree: ctx.tree };
let (ctx, grandson) = ctx.split_children();
for g in grandson {
if let Some(pos) = ctx.widget_box_pos(g) {
let pos = pos + Vector::new(self.padding.left, self.padding.top);
l.update_position(pos);
ctx.update_position(g, pos);
}

grandson_layouter = l.into_next_sibling()
}
}

Expand Down
4 changes: 2 additions & 2 deletions core/src/builtin_widgets/void.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ pub struct Void;
impl Render for Void {
fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size {
ctx
.single_child_layouter()
.map_or_else(Size::zero, |mut l| l.perform_widget_layout(clamp))
.single_child()
.map_or_else(Size::zero, |c| ctx.perform_child_layout(c, clamp))
}

fn paint(&self, _: &mut PaintingCtx) {}
Expand Down
103 changes: 69 additions & 34 deletions core/src/context/layout_ctx.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use ribir_geom::Size;
use ribir_geom::{Point, Size};

use super::{WidgetCtx, WidgetCtxImpl};
use crate::{
widget::{BoxClamp, Layouter, WidgetTree},
widget::{BoxClamp, WidgetTree},
widget_tree::WidgetId,
window::DelayEvent,
};

/// A place to compute the render object's layout. Rather than holding children
Expand All @@ -26,15 +27,77 @@ impl<'a> WidgetCtxImpl for LayoutCtx<'a> {
}

impl<'a> LayoutCtx<'a> {
/// Perform layout of the child widget referenced, resetting the widget
/// position to (0, 0) relative to the parent if its position is not set by
/// its layout logic, and return the resulting size after layout.
pub fn perform_child_layout(&mut self, child: WidgetId, clamp: BoxClamp) -> Size {
let info = self.tree.store.layout_info(child);
let size = info
.filter(|info| info.clamp == clamp)
.and_then(|info| info.size)
.unwrap_or_else(|| {
// Safety: the `tree` just use to get the widget of `id`, and `tree2` not drop
// or modify it during perform layout.
let tree2 = unsafe { &mut *(self.tree as *mut WidgetTree) };
let mut ctx = LayoutCtx { id: child, tree: tree2 };
let size = child
.assert_get(self.tree)
.perform_layout(clamp, &mut ctx);

self
.window()
.add_delay_event(DelayEvent::PerformedLayout(child));

let info = tree2.store.layout_info_or_default(child);
let size = clamp.clamp(size);
info.clamp = clamp;
info.size = Some(size);

size
});

if child != self.tree.root() && self.widget_box_pos(child).is_none() {
self.update_position(child, Point::zero())
}

size
}

/// Adjust the position of the widget where it should be placed relative to
/// its parent.
#[inline]
pub fn update_position(&mut self, child: WidgetId, pos: Point) {
self.tree.store.layout_info_or_default(child).pos = pos;
}

/// Adjust the size of the layout widget. Use this method to directly modify
/// the size of a widget. In most cases, it is unnecessary to call this
/// method; using clamp to constrain the child size is typically sufficient.
/// Only use this method if you are certain of its effects.
#[inline]
pub fn update_size(&mut self, child: WidgetId, size: Size) {
self.tree.store.layout_info_or_default(child).size = Some(size);
}

/// Split a children iterator from the context, returning a tuple of `&mut
/// LayoutCtx` and the iterator of the children.
pub fn split_children(&mut self) -> (&mut Self, impl Iterator<Item = WidgetId> + '_) {
// Safety: The widget tree structure is immutable during the layout phase, so we
// can safely split an iterator of children from the layout.
let tree = unsafe { &*(self.tree as *mut WidgetTree) };
let id = self.id;
(self, id.children(tree))
}

/// Quick method to do the work of computing the layout for the single child,
/// and return its size it should have.
///
/// # Panic
/// panic if there are more than one child it have.
pub fn perform_single_child_layout(&mut self, clamp: BoxClamp) -> Option<Size> {
self
.single_child_layouter()
.map(|mut l| l.perform_widget_layout(clamp))
.single_child()
.map(|child| self.perform_child_layout(child, clamp))
}

/// Quick method to do the work of computing the layout for the single child,
Expand All @@ -43,31 +106,8 @@ impl<'a> LayoutCtx<'a> {
/// # Panic
/// panic if there is not only one child it have.
pub fn assert_perform_single_child_layout(&mut self, clamp: BoxClamp) -> Size {
self
.assert_single_child_layouter()
.perform_widget_layout(clamp)
}

/// Return the layouter of the first child.
pub fn first_child_layouter(&mut self) -> Option<Layouter> {
self
.first_child()
.map(|wid| self.new_layouter(wid))
}

/// Return the layouter of the first child.
pub fn single_child_layouter(&mut self) -> Option<Layouter> {
self
.single_child()
.map(|wid| self.new_layouter(wid))
}

/// Return the layouter of the first child.
/// # Panic
/// panic if there is not only one child it have.
pub fn assert_single_child_layouter(&mut self) -> Layouter {
let wid = self.assert_single_child();
self.new_layouter(wid)
let child = self.assert_single_child();
self.perform_child_layout(child, clamp)
}

/// Clear the child layout information, so the `child` will be force layout
Expand All @@ -78,9 +118,4 @@ impl<'a> LayoutCtx<'a> {
assert_eq!(child.parent(self.tree), Some(self.id));
self.tree.store.force_layout(child).is_some()
}

pub(crate) fn new_layouter(&mut self, id: WidgetId) -> Layouter {
let LayoutCtx { tree, .. } = self;
Layouter::new(id, false, tree)
}
}
18 changes: 11 additions & 7 deletions core/src/context/widget_ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ pub trait WidgetCtx {
fn widget_box_size(&self, wid: WidgetId) -> Option<Size>;
/// Return the box rect of the widget `wid` point to.
fn widget_box_rect(&self, wid: WidgetId) -> Option<Rect>;
/// Return the position of the widget that `wid` references.
fn widget_box_pos(&self, wid: WidgetId) -> Option<Point>;
/// Translates the global window coordinate pos to widget coordinates.
fn map_to_global(&self, pos: Point) -> Point;
/// Translates the global screen coordinate pos to widget coordinates.
Expand Down Expand Up @@ -121,13 +123,7 @@ impl<T: WidgetCtxImpl> WidgetCtx for T {
fn box_rect(&self) -> Option<Rect> { self.widget_box_rect(self.id()) }

#[inline]
fn box_pos(&self) -> Option<Point> {
self
.tree()
.store
.layout_info(self.id())
.map(|info| info.pos)
}
fn box_pos(&self) -> Option<Point> { self.widget_box_pos(self.id()) }

#[inline]
fn box_size(&self) -> Option<Size> { self.widget_box_size(self.id()) }
Expand All @@ -154,6 +150,14 @@ impl<T: WidgetCtxImpl> WidgetCtx for T {
.and_then(|info| info.size)
}

fn widget_box_pos(&self, wid: WidgetId) -> Option<Point> {
self
.tree()
.store
.layout_info(wid)
.map(|info| info.pos)
}

fn widget_box_rect(&self, wid: WidgetId) -> Option<Rect> {
self
.tree()
Expand Down
2 changes: 1 addition & 1 deletion core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ pub mod prelude {
#[doc(no_inline)]
pub use crate::widget_children::*;
#[doc(no_inline)]
pub use crate::widget_tree::{BoxClamp, LayoutInfo, Layouter, WidgetId};
pub use crate::widget_tree::{BoxClamp, LayoutInfo, WidgetId};
#[doc(no_inline)]
pub use crate::window::Window;
pub use crate::{
Expand Down
23 changes: 9 additions & 14 deletions core/src/test_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,21 +204,17 @@ pub struct MockStack {

impl Render for MockStack {
fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size {
let mut layouter = ctx.first_child_layouter();
let mut size = ZERO_SIZE;
let mut i = 0;
while let Some(mut l) = layouter {
let mut child_size = l.perform_widget_layout(clamp);
let (ctx, children) = ctx.split_children();
for (i, c) in children.enumerate() {
let mut child_size = ctx.perform_child_layout(c, clamp);
if let Some(offset) = self.child_pos.get(i) {
l.update_position(*offset);
ctx.update_position(c, *offset);
child_size = Size::new(offset.x + child_size.width, offset.y + child_size.height);
} else {
l.update_position(Point::zero());
ctx.update_position(c, Point::zero());
}
size = size.max(child_size);
layouter = l.into_next_sibling();

i += 1;
}

size
Expand All @@ -236,14 +232,13 @@ pub struct MockBox {

impl Render for MockMulti {
fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size {
let mut layouter = ctx.first_child_layouter();
let mut size = ZERO_SIZE;
while let Some(mut l) = layouter {
let child_size = l.perform_widget_layout(clamp);
l.update_position(Point::new(size.width, 0.));
let (ctx, children) = ctx.split_children();
for c in children {
let child_size = ctx.perform_child_layout(c, clamp);
ctx.update_position(c, Point::new(size.width, 0.));
size.width += child_size.width;
size.height = size.height.max(child_size.height);
layouter = l.into_next_sibling();
}

size
Expand Down
Loading

0 comments on commit 19ca5eb

Please sign in to comment.