Files
StarPilot/openpilot/starpilot/ui/onroad_corner.py
T
firestar5683 a82935f326 User interfaces and Android Auto
Integrate onroad views, configurable widgets, local settings, steering-profile contracts, Bluetooth and Android Auto with their device and runtime services.

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
Co-authored-by: Daniel Koepping <elkoled@gmail.com>
Co-authored-by: Danny <dannyirwin31@yahoo.com>
Co-authored-by: DimitriChrysafis <Dimitri.chrysafis@gmail.com>
Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com>
Co-authored-by: Harald Schäfer <harald.the.engineer@gmail.com>
Co-authored-by: Prabhaav Pillai <prabhaav@msu.edu>
Co-authored-by: Shane Smiskol <shane@smiskol.com>
Co-authored-by: dirwin31 <83434411+dirwin31@users.noreply.github.com>
Co-authored-by: firestarsdog <229254897+firestarsdog@users.noreply.github.com>
Co-authored-by: stef <19478336+stefpi@users.noreply.github.com>
Co-authored-by: whoisdomi <dcantu83@gmail.com>

Co-authored-by: Intelli <contact@intelli.software>
Co-authored-by: pharmacomaniac <blittle65@gmail.com>

Co-authored-by: prabhaavp <prabhaav@msu.edu>
2026-10-14 20:02:39 -05:00

140 lines
5.7 KiB
Python

"""Retained collapsed favorite-menu corner hint for the large onroad shell."""
from functools import lru_cache
import math
import pyray as rl
def _aether_color(t: float, alpha: int) -> rl.Color:
if t <= 0.5:
w = t * 2.0
return rl.Color(int(196 - 24 * w), int(158 - 33 * w), 255, alpha)
w = (t - 0.5) * 2.0
return rl.Color(int(172 - 84 * w), int(125 - 75 * w), int(255 - 97 * w), alpha)
@lru_cache(maxsize=4)
def _commands(x: float, y: float, width: float, height: float) -> tuple:
rect = rl.Rectangle(x, y, width, height)
commands = []
scale = max(0.35, min(rect.width / 2160.0, rect.height / 1080.0))
x0, y0 = rect.x, rect.y + rect.height
size, steps = 150.0 * scale, 48
origin = rl.Vector2(x0, y0)
inverse = 1.0 / steps
top = [rl.Vector2(x0, y0 - size * (k * inverse)) for k in range(steps + 1)]
right = [rl.Vector2(x0 + size * (k * inverse), y0) for k in range(steps + 1)]
for index in range(steps):
t = (index + 0.5) * inverse
base_alpha = int(160 * (1 - t) ** 1.40)
purple_alpha = int(105 * (1 - t) ** 1.75)
for color in (rl.Color(8, 6, 18, base_alpha) if base_alpha > 0 else None,
_aether_color(t, purple_alpha) if purple_alpha > 0 else None):
if color is None:
continue
if index == 0:
commands.append((rl.rl.DrawTriangle, (origin, right[index + 1], top[index + 1], color)))
else:
# A strip emits the same two triangles, in the same blending order,
# while crossing the Python/native boundary only once for this band.
points = rl.ffi.new("Vector2[]", [top[index], right[index], top[index + 1], right[index + 1]])
commands.append((rl.rl.DrawTriangleStrip, (points, 4, color)))
inset = 48.0 * scale
center_x, center_y = rect.x + inset, rect.y + rect.height - inset
tip = rl.Vector2(center_x + 15 * scale, center_y - 15 * scale)
tail = rl.Vector2(center_x - 15 * scale, center_y + 15 * scale)
wing1 = rl.Vector2(tip.x - 14 * scale, tip.y + 1.2 * scale)
wing2 = rl.Vector2(tip.x - 1.2 * scale, tip.y + 14 * scale)
line_width = 4.6 * scale
offset = 1.6 * scale
shifted = tuple(rl.Vector2(point.x + offset, point.y + offset) for point in (tail, tip, wing1, wing2))
layers = ((shifted[0], shifted[1], shifted[2], shifted[3], line_width + 2 * scale, rl.Color(8, 6, 16, 105)),
(tail, tip, wing1, wing2, line_width + 3.2 * scale, rl.Color(185, 145, 255, 55)),
(tail, tip, wing1, wing2, line_width, rl.Color(255, 255, 255, 225)),
(tail, tip, wing1, wing2, 2.0 * scale, rl.Color(255, 255, 255, 240)))
for start, end, first_wing, second_wing, stroke_width, color in layers:
commands.append((rl.rl.DrawLineEx, (start, end, stroke_width, color)))
commands.append((rl.rl.DrawLineEx, (end, first_wing, stroke_width, color)))
commands.append((rl.rl.DrawLineEx, (end, second_wing, stroke_width, color)))
for point in (start, end, first_wing, second_wing):
commands.append((rl.rl.DrawCircleV, (point, stroke_width / 2, color)))
return tuple(commands)
class CornerHintCache:
"""One view-owned texture; failed dimensions use the retained primitives."""
def __init__(self):
self.texture = None
self.key = None
self.failed_key = None
self.pending = None
def close(self) -> None:
texture, self.texture = self.texture, None
self.key = self.failed_key = self.pending = None
if texture is not None and rl.is_window_ready():
rl.unload_render_texture(texture)
def get(self, rect):
key = (rect.width, rect.height)
if self.texture is not None and self.key == key:
return self.texture
if self.failed_key != key:
self.pending = key
return None
def prepare(self):
"""Called before drawing begins, never inside a parent render target."""
key = self.pending
if key is None or not rl.is_window_ready():
return
self.close()
size = max(1, math.ceil(150.0 * max(0.35, min(key[0] / 2160.0, key[1] / 1080.0))))
texture = None
try:
texture = rl.load_render_texture(size, size)
if not texture.id or not texture.texture.id:
raise RuntimeError("corner texture allocation failed")
rl.begin_texture_mode(texture)
try:
rl.clear_background(rl.BLANK)
# Scratch RGB is premultiplied, but alpha must accumulate coverage.
# Raylib BLEND_ALPHA otherwise accumulates alpha squared (rlgl.h).
rl.rl.rlSetBlendFactorsSeparate(0x0302, 0x0303, 1, 0x0303, 0x8006, 0x8006)
rl.begin_blend_mode(rl.BlendMode.BLEND_CUSTOM_SEPARATE)
try:
# Keep original scale and geometry, translating only the local origin.
for draw, arguments in _commands(0.0, size - key[1], *key):
draw(*arguments)
finally:
rl.end_blend_mode()
finally:
rl.end_texture_mode()
except Exception:
if texture is not None and texture.id:
rl.unload_render_texture(texture)
self.failed_key = key
return None
self.texture, self.key = texture, key
return texture
def render_corner_hint(rect: rl.Rectangle, *, cache: CornerHintCache | None = None) -> None:
"""Compose retained primitives once when a view-owned texture is available."""
texture = cache.get(rect) if cache is not None else None
if texture is None:
for draw, arguments in _commands(rect.x, rect.y, rect.width, rect.height):
draw(*arguments)
return
size = texture.texture.width
rl.begin_blend_mode(rl.BlendMode.BLEND_ALPHA_PREMULTIPLY)
try:
rl.draw_texture_pro(texture.texture, rl.Rectangle(0, 0, size, -size),
rl.Rectangle(rect.x, rect.y + rect.height - size, size, size),
rl.Vector2(0, 0), 0.0, rl.WHITE)
finally:
rl.end_blend_mode()