#include "PluginEditor.h" TerionProcessorEditor::TerionProcessorEditor(TerionProcessor& p) : AudioProcessorEditor(p), processorRef(p), scope(p), fadeKnob(*p.apvts.getParameter("fadeTime")), sensitivityKnob(*p.apvts.getParameter("sensitivity")), dotSizeKnob(*p.apvts.getParameter("dotSize")), smoothingKnob(*p.apvts.getParameter("smoothing")) { setSize(kBaseWidth, kBaseHeight); setResizable(true, true); addAndMakeVisible(scope); // Fade knob addAndMakeVisible(fadeKnob); fadeKnob.setRange(0.1, 10.0); fadeAttachment = std::make_unique( processorRef.apvts, "fadeTime", fadeKnob); // Sensitivity knob addAndMakeVisible(sensitivityKnob); sensitivityKnob.setRange(-12.0, 24.0); sensitivityAttachment = std::make_unique( processorRef.apvts, "sensitivity", sensitivityKnob); // Dot size knob addAndMakeVisible(dotSizeKnob); dotSizeKnob.setRange(0.5, 3.0); dotSizeAttachment = std::make_unique( processorRef.apvts, "dotSize", dotSizeKnob); // Smoothing knob addAndMakeVisible(smoothingKnob); smoothingKnob.setRange(0.0, 0.99); smoothingAttachment = std::make_unique( processorRef.apvts, "smoothing", smoothingKnob); // Freeze button addAndMakeVisible(freezeButton); freezeButton.setButtonText("FRZ"); freezeButton.setColour(juce::ToggleButton::textColourId, juce::Colour(0xff888899)); freezeButton.setColour(juce::ToggleButton::tickColourId, juce::Colour(0xff7ecfcf)); freezeAttachment = std::make_unique( processorRef.apvts, "freeze", freezeButton); // Mono button addAndMakeVisible(monoButton); monoButton.setButtonText("MONO"); monoButton.setColour(juce::ToggleButton::textColourId, juce::Colour(0xff888899)); monoButton.setColour(juce::ToggleButton::tickColourId, juce::Colour(0xff7ecfcf)); monoAttachment = std::make_unique( processorRef.apvts, "mono", monoButton); // Scale dropdown addAndMakeVisible(scaleBox); scaleBox.addItem("100%", 1); scaleBox.addItem("125%", 2); scaleBox.addItem("150%", 3); scaleBox.addItem("200%", 4); scaleBox.addItem("300%", 5); scaleBox.setSelectedId(2, juce::dontSendNotification); scaleBox.onChange = [this] { scaleChanged(); }; scaleChanged(); // After any scale change, recompute the resize limits. const int maxDim = static_cast(kBaseWidth * kMaxScale); setResizeLimits(kBaseWidth, kBaseHeight, maxDim, maxDim); // Knob labels (separate from knobs to ensure correct z-order) auto setupLabel = [this](juce::Label& label, const juce::String& text) { addAndMakeVisible(label); label.setText(text, juce::dontSendNotification); label.setJustificationType(juce::Justification::centredTop); label.setFont(juce::Font(juce::FontOptions(9.0f))); label.setColour(juce::Label::textColourId, juce::Colour(0xff888899)); label.setInterceptsMouseClicks(false, false); }; setupLabel(fadeLabel, fadeKnob.getParameterName()); setupLabel(sensitivityLabel, sensitivityKnob.getParameterName()); setupLabel(dotSizeLabel, dotSizeKnob.getParameterName()); setupLabel(smoothingLabel, smoothingKnob.getParameterName()); } TerionProcessorEditor::~TerionProcessorEditor() {} void TerionProcessorEditor::scaleChanged() { int id = scaleBox.getSelectedId(); switch (id) { case 1: currentScale = 1.0f; break; case 2: currentScale = 1.25f; break; case 3: currentScale = 1.5f; break; case 4: currentScale = 2.0f; break; case 5: currentScale = 3.0f; break; default: currentScale = 1.25f; break; } const int maxDim = static_cast(kBaseWidth * kMaxScale); setResizeLimits(kBaseWidth, kBaseHeight, maxDim, maxDim); setSize(static_cast(kBaseWidth * currentScale), static_cast(kBaseHeight * currentScale)); } void TerionProcessorEditor::paint(juce::Graphics& g) { g.fillAll(juce::Colour(0xff0a0a0f)); const float s = currentScale; const int footerH = static_cast(kFooterHeight * s); // Footer auto bounds = getLocalBounds(); auto footer = bounds.removeFromBottom(footerH); g.setColour(juce::Colour(0xff15151e)); g.fillRect(footer); g.setColour(juce::Colour(0xff555566)); g.setFont(juce::Font(juce::FontOptions(9.0f * s))); juce::String footerText = "Terion " + juce::String(TERION_GIT_HASH) + " | " + juce::String(TERION_BUILD_TIME) + " | " + juce::String(TERION_GIT_LOCAL_COMMITS) + " local"; g.drawFittedText(footerText, footer.reduced(static_cast(10.0f * s), 0), juce::Justification::centredLeft, 1); } void TerionProcessorEditor::resized() { const float s = currentScale; const int footerH = static_cast(kFooterHeight * s); const int knobs = static_cast(kKnobSize * s); const int margin = static_cast(kKnobMargin * s); const int scaleW = static_cast(76.0f * s); const int monoW = static_cast(72.0f * s); auto bounds = getLocalBounds(); auto footer = bounds.removeFromBottom(footerH); // Footer: scale dropdown + mono toggle on the right scaleBox.setBounds(footer.getWidth() - scaleW, 1, scaleW, footerH - 2); monoButton.setBounds(footer.getWidth() - scaleW - monoW, 1, monoW, footerH - 2); // Scope fills the remaining area scope.setBounds(bounds); // Controls layout: top-right corner, horizontal row const int startX = bounds.getWidth() - (4 * (knobs + margin) + knobs + margin); const int startY = static_cast(10.0f * s); fadeKnob.setBounds(startX, startY, knobs, knobs); sensitivityKnob.setBounds(startX + (knobs + margin), startY, knobs, knobs); dotSizeKnob.setBounds(startX + 2 * (knobs + margin), startY, knobs, knobs); smoothingKnob.setBounds(startX + 3 * (knobs + margin), startY, knobs, knobs); freezeButton.setBounds(startX + 4 * (knobs + margin), startY + static_cast(10.0f * s), knobs, knobs - static_cast(20.0f * s)); const int labelY = startY + knobs - static_cast(6.0f * s); const int labelH = static_cast(12.0f * s); fadeLabel.setBounds(fadeKnob.getX(), labelY, knobs, labelH); sensitivityLabel.setBounds(sensitivityKnob.getX(), labelY, knobs, labelH); dotSizeLabel.setBounds(dotSizeKnob.getX(), labelY, knobs, labelH); smoothingLabel.setBounds(smoothingKnob.getX(), labelY, knobs, labelH); }