From 78a7ebb9f952d4de8fb69b95a786e570e96bdf1c Mon Sep 17 00:00:00 2001 From: nerix Date: Sat, 6 Jan 2024 21:52:29 +0100 Subject: [PATCH] Improve color selection and display (#5057) Co-authored-by: Rasmus Karlsson --- CHANGELOG.md | 1 + src/CMakeLists.txt | 19 +- .../highlights/UserHighlightModel.cpp | 5 +- .../highlights/UserHighlightModel.hpp | 3 + src/widgets/dialogs/ColorPickerDialog.cpp | 462 +++++------------- src/widgets/dialogs/ColorPickerDialog.hpp | 113 +---- 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 | 163 ++++++ src/widgets/helper/color/AlphaSlider.hpp | 48 ++ src/widgets/helper/color/Checkerboard.cpp | 29 ++ src/widgets/helper/color/Checkerboard.hpp | 13 + src/widgets/helper/color/ColorButton.cpp | 81 +++ src/widgets/helper/color/ColorButton.hpp | 33 ++ src/widgets/helper/color/ColorInput.cpp | 168 +++++++ src/widgets/helper/color/ColorInput.hpp | 52 ++ .../helper/color/ColorItemDelegate.cpp | 35 ++ .../helper/color/ColorItemDelegate.hpp | 16 + src/widgets/helper/color/HueSlider.cpp | 165 +++++++ src/widgets/helper/color/HueSlider.hpp | 48 ++ src/widgets/helper/color/SBCanvas.cpp | 192 ++++++++ src/widgets/helper/color/SBCanvas.hpp | 56 +++ src/widgets/settingspages/GeneralPageView.cpp | 24 +- .../settingspages/HighlightingPage.cpp | 29 +- 26 files changed, 1276 insertions(+), 945 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/ColorInput.cpp create mode 100644 src/widgets/helper/color/ColorInput.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/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/CHANGELOG.md b/CHANGELOG.md index 31305fb88..5df16f6cd 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) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 74a936ec1..2b83ee2a8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -590,12 +590,25 @@ 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/ColorInput.cpp + widgets/helper/color/ColorInput.hpp + widgets/helper/color/ColorItemDelegate.cpp + widgets/helper/color/ColorItemDelegate.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 @@ -610,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 15ca70163..26a44dd51 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 a9185b6c3..e17b84792 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 5d1096d81..a6a579561 100644 --- a/src/widgets/dialogs/ColorPickerDialog.cpp +++ b/src/widgets/dialogs/ColorPickerDialog.cpp @@ -1,18 +1,66 @@ #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/AlphaSlider.hpp" +#include "widgets/helper/color/ColorButton.hpp" +#include "widgets/helper/color/ColorInput.hpp" +#include "widgets/helper/color/HueSlider.hpp" +#include "widgets/helper/color/SBCanvas.hpp" #include -#include #include +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, + std::size_t maxButtons) +{ + auto *layout = new QGridLayout; + + // TODO(nerix): use std::ranges::views::enumerate (C++ 23) + for (std::size_t i = 0; auto color : items) + { + auto *button = new ColorButton(color); + button->setMinimumWidth(40); + QObject::connect(button, &ColorButton::clicked, self, [self, color]() { + self->setColor(color); + }); + + layout->addWidget(button, static_cast(i / COLORS_PER_ROW), + static_cast(i % COLORS_PER_ROW)); + i++; + if (i >= maxButtons) + { + break; + } + } + 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 { -ColorPickerDialog::ColorPickerDialog(const QColor &initial, QWidget *parent) +using namespace literals; + +ColorPickerDialog::ColorPickerDialog(QColor color, QWidget *parent) : BasePopup( { BaseWindow::EnableCustomFrame, @@ -20,371 +68,95 @@ ColorPickerDialog::ColorPickerDialog(const QColor &initial, QWidget *parent) BaseWindow::BoundsCheckOnShow, }, parent) - , color_() - , dialogConfirmed_(false) + , color_(color) { - // This hosts the "business logic" and the dialog button box - LayoutCreator layoutWidget(this->getLayoutContainer()); - auto layout = layoutWidget.setLayoutType().withoutMargin(); + this->setWindowTitle(u"Chatterino - Color picker"_s); + this->setAttribute(Qt::WA_DeleteOnClose); - // 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 + auto *dialogContents = new QHBoxLayout; + dialogContents->setContentsMargins(10, 10, 10, 10); { - LayoutCreator gridCreator(new QWidget()); - this->initRecentColors(gridCreator); + auto *buttons = new QVBoxLayout; + buttons->addWidget(new QLabel(u"Recently used"_s)); + buttons->addLayout(makeColorGrid( + ColorProvider::instance().recentColors(), this, MAX_RECENT_COLORS)); - predef.append(gridCreator.getElement()); + buttons->addSpacing(10); + + buttons->addWidget(new QLabel(u"Default colors"_s)); + buttons->addLayout( + makeColorGrid(ColorProvider::instance().defaultColors(), this, + MAX_DEFAULT_COLORS)); + + buttons->addStretch(1); + buttons->addWidget(new QLabel(u"Selected"_s)); + auto *display = new ColorButton(this->color()); + QObject::connect(this, &ColorPickerDialog::colorChanged, display, + &ColorButton::setColor); + buttons->addWidget(display); + + dialogContents->addLayout(buttons); + dialogContents->addSpacing(10); } - // Default colors { - LayoutCreator gridCreator(new QWidget()); - this->initDefaultColors(gridCreator); + auto *controls = new QVBoxLayout; - 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); + auto *select = new QVBoxLayout; - vbox.append(cpCreator.getElement()); + 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); + + select->addWidget(sbCanvas, 0, Qt::AlignHCenter); + select->addWidget(hueSlider); + select->addWidget(alphaSlider); + + controls->addLayout(select); + } + { + auto *input = new ColorInput(this->color()); + connectSignals(this, input); + controls->addWidget(input); } - // Spin boxes - { - LayoutCreator sbCreator(new QWidget()); - this->initSpinBoxes(sbCreator); - - vbox.append(sbCreator.getElement()); - } - - // HTML color - { - LayoutCreator htmlCreator(new QWidget()); - this->initHtmlColor(htmlCreator); - - vbox.append(htmlCreator.getElement()); - } - - contents.append(obj.getElement()); + dialogContents->addLayout(controls); } - layout.append(contents.getElement()); + auto *dialogLayout = new QVBoxLayout(this->getLayoutContainer()); + dialogLayout->addLayout(dialogContents, 1); + dialogLayout->addStretch(1); - // Dialog buttons - auto buttons = - layout.emplace().emplace(this); - { - auto *button_ok = buttons->addButton(QDialogButtonBox::Ok); - QObject::connect(button_ok, &QPushButton::clicked, [this](bool) { - this->ok(); - }); - auto *button_cancel = buttons->addButton(QDialogButtonBox::Cancel); - QObject::connect(button_cancel, &QAbstractButton::clicked, - [this](bool) { - this->close(); - }); - } + auto *buttonBox = + new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - this->themeChangedEvent(); - this->selectColor(initial, false); + QObject::connect(buttonBox, &QDialogButtonBox::accepted, this, [this] { + emit this->colorConfirmed(this->color()); + this->close(); + }); + QObject::connect(buttonBox, &QDialogButtonBox::rejected, this, + &ColorPickerDialog::close); + dialogLayout->addWidget(buttonBox, 0, Qt::AlignRight); } -void ColorPickerDialog::addShortcuts() +QColor ColorPickerDialog::color() const { -} - -ColorPickerDialog::~ColorPickerDialog() -{ - if (this->htmlColorValidator_) - { - this->htmlColorValidator_->deleteLater(); - this->htmlColorValidator_ = nullptr; - } -} - -QColor ColorPickerDialog::selectedColor() const -{ - if (!this->dialogConfirmed_) - { - // If the Cancel button was clicked, return the invalid color - return QColor(); - } - return this->color_; } -void ColorPickerDialog::closeEvent(QCloseEvent *) -{ - this->closed.invoke(this->selectedColor()); -} - -void ColorPickerDialog::themeChangedEvent() -{ - BaseWindow::themeChangedEvent(); - - QString textCol = this->theme->splits.input.text.name(QColor::HexRgb); - QString bgCol = this->theme->splits.input.background.name(QColor::HexRgb); - - // Labels - - 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) - { - spinBoxLabel->setStyleSheet(labelStyle); - } - - this->ui_.picker.htmlEdit->setStyleSheet( - this->theme->splits.input.styleSheet); - - // Styling spin boxes is too much effort -} - -void ColorPickerDialog::selectColor(const QColor &color, bool fromColorPicker) +void ColorPickerDialog::setColor(const QColor &color) { if (color == this->color_) + { return; - + } 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()); - } - - 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]; - - static_assert(RECENT_COLORS_PER_ROW != 0); - const int rowInd = (ind / RECENT_COLORS_PER_ROW) + 1; - const int columnInd = ind % RECENT_COLORS_PER_ROW; - - grid->addWidget(button, rowInd, columnInd); - - QObject::connect(button, &QPushButton::clicked, [=, this] { - this->selectColor(button->color(), false); - }); - - ++it; - ++ind; - } - - auto spacer = - new QSpacerItem(40, 20, QSizePolicy::Minimum, QSizePolicy::Expanding); - grid->addItem(spacer, (ind / RECENT_COLORS_PER_ROW) + 2, 0, 1, 1, - Qt::AlignTop); -} - -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); - }); - - ++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); -} - -void ColorPickerDialog::initColorPicker(LayoutCreator &creator) -{ - 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); - }); -} - -void ColorPickerDialog::initSpinBoxes(LayoutCreator &creator) -{ - 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) - { - 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); - }); - } -} - -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); - }); + emit this->colorChanged(color); } } // namespace chatterino diff --git a/src/widgets/dialogs/ColorPickerDialog.hpp b/src/widgets/dialogs/ColorPickerDialog.hpp index b932b26e8..d9c896e94 100644 --- a/src/widgets/dialogs/ColorPickerDialog.hpp +++ b/src/widgets/dialogs/ColorPickerDialog.hpp @@ -2,119 +2,26 @@ #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 { + Q_OBJECT + 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); + ColorPickerDialog(QColor color, QWidget *parent); - ~ColorPickerDialog() override; + QColor color() const; - /** - * @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; +signals: + void colorChanged(QColor color); + void colorConfirmed(QColor color); - pajlada::Signals::Signal closed; - -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 afd21f832..000000000 --- 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 7101a8dce..000000000 --- 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 50086efca..000000000 --- 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 408fd344c..000000000 --- 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 000000000..75d159e77 --- /dev/null +++ b/src/widgets/helper/color/AlphaSlider.cpp @@ -0,0 +1,163 @@ +#include "widgets/helper/color/AlphaSlider.hpp" + +#include "widgets/helper/color/Checkerboard.hpp" + +#include +#include + +namespace { + +constexpr int SLIDER_WIDTH = 256; +constexpr int SLIDER_HEIGHT = 12; + +} // namespace + +namespace chatterino { + +AlphaSlider::AlphaSlider(QColor color, QWidget *parent) + : QWidget(parent) + , alpha_(color.alpha()) + , color_(color) +{ + this->setSizePolicy({QSizePolicy::Expanding, QSizePolicy::Fixed}); +} + +void AlphaSlider::setColor(QColor color) +{ + if (this->color_ == color) + { + return; + } + this->alpha_ = color.alpha(); + this->color_ = color; + this->cachedPixmap_ = {}; + this->update(); +} + +int AlphaSlider::alpha() const +{ + return this->alpha_; +} + +QSize AlphaSlider::sizeHint() const +{ + return {SLIDER_WIDTH, SLIDER_HEIGHT}; +} + +void AlphaSlider::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + this->updatePixmap(); +} + +int AlphaSlider::xPosToAlpha(int xPos) const +{ + return (xPos * 255) / (this->width() - this->height()); +} + +void AlphaSlider::mousePressEvent(QMouseEvent *event) +{ + if (event->buttons().testFlag(Qt::MouseButton::LeftButton)) + { + this->trackingMouseEvents_ = true; + this->updateFromEvent(event); + this->setFocus(Qt::FocusReason::MouseFocusReason); + } +} +void AlphaSlider::mouseMoveEvent(QMouseEvent *event) +{ + if (this->trackingMouseEvents_) + { + this->updateFromEvent(event); + event->accept(); + } +} +void AlphaSlider::mouseReleaseEvent(QMouseEvent *event) +{ + if (this->trackingMouseEvents_ && + event->buttons().testFlag(Qt::MouseButton::LeftButton)) + { + this->updateFromEvent(event); + this->trackingMouseEvents_ = false; + event->accept(); + } +} + +void AlphaSlider::updateFromEvent(QMouseEvent *event) +{ + int cornerRadius = this->height() / 2; + auto clampedX = std::clamp(event->pos().x(), cornerRadius, + this->width() - cornerRadius); + this->setAlpha(this->xPosToAlpha(clampedX - cornerRadius)); +} + +void AlphaSlider::updatePixmap() +{ + this->cachedPixmap_ = QPixmap(this->size()); + this->cachedPixmap_.fill(Qt::transparent); + QPainter painter(&this->cachedPixmap_); + painter.setRenderHint(QPainter::Antialiasing); + + qreal cornerRadius = (qreal)this->height() / 2.0; + + QPainterPath mask; + mask.addRoundedRect(QRect({0, 0}, this->size()), cornerRadius, + cornerRadius); + painter.setClipPath(mask); + + drawCheckerboard(painter, this->size(), this->height() / 2); + + QLinearGradient gradient(cornerRadius, 0.0, + (qreal)this->width() - cornerRadius, 0.0); + QColor start = this->color_; + QColor end = this->color_; + start.setAlpha(0); + end.setAlpha(255); + + gradient.setColorAt(0.0, start); + gradient.setColorAt(1.0, end); + + painter.setPen({Qt::transparent, 0}); + painter.setBrush(gradient); + painter.drawRect(QRect({0, 0}, this->size())); +} + +void AlphaSlider::paintEvent(QPaintEvent * /*event*/) +{ + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + if (this->cachedPixmap_.isNull()) + { + this->updatePixmap(); + } + + painter.drawPixmap(this->rect().topLeft(), this->cachedPixmap_); + + int cornerRadius = this->height() / 2; + + QPoint circ = { + cornerRadius + + (this->alpha() * (this->width() - 2 * cornerRadius)) / 255, + cornerRadius}; + auto circleColor = 0; + painter.setPen({QColor(circleColor, circleColor, circleColor), 2}); + auto opaqueBase = this->color_; + opaqueBase.setAlpha(255); + painter.setBrush(opaqueBase); + painter.drawEllipse(circ, cornerRadius - 1, cornerRadius - 1); +} + +void AlphaSlider::setAlpha(int alpha) +{ + if (this->alpha_ == alpha) + { + return; + } + this->alpha_ = alpha; + this->color_.setAlpha(alpha); + + emit this->colorChanged(this->color_); + this->update(); +} + +} // namespace chatterino diff --git a/src/widgets/helper/color/AlphaSlider.hpp b/src/widgets/helper/color/AlphaSlider.hpp new file mode 100644 index 000000000..6a78990d5 --- /dev/null +++ b/src/widgets/helper/color/AlphaSlider.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include + +namespace chatterino { + +class AlphaSlider : public QWidget +{ + Q_OBJECT + +public: + AlphaSlider(QColor color, QWidget *parent = nullptr); + + QSize sizeHint() const override; + + int alpha() const; + +signals: + void colorChanged(QColor color) const; + +public slots: + void setColor(QColor color); + +protected: + void resizeEvent(QResizeEvent *event) override; + void paintEvent(QPaintEvent *event) override; + + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + +private: + int alpha_ = 255; + QColor color_; + + QPixmap cachedPixmap_; + + bool trackingMouseEvents_ = false; + + void updatePixmap(); + int xPosToAlpha(int xPos) const; + + void updateFromEvent(QMouseEvent *event); + + void setAlpha(int alpha); +}; + +} // namespace chatterino diff --git a/src/widgets/helper/color/Checkerboard.cpp b/src/widgets/helper/color/Checkerboard.cpp new file mode 100644 index 000000000..a82827cd9 --- /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 000000000..d845fbcc5 --- /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 000000000..1d84f2e6f --- /dev/null +++ b/src/widgets/helper/color/ColorButton.cpp @@ -0,0 +1,81 @@ +#include "widgets/helper/color/ColorButton.hpp" + +#include "widgets/helper/color/Checkerboard.hpp" + +#include + +namespace chatterino { + +ColorButton::ColorButton(QColor color, QWidget *parent) + : QAbstractButton(parent) + , currentColor_(color) +{ + this->setSizePolicy({QSizePolicy::Expanding, QSizePolicy::Expanding}); + this->setMinimumSize({30, 30}); +} + +QSize ColorButton::sizeHint() const +{ + return {50, 30}; +} + +void ColorButton::setColor(const QColor &color) +{ + if (this->currentColor_ == color) + { + return; + } + + this->currentColor_ = color; + this->update(); +} + +QColor ColorButton::color() const +{ + return this->currentColor_; +} + +void ColorButton::resizeEvent(QResizeEvent * /*event*/) +{ + this->checkerboardCacheValid_ = false; + this->repaint(); +} + +void ColorButton::paintEvent(QPaintEvent * /*event*/) +{ + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + auto rect = this->rect(); + + if (this->currentColor_.alpha() != 255) + { + if (!this->checkerboardCacheValid_) + { + QPixmap cache(this->size()); + cache.fill(Qt::transparent); + + QPainter cachePainter(&cache); + cachePainter.setRenderHint(QPainter::Antialiasing); + QPainterPath path; + path.addRoundedRect(QRect(1, 1, this->size().width() - 2, + this->size().height() - 2), + 5, 5); + cachePainter.setClipPath(path); + + drawCheckerboard(cachePainter, this->size(), + std::min(this->height() / 2, 10)); + cachePainter.end(); + + this->checkerboardCache_ = std::move(cache); + this->checkerboardCacheValid_ = true; + } + painter.drawPixmap(rect.topLeft(), this->checkerboardCache_); + } + painter.setBrush(this->currentColor_); + painter.setPen({QColor(255, 255, 255, 127), 1}); + painter.drawRoundedRect(rect.x() + 1, rect.y() + 1, rect.width() - 2, + rect.height() - 2, 5, 5); +} + +} // namespace chatterino diff --git a/src/widgets/helper/color/ColorButton.hpp b/src/widgets/helper/color/ColorButton.hpp new file mode 100644 index 000000000..33f55c98e --- /dev/null +++ b/src/widgets/helper/color/ColorButton.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include + +namespace chatterino { + +class ColorButton : public QAbstractButton +{ + Q_OBJECT + +public: + ColorButton(QColor color, QWidget *parent = nullptr); + + QSize sizeHint() const override; + + QColor color() const; + + // NOLINTNEXTLINE(readability-redundant-access-specifiers) +public slots: + void setColor(const QColor &color); + +protected: + void paintEvent(QPaintEvent *event) override; + void resizeEvent(QResizeEvent *event) override; + +private: + QColor currentColor_; + + QPixmap checkerboardCache_; + bool checkerboardCacheValid_ = false; +}; + +} // namespace chatterino diff --git a/src/widgets/helper/color/ColorInput.cpp b/src/widgets/helper/color/ColorInput.cpp new file mode 100644 index 000000000..63220644a --- /dev/null +++ b/src/widgets/helper/color/ColorInput.cpp @@ -0,0 +1,168 @@ +#include "widgets/helper/color/ColorInput.hpp" + +namespace { + +// from qtools_p.h +int fromHex(char c) noexcept +{ + if (c >= '0' && c <= '9') + { + return int(c - '0'); + } + if (c >= 'A' && c <= 'F') + { + return int(c - 'A' + 10); + } + if (c >= 'a' && c <= 'f') + { + return int(c - 'a' + 10); + } + + return -1; +} + +QColor parseHexColor(const QString &text) +{ + if (text.length() == 5) // #rgba + { + auto alphaHex = fromHex(text[4].toLatin1()); + QStringView v(text); + v.chop(1); + QColor col(v); + col.setAlpha(alphaHex); + return col; + } + QColor col(text); + if (col.isValid() && text.length() == 9) // #rrggbbaa + { + auto rgba = col.rgba(); + auto alpha = rgba & 0xff; + QColor actual(rgba >> 8); + actual.setAlpha((int)alpha); + return actual; + } + return col; +} + +} // namespace + +namespace chatterino { + +ColorInput::ColorInput(QColor color, QWidget *parent) + : QWidget(parent) + , currentColor_(color) + , hexValidator_(QRegularExpression( + R"(^#([A-Fa-f\d]{3,4}|[A-Fa-f\d]{6}|[A-Fa-f\d]{8})$)")) + , layout_(this) +{ + int row = 0; + const auto initComponent = [&](Component &component, auto label, + auto applyToColor) { + component.lbl.setText(label); + component.box.setRange(0, 255); + QObject::connect(&component.box, + qOverload(&QSpinBox::valueChanged), this, + [this, &component, applyToColor](int value) { + if (component.value == value) + { + return; + } + applyToColor(this->currentColor_, value); + + this->emitUpdate(); + }); + this->layout_.addWidget(&component.lbl, row, 0); + this->layout_.addWidget(&component.box, row, 1); + row++; + }; + + initComponent(this->red_, "Red:", [](auto &color, int value) { + color.setRed(value); + }); + initComponent(this->green_, "Green:", [](auto &color, int value) { + color.setGreen(value); + }); + initComponent(this->blue_, "Red:", [](auto &color, int value) { + color.setBlue(value); + }); + initComponent(this->alpha_, "Alpha:", [](auto &color, int value) { + color.setAlpha(value); + }); + + this->hexLabel_.setText("Hex:"); + this->hexInput_.setValidator(&this->hexValidator_); + QObject::connect(&this->hexInput_, &QLineEdit::editingFinished, [this]() { + auto css = parseHexColor(this->hexInput_.text()); + if (!css.isValid() || this->currentColor_ == css) + { + return; + } + this->currentColor_ = css; + this->emitUpdate(); + }); + this->layout_.addWidget(&this->hexLabel_, row, 0); + this->layout_.addWidget(&this->hexInput_, row, 1); + + this->updateComponents(); +} + +void ColorInput::updateComponents() +{ + auto color = this->currentColor_.toRgb(); + const auto updateComponent = [](Component &component, auto getValue) { + int value = getValue(); + if (component.value != value) + { + component.value = value; + component.box.setValue(value); + } + }; + updateComponent(this->red_, [&]() { + return color.red(); + }); + updateComponent(this->green_, [&]() { + return color.green(); + }); + updateComponent(this->blue_, [&]() { + return color.blue(); + }); + updateComponent(this->alpha_, [&]() { + return color.alpha(); + }); + + this->updateHex(); +} + +void ColorInput::updateHex() +{ + auto rgb = this->currentColor_.rgb(); + rgb <<= 8; + rgb |= this->currentColor_.alpha(); + // we always need to update the CSS color + this->hexInput_.setText(QStringLiteral("#%1").arg(rgb, 8, 16, QChar(u'0'))); +} + +QColor ColorInput::color() const +{ + return this->currentColor_; +} + +void ColorInput::setColor(QColor color) +{ + if (this->currentColor_ == color) + { + return; + } + this->currentColor_ = color; + this->updateComponents(); + // no emit, as we just got the updated color +} + +void ColorInput::emitUpdate() +{ + this->updateComponents(); + // our components triggered this update, emit the new color + emit this->colorChanged(this->currentColor_); +} + +} // namespace chatterino diff --git a/src/widgets/helper/color/ColorInput.hpp b/src/widgets/helper/color/ColorInput.hpp new file mode 100644 index 000000000..a654403ef --- /dev/null +++ b/src/widgets/helper/color/ColorInput.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace chatterino { + +class ColorInput : public QWidget +{ + Q_OBJECT + +public: + ColorInput(QColor color, QWidget *parent = nullptr); + + QColor color() const; + +signals: + void colorChanged(QColor color); + +public slots: + void setColor(QColor color); + +private: + QColor currentColor_; + + struct Component { + QLabel lbl; + QSpinBox box; + int value = -1; + }; + + Component red_; + Component green_; + Component blue_; + Component alpha_; + + QLabel hexLabel_; + QLineEdit hexInput_; + QRegularExpressionValidator hexValidator_; + + QGridLayout layout_; + + void updateComponents(); + void updateHex(); + + void emitUpdate(); +}; + +} // namespace chatterino diff --git a/src/widgets/helper/color/ColorItemDelegate.cpp b/src/widgets/helper/color/ColorItemDelegate.cpp new file mode 100644 index 000000000..5b67f68bc --- /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 000000000..e0cc4e8a2 --- /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/HueSlider.cpp b/src/widgets/helper/color/HueSlider.cpp new file mode 100644 index 000000000..75a91bb4d --- /dev/null +++ b/src/widgets/helper/color/HueSlider.cpp @@ -0,0 +1,165 @@ +#include "widgets/helper/color/HueSlider.hpp" + +#include +#include + +namespace { + +constexpr int SLIDER_WIDTH = 256; +constexpr int SLIDER_HEIGHT = 12; + +} // namespace + +namespace chatterino { + +HueSlider::HueSlider(QColor color, QWidget *parent) + : QWidget(parent) +{ + this->setColor(color); + this->setSizePolicy({QSizePolicy::Expanding, QSizePolicy::Fixed}); +} + +void HueSlider::setColor(QColor color) +{ + if (this->color_ == color) + { + return; + } + this->color_ = color.toHsv(); + + auto hue = std::max(this->color_.hue(), 0); + if (this->hue_ == hue) + { + return; + } + + this->hue_ = hue; + this->update(); +} + +int HueSlider::hue() const +{ + return this->hue_; +} + +QSize HueSlider::sizeHint() const +{ + return {SLIDER_WIDTH, SLIDER_HEIGHT}; +} + +void HueSlider::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + this->updatePixmap(); +} + +int HueSlider::xPosToHue(int xPos) const +{ + return (xPos * 359) / (this->width() - this->height()); +} + +void HueSlider::mousePressEvent(QMouseEvent *event) +{ + if (event->buttons().testFlag(Qt::MouseButton::LeftButton)) + { + this->trackingMouseEvents_ = true; + this->updateFromEvent(event); + event->accept(); + this->setFocus(Qt::FocusReason::MouseFocusReason); + } +} +void HueSlider::mouseMoveEvent(QMouseEvent *event) +{ + if (this->trackingMouseEvents_) + { + this->updateFromEvent(event); + event->accept(); + } +} +void HueSlider::mouseReleaseEvent(QMouseEvent *event) +{ + if (this->trackingMouseEvents_ && + event->buttons().testFlag(Qt::MouseButton::LeftButton)) + { + this->updateFromEvent(event); + this->trackingMouseEvents_ = false; + event->accept(); + } +} + +void HueSlider::updateFromEvent(QMouseEvent *event) +{ + int cornerRadius = this->height() / 2; + auto clampedX = std::clamp(event->pos().x(), cornerRadius, + this->width() - cornerRadius); + this->setHue(this->xPosToHue(clampedX - cornerRadius)); +} + +void HueSlider::updatePixmap() +{ + constexpr int nStops = 10; + constexpr auto nStopsF = (qreal)nStops; + + this->gradientPixmap_ = QPixmap(this->size()); + this->gradientPixmap_.fill(Qt::transparent); + QPainter painter(&this->gradientPixmap_); + painter.setRenderHint(QPainter::Antialiasing); + + qreal cornerRadius = (qreal)this->height() / 2.0; + + QLinearGradient gradient(cornerRadius, 0.0, + (qreal)this->width() - cornerRadius, 0.0); + for (int i = 0; i <= nStops; i++) + { + gradient.setColorAt( + (qreal)i / nStopsF, + QColor::fromHsv(std::min((i * 360) / nStops, 359), 255, 255)); + } + painter.setPen({Qt::transparent, 0}); + painter.setBrush(gradient); + painter.drawRoundedRect(QRect({0, 0}, this->size()), cornerRadius, + cornerRadius); +} + +void HueSlider::paintEvent(QPaintEvent * /*event*/) +{ + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + if (this->gradientPixmap_.isNull()) + { + this->updatePixmap(); + } + + painter.drawPixmap(this->rect().topLeft(), this->gradientPixmap_); + + int cornerRadius = this->height() / 2; + + QPoint circ = { + cornerRadius + (this->hue() * (this->width() - 2 * cornerRadius)) / 360, + cornerRadius}; + auto circleColor = 0; + painter.setPen({QColor(circleColor, circleColor, circleColor), 2}); + painter.setBrush(QColor::fromHsv(this->hue(), 255, 255)); + painter.drawEllipse(circ, cornerRadius - 1, cornerRadius - 1); +} + +void HueSlider::setHue(int hue) +{ + if (this->hue_ == hue) + { + return; + } + this->hue_ = hue; + // ugh + int h{}; + int s{}; + int v{}; + int a{}; + this->color_.getHsv(&h, &s, &v, &a); + this->color_.setHsv(this->hue_, s, v, a); + + emit this->colorChanged(this->color_); + this->update(); +} + +} // namespace chatterino diff --git a/src/widgets/helper/color/HueSlider.hpp b/src/widgets/helper/color/HueSlider.hpp new file mode 100644 index 000000000..90c12f34f --- /dev/null +++ b/src/widgets/helper/color/HueSlider.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include + +namespace chatterino { + +class HueSlider : public QWidget +{ + Q_OBJECT + +public: + HueSlider(QColor color, QWidget *parent = nullptr); + + QSize sizeHint() const override; + + int hue() const; + +signals: + void colorChanged(QColor color) const; + +public slots: + void setColor(QColor color); + +protected: + void resizeEvent(QResizeEvent *event) override; + void paintEvent(QPaintEvent *event) override; + + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + +private: + int hue_ = 0; + QColor color_; + + QPixmap gradientPixmap_; + + bool trackingMouseEvents_ = false; + + void updatePixmap(); + int xPosToHue(int xPos) const; + + void updateFromEvent(QMouseEvent *event); + + void setHue(int hue); +}; + +} // namespace chatterino diff --git a/src/widgets/helper/color/SBCanvas.cpp b/src/widgets/helper/color/SBCanvas.cpp new file mode 100644 index 000000000..78dbef05f --- /dev/null +++ b/src/widgets/helper/color/SBCanvas.cpp @@ -0,0 +1,192 @@ +#include "widgets/helper/color/SBCanvas.hpp" + +#include +#include + +namespace { + +constexpr int PICKER_WIDTH = 256; +constexpr int PICKER_HEIGHT = 256; + +} // namespace + +namespace chatterino { + +SBCanvas::SBCanvas(QColor color, QWidget *parent) + : QWidget(parent) +{ + this->setColor(color); + this->setSizePolicy({QSizePolicy::Fixed, QSizePolicy::Fixed}); +} + +void SBCanvas::setColor(QColor color) +{ + color = color.toHsv(); + if (this->color_ == color) + { + return; + } + this->color_ = color; + + int h{}; + int s{}; + int v{}; + color.getHsv(&h, &s, &v); + h = std::max(h, 0); + + if (this->hue_ == h && this->saturation_ == s && this->brightness_ == v) + { + return; // alpha changed + } + this->hue_ = h; + this->saturation_ = s; + this->brightness_ = v; + + this->gradientPixmap_ = {}; + this->update(); +} + +int SBCanvas::saturation() const +{ + return this->saturation_; +} + +int SBCanvas::brightness() const +{ + return this->brightness_; +} + +QSize SBCanvas::sizeHint() const +{ + return {PICKER_WIDTH, PICKER_HEIGHT}; +} + +void SBCanvas::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + this->updatePixmap(); +} + +int SBCanvas::xPosToSaturation(int xPos) const +{ + return (xPos * 255) / this->width(); +} + +int SBCanvas::yPosToBrightness(int yPos) const +{ + return 255 - (yPos * 255) / this->height(); +} + +void SBCanvas::mousePressEvent(QMouseEvent *event) +{ + if (event->buttons().testFlag(Qt::MouseButton::LeftButton)) + { + this->trackingMouseEvents_ = true; + this->updateFromEvent(event); + event->accept(); + this->setFocus(Qt::FocusReason::MouseFocusReason); + } +} +void SBCanvas::mouseMoveEvent(QMouseEvent *event) +{ + if (this->trackingMouseEvents_) + { + this->updateFromEvent(event); + event->accept(); + } +} +void SBCanvas::mouseReleaseEvent(QMouseEvent *event) +{ + if (this->trackingMouseEvents_ && + event->buttons().testFlag(Qt::MouseButton::LeftButton)) + { + this->updateFromEvent(event); + this->trackingMouseEvents_ = false; + event->accept(); + } +} + +void SBCanvas::updateFromEvent(QMouseEvent *event) +{ + auto clampedX = std::clamp(event->pos().x(), 0, this->width()); + auto clampedY = std::clamp(event->pos().y(), 0, this->height()); + + bool updated = this->setSaturation(this->xPosToSaturation(clampedX)); + updated |= this->setBrightness(this->yPosToBrightness(clampedY)); + + if (updated) + { + this->emitUpdatedColor(); + this->update(); + } +} + +void SBCanvas::updatePixmap() +{ + int w = this->width(); + int h = this->height(); + QImage img(w, h, QImage::Format_RGB32); + uint *pixel = (uint *)img.scanLine(0); + for (int y = 0; y < h; y++) + { + for (int x = 0; x < w; x++) + { + QColor c = QColor::fromHsv(this->hue_, this->xPosToSaturation(x), + this->yPosToBrightness(y)); + *pixel = c.rgb(); + pixel++; + } + } + this->gradientPixmap_ = QPixmap::fromImage(img); +} + +void SBCanvas::paintEvent(QPaintEvent * /*event*/) +{ + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + if (this->gradientPixmap_.isNull()) + { + this->updatePixmap(); + } + + painter.drawPixmap(this->rect().topLeft(), this->gradientPixmap_); + + QPoint circ = {(this->saturation() * this->width()) / 256, + ((255 - this->brightness()) * this->height()) / 256}; + auto circleColor = this->brightness() >= 128 ? 50 : 200; + painter.setPen({QColor(circleColor, circleColor, circleColor), 2}); + painter.setBrush( + QColor::fromHsv(this->hue_, this->saturation_, this->brightness_)); + painter.drawEllipse(circ, 5, 5); +} + +bool SBCanvas::setSaturation(int saturation) +{ + if (this->saturation_ == saturation) + { + return false; + } + + this->saturation_ = saturation; + return true; +} + +bool SBCanvas::setBrightness(int brightness) +{ + if (this->brightness_ == brightness) + { + return false; + } + + this->brightness_ = brightness; + return true; +} + +void SBCanvas::emitUpdatedColor() +{ + this->color_.setHsv(this->hue_, this->saturation_, this->brightness_, + this->color_.alpha()); + emit this->colorChanged(this->color_); +} + +} // namespace chatterino diff --git a/src/widgets/helper/color/SBCanvas.hpp b/src/widgets/helper/color/SBCanvas.hpp new file mode 100644 index 000000000..6a64d82bf --- /dev/null +++ b/src/widgets/helper/color/SBCanvas.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include + +namespace chatterino { + +/// 2D canvas for saturation (x-axis) and brightness (y-axis) +class SBCanvas : public QWidget +{ + Q_OBJECT + +public: + SBCanvas(QColor color, QWidget *parent = nullptr); + + QSize sizeHint() const override; + + int saturation() const; + int brightness() const; + +signals: + void colorChanged(QColor color) const; + +public slots: + void setColor(QColor color); + +protected: + void resizeEvent(QResizeEvent *event) override; + void paintEvent(QPaintEvent *event) override; + + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + +private: + int hue_ = 0; + int saturation_ = 0; + int brightness_ = 0; + QColor color_; + + QPixmap gradientPixmap_; + + bool trackingMouseEvents_ = false; + + void updatePixmap(); + int xPosToSaturation(int xPos) const; + int yPosToBrightness(int yPos) const; + + void updateFromEvent(QMouseEvent *event); + + [[nodiscard]] bool setSaturation(int saturation); + [[nodiscard]] bool setBrightness(int brightness); + + void emitUpdatedColor(); +}; + +} // namespace chatterino diff --git a/src/widgets/settingspages/GeneralPageView.cpp b/src/widgets/settingspages/GeneralPageView.cpp index 95a0d42fc..6b1a6c790 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 61f87b587..136b760cd 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,