okay, release will happen now. i cant find any more bugs, so it must be time.

This commit is contained in:
pszsh 2026-03-01 02:07:35 -08:00
parent 2b18e76c8f
commit 4e86256f1b
10 changed files with 904 additions and 203 deletions

2
.gitignore vendored
View File

@ -13,3 +13,5 @@ ADC-2024/
LICENCE
readme.md
vamp-plugin-sdk.cmake
*.keystore
*.jks

View File

@ -1,4 +1,4 @@
# Enable auto-env through the sdkman_auto_env config
# Add key=value pairs of SDKs to use below
java=17-homebrew
java=17.0.13-tem
gradle=9.2.1

View File

@ -173,6 +173,7 @@ if(EXISTS "${ICON_SOURCE}")
endif()
set(PROJECT_HEADERS
src/Theme.h
src/Utils.h
src/Processor.h
src/AudioEngine.h
@ -213,10 +214,16 @@ if(EXISTS "${ICON_SOURCE}" AND MAGICK_EXECUTABLE)
add_dependencies(YrCrystals GenerateIcons)
endif()
# --- Mobile Definitions ---
# --- Platform Definitions ---
if(BUILD_ANDROID OR BUILD_IOS)
target_compile_definitions(YrCrystals PRIVATE IS_MOBILE)
endif()
if(BUILD_ANDROID)
target_compile_definitions(YrCrystals PRIVATE PLATFORM_ANDROID)
endif()
if(BUILD_IOS)
target_compile_definitions(YrCrystals PRIVATE PLATFORM_IOS)
endif()
# --- Linking ---

View File

@ -13,8 +13,11 @@ TARGET = YrCrystals
# Android Specifics
PKG_NAME = org.qtproject.example.YrCrystals
# CRITICA: Qt6 generates 'android-build-debug.apk' by default
APK_PATH = $(BUILD_DIR_ANDROID)/android-build/build/outputs/apk/debug/android-build-debug.apk
AAB_PATH = $(BUILD_DIR_ANDROID)/android-build/build/outputs/bundle/release/android-build-release.aab
KEYSTORE_PATH = $(CURDIR)/android/release.keystore
KEYSTORE_ALIAS = yrcrystals
KEYSTORE_PASS = yrcrystals2026
all: macos
@ -51,6 +54,24 @@ android:
@cmake --build $(BUILD_DIR_ANDROID) --target apk
@echo "APK generated at $(APK_PATH)"
android-release:
@if [ ! -d "$(QT_ANDROID_KIT)" ]; then echo "Error: QT_ANDROID_KIT not found at $(QT_ANDROID_KIT)"; exit 1; fi
@if [ ! -f "$(KEYSTORE_PATH)" ]; then echo "Error: Release keystore not found at $(KEYSTORE_PATH)"; exit 1; fi
@mkdir -p $(BUILD_DIR_ANDROID)
@cd $(BUILD_DIR_ANDROID) && cmake .. \
-DCMAKE_TOOLCHAIN_FILE=$(QT_ANDROID_KIT)/lib/cmake/Qt6/qt.toolchain.cmake \
-DQT_ANDROID_ABIS="arm64-v8a" \
-DANDROID_PLATFORM=android-24 \
-DQT_ANDROID_BUILD_ALL_ABIS=OFF \
-DBUILD_ANDROID=ON \
-DCMAKE_BUILD_TYPE=Release
@QT_ANDROID_KEYSTORE_PATH=$(KEYSTORE_PATH) \
QT_ANDROID_KEYSTORE_ALIAS=$(KEYSTORE_ALIAS) \
QT_ANDROID_KEYSTORE_STORE_PASS=$(KEYSTORE_PASS) \
QT_ANDROID_KEYSTORE_KEY_PASS=$(KEYSTORE_PASS) \
cmake --build $(BUILD_DIR_ANDROID) --target aab
@echo "Signed AAB at $(AAB_PATH)"
ios: macos
@if [ ! -d "$(QT_IOS_KIT)" ]; then echo "Error: QT_IOS_KIT not found at $(QT_IOS_KIT)"; exit 1; fi
@mkdir -p $(BUILD_DIR_IOS)
@ -93,4 +114,4 @@ distclean:
@rm -rf $(BUILD_DIR_MACOS) $(BUILD_DIR_WIN) $(BUILD_DIR_ANDROID) $(BUILD_DIR_IOS)
@echo "Removed all build directories."
.PHONY: all desktop macos windows android ios run install_android run_android debug_android clean distclean
.PHONY: all desktop macos windows android android-release ios run install_android run_android debug_android clean distclean

View File

@ -1,19 +1,187 @@
// src/CommonWidgets.cpp
#include "CommonWidgets.h"
#include "Theme.h"
#include <QBoxLayout>
#include <QFileInfo>
#include <QHBoxLayout>
#include <QListWidget>
#include <QMouseEvent>
#include <QPainter>
#include <QPropertyAnimation>
#include <QPushButton>
#include <QResizeEvent>
#include <QScrollArea>
#include <QShowEvent>
#include <QVBoxLayout>
#include <algorithm>
#include <cmath>
// --- PlaylistDelegate Implementation ---
// --- TouchSlider ---
TouchSlider::TouchSlider(QWidget *parent) : QWidget(parent) {
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
setFixedHeight(Theme::Dims::SliderHeight);
}
void TouchSlider::setRange(int min, int max) {
m_min = min;
m_max = max;
m_value = std::clamp(m_value, m_min, m_max);
update();
}
void TouchSlider::setValue(int val) {
val = std::clamp(val, m_min, m_max);
if (val == m_value) return;
m_value = val;
update();
emit valueChanged(m_value);
}
QSize TouchSlider::sizeHint() const {
return QSize(200, Theme::Dims::SliderHeight);
}
int TouchSlider::trackLeft() const { return Theme::Dims::TrackPad; }
int TouchSlider::trackRight() const { return width() - Theme::Dims::TrackPad; }
int TouchSlider::handleX() const {
if (m_max == m_min) return trackLeft();
float frac = float(m_value - m_min) / float(m_max - m_min);
return trackLeft() + frac * (trackRight() - trackLeft());
}
void TouchSlider::paintEvent(QPaintEvent *) {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
int cy = height() / 2;
int tl = trackLeft();
int tr = trackRight();
int hx = handleX();
int hh = Theme::Dims::TrackH / 2;
p.setPen(Qt::NoPen);
p.setBrush(Theme::Colors::TrackBg);
p.drawRoundedRect(QRectF(tl, cy - hh, tr - tl, Theme::Dims::TrackH), hh, hh);
p.setBrush(Theme::Colors::Accent);
if (hx > tl)
p.drawRoundedRect(QRectF(tl, cy - hh, hx - tl, Theme::Dims::TrackH), hh, hh);
p.setBrush(Qt::white);
p.drawEllipse(QPointF(hx, cy), Theme::Dims::HandleRadius,
Theme::Dims::HandleRadius);
}
void TouchSlider::mousePressEvent(QMouseEvent *event) {
int hx = handleX();
int dx = event->pos().x() - hx;
int dy = event->pos().y() - height() / 2;
int hr = Theme::Dims::HitRadius;
if (dx * dx + dy * dy <= hr * hr) {
m_dragging = true;
emit sliderPressed();
}
}
void TouchSlider::mouseMoveEvent(QMouseEvent *event) {
if (!m_dragging) return;
int tl = trackLeft();
int tr = trackRight();
float frac = float(event->pos().x() - tl) / float(tr - tl);
frac = std::clamp(frac, 0.0f, 1.0f);
int newVal = m_min + frac * (m_max - m_min);
if (newVal != m_value) {
m_value = newVal;
update();
emit valueChanged(m_value);
}
}
void TouchSlider::mouseReleaseEvent(QMouseEvent *) {
if (m_dragging) {
m_dragging = false;
emit sliderReleased();
}
}
// --- ToggleSwitch ---
ToggleSwitch::ToggleSwitch(const QString &label, QWidget *parent)
: QWidget(parent), m_label(label) {
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
setFixedHeight(Theme::Dims::ToggleRow);
setCursor(Qt::PointingHandCursor);
}
void ToggleSwitch::setChecked(bool checked) {
if (m_checked == checked) return;
m_checked = checked;
if (m_anim) m_anim->stop();
m_anim = new QPropertyAnimation(this, "knobX", this);
connect(m_anim, &QObject::destroyed, this, [this]() { m_anim = nullptr; });
m_anim->setDuration(150);
m_anim->setStartValue(m_knobX);
m_anim->setEndValue(checked ? 1.0f : 0.0f);
m_anim->start(QAbstractAnimation::DeleteWhenStopped);
emit toggled(checked);
}
void ToggleSwitch::setKnobX(float x) {
m_knobX = x;
update();
}
QSize ToggleSwitch::sizeHint() const {
QFontMetrics fm(font());
int textW = m_label.isEmpty() ? 0 : fm.horizontalAdvance(m_label) + 8;
return QSize(Theme::Dims::ToggleW + textW, Theme::Dims::ToggleRow);
}
void ToggleSwitch::paintEvent(QPaintEvent *) {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
constexpr int tw = Theme::Dims::ToggleW;
constexpr int th = Theme::Dims::ToggleH;
constexpr int kd = Theme::Dims::ToggleKnob;
constexpr int pad = 2;
int ty = (height() - th) / 2;
auto off = Theme::Colors::TrackOff;
auto on = Theme::Colors::Accent;
QColor trackColor = QColor::fromRgbF(
off.redF() + (on.redF() - off.redF()) * m_knobX,
off.greenF() + (on.greenF() - off.greenF()) * m_knobX,
off.blueF() + (on.blueF() - off.blueF()) * m_knobX);
p.setPen(Qt::NoPen);
p.setBrush(trackColor);
p.drawRoundedRect(QRectF(0, ty, tw, th), th / 2.0, th / 2.0);
float knobLeft = pad + m_knobX * (tw - kd - 2 * pad);
float knobCX = knobLeft + kd / 2.0;
float knobCY = ty + th / 2.0;
p.setBrush(Qt::white);
p.drawEllipse(QPointF(knobCX, knobCY), kd / 2.0, kd / 2.0);
if (!m_label.isEmpty()) {
p.setPen(Qt::white);
QFont f = font();
f.setPointSize(13);
p.setFont(f);
p.drawText(QRectF(tw + 8, 0, width() - tw - 8, height()),
Qt::AlignLeft | Qt::AlignVCenter, m_label);
}
}
void ToggleSwitch::mousePressEvent(QMouseEvent *) { setChecked(!m_checked); }
// --- PlaylistDelegate ---
void PlaylistDelegate::paint(QPainter *painter,
const QStyleOptionViewItem &option,
@ -21,57 +189,47 @@ void PlaylistDelegate::paint(QPainter *painter,
painter->save();
painter->setRenderHint(QPainter::Antialiasing);
// Background
if (option.state & QStyle::State_Selected) {
painter->fillRect(option.rect, QColor(50, 50, 50));
} else {
painter->fillRect(option.rect, QColor(17, 17, 17)); // Match list background
}
if (option.state & QStyle::State_Selected)
painter->fillRect(option.rect, Theme::Colors::ListSelected);
else
painter->fillRect(option.rect, Theme::Colors::SurfaceDark);
QRect r = option.rect.adjusted(5, 5, -5, -5);
QRect r = option.rect.adjusted(Theme::Dims::ListPad, Theme::Dims::ListPad,
-Theme::Dims::ListPad, -Theme::Dims::ListPad);
// Icon / Art
// CRITICAL OPTIMIZATION: Use pre-scaled thumbnail from DecorationRole
constexpr int iconSize = Theme::Dims::ListIcon;
QPixmap art = index.data(Qt::DecorationRole).value<QPixmap>();
QRect iconRect(r.left(), r.top(), 50, 50);
QRect iconRect(r.left(), r.top(), iconSize, iconSize);
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));
painter->fillRect(iconRect, Theme::Colors::AlbumArtPlaceholder);
}
// Text
QRect textRect = r.adjusted(60, 0, 0, 0);
QRect textRect = r.adjusted(iconSize + 10, 0, 0, 0);
QString title = index.data(Qt::DisplayRole).toString();
QString artist = index.data(Qt::UserRole + 1).toString();
// Title
painter->setPen(Qt::white);
painter->setPen(Theme::Colors::TextPrimary);
QFont f = option.font;
f.setBold(true);
f.setPointSize(14);
f.setPointSize(Theme::Dims::ListTitlePt);
painter->setFont(f);
// Calculate height for title
QFontMetrics fmTitle(f);
int titleHeight = fmTitle.height();
QRect titleRect = textRect;
titleRect.setHeight(titleHeight);
titleRect.setHeight(fmTitle.height());
QString elidedTitle =
fmTitle.elidedText(title, Qt::ElideRight, titleRect.width());
painter->drawText(titleRect, Qt::AlignLeft | Qt::AlignTop, elidedTitle);
// Artist
painter->setPen(QColor(170, 170, 170));
painter->setPen(Theme::Colors::TextSecondary);
f.setBold(false);
f.setPointSize(12);
f.setPointSize(Theme::Dims::ListArtistPt);
painter->setFont(f);
QFontMetrics fmArtist(f);
@ -83,8 +241,7 @@ void PlaylistDelegate::paint(QPainter *painter,
fmArtist.elidedText(artist, Qt::ElideRight, artistRect.width());
painter->drawText(artistRect, Qt::AlignLeft | Qt::AlignTop, elidedArtist);
// Separator
painter->setPen(QColor(34, 34, 34));
painter->setPen(Theme::Colors::ListSeparator);
painter->drawLine(option.rect.bottomLeft(), option.rect.bottomRight());
painter->restore();
@ -92,7 +249,7 @@ void PlaylistDelegate::paint(QPainter *painter,
QSize PlaylistDelegate::sizeHint(const QStyleOptionViewItem &,
const QModelIndex &) const {
return QSize(0, 60);
return QSize(0, Theme::Dims::ListRow);
}
// --- XYPad ---
@ -100,7 +257,7 @@ QSize PlaylistDelegate::sizeHint(const QStyleOptionViewItem &,
XYPad::XYPad(const QString &title, QWidget *parent)
: QWidget(parent), m_title(title) {
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
setMinimumHeight(150);
setMinimumHeight(Theme::Dims::XYMinH);
setCursor(Qt::CrossCursor);
}
@ -118,11 +275,11 @@ void XYPad::paintEvent(QPaintEvent *) {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
p.fillRect(rect(), QColor(40, 40, 40, 200));
p.setPen(QPen(QColor(80, 80, 80), 1));
p.fillRect(rect(), Theme::Colors::XYBg);
p.setPen(QPen(Theme::Colors::XYBorder, 1));
p.drawRect(rect().adjusted(0, 0, -1, -1));
p.setPen(QPen(QColor(60, 60, 60), 1, Qt::DotLine));
p.setPen(QPen(Theme::Colors::XYGrid, 1, Qt::DotLine));
p.drawLine(width() / 2, 0, width() / 2, height());
p.drawLine(0, height() / 2, width(), height() / 2);
@ -130,12 +287,13 @@ void XYPad::paintEvent(QPaintEvent *) {
int py = (1.0f - m_y) * height();
p.setPen(Qt::NoPen);
p.setBrush(QColor(0, 212, 255, 180));
p.drawEllipse(QPoint(px, py), 12, 12);
p.setBrush(Theme::Colors::XYAccentDim);
p.drawEllipse(QPoint(px, py), Theme::Dims::XYIndicator,
Theme::Dims::XYIndicator);
p.setBrush(Qt::white);
p.drawEllipse(QPoint(px, py), 4, 4);
p.drawEllipse(QPoint(px, py), Theme::Dims::XYInner, Theme::Dims::XYInner);
p.setPen(QPen(QColor(0, 212, 255, 100), 1));
p.setPen(QPen(Theme::Colors::XYAccentFaint, 1));
p.drawLine(px, 0, px, height());
p.drawLine(0, py, width(), py);
@ -146,53 +304,196 @@ void XYPad::paintEvent(QPaintEvent *) {
p.setFont(f);
QString text = m_title;
if (m_formatter) {
if (m_formatter)
text += "\n" + m_formatter(m_x, m_y);
}
p.drawText(rect().adjusted(10, 10, -10, -10), Qt::AlignLeft | Qt::AlignTop,
text);
}
void XYPad::mousePressEvent(QMouseEvent *event) { updateFromPos(event->pos()); }
void XYPad::mouseMoveEvent(QMouseEvent *event) { updateFromPos(event->pos()); }
void XYPad::mousePressEvent(QMouseEvent *event) {
int px = m_x * width();
int py = (1.0f - m_y) * height();
QPoint diff = event->pos() - QPoint(px, py);
int hr = Theme::Dims::HitRadius;
if (diff.x() * diff.x() + diff.y() * diff.y() <= hr * hr) {
m_dragging = true;
m_lastPos = event->pos();
}
}
void XYPad::updateFromPos(const QPoint &pos) {
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);
void XYPad::mouseMoveEvent(QMouseEvent *event) {
if (!m_dragging) return;
QPoint delta = event->pos() - m_lastPos;
m_lastPos = event->pos();
m_x = std::clamp(m_x + delta.x() * 0.7f / width(), 0.0f, 1.0f);
m_y = std::clamp(m_y - delta.y() * 0.7f / height(), 0.0f, 1.0f);
update();
emit valuesChanged(m_x, m_y);
}
OverlayWidget::OverlayWidget(QWidget *content, QWidget *parent)
: QWidget(parent), m_content(content) {
QPalette pal = palette();
pal.setColor(QPalette::Window, QColor(0, 0, 0, 100));
setAutoFillBackground(true);
setPalette(pal);
void XYPad::mouseReleaseEvent(QMouseEvent *) { m_dragging = false; }
// --- ExpandedXYPad ---
ExpandedXYPad::ExpandedXYPad(QWidget *parent) : QWidget(parent) {
hide();
}
void ExpandedXYPad::open(const QString &title,
std::function<QString(float, float)> formatter,
float x, float y) {
m_title = title;
m_formatter = formatter;
m_x = x;
m_y = y;
m_dragging = false;
raise();
show();
update();
}
QRect ExpandedXYPad::padRect() const {
int side = std::min(width(), height()) * 85 / 100;
int cx = (width() - side) / 2;
int cy = (height() - side) / 2;
return QRect(cx, cy, side, side);
}
void ExpandedXYPad::paintEvent(QPaintEvent *) {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
p.fillRect(rect(), m_dragging ? Theme::Colors::ExpandedXYBgDragging
: Theme::Colors::ExpandedXYBg);
QRect pr = padRect();
p.fillRect(pr, Theme::Colors::XYBg);
p.setPen(QPen(Theme::Colors::XYBorder, 1));
p.drawRect(pr.adjusted(0, 0, -1, -1));
p.setPen(QPen(Theme::Colors::XYGrid, 1, Qt::DotLine));
p.drawLine(pr.center().x(), pr.top(), pr.center().x(), pr.bottom());
p.drawLine(pr.left(), pr.center().y(), pr.right(), pr.center().y());
int px = pr.left() + m_x * pr.width();
int py = pr.top() + (1.0f - m_y) * pr.height();
p.setPen(Qt::NoPen);
p.setBrush(Theme::Colors::XYAccentDim);
p.drawEllipse(QPoint(px, py), Theme::Dims::XYIndicator,
Theme::Dims::XYIndicator);
p.setBrush(Qt::white);
p.drawEllipse(QPoint(px, py), Theme::Dims::XYInner, Theme::Dims::XYInner);
p.setPen(QPen(Theme::Colors::XYAccentFaint, 1));
p.drawLine(px, pr.top(), px, pr.bottom());
p.drawLine(pr.left(), py, pr.right(), py);
p.setPen(Qt::white);
QFont f = font();
f.setBold(true);
f.setPointSize(12);
p.setFont(f);
QString text = m_title;
if (m_formatter)
text += "\n" + m_formatter(m_x, m_y);
p.drawText(pr.adjusted(10, 10, -10, -10), Qt::AlignLeft | Qt::AlignTop, text);
}
void ExpandedXYPad::mousePressEvent(QMouseEvent *event) {
QRect pr = padRect();
int hr = Theme::Dims::HitRadius;
int px = pr.left() + m_x * pr.width();
int py = pr.top() + (1.0f - m_y) * pr.height();
QPoint diff = event->pos() - QPoint(px, py);
if (diff.x() * diff.x() + diff.y() * diff.y() <= hr * hr) {
m_dragging = true;
m_lastPos = event->pos();
update();
return;
}
QRect cushion = pr.adjusted(-hr, -hr, hr, hr);
if (!cushion.contains(event->pos())) {
hide();
emit closed();
}
}
void ExpandedXYPad::mouseMoveEvent(QMouseEvent *event) {
if (!m_dragging) return;
QRect pr = padRect();
QPoint delta = event->pos() - m_lastPos;
m_lastPos = event->pos();
m_x = std::clamp(m_x + delta.x() * 0.7f / pr.width(), 0.0f, 1.0f);
m_y = std::clamp(m_y - delta.y() * 0.7f / pr.height(), 0.0f, 1.0f);
update();
emit valuesChanged(m_x, m_y);
}
void ExpandedXYPad::mouseReleaseEvent(QMouseEvent *) {
m_dragging = false;
update();
}
// --- OverlayWidget ---
OverlayWidget::OverlayWidget(QWidget *content, QWidget *parent)
: QWidget(parent) {
QVBoxLayout *layout = new QVBoxLayout(this);
layout->setAlignment(Qt::AlignCenter);
layout->setContentsMargins(20, 20, 20, 20);
content->setParent(this);
content->setMaximumWidth(500);
content->setMaximumHeight(600);
QScrollArea *scroll = new QScrollArea(this);
scroll->setWidget(content);
scroll->setWidgetResizable(true);
scroll->setFrameShape(QFrame::NoFrame);
scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scroll->setMaximumWidth(500);
scroll->setStyleSheet(
"QScrollArea { background: transparent; border: none; }"
"QScrollArea > QWidget > QWidget { background: transparent; }");
scroll->viewport()->setStyleSheet("background: transparent;");
layout->addWidget(content);
m_content = scroll;
layout->addWidget(scroll);
hide();
}
void OverlayWidget::mousePressEvent(QMouseEvent *event) {
if (!m_content->geometry().contains(event->pos())) {
hide();
void OverlayWidget::showEvent(QShowEvent *event) {
QWidget::showEvent(event);
if constexpr (Theme::FrostedGlass) {
if (auto *w = window()) {
QPixmap shot = w->grab();
int sw = shot.width() / 10;
int sh = shot.height() / 10;
m_blurredBg = shot.scaled(sw, sh, Qt::IgnoreAspectRatio,
Qt::SmoothTransformation)
.scaled(shot.size(), Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
}
}
}
void OverlayWidget::paintEvent(QPaintEvent *event) {
QPainter p(this);
p.fillRect(rect(), QColor(0, 0, 0, 100));
void OverlayWidget::mousePressEvent(QMouseEvent *event) {
if (!m_content->geometry().contains(event->pos()))
hide();
}
void OverlayWidget::paintEvent(QPaintEvent *) {
QPainter p(this);
if constexpr (Theme::FrostedGlass) {
if (!m_blurredBg.isNull())
p.drawPixmap(0, 0, m_blurredBg);
}
p.fillRect(rect(), Theme::Colors::OverlayDim);
}
// --- WelcomeWidget ---
WelcomeWidget::WelcomeWidget(QWidget *parent) : QWidget(parent) {
QVBoxLayout *layout = new QVBoxLayout(this);
layout->setAlignment(Qt::AlignCenter);
@ -204,10 +505,31 @@ WelcomeWidget::WelcomeWidget(QWidget *parent) : QWidget(parent) {
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; }";
QString minSizeRule = Theme::Dims::BtnMinSize > 0
? QString(" min-height: %1px;").arg(Theme::Dims::BtnMinSize)
: QString();
QString btnStyle = QString(
"QPushButton { background-color: %1; color: %2; border: 1px solid "
"%3; border-radius: 8px; padding: 15px; font-size: 18px;%4 } "
"QPushButton:pressed { background-color: %5; }")
.arg(Theme::hex(Theme::Colors::SurfaceLight),
Theme::hex(Theme::Colors::TextPrimary),
Theme::hex(Theme::Colors::BorderMid),
minSizeRule,
Theme::hex(Theme::Colors::BorderMid));
QString listStyle = QString(
"QListWidget { background: transparent; border: none; color: %1; "
"font-size: 16px; }"
"QListWidget::item { padding: %2px; border-bottom: 1px solid %3; }"
"QListWidget::item:hover { background: %4; }"
"QListWidget::item:selected { background: %5; }")
.arg(Theme::hex(Theme::Colors::TextMuted))
.arg(Theme::Dims::ListItemPad)
.arg(Theme::hex(Theme::Colors::SurfaceLight),
Theme::hex(Theme::Colors::SurfaceMid),
Theme::hex(Theme::Colors::SurfaceLight));
QHBoxLayout *btnLayout = new QHBoxLayout();
btnLayout->setSpacing(20);
@ -228,18 +550,11 @@ WelcomeWidget::WelcomeWidget(QWidget *parent) : QWidget(parent) {
layout->addLayout(btnLayout);
// --- Lists Container (Recent + Frequents) ---
QString listStyle =
"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; }";
QString labelStyle = "color: #aaa; font-size: 16px; margin-top: 20px;";
QString labelStyle = QString("color: %1; font-size: 16px; margin-top: 20px;")
.arg(Theme::hex(Theme::Colors::TextSecondary));
m_listsContainer = new QWidget(this);
// Recent column
QWidget *recentCol = new QWidget(m_listsContainer);
QVBoxLayout *recentLayout = new QVBoxLayout(recentCol);
recentLayout->setContentsMargins(0, 0, 0, 0);
@ -255,7 +570,6 @@ WelcomeWidget::WelcomeWidget(QWidget *parent) : QWidget(parent) {
&WelcomeWidget::onRecentClicked);
recentLayout->addWidget(m_recentList);
// Frequents column
QWidget *freqCol = new QWidget(m_listsContainer);
QVBoxLayout *freqLayout = new QVBoxLayout(freqCol);
freqLayout->setContentsMargins(0, 0, 0, 0);
@ -271,7 +585,6 @@ WelcomeWidget::WelcomeWidget(QWidget *parent) : QWidget(parent) {
&WelcomeWidget::onRecentClicked);
freqLayout->addWidget(m_frequentList);
// Default to vertical layout
m_listsLayout = new QVBoxLayout(m_listsContainer);
m_listsLayout->setContentsMargins(0, 0, 0, 0);
m_listsLayout->addWidget(recentCol);
@ -303,7 +616,6 @@ void WelcomeWidget::refreshRecents() {
m_recentList->addItem(item);
}
// Refresh frequents
m_frequentList->clear();
auto freqs = Utils::getFrequentPaths(10);
for (const auto &pair : freqs) {
@ -327,7 +639,6 @@ void WelcomeWidget::updateListsLayout() {
return;
m_isHorizontal = wantHorizontal;
// Reparent children out of old layout
QList<QWidget *> children;
while (m_listsLayout->count() > 0) {
QLayoutItem *item = m_listsLayout->takeAt(0);

View File

@ -3,12 +3,67 @@
#pragma once
#include "Utils.h"
#include <QLabel>
#include <QListWidget> // Include directly or fwd declare properly
#include <QListWidget>
#include <QStyledItemDelegate>
#include <QWidget>
#include <functional>
// Replaces PlaylistItemWidget for better performance
class QPropertyAnimation;
class TouchSlider : public QWidget {
Q_OBJECT
public:
explicit TouchSlider(QWidget *parent = nullptr);
void setRange(int min, int max);
void setValue(int val);
int value() const { return m_value; }
QSize sizeHint() const override;
signals:
void valueChanged(int value);
void sliderPressed();
void sliderReleased();
protected:
void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
private:
int handleX() const;
int trackLeft() const;
int trackRight() const;
int m_min = 0;
int m_max = 100;
int m_value = 0;
bool m_dragging = false;
};
class ToggleSwitch : public QWidget {
Q_OBJECT
Q_PROPERTY(float knobX READ knobX WRITE setKnobX)
public:
explicit ToggleSwitch(const QString &label = QString(),
QWidget *parent = nullptr);
bool isChecked() const { return m_checked; }
void setChecked(bool checked);
float knobX() const { return m_knobX; }
void setKnobX(float x);
QSize sizeHint() const override;
signals:
void toggled(bool checked);
protected:
void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
private:
bool m_checked = false;
float m_knobX = 0.0f;
QString m_label;
QPropertyAnimation *m_anim = nullptr;
};
class PlaylistDelegate : public QStyledItemDelegate {
Q_OBJECT
public:
@ -25,6 +80,8 @@ public:
XYPad(const QString &title, QWidget *parent = nullptr);
void setFormatter(std::function<QString(float, float)> formatter);
void setValues(float x, float y);
float x() const { return m_x; }
float y() const { return m_y; }
signals:
void valuesChanged(float x, float y);
@ -32,13 +89,39 @@ protected:
void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
private:
void updateFromPos(const QPoint &pos);
QString m_title;
float m_x = 0.5f;
float m_y = 0.5f;
std::function<QString(float, float)> m_formatter;
bool m_dragging = false;
QPoint m_lastPos;
};
class ExpandedXYPad : public QWidget {
Q_OBJECT
public:
ExpandedXYPad(QWidget *parent = nullptr);
void open(const QString &title,
std::function<QString(float, float)> formatter,
float x, float y);
signals:
void valuesChanged(float x, float y);
void closed();
protected:
void paintEvent(QPaintEvent *) override;
void mousePressEvent(QMouseEvent *) override;
void mouseMoveEvent(QMouseEvent *) override;
void mouseReleaseEvent(QMouseEvent *) override;
private:
QRect padRect() const;
QString m_title;
std::function<QString(float, float)> m_formatter;
float m_x = 0.5f, m_y = 0.5f;
bool m_dragging = false;
QPoint m_lastPos;
};
class OverlayWidget : public QWidget {
@ -47,11 +130,13 @@ public:
OverlayWidget(QWidget *content, QWidget *parent = nullptr);
protected:
void showEvent(QShowEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void paintEvent(QPaintEvent *event) override;
private:
QWidget *m_content;
QPixmap m_blurredBg;
};
class QBoxLayout;

View File

@ -1,5 +1,6 @@
// src/MainWindow.cpp
#include "MainWindow.h"
#include "Theme.h"
#include <QApplication>
#include <QCloseEvent>
#include <QDir>
@ -166,9 +167,12 @@ void MainWindow::initUi() {
m_playlist = new QListWidget();
m_playlist->setStyleSheet(
"QListWidget { background-color: #111; border: none; } QListWidget::item "
"{ border-bottom: 1px solid #222; padding: 0px; } "
"QListWidget::item:selected { background-color: #333; }");
QString("QListWidget { background-color: %1; border: none; } "
"QListWidget::item { border-bottom: 1px solid %2; padding: 0px; } "
"QListWidget::item:selected { background-color: %3; }")
.arg(Theme::hex(Theme::Colors::SurfaceDark),
Theme::hex(Theme::Colors::SurfaceMid),
Theme::hex(Theme::Colors::SurfaceLight)));
m_playlist->setSelectionMode(QAbstractItemView::SingleSelection);
m_playlist->setItemDelegate(new PlaylistDelegate(m_playlist));
m_playlist->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
@ -209,9 +213,12 @@ void MainWindow::initUi() {
#ifdef IS_MOBILE
m_mobileTabs = new QTabWidget();
m_mobileTabs->setStyleSheet(
"QTabWidget::pane { border: 0; } QTabBar::tab { background: #222; color: "
"white; padding: 15px; min-width: 100px; } QTabBar::tab:selected { "
"background: #444; }");
QString("QTabWidget::pane { border: 0; } QTabBar::tab { background: %1; "
"color: %2; padding: 15px; min-width: 100px; } "
"QTabBar::tab:selected { background: %3; }")
.arg(Theme::hex(Theme::Colors::SurfaceMid),
Theme::hex(Theme::Colors::TextPrimary),
Theme::hex(Theme::Colors::SurfaceElevated)));
m_mobileTabs->addTab(m_playerPage, "Visualizer");
m_mobileTabs->addTab(m_playlist, "Playlist");
m_stack->addWidget(m_mobileTabs);

View File

@ -1,6 +1,7 @@
// src/PlayerControls.cpp
#include "PlayerControls.h"
#include "Theme.h"
#include <QGridLayout>
#include <QHBoxLayout>
#include <QVBoxLayout>
@ -8,29 +9,45 @@
PlaybackWidget::PlaybackWidget(QWidget *parent) : QWidget(parent) {
setStyleSheet(
"background-color: rgba(0, 0, 0, 150); border-top: 1px solid #444;");
QString("background-color: %1; border-top: 1px solid %2;")
.arg(Theme::rgba(Theme::Colors::PlaybackSurface),
Theme::hex(Theme::Colors::BorderSubtle)));
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(10, 5, 10, 10);
m_seekSlider = new QSlider(Qt::Horizontal, this);
m_seekSlider = new TouchSlider(this);
m_seekSlider->setRange(0, 1000);
m_seekSlider->setFixedHeight(30);
m_seekSlider->setStyleSheet(
"QSlider::handle:horizontal { background: white; width: 20px; margin: "
"-8px 0; border-radius: 10px; }"
"QSlider::groove:horizontal { background: #444; height: 4px; }"
"QSlider::sub-page:horizontal { background: #00d4ff; }");
connect(m_seekSlider, &QSlider::sliderPressed, this,
connect(m_seekSlider, &TouchSlider::sliderPressed, this,
&PlaybackWidget::onSeekPressed);
connect(m_seekSlider, &QSlider::sliderReleased, this,
connect(m_seekSlider, &TouchSlider::sliderReleased, this,
&PlaybackWidget::onSeekReleased);
mainLayout->addWidget(m_seekSlider);
QHBoxLayout *rowLayout = new QHBoxLayout();
QString btnStyle =
"QPushButton { background: transparent; color: white; font-size: 24px; "
"border: 1px solid #444; border-radius: 8px; padding: 10px 20px; } "
"QPushButton:pressed { background: #333; }";
QString minSizeRule = Theme::Dims::BtnMinSize > 0
? QString(" min-width: %1px; min-height: %1px;").arg(Theme::Dims::BtnMinSize)
: QString();
QString btnPad = (Theme::Dims::BtnMinSize > 0) ? "6px 10px" : "10px 20px";
QString btnStyle = QString(
"QPushButton { background: transparent; color: %1; font-size: %2px; "
"border: 1px solid %3; border-radius: 8px; padding: %4;%5 } "
"QPushButton:pressed { background: %6; }")
.arg(Theme::hex(Theme::Colors::TextPrimary))
.arg(Theme::Dims::BtnFontSize)
.arg(Theme::hex(Theme::Colors::BorderSubtle),
btnPad, minSizeRule,
Theme::hex(Theme::Colors::SurfaceLight));
QString auxStyle = QString(
"QPushButton { background: transparent; color: %1; font-size: %2px; "
"border: none; padding: 10px;%3 } "
"QPushButton:pressed { color: %4; }")
.arg(Theme::hex(Theme::Colors::TextSecondary))
.arg(Theme::Dims::BtnFontSize)
.arg(minSizeRule,
Theme::hex(Theme::Colors::TextPrimary));
QPushButton *btnPrev = new QPushButton("<<", this);
btnPrev->setStyleSheet(btnStyle);
@ -45,22 +62,25 @@ PlaybackWidget::PlaybackWidget(QWidget *parent) : QWidget(parent) {
btnNext->setStyleSheet(btnStyle);
connect(btnNext, &QPushButton::clicked, this, &PlaybackWidget::nextClicked);
#if defined(PLATFORM_ANDROID)
QPushButton *btnSettings = new QPushButton("...", this);
#else
QPushButton *btnSettings = new QPushButton("", this);
btnSettings->setStyleSheet(
"QPushButton { background: transparent; color: #aaa; font-size: 24px; "
"border: none; padding: 10px; } QPushButton:pressed { color: white; }");
#endif
btnSettings->setStyleSheet(auxStyle);
connect(btnSettings, &QPushButton::clicked, this,
&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; }");
#if defined(PLATFORM_ANDROID)
QPushButton *btnHome = new QPushButton("<", this);
#else
QPushButton *btnHome = new QPushButton("", this);
#endif
btnHome->setStyleSheet(auxStyle);
connect(btnHome, &QPushButton::clicked, this, &PlaybackWidget::homeClicked);
rowLayout->addWidget(btnHome);
rowLayout
->addStretch(); // Add stretch so home is left-aligned, controls center
rowLayout->addStretch();
rowLayout->addWidget(btnPrev);
rowLayout->addSpacing(10);
rowLayout->addWidget(m_btnPlay);
@ -95,23 +115,37 @@ void PlaybackWidget::onPlayToggle() {
}
SettingsWidget::SettingsWidget(QWidget *parent) : QWidget(parent) {
setStyleSheet("background-color: rgba(30, 30, 30, 230); border-radius: 12px; "
"border: 1px solid #666;");
setStyleSheet(
QString("background-color: %1; border-radius: 12px; "
"border: 1px solid %2;")
.arg(Theme::rgba(Theme::Colors::SettingsSurface),
Theme::hex(Theme::Colors::BorderBright)));
QVBoxLayout *layout = new QVBoxLayout(this);
layout->setContentsMargins(15, 15, 15, 15);
layout->setSpacing(10);
layout->setContentsMargins(Theme::Dims::SettingsPad, Theme::Dims::SettingsPad,
Theme::Dims::SettingsPad, Theme::Dims::SettingsPad);
layout->setSpacing(Theme::Dims::SettingsSpace);
QHBoxLayout *header = new QHBoxLayout();
QLabel *title = new QLabel("Settings", this);
title->setStyleSheet("color: white; font-size: 18px; font-weight: bold; "
"border: none; background: transparent;");
title->setStyleSheet(
QString("color: %1; font-size: 18px; font-weight: bold; "
"border: none; background: transparent;")
.arg(Theme::hex(Theme::Colors::TextPrimary)));
#if defined(PLATFORM_ANDROID)
QPushButton *btnClose = new QPushButton("X", this);
#else
QPushButton *btnClose = new QPushButton("", this);
#endif
btnClose->setFixedSize(30, 30);
btnClose->setStyleSheet("QPushButton { background: #444; color: white; "
btnClose->setStyleSheet(
QString("QPushButton { background: %1; color: %2; "
"border-radius: 15px; border: none; font-weight: "
"bold; } QPushButton:pressed { background: #666; }");
"bold; } QPushButton:pressed { background: %3; }")
.arg(Theme::hex(Theme::Colors::SurfaceElevated),
Theme::hex(Theme::Colors::TextPrimary),
Theme::hex(Theme::Colors::BorderBright)));
connect(btnClose, &QPushButton::clicked, this, &SettingsWidget::closeClicked);
header->addWidget(title);
@ -120,129 +154,145 @@ SettingsWidget::SettingsWidget(QWidget *parent) : QWidget(parent) {
layout->addLayout(header);
QGridLayout *grid = new QGridLayout();
auto createCheck = [&](const QString &text, bool checked, int r, int c) {
QCheckBox *cb = new QCheckBox(text, this);
cb->setChecked(checked);
cb->setStyleSheet("QCheckBox { color: white; font-size: 14px; padding: "
"5px; border: none; background: transparent; } "
"QCheckBox::indicator { width: 20px; height: 20px; }");
connect(cb, &QCheckBox::toggled, this, &SettingsWidget::emitParams);
grid->addWidget(cb, r, c);
return cb;
auto createToggle = [&](const QString &text, bool checked, int r, int c) {
ToggleSwitch *ts = new ToggleSwitch(text, this);
ts->setChecked(checked);
connect(ts, &ToggleSwitch::toggled, this, &SettingsWidget::emitParams);
grid->addWidget(ts, r, c);
return ts;
};
// Updated Defaults based on user request
m_checkGlass = createCheck("Glass", true, 0, 0);
m_checkEntropy = createCheck("Entropy", false, 0, 1);
m_checkAlbumColors = createCheck("Album Colors", false, 1, 0);
m_checkMirrored = createCheck("Mirrored", true, 1, 1);
m_checkInverted = createCheck("Invert", false, 2, 0);
m_checkGlass = createToggle("Glass", true, 0, 0);
m_checkEntropy = createToggle("Entropy", false, 0, 1);
m_checkAlbumColors = createToggle("Album Colors", false, 1, 0);
m_checkMirrored = createToggle("Mirrored", true, 1, 1);
m_checkInverted = createToggle("Invert", false, 2, 0);
layout->addLayout(grid);
// Helper for sliders
QString lblStyle =
QString("color: %1; font-weight: bold; border: none; "
"background: transparent; min-width: 80px;")
.arg(Theme::hex(Theme::Colors::TextPrimary));
auto addSlider = [&](const QString &label, int min, int max, int val,
QSlider *&slider, QLabel *&lbl) {
TouchSlider *&slider, QLabel *&lbl) {
QHBoxLayout *h = new QHBoxLayout();
lbl = new QLabel(label, this);
lbl->setStyleSheet("color: white; font-weight: bold; border: none; "
"background: transparent; min-width: 80px;");
slider = new QSlider(Qt::Horizontal, this);
lbl->setStyleSheet(lblStyle);
slider = new TouchSlider(this);
slider->setRange(min, max);
slider->setValue(val);
slider->setStyleSheet(
"QSlider::handle:horizontal { background: #aaa; width: 24px; margin: "
"-10px 0; border-radius: 12px; } QSlider::groove:horizontal { "
"background: #444; height: 4px; }");
h->addWidget(lbl);
h->addWidget(slider);
layout->addLayout(h);
};
// Updated Slider Defaults
addSlider("Bins: 21", 10, 64, 21, m_sliderBins, m_lblBins);
connect(m_sliderBins, &QSlider::valueChanged, this,
connect(m_sliderBins, &TouchSlider::valueChanged, this,
&SettingsWidget::onBinsChanged);
addSlider("FPS: 60", 15, 120, 60, m_sliderFps, m_lblFps);
connect(m_sliderFps, &QSlider::valueChanged, this, [this](int val) {
connect(m_sliderFps, &TouchSlider::valueChanged, this, [this](int val) {
m_lblFps->setText(QString("FPS: %1").arg(val));
emit fpsChanged(val);
});
addSlider("Bright: 66%", 10, 200, 66, m_sliderBrightness, m_lblBrightness);
connect(m_sliderBrightness, &QSlider::valueChanged, this,
connect(m_sliderBrightness, &TouchSlider::valueChanged, this,
&SettingsWidget::onBrightnessChanged);
addSlider("Granularity", 0, 100, 95, m_sliderGranularity, m_lblGranularity);
connect(m_sliderGranularity, &QSlider::valueChanged, this,
connect(m_sliderGranularity, &TouchSlider::valueChanged, this,
&SettingsWidget::onSmoothingChanged);
addSlider("Detail", 0, 100, 5, m_sliderDetail, m_lblDetail);
connect(m_sliderDetail, &QSlider::valueChanged, this,
connect(m_sliderDetail, &TouchSlider::valueChanged, this,
&SettingsWidget::onSmoothingChanged);
addSlider("Strength", 0, 100, 95, m_sliderStrength, m_lblStrength);
connect(m_sliderStrength, &QSlider::valueChanged, this,
connect(m_sliderStrength, &TouchSlider::valueChanged, this,
&SettingsWidget::onSmoothingChanged);
// Entropy slider — shown only when Entropy checkbox is checked
{
m_entropyContainer = new QWidget(this);
m_entropyContainer->setStyleSheet("border: none; background: transparent;");
QHBoxLayout *h = new QHBoxLayout(m_entropyContainer);
h->setContentsMargins(0, 0, 0, 0);
m_lblEntropy = new QLabel("Entropy: 0.0", this);
m_lblEntropy->setStyleSheet("color: white; font-weight: bold; border: none; "
"background: transparent; min-width: 80px;");
m_sliderEntropy = new QSlider(Qt::Horizontal, this);
m_sliderEntropy->setRange(-150, 150); // -1.5 to 1.5, center detent at 0
m_lblEntropy->setStyleSheet(lblStyle);
m_sliderEntropy = new TouchSlider(this);
m_sliderEntropy->setRange(-150, 150);
m_sliderEntropy->setValue(0);
m_sliderEntropy->setStyleSheet(
"QSlider::handle:horizontal { background: #aaa; width: 24px; margin: "
"-10px 0; border-radius: 12px; } QSlider::groove:horizontal { "
"background: #444; height: 4px; }");
h->addWidget(m_lblEntropy);
h->addWidget(m_sliderEntropy);
layout->addWidget(m_entropyContainer);
m_entropyContainer->setVisible(false);
connect(m_sliderEntropy, &QSlider::valueChanged, this,
connect(m_sliderEntropy, &TouchSlider::valueChanged, this,
&SettingsWidget::onEntropyChanged);
connect(m_checkEntropy, &QCheckBox::toggled, m_entropyContainer,
connect(m_checkEntropy, &ToggleSwitch::toggled, m_entropyContainer,
&QWidget::setVisible);
}
QHBoxLayout *padsLayout = new QHBoxLayout();
m_padDsp = new XYPad("DSP", this);
m_padDsp->setFormatter([](float x, float y) {
m_dspFormatter = [](float x, float y) {
int power = 6 + (int)(x * 7.0f + 0.5f);
int window = std::pow(2, power);
int hop = 64 + y * (8192 - 64);
if (hop > window) hop = window;
return QString("Window: %1\nHop: %2").arg(window).arg(hop);
});
// Default to FFT 8192 (x=1.0), Hop 64 (y=0.0)
m_padDsp->setValues(1.0f, 0.0f);
connect(m_padDsp, &XYPad::valuesChanged, this,
&SettingsWidget::onDspPadChanged);
padsLayout->addWidget(m_padDsp);
m_padColor = new XYPad("Color", this);
m_padColor->setFormatter([](float x, float y) {
};
m_colorFormatter = [](float x, float y) {
float hue = x * 2.0f;
float cont = 0.1f + y * 2.9f;
return QString("Hue: %1\nCont: %2")
.arg(hue, 0, 'f', 2)
.arg(cont, 0, 'f', 2);
});
// Default to Hue 0.35 (x=0.175), Cont 0.10 (y=0.0)
};
m_padDsp = new XYPad("DSP", this);
m_padDsp->setFormatter(m_dspFormatter);
m_padDsp->setValues(1.0f, 0.0f);
connect(m_padDsp, &XYPad::valuesChanged, this,
&SettingsWidget::onDspPadChanged);
m_padColor = new XYPad("Color", this);
m_padColor->setFormatter(m_colorFormatter);
m_padColor->setValues(0.175f, 0.0f);
connect(m_padColor, &XYPad::valuesChanged, this,
&SettingsWidget::onColorPadChanged);
padsLayout->addWidget(m_padColor);
if constexpr (Theme::MobileXYPad) {
m_padDsp->hide();
m_padColor->hide();
QString expandBtnStyle = QString(
"QPushButton { background: %1; color: %2; border: 1px solid %3; "
"border-radius: 8px; font-weight: bold; min-height: %4px; } "
"QPushButton:pressed { background: %5; }")
.arg(Theme::hex(Theme::Colors::SurfaceLight),
Theme::hex(Theme::Colors::TextPrimary),
Theme::hex(Theme::Colors::BorderMid))
.arg(Theme::Dims::XYExpandBtnH)
.arg(Theme::hex(Theme::Colors::SurfaceElevated));
QHBoxLayout *padsLayout = new QHBoxLayout();
QPushButton *btnDsp = new QPushButton("DSP", this);
btnDsp->setStyleSheet(expandBtnStyle);
connect(btnDsp, &QPushButton::clicked, this, &SettingsWidget::expandDspPad);
padsLayout->addWidget(btnDsp);
QPushButton *btnColor = new QPushButton("Color", this);
btnColor->setStyleSheet(expandBtnStyle);
connect(btnColor, &QPushButton::clicked, this, &SettingsWidget::expandColorPad);
padsLayout->addWidget(btnColor);
layout->addLayout(padsLayout);
} else {
QHBoxLayout *padsLayout = new QHBoxLayout();
padsLayout->addWidget(m_padDsp);
padsLayout->addWidget(m_padColor);
layout->addLayout(padsLayout);
}
}
void SettingsWidget::setParams(bool glass, bool albumColors,
@ -329,6 +379,21 @@ void SettingsWidget::onBrightnessChanged(int val) {
emitParams();
}
void SettingsWidget::updateDspPad(float x, float y) {
m_padDsp->setValues(x, y);
onDspPadChanged(x, y);
}
void SettingsWidget::updateColorPad(float x, float y) {
m_padColor->setValues(x, y);
onColorPadChanged(x, y);
}
float SettingsWidget::dspPadX() const { return m_padDsp->x(); }
float SettingsWidget::dspPadY() const { return m_padDsp->y(); }
float SettingsWidget::colorPadX() const { return m_padColor->x(); }
float SettingsWidget::colorPadY() const { return m_padColor->y(); }
void SettingsWidget::onSmoothingChanged(int val) {
m_granularity = m_sliderGranularity->value();
m_detail = m_sliderDetail->value();
@ -351,6 +416,37 @@ PlayerPage::PlayerPage(QWidget *parent) : QWidget(parent) {
connect(m_visualizer, &VisualizerWidget::tapDetected, this,
&PlayerPage::toggleFullScreen);
if constexpr (Theme::MobileXYPad) {
m_expandedPad = new ExpandedXYPad(this);
connect(m_settings, &SettingsWidget::expandDspPad, this, [this]() {
m_overlay->hide();
m_expandedPadIsDsp = true;
m_expandedPad->open("DSP", m_settings->dspFormatter(),
m_settings->dspPadX(), m_settings->dspPadY());
});
connect(m_settings, &SettingsWidget::expandColorPad, this, [this]() {
m_overlay->hide();
m_expandedPadIsDsp = false;
m_expandedPad->open("Color", m_settings->colorFormatter(),
m_settings->colorPadX(), m_settings->colorPadY());
});
connect(m_expandedPad, &ExpandedXYPad::valuesChanged, this,
[this](float x, float y) {
if (m_expandedPadIsDsp)
m_settings->updateDspPad(x, y);
else
m_settings->updateColorPad(x, y);
});
connect(m_expandedPad, &ExpandedXYPad::closed, this, [this]() {
m_overlay->raise();
m_overlay->show();
});
}
}
void PlayerPage::setFullScreen(bool fs) { m_playback->setVisible(!fs); }
@ -376,4 +472,7 @@ void PlayerPage::resizeEvent(QResizeEvent *event) {
m_playback->setGeometry(0, h - pbHeight, w, pbHeight);
m_overlay->setGeometry(0, 0, w, h);
if (m_expandedPad)
m_expandedPad->setGeometry(0, 0, w, h);
}

View File

@ -3,10 +3,8 @@
#pragma once
#include "CommonWidgets.h"
#include "VisualizerWidget.h"
#include <QCheckBox>
#include <QLabel>
#include <QPushButton>
#include <QSlider>
#include <QWidget>
class PlaybackWidget : public QWidget {
@ -22,14 +20,14 @@ signals:
void prevClicked();
void seekChanged(float pos);
void settingsClicked();
void homeClicked(); // New signal
void homeClicked();
private slots:
void onSeekPressed();
void onSeekReleased();
void onPlayToggle();
private:
QSlider *m_seekSlider;
TouchSlider *m_seekSlider;
bool m_seeking = false;
bool m_isPlaying = false;
QPushButton *m_btnPlay;
@ -40,6 +38,15 @@ class SettingsWidget : public QWidget {
public:
SettingsWidget(QWidget *parent = nullptr);
void updateDspPad(float x, float y);
void updateColorPad(float x, float y);
float dspPadX() const;
float dspPadY() const;
float colorPadX() const;
float colorPadY() const;
std::function<QString(float, float)> dspFormatter() const { return m_dspFormatter; }
std::function<QString(float, float)> colorFormatter() const { return m_colorFormatter; }
bool isGlass() const { return m_checkGlass->isChecked(); }
bool isEntropy() const { return m_checkEntropy->isChecked(); }
float getEntropy() const { return m_entropy; }
@ -68,6 +75,8 @@ signals:
void dspParamsChanged(int fft, int hop);
void binsChanged(int n);
void closeClicked();
void expandDspPad();
void expandColorPad();
private slots:
void emitParams();
@ -79,28 +88,28 @@ private slots:
void onEntropyChanged(int val);
private:
QCheckBox *m_checkGlass;
QCheckBox *m_checkEntropy;
QCheckBox *m_checkAlbumColors;
QCheckBox *m_checkMirrored;
QCheckBox *m_checkInverted;
ToggleSwitch *m_checkGlass;
ToggleSwitch *m_checkEntropy;
ToggleSwitch *m_checkAlbumColors;
ToggleSwitch *m_checkMirrored;
ToggleSwitch *m_checkInverted;
XYPad *m_padDsp;
XYPad *m_padColor;
QSlider *m_sliderBins;
TouchSlider *m_sliderBins;
QLabel *m_lblBins;
QSlider *m_sliderFps;
TouchSlider *m_sliderFps;
QLabel *m_lblFps;
QSlider *m_sliderBrightness;
TouchSlider *m_sliderBrightness;
QLabel *m_lblBrightness;
QSlider *m_sliderGranularity;
TouchSlider *m_sliderGranularity;
QLabel *m_lblGranularity;
QSlider *m_sliderDetail;
TouchSlider *m_sliderDetail;
QLabel *m_lblDetail;
QSlider *m_sliderStrength;
TouchSlider *m_sliderStrength;
QLabel *m_lblStrength;
QSlider *m_sliderEntropy;
TouchSlider *m_sliderEntropy;
QLabel *m_lblEntropy;
QWidget *m_entropyContainer;
float m_entropy = 0.0f;
@ -116,6 +125,9 @@ private:
int m_fft = 4096;
int m_hop = 1024;
int m_bins = 26;
std::function<QString(float, float)> m_dspFormatter;
std::function<QString(float, float)> m_colorFormatter;
};
class PlayerPage : public QWidget {
@ -128,7 +140,7 @@ public:
void setFullScreen(bool fs);
signals:
void toggleFullScreen();
void homeClicked(); // New signal
void homeClicked();
protected:
void resizeEvent(QResizeEvent *event) override;
@ -141,4 +153,6 @@ private:
PlaybackWidget *m_playback;
SettingsWidget *m_settings;
OverlayWidget *m_overlay;
ExpandedXYPad *m_expandedPad = nullptr;
bool m_expandedPadIsDsp = true;
};

155
src/Theme.h Normal file
View File

@ -0,0 +1,155 @@
// src/Theme.h
#pragma once
#include <QColor>
#include <QString>
namespace Theme {
#if defined(PLATFORM_IOS)
inline constexpr bool FrostedGlass = true;
#else
inline constexpr bool FrostedGlass = false;
#endif
#if defined(PLATFORM_IOS) || defined(PLATFORM_ANDROID)
inline constexpr bool MobileXYPad = true;
#else
inline constexpr bool MobileXYPad = false;
#endif
namespace Colors {
// Accent & palette (shared across all skins)
inline const QColor Accent(0x00, 0xd4, 0xff);
inline const QColor TrackBg(0x44, 0x44, 0x44);
inline const QColor TrackOff(0x55, 0x55, 0x55);
inline const QColor TextPrimary(Qt::white);
inline const QColor TextSecondary(0xaa, 0xaa, 0xaa);
inline const QColor TextMuted(0xdd, 0xdd, 0xdd);
// Surface tiers
inline const QColor SurfaceDark(0x11, 0x11, 0x11);
inline const QColor SurfaceMid(0x22, 0x22, 0x22);
inline const QColor SurfaceLight(0x33, 0x33, 0x33);
inline const QColor SurfaceElevated(0x44, 0x44, 0x44);
// Borders
inline const QColor BorderSubtle(0x44, 0x44, 0x44);
inline const QColor BorderMid(0x55, 0x55, 0x55);
inline const QColor BorderBright(0x66, 0x66, 0x66);
// XYPad
inline const QColor XYBg(40, 40, 40, 200);
inline const QColor XYBorder(80, 80, 80);
inline const QColor XYGrid(60, 60, 60);
inline const QColor XYAccentDim(0, 212, 255, 180);
inline const QColor XYAccentFaint(0, 212, 255, 100);
// Expanded XYPad overlay
inline const QColor ExpandedXYBg(0, 0, 0, 160);
inline const QColor ExpandedXYBgDragging(0, 0, 0, 20);
// List
inline const QColor ListSelected(50, 50, 50);
inline const QColor ListSeparator(34, 34, 34);
inline const QColor AlbumArtPlaceholder(40, 40, 40);
// Platform-specific alpha levels
#if defined(PLATFORM_IOS)
inline const QColor OverlayDim(0, 0, 0, 60);
inline const QColor SettingsSurface(30, 30, 30, 160);
inline const QColor PlaybackSurface(0, 0, 0, 100);
#elif defined(PLATFORM_ANDROID)
inline const QColor OverlayDim(0, 0, 0, 100);
inline const QColor SettingsSurface(28, 28, 28, 240);
inline const QColor PlaybackSurface(0, 0, 0, 150);
#else
inline const QColor OverlayDim(0, 0, 0, 100);
inline const QColor SettingsSurface(28, 28, 28, 240);
inline const QColor PlaybackSurface(0, 0, 0, 150);
#endif
} // namespace Colors
namespace Dims {
#if defined(PLATFORM_IOS) || defined(PLATFORM_ANDROID)
// Touch-sized
inline constexpr int SliderHeight = 48;
inline constexpr int HandleRadius = 20;
inline constexpr int TrackH = 6;
inline constexpr int HitRadius = 40;
inline constexpr int TrackPad = 20;
inline constexpr int ToggleW = 52;
inline constexpr int ToggleH = 28;
inline constexpr int ToggleKnob = 24;
inline constexpr int ToggleRow = 36;
inline constexpr int XYIndicator = 20;
inline constexpr int XYInner = 6;
inline constexpr int XYMinH = 200;
inline constexpr int ListIcon = 60;
inline constexpr int ListRow = 80;
inline constexpr int ListTitlePt = 16;
inline constexpr int ListArtistPt = 14;
inline constexpr int ListPad = 7;
inline constexpr int ListItemPad = 14;
inline constexpr int BtnFontSize = 28;
inline constexpr int BtnMinSize = 48;
inline constexpr int SettingsPad = 12;
inline constexpr int SettingsSpace = 8;
inline constexpr int XYExpandBtnH = 48;
#else
// Desktop: mouse-optimized, compact
inline constexpr int SliderHeight = 36;
inline constexpr int HandleRadius = 12;
inline constexpr int TrackH = 4;
inline constexpr int HitRadius = 28;
inline constexpr int TrackPad = 14;
inline constexpr int ToggleW = 44;
inline constexpr int ToggleH = 22;
inline constexpr int ToggleKnob = 18;
inline constexpr int ToggleRow = 30;
inline constexpr int XYIndicator = 12;
inline constexpr int XYInner = 4;
inline constexpr int XYMinH = 150;
inline constexpr int ListIcon = 50;
inline constexpr int ListRow = 60;
inline constexpr int ListTitlePt = 14;
inline constexpr int ListArtistPt = 12;
inline constexpr int ListPad = 5;
inline constexpr int ListItemPad = 10;
inline constexpr int BtnFontSize = 24;
inline constexpr int BtnMinSize = 0;
inline constexpr int SettingsPad = 12;
inline constexpr int SettingsSpace = 10;
inline constexpr int XYExpandBtnH = 0;
#endif
} // namespace Dims
inline QString hex(const QColor &c) { return c.name(QColor::HexRgb); }
inline QString rgba(const QColor &c) {
return QString("rgba(%1,%2,%3,%4)")
.arg(c.red())
.arg(c.green())
.arg(c.blue())
.arg(c.alpha());
}
} // namespace Theme