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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
//! EditorHook: the Behavior panel's session state. The actions that read the
//! world or the entry list live in `behavior.rs`; this holds what the panel
//! remembers between frames and the resets that touch nothing else.
use crate::editor::behavior::clip::Clip;
use crate::editor::behavior::panel::{Status, ViewMode};
use crate::editor::behavior::path::Path;
use crate::editor::behavior::pulse::NodePulse;
// Shown state, which of the world's Behavior entries is open (an ordinal into
// them, so an unrelated add / delete cannot retarget it), the selected outline
// row, the outline and palette scrolls, whether the palette is up and which of
// its options the keyboard is on, whether the value field holds keyboard focus,
// and the world checker's verdict on the open behavior. The name field carries
// its own focus, and `remove_armed` is the removal chip waiting on the press
// that carries it out.
#[derive(Debug, Default)]
pub(in crate::editor::hook) struct BehaviorState {
pub(in crate::editor::hook) index: usize,
pub(in crate::editor::hook) row: Option<usize>,
pub(in crate::editor::hook) scroll: usize,
pub(in crate::editor::hook) picking: bool,
pub(in crate::editor::hook) pick_scroll: usize,
pub(in crate::editor::hook) pick: usize,
// The palette's filter text, mirrored off its field once a frame so the
// presses and draws that follow all narrow by the same query.
pub(in crate::editor::hook) filter: String,
pub(in crate::editor::hook) focus: bool,
pub(in crate::editor::hook) name_focus: bool,
pub(in crate::editor::hook) remove_armed: bool,
pub(in crate::editor::hook) status: Option<Status>,
pub(in crate::editor::hook) mode: ViewMode,
// The chart's scroll offset, and the anchor an in-flight canvas pan holds.
pub(in crate::editor::hook) pan: [f32; 2],
pub(in crate::editor::hook) pan_drag: Option<[f32; 2]>,
// The list member held for a paste, with the kind of list it came out of so
// it can only land in one of the same kind. Session state, not an edit, and
// deliberately not cleared by opening another behavior: carrying a node
// between two of them is most of the point.
pub(in crate::editor::hook) clip: Option<Clip>,
// The overview's selected card. The map's cards stand for whole behaviors
// and the things they reach rather than for places inside one, so the
// outline row the other two views share cannot address them.
pub(in crate::editor::hook) overview_card: Option<usize>,
// Live-debug state fed by the runtime's execution trace while a play
// session runs with the Behavior or Variables panel open
// (`hook/drive/trace.rs`). Pulses cover the OPEN behavior only (paths are
// per-body); breakpoints are held by behavior NAME + node path so they
// survive preview rebuilds and body edits shifting node ids.
pub(in crate::editor::hook) pulses: Vec<NodePulse>,
pub(in crate::editor::hook) breakpoints: Vec<(String, Path)>,
}
impl BehaviorState {
// Release every input the panel can hold the keyboard with.
pub(in crate::editor::hook) fn blur_inputs(&mut self) {
self.focus = false;
self.name_focus = false;
self.remove_armed = false;
self.picking = false;
}
// Forget what indexed the world being left. The clip and the breakpoints
// survive a switch: both are keyed by content, not by the old entry list.
pub(in crate::editor::hook) fn reset_for_world(&mut self) {
self.index = 0;
self.row = None;
self.scroll = 0;
self.status = None;
}
// Step to the next view. The pan does not survive, because each chart is
// its own shape.
pub(in crate::editor::hook) fn toggle_view(&mut self) {
self.mode = self.mode.other();
self.picking = false;
self.pan_drag = None;
self.pan = [0.0, 0.0];
}
// Toggle the breakpoint on `path` inside the behavior called `name`.
pub(in crate::editor::hook) fn toggle_breakpoint(&mut self, name: String, path: &Path) {
match self
.breakpoints
.iter()
.position(|(n, p)| n == &name && p == path)
{
Some(i) => {
self.breakpoints.remove(i);
}
None => self.breakpoints.push((name, path.clone())),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::editor::behavior::outline::List;
fn dirty() -> BehaviorState {
BehaviorState {
index: 3,
row: Some(2),
scroll: 4,
picking: true,
pick_scroll: 5,
pick: 6,
filter: "wait".into(),
focus: true,
name_focus: true,
remove_armed: true,
status: Some(Status::message("bad")),
mode: ViewMode::Chart,
pan: [1.0, 2.0],
pan_drag: Some([3.0, 4.0]),
clip: Some(Clip {
list: List::Nodes,
value: serde_json::json!({ "kind": "wait" }),
}),
overview_card: Some(1),
pulses: Vec::new(),
breakpoints: vec![("b".into(), Vec::new())],
}
}
#[test]
fn blur_inputs_releases_only_the_input_flags() {
let mut s = dirty();
s.blur_inputs();
assert!(!s.focus && !s.name_focus && !s.remove_armed && !s.picking);
assert_eq!((s.index, s.row, s.scroll), (3, Some(2), 4));
assert_eq!((s.pick, s.pick_scroll, s.filter.as_str()), (6, 5, "wait"));
assert!(s.status.is_some());
assert_eq!(s.mode, ViewMode::Chart);
assert_eq!((s.pan, s.pan_drag), ([1.0, 2.0], Some([3.0, 4.0])));
}
#[test]
fn reset_for_world_keeps_the_clip_and_breakpoints() {
let mut s = dirty();
s.reset_for_world();
assert_eq!((s.index, s.row, s.scroll, s.status), (0, None, 0, None));
assert!(s.clip.is_some());
assert_eq!(s.breakpoints.len(), 1);
assert!(s.focus && s.name_focus && s.remove_armed && s.picking);
assert_eq!((s.pick, s.pick_scroll, s.filter.as_str()), (6, 5, "wait"));
assert_eq!((s.mode, s.overview_card), (ViewMode::Chart, Some(1)));
assert_eq!((s.pan, s.pan_drag), ([1.0, 2.0], Some([3.0, 4.0])));
}
}