Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e7c9b61
it's adversarial
sshane Oct 15, 2025
442087a
try 2
sshane Oct 15, 2025
ad73eac
just do this
sshane Oct 15, 2025
74a5c6c
kinda works but doesn' tmatch
sshane Oct 15, 2025
7cfc535
fine
sshane Oct 15, 2025
62d2d42
qt is banned word
sshane Oct 15, 2025
e05f0d9
Merge remote-tracking branch 'upstream/master' into rl-branch-switcher
sshane Oct 15, 2025
1f6baf8
test
sshane Oct 15, 2025
869e6a6
fix test
sshane Oct 15, 2025
28c3e0e
add elide support to Label
sshane Oct 15, 2025
3033e65
fixup
sshane Oct 15, 2025
92c2d66
Revert "add elide support to Label"
sshane Oct 15, 2025
10ee224
Reapply "add elide support to Label"
sshane Oct 15, 2025
5677c94
todo
sshane Oct 15, 2025
29f81d2
Merge remote-tracking branch 'upstream/master' into rl-branch-switcher
sshane Oct 18, 2025
49ef679
elide button value properly + debug/stash
sshane Oct 18, 2025
3c5a433
clean up
sshane Oct 18, 2025
82822df
clean up
sshane Oct 18, 2025
2e0f802
yep looks good
sshane Oct 18, 2025
5fe3368
clean up
sshane Oct 18, 2025
4f47641
eval visible once
sshane Oct 18, 2025
239a85f
no s
sshane Oct 18, 2025
5688fab
don't need
sshane Oct 18, 2025
7959c37
can do this
sshane Oct 18, 2025
47bc44e
but this also works
sshane Oct 18, 2025
44fc9ec
clip to parent rect
sshane Oct 18, 2025
81931b4
Merge remote-tracking branch 'upstream/master' into rl-branch-switcher
sshane Oct 23, 2025
bdeebd5
fixes and multilang
sshane Oct 23, 2025
591fe9c
Merge remote-tracking branch 'upstream/master' into rl-branch-switcher
sshane Oct 23, 2025
3c1cf89
clean up
sshane Oct 23, 2025
c1b7e69
set target branch
sshane Oct 23, 2025
28b4e28
whops
sshane Oct 23, 2025
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
46 changes: 37 additions & 9 deletions selfdrive/ui/layouts/settings/software.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from openpilot.system.ui.widgets import Widget, DialogResult
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog
from openpilot.system.ui.widgets.list_view import button_item, text_item, ListItem
from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog
from openpilot.system.ui.widgets.scroller import Scroller

# TODO: remove this. updater fails to respond on startup if time is not correct
Expand Down Expand Up @@ -56,20 +57,20 @@ def __init__(self):
self._waiting_for_updater = False
self._waiting_start_ts: float = 0.0

items = self._init_items()
self._scroller = Scroller(items, line_separator=True, spacing=0)
# Branch switcher
self._branch_btn = button_item(lambda: tr("Target Branch"), lambda: tr("SELECT"), callback=self._on_select_branch)
self._branch_btn.set_visible(not ui_state.params.get_bool("IsTestedBranch"))
self._branch_btn.action_item.set_value(ui_state.params.get("UpdaterTargetBranch") or "")
self._branch_dialog: MultiOptionDialog | None = None

def _init_items(self):
items = [
self._scroller = Scroller([
self._onroad_label,
self._version_item,
self._download_btn,
self._install_btn,
# TODO: implement branch switching
# button_item("Target Branch", "SELECT", callback=self._on_select_branch),
self._branch_btn,
button_item(lambda: tr("Uninstall"), lambda: tr("UNINSTALL"), callback=self._on_uninstall),
]
return items
], line_separator=True, spacing=0)

def show_event(self):
self._scroller.show_event()
Expand Down Expand Up @@ -123,6 +124,10 @@ def _update_state(self):
# Only enable if we're not waiting for updater to flip out of idle
self._download_btn.action_item.set_enabled(not self._waiting_for_updater)

# Update target branch button value
current_branch = ui_state.params.get("UpdaterTargetBranch") or ""
self._branch_btn.action_item.set_value(current_branch)

# Update install button
self._install_btn.set_visible(ui_state.is_offroad() and update_available)
if update_available:
Expand Down Expand Up @@ -163,4 +168,27 @@ def _on_install_update(self):
self._install_btn.action_item.set_enabled(False)
ui_state.params.put_bool("DoReboot", True)

def _on_select_branch(self): pass
def _on_select_branch(self):
# Get available branches and order
current_git_branch = ui_state.params.get("GitBranch") or ""
branches_str = ui_state.params.get("UpdaterAvailableBranches") or ""
branches = [b for b in branches_str.split(",") if b]

for b in [current_git_branch, "devel-staging", "devel", "nightly", "nightly-dev", "master"]:
if b in branches:
branches.remove(b)
branches.insert(0, b)

current_target = ui_state.params.get("UpdaterTargetBranch") or ""
self._branch_dialog = MultiOptionDialog("Select a branch", branches, current_target)

def handle_selection(result):
# Confirmed selection
if result == DialogResult.CONFIRM and self._branch_dialog is not None and self._branch_dialog.selection:
selection = self._branch_dialog.selection
ui_state.params.put("UpdaterTargetBranch", selection)
self._branch_btn.action_item.set_value(selection)
os.system("pkill -SIGUSR1 -f system.updated.updated")
self._branch_dialog = None

gui_app.set_modal_overlay(self._branch_dialog, callback=handle_selection)
19 changes: 16 additions & 3 deletions selfdrive/ui/tests/test_ui/raylib_screenshots.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,17 @@
SCREENSHOTS_DIR = TEST_OUTPUT_DIR / "screenshots"
UI_DELAY = 0.2

BRANCH_NAME = "this-is-a-really-super-mega-ultra-max-extreme-ultimate-long-branch-name"
VERSION = f"0.10.1 / {BRANCH_NAME} / 7864838 / Oct 03"

# Offroad alerts to test
OFFROAD_ALERTS = ['Offroad_IsTakingSnapshot']


def put_update_params(params: Params):
params.put("UpdaterCurrentReleaseNotes", parse_release_notes(BASEDIR))
params.put("UpdaterNewReleaseNotes", parse_release_notes(BASEDIR))
params.put("UpdaterTargetBranch", BRANCH_NAME)


def setup_homescreen(click, pm: PubMaster):
Expand Down Expand Up @@ -89,6 +93,15 @@ def setup_settings_software_release_notes(click, pm: PubMaster):
click(588, 110) # expand description for current version


def setup_settings_software_branch_switcher(click, pm: PubMaster):
setup_settings_software(click, pm)
params = Params()
params.put("UpdaterAvailableBranches", f"master,nightly,release,{BRANCH_NAME}")
params.put("GitBranch", BRANCH_NAME) # should be on top
params.put("UpdaterTargetBranch", "nightly") # should be selected
click(1984, 449)


def setup_settings_firehose(click, pm: PubMaster):
setup_settings(click, pm)
click(278, 845)
Expand Down Expand Up @@ -240,6 +253,7 @@ def setup_onroad_full_alert_long_text(click, pm: PubMaster):
"settings_software": setup_settings_software,
"settings_software_download": setup_settings_software_download,
"settings_software_release_notes": setup_settings_software_release_notes,
"settings_software_branch_switcher": setup_settings_software_branch_switcher,
"settings_firehose": setup_settings_firehose,
"settings_developer": setup_settings_developer,
"keyboard": setup_keyboard,
Expand Down Expand Up @@ -309,9 +323,8 @@ def create_screenshots():
params.put("DongleId", "123456789012345")

# Set branch name
description = "0.10.1 / this-is-a-really-super-mega-long-branch-name / 7864838 / Oct 03"
params.put("UpdaterCurrentDescription", description)
params.put("UpdaterNewDescription", description)
params.put("UpdaterCurrentDescription", VERSION)
params.put("UpdaterNewDescription", VERSION)

if name == "homescreen_paired":
params.put("PrimeType", 0) # NONE
Expand Down
3 changes: 2 additions & 1 deletion system/ui/widgets/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ def __init__(self,
text_alignment: int = rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
text_padding: int = 20,
icon=None,
elide_right: bool = False,
multi_touch: bool = False,
):

Expand All @@ -97,7 +98,7 @@ def __init__(self,
self._background_color = BUTTON_BACKGROUND_COLORS[self._button_style]

self._label = Label(text, font_size, font_weight, text_alignment, text_padding=text_padding,
text_color=BUTTON_TEXT_COLOR[self._button_style], icon=icon)
text_color=BUTTON_TEXT_COLOR[self._button_style], icon=icon, elide_right=elide_right)

self._click_callback = click_callback
self._multi_touch = multi_touch
Expand Down
34 changes: 30 additions & 4 deletions system/ui/widgets/label.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,17 @@ def gui_label(

# Elide text to fit within the rectangle
if elide_right and text_size.x > rect.width:
ellipsis = "..."
_ellipsis = "..."
left, right = 0, len(text)
while left < right:
mid = (left + right) // 2
candidate = text[:mid] + ellipsis
candidate = text[:mid] + _ellipsis
candidate_size = measure_text_cached(font, candidate, font_size)
if candidate_size.x <= rect.width:
left = mid + 1
else:
right = mid
display_text = text[: left - 1] + ellipsis if left > 0 else ellipsis
display_text = text[: left - 1] + _ellipsis if left > 0 else _ellipsis
text_size = measure_text_cached(font, display_text, font_size)

# Calculate horizontal position based on alignment
Expand Down Expand Up @@ -106,6 +106,7 @@ def __init__(self,
text_padding: int = 0,
text_color: rl.Color = DEFAULT_TEXT_COLOR,
icon: Union[rl.Texture, None] = None, # noqa: UP007
elide_right: bool = False,
):

super().__init__()
Expand All @@ -117,6 +118,7 @@ def __init__(self,
self._text_padding = text_padding
self._text_color = text_color
self._icon = icon
self._elide_right = elide_right

self._text = text
self.set_text(text)
Expand All @@ -138,7 +140,31 @@ def _update_layout_rects(self):
def _update_text(self, text):
self._emojis = []
self._text_size = []
self._text_wrapped = wrap_text(self._font, _resolve_value(text), self._font_size, round(self._rect.width - (self._text_padding * 2)))
text = _resolve_value(text)

if self._elide_right:
display_text = text

# Elide text to fit within the rectangle
text_size = measure_text_cached(self._font, text, self._font_size)
content_width = self._rect.width - self._text_padding * 2
if text_size.x > content_width:
_ellipsis = "..."
left, right = 0, len(text)
while left < right:
mid = (left + right) // 2
candidate = text[:mid] + _ellipsis
candidate_size = measure_text_cached(self._font, candidate, self._font_size)
if candidate_size.x <= content_width:
left = mid + 1
else:
right = mid
display_text = text[: left - 1] + _ellipsis if left > 0 else _ellipsis

self._text_wrapped = [display_text]
else:
self._text_wrapped = wrap_text(self._font, text, self._font_size, round(self._rect.width - (self._text_padding * 2)))

for t in self._text_wrapped:
self._emojis.append(find_emoji(t))
self._text_size.append(measure_text_cached(self._font, t, self._font_size))
Expand Down
19 changes: 13 additions & 6 deletions system/ui/widgets/list_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,14 @@ def pressed():
)
self.set_enabled(enabled)

def get_width_hint(self) -> float:
value_text = self.value
if value_text:
text_width = measure_text_cached(self._font, value_text, ITEM_TEXT_FONT_SIZE).x
return text_width + BUTTON_WIDTH + TEXT_PADDING
else:
return BUTTON_WIDTH

def set_touch_valid_callback(self, touch_callback: Callable[[], bool]) -> None:
super().set_touch_valid_callback(touch_callback)
self._button.set_touch_valid_callback(touch_callback)
Expand All @@ -121,16 +129,15 @@ def value(self):
def _render(self, rect: rl.Rectangle) -> bool:
self._button.set_text(self.text)
self._button.set_enabled(_resolve_value(self.enabled))
button_rect = rl.Rectangle(rect.x, rect.y + (rect.height - BUTTON_HEIGHT) / 2, BUTTON_WIDTH, BUTTON_HEIGHT)
button_rect = rl.Rectangle(rect.x + rect.width - BUTTON_WIDTH, rect.y + (rect.height - BUTTON_HEIGHT) / 2, BUTTON_WIDTH, BUTTON_HEIGHT)
self._button.render(button_rect)

value_text = self.value
if value_text:
spacing = 20
text_size = measure_text_cached(self._font, value_text, ITEM_TEXT_FONT_SIZE)
text_x = button_rect.x - spacing - text_size.x
text_y = rect.y + (rect.height - text_size.y) / 2
rl.draw_text_ex(self._font, value_text, rl.Vector2(text_x, text_y), ITEM_TEXT_FONT_SIZE, 0, ITEM_TEXT_VALUE_COLOR)
value_rect = rl.Rectangle(rect.x, rect.y, rect.width - BUTTON_WIDTH - TEXT_PADDING, rect.height)
gui_label(value_rect, value_text, font_size=ITEM_TEXT_FONT_SIZE, color=ITEM_TEXT_VALUE_COLOR,
font_weight=FontWeight.NORMAL, alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT,
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE)

# TODO: just use the generic Widget click callbacks everywhere, no returning from render
pressed = self._pressed
Expand Down
6 changes: 3 additions & 3 deletions system/ui/widgets/option_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ def __init__(self, title, options, current=""):
# Create scroller with option buttons
self.option_buttons = [Button(option, click_callback=lambda opt=option: self._on_option_clicked(opt),
text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, button_style=ButtonStyle.NORMAL,
text_padding=50) for option in options]
text_padding=50, elide_right=True) for option in options]
self.scroller = Scroller(self.option_buttons, spacing=LIST_ITEM_SPACING)

self.cancel_button = Button(tr("Cancel"), click_callback=lambda: self._set_result(DialogResult.CANCEL))
self.select_button = Button(tr("Select"), click_callback=lambda: self._set_result(DialogResult.CONFIRM), button_style=ButtonStyle.PRIMARY)
self.cancel_button = Button(lambda: tr("Cancel"), click_callback=lambda: self._set_result(DialogResult.CANCEL))
self.select_button = Button(lambda: tr("Select"), click_callback=lambda: self._set_result(DialogResult.CONFIRM), button_style=ButtonStyle.PRIMARY)

def _set_result(self, result: DialogResult):
self._result = result
Expand Down
Loading