feat: add scrolling with configurable scrolloff

This commit is contained in:
lohhiiccc 2026-07-02 20:06:30 +02:00
parent a144262f8c
commit 87c9a6f641
2 changed files with 43 additions and 5 deletions

View file

@ -12,6 +12,8 @@ if TYPE_CHECKING:
class UI: class UI:
def __init__(self, app: App): def __init__(self, app: App):
self._app = app self._app = app
self._scroll_top = 0
self._scroll_path = None
HEADER_ROWS = 1 HEADER_ROWS = 1
@ -31,14 +33,38 @@ class UI:
items = navigator.items items = navigator.items
available = max(0, height - self.HEADER_ROWS) available = max(0, height - self.HEADER_ROWS)
for index in range(min(available, len(items))): top = self._update_scroll(navigator, available)
item = items[index] for offset, item in enumerate(items[top:top + available]):
self._render_item(stdscr, item, index, index + self.HEADER_ROWS, width) index = top + offset
self._render_item(stdscr, item, index, offset + self.HEADER_ROWS, width)
prompt = self.app.prompt prompt = self.app.prompt
if prompt: if prompt:
self._render_modal(stdscr, prompt, height, width) self._render_modal(stdscr, prompt, height, width)
def _update_scroll(self, navigator, available: int) -> int:
current_path = navigator.history[-1]
if current_path != self._scroll_path:
self._scroll_path = current_path
self._scroll_top = 0
total = len(navigator.items)
if available <= 0 or total <= available:
self._scroll_top = 0
return self._scroll_top
offset = min(self.app.config.scroll_offset, (available - 1) // 2)
pos = navigator.cursor.pos
max_top = total - available
if pos < self._scroll_top + offset:
self._scroll_top = pos - offset
elif pos > self._scroll_top + available - 1 - offset:
self._scroll_top = pos - available + 1 + offset
self._scroll_top = max(0, min(self._scroll_top, max_top))
return self._scroll_top
MODAL_MAX_WIDTH = 60 MODAL_MAX_WIDTH = 60
MODAL_PADDING = 2 MODAL_PADDING = 2

View file

@ -20,15 +20,17 @@ DEFAULT_KEYBINDS: dict[str, list[str]] = {
} }
DEFAULT_CURSOR_RESET = "top" DEFAULT_CURSOR_RESET = "top"
DEFAULT_SCROLL_OFFSET = 4
class Config: class Config:
def __init__(self, keybinds: dict[str, list[str]], cursor_reset: str, def __init__(self, keybinds: dict[str, list[str]], cursor_reset: str,
sort_mode: str, theme: Theme) -> None: sort_mode: str, theme: Theme, scroll_offset: int) -> None:
self._keybinds = keybinds self._keybinds = keybinds
self._cursor_reset = cursor_reset self._cursor_reset = cursor_reset
self._sort_mode = sort_mode self._sort_mode = sort_mode
self._theme = theme self._theme = theme
self._scroll_offset = scroll_offset
self._key_to_action: dict[str, str] = { self._key_to_action: dict[str, str] = {
key: action key: action
for action, keys in keybinds.items() for action, keys in keybinds.items()
@ -57,7 +59,13 @@ class Config:
f"falling back to {Sort.DEFAULT_MODE!r}", file=sys.stderr) f"falling back to {Sort.DEFAULT_MODE!r}", file=sys.stderr)
sort_mode = Sort.DEFAULT_MODE sort_mode = Sort.DEFAULT_MODE
theme = Theme.load(data) theme = Theme.load(data)
return cls(keybinds, cursor_reset, sort_mode, theme) scroll_offset = data.get("scroll", {}).get("offset",
DEFAULT_SCROLL_OFFSET)
if not isinstance(scroll_offset, int) or scroll_offset < 0:
print(f"pyexplorer: invalid scroll offset {scroll_offset!r}, "
f"falling back to {DEFAULT_SCROLL_OFFSET}", file=sys.stderr)
scroll_offset = DEFAULT_SCROLL_OFFSET
return cls(keybinds, cursor_reset, sort_mode, theme, scroll_offset)
@staticmethod @staticmethod
def _default_path() -> Path: def _default_path() -> Path:
@ -81,3 +89,7 @@ class Config:
@property @property
def theme(self) -> Theme: def theme(self) -> Theme:
return self._theme return self._theme
@property
def scroll_offset(self) -> int:
return self._scroll_offset