// src/PlayerControls.cpp #include "PlayerControls.h" #include #include #include #include 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(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::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(brightness * 100.0f)); m_lblBrightness->setText( QString("Bright: %1%").arg(static_cast(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(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); }