{"id":13495820,"url":"https://github.com/audulus/rui","last_synced_at":"2025-05-13T19:07:37.141Z","repository":{"id":38102430,"uuid":"449541992","full_name":"audulus/rui","owner":"audulus","description":"Declarative Rust UI library","archived":false,"fork":false,"pushed_at":"2025-03-29T05:23:35.000Z","size":2638,"stargazers_count":1862,"open_issues_count":15,"forks_count":42,"subscribers_count":25,"default_branch":"main","last_synced_at":"2025-04-24T06:53:54.827Z","etag":null,"topics":["declarative-ui","gpu","graphics","gui","immediate-gui","rust","ui","user-interface","vger","wgpu","winit"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/audulus.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-01-19T04:06:54.000Z","updated_at":"2025-04-23T08:34:05.000Z","dependencies_parsed_at":"2024-02-26T00:27:26.938Z","dependency_job_id":"f77d8c45-4ae0-4c4b-8223-634f8224c84d","html_url":"https://github.com/audulus/rui","commit_stats":{"total_commits":1618,"total_committers":14,"mean_commits":"115.57142857142857","dds":0.08899876390605688,"last_synced_commit":"708a3d02cf1bf3fc774ef7b620ae78261627f8fa"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/audulus%2Frui","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/audulus%2Frui/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/audulus%2Frui/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/audulus%2Frui/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/audulus","download_url":"https://codeload.github.com/audulus/rui/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250870623,"owners_count":21500510,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["declarative-ui","gpu","graphics","gui","immediate-gui","rust","ui","user-interface","vger","wgpu","winit"],"created_at":"2024-07-31T19:01:38.567Z","updated_at":"2025-04-25T18:26:51.839Z","avatar_url":"https://github.com/audulus.png","language":"Rust","funding_links":[],"categories":["Rust","UI Framework"],"sub_categories":["Native"],"readme":"\u003cp align=\"center\"\u003e\n\u003cimg src=\"rui.png\" alt=\"logo\" width=\"200\"/\u003e\n\u003c/p\u003e\n\n# rui\n\n![build status](https://github.com/audulus/rui/actions/workflows/rust.yml/badge.svg)\n[![dependency status](https://deps.rs/repo/github/audulus/rui/status.svg)](https://deps.rs/repo/github/audulus/rui)\n\nExperimental Rust UI library, inspired by SwiftUI. Early days, but some stuff already works. rui will be used for a future version of [Audulus](http://audulus.com/)\n\nrui 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.\n\n[discord server](https://discord.gg/JCVVBU3sCN)\n\n- macOS ✅ \n- Windows ✅\n- Linux ✅ \n- iOS ✅ (see https://github.com/audulus/rui-ios)\n- wasm (WIP)\n\n## Examples\n\nobligatory Counter:\n\n```\ncargo run --example counter\n```\n\n```rust\nuse rui::*;\n\nfn main() {\n    state(\n        || 1,\n        |count, cx| {\n            vstack((\n                cx[count].padding(Auto),\n                button(\"increment\", move |cx| {\n                    cx[count] += 1;\n                })\n                .padding(Auto),\n            ))\n        },\n    )\n    .run()\n}\n```\n\n\u003cimg src=\"screenshots/counter.png\" alt=\"counter screenshot\" style=\"width:50%;\"\u003e\n\nsome shapes:\n\n```\ncargo run --example shapes\n```\n\n```rust\nuse rui::*;\n\nfn main() {\n    vstack((\n        circle()\n            .color(RED_HIGHLIGHT)\n            .padding(Auto),\n        rectangle()\n            .corner_radius(5.0)\n            .color(AZURE_HIGHLIGHT)\n            .padding(Auto)\n    ))\n    .run()\n}\n```\n\n\u003cimg src=\"screenshots/shapes.png\" alt=\"shapes screenshot\" style=\"width:50%;\"\u003e\n\ncanvas for gpu drawing:\n\n```\ncargo run --example canvas\n```\n\n```rust\nuse rui::*;\n\nfn main() {\n    canvas(|_, rect, vger| {\n        vger.translate(rect.center() - LocalPoint::zero());\n\n        let paint = vger.linear_gradient(\n            [-100.0, -100.0],\n            [100.0, 100.0],\n            AZURE_HIGHLIGHT,\n            RED_HIGHLIGHT,\n            0.0,\n        );\n\n        let radius = 100.0;\n        vger.fill_circle(LocalPoint::zero(), radius, paint);\n    })\n    .run()\n}\n```\n\n\u003cimg src=\"screenshots/canvas.png\" alt=\"canvas screenshot\" style=\"width:50%;\"\u003e\n\n`slider` with `map`:\n\n```\ncargo run --example slider\n```\n\n```rust\nuse rui::*;\n\n#[derive(Default)]\nstruct MyState {\n    value: f32,\n}\n\n/// A slider with a value.\nfn my_slider(s: impl Binding\u003cf32\u003e) -\u003e impl View {\n    with_ref(s, move |v| {\n        vstack((\n            v.to_string().font_size(10).padding(Auto),\n            hslider(s).thumb_color(RED_HIGHLIGHT).padding(Auto),\n        ))\n    })\n}\n\nfn main() {\n    state(MyState::default, |state, cx| \n        map(\n            cx[state].value,\n            move |v, cx| cx[state].value = v,\n            |s, _| my_slider(s),\n        ),\n    )\n    .run()\n}\n```\n\n\u003cimg src=\"screenshots/slider.png\" alt=\"slider screenshot\" style=\"width:50%;\"\u003e\n\ncalculator:\n\n```\ncd examples/calculator \u0026\u0026 cargo run\n```\n\n\u003cimg src=\"screenshots/calculator.png\" alt=\"calculator screenshot\" style=\"width:50%;\"\u003e\n\nsynth:\n\n```\ncd examples/synth \u0026\u0026 cargo run\n```\n\n\u003cimg src=\"screenshots/synth.png\" alt=\"synth screenshot\" style=\"width:50%;\"\u003e\n\nwidget gallery:\n\n```\ncargo run --example gallery\n```\n\n\u003cimg src=\"screenshots/gallery.png\" alt=\"widgets gallery screenshot\" style=\"width:50%;\"\u003e\n\n## Goals\n\n- Encode UI in types to ensure stable identity.\n- Optimize to reduce redraw.\n- Use [vger-rs](https://github.com/audulus/vger-rs) for rendering.\n- Minimal boilerplate.\n- Good looking.\n- No `unsafe`.\n- No use of `RefCell` (leads to potential runtime errors).\n- Accessibility for assistive technologies.\n\n## Optional Features\n\n- `winit` - (*enabled by default*) use winit for windowing.\n- Use `default-features = false` if you are embedding rui (see https://github.com/audulus/rui-ios).\n\n## Why and how?\n\nIn the long term, I'd like to move [Audulus](http://audulus.com/) 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.\n\nI had been enjoying the ergonomics of SwiftUI, but SwiftUI simply can't handle big node graphs very well ([we have tried]( https://github.com/audiokit/flow) and had to fall back to manual layout and render with [Canvas](https://developer.apple.com/documentation/swiftui/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.\n\nNot 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?\n\nSo 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.\n\nLayout, 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!\n\n## Status\n\n- ✅ basic shapes: circle, rounded rectangle\n- ✅ basic gestures: tap, drag\n- ✅ hstack/vstack\n- ✅ text\n- ✅ padding\n- ✅ offsets\n- ✅ state\n- ✅ zstack\n- ✅ canvas (GPU vector graphics with vger)\n- ✅ bindings\n- ✅ list\n- ✅ sliders\n- ✅ knobs\n- ✅ editable text (still a bit rough)\n- ✅ any_view (view type erasure)\n- ✅ layout feedback\n- ✅ animation\n- ✅ UI unit testing\n\n## References\n\n[Towards principled reactive UI](https://raphlinus.github.io/rust/druid/2020/09/25/principled-reactive-ui.html)\n\n[Towards a unified theory of reactive UI](https://raphlinus.github.io/ui/druid/2019/11/22/reactive-ui.html)\n\n[Flutter's Rendering Pipeline](https://www.youtube.com/watch?v=UUfXWzp0-DU)\n\n[Static Types in SwiftUI](https://www.objc.io/blog/2019/11/05/static-types-in-swiftui/)\n\n[How Layout Works in SwiftUI](https://www.hackingwithswift.com/books/ios-swiftui/how-layout-works-in-swiftui)\n\n[Xilem: an architecture for UI in Rust](https://raphlinus.github.io/rust/gui/2022/05/07/ui-architecture.html)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faudulus%2Frui","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faudulus%2Frui","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faudulus%2Frui/lists"}