rui Documentation

repository·main·Indexed 24 days ago

https://github.com/audulus/rui

An experimental declarative UI library for desktop and mobile applications. rui uses a reactive paradigm with an immutable view model, centralized state management in a Context, and GPU-accelerated rendering via vger. It features a hierarchical tree of Views, a state-driven update model, and a Binding system using lenses to connect UI controls to application data.

Tokens
7.9K
Snippets
22
Records
58
Agent score
84%

What's inside rui

  1. What is rui?

    main

    rui is a minimalistic, declarative UI library designed for desktop and mobile applications. It follows a reactive paradigm where the UI is a function of the application state, meaning the interface automatically updates whenever the state changes.

    Key characteristics:

    • Declarative: Define your UI structure, and rui handles the updates.
    • GPU-Accelerated: Uses vger, a GPU-based vector graphics renderer, for high-performance rendering.
    • Custom Rendering: Does not use native widgets; instead, it renders its own UI components.
    • Experimental: The library is currently experimental and not intended for production use.
  2. Core concepts of rui: View, State, and Binding

    main

    To build applications with rui, you must understand three fundamental abstractions that compose the reactive UI model:

    1. View: The UI is expressed as a hierarchical tree of views. You define the structure of your interface by composing these view elements.
    2. State: This is the single source of truth that stores your application's data. Because the UI is a function of state, any change to this data triggers a re-render.
    3. Binding: A binding acts as a specific view or window into your application state. Bindings are passed to UI controls to connect them to the underlying state, allowing controls to read from or write to the state.
  3. Understand the MIDI Synthesizer architecture

    main

    The synthesizer application is composed of three primary components:

    1. MIDI Keyboard UI (midi_keyboard.rs): Handles the visual representation and user interaction of the piano keys.
    2. Sound Synthesis (synth): Manages the real-time audio synthesis logic.
    3. Main Application (main.rs): The entry point that orchestrates the UI and the synthesis engine.
  4. Understand the rui rendering lifecycle

    main

    The rui rendering engine follows a reactive model:

    1. A change occurs in the State (stored in the Context).
    2. The entire UI is laid out and redrawn.
    3. To optimize performance, multiple changes to State within a single event cycle are coalesced into a single redraw cycle.
  5. How to manage application data with State

    main

    In rui, State acts as the container for your application's data model. You attach state to a specific position in the view tree using the state function.

    State implements the Binding trait, providing get and get_mut methods. While you can pass the entire State object directly to views, the recommended pattern is to use the bind function to create a Binding to a specific field within your state, and then pass that specific binding to the view. This ensures views only react to the specific data they depend on.

    state(0.0, |my_state: State<f32>| {
        slider(my_state)
    })
  6. Implement the View trait

    main

    The View trait defines the core lifecycle of a UI element. Typical methods include:

    • Event processing
    • Rendering
    • Layout

    Best Practice: Composition over Implementation Whenever possible, you should implement views by composing them from other existing views rather than implementing the trait methods directly. This allows you to leverage existing logic and maintain a more declarative structure. For example, you can add custom functionality to a composed view by using modifiers (see examples/custom_modifier.rs for implementation details).

  7. How Bindings work in rui

    main

    Bindings are the mechanism used to expose specific parts of a data model to a View. They allow UI controls (like sliders) to read from and write to specific fields within your application state. A Binding acts as a lens into your state, providing access to a value of type S via a Context.

    pub trait Binding<S>: Clone + 'static {
        fn get<'a>(&self, cx: &'a mut Context) -> &'a S;
        fn get_mut<'a>(&self, cx: &'a mut Context) -> &'a mut S;
    }
  8. Updating state from background threads

    main

    While State can be safely passed to other threads, the Context cannot. This means you cannot directly update state values from a background thread using the standard context-based methods.

    To update state from an asynchronous or background task, you must use the on_main function to schedule the update to run on the main thread where the Context is available.

  9. Create a binding for a struct member using `make_lens!` and `bind`

    main

    To bind a UI control to a specific field in a struct, you must first define a lens using the make_lens! macro, and then wrap your state and lens in the bind function.

    1. Use make_lens!(LensName, StateType, FieldType, field_name) to generate the lens.
    2. Use bind(state, LensName{}) to create the binding for a widget.

    Example: Binding an f32 field to an hslider.

    struct MyState {
        value: f32,
    }
    make_lens!(MyLens, MyState, f32, value);
    
    // In your view definition:
    hslider(bind(state, MyLens{}))