Skip to content

Commit 7bb2c4f

Browse files
Drop elements at the mouse position after Ctrl+Drag
1 parent d838c36 commit 7bb2c4f

5 files changed

Lines changed: 73 additions & 19 deletions

File tree

src/editors/layout/LayoutUndoCommands.cpp

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,12 @@ LayoutMoveInHierarchyCommand::LayoutMoveInHierarchyCommand(LayoutVisualMode& vis
659659
, _records(std::move(records))
660660
, _newParentPath(newParentPath)
661661
{
662+
// Only one root widget can exist, and it can't be reparented
663+
_records.erase(std::remove_if(_records.begin(), _records.end(), [this](const Record& rec)
664+
{
665+
return !_visualMode.getScene()->getManipulatorByPath(rec.oldParentPath);
666+
}), _records.end());
667+
662668
// Sort by old child index to maintain predictable insertion order.
663669
// Otherwise it would depend on the order in which user selected items.
664670
std::sort(_records.begin(), _records.end(), [this](const Record& a, const Record& b)
@@ -716,8 +722,8 @@ void LayoutMoveInHierarchyCommand::undo()
716722
{
717723
const auto& rec = *it;
718724
auto widgetManipulator = _visualMode.getScene()->getManipulatorByPath(_newParentPath + '/' + rec.newName);
719-
auto oldParentManipulator = _visualMode.getScene()->getManipulatorByPath(rec.oldParentPath);
720725
auto newParentManipulator = dynamic_cast<LayoutManipulator*>(widgetManipulator->parentItem());
726+
auto oldParentManipulator = _visualMode.getScene()->getManipulatorByPath(rec.oldParentPath);
721727

722728
// Remove it from the current CEGUI parent widget
723729
if (oldParentManipulator != newParentManipulator)
@@ -770,8 +776,8 @@ void LayoutMoveInHierarchyCommand::redo()
770776
for (const auto& rec : _records)
771777
{
772778
auto widgetManipulator = _visualMode.getScene()->getManipulatorByPath(rec.oldParentPath + '/' + rec.oldName);
773-
auto newParentManipulator = _visualMode.getScene()->getManipulatorByPath(_newParentPath);
774779
auto oldParentManipulator = dynamic_cast<LayoutManipulator*>(widgetManipulator->parentItem());
780+
auto newParentManipulator = _visualMode.getScene()->getManipulatorByPath(_newParentPath);
775781

776782
// Remove it from the current CEGUI parent widget
777783
if (oldParentManipulator != newParentManipulator)
@@ -806,7 +812,7 @@ void LayoutMoveInHierarchyCommand::redo()
806812

807813
// Update widget and its previous parent (the second is mostly for the layout container case)
808814
widgetManipulator->updateFromWidget(true, true);
809-
if (oldParentManipulator) oldParentManipulator->updateFromWidget(true, true);
815+
oldParentManipulator->updateFromWidget(true, true);
810816
}
811817

812818
_visualMode.getHierarchyDockWidget()->refresh();

src/editors/layout/LayoutVisualMode.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -365,12 +365,11 @@ void LayoutVisualMode::zoomReset()
365365
ceguiWidget->getView()->zoomReset();
366366
}
367367

368-
bool LayoutVisualMode::moveWidgetsInHierarchy(QStringList&& paths, LayoutManipulator* newParentManipulator, size_t newChildIndex)
368+
// NB: paths must not contain children of any contained widget
369+
bool LayoutVisualMode::moveWidgetsInHierarchy(const QStringList& paths, LayoutManipulator* newParentManipulator, size_t newChildIndex)
369370
{
370371
if (!newParentManipulator || paths.empty()) return false;
371372

372-
CEGUIUtils::removeNestedPaths(paths);
373-
374373
std::vector<LayoutMoveInHierarchyCommand::Record> records;
375374
std::unordered_set<QString> usedNames;
376375
size_t addedChildCount = 0;
@@ -381,6 +380,10 @@ bool LayoutVisualMode::moveWidgetsInHierarchy(QStringList&& paths, LayoutManipul
381380
if (!manipulator || manipulator == newParentManipulator) continue;
382381

383382
auto oldParentManipulator = dynamic_cast<LayoutManipulator*>(manipulator->parentItem());
383+
384+
// Only one root widget can exist, and it can't be reparented
385+
if (!oldParentManipulator) continue;
386+
384387
const size_t oldChildIndex = manipulator->getWidgetIndexInParent();
385388
const QString oldWidgetName = manipulator->getWidgetName();
386389
QString suggestedName = oldWidgetName;

src/editors/layout/LayoutVisualMode.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class LayoutVisualMode : public QWidget, public IEditMode
4747
void zoomIn();
4848
void zoomOut();
4949
void zoomReset();
50-
bool moveWidgetsInHierarchy(QStringList&& paths, LayoutManipulator* newParentManipulator, size_t newChildIndex);
50+
bool moveWidgetsInHierarchy(const QStringList& paths, LayoutManipulator* newParentManipulator, size_t newChildIndex);
5151

5252
LayoutScene* getScene() const { return scene; }
5353
CreateWidgetDockWidget* getCreateWidgetDockWidget() const { return createWidgetDockWidget; }

src/ui/layout/LayoutManipulator.cpp

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -367,17 +367,59 @@ void LayoutManipulator::dropEvent(QGraphicsSceneDragDropEvent* event)
367367
widgetPaths.append(name);
368368
}
369369

370+
CEGUIUtils::removeNestedPaths(widgetPaths);
371+
370372
if (event->dropAction() == Qt::MoveAction)
371373
{
372-
_visualMode.moveWidgetsInHierarchy(std::move(widgetPaths), this, getWidget()->getChildCount());
373-
event->acceptProposedAction();
374+
// Remember desired offsets before reparenting:
375+
// - Dragged item is always the first (only for visual mode dragging for now, not from the tree)
376+
// - The first dragged item from each parent is dropped at a cursor position, with a small cascade offset
377+
// - All subsequent items from the same parent keep relative position to the first item
378+
std::map<LayoutManipulator*, QPointF> offsetByParent;
379+
std::map<CEGUI::Window*, CEGUI::UVector2> newPositions;
380+
const auto dropOffset = event->scenePos() - scenePos();
381+
382+
for (const QString& widgetPath : widgetPaths)
383+
{
384+
auto manipulator = _visualMode.getScene()->getManipulatorByPath(widgetPath);
385+
if (!manipulator) continue;
386+
387+
auto oldParentManipulator = dynamic_cast<LayoutManipulator*>(manipulator->parentItem());
388+
if (!oldParentManipulator) continue;
389+
390+
auto it = offsetByParent.find(oldParentManipulator);
391+
if (it == offsetByParent.cend())
392+
{
393+
const qreal cascadeOffset = offsetByParent.size() * 10.0;
394+
const QPointF offset = dropOffset - manipulator->pos() + QPointF(cascadeOffset, cascadeOffset);
395+
it = offsetByParent.emplace(oldParentManipulator, offset).first;
396+
}
397+
398+
if (it->second.manhattanLength() > 0.0)
399+
{
400+
auto newPos = manipulator->getWidget()->getPosition();
401+
newPos.d_x.d_offset += static_cast<float>(it->second.x());
402+
newPos.d_y.d_offset += static_cast<float>(it->second.y());
403+
newPositions.emplace(manipulator->getWidget(), newPos);
404+
}
405+
}
406+
407+
// First reparent widgets to the new parent
408+
_visualMode.moveWidgetsInHierarchy(widgetPaths, this, getWidget()->getChildCount());
374409

375-
// calc offset
376-
// get pos of the widget in old parent (need widget that we started to drag, or always first? or drag start pos, if more than one widget?)
377-
// get pos of the drop inside this
378-
//event->scenePos() - scenePos();
379-
// Add move command
410+
// Then move widgets using the drop position with a separate command
411+
std::vector<LayoutMoveCommand::Record> records;
412+
LayoutMoveCommand::Record rec;
413+
for (const auto& pair : newPositions)
414+
{
415+
rec.path = CEGUIUtils::stringToQString(pair.first->getNamePath());
416+
rec.oldPos = pair.first->getPosition();
417+
rec.newPos = pair.second;
418+
records.push_back(std::move(rec));
419+
}
420+
_visualMode.getEditor().getUndoStack()->push(new LayoutMoveCommand(_visualMode, std::move(records)));
380421

422+
event->acceptProposedAction();
381423
return;
382424
}
383425
}
@@ -397,24 +439,25 @@ void LayoutManipulator::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
397439
setCursor(Qt::ClosedHandCursor);
398440

399441
std::set<LayoutManipulator*> selectedWidgets;
400-
_visualMode.getScene()->collectSelectedWidgets(selectedWidgets);
401442

402443
/* UX: not sure what is better, move all selection or only a Ctrl-dragged item
403-
if (selectedWidgets.find(this) == selectedWidgets.end())
444+
if (!isSelected() && !isAnyHandleSelected())
404445
{
405446
_visualMode.getScene()->clearSelection();
406-
selectedWidgets.clear();
407-
selectedWidgets.insert(this);
408447
}
409448
else
410449
*/
411450
{
412-
selectedWidgets.insert(this);
451+
_visualMode.getScene()->collectSelectedWidgets(selectedWidgets);
413452
LayoutVisualMode::removeNestedManipulators(selectedWidgets);
453+
selectedWidgets.erase(this); // Will add to stream manually
414454
}
415455

416456
QByteArray bytes;
417457
QDataStream stream(&bytes, QIODevice::WriteOnly);
458+
459+
// Add dragged widget first, then the rest of selection
460+
stream << getWidgetPath();
418461
for (LayoutManipulator* manipulator : selectedWidgets)
419462
stream << manipulator->getWidgetPath();
420463

src/ui/layout/WidgetHierarchyTreeModel.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ bool WidgetHierarchyTreeModel::dropMimeData(const QMimeData* mimeData, Qt::DropA
6767
widgetPaths.append(name);
6868
}
6969

70+
CEGUIUtils::removeNestedPaths(widgetPaths);
71+
7072
const QString newParentPath = data(parent, Qt::UserRole).toString();
7173
auto newParentManipulator = _visualMode.getScene()->getManipulatorByPath(newParentPath);
7274

0 commit comments

Comments
 (0)