From fbca80dfe34a8e64f9abe0933f6dc45a2508b67b Mon Sep 17 00:00:00 2001 From: Armin Date: Mon, 31 Aug 2026 21:39:12 +0200 Subject: [PATCH] Add persistent UI scale parameter (75%..200%) - Add uiScale AudioParameterChoice to APVTS with 5 options (75%, 100%, 125%, 150%, 200%) - Connect uiScaleBox ComboBox to parameter via ComboBoxAttachment - Restore saved scale on editor creation - Scale persists across plugin close/reopen via APVTS state --- Source/PluginEditor.cpp | 51 ++++++++++++++++++++++---------------- Source/PluginEditor.h | 1 + Source/PluginProcessor.cpp | 5 ++++ 3 files changed, 35 insertions(+), 22 deletions(-) diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 24cdfbf..93ee936 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -844,16 +844,11 @@ MainContentComponent::MainContentComponent(ChromaFlockProcessor& p) setupCB(arpOctavesBox, arpOctavesAttach, "arpOctaves", {"1", "2", "3"}); setupCB(arpRateBox, arpRateAttach, "arpRate", {"1/32", "1/16", "1/8", "1/4", "1/2", "1", "2"}); - setupCombo(uiScaleBox); - uiScaleBox.addItem("100%", 1); - uiScaleBox.addItem("125%", 2); - uiScaleBox.addItem("150%", 3); - uiScaleBox.addItem("200%", 4); - uiScaleBox.setSelectedId(1, juce::dontSendNotification); + setupCB(uiScaleBox, uiScaleAttach, "uiScale", {"75%", "100%", "125%", "150%", "200%"}); uiScaleBox.onChange = [this]() { - static const float scales[] = {1.0f, 1.25f, 1.5f, 2.0f}; + static const float scales[] = {0.75f, 1.0f, 1.25f, 1.5f, 2.0f}; int idx = uiScaleBox.getSelectedId() - 1; - if (idx >= 0 && idx < 4 && onScaleChanged) + if (idx >= 0 && idx < 5 && onScaleChanged) onScaleChanged(scales[idx]); }; } @@ -1179,9 +1174,9 @@ void MainContentComponent::paint(juce::Graphics& g) { drawSection(10, 88, 498, 200); drawSection(518, 88, 624, 200); drawSection(1152, 88, 498, 200); - drawSection(10, 298, 563, 160); - drawSection(583, 298, 502, 160); - drawSection(1095, 298, 555, 160); + drawSection(10, 298, 540, 160); + drawSection(560, 298, 540, 160); + drawSection(1110, 298, 540, 160); drawSection(10, 892, 1640, 136); // FX sub-sections @@ -1225,7 +1220,7 @@ void MainContentComponent::paint(juce::Graphics& g) { void MainContentComponent::resized() { auto sectionY = 92; - auto knobSize = 112; + auto knobSize = 93; auto knobSpacing = 10; auto labelH = 18; auto comboH = 48; @@ -1248,16 +1243,16 @@ void MainContentComponent::resized() { phaseOffsetKnob.setBounds(528 + (knobSize + knobSpacing) * 4, knobY2, knobSize, knobSize); filterLabel.setBounds(1152, sectionY, 498, labelH); - filterTypeBox.setBounds(1430, 88, 220, 28); + filterTypeBox.setBounds(1430, 114, 220, 28); // FILTER holds 5 knobs on a single row (the per-note VEL RES knob was // removed); the type dropdown sits in the header row and the knobs span // the section width at the original large size. { - int fSize = 89; + int fSize = knobSize; int fGap = 8; int rowW = 5 * fSize + 4 * fGap; int fSx = 1152 + (498 - rowW) / 2; - int fRowY = 124; + int fRowY = 166; filterCutoffKnob.setBounds(fSx, fRowY, fSize, fSize); filterResKnob.setBounds(fSx + (fSize + fGap), fRowY, fSize, fSize); filterEnvAmtKnob.setBounds(fSx + (fSize + fGap) * 2, fRowY, fSize, fSize); @@ -1265,10 +1260,10 @@ void MainContentComponent::resized() { velToCutKnob.setBounds(fSx + (fSize + fGap) * 4, fRowY, fSize, fSize); } - envLabel.setBounds(10, 302, 563, labelH); + envLabel.setBounds(10, 302, 540, labelH); { int rowW = 4 * knobSize + 3 * knobSpacing; - int sx = 10 + (563 - rowW) / 2; + int sx = 10 + (540 - rowW) / 2; int knobYA = 312 + labelH + 6; envAttackKnob.setBounds(sx, knobYA, knobSize, knobSize); envDecayKnob.setBounds(sx + (knobSize + knobSpacing), knobYA, knobSize, knobSize); @@ -1276,10 +1271,10 @@ void MainContentComponent::resized() { envReleaseKnob.setBounds(sx + (knobSize + knobSpacing) * 3, knobYA, knobSize, knobSize); } - fEnvLabel.setBounds(583, 302, 502, labelH); + fEnvLabel.setBounds(560, 302, 540, labelH); { int rowW = 4 * knobSize + 3 * knobSpacing; - int sx = 583 + (502 - rowW) / 2; + int sx = 560 + (540 - rowW) / 2; int knobYFE = 312 + labelH + 6; fEnvAttackKnob.setBounds(sx, knobYFE, knobSize, knobSize); fEnvDecayKnob.setBounds(sx + (knobSize + knobSpacing), knobYFE, knobSize, knobSize); @@ -1287,10 +1282,10 @@ void MainContentComponent::resized() { fEnvReleaseKnob.setBounds(sx + (knobSize + knobSpacing) * 3, knobYFE, knobSize, knobSize); } - globalLabel.setBounds(1095, 302, 555, labelH); + globalLabel.setBounds(1110, 302, 540, labelH); { int rowW = 4 * knobSize + 3 * knobSpacing; - int sx = 1095 + (555 - rowW) / 2; + int sx = 1110 + (540 - rowW) / 2; int knobYM = 312 + labelH + 6; panKnob.setBounds(sx, knobYM, knobSize, knobSize); driveKnob.setBounds(sx + (knobSize + knobSpacing), knobYM, knobSize, knobSize); @@ -1747,7 +1742,19 @@ ChromaFlockEditor::ChromaFlockEditor(ChromaFlockProcessor& p) : AudioProcessorEditor(&p), content(p) { addAndMakeVisible(content); content.onScaleChanged = [this](float s) { setUIScale(s); }; - setSize(baseWidth, baseHeight); + + // Restore saved UI scale + auto* scaleParam = p.apvts.getRawParameterValue("uiScale"); + float initialScale = 1.0f; + if (scaleParam) { + int idx = static_cast(scaleParam->load()); + static const float scales[] = {0.75f, 1.0f, 1.25f, 1.5f, 2.0f}; + if (idx >= 0 && idx < 5) + initialScale = scales[idx]; + } + currentScale = initialScale; + + setSize(static_cast(baseWidth * currentScale), static_cast(baseHeight * currentScale)); setResizable(false, false); } diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 3216112..ed6d755 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -296,6 +296,7 @@ private: // Arpeggiator attachments std::unique_ptr arpPatternAttach, arpOctavesAttach, arpRateAttach; + std::unique_ptr uiScaleAttach; void setupLabel(juce::Label& label, const juce::String& text); void setupKnob(juce::Slider& knob, const juce::String& name); diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 04074b3..7358e76 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -288,6 +288,11 @@ juce::AudioProcessorValueTreeState::ParameterLayout ChromaFlockProcessor::create juce::ParameterID{"reverbOn", 1}, "Reverb On", juce::StringArray{"Off", "On"}, 1)); + // UI SCALE + layout.add(std::make_unique( + juce::ParameterID{"uiScale", 1}, "UI Scale", + juce::StringArray{"75%", "100%", "125%", "150%", "200%"}, 1)); + // ARPEGGIATOR layout.add(std::make_unique( juce::ParameterID{"arpEnabled", 1}, "Arp On",