Files
onepilot/tsk/c3/ui/header.py
T
2026-04-10 13:49:54 -07:00

193 lines
5.8 KiB
Python

# tsk/c3/ui/header.py
"""
TSK Header widget with navigation and status display.
Pure Widget architecture - NO platform detection needed.
"""
from typing import Optional
import pyray as rl
from openpilot.system.ui.lib.application import gui_app
from tsk.c3.ui.button import TSKButton
from tsk.c3.ui.measure_text import measure_text
from tsk.c3.ui.layout import Theme
from tsk.common.key_file_manager import KeyFileManager
from tsk.common.widget import TSKWidget
class TSKHeader(TSKWidget):
"""
Header widget for TSK Manager.
Displays:
- Title with current menu name
- Navigation buttons (left/right based on current menu)
- Key installation status
NO platform detection needed - Widget handles everything.
"""
def __init__(self):
super().__init__()
self.key_manager = KeyFileManager()
self._current_menu = Theme.menu_tools
self._nav_result = None
# Create navigation buttons (TSKButton handles events automatically)
self._nav_left = TSKButton(
labels=[
{"text": "< Tools", "x_offset": 20, "y_offset": 30},
{"text": "< Menu", "x_offset": 20, "y_offset": 100},
],
click_callback=lambda: self._set_nav(Theme.menu_tools),
font_size=Theme.nav_button_font_size,
multi_line=True
)
self._nav_right = TSKButton(
labels=[
{"text": "Reboot >", "x_offset": 43, "y_offset": 30},
{"text": "Menu >", "x_offset": 90, "y_offset": 100},
],
click_callback=lambda: self._set_nav(Theme.menu_reboot),
font_size=Theme.nav_button_font_size,
multi_line=True
)
def _set_nav(self, menu_id: int):
"""Set navigation result when button is clicked."""
self._nav_result = menu_id
def get_height(self) -> float:
"""Calculate the total height of the header."""
title_height = measure_text("TSK Manager: ", Theme.title_font_size).y * 1.5
key_status_height = Theme.key_status_font_size * 1.5
return title_height + key_status_height
def set_current_menu(self, menu_id: int):
"""Update the current menu for display."""
self._current_menu = menu_id
def _render(self, rect: rl.Rectangle) -> Optional[int]:
"""
Render the header.
Returns:
New menu ID if navigation button was clicked, None otherwise
"""
title_height = measure_text("TSK Manager: ", Theme.title_font_size).y * 1.5
key_status_height = Theme.key_status_font_size * 1.5
# Draw title strip
title_rect = rl.Rectangle(rect.x, rect.y, rect.width, title_height)
rl.draw_rectangle_rec(title_rect, Theme.title_bg_color)
self._draw_title(title_rect)
# Draw key status strip
key_status_y = rect.y + title_height
key_status_rect = rl.Rectangle(
rect.x,
key_status_y,
rect.width,
key_status_height
)
rl.draw_rectangle_rec(key_status_rect, Theme.key_bg_color)
self._draw_key_status(key_status_rect)
# Draw navigation buttons
self._nav_result = None
self._draw_navigation_buttons(rect, title_height, key_status_height)
return self._nav_result
def _draw_title(self, rect: rl.Rectangle):
"""Draw the title text."""
# Use white text for header title
title_text_color = Theme.brighten_color(Theme.title_bg_color, Theme.brighten_amount)
menu_name = Theme.menu_names.get(self._current_menu, "Unknown Menu")
prefix = "TSK Manager: "
# Measure and position text
prefix_size = measure_text(prefix, Theme.title_font_size)
prefix_x = rect.width * Theme.title_prefix_x_offset_percent + Theme.title_x_offset
menu_name_x = prefix_x + prefix_size.x
full_text = f"{prefix}{menu_name}"
full_size = measure_text(full_text, Theme.title_font_size)
text_y = rect.y + (rect.height - full_size.y) / 2
# Draw prefix
rl.draw_text_ex(
gui_app.font(),
prefix,
rl.Vector2(prefix_x, text_y),
Theme.title_font_size,
0,
title_text_color
)
# Draw menu name
rl.draw_text_ex(
gui_app.font(),
menu_name,
rl.Vector2(menu_name_x, text_y),
Theme.title_font_size,
0,
title_text_color
)
def _draw_key_status(self, rect: rl.Rectangle):
"""Draw the key installation status."""
if self.key_manager.installed_key:
status_text = f"Key installed: {self.key_manager.installed_key}"
else:
status_text = "Key not installed"
text_size = measure_text(status_text, Theme.key_status_font_size)
# Center text
text_x = rect.x + (rect.width - text_size.x) / 2
text_y = rect.y + (rect.height - text_size.y) / 2
rl.draw_text_ex(
gui_app.font(),
status_text,
rl.Vector2(text_x, text_y),
Theme.key_status_font_size,
0,
Theme.key_text_color
)
def _draw_navigation_buttons(
self,
rect: rl.Rectangle,
title_height: float,
key_status_height: float
):
"""Draw navigation buttons overlapping the header."""
# Calculate button dimensions
button_height = title_height + key_status_height
# Calculate button width based on text
left_text_size = measure_text(Theme.nav_button_text_left.replace('\\n', ' '), Theme.nav_button_font_size)
right_text_size = measure_text(Theme.nav_button_text_right.replace('\\n', ' '), Theme.nav_button_font_size)
button_width = max(left_text_size.x, right_text_size.x) + 70
# Show appropriate button based on current menu
if self._current_menu == Theme.menu_reboot:
# Show left button (back to tools)
left_rect = rl.Rectangle(rect.x, rect.y, button_width, button_height)
self._nav_left.render(left_rect)
elif self._current_menu == Theme.menu_tools:
# Show right button (go to reboot)
right_rect = rl.Rectangle(
rect.x + rect.width - button_width,
rect.y,
button_width,
button_height
)
self._nav_right.render(right_rect)