kas_widgets/
dialog.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4//     https://www.apache.org/licenses/LICENSE-2.0
5
6//! Dialog boxes
7//!
8//! KAS dialog boxes are pre-configured windows, usually allowing some
9//! customisation.
10//!
11//! # Design status
12//!
13//! At the current time, only a minimal selection of dialog boxes are provided
14//! and their design is likely to change.
15
16use crate::{Button, EditBox, Filler, Label, adapt::AdaptWidgetAny};
17use kas::event::NamedKey;
18use kas::prelude::*;
19use kas::text::format::FormattableText;
20
21#[derive(Copy, Clone, Debug)]
22struct MessageBoxOk;
23
24#[impl_self]
25mod MessageBox {
26    /// A simple message box.
27    #[widget]
28    #[layout(column! [self.label, self.button])]
29    pub struct MessageBox<T: FormattableText + 'static> {
30        core: widget_core!(),
31        #[widget]
32        label: Label<T>,
33        #[widget]
34        button: Button<Label<&'static str>>,
35    }
36
37    impl Self {
38        /// Construct
39        pub fn new(message: T) -> Self {
40            MessageBox {
41                core: Default::default(),
42                label: Label::new(message),
43                button: Button::new_msg(Label::new("Ok"), MessageBoxOk)
44                    .with_access_key(NamedKey::Enter.into()),
45            }
46        }
47
48        /// Build a [`Window`]
49        pub fn into_window<A: 'static>(self, title: impl ToString) -> Window<A> {
50            Window::new(self.map_any(), title).with_restrictions(true, true)
51        }
52    }
53
54    // TODO: call register_nav_fallback and close on Command::Escape, Enter
55    impl Events for Self {
56        type Data = ();
57
58        fn configure(&mut self, cx: &mut ConfigCx) {
59            cx.register_nav_fallback(self.id());
60        }
61
62        fn handle_event(&mut self, cx: &mut EventCx, _: &Self::Data, event: Event) -> IsUsed {
63            match event {
64                Event::Command(Command::Escape, _) | Event::Command(Command::Enter, _) => {
65                    cx.window_action(Action::CLOSE);
66                    Used
67                }
68                _ => Unused,
69            }
70        }
71
72        fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) {
73            if let Some(MessageBoxOk) = cx.try_pop() {
74                cx.action(self, Action::CLOSE);
75            }
76        }
77    }
78}
79
80/// Message sent by [`TextEdit`] on closure.
81#[derive(Debug)]
82pub enum TextEditResult {
83    Cancel,
84    Ok(String),
85}
86
87#[derive(Clone, Debug)]
88struct MsgClose(bool);
89
90#[impl_self]
91mod TextEdit {
92    #[widget]
93    #[layout(grid! {
94        (0..3, 0) => self.edit,
95        (0, 1) => Filler::maximize(),
96        (1, 1) => Button::label_msg("&Cancel", MsgClose(false)),
97        (2, 1) => Button::label_msg("&Save", MsgClose(true)),
98    })]
99    /// An editor over a `String`
100    ///
101    /// Emits a [`TextEditResult`] message when the "Ok" or "Cancel" button is
102    /// pressed. When used as a pop-up, it is up to the caller to close on this
103    /// message.
104    pub struct TextEdit {
105        core: widget_core!(),
106        #[widget]
107        edit: EditBox,
108    }
109
110    impl Self {
111        /// Construct
112        pub fn new(text: impl ToString, multi_line: bool) -> Self {
113            TextEdit {
114                core: Default::default(),
115                edit: EditBox::text(text).with_multi_line(multi_line),
116            }
117        }
118
119        /// Set text
120        pub fn set_text(&mut self, cx: &mut EventState, text: impl ToString) {
121            self.edit.set_string(cx, text.to_string());
122        }
123
124        /// Build a [`Window`]
125        pub fn into_window<A: 'static>(self, title: impl ToString) -> Window<A> {
126            Window::new(self.map_any(), title)
127        }
128
129        fn close(&mut self, cx: &mut EventCx, commit: bool) -> IsUsed {
130            cx.push(if commit {
131                TextEditResult::Ok(self.edit.clone_string())
132            } else {
133                TextEditResult::Cancel
134            });
135            Used
136        }
137    }
138
139    impl Events for Self {
140        type Data = ();
141
142        /* NOTE: this makes sense for a window but not an embedded editor.
143        fn configure(&mut self, cx: &mut ConfigCx) {
144            cx.register_nav_fallback(self.id());
145
146            // Focus first item initially:
147            if cx.nav_focus().is_none() {
148                cx.next_nav_focus(self.id(), false, FocusSource::Synthetic);
149            }
150        }*/
151
152        fn handle_event(&mut self, cx: &mut EventCx, _: &Self::Data, event: Event) -> IsUsed {
153            match event {
154                Event::Command(Command::Escape, _) => self.close(cx, false),
155                Event::Command(Command::Enter, _) => self.close(cx, true),
156                _ => Unused,
157            }
158        }
159
160        fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) {
161            if let Some(MsgClose(commit)) = cx.try_pop() {
162                let _ = self.close(cx, commit);
163            }
164        }
165    }
166}