diff --git a/DESIGNING.md b/DESIGNING.md new file mode 100644 index 00000000..74a89861 --- /dev/null +++ b/DESIGNING.md @@ -0,0 +1,44 @@ +Designing in foleys_gui_magic +============================= + +General Setup +------------- + +PluginGuiMagic is a layout engine and editor to create GUIs in JUCE without writing code. +The whole GUI is defined as a DOM tree of Components and CSS instructions of visual properties. + +Each Component is wrapped into a GuiItem that adds decorations to or around the Components. +The main decorations is a margin, padding and a border, as well as a caption. +The background can be an image, a colour or a colour gradient. + +The different panels are hierarchically organised. Each View contains other View containers or Components. +Every View has a property `display` which selects, how the children are layouted. By default this is set to `FlexBox`. +If you set it to `Content` you can drag each child individually to any place. The number is reflected in the +property view in the side panel under `Node`. By adding a % as suffix the positions and dimensions will become relative +to the parent View instead of in absolute pixels. the third option is Tabbed, which will create a tab bar for the child Views or Components. + +CSS Classes +----------- + +You can set all properties to the nodes. But the properties are looked up traversing the DOM. +Every Component or View can be assigned a list of CSS classes, so the properties are looked up in those classes too. +The classes have a switch, if the properties shall be used by the children as well or just by the class referencing the CSS class. + +Responsive design +----------------- + +The layout is managed via FlexBox, which offers a lot of options how to react to resizing. + +Additionally the CSS classes have switches to be turned on or off. You can connect that for instance to a checkbox to turn +the visibility on or off. Another method to change the layout rules is to set a min size or max size to a CSS class, which also +switches the class off if the plugin size is smaller than the min size or greater than the max size. + +Saving and loading +------------------ + +To save the GUI use the File menu in the toolbox. You can also use `clear` in the File menu to start from scratch or +use `default` to create a default GUI from the parameters and visualisers found in the processor. + +To get the GUI into the product make sure the BinaryData is recreated (which happens automatically in cmake but +using the Projucer you need to save the Project there and recompile. + diff --git a/Editor/foleys_PropertiesEditor.cpp b/Editor/foleys_PropertiesEditor.cpp index a151828c..6a730004 100644 --- a/Editor/foleys_PropertiesEditor.cpp +++ b/Editor/foleys_PropertiesEditor.cpp @@ -232,6 +232,7 @@ void PropertiesEditor::addNodeProperties() array.add (new juce::TextPropertyComponent (styleItem.getPropertyAsValue (IDs::maxWidth, &undo), IDs::maxWidth.toString(), 8, false)); array.add (new juce::TextPropertyComponent (styleItem.getPropertyAsValue (IDs::minHeight, &undo), IDs::minHeight.toString(), 8, false)); array.add (new juce::TextPropertyComponent (styleItem.getPropertyAsValue (IDs::maxHeight, &undo), IDs::maxHeight.toString(), 8, false)); + array.add (new juce::TextPropertyComponent (styleItem.getPropertyAsValue (IDs::aspect, &undo), IDs::aspect.toString(), 8, false)); } auto classNames = builder.getStylesheet().getAllClassesNames(); diff --git a/Editor/foleys_StyleChoicePropertyComponent.h b/Editor/foleys_StyleChoicePropertyComponent.h index 746c1f9b..53b2d375 100644 --- a/Editor/foleys_StyleChoicePropertyComponent.h +++ b/Editor/foleys_StyleChoicePropertyComponent.h @@ -53,7 +53,6 @@ class StyleChoicePropertyComponent : public StylePropertyComponent, void valueChanged (juce::Value& value) override; - SettableProperty::PropertyType type = SettableProperty::Choice; juce::StringArray choices; std::function menuCreationLambda; juce::Value proxy; diff --git a/Editor/foleys_StylePropertyComponent.cpp b/Editor/foleys_StylePropertyComponent.cpp index ba34c384..a9277ed4 100644 --- a/Editor/foleys_StylePropertyComponent.cpp +++ b/Editor/foleys_StylePropertyComponent.cpp @@ -76,6 +76,13 @@ StylePropertyComponent::StylePropertyComponent (MagicGUIBuilder& builderToUse, j node.removeProperty (property, &builder.getUndoManager()); refresh(); }; + + node.addListener (this); +} + +StylePropertyComponent::~StylePropertyComponent() +{ + node.removeListener (this); } juce::var StylePropertyComponent::lookupValue() @@ -131,5 +138,10 @@ void StylePropertyComponent::mouseDoubleClick (const juce::MouseEvent&) builder.getMagicToolBox().setNodeToEdit (inheritedFrom); } +void StylePropertyComponent::valueTreePropertyChanged (juce::ValueTree& tree, const juce::Identifier& changedProperty) +{ + if (tree == node && property == changedProperty) + refresh(); +} } // namespace foleys diff --git a/Editor/foleys_StylePropertyComponent.h b/Editor/foleys_StylePropertyComponent.h index 9b55eb67..265f3183 100644 --- a/Editor/foleys_StylePropertyComponent.h +++ b/Editor/foleys_StylePropertyComponent.h @@ -40,10 +40,12 @@ namespace foleys { -class StylePropertyComponent : public juce::PropertyComponent +class StylePropertyComponent : public juce::PropertyComponent, + private juce::ValueTree::Listener { public: StylePropertyComponent (MagicGUIBuilder& builder, juce::Identifier property, juce::ValueTree& node); + ~StylePropertyComponent() override; void paint (juce::Graphics& g) override; void resized() override; @@ -65,6 +67,8 @@ class StylePropertyComponent : public juce::PropertyComponent juce::TextButton remove { "X" }; private: + void valueTreePropertyChanged (juce::ValueTree& treeWhosePropertyHasChanged, + const juce::Identifier& changedProperty) override; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StylePropertyComponent) }; diff --git a/Editor/foleys_ToolBox.cpp b/Editor/foleys_ToolBox.cpp index 945adbe8..944e8b0d 100644 --- a/Editor/foleys_ToolBox.cpp +++ b/Editor/foleys_ToolBox.cpp @@ -74,7 +74,6 @@ ToolBox::ToolBox (juce::Component* parentToUse, MagicGUIBuilder& builderToContro file.addItem ("Save XML", [&] { saveDialog(); }); file.addSeparator(); file.addItem ("Clear", [&] { builder.clearGUI(); }); - file.addItem ("Default", [&] { builder.resetToDefaultGUI(); }); file.addSeparator(); file.addItem ("Refresh", [&] { builder.updateComponents(); }); file.show(); diff --git a/General/foleys_MagicGUIBuilder.cpp b/General/foleys_MagicGUIBuilder.cpp index 76891bc0..081c1ed6 100644 --- a/General/foleys_MagicGUIBuilder.cpp +++ b/General/foleys_MagicGUIBuilder.cpp @@ -89,7 +89,7 @@ void MagicGUIBuilder::updateStylesheet() { auto stylesNode = getConfigTree().getOrCreateChildWithName (IDs::styles, &undo); if (stylesNode.getNumChildren() == 0) - stylesNode.appendChild (magicState.createDefaultStylesheet(), &undo); + stylesNode.appendChild (DefaultGuiTrees::createDefaultStylesheet(), &undo); auto selectedName = stylesNode.getProperty (IDs::selected, {}).toString(); if (selectedName.isNotEmpty()) @@ -115,16 +115,6 @@ void MagicGUIBuilder::clearGUI() updateComponents(); } -void MagicGUIBuilder::resetToDefaultGUI() -{ - auto guiNode = getConfigTree().getOrCreateChildWithName (IDs::view, &undo); - guiNode.removeAllChildren (&undo); - guiNode.removeAllProperties (&undo); - guiNode.copyPropertiesAndChildrenFrom (magicState.createDefaultGUITree(), &undo); - - updateComponents(); -} - void MagicGUIBuilder::showOverlayDialog (std::unique_ptr dialog) { if (parent == nullptr) diff --git a/General/foleys_MagicGUIBuilder.h b/General/foleys_MagicGUIBuilder.h index b78f0e99..8e3ebb70 100644 --- a/General/foleys_MagicGUIBuilder.h +++ b/General/foleys_MagicGUIBuilder.h @@ -153,11 +153,6 @@ class MagicGUIBuilder : public juce::ChangeListener */ void clearGUI(); - /** - Remove the current GUI and replaces it with a generated default - */ - void resetToDefaultGUI(); - /** This is used to display a dialog box. It is called by the GUI editor, but in future it might be reached using the configured GUI. diff --git a/General/foleys_MagicJUCEFactories.cpp b/General/foleys_MagicJUCEFactories.cpp index bf37c1ee..a152f54a 100644 --- a/General/foleys_MagicJUCEFactories.cpp +++ b/General/foleys_MagicJUCEFactories.cpp @@ -55,6 +55,11 @@ class SliderItem : public GuiItem static const juce::Identifier pValue; static const juce::Identifier pMinValue; static const juce::Identifier pMaxValue; + static const juce::Identifier pInterval; + static const juce::Identifier pSuffix; + + static const juce::Identifier pFilmStrip; + static const juce::Identifier pNumImages; SliderItem (MagicGUIBuilder& builder, const juce::ValueTree& node) : GuiItem (builder, node) { @@ -106,8 +111,12 @@ class SliderItem : public GuiItem double minValue = getProperty (pMinValue); double maxValue = getProperty (pMaxValue); + double interval = getProperty (pInterval); if (maxValue > minValue) - slider.setRange (minValue, maxValue); + slider.setRange (minValue, maxValue, interval); + + auto suffix = getProperty (pSuffix).toString(); + slider.setTextValueSuffix (suffix); auto valueID = configNode.getProperty (pValue, juce::String()).toString(); if (valueID.isNotEmpty()) @@ -116,6 +125,16 @@ class SliderItem : public GuiItem auto paramID = getControlledParameterID ({}); if (paramID.isNotEmpty()) attachment = getMagicState().createAttachment (paramID, slider); + + auto filmStripName = getProperty (pFilmStrip).toString(); + if (filmStripName.isNotEmpty()) + { + auto filmStrip = Resources::getImage (filmStripName); + slider.setFilmStrip (filmStrip); + } + + int numFilmImages = getProperty (pNumImages); + slider.setNumImages (numFilmImages, false); } std::vector getSettableProperties() const override @@ -128,6 +147,10 @@ class SliderItem : public GuiItem props.push_back ({ configNode, pValue, SettableProperty::Choice, 1.0f, magicBuilder.createPropertiesMenuLambda() }); props.push_back ({ configNode, pMinValue, SettableProperty::Number, 0.0f, {} }); props.push_back ({ configNode, pMaxValue, SettableProperty::Number, 2.0f, {} }); + props.push_back ({ configNode, pInterval, SettableProperty::Number, 0.0f, {} }); + props.push_back ({ configNode, pSuffix, SettableProperty::Text, {}, {} }); + props.push_back ({ configNode, pFilmStrip, SettableProperty::Choice, 0.0f, magicBuilder.createChoicesMenuLambda(Resources::getResourceFileNames()) }); + props.push_back ({ configNode, pNumImages, SettableProperty::Number, 0.0f, {} }); return props; } @@ -155,6 +178,10 @@ const juce::StringArray SliderItem::pTextBoxPositions { "no-textbox", "textbox-a const juce::Identifier SliderItem::pValue { "value" }; const juce::Identifier SliderItem::pMinValue { "min-value" }; const juce::Identifier SliderItem::pMaxValue { "max-value" }; +const juce::Identifier SliderItem::pInterval { "interval" }; +const juce::Identifier SliderItem::pSuffix { "suffix" }; +const juce::Identifier SliderItem::pFilmStrip { "filmstrip" }; +const juce::Identifier SliderItem::pNumImages { "num-filmstrip-images" }; //============================================================================== diff --git a/General/foleys_MagicPluginEditor.cpp b/General/foleys_MagicPluginEditor.cpp index bb94567c..b29bf087 100644 --- a/General/foleys_MagicPluginEditor.cpp +++ b/General/foleys_MagicPluginEditor.cpp @@ -55,17 +55,9 @@ MagicPluginEditor::MagicPluginEditor (MagicProcessorState& stateToUse, std::uniq builder->registerJUCELookAndFeels(); } -#if FOLEYS_SAVE_EDITED_GUI_IN_PLUGIN_STATE - auto guiTree = processorState.getValueTree().getChildWithName ("magic"); - if (guiTree.isValid()) - setConfigTree (guiTree); - else - builder->createGUI (*this); -#else // FOLEYS_SAVE_EDITED_GUI_IN_PLUGIN_STATE auto guiTree = processorState.getGuiTree(); if (guiTree.isValid()) setConfigTree (guiTree); -#endif // FOLEYS_SAVE_EDITED_GUI_IN_PLUGIN_STATE updateSize(); @@ -108,24 +100,31 @@ void MagicPluginEditor::updateSize() int minHeight = rootNode.getProperty (IDs::minHeight, 10); int maxWidth = rootNode.getProperty (IDs::maxWidth, maximalBounds.getWidth()); int maxHeight = rootNode.getProperty (IDs::maxHeight, maximalBounds.getHeight()); + double aspect = rootNode.getProperty (IDs::aspect, 0.0); setResizable (resizable, resizeCorner); setResizeLimits (minWidth, minHeight, maxWidth, maxHeight); + if (aspect > 0.0) + getConstrainer()->setFixedAspectRatio (aspect); } setSize (width, height); } -void MagicPluginEditor::setConfigTree (const juce::ValueTree& gui) +void MagicPluginEditor::setConfigTree (const juce::ValueTree& config) { + jassert (config.isValid() && config.hasType(IDs::magic)); + // Set default values - auto rootNode = gui.getChildWithName (IDs::view); - auto& undo = builder->getUndoManager(); - if (! rootNode.hasProperty (IDs::resizable)) rootNode.setProperty (IDs::resizable, true, &undo); - if (! rootNode.hasProperty (IDs::resizeCorner)) rootNode.setProperty (IDs::resizeCorner, true, &undo); + auto rootNode = config.getChildWithName (IDs::view); - processorState.setGuiValueTree (gui); - builder->createGUI (*this); + if (rootNode.isValid()) + { + auto& undo = builder->getUndoManager(); + if (!rootNode.hasProperty(IDs::resizable)) rootNode.setProperty (IDs::resizable, true, &undo); + if (!rootNode.hasProperty(IDs::resizeCorner)) rootNode.setProperty (IDs::resizeCorner, true, &undo); + } + builder->createGUI (*this); updateSize(); } diff --git a/General/foleys_MagicPluginEditor.h b/General/foleys_MagicPluginEditor.h index 8d3dfd0a..a54cacad 100644 --- a/General/foleys_MagicPluginEditor.h +++ b/General/foleys_MagicPluginEditor.h @@ -58,9 +58,9 @@ class MagicPluginEditor : public juce::AudioProcessorEditor, /** Setup a GUI from a previously stored ValueTree - @param gui the ValueTree that defines the GUI of the editor + @param gui the ValueTree that defines the Stylesheet, colour palette and GUI components of the editor */ - void setConfigTree (const juce::ValueTree& gui); + void setConfigTree (const juce::ValueTree& config); /** Grants access to the MagicGUIBuilder diff --git a/General/foleys_MagicProcessor.cpp b/General/foleys_MagicProcessor.cpp index 24123a9c..0d999c13 100644 --- a/General/foleys_MagicProcessor.cpp +++ b/General/foleys_MagicProcessor.cpp @@ -66,6 +66,25 @@ void MagicProcessor::initialiseBuilder (MagicGUIBuilder& builder) builder.registerJUCELookAndFeels(); } +juce::ValueTree MagicProcessor::createGuiValueTree() +{ + juce::ValueTree magic { IDs::magic }; + magic.appendChild (DefaultGuiTrees::createDefaultStylesheet(), nullptr); + + juce::ValueTree rootNode {IDs::view, {{ IDs::id, IDs::root }}}; + + auto plotView = DefaultGuiTrees::createPlotView (magicState); + if (plotView.isValid()) + rootNode.appendChild (plotView, nullptr); + + auto params = DefaultGuiTrees::createProcessorGui (getParameterTree()); + rootNode.appendChild (params, nullptr); + + magic.appendChild (rootNode, nullptr); + + return magic; +} + juce::AudioProcessorEditor* MagicProcessor::createEditor() { magicState.updateParameterMap(); @@ -73,6 +92,9 @@ juce::AudioProcessorEditor* MagicProcessor::createEditor() auto builder = std::make_unique(magicState); initialiseBuilder (*builder); + if (magicState.getGuiTree().getChildWithName (IDs::view).isValid() == false) + magicState.setGuiValueTree (createGuiValueTree()); + return new MagicPluginEditor (magicState, std::move (builder)); } diff --git a/General/foleys_MagicProcessor.h b/General/foleys_MagicProcessor.h index f61487e1..9a0aba18 100644 --- a/General/foleys_MagicProcessor.h +++ b/General/foleys_MagicProcessor.h @@ -71,6 +71,16 @@ class MagicProcessor : public juce::AudioProcessor */ virtual void initialiseBuilder (MagicGUIBuilder& builder); + /** + This method is called to create the GUI. The default implementation will come up with a ValueTree containing + a default Stylesheet and populate the GUI components from the AudioProcessorParameters it finds using + getParameterTree() as well as getting the MagicPlotSources. + + You can override this method with your bespoke algorithm to create a ValueTree or to load your ValueTree + from BinaryData. + */ + virtual juce::ValueTree createGuiValueTree(); + /** If there is anything you need to do after a new state was loaded you can override this method */ diff --git a/General/foleys_StringDefinitions.h b/General/foleys_StringDefinitions.h index 0ae2a9bc..8d038a36 100644 --- a/General/foleys_StringDefinitions.h +++ b/General/foleys_StringDefinitions.h @@ -157,6 +157,7 @@ namespace IDs static juce::Identifier maxHeight { "max-height" }; static juce::Identifier width { "width" }; static juce::Identifier height { "height" }; + static juce::Identifier aspect { "aspect" }; static juce::Identifier posX { "pos-x" }; static juce::Identifier posY { "pos-y" }; diff --git a/Helpers/foleys_DefaultGuiTrees.cpp b/Helpers/foleys_DefaultGuiTrees.cpp new file mode 100644 index 00000000..3240d7b8 --- /dev/null +++ b/Helpers/foleys_DefaultGuiTrees.cpp @@ -0,0 +1,167 @@ +/* + ============================================================================== + Copyright (c) 2021 Foleys Finest Audio - Daniel Walz + All rights reserved. + + License for non-commercial projects: + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + 3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + License for commercial products: + + To sell commercial products containing this module, you are required to buy a + License from https://foleysfinest.com/developer/pluginguimagic/ + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + OF THE POSSIBILITY OF SUCH DAMAGE. + ============================================================================== + */ + + +namespace foleys +{ + + +juce::ValueTree DefaultGuiTrees::createDefaultDocument (juce::ValueTree gui) +{ + return { IDs::magic, {}, + { + juce::ValueTree (IDs::styles, {}, { DefaultGuiTrees::createDefaultStylesheet() }), + gui + } + }; +} + +juce::ValueTree DefaultGuiTrees::createHelloWorld() +{ + return {IDs::view, {{"id", "root"}}, + { + {"Label", { + {"text", "Hello world!"}, + {"font-size", "25"}, + {"justification", "centred"} + }} + }}; +} + +void DefaultGuiTrees::createDefaultFromParameters (juce::ValueTree& node, const juce::AudioProcessorParameterGroup& tree) +{ + for (const auto& sub : tree.getSubgroups (false)) + { + auto child = juce::ValueTree (IDs::view, { + {IDs::caption, sub->getName()}, + {IDs::styleClass, "group"}}); + + createDefaultFromParameters (child, *sub); + node.appendChild (child, nullptr); + } + + for (const auto& param : tree.getParameters (false)) + { + auto child = juce::ValueTree (IDs::slider); + if (dynamic_cast(param) != nullptr) + child = juce::ValueTree (IDs::comboBox); + else if (dynamic_cast(param) != nullptr) + child = juce::ValueTree (IDs::toggleButton); + + child.setProperty (IDs::caption, param->getName (64), nullptr); + if (const auto* parameterWithID = dynamic_cast(param)) + child.setProperty (IDs::parameter, parameterWithID->paramID, nullptr); + + node.appendChild (child, nullptr); + } +} + +juce::ValueTree DefaultGuiTrees::createProcessorGui (const juce::AudioProcessorParameterGroup& tree) +{ + juce::ValueTree params { IDs::view, { + { IDs::styleClass, "parameters nomargin" }}}; + + createDefaultFromParameters (params, tree); + + return params; +} + +juce::ValueTree DefaultGuiTrees::createPlotView (const MagicGUIState& magicState) +{ + auto plotNames = magicState.getObjectIDsByType(); + + if (plotNames.isEmpty()) + return {}; + + juce::StringArray colours { "orange", "blue", "red", "silver", "green", "cyan", "brown", "white" }; + int nextColour = 0; + + juce::ValueTree child { IDs::view, { + { IDs::id, "plot-view" }, + { IDs::styleClass, "plot-view" }}}; + + for (auto plotName : plotNames) + { + child.appendChild ({IDs::plot, { + { IDs::source, plotName }, + { IDs::styleClass, "transparent" }, + { "plot-color", colours [nextColour++] }}}, nullptr); + + if (nextColour >= colours.size()) + nextColour = 0; + } + + return child; +} + +juce::ValueTree DefaultGuiTrees::createDefaultStylesheet() +{ + juce::ValueTree style (IDs::style, {{ IDs::name, "default" }}, + { + { IDs::nodes, {} }, + { IDs::classes, {}, { + { "plot-view", { + { IDs::border, 2 }, + { IDs::backgroundColour, "black" }, + { IDs::borderColour, "silver" }, + { IDs::display, IDs::contents } + } }, + { "nomargin", { + { IDs::margin, 0 }, + { IDs::padding, 0 }, + { IDs::border, 0 }} }, + { "group", { + { IDs::margin, 5 }, + { IDs::padding, 5 }, + { IDs::border, 2 }, + { IDs::flexDirection, IDs::flexDirColumn }} }, + { "transparent", { + { IDs::backgroundColour, "transparentblack" }} } + } }, + { IDs::types, {}, { + { "Slider", {{ IDs::border, 0 }, { "slider-textbox", "textbox-below" }} }, + { "ToggleButton", {{ IDs::border, 0 }, { IDs::maxHeight, 50 }, { IDs::captionSize, 0 }, { "text", "Active" }} }, + { "TextButton", {{ IDs::border, 0 }, { IDs::maxHeight, 50 }, { IDs::captionSize, 0 }} }, + { "ComboBox", {{ IDs::border, 0 }, { IDs::maxHeight, 50 }, { IDs::captionSize, 0 }} }, + { "Plot", {{ IDs::border, 0 }, { IDs::margin, 0 }, { IDs::padding, 0 }, { IDs::backgroundColour, "00000000" }, {IDs::radius, 0}} }, + { "XYDragComponent", {{ IDs::border, 0 }, { IDs::margin, 0 }, { IDs::padding, 0 }, { IDs::backgroundColour, "00000000" }, {IDs::radius, 0}} } + } } + }); + + return style; +} + +} diff --git a/Helpers/foleys_DefaultGuiTrees.h b/Helpers/foleys_DefaultGuiTrees.h new file mode 100644 index 00000000..908a21ea --- /dev/null +++ b/Helpers/foleys_DefaultGuiTrees.h @@ -0,0 +1,62 @@ +/* + ============================================================================== + Copyright (c) 2021 Foleys Finest Audio - Daniel Walz + All rights reserved. + + License for non-commercial projects: + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + 3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + License for commercial products: + + To sell commercial products containing this module, you are required to buy a + License from https://foleysfinest.com/developer/pluginguimagic/ + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + OF THE POSSIBILITY OF SUCH DAMAGE. + ============================================================================== + */ + +#pragma once + +namespace foleys +{ + +class MagicGUIState; + +namespace DefaultGuiTrees +{ + +static inline juce::ValueTree createDefaultDocument (juce::ValueTree gui); + +static inline juce::ValueTree createHelloWorld(); + +static inline juce::ValueTree createProcessorGui (const juce::AudioProcessorParameterGroup& tree); + +static inline void createDefaultFromParameters (juce::ValueTree& node, const juce::AudioProcessorParameterGroup& tree); + +static inline juce::ValueTree createPlotView (const MagicGUIState& magicState); + +static inline juce::ValueTree createDefaultStylesheet(); + +} + + +} diff --git a/Layout/foleys_Container.cpp b/Layout/foleys_Container.cpp index 72a63bee..87b108dd 100644 --- a/Layout/foleys_Container.cpp +++ b/Layout/foleys_Container.cpp @@ -40,6 +40,7 @@ namespace foleys Container::Container (MagicGUIBuilder& builder, juce::ValueTree node) : GuiItem (builder, node) { + addAndMakeVisible (containerBox); } void Container::update() @@ -67,7 +68,7 @@ void Container::update() void Container::addChildItem (std::unique_ptr child) { - addAndMakeVisible (child.get()); + containerBox.addAndMakeVisible (child.get()); children.push_back (std::move (child)); } @@ -80,7 +81,7 @@ void Container::createSubComponents() auto childItem = magicBuilder.createGuiItem (childNode); if (childItem) { - addAndMakeVisible (childItem.get()); + containerBox.addAndMakeVisible (childItem.get()); childItem->createSubComponents(); children.push_back (std::move (childItem)); @@ -147,7 +148,8 @@ void Container::updateLayout() if (children.empty()) return; - auto clientBounds = getClientBounds(); + containerBox.setBounds (getClientBounds()); + containerBox.setBackgroundColour (decorator.getBackgroundColour()); if (layout != LayoutType::Tabbed) tabbedButtons.reset(); @@ -158,10 +160,11 @@ void Container::updateLayout() for (auto& child : children) flexBox.items.add (child->getFlexItem()); - flexBox.performLayout (clientBounds); + flexBox.performLayout (containerBox.getLocalBounds()); } else if (layout == LayoutType::Tabbed) { + auto clientBounds = containerBox.getLocalBounds(); updateTabbedButtons(); tabbedButtons->setBounds (clientBounds.removeFromTop (30)); @@ -171,7 +174,7 @@ void Container::updateLayout() else // layout == Layout::Contents { for (auto& child : children) - child->setBounds (child->resolvePosition (clientBounds)); + child->setBounds (child->resolvePosition (containerBox.getLocalBounds())); } for (auto& child : children) @@ -202,7 +205,7 @@ void Container::updateContinuousRedraw() void Container::updateTabbedButtons() { tabbedButtons = std::make_unique(juce::TabbedButtonBar::TabsAtTop); - addAndMakeVisible (*tabbedButtons); + containerBox.addAndMakeVisible (*tabbedButtons); for (auto& child : children) { @@ -281,7 +284,7 @@ void Container::timerCallback() if (p) needsRepaint |= p->needsUpdate(); if (needsRepaint) - repaint(); + containerBox.repaint(); } void Container::changeListenerCallback (juce::ChangeBroadcaster*) @@ -317,4 +320,20 @@ void Container::setEditMode (bool shouldEdit) } #endif +//============================================================================== + +Container::ContainerBox::ContainerBox (Container& ownerToUse) +: owner (ownerToUse) {} + +void Container::ContainerBox::paint (juce::Graphics& g) +{ + owner.decorator.drawDecorator (g, {-getX(), -getY(), owner.getWidth(), owner.getHeight()}); +} + +void Container::ContainerBox::setBackgroundColour (juce::Colour colour) +{ + backgroundColour = colour; + setOpaque (backgroundColour.isOpaque()); +} + } // namespace foleys diff --git a/Layout/foleys_Container.h b/Layout/foleys_Container.h index 9406a0c1..26185615 100644 --- a/Layout/foleys_Container.h +++ b/Layout/foleys_Container.h @@ -126,6 +126,19 @@ class Container : public GuiItem, #endif private: + class ContainerBox : public juce::Component + { + public: + ContainerBox (Container& owner); + + void paint (juce::Graphics& g) override; + void setBackgroundColour (juce::Colour colour); + + private: + Container& owner; + juce::Colour backgroundColour; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ContainerBox) + }; void changeListenerCallback (juce::ChangeBroadcaster*) override; void timerCallback() override; @@ -139,6 +152,7 @@ class Container : public GuiItem, LayoutType layout = LayoutType::FlexBox; juce::FlexBox flexBox; + ContainerBox containerBox { *this }; std::unique_ptr tabbedButtons; std::vector> children; diff --git a/Layout/foleys_Decorator.cpp b/Layout/foleys_Decorator.cpp index c5ebf36b..9ae337a5 100644 --- a/Layout/foleys_Decorator.cpp +++ b/Layout/foleys_Decorator.cpp @@ -104,6 +104,11 @@ juce::Colour Decorator::getTabColour() const return tabColour; } +juce::Colour Decorator::getBackgroundColour() const +{ + return backgroundColour; +} + void Decorator::updateColours (MagicGUIBuilder& builder, const juce::ValueTree& node) { auto& stylesheet = builder.getStylesheet(); @@ -128,7 +133,9 @@ Decorator::ClientBounds Decorator::getClientBounds (juce::Rectangle overall if (caption.isNotEmpty()) { - if (justification.getOnlyVerticalFlags() & juce::Justification::top) + if (justification == juce::Justification::centred) + captionBox = overallBounds; + else if (justification.getOnlyVerticalFlags() & juce::Justification::top) captionBox = box.removeFromTop (captionSize).toNearestInt(); else if (justification.getOnlyVerticalFlags() & juce::Justification::bottom) captionBox = box.removeFromBottom (captionSize).toNearestInt(); @@ -167,8 +174,8 @@ void Decorator::configure (MagicGUIBuilder& builder, const juce::ValueTree& node if (! radiusVar.isVoid()) radius = static_cast (radiusVar); - caption = node.getProperty (IDs::caption, juce::String()); - tabCaption = node.getProperty (IDs::tabCaption, juce::String()); + caption = builder.getStyleProperty (IDs::caption, node); + tabCaption = builder.getStyleProperty (IDs::tabCaption, node); auto tc = builder.getStyleProperty (IDs::tabColour, node); if (! tc.isVoid()) tabColour = stylesheet.getColour (tc.toString()); @@ -178,7 +185,7 @@ void Decorator::configure (MagicGUIBuilder& builder, const juce::ValueTree& node captionSize = static_cast (sizeVar); auto placementVar = builder.getStyleProperty (IDs::captionPlacement, node); - if (! placementVar.isVoid()) + if (! placementVar.isVoid() && placementVar.toString().isNotEmpty()) justification = juce::Justification (makeJustificationsChoices()[placementVar.toString()]); else justification = juce::Justification::centredTop; diff --git a/Layout/foleys_Decorator.h b/Layout/foleys_Decorator.h index 48edb5a6..d447c748 100644 --- a/Layout/foleys_Decorator.h +++ b/Layout/foleys_Decorator.h @@ -68,6 +68,8 @@ class Decorator juce::String getTabCaption (const juce::String& defaultName) const; juce::Colour getTabColour() const; + juce::Colour getBackgroundColour() const; + private: juce::Colour backgroundColour { juce::Colours::darkgrey }; diff --git a/Layout/foleys_GuiItem.cpp b/Layout/foleys_GuiItem.cpp index 6a1e53f3..c40f74ef 100644 --- a/Layout/foleys_GuiItem.cpp +++ b/Layout/foleys_GuiItem.cpp @@ -291,7 +291,7 @@ void GuiItem::valueTreePropertyChanged (juce::ValueTree& treeThatChanged, const { if (treeThatChanged == configNode) { - if (auto* parent = dynamic_cast(getParentComponent())) + if (auto* parent = findParentComponentOfClass()) parent->updateInternal(); else updateInternal(); @@ -428,7 +428,7 @@ void GuiItem::setDraggable (bool selected) void GuiItem::savePosition () { - auto* container = dynamic_cast(getParentComponent()); + auto* container = findParentComponentOfClass(); if (container == nullptr) return; diff --git a/Layout/foleys_Stylesheet.cpp b/Layout/foleys_Stylesheet.cpp index 7984d494..e9fa135b 100644 --- a/Layout/foleys_Stylesheet.cpp +++ b/Layout/foleys_Stylesheet.cpp @@ -248,7 +248,11 @@ juce::Colour Stylesheet::parseColour (const juce::String& name) juce::LookAndFeel* Stylesheet::getLookAndFeel (const juce::ValueTree& node) const { - auto lnf = getStyleProperty (IDs::lookAndFeel, node).toString(); + auto lnfNode = getStyleProperty (IDs::lookAndFeel, node); + if (lnfNode.isVoid()) + return nullptr; + + auto lnf = lnfNode.toString(); if (lnf.isNotEmpty()) { const auto& it = lookAndFeels.find (lnf); @@ -351,41 +355,6 @@ bool Stylesheet::isColourPaletteNode (const juce::ValueTree& node) const return false; } -juce::ValueTree Stylesheet::createDefaultStyle() -{ - juce::ValueTree style (IDs::style, {{ IDs::name, "default" }}, - { - { IDs::nodes, {} }, - { IDs::classes, {}, { - { "plot-view", { - { IDs::border, 2 }, - { IDs::backgroundColour, "black" }, - { IDs::borderColour, "silver" }, - { IDs::display, IDs::contents } - } }, - { "nomargin", { - { IDs::margin, 0 }, - { IDs::padding, 0 }, - { IDs::border, 0 }} }, - { "group", { - { IDs::margin, 5 }, - { IDs::padding, 5 }, - { IDs::border, 2 }, - { IDs::flexDirection, IDs::flexDirColumn }} } - } }, - { IDs::types, {}, { - { "Slider", {{ IDs::border, 0 }, { "slider-textbox", "textbox-below" }} }, - { "ToggleButton", {{ IDs::border, 0 }, { IDs::maxHeight, 50 }, { IDs::captionSize, 0 }, { "text", "Active" }} }, - { "TextButton", {{ IDs::border, 0 }, { IDs::maxHeight, 50 }, { IDs::captionSize, 0 }} }, - { "ComboBox", {{ IDs::border, 0 }, { IDs::maxHeight, 50 }, { IDs::captionSize, 0 }} }, - { "Plot", {{ IDs::border, 0 }, { IDs::margin, 0 }, { IDs::padding, 0 }, { IDs::backgroundColour, "00000000" }, {IDs::radius, 0}} }, - { "XYDragComponent", {{ IDs::border, 0 }, { IDs::margin, 0 }, { IDs::padding, 0 }, { IDs::backgroundColour, "00000000" }, {IDs::radius, 0}} } - } } - }); - - return style; -} - juce::StringArray Stylesheet::getAllClassesNames() const { juce::StringArray names; diff --git a/Layout/foleys_Stylesheet.h b/Layout/foleys_Stylesheet.h index 64275e49..a2e8a837 100644 --- a/Layout/foleys_Stylesheet.h +++ b/Layout/foleys_Stylesheet.h @@ -156,11 +156,6 @@ class Stylesheet : private juce::ValueTree::Listener */ void registerLookAndFeel (juce::String name, std::unique_ptr lookAndFeel); - /** - This creates a default stylesheet from scratch, to allow the default GUI to look sensible. - */ - static juce::ValueTree createDefaultStyle(); - juce::StringArray getAllClassesNames() const; juce::StringArray getLookAndFeelNames() const; diff --git a/LookAndFeels/foleys_LookAndFeel.cpp b/LookAndFeels/foleys_LookAndFeel.cpp index ad38d0c3..760c31d7 100644 --- a/LookAndFeels/foleys_LookAndFeel.cpp +++ b/LookAndFeels/foleys_LookAndFeel.cpp @@ -247,4 +247,100 @@ void LookAndFeel::drawTabButton (juce::TabBarButton& button, juce::Graphics& g, textLayout.draw (g, juce::Rectangle (length, depth)); } +//============================================================================== + +juce::Colour LookAndFeel::findPopupColour (int colourId, juce::Component* target) +{ + if (target) + return target->findColour (colourId); + + return findColour (colourId); +} + +void LookAndFeel::drawPopupMenuItemWithOptions (juce::Graphics& g, const juce::Rectangle& area, + const bool isHighlighted, + const juce::PopupMenu::Item& item, + const juce::PopupMenu::Options& options) +{ + auto textColour = findPopupColour (juce::PopupMenu::textColourId, options.getTargetComponent()); + + if (item.isSeparator) + { + auto r = area.reduced (5, 0); + r.removeFromTop (juce::roundToInt (((float) r.getHeight() * 0.5f) - 0.5f)); + g.setColour (textColour.withAlpha (0.3f)); + g.fillRect (r.removeFromTop (1)); + } + else + { + auto r = area.reduced (1); + + if (isHighlighted && item.isEnabled) + { + g.setColour (findPopupColour (juce::PopupMenu::highlightedBackgroundColourId, + options.getTargetComponent())); + g.fillRect (r); + + g.setColour (findPopupColour (juce::PopupMenu::highlightedTextColourId, + options.getTargetComponent())); + } + else + { + g.setColour (textColour.withMultipliedAlpha (item.isEnabled ? 1.0f : 0.5f)); + } + + r.reduce (juce::jmin (5, area.getWidth() / 20), 0); + + auto font = getPopupMenuFont(); + + auto maxFontHeight = (float) r.getHeight() / 1.3f; + + if (font.getHeight() > maxFontHeight) + font.setHeight (maxFontHeight); + + g.setFont (font); + + auto iconArea = r.removeFromLeft (juce::roundToInt (maxFontHeight)).toFloat(); + + if (item.image != nullptr) + { + item.image->drawWithin (g, iconArea, juce::RectanglePlacement::centred | juce::RectanglePlacement::onlyReduceInSize, 1.0f); + r.removeFromLeft (juce::roundToInt (maxFontHeight * 0.5f)); + } + else if (item.isTicked) + { + auto tick = getTickShape (1.0f); + g.fillPath (tick, tick.getTransformToScaleToFit (iconArea.reduced (iconArea.getWidth() / 5, 0).toFloat(), true)); + } + + if (item.subMenu.get() != nullptr) + { + auto arrowH = 0.6f * getPopupMenuFont().getAscent(); + + auto x = static_cast (r.removeFromRight ((int) arrowH).getX()); + auto halfH = static_cast (r.getCentreY()); + + juce::Path path; + path.startNewSubPath (x, halfH - arrowH * 0.5f); + path.lineTo (x + arrowH * 0.6f, halfH); + path.lineTo (x, halfH + arrowH * 0.5f); + + g.strokePath (path, juce::PathStrokeType (2.0f)); + } + + r.removeFromRight (3); + g.drawFittedText (item.text, r, juce::Justification::centredLeft, 1); + + if (item.shortcutKeyDescription.isNotEmpty()) + { + auto f2 = font; + f2.setHeight (f2.getHeight() * 0.75f); + f2.setHorizontalScale (0.95f); + g.setFont (f2); + + g.drawText (item.shortcutKeyDescription, r, juce::Justification::centredRight, true); + } + } +} + } // namespace foleys diff --git a/LookAndFeels/foleys_LookAndFeel.h b/LookAndFeels/foleys_LookAndFeel.h index f4bc15cd..e35e511e 100644 --- a/LookAndFeels/foleys_LookAndFeel.h +++ b/LookAndFeels/foleys_LookAndFeel.h @@ -61,7 +61,16 @@ class LookAndFeel : public juce::LookAndFeel_V4 void drawTabButton (juce::TabBarButton&, juce::Graphics&, bool isMouseOver, bool isMouseDown) override; + //============================================================================== + + void drawPopupMenuItemWithOptions (juce::Graphics&, const juce::Rectangle& area, + bool isHighlighted, + const juce::PopupMenu::Item& item, + const juce::PopupMenu::Options&) override; + + private: + juce::Colour findPopupColour (int colourId, juce::Component* target); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LookAndFeel) }; diff --git a/LookAndFeels/foleys_Skeuomorphic.h b/LookAndFeels/foleys_Skeuomorphic.h index c0db4626..33622dc0 100644 --- a/LookAndFeels/foleys_Skeuomorphic.h +++ b/LookAndFeels/foleys_Skeuomorphic.h @@ -72,6 +72,7 @@ class Skeuomorphic : public juce::LookAndFeel_V4 const knobImages& getKnobImages (int diameter); +private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Skeuomorphic) }; diff --git a/README.md b/README.md index 4094cd8d..c0e9cdc6 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ that provides a hierarchical information, and a CSS cascading stylesheet to defi rules for the appearance of the GUI. There is a drag and drop editor to add GUI elements, and to connect to -parameters of your AudioProcessorValueTreeState. Also an editor in the style of FireBug +parameters of your AudioProcessor. Also an editor in the style of FireBug to investigate the individual properties, and how they were obtained/calculated. @@ -20,7 +20,7 @@ All feedback is welcome. Setup ----- -To use the WYSWYG plugin editor, add this module via Projucer to your JUCE project. +To use the WYSWYG plugin editor, add this module via Projucer or CMake to your JUCE project. Instead of inheriting from juce::AudioProcessor inherit foleys::MagicProcessor. Get rid of those methods: diff --git a/State/foleys_MagicGUIState.cpp b/State/foleys_MagicGUIState.cpp index ab684164..d4466a27 100644 --- a/State/foleys_MagicGUIState.cpp +++ b/State/foleys_MagicGUIState.cpp @@ -82,6 +82,8 @@ juce::ValueTree MagicGUIState::getPropertyRoot() const void MagicGUIState::setGuiValueTree (const juce::ValueTree& dom) { + jassert (dom.hasType (IDs::magic)); + guiValueTree = dom; } @@ -97,7 +99,7 @@ void MagicGUIState::setGuiValueTree (const juce::File& file) { auto dom = juce::ValueTree::fromXml (file.loadFileAsString()); if (dom.isValid()) - guiValueTree = dom; + setGuiValueTree (dom); } juce::ValueTree& MagicGUIState::getGuiTree() @@ -120,23 +122,6 @@ juce::ValueTree& MagicGUIState::getSettings() return settings->settings; } -juce::ValueTree MagicGUIState::createDefaultStylesheet() const -{ - return Stylesheet::createDefaultStyle(); -} - -juce::ValueTree MagicGUIState::createDefaultGUITree() const -{ - return {IDs::view, {{"id", "root"}}, - { - {"Label", { - {"text", "Hello world!"}, - {"font-size", "25"}, - {"justification", "centred"} - }} - }}; -} - juce::Value MagicGUIState::getPropertyAsValue (const juce::String& pathToProperty) { auto path = juce::StringArray::fromTokens (pathToProperty, ":", ""); diff --git a/State/foleys_MagicGUIState.h b/State/foleys_MagicGUIState.h index c41e7405..e8411372 100644 --- a/State/foleys_MagicGUIState.h +++ b/State/foleys_MagicGUIState.h @@ -111,16 +111,6 @@ class MagicGUIState virtual std::unique_ptr createAttachment (const juce::String& paramID, juce::Button&) { juce::ignoreUnused(paramID); return nullptr; } - /** - Override this method to create a default Stylesheet, in case nothing was loaded - */ - virtual juce::ValueTree createDefaultStylesheet() const; - - /** - Override this to create a default GUI - */ - virtual juce::ValueTree createDefaultGUITree() const; - /** Return a hierarchical menu of the AudioParameters @@ -236,7 +226,7 @@ class MagicGUIState */ SharedApplicationSettings settings; - juce::ValueTree guiValueTree { "magic" }; + juce::ValueTree guiValueTree { IDs::magic }; juce::ValueTree state { "state" }; juce::MidiKeyboardState keyboardState; diff --git a/State/foleys_MagicProcessorState.cpp b/State/foleys_MagicProcessorState.cpp index 869a9a8e..fdcf4393 100644 --- a/State/foleys_MagicProcessorState.cpp +++ b/State/foleys_MagicProcessorState.cpp @@ -213,75 +213,6 @@ int MagicProcessorState::getLastController() const return midiMapper.getLastController(); } -juce::ValueTree MagicProcessorState::createDefaultGUITree() const -{ - juce::ValueTree rootNode {IDs::view, {{ IDs::id, IDs::root }}}; - - auto current = rootNode; - auto plotNames = getObjectIDsByType(); - - if (plotNames.isEmpty() == false) - { - juce::StringArray colours { "orange", "blue", "red", "silver", "green", "cyan", "brown", "white" }; - int nextColour = 0; - - juce::ValueTree child { IDs::view, { - { IDs::id, "plot-view" }, - { IDs::styleClass, "plot-view" }}}; - - for (auto plotName : plotNames) - { - child.appendChild ({IDs::plot, { - { IDs::source, plotName }, - { "plot-color", colours [nextColour++] }}}, nullptr); - - if (nextColour >= colours.size()) - nextColour = 0; - } - - current.appendChild (child, nullptr); - - juce::ValueTree params { IDs::view, { - { IDs::styleClass, "parameters nomargin" }}}; - - current.appendChild (params, nullptr); - current = params; - } - - createDefaultFromParameters (current, processor.getParameterTree()); - - return rootNode; -} - -void MagicProcessorState::createDefaultFromParameters (juce::ValueTree& node, const juce::AudioProcessorParameterGroup& tree) const -{ - for (const auto& sub : tree.getSubgroups (false)) - { - auto child = juce::ValueTree (IDs::view, { - {IDs::caption, sub->getName()}, - {IDs::styleClass, "group"}}); - - createDefaultFromParameters (child, *sub); - node.appendChild (child, nullptr); - } - - for (const auto& param : tree.getParameters (false)) - { - auto child = juce::ValueTree (IDs::slider); - if (dynamic_cast(param) != nullptr) - child = juce::ValueTree (IDs::comboBox); - else if (dynamic_cast(param) != nullptr) - child = juce::ValueTree (IDs::toggleButton); - - child.setProperty (IDs::caption, param->getName (64), nullptr); - if (const auto* parameterWithID = dynamic_cast(param)) - child.setProperty (IDs::parameter, parameterWithID->paramID, nullptr); - - node.appendChild (child, nullptr); - } -} - - void MagicProcessorState::timerCallback() { getPropertyAsValue ("playhead:bpm").setValue (bpm.load()); diff --git a/State/foleys_MagicProcessorState.h b/State/foleys_MagicProcessorState.h index 91cdf23b..366c6d5f 100644 --- a/State/foleys_MagicProcessorState.h +++ b/State/foleys_MagicProcessorState.h @@ -110,11 +110,6 @@ class MagicProcessorState : public MagicGUIState, std::unique_ptr createAttachment (const juce::String& paramID, juce::ComboBox& combobox) override; std::unique_ptr createAttachment (const juce::String& paramID, juce::Button& button) override; - /** - This override creates the ValueTree defining the GuiItems from the getParameterTree() - */ - juce::ValueTree createDefaultGUITree() const override; - juce::AudioProcessor* getProcessor() override; /** diff --git a/VERSION.md b/VERSION.md index 7155d24b..8c8105a3 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1,9 +1,21 @@ PluginGuiMagic - Versions history ================================ -1.3.2 +1.3.3 ----- +- Added callback to MagicProcessor to allow bespoke generic GUI trees +- Allow caption to be configured from CSS style class +- Added Filmstrip option for knobs +- Added aspect ratio to resize constrainer +- Added interval and suffix to Slider (not with parameter) +- Allow caption to be centered +- Removed `FOLEYS_SAVE_EDITED_GUI_IN_PLUGIN_STATE` +- Fixed default GUI creation + +1.3.2 - 04.04.2021 +------------------ + - New components respect current edit mode - Fixed accidently draggable components - Allow dragging of groups (instead of selecting the child) diff --git a/Widgets/foleys_AutoOrientationSlider.h b/Widgets/foleys_AutoOrientationSlider.h index 034b7894..fb0cce5b 100644 --- a/Widgets/foleys_AutoOrientationSlider.h +++ b/Widgets/foleys_AutoOrientationSlider.h @@ -54,6 +54,32 @@ class AutoOrientationSlider : public juce::Slider resized(); } + void paint (juce::Graphics& g) override + { + if (filmStrip.isNull() || numImages == 0) + { + juce::Slider::paint (g); + } + else + { + auto index = juce::roundToInt ((numImages - 1) * valueToProportionOfLength (getValue())); + auto knobArea = getLookAndFeel().getSliderLayout(*this).sliderBounds; + + if (horizontalFilmStrip) + { + auto w = filmStrip.getWidth() / numImages; + g.drawImage (filmStrip, knobArea.getX(), knobArea.getY(), knobArea.getWidth(), knobArea.getHeight(), + index * w, 0, w, filmStrip.getHeight()); + } + else + { + auto h = filmStrip.getHeight() / numImages; + g.drawImage (filmStrip, knobArea.getX(), knobArea.getY(), knobArea.getWidth(), knobArea.getHeight(), + 0, index * h, filmStrip.getWidth(), h); + } + } + } + void resized() override { if (autoOrientation) @@ -72,10 +98,25 @@ class AutoOrientationSlider : public juce::Slider juce::Slider::resized(); } + void setFilmStrip (juce::Image& image) + { + filmStrip = image; + } + + void setNumImages (int num, bool horizontal) + { + numImages = num; + horizontalFilmStrip = horizontal; + } + private: bool autoOrientation = true; + juce::Image filmStrip; + int numImages = 0; + bool horizontalFilmStrip = false; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AutoOrientationSlider) }; diff --git a/foleys_gui_magic.cpp b/foleys_gui_magic.cpp index 3d7337e3..26e459bf 100644 --- a/foleys_gui_magic.cpp +++ b/foleys_gui_magic.cpp @@ -61,6 +61,8 @@ #include "Layout/foleys_Container.cpp" #include "Layout/foleys_GuiItem.cpp" +#include "Helpers/foleys_DefaultGuiTrees.cpp" + #include "Visualisers/foleys_MagicLevelSource.cpp" #include "Visualisers/foleys_MagicFilterPlot.cpp" #include "Visualisers/foleys_MagicAnalyser.cpp" diff --git a/foleys_gui_magic.h b/foleys_gui_magic.h index 727ba6e1..55df1533 100644 --- a/foleys_gui_magic.h +++ b/foleys_gui_magic.h @@ -37,7 +37,7 @@ ID: foleys_gui_magic vendor: Foleys Finest Audio - version: 1.3.2 + version: 1.3.3 name: Foleys GUI magic description: This module allows to create GUI with a drag and drop editor dependencies: juce_core, juce_audio_basics, juce_audio_devices, juce_audio_formats, @@ -61,12 +61,12 @@ #define FOLEYS_SHOW_GUI_EDITOR_PALLETTE 1 #endif -/** Config: FOLEYS_SAVE_EDITED_GUI_IN_PLUGIN_STATE - This will save the currently edited GUI in the plugin instances state. Best to turn this off - in the product to avoid confusion in updates. - */ -#ifndef FOLEYS_SAVE_EDITED_GUI_IN_PLUGIN_STATE -#define FOLEYS_SAVE_EDITED_GUI_IN_PLUGIN_STATE 1 +/** Config: FOLEYS_ENABLE_BINARY_DATA + Makes the binary resources available to the GUI. Make sure you actually have + at least one file added, or this will fail to compile. + */ +#ifndef FOLEYS_ENABLE_BINARY_DATA +#define FOLEYS_ENABLE_BINARY_DATA 0 #endif /** Config: FOLEYS_ENABLE_OPEN_GL_CONTEXT @@ -105,6 +105,7 @@ #include "Helpers/foleys_ParameterAttachment.h" #include "Helpers/foleys_AtomicValueAttachment.h" #include "Helpers/foleys_Conversions.h" +#include "Helpers/foleys_DefaultGuiTrees.h" #include "Layout/foleys_GradientBackground.h" #include "Layout/foleys_BoxModel.h"