mirror of
https://github.com/infiniteCable2/openpilot.git
synced 2026-07-25 19:32:03 +08:00
Merge branch 'master' of https://github.com/sunnypilot/sunnypilot into sync
This commit is contained in:
@@ -13,66 +13,10 @@ Join the official sunnypilot community forum to stay up to date with all the lat
|
||||
https://docs.sunnypilot.ai/ is your one stop shop for everything from features to installation to FAQ about the sunnypilot
|
||||
|
||||
## 🚘 Running on a dedicated device in a car
|
||||
* A supported device to run this software
|
||||
* a [comma three](https://comma.ai/shop/products/three) or a [C3X](https://comma.ai/shop/comma-3x)
|
||||
* This software
|
||||
* One of [the 325+ supported cars](https://github.com/sunnypilot/sunnypilot/blob/master/docs/CARS.md). We support Honda, Toyota, Hyundai, Nissan, Kia, Chrysler, Lexus, Acura, Audi, VW, Ford, and more. If your car is not supported but has adaptive cruise control and lane-keeping assist, it's likely able to run sunnypilot.
|
||||
* A [car harness](https://comma.ai/shop/products/car-harness) to connect to your car
|
||||
|
||||
Detailed instructions for [how to mount the device in a car](https://comma.ai/setup).
|
||||
First, check out this list of items you'll need to [get started](https://community.sunnypilot.ai/t/getting-started-using-sunnypilot-in-your-supported-car/251).
|
||||
|
||||
## Installation
|
||||
Please refer to [Recommended Branches](#recommended-branches) to find your preferred/supported branch. This guide will assume you want to install the latest `staging` branch.
|
||||
|
||||
### If you want to use our newest branches (our rewrite)
|
||||
> [!TIP]
|
||||
>You can see the rewrite state on our [rewrite project board](https://github.com/orgs/sunnypilot/projects/2), and to install the new branches, you can use the following links
|
||||
|
||||
* sunnypilot not installed or you installed a version before 0.8.17?
|
||||
1. [Factory reset/uninstall](https://github.com/commaai/openpilot/wiki/FAQ#how-can-i-reset-the-device) the previous software if you have another software/fork installed.
|
||||
2. After factory reset/uninstall and upon reboot, select `Custom Software` when given the option.
|
||||
3. Input the installation URL per [Recommended Branches](#recommended-branches). Example: ```https://staging.sunnypilot.ai```.
|
||||
4. Complete the rest of the installation following the onscreen instructions.
|
||||
|
||||
* sunnypilot already installed and you installed a version after 0.8.17?
|
||||
1. On the comma three/3X, go to `Settings` ▶️ `Software`.
|
||||
2. At the `Download` option, press `CHECK`. This will fetch the list of latest branches from sunnypilot.
|
||||
3. At the `Target Branch` option, press `SELECT` to open the Target Branch selector.
|
||||
4. Scroll to select the desired branch per Recommended Branches (see below). Example: `staging`
|
||||
|
||||
### Recommended Branches
|
||||
| Branch | Installation URL |
|
||||
|:---------------:|:---------------------------------------------:|
|
||||
| `release` | `https://release.sunnypilot.ai` |
|
||||
| `staging` | `https://staging.sunnypilot.ai` |
|
||||
| `dev` | `https://dev.sunnypilot.ai` |
|
||||
| `custom-branch` | `https://install.sunnypilot.ai/{branch_name}` |
|
||||
|
||||
> [!TIP]
|
||||
> You can use sunnypilot/targetbranch as an install URL. Example: 'sunnypilot/staging'.
|
||||
|
||||
> [!NOTE]
|
||||
> Do you require further assistance with software installation? Join the [sunnypilot community forum](https://community.sunnypilot.ai/new-topic?category=general/qa) and create a topic in the General/Q&A Category channel.
|
||||
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Older legacy branches</summary>
|
||||
|
||||
### If you want to use our older legacy branches (*not recommended*)
|
||||
|
||||
> [**IMPORTANT**]
|
||||
> It is recommended to [re-flash AGNOS](https://flash.comma.ai/) if you intend to downgrade from the new branches.
|
||||
> You can still restore the latest sunnylink backup made on the old branches.
|
||||
|
||||
| Branch | Installation URL |
|
||||
|:------------:|:--------------------------------:|
|
||||
| `release-c3` | https://release-c3.sunnypilot.ai |
|
||||
| `staging-c3` | https://staging-c3.sunnypilot.ai |
|
||||
| `dev-c3` | https://dev-c3.sunnypilot.ai |
|
||||
|
||||
</details>
|
||||
|
||||
Next, refer to the sunnypilot community forum for [installation instructions](https://community.sunnypilot.ai/t/read-before-installing-sunnypilot/254), as well as a complete list of [Recommended Branch Installations](https://community.sunnypilot.ai/t/recommended-branch-installations/235).
|
||||
|
||||
## 🎆 Pull Requests
|
||||
We welcome both pull requests and issues on GitHub. Bug fixes are encouraged.
|
||||
|
||||
@@ -5,8 +5,216 @@ This file is part of sunnypilot and is licensed under the MIT License.
|
||||
See the LICENSE.md file in the root directory for more details.
|
||||
"""
|
||||
from openpilot.selfdrive.ui.layouts.settings.device import DeviceLayout
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
from openpilot.system.ui.lib.application import gui_app
|
||||
from openpilot.system.ui.lib.multilang import tr
|
||||
from openpilot.system.ui.sunnypilot.widgets.list_view import option_item_sp, multiple_button_item_sp, button_item_sp, \
|
||||
dual_button_item_sp, Spacer
|
||||
from openpilot.system.ui.widgets import DialogResult
|
||||
from openpilot.system.ui.widgets.button import ButtonStyle
|
||||
from openpilot.system.ui.widgets.confirm_dialog import alert_dialog, ConfirmDialog
|
||||
from openpilot.system.ui.widgets.list_view import text_item
|
||||
from openpilot.system.ui.widgets.scroller_tici import LineSeparator
|
||||
|
||||
offroad_time_options = {
|
||||
0: 0,
|
||||
1: 5,
|
||||
2: 10,
|
||||
3: 15,
|
||||
4: 30,
|
||||
5: 60,
|
||||
6: 120,
|
||||
7: 180,
|
||||
8: 300,
|
||||
9: 600,
|
||||
10: 1440,
|
||||
11: 1800,
|
||||
}
|
||||
|
||||
|
||||
class DeviceLayoutSP(DeviceLayout):
|
||||
def __init__(self):
|
||||
DeviceLayout.__init__(self)
|
||||
self._scroller._line_separator = None
|
||||
|
||||
def _initialize_items(self):
|
||||
DeviceLayout._initialize_items(self)
|
||||
|
||||
# Using dual button with no right button for better alignment
|
||||
self._always_offroad_btn = dual_button_item_sp(
|
||||
left_text=lambda: tr("Enable Always Offroad"),
|
||||
left_callback=self._handle_always_offroad,
|
||||
right_text="",
|
||||
right_callback=None,
|
||||
)
|
||||
self._always_offroad_btn.action_item.right_button.set_visible(False)
|
||||
|
||||
self._max_time_offroad = option_item_sp(
|
||||
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)"),
|
||||
param="MaxTimeOffroad",
|
||||
min_value=0,
|
||||
max_value=11,
|
||||
value_change_step=1,
|
||||
on_value_changed=None,
|
||||
enabled=True,
|
||||
icon="",
|
||||
value_map=offroad_time_options,
|
||||
label_width=360,
|
||||
use_float_scaling=False,
|
||||
inline=True,
|
||||
label_callback=self._update_max_time_offroad_label
|
||||
)
|
||||
|
||||
self._device_wake_mode = multiple_button_item_sp(
|
||||
title=lambda: tr("Wake Up Behavior"),
|
||||
description=self.wake_mode_description,
|
||||
param="DeviceBootMode",
|
||||
buttons=[lambda: tr("Default"), lambda: tr("Offroad")],
|
||||
button_width=364,
|
||||
callback=None,
|
||||
inline=True,
|
||||
)
|
||||
|
||||
self._quiet_mode_and_dcam = dual_button_item_sp(
|
||||
left_text=lambda: tr("Quiet Mode"),
|
||||
right_text=lambda: tr("Driver Camera Preview"),
|
||||
left_callback=lambda: ui_state.params.put_bool("QuietMode", not ui_state.params.get_bool("QuietMode")),
|
||||
right_callback=self._show_driver_camera
|
||||
)
|
||||
self._quiet_mode_and_dcam.action_item.right_button.set_button_style(ButtonStyle.NORMAL)
|
||||
|
||||
self._reg_and_training = dual_button_item_sp(
|
||||
left_text=lambda: tr("Regulatory"),
|
||||
left_callback=self._on_regulatory,
|
||||
right_text=lambda: tr("Training Guide"),
|
||||
right_callback=self._on_review_training_guide
|
||||
)
|
||||
self._reg_and_training.action_item.right_button.set_button_style(ButtonStyle.NORMAL)
|
||||
|
||||
self._onroad_uploads_and_reset_settings = dual_button_item_sp(
|
||||
left_text=lambda: tr("Onroad Uploads"),
|
||||
left_callback=lambda: ui_state.params.put_bool("OnroadUploads", not ui_state.params.get_bool("OnroadUploads")),
|
||||
right_text=lambda: tr("Reset Settings"),
|
||||
right_callback=self._reset_settings
|
||||
)
|
||||
|
||||
self._power_buttons = dual_button_item_sp(
|
||||
left_text=lambda: tr("Reboot"),
|
||||
right_text=lambda: tr("Power Off"),
|
||||
left_callback=self._reboot_prompt,
|
||||
right_callback=self._power_off_prompt
|
||||
)
|
||||
|
||||
items = [
|
||||
text_item(lambda: tr("Dongle ID"), self._params.get("DongleId") or (lambda: tr("N/A"))),
|
||||
LineSeparator(),
|
||||
text_item(lambda: tr("Serial"), self._params.get("HardwareSerial") or (lambda: tr("N/A"))),
|
||||
LineSeparator(),
|
||||
self._pair_device_btn,
|
||||
LineSeparator(),
|
||||
self._reset_calib_btn,
|
||||
LineSeparator(),
|
||||
button_item_sp(lambda: tr("Change Language"), lambda: tr("CHANGE"), callback=self._show_language_dialog),
|
||||
LineSeparator(),
|
||||
self._device_wake_mode,
|
||||
LineSeparator(),
|
||||
self._max_time_offroad,
|
||||
LineSeparator(height=10),
|
||||
self._quiet_mode_and_dcam,
|
||||
self._reg_and_training,
|
||||
self._onroad_uploads_and_reset_settings,
|
||||
Spacer(10),
|
||||
LineSeparator(height=10),
|
||||
self._power_buttons,
|
||||
]
|
||||
|
||||
return items
|
||||
|
||||
def _offroad_transition(self):
|
||||
self._power_buttons.action_item.right_button.set_visible(ui_state.is_offroad())
|
||||
|
||||
@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.")
|
||||
|
||||
return f"{header}\n\n{def_str}\n{offrd_str}"
|
||||
|
||||
@staticmethod
|
||||
def _reset_settings():
|
||||
def _do_reset(result: int):
|
||||
if result == DialogResult.CONFIRM:
|
||||
for _key in ui_state.params.all_keys():
|
||||
ui_state.params.remove(_key)
|
||||
HARDWARE.reboot()
|
||||
|
||||
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."),
|
||||
confirm_text=tr("Confirm")
|
||||
), callback=_do_reset)
|
||||
|
||||
gui_app.set_modal_overlay(ConfirmDialog(
|
||||
text=tr("Are you sure you want to reset all sunnypilot settings to default? Once the settings are reset, there is no going back."),
|
||||
confirm_text=tr("Reset")
|
||||
), callback=_second_confirm)
|
||||
|
||||
@staticmethod
|
||||
def _handle_always_offroad():
|
||||
if ui_state.engaged:
|
||||
gui_app.set_modal_overlay(alert_dialog(tr("Disengage to Enter Always Offroad Mode")))
|
||||
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?")
|
||||
|
||||
def _set_always_offroad(result: int):
|
||||
if result == DialogResult.CONFIRM and not ui_state.engaged:
|
||||
ui_state.params.put_bool("OffroadMode", not _offroad_mode_state)
|
||||
|
||||
gui_app.set_modal_overlay(ConfirmDialog(_offroad_mode_str, tr("Confirm")), callback=lambda result: _set_always_offroad(result))
|
||||
|
||||
@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(" (Default)") if value == 1800 else ""
|
||||
return label
|
||||
|
||||
def _update_state(self):
|
||||
super()._update_state()
|
||||
|
||||
# Handle Always Offroad button
|
||||
always_offroad = ui_state.params.get_bool("OffroadMode")
|
||||
|
||||
# Text & Color
|
||||
offroad_mode_btn_text = tr("Exit Always Offroad") if always_offroad else tr("Enable Always Offroad")
|
||||
offroad_mode_btn_style = ButtonStyle.NORMAL 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)
|
||||
|
||||
# Position
|
||||
if self._scroller._items.__contains__(self._always_offroad_btn):
|
||||
self._scroller._items.remove(self._always_offroad_btn)
|
||||
if ui_state.is_offroad() and not always_offroad:
|
||||
self._scroller._items.insert(len(self._scroller._items) - 1, self._always_offroad_btn)
|
||||
elif not ui_state.is_offroad():
|
||||
self._scroller._items.insert(0, self._always_offroad_btn)
|
||||
|
||||
# Quiet Mode button
|
||||
self._quiet_mode_and_dcam.action_item.left_button.set_button_style(ButtonStyle.PRIMARY if ui_state.params.get_bool("QuietMode") else ButtonStyle.NORMAL)
|
||||
|
||||
# Onroad Uploads
|
||||
self._onroad_uploads_and_reset_settings.action_item.left_button.set_button_style(
|
||||
ButtonStyle.PRIMARY if ui_state.params.get_bool("OnroadUploads") else ButtonStyle.NORMAL
|
||||
)
|
||||
|
||||
# Offroad only buttons
|
||||
self._quiet_mode_and_dcam.action_item.right_button.set_enabled(ui_state.is_offroad())
|
||||
self._reg_and_training.action_item.left_button.set_enabled(ui_state.is_offroad())
|
||||
self._reg_and_training.action_item.right_button.set_enabled(ui_state.is_offroad())
|
||||
self._onroad_uploads_and_reset_settings.action_item.right_button.set_enabled(ui_state.is_offroad())
|
||||
|
||||
@@ -9,29 +9,29 @@ from enum import IntEnum
|
||||
|
||||
import pyray as rl
|
||||
from openpilot.selfdrive.ui.layouts.settings import settings as OP
|
||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.device import DeviceLayoutSP
|
||||
from openpilot.selfdrive.ui.layouts.settings.firehose import FirehoseLayout
|
||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.software import SoftwareLayoutSP
|
||||
from openpilot.selfdrive.ui.layouts.settings.toggles import TogglesLayout
|
||||
from openpilot.selfdrive.ui.layouts.settings.ictoggles import ICTogglesLayout
|
||||
from openpilot.system.ui.lib.application import gui_app, MousePos
|
||||
from openpilot.system.ui.lib.multilang import tr_noop
|
||||
from openpilot.system.ui.sunnypilot.lib.styles import style
|
||||
from openpilot.system.ui.widgets.scroller_tici import Scroller
|
||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||
from openpilot.system.ui.lib.wifi_manager import WifiManager
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.cruise import CruiseLayout
|
||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.developer import DeveloperLayoutSP
|
||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.device import DeviceLayoutSP
|
||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.display import DisplayLayout
|
||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.models import ModelsLayout
|
||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.network import NetworkUISP
|
||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.sunnylink import SunnylinkLayout
|
||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.osm import OSMLayout
|
||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.software import SoftwareLayoutSP
|
||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.steering import SteeringLayout
|
||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.sunnylink import SunnylinkLayout
|
||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.trips import TripsLayout
|
||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle import VehicleLayout
|
||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.steering import SteeringLayout
|
||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.cruise import CruiseLayout
|
||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.visuals import VisualsLayout
|
||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.display import DisplayLayout
|
||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.developer import DeveloperLayoutSP
|
||||
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
|
||||
from openpilot.system.ui.lib.wifi_manager import WifiManager
|
||||
from openpilot.system.ui.sunnypilot.lib.styles import style
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.system.ui.widgets.scroller_tici import Scroller
|
||||
|
||||
# from openpilot.selfdrive.ui.sunnypilot.layouts.settings.navigation import NavigationLayout
|
||||
|
||||
|
||||
@@ -73,3 +73,12 @@ class UIStateSP:
|
||||
self.developer_ui = self.params.get("DevUIInfo")
|
||||
self.rainbow_path = self.params.get_bool("RainbowMode")
|
||||
self.chevron_metrics = self.params.get("ChevronInfo")
|
||||
|
||||
|
||||
class DeviceSP:
|
||||
def __init__(self):
|
||||
self._params = Params()
|
||||
|
||||
def _set_awake(self, on: bool):
|
||||
if on and self._params.get("DeviceBootMode", return_default=True) == 1:
|
||||
self._params.put_bool("OffroadMode", True)
|
||||
|
||||
@@ -12,7 +12,7 @@ from openpilot.selfdrive.ui.lib.prime_state import PrimeState
|
||||
from openpilot.system.ui.lib.application import gui_app
|
||||
from openpilot.system.hardware import HARDWARE, PC
|
||||
|
||||
from openpilot.selfdrive.ui.sunnypilot.ui_state import UIStateSP
|
||||
from openpilot.selfdrive.ui.sunnypilot.ui_state import UIStateSP, DeviceSP
|
||||
|
||||
BACKLIGHT_OFFROAD = 65 if HARDWARE.get_device_type() == "mici" else 50
|
||||
|
||||
@@ -207,8 +207,9 @@ class UIState(UIStateSP):
|
||||
self.onroad_screen_timeout = self.params.get_bool("DisableScreenTimer")
|
||||
|
||||
|
||||
class Device:
|
||||
class Device(DeviceSP):
|
||||
def __init__(self):
|
||||
DeviceSP.__init__(self)
|
||||
self._ignition = False
|
||||
self._interaction_time: float = -1
|
||||
self._override_interactive_timeout: int | None = None
|
||||
@@ -305,6 +306,7 @@ class Device:
|
||||
|
||||
def _set_awake(self, on: bool):
|
||||
if on != self._awake:
|
||||
DeviceSP._set_awake(self, on)
|
||||
self._awake = on
|
||||
cloudlog.debug(f"setting display power {int(on)}")
|
||||
HARDWARE.set_display_power(on)
|
||||
|
||||
@@ -11,15 +11,29 @@ from openpilot.common.params import Params
|
||||
from openpilot.system.ui.lib.application import gui_app, MousePos, FontWeight
|
||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||
from openpilot.system.ui.sunnypilot.widgets.toggle import ToggleSP
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.system.ui.widgets.button import Button, ButtonStyle
|
||||
from openpilot.system.ui.widgets.label import gui_label
|
||||
from openpilot.system.ui.widgets.list_view import ListItem, ToggleAction, ItemAction, MultipleButtonAction, ButtonAction, \
|
||||
_resolve_value, BUTTON_WIDTH, BUTTON_HEIGHT, TEXT_PADDING
|
||||
_resolve_value, BUTTON_WIDTH, BUTTON_HEIGHT, TEXT_PADDING, DualButtonAction
|
||||
from openpilot.system.ui.widgets.scroller_tici import LineSeparator, LINE_COLOR, LINE_PADDING
|
||||
from openpilot.system.ui.sunnypilot.lib.styles import style
|
||||
from openpilot.system.ui.sunnypilot.widgets.option_control import OptionControlSP, LABEL_WIDTH
|
||||
|
||||
|
||||
class Spacer(Widget):
|
||||
def __init__(self, height: int = 1):
|
||||
super().__init__()
|
||||
self._rect = rl.Rectangle(0, 0, 0, height)
|
||||
|
||||
def set_parent_rect(self, parent_rect: rl.Rectangle) -> None:
|
||||
super().set_parent_rect(parent_rect)
|
||||
self._rect.width = parent_rect.width
|
||||
|
||||
def _render(self, _):
|
||||
rl.draw_rectangle(int(self._rect.x), int(self._rect.y), int(self._rect.x + self._rect.width), int(self._rect.y), rl.Color(0,0,0,0))
|
||||
|
||||
|
||||
class ToggleActionSP(ToggleAction):
|
||||
def __init__(self, initial_state: bool = False, width: int = style.TOGGLE_WIDTH, enabled: bool | Callable[[], bool] = True,
|
||||
callback: Callable[[bool], None] | None = None, param: str | None = None):
|
||||
@@ -84,6 +98,33 @@ class ButtonActionSP(ButtonAction):
|
||||
return pressed
|
||||
|
||||
|
||||
class DualButtonActionSP(DualButtonAction):
|
||||
def __init__(self, left_text: str | Callable[[], str], right_text: str | Callable[[], str], left_callback: Callable = None,
|
||||
right_callback: Callable = None, enabled: bool | Callable[[], bool] = True, border_radius: int = 15):
|
||||
DualButtonAction.__init__(self, left_text, right_text, left_callback, right_callback, enabled)
|
||||
self.left_button._border_radius = self.right_button._border_radius = border_radius
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
button_spacing = 20
|
||||
button_height = 150
|
||||
button_width = (rect.width - button_spacing) / 2
|
||||
button_y = rect.y + (rect.height - button_height) / 2
|
||||
|
||||
left_rect = rl.Rectangle(rect.x, button_y, button_width, button_height)
|
||||
right_rect = rl.Rectangle(rect.x + button_width + button_spacing, button_y, button_width, button_height)
|
||||
|
||||
# expand one to full width if other is not visible
|
||||
if not self.left_button.is_visible:
|
||||
right_rect.x = rect.x
|
||||
right_rect.width = rect.width
|
||||
elif not self.right_button.is_visible:
|
||||
left_rect.width = rect.width
|
||||
|
||||
# Render buttons
|
||||
self.left_button.render(left_rect)
|
||||
self.right_button.render(right_rect)
|
||||
|
||||
|
||||
class MultipleButtonActionSP(MultipleButtonAction):
|
||||
def __init__(self, buttons: list[str | Callable[[], str]], button_width: int, selected_index: int = 0, callback: Callable = None,
|
||||
param: str | None = None):
|
||||
@@ -251,13 +292,13 @@ class ListItemSP(ListItem):
|
||||
item_y = self._rect.y + (style.ITEM_BASE_HEIGHT - self._text_size.y) // 2 if self.inline else self._rect.y + style.ITEM_PADDING * 1.5
|
||||
rl.draw_text_ex(self._font, self.title, rl.Vector2(text_x, item_y), style.ITEM_TEXT_FONT_SIZE, 0, self.title_color)
|
||||
|
||||
# Draw right item if present
|
||||
if self.action_item:
|
||||
right_rect = self.get_right_item_rect(self._rect)
|
||||
if self.action_item.render(right_rect) and self.action_item.enabled:
|
||||
# Right item was clicked/activated
|
||||
if self.callback:
|
||||
self.callback()
|
||||
# Draw right item if present
|
||||
if self.action_item:
|
||||
right_rect = self.get_right_item_rect(self._rect)
|
||||
if self.action_item.render(right_rect) and self.action_item.enabled:
|
||||
# Right item was clicked/activated
|
||||
if self.callback:
|
||||
self.callback()
|
||||
|
||||
# Draw description if visible
|
||||
if self.description_visible:
|
||||
@@ -310,6 +351,13 @@ def button_item_sp(title: str | Callable[[], str], button_text: str | Callable[[
|
||||
return ListItemSP(title=title, description=description, action_item=action, callback=callback)
|
||||
|
||||
|
||||
def dual_button_item_sp(left_text: str | Callable[[], str], right_text: str | Callable[[], str], left_callback: Callable = None,
|
||||
right_callback: Callable = None, description: str | Callable[[], str] | None = None,
|
||||
enabled: bool | Callable[[], bool] = True, border_radius: int = 15) -> ListItemSP:
|
||||
action = DualButtonActionSP(left_text, right_text, left_callback, right_callback, enabled, border_radius)
|
||||
return ListItemSP(title="", description=description, action_item=action)
|
||||
|
||||
|
||||
class LineSeparatorSP(LineSeparator):
|
||||
def __init__(self, height: int = 1):
|
||||
super().__init__()
|
||||
|
||||
Reference in New Issue
Block a user