diff --git a/src/tagstudio/core/constants.py b/src/tagstudio/core/constants.py index 4cd6f582..6c1cd65f 100644 --- a/src/tagstudio/core/constants.py +++ b/src/tagstudio/core/constants.py @@ -4,7 +4,10 @@ VERSION: str = "9.5.7" # Major.Minor.Patch VERSION_BRANCH: str = "" # Usually "" or "Pre-Release" +GITHUB_REPO_URL = "https://github.com/TagStudioDev/TagStudio" GITHUB_RELEASE_URL = "https://github.com/TagStudioDev/TagStudio/releases/latest" +DOCS_URL = "https://docs.tagstud.io" +DISCORD_URL = "https://discord.com/invite/hRNnVKhF2G" # The folder & file names where TagStudio keeps its data relative to a library. TS_FOLDER_NAME: str = ".TagStudio" @@ -13,9 +16,7 @@ COLLAGE_FOLDER_NAME: str = "collages" IGNORE_NAME: str = ".ts_ignore" THUMB_CACHE_NAME: str = "thumbs" -FONT_SAMPLE_TEXT: str = ( - """ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!?@$%(){}[]""" -) +FONT_SAMPLE_TEXT: str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!?@$%(){}[]" FONT_SAMPLE_SIZES: list[int] = [10, 15, 20] # NOTE: These were the field IDs used for the "Tags", "Content Tags", and "Meta Tags" fields inside @@ -27,5 +28,4 @@ TAG_FAVORITE = 1 TAG_META = 2 RESERVED_TAG_START = 0 RESERVED_TAG_END = 999 - RESERVED_NAMESPACE_PREFIX = "tagstudio" diff --git a/src/tagstudio/qt/controllers/edit_field_template_modal.py b/src/tagstudio/qt/controllers/edit_field_template_modal.py index 662649b7..5ac7cf30 100644 --- a/src/tagstudio/qt/controllers/edit_field_template_modal.py +++ b/src/tagstudio/qt/controllers/edit_field_template_modal.py @@ -9,9 +9,9 @@ from tagstudio.core.library.alchemy.fields import ( DatetimeFieldTemplate, TextFieldTemplate, ) -from tagstudio.qt.models.palette import ColorType, UiColor, get_ui_color from tagstudio.qt.translations import Translations from tagstudio.qt.views.edit_field_template_modal_view import EditFieldTemplateModalView +from tagstudio.qt.views.stylesheets.stylesheets import line_edit_style logger = structlog.get_logger(__name__) @@ -71,11 +71,7 @@ class EditFieldTemplateModal(EditFieldTemplateModalView): def __on_name_changed(self): is_empty = not self.name_field.text().strip() - self.name_field.setStyleSheet( - f"border: 1px solid {get_ui_color(ColorType.PRIMARY, UiColor.RED)}; border-radius: 2px" - if is_empty - else "" - ) + self.name_field.setStyleSheet(line_edit_style() if is_empty else "") if self.panel_save_button is not None: self.panel_save_button.setDisabled(is_empty) diff --git a/src/tagstudio/qt/controllers/fix_ignored_modal_controller.py b/src/tagstudio/qt/controllers/fix_ignored_modal_controller.py index e841f61d..cad8181c 100644 --- a/src/tagstudio/qt/controllers/fix_ignored_modal_controller.py +++ b/src/tagstudio/qt/controllers/fix_ignored_modal_controller.py @@ -13,6 +13,7 @@ from tagstudio.qt.mixed.progress_bar import ProgressWidget from tagstudio.qt.mixed.remove_ignored_modal import RemoveIgnoredModal from tagstudio.qt.translations import Translations from tagstudio.qt.views.fix_ignored_modal_view import FixIgnoredEntriesModalView +from tagstudio.qt.views.stylesheets.stylesheets import header # Only import for type checking/autocompletion, will not be imported at runtime. if TYPE_CHECKING: @@ -78,7 +79,7 @@ class FixIgnoredEntriesModal(FixIgnoredEntriesModalView): count_text: str = Translations.format( "entries.ignored.ignored_count", count=count if count >= 0 else "—" ) - self.ignored_count_label.setText(f"

{count_text}

") + self.ignored_count_label.setText(header(count_text, 3)) def update_driver_widgets(self): if ( diff --git a/src/tagstudio/qt/controllers/library_info_window_controller.py b/src/tagstudio/qt/controllers/library_info_window_controller.py index 426e1edd..d0828dcb 100644 --- a/src/tagstudio/qt/controllers/library_info_window_controller.py +++ b/src/tagstudio/qt/controllers/library_info_window_controller.py @@ -22,6 +22,7 @@ from tagstudio.core.utils.types import unwrap from tagstudio.qt.translations import Translations from tagstudio.qt.utils import file_opener from tagstudio.qt.views.library_info_window_view import LibraryInfoWindowView +from tagstudio.qt.views.stylesheets.stylesheets import header # Only import for type checking/autocompletion, will not be imported at runtime. if TYPE_CHECKING: @@ -61,7 +62,7 @@ class LibraryInfoWindow(LibraryInfoWindowView): title: str = Translations.format( "library_info.title", library_dir=self.lib.library_dir.stem ) - self.title_label.setText(f"

{title}

") + self.title_label.setText(header(title, 2)) def update_stats(self): self.entry_count_label.setText(f"{self.lib.entries_count}") diff --git a/src/tagstudio/qt/controllers/paged_panel_controller.py b/src/tagstudio/qt/controllers/paged_panel_controller.py index f4a7e8e8..53cc146f 100644 --- a/src/tagstudio/qt/controllers/paged_panel_controller.py +++ b/src/tagstudio/qt/controllers/paged_panel_controller.py @@ -10,6 +10,7 @@ from PySide6.QtCore import Qt from PySide6.QtWidgets import QHBoxLayout, QLabel, QVBoxLayout, QWidget from tagstudio.qt.controllers.paged_panel_state import PagedPanelState +from tagstudio.qt.views.stylesheets.stylesheets import header logger = structlog.get_logger(__name__) @@ -89,7 +90,7 @@ class PagedPanel(QWidget): # Update Title self.setWindowTitle(frame.title) - self.title_label.setText(f"

{frame.title}

") + self.title_label.setText(header(frame.title, 1)) # Update Body Widget if self.body_layout.itemAt(0): @@ -107,7 +108,7 @@ class PagedPanel(QWidget): if isinstance(item, QWidget): self.button_nav_layout.addWidget(item) item.setHidden(False) - elif isinstance(item, int): + elif isinstance(item, int): # pyright: ignore[reportUnnecessaryIsInstance] self.button_nav_layout.addStretch(item) @override diff --git a/src/tagstudio/qt/mixed/about_modal.py b/src/tagstudio/qt/mixed/about_modal.py index 3d1092f1..61033973 100644 --- a/src/tagstudio/qt/mixed/about_modal.py +++ b/src/tagstudio/qt/mixed/about_modal.py @@ -3,10 +3,11 @@ import math +from pathlib import Path from PIL import ImageQt from PySide6.QtCore import Qt -from PySide6.QtGui import QGuiApplication, QPixmap +from PySide6.QtGui import QPixmap from PySide6.QtWidgets import ( QFormLayout, QHBoxLayout, @@ -17,35 +18,29 @@ from PySide6.QtWidgets import ( QWidget, ) -from tagstudio.core.constants import VERSION, VERSION_BRANCH -from tagstudio.core.enums import Theme +from tagstudio.core.constants import ( + DISCORD_URL, + DOCS_URL, + GITHUB_REPO_URL, + VERSION, + VERSION_BRANCH, +) from tagstudio.core.ts_core import TagStudioCore from tagstudio.core.utils.types import unwrap from tagstudio.qt.models.palette import ColorType, UiColor, get_ui_color from tagstudio.qt.previews.vendored import ffmpeg from tagstudio.qt.resource_manager import ResourceManager from tagstudio.qt.translations import Translations +from tagstudio.qt.views.stylesheets.stylesheets import form_content_style, header class AboutModal(QWidget): - def __init__(self, config_path): + def __init__(self, config_path: Path | str): super().__init__() self.setWindowTitle(Translations["about.title"]) self.rm: ResourceManager = ResourceManager() - # TODO: There should be a global button theme somewhere. - self.form_content_style = ( - f"background-color:{ - Theme.COLOR_BG.value - if QGuiApplication.styleHints().colorScheme() is Qt.ColorScheme.Dark - else Theme.COLOR_BG_LIGHT.value - };" - "border-radius:3px;" - "font-weight: 500;" - "padding: 2px;" - ) - self.setWindowModality(Qt.WindowModality.ApplicationModal) self.setMinimumSize(360, 540) self.setMaximumSize(600, 600) @@ -72,7 +67,8 @@ class AboutModal(QWidget): # Title ---------------------------------------------------------------- branch: str = (" (" + VERSION_BRANCH + ")") if VERSION_BRANCH else "" - self.title_label = QLabel(f"

TagStudio Alpha {VERSION}{branch}

") + # NOTE: Do not localize program name. + self.title_label = QLabel(header(f"TagStudio Alpha {VERSION}{branch}", 2)) self.title_label.setAlignment(Qt.AlignmentFlag.AlignCenter) # Description ---------------------------------------------------------- @@ -105,14 +101,18 @@ class AboutModal(QWidget): self.system_info_layout.setLabelAlignment(Qt.AlignmentFlag.AlignRight) # Version - version_title = QLabel("Version") - most_recent_release = unwrap(TagStudioCore.get_most_recent_release_version(), "UNKNOWN") - version_content_style = self.form_content_style - if most_recent_release == VERSION: + version_title = QLabel(Translations["about.version"]) + latest_version = unwrap(TagStudioCore.get_most_recent_release_version(), "?") + version_content_style = form_content_style() + if latest_version == VERSION: version_content = QLabel(f"{VERSION}") else: - version_content = QLabel(f"{VERSION} (Latest Release: {most_recent_release})") - version_content_style += "color: #d9534f;" + version_content = QLabel( + Translations.format( + "about.version.latest", built_version=VERSION, latest_version=latest_version + ) + ) + version_content_style += f"color: {red};" version_content.setStyleSheet(version_content_style) version_content.setMaximumWidth(version_content.sizeHint().width()) self.system_info_layout.addRow(version_title, version_content) @@ -120,40 +120,37 @@ class AboutModal(QWidget): # License license_title = QLabel(f"{Translations['about.license']}") license_content = QLabel("GPLv3") - license_content.setStyleSheet(self.form_content_style) + license_content.setStyleSheet(form_content_style()) license_content.setMaximumWidth(license_content.sizeHint().width()) self.system_info_layout.addRow(license_title, license_content) # Config Path config_path_title = QLabel(f"{Translations['about.config_path']}") config_path_content = QLabel(f"{config_path}") - config_path_content.setStyleSheet(self.form_content_style) + config_path_content.setStyleSheet(form_content_style()) config_path_content.setWordWrap(True) self.system_info_layout.addRow(config_path_title, config_path_content) # FFmpeg Status ffmpeg_path_title = QLabel("FFmpeg") ffmpeg_path_content = QLabel(f"{ffmpeg_status}") - ffmpeg_path_content.setStyleSheet(self.form_content_style) + ffmpeg_path_content.setStyleSheet(form_content_style()) ffmpeg_path_content.setMaximumWidth(ffmpeg_path_content.sizeHint().width()) self.system_info_layout.addRow(ffmpeg_path_title, ffmpeg_path_content) # FFprobe Status ffprobe_path_title = QLabel("FFprobe") ffprobe_path_content = QLabel(f"{ffprobe_status}") - ffprobe_path_content.setStyleSheet(self.form_content_style) + ffprobe_path_content.setStyleSheet(form_content_style()) ffprobe_path_content.setMaximumWidth(ffprobe_path_content.sizeHint().width()) self.system_info_layout.addRow(ffprobe_path_title, ffprobe_path_content) # Links ---------------------------------------------------------------- - repo_link = "https://github.com/TagStudioDev/TagStudio" - docs_link = "https://docs.tagstud.io" - discord_link = "https://discord.com/invite/hRNnVKhF2G" self.links_label = QLabel( - f'

GitHub | ' - f'{Translations["about.documentation"]} | ' - f'Discord

' + f'

GitHub | ' + f'{Translations["about.documentation"]} | ' + f'Discord

' ) self.links_label.setWordWrap(True) self.links_label.setOpenExternalLinks(True) diff --git a/src/tagstudio/qt/mixed/add_field.py b/src/tagstudio/qt/mixed/add_field.py index 653b9788..946f4d74 100644 --- a/src/tagstudio/qt/mixed/add_field.py +++ b/src/tagstudio/qt/mixed/add_field.py @@ -19,6 +19,7 @@ from PySide6.QtWidgets import ( from tagstudio.core.library.alchemy.library import Library from tagstudio.qt.translations import FIELD_TYPE_KEYS, Translations +from tagstudio.qt.views.stylesheets.stylesheets import header logger = structlog.get_logger(__name__) @@ -39,10 +40,9 @@ class AddFieldModal(QWidget): self.root_layout = QVBoxLayout(self) self.root_layout.setContentsMargins(6, 6, 6, 6) - self.title_widget = QLabel(Translations["field.add"]) + self.title_widget = QLabel(header(Translations["field.add"], 3)) self.title_widget.setObjectName("fieldTitle") self.title_widget.setWordWrap(True) - self.title_widget.setStyleSheet("font-weight:bold;font-size:14px;padding-top: 6px;") self.title_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) self.list_widget = QListWidget() diff --git a/src/tagstudio/qt/mixed/build_color.py b/src/tagstudio/qt/mixed/build_color.py index 1c16f036..9791aaa7 100644 --- a/src/tagstudio/qt/mixed/build_color.py +++ b/src/tagstudio/qt/mixed/build_color.py @@ -3,6 +3,7 @@ import contextlib +from typing import override import structlog from PySide6.QtCore import Qt, Signal @@ -24,15 +25,14 @@ from tagstudio.core.library.alchemy.library import Library, slugify from tagstudio.core.library.alchemy.models import TagColorGroup from tagstudio.core.utils.types import unwrap from tagstudio.qt.mixed.tag_color_preview import TagColorPreview -from tagstudio.qt.mixed.tag_widget import ( - get_border_color, - get_highlight_color, - get_text_color, -) -from tagstudio.qt.models.palette import ColorType, UiColor, get_tag_color, get_ui_color +from tagstudio.qt.models.palette import ColorType, get_tag_color from tagstudio.qt.translations import Translations from tagstudio.qt.views.panel_modal import PanelWidget -from tagstudio.qt.views.stylesheets.stylesheets import checkbox_style +from tagstudio.qt.views.stylesheets.stylesheets import ( + checkbox_style, + line_edit_style, + list_button_style, +) logger = structlog.get_logger(__name__) @@ -130,7 +130,6 @@ class BuildColorPanel(PanelWidget): color=QColor(unwrap(self.preview_button.tag_color_group).secondary) if unwrap(self.preview_button.tag_color_group).secondary else None, - color_border=checked, ) ) self.border_layout.addWidget(self.border_checkbox) @@ -193,89 +192,20 @@ class BuildColorPanel(PanelWidget): def update_primary(self, color: QColor): logger.info("[BuildColorPanel] Updating Primary", primary_color=color) - highlight_color = get_highlight_color(color) - text_color = get_text_color(color, highlight_color) - border_color = get_border_color(color) - hex_code = color.name().upper() + self.primary_button.setText(hex_code) - self.primary_button.setStyleSheet( - f"QPushButton{{" - f"background: rgba{color.toTuple()};" - f"color: rgba{text_color.toTuple()};" - f"font-weight: 600;" - f"border-color: rgba{border_color.toTuple()};" - f"border-radius: 6px;" - f"border-style:solid;" - f"border-width: 2px;" - f"padding-right: 4px;" - f"padding-bottom: 1px;" - f"padding-left: 4px;" - f"font-size: 13px" - f"}}" - f"QPushButton::hover{{" - f"border-color: rgba{highlight_color.toTuple()};" - f"}}" - f"QPushButton::pressed{{" - f"background: rgba{highlight_color.toTuple()};" - f"color: rgba{color.toTuple()};" - f"border-color: rgba{color.toTuple()};" - f"}}" - f"QPushButton::focus{{" - f"padding-right: 0px;" - f"padding-left: 0px;" - f"outline-style: solid;" - f"outline-width: 1px;" - f"outline-radius: 4px;" - f"outline-color: rgba{text_color.toTuple()};" - f"}}" - ) + self.primary_button.setStyleSheet(list_button_style(color)) self.preview_button.set_tag_color_group(self.build_color()[1]) - def update_secondary(self, color: QColor | None = None, color_border: bool = False): + def update_secondary(self, color: QColor | None = None): logger.info("[BuildColorPanel] Updating Secondary", color=color) color_ = color or QColor(get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT)) - - highlight_color = get_highlight_color(color_) - text_color = get_text_color(color_, highlight_color) - border_color = get_border_color(color_) - hex_code = "" if not color else color.name().upper() - self.secondary_button.setText( - Translations["color.title.no_color"] if not color else hex_code - ) - self.secondary_button.setStyleSheet( - f"QPushButton{{" - f"background: rgba{color_.toTuple()};" - f"color: rgba{text_color.toTuple()};" - f"font-weight: 600;" - f"border-color: rgba{border_color.toTuple()};" - f"border-radius: 6px;" - f"border-style:solid;" - f"border-width: 2px;" - f"padding-right: 4px;" - f"padding-bottom: 1px;" - f"padding-left: 4px;" - f"font-size: 13px" - f"}}" - f"QPushButton::hover{{" - f"border-color: rgba{highlight_color.toTuple()};" - f"}}" - f"QPushButton::pressed{{" - f"background: rgba{highlight_color.toTuple()};" - f"color: rgba{color_.toTuple()};" - f"border-color: rgba{color_.toTuple()};" - f"}}" - f"QPushButton::focus{{" - f"padding-right: 0px;" - f"padding-left: 0px;" - f"outline-style: solid;" - f"outline-width: 1px;" - f"outline-radius: 4px;" - f"outline-color: rgba{text_color.toTuple()};" - f"}}" - ) + + self.secondary_button.setText(hex_code if color else Translations["color.title.no_color"]) + self.secondary_button.setStyleSheet(list_button_style(color_)) self.preview_button.set_tag_color_group(self.build_color()[1]) def update_known_colors(self): @@ -317,17 +247,9 @@ class BuildColorPanel(PanelWidget): is_slug_empty = not slug is_invalid = False - self.name_field.setStyleSheet( - f"border: 1px solid {get_ui_color(ColorType.PRIMARY, UiColor.RED)}; border-radius: 2px" - if is_name_empty - else "" - ) + self.name_field.setStyleSheet(line_edit_style() if is_name_empty else "") - self.slug_field.setStyleSheet( - f"border: 1px solid {get_ui_color(ColorType.PRIMARY, UiColor.RED)}; border-radius: 2px" - if is_slug_empty or is_invalid - else "" - ) + self.slug_field.setStyleSheet(line_edit_style() if is_slug_empty or is_invalid else "") self.slug_field.setText(slug) self.update_preview_text() @@ -364,13 +286,7 @@ class BuildColorPanel(PanelWidget): ) return (self.color_group, new_color) + @override def parent_post_init(self): - # self.setTabOrder(self.name_field, self.shorthand_field) - # self.setTabOrder(self.shorthand_field, self.aliases_add_button) - # self.setTabOrder(self.aliases_add_button, self.parent_tags_add_button) - # self.setTabOrder(self.parent_tags_add_button, self.color_button) - # self.setTabOrder(self.color_button, self.panel_cancel_button) - # self.setTabOrder(self.panel_cancel_button, self.panel_save_button) - # self.setTabOrder(self.panel_save_button, self.aliases_table.cellWidget(0, 1)) self.name_field.selectAll() self.name_field.setFocus() diff --git a/src/tagstudio/qt/mixed/build_namespace.py b/src/tagstudio/qt/mixed/build_namespace.py index 5b91a700..36487a7b 100644 --- a/src/tagstudio/qt/mixed/build_namespace.py +++ b/src/tagstudio/qt/mixed/build_namespace.py @@ -3,6 +3,7 @@ import contextlib +from typing import override from uuid import uuid4 import structlog @@ -12,9 +13,9 @@ from PySide6.QtWidgets import QLabel, QLineEdit, QVBoxLayout, QWidget from tagstudio.core.constants import RESERVED_NAMESPACE_PREFIX from tagstudio.core.library.alchemy.library import Library, ReservedNamespaceError, slugify from tagstudio.core.library.alchemy.models import Namespace -from tagstudio.qt.models.palette import ColorType, UiColor, get_ui_color from tagstudio.qt.translations import Translations from tagstudio.qt.views.panel_modal import PanelWidget +from tagstudio.qt.views.stylesheets.stylesheets import line_edit_style logger = structlog.get_logger(__name__) @@ -111,17 +112,9 @@ class BuildNamespacePanel(PanelWidget): is_slug_empty = not slug is_invalid = False - self.name_field.setStyleSheet( - f"border: 1px solid {get_ui_color(ColorType.PRIMARY, UiColor.RED)}; border-radius: 2px" - if is_name_empty - else "" - ) + self.name_field.setStyleSheet(line_edit_style() if is_name_empty else "") - self.slug_field.setStyleSheet( - f"border: 1px solid {get_ui_color(ColorType.PRIMARY, UiColor.RED)}; border-radius: 2px" - if is_slug_empty or is_invalid - else "" - ) + self.slug_field.setStyleSheet(line_edit_style() if is_slug_empty or is_invalid else "") self.slug_field.setText(slug) @@ -156,6 +149,7 @@ class BuildNamespacePanel(PanelWidget): logger.info("[BuildNamespacePanel] Built Namespace", slug=slug, name=name) return namespace + @override def parent_post_init(self): self.setTabOrder(self.name_field, self.slug_field) self.name_field.selectAll() diff --git a/src/tagstudio/qt/mixed/build_tag.py b/src/tagstudio/qt/mixed/build_tag.py index eb17024c..4c81a968 100644 --- a/src/tagstudio/qt/mixed/build_tag.py +++ b/src/tagstudio/qt/mixed/build_tag.py @@ -33,15 +33,19 @@ from tagstudio.qt.mixed.tag_color_preview import TagColorPreview from tagstudio.qt.mixed.tag_color_selection import TagColorSelection from tagstudio.qt.mixed.tag_widget import ( TagWidget, - get_border_color, - get_highlight_color, - get_primary_color, - get_text_color, + get_tag_border_color, + get_tag_highlight_color, + get_tag_primary_color, + get_tag_text_color, ) -from tagstudio.qt.models.palette import ColorType, UiColor, get_ui_color from tagstudio.qt.translations import Translations from tagstudio.qt.views.panel_modal import PanelModal, PanelWidget -from tagstudio.qt.views.stylesheets.stylesheets import checkbox_style +from tagstudio.qt.views.stylesheets.stylesheets import ( + checkbox_style, + colored_radio_button_style, + header, + line_edit_style, +) from tagstudio.qt.views.tag_search_panel_view import TagSearchPanelView logger = structlog.get_logger(__name__) @@ -244,7 +248,7 @@ class BuildTagPanel(PanelWidget): self.root_layout.addWidget(self.aliases_add_button) self.root_layout.addWidget(self.parent_tags_widget) self.root_layout.addWidget(self.color_widget) - self.root_layout.addWidget(QLabel("

Properties

")) + self.root_layout.addWidget(QLabel(header(Translations["tag.properties"], 3))) self.root_layout.addWidget(self.cat_widget) self.root_layout.addWidget(self.hidden_widget) @@ -357,13 +361,13 @@ class BuildTagPanel(PanelWidget): row.setSpacing(3) # Init Colors - primary_color = get_primary_color(tag) + primary_color = get_tag_primary_color(tag) border_color = ( - get_border_color(primary_color) + get_tag_border_color(primary_color) if not (tag.color and tag.color.secondary and tag.color.color_border) else (QColor(tag.color.secondary)) ) - highlight_color = get_highlight_color( + highlight_color = get_tag_highlight_color( primary_color if not (tag.color and tag.color.secondary) else QColor(tag.color.secondary) @@ -372,7 +376,7 @@ class BuildTagPanel(PanelWidget): if tag.color and tag.color.secondary: text_color = QColor(tag.color.secondary) else: - text_color = get_text_color(primary_color, highlight_color) + text_color = get_tag_text_color(primary_color, highlight_color) # Add Tag Widget tag_widget = TagWidget( @@ -395,35 +399,7 @@ class BuildTagPanel(PanelWidget): disam_button.setFixedSize(22, 22) disam_button.setToolTip(Translations["tag.disambiguation.tooltip"]) disam_button.setStyleSheet( - f"QRadioButton{{" - f"background: rgba{primary_color.toTuple()};" - f"color: rgba{text_color.toTuple()};" - f"border-color: rgba{border_color.toTuple()};" - f"border-radius: 6px;" - f"border-style:solid;" - f"border-width: 2px;" - f"}}" - f"QRadioButton::indicator{{" - f"width: 10px;" - f"height: 10px;" - f"border-radius: 2px;" - f"margin: 4px;" - f"}}" - f"QRadioButton::indicator:checked{{" - f"background: rgba{text_color.toTuple()};" - f"}}" - f"QRadioButton::hover{{" - f"border-color: rgba{highlight_color.toTuple()};" - f"}}" - f"QRadioButton::pressed{{" - f"background: rgba{border_color.toTuple()};" - f"color: rgba{primary_color.toTuple()};" - f"border-color: rgba{primary_color.toTuple()};" - f"}}" - f"QRadioButton::focus{{" - f"border-color: rgba{highlight_color.toTuple()};" - f"outline:none;" - f"}}" + colored_radio_button_style(primary_color, text_color, border_color, highlight_color) ) self.disam_button_group.addButton(disam_button) @@ -543,11 +519,7 @@ class BuildTagPanel(PanelWidget): def on_name_changed(self): is_empty = not self.name_field.text().strip() - self.name_field.setStyleSheet( - f"border: 1px solid {get_ui_color(ColorType.PRIMARY, UiColor.RED)}; border-radius: 2px" - if is_empty - else "" - ) + self.name_field.setStyleSheet(line_edit_style() if is_empty else "") if self.panel_save_button is not None: self.panel_save_button.setDisabled(is_empty) diff --git a/src/tagstudio/qt/mixed/color_box.py b/src/tagstudio/qt/mixed/color_box.py index d4d7703e..88771bbd 100644 --- a/src/tagstudio/qt/mixed/color_box.py +++ b/src/tagstudio/qt/mixed/color_box.py @@ -10,16 +10,15 @@ from PySide6.QtCore import Signal from PySide6.QtWidgets import QMessageBox, QPushButton from tagstudio.core.constants import RESERVED_NAMESPACE_PREFIX -from tagstudio.core.library.alchemy.enums import TagColorEnum from tagstudio.core.library.alchemy.models import TagColorGroup from tagstudio.core.utils.types import unwrap from tagstudio.qt.mixed.build_color import BuildColorPanel from tagstudio.qt.mixed.field_widget import FieldWidget from tagstudio.qt.mixed.tag_color_label import TagColorLabel -from tagstudio.qt.models.palette import ColorType, get_tag_color from tagstudio.qt.translations import Translations from tagstudio.qt.views.layouts.flow_layout import FlowLayout from tagstudio.qt.views.panel_modal import PanelModal +from tagstudio.qt.views.stylesheets.stylesheets import add_button_style if typing.TYPE_CHECKING: from tagstudio.core.library.alchemy.library import Library @@ -43,34 +42,6 @@ class ColorBoxWidget(FieldWidget): title = "" if not self.lib.engine else self.lib.get_namespace_name(group) super().__init__(title) - self.add_button_stylesheet = ( - f"QPushButton{{" - f"background: {get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT)};" - f"color: {get_tag_color(ColorType.TEXT, TagColorEnum.DEFAULT)};" - f"font-weight: 600;" - f"border-color:{get_tag_color(ColorType.BORDER, TagColorEnum.DEFAULT)};" - f"border-radius: 6px;" - f"border-style:solid;" - f"border-width: 2px;" - f"padding-right: 4px;" - f"padding-bottom: 2px;" - f"padding-left: 4px;" - f"font-size: 15px" - f"}}" - f"QPushButton::hover{{" - f"border-color:{get_tag_color(ColorType.LIGHT_ACCENT, TagColorEnum.DEFAULT)};" - f"}}" - f"QPushButton::pressed{{" - f"background: {get_tag_color(ColorType.LIGHT_ACCENT, TagColorEnum.DEFAULT)};" - f"color: {get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT)};" - f"border-color: {get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT)};" - f"}}" - f"QPushButton::focus{{" - f"border-color: {get_tag_color(ColorType.LIGHT_ACCENT, TagColorEnum.DEFAULT)};" - f"outline:none;" - f"}}" - ) - self.setObjectName("colorBox") self.base_layout = FlowLayout() self.base_layout.enable_grid_optimizations(value=True) @@ -114,7 +85,7 @@ class ColorBoxWidget(FieldWidget): add_button.setText("+") add_button.setFlat(True) add_button.setFixedSize(22, 22) - add_button.setStyleSheet(self.add_button_stylesheet) + add_button.setStyleSheet(add_button_style()) add_button.clicked.connect( lambda: self.edit_color( TagColorGroup( diff --git a/src/tagstudio/qt/mixed/datetime_picker.py b/src/tagstudio/qt/mixed/datetime_picker.py index d28bfb1b..1706eaa5 100644 --- a/src/tagstudio/qt/mixed/datetime_picker.py +++ b/src/tagstudio/qt/mixed/datetime_picker.py @@ -10,6 +10,7 @@ from PySide6.QtCore import QDateTime from PySide6.QtWidgets import QDateTimeEdit, QLineEdit, QVBoxLayout from tagstudio.qt.views.panel_modal import PanelWidget +from tagstudio.qt.views.stylesheets.stylesheets import title_line_edit_style if typing.TYPE_CHECKING: from tagstudio.qt.ts_qt import QtDriver @@ -46,7 +47,7 @@ class DatetimePicker(PanelWidget): self.root_layout.setContentsMargins(6, 0, 6, 0) self.name_field = QLineEdit() - self.name_field.setStyleSheet("font-weight:bold;font-size:14px;padding-top:6px") + self.name_field.setStyleSheet(title_line_edit_style()) self.name_field.setText(name) if isinstance(datetime, str): diff --git a/src/tagstudio/qt/mixed/field_containers.py b/src/tagstudio/qt/mixed/field_containers.py index 5a60a64e..6a7d5555 100644 --- a/src/tagstudio/qt/mixed/field_containers.py +++ b/src/tagstudio/qt/mixed/field_containers.py @@ -434,7 +434,7 @@ class FieldContainers(QWidget): ) else: text = f"{Translations['field.mixed_data']}" - inner_widget = TextContainerWidget("Mixed Tags", text) + inner_widget = TextContainerWidget("Mixed Tags", text) # NOTE: Unlocalized but unused container.set_inner_widget(inner_widget) container.set_edit_callback() diff --git a/src/tagstudio/qt/mixed/field_widget.py b/src/tagstudio/qt/mixed/field_widget.py index 845ab504..4bc9acc6 100644 --- a/src/tagstudio/qt/mixed/field_widget.py +++ b/src/tagstudio/qt/mixed/field_widget.py @@ -14,7 +14,7 @@ from PySide6.QtCore import QEvent, 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.views.stylesheets.stylesheets import container_style, header logger = structlog.get_logger(__name__) @@ -37,17 +37,6 @@ class FieldContainer(QWidget): trash_icon_128.load() # TODO: There should be a global button theme somewhere. - container_style = ( - f"QWidget#fieldContainer{{" - "border-radius:4px;" - f"}}" - f"QWidget#fieldContainer::hover{{" - f"background-color:{Theme.COLOR_HOVER.value};" - f"}}" - f"QWidget#fieldContainer::pressed{{" - f"background-color:{Theme.COLOR_PRESSED.value};" - f"}}" - ) def __init__(self, title: str = "Field", inline: bool = True) -> None: super().__init__() @@ -126,7 +115,7 @@ class FieldContainer(QWidget): self.inner_layout.addWidget(self.field) self.set_title(title) - self.setStyleSheet(FieldContainer.container_style) + self.setStyleSheet(container_style()) def set_copy_callback(self, callback: Callable[[], None] | None = None) -> None: with catch_warnings(record=True): @@ -166,7 +155,7 @@ class FieldContainer(QWidget): return None def set_title(self, title: str) -> None: - self.title = self.title = f"

{title}

" + self.title = header(title, 4) self.title_widget.setText(self.title) @override diff --git a/src/tagstudio/qt/mixed/file_attributes.py b/src/tagstudio/qt/mixed/file_attributes.py index 38dfd3f0..3193aee0 100644 --- a/src/tagstudio/qt/mixed/file_attributes.py +++ b/src/tagstudio/qt/mixed/file_attributes.py @@ -11,13 +11,12 @@ from datetime import timedelta from pathlib import Path import structlog -from humanfriendly import format_size +from humanfriendly import format_size # pyright: ignore[reportUnknownVariableType] from PIL import ImageFont from PySide6.QtCore import Qt -from PySide6.QtGui import QGuiApplication from PySide6.QtWidgets import QLabel, QVBoxLayout, QWidget -from tagstudio.core.enums import ShowFilepathOption, Theme +from tagstudio.core.enums import ShowFilepathOption from tagstudio.core.library.alchemy.library import Library from tagstudio.core.library.ignore import Ignore from tagstudio.core.media_types import MediaCategories @@ -25,6 +24,7 @@ from tagstudio.core.utils.types import unwrap from tagstudio.qt.models.palette import ColorType, UiColor, get_ui_color from tagstudio.qt.translations import Translations from tagstudio.qt.utils.file_opener import FileOpenerHelper, FileOpenerLabel +from tagstudio.qt.views.stylesheets.stylesheets import properties_style if typing.TYPE_CHECKING: from tagstudio.qt.ts_qt import QtDriver @@ -48,26 +48,8 @@ class FileAttributes(QWidget): root_layout.setContentsMargins(0, 0, 0, 0) root_layout.setSpacing(0) - label_bg_color = ( - Theme.COLOR_BG_DARK.value - if QGuiApplication.styleHints().colorScheme() is Qt.ColorScheme.Dark - else Theme.COLOR_DARK_LABEL.value - ) - - self.date_style = "font-size:12px;" + self.date_style = "font-size: 12px;" self.file_label_style = "font-size: 12px" - self.properties_style = ( - f"background-color:{label_bg_color};" - "color:#FFFFFF;" - "font-family:Oxanium;" - "font-weight:bold;" - "font-size:12px;" - "border-radius:3px;" - "padding-top: 4px;" - "padding-right: 1px;" - "padding-bottom: 1px;" - "padding-left: 1px;" - ) self.file_label = FileOpenerLabel() self.file_label.setObjectName("filenameLabel") @@ -93,7 +75,7 @@ class FileAttributes(QWidget): self.dimensions_label = QLabel() self.dimensions_label.setObjectName("dimensionsLabel") self.dimensions_label.setWordWrap(True) - self.dimensions_label.setStyleSheet(self.properties_style) + self.dimensions_label.setStyleSheet(properties_style()) self.dimensions_label.setHidden(True) self.date_container = QWidget() diff --git a/src/tagstudio/qt/mixed/fix_dupe_files.py b/src/tagstudio/qt/mixed/fix_dupe_files.py index c7c3165b..9b102dfa 100644 --- a/src/tagstudio/qt/mixed/fix_dupe_files.py +++ b/src/tagstudio/qt/mixed/fix_dupe_files.py @@ -19,6 +19,7 @@ from tagstudio.core.library.alchemy.library import Library from tagstudio.core.library.alchemy.registries.dupe_files_registry import DupeFilesRegistry from tagstudio.qt.mixed.mirror_entries_modal import MirrorEntriesModal from tagstudio.qt.translations import Translations +from tagstudio.qt.views.stylesheets.stylesheets import header # Only import for type checking/autocompletion, will not be imported at runtime. if TYPE_CHECKING: @@ -49,7 +50,6 @@ class FixDupeFilesModal(QWidget): self.dupe_count = QLabel() self.dupe_count.setObjectName("dupeCountLabel") - self.dupe_count.setStyleSheet("font-weight:bold;font-size:14px;") self.dupe_count.setAlignment(Qt.AlignmentFlag.AlignCenter) self.file_label = QLabel(Translations["file.duplicates.dupeguru.no_file"]) @@ -119,13 +119,19 @@ class FixDupeFilesModal(QWidget): def set_dupe_count(self, count: int): if count < 0: self.mirror_button.setDisabled(True) - self.dupe_count.setText(Translations["file.duplicates.matches_uninitialized"]) + self.dupe_count.setText( + header(Translations["file.duplicates.matches_uninitialized"], 4) + ) elif count == 0: self.mirror_button.setDisabled(True) - self.dupe_count.setText(Translations.format("file.duplicates.matches", count=count)) + self.dupe_count.setText( + header(Translations.format("file.duplicates.matches", count=count), 4) + ) else: self.mirror_button.setDisabled(False) - self.dupe_count.setText(Translations.format("file.duplicates.matches", count=count)) + self.dupe_count.setText( + header(Translations.format("file.duplicates.matches", count=count), 4) + ) @override def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: # noqa N802 diff --git a/src/tagstudio/qt/mixed/fix_unlinked.py b/src/tagstudio/qt/mixed/fix_unlinked.py index 59474c1f..f96bf2d0 100644 --- a/src/tagstudio/qt/mixed/fix_unlinked.py +++ b/src/tagstudio/qt/mixed/fix_unlinked.py @@ -15,6 +15,7 @@ from tagstudio.qt.mixed.progress_bar import ProgressWidget from tagstudio.qt.mixed.relink_entries_modal import RelinkUnlinkedEntries from tagstudio.qt.mixed.remove_unlinked_modal import RemoveUnlinkedEntriesModal from tagstudio.qt.translations import Translations +from tagstudio.qt.views.stylesheets.stylesheets import header # Only import for type checking/autocompletion, will not be imported at runtime. if TYPE_CHECKING: @@ -148,7 +149,7 @@ class FixUnlinkedEntriesModal(QWidget): count_text: str = Translations.format( "entries.unlinked.unlinked_count", count=count if count >= 0 else "—" ) - self.unlinked_count_label.setText(f"

{count_text}

") + self.unlinked_count_label.setText(header(count_text, 3)) @override def showEvent(self, event: QtGui.QShowEvent) -> None: diff --git a/src/tagstudio/qt/mixed/folders_to_tags.py b/src/tagstudio/qt/mixed/folders_to_tags.py index 45cdbc0a..0f9742e2 100644 --- a/src/tagstudio/qt/mixed/folders_to_tags.py +++ b/src/tagstudio/qt/mixed/folders_to_tags.py @@ -29,6 +29,7 @@ from tagstudio.core.utils.types import unwrap from tagstudio.qt.models.palette import ColorType, get_tag_color from tagstudio.qt.translations import Translations from tagstudio.qt.views.layouts.flow_layout import FlowLayout +from tagstudio.qt.views.stylesheets.stylesheets import header if TYPE_CHECKING: from tagstudio.qt.ts_qt import QtDriver @@ -56,7 +57,7 @@ def add_folders_to_tree(library: Library, tree: BranchData, items: tuple[str, .. return branch -@deprecated("Will be replaced with upcoming 'Macros' feature before v9.6") +@deprecated("Will be replaced with upcoming 'Macros' feature.") def folders_to_tags(library: Library): logger.info("Converting folders to Tags") tree = BranchData() @@ -177,10 +178,9 @@ class FoldersToTagsModal(QWidget): self.root_layout = QVBoxLayout(self) self.root_layout.setContentsMargins(6, 6, 6, 6) - self.title_widget = QLabel(Translations["folders_to_tags.title"]) + self.title_widget = QLabel(header(Translations["folders_to_tags.title"], 3)) self.title_widget.setObjectName("title") self.title_widget.setWordWrap(True) - self.title_widget.setStyleSheet("font-weight:bold;font-size:14px;padding-top: 6px") self.title_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) self.desc_widget = QLabel() diff --git a/src/tagstudio/qt/mixed/migration_modal.py b/src/tagstudio/qt/mixed/migration_modal.py index 0859b0ec..7f21f825 100644 --- a/src/tagstudio/qt/mixed/migration_modal.py +++ b/src/tagstudio/qt/mixed/migration_modal.py @@ -48,6 +48,7 @@ from tagstudio.qt.utils.custom_runnable import CustomRunnable from tagstudio.qt.utils.function_iterator import FunctionIterator from tagstudio.qt.views.paged_body_wrapper import PagedBodyWrapper from tagstudio.qt.views.qbutton_wrapper import QPushButtonWrapper +from tagstudio.qt.views.stylesheets.stylesheets import header logger = structlog.get_logger(__name__) @@ -364,7 +365,7 @@ class JsonMigrationModal(QObject): iterator = FunctionIterator(self.migration_iterator) iterator.value.connect( lambda x: ( - pb.setLabelText(f"

{x}

"), + pb.setLabelText(header(x, 4)), self.update_sql_value_ui(show_msg_box=False) if x == Translations["json_migration.checking_for_parity"] else (), @@ -386,7 +387,7 @@ class JsonMigrationModal(QObject): QThreadPool.globalInstance().start(r) except Exception as e: logger.error("[MigrationModal][Iterator] Error:", error=e) - pb.setLabelText(f"

{type(e).__name__}

") + pb.setLabelText(header(type(e).__name__, 4)) pb.setMinimum(1) pb.setValue(1) @@ -410,7 +411,7 @@ class JsonMigrationModal(QObject): ) self.sql_lib.migrate_json_to_sqlite(self.json_lib) yield Translations["json_migration.checking_for_parity"] - check_set = set() + check_set: set[bool] = set() check_set.add(self.check_field_parity()) check_set.add(self.check_path_parity()) check_set.add(self.check_name_parity()) @@ -522,7 +523,7 @@ class JsonMigrationModal(QObject): def assert_ignore_parity(self) -> None: compiled_pats = fnmatch.compile( ignore_to_glob( - Ignore._load_ignore_file( + Ignore._load_ignore_file( # pyright: ignore[reportPrivateUsage] unwrap(self.json_lib.library_dir) / TS_FOLDER_NAME / IGNORE_NAME ) ), diff --git a/src/tagstudio/qt/mixed/tag_color_label.py b/src/tagstudio/qt/mixed/tag_color_label.py index 8052f59d..90d797b7 100644 --- a/src/tagstudio/qt/mixed/tag_color_label.py +++ b/src/tagstudio/qt/mixed/tag_color_label.py @@ -11,12 +11,14 @@ from PySide6.QtWidgets import QHBoxLayout, QPushButton, QVBoxLayout, QWidget from tagstudio.core.library.alchemy.models import TagColorGroup from tagstudio.qt.helpers.escape_text import escape_text -from tagstudio.qt.mixed.tag_widget import ( - get_border_color, - get_highlight_color, - get_text_color, -) from tagstudio.qt.translations import Translations +from tagstudio.qt.views.stylesheets.stylesheets import ( + get_tag_border_color, + get_tag_highlight_color, + get_tag_text_color, + tag_remove_button_style, + tag_style, +) logger = structlog.get_logger(__name__) @@ -101,76 +103,25 @@ class TagColorLabel(QWidget): primary_color = self._get_primary_color(color) border_color = ( - get_border_color(primary_color) + get_tag_border_color(primary_color) if not (color and color.secondary and color.color_border) else (QColor(color.secondary)) ) - highlight_color = get_highlight_color( + highlight_color = get_tag_highlight_color( primary_color if not (color and color.secondary) else QColor(color.secondary) ) text_color: QColor if color and color.secondary: text_color = QColor(color.secondary) else: - text_color = get_text_color(primary_color, highlight_color) + text_color = get_tag_text_color(primary_color, highlight_color) self.bg_button.setStyleSheet( - f"QPushButton{{" - f"background: rgba{primary_color.toTuple()};" - f"color: rgba{text_color.toTuple()};" - f"font-weight: 600;" - f"border-color: rgba{border_color.toTuple()};" - f"border-radius: 6px;" - f"border-style:solid;" - f"border-width: 2px;" - f"padding-right: 4px;" - f"padding-left: 4px;" - f"font-size: 13px" - f"}}" - f"QPushButton::hover{{" - f"border-color: rgba{highlight_color.toTuple()};" - f"}}" - f"QPushButton::pressed{{" - f"background: rgba{highlight_color.toTuple()};" - f"color: rgba{primary_color.toTuple()};" - f"border-color: rgba{primary_color.toTuple()};" - f"}}" - f"QPushButton::focus{{" - f"padding-right: 0px;" - f"padding-left: 0px;" - f"outline-style: solid;" - f"outline-width: 1px;" - f"outline-radius: 4px;" - f"outline-color: rgba{text_color.toTuple()};" - f"}}" + tag_style(primary_color, text_color, border_color, highlight_color) ) self.remove_button.setStyleSheet( - f"QPushButton{{" - f"color: rgba{primary_color.toTuple()};" - f"background: rgba{text_color.toTuple()};" - f"font-weight: 800;" - f"border-radius: 5px;" - f"border-width: 4;" - f"border-color: rgba(0,0,0,0);" - f"padding-bottom: 4px;" - f"font-size: 14px" - f"}}" - f"QPushButton::hover{{" - f"background: rgba{primary_color.toTuple()};" - f"color: rgba{text_color.toTuple()};" - f"border-color: rgba{highlight_color.toTuple()};" - f"border-width: 2;" - f"border-radius: 6px;" - f"}}" - f"QPushButton::pressed{{" - f"background: rgba{border_color.toTuple()};" - f"color: rgba{highlight_color.toTuple()};" - f"}}" - f"QPushButton::focus{{" - f"background: rgba{border_color.toTuple()};" - f"outline:none;" - f"}}" + tag_remove_button_style(primary_color, text_color, border_color, highlight_color) ) self.bg_button.setText(escape_text(color.name)) @@ -183,13 +134,15 @@ class TagColorLabel(QWidget): def set_has_remove(self, has_remove: bool): self.has_remove = has_remove - def enterEvent(self, event: QEnterEvent) -> None: # noqa: N802 + @typing.override + def enterEvent(self, event: QEnterEvent) -> None: if self.has_remove: self.remove_button.setHidden(False) self.update() return super().enterEvent(event) - def leaveEvent(self, event: QEvent) -> None: # noqa: N802 + @typing.override + def leaveEvent(self, event: QEvent) -> None: if self.has_remove: self.remove_button.setHidden(True) self.update() diff --git a/src/tagstudio/qt/mixed/tag_color_manager.py b/src/tagstudio/qt/mixed/tag_color_manager.py index ad1b72fe..99f5b47c 100644 --- a/src/tagstudio/qt/mixed/tag_color_manager.py +++ b/src/tagstudio/qt/mixed/tag_color_manager.py @@ -3,7 +3,7 @@ from collections.abc import Callable -from typing import TYPE_CHECKING, override +from typing import TYPE_CHECKING, Any, override import structlog from PySide6 import QtCore, QtGui @@ -28,6 +28,7 @@ from tagstudio.qt.mixed.color_box import ColorBoxWidget from tagstudio.qt.mixed.field_widget import FieldContainer from tagstudio.qt.translations import Translations from tagstudio.qt.views.panel_modal import PanelModal +from tagstudio.qt.views.stylesheets.stylesheets import header logger = structlog.get_logger(__name__) @@ -62,7 +63,7 @@ class TagColorManager(QWidget): self.title_label = QLabel() self.title_label.setObjectName("titleLabel") self.title_label.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.title_label.setText(f"

{Translations['color_manager.title']}

") + self.title_label.setText(header(Translations["color_manager.title"], 3)) self.scroll_layout = QVBoxLayout() self.scroll_layout.setAlignment(Qt.AlignmentFlag.AlignTop) @@ -189,7 +190,7 @@ class TagColorManager(QWidget): self.create_namespace_modal.show() - def delete_namespace_dialog(self, prompt: str, callback: Callable) -> None: + def delete_namespace_dialog(self, prompt: str, callback: Callable[..., Any]) -> None: # pyright: ignore[reportExplicitAny] message_box = QMessageBox() message_box.setText(prompt) message_box.setWindowTitle(Translations["color.namespace.delete.title"]) @@ -207,13 +208,13 @@ class TagColorManager(QWidget): callback() @override - def showEvent(self, event: QtGui.QShowEvent) -> None: # noqa N802 + def showEvent(self, event: QtGui.QShowEvent) -> None: if not self.is_initialized: self.setup_color_groups() return super().showEvent(event) @override - def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: # noqa N802 + def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: if event.key() == QtCore.Qt.Key.Key_Escape: # noqa SIM114 self.done_button.click() elif event.key() == Qt.Key.Key_Return or event.key() == Qt.Key.Key_Enter: diff --git a/src/tagstudio/qt/mixed/tag_color_preview.py b/src/tagstudio/qt/mixed/tag_color_preview.py index c73571d4..965a823a 100644 --- a/src/tagstudio/qt/mixed/tag_color_preview.py +++ b/src/tagstudio/qt/mixed/tag_color_preview.py @@ -11,9 +11,14 @@ from PySide6.QtWidgets import QPushButton, QVBoxLayout, QWidget from tagstudio.core.library.alchemy.enums import TagColorEnum from tagstudio.core.library.alchemy.models import TagColorGroup -from tagstudio.qt.mixed.tag_widget import get_border_color, get_highlight_color, get_text_color from tagstudio.qt.models.palette import ColorType, get_tag_color from tagstudio.qt.translations import Translations +from tagstudio.qt.views.stylesheets.stylesheets import ( + get_tag_border_color, + get_tag_highlight_color, + get_tag_text_color, + tag_style, +) if typing.TYPE_CHECKING: from tagstudio.core.library.alchemy.library import Library @@ -66,11 +71,11 @@ class TagColorPreview(QWidget): primary_color = self._get_primary_color(color_group) border_color = ( - get_border_color(primary_color) + get_tag_border_color(primary_color) if not (color_group and color_group.secondary and color_group.color_border) else (QColor(color_group.secondary)) ) - highlight_color = get_highlight_color( + highlight_color = get_tag_highlight_color( primary_color if not (color_group and color_group.secondary) else QColor(color_group.secondary) @@ -79,32 +84,10 @@ class TagColorPreview(QWidget): if color_group and color_group.secondary: text_color = QColor(color_group.secondary) else: - text_color = get_text_color(primary_color, highlight_color) + text_color = get_tag_text_color(primary_color, highlight_color) self.button.setStyleSheet( - f"QPushButton{{" - f"background: rgba{primary_color.toTuple()};" - f"color: rgba{text_color.toTuple()};" - f"font-weight: 600;" - f"border-color: rgba{border_color.toTuple()};" - f"border-radius: 6px;" - f"border-style:solid;" - f"border-width: 2px;" - f"padding-right: 8px;" - f"padding-left: 8px;" - f"font-size: 14px" - f"}}" - f"QPushButton::hover{{" - f"border-color: rgba{highlight_color.toTuple()};" - f"}}" - f"QPushButton::focus{{" - f"padding-right: 0px;" - f"padding-left: 0px;" - f"outline-style: solid;" - f"outline-width: 1px;" - f"outline-radius: 4px;" - f"outline-color: rgba{text_color.toTuple()};" - f"}}" + tag_style(primary_color, text_color, border_color, highlight_color) ) # Add back the padding if the hint is generated while the button has focus (no padding) self.button.setMinimumWidth( diff --git a/src/tagstudio/qt/mixed/tag_color_selection.py b/src/tagstudio/qt/mixed/tag_color_selection.py index a58d3c06..35bc2c2e 100644 --- a/src/tagstudio/qt/mixed/tag_color_selection.py +++ b/src/tagstudio/qt/mixed/tag_color_selection.py @@ -20,11 +20,16 @@ from PySide6.QtWidgets import ( from tagstudio.core.library.alchemy.enums import TagColorEnum from tagstudio.core.library.alchemy.library import Library from tagstudio.core.library.alchemy.models import TagColorGroup -from tagstudio.qt.mixed.tag_widget import get_border_color, get_highlight_color, get_text_color +from tagstudio.qt.mixed.tag_widget import ( + get_tag_border_color, + get_tag_highlight_color, + get_tag_text_color, +) from tagstudio.qt.models.palette import ColorType, get_tag_color from tagstudio.qt.translations import Translations from tagstudio.qt.views.layouts.flow_layout import FlowLayout from tagstudio.qt.views.panel_modal import PanelWidget +from tagstudio.qt.views.stylesheets.stylesheets import color_swatch_style, header logger = structlog.get_logger(__name__) @@ -67,9 +72,7 @@ class TagColorSelection(PanelWidget): self.scroll_layout.addSpacerItem(QSpacerItem(1, 6)) for group, colors in tag_color_groups.items(): display_name: str = self.lib.get_namespace_name(group) - self.scroll_layout.addWidget( - QLabel(f"

{display_name if display_name else group}

") - ) + self.scroll_layout.addWidget(QLabel(header(display_name if display_name else group, 4))) color_box_widget = QWidget() color_group_layout = FlowLayout() color_group_layout.setSpacing(4) @@ -79,54 +82,31 @@ class TagColorSelection(PanelWidget): for color in colors: primary_color = self._get_primary_color(color) border_color = ( - get_border_color(primary_color) + get_tag_border_color(primary_color) if not (color and color.secondary and color.color_border) else (QColor(color.secondary)) ) - highlight_color = get_highlight_color( + highlight_color = get_tag_highlight_color( primary_color if not (color and color.secondary) else QColor(color.secondary) ) text_color: QColor if color and color.secondary: text_color = QColor(color.secondary) else: - text_color = get_text_color(primary_color, highlight_color) + text_color = get_tag_text_color(primary_color, highlight_color) radio_button = QRadioButton() radio_button.setObjectName(f"{color.namespace}.{color.slug}") radio_button.setToolTip(color.name) radio_button.setFixedSize(24, 24) - bottom_color: str = ( - f"border-bottom-color: rgba{text_color.toTuple()};" if color.secondary else "" - ) radio_button.setStyleSheet( - f"QRadioButton{{" - f"background: rgba{primary_color.toTuple()};" - f"color: rgba{text_color.toTuple()};" - f"border-color: rgba{border_color.toTuple()};" - f"{bottom_color}" - f"border-radius: 3px;" - f"border-style:solid;" - f"border-width: 2px;" - f"}}" - f"QRadioButton::indicator{{" - f"width: 12px;" - f"height: 12px;" - f"border-radius: 1px;" - f"margin: 4px;" - f"}}" - f"QRadioButton::indicator:checked{{" - f"background: rgba{text_color.toTuple()};" - f"}}" - f"QRadioButton::hover{{" - f"border-color: rgba{highlight_color.toTuple()};" - f"}}" - f"QRadioButton::focus{{" - f"outline-style: solid;" - f"outline-width: 2px;" - f"outline-radius: 3px;" - f"outline-color: rgba{highlight_color.toTuple()};" - f"}}" + color_swatch_style( + primary_color, + text_color, + border_color, + highlight_color, + text_color if color.secondary else None, + ) ) radio_button.clicked.connect(lambda checked=False, x=color: self.select_color(x)) color_group_layout.addWidget(radio_button) @@ -136,7 +116,7 @@ class TagColorSelection(PanelWidget): def add_no_color_widget(self): no_color_str: str = Translations["color.title.no_color"] - self.scroll_layout.addWidget(QLabel(f"

{no_color_str}

")) + self.scroll_layout.addWidget(QLabel(header(no_color_str, 4))) color_box_widget = QWidget() color_group_layout = FlowLayout() color_group_layout.setSpacing(4) @@ -145,45 +125,20 @@ class TagColorSelection(PanelWidget): color_box_widget.setLayout(color_group_layout) color = None primary_color = self._get_primary_color(color) - border_color = get_border_color(primary_color) - highlight_color = get_highlight_color(primary_color) + border_color = get_tag_border_color(primary_color) + highlight_color = get_tag_highlight_color(primary_color) text_color: QColor if color and color.secondary and color.color_border: text_color = QColor(color.secondary) else: - text_color = get_text_color(primary_color, highlight_color) + text_color = get_tag_text_color(primary_color, highlight_color) radio_button = QRadioButton() - radio_button.setObjectName("None") # NOTE: Internal use, no translation needed. + radio_button.setObjectName("None") radio_button.setToolTip(no_color_str) radio_button.setFixedSize(24, 24) radio_button.setStyleSheet( - f"QRadioButton{{" - f"background: rgba{primary_color.toTuple()};" - f"color: rgba{text_color.toTuple()};" - f"border-color: rgba{border_color.toTuple()};" - f"border-radius: 3px;" - f"border-style:solid;" - f"border-width: 2px;" - f"}}" - f"QRadioButton::indicator{{" - f"width: 12px;" - f"height: 12px;" - f"border-radius: 1px;" - f"margin: 4px;" - f"}}" - f"QRadioButton::indicator:checked{{" - f"background: rgba{text_color.toTuple()};" - f"}}" - f"QRadioButton::hover{{" - f"border-color: rgba{highlight_color.toTuple()};" - f"}}" - f"QRadioButton::focus{{" - f"outline-style: solid;" - f"outline-width: 2px;" - f"outline-radius: 3px;" - f"outline-color: rgba{highlight_color.toTuple()};" - f"}}" + color_swatch_style(primary_color, text_color, border_color, highlight_color) ) radio_button.clicked.connect(lambda checked=False, x=color: self.select_color(x)) color_group_layout.addWidget(radio_button) diff --git a/src/tagstudio/qt/mixed/tag_widget.py b/src/tagstudio/qt/mixed/tag_widget.py index 03a0db0f..c17cf196 100644 --- a/src/tagstudio/qt/mixed/tag_widget.py +++ b/src/tagstudio/qt/mixed/tag_widget.py @@ -15,6 +15,14 @@ from tagstudio.core.library.alchemy.models import Tag from tagstudio.qt.helpers.escape_text import escape_text from tagstudio.qt.models.palette import ColorType, get_tag_color from tagstudio.qt.translations import Translations +from tagstudio.qt.views.stylesheets.stylesheets import ( + get_tag_border_color, + get_tag_highlight_color, + get_tag_primary_color, + get_tag_text_color, + tag_remove_button_style, + tag_style, +) logger = structlog.get_logger(__name__) @@ -188,13 +196,13 @@ class TagWidget(QWidget): if not tag: return - primary_color = get_primary_color(tag) + primary_color = get_tag_primary_color(tag) border_color = ( - get_border_color(primary_color) + get_tag_border_color(primary_color) if not (tag.color and tag.color.secondary and tag.color.color_border) else (QColor(tag.color.secondary)) ) - highlight_color = get_highlight_color( + highlight_color = get_tag_highlight_color( primary_color if not (tag.color and tag.color.secondary) else QColor(tag.color.secondary) @@ -203,65 +211,14 @@ class TagWidget(QWidget): if tag.color and tag.color.secondary: text_color = QColor(tag.color.secondary) else: - text_color = get_text_color(primary_color, highlight_color) + text_color = get_tag_text_color(primary_color, highlight_color) self.bg_button.setStyleSheet( - f"QPushButton{{" - f"background: rgba{primary_color.toTuple()};" - f"color: rgba{text_color.toTuple()};" - f"font-weight: 600;" - f"border-color: rgba{border_color.toTuple()};" - f"border-radius: 6px;" - f"border-style:solid;" - f"border-width: 2px;" - f"padding-right: 4px;" - f"padding-left: 4px;" - f"font-size: 13px" - f"}}" - f"QPushButton::hover{{" - f"border-color: rgba{highlight_color.toTuple()};" - f"}}" - f"QPushButton::pressed{{" - f"background: rgba{highlight_color.toTuple()};" - f"color: rgba{primary_color.toTuple()};" - f"border-color: rgba{primary_color.toTuple()};" - f"}}" - f"QPushButton::focus{{" - f"padding-right: 0px;" - f"padding-left: 0px;" - f"outline-style: solid;" - f"outline-width: 1px;" - f"outline-radius: 4px;" - f"outline-color: rgba{text_color.toTuple()};" - f"}}" + tag_style(primary_color, text_color, border_color, highlight_color) ) self._delete_button.setStyleSheet( - f"QPushButton{{" - f"color: rgba{primary_color.toTuple()};" - f"background: rgba{text_color.toTuple()};" - f"font-weight: 800;" - f"border-radius: 5px;" - f"border-width: 4;" - f"border-color: rgba(0,0,0,0);" - f"padding-bottom: 4px;" - f"font-size: 14px" - f"}}" - f"QPushButton::hover{{" - f"background: rgba{primary_color.toTuple()};" - f"color: rgba{text_color.toTuple()};" - f"border-color: rgba{highlight_color.toTuple()};" - f"border-width: 2;" - f"border-radius: 6px;" - f"}}" - f"QPushButton::pressed{{" - f"background: rgba{border_color.toTuple()};" - f"color: rgba{highlight_color.toTuple()};" - f"}}" - f"QPushButton::focus{{" - f"background: rgba{border_color.toTuple()};" - f"outline:none;" - f"}}" + tag_remove_button_style(primary_color, text_color, border_color, highlight_color) ) if self.lib: @@ -285,42 +242,3 @@ class TagWidget(QWidget): self._delete_button.setHidden(True) self.update() return super().leaveEvent(event) - - -def get_primary_color(tag: Tag) -> QColor: - primary_color = QColor( - get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT) - if not tag.color - else tag.color.primary - ) - - return primary_color - - -def get_border_color(primary_color: QColor) -> QColor: - border_color: QColor = QColor(primary_color) - border_color.setRed(min(border_color.red() + 20, 255)) - border_color.setGreen(min(border_color.green() + 20, 255)) - border_color.setBlue(min(border_color.blue() + 20, 255)) - - return border_color - - -def get_highlight_color(primary_color: QColor) -> QColor: - highlight_color: QColor = QColor(primary_color) - highlight_color = highlight_color.toHsl() - highlight_color.setHsl(highlight_color.hue(), min(highlight_color.saturation(), 200), 225, 255) - highlight_color = highlight_color.toRgb() - - return highlight_color - - -def get_text_color(primary_color: QColor, highlight_color: QColor) -> QColor: - # logger.info("[TagWidget] Evaluating tag text color", lightness=primary_color.lightness()) - if primary_color.lightness() > 120: - text_color = QColor(primary_color) - text_color = text_color.toHsl() - text_color.setHsl(text_color.hue(), text_color.saturation(), 50, 255) - return text_color.toRgb() - else: - return highlight_color diff --git a/src/tagstudio/qt/ts_qt.py b/src/tagstudio/qt/ts_qt.py index cce250da..ad9aaadc 100644 --- a/src/tagstudio/qt/ts_qt.py +++ b/src/tagstudio/qt/ts_qt.py @@ -99,6 +99,7 @@ from tagstudio.qt.views.field_template_search_panel_view import FieldTemplateSea from tagstudio.qt.views.main_window import MainWindow from tagstudio.qt.views.panel_modal import PanelModal from tagstudio.qt.views.splash import SplashScreen +from tagstudio.qt.views.stylesheets.stylesheets import header from tagstudio.qt.views.tag_search_panel_view import TagSearchPanelView BADGE_TAGS = { @@ -1014,9 +1015,8 @@ class QtDriver(DriverMixin, QObject): perm_warning_msg = Translations.format( "trash.dialog.permanent_delete_warning", trash_term=trash_term() ) - perm_warning: str = ( - f"

" - f"{perm_warning_msg}

" + perm_warning: str = header( + perm_warning_msg, 4, get_ui_color(ColorType.PRIMARY, UiColor.RED) ) msg = QMessageBox() @@ -1033,8 +1033,8 @@ class QtDriver(DriverMixin, QObject): "trash.dialog.move.confirmation.singular", trash_term=trash_term() ) msg.setText( - f"

{msg_text}

" - f"

{Translations['trash.dialog.disambiguation_warning.singular']}

" + f"{header(msg_text, 3)}" + f"{header(Translations['trash.dialog.disambiguation_warning.singular'], 4)}" f"{filename if filename else ''}" f"{perm_warning}
" ) @@ -1045,8 +1045,8 @@ class QtDriver(DriverMixin, QObject): trash_term=trash_term(), ) msg.setText( - f"

{msg_text}

" - f"

{Translations['trash.dialog.disambiguation_warning.plural']}

" + f"{header(msg_text, 3)}" + f"{header(Translations['trash.dialog.disambiguation_warning.plural'], 4)}" f"{perm_warning}
" ) @@ -1069,9 +1069,7 @@ class QtDriver(DriverMixin, QObject): pw.update_label(Translations["library.refresh.scanning_preparing"]) pw.show() - iterator = FunctionIterator( - lambda lib=unwrap(self.lib.library_dir): tracker.refresh_dir(lib) # noqa: B008 - ) + iterator = FunctionIterator(lambda lib=self.lib.library_dir: tracker.refresh_dir(lib)) iterator.value.connect( lambda x: ( pw.update_progress(x + 1), diff --git a/src/tagstudio/qt/views/edit_text_view.py b/src/tagstudio/qt/views/edit_text_view.py index 42727240..1ec7ed33 100644 --- a/src/tagstudio/qt/views/edit_text_view.py +++ b/src/tagstudio/qt/views/edit_text_view.py @@ -16,7 +16,7 @@ from PySide6.QtWidgets import ( from tagstudio.qt.controllers.clickable_label import ClickableLabel from tagstudio.qt.translations import Translations from tagstudio.qt.views.panel_modal import PanelWidget -from tagstudio.qt.views.stylesheets.stylesheets import checkbox_style +from tagstudio.qt.views.stylesheets.stylesheets import checkbox_style, title_line_edit_style class EditTextView(PanelWidget): @@ -27,7 +27,7 @@ class EditTextView(PanelWidget): self.root_layout.setContentsMargins(6, 0, 6, 0) self.name_field = QLineEdit() - self.name_field.setStyleSheet("font-weight:bold;font-size:14px;padding-top:6px") + self.name_field.setStyleSheet(title_line_edit_style()) self.text_box = QPlainTextEdit() self.text_line = QLineEdit() diff --git a/src/tagstudio/qt/views/field_template_widget_view.py b/src/tagstudio/qt/views/field_template_widget_view.py index 46c2285b..78efdb93 100644 --- a/src/tagstudio/qt/views/field_template_widget_view.py +++ b/src/tagstudio/qt/views/field_template_widget_view.py @@ -5,41 +5,19 @@ from PySide6.QtCore import Signal from PySide6.QtGui import QColor from PySide6.QtWidgets import QHBoxLayout, QPushButton, QVBoxLayout, QWidget -from tagstudio.core.enums import Theme from tagstudio.core.library.alchemy.enums import TagColorEnum -from tagstudio.qt.mixed.tag_widget import get_border_color, get_highlight_color, get_text_color -from tagstudio.qt.models.palette import ColorType, UiColor, get_tag_color, get_ui_color +from tagstudio.qt.models.palette import ColorType, get_tag_color +from tagstudio.qt.views.stylesheets.stylesheets import ( + get_tag_border_color, + get_tag_highlight_color, + get_tag_text_color, + list_button_style, +) primary_color: QColor = QColor(get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT)) -border_color: QColor = get_border_color(primary_color) -highlight_color: QColor = get_highlight_color(primary_color) -text_color: QColor = get_text_color(primary_color, highlight_color) - -FIELD_TEMPLATE_BUTTON_STYLESHEET = f""" - QPushButton{{ - background-color: {Theme.COLOR_BG.value}; - font-weight: 600; - border-radius: 6px; - padding-right: 4px; - padding-left: 4px; - font-size: 13px; - text-align: center; - }} - - QPushButton::hover{{ - background-color: {Theme.COLOR_HOVER.value}; - border-color: {get_ui_color(ColorType.BORDER, UiColor.THEME_DARK)}; - border-style: solid; - border-width: 2px; - }} - - QPushButton::pressed{{ - background-color: {Theme.COLOR_PRESSED.value}; - border-color: {get_ui_color(ColorType.LIGHT_ACCENT, UiColor.THEME_DARK)}; - border-style: solid; - border-width: 2px; - }} -""" +border_color: QColor = get_tag_border_color(primary_color) +highlight_color: QColor = get_tag_highlight_color(primary_color) +text_color: QColor = get_tag_text_color(primary_color, highlight_color) class FieldTemplateWidgetView(QWidget): @@ -63,7 +41,7 @@ class FieldTemplateWidgetView(QWidget): self._bg_button.setMinimumSize(44, 22) self._bg_button.setMinimumHeight(22) self._bg_button.setMaximumHeight(22) - self._bg_button.setStyleSheet(FIELD_TEMPLATE_BUTTON_STYLESHEET) + self._bg_button.setStyleSheet(list_button_style()) self.__inner_layout = QHBoxLayout() self.__inner_layout.setObjectName("inner_layout") diff --git a/src/tagstudio/qt/views/library_info_window_view.py b/src/tagstudio/qt/views/library_info_window_view.py index bfd06c0a..88f47027 100644 --- a/src/tagstudio/qt/views/library_info_window_view.py +++ b/src/tagstudio/qt/views/library_info_window_view.py @@ -24,6 +24,7 @@ from PySide6.QtWidgets import ( from tagstudio.qt.helpers.color_overlay import theme_fg_overlay from tagstudio.qt.platform_strings import open_file_str from tagstudio.qt.translations import Translations +from tagstudio.qt.views.stylesheets.stylesheets import header # Only import for type checking/autocompletion, will not be imported at runtime. if TYPE_CHECKING: @@ -65,7 +66,7 @@ class LibraryInfoWindowView(QWidget): self.stats_layout.setContentsMargins(0, 0, 0, 0) self.stats_layout.setSpacing(12) - self.stats_label = QLabel(f"

{Translations['library_info.stats']}

") + self.stats_label = QLabel(header(Translations["library_info.stats"], 3)) self.stats_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.stats_grid: QWidget = QWidget() @@ -223,7 +224,7 @@ class LibraryInfoWindowView(QWidget): self.cleanup_layout.setContentsMargins(0, 0, 0, 0) self.cleanup_layout.setSpacing(12) - self.cleanup_label = QLabel(f"

{Translations['library_info.cleanup']}

") + self.cleanup_label = QLabel(header(Translations["library_info.cleanup"], 3)) self.cleanup_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.cleanup_grid: QWidget = QWidget() diff --git a/src/tagstudio/qt/views/preview_panel_view.py b/src/tagstudio/qt/views/preview_panel_view.py index 19144a00..266e06fe 100644 --- a/src/tagstudio/qt/views/preview_panel_view.py +++ b/src/tagstudio/qt/views/preview_panel_view.py @@ -16,45 +16,20 @@ from PySide6.QtWidgets import ( QWidget, ) -from tagstudio.core.enums import Theme from tagstudio.core.library.alchemy.library import Library from tagstudio.core.library.alchemy.models import Entry from tagstudio.core.utils.types import unwrap from tagstudio.qt.controllers.preview_thumb_controller import PreviewThumb from tagstudio.qt.mixed.field_containers import FieldContainers from tagstudio.qt.mixed.file_attributes import FileAttributeData, FileAttributes -from tagstudio.qt.models.palette import ColorType, UiColor, get_ui_color from tagstudio.qt.translations import Translations +from tagstudio.qt.views.stylesheets.stylesheets import button_style if typing.TYPE_CHECKING: from tagstudio.qt.ts_qt import QtDriver logger = structlog.get_logger(__name__) -BUTTON_STYLE: str = f""" - QPushButton{{ - background-color: {Theme.COLOR_BG.value}; - border-radius: 6px; - font-weight: 500; - text-align: center; - }} - QPushButton::hover{{ - background-color: {Theme.COLOR_HOVER.value}; - border-color: {get_ui_color(ColorType.BORDER, UiColor.THEME_DARK)}; - border-style: solid; - border-width: 2px; - }} - QPushButton::pressed{{ - background-color: {Theme.COLOR_PRESSED.value}; - border-color: {get_ui_color(ColorType.LIGHT_ACCENT, UiColor.THEME_DARK)}; - border-style: solid; - border-width: 2px; - }} - QPushButton::disabled{{ - background-color: {Theme.COLOR_DISABLED_BG.value}; - }} -""" - class PreviewPanelView(QWidget): lib: Library @@ -94,13 +69,13 @@ class PreviewPanelView(QWidget): self.__add_tag_button.setEnabled(False) self.__add_tag_button.setCursor(Qt.CursorShape.PointingHandCursor) self.__add_tag_button.setMinimumHeight(28) - self.__add_tag_button.setStyleSheet(BUTTON_STYLE) + self.__add_tag_button.setStyleSheet(button_style()) self.__add_field_button = QPushButton(Translations["field.add"]) self.__add_field_button.setEnabled(False) self.__add_field_button.setCursor(Qt.CursorShape.PointingHandCursor) self.__add_field_button.setMinimumHeight(28) - self.__add_field_button.setStyleSheet(BUTTON_STYLE) + self.__add_field_button.setStyleSheet(button_style()) add_buttons_layout.addWidget(self.__add_tag_button) add_buttons_layout.addWidget(self.__add_field_button) diff --git a/src/tagstudio/qt/views/search_panel_view.py b/src/tagstudio/qt/views/search_panel_view.py index f6c54cbe..175828af 100644 --- a/src/tagstudio/qt/views/search_panel_view.py +++ b/src/tagstudio/qt/views/search_panel_view.py @@ -16,46 +16,14 @@ from PySide6.QtWidgets import ( QWidget, ) -from tagstudio.core.library.alchemy.enums import TagColorEnum from tagstudio.core.library.alchemy.library import Library -from tagstudio.qt.models.palette import ColorType, get_tag_color from tagstudio.qt.translations import Translations from tagstudio.qt.views.panel_modal import PanelWidget +from tagstudio.qt.views.stylesheets.stylesheets import list_button_style if TYPE_CHECKING: from tagstudio.qt.controllers.search_panel_controller import SearchPanel -CREATE_BUTTON_STYLESHEET: str = f""" - QPushButton{{ - background: {get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT)}; - color: {get_tag_color(ColorType.TEXT, TagColorEnum.DEFAULT)}; - font-weight: 600; - border-color:{get_tag_color(ColorType.BORDER, TagColorEnum.DEFAULT)}; - border-radius: 6px; - border-style: dashed; - border-width: 2px; - padding-right: 4px; - padding-bottom: 1px; - padding-left: 4px; - font-size: 13px - }} - - QPushButton::hover{{ - border-color: {get_tag_color(ColorType.LIGHT_ACCENT, TagColorEnum.DEFAULT)}; - }} - - QPushButton::pressed{{ - background: {get_tag_color(ColorType.LIGHT_ACCENT, TagColorEnum.DEFAULT)}; - color: {get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT)}; - border-color: {get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT)}; - }} - - QPushButton::focus{{ - border-color: {get_tag_color(ColorType.LIGHT_ACCENT, TagColorEnum.DEFAULT)}; - outline: none; - }} -""" - class SearchPanelView(PanelWidget): def __init__(self, is_chooser: bool) -> None: @@ -120,7 +88,7 @@ class SearchPanelView(PanelWidget): self.create_and_add_button = QPushButton() self.create_and_add_button.setFlat(True) self.create_and_add_button.setMinimumSize(22, 22) - self.create_and_add_button.setStyleSheet(CREATE_BUTTON_STYLESHEET) + self.create_and_add_button.setStyleSheet(list_button_style(border_style="dashed")) @property def scroll_layout(self) -> QVBoxLayout: diff --git a/src/tagstudio/qt/views/stylesheets/stylesheets.py b/src/tagstudio/qt/views/stylesheets/stylesheets.py index af331fc7..542ebd3f 100644 --- a/src/tagstudio/qt/views/stylesheets/stylesheets.py +++ b/src/tagstudio/qt/views/stylesheets/stylesheets.py @@ -1,41 +1,431 @@ # SPDX-FileCopyrightText: (c) TagStudio Contributors # SPDX-License-Identifier: GPL-3.0-only -from PySide6.QtGui import QColor +from PySide6.QtCore import Qt +from PySide6.QtGui import QColor, QGuiApplication + +from tagstudio.core.enums import Theme from tagstudio.core.library.alchemy.enums import TagColorEnum -from tagstudio.qt.mixed.tag_widget import get_border_color, get_highlight_color, get_text_color -from tagstudio.qt.models.palette import ColorType, get_tag_color +from tagstudio.core.library.alchemy.models import Tag +from tagstudio.qt.models.palette import ColorType, UiColor, get_tag_color, get_ui_color + +# TODO: There's plenty of good opportunities here to consolidate similar styles. +# Work should be done to more closely use Qt's theming systems rather than override them. + + +def add_button_style() -> str: + """Style used for tag-like "Add" buttons [+].""" + return f""" + QPushButton{{ + background: {get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT)}; + color: {get_tag_color(ColorType.TEXT, TagColorEnum.DEFAULT)}; + font-weight: 600; + border-color: {get_tag_color(ColorType.BORDER, TagColorEnum.DEFAULT)}; + border-radius: 6px; + border-style: solid; + border-width: 2px; + padding-right: 4px; + padding-bottom: 2px; + padding-left: 4px; + font-size: 15px + }} + QPushButton::hover{{ + border-color: {get_tag_color(ColorType.LIGHT_ACCENT, TagColorEnum.DEFAULT)}; + }} + QPushButton::pressed{{ + background: {get_tag_color(ColorType.LIGHT_ACCENT, TagColorEnum.DEFAULT)}; + color: {get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT)}; + border-color: {get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT)}; + }} + QPushButton::focus{{ + border-color: {get_tag_color(ColorType.LIGHT_ACCENT, TagColorEnum.DEFAULT)}; + outline: none; + }} + """ + + +def button_style() -> str: + """Style used for common QPushButtons.""" + return f""" + QPushButton{{ + background-color: {Theme.COLOR_BG.value}; + border-radius: 6px; + font-weight: 500; + text-align: center; + }} + QPushButton::hover{{ + background-color: {Theme.COLOR_HOVER.value}; + border-color: {get_ui_color(ColorType.BORDER, UiColor.THEME_DARK)}; + border-style: solid; + border-width: 2px; + }} + QPushButton::pressed{{ + background-color: {Theme.COLOR_PRESSED.value}; + border-color: {get_ui_color(ColorType.LIGHT_ACCENT, UiColor.THEME_DARK)}; + border-style: solid; + border-width: 2px; + }} + QPushButton::disabled{{ + background-color: {Theme.COLOR_DISABLED_BG.value}; + }} +""" def checkbox_style() -> str: + """Style used for QCheckBoxes.""" primary_color = QColor(get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT)) - border_color = get_border_color(primary_color) - highlight_color = get_highlight_color(primary_color) - text_color: QColor = get_text_color(primary_color, highlight_color) - return ( - f"QCheckBox{{" - f"background: rgba{primary_color.toTuple()};" - f"color: rgba{text_color.toTuple()};" - f"border-color: rgba{border_color.toTuple()};" - f"border-radius: 6px;" - f"border-style:solid;" - f"border-width: 2px;" - f"}}" - f"QCheckBox::indicator{{" - f"width: 10px;" - f"height: 10px;" - f"border-radius: 2px;" - f"margin: 4px;" - f"}}" - f"QCheckBox::indicator:checked{{" - f"background: rgba{text_color.toTuple()};" - f"}}" - f"QCheckBox::hover{{" - f"border-color: rgba{highlight_color.toTuple()};" - f"}}" - f"QCheckBox::focus{{" - f"border-color: rgba{highlight_color.toTuple()};" - f"outline:none;" - f"}}" + border_color = get_tag_border_color(primary_color) + highlight_color = get_tag_highlight_color(primary_color) + text_color: QColor = get_tag_text_color(primary_color, highlight_color) + return f""" + QCheckBox{{ + background: rgba{primary_color.toTuple()}; + color: rgba{text_color.toTuple()}; + border-color: rgba{border_color.toTuple()}; + border-radius: 6px; + border-style: solid; + border-width: 2px; + }} + QCheckBox::indicator{{ + width: 10px; + height: 10px; + border-radius: 2px; + margin: 4px; + }} + QCheckBox::indicator:checked{{ + background: rgba{text_color.toTuple()}; + }} + QCheckBox::hover{{ + border-color: rgba{highlight_color.toTuple()}; + }} + QCheckBox::focus{{ + border-color: rgba{highlight_color.toTuple()}; + outline: none; + }} + """ + + +def colored_radio_button_style( + primary_color: QColor, + text_color: QColor, + border_color: QColor, + highlight_color: QColor, +) -> str: + return f""" + QRadioButton{{ + background: rgba{primary_color.toTuple()}; + color: rgba{text_color.toTuple()}; + border-color: rgba{border_color.toTuple()}; + border-radius: 6px; + border-style: solid; + border-width: 2px; + }} + QRadioButton::indicator{{ + width: 10px; + height: 10px; + border-radius: 2px; + margin: 4px; + }} + QRadioButton::indicator:checked{{ + background: rgba{text_color.toTuple()}; + }} + QRadioButton::hover{{ + border-color: rgba{highlight_color.toTuple()}; + }} + QRadioButton::pressed{{ + background: rgba{border_color.toTuple()}; + color: rgba{primary_color.toTuple()}; + border-color: rgba{primary_color.toTuple()}; + }} + QRadioButton::focus{{ + border-color: rgba{highlight_color.toTuple()}; + outline: none; + }} + """ + + +def color_swatch_style( + primary_color: QColor, + text_color: QColor, + border_color: QColor, + highlight_color: QColor, + bottom_color: QColor | None = None, +) -> str: + """A style used for color swatches (aka special QRadioButtons).""" + bottom_color_str: str = ( + f"border-bottom-color: rgba{bottom_color.toTuple()};" if bottom_color else "" ) + + return f""" + QRadioButton{{ + background: rgba{primary_color.toTuple()}; + color: rgba{text_color.toTuple()}; + border-color: rgba{border_color.toTuple()}; + {bottom_color_str} + border-radius: 3px; + border-style: solid; + border-width: 2px; + }} + QRadioButton::indicator{{ + width: 12px; + height: 12px; + border-radius: 1px; + margin: 4px; + }} + QRadioButton::indicator:checked{{ + background: rgba{text_color.toTuple()}; + }} + QRadioButton::hover{{ + border-color: rgba{highlight_color.toTuple()}; + }} + QRadioButton::focus{{ + outline-style: solid; + outline-width: 2px; + outline-radius: 3px; + outline-color: rgba{highlight_color.toTuple()}; + }} + """ + + +def container_style() -> str: + """Style used for field containers.""" + return f""" + QWidget#fieldContainer{{ + border-radius: 4px; + }} + QWidget#fieldContainer::hover{{ + background-color: {Theme.COLOR_HOVER.value}; + }} + QWidget#fieldContainer::pressed{{ + background-color: {Theme.COLOR_PRESSED.value}; + }} + """ + + +def form_content_style() -> str: + return f""" + background-color: { + Theme.COLOR_BG.value + if QGuiApplication.styleHints().colorScheme() is Qt.ColorScheme.Dark + else Theme.COLOR_BG_LIGHT.value + }; + border-radius: 3px; + font-weight: 500; + padding: 1px; + """ + + +def line_edit_style() -> str: + """Style used for QLineEdits.""" + return f""" + border: 1px solid {get_ui_color(ColorType.PRIMARY, UiColor.RED)}; + border-radius: 2px + """ + + +def list_button_style( + color: QColor | None = None, + border_style: str = "solid", +) -> str: + """Style used for special QPushButtons found in lists.""" + if color is None: + color = QColor(get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT)) + + highlight_color = get_tag_highlight_color(color) + text_color = get_tag_text_color(color, highlight_color) + border_color = get_tag_border_color(color) + + return f""" + QPushButton{{ + background: rgba{color.toTuple()}; + color: rgba{text_color.toTuple()}; + font-weight: 600; + border-color: rgba{border_color.toTuple()}; + border-radius: 6px; + border-style: {border_style}; + border-width: 2px; + padding-right: 4px; + padding-bottom: 1px; + padding-left: 4px; + font-size: 13px + }} + QPushButton::hover{{ + border-color: rgba{highlight_color.toTuple()}; + }} + QPushButton::pressed{{ + background: rgba{highlight_color.toTuple()}; + color: rgba{color.toTuple()}; + border-color: rgba{color.toTuple()}; + }} + QPushButton::focus{{ + padding-right: 0px; + padding-left: 0px; + outline-style: solid; + outline-width: 1px; + outline-radius: 4px; + outline-color: rgba{text_color.toTuple()}; + }} + """ + + +def properties_style() -> str: + """Style used for small labels such as file properties.""" + label_bg_color = ( + Theme.COLOR_BG_DARK.value + if QGuiApplication.styleHints().colorScheme() is Qt.ColorScheme.Dark + else Theme.COLOR_DARK_LABEL.value + ) + + return f""" + background-color: {label_bg_color}; + color: #FFFFFF; + font-family: Oxanium; + font-weight: bold; + font-size: 12px; + border-radius: 3px; + padding-top: 4px; + padding-right: 1px; + padding-bottom: 1px; + padding-left: 1px; + """ + + +def tag_style( + primary_color: QColor, + text_color: QColor, + border_color: QColor, + highlight_color: QColor, + border_style: str = "solid", +) -> str: + """Style used for TagWidgets.""" + return f""" + QPushButton{{ + background: rgba{primary_color.toTuple()}; + color: rgba{text_color.toTuple()}; + font-weight: 600; + border-color: rgba{border_color.toTuple()}; + border-radius: 6px; + border-style: {border_style}; + border-width: 2px; + padding-right: 4px; + padding-left: 4px; + font-size: 13px + }} + QPushButton::hover{{ + border-color: rgba{highlight_color.toTuple()}; + }} + QPushButton::pressed{{ + background: rgba{highlight_color.toTuple()}; + color: rgba{primary_color.toTuple()}; + border-color: rgba{primary_color.toTuple()}; + }} + QPushButton::focus{{ + padding-right: 0px; + padding-left: 0px; + outline-style: solid; + outline-width: 1px; + outline-radius: 4px; + outline-color: rgba{text_color.toTuple()}; + }} + """ + + +def tag_remove_button_style( + primary_color: QColor, text_color: QColor, border_color: QColor, highlight_color: QColor +) -> str: + """Style used for "Remove" buttons on TagWidgets [-].""" + return f""" + QPushButton{{ + color: rgba{primary_color.toTuple()}; + background: rgba{text_color.toTuple()}; + font-weight: 800; + border-radius: 5px; + border-width: 4; + border-color: rgba(0,0,0,0); + padding-bottom: 4px; + font-size: 14px + }} + QPushButton::hover{{ + background: rgba{primary_color.toTuple()}; + color: rgba{text_color.toTuple()}; + border-color: rgba{highlight_color.toTuple()}; + border-width: 2; + border-radius: 6px; + }} + QPushButton::pressed{{ + background: rgba{border_color.toTuple()}; + color: rgba{highlight_color.toTuple()}; + }} + QPushButton::focus{{ + background: rgba{border_color.toTuple()}; + outline: none; + }} + """ + + +def title_line_edit_style() -> str: + """Used to mimic an H3-like header style inside a QLineEdit.""" + return """ + font-weight: bold; + font-size: 16px; + """ + + +def header(string: str, level: int, color: str | None = None) -> str: + """Wrap a string in HTML header tags. + + Args: + string (str): The string to format. + level (int): A value between 1 and 6 denoting the header level. + For example, "1" will create

tags, "6" will create

tags, etc. + color: Optional color string to pass as an inline HTML style. + """ + if level < 1: + level = 1 + elif level > 6: + level = 6 + + style_tag: str = "" + if color is not None: + style_tag = f" style='color: {color}'" + + return f"{string}" + + +def get_tag_primary_color(tag: Tag) -> QColor: + primary_color = QColor( + get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT) + if not tag.color + else tag.color.primary + ) + + return primary_color + + +def get_tag_border_color(primary_color: QColor) -> QColor: + border_color: QColor = QColor(primary_color) + border_color.setRed(min(border_color.red() + 20, 255)) + border_color.setGreen(min(border_color.green() + 20, 255)) + border_color.setBlue(min(border_color.blue() + 20, 255)) + + return border_color + + +def get_tag_highlight_color(primary_color: QColor) -> QColor: + highlight_color: QColor = QColor(primary_color) + highlight_color = highlight_color.toHsl() + highlight_color.setHsl(highlight_color.hue(), min(highlight_color.saturation(), 200), 225, 255) + highlight_color = highlight_color.toRgb() + + return highlight_color + + +def get_tag_text_color(primary_color: QColor, highlight_color: QColor) -> QColor: + if primary_color.lightness() > 120: + text_color = QColor(primary_color) + text_color = text_color.toHsl() + text_color.setHsl(text_color.hue(), text_color.saturation(), 50, 255) + return text_color.toRgb() + else: + return highlight_color diff --git a/src/tagstudio/resources/translations/en.json b/src/tagstudio/resources/translations/en.json index e0cd5dbb..c04a4f62 100644 --- a/src/tagstudio/resources/translations/en.json +++ b/src/tagstudio/resources/translations/en.json @@ -5,6 +5,8 @@ "about.license": "License", "about.module.found": "Found", "about.title": "About TagStudio", + "about.version": "Version", + "about.version.latest": "{built_version} (Latest Release: {latest_version})", "about.website": "Website", "app.git": "Git Commit", "app.pre_release": "Pre-Release", @@ -352,6 +354,7 @@ "tag.parent_tags": "Parent Tags", "tag.parent_tags.add": "Add Parent Tag(s)", "tag.parent_tags.description": "This tag can be treated as a substitute for any of these Parent Tags in searches.", + "tag.properties": "Properties", "tag.remove": "Remove Tag", "tag.search_for_tag": "Search for Tag", "tag.shorthand": "Shorthand",