Add Landing Page When No Library Is Opened (#258)

* Add landing page when no library is open

- Add landing page when no library is open
- Add linear_gradient method
- Reformat main_window.py with spaces instead of tabs because apparently it wasn't formatted already?

* Add color_overlay methods, ClickableLabel widget

- Add color_overlay helper methods
- Add clickable_label widget
- Add docstrings to landing.py methods
- Add logo easter egg
- Refactor landing.py content

* Fix redefinition

* Fix macOS shortcut text
This commit is contained in:
Travis Abendshien
2024-06-08 15:18:40 -07:00
committed by GitHub
parent 6a680ad3d1
commit 08761d5f8a
7 changed files with 481 additions and 237 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@@ -0,0 +1,59 @@
# Copyright (C) 2024 Travis Abendshien (CyanVoxel).
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
from PIL import Image
from PySide6.QtCore import Qt
from PySide6.QtGui import QGuiApplication
from src.qt.helpers.gradient import linear_gradient
# TODO: Consolidate the built-in QT theme values with the values
# here, in enums.py, and in palette.py.
_THEME_DARK_FG: str = "#FFFFFF55"
_THEME_LIGHT_FG: str = "#000000DD"
def theme_fg_overlay(image: Image.Image) -> Image.Image:
"""
Overlay the foreground theme color onto an image.
Args:
image (Image): The PIL Image object to apply an overlay to.
"""
overlay_color = (
_THEME_DARK_FG
if QGuiApplication.styleHints().colorScheme() is Qt.ColorScheme.Dark
else _THEME_LIGHT_FG
)
im = Image.new(mode="RGBA", size=image.size, color=overlay_color)
return _apply_overlay(image, im)
def gradient_overlay(image: Image.Image, gradient=list[str]) -> Image.Image:
"""
Overlay a color gradient onto an image.
Args:
image (Image): The PIL Image object to apply an overlay to.
gradient (list[str): A list of string hex color codes for use as
the colors of the gradient.
"""
im: Image.Image = _apply_overlay(image, linear_gradient(image.size, gradient))
return im
def _apply_overlay(image: Image.Image, overlay: Image.Image) -> Image.Image:
"""
Internal method to apply an overlay on top of an image, using
the image's alpha channel as a mask.
Args:
image (Image): The PIL Image object to apply an overlay to.
overlay (Image): The PIL Image object to act as the overlay contents.
"""
im: Image.Image = Image.new(mode="RGBA", size=image.size, color="#00000000")
im.paste(overlay, (0, 0), mask=image)
return im

View File

@@ -5,7 +5,9 @@
from PIL import Image, ImageEnhance, ImageChops
def four_corner_gradient_background(image: Image.Image, adj_size, mask, hl):
def four_corner_gradient_background(
image: Image.Image, adj_size, mask, hl
) -> Image.Image:
if image.size != (adj_size, adj_size):
# Old 1 color method.
# bg_col = image.copy().resize((1, 1)).getpixel((0,0))
@@ -48,3 +50,16 @@ def four_corner_gradient_background(image: Image.Image, adj_size, mask, hl):
hl_soft.putalpha(ImageEnhance.Brightness(hl.getchannel(3)).enhance(0.5))
final.paste(ImageChops.soft_light(final, hl_soft), mask=hl_soft.getchannel(3))
return final
def linear_gradient(
size=tuple[int, int],
colors=list[str],
interpolation: Image.Resampling = Image.Resampling.BICUBIC,
) -> Image.Image:
seed: Image.Image = Image.new(mode="RGBA", size=(len(colors), 1), color="#000000")
for i, color in enumerate(colors):
c_im: Image.Image = Image.new(mode="RGBA", size=(1, 1), color=color)
seed.paste(c_im, (i, 0))
gradient: Image.Image = seed.resize(size, resample=interpolation)
return gradient

View File

@@ -12,256 +12,227 @@
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
from PySide6.QtCore import (QCoreApplication, QMetaObject, QRect,
QSize, Qt)
from PySide6.QtGui import (QFont, QAction)
import logging
import typing
from PySide6.QtCore import (QCoreApplication, QMetaObject, QRect,QSize, Qt)
from PySide6.QtGui import QFont
from PySide6.QtWidgets import (QComboBox, QFrame, QGridLayout,
QHBoxLayout, QVBoxLayout, QLayout, QLineEdit, QMainWindow,
QPushButton, QScrollArea, QSizePolicy,
QStatusBar, QWidget, QSplitter, QCheckBox,
QSpacerItem)
QHBoxLayout, QVBoxLayout, QLayout, QLineEdit, QMainWindow,
QPushButton, QScrollArea, QSizePolicy,
QStatusBar, QWidget, QSplitter, QCheckBox,
QSpacerItem)
from src.qt.pagination import Pagination
from src.qt.widgets.landing import LandingWidget
# Only import for type checking/autocompletion, will not be imported at runtime.
if typing.TYPE_CHECKING:
from src.qt.ts_qt import QtDriver
logging.basicConfig(format="%(message)s", level=logging.INFO)
class Ui_MainWindow(QMainWindow):
def __init__(self, parent=None) -> None:
super().__init__(parent)
self.setupUi(self)
def __init__(self, driver: "QtDriver", parent=None) -> None:
super().__init__(parent)
self.driver: "QtDriver" = driver
self.setupUi(self)
# self.setWindowFlag(Qt.WindowType.NoDropShadowWindowHint, True)
# self.setWindowFlag(Qt.WindowType.WindowTransparentForInput, False)
# # self.setWindowFlag(Qt.WindowType.FramelessWindowHint, True)
# self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
# NOTE: These are old attempts to allow for a translucent/acrylic
# window effect. This may be attempted again in the future.
# self.setWindowFlag(Qt.WindowType.NoDropShadowWindowHint, True)
# self.setWindowFlag(Qt.WindowType.WindowTransparentForInput, False)
# # self.setWindowFlag(Qt.WindowType.FramelessWindowHint, True)
# self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
# self.windowFX = WindowEffect()
# self.windowFX.setAcrylicEffect(self.winId(), isEnableShadow=False)
# self.windowFX = WindowEffect()
# self.windowFX.setAcrylicEffect(self.winId(), isEnableShadow=False)
# # self.setStyleSheet(
# # 'background:#EE000000;'
# # )
# # self.setStyleSheet(
# # 'background:#EE000000;'
# # )
def setupUi(self, MainWindow):
if not MainWindow.objectName():
MainWindow.setObjectName(u"MainWindow")
MainWindow.resize(1300, 720)
self.centralwidget = QWidget(MainWindow)
self.centralwidget.setObjectName(u"centralwidget")
self.gridLayout = QGridLayout(self.centralwidget)
self.gridLayout.setObjectName(u"gridLayout")
self.horizontalLayout = QHBoxLayout()
self.horizontalLayout.setObjectName(u"horizontalLayout")
def setupUi(self, MainWindow):
if not MainWindow.objectName():
MainWindow.setObjectName(u"MainWindow")
MainWindow.resize(1300, 720)
self.centralwidget = QWidget(MainWindow)
self.centralwidget.setObjectName(u"centralwidget")
self.gridLayout = QGridLayout(self.centralwidget)
self.gridLayout.setObjectName(u"gridLayout")
self.horizontalLayout = QHBoxLayout()
self.horizontalLayout.setObjectName(u"horizontalLayout")
# ComboBox goup for search type and thumbnail size
self.horizontalLayout_3 = QHBoxLayout()
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
# ComboBox goup for search type and thumbnail size
self.horizontalLayout_3 = QHBoxLayout()
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
# left side spacer
spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
self.horizontalLayout_3.addItem(spacerItem)
# left side spacer
spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
self.horizontalLayout_3.addItem(spacerItem)
# Search type selector
self.comboBox_2 = QComboBox(self.centralwidget)
self.comboBox_2.setMinimumSize(QSize(165, 0))
self.comboBox_2.setObjectName("comboBox_2")
self.comboBox_2.addItem("")
self.comboBox_2.addItem("")
self.horizontalLayout_3.addWidget(self.comboBox_2)
# Search type selector
self.comboBox_2 = QComboBox(self.centralwidget)
self.comboBox_2.setMinimumSize(QSize(165, 0))
self.comboBox_2.setObjectName("comboBox_2")
self.comboBox_2.addItem("")
self.comboBox_2.addItem("")
self.horizontalLayout_3.addWidget(self.comboBox_2)
# Thumbnail Size placeholder
self.comboBox = QComboBox(self.centralwidget)
self.comboBox.setObjectName(u"comboBox")
sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(
self.comboBox.sizePolicy().hasHeightForWidth())
self.comboBox.setSizePolicy(sizePolicy)
self.comboBox.setMinimumWidth(128)
self.comboBox.setMaximumWidth(128)
self.horizontalLayout_3.addWidget(self.comboBox)
self.gridLayout.addLayout(self.horizontalLayout_3, 5, 0, 1, 1)
self.comboBox = QComboBox(self.centralwidget)
self.comboBox.setObjectName(u"comboBox")
sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(
self.comboBox.sizePolicy().hasHeightForWidth())
self.comboBox.setSizePolicy(sizePolicy)
self.comboBox.setMinimumWidth(128)
self.comboBox.setMaximumWidth(128)
self.horizontalLayout_3.addWidget(self.comboBox)
self.gridLayout.addLayout(self.horizontalLayout_3, 5, 0, 1, 1)
self.splitter = QSplitter()
self.splitter.setObjectName(u"splitter")
self.splitter.setHandleWidth(12)
self.splitter = QSplitter()
self.splitter.setObjectName(u"splitter")
self.splitter.setHandleWidth(12)
self.frame_container = QWidget()
self.frame_layout = QVBoxLayout(self.frame_container)
self.frame_layout.setSpacing(0)
self.frame_container = QWidget()
self.frame_layout = QVBoxLayout(self.frame_container)
self.frame_layout.setSpacing(0)
self.scrollArea = QScrollArea()
self.scrollArea.setObjectName(u"scrollArea")
self.scrollArea.setFocusPolicy(Qt.WheelFocus)
self.scrollArea.setFrameShape(QFrame.NoFrame)
self.scrollArea.setFrameShadow(QFrame.Plain)
self.scrollArea.setWidgetResizable(True)
self.scrollAreaWidgetContents = QWidget()
self.scrollAreaWidgetContents.setObjectName(
u"scrollAreaWidgetContents")
self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 1260, 590))
self.gridLayout_2 = QGridLayout(self.scrollAreaWidgetContents)
self.gridLayout_2.setSpacing(8)
self.gridLayout_2.setObjectName(u"gridLayout_2")
self.gridLayout_2.setContentsMargins(0, 0, 0, 8)
self.scrollArea.setWidget(self.scrollAreaWidgetContents)
self.frame_layout.addWidget(self.scrollArea)
self.scrollArea = QScrollArea()
self.scrollArea.setObjectName(u"scrollArea")
self.scrollArea.setFocusPolicy(Qt.WheelFocus)
self.scrollArea.setFrameShape(QFrame.NoFrame)
self.scrollArea.setFrameShadow(QFrame.Plain)
self.scrollArea.setWidgetResizable(True)
self.scrollAreaWidgetContents = QWidget()
self.scrollAreaWidgetContents.setObjectName(
u"scrollAreaWidgetContents")
self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 1260, 590))
self.gridLayout_2 = QGridLayout(self.scrollAreaWidgetContents)
self.gridLayout_2.setSpacing(8)
self.gridLayout_2.setObjectName(u"gridLayout_2")
self.gridLayout_2.setContentsMargins(0, 0, 0, 8)
self.scrollArea.setWidget(self.scrollAreaWidgetContents)
self.frame_layout.addWidget(self.scrollArea)
self.landing_widget: LandingWidget = LandingWidget(self.driver, self.devicePixelRatio())
self.frame_layout.addWidget(self.landing_widget)
# self.page_bar_controls = QWidget()
# self.page_bar_controls.setStyleSheet('background:blue;')
# self.page_bar_controls.setMinimumHeight(32)
self.pagination = Pagination()
self.frame_layout.addWidget(self.pagination)
self.pagination = Pagination()
self.frame_layout.addWidget(self.pagination)
self.horizontalLayout.addWidget(self.splitter)
self.splitter.addWidget(self.frame_container)
self.splitter.setStretchFactor(0, 1)
# self.frame_layout.addWidget(self.page_bar_controls)
# self.frame_layout.addWidget(self.page_bar_controls)
self.gridLayout.addLayout(self.horizontalLayout, 10, 0, 1, 1)
self.horizontalLayout.addWidget(self.splitter)
self.splitter.addWidget(self.frame_container)
self.splitter.setStretchFactor(0, 1)
self.horizontalLayout_2 = QHBoxLayout()
self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
self.horizontalLayout_2.setSizeConstraint(QLayout.SetMinimumSize)
self.backButton = QPushButton(self.centralwidget)
self.backButton.setObjectName(u"backButton")
self.backButton.setMinimumSize(QSize(0, 32))
self.backButton.setMaximumSize(QSize(32, 16777215))
font = QFont()
font.setPointSize(14)
font.setBold(True)
self.backButton.setFont(font)
self.gridLayout.addLayout(self.horizontalLayout, 10, 0, 1, 1)
self.horizontalLayout_2.addWidget(self.backButton)
self.horizontalLayout_2 = QHBoxLayout()
self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
self.horizontalLayout_2.setSizeConstraint(QLayout.SetMinimumSize)
self.backButton = QPushButton(self.centralwidget)
self.backButton.setObjectName(u"backButton")
self.backButton.setMinimumSize(QSize(0, 32))
self.backButton.setMaximumSize(QSize(32, 16777215))
font = QFont()
font.setPointSize(14)
font.setBold(True)
self.backButton.setFont(font)
self.forwardButton = QPushButton(self.centralwidget)
self.forwardButton.setObjectName(u"forwardButton")
self.forwardButton.setMinimumSize(QSize(0, 32))
self.forwardButton.setMaximumSize(QSize(32, 16777215))
font1 = QFont()
font1.setPointSize(14)
font1.setBold(True)
font1.setKerning(True)
self.forwardButton.setFont(font1)
self.horizontalLayout_2.addWidget(self.backButton)
self.horizontalLayout_2.addWidget(self.forwardButton)
self.forwardButton = QPushButton(self.centralwidget)
self.forwardButton.setObjectName(u"forwardButton")
self.forwardButton.setMinimumSize(QSize(0, 32))
self.forwardButton.setMaximumSize(QSize(32, 16777215))
font1 = QFont()
font1.setPointSize(14)
font1.setBold(True)
font1.setKerning(True)
self.forwardButton.setFont(font1)
self.searchField = QLineEdit(self.centralwidget)
self.searchField.setObjectName(u"searchField")
self.searchField.setMinimumSize(QSize(0, 32))
font2 = QFont()
font2.setPointSize(11)
font2.setBold(False)
self.searchField.setFont(font2)
self.horizontalLayout_2.addWidget(self.forwardButton)
self.horizontalLayout_2.addWidget(self.searchField)
self.searchField = QLineEdit(self.centralwidget)
self.searchField.setObjectName(u"searchField")
self.searchField.setMinimumSize(QSize(0, 32))
font2 = QFont()
font2.setPointSize(11)
font2.setBold(False)
self.searchField.setFont(font2)
self.searchButton = QPushButton(self.centralwidget)
self.searchButton.setObjectName(u"searchButton")
self.searchButton.setMinimumSize(QSize(0, 32))
self.searchButton.setFont(font2)
self.horizontalLayout_2.addWidget(self.searchField)
self.horizontalLayout_2.addWidget(self.searchButton)
self.gridLayout.addLayout(self.horizontalLayout_2, 3, 0, 1, 1)
self.gridLayout_2.setContentsMargins(6, 6, 6, 6)
self.searchButton = QPushButton(self.centralwidget)
self.searchButton.setObjectName(u"searchButton")
self.searchButton.setMinimumSize(QSize(0, 32))
self.searchButton.setFont(font2)
MainWindow.setCentralWidget(self.centralwidget)
self.statusbar = QStatusBar(MainWindow)
self.statusbar.setObjectName(u"statusbar")
sizePolicy1 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum)
sizePolicy1.setHorizontalStretch(0)
sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(
self.statusbar.sizePolicy().hasHeightForWidth())
self.statusbar.setSizePolicy(sizePolicy1)
MainWindow.setStatusBar(self.statusbar)
self.horizontalLayout_2.addWidget(self.searchButton)
self.gridLayout.addLayout(self.horizontalLayout_2, 3, 0, 1, 1)
self.retranslateUi(MainWindow)
# self.comboBox = QComboBox(self.centralwidget)
# self.comboBox.setObjectName(u"comboBox")
# sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
# sizePolicy.setHorizontalStretch(0)
# sizePolicy.setVerticalStretch(0)
# sizePolicy.setHeightForWidth(
# self.comboBox.sizePolicy().hasHeightForWidth())
# self.comboBox.setSizePolicy(sizePolicy)
# self.comboBox.setMinimumWidth(128)
# self.comboBox.setMaximumWidth(128)
QMetaObject.connectSlotsByName(MainWindow)
# setupUi
# self.gridLayout.addWidget(self.comboBox, 4, 0, 1, 1, Qt.AlignRight)
self.gridLayout_2.setContentsMargins(6, 6, 6, 6)
MainWindow.setCentralWidget(self.centralwidget)
# self.menubar = QMenuBar(MainWindow)
# self.menubar.setObjectName(u"menubar")
# self.menubar.setGeometry(QRect(0, 0, 1280, 22))
# MainWindow.setMenuBar(self.menubar)
self.statusbar = QStatusBar(MainWindow)
self.statusbar.setObjectName(u"statusbar")
sizePolicy1 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum)
sizePolicy1.setHorizontalStretch(0)
sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(
self.statusbar.sizePolicy().hasHeightForWidth())
self.statusbar.setSizePolicy(sizePolicy1)
MainWindow.setStatusBar(self.statusbar)
# menu_bar = self.menuBar()
# self.setMenuBar(menu_bar)
# self.gridLayout.addWidget(menu_bar, 4, 0, 1, 1, Qt.AlignRight)
# self.frame_layout.addWidget(menu_bar)
self.retranslateUi(MainWindow)
QMetaObject.connectSlotsByName(MainWindow)
# setupUi
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(QCoreApplication.translate(
"MainWindow", u"MainWindow", None))
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(QCoreApplication.translate(
"MainWindow", u"MainWindow", None))
# Navigation buttons
self.backButton.setText(
QCoreApplication.translate("MainWindow", u"<", None))
self.forwardButton.setText(
QCoreApplication.translate("MainWindow", u">", None))
self.backButton.setText(
QCoreApplication.translate("MainWindow", u"<", None))
self.forwardButton.setText(
QCoreApplication.translate("MainWindow", u">", None))
# Search field
self.searchField.setPlaceholderText(
QCoreApplication.translate("MainWindow", u"Search Entries", None))
self.searchButton.setText(
QCoreApplication.translate("MainWindow", u"Search", None))
self.searchField.setPlaceholderText(
QCoreApplication.translate("MainWindow", u"Search Entries", None))
self.searchButton.setText(
QCoreApplication.translate("MainWindow", u"Search", None))
# Search type selector
self.comboBox_2.setItemText(0, QCoreApplication.translate("MainWindow", "And (Includes All Tags)"))
self.comboBox_2.setItemText(1, QCoreApplication.translate("MainWindow", "Or (Includes Any Tag)"))
self.comboBox.setCurrentText("")
self.comboBox_2.setItemText(0, QCoreApplication.translate("MainWindow", "And (Includes All Tags)"))
self.comboBox_2.setItemText(1, QCoreApplication.translate("MainWindow", "Or (Includes Any Tag)"))
self.comboBox.setCurrentText("")
# Tumbnail size selector
self.comboBox.setPlaceholderText(
QCoreApplication.translate("MainWindow", u"Thumbnail Size", None))
# retranslateUi
# Thumbnail size selector
self.comboBox.setPlaceholderText(
QCoreApplication.translate("MainWindow", u"Thumbnail Size", None))
# retranslateUi
def moveEvent(self, event) -> None:
# time.sleep(0.02) # sleep for 20ms
pass
def moveEvent(self, event) -> None:
# time.sleep(0.02) # sleep for 20ms
pass
def resizeEvent(self, event) -> None:
# time.sleep(0.02) # sleep for 20ms
pass
def resizeEvent(self, event) -> None:
# time.sleep(0.02) # sleep for 20ms
pass
# def _createMenuBar(self, main_window):
# menu_bar = QMenuBar(main_window)
# file_menu = QMenu('&File', main_window)
# edit_menu = QMenu('&Edit', main_window)
# tools_menu = QMenu('&Tools', main_window)
# macros_menu = QMenu('&Macros', main_window)
# help_menu = QMenu('&Help', main_window)
# file_menu.addAction(QAction('&New Library', main_window))
# file_menu.addAction(QAction('&Open Library', main_window))
# file_menu.addAction(QAction('&Save Library', main_window))
# file_menu.addAction(QAction('&Close Library', main_window))
# file_menu.addAction(QAction('&Refresh Directories', main_window))
# file_menu.addAction(QAction('&Add New Files to Library', main_window))
# menu_bar.addMenu(file_menu)
# menu_bar.addMenu(edit_menu)
# menu_bar.addMenu(tools_menu)
# menu_bar.addMenu(macros_menu)
# menu_bar.addMenu(help_menu)
# main_window.setMenuBar(menu_bar)
def toggle_landing_page(self, enabled: bool):
if enabled:
self.scrollArea.setHidden(True)
self.landing_widget.setHidden(False)
self.landing_widget.animate_logo_in()
else:
self.landing_widget.setHidden(True)
self.landing_widget.set_status_label("")
self.scrollArea.setHidden(False)

View File

@@ -21,7 +21,15 @@ from queue import Queue
from typing import Optional
from PIL import Image
from PySide6 import QtCore
from PySide6.QtCore import QObject, QThread, Signal, Qt, QThreadPool, QTimer, QSettings
from PySide6.QtCore import (
QObject,
QThread,
Signal,
Qt,
QThreadPool,
QTimer,
QSettings,
)
from PySide6.QtGui import (
QGuiApplication,
QPixmap,
@@ -51,22 +59,6 @@ from src.core.enums import SettingItems, SearchMode
from src.core.library import ItemType
from src.core.ts_core import TagStudioCore
from src.core.constants import (
PLAINTEXT_TYPES,
TAG_COLORS,
DATE_FIELDS,
TEXT_FIELDS,
BOX_FIELDS,
ALL_FILE_TYPES,
SHORTCUT_TYPES,
PROGRAM_TYPES,
ARCHIVE_TYPES,
PRESENTATION_TYPES,
SPREADSHEET_TYPES,
DOC_TYPES,
AUDIO_TYPES,
VIDEO_TYPES,
IMAGE_TYPES,
LIBRARY_FILENAME,
COLLAGE_FOLDER_NAME,
BACKUP_FOLDER_NAME,
TS_FOLDER_NAME,
@@ -266,7 +258,7 @@ class QtDriver(QObject):
timer.timeout.connect(lambda: None)
# self.main_window = loader.load(home_path)
self.main_window = Ui_MainWindow()
self.main_window = Ui_MainWindow(self)
self.main_window.setWindowTitle(self.base_title)
self.main_window.mousePressEvent = self.mouse_navigation # type: ignore
# self.main_window.setStyleSheet(
@@ -557,6 +549,7 @@ class QtDriver(QObject):
self.shutdown()
def init_library_window(self):
# self._init_landing_page() # Taken care of inside the widget now
self._init_thumb_grid()
# TODO: Put this into its own method that copies the font file(s) into memory
@@ -585,6 +578,13 @@ class QtDriver(QObject):
forward_button: QPushButton = self.main_window.forwardButton
forward_button.clicked.connect(self.nav_forward)
# NOTE: Putting this early will result in a white non-responsive
# window until everything is loaded. Consider adding a splash screen
# or implementing some clever loading tricks.
self.main_window.show()
self.main_window.activateWindow()
self.main_window.toggle_landing_page(True)
self.frame_dict = {}
self.main_window.pagination.index.connect(
lambda i: (
@@ -606,11 +606,6 @@ class QtDriver(QObject):
# self.render_times: list = []
# self.main_window.setWindowFlag(Qt.FramelessWindowHint)
# NOTE: Putting this early will result in a white non-responsive
# window until everything is loaded. Consider adding a splash screen
# or implementing some clever loading tricks.
self.main_window.show()
self.main_window.activateWindow()
# self.main_window.raise_()
self.splash.finish(self.main_window)
self.preview_panel.update_widgets()
@@ -696,6 +691,7 @@ class QtDriver(QObject):
self.selected.clear()
self.preview_panel.update_widgets()
self.filter_items()
self.main_window.toggle_landing_page(True)
end_time = time.time()
self.main_window.statusbar.showMessage(
@@ -1439,15 +1435,18 @@ class QtDriver(QObject):
def open_library(self, path: Path):
"""Opens a TagStudio library."""
open_message: str = f'Opening Library "{str(path)}"...'
self.main_window.landing_widget.set_status_label(open_message)
self.main_window.statusbar.showMessage(open_message, 3)
self.main_window.repaint()
if self.lib.library_dir:
self.save_library()
self.lib.clear_internal_vars()
self.main_window.statusbar.showMessage(f"Opening Library {str(path)}", 3)
return_code = self.lib.open_library(path)
if return_code == 1:
pass
else:
logging.info(
f"{ERROR} No existing TagStudio library found at '{path}'. Creating one."
@@ -1465,6 +1464,7 @@ class QtDriver(QObject):
self.selected.clear()
self.preview_panel.update_widgets()
self.filter_items()
self.main_window.toggle_landing_page(False)
def create_collage(self) -> None:
"""Generates and saves an image collage based on Library Entries."""

View File

@@ -0,0 +1,18 @@
# Copyright (C) 2024 Travis Abendshien (CyanVoxel).
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
from PySide6.QtCore import Signal
from PySide6.QtWidgets import QLabel
class ClickableLabel(QLabel):
"""A clickable Label widget."""
clicked = Signal()
def __init__(self):
super().__init__()
def mousePressEvent(self, event):
self.clicked.emit()

View File

@@ -0,0 +1,181 @@
# Copyright (C) 2024 Travis Abendshien (CyanVoxel).
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
import logging
import sys
import typing
from pathlib import Path
from PIL import Image, ImageQt
from PySide6.QtCore import Qt, QPropertyAnimation, QPoint, QEasingCurve
from PySide6.QtGui import QPixmap
from PySide6.QtWidgets import QWidget, QLabel, QVBoxLayout, QPushButton
from src.qt.widgets.clickable_label import ClickableLabel
from src.qt.helpers.color_overlay import gradient_overlay, theme_fg_overlay
# Only import for type checking/autocompletion, will not be imported at runtime.
if typing.TYPE_CHECKING:
from src.qt.ts_qt import QtDriver
logging.basicConfig(format="%(message)s", level=logging.INFO)
class LandingWidget(QWidget):
def __init__(self, driver: "QtDriver", pixel_ratio: float):
super().__init__()
self.driver: "QtDriver" = driver
self.logo_label: ClickableLabel = ClickableLabel()
self._pixel_ratio: float = pixel_ratio
self._logo_width: int = int(480 * pixel_ratio)
self._special_click_count: int = 0
# Create layout --------------------------------------------------------
self.landing_layout = QVBoxLayout()
self.landing_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.landing_layout.setSpacing(12)
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[3]
/ "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)
# Initialize landing logo animation ------------------------------------
self.logo_pos_anim = QPropertyAnimation(self.logo_label, b"pos")
self.logo_pos_anim.setEasingCurve(QEasingCurve.Type.OutCubic)
self.logo_pos_anim.setDuration(1000)
self.logo_special_anim = QPropertyAnimation(self.logo_label, b"pos")
self.logo_special_anim.setEasingCurve(QEasingCurve.Type.OutCubic)
self.logo_special_anim.setDuration(500)
# Create "Open/Create Library" button ----------------------------------
open_shortcut_text: str = ""
if sys.platform == "darwin":
open_shortcut_text = "(⌘+O)"
else:
open_shortcut_text = "(Ctrl+O)"
self.open_button: QPushButton = QPushButton()
self.open_button.setMinimumWidth(200)
self.open_button.setText(f"Open/Create Library {open_shortcut_text}")
self.open_button.clicked.connect(self.driver.open_library_from_dialog)
# Create status label --------------------------------------------------
self.status_label = QLabel()
self.status_label.setMinimumWidth(200)
self.status_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.status_label.setText("")
# Initialize landing logo animation ------------------------------------
self.status_pos_anim = QPropertyAnimation(self.status_label, b"pos")
self.status_pos_anim.setEasingCurve(QEasingCurve.Type.OutCubic)
self.status_pos_anim.setDuration(500)
# Add widgets to layout ------------------------------------------------
self.landing_layout.addWidget(self.logo_label)
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: str = "mono"):
"""
Update the color of the TagStudio logo.
Args:
style (str): = The style of the logo. Either "mono" or "gradient".
"""
logo_im: Image.Image = None
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_final: Image.Image = Image.new(
mode="RGBA", size=self.logo_raw.size, color="#00000000"
)
logo_final.paste(logo_im, (0, 0), mask=self.logo_raw)
self.landing_pixmap = QPixmap.fromImage(ImageQt.ImageQt(logo_im))
self.landing_pixmap.setDevicePixelRatio(self._pixel_ratio)
self.landing_pixmap = self.landing_pixmap.scaledToWidth(
self._logo_width, Qt.TransformationMode.SmoothTransformation
)
self.logo_label.setMaximumHeight(
int(self.logo_raw.size[1] * (self.logo_raw.size[0] / self._logo_width))
)
self.logo_label.setMaximumWidth(self._logo_width)
self.logo_label.setPixmap(self.landing_pixmap)
def _update_special_click(self):
"""
Increment the click count for the logo easter egg if it has not
been triggered. If it reaches the click threshold, this triggers it
and prevents it from triggering again.
"""
if self._special_click_count >= 0:
self._special_click_count += 1
if self._special_click_count >= 10:
self.update_logo_color("gradient")
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:
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()
def animate_logo_pop(self):
"""Special pop animation for the TagStudio logo."""
self.logo_special_anim.setStartValue(self.logo_label.pos())
self.logo_special_anim.setKeyValueAt(
0.25, QPoint(self.logo_label.x() - 5, self.logo_label.y())
)
self.logo_special_anim.setKeyValueAt(
0.5, QPoint(self.logo_label.x() + 5, self.logo_label.y() - 10)
)
self.logo_special_anim.setKeyValueAt(
0.75, QPoint(self.logo_label.x() - 5, self.logo_label.y())
)
self.logo_special_anim.setEndValue(self.logo_label.pos())
self.logo_special_anim.start()
# def animate_status(self):
# # if self.status_label.y() > 50:
# logging.info(f"{self.status_label.pos()}")
# self.status_pos_anim.setStartValue(
# QPoint(self.status_label.x(), self.status_label.y() + 50)
# )
# self.status_pos_anim.setEndValue(self.status_label.pos())
# self.status_pos_anim.start()
def set_status_label(self, text=str):
"""
Set the text of the status label.
Args:
text (str): Text of the status to set.
"""
# if text:
# self.animate_status()
self.status_label.setText(text)