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
96
97
98
//! EditorHook: the open Behavior as a whole asset -- naming it and taking it out
//! of the world. Editing what a behavior does lives in `edit/behavior.rs`; these
//! act on the authored line itself, so both go through `mark_changed` and land in
//! the undo history like any other entry edit.
//!
//! Removing one takes two presses. `mark_changed` does record the pre-edit list,
//! so Undo brings a removed behavior back, but an undo restores the entry list
//! alone: the checker's verdict is left standing from whatever was open before
//! the jump, and the history is bounded, so the recovery is neither immediate nor
//! indefinite. Arming the chip keeps a stray press from needing it at all.
use concinnity_core::ecs::World;
use super::{EditorHook, declared_id};
use crate::editor::behavior;
use crate::editor::behavior::panel::Status;
use crate::editor::widget;
impl EditorHook {
// Arm the removal, then carry it out. Any other press on the panel disarms
// it, so the second press has to be a deliberate one.
pub(super) fn remove_behavior(&mut self, armed: bool, world: &mut World) {
let Some(idx) = self.behavior_entry() else {
return;
};
if !armed {
self.behavior.remove_armed = true;
return;
}
self.remove_entry_at(idx);
// Reopening clamps the ordinal, so the panel lands on whatever has slid
// into it -- or on the empty-world prompt, once the last one is gone.
self.open_behavior(world);
}
// Take the keyboard for the name field. Re-pressing a focused field leaves
// what is typed there alone; arriving at it fresh seeds it from the entry.
pub(super) fn focus_behavior_name(&mut self, world: &mut World) {
if self.behavior_entry().is_none() || self.behavior.name_focus {
return;
}
self.behavior.focus = false;
self.behavior.picking = false;
self.seed_behavior_name(world);
self.behavior.name_focus = true;
}
// Give up the name field without committing, reverting an abandoned edit
// rather than leaving it in the field to be committed by a later Enter.
pub(super) fn blur_behavior_name(&mut self, world: &mut World) {
if !self.behavior.name_focus {
return;
}
self.behavior.name_focus = false;
self.seed_behavior_name(world);
}
pub(super) fn seed_behavior_name(&mut self, world: &mut World) {
let name = self
.behavior_entry()
.and_then(|i| declared_id(&self.entries[i]))
.unwrap_or("")
.to_string();
widget::seed_field(world, behavior::panel::NAME_INPUT, &name);
}
// Commit the name field onto the open behavior. A blank name is refused
// rather than written, and one already taken is suffixed until it is free,
// so the entry list keeps its one-name-one-asset rule. The field is re-seeded
// from what actually landed, so the panel never shows a name the world does
// not hold.
pub(super) fn rename_behavior(&mut self, world: &mut World) {
let Some(idx) = self.behavior_entry() else {
return;
};
let typed = widget::field_text(world, behavior::panel::NAME_INPUT);
if typed.trim().is_empty() {
self.behavior.status = Some(Status::message("a behavior needs a name"));
self.blur_behavior_name(world);
return;
}
let Some(key) = self.entries.key_at(idx) else {
return;
};
let renamed = self.rename_entry(key, &typed);
self.mark_changed();
if let Some(message) = renamed.message("Behavior") {
self.notifier.success(&message);
}
self.behavior.name_focus = false;
// The checker's messages carry the behavior's name, so its verdict is
// re-read under the new one rather than left quoting the old.
let prev_fault = self.behavior_fault_message();
self.refresh_behavior_status();
self.notify_behavior_fault(prev_fault);
self.seed_behavior_name(world);
}
}