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
[`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.
73
79
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?
76
81
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.
78
83
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]`
80
85
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 { /* .. */ }
89
90
```
90
91
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
93
100
core: widget_core!(),
94
101
```
95
102
96
-
**Third**, any fields which are child widgets must be annotated with `#[widget]`. (This enables them to be configured and updated.)
103
+
#### Child widgets
97
104
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).
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
110
107
#[widget(&self.count)]
111
108
display: Text<i32, String>,
112
109
#[widget]
113
-
buttons:Row<Button<AccessLabel>>,
114
-
count:i32,
115
-
}
116
-
# }
110
+
buttons: Row<[Button<AccessLabel>; 2]>,
117
111
```
118
112
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`:
120
157
```rust
121
158
# externcrate kas;
122
159
# usekas::prelude::*;
@@ -143,22 +180,26 @@ We can use this attribute to configure the child widget's input data too: in thi
143
180
# }
144
181
```
145
182
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.
147
184
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.
158
199
159
200
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.
160
201
161
-
### Aside: child widget type
202
+
### Aside: the type of child widgets
162
203
163
204
Our `Counter` has two (explicit) child widgets, and we must specify the type of each:
164
205
```rust
@@ -175,47 +216,47 @@ Our `Counter` has two (explicit) child widgets, and we must specify the type of
175
216
#[widget(&self.count)]
176
217
display:Text<i32, String>,
177
218
#[widget]
178
-
buttons:Row<Button<AccessLabel>>,
219
+
buttons:Row<[Button<AccessLabel>; 2]>,
179
220
# count:i32,
180
221
# }
181
222
# }
182
223
```
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?
186
225
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:
191
228
192
229
```rust,ignore
193
230
type MyButtons = impl Widget<Data = ()>;
194
231
```
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.
195
235
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.
0 commit comments