performance on ios
This commit is contained in:
parent
762fa82da2
commit
61e220f185
|
|
@ -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
|
// Icon / Art
|
||||||
// CRITICAL OPTIMIZATION: Use pre-scaled thumbnail from DecorationRole
|
// CRITICAL OPTIMIZATION: Use pre-scaled thumbnail from DecorationRole
|
||||||
QPixmap art = index.data(Qt::DecorationRole).value<QPixmap>();
|
QPixmap art = index.data(Qt::DecorationRole).value<QPixmap>();
|
||||||
QRect iconRect(r.left(), r.top(), 50, 50);
|
QRect iconRect(r.left(), r.top(), 50, 50);
|
||||||
|
|
||||||
if (!art.isNull()) {
|
if (!art.isNull()) {
|
||||||
// Draw pre-scaled art directly. No scaling in paint loop.
|
// Draw pre-scaled art directly. No scaling in paint loop.
|
||||||
// Center it if aspect ratio differs slightly
|
// Center it if aspect ratio differs slightly
|
||||||
int x = iconRect.x() + (iconRect.width() - art.width()) / 2;
|
int x = iconRect.x() + (iconRect.width() - art.width()) / 2;
|
||||||
int y = iconRect.y() + (iconRect.height() - art.height()) / 2;
|
int y = iconRect.y() + (iconRect.height() - art.height()) / 2;
|
||||||
painter->drawPixmap(x, y, art);
|
painter->drawPixmap(x, y, art);
|
||||||
} else {
|
} else {
|
||||||
// Placeholder
|
// Placeholder
|
||||||
painter->fillRect(iconRect, QColor(40, 40, 40));
|
painter->fillRect(iconRect, QColor(40, 40, 40));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text
|
// Text
|
||||||
QRect textRect = r.adjusted(60, 0, 0, 0);
|
QRect textRect = r.adjusted(60, 0, 0, 0);
|
||||||
QString title = index.data(Qt::DisplayRole).toString();
|
QString title = index.data(Qt::DisplayRole).toString();
|
||||||
QString artist = index.data(Qt::UserRole + 1).toString();
|
QString artist = index.data(Qt::UserRole + 1).toString();
|
||||||
|
|
||||||
// Title
|
// Title
|
||||||
painter->setPen(Qt::white);
|
painter->setPen(Qt::white);
|
||||||
QFont f = option.font;
|
QFont f = option.font;
|
||||||
f.setBold(true);
|
f.setBold(true);
|
||||||
f.setPointSize(14);
|
f.setPointSize(14);
|
||||||
painter->setFont(f);
|
painter->setFont(f);
|
||||||
|
|
||||||
// Calculate height for title
|
// Calculate height for title
|
||||||
QFontMetrics fmTitle(f);
|
QFontMetrics fmTitle(f);
|
||||||
int titleHeight = fmTitle.height();
|
int titleHeight = fmTitle.height();
|
||||||
QRect titleRect = textRect;
|
QRect titleRect = textRect;
|
||||||
titleRect.setHeight(titleHeight);
|
titleRect.setHeight(titleHeight);
|
||||||
|
|
||||||
QString elidedTitle = fmTitle.elidedText(title, Qt::ElideRight, titleRect.width());
|
QString elidedTitle =
|
||||||
painter->drawText(titleRect, Qt::AlignLeft | Qt::AlignTop, elidedTitle);
|
fmTitle.elidedText(title, Qt::ElideRight, titleRect.width());
|
||||||
|
painter->drawText(titleRect, Qt::AlignLeft | Qt::AlignTop, elidedTitle);
|
||||||
|
|
||||||
// Artist
|
// Artist
|
||||||
painter->setPen(QColor(170, 170, 170));
|
painter->setPen(QColor(170, 170, 170));
|
||||||
f.setBold(false);
|
f.setBold(false);
|
||||||
f.setPointSize(12);
|
f.setPointSize(12);
|
||||||
painter->setFont(f);
|
painter->setFont(f);
|
||||||
|
|
||||||
QFontMetrics fmArtist(f);
|
QFontMetrics fmArtist(f);
|
||||||
QRect artistRect = textRect;
|
QRect artistRect = textRect;
|
||||||
artistRect.setTop(titleRect.bottom() + 2);
|
artistRect.setTop(titleRect.bottom() + 2);
|
||||||
artistRect.setHeight(fmArtist.height());
|
artistRect.setHeight(fmArtist.height());
|
||||||
|
|
||||||
QString elidedArtist = fmArtist.elidedText(artist, Qt::ElideRight, artistRect.width());
|
QString elidedArtist =
|
||||||
painter->drawText(artistRect, Qt::AlignLeft | Qt::AlignTop, elidedArtist);
|
fmArtist.elidedText(artist, Qt::ElideRight, artistRect.width());
|
||||||
|
painter->drawText(artistRect, Qt::AlignLeft | Qt::AlignTop, elidedArtist);
|
||||||
|
|
||||||
// Separator
|
// Separator
|
||||||
painter->setPen(QColor(34, 34, 34));
|
painter->setPen(QColor(34, 34, 34));
|
||||||
painter->drawLine(option.rect.bottomLeft(), option.rect.bottomRight());
|
painter->drawLine(option.rect.bottomLeft(), option.rect.bottomRight());
|
||||||
|
|
||||||
painter->restore();
|
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);
|
||||||
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; }";
|
// Tooltip showing full path
|
||||||
|
item->setToolTip(path);
|
||||||
QPushButton* btnFile = new QPushButton("Open File", this);
|
m_recentList->addItem(item);
|
||||||
btnFile->setStyleSheet(btnStyle);
|
}
|
||||||
btnFile->setFixedWidth(250);
|
for (const auto &path : files) {
|
||||||
connect(btnFile, &QPushButton::clicked, this, &WelcomeWidget::openFileClicked);
|
QListWidgetItem *item =
|
||||||
layout->addWidget(btnFile);
|
new QListWidgetItem("🎵 " + QFileInfo(path).fileName());
|
||||||
|
item->setData(Qt::UserRole, path);
|
||||||
QPushButton* btnFolder = new QPushButton("Open Folder", this);
|
item->setToolTip(path);
|
||||||
btnFolder->setStyleSheet(btnStyle);
|
m_recentList->addItem(item);
|
||||||
btnFolder->setFixedWidth(250);
|
}
|
||||||
connect(btnFolder, &QPushButton::clicked, this, &WelcomeWidget::openFolderClicked);
|
}
|
||||||
layout->addWidget(btnFolder);
|
|
||||||
|
void WelcomeWidget::onRecentClicked(QListWidgetItem *item) {
|
||||||
|
if (item) {
|
||||||
|
emit pathSelected(item->data(Qt::UserRole).toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
887
src/Utils.cpp
887
src/Utils.cpp
File diff suppressed because it is too large
Load Diff
115
src/Utils.h
115
src/Utils.h
|
|
@ -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
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue