347 lines
8.7 KiB
C++
347 lines
8.7 KiB
C++
#include "slidingstackedwidget.h"
|
|
#include <QWidget>
|
|
#include <QLabel>
|
|
#include <QScreen>
|
|
#include "../../qEmbyCore/config/configstore.h"
|
|
#include "config/config_keys.h"
|
|
#include <cmath>
|
|
|
|
namespace {
|
|
QPoint interpolatePoint(const QPoint &start, const QPoint &end, qreal progress)
|
|
{
|
|
return QPoint(
|
|
qRound(start.x() + (end.x() - start.x()) * progress),
|
|
qRound(start.y() + (end.y() - start.y()) * progress));
|
|
}
|
|
}
|
|
|
|
SlidingStackedWidget::SlidingStackedWidget(QWidget *parent)
|
|
: QStackedWidget(parent),
|
|
m_speed(350),
|
|
m_animationType(QEasingCurve::OutCubic),
|
|
m_isAnimating(false),
|
|
m_nextIndex(0)
|
|
{
|
|
m_slideTimer.setTimerType(Qt::PreciseTimer);
|
|
connect(&m_slideTimer, &QTimer::timeout, this,
|
|
&SlidingStackedWidget::updateSlideAnimation);
|
|
}
|
|
|
|
void SlidingStackedWidget::setSpeed(int speed)
|
|
{
|
|
m_speed = speed;
|
|
}
|
|
|
|
void SlidingStackedWidget::setEasingCurve(QEasingCurve::Type curveType)
|
|
{
|
|
m_animationType = curveType;
|
|
}
|
|
|
|
void SlidingStackedWidget::slideInWgt(QWidget *widget, SlideDirection direction)
|
|
{
|
|
int idx = indexOf(widget);
|
|
if (idx != -1) {
|
|
slideInIdx(idx, direction);
|
|
}
|
|
}
|
|
|
|
void SlidingStackedWidget::slideInIdx(int idx, SlideDirection direction)
|
|
{
|
|
if (idx < 0 || idx >= count())
|
|
return;
|
|
|
|
|
|
if (currentIndex() == idx && !m_isAnimating)
|
|
return;
|
|
|
|
|
|
|
|
if (m_isAnimating) {
|
|
m_slideTimer.stop();
|
|
animationDoneSlot();
|
|
}
|
|
|
|
|
|
bool reduceAnimations = ConfigStore::instance()->get<bool>(ConfigKeys::UiAnimations, false);
|
|
if (reduceAnimations) {
|
|
m_nextIndex = idx;
|
|
setCurrentIndex(idx);
|
|
QWidget *w = widget(idx);
|
|
if (w) w->raise();
|
|
flushPendingWidgetDisposals();
|
|
Q_EMIT animationFinished();
|
|
return;
|
|
}
|
|
|
|
m_isAnimating = true;
|
|
m_nextIndex = idx;
|
|
|
|
int now = currentIndex();
|
|
QWidget *widgetNow = widget(now);
|
|
QWidget *widgetNext = widget(idx);
|
|
|
|
|
|
if (!widgetNow || !widgetNext) {
|
|
m_isAnimating = false;
|
|
if (widgetNext) {
|
|
setCurrentIndex(idx);
|
|
}
|
|
return;
|
|
}
|
|
|
|
int width = this->width();
|
|
int height = this->height();
|
|
|
|
int offsetX = 0;
|
|
int offsetY = 0;
|
|
|
|
SlideDirection actualDirection = direction;
|
|
if (direction == Automatic) {
|
|
if (now < idx)
|
|
actualDirection = RightToLeft;
|
|
else
|
|
actualDirection = LeftToRight;
|
|
}
|
|
|
|
if (actualDirection == RightToLeft) {
|
|
offsetX = width;
|
|
offsetY = 0;
|
|
} else if (actualDirection == LeftToRight) {
|
|
offsetX = -width;
|
|
offsetY = 0;
|
|
} else if (actualDirection == BottomToTop) {
|
|
offsetX = 0;
|
|
offsetY = height;
|
|
} else if (actualDirection == TopToBottom) {
|
|
offsetX = 0;
|
|
offsetY = -height;
|
|
}
|
|
|
|
|
|
widgetNext->setGeometry(0, 0, width, height);
|
|
|
|
QPoint pNext = widgetNext->pos();
|
|
QPoint pNow = widgetNow->pos();
|
|
|
|
const bool useSnapshot =
|
|
ConfigStore::instance()->get<bool>(ConfigKeys::SnapshotNavigation, true);
|
|
|
|
QWidget *animTargetNow = widgetNow;
|
|
QWidget *animTargetNext = widgetNext;
|
|
bool usingSnapshotAnimation = false;
|
|
|
|
if (useSnapshot) {
|
|
clearSnapshotLabels();
|
|
|
|
m_currentSnapshotLabel =
|
|
createSnapshotLabel(widgetNow, QRect(pNow, QSize(width, height)));
|
|
|
|
widgetNext->show();
|
|
widgetNext->raise();
|
|
m_nextSnapshotLabel =
|
|
createSnapshotLabel(widgetNext,
|
|
QRect(QPoint(pNext.x() + offsetX,
|
|
pNext.y() + offsetY),
|
|
QSize(width, height)));
|
|
|
|
usingSnapshotAnimation =
|
|
m_currentSnapshotLabel && m_nextSnapshotLabel;
|
|
}
|
|
|
|
if (usingSnapshotAnimation) {
|
|
animTargetNow = m_currentSnapshotLabel;
|
|
animTargetNext = m_nextSnapshotLabel;
|
|
widgetNow->hide();
|
|
widgetNext->hide();
|
|
} else {
|
|
clearSnapshotLabels();
|
|
|
|
widgetNext->move(pNext.x() + offsetX, pNext.y() + offsetY);
|
|
widgetNext->show();
|
|
widgetNext->raise();
|
|
}
|
|
|
|
startSlideAnimation(animTargetNow,
|
|
pNow,
|
|
QPoint(pNow.x() - offsetX, pNow.y() - offsetY),
|
|
animTargetNext,
|
|
QPoint(pNext.x() + offsetX, pNext.y() + offsetY),
|
|
pNext);
|
|
}
|
|
|
|
void SlidingStackedWidget::disposeWidgetWhenSafe(QWidget *widget)
|
|
{
|
|
if (!widget)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const QPointer<QWidget> safeWidget(widget);
|
|
if (!m_pendingWidgetDisposals.contains(safeWidget))
|
|
{
|
|
m_pendingWidgetDisposals.append(safeWidget);
|
|
}
|
|
|
|
if (!m_isAnimating)
|
|
{
|
|
flushPendingWidgetDisposals();
|
|
}
|
|
}
|
|
|
|
QLabel *SlidingStackedWidget::createSnapshotLabel(QWidget *source,
|
|
const QRect &geometry)
|
|
{
|
|
if (!source || geometry.size().isEmpty())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
QPixmap snapshot = source->grab(QRect(QPoint(0, 0), geometry.size()));
|
|
if (snapshot.isNull())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
auto *label = new QLabel(this);
|
|
label->setAttribute(Qt::WA_TransparentForMouseEvents, true);
|
|
label->setPixmap(snapshot);
|
|
label->setGeometry(geometry);
|
|
label->show();
|
|
label->raise();
|
|
return label;
|
|
}
|
|
|
|
void SlidingStackedWidget::clearSnapshotLabels()
|
|
{
|
|
if (m_currentSnapshotLabel)
|
|
{
|
|
m_currentSnapshotLabel->hide();
|
|
m_currentSnapshotLabel->deleteLater();
|
|
m_currentSnapshotLabel = nullptr;
|
|
}
|
|
|
|
if (m_nextSnapshotLabel)
|
|
{
|
|
m_nextSnapshotLabel->hide();
|
|
m_nextSnapshotLabel->deleteLater();
|
|
m_nextSnapshotLabel = nullptr;
|
|
}
|
|
}
|
|
|
|
void SlidingStackedWidget::startSlideAnimation(QWidget *widgetNow,
|
|
const QPoint &nowStart,
|
|
const QPoint &nowEnd,
|
|
QWidget *widgetNext,
|
|
const QPoint &nextStart,
|
|
const QPoint &nextEnd)
|
|
{
|
|
m_animWidgetNow = widgetNow;
|
|
m_animWidgetNext = widgetNext;
|
|
m_animNowStart = nowStart;
|
|
m_animNowEnd = nowEnd;
|
|
m_animNextStart = nextStart;
|
|
m_animNextEnd = nextEnd;
|
|
|
|
if (!m_animWidgetNow || !m_animWidgetNext || m_speed <= 0)
|
|
{
|
|
animationDoneSlot();
|
|
return;
|
|
}
|
|
|
|
m_slideTimer.setInterval(animationFrameIntervalMs());
|
|
m_slideClock.restart();
|
|
updateSlideAnimation();
|
|
m_slideTimer.start();
|
|
}
|
|
|
|
void SlidingStackedWidget::updateSlideAnimation()
|
|
{
|
|
if (!m_animWidgetNow || !m_animWidgetNext)
|
|
{
|
|
animationDoneSlot();
|
|
return;
|
|
}
|
|
|
|
const qreal elapsedMs = m_slideClock.isValid()
|
|
? m_slideClock.nsecsElapsed() / 1000000.0
|
|
: static_cast<qreal>(m_speed);
|
|
const qreal linearProgress =
|
|
qBound<qreal>(0.0, elapsedMs / qMax(1, m_speed), 1.0);
|
|
const qreal easedProgress =
|
|
QEasingCurve(m_animationType).valueForProgress(linearProgress);
|
|
|
|
m_animWidgetNow->move(
|
|
interpolatePoint(m_animNowStart, m_animNowEnd, easedProgress));
|
|
m_animWidgetNext->move(
|
|
interpolatePoint(m_animNextStart, m_animNextEnd, easedProgress));
|
|
|
|
if (linearProgress >= 1.0)
|
|
{
|
|
animationDoneSlot();
|
|
}
|
|
}
|
|
|
|
int SlidingStackedWidget::animationFrameIntervalMs() const
|
|
{
|
|
const QScreen *targetScreen = screen();
|
|
if (!targetScreen && window())
|
|
{
|
|
targetScreen = window()->screen();
|
|
}
|
|
|
|
const qreal refreshRate = targetScreen ? targetScreen->refreshRate() : 0.0;
|
|
if (refreshRate <= 0.0)
|
|
{
|
|
return 16;
|
|
}
|
|
|
|
return qBound(4,
|
|
static_cast<int>(std::lround(1000.0 / refreshRate)),
|
|
16);
|
|
}
|
|
|
|
void SlidingStackedWidget::animationDoneSlot()
|
|
{
|
|
m_slideTimer.stop();
|
|
m_animWidgetNow.clear();
|
|
m_animWidgetNext.clear();
|
|
clearSnapshotLabels();
|
|
|
|
|
|
if (m_nextIndex >= 0 && m_nextIndex < count()) {
|
|
setCurrentIndex(m_nextIndex);
|
|
QWidget *w = widget(m_nextIndex);
|
|
if (w) {
|
|
w->raise();
|
|
}
|
|
}
|
|
m_isAnimating = false;
|
|
flushPendingWidgetDisposals();
|
|
Q_EMIT animationFinished();
|
|
}
|
|
|
|
void SlidingStackedWidget::flushPendingWidgetDisposals()
|
|
{
|
|
if (m_isAnimating || m_pendingWidgetDisposals.isEmpty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
const auto widgetsToDispose = m_pendingWidgetDisposals;
|
|
m_pendingWidgetDisposals.clear();
|
|
|
|
for (const QPointer<QWidget> &widget : widgetsToDispose)
|
|
{
|
|
if (!widget)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (indexOf(widget) != -1)
|
|
{
|
|
removeWidget(widget);
|
|
}
|
|
widget->deleteLater();
|
|
}
|
|
}
|