The Marquee, shapes and a moved selection snap to View › Snap To; Ctrl draws freely (13.5a)

This commit is contained in:
ZacharyZhang-NY committed 2026-10-01 09:55:24 -04:00
1 parent 64ebd1cdf9
commit 3992bc913d
11 files changed
+271 -11

No files matched your search

+35
View File
@@ -53,6 +53,7 @@ private slots:
void layoutGridLinesIncludeMajorsAndSubdivisions();
void aDragCreatesMovesAndDeletesGuides();
void aDragSnapsAndHitsTheNearestGuide();
void drawnPointsSnapToTheSnapToTargets();
};
void GuideTests::newGuidesUndoAndClear()
@@ -427,5 +428,39 @@ void GuideTests::aDragSnapsAndHitsTheNearestGuide()
QVERIFY(!session->hitGuide(near));
}
// Swift's drawnPointsSnapToTheSnapToTargets, as written.
void GuideTests::drawnPointsSnapToTheSnapToTargets()
{
const auto session = paintedSession();
session->setSnapToLayers(false);
session->setSnapToDocumentBounds(true);
session->setShowsGrid(true);
session->setSnapToGrid(true);
// Each axis alone: grid every 8, the edge at 400.
QCOMPARE(session->snappedPoint(QPointF(62, 20), 3), QPointF(64, 20));
QCOMPARE(session->snappedPoint(QPointF(397.5, 9), 3), QPointF(400, 8));
QVERIFY(session->snapGuides.xs == std::vector<double>{400} && session->snapGuides.ys == std::vector<double>{8});
// A tie keeps the first line met; reach counts inclusively.
QCOMPARE(session->snappedPoint(QPointF(4, 17), 4), QPointF(0, 16));
// Two lines in reach: the nearer wins.
QCOMPARE(session->snappedPoint(QPointF(3, 19), 5), QPointF(0, 16));
QCOMPARE(session->snappedPoint(QPointF(5, 21), 5), QPointF(8, 24));
QCOMPARE(session->snappedPoint(QPointF(63, 11), 1), QPointF(64, 11));
// The canvas's middle is no target for a drawn point.
QCOMPARE(session->snappedPoint(QPointF(62, 149), 2), QPointF(64, 149));
QVERIFY(session->snapGuides.ys.empty());
session->setSnappingEnabled(false);
session->snapGuides = {{1}, {2}};
QCOMPARE(session->snappedPoint(QPointF(62, 20), 3), QPointF(62, 20));
QVERIFY(session->snapGuides.xs.empty() && session->snapGuides.ys.empty());
session->setSnappingEnabled(true);
session->setSnapEnabled(false);
QCOMPARE(session->snappedPoint(QPointF(62, 20), 3), QPointF(62, 20));
QVERIFY(session->snapGuides.xs.empty() && session->snapGuides.ys.empty());
session->setSnapEnabled(true);
session->setShowsGrid(false);
session->setSnapToGrid(false);
}
QTEST_GUILESS_MAIN(GuideTests)
#include "GuideTests.moc"
+4 -3
View File
@@ -212,13 +212,14 @@ void SelectionCanvasTests::focusLossEndsAMarqueeDraftAndAnOutlineMove()
QVERIFY(session.lassoDraft().has_value());
session.cancelLasso();
session.selectAll();
// Twenty across: out of the canvas edges' snapping reach.
shown.press(QPointF(200, 150));
shown.move(QPointF(210, 150));
shown.move(QPointF(220, 150));
shown.canvas->clearFocus();
QCOMPARE(session.history.undoName(), QString("Move Selection"));
QVERIFY(session.canUndo());
shown.release(QPointF(210, 150));
QCOMPARE(bounds(session), QRectF(10, 0, 400, 300));
shown.release(QPointF(220, 150));
QCOMPARE(bounds(session), QRectF(20, 0, 400, 300));
}
void SelectionCanvasTests::shiftDeleteBeginsAContentAwareFill()
+160
View File
@@ -0,0 +1,160 @@
#include "SelectionCanvasFixtures.h"
// Swift 1.3.5's snapping while drawing and moving a selection.
class SnapDrawingCanvasTests : public QObject {
Q_OBJECT
private slots:
void initTestCase() { QStandardPaths::setTestModeEnabled(true); }
void aMarqueeSnapsWhereItStartsAndEnds();
void aShapeSnapsItsCorners();
void aMovedSelectionSnapsUnlessCtrl();
void theReachIsTenViewPoints();
void aMovedSelectionSnapsItsEdges();
};
namespace {
// Only the canvas's edges are targets: 0 to 400, 300.
struct Snapping : Canvas {
Snapping()
{
session.setSnappingEnabled(true);
session.setSnapEnabled(true);
session.setSnapToDocumentBounds(true);
session.setSnapToLayers(false);
session.setSnapToGrid(false);
}
QRectF selected() const { return session.selection().value().path.boundingRect(); }
};
}
void SnapDrawingCanvasTests::aMarqueeSnapsWhereItStartsAndEnds()
{
Snapping shown;
// Within ten points, press and drag meet the edges.
shown.press(QPointF(6, 8));
shown.move(QPointF(393, 291));
QVERIFY(shown.session.snapGuides.xs == std::vector<double>{400} && shown.session.snapGuides.ys == std::vector<double>{300});
shown.release(QPointF(393, 291));
QCOMPARE(shown.selected(), QRectF(0, 0, 400, 300));
QVERIFY(shown.session.snapGuides.xs.empty() && shown.session.snapGuides.ys.empty());
// Ctrl draws freely, both ends.
shown.session.deselect();
shown.drag(QPointF(6, 8), QPointF(393, 291), Qt::ControlModifier);
QCOMPARE(shown.selected(), QRectF(6, 8, 387, 283));
// A Lasso starts where it is pressed.
shown.session.deselect();
shown.session.selectTool(NavigationTool::lasso);
shown.press(QPointF(6, 8));
QCOMPARE(shown.session.lassoDraft().value().points.front(), QPointF(6, 8));
shown.release(QPointF(6, 8));
}
void SnapDrawingCanvasTests::aShapeSnapsItsCorners()
{
Snapping shown;
shown.session.selectTool(NavigationTool::shape);
shown.press(QPointF(6, 8));
shown.move(QPointF(393, 291));
QCOMPARE(shown.session.shapeDraft().value().rect, QRectF(0, 0, 400, 300));
QVERIFY(shown.session.snapGuides.xs == std::vector<double>{400});
shown.move(QPointF(393, 291), Qt::ControlModifier);
QCOMPARE(shown.session.shapeDraft().value().rect, QRectF(0, 0, 393, 291));
QVERIFY(shown.session.snapGuides.xs.empty());
shown.session.cancelShape();
shown.press(QPointF(6, 8), Qt::ControlModifier);
QCOMPARE(shown.session.shapeDraft().value().anchor, QPointF(6, 8));
shown.session.cancelShape();
}
void SnapDrawingCanvasTests::aMovedSelectionSnapsUnlessCtrl()
{
Snapping shown;
// Drawn freely, five in from the top left.
shown.drag(QPointF(5, 5), QPointF(55, 35), Qt::ControlModifier);
QCOMPARE(shown.selected(), QRectF(5, 5, 50, 30));
// Twelve up: both near edges meet the canvas.
shown.press(QPointF(30, 20));
shown.move(QPointF(30, 8));
QCOMPARE(shown.selected(), QRectF(0, 0, 50, 30));
QVERIFY(shown.session.snapGuides.xs == std::vector<double>{0} && shown.session.snapGuides.ys == std::vector<double>{0});
// Shift keeps one axis: the locked one never snaps.
shown.move(QPointF(30, 8), Qt::ShiftModifier);
QCOMPARE(shown.selected(), QRectF(5, 0, 50, 30));
QVERIFY(shown.session.snapGuides.xs.empty() && shown.session.snapGuides.ys == std::vector<double>{0});
shown.move(QPointF(18, 19), Qt::ShiftModifier);
QCOMPARE(shown.selected(), QRectF(0, 5, 50, 30));
QVERIFY(shown.session.snapGuides.xs == std::vector<double>{0} && shown.session.snapGuides.ys.empty());
// Ctrl moves freely and shows no line.
shown.move(QPointF(30, 8), Qt::ControlModifier);
QCOMPARE(shown.selected(), QRectF(5, -7, 50, 30));
QVERIFY(shown.session.snapGuides.xs.empty() && shown.session.snapGuides.ys.empty());
shown.release(QPointF(30, 8), Qt::ControlModifier);
}
void SnapDrawingCanvasTests::theReachIsTenViewPoints()
{
Snapping shown;
shown.session.addGuide({QUuid::createUuid(), CanvasGuide::Axis::vertical, 200});
const QSizeF size = shown.documentSize();
// Seven pixels off the guide: snapped at 100%, not 200%.
shown.drag(shown.session.viewport.viewPoint(QPointF(207, 150), size), shown.session.viewport.viewPoint(QPointF(250, 180), size));
QCOMPARE(shown.selected(), QRectF(200, 150, 50, 30));
shown.session.deselect();
shown.session.zoom(2);
shown.drag(shown.session.viewport.viewPoint(QPointF(207, 150), size), shown.session.viewport.viewPoint(QPointF(250, 180), size));
QCOMPARE(shown.selected(), QRectF(207, 150, 43, 30));
// A moved selection's reach is ten view points too.
QPainterPath box;
box.addRect(210, 150, 40, 20);
shown.session.setSelection(DocumentSelection{box}, QStringLiteral("Marquee"));
shown.press(shown.session.viewport.viewPoint(QPointF(230, 160), size));
shown.move(shown.session.viewport.viewPoint(QPointF(227, 160), size));
QCOMPARE(shown.selected(), QRectF(207, 150, 40, 20));
shown.release(shown.session.viewport.viewPoint(QPointF(227, 160), size));
}
void SnapDrawingCanvasTests::aMovedSelectionSnapsItsEdges()
{
Snapping shown;
EditorSession *const session = &shown.session;
QPainterPath box;
box.addRect(10, 10, 50, 30);
// No move begun: the offset as it came, no lines.
session->snapGuides = {{1}, {}};
QCOMPARE(session->snappedSelectionOffset(QSizeF(-8.6, 0), 3), QSizeF(-8.6, 0));
QVERIFY(session->snapGuides.xs.empty());
session->setSelection(DocumentSelection{box}, QStringLiteral("Marquee"));
QVERIFY(session->beginSelectionMove());
// Whole pixels first; the left edge then meets the canvas.
QCOMPARE(session->snappedSelectionOffset(QSizeF(-8.6, 0.4), 3), QSizeF(-10, 0));
QVERIFY(session->snapGuides.xs == std::vector<double>{0} && session->snapGuides.ys.empty());
// The far edge meets the far side; beyond reach, nothing.
QCOMPARE(session->snappedSelectionOffset(QSizeF(338.6, 258.6), 3), QSizeF(340, 260));
QVERIFY(session->snapGuides.xs == std::vector<double>{400} && session->snapGuides.ys == std::vector<double>{300});
QCOMPARE(session->snappedSelectionOffset(QSizeF(-5, 0), 3), QSizeF(-5, 0));
// Rounded half away from zero; the middle is no target.
QCOMPARE(session->snappedSelectionOffset(QSizeF(8.6, 0), 3), QSizeF(9, 0));
QCOMPARE(session->snappedSelectionOffset(QSizeF(2.5, 0), 3), QSizeF(3, 0));
QCOMPARE(session->snappedSelectionOffset(QSizeF(163, 0), 3), QSizeF(163, 0));
// An axis Shift holds does not snap.
QCOMPARE(session->snappedSelectionOffset(QSizeF(-8.6, -8.6), 3, true, false), QSizeF(-10, -9));
QCOMPARE(session->snappedSelectionOffset(QSizeF(-8.6, -8.6), 3, false, true), QSizeF(-9, -10));
QVERIFY(session->snapGuides.xs.empty() && session->snapGuides.ys == std::vector<double>{0});
// A turned ellipse snaps its own box, not control points.
session->endSelectionMove();
QPainterPath ellipse;
ellipse.addEllipse(100, 100, 200, 60);
ellipse = QTransform().translate(200, 130).rotate(45).translate(-200, -130).map(ellipse);
const double left = ellipse.boundingRect().left();
QVERIFY(left - ellipse.controlPointRect().left() > 6);
session->setSelection(DocumentSelection{ellipse}, QStringLiteral("Marquee"));
QVERIFY(session->beginSelectionMove());
const double whole = std::round(2 - left);
QCOMPARE(session->snappedSelectionOffset(QSizeF(whole, 0), 3), QSizeF(whole - (left + whole), 0));
session->setSnappingEnabled(false);
QCOMPARE(session->snappedSelectionOffset(QSizeF(-8.6, 0), 3), QSizeF(-8.6, 0));
QVERIFY(session->snapGuides.xs.empty());
}
QTEST_MAIN(SnapDrawingCanvasTests)
#include "SnapDrawingCanvasTests.moc"
+1
View File
@@ -180,6 +180,7 @@ compositor_test(ColorRangeCanvasTests)
compositor_test(ColorRangeSheetTests)
compositor_test(DialogColorPickerTests)
compositor_test(JPEGPreviewTests)
compositor_test(SnapDrawingCanvasTests)
# It calls the colour-range kernel itself.
target_include_directories(ColorRangeTests PRIVATE ${OMAPHOTO_KERNELS})
compositor_test(DitherTests)