mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-10-01 03:43:46 +08:00
push it
This commit is contained in:
@@ -31,6 +31,9 @@ HEADER_ICON_SIZE = 34
|
||||
HEADER_FONT_SIZE = 28
|
||||
VALUE_FONT_SIZE = 96
|
||||
UNIT_FONT_SIZE = 28
|
||||
PAUSE_ICON_WIDTH = 12
|
||||
PAUSE_ICON_HEIGHT = 14
|
||||
PAUSE_ICON_GAP = 8
|
||||
OFFSET_FONT_SIZE = 22
|
||||
OFFSET_PILL_HEIGHT = 30
|
||||
CONFIRMATION_COLOR = rl.Color(188, 132, 255, 255)
|
||||
@@ -81,6 +84,7 @@ class UnifiedSpeedWidget(LayoutWidget):
|
||||
self._slc_enabled = False
|
||||
self._presentation: UnifiedSpeedPresentation | None = None
|
||||
self._show_max = False
|
||||
self._pedal_override = False
|
||||
self._snapshot_frame: int | None = None
|
||||
|
||||
def _refresh_snapshot(self) -> None:
|
||||
@@ -94,6 +98,11 @@ class UnifiedSpeedWidget(LayoutWidget):
|
||||
self.hud_renderer.is_cruise_available and
|
||||
not ui_state.starpilot_toggles.get("hide_max_speed", False)
|
||||
)
|
||||
self._pedal_override = (
|
||||
self.hud_renderer.is_cruise_set and ui_state.engaged and
|
||||
ui_state.sm.valid.get("carState", False) and ui_state.sm.alive.get("carState", False) and
|
||||
ui_state.sm.recv_frame["carState"] >= ui_state.started_frame and ui_state.sm["carState"].gasPressed
|
||||
)
|
||||
self._presentation = resolve_unified_speed(
|
||||
self._show_max, self.hud_renderer.is_cruise_set, self.hud_renderer.set_speed,
|
||||
self._slc_state, self._slc_enabled, ui_state.is_metric,
|
||||
@@ -158,17 +167,32 @@ class UnifiedSpeedWidget(LayoutWidget):
|
||||
rl.draw_rectangle_rounded_lines_ex(pill, roundness_for(pill, 17), 8, 2, OFFSET_COLOR)
|
||||
self._draw_centered_text(text, pill, y + (pill.height - text_size.y) / 2, OFFSET_FONT_SIZE, OFFSET_COLOR)
|
||||
|
||||
@staticmethod
|
||||
def _max_header_color(active_side: str, cruise_set: bool) -> rl.Color:
|
||||
def _draw_unit(self, bounds: rl.Rectangle, y: float) -> None:
|
||||
text = tr(self._presentation.unit_text)
|
||||
color = COLORS.WHITE_TRANSLUCENT
|
||||
if self._pedal_override:
|
||||
text_size = measure_text_cached(self._font_semi_bold, text, UNIT_FONT_SIZE)
|
||||
text_shift = (PAUSE_ICON_WIDTH + PAUSE_ICON_GAP) / 2
|
||||
icon_x = bounds.x + (bounds.width - text_size.x) / 2 - text_shift
|
||||
icon_y = y + (text_size.y - PAUSE_ICON_HEIGHT) / 2
|
||||
bar_width = PAUSE_ICON_WIDTH / 3
|
||||
for x in (icon_x, icon_x + 2 * bar_width):
|
||||
rl.draw_rectangle_rec(rl.Rectangle(x, icon_y, bar_width, PAUSE_ICON_HEIGHT), OFFSET_COLOR)
|
||||
bounds = rl.Rectangle(bounds.x + text_shift, bounds.y, bounds.width, bounds.height)
|
||||
color = COLORS.DISENGAGED
|
||||
self._draw_centered_text(text, bounds, y, UNIT_FONT_SIZE, color)
|
||||
|
||||
def _max_header_color(self, active_side: str, cruise_set: bool) -> rl.Color:
|
||||
if self._pedal_override:
|
||||
return COLORS.DISENGAGED
|
||||
if cruise_set and ui_state.status == UIStatus.ENGAGED and active_side in ("max", "shared"):
|
||||
return COLORS.ENGAGED
|
||||
if cruise_set and ui_state.status in (UIStatus.DISENGAGED, UIStatus.OVERRIDE):
|
||||
return COLORS.DISENGAGED
|
||||
return COLORS.GREY
|
||||
|
||||
@staticmethod
|
||||
def _limit_header_color(active_side: str, overridden: bool) -> rl.Color:
|
||||
if overridden or ui_state.status in (UIStatus.DISENGAGED, UIStatus.OVERRIDE):
|
||||
def _limit_header_color(self, active_side: str, overridden: bool) -> rl.Color:
|
||||
if self._pedal_override or overridden or ui_state.status in (UIStatus.DISENGAGED, UIStatus.OVERRIDE):
|
||||
return COLORS.DISENGAGED
|
||||
if ui_state.status == UIStatus.ENGAGED and active_side in ("slc", "shared"):
|
||||
return COLORS.ENGAGED
|
||||
@@ -176,7 +200,7 @@ class UnifiedSpeedWidget(LayoutWidget):
|
||||
|
||||
def _draw_active_emphasis(self, rect: rl.Rectangle) -> None:
|
||||
presentation = self._presentation
|
||||
if presentation.mode == "merged" or ui_state.status != UIStatus.ENGAGED or presentation.active_side == "none":
|
||||
if self._pedal_override or presentation.mode == "merged" or ui_state.status != UIStatus.ENGAGED or presentation.active_side == "none":
|
||||
return
|
||||
if presentation.mode in ("max_only", "limit_only"):
|
||||
bounds = rect
|
||||
@@ -222,6 +246,7 @@ class UnifiedSpeedWidget(LayoutWidget):
|
||||
def _render(self, rect: rl.Rectangle) -> None:
|
||||
presentation = self._presentation
|
||||
state = self._slc_state
|
||||
speed_color = COLORS.DISENGAGED if self._pedal_override else COLORS.WHITE
|
||||
rl.draw_rectangle_rounded_lines_ex(
|
||||
rect, CONTROL_ROUNDNESS, CONTROL_SEGMENTS, 7,
|
||||
rl.Color(UNIFIED_ACCENT.r, UNIFIED_ACCENT.g, UNIFIED_ACCENT.b, 55),
|
||||
@@ -240,12 +265,12 @@ class UnifiedSpeedWidget(LayoutWidget):
|
||||
max_bounds = rl.Rectangle(rect.x, rect.y, rect.width / 2, rect.height) if presentation.mode in ("split", "merged") else rect
|
||||
limit_bounds = self._speed_limit_bounds(rect)
|
||||
if self._show_max or presentation.confirmation_pending:
|
||||
max_color = COLORS.DARK_GREY if not self.hud_renderer.is_cruise_set else COLORS.WHITE
|
||||
max_color = COLORS.DARK_GREY if not self.hud_renderer.is_cruise_set else speed_color
|
||||
max_label_color = self._max_header_color(presentation.active_side, self.hud_renderer.is_cruise_set)
|
||||
self._draw_header(max_bounds, "MAX SET", "speedometer", max_label_color)
|
||||
if presentation.mode != "merged":
|
||||
self._draw_centered_text(presentation.max_speed_text, max_bounds, rect.y + 75, VALUE_FONT_SIZE, max_color, bold=True)
|
||||
self._draw_centered_text(tr(presentation.unit_text), max_bounds, rect.y + 204, UNIT_FONT_SIZE, COLORS.WHITE_TRANSLUCENT)
|
||||
self._draw_unit(max_bounds, rect.y + 204)
|
||||
|
||||
if limit_bounds is not None:
|
||||
icon_key = source_icon_key(presentation.source)
|
||||
@@ -253,16 +278,16 @@ class UnifiedSpeedWidget(LayoutWidget):
|
||||
label_color = self._limit_header_color(presentation.active_side, overridden)
|
||||
self._draw_header(limit_bounds, "SPEED LIMIT", icon_key, label_color)
|
||||
if presentation.mode != "merged":
|
||||
self._draw_centered_text(presentation.posted_speed_text, limit_bounds, rect.y + 75, VALUE_FONT_SIZE, COLORS.WHITE, bold=True)
|
||||
self._draw_centered_text(presentation.posted_speed_text, limit_bounds, rect.y + 75, VALUE_FONT_SIZE, speed_color, bold=True)
|
||||
if presentation.confirmation_pending:
|
||||
self._draw_centered_text(tr("PENDING"), limit_bounds, rect.y + 175, 25, CONFIRMATION_COLOR)
|
||||
elif presentation.offset_text is not None:
|
||||
self._draw_offset_pill(limit_bounds, presentation.offset_text, rect.y + 175)
|
||||
self._draw_centered_text(tr(presentation.unit_text), limit_bounds, rect.y + 204, UNIT_FONT_SIZE, COLORS.WHITE_TRANSLUCENT)
|
||||
self._draw_unit(limit_bounds, rect.y + 204)
|
||||
|
||||
if presentation.mode == "merged":
|
||||
self._draw_centered_text(presentation.effective_speed_text, rect, rect.y + 98, VALUE_FONT_SIZE, COLORS.WHITE, bold=True)
|
||||
self._draw_centered_text(tr(presentation.unit_text), rect, rect.y + 204, UNIT_FONT_SIZE, COLORS.WHITE_TRANSLUCENT)
|
||||
self._draw_centered_text(presentation.effective_speed_text, rect, rect.y + 98, VALUE_FONT_SIZE, speed_color, bold=True)
|
||||
self._draw_unit(rect, rect.y + 204)
|
||||
if presentation.offset_text is not None:
|
||||
self._draw_offset_pill(
|
||||
limit_bounds, presentation.offset_text, rect.y + MERGED_SEPARATOR_Y - OFFSET_PILL_HEIGHT / 2,
|
||||
|
||||
@@ -17,6 +17,7 @@ def make_widget(mode="split", pending=False):
|
||||
widget._presentation = UnifiedSpeedPresentation(mode, "70", "65", "70", "+5", "mph", "Map Data", pending, "slc")
|
||||
widget._show_max = True
|
||||
widget._slc_state = None
|
||||
widget._pedal_override = False
|
||||
widget.hud_renderer = SimpleNamespace(is_cruise_set=True)
|
||||
return widget
|
||||
|
||||
@@ -208,7 +209,7 @@ def test_enabled_slc_stays_full_width_when_plan_is_stale(monkeypatch):
|
||||
widget._snapshot_frame = None
|
||||
widget.hud_renderer = SimpleNamespace(is_cruise_available=True, is_cruise_set=True, set_speed=70)
|
||||
monkeypatch.setattr(unified_speed, "ui_state", SimpleNamespace(
|
||||
sm=SimpleNamespace(frame=1), starpilot_toggles={}, is_metric=False,
|
||||
sm=SimpleNamespace(frame=1), starpilot_toggles={}, is_metric=False, engaged=False,
|
||||
))
|
||||
monkeypatch.setattr(unified_speed, "_is_slc_enabled", lambda: True)
|
||||
monkeypatch.setattr(unified_speed, "_get_slc_state", lambda: None)
|
||||
@@ -217,6 +218,119 @@ def test_enabled_slc_stays_full_width_when_plan_is_stale(monkeypatch):
|
||||
assert widget._presentation.posted_speed_text == "–"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pedal_snapshot(monkeypatch):
|
||||
class SubMaster(dict):
|
||||
pass
|
||||
|
||||
sm = SubMaster(carState=SimpleNamespace(gasPressed=True))
|
||||
sm.frame = 20
|
||||
sm.valid = {"carState": True}
|
||||
sm.alive = {"carState": True}
|
||||
sm.recv_frame = {"carState": 20}
|
||||
ui = SimpleNamespace(sm=sm, started_frame=10, engaged=True, starpilot_toggles={}, is_metric=False)
|
||||
widget = make_widget()
|
||||
widget._snapshot_frame = None
|
||||
widget.hud_renderer = SimpleNamespace(is_cruise_available=True, is_cruise_set=True, set_speed=70)
|
||||
monkeypatch.setattr(unified_speed, "ui_state", ui)
|
||||
monkeypatch.setattr(unified_speed, "_is_slc_enabled", lambda: True)
|
||||
monkeypatch.setattr(unified_speed, "_get_slc_state", lambda: None)
|
||||
return widget, ui
|
||||
|
||||
|
||||
@pytest.mark.parametrize("gas,engaged,cruise_set,valid,alive,received,expected", [
|
||||
(True, True, True, True, True, 20, True),
|
||||
(False, True, True, True, True, 20, False),
|
||||
(True, False, True, True, True, 20, False),
|
||||
(True, True, False, True, True, 20, False),
|
||||
(True, True, True, False, True, 20, False),
|
||||
(True, True, True, True, False, 20, False),
|
||||
(True, True, True, True, True, 9, False),
|
||||
])
|
||||
def test_pedal_override_requires_fresh_gas_and_engaged_cruise(pedal_snapshot, gas, engaged, cruise_set, valid, alive, received, expected):
|
||||
widget, ui = pedal_snapshot
|
||||
ui.sm["carState"].gasPressed = gas
|
||||
ui.engaged = engaged
|
||||
widget.hud_renderer.is_cruise_set = cruise_set
|
||||
ui.sm.valid["carState"] = valid
|
||||
ui.sm.alive["carState"] = alive
|
||||
ui.sm.recv_frame["carState"] = received
|
||||
widget._refresh_snapshot()
|
||||
assert widget._pedal_override == expected
|
||||
|
||||
|
||||
def test_pedal_cue_clears_on_release_with_a_persistent_slc_override(pedal_snapshot, monkeypatch):
|
||||
widget, ui = pedal_snapshot
|
||||
sm = ui.sm
|
||||
state = {
|
||||
"speed_conversion": CV.MS_TO_MPH, "accepted_speed_limit_ms": 65 * CV.MPH_TO_MS,
|
||||
"effective_target_ms": 70 * CV.MPH_TO_MS, "offset_ms": 5 * CV.MPH_TO_MS,
|
||||
"speed_limit_changed": False, "unconfirmed_valid": False, "presented_source": "Map Data",
|
||||
"slc_is_limiting_max_set": False, "slc_overridden_speed": 80 * CV.MPH_TO_MS,
|
||||
}
|
||||
monkeypatch.setattr(unified_speed, "_get_slc_state", lambda: state)
|
||||
widget._refresh_snapshot()
|
||||
assert widget._pedal_override
|
||||
presentation = widget._presentation
|
||||
|
||||
sm["carState"].gasPressed = False
|
||||
widget._refresh_snapshot()
|
||||
assert widget._pedal_override
|
||||
sm.frame += 1
|
||||
sm.recv_frame["carState"] = sm.frame
|
||||
widget._refresh_snapshot()
|
||||
assert not widget._pedal_override
|
||||
assert widget._presentation == presentation
|
||||
assert widget._slc_state["slc_overridden_speed"] > 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mode", ["split", "merged", "max_only", "limit_only"])
|
||||
@pytest.mark.parametrize("unit", ["mph", "km/h"])
|
||||
def test_pedal_cue_mutes_targets_and_preserves_units_offsets_and_layout(monkeypatch, mode, unit):
|
||||
widget = make_widget(mode)
|
||||
widget._pedal_override = True
|
||||
widget._font_semi_bold = None
|
||||
widget._show_max = mode != "limit_only"
|
||||
widget._presentation = replace(widget._presentation, unit_text=unit)
|
||||
if mode in ("max_only", "limit_only"):
|
||||
widget._rect.width = 250
|
||||
values, headers, pauses, offsets, lines = [], [], [], [], []
|
||||
monkeypatch.setattr(unified_speed, "ui_state", SimpleNamespace(status=unified_speed.UIStatus.ENGAGED))
|
||||
monkeypatch.setattr(unified_speed, "draw_control_card", lambda *args, **kwargs: None)
|
||||
monkeypatch.setattr(unified_speed, "measure_text_cached", lambda *args: rl.Vector2(60, 28))
|
||||
monkeypatch.setattr(rl, "draw_rectangle_rounded_lines_ex", lambda *args: None)
|
||||
monkeypatch.setattr(rl, "draw_rectangle_rec", lambda *args: pauses.append(args))
|
||||
monkeypatch.setattr(rl, "draw_line_ex", lambda *args: lines.append(args))
|
||||
monkeypatch.setattr(widget, "_draw_merged_separator", lambda *args: None)
|
||||
monkeypatch.setattr(widget, "_draw_header", lambda bounds, text, icon, color: headers.append(color))
|
||||
monkeypatch.setattr(widget, "_draw_centered_text", lambda text, bounds, y, size, color, **kwargs: values.append((text, bounds, size, color)))
|
||||
monkeypatch.setattr(widget, "_draw_offset_pill", lambda bounds, text, y: offsets.append(text))
|
||||
|
||||
widget._render(widget.rect)
|
||||
speed_values = [value for value in values if value[2] == unified_speed.VALUE_FONT_SIZE]
|
||||
unit_values = [value for value in values if value[2] == unified_speed.UNIT_FONT_SIZE]
|
||||
expected_speeds = {"split": ["70", "65"], "merged": ["70"], "max_only": ["70"], "limit_only": ["65"]}
|
||||
assert [value[0] for value in speed_values] == expected_speeds[mode]
|
||||
assert all(value[3] == unified_speed.COLORS.DISENGAGED for value in speed_values + unit_values)
|
||||
assert all(color == unified_speed.COLORS.DISENGAGED for color in headers)
|
||||
assert [value[0] for value in unit_values] == [unit] * (2 if mode == "split" else 1)
|
||||
assert len(pauses) == 2 * len(unit_values)
|
||||
assert all(color == unified_speed.OFFSET_COLOR for _bounds, color in pauses)
|
||||
assert offsets == ([] if mode == "max_only" else ["+5"])
|
||||
assert not any(line[2] == 3 for line in lines)
|
||||
for index, value in enumerate(unit_values):
|
||||
pause = pauses[index * 2][0]
|
||||
assert pause.x == pytest.approx(value[1].x + (value[1].width - 60) / 2 - 20)
|
||||
|
||||
values.clear()
|
||||
pauses.clear()
|
||||
widget._pedal_override = False
|
||||
widget._render(widget.rect)
|
||||
assert not pauses
|
||||
assert all(value[3] == unified_speed.COLORS.WHITE for value in values if value[2] == unified_speed.VALUE_FONT_SIZE)
|
||||
assert all(value[3] == unified_speed.COLORS.WHITE_TRANSLUCENT for value in values if value[2] == unified_speed.UNIT_FONT_SIZE)
|
||||
|
||||
|
||||
def test_split_merged_transitions_keep_the_same_footprint(monkeypatch):
|
||||
widget = make_widget("split")
|
||||
monkeypatch.setattr(widget, "_refresh_snapshot", lambda: None)
|
||||
|
||||
Reference in New Issue
Block a user