diff --git a/currentGitHash.txt b/currentGitHash.txt index cf5953956f..3df8b19285 100644 --- a/currentGitHash.txt +++ b/currentGitHash.txt @@ -1 +1 @@ -23229250a6528f0d5891992b2979b7ee6fb990ae +3735ffd29ed66c01805992b4fe0e574e29d00c5c diff --git a/hi_backend/backend/CompileExporter.cpp b/hi_backend/backend/CompileExporter.cpp index 4f7f5e1580..dd1cdbf41e 100644 --- a/hi_backend/backend/CompileExporter.cpp +++ b/hi_backend/backend/CompileExporter.cpp @@ -533,7 +533,7 @@ CompileExporter::ErrorCodes CompileExporter::exportInternal(TargetTypes type, Bu String codeCommit = hisePath.getChildFile("currentGitHash.txt").loadFileAsString().trim(); - if(buildCommit != codeCommit) + if(buildCommit != codeCommit && !isUsingCIMode()) { auto confirmation = PresetHandler::getCustomName("", "The source code has a different commit hash than the HISE build. This will likely lead to undefined behaviour including compile errors or undetected errors. In order to proceed with the compilation, type in \"I know\" and click OK"); diff --git a/hi_backend/backend/currentGit.h b/hi_backend/backend/currentGit.h index ba074b8952..71210e757a 100644 --- a/hi_backend/backend/currentGit.h +++ b/hi_backend/backend/currentGit.h @@ -1 +1 @@ -#define PREVIOUS_HISE_COMMIT "bc6a0cd141f7087ae277432dde42aacc604abc22" +#define PREVIOUS_HISE_COMMIT "3735ffd29ed66c01805992b4fe0e574e29d00c5c" diff --git a/hi_backend/backend/debug_components/ApiBrowser.cpp b/hi_backend/backend/debug_components/ApiBrowser.cpp index c5e4f3de90..34607799b9 100644 --- a/hi_backend/backend/debug_components/ApiBrowser.cpp +++ b/hi_backend/backend/debug_components/ApiBrowser.cpp @@ -150,9 +150,8 @@ void DspNodeList::NodeItem::paint(Graphics& g) { if (node != nullptr) { - bool selected = node->getRootNetwork()->isSelected(node); - - + bool selected = node->getRootNetwork()->isSelected(node); + auto ca = area.withWidth(4); diff --git a/hi_backend/backend/debug_components/ApiBrowser.h b/hi_backend/backend/debug_components/ApiBrowser.h index 203d12b938..33cb6abb20 100644 --- a/hi_backend/backend/debug_components/ApiBrowser.h +++ b/hi_backend/backend/debug_components/ApiBrowser.h @@ -403,17 +403,23 @@ class DspNodeList : public SearchableListComponent, dragListener(&dragButton, [parameterIndex](DspNetworkGraph* g){ return DspNetworkListeners::MacroParameterDragListener::findSliderComponent(g, parameterIndex); }) { pname.getTextValue().referTo(ptree.getPropertyAsValue(PropertyIds::ID, parent->getUndoManager())); + + auto value = (double)ptree[PropertyIds::Value]; + valueSlider.setValue(value, dontSendNotification); + valueSlider.getValueObject().referTo(ptree.getPropertyAsValue(PropertyIds::Value, parent->getUndoManager())); addAndMakeVisible(valueSlider); addAndMakeVisible(dragButton); addAndMakeVisible(pname); + + valueSlider.setSliderStyle(juce::Slider::SliderStyle::LinearHorizontal); valueSlider.setTextBoxStyle(juce::Slider::TextEntryBoxPosition::NoTextBox, false, 0, 0); valueSlider.setLookAndFeel(&laf); - valueSlider.setValue(ptree[PropertyIds::Value], dontSendNotification); + pname.setFont(GLOBAL_BOLD_FONT()); pname.setEditable(false, true); @@ -554,6 +560,9 @@ class DspNodeList : public SearchableListComponent, void mouseUp(const MouseEvent& event) override { + if(!isEnabled()) + return; + if(event.mods.isShiftDown()) label.showEditor(); else if (node != nullptr) @@ -573,6 +582,8 @@ class DspNodeList : public SearchableListComponent, HiseShapeButton powerButton; HiseShapeButton dragButton; + bool currentlyVisible = true; + Rectangle area; ScopedPointer dragListener; @@ -674,16 +685,96 @@ class DspNodeList : public SearchableListComponent, String getSearchTermForCollection() const override { return "LocalCables"; } }; - struct UsedNodes : public NodeCollection + struct UsedNodes : public NodeCollection, + public DspNetworkListeners::DspNetworkGraphRootListener { UsedNodes(DspNetwork* network) : NodeCollection(network, 1) { setName("Used Nodes"); addItems(network->getListOfUsedNodeIds(), false); + currentRoot = network->getRootNode(); + refreshAlpha(false); + + lockListener.setCallback(network->getValueTree(), { PropertyIds::Locked }, valuetree::AsyncMode::Asynchronously, [this](ValueTree v, const Identifier&) + { + this->refreshAlpha(true); + }); } + void refreshAlpha(bool fade) + { + auto vt = currentRoot->getValueTree(); + + callRecursive(this, [vt, fade](NodeItem* ni) + { + auto nt = ni->node->getValueTree(); + auto isVisible = nt == vt || nt.isAChildOf(vt); + + ValueTree lockedParent; + + valuetree::Helpers::forEachParent(nt, [&](const ValueTree& v) + { + if(v.getType() == PropertyIds::Node) + { + if(v[PropertyIds::Locked]) + { + lockedParent = v; + return true; + } + } + + return false; + }); + + if(lockedParent.isValid() && lockedParent != nt) + isVisible &= (vt == lockedParent || vt.isAChildOf(lockedParent)); + + auto alpha = isVisible ? 1.0f : 0.2f; + + if(fade) + Desktop::getInstance().getAnimator().animateComponent(ni, ni->getBoundsInParent(), alpha, 500.0, false, 1.5, 1.0); + else + ni->setAlpha(alpha); + + ni->setEnabled(alpha > 0.5f); + + return false; + }); + } + + WeakReference currentRoot; + + void onRootChange(NodeBase* newRoot) override + { + if(currentRoot != newRoot) + { + currentRoot = newRoot; + refreshAlpha(true); + } + } + + static void onRootChange(Component& c, NodeBase* n) + { + jassertfalse; + } + + void resized() override + { + NodeCollection::resized(); + + if(!initialised) + { + DspNetworkListeners::initRootListener(this); + initialised = true; + } + } + + bool initialised = false; + String getSearchTermForCollection() const override { return "UsedNodes"; } + + valuetree::RecursivePropertyListener lockListener; }; struct UnusedNodes : public NodeCollection diff --git a/hi_backend/backend/dialog_library/dialog_library.h b/hi_backend/backend/dialog_library/dialog_library.h index 0246d3537c..eda1d99e9b 100644 --- a/hi_backend/backend/dialog_library/dialog_library.h +++ b/hi_backend/backend/dialog_library/dialog_library.h @@ -115,6 +115,41 @@ struct WelcomeScreen: public multipage::EncodedDialogBase Array fileList; }; +struct ScriptnodeTemplateExporter: public EncodedDialogBase +{ + ScriptnodeTemplateExporter(BackendRootWindow* bpe, scriptnode::NodeBase* n): + EncodedDialogBase(bpe), + node(n) + { + loadFrom("897.sNB..D...............35H...oi...UM.........J09R+fIsAcoA.lrVfl.tzniLwvJeGajdMaHDyvtUbuc0f4YTAhcBXdU3KQESTMhwnxL3b9Afb.LG.7Dthf6CiiV7DCJUtTICBWfYKOh6GHpJUYpnjb1xdX30VcIikKGjASUlJVx7k3+BBYtXIyG8CVgoxBBX1vYN1AUpprTQASEKRNG5bztnlfgBGxyZAQOIaUvrWKExf9FBljKjSisP8eAMoZl91eXRUUkHhpYZ+sCC6dqIUkHR.plu4ucruD2AzizgRfauUMcKLNeYenNjHcn4jkZtLV.be+CffoHWzq.tY9y7Hqmy6VPSXwXCv8xgPLocxseanlPMgR6uojynq23lyoShschGi8Pa29LA+2ewYw+Bia7qyU4Lu0W5wRNL42K4nKqXZDUkDw2C41ztKMimbJeo8ho+S8oJ4LnKRcv5iGUiIsSEBefbQDxd57o8+41L0gZ+8KlWhufgwsm07wwtK1fkYv2HNTT+HsG6ghpZzm8zPyg6qCCQc9F8Jl5u98Bhx4cp2YNYI3oC4rIehZlS9ROBB+xP.lzZIP.vHR3HgRQJJpl3pwwHsqQSmI9rQj21EmUSgl1Sdw2LIG3eYIGrl6gO9jXykzFwimbrbpDzgAcxUd8.wYNGCleHcPddh3rSGM2.8ahz6sCBWtd2zMIKZ1q7YR6nrd0M9mcS30BeKoU6fsqrb04uuOgtvjYTYX.njnFRmx3PJ0LkP..DP..jQ.XLipIHJUr.IUJqBpIyB.QjVejCv.hLcr3rADDVtawtHJEc6PFDSFDXlFZwkeYetmqttN7BArpkJdLolMZDl93Peki8rNALClo0C1RVk9eJTFyZbMxCn9nvdmgpB1GcyPQjaOsX8ZDs4dYEB4wLl248O.3r06wnQKTVBEAw17n8AOxnHi+o8+J4KKRPeitpLXq.BY62kwMsAwIAD.FnumFHgx0H1.XHsWTD2pehVnKAxnIgEGHuA3n+dufq+SGFwLxlzf73CurcPUcxa3VbNKWm.ZZ8novmexiRbZ+SPydVz8lBqtAjpu8fnjeTeWdRDxui7Xt1lph1OU0P1TQ9k+nHKZnODXREj+EoUhRDnQos1CtmTna.ho+AcK.z4gnfhQ9bmmHWI9kyJLfeY9C8hety6kNB..X5H...qi..."); + } + + void bindCallbacks() override + { + MULTIPAGE_BIND_CPP(ScriptnodeTemplateExporter, onCreate); + } + + var onCreate(const var::NativeFunctionArgs& args) + { + auto isLocal = (bool)this->state->globalState["Location"]; + + zstd::ZDefaultCompressor comp; + MemoryBlock mb; + + auto copy = node->getValueTree().createCopy(); + + DspNetworkListeners::PatchAutosaver::stripValueTree(copy); + + auto t = isLocal ? BackendDllManager::FolderSubType::ProjectNodeTemplates : BackendDllManager::FolderSubType::GlobalNodeTemplates; + auto root = BackendDllManager::getSubFolder(getMainController(), t); + auto xml = copy.createXml(); + root.getChildFile(node->getName()).withFileExtension("xml").replaceWithText(xml->createDocument("")); + return var(); + } + + NodeBase::Ptr node; +}; + struct AboutWindow: public multipage::EncodedDialogBase { AboutWindow(BackendRootWindow* bpe): diff --git a/hi_core/hi_components/floating_layout/FloatingIcons.cpp b/hi_core/hi_components/floating_layout/FloatingIcons.cpp index c22b32cad7..6840688327 100644 --- a/hi_core/hi_components/floating_layout/FloatingIcons.cpp +++ b/hi_core/hi_components/floating_layout/FloatingIcons.cpp @@ -289,6 +289,10 @@ namespace ColumnIcons 81,79,66,109,103,93,66,147,24,226,65,109,103,170,66,84,227,179,65,99,109,143,194,227,66,246,40,180,65,98,150,163,15,67,242,210,226,65,76,23,39,67,25,132,79,66,229,208,44,67,172,92,163,66,108,20,46,29,67,90,164,160,66,98,45,178,23,67,68,139,111,66,150, 163,7,67,68,11,47,66,231,123,230,66,70,182,24,66,108,143,194,227,66,246,40,180,65,99,101,0,0 }; + static const unsigned char nextIcon[] = { 110,109,142,210,42,68,248,64,73,67,108,143,98,29,68,248,64,73,67,108,0,128,7,68,130,95,215,67,108,254,239,20,68,130,95,215,67,108,142,210,42,68,248,64,73,67,99,109,0,128,69,68,248,64,73,67,108,228,15,56,68,248,64,73,67,108,107,45,34,68,130,95,215,67, +108,136,157,47,68,130,95,215,67,108,0,128,69,68,248,64,73,67,99,101,0,0 }; + + }; diff --git a/hi_dsp_library/dsp_nodes/CableNodes.h b/hi_dsp_library/dsp_nodes/CableNodes.h index e0269926ab..a58deb0904 100644 --- a/hi_dsp_library/dsp_nodes/CableNodes.h +++ b/hi_dsp_library/dsp_nodes/CableNodes.h @@ -1122,6 +1122,50 @@ namespace control } }; + template struct locked_mod: public mothernode, + public pimpl::parameter_node_base, + public pimpl::no_processing + { + SN_NODE_ID("locked_mod"); + SN_GET_SELF_AS_OBJECT(locked_mod); + SN_DESCRIPTION("Adds a scaled modulation dragger to its immediate locked node container parent"); + SN_ADD_SET_VALUE(locked_mod); + + locked_mod() : + pimpl::parameter_node_base(getStaticId()) + {}; + + void setValue(double input) + { + if (this->getParameter().isConnected()) + this->getParameter().call(input); + } + }; + + template struct locked_mod_unscaled: public mothernode, + public pimpl::parameter_node_base, + public pimpl::no_processing, + public pimpl::no_mod_normalisation + { + SN_NODE_ID("locked_mod_unscaled"); + SN_GET_SELF_AS_OBJECT(locked_mod_unscaled); + SN_DESCRIPTION("Adds a unscaled modulation dragger to its immediate locked node container parent"); + SN_ADD_SET_VALUE(locked_mod_unscaled); + + locked_mod_unscaled() : + pimpl::parameter_node_base(getStaticId()), + pimpl::no_mod_normalisation(getStaticId(), { "Value" }) + {}; + + static constexpr bool isNormalisedModulation() { return false; } + + void setValue(double input) + { + if (this->getParameter().isConnected()) + this->getParameter().call(input); + } + }; + template struct converter : public mothernode, public pimpl::templated_mode, diff --git a/hi_dsp_library/node_api/helpers/node_ids.h b/hi_dsp_library/node_api/helpers/node_ids.h index 79430208cf..5e2bcd068d 100644 --- a/hi_dsp_library/node_api/helpers/node_ids.h +++ b/hi_dsp_library/node_api/helpers/node_ids.h @@ -91,6 +91,7 @@ DECLARE_ID(LocalId); DECLARE_ID(ParameterId); DECLARE_ID(Type); DECLARE_ID(Folded); +DECLARE_ID(Locked); DECLARE_ID(FactoryPath); DECLARE_ID(Frozen); DECLARE_ID(EmbeddedData); diff --git a/hi_scripting/scripting/scriptnode/api/DspNetwork.cpp b/hi_scripting/scripting/scriptnode/api/DspNetwork.cpp index 7062ae568d..af3018df7f 100644 --- a/hi_scripting/scripting/scriptnode/api/DspNetwork.cpp +++ b/hi_scripting/scripting/scriptnode/api/DspNetwork.cpp @@ -923,10 +923,10 @@ NodeBase* DspNetwork::createFromValueTree(bool createPolyIfAvailable, ValueTree StringArray sa; auto newId = getNonExistentId(id, sa); newNode->setValueTreeProperty(PropertyIds::ID, newId); - nodes.add(newNode); + nodes.addIfNotAlreadyThere(newNode); } else - currentNodeHolder->nodes.add(newNode); + currentNodeHolder->nodes.addIfNotAlreadyThere(newNode); return newNode.get(); } @@ -2438,6 +2438,12 @@ void HostHelpers::setNumDataObjectsFromValueTree(OpaqueNode& on, const ValueTree }); } +void DspNetworkListeners::DspNetworkGraphRootListener::onChangeStatic(DspNetworkGraphRootListener& l, NodeBase* n) +{ + if(n != nullptr) + l.onRootChange(n); +} + bool OpaqueNetworkHolder::isPolyphonic() const { return false; } @@ -2569,7 +2575,30 @@ void ScriptnodeExceptionHandler::validateMidiProcessingContext(NodeBase* b) #if USE_BACKEND -DspNetworkListeners::MacroParameterDragListener::MacroParameterDragListener(Component* c_, const std::function& initFunction_): + void DspNetworkListeners::initRootListener(DspNetworkGraphRootListener* l) + { + auto asComponent = dynamic_cast(l); + jassert(asComponent != nullptr); + + Component::callRecursive(asComponent->getTopLevelComponent(), [l](DspNetworkGraph* g) + { + g->rootBroadcaster.addListener(*l, DspNetworkGraphRootListener::onChangeStatic); + return true; + }); + } + + WeakReference DspNetworkListeners::getSourceNodeFromComponentDrag(Component* component) + { + if(auto nc = component->findParentComponentOfClass()) + return nc->node.get(); + + if(auto bc = dynamic_cast(component)) + return bc->node; + + return nullptr; + } + + DspNetworkListeners::MacroParameterDragListener::MacroParameterDragListener(Component* c_, const std::function& initFunction_): c(c_), initFunction(initFunction_) { @@ -2628,8 +2657,7 @@ void DspNetworkListeners::MacroParameterDragListener::mouseUp(const MouseEvent& void DspNetworkListeners::MacroParameterDragListener::mouseDown(const MouseEvent& event) { - if(sliderToDrag == nullptr) - initialise(); + initialise(); if(auto hb = dynamic_cast(this->c.getComponent())) { @@ -2680,17 +2708,29 @@ Component* DspNetworkListeners::MacroParameterDragListener::findModulationDragCo Component* DspNetworkListeners::MacroParameterDragListener::findSliderComponent(DspNetworkGraph* g, int parameterIndex) { - g->root->node->getValueTree().setProperty(PropertyIds::ShowParameters, true, g->network->getUndoManager()); + if(g->isShowingRootNode()) + { + g->root->node->getValueTree().setProperty(PropertyIds::ShowParameters, true, g->network->getUndoManager()); - Array sliders; + Array sliders; - Component::callRecursive(g, [&](MacroParameterSlider* s) + Component::callRecursive(g, [&](MacroParameterSlider* s) + { + sliders.addIfNotAlreadyThere(s); + return false; + }); + + return sliders[parameterIndex]->getDragComponent(); + } + else { - sliders.addIfNotAlreadyThere(s); - return false; - }); + auto bc = g->breadcrumbs.getLast(); - return sliders[parameterIndex]->getDragComponent(); + bc->setIsDraggingParameter(parameterIndex); + return bc; + } + + } diff --git a/hi_scripting/scripting/scriptnode/api/DspNetwork.h b/hi_scripting/scripting/scriptnode/api/DspNetwork.h index e0350e502c..9dbde6a996 100644 --- a/hi_scripting/scripting/scriptnode/api/DspNetwork.h +++ b/hi_scripting/scripting/scriptnode/api/DspNetwork.h @@ -993,6 +993,24 @@ struct DspNetworkGraph; struct DspNetworkListeners { + struct DspNetworkGraphRootListener + { + virtual ~DspNetworkGraphRootListener() + { + + } + + static void onChangeStatic(DspNetworkGraphRootListener& l, NodeBase* n); + + virtual void onRootChange(NodeBase* newRoot) = 0; + + JUCE_DECLARE_WEAK_REFERENCEABLE(DspNetworkGraphRootListener); + }; + + static void initRootListener(DspNetworkGraphRootListener* l); + + static WeakReference getSourceNodeFromComponentDrag(Component* component); + struct MacroParameterDragListener: public MouseListener { MacroParameterDragListener(Component* c_, const std::function& initFunction); diff --git a/hi_scripting/scripting/scriptnode/api/ModulationSourceNode.cpp b/hi_scripting/scripting/scriptnode/api/ModulationSourceNode.cpp index 01db3a5fc3..c474032629 100644 --- a/hi_scripting/scripting/scriptnode/api/ModulationSourceNode.cpp +++ b/hi_scripting/scripting/scriptnode/api/ModulationSourceNode.cpp @@ -182,7 +182,16 @@ void ModulationSourceBaseComponent::mouseDrag(const MouseEvent& e) if (getSourceNodeFromParent() == nullptr) return; - if (auto container = dynamic_cast(findParentComponentOfClass()->root.get())) + auto ng = findParentComponentOfClass(); + + DragAndDropContainer* container = nullptr; + + if(ng->isShowingRootNode()) + container = dynamic_cast(ng->root.get()); + else + container = ng; + + if (container != nullptr) { // We need to be able to drag it anywhere... //while (auto pc = DragAndDropContainer::findParentDragContainerFor(dynamic_cast(container))) @@ -254,7 +263,10 @@ scriptnode::ModulationSourceNode* ModulationSourceBaseComponent::getSourceNodeFr { if (auto pc = findParentComponentOfClass()) { - sourceNode = dynamic_cast(pc->node.get()); + if(auto container = dynamic_cast(pc->node.get())) + sourceNode = container->getLockedModNode(); + else + sourceNode = dynamic_cast(pc->node.get()); } } diff --git a/hi_scripting/scripting/scriptnode/api/ModulationSourceNode.h b/hi_scripting/scripting/scriptnode/api/ModulationSourceNode.h index 598d9bcda6..4699355a65 100644 --- a/hi_scripting/scripting/scriptnode/api/ModulationSourceNode.h +++ b/hi_scripting/scripting/scriptnode/api/ModulationSourceNode.h @@ -83,7 +83,7 @@ class WrapperNode : public NodeBase Rectangle getExtraComponentBounds() const; Rectangle getPositionInCanvas(Point topLeft) const override; - Rectangle createRectangleForParameterSliders(int numColumns) const; + virtual void* getObjectPtr() = 0; diff --git a/hi_scripting/scripting/scriptnode/api/Properties.h b/hi_scripting/scripting/scriptnode/api/Properties.h index 060d7ca4ed..b6c8f9b13e 100644 --- a/hi_scripting/scripting/scriptnode/api/Properties.h +++ b/hi_scripting/scripting/scriptnode/api/Properties.h @@ -65,6 +65,7 @@ static constexpr int MacroDragHeight = 20; static constexpr int NodeWidth = 128; static constexpr int NodeHeight = 48; static constexpr int NodeMargin = 10; +static constexpr int ZoomOffset = 60; static constexpr int DuplicateSize = 128; static constexpr int PinHeight = 24; } diff --git a/hi_scripting/scripting/scriptnode/api/StaticNodeWrappers.cpp b/hi_scripting/scripting/scriptnode/api/StaticNodeWrappers.cpp index b71d1f6981..ede704f0f7 100644 --- a/hi_scripting/scripting/scriptnode/api/StaticNodeWrappers.cpp +++ b/hi_scripting/scripting/scriptnode/api/StaticNodeWrappers.cpp @@ -63,62 +63,13 @@ scriptnode::NodeComponent* WrapperNode::createComponent() return nc; } + juce::Rectangle WrapperNode::getPositionInCanvas(Point topLeft) const { - int numParameters = getNumParameters(); - - if (numParameters == 7) - return createRectangleForParameterSliders(4).withPosition(topLeft); - else if (numParameters == 0) - return createRectangleForParameterSliders(0).withPosition(topLeft); - else if (numParameters % 5 == 0) - return createRectangleForParameterSliders(5).withPosition(topLeft); - else if (numParameters % 4 == 0) - return createRectangleForParameterSliders(4).withPosition(topLeft); - else if (numParameters % 3 == 0) - return createRectangleForParameterSliders(3).withPosition(topLeft); - else if (numParameters % 2 == 0) - return createRectangleForParameterSliders(2).withPosition(topLeft); - else if (numParameters == 1) - return createRectangleForParameterSliders(1).withPosition(topLeft); - else - return createRectangleForParameterSliders(5).withPosition(topLeft); + auto b = NodeComponent::PositionHelpers::getPositionInCanvasForStandardSliders(this, topLeft); + return getBoundsToDisplay(b); } -juce::Rectangle WrapperNode::createRectangleForParameterSliders(int numColumns) const -{ - int h = UIValues::HeaderHeight; - - if (getEmbeddedNetwork() != nullptr) - h += 24; - - auto eb = getExtraComponentBounds(); - - h += eb.getHeight(); - - - int w = 0; - - if (numColumns == 0) - w = eb.getWidth() > 0 ? eb.getWidth() : UIValues::NodeWidth * 2; - else - { - int numParameters = getNumParameters(); - int numRows = (int)std::ceil((float)numParameters / (float)numColumns); - - h += numRows * (48 + 28) - 10; - w = jmin(numColumns * 100, numParameters * 100); - } - - - w = jmax(w, eb.getWidth()); - - auto b = Rectangle(0, 0, w, h); - return getBoundsToDisplay(b.expanded(UIValues::NodeMargin)); -} - - - void InterpretedNode::reset() { this->obj.reset(); @@ -835,7 +786,30 @@ TemplateNodeFactory::TemplateNodeFactory(DspNetwork* n) : registerNodeRaw>(); registerNodeRaw>(); registerNodeRaw>(); - + + auto fileTemplates = BackendDllManager::getAllNodeTemplates(n->getScriptProcessor()->getMainController_()); + + for(auto v: fileTemplates) + { + auto name = v[PropertyIds::Name].toString(); + if(name.isEmpty()) + name = v[PropertyIds::ID].toString(); + + registerNodeWithLambda(name, [v](DspNetwork* n, ValueTree dummy) + { + dummy.setProperty(PropertyIds::ID, "AAARG", nullptr); + + Array changes; + auto newTree = n->cloneValueTreeWithNewIds(v, changes, false); + + for(auto& c: changes) + n->changeNodeId(newTree, c.oldId, c.newId, nullptr); + + auto newNode = n->createFromValueTree(n->isPolyphonic(), newTree, true); + + return newNode; + }); + } } int TemplateNodeFactory::Builder::addNode(int parent, const String& path, const String& id, int index) diff --git a/hi_scripting/scripting/scriptnode/api/StaticNodeWrappers.h b/hi_scripting/scripting/scriptnode/api/StaticNodeWrappers.h index 2572fef9fb..d396735365 100644 --- a/hi_scripting/scripting/scriptnode/api/StaticNodeWrappers.h +++ b/hi_scripting/scripting/scriptnode/api/StaticNodeWrappers.h @@ -533,6 +533,15 @@ struct NodeFactory } } + void registerNodeWithLambda(const Identifier& id, const CreateCallback& f) + { + Item newItem; + newItem.cb = f; + newItem.id = id; + + monoNodes.add(newItem); + } + template void registerNodeRaw() { Item newItem; diff --git a/hi_scripting/scripting/scriptnode/node_library/BackendHostFactory.cpp b/hi_scripting/scripting/scriptnode/node_library/BackendHostFactory.cpp index bdbb5af3db..26cc057ba7 100644 --- a/hi_scripting/scripting/scriptnode/node_library/BackendHostFactory.cpp +++ b/hi_scripting/scripting/scriptnode/node_library/BackendHostFactory.cpp @@ -283,6 +283,8 @@ juce::File BackendDllManager::getSubFolder(const MainController* mc, FolderSubTy case FolderSubType::AdditionalCode: return createIfNotDirectory(f.getChildFile("AdditionalCode")); case FolderSubType::CodeLibrary: return createIfNotDirectory(f.getChildFile("CodeLibrary")); case FolderSubType::FaustCode: return createIfNotDirectory(f.getChildFile("CodeLibrary").getChildFile("faust")); + case FolderSubType::GlobalNodeTemplates: return createIfNotDirectory(ProjectHandler::getAppDataDirectory(nullptr).getChildFile("node_templates")); + case FolderSubType::ProjectNodeTemplates: return createIfNotDirectory(f.getChildFile("CodeLibrary").getChildFile("node_templates")); case FolderSubType::ThirdParty: return createIfNotDirectory(f.getChildFile("ThirdParty")); case FolderSubType::DllLocation: #if JUCE_WINDOWS diff --git a/hi_scripting/scripting/scriptnode/node_library/BackendHostFactory.h b/hi_scripting/scripting/scriptnode/node_library/BackendHostFactory.h index 01becbc42b..d61bfd6d01 100644 --- a/hi_scripting/scripting/scriptnode/node_library/BackendHostFactory.h +++ b/hi_scripting/scripting/scriptnode/node_library/BackendHostFactory.h @@ -74,6 +74,8 @@ struct BackendDllManager : public ReferenceCountedObject, CustomNodes, CodeLibrary, FaustCode, + ProjectNodeTemplates, + GlobalNodeTemplates, AdditionalCode, Binaries, DllLocation, @@ -122,7 +124,32 @@ struct BackendDllManager : public ReferenceCountedObject, { return getSubFolder(mc, FolderSubType::ThirdParty).getChildFile("src").getChildFile("rnbo"); } - + + static Array getAllNodeTemplates(MainController* mc) + { + auto globalFolder = getSubFolder(mc, FolderSubType::GlobalNodeTemplates); + auto localFolder = getSubFolder(mc, FolderSubType::ProjectNodeTemplates); + + Array files; + + files.addArray(globalFolder.findChildFiles(File::findFiles, false, "*.xml")); + files.addArray(localFolder.findChildFiles(File::findFiles, false, "*.xml")); + + files.sort(); + + Array list; + + for(auto f: files) + { + if(auto xml = XmlDocument::parse(f)) + { + list.add(ValueTree::fromXml(*xml)); + } + } + + return list; + } + static void addNodePropertyToJSONFile(const MainController* mc, const String& classId, const Identifier& property) { auto thirdPartyFolder = getSubFolder(mc, FolderSubType::ThirdParty); diff --git a/hi_scripting/scripting/scriptnode/node_library/HiseNodeFactory.cpp b/hi_scripting/scripting/scriptnode/node_library/HiseNodeFactory.cpp index ce8bb02176..9a41065300 100644 --- a/hi_scripting/scripting/scriptnode/node_library/HiseNodeFactory.cpp +++ b/hi_scripting/scripting/scriptnode/node_library/HiseNodeFactory.cpp @@ -1314,6 +1314,8 @@ namespace control registerNoProcessNode, ModulationSourceBaseComponent>(); registerNoProcessNode, ModulationSourceBaseComponent>(); + registerNoProcessNode, ModulationSourceBaseComponent>(); + registerNoProcessNode, ModulationSourceBaseComponent>(); registerNoProcessNode, ModulationSourceBaseComponent>(); diff --git a/hi_scripting/scripting/scriptnode/nodes/NodeContainer.cpp b/hi_scripting/scripting/scriptnode/nodes/NodeContainer.cpp index e08d954d22..5577591e6b 100644 --- a/hi_scripting/scripting/scriptnode/nodes/NodeContainer.cpp +++ b/hi_scripting/scripting/scriptnode/nodes/NodeContainer.cpp @@ -482,9 +482,169 @@ SerialNode::SerialNode(DspNetwork* root, ValueTree data) : isVertical.initialise(this); } +struct LockedContainerExtraComponent: public ScriptnodeExtraComponent, + public PathFactory +{ + LockedContainerExtraComponent(NodeBase* nc): + ScriptnodeExtraComponent(nc, nc->getScriptProcessor()->getMainController_()->getGlobalUIUpdater()), + gotoButton("goto", nullptr, *this), + lockIcon(createPath("lock")) + { + gotoButton.onClick = [this]() + { + findParentComponentOfClass()->setCurrentRootNode(this->getObject()); + }; + + addAndMakeVisible(gotoButton); + stop(); + + + + initConnections(); + + auto w = 128; + auto h = 22; + + if(wantsModulationDragger()) + { + addAndMakeVisible(dragger = new ModulationSourceBaseComponent(nc->getScriptProcessor()->getMainController_()->getGlobalUIUpdater())); + w += 128; + h += 28 + UIValues::NodeMargin; + } + + setSize(w, h); + } + + ScopedPointer dragger; + + bool wantsModulationDragger() const + { + return dynamic_cast(getObject())->getLockedModNode() != nullptr; + } + + void timerCallback() override + { + + } + + Array externalConnections; + + void initConnections() + { + auto vt = getObject()->getValueTree(); + auto root = vt.getRoot(); + + struct Con + { + String node; + String parameter; + }; + + Array connections; + + valuetree::Helpers::forEach(vt, [&](const ValueTree& v) + { + if(v.getType() == PropertyIds::Parameter && v[PropertyIds::Automated]) + { + if(v.getParent().getParent() == vt) + return false; + + connections.add({ v.getParent().getParent()[PropertyIds::ID].toString(), v[PropertyIds::ID].toString()}); + } + + return false; + }); + + for(const auto& c: connections) + { + valuetree::Helpers::forEach(root, [&](const ValueTree& v) + { + if(v.getType() == PropertyIds::Connection) + { + auto match = v[PropertyIds::NodeId].toString() == c.node && + v[PropertyIds::ParameterId].toString() == c.parameter; + + if(match && !v.isAChildOf(vt)) + { + DBG(v.createXml()->createDocument("")); + externalConnections.add(v); + } + + } + + return false; + }); + } + } + + void paint(Graphics& g) override + { + auto b = getLocalBounds(); + + if(dragger != nullptr) + b.removeFromBottom(dragger->getHeight() + UIValues::NodeMargin); + + b.removeFromLeft(b.getHeight() * 2); + b.removeFromRight(b.getHeight() * 2); + + String text = "Locked " + getObject()->getPath().toString(); + + auto w = GLOBAL_FONT().getStringWidth(text) + 10.0f; + + g.setColour(Colours::white.withAlpha(0.2f)); + + if(w < b.getWidth()) + { + g.setFont(GLOBAL_FONT()); + g.drawText(text, b.toFloat(), Justification::centred); + } + + g.fillPath(lockIcon); + + } + + Path createPath(const String& url) const override + { + Path p; + + LOAD_PATH_IF_URL("goto", ColumnIcons::openWorkspaceIcon); + LOAD_PATH_IF_URL("lock", ColumnIcons::lockIcon); + + return p; + } + + void resized() override + { + auto b = getLocalBounds(); + + if(dragger != nullptr) + { + dragger->setBounds(b.removeFromBottom(28)); + b.removeFromBottom(UIValues::NodeMargin); + } + + + gotoButton.setBounds(b.removeFromLeft(b.getHeight()).reduced(3)); + + scalePath(lockIcon, b.removeFromRight(b.getHeight()).reduced(5).toFloat()); + + getProperties().set("circleOffsetX", -1 * getWidth() / 2 + b.getHeight() + UIValues::NodeMargin); + getProperties().set("circleOffsetY", -1 * getHeight() + JUCE_LIVE_CONSTANT_OFF(9)); + } + + HiseShapeButton gotoButton; + Path lockIcon; +}; scriptnode::NodeComponent* SerialNode::createComponent() { + if(isLockedContainer()) + { + auto s = new DefaultParameterNodeComponent(this); + s->setExtraComponent(new LockedContainerExtraComponent(this)); + return s; + } + if (!isVertical.getValue()) return new ParallelNodeComponent(this); else @@ -525,6 +685,17 @@ void SerialNode::DynamicSerialProcessor::prepare(PrepareSpecs) } +Rectangle SerialNode::getPositionInCanvas(Point topLeft) const +{ + if(isLockedContainer()) + { + auto b = getBoundsToDisplay(NodeComponent::PositionHelpers::getPositionInCanvasForStandardSliders(this, topLeft)); + return b; + } + else + return getBoundsToDisplay(getContainerPosition(isVertical.getValue(), topLeft)); +} + ParallelNode::ParallelNode(DspNetwork* root, ValueTree data) : NodeBase(root, data, 0) { @@ -538,7 +709,14 @@ NodeComponent* ParallelNode::createComponent() juce::Rectangle ParallelNode::getPositionInCanvas(Point topLeft) const { - return getBoundsToDisplay(getContainerPosition(false, topLeft)); + if(isLockedContainer()) + { + auto b = getBoundsToDisplay(NodeComponent::PositionHelpers::getPositionInCanvasForStandardSliders(this, topLeft)); + return b; + } + + else + return getBoundsToDisplay(getContainerPosition(false, topLeft)); } diff --git a/hi_scripting/scripting/scriptnode/nodes/NodeContainer.h b/hi_scripting/scripting/scriptnode/nodes/NodeContainer.h index 3ece179053..cece044869 100644 --- a/hi_scripting/scripting/scriptnode/nodes/NodeContainer.h +++ b/hi_scripting/scripting/scriptnode/nodes/NodeContainer.h @@ -73,6 +73,16 @@ struct NodeContainer : public AssignableObject void resetNodes(); + bool forceNoLock = false; + + bool isLockedContainer() const + { + if(forceNoLock) + return false; + + return (bool)asNode()->getValueTree()[PropertyIds::Locked]; + } + ParameterDataList createInternalParametersForMacros(); NodeBase* asNode(); @@ -129,6 +139,36 @@ struct NodeContainer : public AssignableObject Rectangle getContainerPosition(bool isVerticalContainer, Point topLeft) const; + ModulationSourceNode* getLockedModNode() const + { + for(auto n: getNodeList()) + { + auto p = n->getPath().toString(); + + if(p.contains("locked_mod")) + { + return dynamic_cast(n.get()); + } + } + + return nullptr; + } + + Rectangle getLockedExtraComponentBounds() const + { + if(isLockedContainer()) + { + if(getLockedModNode() != nullptr) + return { 0, 0, 256, 22 + 2 * UIValues::NodeMargin + 28}; + else + return { 0, 0, UIValues::NodeWidth, 22 + UIValues::NodeMargin }; + } + else + { + return {}; + } + } + protected: void initListeners(bool initParameterListener=true); @@ -223,11 +263,13 @@ class SerialNode : public NodeBase, return NodeContainer::addMacroConnection(source, targetParameter); } - Rectangle getPositionInCanvas(Point topLeft) const override + Rectangle getExtraComponentBounds() const override { - return getBoundsToDisplay(getContainerPosition(isVertical.getValue(), topLeft)); + return getLockedExtraComponentBounds(); } + Rectangle getPositionInCanvas(Point topLeft) const override; + NodePropertyT isVertical; }; @@ -245,6 +287,11 @@ class ParallelNode : public NodeBase, return NodeContainer::addMacroConnection(source, targetParameter); } + Rectangle getExtraComponentBounds() const override + { + return getLockedExtraComponentBounds(); + } + bool forEach(const std::function& f) override { return forEachNode(f); diff --git a/hi_scripting/scripting/scriptnode/ui/DspNetworkComponents.cpp b/hi_scripting/scripting/scriptnode/ui/DspNetworkComponents.cpp index 38174caadf..3b4312832f 100644 --- a/hi_scripting/scripting/scriptnode/ui/DspNetworkComponents.cpp +++ b/hi_scripting/scripting/scriptnode/ui/DspNetworkComponents.cpp @@ -260,6 +260,7 @@ juce::Path DspNetworkPathFactory::createPath(const String& url) const LOAD_EPATH_IF_URL("surround", HnodeIcons::injectNodeIcon); LOAD_EPATH_IF_URL("save", SampleMapIcons::saveSampleMap); LOAD_EPATH_IF_URL("export", SampleMapIcons::monolith); + LOAD_PATH_IF_URL("lock", ColumnIcons::lockIcon); LOAD_PATH_IF_URL("debug", SnexIcons::bugIcon); #endif @@ -269,12 +270,21 @@ juce::Path DspNetworkPathFactory::createPath(const String& url) const DspNetworkGraph::DspNetworkGraph(DspNetwork* n) : network(n), dataReference(n->getValueTree()), - dragOverlay(*this) + dragOverlay(*this), + rootUndoButtons(*this) { + addChildComponent(rootUndoButtons); network->addSelectionListener(this); rebuildNodes(); setWantsKeyboardFocus(true); + lockListener.setCallback(dataReference, { PropertyIds::Locked}, + valuetree::AsyncMode::Asynchronously, + [this](ValueTree v, Identifier id) + { + rebuildNodes(); + }); + cableRepainter.setCallback(dataReference, { PropertyIds::Bypassed }, valuetree::AsyncMode::Asynchronously, [this](ValueTree v, Identifier id) @@ -378,12 +388,20 @@ void DspNetworkGraph::handleAsyncUpdate() void DspNetworkGraph::rebuildNodes() { - addAndMakeVisible(root = dynamic_cast(network->getRootNode()->createComponent())); + ScopedValueSetter svs(dynamic_cast(getCurrentRootNode())->forceNoLock, true); + addAndMakeVisible(root = dynamic_cast(getCurrentRootNode()->createComponent())); + + if(currentRootNode != nullptr) + currentRootNode->getHelpManager().setShowComments(true); + + rebuildBreadCrumbs(); resizeNodes(); } void DspNetworkGraph::resizeNodes() { + ScopedValueSetter svs(dynamic_cast(getCurrentRootNode())->forceNoLock, true); + Component::callRecursive(this, [](NodeComponent* nc) { if(auto mc = dynamic_cast(nc->node.get())) @@ -394,8 +412,32 @@ void DspNetworkGraph::resizeNodes() return false; }); - auto b = network->getRootNode()->getPositionInCanvas({ UIValues::NodeMargin, UIValues::NodeMargin }); - setSize(b.getWidth() + 2 * UIValues::NodeMargin, b.getHeight() + 2 * UIValues::NodeMargin); + auto b = getCurrentRootNode()->getPositionInCanvas({ UIValues::NodeMargin, UIValues::NodeMargin }); + + if(isShowingRootNode()) + { + auto& hm = getCurrentRootNode()->getHelpManager(); + + auto helpBounds = hm.getHelpSize(); + + if(!helpBounds.isEmpty()) + { + if(hm.isHelpBelow()) + b.removeFromTop(-helpBounds.getHeight()); + else + b.removeFromRight(-helpBounds.getWidth()); + } + } + + auto breadCrumbWidth = UIValues::ZoomOffset; + + for(auto bc: breadcrumbs) + { + breadCrumbWidth += bc->getWidth(); + } + + auto wToUse = jmax(breadCrumbWidth, b.getWidth() + 2 * UIValues::NodeMargin); + setSize(wToUse, b.getHeight() + 2 * UIValues::NodeMargin + (!isShowingRootNode() ? UIValues::ZoomOffset : 0)); resized(); } @@ -447,12 +489,147 @@ void DspNetworkGraph::resized() { if (root != nullptr) { - root->setBounds(getLocalBounds().reduced(UIValues::NodeMargin)); - root->setTopLeftPosition({ UIValues::NodeMargin, UIValues::NodeMargin }); + ScopedValueSetter svs(dynamic_cast(getCurrentRootNode())->forceNoLock, true); + + auto b = getLocalBounds(); + + rootUndoButtons.setVisible(!isShowingRootNode()); + + if(!isShowingRootNode()) + { + auto bcArea = b.removeFromTop(UIValues::ZoomOffset); + + bcArea.removeFromLeft(UIValues::NodeMargin); + + rootUndoButtons.setBounds(bcArea.removeFromLeft(UIValues::ZoomOffset)); + rootUndoButtons.resized(); + + for(int i = breadcrumbs.size() - 1; i >= 0; i--) + { + auto bc = breadcrumbs[i]; + auto w = bc->getWidth(); + bc->setBounds(bcArea.removeFromLeft(w).withSizeKeepingCentre(w, 32)); + } + } + + b = b.reduced(UIValues::NodeMargin); + + auto nb = getCurrentRootNode()->getPositionInCanvas({ UIValues::NodeMargin, UIValues::NodeMargin }); + + if(!isShowingRootNode()) + nb = getCurrentRootNode()->getBoundsWithoutHelp(nb); + + nb = b.withSizeKeepingCentre(nb.getWidth(), nb.getHeight()); + + if(!isShowingRootNode()) + { + auto& hm = getCurrentRootNode()->getHelpManager(); + auto hb = hm.getHelpSize(); + + if(!hm.isHelpBelow()) + { + nb = nb.withX(UIValues::NodeMargin); + } + else + { + nb = nb.withY(b.getY()); + } + } + + root->setBounds(nb); root->resized(); } } +struct RootUndoAction: public UndoableAction +{ + RootUndoAction(DspNetworkGraph& p_, NodeBase* prev_, NodeBase* current_): + UndoableAction(), + p(p_), + prev(prev_), + current(current_) + {} + + bool perform() override + { + p.setCurrentRootNode(current, false); + return true; + } + + bool undo() override + { + p.setCurrentRootNode(prev, false); + return true; + } + + DspNetworkGraph& p; + WeakReference prev, current; +}; + +void DspNetworkGraph::setCurrentRootNode(NodeBase* newRoot, bool useUndo) +{ + if(newRoot == network->getRootNode()) + newRoot = nullptr; + + if(newRoot == currentRootNode) + return; + + if(useUndo) + { + String name; + + if(newRoot == nullptr) + name << "show " << network->getId() << " as root"; + else + name << "show " << newRoot->getName() << " as root"; + + rootUndoManager.beginNewTransaction(name); + rootUndoManager.perform(new RootUndoAction(*this, currentRootNode.get(), newRoot)); + } + else + { + auto prevNode = getCurrentRootNode(); + + currentRootNode = newRoot; + + auto newRootNode = getCurrentRootNode(); + + auto zoomIn = newRootNode->getValueTree().isAChildOf(prevNode->getValueTree()); + + rootBroadcaster.sendMessage(sendNotificationAsync, getCurrentRootNode()); + + if(currentRootNode != nullptr && !network->isSignalDisplayEnabled()) + { + network->setSignalDisplayEnabled(true); + } + + if(currentRootNode == nullptr) + network->setSignalDisplayEnabled(false); + + + auto parent = findParentComponentOfClass(); + + auto zoomFactor = zoomIn ? 1.008f : JUCE_LIVE_CONSTANT_OFF(0.993f); + + parent->makeSwapSnapshot(zoomFactor); + + auto g = this; + + auto f = [parent, g]() + { + parent->clearSwapSnapshot(); + + g->rebuildNodes(); + + parent->zoomToRectangle(g->getLocalBounds().expanded(2* UIValues::NodeMargin)); + g->repaint(); + g->grabKeyboardFocus(); + }; + + Timer::callAfterDelay(JUCE_LIVE_CONSTANT_OFF(350), f); + } +} + Colour getSpecialColour(Component* c, Colour defaultColour) { if (NodeComponent* nc = c->findParentComponentOfClass()) @@ -508,6 +685,37 @@ void drawBlockrateForCable(Graphics& g, Point midPoint, Colour cableColou void DspNetworkGraph::paintOverChildren(Graphics& g) { + if(!isShowingRootNode()) + { + auto b = getLocalBounds().removeFromTop(UIValues::ZoomOffset).toFloat(); + + g.setColour(Colours::black.withAlpha(0.1f)); + g.fillRoundedRectangle(b.reduced(2.0f), 3.0f); + + g.setColour(Colours::white); + g.setFont(GLOBAL_BOLD_FONT().withHeight(16.0)); + + auto& hm = getCurrentRootNode()->getHelpManager(); + + auto hb = hm.getHelpSize().toFloat(); + + if(!hb.isEmpty()) + { + auto nb = root->getBoundsInParent().toFloat(); + + if(hm.isHelpBelow()) + hb.setPosition(nb.getBottomLeft()); + else + hb.setPosition(nb.getTopRight()); + + hm.render(g, hb); + } + + + } + + + float HoverAlpha = 0.5f; if (Component::isMouseButtonDownAnywhere()) @@ -516,6 +724,8 @@ void DspNetworkGraph::paintOverChildren(Graphics& g) if (network->isFrozen()) return; + Array targetSlidersWithCable; + if (dragOverlay.alpha != 0.0f) { g.saveState(); @@ -707,6 +917,8 @@ void DspNetworkGraph::paintOverChildren(Graphics& g) thisAlpha = HoverAlpha; GlobalHiseLookAndFeel::paintCable(g, start, end, colourToUse, thisAlpha, hc); + + targetSlidersWithCable.addIfNotAlreadyThere(targetSlider); } } } @@ -752,6 +964,8 @@ void DspNetworkGraph::paintOverChildren(Graphics& g) Colour hc = s->isMouseOver(true) ? Colours::red : Colour(0xFFAAAAAA); auto midPoint = GlobalHiseLookAndFeel::paintCable(g, start, end, c, thisAlpha, hc, network->getCpuProfileFlag()); + targetSlidersWithCable.addIfNotAlreadyThere(s); + if (!midPoint.isOrigin()) { drawBlockrateForCable(g, midPoint, c, thisAlpha, multiSource->getNode(), s->node); @@ -814,6 +1028,8 @@ void DspNetworkGraph::paintOverChildren(Graphics& g) auto midPoint = GlobalHiseLookAndFeel::paintCable(g, start, end, cableColour, thisAlpha, hc, network->getCpuProfileFlag()); + targetSlidersWithCable.addIfNotAlreadyThere(s); + if (!midPoint.isOrigin()) { auto thisSource = ConnectionBase::Helpers::findRealSource(sourceNode); @@ -1062,7 +1278,143 @@ void DspNetworkGraph::paintOverChildren(Graphics& g) } } + Array lockedContainers; + fillChildComponentList(lockedContainers, this); + + for(auto& c: lockedContainers) + { + if(c->getObject()->getValueTree()[PropertyIds::Folded]) + continue; + + for(const auto& ev: c->externalConnections) + { + auto typeId = ev.getParent().getParent().getType(); + + if(typeId == PropertyIds::Parameter) + { + for(auto p: sliderList) + { + if(ev.isAChildOf(p->pTree)) + { + auto start = getCircle(p, true); + auto end = getCircle(c, false); + + auto t = network->getNodeWithId(ev[PropertyIds::NodeId].toString()); + + auto c = t->getColour(); + + if(c.isTransparent()) + c = Colours::white; + + GlobalHiseLookAndFeel::paintCable(g, start, end, c, 0.5f); + break; +; } + } + } + else + { + bool found = false; + + for(auto m: modSourceList) + { + if(ev.isAChildOf(m->getSourceNodeFromParent()->getValueTree())) + { + auto start = getCircle(m, false); + auto end = getCircle(c, false); + + auto t = network->getNodeWithId(ev[PropertyIds::NodeId].toString()); + + auto c = t->getColour(); + + GlobalHiseLookAndFeel::paintCable(g, start, end, c, alpha * 0.5f); + found = true; + break; + } + } + + if(!found) + { + for(auto m: multiOutputList) + { + if(ev.isAChildOf(m->getNode()->getValueTree())) + { + auto evParent = ev.getParent().getParent(); + jassert(evParent.getType() == PropertyIds::SwitchTarget); + auto connectionIndex = evParent.getParent().indexOf(evParent); + + if(connectionIndex == m->getOutputIndex()) + { + auto start = getCircle(dynamic_cast(m), false); + auto end = getCircle(c, false); + + auto t = network->getNodeWithId(ev[PropertyIds::NodeId].toString()); + + auto c = MultiOutputDragSource::getFadeColour(connectionIndex, m->getNumOutputs()).withAlpha(1.0f); + + GlobalHiseLookAndFeel::paintCable(g, start, end, c, alpha * 0.5f); + break; + } + } + } + + } + } + } + } + + if(!isShowingRootNode()) + { + for(auto p: sliderList) + { + if(p->pTree[PropertyIds::Automated] && !targetSlidersWithCable.contains(p)) + { + auto source = p->getConnectionSourceTree(); + + auto typeId = source.getParent().getParent().getType(); + + // Do not show the connections to the current breadcrumb... + if(source.isAChildOf(getCurrentRootNode()->getValueTree())) + continue; + + for(auto bc: breadcrumbs) + { + if(source.isAChildOf(bc->node->getValueTree())) + { + auto start = getCircle(bc, false).translated(0.0, JUCE_LIVE_CONSTANT(0.0f)); + auto end = getCircle(p); + + auto colourToUse = Colours::white; + + if(typeId == PropertyIds::Parameter) + { + // Use the target node colour for the cable when connected to a parameter + if (auto nc = p->findParentComponentOfClass()) + colourToUse = nc->getHeaderColour(); + + } + else if(typeId == PropertyIds::SwitchTarget) + { + // use the multi out colour for the given output index + auto st = valuetree::Helpers::findParentWithType(source, PropertyIds::SwitchTarget); + auto numOutputs = st.getParent().getNumChildren(); + auto index = st.getParent().indexOf(st); + colourToUse = MultiOutputDragSource::getFadeColour(index, numOutputs).withAlpha(1.0f); + } + else + { + // Use the source node for modulation outputs + auto c = valuetree::Helpers::findParentWithType(source, PropertyIds::Node)[PropertyIds::NodeColour]; + colourToUse = PropertyHelpers::getColourFromVar(c); + } + + GlobalHiseLookAndFeel::paintCable(g, start, end, colourToUse, alpha); + break; + } + } + } + } + } if(isMouseButtonDown(true) || externalDragComponent) { @@ -1078,6 +1430,9 @@ void DspNetworkGraph::paintOverChildren(Graphics& g) return ps->parent.dragging; } + if(auto ps = dynamic_cast(c)) + return ps->draggingIndex != -1; + if(dynamic_cast(c)) return true; @@ -1134,7 +1489,7 @@ void DspNetworkGraph::paintOverChildren(Graphics& g) auto fadeAlpha = jlimit(0.0f, 1.0f, 1.0f - delta.getDistanceFromOrigin() / 30.0f); - auto c = c1.interpolatedWith(c2, jlimit(0.0f, 1.0f, fadeAlpha)); + auto c = c2; @@ -1143,6 +1498,7 @@ void DspNetworkGraph::paintOverChildren(Graphics& g) lastMousePos.setX(lastMousePos.getX() * dragSmoothAlpha + currentPosition.getX() * (1.0f - dragSmoothAlpha)); lastMousePos.setY(lastMousePos.getY() * dragSmoothAlpha + currentPosition.getY() * (1.0f - dragSmoothAlpha)); } + } @@ -1829,6 +2185,11 @@ bool DspNetworkGraph::Actions::foldUnselectedNodes(DspNetworkGraph& g) int counter = 0; + if(g.getCurrentRootNode()->getValueTree().isAChildOf(selection.getFirst()->getValueTree())) + { + g.setCurrentRootNode(selection.getFirst()); + } + auto skipAnimation = valuetree::Helpers::forEach(g.network->getValueTree(), [&](const ValueTree& v) { if(v.getType() == PropertyIds::Node) @@ -2357,6 +2718,21 @@ bool DspNetworkGraph::Actions::showJSONEditorForSelection(DspNetworkGraph& g) return true; } +bool DspNetworkGraph::Actions::lockContainer(DspNetworkGraph& g) +{ + auto list = g.network->getSelection(); + + auto firstValue = (bool)list.getFirst()->getValueTree()[PropertyIds::Locked]; + + for(auto l: list) + { + if(auto c = dynamic_cast(l.get())) + l->getValueTree().setProperty(PropertyIds::Locked, !firstValue, l->getUndoManager()); + } + + return true; +} + bool DspNetworkGraph::Actions::eject(DspNetworkGraph& g) { #if USE_BACKEND @@ -2594,7 +2970,13 @@ bool DspNetworkGraph::Actions::zoomFit(DspNetworkGraph& g) auto f = [&g]() { g.findParentComponentOfClass()->clearSwapSnapshot(); - g.findParentComponentOfClass()->zoomToRectangle(g.root->getBoundsInParent()); + + auto b = g.root->getBoundsInParent(); + + if(!g.isShowingRootNode()) + b = g.getLocalBounds(); + + g.findParentComponentOfClass()->zoomToRectangle(b.expanded(UIValues::NodeMargin*2)); g.repaint(); g.grabKeyboardFocus(); }; @@ -2935,11 +3317,6 @@ void DspNetworkGraph::WrapperWithMenuBar::rebuildAfterContentChange() auto id = n->getId(); - if (n->getParentNetwork() != nullptr) - { - BACKEND_ONLY(addCustomComponent(new BreadcrumbComponent(n.get()))); - } - //addButton("debug"); if(n->canBeFrozen()) @@ -2967,6 +3344,10 @@ void DspNetworkGraph::WrapperWithMenuBar::rebuildAfterContentChange() addButton("colour"); addButton("profile"); + addSpacer(10); + + addButton("lock"); + addSpacer(10); addButton("undo"); addButton("redo"); @@ -3029,7 +3410,24 @@ void DspNetworkGraph::WrapperWithMenuBar::addButton(const String& name) b->actionFunction = Actions::showParameterPopup; b->setTooltip("Show all parameters in a popup"); } - + + if(name == "lock") + { + b->actionFunction = Actions::lockContainer; + b->setTooltip("Locks the current container"); + b->enabledFunction = [](DspNetworkGraph& g) + { + auto s = g.network->getSelection(); + return !s.isEmpty() && dynamic_cast(s.getFirst().get()) != nullptr; + }; + + b->stateFunction = [](DspNetworkGraph& g) + { + auto s = g.network->getSelection(); + return !s.isEmpty() && (bool)s.getFirst()->getValueTree()[PropertyIds::Locked]; + }; + } + if(name == "eject") { b->actionFunction = Actions::eject; @@ -3465,42 +3863,4 @@ void KeyboardPopup::ImagePreviewCreator::timerCallback() stopTimer(); } -#if USE_BACKEND -DspNetworkGraph::BreadcrumbComponent::NetworkButton::NetworkButton(DspNetwork* n, bool current_) : - TextButton(n->getId()), - network(n), - current(current_) -{ - w = GLOBAL_BOLD_FONT().getStringWidth(getName()) + 30; - - if (current) - { - changeWarning = new DspNetworkListeners::LambdaAtNetworkChange(n); - changeWarning->additionalCallback = [this]() { this->repaint(); }; - - auto td = BackendDllManager::getSubFolder(n->getScriptProcessor()->getMainController_(), BackendDllManager::FolderSubType::Networks); - - currentSaver = new DspNetworkListeners::PatchAutosaver(n, td); - } - - onClick = BIND_MEMBER_FUNCTION_0(NetworkButton::click); -} - -void DspNetworkGraph::BreadcrumbComponent::NetworkButton::click() -{ - auto bc = findParentComponentOfClass(); - - for (auto nb : bc->buttons) - { - if (nb == this) - continue; - - nb->requestClose(); - } - - findParentComponentOfClass()->canvas.setNewContent(new DspNetworkGraph(network), nullptr); -} - -#endif - } diff --git a/hi_scripting/scripting/scriptnode/ui/DspNetworkComponents.h b/hi_scripting/scripting/scriptnode/ui/DspNetworkComponents.h index 5f13faaa2f..d63153af33 100644 --- a/hi_scripting/scripting/scriptnode/ui/DspNetworkComponents.h +++ b/hi_scripting/scripting/scriptnode/ui/DspNetworkComponents.h @@ -570,9 +570,9 @@ class KeyboardPopup : public Component, - class DspNetworkGraph : public ComponentWithMiddleMouseDrag, public AsyncUpdater, + public DragAndDropContainer, public DspNetwork::SelectionListener { public: @@ -601,116 +601,7 @@ class DspNetworkGraph : public ComponentWithMiddleMouseDrag, repaint(); } }; - -#if USE_BACKEND - struct BreadcrumbComponent: public Component - { - struct NetworkButton : public TextButton - { - NetworkButton(DspNetwork* n, bool current_); - - void requestClose() - { - if (changeWarning != nullptr && changeWarning->isChanged()) - { - if (PresetHandler::showYesNoWindow("Change detected", "The DSP Network " + network->getId() + " has changed. Do you want to save the changes to the file?")) - { - currentSaver->closeAndDelete(true); - - PresetHandler::showMessageWindow("Saved " + network->getId(), "The nested network was saved. Make sure to reload the root network if you used this network multiple times"); - } - } - - changeWarning = nullptr; - currentSaver = nullptr; - } - - ~NetworkButton() - { - - } - - void click(); - - - - void paintButton(Graphics& g, bool isOver, bool isDown) - { - g.setFont(GLOBAL_BOLD_FONT()); - - float alpha = 0.5f; - - if (isOver) - alpha += 0.2f; - - if (isDown) - alpha += 0.2f; - - if (current) - alpha = 0.8f; - - auto showWarning = false; - - if (changeWarning != nullptr && changeWarning->isChanged()) - { - showWarning = true; - } - - auto s = getName(); - - if (showWarning) - s << "*"; - - g.setColour(Colours::white.withAlpha(alpha)); - g.drawText(s, getLocalBounds().toFloat().reduced(10.0f, 0), Justification::left); - - if (!current) - { - g.setColour(Colours::white.withAlpha(0.2f)); - g.drawText(">", getLocalBounds().removeFromRight(20).toFloat(), Justification::centred); - } - } - - const bool current; - int w = 0; - WeakReference network; - - ScopedPointer changeWarning; - ScopedPointer currentSaver; - - }; - - BreadcrumbComponent(DspNetwork* n) - { - bool current = true; - int w = 0; - - while (n != nullptr) - { - buttons.add(new NetworkButton(n, current)); - addAndMakeVisible(buttons.getLast()); - n = n->getParentNetwork(); - current = false; - w += buttons.getLast()->w; - } - - setSize(w, 24); - } - - void resized() override - { - auto b = getLocalBounds(); - for (auto nb : buttons) - { - nb->setBounds(b.removeFromRight(nb->w)); - } - } - - OwnedArray buttons; - - }; -#endif - + struct WrapperWithMenuBar : public WrapperWithMenuBarBase { static bool selectionEmpty(DspNetworkGraph& g) @@ -761,7 +652,9 @@ class DspNetworkGraph : public ComponentWithMiddleMouseDrag, static bool toggleDebug(DspNetworkGraph& g); - static bool eject(DspNetworkGraph& g); + static bool lockContainer(DspNetworkGraph& g); + + static bool eject(DspNetworkGraph& g); static bool showParameterPopup(DspNetworkGraph& g); @@ -798,6 +691,68 @@ class DspNetworkGraph : public ComponentWithMiddleMouseDrag, } + struct RootUndoButtons: public Component, + public PathFactory, + public Button::Listener + { + RootUndoButtons(DspNetworkGraph& parent_): + parent(parent_), + undo("undo", this, *this), + redo("redo", this, *this) + { + addAndMakeVisible(undo); + addAndMakeVisible(redo); + parent.addMouseListener(this, true); + } + + void mouseDown(const MouseEvent& e) override + { + if(e.mods.isX1ButtonDown()) + undo.triggerClick(); + if(e.mods.isX2ButtonDown()) + redo.triggerClick(); + } + + void buttonClicked(Button* b) override + { + if(b == &undo) + parent.rootUndoManager.undo(); + else + parent.rootUndoManager.redo(); + } + + Path createPath(const String& id) const override + { + static const unsigned char pathData[] = { 110,109,0,128,38,68,40,0,64,67,98,2,156,55,68,40,0,64,67,0,128,69,68,24,144,119,67,0,128,69,68,120,255,157,67,98,0,128,69,68,200,55,192,67,2,156,55,68,240,255,219,67,0,128,38,68,240,255,219,67,98,253,99,21,68,240,255,219,67,0,128,7,68,200,55,192,67,0, + 128,7,68,120,255,157,67,98,0,128,7,68,24,144,119,67,253,99,21,68,40,0,64,67,0,128,38,68,40,0,64,67,99,109,186,222,15,68,32,246,169,67,108,58,10,44,68,32,246,169,67,108,58,10,44,68,160,46,192,67,108,70,33,61,68,120,255,157,67,108,58,10,44,68,72,164,119, + 67,108,58,10,44,68,80,10,146,67,108,186,222,15,68,80,10,146,67,108,186,222,15,68,32,246,169,67,99,101,0,0 }; + + Path path; + path.loadPathFromData (pathData, sizeof (pathData)); + + if(id == "undo") + path.applyTransform(AffineTransform::rotation(float_Pi)); + + return path; + } + + void resized() override + { + auto b = getLocalBounds().reduced(4); + undo.setBounds(b.removeFromLeft(b.getWidth() / 2).reduced(3)); + redo.setBounds(b.reduced(3)); + + undo.setEnabled(parent.rootUndoManager.canUndo()); + redo.setEnabled(parent.rootUndoManager.canRedo()); + + undo.setTooltip(parent.rootUndoManager.getUndoDescription()); + redo.setTooltip(parent.rootUndoManager.getRedoDescription()); + } + + DspNetworkGraph& parent; + HiseShapeButton undo, redo; + } rootUndoButtons; + DspNetworkGraph(DspNetwork* n); ~DspNetworkGraph(); @@ -810,6 +765,16 @@ class DspNetworkGraph : public ComponentWithMiddleMouseDrag, void paint(Graphics& g) override; void resized() override; + UndoManager rootUndoManager; + + void setCurrentRootNode(NodeBase* newRoot, bool useUndo=true); + + NodeBase* getCurrentRootNode() const { return currentRootNode != nullptr ? currentRootNode.get() : network->getRootNode(); } + + bool isShowingRootNode() const { return getCurrentRootNode() == network->getRootNode(); } + + WeakReference currentRootNode; + template static void fillChildComponentList(Array& list, Component* c) { for (int i = 0; i < c->getNumChildComponents(); i++) @@ -836,6 +801,158 @@ class DspNetworkGraph : public ComponentWithMiddleMouseDrag, } } + LambdaBroadcaster rootBroadcaster; + + struct BreadcrumbButton: public Component, + public SettableTooltipClient + { + BreadcrumbButton(NodeBase* n, bool isLast_): + node(n), + isLast(isLast_), + f(GLOBAL_BOLD_FONT()) + { + setMouseCursor(MouseCursor::PointingHandCursor); + + icon = nf.createPath(node->getPath().id.toString()); + next = nf.createPath("next"); + + auto w = f.getStringWidth(node->getName()) + 2 * 32 + 2 * UIValues::NodeMargin; + setSize(w, 32); + setRepaintsOnMouseActivity(true); + setTooltip("Show " + node->getName() + " as root node"); + } + + void mouseDrag(const MouseEvent& e) override + { + ZoomableViewport::checkDragScroll(e, false); + + if(draggingIndex != -1) + { + auto ng = findParentComponentOfClass(); + + DynamicObject::Ptr details = new DynamicObject(); + + details->setProperty(PropertyIds::Automated, false); + details->setProperty(PropertyIds::ID, ng->network->getRootNode()->getId()); + details->setProperty(PropertyIds::ParameterId, ng->network->getParameterIdentifier(draggingIndex).toString()); + + ng->startDragging(var(details.get()), this, ScaledImage(ModulationSourceBaseComponent::createDragImageStatic(false))); + ng->repaint(); + } + + } + + void mouseUp(const MouseEvent& e) override + { + ZoomableViewport::checkDragScroll(e, true); + + auto g = findParentComponentOfClass(); + + if(draggingIndex != -1) + { + draggingIndex = -1; + g->repaint(); + return; + } + + auto n = node.get(); + + MessageManager::callAsync([g, n]() + { + g->setCurrentRootNode(n); + }); + } + + void paint(Graphics& g) override + { + auto nc = node->getColour(); + + if(nc == Colours::transparentBlack) + nc = Colour(0xFF999999); + + auto hover = isMouseOver(); + + g.setColour(nc.withAlpha(hover ? 0.2f : 0.1f)); + + auto area = getLocalBounds().toFloat(); + area.removeFromRight(area.getHeight()); + + g.fillRect(area.reduced(5.0f)); + g.setColour(nc); + g.drawRoundedRectangle(area.reduced(2.0f), 3.0f, 2.0f); + + auto n = node->getName(); + + float alpha = 0.5f; + + if(isMouseOver()) + alpha += 0.1f; + + if(isMouseButtonDown()) + alpha += 0.3f; + + g.setColour(Colours::white.withAlpha(alpha)); + g.setFont(f); + auto b = getLocalBounds().toFloat(); + b.removeFromLeft(b.getHeight()); + g.drawText(n, b, Justification::left); + g.setColour(nc); + g.fillPath(icon); + + if(!isLast) + { + g.setColour(Colours::white.withAlpha(0.2f)); + g.fillPath(next); + } + } + + void resized() override + { + auto b = getLocalBounds(); + + getProperties().set("circleOffsetY", 5); + getProperties().set("circleOffsetX", -1 * (b.getWidth() / 2) + UIValues::NodeMargin); + + nf.scalePath(icon, b.removeFromLeft(getHeight()).reduced(8).toFloat()); + nf.scalePath(next, b.removeFromRight(getHeight()).reduced(10).toFloat()); + } + + void setIsDraggingParameter(int parameterIndex) + { + draggingIndex = parameterIndex; + } + + int draggingIndex = -1; + + Path icon; + Path next; + bool isLast; + NodeComponentFactory nf; + WeakReference node; + Font f; + }; + + OwnedArray breadcrumbs; + + void rebuildBreadCrumbs() + { + breadcrumbs.clear(); + + if(!isShowingRootNode()) + { + auto n = getCurrentRootNode(); + + while(n != nullptr) + { + + auto b = new BreadcrumbButton(n, n == getCurrentRootNode()); + addAndMakeVisible(b); + breadcrumbs.add(b); + n = n->getParentNode(); + } + } + } + void paintOverChildren(Graphics& g) override; void toggleProbeMode() @@ -1004,6 +1121,7 @@ class DspNetworkGraph : public ComponentWithMiddleMouseDrag, Point lastMousePos; valuetree::RecursivePropertyListener cableRepainter; + valuetree::RecursivePropertyListener lockListener; valuetree::ChildListener rebuildListener; valuetree::RecursivePropertyListener resizeListener; diff --git a/hi_scripting/scripting/scriptnode/ui/NodeComponent.cpp b/hi_scripting/scripting/scriptnode/ui/NodeComponent.cpp index 40ff04b471..f42bd8e619 100644 --- a/hi_scripting/scripting/scriptnode/ui/NodeComponent.cpp +++ b/hi_scripting/scripting/scriptnode/ui/NodeComponent.cpp @@ -65,7 +65,9 @@ NodeComponent::Header::Header(NodeComponent& parent_) : tooltip << ", Type: " << d[PropertyIds::FactoryPath].toString(); setTooltip(tooltip); - + + setWantsKeyboardFocus(true); + powerButton.setToggleModeWithColourChange(true); powerButtonUpdater.setCallback(parent.node->getValueTree(), { PropertyIds::Bypassed}, @@ -96,7 +98,10 @@ NodeComponent::Header::Header(NodeComponent& parent_) : freezeButton.setToggleModeWithColourChange(true); - auto isContainer = dynamic_cast(parent.node.get()) != nullptr; + bool isContainer = false; + + if(auto nc = dynamic_cast(parent.node.get())) + isContainer = !nc->isLockedContainer(); parameterButton.setToggleModeWithColourChange(true); parameterButton.setToggleStateAndUpdateIcon(parent.dataReference[PropertyIds::ShowParameters]); @@ -180,8 +185,11 @@ void NodeComponent::Header::resized() freezeButton.setBounds(deleteButton.getBounds()); powerButton.setVisible(!parent.isRoot()); + + + deleteButton.setVisible(!parent.isRoot()); - freezeButton.setVisible(parent.isRoot()); + freezeButton.setVisible(false); } void NodeComponent::Header::mouseDown(const MouseEvent& e) @@ -256,6 +264,49 @@ bool NodeComponent::Header::isInterestedInDragSource(const SourceDetails& detail return dynamic_cast(parent.node.get()) != nullptr; } +void NodeComponent::Header::setShowRenameLabel(bool shouldShow) +{ + auto isShowing = renameLabel != nullptr; + + if(isShowing != shouldShow) + { + if(shouldShow) + { + addAndMakeVisible(renameLabel = new TextEditor()); + renameLabel->setBounds(getLocalBounds()); + renameLabel->setJustification(Justification::centred); + renameLabel->setFont(GLOBAL_BOLD_FONT()); + renameLabel->grabKeyboardFocusAsync(); + renameLabel->setText(parent.node->getName(), dontSendNotification); + + auto f = [this]() + { + MessageManager::callAsync([this]() + { + auto newName = renameLabel->getText(); + parent.node->getValueTree().setProperty(PropertyIds::Name, newName, parent.node->getUndoManager()); + this->setShowRenameLabel(false); + findParentComponentOfClass()->resizeNodes(); + }); + };; + + renameLabel->onReturnKey = f; + renameLabel->onFocusLost = f; + renameLabel->onEscapeKey = f; + + GlobalHiseLookAndFeel::setTextEditorColours(*renameLabel); + } + else + { + renameLabel = nullptr; + } + + repaint(); + } + + +} + void NodeComponent::Header::paint(Graphics& g) { auto textArea = getLocalBounds().toFloat(); @@ -338,6 +389,9 @@ void NodeComponent::Header::paint(Graphics& g) g.drawRect(powerButton.getBounds().expanded(3).toFloat(), 1.0f); } + if(renameLabel != nullptr) + return; + textArea.removeFromLeft(jmax(leftMargin, rightMargin)); textArea.removeFromRight(jmax(leftMargin, rightMargin)); @@ -385,6 +439,61 @@ NodeComponent::~NodeComponent() node = nullptr; } +juce::Rectangle NodeComponent::PositionHelpers::getPositionInCanvasForStandardSliders(const NodeBase* n, + Point topLeft) +{ + auto numParameters = n->getNumParameters(); + + if (numParameters == 7) + return createRectangleForParameterSliders(n, 4).withPosition(topLeft); + else if (numParameters == 0) + return createRectangleForParameterSliders(n, 0).withPosition(topLeft); + else if (numParameters % 5 == 0) + return createRectangleForParameterSliders(n, 5).withPosition(topLeft); + else if (numParameters % 4 == 0) + return createRectangleForParameterSliders(n, 4).withPosition(topLeft); + else if (numParameters % 3 == 0) + return createRectangleForParameterSliders(n, 3).withPosition(topLeft); + else if (numParameters % 2 == 0) + return createRectangleForParameterSliders(n, 2).withPosition(topLeft); + else if (numParameters == 1) + return createRectangleForParameterSliders(n, 1).withPosition(topLeft); + else + return createRectangleForParameterSliders(n, 5).withPosition(topLeft); + +} + + +juce::Rectangle NodeComponent::PositionHelpers::createRectangleForParameterSliders(const NodeBase* n, + int numColumns) +{ + int h = UIValues::HeaderHeight; + + if (n->getEmbeddedNetwork() != nullptr) + h += 24; + + auto eb = n->getExtraComponentBounds(); + + h += eb.getHeight(); + int w = 0; + + if (numColumns == 0) + w = eb.getWidth() > 0 ? eb.getWidth() : UIValues::NodeWidth * 2; + else + { + int numParameters = n->getNumParameters(); + int numRows = (int)std::ceil((float)numParameters / (float)numColumns); + + h += numRows * (48 + 28) - 10; + w = jmin(numColumns * 100, numParameters * 100); + } + + + w = jmax(w, eb.getWidth()); + + auto b = Rectangle(0, 0, w, h); + return b.expanded(UIValues::NodeMargin); +} void NodeComponent::paint(Graphics& g) { @@ -548,6 +657,7 @@ void NodeComponent::selectionChanged(const NodeBase::List& selection) void NodeComponent::fillContextMenu(PopupMenu& m) { m.addItem((int)MenuActions::ExportAsSnippet, "Export as snippet"); + m.addItem((int)MenuActions::ExportAsTemplate, "Export as template"); m.addItem((int)MenuActions::EditProperties, "Edit Properties"); #if 0 @@ -621,6 +731,11 @@ void NodeComponent::handlePopupMenuResult(int result) SystemClipboard::copyTextToClipboard(data); PresetHandler::showMessageWindow("Copied to clipboard", "The node was copied to the clipboard"); } + if(result == (int)MenuActions::ExportAsTemplate) + { + auto r = findParentComponentOfClass(); + r->setModalComponent(new multipage::library::ScriptnodeTemplateExporter(r, node.get())); + } if (result == (int)MenuActions::UnfreezeNode) { DspNetworkGraph::Actions::unfreezeNode(node.get()); @@ -806,7 +921,7 @@ void NodeComponent::handlePopupMenuResult(int result) juce::Colour NodeComponent::getOutlineColour() const { - if (isRoot()) + if (node->getRootNetwork()->getRootNode() == node) return dynamic_cast(node->getScriptProcessor())->getColour(); auto& exceptionHandler = node->getRootNetwork()->getExceptionHandler(); @@ -842,6 +957,9 @@ void NodeComponent::drawTopBodyGradient(Graphics& g, Rectangle b, float a bool NodeComponent::isRoot() const { + if(auto ng = findParentComponentOfClass()) + return ng->getCurrentRootNode() == node.get(); + return node->getRootNetwork()->getRootNode() == node.get(); } @@ -940,6 +1058,7 @@ juce::Path NodeComponentFactory::createPath(const String& id) const LOAD_EPATH_IF_URL("close", HiBinaryData::ProcessorEditorHeaderIcons::closeIcon); LOAD_EPATH_IF_URL("delete", SampleMapIcons::deleteSamples); LOAD_PATH_IF_URL("move", ColumnIcons::moveIcon); + LOAD_EPATH_IF_URL("soft_bypass", HiBinaryData::ProcessorEditorHeaderIcons::bypassShape); LOAD_PATH_IF_URL("goto", ColumnIcons::targetIcon); LOAD_EPATH_IF_URL("parameter", HiBinaryData::SpecialSymbols::macros); LOAD_EPATH_IF_URL("split", ScriptnodeIcons::splitIcon); @@ -958,6 +1077,8 @@ juce::Path NodeComponentFactory::createPath(const String& id) const LOAD_EPATH_IF_URL("clone", SampleMapIcons::copySamples); LOAD_PATH_IF_URL("local", ColumnIcons::localIcon); LOAD_PATH_IF_URL("drag", ColumnIcons::targetIcon); + LOAD_PATH_IF_URL("next", ColumnIcons::nextIcon); + LOAD_PATH_IF_URL("workspace", ColumnIcons::openWorkspaceIcon); if (url.startsWith("fix")) p.loadPathFromData(ScriptnodeIcons::fixIcon, ScriptnodeIcons::fixIcon_Size); diff --git a/hi_scripting/scripting/scriptnode/ui/NodeComponent.h b/hi_scripting/scripting/scriptnode/ui/NodeComponent.h index ed0949dad8..82142ba40e 100644 --- a/hi_scripting/scripting/scriptnode/ui/NodeComponent.h +++ b/hi_scripting/scripting/scriptnode/ui/NodeComponent.h @@ -54,6 +54,7 @@ class NodeComponent : public ComponentWithMiddleMouseDrag, ExportAsCpp = 1, ExportAsCppProject, ExportAsSnippet, + ExportAsTemplate, CreateScreenShot, EditProperties, UnfreezeNode, @@ -94,6 +95,11 @@ class NodeComponent : public ComponentWithMiddleMouseDrag, void paint(Graphics& g) override; void resized() override; + bool keyPressed(const KeyPress& key) override + { + return parent.keyPressed(key); + } + void mouseDoubleClick(const MouseEvent& event) override; void mouseDown(const MouseEvent& e) override; void mouseUp(const MouseEvent& e) override; @@ -145,6 +151,10 @@ class NodeComponent : public ComponentWithMiddleMouseDrag, repaint(); } + void setShowRenameLabel(bool shouldShow); + + ScopedPointer renameLabel; + NodeComponent& parent; Factory f; @@ -205,10 +215,26 @@ class NodeComponent : public ComponentWithMiddleMouseDrag, NodeComponent(NodeBase* b);; virtual ~NodeComponent(); + struct PositionHelpers + { + static juce::Rectangle getPositionInCanvasForStandardSliders(const NodeBase* n, Point topLeft); + + static juce::Rectangle createRectangleForParameterSliders(const NodeBase* n, int numColumns); + }; + void paint(Graphics& g) override; void paintOverChildren(Graphics& g) override; void resized() override; + bool keyPressed(const KeyPress& key) override + { + if(key == KeyPress::F2Key) + { + header.setShowRenameLabel(true); + return true; + } + } + MarkdownLink getLink() const override; void selectionChanged(const NodeBase::List& selection) override; diff --git a/hi_scripting/scripting/scriptnode/ui/NodeContainerComponent.cpp b/hi_scripting/scripting/scriptnode/ui/NodeContainerComponent.cpp index edd2a69701..754b5b5e4c 100644 --- a/hi_scripting/scripting/scriptnode/ui/NodeContainerComponent.cpp +++ b/hi_scripting/scripting/scriptnode/ui/NodeContainerComponent.cpp @@ -114,8 +114,24 @@ ContainerComponent::ContainerComponent(NodeContainer* b) : NodeComponent(b->asNode()), SimpleTimer(b->asNode()->getScriptProcessor()->getMainController_()->getGlobalUIUpdater()), updater(*this), + gotoButton("workspace", nullptr, nf), parameters(new ParameterComponent(*this)) { + addAndMakeVisible(gotoButton); + + gotoButton.setTooltip("Show this container as root"); + + gotoButton.onClick = [this]() + { + auto ng = findParentComponentOfClass(); + auto n = node.get(); + + MessageManager::callAsync([ng, n]() + { + ng->setCurrentRootNode(n); + }); + }; + if (auto sn = dynamic_cast(b)) { verticalValue.referTo(sn->getNodePropertyAsValue(PropertyIds::IsVertical)); @@ -213,6 +229,21 @@ void ContainerComponent::mouseUp(const MouseEvent& e) } } +bool ContainerComponent::keyPressed(const KeyPress& k) +{ + if(NodeComponent::keyPressed(k)) + return true; + + if(k == KeyPress::F3Key) + { + gotoButton.triggerClick(sendNotificationAsync); + + return true; + } + + return false; +} + void ContainerComponent::removeDraggedNode(NodeComponent* draggedNode) { int removeIndex = childNodeComponents.indexOf(draggedNode); @@ -324,6 +355,34 @@ void ContainerComponent::valueChanged(Value& v) } } +void ContainerComponent::resized() +{ + NodeComponent::resized(); + + Component* topComponent = parameters != nullptr ? parameters.get() : extraComponent.get(); + + jassert(topComponent != nullptr); + + topComponent->setVisible(dataReference[PropertyIds::ShowParameters]); + + auto b = getLocalBounds(); + b.expand(-UIValues::NodeMargin, 0); + b.removeFromTop(UIValues::HeaderHeight); + topComponent->setSize(b.getWidth(), topComponent->getHeight()); + topComponent->setTopLeftPosition(b.getTopLeft()); + + gotoButton.setSize(16,16); + + if(auto ng = findParentComponentOfClass()) + { + gotoButton.setVisible(ng->root != this); + } + + auto pos = topComponent->isVisible() ? topComponent->getBounds().getBottomLeft() : topComponent->getPosition(); + + gotoButton.setTopLeftPosition(pos.translated(0, UIValues::NodeMargin)); +} + void ContainerComponent::findLassoItemsInArea(Array& itemsFound, const Rectangle& area) { Array nodeComponents; diff --git a/hi_scripting/scripting/scriptnode/ui/NodeContainerComponent.h b/hi_scripting/scripting/scriptnode/ui/NodeContainerComponent.h index 5e908cc252..97beef694a 100644 --- a/hi_scripting/scripting/scriptnode/ui/NodeContainerComponent.h +++ b/hi_scripting/scripting/scriptnode/ui/NodeContainerComponent.h @@ -671,6 +671,8 @@ class ContainerComponent : public NodeComponent, OwnedArray sliders; }; + NodeComponentFactory nf; + ContainerComponent(NodeContainer* b);; ~ContainerComponent(); @@ -681,6 +683,8 @@ class ContainerComponent : public NodeComponent, void mouseDrag(const MouseEvent& event) override; void mouseUp(const MouseEvent& e) override; + bool keyPressed(const KeyPress& k) override; + void timerCallback() override { repaint(); } float getCircleAmp(int nodeIndex, int channelIndex, bool post) @@ -750,24 +754,9 @@ class ContainerComponent : public NodeComponent, virtual Rectangle getInsertRuler(int ) const { jassertfalse; return {}; } - void resized() override - { - NodeComponent::resized(); - - Component* topComponent = parameters != nullptr ? parameters.get() : extraComponent.get(); - - jassert(topComponent != nullptr); - - topComponent->setVisible(dataReference[PropertyIds::ShowParameters]); - - auto b = getLocalBounds(); - b.expand(-UIValues::NodeMargin, 0); - b.removeFromTop(UIValues::HeaderHeight); - topComponent->setSize(b.getWidth(), topComponent->getHeight()); - topComponent->setTopLeftPosition(b.getTopLeft()); - } + void resized() override; - int getCurrentAddPosition() const { return addPosition; } + int getCurrentAddPosition() const { return addPosition; } void setExtraComponent(Component* newExtraComponent) { @@ -799,7 +788,9 @@ class ContainerComponent : public NodeComponent, ScopedPointer duplicateDisplay; float signalDotOffset = 0.0f; - + + HiseShapeButton gotoButton; + private: struct Updater : public SafeChangeBroadcaster, diff --git a/hi_scripting/scripting/scriptnode/ui/ParameterSlider.cpp b/hi_scripting/scripting/scriptnode/ui/ParameterSlider.cpp index f679e27698..d89d5d6fc0 100644 --- a/hi_scripting/scripting/scriptnode/ui/ParameterSlider.cpp +++ b/hi_scripting/scripting/scriptnode/ui/ParameterSlider.cpp @@ -1058,8 +1058,8 @@ bool ParameterSlider::isInterestedInDragSource(const SourceDetails& details) if(pTree[PropertyIds::Automated]) return false; - auto sourceNode = details.sourceComponent->findParentComponentOfClass()->node.get(); - + WeakReference sourceNode = DspNetworkListeners::getSourceNodeFromComponentDrag(details.sourceComponent); + if(dynamic_cast(node.get()) != nullptr) { if(valuetree::Helpers::isParent(sourceNode->getValueTree(), node->getValueTree())) @@ -1088,7 +1088,7 @@ bool ParameterSlider::isInterestedInDragSource(const SourceDetails& details) return false; } - if(auto modSource = dynamic_cast(sourceNode)) + if(auto modSource = dynamic_cast(sourceNode.get())) { auto h = modSource->getParameterHolder(); @@ -1265,7 +1265,7 @@ juce::ValueTree ParameterSlider::getConnectionSourceTree() return parameterToControl->getConnectionSourceTree(true); } -bool ParameterSlider::matchesConnection(ValueTree& c) const +bool ParameterSlider::matchesConnection(const ValueTree& c) const { if (parameterToControl == nullptr) return false; diff --git a/hi_scripting/scripting/scriptnode/ui/ParameterSlider.h b/hi_scripting/scripting/scriptnode/ui/ParameterSlider.h index a6ea22bab9..57389da0ef 100644 --- a/hi_scripting/scripting/scriptnode/ui/ParameterSlider.h +++ b/hi_scripting/scripting/scriptnode/ui/ParameterSlider.h @@ -220,7 +220,7 @@ struct ParameterSlider : public Slider, /** Returns either the Connection or the ModulationTarget, or SwitchTarget tree if it's connected. */ ValueTree getConnectionSourceTree(); - bool matchesConnection(ValueTree& c) const; + bool matchesConnection(const ValueTree& c) const; void mouseDown(const MouseEvent& e) override; diff --git a/hi_scripting/scripting/scriptnode/ui/PropertyEditor.cpp b/hi_scripting/scripting/scriptnode/ui/PropertyEditor.cpp index 1c7994959c..ea5b4ba7e4 100644 --- a/hi_scripting/scripting/scriptnode/ui/PropertyEditor.cpp +++ b/hi_scripting/scripting/scriptnode/ui/PropertyEditor.cpp @@ -170,6 +170,7 @@ void NodePopupEditor::buttonClicked(Button* b) m.addItem((int)NodeComponent::MenuActions::ExportAsCpp, "Export as custom CPP class"); m.addItem((int)NodeComponent::MenuActions::ExportAsCppProject, "Export as project CPP class"); m.addItem((int)NodeComponent::MenuActions::ExportAsSnippet, "Export as Base64 snippet"); + m.addItem((int)NodeComponent::MenuActions::ExportAsTemplate, "Export as template"); m.addItem((int)NodeComponent::MenuActions::CreateScreenShot, "Create screenshot"); } else if (mode == 1) diff --git a/hi_tools/hi_standalone_components/ZoomableViewport.cpp b/hi_tools/hi_standalone_components/ZoomableViewport.cpp index f53a0e2bcc..f7e30da8fc 100644 --- a/hi_tools/hi_standalone_components/ZoomableViewport.cpp +++ b/hi_tools/hi_standalone_components/ZoomableViewport.cpp @@ -136,6 +136,12 @@ bool ZoomableViewport::checkViewportScroll(const MouseEvent& e, const MouseWheel bool ZoomableViewport::checkMiddleMouseDrag(const MouseEvent& e, MouseEventFlags type) { + if(e.mods.isX1ButtonDown()) + return true; + + if(e.mods.isX2ButtonDown()) + return true; + if (e.mods.isMiddleButtonDown()) { if (auto vp = e.eventComponent->findParentComponentOfClass()) diff --git a/tools/json_dialog/template_export/template_export.json b/tools/json_dialog/template_export/template_export.json new file mode 100644 index 0000000000..fe9b800683 --- /dev/null +++ b/tools/json_dialog/template_export/template_export.json @@ -0,0 +1,73 @@ +{ + "StyleData": { + "Font": "Lato Regular", + "BoldFont": "", + "FontSize": 18.0, + "bgColour": 4281545523, + "codeBgColour": 864585864, + "linkBgColour": 8947967, + "textColour": 4294967295, + "codeColour": 4294967295, + "linkColour": 4289374975, + "tableHeaderBgColour": 864059520, + "tableLineColour": 864059520, + "tableBgColour": 864059520, + "headlineColour": 4287692721, + "UseSpecialBoldFont": false + }, + "Properties": { + "Header": "Export node as template", + "Subtitle": "", + "Image": "", + "ProjectName": "MyProject", + "Company": "MyCompany", + "Version": "1.0.0", + "BinaryName": "My Binary", + "UseGlobalAppData": false, + "Icon": "" + }, + "LayoutData": { + "StyleSheet": "ModalPopup", + "Style": "#total-progress\n{\n\tdisplay: none;\n}\n\n#next\n{\n\tcontent: 'Save';\n}", + "UseViewport": true, + "ConfirmClose": true, + "CloseMessage": "Do you want to close this popup?", + "DialogWidth": 500, + "DialogHeight": 400 + }, + "GlobalState": { + "Location": 0 + }, + "Children": [ + { + "Type": "List", + "Children": [ + { + "Type": "SimpleText", + "Text": "Location\n" + }, + { + "Type": "Button", + "Text": "Global", + "ID": "Location", + "Help": "This will save the template to the global HISE folder so it can be used by multiple projects.\n\n> Note that the global node templates are only available on this system and you have to transfer them manually to another system." + }, + { + "Type": "Button", + "Text": "Project", + "ID": "Location", + "Help": "This will save the template in the project folder (under `DspNetworks/CodeLibrary/templates`, so you can reuse it in this project." + }, + { + "Type": "JavascriptFunction", + "ID": "Create", + "EventTrigger": "OnSubmit", + "Code": "{BIND::onCreate}" + } + ], + "Text": "", + "Style": "gap: 10px;" + } + ], + "Assets": [] +} \ No newline at end of file