Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 42 additions & 21 deletions src/tagstudio/qt/controllers/info_button.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# SPDX-License-Identifier: GPL-3.0-only


from collections.abc import Callable
from typing import override

from PySide6.QtCore import QEvent, QObject, QPoint, QRect, Qt, QVariantAnimation
Expand Down Expand Up @@ -52,17 +53,42 @@ class InfoButton(QAbstractButton):
_SHADOW_ALPHA = 160
_WINDOW_MARGIN = 12

def __init__(self, content: QLayout, title: str, popover_width: int = 360) -> None:
def __init__(
self,
content: QLayout | Callable[[], QLayout],
title: str,
popover_width: int = 360,
) -> None:
"""Create the button.

`content` may be a layout factory, in which case the popover is only built on first open.
"""
super().__init__()
self.setToolTip(Translations.format("info.tooltip.about", title=title))
self.setAccessibleName(self.toolTip())
self.setFixedSize(*self._BUTTON_SIZE)
self.setCursor(Qt.CursorShape.PointingHandCursor)
self.setFocusPolicy(Qt.FocusPolicy.NoFocus)

self._content = content
self._popover_width = popover_width
self._popover: QFrame | None = None

self._is_open = False
self._update_icon(hovered=False)
self._fade = QVariantAnimation(self)
self._fade.setDuration(self._FADE_DURATION)
self._fade.valueChanged.connect(self._set_opacity)
self._fade.finished.connect(self._on_fade_finished)

self.clicked.connect(self._toggle_popover)

def _build_popover(self) -> QFrame:
"""Build the hidden popover, deferred until it is first shown."""
content = self._content if isinstance(self._content, QLayout) else self._content()
self._popover = QFrame(self)
self._popover.setObjectName("info_popover")
self._popover.setFixedWidth(popover_width)
self._popover.setFixedWidth(self._popover_width)
self._popover.setStyleSheet(info_popover_style())
self._popover.setLayout(content)
self._popover.hide()
Expand All @@ -72,29 +98,23 @@ def __init__(self, content: QLayout, title: str, popover_width: int = 360) -> No
self._fade_effect.setOffset(0, 3)
self._fade_effect.setColor(QColor(0, 0, 0, self._SHADOW_ALPHA))
self._popover.setGraphicsEffect(self._fade_effect)

self._is_open = False
self._update_icon(hovered=False)
self._fade = QVariantAnimation(self)
self._fade.setDuration(self._FADE_DURATION)
self._fade.valueChanged.connect(self._set_opacity)
self._fade.finished.connect(self._on_fade_finished)

self.clicked.connect(self._toggle_popover)
return self._popover

def _toggle_popover(self) -> None:
if self._is_open:
self._hide_popover()
return

popover = self._popover or self._build_popover()

# Overlay the popover inside the current window, below the button.
# If there's no room below, flip it above.
window = self.window()
if self._popover.parent() is not window:
self._popover.setParent(window)
width = self._popover.width()
height = self._popover.heightForWidth(width)
self._popover.resize(width, height)
if popover.parent() is not window:
popover.setParent(window)
width = popover.width()
height = popover.heightForWidth(width)
popover.resize(width, height)
pos = self.mapTo(window, QPoint(self.width() - width, self.height() + self._BUTTON_GAP))
if pos.y() + height > window.height() - self._WINDOW_MARGIN:
pos.setY(self.mapTo(window, QPoint(0, 0)).y() - height - self._BUTTON_GAP)
Expand All @@ -103,9 +123,9 @@ def _toggle_popover(self) -> None:
)
pos.setY(max(self._WINDOW_MARGIN, pos.y()))

self._popover.move(pos)
self._popover.raise_()
self._popover.show()
popover.move(pos)
popover.raise_()
popover.show()
self._is_open = True
self._update_icon(self.underMouse())
self._fade_to(1.0)
Expand All @@ -131,7 +151,7 @@ def _set_opacity(self, opacity: float) -> None:

def _on_fade_finished(self) -> None:
if not self._is_open:
self._popover.hide()
unwrap(self._popover).hide()

def _update_icon(self, hovered: bool) -> None:
rm = ResourceManager()
Expand Down Expand Up @@ -163,7 +183,8 @@ def eventFilter(self, watched: QObject, event: QEvent) -> bool:
if isinstance(event, QMouseEvent) and event.type() == QEvent.Type.MouseButtonPress:
click_pos = event.globalPosition().toPoint()
button_rect = QRect(self.mapToGlobal(QPoint(0, 0)), self.size())
popover_rect = QRect(self._popover.mapToGlobal(QPoint(0, 0)), self._popover.size())
popover = unwrap(self._popover)
popover_rect = QRect(popover.mapToGlobal(QPoint(0, 0)), popover.size())
# Clicks outside the popover or the ⓘ button will close it
if not button_rect.contains(click_pos) and not popover_rect.contains(click_pos):
self._hide_popover()
Expand Down
7 changes: 4 additions & 3 deletions src/tagstudio/qt/views/edit_tag_panel_view.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# SPDX-FileCopyrightText: (c) TagStudio Contributors
# SPDX-License-Identifier: GPL-3.0-only

from collections.abc import Callable

from PySide6.QtCore import Qt
from PySide6.QtWidgets import (
Expand Down Expand Up @@ -74,7 +75,7 @@ def __init__(self) -> None:
parent_tags_layout.setSpacing(0)
parent_tags_layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
parent_tags_layout.addWidget(
self._title_row(Translations["tag.parent_tags"], ParentTagsInfoView())
self._title_row(Translations["tag.parent_tags"], ParentTagsInfoView)
)

parent_tags_scroll_contents = QWidget()
Expand All @@ -94,7 +95,7 @@ def __init__(self) -> None:
category_layout.setSpacing(0)
category_layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
category_layout.addWidget(
self._title_row(Translations["tag.categories"], CategoriesInfoView())
self._title_row(Translations["tag.categories"], CategoriesInfoView)
)
category_layout.addWidget(self._subtitle(Translations["tag.categories.subtitle"]))

Expand Down Expand Up @@ -163,7 +164,7 @@ def _scroll_area(self, contents: QWidget) -> QScrollArea:
scroll_area.setWidget(contents)
return scroll_area

def _title_row(self, title: str, info: QLayout) -> QWidget:
def _title_row(self, title: str, info: Callable[[], QLayout]) -> QWidget:
widget = QWidget()
layout = QHBoxLayout(widget)
layout.setContentsMargins(0, 0, 0, 0)
Expand Down