From 87662baa67b7348e67a559501e4b6d413f4e641c Mon Sep 17 00:00:00 2001 From: Olivier FAURE Date: Thu, 23 Jan 2025 17:49:12 +0100 Subject: [PATCH] Update ARCHITECTURE.md file Note: some of the doc depends on #832 and #848 which haven't been merged yet. --- masonry/ARCHITECTURE.md | 116 +++++++++++++++++++--------------------- 1 file changed, 56 insertions(+), 60 deletions(-) diff --git a/masonry/ARCHITECTURE.md b/masonry/ARCHITECTURE.md index fec6fc3c2..7e5906da8 100644 --- a/masonry/ARCHITECTURE.md +++ b/masonry/ARCHITECTURE.md @@ -1,12 +1,8 @@ # ARCHITECTURE -**Note - The crate was migrated from the `PoignardAzur/masonry` repository and ported to work with winit. A lot of stuff was changed during that port and all the documentation wasn't updated. Some of this might be outdated.** - Masonry is a framework that aims to provide the foundation for Rust GUI libraries. -Developers trying to write immediate-mode GUIs, Elm-architecture GUIs, functional reactive GUIs, etc, can import Masonry and get a platform to create windows (using Glazier as a backend) each with a tree of widgets. Each widget has to implement the Widget trait that Masonry provides. - -This crate was originally a fork of Druid that emerged from discussions I had with Raph Levien and Colin Rofls about what it would look like to turn Druid into a foundational library. This means the code looks very similar to Druid's code and has mostly the same dependencies and primitives. +Developers trying to write immediate-mode GUIs, Elm-architecture GUIs, functional reactive GUIs, etc, can import Masonry and get a platform to create windows (using Winit as a backend) each with a tree of widgets. Each widget has to implement the Widget trait that Masonry provides. ## High-level goals @@ -14,7 +10,7 @@ This crate was originally a fork of Druid that emerged from discussions I had wi Masonry has some opinionated design goals: - **Be dumb.** As a general rule, Masonry doesn't do "algorithms". It has no reconciliation logic, no behind-the-scenes dataflow, no clever optimizations, etc. It tries to be efficient, but that efficiency comes from well-designed interfaces and well-placed abstraction boundaries. High-level logic should be implemented in downstream crates. -- **No mutability tricks.** Masonry tries to use as little unsafe code and cells/mutexes as possible. It's designed to work within Rust's ownership system, not to bypass it. +- **No mutability tricks.** Masonry uses no unsafe code and as few cells/mutexes as possible. It's designed to work within Rust's ownership system, not to bypass it. While it relies on the `tree_arena` crate in this repository which *does* perform some mutability tricks, the resulting usage patterns are still very rust-like. - **Facilitate testing.** Masonry implements a `TestHarness` type that helps users write unit tests, including tests with simulated user interactions and screenshot tests. In general, every feature should be designed with easy-to-write high-reliability tests in mind. - **Facilitate debugging.** GUI app bugs are often easy to fix, but extremely painful to track down. GUI framework bugs are worse. Masonry should facilitate reproducing bugs and pinpointing which bit of code they come from. - **Provide reflection.** Masonry should help developers surface some of the inherent structure in GUI programs. It should provide tools out-of-the-box to get information about the widget tree, performance indicators, etc. It should also provide accessibility data out-of-the-box. @@ -22,39 +18,42 @@ Masonry has some opinionated design goals: ## Code layout -### `src/testing/` +### `src/core/` -Contains the TestHarness type, various helper widgets for writing tests, and the snapshot testing code. +Most widget-related code, including the Widget trait, its context types, event types, and the WidgetRef, WidgetMut, and WidgetPod types. + +#### `src/core/widget_state.rs` -### `src/text2/` +Contains the WidgetState type, around which a lot of internal code is based. -Contains text-handling code, for both displaying and editing text. Has been overhauled during the port to winit, but still in rough shape. Here be dragons. +WidgetState is one of the most important internal types in Masonry. +Understanding Masonry pass code will likely be easier if you read WidgetState documentation first. -### `src/text/` +### `src/app/` -Dead code, should probably be cleaned up. +Code for creating a Masonry app, including: -### `src/widget/` +- `event_loop_runner.rs` - glue code between Masonry and winit. +- `render_root.rs` - Masonry's composition root. See **General architecture** section. -Contains widget-related items, including the Widget trait, and the WidgetRef, WidgetMut, and WidgetPod types. +### `src/passes/` -Also includes a list of basic widgets, each defined in a single file. +Masonry's passes are computations that run on the entire widget tree (iff invalidation flags are set) once per frame. -### `src/widget/widget_state.rs` +`event.rs` and `update.rs` include a bunch of related passes. Every other file only includes one pass. `mod.rs` has a utility functions shared between multiple passes. -Contains the WidgetState type, around which a lot of internal code is based. +### `src/doc/` -WidgetState is one of the most important internal types in Masonry. -Understanding Masonry passes will likely be easier if you read WidgetState documentation first. +Documentation for the entire crate. In other projects, this would be an `mdbook` doc, but we choose to directly inline the doc, so that `cargo test` runs on it. +### `src/testing/` -### `src/render_root.rs` +Contains the TestHarness type, various helper widgets for writing tests, and the snapshot testing code. -The composition root of the framework. See **General architecture** section. +### `src/widgets/` -### `src/debug_logger.rs`, `src/debug_values.rs` +A list of basic widgets, each defined in a single file. -WIP logger to get record of widget passes. See . ## Module organization principles @@ -62,17 +61,7 @@ WIP logger to get record of widget passes. See ::set_text()`. This helps Masonry make sure that internal metadata is propagated after every widget change. -Generally speaking, to create a WidgetMut, you need a reference to the parent context that will be updated when the WidgetMut is dropped. That can be the WidgetMut of a parent, an EventCtx / LifecycleCtx, or the WindowRoot. In general, container widgets will have methods such that you can get a WidgetMut of a child from the WidgetMut of a parent. +In general, there's three ways to get a WidgetMut: + +- From a WidgetMut to a parent widget. +- As an argument to the callback passed to `RenderRoot::edit_widget()`. +- As an argument to a callback pushed to the mutate pass. -WidgetMut gives direct mutable access to the widget tree. This can be used by GUI frameworks in their update method, and it can be used in tests to make specific local modifications and test their result. +In most cases, the WidgetMut holds a reference to a WidgetState that will be updated when the WidgetMut is dropped. + +WidgetMut gives direct mutable access to the widget tree. This can be used by GUI frameworks in their tree update methods, and it can be used in tests to make specific local modifications and test their result. ### Tests @@ -147,16 +144,15 @@ Ideally, the harness should provide ways to emulate absolutely every feature tha Each widget has unit tests in its module and major features have modules with dedicated unit test suites. Ideally, we would like to achieve complete coverage within the crate. -#### Mock timers - -For timers in particular, the framework does some special work to simulate the GUI environment. +#### Screenshot tests -The GlobalPassCtx types stores two timer handlers: **timers** and **mock_timer_queue**. The first one connects ids returned by the platform's timer creator to widget ids; the second one stores a list of timer values that have to be manually advanced by calling `TestHarness::move_timers_forward`. +TODO - mention kompari -When a widget calls `request_timer` in a normal running app, a normal timer is requested from the platform. When a widget calls `request_timer` from a simulated app inside a TestHarness, mock_timer_queue is used instead. +TestHarness can render a widget tree, save the result to an image, and compare the image to a stored snapshot. This lets us check that (1) our widgets' paint methods don't panic and (2) changes don't introduce accidental regression in their visual appearance. -All this means you can have timer-based tests without *actually* having to sleep for the duration of the timer. +The screenshots are stored using git LFS, which adds some minor complications but avoids the overhead of committing files directly to Git. +We include some of the screenshots in the documentation; because `docs.rs` doesn't have access to LFS files, we use the `include_screenshot!` to instead link to `https://media.githubusercontent.com` when building doc for `docs.rs`. ## VS Code markers