diff --git a/masonry/README.md b/masonry/README.md
index 4e9e2b19..c1d2f873 100644
--- a/masonry/README.md
+++ b/masonry/README.md
@@ -39,8 +39,6 @@ use masonry::widget::{Button, Flex, Label, Portal, RootWidget, Textbox, WidgetMu
use masonry::{Action, AppDriver, DriverCtx, WidgetId};
use winit::window::Window;
-const VERTICAL_WIDGET_SPACING: f64 = 20.0;
-
struct Driver {
next_task: String,
}
@@ -63,6 +61,8 @@ impl AppDriver for Driver {
}
fn main() {
+ const VERTICAL_WIDGET_SPACING: f64 = 20.0;
+
let main_widget = Portal::new(
Flex::column()
.with_child(
@@ -91,7 +91,9 @@ fn main() {
}
```
-### Create feature flags
+For more information, see [the documentation module](https://docs.rs/masonry/latest/masonry/doc/).
+
+### Crate feature flags
The following feature flags are available:
diff --git a/masonry/src/doc/01_creating_app.md b/masonry/src/doc/01_creating_app.md
new file mode 100644
index 00000000..b4fff80f
--- /dev/null
+++ b/masonry/src/doc/01_creating_app.md
@@ -0,0 +1,263 @@
+# Building a "To-Do List" app
+
+
+
+
+
+
+> [!TIP]
+>
+> This file is intended to be read in rustdoc.
+> Use `cargo doc --open --package masonry --no-deps`.
+
+
+
+
+**TODO - Add screenshots - see [#501](https://github.com/linebender/xilem/issues/501)**
+
+This tutorial explains how to build a simple Masonry app, step by step.
+Though it isn't representative of how we expect Masonry to be used, it does cover the basic architecture.
+
+The app we'll create is identical to the to-do-list example shown in the README.
+
+## The Widget tree
+
+Let's start with the `main()` function.
+
+```rust,ignore
+fn main() {
+ const VERTICAL_WIDGET_SPACING: f64 = 20.0;
+
+ use masonry::widget::{Button, Flex, Portal, RootWidget, Textbox};
+
+ let main_widget = Portal::new(
+ Flex::column()
+ .with_child(
+ Flex::row()
+ .with_flex_child(Textbox::new(""), 1.0)
+ .with_child(Button::new("Add task")),
+ )
+ .with_spacer(VERTICAL_WIDGET_SPACING),
+ );
+ let main_widget = RootWidget::new(main_widget);
+
+ // ...
+
+ masonry::event_loop_runner::run(
+ // ...
+ main_widget,
+ // ...
+ )
+ .unwrap();
+}
+```
+
+First we create our initial widget hierarchy.
+We're trying to build a simple to-do list app, so our root widget is a scrollable area ([`Portal`]) with a vertical list ([`Flex`]), whose first row is a horizontal list (`Flex` again) containing a text field ([`Textbox`]) and an "Add task" button ([`Button`]).
+
+We wrap it in a [`RootWidget`], whose main purpose is to include a `Window` node in the accessibility tree.
+
+At the end of the main function, we pass the root widget to the `event_loop_runner::run` function.
+That function starts the main event loop, which runs until the user closes the window.
+During the course of the event loop, the widget tree will be displayed, and updated as the user interacts with the app.
+
+
+## The `Driver`
+
+To handle user interactions, we need to implement the [`AppDriver`] trait:
+
+```rust,ignore
+trait AppDriver {
+ fn on_action(&mut self, ctx: &mut DriverCtx<'_>, widget_id: WidgetId, action: Action);
+}
+```
+
+Every time the user interacts with the app in a meaningful way (clicking a button, entering text, etc), an [`Action`] is emitted, and the `on_action` method is called.
+
+That method gives our app a [`DriverCtx`] context, which we can use to access the root widget, and a [`WidgetId`] identifying the widget that emitted the action.
+
+We create a `Driver` struct to store a very simple app's state, and we implement the `AppDriver` trait for it:
+
+```rust,ignore
+use masonry::app_driver::{AppDriver, DriverCtx};
+use masonry::{Action, WidgetId};
+use masonry::widget::{Label};
+
+struct Driver {
+ next_task: String,
+}
+
+impl AppDriver for Driver {
+ fn on_action(&mut self, ctx: &mut DriverCtx<'_>, _widget_id: WidgetId, action: Action) {
+ match action {
+ Action::ButtonPressed(_) => {
+ let mut root: WidgetMut>> = ctx.get_root();
+ let mut portal = root.child_mut();
+ let mut flex = portal.child_mut();
+ flex.add_child(Label::new(self.next_task.clone()));
+ }
+ Action::TextChanged(new_text) => {
+ self.next_task = new_text.clone();
+ }
+ _ => {}
+ }
+ }
+}
+```
+
+In `on_action`, we handle the two possible actions:
+
+- `TextChanged`: Update the text of the next task.
+- `ButtonPressed`: Add a task to the list.
+
+Because our widget tree only has one button and one textbox, there is no possible ambiguity as to which widget emitted the event, so we can ignore the `WidgetId` argument.
+
+When handling `ButtonPressed`:
+
+- `ctx.get_root()` returns a `WidgetMut>`.
+- `root.child_mut()` returns a `WidgetMut>` for the `Portal`.
+- `portal.child_mut()` returns a `WidgetMut` for the `Flex`.
+
+A [`WidgetMut`] is a smart reference type which lets us modify the widget tree.
+It's set up to automatically propagate update flags and update internal state when dropped.
+
+We use [`WidgetMut::::add_child()`][add_child] to add a new `Label` with the text of our new task to our list.
+
+In our main function, we create a `Driver` and pass it to `event_loop_runner::run`:
+
+```rust,ignore
+ // ...
+
+ let driver = Driver {
+ next_task: String::new(),
+ };
+
+ // ...
+
+ masonry::event_loop_runner::run(
+ // ...
+ main_widget,
+ driver,
+ )
+ .unwrap();
+```
+
+## Bringing it all together
+
+The last step is to create our Winit window and start our main loop.
+
+```rust,ignore
+ use masonry::dpi::LogicalSize;
+ use winit::window::Window;
+
+ let window_attributes = Window::default_attributes()
+ .with_title("To-do list")
+ .with_resizable(true)
+ .with_min_inner_size(LogicalSize::new(400.0, 400.0));
+
+ masonry::event_loop_runner::run(
+ masonry::event_loop_runner::EventLoop::with_user_event(),
+ window_attributes,
+ main_widget,
+ driver,
+ )
+ .unwrap();
+```
+
+Our complete program therefore looks like this:
+
+```rust,ignore
+fn main() {
+ const VERTICAL_WIDGET_SPACING: f64 = 20.0;
+
+ use masonry::widget::{Button, Flex, Portal, RootWidget, Textbox};
+
+ let main_widget = Portal::new(
+ Flex::column()
+ .with_child(
+ Flex::row()
+ .with_flex_child(Textbox::new(""), 1.0)
+ .with_child(Button::new("Add task")),
+ )
+ .with_spacer(VERTICAL_WIDGET_SPACING),
+ );
+ let main_widget = RootWidget::new(main_widget);
+
+ use masonry::app_driver::{AppDriver, DriverCtx};
+ use masonry::{Action, WidgetId};
+ use masonry::widget::{Label};
+
+ struct Driver {
+ next_task: String,
+ }
+
+ impl AppDriver for Driver {
+ fn on_action(&mut self, ctx: &mut DriverCtx<'_>, _widget_id: WidgetId, action: Action) {
+ match action {
+ Action::ButtonPressed(_) => {
+ let mut root: WidgetMut>> = ctx.get_root();
+ let mut portal = root.child_mut();
+ let mut flex = portal.child_mut();
+ flex.add_child(Label::new(self.next_task.clone()));
+ }
+ Action::TextChanged(new_text) => {
+ self.next_task = new_text.clone();
+ }
+ _ => {}
+ }
+ }
+ }
+
+ let driver = Driver {
+ next_task: String::new(),
+ };
+
+use masonry::dpi::LogicalSize;
+ use winit::window::Window;
+
+ let window_attributes = Window::default_attributes()
+ .with_title("To-do list")
+ .with_resizable(true)
+ .with_min_inner_size(LogicalSize::new(400.0, 400.0));
+
+ masonry::event_loop_runner::run(
+ masonry::event_loop_runner::EventLoop::with_user_event(),
+ window_attributes,
+ main_widget,
+ driver,
+ )
+ .unwrap();
+}
+```
+
+All the Masonry examples follow this structure:
+
+- An initial widget tree.
+- A struct implementing `AppDriver` to handle user interactions.
+- A Winit window and event loop.
+
+Some examples also define custom Widgets, but you can build an interactive app with Masonry's base widget set, though it's not Masonry's intended use.
+
+
+## Higher layers
+
+The above example isn't representative of how we expect Masonry to be used.
+
+In practice, we expect most implementations of `AppDriver` to be GUI frameworks built on top of Masonry and using it to back their own abstractions.
+
+Currently, the only public framework built with Masonry is Xilem, though we hope others will develop as Masonry matures.
+
+Most of this documentation is written to help developers trying to build such a framework.
+
+[`Portal`]: crate::widget::Portal
+[`Flex`]: crate::widget::Flex
+[`Textbox`]: crate::widget::Textbox
+[`Button`]: crate::widget::Button
+[`RootWidget`]: crate::widget::RootWidget
+
+[`AppDriver`]: crate::AppDriver
+[`Action`]: crate::Action
+[`DriverCtx`]: crate::DriverCtx
+[`WidgetId`]: crate::WidgetId
+[`WidgetMut`]: crate::widget::WidgetMut
+[add_child]: crate::widget::WidgetMut::add_child
diff --git a/masonry/src/doc/02_implementing_widget.md b/masonry/src/doc/02_implementing_widget.md
new file mode 100644
index 00000000..47084cd6
--- /dev/null
+++ b/masonry/src/doc/02_implementing_widget.md
@@ -0,0 +1,336 @@
+# Creating a new Widget
+
+
+
+
+
+
+> [!TIP]
+>
+> This file is intended to be read in rustdoc.
+> Use `cargo doc --open --package masonry --no-deps`.
+
+
+
+**TODO - Add screenshots - see [#501](https://github.com/linebender/xilem/issues/501)**
+
+If you're building your own GUI framework on top of Masonry, or even a GUI app with specific needs, you'll want to specify your own widgets.
+
+This tutorial explains how to create a simple leaf widget.
+
+
+## The Widget trait
+
+Widgets are types which implement the [`Widget`] trait.
+
+This trait includes a set of methods that must be implemented to hook into Masonry's internals:
+
+```rust,ignore
+trait Widget {
+ fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &PointerEvent);
+ fn on_text_event(&mut self, ctx: &mut EventCtx, event: &TextEvent);
+ fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent);
+
+ fn on_anim_frame(&mut self, ctx: &mut UpdateCtx, interval: u64);
+ fn update(&mut self, ctx: &mut UpdateCtx, event: &Update);
+
+ fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size;
+
+ fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene);
+ fn accessibility_role(&self) -> Role;
+ fn accessibility(&mut self, ctx: &mut AccessCtx, node: &mut NodeBuilder);
+
+ // ...
+}
+```
+
+These methods are called by the framework at various points, with a `FoobarCtx` parameter giving information about the current widget (for example its size, position, or whether it's currently hovered).
+The information accessible from the context argument depends on the method.
+
+In the course of a frame, Masonry will run a series of passes over the widget tree, which will call these methods at different points:
+
+- `on_pointer_event`, `on_text_event` and `on_access_event` are called once after a user-initiated event (like a mouse click or keyboard input).
+- `on_anim_frame` is called once per frame for animated widgets.
+- `update` is called many times during a frame, with various events reflecting changes in the widget's state (for instance, it gets or loses text focus).
+- `layout` is called during Masonry's layout pass. It takes size constraints and returns the widget's desired size.
+- `paint`, `accessibility_role` and `accessibility` are called roughly every frame for every widget, to allow them to draw to the screen and describe their structure to assistive technologies.
+
+Most passes will skip most widgets by default.
+For instance, the paint pass will only call a widget's `paint` method once, and then cache the resulting scene.
+If your widget's appearance is changed by another method, you need to call `ctx.request_render()` to tell the framework to re-run the paint and accessibility passes.
+
+Most context types include these methods for requesting future passes:
+
+- `request_render()`
+- `request_paint_only()`
+- `request_accessibility_update()`
+- `request_layout()`
+- `request_anim_frame()`
+
+
+## Widget mutation
+
+In Masonry, widgets generally can't be mutated directly.
+That is to say, even if you own a window, and even if that window holds a widget tree with a `Label` instance, you can't get a `&mut Label` directly from that window.
+
+Instead, there are two ways to mutate `Label`:
+
+- Inside a Widget method. Most methods (`on_pointer_event`, `update`, `layout`, etc) take a `&mut self` argument.
+- Through a [`WidgetMut`] wrapper. So, to change your label's text, you will call `WidgetMut::