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:
parent
df9cd21248
commit
f74152988f
|
|
@ -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:?}"));
|
||||||
|
|
|
||||||
|
|
@ -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"));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue