mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-02-18 18:53:55 +08:00
raylib: font sizes from QT should match (#36306)
* pt 2 * fix line height * fixup html renderer * fix sidebar * fix label line height * firehose fixups * fix ssh value font styling * fixup inputbot * do experimental mode * pairing dialog numbers * fix radius for prime user * add emoji to firehose mode * full screen registration * fix registration btn size * fix update and alerts * debugging * Revert "debugging" This reverts commit 0095372e9479d8c727bcc8a78061f582d852133d. * firehose styling * fix offroad alerts missing bottom spacing expansion * huge oof * huge oof
This commit is contained in:
@@ -68,6 +68,7 @@ class HomeLayout(Widget):
|
||||
def _setup_callbacks(self):
|
||||
self.update_alert.set_dismiss_callback(lambda: self._set_state(HomeLayoutState.HOME))
|
||||
self.offroad_alert.set_dismiss_callback(lambda: self._set_state(HomeLayoutState.HOME))
|
||||
self._exp_mode_button.set_click_callback(lambda: self.settings_callback() if self.settings_callback else None)
|
||||
|
||||
def set_settings_callback(self, callback: Callable):
|
||||
self.settings_callback = callback
|
||||
@@ -147,9 +148,9 @@ class HomeLayout(Widget):
|
||||
rl.draw_rectangle_rounded(self.update_notif_rect, 0.3, 10, highlight_color)
|
||||
|
||||
text = "UPDATE"
|
||||
text_width = measure_text_cached(font, text, HEAD_BUTTON_FONT_SIZE).x
|
||||
text_x = self.update_notif_rect.x + (self.update_notif_rect.width - text_width) // 2
|
||||
text_y = self.update_notif_rect.y + (self.update_notif_rect.height - HEAD_BUTTON_FONT_SIZE) // 2
|
||||
text_size = measure_text_cached(font, text, HEAD_BUTTON_FONT_SIZE)
|
||||
text_x = self.update_notif_rect.x + (self.update_notif_rect.width - text_size.x) // 2
|
||||
text_y = self.update_notif_rect.y + (self.update_notif_rect.height - text_size.y) // 2
|
||||
rl.draw_text_ex(font, text, rl.Vector2(int(text_x), int(text_y)), HEAD_BUTTON_FONT_SIZE, 0, rl.WHITE)
|
||||
|
||||
# Alert notification button
|
||||
@@ -161,9 +162,9 @@ class HomeLayout(Widget):
|
||||
rl.draw_rectangle_rounded(self.alert_notif_rect, 0.3, 10, highlight_color)
|
||||
|
||||
alert_text = f"{self.alert_count} ALERT{'S' if self.alert_count > 1 else ''}"
|
||||
text_width = measure_text_cached(font, alert_text, HEAD_BUTTON_FONT_SIZE).x
|
||||
text_x = self.alert_notif_rect.x + (self.alert_notif_rect.width - text_width) // 2
|
||||
text_y = self.alert_notif_rect.y + (self.alert_notif_rect.height - HEAD_BUTTON_FONT_SIZE) // 2
|
||||
text_size = measure_text_cached(font, alert_text, HEAD_BUTTON_FONT_SIZE)
|
||||
text_x = self.alert_notif_rect.x + (self.alert_notif_rect.width - text_size.x) // 2
|
||||
text_y = self.alert_notif_rect.y + (self.alert_notif_rect.height - text_size.y) // 2
|
||||
rl.draw_text_ex(font, alert_text, rl.Vector2(int(text_x), int(text_y)), HEAD_BUTTON_FONT_SIZE, 0, rl.WHITE)
|
||||
|
||||
# Version text (right aligned)
|
||||
|
||||
@@ -50,6 +50,7 @@ class MainLayout(Widget):
|
||||
on_flag=self._on_bookmark_clicked,
|
||||
open_settings=lambda: self.open_settings(PanelType.TOGGLES))
|
||||
self._layouts[MainState.HOME]._setup_widget.set_open_settings_callback(lambda: self.open_settings(PanelType.FIREHOSE))
|
||||
self._layouts[MainState.HOME].set_settings_callback(lambda: self.open_settings(PanelType.TOGGLES))
|
||||
self._layouts[MainState.SETTINGS].set_callbacks(on_close=self._set_mode_for_state)
|
||||
self._layouts[MainState.ONROAD].set_click_callback(self._on_onroad_clicked)
|
||||
device.add_interactive_timeout_callback(self._set_mode_for_state)
|
||||
|
||||
@@ -7,7 +7,8 @@ from openpilot.common.params import Params
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE
|
||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||
from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
|
||||
from openpilot.system.ui.lib.wrap_text import wrap_text
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
@@ -21,7 +22,7 @@ DESCRIPTION = (
|
||||
)
|
||||
INSTRUCTIONS = (
|
||||
"For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.\n\n"
|
||||
+ "Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.\n\n"
|
||||
+ "Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.\n\n\n"
|
||||
+ "Frequently Asked Questions\n\n"
|
||||
+ "Does it matter how or where I drive? Nope, just drive as you normally would.\n\n"
|
||||
+ "Do all of my segments get pulled in Firehose Mode? No, we selectively pull a subset of your segments.\n\n"
|
||||
@@ -43,6 +44,7 @@ class FirehoseLayout(Widget):
|
||||
self.params = Params()
|
||||
self.segment_count = self._get_segment_count()
|
||||
self.scroll_panel = GuiScrollPanel()
|
||||
self._content_height = 0
|
||||
|
||||
self.running = True
|
||||
self.update_thread = threading.Thread(target=self._update_loop, daemon=True)
|
||||
@@ -69,88 +71,61 @@ class FirehoseLayout(Widget):
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
# Calculate content dimensions
|
||||
content_width = rect.width - 80
|
||||
content_height = self._calculate_content_height(int(content_width))
|
||||
content_rect = rl.Rectangle(rect.x, rect.y, rect.width, content_height)
|
||||
content_rect = rl.Rectangle(rect.x, rect.y, rect.width, self._content_height)
|
||||
|
||||
# Handle scrolling and render with clipping
|
||||
scroll_offset = self.scroll_panel.update(rect, content_rect)
|
||||
rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height))
|
||||
self._render_content(rect, scroll_offset)
|
||||
self._content_height = self._render_content(rect, scroll_offset)
|
||||
rl.end_scissor_mode()
|
||||
|
||||
def _calculate_content_height(self, content_width: int) -> int:
|
||||
height = 80 # Top margin
|
||||
|
||||
# Title
|
||||
height += 100 + 40
|
||||
|
||||
# Description
|
||||
desc_font = gui_app.font(FontWeight.NORMAL)
|
||||
desc_lines = wrap_text(desc_font, DESCRIPTION, 45, content_width)
|
||||
height += len(desc_lines) * 45 + 40
|
||||
|
||||
# Status section
|
||||
height += 32 # Separator
|
||||
status_text, _ = self._get_status()
|
||||
status_lines = wrap_text(gui_app.font(FontWeight.BOLD), status_text, 60, content_width)
|
||||
height += len(status_lines) * 60 + 20
|
||||
|
||||
# Contribution count (if available)
|
||||
if self.segment_count > 0:
|
||||
contrib_text = f"{self.segment_count} segment(s) of your driving is in the training dataset so far."
|
||||
contrib_lines = wrap_text(gui_app.font(FontWeight.BOLD), contrib_text, 52, content_width)
|
||||
height += len(contrib_lines) * 52 + 20
|
||||
|
||||
# Instructions section
|
||||
height += 32 # Separator
|
||||
inst_lines = wrap_text(gui_app.font(FontWeight.NORMAL), INSTRUCTIONS, 40, content_width)
|
||||
height += len(inst_lines) * 40 + 40 # Bottom margin
|
||||
|
||||
return height
|
||||
|
||||
def _render_content(self, rect: rl.Rectangle, scroll_offset: float):
|
||||
def _render_content(self, rect: rl.Rectangle, scroll_offset: float) -> int:
|
||||
x = int(rect.x + 40)
|
||||
y = int(rect.y + 40 + scroll_offset)
|
||||
w = int(rect.width - 80)
|
||||
|
||||
# Title
|
||||
# Title (centered)
|
||||
title_font = gui_app.font(FontWeight.MEDIUM)
|
||||
rl.draw_text_ex(title_font, TITLE, rl.Vector2(x, y), 100, 0, rl.WHITE)
|
||||
y += 140
|
||||
text_width = measure_text_cached(title_font, TITLE, 100).x
|
||||
title_x = rect.x + (rect.width - text_width) / 2
|
||||
rl.draw_text_ex(title_font, TITLE, rl.Vector2(title_x, y), 100, 0, rl.WHITE)
|
||||
y += 200
|
||||
|
||||
# Description
|
||||
y = self._draw_wrapped_text(x, y, w, DESCRIPTION, gui_app.font(FontWeight.NORMAL), 45, rl.WHITE)
|
||||
y += 40
|
||||
y += 40 + 20
|
||||
|
||||
# Separator
|
||||
rl.draw_rectangle(x, y, w, 2, self.GRAY)
|
||||
y += 30
|
||||
y += 30 + 20
|
||||
|
||||
# Status
|
||||
status_text, status_color = self._get_status()
|
||||
y = self._draw_wrapped_text(x, y, w, status_text, gui_app.font(FontWeight.BOLD), 60, status_color)
|
||||
y += 20
|
||||
y += 20 + 20
|
||||
|
||||
# Contribution count (if available)
|
||||
if self.segment_count > 0:
|
||||
contrib_text = f"{self.segment_count} segment(s) of your driving is in the training dataset so far."
|
||||
y = self._draw_wrapped_text(x, y, w, contrib_text, gui_app.font(FontWeight.BOLD), 52, rl.WHITE)
|
||||
y += 20
|
||||
y += 20 + 20
|
||||
|
||||
# Separator
|
||||
rl.draw_rectangle(x, y, w, 2, self.GRAY)
|
||||
y += 30
|
||||
y += 30 + 20
|
||||
|
||||
# Instructions
|
||||
self._draw_wrapped_text(x, y, w, INSTRUCTIONS, gui_app.font(FontWeight.NORMAL), 40, self.LIGHT_GRAY)
|
||||
y = self._draw_wrapped_text(x, y, w, INSTRUCTIONS, gui_app.font(FontWeight.NORMAL), 40, self.LIGHT_GRAY)
|
||||
|
||||
def _draw_wrapped_text(self, x, y, width, text, font, size, color):
|
||||
wrapped = wrap_text(font, text, size, width)
|
||||
# bottom margin + remove effect of scroll offset
|
||||
return int(round(y - self.scroll_panel.offset + 40))
|
||||
|
||||
def _draw_wrapped_text(self, x, y, width, text, font, font_size, color):
|
||||
wrapped = wrap_text(font, text, font_size, width)
|
||||
for line in wrapped:
|
||||
rl.draw_text_ex(font, line, rl.Vector2(x, y), size, 0, color)
|
||||
y += size
|
||||
return y
|
||||
rl.draw_text_ex(font, line, rl.Vector2(x, y), font_size, 0, color)
|
||||
y += font_size * FONT_SCALE
|
||||
return round(y)
|
||||
|
||||
def _get_status(self) -> tuple[str, rl.Color]:
|
||||
network_type = ui_state.sm["deviceState"].networkType
|
||||
|
||||
@@ -4,7 +4,7 @@ from dataclasses import dataclass
|
||||
from collections.abc import Callable
|
||||
from cereal import log
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos, FONT_SCALE
|
||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
|
||||
@@ -218,7 +218,7 @@ class Sidebar(Widget):
|
||||
|
||||
# Draw label and value
|
||||
labels = [metric.label, metric.value]
|
||||
text_y = metric_rect.y + (metric_rect.height / 2 - len(labels) * FONT_SIZE)
|
||||
text_y = metric_rect.y + (metric_rect.height / 2 - len(labels) * FONT_SIZE * FONT_SCALE)
|
||||
for text in labels:
|
||||
text_size = measure_text_cached(self._font_bold, text, FONT_SIZE)
|
||||
text_y += text_size.y
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import pyray as rl
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ class ExperimentalModeButton(Widget):
|
||||
super().__init__()
|
||||
|
||||
self.img_width = 80
|
||||
self.horizontal_padding = 50
|
||||
self.horizontal_padding = 25
|
||||
self.button_height = 125
|
||||
|
||||
self.params = Params()
|
||||
@@ -31,11 +31,6 @@ class ExperimentalModeButton(Widget):
|
||||
rl.draw_rectangle_gradient_h(int(rect.x), int(rect.y), int(rect.width), int(rect.height),
|
||||
start_color, end_color)
|
||||
|
||||
def _handle_mouse_release(self, mouse_pos):
|
||||
self.experimental_mode = not self.experimental_mode
|
||||
# TODO: Opening settings for ExperimentalMode
|
||||
self.params.put_bool("ExperimentalMode", self.experimental_mode)
|
||||
|
||||
def _render(self, rect):
|
||||
rl.draw_rectangle_rounded(rect, 0.08, 20, rl.WHITE)
|
||||
|
||||
@@ -51,7 +46,7 @@ class ExperimentalModeButton(Widget):
|
||||
# Draw text label (left aligned)
|
||||
text = "EXPERIMENTAL MODE ON" if self.experimental_mode else "CHILL MODE ON"
|
||||
text_x = rect.x + self.horizontal_padding
|
||||
text_y = rect.y + rect.height / 2 - 45 // 2 # Center vertically
|
||||
text_y = rect.y + rect.height / 2 - 45 * FONT_SCALE // 2 # Center vertically
|
||||
|
||||
rl.draw_text_ex(gui_app.font(FontWeight.NORMAL), text, rl.Vector2(int(text_x), int(text_y)), 45, 0, rl.BLACK)
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE
|
||||
from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
|
||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||
from openpilot.system.ui.lib.wrap_text import wrap_text
|
||||
@@ -65,8 +65,8 @@ class ActionButton(Widget):
|
||||
|
||||
def set_text(self, text: str):
|
||||
self._text = text
|
||||
self._text_width = measure_text_cached(gui_app.font(FontWeight.MEDIUM), self._text, AlertConstants.FONT_SIZE).x
|
||||
self._rect.width = max(self._text_width + 60 * 2, self._min_width)
|
||||
self._text_size = measure_text_cached(gui_app.font(FontWeight.MEDIUM), self._text, AlertConstants.FONT_SIZE)
|
||||
self._rect.width = max(self._text_size.x + 60 * 2, self._min_width)
|
||||
self._rect.height = AlertConstants.BUTTON_HEIGHT
|
||||
|
||||
def _render(self, _):
|
||||
@@ -79,8 +79,8 @@ class ActionButton(Widget):
|
||||
|
||||
# center text
|
||||
color = rl.WHITE if self._style == ButtonStyle.DARK else rl.BLACK
|
||||
text_x = int(self._rect.x + (self._rect.width - self._text_width) // 2)
|
||||
text_y = int(self._rect.y + (self._rect.height - AlertConstants.FONT_SIZE) // 2)
|
||||
text_x = int(self._rect.x + (self._rect.width - self._text_size.x) // 2)
|
||||
text_y = int(self._rect.y + (self._rect.height - self._text_size.y) // 2)
|
||||
rl.draw_text_ex(self._font, self._text, rl.Vector2(text_x, text_y), AlertConstants.FONT_SIZE, 0, color)
|
||||
|
||||
|
||||
@@ -250,9 +250,9 @@ class OffroadAlert(AbstractAlert):
|
||||
text_width = int(self.content_rect.width - (AlertConstants.ALERT_INSET * 2))
|
||||
wrapped_lines = wrap_text(font, alert_data.text, AlertConstants.FONT_SIZE, text_width)
|
||||
line_count = len(wrapped_lines)
|
||||
text_height = line_count * (AlertConstants.FONT_SIZE + 5)
|
||||
text_height = line_count * (AlertConstants.FONT_SIZE * FONT_SCALE)
|
||||
alert_item_height = max(text_height + (AlertConstants.ALERT_INSET * 2), AlertConstants.ALERT_HEIGHT)
|
||||
total_height += alert_item_height + AlertConstants.ALERT_SPACING
|
||||
total_height += round(alert_item_height + AlertConstants.ALERT_SPACING)
|
||||
|
||||
if total_height > 20:
|
||||
total_height = total_height - AlertConstants.ALERT_SPACING + 20
|
||||
@@ -278,7 +278,7 @@ class OffroadAlert(AbstractAlert):
|
||||
text_width = int(content_rect.width - (AlertConstants.ALERT_INSET * 2))
|
||||
wrapped_lines = wrap_text(font, alert_data.text, AlertConstants.FONT_SIZE, text_width)
|
||||
line_count = len(wrapped_lines)
|
||||
text_height = line_count * (AlertConstants.FONT_SIZE + 5)
|
||||
text_height = line_count * (AlertConstants.FONT_SIZE * FONT_SCALE)
|
||||
alert_item_height = max(text_height + (AlertConstants.ALERT_INSET * 2), AlertConstants.ALERT_HEIGHT)
|
||||
|
||||
alert_rect = rl.Rectangle(
|
||||
@@ -298,13 +298,13 @@ class OffroadAlert(AbstractAlert):
|
||||
rl.draw_text_ex(
|
||||
font,
|
||||
line,
|
||||
rl.Vector2(text_x, text_y + i * (AlertConstants.FONT_SIZE + 5)),
|
||||
rl.Vector2(text_x, text_y + i * AlertConstants.FONT_SIZE * FONT_SCALE),
|
||||
AlertConstants.FONT_SIZE,
|
||||
0,
|
||||
AlertColors.TEXT,
|
||||
)
|
||||
|
||||
y_offset += alert_item_height + AlertConstants.ALERT_SPACING
|
||||
y_offset += round(alert_item_height + AlertConstants.ALERT_SPACING)
|
||||
|
||||
|
||||
class UpdateAlert(AbstractAlert):
|
||||
|
||||
@@ -145,8 +145,8 @@ class PairingDialog(Widget):
|
||||
# Circle and number
|
||||
rl.draw_circle(int(circle_x), int(circle_y), circle_radius, rl.Color(70, 70, 70, 255))
|
||||
number = str(i + 1)
|
||||
number_width = measure_text_cached(font, number, 30).x
|
||||
rl.draw_text_ex(font, number, (int(circle_x - number_width // 2), int(circle_y - 15)), 30, 0, rl.WHITE)
|
||||
number_size = measure_text_cached(font, number, 30)
|
||||
rl.draw_text_ex(font, number, (int(circle_x - number_size.x // 2), int(circle_y - number_size.y // 2)), 30, 0, rl.WHITE)
|
||||
|
||||
# Text
|
||||
rl.draw_text_ex(font, "\n".join(wrapped), rl.Vector2(text_x, y), 47, 0.0, rl.BLACK)
|
||||
|
||||
@@ -52,7 +52,7 @@ class PrimeWidget(Widget):
|
||||
def _render_for_prime_user(self, rect: rl.Rectangle):
|
||||
"""Renders the prime user widget with subscription status."""
|
||||
|
||||
rl.draw_rectangle_rounded(rl.Rectangle(rect.x, rect.y, rect.width, 230), 0.02, 10, self.PRIME_BG_COLOR)
|
||||
rl.draw_rectangle_rounded(rl.Rectangle(rect.x, rect.y, rect.width, 230), 0.05, 10, self.PRIME_BG_COLOR)
|
||||
|
||||
x = rect.x + 56
|
||||
y = rect.y + 40
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import pyray as rl
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE
|
||||
from openpilot.system.ui.lib.wrap_text import wrap_text
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.system.ui.widgets.button import Button, ButtonStyle
|
||||
from openpilot.system.ui.widgets.label import Label
|
||||
|
||||
|
||||
class SetupWidget(Widget):
|
||||
@@ -15,6 +16,7 @@ class SetupWidget(Widget):
|
||||
self._pair_device_btn = Button("Pair device", self._show_pairing, button_style=ButtonStyle.PRIMARY)
|
||||
self._open_settings_btn = Button("Open", lambda: self._open_settings_callback() if self._open_settings_callback else None,
|
||||
button_style=ButtonStyle.PRIMARY)
|
||||
self._firehose_label = Label("🔥Firehose Mode 🔥", font_weight=FontWeight.MEDIUM, font_size=64)
|
||||
|
||||
def set_open_settings_callback(self, callback):
|
||||
self._open_settings_callback = callback
|
||||
@@ -28,7 +30,7 @@ class SetupWidget(Widget):
|
||||
def _render_registration(self, rect: rl.Rectangle):
|
||||
"""Render registration prompt."""
|
||||
|
||||
rl.draw_rectangle_rounded(rl.Rectangle(rect.x, rect.y, rect.width, 630), 0.02, 20, rl.Color(51, 51, 51, 255))
|
||||
rl.draw_rectangle_rounded(rl.Rectangle(rect.x, rect.y, rect.width, rect.height), 0.02, 20, rl.Color(51, 51, 51, 255))
|
||||
|
||||
x = rect.x + 64
|
||||
y = rect.y + 48
|
||||
@@ -45,15 +47,15 @@ class SetupWidget(Widget):
|
||||
wrapped = wrap_text(light_font, desc, 50, int(w))
|
||||
for line in wrapped:
|
||||
rl.draw_text_ex(light_font, line, rl.Vector2(x, y), 50, 0, rl.WHITE)
|
||||
y += 50
|
||||
y += 50 * FONT_SCALE
|
||||
|
||||
button_rect = rl.Rectangle(x, y + 50, w, 128)
|
||||
button_rect = rl.Rectangle(x, y + 30, w, 200)
|
||||
self._pair_device_btn.render(button_rect)
|
||||
|
||||
def _render_firehose_prompt(self, rect: rl.Rectangle):
|
||||
"""Render firehose prompt widget."""
|
||||
|
||||
rl.draw_rectangle_rounded(rl.Rectangle(rect.x, rect.y, rect.width, 450), 0.02, 20, rl.Color(51, 51, 51, 255))
|
||||
rl.draw_rectangle_rounded(rl.Rectangle(rect.x, rect.y, rect.width, 500), 0.02, 20, rl.Color(51, 51, 51, 255))
|
||||
|
||||
# Content margins (56, 40, 56, 40)
|
||||
x = rect.x + 56
|
||||
@@ -62,9 +64,8 @@ class SetupWidget(Widget):
|
||||
spacing = 42
|
||||
|
||||
# Title with fire emojis
|
||||
title_font = gui_app.font(FontWeight.MEDIUM)
|
||||
title_text = "Firehose Mode"
|
||||
rl.draw_text_ex(title_font, title_text, rl.Vector2(x, y), 64, 0, rl.WHITE)
|
||||
# TODO: fix Label centering with emojis
|
||||
self._firehose_label.render(rl.Rectangle(x - 40, y, w, 64))
|
||||
y += 64 + spacing
|
||||
|
||||
# Description
|
||||
@@ -74,7 +75,7 @@ class SetupWidget(Widget):
|
||||
|
||||
for line in wrapped_desc:
|
||||
rl.draw_text_ex(desc_font, line, rl.Vector2(x, y), 40, 0, rl.WHITE)
|
||||
y += 40
|
||||
y += 40 * FONT_SCALE
|
||||
|
||||
y += spacing
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ from openpilot.system.ui.widgets.list_view import (
|
||||
BUTTON_WIDTH,
|
||||
)
|
||||
|
||||
VALUE_FONT_SIZE = 48
|
||||
|
||||
|
||||
class SshKeyActionState(Enum):
|
||||
LOADING = "LOADING"
|
||||
@@ -38,7 +40,7 @@ class SshKeyAction(ItemAction):
|
||||
self._keyboard = Keyboard(min_text_size=1)
|
||||
self._params = Params()
|
||||
self._error_message: str = ""
|
||||
self._text_font = gui_app.font(FontWeight.MEDIUM)
|
||||
self._text_font = gui_app.font(FontWeight.NORMAL)
|
||||
self._button = Button("", click_callback=self._handle_button_click, button_style=ButtonStyle.LIST_ACTION,
|
||||
border_radius=BUTTON_BORDER_RADIUS, font_size=BUTTON_FONT_SIZE)
|
||||
|
||||
@@ -62,14 +64,14 @@ class SshKeyAction(ItemAction):
|
||||
|
||||
# Draw username if exists
|
||||
if self._username:
|
||||
text_size = measure_text_cached(self._text_font, self._username, BUTTON_FONT_SIZE)
|
||||
text_size = measure_text_cached(self._text_font, self._username, VALUE_FONT_SIZE)
|
||||
rl.draw_text_ex(
|
||||
self._text_font,
|
||||
self._username,
|
||||
(rect.x + rect.width - BUTTON_WIDTH - text_size.x - 30, rect.y + (rect.height - text_size.y) / 2),
|
||||
BUTTON_FONT_SIZE,
|
||||
VALUE_FONT_SIZE,
|
||||
1.0,
|
||||
rl.WHITE,
|
||||
rl.Color(170, 170, 170, 255),
|
||||
)
|
||||
|
||||
# Draw button
|
||||
|
||||
@@ -30,6 +30,10 @@ SCALE = float(os.getenv("SCALE", "1.0"))
|
||||
DEFAULT_TEXT_SIZE = 60
|
||||
DEFAULT_TEXT_COLOR = rl.WHITE
|
||||
|
||||
# Qt draws fonts accounting for ascent/descent differently, so compensate to match old styles
|
||||
# The real scales for the fonts below range from 1.212 to 1.266
|
||||
FONT_SCALE = 1.242
|
||||
|
||||
ASSETS_DIR = files("openpilot.selfdrive").joinpath("assets")
|
||||
FONT_DIR = ASSETS_DIR.joinpath("fonts")
|
||||
|
||||
@@ -173,6 +177,7 @@ class GuiApplication:
|
||||
self._target_fps = fps
|
||||
self._set_styles()
|
||||
self._load_fonts()
|
||||
self._patch_text_functions()
|
||||
|
||||
if not PC:
|
||||
self._mouse.start()
|
||||
@@ -356,6 +361,16 @@ class GuiApplication:
|
||||
rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiControlProperty.TEXT_COLOR_NORMAL, rl.color_to_int(DEFAULT_TEXT_COLOR))
|
||||
rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiControlProperty.BASE_COLOR_NORMAL, rl.color_to_int(rl.Color(50, 50, 50, 255)))
|
||||
|
||||
def _patch_text_functions(self):
|
||||
# Wrap pyray text APIs to apply a global text size scale so our px sizes match Qt
|
||||
if not hasattr(rl, "_orig_draw_text_ex"):
|
||||
rl._orig_draw_text_ex = rl.draw_text_ex
|
||||
|
||||
def _draw_text_ex_scaled(font, text, position, font_size, spacing, tint):
|
||||
return rl._orig_draw_text_ex(font, text, position, font_size * FONT_SCALE, spacing, tint)
|
||||
|
||||
rl.draw_text_ex = _draw_text_ex_scaled
|
||||
|
||||
def _set_log_callback(self):
|
||||
ffi_libc = cffi.FFI()
|
||||
ffi_libc.cdef("""
|
||||
|
||||
@@ -128,3 +128,7 @@ class GuiScrollPanel:
|
||||
self._offset_filter_y.x = position
|
||||
self._velocity_filter_y.x = 0.0
|
||||
self._scroll_state = ScrollState.IDLE
|
||||
|
||||
@property
|
||||
def offset(self) -> float:
|
||||
return float(self._offset_filter_y.x)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import pyray as rl
|
||||
from openpilot.system.ui.lib.application import FONT_SCALE
|
||||
|
||||
_cache: dict[int, rl.Vector2] = {}
|
||||
|
||||
@@ -9,6 +10,6 @@ def measure_text_cached(font: rl.Font, text: str, font_size: int, spacing: int =
|
||||
if key in _cache:
|
||||
return _cache[key]
|
||||
|
||||
result = rl.measure_text_ex(font, text, font_size, spacing) # noqa: TID251
|
||||
result = rl.measure_text_ex(font, text, font_size * FONT_SCALE, spacing) # noqa: TID251
|
||||
_cache[key] = result
|
||||
return result
|
||||
|
||||
@@ -3,7 +3,7 @@ import pyray as rl
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE
|
||||
from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
|
||||
from openpilot.system.ui.lib.wrap_text import wrap_text
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
@@ -45,7 +45,7 @@ class HtmlElement:
|
||||
font_weight: FontWeight
|
||||
margin_top: int
|
||||
margin_bottom: int
|
||||
line_height: float = 1.2
|
||||
line_height: float = 0.9 # matches Qt visually, unsure why not default 1.2
|
||||
indent_level: int = 0
|
||||
|
||||
|
||||
@@ -61,16 +61,19 @@ class HtmlRenderer(Widget):
|
||||
if text_size is None:
|
||||
text_size = {}
|
||||
|
||||
# Base paragraph size (Qt stylesheet default is 48px in offroad alerts)
|
||||
base_p_size = int(text_size.get(ElementType.P, 48))
|
||||
|
||||
# Untagged text defaults to <p>
|
||||
self.styles: dict[ElementType, dict[str, Any]] = {
|
||||
ElementType.H1: {"size": 68, "weight": FontWeight.BOLD, "margin_top": 20, "margin_bottom": 16},
|
||||
ElementType.H2: {"size": 60, "weight": FontWeight.BOLD, "margin_top": 24, "margin_bottom": 12},
|
||||
ElementType.H3: {"size": 52, "weight": FontWeight.BOLD, "margin_top": 20, "margin_bottom": 10},
|
||||
ElementType.H4: {"size": 48, "weight": FontWeight.BOLD, "margin_top": 16, "margin_bottom": 8},
|
||||
ElementType.H5: {"size": 44, "weight": FontWeight.BOLD, "margin_top": 12, "margin_bottom": 6},
|
||||
ElementType.H6: {"size": 40, "weight": FontWeight.BOLD, "margin_top": 10, "margin_bottom": 4},
|
||||
ElementType.P: {"size": text_size.get(ElementType.P, 38), "weight": FontWeight.NORMAL, "margin_top": 8, "margin_bottom": 12},
|
||||
ElementType.LI: {"size": 38, "weight": FontWeight.NORMAL, "color": rl.Color(40, 40, 40, 255), "margin_top": 6, "margin_bottom": 6},
|
||||
ElementType.H1: {"size": round(base_p_size * 2), "weight": FontWeight.BOLD, "margin_top": 20, "margin_bottom": 16},
|
||||
ElementType.H2: {"size": round(base_p_size * 1.50), "weight": FontWeight.BOLD, "margin_top": 24, "margin_bottom": 12},
|
||||
ElementType.H3: {"size": round(base_p_size * 1.17), "weight": FontWeight.BOLD, "margin_top": 20, "margin_bottom": 10},
|
||||
ElementType.H4: {"size": round(base_p_size * 1.00), "weight": FontWeight.BOLD, "margin_top": 16, "margin_bottom": 8},
|
||||
ElementType.H5: {"size": round(base_p_size * 0.83), "weight": FontWeight.BOLD, "margin_top": 12, "margin_bottom": 6},
|
||||
ElementType.H6: {"size": round(base_p_size * 0.67), "weight": FontWeight.BOLD, "margin_top": 10, "margin_bottom": 4},
|
||||
ElementType.P: {"size": base_p_size, "weight": FontWeight.NORMAL, "margin_top": 8, "margin_bottom": 12},
|
||||
ElementType.LI: {"size": base_p_size, "weight": FontWeight.NORMAL, "color": rl.Color(40, 40, 40, 255), "margin_top": 6, "margin_bottom": 6},
|
||||
ElementType.BR: {"size": 0, "weight": FontWeight.NORMAL, "margin_top": 0, "margin_bottom": 12},
|
||||
}
|
||||
|
||||
@@ -179,8 +182,9 @@ class HtmlRenderer(Widget):
|
||||
wrapped_lines = wrap_text(font, element.content, element.font_size, int(content_width))
|
||||
|
||||
for line in wrapped_lines:
|
||||
if current_y < rect.y - element.font_size:
|
||||
current_y += element.font_size * element.line_height
|
||||
# Use FONT_SCALE from wrapped raylib text functions to match what is drawn
|
||||
if current_y < rect.y - element.font_size * FONT_SCALE:
|
||||
current_y += element.font_size * FONT_SCALE * element.line_height
|
||||
continue
|
||||
|
||||
if current_y > rect.y + rect.height:
|
||||
@@ -189,7 +193,7 @@ class HtmlRenderer(Widget):
|
||||
text_x = rect.x + (max(element.indent_level - 1, 0) * LIST_INDENT_PX)
|
||||
rl.draw_text_ex(font, line, rl.Vector2(text_x + padding, current_y), element.font_size, 0, self._text_color)
|
||||
|
||||
current_y += element.font_size * element.line_height
|
||||
current_y += element.font_size * FONT_SCALE * element.line_height
|
||||
|
||||
# Apply bottom margin
|
||||
current_y += element.margin_bottom
|
||||
@@ -213,7 +217,7 @@ class HtmlRenderer(Widget):
|
||||
wrapped_lines = wrap_text(font, element.content, element.font_size, int(usable_width))
|
||||
|
||||
for _ in wrapped_lines:
|
||||
total_height += element.font_size * element.line_height
|
||||
total_height += element.font_size * FONT_SCALE * element.line_height
|
||||
|
||||
total_height += element.margin_bottom
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import pyray as rl
|
||||
import time
|
||||
from openpilot.system.ui.lib.application import gui_app, MousePos
|
||||
from openpilot.system.ui.lib.application import gui_app, MousePos, FONT_SCALE
|
||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
|
||||
@@ -130,7 +130,7 @@ class InputBox(Widget):
|
||||
rl.draw_text_ex(
|
||||
font,
|
||||
display_text,
|
||||
rl.Vector2(int(rect.x + padding - self._text_offset), int(rect.y + rect.height / 2 - font_size / 2)),
|
||||
rl.Vector2(int(rect.x + padding - self._text_offset), int(rect.y + rect.height / 2 - font_size * FONT_SCALE / 2)),
|
||||
font_size,
|
||||
0,
|
||||
text_color,
|
||||
@@ -145,7 +145,7 @@ class InputBox(Widget):
|
||||
# Apply text offset to cursor position
|
||||
cursor_x -= self._text_offset
|
||||
|
||||
cursor_height = font_size + 4
|
||||
cursor_height = font_size * FONT_SCALE + 4
|
||||
cursor_y = rect.y + rect.height / 2 - cursor_height / 2
|
||||
rl.draw_line(int(cursor_x), int(cursor_y), int(cursor_x), int(cursor_y + cursor_height), rl.WHITE)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ from itertools import zip_longest
|
||||
|
||||
import pyray as rl
|
||||
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, DEFAULT_TEXT_SIZE, DEFAULT_TEXT_COLOR
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, DEFAULT_TEXT_SIZE, DEFAULT_TEXT_COLOR, FONT_SCALE
|
||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||
from openpilot.system.ui.lib.utils import GuiStyleContext
|
||||
from openpilot.system.ui.lib.emoji import find_emoji, emoji_tex
|
||||
@@ -171,7 +171,7 @@ class Label(Widget):
|
||||
|
||||
tex = emoji_tex(emoji)
|
||||
rl.draw_texture_ex(tex, line_pos, 0.0, self._font_size / tex.height, self._text_color)
|
||||
line_pos.x += self._font_size
|
||||
line_pos.x += self._font_size * FONT_SCALE
|
||||
prev_index = end
|
||||
rl.draw_text_ex(self._font, text[prev_index:], line_pos, self._font_size, 0, self._text_color)
|
||||
text_pos.y += text_size.y or self._font_size
|
||||
text_pos.y += text_size.y or self._font_size * FONT_SCALE
|
||||
|
||||
Reference in New Issue
Block a user