ui: implement change language in settings (#35481)

implement change language in settings
pull/35500/head
Dean Lee 6 days ago committed by GitHub
parent a3daca8fd5
commit f0f249ecf8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 35
      selfdrive/ui/layouts/settings/device.py
  2. 107
      system/ui/widgets/option_dialog.py

@ -1,7 +1,12 @@
from openpilot.system.ui.lib.application import Widget
import os
import json
from openpilot.system.ui.lib.application import gui_app, Widget
from openpilot.system.ui.lib.list_view import ListView, text_item, button_item
from openpilot.common.params import Params
from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog
from openpilot.system.hardware import TICI
from openpilot.common.basedir import BASEDIR
# Description constants
DESCRIPTIONS = {
@ -18,9 +23,10 @@ DESCRIPTIONS = {
class DeviceLayout(Widget):
def __init__(self):
super().__init__()
params = Params()
dongle_id = params.get("DongleId", encoding="utf-8") or "N/A"
serial = params.get("HardwareSerial") or "N/A"
self._params = Params()
dongle_id = self._params.get("DongleId", encoding="utf-8") or "N/A"
serial = self._params.get("HardwareSerial") or "N/A"
items = [
text_item("Dongle ID", dongle_id),
@ -37,13 +43,32 @@ class DeviceLayout(Widget):
items.append(button_item("Change Language", "CHANGE", callback=self._on_change_language))
self._list_widget = ListView(items)
self._select_language_dialog: MultiOptionDialog | None = None
def _render(self, rect):
self._list_widget.render(rect)
def _on_change_language(self):
try:
languages_file = os.path.join(BASEDIR, "selfdrive/ui/translations/languages.json")
with open(languages_file, encoding='utf-8') as f:
languages = json.load(f)
self._select_language_dialog = MultiOptionDialog("Select a language", languages)
gui_app.set_modal_overlay(self._select_language_dialog, callback=self._on_select_lang_dialog_closed)
except FileNotFoundError:
pass
def _on_select_lang_dialog_closed(self, result: int):
if result == 1 and self._select_language_dialog:
selected_language = self._select_language_dialog.selection
self._params.put("LanguageSetting", selected_language)
self._select_language_dialog = None
def _on_pair_device(self): pass
def _on_driver_camera(self): pass
def _on_reset_calibration(self): pass
def _on_review_training_guide(self): pass
def _on_regulatory(self): pass
def _on_change_language(self): pass

@ -1,83 +1,70 @@
import pyray as rl
from openpilot.system.ui.lib.application import Widget
from openpilot.system.ui.lib.application import Widget, FontWeight
from openpilot.system.ui.lib.button import gui_button, ButtonStyle, TextAlignment
from openpilot.system.ui.lib.label import gui_label
from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
# Constants
MARGIN = 50
TITLE_FONT_SIZE = 70
ITEM_HEIGHT = 135
BUTTON_SPACING = 50
BUTTON_HEIGHT = 160
ITEM_SPACING = 50
LIST_ITEM_SPACING = 25
class MultiOptionDialog(Widget):
def __init__(self, title, options, current=""):
super().__init__()
self._title = title
self._options = options
self._current = current if current in options else ""
self._selection = self._current
self._option_height = 80
self._padding = 20
self.scroll_panel = GuiScrollPanel()
@property
def selection(self):
return self._selection
self.title = title
self.options = options
self.current = current
self.selection = current
self.scroll = GuiScrollPanel()
def _render(self, rect):
title_rect = rl.Rectangle(rect.x + self._padding, rect.y + self._padding, rect.width - 2 * self._padding, 70)
gui_label(title_rect, self._title, 70)
options_y_start = rect.y + 120
options_height = len(self._options) * (self._option_height + 10)
options_rect = rl.Rectangle(rect.x + self._padding, options_y_start, rect.width - 2 * self._padding, options_height)
view_rect = rl.Rectangle(
rect.x + self._padding, options_y_start, rect.width - 2 * self._padding, rect.height - 200 - 2 * self._padding
)
dialog_rect = rl.Rectangle(rect.x + MARGIN, rect.y + MARGIN, rect.width - 2 * MARGIN, rect.height - 2 * MARGIN)
rl.draw_rectangle_rounded(dialog_rect, 0.02, 20, rl.Color(30, 30, 30, 255))
offset = self.scroll_panel.handle_scroll(view_rect, options_rect)
is_click_valid = self.scroll_panel.is_click_valid()
content_rect = rl.Rectangle(dialog_rect.x + MARGIN, dialog_rect.y + MARGIN,
dialog_rect.width - 2 * MARGIN, dialog_rect.height - 2 * MARGIN)
rl.begin_scissor_mode(int(view_rect.x), int(view_rect.y), int(view_rect.width), int(view_rect.height))
gui_label(rl.Rectangle(content_rect.x, content_rect.y, content_rect.width, TITLE_FONT_SIZE), self.title, 70, font_weight=FontWeight.BOLD)
for i, option in enumerate(self._options):
y_pos = view_rect.y + i * (self._option_height + 10) + offset.y
item_rect = rl.Rectangle(view_rect.x, y_pos, view_rect.width, self._option_height)
# Options area
options_y = content_rect.y + TITLE_FONT_SIZE + ITEM_SPACING
options_h = content_rect.height - TITLE_FONT_SIZE - BUTTON_HEIGHT - 2 * ITEM_SPACING
view_rect = rl.Rectangle(content_rect.x, options_y, content_rect.width, options_h)
content_h = len(self.options) * (ITEM_HEIGHT + 10)
list_content_rect = rl.Rectangle(content_rect.x, options_y, content_rect.width, content_h)
if not rl.check_collision_recs(item_rect, view_rect):
continue
# Scroll and render options
offset = self.scroll.handle_scroll(view_rect, list_content_rect)
valid_click = self.scroll.is_click_valid()
is_selected = option == self._selection
button_style = ButtonStyle.PRIMARY if is_selected else ButtonStyle.NORMAL
rl.begin_scissor_mode(int(view_rect.x), int(options_y), int(view_rect.width), int(options_h))
for i, option in enumerate(self.options):
item_y = options_y + i * (ITEM_HEIGHT + LIST_ITEM_SPACING) + offset.y
item_rect = rl.Rectangle(view_rect.x, item_y, view_rect.width, ITEM_HEIGHT)
if gui_button(item_rect, option, button_style=button_style, text_alignment=TextAlignment.LEFT) and is_click_valid:
self._selection = option
if rl.check_collision_recs(item_rect, view_rect):
selected = option == self.selection
style = ButtonStyle.PRIMARY if selected else ButtonStyle.NORMAL
if gui_button(item_rect, option, button_style=style, text_alignment=TextAlignment.LEFT) and valid_click:
self.selection = option
rl.end_scissor_mode()
button_y = rect.y + rect.height - 80 - self._padding
button_width = (rect.width - 3 * self._padding) / 2
cancel_rect = rl.Rectangle(rect.x + self._padding, button_y, button_width, 80)
if gui_button(cancel_rect, "Cancel"):
return 0 # Canceled
select_rect = rl.Rectangle(rect.x + 2 * self._padding + button_width, button_y, button_width, 80)
has_new_selection = self._selection != "" and self._selection != self._current
if gui_button(select_rect, "Select", is_enabled=has_new_selection, button_style=ButtonStyle.PRIMARY):
return 1 # Selected
return -1 # Still active
# Buttons
button_y = content_rect.y + content_rect.height - BUTTON_HEIGHT
button_w = (content_rect.width - BUTTON_SPACING) / 2
if __name__ == "__main__":
from openpilot.system.ui.lib.application import gui_app
if gui_button(rl.Rectangle(content_rect.x, button_y, button_w, BUTTON_HEIGHT), "Cancel"):
return 0
gui_app.init_window("Multi Option Dialog Example")
options = [f"Option {i}" for i in range(1, 11)]
dialog = MultiOptionDialog("Choose an option", options, options[0])
if gui_button(rl.Rectangle(content_rect.x + button_w + BUTTON_SPACING, button_y, button_w, BUTTON_HEIGHT),
"Select", is_enabled=self.selection != self.current, button_style=ButtonStyle.PRIMARY):
return 1
for _ in gui_app.render():
result = dialog.render(rl.Rectangle(100, 100, 1024, 800))
if isinstance(result, int) and result >= 0:
print(f"Selected: {dialog.selection}" if result > 0 else "Canceled")
break
return -1

Loading…
Cancel
Save