Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve bounding rect code #874

Merged
merged 7 commits into from
Mar 6, 2025
Merged

Improve bounding rect code #874

merged 7 commits into from
Mar 6, 2025

Conversation

PoignardAzur
Copy link
Contributor

@PoignardAzur PoignardAzur commented Feb 20, 2025

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.

@PoignardAzur PoignardAzur changed the title Document concepts of "Layout rect" and "bounding rect". Improve bounding rect code Feb 20, 2025
@PoignardAzur PoignardAzur marked this pull request as ready for review February 20, 2025 11:04
@@ -75,18 +75,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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This note about optimization is not present after the code was moved. Is it still a valid future thing to do?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or is that the new call to overlaps? Either way, the logic in this code has changed, so the PR title that this is just adding documentation starts to become misleading.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, you renamed it after I started looking at it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This note about optimization is not present after the code was moved. Is it still a valid future thing to do?

I'd have to ask @Philipp-M. I'm not sure the note about optimization made sense in the original PR. I'd rather remove it unless we can point at a specific optimization we expect to do.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well since the bounds might need to be bigger when transforming the AABB (due to not being able to tighly fit a transformed AABB), it's probably larger afterwards.
An optimization might be an OOB to more tightly represent the widget, or more optimally an arbitrary shape.
I'm not sure how useful that comment is, but at least it hints that this is not yet optimal.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine not hinting at a future optimization, because I highly doubt there will be one. Tightly clipping an arbitrary path in an oriented bounding box is a hard problem, and it's unclear that it would have any performance benefit at all.

99.9% of cases are going to be AABBs anyway, for which the current code is close to optimal.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I'm not too attached to that comment, feel free to remove it.

@Philipp-M Philipp-M self-requested a review February 20, 2025 12:48
@PoignardAzur
Copy link
Contributor Author

@Philipp-M Any chance you can review this weekend? Otherwise, I expect to merge on Monday.

Copy link
Contributor

@Philipp-M Philipp-M left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little bit uncomfortable with this change in this form TBH.

I do think we may even want to get rid of layout rects completely, and just keep the "OOB" (ignoring children, i.e. transformed local layout rect?) which is defined by the origin and size * window_transform to keep it more simple.

Comment on lines 739 to 741
pub fn global_layout_rect(&self) -> Rect {
Rect::from_origin_size(self.widget_state.window_origin(), self.widget_state.size)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure whether this is really useful at all, as this doesn't really represent the (global) widgets position, bounds or size (due to potential transformations).

I wonder whether we should get rid of "layout rects"?
I think they cause more confusion than being helpful.

Comment on lines 103 to 109
## Layout rect

A widget's layout rect includes its self-declared size, and the position attributed by its parent (local position) / parents (global position).

Pointer events cannot target a widget if they are outside its layout rect, though they can target its children (see **Bounding rect** section).

A layout rect doesn't necessarily have a formal definition; it's generally where the widget will be drawn, though a widget can be drawn outside its layout rect; it's usually a widget's own space and containers will try to avoid having its children's layout rects overlap.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this section should somehow explain, that a widget can be transformed by its transform.

Pointer events also only reach the widget, when they intersect the transformed widget, thus I don't think it's currently correct. Try for example transformations on the left portal in the xilem http_cats example, to see what I mean.

Comment on lines 114 to 117
A widget's bounding rect is a 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth mentioning that bounding rects currently are in window space and axis aligned.

@@ -75,18 +75,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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well since the bounds might need to be bigger when transforming the AABB (due to not being able to tighly fit a transformed AABB), it's probably larger afterwards.
An optimization might be an OOB to more tightly represent the widget, or more optimally an arbitrary shape.
I'm not sure how useful that comment is, but at least it hints that this is not yet optimal.

@@ -472,8 +472,7 @@ impl TestHarness {
#[track_caller]
pub fn mouse_move_to(&mut self, id: WidgetId) {
let widget = self.get_widget(id);
let local_widget_center = (widget.ctx().size() / 2.0).to_vec2().to_point();
let widget_center = widget.ctx().widget_state.window_transform * local_widget_center;
let widget_center = widget.ctx().global_layout_rect().center();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this change is correct, in that a transformed widget might not be hit.

@PoignardAzur
Copy link
Contributor Author

On second thought, I agree with @Philipp-M that the "layout rect" concept doesn't really make sense anymore with arbitrary transforms.

I've edited the documentation to reflect this.

Copy link
Contributor

@Philipp-M Philipp-M left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I'm much more comfortable with this.
I think the PR could already be handling the added TODOs (as IME merged TODOs are less likely to be handled, and I don't think they are a lot of work to handle), but I'm not going to block on this.

/// 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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's only used in Flex maybe:

Suggested change
pub fn child_layout_rect(&self, child: &WidgetPod<impl Widget + ?Sized>) -> Rect {
pub(crate) fn child_layout_rect(&self, child: &WidgetPod<impl Widget + ?Sized>) -> Rect {

Might also be worth to fix the parts where the code is used in Flex?
So that we can get rid of it entirely here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's a whole PR on its own.

Comment on lines +111 to +121
<!-- TODO - Include illustration. -->

<!-- TODO - Add section about clip paths and pointer detection. -->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you plan to handle the TODOs in this PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to refactor how clip paths work at some point, it would be part of that.

Comment on lines +116 to +128
## 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if it's even worth mentioning anymore, when all the methods are all "private" and planned to be deleted?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to keep it at least until all references are removed from the code, including private code.

@@ -75,18 +75,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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I'm not too attached to that comment, feel free to remove it.

@PoignardAzur PoignardAzur enabled auto-merge March 6, 2025 12:54
@PoignardAzur PoignardAzur added this pull request to the merge queue Mar 6, 2025
Merged via the queue into main with commit 93e000d Mar 6, 2025
18 checks passed
@PoignardAzur PoignardAzur deleted the transform_fixup branch March 6, 2025 13:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants