feat: add setting to select splash screen (#1077)

* feat: add setting to select splash screen

* feat: add random setting to splash screens
This commit is contained in:
Travis Abendshien
2025-09-01 12:01:32 -07:00
committed by GitHub
parent 2f4b72fd4d
commit 1ae92a3661
7 changed files with 147 additions and 104 deletions

View File

@@ -3,7 +3,7 @@
import platform
from datetime import datetime
from enum import Enum
from enum import Enum, IntEnum, StrEnum
from pathlib import Path
from typing import override
@@ -13,34 +13,41 @@ from pydantic import BaseModel, Field
from tagstudio.core.enums import ShowFilepathOption, TagClickActionOption
if platform.system() == "Windows":
DEFAULT_GLOBAL_SETTINGS_PATH = (
Path.home() / "Appdata" / "Roaming" / "TagStudio" / "settings.toml"
)
else:
DEFAULT_GLOBAL_SETTINGS_PATH = Path.home() / ".config" / "TagStudio" / "settings.toml"
DEFAULT_GLOBAL_SETTINGS_PATH = (
Path.home() / "Appdata" / "Roaming" / "TagStudio" / "settings.toml"
if platform.system() == "Windows"
else Path.home() / ".config" / "TagStudio" / "settings.toml"
)
logger = structlog.get_logger(__name__)
class TomlEnumEncoder(toml.TomlEncoder):
@override
def dump_value(self, v):
def dump_value(self, v): # pyright: ignore[reportMissingParameterType]
if isinstance(v, Enum):
return super().dump_value(v.value)
return super().dump_value(v)
class Theme(Enum):
class Theme(IntEnum):
DARK = 0
LIGHT = 1
SYSTEM = 2
DEFAULT = SYSTEM
class Splash(StrEnum):
DEFAULT = "default"
RANDOM = "random"
CLASSIC = "classic"
GOO_GEARS = "goo_gears"
NINETY_FIVE = "95"
# NOTE: pydantic also has a BaseSettings class (from pydantic-settings) that allows any settings
# properties to be overwritten with environment variables. as tagstudio is not currently using
# environment variables, i did not base it on that, but that may be useful in the future.
# properties to be overwritten with environment variables. As TagStudio is not currently using
# environment variables, this was not based on that, but that may be useful in the future.
class GlobalSettings(BaseModel):
language: str = Field(default="en")
open_last_loaded_on_startup: bool = Field(default=True)
@@ -50,8 +57,9 @@ class GlobalSettings(BaseModel):
show_filenames_in_grid: bool = Field(default=True)
page_size: int = Field(default=100)
show_filepath: ShowFilepathOption = Field(default=ShowFilepathOption.DEFAULT)
theme: Theme = Field(default=Theme.SYSTEM)
tag_click_action: TagClickActionOption = Field(default=TagClickActionOption.DEFAULT)
theme: Theme = Field(default=Theme.SYSTEM)
splash: Splash = Field(default=Splash.DEFAULT)
date_format: str = Field(default="%x")
hour_format: bool = Field(default=True)

View File

@@ -3,7 +3,7 @@
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any
from PySide6.QtCore import Qt
from PySide6.QtWidgets import (
@@ -18,65 +18,65 @@ from PySide6.QtWidgets import (
)
from tagstudio.core.enums import ShowFilepathOption, TagClickActionOption
from tagstudio.core.global_settings import Theme
from tagstudio.core.global_settings import Splash, Theme
from tagstudio.qt.translations import DEFAULT_TRANSLATION, LANGUAGES, Translations
from tagstudio.qt.widgets.panel import PanelModal, PanelWidget
if TYPE_CHECKING:
from tagstudio.qt.ts_qt import QtDriver
FILEPATH_OPTION_MAP: dict[ShowFilepathOption, str] = {}
THEME_MAP: dict[Theme, str] = {}
TAG_CLICK_ACTION_MAP: dict[TagClickActionOption, str] = {}
DATE_FORMAT_MAP: dict[str, str] = {
"%d/%m/%y": "21/08/24",
"%d/%m/%Y": "21/08/2024",
"%d.%m.%y": "21.08.24",
"%d.%m.%Y": "21.08.2024",
"%d-%m-%y": "21-08-24",
"%d-%m-%Y": "21-08-2024",
"%x": "08/21/24",
"%m/%d/%Y": "08/21/2024",
"%m-%d-%y": "08-21-24",
"%m-%d-%Y": "08-21-2024",
"%m.%d.%y": "08.21.24",
"%m.%d.%Y": "08.21.2024",
"%Y/%m/%d": "2024/08/21",
"%Y-%m-%d": "2024-08-21",
"%Y.%m.%d": "2024.08.21",
}
class SettingsPanel(PanelWidget):
driver: "QtDriver"
filepath_option_map: dict[ShowFilepathOption, str] = {
ShowFilepathOption.SHOW_FULL_PATHS: Translations["settings.filepath.option.full"],
ShowFilepathOption.SHOW_RELATIVE_PATHS: Translations["settings.filepath.option.relative"],
ShowFilepathOption.SHOW_FILENAMES_ONLY: Translations["settings.filepath.option.name"],
}
theme_map: dict[Theme, str] = {
Theme.SYSTEM: Translations["settings.theme.system"],
Theme.DARK: Translations["settings.theme.dark"],
Theme.LIGHT: Translations["settings.theme.light"],
}
splash_map: dict[Splash, str] = {
Splash.DEFAULT: Translations["settings.splash.option.default"],
Splash.RANDOM: Translations["settings.splash.option.random"],
Splash.CLASSIC: Translations["settings.splash.option.classic"],
Splash.GOO_GEARS: Translations["settings.splash.option.goo_gears"],
Splash.NINETY_FIVE: Translations["settings.splash.option.ninety_five"],
}
tag_click_action_map: dict[TagClickActionOption, str] = {
TagClickActionOption.OPEN_EDIT: Translations["settings.tag_click_action.open_edit"],
TagClickActionOption.SET_SEARCH: Translations["settings.tag_click_action.set_search"],
TagClickActionOption.ADD_TO_SEARCH: Translations["settings.tag_click_action.add_to_search"],
}
date_format_map: dict[str, str] = {
"%d/%m/%y": "21/08/24",
"%d/%m/%Y": "21/08/2024",
"%d.%m.%y": "21.08.24",
"%d.%m.%Y": "21.08.2024",
"%d-%m-%y": "21-08-24",
"%d-%m-%Y": "21-08-2024",
"%x": "08/21/24",
"%m/%d/%Y": "08/21/2024",
"%m-%d-%y": "08-21-24",
"%m-%d-%Y": "08-21-2024",
"%m.%d.%y": "08.21.24",
"%m.%d.%Y": "08.21.2024",
"%Y/%m/%d": "2024/08/21",
"%Y-%m-%d": "2024-08-21",
"%Y.%m.%d": "2024.08.21",
}
def __init__(self, driver: "QtDriver"):
super().__init__()
# set these "constants" because language will be loaded from config shortly after startup
# and we want to use the current language for the dropdowns
global FILEPATH_OPTION_MAP, THEME_MAP, TAG_CLICK_ACTION_MAP
FILEPATH_OPTION_MAP = {
ShowFilepathOption.SHOW_FULL_PATHS: Translations["settings.filepath.option.full"],
ShowFilepathOption.SHOW_RELATIVE_PATHS: Translations[
"settings.filepath.option.relative"
],
ShowFilepathOption.SHOW_FILENAMES_ONLY: Translations["settings.filepath.option.name"],
}
THEME_MAP = {
Theme.DARK: Translations["settings.theme.dark"],
Theme.LIGHT: Translations["settings.theme.light"],
Theme.SYSTEM: Translations["settings.theme.system"],
}
TAG_CLICK_ACTION_MAP = {
TagClickActionOption.OPEN_EDIT: Translations["settings.tag_click_action.open_edit"],
TagClickActionOption.SET_SEARCH: Translations["settings.tag_click_action.set_search"],
TagClickActionOption.ADD_TO_SEARCH: Translations[
"settings.tag_click_action.add_to_search"
],
}
self.driver = driver
self.setMinimumSize(400, 300)
@@ -84,6 +84,8 @@ class SettingsPanel(PanelWidget):
self.root_layout = QVBoxLayout(self)
self.root_layout.setContentsMargins(0, 6, 0, 0)
self.library_settings_container = QWidget()
# Tabs
self.tab_widget = QTabWidget()
@@ -135,6 +137,7 @@ class SettingsPanel(PanelWidget):
Translations["settings.open_library_on_start"], self.open_last_lib_checkbox
)
# Generate Thumbnails
self.generate_thumbs = QCheckBox()
self.generate_thumbs.setChecked(self.driver.settings.generate_thumbs)
form_layout.addRow(Translations["settings.generate_thumbs"], self.generate_thumbs)
@@ -165,49 +168,61 @@ class SettingsPanel(PanelWidget):
# Show Filepath
self.filepath_combobox = QComboBox()
for k in FILEPATH_OPTION_MAP:
self.filepath_combobox.addItem(FILEPATH_OPTION_MAP[k], k)
for k in SettingsPanel.filepath_option_map:
self.filepath_combobox.addItem(SettingsPanel.filepath_option_map[k], k)
filepath_option: ShowFilepathOption = self.driver.settings.show_filepath
if filepath_option not in FILEPATH_OPTION_MAP:
if filepath_option not in SettingsPanel.filepath_option_map:
filepath_option = ShowFilepathOption.DEFAULT
self.filepath_combobox.setCurrentIndex(
list(FILEPATH_OPTION_MAP.keys()).index(filepath_option)
list(SettingsPanel.filepath_option_map.keys()).index(filepath_option)
)
form_layout.addRow(Translations["settings.filepath.label"], self.filepath_combobox)
# Dark Mode
self.theme_combobox = QComboBox()
for k in THEME_MAP:
self.theme_combobox.addItem(THEME_MAP[k], k)
theme = self.driver.settings.theme
if theme not in THEME_MAP:
theme = Theme.DEFAULT
self.theme_combobox.setCurrentIndex(list(THEME_MAP.keys()).index(theme))
self.theme_combobox.currentIndexChanged.connect(self.__update_restart_label)
form_layout.addRow(Translations["settings.theme.label"], self.theme_combobox)
# Tag Click Action
self.tag_click_action_combobox = QComboBox()
for k in TAG_CLICK_ACTION_MAP:
self.tag_click_action_combobox.addItem(TAG_CLICK_ACTION_MAP[k], k)
for k in SettingsPanel.tag_click_action_map:
self.tag_click_action_combobox.addItem(SettingsPanel.tag_click_action_map[k], k)
tag_click_action = self.driver.settings.tag_click_action
if tag_click_action not in TAG_CLICK_ACTION_MAP:
if tag_click_action not in SettingsPanel.tag_click_action_map:
tag_click_action = TagClickActionOption.DEFAULT
self.tag_click_action_combobox.setCurrentIndex(
list(TAG_CLICK_ACTION_MAP.keys()).index(tag_click_action)
list(SettingsPanel.tag_click_action_map.keys()).index(tag_click_action)
)
form_layout.addRow(
Translations["settings.tag_click_action.label"], self.tag_click_action_combobox
)
# Dark Mode
self.theme_combobox = QComboBox()
for k in SettingsPanel.theme_map:
self.theme_combobox.addItem(SettingsPanel.theme_map[k], k)
theme = self.driver.settings.theme
if theme not in SettingsPanel.theme_map:
theme = Theme.DEFAULT
self.theme_combobox.setCurrentIndex(list(SettingsPanel.theme_map.keys()).index(theme))
self.theme_combobox.currentIndexChanged.connect(self.__update_restart_label)
form_layout.addRow(Translations["settings.theme.label"], self.theme_combobox)
# Splash Screen
self.splash_combobox = QComboBox()
for k in SettingsPanel.splash_map:
self.splash_combobox.addItem(SettingsPanel.splash_map[k], k)
splash = self.driver.settings.splash
if splash not in SettingsPanel.splash_map:
splash = Splash.DEFAULT
self.splash_combobox.setCurrentIndex(list(SettingsPanel.splash_map.keys()).index(splash))
form_layout.addRow(Translations["settings.splash.label"], self.splash_combobox)
# Date Format
self.dateformat_combobox = QComboBox()
for k in DATE_FORMAT_MAP:
self.dateformat_combobox.addItem(DATE_FORMAT_MAP[k], k)
for k in SettingsPanel.date_format_map:
self.dateformat_combobox.addItem(SettingsPanel.date_format_map[k], k)
dateformat: str = self.driver.settings.date_format
if dateformat not in DATE_FORMAT_MAP:
if dateformat not in SettingsPanel.date_format_map:
dateformat = "%x"
self.dateformat_combobox.setCurrentIndex(list(DATE_FORMAT_MAP.keys()).index(dateformat))
self.dateformat_combobox.setCurrentIndex(
list(SettingsPanel.date_format_map.keys()).index(dateformat)
)
self.dateformat_combobox.currentIndexChanged.connect(self.__update_restart_label)
form_layout.addRow(Translations["settings.dateformat.label"], self.dateformat_combobox)
@@ -221,8 +236,8 @@ class SettingsPanel(PanelWidget):
self.zeropadding_checkbox.setChecked(self.driver.settings.zero_padding)
form_layout.addRow(Translations["settings.zeropadding.label"], self.zeropadding_checkbox)
def __build_library_settings(self):
self.library_settings_container = QWidget()
# TODO: Implement Library Settings
def __build_library_settings(self): # pyright: ignore[reportUnusedFunction]
form_layout = QFormLayout(self.library_settings_container)
form_layout.setContentsMargins(6, 6, 6, 6)
@@ -232,7 +247,7 @@ class SettingsPanel(PanelWidget):
def __get_language(self) -> str:
return list(LANGUAGES.values())[self.language_combobox.currentIndex()]
def get_settings(self) -> dict:
def get_settings(self) -> dict[str, Any]: # pyright: ignore[reportExplicitAny]
return {
"language": self.__get_language(),
"open_last_loaded_on_startup": self.open_last_lib_checkbox.isChecked(),
@@ -246,6 +261,7 @@ class SettingsPanel(PanelWidget):
"date_format": self.dateformat_combobox.currentData(),
"hour_format": self.hourformat_checkbox.isChecked(),
"zero_padding": self.zeropadding_checkbox.isChecked(),
"splash": self.splash_combobox.currentData(),
}
def update_settings(self, driver: "QtDriver"):
@@ -263,6 +279,7 @@ class SettingsPanel(PanelWidget):
driver.settings.date_format = settings["date_format"]
driver.settings.hour_format = settings["hour_format"]
driver.settings.zero_padding = settings["zero_padding"]
driver.settings.splash = settings["splash"]
driver.settings.save()

View File

@@ -7,6 +7,10 @@
"path": "qt/images/splash/goo_gears.png",
"mode": "qpixmap"
},
"splash_95": {
"path": "qt/images/splash/95.png",
"mode": "qpixmap"
},
"icon": {
"path": "icon.png",
"mode": "pil"

View File

@@ -4,6 +4,7 @@
import math
import random
import structlog
from PySide6.QtCore import QRect, Qt
@@ -11,12 +12,13 @@ from PySide6.QtGui import QColor, QFont, QPainter, QPen, QPixmap
from PySide6.QtWidgets import QSplashScreen, QWidget
from tagstudio.core.constants import VERSION, VERSION_BRANCH
from tagstudio.core.global_settings import Splash
from tagstudio.qt.resource_manager import ResourceManager
logger = structlog.get_logger(__name__)
class Splash:
class SplashScreen:
"""The custom splash screen widget for TagStudio."""
COPYRIGHT_YEARS: str = "2021-2025"
@@ -24,11 +26,7 @@ class Splash:
VERSION_STR: str = (
f"Version {VERSION} {(' (' + VERSION_BRANCH + ')') if VERSION_BRANCH else ''}"
)
SPLASH_CLASSIC: str = "classic"
SPLASH_GOO_GEARS: str = "goo_gears"
SPLASH_95: str = "95"
DEFAULT_SPLASH: str = SPLASH_GOO_GEARS
DEFAULT_SPLASH = Splash.GOO_GEARS
def __init__(
self,
@@ -41,11 +39,19 @@ class Splash:
self.screen_width = screen_width
self.ratio: float = device_ratio
self.splash_screen: QSplashScreen | None = None
self.splash_name: str = splash_name if splash_name else Splash.DEFAULT_SPLASH
if not splash_name or splash_name == Splash.DEFAULT:
self.splash_name: str = SplashScreen.DEFAULT_SPLASH
elif splash_name == Splash.RANDOM:
splash_list = list(Splash)
splash_list.remove(Splash.DEFAULT)
splash_list.remove(Splash.RANDOM)
self.splash_name = random.choice(splash_list)
else:
self.splash_name = splash_name
def get_pixmap(self) -> QPixmap:
"""Get the pixmap used for the splash screen."""
pixmap: QPixmap | None = self.rm.get(f"splash_{self.splash_name}")
pixmap: QPixmap | None = self.rm.get(f"splash_{self.splash_name}") # pyright: ignore[reportAssignmentType]
if not pixmap:
logger.error("[Splash] Splash screen not found:", splash_name=self.splash_name)
pixmap = QPixmap(960, 540)
@@ -55,10 +61,12 @@ class Splash:
match painter.font().family():
case "Segoe UI":
point_size_scale = 0.75
case _:
pass
# TODO: Store any differing data elsewhere and load dynamically instead of hardcoding.
match self.splash_name:
case Splash.SPLASH_CLASSIC:
case Splash.CLASSIC:
# Copyright
font = painter.font()
font.setPointSize(math.floor(22 * point_size_scale))
@@ -68,7 +76,7 @@ class Splash:
painter.drawText(
QRect(0, -50, 960, 540),
int(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignHCenter),
Splash.COPYRIGHT_STR,
SplashScreen.COPYRIGHT_STR,
)
# Version
pen = QPen(QColor("#809782ff"))
@@ -76,10 +84,10 @@ class Splash:
painter.drawText(
QRect(0, -25, 960, 540),
int(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignHCenter),
Splash.VERSION_STR,
SplashScreen.VERSION_STR,
)
case Splash.SPLASH_GOO_GEARS:
case Splash.GOO_GEARS:
# Copyright
font = painter.font()
font.setPointSize(math.floor(22 * point_size_scale))
@@ -88,7 +96,7 @@ class Splash:
painter.setPen(pen)
painter.drawText(
QRect(40, 450, 960, 540),
Splash.COPYRIGHT_STR,
SplashScreen.COPYRIGHT_STR,
)
# Version
font = painter.font()
@@ -98,10 +106,10 @@ class Splash:
painter.setPen(pen)
painter.drawText(
QRect(40, 475, 960, 540),
Splash.VERSION_STR,
SplashScreen.VERSION_STR,
)
case Splash.SPLASH_95:
case Splash.NINETY_FIVE:
# Copyright
font = QFont()
font.setFamily("Times")
@@ -114,7 +122,7 @@ class Splash:
painter.drawText(
QRect(88, -25, 960, 540),
int(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignLeft),
Splash.COPYRIGHT_STR,
SplashScreen.COPYRIGHT_STR,
)
# Version
font.setPointSize(math.floor(22 * point_size_scale))
@@ -124,7 +132,7 @@ class Splash:
painter.drawText(
QRect(-30, 25, 960, 540),
int(Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignRight),
Splash.VERSION_STR,
SplashScreen.VERSION_STR,
)
case _:

View File

@@ -91,7 +91,7 @@ from tagstudio.qt.modals.tag_database import TagDatabasePanel
from tagstudio.qt.modals.tag_search import TagSearchModal
from tagstudio.qt.platform_strings import trash_term
from tagstudio.qt.resource_manager import ResourceManager
from tagstudio.qt.splash import Splash
from tagstudio.qt.splash import SplashScreen
from tagstudio.qt.translations import Translations
from tagstudio.qt.widgets.item_thumb import BadgeType, ItemThumb
from tagstudio.qt.widgets.migration_modal import JsonMigrationModal
@@ -327,10 +327,10 @@ class QtDriver(DriverMixin, QObject):
self.main_window.dragMoveEvent = self.drag_move_event
self.main_window.dropEvent = self.drop_event
self.splash: Splash = Splash(
self.splash: SplashScreen = SplashScreen(
resource_manager=self.rm,
screen_width=QGuiApplication.primaryScreen().geometry().width(),
splash_name="", # TODO: Get splash name from config
splash_name=self.settings.splash,
device_ratio=self.main_window.devicePixelRatio(),
)
self.splash.show()

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

View File

@@ -273,6 +273,12 @@
"settings.restart_required": "Please restart TagStudio for changes to take effect.",
"settings.show_filenames_in_grid": "Show Filenames in Grid",
"settings.show_recent_libraries": "Show Recent Libraries",
"settings.splash.label": "Splash Screen",
"settings.splash.option.classic": "Classic (9.0)",
"settings.splash.option.default": "Default",
"settings.splash.option.goo_gears": "Open Source (9.4)",
"settings.splash.option.ninety_five": "'95 (9.5)",
"settings.splash.option.random": "Random",
"settings.tag_click_action.add_to_search": "Add Tag to Search",
"settings.tag_click_action.label": "Tag Click Action",
"settings.tag_click_action.open_edit": "Edit Tag",