From 85341237ae3659decb8fca7387f2302c12a2b5aa Mon Sep 17 00:00:00 2001 From: firestarsdog <229254897+firestarsdog@users.noreply.github.com> Date: Sun, 11 Oct 2026 02:00:38 -0400 Subject: [PATCH] Pagination Edge Gradient Cleanup --- openpilot/starpilot/ui/feature_settings.py | 27 ++++++------ openpilot/starpilot/ui/runtime_app.py | 23 ++++++----- .../ui/tests/test_feature_settings_visuals.py | 13 +++++- .../starpilot/ui/tests/test_shell_actions.py | 41 +++++++++++++++++++ 4 files changed, 80 insertions(+), 24 deletions(-) diff --git a/openpilot/starpilot/ui/feature_settings.py b/openpilot/starpilot/ui/feature_settings.py index efed1d2dc4..661df9eb90 100644 --- a/openpilot/starpilot/ui/feature_settings.py +++ b/openpilot/starpilot/ui/feature_settings.py @@ -114,21 +114,22 @@ class FeatureSettingsView: self._value_row(state, state.rows[0], rl.Rectangle(*value_editor_rect(state)), buttons) self._value_done(state) return + can_page = drag_x != 0 and feature_scroll(state.scroll, 1 if drag_x < 0 else -1, len(state.rows)) != state.scroll + rows_state, pull_x, cover = self._page_frame(state, feature_pull(drag_x, can_page), drag_x != 0) + if pull_x and can_page: + edge_x = 2125 - FEATURE_FLICK_DISTANCE if pull_x < 0 else left + 25 + gap_x = 2125 + pull_x if pull_x < 0 else left - 20 + gap_right = 2160 if pull_x < 0 else left + 25 + pull_x + color_left, color_right = (PANEL_BG, EDGE_ACCENT) if pull_x < 0 else (EDGE_ACCENT, PANEL_BG) + rl.draw_rectangle_gradient_ex(rl.Rectangle(gap_x, row_top, gap_right - gap_x, FEATURE_VISIBLE_ROWS * FEATURE_ROW_HEIGHT), + color_left, color_left, color_right, color_right) + cx, cy = edge_x + FEATURE_FLICK_DISTANCE / 2, row_top + FEATURE_VISIBLE_ROWS * FEATURE_ROW_HEIGHT / 2 + direction = 1 if pull_x < 0 else -1 + tip = rl.Vector2(cx + direction * 8, cy) + rl.draw_line_ex(rl.Vector2(cx - direction * 8, cy - 16), tip, 4, TEXT_PRIMARY) + rl.draw_line_ex(tip, rl.Vector2(cx - direction * 8, cy + 16), 4, TEXT_PRIMARY) clip.begin_scissor_mode(left + 25, row_top, 2100 - left, FEATURE_VISIBLE_ROWS * FEATURE_ROW_HEIGHT) try: - can_page = drag_x != 0 and feature_scroll(state.scroll, 1 if drag_x < 0 else -1, len(state.rows)) != state.scroll - rows_state, pull_x, cover = self._page_frame(state, feature_pull(drag_x, can_page), drag_x != 0) - if pull_x and can_page: - edge_x = 2125 - FEATURE_FLICK_DISTANCE if pull_x < 0 else left + 25 - gap_x = 2125 + pull_x if pull_x < 0 else left + 25 - color_left, color_right = (PANEL_BG, EDGE_ACCENT) if pull_x < 0 else (EDGE_ACCENT, PANEL_BG) - rl.draw_rectangle_gradient_ex(rl.Rectangle(gap_x, row_top, abs(pull_x), FEATURE_VISIBLE_ROWS * FEATURE_ROW_HEIGHT), - color_left, color_left, color_right, color_right) - cx, cy = edge_x + FEATURE_FLICK_DISTANCE / 2, row_top + FEATURE_VISIBLE_ROWS * FEATURE_ROW_HEIGHT / 2 - direction = 1 if pull_x < 0 else -1 - tip = rl.Vector2(cx + direction * 8, cy) - rl.draw_line_ex(rl.Vector2(cx - direction * 8, cy - 16), tip, 4, TEXT_PRIMARY) - rl.draw_line_ex(tip, rl.Vector2(cx - direction * 8, cy + 16), 4, TEXT_PRIMARY) if pull_x: rl.rl_push_matrix() try: diff --git a/openpilot/starpilot/ui/runtime_app.py b/openpilot/starpilot/ui/runtime_app.py index 9e851d2e10..af941dfe92 100644 --- a/openpilot/starpilot/ui/runtime_app.py +++ b/openpilot/starpilot/ui/runtime_app.py @@ -1270,16 +1270,11 @@ class StarShellSession: pane = _FEATURE_SETTINGS_PANES.get(snapshot.selected) drag_x = getattr(self.input, pane[2]).drag_x if pane else 0.0 self.view.render(replace(snapshot, selected=Destination.NETWORK) if external else snapshot, drag_x=drag_x) - if external: - self.view.settings.render_rail(snapshot.settings, selected=snapshot.selected) if mode == ShellMode.ONROAD: self.favorites.render(time.monotonic()) - if self.notice and time.monotonic() < self.notice_until: - rl.draw_rectangle(0, self.profile.size[1] - 42, self.profile.size[0], 42, rl.Color(0, 0, 0, 220)) - self.fonts.draw(self.notice[:65], FontRole.MEDIUM, 23 if self.profile == Profile.COMPACT else 27, - 12, self.profile.size[1] - 37) - if (mode == ShellMode.SETTINGS and self.profile == Profile.LARGE and snapshot.selected == Destination.NETWORK and - snapshot.settings.destination(Destination.NETWORK).available and self.network_layer is not None): + network = (mode == ShellMode.SETTINGS and self.profile == Profile.LARGE and snapshot.selected == Destination.NETWORK and + snapshot.settings.destination(Destination.NETWORK).available and self.network_layer is not None) + if network: rail_width = 500 if snapshot.settings.sidebar_expanded else 0 content = rl.Rectangle(rect.x + rail_width + 50, rect.y + 25, rect.width - rail_width - 100, rect.height - 50) @@ -1291,12 +1286,20 @@ class StarShellSession: self._on_destination_change(Destination.STAR) self._unavailable("network panel is unavailable") - if (mode == ShellMode.SETTINGS and self.profile == Profile.LARGE and - snapshot.selected in NATIVE_SETTINGS_PANELS and self.settings_layer is not None): + if external: rail_width = 500 if snapshot.settings.sidebar_expanded else 0 content = rl.Rectangle(rect.x + rail_width + 50, rect.y + 25, rect.width - rail_width - 100, rect.height - 50) self.settings_layer(snapshot.selected, content) + if external or network or self.notice: + with placed_at(rect, parent_clip): + if external or network: + self.view.settings.render_rail(snapshot.settings, selected=snapshot.selected) + if self.notice and time.monotonic() < self.notice_until: + rl.draw_rectangle(0, self.profile.size[1] - 42, self.profile.size[0], 42, rl.Color(0, 0, 0, 220)) + self.fonts.draw(self.notice[:65], FontRole.MEDIUM, 23 if self.profile == Profile.COMPACT else 27, + 12, self.profile.size[1] - 37) + def _unavailable(self, label: str) -> None: self.notice = f"Unavailable: {label}" self.notice_until = time.monotonic() + 3.0 diff --git a/openpilot/starpilot/ui/tests/test_feature_settings_visuals.py b/openpilot/starpilot/ui/tests/test_feature_settings_visuals.py index 0c3b023a4a..2af1ec7acd 100644 --- a/openpilot/starpilot/ui/tests/test_feature_settings_visuals.py +++ b/openpilot/starpilot/ui/tests/test_feature_settings_visuals.py @@ -272,6 +272,9 @@ class FeatureVisualTests(unittest.TestCase): rl.draw_line_ex.reset_mock() for transform in (rl.rl_push_matrix, rl.rl_translatef, rl.rl_pop_matrix): transform.reset_mock() + draws = Mock() + for name in ("draw_rectangle_gradient_ex", "draw_line_ex", "begin_scissor_mode", "draw_rectangle_rec"): + draws.attach_mock(getattr(rl, name), name) fonts = fake_fonts() with patch.object(view, "feature_scroll", wraps=feature_scroll) as paging, patch.object(view, "draw_settings_header"): view.FeatureSettingsView(fonts).render(FeatureSettingsState(rows=rows, scroll=scroll, sidebar_expanded=expanded), drag) @@ -300,11 +303,19 @@ class FeatureVisualTests(unittest.TestCase): gradient = rl.draw_rectangle_gradient_ex.call_args gap = abs(view.feature_pull(drag, can_page)) bounds = gradient.args[0] + gap_x = 2125 - gap if drag < 0 else left - 20 + gap_right = 2160 if drag < 0 else left + 25 + gap self.assertEqual((bounds.x, bounds.y, bounds.width, bounds.height), - (2125 - gap if drag < 0 else left + 25, FEATURE_ROW_TOP, gap, 5 * FEATURE_ROW_HEIGHT)) + (gap_x, FEATURE_ROW_TOP, gap_right - gap_x, 5 * FEATURE_ROW_HEIGHT)) color_left, color_right = (geometry.PANEL_BG, view.EDGE_ACCENT) if drag < 0 else (view.EDGE_ACCENT, geometry.PANEL_BG) self.assertEqual(gradient.args[1:], (color_left, color_left, color_right, color_right)) self.assertEqual(rl.draw_line_ex.call_args.args[0].x, (2095 if drag < 0 else left + 55) + (8 if drag < 0 else -8)) + order = [call[0] for call in draws.mock_calls] + self.assertLess(order.index("draw_rectangle_gradient_ex"), order.index("draw_line_ex")) + self.assertLess(max(index for index, name in enumerate(order) if name == "draw_line_ex"), + max(index for index, name in enumerate(order) if name == "begin_scissor_mode")) + self.assertLess(max(index for index, name in enumerate(order) if name == "begin_scissor_mode"), + order.index("draw_rectangle_rec")) footer = [call for call in fonts.draw.call_args_list if call.args[0] in ("Previous", "Next")] self.assertEqual(len(footer), 2) diff --git a/openpilot/starpilot/ui/tests/test_shell_actions.py b/openpilot/starpilot/ui/tests/test_shell_actions.py index bcd09aeb6f..1ba8c21e73 100644 --- a/openpilot/starpilot/ui/tests/test_shell_actions.py +++ b/openpilot/starpilot/ui/tests/test_shell_actions.py @@ -161,6 +161,47 @@ class ShellActionTests(unittest.TestCase): self.assertEqual(shell_render.call_args.kwargs["drag_x"], 0) feature_render.assert_not_called() + def test_adapted_panels_draw_before_placed_navigation_and_notices(self): + import pyray as rl + from openpilot.starpilot.ui import clip, runtime_app + from openpilot.starpilot.ui.settings_state import DestinationAvailability + + rect = rl.Rectangle(20, 30, 2160, 1080) + parent = rl.Rectangle(25, 35, 2140, 1060) + for destination in (*runtime_app.NATIVE_SETTINGS_PANELS, Destination.NETWORK): + for expanded in (True, False): + with self.subTest(destination=destination, expanded=expanded): + session = runtime_app.StarShellSession.__new__(runtime_app.StarShellSession) + session.profile, session.slc_actions = Profile.LARGE, None + session.favorites, session.pip_warning = Mock(), Mock() + session.input, session.view, session.fonts = Mock(), Mock(), Mock() + session.settings_layer, session.network_layer = Mock(), Mock(return_value=True) + session.notice, session.notice_until = "Test notice", float("inf") + settings = SettingsState(sidebar_expanded=expanded, availability=(DestinationAvailability(Destination.NETWORK, True),)) + snapshot = ShellSnapshot(ShellMode.SETTINGS, reference_state(), settings, + OnroadState(False, False, None, None, SpeedLimitObservation()), selected=destination) + session.snapshot = Mock(return_value=snapshot) + def rail(*_args, **_kwargs): + ox, oy, viewport, parent_clip = clip._placement.get() + self.assertEqual((ox, oy, viewport.x, viewport.y, viewport.width, viewport.height), (20, 30, 25, 35, 2140, 1060)) + self.assertIs(parent_clip, parent) + session.view.settings.render_rail.side_effect = rail + order = Mock() + order.attach_mock(session.view.render, "view") + panel = session.network_layer if destination == Destination.NETWORK else session.settings_layer + order.attach_mock(panel, "panel") + order.attach_mock(session.view.settings.render_rail, "rail") + order.attach_mock(session.fonts.draw, "notice") + with patch.object(rl, "rl_push_matrix"), patch.object(rl, "rl_pop_matrix"), patch.object(rl, "rl_translatef"), \ + patch.object(rl, "begin_scissor_mode"), patch.object(rl, "end_scissor_mode"), patch.object(rl, "draw_rectangle"): + session.render(ShellMode.SETTINGS, rect, parent) + self.assertEqual([call[0] for call in order.mock_calls], ["view", "panel", "rail", "notice"]) + session.view.settings.render_rail.assert_called_once_with(settings, selected=destination) + content = panel.call_args.args[-1] + self.assertEqual((content.x, content.y, content.width, content.height), + (570 if expanded else 70, 55, 1560 if expanded else 2060, 1030)) + self.assertIsNone(clip._placement.get()) + def test_toggle_request_needs_same_displayed_value_on_release(self) -> None: requests = [] touch = TogglesInput(requests.append)