2026-09-16 17:47:45 +02:00
|
|
|
#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")),
|
2026-09-16 21:16:37 +02:00
|
|
|
smoothingKnob(*p.apvts.getParameter("smoothing")),
|
|
|
|
|
afterglowKnob(*p.apvts.getParameter("afterglow"))
|
2026-09-16 17:47:45 +02:00
|
|
|
{
|
|
|
|
|
setSize(kBaseWidth, kBaseHeight);
|
|
|
|
|
setResizable(true, true);
|
|
|
|
|
|
|
|
|
|
addAndMakeVisible(scope);
|
|
|
|
|
|
|
|
|
|
// Fade knob
|
|
|
|
|
addAndMakeVisible(fadeKnob);
|
|
|
|
|
fadeKnob.setRange(0.1, 10.0);
|
|
|
|
|
fadeAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
|
|
|
|
|
processorRef.apvts, "fadeTime", fadeKnob);
|
|
|
|
|
|
|
|
|
|
// Sensitivity knob
|
|
|
|
|
addAndMakeVisible(sensitivityKnob);
|
|
|
|
|
sensitivityKnob.setRange(-12.0, 24.0);
|
|
|
|
|
sensitivityAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
|
|
|
|
|
processorRef.apvts, "sensitivity", sensitivityKnob);
|
|
|
|
|
|
|
|
|
|
// Dot size knob
|
|
|
|
|
addAndMakeVisible(dotSizeKnob);
|
2026-09-16 21:16:37 +02:00
|
|
|
dotSizeKnob.setRange(0.5, 8.0);
|
2026-09-16 17:47:45 +02:00
|
|
|
dotSizeAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
|
|
|
|
|
processorRef.apvts, "dotSize", dotSizeKnob);
|
|
|
|
|
|
|
|
|
|
// Smoothing knob
|
|
|
|
|
addAndMakeVisible(smoothingKnob);
|
|
|
|
|
smoothingKnob.setRange(0.0, 0.99);
|
|
|
|
|
smoothingAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
|
|
|
|
|
processorRef.apvts, "smoothing", smoothingKnob);
|
|
|
|
|
|
2026-09-16 21:16:37 +02:00
|
|
|
// Afterglow knob
|
|
|
|
|
addAndMakeVisible(afterglowKnob);
|
|
|
|
|
afterglowKnob.setRange(0.0, 5.0);
|
|
|
|
|
afterglowAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
|
|
|
|
|
processorRef.apvts, "afterglow", afterglowKnob);
|
|
|
|
|
|
2026-09-16 17:47:45 +02:00
|
|
|
// Freeze button
|
|
|
|
|
addAndMakeVisible(freezeButton);
|
2026-09-17 11:29:01 +02:00
|
|
|
freezeButton.setButtonText("Freeze");
|
2026-09-16 17:47:45 +02:00
|
|
|
freezeButton.setColour(juce::ToggleButton::textColourId, juce::Colour(0xff888899));
|
|
|
|
|
freezeButton.setColour(juce::ToggleButton::tickColourId, juce::Colour(0xff7ecfcf));
|
|
|
|
|
freezeAttachment = std::make_unique<juce::AudioProcessorValueTreeState::ButtonAttachment>(
|
|
|
|
|
processorRef.apvts, "freeze", freezeButton);
|
|
|
|
|
|
2026-09-16 18:38:35 +02:00
|
|
|
// Mono button
|
|
|
|
|
addAndMakeVisible(monoButton);
|
2026-09-17 11:29:01 +02:00
|
|
|
monoButton.setButtonText("Mono");
|
2026-09-16 18:38:35 +02:00
|
|
|
monoButton.setColour(juce::ToggleButton::textColourId, juce::Colour(0xff888899));
|
|
|
|
|
monoButton.setColour(juce::ToggleButton::tickColourId, juce::Colour(0xff7ecfcf));
|
|
|
|
|
monoAttachment = std::make_unique<juce::AudioProcessorValueTreeState::ButtonAttachment>(
|
|
|
|
|
processorRef.apvts, "mono", monoButton);
|
|
|
|
|
|
2026-09-16 17:47:45 +02:00
|
|
|
// Scale dropdown
|
|
|
|
|
addAndMakeVisible(scaleBox);
|
|
|
|
|
scaleBox.addItem("100%", 1);
|
|
|
|
|
scaleBox.addItem("125%", 2);
|
|
|
|
|
scaleBox.addItem("150%", 3);
|
|
|
|
|
scaleBox.addItem("200%", 4);
|
2026-09-16 21:16:37 +02:00
|
|
|
scaleBox.addItem("250%", 5);
|
|
|
|
|
scaleBox.addItem("300%", 6);
|
|
|
|
|
scaleBox.setSelectedId(3, juce::dontSendNotification);
|
2026-09-16 17:47:45 +02:00
|
|
|
scaleBox.onChange = [this] { scaleChanged(); };
|
|
|
|
|
scaleChanged();
|
|
|
|
|
|
2026-09-16 18:38:35 +02:00
|
|
|
// After any scale change, recompute the resize limits.
|
|
|
|
|
const int maxDim = static_cast<int>(kBaseWidth * kMaxScale);
|
|
|
|
|
setResizeLimits(kBaseWidth, kBaseHeight, maxDim, maxDim);
|
|
|
|
|
|
2026-09-16 17:47:45 +02:00
|
|
|
// 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.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());
|
2026-09-16 21:16:37 +02:00
|
|
|
setupLabel(afterglowLabel, afterglowKnob.getParameterName());
|
2026-09-16 17:47:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
2026-09-16 21:16:37 +02:00
|
|
|
case 5: currentScale = 2.5f; break;
|
|
|
|
|
case 6: currentScale = 3.0f; break;
|
|
|
|
|
default: currentScale = 1.5f; break;
|
2026-09-16 17:47:45 +02:00
|
|
|
}
|
|
|
|
|
|
2026-09-16 18:38:35 +02:00
|
|
|
const int maxDim = static_cast<int>(kBaseWidth * kMaxScale);
|
|
|
|
|
setResizeLimits(kBaseWidth, kBaseHeight, maxDim, maxDim);
|
2026-09-16 21:16:37 +02:00
|
|
|
scope.setScale(currentScale);
|
2026-09-16 17:47:45 +02:00
|
|
|
setSize(static_cast<int>(kBaseWidth * currentScale),
|
|
|
|
|
static_cast<int>(kBaseHeight * currentScale));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TerionProcessorEditor::paint(juce::Graphics& g)
|
|
|
|
|
{
|
|
|
|
|
g.fillAll(juce::Colour(0xff0a0a0f));
|
|
|
|
|
|
2026-09-16 18:38:35 +02:00
|
|
|
const float s = currentScale;
|
|
|
|
|
const int footerH = static_cast<int>(kFooterHeight * s);
|
|
|
|
|
|
2026-09-16 17:47:45 +02:00
|
|
|
// Footer
|
|
|
|
|
auto bounds = getLocalBounds();
|
2026-09-16 18:38:35 +02:00
|
|
|
auto footer = bounds.removeFromBottom(footerH);
|
2026-09-16 17:47:45 +02:00
|
|
|
|
|
|
|
|
g.setColour(juce::Colour(0xff15151e));
|
|
|
|
|
g.fillRect(footer);
|
|
|
|
|
|
|
|
|
|
g.setColour(juce::Colour(0xff555566));
|
2026-09-16 18:38:35 +02:00
|
|
|
g.setFont(juce::Font(juce::FontOptions(9.0f * s)));
|
2026-09-16 17:47:45 +02:00
|
|
|
|
2026-09-17 11:29:01 +02:00
|
|
|
juce::String footerText = "Terion (Git: "
|
2026-09-16 17:47:45 +02:00
|
|
|
+ juce::String(TERION_GIT_HASH)
|
2026-09-17 11:29:01 +02:00
|
|
|
+ ", " + juce::String(TERION_GIT_DIRTY)
|
|
|
|
|
+ ") | built: " + juce::String(TERION_BUILD_TIME);
|
2026-09-16 17:47:45 +02:00
|
|
|
|
2026-09-16 18:38:35 +02:00
|
|
|
g.drawFittedText(footerText, footer.reduced(static_cast<int>(10.0f * s), 0),
|
2026-09-16 17:47:45 +02:00
|
|
|
juce::Justification::centredLeft, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TerionProcessorEditor::resized()
|
|
|
|
|
{
|
2026-09-16 18:38:35 +02:00
|
|
|
const float s = currentScale;
|
|
|
|
|
const int footerH = static_cast<int>(kFooterHeight * s);
|
|
|
|
|
const int knobs = static_cast<int>(kKnobSize * s);
|
|
|
|
|
const int margin = static_cast<int>(kKnobMargin * s);
|
|
|
|
|
const int scaleW = static_cast<int>(76.0f * s);
|
2026-09-17 11:29:01 +02:00
|
|
|
const int gap = static_cast<int>(8.0f * s);
|
2026-09-16 17:47:45 +02:00
|
|
|
|
2026-09-16 18:38:35 +02:00
|
|
|
auto bounds = getLocalBounds();
|
|
|
|
|
auto footer = bounds.removeFromBottom(footerH);
|
|
|
|
|
|
2026-09-17 11:29:01 +02:00
|
|
|
// Footer: scale dropdown with an 8px gap on the top and right
|
|
|
|
|
scaleBox.setBounds(footer.getWidth() - scaleW - gap, gap, scaleW, footerH - 2 - gap);
|
2026-09-16 17:47:45 +02:00
|
|
|
|
|
|
|
|
// Scope fills the remaining area
|
|
|
|
|
scope.setBounds(bounds);
|
|
|
|
|
|
2026-09-17 11:29:01 +02:00
|
|
|
// Controls layout: knob row sits 8px above the footer, Freeze/Mono stay top-right
|
2026-09-16 21:16:37 +02:00
|
|
|
const int numKnobs = 5;
|
|
|
|
|
const int rowH = knobs + static_cast<int>(12.0f * s) - static_cast<int>(6.0f * s);
|
|
|
|
|
const int startX = bounds.getWidth() - (numKnobs * (knobs + margin) + knobs + margin);
|
|
|
|
|
const int startY = bounds.getHeight() - rowH - gap;
|
2026-09-16 17:47:45 +02:00
|
|
|
|
2026-09-16 18:38:35 +02:00
|
|
|
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);
|
2026-09-16 21:16:37 +02:00
|
|
|
afterglowKnob.setBounds(startX + 4 * (knobs + margin), startY, knobs, knobs);
|
2026-09-17 11:29:01 +02:00
|
|
|
const int freezeY = static_cast<int>(20.0f * s);
|
|
|
|
|
const int freezeH = knobs - static_cast<int>(20.0f * s);
|
|
|
|
|
freezeButton.setBounds(bounds.getWidth() - knobs - margin, freezeY, knobs, freezeH);
|
|
|
|
|
monoButton.setBounds(bounds.getWidth() - knobs - margin, freezeY + freezeH + gap,
|
|
|
|
|
knobs, freezeH);
|
2026-09-16 17:47:45 +02:00
|
|
|
|
2026-09-16 18:38:35 +02:00
|
|
|
const int labelY = startY + knobs - static_cast<int>(6.0f * s);
|
|
|
|
|
const int labelH = static_cast<int>(12.0f * s);
|
2026-09-16 21:16:37 +02:00
|
|
|
const float labelFont = 9.0f * s;
|
2026-09-16 18:38:35 +02:00
|
|
|
fadeLabel.setBounds(fadeKnob.getX(), labelY, knobs, labelH);
|
2026-09-16 21:16:37 +02:00
|
|
|
fadeLabel.setFont(juce::Font(juce::FontOptions(labelFont)));
|
2026-09-16 18:38:35 +02:00
|
|
|
sensitivityLabel.setBounds(sensitivityKnob.getX(), labelY, knobs, labelH);
|
2026-09-16 21:16:37 +02:00
|
|
|
sensitivityLabel.setFont(juce::Font(juce::FontOptions(labelFont)));
|
2026-09-16 18:38:35 +02:00
|
|
|
dotSizeLabel.setBounds(dotSizeKnob.getX(), labelY, knobs, labelH);
|
2026-09-16 21:16:37 +02:00
|
|
|
dotSizeLabel.setFont(juce::Font(juce::FontOptions(labelFont)));
|
2026-09-16 18:38:35 +02:00
|
|
|
smoothingLabel.setBounds(smoothingKnob.getX(), labelY, knobs, labelH);
|
2026-09-16 21:16:37 +02:00
|
|
|
smoothingLabel.setFont(juce::Font(juce::FontOptions(labelFont)));
|
|
|
|
|
afterglowLabel.setBounds(afterglowKnob.getX(), labelY, knobs, labelH);
|
|
|
|
|
afterglowLabel.setFont(juce::Font(juce::FontOptions(labelFont)));
|
2026-09-16 17:47:45 +02:00
|
|
|
}
|