xilem/masonry
Olivier FAURE 9ffc650983
Remove `Box<dyn Widget>` from a few places (#837)
Having `Box<dyn Widget>` implement the `Widget` trait is a crutch that
makes a bunch of things more complicated.

It leads to double-boxing in cases when the `dyn Widget` is itself a
`Box<dyn Widget>` (especially since the arena's current implementation
boxes all widgets by default), makes it harder to reason about
downcasting, and ends up producing a lot of code to handle the edge
cases.

On the xilem side, `Box<dyn Widget>` is slightly redundant with
`DynWidget`.

This PR still leaves a lot of boxing. On the long term, we'd like for
the arena to pack arbitrary widgets efficiently using some kind of
archetype scheme, but in the meantime, boxing will have to do.

Changes in this PR:
- New `FromDynWidget` trait that maybe-downcasts widgets to a
maybe-sized `Widget` type.
- Most places that accept `Widget` now accept `Widget + ?Sized`.
- Remove `impl Widget for Box<dyn Widget>`.
- Replace all instances of `WidgetPod<Box<dyn Widget>>` with
`WidgetPod<dyn Widget>`.
- Replace all instances of `xilem::Pod<Box<dyn Widget>>` with
`xilem::Pod<dyn Widget>`.
- Rename WidgetPod to WidgetBox in xilem_core example to avoid
ambiguity.
2025-01-20 12:52:08 +00:00
..
examples Apply is/has naming convention for flags and methods (#831) 2025-01-16 15:17:39 +00:00
resources Run tests in CI, and fix fonts for snapshot testing (#233) 2024-08-05 13:01:47 +00:00
src Remove `Box<dyn Widget>` from a few places (#837) 2025-01-20 12:52:08 +00:00
.gitignore Move crates to the repository root (#302) 2024-05-11 21:59:03 +00:00
ARCHITECTURE.md Document and clean up WidgetState (#707) 2024-10-22 09:24:39 +00:00
Cargo.toml Update to git Parley and Vello. (#798) 2024-12-20 14:52:05 +00:00
LICENSE Move crates to the repository root (#302) 2024-05-11 21:59:03 +00:00
README.md Implement very basic widget inspector (#820) 2025-01-15 17:10:20 +00:00

README.md

Masonry

A foundational framework for Rust GUI libraries.

Latest published version. Documentation build status. Apache 2.0 license.

Linebender Zulip chat. GitHub Actions CI status. Dependency staleness status.

Masonry gives you a platform to create windows (using winit as a backend) each with a tree of widgets. It also gives you tools to inspect that widget tree at runtime, write unit tests on it, and generally have an easier time debugging and maintaining your app.

The framework is not opinionated about what your user-facing abstraction will be: you can implement immediate-mode GUI, the Elm architecture, functional reactive GUI, etc, on top of Masonry.

See Xilem as an example of reactive UI built on top of Masonry.

Masonry was originally a fork of Druid that emerged from discussions within the Linebender community about what it would look like to turn Druid into a foundational library.

Masonry can currently be considered to be in an alpha state. Lots of things need improvements, e.g. text input is janky and snapshot testing is not consistent across platforms.

Example

The to-do-list example looks like this:

use masonry::dpi::LogicalSize;
use masonry::widget::{Button, Flex, Label, Portal, RootWidget, Textbox, WidgetMut};
use masonry::{Action, AppDriver, DriverCtx, WidgetId};
use winit::window::Window;

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(_) => {
                ctx.render_root().edit_root_widget(|mut root| {
                    let mut root = root.downcast::<RootWidget<Portal<Flex>>>();
                    let mut portal = RootWidget::child_mut(&mut root);
                    let mut flex = Portal::child_mut(&mut portal);
                    Flex::add_child(&mut flex, Label::new(self.next_task.clone()));
                });
            }
            Action::TextChanged(new_text) => {
                self.next_task = new_text.clone();
            }
            _ => {}
        }
    }
}

fn main() {
    const VERTICAL_WIDGET_SPACING: f64 = 20.0;

    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 window_size = LogicalSize::new(400.0, 400.0);
    let window_attributes = Window::default_attributes()
        .with_title("To-do list")
        .with_resizable(true)
        .with_min_inner_size(window_size);

    masonry::event_loop_runner::run(
        masonry::event_loop_runner::EventLoop::with_user_event(),
        window_attributes,
        RootWidget::new(main_widget),
        Driver {
            next_task: String::new(),
        },
    )
    .unwrap();
}

For more information, see the documentation module.

Crate feature flags

The following feature flags are available:

  • tracy: Enables creating output for the Tracy profiler using tracing-tracy. This can be used by installing Tracy and connecting to a Masonry with this feature enabled.

Debugging features

Masonry apps currently ship with two debugging features built in:

  • A rudimentary widget inspector - toggled by F11 key.
  • A debug mode painting widget layout rectangles - toggled by F12 key.

Minimum supported Rust Version (MSRV)

This version of Masonry has been verified to compile with Rust 1.82 and later.

Future versions of Masonry might increase the Rust version requirement. It will not be treated as a breaking change and as such can even happen with small patch releases.

Click here if compiling fails.

As time has passed, some of Masonry's dependencies could have released versions with a higher Rust requirement. If you encounter a compilation issue due to a dependency and don't want to upgrade your Rust toolchain, then you could downgrade the dependency.

# Use the problematic dependency's name and version
cargo update -p package_name --precise 0.1.1

Community

Discussion of Masonry development happens in the Linebender Zulip, specifically the #masonry channel. All public content can be read without logging in.

Contributions are welcome by pull request. The Rust code of conduct applies.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache 2.0 license, shall be licensed as noted in the License section, without any additional terms or conditions.

License

Licensed under the Apache License, Version 2.0 (LICENSE or http://www.apache.org/licenses/LICENSE-2.0)