https://github.com/jazzfool/otway
Rust UI toolkit built on Reclutch with a familiar and straight-forward API.
https://github.com/jazzfool/otway
gui rust toolkit ui
Last synced: 10 months ago
JSON representation
Rust UI toolkit built on Reclutch with a familiar and straight-forward API.
- Host: GitHub
- URL: https://github.com/jazzfool/otway
- Owner: jazzfool
- License: other
- Created: 2020-04-02T00:30:26.000Z (about 6 years ago)
- Default Branch: master
- Last Pushed: 2021-07-01T05:22:18.000Z (almost 5 years ago)
- Last Synced: 2025-06-04T17:21:47.462Z (about 1 year ago)
- Topics: gui, rust, toolkit, ui
- Language: Rust
- Homepage:
- Size: 244 KB
- Stars: 9
- Watchers: 1
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Roadmap: ROADMAP.md
Awesome Lists containing this project
README
# Otway
### GUI toolkit library which aims to continue the simplicity of Reclutch
## Design Goals
- **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`.
- **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.
- **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.
- **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`.

## ECS-like Components
A lot of logic tends to be shared almost identically across many widgets. For example:
- Mouse Interaction
- Keyboard Interaction
- Widget Focus
What 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.
In 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.
You can write your own components to introduce custom logic, but some have already been written in the toolkit.
## Event Handling
All the eventing is routed through a single `uniq::rc::Queue`.
`uniq` has support for arbitrary read/write parameters, thus anything can be passed through to event handling code:
```rust
let mut l = aux.listen::<(
Write,
Read>,
Write,
)>().and_on(aux.id, |(this, aux, count), ev: &SomeEvent| {
// this: &mut Self
// aux: &Aux
// count: &mut i32
});
self.listener = l;
// ...
let mut count = 42;
// read from the event queue and dispatch applicable events to the handlers.
ui::dispatch((self, aux, &mut count), |(x, _, _)| &mut x.listener);
```
Since it's all in a single queue, out-of-order events are impossible.
Furthermore, 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.
## Layout
The layout API should feel familiar if you've ever used Qt:
```rust
let mut hstack = HStack::new().into_node(/* layout size */ None);
hstack.push(&mut button, /* per-item configuration */ None);
hstack.push(&mut label, None);
let mut vstack = VStack::new().into_node(None);
hstack.push(vstack); // layouts can nest
// hook the layout tree into the widget tree
container.set_layout(hstack);
```
An explicit goal is for the `Layout` trait to be easy to implement in order to make custom layouts as simple as possible.
## View
It's exhausting to deal with `Component` and `Listener` when all you want to do is throw together some buttons in a layout.
`View` is an abstraction aiming to give you that comfort:
```rust
// A simple counter
fn counter(p: CommonRef, a: &mut Aux) -> View {
let mut view = View::new(p, a, /* initial state */ 0u32);
let mut vstack = VStack::new().into_node(None);
view.button(a) // view mix-in which integrates the toolkit
.layout(&mut vstack)
.text("Increment")
.click(|view, _, _| {
view.set_state(|x| *x += 1);
});
let label = view.label(a)
.layout(&mut vstack)
.size(/* font size */ 42.0)
.into_inner();
view.state_changed(move |view| {
let txt = format!("Count: {}", view.state());
view.get_mut(label).unwrap().set_text(txt);
});
view.set_state(|_| {});
view
}
```
## Partial View
You may have noticed from the previous example that children of a `View` are accessed by some sort of index: `view.get_mut(label).unwrap()`.
This 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.
A `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":
```rust
struct Counter {
count: u32,
incr: kit::Button,
label: kit::Label,
}
impl ViewPart for Counter {
fn children(&self) -> Vec<&dyn WidgetChildren> {
vec![&self.incr, &self.label]
}
fn children_mut(&mut self) -> Vec<&mut dyn WidgetChildren> {
vec![&mut self.incr, &mut self.label]
}
// alternatively:
// otway::children![for ; incr, label];
}
fn counter(p: CommonRef, a: &mut ui::Aux) -> PartialView> {
let mut view = PartialView::new(p, a, move |p| {
Counter {
count: 0,
incr: kit::Button::new(p, a),
label: kit::Label::new(p, a),
}
});
let btn_id = view.state().incr.id();
view.listener_mut()
.on(btn_id, |(view, _), _: &PressEvent| {
view.set_state(|x| {
x.count += 1;
});
});
view.state_changed(|view| {
let txt = format!("Count: {}", view.state().count);
view.state_mut().label.set_text(txt);
});
view
}
```
We didn't have to implement anything but `ViewPart` since `PartialView` will implement `Element`, `WidgetChildren` and everything else for us.
Notice 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<_, Box>` (which is what `View` uses under-the-hood). Of course the trade-off is a somewhat more verbose API.
## License
Otway is licensed under either
- [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0)
- [MIT](https://opensource.org/licenses/MIT)
at your choosing.