mirror of
https://git.srhtnin.garden/sarah/SaraFem.git
synced 2026-10-13 18:18:11 +02:00
Shared SaraSuite settings (theme, font, title bar) with a SaraSuite settings tab
This commit is contained in:
1 parent
089dfc005f
commit
a5f5c5ca79
3 files changed
+159
-19
No files matched your search
@@ -6,6 +6,7 @@ one accent colour (Purple/Mauve) used everywhere, not a mauve+lavender mix.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import shutil
|
import shutil
|
||||||
|
import subprocess
|
||||||
from dataclasses import asdict, dataclass, field
|
from dataclasses import asdict, dataclass, field
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -37,7 +38,11 @@ class Palette:
|
|||||||
|
|
||||||
|
|
||||||
AppName = "SaraFem"
|
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"
|
CacheDir = Path.home() / ".cache" / "SaraFem"
|
||||||
ThumbnailCacheDir = CacheDir / "Thumbnails"
|
ThumbnailCacheDir = CacheDir / "Thumbnails"
|
||||||
@@ -48,6 +53,9 @@ SharedConfigDir = Path.home() / ".config" / "SaraSuite"
|
|||||||
ThemesDir = SharedConfigDir / "Themes"
|
ThemesDir = SharedConfigDir / "Themes"
|
||||||
ConfigDir = SharedConfigDir / "SaraFem"
|
ConfigDir = SharedConfigDir / "SaraFem"
|
||||||
ConfigPath = ConfigDir / "Config.toml"
|
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"
|
BookmarksPath = ConfigDir / ".sfem_bookmarks.toml"
|
||||||
|
|
||||||
_OldConfigDir = Path.home() / ".config" / "SaraFem"
|
_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
|
(nothing changes visually until the user opts in via Ctrl+B/P/H/T or edits
|
||||||
Config.toml directly)."""
|
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/
|
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
|
ShowDotfiles: bool = True
|
||||||
ViewMode: str = "list" # "list" or "grid"
|
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
|
# Batch-rename pattern for multi-select rename, syntax modeled on
|
||||||
# SaraSortd's own placeholder characters (see ~/Applications/SaraSortd's
|
# SaraSortd's own placeholder characters (see ~/Applications/SaraSortd's
|
||||||
@@ -252,6 +266,68 @@ def LoadConfig() -> AppConfig:
|
|||||||
return AppConfig(**Filtered)
|
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:
|
def BuildTheme() -> ft.Theme:
|
||||||
ColorScheme = ft.ColorScheme(
|
ColorScheme = ft.ColorScheme(
|
||||||
primary=Palette.Mauve,
|
primary=Palette.Mauve,
|
||||||
@@ -275,7 +351,7 @@ def BuildTheme() -> ft.Theme:
|
|||||||
on_error=Palette.Crust,
|
on_error=Palette.Crust,
|
||||||
)
|
)
|
||||||
return ft.Theme(
|
return ft.Theme(
|
||||||
font_family=FontFamily,
|
font_family=ActiveFontFamily,
|
||||||
color_scheme=ColorScheme,
|
color_scheme=ColorScheme,
|
||||||
use_material3=True,
|
use_material3=True,
|
||||||
scaffold_bgcolor=Palette.Base,
|
scaffold_bgcolor=Palette.Base,
|
||||||
@@ -283,7 +359,7 @@ def BuildTheme() -> ft.Theme:
|
|||||||
dialog_theme=ft.DialogTheme(
|
dialog_theme=ft.DialogTheme(
|
||||||
bgcolor=Palette.Mantle,
|
bgcolor=Palette.Mantle,
|
||||||
shape=ft.RoundedRectangleBorder(radius=8, side=ft.BorderSide(1, Palette.Mauve)),
|
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),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from typing import Optional
|
|||||||
import flet as ft
|
import flet as ft
|
||||||
|
|
||||||
from Browser import Browser
|
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
|
from Sidebar import Sidebar
|
||||||
|
|
||||||
|
|
||||||
@@ -189,16 +189,17 @@ def _RunPickerMode(Page: ft.Page, Config, Args: argparse.Namespace) -> None:
|
|||||||
|
|
||||||
def Main(Page: ft.Page) -> None:
|
def Main(Page: ft.Page) -> None:
|
||||||
Config = LoadConfig()
|
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_mode = ft.ThemeMode.DARK
|
||||||
Page.theme = BuildTheme()
|
Page.theme = BuildTheme()
|
||||||
Page.dark_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.padding = 0
|
||||||
Page.window.width = 1100
|
Page.window.width = 1100
|
||||||
Page.window.height = 700
|
Page.window.height = 700
|
||||||
Page.window.title_bar_hidden = Config.HideWindowTitleBar
|
Page.window.title_bar_hidden = Appearance.HideWindowTitleBar
|
||||||
|
|
||||||
PickerArgs = _ParsePickerArgs()
|
PickerArgs = _ParsePickerArgs()
|
||||||
if PickerArgs.pick_open or PickerArgs.pick_save or PickerArgs.pick_folder:
|
if PickerArgs.pick_open or PickerArgs.pick_save or PickerArgs.pick_folder:
|
||||||
|
|||||||
+74
-11
@@ -9,12 +9,15 @@ from typing import Callable
|
|||||||
|
|
||||||
import flet as ft
|
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).
|
# Settings Browser can already hot-apply live (dotfiles, breadcrumbs, preview, tags).
|
||||||
# The window title bar, sidebar (bookmarks/filetree) layout, theme, and SaraTag's
|
# The window title bar, sidebar (bookmarks/filetree) layout, theme, and SaraTag's
|
||||||
# dynamically-loaded module need a restart to take effect.
|
# 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
|
DialogWidth = 700
|
||||||
DialogHeight = 520
|
DialogHeight = 520
|
||||||
@@ -64,13 +67,53 @@ def BuildSettingsDialog(
|
|||||||
OnSave: Callable[[], None],
|
OnSave: Callable[[], None],
|
||||||
OnDismiss: Callable[[], None],
|
OnDismiss: Callable[[], None],
|
||||||
) -> ft.AlertDialog:
|
) -> ft.AlertDialog:
|
||||||
ThemeDropdown = ft.Dropdown(
|
Themes = AvailableThemes()
|
||||||
value=Config.Theme,
|
Fonts = InstalledFontFamilies()
|
||||||
label="Theme (shared across SaraSuite)",
|
|
||||||
options=[ft.DropdownOption(key=Name, text=Name) for Name in AvailableThemes()],
|
def ThemePicker(Value: str) -> ft.Dropdown:
|
||||||
)
|
return ft.Dropdown(
|
||||||
DotfilesSwitch = ft.Switch(label="Show dotfiles", value=Config.ShowDotfiles)
|
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)
|
||||||
|
|
||||||
|
# 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)
|
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(
|
BatchRenamePatternField = ft.TextField(
|
||||||
value=Config.BatchRenamePattern,
|
value=Config.BatchRenamePattern,
|
||||||
label="Batch rename pattern (~ = name, ! = counter, ^ = extension)",
|
label="Batch rename pattern (~ = name, ! = counter, ^ = extension)",
|
||||||
@@ -166,17 +209,30 @@ def BuildSettingsDialog(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Categories: dict[str, ft.Control] = {
|
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(
|
"General": ft.Column(
|
||||||
[
|
[
|
||||||
ThemeDropdown,
|
|
||||||
DotfilesSwitch,
|
DotfilesSwitch,
|
||||||
TitleBarSwitch,
|
|
||||||
BatchRenamePatternField,
|
BatchRenamePatternField,
|
||||||
DeleteZipSwitch,
|
DeleteZipSwitch,
|
||||||
_SectionLabel("Default compress format"),
|
_SectionLabel("Default compress format"),
|
||||||
ArchiveFormatPicker,
|
ArchiveFormatPicker,
|
||||||
|
ft.Divider(height=1, color=Palette.Surface1),
|
||||||
|
OwnAppearance,
|
||||||
],
|
],
|
||||||
spacing=12,
|
spacing=12,
|
||||||
|
scroll=ft.ScrollMode.AUTO,
|
||||||
),
|
),
|
||||||
"Toolbar": ft.Column(
|
"Toolbar": ft.Column(
|
||||||
[
|
[
|
||||||
@@ -270,9 +326,16 @@ def BuildSettingsDialog(
|
|||||||
)
|
)
|
||||||
|
|
||||||
def DoSave(e=None) -> None:
|
def DoSave(e=None) -> None:
|
||||||
|
Config.UseSharedSettings = UseSharedSwitch.value
|
||||||
Config.Theme = ThemeDropdown.value
|
Config.Theme = ThemeDropdown.value
|
||||||
Config.ShowDotfiles = DotfilesSwitch.value
|
Config.FontFamily = FontDropdown.value or Config.FontFamily
|
||||||
Config.HideWindowTitleBar = TitleBarSwitch.value
|
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.BatchRenamePattern = BatchRenamePatternField.value.strip() or "~!^"
|
||||||
Config.DeleteZipAfterExtract = DeleteZipSwitch.value
|
Config.DeleteZipAfterExtract = DeleteZipSwitch.value
|
||||||
Config.DefaultArchiveFormat = GetArchiveFormat()
|
Config.DefaultArchiveFormat = GetArchiveFormat()
|
||||||
|
|||||||
Reference in new issue
Block a user