zng_wgt_setup/page/
install_dir.rs1use std::path::PathBuf;
2
3use zng_ext_l10n::l10n;
4use zng_unit::ByteLength;
5use zng_wgt::{align, prelude::*, visibility};
6use zng_wgt_button::Button;
7use zng_wgt_container::Container;
8use zng_wgt_dialog::DIALOG;
9use zng_wgt_menu::context::ContextMenu;
10use zng_wgt_text_input::{TextInput, label::Label, selectable::SelectableText};
11use zng_wgt_toggle::{self as toggle, Toggle};
12use zng_wgt_wizard::Page;
13
14use crate::{APP_NAME_VAR, APP_ORG_VAR, SETUP_OP_VAR, SetupOp};
15
16#[non_exhaustive]
18pub struct InstallDirPage {
19 pub default_dir: Var<PathBuf>,
21 pub install_dir: Var<PathBuf>,
23 pub min_required_space: Var<ByteLength>,
27}
28impl Default for InstallDirPage {
29 fn default() -> Self {
30 let default_dir = expr_var! {
31 let org = #{APP_ORG_VAR};
32 let app = #{APP_NAME_VAR};
33
34 if let Ok(pf) = std::env::var("ProgramFiles") {
35 let mut dir = PathBuf::from(pf);
36 if !org.is_empty() {
37 dir.push(org);
38 }
39 if !app.is_empty() {
40 dir.push(app);
41 }
42 dir
43 } else {
44 PathBuf::new()
45 }
46 };
47 Self {
48 install_dir: default_dir.cow(),
49 default_dir,
50 min_required_space: const_var(0.bytes()),
51 }
52 }
53}
54impl InstallDirPage {
55 pub fn new() -> Self {
57 Self::default()
58 }
59
60 pub fn build(self) -> Page {
62 let title = SETUP_OP_VAR.flat_map(|op| match op {
63 SetupOp::Install => l10n!("install_dir/title.install", "Select Install Location"),
64 SetupOp::Update | SetupOp::Repair => l10n!("destination/title.update-repair", "Change Install Location"),
65 SetupOp::Uninstall => l10n!("install_dir/title.uninstall", "Install Location"),
66 });
67 let info = SETUP_OP_VAR.flat_map(|op| match op {
68 SetupOp::Install => l10n!("install_dir/info.install", "Where should {$app} be installed?", app = APP_NAME_VAR),
69 SetupOp::Update | SetupOp::Repair | SetupOp::Uninstall => {
70 l10n!(
71 "install_dir/info.update-repair-uninstall",
72 "Where {$app} is installed.",
73 app = APP_NAME_VAR
74 )
75 }
76 });
77 let min_space = self.min_required_space.flat_map(|&s| {
78 if s == 0.bytes() {
79 const_var(Txt::default())
80 } else {
81 l10n!(
83 "install-dir/min-required-space",
84 "At least {$bytes} of free disk space is required.",
85 bytes = s
86 )
87 }
88 });
89
90 let mut pg = Page::new(
91 title.clone(),
92 info,
93 wgt_fn!(|_| { install_dir_ui(self.default_dir.clone(), self.install_dir.clone(), title.clone(), min_space.clone()) }),
94 );
95 pg.side = WidgetFn::nil();
96 pg
97 }
98}
99fn install_dir_ui(
100 default_dir: Var<PathBuf>,
101 install_dir: Var<PathBuf>,
102 select_dlg_title: Var<Txt>,
103 min_required_space: Var<Txt>,
104) -> UiNode {
105 let can_modify = if install_dir.capabilities().is_always_read_only() {
106 const_var(false)
107 } else {
108 SETUP_OP_VAR.map(|op| !matches!(op, SetupOp::Uninstall))
109 };
110 let can_reset = expr_var! {
111 *#{can_modify.clone()} && #{default_dir.clone()} != #{install_dir.clone()}
112 };
113 Container! {
114 child_spacing = 5;
115 child = TextInput! {
116 txt = install_dir.map(|p| {
117 let p = p.display().to_txt();
118 if cfg!(windows) {
119 p.replace('/', "\\")
120 } else {
121 p.replace('\\', "/")
122 }
123 .to_txt()
124 });
125 txt_editable = false;
126 align = Align::FILL_TOP;
127 };
128
129 when #{can_modify} {
130 child_end = select_dir_btn(install_dir.clone(), select_dlg_title.clone());
131 child_bottom = SelectableText! {
132 align = Align::BOTTOM_START;
133 visibility = min_required_space.map(|t| (!t.is_empty()).into());
134 txt = min_required_space;
135 };
136 }
137 when #{can_reset} {
138 child_end = Toggle! {
139 style_fn = toggle::ComboStyle!();
140 child = select_dir_btn(install_dir.clone(), select_dlg_title);
141 align = Align::FILL_TOP;
142 checked_popup = wgt_fn!(|_| ContextMenu! {
143 children = ui_vec![
144 Button! {
145 child = Label! {
146 txt = l10n!("install_dir/reset-label", "Default Location");
147 };
148 on_click = hn!(default_dir, install_dir, |args| {
149 args.propagation.stop();
150 install_dir.set(default_dir.get());
151 });
152 }
153 ]
154 });
155 };
156 }
157 }
158}
159fn select_dir_btn(install_dir: Var<PathBuf>, select_dlg_title: Var<Txt>) -> UiNode {
160 Button! {
161 child = Label! {
162 txt = l10n!("install_dir/select-label", "Select Location");
163 };
164 on_click = async_hn!(install_dir, select_dlg_title, |args| {
165 args.propagation.stop();
166 select_dir_dlg(install_dir, select_dlg_title).await;
167 });
168 align = Align::FILL_TOP;
169 }
170}
171async fn select_dir_dlg(install_dir: Var<PathBuf>, title: Var<Txt>) {
172 let current = install_dir.get();
173 let current_parent = current.parent().map(PathBuf::from).unwrap_or_default();
174 let current_name = current.file_name().unwrap_or_default().to_str().unwrap_or_default().to_txt();
175 let r = DIALOG.select_folder(title, current_parent, current_name);
176
177 match r.wait_rsp().await {
178 zng_wgt_dialog::FileDialogResponse::Selected(mut p) => install_dir.set(p.remove(0)),
179 zng_wgt_dialog::FileDialogResponse::Cancel => {}
180 zng_wgt_dialog::FileDialogResponse::Error(e) => {
181 tracing::error!("cannot select install dir, {e}");
182 }
183 _ => unreachable!(),
184 }
185}