-
Notifications
You must be signed in to change notification settings - Fork 117
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
masonry: reimplement Widget::get_child_at_pos
#565
Conversation
f56694b
to
f569230
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know about the overall strategy that is desired for this, but I'm definitely interested in seeing this feature be underway.
Would be good to have a couple of tests as well though, including checking skip_pointer
and some other edge cases.
That assumption is likely to remain, since it's how children are painted right now, and the cursor should general pick the widget whose pixels are "visible" under the mouse. I'm not crazy about the approach of passing an array of WidgetRef, though. I would lean towards passing a nont-mutable LifecycleCtx (soon to be renamed to UpdateCtx) instead. In the cases where that method would be useful (eg a very large container), you want to avoid any kind of |
That's a good point. Currently LifeCycleCtx (UpdateCtx) requires mutable-everything: Lines 65 to 73 in 6c49516
That would mean |
It would be nice if this supported the sort of use case where one might want an acceleration structure (such as a quad tree) for a widget-canvas with a lot of widgets. |
Indeed, that's a good use-case. If, as @PoignardAzur suggested, a cheaply-constructable context type is passed to the widget, the widget can choose to either do a linear search through its children (for example, sticking to the default behavior), or it can use whatever domain knowledge it has (for example, your use-case of a canvas with a quad tree for searching). |
Oh, that's right. That makes things more awkward.
Probably, yes, but I'm not happy about adding yet another context type. |
Instead of creating a new context type, fn get_child_at_pos<'w>(
&self,
widget_ref: WidgetRef<'w, dyn Widget>,
pos: Point,
) -> Option<WidgetRef<'w, dyn Widget>> I've pushed a commit with this change to illustrate. It is a bit circular, though: Going the context route, the context could look as follows. Some of the methods on /// A context provided to widget methods that need read-only (shared) access.
pub struct QueryCtx<'a> {
pub(crate) global_state: &'a RenderRootState,
pub(crate) widget_state: &'a WidgetState,
pub(crate) widget_state_children: ArenaRefChildren<'a, WidgetState>,
pub(crate) widget_children: ArenaRefChildren<'a, Box<dyn Widget>>,
} Note xilem/masonry/src/widget/widget_ref.rs Lines 109 to 112 in 6c49516
|
Sounds good. Fair warning, I won't have time to look at this over the weekend, but I'll have a look on Monday. |
A context type plus a Unlike Existing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some "drive-by" thoughts, pending Olivier's review.
be65d65
to
48c8325
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something that occurs to me reading this is we probably want to rename find_widget_at_pos
to find_widget_under_cursor
across the crate. It's of scope for now though.
I think the WidgetRef based API is too awkward. I'd rather have one based on a new QueryCtx type.
381d996
to
cb2dcaa
Compare
Rebased onto main, and added commits. One introduces The commit after explores a more substantial change. Some thoughts:
(Zulip thread: https://xi.zulipchat.com/#narrow/stream/317477-masonry/topic/.60WidgetRef.60.20and.20.60WidgetMut.60) |
masonry/src/widget/widget_ref.rs
Outdated
// Get child from the widget arena to bind it to the arena's lifetime. Is there a | ||
// way around this? | ||
let child = root.get_widget(child.id()).unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are lifetime issues here: child widgets fetched from Widget::get_child_at_pos
can't be returned from the function because they don't live long enough. To get around this, the render root is passed in to refetch widgets with a longer lifetime from the global widget arena, but maybe there's a more elegant solution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this exact kind of lifetime issue (when you want to return a borrow to something pointed to by a local, but rustc thinks you're returning a borrow to the local) has bitten me before.
I'm not sure there's an elegant solution, but finding one is a mid-high priority.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM at a skim. Will look more in-depth later.
masonry/src/widget/widget_ref.rs
Outdated
// Get child from the widget arena to bind it to the arena's lifetime. Is there a | ||
// way around this? | ||
let child = root.get_widget(child.id()).unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this exact kind of lifetime issue (when you want to return a borrow to something pointed to by a local, but rustc thinks you're returning a borrow to the local) has bitten me before.
I'm not sure there's an elegant solution, but finding one is a mid-high priority.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is roughly what a idiomatic version looks like. The key thing is that since QueryCtx is Copy, you can take it by value in QueryCtx::get
, and that means you can request "smaller" lifetimes.
ctx: QueryCtx<'c>, | ||
pos: Point, | ||
) -> Option<WidgetRef<'c, dyn Widget>> { | ||
let relative_pos = pos - ctx.widget_state.window_origin().to_vec2(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
QueryCtx::widget_state
is pub(crate)
, we can access it here but user code currently can't.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, same with the clip
field access.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
You could probably add a few methods to QueryCtx so that the default implementation doesn't need to use private fields, but either way, it's a good first implementation.
38d6379
to
1834cbe
Compare
I'll do a follow-up PR for that, if that's fine with you? Then we can think about the signatures of |
Sure. |
This adds public getters for clip path to non-`LayoutCtx` contexts, and also to `WidgetState` as it seems relevant for painting. The motivation is to remove private field accesses from `Widget::get_child_at_pos` as mentioned in linebender#565 (comment). This leaves one private field access, namely `WidgetState::is_stashed`. As per the docs of `WidgetState`, that field should probably not be publicly accessible from `WidgetState`, but we may want to make it available in some of the context types.
This adds public getters for clip path to non-`LayoutCtx` contexts, and also to `WidgetState` as it seems relevant for painting. The motivation is to remove private field accesses from `Widget::get_child_at_pos` as mentioned in linebender#565 (comment). This leaves one private field access, namely `WidgetState::is_stashed`. As per the docs of `WidgetState` ("widgets will generally not interact with it directly"), that field perhaps should not be publicly accessible from `WidgetState`, but then it should be accessible from `WidgetRef`.
This adds public getters for clip path to non-`LayoutCtx` contexts, and also to `WidgetState` as it seems relevant for painting. The motivation is to remove private field accesses from `Widget::get_child_at_pos` as mentioned in #565 (comment). This leaves one private field access, namely `WidgetState::is_stashed`. As per the docs of `WidgetState` ("widgets will generally not interact with it directly"), that field perhaps should not be publicly accessible from `WidgetState`, but then it should be accessible from `WidgetRef`.
The
Widget::get_child_at_pos
has a default linear search implementation, upholding some conditions (e.g., picking the last child in "z-order" in case of overlap). Widgets can override this with a custom implementation (e.g., a widget with many children that maintains a quadtree to search through). Custom implementations should uphold the same conditions, we can add some debug assertions to check this in a later PR.This introduces a
QueryCtx
, allowing widget methods access to the global state, widget state, and widget subtree.QueryCtx
is similar to the other (mutable) context types, but can be shared.This also changes
WidgetRef
to consist of aWidget
and aQueryCtx
, similar toWidgetMut
consisting of aWidget
andMutateCtx
. This required changing whereWidgetRef
can be constructed.This resolves code comment
xilem/masonry/src/widget/widget_ref.rs
Lines 192 to 199 in ac95f25