A react-inspired UI library for building multimedia desktop apps with rust and vulkan.

Related tags

GUI react rust ui vulkan
Overview

narui

A react-inspired UI library for building multimedia desktop apps with rust and vulkan.

  • declarative UI with Ergonomics similar to React with hooks
  • JSX-like syntax for composing widgets
  • clean, readable & familiar looking application code
  • flutter-style box layout algorithm

narui node graph demo gif

Usage

Here is a small introduction of the basic concepts of narui. Many things might sound familiar if you used modern react or flutter.

Make sure to also check out the examples that cover some more advanced topics and contain more complicated code.

Basics

narui UIs are composed of widgets. These building blocks can be anything from a simple box to a complex node graph node or even a whole application. The widgets of an application form a tree, that is partially re-evaluated when needed.

widgets are functions that are annotated with the widget attribute macro and return either Fragment for composed widgets or FragmentInner for primitive widgets.

#[widget]
pub fn square(context: &mut WidgetContext) -> Fragment {
    rsx! {
        <rect fill=Some(color!(#ffffff)) constraint=BoxConstraints::tight(10.0, 10.0)>
    }
}

The widgets that are defined that way can then be used in other widgets or as the application toplevel via the rsx macro:

fn main() {
    render(
        WindowBuilder::new(),
        rsx_toplevel! {
            <square />
        },
    );
}

Composition

narui follows the principle of composition over inheritance: You build small reusable pieces that then form larger widgets and applications. To enable that, narui widgets can have parameters and children.

#[widget(color = color!(#00aaaa))]  // we assign a default value to the color attribute which is used when color is unspecified
pub fn colored_column(children: FragmentChildren, color: Color, context: &mut WidgetContext) -> Fragment {
    rsx! {
        <rect fill=Some(color)>
            <padding padding=EdgeInsets::all(10.0)>
                <column>
                    {children}
                </column>
            </padding>
        </rect>
    }
}

We can then use that widget like this:

} ">
rsx! {
    <colored_container>
        <text>{"Hello, world"}</text>
        <square />
    </colored_container>
}

If we programmatically generate multiple widgets (for example to display a list), we have to manually specify a key so that each widget can be uniquely identified:

} }) } } ">
rsx! {
    <colored_container>
        {
            (0..10).map(|i| {
                rsx! { 
                    <text key=&i> // <-- explicit key is given here
                        {format!("{}", i)}
                    </text> 
                }
            })
        }
    </colored_container>
}

State, Hooks & Input handling

The context, that is passed to every widget, acts like a pointer into the widget tree (and can therefore be cheaply copied), and is used to associate data to a specific widget.

State management in narui is done using hooks. Hooks work similiar to react hooks. The most simple hook is the context.listenable hook, which is used to store state. Widgets can subscribe to Listenables with the context.listen method and get reevaluated when the state that they listen to changed. Similiarily, the value of a listenable can be updated by using the context.shout method.

} } ">
#[widget(initial_value = 1)]
pub fn counter(initial_value: i32, context: &mut WidgetContext) -> Fragment {
    let count = context.listenable(initial_value);
    let on_click = move |context: &CallbackContext| {
        context.shout(count, context.spy(count) + 1)
    };

    rsx! {
        <button on_click=on_click>
            <text>
                {format!("{}", context.listen(count))}
            </text>
        </button>
    }
}

Animations

Usually widgets change state in reaction to a outside event like a mouse click or a message sent over a mpsc channel. Sometimes however it can be useful to drive a state change by the widget itself (for example to create animations). Listenables should not be updated during the evaluation of a widget but only in reaction to external events. This way, re-render loops can be avoided in a clean and easy way.

To create animations despite the existance of that rule, one can use the context.after_frame hook, which allows widgets to run a closure after each frame is rendered. This allows widgets to change Listenables in each frame and therefore create animations.

Business logic interaction & interfacing the rest of the world

Interaction with non UI-related code should be done similiar to how interaction with UI related code is done:

  • Listenables should signal the state from business logic to the UI.
  • Events should signal input from the UI to the business logic. This can be simple callbacks as you would to in your UI code, but it can also be more complicated with mpscs or comparable techniques.

The first step of interacting with Business logic is to run it. This can be done with the effect hook manually or by using the thread hook as a utility over that. For a simple example of how that can be acomplished, see examples/stopwatch.rs.

Custom rendering

narui allows widgets defined in downstream application code to emit fully custom vulkan api calls including drawcalls. This is especially important for multimedia applications. Example widgets that could be implemented this way are 3D viewports, image / video views and similiar things.

Comments
  • Subpass rendering

    Subpass rendering

    This implements arbitrary subpass rendering meaning: There is a new type of Fragment, a Fragment::Node with a subpass_resolve function. The rendering of this type of Fragment is defined to first render all its children on a seperate Framebuffer and then render this framebuffer onto the "next higher" Framebuffer with a user defined subpass_resolve function, which gets the color and depth attachments of the child Framebuffer as inputs and can then emit arbitrary vulkan draw calls.

    As a side artifact, this also now has the probper draw order forText and Raw RenderObjects.

    Open questions:

    • do we need any synchronization between the multiple renderpasses? -> NO
    • does this interact correctly with clipping?
    • does this currently properly render Raw RenderObjects?
    • how do downstream widgets properly cache the pipeline, etc? -> we store the device, renderpass and queue in the context and the widgets can use context.effect to cache them.
    • should we be using sub commandbuffers? (Then we could simply return a command buffer from the subpass_resolve function, and we should be able to bind our own index and vertex buffers (if this is ever necessary). -> YES
    • do we want partial redraws, using the subpasses as natural boundaries? -> LATER
    • is there any way to do this without vulkano from git? (The BufferAccess::slice function stores a reference to the original buffer and not something like a Arc :()
    opened by rroohhh 2
  • render lyon, text and rounded rect single pass

    render lyon, text and rounded rect single pass

    This renders lyon and text in a single pass, as well as adding special handling for (inverted) (stroked) rounded rects.

    Brings circle_bench from ~55fps to ~85-90fps

    TODO:

    • [ ] maybe properly sorting the text with the rest of the lyon / rounded rect elements
    • [ ] maybe split the draw calls at z indices where a RawRenderer exists
    opened by rroohhh 1
  • Frame pacing

    Frame pacing

    still has some bugs:

    1. latency for refresh_factor != 1 seems higher than needed?
    2. we always have a mismatch between the targeted frame seq and the actual, can we do something about that?
    3. sometime the seq get negative and everything hangs
    opened by rroohhh 0
  • Fix subpass

    Fix subpass

    Fixes subpass rendering (and rendering generally, the clipping example was broken without this aswell).

    Depends on #29 so only look at the last commit

    opened by rroohhh 0
  • do not eagerly acquire new swapchain images

    do not eagerly acquire new swapchain images

    This seems to break MoltenVK and it is generally unclear if what we were doing here was legal. Instead of this we should do proper frame pacing, but it seems like most of the vulkan extensions that would make this better are not available yet. So for now just degrade the latency again.

    opened by rroohhh 0
  • Macro rework

    Macro rework

    This reworks the macros to

    1. Reexport everything they need from narui / narui_core under their own module and use them in the generated macros.
    2. Put everything the widget macro generates into the widget module.

    This on the one hand fixes all the macro import issues and makes non star imports work and on the other hand reduces the global namespace pollution by putting everything into the generated module.

    (Depends on #15 aswell, so merge this after that)

    opened by rroohhh 0
  • implement context provider

    implement context provider

    Context providers should give a way to search for a given thing identified by type up in the widget tree. This would be usefull for e.g. themes. Children that depend on changed parent state should be re rendered when said state changes.

    opened by anuejn 0
  • Use `stencil` and `depth`

    Use `stencil` and `depth`

    Currently we use the depth buffer for clipping and depth testing, which is a bit awkward and requires some user intervention (the is_clipper attribute of rects for example).

    We probably should use the stencil buffer for clipping and the depth buffer just for depth testing.

    opened by rroohhh 0
  • Keyboard Input

    Keyboard Input

    After some discussions with @rroohhh we came up with a design that uses a (to be invented) context provider that tracks the keyboard focus. Focusable inputs should register with that provider and in turn get a callback that is called when they are (un-)focussed.

    opened by anuejn 0
Releases(narui_widgets-v0.1.1)
  • narui_widgets-v0.1.1(Dec 13, 2021)

    minor fixes for better compatibility

    Commit Statistics

    • 3 commits contributed to the release.
    • 0 commits where understood as conventional.
    • 0 issues like '(#ID)' where seen in commit messages

    Commit Details

    view details
    • Uncategorized
      • Release narui_core v0.1.1, narui_widgets v0.1.1 (d0eb5a7)
      • Adjusting changelogs prior to release of narui_core v0.1.1, narui_widgets v0.1.1 (7e51058)
      • upgrade vulcano-rs to 0.27.0 (a4ec9f9)
    Source code(tar.gz)
    Source code(zip)
  • narui_core-v0.1.2(Dec 13, 2021)

    Commit Statistics

    • 2 commits contributed to the release.
    • 1 commit where understood as conventional.
    • 0 issues like '(#ID)' where seen in commit messages

    Commit Details

    view details
    • Uncategorized
      • Release narui_core v0.1.1, narui_widgets v0.1.1 (5fe495b)
      • use TriangleList again (e9147ed)
    Source code(tar.gz)
    Source code(zip)
  • narui_core-v0.1.1(Dec 13, 2021)

    minor fixes for better compatibility

    Commit Statistics

    • 4 commits contributed to the release over the course of 14 calendar days.
    • 0 commits where understood as conventional.
    • 0 issues like '(#ID)' where seen in commit messages

    Commit Details

    view details
    • Uncategorized
      • Adjusting changelogs prior to release of narui_core v0.1.1, narui_widgets v0.1.1 (7e51058)
      • switch to 4 sample anti-aliasing (cdcc471)
      • upgrade vulcano-rs to 0.27.0 (a4ec9f9)
      • do not eagerly acquire new swapchain images (6b78d9b)
    Source code(tar.gz)
    Source code(zip)
Owner
apertus° - open source cinema
Open Technology for Professional Film Production
apertus° - open source cinema
Build beautiful desktop apps with flutter and rust. 🌠 (wip)

flutter-rs Build flutter desktop app in dart & rust. Get Started Install requirements Rust flutter sdk Develop install the cargo flutter command cargo

null 2k Dec 26, 2022
egui integration for ash (Vulkan).

egui-ash egui integration for ash (Vulkan). This crate natively supports the multi-viewports feature added since version 0.24 of egui. You can use gpu

Orito Itsuki 6 Dec 24, 2023
A Vulkan renderer for egui using Ash.

egui-ash-renderer A Vulkan renderer for egui using Ash. This is meant to add support for egui in your existing Vulkan/ash applications. Not a full efr

Adrien Bennadji 4 Apr 10, 2024
Sycamore - A reactive library for creating web apps in Rust and WebAssembly

Sycamore What is Sycamore? Sycamore is a modern VDOM-less web library with fine-grained reactivity. Lightning Speed: Sycamore harnesses the full power

Sycamore 1.8k Jan 5, 2023
Automatically create GUI applications from clap3 apps

Automatically create GUI applications from clap3 apps

Michał Gniadek 340 Dec 20, 2022
An idiomatic GUI library inspired by Elm and based on gtk4-rs

An idiomatic GUI library inspired by Elm and based on gtk4-rs. Relm4 is a new version of relm that's built from scratch and is compatible with GTK4 an

Aaron Erhardt 722 Dec 31, 2022
🖼 A Rust library for building user interfaces on the web with WebGL.

wasm-ui Hey! Welcome to my little experiment. It's a Rust library for building user interfaces on the web. You write the interface in Rust, and wasm-u

Harry 10 Dec 1, 2022
Idiomatic, GTK+-based, GUI library, inspired by Elm, written in Rust

Relm Asynchronous, GTK+-based, GUI library, inspired by Elm, written in Rust. This library is in beta stage: it has not been thoroughly tested and its

null 2.2k Dec 31, 2022
A cross-platform GUI library for Rust, inspired by Elm

Iced A cross-platform GUI library for Rust focused on simplicity and type-safety. Inspired by Elm. Features Simple, easy-to-use, batteries-included AP

Héctor Ramón 17.5k Jan 2, 2023
SwiftUI Inspired UI Library written in rust

Mule (Definitely a Work in Progress) The night I started this project I was on the couch drinking a Moscow Mule.

null 40 Jun 22, 2022
A cross-platform GUI library for Rust, inspired by Elm

Iced A cross-platform GUI library for Rust focused on simplicity and type-safety. Inspired by Elm. Features Simple, easy-to-use, batteries-included AP

null 17.5k Dec 28, 2022
SixtyFPS is a toolkit to efficiently develop fluid graphical user interfaces for any display: embedded devices and desktop applications. We support multiple programming languages, such as Rust, C++ or JavaScript.

SixtyFPS is a toolkit to efficiently develop fluid graphical user interfaces for any display: embedded devices and desktop applications. We support multiple programming languages, such as Rust, C++ or JavaScript.

SixtyFPS 5.5k Jan 1, 2023
Build smaller, faster, and more secure desktop applications with a web frontend.

TAURI Tauri Apps footprint: minuscule performance: ludicrous flexibility: gymnastic security: hardened Current Releases Component Descrip

Tauri 56.3k Jan 3, 2023
ZeroTier Desktop Tray Application and UI

ZeroTier Desktop Tray Application and User Interface This is (as of ZeroTier 1.8) the system tray application and user interface for controlling a loc

ZeroTier, Inc. 102 Dec 20, 2022
Native Maps for Web, Mobile and Desktop

mapr Native Maps for Web, Mobile and Linux A map rendering library written in Rust. Example | Book | API | Chat in Matrix Space Project State This pro

MapLibre 942 Jan 2, 2023
A powerful desktop widget app for windows, built with Vue and Tauri.

DashboardX Widgets A powerful desktop widget app for windows, built with Vue and Tauri. Still in development Currently only runs on windows (uses nati

DashboardX Widgets 3 Oct 25, 2023
Access German-language public broadcasting live streams and archives on the Linux Desktop

Deutsche Version Televido Televido (“Television” in Esperanto) lets you livestream, search, play and download media from German-language public televi

David C. 10 Nov 4, 2023
Integrate Qml and Rust by building the QMetaObject at compile time.

QMetaObject crate for Rust The qmetaobject crate is a crate which is used to expose rust object to Qt and QML. Objectives Rust procedural macro (custo

Woboq GmbH 495 Jan 3, 2023
Desktop GUI Framework

Azul - Desktop GUI framework WARNING: The features advertised in this README may not work yet. Azul is a free, functional, immediate mode GUI framewor

Maps4Print 5.4k Jan 1, 2023