Mema
Memory Matrix — multi-channel audio matrix monitor and router
Loading...
Searching...
No Matches
PluginControlComponent.cpp
Go to the documentation of this file.
1/* Copyright (c) 2025, Christian Ahrens
2 *
3 * This file is part of Mema <https://github.com/ChristianAhrens/Mema>
4 *
5 * This tool is free software; you can redistribute it and/or modify it under
6 * the terms of the GNU Lesser General Public License version 3.0 as published
7 * by the Free Software Foundation.
8 *
9 * This tool is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
12 * details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this tool; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
20
21#include <CustomLookAndFeel.h>
22#include <ToggleStateSlider.h>
24
25
26namespace Mema
27{
28
29
32{
33 m_parameterControlsGrid = std::make_unique<juce::Grid>();
34 m_pluginNameLabel = std::make_unique<juce::Label>("pluginName");
35 m_pluginNameLabel->setFont(m_pluginNameLabel->getFont().withHeight(25));
36 m_pluginNameLabel->setJustificationType(juce::Justification::centred);
37 addAndMakeVisible(m_pluginNameLabel.get());
38}
39
43
44void PluginControlComponent::paint(juce::Graphics& g)
45{
46 // solid overall background
47 g.fillAll(getLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId));
48}
49
51{
52 auto margin = 25;
53 auto bounds = getLocalBounds();
54 if (m_pluginNameLabel)
55 m_pluginNameLabel->setBounds(bounds.removeFromTop(margin));
56 bounds.removeFromLeft(margin);
57 bounds.removeFromRight(margin);
58 m_parameterBounds = bounds;
60}
61
63{
64 for (auto const& comboKV : m_parameterValueComboBoxes)
65 comboKV.second->setColour(juce::ComboBox::ColourIds::focusedOutlineColourId, getLookAndFeel().findColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringRmsColourId));
66 for (auto const& sliderKV : m_parameterValueSliders)
67 sliderKV.second->setColour(juce::Slider::ColourIds::trackColourId, getLookAndFeel().findColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringRmsColourId));
68 for (auto const& buttonKV : m_parameterValueButtons)
69 buttonKV.second->setColour(juce::TextButton::ColourIds::buttonOnColourId, getLookAndFeel().findColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringRmsColourId));
70}
71
73{
74 setIOCount({ 0,0 }); // irrelevant
75
76 m_pluginName.clear();
77 m_pluginParameterInfos.clear();
78}
79
86
88{
89 return m_pluginName;
90}
91
92void PluginControlComponent::setPluginName(const std::string& pluginName)
93{
94 if (m_pluginName != pluginName)
95 {
96 m_pluginName = pluginName;
97 if (m_pluginNameLabel) m_pluginNameLabel->setText(m_pluginName, juce::dontSendNotification);
98 }
99}
100
101const std::vector<Mema::PluginParameterInfo>& PluginControlComponent::getParameterInfos()
102{
103 return m_pluginParameterInfos;
104}
105
106void PluginControlComponent::setParameterInfos(const std::vector<Mema::PluginParameterInfo>& parameterInfos)
107{
108 if (m_pluginParameterInfos != parameterInfos)
109 {
110 m_pluginParameterInfos = parameterInfos;
113 }
114}
115
116void PluginControlComponent::setParameterValue(std::uint16_t index, std::string id, float value)
117{
118 if (index < m_pluginParameterInfos.size())
119 {
120 m_pluginParameterInfos.at(index).currentValue = value;
121 m_pluginParameterInfos.at(index).id = id;
122
123 if (m_parameterValueComboBoxes.count(index) != 0)
124 m_parameterValueComboBoxes.at(index)->setSelectedId(int(value * (m_pluginParameterInfos.at(index).stepCount - 1) + 1), juce::dontSendNotification);
125 else if (m_parameterValueSliders.count(index) != 0)
126 m_parameterValueSliders.at(index)->setValue(float(value), juce::dontSendNotification);
127 else if (m_parameterValueButtons.count(index) != 0)
128 m_parameterValueButtons.at(index)->setToggleState(value > 0.5f, juce::dontSendNotification);
129 }
130}
131
133{
134 for (auto const& parameterInfo : m_pluginParameterInfos)
135 {
136 if (!parameterInfo.isRemoteControllable)
137 continue;
138
139 if (parameterInfo.type != ParameterControlType::Toggle)
140 {
141 if (m_parameterNameLabels.count(parameterInfo.index) != 0)
142 m_parameterNameLabels.at(parameterInfo.index)->setText(parameterInfo.name, juce::dontSendNotification);
143 else
144 {
145 m_parameterNameLabels[parameterInfo.index] = std::make_unique<juce::Label>(parameterInfo.id);
146 m_parameterNameLabels[parameterInfo.index]->setJustificationType(juce::Justification::centredBottom);
147 m_parameterNameLabels[parameterInfo.index]->setText(parameterInfo.name, juce::dontSendNotification);
148 addAndMakeVisible(m_parameterNameLabels[parameterInfo.index].get());
149 }
150 }
151
152 if (parameterInfo.type == ParameterControlType::Discrete)
153 {
154 if (m_parameterValueComboBoxes.count(parameterInfo.index) != 0)
155 m_parameterValueComboBoxes.at(parameterInfo.index)->setSelectedId(int(parameterInfo.currentValue * (parameterInfo.stepCount - 1) + 1), juce::dontSendNotification);
156 else
157 {
158 m_parameterValueComboBoxes[parameterInfo.index] = std::make_unique<juce::ComboBox>(parameterInfo.id);
159 jassert(parameterInfo.stepCount == parameterInfo.stepNames.size());
160 int i = 1;
161 for (auto const& stepName : parameterInfo.stepNames)
162 m_parameterValueComboBoxes[parameterInfo.index]->addItem(stepName, i++);
163 m_parameterValueComboBoxes[parameterInfo.index]->setSelectedId(int(parameterInfo.currentValue * (parameterInfo.stepCount - 1) + 1), juce::dontSendNotification);
164 m_parameterValueComboBoxes[parameterInfo.index]->onChange = [=]() {
165 auto pIdx = parameterInfo.index;
166 if (m_parameterValueComboBoxes.count(pIdx) > 0 && m_pluginParameterInfos.size() > pIdx)
167 {
168 auto sId = m_parameterValueComboBoxes[pIdx]->getSelectedId();
169 m_pluginParameterInfos[pIdx].currentValue = float((1.0f / (parameterInfo.stepCount - 1)) * (sId - 1));
170
172 onPluginParameterValueChanged(pIdx, m_pluginParameterInfos[pIdx].id.toStdString(), m_pluginParameterInfos[pIdx].currentValue);
173 }
174 };
175 addAndMakeVisible(m_parameterValueComboBoxes[parameterInfo.index].get());
176 }
177 }
178 else if (parameterInfo.type == ParameterControlType::Continuous)
179 {
180 if (m_parameterValueSliders.count(parameterInfo.index) != 0)
181 m_parameterValueSliders.at(parameterInfo.index)->setValue(int(parameterInfo.currentValue));
182 else
183 {
184 m_parameterValueSliders[parameterInfo.index] = std::make_unique<JUCEAppBasics::ToggleStateSlider>(juce::Slider::Rotary, juce::Slider::NoTextBox);
185 m_parameterValueSliders[parameterInfo.index]->setSliderStyle(juce::Slider::SliderStyle::Rotary);
186 m_parameterValueSliders[parameterInfo.index]->setTogglalbe(false);
187 m_parameterValueSliders[parameterInfo.index]->setRange(parameterInfo.minValue, parameterInfo.maxValue, juce::dontSendNotification);
188 m_parameterValueSliders[parameterInfo.index]->setValue(parameterInfo.currentValue);
189 m_parameterValueSliders[parameterInfo.index]->onValueChange = [=]() {
190 auto pIdx = parameterInfo.index;
191 if (m_parameterValueSliders.count(pIdx) > 0 && m_pluginParameterInfos.size() > pIdx)
192 {
193 auto value = float(m_parameterValueSliders[pIdx]->getValue());
194 m_pluginParameterInfos[pIdx].currentValue = value;
195
197 onPluginParameterValueChanged(pIdx, m_pluginParameterInfos[pIdx].id.toStdString(), m_pluginParameterInfos[pIdx].currentValue);
198 }
199 };
200 addAndMakeVisible(m_parameterValueSliders[parameterInfo.index].get());
201 }
202 }
203 else if (parameterInfo.type == ParameterControlType::Toggle)
204 {
205 if (m_parameterValueButtons.count(parameterInfo.index) != 0)
206 m_parameterValueButtons.at(parameterInfo.index)->setToggleState(parameterInfo.currentValue > 0.5f, juce::dontSendNotification);
207 else
208 {
209 m_parameterValueButtons[parameterInfo.index] = std::make_unique<juce::TextButton>(parameterInfo.name, "Toggle to enable or disable parameter.");
210 m_parameterValueButtons[parameterInfo.index]->setClickingTogglesState(true);
211 m_parameterValueButtons[parameterInfo.index]->setToggleState(parameterInfo.currentValue > 0.5f, juce::dontSendNotification);
212 m_parameterValueButtons[parameterInfo.index]->onStateChange = [=]() {
213 auto pIdx = parameterInfo.index;
214 if (m_parameterValueButtons.count(pIdx) > 0 && m_pluginParameterInfos.size() > pIdx)
215 {
216 auto value = float(m_parameterValueButtons[pIdx]->getToggleState() ? 1.0f : 0.0f);
217 m_pluginParameterInfos[pIdx].currentValue = value;
218
220 onPluginParameterValueChanged(pIdx, m_pluginParameterInfos[pIdx].id.toStdString(), m_pluginParameterInfos[pIdx].currentValue);
221 }
222 };
223 addAndMakeVisible(m_parameterValueButtons[parameterInfo.index].get());
224 }
225 }
226 }
227
228 lookAndFeelChanged(); // trigger colour updates for all controls
229}
230
232{
233 auto bounds = m_parameterBounds;
234 if (bounds.isEmpty())
235 return;
236
237 // Get the number of items to display
238 auto itemCount = int(m_parameterValueComboBoxes.size() + m_parameterValueSliders.size() + m_parameterValueButtons.size());
239 if (itemCount == 0)
240 return;
241
242 // Get the control size from the method
243 auto gridItemControlSize = 3 * int(getControlsSize());
244
245 // Calculate how many items fit per row based on available width and control size
246 const int labelHeight = 30;
247 const int controlMargin = 5;
248
249 int availableWidth = bounds.getWidth();
250 int availableHeight = bounds.getHeight();
251
252 // Calculate items per row based on control size
253 int itemsPerRow = juce::jmax(1, availableWidth / gridItemControlSize);
254 itemsPerRow = juce::jmin(itemCount, itemsPerRow); // Don't exceed total items
255
256 // Calculate how many row pairs we need (each item takes 2 rows: label + control)
257 int numRowPairs = static_cast<int>(std::ceil(static_cast<float>(itemCount) / static_cast<float>(itemsPerRow)));
258
259 // Use controlSize for both column width and control height
260 int columnWidth = gridItemControlSize;
261 int controlHeight = gridItemControlSize;
262
263 // Build the grid
264 m_parameterControlsGrid->templateRows.clear();
265 m_parameterControlsGrid->templateColumns.clear();
266 m_parameterControlsGrid->items.clear();
267
268 // Set up rows: alternating between label rows and control rows
269 for (int i = 0; i < numRowPairs; ++i)
270 {
271 m_parameterControlsGrid->templateRows.add(juce::Grid::TrackInfo(juce::Grid::Px(labelHeight)));
272 m_parameterControlsGrid->templateRows.add(juce::Grid::TrackInfo(juce::Grid::Px(controlHeight)));
273 }
274
275 // Set up columns with calculated width
276 for (int i = 0; i < itemsPerRow; ++i)
277 m_parameterControlsGrid->templateColumns.add(juce::Grid::TrackInfo(juce::Grid::Px(columnWidth)));
278
279 // Track current position in grid
280 int currentItem = 0;
281
282 // Add buttons
283 for (auto const& buttonKV : m_parameterValueButtons)
284 {
285 auto* button = buttonKV.second.get();
286
287 // Calculate grid position
288 int row = (currentItem / itemsPerRow) * 2;
289 int col = currentItem % itemsPerRow;
290
291 // Add button (row + 1, col) - centered and square
292 auto buttonSize = float(gridItemControlSize - (controlMargin * 2));
293
294 m_parameterControlsGrid->items.add(juce::GridItem(*button)
295 .withArea(row + 2, col + 1)
296 .withMargin(juce::GridItem::Margin(controlMargin))
297 .withWidth(buttonSize)
298 .withHeight(buttonSize)
299 .withJustifySelf(juce::GridItem::JustifySelf::center)
300 .withAlignSelf(juce::GridItem::AlignSelf::center));
301
302 currentItem++;
303 }
304
305 // Add combo boxes
306 for (auto const& comboKV : m_parameterValueComboBoxes)
307 {
308 auto paramIndex = comboKV.first;
309 auto* combo = comboKV.second.get();
310
311 // Find corresponding label
312 auto labelIter = m_parameterNameLabels.find(paramIndex);
313 if (labelIter == m_parameterNameLabels.end() || !labelIter->second)
314 continue;
315
316 auto* label = labelIter->second.get();
317
318 // Calculate grid position
319 int row = (currentItem / itemsPerRow) * 2;
320 int col = currentItem % itemsPerRow;
321
322 // Add label (row, col) - Grid uses 1-based indexing
323 m_parameterControlsGrid->items.add(juce::GridItem(*label)
324 .withArea(row + 1, col + 1)
325 .withMargin(juce::GridItem::Margin(2.0f)));
326
327 // Add control (row + 1, col)
328 m_parameterControlsGrid->items.add(juce::GridItem(*combo)
329 .withArea(row + 2, col + 1)
330 .withMargin(juce::GridItem::Margin(controlMargin))
331 .withHeight(30)
332 .withJustifySelf(juce::GridItem::JustifySelf::center)
333 .withAlignSelf(juce::GridItem::AlignSelf::center));
334
335 currentItem++;
336 }
337
338 // Add sliders
339 for (auto const& sliderKV : m_parameterValueSliders)
340 {
341 auto paramIndex = sliderKV.first;
342 auto* slider = sliderKV.second.get();
343
344 // Find corresponding label
345 auto labelIter = m_parameterNameLabels.find(paramIndex);
346 if (labelIter == m_parameterNameLabels.end() || !labelIter->second)
347 continue;
348
349 auto* label = labelIter->second.get();
350
351 // Calculate grid position
352 int row = (currentItem / itemsPerRow) * 2;
353 int col = currentItem % itemsPerRow;
354
355 // Add label (row, col)
356 m_parameterControlsGrid->items.add(juce::GridItem(*label)
357 .withArea(row + 1, col + 1)
358 .withMargin(juce::GridItem::Margin(2.0f)));
359
360 // Add slider (row + 1, col) - centered and square
361 auto sliderSize = float(gridItemControlSize - (controlMargin * 2));
362
363 m_parameterControlsGrid->items.add(juce::GridItem(*slider)
364 .withArea(row + 2, col + 1)
365 .withMargin(juce::GridItem::Margin(controlMargin))
366 .withWidth(sliderSize)
367 .withHeight(sliderSize)
368 .withJustifySelf(juce::GridItem::JustifySelf::center)
369 .withAlignSelf(juce::GridItem::AlignSelf::center));
370
371 currentItem++;
372 }
373
374 // CRITICAL: Actually perform the layout with the bounds
375 m_parameterControlsGrid->performLayout(bounds);
376}
377
378
379} // namespace Mema
Abstract base for all Mema.Re client control panels.
virtual void setControlsSize(const ControlsSize &ctrlsSize)
virtual void setIOCount(const std::pair< int, int > &ioCount)
ControlsSize
Size category for rendered control elements.
void setControlsSize(const ControlsSize &ctrlsSize) override
std::function< void(std::uint16_t, std::string, float)> onPluginParameterValueChanged
void setParameterInfos(const std::vector< Mema::PluginParameterInfo > &parameterInfos)
void setParameterValue(std::uint16_t index, std::string id, float value)
const std::vector< Mema::PluginParameterInfo > & getParameterInfos()
void setPluginName(const std::string &pluginName)
Definition Mema.cpp:27