123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436 |
- #include "GradientWidget.h"
- #include <QPaintEvent>
- #include <QPainter>
- #include <QStyleOptionSlider>
- #include <QColorDialog>
- #include <QStyle>
- #include <algorithm>
- #include <cmath>
- GradientWidget::GradientWidget(QWidget* parent) :
- QWidget{ parent }
- {
- dragging = false;
- selectedHandle = -1;
- mouseOver = -1;
- setMouseTracking(true);
- }
- const QVector<QPair<float, QColor>>& GradientWidget::getGradient(void) const
- {
- return points;
- }
- void GradientWidget::setGradient(QVector<QPair<float, QColor>> vec)
- {
- points = std::move(vec);
- }
- QColor lerp(const QColor& a, const QColor& b, float v)
- {
- float ar = a.redF();
- float ag = a.greenF();
- float ab = a.blueF();
- float br = b.redF();
- float bg = b.greenF();
- float bb = b.blueF();
- const float gamma = 2.2;
- ar = std::pow(ar, gamma);
- ag = std::pow(ag, gamma);
- ab = std::pow(ab, gamma);
- br = std::pow(br, gamma);
- bg = std::pow(bg, gamma);
- bb = std::pow(bb, gamma);
- float nr = br * v + (1 - v) * ar;
- float ng = bg * v + (1 - v) * ag;
- float nb = bb * v + (1 - v) * ab;
- nr = std::pow(nr, 1/gamma);
- ng = std::pow(ng, 1/gamma);
- nb = std::pow(nb, 1/gamma);
- return QColor{ int(255 * nr), int(255 * ng), int(255 * nb) };
- }
- QColor GradientWidget::colorAtY(float y)
- {
- float v = handleYToGradVal(y);
- QColor up = QColor(QColor::Invalid);
- QColor down = QColor(QColor::Invalid);
- float upv = 0;
- float downv = 1;
- for (const auto& [val, color] : points) {
- if (val >= upv && val < v) {
- upv = val;
- up = color;
- }
- if (val <= downv && val > v) {
- downv = val;
- down = color;
- }
- }
- if (!up.isValid())
- return down;
- if (!down.isValid())
- return up;
- return lerp(up, down, (v - upv) / (downv - upv));
- }
- void GradientWidget::paintEvent(QPaintEvent* e)
- {
- QPainter painter{ this };
- QRect gradientRect = getGradientRect();
- QStyleOption frameOptions;
- frameOptions.init(this);
- frameOptions.rect = gradientRect;
- frameOptions.state |= QStyle::State_Sunken;
- style()->drawPrimitive(
- QStyle::PrimitiveElement::PE_Frame, &frameOptions, &painter, this);
- int fhmargins = style()->pixelMetric(QStyle::PixelMetric::PM_FocusFrameHMargin);
- int fvmargins = style()->pixelMetric(QStyle::PixelMetric::PM_FocusFrameVMargin);
- if (fhmargins == -1) {
- fhmargins = fvmargins = 3;
- }
- QLinearGradient gradient;
- gradient.setStart(0, gradientRect.top());
- gradient.setFinalStop(0, gradientRect.bottom());
- // adjust rect to have small margins, so the frame
- // around the gradient is visible
- gradientRect.adjust(fhmargins, fvmargins, -fhmargins, -fvmargins);
- float lastPoint = 0;
- QColor lastColor = QColor{ 0, 0, 0 };
- std::vector<int> orderedIndices(points.size());
- for (int i = 0; i < points.size(); i++)
- orderedIndices.push_back(i);
- std::sort(orderedIndices.begin(), orderedIndices.end(),
- [this] (int l, int r) {
- return points[l].first < points[r].first;
- });
- // traverse gradient in order and interpolate in linear
- // RGB space to avoid interpolating in sRGB
- for (int i = 0; i < orderedIndices.size(); i++) {
- int index = orderedIndices[i];
- auto& [point, color] = points[index];
- int m = 17;
- if (i > 0) {
- for (int i = 0; i < m; i++) {
- float v = float(i) / m;
- gradient.setColorAt(lastPoint + (point - lastPoint) / m * i,
- lerp(lastColor, color, v));
- }
- }
- gradient.setColorAt(point, color);
- lastPoint = point;
- lastColor = color;
- }
- QBrush brush{ gradient };
- painter.fillRect(gradientRect, brush);
- int index = 0;
- for (auto& [point, color] : points) {
- QRect r = getHandleRect(index);
- /*QStyleOptionButton so;
- so.init(this);
- so.rect = r;
- if (dragging && selectedHandle == index)
- so.state |= QStyle::State_Sunken;
- else if (selectedHandle == index)
- so.state |= QStyle::State_HasFocus;
- else
- so.state &= ~QStyle::State_Sunken & ~QStyle::State_HasFocus;
- if (mouseOver == index)
- so.state |= QStyle::State_MouseOver;
- else
- so.state &= ~QStyle::State_MouseOver;
-
- so.palette.setColor(QPalette::ColorRole::Button, color);
- style()->drawControl(QStyle::ControlElement::CE_PushButton, &so, &painter, this);*/
- int hs = HandleState::HANDLE_NORMAL;
- if (dragging && selectedHandle == index)
- hs |= HANDLE_DOWN;
- if (mouseOver == index)
- hs |= HANDLE_MOUSEOVER;
- if (selectedHandle == index)
- hs |= HANDLE_SELECTED;
- paintHandle(painter, r, color, hs);
- index++;
- }
- /*for (auto&[point, color] : points) {
- QStyleOptionSlider qsos;
- qsos.rect = QRect{ 100, static_cast<int>(point * height() - 10), 20, 20 };
- qsos.orientation = Qt::Vertical;
- qsos.subControls = QStyle::SC_SliderHandle;
- if (selectedHandle == index) {
- qsos.state |= QStyle::State_Sunken;
- }
- else {
- qsos.state &= ~QStyle::State_Sunken;
- }
- style()->drawComplexControl(QStyle::CC_Slider, &qsos, &painter, this);
- index++;
- }*/
- /*
- QPen pen(Qt::red);
- pen.setWidth(10);
- painter.setPen(pen);
- painter.drawRect(30, 30, 50, 50);
- painter.fillRect(QRect(0, 0, width(), height()), QColor{ 100, 20, 20 });
- qDebug(std::to_string(width()).c_str());
- */
- }
- void GradientWidget::paintHandle(QPainter& painter, const QRectF& pos,
- QColor c, int handleState)
- {
- const float lineWidth = 2;
- QPainterPath qpp = createSlideHandle(pos.width() - lineWidth, pos.height() - lineWidth);
- qpp.translate(pos.x() + lineWidth / 2, pos.y() + lineWidth / 2);
- if (handleState & HANDLE_SELECTED) {
- QColor absLighter;
- absLighter.setHsvF(c.hueF(), c.saturationF(), c.valueF() > 0.3 ? c.valueF() : 0.3);
- painter.setPen(QPen(QBrush(absLighter.lighter(130)), lineWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
- } else
- painter.setPen(QPen(QBrush(c.darker(200)), lineWidth / 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
- painter.setRenderHint(QPainter::Antialiasing);
- QLinearGradient bevel{ 0, pos.top(), 0, pos.bottom() }; // top down linear gradient
- if (handleState & HANDLE_DOWN) {
- bevel.setColorAt(0, c.darker(120));
- bevel.setColorAt(1, c.lighter(120));
- }
- else if (handleState & HANDLE_MOUSEOVER) {
- bevel.setColorAt(0, c.lighter(130));
- bevel.setColorAt(1, c.darker(110));
- }
- else {
- bevel.setColorAt(0, c.lighter(120));
- bevel.setColorAt(1, c.darker(120));
- }
- painter.fillPath(qpp, QBrush(bevel));
- painter.drawPath(qpp);
- }
- void GradientWidget::mousePressEvent(QMouseEvent* e)
- {
- int handle = handleAtPos(e->pos());
- if (handle != -1) {
- selectedHandle = handle;
- dragging = true;
- selectOffsetY = e->y() - gradValToHandleY(
- points[handle].first);
- update();
- e->accept();
- }
- else {
- e->ignore();
- }
- }
- void GradientWidget::mouseReleaseEvent(QMouseEvent* e)
- {
- if (dragging) {
- dragging = false;
- update();
- e->accept();
- }
- else {
- e->ignore();
- }
- }
- void GradientWidget::mouseMoveEvent(QMouseEvent* e)
- {
- if (dragging) {
- float newVal = handleYToGradVal(e->y() - selectOffsetY);
- newVal = std::clamp(newVal, 0.0f, 1.0f);
- points[selectedHandle].first = newVal;
- update();
- e->accept();
- }
- else {
- int handle = handleAtPos(e->pos());
- bool needsUpdate = false;
- if (mouseOver != handle)
- needsUpdate = true;
- mouseOver = handle;
- e->accept();
- if (needsUpdate)
- update();
- }
- }
- void GradientWidget::mouseDoubleClickEvent(QMouseEvent* e)
- {
- QRect handleArea = getHandleArea();
- int handle = handleAtPos(e->pos());
- if (handle != -1) {
- QColor current = points.at(handle).second;
- QColor newColor = QColorDialog::getColor(current,
- this,
- tr("Pick Color"));
- if (newColor.isValid()) {
- points[handle].second = newColor;
- update();
- }
- e->accept();
- }
- else if (handleArea.contains(e->pos())) {
- float v = handleYToGradVal(e->pos().y());
- points.append({ v, colorAtY(e->pos().y()) });
- e->accept();
- update();
- }
- else {
- e->ignore();
- }
- }
- QSize GradientWidget::minimumSizeHint(void) const
- {
- int spacing = this->style()->pixelMetric(
- QStyle::PM_LayoutHorizontalSpacing);
- if (spacing == -1) {
- spacing = this->style()->layoutSpacing(
- QSizePolicy::Frame,
- QSizePolicy::PushButton,
- Qt::Horizontal);
- }
- return QSize{ int(handleWidth * 1.5 + spacing), handleHeight * 5 };
- }
- QSize GradientWidget::sizeHint(void) const
- {
- int spacing = this->style()->pixelMetric(
- QStyle::PM_LayoutHorizontalSpacing);
- if (spacing == -1) {
- spacing = this->style()->layoutSpacing(
- QSizePolicy::Frame,
- QSizePolicy::PushButton,
- Qt::Horizontal);
- }
- return QSize{ int(handleWidth * 1.1 + spacing), handleHeight };
- }
- QRect GradientWidget::getGradientRect(void) const
- {
- int left, top, right, bottom;
- getContentsMargins(&left, &top, &right, &bottom);
- int spacing = this->style()->pixelMetric(
- QStyle::PM_LayoutHorizontalSpacing);
- if (spacing == -1) {
- spacing = this->style()->layoutSpacing(
- QSizePolicy::Frame,
- QSizePolicy::PushButton,
- Qt::Horizontal);
- }
- top += handleHeight / 2;
- bottom += handleHeight / 2;
- return QRect{ left, top,
- width() - left - right - handleWidth - spacing,
- height() - bottom - top };
- }
- QRect GradientWidget::getHandleRect(int index) const
- {
- QRect handleArea = getHandleArea();
- float y = handleArea.top() + points.at(index).first * handleArea.height();
- return QRect {
- handleArea.x(), int(y - handleHeight / 2),
- handleWidth, handleHeight
- };
- }
- QRect GradientWidget::getHandleArea(void) const
- {
- int left, top, right, bottom;
- getContentsMargins(&left, &top, &right, &bottom);
- top += handleHeight / 2;
- bottom += handleHeight / 2;
- float y = top;
- float x = width() - handleWidth - right;
- return QRect {
- int(x), top, handleWidth, height() - top - bottom
- };
- }
- int GradientWidget::handleAtPos(QPoint pos) const
- {
- for (int i = points.size() - 1; i >= 0; i--) {
- QRect rect = getHandleRect(i);
- if (rect.contains(pos)) {
- return i;
- }
- }
- return -1;
- }
- float GradientWidget::handleYToGradVal(float y) const
- {
- QRect area = getHandleArea();
- return (y - area.top()) / area.height();
- }
- float GradientWidget::gradValToHandleY(float v) const
- {
- QRect area = getHandleArea();
- return area.top() + v * area.height();
- }
- QPainterPath GradientWidget::createSlideHandle(float w, float h)
- {
- const float rounding = 4;
- QPainterPath qpp;
- QPolygonF qpf;
- qpf << QPointF{ 0, 0.5 * h }
- << QPointF{ 0.3 * w, h };
- qpp.moveTo(0, 0.5 * h);
- qpp.lineTo(0.3 * w, h);
- qpp.arcTo(w - rounding, h - rounding, rounding, rounding, -90, 90);
- qpp.arcTo(w - rounding, 0, rounding, rounding, 0, 90);
- qpp.lineTo(0.3 * w, 0);
- qpp.lineTo(0, 0.5 * h);
- return qpp;
- }
|