Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

xilem: Docs updating #818

Merged
merged 10 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .clippy.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ trivial-copy-size-limit = 16
# END LINEBENDER LINT SET

# Don't warn about these identifiers when using clippy::doc_markdown.
doc-valid-idents = ["MathML", ".."]
doc-valid-idents = ["MathML", "..", "RustNL"]

# The default clippy value for this is 250, which causes warnings for rather simple types
# like Box<dyn Fn(&mut Env, &T)>, which seems overly strict. The new value of 400 is
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ jobs:

- name: Run cargo rdme (tree_arena)
run: cargo rdme --check --heading-base-level=0 --workspace-project=tree_arena

- name: Run cargo rdme (Xilem)
run: cargo rdme --check --heading-base-level=0 --workspace-project=xilem

clippy-stable:
name: cargo clippy
Expand Down
159 changes: 132 additions & 27 deletions xilem/README.md
ArtyomSinyugin marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,138 @@

</div>

Xilem is a UI toolkit.
It combines ideas from Flutter, SwiftUI, and Elm.
Like all of these, it uses lightweight view objects, diffing them to provide minimal updates to a retained UI.
Like SwiftUI, it is strongly typed.
For more details on Xilem's reactive architecture see [Xilem: an architecture for UI in Rust].

Xilem's reactive layer is built on top of a wide array of foundational Rust UI projects, e.g.:
* Widgets are provided by [Masonry], which is a fork of the now discontinued [Druid] UI toolkit.
* Rendering is provided by [Vello], a high performance GPU compute-centric 2D renderer.
* GPU compute infrastructure is provided by [wgpu].
* Text support is provided by [Parley], [Fontique], [Swash], and [Skrifa].
* Accessibility is provided by [AccessKit].
* Window handling is provided by [winit].

Xilem can currently be considered to be in an alpha state.
Lots of things need improvements.
<!-- We use cargo-rdme to update the README with the contents of lib.rs.
To edit the following section, update it in lib.rs, then run:
cargo rdme --workspace-project=color --heading-base-level=0
Full documentation at https://github.com/orium/cargo-rdme -->

<!-- Intra-doc links used in lib.rs should be evaluated here.
See https://linebender.org/blog/doc-include/ for related discussion. -->

[accesskit_docs]: https://docs.rs/accesskit/latest/accesskit
[crate::core::lens]: https://docs.rs/xilem_core/latest/xilem_core/fn.lens.html
[crate::core::memoize]: https://docs.rs/xilem_core/latest/xilem_core/fn.memoize.html
[crate::view::button]: https://docs.rs/xilem/latest/xilem/view/fn.button.html
[crate::view::flex]: https://docs.rs/xilem/latest/xilem/view/fn.flex.html
[crate::view::grid]: https://docs.rs/xilem/latest/xilem/view/fn.grid.html
[crate::view::image]: https://docs.rs/xilem/latest/xilem/view/fn.image.html
[crate::view::portal]: https://docs.rs/xilem/latest/xilem/view/fn.portal.html
[crate::view::progress_bar]: https://docs.rs/xilem/latest/xilem/view/fn.progress_bar.html
[crate::view::prose]: https://docs.rs/xilem/latest/xilem/view/fn.prose.html
[crate::view::sized_box]: https://docs.rs/xilem/latest/xilem/view/fn.sized_box.html
[crate::view::task]: https://docs.rs/xilem/latest/xilem/view/fn.task.html
[crate::view::textbox]: https://docs.rs/xilem/latest/xilem/view/fn.textbox.html
[crate::view::zstack]: https://docs.rs/xilem/latest/xilem/view/fn.zstack.html
[masonry::parley]: https://docs.rs/parley/latest/parley
[masonry::vello::wgpu]: https://docs.rs/wgpu/latest/wgpu
[masonry::vello]: https://docs.rs/vello/latest/vello/
[xilem_core]: https://docs.rs/parley_core/latest/xilem_core
[xilem_examples]: ./examples/

<!-- markdownlint-disable MD053 -->
<!-- cargo-rdme start -->

Xilem is a UI toolkit. It combines ideas from `Flutter`, `SwiftUI`, and `Elm`.
Like all of these, it uses lightweight view objects, diffing them to provide
minimal updates to a retained UI. Like `SwiftUI`, it is strongly typed.

The talk *[Xilem: Let's Build High Performance Rust UI](https://www.youtube.com/watch?v=OvfNipIcRiQ)* by Raph Levien
was presented at the RustNL conference in 2024, and gives a video introduction to these ideas.
Xilem is implemented as a reactive layer on top of [Masonry][masonry], a widget toolkit which is developed alongside Xilem.
Masonry itself is built on top of a wide array of foundational Rust UI projects:

* Rendering is provided by [Vello][masonry::vello], a high performance GPU compute-centric 2D renderer.
* GPU compute infrastructure is provided by [wgpu][masonry::vello::wgpu].
* Text layout is provided by [Parley][masonry::parley].
* Accessibility is provided by [AccessKit][] ([docs][accesskit_docs]).
* Window handling is provided by [winit][].

Xilem can currently be considered to be in an alpha state. Lots of things need improvements (including this documentation!).

There is also a [blog post][xilem_blog] from when Xilem was first introduced.

## Example

A simple incrementing counter application looks like:

```rust
use winit::error::EventLoopError;
use xilem::view::{button, flex, label};
use xilem::{EventLoop, WidgetView, Xilem};

#[derive(Default)]
struct Counter {
num: i32,
}

fn app_logic(data: &mut Counter) -> impl WidgetView<Counter> {
flex((
label(format!("{}", data.num)),
button("increment", |data: &mut Counter| data.num += 1),
))
}

fn main() -> Result<(), EventLoopError> {
let app = Xilem::new(Counter::default(), app_logic);
app.run_windowed(EventLoop::with_user_event(), "Counter app".into())?;
Ok(())
}
```

A key feature of Xilem's architecture is that the application's state, in this case `Counter`, is an arbitrary `'static` Rust type.
In this example, `app_logic` is the root component, which creates the view value it returns.
This, in turn, leads to corresponding Masonry widgets being created, in this case a button and a label.
When the button is pressed, the number will be incremented, and then `app_logic` will be re-ran.
The returned view will be compared with its previous value, which will minimally update the contents of these widgets.
As the `num` field's value has changed, the `label`'s formatted text will be different.
This means that the label widget's text will be updated, updating the value displayed to the user.
In this case, because the button is the same, it will not be updated.

More examples can be found [in the repository][xilem_examples].

**Note: The linked examples are for the `main` branch of Xilem. If you are using a released version, please view the examples in the tag for that release.**

## Reactive layer

The core concepts of the reactive layer are explained in [Xilem Core][xilem_core].

## View elements

The primitives your `Xilem` app’s view tree will generally be constructed from:

* [`flex`][crate::view::flex]: defines how items will be arranged in a row or column
* [`grid`][crate::view::grid]: divides a window into regions and defines the relationship
between inner elements in terms of size and position
* [`sized_box`][crate::view::sized_box]: forces its child to have a specific width and/or height
* [`button`][crate::view::button]: basic button element
* [`image`][crate::view::image]: displays a bitmap image
* [`portal`][crate::view::portal]: a scrollable region
* [`progress_bar`][crate::view::progress_bar]: progress bar element
* [`prose`][crate::view::prose]: displays immutable, selectable text
* [`textbox`][crate::view::textbox]: allows text to be edited by the user
* [`task`][crate::view::task]: launch an async task which will run until the view is no longer in the tree
* [`zstack`][crate::view::zstack]: an element that lays out its children on top of each other

You should also expect to use the adapters from Xilem Core, including:

* [`lens`][crate::core::lens]: an adapter for using a component from a field of the current state.
* [`memoize`][crate::core::memoize]: allows you to avoid recreating views you know won't have changed, based on a key.

[accesskit_docs]: accesskit
[AccessKit]: https://accesskit.dev/
[Druid]: https://crates.io/crates/druid
[Fontique]: https://crates.io/crates/fontique
[Masonry]: https://crates.io/crates/masonry
[Parley]: https://crates.io/crates/parley
[skrifa]: https://crates.io/crates/skrifa
[swash]: https://crates.io/crates/swash
[Vello]: https://crates.io/crates/vello
[winit]: https://crates.io/crates/winit
[xilem_blog]: https://raphlinus.github.io/rust/gui/2022/05/07/ui-architecture.html
[xilem_examples]: https://github.com/linebender/xilem/tree/main/xilem/examples

<!-- cargo-rdme end -->
<!-- markdownlint-enable MD053 -->

## Minimum supported Rust Version (MSRV)

Expand Down Expand Up @@ -71,15 +187,4 @@ Some files used for examples are under different licenses:

Note that these files are *not* distributed with the released crate.

[Masonry]: https://crates.io/crates/masonry
[Druid]: https://crates.io/crates/druid
[Vello]: https://crates.io/crates/vello
[wgpu]: https://crates.io/crates/wgpu
[Parley]: https://crates.io/crates/parley
[Fontique]: https://crates.io/crates/fontique
[Swash]: https://crates.io/crates/swash
[Skrifa]: https://crates.io/crates/skrifa
[AccessKit]: https://crates.io/crates/accesskit
[winit]: https://crates.io/crates/winit
[Xilem: an architecture for UI in Rust]: https://raphlinus.github.io/rust/gui/2022/05/07/ui-architecture.html
[Rust code of conduct]: https://www.rust-lang.org/policies/code-of-conduct
105 changes: 103 additions & 2 deletions xilem/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,106 @@
// Copyright 2024 the Xilem Authors
// SPDX-License-Identifier: Apache-2.0

//! An experimental Rust native UI framework.

//! Xilem is a UI toolkit. It combines ideas from `Flutter`, `SwiftUI`, and `Elm`.
//! Like all of these, it uses lightweight view objects, diffing them to provide
//! minimal updates to a retained UI. Like `SwiftUI`, it is strongly typed.
//!
//! The talk *[Xilem: Let's Build High Performance Rust UI](https://www.youtube.com/watch?v=OvfNipIcRiQ)* by Raph Levien
//! was presented at the RustNL conference in 2024, and gives a video introduction to these ideas.
//! Xilem is implemented as a reactive layer on top of [Masonry][masonry], a widget toolkit which is developed alongside Xilem.
//! Masonry itself is built on top of a wide array of foundational Rust UI projects:
//!
//! * Rendering is provided by [Vello][masonry::vello], a high performance GPU compute-centric 2D renderer.
//! * GPU compute infrastructure is provided by [wgpu][masonry::vello::wgpu].
//! * Text layout is provided by [Parley][masonry::parley].
//! * Accessibility is provided by [AccessKit][] ([docs][accesskit_docs]).
//! * Window handling is provided by [winit][].
//!
//! Xilem can currently be considered to be in an alpha state. Lots of things need improvements (including this documentation!).
//!
//! There is also a [blog post][xilem_blog] from when Xilem was first introduced.
//!
//! ## Example
//!
//! A simple incrementing counter application looks like:
//!
//! ```rust,no_run
//! use winit::error::EventLoopError;
//! use xilem::view::{button, flex, label};
//! use xilem::{EventLoop, WidgetView, Xilem};
//!
//! #[derive(Default)]
//! struct Counter {
//! num: i32,
//! }
//!
//! fn app_logic(data: &mut Counter) -> impl WidgetView<Counter> {
//! flex((
//! label(format!("{}", data.num)),
//! button("increment", |data: &mut Counter| data.num += 1),
//! ))
//! }
//!
//! fn main() -> Result<(), EventLoopError> {
//! let app = Xilem::new(Counter::default(), app_logic);
//! app.run_windowed(EventLoop::with_user_event(), "Counter app".into())?;
//! Ok(())
//! }
//! ```
//!
//! A key feature of Xilem's architecture is that the application's state, in this case `Counter`, is an arbitrary `'static` Rust type.
//! In this example, `app_logic` is the root component, which creates the view value it returns.
//! This, in turn, leads to corresponding Masonry widgets being created, in this case a button and a label.
//! When the button is pressed, the number will be incremented, and then `app_logic` will be re-ran.
//! The returned view will be compared with its previous value, which will minimally update the contents of these widgets.
//! As the `num` field's value has changed, the `label`'s formatted text will be different.
//! This means that the label widget's text will be updated, updating the value displayed to the user.
//! In this case, because the button is the same, it will not be updated.
//!
//! More examples can be found [in the repository][xilem_examples].
//!
//! **Note: The linked examples are for the `main` branch of Xilem. If you are using a released version, please view the examples in the tag for that release.**
//!
//! ## Reactive layer
//!
//! The core concepts of the reactive layer are explained in [Xilem Core][xilem_core].
//!
//! ## View elements
//!
//! The primitives your `Xilem` app’s view tree will generally be constructed from:
//!
//! * [`flex`][crate::view::flex]: defines how items will be arranged in a row or column
//! * [`grid`][crate::view::grid]: divides a window into regions and defines the relationship
//! between inner elements in terms of size and position
//! * [`sized_box`][crate::view::sized_box]: forces its child to have a specific width and/or height
//! * [`button`][crate::view::button]: basic button element
//! * [`image`][crate::view::image]: displays a bitmap image
//! * [`portal`][crate::view::portal]: a scrollable region
//! * [`progress_bar`][crate::view::progress_bar]: progress bar element
//! * [`prose`][crate::view::prose]: displays immutable, selectable text
//! * [`textbox`][crate::view::textbox]: allows text to be edited by the user
//! * [`task`][crate::view::task]: launch an async task which will run until the view is no longer in the tree
//! * [`zstack`][crate::view::zstack]: an element that lays out its children on top of each other
//!
//! You should also expect to use the adapters from Xilem Core, including:
//!
//! * [`lens`][crate::core::lens]: an adapter for using a component from a field of the current state.
//! * [`memoize`][crate::core::memoize]: allows you to avoid recreating views you know won't have changed, based on a key.
//!
//! [accesskit_docs]: accesskit
//! [AccessKit]: https://accesskit.dev/
//! [Druid]: https://crates.io/crates/druid
//! [Fontique]: https://crates.io/crates/fontique
//! [Masonry]: https://crates.io/crates/masonry
//! [Parley]: https://crates.io/crates/parley
//! [skrifa]: https://crates.io/crates/skrifa
//! [swash]: https://crates.io/crates/swash
//! [Vello]: https://crates.io/crates/vello
//! [winit]: https://crates.io/crates/winit
//! [xilem_blog]: https://raphlinus.github.io/rust/gui/2022/05/07/ui-architecture.html
//! [xilem_examples]: https://github.com/linebender/xilem/tree/main/xilem/examples

#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46134943?s=48&v=4")]
// LINEBENDER LINT SET - lib.rs - v1
// See https://linebender.org/wiki/canonical-lints/
// These lints aren't included in Cargo.toml because they
Expand Down Expand Up @@ -67,6 +165,7 @@ pub mod view;
pub use any_view::AnyWidgetView;
pub use driver::{async_action, MasonryDriver, MasonryProxy, ASYNC_MARKER_WIDGET};

/// Runtime builder.
#[must_use = "A Xilem app does nothing unless ran."]
pub struct Xilem<State, Logic> {
state: State,
Expand Down Expand Up @@ -108,6 +207,7 @@ where
}

// TODO: Make windows a specific view
/// Run app with default window attributes.
pub fn run_windowed(
self,
// We pass in the event loop builder to allow
Expand All @@ -129,6 +229,7 @@ where
}

// TODO: Make windows into a custom view
/// Run app with custom window attributes.
pub fn run_windowed_in(
self,
mut event_loop: EventLoopBuilder,
Expand Down
45 changes: 45 additions & 0 deletions xilem/src/view/button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,48 @@ use crate::{Affine, MessageResult, Pod, ViewCtx, ViewId};
use super::Transformable;

/// A button which calls `callback` when the primary mouse button (normally left) is pressed.
///
/// # Examples
/// To use button provide it with a button text and a closure.
/// ```ignore
/// use xilem::view::button;
///
/// struct State {
/// int: i32,
/// }
///
/// impl State {
/// fn increase(&mut self) {
/// self.int += 1;
/// }
/// }
///
/// button("Button", |state: &mut State| {
/// state.increase();
/// })
/// ```
///
/// Create a `button` with a custom `label`.
///
/// ```ignore
/// use xilem::view::{button, label};
///
/// struct State {
/// int: i32,
/// }
///
/// impl State {
/// fn increase(&mut self) {
/// self.int += 1;
/// }
/// }
///
/// let label = label("Button").weight(FontWeight::BOLD);
///
/// button(label, |state: &mut State| {
/// state.increase();
/// })
/// ```
pub fn button<State, Action>(
label: impl Into<Label>,
callback: impl Fn(&mut State) -> Action + Send + 'static,
Expand Down Expand Up @@ -40,6 +82,9 @@ pub fn button_any_pointer<State, Action>(
}
}

/// The [`View`] created by [`button`] from a `label` and a callback.
///
/// See `button` documentation for more context.
#[must_use = "View values do nothing unless provided to Xilem."]
pub struct Button<F> {
label: Label,
Expand Down
Loading
Loading