mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-08-17 14:23:45 +08:00
BigUI WIP: Optional Pagenation
This commit is contained in:
@@ -472,6 +472,20 @@ class PanelManagerView(AetherInteractiveMixin, Widget):
|
||||
self._content_height = 0.0
|
||||
self._scroll_offset = 0.0
|
||||
self._scroll_rect = rl.Rectangle(0, 0, 0, 0)
|
||||
self._toggle_pages: list[list] = []
|
||||
self._page_count = 1
|
||||
self._current_page = 0
|
||||
self._page_grid: TileGrid | None = None
|
||||
self._page_animating = False
|
||||
self._page_anim_start = 0.0
|
||||
self._page_anim_from = 0.0
|
||||
self._page_anim_committed = False
|
||||
self._page_anim_prev_tiles: list = []
|
||||
self._page_drag_active = False
|
||||
self._page_drag_offset = 0.0
|
||||
self._page_drag_start_x = 0.0
|
||||
self._page_drag_start_y = 0.0
|
||||
self._page_clip_rect: rl.Rectangle | None = None
|
||||
|
||||
# ── hooks ─────────────────────────────────────────────────
|
||||
|
||||
@@ -516,6 +530,7 @@ class PanelManagerView(AetherInteractiveMixin, Widget):
|
||||
|
||||
draw_list_scroll_fades(scroll_rect, self._content_height, self._scroll_offset,
|
||||
AetherListColors.PANEL_BG)
|
||||
self._draw_page_dots(frame.scroll)
|
||||
|
||||
# ── shared layout helpers ─────────────────────────────────
|
||||
|
||||
@@ -551,6 +566,198 @@ class PanelManagerView(AetherInteractiveMixin, Widget):
|
||||
disabled_label=d.get("disabled_label", ""),
|
||||
)
|
||||
|
||||
# ── pagination ─────────────────────────────────────────────
|
||||
|
||||
PAGE_COMMIT_RATIO = 0.35
|
||||
PAGE_ANIM_DURATION = 0.28
|
||||
PAGE_SNAP_DURATION = 0.20
|
||||
|
||||
PAGE_DOT_RADIUS = 6.0
|
||||
PAGE_DOT_GAP = 20.0
|
||||
|
||||
@property
|
||||
def _has_pagination(self) -> bool:
|
||||
return self._page_count > 1
|
||||
|
||||
def _set_toggle_pages(self, pages: list[list]) -> None:
|
||||
self._toggle_pages = pages
|
||||
self._page_count = max(1, len(pages))
|
||||
self._current_page = 0
|
||||
self._on_page_changed()
|
||||
|
||||
def _get_page_defs(self) -> list:
|
||||
if self._has_pagination and self._current_page < len(self._toggle_pages):
|
||||
return self._toggle_pages[self._current_page]
|
||||
return []
|
||||
|
||||
def _on_page_changed(self) -> None:
|
||||
if not self._has_pagination or self._page_grid is None:
|
||||
return
|
||||
self._page_grid.clear()
|
||||
for d in self._get_page_defs():
|
||||
self._page_grid.add_tile(self._make_toggle_tile(d))
|
||||
|
||||
# ── scissor helpers ────────────────────────────────────────
|
||||
|
||||
def _page_scissor_push(self, rect: rl.Rectangle | None) -> None:
|
||||
if rect is None:
|
||||
return
|
||||
rl.end_scissor_mode()
|
||||
rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height))
|
||||
|
||||
def _page_scissor_pop(self) -> None:
|
||||
rl.end_scissor_mode()
|
||||
rl.begin_scissor_mode(int(self._scroll_rect.x), int(self._scroll_rect.y),
|
||||
int(self._scroll_rect.width), int(self._scroll_rect.height))
|
||||
|
||||
# ── drag + animation ───────────────────────────────────────
|
||||
|
||||
GRID_PADDING = 12
|
||||
|
||||
def _start_drag_commit(self, from_offset: float) -> None:
|
||||
if self._page_grid is not None:
|
||||
self._page_anim_prev_tiles = list(self._page_grid.tiles)
|
||||
self._page_animating = True
|
||||
self._page_anim_committed = True
|
||||
self._page_anim_start = time.monotonic()
|
||||
self._page_anim_from = from_offset
|
||||
|
||||
def _start_drag_snap(self, from_offset: float) -> None:
|
||||
self._page_animating = True
|
||||
self._page_anim_committed = False
|
||||
self._page_anim_start = time.monotonic()
|
||||
self._page_anim_from = from_offset
|
||||
|
||||
def _render_page_grid(self, grid: TileGrid, rect: rl.Rectangle, clip_rect: rl.Rectangle | None = None) -> None:
|
||||
if clip_rect is None:
|
||||
clip_rect = rl.Rectangle(rect.x - self.GRID_PADDING, rect.y - self.GRID_PADDING,
|
||||
rect.width + self.GRID_PADDING * 2, rect.height + self.GRID_PADDING * 2)
|
||||
self._page_clip_rect = clip_rect
|
||||
|
||||
# active drag
|
||||
if self._page_drag_active:
|
||||
self._page_scissor_push(clip_rect)
|
||||
grid.render(rl.Rectangle(rect.x + self._page_drag_offset, rect.y, rect.width, rect.height))
|
||||
self._page_scissor_pop()
|
||||
return
|
||||
|
||||
# no animation
|
||||
if not self._page_animating:
|
||||
grid.render(rect)
|
||||
return
|
||||
|
||||
# animation
|
||||
elapsed = time.monotonic() - self._page_anim_start
|
||||
duration = self.PAGE_ANIM_DURATION if self._page_anim_committed else self.PAGE_SNAP_DURATION
|
||||
if elapsed >= duration:
|
||||
self._page_animating = False
|
||||
self._page_anim_prev_tiles.clear()
|
||||
grid.render(rect)
|
||||
return
|
||||
|
||||
t = elapsed / duration
|
||||
t = 1.0 - (1.0 - t) ** 3
|
||||
|
||||
if self._page_anim_committed:
|
||||
direction = 1 if self._page_anim_from < 0 else -1
|
||||
neighbor_start = direction * rect.width + self._page_anim_from
|
||||
cur_offset = neighbor_start + (0.0 - neighbor_start) * t
|
||||
old_target = -direction * rect.width
|
||||
prev_offset = self._page_anim_from + (old_target - self._page_anim_from) * t
|
||||
|
||||
self._page_scissor_push(rl.Rectangle(clip_rect.x, clip_rect.y,
|
||||
clip_rect.width + rect.width, clip_rect.height))
|
||||
if self._page_anim_prev_tiles:
|
||||
old_grid = TileGrid(columns=grid.get_column_count(), padding=grid.gap)
|
||||
old_grid.tiles.extend(self._page_anim_prev_tiles)
|
||||
old_grid.set_parent_rect(self._scroll_rect)
|
||||
old_grid.render(rl.Rectangle(rect.x + prev_offset, rect.y, rect.width, rect.height))
|
||||
grid.set_parent_rect(self._scroll_rect)
|
||||
grid.render(rl.Rectangle(rect.x + cur_offset, rect.y, rect.width, rect.height))
|
||||
self._page_scissor_pop()
|
||||
else:
|
||||
cur_offset = self._page_anim_from * (1.0 - t)
|
||||
self._page_scissor_push(clip_rect)
|
||||
grid.set_parent_rect(self._scroll_rect)
|
||||
grid.render(rl.Rectangle(rect.x + cur_offset, rect.y, rect.width, rect.height))
|
||||
self._page_scissor_pop()
|
||||
|
||||
# ── mouse handling ─────────────────────────────────────────
|
||||
|
||||
def _handle_mouse_press(self, mouse_pos: MousePos) -> None:
|
||||
super()._handle_mouse_press(mouse_pos)
|
||||
if self._has_pagination and not self._page_animating:
|
||||
self._page_drag_start_x = mouse_pos.x
|
||||
self._page_drag_start_y = mouse_pos.y
|
||||
self._page_drag_active = True
|
||||
self._page_drag_offset = 0.0
|
||||
|
||||
def _handle_mouse_event(self, mouse_event: MouseEvent) -> None:
|
||||
super()._handle_mouse_event(mouse_event)
|
||||
if self._page_drag_active and self._has_pagination:
|
||||
dx = mouse_event.pos.x - self._page_drag_start_x
|
||||
dy = abs(mouse_event.pos.y - self._page_drag_start_y)
|
||||
if dy > abs(dx) * 1.2 and dy > 32:
|
||||
self._page_drag_active = False
|
||||
self._page_drag_offset = 0.0
|
||||
return
|
||||
self._page_drag_offset = dx
|
||||
if abs(dx) > 6:
|
||||
self._pressed_target = None
|
||||
self._can_click = False
|
||||
|
||||
def _handle_mouse_release(self, mouse_pos: MousePos) -> None:
|
||||
if self._page_drag_active and self._has_pagination:
|
||||
self._page_drag_active = False
|
||||
offset = self._page_drag_offset
|
||||
self._page_drag_offset = 0.0
|
||||
threshold = self._scroll_rect.width * self.PAGE_COMMIT_RATIO
|
||||
if abs(offset) > threshold:
|
||||
direction = 1 if offset < 0 else -1
|
||||
new_page = self._current_page + (1 if direction == 1 else -1)
|
||||
if 0 <= new_page < self._page_count:
|
||||
self._start_drag_commit(offset)
|
||||
self._current_page = new_page
|
||||
self._on_page_changed()
|
||||
elif abs(offset) > 8:
|
||||
self._start_drag_snap(offset)
|
||||
elif abs(offset) > 8:
|
||||
self._start_drag_snap(offset)
|
||||
return
|
||||
super()._handle_mouse_release(mouse_pos)
|
||||
|
||||
# ── page indicator ─────────────────────────────────────────
|
||||
|
||||
def _draw_page_dots(self, rect: rl.Rectangle) -> None:
|
||||
if not self._has_pagination:
|
||||
return
|
||||
clip = self._page_clip_rect
|
||||
n = min(self._page_count, 8)
|
||||
total_w = n * self.PAGE_DOT_RADIUS * 2 + max(0, n - 1) * self.PAGE_DOT_GAP
|
||||
start_x = (clip.x + (clip.width - total_w) / 2) if clip else rect.x + (rect.width - total_w) / 2
|
||||
dot_y = rect.y + rect.height + 8
|
||||
for i in range(n):
|
||||
cx = start_x + i * (self.PAGE_DOT_RADIUS * 2 + self.PAGE_DOT_GAP) + self.PAGE_DOT_RADIUS
|
||||
fill = self.PANEL_STYLE.accent if i == self._current_page else _with_alpha(AetherListColors.MUTED, 100)
|
||||
rl.draw_circle(int(cx), int(dot_y), self.PAGE_DOT_RADIUS, fill)
|
||||
if self._page_count > 8:
|
||||
more_x = start_x + n * (self.PAGE_DOT_RADIUS * 2 + self.PAGE_DOT_GAP)
|
||||
rl.draw_text_ex(
|
||||
gui_app.font(FontWeight.MEDIUM), "···",
|
||||
rl.Vector2(more_x, dot_y - 8), 16, 0, AetherListColors.MUTED)
|
||||
|
||||
# ── lifecycle ──────────────────────────────────────────────
|
||||
|
||||
def show_event(self) -> None:
|
||||
super().show_event()
|
||||
self._page_drag_active = False
|
||||
self._page_drag_offset = 0.0
|
||||
self._page_animating = False
|
||||
self._page_anim_prev_tiles.clear()
|
||||
if self._has_pagination and self._current_page != 0:
|
||||
self._current_page = 0
|
||||
self._on_page_changed()
|
||||
|
||||
|
||||
PANEL_HEADER_TITLE_Y: int = 4
|
||||
PANEL_HEADER_SUBTITLE_Y: int = 48
|
||||
|
||||
@@ -297,19 +297,11 @@ class SystemSettingsManagerView(PanelManagerView):
|
||||
self._basics_tile_grid_h = 0.0
|
||||
|
||||
self._connectivity_tile_grid = TileGrid(columns=2, padding=12)
|
||||
for toggle_def in self._toggle_defs:
|
||||
tile = ToggleTile(
|
||||
title=toggle_def["title"],
|
||||
get_state=toggle_def["get"],
|
||||
set_state=toggle_def["set"],
|
||||
bg_color=PANEL_STYLE.accent,
|
||||
desc=toggle_def["subtitle"],
|
||||
is_enabled=toggle_def.get("is_enabled"),
|
||||
disabled_label=toggle_def.get("disabled_label", ""),
|
||||
)
|
||||
self._connectivity_tile_grid.add_tile(tile)
|
||||
self._connectivity_tile_grid.set_touch_valid_callback(lambda: self._scroll_panel.is_touch_valid())
|
||||
self._child(self._connectivity_tile_grid)
|
||||
self._page_grid = self._connectivity_tile_grid
|
||||
|
||||
self._set_toggle_pages([self._toggle_defs[i:i+4] for i in range(0, len(self._toggle_defs), 4)])
|
||||
|
||||
self._drive_mode_control = self._child(
|
||||
AetherSegmentedControl(
|
||||
@@ -554,8 +546,7 @@ class SystemSettingsManagerView(PanelManagerView):
|
||||
|
||||
def _draw_connectivity_tiles_column(self, y: float, x: float, width: float, height: float):
|
||||
draw_list_group_shell(rl.Rectangle(x, y, width, height), style=PANEL_STYLE)
|
||||
self._connectivity_tile_grid.set_parent_rect(self._scroll_rect)
|
||||
self._connectivity_tile_grid.render(rl.Rectangle(x + 12, y + 12, width - 24, height - 24))
|
||||
self._render_page_grid(self._connectivity_tile_grid, rl.Rectangle(x + 12, y + 12, width - 24, height - 24))
|
||||
|
||||
def _draw_connectivity_tiles_section(self, y: float, x: float, width: float):
|
||||
tile_rows = self._connectivity_tile_grid.get_row_count(len(self._connectivity_tile_grid.tiles), available_width=width)
|
||||
@@ -563,8 +554,7 @@ class SystemSettingsManagerView(PanelManagerView):
|
||||
tiles_content_h = tile_rows * 130 + tile_gaps
|
||||
|
||||
draw_list_group_shell(rl.Rectangle(x, y, width, tiles_content_h + 24), style=PANEL_STYLE)
|
||||
self._connectivity_tile_grid.set_parent_rect(self._scroll_rect)
|
||||
self._connectivity_tile_grid.render(rl.Rectangle(x + 12, y + 12, width - 24, tiles_content_h))
|
||||
self._render_page_grid(self._connectivity_tile_grid, rl.Rectangle(x + 12, y + 12, width - 24, tiles_content_h))
|
||||
|
||||
def _draw_slider_section(self, y: float, x: float, width: float, title: str, keys: list[str]) -> float:
|
||||
draw_section_header(rl.Rectangle(x, y, width, SECTION_HEADER_HEIGHT), title, style=PANEL_STYLE)
|
||||
|
||||
Reference in New Issue
Block a user