From 0dbc7de6172cf6d114d6891536e4d35b4f122881 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sat, 30 Dec 2023 16:17:41 +0100 Subject: [PATCH 01/17] refactor: improve alpha input/display --- src/CMakeLists.txt | 21 +- .../highlights/UserHighlightModel.cpp | 5 +- .../highlights/UserHighlightModel.hpp | 3 + src/widgets/dialogs/ColorPickerDialog.cpp | 428 ++++-------------- src/widgets/dialogs/ColorPickerDialog.hpp | 114 +---- src/widgets/helper/ColorButton.cpp | 23 - src/widgets/helper/ColorButton.hpp | 20 - src/widgets/helper/QColorPicker.cpp | 292 ------------ src/widgets/helper/QColorPicker.hpp | 131 ------ src/widgets/helper/color/AlphaSlider.cpp | 156 +++++++ src/widgets/helper/color/AlphaSlider.hpp | 47 ++ src/widgets/helper/color/Checkerboard.cpp | 29 ++ src/widgets/helper/color/Checkerboard.hpp | 13 + src/widgets/helper/color/ColorButton.cpp | 74 +++ src/widgets/helper/color/ColorButton.hpp | 34 ++ src/widgets/helper/color/ColorDetails.cpp | 159 +++++++ src/widgets/helper/color/ColorDetails.hpp | 51 +++ .../helper/color/ColorItemDelegate.cpp | 35 ++ .../helper/color/ColorItemDelegate.hpp | 16 + src/widgets/helper/color/ColorSelect.cpp | 86 ++++ src/widgets/helper/color/ColorSelect.hpp | 50 ++ src/widgets/helper/color/HueSlider.cpp | 146 ++++++ src/widgets/helper/color/HueSlider.hpp | 46 ++ src/widgets/helper/color/SBCanvas.cpp | 164 +++++++ src/widgets/helper/color/SBCanvas.hpp | 55 +++ src/widgets/settingspages/GeneralPageView.cpp | 24 +- .../settingspages/HighlightingPage.cpp | 29 +- 27 files changed, 1307 insertions(+), 944 deletions(-) delete mode 100644 src/widgets/helper/ColorButton.cpp delete mode 100644 src/widgets/helper/ColorButton.hpp delete mode 100644 src/widgets/helper/QColorPicker.cpp delete mode 100644 src/widgets/helper/QColorPicker.hpp create mode 100644 src/widgets/helper/color/AlphaSlider.cpp create mode 100644 src/widgets/helper/color/AlphaSlider.hpp create mode 100644 src/widgets/helper/color/Checkerboard.cpp create mode 100644 src/widgets/helper/color/Checkerboard.hpp create mode 100644 src/widgets/helper/color/ColorButton.cpp create mode 100644 src/widgets/helper/color/ColorButton.hpp create mode 100644 src/widgets/helper/color/ColorDetails.cpp create mode 100644 src/widgets/helper/color/ColorDetails.hpp create mode 100644 src/widgets/helper/color/ColorItemDelegate.cpp create mode 100644 src/widgets/helper/color/ColorItemDelegate.hpp create mode 100644 src/widgets/helper/color/ColorSelect.cpp create mode 100644 src/widgets/helper/color/ColorSelect.hpp create mode 100644 src/widgets/helper/color/HueSlider.cpp create mode 100644 src/widgets/helper/color/HueSlider.hpp create mode 100644 src/widgets/helper/color/SBCanvas.cpp create mode 100644 src/widgets/helper/color/SBCanvas.hpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f16ea09cf15..c0a4dc8b799 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -588,12 +588,27 @@ set(SOURCE_FILES widgets/dialogs/switcher/SwitchSplitItem.cpp widgets/dialogs/switcher/SwitchSplitItem.hpp + widgets/helper/color/AlphaSlider.cpp + widgets/helper/color/AlphaSlider.hpp + widgets/helper/color/Checkerboard.cpp + widgets/helper/color/Checkerboard.hpp + widgets/helper/color/ColorButton.cpp + widgets/helper/color/ColorButton.hpp + widgets/helper/color/ColorDetails.cpp + widgets/helper/color/ColorDetails.hpp + widgets/helper/color/ColorItemDelegate.cpp + widgets/helper/color/ColorItemDelegate.hpp + widgets/helper/color/ColorSelect.cpp + widgets/helper/color/ColorSelect.hpp + widgets/helper/color/HueSlider.cpp + widgets/helper/color/HueSlider.hpp + widgets/helper/color/SBCanvas.cpp + widgets/helper/color/SBCanvas.hpp + widgets/helper/Button.cpp widgets/helper/Button.hpp widgets/helper/ChannelView.cpp widgets/helper/ChannelView.hpp - widgets/helper/ColorButton.cpp - widgets/helper/ColorButton.hpp widgets/helper/ComboBoxItemDelegate.cpp widgets/helper/ComboBoxItemDelegate.hpp widgets/helper/DebugPopup.cpp @@ -608,8 +623,6 @@ set(SOURCE_FILES widgets/helper/NotebookButton.hpp widgets/helper/NotebookTab.cpp widgets/helper/NotebookTab.hpp - widgets/helper/QColorPicker.cpp - widgets/helper/QColorPicker.hpp widgets/helper/RegExpItemDelegate.cpp widgets/helper/RegExpItemDelegate.hpp widgets/helper/TrimRegExpValidator.cpp diff --git a/src/controllers/highlights/UserHighlightModel.cpp b/src/controllers/highlights/UserHighlightModel.cpp index 15ca70163c1..26a44dd518f 100644 --- a/src/controllers/highlights/UserHighlightModel.cpp +++ b/src/controllers/highlights/UserHighlightModel.cpp @@ -1,7 +1,6 @@ -#include "UserHighlightModel.hpp" +#include "controllers/highlights/UserHighlightModel.hpp" #include "Application.hpp" -#include "controllers/highlights/HighlightModel.hpp" #include "controllers/highlights/HighlightPhrase.hpp" #include "providers/colors/ColorProvider.hpp" #include "singletons/Settings.hpp" @@ -10,8 +9,6 @@ namespace chatterino { -using Column = HighlightModel::Column; - // commandmodel UserHighlightModel::UserHighlightModel(QObject *parent) : SignalVectorModel(Column::COUNT, parent) diff --git a/src/controllers/highlights/UserHighlightModel.hpp b/src/controllers/highlights/UserHighlightModel.hpp index a9185b6c302..e17b8479256 100644 --- a/src/controllers/highlights/UserHighlightModel.hpp +++ b/src/controllers/highlights/UserHighlightModel.hpp @@ -1,6 +1,7 @@ #pragma once #include "common/SignalVectorModel.hpp" +#include "controllers/highlights/HighlightModel.hpp" #include @@ -12,6 +13,8 @@ class HighlightPhrase; class UserHighlightModel : public SignalVectorModel { public: + using Column = HighlightModel::Column; + explicit UserHighlightModel(QObject *parent); protected: diff --git a/src/widgets/dialogs/ColorPickerDialog.cpp b/src/widgets/dialogs/ColorPickerDialog.cpp index 5d1096d815e..9a6e2dd40da 100644 --- a/src/widgets/dialogs/ColorPickerDialog.cpp +++ b/src/widgets/dialogs/ColorPickerDialog.cpp @@ -1,390 +1,130 @@ #include "widgets/dialogs/ColorPickerDialog.hpp" +#include "common/Literals.hpp" #include "providers/colors/ColorProvider.hpp" -#include "singletons/Theme.hpp" -#include "util/LayoutCreator.hpp" -#include "widgets/helper/ColorButton.hpp" -#include "widgets/helper/QColorPicker.hpp" +#include "widgets/helper/color/ColorButton.hpp" +#include "widgets/helper/color/ColorDetails.hpp" +#include "widgets/helper/color/ColorSelect.hpp" -#include -#include -#include +namespace { -namespace chatterino { - -ColorPickerDialog::ColorPickerDialog(const QColor &initial, QWidget *parent) - : BasePopup( - { - BaseWindow::EnableCustomFrame, - BaseWindow::DisableLayoutSave, - BaseWindow::BoundsCheckOnShow, - }, - parent) - , color_() - , dialogConfirmed_(false) -{ - // This hosts the "business logic" and the dialog button box - LayoutCreator layoutWidget(this->getLayoutContainer()); - auto layout = layoutWidget.setLayoutType().withoutMargin(); - - // This hosts the business logic: color picker and predefined colors - LayoutCreator contentCreator(new QWidget()); - auto contents = contentCreator.setLayoutType(); - - // This hosts the predefined colors (and also the currently selected color) - LayoutCreator predefCreator(new QWidget()); - auto predef = predefCreator.setLayoutType(); - - // Recently used colors - { - LayoutCreator gridCreator(new QWidget()); - this->initRecentColors(gridCreator); - - predef.append(gridCreator.getElement()); - } - - // Default colors - { - LayoutCreator gridCreator(new QWidget()); - this->initDefaultColors(gridCreator); - - predef.append(gridCreator.getElement()); - } - - // Currently selected color - { - LayoutCreator curColorCreator(new QWidget()); - auto curColor = curColorCreator.setLayoutType(); - curColor.emplace("Selected:").assign(&this->ui_.selected.label); - curColor.emplace(initial).assign( - &this->ui_.selected.color); - - predef.append(curColor.getElement()); - } - - contents.append(predef.getElement()); - - // Color picker - { - LayoutCreator obj(new QWidget()); - auto vbox = obj.setLayoutType(); - - // The actual color picker - { - LayoutCreator cpCreator(new QWidget()); - this->initColorPicker(cpCreator); - - vbox.append(cpCreator.getElement()); - } - - // Spin boxes - { - LayoutCreator sbCreator(new QWidget()); - this->initSpinBoxes(sbCreator); +using namespace chatterino; - vbox.append(sbCreator.getElement()); - } +constexpr size_t COLORS_PER_ROW = 5; - // HTML color - { - LayoutCreator htmlCreator(new QWidget()); - this->initHtmlColor(htmlCreator); - - vbox.append(htmlCreator.getElement()); - } - - contents.append(obj.getElement()); - } - - layout.append(contents.getElement()); +QGridLayout *makeColorGrid(const auto &items, auto *self) +{ + auto *layout = new QGridLayout; - // Dialog buttons - auto buttons = - layout.emplace().emplace(this); + // TODO(nerix): use std::ranges::views::enumerate (C++ 23) + for (std::size_t i = 0; auto color : items) { - auto *button_ok = buttons->addButton(QDialogButtonBox::Ok); - QObject::connect(button_ok, &QPushButton::clicked, [this](bool) { - this->ok(); + auto *button = new ColorButton(color); + button->setMinimumWidth(40); + QObject::connect(button, &ColorButton::clicked, self, [self, color]() { + self->setColor(color); }); - auto *button_cancel = buttons->addButton(QDialogButtonBox::Cancel); - QObject::connect(button_cancel, &QAbstractButton::clicked, - [this](bool) { - this->close(); - }); - } - - this->themeChangedEvent(); - this->selectColor(initial, false); -} -void ColorPickerDialog::addShortcuts() -{ -} - -ColorPickerDialog::~ColorPickerDialog() -{ - if (this->htmlColorValidator_) - { - this->htmlColorValidator_->deleteLater(); - this->htmlColorValidator_ = nullptr; + layout->addWidget(button, static_cast(i / COLORS_PER_ROW), + static_cast(i % COLORS_PER_ROW)); + i++; } + return layout; } -QColor ColorPickerDialog::selectedColor() const -{ - if (!this->dialogConfirmed_) - { - // If the Cancel button was clicked, return the invalid color - return QColor(); - } +} // namespace - return this->color_; -} +namespace chatterino { -void ColorPickerDialog::closeEvent(QCloseEvent *) -{ - this->closed.invoke(this->selectedColor()); -} +using namespace literals; -void ColorPickerDialog::themeChangedEvent() +ColorPickerDialog::ColorPickerDialog(QColor color, QWidget *parent) + : BasePopup( + { + BaseWindow::EnableCustomFrame, + BaseWindow::DisableLayoutSave, + BaseWindow::BoundsCheckOnShow, + }, + parent) + , color_(color) { - BaseWindow::themeChangedEvent(); - - QString textCol = this->theme->splits.input.text.name(QColor::HexRgb); - QString bgCol = this->theme->splits.input.background.name(QColor::HexRgb); - - // Labels + this->setWindowTitle(u"Chatterino - Color picker"_s); + this->setAttribute(Qt::WA_DeleteOnClose); - QString labelStyle = QString("color: %1;").arg(textCol); - - this->ui_.recent.label->setStyleSheet(labelStyle); - this->ui_.def.label->setStyleSheet(labelStyle); - this->ui_.selected.label->setStyleSheet(labelStyle); - this->ui_.picker.htmlLabel->setStyleSheet(labelStyle); - - for (auto spinBoxLabel : this->ui_.picker.spinBoxLabels) + auto *dialogContents = new QHBoxLayout; + dialogContents->setContentsMargins(10, 10, 10, 10); { - spinBoxLabel->setStyleSheet(labelStyle); - } + auto *buttons = new QVBoxLayout; + buttons->addWidget(new QLabel(u"Recently used"_s)); + buttons->addLayout( + makeColorGrid(ColorProvider::instance().recentColors(), this)); - this->ui_.picker.htmlEdit->setStyleSheet( - this->theme->splits.input.styleSheet); + buttons->addSpacing(10); - // Styling spin boxes is too much effort -} + buttons->addWidget(new QLabel(u"Default colors"_s)); + buttons->addLayout( + makeColorGrid(ColorProvider::instance().defaultColors(), this)); -void ColorPickerDialog::selectColor(const QColor &color, bool fromColorPicker) -{ - if (color == this->color_) - return; + buttons->addStretch(1); + auto *display = new ColorButton(this->color()); + QObject::connect(this, &ColorPickerDialog::colorChanged, display, + &ColorButton::setColor); + buttons->addWidget(display); - this->color_ = color; - - // Update UI elements - this->ui_.selected.color->setColor(this->color_); - - /* - * Somewhat "ugly" hack to prevent feedback loop between widgets. Since - * this method is private, I'm okay with this being ugly. - */ - if (!fromColorPicker) - { - this->ui_.picker.colorPicker->setCol(this->color_.hslHue(), - this->color_.hslSaturation()); - this->ui_.picker.luminancePicker->setCol(this->color_.hsvHue(), - this->color_.hsvSaturation(), - this->color_.value()); + dialogContents->addLayout(buttons); + dialogContents->addSpacing(10); } - this->ui_.picker.spinBoxes[SpinBox::RED]->setValue(this->color_.red()); - this->ui_.picker.spinBoxes[SpinBox::GREEN]->setValue(this->color_.green()); - this->ui_.picker.spinBoxes[SpinBox::BLUE]->setValue(this->color_.blue()); - this->ui_.picker.spinBoxes[SpinBox::ALPHA]->setValue(this->color_.alpha()); - - /* - * Here, we are intentionally using HexRgb instead of HexArgb. Most online - * sites (or other applications) will likely not include the alpha channel - * in their output. - */ - this->ui_.picker.htmlEdit->setText(this->color_.name(QColor::HexRgb)); -} - -void ColorPickerDialog::ok() -{ - this->dialogConfirmed_ = true; - this->close(); -} - -void ColorPickerDialog::initRecentColors(LayoutCreator &creator) -{ - auto grid = creator.setLayoutType(); - - auto label = this->ui_.recent.label = new QLabel("Recently used:"); - grid->addWidget(label, 0, 0, 1, -1); - - const auto recentColors = ColorProvider::instance().recentColors(); - auto it = recentColors.begin(); - size_t ind = 0; - while (it != recentColors.end() && ind < MAX_RECENT_COLORS) { - this->ui_.recent.colors.push_back(new ColorButton(*it, this)); - auto *button = this->ui_.recent.colors[ind]; + auto *controls = new QVBoxLayout; - static_assert(RECENT_COLORS_PER_ROW != 0); - const int rowInd = (ind / RECENT_COLORS_PER_ROW) + 1; - const int columnInd = ind % RECENT_COLORS_PER_ROW; + auto *colorSelect = new ColorSelect(this->color()); + auto *colorDetails = new ColorDetails(this->color()); - grid->addWidget(button, rowInd, columnInd); + QObject::connect(colorSelect, &ColorSelect::colorChanged, this, + &ColorPickerDialog::setColor); + QObject::connect(colorDetails, &ColorDetails::colorChanged, this, + &ColorPickerDialog::setColor); + QObject::connect(this, &ColorPickerDialog::colorChanged, colorSelect, + &ColorSelect::setColor); + QObject::connect(this, &ColorPickerDialog::colorChanged, colorDetails, + &ColorDetails::setColor); - QObject::connect(button, &QPushButton::clicked, [=, this] { - this->selectColor(button->color(), false); - }); + controls->addWidget(colorSelect); + controls->addWidget(colorDetails); - ++it; - ++ind; + dialogContents->addLayout(controls); } - auto spacer = - new QSpacerItem(40, 20, QSizePolicy::Minimum, QSizePolicy::Expanding); - grid->addItem(spacer, (ind / RECENT_COLORS_PER_ROW) + 2, 0, 1, 1, - Qt::AlignTop); -} + auto *dialogLayout = new QVBoxLayout(this->getLayoutContainer()); + dialogLayout->addLayout(dialogContents, 1); + dialogLayout->addStretch(1); -void ColorPickerDialog::initDefaultColors(LayoutCreator &creator) -{ - auto grid = creator.setLayoutType(); - - auto label = this->ui_.def.label = new QLabel("Default colors:"); - grid->addWidget(label, 0, 0, 1, -1); - - const auto defaultColors = ColorProvider::instance().defaultColors(); - auto it = defaultColors.begin(); - size_t ind = 0; - while (it != defaultColors.end()) - { - this->ui_.def.colors.push_back(new ColorButton(*it, this)); - auto *button = this->ui_.def.colors[ind]; - - const int rowInd = (ind / DEFAULT_COLORS_PER_ROW) + 1; - const int columnInd = ind % DEFAULT_COLORS_PER_ROW; - - grid->addWidget(button, rowInd, columnInd); - - QObject::connect(button, &QPushButton::clicked, [=, this] { - this->selectColor(button->color(), false); - }); + auto *buttonBox = + new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - ++it; - ++ind; - } - - auto spacer = - new QSpacerItem(40, 20, QSizePolicy::Minimum, QSizePolicy::Expanding); - grid->addItem(spacer, (ind / DEFAULT_COLORS_PER_ROW) + 2, 0, 1, 1, - Qt::AlignTop); + QObject::connect(buttonBox, &QDialogButtonBox::accepted, this, [this] { + emit this->colorConfirmed(this->color()); + this->close(); + }); + QObject::connect(buttonBox, &QDialogButtonBox::rejected, this, + &ColorSelect::close); + dialogLayout->addWidget(buttonBox, 0, Qt::AlignRight); } -void ColorPickerDialog::initColorPicker(LayoutCreator &creator) +QColor ColorPickerDialog::color() const { - this->setWindowTitle("Chatterino - color picker"); - auto cpPanel = creator.setLayoutType(); - - /* - * For some reason, LayoutCreator::emplace didn't work for these. - * (Or maybe I was too dense to make it work.) - * After trying to debug for 4 hours or so, I gave up and settled - * for this solution. - */ - auto *colorPicker = new QColorPicker(this); - this->ui_.picker.colorPicker = colorPicker; - - auto *luminancePicker = new QColorLuminancePicker(this); - this->ui_.picker.luminancePicker = luminancePicker; - - cpPanel.append(colorPicker); - cpPanel.append(luminancePicker); - - QObject::connect(colorPicker, SIGNAL(newCol(int, int)), luminancePicker, - SLOT(setCol(int, int))); - - QObject::connect( - luminancePicker, &QColorLuminancePicker::newHsv, - [this](int h, int s, int v) { - int alpha = this->ui_.picker.spinBoxes[SpinBox::ALPHA]->value(); - this->selectColor(QColor::fromHsv(h, s, v, alpha), true); - }); + return this->color_; } -void ColorPickerDialog::initSpinBoxes(LayoutCreator &creator) +void ColorPickerDialog::setColor(const QColor &color) { - auto spinBoxes = creator.setLayoutType(); - - auto *red = this->ui_.picker.spinBoxes[SpinBox::RED] = - new QColSpinBox(this); - auto *green = this->ui_.picker.spinBoxes[SpinBox::GREEN] = - new QColSpinBox(this); - auto *blue = this->ui_.picker.spinBoxes[SpinBox::BLUE] = - new QColSpinBox(this); - auto *alpha = this->ui_.picker.spinBoxes[SpinBox::ALPHA] = - new QColSpinBox(this); - - // We need pointers to these for theme changes - auto *redLbl = this->ui_.picker.spinBoxLabels[SpinBox::RED] = - new QLabel("Red:"); - auto *greenLbl = this->ui_.picker.spinBoxLabels[SpinBox::GREEN] = - new QLabel("Green:"); - auto *blueLbl = this->ui_.picker.spinBoxLabels[SpinBox::BLUE] = - new QLabel("Blue:"); - auto *alphaLbl = this->ui_.picker.spinBoxLabels[SpinBox::ALPHA] = - new QLabel("Alpha:"); - - spinBoxes->addWidget(redLbl, 0, 0); - spinBoxes->addWidget(red, 0, 1); - - spinBoxes->addWidget(greenLbl, 1, 0); - spinBoxes->addWidget(green, 1, 1); - - spinBoxes->addWidget(blueLbl, 2, 0); - spinBoxes->addWidget(blue, 2, 1); - - spinBoxes->addWidget(alphaLbl, 3, 0); - spinBoxes->addWidget(alpha, 3, 1); - - for (size_t i = 0; i < SpinBox::END; ++i) + if (color == this->color_) { - QObject::connect( - this->ui_.picker.spinBoxes[i], - QOverload::of(&QSpinBox::valueChanged), [=, this](int value) { - this->selectColor(QColor(red->value(), green->value(), - blue->value(), alpha->value()), - false); - }); + return; } -} - -void ColorPickerDialog::initHtmlColor(LayoutCreator &creator) -{ - auto html = creator.setLayoutType(); - - // Copied from Qt source for QColorShower - static QRegularExpression regExp( - QStringLiteral("#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})")); - auto *validator = this->htmlColorValidator_ = - new QRegularExpressionValidator(regExp, this); - - auto *htmlLabel = this->ui_.picker.htmlLabel = new QLabel("HTML:"); - auto *htmlEdit = this->ui_.picker.htmlEdit = new QLineEdit(this); - - htmlEdit->setValidator(validator); - - html->addWidget(htmlLabel, 0, 0); - html->addWidget(htmlEdit, 0, 1); - - QObject::connect(htmlEdit, &QLineEdit::editingFinished, [this] { - const QColor col(this->ui_.picker.htmlEdit->text()); - if (col.isValid()) - this->selectColor(col, false); - }); + this->color_ = color; + emit this->colorChanged(color); } } // namespace chatterino diff --git a/src/widgets/dialogs/ColorPickerDialog.hpp b/src/widgets/dialogs/ColorPickerDialog.hpp index b932b26e814..a1bc2a7085b 100644 --- a/src/widgets/dialogs/ColorPickerDialog.hpp +++ b/src/widgets/dialogs/ColorPickerDialog.hpp @@ -2,119 +2,27 @@ #include "widgets/BasePopup.hpp" -#include -#include -#include -#include - -#include - namespace chatterino { -class ColorButton; -class QColorLuminancePicker; -class QColorPicker; -class QColSpinBox; - -template -class LayoutCreator; - -/** - * @brief A custom color picker dialog. - * - * This class exists because QColorPickerDialog did not suit our use case. - * This dialog provides buttons for recently used and default colors, as well - * as a color picker widget identical to the one used in QColorPickerDialog. - */ class ColorPickerDialog : public BasePopup { -public: - /** - * @brief Create a new color picker dialog that selects the initial color. - * - * You can connect to the ::closed signal of this instance to get notified - * when the dialog is closed. - */ - ColorPickerDialog(const QColor &initial, QWidget *parent); + Q_OBJECT + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) - ~ColorPickerDialog() override; +public: + ColorPickerDialog(QColor color = {}, QWidget *parent = nullptr); - /** - * @brief Return the final color selected by the user. - * - * Note that this method will always return the invalid color if the dialog - * is still open, or if the dialog has not been confirmed. - * - * @return The color selected by the user, if the dialog was confirmed. - * The invalid color, if the dialog has not been confirmed. - */ - QColor selectedColor() const; + QColor color() const; - pajlada::Signals::Signal closed; +signals: + void colorChanged(QColor color); + void colorConfirmed(QColor color); -protected: - void closeEvent(QCloseEvent *) override; - void themeChangedEvent() override; +public slots: + void setColor(const QColor &color); private: - struct { - struct { - QLabel *label; - std::vector colors; - } recent; - - struct { - QLabel *label; - std::vector colors; - } def; - - struct { - QLabel *label; - ColorButton *color; - } selected{}; - - struct { - QColorPicker *colorPicker; - QColorLuminancePicker *luminancePicker; - - std::array spinBoxLabels; - std::array spinBoxes; - - QLabel *htmlLabel; - QLineEdit *htmlEdit; - } picker{}; - } ui_; - - enum SpinBox : size_t { RED = 0, GREEN = 1, BLUE = 2, ALPHA = 3, END }; - - static const size_t MAX_RECENT_COLORS = 10; - static const size_t RECENT_COLORS_PER_ROW = 5; - static const size_t DEFAULT_COLORS_PER_ROW = 5; - QColor color_; - bool dialogConfirmed_; - QRegularExpressionValidator *htmlColorValidator_{}; - - /** - * @brief Update the currently selected color. - * - * @param color Color to update to. - * @param fromColorPicker Whether the color update has been triggered by - * one of the color picker widgets. This is needed - * to prevent weird widget behavior. - */ - void selectColor(const QColor &color, bool fromColorPicker); - - /// Called when the dialog is confirmed. - void ok(); - - // Helper methods for initializing UI elements - void initRecentColors(LayoutCreator &creator); - void initDefaultColors(LayoutCreator &creator); - void initColorPicker(LayoutCreator &creator); - void initSpinBoxes(LayoutCreator &creator); - void initHtmlColor(LayoutCreator &creator); - - void addShortcuts() override; }; + } // namespace chatterino diff --git a/src/widgets/helper/ColorButton.cpp b/src/widgets/helper/ColorButton.cpp deleted file mode 100644 index afd21f8328b..00000000000 --- a/src/widgets/helper/ColorButton.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "widgets/helper/ColorButton.hpp" - -namespace chatterino { - -ColorButton::ColorButton(const QColor &color, QWidget *parent) - : QPushButton(parent) - , color_(color) -{ - this->setColor(color_); -} - -const QColor &ColorButton::color() const -{ - return this->color_; -} - -void ColorButton::setColor(QColor color) -{ - this->color_ = color; - this->setStyleSheet("background-color: " + color.name(QColor::HexArgb)); -} - -} // namespace chatterino diff --git a/src/widgets/helper/ColorButton.hpp b/src/widgets/helper/ColorButton.hpp deleted file mode 100644 index 7101a8dce9b..00000000000 --- a/src/widgets/helper/ColorButton.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include - -namespace chatterino { - -class ColorButton : public QPushButton -{ -public: - ColorButton(const QColor &color, QWidget *parent = nullptr); - - const QColor &color() const; - - void setColor(QColor color); - -private: - QColor color_; -}; - -} // namespace chatterino diff --git a/src/widgets/helper/QColorPicker.cpp b/src/widgets/helper/QColorPicker.cpp deleted file mode 100644 index 50086efcade..00000000000 --- a/src/widgets/helper/QColorPicker.cpp +++ /dev/null @@ -1,292 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtWidgets module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ -#include "widgets/helper/QColorPicker.hpp" - -#include -#include -#include - -/* - * These classes are literally copied from the Qt source. - * Unfortunately, they are private to the QColorDialog class so we cannot use - * them directly. - * If they become public at any point in the future, it should be possible to - * replace every include of this header with the respective includes for the - * QColorPicker, QColorLuminancePicker, and QColSpinBox classes. - */ -namespace chatterino { - -int QColorLuminancePicker::y2val(int y) -{ - int d = height() - 2 * coff - 1; - return 255 - (y - coff) * 255 / d; -} - -int QColorLuminancePicker::val2y(int v) -{ - int d = height() - 2 * coff - 1; - return coff + (255 - v) * d / 255; -} - -QColorLuminancePicker::QColorLuminancePicker(QWidget *parent) - : QWidget(parent) -{ - hue = 100; - val = 100; - sat = 100; - pix = 0; - setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); -} - -QColorLuminancePicker::~QColorLuminancePicker() -{ - delete pix; -} - -void QColorLuminancePicker::mouseMoveEvent(QMouseEvent *m) -{ - setVal(y2val(m->y())); -} - -void QColorLuminancePicker::mousePressEvent(QMouseEvent *m) -{ - setVal(y2val(m->y())); -} - -void QColorLuminancePicker::setVal(int v) -{ - if (val == v) - return; - val = qMax(0, qMin(v, 255)); - delete pix; - pix = 0; - repaint(); - emit newHsv(hue, sat, val); -} - -//receives from a hue,sat chooser and relays. -void QColorLuminancePicker::setCol(int h, int s) -{ - setCol(h, s, val); - emit newHsv(h, s, val); -} - -QSize QColorLuminancePicker::sizeHint() const -{ - return QSize(LUMINANCE_PICKER_WIDTH, LUMINANCE_PICKER_HEIGHT); -} - -void QColorLuminancePicker::paintEvent(QPaintEvent *) -{ - int w = width() - 5; - QRect r(0, foff, w, height() - 2 * foff); - int wi = r.width() - 2; - int hi = r.height() - 2; - if (!pix || pix->height() != hi || pix->width() != wi) - { - delete pix; - QImage img(wi, hi, QImage::Format_RGB32); - int y; - uint *pixel = (uint *)img.scanLine(0); - for (y = 0; y < hi; y++) - { - uint *end = pixel + wi; - std::fill(pixel, end, - QColor::fromHsv(hue, sat, y2val(y + coff)).rgb()); - pixel = end; - } - pix = new QPixmap(QPixmap::fromImage(img)); - } - QPainter p(this); - p.drawPixmap(1, coff, *pix); - const QPalette &g = palette(); - qDrawShadePanel(&p, r, g, true); - p.setPen(g.windowText().color()); - p.setBrush(g.windowText()); - QPolygon a; - int y = val2y(val); - a.setPoints(3, w, y, w + 5, y + 5, w + 5, y - 5); - p.eraseRect(w, 0, 5, height()); - p.drawPolygon(a); -} - -void QColorLuminancePicker::setCol(int h, int s, int v) -{ - val = v; - hue = h; - sat = s; - delete pix; - pix = 0; - repaint(); -} - -QPoint QColorPicker::colPt() -{ - QRect r = contentsRect(); - return QPoint((360 - hue) * (r.width() - 1) / 360, - (255 - sat) * (r.height() - 1) / 255); -} - -int QColorPicker::huePt(const QPoint &pt) -{ - QRect r = contentsRect(); - return 360 - pt.x() * 360 / (r.width() - 1); -} - -int QColorPicker::satPt(const QPoint &pt) -{ - QRect r = contentsRect(); - return 255 - pt.y() * 255 / (r.height() - 1); -} - -void QColorPicker::setCol(const QPoint &pt) -{ - setCol(huePt(pt), satPt(pt)); -} - -QColorPicker::QColorPicker(QWidget *parent) - : QFrame(parent) - , crossVisible(true) -{ - hue = 0; - sat = 0; - setCol(150, 255); - setAttribute(Qt::WA_NoSystemBackground); - setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); -} - -QColorPicker::~QColorPicker() -{ -} - -void QColorPicker::setCrossVisible(bool visible) -{ - if (crossVisible != visible) - { - crossVisible = visible; - update(); - } -} - -QSize QColorPicker::sizeHint() const -{ - return QSize(COLOR_PICKER_WIDTH, COLOR_PICKER_HEIGHT); -} - -void QColorPicker::setCol(int h, int s) -{ - int nhue = qMin(qMax(0, h), 359); - int nsat = qMin(qMax(0, s), 255); - if (nhue == hue && nsat == sat) - return; - QRect r(colPt(), QSize(20, 20)); - hue = nhue; - sat = nsat; - r = r.united(QRect(colPt(), QSize(20, 20))); - r.translate(contentsRect().x() - 9, contentsRect().y() - 9); - repaint(r); -} - -void QColorPicker::mouseMoveEvent(QMouseEvent *m) -{ - QPoint p = m->pos() - contentsRect().topLeft(); - setCol(p); - emit newCol(hue, sat); -} - -void QColorPicker::mousePressEvent(QMouseEvent *m) -{ - QPoint p = m->pos() - contentsRect().topLeft(); - setCol(p); - emit newCol(hue, sat); -} - -void QColorPicker::paintEvent(QPaintEvent *) -{ - QPainter p(this); - drawFrame(&p); - QRect r = contentsRect(); - p.drawPixmap(r.topLeft(), pix); - if (crossVisible) - { - QPoint pt = colPt() + r.topLeft(); - p.setPen(Qt::black); - p.fillRect(pt.x() - 9, pt.y(), 20, 2, Qt::black); - p.fillRect(pt.x(), pt.y() - 9, 2, 20, Qt::black); - } -} - -void QColorPicker::resizeEvent(QResizeEvent *ev) -{ - QFrame::resizeEvent(ev); - int w = width() - frameWidth() * 2; - int h = height() - frameWidth() * 2; - QImage img(w, h, QImage::Format_RGB32); - int x, y; - uint *pixel = (uint *)img.scanLine(0); - for (y = 0; y < h; y++) - { - const uint *end = pixel + w; - x = 0; - while (pixel < end) - { - QPoint p(x, y); - QColor c; - c.setHsv(huePt(p), satPt(p), 200); - *pixel = c.rgb(); - ++pixel; - ++x; - } - } - pix = QPixmap::fromImage(img); -} - -QColSpinBox::QColSpinBox(QWidget *parent) - : QSpinBox(parent) -{ - this->setRange(0, 255); -} - -void QColSpinBox::setValue(int i) -{ - const QSignalBlocker blocker(this); - QSpinBox::setValue(i); -} - -} // namespace chatterino diff --git a/src/widgets/helper/QColorPicker.hpp b/src/widgets/helper/QColorPicker.hpp deleted file mode 100644 index 408fd344c9d..00000000000 --- a/src/widgets/helper/QColorPicker.hpp +++ /dev/null @@ -1,131 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtWidgets module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ -#pragma once - -#include -#include - -namespace chatterino { - -/* - * These classes are literally copied from the Qt source. - * Unfortunately, they are private to the QColorDialog class so we cannot use - * them directly. - * If they become public at any point in the future, it should be possible to - * replace every include of this header with the respective includes for the - * QColorPicker, QColorLuminancePicker, and QColSpinBox classes. - */ -class QColorPicker : public QFrame -{ - Q_OBJECT -public: - QColorPicker(QWidget *parent); - ~QColorPicker() override; - void setCrossVisible(bool visible); - -public slots: - void setCol(int h, int s); - -signals: - void newCol(int h, int s); - -protected: - QSize sizeHint() const override; - void paintEvent(QPaintEvent *) override; - void mouseMoveEvent(QMouseEvent *) override; - void mousePressEvent(QMouseEvent *) override; - void resizeEvent(QResizeEvent *) override; - -private: - int hue; - int sat; - QPoint colPt(); - int huePt(const QPoint &pt); - int satPt(const QPoint &pt); - void setCol(const QPoint &pt); - QPixmap pix; - bool crossVisible; -}; - -static const int COLOR_PICKER_WIDTH = 220; -static const int COLOR_PICKER_HEIGHT = 200; - -class QColorLuminancePicker : public QWidget -{ - Q_OBJECT -public: - QColorLuminancePicker(QWidget *parent = 0); - ~QColorLuminancePicker() override; - -public slots: - void setCol(int h, int s, int v); - void setCol(int h, int s); - -signals: - void newHsv(int h, int s, int v); - -protected: - QSize sizeHint() const override; - void paintEvent(QPaintEvent *) override; - void mouseMoveEvent(QMouseEvent *) override; - void mousePressEvent(QMouseEvent *) override; - -private: - enum { foff = 3, coff = 4 }; //frame and contents offset - int val; - int hue; - int sat; - int y2val(int y); - int val2y(int val); - void setVal(int v); - QPixmap *pix; -}; - -static const int LUMINANCE_PICKER_WIDTH = 25; -static const int LUMINANCE_PICKER_HEIGHT = COLOR_PICKER_HEIGHT; - -class QColSpinBox : public QSpinBox -{ -public: - QColSpinBox(QWidget *parent); - - void setValue(int i); -}; - -} // namespace chatterino diff --git a/src/widgets/helper/color/AlphaSlider.cpp b/src/widgets/helper/color/AlphaSlider.cpp new file mode 100644 index 00000000000..d43e7280b49 --- /dev/null +++ b/src/widgets/helper/color/AlphaSlider.cpp @@ -0,0 +1,156 @@ +#include "widgets/helper/color/AlphaSlider.hpp" + +#include "widgets/helper/color/Checkerboard.hpp" + +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()) + , baseColor_(color) +{ + this->setSizePolicy({QSizePolicy::Expanding, QSizePolicy::Fixed}); +} + +void AlphaSlider::updateColor(const QColor &color) +{ + if (this->baseColor_ == color) + { + return; + } + this->alpha_ = color.alpha(); + this->baseColor_ = color; + this->updatePixmap(); + this->repaint(); +} + +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->baseColor_; + QColor end = this->baseColor_; + 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}); + painter.setBrush(Qt::transparent); + painter.drawEllipse(circ, cornerRadius - 1, cornerRadius - 1); +} + +void AlphaSlider::setAlpha(int alpha) +{ + if (this->alpha_ == alpha) + { + return; + } + this->alpha_ = alpha; + this->update(); + emit alphaChanged(alpha); +} + +} // namespace chatterino diff --git a/src/widgets/helper/color/AlphaSlider.hpp b/src/widgets/helper/color/AlphaSlider.hpp new file mode 100644 index 00000000000..c5528700827 --- /dev/null +++ b/src/widgets/helper/color/AlphaSlider.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include + +namespace chatterino { + +class AlphaSlider : public QWidget +{ + Q_OBJECT + Q_PROPERTY(int alpha READ alpha NOTIFY alphaChanged) + +public: + AlphaSlider(QColor color = {}, QWidget *parent = nullptr); + + void updateColor(const QColor &color); + QSize sizeHint() const override; + + int alpha() const; + +signals: + void alphaChanged(int hue) const; + +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 baseColor_; + + QPixmap cachedPixmap_; + + bool trackingMouseEvents_ = false; + + void updatePixmap(); + int xPosToAlpha(int xPos) const; + + void updateFromEvent(QMouseEvent *event); + + void setAlpha(int alpha); +}; + +} // namespace chatterino diff --git a/src/widgets/helper/color/Checkerboard.cpp b/src/widgets/helper/color/Checkerboard.cpp new file mode 100644 index 00000000000..a82827cd9ba --- /dev/null +++ b/src/widgets/helper/color/Checkerboard.cpp @@ -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 diff --git a/src/widgets/helper/color/Checkerboard.hpp b/src/widgets/helper/color/Checkerboard.hpp new file mode 100644 index 00000000000..d845fbcc508 --- /dev/null +++ b/src/widgets/helper/color/Checkerboard.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include + +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 diff --git a/src/widgets/helper/color/ColorButton.cpp b/src/widgets/helper/color/ColorButton.cpp new file mode 100644 index 00000000000..dd8332f815b --- /dev/null +++ b/src/widgets/helper/color/ColorButton.cpp @@ -0,0 +1,74 @@ +#include "widgets/helper/color/ColorButton.hpp" + +#include "widgets/helper/color/Checkerboard.hpp" + +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) +{ + 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 diff --git a/src/widgets/helper/color/ColorButton.hpp b/src/widgets/helper/color/ColorButton.hpp new file mode 100644 index 00000000000..3601e4940a5 --- /dev/null +++ b/src/widgets/helper/color/ColorButton.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include + +namespace chatterino { + +class ColorButton : public QAbstractButton +{ + Q_OBJECT + Q_PROPERTY(QColor color READ color WRITE setColor) + +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 diff --git a/src/widgets/helper/color/ColorDetails.cpp b/src/widgets/helper/color/ColorDetails.cpp new file mode 100644 index 00000000000..268b7487800 --- /dev/null +++ b/src/widgets/helper/color/ColorDetails.cpp @@ -0,0 +1,159 @@ +#include "widgets/helper/color/ColorDetails.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 parseCssColor(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 { + +ColorDetails::ColorDetails(QColor color, QWidget *parent) + : QWidget(parent) + , currentColor_(color) + , cssValidator_(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 updateColor) { + component.lbl.setText(label); + component.box.setRange(0, 255); + QObject::connect(&component.box, &QSpinBox::valueChanged, + [this, &component, updateColor](int value) { + if (component.value == value) + { + return; + } + component.value = value; + updateColor(this->currentColor_, value); + this->updateCss(); + emit colorChanged(this->currentColor_); + }); + 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->cssLabel_.setText("CSS:"); + this->cssInput_.setValidator(&this->cssValidator_); + QObject::connect(&this->cssInput_, &QLineEdit::editingFinished, [this]() { + auto css = parseCssColor(this->cssInput_.text()); + if (!css.isValid() || this->currentColor_ == css) + { + return; + } + this->setColor(css); + }); + this->layout_.addWidget(&this->cssLabel_, row, 0); + this->layout_.addWidget(&this->cssInput_, row, 1); + + this->updateComponents(); +} + +void ColorDetails::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->updateCss(); +} + +void ColorDetails::updateCss() +{ + auto rgb = this->currentColor_.rgb(); + rgb <<= 8; + rgb |= this->currentColor_.alpha(); + // we always need to update the CSS color + this->cssInput_.setText(QStringLiteral("#%1").arg(rgb, 8, 16, QChar(u'0'))); +} + +QColor ColorDetails::color() const +{ + return this->currentColor_; +} + +void ColorDetails::setColor(const QColor &color) +{ + if (this->currentColor_ != color) + { + this->currentColor_ = color; + this->updateComponents(); + emit colorChanged(color); + } +} + +} // namespace chatterino diff --git a/src/widgets/helper/color/ColorDetails.hpp b/src/widgets/helper/color/ColorDetails.hpp new file mode 100644 index 00000000000..4ee9fb7489f --- /dev/null +++ b/src/widgets/helper/color/ColorDetails.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace chatterino { + +class ColorDetails : public QWidget +{ + Q_OBJECT + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) + +public: + ColorDetails(QColor color = {}, QWidget *parent = nullptr); + + QColor color() const; + +signals: + void colorChanged(QColor color); + +public slots: + void setColor(const QColor &color); + +private: + QColor currentColor_; + + struct Component { + QLabel lbl; + QSpinBox box; + int value = -1; + }; + + Component red_; + Component green_; + Component blue_; + Component alpha_; + + QLabel cssLabel_; + QLineEdit cssInput_; + QRegularExpressionValidator cssValidator_; + + QGridLayout layout_; + + void updateComponents(); + void updateCss(); +}; + +} // namespace chatterino diff --git a/src/widgets/helper/color/ColorItemDelegate.cpp b/src/widgets/helper/color/ColorItemDelegate.cpp new file mode 100644 index 00000000000..5b67f68bccf --- /dev/null +++ b/src/widgets/helper/color/ColorItemDelegate.cpp @@ -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(); + + 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 diff --git a/src/widgets/helper/color/ColorItemDelegate.hpp b/src/widgets/helper/color/ColorItemDelegate.hpp new file mode 100644 index 00000000000..e0cc4e8a2f4 --- /dev/null +++ b/src/widgets/helper/color/ColorItemDelegate.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include + +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 diff --git a/src/widgets/helper/color/ColorSelect.cpp b/src/widgets/helper/color/ColorSelect.cpp new file mode 100644 index 00000000000..70aca7bbc2e --- /dev/null +++ b/src/widgets/helper/color/ColorSelect.cpp @@ -0,0 +1,86 @@ +#include "widgets/helper/color/ColorSelect.hpp" + +namespace chatterino { + +ColorSelect::ColorSelect(QColor color, QWidget *parent) + : QWidget(parent) + , currentColor_(color) + , sbCanvas_(color) + , hueSlider_(color) + , alphaSlider_(color) + +{ + color.getHsv(&this->hue_, &this->saturation_, &this->brightness_); + + this->rows_.addWidget(&this->sbCanvas_, 0, Qt::AlignHCenter); + this->rows_.addWidget(&this->hueSlider_); + this->rows_.addWidget(&this->alphaSlider_); + this->setLayout(&this->rows_); + this->connectWidgets(); +} + +void ColorSelect::connectWidgets() +{ + const auto applyColor = [this]() { + emit colorChanged(this->currentColor_); + }; + + const auto updateCurrentColor = [this, applyColor]() { + this->currentColor_ = + QColor::fromHsv(this->hue_, this->saturation_, this->brightness_, + this->currentColor_.alpha()); + applyColor(); + }; + + QObject::connect(&this->hueSlider_, &HueSlider::hueChanged, + [this, updateCurrentColor](int hue) { + this->hue_ = hue; + this->sbCanvas_.setHue(hue); + updateCurrentColor(); + this->alphaSlider_.updateColor(this->currentColor_); + }); + QObject::connect(&this->alphaSlider_, &AlphaSlider::alphaChanged, + [this, applyColor](int alpha) { + this->currentColor_.setAlpha(alpha); + applyColor(); + }); + const auto updateAfterSbChanged = [this, updateCurrentColor]() { + updateCurrentColor(); + this->alphaSlider_.updateColor(this->currentColor_); + }; + QObject::connect(&this->sbCanvas_, &SBCanvas::saturationChanged, + [this, updateAfterSbChanged](int saturation) { + this->saturation_ = saturation; + updateAfterSbChanged(); + }); + QObject::connect(&this->sbCanvas_, &SBCanvas::brightnessChanged, + [this, updateAfterSbChanged](int brightness) { + this->brightness_ = brightness; + updateAfterSbChanged(); + }); +} + +QColor ColorSelect::color() const +{ + return this->currentColor_; +} + +void ColorSelect::setColor(const QColor &color) +{ + if (this->currentColor_ == color) + { + return; + } + this->currentColor_ = color; + color.getHsv(&this->hue_, &this->saturation_, &this->brightness_); + if (this->hue_ < 0) + { + this->hue_ = 0; + } + + this->sbCanvas_.updateColor(color); + this->hueSlider_.updateColor(color); + this->alphaSlider_.updateColor(color); +} + +} // namespace chatterino diff --git a/src/widgets/helper/color/ColorSelect.hpp b/src/widgets/helper/color/ColorSelect.hpp new file mode 100644 index 00000000000..744f132fce1 --- /dev/null +++ b/src/widgets/helper/color/ColorSelect.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include "widgets/helper/color/AlphaSlider.hpp" +#include "widgets/helper/color/HueSlider.hpp" +#include "widgets/helper/color/SBCanvas.hpp" + +#include +#include + +class SBCanvas; +class HueSlider; +class AlphaSlider; + +namespace chatterino { + +class ColorSelect : public QWidget +{ + Q_OBJECT + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) + +public: + ColorSelect(QColor color = {}, QWidget *parent = nullptr); + + QColor color() const; + +signals: + void colorChanged(QColor color); + +public slots: + void setColor(const QColor &color); + +private: + QColor currentColor_; + int hue_ = 0; + int saturation_ = 0; + int brightness_ = 0; + + SBCanvas sbCanvas_; + HueSlider hueSlider_; + AlphaSlider alphaSlider_; + QVBoxLayout rows_; + + void connectWidgets(); + +signals: + void pickerColorChanged(QColor color); + void detailsColorChanged(QColor color); +}; + +} // namespace chatterino diff --git a/src/widgets/helper/color/HueSlider.cpp b/src/widgets/helper/color/HueSlider.cpp new file mode 100644 index 00000000000..ac393c7cc56 --- /dev/null +++ b/src/widgets/helper/color/HueSlider.cpp @@ -0,0 +1,146 @@ +#include "widgets/helper/color/HueSlider.hpp" + +namespace { + +constexpr int SLIDER_WIDTH = 256; +constexpr int SLIDER_HEIGHT = 12; + +} // namespace + +namespace chatterino { + +HueSlider::HueSlider(QColor color, QWidget *parent) + : QWidget(parent) +{ + this->updateColor(color); + this->setSizePolicy({QSizePolicy::Expanding, QSizePolicy::Fixed}); +} + +void HueSlider::updateColor(const QColor &color) +{ + this->hue_ = color.hue(); + if (this->hue_ < 0) + { + this->hue_ = 0; + } + 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(Qt::transparent); + painter.drawEllipse(circ, cornerRadius - 1, cornerRadius - 1); +} + +void HueSlider::setHue(int hue) +{ + if (this->hue_ == hue) + { + return; + } + this->hue_ = hue; + this->update(); + emit hueChanged(hue); +} + +} // namespace chatterino diff --git a/src/widgets/helper/color/HueSlider.hpp b/src/widgets/helper/color/HueSlider.hpp new file mode 100644 index 00000000000..27886c78b17 --- /dev/null +++ b/src/widgets/helper/color/HueSlider.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include + +namespace chatterino { + +class HueSlider : public QWidget +{ + Q_OBJECT + Q_PROPERTY(int hue READ hue NOTIFY hueChanged) + +public: + HueSlider(QColor color = {}, QWidget *parent = nullptr); + + void updateColor(const QColor &color); + QSize sizeHint() const override; + + int hue() const; + +signals: + void hueChanged(int hue) const; + +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; + + QPixmap gradientPixmap_; + + bool trackingMouseEvents_ = false; + + void updatePixmap(); + int xPosToHue(int xPos) const; + + void updateFromEvent(QMouseEvent *event); + + void setHue(int hue); +}; + +} // namespace chatterino diff --git a/src/widgets/helper/color/SBCanvas.cpp b/src/widgets/helper/color/SBCanvas.cpp new file mode 100644 index 00000000000..ce6ce60bfe1 --- /dev/null +++ b/src/widgets/helper/color/SBCanvas.cpp @@ -0,0 +1,164 @@ +#include "widgets/helper/color/SBCanvas.hpp" + +namespace { + +constexpr int PICKER_WIDTH = 256; +constexpr int PICKER_HEIGHT = 256; + +} // namespace + +namespace chatterino { + +SBCanvas::SBCanvas(QColor color, QWidget *parent) + : QWidget(parent) +{ + this->updateColor(color); + this->setSizePolicy({QSizePolicy::Fixed, QSizePolicy::Fixed}); +} + +void SBCanvas::updateColor(const QColor &color) +{ + color.getHsv(&this->hue_, &this->saturation_, &this->brightness_); + if (this->hue_ < 0) + { + this->hue_ = 0; + } + this->update(); +} + +void SBCanvas::setHue(int hue) +{ + this->hue_ = hue; + this->updatePixmap(); + this->repaint(); +} + +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()); + this->setSaturation(this->xPosToSaturation(clampedX)); + this->setBrightness(this->yPosToBrightness(clampedY)); +} + +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(Qt::transparent); + painter.drawEllipse(circ, 5, 5); +} + +void SBCanvas::setSaturation(int saturation) +{ + if (this->saturation_ == saturation) + { + return; + } + this->saturation_ = saturation; + this->update(); + emit saturationChanged(saturation); +} + +void SBCanvas::setBrightness(int brightness) +{ + if (this->brightness_ == brightness) + { + return; + } + this->brightness_ = brightness; + this->update(); + emit brightnessChanged(brightness); +} + +} // namespace chatterino diff --git a/src/widgets/helper/color/SBCanvas.hpp b/src/widgets/helper/color/SBCanvas.hpp new file mode 100644 index 00000000000..f76cf149df5 --- /dev/null +++ b/src/widgets/helper/color/SBCanvas.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include + +namespace chatterino { + +class SBCanvas : public QWidget +{ + Q_OBJECT + Q_PROPERTY(int saturation READ saturation NOTIFY saturationChanged) + Q_PROPERTY(int brightness READ brightness NOTIFY brightnessChanged) + +public: + SBCanvas(QColor color = {}, QWidget *parent = nullptr); + + void updateColor(const QColor &color); + void setHue(int hue); + QSize sizeHint() const override; + +signals: + void saturationChanged(int saturation) const; + void brightnessChanged(int brightness) const; + +public slots: + int saturation() const; + int brightness() const; + +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; + + QPixmap gradientPixmap_; + + bool trackingMouseEvents_ = false; + + void updatePixmap(); + int xPosToSaturation(int xPos) const; + int yPosToBrightness(int yPos) const; + + void updateFromEvent(QMouseEvent *event); + + void setSaturation(int saturation); + void setBrightness(int brightness); +}; + +} // namespace chatterino diff --git a/src/widgets/settingspages/GeneralPageView.cpp b/src/widgets/settingspages/GeneralPageView.cpp index 95a0d42fc49..6b1a6c790ae 100644 --- a/src/widgets/settingspages/GeneralPageView.cpp +++ b/src/widgets/settingspages/GeneralPageView.cpp @@ -4,7 +4,7 @@ #include "util/LayoutHelper.hpp" #include "util/RapidJsonSerializeQString.hpp" #include "widgets/dialogs/ColorPickerDialog.hpp" -#include "widgets/helper/ColorButton.hpp" +#include "widgets/helper/color/ColorButton.hpp" #include "widgets/helper/Line.hpp" #include @@ -214,20 +214,18 @@ ColorButton *GeneralPageView::addColorButton( QObject::connect( colorButton, &ColorButton::clicked, [this, &setting, colorButton]() { - auto dialog = new ColorPickerDialog(QColor(setting), this); - dialog->setAttribute(Qt::WA_DeleteOnClose); - dialog->show(); - // We can safely ignore this signal connection, for now, since the + auto *dialog = new ColorPickerDialog(QColor(setting), this); // colorButton & setting are never deleted and the signal is deleted // once the dialog is closed - std::ignore = dialog->closed.connect( - [&setting, colorButton](QColor selected) { - if (selected.isValid()) - { - setting = selected.name(QColor::HexArgb); - colorButton->setColor(selected); - } - }); + QObject::connect(dialog, &ColorPickerDialog::colorConfirmed, this, + [&setting, colorButton](auto selected) { + if (selected.isValid()) + { + setting = selected.name(QColor::HexArgb); + colorButton->setColor(selected); + } + }); + dialog->show(); }); this->groups_.back().widgets.push_back({label, {text}}); diff --git a/src/widgets/settingspages/HighlightingPage.cpp b/src/widgets/settingspages/HighlightingPage.cpp index 61f87b58786..136b760cd68 100644 --- a/src/widgets/settingspages/HighlightingPage.cpp +++ b/src/widgets/settingspages/HighlightingPage.cpp @@ -15,6 +15,7 @@ #include "util/LayoutCreator.hpp" #include "widgets/dialogs/BadgePickerDialog.hpp" #include "widgets/dialogs/ColorPickerDialog.hpp" +#include "widgets/helper/color/ColorItemDelegate.hpp" #include "widgets/helper/EditableModelView.hpp" #include @@ -82,6 +83,8 @@ HighlightingPage::HighlightingPage() QHeaderView::Fixed); view->getTableView()->horizontalHeader()->setSectionResizeMode( 0, QHeaderView::Stretch); + view->getTableView()->setItemDelegateForColumn( + HighlightModel::Column::Color, new ColorItemDelegate(view)); // fourtf: make class extrend BaseWidget and add this to // dpiChanged @@ -134,6 +137,9 @@ HighlightingPage::HighlightingPage() QHeaderView::Fixed); view->getTableView()->horizontalHeader()->setSectionResizeMode( 0, QHeaderView::Stretch); + view->getTableView()->setItemDelegateForColumn( + UserHighlightModel::Column::Color, + new ColorItemDelegate(view)); // fourtf: make class extrend BaseWidget and add this to // dpiChanged @@ -176,6 +182,9 @@ HighlightingPage::HighlightingPage() QHeaderView::Fixed); view->getTableView()->horizontalHeader()->setSectionResizeMode( 0, QHeaderView::Stretch); + view->getTableView()->setItemDelegateForColumn( + BadgeHighlightModel::Column::Color, + new ColorItemDelegate(view)); // fourtf: make class extrend BaseWidget and add this to // dpiChanged @@ -330,18 +339,18 @@ void HighlightingPage::openColorDialog(const QModelIndex &clicked, auto initial = view->getModel()->data(clicked, Qt::DecorationRole).value(); - auto dialog = new ColorPickerDialog(initial, this); - dialog->setAttribute(Qt::WA_DeleteOnClose); - dialog->show(); - // We can safely ignore this signal connection since the view and tab are never deleted + auto *dialog = new ColorPickerDialog(initial, this); // TODO: The QModelIndex clicked is technically not safe to persist here since the model // can be changed between the color dialog being created & the color dialog being closed - std::ignore = dialog->closed.connect([=](auto selected) { - if (selected.isValid()) - { - view->getModel()->setData(clicked, selected, Qt::DecorationRole); - } - }); + QObject::connect(dialog, &ColorPickerDialog::colorConfirmed, this, + [=](auto selected) { + if (selected.isValid()) + { + view->getModel()->setData(clicked, selected, + Qt::DecorationRole); + } + }); + dialog->show(); } void HighlightingPage::tableCellClicked(const QModelIndex &clicked, From 876612bc9a9addc6bfea3d1419613f13f21d79db Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sat, 30 Dec 2023 16:55:44 +0100 Subject: [PATCH 02/17] chore: add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1c04e5e75a..e51ad4a9a76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -104,6 +104,7 @@ - Dev: Refactor Args to be less of a singleton. (#5041) - Dev: Channels without any animated elements on screen will skip updates from the GIF timer. (#5042, #5043, #5045) - Dev: Autogenerate docs/plugin-meta.lua. (#5055) +- Dev: Improved color selection and display. (#5057) ## 2.4.6 From 0e1921164db8e13bcc5a4a97a3e93ec7a9dee55b Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sat, 30 Dec 2023 17:07:58 +0100 Subject: [PATCH 03/17] fix: pchfun1 --- src/widgets/dialogs/ColorPickerDialog.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/widgets/dialogs/ColorPickerDialog.cpp b/src/widgets/dialogs/ColorPickerDialog.cpp index 9a6e2dd40da..998a6938b7c 100644 --- a/src/widgets/dialogs/ColorPickerDialog.cpp +++ b/src/widgets/dialogs/ColorPickerDialog.cpp @@ -6,6 +6,8 @@ #include "widgets/helper/color/ColorDetails.hpp" #include "widgets/helper/color/ColorSelect.hpp" +#include + namespace { using namespace chatterino; From 7f1e14c4b068e2083a1c56cdacf17d3f8018a1d2 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sat, 30 Dec 2023 17:11:57 +0100 Subject: [PATCH 04/17] fix: pchfun2 --- src/widgets/dialogs/ColorPickerDialog.cpp | 1 + src/widgets/helper/color/AlphaSlider.cpp | 3 +++ src/widgets/helper/color/ColorButton.cpp | 2 ++ src/widgets/helper/color/ColorSelect.hpp | 4 ---- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/widgets/dialogs/ColorPickerDialog.cpp b/src/widgets/dialogs/ColorPickerDialog.cpp index 998a6938b7c..c9966c8937a 100644 --- a/src/widgets/dialogs/ColorPickerDialog.cpp +++ b/src/widgets/dialogs/ColorPickerDialog.cpp @@ -7,6 +7,7 @@ #include "widgets/helper/color/ColorSelect.hpp" #include +#include namespace { diff --git a/src/widgets/helper/color/AlphaSlider.cpp b/src/widgets/helper/color/AlphaSlider.cpp index d43e7280b49..9a1e9b413a0 100644 --- a/src/widgets/helper/color/AlphaSlider.cpp +++ b/src/widgets/helper/color/AlphaSlider.cpp @@ -2,6 +2,9 @@ #include "widgets/helper/color/Checkerboard.hpp" +#include +#include + namespace { constexpr int SLIDER_WIDTH = 256; diff --git a/src/widgets/helper/color/ColorButton.cpp b/src/widgets/helper/color/ColorButton.cpp index dd8332f815b..d51d9f27e24 100644 --- a/src/widgets/helper/color/ColorButton.cpp +++ b/src/widgets/helper/color/ColorButton.cpp @@ -2,6 +2,8 @@ #include "widgets/helper/color/Checkerboard.hpp" +#include + namespace chatterino { ColorButton::ColorButton(QColor color, QWidget *parent) diff --git a/src/widgets/helper/color/ColorSelect.hpp b/src/widgets/helper/color/ColorSelect.hpp index 744f132fce1..aa703ceebf1 100644 --- a/src/widgets/helper/color/ColorSelect.hpp +++ b/src/widgets/helper/color/ColorSelect.hpp @@ -7,10 +7,6 @@ #include #include -class SBCanvas; -class HueSlider; -class AlphaSlider; - namespace chatterino { class ColorSelect : public QWidget From 330fc21a5d48349d529d53958a7176ede1639922 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sat, 30 Dec 2023 17:25:47 +0100 Subject: [PATCH 05/17] fix: pchfun3 --- src/widgets/helper/color/ColorDetails.cpp | 23 ++++++++++++----------- src/widgets/helper/color/HueSlider.cpp | 3 +++ src/widgets/helper/color/SBCanvas.cpp | 3 +++ 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/widgets/helper/color/ColorDetails.cpp b/src/widgets/helper/color/ColorDetails.cpp index 268b7487800..9b038b9c417 100644 --- a/src/widgets/helper/color/ColorDetails.cpp +++ b/src/widgets/helper/color/ColorDetails.cpp @@ -60,17 +60,18 @@ ColorDetails::ColorDetails(QColor color, QWidget *parent) auto updateColor) { component.lbl.setText(label); component.box.setRange(0, 255); - QObject::connect(&component.box, &QSpinBox::valueChanged, - [this, &component, updateColor](int value) { - if (component.value == value) - { - return; - } - component.value = value; - updateColor(this->currentColor_, value); - this->updateCss(); - emit colorChanged(this->currentColor_); - }); + QObject::connect( + &component.box, &QSpinBox::valueChanged, + this, [this, &component, updateColor](int value) { + if (component.value == value) + { + return; + } + component.value = value; + updateColor(this->currentColor_, value); + this->updateCss(); + emit colorChanged(this->currentColor_); + }); this->layout_.addWidget(&component.lbl, row, 0); this->layout_.addWidget(&component.box, row, 1); row++; diff --git a/src/widgets/helper/color/HueSlider.cpp b/src/widgets/helper/color/HueSlider.cpp index ac393c7cc56..2a2dedb2465 100644 --- a/src/widgets/helper/color/HueSlider.cpp +++ b/src/widgets/helper/color/HueSlider.cpp @@ -1,5 +1,8 @@ #include "widgets/helper/color/HueSlider.hpp" +#include +#include + namespace { constexpr int SLIDER_WIDTH = 256; diff --git a/src/widgets/helper/color/SBCanvas.cpp b/src/widgets/helper/color/SBCanvas.cpp index ce6ce60bfe1..875f374a95b 100644 --- a/src/widgets/helper/color/SBCanvas.cpp +++ b/src/widgets/helper/color/SBCanvas.cpp @@ -1,5 +1,8 @@ #include "widgets/helper/color/SBCanvas.hpp" +#include +#include + namespace { constexpr int PICKER_WIDTH = 256; From 451b36c74205f2258a1fbd37425ba2451d82802d Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sat, 30 Dec 2023 17:28:24 +0100 Subject: [PATCH 06/17] fix: format-on-save btw --- src/widgets/helper/color/ColorDetails.cpp | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/widgets/helper/color/ColorDetails.cpp b/src/widgets/helper/color/ColorDetails.cpp index 9b038b9c417..e4c7db4a485 100644 --- a/src/widgets/helper/color/ColorDetails.cpp +++ b/src/widgets/helper/color/ColorDetails.cpp @@ -60,18 +60,17 @@ ColorDetails::ColorDetails(QColor color, QWidget *parent) auto updateColor) { component.lbl.setText(label); component.box.setRange(0, 255); - QObject::connect( - &component.box, &QSpinBox::valueChanged, - this, [this, &component, updateColor](int value) { - if (component.value == value) - { - return; - } - component.value = value; - updateColor(this->currentColor_, value); - this->updateCss(); - emit colorChanged(this->currentColor_); - }); + QObject::connect(&component.box, &QSpinBox::valueChanged, this, + [this, &component, updateColor](int value) { + if (component.value == value) + { + return; + } + component.value = value; + updateColor(this->currentColor_, value); + this->updateCss(); + emit colorChanged(this->currentColor_); + }); this->layout_.addWidget(&component.lbl, row, 0); this->layout_.addWidget(&component.box, row, 1); row++; From 3baf1d1f02c6592fe597d77118d8630cd8fb7060 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sat, 30 Dec 2023 17:41:24 +0100 Subject: [PATCH 07/17] fix: qt 5.12.12 please drop support now --- src/widgets/helper/color/ColorDetails.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/widgets/helper/color/ColorDetails.cpp b/src/widgets/helper/color/ColorDetails.cpp index e4c7db4a485..4f5bdd91432 100644 --- a/src/widgets/helper/color/ColorDetails.cpp +++ b/src/widgets/helper/color/ColorDetails.cpp @@ -60,7 +60,8 @@ ColorDetails::ColorDetails(QColor color, QWidget *parent) auto updateColor) { component.lbl.setText(label); component.box.setRange(0, 255); - QObject::connect(&component.box, &QSpinBox::valueChanged, this, + QObject::connect(&component.box, + qOverload(&QSpinBox::valueChanged), this, [this, &component, updateColor](int value) { if (component.value == value) { From dbc67104a720bececdd006916b6e9189670dddcb Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sun, 31 Dec 2023 11:28:01 +0100 Subject: [PATCH 08/17] fix: add label --- src/widgets/dialogs/ColorPickerDialog.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/widgets/dialogs/ColorPickerDialog.cpp b/src/widgets/dialogs/ColorPickerDialog.cpp index c9966c8937a..e44ce4ebcd3 100644 --- a/src/widgets/dialogs/ColorPickerDialog.cpp +++ b/src/widgets/dialogs/ColorPickerDialog.cpp @@ -69,6 +69,7 @@ ColorPickerDialog::ColorPickerDialog(QColor color, QWidget *parent) makeColorGrid(ColorProvider::instance().defaultColors(), this)); buttons->addStretch(1); + buttons->addWidget(new QLabel(u"Selected"_s)); auto *display = new ColorButton(this->color()); QObject::connect(this, &ColorPickerDialog::colorChanged, display, &ColorButton::setColor); From eeff8f2fd828c1ad0165e7ee6a8f7b50ff9b3c6f Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sun, 31 Dec 2023 11:29:33 +0100 Subject: [PATCH 09/17] fix: show solid color in circle --- src/widgets/helper/color/AlphaSlider.cpp | 4 +++- src/widgets/helper/color/HueSlider.cpp | 2 +- src/widgets/helper/color/SBCanvas.cpp | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/widgets/helper/color/AlphaSlider.cpp b/src/widgets/helper/color/AlphaSlider.cpp index 9a1e9b413a0..0e566635294 100644 --- a/src/widgets/helper/color/AlphaSlider.cpp +++ b/src/widgets/helper/color/AlphaSlider.cpp @@ -141,7 +141,9 @@ void AlphaSlider::paintEvent(QPaintEvent * /*event*/) cornerRadius}; auto circleColor = 0; painter.setPen({QColor(circleColor, circleColor, circleColor), 2}); - painter.setBrush(Qt::transparent); + auto opaqueBase = this->baseColor_; + opaqueBase.setAlpha(255); + painter.setBrush(opaqueBase); painter.drawEllipse(circ, cornerRadius - 1, cornerRadius - 1); } diff --git a/src/widgets/helper/color/HueSlider.cpp b/src/widgets/helper/color/HueSlider.cpp index 2a2dedb2465..dc9a5721c20 100644 --- a/src/widgets/helper/color/HueSlider.cpp +++ b/src/widgets/helper/color/HueSlider.cpp @@ -131,7 +131,7 @@ void HueSlider::paintEvent(QPaintEvent * /*event*/) cornerRadius}; auto circleColor = 0; painter.setPen({QColor(circleColor, circleColor, circleColor), 2}); - painter.setBrush(Qt::transparent); + painter.setBrush(QColor::fromHsv(this->hue(), 255, 255)); painter.drawEllipse(circ, cornerRadius - 1, cornerRadius - 1); } diff --git a/src/widgets/helper/color/SBCanvas.cpp b/src/widgets/helper/color/SBCanvas.cpp index 875f374a95b..0560569b781 100644 --- a/src/widgets/helper/color/SBCanvas.cpp +++ b/src/widgets/helper/color/SBCanvas.cpp @@ -138,7 +138,8 @@ void SBCanvas::paintEvent(QPaintEvent * /*event*/) ((255 - this->brightness()) * this->height()) / 256}; auto circleColor = this->brightness() >= 128 ? 50 : 200; painter.setPen({QColor(circleColor, circleColor, circleColor), 2}); - painter.setBrush(Qt::transparent); + painter.setBrush( + QColor::fromHsv(this->hue_, this->saturation_, this->brightness_)); painter.drawEllipse(circ, 5, 5); } From d99c1ce6f5248c714496f36fed55419fa2bd4579 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sun, 31 Dec 2023 16:15:35 +0100 Subject: [PATCH 10/17] fix: suggestions --- src/CMakeLists.txt | 6 +- src/widgets/dialogs/ColorPickerDialog.cpp | 50 +++++++---- src/widgets/helper/color/AlphaSlider.cpp | 22 ++--- src/widgets/helper/color/AlphaSlider.hpp | 9 +- src/widgets/helper/color/ColorButton.hpp | 1 - .../{ColorDetails.cpp => ColorInput.cpp} | 62 +++++++------ .../{ColorDetails.hpp => ColorInput.hpp} | 17 ++-- src/widgets/helper/color/ColorSelect.cpp | 86 ------------------- src/widgets/helper/color/ColorSelect.hpp | 46 ---------- src/widgets/helper/color/HueSlider.cpp | 28 ++++-- src/widgets/helper/color/HueSlider.hpp | 8 +- src/widgets/helper/color/SBCanvas.cpp | 68 ++++++++++----- src/widgets/helper/color/SBCanvas.hpp | 21 ++--- 13 files changed, 182 insertions(+), 242 deletions(-) rename src/widgets/helper/color/{ColorDetails.cpp => ColorInput.cpp} (68%) rename src/widgets/helper/color/{ColorDetails.hpp => ColorInput.hpp} (62%) delete mode 100644 src/widgets/helper/color/ColorSelect.cpp delete mode 100644 src/widgets/helper/color/ColorSelect.hpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index af02d56dbac..2b83ee2a805 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -596,12 +596,10 @@ set(SOURCE_FILES widgets/helper/color/Checkerboard.hpp widgets/helper/color/ColorButton.cpp widgets/helper/color/ColorButton.hpp - widgets/helper/color/ColorDetails.cpp - widgets/helper/color/ColorDetails.hpp + widgets/helper/color/ColorInput.cpp + widgets/helper/color/ColorInput.hpp widgets/helper/color/ColorItemDelegate.cpp widgets/helper/color/ColorItemDelegate.hpp - widgets/helper/color/ColorSelect.cpp - widgets/helper/color/ColorSelect.hpp widgets/helper/color/HueSlider.cpp widgets/helper/color/HueSlider.hpp widgets/helper/color/SBCanvas.cpp diff --git a/src/widgets/dialogs/ColorPickerDialog.cpp b/src/widgets/dialogs/ColorPickerDialog.cpp index e44ce4ebcd3..81c25c91ac2 100644 --- a/src/widgets/dialogs/ColorPickerDialog.cpp +++ b/src/widgets/dialogs/ColorPickerDialog.cpp @@ -2,9 +2,11 @@ #include "common/Literals.hpp" #include "providers/colors/ColorProvider.hpp" +#include "widgets/helper/color/AlphaSlider.hpp" #include "widgets/helper/color/ColorButton.hpp" -#include "widgets/helper/color/ColorDetails.hpp" -#include "widgets/helper/color/ColorSelect.hpp" +#include "widgets/helper/color/ColorInput.hpp" +#include "widgets/helper/color/HueSlider.hpp" +#include "widgets/helper/color/SBCanvas.hpp" #include #include @@ -35,6 +37,16 @@ QGridLayout *makeColorGrid(const auto &items, auto *self) return layout; } +/// All color inputs have the same two signals and slots: +/// `colorChanged` and `setColor`. +/// `colorChanged` is emitted when the user changed the color (not after calling `setColor`). +template +void connectSignals(D *dialog, W *widget) +{ + QObject::connect(widget, &W::colorChanged, dialog, &D::setColor); + QObject::connect(dialog, &D::colorChanged, widget, &W::setColor); +} + } // namespace namespace chatterino { @@ -82,20 +94,28 @@ ColorPickerDialog::ColorPickerDialog(QColor color, QWidget *parent) { auto *controls = new QVBoxLayout; - auto *colorSelect = new ColorSelect(this->color()); - auto *colorDetails = new ColorDetails(this->color()); + { + auto *select = new QVBoxLayout; + + auto *sbCanvas = new SBCanvas(this->color()); + auto *hueSlider = new HueSlider(this->color()); + auto *alphaSlider = new AlphaSlider(this->color()); + + connectSignals(this, sbCanvas); + connectSignals(this, hueSlider); + connectSignals(this, alphaSlider); - QObject::connect(colorSelect, &ColorSelect::colorChanged, this, - &ColorPickerDialog::setColor); - QObject::connect(colorDetails, &ColorDetails::colorChanged, this, - &ColorPickerDialog::setColor); - QObject::connect(this, &ColorPickerDialog::colorChanged, colorSelect, - &ColorSelect::setColor); - QObject::connect(this, &ColorPickerDialog::colorChanged, colorDetails, - &ColorDetails::setColor); + select->addWidget(sbCanvas, 0, Qt::AlignHCenter); + select->addWidget(hueSlider); + select->addWidget(alphaSlider); - controls->addWidget(colorSelect); - controls->addWidget(colorDetails); + controls->addLayout(select); + } + { + auto *input = new ColorInput(this->color()); + connectSignals(this, input); + controls->addWidget(input); + } dialogContents->addLayout(controls); } @@ -112,7 +132,7 @@ ColorPickerDialog::ColorPickerDialog(QColor color, QWidget *parent) this->close(); }); QObject::connect(buttonBox, &QDialogButtonBox::rejected, this, - &ColorSelect::close); + &ColorPickerDialog::close); dialogLayout->addWidget(buttonBox, 0, Qt::AlignRight); } diff --git a/src/widgets/helper/color/AlphaSlider.cpp b/src/widgets/helper/color/AlphaSlider.cpp index 0e566635294..75d159e77bc 100644 --- a/src/widgets/helper/color/AlphaSlider.cpp +++ b/src/widgets/helper/color/AlphaSlider.cpp @@ -17,21 +17,21 @@ namespace chatterino { AlphaSlider::AlphaSlider(QColor color, QWidget *parent) : QWidget(parent) , alpha_(color.alpha()) - , baseColor_(color) + , color_(color) { this->setSizePolicy({QSizePolicy::Expanding, QSizePolicy::Fixed}); } -void AlphaSlider::updateColor(const QColor &color) +void AlphaSlider::setColor(QColor color) { - if (this->baseColor_ == color) + if (this->color_ == color) { return; } this->alpha_ = color.alpha(); - this->baseColor_ = color; - this->updatePixmap(); - this->repaint(); + this->color_ = color; + this->cachedPixmap_ = {}; + this->update(); } int AlphaSlider::alpha() const @@ -109,8 +109,8 @@ void AlphaSlider::updatePixmap() QLinearGradient gradient(cornerRadius, 0.0, (qreal)this->width() - cornerRadius, 0.0); - QColor start = this->baseColor_; - QColor end = this->baseColor_; + QColor start = this->color_; + QColor end = this->color_; start.setAlpha(0); end.setAlpha(255); @@ -141,7 +141,7 @@ void AlphaSlider::paintEvent(QPaintEvent * /*event*/) cornerRadius}; auto circleColor = 0; painter.setPen({QColor(circleColor, circleColor, circleColor), 2}); - auto opaqueBase = this->baseColor_; + auto opaqueBase = this->color_; opaqueBase.setAlpha(255); painter.setBrush(opaqueBase); painter.drawEllipse(circ, cornerRadius - 1, cornerRadius - 1); @@ -154,8 +154,10 @@ void AlphaSlider::setAlpha(int alpha) return; } this->alpha_ = alpha; + this->color_.setAlpha(alpha); + + emit this->colorChanged(this->color_); this->update(); - emit alphaChanged(alpha); } } // namespace chatterino diff --git a/src/widgets/helper/color/AlphaSlider.hpp b/src/widgets/helper/color/AlphaSlider.hpp index c5528700827..9a7d3fe4a9b 100644 --- a/src/widgets/helper/color/AlphaSlider.hpp +++ b/src/widgets/helper/color/AlphaSlider.hpp @@ -7,18 +7,19 @@ namespace chatterino { class AlphaSlider : public QWidget { Q_OBJECT - Q_PROPERTY(int alpha READ alpha NOTIFY alphaChanged) public: AlphaSlider(QColor color = {}, QWidget *parent = nullptr); - void updateColor(const QColor &color); QSize sizeHint() const override; int alpha() const; signals: - void alphaChanged(int hue) const; + void colorChanged(QColor color) const; + +public slots: + void setColor(QColor color); protected: void resizeEvent(QResizeEvent *event) override; @@ -30,7 +31,7 @@ class AlphaSlider : public QWidget private: int alpha_ = 255; - QColor baseColor_; + QColor color_; QPixmap cachedPixmap_; diff --git a/src/widgets/helper/color/ColorButton.hpp b/src/widgets/helper/color/ColorButton.hpp index 3601e4940a5..5bec01ec279 100644 --- a/src/widgets/helper/color/ColorButton.hpp +++ b/src/widgets/helper/color/ColorButton.hpp @@ -7,7 +7,6 @@ namespace chatterino { class ColorButton : public QAbstractButton { Q_OBJECT - Q_PROPERTY(QColor color READ color WRITE setColor) public: ColorButton(QColor color = {}, QWidget *parent = nullptr); diff --git a/src/widgets/helper/color/ColorDetails.cpp b/src/widgets/helper/color/ColorInput.cpp similarity index 68% rename from src/widgets/helper/color/ColorDetails.cpp rename to src/widgets/helper/color/ColorInput.cpp index 4f5bdd91432..63220644a12 100644 --- a/src/widgets/helper/color/ColorDetails.cpp +++ b/src/widgets/helper/color/ColorInput.cpp @@ -1,4 +1,4 @@ -#include "widgets/helper/color/ColorDetails.hpp" +#include "widgets/helper/color/ColorInput.hpp" namespace { @@ -21,7 +21,7 @@ int fromHex(char c) noexcept return -1; } -QColor parseCssColor(const QString &text) +QColor parseHexColor(const QString &text) { if (text.length() == 5) // #rgba { @@ -48,29 +48,28 @@ QColor parseCssColor(const QString &text) namespace chatterino { -ColorDetails::ColorDetails(QColor color, QWidget *parent) +ColorInput::ColorInput(QColor color, QWidget *parent) : QWidget(parent) , currentColor_(color) - , cssValidator_(QRegularExpression( + , 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 updateColor) { + auto applyToColor) { component.lbl.setText(label); component.box.setRange(0, 255); QObject::connect(&component.box, qOverload(&QSpinBox::valueChanged), this, - [this, &component, updateColor](int value) { + [this, &component, applyToColor](int value) { if (component.value == value) { return; } - component.value = value; - updateColor(this->currentColor_, value); - this->updateCss(); - emit colorChanged(this->currentColor_); + applyToColor(this->currentColor_, value); + + this->emitUpdate(); }); this->layout_.addWidget(&component.lbl, row, 0); this->layout_.addWidget(&component.box, row, 1); @@ -90,23 +89,24 @@ ColorDetails::ColorDetails(QColor color, QWidget *parent) color.setAlpha(value); }); - this->cssLabel_.setText("CSS:"); - this->cssInput_.setValidator(&this->cssValidator_); - QObject::connect(&this->cssInput_, &QLineEdit::editingFinished, [this]() { - auto css = parseCssColor(this->cssInput_.text()); + 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->setColor(css); + this->currentColor_ = css; + this->emitUpdate(); }); - this->layout_.addWidget(&this->cssLabel_, row, 0); - this->layout_.addWidget(&this->cssInput_, row, 1); + this->layout_.addWidget(&this->hexLabel_, row, 0); + this->layout_.addWidget(&this->hexInput_, row, 1); this->updateComponents(); } -void ColorDetails::updateComponents() +void ColorInput::updateComponents() { auto color = this->currentColor_.toRgb(); const auto updateComponent = [](Component &component, auto getValue) { @@ -130,31 +130,39 @@ void ColorDetails::updateComponents() return color.alpha(); }); - this->updateCss(); + this->updateHex(); } -void ColorDetails::updateCss() +void ColorInput::updateHex() { auto rgb = this->currentColor_.rgb(); rgb <<= 8; rgb |= this->currentColor_.alpha(); // we always need to update the CSS color - this->cssInput_.setText(QStringLiteral("#%1").arg(rgb, 8, 16, QChar(u'0'))); + this->hexInput_.setText(QStringLiteral("#%1").arg(rgb, 8, 16, QChar(u'0'))); } -QColor ColorDetails::color() const +QColor ColorInput::color() const { return this->currentColor_; } -void ColorDetails::setColor(const QColor &color) +void ColorInput::setColor(QColor color) { - if (this->currentColor_ != color) + if (this->currentColor_ == color) { - this->currentColor_ = color; - this->updateComponents(); - emit colorChanged(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 diff --git a/src/widgets/helper/color/ColorDetails.hpp b/src/widgets/helper/color/ColorInput.hpp similarity index 62% rename from src/widgets/helper/color/ColorDetails.hpp rename to src/widgets/helper/color/ColorInput.hpp index 4ee9fb7489f..bc80384ff38 100644 --- a/src/widgets/helper/color/ColorDetails.hpp +++ b/src/widgets/helper/color/ColorInput.hpp @@ -8,13 +8,12 @@ namespace chatterino { -class ColorDetails : public QWidget +class ColorInput : public QWidget { Q_OBJECT - Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) public: - ColorDetails(QColor color = {}, QWidget *parent = nullptr); + ColorInput(QColor color = {}, QWidget *parent = nullptr); QColor color() const; @@ -22,7 +21,7 @@ class ColorDetails : public QWidget void colorChanged(QColor color); public slots: - void setColor(const QColor &color); + void setColor(QColor color); private: QColor currentColor_; @@ -38,14 +37,16 @@ public slots: Component blue_; Component alpha_; - QLabel cssLabel_; - QLineEdit cssInput_; - QRegularExpressionValidator cssValidator_; + QLabel hexLabel_; + QLineEdit hexInput_; + QRegularExpressionValidator hexValidator_; QGridLayout layout_; void updateComponents(); - void updateCss(); + void updateHex(); + + void emitUpdate(); }; } // namespace chatterino diff --git a/src/widgets/helper/color/ColorSelect.cpp b/src/widgets/helper/color/ColorSelect.cpp deleted file mode 100644 index 70aca7bbc2e..00000000000 --- a/src/widgets/helper/color/ColorSelect.cpp +++ /dev/null @@ -1,86 +0,0 @@ -#include "widgets/helper/color/ColorSelect.hpp" - -namespace chatterino { - -ColorSelect::ColorSelect(QColor color, QWidget *parent) - : QWidget(parent) - , currentColor_(color) - , sbCanvas_(color) - , hueSlider_(color) - , alphaSlider_(color) - -{ - color.getHsv(&this->hue_, &this->saturation_, &this->brightness_); - - this->rows_.addWidget(&this->sbCanvas_, 0, Qt::AlignHCenter); - this->rows_.addWidget(&this->hueSlider_); - this->rows_.addWidget(&this->alphaSlider_); - this->setLayout(&this->rows_); - this->connectWidgets(); -} - -void ColorSelect::connectWidgets() -{ - const auto applyColor = [this]() { - emit colorChanged(this->currentColor_); - }; - - const auto updateCurrentColor = [this, applyColor]() { - this->currentColor_ = - QColor::fromHsv(this->hue_, this->saturation_, this->brightness_, - this->currentColor_.alpha()); - applyColor(); - }; - - QObject::connect(&this->hueSlider_, &HueSlider::hueChanged, - [this, updateCurrentColor](int hue) { - this->hue_ = hue; - this->sbCanvas_.setHue(hue); - updateCurrentColor(); - this->alphaSlider_.updateColor(this->currentColor_); - }); - QObject::connect(&this->alphaSlider_, &AlphaSlider::alphaChanged, - [this, applyColor](int alpha) { - this->currentColor_.setAlpha(alpha); - applyColor(); - }); - const auto updateAfterSbChanged = [this, updateCurrentColor]() { - updateCurrentColor(); - this->alphaSlider_.updateColor(this->currentColor_); - }; - QObject::connect(&this->sbCanvas_, &SBCanvas::saturationChanged, - [this, updateAfterSbChanged](int saturation) { - this->saturation_ = saturation; - updateAfterSbChanged(); - }); - QObject::connect(&this->sbCanvas_, &SBCanvas::brightnessChanged, - [this, updateAfterSbChanged](int brightness) { - this->brightness_ = brightness; - updateAfterSbChanged(); - }); -} - -QColor ColorSelect::color() const -{ - return this->currentColor_; -} - -void ColorSelect::setColor(const QColor &color) -{ - if (this->currentColor_ == color) - { - return; - } - this->currentColor_ = color; - color.getHsv(&this->hue_, &this->saturation_, &this->brightness_); - if (this->hue_ < 0) - { - this->hue_ = 0; - } - - this->sbCanvas_.updateColor(color); - this->hueSlider_.updateColor(color); - this->alphaSlider_.updateColor(color); -} - -} // namespace chatterino diff --git a/src/widgets/helper/color/ColorSelect.hpp b/src/widgets/helper/color/ColorSelect.hpp deleted file mode 100644 index aa703ceebf1..00000000000 --- a/src/widgets/helper/color/ColorSelect.hpp +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -#include "widgets/helper/color/AlphaSlider.hpp" -#include "widgets/helper/color/HueSlider.hpp" -#include "widgets/helper/color/SBCanvas.hpp" - -#include -#include - -namespace chatterino { - -class ColorSelect : public QWidget -{ - Q_OBJECT - Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) - -public: - ColorSelect(QColor color = {}, QWidget *parent = nullptr); - - QColor color() const; - -signals: - void colorChanged(QColor color); - -public slots: - void setColor(const QColor &color); - -private: - QColor currentColor_; - int hue_ = 0; - int saturation_ = 0; - int brightness_ = 0; - - SBCanvas sbCanvas_; - HueSlider hueSlider_; - AlphaSlider alphaSlider_; - QVBoxLayout rows_; - - void connectWidgets(); - -signals: - void pickerColorChanged(QColor color); - void detailsColorChanged(QColor color); -}; - -} // namespace chatterino diff --git a/src/widgets/helper/color/HueSlider.cpp b/src/widgets/helper/color/HueSlider.cpp index dc9a5721c20..75a91bb4d15 100644 --- a/src/widgets/helper/color/HueSlider.cpp +++ b/src/widgets/helper/color/HueSlider.cpp @@ -15,17 +15,25 @@ namespace chatterino { HueSlider::HueSlider(QColor color, QWidget *parent) : QWidget(parent) { - this->updateColor(color); + this->setColor(color); this->setSizePolicy({QSizePolicy::Expanding, QSizePolicy::Fixed}); } -void HueSlider::updateColor(const QColor &color) +void HueSlider::setColor(QColor color) { - this->hue_ = color.hue(); - if (this->hue_ < 0) + if (this->color_ == color) { - this->hue_ = 0; + return; + } + this->color_ = color.toHsv(); + + auto hue = std::max(this->color_.hue(), 0); + if (this->hue_ == hue) + { + return; } + + this->hue_ = hue; this->update(); } @@ -142,8 +150,16 @@ void HueSlider::setHue(int 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(); - emit hueChanged(hue); } } // namespace chatterino diff --git a/src/widgets/helper/color/HueSlider.hpp b/src/widgets/helper/color/HueSlider.hpp index 27886c78b17..c874e223c91 100644 --- a/src/widgets/helper/color/HueSlider.hpp +++ b/src/widgets/helper/color/HueSlider.hpp @@ -7,18 +7,19 @@ namespace chatterino { class HueSlider : public QWidget { Q_OBJECT - Q_PROPERTY(int hue READ hue NOTIFY hueChanged) public: HueSlider(QColor color = {}, QWidget *parent = nullptr); - void updateColor(const QColor &color); QSize sizeHint() const override; int hue() const; signals: - void hueChanged(int hue) const; + void colorChanged(QColor color) const; + +public slots: + void setColor(QColor color); protected: void resizeEvent(QResizeEvent *event) override; @@ -30,6 +31,7 @@ class HueSlider : public QWidget private: int hue_ = 0; + QColor color_; QPixmap gradientPixmap_; diff --git a/src/widgets/helper/color/SBCanvas.cpp b/src/widgets/helper/color/SBCanvas.cpp index 0560569b781..78dbef05f42 100644 --- a/src/widgets/helper/color/SBCanvas.cpp +++ b/src/widgets/helper/color/SBCanvas.cpp @@ -15,25 +15,35 @@ namespace chatterino { SBCanvas::SBCanvas(QColor color, QWidget *parent) : QWidget(parent) { - this->updateColor(color); + this->setColor(color); this->setSizePolicy({QSizePolicy::Fixed, QSizePolicy::Fixed}); } -void SBCanvas::updateColor(const QColor &color) +void SBCanvas::setColor(QColor color) { - color.getHsv(&this->hue_, &this->saturation_, &this->brightness_); - if (this->hue_ < 0) + color = color.toHsv(); + if (this->color_ == color) { - this->hue_ = 0; + return; } - this->update(); -} + this->color_ = color; -void SBCanvas::setHue(int hue) -{ - this->hue_ = hue; - this->updatePixmap(); - this->repaint(); + 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 @@ -100,8 +110,15 @@ 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()); - this->setSaturation(this->xPosToSaturation(clampedX)); - this->setBrightness(this->yPosToBrightness(clampedY)); + + bool updated = this->setSaturation(this->xPosToSaturation(clampedX)); + updated |= this->setBrightness(this->yPosToBrightness(clampedY)); + + if (updated) + { + this->emitUpdatedColor(); + this->update(); + } } void SBCanvas::updatePixmap() @@ -143,26 +160,33 @@ void SBCanvas::paintEvent(QPaintEvent * /*event*/) painter.drawEllipse(circ, 5, 5); } -void SBCanvas::setSaturation(int saturation) +bool SBCanvas::setSaturation(int saturation) { if (this->saturation_ == saturation) { - return; + return false; } + this->saturation_ = saturation; - this->update(); - emit saturationChanged(saturation); + return true; } -void SBCanvas::setBrightness(int brightness) +bool SBCanvas::setBrightness(int brightness) { if (this->brightness_ == brightness) { - return; + return false; } + this->brightness_ = brightness; - this->update(); - emit brightnessChanged(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 diff --git a/src/widgets/helper/color/SBCanvas.hpp b/src/widgets/helper/color/SBCanvas.hpp index f76cf149df5..a0c6b3f670a 100644 --- a/src/widgets/helper/color/SBCanvas.hpp +++ b/src/widgets/helper/color/SBCanvas.hpp @@ -4,26 +4,24 @@ namespace chatterino { +/// 2D canvas for saturation (x-axis) and brightness (y-axis) class SBCanvas : public QWidget { Q_OBJECT - Q_PROPERTY(int saturation READ saturation NOTIFY saturationChanged) - Q_PROPERTY(int brightness READ brightness NOTIFY brightnessChanged) public: SBCanvas(QColor color = {}, QWidget *parent = nullptr); - void updateColor(const QColor &color); - void setHue(int hue); QSize sizeHint() const override; + int saturation() const; + int brightness() const; + signals: - void saturationChanged(int saturation) const; - void brightnessChanged(int brightness) const; + void colorChanged(QColor color) const; public slots: - int saturation() const; - int brightness() const; + void setColor(QColor color); protected: void resizeEvent(QResizeEvent *event) override; @@ -37,6 +35,7 @@ public slots: int hue_ = 0; int saturation_ = 0; int brightness_ = 0; + QColor color_; QPixmap gradientPixmap_; @@ -48,8 +47,10 @@ public slots: void updateFromEvent(QMouseEvent *event); - void setSaturation(int saturation); - void setBrightness(int brightness); + [[nodiscard]] bool setSaturation(int saturation); + [[nodiscard]] bool setBrightness(int brightness); + + void emitUpdatedColor(); }; } // namespace chatterino From 96cdea1afe6924fb6cf6ab497bd20c052c72fe10 Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 6 Jan 2024 20:17:44 +0100 Subject: [PATCH 11/17] nit: Remove unused include QSet from ColorPickerDialog --- src/widgets/dialogs/ColorPickerDialog.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/widgets/dialogs/ColorPickerDialog.cpp b/src/widgets/dialogs/ColorPickerDialog.cpp index 81c25c91ac2..6dd61962f94 100644 --- a/src/widgets/dialogs/ColorPickerDialog.cpp +++ b/src/widgets/dialogs/ColorPickerDialog.cpp @@ -9,7 +9,6 @@ #include "widgets/helper/color/SBCanvas.hpp" #include -#include namespace { From 1bc0aa3f70ce83866ea30b3805ca23d9f6aba766 Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 6 Jan 2024 20:22:04 +0100 Subject: [PATCH 12/17] nit: Remove Q_PROPERTY usage --- src/widgets/dialogs/ColorPickerDialog.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/widgets/dialogs/ColorPickerDialog.hpp b/src/widgets/dialogs/ColorPickerDialog.hpp index a1bc2a7085b..12ac5b11860 100644 --- a/src/widgets/dialogs/ColorPickerDialog.hpp +++ b/src/widgets/dialogs/ColorPickerDialog.hpp @@ -7,7 +7,6 @@ namespace chatterino { class ColorPickerDialog : public BasePopup { Q_OBJECT - Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) public: ColorPickerDialog(QColor color = {}, QWidget *parent = nullptr); From 5d0c59593fd27aced59b3a101158bf766f5b6e81 Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 6 Jan 2024 20:27:34 +0100 Subject: [PATCH 13/17] nit: remove some default params --- src/widgets/dialogs/ColorPickerDialog.hpp | 2 +- src/widgets/helper/color/AlphaSlider.hpp | 2 +- src/widgets/helper/color/ColorButton.hpp | 2 +- src/widgets/helper/color/ColorInput.hpp | 2 +- src/widgets/helper/color/HueSlider.hpp | 2 +- src/widgets/helper/color/SBCanvas.hpp | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/widgets/dialogs/ColorPickerDialog.hpp b/src/widgets/dialogs/ColorPickerDialog.hpp index 12ac5b11860..d9c896e943a 100644 --- a/src/widgets/dialogs/ColorPickerDialog.hpp +++ b/src/widgets/dialogs/ColorPickerDialog.hpp @@ -9,7 +9,7 @@ class ColorPickerDialog : public BasePopup Q_OBJECT public: - ColorPickerDialog(QColor color = {}, QWidget *parent = nullptr); + ColorPickerDialog(QColor color, QWidget *parent); QColor color() const; diff --git a/src/widgets/helper/color/AlphaSlider.hpp b/src/widgets/helper/color/AlphaSlider.hpp index 9a7d3fe4a9b..6a78990d5ff 100644 --- a/src/widgets/helper/color/AlphaSlider.hpp +++ b/src/widgets/helper/color/AlphaSlider.hpp @@ -9,7 +9,7 @@ class AlphaSlider : public QWidget Q_OBJECT public: - AlphaSlider(QColor color = {}, QWidget *parent = nullptr); + AlphaSlider(QColor color, QWidget *parent = nullptr); QSize sizeHint() const override; diff --git a/src/widgets/helper/color/ColorButton.hpp b/src/widgets/helper/color/ColorButton.hpp index 5bec01ec279..33f55c98eba 100644 --- a/src/widgets/helper/color/ColorButton.hpp +++ b/src/widgets/helper/color/ColorButton.hpp @@ -9,7 +9,7 @@ class ColorButton : public QAbstractButton Q_OBJECT public: - ColorButton(QColor color = {}, QWidget *parent = nullptr); + ColorButton(QColor color, QWidget *parent = nullptr); QSize sizeHint() const override; diff --git a/src/widgets/helper/color/ColorInput.hpp b/src/widgets/helper/color/ColorInput.hpp index bc80384ff38..a654403ef5e 100644 --- a/src/widgets/helper/color/ColorInput.hpp +++ b/src/widgets/helper/color/ColorInput.hpp @@ -13,7 +13,7 @@ class ColorInput : public QWidget Q_OBJECT public: - ColorInput(QColor color = {}, QWidget *parent = nullptr); + ColorInput(QColor color, QWidget *parent = nullptr); QColor color() const; diff --git a/src/widgets/helper/color/HueSlider.hpp b/src/widgets/helper/color/HueSlider.hpp index c874e223c91..90c12f34fe0 100644 --- a/src/widgets/helper/color/HueSlider.hpp +++ b/src/widgets/helper/color/HueSlider.hpp @@ -9,7 +9,7 @@ class HueSlider : public QWidget Q_OBJECT public: - HueSlider(QColor color = {}, QWidget *parent = nullptr); + HueSlider(QColor color, QWidget *parent = nullptr); QSize sizeHint() const override; diff --git a/src/widgets/helper/color/SBCanvas.hpp b/src/widgets/helper/color/SBCanvas.hpp index a0c6b3f670a..6a64d82bf32 100644 --- a/src/widgets/helper/color/SBCanvas.hpp +++ b/src/widgets/helper/color/SBCanvas.hpp @@ -10,7 +10,7 @@ class SBCanvas : public QWidget Q_OBJECT public: - SBCanvas(QColor color = {}, QWidget *parent = nullptr); + SBCanvas(QColor color, QWidget *parent = nullptr); QSize sizeHint() const override; From 37395bff878af65a169af13e26f9d5cfde689885 Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 6 Jan 2024 20:41:32 +0100 Subject: [PATCH 14/17] Add a param to makeColorGrid to limit the amount of buttons it creates --- src/widgets/dialogs/ColorPickerDialog.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/widgets/dialogs/ColorPickerDialog.cpp b/src/widgets/dialogs/ColorPickerDialog.cpp index 6dd61962f94..7d9e5e30b51 100644 --- a/src/widgets/dialogs/ColorPickerDialog.cpp +++ b/src/widgets/dialogs/ColorPickerDialog.cpp @@ -15,8 +15,11 @@ namespace { using namespace chatterino; constexpr size_t COLORS_PER_ROW = 5; +constexpr size_t MAX_RECENT_COLORS = 15; +constexpr size_t MAX_DEFAULT_COLORS = 15; -QGridLayout *makeColorGrid(const auto &items, auto *self) +QGridLayout *makeColorGrid(const auto &items, auto *self, + std::size_t maxButtons) { auto *layout = new QGridLayout; @@ -32,6 +35,10 @@ QGridLayout *makeColorGrid(const auto &items, auto *self) layout->addWidget(button, static_cast(i / COLORS_PER_ROW), static_cast(i % COLORS_PER_ROW)); i++; + if (i >= maxButtons) + { + break; + } } return layout; } @@ -70,14 +77,15 @@ ColorPickerDialog::ColorPickerDialog(QColor color, QWidget *parent) { auto *buttons = new QVBoxLayout; buttons->addWidget(new QLabel(u"Recently used"_s)); - buttons->addLayout( - makeColorGrid(ColorProvider::instance().recentColors(), this)); + buttons->addLayout(makeColorGrid( + ColorProvider::instance().recentColors(), this, MAX_RECENT_COLORS)); buttons->addSpacing(10); buttons->addWidget(new QLabel(u"Default colors"_s)); buttons->addLayout( - makeColorGrid(ColorProvider::instance().defaultColors(), this)); + makeColorGrid(ColorProvider::instance().defaultColors(), this, + MAX_DEFAULT_COLORS)); buttons->addStretch(1); buttons->addWidget(new QLabel(u"Selected"_s)); From d34244e0be2ec008282832a6a01b79fffcc826c6 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sat, 6 Jan 2024 21:10:22 +0100 Subject: [PATCH 15/17] fix: add check --- src/widgets/helper/color/ColorButton.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/widgets/helper/color/ColorButton.cpp b/src/widgets/helper/color/ColorButton.cpp index d51d9f27e24..1d84f2e6ff4 100644 --- a/src/widgets/helper/color/ColorButton.cpp +++ b/src/widgets/helper/color/ColorButton.cpp @@ -21,6 +21,11 @@ QSize ColorButton::sizeHint() const void ColorButton::setColor(const QColor &color) { + if (this->currentColor_ == color) + { + return; + } + this->currentColor_ = color; this->update(); } From 065283ab0a11ddbdd22d374edfaa119d7e6f903b Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sat, 6 Jan 2024 21:11:57 +0100 Subject: [PATCH 16/17] fix: include --- src/widgets/dialogs/ColorPickerDialog.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/widgets/dialogs/ColorPickerDialog.cpp b/src/widgets/dialogs/ColorPickerDialog.cpp index 7d9e5e30b51..a6a57956133 100644 --- a/src/widgets/dialogs/ColorPickerDialog.cpp +++ b/src/widgets/dialogs/ColorPickerDialog.cpp @@ -9,6 +9,7 @@ #include "widgets/helper/color/SBCanvas.hpp" #include +#include namespace { From ef4e90b05a5140d1b5c582791f7079fa070da2e1 Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 6 Jan 2024 21:26:22 +0100 Subject: [PATCH 17/17] Upgrade changelog entry from dev to minor --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8a9e985105..5df16f6cd1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - Minor: Re-enabled _Restart on crash_ option on Windows. (#5012) - Minor: The whisper highlight color can now be configured through the settings. (#5053) - Minor: Added missing periods at various moderator messages and commands. (#5061) +- Minor: Improved color selection and display. (#5057) - Bugfix: Fixed an issue where certain emojis did not send to Twitch chat correctly. (#4840) - Bugfix: Fixed capitalized channel names in log inclusion list not being logged. (#4848) - Bugfix: Trimmed custom streamlink paths on all platforms making sure you don't accidentally add spaces at the beginning or end of its path. (#4834) @@ -109,7 +110,6 @@ - Dev: Refactor Args to be less of a singleton. (#5041) - Dev: Channels without any animated elements on screen will skip updates from the GIF timer. (#5042, #5043, #5045) - Dev: Autogenerate docs/plugin-meta.lua. (#5055) -- Dev: Improved color selection and display. (#5057) - Dev: Removed duplicate scale in settings dialog. (#5069) - Dev: Fix `NotebookTab` emitting updates for every message. (#5068)