rg3d_ui/
messagebox.rs

1use crate::{
2    button::{ButtonBuilder, ButtonMessage},
3    core::{algebra::Vector2, pool::Handle},
4    define_constructor,
5    draw::DrawingContext,
6    formatted_text::WrapMode,
7    grid::{Column, GridBuilder, Row},
8    message::{MessageDirection, OsEvent, UiMessage},
9    stack_panel::StackPanelBuilder,
10    text::{TextBuilder, TextMessage},
11    widget::{Widget, WidgetBuilder},
12    window::{Window, WindowBuilder, WindowMessage, WindowTitle},
13    BuildContext, Control, HorizontalAlignment, NodeHandleMapping, Orientation, RestrictionEntry,
14    Thickness, UiNode, UserInterface,
15};
16use std::{
17    any::{Any, TypeId},
18    ops::{Deref, DerefMut},
19    sync::mpsc::Sender,
20};
21
22#[derive(Debug, Clone, PartialEq)]
23pub enum MessageBoxMessage {
24    Open {
25        title: Option<String>,
26        text: Option<String>,
27    },
28    Close(MessageBoxResult),
29}
30
31impl MessageBoxMessage {
32    define_constructor!(MessageBoxMessage:Open => fn open(title: Option<String>, text: Option<String>), layout: false);
33    define_constructor!(MessageBoxMessage:Close => fn close(MessageBoxResult), layout: false);
34}
35
36#[derive(Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Hash, Debug)]
37pub enum MessageBoxResult {
38    Ok,
39    No,
40    Yes,
41    Cancel,
42}
43
44#[derive(Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Hash, Debug)]
45pub enum MessageBoxButtons {
46    Ok,
47    YesNo,
48    YesNoCancel,
49}
50
51#[derive(Clone)]
52pub struct MessageBox {
53    window: Window,
54    buttons: MessageBoxButtons,
55    ok_yes: Handle<UiNode>,
56    no: Handle<UiNode>,
57    cancel: Handle<UiNode>,
58    text: Handle<UiNode>,
59}
60
61impl Deref for MessageBox {
62    type Target = Widget;
63
64    fn deref(&self) -> &Self::Target {
65        &self.window
66    }
67}
68
69impl DerefMut for MessageBox {
70    fn deref_mut(&mut self) -> &mut Self::Target {
71        &mut self.window
72    }
73}
74
75// Message box extends Window widget so it delegates most of calls
76// to inner window.
77impl Control for MessageBox {
78    fn query_component(&self, type_id: TypeId) -> Option<&dyn Any> {
79        self.window.query_component(type_id).or_else(|| {
80            if type_id == TypeId::of::<Self>() {
81                Some(self)
82            } else {
83                None
84            }
85        })
86    }
87
88    fn resolve(&mut self, node_map: &NodeHandleMapping) {
89        self.window.resolve(node_map);
90        node_map.resolve(&mut self.ok_yes);
91        node_map.resolve(&mut self.no);
92        node_map.resolve(&mut self.cancel);
93        node_map.resolve(&mut self.text);
94    }
95
96    fn measure_override(&self, ui: &UserInterface, available_size: Vector2<f32>) -> Vector2<f32> {
97        self.window.measure_override(ui, available_size)
98    }
99
100    fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
101        self.window.arrange_override(ui, final_size)
102    }
103
104    fn draw(&self, drawing_context: &mut DrawingContext) {
105        self.window.draw(drawing_context)
106    }
107
108    fn update(&mut self, dt: f32, sender: &Sender<UiMessage>) {
109        self.window.update(dt, sender);
110    }
111
112    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
113        self.window.handle_routed_message(ui, message);
114
115        if let Some(ButtonMessage::Click) = message.data::<ButtonMessage>() {
116            if message.destination() == self.ok_yes {
117                let result = match self.buttons {
118                    MessageBoxButtons::Ok => MessageBoxResult::Ok,
119                    MessageBoxButtons::YesNo => MessageBoxResult::Yes,
120                    MessageBoxButtons::YesNoCancel => MessageBoxResult::Yes,
121                };
122                ui.send_message(MessageBoxMessage::close(
123                    self.handle,
124                    MessageDirection::ToWidget,
125                    result,
126                ));
127            } else if message.destination() == self.cancel {
128                ui.send_message(MessageBoxMessage::close(
129                    self.handle(),
130                    MessageDirection::ToWidget,
131                    MessageBoxResult::Cancel,
132                ));
133            } else if message.destination() == self.no {
134                ui.send_message(MessageBoxMessage::close(
135                    self.handle(),
136                    MessageDirection::ToWidget,
137                    MessageBoxResult::No,
138                ));
139            }
140        } else if let Some(msg) = message.data::<MessageBoxMessage>() {
141            match msg {
142                MessageBoxMessage::Open { title, text } => {
143                    if let Some(title) = title {
144                        ui.send_message(WindowMessage::title(
145                            self.handle(),
146                            MessageDirection::ToWidget,
147                            WindowTitle::Text(title.clone()),
148                        ));
149                    }
150
151                    if let Some(text) = text {
152                        ui.send_message(TextMessage::text(
153                            self.text,
154                            MessageDirection::ToWidget,
155                            text.clone(),
156                        ));
157                    }
158
159                    ui.send_message(WindowMessage::open_modal(
160                        self.handle(),
161                        MessageDirection::ToWidget,
162                        true,
163                    ));
164                }
165                MessageBoxMessage::Close(_) => {
166                    // Translate message box message into window message.
167                    ui.send_message(WindowMessage::close(
168                        self.handle(),
169                        MessageDirection::ToWidget,
170                    ));
171                }
172            }
173        }
174    }
175
176    fn preview_message(&self, ui: &UserInterface, message: &mut UiMessage) {
177        self.window.preview_message(ui, message);
178    }
179
180    fn handle_os_event(
181        &mut self,
182        self_handle: Handle<UiNode>,
183        ui: &mut UserInterface,
184        event: &OsEvent,
185    ) {
186        self.window.handle_os_event(self_handle, ui, event);
187    }
188}
189
190pub struct MessageBoxBuilder<'b> {
191    window_builder: WindowBuilder,
192    buttons: MessageBoxButtons,
193    text: &'b str,
194}
195
196impl<'a, 'b> MessageBoxBuilder<'b> {
197    pub fn new(window_builder: WindowBuilder) -> Self {
198        Self {
199            window_builder,
200            buttons: MessageBoxButtons::Ok,
201            text: "",
202        }
203    }
204
205    pub fn with_text(mut self, text: &'b str) -> Self {
206        self.text = text;
207        self
208    }
209
210    pub fn with_buttons(mut self, buttons: MessageBoxButtons) -> Self {
211        self.buttons = buttons;
212        self
213    }
214
215    pub fn build(mut self, ctx: &mut BuildContext) -> Handle<UiNode> {
216        let ok_yes;
217        let mut no = Default::default();
218        let mut cancel = Default::default();
219        let text;
220        let content = match self.buttons {
221            MessageBoxButtons::Ok => GridBuilder::new(
222                WidgetBuilder::new()
223                    .with_child({
224                        text = TextBuilder::new(
225                            WidgetBuilder::new().with_margin(Thickness::uniform(4.0)),
226                        )
227                        .with_text(self.text)
228                        .with_wrap(WrapMode::Word)
229                        .build(ctx);
230                        text
231                    })
232                    .with_child({
233                        ok_yes = ButtonBuilder::new(
234                            WidgetBuilder::new()
235                                .with_margin(Thickness::uniform(1.0))
236                                .with_width(80.0)
237                                .on_row(1)
238                                .with_horizontal_alignment(HorizontalAlignment::Center),
239                        )
240                        .with_text("OK")
241                        .build(ctx);
242                        ok_yes
243                    }),
244            )
245            .add_row(Row::stretch())
246            .add_row(Row::strict(25.0))
247            .add_column(Column::stretch())
248            .build(ctx),
249            MessageBoxButtons::YesNo => GridBuilder::new(
250                WidgetBuilder::new()
251                    .with_child({
252                        text = TextBuilder::new(WidgetBuilder::new())
253                            .with_text(self.text)
254                            .with_wrap(WrapMode::Word)
255                            .build(ctx);
256                        text
257                    })
258                    .with_child(
259                        StackPanelBuilder::new(
260                            WidgetBuilder::new()
261                                .with_horizontal_alignment(HorizontalAlignment::Right)
262                                .on_row(1)
263                                .with_child({
264                                    ok_yes = ButtonBuilder::new(
265                                        WidgetBuilder::new()
266                                            .with_width(80.0)
267                                            .with_margin(Thickness::uniform(1.0)),
268                                    )
269                                    .with_text("Yes")
270                                    .build(ctx);
271                                    ok_yes
272                                })
273                                .with_child({
274                                    no = ButtonBuilder::new(
275                                        WidgetBuilder::new()
276                                            .with_width(80.0)
277                                            .with_margin(Thickness::uniform(1.0)),
278                                    )
279                                    .with_text("No")
280                                    .build(ctx);
281                                    no
282                                }),
283                        )
284                        .with_orientation(Orientation::Horizontal)
285                        .build(ctx),
286                    ),
287            )
288            .add_row(Row::stretch())
289            .add_row(Row::strict(25.0))
290            .add_column(Column::stretch())
291            .build(ctx),
292            MessageBoxButtons::YesNoCancel => GridBuilder::new(
293                WidgetBuilder::new()
294                    .with_child({
295                        text = TextBuilder::new(WidgetBuilder::new())
296                            .with_text(self.text)
297                            .with_wrap(WrapMode::Word)
298                            .build(ctx);
299                        text
300                    })
301                    .with_child(
302                        StackPanelBuilder::new(
303                            WidgetBuilder::new()
304                                .with_horizontal_alignment(HorizontalAlignment::Right)
305                                .on_row(1)
306                                .with_child({
307                                    ok_yes = ButtonBuilder::new(
308                                        WidgetBuilder::new()
309                                            .with_width(80.0)
310                                            .with_margin(Thickness::uniform(1.0)),
311                                    )
312                                    .with_text("Yes")
313                                    .build(ctx);
314                                    ok_yes
315                                })
316                                .with_child({
317                                    no = ButtonBuilder::new(
318                                        WidgetBuilder::new()
319                                            .with_width(80.0)
320                                            .with_margin(Thickness::uniform(1.0)),
321                                    )
322                                    .with_text("No")
323                                    .build(ctx);
324                                    no
325                                })
326                                .with_child({
327                                    cancel = ButtonBuilder::new(
328                                        WidgetBuilder::new()
329                                            .with_width(80.0)
330                                            .with_margin(Thickness::uniform(1.0)),
331                                    )
332                                    .with_text("Cancel")
333                                    .build(ctx);
334                                    cancel
335                                }),
336                        )
337                        .with_orientation(Orientation::Horizontal)
338                        .build(ctx),
339                    ),
340            )
341            .add_row(Row::stretch())
342            .add_row(Row::strict(25.0))
343            .add_column(Column::stretch())
344            .build(ctx),
345        };
346
347        if self.window_builder.widget_builder.min_size.is_none() {
348            self.window_builder.widget_builder.min_size = Some(Vector2::new(200.0, 100.0));
349        }
350
351        self.window_builder.widget_builder.handle_os_events = true;
352
353        let is_open = self.window_builder.open;
354
355        let message_box = MessageBox {
356            buttons: self.buttons,
357            window: self.window_builder.with_content(content).build_window(ctx),
358            ok_yes,
359            no,
360            cancel,
361            text,
362        };
363
364        let handle = ctx.add_node(UiNode::new(message_box));
365
366        if is_open {
367            // We must restrict picking because message box is modal.
368            ctx.ui
369                .push_picking_restriction(RestrictionEntry { handle, stop: true });
370        }
371
372        handle
373    }
374}