Krotos Modules 3
Loading...
Searching...
No Matches
PerformanceArea.cpp
Go to the documentation of this file.
1namespace krotos
2{
3#include "PerformanceArea.h"
4
6
7 const String PerformanceArea::TemplateFileExtension = "kst";
8 const Identifier PerformanceArea::Tag::Template = "TEMPLATE";
10 const String PerformanceArea::ComponentIds::ComboboxTemplateId = "performTemplateDropdown";
11
12 const std::string PerformanceArea::analyticsEventTemplateSelected{"analyticsEventTemplateSelected:"};
13
14 PerformanceArea::PerformanceArea(ValueTree& customDataTree, KwidgetAudioProcessor& processor, int id,
15 const StringArray& kTypes)
16 : m_customDataTree(customDataTree), m_processor(processor), m_layoutSelector("layout selector"),
17 m_editorSelector("editor selector"), m_layoutBuilderTree(XmlType::Tag::kwidgets), m_id(id),
18 m_optionKTypes(kTypes)
19 {
20 initialiseListeners();
21
22 addAndMakeVisible(m_layoutEditor);
23 addAndMakeVisible(m_layoutSelector);
24 addChildComponent(m_editorSelector);
25
26 initialiseEditorControls();
27 m_layoutEditor.setComponentID(ComponentIds::PerformanceAreaLayoutComponentId);
28
29 // Reset the layout editor selection after a state change
30 m_layoutEditor.treeRedirected = [this]() { m_layoutSelector.setSelectedId(0); };
31
32 m_layoutSelector.setComponentID(ComponentIds::ComboboxTemplateId);
33
34 m_layoutSelector.getProperties().set(LAFProperty::ComboBox::DisableHover, true);
35 m_layoutSelector.getProperties().set(LAFProperty::ComboBox::RoundedCornerID,
36 LAFProperty::ComboBox::RoundedCorner::Top);
37 m_layoutSelector.getProperties().set(LAFProperty::ComboBox::IconPositionID,
38 LAFProperty::ComboBox::IconPosition::Left);
39 m_layoutSelector.getProperties().set(LAFProperty::ComboBox::UseCustomIcon, true);
40 m_layoutSelector.getProperties().set(LAFProperty::ComboBox::FontSize, 18.0f);
42 m_layoutSelector.setColour(ComboBox::backgroundColourId, findColour(TabbedComponent::backgroundColourId));
43 m_layoutSelector.setColour(ComboBox::textColourId, Colours::white);
44 m_layoutSelector.setTextWhenNothingSelected("Select a Performing Template");
45
46 // Custom icon for dropdown
47 m_dropdownIcon = Drawable::createFromImageData(KrotosBinaryData::SampleBrowser_Dropdown_Icon_svg,
48 KrotosBinaryData::SampleBrowser_Dropdown_Icon_svgSize);
49 addAndMakeVisible(*m_dropdownIcon);
50
51 m_editorSelector.getProperties().set(LAFProperty::ComboBox::DisableHover, true);
52 m_editorSelector.setColour(ComboBox::backgroundColourId, findColour(TabbedComponent::backgroundColourId));
53
54 m_lastSelectedTemplate = m_layoutSelector.getSelectedId();
55 m_layoutSelector.onChange = [this] {
56 // Template hasn't changed; Don't do anything
57 auto selectedId = m_layoutSelector.getSelectedId();
58 if (selectedId == m_lastSelectedTemplate || selectedId == 0)
59 {
60 m_lastSelectedTemplate = selectedId;
61 return;
62 }
63
64 std::function<void(int)> changeCallback = [this, selectedId,
65 target = WeakReference<Component>{this}](int option) {
66 // Parent has been destroyed; Abort
67 if (!target)
68 {
69 return;
70 }
71
72 if (option == 0)
73 {
74 if (selectedId > 0 && selectedId <= m_templateFiles.size())
75 {
76 File file = m_templateFiles[selectedId - 1];
77 if (file.hasFileExtension(PerformanceArea::TemplateFileExtension))
78 {
79 XmlDocument doc(file);
80 loadTemplateXml(*doc.getDocumentElement());
81 m_lastSelectedTemplate = selectedId;
82
83 // Mixpanel analytics event for template selected
84 sendActionMessage(analyticsEventTemplateSelected +
85 m_layoutSelector.getItemText(selectedId).toStdString());
86 }
87 }
88 }
89 else
90 {
91 m_layoutSelector.setSelectedId(m_lastSelectedTemplate);
92 }
93 };
96 AlertWindow::showAsync(
97 MessageBoxOptions()
98 .withIconType(MessageBoxIconType::WarningIcon)
99 .withTitle("Template Switch")
100 .withMessage("Please note that switching to another\ntemplate will lead to a "
101 "destructive change,\nresulting in the loss of all prior "
102 "components,\nparameter settings, and modulation\nassignments")
103 .withButton("Cancel")
104 .withButton("Switch")
105 .withAssociatedComponent(this),
106 changeCallback);
107 };
108
109 refreshTemplates();
110
111 // pass on the callback from customlayout to fetch autolayout bounds from the editor
112 m_layoutEditor.getAutoLayoutBoundsFromType = [this](String type, Rectangle<int> currentBounds) {
113 if (getAutoLayoutBoundsFromType)
115 return getAutoLayoutBoundsFromType(type, currentBounds);
116 }
117 else
119 jassertfalse;
120 return Rectangle<int>();
122 };
123
124 // load background image
125 m_backgroundImage = Drawable::createFromImageData(KrotosBinaryData::Perform_Area_Background_png,
126 KrotosBinaryData::Perform_Area_Background_pngSize);
127
128 m_dropdownIcon->toFront(false);
129 }
130
131 // Initialise listeners and assign the layout tree to the custom data tree, in order to save
132 // and recall layout from the main plugin state.
133 void PerformanceArea::initialiseListeners()
135 // If you hit these, the custom data tree is invalid.
136 // This should have been passed in the ctor. Does it exist in the state?
137 jassert(m_customDataTree.isValid());
138 jassert(m_customDataTree.hasType(XmlType::Tag::customData));
139
140 // Set up attachment to listen to the custom data tree, so if the layout tree
141 // is added or removed from it as, we know about it and can update the layout accordingly.
142 m_customTreeAttachment.reset(new ValueTreeAttachment(m_customDataTree));
143
144 // First, find a layout tree in the main state
145 auto layoutTree = m_customDataTree.getChildWithName(CustomLayout::Tag::layout);
146
147 // Initialise tree to save / recall from state
148
149 // If a layout tree already exists in the state, we want to use that.
150 if (layoutTree.isValid())
151 {
152 // Must be a "Layout" tree!
153 jassert(layoutTree.hasType(CustomLayout::Tag::layout));
154
155 // Replace local layout tree with the one in the main state.
156 resetLayoutTree(layoutTree);
157 }
158 else
159 {
160 // Layout tree wasn't valid or didn't exist, so we'll add our local one to the state instead.
161 m_customDataTree.appendChild(getLayoutTree(), nullptr);
162 // Fetch the reference to it and replace local tree reference with the state reference.
163 layoutTree = m_customDataTree.getChildWithName(CustomLayout::Tag::layout);
164
165 jassert(layoutTree.isValid());
166 jassert(layoutTree.hasType(CustomLayout::Tag::layout));
167
168 setLayoutTree(layoutTree);
169 }
170
171 // Set up listeners...
172
173 m_customTreeAttachment->onChildAdded = [this](ValueTree& /*parent*/, ValueTree& child) {
174 // If a new layout tree was added to the main state. e.g. When a preset is loaded
175 // Replace current layout with the new one!
176 if (child.getType() == CustomLayout::Tag::layout)
178 resetLayoutTree(child);
179 }
180 };
181
182 m_customTreeAttachment->onChildRemoved = [this](ValueTree& /*parent*/, ValueTree& child) {
183 // Layout tree was removed from state for whateve reason, so clear layout.
184 if (child.getType() == CustomLayout::Tag::layout)
185 m_layoutBuilderTree.removeAllChildren(nullptr);
186 };
187 }
189 void PerformanceArea::resized()
190 {
191 const auto barHeight = 30;
192
193 auto bounds = getLocalBounds();
194 // Condition to prevent a visible offset if no editor controls are visible
195 auto selectorBar = m_layoutSelector.isVisible() || m_showEditorControls ? bounds.removeFromTop(barHeight)
196 : Rectangle<int>();
197
198 if (m_showEditorControls)
199 {
200 setTemplateSelectorVisible(true);
201 m_editorSelector.setBounds(selectorBar.removeFromLeft((int)((float)selectorBar.getWidth() * 0.5f)));
202 }
203 auto layoutBounds = bounds;
204 layoutBounds.setHeight(bounds.getHeight());
205 m_layoutEditor.setBounds(layoutBounds);
206
207 selectorBar.removeFromRight(Layout::selectorBar);
208 m_layoutSelector.setBounds(selectorBar);
209
210 auto layoutBoxBounds = m_layoutSelector.getBounds();
211
212 auto iconBounds = layoutBoxBounds.removeFromLeft(28).withSizeKeepingCentre(12, 12);
213 m_dropdownIcon->setBounds(iconBounds);
214 }
215
216 void PerformanceArea::paint(Graphics& g)
217 {
218 if (m_drawBackground)
220 m_backgroundImage->drawWithin(g, getLocalBounds().toFloat(), RectanglePlacement::stretchToFit, 1.0f);
223
224 void PerformanceArea::addToLayoutBuilder(const ValueTree& tree)
226 m_layoutBuilderTree.appendChild(tree.createCopy(), nullptr);
227 // DBG(m_layoutBuilderTree.toXmlString());
229
230 void PerformanceArea::addKwidgetGUI(KwidgetGUI* gui, Rectangle<int> bounds)
232 jassert(gui != nullptr);
234 auto id = gui->getKwidgetID();
236 auto slotTree = getLayoutTree().getChildWithProperty(CustomLayout::Property::name, id);
238 // If this kwidget already has a slot in the performance area (i.e. has bounds already) pass it on
239 if (slotTree.isValid())
240 addComponent(*gui, slotTree);
241 // else add it with default sizes.
242 else
243 addComponent(*gui, bounds);
245 addToLayoutBuilder(m_processor.getKwidget(id)->getState());
246 }
248 std::unique_ptr<XmlElement> PerformanceArea::saveTemplateXml()
249 {
250 ValueTree outputTree(Tag::Template);
251 outputTree.appendChild(m_layoutBuilderTree.createCopy(), nullptr);
252 outputTree.appendChild(getLayoutTree().createCopy(), nullptr);
253 // DBG("OUTPUT TEMPLATE TREE");
254 // DBG(outputTree.toXmlString());
255 return outputTree.createXml();
256 }
257
258 void PerformanceArea::loadTemplateXml(const XmlElement& templateXml)
259 {
260 auto newTree = ValueTree::fromXml(templateXml);
261 if (newTree.hasType(Tag::Template))
262 {
263 auto kwidgetsTree = newTree.getChildWithName(XmlType::Tag::kwidgets);
264 auto layoutTree = newTree.getChildWithName(CustomLayout::Tag::layout);
265
266 // Do some validation
267 jassert(kwidgetsTree.isValid());
268 jassert(layoutTree.isValid());
269
270 auto numberOfLayoutSlots = newTree.getChildWithName(CustomLayout::Tag::layout).getNumChildren();
271 auto numberOfKwidgets = kwidgetsTree.getNumChildren();
272
273 // Validate there's a slot for every kwidget
274 // (If you hit this, check the template preset for any oddities)
275 if (numberOfKwidgets != numberOfLayoutSlots)
276 {
277 AlertWindow::showMessageBoxAsync(MessageBoxIconType::WarningIcon, "Could not load template",
278 "Error: Invalid template.");
279
280 return;
281 }
282
283 // Validate the layout and kwidget trees contain the same kwidgets ids...
284 StringArray slotNames, kwidgetNames;
285
286 for (auto s : layoutTree)
287 slotNames.add(s[CustomLayout::Property::name]);
288
289 for (auto k : kwidgetsTree)
290 kwidgetNames.add(k[XmlType::Property::id]);
291
292 // Merge the two arrays.
293 // Merge array doesn't add a string if it already exists...
294 slotNames.mergeArray(kwidgetNames);
295
296 // ... so we can validate by checking the size hasn't changed
297 if (slotNames.size() != numberOfKwidgets)
298 {
299 AlertWindow::showMessageBoxAsync(MessageBoxIconType::WarningIcon, "Could not load template",
300 "Error: Invalid template.");
301
302 return;
303 }
304 // Validation complete!
305
306 // Begin loading the preset...
307
308 // Update layout tree first, as this is needed for the new kwidgets
309 if (layoutTree.isValid())
310 {
311 clearLayout();
312
313 getLayoutTree().copyPropertiesAndChildrenFrom(layoutTree, nullptr);
314 }
315
316 // Todo - pass in the whole tree
317 if (kwidgetsTree.isValid())
318 {
319 for (auto kwidget : kwidgetsTree)
320 {
321 // use random kwidget ID for now...
322 // todo: set id to -1 to inform param manager that it needs an id generated
323 Random r;
324 String kType = kwidget[XmlType::Property::type];
325 String ID = kwidget[XmlType::Property::id];
326 auto k = KwidgetFactory::createKwidget(kType, ID); // kType + String(r.nextInt()));
327 if (k->getCustomParameter(Kwidget::Constants::Parent) != nullptr)
328 k->getCustomParameter(Kwidget::Constants::Parent)->setValue(1);
329 auto vt = k->getState().createCopy();
330 m_processor.addKwidgetFromState(*std::move(&vt));
331 }
332 }
333 }
334 }
335
336 void PerformanceArea::clearLayout()
337 {
338 m_layoutBuilderTree.removeAllChildren(nullptr);
339
340 // Remove kwidgets from current layout
341 for (auto slot : getLayoutTree())
342 {
343 auto kwidgetToRemove = slot[CustomLayout::Property::name];
344 m_processor.removeKwidget(kwidgetToRemove);
345 }
346 getLayoutTree().removeAllChildren(nullptr);
347 }
348
349 void PerformanceArea::refreshTemplates()
350 {
351 m_layoutSelector.clear();
352
353 File templateDir = PresetManager::getFactoryTemplateDirectory().getFullPathName();
354
355 m_templateFiles = templateDir.findChildFiles(File::TypesOfFileToFind::findFiles, true,
356 "*." + PerformanceArea::TemplateFileExtension);
357 // Sort alphabetically
358 std::sort(m_templateFiles.begin(), m_templateFiles.end());
359
360 // Show the editor controls if the secret edit file exists in the directory...
361 showEditorControls(
362 !templateDir.findChildFiles(File::TypesOfFileToFind::findFiles, true, "*." + edit_mode_file_ext)
363 .isEmpty());
364
365 int i = 1;
366 for (auto file : m_templateFiles)
367 {
368 m_layoutSelector.addItem(file.getFileNameWithoutExtension(), i++);
369 }
370 }
371
372 void PerformanceArea::initialiseEditorControls()
373 {
374 m_editorSelector.clear();
375
376 // Save layout window
377 m_saveWindow.reset(new juce::FileChooser("Save template",
378 PresetManager::getFactoryTemplateDirectory().getFullPathName(),
379 "*." + PerformanceArea::TemplateFileExtension));
380
381 // Additional options
382 const StringArray optionSettings{"edit", "save", "clear"};
383
384 const String pAdd = "add ";
385 const String pSmall = pAdd + "small ";
386
387 // Add to dropdown
388 StringArray itemList;
389
390 for (int i = 0; i < m_optionKTypes.size(); ++i)
391 {
392 itemList.add(pAdd + m_optionKTypes[i]);
393
394 // Add "add small" options
395 if (m_optionKTypes[i] != KType::TriggerButton)
396 itemList.add(pSmall + m_optionKTypes[i]);
397 }
398 itemList.addArray(optionSettings);
399
400 m_editorSelector.addItemList(itemList, 1);
401
402 // Set up callback for adding kwidgets
403 m_editorSelector.onChange = [=] {
404 auto id = m_editorSelector.getSelectedId() - 1;
405 auto option = itemList[id];
406
407 // if function ptr exists and id is within bounds of kwidgets
408 if (addKwidget && id >= 0 && id < (itemList.size() - optionSettings.size()))
409 {
410 String kwidget;
411 // extract ktype from the dropdow options name
412 if (option.contains(pSmall))
413 {
414 kwidget = option.substring(pSmall.length());
415 smallify = true;
416 }
417 else if (option.contains(pAdd))
418 {
419 kwidget = option.substring(pAdd.length());
420 }
421
422 addKwidget(kwidget);
423 }
424
425 if (option == "edit")
426 {
427 setEditModeActive(!isEditModeActive());
428 }
429 else if (option == "save")
430 {
431 auto flags = juce::FileBrowserComponent::saveMode;
432 m_saveWindow->launchAsync(flags, [this](const juce::FileChooser& fc) {
433 auto file = fc.getResult();
434 if (file != juce::File())
435 {
436 saveTemplateXml()->writeTo(file);
437
438 refreshTemplates();
439 }
440 });
441 }
442 else if (option == "clear")
443 {
444 clearLayout();
445 }
446 // add your options here...
447 // make sure it's name is in optionSettings
448
449 m_editorSelector.setSelectedId(0);
450 };
451 }
452
453 void PerformanceArea::showEditorControls(bool isEnabled)
454 {
455 m_showEditorControls = isEnabled;
456 m_editorSelector.setVisible(isEnabled);
457 resized();
458 }
459} // namespace krotos
StringArray m_optionKTypes
Definition PerformanceArea.h:234
bool m_showEditorControls
Definition PerformanceArea.h:247
bool smallify
Definition PerformanceArea.h:159
ComboBox m_editorSelector
Definition PerformanceArea.h:232
std::function< void(String kType)> addKwidget
Definition PerformanceArea.h:82
std::unique_ptr< FileChooser > m_saveWindow
Definition PerformanceArea.h:236
std::function< Rectangle< int >(String componentType, Rectangle< int > currentBounds)> getAutoLayoutBoundsFromType
Used to look up what bounds should be used for a given component type when using auto layout....
Definition CustomLayout.h:134
std::function< void()> treeRedirected
Definition CustomLayout.h:138
Kwidget * getKwidget(const String &kwidgetID)
Attempt to find a kwidget by its ID.
Definition KwidgetAudioProcessor.cpp:1061
Kwidget * addKwidgetFromState(const ValueTree &kwidgetTree)
Definition KwidgetAudioProcessor.cpp:1221
void removeKwidget(const String &kwidgetID)
Definition KwidgetAudioProcessor.cpp:688
Interface for a UI Component that controls a KwidgetProcessor.
Definition KwidgetGUI.h:24
const String & getKwidgetID() const
Definition KwidgetGUI.cpp:70
const ValueTree & getState()
Definition Kwidget.cpp:242
Definition ValueTreeAttachment.h:4
Definition AirAbsorptionFilter.cpp:2
static const Identifier AlertHeightID
Definition Krotos_LookAndFeel.h:24
static const Identifier AlertWidthID
Definition Krotos_LookAndFeel.h:23
Definition Krotos_LookAndFeel.h:20
static const String PerformanceAreaLayoutComponentId
Definition PerformanceArea.h:42
static const String ComboboxTemplateId
Definition PerformanceArea.h:43
static const Identifier Template
Definition PerformanceArea.h:37