mirror of https://github.com/linebender/xilem
Improve bounding rect code (#874)
Document concepts of "layout rect" and "bounding rect". Deprecate `local_layout_rect()` method. Add `global_layout_rect()` method. Remove `transform_changed()` method. Improve bounding rect merging code. Tweak `TestHarness::mouse_move_to`. Tweak WidgetState doc.
This commit is contained in:
parent
60c037dc1f
commit
93e000d9c1
|
@ -632,12 +632,8 @@ impl LayoutCtx<'_> {
|
|||
self.get_child_state(child).baseline_offset
|
||||
}
|
||||
|
||||
/// Get the given child's layout rect.
|
||||
///
|
||||
/// ## Panics
|
||||
///
|
||||
/// This method will panic if [`LayoutCtx::run_layout`] and [`LayoutCtx::place_child`]
|
||||
/// have not been called yet for the child.
|
||||
// TODO - Remove (used in Flex)
|
||||
#[doc(hidden)]
|
||||
#[track_caller]
|
||||
pub fn child_layout_rect(&self, child: &WidgetPod<impl Widget + ?Sized>) -> Rect {
|
||||
self.assert_layout_done(child, "child_layout_rect");
|
||||
|
@ -667,7 +663,7 @@ impl LayoutCtx<'_> {
|
|||
#[track_caller]
|
||||
pub fn child_size(&self, child: &WidgetPod<impl Widget + ?Sized>) -> Size {
|
||||
self.assert_layout_done(child, "child_size");
|
||||
self.get_child_state(child).layout_rect().size()
|
||||
self.get_child_state(child).size
|
||||
}
|
||||
|
||||
/// Skips running the layout pass and calling [`LayoutCtx::place_child`] on the child.
|
||||
|
@ -768,12 +764,9 @@ impl_context_method!(
|
|||
self.widget_state.size
|
||||
}
|
||||
|
||||
// TODO - Remove? A widget doesn't really have a concept of its own "origin",
|
||||
// it's more useful for the parent widget.
|
||||
/// The layout rect of the widget.
|
||||
///
|
||||
/// This is the layout [size](Self::size) and origin (in the parent's coordinate space) combined.
|
||||
pub fn layout_rect(&self) -> Rect {
|
||||
// TODO - Remove
|
||||
#[allow(dead_code, reason = "Only used in tests")]
|
||||
pub(crate) fn local_layout_rect(&self) -> Rect {
|
||||
self.widget_state.layout_rect()
|
||||
}
|
||||
|
||||
|
@ -788,7 +781,10 @@ impl_context_method!(
|
|||
self.widget_state.window_origin()
|
||||
}
|
||||
|
||||
/// The axis aligned bounding rect of this widget in window coordinates.
|
||||
/// The bounding rect of the widget in window coordinates.
|
||||
///
|
||||
/// See [bounding rect documentation](crate::doc::doc_06_masonry_concepts#bounding-rect)
|
||||
/// for details.
|
||||
pub fn bounding_rect(&self) -> Rect {
|
||||
self.widget_state.bounding_rect()
|
||||
}
|
||||
|
@ -1030,13 +1026,6 @@ impl_context_method!(MutateCtx<'_>, EventCtx<'_>, UpdateCtx<'_>, {
|
|||
self.request_layout();
|
||||
}
|
||||
|
||||
/// Indicate that the transform of this widget has changed.
|
||||
pub fn transform_changed(&mut self) {
|
||||
trace!("transform_changed");
|
||||
self.widget_state.transform_changed = true;
|
||||
self.request_compose();
|
||||
}
|
||||
|
||||
/// Indicate that a child is about to be removed from the tree.
|
||||
///
|
||||
/// Container widgets should avoid dropping `WidgetPod`s. Instead, they should
|
||||
|
@ -1073,7 +1062,8 @@ impl_context_method!(MutateCtx<'_>, EventCtx<'_>, UpdateCtx<'_>, {
|
|||
/// It behaves similarly as CSS transforms
|
||||
pub fn set_transform(&mut self, transform: Affine) {
|
||||
self.widget_state.transform = transform;
|
||||
self.transform_changed();
|
||||
self.widget_state.transform_changed = true;
|
||||
self.request_compose();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -96,11 +96,13 @@ pub(crate) struct WidgetState {
|
|||
// efficiently hold an arbitrary shape.
|
||||
pub(crate) clip_path: Option<Rect>,
|
||||
|
||||
/// This is being computed out of all ancestor transforms and `translation`
|
||||
pub(crate) window_transform: Affine,
|
||||
/// Local transform of this widget in the parent coordinate space.
|
||||
pub(crate) transform: Affine,
|
||||
/// translation applied by scrolling, this is applied after applying `transform` to this widget.
|
||||
/// Global transform of this widget in the window coordinate space.
|
||||
///
|
||||
/// Computed from all `transform` and `scroll_translation` values from this to the root widget.
|
||||
pub(crate) window_transform: Affine,
|
||||
/// Translation applied by scrolling, applied after applying `transform` to this widget.
|
||||
pub(crate) scroll_translation: Vec2,
|
||||
/// The `transform` or `scroll_translation` has changed.
|
||||
pub(crate) transform_changed: bool,
|
||||
|
@ -292,6 +294,8 @@ impl WidgetState {
|
|||
///
|
||||
/// By default, returns the same as [`Self::bounding_rect`].
|
||||
pub(crate) fn get_ime_area(&self) -> Rect {
|
||||
// Note: this returns sensible values for a widget that is translated and/or rescaled.
|
||||
// Other transformations like rotation may produce weird IME areas.
|
||||
self.window_transform
|
||||
.transform_rect_bbox(self.ime_area.unwrap_or_else(|| self.size.to_rect()))
|
||||
}
|
||||
|
@ -300,6 +304,24 @@ impl WidgetState {
|
|||
self.window_transform.translation().to_point()
|
||||
}
|
||||
|
||||
/// Return the result of intersecting the widget's clip path (if any) with the given rect.
|
||||
///
|
||||
/// Both the argument and the result are in window coordinates.
|
||||
///
|
||||
/// Returns `None` if the given rect is clipped out.
|
||||
pub(crate) fn clip_child(&self, child_rect: Rect) -> Option<Rect> {
|
||||
if let Some(clip_path) = self.clip_path {
|
||||
let clip_path_global = self.window_transform.transform_rect_bbox(clip_path);
|
||||
if clip_path_global.overlaps(child_rect) {
|
||||
Some(clip_path_global.intersect(child_rect))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
Some(child_rect)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn needs_rewrite_passes(&self) -> bool {
|
||||
self.needs_layout
|
||||
|| self.needs_compose
|
||||
|
|
|
@ -107,6 +107,27 @@ These properties are mostly used for styling and event handling.
|
|||
|
||||
See [Reading Widget Properties](crate::doc::doc_04b_widget_properties) for more info.
|
||||
|
||||
|
||||
## Bounding rect
|
||||
|
||||
A widget's bounding rect is a window-space axis-aligned rectangle inside of which pointer events might affect either the widget or its descendants.
|
||||
|
||||
In general, the bounding rect is a union or a widget's layout rect and the bounding rects of all its descendants.
|
||||
|
||||
The bounding rects of the widget tree form a kind of "bounding volume hierarchy": when looking to find which widget a pointer is on, Masonry will automatically exclude any widget if the pointer is outside its bounding rect.
|
||||
|
||||
<!-- TODO - Include illustration. -->
|
||||
|
||||
<!-- TODO - Add section about clip paths and pointer detection. -->
|
||||
|
||||
|
||||
## Layout rect
|
||||
|
||||
Previous versions of Masonry had a concept of a widget's "layout rect", composed of its self-declared size and the position attributed by its parent.
|
||||
|
||||
However, given that widgets can have arbitrary transforms, the concept of an axis-aligned layout rect doesn't really make sense anymore.
|
||||
|
||||
|
||||
## Safety rails
|
||||
|
||||
When debug assertions are on, Masonry runs a bunch of checks every frame to make sure widget code doesn't have logical errors.
|
||||
|
|
|
@ -80,18 +80,10 @@ fn compose_widget(
|
|||
);
|
||||
let parent_bounding_rect = parent_state.bounding_rect;
|
||||
|
||||
// This could be further optimized by more tightly clipping the child bounding rect according to the clip path.
|
||||
let clipped_child_bounding_rect = if let Some(clip_path) = parent_state.clip_path {
|
||||
let clip_path_bounding_rect =
|
||||
parent_state.window_transform.transform_rect_bbox(clip_path);
|
||||
state.item.bounding_rect.intersect(clip_path_bounding_rect)
|
||||
} else {
|
||||
state.item.bounding_rect
|
||||
};
|
||||
if !clipped_child_bounding_rect.is_zero_area() {
|
||||
parent_state.bounding_rect =
|
||||
parent_bounding_rect.union(clipped_child_bounding_rect);
|
||||
if let Some(child_bounding_rect) = parent_state.clip_child(state.item.bounding_rect) {
|
||||
parent_state.bounding_rect = parent_bounding_rect.union(child_bounding_rect);
|
||||
}
|
||||
|
||||
parent_state.merge_up(state.item);
|
||||
},
|
||||
);
|
||||
|
|
|
@ -212,12 +212,12 @@ impl<W: Widget + FromDynWidget + ?Sized> Portal<W> {
|
|||
}
|
||||
|
||||
pub fn set_viewport_pos(this: &mut WidgetMut<'_, Self>, position: Point) -> bool {
|
||||
let portal_size = this.ctx.layout_rect().size();
|
||||
let portal_size = this.ctx.local_layout_rect().size();
|
||||
let content_size = this
|
||||
.ctx
|
||||
.get_mut(&mut this.widget.child)
|
||||
.ctx
|
||||
.layout_rect()
|
||||
.local_layout_rect()
|
||||
.size();
|
||||
|
||||
let pos_changed = this
|
||||
|
@ -269,7 +269,11 @@ impl<W: Widget + FromDynWidget + ?Sized> Widget for Portal<W> {
|
|||
const SCROLLING_SPEED: f64 = 10.0;
|
||||
|
||||
let portal_size = ctx.size();
|
||||
let content_size = ctx.get_raw_ref(&mut self.child).ctx().layout_rect().size();
|
||||
let content_size = ctx
|
||||
.get_raw_ref(&mut self.child)
|
||||
.ctx()
|
||||
.local_layout_rect()
|
||||
.size();
|
||||
|
||||
match event {
|
||||
PointerEvent::MouseWheel(delta, _) => {
|
||||
|
@ -353,7 +357,11 @@ impl<W: Widget + FromDynWidget + ?Sized> Widget for Portal<W> {
|
|||
match event {
|
||||
Update::RequestPanToChild(target) => {
|
||||
let portal_size = ctx.size();
|
||||
let content_size = ctx.get_raw_ref(&mut self.child).ctx().layout_rect().size();
|
||||
let content_size = ctx
|
||||
.get_raw_ref(&mut self.child)
|
||||
.ctx()
|
||||
.local_layout_rect()
|
||||
.size();
|
||||
|
||||
self.pan_viewport_to_raw(portal_size, content_size, *target);
|
||||
ctx.request_compose();
|
||||
|
@ -561,7 +569,7 @@ mod tests {
|
|||
|
||||
assert_render_snapshot!(harness, "button_list_scrolled");
|
||||
|
||||
let item_3_rect = harness.get_widget(item_3_id).ctx().layout_rect();
|
||||
let item_3_rect = harness.get_widget(item_3_id).ctx().local_layout_rect();
|
||||
harness.edit_root_widget(|mut portal| {
|
||||
let mut portal = portal.downcast::<Portal<Flex>>();
|
||||
Portal::pan_viewport_to(&mut portal, item_3_rect);
|
||||
|
@ -569,7 +577,7 @@ mod tests {
|
|||
|
||||
assert_render_snapshot!(harness, "button_list_scroll_to_item_3");
|
||||
|
||||
let item_13_rect = harness.get_widget(item_13_id).ctx().layout_rect();
|
||||
let item_13_rect = harness.get_widget(item_13_id).ctx().local_layout_rect();
|
||||
harness.edit_root_widget(|mut portal| {
|
||||
let mut portal = portal.downcast::<Portal<Flex>>();
|
||||
Portal::pan_viewport_to(&mut portal, item_13_rect);
|
||||
|
|
|
@ -25,7 +25,7 @@ fn layout_simple() {
|
|||
|
||||
let harness = TestHarness::create(widget);
|
||||
|
||||
let first_box_rect = harness.get_widget(id_1).ctx().layout_rect();
|
||||
let first_box_rect = harness.get_widget(id_1).ctx().local_layout_rect();
|
||||
let first_box_paint_rect = harness.get_widget(id_1).ctx().paint_rect();
|
||||
|
||||
assert_eq!(first_box_rect.x0, 0.0);
|
||||
|
|
Loading…
Reference in New Issue