-
Notifications
You must be signed in to change notification settings - Fork 119
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
Support transforms for each widget #753
base: main
Are you sure you want to change the base?
Conversation
7da5c16
to
b97a9e9
Compare
…-tree) and fix transform issues.
…ate, to avoid a lot of boilerplate
I found a way to avoid the boilerplate on the masonry side, by using But in this state I'm happy enough to merge this (after documentation and tests). As mentioned on zulip, avoiding even more boilerplate on the xilem level, is probably not reasonable, as either the API suffers (by using composition types, which doesn't allow further setting attributes of the underlying views after using a transform wrapper), or requires something like in xilem_web by only using composition types, which would be a big refactor (and a lot more complexity and code)... Anyway to get a feeling for the added boilerplate on the xilem side, I've implemented transforms now for all views (and masonry also supports it for all widgets). I think it's not too bad and a good trade-off between complexity and boilerplate. So apart of missing tests and docs, I think this is finished (but I might have missed things, so manual testing for now is appreciated). |
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.
Sorry for the very late review.
This is not something anyone likes to hear, but I'm pretty confident this needs to be split into multiple PRs.
This PR essentially does three things:
- Replace the translation property with a general transform.
- Correct the pointer search algorithm to account for OOB sub-children.
- Add a way to set transform directly from creation.
I think all three of these changes would be worth their own PR. The second in particular feels like it needs some concepts (declared layout rect vs computed layout rect) to be reified and documented, or at least considered.
let widget_rect = self.get_widget(id).ctx().window_layout_rect(); | ||
let widget_rect = self.get_widget(id).ctx().bounding_rect(); |
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 have a major concern with this PR that shows up in a few places.
It seems the PR conflates two things: the bounding rect as the widget's self-expressed size plus its assigned position, and the bounding rect as the total area that needs to be invalidated if the widget is repainted.
So for example, let's say we have a clickable area, and that area has a child that overflows a thousand pixels right of the area. To click the area, we want to place the cursor in the middle of its self-reported rectangle, and not the rectangle that is the union of the parent and the out-of-bounds child.
let local_translation = state.item.translation + state.item.origin.to_vec2(); | ||
|
||
state.item.window_transform = | ||
parent_window_transform * state.item.transform.then_translate(local_translation); | ||
state.item.window_origin = state.item.window_transform.translation().to_point(); |
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 code first applies the widget's transform, then the translation. Is that the right order? At first I was thinking it should be opposite, but I guess if the transform includes a rotation, you don't want the position vector to be rotated too.
We might want to add a comment to clarify this.
pub fn new(text: impl Into<ArcStr>) -> Button { | ||
pub fn new(text: impl Into<ArcStr>) -> Self { |
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.
Not a dealbreaker, but I think this PR has a few too many drive-by fixes given its already large size.
if widget.ctx.widget_state.bounding_rect.contains(pos) { | ||
let local_pos = widget.ctx().widget_state.window_transform.inverse() * pos; | ||
|
||
if Some(false) == widget.ctx.clip_path().map(|clip| clip.contains(local_pos)) { | ||
return None; | ||
} | ||
|
||
// Assumes `Self::children_ids` is in increasing "z-order", picking the last child in case | ||
// of overlapping children. | ||
for child_id in widget.children_ids().iter().rev() { | ||
let child_ref = widget.ctx.get(*child_id); | ||
if let Some(child) = child_ref.widget.find_widget_at_pos(child_ref.ctx, pos) { | ||
return Some(child); | ||
} | ||
} | ||
if !widget.ctx.is_stashed() | ||
&& widget.ctx.accepts_pointer_interaction() | ||
&& widget.ctx.size().to_rect().contains(local_pos) | ||
{ | ||
return Some(child); | ||
Some(*widget) | ||
} else { | ||
None | ||
} | ||
} else { | ||
None | ||
} |
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 a core change in the PR, and it's one with a lot of semantic importance, so I'm giving it extra scrutiny. Some notes:
- This code has a lot of rightward drift that could be avoided with early returns.
- Again the difference between "declared layout rect" and "bounding rect of self and children" shows up. I think your code handles it correctly, but it does so implicitly. I think that distinction should be documented in a few places and given special attention in this function.
- I think
is_stashed
should be considered before the children. A stashed widget is never going to have un-stashed children.accepts_pointer_interaction
is probably fine here, if we want to behave likepointer-events: none
in the DOM, but that should be documented.
/// Create a new widget pod with a custom transform. | ||
pub fn new_with_transform(inner: W, transform: Affine) -> WidgetPod<W> { | ||
Self::new_with_id_and_transform(inner, WidgetId::next(), transform) | ||
} |
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.
Okay, so I get why the PR does it this way, but I'm somewhat uncomfortable with adding a new constructor and all the special-casing that comes with it for a single attribute.
I don't know how else to do it, ideally it should be part of the styling refactor, but I don't want to block on that.
parent_state.bounding_rect = parent_state.bounding_rect.union(state.item.bounding_rect); | ||
parent_state.merge_up(state.item); |
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.
By the way, this is needlessly pessimistic when the parent widget has a clipping rect.
Checkout this zulip thread for more info/discussion.
This is far from the final state, and at this point should more serve for discussion.See the xilem
mason
orhttp_cats
example, which I have abused to get an initial demo working.