Skip to content

Commit 89acbfd

Browse files
committed
Update Counter
1 parent 7046249 commit 89acbfd

2 files changed

Lines changed: 86 additions & 100 deletions

File tree

examples/counter.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ use kas::widgets::{Button, column, format_value, row};
55
struct Increment(i32);
66

77
fn counter() -> impl Widget<Data = ()> {
8+
let buttons = row![
9+
Button::label_msg("−", Increment(-1)),
10+
Button::label_msg("+", Increment(1)),
11+
];
812
let tree = column![
913
format_value!("{}").align(AlignHints::CENTER),
10-
row![
11-
Button::label_msg("−", Increment(-1)),
12-
Button::label_msg("+", Increment(1)),
13-
]
14-
.map_any(),
14+
buttons.map_any(),
1515
];
1616

1717
tree.with_state(0)

src/counter.md

Lines changed: 81 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -9,35 +9,38 @@ The last example was a bit boring. Lets get interactive!
99
```rust
1010
# extern crate kas;
1111
use kas::prelude::*;
12-
use kas::widgets::{format_value, Adapt, Button};
12+
use kas::widgets::{Button, column, format_value, row};
1313

1414
#[derive(Clone, Debug)]
1515
struct Increment(i32);
1616

1717
fn counter() -> impl Widget<Data = ()> {
18-
let tree = kas::column![
19-
align!(center, format_value!("{}")),
20-
kas::row![
21-
Button::label_msg("", Increment(-1)),
22-
Button::label_msg("+", Increment(1)),
23-
]
24-
.map_any(),
18+
let buttons = row![
19+
Button::label_msg("", Increment(-1)),
20+
Button::label_msg("+", Increment(1)),
21+
];
22+
let tree = column![
23+
format_value!("{}").align(AlignHints::CENTER),
24+
buttons.map_any(),
2525
];
2626

27-
Adapt::new(tree, 0).on_message(|_, count, Increment(add)| *count += add)
27+
tree.with_state(0)
28+
.on_message(|_, count, Increment(add)| *count += add)
2829
}
2930

30-
fn main() -> kas::app::Result<()> {
31+
fn main() -> kas::runner::Result<()> {
3132
env_logger::init();
3233

33-
let theme = kas::theme::SimpleTheme::new().with_font_size(24.0);
34-
kas::app::Default::with_theme(theme)
35-
.build(())?
36-
.with(Window::new(counter(), "Counter"))
37-
.run()
34+
let theme = kas::theme::SimpleTheme::new();
35+
let mut app = kas::runner::Runner::with_theme(theme).build(())?;
36+
let _ = app.config_mut().font.set_size(24.0);
37+
let window = Window::new(counter(), "Counter").escapable();
38+
app.with(window).run()
3839
}
3940
```
4041

42+
## Preamble
43+
4144
#### Prelude
4245

4346
The [`kas::prelude`] includes a bunch of commonly-used, faily unambiguous stuff:
@@ -61,59 +64,59 @@ This is (return position) [impl trait](https://doc.rust-lang.org/stable/rust-by-
6164
(We'll get back to this type `Data` in a bit.)
6265

6366

64-
## Layout
67+
## Widgets
68+
69+
What is a widget? Simply a type implementing the [`Widget`] trait (or, depending on the context, an instance of such a type).
6570

66-
Our user interface should be a widget tree: lets use a column layout over the count and \[a row layout over the buttons\].
71+
Widgets must implement the super-traits [`Layout`] and [`Tile`], both of which are object-safe (use [`Tile::as_tile`] to get a `&dyn Tile`). [`Widget`] is also object-safe, but only where its associated [`Widget::Data`] type is specified (see [Input Data](#input-data) below).
72+
73+
In this example we'll only use library widgets and macro-synthesized widgets; [custom widgets](custom-widget.md) will be covered later.
74+
75+
### Layout macros
76+
77+
Our user interface should be a widget tree: lets use a [`row!`] of buttons and a [`column!`] layout for the top-level UI tree:
6778
```rust
6879
# extern crate kas;
6980
# use kas::prelude::*;
7081
# use kas::widgets::{format_value, Adapt, Button};
7182
# #[derive(Clone, Debug)]
7283
# struct Increment(i32);
7384
# fn counter() -> impl Widget<Data = ()> {
74-
let tree = kas::column![
75-
align!(center, format_value!("{}")),
76-
kas::row![
85+
let buttons = row![
7786
Button::label_msg("", Increment(-1)),
7887
Button::label_msg("+", Increment(1)),
79-
]
80-
.map_any(),
81-
];
82-
# Adapt::new(tree, 0)
88+
];
89+
let tree = column![
90+
format_value!("{}").align(AlignHints::CENTER),
91+
buttons.map_any(),
92+
];
93+
# let _ = tree;
8394
# }
8495
```
8596

86-
### Layout macros
87-
88-
[`kas::column!`] and [`kas::row!`] are layout macros which, as the name suggests, construct a column/row over other widgets.
97+
[`row!`] and [`column!`] are deceptively simple macros which construct a column or row over other widgets. I say *deceptively* simple because a fair amount of these macro's functionality is hidden, such as constructing a label widget from a string literal and emulating the [`.align(..)`](`AdaptWidget::align`) and [`.map_any()`](`AdaptWidgetAny::map_any`) (see [Input Data](#input-data)) method calls we see here. Still, you *should* be able to ignore this complexity.
8998

90-
[`kas::align!`] is another layout macro. Above, the `kas::` prefix is skipped, *not* because `kas::align` was imported, *but* because layout macros (in this case [`kas::column!`]) have direct support for parsing and evaluating other layout macros. (If you wrote `kas::align!` instead the result would function identically but with slightly different code generation.)
9199

92-
Now, you could, if you prefer, import the layout macros: `use kas::{align, column, row};`. *However,*
100+
## Input Data
93101

94-
- (`std`) [`column!`](https://doc.rust-lang.org/stable/std/macro.column.html) is a *very* different macro. This can result in surprising error messages if you forget to import `kas::column`.
95-
- If you replace `kas::row!` with `row!` you will get a compile error: the layout macro parser cannot handle <code>.[map_any][]()</code>. `kas::row![..]` evaluates to a complete widget; `row![..]` as an embedded layout does not.
102+
The [`Widget::Data`] type mentioned above is used to provide all Kas widgets with *input data*. This is passed into [`Events::update`] (called whenever the data may have changed) and to a number of event-handling methods.
96103

104+
Why? Most UIs need some form of mutable state. Some modern UI toolkits like [Iced](https://github.com/iced-rs/iced) and [Xilem](https://github.com/linebender/xilem) reconstruct their view tree (over a hidden widget tree) when this state changes; [egui](https://github.com/emilk/egui) goes even further and reconstructs the whole widget tree. Older stateful toolkits like GTK and Qt require binding widget properties or explicitly updating widgets. Kas finds a compromise between these models: widgets are stateful, yet derived from a common object and updated as required.
97105

98-
## Input data
106+
In our case, [`format_value!`] constructs a [`Text`] widget which formats its input data (an `i32`) to a `String` and displays that.
99107

100-
So, you may have wondered what the [`Widget::Data`] type encountered above is about. All widgets in Kas are provided *input data* (via [`Events::update`]) when the UI is initialised and *whenever that data changes* (not strictly true as you'll see when we get to custom widgets).
108+
Since it would be inconvenient to require an entire UI tree to use the same input data, Kas provides some tools to map that data (or in Xilem/Druid terminology, view that data through a lens):
101109

102-
The point is, a widget like [`Text`] is essentially a function `Fn(&A) -> String` where `&A` is *your input data*. [`format_value!`] is just a convenient macro to construct a [`Text`] widget.
103-
104-
Thus, `format_value!("{}")` is a [`Text`] widget which formats some input data to a `String`. But *what* input data?
110+
- [`AdaptWidget::map`] takes a closure which can, for example, map a struct-reference to a struct-field-reference. (In fact this is effectively all it can do due to lifetime restrictions; anything more complex requires using [`Adapt`] or similar.)
111+
- [`AdaptWidgetAny::map_any`] simply discards its input, passing `&()` to its child.
112+
- [`Adapt`] stores a mutable value in the UI tree, passing this value to its child.
113+
- [Custom widgets](custom-widget.md) may store state in the UI tree and pass arbitrary references to children.
105114

106115
### Providing input data: Adapt
107116

108-
There are three methods of providing *input data* to a UI:
109-
110-
- Custom widgets (advanced topic)
111-
- Top-level app data (the `()` of `.build(())`; we'll be using this in the next chapter)
112-
- [`Adapt`] nodes
117+
In this case, we'll use `()` as our top-level data and an [`Adapt`] node for the mutable state (the count). The [next chapter](sync-counter.md) will use top-level data instead.
113118

114-
All widgets in Kas may store state (though some are not persistent, namely view widgets (another advanced topic)). [`Adapt`] is a widget which stores user-defined data and message handlers.
115-
116-
Thus,
119+
The code:
117120
```rust
118121
# extern crate kas;
119122
# use kas::prelude::*;
@@ -122,54 +125,26 @@ Thus,
122125
# struct Increment(i32);
123126
# fn counter() -> impl Widget<Data = ()> {
124127
# let tree = format_value!("{}");
125-
Adapt::new(tree, 0)
128+
tree.with_state(0)
126129
# }
127130
```
128-
is a widget which wraps `tree`, providing it with *input data* of 0.
129-
130-
But to make this *do something* we need one more concept: *messages*.
131-
132-
### Mapping data
133-
134-
We should briefly justify `.map_any()` in our example: our [`Text`] widget expects input data (of type `i32`), while [`Button::label_msg`] constructs a <code>[Button][]\<[AccessLabel][]\></code> expecting data of type `()`.
135-
136-
The method <code>.[map_any][]()</code> maps the row of buttons to a new widget supporting (and ignoring) *any* input data.
137-
138-
We could instead use <code>[Button][]::new([label_any][]("+"))</code> which serves the same purpose, but ignoring that input data much further down the tree.
131+
calls [`AdaptWidget::with_state`] to construct an [`Adapt`] widget over `0` (with type `i32`).
139132

133+
A reference to this (i.e. `&i32`) is passed into our display widget (`format_value!("{}")`). Meanwhile,
134+
we used `buttons.map_any()` to ignore this value and pass `&()` to the [`Button`] widgets.
140135

141136
## Messages
142137

143-
Kas has a fairly simple event-handling model: **events** (like mouse clicks) and **input data** go *down* the tree, **messages** come back *up*. You can read more about this in [`kas::event`] docs.
144-
145-
When widgets receive an event, *often* this must be handled by some widget higher up the tree (an ancestor). For example, our "+" button must cause our [`Adapt`] widget to increment its state. To do that,
146-
147-
1. We define a message type, `Increment`
148-
2. The button [`push`]es a message to the message stack
149-
3. Our [`Adapt`] widget uses [`try_pop`] to retrieve that message
150-
151-
Aside: widgets have an associated type `Data`. So why don't they also have an associated type `Message` (or `Msg` for short)? Early versions of Kas (up to v0.10) did in fact have an `Msg` type, but this had some issues: translating message types between child and parent widgets was a pain, and supporting multiple message types was even more of a pain (mapping to a custom enum), and the `Msg` type must be specified when using `dyn Widget`. Using a variadic (type-erased) message stack completely avoids these issues, and at worst you'll see an `unhandled` warning in the log. In contrast, compile-time typing of input data is considerably more useful and probably a little easier to deal with (the main nuisance being mapping input data to `()` for widgets like labels which don't use it).
152-
153-
### Message types
154-
155-
What *is* a message? Nearly anything: the type *must* support [`Debug`] and *should* have a unique name. Our example defines:
156-
```rust
157-
#[derive(Clone, Debug)]
158-
struct Increment(i32);
159-
```
160-
Note that if your UI pushes a message to the stack but fails to handle it, you will get a warning message like this:
161-
```text
162-
[WARN kas_core::erased] unhandled: counter::Increment::Increment(1)
163-
```
164-
Use of built-in types like `()` or `i32` is possible but considered bad practice (imagine if the above warning was just `unhandled: 1`).
138+
While *input data* gets state *into* widgets, *messages* let us get, well, messages *out* of widgets.
165139

166-
### Buttons
140+
Any widget in the UI tree may post a message. While sometimes such messages have an intended recipient, often they are simply pushed to a message stack. Any widget above the source in the UI tree may handle messages (of known type).
167141

168-
This should be obvious: `Button::label_msg("+", Increment(1))` constructs a [`Button`][Button] which pushes the message `Increment(1)` when pressed.
142+
In practice, message handling has three steps:
169143

170-
### Handling messages
144+
1. Define a message type, in this case `Increment`. The only requirement of this type is that it supports `Debug`. (While we could in this case just use `i32`, using a custom type improves type safety and provides a better message in the log should any message go unhandled.)
145+
2. A widget (e.g. our buttons) pushes a message to the stack using [`EventCx::push`]. Many widgets provide convenience methods to do this, for example [`Button::label_msg`].
146+
3. Some widget above the sender in the UI tree retrieves the message using [`EventCx::try_pop`] and handles it somehow. [`Adapt::on_message`] provides a convenient way to write such a handler.
171147

172-
Finally, we can handle our button click:
173148
```rust
174149
# extern crate kas;
175150
# use kas::prelude::*;
@@ -178,27 +153,38 @@ Finally, we can handle our button click:
178153
# struct Increment(i32);
179154
# fn counter() -> impl Widget<Data = ()> {
180155
# let tree = format_value!("{}");
181-
Adapt::new(tree, 0)
182-
.on_message(|_, count, Increment(add)| *count += add)
156+
tree.with_state(0)
157+
.on_message(|_, count, Increment(add)| *count += add)
183158
# }
184159
```
185-
[`Adapt::on_message`] calls our closure whenever an `Increment` message is pushed with a mutable reference to its state, `count`. After handling our message, [`Adapt`] will update its descendants with the new value of `count`, thus refreshing the label: `format_value!("{}"))`.
186160

187-
[map_any]: https://docs.rs/kas/latest/kas/widgets/trait.AdaptWidgetAny.html#method.map_any
161+
Aside: feel free to write your message emitters first and handlers later. If you miss a handler you will see a message like this in your log:
162+
```text
163+
[2025-09-10T14:38:06Z WARN kas_core::erased] unhandled: Erased(Increment(1))
164+
```
165+
While the custom message types like `Increment` will not save you from *forgetting* to handle something, they will at least yield a comprehensible message in your log and prevent something else from handling the wrong message.
166+
167+
Should multiple messages use `enum` variants or discrete struct types? Either option works fine. Consider perhaps where the messages will be handled.
168+
169+
170+
[`Widget`]: https://docs.rs/kas/latest/kas/trait.Widget.html
171+
[`Layout`]: https://docs.rs/kas/latest/kas/trait.Layout.html
172+
[`Tile`]: https://docs.rs/kas/latest/kas/trait.Tile.html
173+
[`Tile::as_tile`]: https://docs.rs/kas/latest/kas/trait.Tile.html#method.as_tile
174+
[`AdaptWidgetAny::map_any`]: https://docs.rs/kas/latest/kas/widgets/trait.AdaptWidgetAny.html#method.map_any
188175
[`kas::prelude`]: https://docs.rs/kas/latest/kas/prelude/index.html
189-
[`kas::column!`]: https://docs.rs/kas/latest/kas/macro.column.html
190-
[`kas::row!`]: https://docs.rs/kas/latest/kas/macro.row.html
191-
[`kas::align!`]: https://docs.rs/kas/latest/kas/macro.align.html
176+
[`column!`]: https://docs.rs/kas/latest/kas/widgets/macro.column.html
177+
[`row!`]: https://docs.rs/kas/latest/kas/widgets/macro.row.html
178+
[`AdaptWidget::align`]: https://docs.rs/kas/latest/kas/widgets/trait.AdaptWidget.html#method.align
179+
[`AdaptWidget::map`]: https://docs.rs/kas/latest/kas/widgets/trait.AdaptWidget.html#method.map
180+
[`AdaptWidget::with_state`]: https://docs.rs/kas/latest/kas/widgets/trait.AdaptWidget.html#method.with_state
192181
[`Widget::Data`]: https://docs.rs/kas/latest/kas/trait.Widget.html#associatedtype.Data
193182
[`Events::update`]: https://docs.rs/kas/latest/kas/trait.Events.html#method.update
194183
[`Text`]: https://docs.rs/kas/latest/kas/widgets/struct.Text.html
195184
[`format_value!`]: https://docs.rs/kas/latest/kas/widgets/macro.format_value.html
196185
[`Adapt`]: https://docs.rs/kas/latest/kas/widgets/struct.Adapt.html
197-
[`kas::event`]: https://docs.rs/kas/latest/kas/event/index.html
198-
[`push`]: https://docs.rs/kas/latest/kas/event/struct.EventCx.html#method.push
199-
[`try_pop`]: https://docs.rs/kas/latest/kas/event/struct.EventCx.html#method.try_pop
200-
[Button]: https://docs.rs/kas/latest/kas/widgets/struct.Button.html
186+
[`EventCx::push`]: https://docs.rs/kas/latest/kas/event/struct.EventCx.html#method.push
187+
[`EventCx::try_pop`]: https://docs.rs/kas/latest/kas/event/struct.EventCx.html#method.try_pop
188+
[`Button`]: https://docs.rs/kas/latest/kas/widgets/struct.Button.html
201189
[`Adapt::on_message`]: https://docs.rs/kas/latest/kas/widgets/struct.Adapt.html#method.on_message
202-
[AccessLabel]: https://docs.rs/kas/latest/kas/widgets/struct.AccessLabel.html
203-
[label_any]: https://docs.rs/kas/latest/kas/widgets/fn.label_any.html
204190
[`Button::label_msg`]: https://docs.rs/kas/latest/kas/widgets/struct.Button.html#method.label_msg

0 commit comments

Comments
 (0)