zng_wgt_wizard/view_fn.rs
1use crate::{BACK_CMD, BEGIN_CMD, CANCEL_CMD, FINISH_CMD, NEXT_CMD, PageArgs, Wizard};
2use zng_ext_font::FontWeight;
3use zng_ext_input::focus::TabIndex;
4use zng_wgt::{align, border, is_rtl, prelude::*};
5use zng_wgt_button::Button;
6use zng_wgt_container::{Container, padding};
7use zng_wgt_fill::{background, background_color};
8use zng_wgt_input::focus::tab_index;
9use zng_wgt_markdown::Markdown;
10use zng_wgt_scroll::{Scroll, ScrollMode};
11use zng_wgt_size_offset::width;
12use zng_wgt_stack::{Stack, StackDirection};
13use zng_wgt_style::style_fn;
14use zng_wgt_text::Text;
15
16context_var! {
17 /// Widget function that builds a wizard header container.
18 pub static HEADER_FN_VAR: WidgetFn<HeaderFnArgs> = WidgetFn::new(default_header_fn);
19
20 /// Widget function that builds a background visual for the header container.
21 pub static HEADER_BACKGROUND_FN_VAR: WidgetFn<()> = WidgetFn::nil();
22
23 /// Widget function that builds a wizard side container.
24 pub static SIDE_FN_VAR: WidgetFn<SideFnArgs> = WidgetFn::new(default_side_fn);
25
26 /// Widget function that builds a background visual for the side container.
27 pub static SIDE_BACKGROUND_FN_VAR: WidgetFn<()> = WidgetFn::nil();
28
29 /// Widget function that builds extra content for the side panel for pages
30 /// that define side content.
31 pub static SIDE_EXTRA_FN_VAR: WidgetFn<PageArgs> = WidgetFn::nil();
32
33 /// Widget function that builds a wizard main content container.
34 pub static CONTENT_FN_VAR: WidgetFn<ContentFnArgs> = WidgetFn::new(default_content_fn);
35
36 /// Widget function that builds a wizard footer container.
37 pub static FOOTER_FN_VAR: WidgetFn<FooterFnArgs> = WidgetFn::new(default_footer_fn);
38
39 /// Widget function that builds extra content for the footer panel.
40 pub static FOOTER_EXTRA_FN_VAR: WidgetFn<PageArgs> = WidgetFn::nil();
41
42 /// Widget function that brings together all the wizard parts.
43 pub static PANEL_FN_VAR: WidgetFn<PanelFnArgs> = WidgetFn::new(default_panel_fn);
44}
45
46/// Arguments for a wizard header container builder.
47///
48/// See [`HEADER_FN_VAR`] for more details.
49#[non_exhaustive]
50pub struct HeaderFnArgs {
51 /// Page header content.
52 ///
53 /// This is the [`Page::header`] instance.
54 ///
55 /// [`Page::header`]: crate::Page::header
56 pub header: UiNode,
57
58 /// Background visual.
59 ///
60 /// This is the [`HEADER_BACKGROUND_FN_VAR`] instance.
61 pub background: UiNode,
62
63 /// Page index on the pages list.
64 ///
65 /// Is `usize::MAX` if the page is a custom assign to [`WIZARD::selected_page`] that is not on the list.
66 ///
67 /// [`WIZARD::selected_page`]: crate::WIZARD::selected_page
68 pub index: usize,
69 /// Count of pages on the list.
70 pub pages_len: usize,
71 /// Read-only title of each page.
72 pub titles: Vec<Var<Txt>>,
73 /// Read-only skip status of each page.
74 pub skips: Vec<Var<bool>>,
75}
76impl HeaderFnArgs {
77 /// Is first page on the list.
78 pub fn is_first(&self) -> bool {
79 self.index == 0
80 }
81
82 /// Is last page on the list.
83 pub fn is_last(&self) -> bool {
84 self.index == self.pages_len.saturating_sub(1)
85 }
86
87 /// Is custom page, not on the list.
88 pub fn is_custom(&self) -> bool {
89 self.index == usize::MAX
90 }
91
92 /// Get `WIDGET.id()`.
93 pub fn wizard_id(&self) -> WidgetId {
94 WIDGET.id()
95 }
96}
97
98/// Arguments for a wizard side container builder.
99///
100/// See [`SIDE_FN_VAR`] for more details.
101#[non_exhaustive]
102pub struct SideFnArgs {
103 /// Page side content.
104 ///
105 /// This is the [`Page::side`] instance.
106 ///
107 /// This is never [`UiNode::nil`] as that is handled by [`Wizard!`].
108 ///
109 /// This can be [`UiNode::is_list`], in this case a layout panel must be generated for
110 /// the items, usually a vertical stack aligned to [`Align::BOTTOM`].
111 ///
112 /// [`Page::side`]: crate::Page::side
113 /// [`Wizard!`]: struct@Wizard
114 pub side: UiNode,
115
116 /// Extra side content that is the same for all pages.
117 ///
118 /// This is the [`side_extra_fn`] instance.
119 ///
120 /// This can be [`UiNode::nil`] if no extra content is defined, otherwise it is
121 /// like `side` a list or a single node, usually presented under the `side` list,
122 /// with a separator.
123 ///
124 /// [`side_extra_fn`]: fn@side_extra_fn
125 pub side_extra: UiNode,
126
127 /// Background visual.
128 ///
129 /// This is the [`SIDE_BACKGROUND_FN_VAR`] instance.
130 pub background: UiNode,
131
132 /// Page index on the pages list.
133 ///
134 /// Is `usize::MAX` if the page is a custom assign to [`WIZARD::selected_page`] that is not on the list.
135 ///
136 /// [`WIZARD::selected_page`]: crate::WIZARD::selected_page
137 pub index: usize,
138 /// Count of pages on the list.
139 pub pages_len: usize,
140}
141impl SideFnArgs {
142 /// Is first page on the list.
143 pub fn is_first(&self) -> bool {
144 self.index == 0
145 }
146
147 /// Is last page on the list.
148 pub fn is_last(&self) -> bool {
149 self.index == self.pages_len.saturating_sub(1)
150 }
151
152 /// Is custom page, not on the list.
153 pub fn is_custom(&self) -> bool {
154 self.index == usize::MAX
155 }
156
157 /// Get `WIDGET.id()`.
158 pub fn wizard_id(&self) -> WidgetId {
159 WIDGET.id()
160 }
161}
162
163/// Arguments for a wizard main content container builder.
164///
165/// See [`CONTENT_FN_VAR`] for more details.
166#[non_exhaustive]
167pub struct ContentFnArgs {
168 /// Page content instance.
169 ///
170 /// This is the [`Page::content`] instance.
171 ///
172 /// [`Page::content`]: crate::Page::content
173 pub content: UiNode,
174
175 /// Page content instance prefers to fill the full content area.
176 ///
177 /// This is the [`Page::content_fill`] value.
178 ///
179 /// [`Page::content_fill`]: crate::Page::content_fill
180 pub content_fill: bool,
181
182 /// Page index on the pages list.
183 ///
184 /// Is `usize::MAX` if the page is a custom assign to [`WIZARD::selected_page`] that is not on the list.
185 ///
186 /// [`WIZARD::selected_page`]: crate::WIZARD::selected_page
187 pub index: usize,
188 /// Count of pages on the list.
189 pub pages_len: usize,
190}
191impl ContentFnArgs {
192 /// Is first page on the list.
193 pub fn is_first(&self) -> bool {
194 self.index == 0
195 }
196
197 /// Is last page on the list.
198 pub fn is_last(&self) -> bool {
199 self.index == self.pages_len.saturating_sub(1)
200 }
201
202 /// Is custom page, not on the list.
203 pub fn is_custom(&self) -> bool {
204 self.index == usize::MAX
205 }
206
207 /// Get `WIDGET.id()`.
208 pub fn wizard_id(&self) -> WidgetId {
209 WIDGET.id()
210 }
211}
212
213/// Arguments for a wizard footer container builder.
214///
215/// See [`FOOTER_FN_VAR`] for more details.
216#[non_exhaustive]
217pub struct FooterFnArgs {
218 /// Page footer instance.
219 ///
220 /// This can be [`UiNode::is_list`], in this case a layout panel must be generated for
221 /// the items, usually an horizontal stack aligned to the [`Align::END`] side.
222 pub footer: UiNode,
223
224 /// Extra footer content that is the same for all pages.
225 ///
226 /// This is the [`footer_extra_fn`] instance.
227 ///
228 /// This can be [`UiNode::nil`] if no extra content is defined, otherwise it is
229 /// like `footer` a list or a single node, usually presented as an horizontal stack
230 /// aligned to the [`Align::START`] side.
231 ///
232 /// [`footer_extra_fn`]: fn@footer_extra_fn
233 pub footer_extra: UiNode,
234
235 /// Page index on the pages list.
236 ///
237 /// Is `usize::MAX` if the page is a custom assign to [`WIZARD::selected_page`] that is not on the list.
238 ///
239 /// [`WIZARD::selected_page`]: crate::WIZARD::selected_page
240 pub index: usize,
241 /// Count of pages on the list.
242 pub pages_len: usize,
243}
244impl FooterFnArgs {
245 /// Is first page on the list.
246 pub fn is_first(&self) -> bool {
247 self.index == 0
248 }
249
250 /// Is last page on the list.
251 pub fn is_last(&self) -> bool {
252 self.index == self.pages_len.saturating_sub(1)
253 }
254
255 /// Is custom page, not on the list.
256 pub fn is_custom(&self) -> bool {
257 self.index == usize::MAX
258 }
259
260 /// Get `WIDGET.id()`.
261 pub fn wizard_id(&self) -> WidgetId {
262 WIDGET.id()
263 }
264}
265
266/// Arguments for the wizard root panel builder.
267///
268/// if any part [`is_nil`] its region on the panel must be fully collapsed.
269///
270/// See [`PANEL_FN_VAR`] for more details.
271///
272/// [`is_nil`]: UiNode::is_nil
273pub struct PanelFnArgs {
274 /// Header container.
275 ///
276 /// This is the [`header_fn`] instance.
277 ///
278 /// [`header_fn`]: fn@header_fn
279 pub header: UiNode,
280 /// Side container.
281 ///
282 /// This is the [`side_fn`] instance.
283 ///
284 /// [`side_fn`]: fn@side_fn
285 pub side: UiNode,
286 /// Main content container.
287 ///
288 /// This is the [`content_fn`] instance.
289 ///
290 /// [`content_fn`]: fn@content_fn
291 pub content: UiNode,
292 /// Footer container.
293 ///
294 /// This is the [`footer_fn`] instance.
295 ///
296 /// [`footer_fn`]: fn@footer_fn
297 pub footer: UiNode,
298}
299
300/// Default wizard header container.
301///
302/// See [`HEADER_FN_VAR`] for more details.
303pub fn default_header_fn(args: HeaderFnArgs) -> UiNode {
304 Container! {
305 child = args.header;
306 padding = (10, 10, 10, 20);
307 border = {
308 widths: (0, 0, 1, 0),
309 sides: colors::GRAY.with_alpha(40.pct()),
310 };
311 background = args.background;
312 }
313}
314
315/// Default wizard page header content.
316///
317/// See [`Page::header`] for more details.
318///
319/// [`Page::header`]: crate::Page::header
320pub fn default_page_header(args: PageArgs) -> UiNode {
321 Stack! {
322 children = ui_vec![default_page_header_title(args.title), default_page_header_info(args.info),];
323 direction = StackDirection::top_to_bottom();
324 spacing = 5;
325 align = Align::START;
326 }
327}
328/// Default [`Page::title`] presenter.
329///
330/// [`Page::title`]: crate::Page::title
331pub fn default_page_header_title(title: Var<Txt>) -> UiNode {
332 Text! {
333 txt = title;
334 font_weight = FontWeight::BOLD;
335 }
336}
337/// Default [`Page::info`] presenter.
338///
339/// [`Page::info`]: crate::Page::info
340pub fn default_page_header_info(info: Var<Txt>) -> UiNode {
341 Markdown! {
342 txt = info;
343 }
344}
345
346/// Default wizard side container.
347///
348/// See [`SIDE_FN_VAR`] for more details.
349pub fn default_side_fn(args: SideFnArgs) -> UiNode {
350 Stack! {
351 direction = StackDirection::top_to_bottom();
352 children = args.side;
353 children_align = Align::BOTTOM_START;
354 spacing = 5;
355 padding = 5;
356 width = 200;
357 background = args.background;
358 border = {
359 widths: (0, 1, 0, 0),
360 sides: colors::GRAY.with_alpha(40.pct()),
361 };
362 when #is_rtl {
363 border = {
364 widths: (0, 0, 0, 1),
365 sides: colors::GRAY.with_alpha(40.pct()),
366 };
367 }
368
369 zng_wgt_button::style_fn = style_fn!(|_| zng_wgt_button::LinkStyle!());
370 }
371}
372
373/// Default wizard page side content.
374///
375/// See [`Page::side`] for more details.
376///
377/// [`Page::side`]: crate::Page::side
378pub fn default_page_side(_: PageArgs) -> UiNode {
379 ui_vec![].into_node()
380}
381
382/// Default wizard main content container.
383///
384/// See [`SIDE_FN_VAR`] for more details.
385pub fn default_content_fn(args: ContentFnArgs) -> UiNode {
386 if args.content_fill {
387 Container! {
388 child = args.content;
389 background_color = light_dark(rgb(0.85, 0.85, 0.85), rgb(0.15, 0.15, 0.15));
390 }
391 } else {
392 Scroll! {
393 mode = ScrollMode::VERTICAL;
394 child = args.content;
395 child_align = Align::FILL;
396 padding = 20;
397 background_color = light_dark(rgb(0.85, 0.85, 0.85), rgb(0.15, 0.15, 0.15));
398 }
399 }
400}
401
402/// Default wizard footer container.
403///
404/// See [`FOOTER_FN_VAR`] for more details.
405pub fn default_footer_fn(args: FooterFnArgs) -> UiNode {
406 Stack! {
407 children = args.footer.into_list();
408 direction = StackDirection::start_to_end();
409 spacing = 5;
410 padding = 5;
411 children_align = Align::END;
412 border = {
413 widths: (1, 0, 0, 0),
414 sides: colors::GRAY.with_alpha(40.pct()),
415 };
416 }
417}
418
419/// Default wizard page footer content.
420///
421/// See [`Page::footer`] for more details.
422///
423/// [`Page::footer`]: crate::Page::footer
424pub fn default_page_footer(args: PageArgs) -> UiNode {
425 let id = args.wizard_id();
426 if args.is_first() {
427 if args.is_last() {
428 ui_vec![default_page_footer_begin(id), default_page_footer_cancel(id)]
429 } else {
430 ui_vec![default_page_footer_next(id), default_page_footer_cancel(id)]
431 }
432 } else if args.is_last() {
433 ui_vec![
434 default_page_footer_back(id),
435 default_page_footer_begin(id),
436 default_page_footer_cancel(id)
437 ]
438 } else if args.is_custom() {
439 ui_vec![default_page_footer_cancel(id)]
440 } else {
441 ui_vec![
442 default_page_footer_back(id),
443 default_page_footer_next(id),
444 default_page_footer_cancel(id)
445 ]
446 }
447 .into_node()
448}
449/// Default [`BACK_CMD`] button.
450pub fn default_page_footer_back(wizard_id: WidgetId) -> UiNode {
451 Button! {
452 cmd = BACK_CMD.scoped(wizard_id);
453 tab_index = TabIndex::FIRST - 1;
454 }
455}
456/// Default [`NEXT_CMD`] button.
457pub fn default_page_footer_next(wizard_id: WidgetId) -> UiNode {
458 Button! {
459 cmd = NEXT_CMD.scoped(wizard_id);
460 tab_index = TabIndex::FIRST;
461 }
462}
463/// Default [`BEGIN_CMD`] button.
464pub fn default_page_footer_begin(wizard_id: WidgetId) -> UiNode {
465 Button! {
466 cmd = BEGIN_CMD.scoped(wizard_id);
467 tab_index = TabIndex::FIRST;
468 style_fn = style_fn!(|_| zng_wgt_button::PrimaryStyle!());
469 }
470}
471/// Default [`FINISH_CMD`] button.
472pub fn default_page_footer_finish(wizard_id: WidgetId) -> UiNode {
473 Button! {
474 cmd = FINISH_CMD.scoped(wizard_id);
475 tab_index = TabIndex::FIRST;
476 style_fn = style_fn!(|_| zng_wgt_button::PrimaryStyle!());
477 }
478}
479/// Default [`CANCEL_CMD`] button.
480pub fn default_page_footer_cancel(wizard_id: WidgetId) -> UiNode {
481 Button! {
482 cmd = CANCEL_CMD.scoped(wizard_id);
483 tab_index = TabIndex::FIRST - 2;
484 }
485}
486
487/// Default wizard root panel.
488///
489/// See [`PANEL_FN_VAR`] for more details.
490pub fn default_panel_fn(args: PanelFnArgs) -> UiNode {
491 Container! {
492 child_bottom = args.footer;
493 child_start = args.side;
494 child_top = args.header;
495 child = args.content;
496 }
497}
498
499/// Widget function that converts a [`HeaderFnArgs`] into a page header container widget.
500///
501/// This property sets the [`HEADER_FN_VAR`].
502#[property(CONTEXT, default(HEADER_FN_VAR), widget_impl(Wizard))]
503pub fn header_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<HeaderFnArgs>>) -> UiNode {
504 with_context_var(child, HEADER_FN_VAR, wgt_fn)
505}
506
507/// Widget function that makes a background visual for the page header container widget.
508///
509/// This property sets the [`HEADER_BACKGROUND_FN_VAR`].
510#[property(CONTEXT, default(HEADER_BACKGROUND_FN_VAR), widget_impl(Wizard))]
511pub fn header_background_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<()>>) -> UiNode {
512 with_context_var(child, HEADER_BACKGROUND_FN_VAR, wgt_fn)
513}
514
515/// Widget function that converts a [`SideFnArgs`] into a page side container widget.
516///
517/// This property sets the [`SIDE_FN_VAR`].
518#[property(CONTEXT, default(SIDE_FN_VAR), widget_impl(Wizard))]
519pub fn side_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<SideFnArgs>>) -> UiNode {
520 with_context_var(child, SIDE_FN_VAR, wgt_fn)
521}
522
523/// Widget function that makes a background visual for the page side container widget.
524///
525/// This property sets the [`SIDE_BACKGROUND_FN_VAR`].
526#[property(CONTEXT, default(SIDE_BACKGROUND_FN_VAR), widget_impl(Wizard))]
527pub fn side_background_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<()>>) -> UiNode {
528 with_context_var(child, SIDE_BACKGROUND_FN_VAR, wgt_fn)
529}
530
531/// Widget function that converts [`PageArgs`] to extra side panel content.
532///
533/// Note that this extra content will only instantiate if the [`Page::side`] does not instantiate nil.
534///
535/// This property sets the [`SIDE_EXTRA_FN_VAR`].
536///
537/// [`Page::side`]: crate::Page::side
538#[property(CONTEXT, default(SIDE_EXTRA_FN_VAR), widget_impl(Wizard))]
539pub fn side_extra_fn(child: impl IntoUiNode, side_extra_fn: impl IntoVar<WidgetFn<PageArgs>>) -> UiNode {
540 with_context_var(child, SIDE_EXTRA_FN_VAR, side_extra_fn)
541}
542
543/// Widget function that converts a [`ContentFnArgs`] into a page main content container container widget.
544///
545/// This property sets the [`CONTENT_FN_VAR`].
546#[property(CONTEXT, default(CONTENT_FN_VAR), widget_impl(Wizard))]
547pub fn content_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<ContentFnArgs>>) -> UiNode {
548 with_context_var(child, CONTENT_FN_VAR, wgt_fn)
549}
550
551/// Widget function that converts a [`FooterFnArgs`] into a page side container widget.
552///
553/// This property sets the [`FOOTER_FN_VAR`].
554#[property(CONTEXT, default(FOOTER_FN_VAR), widget_impl(Wizard))]
555pub fn footer_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<FooterFnArgs>>) -> UiNode {
556 with_context_var(child, FOOTER_FN_VAR, wgt_fn)
557}
558
559/// Widget function that converts [`PageArgs`] to extra footer content.
560///
561/// This property sets the [`FOOTER_EXTRA_FN_VAR`].
562#[property(CONTEXT, default(FOOTER_EXTRA_FN_VAR), widget_impl(Wizard))]
563pub fn footer_extra_fn(child: impl IntoUiNode, footer_extra_fn: impl IntoVar<WidgetFn<PageArgs>>) -> UiNode {
564 with_context_var(child, FOOTER_EXTRA_FN_VAR, footer_extra_fn)
565}
566
567/// Widget function that converts a [`PanelFnArgs`] into a wizard.
568///
569/// This property sets the [`PANEL_FN_VAR`].
570#[property(CONTEXT, default(PANEL_FN_VAR), widget_impl(Wizard))]
571pub fn panel_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<PanelFnArgs>>) -> UiNode {
572 with_context_var(child, PANEL_FN_VAR, wgt_fn)
573}