BigUI WIP: Optional Pagenation

This commit is contained in:
firestarsdog
2026-06-09 00:13:11 -04:00
parent 8cc9c59330
commit e836ed09c8
2 changed files with 212 additions and 15 deletions
@@ -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)