1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
//! # recompose
//!
//! `recompose` is a library for [Bevy](https://docs.rs/bevy/) that provides a declarative way to compose structures in
//! a way that is easy to write, and is ECS- and borrow-checker friendly.
//!
//! It is most useful for UI-building, but can be applied to other ECS-structures as well. For more information, check
//! out the [examples](https://github.com/ad-kr/recompose/tree/main/examples) and docs.
//!
//! # Example
//! ```
//! use bevy::prelude::*;
//! use recompose::prelude::*;
//!
//! fn main() {
//! App::new()
//! .add_plugins(DefaultPlugins)
//! .add_plugins(RecomposePlugin)
//! .add_systems(Startup, setup)
//! .run();
//! }
//!
//! fn setup(mut commands: Commands) {
//! commands.spawn(Camera2d);
//! commands.spawn((Root::new(squares), Node::default()));
//! }
//!
//! // `Fn(&mut Scope) -> impl Compose` implements Compose, so we can use functions for simple composables.
//! fn squares(cx: &mut Scope) -> impl Compose {
//! let count = cx.use_state(42);
//!
//! Node {
//! display: Display::Flex,
//! column_gap: Val::Px(8.0),
//! ..default()
//! }
//! .children((
//! Square(Srgba::RED.into()),
//! Square(Srgba::GREEN.into()),
//! Square(Srgba::BLUE.into()),
//! Text::new(count.to_string()).to_compose(),
//! ))
//! }
//!
//! #[derive(Clone)]
//! struct Square(Color);
//!
//! // For more complex composables with input, we can implement Compose directly on a struct.
//! impl Compose for Square {
//! fn compose<'a>(&self, _: &mut Scope) -> impl Compose + 'a {
//! (
//! Node {
//! width: Val::Px(32.0),
//! height: Val::Px(32.0),
//! ..default()
//! },
//! BackgroundColor(self.0),
//! )
//! .to_compose()
//! }
//! }
//! ```
//! # Hooks and composing
//! If you've ever used React or similar libraries, you might be familiar with the concept of "hooks". In `recompose`,
//! "hooks" are functions that interact and/or modify the `Scope`. Read the [How it works](#how-it-works) section for
//! more details.
//!
//! Unlike React, not all hooks are required to follow the "rules of hooks" - being called in the same order, and never
//! conditionally, in a loop and so on. Functions that must obey these rules are prefixed wiht `use_`.
//!
//! Some of the hooks available:
//!
//! **State**
//! ```
//! let count = cx.use_state(42); // Get a state that persists between recompositions.
//! cx.set_state(&count, *count + 1); // Set the state to a new value.
//! ```
//!
//! **Composable lifetime**
//! ```
//! cx.use_mount(|| { /* Do something */}); // Called when the composable is first composed.
//! cx.effect(|| { /* Do something */}, (&count, &name)); // Called only when dependencies have changed.
//! ```
//!
//! **ECS World interaction**
//! ```
//! // Run a system each time the composable is recomposed.
//! cx.run_system(|names: Query<&Name>, mut state: SetState| {
//! state.set(&count, 30); // Set the state to a new value.
//! state.modify(&count, |count| *count + 1); // Modify the state.
//! });
//!
//! // Run a system once, when the composable is first composed.
//! cx.use_system_once(|| { /* .. */ });
//! ```
//!
//! # How it works
//!
//! `recompose` is built around the concept of composables that implement the [`Compose`](prelude::Compose) trait. The
//! [`compose`](prelude::Compose::compose) function of the `Compose` trait modifes the given [`Scope`](prelude::Scope)
//! (a `Scope` can be thought of as a node in a tree-structure) and may return a new `Compose`, which is then added as
//! the current `Scope`s' child. The compose function is called when the composable is first added, when one of the
//! scope's state changes, or when the parent composable "recomposes".
//!
//! **Some notable [`Compose`](prelude::Compose) implementations:**
//!
//! - `Fn(&mut Scope) -> impl Compose` - A function that takes a mutable reference to a `Scope` and returns a
//! composable. Useful for simple composables that don't need any input.
//! - [`Spawn`](prelude::Spawn) - Spawns a new entity with the from a bundle.
//! - [`DynCompose`](prelude::DynCompose) - Allows for dynamic composables that "erase" their type definition.
//! - `Option<C>` - Composes `C` if the option is `Some`, otherwise does nothing.
//! - Tuples `(C0, .., C9)` - Compose multiple composables at once.
//! - `Vec<C>` - Compose any number of composables. This requires that the items implement the
//! [`Key`](prelude::Key)-trait.
//! - [`Keyed`](prelude::Keyed) - Implements the `Key`-trait and can be used to wrap any composable. The added
//! advantage is that the type is "erased" so that composables of different types can be composed in the same
//! `Vec`.
//! - `()` - Empty composable that does nothing. Implementing `Compose` for `()` lets us skip returning anything from
//! the [`compose`](prelude::Compose::compose) function.
//!
//! **Bundle**
//! - The `Bundle`-trait does not implemented the [`Compose`](prelude::Compose)-trait, however it does implement the
//! [`BundleExtension`](prelude::BundleExtension)-trait which lets us convert a `Bundle` into a
//! [`Spawn`](prelude::Spawn)-composable very easily. Through `BundleExtension`, it also implements
//! [`ModfiyFunctions`](prelude::ModifyFunctions)-trait which lets us use functions like
//! [`children`](prelude::ModifyFunctions::children), [`observe`](prelude::ModifyFunctions::observe).
//!
//! # Compatibility with Bevy
//! | Bevy | recompose |
//! | ---- | --------- |
//! | 0.15 | 0.1-0.5 |
//!
//! # Motivation
//! Recompose is heavily inspired by the [actuate](https://docs.rs/actuate/) crate, which also provides a declarative
//! way to construct ECS structures. This crate strives to be more robust, safer and less error-prone than `actuate`.
//!
//! The goal of `recompose` is not necessarily to be the most performant solution, but rather one that is easy to use
//! and easy to understand.