Shared SaraSuite settings (theme, font, title bar) with a SaraSuite settings tab

This commit is contained in:
sarah committed 2026-09-24 23:40:21 +02:00
1 parent 089dfc005f
commit a5f5c5ca79
3 files changed
+158 -18

No files matched your search

+80 -4
View File
@@ -6,6 +6,7 @@ one accent colour (Purple/Mauve) used everywhere, not a mauve+lavender mix.
"""
import shutil
import subprocess
from dataclasses import asdict, dataclass, field
from pathlib import Path
@@ -37,7 +38,11 @@ class Palette:
AppName = "SaraFem"
FontFamily = "Fixedsys Excelsior"
# Bundled in assets/fonts and always registered, so it works as FontFamily
# without being installed. Any other FontFamily is looked up among the
# system's installed fonts.
BundledFontFamily = "Fixedsys Excelsior"
ActiveFontFamily = BundledFontFamily # set by ApplyAppearance, read by BuildTheme
CacheDir = Path.home() / ".cache" / "SaraFem"
ThumbnailCacheDir = CacheDir / "Thumbnails"
@@ -48,6 +53,9 @@ SharedConfigDir = Path.home() / ".config" / "SaraSuite"
ThemesDir = SharedConfigDir / "Themes"
ConfigDir = SharedConfigDir / "SaraFem"
ConfigPath = ConfigDir / "Config.toml"
# Settings shared by every SaraSuite program. Same key names as the matching
# fields in each program's own Config.toml.
SharedConfigPath = SharedConfigDir / "Config.toml"
BookmarksPath = ConfigDir / ".sfem_bookmarks.toml"
_OldConfigDir = Path.home() / ".config" / "SaraFem"
@@ -152,10 +160,16 @@ class AppConfig:
(nothing changes visually until the user opts in via Ctrl+B/P/H/T or edits
Config.toml directly)."""
# True: Theme, FontFamily and HideWindowTitleBar come from the shared
# ~/.config/SaraSuite/Config.toml, like every other SaraSuite program.
# False: SaraFem uses its own values below instead.
UseSharedSettings: bool = True
Theme: str = "CatppuccinMocha" # matches a file in ~/.config/SaraSuite/Themes/
FontFamily: str = BundledFontFamily
HideWindowTitleBar: bool = False # the actual OS window title bar, not the toolbar below
ShowDotfiles: bool = True
ViewMode: str = "list" # "list" or "grid"
HideWindowTitleBar: bool = False # the actual OS window title bar, not the toolbar below
# Batch-rename pattern for multi-select rename, syntax modeled on
# SaraSortd's own placeholder characters (see ~/Applications/SaraSortd's
@@ -252,6 +266,68 @@ def LoadConfig() -> AppConfig:
return AppConfig(**Filtered)
@dataclass
class SharedSettings:
"""~/.config/SaraSuite/Config.toml: the settings every SaraSuite program
shares. A program whose own UseSharedSettings is off reads these same
keys from its own Config.toml instead."""
Theme: str = "CatppuccinMocha"
FontFamily: str = BundledFontFamily
HideWindowTitleBar: bool = False
def Save(self) -> None:
SharedConfigDir.mkdir(parents=True, exist_ok=True)
SharedConfigPath.write_text(toml.dumps(asdict(self)))
def _OwnSettings(Config: AppConfig) -> SharedSettings:
return SharedSettings(Config.Theme, Config.FontFamily, Config.HideWindowTitleBar)
def LoadSharedSettings(Config: AppConfig) -> SharedSettings:
"""The shared file, created from SaraFem's own values the first time so
switching to it doesn't change anything visible."""
if not SharedConfigPath.exists():
Shared = _OwnSettings(Config)
Shared.Save()
return Shared
Result = _OwnSettings(Config) # fallback for keys the file doesn't have
try:
Data = toml.loads(SharedConfigPath.read_text())
except toml.TomlDecodeError:
return Result
for Key in SharedSettings.__dataclass_fields__:
if Key in Data:
setattr(Result, Key, Data[Key])
return Result
def EffectiveSettings(Config: AppConfig) -> SharedSettings:
"""What SaraFem actually uses: the shared settings, or its own."""
return LoadSharedSettings(Config) if Config.UseSharedSettings else _OwnSettings(Config)
def ApplyAppearance(Settings: SharedSettings) -> None:
"""Theme colours and font. Must run before anything reads Palette.X or
calls BuildTheme(). The window title bar is applied by Main."""
global ActiveFontFamily
ApplyTheme(Settings.Theme)
ActiveFontFamily = Settings.FontFamily or BundledFontFamily
def InstalledFontFamilies() -> list[str]:
"""Font families fontconfig knows about, for the font picker. The
bundled font is always first, since it isn't installed system-wide."""
try:
Result = subprocess.run(["fc-list", ":", "family"], capture_output=True, text=True, timeout=5)
Families = {Line.split(",")[0].strip() for Line in Result.stdout.splitlines() if Line.strip()}
except (OSError, subprocess.SubprocessError):
Families = set()
Families.discard(BundledFontFamily)
return [BundledFontFamily] + sorted(Families, key=str.lower)
def BuildTheme() -> ft.Theme:
ColorScheme = ft.ColorScheme(
primary=Palette.Mauve,
@@ -275,7 +351,7 @@ def BuildTheme() -> ft.Theme:
on_error=Palette.Crust,
)
return ft.Theme(
font_family=FontFamily,
font_family=ActiveFontFamily,
color_scheme=ColorScheme,
use_material3=True,
scaffold_bgcolor=Palette.Base,
@@ -283,7 +359,7 @@ def BuildTheme() -> ft.Theme:
dialog_theme=ft.DialogTheme(
bgcolor=Palette.Mantle,
shape=ft.RoundedRectangleBorder(radius=8, side=ft.BorderSide(1, Palette.Mauve)),
title_text_style=ft.TextStyle(color=Palette.Mauve, size=18, font_family=FontFamily),
title_text_style=ft.TextStyle(color=Palette.Mauve, size=18, font_family=ActiveFontFamily),
),
)
+5 -4
View File
@@ -11,7 +11,7 @@ from typing import Optional
import flet as ft
from Browser import Browser
from Core import AppName, ApplyTheme, BuildTheme, FontFamily, LoadConfig
from Core import AppName, ApplyAppearance, BuildTheme, BundledFontFamily, EffectiveSettings, LoadConfig
from Sidebar import Sidebar
@@ -189,16 +189,17 @@ def _RunPickerMode(Page: ft.Page, Config, Args: argparse.Namespace) -> None:
def Main(Page: ft.Page) -> None:
Config = LoadConfig()
ApplyTheme(Config.Theme) # must run before anything reads Palette.X
Appearance = EffectiveSettings(Config) # shared SaraSuite settings, or SaraFem's own
ApplyAppearance(Appearance) # must run before anything reads Palette.X
Page.theme_mode = ft.ThemeMode.DARK
Page.theme = BuildTheme()
Page.dark_theme = BuildTheme()
Page.fonts = {FontFamily: "fonts/Fixedsys_Excelsior_Regular.ttf"}
Page.fonts = {BundledFontFamily: "fonts/Fixedsys_Excelsior_Regular.ttf"}
Page.padding = 0
Page.window.width = 1100
Page.window.height = 700
Page.window.title_bar_hidden = Config.HideWindowTitleBar
Page.window.title_bar_hidden = Appearance.HideWindowTitleBar
PickerArgs = _ParsePickerArgs()
if PickerArgs.pick_open or PickerArgs.pick_save or PickerArgs.pick_folder:
+73 -10
View File
@@ -9,12 +9,15 @@ from typing import Callable
import flet as ft
from Core import AppConfig, AvailableThemes, ChipButtonStyle, Palette
from Core import (
AppConfig, AvailableThemes, ChipButtonStyle, InstalledFontFamilies, LoadSharedSettings, Palette,
SharedSettings,
)
# 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, Sidebar and SaraTag settings need a restart to take effect."
RestartRequiredNote = "Window title bar, Theme, Font, Sidebar and SaraTag settings need a restart to take effect."
DialogWidth = 700
DialogHeight = 520
@@ -64,13 +67,53 @@ def BuildSettingsDialog(
OnSave: Callable[[], None],
OnDismiss: Callable[[], None],
) -> ft.AlertDialog:
ThemeDropdown = ft.Dropdown(
value=Config.Theme,
label="Theme (shared across SaraSuite)",
options=[ft.DropdownOption(key=Name, text=Name) for Name in AvailableThemes()],
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],
)
DotfilesSwitch = ft.Switch(label="Show dotfiles", value=Config.ShowDotfiles)
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)
# 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)
BatchRenamePatternField = ft.TextField(
value=Config.BatchRenamePattern,
label="Batch rename pattern (~ = name, ! = counter, ^ = extension)",
@@ -166,17 +209,30 @@ def BuildSettingsDialog(
)
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,
],
spacing=12,
),
"General": ft.Column(
[
ThemeDropdown,
DotfilesSwitch,
TitleBarSwitch,
BatchRenamePatternField,
DeleteZipSwitch,
_SectionLabel("Default compress format"),
ArchiveFormatPicker,
ft.Divider(height=1, color=Palette.Surface1),
OwnAppearance,
],
spacing=12,
scroll=ft.ScrollMode.AUTO,
),
"Toolbar": ft.Column(
[
@@ -270,9 +326,16 @@ def BuildSettingsDialog(
)
def DoSave(e=None) -> None:
Config.UseSharedSettings = UseSharedSwitch.value
Config.Theme = ThemeDropdown.value
Config.ShowDotfiles = DotfilesSwitch.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.BatchRenamePattern = BatchRenamePatternField.value.strip() or "~!^"
Config.DeleteZipAfterExtract = DeleteZipSwitch.value
Config.DefaultArchiveFormat = GetArchiveFormat()