Skip to content

Commit db5e450

Browse files
committed
Merge branch 'imgui-keyboard-input-fix'
2 parents 7cb7362 + caaaa25 commit db5e450

2 files changed

Lines changed: 230 additions & 3 deletions

File tree

crates/renderide/src/diagnostics/hud/input.rs

Lines changed: 208 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,118 @@
77
//! from the [`InputState`] sent to the host while ImGui reports it captured the previous frame.
88
99
use glam::Vec2;
10-
use imgui::{Io, MouseButton as ImGuiMouseButton};
10+
use imgui::{Io, Key as ImGuiKey, MouseButton as ImGuiMouseButton};
11+
use winit::keyboard::ModifiersState;
1112

12-
use crate::shared::InputState;
13+
use crate::shared::{InputState, Key};
14+
15+
const SUPPORTED_KEY_EVENTS: &[(Key, ImGuiKey)] = &[
16+
(Key::Tab, ImGuiKey::Tab),
17+
(Key::LeftArrow, ImGuiKey::LeftArrow),
18+
(Key::RightArrow, ImGuiKey::RightArrow),
19+
(Key::UpArrow, ImGuiKey::UpArrow),
20+
(Key::DownArrow, ImGuiKey::DownArrow),
21+
(Key::PageUp, ImGuiKey::PageUp),
22+
(Key::PageDown, ImGuiKey::PageDown),
23+
(Key::Home, ImGuiKey::Home),
24+
(Key::End, ImGuiKey::End),
25+
(Key::Insert, ImGuiKey::Insert),
26+
(Key::Delete, ImGuiKey::Delete),
27+
(Key::Backspace, ImGuiKey::Backspace),
28+
(Key::Space, ImGuiKey::Space),
29+
(Key::Return, ImGuiKey::Enter),
30+
(Key::Escape, ImGuiKey::Escape),
31+
(Key::LeftControl, ImGuiKey::LeftCtrl),
32+
(Key::LeftShift, ImGuiKey::LeftShift),
33+
(Key::LeftAlt, ImGuiKey::LeftAlt),
34+
(Key::LeftWindows, ImGuiKey::LeftSuper),
35+
(Key::RightControl, ImGuiKey::RightCtrl),
36+
(Key::RightShift, ImGuiKey::RightShift),
37+
(Key::RightAlt, ImGuiKey::RightAlt),
38+
(Key::RightWindows, ImGuiKey::RightSuper),
39+
(Key::Menu, ImGuiKey::Menu),
40+
(Key::Alpha0, ImGuiKey::Alpha0),
41+
(Key::Alpha1, ImGuiKey::Alpha1),
42+
(Key::Alpha2, ImGuiKey::Alpha2),
43+
(Key::Alpha3, ImGuiKey::Alpha3),
44+
(Key::Alpha4, ImGuiKey::Alpha4),
45+
(Key::Alpha5, ImGuiKey::Alpha5),
46+
(Key::Alpha6, ImGuiKey::Alpha6),
47+
(Key::Alpha7, ImGuiKey::Alpha7),
48+
(Key::Alpha8, ImGuiKey::Alpha8),
49+
(Key::Alpha9, ImGuiKey::Alpha9),
50+
(Key::A, ImGuiKey::A),
51+
(Key::B, ImGuiKey::B),
52+
(Key::C, ImGuiKey::C),
53+
(Key::D, ImGuiKey::D),
54+
(Key::E, ImGuiKey::E),
55+
(Key::F, ImGuiKey::F),
56+
(Key::G, ImGuiKey::G),
57+
(Key::H, ImGuiKey::H),
58+
(Key::I, ImGuiKey::I),
59+
(Key::J, ImGuiKey::J),
60+
(Key::K, ImGuiKey::K),
61+
(Key::L, ImGuiKey::L),
62+
(Key::M, ImGuiKey::M),
63+
(Key::N, ImGuiKey::N),
64+
(Key::O, ImGuiKey::O),
65+
(Key::P, ImGuiKey::P),
66+
(Key::Q, ImGuiKey::Q),
67+
(Key::R, ImGuiKey::R),
68+
(Key::S, ImGuiKey::S),
69+
(Key::T, ImGuiKey::T),
70+
(Key::U, ImGuiKey::U),
71+
(Key::V, ImGuiKey::V),
72+
(Key::W, ImGuiKey::W),
73+
(Key::X, ImGuiKey::X),
74+
(Key::Y, ImGuiKey::Y),
75+
(Key::Z, ImGuiKey::Z),
76+
(Key::F1, ImGuiKey::F1),
77+
(Key::F2, ImGuiKey::F2),
78+
(Key::F3, ImGuiKey::F3),
79+
(Key::F4, ImGuiKey::F4),
80+
(Key::F5, ImGuiKey::F5),
81+
(Key::F6, ImGuiKey::F6),
82+
(Key::F7, ImGuiKey::F7),
83+
(Key::F8, ImGuiKey::F8),
84+
(Key::F9, ImGuiKey::F9),
85+
(Key::F10, ImGuiKey::F10),
86+
(Key::F11, ImGuiKey::F11),
87+
(Key::F12, ImGuiKey::F12),
88+
(Key::Quote, ImGuiKey::Apostrophe),
89+
(Key::Comma, ImGuiKey::Comma),
90+
(Key::Minus, ImGuiKey::Minus),
91+
(Key::Period, ImGuiKey::Period),
92+
(Key::Slash, ImGuiKey::Slash),
93+
(Key::Semicolon, ImGuiKey::Semicolon),
94+
(Key::Equals, ImGuiKey::Equal),
95+
(Key::LeftBracket, ImGuiKey::LeftBracket),
96+
(Key::Backslash, ImGuiKey::Backslash),
97+
(Key::RightBracket, ImGuiKey::RightBracket),
98+
(Key::BackQuote, ImGuiKey::GraveAccent),
99+
(Key::CapsLock, ImGuiKey::CapsLock),
100+
(Key::ScrollLock, ImGuiKey::ScrollLock),
101+
(Key::Numlock, ImGuiKey::NumLock),
102+
(Key::Print, ImGuiKey::PrintScreen),
103+
(Key::Pause, ImGuiKey::Pause),
104+
(Key::Keypad0, ImGuiKey::Keypad0),
105+
(Key::Keypad1, ImGuiKey::Keypad1),
106+
(Key::Keypad2, ImGuiKey::Keypad2),
107+
(Key::Keypad3, ImGuiKey::Keypad3),
108+
(Key::Keypad4, ImGuiKey::Keypad4),
109+
(Key::Keypad5, ImGuiKey::Keypad5),
110+
(Key::Keypad6, ImGuiKey::Keypad6),
111+
(Key::Keypad7, ImGuiKey::Keypad7),
112+
(Key::Keypad8, ImGuiKey::Keypad8),
113+
(Key::Keypad9, ImGuiKey::Keypad9),
114+
(Key::KeypadPeriod, ImGuiKey::KeypadDecimal),
115+
(Key::KeypadDivide, ImGuiKey::KeypadDivide),
116+
(Key::KeypadMultiply, ImGuiKey::KeypadMultiply),
117+
(Key::KeypadMinus, ImGuiKey::KeypadSubtract),
118+
(Key::KeypadPlus, ImGuiKey::KeypadAdd),
119+
(Key::KeypadEnter, ImGuiKey::KeypadEnter),
120+
(Key::KeypadEquals, ImGuiKey::KeypadEqual),
121+
];
13122

14123
/// Strips pointer, scroll, drag/drop, and keyboard data from `input` when ImGui reported capture on the previous frame.
15124
///
@@ -42,7 +151,7 @@ pub fn sanitize_input_state_for_imgui_host(
42151
}
43152

44153
/// Pointer and window hints for ImGui, in **physical** pixels where noted.
45-
#[derive(Clone, Copy, Debug, Default)]
154+
#[derive(Clone, Debug, Default)]
46155
pub struct DebugHudInput {
47156
/// Cursor position in physical pixels (or `[-inf, -inf]` when unavailable).
48157
pub cursor_px: [f32; 2],
@@ -62,6 +171,12 @@ pub struct DebugHudInput {
62171
pub extra1: bool,
63172
/// Fifth mouse button held (e.g. side forward).
64173
pub extra2: bool,
174+
/// Current keyboard modifiers from winit.
175+
pub keyboard_modifiers: ModifiersState,
176+
/// Keys currently held, in host [`Key`] form.
177+
pub held_keys: Vec<Key>,
178+
/// Text committed since the previous HUD input snapshot.
179+
pub text: String,
65180
}
66181

67182
impl DebugHudInput {
@@ -89,6 +204,9 @@ impl DebugHudInput {
89204
middle: acc.middle_held,
90205
extra1: acc.button4_held,
91206
extra2: acc.button5_held,
207+
keyboard_modifiers: acc.keyboard_modifiers(),
208+
held_keys: acc.held_keys.clone(),
209+
text: acc.take_hud_text(),
92210
}
93211
}
94212
}
@@ -109,6 +227,93 @@ pub(crate) fn apply_input(io: &mut Io, input: &DebugHudInput) {
109227
io.add_mouse_button_event(ImGuiMouseButton::Extra1, input.extra1);
110228
io.add_mouse_button_event(ImGuiMouseButton::Extra2, input.extra2);
111229
io.add_mouse_wheel_event([input.mouse_wheel_delta.x, input.mouse_wheel_delta.y]);
230+
apply_keyboard_input(io, input);
231+
}
232+
233+
fn apply_keyboard_input(io: &mut Io, input: &DebugHudInput) {
234+
for (key, down) in modifier_key_states(input.keyboard_modifiers) {
235+
io.add_key_event(key, down);
236+
}
237+
for &(host_key, imgui_key) in SUPPORTED_KEY_EVENTS {
238+
io.add_key_event(imgui_key, input.held_keys.contains(&host_key));
239+
}
240+
for character in input.text.chars() {
241+
io.add_input_character(character);
242+
}
243+
}
244+
245+
fn modifier_key_states(modifiers: ModifiersState) -> [(ImGuiKey, bool); 4] {
246+
[
247+
(ImGuiKey::ModCtrl, modifiers.control_key()),
248+
(ImGuiKey::ModShift, modifiers.shift_key()),
249+
(ImGuiKey::ModAlt, modifiers.alt_key()),
250+
(ImGuiKey::ModSuper, modifiers.meta_key()),
251+
]
252+
}
253+
254+
#[cfg(test)]
255+
fn host_key_to_imgui_key(key: Key) -> Option<ImGuiKey> {
256+
SUPPORTED_KEY_EVENTS
257+
.iter()
258+
.find_map(|&(host_key, imgui_key)| (host_key == key).then_some(imgui_key))
259+
}
260+
261+
#[cfg(test)]
262+
mod input_bridge_tests {
263+
use imgui::Context;
264+
use winit::keyboard::ModifiersState;
265+
266+
use super::{
267+
DebugHudInput, ImGuiKey, Key, apply_input, host_key_to_imgui_key, modifier_key_states,
268+
};
269+
270+
#[test]
271+
fn host_keys_map_to_imgui_keys_needed_by_text_entry() {
272+
for (host_key, imgui_key) in [
273+
(Key::Alpha1, ImGuiKey::Alpha1),
274+
(Key::Backspace, ImGuiKey::Backspace),
275+
(Key::Return, ImGuiKey::Enter),
276+
(Key::LeftArrow, ImGuiKey::LeftArrow),
277+
(Key::KeypadPeriod, ImGuiKey::KeypadDecimal),
278+
] {
279+
assert_eq!(host_key_to_imgui_key(host_key), Some(imgui_key));
280+
}
281+
assert_eq!(host_key_to_imgui_key(Key::F13), None);
282+
}
283+
284+
#[test]
285+
fn modifier_states_include_ctrl_for_drag_input_activation() {
286+
let states = modifier_key_states(ModifiersState::CONTROL | ModifiersState::SHIFT);
287+
288+
assert!(states.contains(&(ImGuiKey::ModCtrl, true)));
289+
assert!(states.contains(&(ImGuiKey::ModShift, true)));
290+
assert!(states.contains(&(ImGuiKey::ModAlt, false)));
291+
assert!(states.contains(&(ImGuiKey::ModSuper, false)));
292+
}
293+
294+
#[test]
295+
fn apply_input_feeds_modifiers_and_held_keys_to_imgui() {
296+
let mut context = Context::create();
297+
context.fonts().build_rgba32_texture();
298+
let input = DebugHudInput {
299+
window_focused: true,
300+
keyboard_modifiers: ModifiersState::CONTROL,
301+
held_keys: vec![Key::Backspace, Key::Alpha1],
302+
text: "12.5".into(),
303+
..Default::default()
304+
};
305+
306+
{
307+
let io = context.io_mut();
308+
io.display_size = [100.0, 100.0];
309+
apply_input(io, &input);
310+
}
311+
312+
let ui = context.frame();
313+
assert!(ui.io().key_ctrl);
314+
assert!(ui.is_key_down(ImGuiKey::Backspace));
315+
assert!(ui.is_key_down(ImGuiKey::Alpha1));
316+
}
112317
}
113318

114319
#[cfg(test)]

crates/renderide/src/frontend/input/accumulator.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ pub struct WindowInputAccumulator {
5252
ime_commit_buffer: String,
5353
/// Single-character text from key events (supplements IME for simple typing).
5454
text_typing_buffer: String,
55+
/// Text committed since the last HUD input snapshot.
56+
hud_text_buffer: String,
5557
/// Paths from [`WindowEvent::DragDropped`](winit::event::WindowEvent::DragDropped) coalesced until take.
5658
pending_drop_paths: Vec<String>,
5759
/// Last cursor position in physical pixels (for drop-point reporting).
@@ -78,6 +80,7 @@ impl Default for WindowInputAccumulator {
7880
held_keys: Vec::new(),
7981
ime_commit_buffer: String::new(),
8082
text_typing_buffer: String::new(),
83+
hud_text_buffer: String::new(),
8184
pending_drop_paths: Vec::new(),
8285
last_cursor_pixel: IVec2::ZERO,
8386
keyboard_modifiers: ModifiersState::empty(),
@@ -90,11 +93,13 @@ impl WindowInputAccumulator {
9093
/// Records IME-composed text committed by the platform.
9194
pub fn push_ime_commit(&mut self, text: &str) {
9295
self.ime_commit_buffer.push_str(text);
96+
self.hud_text_buffer.push_str(text);
9397
}
9498

9599
/// Records printable text associated with a key press (not repeats).
96100
pub fn push_key_text(&mut self, text: &str) {
97101
self.text_typing_buffer.push_str(text);
102+
self.hud_text_buffer.push_str(text);
98103
}
99104

100105
/// Records a file dropped onto the window; paths are batched into the next [`InputState`].
@@ -186,6 +191,11 @@ impl WindowInputAccumulator {
186191
pub fn take_hud_scroll_delta(&mut self) -> Vec2 {
187192
std::mem::take(&mut self.hud_scroll_delta)
188193
}
194+
195+
/// Returns text committed since the HUD last read it.
196+
pub fn take_hud_text(&mut self) -> String {
197+
std::mem::take(&mut self.hud_text_buffer)
198+
}
189199
}
190200

191201
#[cfg(test)]
@@ -299,6 +309,18 @@ mod tests {
299309
assert!(s2.keyboard.expect("kb").type_delta.is_none());
300310
}
301311

312+
#[test]
313+
fn hud_text_survives_host_snapshot_drain() {
314+
let mut w = WindowInputAccumulator::default();
315+
w.push_ime_commit("12");
316+
w.push_key_text(".");
317+
318+
let s = w.take_input_state(false);
319+
assert_eq!(s.keyboard.expect("kb").type_delta.as_deref(), Some("12."));
320+
assert_eq!(w.take_hud_text(), "12.");
321+
assert!(w.take_hud_text().is_empty());
322+
}
323+
302324
/// Normalized UV at logical center when resolution and position share logical space.
303325
#[test]
304326
fn normalized_center_at_logical_half_resolution() {

0 commit comments

Comments
 (0)