mirror of
https://git.srhtnin.garden/sarah/SaraFem.git
synced 2026-10-13 18:18:11 +02:00
701 lines
31 KiB
Python
701 lines
31 KiB
Python
"""In-app preferences dialog (Features/Major: "Edit preferences in SaraFem").
|
|
|
|
Laid out as a category sidebar + detail panel (Features/Minor: "Make settings
|
|
look good... sidebar with areas, then a much bigger main box"), with the
|
|
left/right/top/bottom-style choices as icon-button pickers instead of text
|
|
dropdowns ("some way to choose layout in a more graphical... way")."""
|
|
|
|
from pathlib import Path
|
|
from typing import Callable
|
|
|
|
import flet as ft
|
|
|
|
from Core import (
|
|
AppConfig, AvailableThemes, ChipButtonStyle, FolderIconBand, InstalledFontFamilies, LoadSharedSettings, Palette,
|
|
SharedSettings,
|
|
)
|
|
from FolderIcons import FolderIconChoices, FolderIconLabelOverrides
|
|
|
|
# Settings Browser can already hot-apply live (dotfiles, breadcrumbs, preview, tags).
|
|
# The window title bar, sidebar (bookmarks/filetree) layout, theme, and SaraTag's
|
|
# dynamically-loaded module need a restart to take effect.
|
|
RestartRequiredNote = "Window title bar, Theme, Font and SaraTag settings need a restart to take effect."
|
|
|
|
DialogWidth = 700
|
|
DialogHeight = 520
|
|
CategoryListWidth = 150
|
|
|
|
|
|
def _MinMaxRow(Label: str, MinValue: int, MaxValue: int) -> tuple[ft.Row, ft.TextField, ft.TextField]:
|
|
MinField = ft.TextField(value=str(MinValue), label=f"{Label} min", keyboard_type=ft.KeyboardType.NUMBER, expand=True)
|
|
MaxField = ft.TextField(value=str(MaxValue), label=f"{Label} max", keyboard_type=ft.KeyboardType.NUMBER, expand=True)
|
|
return ft.Row([MinField, MaxField]), MinField, MaxField
|
|
|
|
|
|
def _SectionLabel(Text: str) -> ft.Text:
|
|
return ft.Text(Text, color=Palette.Overlay0, size=12)
|
|
|
|
|
|
def _UnselectedPickerStyle() -> ft.ButtonStyle:
|
|
return ft.ButtonStyle(color=Palette.Overlay0, shape=ft.RoundedRectangleBorder(radius=4))
|
|
|
|
|
|
def _OptionPicker(Options: list[tuple[str, str, str]], Initial: str) -> tuple[ft.Control, Callable[[], str]]:
|
|
"""A row of icon buttons standing in for a text dropdown — Options is a
|
|
list of (key, label, Icons-attribute-name). Returns (control, get_value)."""
|
|
State = {"value": Initial}
|
|
Buttons: dict[str, ft.OutlinedButton] = {}
|
|
|
|
def Select(Key: str) -> None:
|
|
State["value"] = Key
|
|
for OptionKey, Button in Buttons.items():
|
|
Button.style = ChipButtonStyle() if OptionKey == Key else _UnselectedPickerStyle()
|
|
Row.update()
|
|
|
|
for Key, Label, IconName in Options:
|
|
Buttons[Key] = ft.OutlinedButton(
|
|
content=Label,
|
|
icon=getattr(ft.Icons, IconName),
|
|
style=ChipButtonStyle() if Key == Initial else _UnselectedPickerStyle(),
|
|
on_click=lambda e, k=Key: Select(k),
|
|
)
|
|
|
|
Row = ft.Row(list(Buttons.values()), wrap=True)
|
|
return Row, lambda: State["value"]
|
|
|
|
|
|
def BuildSettingsDialog(
|
|
Config: AppConfig,
|
|
OnSave: Callable[[], None],
|
|
OnDismiss: Callable[[], None],
|
|
OnExport: Callable[[str], None],
|
|
OnImport: Callable[[str], None],
|
|
) -> ft.AlertDialog:
|
|
Themes = AvailableThemes()
|
|
Fonts = InstalledFontFamilies()
|
|
|
|
def ThemePicker(Value: str) -> ft.Dropdown:
|
|
return ft.Dropdown(
|
|
value=Value, label="Theme", options=[ft.DropdownOption(key=Name, text=Name) for Name in Themes],
|
|
)
|
|
|
|
def FontPicker(Value: str) -> ft.Dropdown:
|
|
# Installed fonts plus the bundled one; type to filter the long list.
|
|
Names = Fonts if Value in Fonts else [Value] + Fonts
|
|
return ft.Dropdown(
|
|
value=Value, label="Font", editable=True, enable_filter=True, menu_height=320,
|
|
options=[ft.DropdownOption(key=Name, text=Name) for Name in Names],
|
|
)
|
|
|
|
# "SaraSuite" tab: the shared settings every SaraSuite program reads from
|
|
# ~/.config/SaraSuite/Config.toml, and whether SaraFem follows them.
|
|
Shared = LoadSharedSettings(Config)
|
|
UseSharedSwitch = ft.Switch(label="SaraFem uses these shared settings", value=Config.UseSharedSettings)
|
|
SharedThemeDropdown = ThemePicker(Shared.Theme)
|
|
SharedFontDropdown = FontPicker(Shared.FontFamily)
|
|
SharedTitleBarSwitch = ft.Switch(label="Hide window title bar", value=Shared.HideWindowTitleBar)
|
|
|
|
# Export/import as one portable file (Features/Major) — separate from
|
|
# the shared settings above: that's the always-on, automatic sync every
|
|
# SaraSuite program reads on startup, this is a one-off snapshot you
|
|
# choose a path for, to back up or carry to another machine.
|
|
SettingsFileField = ft.TextField(
|
|
value=str(Path.home() / "SaraFem-Settings.toml"), label="Settings file path"
|
|
)
|
|
|
|
# "Middle" tab: the main file-list pane itself, not the panels around it.
|
|
ViewModePicker, GetViewMode = _OptionPicker(
|
|
[("list", "List", "VIEW_LIST_OUTLINED"), ("grid", "Grid", "GRID_VIEW_OUTLINED")], Config.ViewMode
|
|
)
|
|
ListDescriptionSwitch = ft.Switch(label="Description", value=Config.ListShowDescription)
|
|
ListSizeSwitch = ft.Switch(label="Size", value=Config.ListShowSize)
|
|
ListModifiedSwitch = ft.Switch(label="Modified", value=Config.ListShowModified)
|
|
ListOwnerSwitch = ft.Switch(label="Owner", value=Config.ListShowOwner)
|
|
ListCreatedSwitch = ft.Switch(label="Created", value=Config.ListShowCreated)
|
|
ListContentSwitch = ft.Switch(
|
|
label="Content (item count / line count / image size)", value=Config.ListShowContent
|
|
)
|
|
ListAccessSwitch = ft.Switch(label="Access (rwx)", value=Config.ListShowAccess)
|
|
GridIconSizeField = ft.TextField(
|
|
value=str(Config.GridIconSize), label="Grid icon size", keyboard_type=ft.KeyboardType.NUMBER
|
|
)
|
|
GridNamePositionPicker, GetGridNamePosition = _OptionPicker(
|
|
[("below", "Below", "SOUTH"), ("above", "Above", "NORTH")],
|
|
"below" if Config.GridNameBelowIcon else "above",
|
|
)
|
|
ListIconSizeField = ft.TextField(
|
|
value=str(Config.ListIconSize), label="List icon size", keyboard_type=ft.KeyboardType.NUMBER
|
|
)
|
|
FontSizeField = ft.TextField(
|
|
value=str(Config.FontSize), label="Name font size", keyboard_type=ft.KeyboardType.NUMBER
|
|
)
|
|
ToolbarHamburgerSwitch = ft.Switch(
|
|
label="Collapse toolbar buttons into one menu", value=Config.ToolbarHamburger
|
|
)
|
|
|
|
# Folder Icons (Features: "turning on different variants depending on
|
|
# how full a directory is... add more... choose icons (or custom
|
|
# images) for each"). WorkingFolderBands is edited live in the dialog;
|
|
# only committed to Config.FolderIconBands in DoSave, same as every
|
|
# other field here. One band (the default) behaves exactly like
|
|
# today's plain folder icon everywhere — nothing changes until a
|
|
# second variant gets added.
|
|
FolderThresholdField = ft.TextField(
|
|
value=str(Config.FolderFullnessThreshold),
|
|
label="Items counted as 100% full",
|
|
keyboard_type=ft.KeyboardType.NUMBER,
|
|
)
|
|
WorkingFolderBands: list[dict] = [
|
|
{"Icon": B.Icon, "ImagePath": B.ImagePath} for B in (Config.FolderIconBands or [FolderIconBand()])
|
|
]
|
|
# Live widget refs, one (IconDropdown, ImageField) pair per currently-
|
|
# displayed row — read from directly at save time (and before any
|
|
# Add/Remove rebuild) rather than syncing via on_change, since Dropdown
|
|
# only exposes on_select (a different event) and TextField already
|
|
# keeps its own .value current without any handler needed.
|
|
FolderBandWidgets: list[tuple[ft.Dropdown, ft.TextField]] = []
|
|
FolderBandsColumn = ft.Column(spacing=8)
|
|
|
|
def _FolderIconLabel(Name: str) -> str:
|
|
return FolderIconLabelOverrides.get(Name, Name.split("_")[0].title())
|
|
|
|
def _SyncWorkingFolderBands() -> None:
|
|
for Index, (IconDropdown, ImageField) in enumerate(FolderBandWidgets):
|
|
WorkingFolderBands[Index] = {
|
|
"Icon": IconDropdown.value or "FOLDER_OUTLINED", "ImagePath": (ImageField.value or "").strip(),
|
|
}
|
|
|
|
def RebuildFolderBandsColumn(DoUpdate: bool = True) -> None:
|
|
FolderBandWidgets.clear()
|
|
Count = len(WorkingFolderBands)
|
|
Rows: list[ft.Control] = []
|
|
for Index, BandData in enumerate(WorkingFolderBands):
|
|
Low = round(Index / Count * 100)
|
|
High = round((Index + 1) / Count * 100)
|
|
|
|
def OnRemove(e, I=Index) -> None:
|
|
if len(WorkingFolderBands) > 1:
|
|
_SyncWorkingFolderBands()
|
|
WorkingFolderBands.pop(I)
|
|
RebuildFolderBandsColumn()
|
|
|
|
IconDropdown = ft.Dropdown(
|
|
value=BandData["Icon"],
|
|
options=[
|
|
ft.DropdownOption(key=Name, text=_FolderIconLabel(Name), leading_icon=getattr(ft.Icons, Name))
|
|
for Name in FolderIconChoices
|
|
],
|
|
width=150,
|
|
)
|
|
ImageField = ft.TextField(value=BandData["ImagePath"], label="Or custom image path", expand=True)
|
|
FolderBandWidgets.append((IconDropdown, ImageField))
|
|
RowItems: list[ft.Control] = [
|
|
ft.Text(f"{Low}-{High}%", width=55, color=Palette.Overlay0), IconDropdown, ImageField,
|
|
]
|
|
if Count > 1:
|
|
RowItems.append(
|
|
ft.IconButton(icon=ft.Icons.DELETE_OUTLINE, tooltip="Remove this variant", on_click=OnRemove)
|
|
)
|
|
Rows.append(ft.Row(RowItems, spacing=8))
|
|
FolderBandsColumn.controls = Rows
|
|
if DoUpdate:
|
|
FolderBandsColumn.update()
|
|
|
|
def AddFolderBand(e=None) -> None:
|
|
_SyncWorkingFolderBands()
|
|
WorkingFolderBands.append({"Icon": "FOLDER_OUTLINED", "ImagePath": ""})
|
|
RebuildFolderBandsColumn()
|
|
|
|
RebuildFolderBandsColumn(DoUpdate=False) # dialog isn't shown yet
|
|
AddFolderBandButton = ft.OutlinedButton(
|
|
content="Add Variant", icon=ft.Icons.ADD, style=ChipButtonStyle(), on_click=AddFolderBand,
|
|
)
|
|
|
|
# SaraFem's own values ("General" tab), only used with shared settings off.
|
|
ThemeDropdown = ThemePicker(Config.Theme)
|
|
FontDropdown = FontPicker(Config.FontFamily)
|
|
TitleBarSwitch = ft.Switch(label="Hide window title bar", value=Config.HideWindowTitleBar)
|
|
OwnAppearance = ft.Column(
|
|
[
|
|
_SectionLabel("SaraFem's own appearance, used when shared settings are off (SaraSuite tab)"),
|
|
ThemeDropdown,
|
|
FontDropdown,
|
|
TitleBarSwitch,
|
|
],
|
|
spacing=12,
|
|
disabled=Config.UseSharedSettings,
|
|
opacity=0.5 if Config.UseSharedSettings else 1,
|
|
)
|
|
|
|
def OnUseSharedChanged(e) -> None:
|
|
OwnAppearance.disabled = UseSharedSwitch.value
|
|
OwnAppearance.opacity = 0.5 if UseSharedSwitch.value else 1
|
|
OwnAppearance.update()
|
|
|
|
UseSharedSwitch.on_change = OnUseSharedChanged
|
|
DotfilesSwitch = ft.Switch(label="Show dotfiles", value=Config.ShowDotfiles)
|
|
OpenFilesInAppSwitch = ft.Switch(
|
|
label="Open text/config/markup files in SaraFem's own viewer, not your default app",
|
|
value=Config.OpenFilesInApp,
|
|
)
|
|
ShowSearchSwitch = ft.Switch(label="Enable search (Ctrl+F)", value=Config.ShowSearch)
|
|
SearchAlwaysVisibleSwitch = ft.Switch(
|
|
label="Always show the search bar (off = only on Ctrl+F, Escape to dismiss)",
|
|
value=Config.SearchAlwaysVisible,
|
|
)
|
|
SearchResetOnNavigateSwitch = ft.Switch(
|
|
label="Clear search when changing directory", value=Config.SearchResetOnNavigate
|
|
)
|
|
ShowSortSwitch = ft.Switch(label="Enable sort control in the sidebar", value=Config.ShowSort)
|
|
BatchRenamePatternField = ft.TextField(
|
|
value=Config.BatchRenamePattern,
|
|
label="Batch rename pattern (~ = name, ! = counter, ^ = extension)",
|
|
)
|
|
DeleteZipSwitch = ft.Switch(
|
|
label="Delete zip after extracting, by default", value=Config.DeleteZipAfterExtract
|
|
)
|
|
ArchiveFormatPicker, GetArchiveFormat = _OptionPicker(
|
|
[("zip", "Zip", "FOLDER_ZIP_OUTLINED"), ("targz", "Tar.gz", "ARCHIVE_OUTLINED")],
|
|
Config.DefaultArchiveFormat,
|
|
)
|
|
|
|
NewDirectoryButtonSwitch = ft.Switch(label="Show New Directory button", value=Config.ShowNewDirectoryButton)
|
|
PasteButtonSwitch = ft.Switch(label="Show Paste button", value=Config.ShowPasteButton)
|
|
RefreshButtonSwitch = ft.Switch(label="Show Refresh button", value=Config.ShowRefreshButton)
|
|
ViewToggleButtonSwitch = ft.Switch(label="Show Grid/List button", value=Config.ShowViewToggleButton)
|
|
OptionsButtonSwitch = ft.Switch(label="Show Options button", value=Config.ShowOptionsButton)
|
|
ToolbarSwitch = ft.Switch(label="Show toolbar (breadcrumbs + buttons bar)", value=Config.ShowToolbar)
|
|
BreadcrumbsSwitch = ft.Switch(label="Show breadcrumbs", value=Config.ShowBreadcrumbs)
|
|
BreadcrumbsPositionPicker, GetBreadcrumbsPosition = _OptionPicker(
|
|
[("top", "Top", "NORTH"), ("bottom", "Bottom", "SOUTH")], Config.BreadcrumbsPosition
|
|
)
|
|
|
|
SidebarSwitch = ft.Switch(label="Show sidebar", value=Config.ShowSidebar)
|
|
SidebarSidePicker, GetSidebarSide = _OptionPicker(
|
|
[("left", "Left", "WEST"), ("right", "Right", "EAST")], Config.SidebarSide
|
|
)
|
|
SidebarWidthField = ft.TextField(
|
|
value=str(Config.SidebarWidth), label="Sidebar width", keyboard_type=ft.KeyboardType.NUMBER
|
|
)
|
|
BookmarksSwitch = ft.Switch(label="Show bookmarks section", value=Config.ShowBookmarks)
|
|
BookmarksHeightRow, BookmarksMinHeightField, BookmarksMaxHeightField = _MinMaxRow(
|
|
"Bookmarks height", Config.BookmarksMinHeight, Config.BookmarksMaxHeight
|
|
)
|
|
DevicesSwitch = ft.Switch(label="Show devices section", value=Config.ShowDevices)
|
|
AutoMountSwitch = ft.Switch(label="Mount devices when plugged in", value=Config.AutoMountDevices)
|
|
InternalDrivesSwitch = ft.Switch(label="Also show internal drives", value=Config.ShowInternalDrives)
|
|
SystemDriveSwitch = ft.Switch(
|
|
label="Include the main drive (/) too (never ejectable)", value=Config.ShowSystemDrive
|
|
)
|
|
AutoHideEmptyDevicesSwitch = ft.Switch(
|
|
label="Hide the devices section when there's nothing plugged in", value=Config.AutoHideEmptyDevices
|
|
)
|
|
DevicesHeightRow, DevicesMinHeightField, DevicesMaxHeightField = _MinMaxRow(
|
|
"Devices height", Config.DevicesMinHeight, Config.DevicesMaxHeight
|
|
)
|
|
FiletreeSwitch = ft.Switch(label="Show filetree section", value=Config.ShowFiletree)
|
|
FiletreeRootField = ft.TextField(value=Config.FiletreeRoot, label="Filetree root", hint_text="/")
|
|
FiletreeHeightRow, FiletreeMinHeightField, FiletreeMaxHeightField = _MinMaxRow(
|
|
"Filetree height", Config.FiletreeMinHeight, Config.FiletreeMaxHeight
|
|
)
|
|
SplitSidebarEvenlySwitch = ft.Switch(
|
|
label="Split Bookmarks/Filetree evenly (overrides the heights above)",
|
|
value=Config.SplitBookmarksFiletreeEvenly,
|
|
)
|
|
|
|
PreviewSwitch = ft.Switch(label="Show preview pane", value=Config.ShowPreview)
|
|
PreviewSidePicker, GetPreviewSide = _OptionPicker(
|
|
[("left", "Left", "WEST"), ("right", "Right", "EAST")], Config.PreviewSide
|
|
)
|
|
PreviewWidthField = ft.TextField(
|
|
value=str(Config.PreviewWidth), label="Preview width", keyboard_type=ft.KeyboardType.NUMBER
|
|
)
|
|
PreviewHeightRow, PreviewMinHeightField, PreviewMaxHeightField = _MinMaxRow(
|
|
"Preview height (only when Tags is under preview)", Config.PreviewMinHeight, Config.PreviewMaxHeight
|
|
)
|
|
SplitEvenlySwitch = ft.Switch(
|
|
label="Split Preview/Tags evenly (overrides the heights above)",
|
|
value=Config.SplitPreviewTagsEvenly,
|
|
)
|
|
|
|
TagsSwitch = ft.Switch(label="Show SaraTag tags", value=Config.ShowTags)
|
|
TagsPositionPicker, GetTagsPosition = _OptionPicker(
|
|
[
|
|
("under_preview", "Under Preview", "VIEW_AGENDA_OUTLINED"),
|
|
("top", "Top", "NORTH"),
|
|
("bottom", "Bottom", "SOUTH"),
|
|
],
|
|
Config.TagsPosition,
|
|
)
|
|
TagsLayoutPicker, GetTagsLayout = _OptionPicker(
|
|
[("flex", "Flex", "VIEW_STREAM_OUTLINED"), ("vertical", "Vertical", "VIEW_COLUMN_OUTLINED")],
|
|
Config.TagsLayout,
|
|
)
|
|
TagMinWidthField = ft.TextField(
|
|
value=str(Config.TagMinWidth), label="Tag min width (0 = none)", keyboard_type=ft.KeyboardType.NUMBER
|
|
)
|
|
TagMaxWidthField = ft.TextField(
|
|
value=str(Config.TagMaxWidth), label="Tag max width (0 = none)", keyboard_type=ft.KeyboardType.NUMBER
|
|
)
|
|
TagPanelHeightRow, TagPanelMinHeightField, TagPanelMaxHeightField = _MinMaxRow(
|
|
"Tags box height", Config.TagPanelMinHeight, Config.TagPanelMaxHeight
|
|
)
|
|
|
|
SaraTagSwitch = ft.Switch(label="Enable SaraTag integration", value=Config.SaraTag.Enabled)
|
|
SaraTagPathField = ft.TextField(
|
|
value=Config.SaraTag.CoreModulePath,
|
|
label="SaraTag .saratag path",
|
|
hint_text="/home/sarah/Applications/SaraTag/.saratag",
|
|
)
|
|
|
|
SaraSortdSwitch = ft.Switch(label="Enable SaraSortd integration", value=Config.SaraSortd.Enabled)
|
|
SaraSortdPathField = ft.TextField(
|
|
value=Config.SaraSortd.GlobalConfPath,
|
|
label="SaraSortd GlobalConf.toml path",
|
|
hint_text="/home/sarah/Applications/SaraSortd/GlobalConf.toml",
|
|
)
|
|
SaraSortdShowBannerSwitch = ft.Switch(
|
|
label="Show the SaraSortd info banner above the file list", value=Config.SaraSortd.ShowBanner
|
|
)
|
|
SaraSortdCondenseBannerSwitch = ft.Switch(
|
|
label="Condense multiple patterns per destination (collapsible)", value=Config.SaraSortd.CondenseBanner
|
|
)
|
|
|
|
Categories: dict[str, ft.Control] = {
|
|
"SaraSuite": ft.Column(
|
|
[
|
|
_SectionLabel("Shared by every SaraSuite program (~/.config/SaraSuite/Config.toml). "
|
|
"Other programs pick changes up the next time they start."),
|
|
UseSharedSwitch,
|
|
ft.Divider(height=1, color=Palette.Surface1),
|
|
SharedThemeDropdown,
|
|
SharedFontDropdown,
|
|
SharedTitleBarSwitch,
|
|
ft.Divider(height=1, color=Palette.Surface1),
|
|
_SectionLabel("Export/import settings as one file — this tab's shared "
|
|
"settings plus everything else in Options"),
|
|
SettingsFileField,
|
|
ft.Row(
|
|
[
|
|
ft.OutlinedButton(
|
|
content="Export", style=ChipButtonStyle(),
|
|
on_click=lambda e: OnExport(SettingsFileField.value.strip()),
|
|
),
|
|
ft.OutlinedButton(
|
|
content="Import", style=ChipButtonStyle(),
|
|
on_click=lambda e: OnImport(SettingsFileField.value.strip()),
|
|
),
|
|
]
|
|
),
|
|
],
|
|
spacing=12,
|
|
scroll=ft.ScrollMode.AUTO,
|
|
),
|
|
"General": ft.Column(
|
|
[
|
|
DotfilesSwitch,
|
|
OpenFilesInAppSwitch,
|
|
BatchRenamePatternField,
|
|
DeleteZipSwitch,
|
|
_SectionLabel("Default compress format"),
|
|
ArchiveFormatPicker,
|
|
ft.Divider(height=1, color=Palette.Surface1),
|
|
OwnAppearance,
|
|
],
|
|
spacing=12,
|
|
scroll=ft.ScrollMode.AUTO,
|
|
),
|
|
"Middle": ft.Column(
|
|
[
|
|
_SectionLabel("View"),
|
|
ViewModePicker,
|
|
ft.Divider(height=1, color=Palette.Surface1),
|
|
_SectionLabel("List columns (besides Name)"),
|
|
ListDescriptionSwitch,
|
|
ListSizeSwitch,
|
|
ListModifiedSwitch,
|
|
ListOwnerSwitch,
|
|
ListCreatedSwitch,
|
|
ListContentSwitch,
|
|
ListAccessSwitch,
|
|
ft.Divider(height=1, color=Palette.Surface1),
|
|
_SectionLabel("Font and icon sizes"),
|
|
FontSizeField,
|
|
ListIconSizeField,
|
|
ft.Divider(height=1, color=Palette.Surface1),
|
|
_SectionLabel("Grid"),
|
|
GridIconSizeField,
|
|
_SectionLabel("Name position"),
|
|
GridNamePositionPicker,
|
|
],
|
|
spacing=12,
|
|
scroll=ft.ScrollMode.AUTO,
|
|
),
|
|
"Folder Icons": ft.Column(
|
|
[
|
|
_SectionLabel(
|
|
"One variant always shows the same icon (today's plain folder, unless changed "
|
|
"below). Add more to vary by how full a directory is, like Windows' folder fill "
|
|
"levels — item count only, never a slow recursive size walk."
|
|
),
|
|
FolderThresholdField,
|
|
FolderBandsColumn,
|
|
AddFolderBandButton,
|
|
ft.Divider(height=1, color=Palette.Surface1),
|
|
_SectionLabel(
|
|
'Per-directory overrides (right-click a folder → Properties → "Save Icon") '
|
|
"always win over these."
|
|
),
|
|
],
|
|
spacing=12,
|
|
scroll=ft.ScrollMode.AUTO,
|
|
),
|
|
"Toolbar": ft.Column(
|
|
[
|
|
ToolbarSwitch,
|
|
BreadcrumbsSwitch,
|
|
_SectionLabel("Position"),
|
|
BreadcrumbsPositionPicker,
|
|
ft.Divider(height=1, color=Palette.Surface1),
|
|
NewDirectoryButtonSwitch,
|
|
PasteButtonSwitch,
|
|
RefreshButtonSwitch,
|
|
ViewToggleButtonSwitch,
|
|
OptionsButtonSwitch,
|
|
ft.Divider(height=1, color=Palette.Surface1),
|
|
ToolbarHamburgerSwitch,
|
|
],
|
|
spacing=12,
|
|
scroll=ft.ScrollMode.AUTO,
|
|
),
|
|
"Sidebar": ft.Column(
|
|
[
|
|
SidebarSwitch,
|
|
_SectionLabel("Side"),
|
|
SidebarSidePicker,
|
|
SidebarWidthField,
|
|
ft.Divider(height=1, color=Palette.Surface1),
|
|
BookmarksSwitch,
|
|
BookmarksHeightRow,
|
|
ft.Divider(height=1, color=Palette.Surface1),
|
|
DevicesSwitch,
|
|
AutoMountSwitch,
|
|
InternalDrivesSwitch,
|
|
SystemDriveSwitch,
|
|
AutoHideEmptyDevicesSwitch,
|
|
DevicesHeightRow,
|
|
ft.Divider(height=1, color=Palette.Surface1),
|
|
FiletreeSwitch,
|
|
FiletreeRootField,
|
|
FiletreeHeightRow,
|
|
ft.Divider(height=1, color=Palette.Surface1),
|
|
SplitSidebarEvenlySwitch,
|
|
],
|
|
spacing=12,
|
|
scroll=ft.ScrollMode.AUTO,
|
|
),
|
|
"Preview": ft.Column(
|
|
[
|
|
PreviewSwitch,
|
|
_SectionLabel("Side"),
|
|
PreviewSidePicker,
|
|
PreviewWidthField,
|
|
PreviewHeightRow,
|
|
],
|
|
spacing=12,
|
|
scroll=ft.ScrollMode.AUTO,
|
|
),
|
|
"Tags": ft.Column(
|
|
[
|
|
TagsSwitch,
|
|
_SectionLabel("Position"),
|
|
TagsPositionPicker,
|
|
_SectionLabel("Layout"),
|
|
TagsLayoutPicker,
|
|
TagMinWidthField,
|
|
TagMaxWidthField,
|
|
TagPanelHeightRow,
|
|
SplitEvenlySwitch,
|
|
],
|
|
spacing=12,
|
|
scroll=ft.ScrollMode.AUTO,
|
|
),
|
|
"Search & Sort": ft.Column(
|
|
[
|
|
ShowSearchSwitch,
|
|
SearchAlwaysVisibleSwitch,
|
|
SearchResetOnNavigateSwitch,
|
|
ft.Divider(height=1, color=Palette.Surface1),
|
|
ShowSortSwitch,
|
|
_SectionLabel('The sort control ("[Sort: Name] v") sits above Bookmarks in the sidebar.'),
|
|
],
|
|
spacing=12,
|
|
scroll=ft.ScrollMode.AUTO,
|
|
),
|
|
"SaraTag": ft.Column([SaraTagSwitch, SaraTagPathField], spacing=12, scroll=ft.ScrollMode.AUTO),
|
|
"SaraSortd": ft.Column(
|
|
[
|
|
SaraSortdSwitch,
|
|
SaraSortdPathField,
|
|
_SectionLabel(
|
|
'Shows what SaraSortd does with the directory you\'re browsing, e.g. '
|
|
'"*.png -> ~/Pictures" for a watched input directory, or '
|
|
'"*.png <- ~/Downloads" for one of its output directories.'
|
|
),
|
|
ft.Divider(height=1, color=Palette.Surface1),
|
|
SaraSortdShowBannerSwitch,
|
|
SaraSortdCondenseBannerSwitch,
|
|
],
|
|
spacing=12,
|
|
scroll=ft.ScrollMode.AUTO,
|
|
),
|
|
}
|
|
|
|
DetailArea = ft.Container(Categories["General"], expand=True, padding=ft.Padding.only(left=12))
|
|
|
|
CategoryTiles: dict[str, ft.ListTile] = {}
|
|
|
|
def SelectCategory(Name: str) -> None:
|
|
for CategoryName, Tile in CategoryTiles.items():
|
|
Tile.selected = CategoryName == Name
|
|
DetailArea.content = Categories[Name]
|
|
DetailArea.update()
|
|
CategoryListView.update()
|
|
|
|
for Name in Categories:
|
|
CategoryTiles[Name] = ft.ListTile(
|
|
title=Name,
|
|
selected=Name == "General",
|
|
selected_tile_color=Palette.Surface1,
|
|
selected_color=Palette.Mauve,
|
|
on_click=lambda e, n=Name: SelectCategory(n),
|
|
)
|
|
|
|
CategoryListView = ft.Column(list(CategoryTiles.values()), spacing=0, scroll=ft.ScrollMode.AUTO)
|
|
CategorySidebar = ft.Container(
|
|
CategoryListView, width=CategoryListWidth, bgcolor=Palette.Surface0, border_radius=4, padding=4
|
|
)
|
|
|
|
def DoSave(e=None) -> None:
|
|
Config.ViewMode = GetViewMode()
|
|
Config.ListShowDescription = ListDescriptionSwitch.value
|
|
Config.ListShowSize = ListSizeSwitch.value
|
|
Config.ListShowModified = ListModifiedSwitch.value
|
|
Config.ListShowOwner = ListOwnerSwitch.value
|
|
Config.ListShowCreated = ListCreatedSwitch.value
|
|
Config.ListShowContent = ListContentSwitch.value
|
|
Config.ListShowAccess = ListAccessSwitch.value
|
|
Config.GridNameBelowIcon = GetGridNamePosition() == "below"
|
|
Config.ToolbarHamburger = ToolbarHamburgerSwitch.value
|
|
Config.UseSharedSettings = UseSharedSwitch.value
|
|
Config.Theme = ThemeDropdown.value
|
|
Config.FontFamily = FontDropdown.value or Config.FontFamily
|
|
Config.HideWindowTitleBar = TitleBarSwitch.value
|
|
SharedSettings(
|
|
Theme=SharedThemeDropdown.value,
|
|
FontFamily=SharedFontDropdown.value or Shared.FontFamily,
|
|
HideWindowTitleBar=SharedTitleBarSwitch.value,
|
|
).Save()
|
|
Config.ShowDotfiles = DotfilesSwitch.value
|
|
Config.OpenFilesInApp = OpenFilesInAppSwitch.value
|
|
Config.ShowSearch = ShowSearchSwitch.value
|
|
Config.SearchAlwaysVisible = SearchAlwaysVisibleSwitch.value
|
|
Config.SearchResetOnNavigate = SearchResetOnNavigateSwitch.value
|
|
Config.ShowSort = ShowSortSwitch.value
|
|
Config.BatchRenamePattern = BatchRenamePatternField.value.strip() or "~!^"
|
|
Config.DeleteZipAfterExtract = DeleteZipSwitch.value
|
|
Config.DefaultArchiveFormat = GetArchiveFormat()
|
|
_SyncWorkingFolderBands()
|
|
Config.FolderIconBands = [
|
|
FolderIconBand(Icon=B["Icon"] or "FOLDER_OUTLINED", ImagePath=B["ImagePath"])
|
|
for B in WorkingFolderBands
|
|
]
|
|
|
|
Config.ShowNewDirectoryButton = NewDirectoryButtonSwitch.value
|
|
Config.ShowPasteButton = PasteButtonSwitch.value
|
|
Config.ShowRefreshButton = RefreshButtonSwitch.value
|
|
Config.ShowViewToggleButton = ViewToggleButtonSwitch.value
|
|
Config.ShowOptionsButton = OptionsButtonSwitch.value
|
|
Config.ShowToolbar = ToolbarSwitch.value
|
|
Config.ShowBreadcrumbs = BreadcrumbsSwitch.value
|
|
Config.BreadcrumbsPosition = GetBreadcrumbsPosition()
|
|
|
|
Config.ShowSidebar = SidebarSwitch.value
|
|
Config.SidebarSide = GetSidebarSide()
|
|
Config.ShowBookmarks = BookmarksSwitch.value
|
|
Config.ShowDevices = DevicesSwitch.value
|
|
Config.AutoMountDevices = AutoMountSwitch.value
|
|
Config.ShowInternalDrives = InternalDrivesSwitch.value
|
|
Config.ShowSystemDrive = SystemDriveSwitch.value
|
|
Config.AutoHideEmptyDevices = AutoHideEmptyDevicesSwitch.value
|
|
Config.ShowFiletree = FiletreeSwitch.value
|
|
Config.SplitBookmarksFiletreeEvenly = SplitSidebarEvenlySwitch.value
|
|
Config.FiletreeRoot = FiletreeRootField.value.strip() or "/"
|
|
|
|
Config.PreviewSide = GetPreviewSide()
|
|
Config.ShowPreview = PreviewSwitch.value
|
|
Config.SplitPreviewTagsEvenly = SplitEvenlySwitch.value
|
|
|
|
Config.TagsPosition = GetTagsPosition()
|
|
Config.TagsLayout = GetTagsLayout()
|
|
Config.ShowTags = TagsSwitch.value
|
|
|
|
Config.SaraTag.Enabled = SaraTagSwitch.value
|
|
Config.SaraTag.CoreModulePath = SaraTagPathField.value.strip()
|
|
|
|
Config.SaraSortd.Enabled = SaraSortdSwitch.value
|
|
Config.SaraSortd.GlobalConfPath = SaraSortdPathField.value.strip()
|
|
Config.SaraSortd.ShowBanner = SaraSortdShowBannerSwitch.value
|
|
Config.SaraSortd.CondenseBanner = SaraSortdCondenseBannerSwitch.value
|
|
|
|
for Field, TargetAttr in (
|
|
(SidebarWidthField, "SidebarWidth"),
|
|
(BookmarksMinHeightField, "BookmarksMinHeight"),
|
|
(BookmarksMaxHeightField, "BookmarksMaxHeight"),
|
|
(DevicesMinHeightField, "DevicesMinHeight"),
|
|
(DevicesMaxHeightField, "DevicesMaxHeight"),
|
|
(FiletreeMinHeightField, "FiletreeMinHeight"),
|
|
(FiletreeMaxHeightField, "FiletreeMaxHeight"),
|
|
(PreviewWidthField, "PreviewWidth"),
|
|
(PreviewMinHeightField, "PreviewMinHeight"),
|
|
(PreviewMaxHeightField, "PreviewMaxHeight"),
|
|
(TagMinWidthField, "TagMinWidth"),
|
|
(TagMaxWidthField, "TagMaxWidth"),
|
|
(TagPanelMinHeightField, "TagPanelMinHeight"),
|
|
(TagPanelMaxHeightField, "TagPanelMaxHeight"),
|
|
(GridIconSizeField, "GridIconSize"),
|
|
(ListIconSizeField, "ListIconSize"),
|
|
(FontSizeField, "FontSize"),
|
|
(FolderThresholdField, "FolderFullnessThreshold"),
|
|
):
|
|
try:
|
|
setattr(Config, TargetAttr, int(Field.value))
|
|
except ValueError:
|
|
pass # keep the previous value rather than crash on bad input
|
|
|
|
Config.Save()
|
|
OnSave()
|
|
|
|
return ft.AlertDialog(
|
|
title="Options",
|
|
content=ft.Column(
|
|
[
|
|
# STRETCH confirmed necessary, not assumed: without it, Row
|
|
# doesn't bound CategorySidebar/DetailArea's height to the
|
|
# dialog's own fixed height, so their scroll=AUTO had nothing
|
|
# to actually scroll within — content just overflowed instead
|
|
# (Bugs/Minor: "Settings should be scrollable... too many for
|
|
# one window").
|
|
ft.Row([CategorySidebar, DetailArea], expand=True, vertical_alignment=ft.CrossAxisAlignment.STRETCH),
|
|
ft.Divider(height=1, color=Palette.Surface1),
|
|
ft.Text(RestartRequiredNote, color=Palette.Overlay0, size=12),
|
|
],
|
|
width=DialogWidth,
|
|
height=DialogHeight,
|
|
),
|
|
actions=[
|
|
ft.OutlinedButton(content="Cancel", style=ChipButtonStyle(), on_click=lambda e: OnDismiss()),
|
|
ft.OutlinedButton(content="Save", style=ChipButtonStyle(), on_click=DoSave),
|
|
],
|
|
)
|