From c7824d080e0b9f2701bea025fc91a71a1562b38d Mon Sep 17 00:00:00 2001 From: MrStevns Date: Sat, 14 Mar 2026 15:17:02 +0100 Subject: [PATCH 1/9] ColorInspector: Fix undefined color when hue is 0 --- app/src/colorinspector.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/colorinspector.cpp b/app/src/colorinspector.cpp index 6b8c72cdfe..8687637665 100644 --- a/app/src/colorinspector.cpp +++ b/app/src/colorinspector.cpp @@ -200,7 +200,9 @@ void ColorInspector::onColorSpecChanged() } else { - mCurrentColor = mCurrentColor.toHsv(); + if (mCurrentColor.hue() != -1) { + mCurrentColor = mCurrentColor.toHsv(); + } } updateControls(); From 64f2503c68a927dd6b0e8977ac3e0f1cfc81291d Mon Sep 17 00:00:00 2001 From: MrStevns Date: Sat, 14 Mar 2026 15:21:21 +0100 Subject: [PATCH 2/9] ColorSlider: Update appearance - Slider is now bigger - Picker stays within the slider boundary - Slider now scales according to HDPI - Picker is now multi colored, rather than switching color - The middle of the picker now corresponds to the cursor position, rather than shifting closer to the nearest edge. --- app/src/colorslider.cpp | 205 ++++++++++++++------------ app/src/colorslider.h | 26 ++-- core_lib/core_lib.pro | 2 + core_lib/src/util/drawsliderstyle.cpp | 88 +++++++++++ core_lib/src/util/drawsliderstyle.h | 32 ++++ 5 files changed, 245 insertions(+), 108 deletions(-) create mode 100644 core_lib/src/util/drawsliderstyle.cpp create mode 100644 core_lib/src/util/drawsliderstyle.h diff --git a/app/src/colorslider.cpp b/app/src/colorslider.cpp index 1f67e05853..43d8dd5748 100644 --- a/app/src/colorslider.cpp +++ b/app/src/colorslider.cpp @@ -21,7 +21,6 @@ GNU General Public License for more details. #include #include - ColorSlider::ColorSlider(QWidget* parent) : QWidget(parent) { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); @@ -43,7 +42,13 @@ void ColorSlider::init(ColorSpecType specType, ColorType type, const QColor &col update(); } -void ColorSlider::paintEvent(QPaintEvent *) +void ColorSlider::setupPicker() +{ + QRectF sliderRect = calculatedContentsRect(contentsRect(), this->devicePixelRatioF(), mSliderStyle.borderWidth); + mPickerSize = QSizeF(10, sliderRect.bottom() - sliderRect.top() - mSliderStyle.borderWidth); +} + +void ColorSlider::paintEvent(QPaintEvent*) { drawColorBox(mColor, size()); @@ -73,36 +78,36 @@ QLinearGradient ColorSlider::rgbGradient(const QColor &color) switch (mColorType) { case RED: - for (; val < mMax; val += 1) + for (; val <= mMax; val += 1) { - mGradient.setColorAt(val / mMax, QColor::fromRgb(val, + mGradient.setColorAt(static_cast(val) / mMax, QColor::fromRgb(val, 255, 255, color.alpha())); } break; case GREEN: - for (; val < mMax; val += 1) + for (; val <= mMax; val += 1) { - mGradient.setColorAt(val / mMax, QColor::fromRgb(color.red(), + mGradient.setColorAt(static_cast(val) / mMax, QColor::fromRgb(color.red(), val, color.blue(), color.alpha())); } break; case BLUE: - for (; val < mMax; val += 1) + for (; val <= mMax; val += 1) { - mGradient.setColorAt(val / mMax, QColor::fromRgb(color.red(), + mGradient.setColorAt(static_cast(val) / mMax, QColor::fromRgb(color.red(), color.green(), val, color.alpha())); } break; case ALPHA: - for (; val < mMax; val += 1) + for (; val <= mMax; val += 1) { - mGradient.setColorAt(val / mMax, QColor::fromRgb(0, + mGradient.setColorAt(static_cast(val) / mMax, QColor::fromRgb(0, 0, 0, val)); @@ -120,36 +125,36 @@ QLinearGradient ColorSlider::hsvGradient(const QColor &color) switch (mColorType) { case HUE: - for (; val < mMax; val += 1) + for (; val <= mMax; val += 1) { - mGradient.setColorAt(val / mMax, QColor::fromHsv(val, + mGradient.setColorAt(static_cast(val) / mMax, QColor::fromHsv(val, 255, 255, color.alpha())); } break; case SAT: - for (; val < mMax; val += 1) + for (; val <= mMax; val += 1) { - mGradient.setColorAt(val / mMax, QColor::fromHsv(color.hsvHue(), + mGradient.setColorAt(static_cast(val) / mMax, QColor::fromHsv(color.hsvHue(), val, color.value(), color.alpha())); } break; case VAL: - for (; val < mMax; val += 1) + for (; val <= mMax; val += 1) { - mGradient.setColorAt(val / mMax, QColor::fromHsv(color.hsvHue(), + mGradient.setColorAt(static_cast(val) / mMax, QColor::fromHsv(color.hsvHue(), color.hsvSaturation(), val, color.alpha())); } break; case ALPHA: - for (; val < mMax; val += 1) + for (; val <= mMax; val += 1) { - mGradient.setColorAt(val / mMax, QColor::fromHsv(0, + mGradient.setColorAt(static_cast(val) / mMax, QColor::fromHsv(0, 0, 0, val)); @@ -161,65 +166,60 @@ QLinearGradient ColorSlider::hsvGradient(const QColor &color) return mGradient; } -void ColorSlider::drawColorBox(const QColor &color, QSize size) +void ColorSlider::setRgb(const QColor &rgb) { - QStyleOption option; - option.initFrom(this); + mColor.setRgb(rgb.red(), + rgb.green(), + rgb.blue(), + rgb.alpha()); - QBrush backgroundBrush = option.palette.window(); + mPixmapCacheInvalid = true; +} - mBoxPixmapSource = QPixmap(size); +void ColorSlider::setHsv(const QColor &hsv) +{ + mColor.setHsv(hsv.hsvHue(), + hsv.hsvSaturation(), + hsv.value(), + hsv.alpha()); - QPainter painter(&mBoxPixmapSource); - painter.setRenderHint(QPainter::Antialiasing); + mPixmapCacheInvalid = true; +} - painter.fillRect(mBoxPixmapSource.rect(), backgroundBrush); +void ColorSlider::resizeEvent(QResizeEvent*) +{ + mPixmapCacheInvalid = true; + update(); +} - mGradient = QLinearGradient(0,0,mBoxPixmapSource.width(),0); - mGradient = setColorSpec(color); +void ColorSlider::drawColorBox(const QColor &color, QSize size) +{ + QStyleOption option; + option.initFrom(this); - painter.end(); + QBrush backgroundBrush = option.palette.window(); - // draw checkerboard background - painter.begin(&mBoxPixmapSource); - QBrush brush2(QBrush(QPixmap(":icons/general/checkerboard_smaller.png"))); - - painter.setBrush(brush2); - QPen pen2; - pen2.setWidthF(0); - pen2.setColor(Qt::gray); - pen2.setCosmetic(false); - painter.setPen(pen2); - painter.drawRoundedRect(0, - 0, - mBoxPixmapSource.width(), - mBoxPixmapSource.height(), - 4, - mBoxPixmapSource.width(), - Qt::SizeMode::AbsoluteSize); + if (mPixmapCacheInvalid) { + setupPicker(); - painter.end(); + QRectF sliderRect = calculatedContentsRect(contentsRect(), devicePixelRatioF(), mSliderStyle.borderWidth); + mBoxPixmapSource = QPixmap(size * devicePixelRatio()); + mBoxPixmapSource.setDevicePixelRatio(devicePixelRatioF()); + mBoxPixmapSource.fill(Qt::transparent); + mPixmapCacheInvalid = false; - painter.begin(&mBoxPixmapSource); - painter.setRenderHint(QPainter::Antialiasing); + mGradient = QLinearGradient(0,0,sliderRect.width(),0); + mGradient = setColorSpec(color); - QBrush brush(mGradient); - QPen pen; - pen.setWidthF(0); - pen.setColor(Qt::gray); - pen.setCosmetic(false); - painter.setPen(pen); + QPainter painter(&mBoxPixmapSource); - painter.setBrush(brush); + mSliderStyle.customFill = QBrush(mCheckerboardPixmap); + drawSliderStyle(painter, sliderRect, mSliderStyle, option.palette); - painter.drawRoundedRect(0, - 0, - mBoxPixmapSource.width(), - mBoxPixmapSource.height(), - 4, - mBoxPixmapSource.width(), - Qt::SizeMode::AbsoluteSize); - painter.end(); + QBrush brush(mGradient); + mSliderStyle.customFill = brush; + drawSliderStyle(painter, sliderRect, mSliderStyle, option.palette); + } } QSize ColorSlider::sizeHint() const @@ -241,11 +241,11 @@ void ColorSlider::drawPicker(const QColor &color) { QPainter painter(this); qreal val = 0; - QSize mPickerSize = QSize(10, this->height() - 1); - QPen pen; - pen.setWidth(0); - pen.setColor(QColor(0, 0, 0, 255)); + QRectF sliderRect = calculatedContentsRect(contentsRect(), devicePixelRatioF(), mSliderStyle.borderWidth); + + qreal padding = (mSliderStyle.borderWidth * 2); + qreal pickerDiff = sliderRect.width() - padding - mPickerSize.width(); switch (mSpecType) { @@ -253,21 +253,13 @@ void ColorSlider::drawPicker(const QColor &color) switch (mColorType) { case HUE: - val = color.hsvHueF() * (mBoxPixmapSource.width() - mPickerSize.width()); + val = color.hsvHueF(); break; case SAT: - if ((color.hsvSaturation() > 127 || color.value() < 127) && color.alpha() > 127) - { - pen.setColor(Qt::white); - } - val = color.hsvSaturationF() * (mBoxPixmapSource.width() - mPickerSize.width()); + val = color.hsvSaturationF(); break; case VAL: - if (color.value() < 127 && color.alpha() > 127) - { - pen.setColor(Qt::white); - } - val = color.valueF() * (mBoxPixmapSource.width() - mPickerSize.width()); + val = color.valueF(); break; case ALPHA: break; @@ -279,21 +271,13 @@ void ColorSlider::drawPicker(const QColor &color) switch (mColorType) { case RED: - val = color.redF() * (mBoxPixmapSource.width() - mPickerSize.width()); + val = color.redF(); break; case GREEN: - if (color.alpha() > 127) - { - pen.setColor(Qt::white); - } - val = color.greenF() * (mBoxPixmapSource.width() - mPickerSize.width()); + val = color.greenF(); break; case BLUE: - if (color.alpha() > 127) - { - pen.setColor(Qt::white); - } - val = color.blueF() * (mBoxPixmapSource.width() - mPickerSize.width()); + val = color.blueF(); break; case ALPHA: break; @@ -306,24 +290,46 @@ void ColorSlider::drawPicker(const QColor &color) } if (mColorType == ALPHA) { - if (color.alpha() > 127) - { - pen.setColor(Qt::white); - } - val = color.alphaF() * (mBoxPixmapSource.width() - mPickerSize.width()); + val = color.alphaF(); } + val = static_cast(sliderRect.left() + mSliderStyle.borderWidth + qMax(mMin, (val * pickerDiff))); + + QPen pen; + pen.setJoinStyle(Qt::MiterJoin); + pen.setWidthF(mSliderStyle.borderWidth); + pen.setColor(QColor(0, 0, 0, 255)); + + painter.setRenderHint(QPainter::Antialiasing); painter.setPen(pen); - painter.drawRect(static_cast(val), 0, mPickerSize.width(), mPickerSize.height()); - painter.end(); + + QRectF outerRect = QRectF(val, + sliderRect.top() + mSliderStyle.borderWidth, + mPickerSize.width(), + mPickerSize.height() - mSliderStyle.borderWidth); + painter.drawRoundedRect(outerRect, + mSliderStyle.cachedCornerRadiusX, + mSliderStyle.cachedCornerRadiusY, + Qt::AbsoluteSize); + + painter.setPen(palette().dark().color()); + painter.drawRoundedRect(outerRect.adjusted( + mSliderStyle.borderWidth, + mSliderStyle.borderWidth, + -mSliderStyle.borderWidth, + -mSliderStyle.borderWidth), + innerCornerRadius(mSliderStyle.cachedCornerRadiusX, mSliderStyle.borderWidth), + innerCornerRadius(mSliderStyle.cachedCornerRadiusY, mSliderStyle.borderWidth), + Qt::AbsoluteSize); } void ColorSlider::colorPicked(QPoint point) { + QRectF sliderRect = calculatedContentsRect(contentsRect(), devicePixelRatioF(), mSliderStyle.borderWidth); QColor colorPicked = mColor; int colorMax = static_cast(mMax); - int colorVal = point.x() * colorMax / mBoxPixmapSource.width(); + int colorVal = (point.x() - (mPickerSize.width() * 0.5)) * colorMax / (sliderRect.right() - (mPickerSize.width())); colorVal = (colorVal > colorMax) ? colorMax : colorVal; colorVal = (colorVal < 0) ? 0 : colorVal; @@ -415,6 +421,9 @@ void ColorSlider::colorPicked(QPoint point) default: Q_UNREACHABLE(); } + mColor = colorPicked; + mPixmapCacheInvalid = true; + emit valueChanged(mColor); } diff --git a/app/src/colorslider.h b/app/src/colorslider.h index 037130e8a3..fba4339a5d 100644 --- a/app/src/colorslider.h +++ b/app/src/colorslider.h @@ -19,6 +19,7 @@ GNU General Public License for more details. #include +#include "drawsliderstyle.h" class ColorSlider : public QWidget { @@ -50,17 +51,9 @@ class ColorSlider : public QWidget QColor color() { return mColor; } - void setHsv(const QColor& hsv) { mColor.setHsv(hsv.hsvHue(), - hsv.hsvSaturation(), - hsv.value(), - hsv.alpha()); - } + void setHsv(const QColor& hsv); - void setRgb(const QColor& rgb) { mColor.setRgb(rgb.red(), - rgb.green(), - rgb.blue(), - rgb.alpha()); - } + void setRgb(const QColor& rgb); void setColorSpecType(ColorSpecType newType) { this->mSpecType = newType; } void setColorType(ColorType newType) { this->mColorType = newType; } @@ -72,6 +65,7 @@ class ColorSlider : public QWidget protected: void paintEvent(QPaintEvent* event) override; + void resizeEvent(QResizeEvent *event) override; void mouseMoveEvent(QMouseEvent* event) override; void mousePressEvent(QMouseEvent* event) override; @@ -80,6 +74,7 @@ class ColorSlider : public QWidget private: + void setupPicker(); void drawColorBox(const QColor &color, QSize size); void drawPicker(const QColor &color); QLinearGradient hsvGradient(const QColor &color); @@ -87,16 +82,27 @@ class ColorSlider : public QWidget void colorPicked(QPoint point); + bool mPixmapCacheInvalid = true; QPixmap mBoxPixmapSource; QColor mColor; qreal mMin = 0.0; qreal mMax = 0.0; + QSizeF mPickerSize = QSize(-1, -1); + ColorType mColorType = ColorType::HUE; ColorSpecType mSpecType = ColorSpecType::RGB; QLinearGradient mGradient; + + SliderPainterStyle mSliderStyle { + .hasCustomFill = true, + .customFill = QBrush(QPixmap(":icons/general/checkerboard_smaller.png")), + .strokeRole = QPalette::Dark, + }; + + QPixmap mCheckerboardPixmap = QPixmap(":icons/general/checkerboard_smaller.png"); }; #endif // COLORSLIDER_H diff --git a/core_lib/core_lib.pro b/core_lib/core_lib.pro index 1968998c1b..e6c55eaa06 100644 --- a/core_lib/core_lib.pro +++ b/core_lib/core_lib.pro @@ -96,6 +96,7 @@ HEADERS += \ src/util/cameraeasingtype.h \ src/util/camerafieldoption.h \ src/util/colordictionary.h \ + src/util/drawsliderstyle.h \ src/util/fileformat.h \ src/util/filetype.h \ src/util/importimageconfig.h \ @@ -185,6 +186,7 @@ SOURCES += src/graphics/bitmap/bitmapimage.cpp \ src/tool/transformtool.cpp \ src/util/blitrect.cpp \ src/util/cameraeasingtype.cpp \ + src/util/drawsliderstyle.cpp \ src/util/fileformat.cpp \ src/util/pencilerror.cpp \ src/util/pencilsettings.cpp \ diff --git a/core_lib/src/util/drawsliderstyle.cpp b/core_lib/src/util/drawsliderstyle.cpp new file mode 100644 index 0000000000..7818a72cf9 --- /dev/null +++ b/core_lib/src/util/drawsliderstyle.cpp @@ -0,0 +1,88 @@ +#include "drawsliderstyle.h" + +QColor resolveColorRole(const QPalette& palette, QPalette::ColorRole role) +{ + if (role == QPalette::NoRole) { + return Qt::transparent; + } + return palette.color(role); +} + +QBrush resolveFill(const SliderPainterStyle& style, const QPalette& palette) +{ + if (style.hasCustomFill) { + return style.customFill; + } else { + return resolveColorRole(palette, style.fillRole); + } +} + +void drawSliderStyle(QPainter& painter, const QRectF& rect, SliderPainterStyle& style, const QPalette& palette) +{ + updateSliderStyleCache(style, rect.size()); + + painter.save(); + painter.setRenderHint(QPainter::Antialiasing); + + painter.setCompositionMode(QPainter::CompositionMode_SourceOver); + + QPen pen = resolveColorRole(palette, style.strokeRole); + pen.setWidth(style.borderWidth); + painter.setPen(pen); + + painter.setBrush(resolveFill(style, palette)); + painter.drawRoundedRect(rect, + style.cachedCornerRadiusX, + style.cachedCornerRadiusY, + Qt::SizeMode::AbsoluteSize); + + painter.restore(); +} + +void updateSliderStyleCache(SliderPainterStyle& style, const QSizeF& newSize) +{ + if (style.cachedSize == newSize) { + return; + } + + const qreal minRad = qMin(newSize.width(), newSize.height()); + const qreal maxRad = qMax(newSize.width(), newSize.height()); + + qreal radiusRatio = style.cornerRadiusRatio; + qreal absolutePercentage = maxRad * radiusRatio; + + if (minRad * radiusRatio < absolutePercentage) { + style.cachedCornerRadiusX = minRad * radiusRatio; + style.cachedCornerRadiusY = style.cachedCornerRadiusX; + } else { + style.cachedCornerRadiusX = absolutePercentage; + style.cachedCornerRadiusY = style.cachedCornerRadiusX; + } + style.cachedSize = newSize; +} + +QRectF subPixelAdjustRectF(const QRectF rect, qreal devicePixelRatio, qreal borderWidth) +{ + qreal topLeftRatio = devicePixelRatio > 1 ? 0.0 : 0.5; + qreal bottomRightRatio = devicePixelRatio > 1 ? 0.5 : 0.0; + + return QRectF(rect.left() + borderWidth + topLeftRatio, + rect.top() + borderWidth + topLeftRatio, + rect.right() - borderWidth - bottomRightRatio, + rect.bottom() - borderWidth - bottomRightRatio + ); +} + +QRectF calculatedContentsRect(const QRectF& contentRect, qreal devicePixelRatio, qreal borderWidth) +{ + // For non high DPI scaling, + // we have to move the coordinate 0.5 pixel to account for anti-aliasing + // Otherwise certain lines will look blurry + + return subPixelAdjustRectF(contentRect, devicePixelRatio, borderWidth); +} + +qreal innerCornerRadius(qreal outerRadius, qreal borderWidth) +{ + return outerRadius - borderWidth; +} diff --git a/core_lib/src/util/drawsliderstyle.h b/core_lib/src/util/drawsliderstyle.h new file mode 100644 index 0000000000..eaa6b913a3 --- /dev/null +++ b/core_lib/src/util/drawsliderstyle.h @@ -0,0 +1,32 @@ +#ifndef DRAWSLIDERSTYLE_H +#define DRAWSLIDERSTYLE_H + +#include +#include + +struct SliderPainterStyle { + + // The filled part of the slider + QPalette::ColorRole fillRole = QPalette::Window; + + // The border of the slider, by default there is none + QPalette::ColorRole strokeRole = QPalette::NoRole; + + bool hasCustomFill = false; + QBrush customFill = QBrush(); + + float borderWidth = 1.0f; + float cornerRadiusRatio = 0.2; + + QSizeF cachedSize = {}; + + float cachedCornerRadiusX = 0; + float cachedCornerRadiusY = 0; +}; + +void drawSliderStyle(QPainter& painter, const QRectF& rect, SliderPainterStyle& style, const QPalette& palette); +void updateSliderStyleCache(SliderPainterStyle& style, const QSizeF& newSize); +QRectF calculatedContentsRect(const QRectF& contentRect, qreal devicePixelRatio, qreal borderWidth); +qreal innerCornerRadius(qreal outerRadius, qreal borderWidth); + +#endif // DRAWSLIDERSTYLE_H From 8bb310a3e00ccfeed933d4df545d7adc5a174316 Mon Sep 17 00:00:00 2001 From: MrStevns Date: Sat, 14 Mar 2026 15:40:56 +0100 Subject: [PATCH 3/9] ColorSlider: Use namespace to make usecase clearer --- app/src/colorslider.cpp | 16 +++++----- app/src/colorslider.h | 1 + core_lib/core_lib.pro | 2 ++ core_lib/src/util/drawsliderstyle.cpp | 46 +++++++++++---------------- core_lib/src/util/drawsliderstyle.h | 24 +++++++++++--- core_lib/src/util/slidergeometry.cpp | 43 +++++++++++++++++++++++++ core_lib/src/util/slidergeometry.h | 28 ++++++++++++++++ 7 files changed, 120 insertions(+), 40 deletions(-) create mode 100644 core_lib/src/util/slidergeometry.cpp create mode 100644 core_lib/src/util/slidergeometry.h diff --git a/app/src/colorslider.cpp b/app/src/colorslider.cpp index 43d8dd5748..be64edee4f 100644 --- a/app/src/colorslider.cpp +++ b/app/src/colorslider.cpp @@ -44,7 +44,7 @@ void ColorSlider::init(ColorSpecType specType, ColorType type, const QColor &col void ColorSlider::setupPicker() { - QRectF sliderRect = calculatedContentsRect(contentsRect(), this->devicePixelRatioF(), mSliderStyle.borderWidth); + QRectF sliderRect = SliderGeometry::contentsRect(contentsRect(), this->devicePixelRatioF(), mSliderStyle.borderWidth); mPickerSize = QSizeF(10, sliderRect.bottom() - sliderRect.top() - mSliderStyle.borderWidth); } @@ -202,7 +202,7 @@ void ColorSlider::drawColorBox(const QColor &color, QSize size) if (mPixmapCacheInvalid) { setupPicker(); - QRectF sliderRect = calculatedContentsRect(contentsRect(), devicePixelRatioF(), mSliderStyle.borderWidth); + QRectF sliderRect = SliderGeometry::contentsRect(contentsRect(), devicePixelRatioF(), mSliderStyle.borderWidth); mBoxPixmapSource = QPixmap(size * devicePixelRatio()); mBoxPixmapSource.setDevicePixelRatio(devicePixelRatioF()); mBoxPixmapSource.fill(Qt::transparent); @@ -214,11 +214,11 @@ void ColorSlider::drawColorBox(const QColor &color, QSize size) QPainter painter(&mBoxPixmapSource); mSliderStyle.customFill = QBrush(mCheckerboardPixmap); - drawSliderStyle(painter, sliderRect, mSliderStyle, option.palette); + SliderPainter::drawSliderStyle(painter, sliderRect, mSliderStyle, option.palette); QBrush brush(mGradient); mSliderStyle.customFill = brush; - drawSliderStyle(painter, sliderRect, mSliderStyle, option.palette); + SliderPainter::drawSliderStyle(painter, sliderRect, mSliderStyle, option.palette); } } @@ -242,7 +242,7 @@ void ColorSlider::drawPicker(const QColor &color) QPainter painter(this); qreal val = 0; - QRectF sliderRect = calculatedContentsRect(contentsRect(), devicePixelRatioF(), mSliderStyle.borderWidth); + QRectF sliderRect = SliderGeometry::contentsRect(contentsRect(), devicePixelRatioF(), mSliderStyle.borderWidth); qreal padding = (mSliderStyle.borderWidth * 2); qreal pickerDiff = sliderRect.width() - padding - mPickerSize.width(); @@ -318,14 +318,14 @@ void ColorSlider::drawPicker(const QColor &color) mSliderStyle.borderWidth, -mSliderStyle.borderWidth, -mSliderStyle.borderWidth), - innerCornerRadius(mSliderStyle.cachedCornerRadiusX, mSliderStyle.borderWidth), - innerCornerRadius(mSliderStyle.cachedCornerRadiusY, mSliderStyle.borderWidth), + SliderGeometry::innerCornerRadius(mSliderStyle.cachedCornerRadiusX, mSliderStyle.borderWidth), + SliderGeometry::innerCornerRadius(mSliderStyle.cachedCornerRadiusY, mSliderStyle.borderWidth), Qt::AbsoluteSize); } void ColorSlider::colorPicked(QPoint point) { - QRectF sliderRect = calculatedContentsRect(contentsRect(), devicePixelRatioF(), mSliderStyle.borderWidth); + QRectF sliderRect = SliderGeometry::contentsRect(contentsRect(), devicePixelRatioF(), mSliderStyle.borderWidth); QColor colorPicked = mColor; int colorMax = static_cast(mMax); diff --git a/app/src/colorslider.h b/app/src/colorslider.h index fba4339a5d..e9b6035cac 100644 --- a/app/src/colorslider.h +++ b/app/src/colorslider.h @@ -20,6 +20,7 @@ GNU General Public License for more details. #include #include "drawsliderstyle.h" +#include "slidergeometry.h" class ColorSlider : public QWidget { diff --git a/core_lib/core_lib.pro b/core_lib/core_lib.pro index e6c55eaa06..863ce3e3af 100644 --- a/core_lib/core_lib.pro +++ b/core_lib/core_lib.pro @@ -108,6 +108,7 @@ HEADERS += \ src/util/pencilerror.h \ src/util/pencilsettings.h \ src/util/preferencesdef.h \ + src/util/slidergeometry.h \ src/util/transform.h \ src/util/util.h \ src/util/log.h \ @@ -191,6 +192,7 @@ SOURCES += src/graphics/bitmap/bitmapimage.cpp \ src/util/pencilerror.cpp \ src/util/pencilsettings.cpp \ src/util/log.cpp \ + src/util/slidergeometry.cpp \ src/util/transform.cpp \ src/util/util.cpp \ src/util/pointerevent.cpp \ diff --git a/core_lib/src/util/drawsliderstyle.cpp b/core_lib/src/util/drawsliderstyle.cpp index 7818a72cf9..194b6c48ec 100644 --- a/core_lib/src/util/drawsliderstyle.cpp +++ b/core_lib/src/util/drawsliderstyle.cpp @@ -1,3 +1,19 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2012-2020 Matthew Chiawen Chang + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ #include "drawsliderstyle.h" QColor resolveColorRole(const QPalette& palette, QPalette::ColorRole role) @@ -17,7 +33,7 @@ QBrush resolveFill(const SliderPainterStyle& style, const QPalette& palette) } } -void drawSliderStyle(QPainter& painter, const QRectF& rect, SliderPainterStyle& style, const QPalette& palette) +void SliderPainter::drawSliderStyle(QPainter& painter, const QRectF& rect, SliderPainterStyle& style, const QPalette& palette) { updateSliderStyleCache(style, rect.size()); @@ -39,7 +55,7 @@ void drawSliderStyle(QPainter& painter, const QRectF& rect, SliderPainterStyle& painter.restore(); } -void updateSliderStyleCache(SliderPainterStyle& style, const QSizeF& newSize) +void SliderPainter::updateSliderStyleCache(SliderPainterStyle& style, const QSizeF& newSize) { if (style.cachedSize == newSize) { return; @@ -60,29 +76,3 @@ void updateSliderStyleCache(SliderPainterStyle& style, const QSizeF& newSize) } style.cachedSize = newSize; } - -QRectF subPixelAdjustRectF(const QRectF rect, qreal devicePixelRatio, qreal borderWidth) -{ - qreal topLeftRatio = devicePixelRatio > 1 ? 0.0 : 0.5; - qreal bottomRightRatio = devicePixelRatio > 1 ? 0.5 : 0.0; - - return QRectF(rect.left() + borderWidth + topLeftRatio, - rect.top() + borderWidth + topLeftRatio, - rect.right() - borderWidth - bottomRightRatio, - rect.bottom() - borderWidth - bottomRightRatio - ); -} - -QRectF calculatedContentsRect(const QRectF& contentRect, qreal devicePixelRatio, qreal borderWidth) -{ - // For non high DPI scaling, - // we have to move the coordinate 0.5 pixel to account for anti-aliasing - // Otherwise certain lines will look blurry - - return subPixelAdjustRectF(contentRect, devicePixelRatio, borderWidth); -} - -qreal innerCornerRadius(qreal outerRadius, qreal borderWidth) -{ - return outerRadius - borderWidth; -} diff --git a/core_lib/src/util/drawsliderstyle.h b/core_lib/src/util/drawsliderstyle.h index eaa6b913a3..186c82307d 100644 --- a/core_lib/src/util/drawsliderstyle.h +++ b/core_lib/src/util/drawsliderstyle.h @@ -1,3 +1,19 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2012-2020 Matthew Chiawen Chang + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ #ifndef DRAWSLIDERSTYLE_H #define DRAWSLIDERSTYLE_H @@ -24,9 +40,9 @@ struct SliderPainterStyle { float cachedCornerRadiusY = 0; }; -void drawSliderStyle(QPainter& painter, const QRectF& rect, SliderPainterStyle& style, const QPalette& palette); -void updateSliderStyleCache(SliderPainterStyle& style, const QSizeF& newSize); -QRectF calculatedContentsRect(const QRectF& contentRect, qreal devicePixelRatio, qreal borderWidth); -qreal innerCornerRadius(qreal outerRadius, qreal borderWidth); +namespace SliderPainter { + void drawSliderStyle(QPainter& painter, const QRectF& rect, SliderPainterStyle& style, const QPalette& palette); + void updateSliderStyleCache(SliderPainterStyle& style, const QSizeF& newSize); +} #endif // DRAWSLIDERSTYLE_H diff --git a/core_lib/src/util/slidergeometry.cpp b/core_lib/src/util/slidergeometry.cpp new file mode 100644 index 0000000000..f6a0b5a94a --- /dev/null +++ b/core_lib/src/util/slidergeometry.cpp @@ -0,0 +1,43 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2012-2020 Matthew Chiawen Chang + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ +#include "slidergeometry.h" + +QRectF SliderGeometry::contentsRect(const QRectF& contentRect, qreal devicePixelRatio, qreal borderWidth) +{ + // For non high DPI scaling, + // we have to move the coordinate 0.5 pixel to account for anti-aliasing + // Otherwise certain lines will look blurry + + return SliderGeometry::subPixelAdjustedRectF(contentRect, devicePixelRatio, borderWidth); +} + +qreal SliderGeometry::innerCornerRadius(qreal outerRadius, qreal borderWidth) +{ + return outerRadius - borderWidth; +} + +QRectF SliderGeometry::subPixelAdjustedRectF(const QRectF& rect, qreal devicePixelRatio, qreal borderWidth) +{ + qreal topLeftRatio = devicePixelRatio > 1 ? 0.0 : 0.5; + qreal bottomRightRatio = devicePixelRatio > 1 ? 0.5 : 0.0; + + return QRectF(rect.left() + borderWidth + topLeftRatio, + rect.top() + borderWidth + topLeftRatio, + rect.right() - borderWidth - bottomRightRatio, + rect.bottom() - borderWidth - bottomRightRatio + ); +} diff --git a/core_lib/src/util/slidergeometry.h b/core_lib/src/util/slidergeometry.h new file mode 100644 index 0000000000..579c33f4b2 --- /dev/null +++ b/core_lib/src/util/slidergeometry.h @@ -0,0 +1,28 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2012-2020 Matthew Chiawen Chang + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ +#ifndef SLIDERGEOMETRY_H +#define SLIDERGEOMETRY_H + +#include + +namespace SliderGeometry { + QRectF contentsRect(const QRectF& contentRect, qreal devicePixelRatio, qreal borderWidth); + QRectF subPixelAdjustedRectF(const QRectF& rect, qreal devicePixelRatio, qreal borderWidth); + qreal innerCornerRadius(qreal outerRadius, qreal borderWidth); +} + +#endif // SLIDERGEOMETRY_H From 125d7ce02014fa986d9024c75a13fe03479e0528 Mon Sep 17 00:00:00 2001 From: MrStevns Date: Sun, 15 Mar 2026 10:32:37 +0100 Subject: [PATCH 4/9] ColorSlider: Fix pixel alignment What I thought was caused by the device pixel ratio, ended up being me forgetting to compensate for the borderWidth. --- app/src/colorslider.cpp | 46 ++++++++++++++++------------ core_lib/src/util/slidergeometry.cpp | 23 ++++++-------- core_lib/src/util/slidergeometry.h | 4 +-- 3 files changed, 38 insertions(+), 35 deletions(-) diff --git a/app/src/colorslider.cpp b/app/src/colorslider.cpp index be64edee4f..e376d606d5 100644 --- a/app/src/colorslider.cpp +++ b/app/src/colorslider.cpp @@ -44,7 +44,7 @@ void ColorSlider::init(ColorSpecType specType, ColorType type, const QColor &col void ColorSlider::setupPicker() { - QRectF sliderRect = SliderGeometry::contentsRect(contentsRect(), this->devicePixelRatioF(), mSliderStyle.borderWidth); + QRectF sliderRect = SliderGeometry::contentsRect(contentsRect(), mSliderStyle.borderWidth); mPickerSize = QSizeF(10, sliderRect.bottom() - sliderRect.top() - mSliderStyle.borderWidth); } @@ -202,7 +202,7 @@ void ColorSlider::drawColorBox(const QColor &color, QSize size) if (mPixmapCacheInvalid) { setupPicker(); - QRectF sliderRect = SliderGeometry::contentsRect(contentsRect(), devicePixelRatioF(), mSliderStyle.borderWidth); + QRectF sliderRect = SliderGeometry::contentsRect(contentsRect(), mSliderStyle.borderWidth); mBoxPixmapSource = QPixmap(size * devicePixelRatio()); mBoxPixmapSource.setDevicePixelRatio(devicePixelRatioF()); mBoxPixmapSource.fill(Qt::transparent); @@ -242,10 +242,10 @@ void ColorSlider::drawPicker(const QColor &color) QPainter painter(this); qreal val = 0; - QRectF sliderRect = SliderGeometry::contentsRect(contentsRect(), devicePixelRatioF(), mSliderStyle.borderWidth); - - qreal padding = (mSliderStyle.borderWidth * 2); - qreal pickerDiff = sliderRect.width() - padding - mPickerSize.width(); + const qreal borderWidth = mSliderStyle.borderWidth; + const qreal inset = SliderGeometry::penStrokeInset(borderWidth); + const QRectF innerSliderRect = SliderGeometry::contentsRect(contentsRect(), borderWidth) + .adjusted(borderWidth, borderWidth, -borderWidth, -borderWidth); switch (mSpecType) { @@ -293,27 +293,35 @@ void ColorSlider::drawPicker(const QColor &color) val = color.alphaF(); } - val = static_cast(sliderRect.left() + mSliderStyle.borderWidth + qMax(mMin, (val * pickerDiff))); + const qreal pickerMaxXPos = innerSliderRect.width() - mPickerSize.width(); + val = static_cast(innerSliderRect.left() + qMax(mMin, (val * pickerMaxXPos))) + inset; - QPen pen; - pen.setJoinStyle(Qt::MiterJoin); - pen.setWidthF(mSliderStyle.borderWidth); - pen.setColor(QColor(0, 0, 0, 255)); + QPen ounterPen; + ounterPen.setJoinStyle(Qt::MiterJoin); + ounterPen.setWidthF(mSliderStyle.borderWidth); + ounterPen.setColor(QColor(0, 0, 0, 255)); painter.setRenderHint(QPainter::Antialiasing); - painter.setPen(pen); + painter.setPen(ounterPen); - QRectF outerRect = QRectF(val, - sliderRect.top() + mSliderStyle.borderWidth, + QRectF pickerOuterRect = QRectF(val, + innerSliderRect.top(), mPickerSize.width(), - mPickerSize.height() - mSliderStyle.borderWidth); - painter.drawRoundedRect(outerRect, + innerSliderRect.height()); + + painter.drawRoundedRect(pickerOuterRect, mSliderStyle.cachedCornerRadiusX, mSliderStyle.cachedCornerRadiusY, Qt::AbsoluteSize); - painter.setPen(palette().dark().color()); - painter.drawRoundedRect(outerRect.adjusted( + QPen innerPen; + innerPen.setJoinStyle(Qt::MiterJoin); + innerPen.setWidthF(mSliderStyle.borderWidth); + innerPen.setColor(palette().dark().color()); + painter.setPen(innerPen); + + // Draw inner picker + painter.drawRoundedRect(pickerOuterRect.adjusted( mSliderStyle.borderWidth, mSliderStyle.borderWidth, -mSliderStyle.borderWidth, @@ -325,7 +333,7 @@ void ColorSlider::drawPicker(const QColor &color) void ColorSlider::colorPicked(QPoint point) { - QRectF sliderRect = SliderGeometry::contentsRect(contentsRect(), devicePixelRatioF(), mSliderStyle.borderWidth); + QRectF sliderRect = SliderGeometry::contentsRect(contentsRect(), mSliderStyle.borderWidth); QColor colorPicked = mColor; int colorMax = static_cast(mMax); diff --git a/core_lib/src/util/slidergeometry.cpp b/core_lib/src/util/slidergeometry.cpp index f6a0b5a94a..4252de768a 100644 --- a/core_lib/src/util/slidergeometry.cpp +++ b/core_lib/src/util/slidergeometry.cpp @@ -16,13 +16,15 @@ GNU General Public License for more details. */ #include "slidergeometry.h" -QRectF SliderGeometry::contentsRect(const QRectF& contentRect, qreal devicePixelRatio, qreal borderWidth) +QRectF SliderGeometry::contentsRect(const QRectF& rect, qreal borderWidth) { - // For non high DPI scaling, - // we have to move the coordinate 0.5 pixel to account for anti-aliasing - // Otherwise certain lines will look blurry + qreal inset = SliderGeometry::penStrokeInset(borderWidth); - return SliderGeometry::subPixelAdjustedRectF(contentRect, devicePixelRatio, borderWidth); + return QRectF(rect.left() + inset, + rect.top() + inset, + rect.width() - borderWidth, + rect.height() - borderWidth + ); } qreal SliderGeometry::innerCornerRadius(qreal outerRadius, qreal borderWidth) @@ -30,14 +32,7 @@ qreal SliderGeometry::innerCornerRadius(qreal outerRadius, qreal borderWidth) return outerRadius - borderWidth; } -QRectF SliderGeometry::subPixelAdjustedRectF(const QRectF& rect, qreal devicePixelRatio, qreal borderWidth) +qreal SliderGeometry::penStrokeInset(qreal borderWidth) { - qreal topLeftRatio = devicePixelRatio > 1 ? 0.0 : 0.5; - qreal bottomRightRatio = devicePixelRatio > 1 ? 0.5 : 0.0; - - return QRectF(rect.left() + borderWidth + topLeftRatio, - rect.top() + borderWidth + topLeftRatio, - rect.right() - borderWidth - bottomRightRatio, - rect.bottom() - borderWidth - bottomRightRatio - ); + return borderWidth * 0.5; } diff --git a/core_lib/src/util/slidergeometry.h b/core_lib/src/util/slidergeometry.h index 579c33f4b2..bf3942e022 100644 --- a/core_lib/src/util/slidergeometry.h +++ b/core_lib/src/util/slidergeometry.h @@ -20,8 +20,8 @@ GNU General Public License for more details. #include namespace SliderGeometry { - QRectF contentsRect(const QRectF& contentRect, qreal devicePixelRatio, qreal borderWidth); - QRectF subPixelAdjustedRectF(const QRectF& rect, qreal devicePixelRatio, qreal borderWidth); + QRectF contentsRect(const QRectF& rect, qreal borderWidth); + qreal penStrokeInset(qreal borderWidth); qreal innerCornerRadius(qreal outerRadius, qreal borderWidth); } From 1c578fc629ddb1e61a205c0a2cd7b636d5d5b57e Mon Sep 17 00:00:00 2001 From: MrStevns Date: Sun, 15 Mar 2026 10:50:55 +0100 Subject: [PATCH 5/9] ColorInspector: adjust spacings and how the tabs expand --- app/ui/colorinspector.ui | 260 +++++++++++++++++++++++---------------- 1 file changed, 152 insertions(+), 108 deletions(-) diff --git a/app/ui/colorinspector.ui b/app/ui/colorinspector.ui index 2140a289c6..9d0a9d03df 100644 --- a/app/ui/colorinspector.ui +++ b/app/ui/colorinspector.ui @@ -8,7 +8,7 @@ 0 0 120 - 263 + 220 @@ -24,6 +24,9 @@ + + 3 + 3 @@ -45,7 +48,7 @@ - 0 + 1 @@ -64,36 +67,8 @@ 3 - - - - H - - - - - - - S - - - - - - - V - - - - - - - A - - - - - + + 0 @@ -102,13 +77,13 @@ - - - - - 0 - 0 - + + + + % + + + 100 @@ -122,8 +97,32 @@ - - + + + + V + + + + + + + % + + + 100 + + + + + + + H + + + + + 0 @@ -142,8 +141,8 @@ - - + + % @@ -152,26 +151,46 @@ - - - - % - - - 100 + + + + + 0 + 0 + - - - - % + + + + S - - 100 + + + + + + A + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::MinimumExpanding + + + + 0 + 0 + + + + @@ -191,22 +210,22 @@ 3 - - + + - A + B - - - - G + + + + 255 - - + + 0 @@ -215,20 +234,38 @@ - - - - - 0 - 0 - + + + + 255 - - + + - B + G + + + + + + + 255 + + + + + + + A + + + + + + + 255 @@ -242,8 +279,8 @@ - - + + 0 @@ -259,33 +296,31 @@ - - - - 255 + + + + + 0 + 0 + - - - - 255 + + + + Qt::Orientation::Vertical - - - - - - 255 + + QSizePolicy::Policy::MinimumExpanding - - - - - - 255 + + + 0 + 0 + - + @@ -297,10 +332,10 @@ true - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -316,15 +351,18 @@ 0 - - - true - - - QFrame::Box + + + + 0 + 0 + - - QFrame::Raised + + + 50 + 50 + @@ -340,6 +378,12 @@
colorslider.h
1 + + ColorPreviewWidget + QWidget +
colorpreviewwidget.h
+ 1 +
From 972a3567319a4df3cbb6ebdfd17bdcc59acffe2f Mon Sep 17 00:00:00 2001 From: MrStevns Date: Sun, 15 Mar 2026 10:58:36 +0100 Subject: [PATCH 6/9] ColorInspector: Fix color preview having no checkerboard background --- app/app.pro | 2 ++ app/src/colorinspector.cpp | 13 ++-------- app/src/colorpreviewwidget.cpp | 43 ++++++++++++++++++++++++++++++++++ app/src/colorpreviewwidget.h | 41 ++++++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 11 deletions(-) create mode 100644 app/src/colorpreviewwidget.cpp create mode 100644 app/src/colorpreviewwidget.h diff --git a/app/app.pro b/app/app.pro index e1cf3eb823..80ca129b61 100644 --- a/app/app.pro +++ b/app/app.pro @@ -79,6 +79,7 @@ HEADERS += \ src/basewidget.h \ src/appearance.h \ src/buttonappearancewatcher.h \ + src/colorpreviewwidget.h \ src/importlayersdialog.h \ src/importpositiondialog.h \ src/layeropacitydialog.h \ @@ -136,6 +137,7 @@ SOURCES += \ src/addtransparencytopaperdialog.cpp \ src/basewidget.cpp \ src/buttonappearancewatcher.cpp \ + src/colorpreviewwidget.cpp \ src/importlayersdialog.cpp \ src/importpositiondialog.cpp \ src/layeropacitydialog.cpp \ diff --git a/app/src/colorinspector.cpp b/app/src/colorinspector.cpp index 8687637665..b5aa87d72b 100644 --- a/app/src/colorinspector.cpp +++ b/app/src/colorinspector.cpp @@ -66,14 +66,6 @@ void ColorInspector::initUI() ui->valueSlider->init(ColorSlider::ColorSpecType::HSV, ColorSlider::ColorType::VAL, mCurrentColor, 0.0, 255.0); ui->hsvAlphaSlider->init(ColorSlider::ColorSpecType::HSV, ColorSlider::ColorType::ALPHA, mCurrentColor, 0.0, 255.0); - QPalette p1 = ui->colorWrapper->palette(); - p1.setBrush(QPalette::Window, QBrush(QImage(":/background/checkerboard.png"))); - ui->colorWrapper->setPalette(p1); - - QPalette p2 = ui->color->palette(); - p2.setColor(QPalette::Window, mCurrentColor); - ui->color->setPalette(p2); - connect(ui->colorSpecTabWidget, &QTabWidget::currentChanged, this, &ColorInspector::onColorSpecChanged); auto onColorChangedSlider = static_cast(&ColorInspector::onColorChanged); @@ -179,9 +171,8 @@ void ColorInspector::updateControls() ui->valueSpinBox->setValue(qRound(mCurrentColor.value() / 2.55)); ui->hsvAlphaSpinBox->setValue(qRound(mCurrentColor.alpha() / 2.55)); - QPalette p = ui->color->palette(); - p.setColor(QPalette::Window, mCurrentColor); - ui->color->setPalette(p); + + ui->colorPreview->setColor(mCurrentColor); update(); } diff --git a/app/src/colorpreviewwidget.cpp b/app/src/colorpreviewwidget.cpp new file mode 100644 index 0000000000..4a8ca6560a --- /dev/null +++ b/app/src/colorpreviewwidget.cpp @@ -0,0 +1,43 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2026 Oliver Stevns Larsen + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ +#include "colorpreviewwidget.h" + +#include +#include + +ColorPreviewWidget::ColorPreviewWidget(QWidget*) +{ + +} + +void ColorPreviewWidget::setColor(QColor &color) +{ + if (color == mColor) { return; } + + mColor = color; + update(); +} + +void ColorPreviewWidget::paintEvent(QPaintEvent* event) +{ + QPainter painter(this); + + painter.setPen(Qt::NoPen); + painter.setBrush(QBrush(mCheckerboard)); + painter.drawRect(event->rect()); + + painter.fillRect(event->rect(), mColor); +} diff --git a/app/src/colorpreviewwidget.h b/app/src/colorpreviewwidget.h new file mode 100644 index 0000000000..abb24c917c --- /dev/null +++ b/app/src/colorpreviewwidget.h @@ -0,0 +1,41 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2026 Oliver Stevns Larsen + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ +#ifndef COLORPREVIEWWIDGET_H +#define COLORPREVIEWWIDGET_H + +#include +#include + +class ColorPreviewWidget: public QWidget +{ + Q_OBJECT +public: + ColorPreviewWidget(QWidget* = nullptr); + + void setColor(QColor& color); + +protected: + void paintEvent(QPaintEvent* event) override; + +private: + + QPixmap mColorPreviewPixmap; + QPixmap mCheckerboard = QPixmap(":/background/checkerboard.png"); + + QColor mColor; +}; + +#endif // COLORPREVIEWWIDGET_H From b4e0956f7df8a96a1ff093814636ccbc18f8ac87 Mon Sep 17 00:00:00 2001 From: MrStevns Date: Sun, 15 Mar 2026 12:22:29 +0100 Subject: [PATCH 7/9] ColorSlider: Fix slider quantization issue Fix quantization issue where the hue slider skipped one value at the end. Because we already specify the color type, we don't need to specify the min and max from outside, so that has been removed as well --- app/src/colorinspector.cpp | 18 +++--- app/src/colorslider.cpp | 82 ++++++++++++++++++---------- app/src/colorslider.h | 11 ++-- core_lib/src/util/slidergeometry.cpp | 5 ++ core_lib/src/util/slidergeometry.h | 1 + 5 files changed, 73 insertions(+), 44 deletions(-) diff --git a/app/src/colorinspector.cpp b/app/src/colorinspector.cpp index b5aa87d72b..37cb34caf5 100644 --- a/app/src/colorinspector.cpp +++ b/app/src/colorinspector.cpp @@ -56,15 +56,15 @@ void ColorInspector::initUI() } onColorSpecChanged(); - ui->redSlider->init(ColorSlider::ColorSpecType::RGB, ColorSlider::ColorType::RED, mCurrentColor, 0.0, 255.0); - ui->greenSlider->init(ColorSlider::ColorSpecType::RGB, ColorSlider::ColorType::GREEN, mCurrentColor, 0.0, 255.0); - ui->blueSlider->init(ColorSlider::ColorSpecType::RGB, ColorSlider::ColorType::BLUE, mCurrentColor, 0.0, 255.0); - ui->rgbAlphaSlider->init(ColorSlider::ColorSpecType::RGB, ColorSlider::ColorType::ALPHA, mCurrentColor, 0.0, 255.0); - - ui->hueSlider->init(ColorSlider::ColorSpecType::HSV, ColorSlider::ColorType::HUE, mCurrentColor, 0.0, 359.0); - ui->saturationSlider->init(ColorSlider::ColorSpecType::HSV, ColorSlider::ColorType::SAT, mCurrentColor, 0.0, 255.0); - ui->valueSlider->init(ColorSlider::ColorSpecType::HSV, ColorSlider::ColorType::VAL, mCurrentColor, 0.0, 255.0); - ui->hsvAlphaSlider->init(ColorSlider::ColorSpecType::HSV, ColorSlider::ColorType::ALPHA, mCurrentColor, 0.0, 255.0); + ui->redSlider->init(ColorSlider::ColorSpecType::RGB, ColorSlider::ColorType::RED, mCurrentColor); + ui->greenSlider->init(ColorSlider::ColorSpecType::RGB, ColorSlider::ColorType::GREEN, mCurrentColor); + ui->blueSlider->init(ColorSlider::ColorSpecType::RGB, ColorSlider::ColorType::BLUE, mCurrentColor); + ui->rgbAlphaSlider->init(ColorSlider::ColorSpecType::RGB, ColorSlider::ColorType::ALPHA, mCurrentColor); + + ui->hueSlider->init(ColorSlider::ColorSpecType::HSV, ColorSlider::ColorType::HUE, mCurrentColor); + ui->saturationSlider->init(ColorSlider::ColorSpecType::HSV, ColorSlider::ColorType::SAT, mCurrentColor); + ui->valueSlider->init(ColorSlider::ColorSpecType::HSV, ColorSlider::ColorType::VAL, mCurrentColor); + ui->hsvAlphaSlider->init(ColorSlider::ColorSpecType::HSV, ColorSlider::ColorType::ALPHA, mCurrentColor); connect(ui->colorSpecTabWidget, &QTabWidget::currentChanged, this, &ColorInspector::onColorSpecChanged); diff --git a/app/src/colorslider.cpp b/app/src/colorslider.cpp index e376d606d5..3e6975775b 100644 --- a/app/src/colorslider.cpp +++ b/app/src/colorslider.cpp @@ -31,10 +31,8 @@ ColorSlider::~ColorSlider() } -void ColorSlider::init(ColorSpecType specType, ColorType type, const QColor &color, qreal min, qreal max) +void ColorSlider::init(ColorSpecType specType, ColorType type, const QColor &color) { - mMin = min; - mMax = max; mColor = color; mColorType = type; mSpecType = specType; @@ -48,9 +46,9 @@ void ColorSlider::setupPicker() mPickerSize = QSizeF(10, sliderRect.bottom() - sliderRect.top() - mSliderStyle.borderWidth); } -void ColorSlider::paintEvent(QPaintEvent*) +void ColorSlider::paintEvent(QPaintEvent* event) { - drawColorBox(mColor, size()); + drawColorBox(mColor, event->rect().size()); QPainter painter(this); painter.drawPixmap(0, 0, mBoxPixmapSource); @@ -59,6 +57,26 @@ void ColorSlider::paintEvent(QPaintEvent*) drawPicker(mColor); } +int ColorSlider::colorTypeMax() const +{ + switch (mColorType) + { + case HUE: return 359; + case RED: + case GREEN: + case BLUE: + case SAT: + case VAL: + case ALPHA: return 255; + default: Q_UNREACHABLE(); + } +} + +int ColorSlider::colorSteps() const +{ + return colorTypeMax() + 1; +} + QLinearGradient ColorSlider::setColorSpec(const QColor &color) { switch (mSpecType) @@ -75,39 +93,40 @@ QLinearGradient ColorSlider::setColorSpec(const QColor &color) QLinearGradient ColorSlider::rgbGradient(const QColor &color) { int val = 0; + int max = colorSteps(); switch (mColorType) { case RED: - for (; val <= mMax; val += 1) + for (; val < max; val += 1) { - mGradient.setColorAt(static_cast(val) / mMax, QColor::fromRgb(val, + mGradient.setColorAt(static_cast(val) / max, QColor::fromRgb(val, 255, 255, color.alpha())); } break; case GREEN: - for (; val <= mMax; val += 1) + for (; val < max; val += 1) { - mGradient.setColorAt(static_cast(val) / mMax, QColor::fromRgb(color.red(), + mGradient.setColorAt(static_cast(val) / max, QColor::fromRgb(color.red(), val, color.blue(), color.alpha())); } break; case BLUE: - for (; val <= mMax; val += 1) + for (; val < max; val += 1) { - mGradient.setColorAt(static_cast(val) / mMax, QColor::fromRgb(color.red(), + mGradient.setColorAt(static_cast(val) / max, QColor::fromRgb(color.red(), color.green(), val, color.alpha())); } break; case ALPHA: - for (; val <= mMax; val += 1) + for (; val < max; val += 1) { - mGradient.setColorAt(static_cast(val) / mMax, QColor::fromRgb(0, + mGradient.setColorAt(static_cast(val) / max, QColor::fromRgb(0, 0, 0, val)); @@ -122,39 +141,40 @@ QLinearGradient ColorSlider::rgbGradient(const QColor &color) QLinearGradient ColorSlider::hsvGradient(const QColor &color) { int val = 0; + int max = colorSteps(); switch (mColorType) { case HUE: - for (; val <= mMax; val += 1) + for (; val < max; val += 1) { - mGradient.setColorAt(static_cast(val) / mMax, QColor::fromHsv(val, + mGradient.setColorAt(static_cast(val) / max, QColor::fromHsv(val, 255, 255, color.alpha())); } break; case SAT: - for (; val <= mMax; val += 1) + for (; val < max; val += 1) { - mGradient.setColorAt(static_cast(val) / mMax, QColor::fromHsv(color.hsvHue(), + mGradient.setColorAt(static_cast(val) / max, QColor::fromHsv(color.hsvHue(), val, color.value(), color.alpha())); } break; case VAL: - for (; val <= mMax; val += 1) + for (; val < max; val += 1) { - mGradient.setColorAt(static_cast(val) / mMax, QColor::fromHsv(color.hsvHue(), + mGradient.setColorAt(static_cast(val) / max, QColor::fromHsv(color.hsvHue(), color.hsvSaturation(), val, color.alpha())); } break; case ALPHA: - for (; val <= mMax; val += 1) + for (; val < max; val += 1) { - mGradient.setColorAt(static_cast(val) / mMax, QColor::fromHsv(0, + mGradient.setColorAt(static_cast(val) / max, QColor::fromHsv(0, 0, 0, val)); @@ -293,8 +313,8 @@ void ColorSlider::drawPicker(const QColor &color) val = color.alphaF(); } - const qreal pickerMaxXPos = innerSliderRect.width() - mPickerSize.width(); - val = static_cast(innerSliderRect.left() + qMax(mMin, (val * pickerMaxXPos))) + inset; + const qreal maxDistance = SliderGeometry::pickerMaxDistance(innerSliderRect.width(), mPickerSize.width()); + val = static_cast(innerSliderRect.left() + qMax(static_cast(mMin), (val * maxDistance))) + inset; QPen ounterPen; ounterPen.setJoinStyle(Qt::MiterJoin); @@ -333,14 +353,18 @@ void ColorSlider::drawPicker(const QColor &color) void ColorSlider::colorPicked(QPoint point) { - QRectF sliderRect = SliderGeometry::contentsRect(contentsRect(), mSliderStyle.borderWidth); + qreal borderWidth = mSliderStyle.borderWidth; + QRectF innerSliderRect = SliderGeometry::contentsRect(contentsRect(), borderWidth) + .adjusted(borderWidth, + borderWidth, + -borderWidth, + -borderWidth); QColor colorPicked = mColor; - int colorMax = static_cast(mMax); - - int colorVal = (point.x() - (mPickerSize.width() * 0.5)) * colorMax / (sliderRect.right() - (mPickerSize.width())); + int colorMax = colorTypeMax(); - colorVal = (colorVal > colorMax) ? colorMax : colorVal; - colorVal = (colorVal < 0) ? 0 : colorVal; + qreal pickerCenter = mPickerSize.width() * 0.5; + int colorVal = (point.x() - pickerCenter) * colorSteps() / SliderGeometry::pickerMaxDistance(innerSliderRect.width(), mPickerSize.width()); + colorVal = qBound(mMin, colorVal, colorMax); switch (mSpecType) { diff --git a/app/src/colorslider.h b/app/src/colorslider.h index e9b6035cac..8f0796b123 100644 --- a/app/src/colorslider.h +++ b/app/src/colorslider.h @@ -46,7 +46,7 @@ class ColorSlider : public QWidget explicit ColorSlider(QWidget* parent); ~ColorSlider() override; - void init(ColorSpecType specType, ColorType type, const QColor &color, qreal min, qreal max); + void init(ColorSpecType specType, ColorType type, const QColor &color); QLinearGradient setColorSpec(const QColor &color); @@ -59,9 +59,6 @@ class ColorSlider : public QWidget void setColorSpecType(ColorSpecType newType) { this->mSpecType = newType; } void setColorType(ColorType newType) { this->mColorType = newType; } - void setMin(qreal min) { mMin = min; } - void setMax(qreal max) { mMax = max; } - QSize sizeHint() const override; protected: @@ -75,6 +72,9 @@ class ColorSlider : public QWidget private: + int colorSteps() const; + int colorTypeMax() const; + void setupPicker(); void drawColorBox(const QColor &color, QSize size); void drawPicker(const QColor &color); @@ -87,8 +87,7 @@ class ColorSlider : public QWidget QPixmap mBoxPixmapSource; QColor mColor; - qreal mMin = 0.0; - qreal mMax = 0.0; + int mMin = 0; QSizeF mPickerSize = QSize(-1, -1); diff --git a/core_lib/src/util/slidergeometry.cpp b/core_lib/src/util/slidergeometry.cpp index 4252de768a..39c786510b 100644 --- a/core_lib/src/util/slidergeometry.cpp +++ b/core_lib/src/util/slidergeometry.cpp @@ -36,3 +36,8 @@ qreal SliderGeometry::penStrokeInset(qreal borderWidth) { return borderWidth * 0.5; } + +qreal SliderGeometry::pickerMaxDistance(qreal sliderWidth, qreal pickerWidth) +{ + return sliderWidth - pickerWidth; +} diff --git a/core_lib/src/util/slidergeometry.h b/core_lib/src/util/slidergeometry.h index bf3942e022..62ed10b0bb 100644 --- a/core_lib/src/util/slidergeometry.h +++ b/core_lib/src/util/slidergeometry.h @@ -23,6 +23,7 @@ namespace SliderGeometry { QRectF contentsRect(const QRectF& rect, qreal borderWidth); qreal penStrokeInset(qreal borderWidth); qreal innerCornerRadius(qreal outerRadius, qreal borderWidth); + qreal pickerMaxDistance(qreal sliderWidth, qreal pickerWidth); } #endif // SLIDERGEOMETRY_H From 53fa087f9dc015508bd553375fc053974836b1f5 Mon Sep 17 00:00:00 2001 From: MrStevns Date: Sun, 15 Mar 2026 12:30:18 +0100 Subject: [PATCH 8/9] Fix SonarQube issues --- app/src/colorpreviewwidget.cpp | 2 +- app/src/colorpreviewwidget.h | 4 ++-- app/src/colorslider.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/colorpreviewwidget.cpp b/app/src/colorpreviewwidget.cpp index 4a8ca6560a..5ebd0e31c7 100644 --- a/app/src/colorpreviewwidget.cpp +++ b/app/src/colorpreviewwidget.cpp @@ -23,7 +23,7 @@ ColorPreviewWidget::ColorPreviewWidget(QWidget*) } -void ColorPreviewWidget::setColor(QColor &color) +void ColorPreviewWidget::setColor(const QColor& color) { if (color == mColor) { return; } diff --git a/app/src/colorpreviewwidget.h b/app/src/colorpreviewwidget.h index abb24c917c..e479d4f06e 100644 --- a/app/src/colorpreviewwidget.h +++ b/app/src/colorpreviewwidget.h @@ -23,9 +23,9 @@ class ColorPreviewWidget: public QWidget { Q_OBJECT public: - ColorPreviewWidget(QWidget* = nullptr); + explicit ColorPreviewWidget(QWidget* = nullptr); - void setColor(QColor& color); + void setColor(const QColor& color); protected: void paintEvent(QPaintEvent* event) override; diff --git a/app/src/colorslider.h b/app/src/colorslider.h index 8f0796b123..d363ae0bb2 100644 --- a/app/src/colorslider.h +++ b/app/src/colorslider.h @@ -97,9 +97,9 @@ class ColorSlider : public QWidget QLinearGradient mGradient; SliderPainterStyle mSliderStyle { + .strokeRole = QPalette::Dark, .hasCustomFill = true, .customFill = QBrush(QPixmap(":icons/general/checkerboard_smaller.png")), - .strokeRole = QPalette::Dark, }; QPixmap mCheckerboardPixmap = QPixmap(":icons/general/checkerboard_smaller.png"); From dd4828238985bea316608cd355fb9c30d78fe08a Mon Sep 17 00:00:00 2001 From: MrStevns Date: Sun, 15 Mar 2026 12:53:05 +0100 Subject: [PATCH 9/9] Fix Qt5 compatibility issues --- app/src/colorslider.h | 10 +++++----- core_lib/src/util/drawsliderstyle.h | 25 +++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/app/src/colorslider.h b/app/src/colorslider.h index d363ae0bb2..1a487b4c0a 100644 --- a/app/src/colorslider.h +++ b/app/src/colorslider.h @@ -96,11 +96,11 @@ class ColorSlider : public QWidget QLinearGradient mGradient; - SliderPainterStyle mSliderStyle { - .strokeRole = QPalette::Dark, - .hasCustomFill = true, - .customFill = QBrush(QPixmap(":icons/general/checkerboard_smaller.png")), - }; + SliderPainterStyle mSliderStyle = SliderPainterStyle( + QPalette::Dark, + true, + QBrush(QPixmap(":icons/general/checkerboard_smaller.png")) + ); QPixmap mCheckerboardPixmap = QPixmap(":icons/general/checkerboard_smaller.png"); }; diff --git a/core_lib/src/util/drawsliderstyle.h b/core_lib/src/util/drawsliderstyle.h index 186c82307d..b2a1ff36b3 100644 --- a/core_lib/src/util/drawsliderstyle.h +++ b/core_lib/src/util/drawsliderstyle.h @@ -22,6 +22,31 @@ GNU General Public License for more details. struct SliderPainterStyle { + SliderPainterStyle(QPalette::ColorRole fillRole, + QPalette::ColorRole strokeRole, + bool hasCustomFill, + QBrush customFill, + float borderWidth, + float cornerRadiusRatio) + { + this->fillRole = fillRole; + this->strokeRole = strokeRole; + this->hasCustomFill = hasCustomFill; + this->customFill = customFill; + this->borderWidth = borderWidth; + this->cornerRadiusRatio = cornerRadiusRatio; + } + + SliderPainterStyle(QPalette::ColorRole strokeRole, + bool hasCustomFill, + QBrush customFill) + { + this->strokeRole = strokeRole; + this->hasCustomFill = hasCustomFill; + this->customFill = customFill; + } + + // The filled part of the slider QPalette::ColorRole fillRole = QPalette::Window;