perf: improve animation smoothness on Wayland
This commit is contained in:
@@ -31,6 +31,12 @@ sudo pacman -U qemby-*.pkg.tar.zst
|
|||||||
qemby
|
qemby
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If native Wayland causes windowing or OpenGL issues, run with the XCB fallback:
|
||||||
|
|
||||||
|
```fish
|
||||||
|
QEMBY_FORCE_XCB=1 qemby
|
||||||
|
```
|
||||||
|
|
||||||
## Uninstall
|
## Uninstall
|
||||||
|
|
||||||
```fish
|
```fish
|
||||||
|
|||||||
@@ -1,8 +1,19 @@
|
|||||||
#include "slidingstackedwidget.h"
|
#include "slidingstackedwidget.h"
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
|
#include <QScreen>
|
||||||
#include "../../qEmbyCore/config/configstore.h"
|
#include "../../qEmbyCore/config/configstore.h"
|
||||||
#include "config/config_keys.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)
|
SlidingStackedWidget::SlidingStackedWidget(QWidget *parent)
|
||||||
: QStackedWidget(parent),
|
: QStackedWidget(parent),
|
||||||
@@ -11,8 +22,9 @@ SlidingStackedWidget::SlidingStackedWidget(QWidget *parent)
|
|||||||
m_isAnimating(false),
|
m_isAnimating(false),
|
||||||
m_nextIndex(0)
|
m_nextIndex(0)
|
||||||
{
|
{
|
||||||
m_animGroup = new QParallelAnimationGroup(this);
|
m_slideTimer.setTimerType(Qt::PreciseTimer);
|
||||||
connect(m_animGroup, &QParallelAnimationGroup::finished, this, &SlidingStackedWidget::animationDoneSlot);
|
connect(&m_slideTimer, &QTimer::timeout, this,
|
||||||
|
&SlidingStackedWidget::updateSlideAnimation);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SlidingStackedWidget::setSpeed(int speed)
|
void SlidingStackedWidget::setSpeed(int speed)
|
||||||
@@ -45,22 +57,7 @@ void SlidingStackedWidget::slideInIdx(int idx, SlideDirection direction)
|
|||||||
|
|
||||||
|
|
||||||
if (m_isAnimating) {
|
if (m_isAnimating) {
|
||||||
|
m_slideTimer.stop();
|
||||||
|
|
||||||
|
|
||||||
bool allTargetsAlive = true;
|
|
||||||
for (int i = 0; i < m_animGroup->animationCount(); ++i) {
|
|
||||||
auto *anim = qobject_cast<QPropertyAnimation *>(m_animGroup->animationAt(i));
|
|
||||||
if (anim && !anim->targetObject()) {
|
|
||||||
allTargetsAlive = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allTargetsAlive) {
|
|
||||||
m_animGroup->stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
animationDoneSlot();
|
animationDoneSlot();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,45 +123,50 @@ void SlidingStackedWidget::slideInIdx(int idx, SlideDirection direction)
|
|||||||
QPoint pNext = widgetNext->pos();
|
QPoint pNext = widgetNext->pos();
|
||||||
QPoint pNow = widgetNow->pos();
|
QPoint pNow = widgetNow->pos();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const bool useSnapshot =
|
const bool useSnapshot =
|
||||||
ConfigStore::instance()->get<bool>(ConfigKeys::SnapshotNavigation, false);
|
ConfigStore::instance()->get<bool>(ConfigKeys::SnapshotNavigation, true);
|
||||||
|
|
||||||
|
QWidget *animTargetNow = widgetNow;
|
||||||
|
QWidget *animTargetNext = widgetNext;
|
||||||
|
bool usingSnapshotAnimation = false;
|
||||||
|
|
||||||
if (useSnapshot) {
|
if (useSnapshot) {
|
||||||
QPixmap snapshot = widgetNow->grab();
|
clearSnapshotLabels();
|
||||||
m_snapshotLabel = new QLabel(this);
|
|
||||||
m_snapshotLabel->setPixmap(snapshot);
|
m_currentSnapshotLabel =
|
||||||
m_snapshotLabel->setGeometry(widgetNow->geometry());
|
createSnapshotLabel(widgetNow, QRect(pNow, QSize(width, height)));
|
||||||
m_snapshotLabel->show();
|
|
||||||
widgetNow->hide();
|
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) {
|
||||||
widgetNext->move(pNext.x() + offsetX, pNext.y() + offsetY);
|
animTargetNow = m_currentSnapshotLabel;
|
||||||
widgetNext->show();
|
animTargetNext = m_nextSnapshotLabel;
|
||||||
widgetNext->raise();
|
widgetNow->hide();
|
||||||
|
widgetNext->hide();
|
||||||
|
} else {
|
||||||
|
clearSnapshotLabels();
|
||||||
|
|
||||||
|
widgetNext->move(pNext.x() + offsetX, pNext.y() + offsetY);
|
||||||
QObject *animTarget = useSnapshot ? static_cast<QObject*>(m_snapshotLabel)
|
widgetNext->show();
|
||||||
: static_cast<QObject*>(widgetNow);
|
widgetNext->raise();
|
||||||
QPropertyAnimation *animNow = new QPropertyAnimation(animTarget, "pos");
|
}
|
||||||
animNow->setDuration(m_speed);
|
|
||||||
animNow->setEasingCurve(m_animationType);
|
|
||||||
animNow->setStartValue(pNow);
|
|
||||||
animNow->setEndValue(QPoint(pNow.x() - offsetX, pNow.y() - offsetY));
|
|
||||||
|
|
||||||
|
startSlideAnimation(animTargetNow,
|
||||||
QPropertyAnimation *animNext = new QPropertyAnimation(widgetNext, "pos");
|
pNow,
|
||||||
animNext->setDuration(m_speed);
|
QPoint(pNow.x() - offsetX, pNow.y() - offsetY),
|
||||||
animNext->setEasingCurve(m_animationType);
|
animTargetNext,
|
||||||
animNext->setStartValue(widgetNext->pos());
|
QPoint(pNext.x() + offsetX, pNext.y() + offsetY),
|
||||||
animNext->setEndValue(pNext);
|
pNext);
|
||||||
|
|
||||||
m_animGroup->clear();
|
|
||||||
m_animGroup->addAnimation(animNow);
|
|
||||||
m_animGroup->addAnimation(animNext);
|
|
||||||
m_animGroup->start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SlidingStackedWidget::disposeWidgetWhenSafe(QWidget *widget)
|
void SlidingStackedWidget::disposeWidgetWhenSafe(QWidget *widget)
|
||||||
@@ -186,14 +188,124 @@ void SlidingStackedWidget::disposeWidgetWhenSafe(QWidget *widget)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
void SlidingStackedWidget::animationDoneSlot()
|
||||||
{
|
{
|
||||||
|
m_slideTimer.stop();
|
||||||
if (m_snapshotLabel) {
|
m_animWidgetNow.clear();
|
||||||
m_snapshotLabel->hide();
|
m_animWidgetNext.clear();
|
||||||
m_snapshotLabel->deleteLater();
|
clearSnapshotLabels();
|
||||||
m_snapshotLabel = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (m_nextIndex >= 0 && m_nextIndex < count()) {
|
if (m_nextIndex >= 0 && m_nextIndex < count()) {
|
||||||
|
|||||||
@@ -2,11 +2,13 @@
|
|||||||
#define SLIDINGSTACKEDWIDGET_H
|
#define SLIDINGSTACKEDWIDGET_H
|
||||||
|
|
||||||
#include <QStackedWidget>
|
#include <QStackedWidget>
|
||||||
#include <QPropertyAnimation>
|
|
||||||
#include <QParallelAnimationGroup>
|
|
||||||
#include <QEasingCurve>
|
#include <QEasingCurve>
|
||||||
|
#include <QElapsedTimer>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
|
#include <QPoint>
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
|
#include <QRect>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
class QLabel;
|
class QLabel;
|
||||||
|
|
||||||
@@ -42,15 +44,31 @@ private Q_SLOTS:
|
|||||||
void animationDoneSlot();
|
void animationDoneSlot();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
QLabel *createSnapshotLabel(QWidget *source, const QRect &geometry);
|
||||||
|
void clearSnapshotLabels();
|
||||||
|
void startSlideAnimation(QWidget *widgetNow, const QPoint &nowStart,
|
||||||
|
const QPoint &nowEnd, QWidget *widgetNext,
|
||||||
|
const QPoint &nextStart,
|
||||||
|
const QPoint &nextEnd);
|
||||||
|
void updateSlideAnimation();
|
||||||
|
int animationFrameIntervalMs() const;
|
||||||
void flushPendingWidgetDisposals();
|
void flushPendingWidgetDisposals();
|
||||||
|
|
||||||
int m_speed;
|
int m_speed;
|
||||||
QEasingCurve::Type m_animationType;
|
QEasingCurve::Type m_animationType;
|
||||||
QParallelAnimationGroup *m_animGroup;
|
QTimer m_slideTimer;
|
||||||
|
QElapsedTimer m_slideClock;
|
||||||
|
QPointer<QWidget> m_animWidgetNow;
|
||||||
|
QPointer<QWidget> m_animWidgetNext;
|
||||||
|
QPoint m_animNowStart;
|
||||||
|
QPoint m_animNowEnd;
|
||||||
|
QPoint m_animNextStart;
|
||||||
|
QPoint m_animNextEnd;
|
||||||
bool m_isAnimating;
|
bool m_isAnimating;
|
||||||
int m_nextIndex;
|
int m_nextIndex;
|
||||||
QList<QPointer<QWidget>> m_pendingWidgetDisposals;
|
QList<QPointer<QWidget>> m_pendingWidgetDisposals;
|
||||||
QLabel* m_snapshotLabel = nullptr;
|
QLabel* m_currentSnapshotLabel = nullptr;
|
||||||
|
QLabel* m_nextSnapshotLabel = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -27,17 +27,19 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
#if defined(Q_OS_LINUX)
|
#if defined(Q_OS_LINUX)
|
||||||
const QByteArray qpaPlatform = qgetenv("QT_QPA_PLATFORM");
|
const QByteArray qpaPlatform = qgetenv("QT_QPA_PLATFORM");
|
||||||
const QByteArray sessionType = qgetenv("XDG_SESSION_TYPE").toLower();
|
const QByteArray forceXcb = qgetenv("QEMBY_FORCE_XCB").toLower();
|
||||||
if (qpaPlatform.isEmpty() && sessionType == "wayland" &&
|
if (qpaPlatform.isEmpty() &&
|
||||||
!qgetenv("DISPLAY").isEmpty()) {
|
(forceXcb == "1" || forceXcb == "true" || forceXcb == "yes")) {
|
||||||
qputenv("QT_QPA_PLATFORM", "xcb");
|
qputenv("QT_QPA_PLATFORM", "xcb");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
QSurfaceFormat format;
|
QSurfaceFormat format;
|
||||||
|
#if !defined(Q_OS_LINUX)
|
||||||
format.setVersion(3, 3);
|
format.setVersion(3, 3);
|
||||||
format.setProfile(QSurfaceFormat::CoreProfile);
|
format.setProfile(QSurfaceFormat::CoreProfile);
|
||||||
|
#endif
|
||||||
format.setDepthBufferSize(24);
|
format.setDepthBufferSize(24);
|
||||||
format.setStencilBufferSize(8);
|
format.setStencilBufferSize(8);
|
||||||
format.setSwapInterval(1);
|
format.setSwapInterval(1);
|
||||||
|
|||||||
@@ -48,6 +48,36 @@
|
|||||||
#include <widgetframe/windowbar.h>
|
#include <widgetframe/windowbar.h>
|
||||||
#include <widgetframe/windowbutton.h>
|
#include <widgetframe/windowbutton.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
bool canAnimateTopLevelWindowPosition()
|
||||||
|
{
|
||||||
|
#if defined(Q_OS_LINUX)
|
||||||
|
return !QGuiApplication::platformName().contains(QStringLiteral("wayland"),
|
||||||
|
Qt::CaseInsensitive);
|
||||||
|
#else
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
QRect centeredRectWithin(const QRect &sourceGeometry, const QSize &targetSize,
|
||||||
|
const QRect &bounds)
|
||||||
|
{
|
||||||
|
QRect target(QPoint(0, 0), targetSize);
|
||||||
|
target.moveCenter(sourceGeometry.center());
|
||||||
|
|
||||||
|
if (target.left() < bounds.left())
|
||||||
|
target.moveLeft(bounds.left());
|
||||||
|
if (target.top() < bounds.top())
|
||||||
|
target.moveTop(bounds.top());
|
||||||
|
if (target.right() > bounds.right())
|
||||||
|
target.moveRight(bounds.right());
|
||||||
|
if (target.bottom() > bounds.bottom())
|
||||||
|
target.moveBottom(bounds.bottom());
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static inline void emulateLeaveEvent(QWidget *widget) {
|
static inline void emulateLeaveEvent(QWidget *widget) {
|
||||||
Q_ASSERT(widget);
|
Q_ASSERT(widget);
|
||||||
if (!widget) return;
|
if (!widget) return;
|
||||||
@@ -966,6 +996,51 @@ void MainWindow::navigateToHome()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!canAnimateTopLevelWindowPosition()) {
|
||||||
|
hide();
|
||||||
|
setWindowOpacity(0.0);
|
||||||
|
setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
|
||||||
|
setMinimumSize(0, 0);
|
||||||
|
|
||||||
|
const QString windowState = ConfigStore::instance()->get<QString>(
|
||||||
|
ConfigKeys::StartupWindowState, "normal");
|
||||||
|
const bool shouldMaximize = (windowState == "maximized");
|
||||||
|
|
||||||
|
m_loginView->setGraphicsEffect(nullptr);
|
||||||
|
m_homeView->setGraphicsEffect(nullptr);
|
||||||
|
m_viewStack->setCurrentWidget(m_homeView);
|
||||||
|
|
||||||
|
#if !defined(Q_OS_MACOS) && !defined(Q_OS_MAC)
|
||||||
|
if (auto* minBtn = findChild<QWidget*>("min-button")) minBtn->show();
|
||||||
|
if (auto* maxBtn = findChild<QWidget*>("max-button")) maxBtn->show();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (shouldMaximize) {
|
||||||
|
showMaximized();
|
||||||
|
} else {
|
||||||
|
resize(1280, 800);
|
||||||
|
}
|
||||||
|
setMinimumSize(900, 600);
|
||||||
|
show();
|
||||||
|
raise();
|
||||||
|
activateWindow();
|
||||||
|
|
||||||
|
auto *fadeIn = new QPropertyAnimation(this, "windowOpacity", this);
|
||||||
|
fadeIn->setDuration(180);
|
||||||
|
fadeIn->setStartValue(0.0);
|
||||||
|
fadeIn->setEndValue(1.0);
|
||||||
|
fadeIn->setEasingCurve(QEasingCurve::OutCubic);
|
||||||
|
|
||||||
|
connect(fadeIn, &QPropertyAnimation::finished, this, [this]() {
|
||||||
|
setWindowOpacity(1.0);
|
||||||
|
m_viewTransitionInProgress = false;
|
||||||
|
});
|
||||||
|
QTimer::singleShot(0, fadeIn, [fadeIn]() {
|
||||||
|
fadeIn->start(QAbstractAnimation::DeleteWhenStopped);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
auto *loginOpacity = new QGraphicsOpacityEffect(m_loginView);
|
auto *loginOpacity = new QGraphicsOpacityEffect(m_loginView);
|
||||||
loginOpacity->setOpacity(1.0);
|
loginOpacity->setOpacity(1.0);
|
||||||
@@ -979,6 +1054,7 @@ void MainWindow::navigateToHome()
|
|||||||
connect(fadeOut, &QPropertyAnimation::finished, this, [this]() {
|
connect(fadeOut, &QPropertyAnimation::finished, this, [this]() {
|
||||||
|
|
||||||
setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
|
setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
|
||||||
|
setMinimumSize(0, 0);
|
||||||
|
|
||||||
|
|
||||||
QString windowState = ConfigStore::instance()->get<QString>(
|
QString windowState = ConfigStore::instance()->get<QString>(
|
||||||
@@ -994,32 +1070,13 @@ void MainWindow::navigateToHome()
|
|||||||
|
|
||||||
int targetWidth = 1280;
|
int targetWidth = 1280;
|
||||||
int targetHeight = 800;
|
int targetHeight = 800;
|
||||||
targetGeo = QRect(
|
|
||||||
startGeo.x() - (targetWidth - startGeo.width()) / 2,
|
|
||||||
startGeo.y() - (targetHeight - startGeo.height()) / 2,
|
|
||||||
targetWidth, targetHeight);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
QRect screenGeo = screen()->availableGeometry();
|
QRect screenGeo = screen()->availableGeometry();
|
||||||
if (targetGeo.left() < screenGeo.left())
|
targetGeo = centeredRectWithin(startGeo,
|
||||||
targetGeo.moveLeft(screenGeo.left());
|
QSize(targetWidth, targetHeight),
|
||||||
if (targetGeo.top() < screenGeo.top())
|
screenGeo);
|
||||||
targetGeo.moveTop(screenGeo.top());
|
|
||||||
if (targetGeo.right() > screenGeo.right())
|
|
||||||
targetGeo.moveRight(screenGeo.right());
|
|
||||||
if (targetGeo.bottom() > screenGeo.bottom())
|
|
||||||
targetGeo.moveBottom(screenGeo.bottom());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto *geoAnim = new QPropertyAnimation(this, "geometry", this);
|
auto finishWindowTransition = [this, shouldMaximize]() {
|
||||||
|
|
||||||
geoAnim->setDuration(shouldMaximize ? 500 : 350);
|
|
||||||
geoAnim->setStartValue(startGeo);
|
|
||||||
geoAnim->setEndValue(targetGeo);
|
|
||||||
geoAnim->setEasingCurve(QEasingCurve::InOutCubic);
|
|
||||||
|
|
||||||
connect(geoAnim, &QPropertyAnimation::finished, this, [this, shouldMaximize]() {
|
|
||||||
|
|
||||||
setMinimumSize(900, 600);
|
setMinimumSize(900, 600);
|
||||||
|
|
||||||
@@ -1055,7 +1112,16 @@ void MainWindow::navigateToHome()
|
|||||||
m_viewTransitionInProgress = false;
|
m_viewTransitionInProgress = false;
|
||||||
});
|
});
|
||||||
fadeIn->start(QAbstractAnimation::DeleteWhenStopped);
|
fadeIn->start(QAbstractAnimation::DeleteWhenStopped);
|
||||||
});
|
};
|
||||||
|
|
||||||
|
auto *geoAnim = new QPropertyAnimation(this, "geometry", this);
|
||||||
|
|
||||||
|
geoAnim->setDuration(shouldMaximize ? 500 : 350);
|
||||||
|
geoAnim->setStartValue(startGeo);
|
||||||
|
geoAnim->setEndValue(targetGeo);
|
||||||
|
geoAnim->setEasingCurve(QEasingCurve::InOutCubic);
|
||||||
|
connect(geoAnim, &QPropertyAnimation::finished, this,
|
||||||
|
finishWindowTransition);
|
||||||
geoAnim->start(QAbstractAnimation::DeleteWhenStopped);
|
geoAnim->start(QAbstractAnimation::DeleteWhenStopped);
|
||||||
});
|
});
|
||||||
fadeOut->start(QAbstractAnimation::DeleteWhenStopped);
|
fadeOut->start(QAbstractAnimation::DeleteWhenStopped);
|
||||||
@@ -1132,20 +1198,11 @@ void MainWindow::navigateToLogin() {
|
|||||||
QRect startGeo = geometry();
|
QRect startGeo = geometry();
|
||||||
int targetWidth = m_defaultWidth;
|
int targetWidth = m_defaultWidth;
|
||||||
int targetHeight = m_defaultHeight;
|
int targetHeight = m_defaultHeight;
|
||||||
|
QRect targetGeo = centeredRectWithin(
|
||||||
|
startGeo, QSize(targetWidth, targetHeight),
|
||||||
QRect targetGeo(startGeo.x() + (startGeo.width() - targetWidth) / 2,
|
screen()->availableGeometry());
|
||||||
startGeo.y() + (startGeo.height() - targetHeight) / 2,
|
|
||||||
targetWidth, targetHeight);
|
|
||||||
|
|
||||||
auto *geoAnim = new QPropertyAnimation(this, "geometry", this);
|
auto finishWindowTransition = [this]() {
|
||||||
|
|
||||||
geoAnim->setDuration(startGeo.width() > 1300 ? 500 : 350);
|
|
||||||
geoAnim->setStartValue(startGeo);
|
|
||||||
geoAnim->setEndValue(targetGeo);
|
|
||||||
geoAnim->setEasingCurve(QEasingCurve::InOutCubic);
|
|
||||||
|
|
||||||
connect(geoAnim, &QPropertyAnimation::finished, this, [this]() {
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1172,8 +1229,22 @@ void MainWindow::navigateToLogin() {
|
|||||||
m_viewTransitionInProgress = false;
|
m_viewTransitionInProgress = false;
|
||||||
});
|
});
|
||||||
fadeIn->start(QAbstractAnimation::DeleteWhenStopped);
|
fadeIn->start(QAbstractAnimation::DeleteWhenStopped);
|
||||||
});
|
};
|
||||||
geoAnim->start(QAbstractAnimation::DeleteWhenStopped);
|
|
||||||
|
if (canAnimateTopLevelWindowPosition()) {
|
||||||
|
auto *geoAnim = new QPropertyAnimation(this, "geometry", this);
|
||||||
|
|
||||||
|
geoAnim->setDuration(startGeo.width() > 1300 ? 500 : 350);
|
||||||
|
geoAnim->setStartValue(startGeo);
|
||||||
|
geoAnim->setEndValue(targetGeo);
|
||||||
|
geoAnim->setEasingCurve(QEasingCurve::InOutCubic);
|
||||||
|
connect(geoAnim, &QPropertyAnimation::finished, this,
|
||||||
|
finishWindowTransition);
|
||||||
|
geoAnim->start(QAbstractAnimation::DeleteWhenStopped);
|
||||||
|
} else {
|
||||||
|
resize(targetGeo.size());
|
||||||
|
finishWindowTransition();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
fadeOut->start(QAbstractAnimation::DeleteWhenStopped);
|
fadeOut->start(QAbstractAnimation::DeleteWhenStopped);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "smoothscrollcontroller.h"
|
#include "smoothscrollcontroller.h"
|
||||||
|
|
||||||
#include <QScrollBar>
|
#include <QScrollBar>
|
||||||
|
#include <QScreen>
|
||||||
#include <QWheelEvent>
|
#include <QWheelEvent>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
@@ -150,6 +151,27 @@ int SmoothScrollController::wheelDelta(const QWheelEvent *event,
|
|||||||
return orientation == Qt::Horizontal ? angleDelta.x() : angleDelta.y();
|
return orientation == Qt::Horizontal ? angleDelta.x() : angleDelta.y();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int SmoothScrollController::frameIntervalMs() const
|
||||||
|
{
|
||||||
|
if (!m_scrollBar) {
|
||||||
|
return 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QScreen *targetScreen = m_scrollBar->screen();
|
||||||
|
if (!targetScreen && m_scrollBar->window()) {
|
||||||
|
targetScreen = m_scrollBar->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 SmoothScrollController::startTicker()
|
void SmoothScrollController::startTicker()
|
||||||
{
|
{
|
||||||
if (!m_scrollBar || m_scrollBar->value() == m_targetValue) {
|
if (!m_scrollBar || m_scrollBar->value() == m_targetValue) {
|
||||||
@@ -157,6 +179,7 @@ void SmoothScrollController::startTicker()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_ticker.setInterval(frameIntervalMs());
|
||||||
if (!m_ticker.isActive()) {
|
if (!m_ticker.isActive()) {
|
||||||
m_frameClock.restart();
|
m_frameClock.restart();
|
||||||
m_ticker.start();
|
m_ticker.start();
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ private:
|
|||||||
int boundedValue(int value) const;
|
int boundedValue(int value) const;
|
||||||
int wheelDelta(const QWheelEvent *event,
|
int wheelDelta(const QWheelEvent *event,
|
||||||
Qt::Orientation orientation) const;
|
Qt::Orientation orientation) const;
|
||||||
|
int frameIntervalMs() const;
|
||||||
void startTicker();
|
void startTicker();
|
||||||
|
|
||||||
QScrollBar *m_scrollBar = nullptr;
|
QScrollBar *m_scrollBar = nullptr;
|
||||||
|
|||||||
@@ -66,6 +66,9 @@ ConfigStore::ConfigStore(QObject* parent) : QObject(parent) {
|
|||||||
if (!m_settings->contains(ConfigKeys::ImageCacheDuration)) {
|
if (!m_settings->contains(ConfigKeys::ImageCacheDuration)) {
|
||||||
m_settings->setValue(ConfigKeys::ImageCacheDuration, "7");
|
m_settings->setValue(ConfigKeys::ImageCacheDuration, "7");
|
||||||
}
|
}
|
||||||
|
if (!m_settings->contains(ConfigKeys::SnapshotNavigation)) {
|
||||||
|
m_settings->setValue(ConfigKeys::SnapshotNavigation, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigStore::~ConfigStore() {
|
ConfigStore::~ConfigStore() {
|
||||||
|
|||||||
Reference in New Issue
Block a user