diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py index e87e62503..da5b0ac5e 100644 --- a/selfdrive/ui/mici/layouts/home.py +++ b/selfdrive/ui/mici/layouts/home.py @@ -7,7 +7,7 @@ from collections.abc import Callable from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.layouts import HBoxLayout from openpilot.system.ui.widgets.icon_widget import IconWidget -from openpilot.system.ui.widgets.label import MiciLabel, UnifiedLabel +from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.version import RELEASE_BRANCHES @@ -103,12 +103,12 @@ class MiciHomeLayout(Widget): self._mic_icon, ], spacing=18) - self._openpilot_label = MiciLabel("openpilot", font_size=96, color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.DISPLAY) - self._version_label = MiciLabel("", font_size=36, font_weight=FontWeight.ROMAN) - self._large_version_label = MiciLabel("", font_size=64, color=rl.GRAY, font_weight=FontWeight.ROMAN) - self._date_label = MiciLabel("", font_size=36, color=rl.GRAY, font_weight=FontWeight.ROMAN) + self._openpilot_label = UnifiedLabel("openpilot", font_size=96, font_weight=FontWeight.DISPLAY, max_width=480, wrap_text=False) + self._version_label = UnifiedLabel("", font_size=36, font_weight=FontWeight.ROMAN, max_width=480, wrap_text=False) + self._large_version_label = UnifiedLabel("", font_size=64, text_color=rl.GRAY, font_weight=FontWeight.ROMAN, max_width=480, wrap_text=False) + self._date_label = UnifiedLabel("", font_size=36, text_color=rl.GRAY, font_weight=FontWeight.ROMAN, max_width=480, wrap_text=False) self._branch_label = UnifiedLabel("", font_size=36, text_color=rl.GRAY, font_weight=FontWeight.ROMAN, scroll=True) - self._version_commit_label = MiciLabel("", font_size=36, color=rl.GRAY, font_weight=FontWeight.ROMAN) + self._version_commit_label = UnifiedLabel("", font_size=36, text_color=rl.GRAY, font_weight=FontWeight.ROMAN, max_width=480, wrap_text=False) def show_event(self): super().show_event() @@ -183,12 +183,12 @@ class MiciHomeLayout(Widget): self._version_label.render() self._date_label.set_text(" " + self._version_text[3]) - self._date_label.set_position(version_pos.x + self._version_label.rect.width + 10, version_pos.y) + self._date_label.set_position(version_pos.x + self._version_label.text_width + 10, version_pos.y) self._date_label.render() - self._branch_label.set_max_width(gui_app.width - self._version_label.rect.width - self._date_label.rect.width - 32) + self._branch_label.set_max_width(gui_app.width - self._version_label.text_width - self._date_label.text_width - 32) self._branch_label.set_text(" " + ("release" if release_branch else self._version_text[1])) - self._branch_label.set_position(version_pos.x + self._version_label.rect.width + self._date_label.rect.width + 20, version_pos.y) + self._branch_label.set_position(version_pos.x + self._version_label.text_width + self._date_label.text_width + 20, version_pos.y) self._branch_label.render() if not release_branch: diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index 2be32d66b..909e30ac7 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -17,7 +17,7 @@ from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import Widget from openpilot.selfdrive.ui.ui_state import device, ui_state -from openpilot.system.ui.widgets.label import MiciLabel +from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.widgets.html_render import HtmlModal, HtmlRenderer from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID @@ -98,14 +98,15 @@ class DeviceInfoLayoutMici(Widget): self.set_rect(rl.Rectangle(0, 0, 360, 180)) params = Params() - header_color = rl.Color(255, 255, 255, int(255 * 0.9)) subheader_color = rl.Color(255, 255, 255, int(255 * 0.9 * 0.65)) max_width = int(self._rect.width - 20) - self._dongle_id_label = MiciLabel("device ID", 48, width=max_width, color=header_color, font_weight=FontWeight.DISPLAY) - self._dongle_id_text_label = MiciLabel(params.get("DongleId") or 'N/A', 32, width=max_width, color=subheader_color, font_weight=FontWeight.ROMAN) + self._dongle_id_label = UnifiedLabel("device ID", 48, max_width=max_width, font_weight=FontWeight.DISPLAY, wrap_text=False) + self._dongle_id_text_label = UnifiedLabel(params.get("DongleId") or 'N/A', 32, max_width=max_width, text_color=subheader_color, + font_weight=FontWeight.ROMAN, wrap_text=False) - self._serial_number_label = MiciLabel("serial", 48, color=header_color, font_weight=FontWeight.DISPLAY) - self._serial_number_text_label = MiciLabel(params.get("HardwareSerial") or 'N/A', 32, width=max_width, color=subheader_color, font_weight=FontWeight.ROMAN) + self._serial_number_label = UnifiedLabel("serial", 48, max_width=max_width, font_weight=FontWeight.DISPLAY, wrap_text=False) + self._serial_number_text_label = UnifiedLabel(params.get("HardwareSerial") or 'N/A', 32, max_width=max_width, text_color=subheader_color, + font_weight=FontWeight.ROMAN, wrap_text=False) def _render(self, _): self._dongle_id_label.set_position(self._rect.x + 20, self._rect.y - 10) diff --git a/selfdrive/ui/mici/widgets/pairing_dialog.py b/selfdrive/ui/mici/widgets/pairing_dialog.py index 991cb05a8..8421b516d 100644 --- a/selfdrive/ui/mici/widgets/pairing_dialog.py +++ b/selfdrive/ui/mici/widgets/pairing_dialog.py @@ -9,7 +9,7 @@ from openpilot.common.params import Params from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.lib.application import FontWeight, gui_app -from openpilot.system.ui.widgets.label import MiciLabel +from openpilot.system.ui.widgets.label import UnifiedLabel class PairingDialog(NavWidget): @@ -24,8 +24,7 @@ class PairingDialog(NavWidget): self._last_qr_generation = float("-inf") self._txt_pair = gui_app.texture("icons_mici/settings/device/pair.png", 33, 60) - self._pair_label = MiciLabel("pair with comma connect", 48, font_weight=FontWeight.BOLD, - color=rl.Color(255, 255, 255, int(255 * 0.9)), line_height=40, wrap_text=True) + self._pair_label = UnifiedLabel("pair with comma connect", font_size=48, font_weight=FontWeight.BOLD, line_height=0.8) def _get_pairing_url(self) -> str: try: @@ -77,7 +76,7 @@ class PairingDialog(NavWidget): self._render_qr_code() label_x = self._rect.x + 8 + self._rect.height + 24 - self._pair_label.set_width(int(self._rect.width - label_x)) + self._pair_label.set_max_width(int(self._rect.width - label_x)) self._pair_label.set_position(label_x, self._rect.y + 16) self._pair_label.render() diff --git a/system/ui/widgets/label.py b/system/ui/widgets/label.py index 8ed9ec62f..0b2444ec6 100644 --- a/system/ui/widgets/label.py +++ b/system/ui/widgets/label.py @@ -26,166 +26,6 @@ class ScrollState(IntEnum): SCROLLING = 1 -# TODO: merge anything new here to master -class MiciLabel(Widget): - def __init__(self, - text: str, - font_size: int = DEFAULT_TEXT_SIZE, - width: int | None = None, - color: rl.Color = DEFAULT_TEXT_COLOR, - font_weight: FontWeight = FontWeight.NORMAL, - alignment: int = rl.GuiTextAlignment.TEXT_ALIGN_LEFT, - alignment_vertical: int = rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP, - spacing: int = 0, - line_height: int | None = None, - elide_right: bool = True, - wrap_text: bool = False, - scroll: bool = False): - super().__init__() - self.text = text - self.wrapped_text: list[str] = [] - self.font_size = font_size - self.width = width - self.color = color - self.font_weight = font_weight - self.alignment = alignment - self.alignment_vertical = alignment_vertical - self.spacing = spacing - self.line_height = line_height if line_height is not None else font_size - self.elide_right = elide_right - self.wrap_text = wrap_text - self._height = 0 - - # Scroll state - self.scroll = scroll - self._needs_scroll = False - self._scroll_offset = 0 - self._scroll_pause_t: float | None = None - self._scroll_state: ScrollState = ScrollState.STARTING - - assert not (self.scroll and self.wrap_text), "Cannot enable both scroll and wrap_text" - assert not (self.scroll and self.elide_right), "Cannot enable both scroll and elide_right" - - self.set_text(text) - - @property - def text_height(self): - return self._height - - def set_font_size(self, font_size: int): - self.font_size = font_size - self.set_text(self.text) - - def set_width(self, width: int): - self.width = width - self._rect.width = width - self.set_text(self.text) - - def set_text(self, txt: str): - self.text = txt - text_size = measure_text_cached(gui_app.font(self.font_weight), self.text, self.font_size, self.spacing) - if self.width is not None: - self._rect.width = self.width - else: - self._rect.width = text_size.x - - if self.wrap_text: - self.wrapped_text = wrap_text(gui_app.font(self.font_weight), self.text, self.font_size, int(self._rect.width)) - self._height = len(self.wrapped_text) * self.line_height - elif self.scroll: - self._needs_scroll = self.scroll and text_size.x > self._rect.width - self._rect.height = text_size.y - - def set_color(self, color: rl.Color): - self.color = color - - def set_font_weight(self, font_weight: FontWeight): - self.font_weight = font_weight - self.set_text(self.text) - - def _render(self, rect: rl.Rectangle): - # Only scissor when we know there is a single scrolling line - if self._needs_scroll: - rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height)) - - font = gui_app.font(self.font_weight) - - text_y_offset = 0 - # Draw the text in the specified rectangle - lines = self.wrapped_text or [self.text] - if self.alignment_vertical == rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM: - lines = lines[::-1] - - for display_text in lines: - text_size = measure_text_cached(font, display_text, self.font_size, self.spacing) - - # Elide text to fit within the rectangle - if self.elide_right and text_size.x > rect.width: - ellipsis = "..." - left, right = 0, len(display_text) - while left < right: - mid = (left + right) // 2 - candidate = display_text[:mid] + ellipsis - candidate_size = measure_text_cached(font, candidate, self.font_size, self.spacing) - if candidate_size.x <= rect.width: - left = mid + 1 - else: - right = mid - display_text = display_text[: left - 1] + ellipsis if left > 0 else ellipsis - text_size = measure_text_cached(font, display_text, self.font_size, self.spacing) - - # Handle scroll state - elif self.scroll and self._needs_scroll: - if self._scroll_state == ScrollState.STARTING: - if self._scroll_pause_t is None: - self._scroll_pause_t = rl.get_time() + 2.0 - if rl.get_time() >= self._scroll_pause_t: - self._scroll_state = ScrollState.SCROLLING - self._scroll_pause_t = None - - elif self._scroll_state == ScrollState.SCROLLING: - self._scroll_offset -= 0.8 / 60. * gui_app.target_fps - # don't fully hide - if self._scroll_offset <= -text_size.x - self._rect.width / 3: - self._scroll_offset = 0 - self._scroll_state = ScrollState.STARTING - self._scroll_pause_t = None - - # Calculate horizontal position based on alignment - text_x = rect.x + { - rl.GuiTextAlignment.TEXT_ALIGN_LEFT: 0, - rl.GuiTextAlignment.TEXT_ALIGN_CENTER: (rect.width - text_size.x) / 2, - rl.GuiTextAlignment.TEXT_ALIGN_RIGHT: rect.width - text_size.x, - }.get(self.alignment, 0) + self._scroll_offset - - # Calculate vertical position based on alignment - text_y = rect.y + { - rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP: 0, - rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE: (rect.height - text_size.y) / 2, - rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM: rect.height - text_size.y, - }.get(self.alignment_vertical, 0) - text_y += text_y_offset - - rl.draw_text_ex(font, display_text, rl.Vector2(round(text_x), text_y), self.font_size, self.spacing, self.color) - # Draw 2nd instance for scrolling - if self._needs_scroll and self._scroll_state != ScrollState.STARTING: - text2_scroll_offset = text_size.x + self._rect.width / 3 - rl.draw_text_ex(font, display_text, rl.Vector2(round(text_x + text2_scroll_offset), text_y), self.font_size, self.spacing, self.color) - if self.alignment_vertical == rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM: - text_y_offset -= self.line_height - else: - text_y_offset += self.line_height - - if self._needs_scroll: - # draw black fade on left and right - fade_width = 20 - rl.draw_rectangle_gradient_h(int(rect.x + rect.width - fade_width), int(rect.y), fade_width, int(rect.height), rl.BLANK, rl.BLACK) - if self._scroll_state != ScrollState.STARTING: - rl.draw_rectangle_gradient_h(int(rect.x), int(rect.y), fade_width, int(rect.height), rl.BLACK, rl.BLANK) - - rl.end_scissor_mode() - - # TODO: This should be a Widget class def gui_label( rect: rl.Rectangle, @@ -392,7 +232,7 @@ class Label(Widget): class UnifiedLabel(Widget): """ - Unified label widget that combines functionality from gui_label, gui_text_box, Label, and MiciLabel. + Unified label widget that combines functionality from gui_label, gui_text_box, and Label. Supports: - Emoji rendering @@ -467,6 +307,14 @@ class UnifiedLabel(Widget): """Get the current text content.""" return str(_resolve_value(self._text)) + @property + def font_size(self) -> int: + return self._font_size + + @property + def text_width(self) -> float: + return max((s.x for s in self._cached_line_sizes), default=0.0) + def set_text_color(self, color: rl.Color): """Update the text color.""" self._text_color = color