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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
//! One reading of the keyboard as text.
//!
//! Three screens take typed input: a level's name, a seat's name, a line of
//! chat. As a copy of the ladder apiece (skip releases, Backspace and
//! Delete erase, a handful of keys finish, everything else is the text the
//! keystroke produces) they had grown three ideas of "finished". Here the
//! ladder is written once and the finishing keys are the caller's list.
use bevy::input::keyboard::KeyboardInput;
use bevy::prelude::*;
/// What one keystroke means to a text box.
pub enum Keystroke {
/// The text a key produced, one character at a time. Control
/// characters are already dropped; any length cap is the caller's.
Char(char),
/// Backspace or Delete: take a character back.
Erase,
/// One of the caller's finishing keys, which one included, since Enter
/// commonly commits where Escape abandons.
Done(KeyCode),
}
/// Drain this frame's keystrokes into what each means to a text box.
/// `done` names the keys that finish the entry rather than typing.
pub fn keystrokes(typed: &mut MessageReader<KeyboardInput>, done: &[KeyCode]) -> Vec<Keystroke> {
let mut strokes = Vec::new();
for event in typed.read() {
if !event.state.is_pressed() {
continue;
}
if matches!(event.key_code, KeyCode::Backspace | KeyCode::Delete) {
strokes.push(Keystroke::Erase);
} else if done.contains(&event.key_code) {
strokes.push(Keystroke::Done(event.key_code));
} else {
strokes.extend(
event
.text
.iter()
.flat_map(|text| text.chars())
.filter(|ch| !ch.is_control())
.map(Keystroke::Char),
);
}
}
strokes
}
#[cfg(test)]
mod tests {
use super::*;
use bevy::ecs::system::RunSystemOnce;
use bevy::input::ButtonState;
use bevy::input::keyboard::{Key, NativeKey};
/// What one stroke came out as, in a form a test can compare.
#[derive(PartialEq, Eq, Debug)]
enum Says {
Ch(char),
Erase,
Done(KeyCode),
}
/// Feed the window's own report of some keys and see what the text box
/// is told they mean.
fn read(keys: &[(KeyCode, Option<&str>, ButtonState)], done: &[KeyCode]) -> Vec<Says> {
let mut app = App::new();
app.add_message::<KeyboardInput>();
for &(key_code, text, state) in keys {
app.world_mut().write_message(KeyboardInput {
key_code,
logical_key: text.map_or(Key::Unidentified(NativeKey::Unidentified), |t| {
Key::Character(t.into())
}),
state,
text: text.map(Into::into),
repeat: false,
window: Entity::PLACEHOLDER,
});
}
let done = done.to_vec();
app.world_mut()
.run_system_once(move |mut typed: MessageReader<KeyboardInput>| {
keystrokes(&mut typed, &done)
.into_iter()
.map(|stroke| match stroke {
Keystroke::Char(ch) => Says::Ch(ch),
Keystroke::Erase => Says::Erase,
Keystroke::Done(key) => Says::Done(key),
})
.collect::<Vec<_>>()
})
.expect("the reader ran")
}
/// The three things a keystroke can mean to a text box, in the order
/// they were typed. One ladder for the level's name, the seat's name
/// and a line of chat, which had grown three ideas of what finishes.
#[test]
fn a_stroke_is_text_an_erasure_or_the_key_that_finished_it() {
let said = read(
&[
(KeyCode::KeyH, Some("h"), ButtonState::Pressed),
(KeyCode::KeyI, Some("i"), ButtonState::Pressed),
(KeyCode::Backspace, None, ButtonState::Pressed),
(KeyCode::Delete, None, ButtonState::Pressed),
(KeyCode::Enter, Some("\r"), ButtonState::Pressed),
],
&[KeyCode::Enter, KeyCode::Escape],
);
assert_eq!(
said,
[
Says::Ch('h'),
Says::Ch('i'),
Says::Erase,
Says::Erase,
Says::Done(KeyCode::Enter),
]
);
}
/// Which finishing key it was travels with it, because Enter commonly
/// commits where Escape abandons, and a caller that could not tell
/// them apart would save the line somebody pressed Escape on.
#[test]
fn the_key_that_finished_says_which_one_it_was() {
let said = read(
&[(KeyCode::Escape, None, ButtonState::Pressed)],
&[KeyCode::Enter, KeyCode::Escape],
);
assert_eq!(said, [Says::Done(KeyCode::Escape)]);
// A key nobody named as finishing is just a key, and one with no
// text to it types nothing at all.
let said = read(&[(KeyCode::Escape, None, ButtonState::Pressed)], &[]);
assert_eq!(said, [], "Escape typed a character");
}
/// Letting a key go is not typing it again. Read from the raw stream,
/// every character in a name arrived twice.
#[test]
fn letting_a_key_go_types_nothing() {
let said = read(
&[
(KeyCode::KeyA, Some("a"), ButtonState::Pressed),
(KeyCode::KeyA, Some("a"), ButtonState::Released),
(KeyCode::Backspace, None, ButtonState::Released),
(KeyCode::Enter, None, ButtonState::Released),
],
&[KeyCode::Enter],
);
assert_eq!(said, [Says::Ch('a')]);
}
/// A control character is not text, whatever the window calls it.
/// Enter reports itself as "\r" and Tab as "\t", and a box that took
/// them at their word had a carriage return in the middle of a name.
#[test]
fn a_control_character_is_not_something_to_type() {
let said = read(
&[
(KeyCode::Enter, Some("\r"), ButtonState::Pressed),
(KeyCode::Tab, Some("\t"), ButtonState::Pressed),
(KeyCode::Space, Some(" "), ButtonState::Pressed),
],
&[],
);
assert_eq!(said, [Says::Ch(' ')], "only the space is text");
}
}