Skip to content

Commit c1e0c83

Browse files
authored
Have AppDriver take a reference to RenderRoot for more flexibility (#808)
1 parent 6da98ae commit c1e0c83

12 files changed

+96
-90
lines changed

masonry/README.md

+6-4
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,12 @@ impl AppDriver for Driver {
5454
fn on_action(&mut self, ctx: &mut DriverCtx<'_>, _widget_id: WidgetId, action: Action) {
5555
match action {
5656
Action::ButtonPressed(_) => {
57-
let mut root: WidgetMut<RootWidget<Portal<Flex>>> = ctx.get_root();
58-
let mut portal = RootWidget::child_mut(&mut root);
59-
let mut flex = Portal::child_mut(&mut portal);
60-
Flex::add_child(&mut flex, Label::new(self.next_task.clone()));
57+
ctx.render_root().edit_root_widget(|mut root| {
58+
let mut root = root.downcast::<RootWidget<Portal<Flex>>>();
59+
let mut portal = RootWidget::child_mut(&mut root);
60+
let mut flex = Portal::child_mut(&mut portal);
61+
Flex::add_child(&mut flex, Label::new(self.next_task.clone()));
62+
});
6163
}
6264
Action::TextChanged(new_text) => {
6365
self.next_task = new_text.clone();

masonry/examples/calc_masonry.rs

+7-5
Original file line numberDiff line numberDiff line change
@@ -254,11 +254,13 @@ impl AppDriver for CalcState {
254254
_ => unreachable!(),
255255
}
256256

257-
let mut root = ctx.get_root::<RootWidget<Flex>>();
258-
let mut flex = RootWidget::child_mut(&mut root);
259-
let mut label = Flex::child_mut(&mut flex, 1).unwrap();
260-
let mut label = label.downcast::<Label>();
261-
Label::set_text(&mut label, &*self.value);
257+
ctx.render_root().edit_root_widget(|mut root| {
258+
let mut root = root.downcast::<RootWidget<Flex>>();
259+
let mut flex = RootWidget::child_mut(&mut root);
260+
let mut label = Flex::child_mut(&mut flex, 1).unwrap();
261+
let mut label = label.downcast::<Label>();
262+
Label::set_text(&mut label, &*self.value);
263+
});
262264
}
263265
}
264266

masonry/examples/grid_masonry.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@ impl AppDriver for Driver {
2828
self.grid_spacing += 0.5;
2929
}
3030

31-
let mut root = ctx.get_root::<RootWidget<Grid>>();
32-
let mut grid = RootWidget::child_mut(&mut root);
33-
Grid::set_spacing(&mut grid, self.grid_spacing);
31+
ctx.render_root().edit_root_widget(|mut root| {
32+
let mut root = root.downcast::<RootWidget<Grid>>();
33+
let mut grid = RootWidget::child_mut(&mut root);
34+
Grid::set_spacing(&mut grid, self.grid_spacing);
35+
});
3436
}
3537
}
3638
}

masonry/examples/to_do_list.rs

+14-12
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,9 @@
66
77
// On Windows platform, don't show a console when opening the app.
88
#![windows_subsystem = "windows"]
9-
#![expect(elided_lifetimes_in_paths, reason = "Deferred: Noisy")]
109

1110
use masonry::dpi::LogicalSize;
12-
use masonry::widget::{Button, Flex, Label, Portal, RootWidget, TextArea, Textbox, WidgetMut};
11+
use masonry::widget::{Button, Flex, Label, Portal, RootWidget, TextArea, Textbox};
1312
use masonry::{Action, AppDriver, DriverCtx, WidgetId};
1413
use winit::window::Window;
1514

@@ -23,17 +22,20 @@ impl AppDriver for Driver {
2322
fn on_action(&mut self, ctx: &mut DriverCtx<'_>, _widget_id: WidgetId, action: Action) {
2423
match action {
2524
Action::ButtonPressed(_) => {
26-
let mut root: WidgetMut<RootWidget<Portal<Flex>>> = ctx.get_root();
27-
let mut portal = RootWidget::child_mut(&mut root);
28-
let mut flex = Portal::child_mut(&mut portal);
29-
Flex::add_child(&mut flex, Label::new(self.next_task.clone()));
25+
ctx.render_root().edit_root_widget(|mut root| {
26+
let mut root = root.downcast::<RootWidget<Portal<Flex>>>();
3027

31-
let mut first_row = Flex::child_mut(&mut flex, 0).unwrap();
32-
let mut first_row = first_row.downcast::<Flex>();
33-
let mut textbox = Flex::child_mut(&mut first_row, 0).unwrap();
34-
let mut textbox = textbox.downcast::<Textbox>();
35-
let mut text_area = Textbox::text_mut(&mut textbox);
36-
TextArea::reset_text(&mut text_area, "");
28+
let mut portal = RootWidget::child_mut(&mut root);
29+
let mut flex = Portal::child_mut(&mut portal);
30+
Flex::add_child(&mut flex, Label::new(self.next_task.clone()));
31+
32+
let mut first_row = Flex::child_mut(&mut flex, 0).unwrap();
33+
let mut first_row = first_row.downcast::<Flex>();
34+
let mut textbox = Flex::child_mut(&mut first_row, 0).unwrap();
35+
let mut textbox = textbox.downcast::<Textbox>();
36+
let mut text_area = Textbox::text_mut(&mut textbox);
37+
TextArea::reset_text(&mut text_area, "");
38+
});
3739
}
3840
Action::TextChanged(new_text) => {
3941
self.next_task = new_text.clone();

masonry/src/app_driver.rs

+6-16
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,10 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
use crate::event_loop_runner::MasonryState;
5-
use crate::widget::WidgetMut;
6-
use crate::{Action, Widget, WidgetId};
5+
use crate::{Action, RenderRoot, WidgetId};
76

87
pub struct DriverCtx<'a> {
9-
// TODO
10-
// This is exposed publicly for now to let people drive
11-
// masonry on their own, but this is not expected to be
12-
// stable or even supported. This is for short term
13-
// expedience only while better solutions are devised.
14-
#[doc(hidden)]
15-
pub main_root_widget: WidgetMut<'a, Box<dyn Widget>>,
8+
pub(crate) render_root: &'a mut RenderRoot,
169
}
1710

1811
pub trait AppDriver {
@@ -29,15 +22,12 @@ pub trait AppDriver {
2922
impl DriverCtx<'_> {
3023
// TODO - Add method to create timer
3124

32-
/// Return a [`WidgetMut`] to the root widget.
33-
pub fn get_root<W: Widget>(&mut self) -> WidgetMut<'_, W> {
34-
self.main_root_widget.downcast()
25+
/// Access the [`RenderRoot`].
26+
pub fn render_root(&mut self) -> &mut RenderRoot {
27+
self.render_root
3528
}
3629

3730
pub fn content_changed(&self) -> bool {
38-
let ctx = &self.main_root_widget.ctx;
39-
ctx.widget_state.needs_rewrite_passes()
40-
|| ctx.widget_state.needs_render()
41-
|| ctx.global_state.focus_changed()
31+
self.render_root.needs_rewrite_passes()
4232
}
4333
}

masonry/src/doc/01_creating_app.md

+22-15
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,12 @@ impl AppDriver for Driver {
9090
fn on_action(&mut self, ctx: &mut DriverCtx<'_>, _widget_id: WidgetId, action: Action) {
9191
match action {
9292
Action::ButtonPressed(_) => {
93-
let mut root: WidgetMut<RootWidget<Portal<Flex>>> = ctx.get_root();
94-
let mut portal = RootWidget::child_mut(&mut root);
95-
let mut flex = Portal::child_mut(&mut portal);
96-
Flex::add_child(&mut flex, Label::new(self.next_task.clone()));
93+
ctx.render_root().edit_root_widget(|mut root| {
94+
let mut root = root.downcast::<RootWidget<Portal<Flex>>>();
95+
let mut portal = RootWidget::child_mut(&mut root);
96+
let mut flex = Portal::child_mut(&mut portal);
97+
Flex::add_child(&mut flex, Label::new(self.next_task.clone()));
98+
});
9799
}
98100
Action::TextChanged(new_text) => {
99101
self.next_task = new_text.clone();
@@ -113,9 +115,11 @@ Because our widget tree only has one button and one textbox, there is no possibl
113115

114116
When handling `ButtonPressed`:
115117

116-
- `ctx.get_root()` returns a `WidgetMut<RootWidget<...>>`.
117-
- `root.child_mut()` returns a `WidgetMut<Portal<...>>` for the `Portal`.
118-
- `portal.child_mut()` returns a `WidgetMut<Flex>` for the `Flex`.
118+
- `ctx.render_root()` returns a reference to the `RenderRoot`, which owns the widget tree and all the associated visual state.
119+
- `RenderRoot::edit_root_widget()` takes a closure; that closure takes a `WidgetMut<Box<dyn Widget>>` which we call `root`. Once the closure returns, `RenderRoot` runs some passes to update the app's internal states.
120+
- `root.downcast::<...>()` returns a `WidgetMut<RootWidget<...>>`.
121+
- `RootWidget::child_mut()` returns a `WidgetMut<Portal<...>>` for the `Portal`.
122+
- `Portal::child_mut()` returns a `WidgetMut<Flex>` for the `Flex`.
119123

120124
A [`WidgetMut`] is a smart reference type which lets us modify the widget tree.
121125
It's set up to automatically propagate update flags and update internal state when dropped.
@@ -165,7 +169,7 @@ The last step is to create our Winit window and start our main loop.
165169

166170
Our complete program therefore looks like this:
167171

168-
```rust,ignore
172+
```rust
169173
fn main() {
170174
const VERTICAL_WIDGET_SPACING: f64 = 20.0;
171175

@@ -182,8 +186,7 @@ fn main() {
182186
);
183187
let main_widget = RootWidget::new(main_widget);
184188

185-
use masonry::app_driver::{AppDriver, DriverCtx};
186-
use masonry::{Action, WidgetId};
189+
use masonry::{Action, AppDriver, DriverCtx, WidgetId};
187190
use masonry::widget::{Label};
188191

189192
struct Driver {
@@ -194,10 +197,12 @@ fn main() {
194197
fn on_action(&mut self, ctx: &mut DriverCtx<'_>, _widget_id: WidgetId, action: Action) {
195198
match action {
196199
Action::ButtonPressed(_) => {
197-
let mut root: WidgetMut<RootWidget<Portal<Flex>>> = ctx.get_root();
198-
let mut portal = root.child_mut();
199-
let mut flex = portal.child_mut();
200-
flex.add_child(Label::new(self.next_task.clone()));
200+
ctx.render_root().edit_root_widget(|mut root| {
201+
let mut root = root.downcast::<RootWidget<Portal<Flex>>>();
202+
let mut portal = RootWidget::child_mut(&mut root);
203+
let mut flex = Portal::child_mut(&mut portal);
204+
Flex::add_child(&mut flex, Label::new(self.next_task.clone()));
205+
});
201206
}
202207
Action::TextChanged(new_text) => {
203208
self.next_task = new_text.clone();
@@ -211,14 +216,16 @@ fn main() {
211216
next_task: String::new(),
212217
};
213218

214-
use masonry::dpi::LogicalSize;
219+
use masonry::dpi::LogicalSize;
215220
use winit::window::Window;
216221

217222
let window_attributes = Window::default_attributes()
218223
.with_title("To-do list")
219224
.with_resizable(true)
220225
.with_min_inner_size(LogicalSize::new(400.0, 400.0));
221226

227+
# return;
228+
222229
masonry::event_loop_runner::run(
223230
masonry::event_loop_runner::EventLoop::with_user_event(),
224231
window_attributes,

masonry/src/doc/02_implementing_widget.md

+2-6
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,9 @@ As mentioned in the previous chapter, a `WidgetMut` is a smart reference type to
8282
Most Widgets will implement methods that let their users "project" a WidgetMut from a parent to its child.
8383
For example, `WidgetMut<Portal<MyWidget>>` has a `get_child_mut()` method that returns a `WidgetMut<MyWidget>`.
8484

85-
So far, we've seen one way to get a WidgetMut: the [`DriverCtx::get_root()`] method in `AppDriver` implementations.
85+
So far, we've seen one way to get a WidgetMut: the [`RenderRoot::edit_root_widget()`] method.
8686
This methods returns a WidgetMut to the root widget, which you can then project into a WidgetMut reference to its descendants.
8787

88-
<!-- TODO - Change AppDriver trait to take a `&mut RenderRoot` instead, and rewrite above doc. -->
89-
90-
<!-- TODO - Mention edit_root_widget, edit_widget. -->
91-
9288
### Using WidgetMut in your custom Widget code
9389

9490
The WidgetMut type only has two fields, both public:
@@ -330,7 +326,7 @@ The next one is about creating a container widgets, and the complications it add
330326

331327
[`Widget`]: crate::Widget
332328
[`WidgetMut`]: crate::widget::WidgetMut
333-
[`DriverCtx::get_root()`]: crate::DriverCtx::get_root
334329
[`ButtonPressed`]: crate::Action::ButtonPressed
335330
[`vello::Scene`]: crate::vello::Scene
336331
[`Role::Button`]: accesskit::Role::Button
332+
[`RenderRoot::edit_root_widget()`]: crate::RenderRoot::edit_root_widget

masonry/src/event_loop_runner.rs

+5-7
Original file line numberDiff line numberDiff line change
@@ -677,13 +677,11 @@ impl MasonryState<'_> {
677677
while let Some(signal) = self.render_root.pop_signal() {
678678
match signal {
679679
render_root::RenderRootSignal::Action(action, widget_id) => {
680-
self.render_root.edit_root_widget(|root| {
681-
debug!("Action {:?} on widget {:?}", action, widget_id);
682-
let mut driver_ctx = DriverCtx {
683-
main_root_widget: root,
684-
};
685-
app_driver.on_action(&mut driver_ctx, widget_id, action);
686-
});
680+
let mut driver_ctx = DriverCtx {
681+
render_root: &mut self.render_root,
682+
};
683+
debug!("Action {:?} on widget {:?}", action, widget_id);
684+
app_driver.on_action(&mut driver_ctx, widget_id, action);
687685
}
688686
render_root::RenderRootSignal::StartIme => {
689687
window.set_ime_allowed(true);

masonry/src/lib.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,12 @@
3030
//! fn on_action(&mut self, ctx: &mut DriverCtx<'_>, _widget_id: WidgetId, action: Action) {
3131
//! match action {
3232
//! Action::ButtonPressed(_) => {
33-
//! let mut root: WidgetMut<RootWidget<Portal<Flex>>> = ctx.get_root();
34-
//! let mut portal = RootWidget::child_mut(&mut root);
35-
//! let mut flex = Portal::child_mut(&mut portal);
36-
//! Flex::add_child(&mut flex, Label::new(self.next_task.clone()));
33+
//! ctx.render_root().edit_root_widget(|mut root| {
34+
//! let mut root = root.downcast::<RootWidget<Portal<Flex>>>();
35+
//! let mut portal = RootWidget::child_mut(&mut root);
36+
//! let mut flex = Portal::child_mut(&mut portal);
37+
//! Flex::add_child(&mut flex, Label::new(self.next_task.clone()));
38+
//! });
3739
//! }
3840
//! Action::TextChanged(new_text) => {
3941
//! self.next_task = new_text.clone();

masonry/src/render_root.rs

+13-5
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,16 @@ impl RenderRoot {
206206
root
207207
}
208208

209-
pub(crate) fn root_state(&mut self) -> &mut WidgetState {
209+
pub(crate) fn root_state(&self) -> &WidgetState {
210+
self.widget_arena
211+
.widget_states
212+
.root_token()
213+
.into_child(self.root.id())
214+
.expect("root widget not in widget tree")
215+
.item
216+
}
217+
218+
pub(crate) fn root_state_mut(&mut self) -> &mut WidgetState {
210219
self.widget_arena
211220
.widget_states
212221
.root_token_mut()
@@ -225,8 +234,8 @@ impl RenderRoot {
225234
}
226235
WindowEvent::Resize(size) => {
227236
self.size = size;
228-
self.root_state().request_layout = true;
229-
self.root_state().needs_layout = true;
237+
self.root_state_mut().request_layout = true;
238+
self.root_state_mut().needs_layout = true;
230239
self.run_rewrite_passes();
231240
Handled::Yes
232241
}
@@ -592,8 +601,7 @@ impl RenderRoot {
592601
&self.root_state().focus_chain
593602
}
594603

595-
#[allow(dead_code)]
596-
pub(crate) fn needs_rewrite_passes(&mut self) -> bool {
604+
pub(crate) fn needs_rewrite_passes(&self) -> bool {
597605
self.root_state().needs_rewrite_passes() || self.global_state.focus_changed()
598606
}
599607
}

masonry/src/widget/widget_state.rs

-4
Original file line numberDiff line numberDiff line change
@@ -278,8 +278,4 @@ impl WidgetState {
278278
|| self.needs_update_disabled
279279
|| self.needs_update_stashed
280280
}
281-
282-
pub(crate) fn needs_render(&self) -> bool {
283-
self.needs_anim || self.needs_paint || self.needs_accessibility
284-
}
285281
}

xilem/src/driver.rs

+10-9
Original file line numberDiff line numberDiff line change
@@ -109,15 +109,16 @@ where
109109
if rebuild {
110110
let next_view = (self.logic)(&mut self.state);
111111

112-
let mut root = masonry_ctx.get_root::<RootWidget<View::Widget>>();
113-
114-
next_view.rebuild(
115-
&self.current_view,
116-
&mut self.view_state,
117-
&mut self.ctx,
118-
RootWidget::child_mut(&mut root),
119-
);
120-
self.current_view = next_view;
112+
masonry_ctx.render_root().edit_root_widget(|mut root| {
113+
let mut root = root.downcast::<RootWidget<View::Widget>>();
114+
next_view.rebuild(
115+
&self.current_view,
116+
&mut self.view_state,
117+
&mut self.ctx,
118+
RootWidget::child_mut(&mut root),
119+
);
120+
self.current_view = next_view;
121+
});
121122
}
122123
if cfg!(debug_assertions) && rebuild && !masonry_ctx.content_changed() {
123124
tracing::debug!("Nothing changed as result of action");

0 commit comments

Comments
 (0)