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
- A reset button
- 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:
water create --name "Counter App" \
--directory counter-app \
--bundle-identifier com.example.counterapp \
--backend web \
--yes --dev
cd counter-app
We will edit src/lib.rs so the shared code can run on any backend the CLI installed.
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 { "Counter App" } }
Run this to make sure everything works:
water run --platform web --project counter-app
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(( "Counter App", "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 re-exported binding helper together with Binding's convenience methods and the text! macro for reactive text:
#![allow(unused)] fn main() { use waterui::prelude::*; use waterui::reactive::binding; use waterui::Binding; pub fn counter() -> impl View { let count: Binding<i32> = binding(0); vstack(( "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 --platform web --project counter-app 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::reactive::binding; use waterui::Binding; 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::*; use waterui::reactive::binding; use waterui::Binding; 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::*; use waterui::reactive::binding; use waterui::Binding; 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 stateBinding<i32>::decrementandBinding<i32>::incrementprovide ergonomic arithmetic updates without manual closures
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.