Skip to content

Commit

Permalink
Merge pull request #404 from friction2d/pivot-align
Browse files Browse the repository at this point in the history
Support pivot alignment
rodlie authored Dec 31, 2024
2 parents d46828b + 7485742 commit fff959c
Showing 8 changed files with 335 additions and 16 deletions.
122 changes: 121 additions & 1 deletion src/app/GUI/extraactions.cpp
Original file line number Diff line number Diff line change
@@ -161,7 +161,7 @@ void MainWindow::setupMenuExtras()
}
// align
{
const int alignTotal = 28;
const int alignTotal = 40;

const QString alignTextDefault = tr("Align %1 %2 Relative to %3");
const QString alignGeometry = tr("Geometry");
@@ -481,6 +481,126 @@ void MainWindow::setupMenuExtras()
alignBoth = false;
align = Qt::AlignVCenter;
break;
case 28: // Pivot alignment to Bounding Box - VCenter -----------------------------------------------------------------------------------
alignString = alignVCenter;
pivotString = alignPivot;
relString = alignLast;
pivot = AlignPivot::pivotItself;
rel = AlignRelativeTo::boundingBox;
iconString = alignVCenterIcon;
alignBoth = false;
align = Qt::AlignVCenter;
break;
case 29: // Pivot alignment to Bounding Box - HCenter
alignString = alignVCenter;
pivotString = alignPivot;
relString = alignLast;
pivot = AlignPivot::pivotItself;
rel = AlignRelativeTo::boundingBox;
iconString = alignVCenterIcon;
alignBoth = false;
align = Qt::AlignHCenter;
break;
case 30: // Pivot alignment to Bounding Box - Left
alignString = alignVCenter;
pivotString = alignPivot;
relString = alignLast;
pivot = AlignPivot::pivotItself;
rel = AlignRelativeTo::boundingBox;
iconString = alignVCenterIcon;
alignBoth = false;
align = Qt::AlignLeft;
break;
case 31: // Pivot alignment to Bounding Box - Right
alignString = alignVCenter;
pivotString = alignPivot;
relString = alignLast;
pivot = AlignPivot::pivotItself;
rel = AlignRelativeTo::boundingBox;
iconString = alignVCenterIcon;
alignBoth = false;
align = Qt::AlignRight;
break;
case 32: // Pivot alignment to Bounding Box - Top
alignString = alignVCenter;
pivotString = alignPivot;
relString = alignLast;
pivot = AlignPivot::pivotItself;
rel = AlignRelativeTo::boundingBox;
iconString = alignVCenterIcon;
alignBoth = false;
align = Qt::AlignTop;
break;
case 33: // Pivot alignment to Bounding Box - Bottom
alignString = alignVCenter;
pivotString = alignPivot;
relString = alignLast;
pivot = AlignPivot::pivotItself;
rel = AlignRelativeTo::boundingBox;
iconString = alignVCenterIcon;
alignBoth = false;
align = Qt::AlignBottom;
break;
case 34: // Pivot alignment to Scene - VCenter -----------------------------------------------------------------------------------
alignString = alignVCenter;
pivotString = alignPivot;
relString = alignLast;
pivot = AlignPivot::pivotItself;
rel = AlignRelativeTo::scene;
iconString = alignVCenterIcon;
alignBoth = false;
align = Qt::AlignVCenter;
break;
case 35: // Pivot alignment to Scene - HCenter
alignString = alignVCenter;
pivotString = alignPivot;
relString = alignLast;
pivot = AlignPivot::pivotItself;
rel = AlignRelativeTo::scene;
iconString = alignVCenterIcon;
alignBoth = false;
align = Qt::AlignHCenter;
break;
case 36: // Pivot alignment to Scene - Left
alignString = alignVCenter;
pivotString = alignPivot;
relString = alignLast;
pivot = AlignPivot::pivotItself;
rel = AlignRelativeTo::scene;
iconString = alignVCenterIcon;
alignBoth = false;
align = Qt::AlignLeft;
break;
case 37: // Pivot alignment to Scene - Right
alignString = alignVCenter;
pivotString = alignPivot;
relString = alignLast;
pivot = AlignPivot::pivotItself;
rel = AlignRelativeTo::scene;
iconString = alignVCenterIcon;
alignBoth = false;
align = Qt::AlignRight;
break;
case 38: // Pivot alignment to Scene - Top
alignString = alignVCenter;
pivotString = alignPivot;
relString = alignLast;
pivot = AlignPivot::pivotItself;
rel = AlignRelativeTo::scene;
iconString = alignVCenterIcon;
alignBoth = false;
align = Qt::AlignTop;
break;
case 39: // Pivot alignment to Scene - Bottom
alignString = alignVCenter;
pivotString = alignPivot;
relString = alignLast;
pivot = AlignPivot::pivotItself;
rel = AlignRelativeTo::scene;
iconString = alignVCenterIcon;
alignBoth = false;
align = Qt::AlignBottom;
break;
default:
return;
}
3 changes: 2 additions & 1 deletion src/app/friction.qss
Original file line number Diff line number Diff line change
@@ -479,7 +479,8 @@ QMenu::separator {
background-color: %4;
}

QMenu::item:disabled {
QMenu::item:disabled,
AlignWidget QPushButton::disabled {
background: transparent;
}

119 changes: 119 additions & 0 deletions src/core/Boxes/boundingbox.cpp
Original file line number Diff line number Diff line change
@@ -865,6 +865,125 @@ void BoundingBox::alignPivot(const Qt::Alignment align, const QRectF& to) {
alignGeometry(QRectF(pivot, pivot), align, to);
}

void BoundingBox::alignPivotItself(const Qt::Alignment align,
const QRectF& to,
const AlignRelativeTo relativeTo,
const QPointF lastPivotAbsPos)
{
QPointF currentPivot = mTransformAnimator->getPivot();
QPointF currentPivotAbsPos = getPivotAbsPos();

QPointF lastSelectedPivotAbsPos = lastPivotAbsPos;

QPointF center = getRelCenterPosition();

switch (relativeTo) {
case AlignRelativeTo::scene:
switch (align) {
case Qt::AlignVCenter:
center.setX(currentPivot.x());
center.setY(currentPivot.y() - currentPivotAbsPos.y() + to.bottomRight().y()/2);
break;
case Qt::AlignHCenter:
center.setX(currentPivot.x() - currentPivotAbsPos.x() + to.bottomRight().x()/2);
center.setY(currentPivot.y());
break;
case Qt::AlignLeft:
center.setX(currentPivot.x() - currentPivotAbsPos.x());
center.setY(currentPivot.y());
break;
case Qt::AlignRight:
center.setX(currentPivot.x() + (to.topRight().x() - currentPivotAbsPos.x()));
center.setY(currentPivot.y());
break;
case Qt::AlignTop:
center.setX(currentPivot.x());
center.setY(currentPivot.y() - currentPivotAbsPos.y());
break;
case Qt::AlignBottom:
center.setX(currentPivot.x());
center.setY(currentPivot.y() + (to.bottomRight().y() - currentPivotAbsPos.y()));
break;
}
break;
case AlignRelativeTo::lastSelected:
switch (align) {
case Qt::AlignVCenter:
center.setX(currentPivot.x());
center.setY(currentPivot.y() - currentPivotAbsPos.y() + to.center().y());
break;
case Qt::AlignHCenter:
center.setX(currentPivot.x() - currentPivotAbsPos.x() + to.center().x());
center.setY(currentPivot.y());
break;
case Qt::AlignLeft:
center.setX(currentPivot.x() - currentPivotAbsPos.x() + to.topLeft().x());
center.setY(currentPivot.y());
break;
case Qt::AlignRight:
center.setX(currentPivot.x() + (to.topRight().x() - currentPivotAbsPos.x()));
center.setY(currentPivot.y());
break;
case Qt::AlignTop:
center.setX(currentPivot.x());
center.setY(currentPivot.y() - currentPivotAbsPos.y() + to.topLeft().y());
break;
case Qt::AlignBottom:
center.setX(currentPivot.x());
center.setY(currentPivot.y() + (to.bottomRight().y() - currentPivotAbsPos.y()));
break;
}
break;
case AlignRelativeTo::lastSelectedPivot:
switch (align) {
case Qt::AlignVCenter:
center.setX(currentPivot.x());
center.setY(currentPivot.y() - currentPivotAbsPos.y() + lastSelectedPivotAbsPos.y());
break;
case Qt::AlignHCenter:
center.setX(currentPivot.x() - currentPivotAbsPos.x() + lastSelectedPivotAbsPos.x());
center.setY(currentPivot.y());
break;
default:
center.setX(currentPivot.x());
center.setY(currentPivot.y());
break;
}
break;
case AlignRelativeTo::boundingBox:
switch (align) {
case Qt::AlignVCenter:
center.setX(currentPivot.x());
break;
case Qt::AlignHCenter:
center.setY(currentPivot.y());
break;
case Qt::AlignLeft:
center.setX(mRelRect.topLeft().x());
center.setY(currentPivot.y());
break;
case Qt::AlignRight:
center.setX(mRelRect.topRight().x());
center.setY(currentPivot.y());
break;
case Qt::AlignTop:
center.setX(currentPivot.x());
center.setY(mRelRect.topLeft().y());
break;
case Qt::AlignBottom:
center.setX(currentPivot.x());
center.setY(mRelRect.bottomLeft().y());
break;
}
break;
}

startPivotTransform();
mTransformAnimator->setPivotFixedTransform(center);
requestGlobalPivotUpdateIfSelected();
finishPivotTransform();
}

void BoundingBox::moveByAbs(const QPointF &trans) {
mTransformAnimator->moveByAbs(trans);
}
6 changes: 6 additions & 0 deletions src/core/Boxes/boundingbox.h
Original file line number Diff line number Diff line change
@@ -40,6 +40,8 @@

class Canvas;

enum class AlignRelativeTo;

class QrealAction;
class MovablePoint;

@@ -298,6 +300,10 @@ class CORE_EXPORT BoundingBox : public eBoxOrSound {

void alignGeometry(const Qt::Alignment align, const QRectF& to);
void alignPivot(const Qt::Alignment align, const QRectF& to);
void alignPivotItself(const Qt::Alignment align,
const QRectF& to,
const AlignRelativeTo relativeTo,
const QPointF lastPivotAbsPos);

QMatrix getTotalTransform() const;

4 changes: 2 additions & 2 deletions src/core/canvas.h
Original file line number Diff line number Diff line change
@@ -69,11 +69,11 @@ class eKeyEvent;
enum class CtrlsMode : short;

enum class AlignPivot {
geometry, pivot
geometry, pivot, pivotItself
};

enum class AlignRelativeTo {
scene, lastSelected
scene, lastSelected, lastSelectedPivot, boundingBox
};

class CORE_EXPORT Canvas : public CanvasBase
26 changes: 20 additions & 6 deletions src/core/canvasselectedboxesactions.cpp
Original file line number Diff line number Diff line change
@@ -851,31 +851,45 @@ void Canvas::selectedPathsCombine() {

void Canvas::alignSelectedBoxes(const Qt::Alignment align,
const AlignPivot pivot,
const AlignRelativeTo relativeTo) {
if(mSelectedBoxes.isEmpty()) return;
const AlignRelativeTo relativeTo)
{
if (mSelectedBoxes.isEmpty()) { return; }
QRectF geometry;
BoundingBox* skip = nullptr;
switch(relativeTo) {
case AlignRelativeTo::scene:
case AlignRelativeTo::boundingBox:
geometry = QRectF(0., 0., mWidth, mHeight);
break;
case AlignRelativeTo::lastSelected:
if(!mLastSelectedBox) return;
if (!mLastSelectedBox) { return; }
skip = mLastSelectedBox;
geometry = mLastSelectedBox->getAbsBoundingRect();
break;
case AlignRelativeTo::lastSelectedPivot:
if (!mLastSelectedBox) { return; }
skip = mLastSelectedBox;
geometry = QRectF(mLastSelectedBox->getPivotAbsPos(),
mLastSelectedBox->getPivotAbsPos());
break;
}

pushUndoRedoName("align");
for(const auto &box : mSelectedBoxes) {
if(box == skip) continue;
pushUndoRedoName(pivot == AlignPivot::pivotItself ? "pivot align" : "box align");
for (const auto &box : mSelectedBoxes) {
if (box == skip) { continue; }
switch(pivot) {
case AlignPivot::pivot:
box->alignPivot(align, geometry);
break;
case AlignPivot::geometry:
box->alignGeometry(align, geometry);
break;
case AlignPivot::pivotItself:
box->alignPivotItself(align,
geometry,
relativeTo,
mLastSelectedBox->getPivotAbsPos());
break;
}
}
}
68 changes: 62 additions & 6 deletions src/ui/widgets/alignwidget.cpp
Original file line number Diff line number Diff line change
@@ -28,11 +28,21 @@
#include <QLabel>
#include <QHBoxLayout>
#include <QPushButton>
#include <QStandardItemModel>

#include "GUI/global.h"
#include "Private/esettings.h"
#include "themesupport.h"

#define INDEX_ALIGN_GEOMETRY 0
#define INDEX_ALIGN_GEOMETRY_PIVOT 1
#define INDEX_ALIGN_PIVOT 2

#define INDEX_REL_SCENE 0
#define INDEX_REL_LAST_SELECTED 1
#define INDEX_REL_LAST_SELECTED_PIVOT 2
#define INDEX_REL_BOUNDINGBOX 3

AlignWidget::AlignWidget(QWidget* const parent)
: QWidget(parent)
, mAlignPivot(nullptr)
@@ -45,24 +55,30 @@ AlignWidget::AlignWidget(QWidget* const parent)
const auto combosLay = new QHBoxLayout;
mainLayout->addLayout(combosLay);

//combosLay->addWidget(new QLabel(tr("Align")));
combosLay->addWidget(new QLabel(tr("Align"), this));
mAlignPivot = new QComboBox(this);
mAlignPivot->setMinimumWidth(20);
mAlignPivot->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
mAlignPivot->setFocusPolicy(Qt::NoFocus);
mAlignPivot->addItem(tr("Align Geometry"));
mAlignPivot->addItem(tr("Align Pivot"));
mAlignPivot->addItem(tr("Geometry")); // INDEX_ALIGN_GEOMETRY
mAlignPivot->addItem(tr("Geometry by Pivot")); // INDEX_ALIGN_GEOMETRY_PIVOT
mAlignPivot->addItem(tr("Pivot")); // INDEX_ALIGN_PIVOT
combosLay->addWidget(mAlignPivot);

//combosLay->addWidget(new QLabel(tr("Relative to")));
combosLay->addWidget(new QLabel(tr("To"), this));
mRelativeTo = new QComboBox(this);
mRelativeTo->setMinimumWidth(20);
mRelativeTo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
mRelativeTo->setFocusPolicy(Qt::NoFocus);
mRelativeTo->addItem(tr("Relative to Scene"));
mRelativeTo->addItem(tr("Relative to Last Selected"));
mRelativeTo->addItem(tr("Scene")); // INDEX_REL_SCENE
mRelativeTo->addItem(tr("Last Selected")); // INDEX_REL_LAST_SELECTED
mRelativeTo->addItem(tr("Last Selected Pivot")); // INDEX_REL_LAST_SELECTED_PIVOT
mRelativeTo->addItem(tr("Bounding Box")); // INDEX_REL_BOUNDINGBOX
combosLay->addWidget(mRelativeTo);

setComboBoxItemState(mRelativeTo, INDEX_REL_LAST_SELECTED_PIVOT, false);
setComboBoxItemState(mRelativeTo, INDEX_REL_BOUNDINGBOX, false);

const auto buttonsLay = new QHBoxLayout;
mainLayout->addLayout(buttonsLay);
mainLayout->addStretch();
@@ -121,6 +137,31 @@ AlignWidget::AlignWidget(QWidget* const parent)
});
buttonsLay->addWidget(bottomButton);

connect(mAlignPivot,
QOverload<int>::of(&QComboBox::currentIndexChanged),
this, [this](int index) {
setComboBoxItemState(mRelativeTo,
INDEX_REL_LAST_SELECTED_PIVOT,
index == INDEX_ALIGN_PIVOT);
setComboBoxItemState(mRelativeTo,
INDEX_REL_BOUNDINGBOX,
index == INDEX_ALIGN_PIVOT);
if (index == INDEX_ALIGN_PIVOT) { mRelativeTo->setCurrentIndex(INDEX_REL_BOUNDINGBOX); }
else { mRelativeTo->setCurrentIndex(INDEX_REL_SCENE); }
});

connect(mRelativeTo,
QOverload<int>::of(&QComboBox::currentIndexChanged),
this, [leftButton,
rightButton,
topButton,
bottomButton](int index) {
leftButton->setEnabled(index != INDEX_REL_LAST_SELECTED_PIVOT);
rightButton->setEnabled(index != INDEX_REL_LAST_SELECTED_PIVOT);
topButton->setEnabled(index != INDEX_REL_LAST_SELECTED_PIVOT);
bottomButton->setEnabled(index != INDEX_REL_LAST_SELECTED_PIVOT);
});

eSizesUI::widget.add(leftButton, [leftButton,
hCenterButton,
rightButton,
@@ -143,3 +184,18 @@ void AlignWidget::triggerAlign(const Qt::Alignment align)
const auto relativeTo = static_cast<AlignRelativeTo>(mRelativeTo->currentIndex());
emit alignTriggered(align, alignPivot, relativeTo);
}

void AlignWidget::setComboBoxItemState(QComboBox *box,
int index,
bool enabled)
{
auto model = qobject_cast<QStandardItemModel*>(box->model());
if (!model) { return; }

if (index >= box->count() || index < 0) { return; }

auto item = model->item(index);
if (!item) { return; }

item->setEnabled(enabled);
}
3 changes: 3 additions & 0 deletions src/ui/widgets/alignwidget.h
Original file line number Diff line number Diff line change
@@ -47,6 +47,9 @@ class UI_EXPORT AlignWidget : public QWidget

private:
void triggerAlign(const Qt::Alignment align);
void setComboBoxItemState(QComboBox *box,
int index,
bool enabled);

QComboBox *mAlignPivot;
QComboBox *mRelativeTo;

0 comments on commit fff959c

Please sign in to comment.