55 lines
1.9 KiB
Python
55 lines
1.9 KiB
Python
from __future__ import annotations
|
||
|
||
from typing import List, Tuple
|
||
|
||
from PIL import ImageDraw
|
||
|
||
from .base import SelectableList
|
||
from .item import ListItem
|
||
|
||
|
||
class ListView(SelectableList):
|
||
"""Compact scrolling list – shows up to *max_visible* items at once.
|
||
|
||
The visible window is kept centred on the selected item and scrolls
|
||
as the selection moves.
|
||
|
||
``font_size`` is forwarded to :pymeth:`ListItem.render` so the
|
||
underlying render function can use it (e.g. for PIL ``draw.text``).
|
||
Pass ``None`` to let render functions use their own default.
|
||
"""
|
||
|
||
def __init__(self, *args, font_size: int | None = None, **kwargs):
|
||
super().__init__(*args, **kwargs)
|
||
self.font_size = font_size
|
||
|
||
def _get_visible_window(self) -> Tuple[List[ListItem], int]:
|
||
"""Return (visible_items, start_offset)."""
|
||
total = len(self._items)
|
||
if total <= self.max_visible:
|
||
return self._items, 0
|
||
|
||
start = max(0, self.selected_index - self.max_visible // 2)
|
||
start = min(start, total - self.max_visible)
|
||
end = start + self.max_visible
|
||
return self._items[start:end], start
|
||
|
||
def render(self, draw: ImageDraw.ImageDraw, y_start: int) -> None:
|
||
if not self._items:
|
||
return
|
||
|
||
visible_items, start = self._get_visible_window()
|
||
|
||
fs = self.font_size or 10
|
||
kwargs = {} if self.font_size is None else {'font_size': self.font_size}
|
||
|
||
for idx, item in enumerate(visible_items):
|
||
actual_idx = start + idx
|
||
y_pos = y_start + idx * self.item_height
|
||
|
||
if actual_idx == self.selected_index:
|
||
self._draw_highlight(draw, self.x_offset, y_pos + fs // 2, font_size=fs)
|
||
item.render(draw, (self.x_offset, y_pos), fill='white', **kwargs)
|
||
else:
|
||
item.render(draw, (self.x_offset, y_pos), fill='black', **kwargs)
|