From 544ae787d3b5f463013756b7d47769ad83c9d8f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Thu, 9 Jan 2025 13:53:54 +0100 Subject: [PATCH] Improved the color property editor Now it is possible to enter the hex value (or CSS color name) to set a color's value. --- src/tiled/colorbutton.cpp | 2 +- src/tiled/propertiesview.cpp | 43 ++------- src/tiled/propertyeditorwidgets.cpp | 132 ++++++++++++++++++++++++++++ src/tiled/propertyeditorwidgets.h | 33 +++++++ src/tiled/utils.cpp | 5 +- 5 files changed, 176 insertions(+), 39 deletions(-) diff --git a/src/tiled/colorbutton.cpp b/src/tiled/colorbutton.cpp index 32358d4a23..9489489349 100644 --- a/src/tiled/colorbutton.cpp +++ b/src/tiled/colorbutton.cpp @@ -76,7 +76,7 @@ void ColorButton::pickColor() void ColorButton::updateIcon() { // todo: fix gray icon in disabled state (consider using opacity, and not using an icon at all) - setIcon(Utils::colorIcon(mColor, iconSize())); + setIcon(mColor.isValid() ? Utils::colorIcon(mColor, iconSize()) : QIcon()); setText(mColor.isValid() ? QString() : tr("Not set")); } diff --git a/src/tiled/propertiesview.cpp b/src/tiled/propertiesview.cpp index df31778f09..18022825b2 100644 --- a/src/tiled/propertiesview.cpp +++ b/src/tiled/propertiesview.cpp @@ -20,7 +20,6 @@ #include "propertiesview.h" -#include "colorbutton.h" #include "fileedit.h" #include "textpropertyedit.h" #include "utils.h" @@ -475,46 +474,22 @@ QWidget *RectFProperty::createEditor(QWidget *parent) QWidget *ColorProperty::createEditor(QWidget *parent) { - auto editor = new QWidget(parent); - auto layout = new QHBoxLayout(editor); - layout->setContentsMargins(QMargins()); - layout->setSpacing(0); - - auto colorButton = new ColorButton(editor); - colorButton->setShowAlphaChannel(m_alpha); - colorButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); - - QIcon resetIcon(QStringLiteral(":/images/16/edit-clear.png")); - resetIcon.addFile(QStringLiteral(":/images/24/edit-clear.png")); - - auto resetButton = new QToolButton(editor); - resetButton->setIcon(resetIcon); - resetButton->setToolTip(tr("Unset Color")); - resetButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::MinimumExpanding); - Utils::setThemeIcon(resetButton, "edit-clear"); - - layout->addWidget(colorButton); - layout->addWidget(resetButton); + auto editor = new ColorEdit(parent); + editor->setShowAlpha(m_alpha); auto syncEditor = [=] { - const QSignalBlocker blocker(colorButton); - auto v = value(); - colorButton->setColor(v); - resetButton->setEnabled(v.isValid()); + // Not using QSignalBlocker, because the editor internally needs its + // textChanged signal to be emitted. Instead, we rely on 'valueEdited', + // which isn't emitted when setting the value like this. + editor->setValue(value()); }; syncEditor(); - connect(resetButton, &QToolButton::clicked, colorButton, [colorButton] { - colorButton->setColor(QColor()); - }); - connect(this, &Property::valueChanged, colorButton, syncEditor); - connect(colorButton, &ColorButton::colorChanged, this, [=] { - resetButton->setEnabled(colorButton->color().isValid()); - setValue(colorButton->color()); + connect(this, &Property::valueChanged, editor, syncEditor); + connect(editor, &ColorEdit::valueEdited, this, [=] { + setValue(editor->value()); }); - editor->setFocusProxy(colorButton); - return editor; } diff --git a/src/tiled/propertyeditorwidgets.cpp b/src/tiled/propertyeditorwidgets.cpp index c7f794a562..122b6fdc28 100644 --- a/src/tiled/propertyeditorwidgets.cpp +++ b/src/tiled/propertyeditorwidgets.cpp @@ -23,12 +23,18 @@ #include "propertiesview.h" #include "utils.h" +#include +#include +#include #include +#include #include #include +#include #include #include #include +#include namespace Tiled { @@ -643,6 +649,132 @@ QRectF RectFEdit::value() const } +class ColorValidator : public QValidator +{ + Q_OBJECT + +public: + using QValidator::QValidator; + + State validate(QString &input, int &) const override + { + if (isValidName(input)) + return State::Acceptable; + + return State::Intermediate; + } + + void fixup(QString &input) const override + { + if (!isValidName(input) && isValidName(QLatin1Char('#') + input)) + input.prepend(QLatin1Char('#')); + } + + static bool isValidName(const QString &name) + { +#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0) + return QColor::isValidColorName(name); +#else + return QColor::isValidColor(name); +#endif + } +}; + + +ColorEdit::ColorEdit(QWidget *parent) + : LineEdit(parent) +{ + setValidator(new ColorValidator(this)); + setClearButtonEnabled(true); + setPlaceholderText(tr("Not set")); + setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); + + m_colorIconAction = addAction(Utils::colorIcon(QColor(), Utils::smallIconSize()), QLineEdit::LeadingPosition); + m_colorIconAction->setText(tr("Pick Color")); + connect(m_colorIconAction, &QAction::triggered, this, &ColorEdit::pickColor); + + connect(this, &QLineEdit::textEdited, this, &ColorEdit::onTextEdited); +} + +void ColorEdit::setValue(const QColor &color) +{ + if (m_color == color) + return; + + m_color = color; + + if (!m_editingText) { + if (!color.isValid()) + setText(QString()); + else if (m_color.alpha() == 255) + setText(color.name(QColor::HexRgb)); + else + setText(color.name(QColor::HexArgb)); + } + + m_colorIconAction->setIcon(Utils::colorIcon(color, Utils::smallIconSize())); + + if (m_editingText) + emit valueEdited(); +} + +void ColorEdit::setShowAlpha(bool enabled) +{ + if (m_showAlpha == enabled) + return; + + m_showAlpha = enabled; +} + +void ColorEdit::contextMenuEvent(QContextMenuEvent *event) +{ + QPointer menu = createStandardContextMenu(); + if (!menu) + return; + + menu->addSeparator(); + menu->addAction(tr("Pick Color"), this, &ColorEdit::pickColor); + menu->exec(event->globalPos()); + delete static_cast(menu); + + event->accept(); +} + +void ColorEdit::onTextEdited() +{ + QScopedValueRollback editing(m_editingText, true); + + const QString text = this->text(); + QColor color; + + if (!text.isEmpty()) { + if (ColorValidator::isValidName(text)) + color = QColor(text); + else if (ColorValidator::isValidName(QLatin1Char('#') + text)) + color = QColor(QLatin1Char('#') + text); + else + return; + } + + setValue(color); +} + +void ColorEdit::pickColor() +{ + QColorDialog::ColorDialogOptions options; + if (m_showAlpha) + options |= QColorDialog::ShowAlphaChannel; + + const QColor newColor = QColorDialog::getColor(m_color, this, QString(), + options); + + if (newColor.isValid() && newColor != m_color) { + setValue(newColor); + emit valueEdited(); + } +} + + ElidingLabel::ElidingLabel(QWidget *parent) : ElidingLabel(QString(), parent) {} diff --git a/src/tiled/propertyeditorwidgets.h b/src/tiled/propertyeditorwidgets.h index d28e6a4e29..c8da68586d 100644 --- a/src/tiled/propertyeditorwidgets.h +++ b/src/tiled/propertyeditorwidgets.h @@ -26,6 +26,7 @@ #include #include +class QAction; class QLabel; namespace Tiled { @@ -284,6 +285,38 @@ class RectFEdit : public QWidget DoubleSpinBox *m_heightSpinBox; }; +/** + * A widget for editing a QColor value. + */ +class ColorEdit : public LineEdit +{ + Q_OBJECT + Q_PROPERTY(QColor value READ value WRITE setValue NOTIFY valueEdited FINAL) + +public: + ColorEdit(QWidget *parent = nullptr); + + void setValue(const QColor &color); + QColor value() const { return m_color; } + + void setShowAlpha(bool enabled); + +signals: + void valueEdited(); + +protected: + void contextMenuEvent(QContextMenuEvent *event) override; + +private: + void onTextEdited(); + void pickColor(); + + QColor m_color; + bool m_showAlpha = false; + bool m_editingText = false; + QAction *m_colorIconAction; +}; + /** * A label that elides its text if there is not enough space. * diff --git a/src/tiled/utils.cpp b/src/tiled/utils.cpp index a33c256869..16ca823d37 100644 --- a/src/tiled/utils.cpp +++ b/src/tiled/utils.cpp @@ -312,11 +312,8 @@ QIcon themeIcon(const QString &name) QIcon colorIcon(const QColor &color, QSize size) { - if (!color.isValid()) - return QIcon(); - QPixmap pixmap(size); - pixmap.fill(color); + pixmap.fill(color.isValid() ? color : Qt::transparent); QPainter painter(&pixmap); painter.setPen(QColor(0, 0, 0, 128));