mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-08-24 01:33:46 +08:00
test nav styling
This commit is contained in:
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import math
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import pyray as rl
|
||||
@@ -55,6 +56,59 @@ def _normalize_maneuver_type(maneuver_type: str) -> str:
|
||||
return normalized.replace(" ", "_")
|
||||
|
||||
|
||||
_STREET_SUFFIXES = {
|
||||
"street": "St", "avenue": "Ave", "boulevard": "Blvd", "road": "Rd",
|
||||
"drive": "Dr", "lane": "Ln", "court": "Ct", "place": "Pl",
|
||||
"highway": "Hwy", "parkway": "Pkwy", "square": "Sq", "terrace": "Ter",
|
||||
"circle": "Cir", "trail": "Trl", "turnpike": "Tpke", "causeway": "Cswy",
|
||||
"crossing": "Xing", "loop": "Loop", "way": "Way", "alley": "Aly",
|
||||
"plaza": "Plz", "point": "Pt",
|
||||
}
|
||||
_STREET_PLURALS = {
|
||||
"streets": "Sts", "avenues": "Aves", "boulevards": "Blvds", "roads": "Rds",
|
||||
"drives": "Drs", "lanes": "Lns", "courts": "Cts", "places": "Pls",
|
||||
"highways": "Hwys", "parkways": "Pkwys", "squares": "Sqs", "terraces": "Ters",
|
||||
"circles": "Cirs", "trails": "Trls", "turnpikes": "Tpkes", "causeways": "Cswys",
|
||||
"crossings": "Xings", "loops": "Loops", "alleys": "Alys", "plazas": "Plzs",
|
||||
"points": "Pts",
|
||||
}
|
||||
_DIRECTIONS = {
|
||||
"north": "N", "south": "S", "east": "E", "west": "W",
|
||||
"northeast": "NE", "northwest": "NW", "southeast": "SE", "southwest": "SW",
|
||||
}
|
||||
_STREET_WORD_MAP = {**_STREET_SUFFIXES, **_STREET_PLURALS, **_DIRECTIONS}
|
||||
_STREET_WORD_RE = re.compile(
|
||||
r"\b(" + "|".join(sorted(_STREET_WORD_MAP, key=len, reverse=True)) + r")\b\.?",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
_ROUTE_PATTERNS = (
|
||||
(re.compile(r"\binterstate\s+(\d+)\b", re.IGNORECASE), r"I-\1"),
|
||||
(re.compile(r"\bstate\s+route\s+(\d+)\b", re.IGNORECASE), r"SR \1"),
|
||||
(re.compile(r"\bhighway\s+(\d+)\b", re.IGNORECASE), r"Hwy \1"),
|
||||
(re.compile(r"\broute\s+(\d+)\b", re.IGNORECASE), r"Rte \1"),
|
||||
)
|
||||
_LATIN_RE = re.compile(r"[A-Za-z]")
|
||||
|
||||
|
||||
def _abbreviate_street_suffixes(text: str) -> str:
|
||||
if not text or not _LATIN_RE.search(text):
|
||||
return text
|
||||
for pattern, repl in _ROUTE_PATTERNS:
|
||||
text = pattern.sub(repl, text)
|
||||
return _STREET_WORD_RE.sub(lambda m: _STREET_WORD_MAP[m.group(1).lower()], text)
|
||||
|
||||
|
||||
def _fit_3dots(text: str, max_width: float, font_size: int, font) -> str:
|
||||
if measure_text_cached(font, text, font_size).x <= max_width:
|
||||
return text
|
||||
shortened = text.rstrip()
|
||||
candidate = f"{shortened}..."
|
||||
while shortened and measure_text_cached(font, candidate, font_size).x > max_width:
|
||||
shortened = shortened[:-1].rstrip()
|
||||
candidate = f"{shortened}..." if shortened else "..."
|
||||
return candidate
|
||||
|
||||
|
||||
class NavigationCardRenderer(Widget):
|
||||
def __init__(self, layout_variant: str = "default"):
|
||||
"""layout_variant: "default" (tici/tizi) or "mici"."""
|
||||
@@ -204,7 +258,8 @@ class NavigationCardRenderer(Widget):
|
||||
|
||||
self._valid = True
|
||||
|
||||
def _wrap_text_lines(self, text: str, max_width: float, font_size: int, max_lines: int = 2) -> tuple[list[str], bool]:
|
||||
def _wrap_text_lines(self, text: str, max_width: float, font_size: int, max_lines: int = 2, font=None) -> tuple[list[str], bool]:
|
||||
font = font or self._font_bold
|
||||
words = text.split()
|
||||
if not words:
|
||||
return [], False
|
||||
@@ -216,7 +271,7 @@ class NavigationCardRenderer(Widget):
|
||||
while word_index < len(words):
|
||||
candidate_words = current_words + [words[word_index]]
|
||||
candidate_line = " ".join(candidate_words)
|
||||
text_width = measure_text_cached(self._font_bold, candidate_line, font_size).x
|
||||
text_width = measure_text_cached(font, candidate_line, font_size).x
|
||||
if text_width <= max_width or not current_words:
|
||||
current_words = candidate_words
|
||||
word_index += 1
|
||||
@@ -233,7 +288,7 @@ class NavigationCardRenderer(Widget):
|
||||
if truncated and lines:
|
||||
shortened = lines[-1]
|
||||
candidate = f"{shortened}…"
|
||||
while shortened and measure_text_cached(self._font_bold, candidate, font_size).x > max_width:
|
||||
while shortened and measure_text_cached(font, candidate, font_size).x > max_width:
|
||||
if " " in shortened:
|
||||
shortened = shortened.rsplit(" ", 1)[0]
|
||||
else:
|
||||
@@ -278,6 +333,32 @@ class NavigationCardRenderer(Widget):
|
||||
line_y = y + index * (font_size + line_gap)
|
||||
rl.draw_text_ex(self._font_bold, line, rl.Vector2(x, line_y), font_size, 0, rl.WHITE)
|
||||
|
||||
def _draw_lines(self, lines: list[str], x: float, y0: float, font_size: int, font, color) -> None:
|
||||
for index, line in enumerate(lines[:2]):
|
||||
rl.draw_text_ex(font, line, rl.Vector2(x, y0 + index * font_size), font_size, 0, color)
|
||||
|
||||
def _fit_intelligent(self, text, max_width, max_lines, preferred, minimum, font):
|
||||
text = _abbreviate_street_suffixes(text or "")
|
||||
if not text:
|
||||
return [], preferred
|
||||
|
||||
one_line_font = minimum
|
||||
for fs in range(preferred, minimum - 1, -2):
|
||||
if measure_text_cached(font, text, fs).x <= max_width:
|
||||
one_line_font = fs
|
||||
break
|
||||
|
||||
for fs in range(preferred, minimum - 1, -2):
|
||||
lines, truncated = self._wrap_text_lines(text, max_width, fs, max_lines, font)
|
||||
if not truncated and all(measure_text_cached(font, ln, fs).x <= max_width for ln in lines):
|
||||
if one_line_font > minimum and len(lines) == 2 and len(lines[1].split()) == 1:
|
||||
if fs - one_line_font <= 4 or measure_text_cached(font, lines[1], fs).x < 0.35 * max_width:
|
||||
return [text], one_line_font
|
||||
return lines, fs
|
||||
|
||||
lines, _ = self._wrap_text_lines(text, max_width, minimum, max_lines, font)
|
||||
return [self._fit_3dots(ln.replace("…", "..."), max_width, minimum, font) for ln in lines], minimum
|
||||
|
||||
def _render_default(self, rect: rl.Rectangle) -> None: # tici/tizi expanded card
|
||||
right_margin = 40
|
||||
card_width = 560
|
||||
@@ -291,6 +372,8 @@ class NavigationCardRenderer(Widget):
|
||||
then_icon_size = 72
|
||||
preferred_font_size = 48
|
||||
minimum_font_size = 38
|
||||
secondary_preferred = 26
|
||||
secondary_minimum = 22
|
||||
distance_font_size = 32
|
||||
|
||||
container = rl.Rectangle(container_x, container_y, card_width, card_height)
|
||||
@@ -315,28 +398,17 @@ class NavigationCardRenderer(Widget):
|
||||
text_x = icon_x + icon_size + gap_after_icon
|
||||
right_gutter = icon_padding + (then_section_width if show_next else 0)
|
||||
text_area_width = card_width - (text_x - container_x) - right_gutter
|
||||
title_lines, title_font_size = self._fit_title(self._primary_text, text_area_width, preferred_font_size, minimum_font_size)
|
||||
title_lines, title_font_size = self._fit_intelligent(self._primary_text, text_area_width, 2, preferred_font_size, minimum_font_size, self._font_bold)
|
||||
secondary_lines, secondary_font_size = self._fit_intelligent(self._secondary_text or "", text_area_width, 2, secondary_preferred, secondary_minimum, self._font_medium)
|
||||
|
||||
secondary_font_size = 26
|
||||
text_block_h = len(title_lines) * title_font_size
|
||||
if self._secondary_text:
|
||||
text_block_h += 8 + secondary_font_size
|
||||
n_title = len(title_lines)
|
||||
n_sec = len(secondary_lines)
|
||||
gap = 8 if n_sec else 0
|
||||
text_block_h = n_title * title_font_size + gap + n_sec * secondary_font_size
|
||||
text_block_y = container_y + (card_height - text_block_h) // 2
|
||||
|
||||
for index, line in enumerate(title_lines[:2]):
|
||||
rl.draw_text_ex(self._font_bold, line, rl.Vector2(text_x, text_block_y + index * title_font_size), title_font_size, 0, rl.WHITE)
|
||||
|
||||
if self._secondary_text:
|
||||
secondary_text = self._truncate_text(self._secondary_text, text_area_width, secondary_font_size)
|
||||
secondary_y = text_block_y + len(title_lines) * title_font_size + 8
|
||||
rl.draw_text_ex(
|
||||
self._font_medium,
|
||||
secondary_text,
|
||||
rl.Vector2(text_x, secondary_y),
|
||||
secondary_font_size,
|
||||
0,
|
||||
rl.Color(255, 255, 255, 180),
|
||||
)
|
||||
self._draw_lines(title_lines, text_x, text_block_y, title_font_size, self._font_bold, rl.WHITE)
|
||||
self._draw_lines(secondary_lines, text_x, text_block_y + n_title * title_font_size + gap, secondary_font_size, self._font_medium, rl.Color(255, 255, 255, 180))
|
||||
|
||||
if not show_next:
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user