Skip to main content

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}