68 lines
2.4 KiB
Python
68 lines
2.4 KiB
Python
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')
|