Skip to content

Commit 9768109

Browse files
authored
ui: generic hold gesture (#36893)
* generic * fix * use in home * clean up * rm * clean up
1 parent 4fa4237 commit 9768109

File tree

2 files changed

+36
-27
lines changed

2 files changed

+36
-27
lines changed

selfdrive/ui/mici/layouts/home.py

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import time
2-
31
from cereal import log
42
import pyray as rl
53
from collections.abc import Callable
@@ -83,9 +81,6 @@ def __init__(self):
8381
self._on_settings_click: Callable | None = None
8482

8583
self._last_refresh = 0
86-
self._mouse_down_t: None | float = None
87-
self._did_long_press = False
88-
self._is_pressed_prev = False
8984

9085
self._version_text = None
9186
self._experimental_mode = False
@@ -124,23 +119,13 @@ def show_event(self):
124119
def _update_params(self):
125120
self._experimental_mode = ui_state.params.get_bool("ExperimentalMode")
126121

127-
def _update_state(self):
128-
if self.is_pressed and not self._is_pressed_prev:
129-
self._mouse_down_t = time.monotonic()
130-
elif not self.is_pressed and self._is_pressed_prev:
131-
self._mouse_down_t = None
132-
self._did_long_press = False
133-
self._is_pressed_prev = self.is_pressed
134-
135-
if self._mouse_down_t is not None:
136-
if time.monotonic() - self._mouse_down_t > 0.5:
137-
# long gating for experimental mode - only allow toggle if longitudinal control is available
138-
if ui_state.has_longitudinal_control:
139-
self._experimental_mode = not self._experimental_mode
140-
ui_state.params.put("ExperimentalMode", self._experimental_mode)
141-
self._mouse_down_t = None
142-
self._did_long_press = True
122+
def _handle_long_press(self, _):
123+
# long gating for experimental mode - only allow toggle if longitudinal control is available
124+
if ui_state.has_longitudinal_control:
125+
self._experimental_mode = not self._experimental_mode
126+
ui_state.params.put("ExperimentalMode", self._experimental_mode)
143127

128+
def _update_state(self):
144129
if rl.get_time() - self._last_refresh > 5.0:
145130
device_state = ui_state.sm['deviceState']
146131
self._update_network_status(device_state)
@@ -159,10 +144,8 @@ def set_callbacks(self, on_settings: Callable | None = None):
159144
self._on_settings_click = on_settings
160145

161146
def _handle_mouse_release(self, mouse_pos: MousePos):
162-
if not self._did_long_press:
163-
if self._on_settings_click:
164-
self._on_settings_click()
165-
self._did_long_press = False
147+
if self._on_settings_click:
148+
self._on_settings_click()
166149

167150
def _get_version_text(self) -> tuple[str, str, str, str] | None:
168151
description = ui_state.params.get("UpdaterCurrentDescription")

system/ui/widgets/__init__.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ class DialogResult(IntEnum):
2020

2121

2222
class Widget(abc.ABC):
23+
LONG_PRESS_TIME = 0.5
24+
2325
def __init__(self):
2426
self._rect: rl.Rectangle = rl.Rectangle(0, 0, 0, 0)
2527
self._parent_rect: rl.Rectangle | None = None
@@ -33,6 +35,10 @@ def __init__(self):
3335
self._multi_touch = False
3436
self.__was_awake = True
3537

38+
# Long press state (single touch only, slot 0)
39+
self._long_press_start_t: float | None = None
40+
self._long_press_fired: bool = False
41+
3642
@property
3743
def rect(self) -> rl.Rectangle:
3844
return self._rect
@@ -127,19 +133,28 @@ def _process_mouse_events(self) -> None:
127133
self._handle_mouse_press(mouse_event.pos)
128134
self.__is_pressed[mouse_event.slot] = True
129135
self.__tracking_is_pressed[mouse_event.slot] = True
136+
if mouse_event.slot == 0:
137+
self._long_press_start_t = rl.get_time()
138+
self._long_press_fired = False
130139
self._handle_mouse_event(mouse_event)
131140

132141
# Callback such as scroll panel signifies user is scrolling
133142
elif not touch_valid:
134143
self.__is_pressed[mouse_event.slot] = False
135144
self.__tracking_is_pressed[mouse_event.slot] = False
145+
if mouse_event.slot == 0:
146+
self._long_press_start_t = None
147+
self._long_press_fired = False
136148

137149
elif mouse_event.left_released:
138150
self._handle_mouse_event(mouse_event)
139-
if self.__is_pressed[mouse_event.slot] and mouse_in_rect:
151+
if self.__is_pressed[mouse_event.slot] and mouse_in_rect and not (mouse_event.slot == 0 and self._long_press_fired):
140152
self._handle_mouse_release(mouse_event.pos)
141153
self.__is_pressed[mouse_event.slot] = False
142154
self.__tracking_is_pressed[mouse_event.slot] = False
155+
if mouse_event.slot == 0:
156+
self._long_press_start_t = None
157+
self._long_press_fired = False
143158

144159
# Mouse/touch is still within our rect
145160
elif mouse_in_rect:
@@ -150,8 +165,17 @@ def _process_mouse_events(self) -> None:
150165
# Mouse/touch left our rect but may come back into focus later
151166
elif not mouse_in_rect:
152167
self.__is_pressed[mouse_event.slot] = False
168+
if mouse_event.slot == 0:
169+
self._long_press_start_t = None
170+
self._long_press_fired = False
153171
self._handle_mouse_event(mouse_event)
154172

173+
# Long press detection
174+
if self._long_press_start_t is not None and not self._long_press_fired:
175+
if (rl.get_time() - self._long_press_start_t) >= self.LONG_PRESS_TIME:
176+
self._long_press_fired = True
177+
self._handle_long_press(gui_app.last_mouse_event.pos)
178+
155179
def _layout(self) -> None:
156180
"""Optionally lay out child widgets separately. This is called before rendering."""
157181

@@ -175,9 +199,11 @@ def _handle_mouse_release(self, mouse_pos: MousePos) -> bool:
175199
self._click_callback()
176200
return False
177201

202+
def _handle_long_press(self, mouse_pos: MousePos) -> None:
203+
"""Optionally handle a long-press gesture."""
204+
178205
def _handle_mouse_event(self, mouse_event: MouseEvent) -> None:
179206
"""Optionally handle mouse events. This is called before rendering."""
180-
# Default implementation does nothing, can be overridden by subclasses
181207

182208
def show_event(self):
183209
"""Optionally handle show event. Parent must manually call this"""

0 commit comments

Comments
 (0)