@@ -6,6 +6,7 @@ Tip: Use `git checkout v0.3.2` for example to check out any previous version men | |||
- Added sub-menus for each plugin, includes optional plugin metadata like URLs | |||
- Added new scrolling methods: middle-click-and-drag, shift-scroll, and arrow keys | |||
- Added engine pausing in sample rate menu | |||
- Added resizable blank to Core | |||
- Support for AMD Phenom II processors | |||
- Use self-contained Mac app bundle, no need for a Rack folder | |||
@@ -35,28 +35,30 @@ struct ModuleWidget : OpaqueWidget { | |||
~ModuleWidget(); | |||
void setModule(Module *module); | |||
// Convenience functions for adding special widgets (calls addChild()) | |||
/** Convenience functions for adding special widgets (calls addChild()) */ | |||
void addInput(Port *input); | |||
void addOutput(Port *output); | |||
void addParam(ParamWidget *param); | |||
json_t *toJson(); | |||
void fromJson(json_t *root); | |||
/** Disconnects cables from all ports */ | |||
void disconnect(); | |||
/** Resets the state of the module */ | |||
void initialize(); | |||
/** Randomizes the state of the module | |||
This method just randomizes parameters. Override and call this function if your module contains other state information that you wish to randomize. | |||
virtual json_t *toJson(); | |||
virtual void fromJson(json_t *rootJ); | |||
/** Disconnects cables from all ports | |||
Called when the user clicks Disconnect Cables in the context menu. | |||
*/ | |||
virtual void disconnect(); | |||
/** Resets the parameters of the module and calls the Module's randomize(). | |||
Called when the user clicks Initialize in the context menu. | |||
*/ | |||
void randomize(); | |||
virtual void initialize(); | |||
/** Randomizes the parameters of the module and calls the Module's randomize(). | |||
Called when the user clicks Randomize in the context menu. | |||
*/ | |||
virtual void randomize(); | |||
virtual Menu *createContextMenu(); | |||
void draw(NVGcontext *vg); | |||
bool requested = false; | |||
Vec requestedPos; | |||
Vec dragPos; | |||
Widget *onMouseMove(Vec pos, Vec mouseRel); | |||
Widget *onHoverKey(Vec pos, int key); | |||
@@ -118,14 +120,16 @@ struct RackWidget : OpaqueWidget { | |||
void savePatch(std::string filename); | |||
void loadPatch(std::string filename); | |||
json_t *toJson(); | |||
void fromJson(json_t *root); | |||
void fromJson(json_t *rootJ); | |||
void addModule(ModuleWidget *m); | |||
/** Transfers ownership to the caller so they must `delete` it if that is the intension */ | |||
void deleteModule(ModuleWidget *m); | |||
void cloneModule(ModuleWidget *m); | |||
/** Sets a module's box if non-colliding. Returns true if set */ | |||
bool requestModuleBox(ModuleWidget *m, Rect box); | |||
/** Moves a module to the closest non-colliding position */ | |||
void repositionModule(ModuleWidget *m); | |||
bool requestModuleBoxNearest(ModuleWidget *m, Rect box); | |||
void step(); | |||
void draw(NVGcontext *vg); | |||
@@ -138,7 +142,6 @@ struct RackRail : TransparentWidget { | |||
struct Panel : TransparentWidget { | |||
NVGcolor backgroundColor; | |||
NVGcolor borderColor; | |||
std::shared_ptr<Image> backgroundImage; | |||
void draw(NVGcontext *vg); | |||
}; | |||
@@ -167,7 +170,7 @@ struct ParamWidget : OpaqueWidget, QuantityWidget { | |||
int paramId; | |||
json_t *toJson(); | |||
void fromJson(json_t *root); | |||
void fromJson(json_t *rootJ); | |||
virtual void randomize(); | |||
void onMouseDownOpaque(int button); | |||
void onChange(); | |||
@@ -617,14 +617,12 @@ struct LightPanel : Panel { | |||
LightPanel() { | |||
// backgroundColor = nvgRGB(0xe6, 0xe6, 0xe6); | |||
backgroundColor = nvgRGB(0xf0, 0xf0, 0xf0); | |||
borderColor = nvgRGB(0xac, 0xac, 0xac); | |||
} | |||
}; | |||
struct DarkPanel : Panel { | |||
DarkPanel() { | |||
backgroundColor = nvgRGB(0x17, 0x17, 0x17); | |||
borderColor = nvgRGB(0x5e, 0x5e, 0x5e); | |||
} | |||
}; | |||
@@ -55,7 +55,7 @@ struct Module { | |||
virtual json_t *toJson() { return NULL; } | |||
virtual void fromJson(json_t *root) {} | |||
/** Override these to implement behavior when user clicks Initialize and Randomize */ | |||
/** Override these to implement spacial behavior when user clicks Initialize and Randomize */ | |||
virtual void initialize() {} | |||
virtual void randomize() {} | |||
}; | |||
@@ -6,6 +6,7 @@ | |||
namespace rack { | |||
ModuleWidget::~ModuleWidget() { | |||
// Make sure WireWidget destructors are called *before* removing `module` from the rack. | |||
disconnect(); | |||
@@ -49,8 +50,8 @@ json_t *ModuleWidget::toJson() { | |||
// model | |||
json_object_set_new(rootJ, "model", json_string(model->slug.c_str())); | |||
// pos | |||
json_t *pos = json_pack("[f, f]", (double) box.pos.x, (double) box.pos.y); | |||
json_object_set_new(rootJ, "pos", pos); | |||
json_t *posJ = json_pack("[f, f]", (double) box.pos.x, (double) box.pos.y); | |||
json_object_set_new(rootJ, "pos", posJ); | |||
// params | |||
json_t *paramsJ = json_array(); | |||
for (ParamWidget *paramWidget : params) { | |||
@@ -70,12 +71,10 @@ json_t *ModuleWidget::toJson() { | |||
} | |||
void ModuleWidget::fromJson(json_t *rootJ) { | |||
initialize(); | |||
// pos | |||
json_t *pos = json_object_get(rootJ, "pos"); | |||
json_t *posJ = json_object_get(rootJ, "pos"); | |||
double x, y; | |||
json_unpack(pos, "[F, F]", &x, &y); | |||
json_unpack(posJ, "[F, F]", &x, &y); | |||
box.pos = Vec(x, y); | |||
// params | |||
@@ -197,8 +196,9 @@ void ModuleWidget::onDragStart() { | |||
} | |||
void ModuleWidget::onDragMove(Vec mouseRel) { | |||
requestedPos = gMousePos.minus(parent->getAbsolutePos()).minus(dragPos); | |||
requested = true; | |||
Rect newBox = box; | |||
newBox.pos = gMousePos.minus(parent->getAbsolutePos()).minus(dragPos); | |||
gRackWidget->requestModuleBoxNearest(this, newBox); | |||
} | |||
void ModuleWidget::onDragEnd() { | |||
@@ -3,13 +3,16 @@ | |||
namespace rack { | |||
void Panel::draw(NVGcontext *vg) { | |||
nvgBeginPath(vg); | |||
nvgRect(vg, 0.0, 0.0, box.size.x, box.size.y); | |||
// Background color | |||
nvgFillColor(vg, backgroundColor); | |||
nvgFill(vg); | |||
if (backgroundColor.a > 0) { | |||
nvgFillColor(vg, backgroundColor); | |||
nvgFill(vg); | |||
} | |||
// Background image | |||
if (backgroundImage) { | |||
@@ -21,11 +24,14 @@ void Panel::draw(NVGcontext *vg) { | |||
} | |||
// Border | |||
NVGcolor borderColor = nvgRGBAf(0.5, 0.5, 0.5, 0.5); | |||
nvgBeginPath(vg); | |||
nvgRect(vg, 0.5, 0.5, box.size.x - 1, box.size.y - 1); | |||
nvgRect(vg, 0.5, 0.5, box.size.x - 1.0, box.size.y - 1.0); | |||
nvgStrokeColor(vg, borderColor); | |||
nvgStrokeWidth(vg, 1.0); | |||
nvgStroke(vg); | |||
Widget::draw(vg); | |||
} | |||
} // namespace rack |
@@ -261,15 +261,27 @@ void RackWidget::cloneModule(ModuleWidget *m) { | |||
json_t *moduleJ = m->toJson(); | |||
clonedModuleWidget->fromJson(moduleJ); | |||
json_decref(moduleJ); | |||
clonedModuleWidget->requestedPos = m->box.pos; | |||
clonedModuleWidget->requested = true; | |||
Rect clonedBox = clonedModuleWidget->box; | |||
clonedBox.pos = m->box.pos; | |||
requestModuleBoxNearest(clonedModuleWidget, clonedBox); | |||
addModule(clonedModuleWidget); | |||
} | |||
void RackWidget::repositionModule(ModuleWidget *m) { | |||
bool RackWidget::requestModuleBox(ModuleWidget *m, Rect box) { | |||
for (Widget *child2 : moduleContainer->children) { | |||
if (m == child2) continue; | |||
if (box.intersects(child2->box)) { | |||
return false; | |||
} | |||
} | |||
m->box = box; | |||
return true; | |||
} | |||
bool RackWidget::requestModuleBoxNearest(ModuleWidget *m, Rect box) { | |||
// Create possible positions | |||
int x0 = roundf(m->requestedPos.x / RACK_GRID_WIDTH); | |||
int y0 = roundf(m->requestedPos.y / RACK_GRID_HEIGHT); | |||
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 = maxi(0, y0 - 4); y < y0 + 4; y++) { | |||
for (int x = maxi(0, x0 - 200); x < x0 + 200; x++) { | |||
@@ -278,27 +290,18 @@ void RackWidget::repositionModule(ModuleWidget *m) { | |||
} | |||
// Sort possible positions by distance to the requested position | |||
Vec requestedPos = m->requestedPos; | |||
std::sort(positions.begin(), positions.end(), [requestedPos](Vec a, Vec b) { | |||
return a.minus(requestedPos).norm() < b.minus(requestedPos).norm(); | |||
std::sort(positions.begin(), positions.end(), [box](Vec a, Vec b) { | |||
return a.minus(box.pos).norm() < b.minus(box.pos).norm(); | |||
}); | |||
// Find a position that does not collide | |||
for (Vec pos : positions) { | |||
Rect newBox = Rect(pos, m->box.size); | |||
bool collides = false; | |||
for (Widget *child2 : moduleContainer->children) { | |||
if (m == child2) continue; | |||
if (newBox.intersects(child2->box)) { | |||
collides = true; | |||
break; | |||
} | |||
} | |||
if (collides) continue; | |||
m->box.pos = pos; | |||
break; | |||
for (Vec position : positions) { | |||
Rect newBox = box; | |||
newBox.pos = position; | |||
if (requestModuleBox(m, newBox)) | |||
return true; | |||
} | |||
return false; | |||
} | |||
void RackWidget::step() { | |||
@@ -309,16 +312,6 @@ void RackWidget::step() { | |||
// We assume that the size is reset by a parent before calling step(). Otherwise it will grow unbounded. | |||
box.size = box.size.max(moduleSize); | |||
// Reposition modules | |||
for (Widget *child : moduleContainer->children) { | |||
ModuleWidget *module = dynamic_cast<ModuleWidget*>(child); | |||
assert(module); | |||
if (module->requested) { | |||
repositionModule(module); | |||
module->requested = false; | |||
} | |||
} | |||
// Autosave every 15 seconds | |||
if (gGuiFrame % (60*15) == 0) { | |||
savePatch(assetLocal("autosave.vcv")); | |||
@@ -344,9 +337,12 @@ struct AddModuleMenuItem : MenuItem { | |||
Vec modulePos; | |||
void onAction() { | |||
ModuleWidget *moduleWidget = model->createModuleWidget(); | |||
moduleWidget->requestedPos = modulePos.minus(moduleWidget->box.getCenter()); | |||
moduleWidget->requested = true; | |||
gRackWidget->moduleContainer->addChild(moduleWidget); | |||
// Move module nearest to the mouse position | |||
Rect box; | |||
box.size = moduleWidget->box.size; | |||
box.pos = modulePos.minus(box.getCenter()); | |||
gRackWidget->requestModuleBoxNearest(moduleWidget, box); | |||
} | |||
}; | |||
@@ -6,11 +6,7 @@ namespace rack { | |||
struct PanelBorder : TransparentWidget { | |||
void draw(NVGcontext *vg) { | |||
nvgBeginPath(vg); | |||
nvgRect(vg, 0.0, 0.0, box.size.x, box.size.y); | |||
NVGcolor borderColor = nvgRGBAf(0.5, 0.5, 0.5, 0.5); | |||
nvgBeginPath(vg); | |||
nvgRect(vg, 0.5, 0.5, box.size.x - 1.0, box.size.y - 1.0); | |||
nvgStrokeColor(vg, borderColor); | |||
@@ -21,6 +17,8 @@ struct PanelBorder : TransparentWidget { | |||
void SVGPanel::setBackground(std::shared_ptr<SVG> svg) { | |||
clearChildren(); | |||
SVGWidget *sw = new SVGWidget(); | |||
sw->wrap(); | |||
sw->svg = svg; | |||
@@ -0,0 +1,109 @@ | |||
#include "core.hpp" | |||
using namespace rack; | |||
struct ModuleResizeHandle : Widget { | |||
bool right = false; | |||
float originalWidth; | |||
float totalX; | |||
ModuleResizeHandle() { | |||
box.size = Vec(RACK_GRID_WIDTH * 1, RACK_GRID_HEIGHT); | |||
} | |||
Widget *onMouseDown(Vec pos, int button) { | |||
if (button == 0) | |||
return this; | |||
return NULL; | |||
} | |||
void onDragStart() { | |||
assert(parent); | |||
originalWidth = parent->box.size.x; | |||
totalX = 0.0; | |||
} | |||
void onDragMove(Vec mouseRel) { | |||
ModuleWidget *m = dynamic_cast<ModuleWidget*>(parent); | |||
assert(m); | |||
totalX += mouseRel.x; | |||
float targetWidth = originalWidth; | |||
if (right) | |||
targetWidth += totalX; | |||
else | |||
targetWidth -= totalX; | |||
targetWidth = RACK_GRID_WIDTH * roundf(targetWidth / RACK_GRID_WIDTH); | |||
targetWidth = fmaxf(targetWidth, RACK_GRID_WIDTH * 3); | |||
Rect newBox = m->box; | |||
newBox.size.x = targetWidth; | |||
if (!right) { | |||
newBox.pos.x = m->box.pos.x + m->box.size.x - newBox.size.x; | |||
} | |||
gRackWidget->requestModuleBox(m, newBox); | |||
} | |||
void draw(NVGcontext *vg) { | |||
for (float x = 5.0; x <= 10.0; x += 5.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); | |||
} | |||
} | |||
}; | |||
BlankWidget::BlankWidget() { | |||
box.size = Vec(RACK_GRID_WIDTH * 10, RACK_GRID_HEIGHT); | |||
{ | |||
panel = new LightPanel(); | |||
panel->box.size = box.size; | |||
addChild(panel); | |||
} | |||
ModuleResizeHandle *leftHandle = new ModuleResizeHandle(); | |||
ModuleResizeHandle *rightHandle = new ModuleResizeHandle(); | |||
rightHandle->right = true; | |||
this->rightHandle = rightHandle; | |||
addChild(leftHandle); | |||
addChild(rightHandle); | |||
addChild(createScrew<ScrewSilver>(Vec(15, 0))); | |||
addChild(createScrew<ScrewSilver>(Vec(15, 365))); | |||
topRightScrew = createScrew<ScrewSilver>(Vec(box.size.x - 30, 0)); | |||
bottomRightScrew = createScrew<ScrewSilver>(Vec(box.size.x - 30, 365)); | |||
addChild(topRightScrew); | |||
addChild(bottomRightScrew); | |||
} | |||
void BlankWidget::step() { | |||
panel->box.size = box.size; | |||
topRightScrew->box.pos.x = box.size.x - 30; | |||
bottomRightScrew->box.pos.x = box.size.x - 30; | |||
if (box.size.x < RACK_GRID_WIDTH * 6) { | |||
topRightScrew->visible = bottomRightScrew->visible = false; | |||
} | |||
else { | |||
topRightScrew->visible = bottomRightScrew->visible = true; | |||
} | |||
rightHandle->box.pos.x = box.size.x - rightHandle->box.size.x; | |||
ModuleWidget::step(); | |||
} | |||
json_t *BlankWidget::toJson() { | |||
json_t *rootJ = ModuleWidget::toJson(); | |||
// // width | |||
json_object_set_new(rootJ, "width", json_real(box.size.x)); | |||
return rootJ; | |||
} | |||
void BlankWidget::fromJson(json_t *rootJ) { | |||
ModuleWidget::fromJson(rootJ); | |||
// width | |||
json_t *widthJ = json_object_get(rootJ, "width"); | |||
if (widthJ) | |||
box.size.x = json_number_value(widthJ); | |||
} |
@@ -8,4 +8,5 @@ void init(rack::Plugin *plugin) { | |||
createModel<AudioInterfaceWidget>(plugin, "AudioInterface", "Audio Interface"); | |||
createModel<MidiInterfaceWidget>(plugin, "MidiInterface", "MIDI Interface"); | |||
// createModel<BridgeWidget>(plugin, "Bridge", "Bridge"); | |||
createModel<BlankWidget>(plugin, "Blank", "Blank"); | |||
} |
@@ -20,3 +20,14 @@ struct MidiInterfaceWidget : ModuleWidget { | |||
struct BridgeWidget : ModuleWidget { | |||
BridgeWidget(); | |||
}; | |||
struct BlankWidget : ModuleWidget { | |||
Panel *panel; | |||
Widget *topRightScrew; | |||
Widget *bottomRightScrew; | |||
Widget *rightHandle; | |||
BlankWidget(); | |||
void step(); | |||
json_t *toJson(); | |||
void fromJson(json_t *rootJ); | |||
}; |