Git Product home page Git Product logo

rui's Introduction

logo

rui

build status dependency status

Experimental Rust UI library, inspired by SwiftUI. Early days, but some stuff already works. rui will be used for a future version of Audulus

rui is GPU rendered and updates reactively (when your state changes). The focus of rui is to have the best ergonomics, and use the simplest possible implementation. As such, there is no retained view tree (DOM) or view diffing. Everything is re-rendered when state changes, under the assumption that we can do that quickly.

discord server

Examples

obligatory Counter (cargo run --example counter):

use rui::*;

fn main() {
    state(
        || 1,
        |count, cx| {
            vstack((
                cx[count].padding(Auto),
                button("increment", move |cx| {
                    cx[count] += 1;
                })
                .padding(Auto),
            ))
        },
    )
    .run()
}

counter screenshot

some shapes (cargo run --example shapes):

use rui::*;

fn main() {
    vstack((
        circle()
            .color(RED_HIGHLIGHT)
            .padding(Auto),
        rectangle()
            .corner_radius(5.0)
            .color(AZURE_HIGHLIGHT)
            .padding(Auto)
    ))
    .run()
}

shapes screenshot

canvas for gpu drawing (cargo run --example canvas):

use rui::*;

fn main() {
    canvas(|_, rect, vger| {
        vger.translate(rect.center() - LocalPoint::zero());

        let paint = vger.linear_gradient(
            [-100.0, -100.0],
            [100.0, 100.0],
            AZURE_HIGHLIGHT,
            RED_HIGHLIGHT,
            0.0,
        );

        let radius = 100.0;
        vger.fill_circle(LocalPoint::zero(), radius, paint);
    })
    .run()
}

canvas screenshot

slider with map (cargo run --example slider):

use rui::*;

#[derive(Default)]
struct MyState {
    value: f32,
}

/// A slider with a value.
fn my_slider(s: impl Binding<f32>) -> impl View {
    with_ref(s, move |v| {
        vstack((
            v.to_string().font_size(10).padding(Auto),
            hslider(s).thumb_color(RED_HIGHLIGHT).padding(Auto),
        ))
    })
}

fn main() {
    state(MyState::default, |state, cx| 
        map(
            cx[state].value,
            move |v, cx| cx[state].value = v,
            |s, _| my_slider(s),
        ),
    )
    .run()
}

slider screenshot

widget gallery (cargo run --example gallery):

widgets gallery screenshot

Goals

  • Encode UI in types to ensure stable identity.
  • Optimize to reduce redraw.
  • Use vger-rs for rendering.
  • Minimal boilerplate.
  • Good looking.
  • No unsafe.
  • Accessibility for assistive technologies.

Optional Features

Why and how?

In the long term, I'd like to move Audulus over to Rust. After looking at other available UI options, it seemed best to implement something resembling the existing immediate mode UI system I already have working in Audulus, but better.

I had been enjoying the ergonomics of SwiftUI, but SwiftUI simply can't handle big node graphs very well (we have tried and had to fall back to manual layout and render with Canvas, so we couldn't put custom Views within each node). What you find with SwiftUI (particularly when profiling) is that there's a lot of machinery dealing with the caching aspects of things. It's opaque, scary (crashes on occasion, parts are implemented in C++ not Swift!), and can be rather slow. Often, it seems to be caching things thare are trivial to recompute in the first place.

Not so long ago, before programmable shaders, it was necessary to cache parts of a UI in textures (CoreAnimation for example does this) to get good performance. Now we have extremely fast GPUs and such caching is not necessary to achieve good performance. In fact if enough is animating, lots of texture caching can hinder performance, since the caches need to be updated so often. Plus, the textures consume a fair amount of memory, and when you have an unbounded node-graph like Audulus, that memory usage would be unbounded. And what resolution do you pick for those textures?

So rui starts from the assumption that 2D UI graphics (not general vector graphics!) are a trivial workload for a GPU. If you consider how advanced games are now, doing realtime global illumination and such, this seems intuitively correct, but Audulus more-or-less proves it. So that means we can do away with the texture caching, and we really might not even need damage regions either. I'm also skeptical of the need for parallel encoding or caching parts of the scene for 2D UI graphics, since, again, it's just a trivial GPU workload.

Layout, on the other hand, can't easily be offloaded to GPU free-performance land. It's necessary to cache layout information and try not to recompute it all the time. So rui caches layout and only recomputes it when the state changes (unlike a typical immediate mode UI which computes layout on the fly and is constrained to very simple layouts). For Audulus, this isn't quite enough, since some view-local state will be changing all the time as things are animating (Audulus solves this by only recomputing layout when the central document state changes). Perhaps this is where proponents of DOM-ish things (some other OOP-ish tree of widgets) would jump in and make their case, but I'm skeptical that's really necessary. Think of what actually needs to be (re)computed: a layout box for each (ephemeral) View. Does this really require a separate tree of objects? Time will tell!

Status

  • ✅ basic shapes: circle, rounded rectangle
  • ✅ basic gestures: tap, drag
  • ✅ hstack/vstack
  • ✅ text
  • ✅ padding
  • ✅ offsets
  • ✅ state
  • ✅ zstack
  • ✅ canvas (GPU vector graphics with vger)
  • ✅ bindings
  • ✅ list
  • ✅ sliders
  • ✅ knobs
  • ✅ editable text (still a bit rough)
  • ✅ any_view (view type erasure)
  • ✅ layout feedback
  • ✅ animation
  • ✅ UI unit testing

References

Towards principled reactive UI

Towards a unified theory of reactive UI

Flutter's Rendering Pipeline

Static Types in SwiftUI

How Layout Works in SwiftUI

Xilem: an architecture for UI in Rust

rui's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rui's Issues

Get error when running the example

I get an error when running the example:

RUST_BACKTRACE=1 cargo run --example counter

then

    Finished dev [unoptimized + debuginfo] target(s) in 0.12s
     Running `target/debug/examples/counter`
Using Mesa Intel(R) UHD Graphics 630 (CFL GT2) (Gl)
thread 'main' panicked at 'wgpu error: Validation Error

Caused by:
    In a RenderPass
      note: encoder = `vger encoder`
    In a set_pipeline command
      note: render pipeline = `<RenderPipeline-(0, 1, Gl)>`
    Render pipeline targets are incompatible with render pass
    Incompatible color attachment: the renderpass expected [Some(Rgba8UnormSrgb)] but was given [Some(Bgra8UnormSrgb)]

', /home/timpaikx/.cargo/registry/src/mirrors.bfsu.edu.cn-4c6e9dcaa6bd74e7/wgpu-0.13.1/src/backend/direct.rs:2391:5
stack backtrace:
   0: rust_begin_unwind
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/panicking.rs:584:5
   1: core::panicking::panic_fmt
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/panicking.rs:142:14
   2: wgpu::backend::direct::default_error_handler
             at /home/timpaikx/.cargo/registry/src/mirrors.bfsu.edu.cn-4c6e9dcaa6bd74e7/wgpu-0.13.1/src/backend/direct.rs:2391:5
   3: core::ops::function::Fn::call
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/ops/function.rs:77:5
   4: <alloc::boxed::Box<F,A> as core::ops::function::Fn<Args>>::call
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/alloc/src/boxed.rs:1954:9
   5: wgpu::backend::direct::ErrorSinkRaw::handle_error
             at /home/timpaikx/.cargo/registry/src/mirrors.bfsu.edu.cn-4c6e9dcaa6bd74e7/wgpu-0.13.1/src/backend/direct.rs:2377:17
   6: wgpu::backend::direct::Context::handle_error
             at /home/timpaikx/.cargo/registry/src/mirrors.bfsu.edu.cn-4c6e9dcaa6bd74e7/wgpu-0.13.1/src/backend/direct.rs:261:9
   7: <wgpu::backend::direct::Context as wgpu::Context>::command_encoder_end_render_pass
             at /home/timpaikx/.cargo/registry/src/mirrors.bfsu.edu.cn-4c6e9dcaa6bd74e7/wgpu-0.13.1/src/backend/direct.rs:2087:13
   8: <wgpu::RenderPass as core::ops::drop::Drop>::drop
             at /home/timpaikx/.cargo/registry/src/mirrors.bfsu.edu.cn-4c6e9dcaa6bd74e7/wgpu-0.13.1/src/lib.rs:3143:13
   9: core::ptr::drop_in_place<wgpu::RenderPass>
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/ptr/mod.rs:487:1
  10: vger::Vger::encode
             at /home/timpaikx/.cargo/registry/src/mirrors.bfsu.edu.cn-4c6e9dcaa6bd74e7/vger-0.2.4/src/lib.rs:293:9
  11: rui::context::Context::render
             at ./src/context.rs:280:9
  12: rui::event_loop::rui::{{closure}}
             at ./src/event_loop.rs:385:17
  13: winit::platform_impl::platform::sticky_exit_callback
             at /home/timpaikx/.cargo/registry/src/mirrors.bfsu.edu.cn-4c6e9dcaa6bd74e7/winit-0.26.1/src/platform_impl/linux/mod.rs:753:5
  14: winit::platform_impl::platform::wayland::event_loop::EventLoop<T>::run_return
             at /home/timpaikx/.cargo/registry/src/mirrors.bfsu.edu.cn-4c6e9dcaa6bd74e7/winit-0.26.1/src/platform_impl/linux/wayland/event_loop/mod.rs:394:21
  15: winit::platform_impl::platform::wayland::event_loop::EventLoop<T>::run
             at /home/timpaikx/.cargo/registry/src/mirrors.bfsu.edu.cn-4c6e9dcaa6bd74e7/winit-0.26.1/src/platform_impl/linux/wayland/event_loop/mod.rs:209:9
  16: winit::platform_impl::platform::EventLoop<T>::run
             at /home/timpaikx/.cargo/registry/src/mirrors.bfsu.edu.cn-4c6e9dcaa6bd74e7/winit-0.26.1/src/platform_impl/linux/mod.rs:669:56
  17: winit::event_loop::EventLoop<T>::run
             at /home/timpaikx/.cargo/registry/src/mirrors.bfsu.edu.cn-4c6e9dcaa6bd74e7/winit-0.26.1/src/event_loop.rs:154:9
  18: rui::event_loop::rui
             at ./src/event_loop.rs:289:5
  19: counter::main
             at ./examples/counter.rs:4:5
  20: core::ops::function::FnOnce::call_once
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/ops/function.rs:248:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
fish: Job 1, 'RUST_BACKTRACE=1 cargo run --ex…' terminated by signal SIGSEGV (Address boundary error)

OS: Archlinux
DE: GNOME 43 Wayland
git commit: 15273d1

If you need more information, I'm more than happy to provide it

Update on README or perhaps including a change log?

Was pretty excited to see where this project was heading when it was announce last year! I haven't seen or heard if much progress is still happening? If development is still active it would be great to see a change log with periodic updates.

Why not use AppKit/UIKit APIs for iOS/macOS targets?

It’s nice to see a UI toolkit inspired by SwiftUI, there’s a lot of things I really like about it, but I believe SwiftUI is just a VDOM-like frontend to UIKit/AppKit.

Anyway with regards to this library, wouldn’t it make more sense to build upon UIKit/AppKit APIs for iOS and macOS targets?

It’s all written in objective-c and therefore it’s viable to use from rust, and seems like you’d get much more mileage/utility from it.

passing a view into a `state` closure

I'm trying to add state to button for hovering:

/// Calls a function when the button is tapped.
pub fn button<A: 'static, F: Fn(&mut Context) -> A + 'static>(view: impl View, f: F) -> impl View {
    state(
        || false,
        move |hover, _| {
            view.padding(Auto)
                .background(
                    rectangle()
                        .corner_radius(BUTTON_CORNER_RADIUS)
                        .color(BUTTON_BACKGROUND_COLOR),
                )
                .tap(move |cx| f(cx))
                .hover(|_, inside| {
                    println!("inside button: {}", inside);
                })
                .role(Role::Button)
        },
    )
}

The error here is that padding (or any other modifier) wants to move out of view, which is captured by the closure passed to state.

I'm not quite sure how to deal with this. I've tried making Views Clone, but it seems to cause a big ripple.

make rui more testable

Currently rui would require a GPU for automated testing. If we could provide a text layout interface to layout (instead of vger) then we could do testing without a GPU (GitHub CI VMs don't have GPUs)

Questions regarding this project

Hey guys I have some questions regarding this project:

  1. Can this library be used to integrate into a game engine as easily as somehting like egui
  2. Are there any caching capabilities even though it is immediate?
  3. Does it compile for web as well?
  4. Does it lock you into some kind of framework like iced does?

support for layout where things don't expand to the available space

I'm working on a port of Flow to rui.

One immediate issue is with layout: a node in a node graph doesn't expand to fit the available space.

How can rui support that sort of layout?

It seems that SwiftUI doesn't actually expand stacks like rui does. Here's a simple SwiftUI VStack:

image

Here's a rui vstack:

image

Rendering artifacts with vger path fill in canvas

I'm not sure whether this is rui or vger-rs issue, but when I use vger path fill, the filled path "lingers", see attachment.

My code is roughly:

// start: LocalPoint, end: LocalPoint

canvas(|_cx, _rect, vger| {
    vger.move_to(start);
    vger.quad_to(end, end);
    vger.fill(red_paint);
})

canvas

Wrong rendering of Gradient in Windows

I tried running the gradient example from the readme in my Windows 10:

use rui::*;

fn main() {
    rui(canvas(|rect, vger| {
        vger.translate(rect.center() - LocalPoint::zero());

        let paint = vger.linear_gradient(
            [-100.0, -100.0],
            [100.0, 100.0],
            AZURE_HIGHLIGHT,
            RED_HIGHLIGHT,
            0.0,
        );

        let radius = 100.0;
        vger.fill_circle(LocalPoint::zero(), radius, paint);
    }));
}

And it looks like:

image

When it is supposed to look like ( image from the readme ):

image

can we nest states using only references?

I was looking into using references instead of passing around a context, and it seems tricky. Just wanted to record somewhere how far I got.

This is close, but doesn't quite work:

    struct State<F, V> {
        f: F,
        state: String,
        phantom: std::marker::PhantomData<V>,
    }

    impl<'a, V: View + 'a, F: Fn(&'a String) -> V + 'a> View for State<F, V> {
        fn draw(&self) {
            (self.f)(&self.state).draw();
        }
    }

The problem is in F: Fn(&'a String) -> V + 'a. This says the entire closure cannot outlive the String passed in. That's not quite what we want. It's ok if the closure lives longer. What we want is for the return type (V) to live as long as the String passed in.

What we want is something like F: for<'a> Fn(&'a String) -> V<'a> where V is a higher-kinded-type, but rust doesn't (yet) have this feature.

Z-sorting components based on user interaction

I'd like to make some textboxes which are draggable. That means they'll be able to overlap, so I have to make the one that has been interacted with most recently float on top of the others.

Can I do it with the current facilities (without implementing this manually with canvas)?

Potential bug? drag() callback is never called with GestureState::Began

To the best of my understanding the expected behaviour for drag is that when a drag begins the callback should be called with GestureState::Began
This does not happen currently.
Here's a minimal example:

use rui::*;

fn main() {
    rui(rectangle()
        .corner_radius(5.0)
        .color(AZURE_HIGHLIGHT.alpha(0.8))
        // Trivial drag implementation that only prints GestureState.
        .drag(move |_cx, _delta, state, _| match state {
            GestureState::Began => {
                println!("Began");
            }
            GestureState::Changed => {
                println!("Changed");
            }
            GestureState::Ended => {
                println!("Ended")
            }
        })
        .padding(Auto)
    );
}

The output looks as follows:

$ cargo run
...
Using Intel HD Graphics 4000 (Metal)
Changed
Changed
Changed
Changed
Changed
Changed
Changed
Changed
Ended

This was unexpected to me. Maybe that's intended, but I just thought I'd let you know.

Andoid OS

I m sorry, but I m curious,, are there plan to make android apk (native)?

wrong display in shapes example on Linux Xorg/Nvidia

excepted display a circle and rectangle but the display is confused

image

[w@ww rui]$ rustc --version
rustc 1.59.0 (9d1b2106e 2022-02-23)
[w@ww rui]$ screenfetch -n
 w@ww
 OS: Manjaro 21.2.4 Qonos
 Kernel: x86_64 Linux 5.10.102-1-MANJARO
 Uptime: 1d 1h 9m
 Packages: 1541
 Shell: bash 5.1.16
 Resolution: 1920x1080
 DE: KDE 5.91.0 / Plasma 5.24.2
 WM: KWin
 GTK Theme: Adwaita-dark [GTK2/3]
 Icon Theme: breeze-dark
 Disk: 247G / 489G (53%)
 CPU: AMD Ryzen 9 5900X 12-Core @ 24x 3.7GHz
 GPU: NVIDIA GeForce GT 1030
 RAM: 20197MiB / 64288MiB

all example of rui not working on Linux with Nvidia graphic card

how to add modifiers for views built through function composition

For example, currently, sliders are built through composition of other views:

pub fn hslider(value: impl Binding<f32>) -> impl View {

The view returned by hslider is an opaque type, so we can't add methods to it.

How can methods be added to the result of hslider while still defining hslider mostly as a function (i.e. not having to define it entirely in terms of an implementation of View)?

improve text_editor

  • selection
  • fix insertion point position
  • fix up/down arrow position
  • long text is rendered outside of the view

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.