zng_wgt_setup/page/
optional_tasks.rs1use zng_ext_l10n::l10n;
2use zng_var::MergeVarBuilder;
3use zng_wgt::{margin, prelude::*};
4use zng_wgt_container::Container;
5use zng_wgt_scroll::{Scroll, ScrollMode};
6use zng_wgt_stack::{Stack, StackDirection};
7use zng_wgt_text::Text;
8use zng_wgt_toggle::Toggle;
9use zng_wgt_wizard::Page;
10
11use crate::APP_NAME_VAR;
12
13#[non_exhaustive]
18pub struct OptionalTasksPage {
19 pub title: Var<Txt>,
23
24 pub info: Var<Txt>,
28
29 pub message: Var<Txt>,
31
32 pub tasks: Vec<OptionalTask>,
37
38 pub groups: Vec<OptionalTaskGroup>,
43}
44impl OptionalTasksPage {
49 pub fn new_generic() -> Self {
51 Self {
52 title: const_var("generic".into()),
53 info: const_var("generic".into()),
54 message: const_var("generic".into()),
55 tasks: vec![],
56 groups: vec![],
57 }
58 }
59
60 pub fn new_associate_files() -> Self {
62 Self {
63 title: const_var("associate_files".into()),
64 info: const_var("associate_files".into()),
65 message: const_var("associate_files".into()),
66 tasks: vec![],
67 groups: vec![],
68 }
69 }
70
71 pub fn new_additional_shortcuts() -> Self {
77 Self {
78 title: const_var("additional_shortcuts".into()),
79 info: const_var("additional_shortcuts".into()),
80 message: const_var("".into()),
81 tasks: vec![],
82 groups: vec![],
83 }
84 }
85}
86impl OptionalTasksPage {
87 fn resolve_builtin(&mut self) {
88 fn marker(v: &Var<Txt>) -> Option<&'static str> {
89 if v.capabilities().is_const() {
90 v.get().as_static_str()
91 } else {
92 None
93 }
94 }
95
96 let tasks = self.tasks.len();
97 let app = APP_NAME_VAR;
98
99 match marker(&self.title) {
100 Some("generic") => self.title = l10n!("optional_tasks/generic.title", "Select Additional Tasks"),
101 Some("associate_files") => {
102 self.title = l10n!(
103 "optional_tasks/associate_files.title",
104 "{ $tasks ->
105 [one] Associate File Type
106 *[other] Associate File Types
107 }"
108 )
109 }
110 Some("additional_shortcuts") => {
111 self.title = l10n!(
112 "optional_tasks/additional_shortcuts.title",
113 "{ $tasks ->
114 [one] Additional Shortcut
115 *[other] Additional Shortcuts
116 }"
117 )
118 }
119 _ => {}
120 }
121 match marker(&self.info) {
122 Some("generic") => self.info = l10n!("optional_tasks/generic.info", "Which additional tasks should be performed?"),
123 Some("associate_files") => {
124 self.info = l10n!(
125 "optional_tasks/associate_files.info",
126 "{ $tasks ->
127 [one] Should this file type open with {$app} by default?
128 *[other] Which file types should open with {$app} by default?
129 }",
130 )
131 }
132 Some("additional_shortcuts") => {
133 self.info = l10n!(
134 "optional_tasks/additional_shortcuts.info",
135 "{ $tasks ->
136 [one] Create additional shortcut?
137 *[other] Which additional shortcuts should be created?
138 }",
139 )
140 }
141 _ => {}
142 }
143 match marker(&self.message) {
144 Some("generic") => {
145 self.message = l10n!(
146 "optional_tasks/generic.message",
147 "Select additional tasks you would like to setup to perform."
148 )
149 }
150 Some("associate_files") => {
151 self.message = l10n!(
153 "optional_tasks/associate_files.message",
154 "{ $tasks ->
155 *[other] Select file types you would like associate with {$app}.
156 }",
157 )
158 }
159 _ => {}
160 }
161 }
162
163 pub fn build(mut self) -> Page {
169 assert!(!self.tasks.is_empty());
170
171 self.resolve_builtin();
172
173 let mut items = Vec::with_capacity(self.tasks.len() + self.groups.len());
175 enum Item {
176 Task(Var<Txt>, Var<bool>),
177 GroupHeader(Var<Txt>, Var<Option<bool>>),
178 GroupTask(Var<Txt>, Var<bool>),
179 GroupReserved,
180 }
181 let mut taken = vec![false; self.tasks.len()];
182 for i in 0..self.tasks.len() {
183 if taken[i] {
184 continue;
185 }
186 taken[i] = true;
187
188 let task = &self.tasks[i];
189 if task.group_id.is_empty() {
190 items.push(Item::Task(task.name.clone(), task.enabled.clone()));
191 } else {
192 let group = self
193 .groups
194 .iter()
195 .find(|g| g.id == task.group_id)
196 .unwrap_or_else(|| panic!("group {:?} not defined", task.group_id));
197 let group_i = items.len();
198 let mut group_enabled = MergeVarBuilder::new();
199
200 items.push(Item::GroupReserved);
201
202 let mut modifiable = task.enabled.capabilities().can_modify();
203
204 group_enabled.push(task.enabled.clone());
205 items.push(Item::GroupTask(task.name.clone(), task.enabled.clone()));
206
207 #[allow(clippy::needless_range_loop)] for i in (i + 1)..self.tasks.len() {
209 let task = &self.tasks[i];
210 if task.group_id == group.id {
211 taken[i] = true;
212 modifiable |= task.enabled.capabilities().can_modify();
213 group_enabled.push(task.enabled.clone());
214 items.push(Item::GroupTask(task.name.clone(), task.enabled.clone()));
215 }
216 }
217
218 fn merge(i: zng_var::MergeVarInputs<'_, bool>) -> Option<bool> {
219 let enabled = i.get(0);
220 if i.iter().all(|i| i == enabled) { Some(enabled) } else { None }
221 }
222 let group_enabled = if modifiable {
223 group_enabled.build_bidi_modify(merge, |m, _, mut v| {
224 if let Some(enabled) = *m {
225 v.set(enabled);
226 }
227 })
228 } else {
229 group_enabled.build(merge)
230 };
231
232 items[group_i] = Item::GroupHeader(group.name.clone(), group_enabled);
233 }
234 }
235
236 let message = self.message;
237 let mut pg = Page::new(
238 self.title,
239 self.info,
240 wgt_fn!(|_| {
241 let items = Stack! {
242 direction = StackDirection::top_to_bottom();
243 spacing = 8;
244 zng_wgt_toggle::style_fn = zng_wgt_style::style_fn!(|_| zng_wgt_toggle::CheckStyle!());
245 children =
246 items
247 .iter()
248 .map(|it| match it {
249 Item::Task(name, enabled) => Toggle! {
250 child = Text!(name.clone());
251 checked = enabled.clone();
252 },
253 Item::GroupHeader(name, enabled) => Toggle! {
254 child = Text!(name.clone());
255 checked_opt = enabled.clone();
256 },
257 Item::GroupTask(name, enabled) => Toggle! {
258 child = Text!(name.clone());
259 checked = enabled.clone();
260 margin = (10, 0);
261 },
262 Item::GroupReserved => unreachable!(),
263 })
264 .collect::<UiVec>(),
265 ;
266 };
267 Container! {
268 child_top = Text! {
269 txt = message.clone();
270 margin = 10;
271 };
272 child = Scroll! {
273 padding = 10;
274 mode = ScrollMode::VERTICAL;
275 child = items;
276 child_align = Align::FILL;
277 };
278 }
279 }),
280 );
281 pg.content_fill = true;
282 pg
283 }
284}
285
286#[non_exhaustive]
288pub struct OptionalTaskGroup {
289 pub id: Txt,
293 pub name: Var<Txt>,
295}
296impl OptionalTaskGroup {
297 pub fn new(id: impl Into<Txt>, name: Var<Txt>) -> Self {
303 let r = Self { id: id.into(), name };
304 assert!(r.id.is_empty());
305 r
306 }
307}
308
309#[non_exhaustive]
311pub struct OptionalTask {
312 pub group_id: Txt,
316 pub name: Var<Txt>,
318 pub enabled: Var<bool>,
322}
323impl OptionalTask {
324 pub fn new(name: Var<Txt>) -> Self {
326 Self {
327 group_id: "".into(),
328 name,
329 enabled: var(true),
330 }
331 }
332
333 pub fn new_create_desktop_shortcut() -> Self {
335 Self::new(l10n!("optional_tasks/task-create-desktop-shortcut", "Create desktop shortcut"))
336 }
337}