openpilot is an open source driver assistance system. openpilot performs the functions of Automated Lane Centering and Adaptive Cruise Control for over 200 supported car makes and models.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

177 lines
6.9 KiB

from enum import IntEnum
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.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
from openpilot.system.ui.lib.wrap_text import wrap_text
from openpilot.system.ui.widgets import Widget
ICON_PADDING = 15
class TextAlignment(IntEnum):
LEFT = 0
CENTER = 1
RIGHT = 2
# TODO: This should be a Widget class
def gui_label(
rect: rl.Rectangle,
text: str,
font_size: int = DEFAULT_TEXT_SIZE,
color: rl.Color = DEFAULT_TEXT_COLOR,
font_weight: FontWeight = FontWeight.NORMAL,
alignment: int = rl.GuiTextAlignment.TEXT_ALIGN_LEFT,
alignment_vertical: int = rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE,
elide_right: bool = True
):
font = gui_app.font(font_weight)
text_size = measure_text_cached(font, text, font_size)
display_text = text
# Elide text to fit within the rectangle
if elide_right and text_size.x > rect.width:
ellipsis = "..."
left, right = 0, len(text)
while left < right:
mid = (left + right) // 2
candidate = text[:mid] + ellipsis
candidate_size = measure_text_cached(font, candidate, font_size)
if candidate_size.x <= rect.width:
left = mid + 1
else:
right = mid
display_text = text[: left - 1] + ellipsis if left > 0 else ellipsis
text_size = measure_text_cached(font, display_text, font_size)
# Calculate horizontal position based on alignment
text_x = rect.x + {
rl.GuiTextAlignment.TEXT_ALIGN_LEFT: 0,
rl.GuiTextAlignment.TEXT_ALIGN_CENTER: (rect.width - text_size.x) / 2,
rl.GuiTextAlignment.TEXT_ALIGN_RIGHT: rect.width - text_size.x,
}.get(alignment, 0)
# Calculate vertical position based on alignment
text_y = rect.y + {
rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP: 0,
rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE: (rect.height - text_size.y) / 2,
rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM: rect.height - text_size.y,
}.get(alignment_vertical, 0)
# Draw the text in the specified rectangle
rl.draw_text_ex(font, display_text, rl.Vector2(text_x, text_y), font_size, 0, color)
def gui_text_box(
rect: rl.Rectangle,
text: str,
font_size: int = DEFAULT_TEXT_SIZE,
color: rl.Color = DEFAULT_TEXT_COLOR,
alignment: int = rl.GuiTextAlignment.TEXT_ALIGN_LEFT,
alignment_vertical: int = rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP,
font_weight: FontWeight = FontWeight.NORMAL,
):
styles = [
(rl.GuiControl.DEFAULT, rl.GuiControlProperty.TEXT_COLOR_NORMAL, rl.color_to_int(color)),
(rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_SIZE, font_size),
(rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_LINE_SPACING, font_size),
(rl.GuiControl.DEFAULT, rl.GuiControlProperty.TEXT_ALIGNMENT, alignment),
(rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_ALIGNMENT_VERTICAL, alignment_vertical),
(rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_WRAP_MODE, rl.GuiTextWrapMode.TEXT_WRAP_WORD)
]
if font_weight != FontWeight.NORMAL:
rl.gui_set_font(gui_app.font(font_weight))
with GuiStyleContext(styles):
rl.gui_label(rect, text)
if font_weight != FontWeight.NORMAL:
rl.gui_set_font(gui_app.font(FontWeight.NORMAL))
# Non-interactive text area. Can render emojis and an optional specified icon.
class Label(Widget):
def __init__(self,
text: str,
font_size: int = DEFAULT_TEXT_SIZE,
font_weight: FontWeight = FontWeight.NORMAL,
text_alignment: TextAlignment = TextAlignment.CENTER,
text_padding: int = 20,
text_color: rl.Color = DEFAULT_TEXT_COLOR,
icon = None,
):
super().__init__()
self._font_weight = font_weight
self._font = gui_app.font(self._font_weight)
self._font_size = font_size
self._text_alignment = text_alignment
self._text_padding = text_padding
self._text_color = text_color
self._icon = icon
self.set_text(text)
def set_text(self, text):
self._text_raw = text
self._update_text(self._text_raw)
def set_text_color(self, color):
self._text_color = color
def _update_layout_rects(self):
self._update_text(self._text_raw)
def _update_text(self, text):
self._emojis = []
self._text_size = []
self._text = wrap_text(self._font, text, self._font_size, self._rect.width - (self._text_padding*2))
for t in self._text:
self._emojis.append(find_emoji(t))
self._text_size.append(measure_text_cached(self._font, t, self._font_size))
def _render(self, _):
text = self._text[0] if self._text else None
text_size = self._text_size[0] if self._text_size else rl.Vector2(0.0, 0.0)
text_pos = rl.Vector2(0, (self._rect.y + (self._rect.height - (text_size.y)) // 2))
if self._icon:
icon_y = self._rect.y + (self._rect.height - self._icon.height) / 2
if text:
if self._text_alignment == TextAlignment.LEFT:
icon_x = self._rect.x + self._text_padding
text_pos.x = self._icon.width + ICON_PADDING
elif self._text_alignment == TextAlignment.CENTER:
total_width = self._icon.width + ICON_PADDING + text_size.x
icon_x = self._rect.x + (self._rect.width - total_width) / 2
text_pos.x = self._icon.width + ICON_PADDING
else:
icon_x = (self._rect.x + self._rect.width - text_size.x - self._text_padding) - ICON_PADDING - self._icon.width
else:
icon_x = self._rect.x + (self._rect.width - self._icon.width) / 2
rl.draw_texture_v(self._icon, rl.Vector2(icon_x, icon_y), rl.WHITE)
for text, text_size, emojis in zip_longest(self._text, self._text_size, self._emojis, fillvalue=[]):
line_pos = rl.Vector2(text_pos.x, text_pos.y)
if self._text_alignment == TextAlignment.LEFT:
line_pos.x += self._rect.x + self._text_padding
elif self._text_alignment == TextAlignment.CENTER:
line_pos.x += self._rect.x + (self._rect.width - text_size.x) // 2
elif self._text_alignment == TextAlignment.RIGHT:
line_pos.x += self._rect.x + self._rect.width - text_size.x - self._text_padding
prev_index = 0
for start, end, emoji in emojis:
text_before = text[prev_index:start]
width_before = measure_text_cached(self._font, text_before, self._font_size)
rl.draw_text_ex(self._font, text_before, line_pos, self._font_size, 0, self._text_color)
line_pos.x += width_before.x
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
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