Skip to content

Commit ce7ff5c

Browse files
authored
ui: Initial UI rewrite using pyray (spinner and text window) (#34583)
* pyray init version * remove c++ code * cleanup * restruct the directory layout * improve GuiApplication * smooth out the texture after resize * use atexit to close app * rename FontSize->FontWeight * make files executable * use Inter Regular for FrontWeight.NORMAL * set FLAG_VSYNC_HINT to avoid tearing while scrolling * smoother scrolling * mange textures in gui_app
1 parent 958c8d1 commit ce7ff5c

File tree

16 files changed

+327
-192
lines changed

16 files changed

+327
-192
lines changed

SConstruct

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,6 @@ SConscript(['rednose/SConscript'])
352352

353353
# Build system services
354354
SConscript([
355-
'system/ui/SConscript',
356355
'system/proclogd/SConscript',
357356
'system/ubloxd/SConscript',
358357
'system/loggerd/SConscript',

scripts/lint/lint.sh

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ function run_tests() {
5353
run "check_shebang_scripts_are_executable" python3 -m pre_commit_hooks.check_shebang_scripts_are_executable $ALL_FILES
5454
run "check_shebang_format" $DIR/check_shebang_format.sh $ALL_FILES
5555
run "check_nomerge_comments" $DIR/check_nomerge_comments.sh $ALL_FILES
56-
run "check_raylib_includes" $DIR/check_raylib_includes.sh $ALL_FILES
5756
5857
if [[ -z "$FAST" ]]; then
5958
run "mypy" mypy $PYTHON_FILES

system/ui/.gitignore

Lines changed: 0 additions & 1 deletion
This file was deleted.

system/ui/SConscript

Lines changed: 0 additions & 20 deletions
This file was deleted.

system/ui/lib/__init__.py

Whitespace-only changes.

system/ui/lib/application.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import atexit
2+
import os
3+
import pyray as rl
4+
from enum import IntEnum
5+
from openpilot.common.basedir import BASEDIR
6+
7+
DEFAULT_TEXT_SIZE = 60
8+
DEFAULT_FPS = 60
9+
FONT_DIR = os.path.join(BASEDIR, "selfdrive/assets/fonts")
10+
11+
class FontWeight(IntEnum):
12+
BLACK = 0
13+
BOLD = 1
14+
EXTRA_BOLD = 2
15+
EXTRA_LIGHT = 3
16+
MEDIUM = 4
17+
NORMAL = 5
18+
SEMI_BOLD= 6
19+
THIN = 7
20+
21+
22+
class GuiApplication:
23+
def __init__(self, width: int, height: int):
24+
self._fonts: dict[FontWeight, rl.Font] = {}
25+
self._width = width
26+
self._height = height
27+
self._textures: list[rl.Texture] = []
28+
29+
def init_window(self, title: str, fps: int=DEFAULT_FPS):
30+
atexit.register(self.close) # Automatically call close() on exit
31+
32+
rl.set_config_flags(rl.ConfigFlags.FLAG_MSAA_4X_HINT | rl.ConfigFlags.FLAG_VSYNC_HINT)
33+
rl.init_window(self._width, self._height, title)
34+
rl.set_target_fps(fps)
35+
36+
self._set_styles()
37+
self._load_fonts()
38+
39+
def load_texture_from_image(self, file_name: str, width: int, height: int):
40+
"""Load and resize a texture, storing it for later automatic unloading."""
41+
image = rl.load_image(file_name)
42+
rl.image_resize(image, width, height)
43+
texture = rl.load_texture_from_image(image)
44+
# Set texture filtering to smooth the result
45+
rl.set_texture_filter(texture, rl.TextureFilter.TEXTURE_FILTER_BILINEAR)
46+
47+
rl.unload_image(image)
48+
49+
self._textures.append(texture)
50+
return texture
51+
52+
def close(self):
53+
for texture in self._textures:
54+
rl.unload_texture(texture)
55+
56+
for font in self._fonts.values():
57+
rl.unload_font(font)
58+
59+
rl.close_window()
60+
61+
def font(self, font_wight: FontWeight=FontWeight.NORMAL):
62+
return self._fonts[font_wight]
63+
64+
@property
65+
def width(self):
66+
return self._width
67+
68+
@property
69+
def height(self):
70+
return self._height
71+
72+
def _load_fonts(self):
73+
font_files = (
74+
"Inter-Black.ttf",
75+
"Inter-Bold.ttf",
76+
"Inter-ExtraBold.ttf",
77+
"Inter-ExtraLight.ttf",
78+
"Inter-Medium.ttf",
79+
"Inter-Regular.ttf",
80+
"Inter-SemiBold.ttf",
81+
"Inter-Thin.ttf"
82+
)
83+
84+
for index, font_file in enumerate(font_files):
85+
font = rl.load_font_ex(os.path.join(FONT_DIR, font_file), 120, None, 0)
86+
rl.set_texture_filter(font.texture, rl.TextureFilter.TEXTURE_FILTER_BILINEAR)
87+
self._fonts[index] = font
88+
89+
rl.gui_set_font(self._fonts[FontWeight.NORMAL])
90+
91+
def _set_styles(self):
92+
rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiControlProperty.BORDER_WIDTH, 0)
93+
rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_SIZE, DEFAULT_TEXT_SIZE)
94+
rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.BACKGROUND_COLOR, rl.color_to_int(rl.BLACK))
95+
rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiControlProperty.TEXT_COLOR_NORMAL, rl.color_to_int(rl.Color(200, 200, 200, 255)))
96+
rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.BACKGROUND_COLOR, rl.color_to_int(rl.Color(30, 30, 30, 255)))
97+
rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiControlProperty.BASE_COLOR_NORMAL, rl.color_to_int(rl.Color(50, 50, 50, 255)))
98+
99+
100+
gui_app = GuiApplication(2160, 1080)

system/ui/lib/button.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
import pyray as rl
3+
from openpilot.system.ui.lib.utils import GuiStyleContext
4+
5+
BUTTON_DEFAULT_BG_COLOR = rl.Color(51, 51, 51, 255)
6+
7+
def gui_button(rect, text, bg_color=BUTTON_DEFAULT_BG_COLOR):
8+
styles = [
9+
(rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_ALIGNMENT_VERTICAL, rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE),
10+
(rl.GuiControl.DEFAULT, rl.GuiControlProperty.BASE_COLOR_NORMAL, rl.color_to_int(bg_color))
11+
]
12+
13+
with GuiStyleContext(styles):
14+
return rl.gui_button(rect, text)

system/ui/lib/label.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import pyray as rl
2+
from openpilot.system.ui.lib.utils import GuiStyleContext
3+
4+
def gui_label(rect, text, font_size):
5+
styles = [
6+
(rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_SIZE, font_size),
7+
(rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_LINE_SPACING, font_size),
8+
(rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_ALIGNMENT_VERTICAL, rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP),
9+
(rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_WRAP_MODE, rl.GuiTextWrapMode.TEXT_WRAP_WORD)
10+
]
11+
12+
with GuiStyleContext(styles):
13+
rl.gui_label(rect, text)

system/ui/lib/scroll_panel.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import pyray as rl
2+
from cffi import FFI
3+
4+
MOUSE_WHEEL_SCROLL_SPEED = 30
5+
6+
class GuiScrollPanel:
7+
def __init__(self, bounds: rl.Rectangle, content: rl.Rectangle, show_vertical_scroll_bar: bool = False):
8+
self._dragging: bool = False
9+
self._last_mouse_y: float = 0.0
10+
self._bounds = bounds
11+
self._content = content
12+
self._scroll = rl.Vector2(0, 0)
13+
self._view = rl.Rectangle(0, 0, 0, 0)
14+
self._show_vertical_scroll_bar: bool = show_vertical_scroll_bar
15+
16+
def handle_scroll(self)-> rl.Vector2:
17+
mouse_pos = rl.get_mouse_position()
18+
if rl.check_collision_point_rec(mouse_pos, self._bounds) and rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT):
19+
if not self._dragging:
20+
self._dragging = True
21+
self._last_mouse_y = mouse_pos.y
22+
23+
if self._dragging:
24+
if rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT):
25+
delta_y = mouse_pos.y - self._last_mouse_y
26+
self._scroll.y += delta_y
27+
self._last_mouse_y = mouse_pos.y
28+
else:
29+
self._dragging = False
30+
31+
wheel_move = rl.get_mouse_wheel_move()
32+
if self._show_vertical_scroll_bar:
33+
self._scroll.y += wheel_move * (MOUSE_WHEEL_SCROLL_SPEED - 20)
34+
rl.gui_scroll_panel(self._bounds, FFI().NULL, self._content, self._scroll, self._view)
35+
else:
36+
self._scroll.y += wheel_move * MOUSE_WHEEL_SCROLL_SPEED
37+
max_scroll_y = self._content.height - self._bounds.height
38+
self._scroll.y = max(min(self._scroll.y, 0), -max_scroll_y)
39+
40+
return self._scroll

system/ui/lib/utils.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import pyray as rl
2+
3+
class GuiStyleContext:
4+
def __init__(self, styles: list[tuple[int, int, int]]):
5+
"""styles is a list of tuples (control, prop, new_value)"""
6+
self.styles = styles
7+
self.prev_styles: list[tuple[int, int, int]] = []
8+
9+
def __enter__(self):
10+
for control, prop, new_value in self.styles:
11+
prev_value = rl.gui_get_style(control, prop)
12+
self.prev_styles.append((control, prop, prev_value))
13+
rl.gui_set_style(control, prop, new_value)
14+
15+
def __exit__(self, exc_type, exc_value, traceback):
16+
for control, prop, prev_value in self.prev_styles:
17+
rl.gui_set_style(control, prop, prev_value)

0 commit comments

Comments
 (0)