Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 8 additions & 25 deletions selfdrive/ui/mici/layouts/home.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import time

from cereal import log
import pyray as rl
from collections.abc import Callable
Expand Down Expand Up @@ -83,9 +81,6 @@ def __init__(self):
self._on_settings_click: Callable | None = None

self._last_refresh = 0
self._mouse_down_t: None | float = None
self._did_long_press = False
self._is_pressed_prev = False

self._version_text = None
self._experimental_mode = False
Expand Down Expand Up @@ -124,23 +119,13 @@ def show_event(self):
def _update_params(self):
self._experimental_mode = ui_state.params.get_bool("ExperimentalMode")

def _update_state(self):
if self.is_pressed and not self._is_pressed_prev:
self._mouse_down_t = time.monotonic()
elif not self.is_pressed and self._is_pressed_prev:
self._mouse_down_t = None
self._did_long_press = False
self._is_pressed_prev = self.is_pressed

if self._mouse_down_t is not None:
if time.monotonic() - self._mouse_down_t > 0.5:
# long gating for experimental mode - only allow toggle if longitudinal control is available
if ui_state.has_longitudinal_control:
self._experimental_mode = not self._experimental_mode
ui_state.params.put("ExperimentalMode", self._experimental_mode)
self._mouse_down_t = None
self._did_long_press = True
def _handle_long_press(self, _):
# long gating for experimental mode - only allow toggle if longitudinal control is available
if ui_state.has_longitudinal_control:
self._experimental_mode = not self._experimental_mode
ui_state.params.put("ExperimentalMode", self._experimental_mode)

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

def _handle_mouse_release(self, mouse_pos: MousePos):
if not self._did_long_press:
if self._on_settings_click:
self._on_settings_click()
self._did_long_press = False
if self._on_settings_click:
self._on_settings_click()

def _get_version_text(self) -> tuple[str, str, str, str] | None:
description = ui_state.params.get("UpdaterCurrentDescription")
Expand Down
30 changes: 28 additions & 2 deletions system/ui/widgets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ class DialogResult(IntEnum):


class Widget(abc.ABC):
LONG_PRESS_TIME = 0.5

def __init__(self):
self._rect: rl.Rectangle = rl.Rectangle(0, 0, 0, 0)
self._parent_rect: rl.Rectangle | None = None
Expand All @@ -33,6 +35,10 @@ def __init__(self):
self._multi_touch = False
self.__was_awake = True

# Long press state (single touch only, slot 0)
self._long_press_start_t: float | None = None
self._long_press_fired: bool = False

@property
def rect(self) -> rl.Rectangle:
return self._rect
Expand Down Expand Up @@ -127,19 +133,28 @@ def _process_mouse_events(self) -> None:
self._handle_mouse_press(mouse_event.pos)
self.__is_pressed[mouse_event.slot] = True
self.__tracking_is_pressed[mouse_event.slot] = True
if mouse_event.slot == 0:
self._long_press_start_t = rl.get_time()
self._long_press_fired = False
self._handle_mouse_event(mouse_event)

# Callback such as scroll panel signifies user is scrolling
elif not touch_valid:
self.__is_pressed[mouse_event.slot] = False
self.__tracking_is_pressed[mouse_event.slot] = False
if mouse_event.slot == 0:
self._long_press_start_t = None
self._long_press_fired = False

elif mouse_event.left_released:
self._handle_mouse_event(mouse_event)
if self.__is_pressed[mouse_event.slot] and mouse_in_rect:
if self.__is_pressed[mouse_event.slot] and mouse_in_rect and not (mouse_event.slot == 0 and self._long_press_fired):
self._handle_mouse_release(mouse_event.pos)
self.__is_pressed[mouse_event.slot] = False
self.__tracking_is_pressed[mouse_event.slot] = False
if mouse_event.slot == 0:
self._long_press_start_t = None
self._long_press_fired = False

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

# Long press detection
if self._long_press_start_t is not None and not self._long_press_fired:
if (rl.get_time() - self._long_press_start_t) >= self.LONG_PRESS_TIME:
self._long_press_fired = True
self._handle_long_press(gui_app.last_mouse_event.pos)

def _layout(self) -> None:
"""Optionally lay out child widgets separately. This is called before rendering."""

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

def _handle_long_press(self, mouse_pos: MousePos) -> None:
"""Optionally handle a long-press gesture."""

def _handle_mouse_event(self, mouse_event: MouseEvent) -> None:
"""Optionally handle mouse events. This is called before rendering."""
# Default implementation does nothing, can be overridden by subclasses

def show_event(self):
"""Optionally handle show event. Parent must manually call this"""
Expand Down
Loading