137 lines
6.9 KiB
C++
137 lines
6.9 KiB
C++
#include "UI/GradientControls.h"
|
||
#include "Document/EditorSession.h"
|
||
#include "UI/LassoControls.h"
|
||
#include "UI/NumericScrub.h"
|
||
#include <QButtonGroup>
|
||
#include <QCheckBox>
|
||
#include <QComboBox>
|
||
#include <QPainter>
|
||
#include <QPainterPath>
|
||
#include <QPushButton>
|
||
#include <QSignalBlocker>
|
||
#include <QSlider>
|
||
#include <QToolButton>
|
||
#include <cmath>
|
||
|
||
namespace {
|
||
// The session's gradient settings with one field rewritten.
|
||
void changeGradient(EditorSession &session, const std::function<void(GradientSettings &)> &change)
|
||
{
|
||
GradientSettings settings = session.gradientSettings();
|
||
change(settings);
|
||
session.setGradientSettings(settings);
|
||
}
|
||
}
|
||
|
||
GradientSwatch::GradientSwatch(const EditorSession &session, QWidget *parent) : QWidget(parent), m_session(session)
|
||
{
|
||
setObjectName(QStringLiteral("gradientSwatch"));
|
||
setFixedSize(56, 18);
|
||
}
|
||
|
||
void GradientSwatch::paintEvent(QPaintEvent *)
|
||
{
|
||
QPainter painter(this);
|
||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||
QPainterPath shape;
|
||
shape.addRoundedRect(QRectF(rect()), 3, 3);
|
||
painter.setClipPath(shape);
|
||
painter.fillRect(rect(), Qt::white);
|
||
// Four-point squares: transparency reads as transparency.
|
||
const QColor square = QColor::fromRgbF(0.5, 0.5, 0.5, 0.45);
|
||
for (int row = 0; row * 4 < height(); ++row) {
|
||
for (int column = 0; column * 4 < width(); ++column) {
|
||
if ((row + column) % 2 == 0)
|
||
painter.fillRect(QRectF(column * 4, row * 4, 4, 4), square);
|
||
}
|
||
}
|
||
const std::array<QColor, 2> colors = m_session.gradientColors(false);
|
||
QLinearGradient ramp(QPointF(0, 0), QPointF(width(), 0));
|
||
ramp.setColorAt(0, colors[0]);
|
||
ramp.setColorAt(1, colors[1]);
|
||
painter.fillRect(rect(), ramp);
|
||
painter.setClipping(false);
|
||
painter.setPen(QPen(QColor(0, 0, 0, 128), 1));
|
||
painter.drawRoundedRect(QRectF(rect()).adjusted(0.5, 0.5, -0.5, -0.5), 2.5, 2.5);
|
||
}
|
||
|
||
GradientControls::GradientControls(EditorSession &session, QWidget *parent)
|
||
: ToolHeaderBar(QStringLiteral("Gradient"), parent), m_session(session), m_shapes(new QButtonGroup(this)),
|
||
m_linear(shape(QStringLiteral("gradientLinear"), rawValue(GradientShape::linear), false)),
|
||
m_radial(shape(QStringLiteral("gradientRadial"), rawValue(GradientShape::radial), true)), m_swatch(new GradientSwatch(session, this)),
|
||
m_style(new QComboBox(this)), m_reverse(new QCheckBox(QStringLiteral("Reverse"), this)), m_opacitySlider(new QSlider(Qt::Horizontal, this)),
|
||
m_opacity(new SelectionAmountField(session, 1, 100, [this] { return m_session.gradientSettings().opacity * 100; },
|
||
[this](double percent) {
|
||
changeGradient(m_session, [percent](GradientSettings &gradient) { gradient.opacity = percent / 100; });
|
||
}, this)),
|
||
m_mask(new QLabel(QStringLiteral("Mask"), this)), m_cancel(new QPushButton(QStringLiteral("Cancel"), this)),
|
||
m_apply(new QPushButton(QStringLiteral("Apply"), this))
|
||
{
|
||
m_style->setObjectName(QStringLiteral("gradientStyle"));
|
||
for (const GradientStyle style : allGradientStyles)
|
||
m_style->addItem(rawValue(style));
|
||
connect(m_style, &QComboBox::activated, this,
|
||
[this](int index) { changeGradient(m_session, [index](GradientSettings &gradient) { gradient.style = allGradientStyles[size_t(index)]; }); });
|
||
m_reverse->setObjectName(QStringLiteral("gradientReverse"));
|
||
connect(m_reverse, &QCheckBox::clicked, this, [this](bool on) { changeGradient(m_session, [on](GradientSettings &gradient) { gradient.reversed = on; }); });
|
||
m_opacitySlider->setObjectName(QStringLiteral("gradientOpacitySlider"));
|
||
m_opacitySlider->setRange(10, 1000);
|
||
m_opacitySlider->setFixedWidth(100);
|
||
connect(m_opacitySlider, &QSlider::valueChanged, this,
|
||
[this](int value) { changeGradient(m_session, [value](GradientSettings &gradient) { gradient.opacity = value / 1000.0; }); });
|
||
m_opacity->setObjectName(QStringLiteral("gradientOpacity"));
|
||
m_opacity->setFixedWidth(42);
|
||
m_opacity->setToolTip(QStringLiteral("Press 1–9 for 10–90%, 0 for 100%"));
|
||
m_mask->setObjectName(QStringLiteral("gradientMaskNote"));
|
||
m_mask->setForegroundRole(QPalette::PlaceholderText);
|
||
m_cancel->setObjectName(QStringLiteral("gradientCancel"));
|
||
m_apply->setObjectName(QStringLiteral("gradientApply"));
|
||
connect(m_cancel, &QPushButton::clicked, this, [this] { m_session.cancelGradient(); });
|
||
connect(m_apply, &QPushButton::clicked, this, [this] { m_session.commitGradient(); });
|
||
auto *opacity = new QLabel(QStringLiteral("Opacity"), this);
|
||
new NumericScrub(opacity, {.sensitivity = 0.01, .low = 0.01, .high = 1, .step = std::nullopt,
|
||
.value = [this] { return m_session.gradientSettings().opacity; },
|
||
.set = [this](double value) {
|
||
changeGradient(m_session, [value](GradientSettings &gradient) { gradient.opacity = value; });
|
||
m_opacity->showValue();
|
||
}});
|
||
scrolling({m_linear, m_radial, m_swatch, m_style, m_reverse, opacity, m_opacitySlider, m_opacity, new QLabel(QStringLiteral("%"), this)},
|
||
QStringLiteral("gradientScroll"));
|
||
for (QWidget *widget : std::initializer_list<QWidget *>{m_mask, m_cancel, m_apply})
|
||
row->addWidget(widget);
|
||
connect(&m_session, &EditorSession::changed, this, &GradientControls::synchronize);
|
||
synchronize();
|
||
}
|
||
|
||
// A segment of Swift's shape picker.
|
||
QToolButton *GradientControls::shape(const QString &name, const QString &text, bool radial)
|
||
{
|
||
auto *button = new QToolButton(this);
|
||
button->setObjectName(name);
|
||
button->setText(text);
|
||
button->setCheckable(true);
|
||
button->setToolTip(QStringLiteral("Linear runs along the line; Radial spreads out from the start point"));
|
||
m_shapes->addButton(button);
|
||
connect(button, &QToolButton::clicked, this, [this, radial] {
|
||
changeGradient(m_session, [radial](GradientSettings &gradient) { gradient.shape = radial ? GradientShape::radial : GradientShape::linear; });
|
||
});
|
||
return button;
|
||
}
|
||
|
||
void GradientControls::synchronize()
|
||
{
|
||
const GradientSettings &gradient = m_session.gradientSettings();
|
||
(gradient.shape == GradientShape::radial ? m_radial : m_linear)->setChecked(true);
|
||
m_style->setCurrentIndex(int(gradient.style));
|
||
m_reverse->setChecked(gradient.reversed);
|
||
// The slider shows the session's number without writing back.
|
||
const QSignalBlocker blocker(m_opacitySlider);
|
||
m_opacitySlider->setValue(int(std::lround(gradient.opacity * 1000)));
|
||
m_opacity->sync(int(std::lround(gradient.opacity * 100)));
|
||
m_mask->setVisible(m_session.isMaskSelected());
|
||
m_cancel->setVisible(m_session.gradientEdit().has_value());
|
||
m_apply->setVisible(m_session.gradientEdit().has_value());
|
||
m_swatch->update();
|
||
setEnabled(!m_session.showsBusy() && m_session.document().has_value());
|
||
}
|