Skip to content

Commit dfc74a3

Browse files
committed
Update Sync-counter
1 parent ffa66f9 commit dfc74a3

1 file changed

Lines changed: 43 additions & 43 deletions

File tree

src/sync-counter.md

Lines changed: 43 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -9,27 +9,23 @@ We complicate the previous example just a little bit!
99

1010
```rust
1111
# extern crate kas;
12-
use kas::widgets::{format_data, label_any, Adapt, Button, Slider};
13-
use kas::{messages::MessageStack, Action, Window};
12+
use kas::widgets::{AdaptWidget, Button, Label, Slider, column, format_data, row};
13+
use kas::window::Window;
1414

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

1818
#[derive(Clone, Copy, Debug)]
1919
struct Count(i32);
20-
21-
impl kas::app::AppData for Count {
22-
fn handle_messages(&mut self, messages: &mut MessageStack) -> Action {
20+
impl kas::runner::AppData for Count {
21+
fn handle_messages(&mut self, messages: &mut kas::runner::MessageStack) {
2322
if let Some(Increment(add)) = messages.try_pop() {
2423
self.0 += add;
25-
Action::UPDATE
26-
} else {
27-
Action::empty()
2824
}
2925
}
3026
}
3127

32-
fn counter() -> impl kas::Widget<Data = Count> {
28+
fn counter(title: &str) -> Window<Count> {
3329
// Per window state: (count, increment).
3430
type Data = (Count, i32);
3531
let initial: Data = (Count(0), 1);
@@ -38,38 +34,40 @@ fn counter() -> impl kas::Widget<Data = Count> {
3834
struct SetValue(i32);
3935

4036
let slider = Slider::right(1..=10, |_, data: &Data| data.1).with_msg(SetValue);
41-
let ui = kas::column![
37+
let ui = column![
4238
format_data!(data: &Data, "Count: {}", data.0.0),
4339
row![slider, format_data!(data: &Data, "{}", data.1)],
4440
row![
45-
Button::new(label_any("Sub")).with(|cx, data: &Data| cx.push(Increment(-data.1))),
46-
Button::new(label_any("Add")).with(|cx, data: &Data| cx.push(Increment(data.1))),
41+
Button::new(Label::new_any("Sub")).with(|cx, data: &Data| cx.push(Increment(-data.1))),
42+
Button::new(Label::new_any("Add")).with(|cx, data: &Data| cx.push(Increment(data.1))),
4743
],
4844
];
4945

50-
Adapt::new(ui, initial)
46+
let ui = ui
47+
.with_state(initial)
5148
.on_update(|_, state, count| state.0 = *count)
52-
.on_message(|_, state, SetValue(v)| state.1 = v)
49+
.on_message(|_, state, SetValue(v)| state.1 = v);
50+
Window::new(ui, title).escapable()
5351
}
5452

55-
fn main() -> kas::app::Result<()> {
53+
fn main() -> kas::runner::Result<()> {
5654
env_logger::init();
5755

58-
let theme = kas_wgpu::ShadedTheme::new().with_font_size(24.0);
56+
let count = Count(0);
57+
let theme = kas_wgpu::ShadedTheme::new();
5958

60-
kas::app::Default::with_theme(theme)
61-
.build(Count(0))?
62-
.with(Window::new(counter(), "Counter 1"))
63-
.with(Window::new(counter(), "Counter 2"))
59+
let mut runner = kas::runner::Runner::with_theme(theme).build(count)?;
60+
let _ = runner.config_mut().font.set_size(24.0);
61+
runner
62+
.with(counter("Counter 1"))
63+
.with(counter("Counter 2"))
6464
.run()
6565
}
6666
```
6767

6868
## AppData
6969

70-
In the previous example, our top-level `AppData` was `()`: `.build(())`.
71-
72-
This time, we want to store our counter in top-level `AppData`. But, as we saw with `Adapt`, state which doesn't react to messages is useless; hence we use a custom type and implement a message handler:
70+
In the previous example, our top-level `AppData` was `()` and our mutable state was stored in an [`Adapt`] widget. This time, we will store our counter in top-level `AppData`, in a custom type which includes a message handler:
7371
```rust
7472
# extern crate kas;
7573
# use kas::{messages::MessageStack, Action};
@@ -90,13 +88,9 @@ impl kas::app::AppData for Count {
9088
}
9189
}
9290
```
93-
[`AppData::handle_messages`] is less succinct than [`Adapt::on_message`], but dones the same job. The method notifies when widgets must be updated by returning [`Action::UPDATE`].
94-
95-
### As an input
96-
97-
We initialise our app with an instance of `Count`: `.build(Count(0))`.
91+
[`AppData::handle_messages`] is more verbose than [`Adapt::on_message`], but does the same job. The method notifies when widgets must be updated by returning [`Action::UPDATE`].
9892

99-
Note that `Count` is now an input to the widgets we construct:
93+
To integrate this into our example, we pass a `Count` object into [`kas::runner::Builder::build`] and adjust the prototype of `counter` to:
10094
```rust
10195
# extern crate kas;
10296
# use kas::{messages::MessageStack, Action};
@@ -125,7 +119,7 @@ let initial: Data = (Count(0), 1);
125119
```
126120
Note that our local data includes a *copy* of the top-level data `Count` (along with an initial value, `Count(0)`, which will be replaced before it is ever used).
127121

128-
We'll skip right over the widget declarations to the new `Adapt` node:
122+
We'll skip right over the widget declarations to the new [`Adapt`] node:
129123
```rust
130124
# extern crate kas;
131125
# use kas::widgets::{label_any, Adapt};
@@ -136,18 +130,21 @@ We'll skip right over the widget declarations to the new `Adapt` node:
136130
# struct SetValue(i32);
137131
# let ui = label_any("");
138132
# let initial = (Count(0), 1);
139-
Adapt::new(ui, initial)
140-
.on_update(|_, state, count| state.0 = *count)
141-
.on_message(|_, state, SetValue(v)| state.1 = v)
133+
let ui = ui
134+
.with_state(initial)
135+
.on_update(|_, state, count| state.0 = *count)
136+
.on_message(|_, state, SetValue(v)| state.1 = v);
142137
# }
143138
```
144139
The notable addition here is [`Adapt::on_update`], which takes a closure over the expected mutable reference to local `state` as well as *input* data `count` (i.e. the top-level data), allowing us to update local state with the latest top-level `count`.
145140

146-
Aside: this is really not how *adapting* top-level data with local state is *supposed* to work. Ideally, we'd omit the local copy of `Count` entirely and pass something like `(&Count, i32)` to local widgets. But, as any Rustacean knows, a reference requires a lifetime, and dealing with lifetimes can get complicated. The plan is to update our approach once Rust supports object-safe GATs (also known as Extended Generic Associated Types).
141+
Aside: you may wonder why we store `count` in [`Adapt`]'s state at all. Why not simply pass `(&Count, &i32)` (count, increment) down to the local UI? The answer is that we can't, because of lifetimes. To be specific, the input data type is formalized as an associated type, [`Widget::Data`], which must outlive instances of that type: that is any references embedded in an input data type must outlive the instances of the widgets they are passed to. Moreover, [`AppData`] requires lifetime `'static` (more as a simplification than because we truely couldn't support non-static lifetimes here, though there really isn't much use for them).
142+
143+
Aside aside: could we not make [`Widget::Data`] into a Generic Associated Type (GAT) to support lifetimes shorter than that of the widget object? Well, yes, but traits with GATs are not (yet) object-safe. This is a problem because object-safe widget types are important (both for variadic layout — e.g. a `TabStack` where pages use different widget types — and more fundamentally, namely to make [`Node`] work). So *maybe* this will be possible eventually, dependent on future Rust development.
147144

148-
## Multiple windows
145+
## Running multiple windows
149146

150-
It barely seems worth mentioning, but there's nothing stopping us from calling `fn counter` multiple times and constructing a new window around each:
147+
Constructing multiple windows under a UI runner is simple:
151148
```rust
152149
# extern crate kas;
153150
# use kas::{messages::MessageStack, Action, Window};
@@ -160,19 +157,22 @@ It barely seems worth mentioning, but there's nothing stopping us from calling `
160157
# kas::widgets::label_any("")
161158
# }
162159
# fn main() -> kas::app::Result<()> {
163-
# let theme = kas_wgpu::ShadedTheme::new().with_font_size(24.0);
164-
kas::app::Default::with_theme(theme)
165-
.build(Count(0))?
166-
.with(Window::new(counter(), "Counter 1"))
167-
.with(Window::new(counter(), "Counter 2"))
168-
.run()
160+
let mut runner = kas::runner::Runner::with_theme(theme).build(count)?;
161+
let _ = runner.config_mut().font.set_size(24.0);
162+
runner
163+
.with(counter("Counter 1"))
164+
.with(counter("Counter 2"))
165+
.run()
169166
# }
170167
```
171-
Of course, each window has its own local state stored in its [`Adapt`] node (the `increment`) while sharing the top-level `Count`.
168+
Each window has its own local state stored in its [`Adapt`] node (the `increment`) while sharing the top-level `Count`.
172169

173170
[`AppData`]: https://docs.rs/kas/latest/kas/app/trait.AppData.html
174171
[`AppData::handle_messages`]: https://docs.rs/kas/latest/kas/app/trait.AppData.html#tymethod.handle_messages
175172
[`Adapt`]: https://docs.rs/kas/latest/kas/widgets/struct.Adapt.html
176173
[`Adapt::on_message`]: https://docs.rs/kas/latest/kas/widgets/struct.Adapt.html#method.on_message
177174
[`Action::UPDATE`]: https://docs.rs/kas/latest/kas/struct.Action.html#associatedconstant.UPDATE
178175
[`Adapt::on_update`]: https://docs.rs/kas/latest/kas/widgets/struct.Adapt.html#method.on_update
176+
[`kas::runner::Builder::build`]: https://docs.rs/kas/latest/kas/runner/struct.Builder.html#method.build
177+
[`Widget::Data`]: https://docs.rs/kas/latest/kas/trait.Widget.html#associatedtype.Data
178+
[`Node`]: https://docs.rs/kas/latest/kas/struct.Node.html

0 commit comments

Comments
 (0)