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
This commit is contained in:
Girts 2024-09-05 09:49:17 +03:00 committed by GitHub
parent df9cd21248
commit f74152988f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 108 additions and 22 deletions

View File

@ -17,19 +17,76 @@ pub use crate::Key;
pub use touch_state::MultiTouchInfo; pub use touch_state::MultiTouchInfo;
use touch_state::TouchState; use touch_state::TouchState;
/// If the pointer moves more than this, it won't become a click (but it is still a drag) /// Options for input state handling.
const MAX_CLICK_DIST: f32 = 6.0; // TODO(emilk): move to settings #[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 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, /// If a touch is held for this many seconds while still, then it will register as a
/// then it will register as a "long-touch" which is equivalent to a secondary click. /// "long-touch" which is equivalent to a secondary click.
/// ///
/// This is to support "press and hold for context menu" on touch screens. /// 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 pub max_click_duration: f64,
/// The new pointer press must come within this many seconds from previous pointer release /// 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 /// 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,
}
}
}
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. /// Input state that egui updates each frame.
/// ///
@ -166,6 +223,11 @@ pub struct InputState {
/// In-order events received this frame /// In-order events received this frame
pub events: Vec<Event>, pub events: Vec<Event>,
/// 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 { impl Default for InputState {
@ -193,6 +255,7 @@ impl Default for InputState {
modifiers: Default::default(), modifiers: Default::default(),
keys_down: Default::default(), keys_down: Default::default(),
events: Default::default(), events: Default::default(),
input_options: Default::default(),
} }
} }
} }
@ -224,7 +287,7 @@ impl InputState {
for touch_state in self.touch_states.values_mut() { for touch_state in self.touch_states.values_mut() {
touch_state.begin_frame(time, &new, self.pointer.interact_pos); 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 keys_down = self.keys_down;
let mut zoom_factor_delta = 1.0; // TODO(emilk): smoothing for zoom factor let mut zoom_factor_delta = 1.0; // TODO(emilk): smoothing for zoom factor
@ -366,6 +429,7 @@ impl InputState {
keys_down, keys_down,
events: new.events.clone(), // TODO(emilk): remove clone() and use raw.events events: new.events.clone(), // TODO(emilk): remove clone() and use raw.events
raw: new, 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. // 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 { if let Some(press_start_time) = self.pointer.press_start_time {
let press_duration = self.time - press_start_time; let press_duration = self.time - press_start_time;
if press_duration < MAX_CLICK_DURATION { if self.input_options.max_click_duration.is_finite()
let secs_until_menu = MAX_CLICK_DURATION - press_duration; && 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)); return Some(Duration::from_secs_f64(secs_until_menu));
} }
} }
@ -800,6 +866,11 @@ pub struct PointerState {
/// All button events that occurred this frame /// All button events that occurred this frame
pub(crate) pointer_events: Vec<PointerEvent>, pub(crate) pointer_events: Vec<PointerEvent>,
/// 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 { impl Default for PointerState {
@ -822,16 +893,23 @@ impl Default for PointerState {
last_last_click_time: std::f64::NEG_INFINITY, last_last_click_time: std::f64::NEG_INFINITY,
last_move_time: std::f64::NEG_INFINITY, last_move_time: std::f64::NEG_INFINITY,
pointer_events: vec![], pointer_events: vec![],
input_options: Default::default(),
} }
} }
} }
impl PointerState { impl PointerState {
#[must_use] #[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(); let was_decidedly_dragging = self.is_decidedly_dragging();
self.time = time; self.time = time;
self.input_options = options.input_options.clone();
self.pointer_events.clear(); self.pointer_events.clear();
@ -851,7 +929,7 @@ impl PointerState {
if let Some(press_origin) = self.press_origin { if let Some(press_origin) = self.press_origin {
self.has_moved_too_much_for_a_click |= 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)); self.pointer_events.push(PointerEvent::Moved(pos));
@ -889,10 +967,10 @@ impl PointerState {
let clicked = self.could_any_button_be_click(); let clicked = self.could_any_button_be_click();
let click = if clicked { let click = if clicked {
let double_click = let double_click = (time - self.last_click_time)
(time - self.last_click_time) < MAX_DOUBLE_CLICK_DELAY; < self.input_options.max_double_click_delay;
let triple_click = let triple_click = (time - self.last_last_click_time)
(time - self.last_last_click_time) < (MAX_DOUBLE_CLICK_DELAY * 2.0); < (self.input_options.max_double_click_delay * 2.0);
let count = if triple_click { let count = if triple_click {
3 3
} else if double_click { } else if double_click {
@ -1190,7 +1268,7 @@ impl PointerState {
} }
if let Some(press_start_time) = self.press_start_time { 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; return false;
} }
} }
@ -1226,7 +1304,7 @@ impl PointerState {
&& !self.has_moved_too_much_for_a_click && !self.has_moved_too_much_for_a_click
&& self.button_down(PointerButton::Primary) && self.button_down(PointerButton::Primary)
&& self.press_start_time.map_or(false, |press_start_time| { && 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, modifiers,
keys_down, keys_down,
events, events,
input_options: _,
} = self; } = self;
ui.style_mut() ui.style_mut()
@ -1359,6 +1438,7 @@ impl PointerState {
last_last_click_time, last_last_click_time,
pointer_events, pointer_events,
last_move_time, last_move_time,
input_options: _,
} = self; } = self;
ui.label(format!("latest_pos: {latest_pos:?}")); ui.label(format!("latest_pos: {latest_pos:?}"));

View File

@ -254,6 +254,9 @@ pub struct Options {
/// Controls the speed at which we zoom in when doing ctrl/cmd + scroll. /// Controls the speed at which we zoom in when doing ctrl/cmd + scroll.
pub scroll_zoom_speed: f32, 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 /// If `true`, `egui` will discard the loaded image data after
/// the texture is loaded onto the GPU to reduce memory usage. /// the texture is loaded onto the GPU to reduce memory usage.
/// ///
@ -294,6 +297,7 @@ impl Default for Options {
// Input: // Input:
line_scroll_speed, line_scroll_speed,
scroll_zoom_speed: 1.0 / 200.0, scroll_zoom_speed: 1.0 / 200.0,
input_options: Default::default(),
reduce_texture_memory: false, reduce_texture_memory: false,
} }
} }
@ -338,6 +342,7 @@ impl Options {
line_scroll_speed, line_scroll_speed,
scroll_zoom_speed, scroll_zoom_speed,
input_options,
reduce_texture_memory, reduce_texture_memory,
} = self; } = self;
@ -396,6 +401,7 @@ impl Options {
) )
.on_hover_text("How fast to zoom with ctrl/cmd + scroll"); .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")); ui.vertical_centered(|ui| crate::reset_button(ui, self, "Reset all"));