diff --git a/plugins/community/repos/SubmarineFree/src/SubmarineFree.cpp b/plugins/community/repos/SubmarineFree/src/SubmarineFree.cpp index 70858953..b59a72f7 100644 --- a/plugins/community/repos/SubmarineFree/src/SubmarineFree.cpp +++ b/plugins/community/repos/SubmarineFree/src/SubmarineFree.cpp @@ -76,7 +76,8 @@ RACK_PLUGIN_MODEL_DECLARE(SubmarineFree, BP132); RACK_PLUGIN_INIT(SubmarineFree) { RACK_PLUGIN_INIT_ID(); RACK_PLUGIN_INIT_VERSION("0.6.8"); - // https://github.com/david-c14/SubmarineFree + RACK_PLUGIN_INIT_WEBSITE("https://github.com/david-c14/SubmarineFree"); + RACK_PLUGIN_INIT_MANUAL("https://github.com/david-c14/SubmarineFree"); // Add all Models defined throughout the plugin RACK_PLUGIN_MODEL_ADD(SubmarineFree, AG104); diff --git a/plugins/community/repos/SubmarineUtility/.gitignore b/plugins/community/repos/SubmarineUtility/.gitignore new file mode 100644 index 00000000..9367e281 --- /dev/null +++ b/plugins/community/repos/SubmarineUtility/.gitignore @@ -0,0 +1,7 @@ +/build +/dist +/plugin.dylib +/plugin.dll +/plugin.so +.DS_Store +/WK_Custom.tunings diff --git a/plugins/community/repos/SubmarineUtility/LICENSE b/plugins/community/repos/SubmarineUtility/LICENSE new file mode 100644 index 00000000..e1d30ccb --- /dev/null +++ b/plugins/community/repos/SubmarineUtility/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2018, David O'Rourke +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* 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. + +* 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. + +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. diff --git a/plugins/community/repos/SubmarineUtility/Makefile b/plugins/community/repos/SubmarineUtility/Makefile new file mode 100644 index 00000000..b9cd2c27 --- /dev/null +++ b/plugins/community/repos/SubmarineUtility/Makefile @@ -0,0 +1,28 @@ +# Must follow the format in the Naming section of https://vcvrack.com/manual/PluginDevelopmentTutorial.html +SLUG = SubmarineUtility + +# Must follow the format in the Versioning section of https://vcvrack.com/manual/PluginDevelopmentTutorial.html +VERSION = 0.6.2 + +# FLAGS will be passed to both the C and C++ compiler +FLAGS += +CFLAGS += +CXXFLAGS += + +# Careful about linking to shared libraries, since you can't assume much about the user's environment and library search path. +# Static libraries are fine. +LDFLAGS += + +# Add .cpp and .c files to the build +SOURCES += $(wildcard src/*.cpp) + +# Add files to the ZIP package when running `make dist` +# The compiled plugin is automatically added. +DISTRIBUTABLES += $(wildcard LICENSE*) res + +# If RACK_DIR is not defined when calling the Makefile, default to two levels above +RACK_DIR ?= ../.. + +# Include the VCV Rack plugin Makefile framework +include $(RACK_DIR)/plugin.mk + diff --git a/plugins/community/repos/SubmarineUtility/README.md b/plugins/community/repos/SubmarineUtility/README.md new file mode 100644 index 00000000..0c13b594 --- /dev/null +++ b/plugins/community/repos/SubmarineUtility/README.md @@ -0,0 +1,19 @@ +# SubmarineUtility +Utility Modules for VCVRack + +Important note: + +These modules rely on knowledge of VCVRack which goes beyond the published API. The ongoing stability of the code is therefore not guaranteed. At least some of the features of these modules rely on code which is expected to change in future versions of VCVRack, and many other parts are likely to change. + +Whilst I intend to try to keep these modules up to date with changes, I can make no guarantees that this will even be possible. + +**Module Browser will definitely need to be somewhat rewritten for VCVRack v1.0. It is my intention to do so as soon as possible.** + +However, these modules are not designed or intended to form any part of your patch behaviour. They are intended purely as workflow tools. If these modules become incompatible with VCVRack in the future, it should be possible to remove them from the patch without affecting the aural content of the patch. + +## [Manual](https://github.com/david-c14/SubmarineUtility/blob/master/manual/index.md) + +## Licence + +This code is licensed under BSD 3-clause and is mostly copyright © 2018 carbon14 (David O'Rourke) 2018 +Some parts of this code are inevitably based directly on code by Andrew Belt within VCVRack itself; Copyright © 2016 Andrew Belt. diff --git a/plugins/community/repos/SubmarineUtility/make.objects b/plugins/community/repos/SubmarineUtility/make.objects new file mode 100644 index 00000000..1aba5624 --- /dev/null +++ b/plugins/community/repos/SubmarineUtility/make.objects @@ -0,0 +1,5 @@ +ALL_OBJ= \ + src/ModBrowser.o \ + src/SubControls.o \ + src/SubmarineUtility.o \ + src/WireManager.o diff --git a/plugins/community/repos/SubmarineUtility/makefile.linux b/plugins/community/repos/SubmarineUtility/makefile.linux new file mode 100644 index 00000000..be4d06a8 --- /dev/null +++ b/plugins/community/repos/SubmarineUtility/makefile.linux @@ -0,0 +1,7 @@ +SLUG=SubmarineUtility + +include ../../../build_plugin_pre_linux.mk + +include make.objects + +include ../../../build_plugin_post_linux.mk diff --git a/plugins/community/repos/SubmarineUtility/makefile.msvc b/plugins/community/repos/SubmarineUtility/makefile.msvc new file mode 100644 index 00000000..1bc99449 --- /dev/null +++ b/plugins/community/repos/SubmarineUtility/makefile.msvc @@ -0,0 +1,7 @@ +SLUG=SubmarineUtility + +include ../../../build_plugin_pre_msvc.mk + +include make.objects + +include ../../../build_plugin_post_msvc.mk diff --git a/plugins/community/repos/SubmarineUtility/manual/index.md b/plugins/community/repos/SubmarineUtility/manual/index.md new file mode 100644 index 00000000..14daced6 --- /dev/null +++ b/plugins/community/repos/SubmarineUtility/manual/index.md @@ -0,0 +1,14 @@ +# Submarine Utilities + +Important note: + +These modules rely on knowledge of VCVRack which goes beyond the published API. The ongoing stability of the code is therefore not guaranteed. At least some of the features of these modules rely on code which is expected to change in future versions of VCVRack, and many other parts are likely to change. + +Whilst I intend to try to keep these modules up to date with changes, I can make no guarantees that this will even be possible. + +**Module Browser will definitely need to be somewhat rewritten for VCVRack v1.0. It is my intention to do so as soon as possible.** + +However, these modules are not designed or intended to form any part of your patch behaviour. They are intended purely as workflow tools. If these modules become incompatible with VCVRack in the future, it should be possible to remove them from the patch without affecting the aural content of the patch. + +## [Module Browser](modbrowser.md) +## [Wire Manager](wiremanager.md) diff --git a/plugins/community/repos/SubmarineUtility/manual/modbrowser.md b/plugins/community/repos/SubmarineUtility/manual/modbrowser.md new file mode 100644 index 00000000..7e0a7076 --- /dev/null +++ b/plugins/community/repos/SubmarineUtility/manual/modbrowser.md @@ -0,0 +1,71 @@ +# Module Browser + +The module browser offers an alternative to the built in module browser within VCVRack. It has some weaknesses compared to the built-in browser +but at the same time offers some distinct advantages. Both browsers can be used at the same time. + +## Creating Modules + +The first three icons allow you to browse for modules by Author, Tag or from the list of Favorites. Use the icon to select the list you require. + +### ![](../res/plugin.svg) Authors. + +Select an author to be shown a list of the modules available from that author. To go back either select the Authors icon again, or select +the 'Back' option at the top of the list. + +Clicking on a module will cause that module to be added to your patch. The module will be added into empty space as close as possible to the +module browser. + +Alternatively you can drag a module from the module browser to anywhere on your patch. The module will be added into empty space as close as possible +to where you tried to drop it. + +### ![](../res/tag.svg) Tags + +Select a tag to be shown a list of the modules which offer that functionality. To go back either select the Tags icon again, or select +the 'Back' option at the top of the list. + +Clicking on a module will cause that module to be added to your patch. The module will be added into empty space as close as possible to the +module browser. + +Alternatively you can drag a module from the module browser to anywhere on your patch. The module will be added into empty space as close as possible +to where you tried to drop it. + +### ![](../res/favorite.svg) Favorites + +The favorites list is loaded from your settings file. As a result it is normally only updated when you quit VCVRack. Favorites changed during a patching session will not show up in the module browser until the next time that you start VCVRack. + +Clicking on a module will cause that module to be added to your patch. The module will be added into empty space as close as possible to the +module browser. + +Alternatively you can drag a module from the module browser to anywhere on your patch. The module will be added into empty space as close as possible +to where you tried to drop it. + +## ![](../res/load.svg) Loading Patches + +The Disk icon will open up a dialog box allowing you to select either a patch or a preset file. + +### Patch Files + +Loading a patch file will cause all the modules in the patch to be imported into your current patch. Everything already in your patch will remain. + +The loaded modules will be grouped together in their original arrangement, but placed into your patch somewhere where there is sufficient space for the whole arrangement. All the settings and wiring from your patch will be set-up also. This is an ideal tool for +building a library of signal chains that you pick and choose from to construct more complex patches. + +### Preset Files + +Loading a preset file will add a new module from into your patch. It will also be configured with the settings of the preset. This can be quicker than manually adding the module and then opening the preset from within the module to configure it. + +## Sizing + +### ![](../res/min.svg) Minimize + +The Minimize icon will shrink the size of the module browser down to 1-HP. All the modules in the patch to the right of the module browser will be move to the left to take up the space. + +When the module browser is minimized, a Restore button is visible in the centre of the module. Selecting this will restore the module to its previous size. All modules to the right of the module browser will be moved to the right to make space. + +In this way you can keep the module browser in your patch for ready access, without taking up more space than is necessary. + +When the module browser is in its open state, a drag handle at the right-hand edge can be used to make the module wider or narrower. + +### Zooming + +The scrolling list in the module browser is designed to remain readable. If you zoom in, the items in the list will increase in size as you might expect, but if you zoom out to less than 100% the items in the list will remain the same size on the screen to ensure that they remain visible. diff --git a/plugins/community/repos/SubmarineUtility/manual/wiremanager.md b/plugins/community/repos/SubmarineUtility/manual/wiremanager.md new file mode 100644 index 00000000..7b409353 --- /dev/null +++ b/plugins/community/repos/SubmarineUtility/manual/wiremanager.md @@ -0,0 +1,83 @@ +# Wire Manager + +The wire manager offers the ability to choose more colors for the patch cables that you use in your patches. It also has highlighting options +which some users may find useful in tracing wires around the patch. + +## ![](../res/colors.svg) Colors + +The colors page of the wire manager offers a list of different colored wires. To the left of each wire is a checkbox. When you make a new connection +in VCVRack, a color for that wire is chosen from the list of wires that are checked. The list is cycled, so if you select only Red, Yellow and Green +wires in the wire manager, the first wire you add to your patch will be Red, the next Yellow, the third Green and then back to Red. + +If you wish to connect all of the right channel of a stereo signal in Red, you might choose to select only the Red wire in the wire manager, and +then to wire up the entire right channel signal chain. Then change to a Dark Grey wire to wire the left hand signal chain. + +Or perhaps you just want all of the wires to be pink to celebrate Valentine's Day. + +The top-most checkbox in the list allows you to select / deselect all the colors. + +At the top of the list on the right hand side is a plus icon. This will take you to the editing page to create a new wire color which will +be placed an the bottom of the list when you save it. + +To the right of each color are some more icons. These allow you to move your colored wires up or down the list to choose the order of them, and +the three dots icon takes you to an editing page where you can change the color of the wire. This editing page also has the option to +delete the wire. + +### Editing + +On the editing page there are save and cancel buttons, and a delete button (which will offer you a further chance to change your mind). + +There are three sliders here to adjust the color of the wire. Although they are not deliberately not labelled, the sliders control the Red, Green and Blue +parts of the color. Each slider has a gradient background accurately showing the colors that will result from moving that slider. + +Right-clicking on any slider will return it to it's unedited position. + +## ![](../res/hls.svg) Settings. + +The settings page currently has two functions. + +### Variation + +When the variation option is enabled, the color chosen for your wire is randomly varied. You might wish to use all green wires for a patch, +but if this option is enabled, you can have lots of slightly different green wires, emulating a collection of patch cables acquired over a +long and happy period of modular experimentation. + +The three sliders allow you to fine tune the type of variation introduced. + +H: is the hue or actual color of the wire + +L: is the lightness of the wire, from a deep dark red, to a light pale pink for example + +S: is the saturation of the wire, how deep the color is. Wires with a low saturation will appear more gray. + +### Highlighting + +When the highlighting option is selected, an additional transparency is added to the wires in the patch. When you hover over a +module in the patch, only those wires connected to that module will be shown with normal opacity. This may help you to trace pathways +in complex patches. You can quickly point to a module and see where each wire goes. + +There are two highlighting options, they differ in what happens when you are not pointing at a module, when you are pointing at empty space in the +rack. *Always On* will still fade all the wires when you are not hovering over any module; *When Hovering* will only fade wires away when you +are pointing at something. + +## Sizing + +### ![](../res/min.svg) Minimize + +The Minimize icon will shrink the size of the module browser down to 1-HP. All the modules in the patch to the right of the module browser will be move to the left to take up the space. + +When the module browser is minimized, a Restore button is visible in the centre of the module. Selecting this will restore the module to its previous size. All modules to the right of the module browser will be moved to the right to make space. + +In this way you can keep the module browser in your patch for ready access, without taking up more space than is necessary. + +## Saving. + +Once the wires have been connected in your patch, you do not need to keep the wire manager around. You can minimize it to save space, but if +you remove it from the patch completely, the wires will still retain their colors. Highlighting will not function without the wire +manager in the patch. + +The settings that you make are global. They are not saved along with the patch, so your favorite collection of colored wires is available in +every patch that you add wire manager to. + +Do not add more than one wire manager to your patch. It will do no harm, but only one of them will be actually selecting the colors and the +other will be wasting CPU. diff --git a/plugins/community/repos/SubmarineUtility/res/Sub1.svg b/plugins/community/repos/SubmarineUtility/res/Sub1.svg new file mode 100644 index 00000000..35fc8a9f --- /dev/null +++ b/plugins/community/repos/SubmarineUtility/res/Sub1.svg @@ -0,0 +1,17 @@ + + + + diff --git a/plugins/community/repos/SubmarineUtility/res/Sub2.svg b/plugins/community/repos/SubmarineUtility/res/Sub2.svg new file mode 100644 index 00000000..33cfdcfb --- /dev/null +++ b/plugins/community/repos/SubmarineUtility/res/Sub2.svg @@ -0,0 +1,15 @@ + + + + diff --git a/plugins/community/repos/SubmarineUtility/res/colors.svg b/plugins/community/repos/SubmarineUtility/res/colors.svg new file mode 100644 index 00000000..42ee2749 --- /dev/null +++ b/plugins/community/repos/SubmarineUtility/res/colors.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + diff --git a/plugins/community/repos/SubmarineUtility/res/favorite.svg b/plugins/community/repos/SubmarineUtility/res/favorite.svg new file mode 100644 index 00000000..c9309dd2 --- /dev/null +++ b/plugins/community/repos/SubmarineUtility/res/favorite.svg @@ -0,0 +1,44 @@ + + + + + + + + + diff --git a/plugins/community/repos/SubmarineUtility/res/hls.svg b/plugins/community/repos/SubmarineUtility/res/hls.svg new file mode 100644 index 00000000..7dfcd9d9 --- /dev/null +++ b/plugins/community/repos/SubmarineUtility/res/hls.svg @@ -0,0 +1,35 @@ + + + + + + + + + diff --git a/plugins/community/repos/SubmarineUtility/res/load.svg b/plugins/community/repos/SubmarineUtility/res/load.svg new file mode 100644 index 00000000..081a8916 --- /dev/null +++ b/plugins/community/repos/SubmarineUtility/res/load.svg @@ -0,0 +1,39 @@ + + + + + + + + + diff --git a/plugins/community/repos/SubmarineUtility/res/min.svg b/plugins/community/repos/SubmarineUtility/res/min.svg new file mode 100644 index 00000000..83613973 --- /dev/null +++ b/plugins/community/repos/SubmarineUtility/res/min.svg @@ -0,0 +1,33 @@ + + + + + + + + + diff --git a/plugins/community/repos/SubmarineUtility/res/plugin.svg b/plugins/community/repos/SubmarineUtility/res/plugin.svg new file mode 100644 index 00000000..50d91743 --- /dev/null +++ b/plugins/community/repos/SubmarineUtility/res/plugin.svg @@ -0,0 +1,35 @@ + + + + + + + + + diff --git a/plugins/community/repos/SubmarineUtility/res/tag.svg b/plugins/community/repos/SubmarineUtility/res/tag.svg new file mode 100644 index 00000000..8ae55193 --- /dev/null +++ b/plugins/community/repos/SubmarineUtility/res/tag.svg @@ -0,0 +1,34 @@ + + + + + + + + + + diff --git a/plugins/community/repos/SubmarineUtility/src/ModBrowser.cpp b/plugins/community/repos/SubmarineUtility/src/ModBrowser.cpp new file mode 100644 index 00000000..4e03ed6a --- /dev/null +++ b/plugins/community/repos/SubmarineUtility/src/ModBrowser.cpp @@ -0,0 +1,721 @@ +#include "SubControls.hpp" +#include +#include +#include "global_pre.hpp" +#include "global_ui.hpp" +#include "app.hpp" +#include "window.hpp" +#include "osdialog.h" +#include "util/common.hpp" + +namespace rack_plugin_SubmarineUtility { + +struct ModBrowserWidget; + +struct ListElement { + ModBrowserWidget *mbw; + virtual void onAction(EventAction &e) { debug ("Not Implemented"); } + virtual std::string GetLabelOne() { return std::string("Label 1"); }; + virtual std::string GetLabelTwo() { return std::string("Label 2"); }; +}; + +struct TextButton : SubControls::ButtonBase { + std::string label1; + std::string label2; + std::shared_ptr element; + float label1Width = 0; + float label2Width = 0; + void CalculateSizes(NVGcontext *vg, float zoom) { + nvgFontFaceId(vg, rack::global_ui->window.gGuiFont->handle); + nvgFontSize(vg, 13 * zoom); + float bounds[4]; + nvgTextBounds(vg, zoom, box.size.y / 2, label1.c_str(), NULL, bounds); + label1Width = bounds[2] - bounds[0]; + nvgTextBounds(vg, zoom, box.size.y / 2, label2.c_str(), NULL, bounds); + label2Width = bounds[2] - bounds[0]; + } + void draw (NVGcontext *vg) override { + float zoom = 1.0f / clamp(RACK_PLUGIN_UI_RACKSCENE->zoomWidget->zoom, 0.25f, 1.0f); + //if (label1Width == 0.0f) + CalculateSizes(vg, zoom); + if (RACK_PLUGIN_UI_DRAGGED_WIDGET == this) { + nvgBeginPath(vg); + nvgRect(vg, 0, 0, box.size.x - 2, box.size.y); + nvgFillColor(vg, nvgRGB(0x40, 0x40, 0x40)); + nvgFill(vg); + } + nvgFontFaceId(vg, rack::global_ui->window.gGuiFont->handle); + nvgFontSize(vg, 13 * zoom); + // Draw secondary text + nvgFillColor(vg, nvgRGB(0x80, 0x80, 0x80)); + nvgTextAlign(vg, NVG_ALIGN_MIDDLE | NVG_ALIGN_RIGHT); + nvgText(vg, box.size.x - zoom, box.size.y / 2, label2.c_str(), NULL); + // If text overlaps, feather out overlap + if (label1Width + label2Width > box.size.x) { + NVGpaint grad; + if (RACK_PLUGIN_UI_DRAGGED_WIDGET == this) { + nvgFillColor(vg, nvgRGB(0x40, 0x40, 0x40)); + grad = nvgLinearGradient(vg, label1Width, 0, label1Width + 10, 0, nvgRGBA(0x20, 0x20, 0x20, 0xff), nvgRGBA(0x20, 0x20, 0x20, 0)); + } + else { + nvgFillColor(vg, nvgRGB(0, 0, 0)); + grad = nvgLinearGradient(vg, label1Width, 0, label1Width + 10, 0, nvgRGBA(0, 0, 0, 0xff), nvgRGBA(0, 0, 0, 0)); + } + nvgBeginPath(vg); + nvgRect(vg, box.size.x - label2Width, 0, label1Width - box.size.x + label2Width, box.size.y); + nvgFill(vg); + nvgBeginPath(vg); + nvgRect(vg, label1Width, 0, 10, box.size.y); + nvgFillPaint(vg, grad); + nvgFill(vg); + } + // Draw primary text + nvgFillColor(vg, nvgRGB(0xff, 0xff, 0xff)); + nvgTextAlign(vg, NVG_ALIGN_MIDDLE); + nvgText(vg, zoom, box.size.y / 2, label1.c_str(), NULL); + Component::draw(vg); + } + void GetLabels() { + label1 = element->GetLabelOne(); + label2 = element->GetLabelTwo(); + } + void onAction(EventAction &e) override { + element->onAction(e); + } +}; + +// Icons + + +struct MBIconWidget : SubControls::ButtonBase,SVGWidget { + ModBrowserWidget *mbw; +}; + +struct PluginIcon : MBIconWidget { + int selected = 0; + PluginIcon() { + box.size.x = 30; + box.size.y = 30; + } + void onAction(EventAction &e) override; +}; + +struct TagIcon : MBIconWidget { + int selected = 0; + TagIcon() { + box.size.x = 30; + box.size.y = 30; + } + void onAction(EventAction &e) override; +}; + +struct FavIcon : MBIconWidget { + int selected = 0; + FavIcon() { + box.size.x = 30; + box.size.y = 30; + } + void onAction(EventAction &e) override; +}; + +struct LoadIcon : MBIconWidget { + int selected = 0; + LoadIcon() { + box.size.x = 30; + box.size.y = 30; + } + void onAction(EventAction &e) override; +}; + +struct MinimizeIcon : MBIconWidget { + MinimizeIcon() { + box.size.x = 30; + box.size.y = 30; + } + void onAction(EventAction &e) override; +}; + +// Elements + +struct ModelElement : ListElement { + Model *model; + std::string GetLabelOne() override { + return model->name; + } + std::string GetLabelTwo() override { +#undef plugin + return model->plugin->slug; +#define plugin "SubmarineUtility" + } + void onAction(EventAction &e) override; +}; + +struct PluginElement : ListElement { + std::string label; + std::string GetLabelOne() override { + return label; + } + std::string GetLabelTwo() override; + void onAction(EventAction &e) override; +}; + +struct TagElement : ListElement { + unsigned int tag; + std::string GetLabelOne() override { + return gTagNames[tag]; + } + std::string GetLabelTwo() override; + void onAction(EventAction &e) override; +}; + +struct PluginBackElement : ListElement { + std::string label2; + std::string GetLabelOne() override { + return std::string("\xe2\x86\x90 Back"); + } + std::string GetLabelTwo() override { + return label2; + } + void onAction(EventAction &e) override; +}; + +struct TagBackElement : ListElement { + std::string label2; + std::string GetLabelOne() override { + return std::string("\xe2\x86\x90 Back"); + } + std::string GetLabelTwo() override { + return label2; + } + void onAction(EventAction &e) override; +}; + +struct ModBrowserWidget : SubControls::SizeableModuleWidget { + Widget *scrollContainer; + ScrollWidget *scrollWidget; + PluginIcon *pluginIcon; + TagIcon *tagIcon; + FavIcon *favIcon; + LoadIcon *loadIcon; + MinimizeIcon *minimizeIcon; + float width; + float zoom = 1.0f; + std::list> pluginList; + std::list> tagList; + std::list> modelList; + std::string allfilters; + std::string lastPath; + ModBrowserWidget(Module *module) : SubControls::SizeableModuleWidget(module) { + moduleName = "Module Browser"; + zoom = clamp(RACK_PLUGIN_UI_RACKSCENE->zoomWidget->zoom, 0.25f, 1.0f); + allfilters.assign(PATCH_FILTERS); + allfilters.append(";"); + allfilters.append(PRESET_FILTERS); + + pluginIcon = Widget::create(Vec(2, 2)); + pluginIcon->selected = 1; + pluginIcon->mbw = this; + pluginIcon->setSVG(SVG::load(assetPlugin(plugin, "res/plugin.svg"))); + backPanel->addChild(pluginIcon); + + tagIcon = Widget::create(Vec(34, 2)); + tagIcon->mbw = this; + tagIcon->setSVG(SVG::load(assetPlugin(plugin, "res/tag.svg"))); + backPanel->addChild(tagIcon); + + favIcon = Widget::create(Vec(66, 2)); + favIcon->mbw = this; + favIcon->setSVG(SVG::load(assetPlugin(plugin, "res/favorite.svg"))); + backPanel->addChild(favIcon); + + loadIcon = Widget::create(Vec(98, 2)); + loadIcon->mbw = this; + loadIcon->setSVG(SVG::load(assetPlugin(plugin, "res/load.svg"))); + backPanel->addChild(loadIcon); + + minimizeIcon = Widget::create(Vec(130, 2)); + minimizeIcon->mbw = this; + minimizeIcon->setSVG(SVG::load(assetPlugin(plugin, "res/min.svg"))); + backPanel->addChild(minimizeIcon); + + scrollWidget = Widget::create(Vec(0, 35)); + scrollWidget->box.size.x = box.size.x - 20; + scrollWidget->box.size.y = box.size.y - 65; + width = scrollWidget->box.size.x - 20; + backPanel->addChild(scrollWidget); + + scrollContainer = scrollWidget->container; + for (unsigned int i = 1; i < NUM_TAGS; i++) { + std::shared_ptr te = std::make_shared(); + te->mbw = this; + te->tag = i; + tagList.push_back(te); + } + // Sort Tags (probably already sorted) + tagList.sort([](std::shared_ptr te1, std::shared_ptr te2) { return gTagNames[te1->tag].compare(gTagNames[te2->tag]) < 0; } ); + +#undef plugin + for (Plugin *plugin : rack::global->plugin.gPlugins) { + for (Model *model : plugin->models) { + std::shared_ptr me = std::make_shared(); + me->mbw = this; + me->model = model; + modelList.push_back(me); + int found = false; + for (std::shared_ptr pe : pluginList) { + if (!pe->label.compare(me->model->author)) { + found = true; + break; + } + } + if (!found) { + std::shared_ptr pe = std::make_shared(); + pe->mbw = this; + pe->label.assign(me->model->author); + pluginList.push_back(pe); + } + } + } + // Sort Plugins/Authors + pluginList.sort([](std::shared_ptr pe1, std::shared_ptr pe2) { return stringLowercase(pe1->label).compare(stringLowercase(pe2->label)) < 0; } ); + + AddPlugins(); + } + void ResetIcons() { + pluginIcon->selected = 0; + tagIcon->selected = 0; + favIcon->selected = 0; + } + + void onResize() override { + scrollWidget->box.size.x = box.size.x - 20; + SetListWidth(); + } + + void SetListWidth() { + float width = scrollContainer->parent->box.size.x; + float size = 15.0f / zoom; + if (scrollContainer->children.size() * size > scrollContainer->parent->box.size.y) + width -= 13; + float position = 0; + for (Widget *w : scrollContainer->children) { + w->box.pos.y = position; + w->box.size.x = width; + position += w->box.size.y = size; + } + } + void AddElement(std::shared_ptr le, float y) { + TextButton *tb = Widget::create(Vec(0, y)); + tb->element = le; + tb->GetLabels(); + tb->box.size.x = width; + tb->box.size.y = 15; + scrollContainer->addChild(tb); + } + void AddPlugins() { + scrollContainer->clearChildren(); + unsigned int y = 0; + for (std::shared_ptr pe : pluginList) { + AddElement(pe, y); + y += 15; + } + SetListWidth(); + } + void AddTags() { + scrollContainer->clearChildren(); + unsigned int y = 0; + for (std::shared_ptr te : tagList) { + AddElement(te, y); + y += 15; + } + SetListWidth(); + } + void AddFavorites() { + scrollContainer->clearChildren(); + unsigned int y = 0; + FILE *file = fopen(assetLocal("settings.json").c_str(), "r"); + if (!file) + return; + json_error_t error; + json_t *rootJ = json_loadf(file, 0, &error); + if (!rootJ) { + warn("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text); + fclose(file); + return; + } + json_t *modb = json_object_get(rootJ, "moduleBrowser"); + if (modb) { + json_t *favoritesJ = json_object_get(modb, "favorites"); + if (favoritesJ) { + size_t i; + json_t *favoriteJ; + json_array_foreach(favoritesJ, i, favoriteJ) { + json_t *pluginJ = json_object_get(favoriteJ, "plugin"); + json_t *modelJ = json_object_get(favoriteJ, "model"); + if (!pluginJ || !modelJ) + continue; + std::string pluginSlug = json_string_value(pluginJ); + std::string modelSlug = json_string_value(modelJ); + Model *model = pluginGetModel(pluginSlug, modelSlug); + if (!model) + continue; + for (std::shared_ptr me : modelList) { + if (me->model == model) { + AddElement(me, y); + y += 15; + } + } + } + } + } + json_decref(rootJ); + fclose(file); + SetListWidth(); + } + void AddModels(std::string author) { + scrollContainer->clearChildren(); + std::shared_ptr pbe = std::make_shared(); + pbe->mbw = this; + pbe->label2 = author; + AddElement(pbe, 0); + unsigned int y = 15; + for (std::shared_ptr me : modelList) { + if (!me->model->author.compare(author)) { + AddElement(me, y); + y += 15; + } + } + SetListWidth(); + } + void AddModels(unsigned int tag) { + scrollContainer->clearChildren(); + std::shared_ptr tbe = std::make_shared(); + tbe->mbw = this; + tbe->label2 = gTagNames[tag]; + AddElement(tbe, 0); + unsigned int y = 15; + for (std::shared_ptr me : modelList) { + for (ModelTag mt : me->model->tags) { + if (mt == tag) { + AddElement(me, y); + y += 15; + } + } + } + SetListWidth(); + } + void Load() { + if (lastPath.empty()) { + if (RACK_PLUGIN_UI_RACKWIDGET->lastPath.empty()) { + lastPath = assetLocal("patches"); + systemCreateDirectory(lastPath); + } + else { + lastPath = stringDirectory(RACK_PLUGIN_UI_RACKWIDGET->lastPath); + } + } + osdialog_filters *filters = osdialog_filters_parse(allfilters.c_str()); + char *path = osdialog_file(OSDIALOG_OPEN, lastPath.c_str(), NULL, filters); + + if (path) { + Load(path); + lastPath = stringDirectory(path); + free(path); + } + osdialog_filters_free(filters); + } + void Load(std::string filename) { + FILE *file = fopen(filename.c_str(), "r"); + if (!file) { + debug("Unable to open patch %s", filename.c_str()); + return; + } + + json_error_t error; + json_t *rootJ = json_loadf(file, 0, &error); + if (rootJ) { + Load(rootJ); + json_decref(rootJ); + } + else { + std::string message = stringf("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text); + osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str()); + } + fclose(file); + } + void LoadPreset(json_t *rootJ) { + ModuleWidget *moduleWidget = RACK_PLUGIN_UI_RACKWIDGET->moduleFromJson(rootJ); + if (moduleWidget) { + moduleWidget->box.pos = RACK_PLUGIN_UI_RACKWIDGET->lastMousePos.minus(moduleWidget->box.size.div(2)); + RACK_PLUGIN_UI_RACKWIDGET->requestModuleBoxNearest(moduleWidget, moduleWidget->box); + } + } + void Load(json_t *rootJ) { + std::string message; + Rect newBox; + newBox.pos.x = -1; + //load modules + std::map moduleWidgets; + json_t *modulesJ = json_object_get(rootJ, "modules"); + if (!modulesJ) { + LoadPreset(rootJ); + return; + } + std::vector existingWidgets; + for (Widget *child : RACK_PLUGIN_UI_RACKWIDGET->moduleContainer->children) { + existingWidgets.push_back(child); + } + size_t moduleId; + json_t *moduleJ; + json_array_foreach(modulesJ, moduleId, moduleJ) { + ModuleWidget *moduleWidget = RACK_PLUGIN_UI_RACKWIDGET->moduleFromJson(moduleJ); + if (moduleWidget) { + json_t *posJ = json_object_get(moduleJ, "pos"); + double x, y; + json_unpack(posJ, "[F, F]", &x, &y); + Vec pos = Vec(x,y); + moduleWidget->box.pos = pos.mult(RACK_GRID_SIZE); + moduleWidgets[moduleId] = moduleWidget; + if (newBox.pos.x == -1) { + newBox.pos.x = moduleWidget->box.pos.x; + newBox.pos.y = moduleWidget->box.pos.y; + newBox.size.x = moduleWidget->box.size.x; + newBox.size.y = moduleWidget->box.size.y; + } + else { + Rect mbox = moduleWidget->box; + if (mbox.pos.x < newBox.pos.x) { + newBox.size.x += newBox.pos.x - mbox.pos.x; + newBox.pos.x = mbox.pos.x; + } + if (mbox.pos.y < newBox.pos.y) { + newBox.size.y += newBox.pos.y - mbox.pos.y; + newBox.pos.y = mbox.pos.y; + } + if ((mbox.pos.x + mbox.size.x) > (newBox.pos.x + newBox.size.x)) { + newBox.size.x = mbox.pos.x + mbox.size.x - newBox.pos.x; + } + if ((mbox.pos.y + mbox.size.y) > (newBox.pos.y + newBox.size.y)) { + newBox.size.y = mbox.pos.y + mbox.size.y - newBox.pos.y; + } + } + } + } + + //find space for modules and arrange + Rect space = FindSpace(existingWidgets, newBox); + if (space.pos.x == -1) { + // oooh noes!!! couldn't find space for these widgets + warn("Module browser could not find space to load patch"); + for (const auto& kvp : moduleWidgets) { + RACK_PLUGIN_UI_RACKWIDGET->deleteModule(kvp.second); + kvp.second->finalizeEvents(); + delete kvp.second; + } + return; + } + // Move modules into space + float dx = space.pos.x - newBox.pos.x; + float dy = space.pos.y - newBox.pos.y; + for (const auto& kvp : moduleWidgets) { + kvp.second->box.pos.x += dx; + kvp.second->box.pos.y += dy; + } + //wires + json_t *wiresJ = json_object_get(rootJ, "wires"); + if (!wiresJ) return; + size_t wireId; + json_t *wireJ; + json_array_foreach(wiresJ, wireId, wireJ) { + int outputModuleId = json_integer_value(json_object_get(wireJ, "outputModuleId")); + int outputId = json_integer_value(json_object_get(wireJ, "outputId")); + int inputModuleId = json_integer_value(json_object_get(wireJ, "inputModuleId")); + int inputId = json_integer_value(json_object_get(wireJ, "inputId")); + ModuleWidget *outputModuleWidget = moduleWidgets[outputModuleId]; + if (!outputModuleWidget) continue; + ModuleWidget *inputModuleWidget = moduleWidgets[inputModuleId]; + if (!inputModuleWidget) continue; + Port *outputPort = NULL; + Port *inputPort = NULL; + for (Port *port : outputModuleWidget->outputs) { + if (port->portId == outputId) { + outputPort = port; + break; + } + } + for (Port *port : inputModuleWidget->inputs) { + if (port->portId == inputId) { + inputPort = port; + break; + } + } + if (!outputPort || !inputPort) + continue; + WireWidget *wireWidget = new WireWidget(); + wireWidget->fromJson(wireJ); + wireWidget->outputPort = outputPort; + wireWidget->inputPort = inputPort; + wireWidget->updateWire(); + RACK_PLUGIN_UI_RACKWIDGET->wireContainer->addChild(wireWidget); + } + + } + Rect FindSpace(std::vector existingWidgets, Rect box) { + int x0 = roundf(box.pos.x / RACK_GRID_WIDTH); + int y0 = roundf(box.pos.y / RACK_GRID_HEIGHT); + std::vector positions; + for (int y = max(0, y0 - 8); y < y0 + 8; y++) { + for (int x = max(0, x0 - 400); x < x0 + 400; x++) { + positions.push_back(Vec(x * RACK_GRID_WIDTH, y * RACK_GRID_HEIGHT)); + } + } + std::sort(positions.begin(), positions.end(), [box](Vec a, Vec b) { + return a.minus(box.pos).norm() < b.minus(box.pos).norm(); + }); + for (Vec position : positions) { + Rect newBox = box; + newBox.pos = position; + int collide = false; + for (Widget *child : existingWidgets) { + if (newBox.intersects(child->box)) { + collide = true; + break; + } + } + if (!collide) { + return newBox; + } + } + Rect failed; + failed.pos.x = -1; + return failed; + } + void step() override { + float thisZoom = clamp(RACK_PLUGIN_UI_RACKSCENE->zoomWidget->zoom, 0.25f, 1.0f); + if (thisZoom != zoom) { + zoom = thisZoom; + SetListWidth(); + } + stabilized = true; + ModuleWidget::step(); + } + +}; + +// Icon onAction + +void PluginIcon::onAction(EventAction &e) { + mbw->ResetIcons(); + mbw->pluginIcon->selected = 1; + mbw->AddPlugins(); +} + +void TagIcon::onAction(EventAction &e) { + mbw->ResetIcons(); + mbw->tagIcon->selected = 1; + mbw->AddTags(); +} + +void FavIcon::onAction(EventAction &e) { + mbw->pluginIcon->selected = 0; + mbw->favIcon->selected = 1; + mbw->AddFavorites(); +} + +void LoadIcon::onAction(EventAction &e) { + mbw->Load(); +} + +void MinimizeIcon::onAction(EventAction &e) { + mbw->Minimize(true); +} + +// Element onAction + +void PluginElement::onAction(EventAction &e) { + mbw->AddModels(label); +} + +std::string PluginElement::GetLabelTwo() { + unsigned int count = 0; + for (std::shared_ptr me : mbw->modelList) { + if (!label.compare(me->model->author)) + count++; + } + return std::to_string(count).append(" Modules"); +} + +void TagElement::onAction(EventAction &e) { + mbw->AddModels(tag); +} + +std::string TagElement::GetLabelTwo() { + unsigned int count = 0; + for (std::shared_ptr me : mbw->modelList) { + for (ModelTag mt : me->model->tags) { + if (mt == tag) { + count++; + } + } + } + return std::to_string(count).append(" Modules"); +} + +void ModelElement::onAction(EventAction &e) { + ModuleWidget *moduleWidget = model->createModuleWidget(); + if (!moduleWidget) + return; + RACK_PLUGIN_UI_RACKWIDGET->addModule(moduleWidget); + moduleWidget->box.pos = RACK_PLUGIN_UI_RACKWIDGET->lastMousePos.minus(moduleWidget->box.size.div(2)); + RACK_PLUGIN_UI_RACKWIDGET->requestModuleBoxNearest(moduleWidget, moduleWidget->box); +} + +void PluginBackElement::onAction(EventAction &e) { + mbw->AddPlugins(); +} + +void TagBackElement::onAction(EventAction &e) { + mbw->AddTags(); +} + +struct Blank2 : ModuleWidget { + Blank2(Module *module) : ModuleWidget(module) { + box.size = Vec(RACK_GRID_WIDTH * 5, RACK_GRID_HEIGHT * 2); + { + Panel *panel = new LightPanel(); + panel->box.size = box.size; + addChild(panel); + } + } +}; + +struct Blank3 : ModuleWidget { + Blank3(Module *module) : ModuleWidget(module) { + box.size = Vec(RACK_GRID_WIDTH * 5, RACK_GRID_HEIGHT * 3); + { + Panel *panel = new LightPanel(); + panel->box.size = box.size; + addChild(panel); + } + } +}; + +struct Blank5 : ModuleWidget { + Blank5(Module *module) : ModuleWidget(module) { + box.size = Vec(RACK_GRID_WIDTH * 5, RACK_GRID_HEIGHT * 5); + { + Panel *panel = new LightPanel(); + panel->box.size = box.size; + addChild(panel); + } + } +}; + +} // namespace rack_plugin_SubmarineUtility + +using namespace rack_plugin_SubmarineUtility; + +RACK_PLUGIN_MODEL_INIT(SubmarineUtility, ModBrowser) { + Model *modelModBrowser = Model::create("Submarine (Utilities)", "ModBrowser", "Module Browser", UTILITY_TAG); + return modelModBrowser; +} diff --git a/plugins/community/repos/SubmarineUtility/src/SubControls.cpp b/plugins/community/repos/SubmarineUtility/src/SubControls.cpp new file mode 100644 index 00000000..668c6675 --- /dev/null +++ b/plugins/community/repos/SubmarineUtility/src/SubControls.cpp @@ -0,0 +1,335 @@ +#include "SubControls.hpp" +#include "global_pre.hpp" +#include "global_ui.hpp" +#include "window.hpp" + +namespace rack_plugin_SubmarineUtility { + +namespace SubControls { + +struct RowShift { + Vec position; + int handled = false; +}; + +struct RowShifter { + std::vector> rows; + Widget *baseWidget; + unsigned int addRow(Vec position) { + // Search for row in existing list + for (std::shared_ptr row : rows) { + if (row->position.y != position.y) + continue; // This is not the row we are looking for + if (row->handled) + return false; // This is the row but it's been done already + if (row->position.x <= position.x) + return false; // This is the row already and covers the same ground + row->position.x = position.x; // We need to move the start point further to the left + return true; + } + std::shared_ptr row = std::make_shared(); + row->position.x = position.x; + row->position.y = position.y; + rows.push_back(row); + return true; // We didn't find the row so we added it. + } + unsigned int shift(float delta) { + unsigned moreWork = false; + for (std::shared_ptr row : rows) { + if (row->handled) + continue; // This row has been done already + row->handled = true; + for (Widget *w : RACK_PLUGIN_UI_RACKWIDGET->moduleContainer->children) { + if (baseWidget == w) + continue; // We are not moving the widget that caused this. + if (row->position.x > w->box.pos.x) + continue; // This is to the left of our start position + if (row->position.y != w->box.pos.y) { + if (row->position.y > w->box.pos.y) { + if (row->position.y < w->box.pos.y + w->box.size.y) { + for (float i = 0; i < w->box.size.y; i += RACK_GRID_HEIGHT) { + Vec newRow; + newRow.x = w->box.pos.x; + newRow.y = w->box.pos.y + i; + moreWork |= addRow(newRow); // These are the rows covered by a multi-row widget + } + } + } + continue; + } + if (w->box.size.y > RACK_GRID_HEIGHT) { + for (float i = 0; i < w->box.size.y; i += RACK_GRID_HEIGHT) { + Vec newRow; + newRow.x = w->box.pos.x; + newRow.y = w->box.pos.y + i; + moreWork |= addRow(newRow); // These are the rows covered by a multi row widget + } + } + w->box.pos.x += delta; // This widget should be moved + } + } + return moreWork; + } + void shortShiftRight(float endPoint) { + Widget *widgetToMove = baseWidget; + std::shared_ptr row = rows[0]; + for (Widget *w : RACK_PLUGIN_UI_RACKWIDGET->moduleContainer->children) { + if (w->box.pos.y != row->position.y) + continue; // This is not the row we are looking for + if (w->box.pos.x <= row->position.x) + continue; // This is to the left of the region we are looking at + if (w->box.pos.x >= endPoint) + continue; // This is to the right of the region we are looking at + if (w->box.pos.x > widgetToMove->box.pos.x) + widgetToMove = w; + } + if (widgetToMove == baseWidget) + return; // Nothing more to move + float delta = endPoint - widgetToMove->box.pos.x - widgetToMove->box.size.x; + widgetToMove->box.pos.x += delta; // Move this widget as far to the right as we can + endPoint -= widgetToMove->box.size.x; // Adjust our endpoint back to exclude this widget + shortShiftRight(endPoint); // Recurse to move the next widget + } + void shortShiftLeft(float delta, float endPoint) { + std::shared_ptr row = rows[0]; + for (Widget *w : RACK_PLUGIN_UI_RACKWIDGET->moduleContainer->children) { + if (w->box.pos.y != row->position.y) + continue; // This is not the row we are looking for + if (w->box.pos.x <= row->position.x) + continue; // This is to the left of the region we are looking at + if (w->box.pos.x >= endPoint) + continue; // This is to the right of the region we are looking at + w->box.pos.x += delta; + } + } + void process(float delta) { + // Test to see if any multi-row items are involved + float endPoint = 0.0f; + std::shared_ptr row = rows[0]; + for (Widget *w : RACK_PLUGIN_UI_RACKWIDGET->moduleContainer->children) { + if (w->box.size.y == RACK_GRID_HEIGHT) + continue; // This is a single row widget + if (w->box.pos.y > row->position.y) + continue; // This is below the row + if ((w->box.pos.y + w->box.size.y) > (row->position.y + RACK_GRID_HEIGHT)) { // This is what we are looking for + if ((endPoint == 0.0f) || endPoint > w->box.pos.x) { + endPoint = w->box.pos.x; + } + } + } + if (endPoint > 0.0f) { // There is a multi-row element in the way + if (delta < 0.0f) { + shortShiftLeft(delta, endPoint); // Just shift upto the endPoint + return; + } + // Have we got enough space to shuffle up before the multi-row element + float space = endPoint - baseWidget->box.pos.x - baseWidget->box.size.x; + debug("Space %f", space); + for (Widget *w : RACK_PLUGIN_UI_RACKWIDGET->moduleContainer->children) { + if (w->box.pos.y != row->position.y) + continue; // This is not the row we are looking for + if (w->box.pos.x <= row->position.x) + continue; // This is to the left of the region we are looking at + if (w->box.pos.x >= endPoint) + continue; // This is to the right of the region we are looking at + space -= w->box.size.x; + debug("Space adjusted to %f", space); + } + debug("Space %f delta %f", space, delta); + if (space >= delta) { + shortShiftRight(endPoint); // Just shift upto the endPoint + return; + } + } + while (shift(delta)); + } +}; + +SizeableModuleWidget::SizeableModuleWidget(Module *module) : ModuleWidget(module) { + box.size.x = moduleWidth; + box.size.y = 380; + handle = Widget::create(Vec(box.size.x - 10, 175)); + handle->smw = this; + addChild(handle); + + backPanel = Widget::create(Vec(10, 15)); + backPanel->box.size.x = box.size.x - 20; + backPanel->box.size.y = box.size.y - 30; + addChild(backPanel); + + minimizeLogo = Widget::create(Vec(0,0)); +#define plugin "SubmarineUtility" + minimizeLogo->setSVG(SVG::load(assetPlugin(plugin, "res/Sub2.svg"))); + minimizeLogo->visible = false; + addChild(minimizeLogo); + + maximizeLogo = Widget::create(Vec(moduleWidth - 20, 365)); + maximizeLogo->setSVG(SVG::load(assetPlugin(plugin, "res/Sub1.svg"))); + addChild(maximizeLogo); + + maximizeButton = Widget::create(Vec(0, 175)); + maximizeButton->smw = this; + maximizeButton->visible = false; + addChild(maximizeButton); +} + +void SizeableModuleWidget::Resize() { + backPanel->box.size.x = box.size.x - 20; + handle->box.pos.x = box.size.x - 10; + maximizeLogo->box.pos.x = box.size.x - 20; + handle->visible = sizeable; + onResize(); +} + +void SizeableModuleWidget::draw(NVGcontext *vg) { + nvgBeginPath(vg); + nvgRect(vg,0,0,box.size.x, box.size.y); + nvgFillColor(vg,nvgRGB(0x29, 0x4f, 0x77)); + nvgFill(vg); + + nvgBeginPath(vg); + nvgMoveTo(vg, 0, 0); + nvgLineTo(vg, box.size.x, 0); + nvgLineTo(vg, box.size.x - 1, 1); + nvgLineTo(vg, 1, 1); + nvgClosePath(vg); + nvgMoveTo(vg, 1, 1); + nvgLineTo(vg, 1, box.size.y - 1); + nvgLineTo(vg, 0, box.size.y); + nvgLineTo(vg, 0, 0); + nvgClosePath(vg); + nvgFillColor(vg, nvgRGB(0x3a, 0x6e, 0xa5)); + nvgFill(vg); + + nvgBeginPath(vg); + nvgMoveTo(vg, box.size.x, 0); + nvgLineTo(vg, box.size.x, box.size.y); + nvgLineTo(vg, box.size.x - 1, box.size.y - 1); + nvgLineTo(vg, box.size.x -1, 1); + nvgClosePath(vg); + nvgMoveTo(vg, box.size.x, box.size.y); + nvgLineTo(vg, 0, box.size.y); + nvgLineTo(vg, 1, box.size.y - 1); + nvgLineTo(vg, box.size.x - 1, box.size.y - 1); + nvgClosePath(vg); + nvgFillColor(vg, nvgRGB(0x18, 0x2d, 0x44)); + nvgFill(vg); + + if (moduleWidth > 0) { + nvgFontSize(vg, 14); + nvgFontFaceId(vg, font->handle); + nvgFillColor(vg, nvgRGBA(0x71, 0x9f, 0xcf, 0xff)); + nvgTextAlign(vg, NVG_ALIGN_LEFT); + nvgText(vg, 3, 378, "submarine", NULL); + nvgTextAlign(vg, NVG_ALIGN_CENTER); + nvgText(vg, box.size.x / 2, 12, moduleName.c_str(), NULL); + } + else { + nvgSave(vg); + nvgRotate(vg, -M_PI / 2); + nvgFontSize(vg, 14); + nvgFontFaceId(vg, font->handle); + nvgFillColor(vg, nvgRGBA(0x71, 0x9f, 0xcf, 0xff)); + nvgTextAlign(vg, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE); + nvgText(vg, -97.5, 7.5, moduleName.c_str(), NULL); + nvgText(vg, -277.5, 7.5, "submarine", NULL); + nvgRestore(vg); + } + ModuleWidget::draw(vg); +} + +void SizeableModuleWidget::ShiftOthers(float delta) { + if (!stabilized) + return; + if (delta == 0.0f) + return; + RowShifter shifter; + shifter.baseWidget = this; + shifter.addRow(this->box.pos); + shifter.process(delta); +} + +void SizeableModuleWidget::Minimize(unsigned int minimize) { + float oldSize = box.size.x; + if (minimize) { + if (moduleWidth > 0) + moduleWidth = -moduleWidth; + box.size.x = 15; + backPanel->visible = false; + maximizeButton->visible = true; + maximizeLogo->visible = false; + minimizeLogo->visible = true; + handle->visible = false; + ShiftOthers(box.size.x - oldSize); + } + else { + if (moduleWidth < 0) + moduleWidth = -moduleWidth; + ShiftOthers(moduleWidth - oldSize); + box.size.x = moduleWidth; + backPanel->visible = true; + maximizeButton->visible = false; + maximizeLogo->visible = true; + minimizeLogo->visible = false; + handle->visible = sizeable; + Resize(); + } +} + +json_t *SizeableModuleWidget::toJson() { + json_t *rootJ = ModuleWidget::toJson(); + + // moduleWidth + json_object_set_new (rootJ, "width", json_real(moduleWidth)); + + return rootJ; +} + +void SizeableModuleWidget::fromJson(json_t *rootJ) { + ModuleWidget::fromJson(rootJ); + + // width + json_t *widthJ = json_object_get(rootJ, "width"); + if (widthJ) + moduleWidth = json_number_value(widthJ); + Minimize(moduleWidth < 0); +} + +void ModuleDragHandle::onDragStart(EventDragStart &e) { + dragX = RACK_PLUGIN_UI_RACKWIDGET->lastMousePos.x; + originalBox = smw->box; +} + +void ModuleDragHandle::onDragMove(EventDragMove &e) { + + float newDragX = RACK_PLUGIN_UI_RACKWIDGET->lastMousePos.x; + float deltaX = newDragX - dragX; + + Rect newBox = originalBox; + newBox.size.x += deltaX; + newBox.size.x = fmaxf(newBox.size.x, smw->minimumWidth); + newBox.size.x = roundf(newBox.size.x / RACK_GRID_WIDTH) * RACK_GRID_WIDTH; + RACK_PLUGIN_UI_RACKWIDGET->requestModuleBox(smw, newBox); + smw->moduleWidth = smw->box.size.x; + smw->Resize(); +} + +void ModuleDragHandle::draw(NVGcontext *vg) { + for (float x = 2.0; x <= 8.0; x += 2.0) { + nvgBeginPath(vg); + const float margin = 5.0; + nvgMoveTo(vg, x + 0.5, margin + 0.5); + nvgLineTo(vg, x + 0.5, box.size.y - margin + 0.5); + nvgStrokeWidth(vg, 1.0); + nvgStrokeColor(vg, nvgRGBAf(0.5, 0.5, 0.5, 0.5)); + nvgStroke(vg); + } +} + +void MaximizeButton::onAction(EventAction &e) { + smw->Minimize(false); +} + +} // SubControls + +} // namespace rack_plugin_SubmarineUtility diff --git a/plugins/community/repos/SubmarineUtility/src/SubControls.hpp b/plugins/community/repos/SubmarineUtility/src/SubControls.hpp new file mode 100644 index 00000000..164b8c3d --- /dev/null +++ b/plugins/community/repos/SubmarineUtility/src/SubControls.hpp @@ -0,0 +1,225 @@ +#include "SubmarineUtility.hpp" +#undef plugin +#include "global.hpp" +#include "global_pre.hpp" +#include "window.hpp" + +namespace rack_plugin_SubmarineUtility { + +namespace SubControls { + +struct BackPanel : OpaqueWidget { + void draw (NVGcontext *vg) override { + nvgBeginPath(vg); + nvgRect(vg, 0, 0, box.size.x, box.size.y); + nvgFillColor(vg, nvgRGB(0, 0, 0)); + nvgFill(vg); + OpaqueWidget::draw(vg); + } +}; + +struct ButtonBase : Component { + void onDragEnd(EventDragEnd &e) override { + EventAction eAction; + onAction(eAction); + } +}; + +struct RadioButton : ButtonBase { + std::string label; + int selected = false; + void draw (NVGcontext *vg) override { + nvgStrokeWidth(vg, 1); + nvgFillColor(vg, nvgRGB(0xff, 0xff, 0xff)); + if (!label.empty()) { + nvgFontFaceId(vg, rack::global_ui->window.gGuiFont->handle); + nvgFontSize(vg, 13); + nvgTextAlign(vg, NVG_ALIGN_MIDDLE); + nvgText(vg, 21, box.size.y / 2, label.c_str(), NULL); + } + if (selected) { + nvgBeginPath(vg); + nvgCircle(vg, box.size.y / 2 + 1, box.size.y / 2, 5); + nvgFill(vg); + } + nvgBeginPath(vg); + nvgCircle(vg, box.size.y / 2 + 1, box.size.y / 2, 8); + nvgStrokeColor(vg, nvgRGB(0xff, 0xff, 0xff)); + nvgStroke(vg); + Component::draw(vg); + } +}; + +struct CheckButton : ButtonBase { + std::string label; + int selected = false; + void draw (NVGcontext *vg) override { + nvgFillColor(vg, nvgRGB(0xff, 0xff, 0xff)); + if (!label.empty()) { + nvgFontFaceId(vg, rack::global_ui->window.gGuiFont->handle); + nvgFontSize(vg, 13); + nvgTextAlign(vg, NVG_ALIGN_MIDDLE); + nvgText(vg, 21, box.size.y / 2, label.c_str(), NULL); + } + nvgStrokeWidth(vg, 1); + nvgStrokeColor(vg, nvgRGB(0xff, 0xff, 0xff)); + if (selected) { + nvgBeginPath(vg); + nvgMoveTo(vg, box.size.y / 2 - 4, box.size.y / 2 - 5); + nvgLineTo(vg, box.size.y / 2 + 6, box.size.y / 2 + 5); + nvgMoveTo(vg, box.size.y / 2 - 4, box.size.y / 2 + 5); + nvgLineTo(vg, box.size.y / 2 + 6, box.size.y / 2 - 5); + nvgStroke(vg); + } + nvgBeginPath(vg); + nvgRect(vg, box.size.y / 2 - 7, box.size.y / 2 - 8, 16, 16); + nvgStroke(vg); + Component::draw(vg); + } +}; + +struct ClickButton : ButtonBase { + std::string label; + void draw (NVGcontext *vg) override { + nvgFillColor(vg, nvgRGB(0xff, 0xff, 0xff)); + if (!label.empty()) { + nvgFontFaceId(vg, rack::global_ui->window.gGuiFont->handle); + nvgFontSize(vg, 13); + nvgTextAlign(vg, NVG_ALIGN_MIDDLE | NVG_ALIGN_CENTER); + nvgText(vg, box.size.x / 2, box.size.y / 2, label.c_str(), NULL); + } + nvgBeginPath(vg); + nvgRect(vg, 0.5, 0.5, box.size.x - 1, box.size.y - 1); + nvgStrokeColor(vg, nvgRGB(0xff, 0xff, 0xff)); + nvgStrokeWidth(vg, 1); + nvgStroke(vg); + Component::draw(vg); + } +}; + +struct Label : TransparentWidget { + std::string label; + void draw (NVGcontext *vg) override { + nvgFillColor(vg, nvgRGB(0xff, 0xff, 0xff)); + if (!label.empty()) { + nvgFontFaceId(vg, rack::global_ui->window.gGuiFont->handle); + nvgFontSize(vg, 13); + nvgTextAlign(vg, NVG_ALIGN_MIDDLE); + nvgText(vg, 1, box.size.y / 2, label.c_str(), NULL); + } + TransparentWidget::draw(vg); + } +}; + +struct Slider : Knob { + int transparent = false; + void draw(NVGcontext *vg) override { + Vec minHandlePos; + Vec maxHandlePos; + float width; + float height; + if (box.size.x < box.size.y) { + width = box.size.x; + height = 10; + minHandlePos = Vec(box.size.x / 2, height / 2); + maxHandlePos = Vec(box.size.x / 2, box.size.y - height / 2); + } + else { + width = 10; + height = box.size.y; + minHandlePos = Vec(width / 2, box.size.y / 2); + maxHandlePos = Vec(box.size.x - width / 2, box.size.y / 2); + } + Vec pos = Vec(rescale(value, minValue, maxValue, minHandlePos.x, maxHandlePos.x), rescale(value, minValue, maxValue, minHandlePos.y, maxHandlePos.y)); + nvgFillColor(vg, nvgRGB(0, 0, 0)); + nvgStrokeColor(vg, nvgRGB(0xff, 0xff, 0xff)); + nvgStrokeWidth(vg, 1); + if (!transparent) { + nvgBeginPath(vg); + nvgMoveTo(vg, minHandlePos.x, minHandlePos.y); + nvgLineTo(vg, maxHandlePos.x, maxHandlePos.y); + nvgStroke(vg); + } + nvgBeginPath(vg); + nvgRect(vg, pos.x - width / 2 + 0.5, pos.y - height / 2 + 0.5 , width - 1, height - 1); + if (!transparent) nvgFill(vg); + nvgStroke(vg); + } +}; + +struct HSlider : Slider { + void onDragMove(EventDragMove &e) override { + e.mouseRel.y = -e.mouseRel.x; + Knob::onDragMove(e); + } +}; + +struct SubLogo : SVGWidget{}; + +struct ModuleDragHandle; +struct SizeableModuleWidget; + +struct MaximizeButton : ButtonBase { + SizeableModuleWidget *smw; + MaximizeButton() { + box.size.x = 15; + box.size.y = 30; + } + void draw(NVGcontext *vg) override { + nvgBeginPath(vg); + nvgMoveTo(vg, 2, 4); + nvgLineTo(vg, 13, 15); + nvgLineTo(vg, 2, 26); + nvgClosePath(vg); + nvgFillColor(vg, nvgRGB(0x71, 0x9f, 0xcf)); + nvgFill(vg); + Component::draw(vg); + } + void onAction(EventAction &e) override; +}; + +struct SizeableModuleWidget : ModuleWidget { + float moduleWidth = 300.0f; + float minimumWidth = 185.0f; + unsigned int sizeable = true; + unsigned int stabilized = false; + ModuleDragHandle *handle; + BackPanel *backPanel; + SubLogo *minimizeLogo; + SubLogo *maximizeLogo; + MaximizeButton *maximizeButton; + std::shared_ptr font = Font::load(assetGlobal("res/fonts/DejaVuSans.ttf")); + std::string moduleName; + + SizeableModuleWidget(Module *module); + void draw(NVGcontext *vg) override; + void Resize(); + void Minimize(unsigned int minimize); + void ShiftOthers(float delta); + json_t *toJson() override; + void fromJson(json_t *rootJ) override; + + virtual void onResize() { }; +}; + +struct ModuleDragHandle : VirtualWidget { + SizeableModuleWidget *smw; + float dragX; + Rect originalBox; + ModuleDragHandle() { + box.size = Vec(10, 30); + } + void onMouseDown(EventMouseDown &e) override { + if (e.button == 0) { + e.consumed = true; + e.target = this; + } + } + void draw(NVGcontext *vg) override; + void onDragStart(EventDragStart &e) override; + void onDragMove(EventDragMove &e) override; +}; + +} // SubControls + +} // namespace rack_plugin_SubmarineUtility diff --git a/plugins/community/repos/SubmarineUtility/src/SubmarineUtility.cpp b/plugins/community/repos/SubmarineUtility/src/SubmarineUtility.cpp new file mode 100644 index 00000000..41f6dfab --- /dev/null +++ b/plugins/community/repos/SubmarineUtility/src/SubmarineUtility.cpp @@ -0,0 +1,14 @@ +#include "SubmarineUtility.hpp" + +RACK_PLUGIN_MODEL_DECLARE(SubmarineUtility, ModBrowser); +RACK_PLUGIN_MODEL_DECLARE(SubmarineUtility, WireManager); + +RACK_PLUGIN_INIT(SubmarineUtility) { + RACK_PLUGIN_INIT_ID(); + RACK_PLUGIN_INIT_VERSION("0.6.2"); + RACK_PLUGIN_INIT_WEBSITE("https://github.com/david-c14/SubmarineUtility"); + RACK_PLUGIN_INIT_MANUAL("https://github.com/david-c14/SubmarineUtility"); + + RACK_PLUGIN_MODEL_ADD(SubmarineUtility, ModBrowser); + RACK_PLUGIN_MODEL_ADD(SubmarineUtility, WireManager); +} diff --git a/plugins/community/repos/SubmarineUtility/src/SubmarineUtility.hpp b/plugins/community/repos/SubmarineUtility/src/SubmarineUtility.hpp new file mode 100644 index 00000000..3be5e31c --- /dev/null +++ b/plugins/community/repos/SubmarineUtility/src/SubmarineUtility.hpp @@ -0,0 +1,9 @@ +#include "rack.hpp" + +using namespace rack; + +RACK_PLUGIN_DECLARE(SubmarineUtility); + +#ifdef USE_VST2 +#define plugin "SubmarineUtility" +#endif // USE_VST2 diff --git a/plugins/community/repos/SubmarineUtility/src/WireManager.cpp b/plugins/community/repos/SubmarineUtility/src/WireManager.cpp new file mode 100644 index 00000000..3f05d6d5 --- /dev/null +++ b/plugins/community/repos/SubmarineUtility/src/WireManager.cpp @@ -0,0 +1,1023 @@ +#include "SubControls.hpp" +#include +#include +#include "global_pre.hpp" +#include "global_ui.hpp" +#include "window.hpp" +#include "osdialog.h" + +namespace rack_plugin_SubmarineUtility { + +struct WireManagerWidget; + +// Icons + +struct WMIconWidget : SubControls::ButtonBase,SVGWidget { + WireManagerWidget *wmw; +}; + +struct WMMinimizeIcon : WMIconWidget { + WMMinimizeIcon() { + box.size.x = 30; + box.size.y = 30; + } + void onAction(EventAction &e) override; +}; + +struct WMColorIcon : WMIconWidget { + WMColorIcon() { + box.size.x = 30; + box.size.y = 30; + } + void onAction(EventAction &e) override; +}; + +struct WMOptionIcon : WMIconWidget { + WMOptionIcon() { + box.size.x = 30; + box.size.y = 30; + } + void onAction(EventAction &e) override; +}; + +struct WMHighlightButton : SubControls::RadioButton { + WireManagerWidget *wmw; + int status; + void onAction(EventAction &e) override; +}; + +struct WMWireCheck : SubControls::CheckButton { + WireManagerWidget *wmw; + void onAction(EventAction &e) override; +}; + +struct WMWireAdd : SubControls::ButtonBase { + WireManagerWidget *wmw; + void onAction(EventAction &e) override; + void draw(NVGcontext *vg) override { + nvgBeginPath(vg); + nvgFillColor(vg, nvgRGB(0xff, 0xff, 0xff)); + nvgRect(vg, box.size.x / 2 - 1.5, 2, 3, box.size.y - 4); + nvgFill(vg); + nvgBeginPath(vg); + nvgRect(vg, 2, box.size.y / 2 - 1.5, box.size.x - 4, 3); + nvgFill(vg); + SubControls::ButtonBase::draw(vg); + } +}; + +struct WMWireEdit; +struct WMWireUp; +struct WMWireDown; + +struct WMWireButton : VirtualWidget { + NVGcolor color; + WMWireCheck *wmc; + WMWireEdit *wme; + WMWireUp *wmu; + WMWireDown *wmd; + WMWireButton(); + void draw(NVGcontext *vg) override { + NVGcolor colorOutline = nvgLerpRGBA(color, nvgRGBf(0.0, 0.0, 0.0), 0.5); + + nvgBeginPath(vg); + nvgMoveTo(vg, 32, box.size.y / 2); + nvgLineTo(vg, box.size.x - 40, box.size.y / 2); + nvgStrokeColor(vg, colorOutline); + nvgStrokeWidth(vg, 5); + nvgStroke(vg); + + nvgStrokeColor(vg, color); + nvgStrokeWidth(vg, 3); + nvgStroke(vg); + + nvgBeginPath(vg); + nvgCircle(vg, 32, box.size.y / 2, 9); + nvgFillColor(vg, color); + nvgFill(vg); + + nvgStrokeWidth(vg, 1.0); + nvgStrokeColor(vg, colorOutline); + nvgStroke(vg); + + nvgBeginPath(vg); + nvgCircle(vg, 32, box.size.y / 2, 5); + nvgFillColor(vg, nvgRGBf(0.0, 0.0, 0.0)); + nvgFill(vg); + + Widget::draw(vg); + } +}; + +struct WMCheckAll : SubControls::CheckButton { + WireManagerWidget *wmw; + void onAction(EventAction &e) override; +}; + +struct WMManageButton : VirtualWidget { + WMCheckAll *wmc; + WMWireAdd *wma; + WMManageButton() { + wmc = Widget::create(Vec(1,1)); + wmc->box.size.x = 19; + wmc->box.size.y = 19; + addChild(wmc); + wma = Widget::create(Vec(108, 1)); + wma->box.size.x = 19; + wma->box.size.y = 19; + addChild(wma); + } +}; + +struct WMWireEdit : SubControls::ButtonBase { + WMWireButton *wmb; + void onAction(EventAction &e) override; + void draw(NVGcontext *vg) override { + nvgBeginPath(vg); + nvgFillColor(vg, nvgRGB(0, 0, 0)); + nvgRect(vg, 0, 0, box.size.x, box.size.y); + nvgFill(vg); + nvgBeginPath(vg); + nvgFillColor(vg, nvgRGB(0xff, 0xff, 0xff)); + nvgCircle(vg, box.size.x / 2 - 5, box.size.y / 2, 2); + nvgCircle(vg, box.size.x / 2, box.size.y / 2, 2); + nvgCircle(vg, box.size.x / 2 + 5, box.size.y / 2, 2); + nvgFill(vg); + ButtonBase::draw(vg); + } +}; + +struct WMWireUp : SubControls::ButtonBase { + WMWireButton *wmb; + void onAction(EventAction &e) override; + void draw(NVGcontext *vg) override { + nvgBeginPath(vg); + nvgFillColor(vg, nvgRGB(0, 0, 0)); + nvgRect(vg, 0, 0, box.size.x, box.size.y); + nvgFill(vg); + if (wmb->box.pos.y > 22) { + nvgBeginPath(vg); + nvgFillColor(vg, nvgRGB(0xff, 0xff, 0xff)); + nvgMoveTo(vg, box.size.x / 2, 1); + nvgLineTo(vg, box.size.x - 1, box.size.y - 1); + nvgLineTo(vg, 1, box.size.y - 1); + nvgClosePath(vg); + nvgFill(vg); + } + ButtonBase::draw(vg); + } +}; + +struct WMWireDown : SubControls::ButtonBase { + WMWireButton *wmb; + void onAction(EventAction &e) override; + void draw(NVGcontext *vg) override { + nvgBeginPath(vg); + nvgFillColor(vg, nvgRGB(0, 0, 0)); + nvgRect(vg, 0, 0, box.size.x, box.size.y); + nvgFill(vg); + if ((wmb->box.pos.y / 21) < (wmb->parent->children.size() - 1)) { + nvgBeginPath(vg); + nvgFillColor(vg, nvgRGB(0xff, 0xff, 0xff)); + nvgMoveTo(vg, box.size.x / 2, box.size.y - 1); + nvgLineTo(vg, box.size.x - 1, 1); + nvgLineTo(vg, 1, 1); + nvgClosePath(vg); + nvgFill(vg); + } + ButtonBase::draw(vg); + } +}; + +WMWireButton::WMWireButton() { + wmc = Widget::create(Vec(1,1)); + wmc->box.size.x = 19; + wmc->box.size.y = 19; + addChild(wmc); + wme = Widget::create(Vec(108,1)); + wme->box.size.x = 19; + wme->box.size.y = 19; + wme->wmb = this; + addChild(wme); + wmu = Widget::create(Vec(92,2)); + wmu->box.size.x = 15; + wmu->box.size.y = 7; + wmu->wmb = this; + addChild(wmu); + wmd = Widget::create(Vec(92,12)); + wmd->box.size.x = 15; + wmd->box.size.y = 7; + wmd->wmb = this; + addChild(wmd); +} + +struct WMSlider : SubControls::HSlider { + WireManagerWidget *wmw; + void onAction(EventAction &e) override; +}; + +struct WMEditWidget : VirtualWidget { + WireManagerWidget *wmw; + void draw(NVGcontext *vg) override; +}; + +struct WMDeleteWidget : VirtualWidget { + WireManagerWidget *wmw; + void draw(NVGcontext *vg) override; +}; + +struct WMSaveButton : SubControls::ClickButton { + WireManagerWidget *wmw; + void onAction(EventAction &e) override; +}; + +struct WMCancelButton : SubControls::ClickButton { + WireManagerWidget *wmw; + void onAction(EventAction &e) override; +}; + +struct WMDeleteButton : SubControls::ClickButton { + WireManagerWidget *wmw; + void onAction(EventAction &e) override; +}; + +struct WMOKButton : SubControls::ClickButton { + WireManagerWidget *wmw; + void onAction(EventAction &e) override; +}; + +struct WireManagerWidget : SubControls::SizeableModuleWidget { + + enum { + HIGHLIGHT_OFF, + HIGHLIGHT_LOW, + HIGHLIGHT_ON + }; + + WMColorIcon *colorIcon; + WMOptionIcon *optionIcon; + WMMinimizeIcon *minimizeIcon; + ScrollWidget *colorWidget; + VirtualWidget *optionWidget; + WMEditWidget *editWidget; + WMDeleteWidget *deleteWidget; + WMDeleteButton *deleteButton; + + WMHighlightButton *highlightOff; + WMHighlightButton *highlightLow; + WMHighlightButton *highlightOn; + WMSlider *highlightSlider; + WMWireCheck *varyCheck; + WMSlider *varyH; + WMSlider *varyS; + WMSlider *varyL; + + SubControls::HSlider *varyR; + SubControls::HSlider *varyG; + SubControls::HSlider *varyB; + + int wireCount = 0; + Widget *lastWire = NULL; + int highlight = 0; + unsigned int newColorIndex = 1; + WMWireButton *editingColor; + ModuleWidget *lastHover = NULL; + int highlightIsDirty = true; + + WireManagerWidget(Module *module) : SubControls::SizeableModuleWidget(module) { + moduleName = "Wire Manager"; + moduleWidth = 150; + minimumWidth = 150; + sizeable = false; + box.size.x = moduleWidth; + Resize(); + + colorIcon = Widget::create(Vec(2, 2)); + colorIcon->wmw = this; +#define plugin "SubmarineUtility" + colorIcon->setSVG(SVG::load(assetPlugin(plugin, "res/colors.svg"))); + backPanel->addChild(colorIcon); + + optionIcon = Widget::create(Vec(34, 2)); + optionIcon->wmw = this; + optionIcon->setSVG(SVG::load(assetPlugin(plugin, "res/hls.svg"))); + backPanel->addChild(optionIcon); + + minimizeIcon = Widget::create(Vec(66, 2)); + minimizeIcon->wmw = this; + minimizeIcon->setSVG(SVG::load(assetPlugin(plugin, "res/min.svg"))); + backPanel->addChild(minimizeIcon); + + colorWidget = Widget::create(Vec(0,35)); + colorWidget->box.size.x = box.size.x - 20; + colorWidget->box.size.y = box.size.y - 65; + backPanel->addChild(colorWidget); + + WMManageButton *wb = Widget::create(Vec(0, 0)); + wb->wmc->wmw = this; + wb->wma->wmw = this; + wb->box.size.x = colorWidget->box.size.x; + wb->box.size.y = 21; + colorWidget->container->addChild(wb); + + optionWidget = Widget::create(Vec(0, 35)); + optionWidget->box.size.x = box.size.x - 20; + optionWidget->box.size.y = box.size.y - 65; + optionWidget->visible = false; + backPanel->addChild(optionWidget); + + varyCheck = Widget::create(Vec(10, 5)); + varyCheck->wmw = this; + varyCheck->label = "Variation"; + varyCheck->box.size.x = box.size.x - 40; + varyCheck->box.size.y = 19; + optionWidget->addChild(varyCheck); + + SubControls::Label *label = Widget::create(Vec(10, 25)); + label->label = "H"; + label->box.size.x = 10; + label->box.size.y = 19; + optionWidget->addChild(label); + + label = Widget::create(Vec(10, 45)); + label->label = "S"; + label->box.size.x = 10; + label->box.size.y = 19; + optionWidget->addChild(label); + + label = Widget::create(Vec(10, 65)); + label->label = "L"; + label->box.size.x = 10; + label->box.size.y = 19; + optionWidget->addChild(label); + + varyH = Widget::create(Vec(20, 25)); + varyH->wmw = this; + varyH->box.size.x = box.size.x - 50; + varyH->box.size.y = 19; + varyH->minValue = 0.0f; + varyH->maxValue = 1.0f; + varyH->value = 0.1f; + varyH->defaultValue = 0.1f; + optionWidget->addChild(varyH); + + varyS = Widget::create(Vec(20, 45)); + varyS->wmw = this; + varyS->box.size.x = box.size.x - 50; + varyS->box.size.y = 19; + varyS->minValue = 0.0f; + varyS->maxValue = 1.0f; + varyS->value = 0.1f; + varyS->defaultValue = 0.1f; + optionWidget->addChild(varyS); + + varyL = Widget::create(Vec(20, 65)); + varyL->wmw = this; + varyL->box.size.x = box.size.x - 50; + varyL->box.size.y = 19; + varyL->minValue = 0.0f; + varyL->maxValue = 1.0f; + varyL->value = 0.1f; + varyL->defaultValue = 0.1f; + optionWidget->addChild(varyL); + + label = Widget::create(Vec(10, 105)); + label->label = "Highlighting"; + label->box.size.x = box.size.x - 40; + label->box.size.y = 19; + optionWidget->addChild(label); + + highlightOff = Widget::create(Vec(10, 125)); + highlightOff->wmw = this; + highlightOff->status = HIGHLIGHT_OFF; + highlightOff->box.size.x = box.size.x - 40; + highlightOff->box.size.y = 19; + highlightOff->selected = true; + highlightOff->label = "Off"; + optionWidget->addChild(highlightOff); + + highlightLow = Widget::create(Vec(10, 145)); + highlightLow->wmw = this; + highlightLow->status = HIGHLIGHT_LOW; + highlightLow->box.size.x = box.size.x - 40; + highlightLow->box.size.y = 19; + highlightLow->label = "When hovering"; + optionWidget->addChild(highlightLow); + + highlightOn = Widget::create(Vec(10, 165)); + highlightOn->wmw = this; + highlightOn->status = HIGHLIGHT_ON; + highlightOn->box.size.x = box.size.x - 40; + highlightOn->box.size.y = 19; + highlightOn->label = "Always On"; + optionWidget->addChild(highlightOn); + + highlightSlider = Widget::create(Vec(10, 185)); + highlightSlider->wmw = this; + highlightSlider->box.size.x = box.size.x - 40; + highlightSlider->box.size.y = 21; + highlightSlider->minValue = 0; + highlightSlider->maxValue = 1; + highlightSlider->value = 0.1; + highlightSlider->defaultValue = 0.1; + optionWidget->addChild(highlightSlider); + + editWidget = Widget::create(Vec(0, 35)); + editWidget->wmw = this; + editWidget->box.size.x = box.size.x - 20; + editWidget->box.size.y = box.size.y - 65; + editWidget->visible = false; + backPanel->addChild(editWidget); + + varyR = Widget::create(Vec(10, 105)); + varyR->transparent = true; + varyR->box.size.x = box.size.x - 40; + varyR->box.size.y = 19; + varyR->minValue = 0.0f; + varyR->maxValue = 1.0f; + varyR->value = 0.5f; + varyR->defaultValue = 0.5f; + editWidget->addChild(varyR); + + varyG = Widget::create(Vec(10, 145)); + varyG->transparent = true; + varyG->box.size.x = box.size.x - 40; + varyG->box.size.y = 19; + varyG->minValue = 0.0f; + varyG->maxValue = 1.0f; + varyG->value = 0.5f; + varyG->defaultValue = 0.5f; + editWidget->addChild(varyG); + + varyB = Widget::create(Vec(10, 185)); + varyB->transparent = true; + varyB->box.size.x = box.size.x - 40; + varyB->box.size.y = 19; + varyB->minValue = 0.0f; + varyB->maxValue = 1.0f; + varyB->value = 0.5f; + varyB->defaultValue = 0.5f; + editWidget->addChild(varyB); + + WMSaveButton *saveButton = Widget::create(Vec(5, box.size.y - 90)); + saveButton->wmw = this; + saveButton->box.size.x = 55; + saveButton->box.size.y = 19; + saveButton->label = "Save"; + editWidget->addChild(saveButton); + + WMCancelButton *cancelButton = Widget::create(Vec(box.size.x - 80, box.size.y - 90)); + cancelButton->wmw = this; + cancelButton->box.size.x = 55; + cancelButton->box.size.y = 19; + cancelButton->label = "Cancel"; + editWidget->addChild(cancelButton); + + deleteButton = Widget::create(Vec(box.size.x - 80, box.size.y - 130)); + deleteButton->wmw = this; + deleteButton->box.size.x = 55; + deleteButton->box.size.y = 19; + deleteButton->label = "Delete..."; + editWidget->addChild(deleteButton); + + deleteWidget = Widget::create(Vec(0, 35)); + deleteWidget->wmw = this; + deleteWidget->box.size.x = box.size.x - 20; + deleteWidget->box.size.y = box.size.y - 65; + deleteWidget->visible = false; + backPanel->addChild(deleteWidget); + + label = Widget::create(Vec(25, 195)); + label->label = "Delete Color?"; + label->box.size.x = box.size.x - 40; + label->box.size.y = 19; + deleteWidget->addChild(label); + + WMOKButton *okButton = Widget::create(Vec(5, box.size.y - 90)); + okButton->wmw = this; + okButton->box.size.x = 55; + okButton->box.size.y = 19; + okButton->label = "Okay"; + deleteWidget->addChild(okButton); + + cancelButton = Widget::create(Vec(box.size.x - 80, box.size.y - 90)); + cancelButton->wmw = this; + cancelButton->box.size.x = 55; + cancelButton->box.size.y = 19; + cancelButton->label = "Cancel"; + deleteWidget->addChild(cancelButton); + + LoadSettings(); + + wireCount = RACK_PLUGIN_UI_RACKWIDGET->wireContainer->children.size(); + if (wireCount) + lastWire = RACK_PLUGIN_UI_RACKWIDGET->wireContainer->children.back(); + } + + void _delete() override { + if (highlight) { + SetHighlight(HIGHLIGHT_OFF); + highlightWires(); + } + } + + void ResetIcons() { + } + + void AddColor(NVGcolor color, int selected) { + float y = colorWidget->container->children.size() * 21.0; + WMWireButton *wb = Widget::create(Vec(0, y)); + wb->box.size.x = colorWidget->box.size.x; + wb->box.size.y = 21; + wb->color = color; + wb->wmc->selected = selected; + wb->wmc->wmw = this; + colorWidget->container->addChild(wb); + } + + void SetDefaults() { + AddColor(nvgRGB(0xc9, 0xb7, 0x0e), true); + AddColor(nvgRGB(0xc9, 0x18, 0x47), true); + AddColor(nvgRGB(0x0c, 0x8e, 0x15), true); + AddColor(nvgRGB(0x09, 0x86, 0xad), true); + AddColor(nvgRGB(0xff, 0xae, 0xc9), false); + AddColor(nvgRGB(0xb7, 0x00, 0xb5), false); + AddColor(nvgRGB(0x80, 0x80, 0x80), false); + AddColor(nvgRGB(0xff, 0xff, 0xff), false); + AddColor(nvgRGB(0x10, 0x0f, 0x12), false); + AddColor(nvgRGB(0xff, 0x99, 0x41), false); + AddColor(nvgRGB(0x80, 0x36, 0x10), false); + } + + NVGcolor findColor(NVGcolor color) { + auto vi = colorWidget->container->children.begin(); + std::advance(vi, newColorIndex); + for (int i = 0; i < 2; i++) { + while(newColorIndex < colorWidget->container->children.size()) { + newColorIndex++; + WMWireButton *wb = dynamic_cast(*vi); + if (wb->wmc->selected) { + return wb->color; + } + std::advance(vi, 1); + } + newColorIndex = 1; + vi = colorWidget->container->children.begin(); + std::advance(vi, newColorIndex); + } + return color; + } + + NVGcolor varyColor(NVGcolor color) { + float r = color.r; + float g = color.g; + float b = color.b; + float a = color.a; + // convert to hsl + + float Cmax = std::max(r,std::max(g,b)); + float Cmin = std::min(r,std::min(g,b)); + float delta = Cmax - Cmin; + + float h = 0; + float s = 0; + float l = (Cmax + Cmin) / 2; + + if (delta > 0) { + s = delta / (1 - std::abs(l * 2 - 1)); + if (Cmax == r) { + h = std::fmod(6 + (g-b)/delta, 6); + } + else if (Cmax == g) { + h = (b-r)/delta + 2; + } + else { + h = (r-g)/delta + 4; + } + } + // Modify color + + h = std::fmod(1 + h / 6 + (randomUniform() - 0.5f) * varyH->value, 1.0f); + s = rescale(randomUniform(), 0.0f, 1.0f, std::max(s - varyS->value, 0.0f), std::min(s + varyS->value, 1.0f)); + l = rescale(randomUniform(), 0.0f, 1.0f, std::max(l - varyL->value, 0.0f), std::min(l + varyL->value, 1.0f)); + return nvgHSLA(h, s, l, a * 255); + } + + void colorWire(Widget *widget) { + WireWidget *wire = dynamic_cast(widget); + wire->color = findColor(wire->color); + if (varyCheck->selected) { + wire->color = varyColor(wire->color); + } + } + + void step() override { + if (!stabilized) { + wireCount = RACK_PLUGIN_UI_RACKWIDGET->wireContainer->children.size(); + stabilized = true; + } + int newSize = RACK_PLUGIN_UI_RACKWIDGET->wireContainer->children.size(); + if (newSize < wireCount) { + wireCount = newSize; + if (wireCount) + lastWire = RACK_PLUGIN_UI_RACKWIDGET->wireContainer->children.back(); + else + lastWire = NULL; + } + else if (newSize > wireCount) { + std::list::reverse_iterator iterator = RACK_PLUGIN_UI_RACKWIDGET->wireContainer->children.rbegin(); + for (int i = 0; i < newSize - wireCount; i++) { + colorWire(*iterator); + ++iterator; + } + wireCount = newSize; + if (wireCount) + lastWire = RACK_PLUGIN_UI_RACKWIDGET->wireContainer->children.back(); + else + lastWire = NULL; + highlightIsDirty = true; + } + highlightWires(); + ModuleWidget::step(); + } + void highlightWires() { + ModuleWidget *focusedModuleWidget = nullptr; + if (highlight) { + if (RACK_PLUGIN_UI_HOVERED_WIDGET) { + focusedModuleWidget = dynamic_cast(RACK_PLUGIN_UI_HOVERED_WIDGET); + if (!focusedModuleWidget) + focusedModuleWidget = RACK_PLUGIN_UI_HOVERED_WIDGET->getAncestorOfType(); + } + } + if (focusedModuleWidget != lastHover) { + lastHover = focusedModuleWidget; + highlightIsDirty = true; + } + if (highlightIsDirty) { + highlightIsDirty = false; + for (Widget *widget : RACK_PLUGIN_UI_RACKWIDGET->wireContainer->children) { + WireWidget *wire = dynamic_cast(widget); + if (focusedModuleWidget) { + if (!wire->outputPort || !wire->inputPort) { + wire->color = nvgTransRGBA(wire->color, 0xFF); + } + else if (wire->outputPort->getAncestorOfType() == focusedModuleWidget) { + wire->color = nvgTransRGBA(wire->color, 0xFF); + } + else if (wire->inputPort->getAncestorOfType() == focusedModuleWidget) { + wire->color = nvgTransRGBA(wire->color, 0xFF); + } + else { + wire->color = nvgTransRGBAf(wire->color, highlightSlider->value); + } + } + else { + if (highlight == 2) + wire->color = nvgTransRGBAf(wire->color, highlightSlider->value); + else + wire->color = nvgTransRGBA(wire->color, 0xFF); + } + } + } + } + + void Colors() { + colorWidget->visible = true; + optionWidget->visible = false; + editWidget->visible = false; + deleteWidget->visible = false; + } + + void Options() { + colorWidget->visible = false; + optionWidget->visible = true; + editWidget->visible = false; + deleteWidget->visible = false; + } + + void Edit(WMWireButton *wmb) { + colorWidget->visible = false; + optionWidget->visible = false; + editWidget->visible = true; + deleteWidget->visible = false; + editingColor = wmb; + deleteButton->visible = (wmb != NULL); + varyR->defaultValue = varyR->value = wmb?wmb->color.r:0.5; + varyG->defaultValue = varyG->value = wmb?wmb->color.g:0.5; + varyB->defaultValue = varyB->value = wmb?wmb->color.b:0.5; + } + + void Delete() { + colorWidget->visible = false; + optionWidget->visible = false; + editWidget->visible = false; + deleteWidget->visible = true; + } + + void SetHighlight(int status) { + highlightOff->selected = (highlightOff->status == status); + highlightLow->selected = (highlightLow->status == status); + highlightOn->selected = (highlightOn->status == status); + highlight = status; + highlightIsDirty = true; + } + + void SaveSettings() { + json_t *settings = json_object(); + json_t *arr = json_array(); + auto __begin = std::begin(colorWidget->container->children); + auto __end = std::end(colorWidget->container->children); + for (++__begin; __begin != __end; ++__begin) { + WMWireButton *wb = dynamic_cast(*__begin); + json_t *color = json_object(); + std::string s = colorToHexString(wb->color); + json_object_set_new(color, "color", json_string(s.c_str())); + json_object_set_new(color, "selected", json_real(wb->wmc->selected)); + json_array_append_new(arr, color); + } + json_object_set_new(settings, "colors", arr); + json_object_set_new(settings, "highlight", json_real(highlight)); + json_object_set_new(settings, "highlight_trans", json_real(highlightSlider->value)); + json_object_set_new(settings, "variation", json_real(varyCheck->selected)); + json_object_set_new(settings, "variationH", json_real(varyH->value)); + json_object_set_new(settings, "variationS", json_real(varyS->value)); + json_object_set_new(settings, "variationL", json_real(varyL->value)); + + systemCreateDirectory(assetLocal("SubmarineUtility")); + std::string settingsFilename = assetLocal("SubmarineUtility/WireManager.json"); + FILE *file = fopen(settingsFilename.c_str(), "w"); + if (file) { + json_dumpf(settings, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9)); + fclose(file); + } + json_decref(settings); + } + void LoadSettings() { + json_error_t error; + FILE *file = fopen(assetLocal("SubmarineUtility/WireManager.json").c_str(), "r"); + if (!file) { + SetDefaults(); + return; + } + json_t *rootJ = json_loadf(file, 0, &error); + fclose(file); + if (!rootJ) { + std::string message = stringf("Submarine Utilities Wire Manager: JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text); + warn(message.c_str()); + return; + } + json_t *arr = json_object_get(rootJ, "colors"); + if (arr) { + int size = json_array_size(arr); + for (int i = 0; i < size; i++) { + json_t *j1 = json_array_get(arr, i); + if (j1) { + json_t *c1 = json_object_get(j1, "color"); + if (c1) { + int selected = false; + json_t *s1 = json_object_get(j1, "selected"); + if (s1) { + selected = json_number_value(s1); + } + AddColor(colorFromHexString(json_string_value(c1)), selected); + } + } + } + } + json_t *h1 = json_object_get(rootJ, "highlight"); + if (h1) { + highlight = json_number_value(h1); + SetHighlight(highlight); + } + json_t *t1 = json_object_get(rootJ, "highlight_trans"); + if (t1) { + highlightSlider->value = json_number_value(t1); + } + json_t *v1 = json_object_get(rootJ, "variation"); + if (v1) { + varyCheck->selected = json_number_value(v1); + } + v1 = json_object_get(rootJ, "variationH"); + if (v1) { + varyH->value = json_number_value(v1); + } + v1 = json_object_get(rootJ, "variationS"); + if (v1) { + varyS->value = json_number_value(v1); + } + v1 = json_object_get(rootJ, "variationL"); + if (v1) { + varyL->value = json_number_value(v1); + } + json_decref(rootJ); + } + + void Swap(float x) { + unsigned int i = (unsigned int)x; + if (i < 1) return; + if (i > colorWidget->container->children.size() - 2) return; + auto vi = std::begin(colorWidget->container->children); + std::advance(vi, i); + WMWireButton *wb1 = dynamic_cast(* vi++); + WMWireButton *wb2 = dynamic_cast(* vi); + NVGcolor col = wb1->color; + wb1->color = wb2->color; + wb2->color = col; + int sel = wb1->wmc->selected; + wb1->wmc->selected = wb2->wmc->selected; + wb2->wmc->selected = sel; + SaveSettings(); + } + + void Reflow() { + float pos = 0.0f; + for (Widget *w : colorWidget->container->children) { + w->box.pos.y = pos; + pos += w->box.size.y; + } + } +}; + +void WMWireCheck::onAction(EventAction &e) { + selected = !selected; + wmw->SaveSettings(); +} + +void WMCheckAll::onAction(EventAction &e) { + selected = !selected; + auto __begin = std::begin(wmw->colorWidget->container->children); + auto __end = std::end(wmw->colorWidget->container->children); + for (++__begin; __begin != __end; ++__begin) { + WMWireButton *wb = dynamic_cast(*__begin); + wb->wmc->selected = selected; + } + wmw->SaveSettings(); +} + +void WMSlider::onAction(EventAction &e) { + wmw->SaveSettings(); +} + +// Icon onAction + +void WMMinimizeIcon::onAction(EventAction &e) { + wmw->Minimize(true); +} + +void WMOptionIcon::onAction(EventAction &e) { + wmw->Options(); +} + +void WMColorIcon::onAction(EventAction &e) { + wmw->Colors(); +} + +void WMHighlightButton::onAction(EventAction &e) { + wmw->SetHighlight(status); + wmw->SaveSettings(); +} + +void WMWireEdit::onAction(EventAction &e) { + wmb->wmc->wmw->Edit(wmb); +} + +void WMWireUp::onAction(EventAction &e) { + wmb->wmc->wmw->Swap(wmb->box.pos.y / 21 - 1); +} + +void WMWireDown::onAction(EventAction &e) { + wmb->wmc->wmw->Swap(wmb->box.pos.y / 21); +} + +void WMEditWidget::draw(NVGcontext *vg) { + NVGcolor color = nvgRGBAf(wmw->varyR->value, wmw->varyG->value, wmw->varyB->value, 1.0f); + NVGcolor colorOutline = nvgLerpRGBA(color, nvgRGBf(0.0, 0.0, 0.0), 0.5); + + nvgBeginPath(vg); + nvgMoveTo(vg, 12, 12); + nvgQuadTo(vg, box.size.x / 2, 150, box.size.x - 12, 12); + nvgStrokeColor(vg, colorOutline); + nvgStrokeWidth(vg, 5); + nvgStroke(vg); + + nvgStrokeColor(vg, color); + nvgStrokeWidth(vg, 3); + nvgStroke(vg); + + nvgBeginPath(vg); + nvgCircle(vg, 12, 12, 9); + nvgCircle(vg, box.size.x - 12, 12, 9); + nvgFillColor(vg, color); + nvgFill(vg); + + nvgStrokeWidth(vg, 1.0); + nvgStrokeColor(vg, colorOutline); + nvgStroke(vg); + + nvgBeginPath(vg); + nvgCircle(vg, 12, 12, 5); + nvgCircle(vg, box.size.x - 12, 12, 5); + nvgFillColor(vg, nvgRGBf(0.0, 0.0, 0.0)); + nvgFill(vg); + + float l = wmw->varyR->box.pos.x + 5; + float r = wmw->varyR->box.size.x - 10; + + NVGpaint grad = nvgLinearGradient(vg, l, 109.5, l + r, 109.5, nvgRGBf(0.0f, wmw->varyG->value, wmw->varyB->value), nvgRGBf(1.0f, wmw->varyG->value, wmw->varyB->value)); + nvgBeginPath(vg); + nvgFillPaint(vg, grad); + nvgRect(vg, l - 5, 109.5, r + 10, 10); + nvgFill(vg); + + grad = nvgLinearGradient(vg, l, 149.5, l + r, 149.5, nvgRGBf(wmw->varyR->value, 0.0f, wmw->varyB->value), nvgRGBf(wmw->varyR->value, 1.0f, wmw->varyB->value)); + nvgBeginPath(vg); + nvgFillPaint(vg, grad); + nvgRect(vg, l - 5, 149.5, r + 10, 10); + nvgFill(vg); + + grad = nvgLinearGradient(vg, l, 189.5, l + r, 189.5, nvgRGBf(wmw->varyR->value, wmw->varyG->value, 0.0f), nvgRGBf(wmw->varyR->value, wmw->varyG->value, 1.0f)); + nvgBeginPath(vg); + nvgFillPaint(vg, grad); + nvgRect(vg, l - 5, 189.5, r + 10, 10); + nvgFill(vg); + + VirtualWidget::draw(vg); +} + +void WMDeleteWidget::draw(NVGcontext *vg) { + NVGcolor color = wmw->editingColor?wmw->editingColor->color:nvgRGB(0,0,0); + NVGcolor colorOutline = nvgLerpRGBA(color, nvgRGBf(0.0, 0.0, 0.0), 0.5); + + nvgBeginPath(vg); + nvgMoveTo(vg, 12, 12); + nvgQuadTo(vg, box.size.x / 2, 150, box.size.x - 12, 12); + nvgStrokeColor(vg, colorOutline); + nvgStrokeWidth(vg, 5); + nvgStroke(vg); + + nvgStrokeColor(vg, color); + nvgStrokeWidth(vg, 3); + nvgStroke(vg); + + nvgBeginPath(vg); + nvgCircle(vg, 12, 12, 9); + nvgCircle(vg, box.size.x - 12, 12, 9); + nvgFillColor(vg, color); + nvgFill(vg); + + nvgStrokeWidth(vg, 1.0); + nvgStrokeColor(vg, colorOutline); + nvgStroke(vg); + + nvgBeginPath(vg); + nvgCircle(vg, 12, 12, 5); + nvgCircle(vg, box.size.x - 12, 12, 5); + nvgFillColor(vg, nvgRGBf(0.0, 0.0, 0.0)); + nvgFill(vg); + + VirtualWidget::draw(vg); +} + +void WMSaveButton::onAction(EventAction &e) { + if (wmw->editingColor) { + wmw->editingColor->color = nvgRGBAf(wmw->varyR->value, wmw->varyG->value, wmw->varyB->value, 1.0f); + } + else { + wmw->AddColor(nvgRGBAf(wmw->varyR->value, wmw->varyG->value, wmw->varyB->value, 1.0f), false); + } + wmw->SaveSettings(); + wmw->Colors(); +} + +void WMCancelButton::onAction(EventAction &e) { + wmw->Colors(); +} + +void WMWireAdd::onAction(EventAction &e) { + wmw->Edit(NULL); +} + +void WMDeleteButton::onAction(EventAction &e) { + if (!wmw->editingColor) + return; + wmw->Delete(); +} + +void WMOKButton::onAction(EventAction &e) { + if (!wmw->editingColor) + return; + wmw->colorWidget->container->removeChild(wmw->editingColor); + delete wmw->editingColor; + wmw->Reflow(); + wmw->SaveSettings(); + wmw->Colors(); +} + +} // namespace rack_plugin_SubmarineUtility + +using namespace rack_plugin_SubmarineUtility; + +RACK_PLUGIN_MODEL_INIT(SubmarineUtility, WireManager) { + Model *modelWireManager = Model::create("Submarine (Utilities)", "WireManager", "Wire Manager", UTILITY_TAG); + return modelWireManager; +} diff --git a/plugins/makefile.common b/plugins/makefile.common index 66aa8939..b8d8dc04 100644 --- a/plugins/makefile.common +++ b/plugins/makefile.common @@ -74,6 +74,7 @@ bin: $(call run_make,Southpole-parasites,bin) $(call run_make,squinkylabs-plug1,bin) $(call run_make,SubmarineFree,bin) + $(call run_make,SubmarineUtility,bin) $(call run_make,SynthKit,bin) $(call run_make,Template,bin) $(call run_make,Template_shared,bin) @@ -154,6 +155,7 @@ clean: $(call run_make,Southpole-parasites,clean) $(call run_make,squinkylabs-plug1,clean) $(call run_make,SubmarineFree,clean) + $(call run_make,SubmarineUtility,clean) $(call run_make,SynthKit,clean) $(call run_make,Template,clean) $(call run_make,Template_shared,clean) diff --git a/src/plugin_static.cpp b/src/plugin_static.cpp index 5b4ea71f..3720b86e 100644 --- a/src/plugin_static.cpp +++ b/src/plugin_static.cpp @@ -115,6 +115,7 @@ extern void init_plugin_Southpole (rack::Plugin *p); extern void init_plugin_Southpole_parasites (rack::Plugin *p); extern void init_plugin_squinkylabs_plug1 (rack::Plugin *p); extern void init_plugin_SubmarineFree (rack::Plugin *p); +extern void init_plugin_SubmarineUtility (rack::Plugin *p); extern void init_plugin_SynthKit (rack::Plugin *p); extern void init_plugin_Template (rack::Plugin *p); extern void init_plugin_TheXOR (rack::Plugin *p); @@ -220,6 +221,7 @@ void vst2_load_static_rack_plugins(void) { vst2_load_static_rack_plugin("Southpole_parasites", &init_plugin_Southpole_parasites); vst2_load_static_rack_plugin("squinkylabs-plug1", &init_plugin_squinkylabs_plug1); vst2_load_static_rack_plugin("SubmarineFree", &init_plugin_SubmarineFree); + vst2_load_static_rack_plugin("SubmarineUtility", &init_plugin_SubmarineUtility); vst2_load_static_rack_plugin("SynthKit", &init_plugin_SynthKit); vst2_load_static_rack_plugin("Template", &init_plugin_Template); vst2_load_static_rack_plugin("TheXOR", &init_plugin_TheXOR); diff --git a/vst2_bin/plugins/SubmarineUtility/LICENSE b/vst2_bin/plugins/SubmarineUtility/LICENSE new file mode 100644 index 00000000..e1d30ccb --- /dev/null +++ b/vst2_bin/plugins/SubmarineUtility/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2018, David O'Rourke +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* 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. + +* 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. + +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. diff --git a/vst2_bin/plugins/SubmarineUtility/README.md b/vst2_bin/plugins/SubmarineUtility/README.md new file mode 100644 index 00000000..0c13b594 --- /dev/null +++ b/vst2_bin/plugins/SubmarineUtility/README.md @@ -0,0 +1,19 @@ +# SubmarineUtility +Utility Modules for VCVRack + +Important note: + +These modules rely on knowledge of VCVRack which goes beyond the published API. The ongoing stability of the code is therefore not guaranteed. At least some of the features of these modules rely on code which is expected to change in future versions of VCVRack, and many other parts are likely to change. + +Whilst I intend to try to keep these modules up to date with changes, I can make no guarantees that this will even be possible. + +**Module Browser will definitely need to be somewhat rewritten for VCVRack v1.0. It is my intention to do so as soon as possible.** + +However, these modules are not designed or intended to form any part of your patch behaviour. They are intended purely as workflow tools. If these modules become incompatible with VCVRack in the future, it should be possible to remove them from the patch without affecting the aural content of the patch. + +## [Manual](https://github.com/david-c14/SubmarineUtility/blob/master/manual/index.md) + +## Licence + +This code is licensed under BSD 3-clause and is mostly copyright © 2018 carbon14 (David O'Rourke) 2018 +Some parts of this code are inevitably based directly on code by Andrew Belt within VCVRack itself; Copyright © 2016 Andrew Belt. diff --git a/vst2_bin/plugins/SubmarineUtility/res/Sub1.svg b/vst2_bin/plugins/SubmarineUtility/res/Sub1.svg new file mode 100644 index 00000000..35fc8a9f --- /dev/null +++ b/vst2_bin/plugins/SubmarineUtility/res/Sub1.svg @@ -0,0 +1,17 @@ + + + + diff --git a/vst2_bin/plugins/SubmarineUtility/res/Sub2.svg b/vst2_bin/plugins/SubmarineUtility/res/Sub2.svg new file mode 100644 index 00000000..33cfdcfb --- /dev/null +++ b/vst2_bin/plugins/SubmarineUtility/res/Sub2.svg @@ -0,0 +1,15 @@ + + + + diff --git a/vst2_bin/plugins/SubmarineUtility/res/colors.svg b/vst2_bin/plugins/SubmarineUtility/res/colors.svg new file mode 100644 index 00000000..42ee2749 --- /dev/null +++ b/vst2_bin/plugins/SubmarineUtility/res/colors.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + diff --git a/vst2_bin/plugins/SubmarineUtility/res/favorite.svg b/vst2_bin/plugins/SubmarineUtility/res/favorite.svg new file mode 100644 index 00000000..c9309dd2 --- /dev/null +++ b/vst2_bin/plugins/SubmarineUtility/res/favorite.svg @@ -0,0 +1,44 @@ + + + + + + + + + diff --git a/vst2_bin/plugins/SubmarineUtility/res/hls.svg b/vst2_bin/plugins/SubmarineUtility/res/hls.svg new file mode 100644 index 00000000..7dfcd9d9 --- /dev/null +++ b/vst2_bin/plugins/SubmarineUtility/res/hls.svg @@ -0,0 +1,35 @@ + + + + + + + + + diff --git a/vst2_bin/plugins/SubmarineUtility/res/load.svg b/vst2_bin/plugins/SubmarineUtility/res/load.svg new file mode 100644 index 00000000..081a8916 --- /dev/null +++ b/vst2_bin/plugins/SubmarineUtility/res/load.svg @@ -0,0 +1,39 @@ + + + + + + + + + diff --git a/vst2_bin/plugins/SubmarineUtility/res/min.svg b/vst2_bin/plugins/SubmarineUtility/res/min.svg new file mode 100644 index 00000000..83613973 --- /dev/null +++ b/vst2_bin/plugins/SubmarineUtility/res/min.svg @@ -0,0 +1,33 @@ + + + + + + + + + diff --git a/vst2_bin/plugins/SubmarineUtility/res/plugin.svg b/vst2_bin/plugins/SubmarineUtility/res/plugin.svg new file mode 100644 index 00000000..50d91743 --- /dev/null +++ b/vst2_bin/plugins/SubmarineUtility/res/plugin.svg @@ -0,0 +1,35 @@ + + + + + + + + + diff --git a/vst2_bin/plugins/SubmarineUtility/res/tag.svg b/vst2_bin/plugins/SubmarineUtility/res/tag.svg new file mode 100644 index 00000000..8ae55193 --- /dev/null +++ b/vst2_bin/plugins/SubmarineUtility/res/tag.svg @@ -0,0 +1,34 @@ + + + + + + + + + + diff --git a/vst2_common_staticlibs.mk b/vst2_common_staticlibs.mk index 9a6b0c70..16875331 100644 --- a/vst2_common_staticlibs.mk +++ b/vst2_common_staticlibs.mk @@ -64,6 +64,7 @@ EXTRALIBS+= $(call plugin_lib,Southpole) EXTRALIBS+= $(call plugin_lib,Southpole-parasites) EXTRALIBS+= $(call plugin_lib,squinkylabs-plug1) EXTRALIBS+= $(call plugin_lib,SubmarineFree) +EXTRALIBS+= $(call plugin_lib,SubmarineUtility) EXTRALIBS+= $(call plugin_lib,SynthKit) EXTRALIBS+= $(call plugin_lib,Template) EXTRALIBS+= $(call plugin_lib,TheXOR)