Skip to content

Commit cd1ed73

Browse files
authored
Change the definition of clicked_by (#4192)
This is a refactor on the way to add support for opening context menus on touch screens via press-and-hold. This PR changes what `InputState::button_clicked` does (it was ver badly named before), and also changes `Response::clicked_by` to no longer be true if clicking with keyboard (i.e. a widget has keyboard focus and the user presses Space or Enter).
1 parent 820fa3c commit cd1ed73

File tree

7 files changed

+62
-67
lines changed

7 files changed

+62
-67
lines changed

CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ This file is updated upon each release.
77
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
88

99

10+
## Unreleased
11+
12+
### ⚠️ BREAKING
13+
* `Response::clicked*` and `Response::dragged*` may lock the `Context`, so don't call it from a `Context`-locking closure.
14+
* `Response::clicked_by` will no longer be true if clicked with keyboard. Use `Response::clicked` instead.
15+
16+
1017
## 0.26.2 - 2024-02-14
1118
* Avoid interacting twice when not required [#4041](https://github.com/emilk/egui/pull/4041) (thanks [@abey79](https://github.com/abey79)!)
1219

crates/egui/src/context.rs

+7-12
Original file line numberDiff line numberDiff line change
@@ -1104,9 +1104,8 @@ impl Context {
11041104
contains_pointer: false,
11051105
hovered: false,
11061106
highlighted,
1107-
clicked: Default::default(),
1108-
double_clicked: Default::default(),
1109-
triple_clicked: Default::default(),
1107+
clicked: false,
1108+
fake_primary_click: false,
11101109
drag_started: false,
11111110
dragged: false,
11121111
drag_stopped: false,
@@ -1131,15 +1130,15 @@ impl Context {
11311130
&& (input.key_pressed(Key::Space) || input.key_pressed(Key::Enter))
11321131
{
11331132
// Space/enter works like a primary click for e.g. selected buttons
1134-
res.clicked[PointerButton::Primary as usize] = true;
1133+
res.fake_primary_click = true;
11351134
}
11361135

11371136
#[cfg(feature = "accesskit")]
11381137
if enabled
11391138
&& sense.click
11401139
&& input.has_accesskit_action_request(id, accesskit::Action::Default)
11411140
{
1142-
res.clicked[PointerButton::Primary as usize] = true;
1141+
res.fake_primary_click = true;
11431142
}
11441143

11451144
let interaction = memory.interaction();
@@ -1157,13 +1156,9 @@ impl Context {
11571156
let clicked = Some(id) == viewport.interact_widgets.clicked;
11581157

11591158
for pointer_event in &input.pointer.pointer_events {
1160-
if let PointerEvent::Released { click, button } = pointer_event {
1161-
if enabled && sense.click && clicked {
1162-
if let Some(click) = click {
1163-
res.clicked[*button as usize] = true;
1164-
res.double_clicked[*button as usize] = click.is_double();
1165-
res.triple_clicked[*button as usize] = click.is_triple();
1166-
}
1159+
if let PointerEvent::Released { click, .. } = pointer_event {
1160+
if enabled && sense.click && clicked && click.is_some() {
1161+
res.clicked = true;
11671162
}
11681163

11691164
res.is_pointer_button_down_on = false;

crates/egui/src/input_state.rs

+5-7
Original file line numberDiff line numberDiff line change
@@ -489,11 +489,7 @@ impl InputState {
489489
/// delivers a synthetic zoom factor based on ctrl-scroll events, as a fallback.
490490
pub fn multi_touch(&self) -> Option<MultiTouchInfo> {
491491
// In case of multiple touch devices simply pick the touch_state of the first active device
492-
if let Some(touch_state) = self.touch_states.values().find(|t| t.is_active()) {
493-
touch_state.info()
494-
} else {
495-
None
496-
}
492+
self.touch_states.values().find_map(|t| t.info())
497493
}
498494

499495
/// True if there currently are any fingers touching egui.
@@ -976,11 +972,13 @@ impl PointerState {
976972
self.pointer_events.iter().any(|event| event.is_click())
977973
}
978974

979-
/// Was the button given clicked this frame?
975+
/// Was the given pointer button given clicked this frame?
976+
///
977+
/// Returns true on double- and triple- clicks too.
980978
pub fn button_clicked(&self, button: PointerButton) -> bool {
981979
self.pointer_events
982980
.iter()
983-
.any(|event| matches!(event, &PointerEvent::Pressed { button: b, .. } if button == b))
981+
.any(|event| matches!(event, &PointerEvent::Released { button: b, click: Some(_) } if button == b))
984982
}
985983

986984
/// Was the button given double clicked this frame?

crates/egui/src/input_state/touch_state.rs

+1-4
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ impl TouchState {
163163
_ => (),
164164
}
165165
}
166+
166167
// This needs to be called each frame, even if there are no new touch events.
167168
// Otherwise, we would send the same old delta information multiple times:
168169
self.update_gesture(time, pointer_pos);
@@ -176,10 +177,6 @@ impl TouchState {
176177
}
177178
}
178179

179-
pub fn is_active(&self) -> bool {
180-
self.gesture_state.is_some()
181-
}
182-
183180
pub fn info(&self) -> Option<MultiTouchInfo> {
184181
self.gesture_state.as_ref().map(|state| {
185182
// state.previous can be `None` when the number of simultaneous touches has just

crates/egui/src/menu.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,9 @@ impl MenuRoot {
367367
/// Interaction with a context menu (secondary click).
368368
fn context_interaction(response: &Response, root: &mut Option<Self>) -> MenuResponse {
369369
let response = response.interact(Sense::click());
370+
let hovered = response.hovered();
371+
let secondary_clicked = response.secondary_clicked();
372+
370373
response.ctx.input(|input| {
371374
let pointer = &input.pointer;
372375
if let Some(pos) = pointer.interact_pos() {
@@ -377,9 +380,9 @@ impl MenuRoot {
377380
destroy = !in_old_menu && pointer.any_pressed() && root.id == response.id;
378381
}
379382
if !in_old_menu {
380-
if response.hovered() && response.secondary_clicked() {
383+
if hovered && secondary_clicked {
381384
return MenuResponse::Create(pos, response.id);
382-
} else if (response.hovered() && pointer.primary_down()) || destroy {
385+
} else if destroy || hovered && pointer.primary_down() {
383386
return MenuResponse::Close;
384387
}
385388
}

crates/egui/src/response.rs

+36-41
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use std::{any::Any, sync::Arc};
33
use crate::{
44
emath::{Align, Pos2, Rect, Vec2},
55
menu, Context, CursorIcon, Id, LayerId, PointerButton, Sense, Ui, WidgetRect, WidgetText,
6-
NUM_POINTER_BUTTONS,
76
};
87

98
// ----------------------------------------------------------------------------
@@ -15,7 +14,10 @@ use crate::{
1514
///
1615
/// Whenever something gets added to a [`Ui`], a [`Response`] object is returned.
1716
/// [`ui.add`] returns a [`Response`], as does [`ui.button`], and all similar shortcuts.
18-
// TODO(emilk): we should be using bit sets instead of so many bools
17+
///
18+
/// ⚠️ The `Response` contains a clone of [`Context`], and many methods lock the `Context`.
19+
/// It can therefor be a deadlock to use `Context` from within a context-locking closures,
20+
/// such as [`Context::input`].
1921
#[derive(Clone, Debug)]
2022
pub struct Response {
2123
// CONTEXT:
@@ -69,18 +71,23 @@ pub struct Response {
6971
#[doc(hidden)]
7072
pub highlighted: bool,
7173

72-
/// The pointer clicked this thing this frame.
73-
#[doc(hidden)]
74-
pub clicked: [bool; NUM_POINTER_BUTTONS],
75-
76-
// TODO(emilk): `released` for sliders
77-
/// The thing was double-clicked.
74+
/// This widget was clicked this frame.
75+
///
76+
/// Which pointer and how many times we don't know,
77+
/// and ask [`crate::InputState`] about at runtime.
78+
///
79+
/// This is only set to true if the widget was clicked
80+
/// by an actual mouse.
7881
#[doc(hidden)]
79-
pub double_clicked: [bool; NUM_POINTER_BUTTONS],
82+
pub clicked: bool,
8083

81-
/// The thing was triple-clicked.
84+
/// This widget should act as if clicked due
85+
/// to something else than a click.
86+
///
87+
/// This is set to true if the widget has keyboard focus and
88+
/// the user hit the Space or Enter key.
8289
#[doc(hidden)]
83-
pub triple_clicked: [bool; NUM_POINTER_BUTTONS],
90+
pub fake_primary_click: bool,
8491

8592
/// The widget started being dragged this frame.
8693
#[doc(hidden)]
@@ -118,55 +125,62 @@ impl Response {
118125
/// A click is registered when the mouse or touch is released within
119126
/// a certain amount of time and distance from when and where it was pressed.
120127
///
128+
/// This will also return true if the widget was clicked via accessibility integration,
129+
/// or if the widget had keyboard focus and the use pressed Space/Enter.
130+
///
121131
/// Note that the widget must be sensing clicks with [`Sense::click`].
122132
/// [`crate::Button`] senses clicks; [`crate::Label`] does not (unless you call [`crate::Label::sense`]).
123133
///
124134
/// You can use [`Self::interact`] to sense more things *after* adding a widget.
125135
#[inline(always)]
126136
pub fn clicked(&self) -> bool {
127-
self.clicked[PointerButton::Primary as usize]
137+
self.fake_primary_click || self.clicked_by(PointerButton::Primary)
128138
}
129139

130-
/// Returns true if this widget was clicked this frame by the given button.
140+
/// Returns true if this widget was clicked this frame by the given mouse button.
141+
///
142+
/// This will NOT return true if the widget was "clicked" via
143+
/// some accessibility integration, or if the widget had keyboard focus and the
144+
/// user pressed Space/Enter. For that, use [`Self::clicked`] instead.
131145
#[inline]
132146
pub fn clicked_by(&self, button: PointerButton) -> bool {
133-
self.clicked[button as usize]
147+
self.clicked && self.ctx.input(|i| i.pointer.button_clicked(button))
134148
}
135149

136150
/// Returns true if this widget was clicked this frame by the secondary mouse button (e.g. the right mouse button).
137151
#[inline]
138152
pub fn secondary_clicked(&self) -> bool {
139-
self.clicked[PointerButton::Secondary as usize]
153+
self.clicked_by(PointerButton::Secondary)
140154
}
141155

142156
/// Returns true if this widget was clicked this frame by the middle mouse button.
143157
#[inline]
144158
pub fn middle_clicked(&self) -> bool {
145-
self.clicked[PointerButton::Middle as usize]
159+
self.clicked_by(PointerButton::Middle)
146160
}
147161

148162
/// Returns true if this widget was double-clicked this frame by the primary button.
149163
#[inline]
150164
pub fn double_clicked(&self) -> bool {
151-
self.double_clicked[PointerButton::Primary as usize]
165+
self.double_clicked_by(PointerButton::Primary)
152166
}
153167

154168
/// Returns true if this widget was triple-clicked this frame by the primary button.
155169
#[inline]
156170
pub fn triple_clicked(&self) -> bool {
157-
self.triple_clicked[PointerButton::Primary as usize]
171+
self.triple_clicked_by(PointerButton::Primary)
158172
}
159173

160174
/// Returns true if this widget was double-clicked this frame by the given button.
161175
#[inline]
162176
pub fn double_clicked_by(&self, button: PointerButton) -> bool {
163-
self.double_clicked[button as usize]
177+
self.clicked && self.ctx.input(|i| i.pointer.button_double_clicked(button))
164178
}
165179

166180
/// Returns true if this widget was triple-clicked this frame by the given button.
167181
#[inline]
168182
pub fn triple_clicked_by(&self, button: PointerButton) -> bool {
169-
self.triple_clicked[button as usize]
183+
self.clicked && self.ctx.input(|i| i.pointer.button_triple_clicked(button))
170184
}
171185

172186
/// `true` if there was a click *outside* this widget this frame.
@@ -917,27 +931,8 @@ impl Response {
917931
contains_pointer: self.contains_pointer || other.contains_pointer,
918932
hovered: self.hovered || other.hovered,
919933
highlighted: self.highlighted || other.highlighted,
920-
clicked: [
921-
self.clicked[0] || other.clicked[0],
922-
self.clicked[1] || other.clicked[1],
923-
self.clicked[2] || other.clicked[2],
924-
self.clicked[3] || other.clicked[3],
925-
self.clicked[4] || other.clicked[4],
926-
],
927-
double_clicked: [
928-
self.double_clicked[0] || other.double_clicked[0],
929-
self.double_clicked[1] || other.double_clicked[1],
930-
self.double_clicked[2] || other.double_clicked[2],
931-
self.double_clicked[3] || other.double_clicked[3],
932-
self.double_clicked[4] || other.double_clicked[4],
933-
],
934-
triple_clicked: [
935-
self.triple_clicked[0] || other.triple_clicked[0],
936-
self.triple_clicked[1] || other.triple_clicked[1],
937-
self.triple_clicked[2] || other.triple_clicked[2],
938-
self.triple_clicked[3] || other.triple_clicked[3],
939-
self.triple_clicked[4] || other.triple_clicked[4],
940-
],
934+
clicked: self.clicked || other.clicked,
935+
fake_primary_click: self.fake_primary_click || other.fake_primary_click,
941936
drag_started: self.drag_started || other.drag_started,
942937
dragged: self.dragged || other.dragged,
943938
drag_stopped: self.drag_stopped || other.drag_stopped,

crates/egui_demo_lib/src/demo/pan_zoom.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ impl Eq for PanZoom {}
1111

1212
impl super::Demo for PanZoom {
1313
fn name(&self) -> &'static str {
14-
"🗖 Pan Zoom"
14+
"🔍 Pan Zoom"
1515
}
1616

1717
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {

0 commit comments

Comments
 (0)