Files
qemby/src/qEmbyApp/views/settings/settingsview.cpp
T
dela ab1accc821 perf: smooth UI animations and reduce repaint overhead
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.
2026-07-08 11:29:38 +08:00

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);
}