@@ -185,6 +185,8 @@ struct RackWidget : OpaqueWidget { | |||||
bool requestModuleBox(ModuleWidget *m, Rect box); | bool requestModuleBox(ModuleWidget *m, Rect box); | ||||
/** Moves a module to the closest non-colliding position */ | /** Moves a module to the closest non-colliding position */ | ||||
bool requestModuleBoxNearest(ModuleWidget *m, Rect box); | 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 step() override; | ||||
void draw(NVGcontext *vg) override; | void draw(NVGcontext *vg) override; | ||||
@@ -424,7 +424,22 @@ void ModuleWidget::onDragMove(EventDragMove &e) { | |||||
if (!gRackWidget->lockModules) { | if (!gRackWidget->lockModules) { | ||||
Rect newBox = box; | Rect newBox = box; | ||||
newBox.pos = gRackWidget->lastMousePos.minus(dragPos); | 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 | |||||
} // namespace rack |
@@ -470,6 +470,50 @@ bool RackWidget::requestModuleBoxNearest(ModuleWidget *m, Rect box) { | |||||
return false; | 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<ModuleWidget*>(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() { | void RackWidget::step() { | ||||
// Expand size to fit modules | // Expand size to fit modules | ||||
Vec moduleSize = moduleContainer->getChildrenBoundingBox().getBottomRight(); | Vec moduleSize = moduleContainer->getChildrenBoundingBox().getBottomRight(); | ||||