#include "Document/BrushStroke.h" #include "ScrubFixtures.h" #include "UI/CanvasSizeSheet.h" #include "UI/ColorPickerSheet.h" #include "UI/EffectsSheet.h" #include "UI/FilterSheet.h" #include "UI/HueSaturationSheet.h" #include "UI/ImageSizeSheet.h" #include "UI/LevelsSheet.h" #include #include // Swift's scrubbable titles in the sheets and panels. namespace { // A document with one coloured layer, left active. void paint(EditorSession &session) { session.createDocument(8, 8); QImage image = BrushRaster::context(8, 8, false); image.fill(QColor(120, 160, 200)); session.insert(ImportedImage(image, image, QStringLiteral("Colour"))); } void choose(QComboBox &box, const QString &text) { const int index = box.findText(text); box.setCurrentIndex(index); emit box.activated(index); } } class ScrubbableSheetsTests : public QObject { Q_OBJECT private slots: void effectsTitlesScrubToTheTypedLimit(); void hueSaturationTitlesFollowColorize(); void levelsTitlesScrubTonesAndGamma(); void filterTitlesMoveALastDecimalAPoint(); void colorPickerChannelsScrubWholeLevels(); void filterTitlesKeepEveryDecimalsSensitivity(); void canvasSizeTitlesScrubInTheirUnit(); void canvasSizeCentimetresScrubTheirLimits(); void imageSizeTitlesScrubPixelsPrintSizeAndResolution(); void imageSizeLimitsFollowUnitsLockAndResolution(); }; void ScrubbableSheetsTests::effectsTitlesScrubToTheTypedLimit() { EditorSession session; paint(session); session.addEffect(LayerEffectKind::stroke); EffectsSheet sheet(session, LayerEffectKind::stroke); showActive(sheet); const auto stroke = [&session] { return session.editingEffects().stroke.value(); }; drag(scrubbed(sheet, "Size"), 10); QCOMPARE(stroke().size, 14.0); // Past the slider's end, to what typing reaches. drag(scrubbed(sheet, "Size"), 9000); QCOMPARE(stroke().size, 500.0); drag(scrubbed(sheet, "Size"), -9000); QCOMPARE(stroke().size, 0.0); drag(scrubbed(sheet, "Opacity"), -30.5); QVERIFY(qAbs(stroke().opacity - 0.695) < 1e-12); QCOMPARE(scrubOverTyping(find(sheet, "sizeField"), scrubbed(sheet, "Size"), 3), QString("3")); } void ScrubbableSheetsTests::hueSaturationTitlesFollowColorize() { EditorSession session; paint(session); session.beginHueSaturation(); HueSaturationSheet sheet(session); showActive(sheet); const auto settings = [&session] { return session.hueSaturation().value().settings; }; drag(scrubbed(sheet, "Hue"), 25.5); QCOMPARE(settings().hue(), 25.5); drag(scrubbed(sheet, "Hue"), 900); QCOMPARE(settings().hue(), 180.0); drag(scrubbed(sheet, "Hue"), -900); QCOMPARE(settings().hue(), -180.0); drag(scrubbed(sheet, "Saturation"), -900); QCOMPARE(settings().saturation(), -100.0); drag(scrubbed(sheet, "Lightness"), 40); QCOMPARE(settings().lightness(), 40.0); // Colorize sets hue and saturation outright: new ranges. for (QCheckBox *box : sheet.findChildren()) if (box->text() == "Colorize") box->click(); QVERIFY(settings().colorize); drag(scrubbed(sheet, "Hue"), 900); QCOMPARE(settings().hue(), 360.0); drag(scrubbed(sheet, "Saturation"), -900); QCOMPARE(settings().saturation(), 0.0); QCOMPARE(scrubOverTyping(find(sheet, "lightnessField"), scrubbed(sheet, "Lightness"), 3), QString("3")); } void ScrubbableSheetsTests::levelsTitlesScrubTonesAndGamma() { EditorSession session; paint(session); session.beginLevels(); LevelsSheet sheet(session); showActive(sheet); const auto range = [&session] { return session.levels().value().settings.current(); }; // Gamma moves a hundredth a point, within 0.1 and 9.99. drag(scrubbed(sheet, "Gamma"), 50); QVERIFY(qAbs(range().gamma - 1.5) < 1e-12); drag(scrubbed(sheet, "Gamma"), 9000); QCOMPARE(range().gamma, 9.99); drag(scrubbed(sheet, "Gamma"), -9000); QCOMPARE(range().gamma, 0.1); drag(scrubbed(sheet, "Input black"), 20); QCOMPARE(range().black, 20.0); drag(scrubbed(sheet, "Input black"), -900); QCOMPARE(range().black, 0.0); drag(scrubbed(sheet, "Output white"), -30.5); QCOMPARE(range().outputWhite, 224.5); drag(scrubbed(sheet, "Output white"), 900); QCOMPARE(range().outputWhite, 255.0); QCOMPARE(scrubOverTyping(find(sheet, "levelsOutputblack"), scrubbed(sheet, "Output black"), 3), QString("3")); } void ScrubbableSheetsTests::filterTitlesMoveALastDecimalAPoint() { EditorSession session; paint(session); session.beginFilter(FilterKind::gaussianBlur); FilterSheet sheet(session); showActive(sheet); const auto radius = [&session] { return session.filterEdit().value().settings.radius; }; const double start = radius(); // One decimal: a point is a tenth. drag(scrubbed(sheet, "Radius"), 10); QVERIFY(qAbs(radius() - (start + 1)) < 1e-9); drag(scrubbed(sheet, "Radius"), 90000); QCOMPARE(radius(), 250.0); drag(scrubbed(sheet, "Radius"), -90000); QCOMPARE(radius(), 0.1); QCOMPARE(scrubOverTyping(find(sheet, "radiusField"), scrubbed(sheet, "Radius"), 30), QString("3.1")); } void ScrubbableSheetsTests::filterTitlesKeepEveryDecimalsSensitivity() { EditorSession session; paint(session); session.beginFilter(FilterKind::gaussianBlur); { FilterSheet sheet(session); showActive(sheet); const double start = session.filterEdit().value().settings.radius; // No snap: a point and a half is 0.15. drag(scrubbed(sheet, "Radius"), 1.5); QVERIFY(qAbs(session.filterEdit().value().settings.radius - (start + 0.15)) < 1e-9); } session.cancelFilter(); session.beginFilter(FilterKind::exposure); { FilterSheet sheet(session); showActive(sheet); const auto exposure = [&session] { return session.filterEdit().value().settings.exposure; }; drag(scrubbed(sheet, "Exposure"), 50.5); QVERIFY(qAbs(exposure().exposure - 0.505) < 1e-9); // Four decimals: fifty points are 0.005. drag(scrubbed(sheet, "Offset"), 50); QVERIFY(qAbs(exposure().offset - 0.005) < 1e-12); const double gamma = exposure().gamma; drag(scrubbed(sheet, "Gamma"), 25); QVERIFY(qAbs(exposure().gamma - (gamma + 0.25)) < 1e-9); drag(scrubbed(sheet, "Gamma"), -90000); QCOMPARE(exposure().gamma, 0.01); } session.cancelFilter(); session.beginFilter(FilterKind::motionBlur); FilterSheet sheet(session); showActive(sheet); // No decimals: a point is a degree, fractions kept. const double angle = session.filterEdit().value().settings.angle; drag(scrubbed(sheet, "Angle"), 15.5); QCOMPARE(session.filterEdit().value().settings.angle, angle + 15.5); } void ScrubbableSheetsTests::colorPickerChannelsScrubWholeLevels() { EditorSession session; paint(session); session.openColorPicker(false); ColorPickerSheet sheet(session, [](bool) {}); showActive(sheet); const auto color = [&session] { return session.colorPicker().value().color(); }; // Whole levels: 100.6 points lands on 101. drag(scrubbed(sheet, "R"), 100.6); QCOMPARE(color().red, 101 / 255.0); drag(scrubbed(sheet, "G"), 900); QCOMPARE(color().green, 1.0); drag(scrubbed(sheet, "B"), 30); QCOMPARE(std::lround(color().blue * 255), 30L); drag(scrubbed(sheet, "R"), -900); QCOMPARE(color().red, 0.0); QCOMPARE(scrubOverTyping(find(sheet, "b"), scrubbed(sheet, "B"), 3), QString("33")); } void ScrubbableSheetsTests::canvasSizeTitlesScrubInTheirUnit() { CanvasDocument document{1200, 800}; document.resolution = 72; EditorSession session; CanvasSizeSheet sheet(document, session, [](std::optional) {}); showActive(sheet); QLineEdit &width = find(sheet, "canvasWidth"); QLineEdit &height = find(sheet, "canvasHeight"); // A held drag keeps its start through each reshape. QLabel &title = scrubbed(sheet, "Width"); drag(title, 10, false); QCOMPARE(width.text(), QString("1210")); send(title, QEvent::MouseMove, QPointF(22, 2), Qt::NoButton, Qt::LeftButton); QCOMPARE(width.text(), QString("1220")); send(title, QEvent::MouseButtonRelease, QPointF(22, 2), Qt::LeftButton, Qt::NoButton); drag(title, -9.6); QCOMPARE(width.text(), QString("1210")); drag(scrubbed(sheet, "Height"), 90000); QCOMPARE(height.text(), QString("30000")); drag(scrubbed(sheet, "Height"), -90000); QCOMPARE(height.text(), QString("1")); // A percent is twelve points of a 1,200-pixel side. choose(find(sheet, "canvasUnits"), "Percent"); drag(scrubbed(sheet, "Width"), 12); QCOMPARE(width.text(), QString("102")); choose(find(sheet, "canvasUnits"), "Inches"); drag(scrubbed(sheet, "Width"), -144); QCOMPARE(width.text(), QString("15")); // Relative counts from the current size, locked keeps the ratio. choose(find(sheet, "canvasUnits"), "Pixels"); find(sheet, "canvasRelative").click(); drag(scrubbed(sheet, "Width"), -90000); QCOMPARE(width.text(), QString("-1199")); find(sheet, "canvasRelative").click(); find(sheet, "canvasLocked").click(); drag(scrubbed(sheet, "Height"), -90000); QCOMPARE(height.text(), QString("1")); drag(scrubbed(sheet, "Width"), -90000); QCOMPARE(width.text(), QString("1.5")); drag(scrubbed(sheet, "Height"), 90000); QCOMPARE(height.text(), QString("20000")); drag(scrubbed(sheet, "Width"), 90000); QCOMPARE(width.text(), QString("30000")); QCOMPARE(height.text(), QString("20000")); QCOMPARE(scrubOverTyping(find(sheet, "canvasWidth"), scrubbed(sheet, "Width"), -2), QString("29998")); } void ScrubbableSheetsTests::canvasSizeCentimetresScrubTheirLimits() { CanvasDocument document{1200, 800}; document.resolution = 72; EditorSession session; CanvasSizeSheet sheet(document, session, [](std::optional) {}); showActive(sheet); QLineEdit &width = find(sheet, "canvasWidth"); QLineEdit &height = find(sheet, "canvasHeight"); choose(find(sheet, "canvasUnits"), "Centimeters"); // A point is 2.54 / 72 cm; 72 add 2.54. drag(scrubbed(sheet, "Width"), 72); QCOMPARE(width.text(), QString("45")); drag(scrubbed(sheet, "Width"), 90000); QCOMPARE(width.text(), QString("1058.333")); drag(scrubbed(sheet, "Width"), -90000); QCOMPARE(width.text(), QString("0.035")); find(sheet, "canvasRelative").click(); drag(scrubbed(sheet, "Height"), -90000); QCOMPARE(height.text(), QString("-28.187")); find(sheet, "canvasRelative").click(); find(sheet, "canvasLocked").click(); drag(scrubbed(sheet, "Height"), 90000); QCOMPARE(height.text(), QString("705.556")); } void ScrubbableSheetsTests::imageSizeLimitsFollowUnitsLockAndResolution() { { // Unlocked, a hundred megapixels over a 5,000-pixel height. CanvasDocument tall{1000, 5000}; tall.resolution = 72; ImageSizeSheet sheet(tall, [](std::optional) {}); showActive(sheet); find(sheet, "imageLocked").click(); drag(scrubbed(sheet, "Width"), 90000); QCOMPARE(find(sheet, "imageWidth").text(), QString("20000")); QCOMPARE(find(sheet, "imageHeight").text(), QString("5000")); } CanvasDocument document{1200, 800}; document.resolution = 72; ImageSizeSheet sheet(document, [](std::optional) {}); showActive(sheet); QLineEdit &width = find(sheet, "imageWidth"); QLineEdit &height = find(sheet, "imageHeight"); QComboBox &units = find(sheet, "imageUnits"); // A percent is twelve points of 1,200 pixels. choose(units, "Percent"); drag(scrubbed(sheet, "Width"), 12); QCOMPARE(width.text(), QString("101")); drag(scrubbed(sheet, "Height"), -16); QCOMPARE(height.text(), QString("99")); // Resampled inches: 72 points an inch, the pixels following. choose(units, "Inches"); drag(scrubbed(sheet, "Width"), 72); QCOMPARE(width.text(), QString("18")); QCOMPARE(find(sheet, "imageResult").text(), QString("Result: 1,296 × 864 pixels")); // A resolution scrub keeps print sizes and scales the pixels. drag(scrubbed(sheet, "Resolution"), 72); QCOMPARE(find(sheet, "imageResolution").text(), QString("144")); QCOMPARE(width.text(), QString("18")); QCOMPARE(find(sheet, "imageResult").text(), QString("Result: 2,592 × 1,728 pixels")); // Resampled centimetres: 144 points add 2.54 at 144 ppi. choose(units, "Centimeters"); drag(scrubbed(sheet, "Width"), 144); QCOMPARE(width.text(), QString("48")); choose(units, "Inches"); drag(scrubbed(sheet, "Width"), 90000); QCOMPARE(width.text(), QString("85.052")); choose(units, "Centimeters"); drag(scrubbed(sheet, "Height"), -90000); QCOMPARE(height.text(), QString("0.018")); } void ScrubbableSheetsTests::imageSizeTitlesScrubPixelsPrintSizeAndResolution() { CanvasDocument document{1200, 800}; document.resolution = 72; ImageSizeSheet sheet(document, [](std::optional) {}); showActive(sheet); QLineEdit &width = find(sheet, "imageWidth"); QLineEdit &height = find(sheet, "imageHeight"); QLineEdit &resolution = find(sheet, "imageResolution"); // Locked, the height follows; a hundred megapixels at most. drag(scrubbed(sheet, "Width"), 300); QCOMPARE(width.text(), QString("1500")); QCOMPARE(height.text(), QString("1000")); drag(scrubbed(sheet, "Width"), 90000); QCOMPARE(width.text(), QString("12247.449")); drag(scrubbed(sheet, "Height"), -90000); QCOMPARE(height.text(), QString("1")); QCOMPARE(width.text(), QString("1.5")); drag(scrubbed(sheet, "Resolution"), 10.4); QCOMPARE(resolution.text(), QString("82")); drag(scrubbed(sheet, "Resolution"), 90000); QCOMPARE(resolution.text(), QString("9600")); drag(scrubbed(sheet, "Resolution"), -90000); QCOMPARE(resolution.text(), QString("1")); // Without resampling a point is a hundredth of an inch. find(sheet, "imageResample").click(); QCOMPARE(find(sheet, "imageUnits").currentText(), QString("Inches")); QCOMPARE(width.text(), QString("1200")); drag(scrubbed(sheet, "Width"), -60000); QCOMPARE(width.text(), QString("600")); QCOMPARE(resolution.text(), QString("2")); // Up to one pixel per inch, down to 9,600. drag(scrubbed(sheet, "Width"), 90000); QCOMPARE(width.text(), QString("1200")); QCOMPARE(resolution.text(), QString("1")); drag(scrubbed(sheet, "Width"), -200000); QCOMPARE(width.text(), QString("0.125")); // A point is 0.0254 cm: a hundred make 2.54. choose(find(sheet, "imageUnits"), "Centimeters"); drag(scrubbed(sheet, "Width"), 100); QCOMPARE(width.text(), QString("3")); QCOMPARE(resolution.text(), QString("1016")); QCOMPARE(scrubOverTyping(resolution, scrubbed(sheet, "Resolution"), 4), QString("1020")); // A resolution of nothing leaves no print size to scrub. resolution.setFocus(); resolution.selectAll(); QTest::keyClicks(&resolution, QStringLiteral("0")); QTest::keyClick(&resolution, Qt::Key_Return); QVERIFY(!scrubbed(sheet, "Width").isEnabled()); QVERIFY(!scrubbed(sheet, "Height").isEnabled()); // A positive resolution again brings the titles back. drag(scrubbed(sheet, "Resolution"), 72); QCOMPARE(resolution.text(), QString("72")); QVERIFY(scrubbed(sheet, "Width").isEnabled()); QVERIFY(scrubbed(sheet, "Height").isEnabled()); drag(scrubbed(sheet, "Height"), -100); QCOMPARE(height.text(), QString("26")); QCOMPARE(resolution.text(), QString("78.154")); } QTEST_MAIN(ScrubbableSheetsTests) #include "ScrubbableSheetsTests.moc"