Merge branch 'port-d'

# Conflicts:
#	AGENTS.md
This commit is contained in:
ZacharyZhang-NY committed 2026-10-01 23:02:47 -04:00
commit 8204e6a28b
14 files changed
+231 -101

No files matched your search

+6
View File
@@ -345,6 +345,12 @@ void BrushControlsTests::blurHasItsOwnRadius()
QTest::keyClick(&field, Qt::Key_Return);
QCOMPARE(session.brushSettings().blurRadius, 0.5);
QCOMPARE(field.text(), QString("0.5"));
// A number not finite takes 5, as Swift's binding.
type(field, "inf");
QTest::keyClick(&field, Qt::Key_Return);
QCOMPARE(session.brushSettings().blurRadius, 5.0);
type(field, "0");
QTest::keyClick(&field, Qt::Key_Return);
type(field, "abc");
QTest::keyClick(&field, Qt::Key_Return);
QCOMPARE(session.brushSettings().blurRadius, 0.5);
+73
View File
@@ -0,0 +1,73 @@
#include "MovePressFixtures.h"
#include "UI/TransformInspector.h"
#include <QLineEdit>
// The Move bar's value and a folder, under real presses.
class MoveBarPressTests : public QObject {
Q_OBJECT
private slots:
void aTypedValueThenAPressDragsTheActiveLayer();
void ctrlKeepsAFolderAsAutoSelectPicksElsewhere();
};
// Swift picks before focus moves: the active layer drags.
void MoveBarPressTests::aTypedValueThenAPressDragsTheActiveLayer()
{
Canvas shown;
EditorSession &session = shown.session;
session.insert(filled(60, 60, qRgba(0, 0, 255, 255), "Blue"), QPointF(320, 20));
const QUuid blue = session.activeLayerID().value();
session.selectLayer(shown.red);
session.setTransformAutoSelect(true);
shown.window.resize(400, 350);
TransformInspector bar(session, &shown.window);
bar.setGeometry(0, 300, 400, 42);
bar.show();
QLineEdit *x = bar.findChild<QLineEdit *>(QStringLiteral("transformX"));
QVERIFY(x);
x->setFocus();
QTRY_VERIFY(x->hasFocus());
x->selectAll();
QTest::keyClicks(x, "160");
QVERIFY(session.transformEdit().value().fromFields);
const int steps = session.history.undoCount();
// A real press over Blue: focus moves, the canvas picks.
QWindow *window = shown.window.windowHandle();
QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, QPoint(330, 30));
QTest::mouseMove(window, QPoint(337, 30));
QTest::mouseMove(window, QPoint(343, 30));
QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, QPoint(343, 30));
QCoreApplication::processEvents();
QVERIFY(!x->hasFocus());
QCOMPARE(session.activeLayerID(), std::optional(shown.red));
QCOMPARE(layerWith(session, blue).transform.origin, QPointF(290, -10));
QCOMPARE(shown.origin(), QPointF(173, 100));
// Two steps: the typed value, then the drag.
QCOMPARE(session.history.undoCount(), steps + 2);
session.undo();
QCOMPARE(shown.origin(), QPointF(160, 100));
}
void MoveBarPressTests::ctrlKeepsAFolderAsAutoSelectPicksElsewhere()
{
Canvas shown;
EditorSession &session = shown.session;
session.insert(filled(60, 60, qRgba(0, 0, 255, 255), "Blue"), QPointF(320, 20));
const QUuid blue = session.activeLayerID().value();
session.selectLayers({shown.red}, shown.red);
session.groupSelectedLayers();
const QUuid folder = session.activeLayerID().value();
QVERIFY(folder != shown.red && session.transformsAsGroup());
session.setTransformAutoSelect(true);
// Over Blue, past the folder's box: Ctrl drags the folder.
shown.drag(QPointF(330, 30), QPointF(343, 30), Qt::ControlModifier);
QCOMPARE(session.activeLayerID(), std::optional(folder));
QCOMPARE(shown.origin(), QPointF(163, 100));
QCOMPARE(layerWith(session, blue).transform.origin, QPointF(290, -10));
// Without Ctrl, Auto Select picks Blue.
shown.drag(QPointF(330, 30), QPointF(343, 30));
QCOMPARE(session.activeLayerID(), std::optional(blue));
}
QTEST_MAIN(MoveBarPressTests)
#include "MoveBarPressTests.moc"
+38
View File
@@ -0,0 +1,38 @@
#pragma once
#include "CanvasFixtures.h"
// A red square filling the canvas: view points are pixels.
struct Canvas : Shown {
QUuid red;
Canvas() : Shown(QSize(400, 300), QSize(400, 300))
{
settle();
session.zoom(1);
session.insert(filled(100, 100, qRgba(255, 0, 0, 255), "Red"));
red = session.activeLayerID().value();
session.selectTool(NavigationTool::move);
canvas->synchronizeDisplay();
if (session.viewport.viewPoint(QPointF(0, 0), documentSize()) != QPointF(0, 0))
throw std::runtime_error("the document does not fill the canvas");
}
QPointF origin() const { return layerWith(session, red).transform.origin; }
void press(QPointF at, Qt::KeyboardModifiers modifiers = Qt::NoModifier) { QTest::mousePress(canvas, Qt::LeftButton, modifiers, at.toPoint()); }
void move(QPointF to, Qt::KeyboardModifiers modifiers = Qt::NoModifier)
{
QMouseEvent event(QEvent::MouseMove, to, to, canvas->mapToGlobal(to.toPoint()), Qt::NoButton, Qt::LeftButton, modifiers);
QApplication::sendEvent(canvas, &event);
}
void release(QPointF at, Qt::KeyboardModifiers modifiers = Qt::NoModifier) { QTest::mouseRelease(canvas, Qt::LeftButton, modifiers, at.toPoint()); }
void drag(QPointF from, QPointF to, Qt::KeyboardModifiers modifiers = Qt::NoModifier)
{
press(from, modifiers);
move((from + to) / 2, modifiers);
move(to, modifiers);
release(to, modifiers);
}
void click(QPointF at, Qt::KeyboardModifiers modifiers = Qt::NoModifier)
{
press(at, modifiers);
release(at, modifiers);
}
};
+18 -39
View File
@@ -1,48 +1,11 @@
#include "CanvasFixtures.h"
#include "MovePressFixtures.h"
// Swift's resize snapping on the canvas: handles, Ctrl, Shift.
namespace {
// The document fills the canvas: view points are pixels.
struct Canvas : Shown {
QUuid red;
Canvas() : Shown(QSize(400, 300), QSize(400, 300))
{
settle();
session.zoom(1);
session.insert(filled(100, 100, qRgba(255, 0, 0, 255), "Red"));
red = session.activeLayerID().value();
session.selectTool(NavigationTool::move);
canvas->synchronizeDisplay();
if (session.viewport.viewPoint(QPointF(0, 0), documentSize()) != QPointF(0, 0))
throw std::runtime_error("the document does not fill the canvas");
}
QPointF origin() const { return layerWith(session, red).transform.origin; }
void press(QPointF at, Qt::KeyboardModifiers modifiers = Qt::NoModifier) { QTest::mousePress(canvas, Qt::LeftButton, modifiers, at.toPoint()); }
void move(QPointF to, Qt::KeyboardModifiers modifiers = Qt::NoModifier)
{
QMouseEvent event(QEvent::MouseMove, to, to, canvas->mapToGlobal(to.toPoint()), Qt::NoButton, Qt::LeftButton, modifiers);
QApplication::sendEvent(canvas, &event);
}
void release(QPointF at, Qt::KeyboardModifiers modifiers = Qt::NoModifier) { QTest::mouseRelease(canvas, Qt::LeftButton, modifiers, at.toPoint()); }
void drag(QPointF from, QPointF to, Qt::KeyboardModifiers modifiers = Qt::NoModifier)
{
press(from, modifiers);
move((from + to) / 2, modifiers);
move(to, modifiers);
release(to, modifiers);
}
void click(QPointF at, Qt::KeyboardModifiers modifiers = Qt::NoModifier)
{
press(at, modifiers);
release(at, modifiers);
}
};
}
class ResizeSnapCanvasTests : public QObject {
Q_OBJECT
private slots:
void resizedEdgesSnapUnlessCtrl();
void altResizesFromTheCentreAndSnaps();
};
void ResizeSnapCanvasTests::resizedEdgesSnapUnlessCtrl()
@@ -81,5 +44,21 @@ void ResizeSnapCanvasTests::resizedEdgesSnapUnlessCtrl()
shown.release(QPointF(397, 349));
}
// Swift's "resizing from the center with Option snaps too".
void ResizeSnapCanvasTests::altResizesFromTheCentreAndSnaps()
{
Canvas shown;
EditorSession &session = shown.session;
session.setLocksTransformRatio(false);
// Right edge to 396, left to 4: 396 snaps.
shown.press(QPointF(250, 150), Qt::AltModifier);
shown.move(QPointF(396, 150), Qt::AltModifier);
QCOMPARE(session.transformEdit().value().draft, (LayerTransform{.origin = {0, 100}, .size = {400, 100}}));
QCOMPARE(session.snapGuides, (SnapGuides{{400}, {}}));
shown.release(QPointF(396, 150), Qt::AltModifier);
QCOMPARE(layerWith(session, shown.red).transform, (LayerTransform{.origin = {0, 100}, .size = {400, 100}}));
QCOMPARE(int(session.document().value().layers.size()), 1);
}
QTEST_MAIN(ResizeSnapCanvasTests)
#include "ResizeSnapCanvasTests.moc"
+27
View File
@@ -41,6 +41,8 @@ private slots:
void aTurnedLayerDoesntSnap();
void eachEdgeSnapsOnItsOwn();
void theDraggedSideAloneSnaps();
void aLongDragSnapsTheEdgeItCarries();
void aTieTakesTheLowEdge();
void snappingOffOrAnotherDragLeavesThePoint();
};
@@ -141,6 +143,31 @@ void ResizeSnapTests::theDraggedSideAloneSnaps()
QCOMPARE(resized.session.snapGuides, (SnapGuides{{}, {150}}));
}
// Dragged past the anchor, the moved edge is the left.
void ResizeSnapTests::aLongDragSnapsTheEdgeItCarries()
{
Resized resized;
const LayerTransform flipped = resized.resize(3, QPointF(110, 60), QPointF(-3, 60), false);
QCOMPARE(flipped.origin.x(), 0.0);
QCOMPARE(flipped.size.width(), 10.0);
QVERIFY(flipped.flipX);
QCOMPARE(resized.session.snapGuides, (SnapGuides{{0}, {}}));
}
// Half a pixel from the anchor: one pixel, pointer mid-way.
void ResizeSnapTests::aTieTakesTheLowEdge()
{
Resized resized;
resized.place(QPointF(13, 150), QSizeF(20, 20));
resized.session.selectLayer(resized.layer);
const TransformDrag drag{layerWith(resized.session, resized.layer).transform, QPointF(110, 60), {TransformDrag::Kind::resize, 3}, std::nullopt};
QCOMPARE(drag.updated(QPointF(10.5, 60), false, false).size.width(), 1.0);
// The anchor's edge, which no step moves: the pointer stays.
QCOMPARE(resized.session.snappedResizePoint(QPointF(10.5, 60), drag, false, {resized.layer}, 5, [&](QPointF to) { return drag.updated(to, false, false); }),
QPointF(10.5, 60));
QCOMPARE(resized.session.snapGuides, (SnapGuides{{13}, {}}));
}
void ResizeSnapTests::snappingOffOrAnotherDragLeavesThePoint()
{
Resized resized;
+1
View File
@@ -230,6 +230,7 @@ compositor_test(TransformInspectorTests)
compositor_test(TransformSnapTests)
compositor_test(ResizeSnapTests)
compositor_test(ResizeSnapCanvasTests)
compositor_test(MoveBarPressTests)
compositor_test(CursorTests)
compositor_test(DistortCanvasTests)
compositor_test(DistortMaskTests)
+1 -39
View File
@@ -1,44 +1,6 @@
#include "CanvasFixtures.h"
#include "MovePressFixtures.h"
// The Move tool on the canvas: drags, handles, snapping.
namespace {
// The document fills the canvas: view points are pixels.
struct Canvas : Shown {
QUuid red;
Canvas() : Shown(QSize(400, 300), QSize(400, 300))
{
settle();
session.zoom(1);
session.insert(filled(100, 100, qRgba(255, 0, 0, 255), "Red"));
red = session.activeLayerID().value();
session.selectTool(NavigationTool::move);
canvas->synchronizeDisplay();
if (session.viewport.viewPoint(QPointF(0, 0), documentSize()) != QPointF(0, 0))
throw std::runtime_error("the document does not fill the canvas");
}
QPointF origin() const { return layerWith(session, red).transform.origin; }
void press(QPointF at, Qt::KeyboardModifiers modifiers = Qt::NoModifier) { QTest::mousePress(canvas, Qt::LeftButton, modifiers, at.toPoint()); }
void move(QPointF to, Qt::KeyboardModifiers modifiers = Qt::NoModifier)
{
QMouseEvent event(QEvent::MouseMove, to, to, canvas->mapToGlobal(to.toPoint()), Qt::NoButton, Qt::LeftButton, modifiers);
QApplication::sendEvent(canvas, &event);
}
void release(QPointF at, Qt::KeyboardModifiers modifiers = Qt::NoModifier) { QTest::mouseRelease(canvas, Qt::LeftButton, modifiers, at.toPoint()); }
void drag(QPointF from, QPointF to, Qt::KeyboardModifiers modifiers = Qt::NoModifier)
{
press(from, modifiers);
move((from + to) / 2, modifiers);
move(to, modifiers);
release(to, modifiers);
}
void click(QPointF at, Qt::KeyboardModifiers modifiers = Qt::NoModifier)
{
press(at, modifiers);
release(at, modifiers);
}
};
}
class TransformPressTests : public QObject {
Q_OBJECT
private slots:
+19
View File
@@ -17,6 +17,7 @@ private slots:
void liquifyStaysSharp();
void liquifyDrawsFromTheUntouchedLayer();
void smudgeLeavesOneFadingTrail();
void aCommitKeepsATwentiethAndTheLastPoint();
};
// Pushed across and back, the layer ends nearly unchanged.
@@ -94,5 +95,23 @@ void WarpStrokeTests::smudgeLeavesOneFadingTrail()
QVERIFY2(row.front() > row.back() + 20, qPrintable(QString("%1 to %2").arg(row.front()).arg(row.back())));
}
// Swift's thinning: a twentieth of the brush apart, last kept.
void WarpStrokeTests::aCommitKeepsATwentiethAndTheLastPoint()
{
QImage image = BrushRaster::context(80, 20, false);
image.fill(Qt::red);
WarpStroke stroke = warp(image, BlurToolMode::liquify, brush(100, 0.5, 0, 0, 0, 1));
stroke.append(QPointF(0, 10));
stroke.append(QPointF(51, 10));
QCOMPARE(int(stroke.points().size()), 21);
const std::vector<QPointF> kept = stroke.committedPoints();
QCOMPARE(int(kept.size()), 8);
QCOMPARE(kept.front(), stroke.points().front());
QCOMPARE(kept[1], stroke.points()[3]);
QCOMPARE(kept[6], stroke.points()[18]);
// Two and a half from the previous, kept as last.
QCOMPARE(kept.back(), QPointF(51, 10));
}
QTEST_GUILESS_MAIN(WarpStrokeTests)
#include "WarpStrokeTests.moc"