Add an input event history tracker to the egui demo lib (#4693)
This is great for testing how e.g. keyboard events are seen by egui:  * Relevant: https://github.com/emilk/egui/issues/3653
This commit is contained in:
parent
fb4c6cc619
commit
44f49713eb
|
|
@ -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).
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -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,
|
||||
|
||||
// ----------------------------------------------
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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:?}"),
|
||||
}
|
||||
}
|
||||
|
|
@ -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 [
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
mod cursor_test;
|
||||
mod id_test;
|
||||
mod input_event_history;
|
||||
mod input_test;
|
||||
mod layout_test;
|
||||
mod manual_layout_test;
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue