385 lines
13 KiB
C++
385 lines
13 KiB
C++
// src/PlayerControls.cpp
|
|
|
|
#include "PlayerControls.h"
|
|
#include <QGridLayout>
|
|
#include <QHBoxLayout>
|
|
#include <QVBoxLayout>
|
|
#include <cmath>
|
|
|
|
PlaybackWidget::PlaybackWidget(QWidget *parent) : QWidget(parent) {
|
|
setStyleSheet(
|
|
"background-color: rgba(0, 0, 0, 150); border-top: 1px solid #444;");
|
|
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
|
mainLayout->setContentsMargins(10, 5, 10, 10);
|
|
|
|
m_seekSlider = new QSlider(Qt::Horizontal, 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,
|
|
&PlaybackWidget::onSeekPressed);
|
|
connect(m_seekSlider, &QSlider::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; }";
|
|
|
|
QPushButton *btnPrev = new QPushButton("<<", this);
|
|
btnPrev->setStyleSheet(btnStyle);
|
|
connect(btnPrev, &QPushButton::clicked, this, &PlaybackWidget::prevClicked);
|
|
|
|
m_btnPlay = new QPushButton(">", this);
|
|
m_btnPlay->setStyleSheet(btnStyle);
|
|
connect(m_btnPlay, &QPushButton::clicked, this,
|
|
&PlaybackWidget::onPlayToggle);
|
|
|
|
QPushButton *btnNext = new QPushButton(">>", this);
|
|
btnNext->setStyleSheet(btnStyle);
|
|
connect(btnNext, &QPushButton::clicked, this, &PlaybackWidget::nextClicked);
|
|
|
|
QPushButton *btnSettings = new QPushButton("⚙", this);
|
|
btnSettings->setStyleSheet(
|
|
"QPushButton { background: transparent; color: #aaa; font-size: 24px; "
|
|
"border: none; padding: 10px; } QPushButton:pressed { color: white; }");
|
|
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; }");
|
|
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->addSpacing(10);
|
|
rowLayout->addWidget(m_btnPlay);
|
|
rowLayout->addSpacing(10);
|
|
rowLayout->addWidget(btnNext);
|
|
rowLayout->addStretch();
|
|
rowLayout->addWidget(btnSettings);
|
|
|
|
mainLayout->addLayout(rowLayout);
|
|
}
|
|
|
|
void PlaybackWidget::setPlaying(bool playing) {
|
|
m_isPlaying = playing;
|
|
m_btnPlay->setText(playing ? "||" : ">");
|
|
}
|
|
|
|
void PlaybackWidget::updateSeek(float pos) {
|
|
if (!m_seeking)
|
|
m_seekSlider->setValue(static_cast<int>(pos * 1000));
|
|
}
|
|
|
|
void PlaybackWidget::onSeekPressed() { m_seeking = true; }
|
|
void PlaybackWidget::onSeekReleased() {
|
|
m_seeking = false;
|
|
emit seekChanged(m_seekSlider->value() / 1000.0f);
|
|
}
|
|
void PlaybackWidget::onPlayToggle() {
|
|
if (m_isPlaying)
|
|
emit pauseClicked();
|
|
else
|
|
emit playClicked();
|
|
}
|
|
|
|
SettingsWidget::SettingsWidget(QWidget *parent) : QWidget(parent) {
|
|
setStyleSheet("background-color: rgba(30, 30, 30, 230); border-radius: 12px; "
|
|
"border: 1px solid #666;");
|
|
|
|
QVBoxLayout *layout = new QVBoxLayout(this);
|
|
layout->setContentsMargins(15, 15, 15, 15);
|
|
layout->setSpacing(10);
|
|
|
|
QHBoxLayout *header = new QHBoxLayout();
|
|
QLabel *title = new QLabel("Settings", this);
|
|
title->setStyleSheet("color: white; font-size: 18px; font-weight: bold; "
|
|
"border: none; background: transparent;");
|
|
|
|
QPushButton *btnClose = new QPushButton("✕", this);
|
|
btnClose->setFixedSize(30, 30);
|
|
btnClose->setStyleSheet("QPushButton { background: #444; color: white; "
|
|
"border-radius: 15px; border: none; font-weight: "
|
|
"bold; } QPushButton:pressed { background: #666; }");
|
|
connect(btnClose, &QPushButton::clicked, this, &SettingsWidget::closeClicked);
|
|
|
|
header->addWidget(title);
|
|
header->addStretch();
|
|
header->addWidget(btnClose);
|
|
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;
|
|
};
|
|
|
|
// Updated Defaults based on user request
|
|
m_checkGlass = createCheck("Glass", true, 0, 0);
|
|
m_checkFocus = createCheck("Focus", true, 0, 1);
|
|
m_checkAlbumColors = createCheck("Album Colors", false, 1, 0);
|
|
m_checkMirrored = createCheck("Mirrored", true, 1, 1);
|
|
m_checkInverted = createCheck("Invert", false, 2, 0);
|
|
layout->addLayout(grid);
|
|
|
|
// Helper for sliders
|
|
auto addSlider = [&](const QString &label, int min, int max, int val,
|
|
QSlider *&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);
|
|
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,
|
|
&SettingsWidget::onBinsChanged);
|
|
|
|
addSlider("FPS: 60", 15, 120, 60, m_sliderFps, m_lblFps);
|
|
connect(m_sliderFps, &QSlider::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,
|
|
&SettingsWidget::onBrightnessChanged);
|
|
|
|
addSlider("Granularity", 0, 100, 95, m_sliderGranularity, m_lblGranularity);
|
|
connect(m_sliderGranularity, &QSlider::valueChanged, this,
|
|
&SettingsWidget::onSmoothingChanged);
|
|
|
|
addSlider("Detail", 0, 100, 5, m_sliderDetail, m_lblDetail);
|
|
connect(m_sliderDetail, &QSlider::valueChanged, this,
|
|
&SettingsWidget::onSmoothingChanged);
|
|
|
|
addSlider("Strength", 0, 100, 95, m_sliderStrength, m_lblStrength);
|
|
connect(m_sliderStrength, &QSlider::valueChanged, this,
|
|
&SettingsWidget::onSmoothingChanged);
|
|
|
|
// BPM Scale Selector
|
|
QHBoxLayout *bpmLayout = new QHBoxLayout();
|
|
QLabel *lblBpm = new QLabel("BPM Scale:", this);
|
|
lblBpm->setStyleSheet("color: white; font-weight: bold; border: none; "
|
|
"background: transparent; min-width: 80px;");
|
|
|
|
m_comboBpmScale = new QComboBox(this);
|
|
m_comboBpmScale->addItems({"1/1", "1/2", "1/4 (Default)", "1/8", "1/16"});
|
|
m_comboBpmScale->setCurrentIndex(4); // Default to 1/16
|
|
m_comboBpmScale->setStyleSheet(
|
|
"QComboBox { background: #444; color: white; border: 1px solid #666; "
|
|
"border-radius: 4px; padding: 4px; } QComboBox::drop-down { border: "
|
|
"none; }");
|
|
connect(m_comboBpmScale, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
|
this, &SettingsWidget::onBpmScaleChanged);
|
|
|
|
bpmLayout->addWidget(lblBpm);
|
|
bpmLayout->addWidget(m_comboBpmScale);
|
|
layout->addLayout(bpmLayout);
|
|
|
|
QHBoxLayout *padsLayout = new QHBoxLayout();
|
|
|
|
m_padDsp = new XYPad("DSP", this);
|
|
m_padDsp->setFormatter([](float x, float y) {
|
|
int power = 6 + (int)(x * 7.0f + 0.5f);
|
|
int fft = std::pow(2, power);
|
|
int hop = 64 + y * (8192 - 64);
|
|
return QString("FFT: %1\nHop: %2").arg(fft).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) {
|
|
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_padColor->setValues(0.175f, 0.0f);
|
|
connect(m_padColor, &XYPad::valuesChanged, this,
|
|
&SettingsWidget::onColorPadChanged);
|
|
padsLayout->addWidget(m_padColor);
|
|
|
|
layout->addLayout(padsLayout);
|
|
}
|
|
|
|
void SettingsWidget::setParams(bool glass, bool focus, bool albumColors,
|
|
bool mirrored, bool inverted, int bins, int fps,
|
|
float brightness, int granularity, int detail,
|
|
float strength, int bpmScaleIndex) {
|
|
bool oldState = blockSignals(true);
|
|
m_checkGlass->setChecked(glass);
|
|
m_checkFocus->setChecked(focus);
|
|
m_checkAlbumColors->setChecked(albumColors);
|
|
m_checkMirrored->setChecked(mirrored);
|
|
m_checkInverted->setChecked(inverted);
|
|
m_sliderBins->setValue(bins);
|
|
m_lblBins->setText(QString("Bins: %1").arg(bins));
|
|
|
|
m_sliderFps->setValue(fps);
|
|
m_lblFps->setText(QString("FPS: %1").arg(fps));
|
|
|
|
m_brightness = brightness;
|
|
m_sliderBrightness->setValue(static_cast<int>(brightness * 100.0f));
|
|
m_lblBrightness->setText(
|
|
QString("Bright: %1%").arg(static_cast<int>(brightness * 100.0f)));
|
|
|
|
m_granularity = granularity;
|
|
m_sliderGranularity->setValue(granularity);
|
|
|
|
m_detail = detail;
|
|
m_sliderDetail->setValue(detail);
|
|
|
|
m_strength = strength;
|
|
m_sliderStrength->setValue(static_cast<int>(strength * 100.0f));
|
|
|
|
if (bpmScaleIndex >= 0 && bpmScaleIndex < m_comboBpmScale->count()) {
|
|
m_comboBpmScale->setCurrentIndex(bpmScaleIndex);
|
|
}
|
|
|
|
blockSignals(oldState);
|
|
|
|
emitParams();
|
|
emit binsChanged(bins);
|
|
}
|
|
|
|
void SettingsWidget::emitParams() {
|
|
emit paramsChanged(m_checkGlass->isChecked(), m_checkFocus->isChecked(),
|
|
m_checkAlbumColors->isChecked(),
|
|
m_checkMirrored->isChecked(),
|
|
m_checkInverted->isChecked(), m_hue, m_contrast,
|
|
m_brightness, m_granularity, m_detail, m_strength);
|
|
}
|
|
|
|
float SettingsWidget::getBpmScale() const {
|
|
switch (m_comboBpmScale->currentIndex()) {
|
|
case 0:
|
|
return 0.25f; // 1/1
|
|
case 1:
|
|
return 0.5f; // 1/2
|
|
case 2:
|
|
return 1.0f; // 1/4 (Default)
|
|
case 3:
|
|
return 2.0f; // 1/8
|
|
case 4:
|
|
return 4.0f; // 1/16
|
|
default:
|
|
return 1.0f;
|
|
}
|
|
}
|
|
|
|
int SettingsWidget::getBpmScaleIndex() const {
|
|
return m_comboBpmScale->currentIndex();
|
|
}
|
|
|
|
void SettingsWidget::onBpmScaleChanged(int index) {
|
|
emit bpmScaleChanged(getBpmScale());
|
|
}
|
|
|
|
void SettingsWidget::onDspPadChanged(float x, float y) {
|
|
int power = 6 + (int)(x * 7.0f + 0.5f);
|
|
m_fft = std::pow(2, power);
|
|
m_hop = 64 + y * (8192 - 64);
|
|
emit dspParamsChanged(m_fft, m_hop);
|
|
}
|
|
|
|
void SettingsWidget::onColorPadChanged(float x, float y) {
|
|
m_hue = x * 2.0f;
|
|
m_contrast = 0.1f + y * 2.9f;
|
|
emitParams();
|
|
}
|
|
|
|
void SettingsWidget::onBinsChanged(int val) {
|
|
m_bins = val;
|
|
m_lblBins->setText(QString("Bins: %1").arg(val));
|
|
emit binsChanged(val);
|
|
}
|
|
|
|
void SettingsWidget::onBrightnessChanged(int val) {
|
|
m_brightness = val / 100.0f;
|
|
m_lblBrightness->setText(QString("Bright: %1%").arg(val));
|
|
emitParams();
|
|
}
|
|
|
|
void SettingsWidget::onSmoothingChanged(int val) {
|
|
m_granularity = m_sliderGranularity->value();
|
|
m_detail = m_sliderDetail->value();
|
|
m_strength = m_sliderStrength->value() / 100.0f;
|
|
emitParams();
|
|
}
|
|
|
|
PlayerPage::PlayerPage(QWidget *parent) : QWidget(parent) {
|
|
m_visualizer = new VisualizerWidget(this);
|
|
m_playback = new PlaybackWidget(this);
|
|
m_settings = new SettingsWidget();
|
|
m_overlay = new OverlayWidget(m_settings, this);
|
|
|
|
connect(m_playback, &PlaybackWidget::settingsClicked, this,
|
|
&PlayerPage::toggleOverlay);
|
|
connect(m_playback, &PlaybackWidget::homeClicked, this,
|
|
&PlayerPage::homeClicked);
|
|
connect(m_settings, &SettingsWidget::closeClicked, this,
|
|
&PlayerPage::closeOverlay);
|
|
|
|
connect(m_visualizer, &VisualizerWidget::tapDetected, this,
|
|
&PlayerPage::toggleFullScreen);
|
|
}
|
|
|
|
void PlayerPage::setFullScreen(bool fs) { m_playback->setVisible(!fs); }
|
|
|
|
void PlayerPage::toggleOverlay() {
|
|
if (m_overlay->isVisible())
|
|
m_overlay->hide();
|
|
else {
|
|
m_overlay->raise();
|
|
m_overlay->show();
|
|
}
|
|
}
|
|
|
|
void PlayerPage::closeOverlay() { m_overlay->hide(); }
|
|
|
|
void PlayerPage::resizeEvent(QResizeEvent *event) {
|
|
int w = event->size().width();
|
|
int h = event->size().height();
|
|
|
|
m_visualizer->setGeometry(0, 0, w, h);
|
|
|
|
int pbHeight = 120;
|
|
m_playback->setGeometry(0, h - pbHeight, w, pbHeight);
|
|
|
|
m_overlay->setGeometry(0, 0, w, h);
|
|
} |