123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390 |
- #include "GradientWidget.h"
- #include <QPaintEvent>
- #include <QPainter>
- #include <QStyleOptionSlider>
- #include <QColorDialog>
- #include <QStyle>
- #include <algorithm>
- #include <cmath>
- using alm::Gradient;
- GradientWidget::GradientWidget(QWidget* parent) :
- QWidget{ parent }
- {
- dragging = false;
- selectedHandle = -1;
- mouseOver = -1;
- maxValue = 1.0f;
- colorPicker = new QColorDialog(this);
- colorPicker->setOption(QColorDialog::NoButtons);
- connect(colorPicker, &QColorDialog::currentColorChanged, this, &GradientWidget::selectedColorChanged);
- setMouseTracking(true);
- }
- const Gradient& GradientWidget::getGradient(void) const
- {
- return gradient;
- }
- void GradientWidget::setGradient(Gradient gr)
- {
- gradient = std::move(gr);
- points = gradient.getPoints();
- maxValue = gradient.getMax();
- updateGradient();
- }
- void GradientWidget::updateGradient(void)
- {
- gradient = Gradient{ points, maxValue };
- update();
- emit gradientChanged();
- }
- QColor GradientWidget::colorAtY(float y)
- {
- float v = handleYToGradVal(y);
- return fromRGB(gradient.get(v));
- }
- 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 linGrad;
- linGrad.setStart(0, gradientRect.top());
- linGrad.setFinalStop(0, gradientRect.bottom());
- for (auto it = points.cbegin(); it != points.cend(); it++) {
- float at = it->second;
- RGBColor col = it->first;
- linGrad.setColorAt(at / maxValue, QColor{ col.r, col.g, col.b });
- /*if (it + 1 != points.cend()) {
- float nat = (it + 1)->second;
- for (int i = 0; i < 5; i++) {
- float iat = at + i * (nat - at) / 5.0;
- linGrad.setColorAt(iat / maxValue, fromRGB(gradient.get(iat)));
- }
- }*/
- }
- // adjust rect to have small margins, so the frame
- // around the gradient is visible
- gradientRect.adjust(fhmargins, fvmargins, -fhmargins, -fvmargins);
- QBrush brush{ linGrad };
- painter.fillRect(gradientRect, brush);
- int index = 0;
- for (auto& [color, point] : points) {
- QRect r = getHandleRect(index);
- 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, fromRGB(color), hs);
- index++;
- }
- }
- 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].second);
- 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, maxValue);
- points[selectedHandle].second = newVal;
- updateGradient();
- emit gradientChanged();
- 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)
- {
- auto torgb = [](const QColor& c) {
- return RGBColor{
- uint8_t(c.red()), uint8_t(c.green()), uint8_t(c.blue())
- };
- };
- QRect handleArea = getHandleArea();
- int handle = handleAtPos(e->pos());
- if (handle != -1) {
- RGBColor current = points.at(handle).first;
- /*QColor newColor = QColorDialog::getColor(current,
- this,
- tr("Pick Color"));*/
- selectedHandle = handle;
- colorPicker->setCurrentColor(fromRGB(current));
- colorPicker->exec();
- /*if (newColor.isValid()) {
- points[handle].first = torgb(newColor);
- update();
- emit gradientChanged();
- }*/
- e->accept();
- }
- else if (handleArea.contains(e->pos())) {
- float v = handleYToGradVal(e->pos().y());
- points.emplace_back(torgb(colorAtY(e->pos().y())), v);
- e->accept();
- updateGradient();
- }
- 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);
- }
- int spacingV = this->style()->pixelMetric(
- QStyle::PM_LayoutVerticalSpacing);
- return QSize{ int(handleWidth * 1.1 + spacing),
- 2 * spacingV + 3 * handleHeight };
- }
- void GradientWidget::selectedColorChanged(const QColor& newColor)
- {
- if (points.size() > selectedHandle) {
- points.at(selectedHandle).first = RGBColor {
- uint8_t(newColor.red()),
- uint8_t(newColor.green()),
- uint8_t(newColor.blue())
- };
- updateGradient();
- emit gradientChanged();
- }
- }
- void GradientWidget::removeSelectedHandle(void)
- {
- if (selectedHandle >= 0 && selectedHandle < points.size()) {
- points.erase(points.begin() + selectedHandle);
- selectedHandle = -1;
- updateGradient();
- emit gradientChanged();
- }
- }
- QRect GradientWidget::getGradientRect(void) const
- {
- QMargins cm = contentsMargins();
- int top = cm.top();
- int bottom = cm.bottom();
- int left = cm.left();
- int right = cm.right();
- 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).second / maxValue * handleArea.height();
- return QRect {
- handleArea.x(), int(y - handleHeight / 2),
- handleWidth, handleHeight
- };
- }
- QRect GradientWidget::getHandleArea(void) const
- {
- QMargins cm = contentsMargins();
- int top = cm.top();
- int bottom = cm.bottom();
- int left = cm.left();
- int right = cm.right();
- 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 maxValue * (y - area.top()) / area.height();
- }
- float GradientWidget::gradValToHandleY(float v) const
- {
- QRect area = getHandleArea();
- return area.top() + v / maxValue * 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;
- }
|