Skip to content

Commit 0aad995

Browse files
authored
Default the spectrum and color editor windows to always-on-top (#367)
Add a pin/unpin toggle to each window so the user can opt out per session. Hosts like Reaper pin the plugin window above all peers, which would otherwise hide these floating windows. Assisted-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 0811a6b commit 0aad995

3 files changed

Lines changed: 84 additions & 2 deletions

File tree

src/ui/six-sines-editor.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ CMRC_DECLARE(sixsines_fonts);
4848
#include "spectrum-analyzer.h"
4949
#include <sst/jucegui/components/GlyphButton.h>
5050
#include <sst/jucegui/components/ButtonPainter.h>
51+
#include <sst/jucegui/component-adapters/DiscreteToReference.h>
5152
#include "macro-sub-panel.h"
5253
#include "clipboard.h"
5354
#include "patch-data-bindings.h"
@@ -1883,6 +1884,12 @@ void SixSinesEditor::openColorEditor()
18831884
std::unique_ptr<jscr::ColorEditor> colorEditor;
18841885
std::unique_ptr<jcmp::TextPushButton> saveBtn, loadBtn, factoryBtn;
18851886

1887+
bool alwaysOnTopState{true};
1888+
std::unique_ptr<
1889+
sst::jucegui::component_adapters::DiscreteToValueReference<jcmp::ToggleButton, bool>>
1890+
alwaysOnTopAdapter;
1891+
std::function<void(bool)> onAlwaysOnTopChanged;
1892+
18861893
ColorEditorContent(std::unique_ptr<jscr::ColorEditor> ce,
18871894
std::unique_ptr<jcmp::TextPushButton> sb,
18881895
std::unique_ptr<jcmp::TextPushButton> lb,
@@ -1895,6 +1902,21 @@ void SixSinesEditor::openColorEditor()
18951902
addAndMakeVisible(*saveBtn);
18961903
addAndMakeVisible(*loadBtn);
18971904
addAndMakeVisible(*factoryBtn);
1905+
1906+
alwaysOnTopAdapter =
1907+
std::make_unique<sst::jucegui::component_adapters::DiscreteToValueReference<
1908+
jcmp::ToggleButton, bool>>(alwaysOnTopState);
1909+
auto *btn = alwaysOnTopAdapter->widget.get();
1910+
btn->setGlyph(jcmp::GlyphPainter::PIN);
1911+
btn->setOffGlyph(jcmp::GlyphPainter::UNPIN);
1912+
btn->setTitle("Always on Top");
1913+
addAndMakeVisible(*btn);
1914+
alwaysOnTopAdapter->onValueChanged = [this](bool b)
1915+
{
1916+
if (onAlwaysOnTopChanged)
1917+
onAlwaysOnTopChanged(b);
1918+
};
1919+
18981920
// setStyle propagates recursively to all StyleConsumer children.
18991921
setStyle(ss);
19001922
}
@@ -1905,6 +1927,10 @@ void SixSinesEditor::openColorEditor()
19051927
auto strip = b.removeFromBottom(34);
19061928
colorEditor->setBounds(b);
19071929
strip = strip.reduced(6, 3);
1930+
// Always-on-top toggle on the right edge of the button strip.
1931+
auto pinSz = strip.getHeight();
1932+
alwaysOnTopAdapter->widget->setBounds(strip.removeFromRight(pinSz));
1933+
strip.removeFromRight(6);
19081934
saveBtn->setBounds(strip.removeFromLeft(120));
19091935
strip.removeFromLeft(6);
19101936
loadBtn->setBounds(strip.removeFromLeft(120));
@@ -1921,6 +1947,11 @@ void SixSinesEditor::openColorEditor()
19211947
{
19221948
setUsingNativeTitleBar(true);
19231949
setResizable(true, false);
1950+
// Default on-top so hosts (e.g. Reaper) that pin the plugin window above
1951+
// peers don't bury this. The pin toggle in the button strip lets the user
1952+
// opt out per session.
1953+
setAlwaysOnTop(true);
1954+
content->onAlwaysOnTopChanged = [this](bool b) { setAlwaysOnTop(b); };
19241955
setContentOwned(content.release(), true);
19251956
setSize(420, 560);
19261957
}

src/ui/spectrum-analyzer.cpp

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,14 @@
1919
#include <chrono>
2020
#include <cmath>
2121

22+
#include <sst/jucegui/components/GlyphPainter.h>
23+
#include <sst/jucegui/style/StyleSheet.h>
24+
2225
namespace baconpaul::six_sines::ui
2326
{
2427

28+
namespace jcmp = sst::jucegui::components;
29+
2530
namespace
2631
{
2732
// dB range for both spectrogram colormap and line plot
@@ -64,6 +69,25 @@ SpectrumAnalyzerComponent::SpectrumAnalyzerComponent(Synth::audioOutputQueue_t &
6469
if (hostSr > 0.f)
6570
hostSampleRate = hostSr;
6671
setOpaque(true);
72+
73+
alwaysOnTopAdapter = std::make_unique<
74+
sst::jucegui::component_adapters::DiscreteToValueReference<jcmp::ToggleButton, bool>>(
75+
alwaysOnTopState);
76+
auto *btn = alwaysOnTopAdapter->widget.get();
77+
btn->setGlyph(jcmp::GlyphPainter::PIN);
78+
btn->setOffGlyph(jcmp::GlyphPainter::UNPIN);
79+
btn->setTitle("Always on Top");
80+
addAndMakeVisible(*btn);
81+
alwaysOnTopAdapter->onValueChanged = [this](bool b)
82+
{
83+
if (onAlwaysOnTopChanged)
84+
onAlwaysOnTopChanged(b);
85+
};
86+
87+
setStyle(sst::jucegui::style::StyleSheet::getBuiltInStyleSheet(
88+
sst::jucegui::style::StyleSheet::DARK));
89+
90+
// setSize last so the resulting resized() positions the toggle.
6791
setSize(720, 690);
6892

6993
int savedMode = defaultModeIdx;
@@ -103,7 +127,15 @@ SpectrumAnalyzerComponent::~SpectrumAnalyzerComponent()
103127
ring.clear();
104128
}
105129

106-
void SpectrumAnalyzerComponent::resized() {}
130+
void SpectrumAnalyzerComponent::resized()
131+
{
132+
if (alwaysOnTopAdapter)
133+
{
134+
constexpr int sz = 20;
135+
constexpr int pad = 4;
136+
alwaysOnTopAdapter->widget->setBounds(getWidth() - sz - pad, pad, sz, sz);
137+
}
138+
}
107139

108140
void SpectrumAnalyzerComponent::mouseDown(const juce::MouseEvent &e)
109141
{
@@ -587,6 +619,11 @@ SpectrumAnalyzerWindow::SpectrumAnalyzerWindow(std::unique_ptr<SpectrumAnalyzerC
587619
{
588620
setUsingNativeTitleBar(true);
589621
setResizable(true, false);
622+
// Default on-top so the window isn't hidden by hosts (e.g. Reaper) that pin the
623+
// plugin window above all peers. The toggle in the component's upper-right lets
624+
// the user opt out per session.
625+
setAlwaysOnTop(true);
626+
content->onAlwaysOnTopChanged = [this](bool b) { setAlwaysOnTop(b); };
590627
setContentOwned(content.release(), true);
591628
setSize(720, 690);
592629
}

src/ui/spectrum-analyzer.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,17 @@
2727
#include <juce_gui_basics/juce_gui_basics.h>
2828
#include <juce_dsp/juce_dsp.h>
2929

30+
#include <sst/jucegui/components/WindowPanel.h>
31+
#include <sst/jucegui/components/ToggleButton.h>
32+
#include <sst/jucegui/component-adapters/DiscreteToReference.h>
33+
3034
#include "synth/synth.h"
3135
#include "ui-defaults.h"
3236

3337
namespace baconpaul::six_sines::ui
3438
{
3539

36-
struct SpectrumAnalyzerComponent : juce::Component, private juce::AsyncUpdater
40+
struct SpectrumAnalyzerComponent : sst::jucegui::components::WindowPanel, private juce::AsyncUpdater
3741
{
3842
static constexpr int spectrogramColumns = 256;
3943

@@ -65,6 +69,9 @@ struct SpectrumAnalyzerComponent : juce::Component, private juce::AsyncUpdater
6569
// Pushed by the editor when the host sample rate changes; rebuilds buffers if needed.
6670
void setHostSampleRate(float sr);
6771

72+
// Fired by the always-on-top toggle. The window wires this to setAlwaysOnTop().
73+
std::function<void(bool)> onAlwaysOnTopChanged;
74+
6875
private:
6976
void handleAsyncUpdate() override;
7077
void analysisThreadMain();
@@ -131,6 +138,13 @@ struct SpectrumAnalyzerComponent : juce::Component, private juce::AsyncUpdater
131138

132139
std::thread analysisThread;
133140
std::atomic<bool> running{false};
141+
142+
// Always-on-top toggle. Declared after `alwaysOnTopState` so the adapter (and its
143+
// owned widget) is destroyed before the bool it references.
144+
bool alwaysOnTopState{true};
145+
std::unique_ptr<sst::jucegui::component_adapters::DiscreteToValueReference<
146+
sst::jucegui::components::ToggleButton, bool>>
147+
alwaysOnTopAdapter;
134148
};
135149

136150
struct SpectrumAnalyzerWindow : juce::DocumentWindow

0 commit comments

Comments
 (0)