-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
eframe: Added
App::raw_input_hook
allows for the manipulation or fi…
…ltering 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)
- Loading branch information
Showing
8 changed files
with
382 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
[package] | ||
name = "custom_keypad" | ||
version = "0.1.0" | ||
authors = ["Varphone Wong <varphone@qq.com>"] | ||
license = "MIT OR Apache-2.0" | ||
edition = "2021" | ||
rust-version = "1.72" | ||
publish = false | ||
|
||
|
||
[dependencies] | ||
eframe = { workspace = true, features = [ | ||
"default", | ||
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO | ||
] } | ||
|
||
# For image support: | ||
egui_extras = { workspace = true, features = ["default", "image"] } | ||
|
||
env_logger = { version = "0.10", default-features = false, features = [ | ||
"auto-color", | ||
"humantime", | ||
] } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
Example showing how to implements a custom keypad. | ||
|
||
```sh | ||
cargo run -p custom_keypad | ||
``` | ||
|
||
![](screenshot.png) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,255 @@ | ||
use eframe::egui::{self, pos2, vec2, Button, Ui, Vec2}; | ||
|
||
#[derive(Clone, Copy, Debug, Default, PartialEq)] | ||
enum Transition { | ||
#[default] | ||
None, | ||
CloseOnNextFrame, | ||
CloseImmediately, | ||
} | ||
|
||
#[derive(Clone, Debug)] | ||
struct State { | ||
open: bool, | ||
closable: bool, | ||
close_on_next_frame: bool, | ||
start_pos: egui::Pos2, | ||
focus: Option<egui::Id>, | ||
events: Option<Vec<egui::Event>>, | ||
} | ||
|
||
impl State { | ||
fn new() -> Self { | ||
Self { | ||
open: false, | ||
closable: false, | ||
close_on_next_frame: false, | ||
start_pos: pos2(100.0, 100.0), | ||
focus: None, | ||
events: None, | ||
} | ||
} | ||
|
||
fn queue_char(&mut self, c: char) { | ||
let events = self.events.get_or_insert(vec![]); | ||
if let Some(key) = egui::Key::from_name(&c.to_string()) { | ||
events.push(egui::Event::Key { | ||
key, | ||
physical_key: Some(key), | ||
pressed: true, | ||
repeat: false, | ||
modifiers: Default::default(), | ||
}); | ||
} | ||
events.push(egui::Event::Text(c.to_string())); | ||
} | ||
|
||
fn queue_key(&mut self, key: egui::Key) { | ||
let events = self.events.get_or_insert(vec![]); | ||
events.push(egui::Event::Key { | ||
key, | ||
physical_key: Some(key), | ||
pressed: true, | ||
repeat: false, | ||
modifiers: Default::default(), | ||
}); | ||
} | ||
} | ||
|
||
impl Default for State { | ||
fn default() -> Self { | ||
Self::new() | ||
} | ||
} | ||
|
||
/// A simple keypad widget. | ||
pub struct Keypad { | ||
id: egui::Id, | ||
} | ||
|
||
impl Keypad { | ||
pub fn new() -> Self { | ||
Self { | ||
id: egui::Id::new("keypad"), | ||
} | ||
} | ||
|
||
pub fn bump_events(&self, ctx: &egui::Context, raw_input: &mut egui::RawInput) { | ||
let events = ctx.memory_mut(|m| { | ||
m.data | ||
.get_temp_mut_or_default::<State>(self.id) | ||
.events | ||
.take() | ||
}); | ||
if let Some(mut events) = events { | ||
events.append(&mut raw_input.events); | ||
raw_input.events = events; | ||
} | ||
} | ||
|
||
fn buttons(ui: &mut Ui, state: &mut State) -> Transition { | ||
let mut trans = Transition::None; | ||
ui.vertical(|ui| { | ||
let window_margin = ui.spacing().window_margin; | ||
let size_1x1 = vec2(32.0, 26.0); | ||
let _size_1x2 = vec2(32.0, 52.0 + window_margin.top); | ||
let _size_2x1 = vec2(64.0 + window_margin.left, 26.0); | ||
|
||
ui.spacing_mut().item_spacing = Vec2::splat(window_margin.left); | ||
|
||
ui.horizontal(|ui| { | ||
if ui.add_sized(size_1x1, Button::new("1")).clicked() { | ||
state.queue_char('1'); | ||
} | ||
if ui.add_sized(size_1x1, Button::new("2")).clicked() { | ||
state.queue_char('2'); | ||
} | ||
if ui.add_sized(size_1x1, Button::new("3")).clicked() { | ||
state.queue_char('3'); | ||
} | ||
if ui.add_sized(size_1x1, Button::new("⏮")).clicked() { | ||
state.queue_key(egui::Key::Home); | ||
} | ||
if ui.add_sized(size_1x1, Button::new("🔙")).clicked() { | ||
state.queue_key(egui::Key::Backspace); | ||
} | ||
}); | ||
ui.horizontal(|ui| { | ||
if ui.add_sized(size_1x1, Button::new("4")).clicked() { | ||
state.queue_char('4'); | ||
} | ||
if ui.add_sized(size_1x1, Button::new("5")).clicked() { | ||
state.queue_char('5'); | ||
} | ||
if ui.add_sized(size_1x1, Button::new("6")).clicked() { | ||
state.queue_char('6'); | ||
} | ||
if ui.add_sized(size_1x1, Button::new("⏭")).clicked() { | ||
state.queue_key(egui::Key::End); | ||
} | ||
if ui.add_sized(size_1x1, Button::new("⎆")).clicked() { | ||
state.queue_key(egui::Key::Enter); | ||
trans = Transition::CloseOnNextFrame; | ||
} | ||
}); | ||
ui.horizontal(|ui| { | ||
if ui.add_sized(size_1x1, Button::new("7")).clicked() { | ||
state.queue_char('7'); | ||
} | ||
if ui.add_sized(size_1x1, Button::new("8")).clicked() { | ||
state.queue_char('8'); | ||
} | ||
if ui.add_sized(size_1x1, Button::new("9")).clicked() { | ||
state.queue_char('9'); | ||
} | ||
if ui.add_sized(size_1x1, Button::new("⏶")).clicked() { | ||
state.queue_key(egui::Key::ArrowUp); | ||
} | ||
if ui.add_sized(size_1x1, Button::new("⌨")).clicked() { | ||
trans = Transition::CloseImmediately; | ||
} | ||
}); | ||
ui.horizontal(|ui| { | ||
if ui.add_sized(size_1x1, Button::new("0")).clicked() { | ||
state.queue_char('0'); | ||
} | ||
if ui.add_sized(size_1x1, Button::new(".")).clicked() { | ||
state.queue_char('.'); | ||
} | ||
if ui.add_sized(size_1x1, Button::new("⏴")).clicked() { | ||
state.queue_key(egui::Key::ArrowLeft); | ||
} | ||
if ui.add_sized(size_1x1, Button::new("⏷")).clicked() { | ||
state.queue_key(egui::Key::ArrowDown); | ||
} | ||
if ui.add_sized(size_1x1, Button::new("⏵")).clicked() { | ||
state.queue_key(egui::Key::ArrowRight); | ||
} | ||
}); | ||
}); | ||
|
||
trans | ||
} | ||
|
||
pub fn show(&self, ctx: &egui::Context) { | ||
let (focus, mut state) = ctx.memory(|m| { | ||
( | ||
m.focus(), | ||
m.data.get_temp::<State>(self.id).unwrap_or_default(), | ||
) | ||
}); | ||
|
||
let mut is_first_show = false; | ||
if ctx.wants_keyboard_input() && state.focus != focus { | ||
let y = ctx.style().spacing.interact_size.y * 1.25; | ||
state.open = true; | ||
state.start_pos = ctx.input(|i| { | ||
i.pointer | ||
.hover_pos() | ||
.map_or(pos2(100.0, 100.0), |p| p + vec2(0.0, y)) | ||
}); | ||
state.focus = focus; | ||
is_first_show = true; | ||
} | ||
|
||
if state.close_on_next_frame { | ||
state.open = false; | ||
state.close_on_next_frame = false; | ||
state.focus = None; | ||
} | ||
|
||
let mut open = state.open; | ||
|
||
let win = egui::Window::new("⌨ Keypad"); | ||
let win = if is_first_show { | ||
win.current_pos(state.start_pos) | ||
} else { | ||
win.default_pos(state.start_pos) | ||
}; | ||
let resp = win | ||
.movable(true) | ||
.resizable(false) | ||
.open(&mut open) | ||
.show(ctx, |ui| Self::buttons(ui, &mut state)); | ||
|
||
state.open = open; | ||
|
||
if let Some(resp) = resp { | ||
match resp.inner { | ||
Some(Transition::CloseOnNextFrame) => { | ||
state.close_on_next_frame = true; | ||
} | ||
Some(Transition::CloseImmediately) => { | ||
state.open = false; | ||
state.focus = None; | ||
} | ||
_ => {} | ||
} | ||
if !state.closable && resp.response.hovered() { | ||
state.closable = true; | ||
} | ||
if state.closable && resp.response.clicked_elsewhere() { | ||
state.open = false; | ||
state.closable = false; | ||
state.focus = None; | ||
} | ||
if is_first_show { | ||
ctx.move_to_top(resp.response.layer_id); | ||
} | ||
} | ||
|
||
if let (true, Some(focus)) = (state.open, state.focus) { | ||
ctx.memory_mut(|m| { | ||
m.request_focus(focus); | ||
}); | ||
} | ||
|
||
ctx.memory_mut(|m| m.data.insert_temp(self.id, state)); | ||
} | ||
} | ||
|
||
impl Default for Keypad { | ||
fn default() -> Self { | ||
Self::new() | ||
} | ||
} |
Oops, something went wrong.