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:
parent
cd1ed73388
commit
d449cb1d48
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
use std::{borrow::Cow, cell::RefCell, panic::Location, sync::Arc, time::Duration};
|
||||
|
||||
use ahash::HashMap;
|
||||
use epaint::{
|
||||
emath::TSTransform, mutex::*, stats::*, text::Fonts, util::OrderedFloat, TessellationOptions, *,
|
||||
};
|
||||
|
|
@ -421,18 +420,17 @@ impl ContextImpl {
|
|||
// but the `screen_rect` is the most important part.
|
||||
}
|
||||
}
|
||||
let pixels_per_point = self.memory.options.zoom_factor
|
||||
* new_raw_input
|
||||
.viewport()
|
||||
.native_pixels_per_point
|
||||
.unwrap_or(1.0);
|
||||
let native_pixels_per_point = new_raw_input
|
||||
.viewport()
|
||||
.native_pixels_per_point
|
||||
.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 viewport = self.viewports.entry(self.viewport_id()).or_default();
|
||||
|
||||
self.memory
|
||||
.begin_frame(&viewport.input, &new_raw_input, &all_viewport_ids);
|
||||
self.memory.begin_frame(&new_raw_input, &all_viewport_ids);
|
||||
|
||||
viewport.input = std::mem::take(&mut viewport.input).begin_frame(
|
||||
new_raw_input,
|
||||
|
|
@ -440,17 +438,12 @@ impl ContextImpl {
|
|||
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
|
||||
.memory
|
||||
.areas()
|
||||
.order()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, id)| (*id, i))
|
||||
.collect();
|
||||
let area_order = self.memory.areas().order_map();
|
||||
|
||||
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:
|
||||
let screen_rect = viewport.input.screen_rect();
|
||||
self.memory.areas_mut().set_state(
|
||||
LayerId::background(),
|
||||
containers::area::State {
|
||||
|
|
@ -1106,6 +1098,7 @@ impl Context {
|
|||
highlighted,
|
||||
clicked: false,
|
||||
fake_primary_click: false,
|
||||
long_touched: false,
|
||||
drag_started: false,
|
||||
dragged: false,
|
||||
drag_stopped: false,
|
||||
|
|
@ -1141,6 +1134,10 @@ impl Context {
|
|||
res.fake_primary_click = true;
|
||||
}
|
||||
|
||||
if enabled && sense.click && Some(id) == viewport.interact_widgets.long_touched {
|
||||
res.long_touched = true;
|
||||
}
|
||||
|
||||
let interaction = memory.interaction();
|
||||
|
||||
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
|
||||
// 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 {
|
||||
res.interact_pointer_pos = input.pointer.interact_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:
|
||||
res.hovered = false;
|
||||
}
|
||||
|
|
@ -1847,6 +1845,7 @@ impl Context {
|
|||
let interact_widgets = self.write(|ctx| ctx.viewport().interact_widgets.clone());
|
||||
let InteractionSnapshot {
|
||||
clicked,
|
||||
long_touched: _,
|
||||
drag_started: _,
|
||||
dragged,
|
||||
drag_stopped: _,
|
||||
|
|
@ -1956,7 +1955,10 @@ impl ContextImpl {
|
|||
})
|
||||
.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 {
|
||||
nodes,
|
||||
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`]).
|
||||
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.
|
||||
|
|
@ -2481,7 +2483,7 @@ impl Context {
|
|||
.on_hover_text("Is egui currently listening for text input?");
|
||||
ui.label(format!(
|
||||
"Keyboard focus widget: {}",
|
||||
self.memory(|m| m.interaction().focus.focused())
|
||||
self.memory(|m| m.focused())
|
||||
.as_ref()
|
||||
.map(Id::short_debug_format)
|
||||
.unwrap_or_default()
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ impl Default for 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!();
|
||||
let Self {
|
||||
used_ids,
|
||||
|
|
@ -94,8 +94,8 @@ impl FrameState {
|
|||
} = self;
|
||||
|
||||
used_ids.clear();
|
||||
*available_rect = input.screen_rect();
|
||||
*unused_rect = input.screen_rect();
|
||||
*available_rect = screen_rect;
|
||||
*unused_rect = screen_rect;
|
||||
*used_by_panels = Rect::NOTHING;
|
||||
*tooltip_state = None;
|
||||
*scroll_target = [None, None];
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
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)
|
||||
const MAX_CLICK_DURATION: f64 = 0.6; // 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
|
||||
|
|
@ -544,6 +549,14 @@ impl InputState {
|
|||
.cloned()
|
||||
.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,
|
||||
|
||||
/// 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,
|
||||
|
||||
/// When did the pointer get click last?
|
||||
|
|
@ -751,6 +766,7 @@ impl PointerState {
|
|||
button,
|
||||
});
|
||||
} else {
|
||||
// Released
|
||||
let clicked = self.could_any_button_be_click();
|
||||
|
||||
let click = if clicked {
|
||||
|
|
@ -1027,21 +1043,21 @@ impl PointerState {
|
|||
///
|
||||
/// See also [`Self::is_decidedly_dragging`].
|
||||
pub fn could_any_button_be_click(&self) -> bool {
|
||||
if !self.any_down() {
|
||||
return false;
|
||||
}
|
||||
|
||||
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.any_down() || self.any_released() {
|
||||
if self.has_moved_too_much_for_a_click {
|
||||
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.
|
||||
|
|
@ -1060,6 +1076,19 @@ impl PointerState {
|
|||
&& !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?
|
||||
#[inline(always)]
|
||||
pub fn primary_down(&self) -> bool {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,10 @@ pub struct InteractionSnapshot {
|
|||
/// The widget that got clicked this frame.
|
||||
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.
|
||||
///
|
||||
/// This will also be found in `dragged` this frame.
|
||||
|
|
@ -56,6 +60,7 @@ impl InteractionSnapshot {
|
|||
pub fn ui(&self, ui: &mut crate::Ui) {
|
||||
let Self {
|
||||
clicked,
|
||||
long_touched,
|
||||
drag_started,
|
||||
dragged,
|
||||
drag_stopped,
|
||||
|
|
@ -74,6 +79,10 @@ impl InteractionSnapshot {
|
|||
id_ui(ui, clicked);
|
||||
ui.end_row();
|
||||
|
||||
ui.label("long_touched");
|
||||
id_ui(ui, long_touched);
|
||||
ui.end_row();
|
||||
|
||||
ui.label("drag_started");
|
||||
id_ui(ui, drag_started);
|
||||
ui.end_row();
|
||||
|
|
@ -123,6 +132,21 @@ pub(crate) fn interact(
|
|||
|
||||
let mut clicked = None;
|
||||
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.
|
||||
for pointer_event in &input.pointer.pointer_events {
|
||||
|
|
@ -142,7 +166,7 @@ pub(crate) fn interact(
|
|||
}
|
||||
|
||||
PointerEvent::Released { click, button: _ } => {
|
||||
if click.is_some() {
|
||||
if click.is_some() && !input.pointer.is_decidedly_dragging() {
|
||||
if let Some(widget) = interaction
|
||||
.potential_click_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;
|
||||
|
|
@ -201,9 +234,14 @@ pub(crate) fn interact(
|
|||
.map(|w| w.id)
|
||||
.collect();
|
||||
|
||||
let hovered = if clicked.is_some() || dragged.is_some() {
|
||||
// If currently clicking or dragging, nothing else is hovered.
|
||||
clicked.iter().chain(&dragged).copied().collect()
|
||||
let hovered = if clicked.is_some() || dragged.is_some() || long_touched.is_some() {
|
||||
// If currently clicking or dragging, only that and nothing else is hovered.
|
||||
clicked
|
||||
.iter()
|
||||
.chain(&dragged)
|
||||
.chain(&long_touched)
|
||||
.copied()
|
||||
.collect()
|
||||
} else if hits.click.is_some() || hits.drag.is_some() {
|
||||
// We are hovering over an interactive widget or two.
|
||||
hits.click.iter().chain(&hits.drag).map(|w| w.id).collect()
|
||||
|
|
@ -220,6 +258,7 @@ pub(crate) fn interact(
|
|||
|
||||
InteractionSnapshot {
|
||||
clicked,
|
||||
long_touched,
|
||||
drag_started,
|
||||
dragged,
|
||||
drag_stopped,
|
||||
|
|
|
|||
|
|
@ -194,7 +194,6 @@ impl Widget for &memory::InteractionState {
|
|||
let memory::InteractionState {
|
||||
potential_click_id,
|
||||
potential_drag_id,
|
||||
focus: _,
|
||||
} = self;
|
||||
|
||||
ui.vertical(|ui| {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use ahash::HashMap;
|
|||
use epaint::emath::TSTransform;
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
|
|
@ -95,6 +95,9 @@ pub struct Memory {
|
|||
|
||||
#[cfg_attr(feature = "persistence", serde(skip))]
|
||||
pub(crate) interactions: ViewportIdMap<InteractionState>,
|
||||
|
||||
#[cfg_attr(feature = "persistence", serde(skip))]
|
||||
pub(crate) focus: ViewportIdMap<Focus>,
|
||||
}
|
||||
|
||||
impl Default for Memory {
|
||||
|
|
@ -105,6 +108,7 @@ impl Default for Memory {
|
|||
caches: Default::default(),
|
||||
new_font_definitions: Default::default(),
|
||||
interactions: Default::default(),
|
||||
focus: Default::default(),
|
||||
viewport_id: Default::default(),
|
||||
areas: 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
|
||||
/// (at least if the widget is interesated in both clicks and drags).
|
||||
pub potential_drag_id: Option<Id>,
|
||||
|
||||
pub focus: Focus,
|
||||
}
|
||||
|
||||
/// Keeps tracks of what widget has keyboard focus
|
||||
|
|
@ -362,24 +364,6 @@ impl InteractionState {
|
|||
pub fn is_using_pointer(&self) -> bool {
|
||||
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 {
|
||||
|
|
@ -603,30 +587,29 @@ impl Focus {
|
|||
}
|
||||
|
||||
impl Memory {
|
||||
pub(crate) fn begin_frame(
|
||||
&mut self,
|
||||
prev_input: &crate::input_state::InputState,
|
||||
new_input: &crate::data::input::RawInput,
|
||||
viewports: &ViewportIdSet,
|
||||
) {
|
||||
pub(crate) fn begin_frame(&mut self, new_raw_input: &RawInput, viewports: &ViewportIdSet) {
|
||||
crate::profile_function!();
|
||||
|
||||
self.viewport_id = new_raw_input.viewport_id;
|
||||
|
||||
// Cleanup
|
||||
self.interactions.retain(|id, _| viewports.contains(id));
|
||||
self.areas.retain(|id, _| viewports.contains(id));
|
||||
|
||||
self.viewport_id = new_input.viewport_id;
|
||||
self.interactions
|
||||
self.areas.entry(self.viewport_id).or_default();
|
||||
|
||||
// self.interactions is handled elsewhere
|
||||
|
||||
self.focus
|
||||
.entry(self.viewport_id)
|
||||
.or_default()
|
||||
.begin_frame(prev_input, new_input);
|
||||
self.areas.entry(self.viewport_id).or_default();
|
||||
.begin_frame(new_raw_input);
|
||||
}
|
||||
|
||||
pub(crate) fn end_frame(&mut self, used_ids: &IdMap<Rect>) {
|
||||
self.caches.update();
|
||||
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) {
|
||||
|
|
@ -656,7 +639,7 @@ impl Memory {
|
|||
}
|
||||
|
||||
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.
|
||||
|
|
@ -677,12 +660,12 @@ impl Memory {
|
|||
/// from the window and back.
|
||||
#[inline(always)]
|
||||
pub fn has_focus(&self, id: Id) -> bool {
|
||||
self.interaction().focus.focused() == Some(id)
|
||||
self.focused() == Some(id)
|
||||
}
|
||||
|
||||
/// Which widget has keyboard focus?
|
||||
pub fn focus(&self) -> Option<Id> {
|
||||
self.interaction().focus.focused()
|
||||
pub fn focused(&self) -> Option<Id> {
|
||||
self.focus().focused()
|
||||
}
|
||||
|
||||
/// Set an event filter for a widget.
|
||||
|
|
@ -693,7 +676,7 @@ impl Memory {
|
|||
/// You must first give focus to the widget before calling this.
|
||||
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 let Some(focused) = &mut self.interaction_mut().focus.focused_widget {
|
||||
if let Some(focused) = &mut self.focus_mut().focused_widget {
|
||||
if focused.id == id {
|
||||
focused.filter = event_filter;
|
||||
}
|
||||
|
|
@ -705,16 +688,16 @@ impl Memory {
|
|||
/// See also [`crate::Response::request_focus`].
|
||||
#[inline(always)]
|
||||
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.
|
||||
/// See also [`crate::Response::surrender_focus`].
|
||||
#[inline(always)]
|
||||
pub fn surrender_focus(&mut self, id: Id) {
|
||||
let interaction = self.interaction_mut();
|
||||
if interaction.focus.focused() == Some(id) {
|
||||
interaction.focus.focused_widget = None;
|
||||
let focus = self.focus_mut();
|
||||
if focus.focused() == Some(id) {
|
||||
focus.focused_widget = None;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -727,13 +710,13 @@ impl Memory {
|
|||
/// and rendered correctly in a single frame.
|
||||
#[inline(always)]
|
||||
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).
|
||||
#[inline(always)]
|
||||
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?
|
||||
|
|
@ -813,6 +796,16 @@ impl Memory {
|
|||
pub(crate) fn interaction_mut(&mut self) -> &mut InteractionState {
|
||||
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
|
||||
|
|
@ -908,6 +901,15 @@ impl Areas {
|
|||
&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) {
|
||||
self.visible_current_frame.insert(layer_id);
|
||||
self.areas.insert(layer_id.id, state);
|
||||
|
|
|
|||
|
|
@ -89,6 +89,10 @@ pub struct Response {
|
|||
#[doc(hidden)]
|
||||
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.
|
||||
#[doc(hidden)]
|
||||
pub drag_started: bool,
|
||||
|
|
@ -142,15 +146,28 @@ impl Response {
|
|||
/// This will NOT return true if the widget was "clicked" via
|
||||
/// some accessibility integration, or if the widget had keyboard focus and the
|
||||
/// 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]
|
||||
pub fn clicked_by(&self, button: PointerButton) -> bool {
|
||||
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).
|
||||
///
|
||||
/// This also returns true if the widget was pressed-and-held on a touch screen.
|
||||
#[inline]
|
||||
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.
|
||||
|
|
@ -933,6 +950,7 @@ impl Response {
|
|||
highlighted: self.highlighted || other.highlighted,
|
||||
clicked: self.clicked || other.clicked,
|
||||
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,
|
||||
dragged: self.dragged || other.dragged,
|
||||
drag_stopped: self.drag_stopped || other.drag_stopped,
|
||||
|
|
|
|||
|
|
@ -80,6 +80,8 @@ impl super::View for ContextMenus {
|
|||
ui.label("Right-click plot to edit it!");
|
||||
ui.horizontal(|ui| {
|
||||
self.example_plot(ui).context_menu(|ui| {
|
||||
ui.set_min_width(220.0);
|
||||
|
||||
ui.menu_button("Plot", |ui| {
|
||||
if ui.radio_value(&mut self.plot, Plot::Sin, "Sin").clicked()
|
||||
|| ui
|
||||
|
|
@ -96,12 +98,12 @@ impl super::View for ContextMenus {
|
|||
ui.add(
|
||||
egui::DragValue::new(&mut self.width)
|
||||
.speed(1.0)
|
||||
.prefix("Width:"),
|
||||
.prefix("Width: "),
|
||||
);
|
||||
ui.add(
|
||||
egui::DragValue::new(&mut self.height)
|
||||
.speed(1.0)
|
||||
.prefix("Height:"),
|
||||
.prefix("Height: "),
|
||||
);
|
||||
ui.end_row();
|
||||
ui.checkbox(&mut self.show_axes[0], "x-Axis");
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ impl Keypad {
|
|||
pub fn show(&self, ctx: &egui::Context) {
|
||||
let (focus, mut state) = ctx.memory(|m| {
|
||||
(
|
||||
m.focus(),
|
||||
m.focused(),
|
||||
m.data.get_temp::<State>(self.id).unwrap_or_default(),
|
||||
)
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue