Backport Mici UI polish from SP-Dev-Dom (exclude wifi_manager)

This commit is contained in:
firestar5683
2026-03-24 16:05:33 -05:00
parent 948a4e1eb6
commit 8d83e0cdcd
14 changed files with 252 additions and 37 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

@@ -91,7 +91,7 @@ class DeveloperLayoutMici(NavWidget):
self._long_maneuver_toggle,
self._alpha_long_toggle,
self._debug_mode_toggle,
], snap_items=False)
], snap_items=False, scroll_indicator=True, edge_shadows=True)
# Toggle lists
self._refresh_toggles = (
+13 -2
View File
@@ -76,6 +76,8 @@ def _engaged_confirmation_callback(callback: Callable, action_text: str):
icon = "icons_mici/settings/device/reboot.png"
elif action_text == "reset":
icon = "icons_mici/settings/device/lkas.png"
elif action_text == "reset driver monitoring":
icon = "icons_mici/settings/device/cameras.png"
elif action_text == "uninstall":
icon = "icons_mici/settings/device/uninstall.png"
else:
@@ -83,7 +85,7 @@ def _engaged_confirmation_callback(callback: Callable, action_text: str):
icon = "icons_mici/settings/comma_icon.png"
dlg: BigConfirmationDialogV2 | BigDialog = BigConfirmationDialogV2(f"slide to\n{action_text.lower()}", icon, red=red,
exit_on_confirm=action_text == "reset",
exit_on_confirm=action_text in {"reset", "reset driver monitoring"},
confirm_callback=confirm_callback)
gui_app.set_modal_overlay(dlg)
else:
@@ -459,9 +461,17 @@ class DeviceLayoutMici(NavWidget):
params.remove("LiveDelay")
params.put_bool("OnroadCycleRequested", True)
def reset_driver_monitoring_callback():
params = ui_state.params
params.remove("IsRhdDetected")
params.put_bool("OnroadCycleRequested", True)
def uninstall_openpilot_callback():
ui_state.params.put_bool("DoUninstall", True)
reset_driver_monitoring_btn = BigButton("reset driver monitoring calibration", "", "icons_mici/settings/device/cameras.png")
reset_driver_monitoring_btn.set_click_callback(lambda: _engaged_confirmation_callback(reset_driver_monitoring_callback, "reset driver monitoring"))
reset_calibration_btn = BigButton("reset calibration", "", "icons_mici/settings/device/lkas.png")
reset_calibration_btn.set_click_callback(lambda: _engaged_confirmation_callback(reset_calibration_callback, "reset"))
@@ -508,13 +518,14 @@ class DeviceLayoutMici(NavWidget):
PairBigButton(),
review_training_guide_btn,
driver_cam_btn,
reset_driver_monitoring_btn,
# lang_button,
reset_calibration_btn,
uninstall_openpilot_btn,
regulatory_btn,
reboot_btn,
self._power_off_btn,
], snap_items=False)
], snap_items=False, scroll_indicator=True, edge_shadows=True)
# Set up back navigation
self.set_back_callback(back_callback)
@@ -3,7 +3,7 @@ from enum import IntEnum
from collections.abc import Callable
from openpilot.system.ui.widgets.scroller import Scroller
from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici
from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici, WifiIcon, normalize_ssid
from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigMultiToggle, BigToggle, BigParamControl
from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog
from openpilot.selfdrive.ui.ui_state import ui_state
@@ -75,8 +75,14 @@ class NetworkLayoutMici(NavWidget):
self._network_metered_btn = BigMultiToggle("network usage", ["default", "metered", "unmetered"], select_callback=network_metered_callback)
self._network_metered_btn.set_enabled(False)
wifi_button = BigButton("wi-fi")
self._wifi_slash_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_slash.png", 64, 56)
self._wifi_low_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_low.png", 64, 47)
self._wifi_medium_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_medium.png", 64, 47)
self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 64, 47)
wifi_button = BigButton("wi-fi", "not connected", self._wifi_slash_txt)
wifi_button.set_click_callback(lambda: self._switch_to_panel(NetworkPanelType.WIFI))
self._wifi_button = wifi_button
# ******** Advanced settings ********
# ******** Roaming toggle ********
@@ -101,7 +107,7 @@ class NetworkLayoutMici(NavWidget):
self._cellular_metered_btn,
# */
self._ip_address_btn,
], snap_items=False)
], snap_items=False, scroll_indicator=True, edge_shadows=True)
# Set initial config
roaming_enabled = ui_state.params.get_bool("GsmRoaming")
@@ -90,7 +90,7 @@ class SettingsLayout(NavWidget):
#BigDialogButton("manual", "", "icons_mici/settings/manual_icon.png", "Check out the mici user\nmanual at comma.ai/setup"),
firehose_btn,
developer_btn,
], snap_items=False)
], snap_items=False, scroll_indicator=True, edge_shadows=True)
# Set up back navigation
self.set_back_callback(self.close_settings)
@@ -35,7 +35,7 @@ class TogglesLayoutMici(NavWidget):
record_front,
record_mic,
enable_openpilot,
], snap_items=False)
], snap_items=False, scroll_indicator=True, edge_shadows=True)
# Toggle lists
self._refresh_toggles = (
+18 -3
View File
@@ -110,6 +110,7 @@ class BigButton(Widget):
self.text = text
self.value = value
self.set_icon(icon)
self._label_font_size_override: int | None = None
self._scale_filter = BounceFilter(1.0, 0.1, 1 / gui_app.target_fps)
@@ -136,6 +137,18 @@ class BigButton(Widget):
def set_icon(self, icon: Union[str, rl.Texture]):
self._txt_icon = gui_app.texture(icon, 64, 64) if isinstance(icon, str) and len(icon) else icon
def _refresh_label_metrics(self):
font_size = self._label_font_size_override if self._label_font_size_override is not None else self._get_label_font_size()
self._label.set_font_size(font_size)
self._needs_scroll = measure_text_cached(self._label_font, self.text, font_size).x + 25 > self._rect.width
self._scroll_offset = 0
self._scroll_timer = 0
self._scroll_state = ScrollState.PRE_SCROLL
def _set_label_font_size_override(self, font_size: int | None):
self._label_font_size_override = font_size
self._refresh_label_metrics()
def set_rotate_icon(self, rotate: bool):
if rotate and self._rotate_icon_t is not None:
return
@@ -165,10 +178,12 @@ class BigButton(Widget):
def set_text(self, text: str):
self.text = text
self._label.set_text(text)
self._refresh_label_metrics()
def set_value(self, value: str):
self.value = value
self._sub_label.set_text(value)
self._refresh_label_metrics()
def get_value(self) -> str:
return self.value
@@ -256,7 +271,7 @@ class BigToggle(BigButton):
self._checked = initial_state
self._toggle_callback = toggle_callback
self._label.set_font_size(48)
self._set_label_font_size_override(48)
def _load_images(self):
super()._load_images()
@@ -296,8 +311,8 @@ class BigMultiToggle(BigToggle):
self._select_callback = select_callback
self._label.set_width(int(self._rect.width - LABEL_HORIZONTAL_PADDING * 2 - self._txt_enabled_toggle.width))
# TODO: why isn't this automatic?
self._label.set_font_size(self._get_label_font_size())
# Keep the title size stable when the selected option changes.
self._set_label_font_size_override(self._get_label_font_size())
self.set_value(self._options[0])
+9 -1
View File
@@ -113,6 +113,14 @@ class BigConfirmationDialogV2(BigDialogBase):
self._slider = BigSlider(title, icon_txt, confirm_callback=self._on_confirm)
self._slider.set_enabled(lambda: not self._swiping_away)
def show_event(self):
super().show_event()
self._slider.show_event()
def hide_event(self):
super().hide_event()
self._slider.hide_event()
def _on_confirm(self):
if self._confirm_callback:
self._confirm_callback()
@@ -122,7 +130,7 @@ class BigConfirmationDialogV2(BigDialogBase):
def _update_state(self):
super()._update_state()
if self._swiping_away and not self._slider.confirmed:
self._slider.reset()
self._slider.reset(reset_shimmer=False)
def _render(self, _) -> DialogResult:
self._slider.render(self._rect)
+21 -1
View File
@@ -363,6 +363,26 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
connect(dcamBtn, &ButtonControl::clicked, [=]() { emit showDriverView(); });
addItem(dcamBtn);
resetDmCalibBtn = new ButtonControl(
tr("Reset Driver Monitoring"),
tr("RESET"),
tr("Clears the saved driver monitoring wheel-side calibration if the device thinks you're seated on the wrong side. "
"Resetting will restart openpilot if the car is powered on.")
);
connect(resetDmCalibBtn, &ButtonControl::clicked, [&]() {
if (!uiState()->engaged()) {
if (ConfirmationDialog::confirm(tr("Are you sure you want to reset driver monitoring calibration?"), tr("Reset"), this)) {
if (!uiState()->engaged()) {
params.remove("IsRhdDetected");
params.putBool("OnroadCycleRequested", true);
}
}
} else {
ConfirmationDialog::alert(tr("Disengage to Reset Driver Monitoring"), this);
}
});
addItem(resetDmCalibBtn);
resetCalibBtn = new ButtonControl(tr("Reset Calibration"), tr("RESET"), "");
connect(resetCalibBtn, &ButtonControl::showDescriptionEvent, this, &DevicePanel::updateCalibDescription);
connect(resetCalibBtn, &ButtonControl::clicked, [&]() {
@@ -420,7 +440,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
});
QObject::connect(uiState(), &UIState::offroadTransition, [=](bool offroad) {
for (auto btn : findChildren<ButtonControl *>()) {
if (btn != pair_device && btn != resetCalibBtn) {
if (btn != pair_device && btn != resetCalibBtn && btn != resetDmCalibBtn) {
btn->setEnabled(offroad);
}
}
+1
View File
@@ -94,6 +94,7 @@ private:
ButtonControl *pair_galaxy;
QPushButton *galaxy_qr_btn;
ButtonControl *resetCalibBtn;
ButtonControl *resetDmCalibBtn;
};
class TogglesPanel : public ListWidget {
+9 -3
View File
@@ -133,11 +133,17 @@ class SoftwareSelectionPage(Widget):
super().__init__()
self._openpilot_slider = LargerSlider("slide to use\nstarpilot", use_openpilot_callback)
self._custom_software_slider = LargerSlider("slide to use\ncustom software", use_custom_software_callback, green=False)
self._custom_software_slider = LargerSlider("slide to use\ncustom software", use_custom_software_callback,
green=False, shimmer_offset=0.4)
def show_event(self):
super().show_event()
self._openpilot_slider.show_event()
self._custom_software_slider.show_event()
def reset(self):
self._openpilot_slider.reset()
self._custom_software_slider.reset()
self._openpilot_slider.reset(reset_shimmer=False)
self._custom_software_slider.reset(reset_shimmer=False)
def _render(self, rect: rl.Rectangle):
self._openpilot_slider.set_opacity(1.0 - self._custom_software_slider.slider_percentage)
+54 -1
View File
@@ -1,3 +1,4 @@
import math
from enum import IntEnum
from collections.abc import Callable
from itertools import zip_longest
@@ -401,6 +402,12 @@ class UnifiedLabel(Widget):
- Proper multiline vertical alignment
- Height calculation for layout purposes
"""
SHIMMER_BAND_WIDTH = 0.3
SHIMMER_BLUR_RADIUS = 0.12
SHIMMER_CYCLE_PERIOD = 2.5
SHIMMER_SWEEP_FRACTION = 0.9
SHIMMER_LOW_OPACITY = 0.65
def __init__(self,
text: str | Callable[[], str],
font_size: int = DEFAULT_TEXT_SIZE,
@@ -414,7 +421,8 @@ class UnifiedLabel(Widget):
wrap_text: bool = True,
scroll: bool = False,
line_height: float = 1.0,
letter_spacing: float = 0.0):
letter_spacing: float = 0.0,
shimmer: bool = False):
super().__init__()
self._text = text
self._font_size = font_size
@@ -431,6 +439,8 @@ class UnifiedLabel(Widget):
self._line_height = line_height * 0.9
self._letter_spacing = letter_spacing # 0.1 = 10%
self._spacing_pixels = font_size * letter_spacing
self._shimmer = shimmer
self._shimmer_start_time = 0.0
# Scroll state
self._scroll = scroll
@@ -489,6 +499,13 @@ class UnifiedLabel(Widget):
self._spacing_pixels = self._font_size * letter_spacing
self._cached_text = None # Invalidate cache
def set_line_height(self, line_height: float):
"""Update line height (multiplier, e.g., 1.0 = default)."""
new_line_height = line_height * 0.9
if self._line_height != new_line_height:
self._line_height = new_line_height
self._cached_text = None
def set_font_weight(self, font_weight: FontWeight):
"""Update the font weight."""
if self._font_weight != font_weight:
@@ -510,6 +527,9 @@ class UnifiedLabel(Widget):
self._scroll_pause_t = None
self._scroll_state = ScrollState.STARTING
def reset_shimmer(self, offset: float = 0.0):
self._shimmer_start_time = rl.get_time() + offset
def set_max_width(self, max_width: int | None):
"""Set the maximum width constraint for wrapping/eliding."""
if self._max_width != max_width:
@@ -627,6 +647,25 @@ class UnifiedLabel(Widget):
return self._cached_total_height
return 0.0
def _compute_shimmer_alpha(self, char_center_x: float, text_left: float, text_width: float) -> float:
if text_width <= 0:
return self.SHIMMER_LOW_OPACITY
elapsed = rl.get_time() - self._shimmer_start_time
sigma = text_width * self.SHIMMER_BLUR_RADIUS
t_raw = (elapsed % self.SHIMMER_CYCLE_PERIOD) / self.SHIMMER_CYCLE_PERIOD
t_clamped = max(0.0, min(t_raw / self.SHIMMER_SWEEP_FRACTION, 1.0))
t = t_clamped * t_clamped * (3.0 - 2.0 * t_clamped)
margin = text_width * self.SHIMMER_BAND_WIDTH
text_right = text_left + text_width
center = text_right + margin - t * (text_width + 2.0 * margin)
d = char_center_x - center
shimmer = math.exp(-0.5 * d * d / (sigma * sigma)) if sigma > 0 else 0.0
return self.SHIMMER_LOW_OPACITY + (1.0 - self.SHIMMER_LOW_OPACITY) * shimmer
def _render(self, _):
"""Render the label."""
if self._rect.width <= 0 or self._rect.height <= 0:
@@ -770,6 +809,20 @@ class UnifiedLabel(Widget):
line_x = self._rect.x + self._text_padding
line_x += self._scroll_offset + x_offset
if self._shimmer and not emojis and line:
base_alpha = self._text_color.a / 255.0
text_width = max(size.x, 1.0)
cursor_x = line_x
for char in line:
char_width = measure_text_cached(self._font, char, self._font_size, self._spacing_pixels).x
char_center_x = cursor_x + char_width / 2.0
shimmer_alpha = self._compute_shimmer_alpha(char_center_x, line_x, text_width)
char_alpha = int(255 * base_alpha * shimmer_alpha)
char_color = rl.Color(self._text_color.r, self._text_color.g, self._text_color.b, char_alpha)
rl.draw_text_ex(self._font, char, rl.Vector2(cursor_x, current_y), self._font_size, self._spacing_pixels, char_color)
cursor_x += char_width
return
# Render line with emojis
line_pos = rl.Vector2(line_x, current_y)
prev_index = 0
+76 -5
View File
@@ -11,6 +11,7 @@ ITEM_SPACING = 20
LINE_COLOR = rl.GRAY
LINE_PADDING = 40
ANIMATION_SCALE = 0.6
EDGE_SHADOW_WIDTH = 20
MIN_ZOOM_ANIMATION_TIME = 0.075 # seconds
DO_ZOOM = False
@@ -33,9 +34,50 @@ class LineSeparator(Widget):
LINE_COLOR)
class ScrollIndicator(Widget):
def __init__(self):
super().__init__()
self._txt_scroll_indicator = gui_app.texture("icons_mici/settings/horizontal_scroll_indicator.png", 96, 48)
self._scroll_offset: float = 0.0
self._content_size: float = 0.0
self._viewport: rl.Rectangle = rl.Rectangle(0, 0, 0, 0)
def update(self, scroll_offset: float, content_size: float, viewport: rl.Rectangle) -> None:
self._scroll_offset = scroll_offset
self._content_size = content_size
self._viewport = viewport
def _render(self, _):
if self._viewport.width <= 0 or self._viewport.height <= 0:
return
indicator_w = min(float(np.interp(self._content_size, [1000, 3000], [300, 100])), self._viewport.width)
max_scroll = self._content_size - self._viewport.width
if max_scroll > 0:
scroll_ratio = -self._scroll_offset / max_scroll
slide_range = max(self._viewport.width - indicator_w, 0.0)
x = self._viewport.x + scroll_ratio * slide_range
else:
x = self._viewport.x + (self._viewport.width - indicator_w) / 2
y = max(self._viewport.y, 0) + self._viewport.height - self._txt_scroll_indicator.height / 2
dest_left = max(x, self._viewport.x)
dest_right = min(x + indicator_w, self._viewport.x + self._viewport.width)
dest_w = max(indicator_w / 2, dest_right - dest_left)
dest_left = min(dest_left, self._viewport.x + self._viewport.width - dest_w)
dest_left = max(dest_left, self._viewport.x)
src_rec = rl.Rectangle(0, 0, self._txt_scroll_indicator.width, self._txt_scroll_indicator.height)
dest_rec = rl.Rectangle(dest_left, y, dest_w, self._txt_scroll_indicator.height)
rl.draw_texture_pro(self._txt_scroll_indicator, src_rec, dest_rec, rl.Vector2(0, 0), 0.0,
rl.Color(255, 255, 255, int(255 * 0.45)))
class Scroller(Widget):
def __init__(self, items: list[Widget], horizontal: bool = True, snap_items: bool = True, spacing: int = ITEM_SPACING,
line_separator: bool = False, pad_start: int = ITEM_SPACING, pad_end: int = ITEM_SPACING):
line_separator: bool = False, pad_start: int = ITEM_SPACING, pad_end: int = ITEM_SPACING,
scroll_indicator: bool = False, edge_shadows: bool = False):
super().__init__()
self._items: list[Widget] = []
self._horizontal = horizontal
@@ -65,11 +107,18 @@ class Scroller(Widget):
self.scroll_panel = GuiScrollPanel2(self._horizontal, handle_out_of_bounds=not self._snap_items)
self._scroll_enabled: bool | Callable[[], bool] = True
self._txt_scroll_indicator = gui_app.texture("icons_mici/settings/vertical_scroll_indicator.png", 40, 80)
self._txt_vertical_scroll_indicator = gui_app.texture("icons_mici/settings/vertical_scroll_indicator.png", 40, 80)
self._show_scroll_indicator = scroll_indicator and self._horizontal
self._scroll_indicator = ScrollIndicator()
self._edge_shadows = edge_shadows and self._horizontal
for item in items:
self.add_widget(item)
@property
def items(self) -> list[Widget]:
return self._items
def set_reset_scroll_at_show(self, scroll: bool):
self._reset_scroll_at_show = scroll
@@ -93,6 +142,14 @@ class Scroller(Widget):
self._items.append(item)
item.set_touch_valid_callback(lambda: self.scroll_panel.is_touch_valid() and self.enabled)
def move_item(self, from_index: int, to_index: int) -> None:
if from_index == to_index:
return
if not (0 <= from_index < len(self._items) and 0 <= to_index < len(self._items)):
return
item = self._items.pop(from_index)
self._items.insert(to_index, item)
def set_scrolling_enabled(self, enabled: bool | Callable[[], bool]) -> None:
"""Set whether scrolling is enabled (does not affect widget enabled state)."""
self._scroll_enabled = enabled
@@ -243,13 +300,27 @@ class Scroller(Widget):
# Draw scroll indicator
if SCROLL_BAR and not self._horizontal and len(self._visible_items) > 0:
_real_content_size = self._content_size - self._rect.height + self._txt_scroll_indicator.height
_real_content_size = self._content_size - self._rect.height + self._txt_vertical_scroll_indicator.height
scroll_bar_y = -self._scroll_offset / _real_content_size * self._rect.height
scroll_bar_y = min(max(scroll_bar_y, self._rect.y), self._rect.y + self._rect.height - self._txt_scroll_indicator.height)
rl.draw_texture_ex(self._txt_scroll_indicator, rl.Vector2(self._rect.x, scroll_bar_y), 0, 1.0, rl.WHITE)
scroll_bar_y = min(max(scroll_bar_y, self._rect.y), self._rect.y + self._rect.height - self._txt_vertical_scroll_indicator.height)
rl.draw_texture_ex(self._txt_vertical_scroll_indicator, rl.Vector2(self._rect.x, scroll_bar_y), 0, 1.0, rl.WHITE)
rl.end_scissor_mode()
if self._edge_shadows:
rl.draw_rectangle_gradient_h(int(self._rect.x), int(self._rect.y),
EDGE_SHADOW_WIDTH, int(self._rect.height),
rl.Color(0, 0, 0, 166), rl.BLANK)
right_x = int(self._rect.x + self._rect.width - EDGE_SHADOW_WIDTH)
rl.draw_rectangle_gradient_h(right_x, int(self._rect.y),
EDGE_SHADOW_WIDTH, int(self._rect.height),
rl.BLANK, rl.Color(0, 0, 0, 166))
if self._show_scroll_indicator and len(self._visible_items) > 0:
self._scroll_indicator.update(self._scroll_offset, self._content_size, self._rect)
self._scroll_indicator.render()
def show_event(self):
super().show_event()
if self._reset_scroll_at_show:
+39 -15
View File
@@ -5,17 +5,19 @@ import pyray as rl
from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.widgets.label import UnifiedLabel
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.common.filter_simple import BounceFilter, FirstOrderFilter
class SmallSlider(Widget):
HORIZONTAL_PADDING = 8
CONFIRM_DELAY = 0.2
PRESSED_SCALE = 1.07
def __init__(self, title: str, confirm_callback: Callable | None = None):
def __init__(self, title: str, confirm_callback: Callable | None = None, shimmer_offset: float = 0.0):
# TODO: unify this with BigConfirmationDialogV2
super().__init__()
self._confirm_callback = confirm_callback
self._shimmer_offset = shimmer_offset
self._font = gui_app.font(FontWeight.DISPLAY)
@@ -30,29 +32,40 @@ class SmallSlider(Widget):
self._start_x_circle = 0.0
self._scroll_x_circle = 0.0
self._scroll_x_circle_filter = FirstOrderFilter(0, 0.05, 1 / gui_app.target_fps)
self._circle_scale_filter = BounceFilter(1.0, 0.1, 1 / gui_app.target_fps)
self._circle_press_time: float | None = None
self._is_dragging_circle = False
self._label = UnifiedLabel(title, font_size=36, font_weight=FontWeight.MEDIUM, text_color=rl.Color(255, 255, 255, int(255 * 0.65)),
self._label = UnifiedLabel(title, font_size=36, font_weight=FontWeight.SEMI_BOLD, text_color=rl.WHITE,
alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT,
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, line_height=0.9)
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, line_height=0.9, shimmer=True)
def _load_assets(self):
self.set_rect(rl.Rectangle(0, 0, 316 + self.HORIZONTAL_PADDING * 2, 100))
self._bg_txt = gui_app.texture("icons_mici/setup/small_slider/slider_bg.png", 316, 100)
self._circle_bg_txt = gui_app.texture("icons_mici/setup/small_slider/slider_red_circle.png", 100, 100)
self._circle_bg_pressed_txt = self._circle_bg_txt
self._circle_arrow_txt = gui_app.texture("icons_mici/setup/small_slider/slider_arrow.png", 37, 32)
@property
def confirmed(self) -> bool:
return self._confirmed_time > 0.0
def reset(self):
def show_event(self):
super().show_event()
self.reset()
def reset(self, reset_shimmer: bool = True):
# reset all slider state
self._is_dragging_circle = False
self._confirmed_time = 0.0
self._confirm_callback_called = False
self._circle_press_time = None
self._circle_scale_filter.x = 1.0
if reset_shimmer:
self._label.reset_shimmer(self._shimmer_offset)
def set_opacity(self, opacity: float, smooth: bool = False):
if smooth:
@@ -83,6 +96,7 @@ class SmallSlider(Widget):
if rl.check_collision_point_rec(mouse_event.pos, circle_button_rect):
self._start_x_circle = mouse_event.pos.x
self._is_dragging_circle = True
self._circle_press_time = rl.get_time()
elif mouse_event.left_released:
# swiped to left
@@ -129,8 +143,9 @@ class SmallSlider(Widget):
btn_x = bg_txt_x + self._bg_txt.width - self._circle_bg_txt.width + self._scroll_x_circle_filter.x
btn_y = self._rect.y + (self._rect.height - self._circle_bg_txt.height) / 2
if self._confirmed_time == 0.0 or self._scroll_x_circle > 0:
self._label.set_text_color(rl.Color(255, 255, 255, int(255 * 0.65 * (1.0 - self.slider_percentage) * self._opacity_filter.x)))
label_alpha = int(255 * (1.0 - self.slider_percentage) * self._opacity_filter.x)
if label_alpha > 0:
self._label.set_text_color(rl.Color(255, 255, 255, label_alpha))
label_rect = rl.Rectangle(
self._rect.x + 20,
self._rect.y,
@@ -139,18 +154,24 @@ class SmallSlider(Widget):
)
self._label.render(label_rect)
# circle and arrow
rl.draw_texture_ex(self._circle_bg_txt, rl.Vector2(btn_x, btn_y), 0.0, 1.0, white)
circle_pressed = self._is_dragging_circle or self.confirmed or (
self._circle_press_time is not None and rl.get_time() - self._circle_press_time < 0.075
)
circle_bg_txt = self._circle_bg_pressed_txt if circle_pressed else self._circle_bg_txt
scale = self._circle_scale_filter.update(self.PRESSED_SCALE if circle_pressed else 1.0)
scaled_btn_x = btn_x + (self._circle_bg_txt.width * (1 - scale)) / 2
scaled_btn_y = btn_y + (self._circle_bg_txt.height * (1 - scale)) / 2
rl.draw_texture_ex(circle_bg_txt, rl.Vector2(scaled_btn_x, scaled_btn_y), 0.0, scale, white)
arrow_x = btn_x + (self._circle_bg_txt.width - self._circle_arrow_txt.width) / 2
arrow_y = btn_y + (self._circle_bg_txt.height - self._circle_arrow_txt.height) / 2
arrow_x = scaled_btn_x + (self._circle_bg_txt.width * scale - self._circle_arrow_txt.width) / 2
arrow_y = scaled_btn_y + (self._circle_bg_txt.height * scale - self._circle_arrow_txt.height) / 2
rl.draw_texture_ex(self._circle_arrow_txt, rl.Vector2(arrow_x, arrow_y), 0.0, 1.0, white)
class LargerSlider(SmallSlider):
def __init__(self, title: str, confirm_callback: Callable | None = None, green: bool = True):
def __init__(self, title: str, confirm_callback: Callable | None = None, green: bool = True, shimmer_offset: float = 0.0):
self._green = green
super().__init__(title, confirm_callback=confirm_callback)
super().__init__(title, confirm_callback=confirm_callback, shimmer_offset=shimmer_offset)
def _load_assets(self):
self.set_rect(rl.Rectangle(0, 0, 520 + self.HORIZONTAL_PADDING * 2, 115))
@@ -158,6 +179,7 @@ class LargerSlider(SmallSlider):
self._bg_txt = gui_app.texture("icons_mici/setup/small_slider/slider_bg_larger.png", 520, 115)
circle_fn = "slider_green_rounded_rectangle" if self._green else "slider_black_rounded_rectangle"
self._circle_bg_txt = gui_app.texture(f"icons_mici/setup/small_slider/{circle_fn}.png", 180, 115)
self._circle_bg_pressed_txt = self._circle_bg_txt
self._circle_arrow_txt = gui_app.texture("icons_mici/setup/small_slider/slider_arrow.png", 64, 55)
@@ -165,15 +187,16 @@ class BigSlider(SmallSlider):
def __init__(self, title: str, icon: rl.Texture, confirm_callback: Callable | None = None):
self._icon = icon
super().__init__(title, confirm_callback=confirm_callback)
self._label = UnifiedLabel(title, font_size=48, font_weight=FontWeight.DISPLAY, text_color=rl.Color(255, 255, 255, int(255 * 0.65)),
self._label = UnifiedLabel(title, font_size=48, font_weight=FontWeight.DISPLAY, text_color=rl.WHITE,
alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE,
line_height=0.875)
line_height=0.875, shimmer=True)
def _load_assets(self):
self.set_rect(rl.Rectangle(0, 0, 520 + self.HORIZONTAL_PADDING * 2, 180))
self._bg_txt = gui_app.texture("icons_mici/buttons/slider_bg.png", 520, 180)
self._circle_bg_txt = gui_app.texture("icons_mici/buttons/button_circle.png", 180, 180)
self._circle_bg_pressed_txt = gui_app.texture("icons_mici/buttons/button_circle_hover.png", 180, 180)
self._circle_arrow_txt = self._icon
@@ -183,4 +206,5 @@ class RedBigSlider(BigSlider):
self._bg_txt = gui_app.texture("icons_mici/buttons/slider_bg.png", 520, 180)
self._circle_bg_txt = gui_app.texture("icons_mici/buttons/button_circle_red.png", 180, 180)
self._circle_bg_pressed_txt = gui_app.texture("icons_mici/buttons/button_circle_red_hover.png", 180, 180)
self._circle_arrow_txt = self._icon