Coloured slider tracks with double-click reset, aligned filter titles; entries release focus before their owners go (12.14c)

This commit is contained in:
雷电芽衣 committed 2026-09-30 03:32:51 -04:00
1 parent 2183348aa6
commit 2fa8d9921d
39 files changed
+1387 -311

No files matched your search

+189 -22
View File
@@ -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"