| Crates.io | waterui-navigation |
| lib.rs | waterui-navigation |
| version | 0.2.1 |
| created_at | 2025-12-14 08:05:27.191087+00 |
| updated_at | 2025-12-14 11:30:47.051554+00 |
| description | Navigation components for WaterUI |
| homepage | |
| repository | https://github.com/water-rs/waterui |
| max_upload_size | |
| id | 1983997 |
| size | 50,400 |
Navigation containers and stack-based routing for WaterUI applications.
waterui-navigation provides the building blocks for hierarchical navigation patterns commonly found in mobile and desktop applications. It includes stack-based navigation with push/pop semantics, navigation bars with customizable titles and styling, programmatic navigation links, and a tab interface for switching between multiple root views.
The crate operates through a controller-based architecture where native backends implement the CustomNavigationController trait to handle platform-specific navigation rendering (iOS UINavigationController, Android Navigation Component, etc.), while the Rust side manages the navigation state and view hierarchy declaratively.
Add to your Cargo.toml:
[dependencies]
waterui-navigation = "0.1.0"
Or use the main waterui crate which re-exports these components:
[dependencies]
waterui = "0.2"
use waterui::prelude::*;
use waterui_navigation::{NavigationView, NavigationStack};
pub fn main() -> impl View {
NavigationStack::new(
NavigationView::new("Home",
vstack((
text("Welcome to the app"),
// Navigation content here
))
)
)
}
A NavigationView combines a navigation bar (with title, color, visibility) and content. This is the fundamental unit of a navigation hierarchy.
use waterui_navigation::NavigationView;
use waterui_text::Text;
let view = NavigationView::new("Settings",
vstack((
text("App Settings"),
toggle("Enable notifications"),
))
);
The navigation bar can be customized:
let mut view = NavigationView::new("Profile", profile_content);
view.bar.color = Computed::new(Color::blue());
view.bar.hidden = Computed::new(true);
A container that manages a stack of navigation views with push/pop semantics. The simplest form wraps a single root view:
use waterui_navigation::NavigationStack;
NavigationStack::new(
NavigationView::new("Root", root_content)
)
For programmatic navigation, use a NavigationPath to track the stack state:
use waterui_navigation::{NavigationStack, NavigationPath};
use nami::binding;
#[derive(Clone)]
enum Route {
Detail(i32),
Settings,
}
impl View for Route {
fn body(self, _env: &Environment) -> impl View {
match self {
Route::Detail(id) => text(format!("Detail {}", id)),
Route::Settings => text("Settings"),
}
}
}
let path = binding(NavigationPath::new());
NavigationStack::with(path.clone(),
NavigationView::new("Home", home_view)
)
.destination(|route: Route| {
match route {
Route::Detail(id) => NavigationView::new(
format!("Item {}", id),
detail_view(id)
),
Route::Settings => NavigationView::new(
"Settings",
settings_view()
),
}
})
Navigate programmatically:
// Push a new view
path.borrow_mut().push(Route::Detail(42));
// Pop back
path.borrow().pop();
// Pop multiple levels
path.borrow().pop_n(2);
A declarative link that pushes a new view when tapped. Internally implemented as a button with a navigation action.
use waterui_navigation::NavigationLink;
NavigationLink::new(
text("Show Details"),
|| NavigationView::new("Details", detail_content())
)
The label can be any view:
NavigationLink::new(
hstack((
image("icon"),
text("Settings"),
spacer(),
text(">"),
)),
|| NavigationView::new("Settings", settings_view())
)
The NavigationController is injected into the environment by the native backend and provides the runtime connection between Rust navigation commands and platform navigation APIs. Views can extract it to perform navigation actions programmatically:
use waterui_navigation::NavigationController;
button("Go Forward").action(|controller: NavigationController| {
controller.push(NavigationView::new("Next", next_view()));
})
The tab system provides multiple independent navigation stacks with a bottom or top tab bar.
use waterui_navigation::tab::{Tab, Tabs, TabPosition};
use waterui_core::id::{TaggedView, Id};
use nami::binding;
let selection = binding(Id::from(0));
let tabs = Tabs {
selection,
position: TabPosition::Bottom,
tabs: vec![
Tab::new(
TaggedView::new(
Id::from(0),
hstack((icon("house"), text("Home"))).anyview()
),
|| NavigationView::new("Home", home_view())
),
Tab::new(
TaggedView::new(
Id::from(1),
hstack((icon("gear"), text("Settings"))).anyview()
),
|| NavigationView::new("Settings", settings_view())
),
],
};
Tab switching is controlled by mutating the selection binding:
selection.set(Id::from(1)); // Switch to second tab
use waterui::prelude::*;
use waterui_navigation::{NavigationView, NavigationLink};
struct Item {
id: i32,
name: String,
}
fn item_list(items: Vec<Item>) -> impl View {
NavigationView::new("Items",
vstack(
items.into_iter().map(|item| {
NavigationLink::new(
text(item.name.clone()),
move || item_detail(item.id)
)
}).collect::<Vec<_>>()
)
)
}
fn item_detail(id: i32) -> NavigationView {
NavigationView::new(
format!("Item {}", id),
vstack((
text(format!("Details for item {}", id)),
button("Delete").action(|controller: NavigationController| {
// Delete item, then pop back
controller.pop();
}),
))
)
}
use waterui::prelude::*;
use waterui_navigation::{NavigationStack, NavigationPath};
#[derive(Clone)]
enum AppRoute {
Login,
Home,
Profile(String),
}
impl View for AppRoute {
fn body(self, _env: &Environment) -> impl View {
match self {
AppRoute::Login => text("Login Screen"),
AppRoute::Home => text("Home Screen"),
AppRoute::Profile(name) => text(format!("Profile: {}", name)),
}
}
}
fn app() -> impl View {
let path = binding(NavigationPath::new());
NavigationStack::with(path.clone(),
NavigationView::new("Login",
button("Log In").action({
let path = path.clone();
move || {
path.borrow_mut().push(AppRoute::Home);
}
})
)
)
.destination(|route: AppRoute| {
match route {
AppRoute::Home => NavigationView::new("Home",
button("View Profile").action({
let path = path.clone();
move || {
path.borrow_mut().push(AppRoute::Profile("User".into()));
}
})
),
AppRoute::Profile(name) => NavigationView::new(
"Profile",
vstack((
text(format!("Welcome, {}", name)),
button("Back to Home").action(move |_: ()| {
path.borrow().pop();
}),
))
),
_ => NavigationView::new("", text("")),
}
})
}
use waterui::prelude::*;
use waterui_navigation::tab::{Tab, Tabs, TabPosition};
fn main_tabs() -> impl View {
let selection = binding(Id::from(0));
Tabs {
selection: selection.clone(),
position: TabPosition::Bottom,
tabs: vec![
Tab::new(
TaggedView::new(Id::from(0), text("Feed").anyview()),
|| NavigationView::new("Feed", feed_view())
),
Tab::new(
TaggedView::new(Id::from(1), text("Search").anyview()),
|| NavigationView::new("Search", search_view())
),
Tab::new(
TaggedView::new(Id::from(2), text("Profile").anyview()),
|| NavigationView::new("Profile", profile_view())
),
],
}
}
NavigationView - A view with a navigation bar and contentNavigationStack<T, F> - A stack-based navigation containerNavigationPath<T> - Reactive stack state for programmatic navigationNavigationLink<Label, Content> - Declarative navigation linkNavigationController - Runtime controller for push/pop actionsBar - Navigation bar configuration (title, color, visibility)Tabs - Tab container with selection bindingTab<T> - Individual tab with label and contentTabPosition - Tab bar position (Top/Bottom)navigation(title, view) - Convenience function to create a NavigationViewViewExt::title(title) from the main waterui crate creates a NavigationView:
use waterui::prelude::*;
content_view.title("Screen Title")
This crate has no optional features. All navigation components are included by default.
The Rust side provides the declarative API and state management while native backends handle the actual rendering and platform-specific navigation gestures (swipe to go back, etc.).