Skip to content

Commit

Permalink
Add Options::input_options for click-delay etc (emilk#4942)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
girtsf authored and hacknus committed Oct 30, 2024
1 parent 74286c8 commit 8cbd8d1
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 22 deletions.
124 changes: 102 additions & 22 deletions crates/egui/src/input_state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -166,6 +223,11 @@ pub struct InputState {

/// In-order events received this frame
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 {
Expand Down Expand Up @@ -193,6 +255,7 @@ impl Default for InputState {
modifiers: Default::default(),
keys_down: Default::default(),
events: Default::default(),
input_options: Default::default(),
}
}
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(),
}
}

Expand Down Expand Up @@ -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));
}
}
Expand Down Expand Up @@ -800,6 +866,11 @@ pub struct PointerState {

/// All button events that occurred this frame
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 {
Expand All @@ -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();

Expand All @@ -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));
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
}
}
Expand Down Expand Up @@ -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
})
}

Expand Down Expand Up @@ -1274,6 +1352,7 @@ impl InputState {
modifiers,
keys_down,
events,
input_options: _,
} = self;

ui.style_mut()
Expand Down Expand Up @@ -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:?}"));
Expand Down
6 changes: 6 additions & 0 deletions crates/egui/src/memory/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -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,
}
}
Expand Down Expand Up @@ -338,6 +342,7 @@ impl Options {

line_scroll_speed,
scroll_zoom_speed,
input_options,
reduce_texture_memory,
} = self;

Expand Down Expand Up @@ -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"));
Expand Down

0 comments on commit 8cbd8d1

Please sign in to comment.