Files
SaraSuite/Settings.py
T

200 lines
7.8 KiB
Python

"""Settings dialog: the shared SaraSuite settings, SaraSuite's own, and the
install defaults for each program. Laid out like SaraFem's Settings (a
category list on the left, the chosen category on the right)."""
import subprocess
from typing import Callable
import flet as ft
from Core import (
AppConfig, BundledFontFamily, ChildEnvironment, ChipButtonStyle, LoadSharedSettings, Palette,
SharedSettings, ThemesDir,
)
from Installer import DefaultAnswers, DefaultOptions
from Manifests import Program
DialogWidth = 700
DialogHeight = 480
CategoryListWidth = 150
RestartNote = "Theme, font and title bar changes apply the next time a program starts."
def _Note(Text: str) -> ft.Text:
return ft.Text(Text, color=Palette.Overlay0, size=12)
def _Themes() -> list[str]:
Names = {Path.stem for Path in ThemesDir.glob("*.toml")} if ThemesDir.is_dir() else set()
Names.add("CatppuccinMocha") # built in, even before SaraFem seeds the folder
return sorted(Names)
def _Fonts() -> list[str]:
"""Installed font families (fontconfig), with the bundled font first."""
try:
Result = subprocess.run(
["fc-list", ":", "family"], capture_output=True, text=True, timeout=5, env=ChildEnvironment(),
)
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 ShowSettingsDialog(Page: ft.Page, Config: AppConfig, Programs: list[Program], OnSaved: Callable[[], None]) -> None:
Themes = _Themes()
Fonts = _Fonts()
def ThemePicker(Value: str) -> ft.Dropdown:
Names = Themes if Value in Themes else [Value] + Themes
return ft.Dropdown(value=Value, label="Theme", options=[ft.DropdownOption(key=Name, text=Name) for Name in Names])
def FontPicker(Value: str) -> ft.Dropdown:
# 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],
)
# Shared: ~/.config/SaraSuite/Config.toml, read by every SaraSuite program.
Shared = LoadSharedSettings(Config)
UseSharedSwitch = ft.Switch(label="SaraSuite uses these shared settings", value=Config.UseSharedSettings)
SharedTheme = ThemePicker(Shared.Theme)
SharedFont = FontPicker(Shared.FontFamily)
SharedTitleBar = ft.Switch(label="Hide window title bar", value=Shared.HideWindowTitleBar)
# SaraSuite's own appearance, only used with shared settings off.
OwnTheme = ThemePicker(Config.Theme)
OwnFont = FontPicker(Config.FontFamily)
OwnTitleBar = ft.Switch(label="Hide window title bar", value=Config.HideWindowTitleBar)
OwnAppearance = ft.Column(
[_Note("SaraSuite's own appearance, used when shared settings are off"), OwnTheme, OwnFont, OwnTitleBar],
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
GitHostField = ft.TextField(
value=Config.GitHost, label="Git host", hint_text="https://git.srhtnin.garden/sarah",
)
Categories: dict[str, ft.Control] = {
"Shared": ft.Column(
[
_Note("Shared by every SaraSuite program (~/.config/SaraSuite/Config.toml)."),
UseSharedSwitch,
ft.Divider(height=1, color=Palette.Surface1),
SharedTheme,
SharedFont,
SharedTitleBar,
],
spacing=12,
),
"SaraSuite": ft.Column(
[
GitHostField,
_Note("Where programs are installed and updated from; each program's repo is <Git host>/<Name>."),
ft.Divider(height=1, color=Palette.Surface1),
OwnAppearance,
],
spacing=12,
scroll=ft.ScrollMode.AUTO,
),
}
# One tab per program: what its install dialog starts with.
DefaultBoxes: dict[str, dict[str, ft.Checkbox]] = {}
DefaultFields: dict[str, dict[str, ft.TextField]] = {}
for Current in Programs:
if not Current.Options and not Current.Questions:
continue
Answers = DefaultAnswers(Current)
Ticked = DefaultOptions(Current)
DefaultFields[Current.Name] = {
Question.Id: ft.TextField(label=Question.Prompt, value=Answers[Question.Id]) for Question in Current.Questions
}
DefaultBoxes[Current.Name] = {
Option.Id: ft.Checkbox(label=Option.Label, value=Ticked[Option.Id]) for Option in Current.Options
}
Categories[Current.Name] = ft.Column(
[_Note(f"What installing {Current.Name} starts with.")]
+ list(DefaultFields[Current.Name].values())
+ list(DefaultBoxes[Current.Name].values()),
spacing=12,
scroll=ft.ScrollMode.AUTO,
)
DetailArea = ft.Container(Categories["Shared"], 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()
CategoryList.update()
for Name in Categories:
CategoryTiles[Name] = ft.ListTile(
title=Name,
selected=Name == "Shared",
selected_tile_color=Palette.Surface1,
selected_color=Palette.Mauve,
on_click=lambda e, n=Name: SelectCategory(n),
)
CategoryList = ft.Column(list(CategoryTiles.values()), spacing=0)
def Save(e) -> None:
Config.UseSharedSettings = UseSharedSwitch.value
Config.Theme = OwnTheme.value
Config.FontFamily = OwnFont.value or Config.FontFamily
Config.HideWindowTitleBar = OwnTitleBar.value
Config.GitHost = (GitHostField.value or "").strip().rstrip("/") or Config.GitHost
for Name, Boxes in DefaultBoxes.items():
Config.OptionDefaults[Name] = {Id: bool(Box.value) for Id, Box in Boxes.items()}
for Name, Fields in DefaultFields.items():
if Fields:
Config.AnswerDefaults[Name] = {Id: (Field.value or "").strip() for Id, Field in Fields.items()}
Config.Save()
SharedSettings(
Theme=SharedTheme.value,
FontFamily=SharedFont.value or Shared.FontFamily,
HideWindowTitleBar=SharedTitleBar.value,
).Save()
Page.pop_dialog()
OnSaved()
Page.show_dialog(ft.AlertDialog(
title="Settings",
content=ft.Column(
[
ft.Row(
[
ft.Container(
CategoryList, width=CategoryListWidth, bgcolor=Palette.Surface0, border_radius=4, padding=4,
),
DetailArea,
],
expand=True,
),
ft.Divider(height=1, color=Palette.Surface1),
_Note(RestartNote),
],
width=DialogWidth,
height=DialogHeight,
),
actions=[
ft.OutlinedButton(content="Cancel", style=ChipButtonStyle(), on_click=lambda e: Page.pop_dialog()),
ft.OutlinedButton(content="Save", style=ChipButtonStyle(), on_click=Save),
],
))