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