Skip to content

Commit 3a4b4c7

Browse files
committed
Update Custom widget
1 parent 82e4746 commit 3a4b4c7

2 files changed

Lines changed: 126 additions & 84 deletions

File tree

examples/custom-widget.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ use kas::widgets::{AccessLabel, Button, Row, Text, format_value};
44
#[derive(Clone, Debug)]
55
struct Increment(i32);
66

7-
impl_scope! {
7+
#[impl_self]
8+
mod Counter {
89
#[widget]
910
#[layout(column![
1011
self.display.align(AlignHints::CENTER),

src/custom-widget.md

Lines changed: 124 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,36 @@
22

33
*Topics: custom widgets*
44

5+
Custom widgets are essentially the hard way to do things, but they do have their uses:
6+
7+
- Custom local state and event handlers are significantly more flexible than [`Adapt`]
8+
- Custom [`Layout`] implementations and custom mid-level graphics operations (e.g. the [Clock example](https://github.com/kas-gui/kas/tree/master/examples#clock))
9+
510
![Counter](screenshots/counter.png)
611

712
Here we rewrite the counter as a custom widget. There's no reason to do so for this particular case, but it serves as a simple example to the topic.
813

914
```rust
1015
# extern crate kas;
1116
use kas::prelude::*;
12-
use kas::widgets::{format_value, AccessLabel, Button, Row, Text};
17+
use kas::widgets::{AccessLabel, Button, Row, Text, format_value};
1318

1419
#[derive(Clone, Debug)]
1520
struct Increment(i32);
1621

17-
impl_scope! {
18-
#[widget{
19-
layout = column![
20-
align!(center, self.display),
21-
self.buttons,
22-
];
23-
}]
22+
#[impl_self]
23+
mod Counter {
24+
#[widget]
25+
#[layout(column![
26+
self.display.align(AlignHints::CENTER),
27+
self.buttons,
28+
])]
2429
struct Counter {
2530
core: widget_core!(),
2631
#[widget(&self.count)]
2732
display: Text<i32, String>,
2833
#[widget]
29-
buttons: Row<Button<AccessLabel>>,
34+
buttons: Row<[Button<AccessLabel>; 2]>,
3035
count: i32,
3136
}
3237
impl Self {
@@ -54,69 +59,101 @@ impl_scope! {
5459
}
5560
}
5661

57-
fn main() -> kas::app::Result<()> {
62+
fn main() -> kas::runner::Result<()> {
5863
env_logger::init();
5964

60-
let theme = kas::theme::SimpleTheme::new().with_font_size(24.0);
61-
kas::app::Default::with_theme(theme)
62-
.build(())?
63-
.with(Window::new(Counter::new(0), "Counter"))
64-
.run()
65+
let window = Window::new(Counter::new(0), "Counter");
66+
67+
let theme = kas::theme::SimpleTheme::new();
68+
let mut app = kas::runner::Runner::with_theme(theme).build(())?;
69+
let _ = app.config_mut().font.set_size(24.0);
70+
app.with(window).run()
6571
}
6672
```
6773

6874
## Macros
6975

70-
### `impl_scope`
76+
### `impl_self`
7177

72-
[`impl_scope!`] is a macro from [impl-tools]. This macro wraps a type definition and `impl`s on that type. (Unfortunately it also inhibits `rustfmt` from working, [for now](https://github.com/rust-lang/rustfmt/pull/5538).) Here, it serves two purposes:
78+
[`impl_self`] is an attribute macro from [impl-tools]. This macro wraps a type definition and `impl`s on that type with a fake module of the same name. This fake module (here `mod Counter`) does not need to import (`use`) symbols from the parent module; in fact it may only contain one type definition with the same name as the fake module and `impl` items on this type.
7379

74-
1. `impl Self` syntax (not important here, but much more useful on structs with generics)
75-
2. To support the [`#[widget]`][attr-widget] attribute-macro. This attribute-macro is a Kas extension to [`impl_scope!`], and can act on anything within that scope (namely, it will check existing impls of [`Layout`], [`Events`] and [`Widget`], reading definitions of associated `type Data`, injecting certain missing methods into these impls, and write new impls).
80+
Unfortunately, rust-analyzer does not fully support this: it may insert `use` statements inside the fake module. It may also mis-report errors against the entire fake module. One may instead use the [`impl_scope!`] macro, however since [`rustfmt` refuses to format its contents](https://github.com/rust-lang/rustfmt/pull/5538) this is the worse option. Perhaps some day this stuff will get fixed?
7681

77-
### `#[widget]`
82+
So, *why* do we have to wrap our widget implementations with a macro? Firstly, it supports `impl Self` syntax. Secondly (and much more importantly), it allows the `#[widget]` macro to operate on the type definition and various widget trait implementations simultaneously. This allows the macro to do useful things, like provide contextual default method implementations, inject debugging checks into provided method implementations, provide contextual warnings, and use a synthesized type to store extra state required by macro-generated layout code.
7883

79-
The [`#[widget]`][attr-widget] attribute-macro is used to implement the [`Widget`] trait. *This is the only supported way to implement [`Widget`].* There are a few parts to this.
84+
### `#[widget]`
8085

81-
**First**, we must apply [`#[widget]`][attr-widget] to the struct. (The `layout = ...;` argument (and `{ ... }` braces) are optional; some other arguments might also occur here.)
82-
```ignore
83-
#[widget{
84-
layout = column![
85-
align!(center, self.display),
86-
self.buttons,
87-
];
88-
}]
86+
The [`#[widget]`][attr-widget] attribute-macro is used to implement the [`Widget`] trait. *This is the only supported way to implement [`Widget`].*
87+
```rust,ignore
88+
#[widget]
89+
struct Counter { /* .. */ }
8990
```
9091

91-
**Second**, all widgets must have "core data". This *might* be an instance of [`CoreData`] or *might* be some custom generated struct (but with the same public `rect` and `id` fields and constructible via [`Default`]). We *must* provide a field of type `widget_core!()`.
92-
```ignore
92+
Like it or not, the `#[widget]` macro is a fairly significant piece of what makes Kas work. Fortunately, most of the complexity is hidden such that you don't need to know about it and can refer to documentation on standard Rust traits.
93+
94+
To get the best diagnostics, be sure to use the `nightly-diagnostics` feature. (Hopefully Rust will stabilize custom proc-macro lints in the next year or so!)
95+
96+
#### Core data
97+
98+
All widgets must have a "core data" field. Typically this is named `core`; it must have type `widget_core!()` and can be initialized using `Default`.
99+
```rust,ignore
93100
core: widget_core!(),
94101
```
95102

96-
**Third**, any fields which are child widgets must be annotated with `#[widget]`. (This enables them to be configured and updated.)
103+
#### Child widgets
97104

98-
We can use this attribute to configure the child widget's input data too: in this case, `display` is passed `&self.count`. Beware only that there is no automatic update mechanism: when mutating a field used as input data it may be necessary to explicitly update the affected widget(s) (see the note after the fourth step below).
99-
```rust
100-
# extern crate kas;
101-
# use kas::impl_scope;
102-
# use kas::widgets::{AccessLabel, Button, Row, Text};
103-
# impl_scope! {
104-
# #[widget{
105-
# Data = ();
106-
# layout = "";
107-
# }]
108-
struct Counter {
109-
core: widget_core!(),
105+
There are two types of child widgets: hidden layout-generated children and explicit children. The latter are fields with a `#[widget]` attribute:
106+
```rust,ignore
110107
#[widget(&self.count)]
111108
display: Text<i32, String>,
112109
#[widget]
113-
buttons: Row<Button<AccessLabel>>,
114-
count: i32,
115-
}
116-
# }
110+
buttons: Row<[Button<AccessLabel>; 2]>,
117111
```
118112

119-
**Fourth**, the input `Data` type to our `Counter` widget must be specified somewhere. In our case, we specify this by implementing [`Events`]. (If this trait impl was omitted, you could write `Data = ();` as an argument to [`#[widget]`][attr-widget].)
113+
The first of these is a [`Text`] widget, passed `&self.count` as input data. The second is a [`Row`] widget over [`Button`]s over [`AccessLabel`]s. Since we didn't specify a data mapping for this second widget, it is is passed the `Count` widget's input data (`()`).
114+
115+
Omitting `#[widget]` on a field which is a child widget is an error; sometimes the outer `#[widget]` attribute-macro will report the issue but not always. For example, if we omit the attribute on `buttons` and run, we get a backtrace like the following:
116+
```
117+
thread 'main' (413532) panicked at /path/to/kas/crates/kas-core/src/core/data.rs:123:13:
118+
WidgetStatus of #INVALID: require Configured, found New
119+
stack backtrace:
120+
0: __rustc::rust_begin_unwind
121+
at /rustc/a1208bf765ba783ee4ebdc4c29ab0a0c215806ef/library/std/src/panicking.rs:698:5
122+
1: core::panicking::panic_fmt
123+
at /rustc/a1208bf765ba783ee4ebdc4c29ab0a0c215806ef/library/core/src/panicking.rs:75:14
124+
2: kas_core::core::data::WidgetStatus::require
125+
at /path/to/kas/crates/kas-core/src/core/data.rs:123:13
126+
3: kas_core::core::data::WidgetStatus::size_rules
127+
at /path/to/kas/crates/kas-core/src/core/data.rs:157:18
128+
4: <kas_widgets::list::List<C,D> as kas_core::core::layout::Layout>::size_rules
129+
at /path/to/kas/crates/kas-widgets/src/list.rs:207:1
130+
5: <custom_widget::Counter as kas_core::core::layout::MacroDefinedLayout>::size_rules::{{closure}}
131+
at ./examples/custom-widget.rs:7:1
132+
...
133+
27: custom_widget::main
134+
at ./examples/custom-widget.rs:55:22
135+
```
136+
This tells us that some widget should have been `Configured` but had status `New` when calling `size_rules` — because we forgot to say that `buttons` is a `#[widget]` and thus needs to be configured. (This is in fact a debug-mode only check; release builds crash with a much-less-useful backtrace.)
137+
138+
### Layout
139+
140+
All widgets must implement the [`Layout`] trait, but only a few do so directly. Most, instead, use the `#[layout]` attribute-macro.
141+
142+
```rust,ignore
143+
#[layout(column![
144+
self.display.align(AlignHints::CENTER),
145+
self.buttons,
146+
])]
147+
struct Counter { /* .. */ }
148+
```
149+
150+
In this case, we are *not* using the [`column!`] macro (which would not be able to reference `self.display`) but rather an emulation of it. Behaviour should be identical aside from this ability to reference struct fields and not needing to `use kas::widgets::column`.
151+
152+
### Widget traits
153+
154+
[`Widget`] has super-trait [`Tile`] which has super-trait [`Layout`]. Futher, [`Events`] is usually implemented (unless there is no event-handling logic). Impls of any of these traits may appear in a widget implementation, but none are required.
155+
156+
It is however required to define the associated type [`Widget::Data`]. Since it is common to implement [`Events`] instead of [`Widget`] and `trait Events: Widget`, the `#[widget]` macro allows you to take the liberty of defining `type Data` on `Events` instead of `Widget`:
120157
```rust
121158
# extern crate kas;
122159
# use kas::prelude::*;
@@ -143,22 +180,26 @@ We can use this attribute to configure the child widget's input data too: in thi
143180
# }
144181
```
145182

146-
Notice here that after mutating `self.count` we call `cx.update(self.as_node(data))` in order to update `self` (and all children recursively). (In this case it would suffice to update only `display`, e.g. via `cx.update(self.display.as_node(&self.count))`, if you prefer to trade complexity for slightly more efficient code.)
183+
In this case we implement one event-handling method, [`Events::handle_messages`], to update `self.count` when an `Increment` message is received.
147184

148-
**Fifth**, we must specify widget layout somehow. There are two main ways of doing this: implement [`Layout`] or use the `layout` argument of [`#[widget]`][attr-widget]. To recap, we use:
149-
```ignore
150-
#[widget{
151-
layout = column![
152-
align!(center, self.display),
153-
self.buttons,
154-
];
155-
}]
156-
```
157-
This is macro-parsed layout syntax (not real macros). Don't use [`kas::column!`] here; it won't know what `self.display` is!
185+
#### Updating state
186+
187+
When updating local state in a custom widget, it is requried to explicitly trigger an update to any widgets using that state as their input data. This can be done in a few ways:
188+
189+
- <code>cx.[action][](self, [Action::UPDATE])</code> will notify that an update to `self` (and children) is required
190+
- <code>cx.[update][](self.[as_node][](data))</code> will update `self` (and children) immediately
191+
- <code>cx.[update][](self.display.[as_node][](&self.count))</code> will update `self.display` immediately
192+
193+
Note that previously:
194+
195+
- We used [`Adapt::on_message`] to update state: this automatically updates children
196+
- We used [`AppData::handle_messages`]: again, this automatically updates children
197+
198+
Custom widgets are not the same in this regard.
158199

159200
Don't worry about remembering each step; macro diagnostics should point you in the right direction. Detection of fields which are child widgets is however imperfect (nor can it be), so try to at least remember to apply `#[widget]` attributes.
160201

161-
### Aside: child widget type
202+
### Aside: the type of child widgets
162203

163204
Our `Counter` has two (explicit) child widgets, and we must specify the type of each:
164205
```rust
@@ -175,47 +216,47 @@ Our `Counter` has two (explicit) child widgets, and we must specify the type of
175216
#[widget(&self.count)]
176217
display: Text<i32, String>,
177218
#[widget]
178-
buttons: Row<Button<AccessLabel>>,
219+
buttons: Row<[Button<AccessLabel>; 2]>,
179220
# count: i32,
180221
# }
181222
# }
182223
```
183-
Here, this is no problem (though note that we used `Row::new([..])` not `kas::row![..]` specifically to have a known widget type). In other cases, widget types can get hard (or even impossible) to write.
184-
185-
It would therefore be nice if we could just write `impl Widget<Data = ()>` in these cases and be done. Alas, Rust does not support this. We are not completely without options however:
224+
There is no real issue in this case, but widget types can get significantly harder to write than `Row<[Button<AccessLabel>; 2]>`. Worse, some widget types are impossible to write (e.g. the result of [`row!`] or widget generics instantiated with a closure). So what can we do instead?
186225

187-
- We could define our `buttons` directly within `layout` instead of as a field. Alas, this doesn't work when passing a field as input data (as used by `display`), or when code must refer to the child by name.
188-
- We could box the widget with `Box<dyn Widget<Data = ()>>`. (This is what the `layout` syntax does for embedded widgets.)
189-
- The [`impl_anon!`] macro *does* support `impl Trait` syntax. The required code is unfortunately a bit hacky (hidden type generics) and might sometimes cause issues.
190-
- It looks likely that Rust will stabilise support for [`impl Trait` in type aliases](https://doc.rust-lang.org/nightly/unstable-book/language-features/type-alias-impl-trait.html) "soon". This requires writing a type-def outside of the widget definition but is supported in nightly:
226+
- It would be nice if `impl Widget<Data = ()>` worked; alas, it does not, and I have seen little interest in support for field-position-impl-trait. But I believe Rust *could* support this.
227+
- Rust may stabilise support for [`impl Trait` in type aliases](https://doc.rust-lang.org/nightly/unstable-book/language-features/type-alias-impl-trait.html) "soon". This requires writing a type-def outside of the widget definition but is supported in nightly Rust:
191228

192229
```rust,ignore
193230
type MyButtons = impl Widget<Data = ()>;
194231
```
232+
- We could use a `Box`: `Box<dyn Widget<Data = ()>>`.
233+
- We could embed our `buttons` in the `#[layout]` instead of using a field. This is not always possible (e.g. for `display` which takes `&self.count` as input data). Since `#[layout]` uses `Box` internally this is effectively the same as above.
234+
- The [`impl_anon!`] macro *does* support `impl Trait` syntax. The required code is unfortunately *a bit* hacky (hidden type generics) and at least a little prone to spitting out misleading error messages instead of *just working*. Best practice is to cross your fingers.
195235
196-
### Aside: uses
197-
198-
Before Kas 0.14, *all* widgets were custom widgets. (Yes, this made simple things hard.)
199-
200-
In the future, custom widgets *might* become obsolete, or might at least change significantly.
201-
202-
But for now, custom widgets still have their uses:
203-
204-
- Anything with a custom [`Layout`] implementation. E.g. if you want some custom graphics, you can either use [`kas::resvg::Canvas`] or a custom widget.
205-
- Child widgets as named fields allows direct read/write access on these widgets. For example, instead of passing a [`Text`] widget the count to display via input data, we *could* use a simple [`Label`] widget and re-write it every time `count` changes.
206-
- `Adapt` is the "standard" way of storing local state, but as seen here custom widgets may also do so, and you may have good reasons for this (e.g. to provide different data to different children without lots of mapping).
207-
- Since *input data* is a new feature, there are probably some cases it doesn't support yet. One notable example is anything requring a lifetime.
208-
236+
[`impl_self`]: https://docs.rs/impl-tools/latest/impl_tools/attr.impl_self.html
209237
[`impl_scope!`]: https://docs.rs/impl-tools/latest/impl_tools/macro.impl_scope.html
210238
[`impl_anon!`]: https://docs.rs/impl-tools/latest/impl_tools/macro.impl_anon.html
211239
[attr-widget]: https://docs.rs/kas/latest/kas/attr.widget.html
212240
[`Widget`]: https://docs.rs/kas/latest/kas/trait.Widget.html
241+
[`Widget::Data`]: https://docs.rs/kas/latest/kas/trait.Widget.html#associatedtype.Data
242+
[`Tile`]: https://docs.rs/kas/latest/kas/trait.Tile.html
213243
[`Events`]: https://docs.rs/kas/latest/kas/trait.Events.html
214-
[`kas::column!`]: https://docs.rs/kas/latest/kas/macro.column.html
244+
[`Events::handle_messages`]: https://docs.rs/kas/latest/kas/trait.Events.html#method.handle_messages
215245
[`Default`]: https://doc.rust-lang.org/stable/std/default/trait.Default.html
216246
[`Layout`]: https://docs.rs/kas/latest/kas/trait.Layout.html
217247
[impl-tools]: https://crates.io/crates/impl-tools
218248
[`CoreData`]: https://docs.rs/kas/latest/kas/struct.CoreData.html
219249
[`Label`]: https://docs.rs/kas/latest/kas/widgets/struct.Label.html
220250
[`Text`]: https://docs.rs/kas/latest/kas/widgets/struct.Text.html
221251
[`kas::resvg::Canvas`]: https://docs.rs/kas/latest/kas/resvg/struct.Canvas.html
252+
[`column!`]: https://docs.rs/kas/latest/kas/widgets/macro.column.html
253+
[`row!`]: https://docs.rs/kas/latest/kas/widgets/macro.row.html
254+
[`Button`]: https://docs.rs/kas/latest/kas/widgets/struct.Button.html
255+
[`Row`]: https://docs.rs/kas/latest/kas/widgets/struct.Row.html
256+
[`AccessLabel`]: https://docs.rs/kas/latest/kas/widgets/struct.AccessLabel.html
257+
[action]: https://docs.rs/kas/latest/kas/event/struct.EventState.html#method.action
258+
[update]: https://docs.rs/kas/latest/kas/event/struct.EventCx.html#method.update
259+
[Action::UPDATE]: https://docs.rs/kas/latest/kas/struct.Action.html#associatedconstant.UPDATE
260+
[as_node]: https://docs.rs/kas/latest/kas/trait.Widget.html#method.as_node
261+
[`Adapt::on_message`]: https://docs.rs/kas/latest/kas/widgets/struct.Adapt.html#method.on_message
262+
[`AppData::handle_messages`]: https://docs.rs/kas/latest/kas/app/trait.AppData.html#tymethod.handle_messages

0 commit comments

Comments
 (0)