On touch screens, press-and-hold equals a secondary click (#4195)

* Closes https://github.com/emilk/egui/issues/3444
* Closes https://github.com/emilk/egui/issues/865

On a touch screen, if you press down on a widget and hold for 0.6
seconds (`MAX_CLICK_DURATION`), it will now trigger a secondary click,
i.e. `Response::secondary_clicked` will be `true`. This means you can
now open context menus on touch screens.
This commit is contained in:
Emil Ernerfeldt 2024-03-20 11:49:17 +01:00 committed by GitHub
parent cd1ed73388
commit d449cb1d48
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 187 additions and 92 deletions

View File

@ -2,7 +2,6 @@
use std::{borrow::Cow, cell::RefCell, panic::Location, sync::Arc, time::Duration}; use std::{borrow::Cow, cell::RefCell, panic::Location, sync::Arc, time::Duration};
use ahash::HashMap;
use epaint::{ use epaint::{
emath::TSTransform, mutex::*, stats::*, text::Fonts, util::OrderedFloat, TessellationOptions, *, emath::TSTransform, mutex::*, stats::*, text::Fonts, util::OrderedFloat, TessellationOptions, *,
}; };
@ -421,18 +420,17 @@ impl ContextImpl {
// but the `screen_rect` is the most important part. // but the `screen_rect` is the most important part.
} }
} }
let pixels_per_point = self.memory.options.zoom_factor let native_pixels_per_point = new_raw_input
* new_raw_input .viewport()
.viewport() .native_pixels_per_point
.native_pixels_per_point .unwrap_or(1.0);
.unwrap_or(1.0); let pixels_per_point = self.memory.options.zoom_factor * native_pixels_per_point;
let all_viewport_ids: ViewportIdSet = self.all_viewport_ids(); let all_viewport_ids: ViewportIdSet = self.all_viewport_ids();
let viewport = self.viewports.entry(self.viewport_id()).or_default(); let viewport = self.viewports.entry(self.viewport_id()).or_default();
self.memory self.memory.begin_frame(&new_raw_input, &all_viewport_ids);
.begin_frame(&viewport.input, &new_raw_input, &all_viewport_ids);
viewport.input = std::mem::take(&mut viewport.input).begin_frame( viewport.input = std::mem::take(&mut viewport.input).begin_frame(
new_raw_input, new_raw_input,
@ -440,17 +438,12 @@ impl ContextImpl {
pixels_per_point, pixels_per_point,
); );
viewport.frame_state.begin_frame(&viewport.input); let screen_rect = viewport.input.screen_rect;
viewport.frame_state.begin_frame(screen_rect);
{ {
let area_order: HashMap<LayerId, usize> = self let area_order = self.memory.areas().order_map();
.memory
.areas()
.order()
.iter()
.enumerate()
.map(|(i, id)| (*id, i))
.collect();
let mut layers: Vec<LayerId> = viewport.widgets_prev_frame.layer_ids().collect(); let mut layers: Vec<LayerId> = viewport.widgets_prev_frame.layer_ids().collect();
@ -488,7 +481,6 @@ impl ContextImpl {
} }
// Ensure we register the background area so panels and background ui can catch clicks: // Ensure we register the background area so panels and background ui can catch clicks:
let screen_rect = viewport.input.screen_rect();
self.memory.areas_mut().set_state( self.memory.areas_mut().set_state(
LayerId::background(), LayerId::background(),
containers::area::State { containers::area::State {
@ -1106,6 +1098,7 @@ impl Context {
highlighted, highlighted,
clicked: false, clicked: false,
fake_primary_click: false, fake_primary_click: false,
long_touched: false,
drag_started: false, drag_started: false,
dragged: false, dragged: false,
drag_stopped: false, drag_stopped: false,
@ -1141,6 +1134,10 @@ impl Context {
res.fake_primary_click = true; res.fake_primary_click = true;
} }
if enabled && sense.click && Some(id) == viewport.interact_widgets.long_touched {
res.long_touched = true;
}
let interaction = memory.interaction(); let interaction = memory.interaction();
res.is_pointer_button_down_on = interaction.potential_click_id == Some(id) res.is_pointer_button_down_on = interaction.potential_click_id == Some(id)
@ -1168,7 +1165,8 @@ impl Context {
// is_pointer_button_down_on is false when released, but we want interact_pointer_pos // is_pointer_button_down_on is false when released, but we want interact_pointer_pos
// to still work. // to still work.
let is_interacted_with = res.is_pointer_button_down_on || clicked || res.drag_stopped; let is_interacted_with =
res.is_pointer_button_down_on || res.long_touched || clicked || res.drag_stopped;
if is_interacted_with { if is_interacted_with {
res.interact_pointer_pos = input.pointer.interact_pos(); res.interact_pointer_pos = input.pointer.interact_pos();
if let (Some(transform), Some(pos)) = ( if let (Some(transform), Some(pos)) = (
@ -1179,7 +1177,7 @@ impl Context {
} }
} }
if input.pointer.any_down() && !res.is_pointer_button_down_on { if input.pointer.any_down() && !is_interacted_with {
// We don't hover widgets while interacting with *other* widgets: // We don't hover widgets while interacting with *other* widgets:
res.hovered = false; res.hovered = false;
} }
@ -1847,6 +1845,7 @@ impl Context {
let interact_widgets = self.write(|ctx| ctx.viewport().interact_widgets.clone()); let interact_widgets = self.write(|ctx| ctx.viewport().interact_widgets.clone());
let InteractionSnapshot { let InteractionSnapshot {
clicked, clicked,
long_touched: _,
drag_started: _, drag_started: _,
dragged, dragged,
drag_stopped: _, drag_stopped: _,
@ -1956,7 +1955,10 @@ impl ContextImpl {
}) })
.collect() .collect()
}; };
let focus_id = self.memory.focus().map_or(root_id, |id| id.accesskit_id()); let focus_id = self
.memory
.focused()
.map_or(root_id, |id| id.accesskit_id());
platform_output.accesskit_update = Some(accesskit::TreeUpdate { platform_output.accesskit_update = Some(accesskit::TreeUpdate {
nodes, nodes,
tree: Some(accesskit::Tree::new(root_id)), tree: Some(accesskit::Tree::new(root_id)),
@ -2221,7 +2223,7 @@ impl Context {
/// If `true`, egui is currently listening on text input (e.g. typing text in a [`TextEdit`]). /// If `true`, egui is currently listening on text input (e.g. typing text in a [`TextEdit`]).
pub fn wants_keyboard_input(&self) -> bool { pub fn wants_keyboard_input(&self) -> bool {
self.memory(|m| m.interaction().focus.focused().is_some()) self.memory(|m| m.focused().is_some())
} }
/// Highlight this widget, to make it look like it is hovered, even if it isn't. /// Highlight this widget, to make it look like it is hovered, even if it isn't.
@ -2481,7 +2483,7 @@ impl Context {
.on_hover_text("Is egui currently listening for text input?"); .on_hover_text("Is egui currently listening for text input?");
ui.label(format!( ui.label(format!(
"Keyboard focus widget: {}", "Keyboard focus widget: {}",
self.memory(|m| m.interaction().focus.focused()) self.memory(|m| m.focused())
.as_ref() .as_ref()
.map(Id::short_debug_format) .map(Id::short_debug_format)
.unwrap_or_default() .unwrap_or_default()

View File

@ -75,7 +75,7 @@ impl Default for FrameState {
} }
impl FrameState { impl FrameState {
pub(crate) fn begin_frame(&mut self, input: &InputState) { pub(crate) fn begin_frame(&mut self, screen_rect: Rect) {
crate::profile_function!(); crate::profile_function!();
let Self { let Self {
used_ids, used_ids,
@ -94,8 +94,8 @@ impl FrameState {
} = self; } = self;
used_ids.clear(); used_ids.clear();
*available_rect = input.screen_rect(); *available_rect = screen_rect;
*unused_rect = input.screen_rect(); *unused_rect = screen_rect;
*used_by_panels = Rect::NOTHING; *used_by_panels = Rect::NOTHING;
*tooltip_state = None; *tooltip_state = None;
*scroll_target = [None, None]; *scroll_target = [None, None];

View File

@ -11,8 +11,13 @@ use touch_state::TouchState;
/// If the pointer moves more than this, it won't become a click (but it is still a drag) /// 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 const MAX_CLICK_DIST: f32 = 6.0; // TODO(emilk): move to settings
/// If the pointer is down for longer than this, it won't become a click (but it is still a drag) /// If the pointer is down for longer than this it will no longer register as a click.
const MAX_CLICK_DURATION: f64 = 0.6; // TODO(emilk): move to settings ///
/// 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 /// 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 const MAX_DOUBLE_CLICK_DELAY: f64 = 0.3; // TODO(emilk): move to settings
@ -544,6 +549,14 @@ impl InputState {
.cloned() .cloned()
.collect() .collect()
} }
/// A long press is something we detect on touch screens
/// 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()
}
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@ -651,6 +664,8 @@ pub struct PointerState {
pub(crate) has_moved_too_much_for_a_click: bool, pub(crate) has_moved_too_much_for_a_click: bool,
/// Did [`Self::is_decidedly_dragging`] go from `false` to `true` this frame? /// Did [`Self::is_decidedly_dragging`] go from `false` to `true` this frame?
///
/// This could also be the trigger point for a long-touch.
pub(crate) started_decidedly_dragging: bool, pub(crate) started_decidedly_dragging: bool,
/// When did the pointer get click last? /// When did the pointer get click last?
@ -751,6 +766,7 @@ impl PointerState {
button, button,
}); });
} else { } else {
// Released
let clicked = self.could_any_button_be_click(); let clicked = self.could_any_button_be_click();
let click = if clicked { let click = if clicked {
@ -1027,21 +1043,21 @@ impl PointerState {
/// ///
/// See also [`Self::is_decidedly_dragging`]. /// See also [`Self::is_decidedly_dragging`].
pub fn could_any_button_be_click(&self) -> bool { pub fn could_any_button_be_click(&self) -> bool {
if !self.any_down() { if self.any_down() || self.any_released() {
return false; if self.has_moved_too_much_for_a_click {
}
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 {
return false; return false;
} }
}
true if let Some(press_start_time) = self.press_start_time {
if self.time - press_start_time > MAX_CLICK_DURATION {
return false;
}
}
true
} else {
false
}
} }
/// Just because the mouse is down doesn't mean we are dragging. /// Just because the mouse is down doesn't mean we are dragging.
@ -1060,6 +1076,19 @@ impl PointerState {
&& !self.any_click() && !self.any_click()
} }
/// A long press is something we detect on touch screens
/// to trigger a secondary click (context menu).
///
/// Returns `true` only on one frame.
pub(crate) fn is_long_press(&self) -> 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
})
}
/// Is the primary button currently down? /// Is the primary button currently down?
#[inline(always)] #[inline(always)]
pub fn primary_down(&self) -> bool { pub fn primary_down(&self) -> bool {

View File

@ -14,6 +14,10 @@ pub struct InteractionSnapshot {
/// The widget that got clicked this frame. /// The widget that got clicked this frame.
pub clicked: Option<Id>, pub clicked: Option<Id>,
/// This widget was long-pressed on a touch screen,
/// so trigger a secondary click on it (context menu).
pub long_touched: Option<Id>,
/// Drag started on this widget this frame. /// Drag started on this widget this frame.
/// ///
/// This will also be found in `dragged` this frame. /// This will also be found in `dragged` this frame.
@ -56,6 +60,7 @@ impl InteractionSnapshot {
pub fn ui(&self, ui: &mut crate::Ui) { pub fn ui(&self, ui: &mut crate::Ui) {
let Self { let Self {
clicked, clicked,
long_touched,
drag_started, drag_started,
dragged, dragged,
drag_stopped, drag_stopped,
@ -74,6 +79,10 @@ impl InteractionSnapshot {
id_ui(ui, clicked); id_ui(ui, clicked);
ui.end_row(); ui.end_row();
ui.label("long_touched");
id_ui(ui, long_touched);
ui.end_row();
ui.label("drag_started"); ui.label("drag_started");
id_ui(ui, drag_started); id_ui(ui, drag_started);
ui.end_row(); ui.end_row();
@ -123,6 +132,21 @@ pub(crate) fn interact(
let mut clicked = None; let mut clicked = None;
let mut dragged = prev_snapshot.dragged; let mut dragged = prev_snapshot.dragged;
let mut long_touched = None;
if input.is_long_touch() {
// We implement "press-and-hold for context menu" on touch screens here
if let Some(widget) = interaction
.potential_click_id
.and_then(|id| widgets.get(id))
{
dragged = None;
clicked = Some(widget.id);
long_touched = Some(widget.id);
interaction.potential_click_id = None;
interaction.potential_drag_id = None;
}
}
// Note: in the current code a press-release in the same frame is NOT considered a drag. // Note: in the current code a press-release in the same frame is NOT considered a drag.
for pointer_event in &input.pointer.pointer_events { for pointer_event in &input.pointer.pointer_events {
@ -142,7 +166,7 @@ pub(crate) fn interact(
} }
PointerEvent::Released { click, button: _ } => { PointerEvent::Released { click, button: _ } => {
if click.is_some() { if click.is_some() && !input.pointer.is_decidedly_dragging() {
if let Some(widget) = interaction if let Some(widget) = interaction
.potential_click_id .potential_click_id
.and_then(|id| widgets.get(id)) .and_then(|id| widgets.get(id))
@ -179,6 +203,15 @@ pub(crate) fn interact(
} }
} }
if !input.pointer.could_any_button_be_click() {
interaction.potential_click_id = None;
}
if !input.pointer.any_down() || input.pointer.latest_pos().is_none() {
interaction.potential_click_id = None;
interaction.potential_drag_id = None;
}
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
let drag_changed = dragged != prev_snapshot.dragged; let drag_changed = dragged != prev_snapshot.dragged;
@ -201,9 +234,14 @@ pub(crate) fn interact(
.map(|w| w.id) .map(|w| w.id)
.collect(); .collect();
let hovered = if clicked.is_some() || dragged.is_some() { let hovered = if clicked.is_some() || dragged.is_some() || long_touched.is_some() {
// If currently clicking or dragging, nothing else is hovered. // If currently clicking or dragging, only that and nothing else is hovered.
clicked.iter().chain(&dragged).copied().collect() clicked
.iter()
.chain(&dragged)
.chain(&long_touched)
.copied()
.collect()
} else if hits.click.is_some() || hits.drag.is_some() { } else if hits.click.is_some() || hits.drag.is_some() {
// We are hovering over an interactive widget or two. // We are hovering over an interactive widget or two.
hits.click.iter().chain(&hits.drag).map(|w| w.id).collect() hits.click.iter().chain(&hits.drag).map(|w| w.id).collect()
@ -220,6 +258,7 @@ pub(crate) fn interact(
InteractionSnapshot { InteractionSnapshot {
clicked, clicked,
long_touched,
drag_started, drag_started,
dragged, dragged,
drag_stopped, drag_stopped,

View File

@ -194,7 +194,6 @@ impl Widget for &memory::InteractionState {
let memory::InteractionState { let memory::InteractionState {
potential_click_id, potential_click_id,
potential_drag_id, potential_drag_id,
focus: _,
} = self; } = self;
ui.vertical(|ui| { ui.vertical(|ui| {

View File

@ -4,7 +4,7 @@ use ahash::HashMap;
use epaint::emath::TSTransform; use epaint::emath::TSTransform;
use crate::{ use crate::{
area, vec2, EventFilter, Id, IdMap, LayerId, Order, Pos2, Rangef, Rect, Style, Vec2, area, vec2, EventFilter, Id, IdMap, LayerId, Order, Pos2, Rangef, RawInput, Rect, Style, Vec2,
ViewportId, ViewportIdMap, ViewportIdSet, ViewportId, ViewportIdMap, ViewportIdSet,
}; };
@ -95,6 +95,9 @@ pub struct Memory {
#[cfg_attr(feature = "persistence", serde(skip))] #[cfg_attr(feature = "persistence", serde(skip))]
pub(crate) interactions: ViewportIdMap<InteractionState>, pub(crate) interactions: ViewportIdMap<InteractionState>,
#[cfg_attr(feature = "persistence", serde(skip))]
pub(crate) focus: ViewportIdMap<Focus>,
} }
impl Default for Memory { impl Default for Memory {
@ -105,6 +108,7 @@ impl Default for Memory {
caches: Default::default(), caches: Default::default(),
new_font_definitions: Default::default(), new_font_definitions: Default::default(),
interactions: Default::default(), interactions: Default::default(),
focus: Default::default(),
viewport_id: Default::default(), viewport_id: Default::default(),
areas: Default::default(), areas: Default::default(),
layer_transforms: Default::default(), layer_transforms: Default::default(),
@ -308,8 +312,6 @@ pub(crate) struct InteractionState {
/// as that can only happen after the mouse has moved a bit /// as that can only happen after the mouse has moved a bit
/// (at least if the widget is interesated in both clicks and drags). /// (at least if the widget is interesated in both clicks and drags).
pub potential_drag_id: Option<Id>, pub potential_drag_id: Option<Id>,
pub focus: Focus,
} }
/// Keeps tracks of what widget has keyboard focus /// Keeps tracks of what widget has keyboard focus
@ -362,24 +364,6 @@ impl InteractionState {
pub fn is_using_pointer(&self) -> bool { pub fn is_using_pointer(&self) -> bool {
self.potential_click_id.is_some() || self.potential_drag_id.is_some() self.potential_click_id.is_some() || self.potential_drag_id.is_some()
} }
fn begin_frame(
&mut self,
prev_input: &crate::input_state::InputState,
new_input: &crate::data::input::RawInput,
) {
if !prev_input.pointer.could_any_button_be_click() {
self.potential_click_id = None;
}
if !prev_input.pointer.any_down() || prev_input.pointer.latest_pos().is_none() {
// pointer button was not down last frame
self.potential_click_id = None;
self.potential_drag_id = None;
}
self.focus.begin_frame(new_input);
}
} }
impl Focus { impl Focus {
@ -603,30 +587,29 @@ impl Focus {
} }
impl Memory { impl Memory {
pub(crate) fn begin_frame( pub(crate) fn begin_frame(&mut self, new_raw_input: &RawInput, viewports: &ViewportIdSet) {
&mut self,
prev_input: &crate::input_state::InputState,
new_input: &crate::data::input::RawInput,
viewports: &ViewportIdSet,
) {
crate::profile_function!(); crate::profile_function!();
self.viewport_id = new_raw_input.viewport_id;
// Cleanup // Cleanup
self.interactions.retain(|id, _| viewports.contains(id)); self.interactions.retain(|id, _| viewports.contains(id));
self.areas.retain(|id, _| viewports.contains(id)); self.areas.retain(|id, _| viewports.contains(id));
self.viewport_id = new_input.viewport_id; self.areas.entry(self.viewport_id).or_default();
self.interactions
// self.interactions is handled elsewhere
self.focus
.entry(self.viewport_id) .entry(self.viewport_id)
.or_default() .or_default()
.begin_frame(prev_input, new_input); .begin_frame(new_raw_input);
self.areas.entry(self.viewport_id).or_default();
} }
pub(crate) fn end_frame(&mut self, used_ids: &IdMap<Rect>) { pub(crate) fn end_frame(&mut self, used_ids: &IdMap<Rect>) {
self.caches.update(); self.caches.update();
self.areas_mut().end_frame(); self.areas_mut().end_frame();
self.interaction_mut().focus.end_frame(used_ids); self.focus_mut().end_frame(used_ids);
} }
pub(crate) fn set_viewport_id(&mut self, viewport_id: ViewportId) { pub(crate) fn set_viewport_id(&mut self, viewport_id: ViewportId) {
@ -656,7 +639,7 @@ impl Memory {
} }
pub(crate) fn had_focus_last_frame(&self, id: Id) -> bool { pub(crate) fn had_focus_last_frame(&self, id: Id) -> bool {
self.interaction().focus.id_previous_frame == Some(id) self.focus().id_previous_frame == Some(id)
} }
/// True if the given widget had keyboard focus last frame, but not this one. /// True if the given widget had keyboard focus last frame, but not this one.
@ -677,12 +660,12 @@ impl Memory {
/// from the window and back. /// from the window and back.
#[inline(always)] #[inline(always)]
pub fn has_focus(&self, id: Id) -> bool { pub fn has_focus(&self, id: Id) -> bool {
self.interaction().focus.focused() == Some(id) self.focused() == Some(id)
} }
/// Which widget has keyboard focus? /// Which widget has keyboard focus?
pub fn focus(&self) -> Option<Id> { pub fn focused(&self) -> Option<Id> {
self.interaction().focus.focused() self.focus().focused()
} }
/// Set an event filter for a widget. /// Set an event filter for a widget.
@ -693,7 +676,7 @@ impl Memory {
/// You must first give focus to the widget before calling this. /// You must first give focus to the widget before calling this.
pub fn set_focus_lock_filter(&mut self, id: Id, event_filter: EventFilter) { pub fn set_focus_lock_filter(&mut self, id: Id, event_filter: EventFilter) {
if self.had_focus_last_frame(id) && self.has_focus(id) { if self.had_focus_last_frame(id) && self.has_focus(id) {
if let Some(focused) = &mut self.interaction_mut().focus.focused_widget { if let Some(focused) = &mut self.focus_mut().focused_widget {
if focused.id == id { if focused.id == id {
focused.filter = event_filter; focused.filter = event_filter;
} }
@ -705,16 +688,16 @@ impl Memory {
/// See also [`crate::Response::request_focus`]. /// See also [`crate::Response::request_focus`].
#[inline(always)] #[inline(always)]
pub fn request_focus(&mut self, id: Id) { pub fn request_focus(&mut self, id: Id) {
self.interaction_mut().focus.focused_widget = Some(FocusWidget::new(id)); self.focus_mut().focused_widget = Some(FocusWidget::new(id));
} }
/// Surrender keyboard focus for a specific widget. /// Surrender keyboard focus for a specific widget.
/// See also [`crate::Response::surrender_focus`]. /// See also [`crate::Response::surrender_focus`].
#[inline(always)] #[inline(always)]
pub fn surrender_focus(&mut self, id: Id) { pub fn surrender_focus(&mut self, id: Id) {
let interaction = self.interaction_mut(); let focus = self.focus_mut();
if interaction.focus.focused() == Some(id) { if focus.focused() == Some(id) {
interaction.focus.focused_widget = None; focus.focused_widget = None;
} }
} }
@ -727,13 +710,13 @@ impl Memory {
/// and rendered correctly in a single frame. /// and rendered correctly in a single frame.
#[inline(always)] #[inline(always)]
pub fn interested_in_focus(&mut self, id: Id) { pub fn interested_in_focus(&mut self, id: Id) {
self.interaction_mut().focus.interested_in_focus(id); self.focus_mut().interested_in_focus(id);
} }
/// Stop editing of active [`TextEdit`](crate::TextEdit) (if any). /// Stop editing of active [`TextEdit`](crate::TextEdit) (if any).
#[inline(always)] #[inline(always)]
pub fn stop_text_input(&mut self) { pub fn stop_text_input(&mut self) {
self.interaction_mut().focus.focused_widget = None; self.focus_mut().focused_widget = None;
} }
/// Is any widget being dragged? /// Is any widget being dragged?
@ -813,6 +796,16 @@ impl Memory {
pub(crate) fn interaction_mut(&mut self) -> &mut InteractionState { pub(crate) fn interaction_mut(&mut self) -> &mut InteractionState {
self.interactions.entry(self.viewport_id).or_default() self.interactions.entry(self.viewport_id).or_default()
} }
pub(crate) fn focus(&self) -> &Focus {
self.focus
.get(&self.viewport_id)
.expect("Failed to get focus")
}
pub(crate) fn focus_mut(&mut self) -> &mut Focus {
self.focus.entry(self.viewport_id).or_default()
}
} }
/// ## Popups /// ## Popups
@ -908,6 +901,15 @@ impl Areas {
&self.order &self.order
} }
/// For each layer, which order is it in [`Self::order`]?
pub(crate) fn order_map(&self) -> HashMap<LayerId, usize> {
self.order
.iter()
.enumerate()
.map(|(i, id)| (*id, i))
.collect()
}
pub(crate) fn set_state(&mut self, layer_id: LayerId, state: area::State) { pub(crate) fn set_state(&mut self, layer_id: LayerId, state: area::State) {
self.visible_current_frame.insert(layer_id); self.visible_current_frame.insert(layer_id);
self.areas.insert(layer_id.id, state); self.areas.insert(layer_id.id, state);

View File

@ -89,6 +89,10 @@ pub struct Response {
#[doc(hidden)] #[doc(hidden)]
pub fake_primary_click: bool, pub fake_primary_click: bool,
/// This widget was long-pressed on a touch screen to simulate a secondary click.
#[doc(hidden)]
pub long_touched: bool,
/// The widget started being dragged this frame. /// The widget started being dragged this frame.
#[doc(hidden)] #[doc(hidden)]
pub drag_started: bool, pub drag_started: bool,
@ -142,15 +146,28 @@ impl Response {
/// This will NOT return true if the widget was "clicked" via /// This will NOT return true if the widget was "clicked" via
/// some accessibility integration, or if the widget had keyboard focus and the /// some accessibility integration, or if the widget had keyboard focus and the
/// user pressed Space/Enter. For that, use [`Self::clicked`] instead. /// user pressed Space/Enter. For that, use [`Self::clicked`] instead.
///
/// This will likewise ignore the press-and-hold action on touch screens.
/// Use [`Self::secondary_clicked`] instead to also detect that.
#[inline] #[inline]
pub fn clicked_by(&self, button: PointerButton) -> bool { pub fn clicked_by(&self, button: PointerButton) -> bool {
self.clicked && self.ctx.input(|i| i.pointer.button_clicked(button)) self.clicked && self.ctx.input(|i| i.pointer.button_clicked(button))
} }
/// Returns true if this widget was clicked this frame by the secondary mouse button (e.g. the right mouse button). /// Returns true if this widget was clicked this frame by the secondary mouse button (e.g. the right mouse button).
///
/// This also returns true if the widget was pressed-and-held on a touch screen.
#[inline] #[inline]
pub fn secondary_clicked(&self) -> bool { pub fn secondary_clicked(&self) -> bool {
self.clicked_by(PointerButton::Secondary) self.long_touched || self.clicked_by(PointerButton::Secondary)
}
/// Was this long-pressed on a touch screen?
///
/// Usually you want to check [`Self::secondary_clicked`] instead.
#[inline]
pub fn long_touched(&self) -> bool {
self.long_touched
} }
/// Returns true if this widget was clicked this frame by the middle mouse button. /// Returns true if this widget was clicked this frame by the middle mouse button.
@ -933,6 +950,7 @@ impl Response {
highlighted: self.highlighted || other.highlighted, highlighted: self.highlighted || other.highlighted,
clicked: self.clicked || other.clicked, clicked: self.clicked || other.clicked,
fake_primary_click: self.fake_primary_click || other.fake_primary_click, fake_primary_click: self.fake_primary_click || other.fake_primary_click,
long_touched: self.long_touched || other.long_touched,
drag_started: self.drag_started || other.drag_started, drag_started: self.drag_started || other.drag_started,
dragged: self.dragged || other.dragged, dragged: self.dragged || other.dragged,
drag_stopped: self.drag_stopped || other.drag_stopped, drag_stopped: self.drag_stopped || other.drag_stopped,

View File

@ -80,6 +80,8 @@ impl super::View for ContextMenus {
ui.label("Right-click plot to edit it!"); ui.label("Right-click plot to edit it!");
ui.horizontal(|ui| { ui.horizontal(|ui| {
self.example_plot(ui).context_menu(|ui| { self.example_plot(ui).context_menu(|ui| {
ui.set_min_width(220.0);
ui.menu_button("Plot", |ui| { ui.menu_button("Plot", |ui| {
if ui.radio_value(&mut self.plot, Plot::Sin, "Sin").clicked() if ui.radio_value(&mut self.plot, Plot::Sin, "Sin").clicked()
|| ui || ui
@ -96,12 +98,12 @@ impl super::View for ContextMenus {
ui.add( ui.add(
egui::DragValue::new(&mut self.width) egui::DragValue::new(&mut self.width)
.speed(1.0) .speed(1.0)
.prefix("Width:"), .prefix("Width: "),
); );
ui.add( ui.add(
egui::DragValue::new(&mut self.height) egui::DragValue::new(&mut self.height)
.speed(1.0) .speed(1.0)
.prefix("Height:"), .prefix("Height: "),
); );
ui.end_row(); ui.end_row();
ui.checkbox(&mut self.show_axes[0], "x-Axis"); ui.checkbox(&mut self.show_axes[0], "x-Axis");

View File

@ -486,6 +486,10 @@ fn response_summary(response: &egui::Response, show_hovers: bool) -> String {
} }
} }
if response.long_touched() {
writeln!(new_info, "Clicked with long-press").ok();
}
new_info new_info
} }

View File

@ -174,7 +174,7 @@ impl Keypad {
pub fn show(&self, ctx: &egui::Context) { pub fn show(&self, ctx: &egui::Context) {
let (focus, mut state) = ctx.memory(|m| { let (focus, mut state) = ctx.memory(|m| {
( (
m.focus(), m.focused(),
m.data.get_temp::<State>(self.id).unwrap_or_default(), m.data.get_temp::<State>(self.id).unwrap_or_default(),
) )
}); });