mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-08-21 16:23:46 +08:00
raylib: excessive actuation offroad alert (#36242)
* excessive actuation check * from gpt * back * use buttons * use widgets for ultimate clean up - no ai slop * feature parity * revert * clean up
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import pyray as rl
|
||||
from enum import IntEnum
|
||||
from abc import ABC, abstractmethod
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||
from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
|
||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||
from openpilot.system.ui.lib.wrap_text import wrap_text
|
||||
@@ -17,15 +18,16 @@ class AlertColors:
|
||||
LOW_SEVERITY = rl.Color(41, 41, 41, 255)
|
||||
BACKGROUND = rl.Color(57, 57, 57, 255)
|
||||
BUTTON = rl.WHITE
|
||||
BUTTON_PRESSED = rl.Color(200, 200, 200, 255)
|
||||
BUTTON_TEXT = rl.BLACK
|
||||
SNOOZE_BG = rl.Color(79, 79, 79, 255)
|
||||
SNOOZE_BG_PRESSED = rl.Color(100, 100, 100, 255)
|
||||
TEXT = rl.WHITE
|
||||
|
||||
|
||||
class AlertConstants:
|
||||
BUTTON_SIZE = (400, 125)
|
||||
SNOOZE_BUTTON_SIZE = (550, 125)
|
||||
REBOOT_BUTTON_SIZE = (600, 125)
|
||||
MIN_BUTTON_WIDTH = 400
|
||||
BUTTON_HEIGHT = 125
|
||||
MARGIN = 50
|
||||
SPACING = 30
|
||||
FONT_SIZE = 48
|
||||
@@ -43,6 +45,41 @@ class AlertData:
|
||||
visible: bool = False
|
||||
|
||||
|
||||
class ButtonStyle(IntEnum):
|
||||
LIGHT = 0
|
||||
DARK = 1
|
||||
|
||||
|
||||
class ActionButton(Widget):
|
||||
def __init__(self, text: str, style: ButtonStyle = ButtonStyle.LIGHT,
|
||||
min_width: int = AlertConstants.MIN_BUTTON_WIDTH):
|
||||
super().__init__()
|
||||
self._style = style
|
||||
self._min_width = min_width
|
||||
self._font = gui_app.font(FontWeight.MEDIUM)
|
||||
self.set_text(text)
|
||||
|
||||
def set_text(self, text: str):
|
||||
self._text = text
|
||||
self._text_width = measure_text_cached(gui_app.font(FontWeight.MEDIUM), self._text, AlertConstants.FONT_SIZE).x
|
||||
self._rect.width = max(self._text_width + 60 * 2, self._min_width)
|
||||
self._rect.height = AlertConstants.BUTTON_HEIGHT
|
||||
|
||||
def _render(self, _):
|
||||
roundness = AlertConstants.BORDER_RADIUS / self._rect.height
|
||||
bg_color = AlertColors.BUTTON if self._style == ButtonStyle.LIGHT else AlertColors.SNOOZE_BG
|
||||
if self.is_pressed:
|
||||
bg_color = AlertColors.BUTTON_PRESSED if self._style == ButtonStyle.LIGHT else AlertColors.SNOOZE_BG_PRESSED
|
||||
|
||||
rl.draw_rectangle_rounded(self._rect, roundness, 10, bg_color)
|
||||
|
||||
# center text
|
||||
color = rl.WHITE if self._style == ButtonStyle.DARK else rl.BLACK
|
||||
text_x = int(self._rect.x + (self._rect.width - self._text_width) // 2)
|
||||
text_y = int(self._rect.y + (self._rect.height - AlertConstants.FONT_SIZE) // 2)
|
||||
rl.draw_text_ex(self._font, self._text, rl.Vector2(text_x, text_y), AlertConstants.FONT_SIZE, 0, color)
|
||||
|
||||
|
||||
class AbstractAlert(Widget, ABC):
|
||||
def __init__(self, has_reboot_btn: bool = False):
|
||||
super().__init__()
|
||||
@@ -50,17 +87,35 @@ class AbstractAlert(Widget, ABC):
|
||||
self.has_reboot_btn = has_reboot_btn
|
||||
self.dismiss_callback: Callable | None = None
|
||||
|
||||
self.dismiss_btn_rect = rl.Rectangle(0, 0, *AlertConstants.BUTTON_SIZE)
|
||||
self.snooze_btn_rect = rl.Rectangle(0, 0, *AlertConstants.SNOOZE_BUTTON_SIZE)
|
||||
self.reboot_btn_rect = rl.Rectangle(0, 0, *AlertConstants.REBOOT_BUTTON_SIZE)
|
||||
def snooze_callback():
|
||||
self.params.put_bool("SnoozeUpdate", True)
|
||||
if self.dismiss_callback:
|
||||
self.dismiss_callback()
|
||||
|
||||
self.snooze_visible = False
|
||||
def excessive_actuation_callback():
|
||||
self.params.remove("Offroad_ExcessiveActuation")
|
||||
if self.dismiss_callback:
|
||||
self.dismiss_callback()
|
||||
|
||||
self.dismiss_btn = ActionButton("Close")
|
||||
|
||||
self.snooze_btn = ActionButton("Snooze Update", style=ButtonStyle.DARK)
|
||||
self.snooze_btn.set_click_callback(snooze_callback)
|
||||
|
||||
self.excessive_actuation_btn = ActionButton("Acknowledge Excessive Actuation", style=ButtonStyle.DARK, min_width=800)
|
||||
self.excessive_actuation_btn.set_click_callback(excessive_actuation_callback)
|
||||
|
||||
self.reboot_btn = ActionButton("Reboot and Update", min_width=600)
|
||||
self.reboot_btn.set_click_callback(lambda: HARDWARE.reboot())
|
||||
|
||||
# TODO: just use a Scroller?
|
||||
self.content_rect = rl.Rectangle(0, 0, 0, 0)
|
||||
self.scroll_panel_rect = rl.Rectangle(0, 0, 0, 0)
|
||||
self.scroll_panel = GuiScrollPanel()
|
||||
|
||||
def set_dismiss_callback(self, callback: Callable):
|
||||
self.dismiss_callback = callback
|
||||
self.dismiss_btn.set_click_callback(self.dismiss_callback)
|
||||
|
||||
@abstractmethod
|
||||
def refresh(self) -> bool:
|
||||
@@ -70,28 +125,10 @@ class AbstractAlert(Widget, ABC):
|
||||
def get_content_height(self) -> float:
|
||||
pass
|
||||
|
||||
def _handle_mouse_release(self, mouse_pos: MousePos):
|
||||
super()._handle_mouse_release(mouse_pos)
|
||||
|
||||
if not self.scroll_panel.is_touch_valid():
|
||||
return
|
||||
|
||||
if rl.check_collision_point_rec(mouse_pos, self.dismiss_btn_rect):
|
||||
if self.dismiss_callback:
|
||||
self.dismiss_callback()
|
||||
|
||||
elif self.snooze_visible and rl.check_collision_point_rec(mouse_pos, self.snooze_btn_rect):
|
||||
self.params.put_bool("SnoozeUpdate", True)
|
||||
if self.dismiss_callback:
|
||||
self.dismiss_callback()
|
||||
|
||||
elif self.has_reboot_btn and rl.check_collision_point_rec(mouse_pos, self.reboot_btn_rect):
|
||||
HARDWARE.reboot()
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
rl.draw_rectangle_rounded(rect, AlertConstants.BORDER_RADIUS / rect.height, 10, AlertColors.BACKGROUND)
|
||||
|
||||
footer_height = AlertConstants.BUTTON_SIZE[1] + AlertConstants.SPACING
|
||||
footer_height = AlertConstants.BUTTON_HEIGHT + AlertConstants.SPACING
|
||||
content_height = rect.height - 2 * AlertConstants.MARGIN - footer_height
|
||||
|
||||
self.content_rect = rl.Rectangle(
|
||||
@@ -134,47 +171,26 @@ class AbstractAlert(Widget, ABC):
|
||||
pass
|
||||
|
||||
def _render_footer(self, rect: rl.Rectangle):
|
||||
footer_y = rect.y + rect.height - AlertConstants.MARGIN - AlertConstants.BUTTON_SIZE[1]
|
||||
font = gui_app.font(FontWeight.MEDIUM)
|
||||
footer_y = rect.y + rect.height - AlertConstants.MARGIN - AlertConstants.BUTTON_HEIGHT
|
||||
|
||||
self.dismiss_btn_rect.x = rect.x + AlertConstants.MARGIN
|
||||
self.dismiss_btn_rect.y = footer_y
|
||||
roundness = AlertConstants.BORDER_RADIUS / self.dismiss_btn_rect.height
|
||||
rl.draw_rectangle_rounded(self.dismiss_btn_rect, roundness, 10, AlertColors.BUTTON)
|
||||
dismiss_x = rect.x + AlertConstants.MARGIN
|
||||
self.dismiss_btn.set_position(dismiss_x, footer_y)
|
||||
self.dismiss_btn.render()
|
||||
|
||||
text = "Close"
|
||||
text_width = measure_text_cached(font, text, AlertConstants.FONT_SIZE).x
|
||||
text_x = self.dismiss_btn_rect.x + (AlertConstants.BUTTON_SIZE[0] - text_width) // 2
|
||||
text_y = self.dismiss_btn_rect.y + (AlertConstants.BUTTON_SIZE[1] - AlertConstants.FONT_SIZE) // 2
|
||||
rl.draw_text_ex(
|
||||
font, text, rl.Vector2(int(text_x), int(text_y)), AlertConstants.FONT_SIZE, 0, AlertColors.BUTTON_TEXT
|
||||
)
|
||||
if self.has_reboot_btn:
|
||||
reboot_x = rect.x + rect.width - AlertConstants.MARGIN - self.reboot_btn.rect.width
|
||||
self.reboot_btn.set_position(reboot_x, footer_y)
|
||||
self.reboot_btn.render()
|
||||
|
||||
if self.snooze_visible:
|
||||
self.snooze_btn_rect.x = rect.x + rect.width - AlertConstants.MARGIN - AlertConstants.SNOOZE_BUTTON_SIZE[0]
|
||||
self.snooze_btn_rect.y = footer_y
|
||||
roundness = AlertConstants.BORDER_RADIUS / self.snooze_btn_rect.height
|
||||
rl.draw_rectangle_rounded(self.snooze_btn_rect, roundness, 10, AlertColors.SNOOZE_BG)
|
||||
elif self.excessive_actuation_btn.is_visible:
|
||||
actuation_x = rect.x + rect.width - AlertConstants.MARGIN - self.excessive_actuation_btn.rect.width
|
||||
self.excessive_actuation_btn.set_position(actuation_x, footer_y)
|
||||
self.excessive_actuation_btn.render()
|
||||
|
||||
text = "Snooze Update"
|
||||
text_width = measure_text_cached(font, text, AlertConstants.FONT_SIZE).x
|
||||
text_x = self.snooze_btn_rect.x + (AlertConstants.SNOOZE_BUTTON_SIZE[0] - text_width) // 2
|
||||
text_y = self.snooze_btn_rect.y + (AlertConstants.SNOOZE_BUTTON_SIZE[1] - AlertConstants.FONT_SIZE) // 2
|
||||
rl.draw_text_ex(font, text, rl.Vector2(int(text_x), int(text_y)), AlertConstants.FONT_SIZE, 0, AlertColors.TEXT)
|
||||
|
||||
elif self.has_reboot_btn:
|
||||
self.reboot_btn_rect.x = rect.x + rect.width - AlertConstants.MARGIN - AlertConstants.REBOOT_BUTTON_SIZE[0]
|
||||
self.reboot_btn_rect.y = footer_y
|
||||
roundness = AlertConstants.BORDER_RADIUS / self.reboot_btn_rect.height
|
||||
rl.draw_rectangle_rounded(self.reboot_btn_rect, roundness, 10, AlertColors.BUTTON)
|
||||
|
||||
text = "Reboot and Update"
|
||||
text_width = measure_text_cached(font, text, AlertConstants.FONT_SIZE).x
|
||||
text_x = self.reboot_btn_rect.x + (AlertConstants.REBOOT_BUTTON_SIZE[0] - text_width) // 2
|
||||
text_y = self.reboot_btn_rect.y + (AlertConstants.REBOOT_BUTTON_SIZE[1] - AlertConstants.FONT_SIZE) // 2
|
||||
rl.draw_text_ex(
|
||||
font, text, rl.Vector2(int(text_x), int(text_y)), AlertConstants.FONT_SIZE, 0, AlertColors.BUTTON_TEXT
|
||||
)
|
||||
elif self.snooze_btn.is_visible:
|
||||
snooze_x = rect.x + rect.width - AlertConstants.MARGIN - self.snooze_btn.rect.width
|
||||
self.snooze_btn.set_position(snooze_x, footer_y)
|
||||
self.snooze_btn.render()
|
||||
|
||||
|
||||
class OffroadAlert(AbstractAlert):
|
||||
@@ -188,6 +204,7 @@ class OffroadAlert(AbstractAlert):
|
||||
|
||||
active_count = 0
|
||||
connectivity_needed = False
|
||||
excessive_actuation = False
|
||||
|
||||
for alert_data in self.sorted_alerts:
|
||||
text = ""
|
||||
@@ -205,7 +222,11 @@ class OffroadAlert(AbstractAlert):
|
||||
if alert_data.key == "Offroad_ConnectivityNeeded" and alert_data.visible:
|
||||
connectivity_needed = True
|
||||
|
||||
self.snooze_visible = connectivity_needed
|
||||
if alert_data.key == "Offroad_ExcessiveActuation" and alert_data.visible:
|
||||
excessive_actuation = True
|
||||
|
||||
self.excessive_actuation_btn.set_visible(excessive_actuation)
|
||||
self.snooze_btn.set_visible(connectivity_needed and not excessive_actuation)
|
||||
return active_count
|
||||
|
||||
def get_content_height(self) -> float:
|
||||
|
||||
Reference in New Issue
Block a user