Skip to content

Commit

Permalink
Implement mutate pass from pass spec RFC (#510)
Browse files Browse the repository at this point in the history
This is part of the "Pass Specification" RFC:
linebender/rfcs#7

Rename WidgetCtx to MutateCtx.
Add a mutate pass.
Add a `mutate_later` context method to trigger that pass.
Refactor `edit_root_widget` to use a version of that pass.
Add a separate constructor for the synthetic WidgetState created in
RenderRoot.
  • Loading branch information
PoignardAzur authored Aug 14, 2024
1 parent 0c80c0f commit dc2a433
Show file tree
Hide file tree
Showing 14 changed files with 280 additions and 176 deletions.
20 changes: 14 additions & 6 deletions masonry/examples/calc_masonry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,20 +145,25 @@ impl Widget for CalcButton {
match event {
PointerEvent::PointerDown(_, _) => {
if !ctx.is_disabled() {
ctx.get_mut(&mut self.inner)
.set_background(self.active_color);
let color = self.active_color;
ctx.mutate_later(&mut self.inner, move |mut inner| {
inner.set_background(color);
});
ctx.set_active(true);
ctx.request_paint();
trace!("CalcButton {:?} pressed", ctx.widget_id());
}
}
PointerEvent::PointerUp(_, _) => {
if ctx.is_active() && !ctx.is_disabled() {
let color = self.base_color;
ctx.mutate_later(&mut self.inner, move |mut inner| {
inner.set_background(color);
});
ctx.submit_action(Action::Other(Box::new(self.action)));
ctx.request_paint();
trace!("CalcButton {:?} released", ctx.widget_id());
}
ctx.get_mut(&mut self.inner).set_background(self.base_color);
ctx.set_active(false);
}
_ => (),
Expand All @@ -183,12 +188,15 @@ impl Widget for CalcButton {
fn on_status_change(&mut self, ctx: &mut LifeCycleCtx, event: &StatusChange) {
match event {
StatusChange::HotChanged(true) => {
ctx.get_mut(&mut self.inner).set_border(Color::WHITE, 3.0);
ctx.mutate_later(&mut self.inner, move |mut inner| {
inner.set_border(Color::WHITE, 3.0);
});
ctx.request_paint();
}
StatusChange::HotChanged(false) => {
ctx.get_mut(&mut self.inner)
.set_border(Color::TRANSPARENT, 3.0);
ctx.mutate_later(&mut self.inner, move |mut inner| {
inner.set_border(Color::TRANSPARENT, 3.0);
});
ctx.request_paint();
}
_ => (),
Expand Down
111 changes: 41 additions & 70 deletions masonry/src/contexts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use parley::{FontContext, LayoutContext};
use tracing::{trace, warn};

use crate::action::Action;
use crate::render_root::{RenderRootSignal, RenderRootState};
use crate::render_root::{MutateCallback, RenderRootSignal, RenderRootState};
use crate::text::TextBrush;
use crate::text_helpers::{ImeChangeSignal, TextFieldRegistration};
use crate::tree_arena::ArenaMutChildren;
Expand All @@ -34,12 +34,12 @@ macro_rules! impl_context_method {
/// A context provided inside of [`WidgetMut`].
///
/// When you declare a mutable reference type for your widget, methods of this type
/// will have access to a `WidgetCtx`. If that method mutates the widget in a way that
/// will have access to a `MutateCtx`. If that method mutates the widget in a way that
/// requires a later pass (for instance, if your widget has a `set_color` method),
/// you will need to signal that change in the pass (eg `request_paint`).
///
// TODO add tutorial - See https://github.com/linebender/xilem/issues/376
pub struct WidgetCtx<'a> {
pub struct MutateCtx<'a> {
pub(crate) global_state: &'a mut RenderRootState,
pub(crate) parent_widget_state: &'a mut WidgetState,
pub(crate) widget_state: &'a mut WidgetState,
Expand Down Expand Up @@ -110,7 +110,7 @@ pub struct AccessCtx<'a> {
// --- MARK: GETTERS ---
// Methods for all context types
impl_context_method!(
WidgetCtx<'_>,
MutateCtx<'_>,
EventCtx<'_>,
LifeCycleCtx<'_>,
LayoutCtx<'_>,
Expand Down Expand Up @@ -158,7 +158,7 @@ impl_context_method!(

// Methods for all mutable context types
impl_context_method!(
WidgetCtx<'_>,
MutateCtx<'_>,
EventCtx<'_>,
LifeCycleCtx<'_>,
LayoutCtx<'_>,
Expand All @@ -184,7 +184,7 @@ impl_context_method!(
// Methods on all context types except LayoutCtx
// These methods access layout info calculated during the layout pass.
impl_context_method!(
WidgetCtx<'_>,
MutateCtx<'_>,
EventCtx<'_>,
LifeCycleCtx<'_>,
PaintCtx<'_>,
Expand Down Expand Up @@ -226,7 +226,7 @@ impl_context_method!(
// Methods on all context types except LayoutCtx
// Access status information (hot/active/disabled/etc).
impl_context_method!(
WidgetCtx<'_>,
MutateCtx<'_>,
EventCtx<'_>,
LifeCycleCtx<'_>,
PaintCtx<'_>,
Expand Down Expand Up @@ -347,7 +347,7 @@ impl_context_method!(EventCtx<'_>, {

// --- MARK: WIDGET_MUT ---
// Methods to get a child WidgetMut from a parent.
impl<'a> WidgetCtx<'a> {
impl<'a> MutateCtx<'a> {
/// Return a [`WidgetMut`] to a child widget.
pub fn get_mut<'c, Child: Widget>(
&'c mut self,
Expand All @@ -361,7 +361,7 @@ impl<'a> WidgetCtx<'a> {
.widget_children
.get_child_mut(child.id().to_raw())
.expect("get_mut: child not found");
let child_ctx = WidgetCtx {
let child_ctx = MutateCtx {
global_state: self.global_state,
parent_widget_state: self.widget_state,
widget_state: child_state_mut.item,
Expand All @@ -374,73 +374,21 @@ impl<'a> WidgetCtx<'a> {
is_reborrow: false,
}
}
}

// TODO - It's not clear whether EventCtx should be able to create a WidgetMut.
// One of the examples currently uses that feature to change a child widget's color
// in reaction to mouse events, but we might want to address that use-case differently.
impl<'a> EventCtx<'a> {
/// Return a [`WidgetMut`] to a child widget.
pub fn get_mut<'c, Child: Widget>(
&'c mut self,
child: &'c mut WidgetPod<Child>,
) -> WidgetMut<'c, Child> {
let child_state_mut = self
.widget_state_children
.get_child_mut(child.id().to_raw())
.expect("get_mut: child not found");
let child_mut = self
.widget_children
.get_child_mut(child.id().to_raw())
.expect("get_mut: child not found");
let child_ctx = WidgetCtx {
global_state: self.global_state,
parent_widget_state: self.widget_state,
widget_state: child_state_mut.item,
widget_state_children: child_state_mut.children,
widget_children: child_mut.children,
};
WidgetMut {
ctx: child_ctx,
widget: child_mut.item.as_mut_dyn_any().downcast_mut().unwrap(),
is_reborrow: false,
}
}
}

// TODO - It's not clear whether LifeCycleCtx should be able to create a WidgetMut.
impl<'a> LifeCycleCtx<'a> {
/// Return a [`WidgetMut`] to a child widget.
pub fn get_mut<'c, Child: Widget>(
&'c mut self,
child: &'c mut WidgetPod<Child>,
) -> WidgetMut<'c, Child> {
let child_state_mut = self
.widget_state_children
.get_child_mut(child.id().to_raw())
.expect("get_mut: child not found");
let child_mut = self
.widget_children
.get_child_mut(child.id().to_raw())
.expect("get_mut: child not found");
let child_ctx = WidgetCtx {
pub(crate) fn reborrow_mut(&mut self) -> MutateCtx<'_> {
MutateCtx {
global_state: self.global_state,
parent_widget_state: self.widget_state,
widget_state: child_state_mut.item,
widget_state_children: child_state_mut.children,
widget_children: child_mut.children,
};
WidgetMut {
ctx: child_ctx,
widget: child_mut.item.as_mut_dyn_any().downcast_mut().unwrap(),
is_reborrow: false,
parent_widget_state: self.parent_widget_state,
widget_state: self.widget_state,
widget_state_children: self.widget_state_children.reborrow_mut(),
widget_children: self.widget_children.reborrow_mut(),
}
}
}

// --- MARK: UPDATE FLAGS ---
// Methods on WidgetCtx, EventCtx, and LifeCycleCtx
impl_context_method!(WidgetCtx<'_>, EventCtx<'_>, LifeCycleCtx<'_>, {
// Methods on MutateCtx, EventCtx, and LifeCycleCtx
impl_context_method!(MutateCtx<'_>, EventCtx<'_>, LifeCycleCtx<'_>, {
/// Request a [`paint`](crate::Widget::paint) pass.
pub fn request_paint(&mut self) {
trace!("request_paint");
Expand Down Expand Up @@ -540,11 +488,34 @@ impl_context_method!(WidgetCtx<'_>, EventCtx<'_>, LifeCycleCtx<'_>, {
// --- MARK: OTHER METHODS ---
// Methods on all context types except PaintCtx and AccessCtx
impl_context_method!(
WidgetCtx<'_>,
MutateCtx<'_>,
EventCtx<'_>,
LifeCycleCtx<'_>,
LayoutCtx<'_>,
{
pub fn mutate_self_later(
&mut self,
f: impl FnOnce(WidgetMut<'_, Box<dyn Widget>>) + Send + 'static,
) {
let callback = MutateCallback {
id: self.widget_state.id,
callback: Box::new(f),
};
self.global_state.mutate_callbacks.push(callback);
}

pub fn mutate_later<W: Widget>(
&mut self,
child: &mut WidgetPod<W>,
f: impl FnOnce(WidgetMut<'_, W>) + Send + 'static,
) {
let callback = MutateCallback {
id: child.id(),
callback: Box::new(|mut widget_mut| f(widget_mut.downcast())),
};
self.global_state.mutate_callbacks.push(callback);
}

/// Submit an [`Action`].
///
/// Note: Actions are still a WIP feature.
Expand Down
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, PaintCtx, RawWrapper, RawWrapperMut,
WidgetCtx,
AccessCtx, EventCtx, IsContext, LayoutCtx, LifeCycleCtx, MutateCtx, PaintCtx, RawWrapper,
RawWrapperMut,
};
pub use event::{
AccessEvent, InternalLifeCycle, LifeCycle, PointerButton, PointerEvent, PointerState,
Expand Down
1 change: 1 addition & 0 deletions masonry/src/passes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::widget::WidgetArena;
use crate::WidgetId;

pub mod event;
pub mod mutate;
pub mod update;

pub(crate) fn merge_state_up(arena: &mut WidgetArena, widget_id: WidgetId) {
Expand Down
56 changes: 56 additions & 0 deletions masonry/src/passes/mutate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright 2024 the Xilem Authors
// SPDX-License-Identifier: Apache-2.0

use tracing::info_span;

use crate::passes::merge_state_up;
use crate::render_root::RenderRoot;
use crate::widget::WidgetMut;
use crate::{MutateCtx, Widget, WidgetId, WidgetState};

pub(crate) fn mutate_widget(
root: &mut RenderRoot,
id: WidgetId,
mutate_fn: impl FnOnce(WidgetMut<'_, Box<dyn Widget>>),
) {
let mut dummy_state = WidgetState::root(root.root.id(), root.get_kurbo_size());
let (widget_mut, state_mut) = root.widget_arena.get_pair_mut(id);

let _span = info_span!("mutate_widget").entered();
let root_widget = WidgetMut {
ctx: MutateCtx {
global_state: &mut root.state,
// FIXME - This works in practice, because the loop below will merge the
// state up to the root. But it's semantically awkward.
// parent_widget_state should probably be an Option.
parent_widget_state: &mut dummy_state,
widget_state: state_mut.item,
widget_state_children: state_mut.children,
widget_children: widget_mut.children,
},
widget: widget_mut.item,
is_reborrow: false,
};

mutate_fn(root_widget);

let mut current_id = Some(id);
while let Some(id) = current_id {
let parent_id = root.widget_arena.parent_of(id);
merge_state_up(&mut root.widget_arena, id);
current_id = parent_id;
}
}

pub(crate) fn run_mutate_pass(root: &mut RenderRoot, root_state: &mut WidgetState) {
// TODO - Factor out into a "pre-event" function?
// root.state.next_focused_widget = root.state.focused_widget;

let callbacks = std::mem::take(&mut root.state.mutate_callbacks);
for callback in callbacks {
mutate_widget(root, callback.id, callback.callback);
}

root_state.merge_up(root.widget_arena.get_state_mut(root.root.id()).item);
// root.post_event_processing(&mut root_state);
}
Loading

0 comments on commit dc2a433

Please sign in to comment.