BigUI WIP: Tile updates

This commit is contained in:
firestarsdog
2026-06-13 11:56:21 -04:00
parent fa7fa6afaf
commit bd7394ea33
6 changed files with 290 additions and 199 deletions
@@ -567,7 +567,7 @@ class PanelManagerView(AetherInteractiveMixin, Widget):
set_state=d.get("set_state", d.get("set")),
bg_color=self.PANEL_STYLE.accent,
desc=d.get("subtitle", d.get("desc", "")),
is_enabled=d.get("is_enabled"),
is_enabled=d.get("is_enabled", d.get("enabled")),
disabled_label=d.get("disabled_label", ""),
)
@@ -596,10 +596,11 @@ class PanelManagerView(AetherInteractiveMixin, Widget):
return []
def _on_page_changed(self) -> None:
if not self._has_pagination or self._page_grid is None:
if not getattr(self, '_toggle_pages', None) or self._page_grid is None:
return
self._page_grid.clear()
for d in self._get_page_defs():
page_idx = self._current_page if self._current_page < len(self._toggle_pages) else 0
for d in self._toggle_pages[page_idx]:
self._page_grid.add_tile(self._make_toggle_tile(d))
# ── scissor helpers ────────────────────────────────────────
@@ -674,7 +675,7 @@ class PanelManagerView(AetherInteractiveMixin, Widget):
self._page_scissor_push(clip_rect)
if self._page_anim_prev_tiles:
old_grid = TileGrid(columns=grid.get_column_count(), padding=grid.gap)
old_grid = TileGrid(columns=grid.get_column_count(), padding=grid.gap, tile_height=grid._tile_height)
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))
@@ -2819,14 +2820,14 @@ class AetherTile(Widget):
off = i * 2.5 * glow
gr = rl.Rectangle(rx - off, ry - off, rw + off * 2, rh + off * 2)
a = int(25 * (1.0 - i / 5) * glow)
_draw_rounded_fill(gr, rl.Color(accent.r, accent.g, accent.b, a), radius_px=100)
_draw_rounded_fill(gr, rl.Color(accent.r, accent.g, accent.b, max(0, min(255, a))), radius_px=100)
_draw_rounded_fill(face, _HUD_BG_ON, radius_px=100)
bc = rl.Color(
int(off_border.r + (accent.r - off_border.r) * glow),
int(off_border.g + (accent.g - off_border.g) * glow),
int(off_border.b + (accent.b - off_border.b) * glow),
max(0, min(255, int(off_border.r + (accent.r - off_border.r) * glow))),
max(0, min(255, int(off_border.g + (accent.g - off_border.g) * glow))),
max(0, min(255, int(off_border.b + (accent.b - off_border.b) * glow))),
255)
_draw_rounded_stroke(face, bc, radius_px=100)
@@ -2835,9 +2836,9 @@ class AetherTile(Widget):
led_y = ry + 12
led_base = _HUD_LED_BASE
led_col = rl.Color(
int(led_base.r + (accent.r - led_base.r) * glow),
int(led_base.g + (accent.g - led_base.g) * glow),
int(led_base.b + (accent.b - led_base.b) * glow),
max(0, min(255, int(led_base.r + (accent.r - led_base.r) * glow))),
max(0, min(255, int(led_base.g + (accent.g - led_base.g) * glow))),
max(0, min(255, int(led_base.b + (accent.b - led_base.b) * glow))),
255)
rl.draw_rectangle(led_x, led_y, led_w, led_h, led_col)
@@ -3053,49 +3054,55 @@ class ToggleTile(AetherTile):
self._animate_plate(rl.get_frame_time())
if not enabled:
self.surface_color = self._disabled_color
self._plate_offset = 0.0
self._plate_target = 0.0
face = self._render_layers(rect)
state_text = tr(self._disabled_label) if self._disabled_label else tr("LOCKED")
self._draw_signal_edge(face, self.surface_color, width=TILE_SIGNAL_WIDTH, alpha=28)
self._render_tile_stack(face, title=self.title, primary=state_text, desc=self.desc,
title_font=self._font, primary_font=self._font, desc_font=self._font_desc,
title_size=28, primary_size=30)
return
if not self._show_led:
self.surface_color = self._active_color if active else self._inactive_color
self.surface_color = self._disabled_color if not enabled else (self._active_color if active else self._inactive_color)
state_text = tr(self._disabled_label) if not enabled and self._disabled_label else (tr("LOCKED") if not enabled else (tr("ON") if active else tr("OFF")))
signal_color = self._active_color if (enabled and active) else self.surface_color
alpha = 62 if (enabled and active) else 28
face = self._render_layers(rect)
state_text = tr("ON") if active else tr("OFF")
self._draw_signal_edge(face, self._active_color if active else self.surface_color,
width=TILE_SIGNAL_WIDTH, alpha=62 if active else 28)
self._draw_signal_edge(face, signal_color, width=TILE_SIGNAL_WIDTH, alpha=alpha)
self._render_tile_stack(face, title=self.title, primary=state_text, desc=self.desc,
title_font=self._font, primary_font=self._font, desc_font=self._font_desc,
title_size=28, primary_size=30)
return
# --- HUD toggle path (show_led + enabled) ---
face, accent = self._render_hud_background(rect, self._active_color, self._glow)
# --- HUD toggle path (show_led) ---
color = self._active_color if enabled else self._disabled_color
glow = self._glow if enabled else 0.0
face, accent = self._render_hud_background(rect, color, glow)
rx, ry, rw, rh = face.x, face.y, face.width, face.height
content_pad = SPACING.tile_content
max_w = rw - content_pad * 2
text_scale = min(rw / 360.0, rh / 205.0)
title_size = max(18, int(round(22 * text_scale)))
title_color = rl.WHITE if self.get_state() else _HUD_TEXT_DIM
self._draw_text_fit(self._font, self.title,
rl.Vector2(rx + content_pad, ry + int(rh * 0.50)),
max_w, title_size, align_center=True, color=title_color)
led_cx = rx + rw // 2
led_cy = ry + int(rh * 0.75)
if self.get_state():
rl.draw_circle(int(led_cx), int(led_cy), 11, rl.Color(accent.r, accent.g, accent.b, 24))
rl.draw_circle(int(led_cx), int(led_cy), 6, accent)
if not enabled:
self._draw_text_fit(self._font, self.title,
rl.Vector2(rx + content_pad, ry + int(rh * 0.40)),
max_w, title_size, align_center=True, color=_HUD_TEXT_DIM)
disabled_text = tr(self._disabled_label) if self._disabled_label else tr("LOCKED")
desc_size = max(14, int(round(16 * text_scale)))
self._draw_text_fit(self._font_desc, disabled_text,
rl.Vector2(rx + content_pad, ry + int(rh * 0.68)),
max_w, desc_size, align_center=True, color=_HUD_TEXT_DIM)
else:
rl.draw_circle(int(led_cx), int(led_cy), 7, rl.Color(14, 16, 22, 255))
rl.draw_ring(rl.Vector2(led_cx, led_cy), 5, 6, 0, 360, 24, rl.Color(70, 78, 95, 140))
title_color = rl.WHITE if active else _HUD_TEXT_DIM
self._draw_text_fit(self._font, self.title,
rl.Vector2(rx + content_pad, ry + int(rh * 0.50)),
max_w, title_size, align_center=True, color=title_color)
led_cx = rx + rw // 2
led_cy = ry + int(rh * 0.75)
if active:
rl.draw_circle(int(led_cx), int(led_cy), 11, rl.Color(accent.r, accent.g, accent.b, 24))
rl.draw_circle(int(led_cx), int(led_cy), 6, accent)
else:
rl.draw_circle(int(led_cx), int(led_cy), 7, rl.Color(14, 16, 22, 255))
rl.draw_ring(rl.Vector2(led_cx, led_cy), 5, 6, 0, 360, 24, rl.Color(70, 78, 95, 140))
class ValueTile(AetherTile):
@@ -3137,32 +3144,31 @@ class ValueTile(AetherTile):
self._animate_plate(rl.get_frame_time())
if not enabled:
self.surface_color = self._disabled_color
self._plate_offset = 0.0
self._plate_target = 0.0
face = self._render_layers(rect)
self._draw_signal_edge(face, self.surface_color, width=TILE_SIGNAL_WIDTH, alpha=28)
self._render_tile_stack(face, title=self.title, primary=self.get_value(), desc=self.desc,
title_font=self._font, primary_font=self._font, desc_font=self._font_desc,
title_size=28, primary_size=28)
return
face, accent = self._render_hud_background(rect, self._active_color)
color = self._active_color if enabled else self._disabled_color
glow = 1.0 if enabled else 0.0
face, accent = self._render_hud_background(rect, color, glow)
rx, ry, rw, rh = face.x, face.y, face.width, face.height
content_pad = SPACING.tile_content
max_w = rw - content_pad * 2
text_scale = min(rw / 360.0, rh / 205.0)
# Title
title_size = max(18, int(round(22 * text_scale)))
self._draw_text_fit(self._font, self.title,
rl.Vector2(rx + content_pad, ry + int(rh * 0.35)),
max_w, title_size, align_center=True, color=_HUD_TEXT_DIM)
# Value
val_text = self.get_value()
val_size = max(18, int(round(24 * text_scale)))
val_color = accent if enabled else _HUD_TEXT_DIM
self._draw_text_fit(self._font, val_text,
rl.Vector2(rx + content_pad, ry + int(rh * 0.58)),
max_w, val_size, align_center=True, color=accent)
max_w, val_size, align_center=True, color=val_color)
class SliderTile(AetherTile):
@@ -3282,34 +3288,33 @@ class SliderTile(AetherTile):
self._animate_plate(dt)
if not enabled:
self.surface_color = self._disabled_color
self._plate_offset = 0.0
self._plate_target = 0.0
face = self._render_layers(rect)
self._draw_signal_edge(face, self.surface_color, width=TILE_SIGNAL_WIDTH, alpha=28)
val_str = self.labels.get(current_val, f"{int(current_val)}{self.unit}")
self._render_tile_stack(face, title=self.title, primary=val_str, desc=self.desc,
title_font=self._font, primary_font=self._font, desc_font=self._font_desc,
title_size=28, primary_size=28)
return
face, accent = self._render_hud_background(rect, self._active_color)
color = self._active_color if enabled else self._disabled_color
glow = 1.0 if enabled else 0.0
face, accent = self._render_hud_background(rect, color, glow)
rx, ry, rw, rh = face.x, face.y, face.width, face.height
content_pad = SPACING.tile_content
max_w = rw - content_pad * 2
text_scale = min(rw / 360.0, rh / 205.0)
# Title
title_size = max(18, int(round(22 * text_scale)))
self._draw_text_fit(self._font, self.title,
rl.Vector2(rx + content_pad, ry + int(rh * 0.30)),
max_w, title_size, align_center=True, color=_HUD_TEXT_DIM)
# Value text
val_str = self.labels.get(current_val, f"{int(current_val)}{self.unit}")
val_size = max(18, int(round(24 * text_scale)))
val_color = accent if enabled else _HUD_TEXT_DIM
self._draw_text_fit(self._font, val_str,
rl.Vector2(rx + content_pad, ry + int(rh * 0.52)),
max_w, val_size, align_center=True, color=accent)
max_w, val_size, align_center=True, color=val_color)
# Slider meter
value_range = self.max_val - self.min_val
frac = 0.0 if value_range == 0 else max(0.0, min(1.0, (self._smooth_value - self.min_val) / value_range))
meter_h = 6
@@ -3318,7 +3323,8 @@ class SliderTile(AetherTile):
rl.draw_rectangle_rec(_snap_rect(meter_rect), rl.Color(255, 255, 255, 14))
if fill_w > 1:
fill_rect = rl.Rectangle(meter_rect.x, meter_rect.y, fill_w, meter_rect.height)
rl.draw_rectangle_rec(_snap_rect(fill_rect), _with_alpha(accent, 176))
fill_color = _with_alpha(accent, 176) if enabled else rl.Color(120, 120, 120, 100)
rl.draw_rectangle_rec(_snap_rect(fill_rect), fill_color)
class AetherSlider(Widget):
@@ -4113,13 +4119,16 @@ class AetherSegmentedControl(Widget):
class TileGrid(Widget):
def __init__(self, columns: int | None = None, padding: int | None = None, uniform_width: bool = False, min_tile_width: int | None = None):
def __init__(self, columns: int | None = None, padding: int | None = None, uniform_width: bool = False, min_tile_width: int | None = None, tile_height: float | None = None, force_square: bool = False):
super().__init__()
self._columns = columns
self._gap = padding if padding is not None else SPACING.tile_gap
self.tiles = []
self._uniform_width = uniform_width
self._min_tile_width = min_tile_width if min_tile_width is not None else MIN_TILE_WIDTH
self._tile_height = tile_height
self.force_square = force_square
@property
def gap(self) -> int:
@@ -4165,7 +4174,8 @@ class TileGrid(Widget):
return preferred
min_tile_width = max(1, self._min_tile_width)
max_cols_by_width = max(1, int((available_width + self._gap) / (min_tile_width + self._gap)))
return max(1, min(preferred, count, max_cols_by_width))
limit_by_count = count if self._columns is None else preferred
return max(1, min(preferred, limit_by_count, max_cols_by_width))
def get_row_count(self, tile_count: int | None = None, available_width: float | None = None) -> int:
count = len(self.tiles) if tile_count is None else tile_count
@@ -4178,6 +4188,19 @@ class TileGrid(Widget):
rows = self.get_row_count(tile_count, available_width=available_width)
return self._gap * max(0, rows - 1)
def measure_height(self, width: float) -> float:
if not self.tiles:
return 0.0
count = len(self.tiles)
rows = self.get_row_count(count, available_width=width)
if self.force_square:
cols = self.get_effective_column_count(width, count)
col_w = (width - (self._gap * (cols - 1))) / cols
h = col_w
else:
h = self._tile_height if self._tile_height is not None else 130.0
return rows * h + self.get_internal_gap_height(count, available_width=width)
def _render(self, rect: rl.Rectangle):
rect = _snap_rect(rect)
self.set_rect(rect)
@@ -4187,18 +4210,24 @@ class TileGrid(Widget):
count = len(tiles_to_render)
cols = self.get_effective_column_count(rect.width, count)
rows = self.get_row_count(count, available_width=rect.width)
tile_h = (rect.height - (self._gap * (rows - 1))) / rows
uniform_tile_w = (rect.width - (self._gap * (cols - 1))) / cols if self._uniform_width else 0
if self.force_square:
uniform_tile_w = (rect.width - (self._gap * (cols - 1))) / cols
tile_h = uniform_tile_w
else:
if self._tile_height is not None:
tile_h = self._tile_height
else:
tile_h = (rect.height - (self._gap * (rows - 1))) / rows
uniform_tile_w = (rect.width - (self._gap * (cols - 1))) / cols if self._uniform_width else 0
tile_idx = 0
for r in range(rows):
remaining = count - tile_idx
if remaining <= 0:
break
items_in_row = min(cols, remaining)
if self._uniform_width:
if self.force_square or self._uniform_width:
row_tile_w = uniform_tile_w
row_width = (row_tile_w * items_in_row) + (self._gap * (items_in_row - 1))
row_x = rect.x + (rect.width - row_width) / 2
row_x = rect.x
else:
row_tile_w = (rect.width - (self._gap * (items_in_row - 1))) / items_in_row
row_x = rect.x
@@ -239,18 +239,9 @@ class SteeringManagerView(PanelManagerView):
return toggles
def _rebuild_toggle_grid(self):
self._toggle_grid.clear()
for td in self._build_toggle_defs():
self._toggle_grid.add_tile(ToggleTile(
title=td["title"],
get_state=td["get"],
set_state=td["set"],
bg_color=PANEL_STYLE.accent,
desc=td.get("subtitle", ""),
is_enabled=td.get("enabled", True),
disabled_label=td.get("disabled_label", ""),
show_led=True,
))
self._page_grid = self._toggle_grid
defs = self._build_toggle_defs()
self._set_toggle_pages([defs[i:i+6] for i in range(0, len(defs), 6)])
def _measure_content_height(self, width: float) -> float:
sections = self._build_left_sections()
@@ -258,19 +249,15 @@ class SteeringManagerView(PanelManagerView):
tiles_height = 0.0
if self._toggle_grid.tiles:
N = len(self._toggle_grid.tiles)
gap = self._toggle_grid.gap
if self._uses_two_columns(width):
cols = 2
tile_rows = (N + cols - 1) // cols
tile_gaps = gap * (tile_rows - 1) if tile_rows > 0 else 0
tiles_content_h = tile_rows * 130 + tile_gaps
tiles_height = self._section_block_height(tiles_content_h + 24)
self._toggle_grid._columns = 2
column_w = self._column_width(width)
col_w = (column_w - 24 - 12) / 2
grid_h = 2 * col_w + 12
tiles_height = self._section_block_height(grid_h + 24)
else:
cols = 3
tile_rows = (N + cols - 1) // cols
tile_gaps = gap * (tile_rows - 1) if tile_rows > 0 else 0
tiles_content_h = tile_rows * 130 + tile_gaps
self._toggle_grid._columns = 3
tiles_content_h = self._toggle_grid.measure_height(width - 24)
tiles_height = SECTION_GAP + self._section_block_height(tiles_content_h + 24)
if self._uses_two_columns(width):
@@ -315,21 +302,13 @@ class SteeringManagerView(PanelManagerView):
tr("Toggles"), style=PANEL_STYLE)
right_container_y = y + SECTION_HEADER_HEIGHT + SECTION_HEADER_GAP
N = len(self._toggle_grid.tiles)
cols = 2
self._toggle_grid._columns = cols
gap = self._toggle_grid.gap
tile_rows = (N + cols - 1) // cols
tile_gaps = gap * (tile_rows - 1) if tile_rows > 0 else 0
tiles_content_h = tile_rows * 130 + tile_gaps
needed_height = tiles_content_h + 24
viewport_remaining = (self._scroll_rect.y + self._scroll_rect.height) - right_container_y
container_h = max(needed_height, viewport_remaining)
self._toggle_grid._columns = 2
col_w = (column_w - 24 - 12) / 2
grid_h = 2 * col_w + 12
container_h = grid_h + 24
draw_list_group_shell(rl.Rectangle(rx, right_container_y, column_w, container_h), style=PANEL_STYLE)
self._toggle_grid.set_parent_rect(self._scroll_rect)
self._toggle_grid.render(rl.Rectangle(rx + 12, right_container_y + 12, column_w - 24, container_h - 24))
self._render_page_grid(self._toggle_grid, rl.Rectangle(rx + 12, right_container_y + 12, column_w - 24, grid_h))
else:
curr_y = y
for section in sections:
@@ -355,18 +334,12 @@ class SteeringManagerView(PanelManagerView):
tr("Toggles"), style=PANEL_STYLE)
curr_y += SECTION_HEADER_HEIGHT + SECTION_HEADER_GAP
N = len(self._toggle_grid.tiles)
cols = 3
self._toggle_grid._columns = cols
gap = self._toggle_grid.gap
self._toggle_grid._columns = 3
avail_w = width - 24
tile_rows = (N + cols - 1) // cols
tile_gaps = gap * (tile_rows - 1) if tile_rows > 0 else 0
tiles_content_h = tile_rows * 130 + tile_gaps
tiles_content_h = self._toggle_grid.measure_height(avail_w)
draw_list_group_shell(rl.Rectangle(x, curr_y, width, tiles_content_h + 24), style=PANEL_STYLE)
self._toggle_grid.set_parent_rect(self._scroll_rect)
self._toggle_grid.render(rl.Rectangle(x + 12, curr_y + 12, avail_w, tiles_content_h))
self._render_page_grid(self._toggle_grid, rl.Rectangle(x + 12, curr_y + 12, avail_w, tiles_content_h))
def _draw_row(self, rect: rl.Rectangle, row: dict, is_last: bool):
target_id = row["target_id"]
@@ -74,19 +74,22 @@ class SoundsManagerView(PanelManagerView):
def _init_toggles(self):
self._toggle_grid = TileGrid(columns=2, padding=12)
self._child(self._toggle_grid)
self._page_grid = self._toggle_grid
toggle_defs = []
for key in self._controller.CUSTOM_ALERTS_KEYS:
info = self._controller.ALERT_INFO[key]
tile = ToggleTile(
title=tr(info["title"]),
get_state=lambda k=key: self._controller._params.get_bool(k),
set_state=lambda state, k=key: self._controller._params.put_bool(k, state),
bg_color=PANEL_STYLE.accent,
desc=tr(info.get("subtitle", "")),
is_enabled=info.get("is_enabled"),
disabled_label=tr(info.get("disabled_label", "")) if info.get("disabled_label") else "",
)
self._toggle_grid.add_tile(tile)
self._child(self._toggle_grid)
toggle_defs.append({
"title": tr(info["title"]),
"subtitle": tr(info.get("subtitle", "")),
"get": lambda k=key: self._controller._params.get_bool(k),
"set": lambda state, k=key: self._controller._params.put_bool(k, state),
"is_enabled": info.get("is_enabled"),
"disabled_label": tr(info.get("disabled_label", "")) if info.get("disabled_label") else "",
})
self._set_toggle_pages([toggle_defs[i:i+6] for i in range(0, len(toggle_defs), 6)])
def _set_active_adjustor(self, key: str, active: bool):
if active:
@@ -195,8 +198,7 @@ class SoundsManagerView(PanelManagerView):
)
def _handle_mouse_press(self, mouse_pos: MousePos):
self._pressed_target = self._target_at(mouse_pos)
self._can_click = True
super()._handle_mouse_press(mouse_pos)
for adjustor in self._adjustor_rows.values():
adjustor._handle_mouse_press(mouse_pos)
self._toggle_grid._handle_mouse_press(mouse_pos)
@@ -205,17 +207,10 @@ class SoundsManagerView(PanelManagerView):
for adjustor in self._adjustor_rows.values():
adjustor._handle_mouse_release(mouse_pos)
self._toggle_grid._handle_mouse_release(mouse_pos)
target = self._target_at(mouse_pos) if self._scroll_panel.is_touch_valid() else None
if self._pressed_target is not None and self._pressed_target == target and self._can_click:
self._activate_target(target)
self._pressed_target = None
self._can_click = True
super()._handle_mouse_release(mouse_pos)
def _handle_mouse_event(self, mouse_event: MouseEvent):
if not self._scroll_panel.is_touch_valid():
self._can_click = False
return
super()._handle_mouse_event(mouse_event)
for adjustor in self._adjustor_rows.values():
adjustor._handle_mouse_event(mouse_event)
self._toggle_grid._handle_mouse_event(mouse_event)
@@ -251,13 +246,13 @@ class SoundsManagerView(PanelManagerView):
cd_h = self._adjustor_rows[self._controller.COOLDOWN_KEY].measure_height(col_width)
tile_rows = self._toggle_grid.get_row_count(len(self._toggle_grid.tiles), available_width=col_width)
tile_gaps = self._toggle_grid.get_internal_gap_height(len(self._toggle_grid.tiles), available_width=col_width)
tiles_content_h = tile_rows * 130 + tile_gaps
tiles_content_h = self._toggle_grid.measure_height(col_width - 24)
# col_w is the width of each tile in the 2-column grid
col_w = (col_width - 24 - 12) / 2
# Standardize right-side grid height: 2x3 rectangular layout (same container as before)
self._tile_grid_h = 2 * col_w + 12
self._tile_grid_h = max(tiles_content_h, left_column_total_h - cd_h - SECTION_GAP - (GROUP_HEADER_HEIGHT + GROUP_HEADER_GAP) - 16.0)
right_column_total_h = cd_h + SECTION_GAP + (GROUP_HEADER_HEIGHT + GROUP_HEADER_GAP) + (self._tile_grid_h + 16.0)
right_column_total_h = cd_h + SECTION_GAP + (GROUP_HEADER_HEIGHT + GROUP_HEADER_GAP) + (self._tile_grid_h + 24.0)
section_overhead = SECTION_HEADER_HEIGHT + SECTION_HEADER_GAP
min_h = self._scroll_rect.height if self._scroll_rect else 0.0
@@ -349,10 +344,9 @@ class SoundsManagerView(PanelManagerView):
current_y = self._draw_group_header(x + 24, current_y, width - 48, tr("CUSTOM ALERTS"))
draw_list_group_shell(rl.Rectangle(x, current_y, width, self._tile_grid_h + 16), style=PANEL_STYLE)
draw_list_group_shell(rl.Rectangle(x, current_y, width, self._tile_grid_h + 24), style=PANEL_STYLE)
self._toggle_grid.set_parent_rect(self._scroll_rect)
self._toggle_grid.render(rl.Rectangle(x + 12, current_y + 8, width - 24, self._tile_grid_h))
self._render_page_grid(self._toggle_grid, rl.Rectangle(x + 12, current_y + 12, width - 24, self._tile_grid_h))
class StarPilotSoundsLayout(_SettingsPage):
@@ -311,7 +311,7 @@ class SystemSettingsManagerView(PanelManagerView):
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._set_toggle_pages([self._toggle_defs[i:i+6] for i in range(0, len(self._toggle_defs), 6)])
self._drive_mode_control = self._child(
AetherSegmentedControl(
@@ -477,16 +477,17 @@ class SystemSettingsManagerView(PanelManagerView):
display_h = self._section_block_height(self._slider_section_height(self._display_slider_keys, width))
power_h = self._section_block_height(self._slider_section_height(self._power_slider_keys, width))
tile_rows = self._connectivity_tile_grid.get_row_count(
len(self._connectivity_tile_grid.tiles), available_width=width)
tile_gaps = self._connectivity_tile_grid.get_internal_gap_height(
len(self._connectivity_tile_grid.tiles), available_width=width)
tiles_content_h = tile_rows * 130 + tile_gaps
if self._uses_two_columns(width):
column_w = self._column_width(width)
col_w = (column_w - 24 - 12) / 2
grid_h = 2 * col_w + 12
container_h = grid_h + 24
right_h = self._section_block_height(container_h)
left_h = display_h + SECTION_GAP + power_h
min_h = self._scroll_rect.height if self._scroll_rect else 0.0
return max(min_h, display_h + SECTION_GAP + power_h)
return max(min_h, left_h, right_h)
else:
tiles_content_h = self._connectivity_tile_grid.measure_height(width - 24)
return self._stacked_section_height([display_h, power_h, tiles_content_h + 24])
def _slider_section_height(self, keys: list[str], width: float) -> float:
@@ -513,10 +514,12 @@ class SystemSettingsManagerView(PanelManagerView):
display_bottom = self._draw_slider_section(y, x, column_w, tr("Display"), self._display_slider_keys)
power_y = display_bottom + section_gap
power_bottom = self._draw_slider_section(power_y, x, column_w, tr("Power"), self._power_slider_keys)
self._draw_slider_section(power_y, x, column_w, tr("Power"), self._power_slider_keys)
container_top = y + SECTION_HEADER_HEIGHT + SECTION_HEADER_GAP
container_height = power_bottom - container_top
col_w = (column_w - 24 - 12) / 2
grid_h = 2 * col_w + 12
container_height = grid_h + 24
self._draw_connectivity_tiles_column(container_top, x + column_w + self.COLUMN_GAP, column_w, container_height)
return
y = self._draw_slider_section(y, x, width, tr("Display"), self._display_slider_keys)
@@ -530,9 +533,7 @@ class SystemSettingsManagerView(PanelManagerView):
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)
tile_gaps = self._connectivity_tile_grid.get_internal_gap_height(len(self._connectivity_tile_grid.tiles), available_width=width)
tiles_content_h = tile_rows * 130 + tile_gaps
tiles_content_h = self._connectivity_tile_grid.measure_height(width - 24)
draw_list_group_shell(rl.Rectangle(x, y, width, tiles_content_h + 24), style=PANEL_STYLE)
self._render_page_grid(self._connectivity_tile_grid, rl.Rectangle(x + 12, y + 12, width - 24, tiles_content_h))
@@ -209,20 +209,9 @@ class VehicleSettingsManagerView(PanelManagerView):
return toggles
def _rebuild_toggle_grid(self):
self._toggle_grid.clear()
toggles = self._build_driving_toggles()
self._toggle_grid._columns = len(toggles)
for toggle_def in toggles:
tile = ToggleTile(
title=toggle_def["title"],
get_state=toggle_def["get_state"],
set_state=toggle_def["set_state"],
bg_color=PANEL_STYLE.accent,
desc=toggle_def.get("subtitle", ""),
is_enabled=toggle_def.get("is_enabled"),
disabled_label=toggle_def.get("disabled_label", ""),
)
self._toggle_grid.add_tile(tile)
self._page_grid = self._toggle_grid
defs = self._build_driving_toggles()
self._set_toggle_pages([defs[i:i+6] for i in range(0, len(defs), 6)])
def _check_rebuild_grid(self):
current_make = self._controller._get_display_make()
@@ -335,20 +324,16 @@ class VehicleSettingsManagerView(PanelManagerView):
# Right Column/Features height
tiles_height = 0.0
if self._toggle_grid.tiles:
N = len(self._toggle_grid.tiles)
gap = self._toggle_grid.gap
if self._uses_two_columns(width):
cols = 2
tile_rows = (N + cols - 1) // cols
tile_gaps = gap * (tile_rows - 1) if tile_rows > 0 else 0
tiles_content_h = tile_rows * 130 + tile_gaps
tiles_height = self._section_block_height(tiles_content_h + 24)
self._toggle_grid._columns = 2
column_w = self._column_width(width)
col_w = (column_w - 24 - 12) / 2
grid_h = 2 * col_w + 12
tiles_height = self._section_block_height(grid_h + 24)
else:
self._toggle_grid._columns = 3
avail_w = width - 24
cols = 3
tile_rows = (N + cols - 1) // cols
tile_gaps = gap * (tile_rows - 1) if tile_rows > 0 else 0
tiles_content_h = tile_rows * 130 + tile_gaps
tiles_content_h = self._toggle_grid.measure_height(avail_w)
tiles_height = SECTION_GAP + self._section_block_height(tiles_content_h + 24)
if self._uses_two_columns(width):
@@ -414,21 +399,13 @@ class VehicleSettingsManagerView(PanelManagerView):
draw_section_header(rl.Rectangle(rx, y, column_w, SECTION_HEADER_HEIGHT), tr("Features"), style=PANEL_STYLE)
right_container_y = y + SECTION_HEADER_HEIGHT + SECTION_HEADER_GAP
N = len(self._toggle_grid.tiles)
cols = 2
self._toggle_grid._columns = cols
gap = self._toggle_grid.gap
tile_rows = (N + cols - 1) // cols
tile_gaps = gap * (tile_rows - 1) if tile_rows > 0 else 0
tiles_content_h = tile_rows * 130 + tile_gaps
needed_height = tiles_content_h + 24
viewport_remaining = (self._scroll_rect.y + self._scroll_rect.height) - right_container_y
container_h = max(needed_height, viewport_remaining)
self._toggle_grid._columns = 2
col_w = (column_w - 24 - 12) / 2
grid_h = 2 * col_w + 12
container_h = grid_h + 24
draw_list_group_shell(rl.Rectangle(rx, right_container_y, column_w, container_h), style=PANEL_STYLE)
self._toggle_grid.set_parent_rect(self._scroll_rect)
self._toggle_grid.render(rl.Rectangle(rx + 12, right_container_y + 12, column_w - 24, container_h - 24))
self._render_page_grid(self._toggle_grid, rl.Rectangle(rx + 12, right_container_y + 12, column_w - 24, grid_h))
else:
# Single Column Stacked Layout
draw_section_header(rl.Rectangle(x, y, width, SECTION_HEADER_HEIGHT), tr("Vehicle Identity"), style=PANEL_STYLE)
@@ -455,18 +432,12 @@ class VehicleSettingsManagerView(PanelManagerView):
draw_section_header(rl.Rectangle(x, y, width, SECTION_HEADER_HEIGHT), tr("Features"), style=PANEL_STYLE)
y += SECTION_HEADER_HEIGHT + SECTION_HEADER_GAP
N = len(self._toggle_grid.tiles)
cols = 3
self._toggle_grid._columns = cols
gap = self._toggle_grid.gap
self._toggle_grid._columns = 3
avail_w = width - 24
tile_rows = (N + cols - 1) // cols
tile_gaps = gap * (tile_rows - 1) if tile_rows > 0 else 0
tiles_content_h = tile_rows * 130 + tile_gaps
tiles_content_h = self._toggle_grid.measure_height(avail_w)
draw_list_group_shell(rl.Rectangle(x, y, width, tiles_content_h + 24), style=PANEL_STYLE)
self._toggle_grid.set_parent_rect(self._scroll_rect)
self._toggle_grid.render(rl.Rectangle(x + 12, y + 12, avail_w, tiles_content_h))
self._render_page_grid(self._toggle_grid, rl.Rectangle(x + 12, y + 12, avail_w, tiles_content_h))
def _draw_row(self, rect: rl.Rectangle, row: dict, is_last: bool):
target_id = row["target_id"]
+123
View File
@@ -396,6 +396,129 @@ class TestAethergridContracts(unittest.TestCase):
self.assertEqual(dialog._current_val, 6)
self.assertEqual(captured_changes, [6])
def test_tile_grid_measure_height_with_explicit_tile_height(self):
mod = _import_aethergrid()
grid = mod.TileGrid(columns=2, padding=10, tile_height=140)
for _ in range(5):
grid.add_tile(RenderSpy())
h = grid.measure_height(500)
self.assertEqual(h, 740)
def test_tile_grid_measure_height_default_fallback(self):
mod = _import_aethergrid()
grid = mod.TileGrid(columns=2, padding=10, tile_height=None)
for _ in range(5):
grid.add_tile(RenderSpy())
h = grid.measure_height(500)
self.assertEqual(h, 690)
def test_tile_grid_render_top_left_aligned_with_tile_height(self):
mod = _import_aethergrid()
grid = mod.TileGrid(columns=2, padding=10, tile_height=140)
spy = RenderSpy()
grid.add_tile(spy)
grid.render(mod.rl.Rectangle(0, 50, 500, 300))
self.assertTrue(spy.rects)
self.assertEqual(spy.rects[0].y, 50)
self.assertEqual(spy.rects[0].x, 0)
def test_disabled_tiles_hud_mode_rendering(self):
mod = _import_aethergrid()
# ToggleTile disabled, show_led=True
toggle = mod.ToggleTile(
title="Test Loud",
get_state=lambda: True,
set_state=lambda s: None,
is_enabled=lambda: False,
show_led=True
)
# Spy on _render_hud_background
orig_hud_bg = toggle._render_hud_background
spy_called = []
def spy_hud_bg(*a, **k):
spy_called.append("toggle")
return orig_hud_bg(*a, **k)
toggle._render_hud_background = spy_hud_bg
toggle.render(mod.rl.Rectangle(0, 0, 150, 130))
self.assertIn("toggle", spy_called)
# ValueTile disabled
value_tile = mod.ValueTile(
title="Test Value",
get_value=lambda: "Off",
on_click=lambda: None,
is_enabled=lambda: False
)
orig_value_hud_bg = value_tile._render_hud_background
def spy_value_hud_bg(*a, **k):
spy_called.append("value")
return orig_value_hud_bg(*a, **k)
value_tile._render_hud_background = spy_value_hud_bg
value_tile.render(mod.rl.Rectangle(0, 0, 150, 130))
self.assertIn("value", spy_called)
# SliderTile disabled
slider_tile = mod.SliderTile(
title="Test Slider",
get_value=lambda: 50.0,
set_value=lambda v: None,
min_val=0.0,
max_val=100.0,
step=1.0,
is_enabled=lambda: False
)
orig_slider_hud_bg = slider_tile._render_hud_background
def spy_slider_hud_bg(*a, **k):
spy_called.append("slider")
return orig_slider_hud_bg(*a, **k)
slider_tile._render_hud_background = spy_slider_hud_bg
slider_tile.render(mod.rl.Rectangle(0, 0, 150, 130))
self.assertIn("slider", spy_called)
def test_tile_grid_force_square(self):
mod = _import_aethergrid()
grid = mod.TileGrid(columns=2, padding=10, min_tile_width=100, force_square=True)
spies = [RenderSpy() for _ in range(5)]
for spy in spies:
grid.add_tile(spy)
# col_w = (500 - 10) / 2 = 245
# rows = 3, gap_h = 2 * 10 = 20
# expected height = 3 * 245 + 20 = 755
self.assertEqual(grid.measure_height(500), 755)
grid.render(mod.rl.Rectangle(0, 0, 500, 300))
self.assertTrue(spies[0].rects)
self.assertEqual(spies[0].rects[0].width, 245)
self.assertEqual(spies[0].rects[0].height, 245)
def test_tile_grid_column_preservation_with_single_tile(self):
mod = _import_aethergrid()
grid = mod.TileGrid(columns=2, padding=10, min_tile_width=100, force_square=True)
spy = RenderSpy()
grid.add_tile(spy)
# With only 1 tile in a 2-column layout, it should still calculate the tile size based on 2 columns
# col_w = (500 - 10) / 2 = 245
grid.render(mod.rl.Rectangle(0, 0, 500, 300))
self.assertTrue(spy.rects)
self.assertEqual(spy.rects[0].width, 245)
self.assertEqual(spy.rects[0].height, 245)
def test_hud_background_glow_overflow_protection(self):
mod = _import_aethergrid()
tile = mod.ToggleTile("Test", lambda: True, lambda v: None)
# Test with extreme glow values (negative and large positive) to verify no OverflowError occurs
try:
tile._render_hud_background(mod.rl.Rectangle(0, 0, 150, 130), mod.rl.Color(255, 0, 0, 255), glow=5.0)
tile._render_hud_background(mod.rl.Rectangle(0, 0, 150, 130), mod.rl.Color(255, 0, 0, 255), glow=-2.0)
except OverflowError:
self.fail("OverflowError raised with extreme glow values")
if __name__ == "__main__":
unittest.main()