-
Notifications
You must be signed in to change notification settings - Fork 123
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2ae7326
commit dcea01a
Showing
21 changed files
with
286 additions
and
249 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
// Copyright 2024 the Xilem Authors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
//! The layout pass, which provides the size and position of each widget | ||
//! before any translations applied in [`compose`](crate::passes::compose). | ||
//! Most of the logic for this pass happens in [`Widget::layout`] implementations. | ||
use tracing::{info_span, trace}; | ||
use vello::kurbo::{Point, Rect, Size}; | ||
|
||
use crate::render_root::RenderRoot; | ||
use crate::widget::WidgetState; | ||
use crate::{BoxConstraints, LayoutCtx, Widget, WidgetPod}; | ||
|
||
// TODO - Replace with contains_rect once new Kurbo version is released. | ||
// See https://github.com/linebender/kurbo/pull/347 | ||
/// Return `true` if all of `smaller` is within `larger`. | ||
fn rect_contains(larger: &Rect, smaller: &Rect) -> bool { | ||
smaller.x0 >= larger.x0 | ||
&& smaller.x1 <= larger.x1 | ||
&& smaller.y0 >= larger.y0 | ||
&& smaller.y1 <= larger.y1 | ||
} | ||
|
||
// Returns "true" if the Widget's layout method was called, in which case debug checks | ||
// need to be run. (See 'called_widget' in WidgetPod::call_widget_method_with_checks) | ||
pub(crate) fn run_layout_inner<W: Widget>( | ||
parent_ctx: &mut LayoutCtx<'_>, | ||
pod: &mut WidgetPod<W>, | ||
bc: &BoxConstraints, | ||
) -> bool { | ||
let id = pod.id().to_raw(); | ||
let widget_mut = parent_ctx | ||
.widget_children | ||
.get_child_mut(id) | ||
.expect("WidgetPod: inner widget not found in widget tree"); | ||
let mut state_mut = parent_ctx | ||
.widget_state_children | ||
.get_child_mut(id) | ||
.expect("WidgetPod: inner widget not found in widget tree"); | ||
let widget = widget_mut.item; | ||
let state = state_mut.item; | ||
|
||
if state.is_stashed { | ||
debug_panic!( | ||
"Error in '{}' #{}: trying to compute layout of stashed widget.", | ||
widget.short_type_name(), | ||
id, | ||
); | ||
state.size = Size::ZERO; | ||
return false; | ||
} | ||
|
||
// TODO - Not everything that has been re-laid out needs to be repainted. | ||
state.needs_paint = true; | ||
state.needs_compose = true; | ||
state.needs_accessibility = true; | ||
state.request_paint = true; | ||
state.request_compose = true; | ||
state.request_accessibility = true; | ||
|
||
bc.debug_check(widget.short_type_name()); | ||
trace!("Computing layout with constraints {:?}", bc); | ||
|
||
state.local_paint_rect = Rect::ZERO; | ||
|
||
let new_size = { | ||
let mut inner_ctx = LayoutCtx { | ||
widget_state: state, | ||
widget_state_children: state_mut.children.reborrow_mut(), | ||
widget_children: widget_mut.children, | ||
global_state: parent_ctx.global_state, | ||
mouse_pos: parent_ctx.mouse_pos, | ||
}; | ||
|
||
// TODO - If constraints are the same and request_layout isn't set, | ||
// skip calling layout | ||
inner_ctx.widget_state.request_layout = false; | ||
widget.layout(&mut inner_ctx, bc) | ||
}; | ||
if state.request_layout { | ||
debug_panic!( | ||
"Error in '{}' #{}: layout request flag was set during layout pass", | ||
widget.short_type_name(), | ||
id, | ||
); | ||
} | ||
|
||
state.needs_layout = false; | ||
state.is_expecting_place_child_call = true; | ||
|
||
state.local_paint_rect = state | ||
.local_paint_rect | ||
.union(new_size.to_rect() + state.paint_insets); | ||
|
||
#[cfg(debug_assertions)] | ||
{ | ||
for child_id in widget.children_ids() { | ||
let child_id = child_id.to_raw(); | ||
let child_state_mut = state_mut | ||
.children | ||
.get_child_mut(child_id) | ||
.unwrap_or_else(|| panic!("widget #{child_id} not found")); | ||
let child_state = child_state_mut.item; | ||
if child_state.is_expecting_place_child_call { | ||
debug_panic!( | ||
"Error in '{}' #{}: missing call to place_child method for child widget '{}' #{}. During layout pass, if a widget calls WidgetPod::layout() on its child, it then needs to call LayoutCtx::place_child() on the same child.", | ||
widget.short_type_name(), | ||
id, | ||
child_state.widget_name, | ||
child_state.id.to_raw(), | ||
); | ||
} | ||
|
||
// TODO - This check might be redundant with the code updating local_paint_rect | ||
let child_rect = child_state.paint_rect(); | ||
if !rect_contains(&state.local_paint_rect, &child_rect) && !state.is_portal { | ||
debug_panic!( | ||
"Error in '{}' #{}: paint_rect {:?} doesn't contain paint_rect {:?} of child widget '{}' #{}", | ||
widget.short_type_name(), | ||
id, | ||
state.local_paint_rect, | ||
child_rect, | ||
child_state.widget_name, | ||
child_state.id.to_raw(), | ||
); | ||
} | ||
} | ||
} | ||
|
||
// TODO - Figure out how to deal with the overflow problem, eg: | ||
// What happens if a widget returns a size larger than the allowed constraints? | ||
// Some possibilities are: | ||
// - Always clip: might be expensive | ||
// - Display it anyway: might lead to graphical bugs | ||
// - Panic: too harsh? | ||
// Also, we need to avoid spurious crashes when we initialize the app and the | ||
// size is (0,0) | ||
// See https://github.com/linebender/xilem/issues/377 | ||
|
||
let state_mut = parent_ctx | ||
.widget_state_children | ||
.get_child_mut(id) | ||
.expect("WidgetPod: inner widget not found in widget tree"); | ||
parent_ctx.widget_state.merge_up(state_mut.item); | ||
state_mut.item.size = new_size; | ||
|
||
{ | ||
if new_size.width.is_infinite() { | ||
debug_panic!( | ||
"Error in '{}' #{}: width is infinite", | ||
widget.short_type_name(), | ||
id, | ||
); | ||
} | ||
if new_size.height.is_infinite() { | ||
debug_panic!( | ||
"Error in '{}' #{}: height is infinite", | ||
widget.short_type_name(), | ||
id, | ||
); | ||
} | ||
} | ||
|
||
true | ||
} | ||
|
||
/// Run [`Widget::layout`] method on the widget contained in `pod`. | ||
/// This will be called by [`LayoutCtx::run_layout`], which is itself called in the parent widget's `layout`. | ||
pub(crate) fn run_layout_on<W: Widget>( | ||
parent_ctx: &mut LayoutCtx<'_>, | ||
pod: &mut WidgetPod<W>, | ||
bc: &BoxConstraints, | ||
) -> Size { | ||
pod.call_widget_method_with_checks( | ||
"layout", | ||
parent_ctx, | ||
|ctx| { | ||
( | ||
ctx.widget_state_children.reborrow(), | ||
ctx.widget_children.reborrow(), | ||
) | ||
}, | ||
|child, ctx| run_layout_inner(ctx, child, bc), | ||
); | ||
|
||
let id = pod.id().to_raw(); | ||
let state_mut = parent_ctx | ||
.widget_state_children | ||
.get_child_mut(id) | ||
.expect("run_layout_on: inner widget not found in widget tree"); | ||
state_mut.item.size | ||
} | ||
|
||
pub(crate) fn root_layout( | ||
root: &mut RenderRoot, | ||
synthetic_root_state: &mut WidgetState, | ||
bc: &BoxConstraints, | ||
) -> Size { | ||
let _span = info_span!("layout").entered(); | ||
|
||
let mouse_pos = root.last_mouse_pos.map(|pos| (pos.x, pos.y).into()); | ||
let root_state_token = root.widget_arena.widget_states.root_token_mut(); | ||
let root_widget_token = root.widget_arena.widgets.root_token_mut(); | ||
let mut ctx = LayoutCtx { | ||
global_state: &mut root.state, | ||
widget_state: synthetic_root_state, | ||
widget_state_children: root_state_token, | ||
widget_children: root_widget_token, | ||
mouse_pos, | ||
}; | ||
|
||
let size = run_layout_on(&mut ctx, &mut root.root, bc); | ||
ctx.place_child(&mut root.root, Point::ORIGIN); | ||
|
||
size | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.