ui: add v9.6 assets, update misc resources
@@ -10,7 +10,7 @@
|
||||
[](https://github.com/TagStudioDev/TagStudio/actions/workflows/ruff.yaml)
|
||||
|
||||
<p align="center">
|
||||
<img width="60%" src="docs/assets/ts-9-3_logo_text.png">
|
||||
<img width="60%" src="docs/assets/tagstudio_logo-text_color.png">
|
||||
</p>
|
||||
|
||||
TagStudio is a photo & file organization application with an underlying tag-based system that focuses on giving freedom and flexibility to the user. No proprietary programs or formats, no sea of sidecar files, and no complete upheaval of your filesystem structure. **Read the documentation and more at [docs.tagstud.io](https://docs.tagstud.io)!**
|
||||
|
||||
@@ -33,17 +33,18 @@ SPDX-License-Identifier = "GPL-3.0-or-later"
|
||||
|
||||
[[annotations]]
|
||||
path = [
|
||||
"src/tagstudio/resources/qt/images/bxs-clipboard-regular.png",
|
||||
"src/tagstudio/resources/qt/images/bxs-left-arrow.png",
|
||||
"src/tagstudio/resources/qt/images/bxs-pencil-solid.png",
|
||||
"src/tagstudio/resources/qt/images/bxs-right-arrow.png",
|
||||
"src/tagstudio/resources/qt/images/bxs-trash-solid.png",
|
||||
"src/tagstudio/resources/qt/images/bxs-volume-full-solid.png",
|
||||
"src/tagstudio/resources/qt/images/file_icons/database.png",
|
||||
]
|
||||
SPDX-FileCopyrightText = "(c) 2026 Boxicons"
|
||||
SPDX-License-Identifier = "MIT"
|
||||
|
||||
[[annotations]]
|
||||
path = [
|
||||
"src/tagstudio/resources/qt/images/volume.svg",
|
||||
"src/tagstudio/resources/qt/images/volume_mute.svg",
|
||||
]
|
||||
path = ["src/tagstudio/resources/qt/images/dupe_file_stat.png"]
|
||||
SPDX-FileCopyrightText = "(c) github:google/material-design-icons Contributors"
|
||||
SPDX-License-Identifier = "Apache-2.0"
|
||||
|
||||
BIN
docs/assets/favicon.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 158 KiB |
|
Before Width: | Height: | Size: 361 KiB |
|
Before Width: | Height: | Size: 677 KiB |
@@ -1,10 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 739 739" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(0.986683,0,0,0.986683,-136.081,-136.081)">
|
||||
<path d="M535.939,863.161C515.931,843.153 203.505,529.713 183.497,509.705C169.086,495.294 161.469,476.645 160.649,457.754C160.046,443.845 138.078,230.102 137.923,217.139C137.681,196.785 145.323,176.356 160.839,160.839C177.115,144.564 198.795,136.951 220.125,138.016C232.439,138.63 447.036,159.52 461.817,160.931C479.3,162.6 496.329,170.12 509.705,183.497C523.113,196.904 849.753,522.531 863.161,535.939C893.716,566.494 893.716,616.108 863.161,646.663L646.663,863.161C616.108,893.716 566.494,893.716 535.939,863.161ZM321.355,223.613C296.045,198.303 254.947,198.303 229.636,223.613C204.326,248.924 204.326,290.022 229.636,315.332C254.947,340.643 296.045,340.643 321.355,315.332C346.666,290.022 346.666,248.924 321.355,223.613ZM362.109,606.786C409.476,654.152 424.103,598.401 454.027,606.786C468.584,610.865 453.72,642.505 443.028,673.551C425.086,725.641 484.094,757.817 516.601,720.743C545.603,687.667 503.579,655.692 520.581,632.527C537.795,609.074 563.542,633.319 565.542,665.527C568.921,719.955 535.825,735.585 543.999,774.591C553.59,820.348 624.181,827.565 638,774.591C647.736,737.269 603.102,705.31 628.352,644.476C636.209,625.545 662.786,619.154 669.759,644.476C673.976,659.791 660.264,670.152 666.759,693.55C674.41,721.114 725.088,732.96 740.374,693.55C746.873,676.793 734.853,651.273 731.597,640.406C714.283,582.611 826.807,582.426 762.374,517.789C703.034,458.263 493.6,249.017 493.6,249.017C479.164,234.58 457.464,234.58 443.028,249.017L249.017,443.028C234.58,457.464 234.58,479.164 249.017,493.6C249.017,493.6 340.149,584.825 362.109,606.786Z" style="fill:white;"/>
|
||||
</g>
|
||||
<g transform="matrix(0.986683,0,0,0.986683,-136.081,-136.081)">
|
||||
<path d="M733.962,560.164C740.987,553.139 740.987,541.733 733.962,534.708L482.173,282.92C475.148,275.895 463.742,275.895 456.717,282.92L431.261,308.376C424.237,315.4 424.237,326.807 431.261,333.831L683.05,585.62C690.075,592.645 701.481,592.645 708.506,585.62L733.962,560.164ZM439.639,559.207C446.664,552.182 446.664,540.776 439.639,533.751L335.491,429.602C328.466,422.578 317.059,422.578 310.035,429.602L284.579,455.058C277.554,462.083 277.554,473.489 284.579,480.514L388.728,584.663C395.752,591.688 407.159,591.688 414.184,584.663L439.639,559.207ZM584.306,556.624C591.331,549.599 591.331,538.192 584.306,531.168L409.115,355.977C402.091,348.953 390.684,348.953 383.66,355.977L358.204,381.433C351.179,388.458 351.179,399.864 358.204,406.889L533.394,582.079C540.419,589.104 551.825,589.104 558.85,582.079L584.306,556.624ZM298.425,246.543C311.081,259.198 311.081,279.747 298.425,292.402C285.77,305.058 265.221,305.058 252.566,292.402C239.911,279.747 239.911,259.198 252.566,246.543C265.221,233.888 285.77,233.888 298.425,246.543Z" style="fill:white;"/>
|
||||
<svg width="100%" height="100%" viewBox="0 0 944 944" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(0.707107,0.707107,-0.707107,0.707107,48.0404,-676.037)">
|
||||
<path d="M754.971,240.435C773.719,224.654 797.438,216 821.944,216L1440,216C1497.44,216 1544,262.562 1544,320L1544,704C1544,761.438 1497.44,808 1440,808L821.944,808C797.438,808 773.719,799.346 754.971,783.565L526.872,591.565C503.396,571.805 489.845,542.685 489.845,512C489.845,481.315 503.396,452.195 526.872,432.435L754.971,240.435ZM1059.36,727.98C1142.98,727.98 1106.68,665.856 1140.5,646.842C1156.95,637.593 1171.76,678.644 1189.72,715.486C1219.87,777.305 1300.35,753.619 1296.32,692.2C1292.73,637.402 1227.41,646.272 1221.97,610.817C1216.46,574.918 1260.59,573.593 1290.78,600.258C1341.81,645.319 1326.39,688.332 1368.04,715.546C1416.89,747.471 1485.58,691.53 1451.02,632.571C1426.66,591.033 1359.05,602.221 1327.64,526.233C1317.87,502.586 1335.69,473.485 1364.19,489.683C1381.44,499.479 1378.48,520.729 1404.87,535.649C1435.95,553.226 1491.14,518.948 1469.85,470.669C1460.79,450.139 1427.65,438.223 1415.19,431.504C1348.89,395.77 1448.05,296.282 1334.12,296.102C1229.19,295.936 859.62,296.102 859.62,296.102C834.132,296.102 814.978,315.256 814.978,340.744L814.978,683.256C814.978,708.744 834.132,727.898 859.62,727.898C859.62,727.898 1020.59,727.98 1059.36,727.98ZM1085.8,618L1085.8,662C1085.8,674.142 1075.94,684 1063.8,684L878,684C865.858,684 856,674.142 856,662L856,618C856,605.858 865.858,596 878,596L1063.8,596C1075.94,596 1085.8,605.858 1085.8,618ZM1346.44,362L1346.44,406C1346.44,418.142 1336.59,428 1324.44,428L878,428C865.858,428 856,418.142 856,406L856,362C856,349.858 865.858,340 878,340L1324.44,340C1336.59,340 1346.44,349.858 1346.44,362ZM1216,490L1216,534C1216,546.142 1206.14,556 1194,556L878,556C865.858,556 856,546.142 856,534L856,490C856,477.858 865.858,468 878,468L1194,468C1206.14,468 1216,477.858 1216,490ZM752.58,455.42C721.352,424.193 670.648,424.193 639.42,455.42C608.193,486.648 608.193,537.352 639.42,568.58C670.648,599.807 721.352,599.807 752.58,568.58C783.807,537.352 783.807,486.648 752.58,455.42ZM724.29,483.71C739.903,499.324 739.903,524.676 724.29,540.29C708.676,555.903 683.324,555.903 667.71,540.29C652.097,524.676 652.097,499.324 667.71,483.71C683.324,468.097 708.676,468.097 724.29,483.71Z" style="fill:white;"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 156 KiB |
|
Before Width: | Height: | Size: 234 KiB After Width: | Height: | Size: 100 B |
1
docs/assets/tagstudio_logo-text_color.png
Symbolic link
@@ -0,0 +1 @@
|
||||
/Users/cyanvoxel/Local/Dev/TagStudio/src/tagstudio/resources/qt/images/tagstudio_logo-text_color.png
|
||||
|
Before Width: | Height: | Size: 234 KiB After Width: | Height: | Size: 100 B |
@@ -85,7 +85,7 @@ theme:
|
||||
icon: material/lightbulb-night-outline
|
||||
name: Switch to System Preference
|
||||
logo: assets/icon_mono.svg
|
||||
favicon: assets/icon.ico
|
||||
favicon: assets/favicon.png
|
||||
font:
|
||||
code: Jetbrains Mono
|
||||
language: en
|
||||
|
||||
@@ -66,7 +66,7 @@ class TagColorEnum(enum.IntEnum):
|
||||
|
||||
class ItemType(enum.Enum):
|
||||
ENTRY = 0
|
||||
COLLATION = 1
|
||||
ENTRY_GROUP = 1
|
||||
TAG_GROUP = 2
|
||||
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ class Splash(StrEnum):
|
||||
CLASSIC = "classic"
|
||||
GOO_GEARS = "goo_gears"
|
||||
NINETY_FIVE = "95"
|
||||
AURORA = "aurora"
|
||||
|
||||
|
||||
class TomlEnumEncoder(toml.TomlEncoder):
|
||||
|
||||
@@ -16,11 +16,14 @@ _THEME_DARK_BG: str = "#000000DD"
|
||||
_THEME_LIGHT_BG: str = "#FFFFFF55"
|
||||
|
||||
|
||||
def theme_fg_overlay(image: Image.Image, use_alpha: bool = True) -> Image.Image:
|
||||
def theme_fg_overlay(
|
||||
image: Image.Image, inverse: bool = False, use_alpha: bool = True
|
||||
) -> Image.Image:
|
||||
"""Overlay the foreground theme color onto an image.
|
||||
|
||||
Args:
|
||||
image (Image): The PIL Image object to apply an overlay to.
|
||||
inverse (bool): Option inverse the overlay color relative to the current theme.
|
||||
use_alpha (bool): Option to retain the base image's alpha value when applying the overlay.
|
||||
"""
|
||||
dark_fg: str = _THEME_DARK_FG[:-2] if not use_alpha else _THEME_DARK_FG
|
||||
@@ -29,6 +32,8 @@ def theme_fg_overlay(image: Image.Image, use_alpha: bool = True) -> Image.Image:
|
||||
overlay_color = (
|
||||
dark_fg if QGuiApplication.styleHints().colorScheme() is Qt.ColorScheme.Dark else light_fg
|
||||
)
|
||||
if inverse:
|
||||
overlay_color = light_fg if overlay_color == dark_fg else dark_fg
|
||||
|
||||
im = Image.new(mode="RGBA", size=image.size, color=overlay_color)
|
||||
return _apply_overlay(image, im)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
|
||||
import math
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import ImageQt
|
||||
from PySide6.QtCore import Qt
|
||||
@@ -28,7 +29,7 @@ from tagstudio.qt.translations import Translations
|
||||
|
||||
|
||||
class AboutModal(QWidget):
|
||||
def __init__(self, config_path):
|
||||
def __init__(self, config_path: Path | str):
|
||||
super().__init__()
|
||||
self.setWindowTitle(Translations["about.title"])
|
||||
|
||||
@@ -47,8 +48,8 @@ class AboutModal(QWidget):
|
||||
)
|
||||
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
self.setMinimumSize(360, 540)
|
||||
self.setMaximumSize(600, 600)
|
||||
self.setMinimumSize(420, 500)
|
||||
self.setMaximumSize(600, 800)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(0, 12, 0, 0)
|
||||
self.root_layout.setSpacing(0)
|
||||
@@ -59,12 +60,12 @@ class AboutModal(QWidget):
|
||||
self.content_layout.setContentsMargins(12, 12, 12, 12)
|
||||
self.content_layout.setSpacing(12)
|
||||
|
||||
# TagStudio Icon Logo --------------------------------------------------
|
||||
# TagStudio Logo -------------------------------------------------------
|
||||
self.logo_widget = QLabel()
|
||||
self.logo_pixmap = QPixmap.fromImage(ImageQt.ImageQt(self.rm.get("icon")))
|
||||
self.logo_pixmap = QPixmap.fromImage(ImageQt.ImageQt(self.rm.ts_logo_text_color))
|
||||
self.logo_pixmap.setDevicePixelRatio(self.devicePixelRatio())
|
||||
self.logo_pixmap = self.logo_pixmap.scaledToWidth(
|
||||
math.floor(128 * self.devicePixelRatio()), Qt.TransformationMode.SmoothTransformation
|
||||
math.floor(384 * self.devicePixelRatio()), Qt.TransformationMode.SmoothTransformation
|
||||
)
|
||||
self.logo_widget.setPixmap(self.logo_pixmap)
|
||||
self.logo_widget.setContentsMargins(0, 0, 0, 0)
|
||||
@@ -72,7 +73,7 @@ class AboutModal(QWidget):
|
||||
|
||||
# Title ----------------------------------------------------------------
|
||||
branch: str = (" (" + VERSION_BRANCH + ")") if VERSION_BRANCH else ""
|
||||
self.title_label = QLabel(f"<h2>TagStudio Alpha {VERSION}{branch}</h2>")
|
||||
self.title_label = QLabel(f"<h3>{Translations['about.version']} {VERSION} {branch}</h3>")
|
||||
self.title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
# Description ----------------------------------------------------------
|
||||
@@ -105,7 +106,7 @@ class AboutModal(QWidget):
|
||||
self.system_info_layout.setLabelAlignment(Qt.AlignmentFlag.AlignRight)
|
||||
|
||||
# Version
|
||||
version_title = QLabel("Version")
|
||||
version_title = QLabel(Translations["about.version"])
|
||||
most_recent_release = unwrap(TagStudioCore.get_most_recent_release_version(), "UNKNOWN")
|
||||
version_content_style = self.form_content_style
|
||||
if most_recent_release == VERSION:
|
||||
@@ -174,8 +175,8 @@ class AboutModal(QWidget):
|
||||
self.content_layout.addWidget(self.title_label)
|
||||
self.content_layout.addWidget(self.desc_label)
|
||||
self.content_layout.addWidget(self.system_info_widget)
|
||||
self.content_layout.addWidget(self.links_label)
|
||||
self.content_layout.addStretch(1)
|
||||
self.content_layout.addWidget(self.links_label)
|
||||
self.content_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
self.root_layout.addWidget(self.content_widget)
|
||||
|
||||
@@ -2,39 +2,28 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
|
||||
import math
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import override
|
||||
from warnings import catch_warnings
|
||||
|
||||
import structlog
|
||||
from PIL import Image, ImageQt
|
||||
from PySide6.QtCore import QEvent, Qt
|
||||
from PIL import ImageQt
|
||||
from PySide6.QtCore import QEvent, QSize, Qt
|
||||
from PySide6.QtGui import QEnterEvent, QPixmap, QResizeEvent
|
||||
from PySide6.QtWidgets import QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget
|
||||
|
||||
from tagstudio.core.enums import Theme
|
||||
from tagstudio.qt.helpers.color_overlay import theme_fg_overlay
|
||||
from tagstudio.qt.resource_manager import ResourceManager
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
class FieldContainer(QWidget):
|
||||
# TODO: reference a resources folder rather than path.parents[2]?
|
||||
clipboard_icon_128: Image.Image = Image.open(
|
||||
str(Path(__file__).parents[2] / "resources/qt/images/clipboard_icon_128.png")
|
||||
).resize((math.floor(24 * 1.25), math.floor(24 * 1.25)))
|
||||
clipboard_icon_128.load()
|
||||
|
||||
edit_icon_128: Image.Image = Image.open(
|
||||
str(Path(__file__).parents[2] / "resources/qt/images/edit_icon_128.png")
|
||||
).resize((math.floor(24 * 1.25), math.floor(24 * 1.25)))
|
||||
edit_icon_128.load()
|
||||
|
||||
trash_icon_128: Image.Image = Image.open(
|
||||
str(Path(__file__).parents[2] / "resources/qt/images/trash_icon_128.png")
|
||||
).resize((math.floor(24 * 1.25), math.floor(24 * 1.25)))
|
||||
trash_icon_128.load()
|
||||
rm: ResourceManager = ResourceManager()
|
||||
copy_icon = theme_fg_overlay(rm.copy, inverse=True)
|
||||
edit_icon = theme_fg_overlay(rm.edit, inverse=True)
|
||||
trash_icon = theme_fg_overlay(rm.trash, inverse=True)
|
||||
|
||||
# TODO: There should be a global button theme somewhere.
|
||||
container_style = (
|
||||
@@ -93,7 +82,8 @@ class FieldContainer(QWidget):
|
||||
self.copy_button.setMinimumSize(button_size, button_size)
|
||||
self.copy_button.setMaximumSize(button_size, button_size)
|
||||
self.copy_button.setFlat(True)
|
||||
self.copy_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(self.clipboard_icon_128)))
|
||||
self.copy_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(FieldContainer.copy_icon)))
|
||||
self.copy_button.setIconSize(QSize(20, 20))
|
||||
self.copy_button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.title_layout.addWidget(self.copy_button)
|
||||
self.copy_button.setHidden(True)
|
||||
@@ -103,7 +93,8 @@ class FieldContainer(QWidget):
|
||||
self.edit_button.setMinimumSize(button_size, button_size)
|
||||
self.edit_button.setMaximumSize(button_size, button_size)
|
||||
self.edit_button.setFlat(True)
|
||||
self.edit_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(self.edit_icon_128)))
|
||||
self.edit_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(FieldContainer.edit_icon)))
|
||||
self.edit_button.setIconSize(QSize(20, 20))
|
||||
self.edit_button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.title_layout.addWidget(self.edit_button)
|
||||
self.edit_button.setHidden(True)
|
||||
@@ -113,7 +104,8 @@ class FieldContainer(QWidget):
|
||||
self.remove_button.setMinimumSize(button_size, button_size)
|
||||
self.remove_button.setMaximumSize(button_size, button_size)
|
||||
self.remove_button.setFlat(True)
|
||||
self.remove_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(self.trash_icon_128)))
|
||||
self.remove_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(FieldContainer.trash_icon)))
|
||||
self.remove_button.setIconSize(QSize(20, 20))
|
||||
self.remove_button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.title_layout.addWidget(self.remove_button)
|
||||
self.remove_button.setHidden(True)
|
||||
|
||||
@@ -8,7 +8,6 @@ from pathlib import Path
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
||||
import structlog
|
||||
from PIL import Image, ImageQt
|
||||
from PySide6.QtCore import QEvent, QMimeData, QSize, Qt, QUrl
|
||||
from PySide6.QtGui import QAction, QDrag, QEnterEvent, QGuiApplication, QMouseEvent, QPixmap
|
||||
from PySide6.QtWidgets import QBoxLayout, QCheckBox, QHBoxLayout, QLabel, QVBoxLayout, QWidget
|
||||
@@ -62,17 +61,7 @@ def badge_update_lock(func):
|
||||
|
||||
|
||||
class ItemThumb(FlowWidget):
|
||||
"""The thumbnail widget for a library item (Entry, Collation, Tag Group, etc.)."""
|
||||
|
||||
collation_icon_128: Image.Image = Image.open(
|
||||
str(Path(__file__).parents[2] / "resources/qt/images/collation_icon_128.png")
|
||||
)
|
||||
collation_icon_128.load()
|
||||
|
||||
tag_group_icon_128: Image.Image = Image.open(
|
||||
str(Path(__file__).parents[2] / "resources/qt/images/tag_group_icon_128.png")
|
||||
)
|
||||
tag_group_icon_128.load()
|
||||
"""The thumbnail widget for a library item (Entry, Entry Group, etc.)."""
|
||||
|
||||
small_text_style = (
|
||||
"background-color:rgba(0, 0, 0, 192);"
|
||||
@@ -141,8 +130,8 @@ class ItemThumb(FlowWidget):
|
||||
# | ARC FAV| Top Right: Favorite & Archived Badges
|
||||
# | |
|
||||
# | |
|
||||
# |EXT #| Lower Left: File Type, Tag Group Icon, or Collation Icon
|
||||
# +----------+ Lower Right: Collation Count, Video Length, or Word Count
|
||||
# |EXT #| Lower Left: File Type or Entry Group Icon
|
||||
# +----------+ Lower Right: Entry Group Count, Video Length, or Word Count
|
||||
#
|
||||
# Filename Underneath: (Optional) Filename
|
||||
|
||||
@@ -221,19 +210,19 @@ class ItemThumb(FlowWidget):
|
||||
# Static Badges ========================================================
|
||||
|
||||
# Item Type Badge ------------------------------------------------------
|
||||
# Used for showing the Tag Group / Collation icons.
|
||||
# Used for showing the Entry Group icons.
|
||||
# Mutually exclusive with the File Extension Badge.
|
||||
self.item_type_badge = QLabel()
|
||||
self.item_type_badge.setObjectName("itemBadge")
|
||||
self.item_type_badge.setPixmap(
|
||||
QPixmap.fromImage(
|
||||
ImageQt.ImageQt(
|
||||
ItemThumb.collation_icon_128.resize(
|
||||
(check_size, check_size), Image.Resampling.BILINEAR
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
# self.item_type_badge.setPixmap(
|
||||
# QPixmap.fromImage(
|
||||
# ImageQt.ImageQt(
|
||||
# ItemThumb.collation_icon_128.resize(
|
||||
# (check_size, check_size), Image.Resampling.BILINEAR
|
||||
# )
|
||||
# )
|
||||
# )
|
||||
# )
|
||||
self.item_type_badge.setMinimumSize(check_size, check_size)
|
||||
self.item_type_badge.setMaximumSize(check_size, check_size)
|
||||
self.bottom_layout.addWidget(self.item_type_badge)
|
||||
@@ -247,7 +236,7 @@ class ItemThumb(FlowWidget):
|
||||
self.bottom_layout.addStretch(2)
|
||||
|
||||
# Count Badge ----------------------------------------------------------
|
||||
# Used for Tag Group + Collation counts, video length, word count, etc.
|
||||
# Used for Entry Group counts, video length, word count, etc.
|
||||
self.count_badge = QLabel()
|
||||
self.count_badge.setObjectName("countBadge")
|
||||
self.count_badge.setText("-:--")
|
||||
@@ -343,7 +332,7 @@ class ItemThumb(FlowWidget):
|
||||
self.count_badge.setStyleSheet(ItemThumb.small_text_style)
|
||||
self.count_badge.setHidden(True)
|
||||
self.ext_badge.setHidden(True)
|
||||
elif mode == ItemType.COLLATION:
|
||||
elif mode == ItemType.ENTRY_GROUP:
|
||||
self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, on=False)
|
||||
self.thumb_button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.thumb_button.setHidden(False)
|
||||
@@ -352,13 +341,6 @@ class ItemThumb(FlowWidget):
|
||||
self.count_badge.setStyleSheet(ItemThumb.med_text_style)
|
||||
self.count_badge.setHidden(False)
|
||||
self.item_type_badge.setHidden(False)
|
||||
elif mode == ItemType.TAG_GROUP:
|
||||
self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, on=False)
|
||||
self.thumb_button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.thumb_button.setHidden(False)
|
||||
self.ext_badge.setHidden(True)
|
||||
self.count_badge.setHidden(False)
|
||||
self.item_type_badge.setHidden(False)
|
||||
self.mode = mode
|
||||
|
||||
def set_extension(self, filename: Path) -> None:
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
import sys
|
||||
import typing
|
||||
from pathlib import Path
|
||||
|
||||
import structlog
|
||||
from PIL import Image, ImageQt
|
||||
@@ -12,7 +11,8 @@ from PySide6.QtCore import QEasingCurve, QPoint, QPropertyAnimation, Qt
|
||||
from PySide6.QtGui import QPixmap
|
||||
from PySide6.QtWidgets import QLabel, QPushButton, QVBoxLayout, QWidget
|
||||
|
||||
from tagstudio.qt.helpers.color_overlay import gradient_overlay, theme_fg_overlay
|
||||
from tagstudio.qt.helpers.color_overlay import theme_fg_overlay
|
||||
from tagstudio.qt.resource_manager import ResourceManager
|
||||
from tagstudio.qt.translations import Translations
|
||||
from tagstudio.qt.views.clickable_label import ClickableLabel
|
||||
|
||||
@@ -24,6 +24,10 @@ logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
class LandingWidget(QWidget):
|
||||
rm: ResourceManager = ResourceManager()
|
||||
mono_logo: Image.Image = rm.ts_logo_text_mono
|
||||
color_logo: Image.Image = rm.ts_logo_text_color
|
||||
|
||||
def __init__(self, driver: "QtDriver", pixel_ratio: float):
|
||||
super().__init__()
|
||||
self.driver = driver
|
||||
@@ -39,10 +43,6 @@ class LandingWidget(QWidget):
|
||||
self.setLayout(self.landing_layout)
|
||||
|
||||
# Create landing logo --------------------------------------------------
|
||||
# self.landing_logo_pixmap = QPixmap(":/images/tagstudio_logo_text_mono.png")
|
||||
self.logo_raw: Image.Image = Image.open(
|
||||
Path(__file__).parents[2] / "resources/qt/images/tagstudio_logo_text_mono.png"
|
||||
)
|
||||
self.landing_pixmap: QPixmap = QPixmap()
|
||||
self.update_logo_color()
|
||||
self.logo_label.clicked.connect(self._update_special_click)
|
||||
@@ -58,9 +58,9 @@ class LandingWidget(QWidget):
|
||||
|
||||
# Create "Open/Create Library" button ----------------------------------
|
||||
if sys.platform == "darwin":
|
||||
open_shortcut_text = "(⌘+O)"
|
||||
open_shortcut_text = "(⌘ + O)"
|
||||
else:
|
||||
open_shortcut_text = "(Ctrl+O)"
|
||||
open_shortcut_text = "(Ctrl + O)"
|
||||
self.open_button: QPushButton = QPushButton(
|
||||
Translations.format("landing.open_create_library", shortcut=open_shortcut_text)
|
||||
)
|
||||
@@ -83,21 +83,22 @@ class LandingWidget(QWidget):
|
||||
self.landing_layout.addWidget(self.open_button, alignment=Qt.AlignmentFlag.AlignCenter)
|
||||
self.landing_layout.addWidget(self.status_label, alignment=Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
def update_logo_color(self, style: typing.Literal["mono", "gradient"] = "mono"):
|
||||
def update_logo_color(self, style: typing.Literal["mono", "color"] = "mono"):
|
||||
"""Update the color of the TagStudio logo.
|
||||
|
||||
Args:
|
||||
style (str): = The style of the logo. Either "mono" or "gradient".
|
||||
style (str): = The style of the logo. Either "mono" or "color".
|
||||
"""
|
||||
if style == "mono":
|
||||
logo_im = theme_fg_overlay(self.logo_raw)
|
||||
elif style == "gradient":
|
||||
gradient_colors: list[str] = ["#d27bf4", "#7992f5", "#63c6e3", "#63f5cf"]
|
||||
logo_im = gradient_overlay(self.logo_raw, gradient_colors)
|
||||
logo_im = theme_fg_overlay(LandingWidget.mono_logo)
|
||||
elif style == "color":
|
||||
logo_im = LandingWidget.color_logo
|
||||
|
||||
logo_final: Image.Image = Image.new(mode="RGBA", size=self.logo_raw.size, color="#00000000")
|
||||
logo_final: Image.Image = Image.new(
|
||||
mode="RGBA", size=LandingWidget.mono_logo.size, color="#00000000"
|
||||
)
|
||||
|
||||
logo_final.paste(logo_im, (0, 0), mask=self.logo_raw)
|
||||
logo_final.paste(logo_im, (0, 0), mask=LandingWidget.mono_logo)
|
||||
|
||||
self.landing_pixmap = QPixmap.fromImage(ImageQt.ImageQt(logo_im))
|
||||
self.landing_pixmap.setDevicePixelRatio(self._pixel_ratio)
|
||||
@@ -105,7 +106,10 @@ class LandingWidget(QWidget):
|
||||
self._logo_width, Qt.TransformationMode.SmoothTransformation
|
||||
)
|
||||
self.logo_label.setMaximumHeight(
|
||||
int(self.logo_raw.size[1] * (self.logo_raw.size[0] / self._logo_width))
|
||||
int(
|
||||
LandingWidget.mono_logo.size[1]
|
||||
* (LandingWidget.mono_logo.size[0] / self._logo_width)
|
||||
)
|
||||
)
|
||||
self.logo_label.setMaximumWidth(self._logo_width)
|
||||
self.logo_label.setPixmap(self.landing_pixmap)
|
||||
@@ -119,17 +123,13 @@ class LandingWidget(QWidget):
|
||||
if self._special_click_count >= 0:
|
||||
self._special_click_count += 1
|
||||
if self._special_click_count >= 10:
|
||||
self.update_logo_color("gradient")
|
||||
self.update_logo_color("color")
|
||||
self.animate_logo_pop()
|
||||
self._special_click_count = -1
|
||||
|
||||
def animate_logo_in(self):
|
||||
"""Animate in the TagStudio logo."""
|
||||
# NOTE: Sometimes, mostly on startup without a library open, the
|
||||
# y position of logo_label is something like 10. I'm not sure what
|
||||
# the cause of this is, so I've just done this workaround to disable
|
||||
# the animation if the y position is too incorrect.
|
||||
if self.logo_label.y() > 50:
|
||||
"""Animate the TagStudio logo in, if not opening a library on start."""
|
||||
if not self.driver.settings.open_last_loaded_on_startup and not self.driver.args.open:
|
||||
self.logo_pos_anim.setStartValue(QPoint(self.logo_label.x(), self.logo_label.y() - 100))
|
||||
self.logo_pos_anim.setEndValue(self.logo_label.pos())
|
||||
self.logo_pos_anim.start()
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
import typing
|
||||
from pathlib import Path
|
||||
from time import gmtime
|
||||
from typing import cast, override
|
||||
from typing import override
|
||||
|
||||
import structlog
|
||||
from PIL import Image, ImageDraw
|
||||
from PIL import Image, ImageDraw, ImageQt
|
||||
from PySide6.QtCore import QEvent, QObject, QRectF, QSize, Qt, QUrl, QVariantAnimation
|
||||
from PySide6.QtGui import (
|
||||
QAction,
|
||||
@@ -18,12 +18,12 @@ from PySide6.QtGui import (
|
||||
QLinearGradient,
|
||||
QMouseEvent,
|
||||
QPen,
|
||||
QPixmap,
|
||||
QRegion,
|
||||
QResizeEvent,
|
||||
)
|
||||
from PySide6.QtMultimedia import QAudioOutput, QMediaDevices, QMediaPlayer
|
||||
from PySide6.QtMultimediaWidgets import QGraphicsVideoItem
|
||||
from PySide6.QtSvgWidgets import QSvgWidget
|
||||
from PySide6.QtWidgets import (
|
||||
QGraphicsScene,
|
||||
QGraphicsView,
|
||||
@@ -31,10 +31,12 @@ from PySide6.QtWidgets import (
|
||||
QLabel,
|
||||
QSizePolicy,
|
||||
QSlider,
|
||||
QToolButton,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from tagstudio.qt.helpers.color_overlay import theme_fg_overlay
|
||||
from tagstudio.qt.translations import Translations
|
||||
from tagstudio.qt.views.clickable_slider import ClickableSlider
|
||||
|
||||
@@ -55,6 +57,18 @@ class MediaPlayer(QGraphicsView):
|
||||
def __init__(self, driver: "QtDriver") -> None:
|
||||
super().__init__()
|
||||
self.driver = driver
|
||||
self.play_icon = QPixmap.fromImage(
|
||||
ImageQt.ImageQt(theme_fg_overlay(self.driver.rm.bxs_right_arrow, use_alpha=False))
|
||||
)
|
||||
self.pause_icon = QPixmap.fromImage(
|
||||
ImageQt.ImageQt(theme_fg_overlay(self.driver.rm.pause_icon, use_alpha=False))
|
||||
)
|
||||
self.mute_icon = QPixmap.fromImage(
|
||||
ImageQt.ImageQt(theme_fg_overlay(self.driver.rm.mute_icon, use_alpha=False))
|
||||
)
|
||||
self.volume_icon = QPixmap.fromImage(
|
||||
ImageQt.ImageQt(theme_fg_overlay(self.driver.rm.volume_icon, use_alpha=False))
|
||||
)
|
||||
|
||||
slider_style = """
|
||||
QSlider {
|
||||
@@ -169,27 +183,27 @@ class MediaPlayer(QGraphicsView):
|
||||
self.sub_controls.setStyleSheet("background: transparent;")
|
||||
self.sub_controls.setMinimumHeight(16)
|
||||
|
||||
self.play_pause = QSvgWidget()
|
||||
self.play_pause = QToolButton()
|
||||
self.play_pause.setStyleSheet("QToolButton { border: none; background: transparent; }")
|
||||
self.play_pause.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.play_pause.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, on=True)
|
||||
self.play_pause.setMouseTracking(True)
|
||||
self.play_pause.installEventFilter(self)
|
||||
self.play_pause.clicked.connect(self.toggle_play)
|
||||
self.load_toggle_play_icon(playing=False)
|
||||
self.play_pause.resize(16, 16)
|
||||
self.play_pause.setIconSize(QSize(20, 20))
|
||||
self.play_pause.resize(20, 20)
|
||||
self.play_pause.setSizePolicy(fixed_policy)
|
||||
self.play_pause.setStyleSheet("background: transparent;")
|
||||
self.play_pause.hide()
|
||||
|
||||
sub_layout.addWidget(self.play_pause)
|
||||
sub_layout.setAlignment(self.play_pause, Qt.AlignmentFlag.AlignLeft)
|
||||
|
||||
self.mute_unmute = QSvgWidget()
|
||||
self.mute_unmute = QToolButton()
|
||||
self.mute_unmute.setStyleSheet("QToolButton { border: none; background: transparent; }")
|
||||
self.mute_unmute.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.mute_unmute.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, on=True)
|
||||
self.mute_unmute.setMouseTracking(True)
|
||||
self.mute_unmute.clicked.connect(self.toggle_mute)
|
||||
self.mute_unmute.installEventFilter(self)
|
||||
self.load_mute_unmute_icon(muted=False)
|
||||
self.mute_unmute.resize(16, 16)
|
||||
self.mute_unmute.setIconSize(QSize(20, 20))
|
||||
self.mute_unmute.resize(20, 20)
|
||||
self.mute_unmute.setSizePolicy(fixed_policy)
|
||||
self.mute_unmute.hide()
|
||||
|
||||
@@ -210,7 +224,7 @@ class MediaPlayer(QGraphicsView):
|
||||
sub_layout.addStretch()
|
||||
|
||||
self.position_label = QLabel("0:00")
|
||||
self.position_label.setStyleSheet("color: white;")
|
||||
self.position_label.setStyleSheet("color: white; font-family: Oxanium; font-weight:bold;")
|
||||
sub_layout.addWidget(self.position_label)
|
||||
root_layout.setAlignment(self.position_label, Qt.AlignmentFlag.AlignRight)
|
||||
self.position_label.hide()
|
||||
@@ -253,21 +267,10 @@ class MediaPlayer(QGraphicsView):
|
||||
"""Apply a rounded corner effect to the video player."""
|
||||
width: int = int(max(self.contentsRect().size().width(), 0))
|
||||
height: int = int(max(self.contentsRect().size().height(), 0))
|
||||
mask = Image.new(
|
||||
"RGBA",
|
||||
(
|
||||
width,
|
||||
height,
|
||||
),
|
||||
(0, 0, 0, 255),
|
||||
)
|
||||
mask = Image.new("RGBA", (width, height), (0, 0, 0, 255))
|
||||
draw = ImageDraw.Draw(mask)
|
||||
draw.rounded_rectangle(
|
||||
(0, 0) + (width, height),
|
||||
radius=8,
|
||||
fill=(0, 0, 0, 0),
|
||||
)
|
||||
final_mask = mask.getchannel("A").toqpixmap()
|
||||
draw.rounded_rectangle((0, 0) + (width, height), radius=8, fill=(0, 0, 0, 0))
|
||||
final_mask: QPixmap = mask.getchannel("A").toqpixmap() # pyright: ignore[reportUnknownVariableType]
|
||||
self.setMask(QRegion(QBitmap(final_mask)))
|
||||
|
||||
def set_tint_opacity(self, opacity: int) -> None:
|
||||
@@ -322,15 +325,7 @@ class MediaPlayer(QGraphicsView):
|
||||
@override
|
||||
def eventFilter(self, arg__1: QObject, arg__2: QEvent) -> bool:
|
||||
"""Manage events for the media player."""
|
||||
if (
|
||||
arg__2.type() == QEvent.Type.MouseButtonPress
|
||||
and arg__2.button() == Qt.MouseButton.LeftButton # pyright: ignore[reportAttributeAccessIssue]
|
||||
):
|
||||
if arg__1 == self.play_pause:
|
||||
self.toggle_play()
|
||||
elif arg__1 == self.mute_unmute:
|
||||
self.toggle_mute()
|
||||
elif arg__2.type() is QEvent.Type.Enter:
|
||||
if arg__2.type() is QEvent.Type.Enter:
|
||||
if arg__1 == self or arg__1 == self.video_preview:
|
||||
self.underMouse()
|
||||
elif arg__1 == self.mute_unmute:
|
||||
@@ -414,12 +409,12 @@ class MediaPlayer(QGraphicsView):
|
||||
self.player.play()
|
||||
|
||||
def load_toggle_play_icon(self, playing: bool) -> None:
|
||||
icon = cast(bytes, self.driver.rm.pause_icon if playing else self.driver.rm.play_icon)
|
||||
self.play_pause.load(icon)
|
||||
icon = self.pause_icon if playing else self.play_icon
|
||||
self.play_pause.setIcon(icon)
|
||||
|
||||
def load_mute_unmute_icon(self, muted: bool) -> None:
|
||||
icon = cast(bytes, self.driver.rm.volume_mute_icon if muted else self.driver.rm.volume_icon)
|
||||
self.mute_unmute.load(icon)
|
||||
icon = self.mute_icon if muted else self.volume_icon
|
||||
self.mute_unmute.setIcon(icon)
|
||||
|
||||
def slider_value_changed(self, value: int) -> None:
|
||||
if self.timeline_slider.isSliderDown():
|
||||
|
||||
@@ -44,7 +44,7 @@ class Pagination(QWidget):
|
||||
|
||||
# [<] ----------------------------------
|
||||
self.prev_button = QPushButtonWrapper()
|
||||
prev_icon: Image.Image = self.rm.get("bxs-left-arrow") # pyright: ignore[reportAssignmentType]
|
||||
prev_icon: Image.Image = self.rm.bxs_left_arrow
|
||||
prev_icon = theme_fg_overlay(prev_icon, use_alpha=False)
|
||||
self.prev_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(prev_icon)))
|
||||
self.prev_button.setIconSize(QSize(12, 12))
|
||||
@@ -97,7 +97,7 @@ class Pagination(QWidget):
|
||||
|
||||
# ---------------------------------- [>]
|
||||
self.next_button = QPushButtonWrapper()
|
||||
next_icon: Image.Image = self.rm.get("bxs-right-arrow") # pyright: ignore[reportAssignmentType]
|
||||
next_icon: Image.Image = self.rm.bxs_right_arrow
|
||||
next_icon = theme_fg_overlay(next_icon, use_alpha=False)
|
||||
self.next_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(next_icon)))
|
||||
self.next_button.setIconSize(QSize(12, 12))
|
||||
|
||||
@@ -56,6 +56,7 @@ class SettingsPanel(PanelWidget):
|
||||
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"],
|
||||
Splash.AURORA: Translations["settings.splash.option.aurora"],
|
||||
}
|
||||
|
||||
tag_click_action_map: dict[TagClickActionOption, str] = {
|
||||
|
||||
@@ -449,11 +449,10 @@ class ThumbRenderer(QObject):
|
||||
)
|
||||
|
||||
# Get icon by name
|
||||
icon: Image.Image | None = self.rm.get(name) # pyright: ignore[reportAssignmentType]
|
||||
icon = self.rm.get(name)
|
||||
assert isinstance(icon, Image.Image) or icon is None
|
||||
if not icon:
|
||||
icon = self.rm.get("file_generic") # pyright: ignore[reportAssignmentType]
|
||||
if not icon:
|
||||
icon = Image.new(mode="RGBA", size=(32, 32), color="magenta")
|
||||
icon = self.rm.file_generic
|
||||
|
||||
# Resize icon to fit icon_ratio
|
||||
icon = icon.resize((math.ceil(size[0] // icon_ratio), math.ceil(size[1] // icon_ratio)))
|
||||
@@ -547,11 +546,10 @@ class ThumbRenderer(QObject):
|
||||
)
|
||||
|
||||
# Get icon by name
|
||||
icon: Image.Image | None = self.rm.get(name) # pyright: ignore[reportAssignmentType]
|
||||
icon = self.rm.get(name)
|
||||
assert isinstance(icon, Image.Image)
|
||||
if not icon:
|
||||
icon = self.rm.get("file_generic") # pyright: ignore[reportAssignmentType]
|
||||
if not icon:
|
||||
icon = Image.new(mode="RGBA", size=(32, 32), color="magenta")
|
||||
icon = self.rm.file_generic
|
||||
|
||||
# Resize icon to fit icon_ratio
|
||||
icon = icon.resize(
|
||||
@@ -1597,7 +1595,7 @@ class ThumbRenderer(QObject):
|
||||
padding_factor = 18
|
||||
|
||||
im_ = im
|
||||
icon: Image.Image = self.rm.get("ignored") # pyright: ignore[reportAssignmentType]
|
||||
icon: Image.Image = self.rm.ignored
|
||||
|
||||
icon = icon.resize(
|
||||
(
|
||||
|
||||
@@ -3,31 +3,23 @@
|
||||
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Literal, TypedDict
|
||||
|
||||
import structlog
|
||||
import ujson
|
||||
from PIL import Image, ImageFile
|
||||
from PIL import Image
|
||||
from PySide6.QtGui import QPixmap
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
class TResourceJsonAttrDict(TypedDict):
|
||||
path: str
|
||||
mode: Literal["qpixmap", "pil", "rb", "r"]
|
||||
|
||||
|
||||
TData = bytes | str | ImageFile.ImageFile | QPixmap
|
||||
|
||||
RESOURCE_FOLDER: Path = Path(__file__).parents[1]
|
||||
|
||||
|
||||
class ResourceManager:
|
||||
"""A resource manager for retrieving resources."""
|
||||
|
||||
_map: dict[str, TResourceJsonAttrDict] = {}
|
||||
_cache: dict[str, TData] = {}
|
||||
_map: dict[str, dict[str, str]] = {}
|
||||
_cache: dict[str, bytes | str | Image.Image | QPixmap] = {}
|
||||
_instance: "ResourceManager | None" = None
|
||||
|
||||
def __new__(cls):
|
||||
@@ -43,7 +35,7 @@ class ResourceManager:
|
||||
return ResourceManager._instance
|
||||
|
||||
@staticmethod
|
||||
def get_path(id: str) -> Path | None:
|
||||
def get_path(id: str):
|
||||
"""Get a resource's path from the ResourceManager.
|
||||
|
||||
Args:
|
||||
@@ -52,12 +44,21 @@ class ResourceManager:
|
||||
Returns:
|
||||
Path: The resource path if found, else None.
|
||||
"""
|
||||
res: TResourceJsonAttrDict | None = ResourceManager._map.get(id)
|
||||
if res is not None:
|
||||
return RESOURCE_FOLDER / "resources" / res.get("path")
|
||||
return None
|
||||
try:
|
||||
res = ResourceManager._map.get(id)
|
||||
if res is None:
|
||||
raise AttributeError
|
||||
resource_path = res.get("path")
|
||||
if resource_path is None:
|
||||
raise FileNotFoundError
|
||||
|
||||
def get(self, id: str) -> TData | None:
|
||||
except (FileNotFoundError, AttributeError) as e:
|
||||
logger.error("[ResourceManager]: Could not find path for resource: ", id=str, error=e)
|
||||
return None
|
||||
|
||||
return RESOURCE_FOLDER / "resources" / resource_path
|
||||
|
||||
def get(self, id: str):
|
||||
"""Get a resource from the ResourceManager.
|
||||
|
||||
Args:
|
||||
@@ -70,42 +71,51 @@ class ResourceManager:
|
||||
QPixmap: When the data is in PySide6.QtGui.QPixmap format.
|
||||
None: If resource couldn't load.
|
||||
"""
|
||||
cached_res: TData | None = ResourceManager._cache.get(id)
|
||||
cached_res = ResourceManager._cache.get(id)
|
||||
if cached_res is not None:
|
||||
return cached_res
|
||||
|
||||
else:
|
||||
res: TResourceJsonAttrDict | None = ResourceManager._map.get(id)
|
||||
if res is None:
|
||||
res: dict[str, str] | None = ResourceManager._map.get(id)
|
||||
|
||||
try:
|
||||
if res is None:
|
||||
raise AttributeError
|
||||
resource_path = res.get("path")
|
||||
if resource_path is None:
|
||||
raise FileNotFoundError
|
||||
except (FileNotFoundError, AttributeError) as e:
|
||||
logger.error("[ResourceManager]: Could not find resource", id=id, error=e)
|
||||
return None
|
||||
|
||||
file_path: Path = RESOURCE_FOLDER / "resources" / res.get("path")
|
||||
file_path = RESOURCE_FOLDER / "resources" / resource_path
|
||||
mode = res.get("mode")
|
||||
|
||||
data: TData | None = None
|
||||
data = None
|
||||
try:
|
||||
match mode:
|
||||
case "r":
|
||||
data = file_path.read_text()
|
||||
|
||||
case "rb":
|
||||
data = file_path.read_bytes()
|
||||
|
||||
case "pil":
|
||||
data = Image.open(file_path)
|
||||
data.load()
|
||||
|
||||
case "qpixmap":
|
||||
data = QPixmap(file_path.as_posix())
|
||||
case _:
|
||||
raise AttributeError
|
||||
|
||||
except FileNotFoundError:
|
||||
logger.error("[ResourceManager][ERROR]: Could not find resource: ", path=file_path)
|
||||
except (FileNotFoundError, AttributeError) as e:
|
||||
logger.error(
|
||||
"[ResourceManager]: Could not find resource", path=file_path, id=id, error=e
|
||||
)
|
||||
return None
|
||||
|
||||
if data is not None:
|
||||
ResourceManager._cache[id] = data
|
||||
ResourceManager._cache[id] = data
|
||||
return data
|
||||
|
||||
def __getattr__(self, __name: str) -> TData:
|
||||
def __getattr__(self, __name: str):
|
||||
attr = self.get(__name)
|
||||
if attr is not None:
|
||||
return attr
|
||||
|
||||
62
src/tagstudio/qt/resource_manager.pyi
Normal file
@@ -0,0 +1,62 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image
|
||||
from PySide6.QtGui import QPixmap
|
||||
|
||||
class ResourceManager:
|
||||
# Methods
|
||||
get: Callable[..., Image.Image | bytes | None]
|
||||
get_path: Callable[..., Path | None]
|
||||
|
||||
# Attributes
|
||||
_map: dict[str, dict[str, str]]
|
||||
_cache: dict[str, bytes | str | Image.Image | QPixmap]
|
||||
_instance: ResourceManager | None
|
||||
|
||||
# Resources IDs from "resources.json"
|
||||
adobe_illustrator: Image.Image
|
||||
adobe_photoshop: Image.Image
|
||||
affinity_photo: Image.Image
|
||||
archive: Image.Image
|
||||
audio: Image.Image
|
||||
broken_link_icon: Image.Image
|
||||
bxs_left_arrow: Image.Image
|
||||
bxs_right_arrow: Image.Image
|
||||
copy: Image.Image
|
||||
database: Image.Image
|
||||
document: Image.Image
|
||||
dupe_file_stat: Image.Image
|
||||
ebook: Image.Image
|
||||
edit: Image.Image
|
||||
file_generic: Image.Image
|
||||
font: Image.Image
|
||||
icon: Image.Image
|
||||
ignored_stat: Image.Image
|
||||
ignored: Image.Image
|
||||
image_vector: Image.Image
|
||||
image: Image.Image
|
||||
material: Image.Image
|
||||
model: Image.Image
|
||||
mute_icon: Image.Image
|
||||
pause_icon: Image.Image
|
||||
presentation: Image.Image
|
||||
program: Image.Image
|
||||
shader: Image.Image
|
||||
shortcut: Image.Image
|
||||
splash_95: QPixmap
|
||||
splash_aurora: QPixmap
|
||||
splash_classic: QPixmap
|
||||
splash_goo_gears: QPixmap
|
||||
spreadsheet: Image.Image
|
||||
text: Image.Image
|
||||
thumb_loading: Image.Image
|
||||
trash: Image.Image
|
||||
ts_logo_text_color: Image.Image
|
||||
ts_logo_text_mono: Image.Image
|
||||
unlinked_stat: Image.Image
|
||||
video: Image.Image
|
||||
volume_icon: Image.Image
|
||||
@@ -1,150 +1,170 @@
|
||||
{
|
||||
"splash_classic": {
|
||||
"path": "qt/images/splash/classic.png",
|
||||
"mode": "qpixmap"
|
||||
},
|
||||
"splash_goo_gears": {
|
||||
"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"
|
||||
},
|
||||
"play_icon": {
|
||||
"path": "qt/images/play.svg",
|
||||
"mode": "rb"
|
||||
},
|
||||
"pause_icon": {
|
||||
"path": "qt/images/pause.svg",
|
||||
"mode": "rb"
|
||||
},
|
||||
"volume_icon": {
|
||||
"path": "qt/images/volume.svg",
|
||||
"mode": "rb"
|
||||
},
|
||||
"volume_mute_icon": {
|
||||
"path": "qt/images/volume_mute.svg",
|
||||
"mode": "rb"
|
||||
},
|
||||
"broken_link_icon": {
|
||||
"path": "qt/images/broken_link_icon.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"ignored": {
|
||||
"path": "qt/images/ignored_128.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"adobe_illustrator": {
|
||||
"path": "qt/images/file_icons/adobe_illustrator.png",
|
||||
"mode": "pil"
|
||||
"mode": "pil",
|
||||
"path": "qt/images/file_icons/adobe_illustrator.png"
|
||||
},
|
||||
"adobe_photoshop": {
|
||||
"path": "qt/images/file_icons/adobe_photoshop.png",
|
||||
"mode": "pil"
|
||||
"mode": "pil",
|
||||
"path": "qt/images/file_icons/adobe_photoshop.png"
|
||||
},
|
||||
"affinity_photo": {
|
||||
"path": "qt/images/file_icons/affinity_photo.png",
|
||||
"mode": "pil"
|
||||
"mode": "pil",
|
||||
"path": "qt/images/file_icons/affinity_photo.png"
|
||||
},
|
||||
"archive": {
|
||||
"path": "qt/images/file_icons/archive.png",
|
||||
"mode": "pil"
|
||||
"mode": "pil",
|
||||
"path": "qt/images/file_icons/archive.png"
|
||||
},
|
||||
"audio": {
|
||||
"path": "qt/images/file_icons/audio.png",
|
||||
"mode": "pil"
|
||||
"mode": "pil",
|
||||
"path": "qt/images/file_icons/audio.png"
|
||||
},
|
||||
"broken_link_icon": {
|
||||
"mode": "pil",
|
||||
"path": "qt/images/broken_link_icon.png"
|
||||
},
|
||||
"bxs_left_arrow": {
|
||||
"mode": "pil",
|
||||
"path": "qt/images/bxs-left-arrow.png"
|
||||
},
|
||||
"bxs_right_arrow": {
|
||||
"mode": "pil",
|
||||
"path": "qt/images/bxs-right-arrow.png"
|
||||
},
|
||||
"copy": {
|
||||
"mode": "pil",
|
||||
"path": "qt/images/bxs-clipboard-regular.png"
|
||||
},
|
||||
"database": {
|
||||
"path": "qt/images/file_icons/database.png",
|
||||
"mode": "pil"
|
||||
"mode": "pil",
|
||||
"path": "qt/images/file_icons/database.png"
|
||||
},
|
||||
"document": {
|
||||
"path": "qt/images/file_icons/document.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"ebook": {
|
||||
"path": "qt/images/file_icons/ebook.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"file_generic": {
|
||||
"path": "qt/images/file_icons/file_generic.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"font": {
|
||||
"path": "qt/images/file_icons/font.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"image": {
|
||||
"path": "qt/images/file_icons/image.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"image_vector": {
|
||||
"path": "qt/images/file_icons/image_vector.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"material": {
|
||||
"path": "qt/images/file_icons/material.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"model": {
|
||||
"path": "qt/images/file_icons/model.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"presentation": {
|
||||
"path": "qt/images/file_icons/presentation.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"program": {
|
||||
"path": "qt/images/file_icons/program.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"shader": {
|
||||
"path": "qt/images/file_icons/shader.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"shortcut": {
|
||||
"path": "qt/images/file_icons/shortcut.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"spreadsheet": {
|
||||
"path": "qt/images/file_icons/spreadsheet.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"text": {
|
||||
"path": "qt/images/file_icons/text.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"video": {
|
||||
"path": "qt/images/file_icons/video.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"thumb_loading": {
|
||||
"path": "qt/images/thumb_loading.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"bxs-left-arrow": {
|
||||
"path": "qt/images/bxs-left-arrow.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"bxs-right-arrow": {
|
||||
"path": "qt/images/bxs-right-arrow.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"unlinked_stat": {
|
||||
"path": "qt/images/unlinked_stat.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"ignored_stat": {
|
||||
"path": "qt/images/ignored_stat.png",
|
||||
"mode": "pil"
|
||||
"mode": "pil",
|
||||
"path": "qt/images/file_icons/document.png"
|
||||
},
|
||||
"dupe_file_stat": {
|
||||
"path": "qt/images/dupe_file_stat.png",
|
||||
"mode": "pil"
|
||||
"mode": "pil",
|
||||
"path": "qt/images/dupe_file_stat.png"
|
||||
},
|
||||
"ebook": {
|
||||
"mode": "pil",
|
||||
"path": "qt/images/file_icons/ebook.png"
|
||||
},
|
||||
"edit": {
|
||||
"mode": "pil",
|
||||
"path": "qt/images/bxs-pencil-solid.png"
|
||||
},
|
||||
"file_generic": {
|
||||
"mode": "pil",
|
||||
"path": "qt/images/file_icons/file_generic.png"
|
||||
},
|
||||
"font": {
|
||||
"mode": "pil",
|
||||
"path": "qt/images/file_icons/font.png"
|
||||
},
|
||||
"icon": {
|
||||
"mode": "pil",
|
||||
"path": "icon.png"
|
||||
},
|
||||
"ignored": {
|
||||
"mode": "pil",
|
||||
"path": "qt/images/ignored_128.png"
|
||||
},
|
||||
"ignored_stat": {
|
||||
"mode": "pil",
|
||||
"path": "qt/images/ignored_stat.png"
|
||||
},
|
||||
"image": {
|
||||
"mode": "pil",
|
||||
"path": "qt/images/file_icons/image.png"
|
||||
},
|
||||
"image_vector": {
|
||||
"mode": "pil",
|
||||
"path": "qt/images/file_icons/image_vector.png"
|
||||
},
|
||||
"material": {
|
||||
"mode": "pil",
|
||||
"path": "qt/images/file_icons/material.png"
|
||||
},
|
||||
"model": {
|
||||
"mode": "pil",
|
||||
"path": "qt/images/file_icons/model.png"
|
||||
},
|
||||
"presentation": {
|
||||
"mode": "pil",
|
||||
"path": "qt/images/file_icons/presentation.png"
|
||||
},
|
||||
"program": {
|
||||
"mode": "pil",
|
||||
"path": "qt/images/file_icons/program.png"
|
||||
},
|
||||
"shader": {
|
||||
"mode": "pil",
|
||||
"path": "qt/images/file_icons/shader.png"
|
||||
},
|
||||
"shortcut": {
|
||||
"mode": "pil",
|
||||
"path": "qt/images/file_icons/shortcut.png"
|
||||
},
|
||||
"splash_95": {
|
||||
"mode": "qpixmap",
|
||||
"path": "qt/images/splash/95.png"
|
||||
},
|
||||
"splash_aurora": {
|
||||
"mode": "qpixmap",
|
||||
"path": "qt/images/splash/aurora.png"
|
||||
},
|
||||
"splash_classic": {
|
||||
"mode": "qpixmap",
|
||||
"path": "qt/images/splash/classic.png"
|
||||
},
|
||||
"splash_goo_gears": {
|
||||
"mode": "qpixmap",
|
||||
"path": "qt/images/splash/goo_gears.png"
|
||||
},
|
||||
"spreadsheet": {
|
||||
"mode": "pil",
|
||||
"path": "qt/images/file_icons/spreadsheet.png"
|
||||
},
|
||||
"text": {
|
||||
"mode": "pil",
|
||||
"path": "qt/images/file_icons/text.png"
|
||||
},
|
||||
"thumb_loading": {
|
||||
"mode": "pil",
|
||||
"path": "qt/images/thumb_loading.png"
|
||||
},
|
||||
"trash": {
|
||||
"mode": "pil",
|
||||
"path": "qt/images/bxs-trash-solid.png"
|
||||
},
|
||||
"ts_logo_text_color": {
|
||||
"mode": "pil",
|
||||
"path": "qt/images/tagstudio_logo-text_color.png"
|
||||
},
|
||||
"ts_logo_text_mono": {
|
||||
"mode": "pil",
|
||||
"path": "qt/images/tagstudio_logo-text_mono.png"
|
||||
},
|
||||
"unlinked_stat": {
|
||||
"mode": "pil",
|
||||
"path": "qt/images/unlinked_stat.png"
|
||||
},
|
||||
"video": {
|
||||
"mode": "pil",
|
||||
"path": "qt/images/file_icons/video.png"
|
||||
},
|
||||
"volume_icon": {
|
||||
"mode": "pil",
|
||||
"path": "qt/images/bxs-volume-full-solid.png"
|
||||
},
|
||||
"mute_icon": {
|
||||
"mode": "pil",
|
||||
"path": "qt/images/bxs-volume-mute-solid.png"
|
||||
},
|
||||
"pause_icon": {
|
||||
"mode": "pil",
|
||||
"path": "qt/images/pause.png"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,5 @@
|
||||
<file alias = "images/star_icon_filled_128.png">../../resources/qt/images/star_icon_filled_128.png</file>
|
||||
<file alias = "images/box_icon_empty_128.png">../../resources/qt/images/box_icon_empty_128.png</file>
|
||||
<file alias = "images/box_icon_filled_128.png">../../resources/qt/images/box_icon_filled_128.png</file>
|
||||
<!-- <file alias = "images/edit_icon_128.png">../../resources/qt/images/edit_icon_128.png</file> -->
|
||||
<!-- <file alias = "images/trash_icon_128.png">../../resources/qt/images/trash_icon_128.png</file> -->
|
||||
<!-- <file alias = "images/clipboard_icon_128.png">../../resources/qt/images/clipboard_icon_128.png</file> -->
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -268,7 +268,7 @@ class LibraryInfoWindowView(QWidget):
|
||||
)
|
||||
|
||||
self.unlinked_icon = QLabel()
|
||||
unlinked_image: Image.Image = self.driver.rm.get("unlinked_stat") # pyright: ignore[reportAssignmentType]
|
||||
unlinked_image: Image.Image = self.driver.rm.unlinked_stat
|
||||
unlinked_pixmap = QPixmap.fromImage(ImageQt.ImageQt(unlinked_image))
|
||||
unlinked_pixmap.setDevicePixelRatio(self.devicePixelRatio())
|
||||
unlinked_pixmap = unlinked_pixmap.scaledToWidth(
|
||||
@@ -278,7 +278,7 @@ class LibraryInfoWindowView(QWidget):
|
||||
self.unlinked_icon.setPixmap(unlinked_pixmap)
|
||||
|
||||
self.ignored_icon = QLabel()
|
||||
ignored_image: Image.Image = self.driver.rm.get("ignored_stat") # pyright: ignore[reportAssignmentType]
|
||||
ignored_image: Image.Image = self.driver.rm.ignored_stat
|
||||
ignored_pixmap = QPixmap.fromImage(ImageQt.ImageQt(ignored_image))
|
||||
ignored_pixmap.setDevicePixelRatio(self.devicePixelRatio())
|
||||
ignored_pixmap = ignored_pixmap.scaledToWidth(
|
||||
@@ -288,7 +288,7 @@ class LibraryInfoWindowView(QWidget):
|
||||
self.ignored_icon.setPixmap(ignored_pixmap)
|
||||
|
||||
self.dupe_file_icon = QLabel()
|
||||
dupe_file_image: Image.Image = self.driver.rm.get("dupe_file_stat") # pyright: ignore[reportAssignmentType]
|
||||
dupe_file_image: Image.Image = self.driver.rm.dupe_file_stat
|
||||
dupe_file_pixmap = QPixmap.fromImage(
|
||||
ImageQt.ImageQt(theme_fg_overlay(dupe_file_image, use_alpha=False))
|
||||
)
|
||||
|
||||
@@ -547,7 +547,7 @@ class MainWindow(QMainWindow):
|
||||
self.search_bar_layout.setSizeConstraint(QLayout.SizeConstraint.SetMinimumSize)
|
||||
|
||||
self.back_button = QPushButton(self.central_widget)
|
||||
back_icon: Image.Image = self.rm.get("bxs-left-arrow") # pyright: ignore[reportAssignmentType]
|
||||
back_icon: Image.Image = self.rm.bxs_left_arrow
|
||||
back_icon = theme_fg_overlay(back_icon, use_alpha=False)
|
||||
self.back_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(back_icon)))
|
||||
self.back_button.setObjectName("back_button")
|
||||
@@ -556,7 +556,7 @@ class MainWindow(QMainWindow):
|
||||
self.search_bar_layout.addWidget(self.back_button)
|
||||
|
||||
self.forward_button = QPushButton(self.central_widget)
|
||||
forward_icon: Image.Image = self.rm.get("bxs-right-arrow") # pyright: ignore[reportAssignmentType]
|
||||
forward_icon: Image.Image = self.rm.bxs_right_arrow
|
||||
forward_icon = theme_fg_overlay(forward_icon, use_alpha=False)
|
||||
self.forward_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(forward_icon)))
|
||||
self.forward_button.setIconSize(QSize(16, 16))
|
||||
|
||||
@@ -13,6 +13,7 @@ from PySide6.QtWidgets import QSplashScreen, QWidget
|
||||
from tagstudio.core.constants import VERSION, VERSION_BRANCH
|
||||
from tagstudio.qt.global_settings import Splash
|
||||
from tagstudio.qt.resource_manager import ResourceManager
|
||||
from tagstudio.qt.translations import Translations
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
@@ -20,12 +21,11 @@ logger = structlog.get_logger(__name__)
|
||||
class SplashScreen:
|
||||
"""The custom splash screen widget for TagStudio."""
|
||||
|
||||
COPYRIGHT_YEARS: str = "2021-2025"
|
||||
COPYRIGHT_STR: str = f"© {COPYRIGHT_YEARS} Travis Abendshien (CyanVoxel)"
|
||||
VERSION_STR: str = (
|
||||
f"Version {VERSION} {(' (' + VERSION_BRANCH + ')') if VERSION_BRANCH else ''}"
|
||||
)
|
||||
DEFAULT_SPLASH = Splash.GOO_GEARS
|
||||
COPYRIGHT_YEARS: str = "2021-2026"
|
||||
COPYRIGHT: str = f"© {COPYRIGHT_YEARS} Travis Abendshien & TagStudio Contributors"
|
||||
COPYRIGHT_COMPACT: str = f"© {COPYRIGHT_YEARS} Travis Abendshien\n& TagStudio Contributors"
|
||||
VERSION_STR: str = f"{Translations['about.version']} {VERSION} {(' (' + VERSION_BRANCH + ')') if VERSION_BRANCH else ''}" # noqa: E501
|
||||
DEFAULT_SPLASH = Splash.AURORA
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -50,7 +50,8 @@ class SplashScreen:
|
||||
|
||||
def get_pixmap(self) -> QPixmap:
|
||||
"""Get the pixmap used for the splash screen."""
|
||||
pixmap: QPixmap | None = self.rm.get(f"splash_{self.splash_name}") # pyright: ignore[reportAssignmentType]
|
||||
pixmap = self.rm.get(f"splash_{self.splash_name}")
|
||||
assert isinstance(pixmap, QPixmap)
|
||||
if not pixmap:
|
||||
logger.error("[Splash] Splash screen not found:", splash_name=self.splash_name)
|
||||
pixmap = QPixmap(960, 540)
|
||||
@@ -70,19 +71,19 @@ class SplashScreen:
|
||||
font = painter.font()
|
||||
font.setPointSize(math.floor(22 * point_size_scale))
|
||||
painter.setFont(font)
|
||||
pen = QPen(QColor("#9782ff"))
|
||||
painter.setPen(pen)
|
||||
painter.drawText(
|
||||
QRect(0, -50, 960, 540),
|
||||
int(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignHCenter),
|
||||
SplashScreen.COPYRIGHT_STR,
|
||||
)
|
||||
# Version
|
||||
pen = QPen(QColor("#809782ff"))
|
||||
painter.setPen(pen)
|
||||
painter.drawText(
|
||||
QRect(0, -25, 960, 540),
|
||||
int(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignHCenter),
|
||||
SplashScreen.COPYRIGHT,
|
||||
)
|
||||
# Version
|
||||
pen = QPen(QColor("#9782ff"))
|
||||
painter.setPen(pen)
|
||||
painter.drawText(
|
||||
QRect(0, -50, 960, 540),
|
||||
int(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignHCenter),
|
||||
SplashScreen.VERSION_STR,
|
||||
)
|
||||
|
||||
@@ -91,20 +92,20 @@ class SplashScreen:
|
||||
font = painter.font()
|
||||
font.setPointSize(math.floor(22 * point_size_scale))
|
||||
painter.setFont(font)
|
||||
pen = QPen(QColor("#9782ff"))
|
||||
pen = QPen(QColor("#809782ff"))
|
||||
painter.setPen(pen)
|
||||
painter.drawText(
|
||||
QRect(40, 450, 960, 540),
|
||||
SplashScreen.COPYRIGHT_STR,
|
||||
SplashScreen.COPYRIGHT_COMPACT,
|
||||
)
|
||||
# Version
|
||||
font = painter.font()
|
||||
font.setPointSize(math.floor(22 * point_size_scale))
|
||||
painter.setFont(font)
|
||||
pen = QPen(QColor("#809782ff"))
|
||||
pen = QPen(QColor("#9782ff"))
|
||||
painter.setPen(pen)
|
||||
painter.drawText(
|
||||
QRect(40, 475, 960, 540),
|
||||
QRect(40, 420, 960, 540),
|
||||
SplashScreen.VERSION_STR,
|
||||
)
|
||||
|
||||
@@ -121,7 +122,7 @@ class SplashScreen:
|
||||
painter.drawText(
|
||||
QRect(88, -25, 960, 540),
|
||||
int(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignLeft),
|
||||
SplashScreen.COPYRIGHT_STR,
|
||||
SplashScreen.COPYRIGHT,
|
||||
)
|
||||
# Version
|
||||
font.setPointSize(math.floor(22 * point_size_scale))
|
||||
@@ -134,17 +135,33 @@ class SplashScreen:
|
||||
SplashScreen.VERSION_STR,
|
||||
)
|
||||
|
||||
case Splash.AURORA:
|
||||
# Copyright
|
||||
font = painter.font()
|
||||
font.setPointSize(math.floor(22 * point_size_scale))
|
||||
painter.setFont(font)
|
||||
pen = QPen(QColor("#907758FF"))
|
||||
painter.setPen(pen)
|
||||
painter.drawText(
|
||||
QRect(0, -25, 960, 540),
|
||||
int(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignHCenter),
|
||||
SplashScreen.COPYRIGHT,
|
||||
)
|
||||
# Version
|
||||
pen = QPen(QColor("#7758FF"))
|
||||
painter.setPen(pen)
|
||||
painter.drawText(
|
||||
QRect(0, -50, 960, 540),
|
||||
int(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignHCenter),
|
||||
SplashScreen.VERSION_STR,
|
||||
)
|
||||
|
||||
case _:
|
||||
pass
|
||||
|
||||
pixmap.setDevicePixelRatio(self.ratio)
|
||||
pixmap = pixmap.scaledToWidth(
|
||||
math.floor(
|
||||
min(
|
||||
(self.screen_width * self.ratio) / 4,
|
||||
pixmap.width(),
|
||||
)
|
||||
),
|
||||
math.floor(min((self.screen_width * self.ratio) / 4, pixmap.width())), # pyright: ignore[reportCallIssue]
|
||||
Qt.TransformationMode.SmoothTransformation,
|
||||
)
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 361 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 677 KiB After Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
BIN
src/tagstudio/resources/qt/images/bxs-clipboard-regular.png
Normal file
|
After Width: | Height: | Size: 991 B |
BIN
src/tagstudio/resources/qt/images/bxs-pencil-solid.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
src/tagstudio/resources/qt/images/bxs-trash-solid.png
Normal file
|
After Width: | Height: | Size: 993 B |
BIN
src/tagstudio/resources/qt/images/bxs-volume-full-solid.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
src/tagstudio/resources/qt/images/bxs-volume-mute-solid.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
BIN
src/tagstudio/resources/qt/images/pause.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" fill="#ffffff"><path d="M560-200v-560h160v560H560Zm-320 0v-560h160v560H240Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 172 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" fill="#ffffff"><path d="M320-200v-560l440 280-440 280Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 151 B |
BIN
src/tagstudio/resources/qt/images/splash/aurora.png
Normal file
|
After Width: | Height: | Size: 793 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 5.1 KiB |
BIN
src/tagstudio/resources/qt/images/tagstudio_logo-text_color.png
Normal file
|
After Width: | Height: | Size: 195 KiB |
BIN
src/tagstudio/resources/qt/images/tagstudio_logo-text_mono.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" fill="#ffffff"><path d="M560-131v-82q90-26 145-100t55-168q0-94-55-168T560-749v-82q124 28 202 125.5T840-481q0 127-78 224.5T560-131ZM120-360v-240h160l200-200v640L280-360H120Zm440 40v-322q47 22 73.5 66t26.5 96q0 51-26.5 94.5T560-320Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 327 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" fill="#ffffff"><path d="M792-56 671-177q-25 16-53 27.5T560-131v-82q14-5 27.5-10t25.5-12L480-368v208L280-360H120v-240h128L56-792l56-56 736 736-56 56Zm-8-232-58-58q17-31 25.5-65t8.5-70q0-94-55-168T560-749v-82q124 28 202 125.5T840-481q0 53-14.5 102T784-288ZM650-422l-90-90v-130q47 22 73.5 66t26.5 96q0 15-2.5 29.5T650-422ZM480-592 376-696l104-104v208Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 445 B |
@@ -5,10 +5,12 @@
|
||||
"about.license": "License",
|
||||
"about.module.found": "Found",
|
||||
"about.title": "About TagStudio",
|
||||
"about.version": "Version",
|
||||
"about.website": "Website",
|
||||
"app.git": "Git Commit",
|
||||
"app.pre_release": "Pre-Release",
|
||||
"app.title": "{base_title} - Library '{library_dir}'",
|
||||
"color_manager.title": "Manage Tag Colors",
|
||||
"color.color_border": "Use Secondary Color for Border",
|
||||
"color.confirm_delete": "Are you sure you want to delete the color \"{color_name}\"?",
|
||||
"color.delete": "Delete Tag",
|
||||
@@ -22,7 +24,6 @@
|
||||
"color.primary_required": "Primary Color (Required)",
|
||||
"color.secondary": "Secondary Color",
|
||||
"color.title.no_color": "No Color",
|
||||
"color_manager.title": "Manage Tag Colors",
|
||||
"dependency.missing.title": "{dependency} Not Found",
|
||||
"drop_import.description": "The following files match file paths that already exist in the library",
|
||||
"drop_import.duplicates_choice.plural": "The following {count} files match file paths that already exist in the library.",
|
||||
@@ -72,6 +73,13 @@
|
||||
"entries.unlinked.unlinked_count": "Unlinked Entries: {count}",
|
||||
"ffmpeg.missing.description": "FFmpeg and/or FFprobe were not found. FFmpeg is required for multimedia playback and thumbnails.",
|
||||
"ffmpeg.missing.status": "{ffmpeg}: {ffmpeg_status}<br>{ffprobe}: {ffprobe_status}",
|
||||
"field_template_manager.title": "Library Field Templates",
|
||||
"field_template.all_field_templates": "All Field Templates",
|
||||
"field_template.create": "Create Field Template",
|
||||
"field_template.create_add": "Create && Add \"{query}\"",
|
||||
"field_type.datetime": "Datetime",
|
||||
"field_type.text": "Text",
|
||||
"field_type.unknown": "Unknown Type",
|
||||
"field.add": "Add Field",
|
||||
"field.add.plural": "Add Fields",
|
||||
"field.confirm_remove": "Are you sure you want to remove this \"{name}\" field?",
|
||||
@@ -80,13 +88,6 @@
|
||||
"field.mixed_data": "Mixed Data",
|
||||
"field.paste": "Paste Field",
|
||||
"field.remove": "Remove Field",
|
||||
"field_template.all_field_templates": "All Field Templates",
|
||||
"field_template.create": "Create Field Template",
|
||||
"field_template.create_add": "Create && Add \"{query}\"",
|
||||
"field_template_manager.title": "Library Field Templates",
|
||||
"field_type.datetime": "Datetime",
|
||||
"field_type.text": "Text",
|
||||
"field_type.unknown": "Unknown Type",
|
||||
"file.date_added": "Date Added",
|
||||
"file.date_created": "Date Created",
|
||||
"file.date_modified": "Date Modified",
|
||||
@@ -100,8 +101,8 @@
|
||||
"file.duplicates.fix": "Fix Duplicate Files",
|
||||
"file.duplicates.matches": "Duplicate File Matches: {count}",
|
||||
"file.duplicates.matches_uninitialized": "Duplicate File Matches: N/A",
|
||||
"file.duplicates.mirror.description": "Mirror the Entry data across each duplicate match set, combining all data while not removing or duplicating fields. This operation will not delete any files or data.",
|
||||
"file.duplicates.mirror_entries": "&Mirror Entries",
|
||||
"file.duplicates.mirror.description": "Mirror the Entry data across each duplicate match set, combining all data while not removing or duplicating fields. This operation will not delete any files or data.",
|
||||
"file.duration": "Length",
|
||||
"file.not_found": "File Not Found",
|
||||
"file.open_file": "Open file",
|
||||
@@ -150,11 +151,11 @@
|
||||
"generic.skip_alt": "&Skip",
|
||||
"generic.yes": "Yes",
|
||||
"home.search": "Search",
|
||||
"home.search.view_limit": "View Limit:",
|
||||
"home.search_entries": "Search Entries",
|
||||
"home.search_field_templates": "Search Field Templates",
|
||||
"home.search_library": "Search Library",
|
||||
"home.search_tags": "Search Tags",
|
||||
"home.search.view_limit": "View Limit:",
|
||||
"home.show_hidden_entries": "Show Hidden Entries",
|
||||
"home.thumbnail_size": "Thumbnail Size",
|
||||
"home.thumbnail_size.extra_large": "Extra Large Thumbnails",
|
||||
@@ -187,13 +188,6 @@
|
||||
"json_migration.title.new_lib": "<h2>v9.5+ Library</h2>",
|
||||
"json_migration.title.old_lib": "<h2>v9.4 Library</h2>",
|
||||
"landing.open_create_library": "Open/Create Library {shortcut}",
|
||||
"library.missing": "Library Location is Missing",
|
||||
"library.name": "Library",
|
||||
"library.refresh.scanning.plural": "Scanning Directories for New Files...\n{searched_count} Files Searched, {found_count} New Files Found",
|
||||
"library.refresh.scanning.singular": "Scanning Directories for New Files...\n{searched_count} File Searched, {found_count} New Files Found",
|
||||
"library.refresh.scanning_preparing": "Scanning Directories for New Files...\nPreparing...",
|
||||
"library.refresh.title": "Refreshing Directories",
|
||||
"library.scan_library.title": "Scanning Library",
|
||||
"library_info.cleanup": "Cleanup",
|
||||
"library_info.cleanup.backups": "Library Backups:",
|
||||
"library_info.cleanup.dupe_files": "Duplicate Files:",
|
||||
@@ -213,6 +207,13 @@
|
||||
"library_object.name_required": "Name (Required)",
|
||||
"library_object.slug": "ID Slug",
|
||||
"library_object.slug_required": "ID Slug (Required)",
|
||||
"library.missing": "Library Location is Missing",
|
||||
"library.name": "Library",
|
||||
"library.refresh.scanning_preparing": "Scanning Directories for New Files...\nPreparing...",
|
||||
"library.refresh.scanning.plural": "Scanning Directories for New Files...\n{searched_count} Files Searched, {found_count} New Files Found",
|
||||
"library.refresh.scanning.singular": "Scanning Directories for New Files...\n{searched_count} File Searched, {found_count} New Files Found",
|
||||
"library.refresh.title": "Refreshing Directories",
|
||||
"library.scan_library.title": "Scanning Library",
|
||||
"macros.running.dialog.new_entries": "Running Configured Macros on {count}/{total} New File Entries...",
|
||||
"macros.running.dialog.title": "Running Macros on New Entries",
|
||||
"media_player.autoplay": "Autoplay",
|
||||
@@ -288,6 +289,7 @@
|
||||
"settings.show_filenames_in_grid": "Show Filenames in Grid",
|
||||
"settings.show_recent_libraries": "Show Recent Libraries",
|
||||
"settings.splash.label": "Splash Screen",
|
||||
"settings.splash.option.aurora": "Aurora (9.6)",
|
||||
"settings.splash.option.classic": "Classic (9.0)",
|
||||
"settings.splash.option.default": "Default",
|
||||
"settings.splash.option.goo_gears": "Open Source (9.4)",
|
||||
@@ -323,11 +325,12 @@
|
||||
"status.library_version_found": "Found:",
|
||||
"status.library_version_mismatch": "Library Version Mismatch!",
|
||||
"status.results": "Results",
|
||||
"status.results.invalid_syntax": "Invalid Search Syntax:",
|
||||
"status.results_found": "{count} Results Found ({time_span})",
|
||||
"status.results.invalid_syntax": "Invalid Search Syntax:",
|
||||
"tag_manager.title": "Library Tags",
|
||||
"tag.add": "Add Tag",
|
||||
"tag.add.plural": "Add Tags",
|
||||
"tag.add_to_search": "Add to Search",
|
||||
"tag.add.plural": "Add Tags",
|
||||
"tag.aliases": "Aliases",
|
||||
"tag.all_tags": "All Tags",
|
||||
"tag.choose_color": "Choose Tag Color",
|
||||
@@ -348,7 +351,6 @@
|
||||
"tag.search_for_tag": "Search for Tag",
|
||||
"tag.shorthand": "Shorthand",
|
||||
"tag.tag_name_required": "Tag Name (Required)",
|
||||
"tag_manager.title": "Library Tags",
|
||||
"trash.context.ambiguous": "Move file(s) to {trash_term}",
|
||||
"trash.context.plural": "Move files to {trash_term}",
|
||||
"trash.context.singular": "Move file to {trash_term}",
|
||||
|
||||
@@ -13,6 +13,6 @@ logger = structlog.get_logger()
|
||||
def test_get():
|
||||
rm = ResourceManager()
|
||||
|
||||
for res in rm._map: # pyright: ignore[reportPrivateUsage]
|
||||
for res in rm._map:
|
||||
assert rm.get(res), f"Could not get resource '{res}'"
|
||||
assert unwrap(rm.get_path(res)).exists(), f"Filepath for resource '{res}' does not exist"
|
||||
|
||||