{"id":13732371,"url":"https://github.com/apertus-open-source-cinema/narui","last_synced_at":"2025-10-12T20:11:04.605Z","repository":{"id":45923189,"uuid":"398103778","full_name":"apertus-open-source-cinema/narui","owner":"apertus-open-source-cinema","description":"A react-inspired UI library for building multimedia desktop apps with rust and vulkan.","archived":false,"fork":false,"pushed_at":"2022-10-20T07:55:55.000Z","size":1054,"stargazers_count":55,"open_issues_count":12,"forks_count":3,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-07-10T18:53:18.811Z","etag":null,"topics":["hacktoberfest","react","rust","ui","vulkan"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/apertus-open-source-cinema.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-08-19T23:56:53.000Z","updated_at":"2025-05-16T01:37:16.000Z","dependencies_parsed_at":"2022-08-31T13:02:00.712Z","dependency_job_id":null,"html_url":"https://github.com/apertus-open-source-cinema/narui","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/apertus-open-source-cinema/narui","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apertus-open-source-cinema%2Fnarui","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apertus-open-source-cinema%2Fnarui/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apertus-open-source-cinema%2Fnarui/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apertus-open-source-cinema%2Fnarui/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/apertus-open-source-cinema","download_url":"https://codeload.github.com/apertus-open-source-cinema/narui/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apertus-open-source-cinema%2Fnarui/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268832575,"owners_count":24314432,"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","status":"online","status_checked_at":"2025-08-05T02:00:12.334Z","response_time":2576,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["hacktoberfest","react","rust","ui","vulkan"],"created_at":"2024-08-03T02:01:54.693Z","updated_at":"2025-10-12T20:10:59.569Z","avatar_url":"https://github.com/apertus-open-source-cinema.png","language":"Rust","funding_links":[],"categories":["Rust","GUI"],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003e\u003ccode\u003enarui\u003c/code\u003e\u003c/h1\u003e\n\nA react-inspired UI library for building multimedia desktop apps with rust and vulkan.\n\n* declarative UI with Ergonomics similar to React with hooks\n* JSX-like syntax for composing widgets\n* clean, readable \u0026 familiar looking application code\n* flutter-style box layout algorithm\n\n![narui node graph demo gif](./node_graph_demo.gif)\n\n## Usage\nHere is a small introduction of the basic concepts of `narui`. Many things might sound familiar if you used modern react or flutter. \n\nMake sure to also check out [the examples](examples/) that cover some more advanced topics and contain more complicated code.\n\n### Basics\n\n`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.\n\n`widgets` are functions that are annotated with the `widget` attribute macro and return either `Fragment` for composed widgets or `FragmentInner` for primitive widgets.\n\n```rust\n#[widget]\npub fn square(context: \u0026mut WidgetContext) -\u003e Fragment {\n    rsx! {\n        \u003crect fill=Some(color!(#ffffff)) constraint=BoxConstraints::tight(10.0, 10.0)\u003e\n    }\n}\n```\n\nThe widgets that are defined that way can then be used in other widgets or as the application toplevel via the rsx macro:\n```rust\nfn main() {\n    render(\n        WindowBuilder::new(),\n        rsx_toplevel! {\n            \u003csquare /\u003e\n        },\n    );\n}\n\n```\n\n\n### Composition\n\n`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.\n\n```rust\n#[widget(color = color!(#00aaaa))]  // we assign a default value to the color attribute which is used when color is unspecified\npub fn colored_column(children: FragmentChildren, color: Color, context: \u0026mut WidgetContext) -\u003e Fragment {\n    rsx! {\n        \u003crect fill=Some(color)\u003e\n            \u003cpadding padding=EdgeInsets::all(10.0)\u003e\n                \u003ccolumn\u003e\n                    {children}\n                \u003c/column\u003e\n            \u003c/padding\u003e\n        \u003c/rect\u003e\n    }\n}\n```\n\nWe can then use that widget like this:\n```rust\nrsx! {\n    \u003ccolored_container\u003e\n        \u003ctext\u003e{\"Hello, world\"}\u003c/text\u003e\n        \u003csquare /\u003e\n    \u003c/colored_container\u003e\n}\n```\n\nIf 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:\n```rust\nrsx! {\n    \u003ccolored_container\u003e\n        {\n            (0..10).map(|i| {\n                rsx! { \n                    \u003ctext key=\u0026i\u003e // \u003c-- explicit key is given here\n                        {format!(\"{}\", i)}\n                    \u003c/text\u003e \n                }\n            })\n        }\n    \u003c/colored_container\u003e\n}\n```\n\n\n\n### State, Hooks \u0026 Input handling\n\nThe `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.\n\nState management in `narui` is done using `hooks`. \nHooks work similiar to react hooks. The most simple hook is the `context.listenable` hook, which is used to store state. Widgets can subscribe to `Listenable`s 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.\n\n```rust\n#[widget(initial_value = 1)]\npub fn counter(initial_value: i32, context: \u0026mut WidgetContext) -\u003e Fragment {\n    let count = context.listenable(initial_value);\n    let on_click = move |context: \u0026CallbackContext| {\n        context.shout(count, context.spy(count) + 1)\n    };\n\n    rsx! {\n        \u003cbutton on_click=on_click\u003e\n            \u003ctext\u003e\n                {format!(\"{}\", context.listen(count))}\n            \u003c/text\u003e\n        \u003c/button\u003e\n    }\n}\n```\n\n### Animations\n\nUsually 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).\nListenables 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.\n\nTo 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.\n\n\n### Business logic interaction \u0026 interfacing the rest of the world\n\nInteraction with non UI-related code should be done similiar to how interaction with UI related code is done: \n* `Listenables` should signal the state from business logic to the UI.\n* 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 `mpsc`s or comparable techniques.\n\nThe 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](examples/stopwatch.rs).\n\n\n### Custom rendering\n\n`narui` allows `widget`s 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.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fapertus-open-source-cinema%2Fnarui","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fapertus-open-source-cinema%2Fnarui","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fapertus-open-source-cinema%2Fnarui/lists"}