#include "EffectsComponent.h" #include "audio/BitCrushEffect.h" #include "PluginEditor.h" EffectsComponent::EffectsComponent(OscirenderAudioProcessor& p, OscirenderAudioProcessorEditor& editor) : audioProcessor(p), itemData(p, editor), listBoxModel(listBox, itemData), grid(p) { setText("Audio Effects"); addAndMakeVisible(frequency); frequency.slider.setSkewFactorFromMidPoint(500.0); frequency.slider.setTextValueSuffix("Hz"); frequency.slider.setValue(audioProcessor.frequencyEffect->getValue(), juce::dontSendNotification); addAndMakeVisible(randomiseButton); randomiseButton.setTooltip("Randomise all effect parameter values, randomise which effects are enabled, and randomise their order."); randomiseButton.onClick = [this] { itemData.randomise(); listBox.updateContent(); }; { juce::MessageManagerLock lock; audioProcessor.broadcaster.addChangeListener(this); } // Wire list model to notify when user wants to add itemData.onAddNewEffectRequested = [this]() { showingGrid = true; grid.setVisible(true); grid.refreshDisabledStates(); listBox.setVisible(false); resized(); repaint(); }; // Decide initial view: show grid only if there are no selected effects bool anySelected = false; { juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock); for (const auto& eff : audioProcessor.toggleableEffects) { const bool isSelected = (eff->selected == nullptr) ? true : eff->selected->getBoolValue(); if (isSelected) { anySelected = true; break; } } } showingGrid = !anySelected; grid.onEffectSelected = [this](const juce::String& effectId) { { juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock); // Mark the chosen effect as selected and enabled, and move it to the end std::shared_ptr chosen; for (auto& eff : audioProcessor.toggleableEffects) { if (eff->getId() == effectId) { eff->selected->setBoolValueNotifyingHost(true); eff->enabled->setBoolValueNotifyingHost(true); chosen = eff; break; } } // Place chosen effect at the end of the visible (selected) list and update precedence if (chosen != nullptr) { int idx = 0; for (auto& e : itemData.data) { if (e != chosen) e->setPrecedence(idx++); } chosen->setPrecedence(idx++); audioProcessor.updateEffectPrecedence(); } } // Refresh list content to include newly selected itemData.resetData(); listBox.updateContent(); showingGrid = false; listBox.setVisible(true); grid.setVisible(false); resized(); repaint(); }; grid.onCanceled = [this]() { // If canceled while default grid, just show list showingGrid = false; listBox.setVisible(true); grid.setVisible(false); resized(); repaint(); }; listBox.setModel(&listBoxModel); addAndMakeVisible(listBox); // Add a small top spacer so the drop indicator can be visible above the first row { auto spacer = std::make_unique(); spacer->setSize(1, LIST_SPACER); // top padding listBox.setHeaderComponent(std::move(spacer)); } // Setup scroll fade mixin initScrollFade(*this); attachToListBox(listBox); // Wire "+ Add new effect" button below the list addEffectButton.onClick = [this]() { if (itemData.onAddNewEffectRequested) itemData.onAddNewEffectRequested(); }; addAndMakeVisible(addEffectButton); addAndMakeVisible(grid); // Keep disabled states in sync whenever grid is shown if (showingGrid) { grid.setVisible(true); grid.refreshDisabledStates(); listBox.setVisible(false); } else { grid.setVisible(false); listBox.setVisible(true); } } EffectsComponent::~EffectsComponent() { juce::MessageManagerLock lock; audioProcessor.broadcaster.removeChangeListener(this); } void EffectsComponent::resized() { auto area = getLocalBounds(); auto titleBar = area.removeFromTop(30); titleBar.removeFromLeft(100); randomiseButton.setBounds(titleBar.removeFromLeft(20)); area = area.reduced(20); frequency.setBounds(area.removeFromTop(30)); area.removeFromTop(6); if (showingGrid) { grid.setBounds(area); addEffectButton.setVisible(false); // Hide fade when grid is shown setScrollFadeVisible(false); } else { // Reserve space at bottom for the add button auto addBtnHeight = 44; auto listArea = area; auto buttonArea = listArea.removeFromBottom(addBtnHeight); listBox.setBounds(listArea); // Layout bottom fade overlay; visible if list is scrollable layoutScrollFade(listArea.withTrimmedTop(LIST_SPACER), true, 48); addEffectButton.setVisible(true); addEffectButton.setBounds(buttonArea.reduced(0, 4)); } } void EffectsComponent::changeListenerCallback(juce::ChangeBroadcaster* source) { itemData.resetData(); listBox.updateContent(); // Re-layout scroll fades after content changes if (! showingGrid) layoutScrollFade(listBox.getBounds().withTrimmedTop(LIST_SPACER), true, 48); }