@@ -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); | |||
@@ -0,0 +1,7 @@ | |||
/build | |||
/dist | |||
/plugin.dylib | |||
/plugin.dll | |||
/plugin.so | |||
.DS_Store | |||
/WK_Custom.tunings |
@@ -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. |
@@ -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 | |||
@@ -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. |
@@ -0,0 +1,5 @@ | |||
ALL_OBJ= \ | |||
src/ModBrowser.o \ | |||
src/SubControls.o \ | |||
src/SubmarineUtility.o \ | |||
src/WireManager.o |
@@ -0,0 +1,7 @@ | |||
SLUG=SubmarineUtility | |||
include ../../../build_plugin_pre_linux.mk | |||
include make.objects | |||
include ../../../build_plugin_post_linux.mk |
@@ -0,0 +1,7 @@ | |||
SLUG=SubmarineUtility | |||
include ../../../build_plugin_pre_msvc.mk | |||
include make.objects | |||
include ../../../build_plugin_post_msvc.mk |
@@ -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) |
@@ -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. | |||
###  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. | |||
###  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. | |||
###  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. | |||
##  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 | |||
###  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. |
@@ -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. | |||
##  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. | |||
##  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 | |||
###  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. |
@@ -0,0 +1,17 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<svg | |||
xmlns:svg="http://www.w3.org/2000/svg" | |||
xmlns="http://www.w3.org/2000/svg" | |||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||
height="15px" | |||
width="20px"> | |||
<g | |||
style="font-size:12px;font-family:'DejaVu Sans';fill:#719fcf" | |||
font-size="12" | |||
id="logo"> | |||
<path | |||
id="path4136" | |||
d="M 9.35391,1 A 1.9846094,2.2519786 0 0 0 7.36926,3.25192 1.9846094,2.2519786 0 0 0 7.42806,3.78359 8.1692063,5.1693144 0 0 0 1,8.83071 8.1692063,5.1693144 0 0 0 9.16915,14 8.1692063,5.1693144 0 0 0 16.48278,11.11985 3.0461447,4.2480502 0 0 0 19,12.97641 l 0,-2.35772 A 1.3384576,1.9448905 0 0 1 17.7077,8.67718 1.3384576,1.9448905 0 0 1 19,6.73455 L 19,4.48039 A 3.0461447,4.2480502 0 0 0 16.42587,6.45983 8.1692063,5.1693144 0 0 0 9.16915,3.66141 8.1692063,5.1693144 0 0 0 8.79597,3.67331 1.7538409,2.0472531 0 0 1 9.49235,2.67169 l 0,-1.66609 A 1.9846094,2.2519786 0 0 0 9.35391,1 Z M 6.07684,6.83461 A 1.6615335,1.8425278 0 0 1 7.7384,8.67718 1.6615335,1.8425278 0 0 1 6.07684,10.51975 1.6615335,1.8425278 0 0 1 4.41539,8.67718 1.6615335,1.8425278 0 0 1 6.07684,6.83461 Z M 9.86149,7.2441 A 1.2923038,1.4330772 0 0 1 11.1538,8.67718 1.2923038,1.4330772 0 0 1 9.86149,10.11026 1.2923038,1.4330772 0 0 1 8.56919,8.67718 1.2923038,1.4330772 0 0 1 9.86149,7.2441 Z m 3.00928,0.38892 A 0.92307415,1.0236265 0 0 1 13.79381,8.65675 0.92307415,1.0236265 0 0 1 12.87077,9.68034 0.92307415,1.0236265 0 0 1 11.9476,8.65675 0.92307415,1.0236265 0 0 1 12.87077,7.63302 Z" | |||
inkscape:connector-curvature="0" /> | |||
</g> | |||
</svg> |
@@ -0,0 +1,15 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<svg | |||
xmlns:svg="http://www.w3.org/2000/svg" | |||
xmlns="http://www.w3.org/2000/svg" | |||
height="20px" | |||
width="15px"> | |||
<g | |||
style="font-size:12px;font-family:'DejaVu Sans';fill:#719fcf" | |||
font-size="12" | |||
id="logo"> | |||
<path | |||
id="path4136" | |||
d="M 1,10.64609 A 2.2519786,1.9846094 0 0 0 3.25192,12.63074 2.2519786,1.9846094 0 0 0 3.78359,12.57194 5.1693144,8.1692063 0 0 0 8.83071,19 5.1693144,8.1692063 0 0 0 14,10.83085 5.1693144,8.1692063 0 0 0 11.11985,3.51722 4.2480502,3.0461447 0 0 0 12.97641,1 L 10.61869,1 A 1.9448905,1.3384576 0 0 1 8.67718,2.2923 1.9448905,1.3384576 0 0 1 6.73455,1 L 4.48039,1 a 4.2480502,3.0461447 0 0 0 1.97944,2.57413 5.1693144,8.1692063 0 0 0 -2.79842,7.25672 5.1693144,8.1692063 0 0 0 0.0119,0.37318 2.0472531,1.7538409 0 0 1 -1.00162,-0.69638 l -1.66609,0 A 2.2519786,1.9846094 0 0 0 1,10.64609 Z m 5.83461,3.27707 A 1.8425278,1.6615335 0 0 1 8.67718,12.2616 1.8425278,1.6615335 0 0 1 10.51975,13.92316 1.8425278,1.6615335 0 0 1 8.67718,15.58461 1.8425278,1.6615335 0 0 1 6.83461,13.92316 Z M 7.2441,10.13851 A 1.4330772,1.2923038 0 0 1 8.67718,8.8462 1.4330772,1.2923038 0 0 1 10.11026,10.13851 1.4330772,1.2923038 0 0 1 8.67718,11.43081 1.4330772,1.2923038 0 0 1 7.2441,10.13851 Z M 7.63302,7.12923 A 1.0236265,0.92307415 0 0 1 8.65675,6.20619 1.0236265,0.92307415 0 0 1 9.68034,7.12923 1.0236265,0.92307415 0 0 1 8.65675,8.0524 1.0236265,0.92307415 0 0 1 7.63302,7.12923 Z" /> | |||
</g> | |||
</svg> |
@@ -0,0 +1,28 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<svg | |||
xmlns:svg="http://www.w3.org/2000/svg" | |||
xmlns="http://www.w3.org/2000/svg" | |||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||
width="30px" | |||
height="30px" | |||
version="1.1"> | |||
<g | |||
inkscape:label="Background" | |||
inkscape:groupmode="layer" | |||
id="background"> | |||
<rect | |||
x="0" | |||
y="0" | |||
width="30" | |||
height="30" | |||
rx="3" | |||
ry="3" | |||
style="fill:#404040;stroke:none;" | |||
id="rect4255" /> | |||
</g> | |||
<g> | |||
<circle cx="9" cy="20" r="6" style="fill:#ff0000;" /> | |||
<circle cx="21" cy="20" r="6" style="fill:#0000ff;" /> | |||
<circle cx="15" cy="9.6" r="6" style="fill:#00ff00;" /> | |||
</g> | |||
</svg> |
@@ -0,0 +1,44 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<svg | |||
xmlns:svg="http://www.w3.org/2000/svg" | |||
xmlns="http://www.w3.org/2000/svg" | |||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||
width="30px" | |||
height="30px" | |||
version="1.1"> | |||
<g | |||
inkscape:label="Background" | |||
inkscape:groupmode="layer" | |||
id="background"> | |||
<rect | |||
x="0" | |||
y="0" | |||
width="30" | |||
height="30" | |||
rx="3" | |||
ry="3" | |||
style="fill:#404040;stroke:none;" | |||
id="rect4255" /> | |||
</g> | |||
<g | |||
stroke="#ffffff" | |||
fill="none" | |||
stroke-linecap="round" | |||
stroke-linejoin="round" | |||
stroke-width="2"> | |||
<path d=" | |||
M 15 2 | |||
L 20.878 7.91 | |||
L 28.315 11.6738 | |||
L 24.511 19.090 | |||
L 23.8168 27.3262 | |||
L 15 26 | |||
L 6.1832 27.3262 | |||
L 5.489 19.090 | |||
L 1.6852 11.6738 | |||
L 9.122 7.91 | |||
z | |||
" /> | |||
</g> | |||
</svg> |
@@ -0,0 +1,35 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<svg | |||
xmlns:svg="http://www.w3.org/2000/svg" | |||
xmlns="http://www.w3.org/2000/svg" | |||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||
width="30px" | |||
height="30px" | |||
version="1.1"> | |||
<g | |||
inkscape:label="Background" | |||
inkscape:groupmode="layer" | |||
id="background"> | |||
<rect | |||
x="0" | |||
y="0" | |||
width="30" | |||
height="30" | |||
rx="3" | |||
ry="3" | |||
style="fill:#404040;stroke:none;" | |||
id="rect4255" /> | |||
</g> | |||
<g | |||
stroke="#ffffff" | |||
fill="none" | |||
stroke-linecap="round" | |||
stroke-linejoin="round" | |||
stroke-width="2"> | |||
<path d=" | |||
M 3 6 h 24 m -20 0 m 0 -4 v 8 | |||
M 3 15 h 24 m -6 0 m 0 -4 v 8 | |||
M 3 24 h 24 m -13 0 m 0 -4 v 8 | |||
" /> | |||
</g> | |||
</svg> |
@@ -0,0 +1,39 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<svg | |||
xmlns:svg="http://www.w3.org/2000/svg" | |||
xmlns="http://www.w3.org/2000/svg" | |||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||
width="30px" | |||
height="30px" | |||
version="1.1"> | |||
<g | |||
inkscape:label="Background" | |||
inkscape:groupmode="layer" | |||
id="background"> | |||
<rect | |||
x="0" | |||
y="0" | |||
width="30" | |||
height="30" | |||
rx="3" | |||
ry="3" | |||
style="fill:#404040;stroke:none;" | |||
id="rect4255" /> | |||
</g> | |||
<g | |||
stroke="#ffffff" | |||
fill="none" | |||
stroke-linecap="round" | |||
stroke-linejoin="round" | |||
stroke-width="2"> | |||
<path d=" | |||
M 3 2 l 19 0 l 5 5 l 0 21 l -24 0 l 0 -26 | |||
M 8 2 l 0 10 l 13 0 l 0 -10 | |||
M 12 5 l 0 4 | |||
M 7 28 l 0 -12 l 16 0 l 0 12 | |||
M 10 24 l 6 0 | |||
M 10 21 l 9 0 | |||
" /> | |||
</g> | |||
</svg> |
@@ -0,0 +1,33 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<svg | |||
xmlns:svg="http://www.w3.org/2000/svg" | |||
xmlns="http://www.w3.org/2000/svg" | |||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||
width="30px" | |||
height="30px" | |||
version="1.1"> | |||
<g | |||
inkscape:label="Background" | |||
inkscape:groupmode="layer" | |||
id="background"> | |||
<rect | |||
x="0" | |||
y="0" | |||
width="30" | |||
height="30" | |||
rx="3" | |||
ry="3" | |||
style="fill:#404040;stroke:none;" | |||
id="rect4255" /> | |||
</g> | |||
<g | |||
stroke="#ffffff" | |||
fill="none" | |||
stroke-linecap="round" | |||
stroke-linejoin="round" | |||
stroke-width="2"> | |||
<path d=" | |||
M 21 3 l 0 24 l -14 -12 l 14 -12 | |||
" /> | |||
</g> | |||
</svg> |
@@ -0,0 +1,35 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<svg | |||
xmlns:svg="http://www.w3.org/2000/svg" | |||
xmlns="http://www.w3.org/2000/svg" | |||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||
width="30px" | |||
height="30px" | |||
version="1.1"> | |||
<g | |||
inkscape:label="Background" | |||
inkscape:groupmode="layer" | |||
id="background"> | |||
<rect | |||
x="0" | |||
y="0" | |||
width="30" | |||
height="30" | |||
rx="3" | |||
ry="3" | |||
style="fill:#404040;stroke:none;" | |||
id="rect4255" /> | |||
</g> | |||
<g | |||
stroke="#ffffff" | |||
fill="none" | |||
stroke-linecap="round" | |||
stroke-width="2"> | |||
<path d="M 3 27 l 6 -6 | |||
M 13 9 l -4 4 a 3 3 0 0 0 8 8 l 4 -4 | |||
M 12 8 l 10 10 | |||
M 15 11 l 5 -5 | |||
M 19 15 l 5 -5 | |||
" /> | |||
</g> | |||
</svg> |
@@ -0,0 +1,34 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<svg | |||
xmlns:svg="http://www.w3.org/2000/svg" | |||
xmlns="http://www.w3.org/2000/svg" | |||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||
width="30px" | |||
height="30px" | |||
version="1.1"> | |||
<g | |||
inkscape:label="Background" | |||
inkscape:groupmode="layer" | |||
id="background"> | |||
<rect | |||
x="0" | |||
y="0" | |||
width="30" | |||
height="30" | |||
rx="3" | |||
ry="3" | |||
style="fill:#404040;stroke:none;" | |||
id="rect4255" /> | |||
</g> | |||
<g | |||
stroke="#ffffff" | |||
fill="none" | |||
stroke-linecap="round" | |||
stroke-linejoin="round" | |||
stroke-width="2"> | |||
<path d=" | |||
M 3 3 l 10 0 l 14 14 l -10 10 l -14 -14 z | |||
" /> | |||
<circle cx="9" cy="9" r="2.5" /> | |||
</g> | |||
</svg> |
@@ -0,0 +1,721 @@ | |||
#include "SubControls.hpp" | |||
#include <map> | |||
#include <algorithm> | |||
#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<ListElement> 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<std::shared_ptr<PluginElement>> pluginList; | |||
std::list<std::shared_ptr<TagElement>> tagList; | |||
std::list<std::shared_ptr<ModelElement>> 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<PluginIcon>(Vec(2, 2)); | |||
pluginIcon->selected = 1; | |||
pluginIcon->mbw = this; | |||
pluginIcon->setSVG(SVG::load(assetPlugin(plugin, "res/plugin.svg"))); | |||
backPanel->addChild(pluginIcon); | |||
tagIcon = Widget::create<TagIcon>(Vec(34, 2)); | |||
tagIcon->mbw = this; | |||
tagIcon->setSVG(SVG::load(assetPlugin(plugin, "res/tag.svg"))); | |||
backPanel->addChild(tagIcon); | |||
favIcon = Widget::create<FavIcon>(Vec(66, 2)); | |||
favIcon->mbw = this; | |||
favIcon->setSVG(SVG::load(assetPlugin(plugin, "res/favorite.svg"))); | |||
backPanel->addChild(favIcon); | |||
loadIcon = Widget::create<LoadIcon>(Vec(98, 2)); | |||
loadIcon->mbw = this; | |||
loadIcon->setSVG(SVG::load(assetPlugin(plugin, "res/load.svg"))); | |||
backPanel->addChild(loadIcon); | |||
minimizeIcon = Widget::create<MinimizeIcon>(Vec(130, 2)); | |||
minimizeIcon->mbw = this; | |||
minimizeIcon->setSVG(SVG::load(assetPlugin(plugin, "res/min.svg"))); | |||
backPanel->addChild(minimizeIcon); | |||
scrollWidget = Widget::create<ScrollWidget>(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<TagElement> te = std::make_shared<TagElement>(); | |||
te->mbw = this; | |||
te->tag = i; | |||
tagList.push_back(te); | |||
} | |||
// Sort Tags (probably already sorted) | |||
tagList.sort([](std::shared_ptr<TagElement> te1, std::shared_ptr<TagElement> 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<ModelElement> me = std::make_shared<ModelElement>(); | |||
me->mbw = this; | |||
me->model = model; | |||
modelList.push_back(me); | |||
int found = false; | |||
for (std::shared_ptr<PluginElement> pe : pluginList) { | |||
if (!pe->label.compare(me->model->author)) { | |||
found = true; | |||
break; | |||
} | |||
} | |||
if (!found) { | |||
std::shared_ptr<PluginElement> pe = std::make_shared<PluginElement>(); | |||
pe->mbw = this; | |||
pe->label.assign(me->model->author); | |||
pluginList.push_back(pe); | |||
} | |||
} | |||
} | |||
// Sort Plugins/Authors | |||
pluginList.sort([](std::shared_ptr<PluginElement> pe1, std::shared_ptr<PluginElement> 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<ListElement> le, float y) { | |||
TextButton *tb = Widget::create<TextButton>(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<PluginElement> pe : pluginList) { | |||
AddElement(pe, y); | |||
y += 15; | |||
} | |||
SetListWidth(); | |||
} | |||
void AddTags() { | |||
scrollContainer->clearChildren(); | |||
unsigned int y = 0; | |||
for (std::shared_ptr<TagElement> 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<ModelElement> 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<PluginBackElement> pbe = std::make_shared<PluginBackElement>(); | |||
pbe->mbw = this; | |||
pbe->label2 = author; | |||
AddElement(pbe, 0); | |||
unsigned int y = 15; | |||
for (std::shared_ptr<ModelElement> me : modelList) { | |||
if (!me->model->author.compare(author)) { | |||
AddElement(me, y); | |||
y += 15; | |||
} | |||
} | |||
SetListWidth(); | |||
} | |||
void AddModels(unsigned int tag) { | |||
scrollContainer->clearChildren(); | |||
std::shared_ptr<TagBackElement> tbe = std::make_shared<TagBackElement>(); | |||
tbe->mbw = this; | |||
tbe->label2 = gTagNames[tag]; | |||
AddElement(tbe, 0); | |||
unsigned int y = 15; | |||
for (std::shared_ptr<ModelElement> 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<int, ModuleWidget *> moduleWidgets; | |||
json_t *modulesJ = json_object_get(rootJ, "modules"); | |||
if (!modulesJ) { | |||
LoadPreset(rootJ); | |||
return; | |||
} | |||
std::vector<Widget *> 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<Widget *> existingWidgets, Rect box) { | |||
int x0 = roundf(box.pos.x / RACK_GRID_WIDTH); | |||
int y0 = roundf(box.pos.y / RACK_GRID_HEIGHT); | |||
std::vector<Vec> 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<ModelElement> 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<ModelElement> 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<Module, ModBrowserWidget>("Submarine (Utilities)", "ModBrowser", "Module Browser", UTILITY_TAG); | |||
return modelModBrowser; | |||
} |
@@ -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<std::shared_ptr<RowShift>> rows; | |||
Widget *baseWidget; | |||
unsigned int addRow(Vec position) { | |||
// Search for row in existing list | |||
for (std::shared_ptr<RowShift> 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<RowShift> row = std::make_shared<RowShift>(); | |||
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<RowShift> 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<RowShift> 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<RowShift> 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<RowShift> 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<ModuleDragHandle>(Vec(box.size.x - 10, 175)); | |||
handle->smw = this; | |||
addChild(handle); | |||
backPanel = Widget::create<BackPanel>(Vec(10, 15)); | |||
backPanel->box.size.x = box.size.x - 20; | |||
backPanel->box.size.y = box.size.y - 30; | |||
addChild(backPanel); | |||
minimizeLogo = Widget::create<SubLogo>(Vec(0,0)); | |||
#define plugin "SubmarineUtility" | |||
minimizeLogo->setSVG(SVG::load(assetPlugin(plugin, "res/Sub2.svg"))); | |||
minimizeLogo->visible = false; | |||
addChild(minimizeLogo); | |||
maximizeLogo = Widget::create<SubLogo>(Vec(moduleWidth - 20, 365)); | |||
maximizeLogo->setSVG(SVG::load(assetPlugin(plugin, "res/Sub1.svg"))); | |||
addChild(maximizeLogo); | |||
maximizeButton = Widget::create<MaximizeButton>(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 |
@@ -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 = 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 |
@@ -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); | |||
} |
@@ -0,0 +1,9 @@ | |||
#include "rack.hpp" | |||
using namespace rack; | |||
RACK_PLUGIN_DECLARE(SubmarineUtility); | |||
#ifdef USE_VST2 | |||
#define plugin "SubmarineUtility" | |||
#endif // USE_VST2 |
@@ -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) | |||
@@ -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); | |||
@@ -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. |
@@ -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. |
@@ -0,0 +1,17 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<svg | |||
xmlns:svg="http://www.w3.org/2000/svg" | |||
xmlns="http://www.w3.org/2000/svg" | |||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||
height="15px" | |||
width="20px"> | |||
<g | |||
style="font-size:12px;font-family:'DejaVu Sans';fill:#719fcf" | |||
font-size="12" | |||
id="logo"> | |||
<path | |||
id="path4136" | |||
d="M 9.35391,1 A 1.9846094,2.2519786 0 0 0 7.36926,3.25192 1.9846094,2.2519786 0 0 0 7.42806,3.78359 8.1692063,5.1693144 0 0 0 1,8.83071 8.1692063,5.1693144 0 0 0 9.16915,14 8.1692063,5.1693144 0 0 0 16.48278,11.11985 3.0461447,4.2480502 0 0 0 19,12.97641 l 0,-2.35772 A 1.3384576,1.9448905 0 0 1 17.7077,8.67718 1.3384576,1.9448905 0 0 1 19,6.73455 L 19,4.48039 A 3.0461447,4.2480502 0 0 0 16.42587,6.45983 8.1692063,5.1693144 0 0 0 9.16915,3.66141 8.1692063,5.1693144 0 0 0 8.79597,3.67331 1.7538409,2.0472531 0 0 1 9.49235,2.67169 l 0,-1.66609 A 1.9846094,2.2519786 0 0 0 9.35391,1 Z M 6.07684,6.83461 A 1.6615335,1.8425278 0 0 1 7.7384,8.67718 1.6615335,1.8425278 0 0 1 6.07684,10.51975 1.6615335,1.8425278 0 0 1 4.41539,8.67718 1.6615335,1.8425278 0 0 1 6.07684,6.83461 Z M 9.86149,7.2441 A 1.2923038,1.4330772 0 0 1 11.1538,8.67718 1.2923038,1.4330772 0 0 1 9.86149,10.11026 1.2923038,1.4330772 0 0 1 8.56919,8.67718 1.2923038,1.4330772 0 0 1 9.86149,7.2441 Z m 3.00928,0.38892 A 0.92307415,1.0236265 0 0 1 13.79381,8.65675 0.92307415,1.0236265 0 0 1 12.87077,9.68034 0.92307415,1.0236265 0 0 1 11.9476,8.65675 0.92307415,1.0236265 0 0 1 12.87077,7.63302 Z" | |||
inkscape:connector-curvature="0" /> | |||
</g> | |||
</svg> |
@@ -0,0 +1,15 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<svg | |||
xmlns:svg="http://www.w3.org/2000/svg" | |||
xmlns="http://www.w3.org/2000/svg" | |||
height="20px" | |||
width="15px"> | |||
<g | |||
style="font-size:12px;font-family:'DejaVu Sans';fill:#719fcf" | |||
font-size="12" | |||
id="logo"> | |||
<path | |||
id="path4136" | |||
d="M 1,10.64609 A 2.2519786,1.9846094 0 0 0 3.25192,12.63074 2.2519786,1.9846094 0 0 0 3.78359,12.57194 5.1693144,8.1692063 0 0 0 8.83071,19 5.1693144,8.1692063 0 0 0 14,10.83085 5.1693144,8.1692063 0 0 0 11.11985,3.51722 4.2480502,3.0461447 0 0 0 12.97641,1 L 10.61869,1 A 1.9448905,1.3384576 0 0 1 8.67718,2.2923 1.9448905,1.3384576 0 0 1 6.73455,1 L 4.48039,1 a 4.2480502,3.0461447 0 0 0 1.97944,2.57413 5.1693144,8.1692063 0 0 0 -2.79842,7.25672 5.1693144,8.1692063 0 0 0 0.0119,0.37318 2.0472531,1.7538409 0 0 1 -1.00162,-0.69638 l -1.66609,0 A 2.2519786,1.9846094 0 0 0 1,10.64609 Z m 5.83461,3.27707 A 1.8425278,1.6615335 0 0 1 8.67718,12.2616 1.8425278,1.6615335 0 0 1 10.51975,13.92316 1.8425278,1.6615335 0 0 1 8.67718,15.58461 1.8425278,1.6615335 0 0 1 6.83461,13.92316 Z M 7.2441,10.13851 A 1.4330772,1.2923038 0 0 1 8.67718,8.8462 1.4330772,1.2923038 0 0 1 10.11026,10.13851 1.4330772,1.2923038 0 0 1 8.67718,11.43081 1.4330772,1.2923038 0 0 1 7.2441,10.13851 Z M 7.63302,7.12923 A 1.0236265,0.92307415 0 0 1 8.65675,6.20619 1.0236265,0.92307415 0 0 1 9.68034,7.12923 1.0236265,0.92307415 0 0 1 8.65675,8.0524 1.0236265,0.92307415 0 0 1 7.63302,7.12923 Z" /> | |||
</g> | |||
</svg> |
@@ -0,0 +1,28 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<svg | |||
xmlns:svg="http://www.w3.org/2000/svg" | |||
xmlns="http://www.w3.org/2000/svg" | |||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||
width="30px" | |||
height="30px" | |||
version="1.1"> | |||
<g | |||
inkscape:label="Background" | |||
inkscape:groupmode="layer" | |||
id="background"> | |||
<rect | |||
x="0" | |||
y="0" | |||
width="30" | |||
height="30" | |||
rx="3" | |||
ry="3" | |||
style="fill:#404040;stroke:none;" | |||
id="rect4255" /> | |||
</g> | |||
<g> | |||
<circle cx="9" cy="20" r="6" style="fill:#ff0000;" /> | |||
<circle cx="21" cy="20" r="6" style="fill:#0000ff;" /> | |||
<circle cx="15" cy="9.6" r="6" style="fill:#00ff00;" /> | |||
</g> | |||
</svg> |
@@ -0,0 +1,44 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<svg | |||
xmlns:svg="http://www.w3.org/2000/svg" | |||
xmlns="http://www.w3.org/2000/svg" | |||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||
width="30px" | |||
height="30px" | |||
version="1.1"> | |||
<g | |||
inkscape:label="Background" | |||
inkscape:groupmode="layer" | |||
id="background"> | |||
<rect | |||
x="0" | |||
y="0" | |||
width="30" | |||
height="30" | |||
rx="3" | |||
ry="3" | |||
style="fill:#404040;stroke:none;" | |||
id="rect4255" /> | |||
</g> | |||
<g | |||
stroke="#ffffff" | |||
fill="none" | |||
stroke-linecap="round" | |||
stroke-linejoin="round" | |||
stroke-width="2"> | |||
<path d=" | |||
M 15 2 | |||
L 20.878 7.91 | |||
L 28.315 11.6738 | |||
L 24.511 19.090 | |||
L 23.8168 27.3262 | |||
L 15 26 | |||
L 6.1832 27.3262 | |||
L 5.489 19.090 | |||
L 1.6852 11.6738 | |||
L 9.122 7.91 | |||
z | |||
" /> | |||
</g> | |||
</svg> |
@@ -0,0 +1,35 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<svg | |||
xmlns:svg="http://www.w3.org/2000/svg" | |||
xmlns="http://www.w3.org/2000/svg" | |||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||
width="30px" | |||
height="30px" | |||
version="1.1"> | |||
<g | |||
inkscape:label="Background" | |||
inkscape:groupmode="layer" | |||
id="background"> | |||
<rect | |||
x="0" | |||
y="0" | |||
width="30" | |||
height="30" | |||
rx="3" | |||
ry="3" | |||
style="fill:#404040;stroke:none;" | |||
id="rect4255" /> | |||
</g> | |||
<g | |||
stroke="#ffffff" | |||
fill="none" | |||
stroke-linecap="round" | |||
stroke-linejoin="round" | |||
stroke-width="2"> | |||
<path d=" | |||
M 3 6 h 24 m -20 0 m 0 -4 v 8 | |||
M 3 15 h 24 m -6 0 m 0 -4 v 8 | |||
M 3 24 h 24 m -13 0 m 0 -4 v 8 | |||
" /> | |||
</g> | |||
</svg> |
@@ -0,0 +1,39 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<svg | |||
xmlns:svg="http://www.w3.org/2000/svg" | |||
xmlns="http://www.w3.org/2000/svg" | |||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||
width="30px" | |||
height="30px" | |||
version="1.1"> | |||
<g | |||
inkscape:label="Background" | |||
inkscape:groupmode="layer" | |||
id="background"> | |||
<rect | |||
x="0" | |||
y="0" | |||
width="30" | |||
height="30" | |||
rx="3" | |||
ry="3" | |||
style="fill:#404040;stroke:none;" | |||
id="rect4255" /> | |||
</g> | |||
<g | |||
stroke="#ffffff" | |||
fill="none" | |||
stroke-linecap="round" | |||
stroke-linejoin="round" | |||
stroke-width="2"> | |||
<path d=" | |||
M 3 2 l 19 0 l 5 5 l 0 21 l -24 0 l 0 -26 | |||
M 8 2 l 0 10 l 13 0 l 0 -10 | |||
M 12 5 l 0 4 | |||
M 7 28 l 0 -12 l 16 0 l 0 12 | |||
M 10 24 l 6 0 | |||
M 10 21 l 9 0 | |||
" /> | |||
</g> | |||
</svg> |
@@ -0,0 +1,33 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<svg | |||
xmlns:svg="http://www.w3.org/2000/svg" | |||
xmlns="http://www.w3.org/2000/svg" | |||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||
width="30px" | |||
height="30px" | |||
version="1.1"> | |||
<g | |||
inkscape:label="Background" | |||
inkscape:groupmode="layer" | |||
id="background"> | |||
<rect | |||
x="0" | |||
y="0" | |||
width="30" | |||
height="30" | |||
rx="3" | |||
ry="3" | |||
style="fill:#404040;stroke:none;" | |||
id="rect4255" /> | |||
</g> | |||
<g | |||
stroke="#ffffff" | |||
fill="none" | |||
stroke-linecap="round" | |||
stroke-linejoin="round" | |||
stroke-width="2"> | |||
<path d=" | |||
M 21 3 l 0 24 l -14 -12 l 14 -12 | |||
" /> | |||
</g> | |||
</svg> |
@@ -0,0 +1,35 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<svg | |||
xmlns:svg="http://www.w3.org/2000/svg" | |||
xmlns="http://www.w3.org/2000/svg" | |||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||
width="30px" | |||
height="30px" | |||
version="1.1"> | |||
<g | |||
inkscape:label="Background" | |||
inkscape:groupmode="layer" | |||
id="background"> | |||
<rect | |||
x="0" | |||
y="0" | |||
width="30" | |||
height="30" | |||
rx="3" | |||
ry="3" | |||
style="fill:#404040;stroke:none;" | |||
id="rect4255" /> | |||
</g> | |||
<g | |||
stroke="#ffffff" | |||
fill="none" | |||
stroke-linecap="round" | |||
stroke-width="2"> | |||
<path d="M 3 27 l 6 -6 | |||
M 13 9 l -4 4 a 3 3 0 0 0 8 8 l 4 -4 | |||
M 12 8 l 10 10 | |||
M 15 11 l 5 -5 | |||
M 19 15 l 5 -5 | |||
" /> | |||
</g> | |||
</svg> |
@@ -0,0 +1,34 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<svg | |||
xmlns:svg="http://www.w3.org/2000/svg" | |||
xmlns="http://www.w3.org/2000/svg" | |||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||
width="30px" | |||
height="30px" | |||
version="1.1"> | |||
<g | |||
inkscape:label="Background" | |||
inkscape:groupmode="layer" | |||
id="background"> | |||
<rect | |||
x="0" | |||
y="0" | |||
width="30" | |||
height="30" | |||
rx="3" | |||
ry="3" | |||
style="fill:#404040;stroke:none;" | |||
id="rect4255" /> | |||
</g> | |||
<g | |||
stroke="#ffffff" | |||
fill="none" | |||
stroke-linecap="round" | |||
stroke-linejoin="round" | |||
stroke-width="2"> | |||
<path d=" | |||
M 3 3 l 10 0 l 14 14 l -10 10 l -14 -14 z | |||
" /> | |||
<circle cx="9" cy="9" r="2.5" /> | |||
</g> | |||
</svg> |
@@ -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) | |||