waterui_navigation/
lib.rs1#![no_std]
2
3extern crate alloc;
8
9pub mod search;
11pub mod tab;
12
13use alloc::{rc::Rc, vec::Vec};
14use core::{
15 cell::{Cell, RefCell},
16 fmt::Debug,
17};
18
19use nami::{
20 Computed,
21 collection::{Collection, List},
22};
23use waterui_color::Color;
24use waterui_controls::button;
25use waterui_core::{
26 AnyView, Environment, Metadata, Retain, View, env::use_env, handler::ViewBuilder,
27 impl_extractor, layout::StretchAxis, raw_view,
28};
29use waterui_text::Text;
30
31#[derive(Debug)]
36#[must_use]
37pub struct NavigationView {
38 pub bar: Bar,
40 pub content: AnyView,
42}
43
44pub trait CustomNavigationController: 'static {
47 fn push(&mut self, content: NavigationView);
51 fn pop(&mut self);
53}
54
55#[derive(Clone)]
58pub struct NavigationController(Rc<RefCell<dyn CustomNavigationController>>);
59
60impl_extractor!(NavigationController);
61
62impl Debug for NavigationController {
63 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
64 f.debug_struct("NavigationController").finish()
65 }
66}
67
68impl NavigationController {
69 pub fn new(receiver: impl CustomNavigationController) -> Self {
75 Self(Rc::new(RefCell::new(receiver)))
76 }
77
78 pub fn push(&self, content: NavigationView) {
84 self.0.borrow_mut().push(content);
85 }
86 pub fn pop(&self) {
88 self.0.borrow_mut().pop();
89 }
90}
91
92raw_view!(NavigationView, StretchAxis::Both);
93
94#[derive(Debug, Default)]
99pub struct Bar {
100 pub title: Text,
102 pub color: Computed<Color>,
104 pub hidden: Computed<bool>,
106}
107
108#[must_use]
113#[derive(Debug)]
114pub struct NavigationLink<Label, Content> {
115 pub label: Label,
117 pub content: Content,
119}
120impl<Label, Content> NavigationLink<Label, Content>
121where
122 Label: View,
123 Content: ViewBuilder<Output = NavigationView>,
124{
125 pub const fn new(label: Label, content: Content) -> Self {
132 Self { label, content }
133 }
134}
135
136#[must_use]
138#[derive(Debug)]
139pub struct NavigationStack<T, F> {
140 root: AnyView, path: T,
142 destination: F,
143}
144
145impl NavigationStack<(), ()> {
146 pub fn new(root: impl View) -> Self {
151 Self {
152 root: AnyView::new(root),
153 path: (),
154 destination: (),
155 }
156 }
157
158 pub fn into_inner(self) -> AnyView {
160 self.root
161 }
162}
163
164impl<T> NavigationStack<NavigationPath<T>, ()> {
165 pub fn with(path: NavigationPath<T>, root: impl View) -> Self {
171 Self {
172 root: AnyView::new(root),
173 path,
174 destination: (),
175 }
176 }
177
178 pub fn destination<F>(self, destination: F) -> NavigationStack<NavigationPath<T>, F>
183 where
184 F: 'static + Fn(T) -> NavigationView,
185 {
186 NavigationStack {
187 root: self.root,
188 path: self.path,
189 destination,
190 }
191 }
192}
193
194raw_view!(NavigationStack<(),()>, StretchAxis::Both);
195
196impl<T, F> View for NavigationStack<NavigationPath<T>, F>
197where
198 T: 'static + Clone + View,
199 F: 'static + Fn(T) -> NavigationView,
200{
201 fn body(self, _env: &Environment) -> impl View {
202 let path: NavigationPath<T> = self.path;
203 let destination = self.destination;
204 let root = self.root;
205 NavigationStack::new(use_env(move |receiver: NavigationController| {
206 let path = path.inner;
207 for component in &path {
208 receiver.push(destination(component));
209 }
210
211 let old_len = Cell::new(path.len());
212 #[allow(clippy::cast_possible_wrap)]
213 let guard = path.watch(.., move |slice| {
214 let slice = slice.into_value();
216 let len = slice.len();
217 let change = len as isize - old_len.get() as isize;
218 if change > 0 {
219 for item in slice.iter().skip(old_len.get()).take(len - old_len.get()) {
221 receiver.push(destination(item.clone()));
222 }
223 }
224 #[allow(clippy::cast_sign_loss)]
225 if change < 0 {
226 let pop_count = (-change) as usize;
228 for _ in 0..pop_count {
229 receiver.pop();
230 }
231 }
232 old_len.set(len);
233 });
234
235 Metadata::new(root, Retain::new(guard))
236 }))
237 }
238}
239
240#[must_use]
242#[derive(Debug)]
243pub struct NavigationPath<T> {
244 inner: List<T>,
245}
246
247impl<T: 'static> From<Vec<T>> for NavigationPath<T> {
248 fn from(value: Vec<T>) -> Self {
249 Self {
250 inner: value.into(),
251 }
252 }
253}
254
255impl<T: 'static> FromIterator<T> for NavigationPath<T> {
256 fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
257 Self {
258 inner: List::from_iter(iter),
259 }
260 }
261}
262
263impl<T: 'static + Clone> Default for NavigationPath<T> {
264 fn default() -> Self {
265 Self::new()
266 }
267}
268
269impl<T: 'static + Clone> NavigationPath<T> {
270 pub fn new() -> Self {
272 Self { inner: List::new() }
273 }
274
275 pub fn push(&mut self, value: T) {
277 self.inner.push(value);
278 }
279
280 pub fn pop(&self) {
282 let _ = self.inner.pop();
283 }
284
285 pub fn pop_n(&self, n: usize) {
287 for _ in 0..n {
288 self.pop();
289 }
290 }
291
292 pub fn iter(&self) -> impl Iterator<Item = T> {
294 self.inner.iter()
295 }
296}
297
298impl<Label, Content> View for NavigationLink<Label, Content>
299where
300 Label: View,
301 Content: ViewBuilder<Output = NavigationView>,
302{
303 fn body(self, env: &waterui_core::Environment) -> impl View {
304 debug_assert!(
305 env.get::<NavigationController>().is_some(),
306 "NavigationLink used outside of a navigation context"
307 );
308
309 button(self.label).action(move |receiver: NavigationController| {
310 let content = (self.content).build();
311 receiver.push(content);
312 })
313 }
314}
315
316impl NavigationView {
317 pub fn new(title: impl Into<Text>, content: impl View) -> Self {
324 let bar = Bar {
325 title: title.into(),
326 ..Default::default()
327 };
328
329 Self {
330 bar,
331 content: AnyView::new(content),
332 }
333 }
334}
335
336pub fn navigation(title: impl Into<Text>, view: impl View) -> NavigationView {
343 NavigationView::new(title, view)
344}