From f74152988fd4333e04cfde6a0e22fc44b3857d0e Mon Sep 17 00:00:00 2001 From: Girts Date: Thu, 5 Sep 2024 09:49:17 +0300 Subject: [PATCH] Add `Options::input_options` for click-delay etc (#4942) This takes 3 hardcoded constants from `input_state.rs` and puts them in a `InputOptions` struct that then gets added to `Options`. This allows adjusting these values at runtime, for example, to increase `MAX_CLICK_DIST` for touchscreen usage. * [x] I have followed the instructions in the PR template --- crates/egui/src/input_state/mod.rs | 124 ++++++++++++++++++++++++----- crates/egui/src/memory/mod.rs | 6 ++ 2 files changed, 108 insertions(+), 22 deletions(-) diff --git a/crates/egui/src/input_state/mod.rs b/crates/egui/src/input_state/mod.rs index 9e52031452a..8c2c5e79254 100644 --- a/crates/egui/src/input_state/mod.rs +++ b/crates/egui/src/input_state/mod.rs @@ -17,19 +17,76 @@ pub use crate::Key; pub use touch_state::MultiTouchInfo; use touch_state::TouchState; -/// If the pointer moves more than this, it won't become a click (but it is still a drag) -const MAX_CLICK_DIST: f32 = 6.0; // TODO(emilk): move to settings +/// Options for input state handling. +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct InputOptions { + /// After a pointer-down event, if the pointer moves more than this, it won't become a click. + pub max_click_dist: f32, -/// If the pointer is down for longer than this it will no longer register as a click. -/// -/// If a touch is held for this many seconds while still, -/// then it will register as a "long-touch" which is equivalent to a secondary click. -/// -/// This is to support "press and hold for context menu" on touch screens. -const MAX_CLICK_DURATION: f64 = 0.8; // TODO(emilk): move to settings + /// If the pointer is down for longer than this it will no longer register as a click. + /// + /// If a touch is held for this many seconds while still, then it will register as a + /// "long-touch" which is equivalent to a secondary click. + /// + /// This is to support "press and hold for context menu" on touch screens. + pub max_click_duration: f64, + + /// The new pointer press must come within this many seconds from previous pointer release + /// for double click (or when this value is doubled, triple click) to count. + pub max_double_click_delay: f64, +} + +impl Default for InputOptions { + fn default() -> Self { + Self { + max_click_dist: 6.0, + max_click_duration: 0.8, + max_double_click_delay: 0.3, + } + } +} -/// The new pointer press must come within this many seconds from previous pointer release -const MAX_DOUBLE_CLICK_DELAY: f64 = 0.3; // TODO(emilk): move to settings +impl InputOptions { + /// Show the options in the ui. + pub fn ui(&mut self, ui: &mut crate::Ui) { + let Self { + max_click_dist, + max_click_duration, + max_double_click_delay, + } = self; + crate::containers::CollapsingHeader::new("InputOptions") + .default_open(false) + .show(ui, |ui| { + ui.horizontal(|ui| { + ui.label("Max click distance"); + ui.add( + crate::DragValue::new(max_click_dist) + .range(0.0..=f32::INFINITY) + ) + .on_hover_text("If the pointer moves more than this, it won't become a click"); + }); + ui.horizontal(|ui| { + ui.label("Max click duration"); + ui.add( + crate::DragValue::new(max_click_duration) + .range(0.1..=f64::INFINITY) + .speed(0.1), + ) + .on_hover_text("If the pointer is down for longer than this it will no longer register as a click"); + }); + ui.horizontal(|ui| { + ui.label("Max double click delay"); + ui.add( + crate::DragValue::new(max_double_click_delay) + .range(0.01..=f64::INFINITY) + .speed(0.1), + ) + .on_hover_text("Max time interval for double click to count"); + }); + }); + } +} /// Input state that egui updates each frame. /// @@ -166,6 +223,11 @@ pub struct InputState { /// In-order events received this frame pub events: Vec, + + /// Input state management configuration. + /// + /// This gets copied from `egui::Options` at the start of each frame for convenience. + input_options: InputOptions, } impl Default for InputState { @@ -193,6 +255,7 @@ impl Default for InputState { modifiers: Default::default(), keys_down: Default::default(), events: Default::default(), + input_options: Default::default(), } } } @@ -224,7 +287,7 @@ impl InputState { for touch_state in self.touch_states.values_mut() { touch_state.begin_frame(time, &new, self.pointer.interact_pos); } - let pointer = self.pointer.begin_frame(time, &new); + let pointer = self.pointer.begin_frame(time, &new, options); let mut keys_down = self.keys_down; let mut zoom_factor_delta = 1.0; // TODO(emilk): smoothing for zoom factor @@ -366,6 +429,7 @@ impl InputState { keys_down, events: new.events.clone(), // TODO(emilk): remove clone() and use raw.events raw: new, + input_options: options.input_options.clone(), } } @@ -442,8 +506,10 @@ impl InputState { // We need to wake up and check for press-and-hold for the context menu. if let Some(press_start_time) = self.pointer.press_start_time { let press_duration = self.time - press_start_time; - if press_duration < MAX_CLICK_DURATION { - let secs_until_menu = MAX_CLICK_DURATION - press_duration; + if self.input_options.max_click_duration.is_finite() + && press_duration < self.input_options.max_click_duration + { + let secs_until_menu = self.input_options.max_click_duration - press_duration; return Some(Duration::from_secs_f64(secs_until_menu)); } } @@ -800,6 +866,11 @@ pub struct PointerState { /// All button events that occurred this frame pub(crate) pointer_events: Vec, + + /// Input state management configuration. + /// + /// This gets copied from `egui::Options` at the start of each frame for convenience. + input_options: InputOptions, } impl Default for PointerState { @@ -822,16 +893,23 @@ impl Default for PointerState { last_last_click_time: std::f64::NEG_INFINITY, last_move_time: std::f64::NEG_INFINITY, pointer_events: vec![], + input_options: Default::default(), } } } impl PointerState { #[must_use] - pub(crate) fn begin_frame(mut self, time: f64, new: &RawInput) -> Self { + pub(crate) fn begin_frame( + mut self, + time: f64, + new: &RawInput, + options: &crate::Options, + ) -> Self { let was_decidedly_dragging = self.is_decidedly_dragging(); self.time = time; + self.input_options = options.input_options.clone(); self.pointer_events.clear(); @@ -851,7 +929,7 @@ impl PointerState { if let Some(press_origin) = self.press_origin { self.has_moved_too_much_for_a_click |= - press_origin.distance(pos) > MAX_CLICK_DIST; + press_origin.distance(pos) > self.input_options.max_click_dist; } self.pointer_events.push(PointerEvent::Moved(pos)); @@ -889,10 +967,10 @@ impl PointerState { let clicked = self.could_any_button_be_click(); let click = if clicked { - let double_click = - (time - self.last_click_time) < MAX_DOUBLE_CLICK_DELAY; - let triple_click = - (time - self.last_last_click_time) < (MAX_DOUBLE_CLICK_DELAY * 2.0); + let double_click = (time - self.last_click_time) + < self.input_options.max_double_click_delay; + let triple_click = (time - self.last_last_click_time) + < (self.input_options.max_double_click_delay * 2.0); let count = if triple_click { 3 } else if double_click { @@ -1190,7 +1268,7 @@ impl PointerState { } if let Some(press_start_time) = self.press_start_time { - if self.time - press_start_time > MAX_CLICK_DURATION { + if self.time - press_start_time > self.input_options.max_click_duration { return false; } } @@ -1226,7 +1304,7 @@ impl PointerState { && !self.has_moved_too_much_for_a_click && self.button_down(PointerButton::Primary) && self.press_start_time.map_or(false, |press_start_time| { - self.time - press_start_time > MAX_CLICK_DURATION + self.time - press_start_time > self.input_options.max_click_duration }) } @@ -1274,6 +1352,7 @@ impl InputState { modifiers, keys_down, events, + input_options: _, } = self; ui.style_mut() @@ -1359,6 +1438,7 @@ impl PointerState { last_last_click_time, pointer_events, last_move_time, + input_options: _, } = self; ui.label(format!("latest_pos: {latest_pos:?}")); diff --git a/crates/egui/src/memory/mod.rs b/crates/egui/src/memory/mod.rs index 94916007901..f9fb6c9bae5 100644 --- a/crates/egui/src/memory/mod.rs +++ b/crates/egui/src/memory/mod.rs @@ -254,6 +254,9 @@ pub struct Options { /// Controls the speed at which we zoom in when doing ctrl/cmd + scroll. pub scroll_zoom_speed: f32, + /// Options related to input state handling. + pub input_options: crate::input_state::InputOptions, + /// If `true`, `egui` will discard the loaded image data after /// the texture is loaded onto the GPU to reduce memory usage. /// @@ -294,6 +297,7 @@ impl Default for Options { // Input: line_scroll_speed, scroll_zoom_speed: 1.0 / 200.0, + input_options: Default::default(), reduce_texture_memory: false, } } @@ -338,6 +342,7 @@ impl Options { line_scroll_speed, scroll_zoom_speed, + input_options, reduce_texture_memory, } = self; @@ -396,6 +401,7 @@ impl Options { ) .on_hover_text("How fast to zoom with ctrl/cmd + scroll"); }); + input_options.ui(ui); }); ui.vertical_centered(|ui| crate::reset_button(ui, self, "Reset all"));