1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
// src/editor/hook/modal_drive.rs
//
// EditorHook: the confirmation dialog's open state, click routing, and draw.
// The geometry lives in `editor/modal.rs`. While open the dialog is
// screen-modal: every press and wheel is swallowed before any other routing
// (`tick` checks it first), and only a button press closes it -- a click-away
// is not a cancel, since the dialog guards destructive actions.
use super::*;
// An open confirmation dialog.
pub(super) struct ModalState {
pub(super) message: String,
pub(super) buttons: Vec<modal::Button>,
// Whether the dialog carries a name field. A prompt owns the keyboard while
// it is open, so the editor's shortcuts stand down.
pub(super) field: bool,
}
impl EditorHook {
// Open the dialog. Buttons past the widget's pool are dropped.
pub(super) fn open_modal(&mut self, message: &str, buttons: Vec<modal::Button>) {
self.set_modal(message, buttons, false);
}
// The same, with an empty name field to type into.
pub(super) fn open_prompt(&mut self, message: &str, buttons: Vec<modal::Button>) {
self.set_modal(message, buttons, true);
}
fn set_modal(&mut self, message: &str, mut buttons: Vec<modal::Button>, field: bool) {
buttons.truncate(modal::MAX_BUTTONS);
self.modal = Some(ModalState {
message: message.to_string(),
buttons,
field,
});
}
// Resolve a press while the dialog is open: a button runs its action and
// closes the dialog; anywhere else -- the dialog's own chrome or the dimmed
// screen behind it -- is swallowed. Returns whether the dialog was open
// (the press is consumed either way).
pub(super) fn route_modal_click(
&mut self,
input: &FrameInput,
vp: [f32; 2],
world: &mut World,
) -> bool {
let Some(state) = &self.modal else {
return false;
};
let field = state.field;
let hit = modal::hit_button(input.mouse_x, input.mouse_y, vp, state.buttons.len(), field);
if let Some(i) = hit {
let action = state.buttons[i].action.clone();
// Read the field before the dialog closes: reopening it (a rejected
// name) seeds a fresh one, so the typed text has to be taken here.
let typed = widget::field_text(world, modal::NAME_INPUT);
self.modal = None;
widget::seed_field(world, modal::NAME_INPUT, "");
self.run_modal_action(action, &typed, world);
}
true
}
fn run_modal_action(&mut self, action: modal::Action, typed: &str, world: &mut World) {
match action {
modal::Action::Dismiss => {}
modal::Action::Worlds(confirm) => self.apply_worlds_confirm(confirm, world),
modal::Action::NameWorld => self.name_untitled_world(typed),
}
}
// Lay out (or hide) the dialog this frame.
pub(super) fn drive_modal_draw(
&self,
world: &mut World,
vp: [f32; 2],
shown: bool,
mouse: [f32; 2],
) {
match (&self.modal, shown) {
(Some(state), true) => modal::apply(
world,
vp,
&state.message,
&state.buttons,
state.field,
mouse,
),
_ => modal::hide(world),
}
}
}