Consistency is huge when writing UI code. Cohesive components go a long way towards making your plugin look more professional. The normal way of laying out components in your plugin’s UI is to use juce::Rectangle
. This method is super useful but has its pain points. One specific one that really annoys me is laying out rows or columns. I’ve spent who knows how long manually measuring out the margins and laying out rows of components. In general I feel like laying out components using a raw juce::Rectangle
tends to be pretty verbose. To remedy this I wrote up a simple little class called Layout
.
The basic idea was to wrap up juce::Rectangles
and turn them into a tool specifically for laying out child components. You can think of it as a lifetime for juce::Rectangle
. Kind of like a life area. You slice away sections of the contained rectangle and apply them to components as normal. Once you’re out of area in the rectangle it becomes invalid.
The really powerful thing here is that the apply()
function is a variadic template. It accepts any number of any object that has a setBounds()
method. This means you can create container structs and lay them out as if they were JUCE components. The applyAsRow()
function is also a variadic template and lays its arguments out as a row. The resized()
code in PFT is significantly more readable because of this class.
Here’s the first version of the resized code for the Lfo modulator:
auto b{ getLocalBounds() };
const int w{ (b.getWidth() - Layout::Margin * 2) / 3 };
const int h{ (b.getHeight() - Layout::Margin * 3) / 4 };
auto left{ b.removeFromLeft(w) };
m_p->lfoTypeCbx.setBounds(left.removeFromTop(h));
left.removeFromTop(Layout::Margin);
m_p->syncTypeCbx.setBounds(left.removeFromTop(h));
left.removeFromTop(Layout::Margin);
m_p->freqSlider.setBounds(left.removeFromTop(h));
left.removeFromTop(Layout::Margin);
m_p->phaseSlider.setBounds(left.removeFromTop(h));
auto right{ b.removeFromRight(w) };
m_p->warp1TypeCbx.setBounds(right.removeFromTop(h));
right.removeFromTop(Layout::Margin);
m_p->warp1Amount.setBounds(right.removeFromTop(h));
right.removeFromTop(Layout::Margin);
m_p->warp2TypeCbx.setBounds(right.removeFromTop(h));
right.removeFromTop(Layout::Margin);
m_p->warp2Amount.setBounds(right.removeFromTop(h));
b.reduce(Layout::Margin * 2, 0);
m_p->lfoOut.setBounds(b);
m_p->lfoPhase.setBounds(b.reduced(Layout::Margin));
Here’s the code after converting it to use the Layout class:
Layout layout{ getLocalBounds() };
const int w{ layout.getSectionWidth(3) };
layout.removeFromLeft(w).applyColumn(m_p->lfoTypeCbx, m_p->syncTypeCbx,m_p->freqSlider, m_p->phaseSlider);
layout.removeFromRight(w).applyColumn(m_p->warp1TypeCbx, m_p->warp1Amount, m_p->warp2TypeCbx, m_p->warp2Amount);
layout.removeHorizontalMargins();
const auto lfoBounds{ layout.release() };
m_p->lfoOut.setBounds(lfoBounds);
m_p->lfoPhase.setBounds(lfoBounds.reduced(Layout::Margin));