Skip to main content

zng_wgt_wizard/
view_fn.rs

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