Coloured slider tracks with double-click reset, aligned filter titles; entries release focus before their owners go (12.14c)
This commit is contained in:
1 parent
2183348aa6
commit
2fa8d9921d
39 files changed
+1387
-311
No files matched your search
@@ -5,6 +5,15 @@
|
||||
|
||||
// Swift's CameraRawSliderTests.
|
||||
namespace {
|
||||
// A two-colour track's ends.
|
||||
std::pair<QColor, QColor> ends(const CameraRawSliderTrack &track)
|
||||
{
|
||||
const std::vector<QColor> colors = track.colors().value();
|
||||
if (colors.size() != 2)
|
||||
throw std::runtime_error("not a two-colour track");
|
||||
return {colors[0], colors[1]};
|
||||
}
|
||||
|
||||
struct Probe {
|
||||
double changed = std::nan("");
|
||||
int resets = 0;
|
||||
@@ -38,16 +47,17 @@ private slots:
|
||||
void trackClickValueMatchesTheClickedPosition();
|
||||
void aFamilysTracksCentreOnItsHue();
|
||||
void aDragKeepsItsKnob();
|
||||
void aReshapeLeavesADragAlone();
|
||||
};
|
||||
|
||||
void CameraRawSliderTests::colorTracksRunFromTheCoolOrMutedEndToTheWarmOrStrongEnd()
|
||||
{
|
||||
using Kind = CameraRawSliderTrack::Kind;
|
||||
const auto [cool, warm] = CameraRawSliderTrack{Kind::temperature}.colors().value();
|
||||
const auto [cool, warm] = ends(CameraRawSliderTrack{Kind::temperature});
|
||||
QVERIFY(cool.blueF() > warm.blueF());
|
||||
const auto [green, mauve] = CameraRawSliderTrack{Kind::tint}.colors().value();
|
||||
const auto [green, mauve] = ends(CameraRawSliderTrack{Kind::tint});
|
||||
QVERIFY(green.greenF() > mauve.greenF());
|
||||
const auto [gray, red] = CameraRawSliderTrack{Kind::chroma}.colors().value();
|
||||
const auto [gray, red] = ends(CameraRawSliderTrack{Kind::chroma});
|
||||
QVERIFY(std::abs(gray.redF() - gray.greenF()) < 0.05);
|
||||
QVERIFY(red.redF() > red.greenF() + 0.4);
|
||||
QVERIFY(!CameraRawSliderTrack{Kind::plain}.colors());
|
||||
@@ -90,11 +100,11 @@ void CameraRawSliderTests::aFamilysTracksCentreOnItsHue()
|
||||
{
|
||||
using Kind = CameraRawSliderTrack::Kind;
|
||||
// Hue runs 50 degrees either side; red wraps below zero.
|
||||
const auto [below, above] = CameraRawSliderTrack{Kind::hue, 0}.colors().value();
|
||||
const auto [below, above] = ends(CameraRawSliderTrack{Kind::hue, 0});
|
||||
QVERIFY(std::abs(below.hueF() * 360 - 310) < 1 && std::abs(above.hueF() * 360 - 50) < 1);
|
||||
const auto [muted, strong] = CameraRawSliderTrack{Kind::saturation, 240}.colors().value();
|
||||
const auto [muted, strong] = ends(CameraRawSliderTrack{Kind::saturation, 240});
|
||||
QVERIFY(muted.hsvSaturationF() < 0.05 && std::abs(strong.hueF() * 360 - 240) < 1);
|
||||
const auto [dark, light] = CameraRawSliderTrack{Kind::luminance, 120}.colors().value();
|
||||
const auto [dark, light] = ends(CameraRawSliderTrack{Kind::luminance, 120});
|
||||
QVERIFY(dark.valueF() < 0.2 && light.valueF() > 0.9 && std::abs(dark.hueF() * 360 - 120) < 1);
|
||||
}
|
||||
|
||||
@@ -113,5 +123,24 @@ void CameraRawSliderTests::aDragKeepsItsKnob()
|
||||
QVERIFY(std::isnan(probe.changed));
|
||||
}
|
||||
|
||||
void CameraRawSliderTests::aReshapeLeavesADragAlone()
|
||||
{
|
||||
using Kind = CameraRawSliderTrack::Kind;
|
||||
Probe probe;
|
||||
probe.slider.display(20);
|
||||
probe.slider.setSliderDown(true);
|
||||
// Mid-drag: the new track shows, the knob and value stay.
|
||||
probe.slider.reshape(-50, 50, CameraRawSliderTrack{Kind::spectrum, 90}, 30);
|
||||
QVERIFY(probe.slider.isSliderDown());
|
||||
QCOMPARE(probe.slider.value(), 600);
|
||||
QCOMPARE(probe.slider.track().kind, Kind::spectrum);
|
||||
QVERIFY(std::isnan(probe.changed));
|
||||
probe.slider.setSliderDown(false);
|
||||
// Let go, new bounds place the value anew.
|
||||
probe.slider.reshape(0, 100, CameraRawSliderTrack{Kind::chroma}, 25);
|
||||
QCOMPARE(probe.slider.value(), 250);
|
||||
QCOMPARE(probe.slider.shown(), 25.0);
|
||||
}
|
||||
|
||||
QTEST_MAIN(CameraRawSliderTests)
|
||||
#include "CameraRawSliderTests.moc"
|
||||
@@ -0,0 +1,440 @@
|
||||
#include "Document/BrushStroke.h"
|
||||
#include "Document/EditorSession.h"
|
||||
#include "UI/FilterSheet.h"
|
||||
#include "UI/SliderSnap.h"
|
||||
#include <QApplication>
|
||||
#include <QLabel>
|
||||
#include <QMouseEvent>
|
||||
#include <QLayout>
|
||||
#include <QLineEdit>
|
||||
#include <QtTest>
|
||||
|
||||
// Swift 1.3.2's coloured filter sliders: tracks, resets, aligned titles.
|
||||
namespace {
|
||||
using Kind = CameraRawSliderTrack::Kind;
|
||||
|
||||
QImage solid(QColor colour)
|
||||
{
|
||||
QImage image(4, 4, QImage::Format_RGBA8888_Premultiplied);
|
||||
image.fill(colour);
|
||||
return image;
|
||||
}
|
||||
|
||||
struct Sheet {
|
||||
EditorSession session;
|
||||
std::unique_ptr<FilterSheet> sheet;
|
||||
explicit Sheet(FilterKind kind)
|
||||
{
|
||||
session.createDocument(4, 4);
|
||||
const QImage image = solid(QColor::fromRgbF(0.8f, 0.3f, 0.2f));
|
||||
session.insert(ImportedImage(image, image, QStringLiteral("Color")));
|
||||
session.beginFilter(kind);
|
||||
sheet = std::make_unique<FilterSheet>(session);
|
||||
sheet->show();
|
||||
}
|
||||
const FilterSettings &settings() const { return session.filterEdit().value().settings; }
|
||||
void set(const std::function<void(FilterSettings &)> &change, bool preview = true)
|
||||
{
|
||||
FilterSettings changed = settings();
|
||||
change(changed);
|
||||
session.updateFilter(changed, preview);
|
||||
}
|
||||
// The slider a title names, and that title.
|
||||
QSlider &slider(const QString &title) const
|
||||
{
|
||||
for (QLabel *label : sheet->findChildren<QLabel *>()) {
|
||||
if (label->text() == title && label->buddy())
|
||||
return *static_cast<QSlider *>(label->buddy());
|
||||
}
|
||||
throw std::runtime_error("no slider titled " + title.toStdString());
|
||||
}
|
||||
QLabel &title(const QString &text) const
|
||||
{
|
||||
for (QLabel *label : sheet->findChildren<QLabel *>()) {
|
||||
if (label->text() == text && label->buddy())
|
||||
return *label;
|
||||
}
|
||||
throw std::runtime_error("no title " + text.toStdString());
|
||||
}
|
||||
CameraRawSlider &coloured(const QString &title) const
|
||||
{
|
||||
auto *found = qobject_cast<CameraRawSlider *>(&slider(title));
|
||||
if (!found)
|
||||
throw std::runtime_error("no coloured slider titled " + title.toStdString());
|
||||
return *found;
|
||||
}
|
||||
};
|
||||
|
||||
// Swift's Color Balance ends, written out.
|
||||
const std::array<std::vector<QColor>, 3> swiftEnds{
|
||||
std::vector{QColor::fromRgbF(0.10f, 0.72f, 0.80f), QColor::fromRgbF(0.86f, 0.18f, 0.20f)},
|
||||
std::vector{QColor::fromRgbF(0.80f, 0.22f, 0.70f), QColor::fromRgbF(0.24f, 0.70f, 0.30f)},
|
||||
std::vector{QColor::fromRgbF(0.95f, 0.82f, 0.18f), QColor::fromRgbF(0.22f, 0.40f, 0.92f)}};
|
||||
|
||||
void send(QWidget &widget, QEvent::Type type, QPointF at, Qt::MouseButton button, Qt::MouseButtons buttons)
|
||||
{
|
||||
QMouseEvent event(type, at, widget.mapToGlobal(at), button, buttons, Qt::NoModifier);
|
||||
QApplication::sendEvent(&widget, &event);
|
||||
}
|
||||
|
||||
// Counts the paints a widget receives.
|
||||
class PaintCount : public QObject {
|
||||
public:
|
||||
explicit PaintCount(QWidget &widget) { widget.installEventFilter(this); }
|
||||
int paints = 0;
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *, QEvent *event) override
|
||||
{
|
||||
paints += event->type() == QEvent::Paint;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// A point on the knob, found along the slider's middle.
|
||||
QPoint knob(const CameraRawSlider &slider)
|
||||
{
|
||||
for (int x = 0; x < slider.width(); ++x) {
|
||||
const QPoint point(x, slider.height() / 2);
|
||||
if (slider.isOnKnob(point))
|
||||
return point;
|
||||
}
|
||||
throw std::runtime_error("no knob");
|
||||
}
|
||||
|
||||
QList<QColor> pixels(const QImage &image)
|
||||
{
|
||||
QList<QColor> colours;
|
||||
for (int y = 0; y < image.height(); ++y)
|
||||
for (int x = 0; x < image.width(); ++x)
|
||||
colours << image.pixelColor(x, y);
|
||||
return colours;
|
||||
}
|
||||
|
||||
// Shown rows' titles: the widest's width, sixty at least.
|
||||
void checkAligned(const Sheet &shown, const QStringList &visible, const QStringList &hidden)
|
||||
{
|
||||
int widest = 60;
|
||||
for (const QString &text : visible)
|
||||
widest = std::max(widest, shown.title(text).sizeHint().width());
|
||||
for (const QString &text : visible + hidden) {
|
||||
QCOMPARE(shown.title(text).minimumWidth(), widest);
|
||||
QCOMPARE(shown.title(text).maximumWidth(), widest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ColoredSliderTests : public QObject {
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void initTestCase() { SliderSnap::install(); }
|
||||
void colorBalanceTracksRunFromEachColorToItsOpposite();
|
||||
void theSpectrumCentresOnItsHue();
|
||||
void resettingAColoredFilterSliderRestoresTheDefault();
|
||||
void blackAndWhiteAndColorBalanceSlidersAreColoured();
|
||||
void aDoubleClickResetsItsOwnSliderOnly();
|
||||
void titlesLineUpToTheWidestShown();
|
||||
void rowsLayOutAsSwifts();
|
||||
void aResetReplacesTheFieldsTyping();
|
||||
void theTrackPaintsEveryStopInPlace();
|
||||
void aTitleResetsOnlyForALeftDoubleClickWhileEnabled();
|
||||
void aResetThenScrubsFromTheDefault();
|
||||
void colorBalanceResetsEachOfItsNineRows();
|
||||
void aNewTrackRepaintsTheSlider();
|
||||
};
|
||||
|
||||
void ColoredSliderTests::colorBalanceTracksRunFromEachColorToItsOpposite()
|
||||
{
|
||||
const std::vector<QColor> cyanRed = FilterSheet::cyanRedTrack().colors().value();
|
||||
QVERIFY(cyanRed[0].blueF() > cyanRed[0].redF() && cyanRed[1].redF() > cyanRed[1].blueF());
|
||||
const std::vector<QColor> magentaGreen = FilterSheet::magentaGreenTrack().colors().value();
|
||||
QVERIFY(magentaGreen[0].redF() > magentaGreen[0].greenF() && magentaGreen[1].greenF() > magentaGreen[1].redF());
|
||||
const std::vector<QColor> yellowBlue = FilterSheet::yellowBlueTrack().colors().value();
|
||||
QVERIFY(yellowBlue[0].greenF() > yellowBlue[0].blueF() && yellowBlue[1].blueF() > yellowBlue[1].greenF());
|
||||
const QColor greens = CameraRawSliderTrack{Kind::luminance, 120}.colors().value().back();
|
||||
QVERIFY(greens.greenF() > greens.redF() && greens.greenF() > greens.blueF());
|
||||
// Swift's exact ends.
|
||||
QVERIFY(cyanRed == swiftEnds[0] && magentaGreen == swiftEnds[1] && yellowBlue == swiftEnds[2]);
|
||||
}
|
||||
|
||||
void ColoredSliderTests::theSpectrumCentresOnItsHue()
|
||||
{
|
||||
// Thirteen stops every 30 degrees, the hue in the middle.
|
||||
const std::vector<QColor> circle = CameraRawSliderTrack{Kind::spectrum, 60}.colors().value();
|
||||
QCOMPARE(int(circle.size()), 13);
|
||||
for (size_t index = 0; index < circle.size(); ++index) {
|
||||
const double hue = std::fmod(60 - 180 + 30.0 * double(index) + 720, 360);
|
||||
QVERIFY2(std::abs(circle[index].hsvHueF() * 360 - hue) < 0.5 || std::abs(circle[index].hsvHueF() * 360 - hue) > 359.5, qPrintable(QString::number(index)));
|
||||
QVERIFY(std::abs(circle[index].hsvSaturationF() - 0.85) < 0.01 && std::abs(circle[index].valueF() - 0.9) < 0.01);
|
||||
}
|
||||
// Black to white, as Lightness runs.
|
||||
const std::vector<QColor> ends = CameraRawSliderTrack{.kind = Kind::opposing, .from = Qt::black, .to = Qt::white}.colors().value();
|
||||
QVERIFY(ends == (std::vector<QColor>{Qt::black, Qt::white}));
|
||||
}
|
||||
|
||||
void ColoredSliderTests::resettingAColoredFilterSliderRestoresTheDefault()
|
||||
{
|
||||
EditorSession session;
|
||||
session.createDocument(4, 4);
|
||||
const QImage base = solid(QColor::fromRgbF(0.8f, 0.3f, 0.2f));
|
||||
session.insert(ImportedImage(base, base, QStringLiteral("Color")));
|
||||
session.beginFilter(FilterKind::blackWhite);
|
||||
FilterSettings settings = session.filterEdit().value().settings;
|
||||
settings.blackWhite.reds = 250;
|
||||
settings.blackWhite.blues = -100;
|
||||
session.updateFilter(settings, true);
|
||||
settings = FilterSheet::resetting([](FilterSettings &each) -> double & { return each.blackWhite.reds; }, settings);
|
||||
QCOMPARE(settings.blackWhite.reds, BlackWhiteSettings().reds);
|
||||
// Only the double-clicked slider resets.
|
||||
QCOMPARE(settings.blackWhite.blues, -100.0);
|
||||
session.updateFilter(FilterSheet::resetting([](FilterSettings &each) -> double & { return each.blackWhite.blues; }, settings), true);
|
||||
QVERIFY(session.filterEdit().value().settings.blackWhite == BlackWhiteSettings());
|
||||
QVERIFY(session.filterEdit().value().preview);
|
||||
bool done = false;
|
||||
session.commitFilter([&done] { done = true; });
|
||||
QTRY_VERIFY(done);
|
||||
QCOMPARE(pixels(session.activeLayer().value().asset.value().image()), pixels(BlackWhiteSettings().apply(base)));
|
||||
session.undo();
|
||||
|
||||
session.beginFilter(FilterKind::colorBalance);
|
||||
FilterSettings balance = session.filterEdit().value().settings;
|
||||
balance.colorBalance.midCyanRed = -80;
|
||||
session.updateFilter(balance, false);
|
||||
session.updateFilter(FilterSheet::resetting([](FilterSettings &each) -> double & { return each.colorBalance.midCyanRed; }, balance), false);
|
||||
QVERIFY(session.filterEdit().value().settings.colorBalance == ColorBalanceSettings());
|
||||
// A reset leaves Preview off.
|
||||
QVERIFY(!session.filterEdit().value().preview);
|
||||
session.cancelFilter();
|
||||
QCOMPARE(pixels(session.activeLayer().value().asset.value().image()), pixels(base));
|
||||
}
|
||||
|
||||
void ColoredSliderTests::blackAndWhiteAndColorBalanceSlidersAreColoured()
|
||||
{
|
||||
Sheet blackWhite(FilterKind::blackWhite);
|
||||
const std::pair<const char *, double> families[] = {{"Reds", 0}, {"Yellows", 60}, {"Greens", 120}, {"Cyans", 180}, {"Blues", 240}, {"Magentas", 300}};
|
||||
for (const auto &[title, degrees] : families) {
|
||||
const CameraRawSlider &slider = blackWhite.coloured(QString::fromLatin1(title));
|
||||
QVERIFY(slider.track().kind == Kind::luminance && slider.track().degrees == degrees);
|
||||
QCOMPARE(slider.toolTip(), QString::fromLatin1(title) + QStringLiteral(". Double-click to reset."));
|
||||
}
|
||||
// Tint's Hue is plain; Saturation follows the tint's hue.
|
||||
QCOMPARE(blackWhite.coloured(QStringLiteral("Hue")).track().kind, Kind::plain);
|
||||
QVERIFY(blackWhite.coloured(QStringLiteral("Saturation")).track().kind == Kind::saturation
|
||||
&& blackWhite.coloured(QStringLiteral("Saturation")).track().degrees == 40);
|
||||
blackWhite.set([](FilterSettings &settings) { settings.blackWhite.tintHue = 200; });
|
||||
QCOMPARE(blackWhite.coloured(QStringLiteral("Saturation")).track().degrees, 200.0);
|
||||
// Its knob sits at 40, between -200 and 300.
|
||||
QCOMPARE(blackWhite.coloured(QStringLiteral("Reds")).shown(), 40.0);
|
||||
// Dragged, the value lands whole.
|
||||
blackWhite.coloured(QStringLiteral("Reds")).setValue(501);
|
||||
QCOMPARE(blackWhite.settings().blackWhite.reds, 51.0);
|
||||
|
||||
Sheet balance(FilterKind::colorBalance);
|
||||
const QList<CameraRawSlider *> sliders = balance.sheet->findChildren<CameraRawSlider *>();
|
||||
QCOMPARE(sliders.size(), 9);
|
||||
for (int index = 0; index < 9; ++index)
|
||||
QVERIFY(sliders[index]->track().kind == Kind::opposing && sliders[index]->track().colors().value() == swiftEnds[size_t(index % 3)]);
|
||||
// Other filters keep the plain slider.
|
||||
Sheet blur(FilterKind::gaussianBlur);
|
||||
QVERIFY(!qobject_cast<CameraRawSlider *>(&blur.slider(QStringLiteral("Radius"))));
|
||||
}
|
||||
|
||||
void ColoredSliderTests::aDoubleClickResetsItsOwnSliderOnly()
|
||||
{
|
||||
Sheet shown(FilterKind::blackWhite);
|
||||
QVERIFY(QTest::qWaitForWindowExposed(shown.sheet.get()));
|
||||
shown.set([](FilterSettings &settings) {
|
||||
settings.blackWhite.reds = 250;
|
||||
settings.blackWhite.blues = -100;
|
||||
settings.blackWhite.tint = true;
|
||||
settings.blackWhite.tintHue = 90;
|
||||
}, false);
|
||||
// On the title: that row alone, Preview left off.
|
||||
QTest::mouseDClick(&shown.title(QStringLiteral("Reds")), Qt::LeftButton);
|
||||
QVERIFY(shown.settings().blackWhite.reds == 40 && shown.settings().blackWhite.blues == -100);
|
||||
QVERIFY(!shown.session.filterEdit().value().preview);
|
||||
QCOMPARE(shown.sheet->findChild<QLineEdit *>(QStringLiteral("redsField"))->text(), QString("40"));
|
||||
// On the knob.
|
||||
CameraRawSlider &blues = shown.coloured(QStringLiteral("Blues"));
|
||||
QTest::mouseDClick(&blues, Qt::LeftButton, {}, knob(blues));
|
||||
QCOMPARE(shown.settings().blackWhite.blues, 20.0);
|
||||
// The plain Hue resets too.
|
||||
QTest::mouseDClick(&shown.title(QStringLiteral("Hue")), Qt::LeftButton);
|
||||
QCOMPARE(shown.settings().blackWhite.tintHue, 40.0);
|
||||
// A plain filter's title keeps its value.
|
||||
Sheet blur(FilterKind::gaussianBlur);
|
||||
blur.set([](FilterSettings &settings) { settings.radius = 25; });
|
||||
QTest::mouseDClick(&blur.title(QStringLiteral("Radius")), Qt::LeftButton);
|
||||
QCOMPARE(blur.settings().radius, 25.0);
|
||||
}
|
||||
|
||||
void ColoredSliderTests::titlesLineUpToTheWidestShown()
|
||||
{
|
||||
const QStringList pairs{"Cyan / Red", "Magenta / Green", "Yellow / Blue"};
|
||||
Sheet balance(FilterKind::colorBalance);
|
||||
checkAligned(balance, pairs, {});
|
||||
QVERIFY(balance.title(QStringLiteral("Magenta / Green")).width() > 60);
|
||||
// Tint's rows count only while shown.
|
||||
Sheet blackWhite(FilterKind::blackWhite);
|
||||
const QStringList families{"Reds", "Yellows", "Greens", "Cyans", "Blues", "Magentas"};
|
||||
checkAligned(blackWhite, families, {"Hue", "Saturation"});
|
||||
blackWhite.set([](FilterSettings &settings) { settings.blackWhite.tint = true; });
|
||||
checkAligned(blackWhite, families + QStringList{"Hue", "Saturation"}, {});
|
||||
// Hidden again, they stop counting.
|
||||
blackWhite.set([](FilterSettings &settings) { settings.blackWhite.tint = false; });
|
||||
checkAligned(blackWhite, families, {"Hue", "Saturation"});
|
||||
// A lone short title keeps sixty.
|
||||
Sheet blur(FilterKind::gaussianBlur);
|
||||
QCOMPARE(blur.title(QStringLiteral("Radius")).width(), 60);
|
||||
}
|
||||
|
||||
void ColoredSliderTests::rowsLayOutAsSwifts()
|
||||
{
|
||||
// Title, slider 10 on, field 10 on, unit 2 past.
|
||||
for (const auto &[kind, text, field] : {std::tuple(FilterKind::gaussianBlur, "Radius", "radiusField"), std::tuple(FilterKind::blackWhite, "Reds", "redsField")}) {
|
||||
Sheet shown(kind);
|
||||
QVERIFY(QTest::qWaitForWindowExposed(shown.sheet.get()));
|
||||
QLabel &title = shown.title(QString::fromLatin1(text));
|
||||
QSlider &slider = shown.slider(QString::fromLatin1(text));
|
||||
QLineEdit &entry = *shown.sheet->findChild<QLineEdit *>(QString::fromLatin1(field));
|
||||
QWidget *box = title.parentWidget();
|
||||
QCOMPARE(title.x(), 0);
|
||||
QCOMPARE(slider.x() - title.geometry().right() - 1, 10);
|
||||
QCOMPARE(entry.x() - slider.geometry().right() - 1, 10);
|
||||
QLabel *unit = box->findChildren<QLabel *>().last();
|
||||
QCOMPARE(unit->x() - entry.geometry().right() - 1, 2);
|
||||
QCOMPARE(unit->geometry().right() + 1, box->width());
|
||||
QCOMPARE(box->height(), box->sizeHint().height());
|
||||
// The row sits in the sheet's column, among the rest.
|
||||
QVERIFY(shown.sheet->layout()->indexOf(box) >= 0);
|
||||
}
|
||||
}
|
||||
|
||||
void ColoredSliderTests::aResetReplacesTheFieldsTyping()
|
||||
{
|
||||
Sheet shown(FilterKind::blackWhite);
|
||||
QVERIFY(QTest::qWaitForWindowActive(shown.sheet.get()));
|
||||
shown.set([](FilterSettings &settings) { settings.blackWhite.reds = 250; });
|
||||
auto &field = *shown.sheet->findChild<QLineEdit *>(QStringLiteral("redsField"));
|
||||
field.setFocus();
|
||||
field.selectAll();
|
||||
QTest::keyClicks(&field, QStringLiteral("7"));
|
||||
QVERIFY(field.isModified());
|
||||
QTest::mouseDClick(&shown.title(QStringLiteral("Reds")), Qt::LeftButton);
|
||||
QVERIFY(!field.isModified());
|
||||
QCOMPARE(field.text(), QString("40"));
|
||||
// Leaving the field applies nothing more.
|
||||
shown.sheet->findChild<QLineEdit *>(QStringLiteral("bluesField"))->setFocus();
|
||||
QCOMPARE(shown.settings().blackWhite.reds, 40.0);
|
||||
}
|
||||
|
||||
void ColoredSliderTests::theTrackPaintsEveryStopInPlace()
|
||||
{
|
||||
// Round red: violet a quarter in, red in the middle.
|
||||
CameraRawSlider slider(-100, 100, CameraRawSliderTrack{Kind::spectrum, 0}, QStringLiteral("Probe"), [](double) {}, [] {});
|
||||
slider.resize(400, 22);
|
||||
slider.display(100);
|
||||
const QImage painted = slider.grab().toImage();
|
||||
const auto hueAt = [&](double share) { return painted.pixelColor(int(share * painted.width()), painted.height() / 2).hsvHueF() * 360; };
|
||||
QVERIFY2(std::abs(hueAt(0.25) - 270) < 25, qPrintable(QString::number(hueAt(0.25))));
|
||||
const double middle = hueAt(0.5);
|
||||
QVERIFY2(middle < 20 || middle > 340, qPrintable(QString::number(middle)));
|
||||
QVERIFY2(std::abs(hueAt(0.75) - 90) < 25, qPrintable(QString::number(hueAt(0.75))));
|
||||
}
|
||||
|
||||
void ColoredSliderTests::aTitleResetsOnlyForALeftDoubleClickWhileEnabled()
|
||||
{
|
||||
Sheet shown(FilterKind::blackWhite);
|
||||
QVERIFY(QTest::qWaitForWindowExposed(shown.sheet.get()));
|
||||
shown.set([](FilterSettings &settings) { settings.blackWhite.reds = 250; });
|
||||
for (const Qt::MouseButton button : {Qt::RightButton, Qt::MiddleButton}) {
|
||||
QTest::mouseDClick(&shown.title(QStringLiteral("Reds")), button);
|
||||
QCOMPARE(shown.settings().blackWhite.reds, 250.0);
|
||||
}
|
||||
// A resting sheet resets nothing.
|
||||
shown.sheet->setEnabled(false);
|
||||
send(shown.title(QStringLiteral("Reds")), QEvent::MouseButtonDblClick, QPointF(2, 2), Qt::LeftButton, Qt::LeftButton);
|
||||
QCOMPARE(shown.settings().blackWhite.reds, 250.0);
|
||||
shown.sheet->setEnabled(true);
|
||||
QTest::mouseDClick(&shown.title(QStringLiteral("Reds")), Qt::LeftButton);
|
||||
QCOMPARE(shown.settings().blackWhite.reds, 40.0);
|
||||
}
|
||||
|
||||
void ColoredSliderTests::aResetThenScrubsFromTheDefault()
|
||||
{
|
||||
// A real double click: the second press goes on scrubbing.
|
||||
Sheet shown(FilterKind::blackWhite);
|
||||
QVERIFY(QTest::qWaitForWindowExposed(shown.sheet.get()));
|
||||
shown.set([](FilterSettings &settings) { settings.blackWhite.reds = 250; });
|
||||
QLabel &title = shown.title(QStringLiteral("Reds"));
|
||||
send(title, QEvent::MouseButtonPress, QPointF(2, 2), Qt::LeftButton, Qt::LeftButton);
|
||||
send(title, QEvent::MouseButtonRelease, QPointF(2, 2), Qt::LeftButton, Qt::NoButton);
|
||||
send(title, QEvent::MouseButtonDblClick, QPointF(2, 2), Qt::LeftButton, Qt::LeftButton);
|
||||
QCOMPARE(shown.settings().blackWhite.reds, 40.0);
|
||||
send(title, QEvent::MouseMove, QPointF(14, 2), Qt::NoButton, Qt::LeftButton);
|
||||
send(title, QEvent::MouseButtonRelease, QPointF(14, 2), Qt::LeftButton, Qt::NoButton);
|
||||
QCOMPARE(shown.settings().blackWhite.reds, 52.0);
|
||||
}
|
||||
|
||||
void ColoredSliderTests::colorBalanceResetsEachOfItsNineRows()
|
||||
{
|
||||
Sheet shown(FilterKind::colorBalance);
|
||||
QVERIFY(QTest::qWaitForWindowExposed(shown.sheet.get()));
|
||||
const std::array<double ColorBalanceSettings::*, 9> fields{
|
||||
&ColorBalanceSettings::shadowCyanRed, &ColorBalanceSettings::shadowMagentaGreen, &ColorBalanceSettings::shadowYellowBlue,
|
||||
&ColorBalanceSettings::midCyanRed, &ColorBalanceSettings::midMagentaGreen, &ColorBalanceSettings::midYellowBlue,
|
||||
&ColorBalanceSettings::highlightCyanRed, &ColorBalanceSettings::highlightMagentaGreen, &ColorBalanceSettings::highlightYellowBlue};
|
||||
const QList<CameraRawSlider *> sliders = shown.sheet->findChildren<CameraRawSlider *>();
|
||||
QCOMPARE(sliders.size(), 9);
|
||||
for (size_t index = 0; index < fields.size(); ++index) {
|
||||
shown.set([&fields](FilterSettings &settings) {
|
||||
for (size_t each = 0; each < fields.size(); ++each)
|
||||
settings.colorBalance.*fields[each] = double(each + 1) * 10;
|
||||
}, false);
|
||||
CameraRawSlider &slider = *sliders[qsizetype(index)];
|
||||
// Titles on even rows, knobs on odd ones.
|
||||
if (index % 2 == 0) {
|
||||
QLabel *title = nullptr;
|
||||
for (QLabel *label : shown.sheet->findChildren<QLabel *>())
|
||||
title = label->buddy() == &slider ? label : title;
|
||||
QTest::mouseDClick(title, Qt::LeftButton);
|
||||
// The double click's own release ends its scrub.
|
||||
send(*title, QEvent::MouseButtonRelease, QPointF(2, 2), Qt::LeftButton, Qt::NoButton);
|
||||
} else {
|
||||
QTest::mouseDClick(&slider, Qt::LeftButton, {}, knob(slider));
|
||||
}
|
||||
for (size_t each = 0; each < fields.size(); ++each)
|
||||
QCOMPARE(shown.settings().colorBalance.*fields[each], each == index ? 0.0 : double(each + 1) * 10);
|
||||
QVERIFY(!shown.session.filterEdit().value().preview);
|
||||
}
|
||||
}
|
||||
|
||||
void ColoredSliderTests::aNewTrackRepaintsTheSlider()
|
||||
{
|
||||
// Swift's needsDisplay: the knob stays, the gradient changes.
|
||||
CameraRawSlider slider(-100, 100, CameraRawSliderTrack{Kind::saturation, 0}, QStringLiteral("Probe"), [](double) {}, [] {});
|
||||
slider.resize(200, 22);
|
||||
slider.display(20);
|
||||
slider.show();
|
||||
QVERIFY(QTest::qWaitForWindowExposed(&slider));
|
||||
QTest::qWait(50);
|
||||
PaintCount count(slider);
|
||||
slider.reshape(-100, 100, CameraRawSliderTrack{Kind::saturation, 200}, 20);
|
||||
QTRY_COMPARE(count.paints, 1);
|
||||
// Mid-drag too.
|
||||
slider.setSliderDown(true);
|
||||
slider.reshape(-100, 100, CameraRawSliderTrack{Kind::spectrum, 90}, 60);
|
||||
QTRY_COMPARE(count.paints, 2);
|
||||
slider.setSliderDown(false);
|
||||
// In a sheet: Tint's saturation follows its hue.
|
||||
Sheet shown(FilterKind::blackWhite);
|
||||
shown.set([](FilterSettings &settings) { settings.blackWhite.tint = true; });
|
||||
QVERIFY(QTest::qWaitForWindowExposed(shown.sheet.get()));
|
||||
QTest::qWait(50);
|
||||
PaintCount saturation(shown.coloured(QStringLiteral("Saturation")));
|
||||
shown.set([](FilterSettings &settings) { settings.blackWhite.tintHue = 200; });
|
||||
QTRY_VERIFY(saturation.paints >= 1);
|
||||
}
|
||||
|
||||
QTEST_MAIN(ColoredSliderTests)
|
||||
#include "ColoredSliderTests.moc"
|
||||
@@ -101,11 +101,11 @@ void FilterControlsTests::eachKindShowsSwiftsControls()
|
||||
QVERIFY(field.alignment() & Qt::AlignRight);
|
||||
QVERIFY(!field.accessibleName().isEmpty() && field.placeholderText() == field.accessibleName());
|
||||
QSlider &slider = sheet.child<QSlider>(qPrintable(name + QStringLiteral("Slider")));
|
||||
// Swift's title: sixty points at least, naming the slider.
|
||||
// Swift's title: the widest title's width, sixty at least.
|
||||
QLabel *title = nullptr;
|
||||
for (QLabel *each : slider.parentWidget()->findChildren<QLabel *>())
|
||||
title = each->buddy() == &slider ? each : title;
|
||||
QVERIFY(title && title->text() == field.accessibleName() && title->minimumWidth() == 60);
|
||||
QVERIFY(title && title->text() == field.accessibleName() && title->minimumWidth() >= 60 && title->maximumWidth() == title->minimumWidth());
|
||||
}
|
||||
QCOMPARE(sheet.sheet->findChild<CurvesControls *>() != nullptr, kind == FilterKind::curves);
|
||||
QCOMPARE(sheet.sheet->findChild<GradientMapControls *>() != nullptr, kind == FilterKind::gradientMap);
|
||||
|
||||
@@ -11,6 +11,13 @@ private slots:
|
||||
void samplersAndTargetingToggle();
|
||||
void theControlsFollowTheSession();
|
||||
void togglesResetAndTheButtons();
|
||||
void tracksFollowTheRangeAndColorize();
|
||||
void aDoubleClickResetsToNoneOrColorizesStart();
|
||||
void aResetReplacesTheFieldsTyping();
|
||||
void arrowKeysWalkEverySlider();
|
||||
void aResetKeepsPreviewOffInBothModes();
|
||||
void aRangeUnderColorizeKeepsItsTracks();
|
||||
void aTitleResetsOnALeftDoubleClickThenScrubs();
|
||||
};
|
||||
|
||||
void HueSaturationSheetTests::theSheetIsLaidOutAsSwifts()
|
||||
@@ -41,11 +48,12 @@ void HueSaturationSheetTests::theSheetIsLaidOutAsSwifts()
|
||||
if (frame->frameShape() == QFrame::VLine)
|
||||
QVERIFY(frame->isHidden());
|
||||
}
|
||||
const std::pair<const char *, int> rows[] = {{"Hue", 18000}, {"Saturation", 10000}, {"Lightness", 10000}};
|
||||
for (const auto &[title, reach] : rows) {
|
||||
for (const char *title : {"Hue", "Saturation", "Lightness"}) {
|
||||
const QString lower = QString::fromLatin1(title).toLower();
|
||||
QSlider &slider = *shown.sheet->findChild<QSlider *>(lower + QStringLiteral("Slider"));
|
||||
QVERIFY(slider.minimum() == -reach && slider.maximum() == reach && slider.value() == 0);
|
||||
// Swift's coloured slider: centred, its help naming the reset.
|
||||
CameraRawSlider &slider = *shown.sheet->findChild<CameraRawSlider *>(lower + QStringLiteral("Slider"));
|
||||
QVERIFY(slider.minimum() == 0 && slider.maximum() == 1000 && slider.value() == 500 && slider.shown() == 0);
|
||||
QCOMPARE(slider.toolTip(), QString(title) + QStringLiteral(". Double-click to reset."));
|
||||
QCOMPARE(labelled(slider), QString(title));
|
||||
QLineEdit &field = *shown.sheet->findChild<QLineEdit *>(lower + QStringLiteral("Field"));
|
||||
QCOMPARE(field.text(), QString("0"));
|
||||
@@ -127,10 +135,11 @@ void HueSaturationSheetTests::aRangeShowsItsSpectrumAndSamplers()
|
||||
QVERIFY(shown.settings() == HueSaturationSettings::colorizeStart());
|
||||
QVERIFY(!range.isEnabled());
|
||||
QVERIFY(!shown.child<QToolButton>("hueTargeting").isVisible() && !shown.child<QWidget>("hueSpectrum").isVisible());
|
||||
QSlider &hue = *shown.sheet->findChild<QSlider *>(QStringLiteral("hueSlider"));
|
||||
QSlider &saturation = *shown.sheet->findChild<QSlider *>(QStringLiteral("saturationSlider"));
|
||||
QVERIFY(hue.minimum() == 0 && hue.maximum() == 36000);
|
||||
QVERIFY(saturation.minimum() == 0 && saturation.maximum() == 10000 && saturation.value() == 2500);
|
||||
CameraRawSlider &hue = *shown.sheet->findChild<CameraRawSlider *>(QStringLiteral("hueSlider"));
|
||||
CameraRawSlider &saturation = *shown.sheet->findChild<CameraRawSlider *>(QStringLiteral("saturationSlider"));
|
||||
// Both ranges now start at zero.
|
||||
QVERIFY(hue.value() == 0 && hue.shown() == 0);
|
||||
QVERIFY(saturation.value() == 250 && saturation.shown() == 25);
|
||||
QCOMPARE(shown.child<QLineEdit>("saturationField").text(), QString("25"));
|
||||
// Off again: everything back to the start.
|
||||
shown.titled<QCheckBox>(QStringLiteral("Colorize")).click();
|
||||
@@ -145,10 +154,11 @@ void HueSaturationSheetTests::slidersAndFieldsWriteTheirValue()
|
||||
{
|
||||
Sheet shown;
|
||||
QVERIFY(shown.show());
|
||||
QSlider &hue = *shown.sheet->findChild<QSlider *>(QStringLiteral("hueSlider"));
|
||||
QSlider &saturationSlider = *shown.sheet->findChild<QSlider *>(QStringLiteral("saturationSlider"));
|
||||
hue.setValue(3060);
|
||||
QCOMPARE(shown.settings().hue(), 30.6);
|
||||
CameraRawSlider &hue = *shown.sheet->findChild<CameraRawSlider *>(QStringLiteral("hueSlider"));
|
||||
CameraRawSlider &saturationSlider = *shown.sheet->findChild<CameraRawSlider *>(QStringLiteral("saturationSlider"));
|
||||
// 30.6 along the track: Swift sets the whole number.
|
||||
hue.setValue(585);
|
||||
QCOMPARE(shown.settings().hue(), 31.0);
|
||||
QLineEdit &hueField = shown.child<QLineEdit>("hueField");
|
||||
QCOMPARE(hueField.text(), QString("31"));
|
||||
// Visited without typing, the shown whole number stays unread.
|
||||
@@ -157,27 +167,27 @@ void HueSaturationSheetTests::slidersAndFieldsWriteTheirValue()
|
||||
QLineEdit &saturation = shown.child<QLineEdit>("saturationField");
|
||||
saturation.setFocus();
|
||||
QTRY_VERIFY(saturation.hasFocus());
|
||||
QCOMPARE(shown.settings().hue(), 30.6);
|
||||
QCOMPARE(shown.settings().hue(), 31.0);
|
||||
// Typed and left, a number applies unclamped.
|
||||
type(saturation, QStringLiteral("250"));
|
||||
hueField.setFocus();
|
||||
QCOMPARE(shown.settings().saturation(), 250.0);
|
||||
QCOMPARE(saturationSlider.value(), 10000);
|
||||
QCOMPARE(saturationSlider.value(), 1000);
|
||||
QCOMPARE(saturation.text(), QString("250"));
|
||||
// Applied, the field follows the session again.
|
||||
saturationSlider.setValue(-2000);
|
||||
saturationSlider.setValue(400);
|
||||
QCOMPARE(saturation.text(), QString("-20"));
|
||||
// What is no finite number reverts.
|
||||
for (const char *text : {"abc", "inf", "nan"}) {
|
||||
type(hueField, QString::fromLatin1(text));
|
||||
saturation.setFocus();
|
||||
QCOMPARE(shown.settings().hue(), 30.6);
|
||||
QCOMPARE(shown.settings().hue(), 31.0);
|
||||
QCOMPARE(hueField.text(), QString("31"));
|
||||
}
|
||||
// Past any int, the slider rests at its end.
|
||||
type(saturation, QStringLiteral("1e300"));
|
||||
hueField.setFocus();
|
||||
QCOMPARE(saturationSlider.value(), 10000);
|
||||
QCOMPARE(saturationSlider.value(), 1000);
|
||||
// Sliders show Colorize's range without writing.
|
||||
HueSaturationSettings colour = HueSaturationSettings::colorizeStart();
|
||||
colour.setHue(-40);
|
||||
@@ -187,12 +197,12 @@ void HueSaturationSheetTests::slidersAndFieldsWriteTheirValue()
|
||||
QCOMPARE(hueField.text(), QString("-40"));
|
||||
// Typing waits through other values; its own slider replaces it.
|
||||
type(hueField, QStringLiteral("4"));
|
||||
saturationSlider.setValue(1000);
|
||||
saturationSlider.setValue(100);
|
||||
QVERIFY(hueField.text() == "4" && hueField.isModified());
|
||||
hue.setValue(1000);
|
||||
QVERIFY(hueField.text() == "10" && !hueField.isModified() && hueField.hasFocus());
|
||||
hue.setValue(250);
|
||||
QVERIFY(hueField.text() == "90" && !hueField.isModified() && hueField.hasFocus());
|
||||
saturation.setFocus();
|
||||
QCOMPARE(shown.settings().hue(), 10.0);
|
||||
QCOMPARE(shown.settings().hue(), 90.0);
|
||||
}
|
||||
|
||||
void HueSaturationSheetTests::fieldsClampOnReturnAndPrintWhole()
|
||||
@@ -273,7 +283,7 @@ void HueSaturationSheetTests::togglesResetAndTheButtons()
|
||||
preview.click();
|
||||
QVERIFY(!shown.session.hueSaturation().value().preview);
|
||||
// Off, the controls keep Preview off.
|
||||
shown.sheet->findChild<QSlider *>(QStringLiteral("lightnessSlider"))->setValue(500);
|
||||
shown.sheet->findChild<QSlider *>(QStringLiteral("lightnessSlider"))->setValue(525);
|
||||
QVERIFY(!shown.session.hueSaturation().value().preview && shown.settings().lightness() == 5);
|
||||
preview.click();
|
||||
QVERIFY(shown.session.hueSaturation().value().preview);
|
||||
@@ -302,5 +312,162 @@ void HueSaturationSheetTests::togglesResetAndTheButtons()
|
||||
QCOMPARE(shown.session.history.undoCount(), steps);
|
||||
}
|
||||
|
||||
void HueSaturationSheetTests::tracksFollowTheRangeAndColorize()
|
||||
{
|
||||
using Kind = CameraRawSliderTrack::Kind;
|
||||
Sheet shown;
|
||||
const auto track = [&](const char *name) { return shown.child<CameraRawSlider>(name).track(); };
|
||||
// Master: circle round red, gray to red, black to white.
|
||||
QVERIFY(track("hueSlider").kind == Kind::spectrum && track("hueSlider").degrees == 0);
|
||||
QCOMPARE(track("saturationSlider").kind, Kind::chroma);
|
||||
QVERIFY(track("lightnessSlider").kind == Kind::opposing && track("lightnessSlider").colors() == (std::vector<QColor>{Qt::black, Qt::white}));
|
||||
// A range centres both on its own hue.
|
||||
shown.set(HueSaturationSettings(0, 0, 0, false, ColorRange::cyans));
|
||||
QVERIFY(track("hueSlider").kind == Kind::spectrum && track("hueSlider").degrees == 180);
|
||||
QVERIFY(track("saturationSlider").kind == Kind::saturation && track("saturationSlider").degrees == 180);
|
||||
shown.set(HueSaturationSettings(0, 0, 0, false, ColorRange::magentas));
|
||||
QCOMPARE(track("hueSlider").degrees, 300.0);
|
||||
// Colorize: red to red; saturation towards the chosen hue.
|
||||
shown.set(HueSaturationSettings(200, 25, 0, true));
|
||||
QVERIFY(track("hueSlider").kind == Kind::spectrum && track("hueSlider").degrees == 180);
|
||||
QVERIFY(track("saturationSlider").kind == Kind::saturation && track("saturationSlider").degrees == 200);
|
||||
// Its knob at 200 of 0 to 360.
|
||||
QCOMPARE(shown.child<CameraRawSlider>("hueSlider").value(), 556);
|
||||
}
|
||||
|
||||
void HueSaturationSheetTests::aDoubleClickResetsToNoneOrColorizesStart()
|
||||
{
|
||||
Sheet shown;
|
||||
QVERIFY(shown.show());
|
||||
const auto title = [&](const char *text) -> QLabel & { return shown.titled<QLabel>(QString::fromLatin1(text)); };
|
||||
shown.set(HueSaturationSettings(30, -40, 20, false, ColorRange::reds));
|
||||
// The title: that value alone back to none.
|
||||
QTest::mouseDClick(&title("Hue"), Qt::LeftButton);
|
||||
QVERIFY(shown.settings().hue() == 0 && shown.settings().saturation() == -40 && shown.settings().lightness() == 20);
|
||||
// The knob.
|
||||
CameraRawSlider &lightness = shown.child<CameraRawSlider>("lightnessSlider");
|
||||
QPoint knob;
|
||||
for (int x = 0; x < lightness.width() && knob.isNull(); ++x)
|
||||
knob = lightness.isOnKnob(QPoint(x, lightness.height() / 2)) ? QPoint(x, lightness.height() / 2) : knob;
|
||||
QTest::mouseDClick(&lightness, Qt::LeftButton, {}, knob);
|
||||
QVERIFY(shown.settings().lightness() == 0 && shown.settings().saturation() == -40);
|
||||
// Under Colorize, back to its start: saturation 25.
|
||||
shown.set(HueSaturationSettings(200, 70, 10, true));
|
||||
QTest::mouseDClick(&title("Saturation"), Qt::LeftButton);
|
||||
QVERIFY(shown.settings().saturation() == 25 && shown.settings().hue() == 200 && shown.settings().lightness() == 10);
|
||||
QCOMPARE(shown.child<QLineEdit>("saturationField").text(), QString("25"));
|
||||
}
|
||||
|
||||
void HueSaturationSheetTests::aResetReplacesTheFieldsTyping()
|
||||
{
|
||||
Sheet shown;
|
||||
QVERIFY(shown.show());
|
||||
shown.set(HueSaturationSettings(200, 70, 10, true));
|
||||
QLineEdit &hueField = shown.child<QLineEdit>("hueField");
|
||||
hueField.setFocus();
|
||||
QTRY_VERIFY(hueField.hasFocus());
|
||||
hueField.selectAll();
|
||||
QTest::keyClicks(&hueField, QStringLiteral("77"));
|
||||
QTest::mouseDClick(&shown.titled<QLabel>(QStringLiteral("Hue")), Qt::LeftButton);
|
||||
QCOMPARE(shown.settings().hue(), 0.0);
|
||||
QCOMPARE(hueField.text(), QString("0"));
|
||||
QVERIFY(!hueField.isModified());
|
||||
}
|
||||
|
||||
void HueSaturationSheetTests::arrowKeysWalkEverySlider()
|
||||
{
|
||||
// A hundred steps: whole values, the knob keeping its travel.
|
||||
const auto walk = [](Sheet &shown, const char *name, Qt::Key key) {
|
||||
CameraRawSlider &slider = shown.child<CameraRawSlider>(name);
|
||||
slider.setFocus();
|
||||
for (int step = 0; step < 100; ++step)
|
||||
QTest::keyClick(&slider, key);
|
||||
};
|
||||
const std::tuple<bool, const char *, Qt::Key, double> cases[] = {
|
||||
{false, "hueSlider", Qt::Key_Right, 53}, {false, "hueSlider", Qt::Key_Left, -53}, {false, "saturationSlider", Qt::Key_Right, 33},
|
||||
{false, "lightnessSlider", Qt::Key_Left, -33}, {true, "hueSlider", Qt::Key_Right, 53}, {true, "saturationSlider", Qt::Key_Right, 45},
|
||||
{true, "saturationSlider", Qt::Key_Left, 9}, {true, "lightnessSlider", Qt::Key_Right, 33}};
|
||||
for (const auto &[colorize, name, key, expected] : cases) {
|
||||
Sheet shown;
|
||||
QVERIFY(shown.show());
|
||||
shown.set(colorize ? HueSaturationSettings::colorizeStart() : HueSaturationSettings());
|
||||
walk(shown, name, key);
|
||||
const HueSaturationSettings &now = shown.settings();
|
||||
const double value = QByteArray(name) == "hueSlider" ? now.hue() : QByteArray(name) == "saturationSlider" ? now.saturation() : now.lightness();
|
||||
QVERIFY2(value == expected, qPrintable(QStringLiteral("%1 %2: %3").arg(QString::fromLatin1(name)).arg(colorize).arg(value)));
|
||||
}
|
||||
// An unchanged step leaves the field's typing.
|
||||
Sheet shown;
|
||||
QVERIFY(shown.show());
|
||||
QLineEdit &field = shown.child<QLineEdit>("hueField");
|
||||
field.setFocus();
|
||||
QTRY_VERIFY(field.hasFocus());
|
||||
field.selectAll();
|
||||
QTest::keyClicks(&field, QStringLiteral("4"));
|
||||
QTest::keyClick(&shown.child<CameraRawSlider>("hueSlider"), Qt::Key_Right);
|
||||
QVERIFY(field.isModified() && field.text() == "4" && shown.settings().hue() == 0);
|
||||
}
|
||||
|
||||
void HueSaturationSheetTests::aResetKeepsPreviewOffInBothModes()
|
||||
{
|
||||
for (const bool colorize : {false, true}) {
|
||||
Sheet shown;
|
||||
QVERIFY(shown.show());
|
||||
shown.session.updateHueSaturation(HueSaturationSettings(90, 60, 30, colorize), false);
|
||||
QTest::mouseDClick(&shown.titled<QLabel>(QStringLiteral("Saturation")), Qt::LeftButton);
|
||||
QCOMPARE(shown.settings().saturation(), colorize ? 25.0 : 0.0);
|
||||
QVERIFY(shown.settings().hue() == 90 && shown.settings().lightness() == 30);
|
||||
QCOMPARE(shown.child<QLineEdit>("saturationField").text(), colorize ? QString("25") : QString("0"));
|
||||
QVERIFY(!shown.session.hueSaturation().value().preview);
|
||||
CameraRawSlider &lightness = shown.child<CameraRawSlider>("lightnessSlider");
|
||||
QPoint knob;
|
||||
for (int x = 0; x < lightness.width() && knob.isNull(); ++x)
|
||||
knob = lightness.isOnKnob(QPoint(x, lightness.height() / 2)) ? QPoint(x, lightness.height() / 2) : knob;
|
||||
QTest::mouseDClick(&lightness, Qt::LeftButton, {}, knob);
|
||||
QVERIFY(shown.settings().lightness() == 0 && shown.settings().hue() == 90);
|
||||
QVERIFY(!shown.session.hueSaturation().value().preview);
|
||||
}
|
||||
}
|
||||
|
||||
void HueSaturationSheetTests::aRangeUnderColorizeKeepsItsTracks()
|
||||
{
|
||||
using Kind = CameraRawSliderTrack::Kind;
|
||||
Sheet shown;
|
||||
shown.set(HueSaturationSettings(200, 25, 0, true, ColorRange::greens));
|
||||
CameraRawSlider &hue = shown.child<CameraRawSlider>("hueSlider");
|
||||
CameraRawSlider &saturation = shown.child<CameraRawSlider>("saturationSlider");
|
||||
// Red to red, towards the chosen hue, Colorize's bounds.
|
||||
QVERIFY(hue.track().kind == Kind::spectrum && hue.track().degrees == 180);
|
||||
QVERIFY(saturation.track().kind == Kind::saturation && saturation.track().degrees == 200);
|
||||
QVERIFY(hue.value() == 556 && saturation.value() == 250);
|
||||
}
|
||||
|
||||
void HueSaturationSheetTests::aTitleResetsOnALeftDoubleClickThenScrubs()
|
||||
{
|
||||
Sheet shown;
|
||||
QVERIFY(shown.show());
|
||||
shown.set(HueSaturationSettings(30, 0, 0, false));
|
||||
QLabel &title = shown.titled<QLabel>(QStringLiteral("Hue"));
|
||||
const auto send = [&title](QEvent::Type type, double x, Qt::MouseButton button, Qt::MouseButtons buttons) {
|
||||
QMouseEvent event(type, QPointF(x, 2), title.mapToGlobal(QPointF(x, 2)), button, buttons, Qt::NoModifier);
|
||||
QApplication::sendEvent(&title, &event);
|
||||
};
|
||||
// Other buttons and a resting sheet reset nothing.
|
||||
for (const Qt::MouseButton button : {Qt::RightButton, Qt::MiddleButton})
|
||||
send(QEvent::MouseButtonDblClick, 2, button, button);
|
||||
shown.sheet->setEnabled(false);
|
||||
send(QEvent::MouseButtonDblClick, 2, Qt::LeftButton, Qt::LeftButton);
|
||||
shown.sheet->setEnabled(true);
|
||||
QCOMPARE(shown.settings().hue(), 30.0);
|
||||
// A real double click resets; its second press scrubs.
|
||||
send(QEvent::MouseButtonPress, 2, Qt::LeftButton, Qt::LeftButton);
|
||||
send(QEvent::MouseButtonRelease, 2, Qt::LeftButton, Qt::NoButton);
|
||||
send(QEvent::MouseButtonDblClick, 2, Qt::LeftButton, Qt::LeftButton);
|
||||
QCOMPARE(shown.settings().hue(), 0.0);
|
||||
send(QEvent::MouseMove, 14, Qt::NoButton, Qt::LeftButton);
|
||||
send(QEvent::MouseButtonRelease, 14, Qt::LeftButton, Qt::NoButton);
|
||||
QCOMPARE(shown.settings().hue(), 12.0);
|
||||
}
|
||||
|
||||
QTEST_MAIN(HueSaturationSheetTests)
|
||||
#include "HueSaturationSheetTests.moc"
|
||||
@@ -0,0 +1,133 @@
|
||||
#include "Document/BrushStroke.h"
|
||||
#include "ScrubFixtures.h"
|
||||
#include "UI/CameraRawColorControls.h"
|
||||
#include "UI/CameraRawControls.h"
|
||||
#include "UI/CameraRawDetailOpticsControls.h"
|
||||
#include "UI/CameraRawGeometryCalibrationControls.h"
|
||||
#include "UI/CameraRawRow.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"
|
||||
|
||||
// A sheet destroyed while its entry holds keys and typing.
|
||||
namespace {
|
||||
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")));
|
||||
}
|
||||
|
||||
// Names the owner's class as its entry loses the keys.
|
||||
class FocusWitness : public QObject {
|
||||
public:
|
||||
FocusWitness(QWidget &owner, QWidget &field) : m_owner(owner) { field.installEventFilter(this); }
|
||||
QString seen;
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *, QEvent *event) override
|
||||
{
|
||||
if (event->type() == QEvent::FocusOut && seen.isEmpty())
|
||||
seen = QString::fromLatin1(m_owner.metaObject()->className());
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
QWidget &m_owner;
|
||||
};
|
||||
|
||||
// Past its destructor, the owner reads as QWidget.
|
||||
void typeThenDestroy(std::unique_ptr<QWidget> owner, const QString &typed)
|
||||
{
|
||||
showActive(*owner);
|
||||
PickerField *field = owner->findChildren<PickerField *>().value(0);
|
||||
if (!field)
|
||||
throw std::runtime_error(std::string("no entry in ") + owner->metaObject()->className());
|
||||
field->setFocus();
|
||||
if (!QTest::qWaitFor([field] { return field->hasFocus(); }))
|
||||
throw std::runtime_error("the entry never took the keys");
|
||||
field->selectAll();
|
||||
QTest::keyClicks(field, typed);
|
||||
if (!field->isModified())
|
||||
throw std::runtime_error("the typing did not land");
|
||||
const QString name = QString::fromLatin1(owner->metaObject()->className());
|
||||
FocusWitness witness(*owner, *field);
|
||||
owner.reset();
|
||||
// Past its own destructor, the owner would read as QWidget.
|
||||
if (witness.seen != name)
|
||||
throw std::runtime_error("the entry committed into " + witness.seen.toStdString() + ", not " + name.toStdString());
|
||||
}
|
||||
}
|
||||
|
||||
class SheetTeardownTests : public QObject {
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void theColourSheetsCommitAsTheyGo();
|
||||
void theDocumentSheetsGoQuietly();
|
||||
void aCameraRawRowGoesQuietly();
|
||||
void theCameraRawGroupsGoWhole();
|
||||
};
|
||||
|
||||
void SheetTeardownTests::theColourSheetsCommitAsTheyGo()
|
||||
{
|
||||
// Each commits its typing as hiding would, members alive.
|
||||
EditorSession session;
|
||||
paint(session);
|
||||
session.beginHueSaturation();
|
||||
typeThenDestroy(std::make_unique<HueSaturationSheet>(session), QStringLiteral("12"));
|
||||
QCOMPARE(session.hueSaturation().value().settings.hue(), 12.0);
|
||||
session.cancelHueSaturation();
|
||||
session.beginFilter(FilterKind::gaussianBlur);
|
||||
typeThenDestroy(std::make_unique<FilterSheet>(session), QStringLiteral("7"));
|
||||
QCOMPARE(session.filterEdit().value().settings.radius, 7.0);
|
||||
session.cancelFilter();
|
||||
session.beginLevels();
|
||||
typeThenDestroy(std::make_unique<LevelsSheet>(session), QStringLiteral("9"));
|
||||
QCOMPARE(session.levels().value().settings.current().black, 9.0);
|
||||
session.cancelLevels();
|
||||
session.addEffect(LayerEffectKind::stroke);
|
||||
typeThenDestroy(std::make_unique<EffectsSheet>(session, LayerEffectKind::stroke), QStringLiteral("5"));
|
||||
QCOMPARE(session.editingEffects().stroke.value().size, 5.0);
|
||||
session.openColorPicker(false);
|
||||
typeThenDestroy(std::make_unique<ColorPickerSheet>(session, [](bool) {}), QStringLiteral("10"));
|
||||
QCOMPARE(session.colorPicker().value().color().red, 10.0 / 255);
|
||||
}
|
||||
|
||||
void SheetTeardownTests::theDocumentSheetsGoQuietly()
|
||||
{
|
||||
const CanvasDocument canvas{120, 80};
|
||||
typeThenDestroy(std::make_unique<ImageSizeSheet>(canvas, [](std::optional<ImageSizeOptions>) {}), QStringLiteral("60"));
|
||||
typeThenDestroy(std::make_unique<CanvasSizeSheet>(canvas, PaletteColor::black(), PaletteColor::white(), [](std::optional<CanvasSizeOptions>) {}),
|
||||
QStringLiteral("60"));
|
||||
}
|
||||
|
||||
void SheetTeardownTests::aCameraRawRowGoesQuietly()
|
||||
{
|
||||
double typed = 0;
|
||||
typeThenDestroy(std::make_unique<CameraRawRow>(CameraRawRow::Spec{.name = QStringLiteral("probe"), .title = QStringLiteral("Probe"), .help = QString()},
|
||||
[] { return 0.0; }, [](double) {}, [&typed](double value) { typed = value; }, [] {}),
|
||||
QStringLiteral("30"));
|
||||
QCOMPARE(typed, 30.0);
|
||||
}
|
||||
|
||||
void SheetTeardownTests::theCameraRawGroupsGoWhole()
|
||||
{
|
||||
EditorSession session;
|
||||
paint(session);
|
||||
session.beginFilter(FilterKind::cameraRaw);
|
||||
typeThenDestroy(std::make_unique<CameraRawControls>(session), QStringLiteral("1"));
|
||||
typeThenDestroy(std::make_unique<CameraRawCurveControls>(session), QStringLiteral("10"));
|
||||
typeThenDestroy(std::make_unique<CameraRawMixerControls>(session), QStringLiteral("10"));
|
||||
typeThenDestroy(std::make_unique<CameraRawDetailControls>(session), QStringLiteral("10"));
|
||||
typeThenDestroy(std::make_unique<CameraRawOpticsControls>(session), QStringLiteral("10"));
|
||||
typeThenDestroy(std::make_unique<CameraRawGeometryControls>(session), QStringLiteral("10"));
|
||||
typeThenDestroy(std::make_unique<CameraRawCalibrationControls>(session), QStringLiteral("10"));
|
||||
}
|
||||
|
||||
QTEST_MAIN(SheetTeardownTests)
|
||||
#include "SheetTeardownTests.moc"
|
||||
@@ -167,6 +167,8 @@ compositor_test(ProjectReplaceTests)
|
||||
compositor_test(ProjectWorkspaceFlowTests)
|
||||
compositor_test(QuitWhileEditingTextTests)
|
||||
compositor_test(OpenRecentTests)
|
||||
compositor_test(ColoredSliderTests)
|
||||
compositor_test(SheetTeardownTests)
|
||||
compositor_test(RecentProjectsCoreTests)
|
||||
compositor_test(ProjectWorkspaceSignalTests)
|
||||
compositor_test(ProjectWorkspaceTests)
|
||||
|
||||
Reference in new issue
Block a user