StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.
| 1 | # WarpUI |
| 2 | |
| 3 | ## Whirlwind tour |
| 4 | |
| 5 | WarpUI contains many interlocking concepts. It's difficult to explain any one part of the system without reference to other parts. Because of this, this guide tries to provide an overview by exploring the relationships between the major concepts before providing a lot of detail on any one of them. |
| 6 | |
| 7 | Rust's strict ownership rules are a challenge for user interfaces, where multi-directional dataflow is often critical. If every object has one and only one owner, how do we express things like event handlers? |
| 8 | |
| 9 | ## The global App object, entities, and handles |
| 10 | |
| 11 | WarpUI solves this problem with the `App` object, which is the sole owner of all the views and models in the application. We collectively refer to views and models as **entities**. Entities can hold references to other entities via **handles**. A handle provides access to an entity in specific, limited circumstances. Take for example a multi-tabbed terminal. The application window is occupied by a `WorkspaceView`, and we want this workspace to contain multiple `TerminalView`s. Rather than holding the `TerminalView`s directly, the `Workspace` instead holds a vector of `ViewHandle<TerminalView>`. |
| 12 | |
| 13 | ```rust |
| 14 | struct WorkspaceView { |
| 15 | sessions: Vec<ViewHandle<TerminalView>>, |
| 16 | } |
| 17 | ``` |
| 18 | |
| 19 | On its own, a `ViewHandle` can't do much. Its existence prevents the referenced view from being discarded by the global `App` object, but it doesn't provide direct access to the referenced view. A handle is basically a glorified identifier. To convert a handle into an actual reference, you need a reference to an **app context** object, which will be provided by the global `App` object at specific points in time. |
| 20 | |
| 21 | An example of one of those times could be the `render` method on `WorkspaceView`, which will be called by the framework whenever the workspace's on-screen representation is updated. One of the parameters to `render` is an `&AppContext`, which can be passed to the `as_ref` method on a `ViewHandle` to retrieve a reference to the underlying object. |
| 22 | |
| 23 | Many details are elided in the code example below so we can focus on the topic at hand, but imagine we wanted to know the titles of all our terminal sessions so we could render them in tabs: |
| 24 | |
| 25 | ```rust |
| 26 | impl View for WorkspaceView { |
| 27 | fn render<'a>(&self, ..., ctx: &AppContext) -> ... { |
| 28 | let titles = self.sessions.iter().map(|handle| handle.as_ref(ctx).title()).collect::<Vec<String>>; |
| 29 | ... |
| 30 | } |
| 31 | |
| 32 | ... |
| 33 | } |
| 34 | ``` |
| 35 | |
| 36 | After we return from the `render` method and lose access to the `&AppContext` parameter provided during its call, we no longer have access to the terminal views they reference. |
| 37 | |
| 38 | Entities are of course entitled to own any state they like directly as well. Handles are only required when an entity needs to reference an entity. The reasons it would make sense to express a piece of application state as its own entity will become clearer as we sketch in more aspects of the system. |
| 39 | |
| 40 | ## Elements |
| 41 | |
| 42 | The framework requires all views to implement the `View` trait, and a key method of this trait is `render`, which we showcased above. This method's job is to compute a visual description of the view based on its current state, and it is called whenever the view's state changes. |
| 43 | |
| 44 | To do describe the view's appearence, render returns an **element**. While a single view may exist for an arbitrary amount of time, changing its state as the user interacts with the application, an element is designed to exist for only a single frame. More precicely, elements returned by views that haven't changed are recycled across multiple frames, but conceptually you can think of an element as a throwaway object that is discarded and replaced whenever the view that returned it changes. |
| 45 | |
| 46 | The framework ships with several elements that can be composed to perform common tasks such as drawing backgrounds and borders, adding padding, rendering label text, laying out elements horizontally and vertically, handlign events, etc. The stock elements are loosely based on the Flutter framework. It's also straightforward to define your own custom elements, giving you detailed control over layout and the ability to imperatively paint pixels on scene via the hardware-accelerated `Scene` API. |
| 47 | |
| 48 | ## Actions |
| 49 | |
| 50 | ### Action handlers |
| 51 | |
| 52 | ### Action dispatch |
| 53 | |
| 54 | ## Views |
| 55 |