Skip to content

Commit 827fdef

Browse files
authored
eframe: Added App::raw_input_hook allows for the manipulation or filtering of raw input events (#4008)
# What's New * eframe: Added `App::raw_input_hook` allows for the manipulation or filtering of raw input events A filter applied to raw input before [`Self::update`] This allows for the manipulation or filtering of input events before they are processed by egui. This can be used to exclude specific keyboard shortcuts, mouse events, etc. Additionally, it can be used to add custom keyboard or mouse events generated by a virtual keyboard. * examples: Added an example to demonstrates how to implement a custom virtual keyboard. [eframe-custom-keypad.webm](https://github.com/emilk/egui/assets/1274171/a9dc8e34-2c35-4172-b7ef-41010b794fb8)
1 parent 00a399b commit 827fdef

File tree

8 files changed

+382
-0
lines changed

8 files changed

+382
-0
lines changed

Cargo.lock

+9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/eframe/src/epi.rs

+18
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,24 @@ pub trait App {
196196
fn persist_egui_memory(&self) -> bool {
197197
true
198198
}
199+
200+
/// A hook for manipulating or filtering raw input before it is processed by [`Self::update`].
201+
///
202+
/// This function provides a way to modify or filter input events before they are processed by egui.
203+
///
204+
/// It can be used to prevent specific keyboard shortcuts or mouse events from being processed by egui.
205+
///
206+
/// Additionally, it can be used to inject custom keyboard or mouse events into the input stream, which can be useful for implementing features like a virtual keyboard.
207+
///
208+
/// # Arguments
209+
///
210+
/// * `_ctx` - The context of the egui, which provides access to the current state of the egui.
211+
/// * `_raw_input` - The raw input events that are about to be processed. This can be modified to change the input that egui processes.
212+
///
213+
/// # Note
214+
///
215+
/// This function does not return a value. Any changes to the input should be made directly to `_raw_input`.
216+
fn raw_input_hook(&mut self, _ctx: &egui::Context, _raw_input: &mut egui::RawInput) {}
199217
}
200218

201219
/// Selects the level of hardware graphics acceleration.

crates/eframe/src/native/epi_integration.rs

+2
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,8 @@ impl EpiIntegration {
274274

275275
let close_requested = raw_input.viewport().close_requested();
276276

277+
app.raw_input_hook(&self.egui_ctx, &mut raw_input);
278+
277279
let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
278280
if let Some(viewport_ui_cb) = viewport_ui_cb {
279281
// Child viewport

examples/custom_keypad/Cargo.toml

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[package]
2+
name = "custom_keypad"
3+
version = "0.1.0"
4+
authors = ["Varphone Wong <varphone@qq.com>"]
5+
license = "MIT OR Apache-2.0"
6+
edition = "2021"
7+
rust-version = "1.72"
8+
publish = false
9+
10+
11+
[dependencies]
12+
eframe = { workspace = true, features = [
13+
"default",
14+
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
15+
] }
16+
17+
# For image support:
18+
egui_extras = { workspace = true, features = ["default", "image"] }
19+
20+
env_logger = { version = "0.10", default-features = false, features = [
21+
"auto-color",
22+
"humantime",
23+
] }

examples/custom_keypad/README.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Example showing how to implements a custom keypad.
2+
3+
```sh
4+
cargo run -p custom_keypad
5+
```
6+
7+
![](screenshot.png)

examples/custom_keypad/screenshot.png

38.9 KB
Loading

examples/custom_keypad/src/keypad.rs

+255
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
use eframe::egui::{self, pos2, vec2, Button, Ui, Vec2};
2+
3+
#[derive(Clone, Copy, Debug, Default, PartialEq)]
4+
enum Transition {
5+
#[default]
6+
None,
7+
CloseOnNextFrame,
8+
CloseImmediately,
9+
}
10+
11+
#[derive(Clone, Debug)]
12+
struct State {
13+
open: bool,
14+
closable: bool,
15+
close_on_next_frame: bool,
16+
start_pos: egui::Pos2,
17+
focus: Option<egui::Id>,
18+
events: Option<Vec<egui::Event>>,
19+
}
20+
21+
impl State {
22+
fn new() -> Self {
23+
Self {
24+
open: false,
25+
closable: false,
26+
close_on_next_frame: false,
27+
start_pos: pos2(100.0, 100.0),
28+
focus: None,
29+
events: None,
30+
}
31+
}
32+
33+
fn queue_char(&mut self, c: char) {
34+
let events = self.events.get_or_insert(vec![]);
35+
if let Some(key) = egui::Key::from_name(&c.to_string()) {
36+
events.push(egui::Event::Key {
37+
key,
38+
physical_key: Some(key),
39+
pressed: true,
40+
repeat: false,
41+
modifiers: Default::default(),
42+
});
43+
}
44+
events.push(egui::Event::Text(c.to_string()));
45+
}
46+
47+
fn queue_key(&mut self, key: egui::Key) {
48+
let events = self.events.get_or_insert(vec![]);
49+
events.push(egui::Event::Key {
50+
key,
51+
physical_key: Some(key),
52+
pressed: true,
53+
repeat: false,
54+
modifiers: Default::default(),
55+
});
56+
}
57+
}
58+
59+
impl Default for State {
60+
fn default() -> Self {
61+
Self::new()
62+
}
63+
}
64+
65+
/// A simple keypad widget.
66+
pub struct Keypad {
67+
id: egui::Id,
68+
}
69+
70+
impl Keypad {
71+
pub fn new() -> Self {
72+
Self {
73+
id: egui::Id::new("keypad"),
74+
}
75+
}
76+
77+
pub fn bump_events(&self, ctx: &egui::Context, raw_input: &mut egui::RawInput) {
78+
let events = ctx.memory_mut(|m| {
79+
m.data
80+
.get_temp_mut_or_default::<State>(self.id)
81+
.events
82+
.take()
83+
});
84+
if let Some(mut events) = events {
85+
events.append(&mut raw_input.events);
86+
raw_input.events = events;
87+
}
88+
}
89+
90+
fn buttons(ui: &mut Ui, state: &mut State) -> Transition {
91+
let mut trans = Transition::None;
92+
ui.vertical(|ui| {
93+
let window_margin = ui.spacing().window_margin;
94+
let size_1x1 = vec2(32.0, 26.0);
95+
let _size_1x2 = vec2(32.0, 52.0 + window_margin.top);
96+
let _size_2x1 = vec2(64.0 + window_margin.left, 26.0);
97+
98+
ui.spacing_mut().item_spacing = Vec2::splat(window_margin.left);
99+
100+
ui.horizontal(|ui| {
101+
if ui.add_sized(size_1x1, Button::new("1")).clicked() {
102+
state.queue_char('1');
103+
}
104+
if ui.add_sized(size_1x1, Button::new("2")).clicked() {
105+
state.queue_char('2');
106+
}
107+
if ui.add_sized(size_1x1, Button::new("3")).clicked() {
108+
state.queue_char('3');
109+
}
110+
if ui.add_sized(size_1x1, Button::new("⏮")).clicked() {
111+
state.queue_key(egui::Key::Home);
112+
}
113+
if ui.add_sized(size_1x1, Button::new("🔙")).clicked() {
114+
state.queue_key(egui::Key::Backspace);
115+
}
116+
});
117+
ui.horizontal(|ui| {
118+
if ui.add_sized(size_1x1, Button::new("4")).clicked() {
119+
state.queue_char('4');
120+
}
121+
if ui.add_sized(size_1x1, Button::new("5")).clicked() {
122+
state.queue_char('5');
123+
}
124+
if ui.add_sized(size_1x1, Button::new("6")).clicked() {
125+
state.queue_char('6');
126+
}
127+
if ui.add_sized(size_1x1, Button::new("⏭")).clicked() {
128+
state.queue_key(egui::Key::End);
129+
}
130+
if ui.add_sized(size_1x1, Button::new("⎆")).clicked() {
131+
state.queue_key(egui::Key::Enter);
132+
trans = Transition::CloseOnNextFrame;
133+
}
134+
});
135+
ui.horizontal(|ui| {
136+
if ui.add_sized(size_1x1, Button::new("7")).clicked() {
137+
state.queue_char('7');
138+
}
139+
if ui.add_sized(size_1x1, Button::new("8")).clicked() {
140+
state.queue_char('8');
141+
}
142+
if ui.add_sized(size_1x1, Button::new("9")).clicked() {
143+
state.queue_char('9');
144+
}
145+
if ui.add_sized(size_1x1, Button::new("⏶")).clicked() {
146+
state.queue_key(egui::Key::ArrowUp);
147+
}
148+
if ui.add_sized(size_1x1, Button::new("⌨")).clicked() {
149+
trans = Transition::CloseImmediately;
150+
}
151+
});
152+
ui.horizontal(|ui| {
153+
if ui.add_sized(size_1x1, Button::new("0")).clicked() {
154+
state.queue_char('0');
155+
}
156+
if ui.add_sized(size_1x1, Button::new(".")).clicked() {
157+
state.queue_char('.');
158+
}
159+
if ui.add_sized(size_1x1, Button::new("⏴")).clicked() {
160+
state.queue_key(egui::Key::ArrowLeft);
161+
}
162+
if ui.add_sized(size_1x1, Button::new("⏷")).clicked() {
163+
state.queue_key(egui::Key::ArrowDown);
164+
}
165+
if ui.add_sized(size_1x1, Button::new("⏵")).clicked() {
166+
state.queue_key(egui::Key::ArrowRight);
167+
}
168+
});
169+
});
170+
171+
trans
172+
}
173+
174+
pub fn show(&self, ctx: &egui::Context) {
175+
let (focus, mut state) = ctx.memory(|m| {
176+
(
177+
m.focus(),
178+
m.data.get_temp::<State>(self.id).unwrap_or_default(),
179+
)
180+
});
181+
182+
let mut is_first_show = false;
183+
if ctx.wants_keyboard_input() && state.focus != focus {
184+
let y = ctx.style().spacing.interact_size.y * 1.25;
185+
state.open = true;
186+
state.start_pos = ctx.input(|i| {
187+
i.pointer
188+
.hover_pos()
189+
.map_or(pos2(100.0, 100.0), |p| p + vec2(0.0, y))
190+
});
191+
state.focus = focus;
192+
is_first_show = true;
193+
}
194+
195+
if state.close_on_next_frame {
196+
state.open = false;
197+
state.close_on_next_frame = false;
198+
state.focus = None;
199+
}
200+
201+
let mut open = state.open;
202+
203+
let win = egui::Window::new("⌨ Keypad");
204+
let win = if is_first_show {
205+
win.current_pos(state.start_pos)
206+
} else {
207+
win.default_pos(state.start_pos)
208+
};
209+
let resp = win
210+
.movable(true)
211+
.resizable(false)
212+
.open(&mut open)
213+
.show(ctx, |ui| Self::buttons(ui, &mut state));
214+
215+
state.open = open;
216+
217+
if let Some(resp) = resp {
218+
match resp.inner {
219+
Some(Transition::CloseOnNextFrame) => {
220+
state.close_on_next_frame = true;
221+
}
222+
Some(Transition::CloseImmediately) => {
223+
state.open = false;
224+
state.focus = None;
225+
}
226+
_ => {}
227+
}
228+
if !state.closable && resp.response.hovered() {
229+
state.closable = true;
230+
}
231+
if state.closable && resp.response.clicked_elsewhere() {
232+
state.open = false;
233+
state.closable = false;
234+
state.focus = None;
235+
}
236+
if is_first_show {
237+
ctx.move_to_top(resp.response.layer_id);
238+
}
239+
}
240+
241+
if let (true, Some(focus)) = (state.open, state.focus) {
242+
ctx.memory_mut(|m| {
243+
m.request_focus(focus);
244+
});
245+
}
246+
247+
ctx.memory_mut(|m| m.data.insert_temp(self.id, state));
248+
}
249+
}
250+
251+
impl Default for Keypad {
252+
fn default() -> Self {
253+
Self::new()
254+
}
255+
}

0 commit comments

Comments
 (0)