{"id":16916176,"url":"https://github.com/zesterer/gui","last_synced_at":"2025-04-11T16:24:19.917Z","repository":{"id":66134610,"uuid":"318816670","full_name":"zesterer/gui","owner":"zesterer","description":"An experimental stateful, structured, declarative GUI crate","archived":false,"fork":false,"pushed_at":"2024-02-06T22:48:46.000Z","size":85,"stargazers_count":14,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-11T16:24:07.710Z","etag":null,"topics":[],"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/zesterer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":"2020-12-05T15:04:32.000Z","updated_at":"2024-11-04T10:13:59.000Z","dependencies_parsed_at":null,"dependency_job_id":"9c3cb74a-eb18-4de0-a19a-939edbec71da","html_url":"https://github.com/zesterer/gui","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zesterer%2Fgui","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zesterer%2Fgui/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zesterer%2Fgui/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zesterer%2Fgui/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zesterer","download_url":"https://codeload.github.com/zesterer/gui/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248438865,"owners_count":21103490,"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":[],"created_at":"2024-10-13T19:25:30.868Z","updated_at":"2025-04-11T16:24:19.898Z","avatar_url":"https://github.com/zesterer.png","language":"Rust","readme":"The current trend in Rust UI libraries seems to be towards relaxed,\nimmediate-mode APIs that guarantee little and have fairly significant refresh\noverhead. This crate is an attempt by me to shake things up and build something\ndeclarative, fast, structured, type-safe and with a clean separation between\ndata and UI state. Judge the success of this attempt for yourself.\n\n## Example\n\nHere follows an example of a fully-functioning calculator app written using this\nAPI.\n\n# \u003cimg src=\"misc/example.png\" alt=\"It's a calculator!\" text-align=\"center\"/\u003e\n\n```rust\n#[derive(Default)]\nstruct Data {\n    screen: String,\n    second: f64,\n    op: Option\u003cfn(f64, f64) -\u003e f64\u003e,\n}\n\nimpl Data {\n    fn start_op(\u0026mut self, op: fn(f64, f64) -\u003e f64) {\n        self.second = std::mem::take(\u0026mut self.screen).parse().unwrap();\n        self.op = Some(op);\n    }\n\n    fn calc(\u0026mut self) {\n        let screen = self.screen.parse().unwrap_or(0.0);\n        self.screen = match self.op.take() {\n            Some(op) =\u003e op(self.second, screen),\n            None =\u003e return,\n        }.to_string();\n    }\n}\n\nlet num_button = |n: \u0026'static str| Button::\u003cData\u003e::default_state()\n    .containing(Label::\u003cData\u003e::with_state(n).padded(16.0))\n    .on(Click, move |ctx| ctx.data.screen.push_str(\u0026n))\n    .padded(8.0);\n\nlet ui = List::\u003cData\u003e::vertical()\n    .push(Button::\u003cData\u003e::default_state()\n        .containing(Label::\u003cData\u003e::bind_state(|d| \u0026mut d.screen).padded(16.0))\n        .padded(8.0))\n    .push(List::\u003cData\u003e::horizontal()\n        .push(num_button(\"1\"))\n        .push(num_button(\"2\"))\n        .push(num_button(\"3\"))\n        .push(Button::\u003cData\u003e::default_state()\n            .containing(Label::\u003cData\u003e::with_state(\"+\").padded(16.0))\n            .on(Click, |ctx| ctx.data.start_op(|x, y| x + y))\n            .padded(8.0)))\n    .push(List::\u003cData\u003e::horizontal()\n        .push(num_button(\"4\"))\n        .push(num_button(\"5\"))\n        .push(num_button(\"6\"))\n        .push(Button::\u003cData\u003e::default_state()\n            .containing(Label::\u003cData\u003e::with_state(\"-\").padded(16.0))\n            .on(Click, |ctx| ctx.data.start_op(|x, y| x - y))\n            .padded(8.0)))\n    .push(List::\u003cData\u003e::horizontal()\n        .push(num_button(\"7\"))\n        .push(num_button(\"8\"))\n        .push(num_button(\"9\"))\n        .push(Button::\u003cData\u003e::default_state()\n            .containing(Label::\u003cData\u003e::with_state(\"*\").padded(16.0))\n            .on(Click, |ctx| ctx.data.start_op(|x, y| x * y))\n            .padded(8.0)))\n    .push(List::\u003cData\u003e::horizontal()\n        .push(Button::\u003cData\u003e::default_state()\n            .containing(Label::\u003cData\u003e::with_state(\"C\").padded(16.0))\n            .on(Click, |ctx| ctx.data.screen.clear())\n            .padded(8.0))\n        .push(num_button(\"0\"))\n        .push(Button::\u003cData\u003e::default_state()\n            .containing(Label::\u003cData\u003e::with_state(\"=\").padded(16.0))\n            .on(Click, |ctx| ctx.data.calc())\n            .padded(8.0))\n        .push(Button::\u003cData\u003e::default_state()\n            .containing(Label::\u003cData\u003e::with_state(\"/\").padded(16.0))\n            .on(Click, |ctx| ctx.data.start_op(|x, y| x / y))\n            .padded(8.0)))\n    .padded(8.0);\n\nWindow::new(ui).run(Data::default())\n```\n\n## Design\n\nHere follows a brief and very rough description of the philosophy underpinning\nthe API.\n\n- UIs are composed of widgets that sit in a tree hierarchy\n- Widget trees are created using an expressive-yet-intuitive builder pattern\n- Events are recursively passed down from parent widgets to child widgets\n- Children may request various layout properties from their parents (expand,\n  minimum size, etc.)\n- The data that the UI represents is independent of the UI and should, where\n  possible, not require special sauce to be reflected in the UI (i.e: if you\n  want to create a list, you just use a `Vec\u003cT\u003e` in the data model)\n- Widget trees may be constructed hierarchically such that each part of the UI\n  can only 'see' part of the underlying data model. It is possible to integrate\n  sub-trees into a parent in a modular way by providing a mapping between the\n  parent's data model and the child's data model\n- The API for creating widgets that have state (i.e: text boxes, toggle buttons)\n  is consistent\n- 'Transformations' such as padding are generic across widgets and use widget\n  wrapper types\n- The widget tree should never be recreated from scratch to improve performance\n- Rendering is performed on a canvas that emits simple primitives (lines,\n  rectanges, text, etc.) that allow for easy porting to many backends\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzesterer%2Fgui","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzesterer%2Fgui","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzesterer%2Fgui/lists"}