1use crate::{
17 AccessLabel, Button, EditBox, Filler, ScrollLabel, SelectableLabel, adapt::AdaptWidgetAny,
18};
19use kas::prelude::*;
20use kas::runner::AppData;
21use kas::text::format::FormattableText;
22use std::error::Error;
23use std::fmt::Write;
24
25#[derive(Copy, Clone, Debug)]
26struct MessageBoxOk;
27
28#[impl_self]
29mod MessageBox {
30 #[widget]
32 #[layout(column! [self.label, self.button])]
33 pub struct MessageBox<T: FormattableText + 'static> {
34 core: widget_core!(),
35 #[widget]
36 label: SelectableLabel<T>,
37 #[widget]
38 button: Button<AccessLabel>,
39 }
40
41 impl Self {
42 pub fn new(message: T) -> Self {
44 MessageBox {
45 core: Default::default(),
46 label: SelectableLabel::new(message),
47 button: Button::label_msg("&Ok", MessageBoxOk),
48 }
49 }
50
51 pub fn into_window<A: AppData>(self, title: impl ToString) -> Window<A> {
53 Window::new(self.map_any(), title).with_restrictions(true, true)
54 }
55
56 pub fn display(self, cx: &mut EventCx, title: impl ToString) {
58 cx.add_dataless_window(self.into_window(title), true);
59 }
60 }
61
62 impl Events for Self {
63 type Data = ();
64
65 fn configure(&mut self, cx: &mut ConfigCx) {
66 cx.register_nav_fallback(self.id());
67 }
68
69 fn handle_event(&mut self, cx: &mut EventCx, _: &Self::Data, event: Event) -> IsUsed {
70 match event {
71 Event::Command(Command::Escape, _) | Event::Command(Command::Enter, _) => {
72 cx.close_own_window();
73 Used
74 }
75 _ => Unused,
76 }
77 }
78
79 fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) {
80 if let Some(MessageBoxOk) = cx.try_pop() {
81 cx.close_own_window();
82 }
83 }
84 }
85}
86
87#[derive(Copy, Clone, Debug)]
88pub struct ErrorResult;
89
90#[impl_self]
91mod AlertError {
92 #[widget]
94 #[layout(column! [
95 self.label,
96 self.details,
97 self.ok,
98 ])]
99 pub struct AlertError<T: FormattableText + 'static> {
100 core: widget_core!(),
101 parent: Id,
102 title: String,
103 #[widget]
104 label: SelectableLabel<T>,
105 #[widget]
106 details: ScrollLabel<String>,
107 #[widget]
108 ok: Button<AccessLabel>,
109 }
110
111 impl Self {
112 pub fn new(message: T, error: &dyn Error) -> Self {
114 let mut details = format!("{error}");
115 let mut source = error.source();
116 while let Some(error) = source {
117 write!(&mut details, "\nCause: {error}").expect("write! to String failed");
118 source = error.source();
119 }
120
121 AlertError {
122 core: Default::default(),
123 parent: Id::default(),
124 title: "Error".to_string(),
125 label: SelectableLabel::new(message),
126 details: ScrollLabel::new(details),
127 ok: Button::label_msg("&Ok", ErrorResult),
128 }
129 }
130
131 pub fn with_title(mut self, title: impl ToString) -> Self {
133 self.title = title.to_string();
134 self
135 }
136
137 pub fn display_for(mut self, cx: &mut EventCx, parent: Id) {
141 self.parent = parent;
142 let title = std::mem::take(&mut self.title);
143 let window = Window::new(self, title).with_restrictions(true, true);
144 cx.add_dataless_window(window, true);
145 }
146 }
147
148 impl Events for Self {
149 type Data = ();
150
151 fn configure(&mut self, cx: &mut ConfigCx) {
152 cx.register_nav_fallback(self.id());
153 }
154
155 fn handle_event(&mut self, cx: &mut EventCx, _: &Self::Data, event: Event) -> IsUsed {
156 let result = match event {
157 Event::Command(Command::Escape, _) | Event::Command(Command::Enter, _) => {
158 ErrorResult
159 }
160 _ => return Unused,
161 };
162
163 cx.send(self.parent.clone(), result);
164 cx.close_own_window();
165 Used
166 }
167
168 fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) {
169 if let Some(result) = cx.try_pop::<ErrorResult>() {
170 cx.send(self.parent.clone(), result);
171 cx.close_own_window();
172 }
173 }
174 }
175}
176
177#[derive(Copy, Clone, Debug)]
178pub enum UnsavedResult {
179 Save,
180 Discard,
181 Cancel,
182}
183
184#[impl_self]
185mod AlertUnsaved {
186 #[widget]
188 #[layout(column! [
189 self.label,
190 row![self.save, self.discard, self.cancel],
191 ])]
192 pub struct AlertUnsaved<T: FormattableText + 'static> {
193 core: widget_core!(),
194 parent: Id,
195 title: String,
196 #[widget]
197 label: SelectableLabel<T>,
198 #[widget]
199 save: Button<AccessLabel>,
200 #[widget]
201 discard: Button<AccessLabel>,
202 #[widget]
203 cancel: Button<AccessLabel>,
204 }
205
206 impl Self {
207 pub fn new(message: T) -> Self {
209 AlertUnsaved {
210 core: Default::default(),
211 parent: Id::default(),
212 title: "Unsaved changes".to_string(),
213 label: SelectableLabel::new(message),
214 save: Button::label_msg("&Save", UnsavedResult::Save),
215 discard: Button::label_msg("&Discard", UnsavedResult::Discard),
216 cancel: Button::label_msg("&Cancel", UnsavedResult::Cancel),
217 }
218 }
219
220 pub fn with_title(mut self, title: impl ToString) -> Self {
222 self.title = title.to_string();
223 self
224 }
225
226 pub fn display_for(mut self, cx: &mut EventCx, parent: Id) {
230 self.parent = parent;
231 let title = std::mem::take(&mut self.title);
232 let window = Window::new(self, title).with_restrictions(true, true);
233 cx.add_dataless_window(window, true);
234 }
235 }
236
237 impl Events for Self {
238 type Data = ();
239
240 fn configure(&mut self, cx: &mut ConfigCx) {
241 cx.register_nav_fallback(self.id());
242 }
243
244 fn handle_event(&mut self, cx: &mut EventCx, _: &Self::Data, event: Event) -> IsUsed {
245 let result = match event {
246 Event::Command(Command::Escape, _) => UnsavedResult::Cancel,
247 Event::Command(Command::Enter, _) => {
248 if let Some(focus) = cx.nav_focus() {
249 if self.save.is_ancestor_of(focus) {
250 UnsavedResult::Save
251 } else if self.discard.is_ancestor_of(focus) {
252 UnsavedResult::Discard
253 } else if self.cancel.is_ancestor_of(focus) {
254 UnsavedResult::Cancel
255 } else {
256 return Unused;
257 }
258 } else {
259 return Unused;
260 }
261 }
262 _ => return Unused,
263 };
264
265 cx.send(self.parent.clone(), result);
266 cx.close_own_window();
267 Used
268 }
269
270 fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) {
271 if let Some(result) = cx.try_pop::<UnsavedResult>() {
272 cx.send(self.parent.clone(), result);
273 cx.close_own_window();
274 }
275 }
276 }
277}
278
279#[derive(Debug)]
281pub enum TextEditResult {
282 Cancel,
283 Ok(String),
284}
285
286#[derive(Clone, Debug)]
287struct MsgClose(bool);
288
289#[impl_self]
290mod TextEdit {
291 #[widget]
292 #[layout(grid! {
293 (0..=2, 0) => self.edit,
294 (0, 1) => Filler::maximize(),
295 (1, 1) => Button::label_msg("&Cancel", MsgClose(false)),
296 (2, 1) => Button::label_msg("&Save", MsgClose(true)),
297 })]
298 pub struct TextEdit {
304 core: widget_core!(),
305 #[widget]
306 edit: EditBox,
307 }
308
309 impl Self {
310 pub fn new(text: impl ToString, multi_line: bool) -> Self {
312 TextEdit {
313 core: Default::default(),
314 edit: EditBox::text(text).with_multi_line(multi_line),
315 }
316 }
317
318 pub fn set_text(&mut self, cx: &mut EventState, text: impl ToString) {
320 self.edit.clear(cx);
321 self.edit.set_string(cx, text.to_string());
322 }
323
324 pub fn into_window<A: AppData>(self, title: impl ToString) -> Window<A> {
326 Window::new(self.map_any(), title)
327 }
328
329 fn close(&mut self, cx: &mut EventCx, commit: bool) -> IsUsed {
330 cx.push(if commit {
331 TextEditResult::Ok(self.edit.clone_string())
332 } else {
333 TextEditResult::Cancel
334 });
335 Used
336 }
337 }
338
339 impl Events for Self {
340 type Data = ();
341
342 fn handle_event(&mut self, cx: &mut EventCx, _: &Self::Data, event: Event) -> IsUsed {
353 match event {
354 Event::Command(Command::Escape, _) => self.close(cx, false),
355 Event::Command(Command::Enter, _) => self.close(cx, true),
356 _ => Unused,
357 }
358 }
359
360 fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) {
361 if let Some(MsgClose(commit)) = cx.try_pop() {
362 let _ = self.close(cx, commit);
363 }
364 }
365 }
366}