From 99047ade685d3df4301125f9165bbb4864cc3680 Mon Sep 17 00:00:00 2001 From: Daniel Walz Date: Sat, 10 Apr 2021 21:25:59 +0200 Subject: [PATCH 01/19] Fixed combobox colours for FoleysFinest-LookAndFeel --- LookAndFeels/foleys_LookAndFeel.cpp | 96 +++++++++++++++++++++++++++++ LookAndFeels/foleys_LookAndFeel.h | 9 +++ LookAndFeels/foleys_Skeuomorphic.h | 1 + 3 files changed, 106 insertions(+) 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) }; From ae94a26bdaf437dd0a676a6d2016be4048518c88 Mon Sep 17 00:00:00 2001 From: Daniel Walz Date: Sat, 10 Apr 2021 21:26:21 +0200 Subject: [PATCH 02/19] Version bump --- VERSION.md | 5 ++++- foleys_gui_magic.h | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/VERSION.md b/VERSION.md index 7155d24b..1b4babf0 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1,9 +1,12 @@ PluginGuiMagic - Versions history ================================ -1.3.2 +1.3.3 ----- +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/foleys_gui_magic.h b/foleys_gui_magic.h index 727ba6e1..d7b39aaa 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, From 8c2038792d3afd06d9146ce77fd182ebe50d0ea8 Mon Sep 17 00:00:00 2001 From: Daniel Walz Date: Wed, 14 Apr 2021 12:34:59 +0200 Subject: [PATCH 03/19] Added opaque container to avoid redraw of parents --- Layout/foleys_Container.cpp | 30 +++++++++++++++++++++++------- Layout/foleys_Container.h | 12 ++++++++++++ Layout/foleys_Decorator.cpp | 5 +++++ Layout/foleys_Decorator.h | 2 ++ 4 files changed, 42 insertions(+), 7 deletions(-) diff --git a/Layout/foleys_Container.cpp b/Layout/foleys_Container.cpp index 72a63bee..14633f7e 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,17 @@ void Container::setEditMode (bool shouldEdit) } #endif +//============================================================================== + +void Container::ContainerBox::paint (juce::Graphics& g) +{ + g.fillAll (backgroundColour); +} + +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..418b6ab1 100644 --- a/Layout/foleys_Container.h +++ b/Layout/foleys_Container.h @@ -126,6 +126,17 @@ class Container : public GuiItem, #endif private: + class ContainerBox : public juce::Component + { + public: + ContainerBox() = default; + void paint (juce::Graphics& g) override; + void setBackgroundColour (juce::Colour colour); + + private: + juce::Colour backgroundColour; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ContainerBox) + }; void changeListenerCallback (juce::ChangeBroadcaster*) override; void timerCallback() override; @@ -139,6 +150,7 @@ class Container : public GuiItem, LayoutType layout = LayoutType::FlexBox; juce::FlexBox flexBox; + ContainerBox containerBox; std::unique_ptr tabbedButtons; std::vector> children; diff --git a/Layout/foleys_Decorator.cpp b/Layout/foleys_Decorator.cpp index c5ebf36b..d9c04791 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(); 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 }; From bbd1977af3d796b2b1422b7281e300747531c07c Mon Sep 17 00:00:00 2001 From: Daniel Walz Date: Fri, 16 Apr 2021 09:10:38 +0200 Subject: [PATCH 04/19] Restored drawing of background for opaque containers --- Layout/foleys_Container.cpp | 5 ++++- Layout/foleys_Container.h | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Layout/foleys_Container.cpp b/Layout/foleys_Container.cpp index 14633f7e..87b108dd 100644 --- a/Layout/foleys_Container.cpp +++ b/Layout/foleys_Container.cpp @@ -322,9 +322,12 @@ void Container::setEditMode (bool shouldEdit) //============================================================================== +Container::ContainerBox::ContainerBox (Container& ownerToUse) +: owner (ownerToUse) {} + void Container::ContainerBox::paint (juce::Graphics& g) { - g.fillAll (backgroundColour); + owner.decorator.drawDecorator (g, {-getX(), -getY(), owner.getWidth(), owner.getHeight()}); } void Container::ContainerBox::setBackgroundColour (juce::Colour colour) diff --git a/Layout/foleys_Container.h b/Layout/foleys_Container.h index 418b6ab1..26185615 100644 --- a/Layout/foleys_Container.h +++ b/Layout/foleys_Container.h @@ -129,11 +129,13 @@ class Container : public GuiItem, class ContainerBox : public juce::Component { public: - ContainerBox() = default; + 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) }; @@ -150,7 +152,7 @@ class Container : public GuiItem, LayoutType layout = LayoutType::FlexBox; juce::FlexBox flexBox; - ContainerBox containerBox; + ContainerBox containerBox { *this }; std::unique_ptr tabbedButtons; std::vector> children; From f64663780df2d4f72bec5198bc5a602cd5a5a4b9 Mon Sep 17 00:00:00 2001 From: Daniel Walz Date: Sat, 24 Apr 2021 15:15:04 +0200 Subject: [PATCH 05/19] Fixed dragging because of intermediate layer --- Layout/foleys_GuiItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Layout/foleys_GuiItem.cpp b/Layout/foleys_GuiItem.cpp index 6a1e53f3..e624544a 100644 --- a/Layout/foleys_GuiItem.cpp +++ b/Layout/foleys_GuiItem.cpp @@ -428,7 +428,7 @@ void GuiItem::setDraggable (bool selected) void GuiItem::savePosition () { - auto* container = dynamic_cast(getParentComponent()); + auto* container = findParentComponentOfClass(); if (container == nullptr) return; From 9fbaf8d92595cd157cc06aca01eaf2191edbcb15 Mon Sep 17 00:00:00 2001 From: Daniel Walz Date: Sat, 24 Apr 2021 23:24:46 +0200 Subject: [PATCH 06/19] StylePropertyComponent updates now when a property is newly added --- Editor/foleys_StylePropertyComponent.cpp | 12 ++++++++++++ Editor/foleys_StylePropertyComponent.h | 6 +++++- 2 files changed, 17 insertions(+), 1 deletion(-) 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) }; From c2d9167f1833f09f74eb5175622a30528124e96d Mon Sep 17 00:00:00 2001 From: Daniel Walz Date: Sun, 25 Apr 2021 00:17:10 +0200 Subject: [PATCH 07/19] Fixed update of parent on layout changes --- Layout/foleys_GuiItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Layout/foleys_GuiItem.cpp b/Layout/foleys_GuiItem.cpp index e624544a..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(); From 027f7dcb25de30b534049f445ce248f1ecbb1799 Mon Sep 17 00:00:00 2001 From: Daniel Walz Date: Wed, 5 May 2021 15:58:00 +0200 Subject: [PATCH 08/19] Removed obsolete variable --- Editor/foleys_StyleChoicePropertyComponent.h | 1 - 1 file changed, 1 deletion(-) 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; From 812678892eee354d1a6297df33ee5595fc478a81 Mon Sep 17 00:00:00 2001 From: Daniel Walz Date: Wed, 12 May 2021 10:04:32 +0200 Subject: [PATCH 09/19] Averted a crash/assert when starting with empty DOM --- General/foleys_MagicPluginEditor.cpp | 10 +++++++--- State/foleys_MagicGUIState.cpp | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/General/foleys_MagicPluginEditor.cpp b/General/foleys_MagicPluginEditor.cpp index bb94567c..647c5b22 100644 --- a/General/foleys_MagicPluginEditor.cpp +++ b/General/foleys_MagicPluginEditor.cpp @@ -119,9 +119,13 @@ void MagicPluginEditor::setConfigTree (const juce::ValueTree& gui) { // 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); + + 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); + } processorState.setGuiValueTree (gui); builder->createGUI (*this); diff --git a/State/foleys_MagicGUIState.cpp b/State/foleys_MagicGUIState.cpp index ab684164..4ed75f2a 100644 --- a/State/foleys_MagicGUIState.cpp +++ b/State/foleys_MagicGUIState.cpp @@ -97,7 +97,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() From cc661ada03892626627c3a6d3480f74f49375509 Mon Sep 17 00:00:00 2001 From: Daniel Walz Date: Mon, 7 Jun 2021 01:32:54 +0200 Subject: [PATCH 10/19] Allow caption and tabCaption to be configured in style classes --- Layout/foleys_Decorator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Layout/foleys_Decorator.cpp b/Layout/foleys_Decorator.cpp index d9c04791..c58d3f9e 100644 --- a/Layout/foleys_Decorator.cpp +++ b/Layout/foleys_Decorator.cpp @@ -172,8 +172,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()); From feb8ec86e9b5f7aaec1da278062ad309694ea0b0 Mon Sep 17 00:00:00 2001 From: Daniel Walz Date: Mon, 7 Jun 2021 14:37:40 +0200 Subject: [PATCH 11/19] Update version --- VERSION.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/VERSION.md b/VERSION.md index 1b4babf0..23b4501b 100644 --- a/VERSION.md +++ b/VERSION.md @@ -4,6 +4,8 @@ PluginGuiMagic - Versions history 1.3.3 ----- +- Allow caption to be configured from CSS style class + 1.3.2 - 04.04.2021 ------------------ From 3a76c6a89a8755ba11afacc6617c3c7f273b9b1f Mon Sep 17 00:00:00 2001 From: Daniel Walz Date: Sun, 20 Jun 2021 00:40:40 +0200 Subject: [PATCH 12/19] Re-introduced flag for Binary resources --- foleys_gui_magic.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/foleys_gui_magic.h b/foleys_gui_magic.h index d7b39aaa..caea79c8 100644 --- a/foleys_gui_magic.h +++ b/foleys_gui_magic.h @@ -61,6 +61,14 @@ #define FOLEYS_SHOW_GUI_EDITOR_PALLETTE 1 #endif +/** 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_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. From 20a193e40caf2394831aeb9ef62a582f5e3eb1be Mon Sep 17 00:00:00 2001 From: Daniel Walz Date: Sun, 20 Jun 2021 00:41:15 +0200 Subject: [PATCH 13/19] Implemented a Filmstrip option in the Slider --- General/foleys_MagicJUCEFactories.cpp | 17 +++++++++++ Widgets/foleys_AutoOrientationSlider.h | 41 ++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/General/foleys_MagicJUCEFactories.cpp b/General/foleys_MagicJUCEFactories.cpp index bf37c1ee..1cbbd855 100644 --- a/General/foleys_MagicJUCEFactories.cpp +++ b/General/foleys_MagicJUCEFactories.cpp @@ -56,6 +56,9 @@ class SliderItem : public GuiItem static const juce::Identifier pMinValue; static const juce::Identifier pMaxValue; + static const juce::Identifier pFilmStrip; + static const juce::Identifier pNumImages; + SliderItem (MagicGUIBuilder& builder, const juce::ValueTree& node) : GuiItem (builder, node) { setColourTranslation ( @@ -116,6 +119,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 +141,8 @@ 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, pFilmStrip, SettableProperty::Choice, 0.0f, magicBuilder.createChoicesMenuLambda(Resources::getResourceFileNames()) }); + props.push_back ({ configNode, pNumImages, SettableProperty::Number, 0.0f, {} }); return props; } @@ -155,6 +170,8 @@ 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::pFilmStrip { "filmstrip" }; +const juce::Identifier SliderItem::pNumImages { "num-filmstrip-images" }; //============================================================================== 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) }; From 33571a1e40a0fcf80c493aa2dd9960f513c71086 Mon Sep 17 00:00:00 2001 From: Daniel Walz Date: Sat, 26 Jun 2021 00:54:48 +0200 Subject: [PATCH 14/19] Added designing doc --- DESIGNING.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 DESIGNING.md 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. + From c4118a64aadd1ad0afd50adbf729afdcf612bb57 Mon Sep 17 00:00:00 2001 From: Daniel Walz Date: Sat, 26 Jun 2021 01:03:04 +0200 Subject: [PATCH 15/19] Removed FOLEYS_SAVE_EDITED_GUI_IN_PLUGIN_STATE option --- General/foleys_MagicPluginEditor.cpp | 8 -------- README.md | 4 ++-- VERSION.md | 2 ++ foleys_gui_magic.h | 8 -------- 4 files changed, 4 insertions(+), 18 deletions(-) diff --git a/General/foleys_MagicPluginEditor.cpp b/General/foleys_MagicPluginEditor.cpp index 647c5b22..b2033e6d 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(); 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/VERSION.md b/VERSION.md index 23b4501b..293cd94c 100644 --- a/VERSION.md +++ b/VERSION.md @@ -5,6 +5,8 @@ PluginGuiMagic - Versions history ----- - Allow caption to be configured from CSS style class +- Added Filmstrip option for knobs +- Removed `FOLEYS_SAVE_EDITED_GUI_IN_PLUGIN_STATE` 1.3.2 - 04.04.2021 ------------------ diff --git a/foleys_gui_magic.h b/foleys_gui_magic.h index caea79c8..c830766d 100644 --- a/foleys_gui_magic.h +++ b/foleys_gui_magic.h @@ -69,14 +69,6 @@ #define FOLEYS_ENABLE_BINARY_DATA 0 #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 -#endif - /** Config: FOLEYS_ENABLE_OPEN_GL_CONTEXT If selected an juce OpenGLCOntext is attached. Not a big difference on OSX, but vital on Windows. */ From a00802f641772c52fb0b087782cc6fd7fd758951 Mon Sep 17 00:00:00 2001 From: Daniel Walz Date: Sat, 26 Jun 2021 01:19:36 +0200 Subject: [PATCH 16/19] Added check if no LookAndFeel property was found --- Layout/foleys_Stylesheet.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Layout/foleys_Stylesheet.cpp b/Layout/foleys_Stylesheet.cpp index 7984d494..868d67d4 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); From cb5b640564f03ac755701f84a337db6c841cfb98 Mon Sep 17 00:00:00 2001 From: Daniel Walz Date: Wed, 30 Jun 2021 19:37:51 +0200 Subject: [PATCH 17/19] Fixed default GUI, fixed wrong loading error, added callback for own generic GUIs --- General/foleys_MagicGUIBuilder.cpp | 5 +- General/foleys_MagicPluginEditor.cpp | 8 +- General/foleys_MagicPluginEditor.h | 4 +- General/foleys_MagicProcessor.cpp | 22 ++++ General/foleys_MagicProcessor.h | 10 ++ Helpers/foleys_DefaultGuiTrees.cpp | 167 +++++++++++++++++++++++++++ Helpers/foleys_DefaultGuiTrees.h | 62 ++++++++++ Layout/foleys_Stylesheet.cpp | 35 ------ Layout/foleys_Stylesheet.h | 5 - State/foleys_MagicGUIState.cpp | 19 +-- State/foleys_MagicGUIState.h | 12 +- State/foleys_MagicProcessorState.cpp | 69 ----------- State/foleys_MagicProcessorState.h | 5 - foleys_gui_magic.cpp | 2 + foleys_gui_magic.h | 1 + 15 files changed, 276 insertions(+), 150 deletions(-) create mode 100644 Helpers/foleys_DefaultGuiTrees.cpp create mode 100644 Helpers/foleys_DefaultGuiTrees.h diff --git a/General/foleys_MagicGUIBuilder.cpp b/General/foleys_MagicGUIBuilder.cpp index 76891bc0..3fd74b1c 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()) @@ -120,7 +120,8 @@ void MagicGUIBuilder::resetToDefaultGUI() auto guiNode = getConfigTree().getOrCreateChildWithName (IDs::view, &undo); guiNode.removeAllChildren (&undo); guiNode.removeAllProperties (&undo); - guiNode.copyPropertiesAndChildrenFrom (magicState.createDefaultGUITree(), &undo); + // FIXME + // guiNode.copyPropertiesAndChildrenFrom (magicState.createDefaultGUITree(), &undo); updateComponents(); } diff --git a/General/foleys_MagicPluginEditor.cpp b/General/foleys_MagicPluginEditor.cpp index b2033e6d..70feedfd 100644 --- a/General/foleys_MagicPluginEditor.cpp +++ b/General/foleys_MagicPluginEditor.cpp @@ -107,10 +107,12 @@ void MagicPluginEditor::updateSize() 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 rootNode = config.getChildWithName (IDs::view); if (rootNode.isValid()) { @@ -119,9 +121,7 @@ void MagicPluginEditor::setConfigTree (const juce::ValueTree& gui) if (!rootNode.hasProperty(IDs::resizeCorner)) rootNode.setProperty (IDs::resizeCorner, true, &undo); } - processorState.setGuiValueTree (gui); 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/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_Stylesheet.cpp b/Layout/foleys_Stylesheet.cpp index 868d67d4..e9fa135b 100644 --- a/Layout/foleys_Stylesheet.cpp +++ b/Layout/foleys_Stylesheet.cpp @@ -355,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/State/foleys_MagicGUIState.cpp b/State/foleys_MagicGUIState.cpp index 4ed75f2a..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; } @@ -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/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 c830766d..55df1533 100644 --- a/foleys_gui_magic.h +++ b/foleys_gui_magic.h @@ -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" From deb850426addd7df69016652ca93972faf9639c5 Mon Sep 17 00:00:00 2001 From: Daniel Walz Date: Thu, 1 Jul 2021 01:11:31 +0200 Subject: [PATCH 18/19] Added Slider: interval and suffix, PluginEditor: aspect ratio --- Editor/foleys_PropertiesEditor.cpp | 1 + Editor/foleys_ToolBox.cpp | 1 - General/foleys_MagicGUIBuilder.cpp | 11 ----------- General/foleys_MagicGUIBuilder.h | 5 ----- General/foleys_MagicJUCEFactories.cpp | 12 +++++++++++- General/foleys_MagicPluginEditor.cpp | 3 +++ General/foleys_StringDefinitions.h | 1 + VERSION.md | 4 ++++ 8 files changed, 20 insertions(+), 18 deletions(-) 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_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 3fd74b1c..081c1ed6 100644 --- a/General/foleys_MagicGUIBuilder.cpp +++ b/General/foleys_MagicGUIBuilder.cpp @@ -115,17 +115,6 @@ void MagicGUIBuilder::clearGUI() updateComponents(); } -void MagicGUIBuilder::resetToDefaultGUI() -{ - auto guiNode = getConfigTree().getOrCreateChildWithName (IDs::view, &undo); - guiNode.removeAllChildren (&undo); - guiNode.removeAllProperties (&undo); - // FIXME - // 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 1cbbd855..a152f54a 100644 --- a/General/foleys_MagicJUCEFactories.cpp +++ b/General/foleys_MagicJUCEFactories.cpp @@ -55,6 +55,8 @@ 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; @@ -109,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()) @@ -141,6 +147,8 @@ 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, {} }); @@ -170,6 +178,8 @@ 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 70feedfd..b29bf087 100644 --- a/General/foleys_MagicPluginEditor.cpp +++ b/General/foleys_MagicPluginEditor.cpp @@ -100,8 +100,11 @@ 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); 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/VERSION.md b/VERSION.md index 293cd94c..e0ff92f4 100644 --- a/VERSION.md +++ b/VERSION.md @@ -4,9 +4,13 @@ PluginGuiMagic - Versions history 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) - Removed `FOLEYS_SAVE_EDITED_GUI_IN_PLUGIN_STATE` +- Fixed default GUI creation 1.3.2 - 04.04.2021 ------------------ From 9a329d5e64449910aa9984a685d2c6728c34a755 Mon Sep 17 00:00:00 2001 From: Daniel Walz Date: Thu, 1 Jul 2021 16:44:46 +0200 Subject: [PATCH 19/19] Allow captions to be centered --- Layout/foleys_Decorator.cpp | 6 ++++-- VERSION.md | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Layout/foleys_Decorator.cpp b/Layout/foleys_Decorator.cpp index c58d3f9e..9ae337a5 100644 --- a/Layout/foleys_Decorator.cpp +++ b/Layout/foleys_Decorator.cpp @@ -133,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(); @@ -183,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/VERSION.md b/VERSION.md index e0ff92f4..8c8105a3 100644 --- a/VERSION.md +++ b/VERSION.md @@ -9,6 +9,7 @@ PluginGuiMagic - Versions history - 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