Scrub numbers by dragging their titles in the sheets and panels (12.12c2)

This commit is contained in:
雷电芽衣 committed 2026-09-29 17:07:35 -04:00
1 parent 5bc7889387
commit aa253d9715
25 files changed
+830 -26

No files matched your search

+1 -1
View File
@@ -162,7 +162,7 @@ void CameraRawMixerControls::rebuildFamilies(int tab)
};
row(m_families,
{.name = QStringLiteral("family") + name, .title = name, .help = QStringLiteral("%1 of %2.").arg(tabs.at(size_t(tab)), name),
.track = familyTrack(index, tab), .titleWidth = 78, .fixedTitle = true, .fieldWidth = 48},
.track = familyTrack(index, tab), .titleWidth = 78, .fixedTitle = true, .fieldWidth = 48, .scrub = 1},
[family, index](const CameraRawSettings &settings) {
CameraRawSettings copy = settings;
return family(copy).at(size_t(index));
+2 -2
View File
@@ -219,7 +219,7 @@ CameraRawCurveControls::CameraRawCurveControls(EditorSession &session, QWidget *
amounts->setContentsMargins(0, 0, 0, 0);
amounts->setSpacing(8);
const auto amount = [this, amounts](const QString &name, const QString &title, double CameraRawCurveSettings::*key, const QString &help) {
auto *row = new CameraRawRow({.name = name, .title = title, .help = help, .titleWidth = 88, .fixedTitle = true, .fieldWidth = 48},
auto *row = new CameraRawRow({.name = name, .title = title, .help = help, .titleWidth = 88, .fixedTitle = true, .fieldWidth = 48, .scrub = 1},
[this, key] { return raw().curve.*key; },
[this, key](double value) { update([key, value](CameraRawSettings &settings) { settings.curve.*key = value; }); },
[this, key](double value) { update([key, value](CameraRawSettings &settings) { settings.curve.*key = value; }); },
@@ -258,7 +258,7 @@ CameraRawCurveControls::CameraRawCurveControls(EditorSession &session, QWidget *
});
m_refine = new CameraRawRow({.name = QStringLiteral("refineSaturation"), .title = QStringLiteral("Refine Saturation"),
.help = QStringLiteral("How much the RGB curve also changes color strength. Zero keeps it to brightness."), .titleWidth = 88,
.fixedTitle = true, .fieldWidth = 48},
.fixedTitle = true, .fieldWidth = 48, .scrub = 1},
[this] { return raw().curve.refineSaturation; },
[this](double value) { update([value](CameraRawSettings &settings) { settings.curve.refineSaturation = value; }); },
[this](double value) { update([value](CameraRawSettings &settings) { settings.curve.refineSaturation = value; }); },
+1 -1
View File
@@ -268,7 +268,7 @@ void CameraRawControls::slider(QVBoxLayout *column, const QString &name, const Q
{
const double step = std::pow(10.0, decimals);
auto *row = new CameraRawRow(
{.name = name, .title = title, .help = help, .low = low, .high = high, .decimals = decimals, .track = track, .titleResets = true},
{.name = name, .title = title, .help = help, .low = low, .high = high, .decimals = decimals, .track = track, .titleResets = true, .scrub = 1 / step},
[this, key] {
CameraRawSettings settings = raw();
return key(settings);
+2 -2
View File
@@ -91,7 +91,7 @@ CameraRawDetailControls::CameraRawDetailControls(EditorSession &session, QWidget
void CameraRawDetailControls::row(QVBoxLayout *column, const QString &name, const QString &title, double CameraRawDetailSettings::*key,
double high, double reset, bool maskingPreview, const QString &help)
{
auto *row = new CameraRawRow({.name = name, .title = title, .help = help, .low = 0, .high = high, .titleWidth = CameraRawControls::labelWidth},
auto *row = new CameraRawRow({.name = name, .title = title, .help = help, .low = 0, .high = high, .titleWidth = CameraRawControls::labelWidth, .scrub = 1},
[this, key] { return raw(m_session).detail.*key; },
[this, key, maskingPreview](double value) { assign(key, std::round(value), maskingPreview); },
[this, key](double value) { assign(key, value, false); }, [this, key, reset] { assign(key, reset, false); });
@@ -194,7 +194,7 @@ void CameraRawOpticsControls::row(QVBoxLayout *column, const QString &name, cons
double high, double reset, const QString &help)
{
const auto set = [this, key](double value) { update([key, value](CameraRawOpticsSettings &optics) { optics.*key = value; }); };
auto *row = new CameraRawRow({.name = name, .title = title, .help = help, .low = low, .high = high, .titleWidth = CameraRawControls::labelWidth},
auto *row = new CameraRawRow({.name = name, .title = title, .help = help, .low = low, .high = high, .titleWidth = CameraRawControls::labelWidth, .scrub = 1},
[this, key] { return raw(m_session).optics.*key; },
[set, low](double value) { set(low < 0 ? value : std::round(value)); }, set, [set, reset] { set(reset); });
column->addWidget(row);
@@ -55,7 +55,7 @@ void write(EditorSession &session, const std::function<void(CameraRawSettings &)
CameraRawRow *rounded(const QString &name, const QString &title, const QString &help, double low, double high, std::function<double()> value,
std::function<void(double)> set, QWidget *parent)
{
return new CameraRawRow({.name = name, .title = title, .help = help, .low = low, .high = high, .titleWidth = CameraRawControls::labelWidth},
return new CameraRawRow({.name = name, .title = title, .help = help, .low = low, .high = high, .titleWidth = CameraRawControls::labelWidth, .scrub = 1},
std::move(value), [set](double number) { set(std::round(number)); }, set, [set] { set(0); }, parent);
}
}
+13 -1
View File
@@ -1,5 +1,6 @@
#include "UI/CameraRawRow.h"
#include "UI/ColorPickerSheet.h"
#include "UI/NumericScrub.h"
#include <QEvent>
#include <QHBoxLayout>
#include <QLocale>
@@ -38,6 +39,16 @@ CameraRawRow::CameraRawRow(Spec spec, std::function<double()> value, std::functi
row->setSpacing(10);
row->addWidget(m_title);
row->addWidget(m_slider, 1);
// Before the reset's filter, which runs first and passes on.
if (m_spec.scrub) {
new NumericScrub(m_title, {.sensitivity = *m_spec.scrub, .low = m_spec.low, .high = m_spec.high, .step = std::nullopt, .value = m_value,
.set = [this, type](double value) {
type(value);
if (m_field)
m_field->setModified(false);
synchronize();
}});
}
if (m_spec.fieldWidth > 0) {
m_field = new PickerField([this, type = std::move(type)] {
bool number = false;
@@ -73,7 +84,8 @@ bool CameraRawRow::eventFilter(QObject *watched, QEvent *event)
{
if (watched == m_title && event->type() == QEvent::MouseButtonDblClick) {
m_reset();
return true;
// A scrub still takes the second press, as Swift's drag.
return !m_spec.scrub;
}
return QWidget::eventFilter(watched, event);
}
+3
View File
@@ -2,6 +2,7 @@
#include "UI/CameraRawSlider.h"
#include <QWidget>
#include <functional>
#include <optional>
class PickerField;
class QLabel;
@@ -25,6 +26,8 @@ public:
int fieldWidth = 56;
// A double click on the title resets it.
bool titleResets = false;
// Swift's scrubbable title: a point's worth, through `type`.
std::optional<double> scrub = std::nullopt;
};
// `slide` takes the slider's own value, `type` a typed number.
CameraRawRow(Spec spec, std::function<double()> value, std::function<void(double)> slide, std::function<void(double)> type,
+51 -2
View File
@@ -4,6 +4,7 @@
#include "UI/ByteCounts.h"
#include "UI/ColorPaletteControls.h"
#include "UI/ColorPickerSheet.h"
#include "UI/NumericScrub.h"
#include <QButtonGroup>
#include <QCheckBox>
#include <QColorDialog>
@@ -22,6 +23,38 @@ const std::array<QString, 9> anchorNames = {QStringLiteral("Top left"), QStr
QStringLiteral("Middle left"), QStringLiteral("Center"), QStringLiteral("Middle right"),
QStringLiteral("Bottom left"), QStringLiteral("Bottom center"), QStringLiteral("Bottom right")};
// Swift's scrubRange: the pixel limits, shown in the draft's unit.
std::pair<double, double> scrubRange(const CanvasSizeDraft &draft, bool widthAxis)
{
const double original = double(widthAxis ? draft.originalWidth : draft.originalHeight);
const double other = double(widthAxis ? draft.originalHeight : draft.originalWidth);
const double lower = draft.locked ? std::max(1.0, original / other) : 1.0;
const double upper = draft.locked ? std::min(30000.0, 30000 * original / other) : 30000.0;
const auto displayed = [&](double pixels) {
const double difference = pixels - (draft.relative ? original : 0);
switch (draft.unit) {
case CanvasUnit::pixels: return difference;
case CanvasUnit::percent: return difference / original * 100;
case CanvasUnit::inches: return difference / draft.resolution;
case CanvasUnit::centimeters: return difference / draft.resolution * 2.54;
}
throw std::logic_error("no such unit");
};
return {displayed(lower), displayed(upper)};
}
// Swift's scrubSensitivity: a point is a pixel in any unit.
double scrubSensitivity(const CanvasSizeDraft &draft, bool widthAxis)
{
switch (draft.unit) {
case CanvasUnit::pixels: return 1;
case CanvasUnit::percent: return 100 / double(widthAxis ? draft.originalWidth : draft.originalHeight);
case CanvasUnit::inches: return 1 / draft.resolution;
case CanvasUnit::centimeters: return 2.54 / draft.resolution;
}
throw std::logic_error("no such unit");
}
QLabel *text(const QString &words, int pixels, QFont::Weight weight, QPalette::ColorRole role, QWidget *parent)
{
auto *label = new QLabel(words, parent);
@@ -117,9 +150,12 @@ CanvasSizeSheet::CanvasSizeSheet(const CanvasDocument &document, PaletteColor fo
});
fields->addWidget(unitsLabel, 0, 0);
fields->addWidget(units, 0, 1);
fields->addWidget(new QLabel(QStringLiteral("Width"), this), 1, 0);
auto *width = new QLabel(QStringLiteral("Width"), this);
auto *height = new QLabel(QStringLiteral("Height"), this);
m_scrubs = {scrub(width, true), scrub(height, false)};
fields->addWidget(width, 1, 0);
fields->addWidget(m_width, 1, 1);
fields->addWidget(new QLabel(QStringLiteral("Height"), this), 2, 0);
fields->addWidget(height, 2, 0);
fields->addWidget(m_height, 2, 1);
column->addLayout(fields);
@@ -240,6 +276,17 @@ PickerField *CanvasSizeSheet::dimension(bool widthAxis)
return field;
}
NumericScrub *CanvasSizeSheet::scrub(QLabel *title, bool widthAxis)
{
// Dragged sizes snap to whole units; synchronize sets the limits.
return new NumericScrub(title, {.sensitivity = 1, .low = 0, .high = 0, .step = 1, .value = [this, widthAxis] { return m_draft.displayed(widthAxis); },
.set = [this, widthAxis](double value) {
m_draft.set(value, widthAxis);
(widthAxis ? m_width : m_height)->setModified(false);
synchronize();
}});
}
std::optional<CanvasExtensionColor> CanvasSizeSheet::fill() const
{
const auto colour = [](PaletteColor value) { return CanvasExtensionColor{value.red, value.green, value.blue}; };
@@ -272,6 +319,8 @@ void CanvasSizeSheet::synchronize()
{
for (const auto &[field, widthAxis] : {std::pair(m_width, true), std::pair(m_height, false)}) {
// A field being typed in keeps its typing.
const auto [low, high] = scrubRange(m_draft, widthAxis);
m_scrubs[widthAxis ? 0 : 1]->reshape(scrubSensitivity(m_draft, widthAxis), low, high);
const QString number = shown(m_draft.displayed(widthAxis), field->locale());
if (!field->isModified() && field->text() != number)
field->setText(number);
+5
View File
@@ -6,6 +6,7 @@
#include <array>
#include <functional>
class NumericScrub;
class PickerField;
class QButtonGroup;
class QComboBox;
@@ -22,6 +23,8 @@ public:
private:
PickerField *dimension(bool widthAxis);
// Swift's scrubbable Width or Height title.
NumericScrub *scrub(QLabel *title, bool widthAxis);
std::optional<CanvasExtensionColor> fill() const;
void pickCustomColor();
void synchronize();
@@ -34,6 +37,8 @@ private:
PaletteColor m_custom = PaletteColor::white();
PickerField *const m_width;
PickerField *const m_height;
// Their limits follow the unit, Relative and Lock.
std::array<NumericScrub *, 2> m_scrubs{};
QLabel *const m_note;
QButtonGroup *const m_anchors;
QLabel *const m_anchorName;
+14 -1
View File
@@ -1,4 +1,5 @@
#include "UI/ColorPickerSheet.h"
#include "UI/NumericScrub.h"
#include "UI/KeyboardShortcuts.h"
#include <QFontDatabase>
#include <QGridLayout>
@@ -205,7 +206,19 @@ ColorPickerSheet::ColorPickerSheet(EditorSession &session, std::function<void(bo
fields->setHorizontalSpacing(8);
fields->setVerticalSpacing(6);
for (int index = 0; index < 3; ++index) {
fields->addWidget(label(QString(QStringLiteral("RGB").at(index)), this), index, 0);
QLabel *name = label(QString(QStringLiteral("RGB").at(index)), this);
// Swift's scrubbable channel: whole levels, replacing typing.
new NumericScrub(name, {.sensitivity = 1, .low = 0, .high = 255, .step = 1,
.value = [this, index] {
const PaletteColor color = m_session.colorPicker().value().color();
return std::round((index == 0 ? color.red : index == 1 ? color.green : color.blue) * 255);
},
.set = [this, index](double value) {
setChannel(index, value);
m_channels[size_t(index)]->setModified(false);
synchronize();
}});
fields->addWidget(name, index, 0);
fields->addWidget(m_channels[size_t(index)], index, 1, Qt::AlignLeft);
}
m_hex->setObjectName(QStringLiteral("hex"));
+10
View File
@@ -4,6 +4,7 @@
#include "UI/ColorPickerSheet.h"
#include <QButtonGroup>
#include <QHBoxLayout>
#include "UI/NumericScrub.h"
#include <QLabel>
#include <QLocale>
#include <QPushButton>
@@ -195,6 +196,15 @@ void EffectsSheet::slider(const QString &title, std::function<std::optional<doub
slider->setRange(0, travel);
slider->setFixedWidth(130);
label->setBuddy(slider);
// Swift's scrubbable title, over the range typing reaches.
new NumericScrub(label, {.sensitivity = 1, .low = low, .high = typedHigh.value_or(high), .step = std::nullopt,
.value = [this, index] { return m_sliders[index].value(m_session.editingEffects()).value_or(m_sliders[index].low); },
.set = [this, index](double amount) {
const Slider &at = m_sliders[index];
apply(at, amount);
at.field->setModified(false);
synchronize();
}});
connect(slider, &QSlider::valueChanged, this, [this, index](int position) {
const Slider &at = m_sliders[index];
apply(at, at.low + (at.high - at.low) * position / travel);
+12
View File
@@ -1,4 +1,5 @@
#include "UI/FilterSheet.h"
#include "UI/NumericScrub.h"
#include "UI/KeyboardShortcuts.h"
#include "UI/ColorPaletteControls.h"
#include "UI/ColorPickerSheet.h"
@@ -257,6 +258,17 @@ void FilterSheet::control(const QString &title, std::function<double &(FilterSet
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);
+10
View File
@@ -2,6 +2,7 @@
#include "UI/KeyboardShortcuts.h"
#include "Rendering/EyedropperIcon.h"
#include "UI/ColorPickerSheet.h"
#include "UI/NumericScrub.h"
#include <QCheckBox>
#include <QComboBox>
#include <QFrame>
@@ -373,6 +374,14 @@ QWidget *HueSaturationSheet::row(size_t index)
layout->setSpacing(10);
auto *label = new QLabel(title, box);
label->setFixedWidth(76);
// Swift's scrubbable title; synchronize sets its range.
m_scrubs[index] = new NumericScrub(label, {.sensitivity = 1, .low = 0, .high = 0, .step = std::nullopt,
.value = [this, index] { return (current(m_session).*rows[index].value)(); },
.set = [this, index](double value) {
change([&](HueSaturationSettings &settings) { (settings.*rows[index].set)(value); });
m_fields[index]->setModified(false);
synchronize();
}});
auto *slider = new QSlider(Qt::Horizontal, box);
slider->setObjectName(title.toLower() + QStringLiteral("Slider"));
label->setBuddy(slider);
@@ -443,6 +452,7 @@ void HueSaturationSheet::synchronize()
m_targeting->setChecked(m_session.hueTargeting());
for (size_t index = 0; index < rows.size(); ++index) {
const auto [low, high] = bounds(index, settings.colorize);
m_scrubs[index]->reshape(1, low, high);
const double value = (settings.*rows[index].value)();
{
// The sliders show the session's numbers without writing back.
+3
View File
@@ -5,6 +5,7 @@
#include <functional>
#include <optional>
class NumericScrub;
class PickerField;
class QCheckBox;
class QComboBox;
@@ -55,6 +56,8 @@ private:
// Hue, Saturation, Lightness; the rows make them.
std::array<QSlider *, 3> m_sliders{};
std::array<PickerField *, 3> m_fields{};
// Each title's scrub; Colorize changes its range.
std::array<NumericScrub *, 3> m_scrubs{};
const std::array<QWidget *, 3> m_rows;
SpectrumEditor *const m_spectrum;
QCheckBox *const m_invert;
+77 -11
View File
@@ -2,6 +2,7 @@
#include "Document/DocumentLimits.h"
#include "UI/KeyboardShortcuts.h"
#include "UI/ColorPickerSheet.h"
#include "UI/NumericScrub.h"
#include <QCheckBox>
#include <QComboBox>
#include <QGridLayout>
@@ -53,14 +54,8 @@ ImageSizeSheet::ImageSizeSheet(const CanvasDocument &document, std::function<voi
m_resolutionField(new PickerField([this] {
bool number = false;
const double typed = m_resolutionField->locale().toDouble(m_resolutionField->text(), &number);
if (m_resolutionField->isModified() && number) {
// Swift's onChange: a physical size keeps, its pixels scale.
const double old = std::exchange(m_resolution, typed);
if (m_resample && physical(m_unit) && old > 0 && typed > 0 && std::isfinite(typed)) {
m_width *= typed / old;
m_height *= typed / old;
}
}
if (m_resolutionField->isModified() && number)
setResolution(typed);
m_resolutionField->setModified(false);
synchronize();
}, nullptr, this)),
@@ -91,9 +86,11 @@ ImageSizeSheet::ImageSizeSheet(const CanvasDocument &document, std::function<voi
});
fields->addWidget(units, 0, 0);
fields->addWidget(m_units, 0, 1);
fields->addWidget(new QLabel(QStringLiteral("Width"), this), 1, 0);
m_titles = {new QLabel(QStringLiteral("Width"), this), new QLabel(QStringLiteral("Height"), this)};
m_scrubs = {scrub(m_titles[0], true), scrub(m_titles[1], false)};
fields->addWidget(m_titles[0], 1, 0);
fields->addWidget(m_widthField, 1, 1);
fields->addWidget(new QLabel(QStringLiteral("Height"), this), 2, 0);
fields->addWidget(m_titles[1], 2, 0);
fields->addWidget(m_heightField, 2, 1);
column->addLayout(fields);
@@ -104,7 +101,15 @@ ImageSizeSheet::ImageSizeSheet(const CanvasDocument &document, std::function<voi
});
column->addWidget(m_lock);
auto *resolution = new QHBoxLayout;
resolution->addWidget(new QLabel(QStringLiteral("Resolution"), this));
auto *resolutionTitle = new QLabel(QStringLiteral("Resolution"), this);
// Swift's scrubbable Resolution: whole pixels per inch.
new NumericScrub(resolutionTitle, {.sensitivity = 1, .low = 1, .high = 9600, .step = 1, .value = [this] { return m_resolution; },
.set = [this](double value) {
setResolution(value);
m_resolutionField->setModified(false);
synchronize();
}});
resolution->addWidget(resolutionTitle);
m_resolutionField->setObjectName(QStringLiteral("imageResolution"));
m_resolutionField->setPlaceholderText(QStringLiteral("Resolution"));
m_resolutionField->setAccessibleName(QStringLiteral("Resolution"));
@@ -223,6 +228,61 @@ void ImageSizeSheet::setDimension(double value, bool isWidth)
}
}
void ImageSizeSheet::setResolution(double value)
{
const double old = std::exchange(m_resolution, value);
if (m_resample && physical(m_unit) && old > 0 && value > 0 && std::isfinite(value)) {
m_width *= value / old;
m_height *= value / old;
}
}
NumericScrub *ImageSizeSheet::scrub(QLabel *title, bool isWidth)
{
// Whole units; synchronize sets the limits and the gate.
return new NumericScrub(title, {.sensitivity = 0, .low = 0, .high = 0, .step = 1,
.value = [this, isWidth] { return display(isWidth ? m_width : m_height, isWidth ? m_originalWidth : m_originalHeight); },
.set = [this, isWidth](double value) {
setDimension(value, isWidth);
(isWidth ? m_widthField : m_heightField)->setModified(false);
synchronize();
}});
}
bool ImageSizeSheet::canScrubDimensions() const
{
return !physical(m_unit) || (std::isfinite(m_resolution) && m_resolution > 0);
}
// Swift's scrubRange: pixel limits, or print sizes without resampling.
std::pair<double, double> ImageSizeSheet::scrubRange(bool isWidth) const
{
const double pixels = isWidth ? m_width : m_height, other = isWidth ? m_height : m_width;
if (!m_resample) {
const double multiplier = m_unit == QLatin1String("Centimeters") ? 2.54 : 1;
return {pixels * multiplier / 9600, pixels * multiplier};
}
const double minimum = m_locked ? std::max(1.0, pixels / other) : 1.0;
const double dimensionLimit = m_locked ? std::min(30000.0, 30000 * pixels / other) : 30000.0;
const double areaLimit = m_locked ? std::sqrt(100'000'000 * pixels / other) : 100'000'000 / other;
const double maximum = std::max(minimum, std::min(dimensionLimit, areaLimit));
const qint64 original = isWidth ? m_originalWidth : m_originalHeight;
return {display(minimum, original), display(maximum, original)};
}
double ImageSizeSheet::scrubSensitivity(bool isWidth) const
{
if (!m_resample)
return m_unit == QLatin1String("Centimeters") ? 0.0254 : 0.01;
if (m_unit == QLatin1String("Percent"))
return 100 / double(isWidth ? m_originalWidth : m_originalHeight);
if (m_unit == QLatin1String("Inches"))
return 1 / m_resolution;
if (m_unit == QLatin1String("Centimeters"))
return 2.54 / m_resolution;
return 1;
}
// Swift's onChange: without resampling the pixels stay the document's.
void ImageSizeSheet::setResample(bool enabled)
{
@@ -252,6 +312,12 @@ void ImageSizeSheet::synchronize()
}
m_units->setCurrentText(m_unit);
for (const auto &[field, isWidth] : {std::pair(m_widthField, true), std::pair(m_heightField, false)}) {
const bool scrubs = canScrubDimensions();
m_titles[isWidth ? 0 : 1]->setEnabled(scrubs);
if (scrubs) {
const auto [low, high] = scrubRange(isWidth);
m_scrubs[isWidth ? 0 : 1]->reshape(scrubSensitivity(isWidth), low, high);
}
// A field being typed in keeps its typing.
const QString number = shown(display(isWidth ? m_width : m_height, isWidth ? m_originalWidth : m_originalHeight), field->locale());
if (!field->isModified() && field->text() != number)
+11
View File
@@ -2,8 +2,10 @@
#include "Document/EditorSession+Model.h"
#include "IO/ImageResizer.h"
#include <QWidget>
#include <array>
#include <functional>
class NumericScrub;
class PickerField;
class QCheckBox;
class QComboBox;
@@ -21,6 +23,13 @@ private:
double display(double pixels, qint64 original) const;
PickerField *dimension(bool isWidth);
void setDimension(double value, bool isWidth);
// Swift's onChange: a physical size keeps, its pixels scale.
void setResolution(double value);
// Swift's scrubbable Width or Height title and its limits.
NumericScrub *scrub(QLabel *title, bool isWidth);
bool canScrubDimensions() const;
std::pair<double, double> scrubRange(bool isWidth) const;
double scrubSensitivity(bool isWidth) const;
void setResample(bool enabled);
void synchronize();
@@ -37,6 +46,8 @@ private:
QComboBox *const m_units;
PickerField *const m_widthField;
PickerField *const m_heightField;
std::array<QLabel *, 2> m_titles{};
std::array<NumericScrub *, 2> m_scrubs{};
QCheckBox *const m_lock;
PickerField *const m_resolutionField;
QWidget *const m_samplingRow;
+19 -1
View File
@@ -1,4 +1,5 @@
#include "UI/LevelsSheet.h"
#include "UI/NumericScrub.h"
#include "UI/KeyboardShortcuts.h"
#include "Rendering/EyedropperIcon.h"
#include <QEvent>
@@ -327,7 +328,24 @@ PickerField *LevelsSheet::field(size_t index)
auto *layout = new QVBoxLayout(box);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(5);
layout->addWidget(caption(name, box));
QLabel *title = caption(name, box);
layout->addWidget(title);
// Swift's scrubbable caption, over gamma's or a tone's range.
const bool gamma = entries[index].decimals > 0;
new NumericScrub(title, {.sensitivity = gamma ? 0.01 : 1, .low = gamma ? 0.1 : 0, .high = gamma ? 9.99 : 255, .step = std::nullopt,
.value = [this, index] {
const std::optional<LevelsEdit> &edit = m_session.levels();
return edit ? edit->settings.current().*entries[index].value : 0.0;
},
.set = [this, index](double value) {
change([&](LevelsSettings &settings) {
LevelRange range = settings.current();
range.*entries[index].value = value;
settings.setCurrent(range);
});
m_fields[index]->setModified(false);
synchronize();
}});
// A number in the user's locale applies; other text reverts.
auto *entry = new PickerField([this, index] {
PickerField &edited = *m_fields[index];
+7
View File
@@ -63,6 +63,13 @@ bool NumericScrub::eventFilter(QObject *watched, QEvent *event)
return true;
}
void NumericScrub::reshape(double sensitivity, double low, double high)
{
m_options.sensitivity = sensitivity;
m_options.low = low;
m_options.high = high;
}
void NumericScrub::end()
{
if (!m_press)
+3 -1
View File
@@ -25,13 +25,15 @@ public:
NumericScrub(QWidget *label, Options options);
// Ends a drag as a release would.
void end();
// New limits, as SwiftUI's next render hands the modifier.
void reshape(double sensitivity, double low, double high);
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
private:
QWidget *const m_label;
const Options m_options;
Options m_options;
// The press, until the release; Swift's gesture begins there.
std::optional<QPointF> m_press;
// Swift's startValue: set once the drag moves a point.