Making the Counter Reusable
Okay so we've got a reactive counter. It's reactive because when the value changes, via a button press, both labels update to show the new value without us having to explicitly send events to them.
However, with the way we've done it, our counter widget can't be used in another application unless it too has the same CounterState
application data. We need to make our counter more generic.
When using our Counter
widget in an app we won't have access to the label as it's built into the widget. So instead of binding to the label, we need to be able to bind to the counter widget itself.
The Data Associated Type
This is where the Data
associated type comes in from the Widget
trait, which we previously set to ()
. This associated type is used to specify the type of data a widget is expected to receive. It was mentioned before that a Label
expects a String
, which is because its associated data type is type Data = String
.
Let's modify our counter to remove the bind
method on the Label
within the Counter
widget, and change the data associated type to type Data = i32
.
On Update
For actually updating the counter we'll use the on_update
method provided by the Widget
trait. Add the following code to the implementation of Widget
for our Counter
below the on_update
method:
fn on_update(&mut self, state: &mut State, entity: Entity, data: &Self::Data) {
self.label.set_text(state, &data.to_string());
}
As with the on_build
and on_event
methods, the first three arguments are the same: a mutable reference to Self
so internal widget data can be modified, and a mutable reference to State
followed by an Entity
id, for modifying style and layout properties as well as for sending events.
In this case we use the internal label
field (which is the entity id of label) to set its text to the data
value (converted from i32
to a string) received when our counter widget is updated.
The last step is to add a call to bind()
on the counter widget. Change the building of the counter widget to the following:
Counter::default()
.bind(CounterState::value, |value: &i32| *value)
.build(state, app_data, |builder| builder);
We pass the same lens as before to the value of the CounterState
and the conversion closure converts the reference to the value into a copy.
So how does this work?
- Building the
Model
created aStore
, which itself is a widget which contains the application data as well as a list of observer widgets (using entity ids). - The
bind
method sends an event to the store which registers the widget as an observer. - Pressing a button sends an event up the tree to the store, which then mutates the data in response.
- The store then calls the
on_update
method of all bound widgets, passing the new updated value.
And we're done!
When we build and run our app now we get the same behavior as before but our counter widget is now re-usable! We could now add this counter widget into any application, and we just need to bind it to a piece of data, using the conversion closure to convert that data in some way to an i32
. The label would then display the value. The only other part required is to respond to CounterEvent
messages emitted by the counter's buttons.
The full code for this reactive counter can be found in the /examples/counter_reactive.rs
file in the tuix repository and can be run with cargo run --release --example counter_reactive
.
Conclusion
This concludes the tuix 'quick' guide! If you made it this far, thanks for reading! The rest of this book attempts to cover the concepts shown in this guide in more detail, demonstrating what features are available and how they can be used to build complex user interfaces.
Coming Soon...
Coming soon will be an 'advanced' guide in which a todo app will be created with more advanced features such as:
- Binding to lists of items
- 'Local' app data for syncing widgets in a sub-section of the tree
- Animations (almost all style and layout properties are animatable)
- Using more complex built-in widgets
- Constructing more complex custom widgets