mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
Improve color selection and display (#5057)
Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
parent
693d4f401d
commit
78a7ebb9f9
26 changed files with 1276 additions and 945 deletions
|
@ -21,6 +21,7 @@
|
||||||
- Minor: Re-enabled _Restart on crash_ option on Windows. (#5012)
|
- Minor: Re-enabled _Restart on crash_ option on Windows. (#5012)
|
||||||
- Minor: The whisper highlight color can now be configured through the settings. (#5053)
|
- 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: 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 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: 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)
|
- 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)
|
||||||
|
|
|
@ -590,12 +590,25 @@ set(SOURCE_FILES
|
||||||
widgets/dialogs/switcher/SwitchSplitItem.cpp
|
widgets/dialogs/switcher/SwitchSplitItem.cpp
|
||||||
widgets/dialogs/switcher/SwitchSplitItem.hpp
|
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.cpp
|
||||||
widgets/helper/Button.hpp
|
widgets/helper/Button.hpp
|
||||||
widgets/helper/ChannelView.cpp
|
widgets/helper/ChannelView.cpp
|
||||||
widgets/helper/ChannelView.hpp
|
widgets/helper/ChannelView.hpp
|
||||||
widgets/helper/ColorButton.cpp
|
|
||||||
widgets/helper/ColorButton.hpp
|
|
||||||
widgets/helper/ComboBoxItemDelegate.cpp
|
widgets/helper/ComboBoxItemDelegate.cpp
|
||||||
widgets/helper/ComboBoxItemDelegate.hpp
|
widgets/helper/ComboBoxItemDelegate.hpp
|
||||||
widgets/helper/DebugPopup.cpp
|
widgets/helper/DebugPopup.cpp
|
||||||
|
@ -610,8 +623,6 @@ set(SOURCE_FILES
|
||||||
widgets/helper/NotebookButton.hpp
|
widgets/helper/NotebookButton.hpp
|
||||||
widgets/helper/NotebookTab.cpp
|
widgets/helper/NotebookTab.cpp
|
||||||
widgets/helper/NotebookTab.hpp
|
widgets/helper/NotebookTab.hpp
|
||||||
widgets/helper/QColorPicker.cpp
|
|
||||||
widgets/helper/QColorPicker.hpp
|
|
||||||
widgets/helper/RegExpItemDelegate.cpp
|
widgets/helper/RegExpItemDelegate.cpp
|
||||||
widgets/helper/RegExpItemDelegate.hpp
|
widgets/helper/RegExpItemDelegate.hpp
|
||||||
widgets/helper/TrimRegExpValidator.cpp
|
widgets/helper/TrimRegExpValidator.cpp
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#include "UserHighlightModel.hpp"
|
#include "controllers/highlights/UserHighlightModel.hpp"
|
||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "controllers/highlights/HighlightModel.hpp"
|
|
||||||
#include "controllers/highlights/HighlightPhrase.hpp"
|
#include "controllers/highlights/HighlightPhrase.hpp"
|
||||||
#include "providers/colors/ColorProvider.hpp"
|
#include "providers/colors/ColorProvider.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
|
@ -10,8 +9,6 @@
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
using Column = HighlightModel::Column;
|
|
||||||
|
|
||||||
// commandmodel
|
// commandmodel
|
||||||
UserHighlightModel::UserHighlightModel(QObject *parent)
|
UserHighlightModel::UserHighlightModel(QObject *parent)
|
||||||
: SignalVectorModel<HighlightPhrase>(Column::COUNT, parent)
|
: SignalVectorModel<HighlightPhrase>(Column::COUNT, parent)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "common/SignalVectorModel.hpp"
|
#include "common/SignalVectorModel.hpp"
|
||||||
|
#include "controllers/highlights/HighlightModel.hpp"
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
|
@ -12,6 +13,8 @@ class HighlightPhrase;
|
||||||
class UserHighlightModel : public SignalVectorModel<HighlightPhrase>
|
class UserHighlightModel : public SignalVectorModel<HighlightPhrase>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
using Column = HighlightModel::Column;
|
||||||
|
|
||||||
explicit UserHighlightModel(QObject *parent);
|
explicit UserHighlightModel(QObject *parent);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
|
@ -1,18 +1,66 @@
|
||||||
#include "widgets/dialogs/ColorPickerDialog.hpp"
|
#include "widgets/dialogs/ColorPickerDialog.hpp"
|
||||||
|
|
||||||
|
#include "common/Literals.hpp"
|
||||||
#include "providers/colors/ColorProvider.hpp"
|
#include "providers/colors/ColorProvider.hpp"
|
||||||
#include "singletons/Theme.hpp"
|
#include "widgets/helper/color/AlphaSlider.hpp"
|
||||||
#include "util/LayoutCreator.hpp"
|
#include "widgets/helper/color/ColorButton.hpp"
|
||||||
#include "widgets/helper/ColorButton.hpp"
|
#include "widgets/helper/color/ColorInput.hpp"
|
||||||
#include "widgets/helper/QColorPicker.hpp"
|
#include "widgets/helper/color/HueSlider.hpp"
|
||||||
|
#include "widgets/helper/color/SBCanvas.hpp"
|
||||||
|
|
||||||
#include <QDialogButtonBox>
|
#include <QDialogButtonBox>
|
||||||
#include <QLineEdit>
|
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
|
|
||||||
|
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<int>(i / COLORS_PER_ROW),
|
||||||
|
static_cast<int>(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 <typename D, typename W>
|
||||||
|
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 {
|
namespace chatterino {
|
||||||
|
|
||||||
ColorPickerDialog::ColorPickerDialog(const QColor &initial, QWidget *parent)
|
using namespace literals;
|
||||||
|
|
||||||
|
ColorPickerDialog::ColorPickerDialog(QColor color, QWidget *parent)
|
||||||
: BasePopup(
|
: BasePopup(
|
||||||
{
|
{
|
||||||
BaseWindow::EnableCustomFrame,
|
BaseWindow::EnableCustomFrame,
|
||||||
|
@ -20,371 +68,95 @@ ColorPickerDialog::ColorPickerDialog(const QColor &initial, QWidget *parent)
|
||||||
BaseWindow::BoundsCheckOnShow,
|
BaseWindow::BoundsCheckOnShow,
|
||||||
},
|
},
|
||||||
parent)
|
parent)
|
||||||
, color_()
|
, color_(color)
|
||||||
, dialogConfirmed_(false)
|
|
||||||
{
|
{
|
||||||
// This hosts the "business logic" and the dialog button box
|
this->setWindowTitle(u"Chatterino - Color picker"_s);
|
||||||
LayoutCreator<QWidget> layoutWidget(this->getLayoutContainer());
|
this->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
auto layout = layoutWidget.setLayoutType<QVBoxLayout>().withoutMargin();
|
|
||||||
|
|
||||||
// This hosts the business logic: color picker and predefined colors
|
auto *dialogContents = new QHBoxLayout;
|
||||||
LayoutCreator<QWidget> contentCreator(new QWidget());
|
dialogContents->setContentsMargins(10, 10, 10, 10);
|
||||||
auto contents = contentCreator.setLayoutType<QHBoxLayout>();
|
|
||||||
|
|
||||||
// This hosts the predefined colors (and also the currently selected color)
|
|
||||||
LayoutCreator<QWidget> predefCreator(new QWidget());
|
|
||||||
auto predef = predefCreator.setLayoutType<QVBoxLayout>();
|
|
||||||
|
|
||||||
// Recently used colors
|
|
||||||
{
|
{
|
||||||
LayoutCreator<QWidget> gridCreator(new QWidget());
|
auto *buttons = new QVBoxLayout;
|
||||||
this->initRecentColors(gridCreator);
|
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<QWidget> gridCreator(new QWidget());
|
auto *controls = new QVBoxLayout;
|
||||||
this->initDefaultColors(gridCreator);
|
|
||||||
|
|
||||||
predef.append(gridCreator.getElement());
|
{
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Currently selected color
|
dialogContents->addLayout(controls);
|
||||||
{
|
|
||||||
LayoutCreator<QWidget> curColorCreator(new QWidget());
|
|
||||||
auto curColor = curColorCreator.setLayoutType<QHBoxLayout>();
|
|
||||||
curColor.emplace<QLabel>("Selected:").assign(&this->ui_.selected.label);
|
|
||||||
curColor.emplace<ColorButton>(initial).assign(
|
|
||||||
&this->ui_.selected.color);
|
|
||||||
|
|
||||||
predef.append(curColor.getElement());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
contents.append(predef.getElement());
|
auto *dialogLayout = new QVBoxLayout(this->getLayoutContainer());
|
||||||
|
dialogLayout->addLayout(dialogContents, 1);
|
||||||
|
dialogLayout->addStretch(1);
|
||||||
|
|
||||||
// Color picker
|
auto *buttonBox =
|
||||||
{
|
new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||||
LayoutCreator<QWidget> obj(new QWidget());
|
|
||||||
auto vbox = obj.setLayoutType<QVBoxLayout>();
|
|
||||||
|
|
||||||
// The actual color picker
|
QObject::connect(buttonBox, &QDialogButtonBox::accepted, this, [this] {
|
||||||
{
|
emit this->colorConfirmed(this->color());
|
||||||
LayoutCreator<QWidget> cpCreator(new QWidget());
|
|
||||||
this->initColorPicker(cpCreator);
|
|
||||||
|
|
||||||
vbox.append(cpCreator.getElement());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spin boxes
|
|
||||||
{
|
|
||||||
LayoutCreator<QWidget> sbCreator(new QWidget());
|
|
||||||
this->initSpinBoxes(sbCreator);
|
|
||||||
|
|
||||||
vbox.append(sbCreator.getElement());
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTML color
|
|
||||||
{
|
|
||||||
LayoutCreator<QWidget> htmlCreator(new QWidget());
|
|
||||||
this->initHtmlColor(htmlCreator);
|
|
||||||
|
|
||||||
vbox.append(htmlCreator.getElement());
|
|
||||||
}
|
|
||||||
|
|
||||||
contents.append(obj.getElement());
|
|
||||||
}
|
|
||||||
|
|
||||||
layout.append(contents.getElement());
|
|
||||||
|
|
||||||
// Dialog buttons
|
|
||||||
auto buttons =
|
|
||||||
layout.emplace<QHBoxLayout>().emplace<QDialogButtonBox>(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();
|
this->close();
|
||||||
});
|
});
|
||||||
|
QObject::connect(buttonBox, &QDialogButtonBox::rejected, this,
|
||||||
|
&ColorPickerDialog::close);
|
||||||
|
dialogLayout->addWidget(buttonBox, 0, Qt::AlignRight);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->themeChangedEvent();
|
QColor ColorPickerDialog::color() const
|
||||||
this->selectColor(initial, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ColorPickerDialog::addShortcuts()
|
|
||||||
{
|
{
|
||||||
}
|
|
||||||
|
|
||||||
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_;
|
return this->color_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ColorPickerDialog::closeEvent(QCloseEvent *)
|
void ColorPickerDialog::setColor(const QColor &color)
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
if (color == this->color_)
|
if (color == this->color_)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
this->color_ = color;
|
this->color_ = color;
|
||||||
|
emit this->colorChanged(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<QWidget> &creator)
|
|
||||||
{
|
|
||||||
auto grid = creator.setLayoutType<QGridLayout>();
|
|
||||||
|
|
||||||
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<QWidget> &creator)
|
|
||||||
{
|
|
||||||
auto grid = creator.setLayoutType<QGridLayout>();
|
|
||||||
|
|
||||||
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<QWidget> &creator)
|
|
||||||
{
|
|
||||||
this->setWindowTitle("Chatterino - color picker");
|
|
||||||
auto cpPanel = creator.setLayoutType<QHBoxLayout>();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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<QWidget> &creator)
|
|
||||||
{
|
|
||||||
auto spinBoxes = creator.setLayoutType<QGridLayout>();
|
|
||||||
|
|
||||||
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<int>::of(&QSpinBox::valueChanged), [=, this](int value) {
|
|
||||||
this->selectColor(QColor(red->value(), green->value(),
|
|
||||||
blue->value(), alpha->value()),
|
|
||||||
false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ColorPickerDialog::initHtmlColor(LayoutCreator<QWidget> &creator)
|
|
||||||
{
|
|
||||||
auto html = creator.setLayoutType<QGridLayout>();
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -2,119 +2,26 @@
|
||||||
|
|
||||||
#include "widgets/BasePopup.hpp"
|
#include "widgets/BasePopup.hpp"
|
||||||
|
|
||||||
#include <pajlada/signals/signal.hpp>
|
|
||||||
#include <QLabel>
|
|
||||||
#include <QLineEdit>
|
|
||||||
#include <QRegularExpressionValidator>
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
class ColorButton;
|
|
||||||
class QColorLuminancePicker;
|
|
||||||
class QColorPicker;
|
|
||||||
class QColSpinBox;
|
|
||||||
|
|
||||||
template <class T>
|
|
||||||
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
|
class ColorPickerDialog : public BasePopup
|
||||||
{
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
ColorPickerDialog(QColor color, QWidget *parent);
|
||||||
* @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() override;
|
QColor color() const;
|
||||||
|
|
||||||
/**
|
signals:
|
||||||
* @brief Return the final color selected by the user.
|
void colorChanged(QColor color);
|
||||||
*
|
void colorConfirmed(QColor color);
|
||||||
* 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;
|
|
||||||
|
|
||||||
pajlada::Signals::Signal<QColor> closed;
|
public slots:
|
||||||
|
void setColor(const QColor &color);
|
||||||
protected:
|
|
||||||
void closeEvent(QCloseEvent *) override;
|
|
||||||
void themeChangedEvent() override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct {
|
|
||||||
struct {
|
|
||||||
QLabel *label;
|
|
||||||
std::vector<ColorButton *> colors;
|
|
||||||
} recent;
|
|
||||||
|
|
||||||
struct {
|
|
||||||
QLabel *label;
|
|
||||||
std::vector<ColorButton *> colors;
|
|
||||||
} def;
|
|
||||||
|
|
||||||
struct {
|
|
||||||
QLabel *label;
|
|
||||||
ColorButton *color;
|
|
||||||
} selected{};
|
|
||||||
|
|
||||||
struct {
|
|
||||||
QColorPicker *colorPicker;
|
|
||||||
QColorLuminancePicker *luminancePicker;
|
|
||||||
|
|
||||||
std::array<QLabel *, 4> spinBoxLabels;
|
|
||||||
std::array<QColSpinBox *, 4> 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_;
|
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<QWidget> &creator);
|
|
||||||
void initDefaultColors(LayoutCreator<QWidget> &creator);
|
|
||||||
void initColorPicker(LayoutCreator<QWidget> &creator);
|
|
||||||
void initSpinBoxes(LayoutCreator<QWidget> &creator);
|
|
||||||
void initHtmlColor(LayoutCreator<QWidget> &creator);
|
|
||||||
|
|
||||||
void addShortcuts() override;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -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
|
|
|
@ -1,20 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QPushButton>
|
|
||||||
|
|
||||||
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
|
|
|
@ -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 <qdrawutil.h>
|
|
||||||
#include <QMouseEvent>
|
|
||||||
#include <QPainter>
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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
|
|
|
@ -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 <QFrame>
|
|
||||||
#include <QSpinBox>
|
|
||||||
|
|
||||||
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
|
|
163
src/widgets/helper/color/AlphaSlider.cpp
Normal file
163
src/widgets/helper/color/AlphaSlider.cpp
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
#include "widgets/helper/color/AlphaSlider.hpp"
|
||||||
|
|
||||||
|
#include "widgets/helper/color/Checkerboard.hpp"
|
||||||
|
|
||||||
|
#include <QMouseEvent>
|
||||||
|
#include <QPainterPath>
|
||||||
|
|
||||||
|
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
|
48
src/widgets/helper/color/AlphaSlider.hpp
Normal file
48
src/widgets/helper/color/AlphaSlider.hpp
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
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
|
29
src/widgets/helper/color/Checkerboard.cpp
Normal file
29
src/widgets/helper/color/Checkerboard.cpp
Normal file
|
@ -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
|
13
src/widgets/helper/color/Checkerboard.hpp
Normal file
13
src/widgets/helper/color/Checkerboard.hpp
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QPainter>
|
||||||
|
|
||||||
|
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
|
81
src/widgets/helper/color/ColorButton.cpp
Normal file
81
src/widgets/helper/color/ColorButton.cpp
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
#include "widgets/helper/color/ColorButton.hpp"
|
||||||
|
|
||||||
|
#include "widgets/helper/color/Checkerboard.hpp"
|
||||||
|
|
||||||
|
#include <QPainterPath>
|
||||||
|
|
||||||
|
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
|
33
src/widgets/helper/color/ColorButton.hpp
Normal file
33
src/widgets/helper/color/ColorButton.hpp
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QAbstractButton>
|
||||||
|
|
||||||
|
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
|
168
src/widgets/helper/color/ColorInput.cpp
Normal file
168
src/widgets/helper/color/ColorInput.cpp
Normal file
|
@ -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<int>(&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
|
52
src/widgets/helper/color/ColorInput.hpp
Normal file
52
src/widgets/helper/color/ColorInput.hpp
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QGridLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QSpinBox>
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
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
|
35
src/widgets/helper/color/ColorItemDelegate.cpp
Normal file
35
src/widgets/helper/color/ColorItemDelegate.cpp
Normal file
|
@ -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<QColor>();
|
||||||
|
|
||||||
|
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
|
16
src/widgets/helper/color/ColorItemDelegate.hpp
Normal file
16
src/widgets/helper/color/ColorItemDelegate.hpp
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QStyledItemDelegate>
|
||||||
|
|
||||||
|
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
|
165
src/widgets/helper/color/HueSlider.cpp
Normal file
165
src/widgets/helper/color/HueSlider.cpp
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
#include "widgets/helper/color/HueSlider.hpp"
|
||||||
|
|
||||||
|
#include <QMouseEvent>
|
||||||
|
#include <QPainter>
|
||||||
|
|
||||||
|
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
|
48
src/widgets/helper/color/HueSlider.hpp
Normal file
48
src/widgets/helper/color/HueSlider.hpp
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
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
|
192
src/widgets/helper/color/SBCanvas.cpp
Normal file
192
src/widgets/helper/color/SBCanvas.cpp
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
#include "widgets/helper/color/SBCanvas.hpp"
|
||||||
|
|
||||||
|
#include <QMouseEvent>
|
||||||
|
#include <QPainter>
|
||||||
|
|
||||||
|
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
|
56
src/widgets/helper/color/SBCanvas.hpp
Normal file
56
src/widgets/helper/color/SBCanvas.hpp
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
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
|
|
@ -4,7 +4,7 @@
|
||||||
#include "util/LayoutHelper.hpp"
|
#include "util/LayoutHelper.hpp"
|
||||||
#include "util/RapidJsonSerializeQString.hpp"
|
#include "util/RapidJsonSerializeQString.hpp"
|
||||||
#include "widgets/dialogs/ColorPickerDialog.hpp"
|
#include "widgets/dialogs/ColorPickerDialog.hpp"
|
||||||
#include "widgets/helper/ColorButton.hpp"
|
#include "widgets/helper/color/ColorButton.hpp"
|
||||||
#include "widgets/helper/Line.hpp"
|
#include "widgets/helper/Line.hpp"
|
||||||
|
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
|
@ -214,20 +214,18 @@ ColorButton *GeneralPageView::addColorButton(
|
||||||
|
|
||||||
QObject::connect(
|
QObject::connect(
|
||||||
colorButton, &ColorButton::clicked, [this, &setting, colorButton]() {
|
colorButton, &ColorButton::clicked, [this, &setting, colorButton]() {
|
||||||
auto dialog = new ColorPickerDialog(QColor(setting), this);
|
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
|
|
||||||
// colorButton & setting are never deleted and the signal is deleted
|
// colorButton & setting are never deleted and the signal is deleted
|
||||||
// once the dialog is closed
|
// once the dialog is closed
|
||||||
std::ignore = dialog->closed.connect(
|
QObject::connect(dialog, &ColorPickerDialog::colorConfirmed, this,
|
||||||
[&setting, colorButton](QColor selected) {
|
[&setting, colorButton](auto selected) {
|
||||||
if (selected.isValid())
|
if (selected.isValid())
|
||||||
{
|
{
|
||||||
setting = selected.name(QColor::HexArgb);
|
setting = selected.name(QColor::HexArgb);
|
||||||
colorButton->setColor(selected);
|
colorButton->setColor(selected);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
dialog->show();
|
||||||
});
|
});
|
||||||
|
|
||||||
this->groups_.back().widgets.push_back({label, {text}});
|
this->groups_.back().widgets.push_back({label, {text}});
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include "util/LayoutCreator.hpp"
|
#include "util/LayoutCreator.hpp"
|
||||||
#include "widgets/dialogs/BadgePickerDialog.hpp"
|
#include "widgets/dialogs/BadgePickerDialog.hpp"
|
||||||
#include "widgets/dialogs/ColorPickerDialog.hpp"
|
#include "widgets/dialogs/ColorPickerDialog.hpp"
|
||||||
|
#include "widgets/helper/color/ColorItemDelegate.hpp"
|
||||||
#include "widgets/helper/EditableModelView.hpp"
|
#include "widgets/helper/EditableModelView.hpp"
|
||||||
|
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
|
@ -82,6 +83,8 @@ HighlightingPage::HighlightingPage()
|
||||||
QHeaderView::Fixed);
|
QHeaderView::Fixed);
|
||||||
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
||||||
0, QHeaderView::Stretch);
|
0, QHeaderView::Stretch);
|
||||||
|
view->getTableView()->setItemDelegateForColumn(
|
||||||
|
HighlightModel::Column::Color, new ColorItemDelegate(view));
|
||||||
|
|
||||||
// fourtf: make class extrend BaseWidget and add this to
|
// fourtf: make class extrend BaseWidget and add this to
|
||||||
// dpiChanged
|
// dpiChanged
|
||||||
|
@ -134,6 +137,9 @@ HighlightingPage::HighlightingPage()
|
||||||
QHeaderView::Fixed);
|
QHeaderView::Fixed);
|
||||||
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
||||||
0, QHeaderView::Stretch);
|
0, QHeaderView::Stretch);
|
||||||
|
view->getTableView()->setItemDelegateForColumn(
|
||||||
|
UserHighlightModel::Column::Color,
|
||||||
|
new ColorItemDelegate(view));
|
||||||
|
|
||||||
// fourtf: make class extrend BaseWidget and add this to
|
// fourtf: make class extrend BaseWidget and add this to
|
||||||
// dpiChanged
|
// dpiChanged
|
||||||
|
@ -176,6 +182,9 @@ HighlightingPage::HighlightingPage()
|
||||||
QHeaderView::Fixed);
|
QHeaderView::Fixed);
|
||||||
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
||||||
0, QHeaderView::Stretch);
|
0, QHeaderView::Stretch);
|
||||||
|
view->getTableView()->setItemDelegateForColumn(
|
||||||
|
BadgeHighlightModel::Column::Color,
|
||||||
|
new ColorItemDelegate(view));
|
||||||
|
|
||||||
// fourtf: make class extrend BaseWidget and add this to
|
// fourtf: make class extrend BaseWidget and add this to
|
||||||
// dpiChanged
|
// dpiChanged
|
||||||
|
@ -330,18 +339,18 @@ void HighlightingPage::openColorDialog(const QModelIndex &clicked,
|
||||||
auto initial =
|
auto initial =
|
||||||
view->getModel()->data(clicked, Qt::DecorationRole).value<QColor>();
|
view->getModel()->data(clicked, Qt::DecorationRole).value<QColor>();
|
||||||
|
|
||||||
auto dialog = new ColorPickerDialog(initial, this);
|
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
|
|
||||||
// TODO: The QModelIndex clicked is technically not safe to persist here since the model
|
// 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
|
// can be changed between the color dialog being created & the color dialog being closed
|
||||||
std::ignore = dialog->closed.connect([=](auto selected) {
|
QObject::connect(dialog, &ColorPickerDialog::colorConfirmed, this,
|
||||||
|
[=](auto selected) {
|
||||||
if (selected.isValid())
|
if (selected.isValid())
|
||||||
{
|
{
|
||||||
view->getModel()->setData(clicked, selected, Qt::DecorationRole);
|
view->getModel()->setData(clicked, selected,
|
||||||
|
Qt::DecorationRole);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
dialog->show();
|
||||||
}
|
}
|
||||||
|
|
||||||
void HighlightingPage::tableCellClicked(const QModelIndex &clicked,
|
void HighlightingPage::tableCellClicked(const QModelIndex &clicked,
|
||||||
|
|
Loading…
Reference in a new issue