30 : paramIndex(paramIdx)
36 typeCombo = std::make_unique<juce::ComboBox>();
37 typeCombo->addItem(
"Continuous",
static_cast<int>(ParameterControlType::Continuous) + 1);
38 typeCombo->addItem(
"Discrete",
static_cast<int>(ParameterControlType::Discrete) + 1);
39 typeCombo->addItem(
"Toggle",
static_cast<int>(ParameterControlType::Toggle) + 1);
40 typeCombo->setSelectedId(
static_cast<int>(info.
type) + 1, juce::dontSendNotification);
43 stepsEdit = std::make_unique<JUCEAppBasics::FixedFontTextEditor>();
44 stepsEdit->setText(info.
type == ParameterControlType::Toggle ? juce::String(2) : juce::String(info.
stepCount), juce::dontSendNotification);
45 stepsEdit->setEnabled(info.
type != ParameterControlType::Toggle);
46 stepsEdit->setInputRestrictions(3,
"0123456789");
50 auto selectedType =
static_cast<ParameterControlType
>(
typeCombo->getSelectedId() - 1);
51 stepsEdit->setEnabled(selectedType != ParameterControlType::Toggle);
52 if (selectedType == ParameterControlType::Toggle)
53 stepsEdit->setText(juce::String(2), juce::dontSendNotification);
59 auto bounds = getLocalBounds();
62 typeCombo->setBounds(bounds.removeFromLeft(100));
69 const float dotSize = 2.0f;
70 const float colGap = 5.0f;
71 const float rowGap = 5.0f;
72 const float startX = (
gripWidth - colGap - dotSize) * 0.5f;
73 const float startY = (getHeight() - 2.0f * rowGap - dotSize) * 0.5f;
74 g.setColour(findColour(juce::Label::textColourId).withAlpha(0.4f));
75 for (
int r = 0; r < 3; ++r)
76 for (
int c = 0; c < 2; ++c)
77 g.fillEllipse(startX + c * colGap, startY + r * rowGap, dotSize, dotSize);
80 if (m_showInsertionLine)
82 g.setColour(findColour(juce::TextButton::ColourIds::textColourOnId));
83 g.fillRect(0, m_insertionLineAtTop ? 0 : getHeight() - 2, getWidth(), 2);
94 if (m_mouseDownInGrip)
96 if (
auto* container = juce::DragAndDropContainer::findParentDragContainerFor(
this))
97 if (!container->isDragAndDropActive())
98 container->startDragging(juce::var(
paramIndex),
this);
104 m_mouseDownInGrip =
false;
115 m_showInsertionLine =
true;
116 m_insertionLineAtTop = (details.localPosition.y < getHeight() / 2);
122 bool atTop = (details.localPosition.y < getHeight() / 2);
123 if (atTop != m_insertionLineAtTop)
125 m_insertionLineAtTop = atTop;
132 m_showInsertionLine =
false;
141 m_showInsertionLine =
false;
151 row->onRowDropped = [
this](
int from,
int to,
bool before) {
154 addAndMakeVisible(row.get());
155 m_rows.push_back(std::move(row));
161 const int rowHeight = 28;
162 for (
int i = 0; i < static_cast<int>(m_rows.size()); ++i)
163 m_rows[i]->setBounds(0, i * rowHeight, getWidth(), rowHeight);
168 std::vector<int> order;
169 order.reserve(m_rows.size());
170 for (
auto const& row : m_rows)
171 order.push_back(row->paramIndex);
177 for (
auto& row : m_rows)
178 if (row->paramIndex == paramIdx)
185 int fromIdx = -1, toIdx = -1;
186 for (
int i = 0; i < static_cast<int>(m_rows.size()); ++i)
188 if (m_rows[i]->paramIndex == fromParamIndex) fromIdx = i;
189 if (m_rows[i]->paramIndex == toParamIndex) toIdx = i;
192 if (fromIdx < 0 || toIdx < 0)
195 int insertIdx = insertBefore ? toIdx : toIdx + 1;
198 if (insertIdx > fromIdx)
201 if (insertIdx == fromIdx)
204 auto movedRow = std::move(m_rows[fromIdx]);
205 m_rows.erase(m_rows.begin() + fromIdx);
206 m_rows.insert(m_rows.begin() + insertIdx, std::move(movedRow));
215 m_enableButton = std::make_unique<juce::DrawableButton>(
"Enable plug-in", juce::DrawableButton::ButtonStyle::ImageOnButtonBackground);
216 m_enableButton->setTooltip(
"Enable plug-in");
217 m_enableButton->setClickingTogglesState(
true);
218 m_enableButton->onClick = [
this] {
219 if (onPluginEnabledChange)
220 onPluginEnabledChange(m_enableButton->getToggleState());
222 addAndMakeVisible(m_enableButton.get());
224 m_spacing1 = std::make_unique<Spacing>();
225 addAndMakeVisible(m_spacing1.get());
227 m_postButton = std::make_unique<juce::TextButton>(
"Post",
"Toggle plug-in pre/post");
228 m_postButton->setClickingTogglesState(
true);
229 m_postButton->onClick = [
this] {
230 if (onPluginPrePostChange)
231 onPluginPrePostChange(m_postButton->getToggleState());
233 addAndMakeVisible(m_postButton.get());
235 m_spacing2 = std::make_unique<Spacing>();
236 addAndMakeVisible(m_spacing2.get());
238 m_showEditorButton = std::make_unique<juce::TextButton>(
"None",
"Show plug-in editor");
239 m_showEditorButton->onClick = [
this] {
240 if (onShowPluginEditor)
241 onShowPluginEditor();
243 addAndMakeVisible(m_showEditorButton.get());
245 m_triggerSelectButton = std::make_unique<juce::DrawableButton>(
"Show plug-in selection menu", juce::DrawableButton::ButtonStyle::ImageOnButtonBackground);
246 m_triggerSelectButton->setTooltip(
"Show plug-in selection menu");
247 m_triggerSelectButton->onClick = [
this] {
248 showPluginsList(juce::Desktop::getMousePosition());
250 addAndMakeVisible(m_triggerSelectButton.get());
252 m_spacing3 = std::make_unique<Spacing>();
253 addAndMakeVisible(m_spacing3.get());
255 m_parameterConfigButton = std::make_unique<juce::DrawableButton>(
"Configure plug-in parameters", juce::DrawableButton::ButtonStyle::ImageOnButtonBackground);
256 m_parameterConfigButton->setTooltip(
"Configure plug-in parameters");
257 m_parameterConfigButton->onClick = [
this] {
258 showParameterConfig();
260 addAndMakeVisible(m_parameterConfigButton.get());
262 m_spacing4 = std::make_unique<Spacing>();
263 addAndMakeVisible(m_spacing4.get());
265 m_clearButton = std::make_unique<juce::DrawableButton>(
"Clear current plug-in", juce::DrawableButton::ButtonStyle::ImageOnButtonBackground);
266 m_clearButton->setTooltip(
"Clear current plug-in");
267 m_clearButton->onClick = [
this] {
271 addAndMakeVisible(m_clearButton.get());
273 m_pluginSelectionComponent = std::make_unique<PluginListAndSelectComponent>();
274 m_pluginSelectionComponent->onPluginSelected = [=](
const juce::PluginDescription& pluginDescription) {
275 if (onPluginSelected)
276 onPluginSelected(pluginDescription);
287 m_pluginSelectionComponent->setVisible(
true);
288 m_pluginSelectionComponent->addToDesktop(juce::ComponentPeer::windowHasDropShadow);
290 auto const display = juce::Desktop::getInstance().getDisplays().getPrimaryDisplay();
291 if (
nullptr != display &&
nullptr != m_pluginSelectionComponent)
293 if (display->totalArea.getHeight() < showPosition.getY() + m_pluginSelectionComponent->getHeight())
294 showPosition.setY(showPosition.getY() - m_pluginSelectionComponent->getHeight() - 30);
295 if (display->totalArea.getWidth() < showPosition.getX() + m_pluginSelectionComponent->getWidth())
296 showPosition.setX(showPosition.getX() - m_pluginSelectionComponent->getWidth() - 30);
298 m_pluginSelectionComponent->setTopLeftPosition(showPosition);
304 m_enableButton->setToggleState(enabled, juce::dontSendNotification);
310 m_postButton->setToggleState(post, juce::dontSendNotification);
315 m_selectedPluginDescription = pluginDescription;
317 if (m_showEditorButton)
319 if (m_selectedPluginDescription.name.isEmpty())
320 m_showEditorButton->setButtonText(
"None");
322 m_showEditorButton->setButtonText(m_selectedPluginDescription.name);
328 if (infos.size() != m_parameterInfos.size())
330 m_parameterInfos.clear();
331 m_parameterDisplayOrder.clear();
335 for (
auto const& info : infos)
337 if (0 < m_parameterInfos.count(key) || std::as_const(m_parameterInfos[key]) != info)
338 m_parameterInfos[key] = info;
345 return m_parameterInfos;
350 m_parameterDisplayOrder = order;
355 if (m_parameterDisplayOrder.empty())
356 for (
auto& kv : m_parameterInfos)
357 m_parameterDisplayOrder.push_back(kv.first);
358 return m_parameterDisplayOrder;
363 if (m_selectedPluginDescription.name.isEmpty())
365 m_messageBox = std::make_unique<juce::AlertWindow>(
366 "Plug-in parameter setup not available",
367 "No plug-in selected.",
368 juce::MessageBoxIconType::WarningIcon);
369 m_messageBox->addButton(
"Ok", 1, juce::KeyPress(juce::KeyPress::returnKey));
370 m_messageBox->enterModalState(
true, juce::ModalCallbackFunction::create([=](
int returnValue) {
371 ignoreUnused(returnValue);
372 m_messageBox.reset();
375 else if (m_parameterInfos.empty())
377 m_messageBox = std::make_unique<juce::AlertWindow>(
378 "Plug-in parameter setup not available",
379 "No parameters detected.",
380 juce::MessageBoxIconType::WarningIcon);
381 m_messageBox->addButton(
"Ok", 1, juce::KeyPress(juce::KeyPress::returnKey));
382 m_messageBox->enterModalState(
true, juce::ModalCallbackFunction::create([=](
int returnValue) {
383 ignoreUnused(returnValue);
384 m_messageBox.reset();
389 m_messageBox = std::make_unique<juce::AlertWindow>(
390 "Plug-in parameter setup",
391 "Select which parameters should be remote-controllable and configure their control type.\nDrag the grip handle on the left of each row to reorder.",
392 juce::MessageBoxIconType::NoIcon);
395 if (m_parameterDisplayOrder.empty())
396 for (
auto& kv : m_parameterInfos)
397 m_parameterDisplayOrder.push_back(kv.first);
400 auto savedDisplayOrder = m_parameterDisplayOrder;
402 const int rowHeight = 28;
404 const int totalHeight =
static_cast<int>(m_parameterInfos.size()) * rowHeight;
407 m_messageBoxParameterListComponent = std::make_unique<ParameterListComponent>();
408 m_messageBoxParameterListComponent->setSize(totalWidth, totalHeight);
409 for (
int paramIdx : m_parameterDisplayOrder)
410 m_messageBoxParameterListComponent->addRow(
411 std::make_unique<ParameterRowComponent>(paramIdx, m_parameterInfos.at(paramIdx)));
414 const int maxViewportHeight = 400;
415 const int viewportHeight = juce::jmin(totalHeight, maxViewportHeight);
417 m_messageBoxParameterTogglesViewport = std::make_unique<juce::Viewport>();
418 m_messageBoxParameterTogglesViewport->setViewedComponent(m_messageBoxParameterListComponent.get(),
false);
419 m_messageBoxParameterTogglesViewport->setScrollBarsShown(totalHeight > maxViewportHeight,
false);
420 m_messageBoxParameterTogglesViewport->setSize(
421 totalWidth + (totalHeight > maxViewportHeight ? m_messageBoxParameterTogglesViewport->getScrollBarThickness() : 0),
424 m_messageBox->addCustomComponent(m_messageBoxParameterTogglesViewport.get());
425 m_messageBox->addButton(
"Cancel", 0, juce::KeyPress(juce::KeyPress::escapeKey));
426 m_messageBox->addButton(
"Ok", 1, juce::KeyPress(juce::KeyPress::returnKey));
428 m_messageBox->enterModalState(
true, juce::ModalCallbackFunction::create([=](
int returnValue) {
429 if (returnValue == 1)
431 auto changeDetected =
false;
433 for (
auto& parameterKV : m_parameterInfos)
435 auto paramIndex = parameterKV.first;
436 auto& paramInfo = parameterKV.second;
438 auto* row = m_messageBoxParameterListComponent->getRowForParamIndex(paramIndex);
442 auto newRemoteControllable = row->toggleButton->getToggleState();
443 if (newRemoteControllable != paramInfo.isRemoteControllable)
445 paramInfo.isRemoteControllable = newRemoteControllable;
446 changeDetected =
true;
450 auto newType =
static_cast<ParameterControlType
>(row->typeCombo->getSelectedId() - 1);
451 if (newType != paramInfo.type)
453 paramInfo.type = newType;
454 if (newType == ParameterControlType::Toggle)
455 paramInfo.stepCount = 2;
456 changeDetected =
true;
460 if (newType != ParameterControlType::Toggle)
462 auto newStepCount = row->stepsEdit->getText().getIntValue();
463 newStepCount = juce::jmax(2, newStepCount);
464 if (newStepCount != paramInfo.stepCount)
466 paramInfo.stepCount = newStepCount;
467 changeDetected =
true;
472 auto newDisplayOrder = m_messageBoxParameterListComponent->getDisplayOrder();
473 if (newDisplayOrder != savedDisplayOrder)
474 changeDetected =
true;
475 m_parameterDisplayOrder = newDisplayOrder;
486 m_parameterDisplayOrder = savedDisplayOrder;
489 m_messageBoxParameterTogglesViewport.reset();
490 m_messageBoxParameterListComponent.reset();
491 m_messageBox.reset();
498 auto bounds = getLocalBounds();
502 m_enableButton->setBounds(bounds.removeFromLeft(bounds.getHeight()));
504 m_spacing1->setBounds(bounds.removeFromLeft(margin));
506 m_postButton->setBounds(bounds.removeFromLeft(
int(1.5f * bounds.getHeight())));
508 m_spacing2->setBounds(bounds.removeFromLeft(margin));
510 m_clearButton->setBounds(bounds.removeFromRight(bounds.getHeight()));
512 m_spacing3->setBounds(bounds.removeFromRight(margin));
513 if (m_parameterConfigButton)
514 m_parameterConfigButton->setBounds(bounds.removeFromRight(bounds.getHeight()));
516 m_spacing4->setBounds(bounds.removeFromRight(margin));
517 if (m_triggerSelectButton)
518 m_triggerSelectButton->setBounds(bounds.removeFromRight(bounds.getHeight()));
519 if (m_showEditorButton)
520 m_showEditorButton->setBounds(bounds);
526 g.fillAll(getLookAndFeel().findColour(juce::LookAndFeel_V4::ColourScheme::defaultFill));
531 auto enableDrawable = juce::Drawable::createFromSVG(*juce::XmlDocument::parse(BinaryData::power_settings_24dp_svg).get());
532 enableDrawable->replaceColour(juce::Colours::black, getLookAndFeel().findColour(juce::TextButton::ColourIds::textColourOnId));
533 m_enableButton->setImages(enableDrawable.get());
535 auto triggerSelectDrawable = juce::Drawable::createFromSVG(*juce::XmlDocument::parse(BinaryData::stat_minus_1_24dp_svg).get());
536 triggerSelectDrawable->replaceColour(juce::Colours::black, getLookAndFeel().findColour(juce::TextButton::ColourIds::textColourOnId));
537 m_triggerSelectButton->setImages(triggerSelectDrawable.get());
539 auto parameterConfigButtonDrawable = juce::Drawable::createFromSVG(*juce::XmlDocument::parse(BinaryData::settings_24dp_svg).get());
540 parameterConfigButtonDrawable->replaceColour(juce::Colours::black, getLookAndFeel().findColour(juce::TextButton::ColourIds::textColourOnId));
541 m_parameterConfigButton->setImages(parameterConfigButtonDrawable.get());
543 auto clearButtonDrawable = juce::Drawable::createFromSVG(*juce::XmlDocument::parse(BinaryData::replay_24dp_svg).get());
544 clearButtonDrawable->replaceColour(juce::Colours::black, getLookAndFeel().findColour(juce::TextButton::ColourIds::textColourOnId));
545 m_clearButton->setImages(clearButtonDrawable.get());
547 juce::Component::lookAndFeelChanged();
ParameterRowComponent * getRowForParamIndex(int paramIdx)
std::vector< int > getDisplayOrder() const
void addRow(std::unique_ptr< ParameterRowComponent > row)
void reorderRow(int fromParamIndex, int toParamIndex, bool insertBefore)
static constexpr int gripWidth
std::function< void(int fromParamIndex, int toParamIndex, bool insertBefore)> onRowDropped
std::unique_ptr< JUCEAppBasics::FixedFontTextEditor > stepsEdit
void itemDragMove(const SourceDetails &details) override
void itemDropped(const SourceDetails &details) override
std::unique_ptr< juce::ComboBox > typeCombo
void paint(juce::Graphics &g) override
bool isInterestedInDragSource(const SourceDetails &details) override
void itemDragEnter(const SourceDetails &details) override
ParameterRowComponent(int paramIdx, const Mema::PluginParameterInfo &info)
void mouseDown(const juce::MouseEvent &e) override
void mouseDrag(const juce::MouseEvent &e) override
void itemDragExit(const SourceDetails &details) override
void mouseUp(const juce::MouseEvent &e) override
std::unique_ptr< juce::ToggleButton > toggleButton
void lookAndFeelChanged() override
void setSelectedPlugin(const juce::PluginDescription &pluginDescription)
void showParameterConfig()
void setParameterInfos(const std::vector< Mema::PluginParameterInfo > ¶meterInfos)
std::function< void()> onPluginParametersStatusChanged
const std::vector< Mema::PluginParameterInfo > & getParameterInfos()
void setPluginEnabled(bool enabled)
void setPluginPrePost(bool post)
virtual ~PluginControlComponent()
void setParameterDisplayOrder(const std::vector< int > &order)
void showPluginsList(juce::Point< int > showPosition)
void paint(Graphics &) override
const std::vector< int > & getParameterDisplayOrder()
Metadata describing a single plugin parameter exposed for remote control.
juce::String name
Human-readable parameter name.
bool isRemoteControllable
Whether this parameter is exposed for remote control.
int stepCount
Number of discrete steps (0 if continuous).
ParameterControlType type
Control widget type (slider, combo, toggle).