Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 40 additions & 1 deletion masonry/src/contexts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use std::time::Duration;
use accesskit::{NodeBuilder, TreeUpdate};
use parley::{FontContext, LayoutContext};
use tracing::{trace, warn};
use vello::kurbo::Vec2;

use crate::action::Action;
use crate::render_root::{MutateCallback, RenderRootSignal, RenderRootState};
Expand Down Expand Up @@ -84,6 +85,13 @@ pub struct LayoutCtx<'a> {
pub(crate) mouse_pos: Option<Point>,
}

pub struct ComposeCtx<'a> {
pub(crate) global_state: &'a mut RenderRootState,
pub(crate) widget_state: &'a mut WidgetState,
pub(crate) widget_state_children: ArenaMutChildren<'a, WidgetState>,
pub(crate) widget_children: ArenaMutChildren<'a, Box<dyn Widget>>,
}

/// A context passed to paint methods of widgets.
pub struct PaintCtx<'a> {
pub(crate) global_state: &'a mut RenderRootState,
Expand Down Expand Up @@ -114,6 +122,7 @@ impl_context_method!(
EventCtx<'_>,
LifeCycleCtx<'_>,
LayoutCtx<'_>,
ComposeCtx<'_>,
PaintCtx<'_>,
AccessCtx<'_>,
{
Expand Down Expand Up @@ -187,6 +196,7 @@ impl_context_method!(
MutateCtx<'_>,
EventCtx<'_>,
LifeCycleCtx<'_>,
ComposeCtx<'_>,
PaintCtx<'_>,
AccessCtx<'_>,
{
Expand Down Expand Up @@ -229,6 +239,7 @@ impl_context_method!(
MutateCtx<'_>,
EventCtx<'_>,
LifeCycleCtx<'_>,
ComposeCtx<'_>,
PaintCtx<'_>,
AccessCtx<'_>,
{
Expand Down Expand Up @@ -412,6 +423,17 @@ impl_context_method!(MutateCtx<'_>, EventCtx<'_>, LifeCycleCtx<'_>, {
self.widget_state.needs_layout = true;
}

// TODO - Document better
/// Request a [`compose`] pass.
///
/// The compose pass is often cheaper than the layout pass, because it can only transform individual widgets' position.
/// [`compose`]: crate::Widget::compose
Copy link
Contributor

Choose a reason for hiding this comment

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

There's no thing above this that this would be providing a link target for.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

Copy link
Member

Choose a reason for hiding this comment

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

Hmm the Link seems still not to be working using cargo doc --no-deps

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

pub fn request_accessibility_update(&mut self) {
trace!("request_accessibility_update");
self.widget_state.needs_accessibility_update = true;
Expand Down Expand Up @@ -494,6 +516,7 @@ impl_context_method!(
EventCtx<'_>,
LifeCycleCtx<'_>,
LayoutCtx<'_>,
ComposeCtx<'_>,
{
// TODO - Remove from MutateCtx?
/// Queue a callback that will be called with a [`WidgetMut`] for this widget.
Expand Down Expand Up @@ -847,7 +870,7 @@ impl LayoutCtx<'_> {
self.assert_layout_done(child, "place_child");
if origin != self.get_child_state_mut(child).origin {
self.get_child_state_mut(child).origin = origin;
self.get_child_state_mut(child).needs_window_origin = true;
self.get_child_state_mut(child).translation_changed = true;
}
self.get_child_state_mut(child)
.is_expecting_place_child_call = false;
Expand All @@ -859,6 +882,22 @@ impl LayoutCtx<'_> {
}
}

impl ComposeCtx<'_> {
pub fn needs_compose(&self) -> bool {
self.widget_state.needs_compose
}

/// Set a translation for the child widget.
///
/// The translation is applied on top of the position from [`LayoutCtx::place_child`].
pub fn set_child_translation(&mut self, translation: Vec2) {
if self.widget_state.translation != translation {
self.widget_state.translation = translation;
self.widget_state.translation_changed = true;
}
}
}

// --- MARK: OTHER STUFF ---
impl_context_method!(LayoutCtx<'_>, PaintCtx<'_>, {
/// Get the contexts needed to build and paint text sections.
Expand Down
1 change: 0 additions & 1 deletion masonry/src/debug_logger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,6 @@ impl DebugLogger {
StateTree::new("is_explicitly_disabled", w_state.is_explicitly_disabled),
StateTree::new("is_hot", w_state.is_hot),
StateTree::new("needs_layout", w_state.needs_layout),
StateTree::new("needs_window_origin", w_state.needs_window_origin),
StateTree::new("has_focus", w_state.has_focus),
StateTree::new("request_anim", w_state.request_anim),
StateTree::new("children_changed", w_state.children_changed),
Expand Down
7 changes: 0 additions & 7 deletions masonry/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,11 +330,6 @@ pub enum InternalLifeCycle {

/// Used to route the `DisabledChanged` event to the required widgets.
RouteDisabledChanged,

/// The parents widget origin in window coordinate space has changed.
ParentWindowOrigin {
mouse_pos: Option<LogicalPosition<f64>>,
},
}

/// Event indicating status changes within the widget hierarchy.
Expand Down Expand Up @@ -525,7 +520,6 @@ impl LifeCycle {
InternalLifeCycle::RouteWidgetAdded => "RouteWidgetAdded",
InternalLifeCycle::RouteFocusChanged { .. } => "RouteFocusChanged",
InternalLifeCycle::RouteDisabledChanged => "RouteDisabledChanged",
InternalLifeCycle::ParentWindowOrigin { .. } => "ParentWindowOrigin",
},
LifeCycle::WidgetAdded => "WidgetAdded",
LifeCycle::AnimFrame(_) => "AnimFrame",
Expand All @@ -550,7 +544,6 @@ impl InternalLifeCycle {
InternalLifeCycle::RouteWidgetAdded
| InternalLifeCycle::RouteFocusChanged { .. }
| InternalLifeCycle::RouteDisabledChanged => true,
InternalLifeCycle::ParentWindowOrigin { .. } => false,
}
}
}
4 changes: 2 additions & 2 deletions masonry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ mod tree_arena;
pub use action::Action;
pub use box_constraints::BoxConstraints;
pub use contexts::{
AccessCtx, EventCtx, IsContext, LayoutCtx, LifeCycleCtx, MutateCtx, PaintCtx, RawWrapper,
RawWrapperMut,
AccessCtx, ComposeCtx, EventCtx, IsContext, LayoutCtx, LifeCycleCtx, MutateCtx, PaintCtx,
RawWrapper, RawWrapperMut,
};
pub use event::{
AccessEvent, InternalLifeCycle, LifeCycle, PointerButton, PointerEvent, PointerState,
Expand Down
101 changes: 101 additions & 0 deletions masonry/src/passes/compose.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright 2024 the Xilem Authors
// SPDX-License-Identifier: Apache-2.0

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

use crate::render_root::{RenderRoot, RenderRootState};
use crate::tree_arena::{ArenaMut, ArenaMutChildren};
use crate::{ComposeCtx, Widget, WidgetId, WidgetState};

fn recurse_on_children(
id: WidgetId,
mut widget: ArenaMut<'_, Box<dyn Widget>>,
mut state: ArenaMutChildren<'_, WidgetState>,
mut callback: impl FnMut(ArenaMut<'_, Box<dyn Widget>>, ArenaMut<'_, WidgetState>),
) {
let parent_name = widget.item.short_type_name();
let parent_id = id;

for child_id in widget.item.children_ids() {
let widget = widget
.children
.get_child_mut(child_id.to_raw())
.unwrap_or_else(|| {
panic!(
"Error in '{}' #{}: cannot find child #{} returned by children_ids()",
parent_name,
parent_id.to_raw(),
Copy link
Contributor

Choose a reason for hiding this comment

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

Should there be a Display or Debug impl for WidgetId?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, but that's a future PR.

child_id.to_raw()
)
});
let state = state.get_child_mut(child_id.to_raw()).unwrap_or_else(|| {
panic!(
"Error in '{}' #{}: cannot find child #{} returned by children_ids()",
parent_name,
parent_id.to_raw(),
child_id.to_raw()
)
});

callback(widget, state);
}
}

fn compose_widget(
global_state: &mut RenderRootState,
mut widget: ArenaMut<'_, Box<dyn Widget>>,
mut state: ArenaMut<'_, WidgetState>,
parent_moved: bool,
parent_translation: Vec2,
) {
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 mut ctx = ComposeCtx {
global_state,
widget_state: state.item,
widget_state_children: state.children.reborrow_mut(),
widget_children: widget.children.reborrow_mut(),
};
if ctx.widget_state.request_compose {
widget.item.compose(&mut ctx);
}

state.item.needs_compose = false;
state.item.request_compose = false;
state.item.translation_changed = false;

let id = state.item.id;
let parent_state = state.item;
recurse_on_children(
id,
widget.reborrow_mut(),
state.children,
|widget, mut state| {
if !moved && !state.item.translation_changed && !state.item.needs_compose {
return;
}
compose_widget(
global_state,
widget,
state.reborrow_mut(),
moved,
translation,
);
parent_state.merge_up(state.item);
},
);
}

// ----------------

pub fn root_compose(root: &mut RenderRoot, global_root_state: &mut WidgetState) {
let _span = info_span!("compose").entered();

let (root_widget, root_state) = root.widget_arena.get_pair_mut(root.root.id());
compose_widget(&mut root.state, root_widget, root_state, false, Vec2::ZERO);

global_root_state.merge_up(root.widget_arena.get_state_mut(root.root.id()).item);
}
1 change: 1 addition & 0 deletions masonry/src/passes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use crate::widget::WidgetArena;
use crate::WidgetId;

pub mod compose;
pub mod event;
pub mod mutate;
pub mod update;
Expand Down
8 changes: 3 additions & 5 deletions masonry/src/render_root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use crate::contexts::{LayoutCtx, LifeCycleCtx, PaintCtx};
use crate::debug_logger::DebugLogger;
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize};
use crate::event::{PointerEvent, TextEvent, WindowEvent};
use crate::passes::compose::root_compose;
use crate::passes::event::{root_on_access_event, root_on_pointer_event, root_on_text_event};
use crate::passes::mutate::{mutate_widget, run_mutate_pass};
use crate::passes::update::run_update_pointer_pass;
Expand Down Expand Up @@ -603,11 +604,8 @@ impl RenderRoot {
self.state.debug_logger.layout_tree.root = Some(self.root.id().to_raw() as u32);
}

if self.root_state().needs_window_origin && !self.root_state().needs_layout {
let event = LifeCycle::Internal(InternalLifeCycle::ParentWindowOrigin {
mouse_pos: self.last_mouse_pos,
});
self.root_lifecycle(event);
if self.root_state().needs_compose && !self.root_state().needs_layout {
root_compose(self, widget_state);
}

// Update the disabled state if necessary
Expand Down
36 changes: 27 additions & 9 deletions masonry/src/testing/helper_widgets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub type AccessEventFn<S> = dyn FnMut(&mut S, &mut EventCtx, &AccessEvent);
pub type StatusChangeFn<S> = dyn FnMut(&mut S, &mut LifeCycleCtx, &StatusChange);
pub type LifeCycleFn<S> = dyn FnMut(&mut S, &mut LifeCycleCtx, &LifeCycle);
pub type LayoutFn<S> = dyn FnMut(&mut S, &mut LayoutCtx, &BoxConstraints) -> Size;
pub type ComposeFn<S> = dyn FnMut(&mut S, &mut ComposeCtx);
pub type PaintFn<S> = dyn FnMut(&mut S, &mut PaintCtx, &mut Scene);
pub type RoleFn<S> = dyn Fn(&S) -> Role;
pub type AccessFn<S> = dyn FnMut(&mut S, &mut AccessCtx);
Expand All @@ -49,6 +50,7 @@ pub struct ModularWidget<S> {
on_status_change: Option<Box<StatusChangeFn<S>>>,
lifecycle: Option<Box<LifeCycleFn<S>>>,
layout: Option<Box<LayoutFn<S>>>,
compose: Option<Box<ComposeFn<S>>>,
paint: Option<Box<PaintFn<S>>>,
role: Option<Box<RoleFn<S>>>,
access: Option<Box<AccessFn<S>>>,
Expand Down Expand Up @@ -96,6 +98,7 @@ pub enum Record {
SC(StatusChange),
L(LifeCycle),
Layout(Size),
Compose,
Paint,
Access,
}
Expand Down Expand Up @@ -126,6 +129,7 @@ impl<S> ModularWidget<S> {
on_status_change: None,
lifecycle: None,
layout: None,
compose: None,
paint: None,
role: None,
access: None,
Expand Down Expand Up @@ -181,6 +185,11 @@ impl<S> ModularWidget<S> {
self
}

pub fn compose_fn(mut self, f: impl FnMut(&mut S, &mut ComposeCtx) + 'static) -> Self {
self.compose = Some(Box::new(f));
self
}

pub fn paint_fn(mut self, f: impl FnMut(&mut S, &mut PaintCtx, &mut Scene) + 'static) -> Self {
self.paint = Some(Box::new(f));
self
Expand All @@ -205,6 +214,8 @@ impl<S> ModularWidget<S> {
}
}

// TODO
// #[warn(clippy::missing_trait_methods)]
impl<S: 'static> Widget for ModularWidget<S> {
fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &event::PointerEvent) {
if let Some(f) = self.on_pointer_event.as_mut() {
Expand Down Expand Up @@ -248,6 +259,12 @@ impl<S: 'static> Widget for ModularWidget<S> {
.unwrap_or_else(|| Size::new(100., 100.))
}

fn compose(&mut self, ctx: &mut ComposeCtx) {
if let Some(f) = self.compose.as_mut() {
f(&mut self.state, ctx);
}
}

fn accessibility_role(&self) -> Role {
if let Some(f) = self.role.as_ref() {
f(&self.state)
Expand Down Expand Up @@ -299,17 +316,11 @@ impl Widget for ReplaceChild {
self.child.on_event(ctx, event)
}

fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &event::PointerEvent) {
todo!()
}
fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _event: &event::PointerEvent) {}

fn on_text_event(&mut self, ctx: &mut EventCtx, event: &event::TextEvent) {
todo!()
}
fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &event::TextEvent) {}

fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent) {
todo!()
}
fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {}

fn on_status_change(&mut self, ctx: &mut LifeCycleCtx, _event: &StatusChange) {
ctx.request_layout();
Expand All @@ -323,6 +334,8 @@ impl Widget for ReplaceChild {
self.child.layout(ctx, bc)
}

fn compose(&mut self, _ctx: &mut ComposeCtx) {}

fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) {
self.child.paint(ctx, scene);
}
Expand Down Expand Up @@ -403,6 +416,11 @@ impl<W: Widget> Widget for Recorder<W> {
size
}

fn compose(&mut self, ctx: &mut ComposeCtx) {
self.recording.push(Record::Compose);
self.child.compose(ctx);
}

fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) {
self.recording.push(Record::Paint);
self.child.paint(ctx, scene);
Expand Down
Loading