diff --git a/include/app.hpp b/include/app.hpp index 8b9d09f4..45b96d1d 100644 --- a/include/app.hpp +++ b/include/app.hpp @@ -185,6 +185,8 @@ struct RackWidget : OpaqueWidget { bool requestModuleBox(ModuleWidget *m, Rect box); /** Moves a module to the closest non-colliding position */ bool requestModuleBoxNearest(ModuleWidget *m, Rect box); + /** Moves a module by one grid unit, pushing other modules recursively. If pushLeft is true, the push direction is left, otherwise right */ + bool pushModule(ModuleWidget *m, bool pushLeft); void step() override; void draw(NVGcontext *vg) override; diff --git a/src/app/ModuleWidget.cpp b/src/app/ModuleWidget.cpp index 96fedf49..69177452 100644 --- a/src/app/ModuleWidget.cpp +++ b/src/app/ModuleWidget.cpp @@ -424,7 +424,22 @@ void ModuleWidget::onDragMove(EventDragMove &e) { if (!gRackWidget->lockModules) { Rect newBox = box; newBox.pos = gRackWidget->lastMousePos.minus(dragPos); - gRackWidget->requestModuleBoxNearest(this, newBox); + if (windowIsShiftPressed()) { + // push left or right in grid steps + float dx = newBox.pos.x - box.pos.x; + int steps = int(dx / RACK_GRID_WIDTH); + while (steps) { + if (steps < 0) { + gRackWidget->pushModule(this, true); + steps++; + } else { + gRackWidget->pushModule(this, false); + steps--; + } + } + } else { + gRackWidget->requestModuleBoxNearest(this, newBox); + } } } @@ -559,4 +574,4 @@ Menu *ModuleWidget::createContextMenu() { } -} // namespace rack \ No newline at end of file +} // namespace rack diff --git a/src/app/RackWidget.cpp b/src/app/RackWidget.cpp index e7d5c065..c826ed71 100644 --- a/src/app/RackWidget.cpp +++ b/src/app/RackWidget.cpp @@ -470,6 +470,50 @@ bool RackWidget::requestModuleBoxNearest(ModuleWidget *m, Rect box) { return false; } +bool RackWidget::pushModule(ModuleWidget *m, bool pushLeft) { + // calculate desired position + Rect newBox = m->box; + if (pushLeft) { + newBox.pos.x -= RACK_GRID_WIDTH; + } else { + newBox.pos.x += RACK_GRID_WIDTH; + } + + // can't be pushed over the left border + if (newBox.pos.x < 0.0f) return false; + + // get intersection widget after moving + Widget *iw = NULL; + int count = 0; + for (Widget *child2 : moduleContainer->children) { + if (m == child2) continue; + if (newBox.intersects(child2->box)) { + iw = child2; + count++; + } + } + + // if a module is higher than one grid unit, it can push only one module, otherwise we would need a rollback, + // if e.g. one of two modules can't be pushed + if (count > 1) return false; + + // if there is any intersected widget, try to push it recursively + if (iw) { + ModuleWidget *w2 = dynamic_cast(iw); + if (pushModule(w2, pushLeft)) { + // if successful, set new position for this widget + m->box = newBox; + return true; + } else { + return false; + } + } else { + // no intersection, just set the new position + m->box = newBox; + return true; + } +} + void RackWidget::step() { // Expand size to fit modules Vec moduleSize = moduleContainer->getChildrenBoundingBox().getBottomRight();