Skip to main content

zng_wgt_setup/page/
eula.rs

1use zng_ext_l10n::l10n;
2use zng_wgt::{enabled, margin, on_init, prelude::*};
3use zng_wgt_container::Container;
4use zng_wgt_fill::background_color;
5use zng_wgt_markdown::Markdown;
6use zng_wgt_scroll::{SCROLL, Scroll, ScrollMode};
7use zng_wgt_text::Text;
8use zng_wgt_text_input::selectable::SelectableText;
9use zng_wgt_toggle as toggle;
10use zng_wgt_wizard::Page;
11
12use crate::{APP_NAME_VAR, SETUP_OP_VAR};
13
14/// End-user agreement page.
15#[non_exhaustive]
16pub struct EulaPage {
17    /// The agreement text.
18    pub license: Var<EulaTxt>,
19
20    /// Variable that is `true` when the user affirms
21    /// they have read and agreed the `license`.
22    ///
23    /// Is `var(false)` by default.
24    pub user_accepts: Var<bool>,
25
26    /// If user must vertically scroll the `license` text to at
27    /// least this amount or more to enable the option to accept.
28    ///
29    /// This value is checked against the [`SCROLL::vertical_offset`], it must
30    /// be equal or greater than this value to enable the accept option.
31    ///
32    /// Is `0.fct()` by default.
33    pub required_scroll: Factor,
34}
35impl EulaPage {
36    /// New with agreement text.
37    pub fn new(license: impl IntoVar<EulaTxt>) -> Self {
38        Self {
39            license: license.into_var(),
40            user_accepts: var(false),
41            required_scroll: 0.fct(),
42        }
43    }
44
45    /// Build the page.
46    pub fn build(self) -> Page {
47        let title = l10n!("eula/title", "License Agreement");
48        let info = SETUP_OP_VAR.flat_map(|op| match op {
49            crate::SetupOp::Install => l10n!(
50                "eula/info.install",
51                "Please review the license terms before installing {$app}",
52                app = APP_NAME_VAR
53            ),
54            crate::SetupOp::Update => l10n!(
55                "eula/info.update",
56                "Please review the license terms before updating {$app}",
57                app = APP_NAME_VAR
58            ),
59            crate::SetupOp::Repair | crate::SetupOp::Uninstall => const_var("".into()),
60        });
61
62        let Self {
63            license,
64            user_accepts,
65            required_scroll,
66        } = self;
67
68        let mut pg = Page::new(
69            title,
70            info,
71            wgt_fn!(user_accepts, |_| {
72                let accept_enabled = var(required_scroll == 0.fct() || user_accepts.get());
73                Container! {
74                    child_top = Text! {
75                        margin = 10;
76                        txt = if required_scroll >= 0.01.fct() {
77                            l10n!(
78                                "eula/message.requires_scroll",
79                                "You must read and accept the terms of this agreement before continuing."
80                            )
81                        } else {
82                            l10n!("eula/message", "You must accept the terms of this agreement before continuing.")
83                        };
84                    };
85                    child = Scroll! {
86                        padding = 10;
87                        background_color = light_dark(rgb(0.87, 0.87, 0.87), rgb(0.13, 0.13, 0.13));
88                        mode = license.map(|l| {
89                            let mut mode = ScrollMode::VERTICAL;
90                            if matches!(l, EulaTxt::PlainMono(_)) {
91                                // no text wrap
92                                mode |= ScrollMode::HORIZONTAL;
93                            }
94                            mode
95                        });
96                        child = license.present(wgt_fn!(|l| match l {
97                            EulaTxt::Plain(txt) => SelectableText! {
98                                txt;
99                            },
100                            EulaTxt::PlainMono(txt) => SelectableText! {
101                                txt;
102                                font_family = "monospace";
103                                txt_wrap = false;
104                            },
105                            EulaTxt::Markdown(txt) => Markdown! {
106                                txt;
107                            },
108                        }));
109                        on_init = hn!(accept_enabled, |_| {
110                            if accept_enabled.get() {
111                                return;
112                            }
113                            // enable accept choice once scroll >= 95%
114                            let offset = SCROLL.vertical_offset();
115                            if offset.get() >= required_scroll {
116                                accept_enabled.set(true);
117                            } else {
118                                let sub = offset.hook(clmv!(accept_enabled, |args| {
119                                    if *args.value() >= required_scroll {
120                                        accept_enabled.set(true);
121                                        return false;
122                                    }
123                                    true
124                                }));
125                                WIDGET.push_var_handle(sub);
126                            }
127                        });
128                    };
129                    child_bottom = Container! {
130                        margin = 10;
131                        child_spacing = 5;
132                        toggle::selector = toggle::Selector::single(user_accepts.clone());
133                        toggle::style_fn = toggle::RadioStyle!();
134                        child_top = toggle::Toggle! {
135                            child = Text!(l10n!("eula/accept.true", "I accept the agreement"));
136                            enabled = accept_enabled;
137                            value::<bool> = true;
138                        };
139                        child_bottom = toggle::Toggle! {
140                            child = Text!(l10n!("eula/accept.false", "I do not accept the agreement"));
141                            value::<bool> = false;
142                        };
143                    };
144                }
145            }),
146        );
147        pg.can_next.0 = user_accepts.read_only();
148        pg.content_fill = true;
149        pg
150    }
151}
152
153/// Supported formats for [`EulaPage::license`].
154#[derive(Debug, PartialEq, Clone)]
155#[non_exhaustive]
156pub enum EulaTxt {
157    /// Plain text, with normal text font and line wrapping.
158    Plain(Txt),
159    /// Plain text, monospace font, no line wrapping.
160    PlainMono(Txt),
161    /// Markdown formatted text.
162    Markdown(Txt),
163}
164impl_from_and_into_var! {
165    /// `EulaTxt::Plain`.
166    fn from(plain: Txt) -> EulaTxt {
167        EulaTxt::Plain(plain)
168    }
169    /// `EulaTxt::Plain`.
170    fn from(plain: &'static str) -> EulaTxt {
171        EulaTxt::Plain(Txt::from_static(plain))
172    }
173
174    fn from(eula: EulaTxt) -> Txt {
175        match eula {
176            EulaTxt::Plain(txt) | EulaTxt::PlainMono(txt) | EulaTxt::Markdown(txt) => txt,
177        }
178    }
179}