@@ -11,6 +11,7 @@ namespace rack { | |||||
struct Knob : ParamWidget { | struct Knob : ParamWidget { | ||||
/** Multiplier for mouse movement to adjust knob value */ | /** Multiplier for mouse movement to adjust knob value */ | ||||
float speed = 1.0; | float speed = 1.0; | ||||
float oldValue = 0.f; | |||||
void onButton(const event::Button &e) override; | void onButton(const event::Button &e) override; | ||||
void onDragStart(const event::DragStart &e) override; | void onDragStart(const event::DragStart &e) override; | ||||
@@ -22,6 +22,7 @@ struct ModuleWidget : OpaqueWidget { | |||||
std::vector<PortWidget*> outputs; | std::vector<PortWidget*> outputs; | ||||
/** For RackWidget dragging */ | /** For RackWidget dragging */ | ||||
math::Vec dragPos; | math::Vec dragPos; | ||||
math::Vec oldPos; | |||||
ModuleWidget(Module *module); | ModuleWidget(Module *module); | ||||
~ModuleWidget(); | ~ModuleWidget(); | ||||
@@ -2,14 +2,15 @@ | |||||
#include "app/common.hpp" | #include "app/common.hpp" | ||||
#include "widgets/OpaqueWidget.hpp" | #include "widgets/OpaqueWidget.hpp" | ||||
#include "ui/Tooltip.hpp" | #include "ui/Tooltip.hpp" | ||||
#include "ui/Quantity.hpp" | |||||
#include "app/ParamQuantity.hpp" | |||||
#include "history.hpp" | |||||
namespace rack { | namespace rack { | ||||
struct ParamWidget : OpaqueWidget { | struct ParamWidget : OpaqueWidget { | ||||
Quantity *quantity = NULL; | |||||
ParamQuantity *paramQuantity = NULL; | |||||
float dirtyValue = NAN; | float dirtyValue = NAN; | ||||
Tooltip *tooltip = NULL; | Tooltip *tooltip = NULL; | ||||
@@ -44,12 +44,13 @@ struct RackWidget : OpaqueWidget { | |||||
void addModule(ModuleWidget *m); | void addModule(ModuleWidget *m); | ||||
void addModuleAtMouse(ModuleWidget *m); | void addModuleAtMouse(ModuleWidget *m); | ||||
/** Removes the module and transfers ownership to the caller */ | /** Removes the module and transfers ownership to the caller */ | ||||
void deleteModule(ModuleWidget *m); | |||||
void removeModule(ModuleWidget *m); | |||||
void cloneModule(ModuleWidget *m); | void cloneModule(ModuleWidget *m); | ||||
/** Sets a module's box if non-colliding. Returns true if set */ | /** Sets a module's box if non-colliding. Returns true if set */ | ||||
bool requestModuleBox(ModuleWidget *m, math::Rect box); | bool requestModuleBox(ModuleWidget *m, math::Rect box); | ||||
/** Moves a module to the closest non-colliding position */ | /** Moves a module to the closest non-colliding position */ | ||||
bool requestModuleBoxNearest(ModuleWidget *m, math::Rect box); | bool requestModuleBoxNearest(ModuleWidget *m, math::Rect box); | ||||
ModuleWidget *getModule(int moduleId); | |||||
void step() override; | void step() override; | ||||
void draw(NVGcontext *vg) override; | void draw(NVGcontext *vg) override; | ||||
@@ -60,7 +60,7 @@ TParamWidget *createParam(math::Vec pos, Module *module, int paramId) { | |||||
ParamQuantity *q = new ParamQuantity; | ParamQuantity *q = new ParamQuantity; | ||||
q->module = module; | q->module = module; | ||||
q->paramId = paramId; | q->paramId = paramId; | ||||
o->quantity = q; | |||||
o->paramQuantity = q; | |||||
return o; | return o; | ||||
} | } | ||||
@@ -71,7 +71,7 @@ TParamWidget *createParamCentered(math::Vec pos, Module *module, int paramId) { | |||||
ParamQuantity *q = new ParamQuantity; | ParamQuantity *q = new ParamQuantity; | ||||
q->module = module; | q->module = module; | ||||
q->paramId = paramId; | q->paramId = paramId; | ||||
o->quantity = q; | |||||
o->paramQuantity = q; | |||||
return o; | return o; | ||||
} | } | ||||
@@ -3,6 +3,7 @@ | |||||
#include "math.hpp" | #include "math.hpp" | ||||
#include "plugin/Model.hpp" | #include "plugin/Model.hpp" | ||||
#include <vector> | #include <vector> | ||||
#include <jansson.h> | |||||
namespace rack { | namespace rack { | ||||
@@ -16,15 +17,95 @@ struct Action { | |||||
}; | }; | ||||
struct ModuleAdd : Action { | |||||
Model *model; | |||||
/** An action operating on a module | |||||
Subclass this to create your own custom actions for your module. | |||||
*/ | |||||
struct ModuleAction : Action { | |||||
int moduleId; | int moduleId; | ||||
}; | |||||
struct ModuleAdd : ModuleAction { | |||||
Model *model; | |||||
math::Vec pos; | math::Vec pos; | ||||
void undo() override; | void undo() override; | ||||
void redo() override; | void redo() override; | ||||
}; | }; | ||||
struct ModuleRemove : ModuleAction { | |||||
Model *model; | |||||
math::Vec pos; | |||||
json_t *moduleJ; | |||||
struct WireInfo { | |||||
int wireId; | |||||
int outputModuleId; | |||||
int outputId; | |||||
int inputModuleId; | |||||
int inputId; | |||||
}; | |||||
std::vector<WireInfo> wireInfos; | |||||
~ModuleRemove(); | |||||
void undo() override; | |||||
void redo() override; | |||||
}; | |||||
struct ModuleMove : ModuleAction { | |||||
math::Vec oldPos; | |||||
math::Vec newPos; | |||||
void undo() override; | |||||
void redo() override; | |||||
}; | |||||
struct ParamChange : ModuleAction { | |||||
int paramId; | |||||
float oldValue; | |||||
float newValue; | |||||
void undo() override; | |||||
void redo() override; | |||||
}; | |||||
struct WireAdd : Action { | |||||
int wireId; | |||||
int outputModuleId; | |||||
int outputId; | |||||
int inputModuleId; | |||||
int inputId; | |||||
void undo() override; | |||||
void redo() override; | |||||
}; | |||||
struct WireRemove : Action { | |||||
int wireId; | |||||
int outputModuleId; | |||||
int outputId; | |||||
int inputModuleId; | |||||
int inputId; | |||||
void undo() override; | |||||
void redo() override; | |||||
}; | |||||
struct WireMove : Action { | |||||
int wireId; | |||||
int oldOutputModuleId; | |||||
int oldOutputId; | |||||
int oldInputModuleId; | |||||
int oldInputId; | |||||
int newOutputModuleId; | |||||
int newOutputId; | |||||
int newInputModuleId; | |||||
int newInputId; | |||||
void undo() override; | |||||
void redo() override; | |||||
}; | |||||
struct State { | struct State { | ||||
std::vector<Action*> actions; | std::vector<Action*> actions; | ||||
@@ -1,4 +1,7 @@ | |||||
#include "app/Knob.hpp" | #include "app/Knob.hpp" | ||||
#include "app.hpp" | |||||
#include "app/Scene.hpp" | |||||
#include "history.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -17,18 +20,34 @@ void Knob::onButton(const event::Button &e) { | |||||
} | } | ||||
void Knob::onDragStart(const event::DragStart &e) { | void Knob::onDragStart(const event::DragStart &e) { | ||||
if (paramQuantity) | |||||
oldValue = paramQuantity->getValue(); | |||||
app()->window->cursorLock(); | app()->window->cursorLock(); | ||||
} | } | ||||
void Knob::onDragEnd(const event::DragEnd &e) { | void Knob::onDragEnd(const event::DragEnd &e) { | ||||
app()->window->cursorUnlock(); | app()->window->cursorUnlock(); | ||||
if (paramQuantity) { | |||||
float newValue = paramQuantity->getValue(); | |||||
if (oldValue != newValue) { | |||||
// Push ParamChange history action | |||||
history::ParamChange *h = new history::ParamChange; | |||||
h->moduleId = paramQuantity->module->id; | |||||
h->paramId = paramQuantity->paramId; | |||||
h->oldValue = oldValue; | |||||
h->newValue = newValue; | |||||
app()->history->push(h); | |||||
} | |||||
} | |||||
} | } | ||||
void Knob::onDragMove(const event::DragMove &e) { | void Knob::onDragMove(const event::DragMove &e) { | ||||
if (quantity) { | |||||
if (paramQuantity) { | |||||
float range; | float range; | ||||
if (quantity->isBounded()) { | |||||
range = quantity->getRange(); | |||||
if (paramQuantity->isBounded()) { | |||||
range = paramQuantity->getRange(); | |||||
} | } | ||||
else { | else { | ||||
// Continuous encoders scale as if their limits are +/-1 | // Continuous encoders scale as if their limits are +/-1 | ||||
@@ -39,7 +58,7 @@ void Knob::onDragMove(const event::DragMove &e) { | |||||
// Drag slower if Mod is held | // Drag slower if Mod is held | ||||
if (app()->window->isModPressed()) | if (app()->window->isModPressed()) | ||||
delta /= 16.f; | delta /= 16.f; | ||||
quantity->moveValue(delta); | |||||
paramQuantity->moveValue(delta); | |||||
} | } | ||||
ParamWidget::onDragMove(e); | ParamWidget::onDragMove(e); | ||||
@@ -8,6 +8,7 @@ | |||||
#include "app/Scene.hpp" | #include "app/Scene.hpp" | ||||
#include "plugin.hpp" | #include "plugin.hpp" | ||||
#include "app.hpp" | #include "app.hpp" | ||||
#include "history.hpp" | |||||
#include <set> | #include <set> | ||||
#include <algorithm> | #include <algorithm> | ||||
@@ -78,12 +79,19 @@ struct ModuleBox : OpaqueWidget { | |||||
// Create module | // Create module | ||||
ModuleWidget *moduleWidget = model->createModuleWidget(); | ModuleWidget *moduleWidget = model->createModuleWidget(); | ||||
assert(moduleWidget); | assert(moduleWidget); | ||||
app()->scene->rackWidget->addModule(moduleWidget); | |||||
app()->scene->rackWidget->addModuleAtMouse(moduleWidget); | |||||
// This is a bit nonstandard/unsupported usage, but pretend the moduleWidget was clicked so it can be dragged in the RackWidget | // This is a bit nonstandard/unsupported usage, but pretend the moduleWidget was clicked so it can be dragged in the RackWidget | ||||
e.consume(moduleWidget); | |||||
// e.consume(moduleWidget); | |||||
// Close Module Browser | // Close Module Browser | ||||
ModuleBrowser *moduleBrowser = getAncestorOfType<ModuleBrowser>(); | ModuleBrowser *moduleBrowser = getAncestorOfType<ModuleBrowser>(); | ||||
moduleBrowser->visible = false; | moduleBrowser->visible = false; | ||||
// Push ModuleAdd history action | |||||
history::ModuleAdd *h = new history::ModuleAdd; | |||||
h->model = moduleWidget->model; | |||||
h->moduleId = moduleWidget->module->id; | |||||
h->pos = moduleWidget->box.pos; | |||||
app()->history->push(h); | |||||
} | } | ||||
OpaqueWidget::onButton(e); | OpaqueWidget::onButton(e); | ||||
} | } | ||||
@@ -7,6 +7,7 @@ | |||||
#include "helpers.hpp" | #include "helpers.hpp" | ||||
#include "app.hpp" | #include "app.hpp" | ||||
#include "settings.hpp" | #include "settings.hpp" | ||||
#include "history.hpp" | |||||
#include "osdialog.h" | #include "osdialog.h" | ||||
@@ -15,23 +16,12 @@ namespace rack { | |||||
ModuleWidget::ModuleWidget(Module *module) { | ModuleWidget::ModuleWidget(Module *module) { | ||||
if (module) { | |||||
app()->engine->addModule(module); | |||||
} | |||||
this->module = module; | this->module = module; | ||||
} | } | ||||
ModuleWidget::~ModuleWidget() { | ModuleWidget::~ModuleWidget() { | ||||
// HACK | |||||
// If we try to disconnect wires in the Module Browser (e.g. when Rack is closed while the Module Browser is open), app()->scene->rackWidget will be an invalid pointer. | |||||
// So only attempt to disconnect if the module is not NULL. | |||||
if (module) | |||||
disconnect(); | |||||
// Remove and delete the Module instance | |||||
if (module) { | if (module) { | ||||
app()->engine->removeModule(module); | |||||
delete module; | delete module; | ||||
module = NULL; | |||||
} | } | ||||
} | } | ||||
@@ -327,13 +317,26 @@ void ModuleWidget::drawShadow(NVGcontext *vg) { | |||||
nvgFill(vg); | nvgFill(vg); | ||||
} | } | ||||
static void ModuleWidget_removeAction(ModuleWidget *moduleWidget) { | |||||
// Push ModuleRemove history action | |||||
history::ModuleRemove *h = new history::ModuleRemove; | |||||
h->model = moduleWidget->model; | |||||
h->moduleId = moduleWidget->module->id; | |||||
h->pos = moduleWidget->box.pos; | |||||
h->moduleJ = moduleWidget->toJson(); | |||||
app()->history->push(h); | |||||
app()->scene->rackWidget->removeModule(moduleWidget); | |||||
delete moduleWidget; | |||||
} | |||||
void ModuleWidget::onHover(const event::Hover &e) { | void ModuleWidget::onHover(const event::Hover &e) { | ||||
OpaqueWidget::onHover(e); | OpaqueWidget::onHover(e); | ||||
// Instead of checking key-down events, delete the module even if key-repeat hasn't fired yet and the cursor is hovering over the widget. | // Instead of checking key-down events, delete the module even if key-repeat hasn't fired yet and the cursor is hovering over the widget. | ||||
if (glfwGetKey(app()->window->win, GLFW_KEY_DELETE) == GLFW_PRESS || glfwGetKey(app()->window->win, GLFW_KEY_BACKSPACE) == GLFW_PRESS) { | if (glfwGetKey(app()->window->win, GLFW_KEY_DELETE) == GLFW_PRESS || glfwGetKey(app()->window->win, GLFW_KEY_BACKSPACE) == GLFW_PRESS) { | ||||
if (!app()->window->isModPressed() && !app()->window->isShiftPressed()) { | if (!app()->window->isModPressed() && !app()->window->isShiftPressed()) { | ||||
requestedDelete = true; | |||||
ModuleWidget_removeAction(this); | |||||
return; | return; | ||||
} | } | ||||
} | } | ||||
@@ -401,10 +404,19 @@ void ModuleWidget::onHoverKey(const event::HoverKey &e) { | |||||
} | } | ||||
void ModuleWidget::onDragStart(const event::DragStart &e) { | void ModuleWidget::onDragStart(const event::DragStart &e) { | ||||
oldPos = box.pos; | |||||
dragPos = app()->scene->rackWidget->lastMousePos.minus(box.pos); | dragPos = app()->scene->rackWidget->lastMousePos.minus(box.pos); | ||||
} | } | ||||
void ModuleWidget::onDragEnd(const event::DragEnd &e) { | void ModuleWidget::onDragEnd(const event::DragEnd &e) { | ||||
if (!box.pos.isEqual(oldPos)) { | |||||
// Push ModuleMove history action | |||||
history::ModuleMove *h = new history::ModuleMove; | |||||
h->moduleId = module->id; | |||||
h->oldPos = oldPos; | |||||
h->newPos = box.pos; | |||||
app()->history->push(h); | |||||
} | |||||
} | } | ||||
void ModuleWidget::onDragMove(const event::DragMove &e) { | void ModuleWidget::onDragMove(const event::DragMove &e) { | ||||
@@ -526,8 +538,7 @@ struct ModuleDeleteItem : MenuItem { | |||||
rightText = "Backspace/Delete"; | rightText = "Backspace/Delete"; | ||||
} | } | ||||
void onAction(const event::Action &e) override { | void onAction(const event::Action &e) override { | ||||
app()->scene->rackWidget->deleteModule(moduleWidget); | |||||
delete moduleWidget; | |||||
ModuleWidget_removeAction(moduleWidget); | |||||
} | } | ||||
}; | }; | ||||
@@ -5,14 +5,14 @@ namespace rack { | |||||
void MomentarySwitch::onDragStart(const event::DragStart &e) { | void MomentarySwitch::onDragStart(const event::DragStart &e) { | ||||
if (quantity) { | |||||
quantity->setMax(); | |||||
if (paramQuantity) { | |||||
paramQuantity->setMax(); | |||||
} | } | ||||
} | } | ||||
void MomentarySwitch::onDragEnd(const event::DragEnd &e) { | void MomentarySwitch::onDragEnd(const event::DragEnd &e) { | ||||
if (quantity) { | |||||
quantity->setMin(); | |||||
if (paramQuantity) { | |||||
paramQuantity->setMin(); | |||||
} | } | ||||
} | } | ||||
@@ -6,6 +6,7 @@ | |||||
#include "app.hpp" | #include "app.hpp" | ||||
#include "settings.hpp" | #include "settings.hpp" | ||||
#include "random.hpp" | #include "random.hpp" | ||||
#include "history.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -21,15 +22,27 @@ struct ParamField : TextField { | |||||
void setParamWidget(ParamWidget *paramWidget) { | void setParamWidget(ParamWidget *paramWidget) { | ||||
this->paramWidget = paramWidget; | this->paramWidget = paramWidget; | ||||
if (paramWidget->quantity) | |||||
text = paramWidget->quantity->getDisplayValueString(); | |||||
if (paramWidget->paramQuantity) | |||||
text = paramWidget->paramQuantity->getDisplayValueString(); | |||||
selectAll(); | selectAll(); | ||||
} | } | ||||
void onSelectKey(const event::SelectKey &e) override { | void onSelectKey(const event::SelectKey &e) override { | ||||
if (e.action == GLFW_PRESS && (e.key == GLFW_KEY_ENTER || e.key == GLFW_KEY_KP_ENTER)) { | if (e.action == GLFW_PRESS && (e.key == GLFW_KEY_ENTER || e.key == GLFW_KEY_KP_ENTER)) { | ||||
if (paramWidget->quantity) | |||||
paramWidget->quantity->setDisplayValueString(text); | |||||
float oldValue = paramWidget->paramQuantity->getValue(); | |||||
if (paramWidget->paramQuantity) | |||||
paramWidget->paramQuantity->setDisplayValueString(text); | |||||
float newValue = paramWidget->paramQuantity->getValue(); | |||||
if (oldValue != newValue) { | |||||
// Push ParamChange history action | |||||
history::ParamChange *h = new history::ParamChange; | |||||
h->moduleId = paramWidget->paramQuantity->module->id; | |||||
h->paramId = paramWidget->paramQuantity->paramId; | |||||
h->oldValue = oldValue; | |||||
h->newValue = newValue; | |||||
app()->history->push(h); | |||||
} | |||||
MenuOverlay *overlay = getAncestorOfType<MenuOverlay>(); | MenuOverlay *overlay = getAncestorOfType<MenuOverlay>(); | ||||
overlay->requestedDelete = true; | overlay->requestedDelete = true; | ||||
@@ -49,14 +62,14 @@ struct ParamField : TextField { | |||||
ParamWidget::~ParamWidget() { | ParamWidget::~ParamWidget() { | ||||
if (quantity) | |||||
delete quantity; | |||||
if (paramQuantity) | |||||
delete paramQuantity; | |||||
} | } | ||||
void ParamWidget::step() { | void ParamWidget::step() { | ||||
if (quantity) { | |||||
float value = quantity->getValue(); | |||||
// Trigger change event when quantity value changes | |||||
if (paramQuantity) { | |||||
float value = paramQuantity->getValue(); | |||||
// Trigger change event when paramQuantity value changes | |||||
if (value != dirtyValue) { | if (value != dirtyValue) { | ||||
dirtyValue = value; | dirtyValue = value; | ||||
event::Change eChange; | event::Change eChange; | ||||
@@ -65,13 +78,10 @@ void ParamWidget::step() { | |||||
} | } | ||||
if (tooltip) { | if (tooltip) { | ||||
// Quantity string | |||||
if (quantity) { | |||||
tooltip->text = quantity->getString(); | |||||
} | |||||
// Param description | |||||
ParamQuantity *paramQuantity = dynamic_cast<ParamQuantity*>(quantity); | |||||
if (paramQuantity) { | if (paramQuantity) { | ||||
// Quantity string | |||||
tooltip->text = paramQuantity->getString(); | |||||
// Param description | |||||
std::string description = paramQuantity->getParam()->description; | std::string description = paramQuantity->getParam()->description; | ||||
if (!description.empty()) | if (!description.empty()) | ||||
tooltip->text += "\n" + description; | tooltip->text += "\n" + description; | ||||
@@ -86,18 +96,31 @@ void ParamWidget::step() { | |||||
void ParamWidget::fromJson(json_t *rootJ) { | void ParamWidget::fromJson(json_t *rootJ) { | ||||
json_t *valueJ = json_object_get(rootJ, "value"); | json_t *valueJ = json_object_get(rootJ, "value"); | ||||
if (valueJ) { | if (valueJ) { | ||||
if (quantity) | |||||
quantity->setValue(json_number_value(valueJ)); | |||||
if (paramQuantity) | |||||
paramQuantity->setValue(json_number_value(valueJ)); | |||||
} | } | ||||
} | } | ||||
void ParamWidget::onButton(const event::Button &e) { | void ParamWidget::onButton(const event::Button &e) { | ||||
// Right click to reset | // Right click to reset | ||||
if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT && !(e.mods & WINDOW_MOD) && !(e.mods & GLFW_MOD_SHIFT)) { | if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT && !(e.mods & WINDOW_MOD) && !(e.mods & GLFW_MOD_SHIFT)) { | ||||
if (quantity) | |||||
quantity->reset(); | |||||
if (paramQuantity) { | |||||
float oldValue = paramQuantity->getValue(); | |||||
paramQuantity->reset(); | |||||
float newValue = paramQuantity->getValue(); | |||||
if (oldValue != newValue) { | |||||
// Push ParamChange history action | |||||
history::ParamChange *h = new history::ParamChange; | |||||
h->moduleId = paramQuantity->module->id; | |||||
h->paramId = paramQuantity->paramId; | |||||
h->oldValue = oldValue; | |||||
h->newValue = newValue; | |||||
app()->history->push(h); | |||||
} | |||||
} | |||||
// Here's another way of doing it, but either works. | // Here's another way of doing it, but either works. | ||||
// dynamic_cast<ParamQuantity*>(quantity)->getParam()->reset(); | |||||
// paramQuantity->getParam()->reset(); | |||||
e.consume(this); | e.consume(this); | ||||
} | } | ||||
@@ -9,6 +9,7 @@ | |||||
#include "asset.hpp" | #include "asset.hpp" | ||||
#include "system.hpp" | #include "system.hpp" | ||||
#include "plugin.hpp" | #include "plugin.hpp" | ||||
#include "engine/Engine.hpp" | |||||
#include "app.hpp" | #include "app.hpp" | ||||
@@ -53,12 +54,19 @@ RackWidget::RackWidget() { | |||||
} | } | ||||
RackWidget::~RackWidget() { | RackWidget::~RackWidget() { | ||||
clear(); | |||||
} | } | ||||
void RackWidget::clear() { | void RackWidget::clear() { | ||||
wireContainer->activeWire = NULL; | wireContainer->activeWire = NULL; | ||||
wireContainer->clearChildren(); | wireContainer->clearChildren(); | ||||
moduleContainer->clearChildren(); | |||||
// Remove ModuleWidgets | |||||
std::list<Widget*> widgets = moduleContainer->children; | |||||
for (Widget *w : widgets) { | |||||
ModuleWidget *moduleWidget = dynamic_cast<ModuleWidget*>(w); | |||||
assert(moduleWidget); | |||||
removeModule(moduleWidget); | |||||
} | |||||
app()->scene->scrollWidget->offset = math::Vec(0, 0); | app()->scene->scrollWidget->offset = math::Vec(0, 0); | ||||
} | } | ||||
@@ -455,17 +463,32 @@ void RackWidget::pastePresetClipboard() { | |||||
} | } | ||||
void RackWidget::addModule(ModuleWidget *m) { | void RackWidget::addModule(ModuleWidget *m) { | ||||
// Add module to ModuleContainer | |||||
assert(m); | |||||
assert(m->module); | |||||
moduleContainer->addChild(m); | moduleContainer->addChild(m); | ||||
// Add module to Engine | |||||
app()->engine->addModule(m->module); | |||||
} | } | ||||
void RackWidget::addModuleAtMouse(ModuleWidget *m) { | void RackWidget::addModuleAtMouse(ModuleWidget *m) { | ||||
addModule(m); | |||||
assert(m); | |||||
// Move module nearest to the mouse position | // Move module nearest to the mouse position | ||||
m->box.pos = lastMousePos.minus(m->box.size.div(2)); | m->box.pos = lastMousePos.minus(m->box.size.div(2)); | ||||
requestModuleBoxNearest(m, m->box); | requestModuleBoxNearest(m, m->box); | ||||
addModule(m); | |||||
} | } | ||||
void RackWidget::deleteModule(ModuleWidget *m) { | |||||
void RackWidget::removeModule(ModuleWidget *m) { | |||||
// Disconnect wires | |||||
m->disconnect(); | |||||
// Remove module from Engine | |||||
assert(m->module); | |||||
app()->engine->removeModule(m->module); | |||||
// Remove module from ModuleContainer | |||||
moduleContainer->removeChild(m); | moduleContainer->removeChild(m); | ||||
} | } | ||||
@@ -496,8 +519,8 @@ bool RackWidget::requestModuleBox(ModuleWidget *m, math::Rect box) { | |||||
bool RackWidget::requestModuleBoxNearest(ModuleWidget *m, math::Rect box) { | bool RackWidget::requestModuleBoxNearest(ModuleWidget *m, math::Rect box) { | ||||
// Create possible positions | // Create possible positions | ||||
int x0 = roundf(box.pos.x / RACK_GRID_WIDTH); | |||||
int y0 = roundf(box.pos.y / RACK_GRID_HEIGHT); | |||||
int x0 = std::round(box.pos.x / RACK_GRID_WIDTH); | |||||
int y0 = std::round(box.pos.y / RACK_GRID_HEIGHT); | |||||
std::vector<math::Vec> positions; | std::vector<math::Vec> positions; | ||||
for (int y = std::max(0, y0 - 8); y < y0 + 8; y++) { | for (int y = std::max(0, y0 - 8); y < y0 + 8; y++) { | ||||
for (int x = std::max(0, x0 - 400); x < x0 + 400; x++) { | for (int x = std::max(0, x0 - 400); x < x0 + 400; x++) { | ||||
@@ -520,6 +543,16 @@ bool RackWidget::requestModuleBoxNearest(ModuleWidget *m, math::Rect box) { | |||||
return false; | return false; | ||||
} | } | ||||
ModuleWidget *RackWidget::getModule(int moduleId) { | |||||
for (Widget *w : moduleContainer->children) { | |||||
ModuleWidget *moduleWidget = dynamic_cast<ModuleWidget*>(w); | |||||
assert(moduleWidget); | |||||
if (moduleWidget->module->id == moduleId) | |||||
return moduleWidget; | |||||
} | |||||
return NULL; | |||||
} | |||||
void RackWidget::step() { | void RackWidget::step() { | ||||
// Expand size to fit modules | // Expand size to fit modules | ||||
math::Vec moduleSize = moduleContainer->getChildrenBoundingBox().getBottomRight(); | math::Vec moduleSize = moduleContainer->getChildrenBoundingBox().getBottomRight(); | ||||
@@ -32,14 +32,14 @@ void SVGKnob::step() { | |||||
void SVGKnob::onChange(const event::Change &e) { | void SVGKnob::onChange(const event::Change &e) { | ||||
// Re-transform the TransformWidget | // Re-transform the TransformWidget | ||||
if (quantity) { | |||||
if (paramQuantity) { | |||||
float angle; | float angle; | ||||
if (quantity->isBounded()) { | |||||
angle = math::rescale(quantity->getScaledValue(), 0.f, 1.f, minAngle, maxAngle); | |||||
if (paramQuantity->isBounded()) { | |||||
angle = math::rescale(paramQuantity->getScaledValue(), 0.f, 1.f, minAngle, maxAngle); | |||||
angle = std::fmod(angle, 2*M_PI); | angle = std::fmod(angle, 2*M_PI); | ||||
} | } | ||||
else { | else { | ||||
angle = math::rescale(quantity->getValue(), 0.f, 1.f, minAngle, maxAngle); | |||||
angle = math::rescale(paramQuantity->getValue(), 0.f, 1.f, minAngle, maxAngle); | |||||
} | } | ||||
tw->identity(); | tw->identity(); | ||||
// Rotate SVG | // Rotate SVG | ||||
@@ -28,9 +28,9 @@ void SVGSlider::step() { | |||||
} | } | ||||
void SVGSlider::onChange(const event::Change &e) { | void SVGSlider::onChange(const event::Change &e) { | ||||
if (quantity) { | |||||
if (paramQuantity) { | |||||
// Interpolate handle position | // Interpolate handle position | ||||
float v = quantity->getScaledValue(); | |||||
float v = paramQuantity->getScaledValue(); | |||||
handle->box.pos = math::Vec( | handle->box.pos = math::Vec( | ||||
math::rescale(v, 0.f, 1.f, minHandlePos.x, maxHandlePos.x), | math::rescale(v, 0.f, 1.f, minHandlePos.x, maxHandlePos.x), | ||||
math::rescale(v, 0.f, 1.f, minHandlePos.y, maxHandlePos.y)); | math::rescale(v, 0.f, 1.f, minHandlePos.y, maxHandlePos.y)); | ||||
@@ -25,8 +25,8 @@ void SVGSwitch::addFrame(std::shared_ptr<SVG> svg) { | |||||
void SVGSwitch::onChange(const event::Change &e) { | void SVGSwitch::onChange(const event::Change &e) { | ||||
assert(frames.size() > 0); | assert(frames.size() > 0); | ||||
if (quantity) { | |||||
int index = quantity->getScaledValue() * (frames.size() - 1); | |||||
if (paramQuantity) { | |||||
int index = paramQuantity->getScaledValue() * (frames.size() - 1); | |||||
index = math::clamp(index, 0, (int) frames.size() - 1); | index = math::clamp(index, 0, (int) frames.size() - 1); | ||||
sw->setSVG(frames[index]); | sw->setSVG(frames[index]); | ||||
dirty = true; | dirty = true; | ||||
@@ -76,7 +76,7 @@ void Scene::draw(NVGcontext *vg) { | |||||
} | } | ||||
void Scene::onHoverKey(const event::HoverKey &e) { | void Scene::onHoverKey(const event::HoverKey &e) { | ||||
if (e.action == GLFW_PRESS) { | |||||
if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { | |||||
switch (e.key) { | switch (e.key) { | ||||
case GLFW_KEY_N: { | case GLFW_KEY_N: { | ||||
if ((e.mods & WINDOW_MOD) && !(e.mods & GLFW_MOD_SHIFT)) { | if ((e.mods & WINDOW_MOD) && !(e.mods & GLFW_MOD_SHIFT)) { | ||||
@@ -1,4 +1,7 @@ | |||||
#include "app/ToggleSwitch.hpp" | #include "app/ToggleSwitch.hpp" | ||||
#include "app.hpp" | |||||
#include "app/Scene.hpp" | |||||
#include "history.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -7,12 +10,24 @@ namespace rack { | |||||
void ToggleSwitch::onDragStart(const event::DragStart &e) { | void ToggleSwitch::onDragStart(const event::DragStart &e) { | ||||
// Cycle through values | // Cycle through values | ||||
// e.g. a range of [0.0, 3.0] would have modes 0, 1, 2, and 3. | // e.g. a range of [0.0, 3.0] would have modes 0, 1, 2, and 3. | ||||
if (quantity) { | |||||
if (quantity->isMax()) { | |||||
quantity->setMin(); | |||||
if (paramQuantity) { | |||||
float oldValue = paramQuantity->getValue(); | |||||
if (paramQuantity->isMax()) { | |||||
paramQuantity->setMin(); | |||||
} | } | ||||
else { | else { | ||||
quantity->setValue(std::floor(quantity->getValue() + 1)); | |||||
paramQuantity->setValue(std::floor(paramQuantity->getValue() + 1)); | |||||
} | |||||
float newValue = paramQuantity->getValue(); | |||||
if (oldValue != newValue) { | |||||
// Push ParamChange history action | |||||
history::ParamChange *h = new history::ParamChange; | |||||
h->moduleId = paramQuantity->module->id; | |||||
h->paramId = paramQuantity->paramId; | |||||
h->oldValue = oldValue; | |||||
h->newValue = newValue; | |||||
app()->history->push(h); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -230,12 +230,22 @@ void Engine::addModule(Module *module) { | |||||
assert(module); | assert(module); | ||||
VIPLock vipLock(internal->vipMutex); | VIPLock vipLock(internal->vipMutex); | ||||
std::lock_guard<std::mutex> lock(internal->mutex); | std::lock_guard<std::mutex> lock(internal->mutex); | ||||
// Set ID | |||||
assert(module->id == 0); | |||||
module->id = internal->nextModuleId++; | |||||
// Check that the module is not already added | // Check that the module is not already added | ||||
auto it = std::find(modules.begin(), modules.end(), module); | auto it = std::find(modules.begin(), modules.end(), module); | ||||
assert(it == modules.end()); | assert(it == modules.end()); | ||||
// Set ID | |||||
if (module->id == 0) { | |||||
// Automatically assign ID | |||||
module->id = internal->nextModuleId++; | |||||
} | |||||
else { | |||||
// Manual ID | |||||
// Check that the ID is not already taken | |||||
for (Module *m : modules) { | |||||
assert(module->id != m->id); | |||||
} | |||||
} | |||||
// Add module | |||||
modules.push_back(module); | modules.push_back(module); | ||||
} | } | ||||
@@ -299,8 +309,17 @@ void Engine::addWire(Wire *wire) { | |||||
assert(!(wire2->inputModule == wire->inputModule && wire2->inputId == wire->inputId)); | assert(!(wire2->inputModule == wire->inputModule && wire2->inputId == wire->inputId)); | ||||
} | } | ||||
// Set ID | // Set ID | ||||
assert(wire->id == 0); | |||||
wire->id = internal->nextWireId++; | |||||
if (wire->id == 0) { | |||||
// Automatically assign ID | |||||
wire->id = internal->nextWireId++; | |||||
} | |||||
else { | |||||
// Manual ID | |||||
// Check that the ID is not already taken | |||||
for (Wire *w : wires) { | |||||
assert(wire->id != w->id); | |||||
} | |||||
} | |||||
// Add the wire | // Add the wire | ||||
wires.push_back(wire); | wires.push_back(wire); | ||||
Engine_updateActive(this); | Engine_updateActive(this); | ||||
@@ -1,10 +1,82 @@ | |||||
#include "history.hpp" | #include "history.hpp" | ||||
#include "app.hpp" | |||||
#include "app/Scene.hpp" | |||||
namespace rack { | namespace rack { | ||||
namespace history { | namespace history { | ||||
void ModuleAdd::undo() { | |||||
ModuleWidget *moduleWidget = app()->scene->rackWidget->getModule(moduleId); | |||||
assert(moduleWidget); | |||||
app()->scene->rackWidget->removeModule(moduleWidget); | |||||
delete moduleWidget; | |||||
} | |||||
void ModuleAdd::redo() { | |||||
ModuleWidget *moduleWidget = model->createModuleWidget(); | |||||
assert(moduleWidget); | |||||
assert(moduleWidget->module); | |||||
moduleWidget->module->id = moduleId; | |||||
moduleWidget->box.pos = pos; | |||||
app()->scene->rackWidget->addModule(moduleWidget); | |||||
} | |||||
ModuleRemove::~ModuleRemove() { | |||||
json_decref(moduleJ); | |||||
} | |||||
void ModuleRemove::undo() { | |||||
ModuleWidget *moduleWidget = model->createModuleWidget(); | |||||
assert(moduleWidget); | |||||
assert(moduleWidget->module); | |||||
moduleWidget->module->id = moduleId; | |||||
moduleWidget->box.pos = pos; | |||||
moduleWidget->fromJson(moduleJ); | |||||
app()->scene->rackWidget->addModule(moduleWidget); | |||||
// Add wires | |||||
for (WireInfo &wireInfo : wireInfos) { | |||||
// TODO Add wire | |||||
} | |||||
} | |||||
void ModuleRemove::redo() { | |||||
ModuleWidget *moduleWidget = app()->scene->rackWidget->getModule(moduleId); | |||||
assert(moduleWidget); | |||||
app()->scene->rackWidget->removeModule(moduleWidget); | |||||
delete moduleWidget; | |||||
} | |||||
void ModuleMove::undo() { | |||||
ModuleWidget *moduleWidget = app()->scene->rackWidget->getModule(moduleId); | |||||
assert(moduleWidget); | |||||
moduleWidget->box.pos = oldPos; | |||||
} | |||||
void ModuleMove::redo() { | |||||
ModuleWidget *moduleWidget = app()->scene->rackWidget->getModule(moduleId); | |||||
assert(moduleWidget); | |||||
moduleWidget->box.pos = newPos; | |||||
} | |||||
void ParamChange::undo() { | |||||
ModuleWidget *moduleWidget = app()->scene->rackWidget->getModule(moduleId); | |||||
assert(moduleWidget); | |||||
moduleWidget->module->params[paramId].value = oldValue; | |||||
} | |||||
void ParamChange::redo() { | |||||
ModuleWidget *moduleWidget = app()->scene->rackWidget->getModule(moduleId); | |||||
assert(moduleWidget); | |||||
moduleWidget->module->params[paramId].value = newValue; | |||||
} | |||||
State::~State() { | State::~State() { | ||||
for (Action *action : actions) { | for (Action *action : actions) { | ||||
delete action; | delete action; | ||||