| @@ -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; | |||