[TIZI/TICI] ui: Steering panel (#1540)

* commaai/openpilot:d05cb31e2e916fba41ba8167030945f427fd811b

* bump opendbc

* bump opendbc

* bump opendbc

* bump opendbc

* bump opendbc

* sunnypilot: remove Qt

* cabana: revert to stock Qt

* commaai/openpilot:5198b1b079c37742c1050f02ce0aa6dd42b038b9

* commaai/openpilot:954b567b9ba0f3d1ae57d6aa7797fa86dd92ec6e

* commaai/openpilot:7534b2a160faa683412c04c1254440e338931c5e

* sum more

* bump opendbc

* not yet

* should've been symlink'ed

* raylib says wut

* quiet mode back

* more fixes

* no more

* too extra red diff on the side

* need to bring this back

* too extra

* let's update docs here

* Revert "let's update docs here"

This reverts commit 51fe03cd5121e6fdf14657b2c33852c34922b851.

* param to control stock vs sp ui

* init styles

* SP Toggles

* Lint

* optimizations

* multi-button

* Lint

* param to control stock vs sp ui

* init styles

* SP Toggles

* Lint

* optimizations

* Panels. With Icons. And Scroller.

* patience, grasshopper

* more patience, grasshopper

* sp raylib preview

* fix callback

* fix ui preview

* add ui previews

* Option Control

* Need this

* sunnylink state

* introducing ui_state_sp for py

* poll from ui_state_sp

* cloudlog & ruff

* param to control stock vs sp ui

* better

* sponsor & pairing qr

* init panel elements

* backup & restore

* fruit loops

* update

* enable, disable, enable, disable

* handle layout updates

* not needed

* add ui_update callback

* change it up

* better padding

* this

* support for next line multi-button

* uhh

* disabled colors

* better

* listitem -> listitemsp

* listitem -> listitemsp

* Revert "add ui_update callback"

This reverts commit 4da32cc0097434aab0aa6a3c35465eabb23c8958.

* add show_description method

* remove padding from line separator.
like, WHY? 😩😩

* simplify

* I. SAID. SIMPLIFY.

* AAARGGGGGG.....

* option control value fix

* left button

* more init

* simple_button, yay

* simple_button, yay

* more more init

* scroller -> scroller_tici

* scroller -> scroller_tici

* ui: `GuiApplicationExt`

* simple button

* simple button

* add to readme

* use gui_app.sunnypilot_ui()

* use gui_app.sunnypilot_ui()

* use gui_app.sunnypilot_ui()

* optimizations

* change order

* subpanels

* lane change timer

* update toggles

* update toggles

* add cp_sp to ui_state

* mads

* add cp_sp to ui_state_sp

* fix ui crash

* update params

* ui_state changes

* descriptions

* Update scroller.py

* wrong pr

* listen nessa, yes nessa

* i've got something to confessa

* some bs

* # Conflicts:
#	selfdrive/ui/sunnypilot/layouts/settings/steering.py
#	selfdrive/ui/sunnypilot/layouts/settings/vehicle.py
#	system/ui/sunnypilot/widgets/list_view.py

* sine

* more

* Delete selfdrive/ui/sunnypilot/layouts/vehicle_settings/platform_selector.py

* Update styles.py

* allow sunnylink

* nah

* more

* sync

* lint

* revert

* button is always shown, just disabled if off

* revert

* Fix SimpleButtonActionSP not respecting enabled state

* some changes

* new pr

* some more

* ui: `ButtonSP`

* slight cleanup

* fixes

* no

* fix

* much better

* ui: `LineSeparatorSP`

* ui: add `inline` to `option_item_sp`

* small cleanup mads

* lane change

* allow changing title

* torque settings init

* lont

* more logic

* import directly

* more

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
Co-authored-by: DevTekVE <devtekve@gmail.com>
Co-authored-by: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com>
Co-authored-by: discountchubbs <alexgrant990@gmail.com>
This commit is contained in:
Nayan
2026-01-23 22:55:50 -05:00
committed by GitHub
parent a0a5c9b9ca
commit 2e788ae54d
4 changed files with 456 additions and 5 deletions
@@ -4,27 +4,147 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
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.common.params import Params
from cereal import car
from enum import IntEnum
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp, simple_button_item_sp, option_item_sp, LineSeparatorSP
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.system.ui.widgets import Widget
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.steering_sub_layouts.lane_change_settings import LaneChangeSettingsLayout
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.steering_sub_layouts.mads_settings import MadsSettingsLayout
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.steering_sub_layouts.torque_settings import TorqueSettingsLayout
class PanelType(IntEnum):
STEERING = 0
MADS = 1
LANE_CHANGE = 2
TORQUE_CONTROL = 3
class SteeringLayout(Widget):
def __init__(self):
super().__init__()
self._params = Params()
self._current_panel = PanelType.STEERING
self._lane_change_settings_layout = LaneChangeSettingsLayout(lambda: self._set_current_panel(PanelType.STEERING))
self._mads_settings_layout = MadsSettingsLayout(lambda: self._set_current_panel(PanelType.STEERING))
self._torque_control_layout = TorqueSettingsLayout(lambda: self._set_current_panel(PanelType.STEERING))
items = self._initialize_items()
self._scroller = Scroller(items, line_separator=True, spacing=0)
self._scroller = Scroller(items, line_separator=False, spacing=0)
def _initialize_items(self):
items = [
self._mads_base_desc = tr("Enable the beloved MADS feature. " +
"Disable toggle to revert back to stock sunnypilot engagement/disengagement.")
self._mads_limited_desc = tr("This platform supports limited MADS settings.")
self._mads_full_desc = tr("This platform supports all MADS settings.")
self._mads_check_compat_desc = tr("Start the vehicle to check vehicle compatibility.")
self._mads_toggle = toggle_item_sp(
param="Mads",
title=lambda: tr("Modular Assistive Driving System (MADS)"),
description=self._mads_base_desc,
)
self._mads_settings_button = simple_button_item_sp(
button_text=lambda: tr("Customize MADS"),
button_width=800,
callback=lambda: self._set_current_panel(PanelType.MADS)
)
self._lane_change_settings_button = simple_button_item_sp(
button_text=lambda: tr("Customize Lane Change"),
button_width=800,
callback=lambda: self._set_current_panel(PanelType.LANE_CHANGE)
)
self._blinker_control_toggle = toggle_item_sp(
param="BlinkerPauseLateralControl",
description=lambda: tr("Pause lateral control with blinker when traveling below the desired speed selected."),
title=lambda: tr("Pause Lateral Control with Blinker"),
)
self._blinker_control_options = option_item_sp(
param="BlinkerMinLateralControlSpeed",
title=lambda: tr("Minimum Speed to Pause Lateral Control"),
min_value=0,
max_value=255,
value_change_step=5,
description="",
label_callback=lambda speed: f'{speed} {"km/h" if ui_state.is_metric else "mph"}',
)
self._torque_control_toggle = toggle_item_sp(
param="EnforceTorqueControl",
title=lambda: tr("Enforce Torque Lateral Control"),
description=lambda: tr("Enable this to enforce sunnypilot to steer with Torque lateral control."),
)
self._torque_customization_button = simple_button_item_sp(
button_text=lambda: tr("Customize Torque Params"),
button_width=850,
callback=lambda: self._set_current_panel(PanelType.TORQUE_CONTROL)
)
self._nnlc_toggle = toggle_item_sp(
param="NeuralNetworkLateralControl",
title=lambda: tr("Neural Network Lateral Control (NNLC)"),
description=""
)
items = [
self._mads_toggle,
self._mads_settings_button,
LineSeparatorSP(40),
self._lane_change_settings_button,
LineSeparatorSP(40),
self._blinker_control_toggle,
self._blinker_control_options,
LineSeparatorSP(40),
self._torque_control_toggle,
self._torque_customization_button,
LineSeparatorSP(40),
self._nnlc_toggle,
]
return items
def _set_current_panel(self, panel: PanelType):
self._current_panel = panel
def _update_state(self):
super()._update_state()
torque_allowed = True
if ui_state.CP is not None:
mads_main_desc = self._mads_limited_desc if self._mads_settings_layout._mads_limited_settings() else self._mads_full_desc
self._mads_toggle.set_description(f"<b>{mads_main_desc}</b><br><br>{self._mads_base_desc}")
if ui_state.CP.steerControlType == car.CarParams.SteerControlType.angle:
ui_state.params.remove("EnforceTorqueControl")
ui_state.params.remove("NeuralNetworkLateralControl")
torque_allowed = False
else:
self._mads_toggle.set_description(f"<b>{self._mads_check_compat_desc}</b><br><br>{self._mads_base_desc}")
ui_state.params.remove("EnforceTorqueControl")
ui_state.params.remove("NeuralNetworkLateralControl")
torque_allowed = False
self._mads_toggle.action_item.set_enabled(ui_state.is_offroad())
self._mads_settings_button.action_item.set_enabled(ui_state.is_offroad() and self._mads_toggle.action_item.get_state())
self._blinker_control_options.set_visible(self._blinker_control_toggle.action_item.get_state())
enforce_torque_enabled = self._torque_control_toggle.action_item.get_state()
nnlc_enabled = self._nnlc_toggle.action_item.get_state()
self._nnlc_toggle.action_item.set_enabled(ui_state.is_offroad() and torque_allowed and not enforce_torque_enabled)
self._torque_control_toggle.action_item.set_enabled(ui_state.is_offroad() and torque_allowed and not nnlc_enabled)
self._torque_customization_button.action_item.set_enabled(self._torque_control_toggle.action_item.get_state())
def _render(self, rect):
self._scroller.render(rect)
if self._current_panel == PanelType.LANE_CHANGE:
self._lane_change_settings_layout.render(rect)
elif self._current_panel == PanelType.MADS:
self._mads_settings_layout.render(rect)
elif self._current_panel == PanelType.TORQUE_CONTROL:
self._torque_control_layout.render(rect)
else:
self._scroller.render(rect)
def show_event(self):
self._set_current_panel(PanelType.STEERING)
self._scroller.show_event()
@@ -0,0 +1,81 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
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 collections.abc import Callable
import pyray as rl
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp, option_item_sp, LineSeparatorSP
from openpilot.system.ui.widgets.network import NavButton
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.system.ui.widgets import Widget
from openpilot.sunnypilot.selfdrive.controls.lib.auto_lane_change import AutoLaneChangeMode
class LaneChangeSettingsLayout(Widget):
def __init__(self, back_btn_callback: Callable):
super().__init__()
self._back_button = NavButton(tr("Back"))
self._back_button.set_click_callback(back_btn_callback)
items = self._initialize_items()
self._scroller = Scroller(items, line_separator=False, spacing=0)
def _initialize_items(self):
self._lane_change_timer = option_item_sp(
title=lambda: tr("Auto Lane Change by Blinker"),
param="AutoLaneChangeTimer",
description=lambda: tr("Set a timer to delay the auto lane change operation when the blinker is used. " +
"No nudge on the steering wheel is required to auto lane change if a timer is set. Default is Nudge.<br>" +
"Please use caution when using this feature. Only use the blinker when traffic and road conditions permit."),
min_value=-1,
max_value=5,
value_change_step=1,
label_callback=(lambda x:
tr("Off") if x == -1 else
tr("Nudge") if x == 0 else
tr("Nudgeless") if x == 1 else
f"0.5 {tr('s')}" if x == 2 else
f"1 {tr('s')}" if x == 3 else
f"2 {tr('s')}" if x == 4 else
f"3 {tr('s')}")
)
self._bsm_delay = toggle_item_sp(
param="AutoLaneChangeBsmDelay",
title=lambda: tr("Auto Lane Change: Delay with Blind Spot"),
description=lambda: tr("Toggle to enable a delay timer for seamless lane changes when blind spot monitoring " +
"(BSM) detects a obstructing vehicle, ensuring safe maneuvering."),
)
items = [
self._lane_change_timer,
LineSeparatorSP(40),
self._bsm_delay,
]
return items
def _update_state(self):
super()._update_state()
self._update_toggles()
def _render(self, rect):
self._back_button.set_position(self._rect.x, self._rect.y + 20)
self._back_button.render()
# subtract button
content_rect = rl.Rectangle(rect.x, rect.y + self._back_button.rect.height + 40, rect.width, rect.height - self._back_button.rect.height - 40)
self._scroller.render(content_rect)
def show_event(self):
self._scroller.show_event()
def _update_toggles(self):
enable_bsm = ui_state.CP and ui_state.CP.enableBsm
if not enable_bsm and ui_state.params.get_bool("AutoLaneChangeBsmDelay"):
ui_state.params.remove("AutoLaneChangeBsmDelay")
self._bsm_delay.action_item.set_enabled(enable_bsm and ui_state.params.get("AutoLaneChangeTimer", return_default=True) > AutoLaneChangeMode.NUDGE)
@@ -0,0 +1,135 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
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 collections.abc import Callable
import pyray as rl
from opendbc.sunnypilot.car.tesla.values import TeslaFlagsSP
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.widgets.network import NavButton
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.system.ui.sunnypilot.widgets.list_view import multiple_button_item_sp, toggle_item_sp
MADS_STEERING_MODE_OPTIONS = [
(tr("Remain Active"), tr_noop("Remain Active: ALC will remain active when the brake pedal is pressed.")),
(tr("Pause"), tr_noop("Pause: ALC will pause when the brake pedal is pressed.")),
(tr("Disengage"), tr_noop("Disengage: ALC will disengage when the brake pedal is pressed.")),
]
MADS_MAIN_CRUISE_BASE_DESC = tr("Note: For vehicles without LFA/LKAS button, disabling this will prevent lateral control engagement.")
MADS_UNIFIED_ENGAGEMENT_MODE_BASE_DESC = "{engage}<br><h4>{note}</h4>".format(
engage=tr("Engage lateral and longitudinal control with cruise control engagement."),
note=tr("Note: Once lateral control is engaged via UEM, it will remain engaged until it is manually disabled via the MADS button or car shut off."),
)
STATUS_CHECK_COMPATIBILITY = tr("Start the vehicle to check vehicle compatibility.")
DEFAULT_TO_OFF = tr("This feature defaults to OFF, and does not allow selection due to vehicle limitations.")
DEFAULT_TO_ON = tr("This feature defaults to ON, and does not allow selection due to vehicle limitations.")
STATUS_DISENGAGE_ONLY = tr("This platform only supports Disengage mode due to vehicle limitations.")
class MadsSettingsLayout(Widget):
def __init__(self, back_btn_callback: Callable):
super().__init__()
self._back_button = NavButton(tr("Back"))
self._back_button.set_click_callback(back_btn_callback)
self._initialize_items()
self._scroller = Scroller(self.items, line_separator=True, spacing=0)
def _initialize_items(self):
self._main_cruise_toggle = toggle_item_sp(
title=lambda: tr("Toggle with Main Cruise"),
description=MADS_MAIN_CRUISE_BASE_DESC,
param="MadsMainCruiseAllowed",
)
self._unified_engagement_toggle = toggle_item_sp(
title=lambda: tr("Unified Engagement Mode (UEM)"),
description=MADS_UNIFIED_ENGAGEMENT_MODE_BASE_DESC,
param="MadsUnifiedEngagementMode"
)
self._steering_mode = multiple_button_item_sp(
param="MadsSteeringMode",
title=lambda: tr("Steering Mode on Brake Pedal"),
description="",
buttons=[opt[0] for opt in MADS_STEERING_MODE_OPTIONS],
inline=False,
button_width=350,
callback=self._update_steering_mode_description,
)
self.items = [
self._main_cruise_toggle,
self._unified_engagement_toggle,
self._steering_mode,
]
def _update_state(self):
super()._update_state()
self._update_toggles()
def _render(self, rect):
self._back_button.set_position(self._rect.x, self._rect.y + 20)
self._back_button.render()
# subtract button
content_rect = rl.Rectangle(rect.x, rect.y + self._back_button.rect.height + 40, rect.width, rect.height - self._back_button.rect.height - 40)
self._scroller.render(content_rect)
def show_event(self):
self._scroller.show_event()
@staticmethod
def _mads_limited_settings() -> bool:
brand = ""
if ui_state.is_offroad():
bundle = ui_state.params.get("CarPlatformBundle")
if bundle:
brand = bundle.get("brand", "")
if not brand:
brand = ui_state.CP.brand if ui_state.CP else ""
if brand == "rivian":
return True
elif brand == "tesla":
return not (ui_state.CP_SP and ui_state.CP_SP.flags & TeslaFlagsSP.HAS_VEHICLE_BUS)
return False
def _update_steering_mode_description(self, button_index: int):
base_desc = tr("Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot.")
result = base_desc + "<br><br>"
for opt in MADS_STEERING_MODE_OPTIONS:
desc = "<b>" + opt[1] + "</b>" if button_index == MADS_STEERING_MODE_OPTIONS.index(opt) else opt[1]
result += desc + "<br>"
self._steering_mode.set_description(result)
self._steering_mode.show_description(True)
def _update_toggles(self):
self._update_steering_mode_description(self._steering_mode.action_item.get_selected_button())
if self._mads_limited_settings():
ui_state.params.remove("MadsMainCruiseAllowed")
ui_state.params.put_bool("MadsUnifiedEngagementMode", True)
ui_state.params.put("MadsSteeringMode", 2)
self._main_cruise_toggle.action_item.set_enabled(False)
self._main_cruise_toggle.action_item.set_state(False)
self._main_cruise_toggle.set_description("<b>" + DEFAULT_TO_OFF + "</b><br>" + MADS_MAIN_CRUISE_BASE_DESC)
self._unified_engagement_toggle.action_item.set_enabled(False)
self._unified_engagement_toggle.action_item.set_state(True)
self._unified_engagement_toggle.set_description("<b>" + DEFAULT_TO_ON + "</b><br>" + MADS_UNIFIED_ENGAGEMENT_MODE_BASE_DESC)
self._steering_mode.action_item.set_enabled(False)
self._steering_mode.set_description(STATUS_DISENGAGE_ONLY)
self._steering_mode.action_item.set_selected_button(2)
else:
self._main_cruise_toggle.action_item.set_enabled(True)
self._main_cruise_toggle.set_description(MADS_MAIN_CRUISE_BASE_DESC)
self._unified_engagement_toggle.action_item.set_enabled(True)
self._unified_engagement_toggle.set_description(MADS_UNIFIED_ENGAGEMENT_MODE_BASE_DESC)
self._steering_mode.action_item.set_enabled(True)
@@ -0,0 +1,115 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
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 collections.abc import Callable
import pyray as rl
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp, option_item_sp
from openpilot.system.ui.widgets.network import NavButton
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.system.ui.widgets import Widget
class TorqueSettingsLayout(Widget):
def __init__(self, back_btn_callback: Callable):
super().__init__()
self._back_button = NavButton(tr("Back"))
self._back_button.set_click_callback(back_btn_callback)
items = self._initialize_items()
self._scroller = Scroller(items, line_separator=True, spacing=0)
def _initialize_items(self):
self._self_tune_toggle = toggle_item_sp(
param="LiveTorqueParamsToggle",
title=lambda: tr("Self-Tune"),
description=lambda: tr("Enables self-tune for Torque lateral control for platforms that do not use " +
"Torque lateral control by default."),
)
self._relaxed_tune_toggle = toggle_item_sp(
param="LiveTorqueParamsRelaxedToggle",
title=lambda: tr("Less Restrict Settings for Self-Tune (Beta)"),
description=lambda: tr("Less strict settings when using Self-Tune. This allows torqued to be more " +
"forgiving when learning values."),
)
self._custom_tune_toggle = toggle_item_sp(
param="CustomTorqueParams",
title=lambda: tr("Enable Custom Tuning"),
description=lambda: tr("Enables custom tuning for Torque lateral control. " +
"Modifying Lateral Acceleration Factor and Friction below will override the offline values " +
"indicated in the YAML files within \"opendbc/car/torque_data\". " +
"The values will also be used live when \"Manual Real-Time Tuning\" toggle is enabled."),
)
self._torque_prams_override_toggle = toggle_item_sp(
param="TorqueParamsOverrideEnabled",
title=lambda: tr("Manual Real-Time Tuning"),
description=lambda: tr("Enforces the torque lateral controller to use the fixed values instead of the learned " +
"values from Self-Tune. Enabling this toggle overrides Self-Tune values."),
)
self._torque_lat_accel_factor = option_item_sp(
title=lambda: tr("Lateral Acceleration Factor"),
param="TorqueParamsOverrideLatAccelFactor",
description="",
min_value=1,
max_value=500,
value_change_step=1,
label_callback=(lambda x: f"{x/100} m/s^2"),
use_float_scaling=True
)
self._torque_friction = option_item_sp(
title=lambda: tr("Friction"),
param="TorqueParamsOverrideFriction",
description="",
min_value=1,
max_value=100,
value_change_step=1,
label_callback=(lambda x: f"{x/100}"),
use_float_scaling=True
)
items = [
self._self_tune_toggle,
self._relaxed_tune_toggle,
self._custom_tune_toggle,
self._torque_prams_override_toggle,
self._torque_lat_accel_factor,
self._torque_friction,
]
return items
def _update_state(self):
super()._update_state()
if not ui_state.params.get_bool("LiveTorqueParamsToggle"):
ui_state.params.remove("LiveTorqueParamsRelaxedToggle")
self._relaxed_tune_toggle.action_item.set_state(False)
self._self_tune_toggle.action_item.set_enabled(ui_state.is_offroad())
self._relaxed_tune_toggle.action_item.set_enabled(ui_state.is_offroad() and self._self_tune_toggle.action_item.get_state())
self._custom_tune_toggle.action_item.set_enabled(ui_state.is_offroad())
custom_tune_enabled = self._custom_tune_toggle.action_item.get_state()
self._torque_prams_override_toggle.set_visible(custom_tune_enabled)
self._torque_lat_accel_factor.set_visible(custom_tune_enabled)
self._torque_friction.set_visible(custom_tune_enabled)
self._torque_prams_override_toggle.action_item.set_enabled(ui_state.is_offroad())
sliders_enabled = self._torque_prams_override_toggle.action_item.get_state() or ui_state.is_offroad()
self._torque_lat_accel_factor.action_item.set_enabled(sliders_enabled)
self._torque_friction.action_item.set_enabled(sliders_enabled)
title_text = tr("Real-Time & Offline") if ui_state.params.get("TorqueParamsOverrideEnabled") else tr("Offline Only")
self._torque_lat_accel_factor.set_title(lambda: tr("Lateral Acceleration Factor") + " (" + title_text + ")")
self._torque_friction.set_title(lambda: tr("Friction") + " (" + title_text + ")")
def _render(self, rect):
self._back_button.set_position(self._rect.x, self._rect.y + 20)
self._back_button.render()
# subtract button
content_rect = rl.Rectangle(rect.x, rect.y + self._back_button.rect.height + 40, rect.width, rect.height - self._back_button.rect.height - 40)
self._scroller.render(content_rect)
def show_event(self):
self._scroller.show_event()