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) 2024, 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
22namespace Mema
23{
24
25//==============================================================================
27 : juce::Component()
28{
29 m_enableButton = std::make_unique<juce::DrawableButton>("Enable plug-in", juce::DrawableButton::ButtonStyle::ImageOnButtonBackground);
30 m_enableButton->setTooltip("Enable plug-in");
31 m_enableButton->setClickingTogglesState(true);
32 m_enableButton->onClick = [this] {
33 if (onPluginEnabledChange)
34 onPluginEnabledChange(m_enableButton->getToggleState());
35 };
36 addAndMakeVisible(m_enableButton.get());
37
38 m_spacing1 = std::make_unique<Spacing>();
39 addAndMakeVisible(m_spacing1.get());
40
41 m_postButton = std::make_unique<juce::TextButton>("Post", "Toggle plug-in pre/post");
42 m_postButton->setClickingTogglesState(true);
43 m_postButton->onClick = [this] {
44 if (onPluginPrePostChange)
45 onPluginPrePostChange(m_postButton->getToggleState());
46 };
47 addAndMakeVisible(m_postButton.get());
48
49 m_spacing2 = std::make_unique<Spacing>();
50 addAndMakeVisible(m_spacing2.get());
51
52 m_showEditorButton = std::make_unique<juce::TextButton>("None", "Show plug-in editor");
53 m_showEditorButton->onClick = [this] {
54 if (onShowPluginEditor)
55 onShowPluginEditor();
56 };
57 addAndMakeVisible(m_showEditorButton.get());
58
59 m_triggerSelectButton = std::make_unique<juce::DrawableButton>("Show plug-in selection menu", juce::DrawableButton::ButtonStyle::ImageOnButtonBackground);
60 m_triggerSelectButton->setTooltip("Show plug-in selection menu");
61 m_triggerSelectButton->onClick = [this] {
62 showPluginsList(juce::Desktop::getMousePosition());
63 };
64 addAndMakeVisible(m_triggerSelectButton.get());
65
66 m_spacing3 = std::make_unique<Spacing>();
67 addAndMakeVisible(m_spacing3.get());
68
69 m_parameterConfigButton = std::make_unique<juce::DrawableButton>("Configure plug-in parameters", juce::DrawableButton::ButtonStyle::ImageOnButtonBackground);
70 m_parameterConfigButton->setTooltip("Configure plug-in parameters");
71 m_parameterConfigButton->onClick = [this] {
72 showParameterConfig();
73 };
74 addAndMakeVisible(m_parameterConfigButton.get());
75
76 m_spacing4 = std::make_unique<Spacing>();
77 addAndMakeVisible(m_spacing4.get());
78
79 m_clearButton = std::make_unique<juce::DrawableButton>("Clear current plug-in", juce::DrawableButton::ButtonStyle::ImageOnButtonBackground);
80 m_clearButton->setTooltip("Clear current plug-in");
81 m_clearButton->onClick = [this] {
82 if (onClearPlugin)
83 onClearPlugin();
84 };
85 addAndMakeVisible(m_clearButton.get());
86
87 m_pluginSelectionComponent = std::make_unique<PluginListAndSelectComponent>();
88 m_pluginSelectionComponent->onPluginSelected = [=](const juce::PluginDescription& pluginDescription) {
89 if (onPluginSelected)
90 onPluginSelected(pluginDescription);
91 };
92}
93
94PluginControlComponent::~PluginControlComponent()
95{
96
97}
98
99void PluginControlComponent::showPluginsList(juce::Point<int> showPosition)
100{
101 m_pluginSelectionComponent->setVisible(true);
102 m_pluginSelectionComponent->addToDesktop(juce::ComponentPeer::windowHasDropShadow);
103
104 auto const display = juce::Desktop::getInstance().getDisplays().getPrimaryDisplay();
105 if (nullptr != display && nullptr != m_pluginSelectionComponent)
106 {
107 if (display->totalArea.getHeight() < showPosition.getY() + m_pluginSelectionComponent->getHeight())
108 showPosition.setY(showPosition.getY() - m_pluginSelectionComponent->getHeight() - 30);
109 if (display->totalArea.getWidth() < showPosition.getX() + m_pluginSelectionComponent->getWidth())
110 showPosition.setX(showPosition.getX() - m_pluginSelectionComponent->getWidth() - 30);
111 }
112 m_pluginSelectionComponent->setTopLeftPosition(showPosition);
113}
114
115void PluginControlComponent::setPluginEnabled(bool enabled)
116{
117 if (m_enableButton)
118 m_enableButton->setToggleState(enabled, juce::dontSendNotification);
119}
120
121void PluginControlComponent::setPluginPrePost(bool post)
122{
123 if (m_postButton)
124 m_postButton->setToggleState(post, juce::dontSendNotification);
125}
126
127void PluginControlComponent::setSelectedPlugin(const juce::PluginDescription& pluginDescription)
128{
129 m_selectedPluginDescription = pluginDescription;
130
131 if (m_showEditorButton)
132 {
133 if (m_selectedPluginDescription.name.isEmpty())
134 m_showEditorButton->setButtonText("None");
135 else
136 m_showEditorButton->setButtonText(m_selectedPluginDescription.name);
137 }
138}
139
140void PluginControlComponent::setParameterInfos(const std::vector<Mema::PluginParameterInfo>& infos)
141{
142 if (infos.size() != m_parameterInfos.size())
143 m_parameterInfos.clear();
144
145 auto key = 0;
146 for (auto const& info : infos)
147 {
148 if (0 < m_parameterInfos.count(key) || std::as_const(m_parameterInfos[key]) != info)
149 m_parameterInfos[key] = info;
150 key++;
151 }
152}
153
154const std::map<int, Mema::PluginParameterInfo>& PluginControlComponent::getParameterInfos()
155{
156 return m_parameterInfos;
157}
158
159void PluginControlComponent::showParameterConfig()
160{
161 if (m_selectedPluginDescription.name.isEmpty())
162 {
163 m_messageBox = std::make_unique<juce::AlertWindow>(
164 "Plug-in parameter setup not available",
165 "No plug-in selected.",
166 juce::MessageBoxIconType::WarningIcon);
167 m_messageBox->addButton("Ok", 1, juce::KeyPress(juce::KeyPress::returnKey));
168 m_messageBox->enterModalState(true, juce::ModalCallbackFunction::create([=](int returnValue) {
169 ignoreUnused(returnValue);
170 m_messageBox.reset();
171 }));
172 }
173 else if (m_parameterInfos.empty())
174 {
175 m_messageBox = std::make_unique<juce::AlertWindow>(
176 "Plug-in parameter setup not available",
177 "No parameters detected.",
178 juce::MessageBoxIconType::WarningIcon);
179 m_messageBox->addButton("Ok", 1, juce::KeyPress(juce::KeyPress::returnKey));
180 m_messageBox->enterModalState(true, juce::ModalCallbackFunction::create([=](int returnValue) {
181 ignoreUnused(returnValue);
182 m_messageBox.reset();
183 }));
184 }
185 else
186 {
187 m_messageBox = std::make_unique<juce::AlertWindow>(
188 "Plug-in parameter setup",
189 "Select which parameters should be remote-controllable and configure their control type.",
190 juce::MessageBoxIconType::NoIcon);
191
192 // Create the container component
193 m_messageBoxParameterTogglesContainer = std::make_unique<juce::Component>();
194
195 // Layout constants
196 const int rowHeight = 28;
197 const int margin = 2;
198 const int toggleWidth = 180;
199 const int comboWidth = 100;
200 const int stepsWidth = 50;
201 const int totalWidth = toggleWidth + comboWidth + stepsWidth + (margin * 4);
202 const int totalHeight = int(m_parameterInfos.size()) * rowHeight;
203
204 m_messageBoxParameterTogglesContainer->setSize(totalWidth, totalHeight);
205
206 // Build grid
207 juce::Grid grid;
208 grid.templateColumns = {
209 juce::Grid::TrackInfo(juce::Grid::Px(toggleWidth)),
210 juce::Grid::TrackInfo(juce::Grid::Px(comboWidth)),
211 juce::Grid::TrackInfo(juce::Grid::Px(stepsWidth))
212 };
213 for (size_t i = 0; i < m_parameterInfos.size(); ++i)
214 grid.templateRows.add(juce::Grid::TrackInfo(juce::Grid::Px(rowHeight)));
215
216 int gridRow = 1;
217 for (auto const& parameterKV : m_parameterInfos)
218 {
219 auto paramIndex = parameterKV.first;
220 auto const& paramInfo = parameterKV.second;
221
222 // Toggle button - enable/disable remote control
223 m_messageBoxParameterToggles[paramIndex] = std::make_unique<juce::ToggleButton>(paramInfo.name);
224 m_messageBoxParameterToggles[paramIndex]->setToggleState(paramInfo.isRemoteControllable, juce::dontSendNotification);
225 m_messageBoxParameterTogglesContainer->addAndMakeVisible(m_messageBoxParameterToggles[paramIndex].get());
226
227 grid.items.add(juce::GridItem(*m_messageBoxParameterToggles[paramIndex])
228 .withArea(gridRow, 1)
229 .withMargin(juce::GridItem::Margin(margin)));
230
231 // Control type combobox
232 m_messageBoxParameterCtrlTypess[paramIndex] = std::make_unique<juce::ComboBox>();
233 m_messageBoxParameterCtrlTypess[paramIndex]->addItem("Continuous", static_cast<int>(ParameterControlType::Continuous) + 1);
234 m_messageBoxParameterCtrlTypess[paramIndex]->addItem("Discrete", static_cast<int>(ParameterControlType::Discrete) + 1);
235 m_messageBoxParameterCtrlTypess[paramIndex]->addItem("Toggle", static_cast<int>(ParameterControlType::Toggle) + 1);
236 m_messageBoxParameterCtrlTypess[paramIndex]->setSelectedId(static_cast<int>(paramInfo.type) + 1, juce::dontSendNotification);
237 m_messageBoxParameterTogglesContainer->addAndMakeVisible(m_messageBoxParameterCtrlTypess[paramIndex].get());
238
239 grid.items.add(juce::GridItem(*m_messageBoxParameterCtrlTypess[paramIndex])
240 .withArea(gridRow, 2)
241 .withMargin(juce::GridItem::Margin(margin)));
242
243 // Steps editor - disabled for toggle type
244 m_messageBoxParameterCtrlStepsEdit[paramIndex] = std::make_unique<JUCEAppBasics::FixedFontTextEditor>();
245 m_messageBoxParameterCtrlStepsEdit[paramIndex]->setText(paramInfo.type == ParameterControlType::Toggle ? juce::String(2) : juce::String(paramInfo.stepCount), juce::dontSendNotification);
246 m_messageBoxParameterCtrlStepsEdit[paramIndex]->setEnabled(paramInfo.type != ParameterControlType::Toggle);
247 m_messageBoxParameterCtrlStepsEdit[paramIndex]->setInputRestrictions(3, "0123456789");
248 m_messageBoxParameterTogglesContainer->addAndMakeVisible(m_messageBoxParameterCtrlStepsEdit[paramIndex].get());
249
250 grid.items.add(juce::GridItem(*m_messageBoxParameterCtrlStepsEdit[paramIndex])
251 .withArea(gridRow, 3)
252 .withMargin(juce::GridItem::Margin(margin)));
253
254 // When control type changes, enable/disable the steps editor accordingly
255 m_messageBoxParameterCtrlTypess[paramIndex]->onChange = [this, paramIndex]() {
256 auto selectedType = static_cast<ParameterControlType>(m_messageBoxParameterCtrlTypess[paramIndex]->getSelectedId() - 1);
257 m_messageBoxParameterCtrlStepsEdit[paramIndex]->setEnabled(selectedType != ParameterControlType::Toggle);
258 if (selectedType == ParameterControlType::Toggle)
259 m_messageBoxParameterCtrlStepsEdit[paramIndex]->setText(juce::String(2), juce::dontSendNotification);
260 };
261
262 gridRow++;
263 }
264
265 grid.performLayout(m_messageBoxParameterTogglesContainer->getLocalBounds());
266
267 // Add the container to the alert window
268 m_messageBox->addCustomComponent(m_messageBoxParameterTogglesContainer.get());
269
270 m_messageBox->addButton("Cancel", 0, juce::KeyPress(juce::KeyPress::escapeKey));
271 m_messageBox->addButton("Ok", 1, juce::KeyPress(juce::KeyPress::returnKey));
272
273 m_messageBox->enterModalState(true, juce::ModalCallbackFunction::create([=](int returnValue) {
274 if (returnValue == 1)
275 {
276 auto changeDetected = false;
277
278 for (auto& parameterKV : m_parameterInfos)
279 {
280 auto paramIndex = parameterKV.first;
281 auto& paramInfo = parameterKV.second;
282
283 // Check remote controllable toggle
284 auto newRemoteControllable = m_messageBoxParameterToggles[paramIndex]->getToggleState();
285 if (newRemoteControllable != paramInfo.isRemoteControllable)
286 {
287 paramInfo.isRemoteControllable = newRemoteControllable;
288 changeDetected = true;
289 }
290
291 // Check control type
292 auto newType = static_cast<ParameterControlType>(m_messageBoxParameterCtrlTypess[paramIndex]->getSelectedId() - 1);
293 if (newType != paramInfo.type)
294 {
295 paramInfo.type = newType;
296 if (newType == ParameterControlType::Toggle)
297 paramInfo.stepCount = 2;
298 changeDetected = true;
299 }
300
301 // Check step count - ignored for toggle type
302 if (newType != ParameterControlType::Toggle)
303 {
304 auto newStepCount = m_messageBoxParameterCtrlStepsEdit[paramIndex]->getText().getIntValue();
305 newStepCount = juce::jmax(2, newStepCount); // Enforce minimum of 2 steps
306 if (newStepCount != paramInfo.stepCount)
307 {
308 paramInfo.stepCount = newStepCount;
309 changeDetected = true;
310 }
311 }
312 }
313
314 if (changeDetected)
315 {
316 if (onPluginParametersStatusChanged)
317 onPluginParametersStatusChanged();
318 }
319 }
320
321 m_messageBoxParameterToggles.clear();
322 m_messageBoxParameterCtrlTypess.clear();
323 m_messageBoxParameterCtrlStepsEdit.clear();
324 m_messageBoxParameterTogglesContainer.reset();
325 m_messageBox.reset();
326 }));
327 }
328}
329
330void PluginControlComponent::resized()
331{
332 auto bounds = getLocalBounds();
333 auto margin = 1;
334
335 if (m_enableButton)
336 m_enableButton->setBounds(bounds.removeFromLeft(bounds.getHeight()));
337 if (m_spacing1)
338 m_spacing1->setBounds(bounds.removeFromLeft(margin));
339 if (m_postButton)
340 m_postButton->setBounds(bounds.removeFromLeft(int(1.5f * bounds.getHeight())));
341 if (m_spacing2)
342 m_spacing2->setBounds(bounds.removeFromLeft(margin));
343 if (m_clearButton)
344 m_clearButton->setBounds(bounds.removeFromRight(bounds.getHeight()));
345 if (m_spacing3)
346 m_spacing3->setBounds(bounds.removeFromRight(margin));
347 if (m_parameterConfigButton)
348 m_parameterConfigButton->setBounds(bounds.removeFromRight(bounds.getHeight()));
349 if (m_spacing4)
350 m_spacing4->setBounds(bounds.removeFromRight(margin));
351 if (m_triggerSelectButton)
352 m_triggerSelectButton->setBounds(bounds.removeFromRight(bounds.getHeight()));
353 if (m_showEditorButton)
354 m_showEditorButton->setBounds(bounds);
355}
356
357void PluginControlComponent::paint(Graphics& g)
358{
359 // (Our component is opaque, so we must completely fill the background with a solid colour)
360 g.fillAll(getLookAndFeel().findColour(juce::LookAndFeel_V4::ColourScheme::defaultFill));
361}
362
363void PluginControlComponent::lookAndFeelChanged()
364{
365 auto enableDrawable = juce::Drawable::createFromSVG(*juce::XmlDocument::parse(BinaryData::power_settings_24dp_svg).get());
366 enableDrawable->replaceColour(juce::Colours::black, getLookAndFeel().findColour(juce::TextButton::ColourIds::textColourOnId));
367 m_enableButton->setImages(enableDrawable.get());
368
369 auto triggerSelectDrawable = juce::Drawable::createFromSVG(*juce::XmlDocument::parse(BinaryData::stat_minus_1_24dp_svg).get());
370 triggerSelectDrawable->replaceColour(juce::Colours::black, getLookAndFeel().findColour(juce::TextButton::ColourIds::textColourOnId));
371 m_triggerSelectButton->setImages(triggerSelectDrawable.get());
372
373 auto parameterConfigButtonDrawable = juce::Drawable::createFromSVG(*juce::XmlDocument::parse(BinaryData::settings_24dp_svg).get());
374 parameterConfigButtonDrawable->replaceColour(juce::Colours::black, getLookAndFeel().findColour(juce::TextButton::ColourIds::textColourOnId));
375 m_parameterConfigButton->setImages(parameterConfigButtonDrawable.get());
376
377 auto clearButtonDrawable = juce::Drawable::createFromSVG(*juce::XmlDocument::parse(BinaryData::replay_24dp_svg).get());
378 clearButtonDrawable->replaceColour(juce::Colours::black, getLookAndFeel().findColour(juce::TextButton::ColourIds::textColourOnId));
379 m_clearButton->setImages(clearButtonDrawable.get());
380
381 juce::Component::lookAndFeelChanged();
382}
383
384
385}
Definition Mema.cpp:27