Add smooth tab widget with fade transitions

Introduce SmoothTabWidget and a FadingStackedWidget to provide cross-fade transitions between tabs. Added ytsage/gui/ytsage_smooth_tab_widget.py which implements a QTabBar + FadingStackedWidget (default 300ms fade) and overlay-based fade logic. Updated ytsage/gui/ytsage_gui_dialogs/ytsage_dialogs_custom.py to use SmoothTabWidget instead of QTabWidget and adjusted the stylesheet selector to target the new content frame (#tabContent) so existing styles continue to apply.
This commit is contained in:
oop7
2026-02-01 14:30:54 +02:00
parent 79a85cf707
commit 2935eb01c1
2 changed files with 126 additions and 2 deletions
@@ -29,6 +29,7 @@ from PySide6.QtWidgets import (
QWidget,
)
from ..ytsage_smooth_tab_widget import SmoothTabWidget
from ...core.ytsage_yt_dlp import get_yt_dlp_path
from ...core.ytsage_utils import update_auto_update_settings
from ...utils.ytsage_constants import YTDLP_DOCS_URL
@@ -115,7 +116,7 @@ class CustomOptionsDialog(QDialog):
layout = QVBoxLayout(self)
# Create tab widget to organize content
self.tab_widget = QTabWidget()
self.tab_widget = SmoothTabWidget()
layout.addWidget(self.tab_widget)
# === Cookies Tab ===
@@ -542,7 +543,7 @@ class CustomOptionsDialog(QDialog):
QDialog {
background-color: #15181b;
}
QTabWidget::pane {
QFrame#tabContent {
border: 1px solid #3d3d3d;
background-color: #15181b;
}
+123
View File
@@ -0,0 +1,123 @@
from PySide6.QtCore import QEasingCurve, QPropertyAnimation, Qt
from PySide6.QtGui import QPixmap
from PySide6.QtWidgets import (
QFrame,
QGraphicsOpacityEffect,
QLabel,
QStackedWidget,
QTabBar,
QVBoxLayout,
QWidget,
)
class FadingStackedWidget(QStackedWidget):
"""
A QStackedWidget that cross-fades between widgets.
"""
def __init__(self, parent=None):
super().__init__(parent)
self.fade_duration = 300
self.fade_easing = QEasingCurve.Type.OutQuad
def setCurrentIndex(self, index):
curr_index = self.currentIndex()
if index == curr_index:
return
widget = self.widget(index)
curr_widget = self.widget(curr_index)
# If widget isn't visible or valid, just swap
if not self.isVisible() or not curr_widget:
super().setCurrentIndex(index)
return
# 1. Capture the current view (the "old" tab)
# Use grab() for simplicity and reliability in PySide6
pixmap = self.grab()
# 2. Create an overlay label to hold this "old" view
overlay = QLabel(self)
overlay.setPixmap(pixmap)
overlay.setGeometry(0, 0, self.width(), self.height())
overlay.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents) # Don't block clicks
overlay.show()
# 3. Switch the actual stack to the "new" view
super().setCurrentIndex(index)
# CRITICAL: Ensure overlay stays on top of the new widget
overlay.raise_()
# 4. Fade OUT the overlay, revealing the new view
effect = QGraphicsOpacityEffect(overlay)
overlay.setGraphicsEffect(effect)
anim = QPropertyAnimation(effect, b"opacity", overlay)
anim.setDuration(self.fade_duration)
anim.setStartValue(1.0)
anim.setEndValue(0.0)
anim.setEasingCurve(self.fade_easing)
# Cleanup when done
anim.finished.connect(lambda: self._cleanup(overlay))
# Keep reference to prevent GC
self._active_anim = anim
anim.start(QPropertyAnimation.DeletionPolicy.DeleteWhenStopped)
def _cleanup(self, overlay):
overlay.hide()
overlay.deleteLater()
class SmoothTabWidget(QWidget):
"""
A unified Widget that behaves like a QTabWidget but uses smooth fading transitions.
Includes a QTabBar and a FadingStackedWidget.
"""
def __init__(self, parent=None):
super().__init__(parent)
# Main Layout
self.layout = QVBoxLayout(self)
self.layout.setContentsMargins(0, 0, 0, 0)
self.layout.setSpacing(0)
# Tab Bar
self.tab_bar = QTabBar(self)
self.tab_bar.setDrawBase(False) # We draw border on content instead
self.tab_bar.currentChanged.connect(self.set_current_index)
self.layout.addWidget(self.tab_bar)
# Content Area (Frame) - Mimics QTabWidget::pane
self.content_frame = QFrame(self)
self.content_frame.setObjectName("tabContent")
# Layout inside the content frame
self.content_layout = QVBoxLayout(self.content_frame)
self.content_layout.setContentsMargins(0, 0, 0, 0)
self.content_layout.setSpacing(0)
# The Stack
self.stack = FadingStackedWidget(self.content_frame)
self.content_layout.addWidget(self.stack)
self.layout.addWidget(self.content_frame)
def addTab(self, widget, label):
"""Add a tab with the given widget and label."""
self.stack.addWidget(widget)
self.tab_bar.addTab(label)
def set_current_index(self, index):
"""Slot to handle tab bar clicks."""
self.tab_bar.setCurrentIndex(index)
self.stack.setCurrentIndex(index)
def currentWidget(self):
return self.stack.currentWidget()
def currentIndex(self):
return self.stack.currentIndex()