From 01e14cc8af85b5040a70456e034864151b762662 Mon Sep 17 00:00:00 2001 From: Azorlogh Date: Mon, 19 Aug 2024 17:44:17 +0200 Subject: [PATCH] move click timings to options --- crates/egui/src/context.rs | 8 +++-- crates/egui/src/input_state.rs | 58 ++++++++++++++-------------------- crates/egui/src/interaction.rs | 9 +++--- crates/egui/src/memory.rs | 51 ++++++++++++++++++++++++++++++ crates/egui/src/ui.rs | 2 +- 5 files changed, 86 insertions(+), 42 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index d1a05db4bc2..c621948094a 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -489,7 +489,11 @@ impl ContextImpl { &viewport.prev_frame.widgets, &viewport.hits, &viewport.input, - self.memory.interaction_mut(), + self.memory + .interactions + .entry(self.memory.viewport_id) + .or_default(), + &self.memory.options, ); } @@ -2046,7 +2050,7 @@ impl ContextImpl { if repaint_needed { self.request_repaint(ended_viewport_id, RepaintCause::new()); - } else if let Some(delay) = viewport.input.wants_repaint_after() { + } else if let Some(delay) = viewport.input.wants_repaint_after(&self.memory.options) { self.request_repaint_after(delay, ended_viewport_id, RepaintCause::new()); } diff --git a/crates/egui/src/input_state.rs b/crates/egui/src/input_state.rs index 5749e16825f..83a68184908 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -1,6 +1,7 @@ mod touch_state; use crate::data::input::*; +use crate::Options; use crate::{emath::*, util::History}; use std::{ collections::{BTreeMap, HashSet}, @@ -11,20 +12,6 @@ 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 - -/// 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 - -/// 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 - /// Input state that egui updates each frame. /// /// You can access this with [`crate::Context::input`]. @@ -218,7 +205,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 @@ -422,7 +409,7 @@ impl InputState { /// The [`crate::Context`] will call this at the end of each frame to see if we need a repaint. /// /// Returns how long to wait for a repaint. - pub fn wants_repaint_after(&self) -> Option { + pub fn wants_repaint_after(&self, options: &Options) -> Option { if self.pointer.wants_repaint() || self.unprocessed_scroll_delta.abs().max_elem() > 0.2 || self.unprocessed_scroll_delta_for_zoom.abs() > 0.2 @@ -432,12 +419,12 @@ impl InputState { return Some(Duration::ZERO); } - if self.any_touches() && !self.pointer.is_decidedly_dragging() { + if self.any_touches() && !self.pointer.is_decidedly_dragging(options) { // 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 press_duration < options.max_click_duration { + let secs_until_menu = options.max_click_duration - press_duration; return Some(Duration::from_secs_f64(secs_until_menu)); } } @@ -663,8 +650,8 @@ impl InputState { /// to trigger a secondary click (context menu). /// /// Returns `true` only on one frame. - pub(crate) fn is_long_touch(&self) -> bool { - self.any_touches() && self.pointer.is_long_press() + pub(crate) fn is_long_touch(&self, options: &Options) -> bool { + self.any_touches() && self.pointer.is_long_press(options) } } @@ -822,8 +809,8 @@ impl Default for PointerState { impl PointerState { #[must_use] - pub(crate) fn begin_frame(mut self, time: f64, new: &RawInput) -> Self { - let was_decidedly_dragging = self.is_decidedly_dragging(); + pub(crate) fn begin_frame(mut self, time: f64, new: &RawInput, options: &Options) -> Self { + let was_decidedly_dragging = self.is_decidedly_dragging(options); self.time = time; @@ -845,7 +832,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) > options.max_click_dist; } self.pointer_events.push(PointerEvent::Moved(pos)); @@ -880,13 +867,13 @@ impl PointerState { }); } else { // Released - let clicked = self.could_any_button_be_click(); + let clicked = self.could_any_button_be_click(options); 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); + (time - self.last_click_time) < options.max_double_click_delay; + let triple_click = (time - self.last_last_click_time) + < (options.max_double_click_delay * 2.0); let count = if triple_click { 3 } else if double_click { @@ -955,7 +942,8 @@ impl PointerState { self.direction = self.pos_history.velocity().unwrap_or_default().normalized(); - self.started_decidedly_dragging = self.is_decidedly_dragging() && !was_decidedly_dragging; + self.started_decidedly_dragging = + self.is_decidedly_dragging(options) && !was_decidedly_dragging; self } @@ -1177,14 +1165,14 @@ impl PointerState { /// If the pointer button is down, will it register as a click when released? /// /// See also [`Self::is_decidedly_dragging`]. - pub fn could_any_button_be_click(&self) -> bool { + pub fn could_any_button_be_click(&self, options: &Options) -> bool { if self.any_down() || self.any_released() { if self.has_moved_too_much_for_a_click { return false; } 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 > options.max_click_duration { return false; } } @@ -1204,10 +1192,10 @@ impl PointerState { /// but NOT on the first frame it was started. /// /// See also [`Self::could_any_button_be_click`]. - pub fn is_decidedly_dragging(&self) -> bool { + pub fn is_decidedly_dragging(&self, options: &Options) -> bool { (self.any_down() || self.any_released()) && !self.any_pressed() - && !self.could_any_button_be_click() + && !self.could_any_button_be_click(options) && !self.any_click() } @@ -1215,12 +1203,12 @@ impl PointerState { /// to trigger a secondary click (context menu). /// /// Returns `true` only on one frame. - pub(crate) fn is_long_press(&self) -> bool { + pub(crate) fn is_long_press(&self, options: &Options) -> bool { self.started_decidedly_dragging && !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 > options.max_click_duration }) } diff --git a/crates/egui/src/interaction.rs b/crates/egui/src/interaction.rs index 06b19a0b719..2a1e3b6e4ba 100644 --- a/crates/egui/src/interaction.rs +++ b/crates/egui/src/interaction.rs @@ -112,6 +112,7 @@ pub(crate) fn interact( hits: &WidgetHits, input: &InputState, interaction: &mut InteractionState, + options: &Options, ) -> InteractionSnapshot { crate::profile_function!(); @@ -140,7 +141,7 @@ pub(crate) fn interact( interaction.potential_drag_id = None; } - if input.is_long_touch() { + if input.is_long_touch(options) { // We implement "press-and-hold for context menu" on touch screens here if let Some(widget) = interaction .potential_click_id @@ -172,7 +173,7 @@ pub(crate) fn interact( } PointerEvent::Released { click, button: _ } => { - if click.is_some() && !input.pointer.is_decidedly_dragging() { + if click.is_some() && !input.pointer.is_decidedly_dragging(options) { if let Some(widget) = interaction .potential_click_id .and_then(|id| widgets.get(id)) @@ -196,7 +197,7 @@ pub(crate) fn interact( // This widget is sensitive to both clicks and drags. // When the mouse first is pressed, it could be either, // so we postpone the decision until we know. - input.pointer.is_decidedly_dragging() + input.pointer.is_decidedly_dragging(options) } else { // This widget is just sensitive to drags, so we can mark it as dragged right away: widget.sense.drag @@ -209,7 +210,7 @@ pub(crate) fn interact( } } - if !input.pointer.could_any_button_be_click() { + if !input.pointer.could_any_button_be_click(options) { interaction.potential_click_id = None; } diff --git a/crates/egui/src/memory.rs b/crates/egui/src/memory.rs index 86aa3526799..110f4972374 100644 --- a/crates/egui/src/memory.rs +++ b/crates/egui/src/memory.rs @@ -266,6 +266,20 @@ pub struct Options { /// /// Default is `false`. pub reduce_texture_memory: bool, + + /// If the pointer moves more than this, it won't become a click (but it is still a drag) + 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. + pub max_click_duration: f64, + + /// The new pointer press must come within this many seconds from previous pointer release + pub max_double_click_delay: f64, } impl Default for Options { @@ -295,6 +309,10 @@ impl Default for Options { line_scroll_speed, scroll_zoom_speed: 1.0 / 200.0, reduce_texture_memory: false, + + max_click_dist: 6.0, + max_click_duration: 0.8, + max_double_click_delay: 0.3, } } } @@ -339,6 +357,10 @@ impl Options { line_scroll_speed, scroll_zoom_speed, reduce_texture_memory, + + max_click_dist, + max_click_duration, + max_double_click_delay, } = self; use crate::Widget as _; @@ -396,6 +418,35 @@ impl Options { ) .on_hover_text("How fast to zoom with ctrl/cmd + scroll"); }); + + ui.horizontal(|ui| { + ui.label("Max click distance"); + ui.add( + crate::DragValue::new(max_click_dist) + .range(0.0..=f32::INFINITY) + .speed(1.0), + ) + .on_hover_text("If the pointer moves more than this, it won't become a click (but it is still a drag)"); + }); + ui.horizontal(|ui| { + ui.label("Max click duration"); + ui.add( + crate::DragValue::new(max_click_duration) + .range(0.0..=f32::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.0..=f32::INFINITY) + .speed(0.1), + ) + .on_hover_text("The new pointer press must come within this many seconds from previous pointer release"); + }); + }); ui.vertical_centered(|ui| crate::reset_button(ui, self, "Reset all")); diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index b50dadbbff6..ee0b32142bf 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -2721,7 +2721,7 @@ fn register_rect(ui: &Ui, rect: Rect) { return; } - let is_clicking = ui.input(|i| i.pointer.could_any_button_be_click()); + let is_clicking = ui.memory(|m| ui.input(|i| i.pointer.could_any_button_be_click(&m.options))); #[cfg(feature = "callstack")] let callstack = crate::callstack::capture();