diff --git a/openpilot/starpilot/ui/onroad_alerts.py b/openpilot/starpilot/ui/onroad_alerts.py index 9581834b14..29f624d4a9 100644 --- a/openpilot/starpilot/ui/onroad_alerts.py +++ b/openpilot/starpilot/ui/onroad_alerts.py @@ -21,8 +21,14 @@ class AlertRenderer: def _center(self, text: str, role: FontRole, size: int, rect: rl.Rectangle, y: float, color: rl.Color = rl.WHITE, *, letter_spacing: float = 0) -> None: spacing = size * letter_spacing - measured = self.fonts.measure(text, role, size, spacing=spacing) - self.fonts.draw(text, role, size, rect.x + (rect.width - measured.width) / 2, y, color, spacing=spacing) + lines = text.split('\n') + height = self.fonts.measure(text, role, size, spacing=spacing).height + # Preserve Raylib's measured newline spacing when drawing lines separately. + line_step = (height - self.fonts.measure(lines[0], role, size, spacing=spacing).height) / max(1, len(lines) - 1) + for line in lines: + measured = self.fonts.measure(line, role, size, spacing=spacing) + self.fonts.draw(line, role, size, rect.x + (rect.width - measured.width) / 2, y, color, spacing=spacing) + y += line_step def render(self, rect: rl.Rectangle, alert: OnroadAlert, *, signal_direction: int = 0) -> None: event_name = alert.alert_type.split('/', 1)[0] diff --git a/openpilot/starpilot/ui/tests/test_onroad_alerts.py b/openpilot/starpilot/ui/tests/test_onroad_alerts.py index c4ddf3daf5..252af05102 100644 --- a/openpilot/starpilot/ui/tests/test_onroad_alerts.py +++ b/openpilot/starpilot/ui/tests/test_onroad_alerts.py @@ -9,6 +9,30 @@ from openpilot.starpilot.ui.onroad_state import AlertSize, OnroadAlert from openpilot.starpilot.ui.presentation import FontRole, Profile +@pytest.mark.parametrize('width', [1860, 2160]) +@pytest.mark.parametrize('text', ['Reverse\nGear', 'Reverse Gear']) +def test_large_reverse_alert_centers_each_line_in_camera_view(width, text): + fonts = Mock(profile=Profile.LARGE) + def measure(text, role, size, **kwargs): + lines = text.split('\n') + return NS(width=max(len(line) for line in lines) * size * .5, + height=len(lines) * size + (len(lines) - 1) * 2) + fonts.measure.side_effect = measure + renderer = onroad_alerts.AlertRenderer(fonts) + bounds = rl.Rectangle(30, 30, width - 60, 1020) + with patch.object(rl, 'draw_rectangle'), patch.object(rl, 'draw_rectangle_gradient_v'): + renderer.render(bounds, OnroadAlert(AlertSize.FULL, text, alert_type='reverseGear/permanent')) + title = [call.args for call in fonts.draw.call_args_list if call.args[0]] + assert [args[0] for args in title] == text.split('\n') + for line, role, size, x, _y, _color in title: + assert x + measure(line, role, size).width / 2 == width / 2 + top = title[0][4] + bottom = title[-1][4] + measure(title[-1][0], title[-1][1], title[-1][2]).height + assert (top + bottom) / 2 == bounds.y + bounds.height / 2 + if len(title) > 1: + assert title[1][4] - top == title[0][2] + 2 + + @pytest.mark.parametrize("personality", ["Relaxed", "Standard", "Aggressive"]) def test_personality_banner_uses_name_and_consistent_large_type(personality): fonts = Mock(profile=Profile.COMPACT)