Skip to content

Commit 4be8f20

Browse files
committed
Merge commit '46c429d87f916e2ae1f29bc8afa6c6f06ceca76b' into development
2 parents ab09c01 + 46c429d commit 4be8f20

11 files changed

Lines changed: 1149 additions & 41 deletions

‎src/dmt/app/AbstractPluginEditor.h‎

Lines changed: 209 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,100 @@ class AbstractPluginEditor
2323
using OpenGLContext = juce::OpenGLContext;
2424

2525
public:
26+
// Window sizing mode
27+
enum class WindowMode
28+
{
29+
Fixed,
30+
Dynamic
31+
};
32+
33+
// Configuration for window sizing behavior
34+
struct WindowConfig
35+
{
36+
int baseWidth;
37+
int baseHeight;
38+
WindowMode mode = WindowMode::Fixed;
39+
float minSizeMultiplier = 0.5f;
40+
float maxSizeMultiplier = 2.0f;
41+
float minAspectRatio = 0.5f;
42+
float maxAspectRatio = 2.0f;
43+
int dynamicStartWidth = -1;
44+
int dynamicStartHeight = -1;
45+
};
46+
47+
// Custom constrainer that enforces aspect ratio bounds (min/max aspect
48+
// ratios)
49+
class AspectRatioBoundsConstrainer : public juce::ComponentBoundsConstrainer
50+
{
51+
public:
52+
AspectRatioBoundsConstrainer(float minRatio,
53+
float maxRatio,
54+
int baseW,
55+
int baseH,
56+
int headerH)
57+
: minAspectRatio(minRatio)
58+
, maxAspectRatio(maxRatio)
59+
, baseWidth(baseW)
60+
, baseHeight(baseH)
61+
, headerHeight(headerH)
62+
{
63+
}
64+
65+
void checkBounds(juce::Rectangle<int>& bounds,
66+
const juce::Rectangle<int>& previousBounds,
67+
const juce::Rectangle<int>& limits,
68+
bool isStretchingTop,
69+
bool isStretchingLeft,
70+
bool isStretchingBottom,
71+
bool isStretchingRight) override
72+
{
73+
// First apply default size limits
74+
ComponentBoundsConstrainer::checkBounds(bounds,
75+
previousBounds,
76+
limits,
77+
isStretchingTop,
78+
isStretchingLeft,
79+
isStretchingBottom,
80+
isStretchingRight);
81+
82+
// Then enforce aspect ratio bounds
83+
int w = bounds.getWidth();
84+
int h = bounds.getHeight();
85+
double currentRatio = (double)w / (double)h;
86+
87+
if (currentRatio < minAspectRatio) {
88+
// Too narrow - increase width
89+
w = static_cast<int>(h * minAspectRatio);
90+
} else if (currentRatio > maxAspectRatio) {
91+
// Too wide - decrease width
92+
w = static_cast<int>(h * maxAspectRatio);
93+
}
94+
95+
bounds.setSize(w, h);
96+
}
97+
98+
private:
99+
float minAspectRatio, maxAspectRatio;
100+
int baseWidth, baseHeight, headerHeight;
101+
};
102+
26103
// Strategy function type for layout initialization
27104
using LayoutInitializer = std::function<void(dmt::gui::window::Layout&)>;
28105

106+
// Primary constructor with WindowConfig
29107
AbstractPluginEditor(dmt::app::AbstractPluginProcessor& _p,
30108
juce::String _name,
31-
int _baseWidth,
32-
int _baseHeight,
109+
const WindowConfig& _windowConfig,
33110
LayoutInitializer&& _layoutInit)
34111
: juce::AudioProcessorEditor(&_p)
35112
, p(_p)
36-
, baseWidth(_baseWidth)
37-
, baseHeight(_baseHeight)
113+
, baseWidth(_windowConfig.baseWidth)
114+
, baseHeight(_windowConfig.baseHeight)
115+
, windowMode(_windowConfig.mode)
116+
, minSizeMultiplier(_windowConfig.minSizeMultiplier)
117+
, maxSizeMultiplier(_windowConfig.maxSizeMultiplier)
118+
, minAspectRatio(_windowConfig.minAspectRatio)
119+
, maxAspectRatio(_windowConfig.maxAspectRatio)
38120
, sizeFactor(p.sizeFactor)
39121
, mainLayout({}, {})
40122
, compositor(_name, mainLayout, p.apvts, p.properties, sizeFactor)
@@ -81,11 +163,46 @@ class AbstractPluginEditor
81163

82164
#endif
83165

84-
setConstraints(baseWidth, baseHeight + headerHeight);
166+
// Configure window constraints based on mode
167+
if (windowMode == WindowMode::Dynamic) {
168+
// Create custom constrainer for aspect ratio bounds
169+
aspectRatioBoundsConstrainer =
170+
std::make_unique<AspectRatioBoundsConstrainer>(
171+
minAspectRatio,
172+
maxAspectRatio,
173+
baseWidth,
174+
baseHeight,
175+
static_cast<int>(dmt::Settings::Header::height));
176+
setConstrainer(aspectRatioBoundsConstrainer.get());
177+
activateDynamicWindowSize(minSizeMultiplier, maxSizeMultiplier);
178+
} else {
179+
setConstraints(baseWidth, baseHeight + headerHeight);
180+
}
85181
setResizable(false, true);
86182

87-
const auto startWidth = baseWidth * sizeFactor;
88-
const auto startHeight = (baseHeight + headerHeight) * sizeFactor;
183+
// Resolve initial window size using priority order
184+
int initialWidth = baseWidth;
185+
int initialHeight = baseHeight + headerHeight;
186+
187+
// Priority 1: Check for saved window state
188+
int savedWidth = p.getSavedWindowWidth();
189+
int savedHeight = p.getSavedWindowHeight();
190+
if (savedWidth > 0 && savedHeight > 0) {
191+
initialWidth = savedWidth;
192+
initialHeight = savedHeight;
193+
isHeaderHidden = p.getSavedHeaderHiddenState();
194+
}
195+
// Priority 2: Use dynamic start dimensions if in dynamic mode
196+
else if (windowMode == WindowMode::Dynamic &&
197+
_windowConfig.dynamicStartWidth > 0 &&
198+
_windowConfig.dynamicStartHeight > 0) {
199+
initialWidth = _windowConfig.dynamicStartWidth;
200+
initialHeight = _windowConfig.dynamicStartHeight;
201+
}
202+
// Priority 3: Use base dimensions (default, already set above)
203+
204+
const auto startWidth = initialWidth * sizeFactor;
205+
const auto startHeight = initialHeight * sizeFactor;
89206
setSize(startWidth, startHeight);
90207

91208
// Set the callback for header visibility changes
@@ -94,6 +211,20 @@ class AbstractPluginEditor
94211
});
95212
}
96213

214+
// Deprecated: Use the WindowConfig constructor instead
215+
[[deprecated("Use constructor with WindowConfig parameter instead")]]
216+
AbstractPluginEditor(dmt::app::AbstractPluginProcessor& _p,
217+
juce::String _name,
218+
int _baseWidth,
219+
int _baseHeight,
220+
LayoutInitializer&& _layoutInit)
221+
: AbstractPluginEditor(_p,
222+
_name,
223+
WindowConfig{ _baseWidth, _baseHeight },
224+
std::forward<LayoutInitializer>(_layoutInit))
225+
{
226+
}
227+
97228
~AbstractPluginEditor()
98229
{
99230
// Ensure OpenGL context is detached before destruction
@@ -153,11 +284,6 @@ class AbstractPluginEditor
153284
detachCompositorForResize();
154285
}
155286

156-
//==============================================================================
157-
// Handle peer creation for Windows Direct2D setup
158-
159-
void parentHierarchyChanged() override {}
160-
161287
//==============================================================================
162288
// JUCE overrides
163289

@@ -176,12 +302,51 @@ class AbstractPluginEditor
176302
}
177303
}
178304

305+
void activateDynamicWindowSize(float minRatio, float maxRatio)
306+
{
307+
if (auto* constrainer = this->getConstrainer()) {
308+
constrainer->setFixedAspectRatio(0.0); // Allow variable aspect ratio
309+
// Only limit height; width is controlled by aspect ratio bounds
310+
const auto minHeight = static_cast<int>(baseHeight * minRatio);
311+
const auto maxHeight = static_cast<int>(baseHeight * maxRatio);
312+
// Calculate width range based on aspect ratio bounds and height range
313+
const auto minWidth = static_cast<int>(minHeight * minAspectRatio);
314+
const auto maxWidth = static_cast<int>(maxHeight * maxAspectRatio);
315+
constrainer->setSizeLimits(minWidth, minHeight, maxWidth, maxHeight);
316+
} else {
317+
jassertfalse; // Constrainer not set
318+
}
319+
}
320+
179321
void handleHeaderVisibilityChange(bool isHeaderVisible)
180322
{
323+
isHeaderHidden = !isHeaderVisible;
181324
const int adjustedHeight =
182325
isHeaderVisible ? baseHeight + headerHeight : baseHeight;
183-
setConstraints(baseWidth, adjustedHeight);
184-
setSize(baseWidth * sizeFactor, adjustedHeight * sizeFactor);
326+
327+
if (windowMode == WindowMode::Dynamic) {
328+
// In dynamic mode, recalculate size limits but keep aspect ratio bounds
329+
if (auto* constrainer = this->getConstrainer()) {
330+
const auto minHeight =
331+
static_cast<int>(adjustedHeight * minSizeMultiplier);
332+
const auto maxHeight =
333+
static_cast<int>(adjustedHeight * maxSizeMultiplier);
334+
const auto minWidth = static_cast<int>(minHeight * minAspectRatio);
335+
const auto maxWidth = static_cast<int>(maxHeight * maxAspectRatio);
336+
constrainer->setSizeLimits(minWidth, minHeight, maxWidth, maxHeight);
337+
}
338+
// Keep width the same, only adjust height by header height
339+
int heightAdjustment = static_cast<int>(
340+
(isHeaderVisible ? headerHeight : -headerHeight) * sizeFactor);
341+
setSize(getWidth(), getHeight() + heightAdjustment);
342+
} else {
343+
// In fixed mode, enforce fixed aspect ratio
344+
setConstraints(baseWidth, adjustedHeight);
345+
setSize(baseWidth * sizeFactor, adjustedHeight * sizeFactor);
346+
}
347+
348+
// Save the header visibility state
349+
p.saveWindowState(getWidth(), getHeight(), isHeaderHidden);
185350
}
186351

187352
dmt::gui::window::Layout& getMainLayout() { return mainLayout; }
@@ -218,30 +383,36 @@ class AbstractPluginEditor
218383
void attachCompositorAfterResize()
219384
{
220385
if (!compositorAttached) {
221-
// Snap to the correct aspect ratio, considering header visibility
222-
auto bounds = getLocalBounds();
223-
bool headerVisible = compositor.isHeaderVisible();
224-
int aspectHeight =
225-
headerVisible ? (baseHeight + headerHeight) : baseHeight;
226-
const double aspect = (double)baseWidth / (double)aspectHeight;
227-
int w = bounds.getWidth();
228-
int h = bounds.getHeight();
229-
double currentAspect = (double)w / (double)h;
230-
231-
if (currentAspect > aspect) {
232-
// Too wide, adjust width
233-
w = static_cast<int>(h * aspect);
234-
} else if (currentAspect < aspect) {
235-
// Too tall, adjust height
236-
h = static_cast<int>(w / aspect);
386+
// For fixed mode, snap to the correct aspect ratio
387+
// For dynamic mode, keep the current size as-is
388+
if (windowMode == WindowMode::Fixed) {
389+
auto bounds = getLocalBounds();
390+
bool headerVisible = compositor.isHeaderVisible();
391+
int aspectHeight =
392+
headerVisible ? (baseHeight + headerHeight) : baseHeight;
393+
const double aspect = (double)baseWidth / (double)aspectHeight;
394+
int w = bounds.getWidth();
395+
int h = bounds.getHeight();
396+
double currentAspect = (double)w / (double)h;
397+
398+
if (currentAspect > aspect) {
399+
// Too wide, adjust width
400+
w = static_cast<int>(h * aspect);
401+
} else if (currentAspect < aspect) {
402+
// Too tall, adjust height
403+
h = static_cast<int>(w / aspect);
404+
}
405+
setSize(w, h);
237406
}
238-
setSize(w, h);
239407

240408
// Set compositor bounds to fill the editor
241409
addAndMakeVisible(compositor);
242410
compositor.setBounds(getLocalBounds());
243411
compositorAttached = true;
244412
repaint();
413+
414+
// Save the current window state after size correction
415+
p.saveWindowState(getWidth(), getHeight(), isHeaderHidden);
245416
}
246417
}
247418

@@ -333,6 +504,12 @@ class AbstractPluginEditor
333504
const int& headerHeight = dmt::Settings::Header::height;
334505
const int baseWidth;
335506
const int baseHeight;
507+
WindowMode windowMode = WindowMode::Fixed;
508+
float minSizeMultiplier = 0.5f;
509+
float maxSizeMultiplier = 2.0f;
510+
float minAspectRatio = 0.5f;
511+
float maxAspectRatio = 2.0f;
512+
bool isHeaderHidden = false;
336513
int lastWidth = baseWidth;
337514
int lastHeight = baseHeight;
338515
double ratio = baseWidth / baseHeight;
@@ -349,6 +526,7 @@ class AbstractPluginEditor
349526
dmt::gui::window::Compositor compositor;
350527

351528
OpenGLContext openGLContext;
529+
std::unique_ptr<AspectRatioBoundsConstrainer> aspectRatioBoundsConstrainer;
352530
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AbstractPluginEditor)
353531
};
354532
} // namespace app

‎src/dmt/app/AbstractPluginProcessor.h‎

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ class AbstractPluginProcessor : public juce::AudioProcessor
1212
//==============================================================================
1313
AbstractPluginProcessor(
1414
std::function<juce::AudioProcessorValueTreeState::ParameterLayout()>
15-
createParameterLayout)
15+
createParameterLayout,
16+
dmt::configuration::SettingsOverrides overrides = {},
17+
dmt::configuration::SettingsReplacements replacements = {})
1618
: AudioProcessor(
1719
BusesProperties()
1820
#if !JucePlugin_IsMidiEffect
@@ -27,7 +29,7 @@ class AbstractPluginProcessor : public juce::AudioProcessor
2729
#if PERFETTO
2830
MelatoninPerfetto::get().beginSession();
2931
#endif
30-
properties.initialize();
32+
properties.initialize(overrides, replacements);
3133
}
3234

3335
//==============================================================================
@@ -156,6 +158,31 @@ class AbstractPluginProcessor : public juce::AudioProcessor
156158
float getSizeFactor() const { return sizeFactor; }
157159
void setSizeFactor(float newSize) { sizeFactor = newSize; }
158160

161+
//==============================================================================
162+
// Window State Management
163+
164+
void saveWindowState(int width, int height, bool isHeaderHidden)
165+
{
166+
apvts.state.setProperty("windowWidth", width, nullptr);
167+
apvts.state.setProperty("windowHeight", height, nullptr);
168+
apvts.state.setProperty("isHeaderHidden", isHeaderHidden, nullptr);
169+
}
170+
171+
int getSavedWindowWidth() const
172+
{
173+
return static_cast<int>(apvts.state.getProperty("windowWidth", -1));
174+
}
175+
176+
int getSavedWindowHeight() const
177+
{
178+
return static_cast<int>(apvts.state.getProperty("windowHeight", -1));
179+
}
180+
181+
bool getSavedHeaderHiddenState() const
182+
{
183+
return static_cast<bool>(apvts.state.getProperty("isHeaderHidden", false));
184+
}
185+
159186
//==============================================================================
160187
private:
161188
#if PERFETTO

0 commit comments

Comments
 (0)