Middle-button pan, persistent tool settings, Auto Select's foreground rule, the new icon (12.6b)
This commit is contained in:
1 parent
5a2fa91bc7
commit
29eab10edc
27 files changed
+390
-22
No files matched your search
@@ -9,6 +9,7 @@ private slots:
|
||||
void theHandToolAndSpaceDragTheView();
|
||||
void theWheelPansAndZoomsWithAModifier();
|
||||
void aPinchZoomsAboutThePointer();
|
||||
void theMiddleButtonPansFromAnyTool();
|
||||
};
|
||||
|
||||
void CanvasNavigationTests::theZoomToolClicksAndDrags()
|
||||
@@ -167,5 +168,66 @@ void CanvasNavigationTests::aPinchZoomsAboutThePointer()
|
||||
QCOMPARE(shown.session.viewport.zoom(), 1.5);
|
||||
}
|
||||
|
||||
|
||||
void CanvasNavigationTests::theMiddleButtonPansFromAnyTool()
|
||||
{
|
||||
Shown shown;
|
||||
CanvasView &canvas = *shown.canvas;
|
||||
EditorSession &session = shown.session;
|
||||
shown.settle();
|
||||
session.zoom(1);
|
||||
session.selectTool(NavigationTool::brush);
|
||||
canvas.synchronizeDisplay();
|
||||
const auto move = [&](QPointF to, Qt::MouseButtons buttons) {
|
||||
QMouseEvent event(QEvent::MouseMove, to, to, canvas.mapToGlobal(to.toPoint()), Qt::NoButton, buttons, Qt::NoModifier);
|
||||
QApplication::sendEvent(&canvas, &event);
|
||||
};
|
||||
move(QPointF(100, 100), Qt::NoButton);
|
||||
const Qt::CursorShape brush = canvas.cursor().shape();
|
||||
QVERIFY(canvas.brushCursor().circle().has_value());
|
||||
const QSizeF pan = session.viewport.pan;
|
||||
QSignalSpy changes(&session, &EditorSession::changed);
|
||||
// The circle hides and nothing paints; the view follows.
|
||||
QTest::mousePress(&canvas, Qt::MiddleButton, Qt::NoModifier, QPoint(100, 100));
|
||||
QCOMPARE(canvas.cursor().shape(), Qt::ClosedHandCursor);
|
||||
QVERIFY(!canvas.brushCursor().circle().has_value());
|
||||
move(QPointF(130, 120), Qt::MiddleButton);
|
||||
QCOMPARE(session.viewport.pan, pan + QSizeF(30, 20));
|
||||
QCOMPARE(changes.count(), 1);
|
||||
move(QPointF(150, 150), Qt::MiddleButton);
|
||||
QCOMPARE(session.viewport.pan, pan + QSizeF(50, 50));
|
||||
move(QPointF(130, 120), Qt::MiddleButton);
|
||||
QCOMPARE(session.viewport.pan, pan + QSizeF(30, 20));
|
||||
QVERIFY(!session.brushStroke() && !canvas.brushCursor().circle().has_value());
|
||||
QCOMPARE(canvas.cursor().shape(), Qt::ClosedHandCursor);
|
||||
QTest::mouseRelease(&canvas, Qt::MiddleButton, Qt::NoModifier, QPoint(130, 120));
|
||||
QCOMPARE(canvas.cursor().shape(), brush);
|
||||
QVERIFY(canvas.brushCursor().circle().has_value());
|
||||
// Beside a Hand drag each button keeps its own point.
|
||||
session.selectTool(NavigationTool::hand);
|
||||
canvas.synchronizeDisplay();
|
||||
QTest::mousePress(&canvas, Qt::LeftButton, Qt::NoModifier, QPoint(50, 50));
|
||||
QTest::mousePress(&canvas, Qt::MiddleButton, Qt::NoModifier, QPoint(200, 200));
|
||||
move(QPointF(60, 50), Qt::LeftButton | Qt::MiddleButton);
|
||||
QCOMPARE(session.viewport.pan, pan + QSizeF(30, 20) + QSizeF(10, 0) + QSizeF(-140, -150));
|
||||
QTest::mouseRelease(&canvas, Qt::MiddleButton, Qt::NoModifier, QPoint(60, 50));
|
||||
QCOMPARE(canvas.cursor().shape(), Qt::ClosedHandCursor);
|
||||
QTest::mouseRelease(&canvas, Qt::LeftButton, Qt::NoModifier, QPoint(60, 50));
|
||||
QCOMPARE(canvas.cursor().shape(), Qt::OpenHandCursor);
|
||||
// A lost release: a buttonless move ends the pan.
|
||||
const QSizeF held = session.viewport.pan;
|
||||
QTest::mousePress(&canvas, Qt::MiddleButton, Qt::NoModifier, QPoint(50, 50));
|
||||
move(QPointF(70, 70), Qt::NoButton);
|
||||
QCOMPARE(canvas.cursor().shape(), Qt::OpenHandCursor);
|
||||
move(QPointF(90, 90), Qt::MiddleButton);
|
||||
QCOMPARE(session.viewport.pan, held);
|
||||
// Without a document the middle button does nothing.
|
||||
Shown empty(std::nullopt);
|
||||
empty.settle();
|
||||
const Qt::CursorShape before = empty.canvas->cursor().shape();
|
||||
QTest::mousePress(empty.canvas, Qt::MiddleButton, Qt::NoModifier, QPoint(50, 50));
|
||||
QCOMPARE(empty.canvas->cursor().shape(), before);
|
||||
}
|
||||
|
||||
QTEST_MAIN(CanvasNavigationTests)
|
||||
#include "CanvasNavigationTests.moc"
|
||||
@@ -38,7 +38,7 @@ class DistortTests : public QObject {
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void cornersRunClockwiseFromTheTopLeft();
|
||||
void perspectiveMappingHitsTheCornersAndTwistedShapesAreRefused();
|
||||
void perspectiveMappingHitsTheCornersAndDegenerateShapesAreRefused();
|
||||
void distortingWarpsTheLayerIntoTheShapeAsOneUndoStep();
|
||||
void distortedLayerIsTrimmedToItsVisiblePixels();
|
||||
void foldedShapesWarpAsTwoTrianglesAndFlipsSwapCorners();
|
||||
@@ -61,7 +61,7 @@ void DistortTests::cornersRunClockwiseFromTheTopLeft()
|
||||
QVERIFY(!near(corners[1], corners[0]));
|
||||
}
|
||||
|
||||
void DistortTests::perspectiveMappingHitsTheCornersAndTwistedShapesAreRefused()
|
||||
void DistortTests::perspectiveMappingHitsTheCornersAndDegenerateShapesAreRefused()
|
||||
{
|
||||
const QTransform map = DistortWarp::homography(shape);
|
||||
const Corners unit = {QPointF(0, 0), QPointF(1, 0), QPointF(1, 1), QPointF(0, 1)};
|
||||
@@ -108,6 +108,9 @@ void DistortTests::distortingWarpsTheLayerIntoTheShapeAsOneUndoStep()
|
||||
// A collapsed corner: ignored.
|
||||
session.previewCorners({shape[0], shape[0], shape[2], shape[3]});
|
||||
QCOMPARE(session.transformEdit().value().corners.value()[1], QPointF(30, 10));
|
||||
// A folded shape is a distortion of its own.
|
||||
session.previewCorners({shape[0], shape[2], shape[1], shape[3]});
|
||||
QCOMPARE(session.transformEdit().value().corners.value()[1], shape[2]);
|
||||
session.previewCorners(shape);
|
||||
QCOMPARE(session.transformEdit().value().corners.value(), shape);
|
||||
const int count = session.history.undoCount();
|
||||
|
||||
@@ -293,6 +293,8 @@ void SelectionCanvasTests::aCtrlDragMovesSelectedPixelsAndCtrlArrowsNudgeThem()
|
||||
QCOMPARE(session.history.undoCount(), count + 2);
|
||||
// Ctrl-Alt copies; losing the keys mid-drag cancels.
|
||||
session.selectTool(NavigationTool::marquee);
|
||||
// The nudge's move ends a turn after its step lands.
|
||||
QTRY_VERIFY(!session.pixelMove());
|
||||
shown.press(QPointF(270, 40), Qt::ControlModifier | Qt::AltModifier);
|
||||
QVERIFY(session.pixelMove() && session.pixelMove()->duplicate);
|
||||
shown.move(QPointF(300, 40), Qt::ControlModifier | Qt::AltModifier);
|
||||
|
||||
@@ -204,6 +204,7 @@ compositor_test(PSDReaderLayerTests)
|
||||
compositor_test(PSDImportTests)
|
||||
compositor_test(RawImporterTests)
|
||||
compositor_test(RawDevelopTests)
|
||||
compositor_test(ToolDefaultsTests)
|
||||
compositor_test(LayerEffectsRendererTests)
|
||||
compositor_test(LayerFlipTests)
|
||||
compositor_test(LayerMergeTests)
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
#include "Document/EditorSession.h"
|
||||
#include "Document/ToolDefaults.h"
|
||||
#include <QRegularExpression>
|
||||
#include <QSettings>
|
||||
#include <QTemporaryDir>
|
||||
#include <QtTest>
|
||||
|
||||
// Swift's ToolDefaults: toggles that outlive the session and the run.
|
||||
namespace {
|
||||
struct Toggle {
|
||||
QString key;
|
||||
bool fallback;
|
||||
std::function<bool(const EditorSession &)> read;
|
||||
std::function<void(EditorSession &, bool)> write;
|
||||
};
|
||||
|
||||
std::vector<Toggle> toggles()
|
||||
{
|
||||
return {
|
||||
{"autoSelect", false, &EditorSession::transformAutoSelect, &EditorSession::setTransformAutoSelect},
|
||||
{"transformControls", true, &EditorSession::showsTransformControls, &EditorSession::setShowsTransformControls},
|
||||
{"pixelGrid", true, &EditorSession::showsPixelGrid, &EditorSession::setShowsPixelGrid},
|
||||
{"grid", false, &EditorSession::showsGrid, &EditorSession::setShowsGrid},
|
||||
{"guides", true, &EditorSession::showsGuides, &EditorSession::setShowsGuides},
|
||||
{"rulers", false, &EditorSession::showsRulers, &EditorSession::setShowsRulers},
|
||||
{"snap", true, &EditorSession::snapEnabled, &EditorSession::setSnapEnabled},
|
||||
{"snapGuides", true, &EditorSession::snapToGuides, &EditorSession::setSnapToGuides},
|
||||
{"snapGrid", false, &EditorSession::snapToGrid, &EditorSession::setSnapToGrid},
|
||||
{"snapLayers", true, &EditorSession::snapToLayers, &EditorSession::setSnapToLayers},
|
||||
{"snapBounds", true, &EditorSession::snapToDocumentBounds, &EditorSession::setSnapToDocumentBounds},
|
||||
{"lockGuides", false, &EditorSession::locksGuides, &EditorSession::setLocksGuides},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class ToolDefaultsTests : public QObject {
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void untilTheAppTurnsThemOnTheDefaultsHold();
|
||||
void everyToggleOutlivesItsSession();
|
||||
void showingAGuideRemembersGuidesShown();
|
||||
void aStrangeValueIsIgnoredAndSaid();
|
||||
|
||||
private:
|
||||
QTemporaryDir m_folder;
|
||||
};
|
||||
|
||||
void ToolDefaultsTests::initTestCase()
|
||||
{
|
||||
// Settings land in a folder of the test's own.
|
||||
QSettings::setPath(QSettings::NativeFormat, QSettings::UserScope, m_folder.path());
|
||||
}
|
||||
|
||||
void ToolDefaultsTests::untilTheAppTurnsThemOnTheDefaultsHold()
|
||||
{
|
||||
QSettings().setValue("tool.grid", true);
|
||||
QVERIFY(!ToolDefaults::boolean("grid", false));
|
||||
ToolDefaults::set(false, "rulers");
|
||||
QVERIFY(!QSettings().contains("tool.rulers"));
|
||||
EditorSession session;
|
||||
QVERIFY(!session.showsGrid());
|
||||
session.setShowsRulers(true);
|
||||
QVERIFY(!QSettings().contains("tool.rulers"));
|
||||
QSettings().remove("tool.grid");
|
||||
}
|
||||
|
||||
void ToolDefaultsTests::everyToggleOutlivesItsSession()
|
||||
{
|
||||
ToolDefaults::enable();
|
||||
// An unset toggle is no strange value.
|
||||
QTest::failOnWarning(QRegularExpression("ignoring the tool setting"));
|
||||
for (const Toggle &toggle : toggles()) {
|
||||
QVERIFY(!QSettings().contains("tool." + toggle.key));
|
||||
QCOMPARE(toggle.read(EditorSession()), toggle.fallback);
|
||||
{
|
||||
EditorSession session;
|
||||
toggle.write(session, !toggle.fallback);
|
||||
}
|
||||
QCOMPARE(QSettings().value("tool." + toggle.key).toBool(), !toggle.fallback);
|
||||
QVERIFY2(toggle.read(EditorSession()) == !toggle.fallback, qPrintable(toggle.key));
|
||||
// The next run reads text, as the file keeps it.
|
||||
QSettings().setValue("tool." + toggle.key, !toggle.fallback ? "true" : "false");
|
||||
QCOMPARE(ToolDefaults::boolean(toggle.key, toggle.fallback), !toggle.fallback);
|
||||
QSettings().remove("tool." + toggle.key);
|
||||
}
|
||||
}
|
||||
|
||||
void ToolDefaultsTests::showingAGuideRemembersGuidesShown()
|
||||
{
|
||||
ToolDefaults::enable();
|
||||
EditorSession session;
|
||||
session.createDocument(100, 100);
|
||||
for (const bool adding : {true, false}) {
|
||||
session.setShowsGuides(false);
|
||||
QCOMPARE(QSettings().value("tool.guides").toString(), QString("false"));
|
||||
if (adding)
|
||||
session.addGuide(CanvasGuide{QUuid::createUuid(), CanvasGuide::Axis::vertical, 40});
|
||||
else
|
||||
session.beginGuideCreation(CanvasGuide::Axis::horizontal, 20);
|
||||
QVERIFY(session.showsGuides());
|
||||
QCOMPARE(QSettings().value("tool.guides").toString(), QString("true"));
|
||||
QVERIFY(EditorSession().showsGuides());
|
||||
}
|
||||
QSettings().remove("tool.guides");
|
||||
}
|
||||
|
||||
void ToolDefaultsTests::aStrangeValueIsIgnoredAndSaid()
|
||||
{
|
||||
ToolDefaults::enable();
|
||||
QSettings().setValue("tool.grid", "maybe");
|
||||
QTest::ignoreMessage(QtWarningMsg, "ignoring the tool setting grid of maybe");
|
||||
QVERIFY(!ToolDefaults::boolean("grid", false));
|
||||
QTest::ignoreMessage(QtWarningMsg, "ignoring the tool setting grid of maybe");
|
||||
QVERIFY(ToolDefaults::boolean("grid", true));
|
||||
QSettings().setValue("tool.grid", 1);
|
||||
QTest::ignoreMessage(QtWarningMsg, "ignoring the tool setting grid of 1");
|
||||
QVERIFY(!ToolDefaults::boolean("grid", false));
|
||||
QSettings().remove("tool.grid");
|
||||
}
|
||||
|
||||
QTEST_GUILESS_MAIN(ToolDefaultsTests)
|
||||
#include "ToolDefaultsTests.moc"
|
||||
@@ -47,6 +47,7 @@ private slots:
|
||||
void handlesResizeAndTheGripRotates();
|
||||
void movesSnapToTheCanvasAndOtherLayersUnlessCtrl();
|
||||
void autoSelectCanBeDisabledAndCtrlClickOverridesIt();
|
||||
void autoSelectPicksTheForegroundOverASelectedBackground();
|
||||
void escapeMidDragRestoresAndAPersistentEditWaitsForReturn();
|
||||
void losingTheKeysOrTheWheelMidDrag();
|
||||
void aSelectionDragsAsOneBox();
|
||||
@@ -190,16 +191,21 @@ void TransformPressTests::autoSelectCanBeDisabledAndCtrlClickOverridesIt()
|
||||
session.setTransformAutoSelect(true);
|
||||
shown.drag(QPointF(20, 250), QPointF(33, 250));
|
||||
QCOMPARE(shown.origin(), QPointF(163, 100));
|
||||
// Inside the active layer's box, it drags, whatever lies above.
|
||||
// Inside the active layer's box a layer above wins, auto-selecting.
|
||||
session.insert(filled(100, 100, qRgba(0, 255, 0, 255), "Green"), QPointF(220, 120));
|
||||
const QUuid green = session.activeLayerID().value();
|
||||
session.selectLayer(shown.red);
|
||||
shown.click(QPointF(213, 150));
|
||||
QCOMPARE(session.activeLayerID(), std::optional(green));
|
||||
session.selectLayer(shown.red);
|
||||
session.setTransformAutoSelect(false);
|
||||
shown.click(QPointF(213, 150));
|
||||
QCOMPARE(session.activeLayerID(), std::optional(shown.red));
|
||||
// Ctrl picks what lies under the pointer, active included.
|
||||
const QUuid green = session.activeLayerID() == shown.red ? session.document().value().layers.back().id : shown.red;
|
||||
shown.click(QPointF(213, 150), Qt::ControlModifier);
|
||||
QCOMPARE(session.activeLayerID(), std::optional(green));
|
||||
session.selectLayer(shown.red);
|
||||
session.setTransformAutoSelect(true);
|
||||
// A selection's box drags them all; outside it auto-select picks.
|
||||
session.insert(filled(100, 100, qRgba(255, 255, 0, 255), "Yellow"), QPointF(60, 260));
|
||||
const QUuid yellow = session.activeLayerID().value();
|
||||
@@ -211,6 +217,60 @@ void TransformPressTests::autoSelectCanBeDisabledAndCtrlClickOverridesIt()
|
||||
QCOMPARE(session.selectedLayerIDs(), QSet<QUuid>{yellow});
|
||||
}
|
||||
|
||||
void TransformPressTests::autoSelectPicksTheForegroundOverASelectedBackground()
|
||||
{
|
||||
Shown shown(QSize(400, 300), QSize(400, 300));
|
||||
shown.settle();
|
||||
EditorSession &session = shown.session;
|
||||
session.zoom(1);
|
||||
session.insert(filled(400, 300, qRgba(0, 0, 255, 255), "Sky"));
|
||||
const QUuid background = session.activeLayerID().value();
|
||||
session.insert(filled(80, 60, qRgba(255, 0, 0, 255), "Kite"), QPointF(190, 80));
|
||||
const QUuid foreground = session.activeLayerID().value();
|
||||
session.selectTool(NavigationTool::move);
|
||||
shown.canvas->synchronizeDisplay();
|
||||
const auto click = [&](QPoint at) {
|
||||
QTest::mouseClick(shown.canvas, Qt::LeftButton, Qt::NoModifier, at);
|
||||
session.commitTransform();
|
||||
};
|
||||
// Swift's test: the background holds every press; the kite wins.
|
||||
session.selectLayer(background);
|
||||
session.setTransformAutoSelect(true);
|
||||
click(QPoint(180, 70));
|
||||
QCOMPARE(session.activeLayerID(), std::optional(foreground));
|
||||
click(QPoint(20, 20));
|
||||
QCOMPARE(session.activeLayerID(), std::optional(background));
|
||||
click(QPoint(180, 70));
|
||||
QCOMPARE(session.activeLayerID(), std::optional(foreground));
|
||||
session.selectLayer(background);
|
||||
session.setTransformAutoSelect(false);
|
||||
click(QPoint(180, 70));
|
||||
QCOMPARE(session.activeLayerID(), std::optional(background));
|
||||
// A Ctrl+T edit keeps its layer; a plain press picks.
|
||||
session.setTransformAutoSelect(true);
|
||||
session.beginTransform();
|
||||
QTest::mouseClick(shown.canvas, Qt::LeftButton, Qt::NoModifier, QPoint(180, 70));
|
||||
QCOMPARE(session.activeLayerID(), std::optional(background));
|
||||
session.cancelTransform();
|
||||
session.setTransformAutoSelect(false);
|
||||
// A wide mask box keeps its layer over lower ones.
|
||||
session.selectLayer(foreground);
|
||||
session.addMask();
|
||||
session.toggleMaskLink(foreground);
|
||||
session.selectLayerTarget(foreground, true);
|
||||
QVERIFY(session.transformTargetsMask());
|
||||
session.beginTransform();
|
||||
session.previewTransform(LayerTransform(QPointF(40, 20), QSizeF(300, 200)));
|
||||
session.commitTransform();
|
||||
session.setTransformAutoSelect(true);
|
||||
click(QPoint(60, 40));
|
||||
QCOMPARE(session.activeLayerID(), std::optional(foreground));
|
||||
// Over empty canvas nothing lies under the pointer.
|
||||
session.toggleLayerVisibility(background);
|
||||
click(QPoint(60, 40));
|
||||
QCOMPARE(session.activeLayerID(), std::optional(foreground));
|
||||
}
|
||||
|
||||
void TransformPressTests::escapeMidDragRestoresAndAPersistentEditWaitsForReturn()
|
||||
{
|
||||
Canvas shown;
|
||||
|
||||
Reference in new issue
Block a user