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

+8 -102
View File
@@ -21,9 +21,6 @@
#include <cmath>
namespace {
// A slider's travel: thousandths of its range.
constexpr int travel = 1000;
QLabel *words(const QString &text, bool callout, QWidget *parent)
{
auto *label = new QLabel(text, parent);
@@ -39,20 +36,6 @@ QLabel *words(const QString &text, bool callout, QWidget *parent)
return label;
}
// Swift's `.number`: at most `decimals` places, none trailing.
QString shown(double value, int decimals, QLocale locale)
{
locale.setNumberOptions(QLocale::OmitGroupSeparator);
QString text = locale.toString(value, 'f', decimals);
if (decimals == 0)
return text;
while (text.endsWith(locale.zeroDigit()))
text.chop(locale.zeroDigit().size());
if (text.endsWith(locale.decimalPoint()))
text.chop(locale.decimalPoint().size());
return text;
}
QColor colour(const AdjustmentColor &value)
{
return QColor::fromRgbF(float(value.red), float(value.green), float(value.blue));
@@ -247,76 +230,6 @@ FilterSheet::FilterSheet(EditorSession &session, QWidget *parent)
synchronize();
}
void FilterSheet::control(const QString &title, std::function<double &(FilterSettings &)> key, double low, double high, const QString &unit,
int decimals, bool logarithmic)
{
QString name = title;
name.remove(QLatin1Char(' '));
name[0] = name[0].toLower();
const size_t index = m_controls.size();
auto *box = new QWidget(this);
auto *label = new QLabel(title, box);
label->setMinimumWidth(60);
label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
// Swift's scrubbable title: a point is the field's last decimal.
new NumericScrub(label, {.sensitivity = 1 / std::pow(10.0, decimals), .low = low, .high = high, .step = std::nullopt,
.value = [this, index] {
FilterSettings current = settings();
return m_controls[index].key(current);
},
.set = [this, index](double value) {
update([this, index, value](FilterSettings &settings) { m_controls[index].key(settings) = value; });
m_controls[index].field->setModified(false);
synchronize();
}});
auto *slider = new QSlider(Qt::Horizontal, box);
slider->setObjectName(name + QStringLiteral("Slider"));
slider->setRange(0, travel);
label->setBuddy(slider);
// Swift rounds what the slider sets to the field's decimals.
connect(slider, &QSlider::valueChanged, this, [this, index](int position) {
const Control &at = m_controls[index];
const double from = at.logarithmic ? std::log(at.low) : at.low, to = at.logarithmic ? std::log(at.high) : at.high;
const double value = from + (to - from) * position / travel, step = std::pow(10.0, at.decimals);
const double rounded = std::round((at.logarithmic ? std::exp(value) : value) * step) / step;
// Unchanged, the thumb stays, so arrow steps add up.
FilterSettings current = settings();
if (at.key(current) == rounded)
return;
update([&at, rounded](FilterSettings &settings) { at.key(settings) = rounded; });
// Its value replaces the field's typing.
at.field->setModified(false);
synchronize();
});
// A readable number applies as typed; normalizing clamps it.
auto *field = new PickerField([this, index] {
PickerField &edited = *m_controls[index].field;
bool number = false;
const double typed = edited.locale().toDouble(edited.text(), &number);
if (edited.isModified() && number && std::isfinite(typed))
update([this, index, typed](FilterSettings &settings) { m_controls[index].key(settings) = typed; });
edited.setModified(false);
synchronize();
}, nullptr, box);
field->setObjectName(name + QStringLiteral("Field"));
field->setAccessibleName(title);
field->setPlaceholderText(title);
field->setAlignment(Qt::AlignRight);
field->setFixedWidth(56);
auto *suffixed = new QHBoxLayout;
suffixed->setSpacing(2);
suffixed->addWidget(field);
suffixed->addWidget(new QLabel(unit, box));
auto *row = new QHBoxLayout(box);
row->setContentsMargins(0, 0, 0, 0);
row->setSpacing(10);
row->addWidget(label);
row->addWidget(slider, 1);
row->addLayout(suffixed);
m_column->addWidget(box);
m_controls.push_back(Control{std::move(key), low, high, decimals, logarithmic, slider, field});
}
void FilterSheet::noise()
{
auto *box = new QWidget(this);
@@ -408,21 +321,8 @@ void FilterSheet::synchronize()
if (m_vignetteSwatch)
m_vignetteSwatch->update();
const FilterEdit &edit = m_session.filterEdit().value();
for (Control &control : m_controls) {
FilterSettings copy = edit.settings;
const double value = control.key(copy);
const double from = control.logarithmic ? std::log(control.low) : control.low, to = control.logarithmic ? std::log(control.high) : control.high;
const double at = control.logarithmic ? std::log(value) : value;
{
// The slider shows the session's number without writing back.
const QSignalBlocker quiet(control.slider);
control.slider->setValue(int(std::lround((at - from) / (to - from) * travel)));
}
// A field being typed in keeps its typing.
const QString text = shown(value, control.decimals, control.field->locale());
if (!control.field->isModified() && control.field->text() != text)
control.field->setText(text);
}
for (Control &control : m_controls)
showControl(control, edit.settings);
if (m_curves)
m_curves->synchronize();
if (m_gradientMap)
@@ -439,6 +339,7 @@ void FilterSheet::synchronize()
}
for (const auto &[row, shows] : m_shownWhen)
row->setVisible(shows(edit.settings));
alignTitles();
m_preview->setChecked(edit.preview);
m_error->setText(edit.previewError.value_or(QString()));
m_error->setVisible(edit.previewError.has_value());
@@ -452,3 +353,8 @@ void FilterSheet::synchronize()
m_ok->setEnabled(!(isAutomatic(edit.kind) && (edit.preparing || edit.previewError)));
setEnabled(!edit.committing);
}
FilterSheet::~FilterSheet()
{
releaseFocus(*this);
}