-
Notifications
You must be signed in to change notification settings - Fork 120
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?
Changes from 14 commits
3136ecb
1606302
fd8cba1
61a0b13
7f0b1a7
e9d09b7
8b6f311
151d0b8
e2ad4de
045af4d
d8126e3
e73c9ce
4b4969e
90ae403
961caf8
a715a43
506b178
4cbfc65
4945288
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,7 @@ | |
|
||
use tracing::info_span; | ||
use tree_arena::ArenaMut; | ||
use vello::kurbo::Vec2; | ||
use vello::kurbo::Affine; | ||
|
||
use crate::passes::{enter_span_if, recurse_on_children}; | ||
use crate::render_root::{RenderRoot, RenderRootSignal, RenderRootState}; | ||
|
@@ -14,8 +14,8 @@ fn compose_widget( | |
global_state: &mut RenderRootState, | ||
mut widget: ArenaMut<'_, Box<dyn Widget>>, | ||
mut state: ArenaMut<'_, WidgetState>, | ||
parent_moved: bool, | ||
parent_translation: Vec2, | ||
parent_transformed: bool, | ||
parent_window_transform: Affine, | ||
) { | ||
let _span = enter_span_if( | ||
global_state.trace.compose, | ||
|
@@ -24,14 +24,21 @@ fn compose_widget( | |
state.reborrow(), | ||
); | ||
|
||
let moved = parent_moved || state.item.translation_changed; | ||
let translation = parent_translation + state.item.translation + state.item.origin.to_vec2(); | ||
state.item.window_origin = translation.to_point(); | ||
let transformed = parent_transformed || state.item.transform_changed; | ||
|
||
if !parent_moved && !state.item.translation_changed && !state.item.needs_compose { | ||
if !transformed && !state.item.needs_compose { | ||
return; | ||
} | ||
|
||
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(); | ||
|
||
let local_rect = state.item.size.to_rect(); | ||
state.item.bounding_rect = state.item.window_transform.transform_rect_bbox(local_rect); | ||
|
||
let mut ctx = ComposeCtx { | ||
global_state, | ||
widget_state: state.item, | ||
|
@@ -43,7 +50,7 @@ fn compose_widget( | |
} | ||
|
||
// TODO - Add unit tests for this. | ||
if moved && state.item.accepts_text_input && global_state.is_focused(state.item.id) { | ||
if transformed && state.item.accepts_text_input && global_state.is_focused(state.item.id) { | ||
let ime_area = state.item.get_ime_area(); | ||
global_state.emit_signal(RenderRootSignal::new_ime_moved_signal(ime_area)); | ||
} | ||
|
@@ -55,9 +62,10 @@ fn compose_widget( | |
|
||
state.item.needs_compose = false; | ||
state.item.request_compose = false; | ||
state.item.translation_changed = false; | ||
state.item.transform_changed = false; | ||
|
||
let id = state.item.id; | ||
let parent_transform = state.item.window_transform; | ||
let parent_state = state.item; | ||
recurse_on_children( | ||
id, | ||
|
@@ -68,9 +76,10 @@ fn compose_widget( | |
global_state, | ||
widget, | ||
state.reborrow_mut(), | ||
moved, | ||
translation, | ||
transformed, | ||
parent_transform, | ||
); | ||
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 commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right for at least the use case of intersecting pointer events and possibly doing some kind of damage regions (but I think this is basically the use case of bounding rects/boxes here). I've used an intersection of the (transformed) clip path and the child bounding rect instead here. There's for sure further optimization potential, but I think that should be probably done in follow-up PRs when it's more clear what shape clip_path will be. |
||
}, | ||
); | ||
|
@@ -92,6 +101,6 @@ pub(crate) fn run_compose_pass(root: &mut RenderRoot) { | |
root_widget, | ||
root_state, | ||
false, | ||
Vec2::ZERO, | ||
Affine::IDENTITY, | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -418,7 +418,7 @@ impl TestHarness { | |
/// | ||
/// Combines [`mouse_move`](Self::mouse_move), [`mouse_button_press`](Self::mouse_button_press), and [`mouse_button_release`](Self::mouse_button_release). | ||
pub fn mouse_click_on(&mut self, id: WidgetId) { | ||
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 commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Whoops good catch, I think I just didn't pay too much attention to that part. That's obviously wrong, as my intention was that the bounding_rect is in the window coordinate space. I've corrected this. Where are the other few places though? Maybe |
||
let widget_center = widget_rect.center(); | ||
|
||
self.mouse_move(widget_center); | ||
|
@@ -430,7 +430,7 @@ impl TestHarness { | |
pub fn mouse_move_to(&mut self, id: WidgetId) { | ||
// FIXME - handle case where the widget isn't visible | ||
// FIXME - assert that the widget correctly receives the event otherwise? | ||
let widget_rect = self.get_widget(id).ctx().window_layout_rect(); | ||
let widget_rect = self.get_widget(id).ctx().bounding_rect(); | ||
let widget_center = widget_rect.center(); | ||
|
||
self.mouse_move(widget_center); | ||
|
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.
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.
It's the right order, as you can see in the http_cats example (scroll) - at least when we want similar behavior as in CSS.
I've renamed
translation
in the last commit toscroll_translation
so that it's more obvious what happens here, and I don't thinktranslation
is used in any other context, but I can of course revert that when there's arguments for it. Also added a comment.