You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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-
61
64
(We'll get back to this type `Data` in a bit.)
62
65
63
66
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).
65
70
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:
67
78
```rust
68
79
# externcrate kas;
69
80
# usekas::prelude::*;
70
81
# usekas::widgets::{format_value, Adapt, Button};
71
82
# #[derive(Clone, Debug)]
72
83
# structIncrement(i32);
73
84
# fncounter() ->implWidget<Data= ()> {
74
-
lettree=kas::column![
75
-
align!(center, format_value!("{}")),
76
-
kas::row![
85
+
letbuttons=row![
77
86
Button::label_msg("−", Increment(-1)),
78
87
Button::label_msg("+", Increment(1)),
79
-
]
80
-
.map_any(),
81
-
];
82
-
# Adapt::new(tree, 0)
88
+
];
89
+
lettree=column![
90
+
format_value!("{}").align(AlignHints::CENTER),
91
+
buttons.map_any(),
92
+
];
93
+
# let_=tree;
83
94
# }
84
95
```
85
96
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.
89
98
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.)
91
99
92
-
Now, you could, if you prefer, import the layout macros: `use kas::{align, column, row};`. *However,*
100
+
## Input Data
93
101
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.
96
103
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.
97
105
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.
99
107
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):
101
109
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.
105
114
106
115
### Providing input data: Adapt
107
116
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.
113
118
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:
117
120
```rust
118
121
# externcrate kas;
119
122
# usekas::prelude::*;
@@ -122,54 +125,26 @@ Thus,
122
125
# structIncrement(i32);
123
126
# fncounter() ->implWidget<Data= ()> {
124
127
# lettree=format_value!("{}");
125
-
Adapt::new(tree, 0)
128
+
tree.with_state(0)
126
129
# }
127
130
```
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`).
139
132
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.
140
135
141
136
## Messages
142
137
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
-
structIncrement(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:
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.
165
139
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).
167
141
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:
169
143
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.
171
147
172
-
Finally, we can handle our button click:
173
148
```rust
174
149
# externcrate kas;
175
150
# usekas::prelude::*;
@@ -178,27 +153,38 @@ Finally, we can handle our button click:
[`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!("{}"))`.
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.
0 commit comments