IQ.Pilot Release Commit @ 0babf78

This commit is contained in:
IQ.Lvbs CI [bot]
2026-07-27 01:40:11 -05:00
parent 6fb5c0141c
commit b39791a93f
425 changed files with 12180 additions and 6137 deletions

View File

@@ -8,8 +8,8 @@ import pyray as rl
from openpilot.common.params import Params
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.iqpilot.widgets.list_view import IQListItem, IQToggleAction, SafeIQToggleAction
from openpilot.system.ui.iqpilot.widgets.list_view import OptionControl
from openpilot.system.ui.iqwidgets.widgets.list_view import IQListItem, IQToggleAction, SafeIQToggleAction
from openpilot.system.ui.iqwidgets.widgets.list_view import OptionControl
from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.widgets.network import NavButton
from openpilot.system.ui.widgets.scroller_tici import Scroller

View File

@@ -18,17 +18,17 @@ from openpilot.selfdrive.ui.layouts.settings.software import time_ago
from openpilot.selfdrive.ui.ui_state import device, ui_state
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.hardware.hw import Paths
from openpilot.system.ui.iqpilot.lib.styles import style
from openpilot.system.ui.iqpilot.lib.utils import NoElideButtonAction
from openpilot.system.ui.iqpilot.widgets.list_view import IQListItem, toggle_item
from openpilot.system.ui.iqpilot.widgets.list_view import OptionControl
from openpilot.system.ui.iqpilot.widgets.list_view import multiple_button_item, toggle_item
from openpilot.system.ui.iqpilot.widgets.list_view import option_item, toggle_item, ToggleAction
from openpilot.system.ui.iqpilot.widgets.list_view import progress_item
from openpilot.system.ui.iqpilot.widgets.list_view import toggle_item, multiple_button_item, IQListItem, IQLineSeparator
from openpilot.system.ui.iqpilot.widgets.list_view import toggle_item, option_item, IQLineSeparator
from openpilot.system.ui.iqpilot.widgets.list_view import toggle_item, simple_button_item, option_item, IQLineSeparator
from openpilot.system.ui.iqpilot.widgets.list_view import TreeFolder, TreeNode, TreeOptionDialog
from openpilot.system.ui.iqwidgets.lib.styles import style
from openpilot.system.ui.iqwidgets.lib.utils import WideButtonAction
from openpilot.system.ui.iqwidgets.widgets.list_view import IQListItem, toggle_item
from openpilot.system.ui.iqwidgets.widgets.list_view import OptionControl
from openpilot.system.ui.iqwidgets.widgets.list_view import multiple_button_item, toggle_item
from openpilot.system.ui.iqwidgets.widgets.list_view import option_item, toggle_item, ToggleAction
from openpilot.system.ui.iqwidgets.widgets.list_view import progress_item
from openpilot.system.ui.iqwidgets.widgets.list_view import toggle_item, multiple_button_item, IQListItem, IQLineSeparator
from openpilot.system.ui.iqwidgets.widgets.list_view import toggle_item, option_item, IQLineSeparator
from openpilot.system.ui.iqwidgets.widgets.list_view import toggle_item, simple_button_item, option_item, IQLineSeparator
from openpilot.system.ui.iqwidgets.widgets.list_view import PickerGroup, PickerItem, PickerDialog
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.lib.multilang import tr, tr_noop
@@ -60,8 +60,8 @@ from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import CUS
from openpilot.selfdrive.ui.layouts.settings import settings as OP
from openpilot.selfdrive.ui.layouts.settings.toggles import TogglesLayout
from openpilot.system.hardware import HARDWARE
from openpilot.system.ui.iqpilot.lib.styles import metrics
from openpilot.system.ui.iqpilot.widgets.list_view import (
from openpilot.system.ui.iqwidgets.lib.styles import metrics
from openpilot.system.ui.iqwidgets.widgets.list_view import (
option_item as option_item,
multiple_button_item as multiple_button_item,
button_item as button_item,
@@ -69,11 +69,11 @@ from openpilot.system.ui.iqpilot.widgets.list_view import (
NavSectionButton,
Spacer,
)
from openpilot.system.ui.iqpilot.widgets.list_view import IQListItem
from openpilot.system.ui.iqpilot.widgets.list_view import IQListItem, IQMultipleButtonAction, IQToggleAction, IQLineSeparator
from openpilot.system.ui.iqpilot.widgets.list_view import button_item, toggle_item
from openpilot.system.ui.iqpilot.widgets.list_view import NoticeModal
from openpilot.system.ui.iqpilot.widgets.list_view import TreeOptionDialog, TreeNode, TreeFolder
from openpilot.system.ui.iqwidgets.widgets.list_view import IQListItem
from openpilot.system.ui.iqwidgets.widgets.list_view import IQListItem, IQMultipleButtonAction, IQToggleAction, IQLineSeparator
from openpilot.system.ui.iqwidgets.widgets.list_view import button_item, toggle_item
from openpilot.system.ui.iqwidgets.widgets.list_view import NoticeModal
from openpilot.system.ui.iqwidgets.widgets.list_view import PickerDialog, PickerItem, PickerGroup
from openpilot.system.ui.lib.application import gui_app, MousePos
from openpilot.system.ui.lib.multilang import tr_noop
from openpilot.system.ui.lib.text_measure import measure_text_cached
@@ -96,9 +96,9 @@ from iqdbc.car.hyundai.values import CAR, CANFD_UNSUPPORTED_LONGITUDINAL_CAR, UN
from iqdbc.car.subaru.values import CAR, SubaruFlags
from iqdbc.car.volkswagen.values import CAR, VolkswagenFlags
from openpilot.common.basedir import BASEDIR
from openpilot.system.ui.iqpilot.lib.styles import ink
from openpilot.system.ui.iqpilot.widgets.list_view import multiple_button_item
from openpilot.system.ui.iqpilot.widgets.list_view import toggle_item
from openpilot.system.ui.iqwidgets.lib.styles import ink
from openpilot.system.ui.iqwidgets.widgets.list_view import multiple_button_item
from openpilot.system.ui.iqwidgets.widgets.list_view import toggle_item
from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.widgets.list_view import ButtonAction
import json
@@ -116,9 +116,9 @@ def _fmt_seconds(value) -> str:
def onroad_brightness_label(val) -> str:
if val == OnroadBrightness.AUTO:
return tr("Auto (Default)")
return tr("Auto")
if val == OnroadBrightness.AUTO_DARK:
return tr("Auto (Dark)")
return tr("Auto — Dim")
return f"{(val - 1) * 5} %"
@@ -140,17 +140,17 @@ class DisplayLayout(Widget):
label_callback=lambda v: tr("Default") if v == 0 else f"{v} %", inline=True,
)
self._onroad_brightness_row = option_item(
param="OnroadScreenOffBrightness", title=lambda: tr("Onroad Brightness"), description="",
param="OnroadScreenOffBrightness", title=lambda: tr("Driving Brightness"), description="",
min_value=0, max_value=21, value_change_step=1,
label_callback=onroad_brightness_label, inline=True,
)
self._dim_delay_row = option_item(
param="OnroadScreenOffTimer", title=lambda: tr("Onroad Brightness Delay"), description="",
param="OnroadScreenOffTimer", title=lambda: tr("Dim Delay"), description="",
min_value=0, max_value=11, value_change_step=1,
value_map=ONROAD_BRIGHTNESS_TIMER_VALUES, label_callback=_fmt_seconds, inline=True,
)
self._idle_close_row = option_item(
param="InteractivityTimeout", title=lambda: tr("Interactivity Timeout"),
param="InteractivityTimeout", title=lambda: tr("Auto-Close Settings"),
description=lambda: tr("How long the settings screen may sit untouched before it closes itself."),
min_value=0, max_value=120, value_change_step=10,
label_callback=lambda v: tr("Default") if not v else _fmt_seconds(v), inline=True,
@@ -195,7 +195,7 @@ CHEVRON_INFO_DESCRIPTION = {
# param key -> (title fn, description fn)
_HUD_TOGGLES = {
"BlindSpot": (
lambda: tr("Show Blind Spot Warnings"),
lambda: tr("Blind Spot Alerts"),
lambda: tr("Flashes a side warning whenever the car reports something sitting in your blind spot (BSM-equipped cars only)."),
),
"IQExpandedStatus": (
@@ -203,20 +203,20 @@ _HUD_TOGGLES = {
lambda: tr("Bring back the classic UI's wide offroad status strip: temperature, vehicle, and Konn3kt state at a glance."),
),
"TorqueBar": (
lambda: tr("Steering Arc"),
lambda: tr("Steering Effort Arc"),
lambda: tr("Trace an arc over the road view showing how much steering IQ.Pilot is applying while lateral control runs."),
),
"RoadNameToggle": (
lambda: tr("Display Road Name"),
lambda: tr("Road Name Overlay"),
lambda: tr("Show the current road's name over the driving view."
"<br>Requires offline map data for your region to be installed."),
),
"ShowTurnSignals": (
lambda: tr("Display Turn Signals"),
lambda: tr("Blinker Indicators"),
lambda: tr("Mirror the car's blinkers as arrows on the driving screen."),
),
"RocketFuel": (
lambda: tr("Real-time Acceleration Bar"),
lambda: tr("Acceleration Meter"),
lambda: tr("Draw a bar along the left edge tracking measured acceleration and braking — what the car is actually "
"doing right now, not the planner's request."),
),
@@ -240,7 +240,7 @@ class VisualsLayout(Widget):
}
self._chevron_info = multiple_button_item(
title=lambda: tr("Display Metrics Below Chevron"),
title=lambda: tr("Lead Vehicle Readouts"),
description="",
buttons=[lambda: tr("Off"), lambda: tr("Distance"), lambda: tr("Speed"), lambda: tr("Time"), lambda: tr("All")],
param="ChevronInfo",
@@ -319,7 +319,7 @@ class IQNetworkUI(NetworkUI):
def _set_phase(self, phase: ScanPhase):
self._phase = phase
running = phase == ScanPhase.RUNNING
self.scan_button.set_text(tr("Scanning...") if running else tr("Scan"))
self.scan_button.set_text(tr("Searching...") if running else tr("Scan"))
self.scan_button.set_enabled(not running)
# keep the manager's spinner in step with the button, not just the quick scan call
self._wifi_manager._scanning = running
@@ -397,7 +397,7 @@ class IQMapsLayout(Widget):
self._scroller = Scroller(self.items, line_separator=True, spacing=0)
def _build_rows(self):
self._mapd_version = text_item(tr("Mapd Version"), lambda: ui_state.params.get("MapdVersion") or "Loading...")
self._mapd_version = text_item(tr("Routing Engine Version"), lambda: ui_state.params.get("MapdVersion") or "Loading...")
self._online_maps_toggle = toggle_item(
tr("Online On-Screen Maps"),
tr("Pull live Mapbox tiles for the on-screen map whenever the device has internet."),
@@ -409,15 +409,15 @@ class IQMapsLayout(Widget):
"take over the moment connectivity drops. Tiles come down with your selected region."),
param="OfflineOSMaps",
)
self._delete_maps_btn = IQListItem(tr("Downloaded Maps"), action_item=NoElideButtonAction(tr("DELETE"), enabled=True),
self._delete_maps_btn = IQListItem(tr("Stored Maps"), action_item=WideButtonAction(tr("DELETE"), enabled=True),
callback=self._confirm_wipe)
self._progress = progress_item(tr("Downloading Map"))
self._progress = progress_item(tr("Fetching Map"))
self._tile_progress = progress_item(tr("Downloading Map Tiles"))
self._update_btn = IQListItem(tr("Database Update"), action_item=NoElideButtonAction(tr("CHECK"), enabled=True),
self._update_btn = IQListItem(tr("Map Database"), action_item=WideButtonAction(tr("CHECK"), enabled=True),
callback=self._confirm_db_refresh)
self._country_btn = IQListItem(tr("Country"), action_item=NoElideButtonAction(tr("SELECT"), enabled=True),
self._country_btn = IQListItem(tr("Country"), action_item=WideButtonAction(tr("SELECT"), enabled=True),
callback=lambda: self._open_region_picker("Country"))
self._state_btn = IQListItem(tr("State"), action_item=NoElideButtonAction(tr("SELECT"), enabled=True),
self._state_btn = IQListItem(tr("State"), action_item=WideButtonAction(tr("SELECT"), enabled=True),
callback=lambda: self._open_region_picker("State"))
self.items = [self._mapd_version, self._online_maps_toggle, self._offline_maps_toggle, self._delete_maps_btn,
@@ -436,7 +436,7 @@ class IQMapsLayout(Widget):
def _start_wipe(self):
self._delete_maps_btn.action_item.set_enabled(False)
self._delete_maps_btn.action_item.set_text(tr("DELETING..."))
self._delete_maps_btn.action_item.set_text(tr("REMOVING..."))
threading.Thread(target=self._wipe_worker, daemon=True).start()
def _wipe_worker(self):
@@ -475,7 +475,7 @@ class IQMapsLayout(Widget):
def _open_region_picker(self, region_type):
btn = self._country_btn if region_type == "Country" else self._state_btn
btn.action_item.set_enabled(False)
btn.action_item.set_text(tr("FETCHING..."))
btn.action_item.set_text(tr("LOADING..."))
threading.Thread(target=self._region_picker_worker, args=(region_type, btn), daemon=True).start()
@staticmethod
@@ -483,7 +483,7 @@ class IQMapsLayout(Widget):
url = _BOUNDS_BASE_URL + ("nation_bounding_boxes.json" if region_type == "Country" else "us_states_bounding_boxes.json")
try:
data = requests.get(url, timeout=10).json()
return sorted((TreeNode(ref=k, data={'display_name': v['full_name']}) for k, v in data.items()),
return sorted((PickerItem(ref=k, data={'display_name': v['full_name']}) for k, v in data.items()),
key=lambda n: n.data['display_name'])
except Exception:
return []
@@ -491,14 +491,14 @@ class IQMapsLayout(Widget):
def _region_picker_worker(self, region_type, btn):
locations = self._fetch_regions(region_type)
if region_type == "State":
locations.insert(0, TreeNode(ref="All", data={'display_name': tr("All states (~6.0 GB)")}))
locations.insert(0, PickerItem(ref="All", data={'display_name': tr("All states (~6.0 GB)")}))
btn.action_item.set_enabled(True)
btn.action_item.set_text(tr("SELECT"))
key = "OsmLocation" if region_type == "Country" else "OsmState"
current = ui_state.params.get(f"{key}Name") or ""
dialog = TreeOptionDialog(tr(f"Select {region_type}"), [TreeFolder(folder="", nodes=locations)],
dialog = PickerDialog(tr(f"Select {region_type}"), [PickerGroup(folder="", nodes=locations)],
current_ref=current, search_prompt=tr("Perform a search"))
done = lambda res: self._on_region_picked(region_type, locations, key, res, dialog.selection_ref) # noqa: E731
dialog.on_exit = done
@@ -554,14 +554,14 @@ class IQMapsLayout(Widget):
if failed:
self._current_percent = 0.0
bar_text, btn_text = "0% - Downloading Maps", tr("Error: Invalid download. Retry.")
bar_text, btn_text = "0% - Downloading Maps", tr("Download failed — try again.")
elif total > 0 and downloading:
self._current_percent = max(0.0, min(100.0, done / total * 100.0))
pct = int(self._current_percent)
bar_text, btn_text = f"{pct}% - Downloading Maps", f"{done}/{total} ({pct}%)"
else:
self._current_percent = 0.0
bar_text, btn_text = "0% - Downloading Maps", tr("Downloading Maps...")
bar_text, btn_text = "0% - Downloading Maps", tr("Fetching Maps...")
self._progress.action_item.update(self._current_percent, bar_text,
show_progress=total > 0 and downloading and not failed)
@@ -599,7 +599,7 @@ class IQMapsLayout(Widget):
dt = datetime.datetime.fromtimestamp(float(ts), tz=datetime.UTC)
except (ValueError, TypeError):
dt = None
self._update_btn.action_item.set_value(tr("Last checked {}").format(time_ago(dt)))
self._update_btn.action_item.set_value(tr("Checked {}").format(time_ago(dt)))
def _update_state(self):
now = monotonic()
@@ -618,7 +618,7 @@ class IQMapsLayout(Widget):
_TIMER_LABELS = {
-1: lambda: tr("Off"),
0: lambda: tr("Nudge"),
1: lambda: tr("Nudgeless"),
1: lambda: tr("No Nudge"),
2: lambda: f"0.5 {tr('s')}",
3: lambda: f"1 {tr('s')}",
4: lambda: f"2 {tr('s')}",
@@ -635,8 +635,8 @@ class LaneChangeSettingsLayout(Widget):
def _build_rows(self):
self._lane_change_timer = option_item(
title=lambda: tr("Auto Lane Change by Blinker"),
param="AutoLaneChangeTimer",
title=lambda: tr("Blinker-Initiated Lane Change"),
param="IQLaneChangeTimer",
description=lambda: tr("Delay before a blinker-triggered lane change starts on its own — no wheel nudge "
"needed once a delay is set (default is Nudge).<br>Use the blinker for this only "
"when traffic and the road actually allow the maneuver."),
@@ -644,8 +644,8 @@ class LaneChangeSettingsLayout(Widget):
label_callback=lambda x: _TIMER_LABELS[int(x)](),
)
self._bsm_delay = toggle_item(
param="AutoLaneChangeBsmDelay",
title=lambda: tr("Auto Lane Change: Delay with Blind Spot"),
param="IQLaneChangeBsmDelay",
title=lambda: tr("Hold Lane Change for Blind Spot"),
description=lambda: tr("Hold the automatic lane change while blind spot monitoring reports a car in the "
"target lane, releasing it once the lane is clear."),
)
@@ -667,9 +667,9 @@ class LaneChangeSettingsLayout(Widget):
def _update_state(self):
super()._update_state()
has_bsm = bool(ui_state.CP and ui_state.CP.enableBsm)
if not has_bsm and ui_state.params.get_bool("AutoLaneChangeBsmDelay"):
ui_state.params.remove("AutoLaneChangeBsmDelay")
timer_armed = ui_state.params.get("AutoLaneChangeTimer", return_default=True) > AutoLaneChangeMode.NUDGE
if not has_bsm and ui_state.params.get_bool("IQLaneChangeBsmDelay"):
ui_state.params.remove("IQLaneChangeBsmDelay")
timer_armed = ui_state.params.get("IQLaneChangeTimer", return_default=True) > AutoLaneChangeMode.NUDGE
self._bsm_delay.action_item.set_enabled(has_bsm and timer_armed)
def _render(self, rect):
@@ -685,7 +685,7 @@ class LaneChangeSettingsLayout(Widget):
# (segment label, explanation) — order matches the AolSteeringMode param values
SAB_BRAKE_RESPONSE_OPTIONS = [
(tr("Remain Active"), tr_noop("Remain Active: braking never interrupts steering assistance.")),
(tr("Stay Engaged"), tr_noop("Remain Active: braking never interrupts steering assistance.")),
(tr("Standby"), tr_noop("Standby: braking parks steering assistance; it rejoins once you're off the pedal.")),
(tr("Disengage"), tr_noop("Disengage: braking shuts steering assistance off entirely.")),
]
@@ -829,7 +829,7 @@ class SteeringLayout(Widget):
items = self._initialize_items()
self._scroller = Scroller(items, line_separator=False, spacing=0)
for ctrl, key in [(self._lane_turn_value_control, "IQLaneTurnValue"), (self._delay_control, "LagdToggleDelay")]:
for ctrl, key in [(self._lane_turn_value_control, "IQLaneTurnValue"), (self._delay_control, "IQSoftwareSteerDelay")]:
ctrl.action_item.set_value(int(float(ui_state.params.get(key, return_default=True)) * 100))
def _initialize_items(self):
@@ -849,7 +849,7 @@ class SteeringLayout(Widget):
callback=lambda: self._set_current_panel(PanelType.SAB)
)
self._lane_change_settings_button = simple_button_item(
button_text=lambda: tr("Customize Lane Change"),
button_text=lambda: tr("Lane Change Options"),
button_width=800,
callback=lambda: self._set_current_panel(PanelType.LANE_CHANGE)
)
@@ -859,7 +859,7 @@ class SteeringLayout(Widget):
description=""
)
self._lane_turn_desire_toggle = toggle_item(
tr("Use Lane Turn Desires"),
tr("Low-Speed Turn Planning"),
tr("If you're driving at 20 mph (32 km/h) or below and have your blinker on,"
" the car will plan a turn in that direction at the nearest drivable path."
" This prevents situations (like at red lights) where the car might plan the wrong turn direction."),
@@ -872,10 +872,10 @@ class SteeringLayout(Widget):
lambda v: f"{int(round(v * (CV.MPH_TO_KPH if ui_state.is_metric else 1)))}"
f" {'km/h' if ui_state.is_metric else 'mph'}"
)
self._lagd_toggle = toggle_item(tr("Live Learning Steer Delay"), "", param="LagdToggle")
self._steer_delay_toggle = toggle_item(tr("Self-Tuning Steer Delay"), "", param="IQLiveSteerDelay")
self._delay_control = option_item(
tr("Adjust Software Delay"), "LagdToggleDelay", 5, 50,
tr("Adjust the fixed software delay added to steer actuator delay when Live Learning Steer Delay is turned off. The default software delay value is 0.2 s."),
tr("Manual Delay Offset"), "IQSoftwareSteerDelay", 5, 50,
tr("How much lead time to add on top of the car's own steering rack delay while self-tuning is off. Default is 0.2 s."),
1, None, True, "", style.BUTTON_ACTION_WIDTH, None, True, lambda v: f"{float(v):.2f}s"
)
@@ -890,7 +890,7 @@ class SteeringLayout(Widget):
self._lane_turn_desire_toggle,
self._lane_turn_value_control,
IQLineSeparator(40),
self._lagd_toggle,
self._steer_delay_toggle,
self._delay_control,
]
return items
@@ -919,22 +919,23 @@ class SteeringLayout(Widget):
self._nnff_toggle.action_item.set_enabled(ui_state.is_offroad() and steering_supported)
turn_desire = ui_state.params.get_bool("IQLaneTurnDesire")
live_delay = ui_state.params.get_bool("LagdToggle")
live_delay = ui_state.params.get_bool("IQLiveSteerDelay")
self._lane_turn_desire_toggle.action_item.set_state(turn_desire)
self._lane_turn_value_control.set_visible(turn_desire)
self._lagd_toggle.action_item.set_state(live_delay)
self._steer_delay_toggle.action_item.set_state(live_delay)
self._delay_control.set_visible(not live_delay)
new_step = int(round(100 / CV.MPH_TO_KPH)) if ui_state.is_metric else 100
if self._lane_turn_value_control.action_item.value_change_step != new_step:
self._lane_turn_value_control.action_item.value_change_step = new_step
lagd_desc = tr("Enable this for the car to learn and adapt its steering response time. Disable to use a fixed steering response time.")
delay_desc = tr("Let IQ.Pilot measure how long your steering takes to respond and keep that figure up to date. "
"Switch it off to pin the timing yourself.")
if live_delay:
lagd_desc += f"<br>{tr('Live Steer Delay:')} {ui_state.sm['liveDelay'].lateralDelay:.3f} s"
delay_desc += f"<br>{tr('Measured:')} {ui_state.sm['liveDelay'].lateralDelay:.3f} s"
elif ui_state.CP:
sw = float(ui_state.params.get("LagdToggleDelay", "0.2"))
sw = float(ui_state.params.get("IQSoftwareSteerDelay", "0.2"))
cp = ui_state.CP.steerActuatorDelay
lagd_desc += f"<br>{tr('Actuator Delay:')} {cp:.2f} s + {tr('Software Delay:')} {sw:.2f} s = {tr('Total Delay:')} {cp + sw:.2f} s"
self._lagd_toggle.set_description(lagd_desc)
delay_desc += f"<br>{tr('Rack:')} {cp:.2f} s + {tr('Offset:')} {sw:.2f} s = {tr('Total:')} {cp + sw:.2f} s"
self._steer_delay_toggle.set_description(delay_desc)
def _render(self, rect):
if self._current_panel == PanelType.LANE_CHANGE:
@@ -1176,7 +1177,7 @@ class IQDeveloperLayout(DeveloperLayout):
self._scroller.add_widget(item)
def _initialize_items(self):
self.error_log_btn = button_item(tr("Error Log"), tr("VIEW"), tr("View the error log for IQ.Pilot crashes."), callback=self._on_error_log_clicked)
self.error_log_btn = button_item(tr("Crash Log"), tr("VIEW"), tr("View the error log for IQ.Pilot crashes."), callback=self._on_error_log_clicked)
self.items: list = [self.error_log_btn]
@@ -1187,7 +1188,7 @@ class IQDeveloperLayout(DeveloperLayout):
def _on_error_log_closed(self, result, log_exists):
if result == DialogResult.CONFIRM and log_exists:
dialog2 = ConfirmDialog(tr("Would you like to delete this log?"), tr("Yes"), tr("No"), rich=False)
dialog2 = ConfirmDialog(tr("Delete this log?"), tr("Yes"), tr("No"), rich=False)
gui_app.set_modal_overlay(dialog2, callback=self._on_delete_confirm)
def _on_error_log_clicked(self):
@@ -1210,16 +1211,16 @@ class IQDeveloperLayout(DeveloperLayout):
UPDATES_DESCRIPTIONS = {
'disable_updates_offroad': tr_noop(
"When enabled, automatic software updates will be off.<br><b>This requires a reboot to take effect.</b>"
"Turns off over-the-air update checks entirely.<br><b>Reboot for this to take effect.</b>"
),
'disable_updates_onroad': tr_noop(
"Please enable \"Always Offroad\" mode or turn off the vehicle to adjust these toggles."
"Put the device in Always Offroad, or shut the car down, before touching these."
),
'install_mode_offroad': tr_noop(
"Choose whether updates only download and wait for confirmation, or download and install automatically after they are ready."
"Pick whether a ready update just waits for your go-ahead, or downloads and installs on its own."
),
'install_mode_onroad': tr_noop(
"Please enable \"Always Offroad\" mode or turn off the vehicle to adjust update install behavior."
"Put the device in Always Offroad, or shut the car down, before changing how updates install."
)
}
@@ -1239,7 +1240,7 @@ class IQSoftwareLayout(SoftwareLayout):
def __init__(self):
super().__init__()
self.disable_updates_toggle = toggle_item(
lambda: tr("Disable Updates"),
lambda: tr("Pause Updates"),
description="",
initial_state=ui_state.params.get_bool("DisableUpdates"),
callback=self._on_disable_updates_toggled,
@@ -1272,7 +1273,7 @@ class IQSoftwareLayout(SoftwareLayout):
self.disable_updates_toggle.action_item.set_state(ui_state.params.get_bool("DisableUpdates"))
def _on_disable_updates_toggled(self, enabled):
dialog = ConfirmDialog(tr("System reboot required for changes to take effect. Reboot now?"), tr("Reboot"))
dialog = ConfirmDialog(tr("This needs a reboot to apply. Restart now?"), tr("Reboot"))
gui_app.set_modal_overlay(dialog, callback=self._handle_reboot)
def _on_change_install_mode(self):
@@ -1318,11 +1319,11 @@ class IQSoftwareLayout(SoftwareLayout):
# single flat list: pinned branches first, then the rest alphabetically.
# prebuilt ("-prebuilt") branches are internal build outputs, not user-selectable.
pinned_nodes = [TreeNode(b, {'display_name': b}) for b in top_level_branches if b in branches]
other_nodes = [TreeNode(b, {'display_name': b}) for b in sorted(branches)
pinned_nodes = [PickerItem(b, {'display_name': b}) for b in top_level_branches if b in branches]
other_nodes = [PickerItem(b, {'display_name': b}) for b in sorted(branches)
if b not in top_level_branches and not b.endswith("-prebuilt")]
folders = [TreeFolder("", pinned_nodes + other_nodes)]
folders = [PickerGroup("", pinned_nodes + other_nodes)]
def _on_branch_selected(result):
if result == DialogResult.CONFIRM and self._branch_dialog is not None:
@@ -1333,7 +1334,7 @@ class IQSoftwareLayout(SoftwareLayout):
os.system("pkill -SIGUSR1 -f system.updated.updated")
self._branch_dialog = None
self._branch_dialog = TreeOptionDialog(tr("Select a branch"), folders, current_target, "",
self._branch_dialog = PickerDialog(tr("Select a branch"), folders, current_target, "",
on_exit=_on_branch_selected)
gui_app.set_modal_overlay(self._branch_dialog, callback=_on_branch_selected)
@@ -1387,12 +1388,12 @@ class IQDeviceLayout(DeviceLayout):
DeviceLayout._initialize_items(self)
# Using dual button with no right button for better alignment
self._always_offroad_btn = self._left_button(lambda: tr("Enable Always Offroad"), self._handle_always_offroad)
self._always_offroad_btn = self._left_button(lambda: tr("Force Offroad Mode"), self._handle_always_offroad)
self._force_onroad_btn = self._left_button(lambda: tr("Force On-Road (10 min)"), self._handle_force_onroad)
self._max_time_offroad = option_item(
title=lambda: tr("Max Time Offroad"),
description=lambda: tr("Device will automatically shutdown after set time once the engine is turned off.\n(30h is the default)"),
title=lambda: tr("Offroad Shutdown Timer"),
description=lambda: tr("Powers the device down once it has sat this long with the engine off.\n(30h by default)"),
param="MaxTimeOffroad",
min_value=0,
max_value=11,
@@ -1408,7 +1409,7 @@ class IQDeviceLayout(DeviceLayout):
)
self._device_wake_mode = multiple_button_item(
title=lambda: tr("Wake Up Behavior"),
title=lambda: tr("Boot State"),
description=self.wake_mode_description,
param="DeviceBootMode",
buttons=[lambda: tr("Default"), lambda: tr("Offroad")],
@@ -1418,8 +1419,8 @@ class IQDeviceLayout(DeviceLayout):
)
self._change_language_btn = button_item(lambda: tr("Change Language"), lambda: tr("CHANGE"), callback=self._show_language_dialog)
# Quiet Mode moved to the settings-hub top bar (bell bubble); this is just the dcam preview now.
self._driver_camera_btn = button_item(lambda: tr("Driver Camera Preview"), lambda: tr("PREVIEW"),
# Silent Mode moved to the settings-hub top bar (bell bubble); this is just the dcam preview now.
self._driver_camera_btn = button_item(lambda: tr("Driver Camera Check"), lambda: tr("PREVIEW"),
callback=self._show_driver_camera)
self._reg_and_training = dual_button_item(
@@ -1431,9 +1432,9 @@ class IQDeviceLayout(DeviceLayout):
self._reg_and_training.action_item.right_button.set_button_style(ButtonStyle.NORMAL)
self._onroad_uploads_and_reset_settings = dual_button_item(
left_text=lambda: tr("Onroad Uploads"),
left_text=lambda: tr("Upload While Driving"),
left_callback=lambda: ui_state.params.put_bool("OnroadUploads", not ui_state.params.get_bool("OnroadUploads")),
right_text=lambda: tr("Reset Settings"),
right_text=lambda: tr("Restore Defaults"),
right_callback=self._reset_settings
)
@@ -1505,9 +1506,9 @@ class IQDeviceLayout(DeviceLayout):
@staticmethod
def wake_mode_description() -> str:
def_str = tr("Default: Device will boot/wake-up normally & will be ready to engage.")
offrd_str = tr("Offroad: Device will be in Always Offroad mode after boot/wake-up.")
header = tr("Controls state of the device after boot/sleep.")
def_str = tr("Default: comes up ready to drive, engagement available straight away.")
offrd_str = tr("Offroad: lands in Always Offroad every time it boots or wakes.")
header = tr("Sets which state the device settles into after a boot or a wake from sleep.")
return f"{header}\n\n{def_str}\n{offrd_str}"
@@ -1522,7 +1523,7 @@ class IQDeviceLayout(DeviceLayout):
def _second_confirm(result: int):
if result == DialogResult.CONFIRM:
gui_app.set_modal_overlay(ConfirmDialog(
text=tr("The reset cannot be undone. You have been warned."),
text=tr("There's no undo once this runs — last chance to back out."),
confirm_text=tr("Confirm")
), callback=_do_reset)
@@ -1534,18 +1535,18 @@ class IQDeviceLayout(DeviceLayout):
@staticmethod
def _handle_always_offroad():
if ui_state.engaged:
gui_app.set_modal_overlay(alert_dialog(tr("Disengage to Enter Always Offroad Mode")))
gui_app.set_modal_overlay(alert_dialog(tr("Disengage before forcing offroad")))
return
_offroad_mode_state = ui_state.params.get_bool("OffroadMode")
_offroad_mode_str = tr("Are you sure you want to exit Always Offroad mode?") if _offroad_mode_state else \
tr("Are you sure you want to enter Always Offroad mode?")
_offroad_mode_state = ui_state.params.get_bool("IQAlwaysOffroad")
_offroad_mode_str = tr("Leave Always Offroad mode now?") if _offroad_mode_state else \
tr("Switch the device into Always Offroad mode?")
def _set_always_offroad(result: int):
if result == DialogResult.CONFIRM and not ui_state.engaged:
if _offroad_mode_state:
ui_state.params.put(FORCE_ONROAD_PARAM, 0)
ui_state.params.put_bool("OffroadMode", not _offroad_mode_state)
ui_state.params.put_bool("IQAlwaysOffroad", not _offroad_mode_state)
gui_app.set_modal_overlay(ConfirmDialog(_offroad_mode_str, tr("Confirm")), callback=lambda result: _set_always_offroad(result))
@@ -1570,14 +1571,14 @@ class IQDeviceLayout(DeviceLayout):
def _set_force_onroad(result: int):
if result == DialogResult.CONFIRM and not ui_state.engaged:
# Force On-Road relies on Always Offroad being active so expiry returns to offroad.
ui_state.params.put_bool("OffroadMode", True)
ui_state.params.put_bool("IQAlwaysOffroad", True)
ui_state.params.put(FORCE_ONROAD_PARAM, int(time.time()) + FORCE_ONROAD_DURATION_SEC)
gui_app.set_modal_overlay(ConfirmDialog(prompt, tr("Confirm")), callback=_set_force_onroad)
@staticmethod
def _update_max_time_offroad_label(value: int) -> str:
label = tr("Always On") if value == 0 else f"{value}" + tr("m") if value < 60 else f"{value // 60}" + tr("h")
label = tr("Never Sleep") if value == 0 else f"{value}" + tr("m") if value < 60 else f"{value // 60}" + tr("h")
label += tr(" (Default)") if value == 1800 else ""
return label
@@ -1585,13 +1586,13 @@ class IQDeviceLayout(DeviceLayout):
super()._update_state()
# Handle Always Offroad button
always_offroad = ui_state.params.get_bool("OffroadMode")
always_offroad = ui_state.params.get_bool("IQAlwaysOffroad")
now = int(time.time())
force_onroad_until = ui_state.params.get(FORCE_ONROAD_PARAM, return_default=True)
force_onroad_active = force_onroad_until > now
# Text & Color
offroad_mode_btn_text = tr("Exit Always Offroad") if always_offroad else tr("Enable Always Offroad")
offroad_mode_btn_text = tr("Exit Offroad Mode") if always_offroad else tr("Force Offroad Mode")
offroad_mode_btn_style = ButtonStyle.PRIMARY if always_offroad else ButtonStyle.DANGER
self._always_offroad_btn.action_item.left_button.set_text(offroad_mode_btn_text)
self._always_offroad_btn.action_item.left_button.set_button_style(offroad_mode_btn_style)
@@ -1666,7 +1667,7 @@ class IQDeviceLayout(DeviceLayout):
# ===== models =====
if gui_app.iqpilot_ui():
from openpilot.system.ui.iqpilot.widgets.list_view import button_item as button_item
from openpilot.system.ui.iqwidgets.widgets.list_view import button_item as button_item
_ACTIVE_BUNDLE_KEY = "ModelManager_ActiveBundle"
_DOWNLOAD_INDEX_KEY = "ModelManager_DownloadIndex"
@@ -1691,27 +1692,27 @@ class ModelsLayout(Widget):
def _initialize_items(self):
self.current_model_item = IQListItem(
title=tr("Current Model"),
title=tr("Active Model"),
description="",
action_item=NoElideButtonAction(tr("SELECT")),
action_item=WideButtonAction(tr("SELECT")),
callback=self._handle_current_model_clicked
)
self.supercombo_label = progress_item(tr("Driving Model"))
self.vision_label = progress_item(tr("Vision Model"))
self.policy_label = progress_item(tr("Policy Model"))
self.supercombo_label = progress_item(tr("Combined Model"))
self.vision_label = progress_item(tr("Vision Weights"))
self.policy_label = progress_item(tr("Policy Weights"))
self.refresh_item = button_item(tr("Refresh Model List"), tr("REFRESH"), "", self._on_refresh_models)
self.refresh_item = button_item(tr("Reload Model List"), tr("REFRESH"), "", self._on_refresh_models)
self.clear_cache_item = IQListItem(
title=tr("Clear Model Cache"),
title=tr("Purge Model Cache"),
description="",
action_item=NoElideButtonAction(tr("CLEAR")),
action_item=WideButtonAction(tr("CLEAR")),
callback=self._clear_cache
)
self.redownload_item = button_item(tr("Redownload Current Model"), tr("REDOWNLOAD"), "", self._redownload_model)
self.cancel_download_item = button_item(tr("Cancel Download"), tr("Cancel"), "", self._cancel_model_request)
self.cancel_download_item = button_item(tr("Stop Download"), tr("Cancel"), "", self._cancel_model_request)
self.items = [self.current_model_item, self.cancel_download_item, self.supercombo_label, self.vision_label,
self.policy_label, self.redownload_item, self.refresh_item, self.clear_cache_item]
@@ -1937,7 +1938,7 @@ class ModelsLayout(Widget):
@staticmethod
def _bundle_to_node(bundle):
return TreeNode(bundle.ref, {'display_name': bundle.displayName, 'short_name': bundle.internalName})
return PickerItem(bundle.ref, {'display_name': bundle.displayName, 'short_name': bundle.internalName})
def _get_folders(self, favorites):
bundles = self.model_manager.availableBundles
@@ -1945,14 +1946,14 @@ class ModelsLayout(Widget):
for bundle in bundles:
folders.setdefault(next((ov_ride.value for ov_ride in bundle.overrides if ov_ride.key == "folder"), ""), []).append(bundle)
folders_list = [TreeFolder("", [TreeNode("Default", {'display_name': tr("Default (CD210)"), 'short_name': "Default"})])]
folders_list = [PickerGroup("", [PickerItem("Default", {'display_name': tr("Default (CD210)"), 'short_name': "Default"})])]
for folder, folder_bundles in sorted(folders.items(), key=lambda x: max((bundle.index for bundle in x[1]), default=-1), reverse=True):
folder_bundles.sort(key=lambda bundle: bundle.index, reverse=True)
name = folder + (f" - (Updated: {m.group(1)})" if folder_bundles and (m := re.search(r'\(([^)]*)\)[^(]*$', folder_bundles[0].displayName)) else "")
folders_list.append(TreeFolder(name, [self._bundle_to_node(bundle) for bundle in folder_bundles]))
folders_list.append(PickerGroup(name, [self._bundle_to_node(bundle) for bundle in folder_bundles]))
if favorites and (fav_bundles := [bundle for bundle in bundles if bundle.ref in favorites]):
folders_list.insert(1, TreeFolder("Favorites", [self._bundle_to_node(bundle) for bundle in fav_bundles]))
folders_list.insert(1, PickerGroup("Favorites", [self._bundle_to_node(bundle) for bundle in fav_bundles]))
return folders_list
def _handle_current_model_clicked(self):
@@ -1961,7 +1962,7 @@ class ModelsLayout(Widget):
folders_list = self._get_folders(favorites)
active_ref = self.model_manager.activeBundle.ref if self._has_active_bundle_param() and self.model_manager.activeBundle else "Default"
self.model_dialog = TreeOptionDialog(tr("Select a Model"), folders_list, active_ref, "ModelManager_Favs",
self.model_dialog = PickerDialog(tr("Choose a Model"), folders_list, active_ref, "ModelManager_Favs",
get_folders_fn=self._get_folders, on_exit=self._on_model_selected)
gui_app.set_modal_overlay(self.model_dialog, callback=self._on_model_selected)
@@ -2006,7 +2007,7 @@ class ModelsLayout(Widget):
if not ui_state.is_offroad():
self.current_model_item.action_item.set_enabled(False)
self.current_model_item.set_description(tr("Only available when vehicle is off, or always offroad mode is on"))
self.current_model_item.set_description(tr("Reachable only with the car switched off or Always Offroad turned on."))
else:
self.current_model_item.action_item.set_enabled(True)
self.current_model_item.set_description("")
@@ -2164,7 +2165,7 @@ class IQSettingsLayout(OP.SettingsLayout):
# ===== vehicle_brands_base =====
class BrandSettings:
class BrandPanel:
"""Per-brand extra settings rows; brands without any simply don't register."""
def __init__(self):
@@ -2183,12 +2184,12 @@ _TUNING_BLURBS = (
)
class HyundaiSettings(BrandSettings):
class HyundaiSettings(BrandPanel):
def __init__(self):
super().__init__()
self.alpha_long_available = False
self.longitudinal_tuning_item = multiple_button_item(
tr("Custom Longitudinal Tuning"), "",
tr("Longitudinal Tune Profile"), "",
[tr("Off"), tr("Dynamic"), tr("Predictive")],
button_width=300, param="HyundaiLongitudinalTuning", inline=False,
callback=lambda index: ui_state.params.put("HyundaiLongitudinalTuning", index))
@@ -2226,14 +2227,14 @@ class HyundaiSettings(BrandSettings):
_UNSUPPORTED_FLAGS = SubaruFlags.GLOBAL_GEN2 | SubaruFlags.HYBRID
class SubaruSettings(BrandSettings):
class SubaruSettings(BrandPanel):
def __init__(self):
super().__init__()
self._supported = False
self.stop_and_go_toggle = toggle_item(tr("Stop and Go (Beta)"), "", param="SubaruStopAndGo",
self.stop_and_go_toggle = toggle_item(tr("Creep from Standstill (Beta)"), "", param="SubaruStopAndGo",
callback=lambda _: self.update_settings())
self.stop_and_go_manual_parking_brake_toggle = toggle_item(
tr("Stop and Go for Manual Parking Brake (Beta)"), "",
tr("Creep from Standstill — Manual Handbrake (Beta)"), "",
param="SubaruStopAndGoManualParkingBrake", callback=lambda _: self.update_settings())
self.items = [self.stop_and_go_toggle, self.stop_and_go_manual_parking_brake_toggle]
@@ -2248,7 +2249,7 @@ class SubaruSettings(BrandSettings):
if not self._supported:
return tr("Not available on this Subaru platform.")
if not ui_state.is_offroad():
return tr("Enable \"Always Offroad\" in Device panel, or turn vehicle off to toggle.")
return tr("Flip on Always Offroad from the Device panel, or power the car down, to change this.")
return ""
def update_settings(self):
@@ -2282,7 +2283,7 @@ def _speed_text(kmh: int) -> str:
return f"{round(kmh * KM_TO_MILE)} mph"
class TeslaSettings(BrandSettings):
class TeslaSettings(BrandPanel):
def __init__(self):
super().__init__()
self.coop_steering_toggle = toggle_item(tr("VTB (Virtual Torque Blending)"), "", param="TeslaCoopSteering")
@@ -2296,7 +2297,7 @@ class TeslaSettings(BrandSettings):
f"{tr('Active above {} only.').format(_speed_text(COOP_STEERING_MIN_KMH))}")
if not ui_state.is_offroad():
blocker = tr("Enable \"Always Offroad\" in Device panel, or turn vehicle off to toggle.")
blocker = tr("Flip on Always Offroad from the Device panel, or power the car down, to change this.")
body = f"<b>{blocker}</b><br><br>{body}"
self.coop_steering_toggle.set_description(body)
@@ -2305,11 +2306,11 @@ class TeslaSettings(BrandSettings):
# ===== vehicle_brands_toyota =====
class ToyotaSettings(BrandSettings):
class ToyotaSettings(BrandPanel):
def __init__(self):
super().__init__()
self.enforce_stock_longitudinal = toggle_item(
lambda: tr("Enforce Factory Longitudinal Control"),
lambda: tr("Keep Factory Gas and Brake"),
description=lambda: tr("Keeps gas and brakes with the factory Toyota system; IQ.Pilot steers only."),
initial_state=ui_state.params.get_bool("ToyotaEnforceStockLongitudinal"),
callback=self._on_toggled,
@@ -2360,7 +2361,7 @@ DESCRIPTIONS = {
}
class VolkswagenSettings(BrandSettings):
class VolkswagenSettings(BrandPanel):
def __init__(self):
super().__init__()
@@ -2444,7 +2445,7 @@ class VolkswagenSettings(BrandSettings):
# ===== vehicle_brands_factory =====
# Only brands that actually ship extra rows appear here; every other brand gets None.
_REGISTRY: dict[str, type[BrandSettings]] = {
_REGISTRY: dict[str, type[BrandPanel]] = {
"hyundai": HyundaiSettings,
"subaru": SubaruSettings,
"tesla": TeslaSettings,
@@ -2453,12 +2454,12 @@ _REGISTRY: dict[str, type[BrandSettings]] = {
}
def brand_settings_for(brand: str) -> BrandSettings | None:
def brand_settings_for(brand: str) -> BrandPanel | None:
cls = _REGISTRY.get(brand)
return cls() if cls else None
class BrandSettingsFactory:
class BrandPanelFactory:
"""Legacy shim for callers using the old factory name."""
create_brand_settings = staticmethod(brand_settings_for)
@@ -2502,7 +2503,7 @@ class VehicleSelection:
return bundle.get("name", "")
if ui_state.CP and ui_state.CP.carFingerprint != "MOCK":
return ui_state.CP.carFingerprint
return tr("No vehicle selected")
return tr("No vehicle set")
def force(self, platform_name: str) -> bool:
data = self.platforms.get(platform_name)
@@ -2515,22 +2516,22 @@ class VehicleSelection:
def clear():
ui_state.params.remove("CarPlatformBundle")
def picker_folders(self) -> list[TreeFolder]:
def node_for(name: str) -> TreeNode:
def picker_folders(self) -> list[PickerGroup]:
def node_for(name: str) -> PickerItem:
info = self.platforms[name]
years = ' '.join(map(str, info.get('year', [])))
return TreeNode(name, {
return PickerItem(name, {
'display_name': name,
'search_tags': f"{name} {info.get('make')} {years} {info.get('model', name)}",
})
names = sorted(self.platforms)
makes = sorted({self.platforms[n].get('make') for n in names})
return [TreeFolder(make, [node_for(n) for n in names if self.platforms[n].get('make') == make])
return [PickerGroup(make, [node_for(n) for n in names if self.platforms[n].get('make') == make])
for make in makes]
class PlatformSelector(Button):
class VehiclePicker(Button):
"""Row button that either clears a forced platform or opens the vehicle picker."""
def __init__(self, on_platform_change: Callable[[], None] | None = None):
@@ -2573,12 +2574,12 @@ class PlatformSelector(Button):
self._open_picker()
def _open_picker(self):
dialog = TreeOptionDialog(
tr("Select a vehicle"),
dialog = PickerDialog(
tr("Pick your vehicle"),
self.selection.picker_folders(),
search_prompt=tr("Search make or model"),
search_title=tr("Search your vehicle"),
search_subtitle=tr("Enter model year (e.g., 2021) and model (Toyota Corolla):"),
search_title=tr("Find your vehicle"),
search_subtitle=tr("Type a year then a model — for example, 2021 Toyota Corolla:"),
search_funcs=[lambda node: node.data.get('display_name', ''), lambda node: node.data.get('search_tags', '')],
)
done = partial(self._on_picked, dialog)
@@ -2588,8 +2589,8 @@ class PlatformSelector(Button):
def _on_picked(self, dialog, res):
if res != DialogResult.CONFIRM or not dialog.selection_ref:
return
when = tr("This setting will take effect immediately.") if ui_state.is_offroad else \
tr("This setting will take effect once the device enters offroad state.")
when = tr("Applies right away.") if ui_state.is_offroad else \
tr("Applies the next time the device goes offroad.")
def confirm(result):
if result == DialogResult.CONFIRM and self.selection.force(dialog.selection_ref):
@@ -2599,16 +2600,16 @@ class PlatformSelector(Button):
_LEGEND = (
(FingerprintStatus.AUTO, lambda: tr("Fingerprinted automatically")),
(FingerprintStatus.FORCED, lambda: tr("Manually selected fingerprint")),
(FingerprintStatus.NONE, lambda: tr("Not fingerprinted or manually selected")),
(FingerprintStatus.AUTO, lambda: tr("Detected automatically")),
(FingerprintStatus.FORCED, lambda: tr("Chosen manually")),
(FingerprintStatus.NONE, lambda: tr("Undetected and unset")),
)
class LegendWidget(Widget):
"""Explains the fingerprint status colours; the active row is highlighted."""
def __init__(self, platform_selector: PlatformSelector):
def __init__(self, platform_selector: VehiclePicker):
super().__init__()
self.set_rect(rl.Rectangle(0, 0, 0, 350))
self._selector = platform_selector
@@ -2618,9 +2619,9 @@ class LegendWidget(Widget):
def _render(self, rect):
x = rect.x + 20
y = rect.y + 20
rl.draw_text_ex(self._font, tr("Select vehicle to force fingerprint manually."), rl.Vector2(x, y), 40, 0, ink.CAPTION)
rl.draw_text_ex(self._font, tr("Pin a vehicle to skip auto-detection."), rl.Vector2(x, y), 40, 0, ink.CAPTION)
y += 80
rl.draw_text_ex(self._font, tr("Colors represent vehicle fingerprint status:"), rl.Vector2(x, y), 40, 0, ink.CAPTION)
rl.draw_text_ex(self._font, tr("Colour key for fingerprint state:"), rl.Vector2(x, y), 40, 0, ink.CAPTION)
y += 80
active = self._selector.status
@@ -2649,7 +2650,7 @@ class VehicleLayout(Widget):
super().__init__()
self._brand_settings = None
self._current_brand = None
self._platform_selector = PlatformSelector(self._on_vehicle_changed)
self._platform_selector = VehiclePicker(self._on_vehicle_changed)
self._vehicle_item = IQListItem(title=self._platform_selector.text, action_item=ButtonAction(text=tr("SELECT")),
callback=self._platform_selector._on_clicked)
self._legend_widget = LegendWidget(self._platform_selector)