mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-01-28 22:01:24 +00:00
feat(ui): add resizable thumbnail options
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 5.5 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.3 KiB |
@@ -66,7 +66,7 @@ class Ui_MainWindow(QMainWindow):
|
||||
self.horizontalLayout = QHBoxLayout()
|
||||
self.horizontalLayout.setObjectName(u"horizontalLayout")
|
||||
|
||||
# ComboBox goup for search type and thumbnail size
|
||||
# ComboBox group for search type and thumbnail size
|
||||
self.horizontalLayout_3 = QHBoxLayout()
|
||||
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
|
||||
|
||||
@@ -83,17 +83,17 @@ class Ui_MainWindow(QMainWindow):
|
||||
self.horizontalLayout_3.addWidget(self.comboBox_2)
|
||||
|
||||
# Thumbnail Size placeholder
|
||||
self.comboBox = QComboBox(self.centralwidget)
|
||||
self.comboBox.setObjectName(u"comboBox")
|
||||
self.thumb_size_combobox = QComboBox(self.centralwidget)
|
||||
self.thumb_size_combobox.setObjectName(u"thumbSizeComboBox")
|
||||
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.thumb_size_combobox.sizePolicy().hasHeightForWidth())
|
||||
self.thumb_size_combobox.setSizePolicy(sizePolicy)
|
||||
self.thumb_size_combobox.setMinimumWidth(128)
|
||||
self.thumb_size_combobox.setMaximumWidth(352)
|
||||
self.horizontalLayout_3.addWidget(self.thumb_size_combobox)
|
||||
self.gridLayout.addLayout(self.horizontalLayout_3, 5, 0, 1, 1)
|
||||
|
||||
self.splitter = QSplitter()
|
||||
@@ -212,10 +212,10 @@ class Ui_MainWindow(QMainWindow):
|
||||
# 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.thumb_size_combobox.setCurrentText("")
|
||||
|
||||
# Thumbnail size selector
|
||||
self.comboBox.setPlaceholderText(
|
||||
self.thumb_size_combobox.setPlaceholderText(
|
||||
QCoreApplication.translate("MainWindow", u"Thumbnail Size", None))
|
||||
# retranslateUi
|
||||
|
||||
|
||||
@@ -557,11 +557,17 @@ class QtDriver(QObject):
|
||||
str(Path(__file__).parents[2] / "resources/qt/fonts/Oxanium-Bold.ttf")
|
||||
)
|
||||
|
||||
self.thumb_sizes: list[tuple[str, int]] = [
|
||||
("Extra Large Thumbnails", 256),
|
||||
("Large Thumbnails", 192),
|
||||
("Medium Thumbnails", 128),
|
||||
("Small Thumbnails", 96),
|
||||
("Mini Thumbnails", 76),
|
||||
]
|
||||
self.thumb_size = 128
|
||||
self.max_results = 500
|
||||
self.item_thumbs: list[ItemThumb] = []
|
||||
self.thumb_renderers: list[ThumbRenderer] = []
|
||||
self.collation_thumb_size = math.ceil(self.thumb_size * 2)
|
||||
|
||||
self.init_library_window()
|
||||
|
||||
@@ -596,23 +602,35 @@ 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
|
||||
# so the resource isn't being used, then store the specific size variations
|
||||
# in a global dict for methods to access for different DPIs.
|
||||
# adj_font_size = math.floor(12 * self.main_window.devicePixelRatio())
|
||||
# self.ext_font = ImageFont.truetype(os.path.normpath(f'{Path(__file__).parents[2]}/resources/qt/fonts/Oxanium-Bold.ttf'), adj_font_size)
|
||||
|
||||
# Search Button
|
||||
search_button: QPushButton = self.main_window.searchButton
|
||||
search_button.clicked.connect(
|
||||
lambda: self.filter_items(self.main_window.searchField.text())
|
||||
)
|
||||
|
||||
# Search Field
|
||||
search_field: QLineEdit = self.main_window.searchField
|
||||
search_field.returnPressed.connect(
|
||||
lambda: self.filter_items(self.main_window.searchField.text())
|
||||
)
|
||||
|
||||
# Thumbnail Size ComboBox
|
||||
thumb_size_combobox: QComboBox = self.main_window.thumb_size_combobox
|
||||
for size in self.thumb_sizes:
|
||||
thumb_size_combobox.addItem(size[0])
|
||||
thumb_size_combobox.setCurrentIndex(2) # Default: Medium
|
||||
thumb_size_combobox.currentIndexChanged.connect(
|
||||
lambda: self.thumb_size_callback(thumb_size_combobox.currentIndex())
|
||||
)
|
||||
self._init_thumb_grid()
|
||||
|
||||
# Search Type ComboBox
|
||||
search_type_selector: QComboBox = self.main_window.comboBox_2
|
||||
search_type_selector.currentIndexChanged.connect(
|
||||
lambda: self.set_search_type(
|
||||
@@ -1099,6 +1117,30 @@ class QtDriver(QObject):
|
||||
else:
|
||||
self.paste_entry_fields_action.setText("&Paste Fields")
|
||||
|
||||
def thumb_size_callback(self, index: int):
|
||||
"""
|
||||
Performs actions needed when the thumbnail size selection is changed.
|
||||
|
||||
Args:
|
||||
index (int): The index of the item_thumbs/ComboBox list to use.
|
||||
"""
|
||||
# Index 2 is the default (Medium)
|
||||
if index < len(self.thumb_sizes) and index >= 0:
|
||||
self.thumb_size = self.thumb_sizes[index][1]
|
||||
else:
|
||||
logging.error(
|
||||
f"ERROR: Invalid thumbnail size index ({index}). Defaulting to 128px."
|
||||
)
|
||||
self.thumb_size = 128
|
||||
self.update_thumbs()
|
||||
for it in self.item_thumbs:
|
||||
it.resize(self.thumb_size, self.thumb_size)
|
||||
it.thumb_size = (self.thumb_size, self.thumb_size)
|
||||
it.setMinimumSize(self.thumb_size, self.thumb_size)
|
||||
it.setMaximumSize(self.thumb_size, self.thumb_size)
|
||||
it.thumb_button.thumb_size = (self.thumb_size, self.thumb_size)
|
||||
self.flow_container.layout().setSpacing(min(self.thumb_size // 10, 12))
|
||||
|
||||
def mouse_navigation(self, event: QMouseEvent):
|
||||
# print(event.button())
|
||||
if event.button() == Qt.MouseButton.ForwardButton:
|
||||
|
||||
@@ -62,15 +62,10 @@ class ThumbRenderer(QObject):
|
||||
# updatedImage = Signal(QPixmap)
|
||||
# updatedSize = Signal(QSize)
|
||||
|
||||
thumb_mask_512: Image.Image = Image.open(
|
||||
Path(__file__).parents[3] / "resources/qt/images/thumb_mask_512.png"
|
||||
)
|
||||
thumb_mask_512.load()
|
||||
|
||||
thumb_mask_hl_512: Image.Image = Image.open(
|
||||
Path(__file__).parents[3] / "resources/qt/images/thumb_mask_hl_512.png"
|
||||
)
|
||||
thumb_mask_hl_512.load()
|
||||
# Cached thumbnail elements.
|
||||
# Key: Size + Pixel Ratio Tuple (Ex. (512, 512, 1.25))
|
||||
thumb_masks: dict = {}
|
||||
thumb_borders: dict = {}
|
||||
|
||||
thumb_loading_512: Image.Image = Image.open(
|
||||
Path(__file__).parents[3] / "resources/qt/images/thumb_loading_512.png"
|
||||
@@ -98,6 +93,76 @@ class ThumbRenderer(QObject):
|
||||
math.floor(12 * font_pixel_ratio),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _get_mask(size: tuple[int, int], pixel_ratio: float) -> Image.Image:
|
||||
"""
|
||||
Returns a thumbnail mask given a size and pixel ratio.
|
||||
If one is not already cached, then a new one will be rendered.
|
||||
"""
|
||||
item: Image.Image = ThumbRenderer.thumb_masks.get((*size, pixel_ratio))
|
||||
if not item:
|
||||
item = ThumbRenderer._render_mask(size, pixel_ratio)
|
||||
ThumbRenderer.thumb_masks[(*size, pixel_ratio)] = item
|
||||
return item
|
||||
|
||||
@staticmethod
|
||||
def _get_border(size: tuple[int, int], pixel_ratio: float) -> Image.Image:
|
||||
"""
|
||||
Returns a thumbnail border given a size and pixel ratio.
|
||||
If one is not already cached, then a new one will be rendered.
|
||||
"""
|
||||
item: Image.Image = ThumbRenderer.thumb_borders.get((*size, pixel_ratio))
|
||||
if not item:
|
||||
item = ThumbRenderer._render_border(size, pixel_ratio)
|
||||
ThumbRenderer.thumb_borders[(*size, pixel_ratio)] = item
|
||||
return item
|
||||
|
||||
@staticmethod
|
||||
def _render_mask(size: tuple[int, int], pixel_ratio) -> Image.Image:
|
||||
"""Renders a thumbnail mask."""
|
||||
smooth_factor: int = math.ceil(2 * pixel_ratio)
|
||||
radius_factor: int = 8
|
||||
im: Image.Image = Image.new(
|
||||
mode="L",
|
||||
size=tuple([d * smooth_factor for d in size]), # type: ignore
|
||||
color="black",
|
||||
)
|
||||
draw = ImageDraw.Draw(im)
|
||||
draw.rounded_rectangle(
|
||||
(0, 0) + tuple([d - 1 for d in im.size]),
|
||||
radius=math.ceil(radius_factor * smooth_factor * pixel_ratio),
|
||||
fill="white",
|
||||
)
|
||||
im = im.resize(
|
||||
size,
|
||||
resample=Image.Resampling.BILINEAR,
|
||||
)
|
||||
return im
|
||||
|
||||
@staticmethod
|
||||
def _render_border(size: tuple[int, int], pixel_ratio) -> Image.Image:
|
||||
"""Renders a thumbnail border."""
|
||||
smooth_factor: int = math.ceil(2 * pixel_ratio)
|
||||
radius_factor: int = 8
|
||||
im: Image.Image = Image.new(
|
||||
mode="RGBA",
|
||||
size=tuple([d * smooth_factor for d in size]), # type: ignore
|
||||
color="#00000000",
|
||||
)
|
||||
draw = ImageDraw.Draw(im)
|
||||
draw.rounded_rectangle(
|
||||
(0, 0) + tuple([d - 1 for d in im.size]),
|
||||
radius=math.ceil(radius_factor * smooth_factor * pixel_ratio),
|
||||
fill=None,
|
||||
outline="white",
|
||||
width=math.floor(pixel_ratio * 2),
|
||||
)
|
||||
im = im.resize(
|
||||
size,
|
||||
resample=Image.Resampling.BILINEAR,
|
||||
)
|
||||
return im
|
||||
|
||||
def render(
|
||||
self,
|
||||
timestamp: float,
|
||||
@@ -324,11 +389,11 @@ class ThumbRenderer(QObject):
|
||||
)
|
||||
image = image.resize((new_x, new_y), resample=resampling_method)
|
||||
if gradient:
|
||||
mask: Image.Image = ThumbRenderer.thumb_mask_512.resize(
|
||||
(adj_size, adj_size), resample=Image.Resampling.BILINEAR
|
||||
).getchannel(3)
|
||||
hl: Image.Image = ThumbRenderer.thumb_mask_hl_512.resize(
|
||||
(adj_size, adj_size), resample=Image.Resampling.BILINEAR
|
||||
mask: Image.Image = ThumbRenderer._get_mask(
|
||||
(adj_size, adj_size), pixel_ratio
|
||||
)
|
||||
hl: Image.Image = ThumbRenderer._get_border(
|
||||
(adj_size, adj_size), pixel_ratio
|
||||
)
|
||||
final = four_corner_gradient_background(image, adj_size, mask, hl)
|
||||
else:
|
||||
@@ -340,7 +405,7 @@ class ThumbRenderer(QObject):
|
||||
)
|
||||
draw = ImageDraw.Draw(rec)
|
||||
draw.rounded_rectangle(
|
||||
(0, 0) + rec.size,
|
||||
(0, 0) + tuple([d - 1 for d in rec.size]),
|
||||
(base_size[0] // 32) * scalar * pixel_ratio,
|
||||
fill="red",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user