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