#pragma once #include "PluginProcessor.h" #include class SaftpumpeEditor : public juce::AudioProcessorEditor, public juce::Timer, public juce::ComboBox::Listener { public: SaftpumpeEditor(SaftpumpeProcessor&); ~SaftpumpeEditor() override; void paint(juce::Graphics&) override; void resized() override; void timerCallback() override; void comboBoxChanged(juce::ComboBox* box) override; void mouseUp(const juce::MouseEvent& event) override; private: SaftpumpeProcessor& processorRef; float uiScale = 1.0f; // Dark industrial palette juce::Colour bezelColour{0xff0a0a0c}; juce::Colour faceplateTop{0xff222226}; juce::Colour faceplateBot{0xff161618}; juce::Colour knobLight{0xff72727a}; juce::Colour knobDark{0xff3a3a40}; juce::Colour grooveDark{0xff111114}; juce::Colour greenLed{0xff00e676}; juce::Colour redLed{0xffe63333}; juce::Colour amberLed{0xffe6a600}; juce::Colour textColour{0xffb0b0b6}; juce::Colour textDim{0xff6a6a70}; juce::Colour textBright{0xffe0e0e4}; juce::Colour accentLine{0xff00e676}; juce::Colour lcdBg{0xff080c08}; juce::Colour lcdText{0xff00cc66}; juce::Slider thresholdSlider, ratioSlider, attackSlider, releaseSlider; juce::Slider kneeSlider, makeupSlider, mixSlider, autoReleaseSlider; juce::Label thresholdLabel, ratioLabel, attackLabel, releaseLabel; juce::Label kneeLabel, makeupLabel, mixLabel, autoReleaseLabel; // LCD value displays juce::Label thresholdLCD, ratioLCD, attackLCD, releaseLCD; juce::Label kneeLCD, makeupLCD, mixLCD, autoReleaseLCD; juce::ComboBox zoomBox; juce::Label zoomLabel; juce::ComboBox presetBox; juce::Label presetLabel; juce::ToggleButton bypassButton; juce::Label bypassLabel; using SliderAttachment = juce::AudioProcessorValueTreeState::SliderAttachment; using ButtonAttachment = juce::AudioProcessorValueTreeState::ButtonAttachment; std::unique_ptr thresholdAttachment, ratioAttachment, attackAttachment; std::unique_ptr releaseAttachment, kneeAttachment, makeupAttachment; std::unique_ptr mixAttachment, autoReleaseAttachment; std::unique_ptr bypassAttachment; juce::Rectangle meterArea; juce::Rectangle scopeArea; juce::Rectangle zoomArea; juce::Rectangle rowBounds[2]; juce::Rectangle knobBounds[8]; juce::Point knobCentre[8]; float knobRadius = 0.0f; struct PeakHold { float peak = 0.0f; double peakTime = 0.0; static constexpr double holdMs = 1000.0; } grPeak, inPeak, outPeak; void computeLayout(); void drawKnob(juce::Graphics& g, juce::Rectangle bounds, float normalizedValue, float arcFillFrac); void drawLCD(juce::Graphics& g, juce::Rectangle bounds, const juce::String& text); void drawMeter(juce::Graphics& g, juce::Rectangle bounds, float level, float minDb, float maxDb, bool isGainReduction); void drawPeakHold(juce::Graphics& g, juce::Rectangle bounds, float peakNorm, bool fromTop = false); void drawScope(juce::Graphics& g, juce::Rectangle bounds); void drawSpectrum(juce::Graphics& g, juce::Rectangle bounds); void drawScrew(juce::Graphics& g, float cx, float cy, float radius); static constexpr float startAngle = juce::MathConstants::pi * 0.75f; static constexpr float endAngle = juce::MathConstants::pi * 2.25f; juce::String formatValue(int idx, float val); struct Preset { const char* name; float threshold, ratio, attack, release, knee, makeup, mix, autoRelease; }; static const std::vector& getPresets(); void applyPreset(int index); juce::Rectangle presetArea; juce::Rectangle bypassArea; struct NoDotLookAndFeel : juce::LookAndFeel_V4 { void drawRotarySlider(juce::Graphics&, int, int, int, int, float, float, float, juce::Slider&) override {} } lnf; struct ToggleSwitchLNF : juce::LookAndFeel_V4 { void drawToggleButton(juce::Graphics& g, juce::ToggleButton& btn, bool hover, bool /*isDown*/) override { auto bounds = btn.getLocalBounds().toFloat(); float s = bounds.getHeight() / 16.0f; float w = bounds.getWidth(); float h = bounds.getHeight(); float cornerR = h * 0.3f; // Groove g.setColour(juce::Colour(0xff111114)); g.fillRoundedRectangle(bounds, cornerR); g.setColour(juce::Colours::black.withAlpha(0.5f)); g.drawRoundedRectangle(bounds.expanded(0.5f), cornerR, 1.0f); // Slider handle bool on = btn.getToggleState(); float handleW = w * 0.42f; float handleH = h * 0.72f; float handleX = on ? (bounds.getRight() - handleW - 3.0f * s) : (bounds.getX() + 3.0f * s); float handleY = bounds.getY() + (h - handleH) / 2.0f; auto handleBounds = juce::Rectangle(handleX, handleY, handleW, handleH); // Handle shadow g.setColour(juce::Colours::black.withAlpha(0.4f)); g.fillRoundedRectangle(handleBounds.translated(0, 1.0f), cornerR * 0.6f); // Handle body juce::Colour handleCol = on ? juce::Colour(0xff00e676) : juce::Colour(0xff4a4a50); if (hover) handleCol = handleCol.brighter(0.15f); g.setColour(handleCol); g.fillRoundedRectangle(handleBounds, cornerR * 0.6f); // Handle highlight g.setColour(juce::Colours::white.withAlpha(on ? 0.2f : 0.08f)); g.fillRoundedRectangle(handleBounds.withHeight(handleH * 0.4f).translated(0, 1.0f), cornerR * 0.4f); // LED glow when bypass active if (on) { g.setColour(juce::Colour(0xff00e676).withAlpha(0.15f)); g.fillEllipse(bounds.getCentreX() - w * 0.5f, bounds.getCentreY() - h * 0.6f, w * 1.0f, h * 1.2f); } } } toggleLnf; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SaftpumpeEditor) };