Your First WaterUI App
Now that your development environment is set up, let’s build your first interactive WaterUI application! We’ll create a counter app that demonstrates the core concepts of views, state management, and user interaction.
What We’ll Build
Our counter app will feature:
- A display showing the current count
- Buttons to increment and decrement the counter
- Dynamic styling based on the counter value
By the end of this chapter, you’ll understand:
- How to create interactive views
- How to manage reactive state
- How to handle user events
- How to compose views together
Setting Up the Project
If you completed the setup chapter you already have a CLI-generated workspace. Otherwise scaffold one now using playground mode:
water create "Counter App" --playground
cd counter-app
We will edit src/lib.rs to build our application. The playground mode handles all the native configuration for us.
Building the Counter Step by Step
Let’s build our counter app incrementally, learning WaterUI concepts along the way.
Step 1: Basic Structure
Start with a simple view structure. Since our initial view doesn’t need state, we can use a function:
Filename: src/lib.rs
#![allow(unused)]
fn main() {
use waterui::prelude::*;
pub fn counter() -> impl View {
text("Counter App")
}
}
Run this to make sure everything works:
water run
You should see a window with “Counter App” displayed.
Step 2: Adding Layout
Now let’s add some layout structure using stacks:
#![allow(unused)]
fn main() {
use waterui::prelude::*;
pub fn counter() -> impl View {
vstack((
text("Counter App"),
text("Count: 0"),
))
}
}
Note:
vstackcreates a vertical stack of views. We’ll learn abouthstack(horizontal) andzstack(overlay) later.
Step 3: Adding Reactive State
Now comes the exciting part - let’s add reactive state! We’ll use the binding helper from waterui::prelude and the text! macro for reactive text:
#![allow(unused)]
fn main() {
use waterui::prelude::*;
pub fn counter() -> impl View {
let count: Binding<i32> = binding(0);
vstack((
text("Counter App"),
text!("Count: {count}"),
hstack((
button("- Decrement").action_with(&count, |state: Binding<i32>| {
state.set(state.get() - 1);
}),
button("+ Increment").action_with(&count, |state: Binding<i32>| {
state.set(state.get() + 1);
}),
)),
))
}
}
water run will hot reload changes—save the file and keep the terminal open to see updates instantly.
Understanding the Code
Let’s break down the key concepts introduced:
Reactive State with binding
#![allow(unused)]
fn main() {
use waterui::prelude::*;
pub fn make_counter() -> Binding<i32> {
binding(0)
}
}
This creates a reactive binding with an initial value of 0. When this value changes, any UI elements that depend on it will automatically update.
Reactive Text Display
#![allow(unused)]
fn main() {
use waterui::prelude::*;
pub fn reactive_label() -> impl View {
let count: Binding<i32> = binding(0);
text!("Count: {count}")
}
}
- The
text!macro automatically handles reactivity - The text will update whenever
countchanges
Event Handling
#![allow(unused)]
fn main() {
use waterui::prelude::*;
pub fn decrement_button() -> impl View {
let count: Binding<i32> = binding(0);
button("- Decrement").action_with(&count, |count: Binding<i32>| {
count.set(count.get() - 1);
})
}
}
.action_with()attaches an event handler with captured state.Binding<T>implementsCloneefficiently (it’s a reference-counted handle), so you can pass it around.
Layout with Stacks
#![allow(unused)]
fn main() {
use waterui::prelude::*;
pub fn stack_examples() -> impl View {
vstack((
text("First"),
hstack((text("Left"), text("Right"))),
))
}
}
Stacks are the primary layout tools in WaterUI, allowing you to arrange views vertically or horizontally.