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

+67 -30
View File
@@ -9,15 +9,15 @@ from typing import Callable, Optional
import flet as ft
from Bookmarks import IconChoices, LoadBookmarks, SaveBookmarks
from Bookmarks import IconChoices, IconLabelOverrides, LoadBookmarks, SaveBookmarks
from Core import (
AppConfig,
ApplyHeightBounds,
ChipButtonStyle,
DragGroup,
MoveIntoDirectory,
NotificationStyle,
Palette,
ResizeHandle,
)
from Devices import DevicesPanel
from Filetree import Filetree
@@ -40,6 +40,11 @@ class Sidebar(ft.Container):
Sections: list[ft.Control] = []
SplitEvenly = Config.SplitBookmarksFiletreeEvenly and Config.ShowBookmarks and Config.ShowFiletree
# Section boxes a resize-drag can mutate .height on directly — only
# built (and only get a drag handle between them) outside SplitEvenly,
# which is a 50/50 flex split with no single fixed height to drag.
BookmarksBox: Optional[ft.Container] = None
DevicesBox: Optional[ft.Container] = None
if Config.ShowBookmarks:
self.BookmarksListView = ft.ReorderableListView(
@@ -52,21 +57,28 @@ class Sidebar(ft.Container):
self.BookmarksListView.expand = 1
Sections.append(self.BookmarksListView)
else:
Sections.append(
ApplyHeightBounds(self.BookmarksListView, Config.BookmarksMinHeight, Config.BookmarksMaxHeight)
BookmarksBox = ft.Container(
content=self.BookmarksListView,
height=Config.BookmarksMaxHeight or Config.BookmarksMinHeight or None,
)
Sections.append(BookmarksBox)
if Config.ShowBookmarks and (Config.ShowDevices or Config.ShowFiletree):
Sections.append(ft.Divider(height=1, color=Palette.Surface1))
if BookmarksBox is not None:
Sections.append(self._BuildSectionDivider(BookmarksBox, "BookmarksMaxHeight"))
else:
Sections.append(ft.Divider(height=1, color=Palette.Surface1))
if Config.ShowDevices:
self.DevicesView = DevicesPanel(OnNavigate=OnNavigate, Config=Config)
Sections.append(
ApplyHeightBounds(self.DevicesView, Config.DevicesMinHeight, Config.DevicesMaxHeight)
DevicesBox = ft.Container(
content=self.DevicesView,
height=Config.DevicesMaxHeight or Config.DevicesMinHeight or None,
)
Sections.append(DevicesBox)
if Config.ShowDevices and Config.ShowFiletree:
Sections.append(ft.Divider(height=1, color=Palette.Surface1))
Sections.append(self._BuildSectionDivider(DevicesBox, "DevicesMaxHeight"))
if Config.ShowFiletree:
self.FiletreeView = Filetree(
@@ -77,11 +89,9 @@ class Sidebar(ft.Container):
)
if SplitEvenly:
self.FiletreeView.expand = 1
Sections.append(self.FiletreeView)
else:
Sections.append(
ApplyHeightBounds(self.FiletreeView, Config.FiletreeMinHeight, Config.FiletreeMaxHeight)
)
self.FiletreeView.height = Config.FiletreeMaxHeight or Config.FiletreeMinHeight or None
Sections.append(self.FiletreeView)
BorderSide = ft.BorderSide(1, Palette.Surface1)
Border = ft.Border(left=BorderSide) if Config.SidebarSide == "right" else ft.Border(right=BorderSide)
@@ -94,6 +104,25 @@ class Sidebar(ft.Container):
border=Border,
)
DefaultSectionHeight = 150 # starting point for a drag when a section had no explicit height yet
def _BuildSectionDivider(self, Box: ft.Container, MaxHeightAttr: str) -> ft.Control:
"""A resize handle for the section above it (Box) — drags resize that
section, whatever's below just reflows into the space that leaves,
same "resize what's above the divider" convention as the file list's
column dividers resize the column to their left."""
def OnDrag(Delta: float) -> None:
Current = Box.height or self.DefaultSectionHeight
Box.height = max(40, Current + Delta)
Box.update()
def OnDragEnd() -> None:
setattr(self.Config, MaxHeightAttr, int(Box.height))
self.Config.Save()
return ResizeHandle("vertical", self.Config.SidebarWidth, OnDrag, OnDragEnd)
def _BuildBookmarkTiles(self) -> list[ft.Control]:
self.LocationTiles = {}
return [self._BuildBookmarkTile(B) for B in LoadBookmarks()]
@@ -117,6 +146,10 @@ class Sidebar(ft.Container):
# Not a ListTile: the drag-to-reorder handle needs to wrap everything
# except the triple-dot menu, which ListTile's fixed leading/title/
# trailing layout doesn't let us carve out — so this is a plain Row.
# The selected background lives on OuterContainer (wrapping the whole
# Row, triple-dot included) rather than just RowContent — it used to
# stop short of the triple-dot, leaving its area unstyled ("I think
# the background of the button should continue to the menu").
NameLabel = ft.Text(Bookmark.Name, size=14, color=Palette.Text)
RowContent = ft.Container(
content=ft.Row(
@@ -127,34 +160,35 @@ class Sidebar(ft.Container):
spacing=12,
),
padding=ft.Padding.symmetric(horizontal=16, vertical=10),
border_radius=4,
ink=True,
on_click=lambda e, p=Bookmark.Path: self.OnNavigate(p),
expand=True,
)
MenuButton = ft.PopupMenuButton(
icon=ft.Icons.MORE_VERT,
items=[
ft.PopupMenuItem(content="Edit", on_click=lambda e, b=Bookmark: self._ShowEditBookmarkDialog(b)),
],
)
OuterContainer = ft.Container(
content=ft.Row(
[ft.Container(ft.ReorderableDragHandle(content=RowContent), expand=True), MenuButton],
spacing=0,
),
border_radius=4,
)
def SetSelected(IsSelected: bool) -> None:
RowContent.bgcolor = Palette.Surface1 if IsSelected else None
OuterContainer.bgcolor = Palette.Surface1 if IsSelected else None
NameLabel.color = Palette.Mauve if IsSelected else Palette.Text
self.LocationTiles[Bookmark.Path] = SetSelected
Row = ft.Row(
[
ft.Container(ft.ReorderableDragHandle(content=RowContent), expand=True),
ft.PopupMenuButton(
icon=ft.Icons.MORE_VERT,
items=[
ft.PopupMenuItem(
content="Edit", on_click=lambda e, b=Bookmark: self._ShowEditBookmarkDialog(b)
),
],
),
],
spacing=0,
)
return ft.DragTarget(
group=DragGroup,
content=Row,
content=OuterContainer,
on_accept=lambda e, dest=Bookmark.Path: self._OnEntryDropped(e, dest),
)
@@ -178,7 +212,10 @@ class Sidebar(ft.Container):
IconDropdown = ft.Dropdown(
value=Bookmark.Icon,
label="Icon",
options=[ft.DropdownOption(key=Name, text=Name.split("_")[0].title()) for Name in IconChoices],
options=[
ft.DropdownOption(key=Name, text=IconLabelOverrides.get(Name, Name.split("_")[0].title()))
for Name in IconChoices
],
)
def DoSave(e=None) -> None: