List view metadata columns and drag-to-resize everywhere

- New optional list-view columns: Description, Size (now includes
  directories), Modified, Owner, Created (via statx birth time), Content
  (item/line count or image dimensions), Access (rwx)
- Drag-to-resize: list view column headers, sidebar sections (Bookmarks/
  Devices/Filetree), Preview/Tags split, and the sidebar/preview side
  panels themselves — all commit to Config on release, not per drag frame
- Column resizing mutates existing row cells live instead of rebuilding,
  with metadata cached per (file, mtime) — fixes lag in large folders
- Fix list view background click (rows were full ListTile width with no
  empty space to click); bookmark selected-background now covers the
  triple-dot menu too; batch rename/new-file dialogs pre-select their
  default text instead of just pre-filling it
- Toolbar scrolls instead of clipping when buttons don't fit; toolbar and
  breadcrumbs are independently toggleable
- Add a "Server" bookmark icon

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
sarahandClaude Sonnet 5 committed 2026-09-26 17:22:55 +02:00
1 parent a5f5c5ca79
commit 31b6c554e6
6 files changed
+699 -89

No files matched your search

+62
View File
@@ -9,6 +9,7 @@ import shutil
import subprocess
from dataclasses import asdict, dataclass, field
from pathlib import Path
from typing import Callable, Optional
import flet as ft
import toml
@@ -170,6 +171,27 @@ class AppConfig:
ShowDotfiles: bool = True
ViewMode: str = "list" # "list" or "grid"
# List view: which metadata columns show, beyond the always-present Name.
ListShowDescription: bool = False
ListShowSize: bool = True
ListShowModified: bool = True
ListShowOwner: bool = False
ListShowCreated: bool = False
ListShowContent: bool = False
ListShowAccess: bool = False
# List view column widths — drag the header dividers to set these, or
# type a number in Options; both write here.
ListWidthName: int = 220
ListWidthDescription: int = 90
ListWidthSize: int = 80
ListWidthModified: int = 130
ListWidthOwner: int = 90
ListWidthCreated: int = 130
ListWidthContent: int = 90
ListWidthAccess: int = 60
# Grid view.
GridIconSize: int = 64
GridNameBelowIcon: bool = True
# Batch-rename pattern for multi-select rename, syntax modeled on
# SaraSortd's own placeholder characters (see ~/Applications/SaraSortd's
@@ -403,6 +425,46 @@ def ApplyHeightBounds(Content: ft.Control, MinHeight: int, MaxHeight: int) -> ft
return Content
def ResizeHandle(
Axis: str,
Length: Optional[int],
OnDrag: Callable[[float], None],
OnDragEnd: Callable[[], None],
) -> ft.GestureDetector:
"""A thin draggable divider, shared by the file list's column headers,
the sidebar/preview section splits, and the sidebar/preview *panel*
widths. "horizontal" resizes side-by-side things (drag left/right, e.g.
a column or a whole side panel) and needs Length = the row's height, not
just 6px wide with an unset (collapses to zero) height; "vertical"
resizes stacked things (drag up/down, e.g. Bookmarks vs Devices) and
needs Length = the panel's width, for the same reason on the other axis.
Pass Length=None instead when the immediate parent Row/Column already
stretches its children on that cross axis (confirmed, not assumed —
Flet doesn't do this by default) — then an explicit value would fight
that stretch instead of matching it. OnDrag fires with the incremental
delta on every move — cheap for a single header cell or a sidebar
section, but callers with many redraws to do (the file list's rows)
should treat it as a live *preview* and do the expensive rebuild in
OnDragEnd instead, which fires once on release; that's also where
Config gets saved, so a drag isn't disk I/O on every pixel of movement."""
IsHorizontal = Axis == "horizontal"
Handle = ft.GestureDetector(
content=ft.Container(
width=6 if IsHorizontal else Length,
height=Length if IsHorizontal else 6,
bgcolor=Palette.Surface1,
),
mouse_cursor=ft.MouseCursor.RESIZE_COLUMN if IsHorizontal else ft.MouseCursor.RESIZE_ROW,
)
if IsHorizontal:
Handle.on_horizontal_drag_update = lambda e: OnDrag(e.primary_delta or 0)
Handle.on_horizontal_drag_end = lambda e: OnDragEnd()
else:
Handle.on_vertical_drag_update = lambda e: OnDrag(e.primary_delta or 0)
Handle.on_vertical_drag_end = lambda e: OnDragEnd()
return Handle
# The group tag shared by every Draggable source and DragTarget in the app,
# so bookmarks/filetree only accept drops that actually originate from
# SaraFem's own file list (not, say, a Chip or some future draggable widget).