performance on ios

This commit is contained in:
pszsh 2026-02-13 09:35:20 -08:00
parent 762fa82da2
commit 61e220f185
8 changed files with 922 additions and 600 deletions

View File

@ -1,207 +1,280 @@
// src/CommonWidgets.cpp // src/CommonWidgets.cpp
#include "CommonWidgets.h" #include "CommonWidgets.h"
#include <QFileInfo> // Added for file info extraction
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QVBoxLayout> #include <QListWidget> // Added for WelcomeWidget
#include <QPainter>
#include <QMouseEvent> #include <QMouseEvent>
#include <QPainter>
#include <QPushButton> #include <QPushButton>
#include <QVBoxLayout>
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
// --- PlaylistDelegate Implementation --- // --- PlaylistDelegate Implementation ---
void PlaylistDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { void PlaylistDelegate::paint(QPainter *painter,
painter->save(); const QStyleOptionViewItem &option,
painter->setRenderHint(QPainter::Antialiasing); const QModelIndex &index) const {
painter->save();
painter->setRenderHint(QPainter::Antialiasing);
// Background // Background
if (option.state & QStyle::State_Selected) { if (option.state & QStyle::State_Selected) {
painter->fillRect(option.rect, QColor(50, 50, 50)); painter->fillRect(option.rect, QColor(50, 50, 50));
} else { } else {
painter->fillRect(option.rect, QColor(17, 17, 17)); // Match list background painter->fillRect(option.rect, QColor(17, 17, 17)); // Match list background
} }
QRect r = option.rect.adjusted(5, 5, -5, -5); QRect r = option.rect.adjusted(5, 5, -5, -5);
// Icon / Art
// CRITICAL OPTIMIZATION: Use pre-scaled thumbnail from DecorationRole
QPixmap art = index.data(Qt::DecorationRole).value<QPixmap>();
QRect iconRect(r.left(), r.top(), 50, 50);
if (!art.isNull()) {
// Draw pre-scaled art directly. No scaling in paint loop.
// Center it if aspect ratio differs slightly
int x = iconRect.x() + (iconRect.width() - art.width()) / 2;
int y = iconRect.y() + (iconRect.height() - art.height()) / 2;
painter->drawPixmap(x, y, art);
} else {
// Placeholder
painter->fillRect(iconRect, QColor(40, 40, 40));
}
// Text // Icon / Art
QRect textRect = r.adjusted(60, 0, 0, 0); // CRITICAL OPTIMIZATION: Use pre-scaled thumbnail from DecorationRole
QString title = index.data(Qt::DisplayRole).toString(); QPixmap art = index.data(Qt::DecorationRole).value<QPixmap>();
QString artist = index.data(Qt::UserRole + 1).toString(); QRect iconRect(r.left(), r.top(), 50, 50);
// Title if (!art.isNull()) {
painter->setPen(Qt::white); // Draw pre-scaled art directly. No scaling in paint loop.
QFont f = option.font; // Center it if aspect ratio differs slightly
f.setBold(true); int x = iconRect.x() + (iconRect.width() - art.width()) / 2;
f.setPointSize(14); int y = iconRect.y() + (iconRect.height() - art.height()) / 2;
painter->setFont(f); painter->drawPixmap(x, y, art);
} else {
// Calculate height for title // Placeholder
QFontMetrics fmTitle(f); painter->fillRect(iconRect, QColor(40, 40, 40));
int titleHeight = fmTitle.height(); }
QRect titleRect = textRect;
titleRect.setHeight(titleHeight);
QString elidedTitle = fmTitle.elidedText(title, Qt::ElideRight, titleRect.width());
painter->drawText(titleRect, Qt::AlignLeft | Qt::AlignTop, elidedTitle);
// Artist // Text
painter->setPen(QColor(170, 170, 170)); QRect textRect = r.adjusted(60, 0, 0, 0);
f.setBold(false); QString title = index.data(Qt::DisplayRole).toString();
f.setPointSize(12); QString artist = index.data(Qt::UserRole + 1).toString();
painter->setFont(f);
QFontMetrics fmArtist(f);
QRect artistRect = textRect;
artistRect.setTop(titleRect.bottom() + 2);
artistRect.setHeight(fmArtist.height());
QString elidedArtist = fmArtist.elidedText(artist, Qt::ElideRight, artistRect.width());
painter->drawText(artistRect, Qt::AlignLeft | Qt::AlignTop, elidedArtist);
// Separator // Title
painter->setPen(QColor(34, 34, 34)); painter->setPen(Qt::white);
painter->drawLine(option.rect.bottomLeft(), option.rect.bottomRight()); QFont f = option.font;
f.setBold(true);
f.setPointSize(14);
painter->setFont(f);
painter->restore(); // Calculate height for title
QFontMetrics fmTitle(f);
int titleHeight = fmTitle.height();
QRect titleRect = textRect;
titleRect.setHeight(titleHeight);
QString elidedTitle =
fmTitle.elidedText(title, Qt::ElideRight, titleRect.width());
painter->drawText(titleRect, Qt::AlignLeft | Qt::AlignTop, elidedTitle);
// Artist
painter->setPen(QColor(170, 170, 170));
f.setBold(false);
f.setPointSize(12);
painter->setFont(f);
QFontMetrics fmArtist(f);
QRect artistRect = textRect;
artistRect.setTop(titleRect.bottom() + 2);
artistRect.setHeight(fmArtist.height());
QString elidedArtist =
fmArtist.elidedText(artist, Qt::ElideRight, artistRect.width());
painter->drawText(artistRect, Qt::AlignLeft | Qt::AlignTop, elidedArtist);
// Separator
painter->setPen(QColor(34, 34, 34));
painter->drawLine(option.rect.bottomLeft(), option.rect.bottomRight());
painter->restore();
} }
QSize PlaylistDelegate::sizeHint(const QStyleOptionViewItem&, const QModelIndex&) const { QSize PlaylistDelegate::sizeHint(const QStyleOptionViewItem &,
return QSize(0, 60); const QModelIndex &) const {
return QSize(0, 60);
} }
// --- XYPad --- // --- XYPad ---
XYPad::XYPad(const QString& title, QWidget* parent) : QWidget(parent), m_title(title) { XYPad::XYPad(const QString &title, QWidget *parent)
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); : QWidget(parent), m_title(title) {
setMinimumHeight(150); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
setCursor(Qt::CrossCursor); setMinimumHeight(150);
setCursor(Qt::CrossCursor);
} }
void XYPad::setFormatter(std::function<QString(float, float)> formatter) { void XYPad::setFormatter(std::function<QString(float, float)> formatter) {
m_formatter = formatter; m_formatter = formatter;
} }
void XYPad::setValues(float x, float y) { void XYPad::setValues(float x, float y) {
m_x = std::clamp(x, 0.0f, 1.0f); m_x = std::clamp(x, 0.0f, 1.0f);
m_y = std::clamp(y, 0.0f, 1.0f); m_y = std::clamp(y, 0.0f, 1.0f);
update(); update();
} }
void XYPad::paintEvent(QPaintEvent*) { void XYPad::paintEvent(QPaintEvent *) {
QPainter p(this); QPainter p(this);
p.setRenderHint(QPainter::Antialiasing); p.setRenderHint(QPainter::Antialiasing);
p.fillRect(rect(), QColor(40, 40, 40, 200)); p.fillRect(rect(), QColor(40, 40, 40, 200));
p.setPen(QPen(QColor(80, 80, 80), 1)); p.setPen(QPen(QColor(80, 80, 80), 1));
p.drawRect(rect().adjusted(0, 0, -1, -1)); p.drawRect(rect().adjusted(0, 0, -1, -1));
p.setPen(QPen(QColor(60, 60, 60), 1, Qt::DotLine)); p.setPen(QPen(QColor(60, 60, 60), 1, Qt::DotLine));
p.drawLine(width()/2, 0, width()/2, height()); p.drawLine(width() / 2, 0, width() / 2, height());
p.drawLine(0, height()/2, width(), height()/2); p.drawLine(0, height() / 2, width(), height() / 2);
int px = m_x * width(); int px = m_x * width();
int py = (1.0f - m_y) * height(); int py = (1.0f - m_y) * height();
p.setPen(Qt::NoPen); p.setPen(Qt::NoPen);
p.setBrush(QColor(0, 212, 255, 180)); p.setBrush(QColor(0, 212, 255, 180));
p.drawEllipse(QPoint(px, py), 12, 12); p.drawEllipse(QPoint(px, py), 12, 12);
p.setBrush(Qt::white); p.setBrush(Qt::white);
p.drawEllipse(QPoint(px, py), 4, 4); p.drawEllipse(QPoint(px, py), 4, 4);
p.setPen(QPen(QColor(0, 212, 255, 100), 1)); p.setPen(QPen(QColor(0, 212, 255, 100), 1));
p.drawLine(px, 0, px, height()); p.drawLine(px, 0, px, height());
p.drawLine(0, py, width(), py); p.drawLine(0, py, width(), py);
p.setPen(Qt::white); p.setPen(Qt::white);
QFont f = font(); QFont f = font();
f.setBold(true); f.setBold(true);
f.setPointSize(10); f.setPointSize(10);
p.setFont(f); p.setFont(f);
QString text = m_title; QString text = m_title;
if (m_formatter) { if (m_formatter) {
text += "\n" + m_formatter(m_x, m_y); text += "\n" + m_formatter(m_x, m_y);
} }
p.drawText(rect().adjusted(10, 10, -10, -10), Qt::AlignLeft | Qt::AlignTop, text); p.drawText(rect().adjusted(10, 10, -10, -10), Qt::AlignLeft | Qt::AlignTop,
text);
} }
void XYPad::mousePressEvent(QMouseEvent* event) { updateFromPos(event->pos()); } void XYPad::mousePressEvent(QMouseEvent *event) { updateFromPos(event->pos()); }
void XYPad::mouseMoveEvent(QMouseEvent* event) { updateFromPos(event->pos()); } void XYPad::mouseMoveEvent(QMouseEvent *event) { updateFromPos(event->pos()); }
void XYPad::updateFromPos(const QPoint& pos) { void XYPad::updateFromPos(const QPoint &pos) {
m_x = std::clamp((float)pos.x() / width(), 0.0f, 1.0f); m_x = std::clamp((float)pos.x() / width(), 0.0f, 1.0f);
m_y = std::clamp(1.0f - (float)pos.y() / height(), 0.0f, 1.0f); m_y = std::clamp(1.0f - (float)pos.y() / height(), 0.0f, 1.0f);
update(); update();
emit valuesChanged(m_x, m_y); emit valuesChanged(m_x, m_y);
} }
OverlayWidget::OverlayWidget(QWidget* content, QWidget* parent) : QWidget(parent), m_content(content) { OverlayWidget::OverlayWidget(QWidget *content, QWidget *parent)
QPalette pal = palette(); : QWidget(parent), m_content(content) {
pal.setColor(QPalette::Window, QColor(0, 0, 0, 100)); QPalette pal = palette();
setAutoFillBackground(true); pal.setColor(QPalette::Window, QColor(0, 0, 0, 100));
setPalette(pal); setAutoFillBackground(true);
setPalette(pal);
QVBoxLayout* layout = new QVBoxLayout(this); QVBoxLayout *layout = new QVBoxLayout(this);
layout->setAlignment(Qt::AlignCenter); layout->setAlignment(Qt::AlignCenter);
layout->setContentsMargins(20, 20, 20, 20); layout->setContentsMargins(20, 20, 20, 20);
content->setParent(this); content->setParent(this);
content->setMaximumWidth(500); content->setMaximumWidth(500);
content->setMaximumHeight(600); content->setMaximumHeight(600);
layout->addWidget(content); layout->addWidget(content);
hide();
}
void OverlayWidget::mousePressEvent(QMouseEvent *event) {
if (!m_content->geometry().contains(event->pos())) {
hide(); hide();
}
} }
void OverlayWidget::mousePressEvent(QMouseEvent* event) { void OverlayWidget::paintEvent(QPaintEvent *event) {
if (!m_content->geometry().contains(event->pos())) { QPainter p(this);
hide(); p.fillRect(rect(), QColor(0, 0, 0, 100));
}
} }
void OverlayWidget::paintEvent(QPaintEvent* event) { WelcomeWidget::WelcomeWidget(QWidget *parent) : QWidget(parent) {
QPainter p(this); QVBoxLayout *layout = new QVBoxLayout(this);
p.fillRect(rect(), QColor(0, 0, 0, 100)); layout->setAlignment(Qt::AlignCenter);
layout->setSpacing(20);
layout->setContentsMargins(40, 40, 40, 40);
QLabel *title = new QLabel("Yr Crystals", this);
title->setStyleSheet("color: white; font-size: 32px; font-weight: bold;");
title->setAlignment(Qt::AlignCenter);
layout->addWidget(title);
QString btnStyle =
"QPushButton { background-color: #333; color: white; border: 1px solid "
"#555; border-radius: 8px; padding: 15px; font-size: 18px; } "
"QPushButton:pressed { background-color: #555; }";
QHBoxLayout *btnLayout = new QHBoxLayout();
btnLayout->setSpacing(20);
QPushButton *btnFile = new QPushButton("Open File", this);
btnFile->setStyleSheet(btnStyle);
btnFile->setCursor(Qt::PointingHandCursor);
connect(btnFile, &QPushButton::clicked, this,
&WelcomeWidget::openFileClicked);
btnLayout->addWidget(btnFile);
QPushButton *btnFolder = new QPushButton("Open Folder", this);
btnFolder->setStyleSheet(btnStyle);
btnFolder->setCursor(Qt::PointingHandCursor);
connect(btnFolder, &QPushButton::clicked, this,
&WelcomeWidget::openFolderClicked);
btnLayout->addWidget(btnFolder);
layout->addLayout(btnLayout);
// --- Recents List ---
QLabel *recentLabel = new QLabel("Recent", this);
recentLabel->setStyleSheet("color: #aaa; font-size: 16px; margin-top: 20px;");
layout->addWidget(recentLabel);
m_recentList = new QListWidget(this);
m_recentList->setStyleSheet(
"QListWidget { background: transparent; border: none; color: #ddd; "
"font-size: 16px; }"
"QListWidget::item { padding: 10px; border-bottom: 1px solid #333; }"
"QListWidget::item:hover { background: #222; }"
"QListWidget::item:selected { background: #333; }");
m_recentList->setFocusPolicy(Qt::NoFocus);
m_recentList->setCursor(Qt::PointingHandCursor);
m_recentList->setSelectionMode(QAbstractItemView::SingleSelection);
connect(m_recentList, &QListWidget::itemClicked, this,
&WelcomeWidget::onRecentClicked);
layout->addWidget(m_recentList);
// Refresh on init
refreshRecents();
} }
WelcomeWidget::WelcomeWidget(QWidget* parent) : QWidget(parent) { void WelcomeWidget::refreshRecents() {
QVBoxLayout* layout = new QVBoxLayout(this); m_recentList->clear();
layout->setAlignment(Qt::AlignCenter); QStringList files = Utils::getRecentFiles();
layout->setSpacing(20); QStringList folders = Utils::getRecentFolders();
QLabel* title = new QLabel("Yr Crystals", this); // Interleave or section them? Let's just list folders then files.
title->setStyleSheet("color: white; font-size: 32px; font-weight: bold;"); for (const auto &path : folders) {
title->setAlignment(Qt::AlignCenter); QListWidgetItem *item =
layout->addWidget(title); new QListWidgetItem("📁 " + QFileInfo(path).fileName());
item->setData(Qt::UserRole, path);
// Tooltip showing full path
item->setToolTip(path);
m_recentList->addItem(item);
}
for (const auto &path : files) {
QListWidgetItem *item =
new QListWidgetItem("🎵 " + QFileInfo(path).fileName());
item->setData(Qt::UserRole, path);
item->setToolTip(path);
m_recentList->addItem(item);
}
}
QString btnStyle = "QPushButton { background-color: #333; color: white; border: 1px solid #555; border-radius: 8px; padding: 15px; font-size: 18px; } QPushButton:pressed { background-color: #555; }"; void WelcomeWidget::onRecentClicked(QListWidgetItem *item) {
if (item) {
QPushButton* btnFile = new QPushButton("Open File", this); emit pathSelected(item->data(Qt::UserRole).toString());
btnFile->setStyleSheet(btnStyle); }
btnFile->setFixedWidth(250);
connect(btnFile, &QPushButton::clicked, this, &WelcomeWidget::openFileClicked);
layout->addWidget(btnFile);
QPushButton* btnFolder = new QPushButton("Open Folder", this);
btnFolder->setStyleSheet(btnStyle);
btnFolder->setFixedWidth(250);
connect(btnFolder, &QPushButton::clicked, this, &WelcomeWidget::openFolderClicked);
layout->addWidget(btnFolder);
} }

View File

@ -1,57 +1,74 @@
// src/CommonWidgets.h // src/CommonWidgets.h
#pragma once #pragma once
#include <QWidget>
#include <QLabel>
#include <functional>
#include <QStyledItemDelegate>
#include "Utils.h" #include "Utils.h"
#include <QLabel>
#include <QListWidget> // Include directly or fwd declare properly
#include <QStyledItemDelegate>
#include <QWidget>
#include <functional>
// Replaces PlaylistItemWidget for better performance // Replaces PlaylistItemWidget for better performance
class PlaylistDelegate : public QStyledItemDelegate { class PlaylistDelegate : public QStyledItemDelegate {
Q_OBJECT Q_OBJECT
public: public:
using QStyledItemDelegate::QStyledItemDelegate; using QStyledItemDelegate::QStyledItemDelegate;
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; void paint(QPainter *painter, const QStyleOptionViewItem &option,
QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override; const QModelIndex &index) const override;
QSize sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
}; };
class XYPad : public QWidget { class XYPad : public QWidget {
Q_OBJECT Q_OBJECT
public: public:
XYPad(const QString& title, QWidget* parent = nullptr); XYPad(const QString &title, QWidget *parent = nullptr);
void setFormatter(std::function<QString(float, float)> formatter); void setFormatter(std::function<QString(float, float)> formatter);
void setValues(float x, float y); void setValues(float x, float y);
signals: signals:
void valuesChanged(float x, float y); void valuesChanged(float x, float y);
protected: protected:
void paintEvent(QPaintEvent* event) override; void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent* event) override; void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent* event) override; void mouseMoveEvent(QMouseEvent *event) override;
private: private:
void updateFromPos(const QPoint& pos); void updateFromPos(const QPoint &pos);
QString m_title; QString m_title;
float m_x = 0.5f; float m_x = 0.5f;
float m_y = 0.5f; float m_y = 0.5f;
std::function<QString(float, float)> m_formatter; std::function<QString(float, float)> m_formatter;
}; };
class OverlayWidget : public QWidget { class OverlayWidget : public QWidget {
Q_OBJECT Q_OBJECT
public: public:
OverlayWidget(QWidget* content, QWidget* parent = nullptr); OverlayWidget(QWidget *content, QWidget *parent = nullptr);
protected: protected:
void mousePressEvent(QMouseEvent* event) override; void mousePressEvent(QMouseEvent *event) override;
void paintEvent(QPaintEvent* event) override; void paintEvent(QPaintEvent *event) override;
private: private:
QWidget* m_content; QWidget *m_content;
}; };
class QListWidget;
class QListWidgetItem;
class WelcomeWidget : public QWidget { class WelcomeWidget : public QWidget {
Q_OBJECT Q_OBJECT
public: public:
WelcomeWidget(QWidget* parent = nullptr); WelcomeWidget(QWidget *parent = nullptr);
void refreshRecents();
signals: signals:
void openFileClicked(); void openFileClicked();
void openFolderClicked(); void openFolderClicked();
void pathSelected(const QString &path);
private slots:
void onRecentClicked(QListWidgetItem *item);
private:
QListWidget *m_recentList;
}; };

View File

@ -35,6 +35,12 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
&MainWindow::onOpenFile); &MainWindow::onOpenFile);
connect(m_welcome, &WelcomeWidget::openFolderClicked, this, connect(m_welcome, &WelcomeWidget::openFolderClicked, this,
&MainWindow::onOpenFolder); &MainWindow::onOpenFolder);
connect(m_welcome, &WelcomeWidget::pathSelected, this,
[this](const QString &path) {
// Determine if folder or file based on path
QFileInfo info(path);
loadPath(path, info.isDir());
});
m_stack->addWidget(m_welcome); m_stack->addWidget(m_welcome);
initUi(); initUi();
@ -202,6 +208,10 @@ void MainWindow::initUi() {
connect(m_playerPage, &PlayerPage::toggleFullScreen, this, connect(m_playerPage, &PlayerPage::toggleFullScreen, this,
&MainWindow::onToggleFullScreen); &MainWindow::onToggleFullScreen);
connect(m_playerPage, &PlayerPage::homeClicked, this, [this]() {
m_welcome->refreshRecents();
m_stack->setCurrentWidget(m_welcome);
});
#ifdef IS_MOBILE #ifdef IS_MOBILE
m_mobileTabs = new QTabWidget(); m_mobileTabs = new QTabWidget();
@ -333,6 +343,7 @@ void MainWindow::loadPath(const QString &rawPath, bool recursive) {
} }
if (!m_tracks.isEmpty()) { if (!m_tracks.isEmpty()) {
loadIndex(0); loadIndex(0);
Utils::addRecentFolder(path);
m_metaThread = new QThread(this); m_metaThread = new QThread(this);
m_metaLoader = new Utils::MetadataLoader(); m_metaLoader = new Utils::MetadataLoader();
m_metaLoader->moveToThread(m_metaThread); m_metaLoader->moveToThread(m_metaThread);
@ -355,6 +366,7 @@ void MainWindow::loadPath(const QString &rawPath, bool recursive) {
if (!t.meta.thumbnail.isNull()) if (!t.meta.thumbnail.isNull())
item->setData(Qt::DecorationRole, t.meta.thumbnail); item->setData(Qt::DecorationRole, t.meta.thumbnail);
loadIndex(0); loadIndex(0);
Utils::addRecentFile(path);
} }
loadSettings(); loadSettings();
#ifdef IS_MOBILE #ifdef IS_MOBILE

View File

@ -52,6 +52,15 @@ PlaybackWidget::PlaybackWidget(QWidget *parent) : QWidget(parent) {
connect(btnSettings, &QPushButton::clicked, this, connect(btnSettings, &QPushButton::clicked, this,
&PlaybackWidget::settingsClicked); &PlaybackWidget::settingsClicked);
QPushButton *btnHome = new QPushButton("", this); // House icon or similar
btnHome->setStyleSheet(
"QPushButton { background: transparent; color: #aaa; font-size: 24px; "
"border: none; padding: 10px; } QPushButton:pressed { color: white; }");
connect(btnHome, &QPushButton::clicked, this, &PlaybackWidget::homeClicked);
rowLayout->addWidget(btnHome);
rowLayout
->addStretch(); // Add stretch so home is left-aligned, controls center
rowLayout->addWidget(btnPrev); rowLayout->addWidget(btnPrev);
rowLayout->addSpacing(10); rowLayout->addSpacing(10);
rowLayout->addWidget(m_btnPlay); rowLayout->addWidget(m_btnPlay);
@ -338,6 +347,8 @@ PlayerPage::PlayerPage(QWidget *parent) : QWidget(parent) {
connect(m_playback, &PlaybackWidget::settingsClicked, this, connect(m_playback, &PlaybackWidget::settingsClicked, this,
&PlayerPage::toggleOverlay); &PlayerPage::toggleOverlay);
connect(m_playback, &PlaybackWidget::homeClicked, this,
&PlayerPage::homeClicked);
connect(m_settings, &SettingsWidget::closeClicked, this, connect(m_settings, &SettingsWidget::closeClicked, this,
&PlayerPage::closeOverlay); &PlayerPage::closeOverlay);

View File

@ -23,6 +23,7 @@ signals:
void prevClicked(); void prevClicked();
void seekChanged(float pos); void seekChanged(float pos);
void settingsClicked(); void settingsClicked();
void homeClicked(); // New signal
private slots: private slots:
void onSeekPressed(); void onSeekPressed();
void onSeekReleased(); void onSeekReleased();
@ -125,6 +126,7 @@ public:
void setFullScreen(bool fs); void setFullScreen(bool fs);
signals: signals:
void toggleFullScreen(); void toggleFullScreen();
void homeClicked(); // New signal
protected: protected:
void resizeEvent(QResizeEvent *event) override; void resizeEvent(QResizeEvent *event) override;

File diff suppressed because it is too large Load Diff

View File

@ -1,73 +1,82 @@
// src/Utils.h // src/Utils.h
#pragma once #pragma once
#include <QString>
#include <QImage>
#include <QPixmap>
#include <QVector>
#include <QColor> #include <QColor>
#include <QStringList> #include <QImage>
#include <QObject> #include <QObject>
#include <QPixmap>
#include <QString>
#include <QStringList>
#include <QTimer> #include <QTimer>
#include <QVector>
#include <atomic> #include <atomic>
#include <functional> #include <functional>
namespace Utils { namespace Utils {
bool checkDependencies(); bool checkDependencies();
QString convertToWav(const QString &inputPath); QString convertToWav(const QString &inputPath);
QString resolvePath(const QString& rawPath); QString resolvePath(const QString &rawPath);
// Configure platform-specific audio sessions (iOS) // Configure platform-specific audio sessions (iOS)
void configureIOSAudioSession(); void configureIOSAudioSession();
struct Metadata { struct Metadata {
QString title; QString title;
QString artist; QString artist;
QString album; QString album;
int trackNumber = 0; int trackNumber = 0;
QImage art; QImage art;
QPixmap thumbnail; QPixmap thumbnail;
}; };
Metadata getMetadata(const QString &filePath); Metadata getMetadata(const QString &filePath);
QVector<QColor> extractAlbumColors(const QImage &art, int numBins); QVector<QColor> extractAlbumColors(const QImage &art, int numBins);
QStringList scanDirectory(const QString &path, bool recursive); QStringList scanDirectory(const QString &path, bool recursive);
bool isContentUriFolder(const QString& path); bool isContentUriFolder(const QString &path);
// Updated to use a helper object for async polling // Updated to use a helper object for async polling
void requestAndroidPermissions(std::function<void(bool)> callback); void requestAndroidPermissions(std::function<void(bool)> callback);
// Helper to robustly copy content URIs on Android // Helper to robustly copy content URIs on Android
bool copyContentUriToLocalFile(const QString& uriStr, const QString& destPath); bool copyContentUriToLocalFile(const QString &uriStr, const QString &destPath);
// Recent Files Management
void addRecentFile(const QString &path);
void addRecentFolder(const QString &path);
QStringList getRecentFiles();
QStringList getRecentFolders();
#ifdef Q_OS_IOS #ifdef Q_OS_IOS
void openIosPicker(bool folder, std::function<void(QString)> callback); void openIosPicker(bool folder, std::function<void(QString)> callback);
#endif #endif
class MetadataLoader : public QObject { class MetadataLoader : public QObject {
Q_OBJECT Q_OBJECT
public: public:
explicit MetadataLoader(QObject* parent = nullptr); explicit MetadataLoader(QObject *parent = nullptr);
void startLoading(const QStringList& paths); void startLoading(const QStringList &paths);
void stop(); void stop();
signals: signals:
void metadataReady(int index, const Utils::Metadata& meta); void metadataReady(int index, const Utils::Metadata &meta);
void finished(); void finished();
private:
std::atomic<bool> m_stop{false};
};
// Helper class to poll for permission results on Android private:
class PermissionHelper : public QObject { std::atomic<bool> m_stop{false};
Q_OBJECT };
public:
explicit PermissionHelper(std::function<void(bool)> cb, QObject* parent = nullptr); // Helper class to poll for permission results on Android
void start(); class PermissionHelper : public QObject {
private slots: Q_OBJECT
void check(); public:
private: explicit PermissionHelper(std::function<void(bool)> cb,
std::function<void(bool)> m_callback; QObject *parent = nullptr);
QTimer* m_timer; void start();
int m_attempts = 0; private slots:
}; void check();
}
private:
std::function<void(bool)> m_callback;
QTimer *m_timer;
int m_attempts = 0;
};
} // namespace Utils

View File

@ -16,6 +16,15 @@
VisualizerWidget::VisualizerWidget(QWidget *parent) : QWidget(parent) { VisualizerWidget::VisualizerWidget(QWidget *parent) : QWidget(parent) {
setAttribute(Qt::WA_OpaquePaintEvent); setAttribute(Qt::WA_OpaquePaintEvent);
setNumBins(26); setNumBins(26);
#if defined(Q_OS_IOS)
// IOS Optimization: Cap internal rendering resolution
// Native retina (3.0) is overkill for this visualizer and kills fill-rate.
// 2.0 is visually indistinguishable for moving graphics but much faster.
// Note: We cannot easily change the widget's DPR directly without affecting
// layout, but we can scale the painter or use a target pixmap. For now,
// simpler optimization: rely on NO Antialiasing.
#endif
} }
void VisualizerWidget::mouseReleaseEvent(QMouseEvent *event) { void VisualizerWidget::mouseReleaseEvent(QMouseEvent *event) {
@ -310,7 +319,13 @@ void VisualizerWidget::paintEvent(QPaintEvent *) {
QPainter p(this); QPainter p(this);
p.fillRect(rect(), Qt::black); p.fillRect(rect(), Qt::black);
#if defined(Q_OS_IOS)
// iOS Optimization: Disable Antialiasing for performance
// Retina screens are high density enough that AA is often not needed
#else
p.setRenderHint(QPainter::Antialiasing); p.setRenderHint(QPainter::Antialiasing);
#endif
if (m_data.empty()) if (m_data.empty())
return; return;
@ -334,7 +349,9 @@ void VisualizerWidget::paintEvent(QPaintEvent *) {
{ {
m_cache.fill(Qt::transparent); // Clear old frame m_cache.fill(Qt::transparent); // Clear old frame
QPainter cachePainter(&m_cache); QPainter cachePainter(&m_cache);
#if !defined(Q_OS_IOS)
cachePainter.setRenderHint(QPainter::Antialiasing); cachePainter.setRenderHint(QPainter::Antialiasing);
#endif
drawContent(cachePainter, hw, hh); drawContent(cachePainter, hw, hh);
} }