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

+6
View File
@@ -1,4 +1,5 @@
#include "Rendering/EyedropperIcon.h"
#include "UI/ColorPickerSheet.h"
#include "UI/CameraRawColorControls.h"
#include "UI/LayerIcons.h"
#include <QAbstractButton>
@@ -315,3 +316,8 @@ void CameraRawMixerControls::synchronize()
m_target->setChecked(shown.targetsMixer);
m_target->setIcon(QIcon(LayerIcons::pixmap(LayerIcon::scope, 14, palette().color(QPalette::ButtonText), devicePixelRatioF())));
}
CameraRawMixerControls::~CameraRawMixerControls()
{
releaseFocus(*this);
}
+6
View File
@@ -1,4 +1,5 @@
#include "UI/CameraRawColorControls.h"
#include "UI/ColorPickerSheet.h"
#include "UI/CameraRawRow.h"
#include "UI/LayerIcons.h"
#include <QButtonGroup>
@@ -353,3 +354,8 @@ void CameraRawCurveControls::synchronize()
m_target->setChecked(edit && edit->rawPanel.targetsCurve);
m_target->setIcon(QIcon(LayerIcons::pixmap(LayerIcon::scope, 14, palette().color(QPalette::ButtonText), devicePixelRatioF())));
}
CameraRawCurveControls::~CameraRawCurveControls()
{
releaseFocus(*this);
}
+2
View File
@@ -19,6 +19,7 @@ class CameraRawCurveControls : public QWidget {
Q_OBJECT
public:
explicit CameraRawCurveControls(EditorSession &session, QWidget *parent = nullptr);
~CameraRawCurveControls() override;
void synchronize();
private:
@@ -48,6 +49,7 @@ class CameraRawMixerControls : public QWidget {
Q_OBJECT
public:
explicit CameraRawMixerControls(EditorSession &session, QWidget *parent = nullptr);
~CameraRawMixerControls() override;
void synchronize();
private:
+6
View File
@@ -1,4 +1,5 @@
#include "UI/CameraRawControls.h"
#include "UI/ColorPickerSheet.h"
#include "UI/CameraRawColorControls.h"
#include "UI/CameraRawDetailOpticsControls.h"
#include "UI/CameraRawGeometryCalibrationControls.h"
@@ -400,3 +401,8 @@ void CameraRawControls::synchronize()
m_vignetteStyle->setCurrentIndex(int(settings.vignetteStyle));
}
}
CameraRawControls::~CameraRawControls()
{
releaseFocus(*this);
}
+1
View File
@@ -14,6 +14,7 @@ class CameraRawControls : public QWidget {
Q_OBJECT
public:
explicit CameraRawControls(EditorSession &session, QWidget *parent = nullptr);
~CameraRawControls() override;
// Shows the edit as it now stands.
void synchronize();
static constexpr int labelWidth = 96;
+11
View File
@@ -1,4 +1,5 @@
#include "UI/CameraRawDetailOpticsControls.h"
#include "UI/ColorPickerSheet.h"
#include "Rendering/EyedropperIcon.h"
#include "UI/CameraRawControls.h"
#include <QCheckBox>
@@ -252,3 +253,13 @@ void CameraRawOpticsControls::synchronize()
for (const auto &[slider, key] : m_hueSliders)
slider->display(optics.*key);
}
CameraRawDetailControls::~CameraRawDetailControls()
{
releaseFocus(*this);
}
CameraRawOpticsControls::~CameraRawOpticsControls()
{
releaseFocus(*this);
}
+2
View File
@@ -13,6 +13,7 @@ class CameraRawDetailControls : public QWidget {
Q_OBJECT
public:
explicit CameraRawDetailControls(EditorSession &session, QWidget *parent = nullptr);
~CameraRawDetailControls() override;
void synchronize();
private:
@@ -32,6 +33,7 @@ class CameraRawOpticsControls : public QWidget {
Q_OBJECT
public:
explicit CameraRawOpticsControls(EditorSession &session, QWidget *parent = nullptr);
~CameraRawOpticsControls() override;
void synchronize();
private:
@@ -1,4 +1,5 @@
#include "UI/CameraRawGeometryCalibrationControls.h"
#include "UI/ColorPickerSheet.h"
#include "UI/CameraRawColorControls.h"
#include "UI/CameraRawControls.h"
#include "UI/LayerIcons.h"
@@ -213,3 +214,13 @@ void CameraRawCalibrationControls::synchronize()
for (CameraRawRow *row : m_rows)
row->synchronize();
}
CameraRawGeometryControls::~CameraRawGeometryControls()
{
releaseFocus(*this);
}
CameraRawCalibrationControls::~CameraRawCalibrationControls()
{
releaseFocus(*this);
}
@@ -14,6 +14,7 @@ class CameraRawGeometryControls : public QWidget {
Q_OBJECT
public:
explicit CameraRawGeometryControls(EditorSession &session, QWidget *parent = nullptr);
~CameraRawGeometryControls() override;
void synchronize();
private:
@@ -35,6 +36,7 @@ class CameraRawCalibrationControls : public QWidget {
Q_OBJECT
public:
explicit CameraRawCalibrationControls(EditorSession &session, QWidget *parent = nullptr);
~CameraRawCalibrationControls() override;
void synchronize();
private:
+5
View File
@@ -89,3 +89,8 @@ bool CameraRawRow::eventFilter(QObject *watched, QEvent *event)
}
return QWidget::eventFilter(watched, event);
}
CameraRawRow::~CameraRawRow()
{
releaseFocus(*this);
}
+1
View File
@@ -32,6 +32,7 @@ public:
// `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,
std::function<void()> reset, QWidget *parent = nullptr);
~CameraRawRow() override;
// Shows the value; a focused field keeps its typing.
void synchronize();
+27 -10
View File
@@ -17,16 +17,24 @@ QColor hsb(double degrees, double saturation, double brightness)
}
}
std::optional<std::pair<QColor, QColor>> CameraRawSliderTrack::colors() const
std::optional<std::vector<QColor>> CameraRawSliderTrack::colors() const
{
switch (kind) {
case Kind::plain: return std::nullopt;
case Kind::temperature: return std::pair(QColor::fromRgbF(0.22f, 0.46f, 0.95f), QColor::fromRgbF(0.98f, 0.82f, 0.18f));
case Kind::tint: return std::pair(QColor::fromRgbF(0.28f, 0.70f, 0.34f), QColor::fromRgbF(0.70f, 0.40f, 0.64f));
case Kind::chroma: return std::pair(QColor::fromRgbF(0.62f, 0.62f, 0.64f), QColor::fromRgbF(0.86f, 0.18f, 0.20f));
case Kind::hue: return std::pair(hsb(degrees - 50, 0.85, 0.9), hsb(degrees + 50, 0.85, 0.9));
case Kind::saturation: return std::pair(QColor::fromRgbF(0.55f, 0.55f, 0.56f), hsb(degrees, 0.9, 0.9));
case Kind::luminance: return std::pair(hsb(degrees, 0.55, 0.18), hsb(degrees, 0.35, 0.95));
case Kind::temperature: return std::vector{QColor::fromRgbF(0.22f, 0.46f, 0.95f), QColor::fromRgbF(0.98f, 0.82f, 0.18f)};
case Kind::tint: return std::vector{QColor::fromRgbF(0.28f, 0.70f, 0.34f), QColor::fromRgbF(0.70f, 0.40f, 0.64f)};
case Kind::chroma: return std::vector{QColor::fromRgbF(0.62f, 0.62f, 0.64f), QColor::fromRgbF(0.86f, 0.18f, 0.20f)};
case Kind::hue: return std::vector{hsb(degrees - 50, 0.85, 0.9), hsb(degrees + 50, 0.85, 0.9)};
case Kind::saturation: return std::vector{QColor::fromRgbF(0.55f, 0.55f, 0.56f), hsb(degrees, 0.9, 0.9)};
case Kind::luminance: return std::vector{hsb(degrees, 0.55, 0.18), hsb(degrees, 0.35, 0.95)};
case Kind::opposing: return std::vector{from, to};
case Kind::spectrum: {
// Every 30 degrees round the circle, that hue centred.
std::vector<QColor> circle;
for (int offset = -180; offset <= 180; offset += 30)
circle.push_back(hsb(degrees + offset, 0.85, 0.9));
return circle;
}
}
throw std::logic_error("unknown slider track");
}
@@ -52,6 +60,15 @@ void CameraRawSlider::display(double value)
setValue(int(std::lround((value - m_low) / (m_high - m_low) * travel)));
}
void CameraRawSlider::reshape(double low, double high, CameraRawSliderTrack track, double value)
{
m_low = low;
m_high = high;
m_track = track;
display(value);
update();
}
double CameraRawSlider::shown() const
{
return m_low + (m_high - m_low) * value() / travel;
@@ -67,7 +84,7 @@ bool CameraRawSlider::isOnKnob(QPoint point) const
// Swift's GradientSliderCell: the whole track shows the colours.
void CameraRawSlider::paintEvent(QPaintEvent *event)
{
const std::optional<std::pair<QColor, QColor>> colors = m_track.colors();
const std::optional<std::vector<QColor>> colors = m_track.colors();
if (!colors) {
QSlider::paintEvent(event);
return;
@@ -80,8 +97,8 @@ void CameraRawSlider::paintEvent(QPaintEvent *event)
const double height = 4;
const QRectF bar(groove.left(), groove.center().y() + 0.5 - height / 2, groove.width(), height);
QLinearGradient gradient(bar.topLeft(), bar.topRight());
gradient.setColorAt(0, colors->first);
gradient.setColorAt(1, colors->second);
for (size_t index = 0; index < colors->size(); ++index)
gradient.setColorAt(double(index) / double(colors->size() - 1), colors->at(index));
QPainterPath shape;
shape.addRoundedRect(bar, height / 2, height / 2);
painter.fillPath(shape, gradient);
+14 -7
View File
@@ -4,15 +4,19 @@
#include <functional>
#include <optional>
#include <utility>
#include <vector>
// Swift's CameraRawSliderTrack: a coloured track, or the system's.
struct CameraRawSliderTrack {
enum class Kind { plain, temperature, tint, chroma, hue, saturation, luminance };
enum class Kind { plain, temperature, tint, chroma, hue, saturation, luminance, opposing, spectrum };
Kind kind = Kind::plain;
// A family's centre, for hue, saturation and luminance.
// A family's centre, for hue, saturation, luminance and spectrum.
double degrees = 0;
// Left and right colours; none keeps the system track.
std::optional<std::pair<QColor, QColor>> colors() const;
// Opposing's two ends, as Color Balance's Cyan / Red.
QColor from = {};
QColor to = {};
// Left-to-right colours, evenly spaced; none keeps the system track.
std::optional<std::vector<QColor>> colors() const;
};
// Swift's CameraRawSlider: a double click on the knob resets.
@@ -23,7 +27,10 @@ public:
std::function<void()> reset, QWidget *parent = nullptr);
// Shows a value, unless a drag holds the knob.
void display(double value);
// Swift's updateNSView: new bounds and track, the value shown again.
void reshape(double low, double high, CameraRawSliderTrack track, double value);
double shown() const;
const CameraRawSliderTrack &track() const { return m_track; }
bool isOnKnob(QPoint point) const;
protected:
@@ -31,9 +38,9 @@ protected:
void mouseDoubleClickEvent(QMouseEvent *event) override;
private:
const double m_low;
const double m_high;
const CameraRawSliderTrack m_track;
double m_low;
double m_high;
CameraRawSliderTrack m_track;
const std::function<void(double)> m_change;
const std::function<void()> m_reset;
};
+5
View File
@@ -343,3 +343,8 @@ void CanvasSizeSheet::synchronize()
m_customRow->setVisible(m_extension->currentIndex() == 5);
m_ok->setEnabled(valid);
}
CanvasSizeSheet::~CanvasSizeSheet()
{
releaseFocus(*this);
}
+1
View File
@@ -20,6 +20,7 @@ class CanvasSizeSheet : public QWidget {
public:
CanvasSizeSheet(const CanvasDocument &document, PaletteColor foreground, PaletteColor background,
std::function<void(std::optional<CanvasSizeOptions>)> finish, QWidget *parent = nullptr);
~CanvasSizeSheet() override;
private:
PickerField *dimension(bool widthAxis);
+12
View File
@@ -151,6 +151,13 @@ void PickerField::keyPressEvent(QKeyEvent *event)
QLineEdit::keyPressEvent(event);
}
void releaseFocus(QWidget &owner)
{
// Destroyed focused, it would commit into freed members.
if (QWidget *focus = owner.focusWidget(); focus && focus->hasFocus())
focus->clearFocus();
}
void PickerField::focusOutEvent(QFocusEvent *event)
{
// A menu or popup borrows the focus: the typing stays.
@@ -376,3 +383,8 @@ void ColorPickerPanelController::refocus()
{
FloatingPanel::refocus(identifier());
}
ColorPickerSheet::~ColorPickerSheet()
{
releaseFocus(*this);
}
+4
View File
@@ -22,11 +22,15 @@ private:
const std::function<void(int)> m_step;
};
// An owner's destructor: its focused entry commits while members live.
void releaseFocus(QWidget &owner);
// Swift's ColorPickerSheet: field, hue strip, preview, entry.
class ColorPickerSheet : public QWidget {
Q_OBJECT
public:
ColorPickerSheet(EditorSession &session, std::function<void(bool)> finish, QWidget *parent = nullptr);
~ColorPickerSheet() override;
protected:
void paintEvent(QPaintEvent *event) override;
+5
View File
@@ -282,3 +282,8 @@ void EffectsSheet::synchronize()
control.field->setText(text);
}
}
EffectsSheet::~EffectsSheet()
{
releaseFocus(*this);
}
+1
View File
@@ -14,6 +14,7 @@ class EffectsSheet : public QWidget {
Q_OBJECT
public:
EffectsSheet(EditorSession &session, LayerEffectKind kind, QWidget *parent = nullptr);
~EffectsSheet() override;
private:
// Swift's slider: a title, a slider, a whole-number field.
+14 -9
View File
@@ -8,19 +8,23 @@
void FilterSheet::blackWhite()
{
// Each slider: how bright that family of colours becomes.
const std::vector<std::pair<QString, double BlackWhiteSettings::*>> families{
{QStringLiteral("Reds"), &BlackWhiteSettings::reds}, {QStringLiteral("Yellows"), &BlackWhiteSettings::yellows},
{QStringLiteral("Greens"), &BlackWhiteSettings::greens}, {QStringLiteral("Cyans"), &BlackWhiteSettings::cyans},
{QStringLiteral("Blues"), &BlackWhiteSettings::blues}, {QStringLiteral("Magentas"), &BlackWhiteSettings::magentas}};
for (const auto &[title, field] : families)
using Kind = CameraRawSliderTrack::Kind;
const std::vector<std::tuple<QString, double BlackWhiteSettings::*, double>> families{
{QStringLiteral("Reds"), &BlackWhiteSettings::reds, 0}, {QStringLiteral("Yellows"), &BlackWhiteSettings::yellows, 60},
{QStringLiteral("Greens"), &BlackWhiteSettings::greens, 120}, {QStringLiteral("Cyans"), &BlackWhiteSettings::cyans, 180},
{QStringLiteral("Blues"), &BlackWhiteSettings::blues, 240}, {QStringLiteral("Magentas"), &BlackWhiteSettings::magentas, 300}};
for (const auto &[title, field, degrees] : families)
control(title, [field](FilterSettings &settings) -> double & { return settings.blackWhite.*field; }, BlackWhiteSettings::low,
BlackWhiteSettings::high, QStringLiteral("%"), 0, false);
BlackWhiteSettings::high, QStringLiteral("%"), 0, false,
[degrees](const FilterSettings &) { return CameraRawSliderTrack{Kind::luminance, degrees}; });
flag(QStringLiteral("Tint"), QStringLiteral("Color the result while keeping its tones, for a sepia or a cyanotype"),
[](FilterSettings &settings) -> bool & { return settings.blackWhite.tint; });
control(QStringLiteral("Hue"), [](FilterSettings &settings) -> double & { return settings.blackWhite.tintHue; }, 0, 360, QStringLiteral("°"), 0,
false);
false, [](const FilterSettings &) { return CameraRawSliderTrack{}; });
// Saturation runs from gray to the tint's own hue.
control(QStringLiteral("Saturation"), [](FilterSettings &settings) -> double & { return settings.blackWhite.tintSaturation; }, 0, 100,
QStringLiteral("%"), 0, false);
QStringLiteral("%"), 0, false,
[](const FilterSettings &settings) { return CameraRawSliderTrack{Kind::saturation, settings.blackWhite.tintHue}; });
for (size_t index = m_controls.size() - 2; index < m_controls.size(); ++index)
m_shownWhen.emplace_back(m_controls[index].slider->parentWidget(), [](const FilterSettings &settings) { return settings.blackWhite.tint; });
}
@@ -34,11 +38,12 @@ void FilterSheet::colorBalance()
{QStringLiteral("Highlights"),
{&ColorBalanceSettings::highlightCyanRed, &ColorBalanceSettings::highlightMagentaGreen, &ColorBalanceSettings::highlightYellowBlue}}};
const std::array<QString, 3> pairs{QStringLiteral("Cyan / Red"), QStringLiteral("Magenta / Green"), QStringLiteral("Yellow / Blue")};
static constexpr std::array tracks{&FilterSheet::cyanRedTrack, &FilterSheet::magentaGreenTrack, &FilterSheet::yellowBlueTrack};
for (const auto &[range, fields] : ranges) {
headline(range);
for (size_t pair = 0; pair < 3; ++pair)
control(pairs[pair], [field = fields[pair]](FilterSettings &settings) -> double & { return settings.colorBalance.*field; },
ColorBalanceSettings::low, ColorBalanceSettings::high, QString(), 0, false);
ColorBalanceSettings::low, ColorBalanceSettings::high, QString(), 0, false, [pair](const FilterSettings &) { return tracks[pair](); });
}
flag(QStringLiteral("Preserve Luminosity"), QStringLiteral("Put each pixel's brightness back afterwards, so only the color moves"),
[](FilterSettings &settings) -> bool & { return settings.colorBalance.preserveLuminosity; });
+188
View File
@@ -0,0 +1,188 @@
#include "UI/FilterSheet.h"
#include "UI/ColorPickerSheet.h"
#include "UI/NumericScrub.h"
#include <QEvent>
#include <QHBoxLayout>
#include <QLabel>
#include <QLocale>
#include <QMouseEvent>
#include <QSlider>
#include <QVBoxLayout>
#include <cmath>
// Swift's `control`: title, slider, exact field; coloured tracks reset.
namespace {
// 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;
}
CameraRawSliderTrack opposing(QColor from, QColor to)
{
return {.kind = CameraRawSliderTrack::Kind::opposing, .from = from, .to = to};
}
}
FilterSettings FilterSheet::resetting(const std::function<double &(FilterSettings &)> &key, FilterSettings settings)
{
FilterSettings defaults;
key(settings) = key(defaults);
return settings;
}
CameraRawSliderTrack FilterSheet::cyanRedTrack()
{
return opposing(QColor::fromRgbF(0.10f, 0.72f, 0.80f), QColor::fromRgbF(0.86f, 0.18f, 0.20f));
}
CameraRawSliderTrack FilterSheet::magentaGreenTrack()
{
return opposing(QColor::fromRgbF(0.80f, 0.22f, 0.70f), QColor::fromRgbF(0.24f, 0.70f, 0.30f));
}
CameraRawSliderTrack FilterSheet::yellowBlueTrack()
{
return opposing(QColor::fromRgbF(0.95f, 0.82f, 0.18f), QColor::fromRgbF(0.22f, 0.40f, 0.92f));
}
void FilterSheet::control(const QString &title, std::function<double &(FilterSettings &)> key, double low, double high, const QString &unit,
int decimals, bool logarithmic, std::function<CameraRawSliderTrack(const FilterSettings &)> track)
{
if (track && logarithmic)
throw std::logic_error("a coloured track is linear");
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);
// 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();
}});
// Swift rounds what the slider sets to the field's decimals.
const auto slide = [this, index](double value) {
const Control &at = m_controls[index];
const double step = std::pow(10.0, at.decimals), rounded = std::round(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();
};
QSlider *slider = nullptr;
if (track) {
slider = new CameraRawSlider(low, high, track(settings()), title + QStringLiteral(". Double-click to reset."), slide, [this, index] { reset(index); },
box);
// After the scrub's filter, so this one runs first.
label->installEventFilter(this);
} else {
slider = new QSlider(Qt::Horizontal, box);
slider->setRange(0, travel);
connect(slider, &QSlider::valueChanged, this, [this, index, slide](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;
slide(at.logarithmic ? std::exp(value) : value);
});
}
slider->setObjectName(name + QStringLiteral("Slider"));
label->setBuddy(slider);
// 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, label, std::move(track)});
}
void FilterSheet::showControl(Control &control, const FilterSettings &settings)
{
FilterSettings copy = settings;
const double value = control.key(copy);
if (control.track) {
// Swift's updateNSView: the track follows the settings.
static_cast<CameraRawSlider *>(control.slider)->reshape(control.low, control.high, control.track(settings), value);
} else {
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);
}
void FilterSheet::alignTitles()
{
int widest = 60;
for (const Control &control : m_controls) {
if (!control.slider->parentWidget()->isHidden())
widest = std::max(widest, control.title->sizeHint().width());
}
for (const Control &control : m_controls)
control.title->setFixedWidth(widest);
}
void FilterSheet::reset(size_t index)
{
update([this, index](FilterSettings &settings) { settings = resetting(m_controls[index].key, settings); });
m_controls[index].field->setModified(false);
synchronize();
}
// Swift's double click on a coloured slider's title resets it.
bool FilterSheet::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::MouseButtonDblClick && static_cast<QMouseEvent *>(event)->button() == Qt::LeftButton) {
for (size_t index = 0; index < m_controls.size(); ++index) {
if (m_controls[index].title == watched && m_controls[index].track && m_controls[index].title->isEnabled())
reset(index);
}
}
// The scrub still takes the second press, as Swift's drag.
return QWidget::eventFilter(watched, event);
}
+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);
}
+21 -1
View File
@@ -1,5 +1,6 @@
#pragma once
#include "Document/EditorSession.h"
#include "UI/CameraRawSlider.h"
#include <QWidget>
class CurvesControls;
@@ -38,6 +39,16 @@ class FilterSheet : public QWidget {
Q_OBJECT
public:
explicit FilterSheet(EditorSession &session, QWidget *parent = nullptr);
~FilterSheet() override;
// Swift's resetting: that setting back to the filter's default.
static FilterSettings resetting(const std::function<double &(FilterSettings &)> &key, FilterSettings settings);
// Color Balance's tracks, each colour to its opposite.
static CameraRawSliderTrack cyanRedTrack();
static CameraRawSliderTrack magentaGreenTrack();
static CameraRawSliderTrack yellowBlueTrack();
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
private:
// Swift's control: a slider, an exact field and its unit.
@@ -49,9 +60,18 @@ private:
bool logarithmic;
QSlider *slider;
PickerField *field;
QLabel *title;
// A coloured track, as Camera Raw's; a double click resets.
std::function<CameraRawSliderTrack(const FilterSettings &)> track;
};
// A slider's travel: thousandths of its range.
static constexpr int travel = 1000;
void control(const QString &title, std::function<double &(FilterSettings &)> key, double low, double high, const QString &unit,
int decimals, bool logarithmic);
int decimals, bool logarithmic, std::function<CameraRawSliderTrack(const FilterSettings &)> track = {});
void showControl(Control &control, const FilterSettings &settings);
// Swift's LabelWidthKey: every shown title as wide as the widest.
void alignTitles();
void reset(size_t index);
void noise();
// Vignette's colour and rows, Bloom / Glow's, Tonal Contrast's.
void finishing(FilterKind kind);
+141
View File
@@ -0,0 +1,141 @@
#include "UI/HueSaturationSheet.h"
#include <QLabel>
#include <QMouseEvent>
#include <QPainter>
#include <QPainterPath>
#include <QVBoxLayout>
#include <algorithm>
#include <cmath>
// Swift's SpectrumEditor: the bars, their handles and the readout.
namespace {
// Swift's 72 hue slices, as they are or adjusted.
class Spectrum : public QWidget {
public:
Spectrum(std::function<HueSaturationSettings()> value, bool after, QWidget *parent) : QWidget(parent), m_value(std::move(value)), m_after(after)
{
setFixedHeight(16);
}
protected:
void paintEvent(QPaintEvent *) override
{
constexpr int slices = 72;
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
QPainterPath rounded;
rounded.addRoundedRect(QRectF(rect()), 3, 3);
painter.setClipPath(rounded);
const HueSaturationSettings settings = m_value();
const double width = double(this->width()) / slices;
for (int slice = 0; slice < slices; ++slice) {
const double hue = double(slice) / slices * 360;
const double shown = m_after ? HueSaturationFilter::shiftedHue(hue, settings) : hue;
painter.fillRect(QRectF(slice * width, 0, width + 0.5, height()), QColor::fromHsvF(float(shown / 360), 1, 1));
}
}
private:
const std::function<HueSaturationSettings()> m_value;
const bool m_after;
};
// Swift's handles: shoulders as blocks, the core's ends as bars.
class SpectrumHandles : public QWidget {
public:
SpectrumHandles(std::function<HueSaturationSettings()> value, std::function<void(double, double, bool)> drag, QWidget *parent)
: QWidget(parent), m_value(std::move(value)), m_drag(std::move(drag))
{
setFixedHeight(12);
}
protected:
void paintEvent(QPaintEvent *) override
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
const std::array<double, 4> handles = m_value().band().handles();
for (size_t index = 0; index < handles.size(); ++index) {
const double x = handles[index] / 360 * width();
const bool inner = index == 1 || index == 2;
painter.fillRect(inner ? QRectF(x - 1, 0, 2, height()) : QRectF(x - 3.5, height() / 2.0 - 2.5, 7, 5), palette().color(QPalette::WindowText));
}
}
void mousePressEvent(QMouseEvent *event) override
{
if (event->button() == Qt::LeftButton)
m_drag(event->position().x(), width(), true);
}
void mouseMoveEvent(QMouseEvent *event) override
{
if (event->buttons().testFlag(Qt::LeftButton))
m_drag(event->position().x(), width(), false);
}
private:
const std::function<HueSaturationSettings()> m_value;
const std::function<void(double, double, bool)> m_drag;
};
// The nearest handle round the circle; ties take the first.
int nearestHandle(const HueBand &band, double degrees)
{
const std::array<double, 4> handles = band.handles();
std::array<double, 4> distances{};
for (size_t index = 0; index < handles.size(); ++index) {
const double gap = std::fmod(std::abs(handles[index] - degrees), 360);
distances[index] = std::min(gap, 360 - gap);
}
return int(std::min_element(distances.begin(), distances.end()) - distances.begin());
}
}
SpectrumEditor::SpectrumEditor(std::function<HueSaturationSettings()> value, std::function<void(const HueSaturationSettings &)> change, QWidget *parent)
: QWidget(parent), m_value(std::move(value)), m_change(std::move(change)),
m_handles(new SpectrumHandles(m_value, [this](double x, double width, bool pressed) { drag(x, width, pressed); }, this)),
m_after(new Spectrum(m_value, true, this)), m_readout(new QLabel(this))
{
auto *before = new Spectrum(m_value, false, this);
before->setObjectName(QStringLiteral("spectrumBefore"));
m_handles->setObjectName(QStringLiteral("spectrumHandles"));
m_after->setObjectName(QStringLiteral("spectrumAfter"));
m_readout->setObjectName(QStringLiteral("spectrumReadout"));
// Swift's .caption in the secondary ink.
QFont small = m_readout->font();
small.setPixelSize(10);
m_readout->setFont(small);
m_readout->setForegroundRole(QPalette::PlaceholderText);
auto *column = new QVBoxLayout(this);
column->setContentsMargins(0, 0, 0, 0);
column->setSpacing(5);
column->addWidget(before);
column->addWidget(m_handles);
column->addWidget(m_after);
column->addWidget(m_readout, 0, Qt::AlignHCenter);
synchronize();
}
void SpectrumEditor::synchronize()
{
QStringList degrees;
for (const double handle : m_value().band().handles())
degrees << QStringLiteral("%1°").arg(std::lround(handle));
m_readout->setText(degrees.join(QStringLiteral(" ")));
m_handles->update();
m_after->update();
}
void SpectrumEditor::drag(double x, double width, bool pressed)
{
// A press starts afresh: Qt can lose a release.
if (pressed)
m_dragging.reset();
const double degrees = std::clamp(x, 0.0, width) / width * 360;
HueSaturationSettings settings = m_value();
HueBand band = settings.band();
const int index = m_dragging.value_or(nearestHandle(band, degrees));
m_dragging = index;
band.setHandle(index, degrees);
settings.setBand(band);
m_change(settings);
}
+64 -147
View File
@@ -12,7 +12,6 @@
#include <QPainter>
#include <QPainterPath>
#include <QPushButton>
#include <QSlider>
#include <QToolButton>
#include <QVBoxLayout>
#include <algorithm>
@@ -30,9 +29,6 @@ constexpr std::array<Row, 3> rows{{{"Hue", "°", &HueSaturationSettings::hue, &H
{"Saturation", "", &HueSaturationSettings::saturation, &HueSaturationSettings::setSaturation},
{"Lightness", "", &HueSaturationSettings::lightness, &HueSaturationSettings::setLightness}}};
// Sliders move in hundredths, as Swift's run freely.
constexpr double sliderScale = 100;
// Swift's ranges: Colorize sets hue and saturation outright.
std::pair<double, double> bounds(size_t index, bool colorize)
{
@@ -43,92 +39,29 @@ std::pair<double, double> bounds(size_t index, bool colorize)
return {-100.0, 100.0};
}
// Swift's tracks: hue circle, gray to colour, black to white.
CameraRawSliderTrack track(size_t index, const HueSaturationSettings &settings)
{
using Kind = CameraRawSliderTrack::Kind;
// The middle of the chosen range; Master centres on red.
const auto found = std::find(colorRanges.begin(), colorRanges.end(), settings.range);
const double rangeHue = found == colorRanges.end() ? 0 : double(found - colorRanges.begin()) * 60;
if (index == 0)
return {Kind::spectrum, settings.colorize ? 180 : rangeHue};
if (index == 1) {
if (settings.colorize)
return {Kind::saturation, settings.hue()};
return settings.range == ColorRange::master ? CameraRawSliderTrack{Kind::chroma} : CameraRawSliderTrack{Kind::saturation, rangeHue};
}
return {.kind = Kind::opposing, .from = Qt::black, .to = Qt::white};
}
// Swift's `current`: the open edit's settings, else the defaults.
HueSaturationSettings current(const EditorSession &session)
{
return session.hueSaturation() ? session.hueSaturation()->settings : HueSaturationSettings();
}
// Swift's 72 hue slices, as they are or adjusted.
class Spectrum : public QWidget {
public:
Spectrum(std::function<HueSaturationSettings()> value, bool after, QWidget *parent) : QWidget(parent), m_value(std::move(value)), m_after(after)
{
setFixedHeight(16);
}
protected:
void paintEvent(QPaintEvent *) override
{
constexpr int slices = 72;
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
QPainterPath rounded;
rounded.addRoundedRect(QRectF(rect()), 3, 3);
painter.setClipPath(rounded);
const HueSaturationSettings settings = m_value();
const double width = double(this->width()) / slices;
for (int slice = 0; slice < slices; ++slice) {
const double hue = double(slice) / slices * 360;
const double shown = m_after ? HueSaturationFilter::shiftedHue(hue, settings) : hue;
painter.fillRect(QRectF(slice * width, 0, width + 0.5, height()), QColor::fromHsvF(float(shown / 360), 1, 1));
}
}
private:
const std::function<HueSaturationSettings()> m_value;
const bool m_after;
};
// Swift's handles: shoulders as blocks, the core's ends as bars.
class SpectrumHandles : public QWidget {
public:
SpectrumHandles(std::function<HueSaturationSettings()> value, std::function<void(double, double, bool)> drag, QWidget *parent)
: QWidget(parent), m_value(std::move(value)), m_drag(std::move(drag))
{
setFixedHeight(12);
}
protected:
void paintEvent(QPaintEvent *) override
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
const std::array<double, 4> handles = m_value().band().handles();
for (size_t index = 0; index < handles.size(); ++index) {
const double x = handles[index] / 360 * width();
const bool inner = index == 1 || index == 2;
painter.fillRect(inner ? QRectF(x - 1, 0, 2, height()) : QRectF(x - 3.5, height() / 2.0 - 2.5, 7, 5), palette().color(QPalette::WindowText));
}
}
void mousePressEvent(QMouseEvent *event) override
{
if (event->button() == Qt::LeftButton)
m_drag(event->position().x(), width(), true);
}
void mouseMoveEvent(QMouseEvent *event) override
{
if (event->buttons().testFlag(Qt::LeftButton))
m_drag(event->position().x(), width(), false);
}
private:
const std::function<HueSaturationSettings()> m_value;
const std::function<void(double, double, bool)> m_drag;
};
// The nearest handle round the circle; ties take the first.
int nearestHandle(const HueBand &band, double degrees)
{
const std::array<double, 4> handles = band.handles();
std::array<double, 4> distances{};
for (size_t index = 0; index < handles.size(); ++index) {
const double gap = std::fmod(std::abs(handles[index] - degrees), 360);
distances[index] = std::min(gap, 360 - gap);
}
return int(std::min_element(distances.begin(), distances.end()) - distances.begin());
}
// Swift's plain sampling buttons: a glyph, tinted when chosen.
class SampleButton : public QToolButton {
public:
@@ -216,56 +149,6 @@ QFrame *line(QFrame::Shape shape, QWidget *parent)
}
}
SpectrumEditor::SpectrumEditor(std::function<HueSaturationSettings()> value, std::function<void(const HueSaturationSettings &)> change, QWidget *parent)
: QWidget(parent), m_value(std::move(value)), m_change(std::move(change)),
m_handles(new SpectrumHandles(m_value, [this](double x, double width, bool pressed) { drag(x, width, pressed); }, this)),
m_after(new Spectrum(m_value, true, this)), m_readout(new QLabel(this))
{
auto *before = new Spectrum(m_value, false, this);
before->setObjectName(QStringLiteral("spectrumBefore"));
m_handles->setObjectName(QStringLiteral("spectrumHandles"));
m_after->setObjectName(QStringLiteral("spectrumAfter"));
m_readout->setObjectName(QStringLiteral("spectrumReadout"));
// Swift's .caption in the secondary ink.
QFont small = m_readout->font();
small.setPixelSize(10);
m_readout->setFont(small);
m_readout->setForegroundRole(QPalette::PlaceholderText);
auto *column = new QVBoxLayout(this);
column->setContentsMargins(0, 0, 0, 0);
column->setSpacing(5);
column->addWidget(before);
column->addWidget(m_handles);
column->addWidget(m_after);
column->addWidget(m_readout, 0, Qt::AlignHCenter);
synchronize();
}
void SpectrumEditor::synchronize()
{
QStringList degrees;
for (const double handle : m_value().band().handles())
degrees << QStringLiteral("%1°").arg(std::lround(handle));
m_readout->setText(degrees.join(QStringLiteral(" ")));
m_handles->update();
m_after->update();
}
void SpectrumEditor::drag(double x, double width, bool pressed)
{
// A press starts afresh: Qt can lose a release.
if (pressed)
m_dragging.reset();
const double degrees = std::clamp(x, 0.0, width) / width * 360;
HueSaturationSettings settings = m_value();
HueBand band = settings.band();
const int index = m_dragging.value_or(nearestHandle(band, degrees));
m_dragging = index;
band.setHandle(index, degrees);
settings.setBand(band);
m_change(settings);
}
HueSaturationSheet::HueSaturationSheet(EditorSession &session, QWidget *parent)
: QWidget(parent), m_session(session), m_range(new QComboBox(this)),
m_samples{sampleButton(HueSampleMode::replace, this), sampleButton(HueSampleMode::add, this), sampleButton(HueSampleMode::remove, this)},
@@ -382,15 +265,25 @@ QWidget *HueSaturationSheet::row(size_t index)
m_fields[index]->setModified(false);
synchronize();
}});
auto *slider = new QSlider(Qt::Horizontal, box);
const auto [low, high] = bounds(index, current(m_session).colorize);
auto *slider = new CameraRawSlider(
low, high, track(index, current(m_session)), title + QStringLiteral(". Double-click to reset."),
[this, index](double value) {
// Swift sets whole numbers; unchanged, the knob keeps its travel.
const double rounded = std::round(value);
if ((current(m_session).*rows[index].value)() == rounded)
return;
change([&](HueSaturationSettings &settings) { (settings.*rows[index].set)(rounded); });
// Its value replaces the field's typing.
m_fields[index]->setModified(false);
synchronize();
},
[this, index] { reset(index); }, box);
slider->setObjectName(title.toLower() + QStringLiteral("Slider"));
label->setBuddy(slider);
connect(slider, &QSlider::valueChanged, this, [this, index](int value) {
change([&](HueSaturationSettings &settings) { (settings.*rows[index].set)(value / sliderScale); });
// Its value replaces the field's typing.
m_fields[index]->setModified(false);
synchronize();
});
// After the scrub's filter, so this one runs first.
label->installEventFilter(this);
m_titles[index] = label;
// A readable number applies as typed, as Swift's value binding.
auto *field = new PickerField([this, index] {
PickerField &edited = *m_fields[index];
@@ -426,6 +319,29 @@ QWidget *HueSaturationSheet::row(size_t index)
return box;
}
void HueSaturationSheet::reset(size_t index)
{
change([index](HueSaturationSettings &settings) {
const HueSaturationSettings start = settings.colorize ? HueSaturationSettings::colorizeStart() : HueSaturationSettings();
(settings.*rows[index].set)((start.*rows[index].value)());
});
m_fields[index]->setModified(false);
synchronize();
}
// Swift's double click on a title resets its slider.
bool HueSaturationSheet::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::MouseButtonDblClick && static_cast<QMouseEvent *>(event)->button() == Qt::LeftButton) {
for (size_t index = 0; index < m_titles.size(); ++index) {
if (m_titles[index] == watched && m_titles[index]->isEnabled())
reset(index);
}
}
// The scrub still takes the second press, as Swift's drag.
return QWidget::eventFilter(watched, event);
}
void HueSaturationSheet::change(const std::function<void(HueSaturationSettings &)> &edit)
{
HueSaturationSettings settings = current(m_session);
@@ -454,12 +370,8 @@ void HueSaturationSheet::synchronize()
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.
const QSignalBlocker quiet(m_sliders[index]);
m_sliders[index]->setRange(int(low * sliderScale), int(high * sliderScale));
m_sliders[index]->setValue(int(std::lround(std::clamp(value, low, high) * sliderScale)));
}
// Swift's updateNSView: range and track follow the settings.
m_sliders[index]->reshape(low, high, track(index, settings), std::clamp(value, low, high));
// Ungrouped digits; a field being typed in keeps its typing.
QLocale locale = m_fields[index]->locale();
locale.setNumberOptions(QLocale::OmitGroupSeparator);
@@ -476,3 +388,8 @@ void HueSaturationSheet::synchronize()
// An adjustment layer's editor ignores the selection.
m_limited->setVisible(!m_session.adjustmentOriginal() && m_session.selection());
}
HueSaturationSheet::~HueSaturationSheet()
{
releaseFocus(*this);
}
+9 -1
View File
@@ -1,5 +1,6 @@
#pragma once
#include "Document/EditorSession.h"
#include "UI/CameraRawSlider.h"
#include <QWidget>
#include <array>
#include <functional>
@@ -40,12 +41,18 @@ class HueSaturationSheet : public QWidget {
Q_OBJECT
public:
explicit HueSaturationSheet(EditorSession &session, QWidget *parent = nullptr);
~HueSaturationSheet() override;
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
private:
// Swift's settings binding: changed, then handed to the session.
void change(const std::function<void(HueSaturationSettings &)> &edit);
// Swift's slider row: title, slider, field and unit.
QWidget *row(size_t index);
// Swift's resetValues: that row back to Colorize's start or none.
void reset(size_t index);
void synchronize();
EditorSession &m_session;
@@ -54,7 +61,8 @@ private:
QWidget *const m_divider;
QToolButton *const m_targeting;
// Hue, Saturation, Lightness; the rows make them.
std::array<QSlider *, 3> m_sliders{};
std::array<CameraRawSlider *, 3> m_sliders{};
std::array<QLabel *, 3> m_titles{};
std::array<PickerField *, 3> m_fields{};
// Each title's scrub; Colorize changes its range.
std::array<NumericScrub *, 3> m_scrubs{};
+5
View File
@@ -343,3 +343,8 @@ void ImageSizeSheet::synchronize()
m_result->setForegroundRole(isValid ? QPalette::PlaceholderText : QPalette::BrightText);
m_resize->setEnabled(isValid);
}
ImageSizeSheet::~ImageSizeSheet()
{
releaseFocus(*this);
}
+1
View File
@@ -17,6 +17,7 @@ class ImageSizeSheet : public QWidget {
Q_OBJECT
public:
ImageSizeSheet(const CanvasDocument &document, std::function<void(std::optional<ImageSizeOptions>)> finish, QWidget *parent = nullptr);
~ImageSizeSheet() override;
private:
bool valid() const;
+5
View File
@@ -429,3 +429,8 @@ void LevelsSheet::synchronize()
m_spinner->setVisible(edit->committing);
setEnabled(!edit->committing);
}
LevelsSheet::~LevelsSheet()
{
releaseFocus(*this);
}
+1
View File
@@ -15,6 +15,7 @@ class LevelsSheet : public QWidget {
Q_OBJECT
public:
explicit LevelsSheet(EditorSession &session, QWidget *parent = nullptr);
~LevelsSheet() override;
protected:
// A new theme recolours the eyedroppers' ink.