feat(ui): add underline indicator for search bar items

This commit is contained in:
Travis Abendshien
2026-07-19 13:14:06 -07:00
parent 111ea76884
commit 0fbcfdb1ea
7 changed files with 103 additions and 20 deletions
@@ -15,6 +15,7 @@ from tagstudio.core.library.alchemy.library import Library
from tagstudio.qt.controllers.edit_field_template_modal import EditFieldTemplateModal
from tagstudio.qt.controllers.field_template_widget_controller import FieldTemplateWidget
from tagstudio.qt.controllers.suggest_box import SuggestBox
from tagstudio.qt.controllers.underlined_widget import UnderlinedWidget
from tagstudio.qt.translations import Translations
from tagstudio.qt.views.panel_modal import PanelModal, PanelWidget
from tagstudio.qt.views.suggest_box_view import SuggestBoxView
@@ -97,14 +98,19 @@ class FieldSuggestBox(SuggestBox[BaseFieldTemplate]):
@override
def _set_item_widget(self, item: BaseFieldTemplate | None, index: int) -> None:
"""Set the field template of a field template widget at a specific index."""
field_template_widget: FieldTemplateWidget = self._get_item_widget(index, self._lib)
underlined_widget: UnderlinedWidget = self._get_item_widget(index, self._lib)
field_template_widget = underlined_widget.widget
assert isinstance(field_template_widget, FieldTemplateWidget)
field_template_widget.has_remove = False
field_template_widget.set_field_template(item)
field_template_widget.setHidden(item is None)
underlined_widget.setHidden(item is None)
if item is None:
return
# TODO: Add tabbing to different items, and use underline to indicate which will be added
underlined_widget.toggle_underline(index != 0)
# Disconnect previous callbacks
with catch_warnings(record=True):
field_template_widget.on_edit.disconnect()
@@ -139,15 +145,16 @@ class FieldSuggestBox(SuggestBox[BaseFieldTemplate]):
self._update_items(self.layout().search_field.text())
@override
def _get_item_widget(self, index: int, library: Library | None) -> FieldTemplateWidget:
def _get_item_widget(self, index: int, library: Library | None) -> UnderlinedWidget:
"""Gets the item widget at a specific index."""
# Create any new item widgets needed up to the given index
if self.layout().content_layout.count() <= index:
while self.layout().content_layout.count() <= index:
pad_field_template_widget = FieldTemplateWidget()
pad_field_template_widget.setHidden(True)
self.layout().content_layout.addWidget(pad_field_template_widget)
field_template_widget = FieldTemplateWidget()
widget = UnderlinedWidget(field_template_widget)
widget.setHidden(True)
self.layout().content_layout.addWidget(widget)
field_template_widget: QWidget = self.layout().content_layout.itemAt(index).widget()
assert isinstance(field_template_widget, FieldTemplateWidget)
return field_template_widget
widget_: QWidget = self.layout().content_layout.itemAt(index).widget()
assert isinstance(widget_, UnderlinedWidget)
return widget_
+7 -2
View File
@@ -10,6 +10,7 @@ from PySide6.QtWidgets import QGraphicsOpacityEffect, QWidget
from tagstudio.core.library.alchemy.library import Library
from tagstudio.qt.controllers.autofill_line_edit import QtCore, QtGui
from tagstudio.qt.controllers.underlined_widget import UnderlinedWidget
from tagstudio.qt.views.panel_modal import PanelWidget
from tagstudio.qt.views.stylesheets.stylesheets import (
autofill_line_edit_style,
@@ -82,11 +83,15 @@ class SuggestBox[T](QWidget):
opacity_effect = QGraphicsOpacityEffect(self)
opacity_effect.setOpacity(0.3)
if self.layout().content_layout.count() > 0:
self.layout().content_layout.itemAt(0).widget().setGraphicsEffect(opacity_effect)
underlined_widget = self.layout().content_layout.itemAt(0).widget()
assert isinstance(underlined_widget, UnderlinedWidget)
underlined_widget.widget.setGraphicsEffect(opacity_effect)
else:
self._is_shift_held = False
if self.layout().content_layout.count() > 0:
self.layout().content_layout.itemAt(0).widget().setGraphicsEffect(None) # pyright: ignore[reportArgumentType]
underlined_widget = self.layout().content_layout.itemAt(0).widget()
assert isinstance(underlined_widget, UnderlinedWidget)
underlined_widget.widget.setGraphicsEffect(None) # pyright: ignore[reportArgumentType]
def _clear_search_query(self) -> None:
self.layout().search_field.setText("")
@@ -14,6 +14,7 @@ from tagstudio.core.library.alchemy.enums import BrowsingState
from tagstudio.core.library.alchemy.library import Library
from tagstudio.core.library.alchemy.models import Tag
from tagstudio.qt.controllers.suggest_box import SuggestBox
from tagstudio.qt.controllers.underlined_widget import UnderlinedWidget
from tagstudio.qt.mixed.build_tag import BuildTagPanel
from tagstudio.qt.mixed.tag_widget import TagWidget
from tagstudio.qt.translations import Translations
@@ -109,10 +110,12 @@ class TagSuggestBox(SuggestBox[Tag]):
@override
def _set_item_widget(self, item: Tag | None, index: int) -> None:
"""Set the tag of a tag widget at a specific index."""
tag_widget: TagWidget = self._get_item_widget(index, self._lib)
underlined_widget: UnderlinedWidget = self._get_item_widget(index, self._lib)
tag_widget = underlined_widget.widget
assert isinstance(tag_widget, TagWidget)
tag_widget.has_remove = False
tag_widget.set_tag(item)
tag_widget.setHidden(item is None)
underlined_widget.setHidden(item is None)
opacity_effect = QGraphicsOpacityEffect(self)
opacity_effect.setOpacity(0.3)
if item and item.id in self.added:
@@ -123,6 +126,9 @@ class TagSuggestBox(SuggestBox[Tag]):
if item is None:
return
# TODO: Add tabbing to different items, and use underline to indicate which will be added
underlined_widget.toggle_underline(index != 0)
# Disconnect previous callbacks
with catch_warnings(record=True):
tag_widget.on_edit.disconnect()
@@ -165,16 +171,17 @@ class TagSuggestBox(SuggestBox[Tag]):
self._update_items(self.layout().search_field.text())
@override
def _get_item_widget(self, index: int, library: Library | None) -> TagWidget:
def _get_item_widget(self, index: int, library: Library | None) -> UnderlinedWidget:
"""Gets the item widget at a specific index."""
# Create any new item widgets needed up to the given index
if self.layout().content_layout.count() <= index:
while self.layout().content_layout.count() <= index:
tag_widget = TagWidget(tag=None, has_edit=True, has_remove=True, library=library)
tag_widget.on_remove.connect(self._update_items)
tag_widget.setHidden(True)
self.layout().content_layout.addWidget(tag_widget)
widget = UnderlinedWidget(tag_widget)
widget.setHidden(True)
self.layout().content_layout.addWidget(widget)
tag_widget: QWidget = self.layout().content_layout.itemAt(index).widget()
assert isinstance(tag_widget, TagWidget)
return tag_widget
widget_: QWidget = self.layout().content_layout.itemAt(index).widget()
assert isinstance(widget_, UnderlinedWidget)
return widget_
@@ -0,0 +1,28 @@
# SPDX-FileCopyrightText: (c) TagStudio Contributors
# SPDX-License-Identifier: GPL-3.0-only
from typing import override
from PySide6.QtWidgets import QWidget
from tagstudio.qt.views.underlined_widget_view import UnderlinedWidgetView
class UnderlinedWidget(QWidget):
def __init__(self, widget: QWidget) -> None:
super().__init__()
view = UnderlinedWidgetView(widget)
self.setLayout(view)
def toggle_underline(self, is_hidden: bool) -> None:
self.layout().underline.setHidden(is_hidden)
@property
def widget(self) -> QWidget:
return self.layout().itemAt(0).widget()
@override
def layout(self) -> UnderlinedWidgetView:
"""Return the typed layout for this widget."""
return super().layout() # pyright: ignore[reportReturnType]
@@ -410,6 +410,13 @@ def tag_remove_button_style(
"""
def widget_underline_style() -> str:
return f"""
background: {Palette.accent()};
border-radius: 2px;
"""
def title_line_edit_style() -> str:
"""Used to mimic an H3-like header style inside a QLineEdit."""
return """
+3 -1
View File
@@ -59,7 +59,9 @@ class SuggestBoxView(QVBoxLayout):
self.scroll_area.setStyleSheet(scroll_area_style)
scroll_area_container_layout.addWidget(self.scroll_area)
self.scroll_area.setWidget(contents)
self.scroll_area.setMaximumHeight(28)
search_bar_height = 28
underline_padding = 7
self.scroll_area.setMaximumHeight(search_bar_height + underline_padding)
self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.scroll_area.verticalScrollBar().setEnabled(False)
self.scroll_area.setContentsMargins(0, 0, 0, 0)
@@ -0,0 +1,27 @@
# SPDX-FileCopyrightText: (c) TagStudio Contributors
# SPDX-License-Identifier: GPL-3.0-only
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QPushButton, QVBoxLayout, QWidget
from tagstudio.qt.views.stylesheets.stylesheets import widget_underline_style
class UnderlinedWidgetView(QVBoxLayout):
def __init__(self, widget: QWidget) -> None:
super().__init__()
self.setContentsMargins(0, 0, 0, 0)
self.setSpacing(3)
self.setAlignment(Qt.AlignmentFlag.AlignTop)
# HACK: I don't know why I can't just use a QFrame for the outline.
# The styling and sizing only seems to work if it's something like a QPushButton.
self.underline = QPushButton()
self.underline.setFlat(True)
self.underline.setDisabled(True)
self.underline.setMaximumHeight(4)
self.underline.setStyleSheet(widget_underline_style())
self.addWidget(widget)
self.addWidget(self.underline)