mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-02-01 07:39:10 +00:00
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:
committed by
GitHub
parent
6a680ad3d1
commit
08761d5f8a
BIN
tagstudio/resources/qt/images/tagstudio_logo_text_mono.png
Normal file
BIN
tagstudio/resources/qt/images/tagstudio_logo_text_mono.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
59
tagstudio/src/qt/helpers/color_overlay.py
Normal file
59
tagstudio/src/qt/helpers/color_overlay.py
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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."""
|
||||
|
||||
18
tagstudio/src/qt/widgets/clickable_label.py
Normal file
18
tagstudio/src/qt/widgets/clickable_label.py
Normal 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()
|
||||
181
tagstudio/src/qt/widgets/landing.py
Normal file
181
tagstudio/src/qt/widgets/landing.py
Normal 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)
|
||||
Reference in New Issue
Block a user