python ui: refactor spinner&text (#34897)

refactor spinner&text
pull/34908/head
Dean Lee 1 month ago committed by GitHub
parent e785026a98
commit 6d12ade231
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 6
      system/ui/lib/application.py
  2. 61
      system/ui/spinner.py
  3. 44
      system/ui/text.py

@ -64,11 +64,16 @@ class GuiApplication:
return texture return texture
def close(self): def close(self):
if not rl.is_window_ready():
return
for texture in self._textures: for texture in self._textures:
rl.unload_texture(texture) rl.unload_texture(texture)
self._textures = []
for font in self._fonts.values(): for font in self._fonts.values():
rl.unload_font(font) rl.unload_font(font)
self._fonts = []
rl.close_window() rl.close_window()
@ -120,7 +125,6 @@ class GuiApplication:
rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_SIZE, DEFAULT_TEXT_SIZE) rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_SIZE, DEFAULT_TEXT_SIZE)
rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.BACKGROUND_COLOR, rl.color_to_int(rl.BLACK)) rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.BACKGROUND_COLOR, rl.color_to_int(rl.BLACK))
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.TEXT_COLOR_NORMAL, rl.color_to_int(DEFAULT_TEXT_COLOR))
rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.BACKGROUND_COLOR, rl.color_to_int(rl.Color(30, 30, 30, 255)))
rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiControlProperty.BASE_COLOR_NORMAL, rl.color_to_int(rl.Color(50, 50, 50, 255))) rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiControlProperty.BASE_COLOR_NORMAL, rl.color_to_int(rl.Color(50, 50, 50, 255)))
def _monitor_fps(self): def _monitor_fps(self):

@ -1,8 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import pyray as rl import pyray as rl
import os import os
import select import threading
import sys
from openpilot.common.basedir import BASEDIR from openpilot.common.basedir import BASEDIR
from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.application import gui_app
@ -20,57 +19,57 @@ def clamp(value, min_value, max_value):
return max(min(value, max_value), min_value) return max(min(value, max_value), min_value)
def check_input_non_blocking(): class Spinner:
if sys.stdin in select.select([sys.stdin], [], [], 0)[0]: def __init__(self):
return sys.stdin.readline().strip() self._comma_texture = gui_app.load_texture_from_image(os.path.join(BASEDIR, "selfdrive/assets/img_spinner_comma.png"), TEXTURE_SIZE, TEXTURE_SIZE)
return "" self._spinner_texture = gui_app.load_texture_from_image(os.path.join(BASEDIR, "selfdrive/assets/img_spinner_track.png"), TEXTURE_SIZE, TEXTURE_SIZE)
self._rotation = 0.0
self._text: str = ""
self._lock = threading.Lock()
def set_text(self, text: str) -> None:
with self._lock:
self._text = text
def main(): def render(self):
gui_app.init_window("Spinner")
# Load textures
comma_texture = gui_app.load_texture_from_image(os.path.join(BASEDIR, "selfdrive/assets/img_spinner_comma.png"), TEXTURE_SIZE, TEXTURE_SIZE)
spinner_texture = gui_app.load_texture_from_image(os.path.join(BASEDIR, "selfdrive/assets/img_spinner_track.png"), TEXTURE_SIZE, TEXTURE_SIZE)
# Initial values
rotation = 0.0
user_input = ""
center = rl.Vector2(gui_app.width / 2.0, gui_app.height / 2.0) center = rl.Vector2(gui_app.width / 2.0, gui_app.height / 2.0)
spinner_origin = rl.Vector2(TEXTURE_SIZE / 2.0, TEXTURE_SIZE / 2.0) spinner_origin = rl.Vector2(TEXTURE_SIZE / 2.0, TEXTURE_SIZE / 2.0)
comma_position = rl.Vector2(center.x - TEXTURE_SIZE / 2.0, center.y - TEXTURE_SIZE / 2.0) comma_position = rl.Vector2(center.x - TEXTURE_SIZE / 2.0, center.y - TEXTURE_SIZE / 2.0)
for _ in gui_app.render():
fps = rl.get_fps() fps = rl.get_fps()
if fps > 0: if fps > 0:
degrees_per_frame = 360.0 / (ROTATION_TIME_SECONDS * fps) degrees_per_frame = 360.0 / (ROTATION_TIME_SECONDS * fps)
rotation = (rotation + degrees_per_frame) % 360.0 self._rotation = (self._rotation + degrees_per_frame) % 360.0
# Draw rotating spinner and static comma logo # Draw rotating spinner and static comma logo
rl.draw_texture_pro(spinner_texture, rl.Rectangle(0, 0, TEXTURE_SIZE, TEXTURE_SIZE), rl.draw_texture_pro(self._spinner_texture, rl.Rectangle(0, 0, TEXTURE_SIZE, TEXTURE_SIZE),
rl.Rectangle(center.x, center.y, TEXTURE_SIZE, TEXTURE_SIZE), rl.Rectangle(center.x, center.y, TEXTURE_SIZE, TEXTURE_SIZE),
spinner_origin, rotation, rl.WHITE) spinner_origin, self._rotation, rl.WHITE)
rl.draw_texture_v(comma_texture, comma_position, rl.WHITE) rl.draw_texture_v(self._comma_texture, comma_position, rl.WHITE)
# Read user input
if input_str := check_input_non_blocking():
user_input = input_str
# Display progress bar or text based on user input # Display progress bar or text based on user input
if user_input: text = None
with self._lock:
text = self._text
if text:
y_pos = rl.get_screen_height() - MARGIN - PROGRESS_BAR_HEIGHT y_pos = rl.get_screen_height() - MARGIN - PROGRESS_BAR_HEIGHT
if user_input.isdigit(): if text.isdigit():
progress = clamp(int(user_input), 0, 100) progress = clamp(int(text), 0, 100)
bar = rl.Rectangle(center.x - PROGRESS_BAR_WIDTH / 2.0, y_pos, PROGRESS_BAR_WIDTH, PROGRESS_BAR_HEIGHT) bar = rl.Rectangle(center.x - PROGRESS_BAR_WIDTH / 2.0, y_pos, PROGRESS_BAR_WIDTH, PROGRESS_BAR_HEIGHT)
rl.draw_rectangle_rounded(bar, 0.5, 10, rl.GRAY) rl.draw_rectangle_rounded(bar, 0.5, 10, rl.GRAY)
bar.width *= progress / 100.0 bar.width *= progress / 100.0
rl.draw_rectangle_rounded(bar, 0.5, 10, rl.WHITE) rl.draw_rectangle_rounded(bar, 0.5, 10, rl.WHITE)
else: else:
text_size = rl.measure_text_ex(gui_app.font(), user_input, FONT_SIZE, 1.0) text_size = rl.measure_text_ex(gui_app.font(), text, FONT_SIZE, 1.0)
rl.draw_text_ex(gui_app.font(), user_input, rl.draw_text_ex(gui_app.font(), text,
rl.Vector2(center.x - text_size.x / 2, y_pos), FONT_SIZE, 1.0, rl.WHITE) rl.Vector2(center.x - text_size.x / 2, y_pos), FONT_SIZE, 1.0, rl.WHITE)
if __name__ == "__main__": if __name__ == "__main__":
main() gui_app.init_window("Spinner")
spinner = Spinner()
spinner.set_text("Spinner text")
for _ in gui_app.render():
spinner.render()

@ -1,7 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import sys
import pyray as rl import pyray as rl
from openpilot.system.hardware import HARDWARE from openpilot.system.hardware import HARDWARE
from openpilot.system.ui.lib.button import gui_button, ButtonStyle from openpilot.system.ui.lib.button import gui_button, ButtonStyle
from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
@ -33,32 +31,36 @@ def wrap_text(text, font_size, max_width):
return lines return lines
class TextWindow:
def __init__(self, text: str):
self._textarea_rect = rl.Rectangle(MARGIN, MARGIN, gui_app.width - MARGIN * 2, gui_app.height - MARGIN * 2)
self._wrapped_lines = wrap_text(text, FONT_SIZE, self._textarea_rect.width - 20)
self._content_rect = rl.Rectangle(0, 0, self._textarea_rect.width - 20, len(self._wrapped_lines) * LINE_HEIGHT)
self._scroll_panel = GuiScrollPanel(show_vertical_scroll_bar=True)
def main(): def render(self):
gui_app.init_window("Text") scroll = self._scroll_panel.handle_scroll(self._textarea_rect, self._content_rect)
rl.begin_scissor_mode(int(self._textarea_rect.x), int(self._textarea_rect.y), int(self._textarea_rect.width), int(self._textarea_rect.height))
text_content = sys.argv[1] if len(sys.argv) > 1 else DEMO_TEXT for i, line in enumerate(self._wrapped_lines):
position = rl.Vector2(self._textarea_rect.x + scroll.x, self._textarea_rect.y + scroll.y + i * LINE_HEIGHT)
textarea_rect = rl.Rectangle(MARGIN, MARGIN, gui_app.width - MARGIN * 2, gui_app.height - MARGIN * 2) if position.y + LINE_HEIGHT < self._textarea_rect.y or position.y > self._textarea_rect.y + self._textarea_rect.height:
wrapped_lines = wrap_text(text_content, FONT_SIZE, textarea_rect.width - 20)
content_rect = rl.Rectangle(0, 0, textarea_rect.width - 20, len(wrapped_lines) * LINE_HEIGHT)
scroll_panel = GuiScrollPanel(show_vertical_scroll_bar=True)
for _ in gui_app.render():
scroll = scroll_panel.handle_scroll(textarea_rect, content_rect)
rl.begin_scissor_mode(int(textarea_rect.x), int(textarea_rect.y), int(textarea_rect.width), int(textarea_rect.height))
for i, line in enumerate(wrapped_lines):
position = rl.Vector2(textarea_rect.x + scroll.x, textarea_rect.y + scroll.y + i * LINE_HEIGHT)
if position.y + LINE_HEIGHT < textarea_rect.y or position.y > textarea_rect.y + textarea_rect.height:
continue continue
rl.draw_text_ex(gui_app.font(), line.strip(), position, FONT_SIZE, 0, rl.WHITE) rl.draw_text_ex(gui_app.font(), line.strip(), position, FONT_SIZE, 0, rl.WHITE)
rl.end_scissor_mode() rl.end_scissor_mode()
button_bounds = rl.Rectangle(gui_app.width - MARGIN - BUTTON_SIZE.x, gui_app.height - MARGIN - BUTTON_SIZE.y, BUTTON_SIZE.x, BUTTON_SIZE.y) button_bounds = rl.Rectangle(gui_app.width - MARGIN - BUTTON_SIZE.x, gui_app.height - MARGIN - BUTTON_SIZE.y, BUTTON_SIZE.x, BUTTON_SIZE.y)
if gui_button(button_bounds, "Reboot", button_style=ButtonStyle.TRANSPARENT): ret = gui_button(button_bounds, "Reboot", button_style=ButtonStyle.TRANSPARENT)
if ret:
HARDWARE.reboot() HARDWARE.reboot()
return ret
def show_text_in_window(text: str):
gui_app.init_window("Text")
text_window = TextWindow(text)
for _ in gui_app.render():
text_window.render()
if __name__ == "__main__": if __name__ == "__main__":
main() show_text_in_window(DEMO_TEXT)

Loading…
Cancel
Save