@@ -11,6 +11,7 @@ namespace rack { | |||
struct Knob : ParamWidget { | |||
/** Multiplier for mouse movement to adjust knob value */ | |||
float speed = 1.0; | |||
float oldValue = 0.f; | |||
void onButton(const event::Button &e) override; | |||
void onDragStart(const event::DragStart &e) override; | |||
@@ -22,6 +22,7 @@ struct ModuleWidget : OpaqueWidget { | |||
std::vector<PortWidget*> outputs; | |||
/** For RackWidget dragging */ | |||
math::Vec dragPos; | |||
math::Vec oldPos; | |||
ModuleWidget(Module *module); | |||
~ModuleWidget(); | |||
@@ -2,14 +2,15 @@ | |||
#include "app/common.hpp" | |||
#include "widgets/OpaqueWidget.hpp" | |||
#include "ui/Tooltip.hpp" | |||
#include "ui/Quantity.hpp" | |||
#include "app/ParamQuantity.hpp" | |||
#include "history.hpp" | |||
namespace rack { | |||
struct ParamWidget : OpaqueWidget { | |||
Quantity *quantity = NULL; | |||
ParamQuantity *paramQuantity = NULL; | |||
float dirtyValue = NAN; | |||
Tooltip *tooltip = NULL; | |||
@@ -44,12 +44,13 @@ struct RackWidget : OpaqueWidget { | |||
void addModule(ModuleWidget *m); | |||
void addModuleAtMouse(ModuleWidget *m); | |||
/** Removes the module and transfers ownership to the caller */ | |||
void deleteModule(ModuleWidget *m); | |||
void removeModule(ModuleWidget *m); | |||
void cloneModule(ModuleWidget *m); | |||
/** Sets a module's box if non-colliding. Returns true if set */ | |||
bool requestModuleBox(ModuleWidget *m, math::Rect box); | |||
/** Moves a module to the closest non-colliding position */ | |||
bool requestModuleBoxNearest(ModuleWidget *m, math::Rect box); | |||
ModuleWidget *getModule(int moduleId); | |||
void step() override; | |||
void draw(NVGcontext *vg) override; | |||
@@ -60,7 +60,7 @@ TParamWidget *createParam(math::Vec pos, Module *module, int paramId) { | |||
ParamQuantity *q = new ParamQuantity; | |||
q->module = module; | |||
q->paramId = paramId; | |||
o->quantity = q; | |||
o->paramQuantity = q; | |||
return o; | |||
} | |||
@@ -71,7 +71,7 @@ TParamWidget *createParamCentered(math::Vec pos, Module *module, int paramId) { | |||
ParamQuantity *q = new ParamQuantity; | |||
q->module = module; | |||
q->paramId = paramId; | |||
o->quantity = q; | |||
o->paramQuantity = q; | |||
return o; | |||
} | |||
@@ -3,6 +3,7 @@ | |||
#include "math.hpp" | |||
#include "plugin/Model.hpp" | |||
#include <vector> | |||
#include <jansson.h> | |||
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; | |||
}; | |||
struct ModuleAdd : ModuleAction { | |||
Model *model; | |||
math::Vec pos; | |||
void undo() 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 { | |||
std::vector<Action*> actions; | |||
@@ -1,4 +1,7 @@ | |||
#include "app/Knob.hpp" | |||
#include "app.hpp" | |||
#include "app/Scene.hpp" | |||
#include "history.hpp" | |||
namespace rack { | |||
@@ -17,18 +20,34 @@ void Knob::onButton(const event::Button &e) { | |||
} | |||
void Knob::onDragStart(const event::DragStart &e) { | |||
if (paramQuantity) | |||
oldValue = paramQuantity->getValue(); | |||
app()->window->cursorLock(); | |||
} | |||
void Knob::onDragEnd(const event::DragEnd &e) { | |||
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) { | |||
if (quantity) { | |||
if (paramQuantity) { | |||
float range; | |||
if (quantity->isBounded()) { | |||
range = quantity->getRange(); | |||
if (paramQuantity->isBounded()) { | |||
range = paramQuantity->getRange(); | |||
} | |||
else { | |||
// 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 | |||
if (app()->window->isModPressed()) | |||
delta /= 16.f; | |||
quantity->moveValue(delta); | |||
paramQuantity->moveValue(delta); | |||
} | |||
ParamWidget::onDragMove(e); | |||
@@ -8,6 +8,7 @@ | |||
#include "app/Scene.hpp" | |||
#include "plugin.hpp" | |||
#include "app.hpp" | |||
#include "history.hpp" | |||
#include <set> | |||
#include <algorithm> | |||
@@ -78,12 +79,19 @@ struct ModuleBox : OpaqueWidget { | |||
// Create module | |||
ModuleWidget *moduleWidget = model->createModuleWidget(); | |||
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 | |||
e.consume(moduleWidget); | |||
// e.consume(moduleWidget); | |||
// Close Module Browser | |||
ModuleBrowser *moduleBrowser = getAncestorOfType<ModuleBrowser>(); | |||
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); | |||
} | |||
@@ -7,6 +7,7 @@ | |||
#include "helpers.hpp" | |||
#include "app.hpp" | |||
#include "settings.hpp" | |||
#include "history.hpp" | |||
#include "osdialog.h" | |||
@@ -15,23 +16,12 @@ namespace rack { | |||
ModuleWidget::ModuleWidget(Module *module) { | |||
if (module) { | |||
app()->engine->addModule(module); | |||
} | |||
this->module = module; | |||
} | |||
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) { | |||
app()->engine->removeModule(module); | |||
delete module; | |||
module = NULL; | |||
} | |||
} | |||
@@ -327,13 +317,26 @@ void ModuleWidget::drawShadow(NVGcontext *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) { | |||
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. | |||
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()) { | |||
requestedDelete = true; | |||
ModuleWidget_removeAction(this); | |||
return; | |||
} | |||
} | |||
@@ -401,10 +404,19 @@ void ModuleWidget::onHoverKey(const event::HoverKey &e) { | |||
} | |||
void ModuleWidget::onDragStart(const event::DragStart &e) { | |||
oldPos = box.pos; | |||
dragPos = app()->scene->rackWidget->lastMousePos.minus(box.pos); | |||
} | |||
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) { | |||
@@ -526,8 +538,7 @@ struct ModuleDeleteItem : MenuItem { | |||
rightText = "Backspace/Delete"; | |||
} | |||
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) { | |||
if (quantity) { | |||
quantity->setMax(); | |||
if (paramQuantity) { | |||
paramQuantity->setMax(); | |||
} | |||
} | |||
void MomentarySwitch::onDragEnd(const event::DragEnd &e) { | |||
if (quantity) { | |||
quantity->setMin(); | |||
if (paramQuantity) { | |||
paramQuantity->setMin(); | |||
} | |||
} | |||
@@ -6,6 +6,7 @@ | |||
#include "app.hpp" | |||
#include "settings.hpp" | |||
#include "random.hpp" | |||
#include "history.hpp" | |||
namespace rack { | |||
@@ -21,15 +22,27 @@ struct ParamField : TextField { | |||
void setParamWidget(ParamWidget *paramWidget) { | |||
this->paramWidget = paramWidget; | |||
if (paramWidget->quantity) | |||
text = paramWidget->quantity->getDisplayValueString(); | |||
if (paramWidget->paramQuantity) | |||
text = paramWidget->paramQuantity->getDisplayValueString(); | |||
selectAll(); | |||
} | |||
void onSelectKey(const event::SelectKey &e) override { | |||
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>(); | |||
overlay->requestedDelete = true; | |||
@@ -49,14 +62,14 @@ struct ParamField : TextField { | |||
ParamWidget::~ParamWidget() { | |||
if (quantity) | |||
delete quantity; | |||
if (paramQuantity) | |||
delete paramQuantity; | |||
} | |||
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) { | |||
dirtyValue = value; | |||
event::Change eChange; | |||
@@ -65,13 +78,10 @@ void ParamWidget::step() { | |||
} | |||
if (tooltip) { | |||
// Quantity string | |||
if (quantity) { | |||
tooltip->text = quantity->getString(); | |||
} | |||
// Param description | |||
ParamQuantity *paramQuantity = dynamic_cast<ParamQuantity*>(quantity); | |||
if (paramQuantity) { | |||
// Quantity string | |||
tooltip->text = paramQuantity->getString(); | |||
// Param description | |||
std::string description = paramQuantity->getParam()->description; | |||
if (!description.empty()) | |||
tooltip->text += "\n" + description; | |||
@@ -86,18 +96,31 @@ void ParamWidget::step() { | |||
void ParamWidget::fromJson(json_t *rootJ) { | |||
json_t *valueJ = json_object_get(rootJ, "value"); | |||
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) { | |||
// 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 (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. | |||
// dynamic_cast<ParamQuantity*>(quantity)->getParam()->reset(); | |||
// paramQuantity->getParam()->reset(); | |||
e.consume(this); | |||
} | |||
@@ -9,6 +9,7 @@ | |||
#include "asset.hpp" | |||
#include "system.hpp" | |||
#include "plugin.hpp" | |||
#include "engine/Engine.hpp" | |||
#include "app.hpp" | |||
@@ -53,12 +54,19 @@ RackWidget::RackWidget() { | |||
} | |||
RackWidget::~RackWidget() { | |||
clear(); | |||
} | |||
void RackWidget::clear() { | |||
wireContainer->activeWire = NULL; | |||
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); | |||
} | |||
@@ -455,17 +463,32 @@ void RackWidget::pastePresetClipboard() { | |||
} | |||
void RackWidget::addModule(ModuleWidget *m) { | |||
// Add module to ModuleContainer | |||
assert(m); | |||
assert(m->module); | |||
moduleContainer->addChild(m); | |||
// Add module to Engine | |||
app()->engine->addModule(m->module); | |||
} | |||
void RackWidget::addModuleAtMouse(ModuleWidget *m) { | |||
addModule(m); | |||
assert(m); | |||
// Move module nearest to the mouse position | |||
m->box.pos = lastMousePos.minus(m->box.size.div(2)); | |||
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); | |||
} | |||
@@ -496,8 +519,8 @@ bool RackWidget::requestModuleBox(ModuleWidget *m, math::Rect box) { | |||
bool RackWidget::requestModuleBoxNearest(ModuleWidget *m, math::Rect box) { | |||
// 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; | |||
for (int y = std::max(0, y0 - 8); y < y0 + 8; y++) { | |||
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; | |||
} | |||
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() { | |||
// Expand size to fit modules | |||
math::Vec moduleSize = moduleContainer->getChildrenBoundingBox().getBottomRight(); | |||
@@ -32,14 +32,14 @@ void SVGKnob::step() { | |||
void SVGKnob::onChange(const event::Change &e) { | |||
// Re-transform the TransformWidget | |||
if (quantity) { | |||
if (paramQuantity) { | |||
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); | |||
} | |||
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(); | |||
// Rotate SVG | |||
@@ -28,9 +28,9 @@ void SVGSlider::step() { | |||
} | |||
void SVGSlider::onChange(const event::Change &e) { | |||
if (quantity) { | |||
if (paramQuantity) { | |||
// Interpolate handle position | |||
float v = quantity->getScaledValue(); | |||
float v = paramQuantity->getScaledValue(); | |||
handle->box.pos = math::Vec( | |||
math::rescale(v, 0.f, 1.f, minHandlePos.x, maxHandlePos.x), | |||
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) { | |||
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); | |||
sw->setSVG(frames[index]); | |||
dirty = true; | |||
@@ -76,7 +76,7 @@ void Scene::draw(NVGcontext *vg) { | |||
} | |||
void Scene::onHoverKey(const event::HoverKey &e) { | |||
if (e.action == GLFW_PRESS) { | |||
if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { | |||
switch (e.key) { | |||
case GLFW_KEY_N: { | |||
if ((e.mods & WINDOW_MOD) && !(e.mods & GLFW_MOD_SHIFT)) { | |||
@@ -1,4 +1,7 @@ | |||
#include "app/ToggleSwitch.hpp" | |||
#include "app.hpp" | |||
#include "app/Scene.hpp" | |||
#include "history.hpp" | |||
namespace rack { | |||
@@ -7,12 +10,24 @@ namespace rack { | |||
void ToggleSwitch::onDragStart(const event::DragStart &e) { | |||
// Cycle through values | |||
// 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 { | |||
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); | |||
VIPLock vipLock(internal->vipMutex); | |||
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 | |||
auto it = std::find(modules.begin(), modules.end(), module); | |||
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); | |||
} | |||
@@ -299,8 +309,17 @@ void Engine::addWire(Wire *wire) { | |||
assert(!(wire2->inputModule == wire->inputModule && wire2->inputId == wire->inputId)); | |||
} | |||
// 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 | |||
wires.push_back(wire); | |||
Engine_updateActive(this); | |||
@@ -1,10 +1,82 @@ | |||
#include "history.hpp" | |||
#include "app.hpp" | |||
#include "app/Scene.hpp" | |||
namespace rack { | |||
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() { | |||
for (Action *action : actions) { | |||
delete action; | |||