Skip to content

ten3roberts/violet

Repository files navigation

Violet

A retained mode GUI library focused on reactive and composable UI

Violet aims to be a simple library of minimal parts that can be composed to create complex UIs.

State and reactivity is managed locally using async Streams, such as signals or MPSC channels and map methods. This allows composing a declarative reactive UI where data flows naturally from source to destination without re-renders or useState hooks.

Example

image

let name = Mutable::new("".to_string());
let quest = Mutable::new("".to_string());
let color = Mutable::new(Srgba::new(0.0, 0.61, 0.388, 1.0));

// Map a `Mutable<Srgba>` into a `StateDuplex<f32>` for each field
let r = color.clone().map_ref(|v| &v.red, |v| &mut v.red);
let g = color.clone().map_ref(|v| &v.green, |v| &mut v.green);
let b = color.clone().map_ref(|v| &v.blue, |v| &mut v.blue);

let speed = Mutable::new(None as Option<f32>);

col((
    card(row((label("What is your name?"), TextInput::new(name)))),
    card(row((label("What is your quest?"), TextInput::new(quest)))),
    card(col((
        label("What is your favorite colour?"),
        SliderWithLabel::new(r, 0.0, 1.0).round(0.01),
        SliderWithLabel::new(g, 0.0, 1.0).round(0.01),
        SliderWithLabel::new(b, 0.0, 1.0).round(0.01),
        StreamWidget(color.stream().map(|v| {
            Rectangle::new(v)
                .with_maximize(Vec2::X)
                .with_min_size(Unit::px2(100.0, 100.0))
        })),
    ))),
    card(row((
        label("What is the airspeed velocity of an unladen swallow?"),
        // Fallibly parse and fill in the None at the same time using the `State` trait
        // combinators
        TextInput::new(speed.clone().prevent_feedback().filter_map(
            |v| v.map(|v| v.to_string()),
            |v| Some(v.parse::<f32>().ok()),
        )),
        StreamWidget(speed.stream().map(|v| {
            match v {
                Some(v) => pill(Text::new(format!("{v} m/s"))),
                None => pill(Text::rich([
                    TextSegment::new("×").with_weight(violet::core::text::Weight::BOLD)
                ]))
                .with_background(danger_surface()),
            }
        })),
    ))),
))

Features

  • Declarative Widgets and reactive state
  • Flexible layout system for responsive layouts
  • Composable widgets
  • First class async and stream based widget reactivity
  • Thread local !Send + !Sync state and futures
  • Signal based state management
  • State and Stream morphisms
  • Wasm integration
  • State decomposition and composition
  • Renderer agnostic allowing embedding into other applications

State Management

State is primarily managed through futures-signals.

State can be decomposed into smaller parts, composed into larger parts, or be mapped to different types using the built-in state morphism (https://docs.rs/violet/0.1.0/violet/state/).

This allows mapping state from a struct to a string field for use in a text input widget, or mapping a larger user state to a different type to render reactively in a stream.

These state morphisms and bidirectional which means it allows mapping to another type of state and back, supporting both read and write operations.

This makes sliders and text inputs targeting individual fields or even fallible operations such as parsing a string into a number trivial.

Reactivity

Reactivity goes hand in hand with the state management.

State can be converted into an async stream of changes using StateStream and then mapped, filtered and combined using Rust's conventional stream combinators into a widget, such as a text display or color preview.

Most notable is that state and reactivity is managed locally, meaning that each widget can have its own state and reactivity without affecting the rest of the application.

Layout System

Violet uses a custom layout system that allows for a flexible layouts that respond to different sizes.

Each widgets has a preferred size and a minimum size. The layout system uses these sizes to determine how to distribute the available space between widgets.

Layouts

  • Flow - Works similar to a flexbox and distributes widgets in a row or column based on the available space and each widgets minimum and preferred size.
  • Stack - Stacks widgets on top of each other. Can be used to create overlays or centering or aligning widgets.
  • Float - Floats widgets on top of each other. Can be used to create tooltips or floating popups.

Contributing

Contributions are always welcome! Feel free to open an issue or a PR.

About

Retained mode GUI with focus on reactivity and composability

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages