Batch of performance and animation work: shared FadeHelper/MotionPrefs primitives, hover animators, shadow pixmap caching, smooth scrolling, seek time bubble, and widespread component repaint reductions.
285 lines
7.5 KiB
C++
285 lines
7.5 KiB
C++
#include "settingsview.h"
|
|
#include "../../tokens/AnimationTokens.h"
|
|
#include "../../utils/motionprefs.h"
|
|
#include "../../components/slidingstackedwidget.h"
|
|
#include "../../managers/thememanager.h"
|
|
#include "pageabout.h"
|
|
#include "pageappearance.h"
|
|
#include "pagegeneral.h"
|
|
#include "pageplayer.h"
|
|
#include "pagelibrary.h"
|
|
#include <QApplication>
|
|
#include <QEvent>
|
|
#include <QHBoxLayout>
|
|
#include <QScrollArea>
|
|
#include <QScrollBar>
|
|
#include <QVBoxLayout>
|
|
#include <QWheelEvent>
|
|
|
|
|
|
SettingsView::SettingsView(QEmbyCore *core, QWidget *parent)
|
|
: BaseView(core, parent) {
|
|
|
|
setAttribute(Qt::WA_StyledBackground, true);
|
|
setObjectName("SettingsRootView");
|
|
|
|
setupUi();
|
|
setupConnections();
|
|
|
|
|
|
|
|
m_navMenu->setCurrentRow(0);
|
|
}
|
|
|
|
void SettingsView::setupUi() {
|
|
auto *mainLayout = new QHBoxLayout(this);
|
|
|
|
mainLayout->setContentsMargins(0, 0, 0, 0);
|
|
mainLayout->setSpacing(0);
|
|
|
|
|
|
m_leftPanel = new QWidget(this);
|
|
m_leftPanel->setFixedWidth(260);
|
|
m_leftPanel->setObjectName("SettingsLeftPanel");
|
|
auto *leftLayout = new QVBoxLayout(m_leftPanel);
|
|
|
|
leftLayout->setContentsMargins(32, 32, 32, 32);
|
|
leftLayout->setSpacing(24);
|
|
|
|
|
|
m_titleLabel = new QLabel(tr("Settings"), m_leftPanel);
|
|
m_titleLabel->setObjectName("SettingsMainTitle");
|
|
|
|
|
|
m_navMenu = new QListWidget(m_leftPanel);
|
|
m_navMenu->setObjectName("SettingsNavMenu");
|
|
m_navMenu->setFocusPolicy(Qt::NoFocus);
|
|
|
|
m_navMenu->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
m_navMenu->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
|
|
|
|
auto *itemGeneral = new QListWidgetItem(
|
|
ThemeManager::getAdaptiveIcon(":/svg/dark/general.svg"), tr(" General"));
|
|
itemGeneral->setData(Qt::UserRole, ":/svg/dark/general.svg");
|
|
|
|
auto *itemAppearance = new QListWidgetItem(
|
|
ThemeManager::getAdaptiveIcon(":/svg/dark/appearance.svg"),
|
|
tr(" Appearance"));
|
|
itemAppearance->setData(Qt::UserRole, ":/svg/dark/appearance.svg");
|
|
|
|
auto *itemPlayer = new QListWidgetItem(
|
|
ThemeManager::getAdaptiveIcon(":/svg/dark/player.svg"), tr(" Player"));
|
|
itemPlayer->setData(Qt::UserRole, ":/svg/dark/player.svg");
|
|
|
|
auto *itemLibrary = new QListWidgetItem(
|
|
ThemeManager::getAdaptiveIcon(":/svg/dark/library.svg"), tr(" Library"));
|
|
itemLibrary->setData(Qt::UserRole, ":/svg/dark/library.svg");
|
|
|
|
auto *itemAbout = new QListWidgetItem(
|
|
ThemeManager::getAdaptiveIcon(":/svg/dark/about.svg"), tr(" About"));
|
|
itemAbout->setData(Qt::UserRole, ":/svg/dark/about.svg");
|
|
|
|
|
|
itemGeneral->setSizeHint(QSize(220, 44));
|
|
itemAppearance->setSizeHint(QSize(220, 44));
|
|
itemPlayer->setSizeHint(QSize(220, 44));
|
|
itemLibrary->setSizeHint(QSize(220, 44));
|
|
itemAbout->setSizeHint(QSize(220, 44));
|
|
|
|
m_navMenu->addItem(itemGeneral);
|
|
m_navMenu->addItem(itemAppearance);
|
|
m_navMenu->addItem(itemLibrary);
|
|
m_navMenu->addItem(itemPlayer);
|
|
m_navMenu->addItem(itemAbout);
|
|
|
|
leftLayout->addWidget(m_titleLabel);
|
|
leftLayout->addWidget(m_navMenu);
|
|
|
|
|
|
m_stack = new SlidingStackedWidget(this);
|
|
m_stack->setObjectName("SettingsStack");
|
|
|
|
|
|
|
|
|
|
|
|
const int kPageCount = 5;
|
|
m_scrollAreas.reserve(kPageCount);
|
|
m_scrollAnims.reserve(kPageCount);
|
|
m_scrollTargets.reserve(kPageCount);
|
|
m_pages.reserve(kPageCount);
|
|
for (int i = 0; i < kPageCount; ++i) {
|
|
auto *placeholder = new QWidget(m_stack);
|
|
placeholder->setAttribute(Qt::WA_StyledBackground, true);
|
|
placeholder->setObjectName("SettingsPagePlaceholder");
|
|
m_stack->addWidget(placeholder);
|
|
|
|
m_scrollAreas.append(nullptr);
|
|
m_scrollAnims.append(nullptr);
|
|
m_scrollTargets.append(0);
|
|
m_pages.append(QPointer<QWidget>());
|
|
}
|
|
|
|
mainLayout->addWidget(m_leftPanel);
|
|
mainLayout->addWidget(m_stack, 1);
|
|
|
|
|
|
qApp->postEvent(this, new QEvent(QEvent::StyleChange));
|
|
}
|
|
|
|
QScrollArea *SettingsView::wrapInScrollArea(QWidget *page, int row) {
|
|
|
|
page->setAttribute(Qt::WA_StyledBackground, true);
|
|
|
|
auto *scroll = new QScrollArea(m_stack);
|
|
scroll->setObjectName("SettingsScrollArea");
|
|
scroll->setWidget(page);
|
|
scroll->setWidgetResizable(true);
|
|
scroll->setFrameShape(QFrame::NoFrame);
|
|
|
|
|
|
scroll->viewport()->setAutoFillBackground(false);
|
|
|
|
|
|
scroll->viewport()->installEventFilter(this);
|
|
|
|
|
|
if (row >= 0 && row < m_scrollAreas.size()) {
|
|
m_scrollAreas[row] = scroll;
|
|
|
|
auto *anim =
|
|
new QPropertyAnimation(scroll->verticalScrollBar(), "value", this);
|
|
anim->setEasingCurve(AnimationTokens::Curve::enter());
|
|
anim->setDuration(AnimationTokens::DurationMs::kSlow);
|
|
m_scrollAnims[row] = anim;
|
|
m_scrollTargets[row] = 0;
|
|
}
|
|
|
|
return scroll;
|
|
}
|
|
|
|
void SettingsView::ensurePageAt(int row) {
|
|
if (row < 0 || row >= m_pages.size()) {
|
|
return;
|
|
}
|
|
if (m_pages[row]) {
|
|
|
|
return;
|
|
}
|
|
|
|
qDebug() << "[SettingsView] Lazy instantiate page row=" << row;
|
|
|
|
QWidget *page = nullptr;
|
|
switch (row) {
|
|
case 0:
|
|
page = new PageGeneral(m_core, m_stack);
|
|
break;
|
|
case 1:
|
|
page = new PageAppearance(m_core, m_stack);
|
|
break;
|
|
case 2:
|
|
page = new PageLibrary(m_core, m_stack);
|
|
break;
|
|
case 3:
|
|
page = new PagePlayer(m_core, m_stack);
|
|
break;
|
|
case 4:
|
|
page = new PageAbout(m_core, m_stack);
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
QScrollArea *scroll = wrapInScrollArea(page, row);
|
|
|
|
|
|
|
|
QWidget *placeholder = m_stack->widget(row);
|
|
const bool wasBlocked = m_stack->blockSignals(true);
|
|
m_stack->insertWidget(row, scroll);
|
|
m_stack->removeWidget(placeholder);
|
|
m_stack->blockSignals(wasBlocked);
|
|
m_stack->disposeWidgetWhenSafe(placeholder);
|
|
|
|
m_pages[row] = scroll;
|
|
}
|
|
|
|
void SettingsView::setupConnections() {
|
|
|
|
connect(m_navMenu, &QListWidget::currentRowChanged, this, [this](int row) {
|
|
if (row >= 0) {
|
|
ensurePageAt(row);
|
|
// Vertical slide following the nav list's direction of travel.
|
|
m_stack->slideInIdx(row, row > m_stack->currentIndex()
|
|
? SlidingStackedWidget::BottomToTop
|
|
: SlidingStackedWidget::TopToBottom);
|
|
}
|
|
});
|
|
|
|
|
|
connect(ThemeManager::instance(), &ThemeManager::themeChanged, this,
|
|
&SettingsView::onThemeChanged);
|
|
}
|
|
|
|
|
|
void SettingsView::onThemeChanged() {
|
|
for (int i = 0; i < m_navMenu->count(); ++i) {
|
|
auto *item = m_navMenu->item(i);
|
|
|
|
QString svgPath = item->data(Qt::UserRole).toString();
|
|
if (!svgPath.isEmpty()) {
|
|
|
|
item->setIcon(ThemeManager::getAdaptiveIcon(svgPath));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SettingsView::eventFilter(QObject *obj, QEvent *event) {
|
|
if (event->type() == QEvent::Wheel) {
|
|
|
|
for (int i = 0; i < m_scrollAreas.size(); ++i) {
|
|
|
|
if (!m_scrollAreas[i]) {
|
|
continue;
|
|
}
|
|
if (obj == m_scrollAreas[i]->viewport()) {
|
|
auto *we = static_cast<QWheelEvent *>(event);
|
|
auto *vBar = m_scrollAreas[i]->verticalScrollBar();
|
|
auto *anim = m_scrollAnims[i];
|
|
|
|
if (vBar && anim) {
|
|
int currentVal = vBar->value();
|
|
|
|
|
|
if (anim->state() == QAbstractAnimation::Running) {
|
|
currentVal = m_scrollTargets[i];
|
|
}
|
|
|
|
|
|
int step = we->angleDelta().y();
|
|
int newTarget = currentVal - step;
|
|
|
|
|
|
newTarget = qBound(vBar->minimum(), newTarget, vBar->maximum());
|
|
|
|
if (newTarget != vBar->value()) {
|
|
m_scrollTargets[i] = newTarget;
|
|
anim->stop();
|
|
if (MotionPrefs::reduceMotion()) {
|
|
vBar->setValue(newTarget);
|
|
} else {
|
|
anim->setStartValue(vBar->value());
|
|
anim->setEndValue(newTarget);
|
|
anim->start();
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return QWidget::eventFilter(obj, event);
|
|
}
|