mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-02-18 23:33:58 +08:00
Unified label: add scrolling (#36717)
* almost * works! * clean up * fix * trash * Revert "trash" This reverts commit 951d63382810d444fe08103f406a8c490cfcbe25. * fix some bugs and use * clean up * clean up * fix clipping * clean up * fix
This commit is contained in:
@@ -3,7 +3,7 @@ import time
|
||||
from cereal import log
|
||||
import pyray as rl
|
||||
from collections.abc import Callable
|
||||
from openpilot.system.ui.widgets.label import gui_label, MiciLabel
|
||||
from openpilot.system.ui.widgets.label import gui_label, MiciLabel, UnifiedLabel
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, DEFAULT_TEXT_COLOR, MousePos
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
@@ -113,7 +113,7 @@ class MiciHomeLayout(Widget):
|
||||
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._branch_label = MiciLabel("", font_size=36, color=rl.GRAY, font_weight=FontWeight.ROMAN, elide_right=False, scroll=True)
|
||||
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)
|
||||
|
||||
def show_event(self):
|
||||
@@ -195,7 +195,7 @@ class MiciHomeLayout(Widget):
|
||||
self._date_label.set_position(version_pos.x + self._version_label.rect.width + 10, version_pos.y)
|
||||
self._date_label.render()
|
||||
|
||||
self._branch_label.set_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.rect.width - self._date_label.rect.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.render()
|
||||
|
||||
@@ -207,7 +207,7 @@ class NetworkInfoPage(NavWidget):
|
||||
self._connect_btn.set_click_callback(lambda: connect_callback(self._network.ssid) if self._network is not None else None)
|
||||
|
||||
self._title = UnifiedLabel("", 64, FontWeight.DISPLAY, rl.Color(255, 255, 255, int(255 * 0.9)),
|
||||
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE)
|
||||
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, scroll=True)
|
||||
self._subtitle = UnifiedLabel("", 36, FontWeight.ROMAN, rl.Color(255, 255, 255, int(255 * 0.9 * 0.65)),
|
||||
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE)
|
||||
|
||||
@@ -217,6 +217,10 @@ class NetworkInfoPage(NavWidget):
|
||||
self._network: Network | None = None
|
||||
self._connecting: Callable[[], str | None] | None = None
|
||||
|
||||
def show_event(self):
|
||||
super().show_event()
|
||||
self._title.reset_scroll()
|
||||
|
||||
def update_networks(self, networks: dict[str, Network]):
|
||||
# update current network from latest scan results
|
||||
for ssid, network in networks.items():
|
||||
|
||||
@@ -282,7 +282,8 @@ class BigDialogOptionButton(Widget):
|
||||
self._selected = False
|
||||
|
||||
self._label = UnifiedLabel(option, font_size=70, text_color=rl.Color(255, 255, 255, int(255 * 0.58)),
|
||||
font_weight=FontWeight.DISPLAY_REGULAR, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE)
|
||||
font_weight=FontWeight.DISPLAY_REGULAR, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE,
|
||||
scroll=True)
|
||||
|
||||
def set_selected(self, selected: bool):
|
||||
self._selected = selected
|
||||
|
||||
@@ -412,6 +412,7 @@ class UnifiedLabel(Widget):
|
||||
max_width: int | None = None,
|
||||
elide: bool = True,
|
||||
wrap_text: bool = True,
|
||||
scroll: bool = False,
|
||||
line_height: float = 1.0,
|
||||
letter_spacing: float = 0.0):
|
||||
super().__init__()
|
||||
@@ -426,10 +427,23 @@ class UnifiedLabel(Widget):
|
||||
self._max_width = max_width
|
||||
self._elide = elide
|
||||
self._wrap_text = wrap_text
|
||||
self._scroll = scroll
|
||||
self._line_height = line_height * 0.9
|
||||
self._letter_spacing = letter_spacing # 0.1 = 10%
|
||||
self._spacing_pixels = font_size * letter_spacing
|
||||
|
||||
# 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
|
||||
|
||||
# Scroll mode does not support eliding or multiline wrapping
|
||||
if self._scroll:
|
||||
self._elide = False
|
||||
self._wrap_text = False
|
||||
|
||||
# Cached data
|
||||
self._cached_text: str | None = None
|
||||
self._cached_wrapped_lines: list[str] = []
|
||||
@@ -490,6 +504,12 @@ class UnifiedLabel(Widget):
|
||||
"""Update the vertical text alignment."""
|
||||
self._alignment_vertical = alignment_vertical
|
||||
|
||||
def reset_scroll(self):
|
||||
"""Reset scroll state to initial position."""
|
||||
self._scroll_offset = 0
|
||||
self._scroll_pause_t = None
|
||||
self._scroll_state = ScrollState.STARTING
|
||||
|
||||
def set_max_width(self, max_width: int | None):
|
||||
"""Set the maximum width constraint for wrapping/eliding."""
|
||||
if self._max_width != max_width:
|
||||
@@ -528,6 +548,9 @@ class UnifiedLabel(Widget):
|
||||
# Elide lines if needed (for width constraint)
|
||||
self._cached_wrapped_lines = [self._elide_line(line, content_width) for line in self._cached_wrapped_lines]
|
||||
|
||||
if self._scroll:
|
||||
self._cached_wrapped_lines = self._cached_wrapped_lines[:1] # Only first line for scrolling
|
||||
|
||||
# Process each line: measure and find emojis
|
||||
self._cached_line_sizes = []
|
||||
self._cached_line_emojis = []
|
||||
@@ -540,6 +563,11 @@ class UnifiedLabel(Widget):
|
||||
size = rl.Vector2(0, self._font_size * FONT_SCALE)
|
||||
else:
|
||||
size = measure_text_cached(self._font, line, self._font_size, self._spacing_pixels)
|
||||
|
||||
# This is the only line
|
||||
if self._scroll:
|
||||
self._needs_scroll = size.x > content_width
|
||||
|
||||
self._cached_line_sizes.append(size)
|
||||
|
||||
# Calculate total height
|
||||
@@ -683,17 +711,53 @@ class UnifiedLabel(Widget):
|
||||
else: # TEXT_ALIGN_MIDDLE
|
||||
start_y = self._rect.y + (self._rect.height - total_visible_height) / 2
|
||||
|
||||
# Only scissor when we know there is a single scrolling line
|
||||
if self._needs_scroll:
|
||||
rl.begin_scissor_mode(int(self._rect.x), int(self._rect.y), int(self._rect.width), int(self._rect.height))
|
||||
|
||||
# Render each line
|
||||
current_y = start_y
|
||||
for idx, (line, size, emojis) in enumerate(zip(visible_lines, visible_sizes, visible_emojis, strict=True)):
|
||||
if 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 <= -size.x - self._rect.width / 3:
|
||||
self._scroll_offset = 0
|
||||
self._scroll_state = ScrollState.STARTING
|
||||
self._scroll_pause_t = None
|
||||
else:
|
||||
self.reset_scroll()
|
||||
|
||||
self._render_line(line, size, emojis, current_y)
|
||||
|
||||
# Draw 2nd instance for scrolling
|
||||
if self._needs_scroll and self._scroll_state != ScrollState.STARTING:
|
||||
text2_scroll_offset = size.x + self._rect.width / 3
|
||||
self._render_line(line, size, emojis, current_y, text2_scroll_offset)
|
||||
|
||||
# Move to next line (if not last line)
|
||||
if idx < len(visible_lines) - 1:
|
||||
# Use current line's height * line_height for spacing to next line
|
||||
current_y += size.y * self._line_height
|
||||
|
||||
def _render_line(self, line, size, emojis, current_y):
|
||||
if self._needs_scroll:
|
||||
# draw black fade on left and right
|
||||
fade_width = 20
|
||||
rl.draw_rectangle_gradient_h(int(self._rect.x + self._rect.width - fade_width), int(self._rect.y), fade_width, int(self._rect.height), rl.BLANK, rl.BLACK)
|
||||
if self._scroll_state != ScrollState.STARTING:
|
||||
rl.draw_rectangle_gradient_h(int(self._rect.x), int(self._rect.y), fade_width, int(self._rect.height), rl.BLACK, rl.BLANK)
|
||||
|
||||
rl.end_scissor_mode()
|
||||
|
||||
def _render_line(self, line, size, emojis, current_y, x_offset=0.0):
|
||||
# Calculate horizontal position
|
||||
if self._alignment == rl.GuiTextAlignment.TEXT_ALIGN_LEFT:
|
||||
line_x = self._rect.x + self._text_padding
|
||||
@@ -703,6 +767,7 @@ class UnifiedLabel(Widget):
|
||||
line_x = self._rect.x + self._rect.width - size.x - self._text_padding
|
||||
else:
|
||||
line_x = self._rect.x + self._text_padding
|
||||
line_x += self._scroll_offset + x_offset
|
||||
|
||||
# Render line with emojis
|
||||
line_pos = rl.Vector2(line_x, current_y)
|
||||
|
||||
Reference in New Issue
Block a user