Files
SaraTune/Widgets.py
T
2026-10-09 15:33:43 +02:00

92 lines
3.5 KiB
Python

"""What both modes share: the App context (config, notifications, dialogs,
the file picker, switching modes) and small reusable controls."""
import os
import subprocess
from typing import Callable
import flet as ft
from Core import AppConfig, ChildEnvironment, ChipButton, IsWindows, Note, NotificationStyle, Palette
class App:
def __init__(self, Page: ft.Page, Config: AppConfig):
self.Page = Page
self.Config = Config
self.Picker = ft.FilePicker() # registers itself with the page
# Set by Main.
self.SwitchMode: Callable[[str], None] = lambda Name: None
self.OpenInAudio: Callable[[list[str]], None] = lambda Paths: None
def Notify(self, Text: str, Kind: str = "info") -> None:
Background, Accent = NotificationStyle(Kind)
self.Page.show_dialog(ft.SnackBar(
content=ft.Text(Text, color=Accent),
bgcolor=Background,
shape=ft.RoundedRectangleBorder(radius=4, side=ft.BorderSide(1, Accent)),
open=True,
))
def ShowDialog(self, Title: str, Content: ft.Control, Actions: list[ft.Control], Width: int = 480) -> ft.AlertDialog:
Dialog = ft.AlertDialog(title=Title, content=ft.Container(Content, width=Width), actions=Actions)
self.Page.show_dialog(Dialog)
return Dialog
def Close(self) -> None:
self.Page.pop_dialog()
def Confirm(self, Title: str, Text: str, OnYes: Callable[[], None], YesText: str = "Delete") -> None:
def Yes(e) -> None:
self.Close()
OnYes()
self.ShowDialog(Title, ft.Text(Text), [
ChipButton("Cancel", lambda e: self.Close()),
ChipButton(YesText, Yes, Accent=Palette.Red),
], Width=420)
def Prompt(self, Title: str, Label: str, OnOk: Callable[[str], None], Value: str = "", OkText: str = "OK") -> None:
"""Asks for one line of text; OnOk gets it stripped (never empty)."""
Field = ft.TextField(label=Label, value=Value, autofocus=True)
def Ok(e) -> None:
Text = (Field.value or "").strip()
if Text:
self.Close()
OnOk(Text)
Field.on_submit = Ok
self.ShowDialog(Title, Field, [ChipButton("Cancel", lambda e: self.Close()), ChipButton(OkText, Ok)], Width=380)
def OpenFolder(self, PathText: str) -> None:
try:
if IsWindows:
os.startfile(PathText) # Explorer
else:
subprocess.Popen(["xdg-open", PathText], env=ChildEnvironment(), start_new_session=True)
except OSError as Error:
self.Notify(f"Couldn't open {PathText}: {Error}", "error")
def EmptyState(Text: str, Detail: str = "", Icon=ft.Icons.MUSIC_OFF_OUTLINED) -> ft.Control:
return ft.Container(
ft.Column([
ft.Icon(Icon, color=Palette.Surface1, size=56),
ft.Text(Text, color=Palette.Overlay0, size=16),
Note(Detail),
], horizontal_alignment=ft.CrossAxisAlignment.CENTER, tight=True, spacing=8),
alignment=ft.Alignment.CENTER, expand=True,
)
def InfoTable(Rows: list[tuple[str, str]], LabelWidth: int = 110) -> ft.Column:
"""Label/value rows; values are selectable so tags can be copied."""
return ft.Column([
ft.Row([
ft.Text(Label, color=Palette.Overlay0, size=13, width=LabelWidth),
ft.Text(Value, color=Palette.Text, size=13, selectable=True, expand=True),
], vertical_alignment=ft.CrossAxisAlignment.START)
for Label, Value in Rows
], spacing=4)