diff --git a/selfdrive/ui/mici/layouts/main.py b/selfdrive/ui/mici/layouts/main.py index e25b2840f7..b5561014ee 100644 --- a/selfdrive/ui/mici/layouts/main.py +++ b/selfdrive/ui/mici/layouts/main.py @@ -13,6 +13,7 @@ from openpilot.system.ui.lib.application import gui_app if gui_app.sunnypilot_ui(): from openpilot.selfdrive.ui.sunnypilot.mici.layouts.settings import SettingsLayoutSP as SettingsLayout + from openpilot.selfdrive.ui.sunnypilot.mici.layouts.onroad import OnroadViewContainerSP as AugmentedRoadView ONROAD_DELAY = 2.5 # seconds @@ -118,13 +119,15 @@ class MiciMainLayout(Scroller): # FIXME: these two pops can interrupt user interacting in the settings if self._onroad_time_delay is not None and rl.get_time() - self._onroad_time_delay >= ONROAD_DELAY: - gui_app.pop_widgets_to(self, lambda: self._scroll_to(self._onroad_layout)) + if not gui_app.sunnypilot_ui() or self._should_auto_scroll_to_onroad(): + gui_app.pop_widgets_to(self, lambda: self._scroll_to(self._onroad_layout)) self._onroad_time_delay = None # When car leaves standstill, pop nav stack and scroll to onroad CS = ui_state.sm["carState"] if not CS.standstill and self._prev_standstill: - gui_app.pop_widgets_to(self, lambda: self._scroll_to(self._onroad_layout)) + if not gui_app.sunnypilot_ui() or self._should_auto_scroll_to_onroad(): + gui_app.pop_widgets_to(self, lambda: self._scroll_to(self._onroad_layout)) self._prev_standstill = CS.standstill def _on_interactive_timeout(self): diff --git a/selfdrive/ui/sunnypilot/mici/layouts/main.py b/selfdrive/ui/sunnypilot/mici/layouts/main.py new file mode 100644 index 0000000000..b0401e8119 --- /dev/null +++ b/selfdrive/ui/sunnypilot/mici/layouts/main.py @@ -0,0 +1,13 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" + +from openpilot.selfdrive.ui.mici.layouts.main import MiciMainLayout + + +class MiciMainLayoutSP(MiciMainLayout): + def _should_auto_scroll_to_onroad(self) -> bool: + return not self._onroad_layout.is_on_info_panel() diff --git a/selfdrive/ui/sunnypilot/mici/layouts/onroad.py b/selfdrive/ui/sunnypilot/mici/layouts/onroad.py new file mode 100644 index 0000000000..981d60f563 --- /dev/null +++ b/selfdrive/ui/sunnypilot/mici/layouts/onroad.py @@ -0,0 +1,63 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import pyray as rl +from openpilot.system.ui.lib.application import gui_app +from openpilot.selfdrive.ui.sunnypilot.mici.widgets.scroller_sp import ScrollerSP +from openpilot.selfdrive.ui.sunnypilot.mici.onroad.augmented_road_view import AugmentedRoadViewSP +from openpilot.selfdrive.ui.sunnypilot.mici.layouts.onroad_info_panel import OnroadInfoPanel + +CONFIDENCE_BALL_VISIBLE_RATIO = 0.4 +HORIZONTAL_SETTLE_PX = 5 +HORIZONTAL_RESET_RATIO = 0.5 + + +class OnroadViewContainerSP(ScrollerSP): + def __init__(self, bookmark_callback=None): + super().__init__(horizontal=False, snap_items=True, spacing=0, pad=0, scroll_indicator=False, edge_shadows=False) + self.road_view = AugmentedRoadViewSP(bookmark_callback=bookmark_callback) + self.onroad_info_panel = OnroadInfoPanel(bookmark_callback=bookmark_callback) + + self._scroller.add_widgets([ + self.road_view, + self.onroad_info_panel, + ]) + self._scroller.set_reset_scroll_at_show(False) + self._scroller.set_scrolling_enabled(lambda: abs(self.rect.x) < HORIZONTAL_SETTLE_PX) + + for child in (self.road_view, self.onroad_info_panel): + inner_touch_valid = child._touch_valid_callback + child.set_touch_valid_callback( + lambda inner=inner_touch_valid: self._touch_valid() and (inner() if inner else True) + ) + + def set_rect(self, rect: rl.Rectangle): + super().set_rect(rect) + self.road_view.set_rect(rect) + self.onroad_info_panel.set_rect(rect) + return self + + def is_swiping_left(self) -> bool: + return self.road_view.is_swiping_left() or self.onroad_info_panel.is_swiping_left() + + def set_click_callback(self, callback) -> None: + self.road_view.set_click_callback(callback) + self.onroad_info_panel.set_click_callback(callback) + + def is_on_info_panel(self) -> bool: + """True when scrolled past halfway toward onroad_info_panel (used by main layout + to skip auto-pop-back-to-camera while user is reading the info panel).""" + return abs(self._scroller.scroll_panel.get_offset()) > self._rect.height / 2 + + def _render(self, rect: rl.Rectangle): + if abs(self.rect.x) > gui_app.width * HORIZONTAL_RESET_RATIO: + self._scroller.scroll_panel.set_offset(0) + + vertical_offset = self._scroller.scroll_panel.get_offset() + show_ball = abs(vertical_offset) < rect.height * CONFIDENCE_BALL_VISIBLE_RATIO + self.road_view.set_show_confidence_ball(show_ball) + + super()._render(rect) diff --git a/selfdrive/ui/sunnypilot/mici/layouts/onroad_info_panel.py b/selfdrive/ui/sunnypilot/mici/layouts/onroad_info_panel.py new file mode 100644 index 0000000000..c51b8c5f41 --- /dev/null +++ b/selfdrive/ui/sunnypilot/mici/layouts/onroad_info_panel.py @@ -0,0 +1,324 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" + +import pyray as rl +from dataclasses import dataclass +from openpilot.common.constants import CV +from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.lib.text_measure import measure_text_cached +from openpilot.system.ui.lib.application import MousePos +from openpilot.system.ui.widgets import Widget +from openpilot.selfdrive.ui.mici.onroad.alert_renderer import AlertRenderer +from openpilot.selfdrive.ui.mici.onroad.augmented_road_view import BookmarkIcon + +METER_TO_KM = 0.001 +METER_TO_MILE = 0.000621371 + + +@dataclass(frozen=True) +class OnroadInfoPanelColors: + white: rl.Color = rl.WHITE + black: rl.Color = rl.BLACK + red: rl.Color = rl.Color(255, 0, 0, 255) + green: rl.Color = rl.Color(0, 255, 0, 255) + grey: rl.Color = rl.Color(190, 195, 190, 255) + light_grey: rl.Color = rl.Color(200, 200, 200, 255) + dark_grey: rl.Color = rl.Color(100, 100, 100, 255) + bg_dark: rl.Color = rl.Color(0, 0, 0, 255) + card_bg: rl.Color = rl.Color(50, 50, 50, 200) + badge_bg: rl.Color = rl.Color(60, 60, 60, 255) + + +COLORS = OnroadInfoPanelColors() + + +class OnroadInfoPanel(Widget): + def __init__(self, bookmark_callback=None): + super().__init__() + self.speed_limit: float = 0.0 + self.speed_limit_valid: bool = False + self.speed_limit_offset: float = 0.0 + self.next_speed_limit: float = 0.0 + self.next_speed_limit_distance: float = 0.0 + self.road_name: str = "" + self.current_speed: float = 0.0 + self.set_speed: float = 0.0 + self.cruise_enabled: bool = False + + self._sign_slide: float = 0.0 + + self._font_bold: rl.Font = gui_app.font(FontWeight.BOLD) + self._font_semi_bold: rl.Font = gui_app.font(FontWeight.SEMI_BOLD) + self._font_medium: rl.Font = gui_app.font(FontWeight.MEDIUM) + + self._marquee_offset: float = 0.0 + self._marquee_direction: int = 1 + self._marquee_pause_timer: float = 0.0 + self._marquee_speed: float = 40.0 + self._marquee_pause_duration: float = 1.5 + + self._alert_renderer = AlertRenderer() + self._alert_alpha_filter = FirstOrderFilter(0, 0.05, 1 / gui_app.target_fps) + + self._bookmark_icon = BookmarkIcon(bookmark_callback) + + def is_swiping_left(self) -> bool: + return self._bookmark_icon.is_swiping_left() + + def _handle_mouse_release(self, mouse_pos: MousePos) -> None: + # Mirror stock AugmentedRoadView: suppress click while bookmark gesture active + if not self._bookmark_icon.interacting(): + super()._handle_mouse_release(mouse_pos) + + def _update_state(self) -> None: + sm = ui_state.sm + speed_conv = CV.MS_TO_KPH if ui_state.is_metric else CV.MS_TO_MPH + + if sm.valid["longitudinalPlanSP"]: + lp_sp = sm["longitudinalPlanSP"] + resolver = lp_sp.speedLimit.resolver + self.speed_limit = resolver.speedLimit * speed_conv + self.speed_limit_valid = resolver.speedLimitValid + self.speed_limit_offset = resolver.speedLimitOffset * speed_conv + + if sm.valid["liveMapDataSP"]: + lmd = sm["liveMapDataSP"] + self.next_speed_limit = lmd.speedLimitAhead * speed_conv + self.next_speed_limit_distance = lmd.speedLimitAheadDistance + self.road_name = lmd.roadName + + if sm.updated["carState"]: + self.current_speed = sm["carState"].vEgo * speed_conv + + if sm.valid["carState"] and sm.valid["controlsState"]: + self.cruise_enabled = sm["carState"].cruiseState.enabled + v_cruise_cluster = sm["carState"].vCruiseCluster + set_speed_kph = sm["controlsState"].vCruiseDEPRECATED if v_cruise_cluster == 0.0 else v_cruise_cluster + self.set_speed = set_speed_kph * (METER_TO_MILE / METER_TO_KM) if not ui_state.is_metric else set_speed_kph + + def _render(self, rect: rl.Rectangle) -> None: + self._update_state() + + rl.draw_rectangle(int(rect.x), int(rect.y), int(rect.width), int(rect.height), COLORS.bg_dark) + margin = 20 + mid_y = rect.y + rect.height / 2 + + left_x = rect.x + margin + + if self.cruise_enabled: + unit = tr("MAX") + display_speed = self.set_speed + else: + unit = tr("km/h") if ui_state.is_metric else tr("MPH") + display_speed = self.current_speed + + speed_val = str(round(display_speed)) + if self.speed_limit_valid and display_speed > self.speed_limit: + speed_color = COLORS.red + else: + speed_color = COLORS.white + + rl.draw_text_ex(self._font_semi_bold, unit, rl.Vector2(left_x, mid_y - 95), 38, 0, COLORS.grey) + rl.draw_text_ex(self._font_bold, speed_val, rl.Vector2(left_x, mid_y - 60), 110, 0, speed_color) + + sign_width = 135 + sign_height = 135 if ui_state.is_metric else 175 + + has_next = self.next_speed_limit > 0 and self.next_speed_limit != self.speed_limit + target_slide = 1.0 if has_next else 0.0 + slide_speed = 3.0 * rl.get_frame_time() + if self._sign_slide < target_slide: + self._sign_slide = min(self._sign_slide + slide_speed, target_slide) + elif self._sign_slide > target_slide: + self._sign_slide = max(self._sign_slide - slide_speed, target_slide) + + next_w = int(sign_width * 0.7) + next_h = int(sign_height * 0.7) + next_peek = int(next_w * 0.85) + 5 + centered_x = rect.x + rect.width - sign_width - margin + shifted_x = rect.x + rect.width - sign_width - margin - next_peek + sign_x = centered_x + (shifted_x - centered_x) * self._sign_slide + sign_y = rect.y + (rect.height - sign_height) / 2 + + road_y = mid_y + 55 + road_width = sign_x - left_x - margin + self._draw_road_name(left_x, road_y, road_width) + + if has_next and self._sign_slide > 0.01: + next_val = str(round(self.next_speed_limit)) + dist_str = self._format_distance(self.next_speed_limit_distance) + next_x = sign_x + sign_width - int(next_w * 0.15) + next_y = sign_y + (sign_height - next_h) / 2 + + next_speed_color = COLORS.black + if ui_state.is_metric: + self._draw_vienna_sign(next_x, next_y, next_w, next_h, next_val, next_speed_color, is_upcoming=True) + else: + self._draw_mutcd_sign(next_x, next_y, next_w, next_h, next_val, next_speed_color, is_upcoming=True) + + dist_size = measure_text_cached(self._font_medium, dist_str, 24) + rl.draw_text_ex(self._font_medium, dist_str, rl.Vector2(next_x + next_w / 2 - dist_size.x / 2, next_y + next_h + 4), 24, 0, COLORS.grey) + + self._draw_speed_limit_sign(sign_x, sign_y, sign_width, sign_height) + + if self.speed_limit_offset != 0 and self.speed_limit_valid: + offset_val = str(abs(round(self.speed_limit_offset))) + badge_sz = 42 + badge_x = sign_x + sign_width - badge_sz * 0.85 + badge_y = sign_y - badge_sz * 0.25 + + if ui_state.is_metric: + badge_r = badge_sz / 2 + badge_cx = badge_x + badge_r + badge_cy = badge_y + badge_r + rl.draw_circle(int(badge_cx), int(badge_cy), badge_r + 2, COLORS.dark_grey) + rl.draw_circle(int(badge_cx), int(badge_cy), badge_r, COLORS.badge_bg) + self._draw_text_centered(self._font_bold, offset_val, 24, rl.Vector2(badge_cx, badge_cy), COLORS.white) + else: + mutcd_badge_x = sign_x + sign_width - badge_sz * 0.65 + mutcd_badge_y = sign_y - badge_sz * 0.50 + badge_rect = rl.Rectangle(mutcd_badge_x, mutcd_badge_y, badge_sz, badge_sz) + rl.draw_rectangle_rounded(badge_rect, 0.25, 10, COLORS.badge_bg) + rl.draw_rectangle_rounded_lines_ex(badge_rect, 0.25, 10, 2, COLORS.dark_grey) + self._draw_text_centered(self._font_bold, offset_val, 24, rl.Vector2(mutcd_badge_x + badge_sz / 2, mutcd_badge_y + badge_sz / 2), COLORS.white) + + # SCC + speed_size = measure_text_cached(self._font_bold, speed_val, 110) + scc_x = left_x + speed_size.x + 30 + scc_y = mid_y - 50 + self._draw_scc_icons(scc_x, scc_y) + + self._bookmark_icon.render(rect) + + if ui_state.started: + alert_obj, no_alert = self._alert_renderer.will_render() + self._alert_alpha_filter.update(0 if no_alert else 1) + alpha = self._alert_alpha_filter.x + if alpha > 0.01: + rl.draw_rectangle(int(rect.x), int(rect.y), int(rect.width), int(rect.height), rl.Color(0, 0, 0, int(150 * alpha))) + self._alert_renderer.render(rect) + + def _draw_scc_icons(self, x: float, y: float) -> None: + sm = ui_state.sm + if not sm.valid["longitudinalPlanSP"]: + return + scc = sm["longitudinalPlanSP"].smartCruiseControl + + box_w, box_h = 100, 36 + gap = 6 + drawn = 0 + + for label, active in [("SCC-V", scc.vision.active), ("SCC-M", scc.map.active)]: + if not active: + continue + bx = x + by = y + drawn * (box_h + gap) + rl.draw_rectangle_rounded(rl.Rectangle(bx, by, box_w, box_h), 0.3, 10, COLORS.green) + self._draw_text_centered(self._font_bold, label, 20, rl.Vector2(bx + box_w / 2, by + box_h / 2), COLORS.black) + drawn += 1 + + def _draw_speed_limit_sign(self, x: float, y: float, sign_width: float, sign_height: float) -> None: + speed_str = str(round(self.speed_limit)) if self.speed_limit_valid and self.speed_limit > 0 else "--" + speed_color = COLORS.black if not self.speed_limit_valid or self.current_speed <= self.speed_limit else COLORS.red + + if ui_state.is_metric: + self._draw_vienna_sign(x, y, sign_width, sign_height, speed_str, speed_color, is_upcoming=False) + else: + self._draw_mutcd_sign(x, y, sign_width, sign_height, speed_str, speed_color, is_upcoming=False) + + def _draw_road_name(self, x: float, y: float, width: float) -> None: + road_display = self.road_name if self.road_name else "--" + font_size = 30 + road_size = measure_text_cached(self._font_semi_bold, road_display, font_size) + text_width = road_size.x + + if text_width <= width: + self._marquee_offset = 0.0 + self._marquee_direction = 1 + self._marquee_pause_timer = 0.0 + rl.draw_text_ex(self._font_semi_bold, road_display, rl.Vector2(x, y), font_size, 0, COLORS.white) + else: + overflow = text_width - width + dt = rl.get_frame_time() + + if self._marquee_pause_timer > 0: + self._marquee_pause_timer -= dt + else: + self._marquee_offset += self._marquee_direction * self._marquee_speed * dt + + if self._marquee_offset >= overflow: + self._marquee_offset = overflow + self._marquee_direction = -1 + self._marquee_pause_timer = self._marquee_pause_duration + elif self._marquee_offset <= 0: + self._marquee_offset = 0 + self._marquee_direction = 1 + self._marquee_pause_timer = self._marquee_pause_duration + + rl.begin_scissor_mode(int(x), int(y), int(width), int(road_size.y + 4)) + text_pos = rl.Vector2(x - self._marquee_offset, y) + rl.draw_text_ex(self._font_semi_bold, road_display, text_pos, font_size, 0, COLORS.white) + rl.end_scissor_mode() + + def _draw_vienna_sign(self, x: float, y: float, width: float, height: float, speed_str: str, speed_color: rl.Color, is_upcoming: bool = False) -> None: + center = rl.Vector2(x + width / 2, y + height / 2) + outer_radius = min(width, height) / 2 + + rl.draw_circle_v(center, outer_radius, COLORS.white) + ring_width = outer_radius * 0.18 + rl.draw_ring(center, outer_radius - ring_width, outer_radius, 0, 360, 36, COLORS.red) + + font_size = outer_radius * (0.7 if len(speed_str) >= 3 else 0.9) + text_size = measure_text_cached(self._font_bold, speed_str, int(font_size)) + text_pos = rl.Vector2(center.x - text_size.x / 2, center.y - text_size.y / 2) + rl.draw_text_ex(self._font_bold, speed_str, text_pos, font_size, 0, speed_color) + + def _draw_mutcd_sign(self, x: float, y: float, width: float, height: float, speed_str: str, speed_color: rl.Color, is_upcoming: bool = False) -> None: + sign_rect = rl.Rectangle(x, y, width, height) + rl.draw_rectangle_rounded(sign_rect, 0.35, 10, COLORS.white) + + inset = max(4, width * 0.05) + inner_rect = rl.Rectangle(x + inset, y + inset, width - inset * 2, height - inset * 2) + outer_radius = 0.35 * width / 2.0 + inner_radius = outer_radius - inset + inner_roundness = inner_radius / (inner_rect.width / 2.0) + rl.draw_rectangle_rounded_lines_ex(inner_rect, inner_roundness, 10, 3, COLORS.black) + + mid_x = x + width / 2 + label_size = max(18, int(width * 0.26)) + if is_upcoming: + self._draw_text_centered(self._font_bold, tr("AHEAD"), label_size, rl.Vector2(mid_x, y + height * 0.27), COLORS.black) + else: + self._draw_text_centered(self._font_bold, tr("SPEED"), label_size, rl.Vector2(mid_x, y + height * 0.20), COLORS.black) + self._draw_text_centered(self._font_bold, tr("LIMIT"), label_size, rl.Vector2(mid_x, y + height * 0.40), COLORS.black) + + speed_font_size = int(width * 0.52) if len(speed_str) >= 3 else int(width * 0.62) + self._draw_text_centered(self._font_bold, speed_str, speed_font_size, rl.Vector2(mid_x, y + height * 0.72), speed_color) + + def _draw_text_centered(self, font, text, size, pos_center, color): + sz = measure_text_cached(font, text, size) + rl.draw_text_ex(font, text, rl.Vector2(pos_center.x - sz.x / 2, pos_center.y - sz.y / 2), size, 0, color) + + def _format_distance(self, distance: float) -> str: + if ui_state.is_metric: + if distance < 50: + return tr("Near") + if distance >= 1000: + return f"{distance * METER_TO_KM:.1f}" + tr("km") + if distance < 200: + rounded = max(10, int(distance / 10) * 10) + else: + rounded = int(distance / 100) * 100 + return str(rounded) + tr("m") + else: + distance_mi = distance * METER_TO_MILE + if distance_mi < 0.1: + return tr("Near") + return f"{distance_mi:.1f}" + tr("mi") diff --git a/selfdrive/ui/sunnypilot/mici/onroad/augmented_road_view.py b/selfdrive/ui/sunnypilot/mici/onroad/augmented_road_view.py new file mode 100644 index 0000000000..acd4a162d0 --- /dev/null +++ b/selfdrive/ui/sunnypilot/mici/onroad/augmented_road_view.py @@ -0,0 +1,30 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" + +import pyray as rl +from openpilot.selfdrive.ui.mici.onroad.augmented_road_view import AugmentedRoadView + + +class _SuppressedConfidenceBall: + def render(self, *_): + pass + + +class AugmentedRoadViewSP(AugmentedRoadView): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._show_confidence_ball: bool = True + self._real_confidence_ball = self._confidence_ball + self._confidence_ball = _SuppressedConfidenceBall() + + def set_show_confidence_ball(self, show: bool) -> None: + self._show_confidence_ball = show + + def _render(self, rect: rl.Rectangle) -> None: + super()._render(rect) + if self._show_confidence_ball: + self._real_confidence_ball.render(self.rect) diff --git a/selfdrive/ui/sunnypilot/mici/widgets/scroll_panel_sp.py b/selfdrive/ui/sunnypilot/mici/widgets/scroll_panel_sp.py new file mode 100644 index 0000000000..1c97f59e84 --- /dev/null +++ b/selfdrive/ui/sunnypilot/mici/widgets/scroll_panel_sp.py @@ -0,0 +1,34 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" + +import pyray as rl +from openpilot.system.ui.lib.application import MouseEvent +from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2, ScrollState + + +class GuiScrollPanel2SP(GuiScrollPanel2): + """Reject orthogonal-dominant drags so nested scrollers (outer horizontal + + inner vertical) don't both engage on a slightly diagonal swipe. + + Implemented as a post-super state rollback rather than reimplementing the + PRESSED state machine — keeps stock behaviour authoritative.""" + + def _handle_mouse_event(self, mouse_event: MouseEvent, bounds: rl.Rectangle, bounds_size: float, + content_size: float) -> None: + pre_state = self._state + super()._handle_mouse_event(mouse_event, bounds, bounds_size, content_size) + + if self._state == ScrollState.MANUAL_SCROLL and pre_state == ScrollState.PRESSED and \ + self._initial_click_event is not None: + diff_x = abs(mouse_event.pos.x - self._initial_click_event.pos.x) + diff_y = abs(mouse_event.pos.y - self._initial_click_event.pos.y) + along = diff_x if self._horizontal else diff_y + anti = diff_y if self._horizontal else diff_x + if anti > along: + self._state = ScrollState.STEADY + self._velocity = 0.0 + self._velocity_buffer.clear() diff --git a/selfdrive/ui/sunnypilot/mici/widgets/scroller_sp.py b/selfdrive/ui/sunnypilot/mici/widgets/scroller_sp.py new file mode 100644 index 0000000000..3a678162e0 --- /dev/null +++ b/selfdrive/ui/sunnypilot/mici/widgets/scroller_sp.py @@ -0,0 +1,16 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" + +from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.selfdrive.ui.sunnypilot.mici.widgets.scroll_panel_sp import GuiScrollPanel2SP + + +class ScrollerSP(Scroller): + def __init__(self, **kwargs): + super().__init__(**kwargs) + inner = self._scroller + inner.scroll_panel = GuiScrollPanel2SP(inner._horizontal, handle_out_of_bounds=not inner._snap_items) diff --git a/selfdrive/ui/ui.py b/selfdrive/ui/ui.py index e01ddb2513..501b3f1c95 100755 --- a/selfdrive/ui/ui.py +++ b/selfdrive/ui/ui.py @@ -10,6 +10,9 @@ from openpilot.selfdrive.ui.layouts.main import MainLayout from openpilot.selfdrive.ui.mici.layouts.main import MiciMainLayout from openpilot.selfdrive.ui.ui_state import ui_state +if gui_app.sunnypilot_ui(): + from openpilot.selfdrive.ui.sunnypilot.mici.layouts.main import MiciMainLayoutSP as MiciMainLayout + BIG_UI = gui_app.big_ui()