raui/
lib.rs

1//! RAUI is a renderer agnostic UI system that is heavily inspired by **React**'s declarative UI
2//! composition and the **UE4 Slate** widget components system.
3//!
4//! > 🗣 **Pronunciation:** RAUI is pronounced like **"ra"** ( the Egyptian god ) + **"oui"**
5//! > (french for "yes" ) — [Audio Example][pronounciation].
6//!
7//! [pronounciation]: https://itinerarium.github.io/phoneme-synthesis/?w=/%27rawi/
8//!
9//! The main idea behind RAUI architecture is to treat UI as another data source that you transform
10//! into your target renderable data format used by your rendering engine of choice.
11//!
12//! # Architecture
13//!
14//! ## [`Application`]
15//!
16//! [`Application`] is the central point of user interest. It performs whole UI processing logic.
17//! There you apply widget tree that wil be processed, send messages from host application to
18//! widgets and receive signals sent from widgets to host application.
19//!
20//! [`Application`]: core::application::Application
21//!
22//! ```rust
23//! # use raui::prelude::*;
24//! # use raui::renderer::json::JsonRenderer;
25//! # fn app(context: WidgetContext) -> WidgetNode {
26//! #     let WidgetContext { named_slots, .. } = context;
27//! #     unpack_named_slots!(named_slots => { title, content });
28//! #     make_widget!(nav_vertical_box)
29//! #         .listed_slot(title)
30//! #         .listed_slot(content)
31//! #         .into()
32//! # }
33//! # fn title_bar(context: WidgetContext) -> WidgetNode {
34//! #     let WidgetContext { props, .. } = context;  
35//! #     make_widget!(text_box)
36//! #         .with_props(TextBoxProps {
37//! #             text: props.read_cloned_or_default::<String>(),
38//! #             font: TextBoxFont {
39//! #                 name: "verdana.ttf".to_owned(),
40//! #                 size: 64.0,
41//! #             },
42//! #             color: Color {
43//! #                 r: 0.0,
44//! #                 g: 0.0,
45//! #                 b: 0.5,
46//! #                 a: 1.0,
47//! #             },
48//! #             horizontal_align: TextBoxHorizontalAlign::Center,
49//! #             ..Default::default()
50//! #         })
51//! #         .into()
52//! # }
53//! # fn text_button(context: WidgetContext) -> WidgetNode {
54//! #     let WidgetContext { props, .. } = context;
55//! #     make_widget!(button)
56//! #         .named_slot("content", make_widget!(text_box)
57//! #             .with_props(TextBoxProps {
58//! #                 text: props.read_cloned_or_default::<String>(),
59//! #                 font: TextBoxFont {
60//! #                     name: "verdana.ttf".to_owned(),
61//! #                     size: 32.0,
62//! #                 },
63//! #                 color: Color {
64//! #                     r: 1.0,
65//! #                     g: 0.0,
66//! #                     b: 0.5,
67//! #                     a: 1.0,
68//! #                 },
69//! #                 ..Default::default()
70//! #             }))
71//! #             .into()
72//! # }
73//! // Coords mapping tell RAUI renderers how to convert coordinates
74//! // between virtual-space and ui-space.
75//! let mapping = CoordsMapping::new(Rect {
76//!     left: 0.0,
77//!     right: 1024.0,
78//!     top: 0.0,
79//!     bottom: 576.0,
80//! });
81//!
82//! // Application is UI host.
83//! let mut application = Application::default();
84//! // we use setup functions to register component and props mappings for serialization.
85//! application.setup(setup);
86//! // we can also register them at any time one by one.
87//! application.register_component("app", FnWidget::pointer(app));
88//!
89//! // Widget tree is simply a set of nested widget nodes.
90//! let tree = make_widget!(app)
91//!     .named_slot("title", make_widget!(title_bar).with_props("Hello".to_owned()))
92//!     .named_slot("content", make_widget!(vertical_box)
93//!         .listed_slot(make_widget!(text_button).key("hi").with_props("Say hi!".to_owned()))
94//!         .listed_slot(make_widget!(text_button).key("exit").with_props("Exit!".to_owned()))
95//!     );
96//!
97//! // some dummy widget tree renderer.
98//! // it reads widget unit tree and transforms it into target format.
99//! let mut renderer = JsonRenderer::default();
100//!
101//! // `apply()` sets new widget tree.
102//! application.apply(tree);
103//!
104//! // `render()` calls renderer to perform transformations on processed application widget tree.
105//! // by default application won't process widget tree if nothing was changed.
106//! // "change" is either any widget state change, or new message sent to any widget (messages
107//! // can be sent from application host, for example a mouse click, or from another widget).
108//! application.forced_process();
109//! if let Ok(output) = application.render::<JsonRenderer, String, _>(&mapping, &mut renderer) {
110//!     println!("* OUTPUT:\n{}", output);
111//! }
112//! ```
113//!
114//! ## Widgets
115//!
116//! Widgets are divided into three categories:
117//! - **[`WidgetNode`]** - used as source UI trees (variant that can be either a component, unit or
118//!   none)
119//!
120//! [`WidgetNode`]: core::widget::node::WidgetNode
121//!
122//! ```rust
123//! # use raui::prelude::*;
124//! # fn app(context: WidgetContext) -> WidgetNode {
125//! #     let WidgetContext { named_slots, .. } = context;
126//! #     unpack_named_slots!(named_slots => { title, content });
127//! #     make_widget!(nav_vertical_box)
128//! #         .listed_slot(title)
129//! #         .listed_slot(content)
130//! #         .into()
131//! # }
132//! # fn title_bar(context: WidgetContext) -> WidgetNode {
133//! #     let WidgetContext { props, .. } = context;
134//! #     make_widget!(text_box)
135//! #         .with_props(TextBoxProps {
136//! #             text: props.read_cloned_or_default::<String>(),
137//! #             font: TextBoxFont {
138//! #                 name: "verdana.ttf".to_owned(),
139//! #                 size: 64.0,
140//! #             },
141//! #             color: Color {
142//! #                 r: 0.0,
143//! #                 g: 0.0,
144//! #                 b: 0.5,
145//! #                 a: 1.0,
146//! #             },
147//! #             horizontal_align: TextBoxHorizontalAlign::Center,
148//! #             ..Default::default()
149//! #         })
150//! #         .into()
151//! # }
152//! # fn text_button(context: WidgetContext) -> WidgetNode {
153//! #     let WidgetContext { props, .. } = context;
154//! #     make_widget!(button)
155//! #         .named_slot("content", make_widget!(text_box)
156//! #             .with_props(TextBoxProps {
157//! #                 text: props.read_cloned_or_default::<String>(),
158//! #                 font: TextBoxFont {
159//! #                     name: "verdana.ttf".to_owned(),
160//! #                     size: 32.0,
161//! #                 },
162//! #                 color: Color {
163//! #                     r: 1.0,
164//! #                     g: 0.0,
165//! #                     b: 0.5,
166//! #                     a: 1.0,
167//! #                 },
168//! #                 ..Default::default()
169//! #             }))
170//! #             .into()
171//! # }
172//! let tree = make_widget!(app)
173//!     .named_slot("title", make_widget!(title_bar).with_props("Hello".to_owned()))
174//!     .named_slot("content", make_widget!(vertical_box)
175//!         .listed_slot(make_widget!(text_button).key("hi").with_props("Say hi!".to_owned()))
176//!         .listed_slot(make_widget!(text_button).key("exit").with_props("Exit!".to_owned()))
177//!     );
178//! ```
179//!
180//! - **[`WidgetComponent`]** - you can think of them as Virtual DOM nodes, they store:
181//!   - pointer to _component function_ (that process their data)
182//!   - unique _key_ (that is a part of widget ID and will be used to tell the system if it should
183//!     carry its _state_ to next processing run)
184//!   - boxed cloneable _properties_ data
185//!   - _listed slots_ (simply: widget children)
186//!   - _named slots_ (similar to listed slots: widget children, but these ones have names assigned
187//!     to them, so you can access them by name instead of by index)
188//! - **[`WidgetUnit`]** - an atomic element that renderers use to convert into target renderable
189//!   data format for rendering engine of choice.
190//!   ```rust
191//!   # use raui::prelude::*;
192//!   TextBoxNode {
193//!       text: "Hello World".to_owned(),
194//!       ..Default::default()
195//!   };
196//!   ```
197//!
198//! [`WidgetComponent`]: core::widget::component::WidgetComponent
199//!
200//! [`WidgetUnit`]: core::widget::unit::WidgetUnit
201//!
202//! ## Component Function
203//!
204//! Component functions are static functions that transforms input data (properties, state or
205//! neither of them) into output widget tree (usually used to simply wrap another components tree
206//! under one simple component, where at some point the simplest components returns final
207//! _[`WidgetUnit`]'s_). They work together as a chain of transforms - root component applies some
208//! properties into children components using data from its own properties or state.
209//!
210//! ```rust
211//! # use raui::prelude::*;
212//! # use serde::{Serialize, Deserialize};
213//! #[derive(PropsData, Debug, Default, Copy, Clone, Serialize, Deserialize)]
214//! struct AppProps {
215//!     #[serde(default)]
216//!     pub index: usize,
217//! }
218//! fn app(context: WidgetContext) -> WidgetNode {
219//!     let WidgetContext {
220//!         props, named_slots, ..
221//!     } = context;
222//!     // easy way to get widgets from named slots.
223//!     unpack_named_slots!(named_slots => { title, content });
224//!     let index = props.read::<AppProps>().map(|p| p.index).unwrap_or(0);
225//!
226//!     // we always return new widgets tree.
227//!     make_widget!(vertical_box)
228//!         .key(index)
229//!         .listed_slot(title)
230//!         .listed_slot(content)
231//!         .into()
232//! }
233//! ```
234//! ### States
235//!
236//! This may bring up a question: _**"If i use only functions and no objects to tell how to
237//! visualize UI, how do i keep some data between each render run?"**_. For that you use _states_.
238//! State is a data that is stored between each processing calls as long as given widget is alive
239//! (that means: as long as widget id stays the same between two processing calls, to make sure your
240//! widget stays the same, you use keys - if no key is assigned, system will generate one for your
241//! widget but that will make it possible to die at any time if for example number of widget
242//! children changes in your common parent, your widget will change its id when key wasn't
243//! assigned). Some additional notes: While you use _properties_ to send information down the tree
244//! and _states_ to store widget data between processing cals, you can communicate with another
245//! widgets and host application using messages and signals! More than that, you can use hooks to
246//! listen for widget life cycle and perform actions there. It's worth noting that state uses
247//! _properties_ to hold its data, so by that you can for example attach multiple hooks that each of
248//! them uses different data type as widget state, this opens the doors to be very creative when
249//! combining different hooks that operate on the same widget.
250//! ```rust
251//! # use raui::prelude::*;
252//! # use serde::{Serialize, Deserialize};
253//! #[derive(PropsData, Debug, Default, Copy, Clone, Serialize, Deserialize)]
254//! struct ButtonState {
255//!     #[serde(default)]
256//!     pub pressed: bool,
257//! }
258//! ```
259//!
260//! ## Hooks
261//!
262//! Hooks are used to put common widget logic into separate functions that can be chained in widgets
263//! and another hooks (you can build a reusable dependency chain of logic with that). Usually it is
264//! used to listen for life cycle events such as mount, change and unmount, additionally you can
265//! chain hooks to be processed sequentially in order they are chained in widgets and other hooks.
266//!
267//! ```rust
268//! # use raui::prelude::*;
269//! # use serde::{Serialize, Deserialize};
270//! # #[derive(PropsData, Debug, Copy, Clone, Serialize, Deserialize)]
271//! # struct ButtonState {
272//! #     pressed: bool
273//! # }
274//! #[derive(MessageData, Debug, Copy, Clone, PartialEq, Eq)]
275//! enum ButtonAction {
276//!     Pressed,
277//!     Released,
278//! }
279//!
280//! fn use_empty(context: &mut WidgetContext) {
281//!     context.life_cycle.mount(|_| {
282//!         println!("* EMPTY MOUNTED");
283//!     });
284//!
285//!     context.life_cycle.change(|_| {
286//!         println!("* EMPTY CHANGED");
287//!     });
288//!
289//!     context.life_cycle.unmount(|_| {
290//!         println!("* EMPTY UNMOUNTED");
291//!     });
292//! }
293//!
294//! // you use life cycle hooks for storing closures that will be called when widget will be
295//! // mounted/changed/unmounted. they exists for you to be able to reuse some common logic across
296//! // multiple components. each closure provides arguments such as:
297//! // - widget id
298//! // - widget state
299//! // - message sender (this one is used to message other widgets you know about)
300//! // - signal sender (this one is used to message application host)
301//! // although this hook uses only life cycle, you can make different hooks that use many
302//! // arguments, even use context you got from the component!
303//! #[pre_hooks(use_empty)]
304//! fn use_button(context: &mut WidgetContext) {
305//!     context.life_cycle.mount(|context| {
306//!         println!("* BUTTON MOUNTED: {}", context.id.key());
307//!         let _ = context.state.write(ButtonState { pressed: false });
308//!     });
309//!
310//!     context.life_cycle.change(|context| {
311//!         println!("* BUTTON CHANGED: {}", context.id.key());
312//!         for msg in context.messenger.messages {
313//!             if let Some(msg) = msg.as_any().downcast_ref::<ButtonAction>() {
314//!                 let pressed = match msg {
315//!                     ButtonAction::Pressed => true,
316//!                     ButtonAction::Released => false,
317//!                 };
318//!                 println!("* BUTTON ACTION: {:?}", msg);
319//!                 let _ = context.state.write(ButtonState { pressed });
320//!                 let _ = context.signals.write(*msg);
321//!             }
322//!         }
323//!     });
324//!
325//!     context.life_cycle.unmount(|context| {
326//!         println!("* BUTTON UNMOUNTED: {}", context.id.key());
327//!     });
328//! }
329//!
330//! #[pre_hooks(use_button)]
331//! fn button(mut context: WidgetContext) -> WidgetNode {
332//!     let WidgetContext { key, props, .. } = context;
333//!     println!("* PROCESS BUTTON: {}", key);
334//!
335//!     make_widget!(text_box).key(key).merge_props(props.clone()).into()
336//! }
337//! ```
338//!
339//! What happens under the hood:
340//! - Application calls `button` on a node
341//!     - `button` calls `use_button` hook
342//!         - `use_button` calls `use_empty` hook
343//!     - `use_button` logic is executed
344//! - `button` logic is executed
345//!
346//! ## Layouting
347//!
348//! RAUI exposes the [`Application::layout()`][core::application::Application::layout] API to allow
349//! use of virtual-to-real coords mapping and custom layout engines to perform widget tree
350//! positioning data, which is later used by custom UI renderers to specify boxes where given
351//! widgets should be placed. Every call to perform layouting will store a layout data inside
352//! Application, you can always access that data at any time. There is a [`DefaultLayoutEngine`]
353//! that does this in a generic way. If you find some part of its pipeline working different than
354//! what you've expected, feel free to create your custom layout engine!
355//!
356//! ```rust
357//! # use raui::prelude::*;
358//! # let tree = WidgetNode::default();
359//! # let mapping = CoordsMapping::new(Rect::default());
360//! let mut application = Application::default();
361//! let mut layout_engine = DefaultLayoutEngine;
362//! application.apply(tree);
363//! application.forced_process();
364//! println!(
365//!     "* TREE INSPECTION:\n{:#?}",
366//!     application.rendered_tree().inspect()
367//! );
368//! if application.layout(&mapping, &mut layout_engine).is_ok() {
369//!     println!("* LAYOUT:\n{:#?}", application.layout_data());
370//! }
371//! ```
372//!
373//! [`DefaultLayoutEngine`]: core::layout::default_layout_engine::DefaultLayoutEngine
374//!
375//! ## Interactivity
376//!
377//!
378//! RAUI allows you to ease and automate interactions with UI by use of Interactions Engine - this
379//! is just a struct that implements [`perform_interactions`] method with reference to Application,
380//! and all you should do there is to send user input related messages to widgets. There is
381//! [`DefaultInteractionsEngine`] that covers widget navigation, button and input field - actions
382//! sent from input devices such as mouse (or any single pointer), keyboard and gamepad. When it
383//! comes to UI navigation you can send raw [`NavSignal`] messages to the default interactions
384//! engine and despite being able to select/unselect widgets at will, you have typical navigation
385//! actions available: up, down, left, right, previous tab/screen, next tab/screen, also being able
386//! to focus text inputs and send text input changes to focused input widget. All interactive widget
387//! components that are provided by RAUI handle all [`NavSignal`] actions in their hooks, so all
388//! user has to do is to just activate navigation features for them (using [`NavItemActive`] unit
389//! props). RAUI integrations that want to just use use default interactions engine should make use
390//! of this struct composed in them and call its [`interact`] method with information about what
391//! input change was made. There is an example of that feature covered in RAUI App crate
392//! (`AppInteractionsEngine` struct).
393//!
394//! [`NavSignal`]: core::widget::component::interactive::navigation::NavSignal
395//!
396//! [`NavItemActive`]: core::widget::component::interactive::navigation::NavItemActive
397//!
398//! [`perform_interactions`]: core::interactive::InteractionsEngine::perform_interactions
399//!
400//! [`interact`]:
401//! core::interactive::default_interactions_engine::DefaultInteractionsEngine::interact
402//!
403//! [`DefaultInteractionsEngine`]:
404//! core::interactive::default_interactions_engine::DefaultInteractionsEngine
405//!
406//! **NOTE: Interactions engines should use layout for pointer events so make sure that you rebuild
407//! layout before you perform interactions!**
408//!
409//! ```rust
410//! # use raui::prelude::*;
411//! let mut application = Application::default();
412//! // default interactions engine covers typical pointer + keyboard + gamepad navigation/interactions.
413//! let mut interactions = DefaultInteractionsEngine::default();
414//! // we interact with UI by sending interaction messages to the engine.
415//! interactions.interact(Interaction::PointerMove(Vec2 { x: 200.0, y: 100.0 }));
416//! interactions.interact(Interaction::PointerDown(
417//!     PointerButton::Trigger,
418//!     Vec2 { x: 200.0, y: 100.0 },
419//! ));
420//! // navigation/interactions works only if we have navigable items (such as `button`) registered
421//! // in some navigable container (usually containers with `nav_` prefix).
422//! let tree = make_widget!(nav_content_box)
423//!     .key("app")
424//!     .listed_slot(make_widget!(button)
425//!         .key("button")
426//!         .with_props(NavItemActive)
427//!         .named_slot("content", make_widget!(image_box).key("icon"))
428//!     );
429//! application.apply(tree);
430//! application.process();
431//! let mapping = CoordsMapping::new(Rect {
432//!     left: 0.0,
433//!     right: 1024.0,
434//!     top: 0.0,
435//!     bottom: 576.0,
436//! });
437//! application
438//!     .layout(&mapping, &mut DefaultLayoutEngine)
439//!     .unwrap();
440//! // Since interactions engines require constructed layout to process interactions we have to
441//! // process interactions after we layout the UI.
442//! application.interact(&mut interactions).unwrap();
443//! ```
444
445#[doc(inline)]
446pub use raui_core as core;
447
448#[doc(inline)]
449#[cfg(feature = "material")]
450pub use raui_material as material;
451
452/// Renderer implementations
453pub mod renderer {
454    #[cfg(feature = "json")]
455    pub mod json {
456        pub use raui_json_renderer::*;
457    }
458    #[cfg(feature = "tesselate")]
459    pub mod tesselate {
460        pub use raui_tesselate_renderer::*;
461    }
462}
463
464#[doc(hidden)]
465pub mod prelude {
466    #[cfg(feature = "material")]
467    pub use raui_material::prelude::*;
468
469    #[cfg(feature = "retained")]
470    pub use raui_retained::*;
471
472    #[cfg(feature = "immediate")]
473    pub use raui_immediate::*;
474
475    #[cfg(feature = "immediate-widgets")]
476    pub use raui_immediate_widgets::*;
477
478    #[cfg(feature = "json")]
479    pub use raui_json_renderer::*;
480
481    #[cfg(feature = "app")]
482    pub use raui_app::prelude::*;
483
484    pub use raui_core::prelude::*;
485}