Files
OmaPhoto/tests/CursorTests.cpp
T
雷电芽衣andClaude c864304695
Release / deb (push) Canceled after 0s
Release / rpm (push) Canceled after 0s
Release / arch (push) Canceled after 0s
Release / publish (push) Canceled after 0s
OmaPhoto 1.0.0: the Linux port of Compositor
A layered image editor for Linux and Omarchy, ported from Compositor
(github.com/robbietilton/Compositor), whose Swift source is its
specification. It reads and writes Compositor's .comp projects.

Packages for Ubuntu, Fedora and Arch come from the release workflow;
scripts/install/ holds one install script per distribution family,
and flake.nix serves NixOS.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-09-26 00:44:52 -04:00

232 lines
11 KiB
C++

#include "CanvasFixtures.h"
// The Move tool's pointer: move, duplicate, the handles, the grip.
namespace {
QImage image(const QCursor &cursor)
{
return cursor.pixmap().toImage();
}
// A hover with these modifiers, no button held.
void hover(QWidget &widget, QPointF at, Qt::KeyboardModifiers modifiers = Qt::NoModifier)
{
QMouseEvent event(QEvent::MouseMove, at, at, widget.mapToGlobal(at.toPoint()), Qt::NoButton, Qt::NoButton, modifiers);
QApplication::sendEvent(&widget, &event);
}
struct Canvas : Shown {
Canvas() : Shown(QSize(400, 300), QSize(400, 300))
{
settle();
session.zoom(1);
session.insert(filled(100, 100, qRgba(255, 0, 0, 255), "Red"));
session.selectTool(NavigationTool::move);
canvas->synchronizeDisplay();
}
bool shows(const QCursor &cursor) const { return image(canvas->cursor()) == image(cursor); }
};
}
class CursorTests : public QObject {
Q_OBJECT
private slots:
void duplicateAndResizeCursors();
void hiddenTransformControlsLeaveOnlyMoving();
void aDragKeepsItsCursorAndBusySpaceAndOtherToolsTheirs();
void ctrlOverTheSelectionShowsTheScissors();
};
void CursorTests::duplicateAndResizeCursors()
{
Canvas shown;
CanvasView &canvas = *shown.canvas;
const double ratio = canvas.devicePixelRatio();
hover(canvas, QPointF(200, 150));
QVERIFY(shown.shows(CanvasView::moveCursor(ratio)));
hover(canvas, QPointF(200, 150), Qt::AltModifier);
QVERIFY(shown.shows(CanvasView::duplicateCursor(ratio)));
// A press anywhere drags the active layer: Alt anywhere duplicates.
hover(canvas, QPointF(20, 20), Qt::AltModifier);
QVERIFY(shown.shows(CanvasView::duplicateCursor(ratio)));
hover(canvas, QPointF(20, 20));
QVERIFY(shown.shows(CanvasView::moveCursor(ratio)));
// Alt from the keyboard counts too.
QTest::keyPress(&canvas, Qt::Key_Alt, Qt::AltModifier);
QVERIFY(shown.shows(CanvasView::duplicateCursor(ratio)));
QTest::keyRelease(&canvas, Qt::Key_Alt);
QVERIFY(shown.shows(CanvasView::moveCursor(ratio)));
// Corners and edges resize with the box; the grip rotates.
hover(canvas, QPointF(150, 100));
QCOMPARE(canvas.cursor().shape(), Qt::SizeFDiagCursor);
hover(canvas, QPointF(250, 100));
QCOMPARE(canvas.cursor().shape(), Qt::SizeBDiagCursor);
hover(canvas, QPointF(250, 150));
QCOMPARE(canvas.cursor().shape(), Qt::SizeHorCursor);
hover(canvas, QPointF(200, 200));
QCOMPARE(canvas.cursor().shape(), Qt::SizeVerCursor);
hover(canvas, QPointF(200, 72));
QVERIFY(shown.shows(CanvasView::rotationCursor(ratio)));
// The move pointer carries a badge the plain arrow lacks.
QVERIFY(image(CanvasView::moveCursor(1)).pixelColor(19, 21).lightness() < 128);
QVERIFY(image(CanvasView::rotationCursor(1)).pixelColor(12, 5).lightness() < 128);
QVERIFY(image(CanvasView::rotationCursor(1)).pixelColor(12, 12).alpha() == 0);
// A hidden active layer moves nothing; Ctrl picks red.
QVERIFY(canvas.hasMouseTracking());
const QUuid red = shown.session.activeLayerID().value();
shown.session.insert(filled(10, 10, qRgba(0, 0, 255, 255), "Blue"), QPointF(50, 250));
const QUuid blue = shown.session.activeLayerID().value();
shown.session.toggleLayerVisibility(blue);
canvas.synchronizeDisplay();
hover(canvas, QPointF(200, 150));
QCOMPARE(canvas.cursor().shape(), Qt::ArrowCursor);
hover(canvas, QPointF(200, 150), Qt::ControlModifier);
QVERIFY(shown.shows(CanvasView::moveCursor(ratio)));
hover(canvas, QPointF(200, 150));
QCOMPARE(canvas.cursor().shape(), Qt::ArrowCursor);
QTest::keyPress(&canvas, Qt::Key_Control, Qt::ControlModifier);
QVERIFY(shown.shows(CanvasView::moveCursor(ratio)));
QTest::keyRelease(&canvas, Qt::Key_Control);
QCOMPARE(canvas.cursor().shape(), Qt::ArrowCursor);
// Released while hidden, Ctrl is read again on show.
QTest::keyPress(shown.window.windowHandle(), Qt::Key_Control, Qt::ControlModifier);
QVERIFY(shown.shows(CanvasView::moveCursor(ratio)));
canvas.hide();
QTest::keyRelease(shown.window.windowHandle(), Qt::Key_Control);
canvas.show();
QCOMPARE(canvas.cursor().shape(), Qt::ArrowCursor);
// With no layer at all, the plain arrow.
shown.session.selectLayers({red, blue}, red);
shown.session.deleteSelectedLayers();
QVERIFY(shown.session.document().value().layers.empty());
hover(canvas, QPointF(200, 150), Qt::ControlModifier);
QCOMPARE(canvas.cursor().shape(), Qt::ArrowCursor);
}
void CursorTests::hiddenTransformControlsLeaveOnlyMoving()
{
Canvas shown;
CanvasView &canvas = *shown.canvas;
EditorSession &session = shown.session;
const double ratio = canvas.devicePixelRatio();
QVERIFY(session.showsTransformControls());
hover(canvas, QPointF(150, 100));
QCOMPARE(canvas.cursor().shape(), Qt::SizeFDiagCursor);
session.setShowsTransformControls(false);
canvas.synchronizeDisplay();
QVERIFY(shown.shows(CanvasView::moveCursor(ratio)));
hover(canvas, QPointF(150, 100), Qt::ControlModifier);
QVERIFY(shown.shows(CanvasView::moveCursor(ratio)));
hover(canvas, QPointF(150, 100));
// A pending Ctrl+T transform shows its box again.
session.beginTransform(true);
canvas.synchronizeDisplay();
QCOMPARE(canvas.cursor().shape(), Qt::SizeFDiagCursor);
session.cancelTransform();
canvas.synchronizeDisplay();
QVERIFY(shown.shows(CanvasView::moveCursor(ratio)));
}
void CursorTests::aDragKeepsItsCursorAndBusySpaceAndOtherToolsTheirs()
{
Canvas shown;
CanvasView &canvas = *shown.canvas;
EditorSession &session = shown.session;
const double ratio = canvas.devicePixelRatio();
// Over a handle mid-drag, the drag's own cursor stays.
QTest::mousePress(&canvas, Qt::LeftButton, Qt::NoModifier, QPoint(20, 20));
QVERIFY(shown.shows(CanvasView::moveCursor(ratio)));
drag(canvas, QPointF(150, 100));
QVERIFY(shown.shows(CanvasView::moveCursor(ratio)));
QTest::mouseRelease(&canvas, Qt::LeftButton, Qt::NoModifier, QPoint(150, 100));
QVERIFY(shown.shows(CanvasView::moveCursor(ratio)));
QTest::mousePress(&canvas, Qt::LeftButton, Qt::AltModifier, QPoint(20, 20));
QVERIFY(shown.shows(CanvasView::duplicateCursor(ratio)));
QTest::mouseRelease(&canvas, Qt::LeftButton, Qt::AltModifier, QPoint(20, 20));
hover(canvas, QPointF(20, 20));
QVERIFY(shown.shows(CanvasView::moveCursor(ratio)));
// The grip's drag keeps the rotation pointer off the grip.
session.undo();
canvas.synchronizeDisplay();
QTest::mousePress(&canvas, Qt::LeftButton, Qt::NoModifier, QPoint(200, 72));
QVERIFY(shown.shows(CanvasView::rotationCursor(ratio)));
drag(canvas, QPointF(300, 150));
QVERIFY(shown.shows(CanvasView::rotationCursor(ratio)));
QTest::mouseRelease(&canvas, Qt::LeftButton, Qt::NoModifier, QPoint(300, 150));
QVERIFY(!shown.shows(CanvasView::rotationCursor(ratio)));
session.undo();
// Busy shows the arrow, edit or not; Space the hand.
session.beginTransform();
canvas.synchronizeDisplay();
QVERIFY(shown.shows(CanvasView::moveCursor(ratio)));
session.setIsProjectBusy(true);
canvas.synchronizeDisplay();
QCOMPARE(canvas.cursor().shape(), Qt::ArrowCursor);
session.setIsProjectBusy(false);
session.cancelTransform();
canvas.synchronizeDisplay();
QTRY_VERIFY(canvas.hasFocus());
QTest::keyPress(&canvas, Qt::Key_Space);
QCOMPARE(canvas.cursor().shape(), Qt::OpenHandCursor);
QTest::keyRelease(&canvas, Qt::Key_Space);
QVERIFY(shown.shows(CanvasView::moveCursor(ratio)));
session.selectTool(NavigationTool::brush);
canvas.synchronizeDisplay();
QCOMPARE(canvas.cursor().shape(), Qt::CrossCursor);
// Space mid-resize leaves the drag its cursor.
session.selectTool(NavigationTool::move);
canvas.synchronizeDisplay();
QTest::mousePress(&canvas, Qt::LeftButton, Qt::NoModifier, QPoint(250, 200));
QCOMPARE(canvas.cursor().shape(), Qt::SizeFDiagCursor);
QTest::keyPress(&canvas, Qt::Key_Space);
QCOMPARE(canvas.cursor().shape(), Qt::SizeFDiagCursor);
QTest::keyRelease(&canvas, Qt::Key_Space);
QTest::mouseRelease(&canvas, Qt::LeftButton, Qt::NoModifier, QPoint(250, 200));
}
void CursorTests::ctrlOverTheSelectionShowsTheScissors()
{
Canvas shown;
QWidget &canvas = *shown.canvas;
const double ratio = canvas.devicePixelRatioF();
shown.session.selectTool(NavigationTool::marquee);
shown.session.selectAll();
shown.canvas->synchronizeDisplay();
hover(canvas, QPointF(200, 150), Qt::ControlModifier);
QVERIFY(shown.shows(CanvasView::movePixelsCursor(ratio)));
hover(canvas, QPointF(200, 150), Qt::ControlModifier | Qt::AltModifier);
QVERIFY(shown.shows(CanvasView::duplicateCursor(ratio)));
hover(canvas, QPointF(200, 150));
QVERIFY(shown.shows(CanvasView::moveSelectionCursor(ratio)));
// Scissors hang below the arrow, unlike the move badge.
QVERIFY(image(CanvasView::movePixelsCursor(1)) != image(CanvasView::moveCursor(1)));
const QColor scissors = image(CanvasView::movePixelsCursor(1)).pixelColor(13, 23);
QVERIFY(scissors.alpha() > 128 && scissors.lightness() < 128);
// Mid-drag the scissors stay, Ctrl held or not.
QTest::mousePress(&canvas, Qt::LeftButton, Qt::ControlModifier, QPoint(200, 150));
QVERIFY(shown.session.pixelMove() && !shown.session.pixelMove()->duplicate);
QMouseEvent drag(QEvent::MouseMove, QPointF(220, 150), QPointF(220, 150), canvas.mapToGlobal(QPoint(220, 150)), Qt::NoButton, Qt::LeftButton, Qt::NoModifier);
QApplication::sendEvent(&canvas, &drag);
QVERIFY(shown.shows(CanvasView::movePixelsCursor(ratio)));
// Space mid-drag keeps the drag's own cursor.
QTest::keyPress(&canvas, Qt::Key_Space);
QVERIFY(shown.shows(CanvasView::movePixelsCursor(ratio)));
QTest::keyRelease(&canvas, Qt::Key_Space);
QTest::mouseRelease(&canvas, Qt::LeftButton, Qt::NoModifier, QPoint(220, 150));
QTRY_VERIFY(!shown.session.pixelMove());
QCOMPARE(shown.session.history.undoName(), QString("Move Pixels"));
// Released, the plain pointer moves the outline again.
hover(canvas, QPointF(220, 150));
QVERIFY(shown.shows(CanvasView::moveSelectionCursor(ratio)));
// A copy drag keeps its cursor under Space too.
QTest::mousePress(&canvas, Qt::LeftButton, Qt::ControlModifier | Qt::AltModifier, QPoint(220, 150));
QVERIFY(shown.session.pixelMove() && shown.session.pixelMove()->duplicate);
QTest::keyPress(&canvas, Qt::Key_Space);
QVERIFY(shown.shows(CanvasView::duplicateCursor(ratio)));
QTest::keyRelease(&canvas, Qt::Key_Space);
QTest::mouseRelease(&canvas, Qt::LeftButton, Qt::NoModifier, QPoint(220, 150));
QTRY_VERIFY(!shown.session.pixelMove());
}
QTEST_MAIN(CursorTests)
#include "CursorTests.moc"