{"id":21497322,"url":"https://github.com/jazzfool/otway","last_synced_at":"2025-08-12T13:34:53.308Z","repository":{"id":137122394,"uuid":"252317590","full_name":"jazzfool/otway","owner":"jazzfool","description":"Rust UI toolkit built on Reclutch with a familiar and straight-forward API.","archived":false,"fork":false,"pushed_at":"2021-07-01T05:22:18.000Z","size":250,"stargazers_count":9,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-06-04T17:21:47.462Z","etag":null,"topics":["gui","rust","toolkit","ui"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jazzfool.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":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-04-02T00:30:26.000Z","updated_at":"2021-12-23T09:00:26.000Z","dependencies_parsed_at":null,"dependency_job_id":"fc626753-f57b-44f5-93d0-654a158cbbfe","html_url":"https://github.com/jazzfool/otway","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/jazzfool/otway","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jazzfool%2Fotway","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jazzfool%2Fotway/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jazzfool%2Fotway/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jazzfool%2Fotway/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jazzfool","download_url":"https://codeload.github.com/jazzfool/otway/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jazzfool%2Fotway/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261374409,"owners_count":23148975,"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":["gui","rust","toolkit","ui"],"created_at":"2024-11-23T16:23:12.639Z","updated_at":"2025-08-12T13:34:53.115Z","avatar_url":"https://github.com/jazzfool.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Otway\n\n### GUI toolkit library which aims to continue the simplicity of Reclutch\n\n## Design Goals\n\n- **Open-ended input/eventing:** There are no restrictions on the types of user input that can be received. You can plug in your own windowing code and emit custom input events (e.g. stylus input), then write custom event handlers which can be attached to widgets. Since it's based on Reclutch, you can also write your own renderer by implementing `GraphicsDisplay`.\n- **First-class support for custom widgets:** This is driven by the principle that all the surrounding systems used by the toolkit should also be useful to custom widgets. For example, things like the theming system and input handlers can support custom widgets.\n- **Accessibility and replaceability of \"under-the-hood\" components:** Nothing is \"baked in\". Not even input handling. If you have a custom solution to widget mouse/keyboard events, handling widget focus, etc. then all of that is easily replaceable. These systems run alongside the widget event handling and are inspired very much by the concept of \"systems\" in ECS.\n- **High-level abstractions:** As nice as complete control over the UI is, it can be tedious. In that regard, it is easy to write abstractions to hide the details and focus on the content. One such abstraction is already provided; `view`.\n\n\u003cimg src=\".media/todos.png\" height=\"300em\"/\u003e\n\u003cimg src=\".media/counter.png\" height=\"300em\"/\u003e\n\n## ECS-like Components\n\nA lot of logic tends to be shared almost identically across many widgets. For example:\n\n- Mouse Interaction\n- Keyboard Interaction\n- Widget Focus\n\nWhat most toolkits do to generalize this across widgets is to simply bake it into the core UI code. However, this results in the UI being locked into a specific functionality, making it difficult to repurpose or customize.\n\nIn Otway, these are solved by components. Components simply consist of some data and an event handler. In that way, they resemble the structure of a widget. The difference is that components can be attached to any widget to inflict any functionality.\n\nYou can write your own components to introduce custom logic, but some have already been written in the toolkit.\n\n## Event Handling\n\nAll the eventing is routed through a single `uniq::rc::Queue`.\n\n`uniq` has support for arbitrary read/write parameters, thus anything can be passed through to event handling code:\n\n```rust\nlet mut l = aux.listen::\u003c(\n    Write\u003cSelf\u003e,\n    Read\u003cAux\u003cT\u003e\u003e,\n    Write\u003ci32\u003e,\n)\u003e().and_on(aux.id, |(this, aux, count), ev: \u0026SomeEvent| {\n    // this: \u0026mut Self\n    // aux: \u0026Aux\u003cT\u003e\n    // count: \u0026mut i32\n});\n\nself.listener = l;\n\n// ...\n\nlet mut count = 42;\n\n// read from the event queue and dispatch applicable events to the handlers.\nui::dispatch((self, aux, \u0026mut count), |(x, _, _)| \u0026mut x.listener);\n```\n\nSince it's all in a single queue, out-of-order events are impossible.\nFurthermore, the queue code is abstracted over with closures via `uniq` so that it feels natural to use, just like event handling in any other UI library.\n\n## Layout\n\nThe layout API should feel familiar if you've ever used Qt:\n\n```rust\nlet mut hstack = HStack::new().into_node(/* layout size */ None);\n\nhstack.push(\u0026mut button, /* per-item configuration */ None);\nhstack.push(\u0026mut label, None);\n\nlet mut vstack = VStack::new().into_node(None);\n\nhstack.push(vstack); // layouts can nest\n\n// hook the layout tree into the widget tree\ncontainer.set_layout(hstack);\n```\n\nAn explicit goal is for the `Layout` trait to be easy to implement in order to make custom layouts as simple as possible.\n\n## View\n\nIt's exhausting to deal with `Component` and `Listener` when all you want to do is throw together some buttons in a layout.\n\n`View` is an abstraction aiming to give you that comfort:\n\n```rust\n// A simple counter\nfn counter\u003cT: 'static\u003e(p: CommonRef, a: \u0026mut Aux\u003cT\u003e) -\u003e View\u003cT, u32\u003e {\n    let mut view = View::new(p, a, /* initial state */ 0u32);\n\n    let mut vstack = VStack::new().into_node(None);\n\n    view.button(a) // view mix-in which integrates the toolkit\n        .layout(\u0026mut vstack)\n        .text(\"Increment\")\n        .click(|view, _, _| {\n            view.set_state(|x| *x += 1);\n        });\n    \n    let label = view.label(a)\n        .layout(\u0026mut vstack)\n        .size(/* font size */ 42.0)\n        .into_inner();\n    \n    view.state_changed(move |view| {\n        let txt = format!(\"Count: {}\", view.state());\n        view.get_mut(label).unwrap().set_text(txt);\n    });\n\n    view.set_state(|_| {});\n\n    view\n}\n```\n\n## Partial View\n\nYou may have noticed from the previous example that children of a `View` are accessed by some sort of index: `view.get_mut(label).unwrap()`.\n\nThis makes storing children dynamically dead-simple (since you can also do `view.remove(label)`), but what if you precisely know what your children are? In that case, performance and strongly-typed semantics are wasted for no good reason.\n\nA `PartialView` implements `Widget` just like `View`, except it doesn't store children. Instead, you declare a type, implement a specialized version of `WidgetChildren` and `PartialView` will \"connect the dots\":\n\n```rust\nstruct Counter\u003cT: 'static\u003e {\n    count: u32,\n\n    incr: kit::Button\u003cT\u003e,\n    label: kit::Label\u003cT\u003e,\n}\n\nimpl\u003cT: 'static\u003e ViewPart\u003cT\u003e for Counter\u003cT\u003e {\n    fn children(\u0026self) -\u003e Vec\u003c\u0026dyn WidgetChildren\u003cT\u003e\u003e {\n        vec![\u0026self.incr, \u0026self.label]\n    }\n    \n    fn children_mut(\u0026mut self) -\u003e Vec\u003c\u0026mut dyn WidgetChildren\u003cT\u003e\u003e {\n        vec![\u0026mut self.incr, \u0026mut self.label]\n    }\n\n    // alternatively:\n//  otway::children![for \u003cT\u003e; incr, label];\n}\n\nfn counter\u003cT: 'static\u003e(p: CommonRef, a: \u0026mut ui::Aux\u003cT\u003e) -\u003e PartialView\u003cT, Counter\u003cT\u003e\u003e {\n    let mut view = PartialView::new(p, a, move |p| {\n        Counter {\n            count: 0,\n            incr: kit::Button::new(p, a),\n            label: kit::Label::new(p, a),\n        }\n    });\n\n    let btn_id = view.state().incr.id();\n\n    view.listener_mut()\n        .on(btn_id, |(view, _), _: \u0026PressEvent| {\n            view.set_state(|x| {\n                x.count += 1;\n            });\n        });\n\n    view.state_changed(|view| {\n        let txt = format!(\"Count: {}\", view.state().count);\n        view.state_mut().label.set_text(txt);\n    });\n\n    view\n}\n```\n\nWe didn't have to implement anything but `ViewPart` since `PartialView` will implement `Element`, `WidgetChildren` and everything else for us.\n\nNotice that now with `ViewPart` we are creating the children directly (though this is possible with `View`) and we can also access them directly (not possible with `View`). This is saved performance, memory, and static typing. The reason why should become clear when you realise that we are avoiding using `HashMap\u003c_, Box\u003cdyn Widget\u003e\u003e` (which is what `View` uses under-the-hood). Of course the trade-off is a somewhat more verbose API.\n\n## License\n\nOtway is licensed under either\n\n- [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0)\n- [MIT](https://opensource.org/licenses/MIT)\n\nat your choosing.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjazzfool%2Fotway","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjazzfool%2Fotway","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjazzfool%2Fotway/lists"}