Improve color selection and display (#5057)
Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
#include "widgets/helper/color/AlphaSlider.hpp"
|
||||
|
||||
#include "widgets/helper/color/Checkerboard.hpp"
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QPainterPath>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int SLIDER_WIDTH = 256;
|
||||
constexpr int SLIDER_HEIGHT = 12;
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
AlphaSlider::AlphaSlider(QColor color, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, alpha_(color.alpha())
|
||||
, color_(color)
|
||||
{
|
||||
this->setSizePolicy({QSizePolicy::Expanding, QSizePolicy::Fixed});
|
||||
}
|
||||
|
||||
void AlphaSlider::setColor(QColor color)
|
||||
{
|
||||
if (this->color_ == color)
|
||||
{
|
||||
return;
|
||||
}
|
||||
this->alpha_ = color.alpha();
|
||||
this->color_ = color;
|
||||
this->cachedPixmap_ = {};
|
||||
this->update();
|
||||
}
|
||||
|
||||
int AlphaSlider::alpha() const
|
||||
{
|
||||
return this->alpha_;
|
||||
}
|
||||
|
||||
QSize AlphaSlider::sizeHint() const
|
||||
{
|
||||
return {SLIDER_WIDTH, SLIDER_HEIGHT};
|
||||
}
|
||||
|
||||
void AlphaSlider::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
this->updatePixmap();
|
||||
}
|
||||
|
||||
int AlphaSlider::xPosToAlpha(int xPos) const
|
||||
{
|
||||
return (xPos * 255) / (this->width() - this->height());
|
||||
}
|
||||
|
||||
void AlphaSlider::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->buttons().testFlag(Qt::MouseButton::LeftButton))
|
||||
{
|
||||
this->trackingMouseEvents_ = true;
|
||||
this->updateFromEvent(event);
|
||||
this->setFocus(Qt::FocusReason::MouseFocusReason);
|
||||
}
|
||||
}
|
||||
void AlphaSlider::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if (this->trackingMouseEvents_)
|
||||
{
|
||||
this->updateFromEvent(event);
|
||||
event->accept();
|
||||
}
|
||||
}
|
||||
void AlphaSlider::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
if (this->trackingMouseEvents_ &&
|
||||
event->buttons().testFlag(Qt::MouseButton::LeftButton))
|
||||
{
|
||||
this->updateFromEvent(event);
|
||||
this->trackingMouseEvents_ = false;
|
||||
event->accept();
|
||||
}
|
||||
}
|
||||
|
||||
void AlphaSlider::updateFromEvent(QMouseEvent *event)
|
||||
{
|
||||
int cornerRadius = this->height() / 2;
|
||||
auto clampedX = std::clamp(event->pos().x(), cornerRadius,
|
||||
this->width() - cornerRadius);
|
||||
this->setAlpha(this->xPosToAlpha(clampedX - cornerRadius));
|
||||
}
|
||||
|
||||
void AlphaSlider::updatePixmap()
|
||||
{
|
||||
this->cachedPixmap_ = QPixmap(this->size());
|
||||
this->cachedPixmap_.fill(Qt::transparent);
|
||||
QPainter painter(&this->cachedPixmap_);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
qreal cornerRadius = (qreal)this->height() / 2.0;
|
||||
|
||||
QPainterPath mask;
|
||||
mask.addRoundedRect(QRect({0, 0}, this->size()), cornerRadius,
|
||||
cornerRadius);
|
||||
painter.setClipPath(mask);
|
||||
|
||||
drawCheckerboard(painter, this->size(), this->height() / 2);
|
||||
|
||||
QLinearGradient gradient(cornerRadius, 0.0,
|
||||
(qreal)this->width() - cornerRadius, 0.0);
|
||||
QColor start = this->color_;
|
||||
QColor end = this->color_;
|
||||
start.setAlpha(0);
|
||||
end.setAlpha(255);
|
||||
|
||||
gradient.setColorAt(0.0, start);
|
||||
gradient.setColorAt(1.0, end);
|
||||
|
||||
painter.setPen({Qt::transparent, 0});
|
||||
painter.setBrush(gradient);
|
||||
painter.drawRect(QRect({0, 0}, this->size()));
|
||||
}
|
||||
|
||||
void AlphaSlider::paintEvent(QPaintEvent * /*event*/)
|
||||
{
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
if (this->cachedPixmap_.isNull())
|
||||
{
|
||||
this->updatePixmap();
|
||||
}
|
||||
|
||||
painter.drawPixmap(this->rect().topLeft(), this->cachedPixmap_);
|
||||
|
||||
int cornerRadius = this->height() / 2;
|
||||
|
||||
QPoint circ = {
|
||||
cornerRadius +
|
||||
(this->alpha() * (this->width() - 2 * cornerRadius)) / 255,
|
||||
cornerRadius};
|
||||
auto circleColor = 0;
|
||||
painter.setPen({QColor(circleColor, circleColor, circleColor), 2});
|
||||
auto opaqueBase = this->color_;
|
||||
opaqueBase.setAlpha(255);
|
||||
painter.setBrush(opaqueBase);
|
||||
painter.drawEllipse(circ, cornerRadius - 1, cornerRadius - 1);
|
||||
}
|
||||
|
||||
void AlphaSlider::setAlpha(int alpha)
|
||||
{
|
||||
if (this->alpha_ == alpha)
|
||||
{
|
||||
return;
|
||||
}
|
||||
this->alpha_ = alpha;
|
||||
this->color_.setAlpha(alpha);
|
||||
|
||||
emit this->colorChanged(this->color_);
|
||||
this->update();
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class AlphaSlider : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AlphaSlider(QColor color, QWidget *parent = nullptr);
|
||||
|
||||
QSize sizeHint() const override;
|
||||
|
||||
int alpha() const;
|
||||
|
||||
signals:
|
||||
void colorChanged(QColor color) const;
|
||||
|
||||
public slots:
|
||||
void setColor(QColor color);
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
|
||||
private:
|
||||
int alpha_ = 255;
|
||||
QColor color_;
|
||||
|
||||
QPixmap cachedPixmap_;
|
||||
|
||||
bool trackingMouseEvents_ = false;
|
||||
|
||||
void updatePixmap();
|
||||
int xPosToAlpha(int xPos) const;
|
||||
|
||||
void updateFromEvent(QMouseEvent *event);
|
||||
|
||||
void setAlpha(int alpha);
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,29 @@
|
||||
#include "widgets/helper/color/Checkerboard.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
void drawCheckerboard(QPainter &painter, QRect rect, int tileSize)
|
||||
{
|
||||
painter.fillRect(rect, QColor(255, 255, 255));
|
||||
|
||||
if (tileSize <= 0)
|
||||
{
|
||||
tileSize = 1;
|
||||
}
|
||||
|
||||
int overflowY = rect.height() % tileSize == 0 ? 0 : 1;
|
||||
int overflowX = rect.width() % tileSize == 0 ? 0 : 1;
|
||||
for (int row = 0; row < rect.height() / tileSize + overflowY; row++)
|
||||
{
|
||||
int offsetX = row % 2 == 0 ? 0 : 1;
|
||||
for (int col = offsetX; col < rect.width() / tileSize + overflowX;
|
||||
col += 2)
|
||||
{
|
||||
painter.fillRect(rect.x() + col * tileSize,
|
||||
rect.y() + row * tileSize, tileSize, tileSize,
|
||||
QColor(204, 204, 204));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
void drawCheckerboard(QPainter &painter, QRect rect, int tileSize = 4);
|
||||
inline void drawCheckerboard(QPainter &painter, QSize size, int tileSize = 4)
|
||||
{
|
||||
drawCheckerboard(painter, {{0, 0}, size}, tileSize);
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,81 @@
|
||||
#include "widgets/helper/color/ColorButton.hpp"
|
||||
|
||||
#include "widgets/helper/color/Checkerboard.hpp"
|
||||
|
||||
#include <QPainterPath>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
ColorButton::ColorButton(QColor color, QWidget *parent)
|
||||
: QAbstractButton(parent)
|
||||
, currentColor_(color)
|
||||
{
|
||||
this->setSizePolicy({QSizePolicy::Expanding, QSizePolicy::Expanding});
|
||||
this->setMinimumSize({30, 30});
|
||||
}
|
||||
|
||||
QSize ColorButton::sizeHint() const
|
||||
{
|
||||
return {50, 30};
|
||||
}
|
||||
|
||||
void ColorButton::setColor(const QColor &color)
|
||||
{
|
||||
if (this->currentColor_ == color)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this->currentColor_ = color;
|
||||
this->update();
|
||||
}
|
||||
|
||||
QColor ColorButton::color() const
|
||||
{
|
||||
return this->currentColor_;
|
||||
}
|
||||
|
||||
void ColorButton::resizeEvent(QResizeEvent * /*event*/)
|
||||
{
|
||||
this->checkerboardCacheValid_ = false;
|
||||
this->repaint();
|
||||
}
|
||||
|
||||
void ColorButton::paintEvent(QPaintEvent * /*event*/)
|
||||
{
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
auto rect = this->rect();
|
||||
|
||||
if (this->currentColor_.alpha() != 255)
|
||||
{
|
||||
if (!this->checkerboardCacheValid_)
|
||||
{
|
||||
QPixmap cache(this->size());
|
||||
cache.fill(Qt::transparent);
|
||||
|
||||
QPainter cachePainter(&cache);
|
||||
cachePainter.setRenderHint(QPainter::Antialiasing);
|
||||
QPainterPath path;
|
||||
path.addRoundedRect(QRect(1, 1, this->size().width() - 2,
|
||||
this->size().height() - 2),
|
||||
5, 5);
|
||||
cachePainter.setClipPath(path);
|
||||
|
||||
drawCheckerboard(cachePainter, this->size(),
|
||||
std::min(this->height() / 2, 10));
|
||||
cachePainter.end();
|
||||
|
||||
this->checkerboardCache_ = std::move(cache);
|
||||
this->checkerboardCacheValid_ = true;
|
||||
}
|
||||
painter.drawPixmap(rect.topLeft(), this->checkerboardCache_);
|
||||
}
|
||||
painter.setBrush(this->currentColor_);
|
||||
painter.setPen({QColor(255, 255, 255, 127), 1});
|
||||
painter.drawRoundedRect(rect.x() + 1, rect.y() + 1, rect.width() - 2,
|
||||
rect.height() - 2, 5, 5);
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractButton>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class ColorButton : public QAbstractButton
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ColorButton(QColor color, QWidget *parent = nullptr);
|
||||
|
||||
QSize sizeHint() const override;
|
||||
|
||||
QColor color() const;
|
||||
|
||||
// NOLINTNEXTLINE(readability-redundant-access-specifiers)
|
||||
public slots:
|
||||
void setColor(const QColor &color);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
|
||||
private:
|
||||
QColor currentColor_;
|
||||
|
||||
QPixmap checkerboardCache_;
|
||||
bool checkerboardCacheValid_ = false;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,168 @@
|
||||
#include "widgets/helper/color/ColorInput.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
// from qtools_p.h
|
||||
int fromHex(char c) noexcept
|
||||
{
|
||||
if (c >= '0' && c <= '9')
|
||||
{
|
||||
return int(c - '0');
|
||||
}
|
||||
if (c >= 'A' && c <= 'F')
|
||||
{
|
||||
return int(c - 'A' + 10);
|
||||
}
|
||||
if (c >= 'a' && c <= 'f')
|
||||
{
|
||||
return int(c - 'a' + 10);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
QColor parseHexColor(const QString &text)
|
||||
{
|
||||
if (text.length() == 5) // #rgba
|
||||
{
|
||||
auto alphaHex = fromHex(text[4].toLatin1());
|
||||
QStringView v(text);
|
||||
v.chop(1);
|
||||
QColor col(v);
|
||||
col.setAlpha(alphaHex);
|
||||
return col;
|
||||
}
|
||||
QColor col(text);
|
||||
if (col.isValid() && text.length() == 9) // #rrggbbaa
|
||||
{
|
||||
auto rgba = col.rgba();
|
||||
auto alpha = rgba & 0xff;
|
||||
QColor actual(rgba >> 8);
|
||||
actual.setAlpha((int)alpha);
|
||||
return actual;
|
||||
}
|
||||
return col;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
ColorInput::ColorInput(QColor color, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, currentColor_(color)
|
||||
, hexValidator_(QRegularExpression(
|
||||
R"(^#([A-Fa-f\d]{3,4}|[A-Fa-f\d]{6}|[A-Fa-f\d]{8})$)"))
|
||||
, layout_(this)
|
||||
{
|
||||
int row = 0;
|
||||
const auto initComponent = [&](Component &component, auto label,
|
||||
auto applyToColor) {
|
||||
component.lbl.setText(label);
|
||||
component.box.setRange(0, 255);
|
||||
QObject::connect(&component.box,
|
||||
qOverload<int>(&QSpinBox::valueChanged), this,
|
||||
[this, &component, applyToColor](int value) {
|
||||
if (component.value == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
applyToColor(this->currentColor_, value);
|
||||
|
||||
this->emitUpdate();
|
||||
});
|
||||
this->layout_.addWidget(&component.lbl, row, 0);
|
||||
this->layout_.addWidget(&component.box, row, 1);
|
||||
row++;
|
||||
};
|
||||
|
||||
initComponent(this->red_, "Red:", [](auto &color, int value) {
|
||||
color.setRed(value);
|
||||
});
|
||||
initComponent(this->green_, "Green:", [](auto &color, int value) {
|
||||
color.setGreen(value);
|
||||
});
|
||||
initComponent(this->blue_, "Red:", [](auto &color, int value) {
|
||||
color.setBlue(value);
|
||||
});
|
||||
initComponent(this->alpha_, "Alpha:", [](auto &color, int value) {
|
||||
color.setAlpha(value);
|
||||
});
|
||||
|
||||
this->hexLabel_.setText("Hex:");
|
||||
this->hexInput_.setValidator(&this->hexValidator_);
|
||||
QObject::connect(&this->hexInput_, &QLineEdit::editingFinished, [this]() {
|
||||
auto css = parseHexColor(this->hexInput_.text());
|
||||
if (!css.isValid() || this->currentColor_ == css)
|
||||
{
|
||||
return;
|
||||
}
|
||||
this->currentColor_ = css;
|
||||
this->emitUpdate();
|
||||
});
|
||||
this->layout_.addWidget(&this->hexLabel_, row, 0);
|
||||
this->layout_.addWidget(&this->hexInput_, row, 1);
|
||||
|
||||
this->updateComponents();
|
||||
}
|
||||
|
||||
void ColorInput::updateComponents()
|
||||
{
|
||||
auto color = this->currentColor_.toRgb();
|
||||
const auto updateComponent = [](Component &component, auto getValue) {
|
||||
int value = getValue();
|
||||
if (component.value != value)
|
||||
{
|
||||
component.value = value;
|
||||
component.box.setValue(value);
|
||||
}
|
||||
};
|
||||
updateComponent(this->red_, [&]() {
|
||||
return color.red();
|
||||
});
|
||||
updateComponent(this->green_, [&]() {
|
||||
return color.green();
|
||||
});
|
||||
updateComponent(this->blue_, [&]() {
|
||||
return color.blue();
|
||||
});
|
||||
updateComponent(this->alpha_, [&]() {
|
||||
return color.alpha();
|
||||
});
|
||||
|
||||
this->updateHex();
|
||||
}
|
||||
|
||||
void ColorInput::updateHex()
|
||||
{
|
||||
auto rgb = this->currentColor_.rgb();
|
||||
rgb <<= 8;
|
||||
rgb |= this->currentColor_.alpha();
|
||||
// we always need to update the CSS color
|
||||
this->hexInput_.setText(QStringLiteral("#%1").arg(rgb, 8, 16, QChar(u'0')));
|
||||
}
|
||||
|
||||
QColor ColorInput::color() const
|
||||
{
|
||||
return this->currentColor_;
|
||||
}
|
||||
|
||||
void ColorInput::setColor(QColor color)
|
||||
{
|
||||
if (this->currentColor_ == color)
|
||||
{
|
||||
return;
|
||||
}
|
||||
this->currentColor_ = color;
|
||||
this->updateComponents();
|
||||
// no emit, as we just got the updated color
|
||||
}
|
||||
|
||||
void ColorInput::emitUpdate()
|
||||
{
|
||||
this->updateComponents();
|
||||
// our components triggered this update, emit the new color
|
||||
emit this->colorChanged(this->currentColor_);
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QSpinBox>
|
||||
#include <QWidget>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class ColorInput : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ColorInput(QColor color, QWidget *parent = nullptr);
|
||||
|
||||
QColor color() const;
|
||||
|
||||
signals:
|
||||
void colorChanged(QColor color);
|
||||
|
||||
public slots:
|
||||
void setColor(QColor color);
|
||||
|
||||
private:
|
||||
QColor currentColor_;
|
||||
|
||||
struct Component {
|
||||
QLabel lbl;
|
||||
QSpinBox box;
|
||||
int value = -1;
|
||||
};
|
||||
|
||||
Component red_;
|
||||
Component green_;
|
||||
Component blue_;
|
||||
Component alpha_;
|
||||
|
||||
QLabel hexLabel_;
|
||||
QLineEdit hexInput_;
|
||||
QRegularExpressionValidator hexValidator_;
|
||||
|
||||
QGridLayout layout_;
|
||||
|
||||
void updateComponents();
|
||||
void updateHex();
|
||||
|
||||
void emitUpdate();
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,35 @@
|
||||
#include "widgets/helper/color/ColorItemDelegate.hpp"
|
||||
|
||||
#include "widgets/helper/color/Checkerboard.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
ColorItemDelegate::ColorItemDelegate(QObject *parent)
|
||||
: QStyledItemDelegate(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void ColorItemDelegate::paint(QPainter *painter,
|
||||
const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const
|
||||
{
|
||||
auto data = index.data(Qt::DecorationRole);
|
||||
|
||||
if (data.type() != QVariant::Color)
|
||||
{
|
||||
return QStyledItemDelegate::paint(painter, option, index);
|
||||
}
|
||||
auto color = data.value<QColor>();
|
||||
|
||||
painter->save();
|
||||
if (color.alpha() != 255)
|
||||
{
|
||||
drawCheckerboard(*painter, option.rect,
|
||||
std::min(option.rect.height() / 2, 10));
|
||||
}
|
||||
painter->setBrush(color);
|
||||
painter->drawRect(option.rect);
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <QStyledItemDelegate>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class ColorItemDelegate : public QStyledItemDelegate
|
||||
{
|
||||
public:
|
||||
explicit ColorItemDelegate(QObject *parent = nullptr);
|
||||
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const override;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,165 @@
|
||||
#include "widgets/helper/color/HueSlider.hpp"
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int SLIDER_WIDTH = 256;
|
||||
constexpr int SLIDER_HEIGHT = 12;
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
HueSlider::HueSlider(QColor color, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
this->setColor(color);
|
||||
this->setSizePolicy({QSizePolicy::Expanding, QSizePolicy::Fixed});
|
||||
}
|
||||
|
||||
void HueSlider::setColor(QColor color)
|
||||
{
|
||||
if (this->color_ == color)
|
||||
{
|
||||
return;
|
||||
}
|
||||
this->color_ = color.toHsv();
|
||||
|
||||
auto hue = std::max(this->color_.hue(), 0);
|
||||
if (this->hue_ == hue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this->hue_ = hue;
|
||||
this->update();
|
||||
}
|
||||
|
||||
int HueSlider::hue() const
|
||||
{
|
||||
return this->hue_;
|
||||
}
|
||||
|
||||
QSize HueSlider::sizeHint() const
|
||||
{
|
||||
return {SLIDER_WIDTH, SLIDER_HEIGHT};
|
||||
}
|
||||
|
||||
void HueSlider::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
this->updatePixmap();
|
||||
}
|
||||
|
||||
int HueSlider::xPosToHue(int xPos) const
|
||||
{
|
||||
return (xPos * 359) / (this->width() - this->height());
|
||||
}
|
||||
|
||||
void HueSlider::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->buttons().testFlag(Qt::MouseButton::LeftButton))
|
||||
{
|
||||
this->trackingMouseEvents_ = true;
|
||||
this->updateFromEvent(event);
|
||||
event->accept();
|
||||
this->setFocus(Qt::FocusReason::MouseFocusReason);
|
||||
}
|
||||
}
|
||||
void HueSlider::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if (this->trackingMouseEvents_)
|
||||
{
|
||||
this->updateFromEvent(event);
|
||||
event->accept();
|
||||
}
|
||||
}
|
||||
void HueSlider::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
if (this->trackingMouseEvents_ &&
|
||||
event->buttons().testFlag(Qt::MouseButton::LeftButton))
|
||||
{
|
||||
this->updateFromEvent(event);
|
||||
this->trackingMouseEvents_ = false;
|
||||
event->accept();
|
||||
}
|
||||
}
|
||||
|
||||
void HueSlider::updateFromEvent(QMouseEvent *event)
|
||||
{
|
||||
int cornerRadius = this->height() / 2;
|
||||
auto clampedX = std::clamp(event->pos().x(), cornerRadius,
|
||||
this->width() - cornerRadius);
|
||||
this->setHue(this->xPosToHue(clampedX - cornerRadius));
|
||||
}
|
||||
|
||||
void HueSlider::updatePixmap()
|
||||
{
|
||||
constexpr int nStops = 10;
|
||||
constexpr auto nStopsF = (qreal)nStops;
|
||||
|
||||
this->gradientPixmap_ = QPixmap(this->size());
|
||||
this->gradientPixmap_.fill(Qt::transparent);
|
||||
QPainter painter(&this->gradientPixmap_);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
qreal cornerRadius = (qreal)this->height() / 2.0;
|
||||
|
||||
QLinearGradient gradient(cornerRadius, 0.0,
|
||||
(qreal)this->width() - cornerRadius, 0.0);
|
||||
for (int i = 0; i <= nStops; i++)
|
||||
{
|
||||
gradient.setColorAt(
|
||||
(qreal)i / nStopsF,
|
||||
QColor::fromHsv(std::min((i * 360) / nStops, 359), 255, 255));
|
||||
}
|
||||
painter.setPen({Qt::transparent, 0});
|
||||
painter.setBrush(gradient);
|
||||
painter.drawRoundedRect(QRect({0, 0}, this->size()), cornerRadius,
|
||||
cornerRadius);
|
||||
}
|
||||
|
||||
void HueSlider::paintEvent(QPaintEvent * /*event*/)
|
||||
{
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
if (this->gradientPixmap_.isNull())
|
||||
{
|
||||
this->updatePixmap();
|
||||
}
|
||||
|
||||
painter.drawPixmap(this->rect().topLeft(), this->gradientPixmap_);
|
||||
|
||||
int cornerRadius = this->height() / 2;
|
||||
|
||||
QPoint circ = {
|
||||
cornerRadius + (this->hue() * (this->width() - 2 * cornerRadius)) / 360,
|
||||
cornerRadius};
|
||||
auto circleColor = 0;
|
||||
painter.setPen({QColor(circleColor, circleColor, circleColor), 2});
|
||||
painter.setBrush(QColor::fromHsv(this->hue(), 255, 255));
|
||||
painter.drawEllipse(circ, cornerRadius - 1, cornerRadius - 1);
|
||||
}
|
||||
|
||||
void HueSlider::setHue(int hue)
|
||||
{
|
||||
if (this->hue_ == hue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
this->hue_ = hue;
|
||||
// ugh
|
||||
int h{};
|
||||
int s{};
|
||||
int v{};
|
||||
int a{};
|
||||
this->color_.getHsv(&h, &s, &v, &a);
|
||||
this->color_.setHsv(this->hue_, s, v, a);
|
||||
|
||||
emit this->colorChanged(this->color_);
|
||||
this->update();
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class HueSlider : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
HueSlider(QColor color, QWidget *parent = nullptr);
|
||||
|
||||
QSize sizeHint() const override;
|
||||
|
||||
int hue() const;
|
||||
|
||||
signals:
|
||||
void colorChanged(QColor color) const;
|
||||
|
||||
public slots:
|
||||
void setColor(QColor color);
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
|
||||
private:
|
||||
int hue_ = 0;
|
||||
QColor color_;
|
||||
|
||||
QPixmap gradientPixmap_;
|
||||
|
||||
bool trackingMouseEvents_ = false;
|
||||
|
||||
void updatePixmap();
|
||||
int xPosToHue(int xPos) const;
|
||||
|
||||
void updateFromEvent(QMouseEvent *event);
|
||||
|
||||
void setHue(int hue);
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,192 @@
|
||||
#include "widgets/helper/color/SBCanvas.hpp"
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int PICKER_WIDTH = 256;
|
||||
constexpr int PICKER_HEIGHT = 256;
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
SBCanvas::SBCanvas(QColor color, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
this->setColor(color);
|
||||
this->setSizePolicy({QSizePolicy::Fixed, QSizePolicy::Fixed});
|
||||
}
|
||||
|
||||
void SBCanvas::setColor(QColor color)
|
||||
{
|
||||
color = color.toHsv();
|
||||
if (this->color_ == color)
|
||||
{
|
||||
return;
|
||||
}
|
||||
this->color_ = color;
|
||||
|
||||
int h{};
|
||||
int s{};
|
||||
int v{};
|
||||
color.getHsv(&h, &s, &v);
|
||||
h = std::max(h, 0);
|
||||
|
||||
if (this->hue_ == h && this->saturation_ == s && this->brightness_ == v)
|
||||
{
|
||||
return; // alpha changed
|
||||
}
|
||||
this->hue_ = h;
|
||||
this->saturation_ = s;
|
||||
this->brightness_ = v;
|
||||
|
||||
this->gradientPixmap_ = {};
|
||||
this->update();
|
||||
}
|
||||
|
||||
int SBCanvas::saturation() const
|
||||
{
|
||||
return this->saturation_;
|
||||
}
|
||||
|
||||
int SBCanvas::brightness() const
|
||||
{
|
||||
return this->brightness_;
|
||||
}
|
||||
|
||||
QSize SBCanvas::sizeHint() const
|
||||
{
|
||||
return {PICKER_WIDTH, PICKER_HEIGHT};
|
||||
}
|
||||
|
||||
void SBCanvas::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
this->updatePixmap();
|
||||
}
|
||||
|
||||
int SBCanvas::xPosToSaturation(int xPos) const
|
||||
{
|
||||
return (xPos * 255) / this->width();
|
||||
}
|
||||
|
||||
int SBCanvas::yPosToBrightness(int yPos) const
|
||||
{
|
||||
return 255 - (yPos * 255) / this->height();
|
||||
}
|
||||
|
||||
void SBCanvas::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->buttons().testFlag(Qt::MouseButton::LeftButton))
|
||||
{
|
||||
this->trackingMouseEvents_ = true;
|
||||
this->updateFromEvent(event);
|
||||
event->accept();
|
||||
this->setFocus(Qt::FocusReason::MouseFocusReason);
|
||||
}
|
||||
}
|
||||
void SBCanvas::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if (this->trackingMouseEvents_)
|
||||
{
|
||||
this->updateFromEvent(event);
|
||||
event->accept();
|
||||
}
|
||||
}
|
||||
void SBCanvas::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
if (this->trackingMouseEvents_ &&
|
||||
event->buttons().testFlag(Qt::MouseButton::LeftButton))
|
||||
{
|
||||
this->updateFromEvent(event);
|
||||
this->trackingMouseEvents_ = false;
|
||||
event->accept();
|
||||
}
|
||||
}
|
||||
|
||||
void SBCanvas::updateFromEvent(QMouseEvent *event)
|
||||
{
|
||||
auto clampedX = std::clamp(event->pos().x(), 0, this->width());
|
||||
auto clampedY = std::clamp(event->pos().y(), 0, this->height());
|
||||
|
||||
bool updated = this->setSaturation(this->xPosToSaturation(clampedX));
|
||||
updated |= this->setBrightness(this->yPosToBrightness(clampedY));
|
||||
|
||||
if (updated)
|
||||
{
|
||||
this->emitUpdatedColor();
|
||||
this->update();
|
||||
}
|
||||
}
|
||||
|
||||
void SBCanvas::updatePixmap()
|
||||
{
|
||||
int w = this->width();
|
||||
int h = this->height();
|
||||
QImage img(w, h, QImage::Format_RGB32);
|
||||
uint *pixel = (uint *)img.scanLine(0);
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
QColor c = QColor::fromHsv(this->hue_, this->xPosToSaturation(x),
|
||||
this->yPosToBrightness(y));
|
||||
*pixel = c.rgb();
|
||||
pixel++;
|
||||
}
|
||||
}
|
||||
this->gradientPixmap_ = QPixmap::fromImage(img);
|
||||
}
|
||||
|
||||
void SBCanvas::paintEvent(QPaintEvent * /*event*/)
|
||||
{
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
if (this->gradientPixmap_.isNull())
|
||||
{
|
||||
this->updatePixmap();
|
||||
}
|
||||
|
||||
painter.drawPixmap(this->rect().topLeft(), this->gradientPixmap_);
|
||||
|
||||
QPoint circ = {(this->saturation() * this->width()) / 256,
|
||||
((255 - this->brightness()) * this->height()) / 256};
|
||||
auto circleColor = this->brightness() >= 128 ? 50 : 200;
|
||||
painter.setPen({QColor(circleColor, circleColor, circleColor), 2});
|
||||
painter.setBrush(
|
||||
QColor::fromHsv(this->hue_, this->saturation_, this->brightness_));
|
||||
painter.drawEllipse(circ, 5, 5);
|
||||
}
|
||||
|
||||
bool SBCanvas::setSaturation(int saturation)
|
||||
{
|
||||
if (this->saturation_ == saturation)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
this->saturation_ = saturation;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SBCanvas::setBrightness(int brightness)
|
||||
{
|
||||
if (this->brightness_ == brightness)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
this->brightness_ = brightness;
|
||||
return true;
|
||||
}
|
||||
|
||||
void SBCanvas::emitUpdatedColor()
|
||||
{
|
||||
this->color_.setHsv(this->hue_, this->saturation_, this->brightness_,
|
||||
this->color_.alpha());
|
||||
emit this->colorChanged(this->color_);
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
/// 2D canvas for saturation (x-axis) and brightness (y-axis)
|
||||
class SBCanvas : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SBCanvas(QColor color, QWidget *parent = nullptr);
|
||||
|
||||
QSize sizeHint() const override;
|
||||
|
||||
int saturation() const;
|
||||
int brightness() const;
|
||||
|
||||
signals:
|
||||
void colorChanged(QColor color) const;
|
||||
|
||||
public slots:
|
||||
void setColor(QColor color);
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
|
||||
private:
|
||||
int hue_ = 0;
|
||||
int saturation_ = 0;
|
||||
int brightness_ = 0;
|
||||
QColor color_;
|
||||
|
||||
QPixmap gradientPixmap_;
|
||||
|
||||
bool trackingMouseEvents_ = false;
|
||||
|
||||
void updatePixmap();
|
||||
int xPosToSaturation(int xPos) const;
|
||||
int yPosToBrightness(int yPos) const;
|
||||
|
||||
void updateFromEvent(QMouseEvent *event);
|
||||
|
||||
[[nodiscard]] bool setSaturation(int saturation);
|
||||
[[nodiscard]] bool setBrightness(int brightness);
|
||||
|
||||
void emitUpdatedColor();
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
Reference in New Issue
Block a user