Skip to content

Commit

Permalink
Initial dirty hack to get transforms per widget working
Browse files Browse the repository at this point in the history
  • Loading branch information
Philipp-M committed Nov 18, 2024
1 parent 10dc9d1 commit 7da5c16
Show file tree
Hide file tree
Showing 13 changed files with 194 additions and 40 deletions.
10 changes: 8 additions & 2 deletions masonry/src/contexts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,12 @@ impl_context_method!(MutateCtx<'_>, EventCtx<'_>, UpdateCtx<'_>, {
self.widget_state.request_compose = true;
}

pub fn transform_changed(&mut self) {
trace!("transform_changed");
self.widget_state.transform_changed = true;
self.request_compose();
}

/// Request an animation frame.
pub fn request_anim_frame(&mut self) {
trace!("request_anim_frame");
Expand Down Expand Up @@ -1093,7 +1099,7 @@ impl LayoutCtx<'_> {
}
if origin != self.get_child_state_mut(child).origin {
self.get_child_state_mut(child).origin = origin;
self.get_child_state_mut(child).translation_changed = true;
self.get_child_state_mut(child).transform_changed = true;
}
self.get_child_state_mut(child)
.is_expecting_place_child_call = false;
Expand Down Expand Up @@ -1134,7 +1140,7 @@ impl ComposeCtx<'_> {
let child = self.get_child_state_mut(child);
if translation != child.translation {
child.translation = translation;
child.translation_changed = true;
child.transform_changed = true;
}
}
}
Expand Down
28 changes: 16 additions & 12 deletions masonry/src/passes/compose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

use tracing::info_span;
use vello::kurbo::Vec2;
use vello::kurbo::Affine;

use crate::passes::{enter_span_if, recurse_on_children};
use crate::render_root::{RenderRoot, RenderRootSignal, RenderRootState};
Expand All @@ -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,
Expand All @@ -24,14 +24,17 @@ 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;
}

state.item.window_transform = parent_window_transform
.then_translate(state.item.translation + state.item.origin.to_vec2())
* widget.item.transform();
state.item.window_origin = state.item.window_transform.translation().to_point();

let mut ctx = ComposeCtx {
global_state,
widget_state: state.item,
Expand All @@ -43,7 +46,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));
}
Expand All @@ -55,9 +58,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,
Expand All @@ -68,8 +72,8 @@ fn compose_widget(
global_state,
widget,
state.reborrow_mut(),
moved,
translation,
transformed,
parent_transform,
);
parent_state.merge_up(state.item);
},
Expand All @@ -92,6 +96,6 @@ pub(crate) fn run_compose_pass(root: &mut RenderRoot) {
root_widget,
root_state,
false,
Vec2::ZERO,
Affine::IDENTITY,
);
}
4 changes: 2 additions & 2 deletions masonry/src/passes/paint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use std::collections::HashMap;

use tracing::{info_span, trace};
use vello::kurbo::{Affine, Stroke};
use vello::kurbo::Stroke;
use vello::peniko::Mix;
use vello::Scene;

Expand Down Expand Up @@ -53,7 +53,7 @@ fn paint_widget(

let clip = state.item.clip_path;
let has_clip = clip.is_some();
let transform = Affine::translate(state.item.window_origin.to_vec2());
let transform = state.item.window_transform;
let scene = scenes.get(&id).unwrap();

if let Some(clip) = clip {
Expand Down
8 changes: 8 additions & 0 deletions masonry/src/testing/helper_widgets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,10 @@ impl<S: 'static> Widget for ModularWidget<S> {
fn as_mut_any(&mut self) -> &mut dyn std::any::Any {
self.as_mut_dyn_any()
}

fn transform(&self) -> Affine {
Affine::IDENTITY
}
}

impl ReplaceChild {
Expand Down Expand Up @@ -613,4 +617,8 @@ impl<W: Widget> Widget for Recorder<W> {
fn as_mut_any(&mut self) -> &mut dyn std::any::Any {
self.child.as_mut_any()
}

fn transform(&self) -> Affine {
self.child.transform()
}
}
21 changes: 19 additions & 2 deletions masonry/src/widget/button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use accesskit::{Node, Role};
use smallvec::{smallvec, SmallVec};
use tracing::{trace, trace_span, Span};
use vello::kurbo::Affine;
use vello::Scene;

use crate::action::Action;
Expand All @@ -28,6 +29,7 @@ const LABEL_INSETS: Insets = Insets::uniform_xy(8., 2.);
/// Emits [`Action::ButtonPressed`] when pressed.
pub struct Button {
label: WidgetPod<Label>,
transform: Affine,
}

// --- MARK: BUILDERS ---
Expand All @@ -41,7 +43,7 @@ impl Button {
///
/// let button = Button::new("Increment");
/// ```
pub fn new(text: impl Into<ArcStr>) -> Button {
pub fn new(text: impl Into<ArcStr>) -> Self {
Button::from_label(Label::new(text))
}

Expand All @@ -56,11 +58,17 @@ impl Button {
/// let label = Label::new("Increment").with_brush(Color::rgb(0.5, 0.5, 0.5));
/// let button = Button::from_label(label);
/// ```
pub fn from_label(label: Label) -> Button {
pub fn from_label(label: Label) -> Self {
Button {
label: WidgetPod::new(label),
transform: Affine::IDENTITY,
}
}

pub fn with_transform(mut self, transform: Affine) -> Self {
self.transform = transform;
self
}
}

// --- MARK: WIDGETMUT ---
Expand All @@ -73,6 +81,11 @@ impl Button {
pub fn label_mut<'t>(this: &'t mut WidgetMut<'_, Self>) -> WidgetMut<'t, Label> {
this.ctx.get_mut(&mut this.widget.label)
}

pub fn set_transform(this: &mut WidgetMut<'_, Self>, transform: Affine) {
this.widget.transform = transform;
this.ctx.transform_changed();
}
}

// --- MARK: IMPL WIDGET ---
Expand Down Expand Up @@ -213,6 +226,10 @@ impl Widget for Button {
fn get_debug_text(&self) -> Option<String> {
Some(self.label.as_ref().text().as_ref().to_string())
}

fn transform(&self) -> Affine {
self.transform
}
}

// --- MARK: TESTS ---
Expand Down
17 changes: 17 additions & 0 deletions masonry/src/widget/flex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use crate::{
/// This widget is the foundation of most layouts, and is highly configurable.
pub struct Flex {
direction: Axis,
transform: Affine,
cross_alignment: CrossAxisAlignment,
main_alignment: MainAxisAlignment,
fill_major_axis: bool,
Expand Down Expand Up @@ -128,6 +129,7 @@ impl Flex {
fill_major_axis: false,
old_bc: BoxConstraints::tight(Size::ZERO),
gap: None,
transform: Affine::IDENTITY,
}
}

Expand Down Expand Up @@ -165,6 +167,11 @@ impl Flex {
self
}

pub fn with_transform(mut self, transform: Affine) -> Self {
self.transform = transform;
self
}

/// Builder-style method for setting the spacing along the
/// major axis between any two elements in logical pixels.
///
Expand Down Expand Up @@ -328,6 +335,12 @@ impl Flex {
this.ctx.request_layout();
}

/// Set the transform (see [`Affine`]).
pub fn set_transform(this: &mut WidgetMut<'_, Self>, transform: Affine) {
this.widget.transform = transform;
this.ctx.transform_changed();
}

/// Set the spacing along the major axis between any two elements in logical pixels.
///
/// Equivalent to the css [gap] property.
Expand Down Expand Up @@ -1204,6 +1217,10 @@ impl Widget for Flex {
fn make_trace_span(&self, ctx: &QueryCtx<'_>) -> Span {
trace_span!("Flex", id = ctx.widget_id().trace())
}

fn transform(&self) -> Affine {
self.transform
}
}

// --- MARK: TESTS ---
Expand Down
16 changes: 13 additions & 3 deletions masonry/src/widget/widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use cursor_icon::CursorIcon;
use smallvec::SmallVec;
use tracing::field::DisplayValue;
use tracing::{trace_span, Span};
use vello::kurbo::Affine;
use vello::Scene;

use crate::contexts::ComposeCtx;
Expand Down Expand Up @@ -178,6 +179,10 @@ pub trait Widget: AsAny {
/// responsible for visiting all their children during `layout` and `register_children`.
fn children_ids(&self) -> SmallVec<[WidgetId; 16]>;

fn transform(&self) -> Affine {
Affine::IDENTITY
}

/// Whether this widget gets pointer events and hovered status. True by default.
///
/// If false, the widget will be treated as "transparent" for the pointer, meaning
Expand Down Expand Up @@ -308,10 +313,10 @@ pub(crate) fn get_child_at_pos<'c>(
ctx: QueryCtx<'c>,
pos: Point,
) -> Option<WidgetRef<'c, dyn Widget>> {
let relative_pos = pos - ctx.window_origin().to_vec2();
let local_pos = ctx.widget_state.window_transform.inverse() * pos;
if !ctx
.clip_path()
.map_or(true, |clip| clip.contains(relative_pos))
.map_or(true, |clip| clip.contains(local_pos))
{
return None;
}
Expand All @@ -320,12 +325,13 @@ pub(crate) fn get_child_at_pos<'c>(
// of overlapping children.
for child_id in widget.children_ids().iter().rev() {
let child = ctx.get(*child_id);
let local_pos = child.ctx().widget_state.window_transform.inverse() * pos;

// The position must be inside the child's layout and inside the child's clip path (if
// any).
if !child.ctx().is_stashed()
&& child.ctx().accepts_pointer_interaction()
&& child.ctx().window_layout_rect().contains(pos)
&& child.ctx().size().to_rect().contains(local_pos)
{
return Some(child);
}
Expand Down Expand Up @@ -507,4 +513,8 @@ impl Widget for Box<dyn Widget> {
fn as_mut_any(&mut self) -> &mut dyn Any {
self.deref_mut().as_mut_any()
}

fn transform(&self) -> Affine {
self.deref().transform()
}
}
3 changes: 2 additions & 1 deletion masonry/src/widget/widget_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,10 @@ impl<'w> WidgetRef<'w, dyn Widget> {
/// **pos** - the position in global coordinates (e.g. `(0,0)` is the top-left corner of the
/// window).
pub fn find_widget_at_pos(&self, pos: Point) -> Option<WidgetRef<'_, dyn Widget>> {
let local_pos = self.ctx.widget_state.window_transform.inverse() * pos;
let mut innermost_widget = *self;

if !self.ctx.window_layout_rect().contains(pos) {
if !self.ctx.size().to_rect().contains(local_pos) {
return None;
}

Expand Down
12 changes: 8 additions & 4 deletions masonry/src/widget/widget_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

#![cfg(not(tarpaulin_include))]

use vello::kurbo::{Insets, Point, Rect, Size, Vec2};
use vello::kurbo::{Affine, Insets, Point, Rect, Size, Vec2};

use crate::WidgetId;

Expand Down Expand Up @@ -79,9 +79,11 @@ pub(crate) struct WidgetState {
// efficiently hold an arbitrary shape.
pub(crate) clip_path: Option<Rect>,

// TODO - Handle matrix transforms
/// This is being computed out of the descendant transforms and `translation`
pub(crate) window_transform: Affine,
// TODO - Handle matrix transforms correctly
pub(crate) translation: Vec2,
pub(crate) translation_changed: bool,
pub(crate) transform_changed: bool,

// --- PASSES ---
/// `WidgetAdded` hasn't been sent to this widget yet.
Expand Down Expand Up @@ -166,7 +168,7 @@ impl WidgetState {
ime_area: None,
clip_path: Default::default(),
translation: Vec2::ZERO,
translation_changed: false,
transform_changed: false,
is_explicitly_disabled: false,
is_explicitly_stashed: false,
is_disabled: false,
Expand All @@ -192,6 +194,7 @@ impl WidgetState {
update_focus_chain: true,
#[cfg(debug_assertions)]
widget_name,
window_transform: Affine::IDENTITY,
}
}

Expand Down Expand Up @@ -265,6 +268,7 @@ impl WidgetState {
///
/// By default, returns the same as [`Self::window_layout_rect`].
pub(crate) fn get_ime_area(&self) -> Rect {
// TODO correctly calculate IME area based on transform
self.ime_area.unwrap_or_else(|| self.size.to_rect()) + self.window_origin.to_vec2()
}

Expand Down
Loading

0 comments on commit 7da5c16

Please sign in to comment.