from __future__ import annotations from PIL import ImageDraw from .base import SelectableList class CarouselView(SelectableList): """Single-item display – shows the selected item centred and larger. Position indicator dots at the bottom communicate how many items exist and which one is currently selected. Uses :pymethod:`ListItem.render_large` so callers can optionally provide a more detailed rendering for carousel mode. ``large_font_size`` controls the font size passed to :pymethod:`ListItem.render_large` (default 30, roughly 3× PIL default). ``center_x`` sets the horizontal centre used when drawing the item text (default 84, the midpoint of a 168 px wide display). PIL's ``anchor='mm'`` is forwarded so the text is centred on that x coordinate. ``render_height`` is the total vertical space (in pixels) allocated to the carousel; dots are placed at the very bottom of this region. Defaults to ``max_visible * item_height``. """ def __init__(self, *args, large_font_size: int = 30, center_x: int = 84, render_height: int | None = None, **kwargs): super().__init__(*args, **kwargs) self.large_font_size = large_font_size self.center_x = center_x self.render_height = render_height def render(self, draw: ImageDraw.ImageDraw, y_start: int) -> None: if not self._items: return item = self._items[self.selected_index] height = self.render_height or (self.max_visible * self.item_height) y_center = y_start + height // 2 # Render selected item centred (large variant, no highlight) item.render_large( draw, (self.center_x, y_center), fill='black', font_size=self.large_font_size, anchor='mm', ) total = len(self._items) # Position indicator dots at the top of the allocated area if total > 1: indicator_y = y_start dot_spacing = min(8, max(4, 80 // max(total - 1, 1))) total_width = (total - 1) * dot_spacing start_x = self.center_x - total_width // 2 for i in range(total): x = start_x + i * dot_spacing r = 2 if i == self.selected_index else 1 draw.circle((x, indicator_y), r, fill='black')