Skip to content

Commit

Permalink
Add an input event history tracker to the egui demo lib (#4693)
Browse files Browse the repository at this point in the history
This is great for testing how e.g. keyboard events are seen by egui:


![image](https://github.com/emilk/egui/assets/1148717/b2187060-6533-439c-9f43-fc49b8213c28)


* Relevant: #3653
  • Loading branch information
emilk authored Jun 23, 2024
1 parent fb4c6cc commit 44f4971
Show file tree
Hide file tree
Showing 6 changed files with 193 additions and 17 deletions.
36 changes: 35 additions & 1 deletion crates/egui/src/data/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ pub const NUM_POINTER_BUTTONS: usize = 5;
/// NOTE: For cross-platform uses, ALT+SHIFT is a bad combination of modifiers
/// as on mac that is how you type special characters,
/// so those key presses are usually not reported to egui.
#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq)]
#[derive(Clone, Copy, Default, Hash, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Modifiers {
/// Either of the alt keys are down (option ⌥ on Mac).
Expand All @@ -567,6 +567,40 @@ pub struct Modifiers {
pub command: bool,
}

impl std::fmt::Debug for Modifiers {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.is_none() {
return write!(f, "Modifiers::NONE");
}

let Self {
alt,
ctrl,
shift,
mac_cmd,
command,
} = *self;

let mut debug = f.debug_struct("Modifiers");
if alt {
debug.field("alt", &true);
}
if ctrl {
debug.field("ctrl", &true);
}
if shift {
debug.field("shift", &true);
}
if mac_cmd {
debug.field("mac_cmd", &true);
}
if command {
debug.field("command", &true);
}
debug.finish()
}
}

impl Modifiers {
pub const NONE: Self = Self {
alt: false,
Expand Down
35 changes: 21 additions & 14 deletions crates/egui/src/data/key.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/// Keyboard keys.
///
/// egui usually uses logical keys, i.e. after applying any user keymap.
// TODO(emilk): split into `LogicalKey` and `PhysicalKey`
/// egui usually uses logical keys, i.e. after applying any user keymap.\
// See comment at the end of `Key { … }` on how to add new keys.
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum Key {
// ----------------------------------------------
// Commands:
ArrowDown,
ArrowLeft,
ArrowRight,
Expand Down Expand Up @@ -73,34 +75,34 @@ pub enum Key {

// ----------------------------------------------
// Digits:
/// Either from the main row or from the numpad.
/// `0` (from main row or numpad)
Num0,

/// Either from the main row or from the numpad.
/// `1` (from main row or numpad)
Num1,

/// Either from the main row or from the numpad.
/// `2` (from main row or numpad)
Num2,

/// Either from the main row or from the numpad.
/// `3` (from main row or numpad)
Num3,

/// Either from the main row or from the numpad.
/// `4` (from main row or numpad)
Num4,

/// Either from the main row or from the numpad.
/// `5` (from main row or numpad)
Num5,

/// Either from the main row or from the numpad.
/// `6` (from main row or numpad)
Num6,

/// Either from the main row or from the numpad.
/// `7` (from main row or numpad)
Num7,

/// Either from the main row or from the numpad.
/// `8` (from main row or numpad)
Num8,

/// Either from the main row or from the numpad.
/// `9` (from main row or numpad)
Num9,

// ----------------------------------------------
Expand Down Expand Up @@ -169,14 +171,19 @@ pub enum Key {
F33,
F34,
F35,
// When adding keys, remember to also update `crates/egui-winit/src/lib.rs`
// and [`Self::ALL`].
// When adding keys, remember to also update:
// * crates/egui-winit/src/lib.rs
// * Key::ALL
// * Key::from_name
// You should test that it works using the "Input Event History" window in the egui demo app.
// Make sure to test both natively and on web!
// Also: don't add keys last; add them to the group they best belong to.
}

impl Key {
/// All egui keys
pub const ALL: &'static [Self] = &[
// Commands:
Self::ArrowDown,
Self::ArrowLeft,
Self::ArrowRight,
Expand Down
1 change: 1 addition & 0 deletions crates/egui_demo_lib/src/demo/demo_app_windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ impl Default for Tests {
Self::from_demos(vec![
Box::<super::tests::CursorTest>::default(),
Box::<super::tests::IdTest>::default(),
Box::<super::tests::InputEventHistory>::default(),
Box::<super::tests::InputTest>::default(),
Box::<super::tests::LayoutTest>::default(),
Box::<super::tests::ManualLayoutTest>::default(),
Expand Down
132 changes: 132 additions & 0 deletions crates/egui_demo_lib/src/demo/tests/input_event_history.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
//! Show the history of all the input events to

struct HistoryEntry {
summary: String,
entries: Vec<String>,
}

#[derive(Default)]
struct DeduplicatedHistory {
history: std::collections::VecDeque<HistoryEntry>,
}

impl DeduplicatedHistory {
fn add(&mut self, summary: String, full: String) {
if let Some(entry) = self.history.back_mut() {
if entry.summary == summary {
entry.entries.push(full);
return;
}
}
self.history.push_back(HistoryEntry {
summary,
entries: vec![full],
});
if self.history.len() > 100 {
self.history.pop_front();
}
}

fn ui(&self, ui: &mut egui::Ui) {
egui::ScrollArea::vertical()
.auto_shrink(false)
.show(ui, |ui| {
ui.spacing_mut().item_spacing.y = 4.0;
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);

for HistoryEntry { summary, entries } in self.history.iter().rev() {
ui.horizontal(|ui| {
let response = ui.code(summary);
if entries.len() < 2 {
response
} else {
response | ui.weak(format!(" x{}", entries.len()))
}
})
.inner
.on_hover_ui(|ui| {
ui.spacing_mut().item_spacing.y = 4.0;
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
for entry in entries.iter().rev() {
ui.code(entry);
}
});
}
});
}
}

#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Default)]
pub struct InputEventHistory {
#[cfg_attr(feature = "serde", serde(skip))]
history: DeduplicatedHistory,

include_pointer_movements: bool,
}

impl crate::Demo for InputEventHistory {
fn name(&self) -> &'static str {
"Input Event History"
}

fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
egui::Window::new(self.name())
.default_width(800.0)
.open(open)
.resizable(true)
.scroll(false)
.show(ctx, |ui| {
use crate::View as _;
self.ui(ui);
});
}
}

impl crate::View for InputEventHistory {
fn ui(&mut self, ui: &mut egui::Ui) {
ui.input(|i| {
for event in &i.raw.events {
if !self.include_pointer_movements
&& matches!(
event,
egui::Event::PointerMoved(_) | egui::Event::MouseMoved(_)
)
{
continue;
}

let summary = event_summary(event);
let full = format!("{event:#?}");
self.history.add(summary, full);
}
});

ui.vertical_centered(|ui| {
ui.add(crate::egui_github_link_file!());
});

ui.label("Recent history of raw input events to egui.");
ui.label("Hover any entry for details.");
ui.checkbox(
&mut self.include_pointer_movements,
"Include pointer/mouse movements",
);

ui.add_space(8.0);

self.history.ui(ui);
}
}

fn event_summary(event: &egui::Event) -> String {
match event {
egui::Event::PointerMoved(_) => "PointerMoved { .. }".to_owned(),
egui::Event::MouseMoved(_) => "MouseMoved { .. }".to_owned(),
egui::Event::Zoom(_) => "Zoom { .. }".to_owned(),
egui::Event::Touch { phase, .. } => format!("Zoom {{ phase: {phase:?}, .. }}"),
egui::Event::MouseWheel { unit, .. } => format!("MouseWheel {{ unit: {unit:?}, .. }}"),

_ => format!("{event:?}"),
}
}
4 changes: 2 additions & 2 deletions crates/egui_demo_lib/src/demo/tests/input_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ impl crate::View for InputTest {
ui.checkbox(&mut self.late_interaction, "Use Response::interact");

ui.label("This tests how egui::Response reports events.\n\
The different buttons are sensitive to different things.\n\
Try interacting with them with any mouse button by clicking, double-clicking, triple-clicking, or dragging them.");
The different buttons are sensitive to different things.\n\
Try interacting with them with any mouse button by clicking, double-clicking, triple-clicking, or dragging them.");

ui.columns(4, |columns| {
for (i, (sense_name, sense)) in [
Expand Down
2 changes: 2 additions & 0 deletions crates/egui_demo_lib/src/demo/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod cursor_test;
mod id_test;
mod input_event_history;
mod input_test;
mod layout_test;
mod manual_layout_test;
Expand All @@ -8,6 +9,7 @@ mod window_resize_test;

pub use cursor_test::CursorTest;
pub use id_test::IdTest;
pub use input_event_history::InputEventHistory;
pub use input_test::InputTest;
pub use layout_test::LayoutTest;
pub use manual_layout_test::ManualLayoutTest;
Expand Down

0 comments on commit 44f4971

Please sign in to comment.