vizia

repository·main·Indexed 24 days ago

https://github.com/vizia/vizia

A declarative and reactive desktop GUI framework for Rust. Vizia provides cross-platform support for Windows, Linux, and MacOS, utilizing Skia for high-performance rendering and morphorm for adaptive layouts. It features an ECS-based architecture, reactive state management via Signals, stylesheet support with hot-reloading, and accessibility via accesskit. Additionally, it offers a baseview windowing backend for audio plugin development and localization support through fluent.

Tokens
30.2K
Snippets
59
Records
208
Agent score
81%

What's inside vizia

  1. Overview of Vizia features

    main

    Vizia is a declarative, reactive desktop GUI framework for Rust. Key features include:

    • Cross-platform: Supports Windows, Linux, and MacOS.
    • Declarative UI: Write GUI code in pure Rust without DSL macros.
    • Reactive State: Views derive from application state; updating state automatically updates bound views.
    • Flexible Layout: Powered by morphorm for adaptive layouts.
    • Styling: Supports stylesheets with hot-reloading.
    • Accessibility: Powered by accesskit for screen reader support.
    • Optimized Rendering: Uses skia with optimizations to only draw necessary elements.
    • Audio Plugin Support: Provides a baseview windowing backend for audio plugin development (e.g., with nih-plug).
    • Localization: Supports translation via fluent.
  2. Use Handles and Modifiers to configure views

    main

    When a view is built (e.g., via Label::new(cx, ...)), it returns a Handle. A Handle is a wrapper around an Entity ID and a mutable reference to the Context.

    Handle implements various Modifiers traits, allowing you to set view properties during the build phase. For example, the StyleModifiers trait provides methods like background_color() to configure the view's appearance.

  3. How Vizia's ECS-based architecture works

    main

    Vizia uses a simple Entity Component System (ECS) model to manage the UI:

    1. Entities: Every View is assigned a generational Entity ID.
    2. Tree: The hierarchy of views is maintained in a Tree using these IDs.
    3. Components (Style): View properties (like size or color) are not stored in the tree but in separate stores within the Style struct, accessed via the Entity ID.
    4. Systems: A series of specialized systems run every update cycle to process the application:
      • Event Manager: Routes events to Models and Views.
      • Binding System: Detects model changes and updates binding views.
      • Style System: Applies CSS and property inheritance.
      • Layout System: Calculates the size and position of views.
      • Draw System: Renders views to the window.
      • (Other systems handle Images, Animations, and Accessibility.)
    5. Cache: Stores computed data (like final layout bounds) so systems can share results efficiently.
  4. How events are processed in Vizia

    main

    An Event consists of a message (type-erased data) and metadata regarding its origin, target, and propagation rules.

    Workflow:

    1. Window events (from winit) are translated into Vizia WindowEvents.
    2. These are added to an event queue in the Context.
    3. When the event loop reaches the MainEventsCleared event, Vizia processes the queue, routing events to the appropriate Models and Views via their event() methods.
  5. Understand the Vizia View trait

    main

    The View trait defines visual elements in the application. When implementing a custom view, you can override these four methods:

    • build(): Used to construct the view and any sub-views. This is typically called in the constructor. It returns a Handle.
    • element(): (Optional) Returns an element name used for CSS styling based on the view type.
    • event(): (Optional) Handles incoming events.
    • draw(): (Optional) Customizes how the view is rendered. If not implemented, the view is drawn using its style properties.
  6. Create derived signals with Memo and map

    main

    Use derived signals to create UI values that depend on other signals:

    • .map(): Use for lightweight, single-signal transformations (e.g., formatting a string).
    • Memo: Use for computed state that depends on one or more signals or requires more complex logic.

    Rule of thumb: Prefer map for simple projections and Memo for multi-signal or reusable computed state.

    // Memo example (multi-signal or complex logic)
    let orientation = Signal::new(Orientation::Vertical);
    let is_horizontal = Memo::new(move |_| orientation.get() == Orientation::Horizontal);
    
    Switch::new(cx, is_horizontal)
        .on_toggle(|cx| cx.emit(AppEvent::ToggleHorizontal));
    
    // map example (simple transformation)
    let count = Signal::new(0);
    let label_text = count.map(|v| format!("Count: {v}"));
    
    Label::new(cx, label_text);
  7. Implement Collections with Signals

    main

    For lists of items, use specific signal patterns depending on the widget type:

    • Standard List: Use Signal<Vec<Signal<T>>> where each item is itself a signal.
    • VirtualList: Use Signal<Vec<T>> for high-performance scrolling lists.
    // List pattern: Signal<Vec<Signal<T>>>
    let list = Signal::new((0..15u32).map(Signal::new).collect::<Vec<_>>());
    
    List::new(cx, list, |cx, _, item| {
        Label::new(cx, item).hoverable(false);
    });
    
    // VirtualList pattern: Signal<Vec<T>>
    let list = Signal::new((1..100u32).collect::<Vec<_>>());
    
    VirtualList::new(cx, list, 40.0, |cx, index, item| {
        Label::new(cx, item).toggle_class("dark", index % 2 == 0).hoverable(false)
    });
  8. Migrate from Lens-based state to Signals

    main

    Vizia has moved from a lens-driven reactive model to a signal-driven model. In the new system, reactive updates are driven by Signal<T> instead of lenses.

    Key changes:

    • Model fields that were plain values are now typically Signal<T>.
    • Writes move from direct assignment (field = value) to .set() or .update() methods.
    • Views and modifiers now take signal handles directly instead of lens paths (e.g., AppData::field).
    • Derived values should be implemented using Memo or .map() on signals.
  9. Initialize and wire Signals in Models and Views

    main

    Signals must be initialized using Signal::new(...) before being passed into models or views. If setup and usage are split across files, you can access the model state via cx.data::<T>() to retrieve the signals.

    // Standard initialization
    let count = Signal::new(0);
    AppData { count }.build(cx);
    
    HStack::new(cx, |cx| {
        Label::new(cx, count);
    });
    
    // Accessing via cx.data() in separate modules
    let count = cx.data::<AppData>().unwrap().count;
    Label::new(cx, count);
  10. Run Vizia examples

    main

    Vizia includes several examples in the repository. You can run them using cargo run.

    To run a specific example file:

    cargo run --release --example <name_of_example>

    To run an example that is structured as a separate package:

    cargo run -p <package_name>
    cargo run --release --example name_of_example
  11. Initialize a Vizia application

    main

    The Application struct is the entry point for a Vizia application. You use Application::new() to create a Context (the global store for application state) and provide a closure to build your UI. Calling .run() starts the event loop, creates the window, and initializes the Skia Canvas.

    Application::new(|cx| {
        Label::new(cx, "Hello Vizia");
    }).run();