From ef5f794bc8b461955279f4f73b73d3c1aae7dbae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Thu, 15 Aug 2024 09:08:06 +0200 Subject: [PATCH 01/36] WIP: Rewrite of the property editor --- src/tiled/libtilededitor.qbs | 2 + src/tiled/propertiesdock.cpp | 12 +- src/tiled/propertiesdock.h | 4 +- src/tiled/varianteditor.cpp | 399 +++++++++++++++++++++++++++++++++++ src/tiled/varianteditor.h | 46 ++++ 5 files changed, 455 insertions(+), 8 deletions(-) create mode 100644 src/tiled/varianteditor.cpp create mode 100644 src/tiled/varianteditor.h diff --git a/src/tiled/libtilededitor.qbs b/src/tiled/libtilededitor.qbs index 9e576776b4..fcba0f79ce 100644 --- a/src/tiled/libtilededitor.qbs +++ b/src/tiled/libtilededitor.qbs @@ -553,6 +553,8 @@ DynamicLibrary { "undodock.h", "utils.cpp", "utils.h", + "varianteditor.cpp", + "varianteditor.h", "varianteditorfactory.cpp", "varianteditorfactory.h", "variantpropertymanager.cpp", diff --git a/src/tiled/propertiesdock.cpp b/src/tiled/propertiesdock.cpp index 36cde21f64..f47e2e10f0 100644 --- a/src/tiled/propertiesdock.cpp +++ b/src/tiled/propertiesdock.cpp @@ -20,7 +20,7 @@ #include "propertiesdock.h" -#include "propertieswidget.h" +#include "varianteditor.h" #include @@ -28,26 +28,26 @@ namespace Tiled { PropertiesDock::PropertiesDock(QWidget *parent) : QDockWidget(parent) - , mPropertiesWidget(new PropertiesWidget(this)) + , mPropertiesWidget(new VariantEditor(this)) { setObjectName(QLatin1String("propertiesDock")); setWidget(mPropertiesWidget); - connect(mPropertiesWidget, &PropertiesWidget::bringToFront, - this, &PropertiesDock::bringToFront); + // connect(mPropertiesWidget, &PropertiesWidget::bringToFront, + // this, &PropertiesDock::bringToFront); retranslateUi(); } void PropertiesDock::setDocument(Document *document) { - mPropertiesWidget->setDocument(document); + // mPropertiesWidget->setDocument(document); } void PropertiesDock::selectCustomProperty(const QString &name) { bringToFront(); - mPropertiesWidget->selectCustomProperty(name); + // mPropertiesWidget->selectCustomProperty(name); } bool PropertiesDock::event(QEvent *event) diff --git a/src/tiled/propertiesdock.h b/src/tiled/propertiesdock.h index c39532484d..e5e96dbdd7 100644 --- a/src/tiled/propertiesdock.h +++ b/src/tiled/propertiesdock.h @@ -26,7 +26,7 @@ namespace Tiled { class Document; -class PropertiesWidget; +class VariantEditor; class PropertiesDock : public QDockWidget { @@ -50,7 +50,7 @@ public slots: void bringToFront(); void retranslateUi(); - PropertiesWidget *mPropertiesWidget; + VariantEditor *mPropertiesWidget; }; } // namespace Tiled diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp new file mode 100644 index 0000000000..723e0b8904 --- /dev/null +++ b/src/tiled/varianteditor.cpp @@ -0,0 +1,399 @@ +#include "varianteditor.h" +#include "tiled.h" +#include "utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Tiled { + +// These subclasses don't adjust their horizontal size hint based on the +// minimum or maximum value. +struct MinimumSpinBox : QSpinBox +{ + using QSpinBox::QSpinBox; + + QSize minimumSizeHint() const override + { + auto hint = QSpinBox::minimumSizeHint(); + hint.setWidth(Utils::dpiScaled(50)); + return hint; + } +}; +struct MinimumDoubleSpinBox : QDoubleSpinBox +{ + using QDoubleSpinBox::QDoubleSpinBox; + + QSize minimumSizeHint() const override + { + auto hint = QDoubleSpinBox::minimumSizeHint(); + hint.setWidth(Utils::dpiScaled(50)); + return hint; + } +}; + +// A label that matches its preferred height with that of a line edit +struct LineEditLabel : QLabel +{ + using QLabel::QLabel; + + QSize sizeHint() const override + { + const auto lineEditHint = lineEdit.sizeHint(); + auto hint = QLabel::sizeHint(); + hint.setHeight(lineEditHint.height()); + return hint; + } + +private: + QLineEdit lineEdit; +}; + +constexpr QSizePolicy preferredWidthIgnoredHeight(QSizePolicy::Preferred, QSizePolicy::Fixed); + +class StringEditorFactory : public EditorFactory +{ +public: + QWidget *createEditor(const QVariant &value, QWidget *parent) override + { + auto editor = new QLineEdit(parent); + editor->setSizePolicy(preferredWidthIgnoredHeight); + editor->setText(value.toString()); + return editor; + } +}; + +class IntEditorFactory : public EditorFactory +{ +public: + QWidget *createEditor(const QVariant &value, QWidget *parent) override + { + auto editor = new MinimumSpinBox(parent); + editor->setSizePolicy(preferredWidthIgnoredHeight); + editor->setRange(-std::numeric_limits::max(), + std::numeric_limits::max()); + editor->setValue(value.toInt()); + return editor; + } +}; + +class FloatEditorFactory : public EditorFactory +{ +public: + QWidget *createEditor(const QVariant &value, QWidget *parent) override + { + auto editor = new MinimumDoubleSpinBox(parent); + editor->setSizePolicy(preferredWidthIgnoredHeight); + editor->setRange(-std::numeric_limits::max(), + std::numeric_limits::max()); + editor->setValue(value.toDouble()); + return editor; + } +}; + +class BoolEditorFactory : public EditorFactory +{ +public: + QWidget *createEditor(const QVariant &value, QWidget *parent) override + { + auto editor = new QCheckBox(parent); + bool checked = value.toBool(); + editor->setChecked(checked); + editor->setText(checked ? tr("True") : tr("False")); + + QObject::connect(editor, &QCheckBox::toggled, [editor](bool checked) { + editor->setText(checked ? QObject::tr("True") : QObject::tr("False")); + }); + + return editor; + } +}; + +class PointEditorFactory : public EditorFactory +{ +public: + QWidget *createEditor(const QVariant &value, QWidget *parent) override + { + auto editor = new QWidget(parent); + auto horizontalLayout = new QHBoxLayout(editor); + horizontalLayout->setContentsMargins(QMargins()); + + auto xLabel = new QLabel(QStringLiteral("X"), editor); + horizontalLayout->addWidget(xLabel); + + auto xSpinBox = new MinimumSpinBox(editor); + xSpinBox->setSizePolicy(preferredWidthIgnoredHeight); + xLabel->setBuddy(xSpinBox); + xSpinBox->setRange(-std::numeric_limits::max(), + std::numeric_limits::max()); + xSpinBox->setValue(value.toPoint().x()); + horizontalLayout->addWidget(xSpinBox, 0, Qt::AlignRight); + + horizontalLayout->addStretch(); + + auto yLabel = new QLabel(QStringLiteral("Y"), editor); + horizontalLayout->addWidget(yLabel); + + auto ySpinBox = new MinimumSpinBox(editor); + ySpinBox->setSizePolicy(preferredWidthIgnoredHeight); + yLabel->setBuddy(ySpinBox); + ySpinBox->setRange(-std::numeric_limits::max(), + std::numeric_limits::max()); + ySpinBox->setValue(value.toPoint().y()); + horizontalLayout->addWidget(ySpinBox, 0, Qt::AlignRight); + + // Avoid vertical stretching + auto sizePolicy = editor->sizePolicy(); + sizePolicy.setVerticalPolicy(QSizePolicy::Fixed); + editor->setSizePolicy(sizePolicy); + + return editor; + } +}; + +class SizeEditorFactory : public EditorFactory +{ +public: + QWidget *createEditor(const QVariant &value, QWidget *parent) override + { + auto editor = new QWidget(parent); + auto horizontalLayout = new QHBoxLayout(editor); + horizontalLayout->setContentsMargins(QMargins()); + + auto widthLabel = new QLabel(QStringLiteral("W"), editor); + horizontalLayout->addWidget(widthLabel); + + auto widthSpinBox = new MinimumSpinBox(editor); + widthSpinBox->setSizePolicy(preferredWidthIgnoredHeight); + widthLabel->setBuddy(widthSpinBox); + widthSpinBox->setRange(-std::numeric_limits::max(), + std::numeric_limits::max()); + widthSpinBox->setValue(value.toSize().width()); + horizontalLayout->addWidget(widthSpinBox, 0, Qt::AlignRight); + + horizontalLayout->addStretch(); + + auto heightLabel = new QLabel(QStringLiteral("H"), editor); + horizontalLayout->addWidget(heightLabel); + + auto heightSpinBox = new MinimumSpinBox(editor); + heightSpinBox->setSizePolicy(preferredWidthIgnoredHeight); + heightLabel->setBuddy(heightSpinBox); + heightSpinBox->setRange(-std::numeric_limits::max(), + std::numeric_limits::max()); + heightSpinBox->setValue(value.toSize().height()); + horizontalLayout->addWidget(heightSpinBox, 0, Qt::AlignRight); + + // Avoid vertical stretching + auto sizePolicy = editor->sizePolicy(); + sizePolicy.setVerticalPolicy(QSizePolicy::Fixed); + editor->setSizePolicy(sizePolicy); + + return editor; + } +}; + +class RectFEditorFactory : public EditorFactory +{ +public: + QWidget *createEditor(const QVariant &value, QWidget *parent) override + { + auto editor = new QWidget(parent); + auto gridLayout = new QGridLayout(editor); + gridLayout->setContentsMargins(QMargins()); + + auto xLabel = new QLabel(QStringLiteral("X"), editor); + gridLayout->addWidget(xLabel, 0, 0, Qt::AlignRight); + + auto xSpinBox = new MinimumDoubleSpinBox(editor); + xSpinBox->setSizePolicy(preferredWidthIgnoredHeight); + xLabel->setBuddy(xSpinBox); + xSpinBox->setRange(-std::numeric_limits::max(), + std::numeric_limits::max()); + xSpinBox->setValue(value.toRectF().x()); + gridLayout->addWidget(xSpinBox, 0, 1, Qt::AlignRight); + + // gridLayout->addStretch(); + + auto yLabel = new QLabel(QStringLiteral("Y"), editor); + gridLayout->addWidget(yLabel, 0, 2, Qt::AlignRight); + + auto ySpinBox = new MinimumDoubleSpinBox(editor); + ySpinBox->setSizePolicy(preferredWidthIgnoredHeight); + yLabel->setBuddy(ySpinBox); + ySpinBox->setRange(-std::numeric_limits::max(), + std::numeric_limits::max()); + ySpinBox->setValue(value.toRectF().y()); + gridLayout->addWidget(ySpinBox, 0, 3, Qt::AlignRight); + + auto widthLabel = new QLabel(QStringLiteral("W"), editor); + gridLayout->addWidget(widthLabel, 1, 0, Qt::AlignRight); + + auto widthSpinBox = new MinimumDoubleSpinBox(editor); + widthSpinBox->setSizePolicy(preferredWidthIgnoredHeight); + widthLabel->setBuddy(widthSpinBox); + widthSpinBox->setRange(-std::numeric_limits::max(), + std::numeric_limits::max()); + widthSpinBox->setValue(value.toRectF().width()); + gridLayout->addWidget(widthSpinBox, 1, 1, Qt::AlignRight); + + // horizontalLayout->addStretch(); + + auto heightLabel = new QLabel(QStringLiteral("H"), editor); + gridLayout->addWidget(heightLabel, 1, 2, Qt::AlignRight); + + auto heightSpinBox = new MinimumDoubleSpinBox(editor); + heightSpinBox->setSizePolicy(preferredWidthIgnoredHeight); + heightLabel->setBuddy(heightSpinBox); + heightSpinBox->setRange(-std::numeric_limits::max(), + std::numeric_limits::max()); + heightSpinBox->setValue(value.toRectF().height()); + gridLayout->addWidget(heightSpinBox, 1, 3, Qt::AlignRight); + + // Avoid vertical stretching + auto sizePolicy = editor->sizePolicy(); + sizePolicy.setHorizontalPolicy(QSizePolicy::Preferred); + sizePolicy.setVerticalPolicy(QSizePolicy::Fixed); + editor->setSizePolicy(sizePolicy); + + return editor; + } +}; + +class EnumEditorFactory : public EditorFactory +{ +public: + QWidget *createEditor(const QVariant &value, QWidget *parent) override + { + auto editor = new QComboBox(parent); + editor->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); + editor->addItems(m_enumNames); + editor->setCurrentIndex(value.toInt()); + return editor; + } + + void setEnumNames(const QStringList &enumNames) + { + m_enumNames = enumNames; + } + +private: + QStringList m_enumNames; +}; + + +VariantEditor::VariantEditor(QWidget *parent) + : QScrollArea(parent) +{ + m_widget = new QWidget; + auto verticalLayout = new QVBoxLayout(m_widget); + m_gridLayout = new QGridLayout; + verticalLayout->addLayout(m_gridLayout); + verticalLayout->addStretch(); + + setWidget(m_widget); + setWidgetResizable(true); + // setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + // m_widget->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Ignored)); + + m_gridLayout->setSpacing(Utils::dpiScaled(2)); + + registerEditorFactory(QMetaType::QString, std::make_unique()); + registerEditorFactory(QMetaType::Int, std::make_unique()); + registerEditorFactory(QMetaType::Double, std::make_unique()); + registerEditorFactory(QMetaType::Bool, std::make_unique()); + registerEditorFactory(QMetaType::QPoint, std::make_unique()); + registerEditorFactory(QMetaType::QSize, std::make_unique()); + registerEditorFactory(QMetaType::QRectF, std::make_unique()); + + auto alignmentEditorFactory = std::make_unique(); + alignmentEditorFactory->setEnumNames({ + tr("Unspecified"), + tr("Top Left"), + tr("Top"), + tr("Top Right"), + tr("Left"), + tr("Center"), + tr("Right"), + tr("Bottom Left"), + tr("Bottom"), + tr("Bottom Right"), + }); + registerEditorFactory(qMetaTypeId(), std::move(alignmentEditorFactory)); + + setValue(QVariantMap { + { QStringLiteral("Name"), QVariant(QLatin1String("Hello")) }, + { QStringLiteral("Rectangle"), QVariant(QRectF(15, 50, 35, 400)) }, + { QStringLiteral("Margin"), QVariant(10) }, + { QStringLiteral("Opacity"), QVariant(0.5) }, + { QStringLiteral("Visible"), true }, + { QStringLiteral("Object Alignment"), QVariant::fromValue(TopLeft) }, + }); + + m_gridLayout->setColumnStretch(0, 0); + m_gridLayout->setColumnStretch(1, 0); + m_gridLayout->setColumnStretch(2, 1); + m_gridLayout->setColumnMinimumWidth(1, Utils::dpiScaled(10)); + + // setValue(QVariantList { + // QVariant(QLatin1String("Hello")), + // QVariant(10), + // QVariant(3.14) + // }); +} + +void VariantEditor::registerEditorFactory(int type, std::unique_ptr factory) +{ + m_factories[type] = std::move(factory); +} + +void VariantEditor::setValue(const QVariant &value) +{ + const int type = value.userType(); + switch (type) { + case QMetaType::QVariantList: { + const auto list = value.toList(); + for (const auto &item : list) + setValue(item); + break; + } + case QMetaType::QVariantMap: { + const auto map = value.toMap(); + for (auto it = map.constBegin(); it != map.constEnd(); ++it) { + auto label = new LineEditLabel(it.key(), m_widget); + // label->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + label->setBuddy(new QLabel(m_widget)); + m_gridLayout->addWidget(label, m_rowIndex, 0, Qt::AlignTop | Qt::AlignRight); + setValue(it.value()); + } + break; + } + default: { + auto factory = m_factories.find(type); + if (factory != m_factories.end()) { + const auto editor = factory->second->createEditor(value, m_widget); + m_gridLayout->addWidget(editor, m_rowIndex, 2); + ++m_rowIndex; + } else { + qDebug() << "No editor factory for type" << type; + } + } + } +} + +QSize VariantEditor::viewportSizeHint() const +{ + qDebug() << m_widget->minimumSizeHint() << m_widget->sizeHint(); + return m_widget->minimumSizeHint(); +} + +} // namespace Tiled + +#include "moc_varianteditor.cpp" diff --git a/src/tiled/varianteditor.h b/src/tiled/varianteditor.h new file mode 100644 index 0000000000..ffa974cccd --- /dev/null +++ b/src/tiled/varianteditor.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + +class QGridLayout; + +namespace Tiled { + +class EditorFactory +{ + Q_DECLARE_TR_FUNCTIONS(EditorFactory) + +public: + virtual QWidget *createEditor(const QVariant &value, + QWidget *parent) = 0; +}; + +class VariantEditor : public QScrollArea +{ + Q_OBJECT + +public: + VariantEditor(QWidget *parent = nullptr); + + void registerEditorFactory(int type, std::unique_ptr factory); + + void setValue(const QVariant &value); + +private: + QWidget *m_widget; + QGridLayout *m_gridLayout; + int m_rowIndex = 0; + std::unordered_map> m_factories; + + // QAbstractScrollArea interface +protected: + QSize viewportSizeHint() const override; +}; + +} // namespace Tiled From 141e9332ffb46aceaac01f2b90165513c0b5c80b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Sun, 18 Aug 2024 23:24:54 +0200 Subject: [PATCH 02/36] Progress on new property editor * DoubleSpinBox precision logic ported from existing property editor * Allow spin boxes and combo boxes to shrink horizontally * Elide property names when there isn't enough space * Added QPointF and QColor editor factories * Added test with all built-in map properties --- src/tiled/varianteditor.cpp | 446 ++++++++++++++++++++++++++---------- src/tiled/varianteditor.h | 21 ++ 2 files changed, 346 insertions(+), 121 deletions(-) diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index 723e0b8904..58e9bc9eec 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -1,4 +1,26 @@ +/* + * varianteditor.cpp + * Copyright 2024, Thorbjørn Lindeijer + * + * This file is part of Tiled. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + #include "varianteditor.h" +#include "colorbutton.h" +#include "map.h" #include "tiled.h" #include "utils.h" @@ -8,46 +30,165 @@ #include #include #include +#include #include #include +#include namespace Tiled { -// These subclasses don't adjust their horizontal size hint based on the -// minimum or maximum value. -struct MinimumSpinBox : QSpinBox +class SpinBox : public QSpinBox { - using QSpinBox::QSpinBox; + Q_OBJECT + +public: + SpinBox(QWidget *parent = nullptr) + : QSpinBox(parent) + { + // Allow the full range by default. + setRange(std::numeric_limits::lowest(), + std::numeric_limits::max()); + + // Allow the widget to shrink horizontally. + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + } QSize minimumSizeHint() const override { + // Don't adjust the horizontal size hint based on the maximum value. auto hint = QSpinBox::minimumSizeHint(); hint.setWidth(Utils::dpiScaled(50)); return hint; } }; -struct MinimumDoubleSpinBox : QDoubleSpinBox + +/** + * Strips a floating point number representation of redundant trailing zeros. + * Examples: + * + * 0.01000 -> 0.01 + * 3.000 -> 3.0 + */ +QString removeRedundantTrialingZeros(const QString &text) +{ + const QString decimalPoint = QLocale::system().decimalPoint(); + const auto decimalPointIndex = text.lastIndexOf(decimalPoint); + if (decimalPointIndex < 0) // return if there is no decimal point + return text; + + const auto afterDecimalPoint = decimalPointIndex + decimalPoint.length(); + int redundantZeros = 0; + + for (int i = text.length() - 1; i > afterDecimalPoint && text.at(i) == QLatin1Char('0'); --i) + ++redundantZeros; + + return text.left(text.length() - redundantZeros); +} + +class DoubleSpinBox : public QDoubleSpinBox { - using QDoubleSpinBox::QDoubleSpinBox; + Q_OBJECT + +public: + DoubleSpinBox(QWidget *parent = nullptr) + : QDoubleSpinBox(parent) + { + // Allow the full range by default. + setRange(std::numeric_limits::lowest(), + std::numeric_limits::max()); + + // Increase possible precision. + setDecimals(9); + + // Allow the widget to shrink horizontally. + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + } QSize minimumSizeHint() const override { + // Don't adjust the horizontal size hint based on the maximum value. auto hint = QDoubleSpinBox::minimumSizeHint(); hint.setWidth(Utils::dpiScaled(50)); return hint; } + + // QDoubleSpinBox interface + QString textFromValue(double val) const override + { + auto text = QDoubleSpinBox::textFromValue(val); + + // remove redundant trailing 0's in case of high precision + if (decimals() > 3) + return removeRedundantTrialingZeros(text); + + return text; + } +}; + + +// A label that elides its text if there is not enough space +class ElidingLabel : public QLabel +{ + Q_OBJECT + +public: + explicit ElidingLabel(QWidget *parent = nullptr) + : ElidingLabel(QString(), parent) + {} + + ElidingLabel(const QString &text, QWidget *parent = nullptr) + : QLabel(text, parent) + { + setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed)); + } + + QSize minimumSizeHint() const override + { + auto hint = QLabel::minimumSizeHint(); + hint.setWidth(std::min(hint.width(), Utils::dpiScaled(30))); + return hint; + } + + void paintEvent(QPaintEvent *) override + { + const int m = margin(); + const QRect cr = contentsRect().adjusted(m, m, -m, -m); + const Qt::LayoutDirection dir = text().isRightToLeft() ? Qt::RightToLeft : Qt::LeftToRight; + const int align = QStyle::visualAlignment(dir, alignment()); + const int flags = align | (dir == Qt::LeftToRight ? Qt::TextForceLeftToRight + : Qt::TextForceRightToLeft); + + QStyleOption opt; + opt.initFrom(this); + + const auto elidedText = opt.fontMetrics.elidedText(text(), Qt::ElideRight, cr.width()); + + const bool isElided = elidedText != text(); + if (isElided != m_isElided) { + m_isElided = isElided; + setToolTip(isElided ? text() : QString()); + } + + QPainter painter(this); + QWidget::style()->drawItemText(&painter, cr, flags, opt.palette, isEnabled(), elidedText, foregroundRole()); + } + +private: + bool m_isElided = false; }; // A label that matches its preferred height with that of a line edit -struct LineEditLabel : QLabel +class LineEditLabel : public ElidingLabel { - using QLabel::QLabel; + Q_OBJECT + +public: + using ElidingLabel::ElidingLabel; QSize sizeHint() const override { - const auto lineEditHint = lineEdit.sizeHint(); - auto hint = QLabel::sizeHint(); - hint.setHeight(lineEditHint.height()); + auto hint = ElidingLabel::sizeHint(); + hint.setHeight(lineEdit.sizeHint().height()); return hint; } @@ -55,15 +196,12 @@ struct LineEditLabel : QLabel QLineEdit lineEdit; }; -constexpr QSizePolicy preferredWidthIgnoredHeight(QSizePolicy::Preferred, QSizePolicy::Fixed); - class StringEditorFactory : public EditorFactory { public: QWidget *createEditor(const QVariant &value, QWidget *parent) override { auto editor = new QLineEdit(parent); - editor->setSizePolicy(preferredWidthIgnoredHeight); editor->setText(value.toString()); return editor; } @@ -74,10 +212,7 @@ class IntEditorFactory : public EditorFactory public: QWidget *createEditor(const QVariant &value, QWidget *parent) override { - auto editor = new MinimumSpinBox(parent); - editor->setSizePolicy(preferredWidthIgnoredHeight); - editor->setRange(-std::numeric_limits::max(), - std::numeric_limits::max()); + auto editor = new SpinBox(parent); editor->setValue(value.toInt()); return editor; } @@ -88,10 +223,7 @@ class FloatEditorFactory : public EditorFactory public: QWidget *createEditor(const QVariant &value, QWidget *parent) override { - auto editor = new MinimumDoubleSpinBox(parent); - editor->setSizePolicy(preferredWidthIgnoredHeight); - editor->setRange(-std::numeric_limits::max(), - std::numeric_limits::max()); + auto editor = new DoubleSpinBox(parent); editor->setValue(value.toDouble()); return editor; } @@ -105,10 +237,10 @@ class BoolEditorFactory : public EditorFactory auto editor = new QCheckBox(parent); bool checked = value.toBool(); editor->setChecked(checked); - editor->setText(checked ? tr("True") : tr("False")); + editor->setText(checked ? tr("On") : tr("Off")); QObject::connect(editor, &QCheckBox::toggled, [editor](bool checked) { - editor->setText(checked ? QObject::tr("True") : QObject::tr("False")); + editor->setText(checked ? QObject::tr("On") : QObject::tr("Off")); }); return editor; @@ -125,33 +257,55 @@ class PointEditorFactory : public EditorFactory horizontalLayout->setContentsMargins(QMargins()); auto xLabel = new QLabel(QStringLiteral("X"), editor); - horizontalLayout->addWidget(xLabel); + horizontalLayout->addWidget(xLabel, 0, Qt::AlignRight); - auto xSpinBox = new MinimumSpinBox(editor); - xSpinBox->setSizePolicy(preferredWidthIgnoredHeight); + auto xSpinBox = new SpinBox(editor); xLabel->setBuddy(xSpinBox); - xSpinBox->setRange(-std::numeric_limits::max(), - std::numeric_limits::max()); + horizontalLayout->addWidget(xSpinBox, 1); + + auto yLabel = new QLabel(QStringLiteral("Y"), editor); + horizontalLayout->addWidget(yLabel, 0, Qt::AlignRight); + + auto ySpinBox = new SpinBox(editor); + yLabel->setBuddy(ySpinBox); + horizontalLayout->addWidget(ySpinBox, 1); + + // horizontalLayout->addStretch(); + xSpinBox->setValue(value.toPoint().x()); - horizontalLayout->addWidget(xSpinBox, 0, Qt::AlignRight); + ySpinBox->setValue(value.toPoint().y()); - horizontalLayout->addStretch(); + return editor; + } +}; + +class PointFEditorFactory : public EditorFactory +{ +public: + QWidget *createEditor(const QVariant &value, QWidget *parent) override + { + auto editor = new QWidget(parent); + auto horizontalLayout = new QHBoxLayout(editor); + horizontalLayout->setContentsMargins(QMargins()); + + auto xLabel = new QLabel(QStringLiteral("X"), editor); + horizontalLayout->addWidget(xLabel, 0, Qt::AlignRight); + + auto xSpinBox = new DoubleSpinBox(editor); + xLabel->setBuddy(xSpinBox); + horizontalLayout->addWidget(xSpinBox, 1); auto yLabel = new QLabel(QStringLiteral("Y"), editor); - horizontalLayout->addWidget(yLabel); + horizontalLayout->addWidget(yLabel, 0, Qt::AlignRight); - auto ySpinBox = new MinimumSpinBox(editor); - ySpinBox->setSizePolicy(preferredWidthIgnoredHeight); + auto ySpinBox = new DoubleSpinBox(editor); yLabel->setBuddy(ySpinBox); - ySpinBox->setRange(-std::numeric_limits::max(), - std::numeric_limits::max()); - ySpinBox->setValue(value.toPoint().y()); - horizontalLayout->addWidget(ySpinBox, 0, Qt::AlignRight); + horizontalLayout->addWidget(ySpinBox, 1); - // Avoid vertical stretching - auto sizePolicy = editor->sizePolicy(); - sizePolicy.setVerticalPolicy(QSizePolicy::Fixed); - editor->setSizePolicy(sizePolicy); + // horizontalLayout->addStretch(); + + xSpinBox->setValue(value.toPointF().x()); + ySpinBox->setValue(value.toPointF().y()); return editor; } @@ -167,33 +321,25 @@ class SizeEditorFactory : public EditorFactory horizontalLayout->setContentsMargins(QMargins()); auto widthLabel = new QLabel(QStringLiteral("W"), editor); - horizontalLayout->addWidget(widthLabel); + widthLabel->setToolTip(tr("Width")); + horizontalLayout->addWidget(widthLabel, 0, Qt::AlignRight); - auto widthSpinBox = new MinimumSpinBox(editor); - widthSpinBox->setSizePolicy(preferredWidthIgnoredHeight); + auto widthSpinBox = new SpinBox(editor); widthLabel->setBuddy(widthSpinBox); - widthSpinBox->setRange(-std::numeric_limits::max(), - std::numeric_limits::max()); - widthSpinBox->setValue(value.toSize().width()); - horizontalLayout->addWidget(widthSpinBox, 0, Qt::AlignRight); - - horizontalLayout->addStretch(); + horizontalLayout->addWidget(widthSpinBox, 1); auto heightLabel = new QLabel(QStringLiteral("H"), editor); - horizontalLayout->addWidget(heightLabel); + heightLabel->setToolTip(tr("Height")); + horizontalLayout->addWidget(heightLabel, 0, Qt::AlignRight); - auto heightSpinBox = new MinimumSpinBox(editor); - heightSpinBox->setSizePolicy(preferredWidthIgnoredHeight); + auto heightSpinBox = new SpinBox(editor); heightLabel->setBuddy(heightSpinBox); - heightSpinBox->setRange(-std::numeric_limits::max(), - std::numeric_limits::max()); - heightSpinBox->setValue(value.toSize().height()); - horizontalLayout->addWidget(heightSpinBox, 0, Qt::AlignRight); + horizontalLayout->addWidget(heightSpinBox, 1); - // Avoid vertical stretching - auto sizePolicy = editor->sizePolicy(); - sizePolicy.setVerticalPolicy(QSizePolicy::Fixed); - editor->setSizePolicy(sizePolicy); + // horizontalLayout->addStretch(); + + widthSpinBox->setValue(value.toSize().width()); + heightSpinBox->setValue(value.toSize().height()); return editor; } @@ -207,84 +353,79 @@ class RectFEditorFactory : public EditorFactory auto editor = new QWidget(parent); auto gridLayout = new QGridLayout(editor); gridLayout->setContentsMargins(QMargins()); + gridLayout->setColumnStretch(4, 1); auto xLabel = new QLabel(QStringLiteral("X"), editor); gridLayout->addWidget(xLabel, 0, 0, Qt::AlignRight); - auto xSpinBox = new MinimumDoubleSpinBox(editor); - xSpinBox->setSizePolicy(preferredWidthIgnoredHeight); + auto xSpinBox = new DoubleSpinBox(editor); xLabel->setBuddy(xSpinBox); - xSpinBox->setRange(-std::numeric_limits::max(), - std::numeric_limits::max()); - xSpinBox->setValue(value.toRectF().x()); - gridLayout->addWidget(xSpinBox, 0, 1, Qt::AlignRight); - - // gridLayout->addStretch(); + gridLayout->addWidget(xSpinBox, 0, 1); auto yLabel = new QLabel(QStringLiteral("Y"), editor); gridLayout->addWidget(yLabel, 0, 2, Qt::AlignRight); - auto ySpinBox = new MinimumDoubleSpinBox(editor); - ySpinBox->setSizePolicy(preferredWidthIgnoredHeight); + auto ySpinBox = new DoubleSpinBox(editor); yLabel->setBuddy(ySpinBox); - ySpinBox->setRange(-std::numeric_limits::max(), - std::numeric_limits::max()); - ySpinBox->setValue(value.toRectF().y()); - gridLayout->addWidget(ySpinBox, 0, 3, Qt::AlignRight); + gridLayout->addWidget(ySpinBox, 0, 3); auto widthLabel = new QLabel(QStringLiteral("W"), editor); + widthLabel->setToolTip(tr("Width")); gridLayout->addWidget(widthLabel, 1, 0, Qt::AlignRight); - auto widthSpinBox = new MinimumDoubleSpinBox(editor); - widthSpinBox->setSizePolicy(preferredWidthIgnoredHeight); + auto widthSpinBox = new DoubleSpinBox(editor); widthLabel->setBuddy(widthSpinBox); - widthSpinBox->setRange(-std::numeric_limits::max(), - std::numeric_limits::max()); - widthSpinBox->setValue(value.toRectF().width()); - gridLayout->addWidget(widthSpinBox, 1, 1, Qt::AlignRight); - - // horizontalLayout->addStretch(); + gridLayout->addWidget(widthSpinBox, 1, 1); auto heightLabel = new QLabel(QStringLiteral("H"), editor); + heightLabel->setToolTip(tr("Height")); gridLayout->addWidget(heightLabel, 1, 2, Qt::AlignRight); - auto heightSpinBox = new MinimumDoubleSpinBox(editor); - heightSpinBox->setSizePolicy(preferredWidthIgnoredHeight); + auto heightSpinBox = new DoubleSpinBox(editor); heightLabel->setBuddy(heightSpinBox); - heightSpinBox->setRange(-std::numeric_limits::max(), - std::numeric_limits::max()); - heightSpinBox->setValue(value.toRectF().height()); - gridLayout->addWidget(heightSpinBox, 1, 3, Qt::AlignRight); + gridLayout->addWidget(heightSpinBox, 1, 3); - // Avoid vertical stretching - auto sizePolicy = editor->sizePolicy(); - sizePolicy.setHorizontalPolicy(QSizePolicy::Preferred); - sizePolicy.setVerticalPolicy(QSizePolicy::Fixed); - editor->setSizePolicy(sizePolicy); + const auto rect = value.toRectF(); + xSpinBox->setValue(rect.x()); + ySpinBox->setValue(rect.y()); + widthSpinBox->setValue(rect.width()); + heightSpinBox->setValue(rect.height()); return editor; } }; +class ColorEditorFactory : public EditorFactory +{ +public: + QWidget *createEditor(const QVariant &value, QWidget *parent) override + { + auto editor = new ColorButton(parent); + editor->setColor(value.value()); + return editor; + } +}; + class EnumEditorFactory : public EditorFactory { public: QWidget *createEditor(const QVariant &value, QWidget *parent) override { auto editor = new QComboBox(parent); + // This allows the combo box to shrink horizontally. editor->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); - editor->addItems(m_enumNames); + editor->setModel(&m_enumNamesModel); editor->setCurrentIndex(value.toInt()); return editor; } void setEnumNames(const QStringList &enumNames) { - m_enumNames = enumNames; + m_enumNamesModel.setStringList(enumNames); } private: - QStringList m_enumNames; + QStringListModel m_enumNamesModel; }; @@ -299,9 +440,6 @@ VariantEditor::VariantEditor(QWidget *parent) setWidget(m_widget); setWidgetResizable(true); - // setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - - // m_widget->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Ignored)); m_gridLayout->setSpacing(Utils::dpiScaled(2)); @@ -310,8 +448,10 @@ VariantEditor::VariantEditor(QWidget *parent) registerEditorFactory(QMetaType::Double, std::make_unique()); registerEditorFactory(QMetaType::Bool, std::make_unique()); registerEditorFactory(QMetaType::QPoint, std::make_unique()); + registerEditorFactory(QMetaType::QPointF, std::make_unique()); registerEditorFactory(QMetaType::QSize, std::make_unique()); registerEditorFactory(QMetaType::QRectF, std::make_unique()); + registerEditorFactory(QMetaType::QColor, std::make_unique()); auto alignmentEditorFactory = std::make_unique(); alignmentEditorFactory->setEnumNames({ @@ -328,17 +468,63 @@ VariantEditor::VariantEditor(QWidget *parent) }); registerEditorFactory(qMetaTypeId(), std::move(alignmentEditorFactory)); - setValue(QVariantMap { - { QStringLiteral("Name"), QVariant(QLatin1String("Hello")) }, - { QStringLiteral("Rectangle"), QVariant(QRectF(15, 50, 35, 400)) }, - { QStringLiteral("Margin"), QVariant(10) }, - { QStringLiteral("Opacity"), QVariant(0.5) }, - { QStringLiteral("Visible"), true }, - { QStringLiteral("Object Alignment"), QVariant::fromValue(TopLeft) }, - }); - - m_gridLayout->setColumnStretch(0, 0); - m_gridLayout->setColumnStretch(1, 0); + auto orientationEditorFactory = std::make_unique(); + orientationEditorFactory->setEnumNames({ + tr("Unknown"), // todo: hide this one + tr("Orthogonal"), + tr("Isometric"), + tr("Staggered"), + tr("Hexagonal (Staggered)"), + }); + registerEditorFactory(qMetaTypeId(), std::move(orientationEditorFactory)); + + auto staggerAxisEditorFactory = std::make_unique(); + staggerAxisEditorFactory->setEnumNames({ + tr("X"), + tr("Y"), + }); + registerEditorFactory(qMetaTypeId(), std::move(staggerAxisEditorFactory)); + + auto staggerIndexEditorFactory = std::make_unique(); + staggerIndexEditorFactory->setEnumNames({ + tr("Odd"), + tr("Even"), + }); + registerEditorFactory(qMetaTypeId(), std::move(staggerIndexEditorFactory)); + + auto layerFormatEditorFactory = std::make_unique(); + layerFormatEditorFactory->setEnumNames({ + tr("XML (deprecated)"), + tr("Base64"), + tr("Base64Gzip"), + tr("Base64Zlib"), + tr("Base64Zstandard"), + tr("CSV"), + }); + registerEditorFactory(qMetaTypeId(), std::move(layerFormatEditorFactory)); + + auto renderOrderEditorFactory = std::make_unique(); + renderOrderEditorFactory->setEnumNames({ + tr("Right Down"), + tr("Right Up"), + tr("Left Down"), + tr("Left Up"), + }); + registerEditorFactory(qMetaTypeId(), std::move(renderOrderEditorFactory)); + + // setValue(QVariantMap { + // { QStringLiteral("Name"), QVariant(QLatin1String("Hello")) }, + // { QStringLiteral("Position"), QVariant(QPoint(15, 50)) }, + // { QStringLiteral("Size"), QVariant(QSize(35, 400)) }, + // { QStringLiteral("Rectangle"), QVariant(QRectF(15, 50, 35, 400)) }, + // { QStringLiteral("Margin"), QVariant(10) }, + // { QStringLiteral("Opacity"), QVariant(0.5) }, + // { QStringLiteral("Visible"), true }, + // { QStringLiteral("Object Alignment"), QVariant::fromValue(TopLeft) }, + // }); + + // m_gridLayout->setColumnStretch(0, 0); + // m_gridLayout->setColumnStretch(1, 0); m_gridLayout->setColumnStretch(2, 1); m_gridLayout->setColumnMinimumWidth(1, Utils::dpiScaled(10)); @@ -347,6 +533,21 @@ VariantEditor::VariantEditor(QWidget *parent) // QVariant(10), // QVariant(3.14) // }); + + addValue(tr("Class"), QString()); + addValue(tr("Orientation"), QVariant::fromValue(Map::Hexagonal)); + addValue(tr("Size"), QSize(20, 20)); + addValue(tr("Tile Size"), QSize(14, 12)); + addValue(tr("Infinite"), false); + addValue(tr("Tile Side Length (Hex)"), 6); + addValue(tr("Stagger Axis"), QVariant::fromValue(Map::StaggerY)); + addValue(tr("Stagger Index"), QVariant::fromValue(Map::StaggerEven)); + addValue(tr("Parallax Origin"), QPointF()); + addValue(tr("Tile Layer Format"), QVariant::fromValue(Map::Base64Zlib)); + addValue(tr("Output Chunk Size"), QSize(16, 16)); + addValue(tr("Tile Render Order"), QVariant::fromValue(Map::RightDown)); + addValue(tr("Compression Level"), -1); + addValue(tr("Background Color"), QColor()); } void VariantEditor::registerEditorFactory(int type, std::unique_ptr factory) @@ -354,6 +555,14 @@ void VariantEditor::registerEditorFactory(int type, std::unique_ptrsetBuddy(new QLabel(m_widget)); + m_gridLayout->addWidget(label, m_rowIndex, 0, Qt::AlignTop | Qt::AlignRight); + setValue(value); +} + void VariantEditor::setValue(const QVariant &value) { const int type = value.userType(); @@ -366,13 +575,8 @@ void VariantEditor::setValue(const QVariant &value) } case QMetaType::QVariantMap: { const auto map = value.toMap(); - for (auto it = map.constBegin(); it != map.constEnd(); ++it) { - auto label = new LineEditLabel(it.key(), m_widget); - // label->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); - label->setBuddy(new QLabel(m_widget)); - m_gridLayout->addWidget(label, m_rowIndex, 0, Qt::AlignTop | Qt::AlignRight); - setValue(it.value()); - } + for (auto it = map.constBegin(); it != map.constEnd(); ++it) + addValue(it.key(), it.value()); break; } default: { @@ -380,20 +584,20 @@ void VariantEditor::setValue(const QVariant &value) if (factory != m_factories.end()) { const auto editor = factory->second->createEditor(value, m_widget); m_gridLayout->addWidget(editor, m_rowIndex, 2); - ++m_rowIndex; } else { qDebug() << "No editor factory for type" << type; } + ++m_rowIndex; } } } QSize VariantEditor::viewportSizeHint() const { - qDebug() << m_widget->minimumSizeHint() << m_widget->sizeHint(); return m_widget->minimumSizeHint(); } } // namespace Tiled #include "moc_varianteditor.cpp" +#include "varianteditor.moc" diff --git a/src/tiled/varianteditor.h b/src/tiled/varianteditor.h index ffa974cccd..af94988d8f 100644 --- a/src/tiled/varianteditor.h +++ b/src/tiled/varianteditor.h @@ -1,3 +1,23 @@ +/* + * varianteditor.h + * Copyright 2024, Thorbjørn Lindeijer + * + * This file is part of Tiled. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + #pragma once #include @@ -30,6 +50,7 @@ class VariantEditor : public QScrollArea void registerEditorFactory(int type, std::unique_ptr factory); + void addValue(const QString &name, const QVariant &value); void setValue(const QVariant &value); private: From 82be47766ba316b32edca5aadf51ddfe39d674b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Tue, 20 Aug 2024 18:01:09 +0200 Subject: [PATCH 03/36] Progress on new property editor 2 * Added headers (not collapsible for now). * Added separators. * Added support for non-consecutive enums in EnumEditorFactory. * Stretch name and editor widgets by a fixed 2:3 ratio and ignore their size hint. * Some start on possible API for setting/getting the value. --- src/tiled/varianteditor.cpp | 127 +++++++++++++++++++++++++++++------- src/tiled/varianteditor.h | 17 ++++- 2 files changed, 118 insertions(+), 26 deletions(-) diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index 58e9bc9eec..d2bc1f7327 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -20,6 +20,7 @@ #include "varianteditor.h" #include "colorbutton.h" +#include "compression.h" #include "map.h" #include "tiled.h" #include "utils.h" @@ -205,6 +206,11 @@ class StringEditorFactory : public EditorFactory editor->setText(value.toString()); return editor; } + + QVariant value(QWidget *editor) const override + { + return static_cast(editor)->text(); + } }; class IntEditorFactory : public EditorFactory @@ -415,7 +421,12 @@ class EnumEditorFactory : public EditorFactory // This allows the combo box to shrink horizontally. editor->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); editor->setModel(&m_enumNamesModel); - editor->setCurrentIndex(value.toInt()); + + if (m_enumValues.isEmpty()) + editor->setCurrentIndex(value.toInt()); + else + editor->setCurrentIndex(m_enumValues.indexOf(value.toInt())); + return editor; } @@ -424,8 +435,14 @@ class EnumEditorFactory : public EditorFactory m_enumNamesModel.setStringList(enumNames); } + void setEnumValues(const QList &enumValues) + { + m_enumValues = enumValues; + } + private: QStringListModel m_enumNamesModel; + QList m_enumValues; }; @@ -437,11 +454,19 @@ VariantEditor::VariantEditor(QWidget *parent) m_gridLayout = new QGridLayout; verticalLayout->addLayout(m_gridLayout); verticalLayout->addStretch(); + verticalLayout->setContentsMargins(0, 0, 0, 0); setWidget(m_widget); setWidgetResizable(true); - m_gridLayout->setSpacing(Utils::dpiScaled(2)); + m_gridLayout->setContentsMargins(0, 0, 0, 0); + m_gridLayout->setSpacing(Utils::dpiScaled(3)); + + m_gridLayout->setColumnStretch(LabelColumn, 2); + m_gridLayout->setColumnStretch(WidgetColumn, 3); + m_gridLayout->setColumnMinimumWidth(LeftSpacing, Utils::dpiScaled(3)); + m_gridLayout->setColumnMinimumWidth(MiddleSpacing, Utils::dpiScaled(2)); + m_gridLayout->setColumnMinimumWidth(RightSpacing, Utils::dpiScaled(3)); registerEditorFactory(QMetaType::QString, std::make_unique()); registerEditorFactory(QMetaType::Int, std::make_unique()); @@ -470,12 +495,17 @@ VariantEditor::VariantEditor(QWidget *parent) auto orientationEditorFactory = std::make_unique(); orientationEditorFactory->setEnumNames({ - tr("Unknown"), // todo: hide this one tr("Orthogonal"), tr("Isometric"), - tr("Staggered"), + tr("Isometric (Staggered)"), tr("Hexagonal (Staggered)"), }); + orientationEditorFactory->setEnumValues({ + Map::Orthogonal, + Map::Isometric, + Map::Staggered, + Map::Hexagonal, + }); registerEditorFactory(qMetaTypeId(), std::move(orientationEditorFactory)); auto staggerAxisEditorFactory = std::make_unique(); @@ -492,15 +522,30 @@ VariantEditor::VariantEditor(QWidget *parent) }); registerEditorFactory(qMetaTypeId(), std::move(staggerIndexEditorFactory)); + QStringList layerFormatNames = { + QCoreApplication::translate("PreferencesDialog", "XML (deprecated)"), + QCoreApplication::translate("PreferencesDialog", "Base64 (uncompressed)"), + QCoreApplication::translate("PreferencesDialog", "Base64 (gzip compressed)"), + QCoreApplication::translate("PreferencesDialog", "Base64 (zlib compressed)"), + }; + QList layerFormatValues = { + Map::XML, + Map::Base64, + Map::Base64Gzip, + Map::Base64Zlib, + }; + + if (compressionSupported(Zstandard)) { + layerFormatNames.append(QCoreApplication::translate("PreferencesDialog", "Base64 (Zstandard compressed)")); + layerFormatValues.append(Map::Base64Zstandard); + } + + layerFormatNames.append(QCoreApplication::translate("PreferencesDialog", "CSV")); + layerFormatValues.append(Map::CSV); + auto layerFormatEditorFactory = std::make_unique(); - layerFormatEditorFactory->setEnumNames({ - tr("XML (deprecated)"), - tr("Base64"), - tr("Base64Gzip"), - tr("Base64Zlib"), - tr("Base64Zstandard"), - tr("CSV"), - }); + layerFormatEditorFactory->setEnumNames(layerFormatNames); + layerFormatEditorFactory->setEnumValues(layerFormatValues); registerEditorFactory(qMetaTypeId(), std::move(layerFormatEditorFactory)); auto renderOrderEditorFactory = std::make_unique(); @@ -523,10 +568,6 @@ VariantEditor::VariantEditor(QWidget *parent) // { QStringLiteral("Object Alignment"), QVariant::fromValue(TopLeft) }, // }); - // m_gridLayout->setColumnStretch(0, 0); - // m_gridLayout->setColumnStretch(1, 0); - m_gridLayout->setColumnStretch(2, 1); - m_gridLayout->setColumnMinimumWidth(1, Utils::dpiScaled(10)); // setValue(QVariantList { // QVariant(QLatin1String("Hello")), @@ -534,20 +575,26 @@ VariantEditor::VariantEditor(QWidget *parent) // QVariant(3.14) // }); + addHeader(tr("Map")); addValue(tr("Class"), QString()); + addSeparator(); addValue(tr("Orientation"), QVariant::fromValue(Map::Hexagonal)); - addValue(tr("Size"), QSize(20, 20)); - addValue(tr("Tile Size"), QSize(14, 12)); addValue(tr("Infinite"), false); + addValue(tr("Map Size"), QSize(20, 20)); + addValue(tr("Tile Size"), QSize(14, 12)); addValue(tr("Tile Side Length (Hex)"), 6); addValue(tr("Stagger Axis"), QVariant::fromValue(Map::StaggerY)); addValue(tr("Stagger Index"), QVariant::fromValue(Map::StaggerEven)); + addSeparator(); addValue(tr("Parallax Origin"), QPointF()); + addSeparator(); addValue(tr("Tile Layer Format"), QVariant::fromValue(Map::Base64Zlib)); addValue(tr("Output Chunk Size"), QSize(16, 16)); - addValue(tr("Tile Render Order"), QVariant::fromValue(Map::RightDown)); addValue(tr("Compression Level"), -1); + addSeparator(); + addValue(tr("Tile Render Order"), QVariant::fromValue(Map::RightDown)); addValue(tr("Background Color"), QColor()); + addHeader(tr("Custom Properties")); } void VariantEditor::registerEditorFactory(int type, std::unique_ptr factory) @@ -555,22 +602,51 @@ void VariantEditor::registerEditorFactory(int type, std::unique_ptrfont(); + boldFont.setBold(true); + label->setFont(boldFont); + label->setBackgroundRole(QPalette::Dark); + const int verticalMargin = Utils::dpiScaled(3); + const int horizontalMargin = Utils::dpiScaled(6); + label->setContentsMargins(horizontalMargin, verticalMargin, + horizontalMargin, verticalMargin); + + label->setAutoFillBackground(true); + + m_gridLayout->addWidget(label, m_rowIndex, 0, 1, ColumnCount); + ++m_rowIndex; +} + +void VariantEditor::addSeparator() +{ + auto separator = new QFrame(m_widget); + separator->setFrameShape(QFrame::HLine); + separator->setFrameShadow(QFrame::Plain); + separator->setForegroundRole(QPalette::Dark); + m_gridLayout->addWidget(separator, m_rowIndex, 0, 1, ColumnCount); + ++m_rowIndex; +} + void VariantEditor::addValue(const QString &name, const QVariant &value) { auto label = new LineEditLabel(name, m_widget); - label->setBuddy(new QLabel(m_widget)); - m_gridLayout->addWidget(label, m_rowIndex, 0, Qt::AlignTop | Qt::AlignRight); - setValue(value); + // label->setBuddy(m_widget)); // todo: associate with widget somehow + label->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); + m_gridLayout->addWidget(label, m_rowIndex, LabelColumn, Qt::AlignTop/* | Qt::AlignRight*/); + addValue(value); } -void VariantEditor::setValue(const QVariant &value) +void VariantEditor::addValue(const QVariant &value) { const int type = value.userType(); switch (type) { case QMetaType::QVariantList: { const auto list = value.toList(); for (const auto &item : list) - setValue(item); + addValue(item); break; } case QMetaType::QVariantMap: { @@ -583,7 +659,8 @@ void VariantEditor::setValue(const QVariant &value) auto factory = m_factories.find(type); if (factory != m_factories.end()) { const auto editor = factory->second->createEditor(value, m_widget); - m_gridLayout->addWidget(editor, m_rowIndex, 2); + editor->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); + m_gridLayout->addWidget(editor, m_rowIndex, WidgetColumn); } else { qDebug() << "No editor factory for type" << type; } diff --git a/src/tiled/varianteditor.h b/src/tiled/varianteditor.h index af94988d8f..2e46be7ead 100644 --- a/src/tiled/varianteditor.h +++ b/src/tiled/varianteditor.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -39,6 +40,9 @@ class EditorFactory public: virtual QWidget *createEditor(const QVariant &value, QWidget *parent) = 0; + + virtual QVariant value(QWidget *editor) const { return {}; } + virtual void setValue(QWidget *editor, const QVariant &value) {}; }; class VariantEditor : public QScrollArea @@ -50,10 +54,21 @@ class VariantEditor : public QScrollArea void registerEditorFactory(int type, std::unique_ptr factory); + void addHeader(const QString &text); + void addSeparator(); void addValue(const QString &name, const QVariant &value); - void setValue(const QVariant &value); + void addValue(const QVariant &value); private: + enum Column { + LeftSpacing, + LabelColumn, + MiddleSpacing, + WidgetColumn, + RightSpacing, + ColumnCount, + }; + QWidget *m_widget; QGridLayout *m_gridLayout; int m_rowIndex = 0; From 3c6ab56227b30acf83ef258989064e63f412fc69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Wed, 21 Aug 2024 15:50:06 +0200 Subject: [PATCH 04/36] Introduced a responsive editor for QSize It adjusts the layout based on its width. Also made a few other visual tweaks. --- src/tiled/varianteditor.cpp | 153 +++++++++++++++++++++++++++--------- src/tiled/varianteditor.h | 24 +++++- 2 files changed, 135 insertions(+), 42 deletions(-) diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index d2bc1f7327..7b1a1357f6 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -317,36 +317,107 @@ class PointFEditorFactory : public EditorFactory } }; -class SizeEditorFactory : public EditorFactory +class SizeEditor : public QWidget { public: - QWidget *createEditor(const QVariant &value, QWidget *parent) override + SizeEditor(QWidget *parent = nullptr) + : QWidget(parent) + , m_widthLabel(new QLabel(QStringLiteral("W"), this)) + , m_heightLabel(new QLabel(QStringLiteral("H"), this)) + , m_widthSpinBox(new SpinBox(this)) + , m_heightSpinBox(new SpinBox(this)) { - auto editor = new QWidget(parent); - auto horizontalLayout = new QHBoxLayout(editor); - horizontalLayout->setContentsMargins(QMargins()); - - auto widthLabel = new QLabel(QStringLiteral("W"), editor); - widthLabel->setToolTip(tr("Width")); - horizontalLayout->addWidget(widthLabel, 0, Qt::AlignRight); + m_widthLabel->setAlignment(Qt::AlignCenter); + m_heightLabel->setAlignment(Qt::AlignCenter); + + auto layout = new QGridLayout(this); + layout->setContentsMargins(QMargins()); + layout->setColumnStretch(1, 1); + layout->setColumnStretch(3, 1); + layout->setSpacing(Utils::dpiScaled(3)); + + const int horizontalMargin = Utils::dpiScaled(3); + m_widthLabel->setContentsMargins(horizontalMargin, 0, horizontalMargin, 0); + m_heightLabel->setContentsMargins(horizontalMargin, 0, horizontalMargin, 0); + + layout->addWidget(m_widthLabel, 0, 0); + layout->addWidget(m_widthSpinBox, 0, 1); + layout->addWidget(m_heightLabel, 0, 2); + layout->addWidget(m_heightSpinBox, 0, 3); + } - auto widthSpinBox = new SpinBox(editor); - widthLabel->setBuddy(widthSpinBox); - horizontalLayout->addWidget(widthSpinBox, 1); + void setValue(const QSize &size) + { + m_widthSpinBox->setValue(size.width()); + m_heightSpinBox->setValue(size.height()); + } - auto heightLabel = new QLabel(QStringLiteral("H"), editor); - heightLabel->setToolTip(tr("Height")); - horizontalLayout->addWidget(heightLabel, 0, Qt::AlignRight); + QSize value() const + { + return QSize(m_widthSpinBox->value(), m_heightSpinBox->value()); + } - auto heightSpinBox = new SpinBox(editor); - heightLabel->setBuddy(heightSpinBox); - horizontalLayout->addWidget(heightSpinBox, 1); +private: + void resizeEvent(QResizeEvent *event) override + { + QWidget::resizeEvent(event); + + const auto direction = event->size().width() < minimumHorizontalWidth() + ? Qt::Vertical : Qt::Horizontal; + + if (m_direction != direction) { + m_direction = direction; + + auto layout = qobject_cast(this->layout()); + + // Remove all widgets from layout, without deleting them + layout->removeWidget(m_widthLabel); + layout->removeWidget(m_widthSpinBox); + layout->removeWidget(m_heightLabel); + layout->removeWidget(m_heightSpinBox); + + if (direction == Qt::Horizontal) { + layout->addWidget(m_widthLabel, 0, 0); + layout->addWidget(m_widthSpinBox, 0, 1); + layout->addWidget(m_heightLabel, 0, 2); + layout->addWidget(m_heightSpinBox, 0, 3); + layout->setColumnStretch(3, 1); + } else { + layout->addWidget(m_widthLabel, 0, 0); + layout->addWidget(m_widthSpinBox, 0, 1); + layout->addWidget(m_heightLabel, 1, 0); + layout->addWidget(m_heightSpinBox, 1, 1); + layout->setColumnStretch(3, 0); + } + + // this avoids flickering when the layout changes + layout->activate(); + } + } - // horizontalLayout->addStretch(); + int minimumHorizontalWidth() const + { + return m_widthLabel->minimumSizeHint().width() + + m_widthSpinBox->minimumSizeHint().width() + + m_heightLabel->minimumSizeHint().width() + + m_heightSpinBox->minimumSizeHint().width() + + layout()->spacing() * 3; + } - widthSpinBox->setValue(value.toSize().width()); - heightSpinBox->setValue(value.toSize().height()); + Qt::Orientation m_direction = Qt::Horizontal; + QLabel *m_widthLabel; + QLabel *m_heightLabel; + SpinBox *m_widthSpinBox; + SpinBox *m_heightSpinBox; +}; +class SizeEditorFactory : public EditorFactory +{ +public: + QWidget *createEditor(const QVariant &value, QWidget *parent) override + { + auto editor = new SizeEditor(parent); + editor->setValue(value.toSize()); return editor; } }; @@ -450,16 +521,17 @@ VariantEditor::VariantEditor(QWidget *parent) : QScrollArea(parent) { m_widget = new QWidget; + m_widget->setBackgroundRole(QPalette::Base); auto verticalLayout = new QVBoxLayout(m_widget); m_gridLayout = new QGridLayout; verticalLayout->addLayout(m_gridLayout); verticalLayout->addStretch(); - verticalLayout->setContentsMargins(0, 0, 0, 0); + verticalLayout->setContentsMargins(QMargins()); setWidget(m_widget); setWidgetResizable(true); - m_gridLayout->setContentsMargins(0, 0, 0, 0); + m_gridLayout->setContentsMargins(QMargins()); m_gridLayout->setSpacing(Utils::dpiScaled(3)); m_gridLayout->setColumnStretch(LabelColumn, 2); @@ -605,9 +677,6 @@ void VariantEditor::registerEditorFactory(int type, std::unique_ptrfont(); - boldFont.setBold(true); - label->setFont(boldFont); label->setBackgroundRole(QPalette::Dark); const int verticalMargin = Utils::dpiScaled(3); const int horizontalMargin = Utils::dpiScaled(6); @@ -625,7 +694,7 @@ void VariantEditor::addSeparator() auto separator = new QFrame(m_widget); separator->setFrameShape(QFrame::HLine); separator->setFrameShadow(QFrame::Plain); - separator->setForegroundRole(QPalette::Dark); + separator->setForegroundRole(QPalette::Mid); m_gridLayout->addWidget(separator, m_rowIndex, 0, 1, ColumnCount); ++m_rowIndex; } @@ -633,10 +702,12 @@ void VariantEditor::addSeparator() void VariantEditor::addValue(const QString &name, const QVariant &value) { auto label = new LineEditLabel(name, m_widget); - // label->setBuddy(m_widget)); // todo: associate with widget somehow label->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); m_gridLayout->addWidget(label, m_rowIndex, LabelColumn, Qt::AlignTop/* | Qt::AlignRight*/); - addValue(value); + if (auto editor = createEditor(value)) { + m_gridLayout->addWidget(editor, m_rowIndex, WidgetColumn); + } + ++m_rowIndex; } void VariantEditor::addValue(const QVariant &value) @@ -656,22 +727,28 @@ void VariantEditor::addValue(const QVariant &value) break; } default: { - auto factory = m_factories.find(type); - if (factory != m_factories.end()) { - const auto editor = factory->second->createEditor(value, m_widget); - editor->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); - m_gridLayout->addWidget(editor, m_rowIndex, WidgetColumn); - } else { + if (auto editor = createEditor(value)) + m_gridLayout->addWidget(editor, m_rowIndex, LabelColumn, 1, 3); + else qDebug() << "No editor factory for type" << type; - } + ++m_rowIndex; } } } -QSize VariantEditor::viewportSizeHint() const +QWidget *VariantEditor::createEditor(const QVariant &value) { - return m_widget->minimumSizeHint(); + const int type = value.userType(); + auto factory = m_factories.find(type); + if (factory != m_factories.end()) { + const auto editor = factory->second->createEditor(value, m_widget); + editor->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); + return editor; + } else { + qDebug() << "No editor factory for type" << type; + } + return nullptr; } } // namespace Tiled diff --git a/src/tiled/varianteditor.h b/src/tiled/varianteditor.h index 2e46be7ead..22cf2023e7 100644 --- a/src/tiled/varianteditor.h +++ b/src/tiled/varianteditor.h @@ -33,6 +33,25 @@ class QGridLayout; namespace Tiled { +class Property : public QObject +{ + Q_OBJECT + +public: + Property(QObject *parent = nullptr) + : QObject(parent) + { + } + + virtual QWidget *createEditor(QWidget *parent) = 0; + + virtual QVariant value() const = 0; + virtual void setValue(const QVariant &value) = 0; + +signals: + void valueChanged(const QVariant &value); +}; + class EditorFactory { Q_DECLARE_TR_FUNCTIONS(EditorFactory) @@ -58,6 +77,7 @@ class VariantEditor : public QScrollArea void addSeparator(); void addValue(const QString &name, const QVariant &value); void addValue(const QVariant &value); + QWidget *createEditor(const QVariant &value); private: enum Column { @@ -73,10 +93,6 @@ class VariantEditor : public QScrollArea QGridLayout *m_gridLayout; int m_rowIndex = 0; std::unordered_map> m_factories; - - // QAbstractScrollArea interface -protected: - QSize viewportSizeHint() const override; }; } // namespace Tiled From 854f0f2ee76a5126951474194b143abd592c4a07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Mon, 26 Aug 2024 10:46:08 +0200 Subject: [PATCH 05/36] First fully functional "Map Orientation" property Introduced Property class and changed from using editor factories to having the property itself create its widget. --- src/tiled/propertiesdock.cpp | 12 +-- src/tiled/propertiesdock.h | 4 +- src/tiled/propertieswidget.cpp | 97 ++++++++++++++++++++-- src/tiled/propertieswidget.h | 4 +- src/tiled/varianteditor.cpp | 147 ++++++++++++++++++++++----------- src/tiled/varianteditor.h | 85 ++++++++++++++++--- 6 files changed, 271 insertions(+), 78 deletions(-) diff --git a/src/tiled/propertiesdock.cpp b/src/tiled/propertiesdock.cpp index f47e2e10f0..36cde21f64 100644 --- a/src/tiled/propertiesdock.cpp +++ b/src/tiled/propertiesdock.cpp @@ -20,7 +20,7 @@ #include "propertiesdock.h" -#include "varianteditor.h" +#include "propertieswidget.h" #include @@ -28,26 +28,26 @@ namespace Tiled { PropertiesDock::PropertiesDock(QWidget *parent) : QDockWidget(parent) - , mPropertiesWidget(new VariantEditor(this)) + , mPropertiesWidget(new PropertiesWidget(this)) { setObjectName(QLatin1String("propertiesDock")); setWidget(mPropertiesWidget); - // connect(mPropertiesWidget, &PropertiesWidget::bringToFront, - // this, &PropertiesDock::bringToFront); + connect(mPropertiesWidget, &PropertiesWidget::bringToFront, + this, &PropertiesDock::bringToFront); retranslateUi(); } void PropertiesDock::setDocument(Document *document) { - // mPropertiesWidget->setDocument(document); + mPropertiesWidget->setDocument(document); } void PropertiesDock::selectCustomProperty(const QString &name) { bringToFront(); - // mPropertiesWidget->selectCustomProperty(name); + mPropertiesWidget->selectCustomProperty(name); } bool PropertiesDock::event(QEvent *event) diff --git a/src/tiled/propertiesdock.h b/src/tiled/propertiesdock.h index e5e96dbdd7..c39532484d 100644 --- a/src/tiled/propertiesdock.h +++ b/src/tiled/propertiesdock.h @@ -26,7 +26,7 @@ namespace Tiled { class Document; -class VariantEditor; +class PropertiesWidget; class PropertiesDock : public QDockWidget { @@ -50,7 +50,7 @@ public slots: void bringToFront(); void retranslateUi(); - VariantEditor *mPropertiesWidget; + PropertiesWidget *mPropertiesWidget; }; } // namespace Tiled diff --git a/src/tiled/propertieswidget.cpp b/src/tiled/propertieswidget.cpp index e118088c2f..f8ac584c89 100644 --- a/src/tiled/propertieswidget.cpp +++ b/src/tiled/propertieswidget.cpp @@ -22,13 +22,13 @@ #include "actionmanager.h" #include "addpropertydialog.h" +#include "changemapproperty.h" #include "changeproperties.h" #include "clipboardmanager.h" #include "mapdocument.h" -#include "mapobject.h" #include "propertybrowser.h" #include "utils.h" -#include "variantpropertymanager.h" +#include "varianteditor.h" #include #include @@ -46,7 +46,7 @@ namespace Tiled { PropertiesWidget::PropertiesWidget(QWidget *parent) : QWidget{parent} , mDocument(nullptr) - , mPropertyBrowser(new PropertyBrowser) + , mPropertyBrowser(new VariantEditor(this)) { mActionAddProperty = new QAction(this); mActionAddProperty->setEnabled(false); @@ -89,8 +89,8 @@ PropertiesWidget::PropertiesWidget(QWidget *parent) mPropertyBrowser->setContextMenuPolicy(Qt::CustomContextMenu); connect(mPropertyBrowser, &PropertyBrowser::customContextMenuRequested, this, &PropertiesWidget::showContextMenu); - connect(mPropertyBrowser, &PropertyBrowser::selectedItemsChanged, - this, &PropertiesWidget::updateActions); + // connect(mPropertyBrowser, &PropertyBrowser::selectedItemsChanged, + // this, &PropertiesWidget::updateActions); retranslateUi(); } @@ -110,7 +110,7 @@ void PropertiesWidget::setDocument(Document *document) mDocument->disconnect(this); mDocument = document; - mPropertyBrowser->setDocument(document); + // mPropertyBrowser->setDocument(document); if (document) { connect(document, &Document::currentObjectChanged, @@ -131,7 +131,7 @@ void PropertiesWidget::setDocument(Document *document) void PropertiesWidget::selectCustomProperty(const QString &name) { - mPropertyBrowser->selectCustomProperty(name); + // mPropertyBrowser->selectCustomProperty(name); } static bool anyObjectHasProperty(const QList &objects, const QString &name) @@ -143,9 +143,76 @@ static bool anyObjectHasProperty(const QList &objects, const QString &n return false; } +class MapOrientationProperty : public EnumProperty +{ +public: + MapOrientationProperty(MapDocument *mapDocument) + : EnumProperty(tr("Orientation")) + , mMapDocument(mapDocument) + { + setEnumNames({ + tr("Orthogonal"), + tr("Isometric"), + tr("Isometric (Staggered)"), + tr("Hexagonal (Staggered)"), + }); + setEnumValues({ + Map::Orthogonal, + Map::Isometric, + Map::Staggered, + Map::Hexagonal, + }); + + connect(mMapDocument, &MapDocument::mapChanged, + this, &Property::valueChanged); + } + + QVariant value() const override + { + return mMapDocument->map()->orientation(); + } + + void setValue(const QVariant &value) override + { + Map::Orientation orientation = static_cast(value.toInt()); + auto command = new ChangeMapProperty(mMapDocument, orientation); + mMapDocument->undoStack()->push(command); + } + +private: + MapDocument *mMapDocument; +}; + void PropertiesWidget::currentObjectChanged(Object *object) { - mPropertyBrowser->setObject(object); + // mPropertyBrowser->setObject(object); + mPropertyBrowser->clear(); + + if (object) { + switch (object->typeId()) { + case Object::LayerType: + case Object::MapObjectType: + break; + case Object::MapType: { + Map *map = static_cast(object); + mPropertyBrowser->addHeader(tr("Map")); + mPropertyBrowser->addProperty(new MapOrientationProperty(static_cast(mDocument))); + + auto sizeProperty = new VariantProperty(tr("Map Size"), map->size()); + sizeProperty->setEnabled(false); + mPropertyBrowser->addProperty(sizeProperty); + + break; + } + case Object::TilesetType: + case Object::TileType: + case Object::WangSetType: + case Object::WangColorType: + case Object::ProjectType: + case Object::WorldType: + break; + } + } bool editingTileset = mDocument && mDocument->type() == Document::TilesetDocumentType; bool isTileset = object && object->isPartOfTileset(); @@ -157,6 +224,7 @@ void PropertiesWidget::currentObjectChanged(Object *object) void PropertiesWidget::updateActions() { +#if 0 const QList items = mPropertyBrowser->selectedItems(); bool allCustomProperties = !items.isEmpty() && mPropertyBrowser->allCustomPropertyItems(items); bool editingTileset = mDocument && mDocument->type() == Document::TilesetDocumentType; @@ -176,6 +244,7 @@ void PropertiesWidget::updateActions() mActionRemoveProperty->setEnabled(canModify); mActionRenameProperty->setEnabled(canModify && items.size() == 1); +#endif } void PropertiesWidget::cutProperties() @@ -186,6 +255,7 @@ void PropertiesWidget::cutProperties() bool PropertiesWidget::copyProperties() { +#if 0 Object *object = mPropertyBrowser->object(); if (!object) return false; @@ -206,6 +276,7 @@ bool PropertiesWidget::copyProperties() } ClipboardManager::instance()->setProperties(properties); +#endif return true; } @@ -269,11 +340,12 @@ void PropertiesWidget::addProperty(const QString &name, const QVariant &value) name, value)); } - mPropertyBrowser->editCustomProperty(name); + // mPropertyBrowser->editCustomProperty(name); } void PropertiesWidget::removeProperties() { +#if 0 Object *object = mDocument->currentObject(); if (!object) return; @@ -299,10 +371,12 @@ void PropertiesWidget::removeProperties() } undoStack->endMacro(); +#endif } void PropertiesWidget::renameProperty() { +#if 0 QtBrowserItem *item = mPropertyBrowser->currentItem(); if (!mPropertyBrowser->isCustomPropertyItem(item)) return; @@ -317,10 +391,12 @@ void PropertiesWidget::renameProperty() dialog->setWindowTitle(QCoreApplication::translate("Tiled::PropertiesDock", "Rename Property")); connect(dialog, &QInputDialog::textValueSelected, this, &PropertiesWidget::renamePropertyTo); dialog->open(); +#endif } void PropertiesWidget::renamePropertyTo(const QString &name) { +#if 0 if (name.isEmpty()) return; @@ -334,10 +410,12 @@ void PropertiesWidget::renamePropertyTo(const QString &name) QUndoStack *undoStack = mDocument->undoStack(); undoStack->push(new RenameProperty(mDocument, mDocument->currentObjects(), oldName, name)); +#endif } void PropertiesWidget::showContextMenu(const QPoint &pos) { +#if 0 const Object *object = mDocument->currentObject(); if (!object) return; @@ -474,6 +552,7 @@ void PropertiesWidget::showContextMenu(const QPoint &pos) undoStack->endMacro(); } +#endif } bool PropertiesWidget::event(QEvent *event) diff --git a/src/tiled/propertieswidget.h b/src/tiled/propertieswidget.h index 022a520b2f..dc1e2cefe0 100644 --- a/src/tiled/propertieswidget.h +++ b/src/tiled/propertieswidget.h @@ -27,7 +27,7 @@ namespace Tiled { class Object; class Document; -class PropertyBrowser; +class VariantEditor; /** * The PropertiesWidget combines the PropertyBrowser with some controls that @@ -74,7 +74,7 @@ public slots: void retranslateUi(); Document *mDocument; - PropertyBrowser *mPropertyBrowser; + VariantEditor *mPropertyBrowser; QAction *mActionAddProperty; QAction *mActionRemoveProperty; QAction *mActionRenameProperty; diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index 7b1a1357f6..6a644d3715 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -206,11 +207,6 @@ class StringEditorFactory : public EditorFactory editor->setText(value.toString()); return editor; } - - QVariant value(QWidget *editor) const override - { - return static_cast(editor)->text(); - } }; class IntEditorFactory : public EditorFactory @@ -276,8 +272,6 @@ class PointEditorFactory : public EditorFactory yLabel->setBuddy(ySpinBox); horizontalLayout->addWidget(ySpinBox, 1); - // horizontalLayout->addStretch(); - xSpinBox->setValue(value.toPoint().x()); ySpinBox->setValue(value.toPoint().y()); @@ -308,8 +302,6 @@ class PointFEditorFactory : public EditorFactory yLabel->setBuddy(ySpinBox); horizontalLayout->addWidget(ySpinBox, 1); - // horizontalLayout->addStretch(); - xSpinBox->setValue(value.toPointF().x()); ySpinBox->setValue(value.toPointF().y()); @@ -488,17 +480,7 @@ class EnumEditorFactory : public EditorFactory public: QWidget *createEditor(const QVariant &value, QWidget *parent) override { - auto editor = new QComboBox(parent); - // This allows the combo box to shrink horizontally. - editor->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); - editor->setModel(&m_enumNamesModel); - - if (m_enumValues.isEmpty()) - editor->setCurrentIndex(value.toInt()); - else - editor->setCurrentIndex(m_enumValues.indexOf(value.toInt())); - - return editor; + return nullptr; } void setEnumNames(const QStringList &enumNames) @@ -516,6 +498,51 @@ class EnumEditorFactory : public EditorFactory QList m_enumValues; }; +void VariantProperty::setValue(const QVariant &value) +{ + if (m_value != value) { + m_value = value; + emit valueChanged(); + } +} + +QWidget *VariantProperty::createEditor(QWidget *parent) +{ + switch (m_value.userType()) { + case QMetaType::QSize: + return SizeEditorFactory().createEditor(m_value, parent); + default: + break; + } + + return nullptr; +} + +QWidget *EnumProperty::createEditor(QWidget *parent) +{ + auto editor = new QComboBox(parent); + // This allows the combo box to shrink horizontally. + editor->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); + editor->setModel(&m_enumNamesModel); + + auto syncEditor = [editor, this]() { + const QSignalBlocker blocker(editor); + if (m_enumValues.isEmpty()) + editor->setCurrentIndex(value().toInt()); + else + editor->setCurrentIndex(m_enumValues.indexOf(value().toInt())); + }; + syncEditor(); + + connect(this, &Property::valueChanged, editor, syncEditor); + connect(editor, qOverload(&QComboBox::currentIndexChanged), this, + [this](int index) { + setValue(m_enumValues.isEmpty() ? index : m_enumValues.at(index)); + }); + + return editor; +} + VariantEditor::VariantEditor(QWidget *parent) : QScrollArea(parent) @@ -647,26 +674,28 @@ VariantEditor::VariantEditor(QWidget *parent) // QVariant(3.14) // }); - addHeader(tr("Map")); - addValue(tr("Class"), QString()); - addSeparator(); - addValue(tr("Orientation"), QVariant::fromValue(Map::Hexagonal)); - addValue(tr("Infinite"), false); - addValue(tr("Map Size"), QSize(20, 20)); - addValue(tr("Tile Size"), QSize(14, 12)); - addValue(tr("Tile Side Length (Hex)"), 6); - addValue(tr("Stagger Axis"), QVariant::fromValue(Map::StaggerY)); - addValue(tr("Stagger Index"), QVariant::fromValue(Map::StaggerEven)); - addSeparator(); - addValue(tr("Parallax Origin"), QPointF()); - addSeparator(); - addValue(tr("Tile Layer Format"), QVariant::fromValue(Map::Base64Zlib)); - addValue(tr("Output Chunk Size"), QSize(16, 16)); - addValue(tr("Compression Level"), -1); - addSeparator(); - addValue(tr("Tile Render Order"), QVariant::fromValue(Map::RightDown)); - addValue(tr("Background Color"), QColor()); - addHeader(tr("Custom Properties")); + // addHeader(tr("Map")); + // addProperty(new VariantProperty(tr("Class"), QString())); + // addProperty(new VariantProperty(tr("Orientation"), QVariant::fromValue(Map::Hexagonal))); + // addValue(tr("Class"), QString()); + // addSeparator(); + // addValue(tr("Orientation"), QVariant::fromValue(Map::Hexagonal)); + // addValue(tr("Infinite"), false); + // addValue(tr("Map Size"), QSize(20, 20)); + // addValue(tr("Tile Size"), QSize(14, 12)); + // addValue(tr("Tile Side Length (Hex)"), 6); + // addValue(tr("Stagger Axis"), QVariant::fromValue(Map::StaggerY)); + // addValue(tr("Stagger Index"), QVariant::fromValue(Map::StaggerEven)); + // addSeparator(); + // addValue(tr("Parallax Origin"), QPointF()); + // addSeparator(); + // addValue(tr("Tile Layer Format"), QVariant::fromValue(Map::Base64Zlib)); + // addValue(tr("Output Chunk Size"), QSize(16, 16)); + // addValue(tr("Compression Level"), -1); + // addSeparator(); + // addValue(tr("Tile Render Order"), QVariant::fromValue(Map::RightDown)); + // addValue(tr("Background Color"), QColor()); + // addHeader(tr("Custom Properties")); } void VariantEditor::registerEditorFactory(int type, std::unique_ptr factory) @@ -674,6 +703,16 @@ void VariantEditor::registerEditorFactory(int type, std::unique_ptrtakeAt(0))) { + delete item->widget(); + delete item; + } + m_rowIndex = 0; +} + void VariantEditor::addHeader(const QString &text) { auto label = new ElidingLabel(text, m_widget); @@ -699,17 +738,23 @@ void VariantEditor::addSeparator() ++m_rowIndex; } -void VariantEditor::addValue(const QString &name, const QVariant &value) +void VariantEditor::addProperty(Property *property) { - auto label = new LineEditLabel(name, m_widget); + auto label = new LineEditLabel(property->name(), m_widget); label->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); + label->setEnabled(property->isEnabled()); + connect(property, &Property::enabledChanged, label, &QLabel::setEnabled); m_gridLayout->addWidget(label, m_rowIndex, LabelColumn, Qt::AlignTop/* | Qt::AlignRight*/); - if (auto editor = createEditor(value)) { + + if (auto editor = createEditor(property)) { + editor->setEnabled(property->isEnabled()); + connect(property, &Property::enabledChanged, editor, &QWidget::setEnabled); m_gridLayout->addWidget(editor, m_rowIndex, WidgetColumn); } ++m_rowIndex; } +#if 0 void VariantEditor::addValue(const QVariant &value) { const int type = value.userType(); @@ -736,17 +781,21 @@ void VariantEditor::addValue(const QVariant &value) } } } +#endif -QWidget *VariantEditor::createEditor(const QVariant &value) +QWidget *VariantEditor::createEditor(Property *property) { - const int type = value.userType(); - auto factory = m_factories.find(type); - if (factory != m_factories.end()) { - const auto editor = factory->second->createEditor(value, m_widget); + const auto editor = property->createEditor(m_widget); + // const auto value = property->value(); + // const int type = value.userType(); + // auto factory = m_factories.find(type); + // if (factory != m_factories.end()) { + // const auto editor = factory->second->createEditor(value, m_widget); + if (editor) { editor->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); return editor; } else { - qDebug() << "No editor factory for type" << type; + qDebug() << "No editor for property" << property->name(); } return nullptr; } diff --git a/src/tiled/varianteditor.h b/src/tiled/varianteditor.h index 22cf2023e7..8fe48c725a 100644 --- a/src/tiled/varianteditor.h +++ b/src/tiled/varianteditor.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -36,20 +37,85 @@ namespace Tiled { class Property : public QObject { Q_OBJECT + Q_PROPERTY(QString name READ name CONSTANT) + Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged) + Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged) public: - Property(QObject *parent = nullptr) + Property(const QString &name, QObject *parent = nullptr) : QObject(parent) + , m_name(name) + {} + + QString name() const { return m_name; } + + bool isEnabled() const { return m_enabled; } + void setEnabled(bool enabled) { + if (m_enabled != enabled) { + m_enabled = enabled; + emit enabledChanged(enabled); + } } - virtual QWidget *createEditor(QWidget *parent) = 0; - virtual QVariant value() const = 0; virtual void setValue(const QVariant &value) = 0; + virtual QWidget *createEditor(QWidget *parent) = 0; + signals: - void valueChanged(const QVariant &value); + void valueChanged(); + void enabledChanged(bool enabled); + +private: + QString m_name; + bool m_enabled = true; +}; + +class VariantProperty : public Property +{ + Q_OBJECT + +public: + VariantProperty(const QString &name, const QVariant &value, QObject *parent = nullptr) + : Property(name, parent) + , m_value(value) + { + } + + QVariant value() const override { return m_value; } + void setValue(const QVariant &value) override; + + QWidget *createEditor(QWidget *parent) override; + +private: + QVariant m_value; +}; + +class EnumProperty : public Property +{ + Q_OBJECT + +public: + EnumProperty(const QString &name, QObject *parent = nullptr) + : Property(name, parent) + {} + + QWidget *createEditor(QWidget *parent) override; + + void setEnumNames(const QStringList &enumNames) + { + m_enumNamesModel.setStringList(enumNames); + } + + void setEnumValues(const QList &enumValues) + { + m_enumValues = enumValues; + } + +private: + QStringListModel m_enumNamesModel; + QList m_enumValues; }; class EditorFactory @@ -59,9 +125,6 @@ class EditorFactory public: virtual QWidget *createEditor(const QVariant &value, QWidget *parent) = 0; - - virtual QVariant value(QWidget *editor) const { return {}; } - virtual void setValue(QWidget *editor, const QVariant &value) {}; }; class VariantEditor : public QScrollArea @@ -73,13 +136,15 @@ class VariantEditor : public QScrollArea void registerEditorFactory(int type, std::unique_ptr factory); + void clear(); void addHeader(const QString &text); void addSeparator(); - void addValue(const QString &name, const QVariant &value); - void addValue(const QVariant &value); - QWidget *createEditor(const QVariant &value); + void addProperty(Property *property); + // void addValue(const QVariant &value); private: + QWidget *createEditor(Property *property); + enum Column { LeftSpacing, LabelColumn, From 6e2aa3630ac49d306cc45adead83f2014474528e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Fri, 30 Aug 2024 17:14:30 +0200 Subject: [PATCH 06/36] Implemented "Map Size" and "Tile Size" properties * Map size is read-only but features a "Resize Map" button that triggers the resize dialog. * Both map size and tile size are implemented based on the new "ValueTypeEditorFactory" which selects an editor based on the value type. This functionality is moved out of the VariantEditor. * Introduced AbstractProperty, ValueProperty and QObjectProperty in an attempt to provide some convenience on top of the Property interface. I'm not entirely convinced it was a good idea to put the "createEditor" function on the Property. It makes it easy to do custom widgets for a property, but annoying to have it use a type-based editor. --- src/tiled/propertieswidget.cpp | 117 ++++++- src/tiled/propertieswidget.h | 4 + src/tiled/varianteditor.cpp | 561 ++++++++++++++++++++------------- src/tiled/varianteditor.h | 170 ++++++++-- 4 files changed, 593 insertions(+), 259 deletions(-) diff --git a/src/tiled/propertieswidget.cpp b/src/tiled/propertieswidget.cpp index f8ac584c89..0e7a882c01 100644 --- a/src/tiled/propertieswidget.cpp +++ b/src/tiled/propertieswidget.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -47,6 +48,7 @@ PropertiesWidget::PropertiesWidget(QWidget *parent) : QWidget{parent} , mDocument(nullptr) , mPropertyBrowser(new VariantEditor(this)) + , mDefaultEditorFactory(std::make_unique()) { mActionAddProperty = new QAction(this); mActionAddProperty->setEnabled(false); @@ -145,6 +147,8 @@ static bool anyObjectHasProperty(const QList &objects, const QString &n class MapOrientationProperty : public EnumProperty { + Q_OBJECT + public: MapOrientationProperty(MapDocument *mapDocument) : EnumProperty(tr("Orientation")) @@ -183,6 +187,98 @@ class MapOrientationProperty : public EnumProperty MapDocument *mMapDocument; }; +class MapSizeProperty : public AbstractProperty +{ + Q_OBJECT + +public: + MapSizeProperty(MapDocument *mapDocument, EditorFactory *editorFactory) + : AbstractProperty(tr("Map Size"), editorFactory) + , mMapDocument(mapDocument) + { + connect(mMapDocument, &MapDocument::mapChanged, + this, &Property::valueChanged); + } + + QVariant value() const override { return mMapDocument->map()->size(); } + void setValue(const QVariant &) override {}; + + QWidget *createEditor(QWidget *parent) override + { + auto widget = new QWidget(parent); + auto layout = new QVBoxLayout(widget); + auto valueEdit = AbstractProperty::createEditor(widget); + auto resizeButton = new QPushButton(tr("Resize Map"), widget); + + valueEdit->setEnabled(false); + layout->setContentsMargins(QMargins()); + layout->addWidget(valueEdit); + layout->addWidget(resizeButton, 0, Qt::AlignLeft); + + connect(resizeButton, &QPushButton::clicked, [] { + ActionManager::action("ResizeMap")->trigger(); + }); + + return widget; + } + +private: + MapDocument *mMapDocument; +}; + +class TileSizeProperty : public AbstractProperty +{ + Q_OBJECT + +public: + TileSizeProperty(MapDocument *mapDocument, EditorFactory *editorFactory) + : AbstractProperty(tr("Tile Size"), editorFactory) + , mMapDocument(mapDocument) + { + connect(mMapDocument, &Document::changed, + this, &TileSizeProperty::onChanged); + } + + QVariant value() const override + { + return mMapDocument->map()->tileSize(); + } + + void setValue(const QVariant &value) override + { + auto oldSize = mMapDocument->map()->tileSize(); + auto newSize = value.toSize(); + + if (oldSize.width() != newSize.width()) { + auto command = new ChangeMapProperty(mMapDocument, + Map::TileWidthProperty, + newSize.width()); + mMapDocument->undoStack()->push(command); + } + + if (oldSize.height() != newSize.height()) { + auto command = new ChangeMapProperty(mMapDocument, + Map::TileHeightProperty, + newSize.height()); + mMapDocument->undoStack()->push(command); + } + }; + +private: + void onChanged(const ChangeEvent &event) + { + if (event.type != ChangeEvent::MapChanged) + return; + + const auto property = static_cast(event).property; + if (property == Map::TileWidthProperty || property == Map::TileHeightProperty) + emit valueChanged(); + } + + MapDocument *mMapDocument; +}; + + void PropertiesWidget::currentObjectChanged(Object *object) { // mPropertyBrowser->setObject(object); @@ -195,12 +291,22 @@ void PropertiesWidget::currentObjectChanged(Object *object) break; case Object::MapType: { Map *map = static_cast(object); - mPropertyBrowser->addHeader(tr("Map")); - mPropertyBrowser->addProperty(new MapOrientationProperty(static_cast(mDocument))); + auto mapDocument = static_cast(mDocument); - auto sizeProperty = new VariantProperty(tr("Map Size"), map->size()); - sizeProperty->setEnabled(false); - mPropertyBrowser->addProperty(sizeProperty); + mPropertyBrowser->addHeader(tr("Map")); + mPropertyBrowser->addProperty(new MapOrientationProperty(mapDocument)); + mPropertyBrowser->addProperty(new MapSizeProperty(mapDocument, mDefaultEditorFactory.get())); + mPropertyBrowser->addProperty(new TileSizeProperty(mapDocument, mDefaultEditorFactory.get())); + // todo: infinite + // todo: hex side length + // todo: stagger axis + // todo: stagger index + // todo: parallax origin + // todo: layer data format + // todo: chunk size + // todo: tile render order + // todo: compression level + // todo: background color break; } @@ -608,3 +714,4 @@ void PropertiesWidget::retranslateUi() } // namespace Tiled #include "moc_propertieswidget.cpp" +#include "propertieswidget.moc" diff --git a/src/tiled/propertieswidget.h b/src/tiled/propertieswidget.h index dc1e2cefe0..f9f5db095e 100644 --- a/src/tiled/propertieswidget.h +++ b/src/tiled/propertieswidget.h @@ -22,11 +22,14 @@ #include +#include + namespace Tiled { class Object; class Document; +class ValueTypeEditorFactory; class VariantEditor; /** @@ -75,6 +78,7 @@ public slots: Document *mDocument; VariantEditor *mPropertyBrowser; + std::unique_ptr mDefaultEditorFactory; QAction *mActionAddProperty; QAction *mActionRemoveProperty; QAction *mActionRenameProperty; diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index 6a644d3715..60d2290417 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -22,23 +22,35 @@ #include "colorbutton.h" #include "compression.h" #include "map.h" -#include "tiled.h" #include "utils.h" #include #include #include -#include #include #include #include #include +#include #include #include #include namespace Tiled { +AbstractProperty::AbstractProperty(const QString &name, + EditorFactory *editorFactory, + QObject *parent) + : Property(name, parent) + , m_editorFactory(editorFactory) +{} + +QWidget *AbstractProperty::createEditor(QWidget *parent) +{ + return m_editorFactory->createEditor(this, parent); +} + + class SpinBox : public QSpinBox { Q_OBJECT @@ -201,8 +213,9 @@ class LineEditLabel : public ElidingLabel class StringEditorFactory : public EditorFactory { public: - QWidget *createEditor(const QVariant &value, QWidget *parent) override + QWidget *createEditor(Property *property, QWidget *parent) override { + auto value = property->value(); auto editor = new QLineEdit(parent); editor->setText(value.toString()); return editor; @@ -212,8 +225,9 @@ class StringEditorFactory : public EditorFactory class IntEditorFactory : public EditorFactory { public: - QWidget *createEditor(const QVariant &value, QWidget *parent) override + QWidget *createEditor(Property *property, QWidget *parent) override { + auto value = property->value(); auto editor = new SpinBox(parent); editor->setValue(value.toInt()); return editor; @@ -223,8 +237,9 @@ class IntEditorFactory : public EditorFactory class FloatEditorFactory : public EditorFactory { public: - QWidget *createEditor(const QVariant &value, QWidget *parent) override + QWidget *createEditor(Property *property, QWidget *parent) override { + auto value = property->value(); auto editor = new DoubleSpinBox(parent); editor->setValue(value.toDouble()); return editor; @@ -234,8 +249,9 @@ class FloatEditorFactory : public EditorFactory class BoolEditorFactory : public EditorFactory { public: - QWidget *createEditor(const QVariant &value, QWidget *parent) override + QWidget *createEditor(Property *property, QWidget *parent) override { + auto value = property->value(); auto editor = new QCheckBox(parent); bool checked = value.toBool(); editor->setChecked(checked); @@ -252,8 +268,9 @@ class BoolEditorFactory : public EditorFactory class PointEditorFactory : public EditorFactory { public: - QWidget *createEditor(const QVariant &value, QWidget *parent) override + QWidget *createEditor(Property *property, QWidget *parent) override { + auto value = property->value(); auto editor = new QWidget(parent); auto horizontalLayout = new QHBoxLayout(editor); horizontalLayout->setContentsMargins(QMargins()); @@ -282,8 +299,9 @@ class PointEditorFactory : public EditorFactory class PointFEditorFactory : public EditorFactory { public: - QWidget *createEditor(const QVariant &value, QWidget *parent) override + QWidget *createEditor(Property *property, QWidget *parent) override { + auto value = property->value(); auto editor = new QWidget(parent); auto horizontalLayout = new QHBoxLayout(editor); horizontalLayout->setContentsMargins(QMargins()); @@ -309,94 +327,29 @@ class PointFEditorFactory : public EditorFactory } }; -class SizeEditor : public QWidget +/** + * A widget for editing a QSize value. + */ +class SizeEdit : public QWidget { + Q_OBJECT + Q_PROPERTY(QSize value READ value WRITE setValue NOTIFY valueChanged FINAL) + public: - SizeEditor(QWidget *parent = nullptr) - : QWidget(parent) - , m_widthLabel(new QLabel(QStringLiteral("W"), this)) - , m_heightLabel(new QLabel(QStringLiteral("H"), this)) - , m_widthSpinBox(new SpinBox(this)) - , m_heightSpinBox(new SpinBox(this)) - { - m_widthLabel->setAlignment(Qt::AlignCenter); - m_heightLabel->setAlignment(Qt::AlignCenter); - - auto layout = new QGridLayout(this); - layout->setContentsMargins(QMargins()); - layout->setColumnStretch(1, 1); - layout->setColumnStretch(3, 1); - layout->setSpacing(Utils::dpiScaled(3)); - - const int horizontalMargin = Utils::dpiScaled(3); - m_widthLabel->setContentsMargins(horizontalMargin, 0, horizontalMargin, 0); - m_heightLabel->setContentsMargins(horizontalMargin, 0, horizontalMargin, 0); - - layout->addWidget(m_widthLabel, 0, 0); - layout->addWidget(m_widthSpinBox, 0, 1); - layout->addWidget(m_heightLabel, 0, 2); - layout->addWidget(m_heightSpinBox, 0, 3); - } + SizeEdit(QWidget *parent = nullptr); - void setValue(const QSize &size) - { - m_widthSpinBox->setValue(size.width()); - m_heightSpinBox->setValue(size.height()); - } + void setValue(const QSize &size); + QSize value() const; - QSize value() const - { - return QSize(m_widthSpinBox->value(), m_heightSpinBox->value()); - } +signals: + void valueChanged(); private: - void resizeEvent(QResizeEvent *event) override - { - QWidget::resizeEvent(event); - - const auto direction = event->size().width() < minimumHorizontalWidth() - ? Qt::Vertical : Qt::Horizontal; - - if (m_direction != direction) { - m_direction = direction; - - auto layout = qobject_cast(this->layout()); - - // Remove all widgets from layout, without deleting them - layout->removeWidget(m_widthLabel); - layout->removeWidget(m_widthSpinBox); - layout->removeWidget(m_heightLabel); - layout->removeWidget(m_heightSpinBox); - - if (direction == Qt::Horizontal) { - layout->addWidget(m_widthLabel, 0, 0); - layout->addWidget(m_widthSpinBox, 0, 1); - layout->addWidget(m_heightLabel, 0, 2); - layout->addWidget(m_heightSpinBox, 0, 3); - layout->setColumnStretch(3, 1); - } else { - layout->addWidget(m_widthLabel, 0, 0); - layout->addWidget(m_widthSpinBox, 0, 1); - layout->addWidget(m_heightLabel, 1, 0); - layout->addWidget(m_heightSpinBox, 1, 1); - layout->setColumnStretch(3, 0); - } - - // this avoids flickering when the layout changes - layout->activate(); - } - } + void resizeEvent(QResizeEvent *event) override; - int minimumHorizontalWidth() const - { - return m_widthLabel->minimumSizeHint().width() + - m_widthSpinBox->minimumSizeHint().width() + - m_heightLabel->minimumSizeHint().width() + - m_heightSpinBox->minimumSizeHint().width() + - layout()->spacing() * 3; - } + int minimumHorizontalWidth() const; - Qt::Orientation m_direction = Qt::Horizontal; + Qt::Orientation m_orientation = Qt::Horizontal; QLabel *m_widthLabel; QLabel *m_heightLabel; SpinBox *m_widthSpinBox; @@ -406,10 +359,21 @@ class SizeEditor : public QWidget class SizeEditorFactory : public EditorFactory { public: - QWidget *createEditor(const QVariant &value, QWidget *parent) override + QWidget *createEditor(Property *property, QWidget *parent) override { - auto editor = new SizeEditor(parent); - editor->setValue(value.toSize()); + auto editor = new SizeEdit(parent); + auto syncEditor = [property, editor]() { + const QSignalBlocker blocker(editor); + editor->setValue(property->value().toSize()); + }; + syncEditor(); + + QObject::connect(property, &Property::valueChanged, editor, syncEditor); + QObject::connect(editor, &SizeEdit::valueChanged, property, + [property, editor]() { + property->setValue(editor->value()); + }); + return editor; } }; @@ -417,8 +381,9 @@ class SizeEditorFactory : public EditorFactory class RectFEditorFactory : public EditorFactory { public: - QWidget *createEditor(const QVariant &value, QWidget *parent) override + QWidget *createEditor(Property *property, QWidget *parent) override { + auto value = property->value(); auto editor = new QWidget(parent); auto gridLayout = new QGridLayout(editor); gridLayout->setContentsMargins(QMargins()); @@ -467,38 +432,25 @@ class RectFEditorFactory : public EditorFactory class ColorEditorFactory : public EditorFactory { public: - QWidget *createEditor(const QVariant &value, QWidget *parent) override + QWidget *createEditor(Property *property, QWidget *parent) override { + auto value = property->value(); auto editor = new ColorButton(parent); editor->setColor(value.value()); return editor; } }; -class EnumEditorFactory : public EditorFactory -{ -public: - QWidget *createEditor(const QVariant &value, QWidget *parent) override - { - return nullptr; - } - - void setEnumNames(const QStringList &enumNames) - { - m_enumNamesModel.setStringList(enumNames); - } - - void setEnumValues(const QList &enumValues) - { - m_enumValues = enumValues; - } -private: - QStringListModel m_enumNamesModel; - QList m_enumValues; -}; +ValueProperty::ValueProperty(const QString &name, + const QVariant &value, + EditorFactory *editorFactory, + QObject *parent) + : AbstractProperty(name, editorFactory, parent) + , m_value(value) +{} -void VariantProperty::setValue(const QVariant &value) +void ValueProperty::setValue(const QVariant &value) { if (m_value != value) { m_value = value; @@ -506,41 +458,23 @@ void VariantProperty::setValue(const QVariant &value) } } -QWidget *VariantProperty::createEditor(QWidget *parent) -{ - switch (m_value.userType()) { - case QMetaType::QSize: - return SizeEditorFactory().createEditor(m_value, parent); - default: - break; - } - return nullptr; -} +EnumProperty::EnumProperty(const QString &name, + const QStringList &enumNames, + const QList &enumValues, + QObject *parent) + : AbstractProperty(name, &m_editorFactory, parent) + , m_editorFactory(enumNames, enumValues) +{} -QWidget *EnumProperty::createEditor(QWidget *parent) +void EnumProperty::setEnumNames(const QStringList &enumNames) { - auto editor = new QComboBox(parent); - // This allows the combo box to shrink horizontally. - editor->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); - editor->setModel(&m_enumNamesModel); - - auto syncEditor = [editor, this]() { - const QSignalBlocker blocker(editor); - if (m_enumValues.isEmpty()) - editor->setCurrentIndex(value().toInt()); - else - editor->setCurrentIndex(m_enumValues.indexOf(value().toInt())); - }; - syncEditor(); - - connect(this, &Property::valueChanged, editor, syncEditor); - connect(editor, qOverload(&QComboBox::currentIndexChanged), this, - [this](int index) { - setValue(m_enumValues.isEmpty() ? index : m_enumValues.at(index)); - }); + m_editorFactory.setEnumNames(enumNames); +} - return editor; +void EnumProperty::setEnumValues(const QList &enumValues) +{ + m_editorFactory.setEnumValues(enumValues); } @@ -567,59 +501,35 @@ VariantEditor::VariantEditor(QWidget *parent) m_gridLayout->setColumnMinimumWidth(MiddleSpacing, Utils::dpiScaled(2)); m_gridLayout->setColumnMinimumWidth(RightSpacing, Utils::dpiScaled(3)); - registerEditorFactory(QMetaType::QString, std::make_unique()); - registerEditorFactory(QMetaType::Int, std::make_unique()); - registerEditorFactory(QMetaType::Double, std::make_unique()); - registerEditorFactory(QMetaType::Bool, std::make_unique()); - registerEditorFactory(QMetaType::QPoint, std::make_unique()); - registerEditorFactory(QMetaType::QPointF, std::make_unique()); - registerEditorFactory(QMetaType::QSize, std::make_unique()); - registerEditorFactory(QMetaType::QRectF, std::make_unique()); - registerEditorFactory(QMetaType::QColor, std::make_unique()); - - auto alignmentEditorFactory = std::make_unique(); - alignmentEditorFactory->setEnumNames({ - tr("Unspecified"), - tr("Top Left"), - tr("Top"), - tr("Top Right"), - tr("Left"), - tr("Center"), - tr("Right"), - tr("Bottom Left"), - tr("Bottom"), - tr("Bottom Right"), - }); - registerEditorFactory(qMetaTypeId(), std::move(alignmentEditorFactory)); - - auto orientationEditorFactory = std::make_unique(); - orientationEditorFactory->setEnumNames({ - tr("Orthogonal"), - tr("Isometric"), - tr("Isometric (Staggered)"), - tr("Hexagonal (Staggered)"), - }); - orientationEditorFactory->setEnumValues({ - Map::Orthogonal, - Map::Isometric, - Map::Staggered, - Map::Hexagonal, - }); - registerEditorFactory(qMetaTypeId(), std::move(orientationEditorFactory)); - - auto staggerAxisEditorFactory = std::make_unique(); - staggerAxisEditorFactory->setEnumNames({ - tr("X"), - tr("Y"), - }); - registerEditorFactory(qMetaTypeId(), std::move(staggerAxisEditorFactory)); - - auto staggerIndexEditorFactory = std::make_unique(); - staggerIndexEditorFactory->setEnumNames({ - tr("Odd"), - tr("Even"), - }); - registerEditorFactory(qMetaTypeId(), std::move(staggerIndexEditorFactory)); + // auto alignmentEditorFactory = std::make_unique(); + // alignmentEditorFactory->setEnumNames({ + // tr("Unspecified"), + // tr("Top Left"), + // tr("Top"), + // tr("Top Right"), + // tr("Left"), + // tr("Center"), + // tr("Right"), + // tr("Bottom Left"), + // tr("Bottom"), + // tr("Bottom Right"), + // }); + // registerEditorFactory(qMetaTypeId(), std::move(alignmentEditorFactory)); + + + // auto staggerAxisEditorFactory = std::make_unique(); + // staggerAxisEditorFactory->setEnumNames({ + // tr("X"), + // tr("Y"), + // }); + // registerEditorFactory(qMetaTypeId(), std::move(staggerAxisEditorFactory)); + + // auto staggerIndexEditorFactory = std::make_unique(); + // staggerIndexEditorFactory->setEnumNames({ + // tr("Odd"), + // tr("Even"), + // }); + // registerEditorFactory(qMetaTypeId(), std::move(staggerIndexEditorFactory)); QStringList layerFormatNames = { QCoreApplication::translate("PreferencesDialog", "XML (deprecated)"), @@ -642,19 +552,19 @@ VariantEditor::VariantEditor(QWidget *parent) layerFormatNames.append(QCoreApplication::translate("PreferencesDialog", "CSV")); layerFormatValues.append(Map::CSV); - auto layerFormatEditorFactory = std::make_unique(); - layerFormatEditorFactory->setEnumNames(layerFormatNames); - layerFormatEditorFactory->setEnumValues(layerFormatValues); - registerEditorFactory(qMetaTypeId(), std::move(layerFormatEditorFactory)); + // auto layerFormatEditorFactory = std::make_unique(); + // layerFormatEditorFactory->setEnumNames(layerFormatNames); + // layerFormatEditorFactory->setEnumValues(layerFormatValues); + // registerEditorFactory(qMetaTypeId(), std::move(layerFormatEditorFactory)); - auto renderOrderEditorFactory = std::make_unique(); - renderOrderEditorFactory->setEnumNames({ - tr("Right Down"), - tr("Right Up"), - tr("Left Down"), - tr("Left Up"), - }); - registerEditorFactory(qMetaTypeId(), std::move(renderOrderEditorFactory)); + // auto renderOrderEditorFactory = std::make_unique(); + // renderOrderEditorFactory->setEnumNames({ + // tr("Right Down"), + // tr("Right Up"), + // tr("Left Down"), + // tr("Left Up"), + // }); + // registerEditorFactory(qMetaTypeId(), std::move(renderOrderEditorFactory)); // setValue(QVariantMap { // { QStringLiteral("Name"), QVariant(QLatin1String("Hello")) }, @@ -698,11 +608,6 @@ VariantEditor::VariantEditor(QWidget *parent) // addHeader(tr("Custom Properties")); } -void VariantEditor::registerEditorFactory(int type, std::unique_ptr factory) -{ - m_factories[type] = std::move(factory); -} - void VariantEditor::clear() { QLayoutItem *item; @@ -785,13 +690,7 @@ void VariantEditor::addValue(const QVariant &value) QWidget *VariantEditor::createEditor(Property *property) { - const auto editor = property->createEditor(m_widget); - // const auto value = property->value(); - // const int type = value.userType(); - // auto factory = m_factories.find(type); - // if (factory != m_factories.end()) { - // const auto editor = factory->second->createEditor(value, m_widget); - if (editor) { + if (const auto editor = property->createEditor(m_widget)) { editor->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); return editor; } else { @@ -800,6 +699,222 @@ QWidget *VariantEditor::createEditor(Property *property) return nullptr; } +SizeEdit::SizeEdit(QWidget *parent) + : QWidget(parent) + , m_widthLabel(new QLabel(QStringLiteral("W"), this)) + , m_heightLabel(new QLabel(QStringLiteral("H"), this)) + , m_widthSpinBox(new SpinBox(this)) + , m_heightSpinBox(new SpinBox(this)) +{ + m_widthLabel->setAlignment(Qt::AlignCenter); + m_heightLabel->setAlignment(Qt::AlignCenter); + + auto layout = new QGridLayout(this); + layout->setContentsMargins(QMargins()); + layout->setColumnStretch(1, 1); + layout->setColumnStretch(3, 1); + layout->setSpacing(Utils::dpiScaled(3)); + + const int horizontalMargin = Utils::dpiScaled(3); + m_widthLabel->setContentsMargins(horizontalMargin, 0, horizontalMargin, 0); + m_heightLabel->setContentsMargins(horizontalMargin, 0, horizontalMargin, 0); + + layout->addWidget(m_widthLabel, 0, 0); + layout->addWidget(m_widthSpinBox, 0, 1); + layout->addWidget(m_heightLabel, 0, 2); + layout->addWidget(m_heightSpinBox, 0, 3); + + connect(m_widthSpinBox, qOverload(&QSpinBox::valueChanged), this, &SizeEdit::valueChanged); + connect(m_heightSpinBox, qOverload(&QSpinBox::valueChanged), this, &SizeEdit::valueChanged); +} + +void SizeEdit::setValue(const QSize &size) +{ + m_widthSpinBox->setValue(size.width()); + m_heightSpinBox->setValue(size.height()); +} + +QSize SizeEdit::value() const +{ + return QSize(m_widthSpinBox->value(), m_heightSpinBox->value()); +} + +void SizeEdit::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + + const auto orientation = event->size().width() < minimumHorizontalWidth() + ? Qt::Vertical : Qt::Horizontal; + + if (m_orientation != orientation) { + m_orientation = orientation; + + auto layout = qobject_cast(this->layout()); + + // Remove all widgets from layout, without deleting them + layout->removeWidget(m_widthLabel); + layout->removeWidget(m_widthSpinBox); + layout->removeWidget(m_heightLabel); + layout->removeWidget(m_heightSpinBox); + + if (orientation == Qt::Horizontal) { + layout->addWidget(m_widthLabel, 0, 0); + layout->addWidget(m_widthSpinBox, 0, 1); + layout->addWidget(m_heightLabel, 0, 2); + layout->addWidget(m_heightSpinBox, 0, 3); + layout->setColumnStretch(3, 1); + } else { + layout->addWidget(m_widthLabel, 0, 0); + layout->addWidget(m_widthSpinBox, 0, 1); + layout->addWidget(m_heightLabel, 1, 0); + layout->addWidget(m_heightSpinBox, 1, 1); + layout->setColumnStretch(3, 0); + } + + // this avoids flickering when the layout changes + layout->activate(); + } +} + +int SizeEdit::minimumHorizontalWidth() const +{ + return m_widthLabel->minimumSizeHint().width() + + m_widthSpinBox->minimumSizeHint().width() + + m_heightLabel->minimumSizeHint().width() + + m_heightSpinBox->minimumSizeHint().width() + + layout()->spacing() * 3; +} + + +EnumEditorFactory::EnumEditorFactory(const QStringList &enumNames, + const QList &enumValues) + : m_enumNamesModel(enumNames) + , m_enumValues(enumValues) +{} + +void EnumEditorFactory::setEnumNames(const QStringList &enumNames) +{ + m_enumNamesModel.setStringList(enumNames); +} + +void EnumEditorFactory::setEnumValues(const QList &enumValues) +{ + m_enumValues = enumValues; +} + +QWidget *EnumEditorFactory::createEditor(Property *property, QWidget *parent) +{ + auto editor = new QComboBox(parent); + // This allows the combo box to shrink horizontally. + editor->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); + editor->setModel(&m_enumNamesModel); + + auto syncEditor = [property, editor, this]() { + const QSignalBlocker blocker(editor); + if (m_enumValues.isEmpty()) + editor->setCurrentIndex(property->value().toInt()); + else + editor->setCurrentIndex(m_enumValues.indexOf(property->value().toInt())); + }; + syncEditor(); + + QObject::connect(property, &Property::valueChanged, editor, syncEditor); + QObject::connect(editor, qOverload(&QComboBox::currentIndexChanged), property, + [property, this](int index) { + property->setValue(m_enumValues.isEmpty() ? index : m_enumValues.at(index)); + }); + + return editor; +} + + +ValueTypeEditorFactory::ValueTypeEditorFactory() +{ + // Register some useful default editor factories + registerEditorFactory(QMetaType::Bool, std::make_unique()); + registerEditorFactory(QMetaType::Double, std::make_unique()); + registerEditorFactory(QMetaType::Int, std::make_unique()); + registerEditorFactory(QMetaType::QColor, std::make_unique()); + registerEditorFactory(QMetaType::QPoint, std::make_unique()); + registerEditorFactory(QMetaType::QPointF, std::make_unique()); + registerEditorFactory(QMetaType::QRectF, std::make_unique()); + registerEditorFactory(QMetaType::QSize, std::make_unique()); + registerEditorFactory(QMetaType::QString, std::make_unique()); +} + +void ValueTypeEditorFactory::registerEditorFactory(int type, std::unique_ptr factory) +{ + m_factories[type] = std::move(factory); +} + +QObjectProperty *ValueTypeEditorFactory::createQObjectProperty(QObject *qObject, + const char *name, + const QString &displayName) +{ + auto metaObject = qObject->metaObject(); + auto propertyIndex = metaObject->indexOfProperty(name); + if (propertyIndex < 0) + return nullptr; + + return new QObjectProperty(qObject, + metaObject->property(propertyIndex), + displayName.isEmpty() ? QString::fromUtf8(name) + : displayName, + this); +} + +ValueProperty *ValueTypeEditorFactory::createProperty(const QString &name, const QVariant &value) +{ + const int type = value.userType(); + auto factory = m_factories.find(type); + if (factory != m_factories.end()) + return new ValueProperty(name, value, factory->second.get()); + return nullptr; +} + +QWidget *ValueTypeEditorFactory::createEditor(Property *property, QWidget *parent) +{ + const auto value = property->value(); + const int type = value.userType(); + auto factory = m_factories.find(type); + if (factory != m_factories.end()) + return factory->second->createEditor(property, parent); + return nullptr; +} + + +QObjectProperty::QObjectProperty(QObject *object, + QMetaProperty property, + const QString &displayName, + EditorFactory *editorFactory, + QObject *parent) + : AbstractProperty(displayName, editorFactory, parent) + , m_object(object) + , m_property(property) +{ + // If the property has a notify signal, forward it to valueChanged + auto notify = property.notifySignal(); + if (notify.isValid()) { + auto valuePropertyIndex = metaObject()->indexOfProperty("value"); + auto valueProperty = metaObject()->property(valuePropertyIndex); + auto valueChanged = valueProperty.notifySignal(); + + connect(m_object, notify, this, valueChanged); + } + + setEnabled(m_property.isWritable()); +} + +QVariant QObjectProperty::value() const +{ + return m_property.read(m_object); +} + +void QObjectProperty::setValue(const QVariant &value) +{ + m_property.write(m_object, value); +} + } // namespace Tiled #include "moc_varianteditor.cpp" diff --git a/src/tiled/varianteditor.h b/src/tiled/varianteditor.h index 8fe48c725a..2057629da6 100644 --- a/src/tiled/varianteditor.h +++ b/src/tiled/varianteditor.h @@ -21,6 +21,7 @@ #pragma once #include +#include #include #include #include @@ -34,6 +35,9 @@ class QGridLayout; namespace Tiled { +/** + * A property represents a named value that can create its own edit widget. + */ class Property : public QObject { Q_OBJECT @@ -47,7 +51,7 @@ class Property : public QObject , m_name(name) {} - QString name() const { return m_name; } + const QString &name() const { return m_name; } bool isEnabled() const { return m_enabled; } void setEnabled(bool enabled) @@ -72,61 +76,168 @@ class Property : public QObject bool m_enabled = true; }; -class VariantProperty : public Property +/** + * An editor factory is responsible for creating an editor widget for a given + * property. It can be used to share the configuration of editor widgets + * between different properties. + */ +class EditorFactory +{ + Q_DECLARE_TR_FUNCTIONS(EditorFactory) + +public: + virtual QWidget *createEditor(Property *property, QWidget *parent) = 0; +}; + +/** + * An editor factory that creates a combo box for enum properties. + */ +class EnumEditorFactory : public EditorFactory +{ +public: + EnumEditorFactory(const QStringList &enumNames = {}, + const QList &enumValues = {}); + + void setEnumNames(const QStringList &enumNames); + void setEnumValues(const QList &enumValues); + + QWidget *createEditor(Property *property, QWidget *parent) override; + +private: + QStringListModel m_enumNamesModel; + QList m_enumValues; +}; + +/** + * A property that uses an editor factory to create its editor, but does not + * store a value itself. + * + * The property does not take ownership of the editor factory. + */ +class AbstractProperty : public Property { Q_OBJECT public: - VariantProperty(const QString &name, const QVariant &value, QObject *parent = nullptr) - : Property(name, parent) - , m_value(value) - { - } + AbstractProperty(const QString &name, + EditorFactory *editorFactory, + QObject *parent = nullptr); + + QWidget *createEditor(QWidget *parent) override; + +private: + EditorFactory *m_editorFactory; +}; + +/** + * A property that stores a value of a given type and uses an editor factory to + * create its editor. + * + * The property does not take ownership of the editor factory. + */ +class ValueProperty : public AbstractProperty +{ + Q_OBJECT + +public: + ValueProperty(const QString &name, + const QVariant &value, + EditorFactory *editorFactory, + QObject *parent = nullptr); QVariant value() const override { return m_value; } void setValue(const QVariant &value) override; - QWidget *createEditor(QWidget *parent) override; - private: QVariant m_value; }; -class EnumProperty : public Property +/** + * A property that wraps a value of a QObject property and uses an editor + * factory to create its editor. + * + * The property does not take ownership of the editor factory. + */ +class QObjectProperty : public AbstractProperty { Q_OBJECT public: - EnumProperty(const QString &name, QObject *parent = nullptr) - : Property(name, parent) - {} + QObjectProperty(QObject *object, + QMetaProperty property, + const QString &displayName, + EditorFactory *editorFactory, + QObject *parent = nullptr); - QWidget *createEditor(QWidget *parent) override; + QVariant value() const override; + void setValue(const QVariant &value) override; - void setEnumNames(const QStringList &enumNames) - { - m_enumNamesModel.setStringList(enumNames); - } +private: + QObject *m_object; + QMetaProperty m_property; +}; - void setEnumValues(const QList &enumValues) - { - m_enumValues = enumValues; - } +/** + * An editor factory that selects the appropriate editor factory based on the + * type of the property value. + * + * todo: rename to VariantEditorFactory when the old one is removed + */ +class ValueTypeEditorFactory : public EditorFactory +{ +public: + ValueTypeEditorFactory(); + + /** + * Register an editor factory for a given type. + * + * When there is already an editor factory registered for the given type, + * it will be replaced. + */ + void registerEditorFactory(int type, std::unique_ptr factory); + + /** + * Creates a property that wraps a QObject property and will use the editor + * factory registered for the type of the value. + */ + QObjectProperty *createQObjectProperty(QObject *qObject, + const char *name, + const QString &displayName = {}); + + /** + * Creates a property with the given name and value. The property will use + * the editor factory registered for the type of the value. + */ + ValueProperty *createProperty(const QString &name, const QVariant &value); + + QWidget *createEditor(Property *property, QWidget *parent) override; private: - QStringListModel m_enumNamesModel; - QList m_enumValues; + std::unordered_map> m_factories; }; -class EditorFactory +/** + * A property that wraps an enum value and uses an editor factory to create + * its editor. + */ +class EnumProperty : public AbstractProperty { - Q_DECLARE_TR_FUNCTIONS(EditorFactory) + Q_OBJECT public: - virtual QWidget *createEditor(const QVariant &value, - QWidget *parent) = 0; + EnumProperty(const QString &name, + const QStringList &enumNames = {}, + const QList &enumValues = {}, + QObject *parent = nullptr); + + void setEnumNames(const QStringList &enumNames); + void setEnumValues(const QList &enumValues); + +private: + EnumEditorFactory m_editorFactory; }; + class VariantEditor : public QScrollArea { Q_OBJECT @@ -134,8 +245,6 @@ class VariantEditor : public QScrollArea public: VariantEditor(QWidget *parent = nullptr); - void registerEditorFactory(int type, std::unique_ptr factory); - void clear(); void addHeader(const QString &text); void addSeparator(); @@ -157,7 +266,6 @@ class VariantEditor : public QScrollArea QWidget *m_widget; QGridLayout *m_gridLayout; int m_rowIndex = 0; - std::unordered_map> m_factories; }; } // namespace Tiled From faff00d60e61acd1bca9f6beac6199b365f17854 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Mon, 2 Sep 2024 15:32:21 +0200 Subject: [PATCH 07/36] Implemented "Infinite" and "Hex Side Length" properties * Introduced MapProperties class to make it easier to emit valueChanged for all the map's properties, as well to move their creation out of PropertiesWidget::currentObjectChanged. * Added GetSetProperty that makes it easier to define a property in terms of a given getter and setter function, since it avoids the need for a specific Property subclass. * Finished implementation of the BoolEditorFactory (signals connected). * Moved property edit widgets to their own file. --- src/tiled/libtilededitor.qbs | 2 + src/tiled/propertieswidget.cpp | 146 ++++++++++--- src/tiled/propertieswidget.h | 3 +- src/tiled/propertyeditorwidgets.cpp | 230 +++++++++++++++++++++ src/tiled/propertyeditorwidgets.h | 109 ++++++++++ src/tiled/varianteditor.cpp | 310 +++------------------------- src/tiled/varianteditor.h | 36 +++- 7 files changed, 523 insertions(+), 313 deletions(-) create mode 100644 src/tiled/propertyeditorwidgets.cpp create mode 100644 src/tiled/propertyeditorwidgets.h diff --git a/src/tiled/libtilededitor.qbs b/src/tiled/libtilededitor.qbs index fcba0f79ce..8b4ca0cf35 100644 --- a/src/tiled/libtilededitor.qbs +++ b/src/tiled/libtilededitor.qbs @@ -407,6 +407,8 @@ DynamicLibrary { "propertieswidget.h", "propertybrowser.cpp", "propertybrowser.h", + "propertyeditorwidgets.cpp", + "propertyeditorwidgets.h", "propertytypeseditor.cpp", "propertytypeseditor.h", "propertytypeseditor.ui", diff --git a/src/tiled/propertieswidget.cpp b/src/tiled/propertieswidget.cpp index 0e7a882c01..9afabf0231 100644 --- a/src/tiled/propertieswidget.cpp +++ b/src/tiled/propertieswidget.cpp @@ -46,7 +46,6 @@ namespace Tiled { PropertiesWidget::PropertiesWidget(QWidget *parent) : QWidget{parent} - , mDocument(nullptr) , mPropertyBrowser(new VariantEditor(this)) , mDefaultEditorFactory(std::make_unique()) { @@ -150,8 +149,8 @@ class MapOrientationProperty : public EnumProperty Q_OBJECT public: - MapOrientationProperty(MapDocument *mapDocument) - : EnumProperty(tr("Orientation")) + MapOrientationProperty(MapDocument *mapDocument, QObject *parent = nullptr) + : EnumProperty(tr("Orientation"), parent) , mMapDocument(mapDocument) { setEnumNames({ @@ -166,9 +165,6 @@ class MapOrientationProperty : public EnumProperty Map::Staggered, Map::Hexagonal, }); - - connect(mMapDocument, &MapDocument::mapChanged, - this, &Property::valueChanged); } QVariant value() const override @@ -192,8 +188,9 @@ class MapSizeProperty : public AbstractProperty Q_OBJECT public: - MapSizeProperty(MapDocument *mapDocument, EditorFactory *editorFactory) - : AbstractProperty(tr("Map Size"), editorFactory) + MapSizeProperty(MapDocument *mapDocument, EditorFactory *editorFactory, + QObject *parent = nullptr) + : AbstractProperty(tr("Map Size"), editorFactory, parent) , mMapDocument(mapDocument) { connect(mMapDocument, &MapDocument::mapChanged, @@ -231,12 +228,12 @@ class TileSizeProperty : public AbstractProperty Q_OBJECT public: - TileSizeProperty(MapDocument *mapDocument, EditorFactory *editorFactory) - : AbstractProperty(tr("Tile Size"), editorFactory) + TileSizeProperty(MapDocument *mapDocument, + EditorFactory *editorFactory, + QObject *parent = nullptr) + : AbstractProperty(tr("Tile Size"), editorFactory, parent) , mMapDocument(mapDocument) { - connect(mMapDocument, &Document::changed, - this, &TileSizeProperty::onChanged); } QVariant value() const override @@ -265,17 +262,114 @@ class TileSizeProperty : public AbstractProperty }; private: - void onChanged(const ChangeEvent &event) + MapDocument *mMapDocument; +}; + +class MapProperties : public QObject +{ + Q_OBJECT + +public: + MapProperties(MapDocument *mapDocument, + ValueTypeEditorFactory *editorFactory, + QObject *parent = nullptr) + : QObject(parent) + , mMapDocument(mapDocument) + , mOrientationProperty(new MapOrientationProperty(mapDocument, this)) + , mSizeProperty(new MapSizeProperty(mapDocument, editorFactory, this)) + , mTileSizeProperty(new TileSizeProperty(mapDocument, editorFactory, this)) + { + mInfiniteProperty = editorFactory->createProperty( + tr("Infinite"), + [this]() { + return mMapDocument->map()->infinite(); + }, + [this](const QVariant &value) { + auto command = new ChangeMapProperty(mMapDocument, + Map::InfiniteProperty, + value.toBool()); + mMapDocument->undoStack()->push(command); + }); + + mHexSideLengthProperty = editorFactory->createProperty( + tr("Hex Side Length"), + [this]() { + return mMapDocument->map()->hexSideLength(); + }, + [this](const QVariant &value) { + auto command = new ChangeMapProperty(mMapDocument, + Map::HexSideLengthProperty, + value.toInt()); + mMapDocument->undoStack()->push(command); + }); + + connect(mMapDocument, &MapDocument::changed, + this, &MapProperties::onMapChanged); + } + + void populateEditor(VariantEditor *editor) + { + editor->addHeader(tr("Map")); + editor->addProperty(mOrientationProperty); + editor->addProperty(mSizeProperty); + editor->addProperty(mTileSizeProperty); + editor->addProperty(mInfiniteProperty); + editor->addProperty(mHexSideLengthProperty); + // editor->addProperty(mStaggerAxisProperty); + // editor->addProperty(mStaggerIndexProperty); + // editor->addProperty(mParallaxOriginProperty); + // editor->addProperty(mLayerDataFormatProperty); + // editor->addProperty(mChunkSizeProperty); + // editor->addProperty(mTileRenderOrderProperty); + // editor->addProperty(mCompressionLevelProperty); + // editor->addProperty(mBackgroundColorProperty); + } + +private: + void onMapChanged(const ChangeEvent &event) { if (event.type != ChangeEvent::MapChanged) return; const auto property = static_cast(event).property; - if (property == Map::TileWidthProperty || property == Map::TileHeightProperty) - emit valueChanged(); + switch (property) { + case Map::TileWidthProperty: + case Map::TileHeightProperty: + emit mTileSizeProperty->valueChanged(); + break; + case Map::InfiniteProperty: + emit mInfiniteProperty->valueChanged(); + break; + case Map::HexSideLengthProperty: + case Map::StaggerAxisProperty: + case Map::StaggerIndexProperty: + case Map::ParallaxOriginProperty: + case Map::OrientationProperty: + emit mOrientationProperty->valueChanged(); + break; + case Map::RenderOrderProperty: + case Map::BackgroundColorProperty: + case Map::LayerDataFormatProperty: + case Map::CompressionLevelProperty: + case Map::ChunkSizeProperty: + break; + } } MapDocument *mMapDocument; + Property *mOrientationProperty; + Property *mSizeProperty; + Property *mTileSizeProperty; + Property *mInfiniteProperty; + Property *mHexSideLengthProperty; + Property *mStaggerAxisProperty; + Property *mStaggerIndexProperty; + Property *mParallaxOriginProperty; + Property *mLayerDataFormatProperty; + Property *mChunkSizeProperty; + Property *mTileRenderOrderProperty; + Property *mCompressionLevelProperty; + Property *mBackgroundColorProperty; }; @@ -283,6 +377,8 @@ void PropertiesWidget::currentObjectChanged(Object *object) { // mPropertyBrowser->setObject(object); mPropertyBrowser->clear(); + delete mPropertiesObject; + mPropertiesObject = nullptr; if (object) { switch (object->typeId()) { @@ -290,24 +386,10 @@ void PropertiesWidget::currentObjectChanged(Object *object) case Object::MapObjectType: break; case Object::MapType: { - Map *map = static_cast(object); auto mapDocument = static_cast(mDocument); - - mPropertyBrowser->addHeader(tr("Map")); - mPropertyBrowser->addProperty(new MapOrientationProperty(mapDocument)); - mPropertyBrowser->addProperty(new MapSizeProperty(mapDocument, mDefaultEditorFactory.get())); - mPropertyBrowser->addProperty(new TileSizeProperty(mapDocument, mDefaultEditorFactory.get())); - // todo: infinite - // todo: hex side length - // todo: stagger axis - // todo: stagger index - // todo: parallax origin - // todo: layer data format - // todo: chunk size - // todo: tile render order - // todo: compression level - // todo: background color - + auto properties = new MapProperties(mapDocument, mDefaultEditorFactory.get(), this); + properties->populateEditor(mPropertyBrowser); + mPropertiesObject = properties; break; } case Object::TilesetType: diff --git a/src/tiled/propertieswidget.h b/src/tiled/propertieswidget.h index f9f5db095e..55447f7247 100644 --- a/src/tiled/propertieswidget.h +++ b/src/tiled/propertieswidget.h @@ -76,7 +76,8 @@ public slots: void retranslateUi(); - Document *mDocument; + Document *mDocument = nullptr; + QObject *mPropertiesObject = nullptr; VariantEditor *mPropertyBrowser; std::unique_ptr mDefaultEditorFactory; QAction *mActionAddProperty; diff --git a/src/tiled/propertyeditorwidgets.cpp b/src/tiled/propertyeditorwidgets.cpp new file mode 100644 index 0000000000..36e899a3b0 --- /dev/null +++ b/src/tiled/propertyeditorwidgets.cpp @@ -0,0 +1,230 @@ +#include "propertyeditorwidgets.h" + +#include "utils.h" + +#include +#include +#include +#include +#include + +namespace Tiled { + +/** + * Strips a floating point number representation of redundant trailing zeros. + * Examples: + * + * 0.01000 -> 0.01 + * 3.000 -> 3.0 + */ +static QString removeRedundantTrialingZeros(const QString &text) +{ + const QString decimalPoint = QLocale::system().decimalPoint(); + const auto decimalPointIndex = text.lastIndexOf(decimalPoint); + if (decimalPointIndex < 0) // return if there is no decimal point + return text; + + const auto afterDecimalPoint = decimalPointIndex + decimalPoint.length(); + int redundantZeros = 0; + + for (int i = text.length() - 1; i > afterDecimalPoint && text.at(i) == QLatin1Char('0'); --i) + ++redundantZeros; + + return text.left(text.length() - redundantZeros); +} + + +SpinBox::SpinBox(QWidget *parent) + : QSpinBox(parent) +{ + // Allow the full range by default. + setRange(std::numeric_limits::lowest(), + std::numeric_limits::max()); + + // Allow the widget to shrink horizontally. + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); +} + +QSize SpinBox::minimumSizeHint() const +{ + // Don't adjust the horizontal size hint based on the maximum value. + auto hint = QSpinBox::minimumSizeHint(); + hint.setWidth(Utils::dpiScaled(50)); + return hint; +} + + +DoubleSpinBox::DoubleSpinBox(QWidget *parent) + : QDoubleSpinBox(parent) +{ + // Allow the full range by default. + setRange(std::numeric_limits::lowest(), + std::numeric_limits::max()); + + // Increase possible precision. + setDecimals(9); + + // Allow the widget to shrink horizontally. + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); +} + +QSize DoubleSpinBox::minimumSizeHint() const +{ + // Don't adjust the horizontal size hint based on the maximum value. + auto hint = QDoubleSpinBox::minimumSizeHint(); + hint.setWidth(Utils::dpiScaled(50)); + return hint; +} + +QString DoubleSpinBox::textFromValue(double val) const +{ + auto text = QDoubleSpinBox::textFromValue(val); + + // remove redundant trailing 0's in case of high precision + if (decimals() > 3) + return removeRedundantTrialingZeros(text); + + return text; +} + + +SizeEdit::SizeEdit(QWidget *parent) + : QWidget(parent) + , m_widthLabel(new QLabel(QStringLiteral("W"), this)) + , m_heightLabel(new QLabel(QStringLiteral("H"), this)) + , m_widthSpinBox(new SpinBox(this)) + , m_heightSpinBox(new SpinBox(this)) +{ + m_widthLabel->setAlignment(Qt::AlignCenter); + m_heightLabel->setAlignment(Qt::AlignCenter); + + auto layout = new QGridLayout(this); + layout->setContentsMargins(QMargins()); + layout->setColumnStretch(1, 1); + layout->setColumnStretch(3, 1); + layout->setSpacing(Utils::dpiScaled(3)); + + const int horizontalMargin = Utils::dpiScaled(3); + m_widthLabel->setContentsMargins(horizontalMargin, 0, horizontalMargin, 0); + m_heightLabel->setContentsMargins(horizontalMargin, 0, horizontalMargin, 0); + + layout->addWidget(m_widthLabel, 0, 0); + layout->addWidget(m_widthSpinBox, 0, 1); + layout->addWidget(m_heightLabel, 0, 2); + layout->addWidget(m_heightSpinBox, 0, 3); + + connect(m_widthSpinBox, qOverload(&QSpinBox::valueChanged), this, &SizeEdit::valueChanged); + connect(m_heightSpinBox, qOverload(&QSpinBox::valueChanged), this, &SizeEdit::valueChanged); +} + + +void SizeEdit::setValue(const QSize &size) +{ + m_widthSpinBox->setValue(size.width()); + m_heightSpinBox->setValue(size.height()); +} + +QSize SizeEdit::value() const +{ + return QSize(m_widthSpinBox->value(), m_heightSpinBox->value()); +} + +void SizeEdit::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + + const auto orientation = event->size().width() < minimumHorizontalWidth() + ? Qt::Vertical : Qt::Horizontal; + + if (m_orientation != orientation) { + m_orientation = orientation; + + auto layout = qobject_cast(this->layout()); + + // Remove all widgets from layout, without deleting them + layout->removeWidget(m_widthLabel); + layout->removeWidget(m_widthSpinBox); + layout->removeWidget(m_heightLabel); + layout->removeWidget(m_heightSpinBox); + + if (orientation == Qt::Horizontal) { + layout->addWidget(m_widthLabel, 0, 0); + layout->addWidget(m_widthSpinBox, 0, 1); + layout->addWidget(m_heightLabel, 0, 2); + layout->addWidget(m_heightSpinBox, 0, 3); + layout->setColumnStretch(3, 1); + } else { + layout->addWidget(m_widthLabel, 0, 0); + layout->addWidget(m_widthSpinBox, 0, 1); + layout->addWidget(m_heightLabel, 1, 0); + layout->addWidget(m_heightSpinBox, 1, 1); + layout->setColumnStretch(3, 0); + } + + // this avoids flickering when the layout changes + layout->activate(); + } +} + +int SizeEdit::minimumHorizontalWidth() const +{ + return m_widthLabel->minimumSizeHint().width() + + m_widthSpinBox->minimumSizeHint().width() + + m_heightLabel->minimumSizeHint().width() + + m_heightSpinBox->minimumSizeHint().width() + + layout()->spacing() * 3; +} + + +ElidingLabel::ElidingLabel(QWidget *parent) + : ElidingLabel(QString(), parent) +{} + +ElidingLabel::ElidingLabel(const QString &text, QWidget *parent) + : QLabel(text, parent) +{ + setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed)); +} + +QSize ElidingLabel::minimumSizeHint() const +{ + auto hint = QLabel::minimumSizeHint(); + hint.setWidth(std::min(hint.width(), Utils::dpiScaled(30))); + return hint; +} + +void ElidingLabel::paintEvent(QPaintEvent *) +{ + const int m = margin(); + const QRect cr = contentsRect().adjusted(m, m, -m, -m); + const Qt::LayoutDirection dir = text().isRightToLeft() ? Qt::RightToLeft : Qt::LeftToRight; + const int align = QStyle::visualAlignment(dir, alignment()); + const int flags = align | (dir == Qt::LeftToRight ? Qt::TextForceLeftToRight + : Qt::TextForceRightToLeft); + + QStyleOption opt; + opt.initFrom(this); + + const auto elidedText = opt.fontMetrics.elidedText(text(), Qt::ElideRight, cr.width()); + const bool isElided = elidedText != text(); + + if (isElided != m_isElided) { + m_isElided = isElided; + setToolTip(isElided ? text() : QString()); + } + + QPainter painter(this); + QWidget::style()->drawItemText(&painter, cr, flags, opt.palette, isEnabled(), elidedText, foregroundRole()); +} + + +QSize LineEditLabel::sizeHint() const +{ + auto hint = ElidingLabel::sizeHint(); + hint.setHeight(m_lineEdit.sizeHint().height()); + return hint; +} + +} // namespace Tiled + +#include "moc_propertyeditorwidgets.cpp" diff --git a/src/tiled/propertyeditorwidgets.h b/src/tiled/propertyeditorwidgets.h new file mode 100644 index 0000000000..f33eaf7fbd --- /dev/null +++ b/src/tiled/propertyeditorwidgets.h @@ -0,0 +1,109 @@ +#pragma once + +#include +#include +#include + +class QLabel; + +namespace Tiled { + +/** + * A spin box that allows the full range by default and shrinks horizontally. + * It also doesn't adjust the horizontal size hint based on the maximum value. + */ +class SpinBox : public QSpinBox +{ + Q_OBJECT + +public: + SpinBox(QWidget *parent = nullptr); + + QSize minimumSizeHint() const override; +}; + +/** + * A double spin box that allows the full range by default and shrinks + * horizontally. It also doesn't adjust the horizontal size hint based on the + * maximum value. + * + * The precision is increased to 9 decimal places. Redundant trailing 0's are + * removed. + */ +class DoubleSpinBox : public QDoubleSpinBox +{ + Q_OBJECT + +public: + DoubleSpinBox(QWidget *parent = nullptr); + + QSize minimumSizeHint() const override; + QString textFromValue(double val) const override; +}; + +/** + * A widget for editing a QSize value. + */ +class SizeEdit : public QWidget +{ + Q_OBJECT + Q_PROPERTY(QSize value READ value WRITE setValue NOTIFY valueChanged FINAL) + +public: + SizeEdit(QWidget *parent = nullptr); + + void setValue(const QSize &size); + QSize value() const; + +signals: + void valueChanged(); + +private: + void resizeEvent(QResizeEvent *event) override; + + int minimumHorizontalWidth() const; + + Qt::Orientation m_orientation = Qt::Horizontal; + QLabel *m_widthLabel; + QLabel *m_heightLabel; + SpinBox *m_widthSpinBox; + SpinBox *m_heightSpinBox; +}; + +/** + * A label that elides its text if there is not enough space. + * + * The elided text is shown as a tooltip. + */ +class ElidingLabel : public QLabel +{ + Q_OBJECT + +public: + explicit ElidingLabel(QWidget *parent = nullptr); + ElidingLabel(const QString &text, QWidget *parent = nullptr); + + QSize minimumSizeHint() const override; + void paintEvent(QPaintEvent *) override; + +private: + bool m_isElided = false; +}; + +/** + * A label that matches its preferred height with that of a line edit. + */ +class LineEditLabel : public ElidingLabel +{ + Q_OBJECT + +public: + using ElidingLabel::ElidingLabel; + + QSize sizeHint() const override; + +private: + QLineEdit m_lineEdit; +}; + +} // namespace Tiled diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index 60d2290417..523ad504c4 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -19,10 +19,12 @@ */ #include "varianteditor.h" + #include "colorbutton.h" #include "compression.h" #include "map.h" #include "utils.h" +#include "propertyeditorwidgets.h" #include #include @@ -51,164 +53,17 @@ QWidget *AbstractProperty::createEditor(QWidget *parent) } -class SpinBox : public QSpinBox -{ - Q_OBJECT - -public: - SpinBox(QWidget *parent = nullptr) - : QSpinBox(parent) - { - // Allow the full range by default. - setRange(std::numeric_limits::lowest(), - std::numeric_limits::max()); - - // Allow the widget to shrink horizontally. - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); - } - - QSize minimumSizeHint() const override - { - // Don't adjust the horizontal size hint based on the maximum value. - auto hint = QSpinBox::minimumSizeHint(); - hint.setWidth(Utils::dpiScaled(50)); - return hint; - } -}; - -/** - * Strips a floating point number representation of redundant trailing zeros. - * Examples: - * - * 0.01000 -> 0.01 - * 3.000 -> 3.0 - */ -QString removeRedundantTrialingZeros(const QString &text) -{ - const QString decimalPoint = QLocale::system().decimalPoint(); - const auto decimalPointIndex = text.lastIndexOf(decimalPoint); - if (decimalPointIndex < 0) // return if there is no decimal point - return text; - - const auto afterDecimalPoint = decimalPointIndex + decimalPoint.length(); - int redundantZeros = 0; - - for (int i = text.length() - 1; i > afterDecimalPoint && text.at(i) == QLatin1Char('0'); --i) - ++redundantZeros; - - return text.left(text.length() - redundantZeros); -} - -class DoubleSpinBox : public QDoubleSpinBox -{ - Q_OBJECT - -public: - DoubleSpinBox(QWidget *parent = nullptr) - : QDoubleSpinBox(parent) - { - // Allow the full range by default. - setRange(std::numeric_limits::lowest(), - std::numeric_limits::max()); - - // Increase possible precision. - setDecimals(9); - - // Allow the widget to shrink horizontally. - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); - } - - QSize minimumSizeHint() const override - { - // Don't adjust the horizontal size hint based on the maximum value. - auto hint = QDoubleSpinBox::minimumSizeHint(); - hint.setWidth(Utils::dpiScaled(50)); - return hint; - } - - // QDoubleSpinBox interface - QString textFromValue(double val) const override - { - auto text = QDoubleSpinBox::textFromValue(val); - - // remove redundant trailing 0's in case of high precision - if (decimals() > 3) - return removeRedundantTrialingZeros(text); - - return text; - } -}; - - -// A label that elides its text if there is not enough space -class ElidingLabel : public QLabel -{ - Q_OBJECT - -public: - explicit ElidingLabel(QWidget *parent = nullptr) - : ElidingLabel(QString(), parent) - {} - - ElidingLabel(const QString &text, QWidget *parent = nullptr) - : QLabel(text, parent) - { - setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed)); - } - - QSize minimumSizeHint() const override - { - auto hint = QLabel::minimumSizeHint(); - hint.setWidth(std::min(hint.width(), Utils::dpiScaled(30))); - return hint; - } - - void paintEvent(QPaintEvent *) override - { - const int m = margin(); - const QRect cr = contentsRect().adjusted(m, m, -m, -m); - const Qt::LayoutDirection dir = text().isRightToLeft() ? Qt::RightToLeft : Qt::LeftToRight; - const int align = QStyle::visualAlignment(dir, alignment()); - const int flags = align | (dir == Qt::LeftToRight ? Qt::TextForceLeftToRight - : Qt::TextForceRightToLeft); - - QStyleOption opt; - opt.initFrom(this); - - const auto elidedText = opt.fontMetrics.elidedText(text(), Qt::ElideRight, cr.width()); - - const bool isElided = elidedText != text(); - if (isElided != m_isElided) { - m_isElided = isElided; - setToolTip(isElided ? text() : QString()); - } - - QPainter painter(this); - QWidget::style()->drawItemText(&painter, cr, flags, opt.palette, isEnabled(), elidedText, foregroundRole()); - } - -private: - bool m_isElided = false; -}; - -// A label that matches its preferred height with that of a line edit -class LineEditLabel : public ElidingLabel -{ - Q_OBJECT +GetSetProperty::GetSetProperty(const QString &name, + std::function get, + std::function set, + EditorFactory *editorFactory, + QObject *parent) + : AbstractProperty(name, editorFactory, parent) + , m_get(std::move(get)) + , m_set(std::move(set)) +{} -public: - using ElidingLabel::ElidingLabel; - QSize sizeHint() const override - { - auto hint = ElidingLabel::sizeHint(); - hint.setHeight(lineEdit.sizeHint().height()); - return hint; - } - -private: - QLineEdit lineEdit; -}; class StringEditorFactory : public EditorFactory { @@ -251,14 +106,19 @@ class BoolEditorFactory : public EditorFactory public: QWidget *createEditor(Property *property, QWidget *parent) override { - auto value = property->value(); auto editor = new QCheckBox(parent); - bool checked = value.toBool(); - editor->setChecked(checked); - editor->setText(checked ? tr("On") : tr("Off")); + auto syncEditor = [=]() { + const QSignalBlocker blocker(editor); + bool checked = property->value().toBool(); + editor->setChecked(checked); + editor->setText(checked ? tr("On") : tr("Off")); + }; + syncEditor(); - QObject::connect(editor, &QCheckBox::toggled, [editor](bool checked) { + QObject::connect(property, &Property::valueChanged, editor, syncEditor); + QObject::connect(editor, &QCheckBox::toggled, property, [=](bool checked) { editor->setText(checked ? QObject::tr("On") : QObject::tr("Off")); + property->setValue(checked); }); return editor; @@ -327,34 +187,6 @@ class PointFEditorFactory : public EditorFactory } }; -/** - * A widget for editing a QSize value. - */ -class SizeEdit : public QWidget -{ - Q_OBJECT - Q_PROPERTY(QSize value READ value WRITE setValue NOTIFY valueChanged FINAL) - -public: - SizeEdit(QWidget *parent = nullptr); - - void setValue(const QSize &size); - QSize value() const; - -signals: - void valueChanged(); - -private: - void resizeEvent(QResizeEvent *event) override; - - int minimumHorizontalWidth() const; - - Qt::Orientation m_orientation = Qt::Horizontal; - QLabel *m_widthLabel; - QLabel *m_heightLabel; - SpinBox *m_widthSpinBox; - SpinBox *m_heightSpinBox; -}; class SizeEditorFactory : public EditorFactory { @@ -460,11 +292,8 @@ void ValueProperty::setValue(const QVariant &value) EnumProperty::EnumProperty(const QString &name, - const QStringList &enumNames, - const QList &enumValues, QObject *parent) : AbstractProperty(name, &m_editorFactory, parent) - , m_editorFactory(enumNames, enumValues) {} void EnumProperty::setEnumNames(const QStringList &enumNames) @@ -699,91 +528,6 @@ QWidget *VariantEditor::createEditor(Property *property) return nullptr; } -SizeEdit::SizeEdit(QWidget *parent) - : QWidget(parent) - , m_widthLabel(new QLabel(QStringLiteral("W"), this)) - , m_heightLabel(new QLabel(QStringLiteral("H"), this)) - , m_widthSpinBox(new SpinBox(this)) - , m_heightSpinBox(new SpinBox(this)) -{ - m_widthLabel->setAlignment(Qt::AlignCenter); - m_heightLabel->setAlignment(Qt::AlignCenter); - - auto layout = new QGridLayout(this); - layout->setContentsMargins(QMargins()); - layout->setColumnStretch(1, 1); - layout->setColumnStretch(3, 1); - layout->setSpacing(Utils::dpiScaled(3)); - - const int horizontalMargin = Utils::dpiScaled(3); - m_widthLabel->setContentsMargins(horizontalMargin, 0, horizontalMargin, 0); - m_heightLabel->setContentsMargins(horizontalMargin, 0, horizontalMargin, 0); - - layout->addWidget(m_widthLabel, 0, 0); - layout->addWidget(m_widthSpinBox, 0, 1); - layout->addWidget(m_heightLabel, 0, 2); - layout->addWidget(m_heightSpinBox, 0, 3); - - connect(m_widthSpinBox, qOverload(&QSpinBox::valueChanged), this, &SizeEdit::valueChanged); - connect(m_heightSpinBox, qOverload(&QSpinBox::valueChanged), this, &SizeEdit::valueChanged); -} - -void SizeEdit::setValue(const QSize &size) -{ - m_widthSpinBox->setValue(size.width()); - m_heightSpinBox->setValue(size.height()); -} - -QSize SizeEdit::value() const -{ - return QSize(m_widthSpinBox->value(), m_heightSpinBox->value()); -} - -void SizeEdit::resizeEvent(QResizeEvent *event) -{ - QWidget::resizeEvent(event); - - const auto orientation = event->size().width() < minimumHorizontalWidth() - ? Qt::Vertical : Qt::Horizontal; - - if (m_orientation != orientation) { - m_orientation = orientation; - - auto layout = qobject_cast(this->layout()); - - // Remove all widgets from layout, without deleting them - layout->removeWidget(m_widthLabel); - layout->removeWidget(m_widthSpinBox); - layout->removeWidget(m_heightLabel); - layout->removeWidget(m_heightSpinBox); - - if (orientation == Qt::Horizontal) { - layout->addWidget(m_widthLabel, 0, 0); - layout->addWidget(m_widthSpinBox, 0, 1); - layout->addWidget(m_heightLabel, 0, 2); - layout->addWidget(m_heightSpinBox, 0, 3); - layout->setColumnStretch(3, 1); - } else { - layout->addWidget(m_widthLabel, 0, 0); - layout->addWidget(m_widthSpinBox, 0, 1); - layout->addWidget(m_heightLabel, 1, 0); - layout->addWidget(m_heightSpinBox, 1, 1); - layout->setColumnStretch(3, 0); - } - - // this avoids flickering when the layout changes - layout->activate(); - } -} - -int SizeEdit::minimumHorizontalWidth() const -{ - return m_widthLabel->minimumSizeHint().width() + - m_widthSpinBox->minimumSizeHint().width() + - m_heightLabel->minimumSizeHint().width() + - m_heightSpinBox->minimumSizeHint().width() + - layout()->spacing() * 3; -} EnumEditorFactory::EnumEditorFactory(const QStringList &enumNames, @@ -872,6 +616,17 @@ ValueProperty *ValueTypeEditorFactory::createProperty(const QString &name, const return nullptr; } +AbstractProperty *ValueTypeEditorFactory::createProperty(const QString &name, + std::function get, + std::function set) +{ + const int type = get().userType(); + auto factory = m_factories.find(type); + if (factory != m_factories.end()) + return new GetSetProperty(name, get, set, factory->second.get()); + return nullptr; +} + QWidget *ValueTypeEditorFactory::createEditor(Property *property, QWidget *parent) { const auto value = property->value(); @@ -918,4 +673,3 @@ void QObjectProperty::setValue(const QVariant &value) } // namespace Tiled #include "moc_varianteditor.cpp" -#include "varianteditor.moc" diff --git a/src/tiled/varianteditor.h b/src/tiled/varianteditor.h index 2057629da6..8099181347 100644 --- a/src/tiled/varianteditor.h +++ b/src/tiled/varianteditor.h @@ -129,6 +129,31 @@ class AbstractProperty : public Property EditorFactory *m_editorFactory; }; +/** + * A property that uses the given functions to get and set the value and uses + * an editor factory to create its editor. + * + * The property does not take ownership of the editor factory. + */ +class GetSetProperty : public AbstractProperty +{ + Q_OBJECT + +public: + GetSetProperty(const QString &name, + std::function get, + std::function set, + EditorFactory *editorFactory, + QObject *parent = nullptr); + + QVariant value() const override { return m_get(); } + void setValue(const QVariant &value) override { m_set(value); } + +private: + std::function m_get; + std::function m_set; +}; + /** * A property that stores a value of a given type and uses an editor factory to * create its editor. @@ -210,6 +235,15 @@ class ValueTypeEditorFactory : public EditorFactory */ ValueProperty *createProperty(const QString &name, const QVariant &value); + /** + * Creates a property with the given name and get/set functions. The + * property will use the editor factory registered for the type of the + * value. + */ + AbstractProperty *createProperty(const QString &name, + std::function get, + std::function set); + QWidget *createEditor(Property *property, QWidget *parent) override; private: @@ -226,8 +260,6 @@ class EnumProperty : public AbstractProperty public: EnumProperty(const QString &name, - const QStringList &enumNames = {}, - const QList &enumValues = {}, QObject *parent = nullptr); void setEnumNames(const QStringList &enumNames); From bfc568882f96fdde527e29d3e6487abd8eb97c42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Mon, 2 Sep 2024 16:43:11 +0200 Subject: [PATCH 08/36] Implemented "Stagger Axis/Index" properties --- src/tiled/propertieswidget.cpp | 107 ++++++++++++++++++++++++++++++++- src/tiled/propertieswidget.h | 4 ++ src/tiled/varianteditor.cpp | 91 ++++------------------------ 3 files changed, 122 insertions(+), 80 deletions(-) diff --git a/src/tiled/propertieswidget.cpp b/src/tiled/propertieswidget.cpp index 9afabf0231..3515bfc81e 100644 --- a/src/tiled/propertieswidget.cpp +++ b/src/tiled/propertieswidget.cpp @@ -25,6 +25,7 @@ #include "changemapproperty.h" #include "changeproperties.h" #include "clipboardmanager.h" +#include "compression.h" #include "mapdocument.h" #include "propertybrowser.h" #include "utils.h" @@ -93,6 +94,7 @@ PropertiesWidget::PropertiesWidget(QWidget *parent) // connect(mPropertyBrowser, &PropertyBrowser::selectedItemsChanged, // this, &PropertiesWidget::updateActions); + registerEditorFactories(); retranslateUi(); } @@ -303,6 +305,30 @@ class MapProperties : public QObject mMapDocument->undoStack()->push(command); }); + mStaggerAxisProperty = editorFactory->createProperty( + tr("Stagger Axis"), + [this]() { + return QVariant::fromValue(mMapDocument->map()->staggerAxis()); + }, + [this](const QVariant &value) { + auto command = new ChangeMapProperty(mMapDocument, + Map::StaggerAxisProperty, + value.toInt()); + mMapDocument->undoStack()->push(command); + }); + + mStaggerIndexProperty = editorFactory->createProperty( + tr("Stagger Index"), + [this]() { + return QVariant::fromValue(mMapDocument->map()->staggerIndex()); + }, + [this](const QVariant &value) { + auto command = new ChangeMapProperty(mMapDocument, + Map::StaggerIndexProperty, + value.toInt()); + mMapDocument->undoStack()->push(command); + }); + connect(mMapDocument, &MapDocument::changed, this, &MapProperties::onMapChanged); } @@ -315,8 +341,8 @@ class MapProperties : public QObject editor->addProperty(mTileSizeProperty); editor->addProperty(mInfiniteProperty); editor->addProperty(mHexSideLengthProperty); - // editor->addProperty(mStaggerAxisProperty); - // editor->addProperty(mStaggerIndexProperty); + editor->addProperty(mStaggerAxisProperty); + editor->addProperty(mStaggerIndexProperty); // editor->addProperty(mParallaxOriginProperty); // editor->addProperty(mLayerDataFormatProperty); // editor->addProperty(mChunkSizeProperty); @@ -341,8 +367,14 @@ class MapProperties : public QObject emit mInfiniteProperty->valueChanged(); break; case Map::HexSideLengthProperty: + emit mHexSideLengthProperty->valueChanged(); + break; case Map::StaggerAxisProperty: + emit mStaggerAxisProperty->valueChanged(); + break; case Map::StaggerIndexProperty: + emit mStaggerIndexProperty->valueChanged(); + break; case Map::ParallaxOriginProperty: case Map::OrientationProperty: emit mOrientationProperty->valueChanged(); @@ -782,6 +814,77 @@ void PropertiesWidget::keyPressEvent(QKeyEvent *event) } } +void PropertiesWidget::registerEditorFactories() +{ + registerEditorFactory(qMetaTypeId(), + std::make_unique( + QStringList { + tr("Unspecified"), + tr("Top Left"), + tr("Top"), + tr("Top Right"), + tr("Left"), + tr("Center"), + tr("Right"), + tr("Bottom Left"), + tr("Bottom"), + tr("Bottom Right"), + })); + + + registerEditorFactory(qMetaTypeId(), + std::make_unique( + QStringList { + tr("X"), + tr("Y"), + })); + + registerEditorFactory(qMetaTypeId(), + std::make_unique( + QStringList { + tr("Odd"), + tr("Even"), + })); + + QStringList layerFormatNames = { + QCoreApplication::translate("PreferencesDialog", "XML (deprecated)"), + QCoreApplication::translate("PreferencesDialog", "Base64 (uncompressed)"), + QCoreApplication::translate("PreferencesDialog", "Base64 (gzip compressed)"), + QCoreApplication::translate("PreferencesDialog", "Base64 (zlib compressed)"), + }; + QList layerFormatValues = { + Map::XML, + Map::Base64, + Map::Base64Gzip, + Map::Base64Zlib, + }; + + if (compressionSupported(Zstandard)) { + layerFormatNames.append(QCoreApplication::translate("PreferencesDialog", "Base64 (Zstandard compressed)")); + layerFormatValues.append(Map::Base64Zstandard); + } + + layerFormatNames.append(QCoreApplication::translate("PreferencesDialog", "CSV")); + layerFormatValues.append(Map::CSV); + + registerEditorFactory(qMetaTypeId(), + std::make_unique(layerFormatNames, layerFormatValues)); + + registerEditorFactory(qMetaTypeId(), + std::make_unique( + QStringList { + tr("Right Down"), + tr("Right Up"), + tr("Left Down"), + tr("Left Up"), + })); +} + +void PropertiesWidget::registerEditorFactory(int type, std::unique_ptr factory) +{ + mDefaultEditorFactory->registerEditorFactory(type, std::move(factory)); +} + void PropertiesWidget::retranslateUi() { mActionAddProperty->setText(QCoreApplication::translate("Tiled::PropertiesDock", "Add Property")); diff --git a/src/tiled/propertieswidget.h b/src/tiled/propertieswidget.h index 55447f7247..37db77ee6d 100644 --- a/src/tiled/propertieswidget.h +++ b/src/tiled/propertieswidget.h @@ -29,6 +29,7 @@ namespace Tiled { class Object; class Document; +class EditorFactory; class ValueTypeEditorFactory; class VariantEditor; @@ -61,6 +62,9 @@ public slots: void keyPressEvent(QKeyEvent *event) override; private: + void registerEditorFactories(); + void registerEditorFactory(int type, std::unique_ptr factory); + void currentObjectChanged(Object *object); void updateActions(); diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index 523ad504c4..081f3f8f95 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -49,7 +49,8 @@ AbstractProperty::AbstractProperty(const QString &name, QWidget *AbstractProperty::createEditor(QWidget *parent) { - return m_editorFactory->createEditor(this, parent); + return m_editorFactory ? m_editorFactory->createEditor(this, parent) + : nullptr; } @@ -316,7 +317,7 @@ VariantEditor::VariantEditor(QWidget *parent) m_gridLayout = new QGridLayout; verticalLayout->addLayout(m_gridLayout); verticalLayout->addStretch(); - verticalLayout->setContentsMargins(QMargins()); + verticalLayout->setContentsMargins(0, 0, 0, Utils::dpiScaled(6)); setWidget(m_widget); setWidgetResizable(true); @@ -330,71 +331,6 @@ VariantEditor::VariantEditor(QWidget *parent) m_gridLayout->setColumnMinimumWidth(MiddleSpacing, Utils::dpiScaled(2)); m_gridLayout->setColumnMinimumWidth(RightSpacing, Utils::dpiScaled(3)); - // auto alignmentEditorFactory = std::make_unique(); - // alignmentEditorFactory->setEnumNames({ - // tr("Unspecified"), - // tr("Top Left"), - // tr("Top"), - // tr("Top Right"), - // tr("Left"), - // tr("Center"), - // tr("Right"), - // tr("Bottom Left"), - // tr("Bottom"), - // tr("Bottom Right"), - // }); - // registerEditorFactory(qMetaTypeId(), std::move(alignmentEditorFactory)); - - - // auto staggerAxisEditorFactory = std::make_unique(); - // staggerAxisEditorFactory->setEnumNames({ - // tr("X"), - // tr("Y"), - // }); - // registerEditorFactory(qMetaTypeId(), std::move(staggerAxisEditorFactory)); - - // auto staggerIndexEditorFactory = std::make_unique(); - // staggerIndexEditorFactory->setEnumNames({ - // tr("Odd"), - // tr("Even"), - // }); - // registerEditorFactory(qMetaTypeId(), std::move(staggerIndexEditorFactory)); - - QStringList layerFormatNames = { - QCoreApplication::translate("PreferencesDialog", "XML (deprecated)"), - QCoreApplication::translate("PreferencesDialog", "Base64 (uncompressed)"), - QCoreApplication::translate("PreferencesDialog", "Base64 (gzip compressed)"), - QCoreApplication::translate("PreferencesDialog", "Base64 (zlib compressed)"), - }; - QList layerFormatValues = { - Map::XML, - Map::Base64, - Map::Base64Gzip, - Map::Base64Zlib, - }; - - if (compressionSupported(Zstandard)) { - layerFormatNames.append(QCoreApplication::translate("PreferencesDialog", "Base64 (Zstandard compressed)")); - layerFormatValues.append(Map::Base64Zstandard); - } - - layerFormatNames.append(QCoreApplication::translate("PreferencesDialog", "CSV")); - layerFormatValues.append(Map::CSV); - - // auto layerFormatEditorFactory = std::make_unique(); - // layerFormatEditorFactory->setEnumNames(layerFormatNames); - // layerFormatEditorFactory->setEnumValues(layerFormatValues); - // registerEditorFactory(qMetaTypeId(), std::move(layerFormatEditorFactory)); - - // auto renderOrderEditorFactory = std::make_unique(); - // renderOrderEditorFactory->setEnumNames({ - // tr("Right Down"), - // tr("Right Up"), - // tr("Left Down"), - // tr("Left Up"), - // }); - // registerEditorFactory(qMetaTypeId(), std::move(renderOrderEditorFactory)); - // setValue(QVariantMap { // { QStringLiteral("Name"), QVariant(QLatin1String("Hello")) }, // { QStringLiteral("Position"), QVariant(QPoint(15, 50)) }, @@ -607,24 +543,23 @@ QObjectProperty *ValueTypeEditorFactory::createQObjectProperty(QObject *qObject, this); } -ValueProperty *ValueTypeEditorFactory::createProperty(const QString &name, const QVariant &value) +ValueProperty *ValueTypeEditorFactory::createProperty(const QString &name, + const QVariant &value) { - const int type = value.userType(); - auto factory = m_factories.find(type); - if (factory != m_factories.end()) - return new ValueProperty(name, value, factory->second.get()); - return nullptr; + auto f = m_factories.find(value.userType()); + return new ValueProperty(name, value, + f != m_factories.end() ? f->second.get() + : nullptr); } AbstractProperty *ValueTypeEditorFactory::createProperty(const QString &name, std::function get, std::function set) { - const int type = get().userType(); - auto factory = m_factories.find(type); - if (factory != m_factories.end()) - return new GetSetProperty(name, get, set, factory->second.get()); - return nullptr; + auto f = m_factories.find(get().userType()); + return new GetSetProperty(name, get, set, + f != m_factories.end() ? f->second.get() + : nullptr); } QWidget *ValueTypeEditorFactory::createEditor(Property *property, QWidget *parent) From f13fd5c6cf1f2faf2d44d6856e6a9cc9bd05b477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Tue, 3 Sep 2024 17:57:17 +0200 Subject: [PATCH 09/36] Implemented remaining Map properties * Introduced a few helper functions to reduce code duplication, like MapProperties::push. * Disabled properties when they are irrelevant. * Finished connecting the signals for the remaining editor factories: StringEditorFactory, IntEditorFactory, FloatEditorFactory, PointEditorFactory, PointFEditorFactory, RectFEditorFactory and ColorEditorFactory. --- src/tiled/propertieswidget.cpp | 236 +++++++++++++++++++++++---------- src/tiled/varianteditor.cpp | 155 +++++++++++++++------- 2 files changed, 274 insertions(+), 117 deletions(-) diff --git a/src/tiled/propertieswidget.cpp b/src/tiled/propertieswidget.cpp index 3515bfc81e..21e1ff3f46 100644 --- a/src/tiled/propertieswidget.cpp +++ b/src/tiled/propertieswidget.cpp @@ -146,45 +146,6 @@ static bool anyObjectHasProperty(const QList &objects, const QString &n return false; } -class MapOrientationProperty : public EnumProperty -{ - Q_OBJECT - -public: - MapOrientationProperty(MapDocument *mapDocument, QObject *parent = nullptr) - : EnumProperty(tr("Orientation"), parent) - , mMapDocument(mapDocument) - { - setEnumNames({ - tr("Orthogonal"), - tr("Isometric"), - tr("Isometric (Staggered)"), - tr("Hexagonal (Staggered)"), - }); - setEnumValues({ - Map::Orthogonal, - Map::Isometric, - Map::Staggered, - Map::Hexagonal, - }); - } - - QVariant value() const override - { - return mMapDocument->map()->orientation(); - } - - void setValue(const QVariant &value) override - { - Map::Orientation orientation = static_cast(value.toInt()); - auto command = new ChangeMapProperty(mMapDocument, orientation); - mMapDocument->undoStack()->push(command); - } - -private: - MapDocument *mMapDocument; -}; - class MapSizeProperty : public AbstractProperty { Q_OBJECT @@ -277,58 +238,128 @@ class MapProperties : public QObject QObject *parent = nullptr) : QObject(parent) , mMapDocument(mapDocument) - , mOrientationProperty(new MapOrientationProperty(mapDocument, this)) , mSizeProperty(new MapSizeProperty(mapDocument, editorFactory, this)) , mTileSizeProperty(new TileSizeProperty(mapDocument, editorFactory, this)) { + mClassProperty = editorFactory->createProperty( + tr("Class"), + [this]() { + return map()->className(); + }, + [this](const QVariant &value) { + push(new ChangeClassName(mMapDocument, { map() }, + value.toString())); + }); + + mOrientationProperty = editorFactory->createProperty( + tr("Orientation"), + [this]() { + return QVariant::fromValue(map()->orientation()); + }, + [this](const QVariant &value) { + auto orientation = static_cast(value.toInt()); + push(new ChangeMapProperty(mMapDocument, orientation)); + }); + mInfiniteProperty = editorFactory->createProperty( tr("Infinite"), [this]() { - return mMapDocument->map()->infinite(); + return map()->infinite(); }, [this](const QVariant &value) { - auto command = new ChangeMapProperty(mMapDocument, - Map::InfiniteProperty, - value.toBool()); - mMapDocument->undoStack()->push(command); + push(new ChangeMapProperty(mMapDocument, + Map::InfiniteProperty, + value.toInt())); }); mHexSideLengthProperty = editorFactory->createProperty( tr("Hex Side Length"), [this]() { - return mMapDocument->map()->hexSideLength(); + return map()->hexSideLength(); }, [this](const QVariant &value) { - auto command = new ChangeMapProperty(mMapDocument, - Map::HexSideLengthProperty, - value.toInt()); - mMapDocument->undoStack()->push(command); + push(new ChangeMapProperty(mMapDocument, + Map::HexSideLengthProperty, + value.toInt())); }); mStaggerAxisProperty = editorFactory->createProperty( tr("Stagger Axis"), [this]() { - return QVariant::fromValue(mMapDocument->map()->staggerAxis()); + return QVariant::fromValue(map()->staggerAxis()); }, [this](const QVariant &value) { - auto command = new ChangeMapProperty(mMapDocument, - Map::StaggerAxisProperty, - value.toInt()); - mMapDocument->undoStack()->push(command); + auto staggerAxis = static_cast(value.toInt()); + push(new ChangeMapProperty(mMapDocument, staggerAxis)); }); mStaggerIndexProperty = editorFactory->createProperty( tr("Stagger Index"), [this]() { - return QVariant::fromValue(mMapDocument->map()->staggerIndex()); + return QVariant::fromValue(map()->staggerIndex()); }, [this](const QVariant &value) { - auto command = new ChangeMapProperty(mMapDocument, - Map::StaggerIndexProperty, - value.toInt()); - mMapDocument->undoStack()->push(command); + auto staggerIndex = static_cast(value.toInt()); + push(new ChangeMapProperty(mMapDocument, staggerIndex)); }); + mParallaxOriginProperty = editorFactory->createProperty( + tr("Parallax Origin"), + [this]() { + return map()->parallaxOrigin(); + }, + [this](const QVariant &value) { + push(new ChangeMapProperty(mMapDocument, value.value())); + }); + + mLayerDataFormatProperty = editorFactory->createProperty( + tr("Layer Data Format"), + [this]() { + return QVariant::fromValue(map()->layerDataFormat()); + }, + [this](const QVariant &value) { + auto layerDataFormat = static_cast(value.toInt()); + push(new ChangeMapProperty(mMapDocument, layerDataFormat)); + }); + + mChunkSizeProperty = editorFactory->createProperty( + tr("Output Chunk Size"), + [this]() { + return map()->chunkSize(); + }, + [this](const QVariant &value) { + push(new ChangeMapProperty(mMapDocument, value.toSize())); + }); + + mRenderOrderProperty = editorFactory->createProperty( + tr("Tile Render Order"), + [this]() { + return QVariant::fromValue(map()->renderOrder()); + }, + [this](const QVariant &value) { + auto renderOrder = static_cast(value.toInt()); + push(new ChangeMapProperty(mMapDocument, renderOrder)); + }); + + mCompressionLevelProperty = editorFactory->createProperty( + tr("Compression Level"), + [this]() { + return map()->compressionLevel(); + }, + [this](const QVariant &value) { + push(new ChangeMapProperty(mMapDocument, value.toInt())); + }); + + mBackgroundColorProperty = editorFactory->createProperty( + tr("Background Color"), + [this]() { + return map()->backgroundColor(); + }, + [this](const QVariant &value) { + push(new ChangeMapProperty(mMapDocument, value.value())); + }); + + updateEnabledState(); connect(mMapDocument, &MapDocument::changed, this, &MapProperties::onMapChanged); } @@ -336,19 +367,24 @@ class MapProperties : public QObject void populateEditor(VariantEditor *editor) { editor->addHeader(tr("Map")); + editor->addProperty(mClassProperty); + editor->addSeparator(); editor->addProperty(mOrientationProperty); editor->addProperty(mSizeProperty); - editor->addProperty(mTileSizeProperty); editor->addProperty(mInfiniteProperty); + editor->addProperty(mTileSizeProperty); editor->addProperty(mHexSideLengthProperty); editor->addProperty(mStaggerAxisProperty); editor->addProperty(mStaggerIndexProperty); - // editor->addProperty(mParallaxOriginProperty); - // editor->addProperty(mLayerDataFormatProperty); - // editor->addProperty(mChunkSizeProperty); - // editor->addProperty(mTileRenderOrderProperty); - // editor->addProperty(mCompressionLevelProperty); - // editor->addProperty(mBackgroundColorProperty); + editor->addSeparator(); + editor->addProperty(mParallaxOriginProperty); + editor->addSeparator(); + editor->addProperty(mLayerDataFormatProperty); + editor->addProperty(mChunkSizeProperty); + editor->addProperty(mCompressionLevelProperty); + editor->addSeparator(); + editor->addProperty(mRenderOrderProperty); + editor->addProperty(mBackgroundColorProperty); } private: @@ -376,19 +412,68 @@ class MapProperties : public QObject emit mStaggerIndexProperty->valueChanged(); break; case Map::ParallaxOriginProperty: + emit mParallaxOriginProperty->valueChanged(); + break; case Map::OrientationProperty: emit mOrientationProperty->valueChanged(); break; case Map::RenderOrderProperty: + emit mRenderOrderProperty->valueChanged(); + break; case Map::BackgroundColorProperty: + emit mBackgroundColorProperty->valueChanged(); + break; case Map::LayerDataFormatProperty: + emit mLayerDataFormatProperty->valueChanged(); + break; case Map::CompressionLevelProperty: + emit mCompressionLevelProperty->valueChanged(); + break; case Map::ChunkSizeProperty: + emit mChunkSizeProperty->valueChanged(); + break; + } + + updateEnabledState(); + } + + void updateEnabledState() + { + const auto orientation = map()->orientation(); + const bool stagger = orientation == Map::Staggered || orientation == Map::Hexagonal; + + mHexSideLengthProperty->setEnabled(orientation == Map::Hexagonal); + mStaggerAxisProperty->setEnabled(stagger); + mStaggerIndexProperty->setEnabled(stagger); + mRenderOrderProperty->setEnabled(orientation == Map::Orthogonal); + mChunkSizeProperty->setEnabled(map()->infinite()); + + switch (map()->layerDataFormat()) { + case Map::XML: + case Map::Base64: + case Map::CSV: + mCompressionLevelProperty->setEnabled(false); + break; + case Map::Base64Gzip: + case Map::Base64Zlib: + case Map::Base64Zstandard: + mCompressionLevelProperty->setEnabled(true); break; } } + void push(QUndoCommand *command) + { + mMapDocument->undoStack()->push(command); + } + + Map *map() const + { + return mMapDocument->map(); + } + MapDocument *mMapDocument; + Property *mClassProperty; Property *mOrientationProperty; Property *mSizeProperty; Property *mTileSizeProperty; @@ -399,7 +484,7 @@ class MapProperties : public QObject Property *mParallaxOriginProperty; Property *mLayerDataFormatProperty; Property *mChunkSizeProperty; - Property *mTileRenderOrderProperty; + Property *mRenderOrderProperty; Property *mCompressionLevelProperty; Property *mBackgroundColorProperty; }; @@ -831,6 +916,21 @@ void PropertiesWidget::registerEditorFactories() tr("Bottom Right"), })); + // We leave out the "Unknown" orientation, because it shouldn't occur here + registerEditorFactory(qMetaTypeId(), + std::make_unique( + QStringList { + tr("Orthogonal"), + tr("Isometric"), + tr("Isometric (Staggered)"), + tr("Hexagonal (Staggered)"), + }, + QList { + Map::Orthogonal, + Map::Isometric, + Map::Staggered, + Map::Hexagonal, + })); registerEditorFactory(qMetaTypeId(), std::make_unique( diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index 081f3f8f95..f1eebbd365 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -21,8 +21,6 @@ #include "varianteditor.h" #include "colorbutton.h" -#include "compression.h" -#include "map.h" #include "utils.h" #include "propertyeditorwidgets.h" @@ -71,9 +69,15 @@ class StringEditorFactory : public EditorFactory public: QWidget *createEditor(Property *property, QWidget *parent) override { - auto value = property->value(); auto editor = new QLineEdit(parent); - editor->setText(value.toString()); + auto syncEditor = [=] { + editor->setText(property->value().toString()); + }; + syncEditor(); + + QObject::connect(property, &Property::valueChanged, editor, syncEditor); + QObject::connect(editor, &QLineEdit::textEdited, property, &Property::setValue); + return editor; } }; @@ -83,9 +87,17 @@ class IntEditorFactory : public EditorFactory public: QWidget *createEditor(Property *property, QWidget *parent) override { - auto value = property->value(); auto editor = new SpinBox(parent); - editor->setValue(value.toInt()); + auto syncEditor = [=] { + const QSignalBlocker blocker(editor); + editor->setValue(property->value().toInt()); + }; + syncEditor(); + + QObject::connect(property, &Property::valueChanged, editor, syncEditor); + QObject::connect(editor, qOverload(&SpinBox::valueChanged), + property, &Property::setValue); + return editor; } }; @@ -95,9 +107,17 @@ class FloatEditorFactory : public EditorFactory public: QWidget *createEditor(Property *property, QWidget *parent) override { - auto value = property->value(); auto editor = new DoubleSpinBox(parent); - editor->setValue(value.toDouble()); + auto syncEditor = [=] { + const QSignalBlocker blocker(editor); + editor->setValue(property->value().toDouble()); + }; + syncEditor(); + + QObject::connect(property, &Property::valueChanged, editor, syncEditor); + QObject::connect(editor, qOverload(&DoubleSpinBox::valueChanged), + property, &Property::setValue); + return editor; } }; @@ -108,7 +128,7 @@ class BoolEditorFactory : public EditorFactory QWidget *createEditor(Property *property, QWidget *parent) override { auto editor = new QCheckBox(parent); - auto syncEditor = [=]() { + auto syncEditor = [=] { const QSignalBlocker blocker(editor); bool checked = property->value().toBool(); editor->setChecked(checked); @@ -131,7 +151,6 @@ class PointEditorFactory : public EditorFactory public: QWidget *createEditor(Property *property, QWidget *parent) override { - auto value = property->value(); auto editor = new QWidget(parent); auto horizontalLayout = new QHBoxLayout(editor); horizontalLayout->setContentsMargins(QMargins()); @@ -150,8 +169,23 @@ class PointEditorFactory : public EditorFactory yLabel->setBuddy(ySpinBox); horizontalLayout->addWidget(ySpinBox, 1); - xSpinBox->setValue(value.toPoint().x()); - ySpinBox->setValue(value.toPoint().y()); + auto syncEditor = [=] { + const QSignalBlocker xBlocker(xSpinBox); + const QSignalBlocker yBlocker(ySpinBox); + const auto point = property->value().toPoint(); + xSpinBox->setValue(point.x()); + ySpinBox->setValue(point.y()); + }; + auto syncProperty = [=] { + property->setValue(QPoint(xSpinBox->value(), ySpinBox->value())); + }; + syncEditor(); + + QObject::connect(property, &Property::valueChanged, editor, syncEditor); + QObject::connect(xSpinBox, qOverload(&SpinBox::valueChanged), + property, syncProperty); + QObject::connect(ySpinBox, qOverload(&SpinBox::valueChanged), + property, syncProperty); return editor; } @@ -162,7 +196,6 @@ class PointFEditorFactory : public EditorFactory public: QWidget *createEditor(Property *property, QWidget *parent) override { - auto value = property->value(); auto editor = new QWidget(parent); auto horizontalLayout = new QHBoxLayout(editor); horizontalLayout->setContentsMargins(QMargins()); @@ -181,8 +214,23 @@ class PointFEditorFactory : public EditorFactory yLabel->setBuddy(ySpinBox); horizontalLayout->addWidget(ySpinBox, 1); - xSpinBox->setValue(value.toPointF().x()); - ySpinBox->setValue(value.toPointF().y()); + auto syncEditor = [=] { + const QSignalBlocker xBlocker(xSpinBox); + const QSignalBlocker yBlocker(ySpinBox); + const auto point = property->value().toPointF(); + xSpinBox->setValue(point.x()); + ySpinBox->setValue(point.y()); + }; + auto syncProperty = [=] { + property->setValue(QPointF(xSpinBox->value(), ySpinBox->value())); + }; + syncEditor(); + + QObject::connect(property, &Property::valueChanged, editor, syncEditor); + QObject::connect(xSpinBox, qOverload(&DoubleSpinBox::valueChanged), + property, syncProperty); + QObject::connect(ySpinBox, qOverload(&DoubleSpinBox::valueChanged), + property, syncProperty); return editor; } @@ -195,7 +243,7 @@ class SizeEditorFactory : public EditorFactory QWidget *createEditor(Property *property, QWidget *parent) override { auto editor = new SizeEdit(parent); - auto syncEditor = [property, editor]() { + auto syncEditor = [property, editor] { const QSignalBlocker blocker(editor); editor->setValue(property->value().toSize()); }; @@ -203,7 +251,7 @@ class SizeEditorFactory : public EditorFactory QObject::connect(property, &Property::valueChanged, editor, syncEditor); QObject::connect(editor, &SizeEdit::valueChanged, property, - [property, editor]() { + [property, editor] { property->setValue(editor->value()); }); @@ -216,7 +264,6 @@ class RectFEditorFactory : public EditorFactory public: QWidget *createEditor(Property *property, QWidget *parent) override { - auto value = property->value(); auto editor = new QWidget(parent); auto gridLayout = new QGridLayout(editor); gridLayout->setContentsMargins(QMargins()); @@ -252,11 +299,34 @@ class RectFEditorFactory : public EditorFactory heightLabel->setBuddy(heightSpinBox); gridLayout->addWidget(heightSpinBox, 1, 3); - const auto rect = value.toRectF(); - xSpinBox->setValue(rect.x()); - ySpinBox->setValue(rect.y()); - widthSpinBox->setValue(rect.width()); - heightSpinBox->setValue(rect.height()); + auto syncEditor = [=] { + const QSignalBlocker xBlocker(xSpinBox); + const QSignalBlocker yBlocker(ySpinBox); + const QSignalBlocker widthBlocker(widthSpinBox); + const QSignalBlocker heightBlocker(heightSpinBox); + const auto rect = property->value().toRectF(); + xSpinBox->setValue(rect.x()); + ySpinBox->setValue(rect.y()); + widthSpinBox->setValue(rect.width()); + heightSpinBox->setValue(rect.height()); + }; + auto syncProperty = [=] { + property->setValue(QRectF(xSpinBox->value(), + ySpinBox->value(), + widthSpinBox->value(), + heightSpinBox->value())); + }; + syncEditor(); + + QObject::connect(property, &Property::valueChanged, editor, syncEditor); + QObject::connect(xSpinBox, qOverload(&DoubleSpinBox::valueChanged), + property, syncProperty); + QObject::connect(ySpinBox, qOverload(&DoubleSpinBox::valueChanged), + property, syncProperty); + QObject::connect(widthSpinBox, qOverload(&DoubleSpinBox::valueChanged), + property, syncProperty); + QObject::connect(heightSpinBox, qOverload(&DoubleSpinBox::valueChanged), + property, syncProperty); return editor; } @@ -267,9 +337,19 @@ class ColorEditorFactory : public EditorFactory public: QWidget *createEditor(Property *property, QWidget *parent) override { - auto value = property->value(); auto editor = new ColorButton(parent); - editor->setColor(value.value()); + auto syncEditor = [=] { + const QSignalBlocker blocker(editor); + editor->setColor(property->value().value()); + }; + syncEditor(); + + QObject::connect(property, &Property::valueChanged, editor, syncEditor); + QObject::connect(editor, &ColorButton::colorChanged, property, + [property, editor] { + property->setValue(editor->color()); + }); + return editor; } }; @@ -348,29 +428,6 @@ VariantEditor::VariantEditor(QWidget *parent) // QVariant(10), // QVariant(3.14) // }); - - // addHeader(tr("Map")); - // addProperty(new VariantProperty(tr("Class"), QString())); - // addProperty(new VariantProperty(tr("Orientation"), QVariant::fromValue(Map::Hexagonal))); - // addValue(tr("Class"), QString()); - // addSeparator(); - // addValue(tr("Orientation"), QVariant::fromValue(Map::Hexagonal)); - // addValue(tr("Infinite"), false); - // addValue(tr("Map Size"), QSize(20, 20)); - // addValue(tr("Tile Size"), QSize(14, 12)); - // addValue(tr("Tile Side Length (Hex)"), 6); - // addValue(tr("Stagger Axis"), QVariant::fromValue(Map::StaggerY)); - // addValue(tr("Stagger Index"), QVariant::fromValue(Map::StaggerEven)); - // addSeparator(); - // addValue(tr("Parallax Origin"), QPointF()); - // addSeparator(); - // addValue(tr("Tile Layer Format"), QVariant::fromValue(Map::Base64Zlib)); - // addValue(tr("Output Chunk Size"), QSize(16, 16)); - // addValue(tr("Compression Level"), -1); - // addSeparator(); - // addValue(tr("Tile Render Order"), QVariant::fromValue(Map::RightDown)); - // addValue(tr("Background Color"), QColor()); - // addHeader(tr("Custom Properties")); } void VariantEditor::clear() @@ -489,7 +546,7 @@ QWidget *EnumEditorFactory::createEditor(Property *property, QWidget *parent) editor->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); editor->setModel(&m_enumNamesModel); - auto syncEditor = [property, editor, this]() { + auto syncEditor = [property, editor, this] { const QSignalBlocker blocker(editor); if (m_enumValues.isEmpty()) editor->setCurrentIndex(property->value().toInt()); From aa9fd8cdbf93a146c4e7e3887a7a373e2ad97708 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Wed, 4 Sep 2024 08:37:01 +0200 Subject: [PATCH 10/36] Fixed compile against Qt 5 Not sure why Qt 6 didn't need these missing metatype declarations. --- src/libtiled/map.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libtiled/map.h b/src/libtiled/map.h index 0abee3233e..b7e58117fd 100644 --- a/src/libtiled/map.h +++ b/src/libtiled/map.h @@ -791,3 +791,5 @@ Q_DECLARE_METATYPE(Tiled::Map*) Q_DECLARE_METATYPE(Tiled::Map::Orientation) Q_DECLARE_METATYPE(Tiled::Map::LayerDataFormat) Q_DECLARE_METATYPE(Tiled::Map::RenderOrder) +Q_DECLARE_METATYPE(Tiled::Map::StaggerAxis) +Q_DECLARE_METATYPE(Tiled::Map::StaggerIndex) From 993ca5b7a54706cdd2fcfb95901ba22bd3ff83e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Wed, 4 Sep 2024 10:47:39 +0200 Subject: [PATCH 11/36] Implemented custom widget for Class property This property uses an editable combo box with the valid classes set up for the given object type. --- src/tiled/changeproperties.cpp | 2 +- src/tiled/propertieswidget.cpp | 83 ++++++++++++++++++++++++++++++---- 2 files changed, 75 insertions(+), 10 deletions(-) diff --git a/src/tiled/changeproperties.cpp b/src/tiled/changeproperties.cpp index 613c3022ec..9b36a3a479 100644 --- a/src/tiled/changeproperties.cpp +++ b/src/tiled/changeproperties.cpp @@ -34,7 +34,7 @@ ChangeClassName::ChangeClassName(Document *document, QUndoCommand *parent) : ChangeValue(document, objects, className, parent) { - setText(QCoreApplication::translate("Undo Commands", "Change Type")); + setText(QCoreApplication::translate("Undo Commands", "Change Class")); } void ChangeClassName::undo() diff --git a/src/tiled/propertieswidget.cpp b/src/tiled/propertieswidget.cpp index 21e1ff3f46..5dd9d18db4 100644 --- a/src/tiled/propertieswidget.cpp +++ b/src/tiled/propertieswidget.cpp @@ -27,11 +27,13 @@ #include "clipboardmanager.h" #include "compression.h" #include "mapdocument.h" +#include "preferences.h" #include "propertybrowser.h" #include "utils.h" #include "varianteditor.h" #include +#include #include #include #include @@ -146,6 +148,77 @@ static bool anyObjectHasProperty(const QList &objects, const QString &n return false; } +static QStringList classNamesFor(const Object &object) +{ + QStringList names; + for (const auto type : Object::propertyTypes()) + if (type->isClass()) + if (static_cast(type)->isClassFor(object)) + names.append(type->name); + return names; +} + +class ClassProperty : public Property +{ + Q_OBJECT + +public: + ClassProperty(Document *document, Object *object, QObject *parent = nullptr) + : Property(tr("Class"), parent) + , mDocument(document) + , mObject(object) + { + connect(mDocument, &Document::changed, + this, &ClassProperty::onChanged); + } + + QVariant value() const override { return mObject->className(); } + void setValue(const QVariant &value) override + { + QUndoStack *undoStack = mDocument->undoStack(); + undoStack->push(new ChangeClassName(mDocument, + { mObject }, // todo: add support for changing multiple objects + value.toString())); + } + + QWidget *createEditor(QWidget *parent) override + { + auto editor = new QComboBox(parent); + editor->setEditable(true); + editor->addItems(classNamesFor(*mObject)); + auto syncEditor = [this, editor] { + const QSignalBlocker blocker(editor); + editor->setCurrentText(value().toString()); + }; + syncEditor(); + connect(this, &Property::valueChanged, editor, syncEditor); + connect(editor, &QComboBox::currentTextChanged, this, &Property::setValue); + connect(Preferences::instance(), &Preferences::propertyTypesChanged, + editor, [this,editor] { + editor->clear(); + editor->addItems(classNamesFor(*mObject)); + }); + return editor; + } + +private: + void onChanged(const ChangeEvent &event) + { + if (event.type != ChangeEvent::ObjectsChanged) + return; + + const auto objectsEvent = static_cast(event); + if (!objectsEvent.objects.contains(mObject)) + return; + + if (objectsEvent.properties & ObjectsChangeEvent::ClassProperty) + emit valueChanged(); + } + + Document *mDocument; + Object *mObject; +}; + class MapSizeProperty : public AbstractProperty { Q_OBJECT @@ -241,15 +314,7 @@ class MapProperties : public QObject , mSizeProperty(new MapSizeProperty(mapDocument, editorFactory, this)) , mTileSizeProperty(new TileSizeProperty(mapDocument, editorFactory, this)) { - mClassProperty = editorFactory->createProperty( - tr("Class"), - [this]() { - return map()->className(); - }, - [this](const QVariant &value) { - push(new ChangeClassName(mMapDocument, { map() }, - value.toString())); - }); + mClassProperty = new ClassProperty(mMapDocument, mMapDocument->map()); mOrientationProperty = editorFactory->createProperty( tr("Orientation"), From 7be4d4c3d03f80de06d818257c876747f7e664b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Wed, 4 Sep 2024 12:24:44 +0200 Subject: [PATCH 12/36] Made most Tileset properties functional Still needs special handling for: * Color (requires handling invalid/unset values) * Widget for editing allowed transformations * Widget for showing tileset parameters (and trigger edit dialog) * Specifying 1 as minimum value for column count --- src/tiled/propertieswidget.cpp | 250 ++++++++++++++++++++++++++++++++- src/tiled/varianteditor.cpp | 1 + 2 files changed, 246 insertions(+), 5 deletions(-) diff --git a/src/tiled/propertieswidget.cpp b/src/tiled/propertieswidget.cpp index 5dd9d18db4..1dd8bdfa45 100644 --- a/src/tiled/propertieswidget.cpp +++ b/src/tiled/propertieswidget.cpp @@ -29,6 +29,8 @@ #include "mapdocument.h" #include "preferences.h" #include "propertybrowser.h" +#include "tilesetchanges.h" +#include "tilesetdocument.h" #include "utils.h" #include "varianteditor.h" @@ -425,8 +427,8 @@ class MapProperties : public QObject }); updateEnabledState(); - connect(mMapDocument, &MapDocument::changed, - this, &MapProperties::onMapChanged); + connect(mMapDocument, &Document::changed, + this, &MapProperties::onChanged); } void populateEditor(VariantEditor *editor) @@ -453,7 +455,7 @@ class MapProperties : public QObject } private: - void onMapChanged(const ChangeEvent &event) + void onChanged(const ChangeEvent &event) { if (event.type != ChangeEvent::MapChanged) return; @@ -554,10 +556,221 @@ class MapProperties : public QObject Property *mBackgroundColorProperty; }; +class TilesetProperties : public QObject +{ + Q_OBJECT + +public: + TilesetProperties(TilesetDocument *tilesetDocument, + ValueTypeEditorFactory *editorFactory, + QObject *parent = nullptr) + : QObject(parent) + , mTilesetDocument(tilesetDocument) + { + mNameProperty = editorFactory->createProperty( + tr("Name"), + [this]() { + return mTilesetDocument->tileset()->name(); + }, + [this](const QVariant &value) { + push(new RenameTileset(mTilesetDocument, value.toString())); + }); + + mClassProperty = new ClassProperty(tilesetDocument, tilesetDocument->tileset().data()); + + mObjectAlignmentProperty = editorFactory->createProperty( + tr("Object Alignment"), + [this]() { + return QVariant::fromValue(tileset()->objectAlignment()); + }, + [this](const QVariant &value) { + const auto objectAlignment = static_cast(value.toInt()); + push(new ChangeTilesetObjectAlignment(mTilesetDocument, objectAlignment)); + }); + + mTileOffsetProperty = editorFactory->createProperty( + tr("Drawing Offset"), + [this]() { + return tileset()->tileOffset(); + }, + [this](const QVariant &value) { + push(new ChangeTilesetTileOffset(mTilesetDocument, value.value())); + }); + + mTileRenderSizeProperty = editorFactory->createProperty( + tr("Tile Render Size"), + [this]() { + return QVariant::fromValue(tileset()->tileRenderSize()); + }, + [this](const QVariant &value) { + const auto tileRenderSize = static_cast(value.toInt()); + push(new ChangeTilesetTileRenderSize(mTilesetDocument, tileRenderSize)); + }); + + mFillModeProperty = editorFactory->createProperty( + tr("Fill Mode"), + [this]() { + return QVariant::fromValue(tileset()->fillMode()); + }, + [this](const QVariant &value) { + const auto fillMode = static_cast(value.toInt()); + push(new ChangeTilesetFillMode(mTilesetDocument, fillMode)); + }); + + mBackgroundColorProperty = editorFactory->createProperty( + tr("Background Color"), + [this]() { + return tileset()->backgroundColor(); + }, + [this](const QVariant &value) { + push(new ChangeTilesetBackgroundColor(mTilesetDocument, value.value())); + }); + + mOrientationProperty = editorFactory->createProperty( + tr("Orientation"), + [this]() { + return QVariant::fromValue(tileset()->orientation()); + }, + [this](const QVariant &value) { + const auto orientation = static_cast(value.toInt()); + push(new ChangeTilesetOrientation(mTilesetDocument, orientation)); + }); + + mGridSizeProperty = editorFactory->createProperty( + tr("Grid Size"), + [this]() { + return tileset()->gridSize(); + }, + [this](const QVariant &value) { + push(new ChangeTilesetGridSize(mTilesetDocument, value.toSize())); + }); + + // todo: needs 1 as minimum value + mColumnCountProperty = editorFactory->createProperty( + tr("Columns"), + [this]() { + return tileset()->columnCount(); + }, + [this](const QVariant &value) { + push(new ChangeTilesetColumnCount(mTilesetDocument, value.toInt())); + }); + + // todo: this needs a custom widget + mAllowedTransformationsProperty = editorFactory->createProperty( + tr("Allowed Transformations"), + [this]() { + return QVariant::fromValue(tileset()->transformationFlags()); + }, + [this](const QVariant &value) { + const auto flags = static_cast(value.toInt()); + push(new ChangeTilesetTransformationFlags(mTilesetDocument, flags)); + }); + + // todo: this needs a custom widget + mImageProperty = editorFactory->createProperty( + tr("Image"), + [this]() { + return tileset()->imageSource().toString(); + }, + [](const QVariant &) { + // push(new ChangeTilesetImage(mTilesetDocument, value.toString())); + }); + + updateEnabledState(); + connect(mTilesetDocument, &Document::changed, + this, &TilesetProperties::onChanged); + + connect(mTilesetDocument, &TilesetDocument::tilesetNameChanged, + mNameProperty, &Property::valueChanged); + connect(mTilesetDocument, &TilesetDocument::tilesetTileOffsetChanged, + mTileOffsetProperty, &Property::valueChanged); + connect(mTilesetDocument, &TilesetDocument::tilesetObjectAlignmentChanged, + mObjectAlignmentProperty, &Property::valueChanged); + connect(mTilesetDocument, &TilesetDocument::tilesetChanged, + this, &TilesetProperties::onTilesetChanged); + } + + void populateEditor(VariantEditor *editor) + { + editor->addHeader(tr("Tileset")); + editor->addProperty(mNameProperty); + editor->addProperty(mClassProperty); + editor->addSeparator(); + editor->addProperty(mObjectAlignmentProperty); + editor->addProperty(mTileOffsetProperty); + editor->addProperty(mTileRenderSizeProperty); + editor->addProperty(mFillModeProperty); + editor->addProperty(mBackgroundColorProperty); + editor->addProperty(mOrientationProperty); + editor->addProperty(mGridSizeProperty); + editor->addProperty(mColumnCountProperty); + editor->addProperty(mAllowedTransformationsProperty); + editor->addProperty(mImageProperty); + } + +private: + void onChanged(const ChangeEvent &event) + { + if (event.type != ChangeEvent::TilesetChanged) + return; + + const auto property = static_cast(event).property; + switch (property) { + case Tileset::FillModeProperty: + emit mFillModeProperty->valueChanged(); + break; + case Tileset::TileRenderSizeProperty: + emit mTileRenderSizeProperty->valueChanged(); + break; + } + } + + void onTilesetChanged(Tileset *) + { + // the following properties have no specific change events + emit mBackgroundColorProperty->valueChanged(); + emit mOrientationProperty->valueChanged(); + emit mGridSizeProperty->valueChanged(); + emit mColumnCountProperty->valueChanged(); + emit mAllowedTransformationsProperty->valueChanged(); + emit mImageProperty->valueChanged(); + } + + void updateEnabledState() + { + const bool collection = tileset()->isCollection(); + mImageProperty->setEnabled(!collection); + mColumnCountProperty->setEnabled(collection); + } + + void push(QUndoCommand *command) + { + mTilesetDocument->undoStack()->push(command); + } + + Tileset *tileset() const + { + return mTilesetDocument->tileset().data(); + } + + TilesetDocument *mTilesetDocument; + Property *mNameProperty; + Property *mClassProperty; + Property *mObjectAlignmentProperty; + Property *mTileOffsetProperty; + Property *mTileRenderSizeProperty; + Property *mFillModeProperty; + Property *mBackgroundColorProperty; + Property *mOrientationProperty; + Property *mGridSizeProperty; + Property *mColumnCountProperty; + Property *mAllowedTransformationsProperty; + Property *mImageProperty; +}; + void PropertiesWidget::currentObjectChanged(Object *object) { - // mPropertyBrowser->setObject(object); mPropertyBrowser->clear(); delete mPropertiesObject; mPropertiesObject = nullptr; @@ -574,7 +787,13 @@ void PropertiesWidget::currentObjectChanged(Object *object) mPropertiesObject = properties; break; } - case Object::TilesetType: + case Object::TilesetType: { + auto tilesetDocument = static_cast(mDocument); + auto properties = new TilesetProperties(tilesetDocument, + mDefaultEditorFactory.get(), this); + properties->populateEditor(mPropertyBrowser); + mPropertiesObject = properties; + } case Object::TileType: case Object::WangSetType: case Object::WangColorType: @@ -1043,6 +1262,27 @@ void PropertiesWidget::registerEditorFactories() tr("Left Down"), tr("Left Up"), })); + + registerEditorFactory(qMetaTypeId(), + std::make_unique( + QStringList { + tr("Orthogonal"), + tr("Isometric"), + })); + + registerEditorFactory(qMetaTypeId(), + std::make_unique( + QStringList { + tr("Tile Size"), + tr("Map Grid Size"), + })); + + registerEditorFactory(qMetaTypeId(), + std::make_unique( + QStringList { + tr("Stretch"), + tr("Preserve Aspect Ratio"), + })); } void PropertiesWidget::registerEditorFactory(int type, std::unique_ptr factory) diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index f1eebbd365..b266051cca 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -332,6 +332,7 @@ class RectFEditorFactory : public EditorFactory } }; +// todo: needs to handle invalid color (unset value) class ColorEditorFactory : public EditorFactory { public: From 47d82449906333e4fdb346ff5bf707c69e0d8692 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Wed, 4 Sep 2024 13:08:05 +0200 Subject: [PATCH 13/36] Implemented Layer properties Some details remain to be done: * Changes made to layer properties should apply to all selected layers * Opacity should probably be a slider now and have appropriate limits * Step size for opacity and parallax factor is too big --- src/tiled/propertieswidget.cpp | 141 ++++++++++++++++++++++++++++++++- src/tiled/varianteditor.cpp | 3 + 2 files changed, 143 insertions(+), 1 deletion(-) diff --git a/src/tiled/propertieswidget.cpp b/src/tiled/propertieswidget.cpp index 1dd8bdfa45..740da361a8 100644 --- a/src/tiled/propertieswidget.cpp +++ b/src/tiled/propertieswidget.cpp @@ -22,6 +22,7 @@ #include "actionmanager.h" #include "addpropertydialog.h" +#include "changelayer.h" #include "changemapproperty.h" #include "changeproperties.h" #include "clipboardmanager.h" @@ -556,6 +557,138 @@ class MapProperties : public QObject Property *mBackgroundColorProperty; }; +class LayerProperties : public QObject +{ + Q_OBJECT + +public: + LayerProperties(MapDocument *mapDocument, Layer *layer, ValueTypeEditorFactory *editorFactory, QObject *parent = nullptr) + : QObject(parent) + , mMapDocument(mapDocument) + , mLayer(layer) + { + // todo: would be nicer to avoid the SpinBox and use a custom widget + mIdProperty = editorFactory->createProperty( + tr("ID"), + [this]() { return mLayer->id(); }, + [](const QVariant &) {}); + mIdProperty->setEnabled(false); + + // todo: the below should be able to apply to all selected layers + + mNameProperty = editorFactory->createProperty( + tr("Name"), + [this]() { return mLayer->name(); }, + [this](const QVariant &value) { + push(new SetLayerName(mMapDocument, { mLayer }, value.toString())); + }); + + mClassProperty = new ClassProperty(mMapDocument, mLayer); + + mVisibleProperty = editorFactory->createProperty( + tr("Visible"), + [this]() { return mLayer->isVisible(); }, + [this](const QVariant &value) { + push(new SetLayerVisible(mMapDocument, { mLayer }, value.toBool())); + }); + + mLockedProperty = editorFactory->createProperty( + tr("Locked"), + [this]() { return mLayer->isLocked(); }, + [this](const QVariant &value) { + push(new SetLayerLocked(mMapDocument, { mLayer }, value.toBool())); + }); + + // todo: value should be between 0 and 1, and would be nice to use a slider (replacing the one in Layers view) + // todo: singleStep should be 0.1 + mOpacityProperty = editorFactory->createProperty( + tr("Opacity"), + [this]() { return mLayer->opacity(); }, + [this](const QVariant &value) { + push(new SetLayerOpacity(mMapDocument, { mLayer }, value.toReal())); + }); + + mTintColorProperty = editorFactory->createProperty( + tr("Tint Color"), + [this]() { return mLayer->tintColor(); }, + [this](const QVariant &value) { + push(new SetLayerTintColor(mMapDocument, { mLayer }, value.value())); + }); + + mOffsetProperty = editorFactory->createProperty( + tr("Offset"), + [this]() { return mLayer->offset(); }, + [this](const QVariant &value) { + push(new SetLayerOffset(mMapDocument, { mLayer }, value.value())); + }); + + // todo: singleStep should be 0.1 + mParallaxFactorProperty = editorFactory->createProperty( + tr("Parallax Factor"), + [this]() { return mLayer->parallaxFactor(); }, + [this](const QVariant &value) { + push(new SetLayerParallaxFactor(mMapDocument, { mLayer }, value.toPointF())); + }); + + connect(mMapDocument, &Document::changed, + this, &LayerProperties::onChanged); + } + + void populateEditor(VariantEditor *editor) + { + editor->addHeader(tr("Layer")); + editor->addProperty(mIdProperty); + editor->addProperty(mNameProperty); + editor->addProperty(mClassProperty); + editor->addSeparator(); + editor->addProperty(mVisibleProperty); + editor->addProperty(mLockedProperty); + editor->addProperty(mOpacityProperty); + editor->addProperty(mTintColorProperty); + editor->addProperty(mOffsetProperty); + editor->addProperty(mParallaxFactorProperty); + } + +private: + void onChanged(const ChangeEvent &event) + { + if (event.type != ChangeEvent::LayerChanged) + return; + + const auto properties = static_cast(event).properties; + if (properties & LayerChangeEvent::VisibleProperty) + emit mVisibleProperty->valueChanged(); + if (properties & LayerChangeEvent::LockedProperty) + emit mLockedProperty->valueChanged(); + if (properties & LayerChangeEvent::OpacityProperty) + emit mOpacityProperty->valueChanged(); + if (properties & LayerChangeEvent::TintColorProperty) + emit mTintColorProperty->valueChanged(); + if (properties & LayerChangeEvent::OffsetProperty) + emit mOffsetProperty->valueChanged(); + if (properties & LayerChangeEvent::ParallaxFactorProperty) + emit mParallaxFactorProperty->valueChanged(); + } + + void push(QUndoCommand *command) + { + mMapDocument->undoStack()->push(command); + } + + MapDocument *mMapDocument; + Layer *mLayer; + + Property *mIdProperty; + Property *mNameProperty; + Property *mClassProperty; + Property *mVisibleProperty; + Property *mLockedProperty; + Property *mOpacityProperty; + Property *mTintColorProperty; + Property *mOffsetProperty; + Property *mParallaxFactorProperty; +}; + class TilesetProperties : public QObject { Q_OBJECT @@ -777,7 +910,13 @@ void PropertiesWidget::currentObjectChanged(Object *object) if (object) { switch (object->typeId()) { - case Object::LayerType: + case Object::LayerType: { + auto mapDocument = static_cast(mDocument); + auto layer = static_cast(object); + auto properties = new LayerProperties(mapDocument, layer, mDefaultEditorFactory.get(), this); + properties->populateEditor(mPropertyBrowser); + mPropertiesObject = properties; + } case Object::MapObjectType: break; case Object::MapType: { diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index b266051cca..7037a6337a 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -146,6 +146,7 @@ class BoolEditorFactory : public EditorFactory } }; +// todo: implement responsive layout (see SizeEdit) class PointEditorFactory : public EditorFactory { public: @@ -191,6 +192,7 @@ class PointEditorFactory : public EditorFactory } }; +// todo: implement responsive layout (see SizeEdit) class PointFEditorFactory : public EditorFactory { public: @@ -259,6 +261,7 @@ class SizeEditorFactory : public EditorFactory } }; +// todo: implement responsive layout (see SizeEdit) class RectFEditorFactory : public EditorFactory { public: From 09643e8ea64150fc692d00fc79a63634c21a0d35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Wed, 4 Sep 2024 14:02:06 +0200 Subject: [PATCH 14/36] Added Image Layer and Group Layer properties Added support for editing QUrl values using UrlEditorFactory that creates a FileEdit. --- src/libtiled/objectgroup.h | 1 + src/libtiled/tileset.h | 4 + src/tiled/changeevents.h | 2 +- src/tiled/propertieswidget.cpp | 202 ++++++++++++++++++++++++++++++--- src/tiled/varianteditor.cpp | 30 +++++ 5 files changed, 225 insertions(+), 14 deletions(-) diff --git a/src/libtiled/objectgroup.h b/src/libtiled/objectgroup.h index 07765ebdbd..3abacd4a7e 100644 --- a/src/libtiled/objectgroup.h +++ b/src/libtiled/objectgroup.h @@ -238,3 +238,4 @@ TILEDSHARED_EXPORT ObjectGroup::DrawOrder drawOrderFromString(const QString &); } // namespace Tiled Q_DECLARE_METATYPE(Tiled::ObjectGroup*) +Q_DECLARE_METATYPE(Tiled::ObjectGroup::DrawOrder) diff --git a/src/libtiled/tileset.h b/src/libtiled/tileset.h index c7b5b01156..934adfdbfe 100644 --- a/src/libtiled/tileset.h +++ b/src/libtiled/tileset.h @@ -739,5 +739,9 @@ inline void Tileset::setTransformationFlags(TransformationFlags flags) Q_DECLARE_METATYPE(Tiled::Tileset*) Q_DECLARE_METATYPE(Tiled::SharedTileset) +Q_DECLARE_METATYPE(Tiled::Tileset::Orientation) +Q_DECLARE_METATYPE(Tiled::Tileset::TileRenderSize) +Q_DECLARE_METATYPE(Tiled::Tileset::FillMode) +Q_DECLARE_METATYPE(Tiled::Tileset::TransformationFlags) Q_DECLARE_OPERATORS_FOR_FLAGS(Tiled::Tileset::TransformationFlags) diff --git a/src/tiled/changeevents.h b/src/tiled/changeevents.h index c5e4061f27..0dbf599280 100644 --- a/src/tiled/changeevents.h +++ b/src/tiled/changeevents.h @@ -164,7 +164,7 @@ class TileLayerChangeEvent : public LayerChangeEvent class ImageLayerChangeEvent : public LayerChangeEvent { public: - enum TileLayerProperty { + enum ImageLayerProperty { TransparentColorProperty = 1 << 7, ImageSourceProperty = 1 << 8, RepeatProperty = 1 << 9, diff --git a/src/tiled/propertieswidget.cpp b/src/tiled/propertieswidget.cpp index 740da361a8..c7d4cc67f0 100644 --- a/src/tiled/propertieswidget.cpp +++ b/src/tiled/propertieswidget.cpp @@ -22,12 +22,15 @@ #include "actionmanager.h" #include "addpropertydialog.h" +#include "changeimagelayerproperty.h" #include "changelayer.h" #include "changemapproperty.h" +#include "changeobjectgroupproperties.h" #include "changeproperties.h" #include "clipboardmanager.h" #include "compression.h" #include "mapdocument.h" +#include "objectgroup.h" #include "preferences.h" #include "propertybrowser.h" #include "tilesetchanges.h" @@ -568,6 +571,7 @@ class LayerProperties : public QObject , mLayer(layer) { // todo: would be nicer to avoid the SpinBox and use a custom widget + // might also be nice to embed this in the header instead of using a property mIdProperty = editorFactory->createProperty( tr("ID"), [this]() { return mLayer->id(); }, @@ -634,7 +638,7 @@ class LayerProperties : public QObject this, &LayerProperties::onChanged); } - void populateEditor(VariantEditor *editor) + virtual void populateEditor(VariantEditor *editor) { editor->addHeader(tr("Layer")); editor->addProperty(mIdProperty); @@ -649,24 +653,27 @@ class LayerProperties : public QObject editor->addProperty(mParallaxFactorProperty); } -private: - void onChanged(const ChangeEvent &event) +protected: + virtual void onChanged(const ChangeEvent &event) { if (event.type != ChangeEvent::LayerChanged) return; - const auto properties = static_cast(event).properties; - if (properties & LayerChangeEvent::VisibleProperty) + const auto &layerChange = static_cast(event); + if (layerChange.layer != mLayer) + return; + + if (layerChange.properties & LayerChangeEvent::VisibleProperty) emit mVisibleProperty->valueChanged(); - if (properties & LayerChangeEvent::LockedProperty) + if (layerChange.properties & LayerChangeEvent::LockedProperty) emit mLockedProperty->valueChanged(); - if (properties & LayerChangeEvent::OpacityProperty) + if (layerChange.properties & LayerChangeEvent::OpacityProperty) emit mOpacityProperty->valueChanged(); - if (properties & LayerChangeEvent::TintColorProperty) + if (layerChange.properties & LayerChangeEvent::TintColorProperty) emit mTintColorProperty->valueChanged(); - if (properties & LayerChangeEvent::OffsetProperty) + if (layerChange.properties & LayerChangeEvent::OffsetProperty) emit mOffsetProperty->valueChanged(); - if (properties & LayerChangeEvent::ParallaxFactorProperty) + if (layerChange.properties & LayerChangeEvent::ParallaxFactorProperty) emit mParallaxFactorProperty->valueChanged(); } @@ -689,6 +696,148 @@ class LayerProperties : public QObject Property *mParallaxFactorProperty; }; +class ImageLayerProperties : public LayerProperties +{ + Q_OBJECT + +public: + ImageLayerProperties(MapDocument *mapDocument, ImageLayer *layer, ValueTypeEditorFactory *editorFactory, QObject *parent = nullptr) + : LayerProperties(mapDocument, layer, editorFactory, parent) + { + // todo: set a file filter for selecting images (or map files?) + mImageProperty = editorFactory->createProperty( + tr("Image Source"), + [this]() { return imageLayer()->imageSource(); }, + [this](const QVariant &value) { + push(new ChangeImageLayerImageSource(mMapDocument, { imageLayer() }, value.toUrl())); + }); + + mTransparentColorProperty = editorFactory->createProperty( + tr("Transparent Color"), + [this]() { return imageLayer()->transparentColor(); }, + [this](const QVariant &value) { + push(new ChangeImageLayerTransparentColor(mMapDocument, { imageLayer() }, value.value())); + }); + + // todo: consider merging Repeat X and Y into a single property + mRepeatXProperty = editorFactory->createProperty( + tr("Repeat X"), + [this]() { return imageLayer()->repeatX(); }, + [this](const QVariant &value) { + push(new ChangeImageLayerRepeatX(mMapDocument, { imageLayer() }, value.toBool())); + }); + + mRepeatYProperty = editorFactory->createProperty( + tr("Repeat Y"), + [this]() { return imageLayer()->repeatY(); }, + [this](const QVariant &value) { + push(new ChangeImageLayerRepeatY(mMapDocument, { imageLayer() }, value.toBool())); + }); + } + + void populateEditor(VariantEditor *editor) override + { + LayerProperties::populateEditor(editor); + editor->addHeader(tr("Image Layer")); + editor->addProperty(mImageProperty); + editor->addProperty(mTransparentColorProperty); + editor->addSeparator(); + editor->addProperty(mRepeatXProperty); + editor->addProperty(mRepeatYProperty); + } + +private: + void onChanged(const ChangeEvent &event) override + { + LayerProperties::onChanged(event); + + if (event.type != ChangeEvent::ImageLayerChanged) + return; + + const auto &layerChange = static_cast(event); + if (layerChange.layer != mLayer) + return; + + if (layerChange.properties & ImageLayerChangeEvent::ImageSourceProperty) + emit mImageProperty->valueChanged(); + if (layerChange.properties & ImageLayerChangeEvent::TransparentColorProperty) + emit mTransparentColorProperty->valueChanged(); + if (layerChange.properties & ImageLayerChangeEvent::RepeatProperty) { + emit mRepeatXProperty->valueChanged(); + emit mRepeatYProperty->valueChanged(); + } + } + + ImageLayer *imageLayer() const + { + return static_cast(mLayer); + } + + Property *mImageProperty; + Property *mTransparentColorProperty; + Property *mRepeatXProperty; + Property *mRepeatYProperty; +}; + +class ObjectGroupProperties : public LayerProperties +{ + Q_OBJECT + +public: + ObjectGroupProperties(MapDocument *mapDocument, ObjectGroup *layer, ValueTypeEditorFactory *editorFactory, QObject *parent = nullptr) + : LayerProperties(mapDocument, layer, editorFactory, parent) + { + mColorProperty = editorFactory->createProperty( + tr("Color"), + [this]() { return objectGroup()->color(); }, + [this](const QVariant &value) { + push(new ChangeObjectGroupColor(mMapDocument, { objectGroup() }, value.value())); + }); + + mDrawOrderProperty = editorFactory->createProperty( + tr("Draw Order"), + [this]() { return QVariant::fromValue(objectGroup()->drawOrder()); }, + [this](const QVariant &value) { + ObjectGroup::DrawOrder drawOrder = static_cast(value.toInt()); + push(new ChangeObjectGroupDrawOrder(mMapDocument, { objectGroup() }, drawOrder)); + }); + } + + void populateEditor(VariantEditor *editor) override + { + LayerProperties::populateEditor(editor); + editor->addHeader(tr("Object Layer")); + editor->addProperty(mColorProperty); + editor->addProperty(mDrawOrderProperty); + } + +private: + void onChanged(const ChangeEvent &event) override + { + LayerProperties::onChanged(event); + + if (event.type != ChangeEvent::ObjectGroupChanged) + return; + + const auto &layerChange = static_cast(event); + if (layerChange.objectGroup != objectGroup()) + return; + + if (layerChange.properties & ObjectGroupChangeEvent::ColorProperty) + emit mColorProperty->valueChanged(); + if (layerChange.properties & ObjectGroupChangeEvent::DrawOrderProperty) + emit mDrawOrderProperty->valueChanged(); + } + + ObjectGroup *objectGroup() const + { + return static_cast(mLayer); + } + + Property *mColorProperty; + Property *mDrawOrderProperty; +}; + class TilesetProperties : public QObject { Q_OBJECT @@ -913,9 +1062,29 @@ void PropertiesWidget::currentObjectChanged(Object *object) case Object::LayerType: { auto mapDocument = static_cast(mDocument); auto layer = static_cast(object); - auto properties = new LayerProperties(mapDocument, layer, mDefaultEditorFactory.get(), this); - properties->populateEditor(mPropertyBrowser); - mPropertiesObject = properties; + LayerProperties *layerProperties = nullptr; + + switch (layer->layerType()) { + case Layer::ImageLayerType: + layerProperties = new ImageLayerProperties(mapDocument, + static_cast(layer), + mDefaultEditorFactory.get(), this); + break; + case Layer::ObjectGroupType: + layerProperties = new ObjectGroupProperties(mapDocument, + static_cast(layer), + mDefaultEditorFactory.get(), this); + break; + case Layer::TileLayerType: + case Layer::GroupLayerType: + layerProperties = new LayerProperties(mapDocument, + layer, + mDefaultEditorFactory.get(), this); + break; + } + + layerProperties->populateEditor(mPropertyBrowser); + mPropertiesObject = layerProperties; } case Object::MapObjectType: break; @@ -1422,6 +1591,13 @@ void PropertiesWidget::registerEditorFactories() tr("Stretch"), tr("Preserve Aspect Ratio"), })); + + registerEditorFactory(qMetaTypeId(), + std::make_unique( + QStringList { + tr("Top Down"), + tr("Index Order"), + })); } void PropertiesWidget::registerEditorFactory(int type, std::unique_ptr factory) diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index 7037a6337a..854b52d5c7 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -21,6 +21,7 @@ #include "varianteditor.h" #include "colorbutton.h" +#include "fileedit.h" #include "utils.h" #include "propertyeditorwidgets.h" @@ -82,6 +83,34 @@ class StringEditorFactory : public EditorFactory } }; +class UrlEditorFactory : public EditorFactory +{ +public: + QWidget *createEditor(Property *property, QWidget *parent) override + { + auto editor = new FileEdit(parent); + editor->setFilter(m_filter); + + auto syncEditor = [=] { + editor->setFileUrl(property->value().toUrl()); + }; + syncEditor(); + + QObject::connect(property, &Property::valueChanged, editor, syncEditor); + QObject::connect(editor, &FileEdit::fileUrlChanged, property, &Property::setValue); + + return editor; + } + + void setFilter(const QString &filter) + { + m_filter = filter; + } + +private: + QString m_filter; +}; + class IntEditorFactory : public EditorFactory { public: @@ -581,6 +610,7 @@ ValueTypeEditorFactory::ValueTypeEditorFactory() registerEditorFactory(QMetaType::QRectF, std::make_unique()); registerEditorFactory(QMetaType::QSize, std::make_unique()); registerEditorFactory(QMetaType::QString, std::make_unique()); + registerEditorFactory(QMetaType::QUrl, std::make_unique()); } void ValueTypeEditorFactory::registerEditorFactory(int type, std::unique_ptr factory) From 1ce8d98bcf86601988c7f646bf639655f39c4bb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Thu, 5 Sep 2024 17:21:56 +0200 Subject: [PATCH 15/36] Added properties for map objects Also in this change: * Made SpinBox and DoubleSpinBox don't respond to changing from keyboard typing immediately. * Share a single implementation of wrapping label/widget pairs for SizeEdit, SizeFEdit, PointEdit, PointFEdit and RectFEdit. * Added widget factories for QFont, QSizeF and Qt::Alignment. * Allow setting suffix on custom FloatEditorFactory, used for adding degree symbol to rotation values. A few things remain to be done: * Custom widget for editing tile object flipping flags * Try reducing size of font editor with style toggle buttons --- src/tiled/propertieswidget.cpp | 538 +++++++++++++++++++++------- src/tiled/propertieswidget.h | 3 +- src/tiled/propertyeditorwidgets.cpp | 271 +++++++++++--- src/tiled/propertyeditorwidgets.h | 136 ++++++- src/tiled/varianteditor.cpp | 360 +++++++++++-------- src/tiled/varianteditor.h | 15 + 6 files changed, 982 insertions(+), 341 deletions(-) diff --git a/src/tiled/propertieswidget.cpp b/src/tiled/propertieswidget.cpp index c7d4cc67f0..d1f741ab69 100644 --- a/src/tiled/propertieswidget.cpp +++ b/src/tiled/propertieswidget.cpp @@ -24,6 +24,7 @@ #include "addpropertydialog.h" #include "changeimagelayerproperty.h" #include "changelayer.h" +#include "changemapobject.h" #include "changemapproperty.h" #include "changeobjectgroupproperties.h" #include "changeproperties.h" @@ -31,6 +32,7 @@ #include "compression.h" #include "mapdocument.h" #include "objectgroup.h" +#include "objecttemplate.h" #include "preferences.h" #include "propertybrowser.h" #include "tilesetchanges.h" @@ -307,21 +309,46 @@ class TileSizeProperty : public AbstractProperty MapDocument *mMapDocument; }; -class MapProperties : public QObject +class ObjectProperties : public QObject { Q_OBJECT public: - MapProperties(MapDocument *mapDocument, - ValueTypeEditorFactory *editorFactory, - QObject *parent = nullptr) + ObjectProperties(Document *document, Object *object, QObject *parent = nullptr) : QObject(parent) - , mMapDocument(mapDocument) - , mSizeProperty(new MapSizeProperty(mapDocument, editorFactory, this)) - , mTileSizeProperty(new TileSizeProperty(mapDocument, editorFactory, this)) + , mDocument(document) + , mObject(object) + { + mClassProperty = new ClassProperty(document, object, this); + } + + virtual void populateEditor(VariantEditor *) + { + // nothing added here due to property grouping + } + +protected: + void push(QUndoCommand *command) { - mClassProperty = new ClassProperty(mMapDocument, mMapDocument->map()); + mDocument->undoStack()->push(command); + } + + Document *mDocument; + Property *mClassProperty; + Object *mObject; +}; + + +class MapProperties : public ObjectProperties +{ + Q_OBJECT +public: + MapProperties(MapDocument *document, + ValueTypeEditorFactory *editorFactory, + QObject *parent = nullptr) + : ObjectProperties(document, document->map(), parent) + { mOrientationProperty = editorFactory->createProperty( tr("Orientation"), [this]() { @@ -329,16 +356,20 @@ class MapProperties : public QObject }, [this](const QVariant &value) { auto orientation = static_cast(value.toInt()); - push(new ChangeMapProperty(mMapDocument, orientation)); + push(new ChangeMapProperty(mapDocument(), orientation)); }); + mSizeProperty = new MapSizeProperty(mapDocument(), editorFactory, this); + + mTileSizeProperty = new TileSizeProperty(mapDocument(), editorFactory, this); + mInfiniteProperty = editorFactory->createProperty( tr("Infinite"), [this]() { return map()->infinite(); }, [this](const QVariant &value) { - push(new ChangeMapProperty(mMapDocument, + push(new ChangeMapProperty(mapDocument(), Map::InfiniteProperty, value.toInt())); }); @@ -349,7 +380,7 @@ class MapProperties : public QObject return map()->hexSideLength(); }, [this](const QVariant &value) { - push(new ChangeMapProperty(mMapDocument, + push(new ChangeMapProperty(mapDocument(), Map::HexSideLengthProperty, value.toInt())); }); @@ -361,7 +392,7 @@ class MapProperties : public QObject }, [this](const QVariant &value) { auto staggerAxis = static_cast(value.toInt()); - push(new ChangeMapProperty(mMapDocument, staggerAxis)); + push(new ChangeMapProperty(mapDocument(), staggerAxis)); }); mStaggerIndexProperty = editorFactory->createProperty( @@ -371,7 +402,7 @@ class MapProperties : public QObject }, [this](const QVariant &value) { auto staggerIndex = static_cast(value.toInt()); - push(new ChangeMapProperty(mMapDocument, staggerIndex)); + push(new ChangeMapProperty(mapDocument(), staggerIndex)); }); mParallaxOriginProperty = editorFactory->createProperty( @@ -380,7 +411,7 @@ class MapProperties : public QObject return map()->parallaxOrigin(); }, [this](const QVariant &value) { - push(new ChangeMapProperty(mMapDocument, value.value())); + push(new ChangeMapProperty(mapDocument(), value.value())); }); mLayerDataFormatProperty = editorFactory->createProperty( @@ -390,7 +421,7 @@ class MapProperties : public QObject }, [this](const QVariant &value) { auto layerDataFormat = static_cast(value.toInt()); - push(new ChangeMapProperty(mMapDocument, layerDataFormat)); + push(new ChangeMapProperty(mapDocument(), layerDataFormat)); }); mChunkSizeProperty = editorFactory->createProperty( @@ -399,7 +430,7 @@ class MapProperties : public QObject return map()->chunkSize(); }, [this](const QVariant &value) { - push(new ChangeMapProperty(mMapDocument, value.toSize())); + push(new ChangeMapProperty(mapDocument(), value.toSize())); }); mRenderOrderProperty = editorFactory->createProperty( @@ -409,7 +440,7 @@ class MapProperties : public QObject }, [this](const QVariant &value) { auto renderOrder = static_cast(value.toInt()); - push(new ChangeMapProperty(mMapDocument, renderOrder)); + push(new ChangeMapProperty(mapDocument(), renderOrder)); }); mCompressionLevelProperty = editorFactory->createProperty( @@ -418,7 +449,7 @@ class MapProperties : public QObject return map()->compressionLevel(); }, [this](const QVariant &value) { - push(new ChangeMapProperty(mMapDocument, value.toInt())); + push(new ChangeMapProperty(mapDocument(), value.toInt())); }); mBackgroundColorProperty = editorFactory->createProperty( @@ -427,15 +458,15 @@ class MapProperties : public QObject return map()->backgroundColor(); }, [this](const QVariant &value) { - push(new ChangeMapProperty(mMapDocument, value.value())); + push(new ChangeMapProperty(mapDocument(), value.value())); }); updateEnabledState(); - connect(mMapDocument, &Document::changed, + connect(document, &Document::changed, this, &MapProperties::onChanged); } - void populateEditor(VariantEditor *editor) + void populateEditor(VariantEditor *editor) override { editor->addHeader(tr("Map")); editor->addProperty(mClassProperty); @@ -533,18 +564,16 @@ class MapProperties : public QObject } } - void push(QUndoCommand *command) + MapDocument *mapDocument() const { - mMapDocument->undoStack()->push(command); + return static_cast(mDocument); } Map *map() const { - return mMapDocument->map(); + return mapDocument()->map(); } - MapDocument *mMapDocument; - Property *mClassProperty; Property *mOrientationProperty; Property *mSizeProperty; Property *mTileSizeProperty; @@ -560,21 +589,19 @@ class MapProperties : public QObject Property *mBackgroundColorProperty; }; -class LayerProperties : public QObject +class LayerProperties : public ObjectProperties { Q_OBJECT public: - LayerProperties(MapDocument *mapDocument, Layer *layer, ValueTypeEditorFactory *editorFactory, QObject *parent = nullptr) - : QObject(parent) - , mMapDocument(mapDocument) - , mLayer(layer) + LayerProperties(MapDocument *document, Layer *object, ValueTypeEditorFactory *editorFactory, QObject *parent = nullptr) + : ObjectProperties(document, object, parent) { // todo: would be nicer to avoid the SpinBox and use a custom widget // might also be nice to embed this in the header instead of using a property mIdProperty = editorFactory->createProperty( tr("ID"), - [this]() { return mLayer->id(); }, + [this]() { return layer()->id(); }, [](const QVariant &) {}); mIdProperty->setEnabled(false); @@ -582,63 +609,61 @@ class LayerProperties : public QObject mNameProperty = editorFactory->createProperty( tr("Name"), - [this]() { return mLayer->name(); }, + [this]() { return layer()->name(); }, [this](const QVariant &value) { - push(new SetLayerName(mMapDocument, { mLayer }, value.toString())); + push(new SetLayerName(mapDocument(), { layer() }, value.toString())); }); - mClassProperty = new ClassProperty(mMapDocument, mLayer); - mVisibleProperty = editorFactory->createProperty( tr("Visible"), - [this]() { return mLayer->isVisible(); }, + [this]() { return layer()->isVisible(); }, [this](const QVariant &value) { - push(new SetLayerVisible(mMapDocument, { mLayer }, value.toBool())); + push(new SetLayerVisible(mapDocument(), { layer() }, value.toBool())); }); mLockedProperty = editorFactory->createProperty( tr("Locked"), - [this]() { return mLayer->isLocked(); }, + [this]() { return layer()->isLocked(); }, [this](const QVariant &value) { - push(new SetLayerLocked(mMapDocument, { mLayer }, value.toBool())); + push(new SetLayerLocked(mapDocument(), { layer() }, value.toBool())); }); // todo: value should be between 0 and 1, and would be nice to use a slider (replacing the one in Layers view) // todo: singleStep should be 0.1 mOpacityProperty = editorFactory->createProperty( tr("Opacity"), - [this]() { return mLayer->opacity(); }, + [this]() { return layer()->opacity(); }, [this](const QVariant &value) { - push(new SetLayerOpacity(mMapDocument, { mLayer }, value.toReal())); + push(new SetLayerOpacity(mapDocument(), { layer() }, value.toReal())); }); mTintColorProperty = editorFactory->createProperty( tr("Tint Color"), - [this]() { return mLayer->tintColor(); }, + [this]() { return layer()->tintColor(); }, [this](const QVariant &value) { - push(new SetLayerTintColor(mMapDocument, { mLayer }, value.value())); + push(new SetLayerTintColor(mapDocument(), { layer() }, value.value())); }); mOffsetProperty = editorFactory->createProperty( tr("Offset"), - [this]() { return mLayer->offset(); }, + [this]() { return layer()->offset(); }, [this](const QVariant &value) { - push(new SetLayerOffset(mMapDocument, { mLayer }, value.value())); + push(new SetLayerOffset(mapDocument(), { layer() }, value.value())); }); // todo: singleStep should be 0.1 mParallaxFactorProperty = editorFactory->createProperty( tr("Parallax Factor"), - [this]() { return mLayer->parallaxFactor(); }, + [this]() { return layer()->parallaxFactor(); }, [this](const QVariant &value) { - push(new SetLayerParallaxFactor(mMapDocument, { mLayer }, value.toPointF())); + push(new SetLayerParallaxFactor(mapDocument(), { layer() }, value.toPointF())); }); - connect(mMapDocument, &Document::changed, + connect(document, &Document::changed, this, &LayerProperties::onChanged); } - virtual void populateEditor(VariantEditor *editor) + void populateEditor(VariantEditor *editor) override { editor->addHeader(tr("Layer")); editor->addProperty(mIdProperty); @@ -660,7 +685,7 @@ class LayerProperties : public QObject return; const auto &layerChange = static_cast(event); - if (layerChange.layer != mLayer) + if (layerChange.layer != layer()) return; if (layerChange.properties & LayerChangeEvent::VisibleProperty) @@ -677,17 +702,18 @@ class LayerProperties : public QObject emit mParallaxFactorProperty->valueChanged(); } - void push(QUndoCommand *command) + MapDocument *mapDocument() const { - mMapDocument->undoStack()->push(command); + return static_cast(mDocument); } - MapDocument *mMapDocument; - Layer *mLayer; + Layer *layer() const + { + return static_cast(mObject); + } Property *mIdProperty; Property *mNameProperty; - Property *mClassProperty; Property *mVisibleProperty; Property *mLockedProperty; Property *mOpacityProperty; @@ -701,22 +727,22 @@ class ImageLayerProperties : public LayerProperties Q_OBJECT public: - ImageLayerProperties(MapDocument *mapDocument, ImageLayer *layer, ValueTypeEditorFactory *editorFactory, QObject *parent = nullptr) - : LayerProperties(mapDocument, layer, editorFactory, parent) + ImageLayerProperties(MapDocument *document, ImageLayer *object, ValueTypeEditorFactory *editorFactory, QObject *parent = nullptr) + : LayerProperties(document, object, editorFactory, parent) { // todo: set a file filter for selecting images (or map files?) mImageProperty = editorFactory->createProperty( tr("Image Source"), [this]() { return imageLayer()->imageSource(); }, [this](const QVariant &value) { - push(new ChangeImageLayerImageSource(mMapDocument, { imageLayer() }, value.toUrl())); + push(new ChangeImageLayerImageSource(mapDocument(), { imageLayer() }, value.toUrl())); }); mTransparentColorProperty = editorFactory->createProperty( tr("Transparent Color"), [this]() { return imageLayer()->transparentColor(); }, [this](const QVariant &value) { - push(new ChangeImageLayerTransparentColor(mMapDocument, { imageLayer() }, value.value())); + push(new ChangeImageLayerTransparentColor(mapDocument(), { imageLayer() }, value.value())); }); // todo: consider merging Repeat X and Y into a single property @@ -724,14 +750,14 @@ class ImageLayerProperties : public LayerProperties tr("Repeat X"), [this]() { return imageLayer()->repeatX(); }, [this](const QVariant &value) { - push(new ChangeImageLayerRepeatX(mMapDocument, { imageLayer() }, value.toBool())); + push(new ChangeImageLayerRepeatX(mapDocument(), { imageLayer() }, value.toBool())); }); mRepeatYProperty = editorFactory->createProperty( tr("Repeat Y"), [this]() { return imageLayer()->repeatY(); }, [this](const QVariant &value) { - push(new ChangeImageLayerRepeatY(mMapDocument, { imageLayer() }, value.toBool())); + push(new ChangeImageLayerRepeatY(mapDocument(), { imageLayer() }, value.toBool())); }); } @@ -755,7 +781,7 @@ class ImageLayerProperties : public LayerProperties return; const auto &layerChange = static_cast(event); - if (layerChange.layer != mLayer) + if (layerChange.layer != layer()) return; if (layerChange.properties & ImageLayerChangeEvent::ImageSourceProperty) @@ -770,7 +796,7 @@ class ImageLayerProperties : public LayerProperties ImageLayer *imageLayer() const { - return static_cast(mLayer); + return static_cast(mObject); } Property *mImageProperty; @@ -784,14 +810,14 @@ class ObjectGroupProperties : public LayerProperties Q_OBJECT public: - ObjectGroupProperties(MapDocument *mapDocument, ObjectGroup *layer, ValueTypeEditorFactory *editorFactory, QObject *parent = nullptr) - : LayerProperties(mapDocument, layer, editorFactory, parent) + ObjectGroupProperties(MapDocument *document, ObjectGroup *object, ValueTypeEditorFactory *editorFactory, QObject *parent = nullptr) + : LayerProperties(document, object, editorFactory, parent) { mColorProperty = editorFactory->createProperty( tr("Color"), [this]() { return objectGroup()->color(); }, [this](const QVariant &value) { - push(new ChangeObjectGroupColor(mMapDocument, { objectGroup() }, value.value())); + push(new ChangeObjectGroupColor(mapDocument(), { objectGroup() }, value.value())); }); mDrawOrderProperty = editorFactory->createProperty( @@ -799,7 +825,7 @@ class ObjectGroupProperties : public LayerProperties [this]() { return QVariant::fromValue(objectGroup()->drawOrder()); }, [this](const QVariant &value) { ObjectGroup::DrawOrder drawOrder = static_cast(value.toInt()); - push(new ChangeObjectGroupDrawOrder(mMapDocument, { objectGroup() }, drawOrder)); + push(new ChangeObjectGroupDrawOrder(mapDocument(), { objectGroup() }, drawOrder)); }); } @@ -831,35 +857,32 @@ class ObjectGroupProperties : public LayerProperties ObjectGroup *objectGroup() const { - return static_cast(mLayer); + return static_cast(mObject); } Property *mColorProperty; Property *mDrawOrderProperty; }; -class TilesetProperties : public QObject +class TilesetProperties : public ObjectProperties { Q_OBJECT public: - TilesetProperties(TilesetDocument *tilesetDocument, + TilesetProperties(TilesetDocument *document, ValueTypeEditorFactory *editorFactory, QObject *parent = nullptr) - : QObject(parent) - , mTilesetDocument(tilesetDocument) + : ObjectProperties(document, document->tileset().data(), parent) { mNameProperty = editorFactory->createProperty( tr("Name"), [this]() { - return mTilesetDocument->tileset()->name(); + return tilesetDocument()->tileset()->name(); }, [this](const QVariant &value) { - push(new RenameTileset(mTilesetDocument, value.toString())); + push(new RenameTileset(tilesetDocument(), value.toString())); }); - mClassProperty = new ClassProperty(tilesetDocument, tilesetDocument->tileset().data()); - mObjectAlignmentProperty = editorFactory->createProperty( tr("Object Alignment"), [this]() { @@ -867,7 +890,7 @@ class TilesetProperties : public QObject }, [this](const QVariant &value) { const auto objectAlignment = static_cast(value.toInt()); - push(new ChangeTilesetObjectAlignment(mTilesetDocument, objectAlignment)); + push(new ChangeTilesetObjectAlignment(tilesetDocument(), objectAlignment)); }); mTileOffsetProperty = editorFactory->createProperty( @@ -876,7 +899,7 @@ class TilesetProperties : public QObject return tileset()->tileOffset(); }, [this](const QVariant &value) { - push(new ChangeTilesetTileOffset(mTilesetDocument, value.value())); + push(new ChangeTilesetTileOffset(tilesetDocument(), value.value())); }); mTileRenderSizeProperty = editorFactory->createProperty( @@ -886,7 +909,7 @@ class TilesetProperties : public QObject }, [this](const QVariant &value) { const auto tileRenderSize = static_cast(value.toInt()); - push(new ChangeTilesetTileRenderSize(mTilesetDocument, tileRenderSize)); + push(new ChangeTilesetTileRenderSize(tilesetDocument(), tileRenderSize)); }); mFillModeProperty = editorFactory->createProperty( @@ -896,7 +919,7 @@ class TilesetProperties : public QObject }, [this](const QVariant &value) { const auto fillMode = static_cast(value.toInt()); - push(new ChangeTilesetFillMode(mTilesetDocument, fillMode)); + push(new ChangeTilesetFillMode(tilesetDocument(), fillMode)); }); mBackgroundColorProperty = editorFactory->createProperty( @@ -905,7 +928,7 @@ class TilesetProperties : public QObject return tileset()->backgroundColor(); }, [this](const QVariant &value) { - push(new ChangeTilesetBackgroundColor(mTilesetDocument, value.value())); + push(new ChangeTilesetBackgroundColor(tilesetDocument(), value.value())); }); mOrientationProperty = editorFactory->createProperty( @@ -915,7 +938,7 @@ class TilesetProperties : public QObject }, [this](const QVariant &value) { const auto orientation = static_cast(value.toInt()); - push(new ChangeTilesetOrientation(mTilesetDocument, orientation)); + push(new ChangeTilesetOrientation(tilesetDocument(), orientation)); }); mGridSizeProperty = editorFactory->createProperty( @@ -924,7 +947,7 @@ class TilesetProperties : public QObject return tileset()->gridSize(); }, [this](const QVariant &value) { - push(new ChangeTilesetGridSize(mTilesetDocument, value.toSize())); + push(new ChangeTilesetGridSize(tilesetDocument(), value.toSize())); }); // todo: needs 1 as minimum value @@ -934,7 +957,7 @@ class TilesetProperties : public QObject return tileset()->columnCount(); }, [this](const QVariant &value) { - push(new ChangeTilesetColumnCount(mTilesetDocument, value.toInt())); + push(new ChangeTilesetColumnCount(tilesetDocument(), value.toInt())); }); // todo: this needs a custom widget @@ -945,7 +968,7 @@ class TilesetProperties : public QObject }, [this](const QVariant &value) { const auto flags = static_cast(value.toInt()); - push(new ChangeTilesetTransformationFlags(mTilesetDocument, flags)); + push(new ChangeTilesetTransformationFlags(tilesetDocument(), flags)); }); // todo: this needs a custom widget @@ -955,20 +978,20 @@ class TilesetProperties : public QObject return tileset()->imageSource().toString(); }, [](const QVariant &) { - // push(new ChangeTilesetImage(mTilesetDocument, value.toString())); + // push(new ChangeTilesetImage(tilesetDocument(), value.toString())); }); updateEnabledState(); - connect(mTilesetDocument, &Document::changed, + connect(tilesetDocument(), &Document::changed, this, &TilesetProperties::onChanged); - connect(mTilesetDocument, &TilesetDocument::tilesetNameChanged, + connect(tilesetDocument(), &TilesetDocument::tilesetNameChanged, mNameProperty, &Property::valueChanged); - connect(mTilesetDocument, &TilesetDocument::tilesetTileOffsetChanged, + connect(tilesetDocument(), &TilesetDocument::tilesetTileOffsetChanged, mTileOffsetProperty, &Property::valueChanged); - connect(mTilesetDocument, &TilesetDocument::tilesetObjectAlignmentChanged, + connect(tilesetDocument(), &TilesetDocument::tilesetObjectAlignmentChanged, mObjectAlignmentProperty, &Property::valueChanged); - connect(mTilesetDocument, &TilesetDocument::tilesetChanged, + connect(tilesetDocument(), &TilesetDocument::tilesetChanged, this, &TilesetProperties::onTilesetChanged); } @@ -1025,19 +1048,17 @@ class TilesetProperties : public QObject mColumnCountProperty->setEnabled(collection); } - void push(QUndoCommand *command) + TilesetDocument *tilesetDocument() const { - mTilesetDocument->undoStack()->push(command); + return static_cast(mDocument); } Tileset *tileset() const { - return mTilesetDocument->tileset().data(); + return tilesetDocument()->tileset().data(); } - TilesetDocument *mTilesetDocument; Property *mNameProperty; - Property *mClassProperty; Property *mObjectAlignmentProperty; Property *mTileOffsetProperty; Property *mTileRenderSizeProperty; @@ -1050,10 +1071,279 @@ class TilesetProperties : public QObject Property *mImageProperty; }; +class MapObjectProperties : public ObjectProperties +{ + Q_OBJECT + +public: + MapObjectProperties(MapDocument *document, MapObject *object, ValueTypeEditorFactory *editorFactory, QObject *parent = nullptr) + : ObjectProperties(document, object, parent) + , mDegreesEditorFactory(std::make_unique()) + { + mDegreesEditorFactory->setSuffix(QStringLiteral("°")); + + mIdProperty = editorFactory->createProperty( + tr("ID"), + [this]() { return mapObject()->id(); }, + [](const QVariant &) {}); + mIdProperty->setEnabled(false); + + mTemplateProperty = editorFactory->createProperty( + tr("Template"), + [this]() { + if (auto objectTemplate = mapObject()->objectTemplate()) + return QUrl::fromLocalFile(objectTemplate->fileName()); + return QUrl(); + }, + [](const QVariant &) {}); + mTemplateProperty->setEnabled(false); + + mNameProperty = editorFactory->createProperty( + tr("Name"), + [this]() { + return mapObject()->name(); + }, + [this](const QVariant &value) { + changeMapObject(MapObject::NameProperty, value); + }); + + mVisibleProperty = editorFactory->createProperty( + tr("Visible"), + [this]() { + return mapObject()->isVisible(); + }, + [this](const QVariant &value) { + changeMapObject(MapObject::VisibleProperty, value); + }); + + mPositionProperty = editorFactory->createProperty( + tr("Position"), + [this]() { + return mapObject()->position(); + }, + [this](const QVariant &value) { + changeMapObject(MapObject::PositionProperty, value); + }); + + mSizeProperty = editorFactory->createProperty( + tr("Size"), + [this]() { + return mapObject()->size(); + }, + [this](const QVariant &value) { + changeMapObject(MapObject::SizeProperty, value); + }); + + mRotationProperty = new GetSetProperty( + tr("Rotation"), + [this]() { + return mapObject()->rotation(); + }, + [this](const QVariant &value) { + changeMapObject(MapObject::RotationProperty, value); + }, + mDegreesEditorFactory.get(), this); + + // todo: make this a custom widget with "Horizontal" and "Vertical" checkboxes + mFlippingProperty = editorFactory->createProperty( + tr("Flipping"), + [this]() { + return mapObject()->cell().flags(); + }, + [this](const QVariant &value) { + const int flippingFlags = value.toInt(); + + MapObjectCell mapObjectCell; + mapObjectCell.object = mapObject(); + mapObjectCell.cell = mapObject()->cell(); + mapObjectCell.cell.setFlippedHorizontally(flippingFlags & 1); + mapObjectCell.cell.setFlippedVertically(flippingFlags & 2); + + auto command = new ChangeMapObjectCells(mDocument, { mapObjectCell }); + + command->setText(QCoreApplication::translate("Undo Commands", + "Flip %n Object(s)", + nullptr, + mapDocument()->selectedObjects().size())); + push(command); + }); + + mTextProperty = editorFactory->createProperty( + tr("Text"), + [this]() { + return mapObject()->textData().text; + }, + [this](const QVariant &value) { + changeMapObject(MapObject::TextProperty, value); + }); + + mTextAlignmentProperty = editorFactory->createProperty( + tr("Alignment"), + [this]() { + return QVariant::fromValue(mapObject()->textData().alignment); + }, + [this](const QVariant &value) { + changeMapObject(MapObject::TextAlignmentProperty, value); + }); + + mTextFontProperty = editorFactory->createProperty( + tr("Font"), + [this]() { + return mapObject()->textData().font; + }, + [this](const QVariant &value) { + changeMapObject(MapObject::TextFontProperty, value); + }); + + mTextWordWrapProperty = editorFactory->createProperty( + tr("Word Wrap"), + [this]() { + return mapObject()->textData().wordWrap; + }, + [this](const QVariant &value) { + changeMapObject(MapObject::TextWordWrapProperty, value); + }); + + mTextColorProperty = editorFactory->createProperty( + tr("Text Color"), + [this]() { + return mapObject()->textData().color; + }, + [this](const QVariant &value) { + changeMapObject(MapObject::TextColorProperty, value); + }); + + connect(document, &Document::changed, + this, &MapObjectProperties::onChanged); + + updateEnabledState(); + } + + void populateEditor(VariantEditor *editor) override + { + editor->addHeader(tr("Object")); + editor->addProperty(mIdProperty); + editor->addProperty(mTemplateProperty); + editor->addProperty(mNameProperty); + editor->addProperty(mClassProperty); + editor->addSeparator(); + + if (mapDocument()->allowHidingObjects()) + editor->addProperty(mVisibleProperty); + + editor->addProperty(mPositionProperty); + + if (mapObject()->hasDimensions()) + editor->addProperty(mSizeProperty); + + if (mapObject()->canRotate()) + editor->addProperty(mRotationProperty); + + if (mapObject()->isTileObject()) { + editor->addSeparator(); + editor->addProperty(mFlippingProperty); + } + + if (mapObject()->shape() == MapObject::Text) { + editor->addSeparator(); + editor->addProperty(mTextProperty); + editor->addProperty(mTextAlignmentProperty); + editor->addProperty(mTextFontProperty); + editor->addProperty(mTextWordWrapProperty); + editor->addProperty(mTextColorProperty); + } + } + +private: + void onChanged(const ChangeEvent &event) + { + if (event.type != ChangeEvent::MapObjectsChanged) + return; + + const auto &change = static_cast(event); + if (!change.mapObjects.contains(mapObject())) + return; + + if (change.properties & MapObject::NameProperty) + emit mNameProperty->valueChanged(); + if (change.properties & MapObject::VisibleProperty) + emit mVisibleProperty->valueChanged(); + if (change.properties & MapObject::PositionProperty) + emit mPositionProperty->valueChanged(); + if (change.properties & MapObject::SizeProperty) + emit mSizeProperty->valueChanged(); + if (change.properties & MapObject::RotationProperty) + emit mRotationProperty->valueChanged(); + if (change.properties & MapObject::CellProperty) + emit mFlippingProperty->valueChanged(); + if (change.properties & MapObject::TextProperty) + emit mTextProperty->valueChanged(); + if (change.properties & MapObject::TextFontProperty) + emit mTextFontProperty->valueChanged(); + if (change.properties & MapObject::TextAlignmentProperty) + emit mTextAlignmentProperty->valueChanged(); + if (change.properties & MapObject::TextWordWrapProperty) + emit mTextWordWrapProperty->valueChanged(); + if (change.properties & MapObject::TextColorProperty) + emit mTextColorProperty->valueChanged(); + } + + void updateEnabledState() + { + mVisibleProperty->setEnabled(mapDocument()->allowHidingObjects()); + mSizeProperty->setEnabled(mapObject()->hasDimensions()); + mRotationProperty->setEnabled(mapObject()->canRotate()); + mFlippingProperty->setEnabled(mapObject()->isTileObject()); + + const bool isText = mapObject()->shape() == MapObject::Text; + mTextProperty->setEnabled(isText); + mTextAlignmentProperty->setEnabled(isText); + mTextFontProperty->setEnabled(isText); + mTextWordWrapProperty->setEnabled(isText); + mTextColorProperty->setEnabled(isText); + } + + MapDocument *mapDocument() const + { + return static_cast(mDocument); + } + + MapObject *mapObject() const + { + return static_cast(mObject); + } + + void changeMapObject(MapObject::Property property, const QVariant &value) + { + push(new ChangeMapObject(mapDocument(), mapObject(), property, value)); + } + + Property *mIdProperty; + Property *mTemplateProperty; + Property *mNameProperty; + Property *mVisibleProperty; + Property *mPositionProperty; + Property *mSizeProperty; + Property *mRotationProperty; + + // for tile objects + Property *mFlippingProperty; + + // for text objects + Property *mTextProperty; + Property *mTextAlignmentProperty; + Property *mTextFontProperty; + Property *mTextWordWrapProperty; + Property *mTextColorProperty; + + std::unique_ptr mDegreesEditorFactory; +}; + void PropertiesWidget::currentObjectChanged(Object *object) { mPropertyBrowser->clear(); + delete mPropertiesObject; mPropertiesObject = nullptr; @@ -1061,56 +1351,54 @@ void PropertiesWidget::currentObjectChanged(Object *object) switch (object->typeId()) { case Object::LayerType: { auto mapDocument = static_cast(mDocument); - auto layer = static_cast(object); - LayerProperties *layerProperties = nullptr; - switch (layer->layerType()) { + switch (static_cast(object)->layerType()) { case Layer::ImageLayerType: - layerProperties = new ImageLayerProperties(mapDocument, - static_cast(layer), - mDefaultEditorFactory.get(), this); + mPropertiesObject = new ImageLayerProperties(mapDocument, + static_cast(object), + mDefaultEditorFactory.get(), this); break; case Layer::ObjectGroupType: - layerProperties = new ObjectGroupProperties(mapDocument, - static_cast(layer), - mDefaultEditorFactory.get(), this); + mPropertiesObject = new ObjectGroupProperties(mapDocument, + static_cast(object), + mDefaultEditorFactory.get(), this); break; case Layer::TileLayerType: case Layer::GroupLayerType: - layerProperties = new LayerProperties(mapDocument, - layer, - mDefaultEditorFactory.get(), this); + mPropertiesObject = new LayerProperties(mapDocument, + static_cast(object), + mDefaultEditorFactory.get(), this); break; } - - layerProperties->populateEditor(mPropertyBrowser); - mPropertiesObject = layerProperties; + break; } case Object::MapObjectType: + mPropertiesObject = new MapObjectProperties(static_cast(mDocument), + static_cast(object), mDefaultEditorFactory.get(), this); break; - case Object::MapType: { - auto mapDocument = static_cast(mDocument); - auto properties = new MapProperties(mapDocument, mDefaultEditorFactory.get(), this); - properties->populateEditor(mPropertyBrowser); - mPropertiesObject = properties; + case Object::MapType: + mPropertiesObject = new MapProperties(static_cast(mDocument), + mDefaultEditorFactory.get(), this); + break; + case Object::TilesetType: + mPropertiesObject = new TilesetProperties(static_cast(mDocument), + mDefaultEditorFactory.get(), this); break; - } - case Object::TilesetType: { - auto tilesetDocument = static_cast(mDocument); - auto properties = new TilesetProperties(tilesetDocument, - mDefaultEditorFactory.get(), this); - properties->populateEditor(mPropertyBrowser); - mPropertiesObject = properties; - } case Object::TileType: + // todo case Object::WangSetType: + // todo case Object::WangColorType: + // todo case Object::ProjectType: case Object::WorldType: break; } } + if (mPropertiesObject) + mPropertiesObject->populateEditor(mPropertyBrowser); + bool editingTileset = mDocument && mDocument->type() == Document::TilesetDocumentType; bool isTileset = object && object->isPartOfTileset(); bool enabled = object && (!isTileset || editingTileset); diff --git a/src/tiled/propertieswidget.h b/src/tiled/propertieswidget.h index 37db77ee6d..d6ad390af1 100644 --- a/src/tiled/propertieswidget.h +++ b/src/tiled/propertieswidget.h @@ -30,6 +30,7 @@ class Object; class Document; class EditorFactory; +class ObjectProperties; class ValueTypeEditorFactory; class VariantEditor; @@ -81,7 +82,7 @@ public slots: void retranslateUi(); Document *mDocument = nullptr; - QObject *mPropertiesObject = nullptr; + ObjectProperties *mPropertiesObject = nullptr; VariantEditor *mPropertyBrowser; std::unique_ptr mDefaultEditorFactory; QAction *mActionAddProperty; diff --git a/src/tiled/propertyeditorwidgets.cpp b/src/tiled/propertyeditorwidgets.cpp index 36e899a3b0..3bb86cb855 100644 --- a/src/tiled/propertyeditorwidgets.cpp +++ b/src/tiled/propertyeditorwidgets.cpp @@ -41,6 +41,9 @@ SpinBox::SpinBox(QWidget *parent) setRange(std::numeric_limits::lowest(), std::numeric_limits::max()); + // Don't respond to keyboard input immediately. + setKeyboardTracking(false); + // Allow the widget to shrink horizontally. setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); } @@ -64,6 +67,9 @@ DoubleSpinBox::DoubleSpinBox(QWidget *parent) // Increase possible precision. setDecimals(9); + // Don't respond to keyboard input immediately. + setKeyboardTracking(false); + // Allow the widget to shrink horizontally. setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); } @@ -88,36 +94,114 @@ QString DoubleSpinBox::textFromValue(double val) const } -SizeEdit::SizeEdit(QWidget *parent) +ResponsivePairswiseWidget::ResponsivePairswiseWidget(QWidget *parent) : QWidget(parent) - , m_widthLabel(new QLabel(QStringLiteral("W"), this)) - , m_heightLabel(new QLabel(QStringLiteral("H"), this)) - , m_widthSpinBox(new SpinBox(this)) - , m_heightSpinBox(new SpinBox(this)) { - m_widthLabel->setAlignment(Qt::AlignCenter); - m_heightLabel->setAlignment(Qt::AlignCenter); - auto layout = new QGridLayout(this); layout->setContentsMargins(QMargins()); layout->setColumnStretch(1, 1); - layout->setColumnStretch(3, 1); layout->setSpacing(Utils::dpiScaled(3)); +} +void ResponsivePairswiseWidget::setWidgetPairs(const QVector &widgetPairs) +{ const int horizontalMargin = Utils::dpiScaled(3); - m_widthLabel->setContentsMargins(horizontalMargin, 0, horizontalMargin, 0); - m_heightLabel->setContentsMargins(horizontalMargin, 0, horizontalMargin, 0); - layout->addWidget(m_widthLabel, 0, 0); - layout->addWidget(m_widthSpinBox, 0, 1); - layout->addWidget(m_heightLabel, 0, 2); - layout->addWidget(m_heightSpinBox, 0, 3); + for (auto &pair : widgetPairs) { + pair.label->setAlignment(Qt::AlignCenter); + pair.label->setContentsMargins(horizontalMargin, 0, horizontalMargin, 0); + } + + m_widgetPairs = widgetPairs; + + addWidgetsToLayout(); +} + +void ResponsivePairswiseWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + + const auto orientation = event->size().width() < minimumHorizontalWidth() + ? Qt::Vertical : Qt::Horizontal; + + if (m_orientation != orientation) { + m_orientation = orientation; + + auto layout = this->layout(); + + // Remove all widgets from layout, without deleting them + for (auto &pair : m_widgetPairs) { + layout->removeWidget(pair.label); + layout->removeWidget(pair.widget); + } + + addWidgetsToLayout(); + + // This avoids flickering when the layout changes + layout->activate(); + } +} + +void ResponsivePairswiseWidget::addWidgetsToLayout() +{ + auto layout = qobject_cast(this->layout()); + + const int maxColumns = m_orientation == Qt::Horizontal ? 4 : 2; + int row = 0; + int column = 0; + + for (auto &pair : m_widgetPairs) { + layout->addWidget(pair.label, row, column); + layout->addWidget(pair.widget, row, column + 1); + column += 2; + + if (column == maxColumns) { + column = 0; + ++row; + } + } + + layout->setColumnStretch(3, m_orientation == Qt::Horizontal ? 1 : 0); +} + +int ResponsivePairswiseWidget::minimumHorizontalWidth() const +{ + const int spacing = layout()->spacing(); + int sum = 0; + int minimum = 0; + int index = 0; + + for (auto &pair : m_widgetPairs) { + sum += (pair.label->minimumSizeHint().width() + + pair.widget->minimumSizeHint().width() + + spacing * 2); + + if (++index % 2 == 0) { + minimum = std::max(sum - spacing, minimum); + sum = 0; + } + } + + return minimum; +} + + +SizeEdit::SizeEdit(QWidget *parent) + : ResponsivePairswiseWidget(parent) + , m_widthLabel(new QLabel(QStringLiteral("W"), this)) + , m_heightLabel(new QLabel(QStringLiteral("H"), this)) + , m_widthSpinBox(new SpinBox(this)) + , m_heightSpinBox(new SpinBox(this)) +{ + setWidgetPairs({ + { m_widthLabel, m_widthSpinBox }, + { m_heightLabel, m_heightSpinBox }, + }); connect(m_widthSpinBox, qOverload(&QSpinBox::valueChanged), this, &SizeEdit::valueChanged); connect(m_heightSpinBox, qOverload(&QSpinBox::valueChanged), this, &SizeEdit::valueChanged); } - void SizeEdit::setValue(const QSize &size) { m_widthSpinBox->setValue(size.width()); @@ -126,53 +210,136 @@ void SizeEdit::setValue(const QSize &size) QSize SizeEdit::value() const { - return QSize(m_widthSpinBox->value(), m_heightSpinBox->value()); + return QSize(m_widthSpinBox->value(), + m_heightSpinBox->value()); } -void SizeEdit::resizeEvent(QResizeEvent *event) + +SizeFEdit::SizeFEdit(QWidget *parent) + : ResponsivePairswiseWidget(parent) + , m_widthLabel(new QLabel(QStringLiteral("W"), this)) + , m_heightLabel(new QLabel(QStringLiteral("H"), this)) + , m_widthSpinBox(new DoubleSpinBox(this)) + , m_heightSpinBox(new DoubleSpinBox(this)) { - QWidget::resizeEvent(event); + setWidgetPairs({ + { m_widthLabel, m_widthSpinBox }, + { m_heightLabel, m_heightSpinBox }, + }); - const auto orientation = event->size().width() < minimumHorizontalWidth() - ? Qt::Vertical : Qt::Horizontal; + connect(m_widthSpinBox, qOverload(&QDoubleSpinBox::valueChanged), this, &SizeFEdit::valueChanged); + connect(m_heightSpinBox, qOverload(&QDoubleSpinBox::valueChanged), this, &SizeFEdit::valueChanged); +} - if (m_orientation != orientation) { - m_orientation = orientation; +void SizeFEdit::setValue(const QSizeF &size) +{ + m_widthSpinBox->setValue(size.width()); + m_heightSpinBox->setValue(size.height()); +} - auto layout = qobject_cast(this->layout()); +QSizeF SizeFEdit::value() const +{ + return QSizeF(m_widthSpinBox->value(), + m_heightSpinBox->value()); +} - // Remove all widgets from layout, without deleting them - layout->removeWidget(m_widthLabel); - layout->removeWidget(m_widthSpinBox); - layout->removeWidget(m_heightLabel); - layout->removeWidget(m_heightSpinBox); - - if (orientation == Qt::Horizontal) { - layout->addWidget(m_widthLabel, 0, 0); - layout->addWidget(m_widthSpinBox, 0, 1); - layout->addWidget(m_heightLabel, 0, 2); - layout->addWidget(m_heightSpinBox, 0, 3); - layout->setColumnStretch(3, 1); - } else { - layout->addWidget(m_widthLabel, 0, 0); - layout->addWidget(m_widthSpinBox, 0, 1); - layout->addWidget(m_heightLabel, 1, 0); - layout->addWidget(m_heightSpinBox, 1, 1); - layout->setColumnStretch(3, 0); - } - // this avoids flickering when the layout changes - layout->activate(); - } +PointEdit::PointEdit(QWidget *parent) + : ResponsivePairswiseWidget(parent) + , m_xLabel(new QLabel(QStringLiteral("X"), this)) + , m_yLabel(new QLabel(QStringLiteral("Y"), this)) + , m_xSpinBox(new SpinBox(this)) + , m_ySpinBox(new SpinBox(this)) +{ + setWidgetPairs({ + { m_xLabel, m_xSpinBox }, + { m_yLabel, m_ySpinBox }, + }); + + connect(m_xSpinBox, qOverload(&QSpinBox::valueChanged), this, &PointEdit::valueChanged); + connect(m_ySpinBox, qOverload(&QSpinBox::valueChanged), this, &PointEdit::valueChanged); +} + +void PointEdit::setValue(const QPoint &point) +{ + m_xSpinBox->setValue(point.x()); + m_ySpinBox->setValue(point.y()); +} + +QPoint PointEdit::value() const +{ + return QPoint(m_xSpinBox->value(), + m_ySpinBox->value()); +} + + +PointFEdit::PointFEdit(QWidget *parent) + : ResponsivePairswiseWidget(parent) + , m_xLabel(new QLabel(QStringLiteral("X"), this)) + , m_yLabel(new QLabel(QStringLiteral("Y"), this)) + , m_xSpinBox(new DoubleSpinBox(this)) + , m_ySpinBox(new DoubleSpinBox(this)) +{ + setWidgetPairs({ + { m_xLabel, m_xSpinBox }, + { m_yLabel, m_ySpinBox }, + }); + + connect(m_xSpinBox, qOverload(&QDoubleSpinBox::valueChanged), this, &PointFEdit::valueChanged); + connect(m_ySpinBox, qOverload(&QDoubleSpinBox::valueChanged), this, &PointFEdit::valueChanged); +} + +void PointFEdit::setValue(const QPointF &point) +{ + m_xSpinBox->setValue(point.x()); + m_ySpinBox->setValue(point.y()); +} + +QPointF PointFEdit::value() const +{ + return QPointF(m_xSpinBox->value(), + m_ySpinBox->value()); +} + + +RectFEdit::RectFEdit(QWidget *parent) + : ResponsivePairswiseWidget(parent) + , m_xLabel(new QLabel(QStringLiteral("X"), this)) + , m_yLabel(new QLabel(QStringLiteral("Y"), this)) + , m_widthLabel(new QLabel(QStringLiteral("W"), this)) + , m_heightLabel(new QLabel(QStringLiteral("H"), this)) + , m_xSpinBox(new DoubleSpinBox(this)) + , m_ySpinBox(new DoubleSpinBox(this)) + , m_widthSpinBox(new DoubleSpinBox(this)) + , m_heightSpinBox(new DoubleSpinBox(this)) +{ + setWidgetPairs({ + { m_xLabel, m_xSpinBox }, + { m_yLabel, m_ySpinBox }, + { m_widthLabel, m_widthSpinBox }, + { m_heightLabel, m_heightSpinBox }, + }); + + connect(m_xSpinBox, qOverload(&QDoubleSpinBox::valueChanged), this, &RectFEdit::valueChanged); + connect(m_ySpinBox, qOverload(&QDoubleSpinBox::valueChanged), this, &RectFEdit::valueChanged); + connect(m_widthSpinBox, qOverload(&QDoubleSpinBox::valueChanged), this, &RectFEdit::valueChanged); + connect(m_heightSpinBox, qOverload(&QDoubleSpinBox::valueChanged), this, &RectFEdit::valueChanged); +} + +void RectFEdit::setValue(const QRectF &rect) +{ + m_xSpinBox->setValue(rect.x()); + m_ySpinBox->setValue(rect.y()); + m_widthSpinBox->setValue(rect.width()); + m_heightSpinBox->setValue(rect.height()); } -int SizeEdit::minimumHorizontalWidth() const +QRectF RectFEdit::value() const { - return m_widthLabel->minimumSizeHint().width() + - m_widthSpinBox->minimumSizeHint().width() + - m_heightLabel->minimumSizeHint().width() + - m_heightSpinBox->minimumSizeHint().width() + - layout()->spacing() * 3; + return QRectF(m_xSpinBox->value(), + m_ySpinBox->value(), + m_widthSpinBox->value(), + m_heightSpinBox->value()); } diff --git a/src/tiled/propertyeditorwidgets.h b/src/tiled/propertyeditorwidgets.h index f33eaf7fbd..342b0b505a 100644 --- a/src/tiled/propertyeditorwidgets.h +++ b/src/tiled/propertyeditorwidgets.h @@ -41,10 +41,39 @@ class DoubleSpinBox : public QDoubleSpinBox QString textFromValue(double val) const override; }; +/** + * A widget that shows label/widget pairs, wrapping them either two per row + * or each on their own row, depending on the available space. + */ +class ResponsivePairswiseWidget : public QWidget +{ + Q_OBJECT + +public: + struct WidgetPair { + QLabel *label; + QWidget *widget; + }; + + ResponsivePairswiseWidget(QWidget *parent = nullptr); + + void setWidgetPairs(const QVector &widgetPairs); + +protected: + void resizeEvent(QResizeEvent *event) override; + +private: + void addWidgetsToLayout(); + int minimumHorizontalWidth() const; + + Qt::Orientation m_orientation = Qt::Horizontal; + QVector m_widgetPairs; +}; + /** * A widget for editing a QSize value. */ -class SizeEdit : public QWidget +class SizeEdit : public ResponsivePairswiseWidget { Q_OBJECT Q_PROPERTY(QSize value READ value WRITE setValue NOTIFY valueChanged FINAL) @@ -59,17 +88,112 @@ class SizeEdit : public QWidget void valueChanged(); private: - void resizeEvent(QResizeEvent *event) override; - - int minimumHorizontalWidth() const; - - Qt::Orientation m_orientation = Qt::Horizontal; QLabel *m_widthLabel; QLabel *m_heightLabel; SpinBox *m_widthSpinBox; SpinBox *m_heightSpinBox; }; +/** + * A widget for editing a QSizeF value. + */ +class SizeFEdit : public ResponsivePairswiseWidget +{ + Q_OBJECT + Q_PROPERTY(QSizeF value READ value WRITE setValue NOTIFY valueChanged FINAL) + +public: + SizeFEdit(QWidget *parent = nullptr); + + void setValue(const QSizeF &size); + QSizeF value() const; + +signals: + void valueChanged(); + +private: + QLabel *m_widthLabel; + QLabel *m_heightLabel; + DoubleSpinBox *m_widthSpinBox; + DoubleSpinBox *m_heightSpinBox; +}; + +/** + * A widget for editing a QPoint value. + */ +class PointEdit : public ResponsivePairswiseWidget +{ + Q_OBJECT + Q_PROPERTY(QPoint value READ value WRITE setValue NOTIFY valueChanged FINAL) + +public: + PointEdit(QWidget *parent = nullptr); + + void setValue(const QPoint &size); + QPoint value() const; + +signals: + void valueChanged(); + +private: + QLabel *m_xLabel; + QLabel *m_yLabel; + SpinBox *m_xSpinBox; + SpinBox *m_ySpinBox; +}; + +/** + * A widget for editing a QPointF value. + */ +class PointFEdit : public ResponsivePairswiseWidget +{ + Q_OBJECT + Q_PROPERTY(QPointF value READ value WRITE setValue NOTIFY valueChanged FINAL) + +public: + PointFEdit(QWidget *parent = nullptr); + + void setValue(const QPointF &size); + QPointF value() const; + +signals: + void valueChanged(); + +private: + QLabel *m_xLabel; + QLabel *m_yLabel; + DoubleSpinBox *m_xSpinBox; + DoubleSpinBox *m_ySpinBox; +}; + +/** + * A widget for editing a QRectF value. + */ +class RectFEdit : public ResponsivePairswiseWidget +{ + Q_OBJECT + Q_PROPERTY(QRectF value READ value WRITE setValue NOTIFY valueChanged FINAL) + +public: + RectFEdit(QWidget *parent = nullptr); + + void setValue(const QRectF &size); + QRectF value() const; + +signals: + void valueChanged(); + +private: + QLabel *m_xLabel; + QLabel *m_yLabel; + QLabel *m_widthLabel; + QLabel *m_heightLabel; + DoubleSpinBox *m_xSpinBox; + DoubleSpinBox *m_ySpinBox; + DoubleSpinBox *m_widthSpinBox; + DoubleSpinBox *m_heightSpinBox; +}; + /** * A label that elides its text if there is not enough space. * diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index 854b52d5c7..66ef71b144 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -111,45 +112,41 @@ class UrlEditorFactory : public EditorFactory QString m_filter; }; -class IntEditorFactory : public EditorFactory +QWidget *IntEditorFactory::createEditor(Property *property, QWidget *parent) { -public: - QWidget *createEditor(Property *property, QWidget *parent) override - { - auto editor = new SpinBox(parent); - auto syncEditor = [=] { - const QSignalBlocker blocker(editor); - editor->setValue(property->value().toInt()); - }; - syncEditor(); + auto editor = new SpinBox(parent); + auto syncEditor = [=] { + const QSignalBlocker blocker(editor); + editor->setValue(property->value().toInt()); + }; + syncEditor(); - QObject::connect(property, &Property::valueChanged, editor, syncEditor); - QObject::connect(editor, qOverload(&SpinBox::valueChanged), - property, &Property::setValue); + QObject::connect(property, &Property::valueChanged, editor, syncEditor); + QObject::connect(editor, qOverload(&SpinBox::valueChanged), + property, &Property::setValue); + + return editor; +} - return editor; - } -}; -class FloatEditorFactory : public EditorFactory +QWidget *FloatEditorFactory::createEditor(Property *property, QWidget *parent) { -public: - QWidget *createEditor(Property *property, QWidget *parent) override - { - auto editor = new DoubleSpinBox(parent); - auto syncEditor = [=] { - const QSignalBlocker blocker(editor); - editor->setValue(property->value().toDouble()); - }; - syncEditor(); + auto editor = new DoubleSpinBox(parent); + editor->setSuffix(m_suffix); - QObject::connect(property, &Property::valueChanged, editor, syncEditor); - QObject::connect(editor, qOverload(&DoubleSpinBox::valueChanged), - property, &Property::setValue); + auto syncEditor = [=] { + const QSignalBlocker blocker(editor); + editor->setValue(property->value().toDouble()); + }; + syncEditor(); + + QObject::connect(property, &Property::valueChanged, editor, syncEditor); + QObject::connect(editor, qOverload(&DoubleSpinBox::valueChanged), + property, &Property::setValue); + + return editor; +} - return editor; - } -}; class BoolEditorFactory : public EditorFactory { @@ -175,99 +172,50 @@ class BoolEditorFactory : public EditorFactory } }; -// todo: implement responsive layout (see SizeEdit) class PointEditorFactory : public EditorFactory { public: QWidget *createEditor(Property *property, QWidget *parent) override { - auto editor = new QWidget(parent); - auto horizontalLayout = new QHBoxLayout(editor); - horizontalLayout->setContentsMargins(QMargins()); - - auto xLabel = new QLabel(QStringLiteral("X"), editor); - horizontalLayout->addWidget(xLabel, 0, Qt::AlignRight); - - auto xSpinBox = new SpinBox(editor); - xLabel->setBuddy(xSpinBox); - horizontalLayout->addWidget(xSpinBox, 1); - - auto yLabel = new QLabel(QStringLiteral("Y"), editor); - horizontalLayout->addWidget(yLabel, 0, Qt::AlignRight); - - auto ySpinBox = new SpinBox(editor); - yLabel->setBuddy(ySpinBox); - horizontalLayout->addWidget(ySpinBox, 1); - - auto syncEditor = [=] { - const QSignalBlocker xBlocker(xSpinBox); - const QSignalBlocker yBlocker(ySpinBox); - const auto point = property->value().toPoint(); - xSpinBox->setValue(point.x()); - ySpinBox->setValue(point.y()); - }; - auto syncProperty = [=] { - property->setValue(QPoint(xSpinBox->value(), ySpinBox->value())); + auto editor = new PointEdit(parent); + auto syncEditor = [property, editor] { + const QSignalBlocker blocker(editor); + editor->setValue(property->value().toPoint()); }; syncEditor(); QObject::connect(property, &Property::valueChanged, editor, syncEditor); - QObject::connect(xSpinBox, qOverload(&SpinBox::valueChanged), - property, syncProperty); - QObject::connect(ySpinBox, qOverload(&SpinBox::valueChanged), - property, syncProperty); + QObject::connect(editor, &PointEdit::valueChanged, property, + [property, editor] { + property->setValue(editor->value()); + }); return editor; } }; -// todo: implement responsive layout (see SizeEdit) class PointFEditorFactory : public EditorFactory { public: QWidget *createEditor(Property *property, QWidget *parent) override { - auto editor = new QWidget(parent); - auto horizontalLayout = new QHBoxLayout(editor); - horizontalLayout->setContentsMargins(QMargins()); - - auto xLabel = new QLabel(QStringLiteral("X"), editor); - horizontalLayout->addWidget(xLabel, 0, Qt::AlignRight); - - auto xSpinBox = new DoubleSpinBox(editor); - xLabel->setBuddy(xSpinBox); - horizontalLayout->addWidget(xSpinBox, 1); - - auto yLabel = new QLabel(QStringLiteral("Y"), editor); - horizontalLayout->addWidget(yLabel, 0, Qt::AlignRight); - - auto ySpinBox = new DoubleSpinBox(editor); - yLabel->setBuddy(ySpinBox); - horizontalLayout->addWidget(ySpinBox, 1); - - auto syncEditor = [=] { - const QSignalBlocker xBlocker(xSpinBox); - const QSignalBlocker yBlocker(ySpinBox); - const auto point = property->value().toPointF(); - xSpinBox->setValue(point.x()); - ySpinBox->setValue(point.y()); - }; - auto syncProperty = [=] { - property->setValue(QPointF(xSpinBox->value(), ySpinBox->value())); + auto editor = new PointFEdit(parent); + auto syncEditor = [property, editor] { + const QSignalBlocker blocker(editor); + editor->setValue(property->value().toPointF()); }; syncEditor(); QObject::connect(property, &Property::valueChanged, editor, syncEditor); - QObject::connect(xSpinBox, qOverload(&DoubleSpinBox::valueChanged), - property, syncProperty); - QObject::connect(ySpinBox, qOverload(&DoubleSpinBox::valueChanged), - property, syncProperty); + QObject::connect(editor, &PointFEdit::valueChanged, property, + [property, editor] { + property->setValue(editor->value()); + }); return editor; } }; - class SizeEditorFactory : public EditorFactory { public: @@ -290,75 +238,45 @@ class SizeEditorFactory : public EditorFactory } }; -// todo: implement responsive layout (see SizeEdit) -class RectFEditorFactory : public EditorFactory +class SizeFEditorFactory : public EditorFactory { public: QWidget *createEditor(Property *property, QWidget *parent) override { - auto editor = new QWidget(parent); - auto gridLayout = new QGridLayout(editor); - gridLayout->setContentsMargins(QMargins()); - gridLayout->setColumnStretch(4, 1); - - auto xLabel = new QLabel(QStringLiteral("X"), editor); - gridLayout->addWidget(xLabel, 0, 0, Qt::AlignRight); - - auto xSpinBox = new DoubleSpinBox(editor); - xLabel->setBuddy(xSpinBox); - gridLayout->addWidget(xSpinBox, 0, 1); - - auto yLabel = new QLabel(QStringLiteral("Y"), editor); - gridLayout->addWidget(yLabel, 0, 2, Qt::AlignRight); - - auto ySpinBox = new DoubleSpinBox(editor); - yLabel->setBuddy(ySpinBox); - gridLayout->addWidget(ySpinBox, 0, 3); - - auto widthLabel = new QLabel(QStringLiteral("W"), editor); - widthLabel->setToolTip(tr("Width")); - gridLayout->addWidget(widthLabel, 1, 0, Qt::AlignRight); - - auto widthSpinBox = new DoubleSpinBox(editor); - widthLabel->setBuddy(widthSpinBox); - gridLayout->addWidget(widthSpinBox, 1, 1); + auto editor = new SizeFEdit(parent); + auto syncEditor = [property, editor] { + const QSignalBlocker blocker(editor); + editor->setValue(property->value().toSizeF()); + }; + syncEditor(); - auto heightLabel = new QLabel(QStringLiteral("H"), editor); - heightLabel->setToolTip(tr("Height")); - gridLayout->addWidget(heightLabel, 1, 2, Qt::AlignRight); + QObject::connect(property, &Property::valueChanged, editor, syncEditor); + QObject::connect(editor, &SizeFEdit::valueChanged, property, + [property, editor] { + property->setValue(editor->value()); + }); - auto heightSpinBox = new DoubleSpinBox(editor); - heightLabel->setBuddy(heightSpinBox); - gridLayout->addWidget(heightSpinBox, 1, 3); + return editor; + } +}; - auto syncEditor = [=] { - const QSignalBlocker xBlocker(xSpinBox); - const QSignalBlocker yBlocker(ySpinBox); - const QSignalBlocker widthBlocker(widthSpinBox); - const QSignalBlocker heightBlocker(heightSpinBox); - const auto rect = property->value().toRectF(); - xSpinBox->setValue(rect.x()); - ySpinBox->setValue(rect.y()); - widthSpinBox->setValue(rect.width()); - heightSpinBox->setValue(rect.height()); - }; - auto syncProperty = [=] { - property->setValue(QRectF(xSpinBox->value(), - ySpinBox->value(), - widthSpinBox->value(), - heightSpinBox->value())); +class RectFEditorFactory : public EditorFactory +{ +public: + QWidget *createEditor(Property *property, QWidget *parent) override + { + auto editor = new RectFEdit(parent); + auto syncEditor = [property, editor] { + const QSignalBlocker blocker(editor); + editor->setValue(property->value().toRectF()); }; syncEditor(); QObject::connect(property, &Property::valueChanged, editor, syncEditor); - QObject::connect(xSpinBox, qOverload(&DoubleSpinBox::valueChanged), - property, syncProperty); - QObject::connect(ySpinBox, qOverload(&DoubleSpinBox::valueChanged), - property, syncProperty); - QObject::connect(widthSpinBox, qOverload(&DoubleSpinBox::valueChanged), - property, syncProperty); - QObject::connect(heightSpinBox, qOverload(&DoubleSpinBox::valueChanged), - property, syncProperty); + QObject::connect(editor, &RectFEdit::valueChanged, property, + [property, editor] { + property->setValue(editor->value()); + }); return editor; } @@ -387,6 +305,132 @@ class ColorEditorFactory : public EditorFactory } }; +class FontEditorFactory : public EditorFactory +{ +public: + QWidget *createEditor(Property *property, QWidget *parent) override + { + auto editor = new QWidget(parent); + auto layout = new QVBoxLayout(editor); + auto fontComboBox = new QFontComboBox(editor); + auto sizeSpinBox = new QSpinBox(editor); + auto boldCheckBox = new QCheckBox(tr("Bold"), editor); + auto italicCheckBox = new QCheckBox(tr("Italic"), editor); + auto underlineCheckBox = new QCheckBox(tr("Underline"), editor); + auto strikeoutCheckBox = new QCheckBox(tr("Strikeout"), editor); + auto kerningCheckBox = new QCheckBox(tr("Kerning"), editor); + sizeSpinBox->setRange(1, 999); + sizeSpinBox->setSuffix(tr(" px")); + sizeSpinBox->setKeyboardTracking(false); + layout->setContentsMargins(QMargins()); + layout->setSpacing(Utils::dpiScaled(3)); + layout->addWidget(fontComboBox); + layout->addWidget(sizeSpinBox); + layout->addWidget(boldCheckBox); + layout->addWidget(italicCheckBox); + layout->addWidget(underlineCheckBox); + layout->addWidget(strikeoutCheckBox); + layout->addWidget(kerningCheckBox); + + auto syncEditor = [=] { + const auto font = property->value().value(); + const QSignalBlocker fontBlocker(fontComboBox); + const QSignalBlocker sizeBlocker(sizeSpinBox); + const QSignalBlocker boldBlocker(boldCheckBox); + const QSignalBlocker italicBlocker(italicCheckBox); + const QSignalBlocker underlineBlocker(underlineCheckBox); + const QSignalBlocker strikeoutBlocker(strikeoutCheckBox); + const QSignalBlocker kerningBlocker(kerningCheckBox); + fontComboBox->setCurrentFont(font); + sizeSpinBox->setValue(font.pixelSize()); + boldCheckBox->setChecked(font.bold()); + italicCheckBox->setChecked(font.italic()); + underlineCheckBox->setChecked(font.underline()); + strikeoutCheckBox->setChecked(font.strikeOut()); + kerningCheckBox->setChecked(font.kerning()); + }; + + auto syncProperty = [=] { + auto font = fontComboBox->currentFont(); + font.setPixelSize(sizeSpinBox->value()); + font.setBold(boldCheckBox->isChecked()); + font.setItalic(italicCheckBox->isChecked()); + font.setUnderline(underlineCheckBox->isChecked()); + font.setStrikeOut(strikeoutCheckBox->isChecked()); + font.setKerning(kerningCheckBox->isChecked()); + property->setValue(font); + }; + + syncEditor(); + + QObject::connect(property, &Property::valueChanged, fontComboBox, syncEditor); + QObject::connect(fontComboBox, &QFontComboBox::currentFontChanged, property, syncProperty); + QObject::connect(sizeSpinBox, qOverload(&QSpinBox::valueChanged), property, syncProperty); + QObject::connect(boldCheckBox, &QCheckBox::toggled, property, syncProperty); + QObject::connect(italicCheckBox, &QCheckBox::toggled, property, syncProperty); + QObject::connect(underlineCheckBox, &QCheckBox::toggled, property, syncProperty); + QObject::connect(strikeoutCheckBox, &QCheckBox::toggled, property, syncProperty); + QObject::connect(kerningCheckBox, &QCheckBox::toggled, property, syncProperty); + + return editor; + } +}; + +class AlignmentEditorFactory : public EditorFactory +{ +public: + QWidget *createEditor(Property *property, QWidget *parent) override + { + auto editor = new QWidget(parent); + auto layout = new QGridLayout(editor); + layout->setContentsMargins(QMargins()); + layout->setSpacing(Utils::dpiScaled(3)); + + auto horizontalLabel = new ElidingLabel(tr("Horizontal"), editor); + layout->addWidget(horizontalLabel, 0, 0); + + auto verticalLabel = new ElidingLabel(tr("Vertical"), editor); + layout->addWidget(verticalLabel, 1, 0); + + auto horizontalComboBox = new QComboBox(editor); + horizontalComboBox->addItem(tr("Left"), Qt::AlignLeft); + horizontalComboBox->addItem(tr("Center"), Qt::AlignHCenter); + horizontalComboBox->addItem(tr("Right"), Qt::AlignRight); + horizontalComboBox->addItem(tr("Justify"), Qt::AlignJustify); + layout->addWidget(horizontalComboBox, 0, 1); + + auto verticalComboBox = new QComboBox(editor); + verticalComboBox->addItem(tr("Top"), Qt::AlignTop); + verticalComboBox->addItem(tr("Center"), Qt::AlignVCenter); + verticalComboBox->addItem(tr("Bottom"), Qt::AlignBottom); + layout->addWidget(verticalComboBox, 1, 1); + + layout->setColumnStretch(1, 1); + + auto syncEditor = [=] { + const QSignalBlocker horizontalBlocker(horizontalComboBox); + const QSignalBlocker verticalBlocker(verticalComboBox); + const auto alignment = property->value().value(); + horizontalComboBox->setCurrentIndex(horizontalComboBox->findData(static_cast(alignment & Qt::AlignHorizontal_Mask))); + verticalComboBox->setCurrentIndex(verticalComboBox->findData(static_cast(alignment & Qt::AlignVertical_Mask))); + }; + + auto syncProperty = [=] { + const Qt::Alignment alignment(horizontalComboBox->currentData().toInt() | + verticalComboBox->currentData().toInt()); + property->setValue(QVariant::fromValue(alignment)); + }; + + syncEditor(); + + QObject::connect(property, &Property::valueChanged, editor, syncEditor); + QObject::connect(horizontalComboBox, qOverload(&QComboBox::currentIndexChanged), property, syncProperty); + QObject::connect(verticalComboBox, qOverload(&QComboBox::currentIndexChanged), property, syncProperty); + + return editor; + } +}; + ValueProperty::ValueProperty(const QString &name, const QVariant &value, @@ -425,7 +469,7 @@ VariantEditor::VariantEditor(QWidget *parent) : QScrollArea(parent) { m_widget = new QWidget; - m_widget->setBackgroundRole(QPalette::Base); + m_widget->setBackgroundRole(QPalette::AlternateBase); auto verticalLayout = new QVBoxLayout(m_widget); m_gridLayout = new QGridLayout; verticalLayout->addLayout(m_gridLayout); @@ -555,7 +599,6 @@ QWidget *VariantEditor::createEditor(Property *property) } - EnumEditorFactory::EnumEditorFactory(const QStringList &enumNames, const QList &enumValues) : m_enumNamesModel(enumNames) @@ -605,12 +648,15 @@ ValueTypeEditorFactory::ValueTypeEditorFactory() registerEditorFactory(QMetaType::Double, std::make_unique()); registerEditorFactory(QMetaType::Int, std::make_unique()); registerEditorFactory(QMetaType::QColor, std::make_unique()); + registerEditorFactory(QMetaType::QFont, std::make_unique()); registerEditorFactory(QMetaType::QPoint, std::make_unique()); registerEditorFactory(QMetaType::QPointF, std::make_unique()); registerEditorFactory(QMetaType::QRectF, std::make_unique()); registerEditorFactory(QMetaType::QSize, std::make_unique()); + registerEditorFactory(QMetaType::QSizeF, std::make_unique()); registerEditorFactory(QMetaType::QString, std::make_unique()); registerEditorFactory(QMetaType::QUrl, std::make_unique()); + registerEditorFactory(qMetaTypeId(), std::make_unique()); } void ValueTypeEditorFactory::registerEditorFactory(int type, std::unique_ptr factory) diff --git a/src/tiled/varianteditor.h b/src/tiled/varianteditor.h index 8099181347..3b282cb3a1 100644 --- a/src/tiled/varianteditor.h +++ b/src/tiled/varianteditor.h @@ -89,6 +89,21 @@ class EditorFactory virtual QWidget *createEditor(Property *property, QWidget *parent) = 0; }; +struct IntEditorFactory : EditorFactory +{ + QWidget *createEditor(Property *property, QWidget *parent) override; +}; + +struct FloatEditorFactory : EditorFactory +{ + QWidget *createEditor(Property *property, QWidget *parent) override; + + void setSuffix(const QString &suffix) { m_suffix = suffix; } + +private: + QString m_suffix; +}; + /** * An editor factory that creates a combo box for enum properties. */ From 8153799aa8d630f04f6b9f975121e4b779a59ded Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Fri, 6 Sep 2024 13:28:58 +0200 Subject: [PATCH 16/36] Implemented Tile, WangSet and WangColor properties Now all built-in properties are present, apart from some loose ends. * Added RectEdit / RectEditorFactory (QRect values) * Added Property::toolTip (set on label and editor, but currently not functional on label due to the eliding tool tip logic) * Added change events to fix updating of WangSet and WangColor properties (was already broken with old framework) --- src/libtiled/wangset.h | 3 +- src/tiled/changeevents.h | 32 ++- src/tiled/colorbutton.cpp | 1 + src/tiled/propertieswidget.cpp | 348 +++++++++++++++++++++++++++- src/tiled/propertyeditorwidgets.cpp | 41 ++++ src/tiled/propertyeditorwidgets.h | 28 +++ src/tiled/tilesetview.cpp | 2 +- src/tiled/tilesetwangsetmodel.cpp | 18 +- src/tiled/tilesetwangsetmodel.h | 1 + src/tiled/varianteditor.cpp | 34 +++ src/tiled/varianteditor.h | 16 +- src/tiled/wangcolormodel.cpp | 15 ++ src/tiled/wangcolormodel.h | 1 + src/tiled/wangdock.cpp | 2 +- 14 files changed, 530 insertions(+), 12 deletions(-) diff --git a/src/libtiled/wangset.h b/src/libtiled/wangset.h index 2955762c0a..c2df394b53 100644 --- a/src/libtiled/wangset.h +++ b/src/libtiled/wangset.h @@ -424,5 +424,6 @@ TILEDSHARED_EXPORT WangSet::Type wangSetTypeFromString(const QString &); } // namespace Tiled -Q_DECLARE_METATYPE(Tiled::WangSet*) Q_DECLARE_METATYPE(Tiled::WangId) +Q_DECLARE_METATYPE(Tiled::WangSet*) +Q_DECLARE_METATYPE(Tiled::WangSet::Type) diff --git a/src/tiled/changeevents.h b/src/tiled/changeevents.h index 0dbf599280..313bb36dfa 100644 --- a/src/tiled/changeevents.h +++ b/src/tiled/changeevents.h @@ -61,6 +61,7 @@ class ChangeEvent WangSetRemoved, WangSetChanged, WangColorAboutToBeRemoved, + WangColorChanged, } type; protected: @@ -277,17 +278,20 @@ class WangSetChangeEvent : public ChangeEvent { public: enum WangSetProperty { - TypeProperty = 1 << 0, + NameProperty, + TypeProperty, + ImageProperty, + ColorCountProperty, }; - WangSetChangeEvent(WangSet *wangSet, int properties) + WangSetChangeEvent(WangSet *wangSet, WangSetProperty property) : ChangeEvent(WangSetChanged) , wangSet(wangSet) - , properties(properties) + , property(property) {} WangSet *wangSet; - int properties; + WangSetProperty property; }; class WangColorEvent : public ChangeEvent @@ -303,4 +307,24 @@ class WangColorEvent : public ChangeEvent int color; }; +class WangColorChangeEvent : public ChangeEvent +{ +public: + enum WangColorProperty { + NameProperty, + ColorProperty, + ImageProperty, + ProbabilityProperty, + }; + + WangColorChangeEvent(WangColor *wangColor, WangColorProperty property) + : ChangeEvent(WangColorChanged) + , wangColor(wangColor) + , property(property) + {} + + WangColor *wangColor; + WangColorProperty property; +}; + } // namespace Tiled diff --git a/src/tiled/colorbutton.cpp b/src/tiled/colorbutton.cpp index 85d39307c9..33f87c5406 100644 --- a/src/tiled/colorbutton.cpp +++ b/src/tiled/colorbutton.cpp @@ -75,6 +75,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())); } diff --git a/src/tiled/propertieswidget.cpp b/src/tiled/propertieswidget.cpp index d1f741ab69..bc416685b8 100644 --- a/src/tiled/propertieswidget.cpp +++ b/src/tiled/propertieswidget.cpp @@ -28,6 +28,10 @@ #include "changemapproperty.h" #include "changeobjectgroupproperties.h" #include "changeproperties.h" +#include "changetile.h" +#include "changetileimagesource.h" +#include "changewangcolordata.h" +#include "changewangsetdata.h" #include "clipboardmanager.h" #include "compression.h" #include "mapdocument.h" @@ -39,6 +43,7 @@ #include "tilesetdocument.h" #include "utils.h" #include "varianteditor.h" +#include "wangoverlay.h" #include #include @@ -1339,6 +1344,317 @@ class MapObjectProperties : public ObjectProperties std::unique_ptr mDegreesEditorFactory; }; +class TileProperties : public ObjectProperties +{ + Q_OBJECT + +public: + TileProperties(Document *document, Tile *object, ValueTypeEditorFactory *editorFactory, QObject *parent = nullptr) + : ObjectProperties(document, object, parent) + { + mIdProperty = editorFactory->createProperty( + tr("ID"), + [this]() { return tile()->id(); }, + [](const QVariant &) {}); + mIdProperty->setEnabled(false); + + // todo: apply readableImageFormatsFilter + mImageProperty = editorFactory->createProperty( + tr("Image"), + [this]() { return tile()->imageSource(); }, + [this](const QVariant &value) { + push(new ChangeTileImageSource(tilesetDocument(), + tile(), + value.toUrl())); + }); + + mRectangleProperty = editorFactory->createProperty( + tr("Rectangle"), + [this]() { return tile()->imageRect(); }, + [this](const QVariant &value) { + push(new ChangeTileImageRect(tilesetDocument(), + { tile() }, + { value.toRect() })); + }); + + // todo: minimum value should be 0 + mProbabilityProperty = editorFactory->createProperty( + tr("Probability"), + [this]() { return tile()->probability(); }, + [this](const QVariant &value) { + push(new ChangeTileProbability(tilesetDocument(), + tilesetDocument()->selectedTiles(), + value.toReal())); + }); + mProbabilityProperty->setToolTip(tr("Relative chance this tile will be picked")); + + // annoying... maybe we should somehow always have the relevant TilesetDocument + if (auto tilesetDocument = qobject_cast(document)) { + connect(tilesetDocument, &TilesetDocument::tileImageSourceChanged, + this, &TileProperties::tileImageSourceChanged); + + connect(tilesetDocument, &TilesetDocument::tileProbabilityChanged, + this, &TileProperties::tileProbabilityChanged); + } else if (auto mapDocument = qobject_cast(document)) { + connect(mapDocument, &MapDocument::tileImageSourceChanged, + this, &TileProperties::tileImageSourceChanged); + + connect(mapDocument, &MapDocument::tileProbabilityChanged, + this, &TileProperties::tileProbabilityChanged); + } + + updateEnabledState(); + } + + void populateEditor(VariantEditor *editor) override + { + editor->addHeader(tr("Tile")); + editor->addProperty(mIdProperty); + editor->addProperty(mClassProperty); + editor->addSeparator(); + + if (!tile()->imageSource().isEmpty()) + editor->addProperty(mImageProperty); + + editor->addProperty(mRectangleProperty); + editor->addProperty(mProbabilityProperty); + } + +private: + void tileImageSourceChanged(Tile *tile) + { + if (tile != this->tile()) + return; + emit mImageProperty->valueChanged(); + emit mRectangleProperty->valueChanged(); + } + + void tileProbabilityChanged(Tile *tile) + { + if (tile != this->tile()) + return; + emit mProbabilityProperty->valueChanged(); + } + + void updateEnabledState() + { + const bool hasTilesetDocument = tilesetDocument(); + const auto isCollection = tile()->tileset()->isCollection(); + mClassProperty->setEnabled(hasTilesetDocument); + mImageProperty->setEnabled(hasTilesetDocument && isCollection); + mRectangleProperty->setEnabled(hasTilesetDocument && isCollection); + mProbabilityProperty->setEnabled(hasTilesetDocument); + } + + TilesetDocument *tilesetDocument() const + { + return qobject_cast(mDocument); + } + + Tile *tile() const + { + return static_cast(mObject); + } + + Property *mIdProperty; + Property *mImageProperty; + Property *mRectangleProperty; + Property *mProbabilityProperty; +}; + +class WangSetProperties : public ObjectProperties +{ + Q_OBJECT + +public: + WangSetProperties(Document *document, WangSet *object, + ValueTypeEditorFactory *editorFactory, QObject *parent = nullptr) + : ObjectProperties(document, object, parent) + { + mNameProperty = editorFactory->createProperty( + tr("Name"), + [this]() { return wangSet()->name(); }, + [this](const QVariant &value) { + push(new RenameWangSet(tilesetDocument(), wangSet(), value.toString())); + }); + + mTypeProperty = editorFactory->createProperty( + tr("Type"), + [this]() { return QVariant::fromValue(wangSet()->type()); }, + [this](const QVariant &value) { + push(new ChangeWangSetType(tilesetDocument(), wangSet(), static_cast(value.toInt()))); + }); + + // todo: keep between 0 and WangId::MAX_COLOR_COUNT + mColorCountProperty = editorFactory->createProperty( + tr("Color Count"), + [this]() { return wangSet()->colorCount(); }, + [this](const QVariant &value) { + push(new ChangeWangSetColorCount(tilesetDocument(), + wangSet(), + value.toInt())); + }); + + connect(document, &Document::changed, + this, &WangSetProperties::onChanged); + + updateEnabledState(); + } + + void populateEditor(VariantEditor *editor) override + { + editor->addHeader(tr("Terrain Set")); + editor->addProperty(mNameProperty); + editor->addProperty(mClassProperty); + editor->addSeparator(); + editor->addProperty(mTypeProperty); + editor->addProperty(mColorCountProperty); + } + +private: + void onChanged(const ChangeEvent &event) + { + if (event.type != ChangeEvent::WangSetChanged) + return; + + const auto &wangSetChange = static_cast(event); + if (wangSetChange.wangSet != wangSet()) + return; + + switch (wangSetChange.property) { + case WangSetChangeEvent::NameProperty: + emit mNameProperty->valueChanged(); + break; + case WangSetChangeEvent::TypeProperty: + emit mTypeProperty->valueChanged(); + break; + case WangSetChangeEvent::ImageProperty: + break; + case WangSetChangeEvent::ColorCountProperty: + emit mColorCountProperty->valueChanged(); + break; + } + } + + void updateEnabledState() + { + const bool hasTilesetDocument = tilesetDocument(); + mNameProperty->setEnabled(hasTilesetDocument); + mTypeProperty->setEnabled(hasTilesetDocument); + mColorCountProperty->setEnabled(hasTilesetDocument); + } + + TilesetDocument *tilesetDocument() const + { + return qobject_cast(mDocument); + } + + WangSet *wangSet() + { + return static_cast(mObject); + } + + Property *mNameProperty; + Property *mTypeProperty; + Property *mColorCountProperty; +}; + +class WangColorProperties : public ObjectProperties +{ + Q_OBJECT + +public: + WangColorProperties(Document *document, WangColor *object, + ValueTypeEditorFactory *editorFactory, QObject *parent = nullptr) + : ObjectProperties(document, object, parent) + { + mNameProperty = editorFactory->createProperty( + tr("Name"), + [this]() { return wangColor()->name(); }, + [this](const QVariant &value) { + push(new ChangeWangColorName(tilesetDocument(), wangColor(), value.toString())); + }); + + mColorProperty = editorFactory->createProperty( + tr("Color"), + [this]() { return wangColor()->color(); }, + [this](const QVariant &value) { + push(new ChangeWangColorColor(tilesetDocument(), wangColor(), value.value())); + }); + + // todo: set 0.01 as minimum + mProbabilityProperty = editorFactory->createProperty( + tr("Probability"), + [this]() { return wangColor()->probability(); }, + [this](const QVariant &value) { + push(new ChangeWangColorProbability(tilesetDocument(), wangColor(), value.toReal())); + }); + + connect(document, &Document::changed, + this, &WangColorProperties::onChanged); + + updateEnabledState(); + } + + void populateEditor(VariantEditor *editor) override + { + editor->addHeader(tr("Terrain")); + editor->addProperty(mNameProperty); + editor->addProperty(mClassProperty); + editor->addSeparator(); + editor->addProperty(mColorProperty); + editor->addProperty(mProbabilityProperty); + } + +private: + void onChanged(const ChangeEvent &event) + { + if (event.type != ChangeEvent::WangColorChanged) + return; + + const auto &wangColorChange = static_cast(event); + if (wangColorChange.wangColor != wangColor()) + return; + + switch (wangColorChange.property) { + case WangColorChangeEvent::NameProperty: + emit mNameProperty->valueChanged(); + break; + case WangColorChangeEvent::ColorProperty: + emit mColorProperty->valueChanged(); + break; + case WangColorChangeEvent::ImageProperty: + break; + case WangColorChangeEvent::ProbabilityProperty: + emit mProbabilityProperty->valueChanged(); + break; + } + } + + void updateEnabledState() + { + const bool hasTilesetDocument = tilesetDocument(); + mNameProperty->setEnabled(hasTilesetDocument); + mClassProperty->setEnabled(hasTilesetDocument); + mColorProperty->setEnabled(hasTilesetDocument); + mProbabilityProperty->setEnabled(hasTilesetDocument); + } + + TilesetDocument *tilesetDocument() const + { + return qobject_cast(mDocument); + } + + WangColor *wangColor() + { + return static_cast(mObject); + } + + Property *mNameProperty; + Property *mColorProperty; + Property *mProbabilityProperty; +}; + void PropertiesWidget::currentObjectChanged(Object *object) { @@ -1385,13 +1701,23 @@ void PropertiesWidget::currentObjectChanged(Object *object) mDefaultEditorFactory.get(), this); break; case Object::TileType: - // todo + mPropertiesObject = new TileProperties(mDocument, + static_cast(object), + mDefaultEditorFactory.get(), this); + break; case Object::WangSetType: - // todo + mPropertiesObject = new WangSetProperties(mDocument, + static_cast(object), + mDefaultEditorFactory.get(), this); + break; case Object::WangColorType: - // todo + mPropertiesObject = new WangColorProperties(mDocument, + static_cast(object), + mDefaultEditorFactory.get(), this); + break; case Object::ProjectType: case Object::WorldType: + // these types are currently not handled by the Properties dock break; } } @@ -1886,6 +2212,22 @@ void PropertiesWidget::registerEditorFactories() tr("Top Down"), tr("Index Order"), })); + + auto wangSetTypeEditorFactory = std::make_unique( + QStringList { + tr("Corner"), + tr("Edge"), + tr("Mixed"), + }); + + QMap mWangSetIcons; + mWangSetIcons.insert(WangSet::Corner, wangSetIcon(WangSet::Corner)); + mWangSetIcons.insert(WangSet::Edge, wangSetIcon(WangSet::Edge)); + mWangSetIcons.insert(WangSet::Mixed, wangSetIcon(WangSet::Mixed)); + wangSetTypeEditorFactory->setEnumIcons(mWangSetIcons); + + registerEditorFactory(qMetaTypeId(), + std::move(wangSetTypeEditorFactory)); } void PropertiesWidget::registerEditorFactory(int type, std::unique_ptr factory) diff --git a/src/tiled/propertyeditorwidgets.cpp b/src/tiled/propertyeditorwidgets.cpp index 3bb86cb855..848652b8d4 100644 --- a/src/tiled/propertyeditorwidgets.cpp +++ b/src/tiled/propertyeditorwidgets.cpp @@ -302,6 +302,47 @@ QPointF PointFEdit::value() const } +RectEdit::RectEdit(QWidget *parent) + : ResponsivePairswiseWidget(parent) + , m_xLabel(new QLabel(QStringLiteral("X"), this)) + , m_yLabel(new QLabel(QStringLiteral("Y"), this)) + , m_widthLabel(new QLabel(QStringLiteral("W"), this)) + , m_heightLabel(new QLabel(QStringLiteral("H"), this)) + , m_xSpinBox(new SpinBox(this)) + , m_ySpinBox(new SpinBox(this)) + , m_widthSpinBox(new SpinBox(this)) + , m_heightSpinBox(new SpinBox(this)) +{ + setWidgetPairs({ + { m_xLabel, m_xSpinBox }, + { m_yLabel, m_ySpinBox }, + { m_widthLabel, m_widthSpinBox }, + { m_heightLabel, m_heightSpinBox }, + }); + + connect(m_xSpinBox, qOverload(&QSpinBox::valueChanged), this, &RectEdit::valueChanged); + connect(m_ySpinBox, qOverload(&QSpinBox::valueChanged), this, &RectEdit::valueChanged); + connect(m_widthSpinBox, qOverload(&QSpinBox::valueChanged), this, &RectEdit::valueChanged); + connect(m_heightSpinBox, qOverload(&QSpinBox::valueChanged), this, &RectEdit::valueChanged); +} + +void RectEdit::setValue(const QRect &rect) +{ + m_xSpinBox->setValue(rect.x()); + m_ySpinBox->setValue(rect.y()); + m_widthSpinBox->setValue(rect.width()); + m_heightSpinBox->setValue(rect.height()); +} + +QRect RectEdit::value() const +{ + return QRect(m_xSpinBox->value(), + m_ySpinBox->value(), + m_widthSpinBox->value(), + m_heightSpinBox->value()); +} + + RectFEdit::RectFEdit(QWidget *parent) : ResponsivePairswiseWidget(parent) , m_xLabel(new QLabel(QStringLiteral("X"), this)) diff --git a/src/tiled/propertyeditorwidgets.h b/src/tiled/propertyeditorwidgets.h index 342b0b505a..dc8c9417f0 100644 --- a/src/tiled/propertyeditorwidgets.h +++ b/src/tiled/propertyeditorwidgets.h @@ -166,6 +166,34 @@ class PointFEdit : public ResponsivePairswiseWidget DoubleSpinBox *m_ySpinBox; }; +/** + * A widget for editing a QRect value. + */ +class RectEdit : public ResponsivePairswiseWidget +{ + Q_OBJECT + Q_PROPERTY(QRect value READ value WRITE setValue NOTIFY valueChanged FINAL) + +public: + RectEdit(QWidget *parent = nullptr); + + void setValue(const QRect &size); + QRect value() const; + +signals: + void valueChanged(); + +private: + QLabel *m_xLabel; + QLabel *m_yLabel; + QLabel *m_widthLabel; + QLabel *m_heightLabel; + SpinBox *m_xSpinBox; + SpinBox *m_ySpinBox; + SpinBox *m_widthSpinBox; + SpinBox *m_heightSpinBox; +}; + /** * A widget for editing a QRectF value. */ diff --git a/src/tiled/tilesetview.cpp b/src/tiled/tilesetview.cpp index 9693a45d6a..ac15fce3c2 100644 --- a/src/tiled/tilesetview.cpp +++ b/src/tiled/tilesetview.cpp @@ -831,7 +831,7 @@ void TilesetView::onChange(const ChangeEvent &change) case ChangeEvent::WangSetChanged: { auto &wangSetChange = static_cast(change); if (mEditWangSet && wangSetChange.wangSet == mWangSet && - (wangSetChange.properties & WangSetChangeEvent::TypeProperty)) { + (wangSetChange.property == WangSetChangeEvent::TypeProperty)) { viewport()->update(); } break; diff --git a/src/tiled/tilesetwangsetmodel.cpp b/src/tiled/tilesetwangsetmodel.cpp index 5a6d6f5a6d..76ccbf1a8a 100644 --- a/src/tiled/tilesetwangsetmodel.cpp +++ b/src/tiled/tilesetwangsetmodel.cpp @@ -22,6 +22,7 @@ #include "changeevents.h" #include "changewangsetdata.h" +#include "mapdocument.h" #include "tile.h" #include "tileset.h" #include "tilesetdocument.h" @@ -159,13 +160,14 @@ void TilesetWangSetModel::setWangSetName(WangSet *wangSet, const QString &name) Q_ASSERT(wangSet->tileset() == mTilesetDocument->tileset().data()); wangSet->setName(name); emitWangSetChange(wangSet); + emitToTilesetAndMaps(WangSetChangeEvent(wangSet, WangSetChangeEvent::NameProperty)); } void TilesetWangSetModel::setWangSetType(WangSet *wangSet, WangSet::Type type) { Q_ASSERT(wangSet->tileset() == mTilesetDocument->tileset().data()); wangSet->setType(type); - emit mTilesetDocument->changed(WangSetChangeEvent(wangSet, WangSetChangeEvent::TypeProperty)); + emitToTilesetAndMaps(WangSetChangeEvent(wangSet, WangSetChangeEvent::TypeProperty)); } void TilesetWangSetModel::setWangSetColorCount(WangSet *wangSet, int value) @@ -173,6 +175,7 @@ void TilesetWangSetModel::setWangSetColorCount(WangSet *wangSet, int value) Q_ASSERT(wangSet->tileset() == mTilesetDocument->tileset().data()); wangSet->setColorCount(value); emitWangSetChange(wangSet); + emitToTilesetAndMaps(WangSetChangeEvent(wangSet, WangSetChangeEvent::ColorCountProperty)); } void TilesetWangSetModel::setWangSetImage(WangSet *wangSet, int tileId) @@ -187,6 +190,7 @@ void TilesetWangSetModel::insertWangColor(WangSet *wangSet, const QSharedPointer Q_ASSERT(wangSet->tileset() == mTilesetDocument->tileset().data()); wangSet->insertWangColor(wangColor); emitWangSetChange(wangSet); + emitToTilesetAndMaps(WangSetChangeEvent(wangSet, WangSetChangeEvent::ColorCountProperty)); } QSharedPointer TilesetWangSetModel::takeWangColorAt(WangSet *wangSet, int color) @@ -196,6 +200,7 @@ QSharedPointer TilesetWangSetModel::takeWangColorAt(WangSet *wangSet, auto wangColor = wangSet->takeWangColorAt(color); emit wangColorRemoved(wangColor.data()); emitWangSetChange(wangSet); + emitToTilesetAndMaps(WangSetChangeEvent(wangSet, WangSetChangeEvent::ColorCountProperty)); return wangColor; } @@ -206,6 +211,17 @@ void TilesetWangSetModel::emitWangSetChange(WangSet *wangSet) emit wangSetChanged(wangSet); } +void TilesetWangSetModel::emitToTilesetAndMaps(const ChangeEvent &event) +{ + emit mTilesetDocument->changed(event); + + // todo: this doesn't work reliably because it only reaches maps that use + // the tileset, whereas the Properties view can be showing stuff from any + // tileset. + for (MapDocument *mapDocument : mTilesetDocument->mapDocuments()) + emit mapDocument->changed(event); +} + void TilesetWangSetModel::documentChanged(const ChangeEvent &event) { switch (event.type) { diff --git a/src/tiled/tilesetwangsetmodel.h b/src/tiled/tilesetwangsetmodel.h index 3a61c2acd4..6675acb3be 100644 --- a/src/tiled/tilesetwangsetmodel.h +++ b/src/tiled/tilesetwangsetmodel.h @@ -98,6 +98,7 @@ class TilesetWangSetModel : public QAbstractListModel void documentChanged(const ChangeEvent &event); void emitWangSetChange(WangSet *wangSet); + void emitToTilesetAndMaps(const ChangeEvent &event); TilesetDocument *mTilesetDocument; }; diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index 66ef71b144..917a71d2eb 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -260,6 +260,28 @@ class SizeFEditorFactory : public EditorFactory } }; +class RectEditorFactory : public EditorFactory +{ +public: + QWidget *createEditor(Property *property, QWidget *parent) override + { + auto editor = new RectEdit(parent); + auto syncEditor = [property, editor] { + const QSignalBlocker blocker(editor); + editor->setValue(property->value().toRect()); + }; + syncEditor(); + + QObject::connect(property, &Property::valueChanged, editor, syncEditor); + QObject::connect(editor, &RectEdit::valueChanged, property, + [property, editor] { + property->setValue(editor->value()); + }); + + return editor; + } +}; + class RectFEditorFactory : public EditorFactory { public: @@ -546,15 +568,20 @@ void VariantEditor::addProperty(Property *property) { auto label = new LineEditLabel(property->name(), m_widget); label->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); + label->setToolTip(property->toolTip()); label->setEnabled(property->isEnabled()); + connect(property, &Property::toolTipChanged, label, &QWidget::setToolTip); connect(property, &Property::enabledChanged, label, &QLabel::setEnabled); m_gridLayout->addWidget(label, m_rowIndex, LabelColumn, Qt::AlignTop/* | Qt::AlignRight*/); if (auto editor = createEditor(property)) { + editor->setToolTip(property->toolTip()); editor->setEnabled(property->isEnabled()); + connect(property, &Property::toolTipChanged, editor, &QWidget::setToolTip); connect(property, &Property::enabledChanged, editor, &QWidget::setEnabled); m_gridLayout->addWidget(editor, m_rowIndex, WidgetColumn); } + ++m_rowIndex; } @@ -610,6 +637,12 @@ void EnumEditorFactory::setEnumNames(const QStringList &enumNames) m_enumNamesModel.setStringList(enumNames); } +void EnumEditorFactory::setEnumIcons(const QMap &enumIcons) +{ + // todo: add support for showing these icons in the QComboBox + m_enumIcons = enumIcons; +} + void EnumEditorFactory::setEnumValues(const QList &enumValues) { m_enumValues = enumValues; @@ -651,6 +684,7 @@ ValueTypeEditorFactory::ValueTypeEditorFactory() registerEditorFactory(QMetaType::QFont, std::make_unique()); registerEditorFactory(QMetaType::QPoint, std::make_unique()); registerEditorFactory(QMetaType::QPointF, std::make_unique()); + registerEditorFactory(QMetaType::QRect, std::make_unique()); registerEditorFactory(QMetaType::QRectF, std::make_unique()); registerEditorFactory(QMetaType::QSize, std::make_unique()); registerEditorFactory(QMetaType::QSizeF, std::make_unique()); diff --git a/src/tiled/varianteditor.h b/src/tiled/varianteditor.h index 3b282cb3a1..1e05876637 100644 --- a/src/tiled/varianteditor.h +++ b/src/tiled/varianteditor.h @@ -42,6 +42,7 @@ class Property : public QObject { Q_OBJECT Q_PROPERTY(QString name READ name CONSTANT) + Q_PROPERTY(QString toolTip READ toolTip WRITE setToolTip NOTIFY toolTipChanged) Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged) Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged) @@ -53,6 +54,15 @@ class Property : public QObject const QString &name() const { return m_name; } + const QString &toolTip() const { return m_toolTip; } + void setToolTip(const QString &toolTip) + { + if (m_toolTip != toolTip) { + m_toolTip = toolTip; + emit toolTipChanged(toolTip); + } + } + bool isEnabled() const { return m_enabled; } void setEnabled(bool enabled) { @@ -68,11 +78,13 @@ class Property : public QObject virtual QWidget *createEditor(QWidget *parent) = 0; signals: + void toolTipChanged(const QString &toolTip); void valueChanged(); void enabledChanged(bool enabled); private: QString m_name; + QString m_toolTip; bool m_enabled = true; }; @@ -114,12 +126,14 @@ class EnumEditorFactory : public EditorFactory const QList &enumValues = {}); void setEnumNames(const QStringList &enumNames); + void setEnumIcons(const QMap &enumIcons); void setEnumValues(const QList &enumValues); QWidget *createEditor(Property *property, QWidget *parent) override; private: QStringListModel m_enumNamesModel; + QMap m_enumIcons; QList m_enumValues; }; @@ -198,7 +212,7 @@ class ValueProperty : public AbstractProperty * * The property does not take ownership of the editor factory. */ -class QObjectProperty : public AbstractProperty +class QObjectProperty final : public AbstractProperty { Q_OBJECT diff --git a/src/tiled/wangcolormodel.cpp b/src/tiled/wangcolormodel.cpp index 28c177df27..c998f63cd8 100644 --- a/src/tiled/wangcolormodel.cpp +++ b/src/tiled/wangcolormodel.cpp @@ -130,24 +130,28 @@ void WangColorModel::setName(WangColor *wangColor, const QString &name) { wangColor->setName(name); emitDataChanged(wangColor); + emitToTilesetAndMaps(WangColorChangeEvent(wangColor, WangColorChangeEvent::NameProperty)); } void WangColorModel::setImage(WangColor *wangColor, int imageId) { wangColor->setImageId(imageId); emitDataChanged(wangColor); + emitToTilesetAndMaps(WangColorChangeEvent(wangColor, WangColorChangeEvent::ImageProperty)); } void WangColorModel::setColor(WangColor *wangColor, const QColor &color) { wangColor->setColor(color); emitDataChanged(wangColor); + emitToTilesetAndMaps(WangColorChangeEvent(wangColor, WangColorChangeEvent::ColorProperty)); } void WangColorModel::setProbability(WangColor *wangColor, qreal probability) { wangColor->setProbability(probability); // no data changed signal because probability not exposed by model + emitToTilesetAndMaps(WangColorChangeEvent(wangColor, WangColorChangeEvent::ProbabilityProperty)); } void WangColorModel::emitDataChanged(WangColor *wangColor) @@ -156,4 +160,15 @@ void WangColorModel::emitDataChanged(WangColor *wangColor) emit dataChanged(i, i); } +void WangColorModel::emitToTilesetAndMaps(const ChangeEvent &event) +{ + emit mTilesetDocument->changed(event); + + // todo: this doesn't work reliably because it only reaches maps that use + // the tileset, whereas the Properties view can be showing stuff from any + // tileset. + for (MapDocument *mapDocument : mTilesetDocument->mapDocuments()) + emit mapDocument->changed(event); +} + #include "moc_wangcolormodel.cpp" diff --git a/src/tiled/wangcolormodel.h b/src/tiled/wangcolormodel.h index f07de6a1bb..bcfb30b622 100644 --- a/src/tiled/wangcolormodel.h +++ b/src/tiled/wangcolormodel.h @@ -71,6 +71,7 @@ class WangColorModel : public QAbstractListModel private: void emitDataChanged(WangColor *wangColor); + void emitToTilesetAndMaps(const ChangeEvent &event); TilesetDocument *mTilesetDocument; WangSet *mWangSet; diff --git a/src/tiled/wangdock.cpp b/src/tiled/wangdock.cpp index 5ab91b6533..25db17baf5 100644 --- a/src/tiled/wangdock.cpp +++ b/src/tiled/wangdock.cpp @@ -484,7 +484,7 @@ void WangDock::documentChanged(const ChangeEvent &change) } break; case ChangeEvent::WangSetChanged: - if (static_cast(change).properties & WangSetChangeEvent::TypeProperty) + if (static_cast(change).property == WangSetChangeEvent::TypeProperty) mWangTemplateModel->wangSetChanged(); break; default: From fee9eec9a789bd6dcd1f26f806539a42e10268bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Fri, 6 Sep 2024 14:18:57 +0200 Subject: [PATCH 17/36] Fixed compile --- src/tiled/wangcolormodel.cpp | 2 ++ src/tiled/wangcolormodel.h | 1 + 2 files changed, 3 insertions(+) diff --git a/src/tiled/wangcolormodel.cpp b/src/tiled/wangcolormodel.cpp index c998f63cd8..65665cc088 100644 --- a/src/tiled/wangcolormodel.cpp +++ b/src/tiled/wangcolormodel.cpp @@ -20,7 +20,9 @@ #include "wangcolormodel.h" +#include "changeevents.h" #include "changewangcolordata.h" +#include "mapdocument.h" #include "tileset.h" #include "tilesetdocument.h" #include "wangset.h" diff --git a/src/tiled/wangcolormodel.h b/src/tiled/wangcolormodel.h index bcfb30b622..050f3c5fed 100644 --- a/src/tiled/wangcolormodel.h +++ b/src/tiled/wangcolormodel.h @@ -28,6 +28,7 @@ namespace Tiled { class Tileset; +class ChangeEvent; class TilesetDocument; class WangColorModel : public QAbstractListModel From aaf487f6f668cbe978c5c2141cfada607a1f717e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Fri, 6 Sep 2024 17:48:32 +0200 Subject: [PATCH 18/36] Refactor that gets rid of most editor factories Instead there are now typed Property subclasses similar to the generic GetSetProperty. --- src/tiled/propertieswidget.cpp | 133 +++---- src/tiled/varianteditor.cpp | 680 ++++++++++++++++----------------- src/tiled/varianteditor.h | 126 +++++- 3 files changed, 495 insertions(+), 444 deletions(-) diff --git a/src/tiled/propertieswidget.cpp b/src/tiled/propertieswidget.cpp index bc416685b8..525d1fecbd 100644 --- a/src/tiled/propertieswidget.cpp +++ b/src/tiled/propertieswidget.cpp @@ -171,13 +171,22 @@ static QStringList classNamesFor(const Object &object) return names; } -class ClassProperty : public Property +// todo: add support for changing multiple objects +class ClassProperty : public StringProperty { Q_OBJECT public: ClassProperty(Document *document, Object *object, QObject *parent = nullptr) - : Property(tr("Class"), parent) + : StringProperty(tr("Class"), + [this] { return mObject->className(); }, + [this] (const QString &value) { + QUndoStack *undoStack = mDocument->undoStack(); + undoStack->push(new ChangeClassName(mDocument, + { mObject }, + value)); + }, + parent) , mDocument(document) , mObject(object) { @@ -185,15 +194,6 @@ class ClassProperty : public Property this, &ClassProperty::onChanged); } - QVariant value() const override { return mObject->className(); } - void setValue(const QVariant &value) override - { - QUndoStack *undoStack = mDocument->undoStack(); - undoStack->push(new ChangeClassName(mDocument, - { mObject }, // todo: add support for changing multiple objects - value.toString())); - } - QWidget *createEditor(QWidget *parent) override { auto editor = new QComboBox(parent); @@ -232,28 +232,27 @@ class ClassProperty : public Property Object *mObject; }; -class MapSizeProperty : public AbstractProperty +class MapSizeProperty : public SizeProperty { Q_OBJECT public: - MapSizeProperty(MapDocument *mapDocument, EditorFactory *editorFactory, + MapSizeProperty(MapDocument *mapDocument, QObject *parent = nullptr) - : AbstractProperty(tr("Map Size"), editorFactory, parent) + : SizeProperty(tr("Map Size"), + [this]{ return mMapDocument->map()->size(); }, {}, + parent) , mMapDocument(mapDocument) { connect(mMapDocument, &MapDocument::mapChanged, this, &Property::valueChanged); } - QVariant value() const override { return mMapDocument->map()->size(); } - void setValue(const QVariant &) override {}; - QWidget *createEditor(QWidget *parent) override { auto widget = new QWidget(parent); auto layout = new QVBoxLayout(widget); - auto valueEdit = AbstractProperty::createEditor(widget); + auto valueEdit = SizeProperty::createEditor(widget); auto resizeButton = new QPushButton(tr("Resize Map"), widget); valueEdit->setEnabled(false); @@ -272,48 +271,6 @@ class MapSizeProperty : public AbstractProperty MapDocument *mMapDocument; }; -class TileSizeProperty : public AbstractProperty -{ - Q_OBJECT - -public: - TileSizeProperty(MapDocument *mapDocument, - EditorFactory *editorFactory, - QObject *parent = nullptr) - : AbstractProperty(tr("Tile Size"), editorFactory, parent) - , mMapDocument(mapDocument) - { - } - - QVariant value() const override - { - return mMapDocument->map()->tileSize(); - } - - void setValue(const QVariant &value) override - { - auto oldSize = mMapDocument->map()->tileSize(); - auto newSize = value.toSize(); - - if (oldSize.width() != newSize.width()) { - auto command = new ChangeMapProperty(mMapDocument, - Map::TileWidthProperty, - newSize.width()); - mMapDocument->undoStack()->push(command); - } - - if (oldSize.height() != newSize.height()) { - auto command = new ChangeMapProperty(mMapDocument, - Map::TileHeightProperty, - newSize.height()); - mMapDocument->undoStack()->push(command); - } - }; - -private: - MapDocument *mMapDocument; -}; - class ObjectProperties : public QObject { Q_OBJECT @@ -364,22 +321,42 @@ class MapProperties : public ObjectProperties push(new ChangeMapProperty(mapDocument(), orientation)); }); - mSizeProperty = new MapSizeProperty(mapDocument(), editorFactory, this); + mSizeProperty = new MapSizeProperty(mapDocument(), this); - mTileSizeProperty = new TileSizeProperty(mapDocument(), editorFactory, this); + mTileSizeProperty = new SizeProperty( + tr("Tile Size"), + [this] { + return mapDocument()->map()->tileSize(); + }, + [this](const QSize &newSize) { + const auto oldSize = mapDocument()->map()->tileSize(); + + if (oldSize.width() != newSize.width()) { + push(new ChangeMapProperty(mapDocument(), + Map::TileWidthProperty, + newSize.width())); + } + + if (oldSize.height() != newSize.height()) { + push(new ChangeMapProperty(mapDocument(), + Map::TileHeightProperty, + newSize.height())); + } + }, + this); - mInfiniteProperty = editorFactory->createProperty( + mInfiniteProperty = new BoolProperty( tr("Infinite"), [this]() { return map()->infinite(); }, - [this](const QVariant &value) { + [this](const bool &value) { push(new ChangeMapProperty(mapDocument(), Map::InfiniteProperty, - value.toInt())); + value ? 1 : 0)); }); - mHexSideLengthProperty = editorFactory->createProperty( + mHexSideLengthProperty = new IntProperty( tr("Hex Side Length"), [this]() { return map()->hexSideLength(); @@ -1083,24 +1060,19 @@ class MapObjectProperties : public ObjectProperties public: MapObjectProperties(MapDocument *document, MapObject *object, ValueTypeEditorFactory *editorFactory, QObject *parent = nullptr) : ObjectProperties(document, object, parent) - , mDegreesEditorFactory(std::make_unique()) { - mDegreesEditorFactory->setSuffix(QStringLiteral("°")); - - mIdProperty = editorFactory->createProperty( + mIdProperty = new IntProperty( tr("ID"), - [this]() { return mapObject()->id(); }, - [](const QVariant &) {}); + [this]() { return mapObject()->id(); }); mIdProperty->setEnabled(false); - mTemplateProperty = editorFactory->createProperty( + mTemplateProperty = new UrlProperty( tr("Template"), [this]() { if (auto objectTemplate = mapObject()->objectTemplate()) return QUrl::fromLocalFile(objectTemplate->fileName()); return QUrl(); - }, - [](const QVariant &) {}); + }); mTemplateProperty->setEnabled(false); mNameProperty = editorFactory->createProperty( @@ -1139,15 +1111,16 @@ class MapObjectProperties : public ObjectProperties changeMapObject(MapObject::SizeProperty, value); }); - mRotationProperty = new GetSetProperty( + mRotationProperty = new FloatProperty( tr("Rotation"), [this]() { return mapObject()->rotation(); }, - [this](const QVariant &value) { + [this](const qreal &value) { changeMapObject(MapObject::RotationProperty, value); }, - mDegreesEditorFactory.get(), this); + this); + mRotationProperty->setSuffix(QStringLiteral("°")); // todo: make this a custom widget with "Horizontal" and "Vertical" checkboxes mFlippingProperty = editorFactory->createProperty( @@ -1329,7 +1302,7 @@ class MapObjectProperties : public ObjectProperties Property *mVisibleProperty; Property *mPositionProperty; Property *mSizeProperty; - Property *mRotationProperty; + FloatProperty *mRotationProperty; // for tile objects Property *mFlippingProperty; @@ -1340,8 +1313,6 @@ class MapObjectProperties : public ObjectProperties Property *mTextFontProperty; Property *mTextWordWrapProperty; Property *mTextColorProperty; - - std::unique_ptr mDegreesEditorFactory; }; class TileProperties : public ObjectProperties diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index 917a71d2eb..52ae9513a4 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -65,393 +65,334 @@ GetSetProperty::GetSetProperty(const QString &name, {} - -class StringEditorFactory : public EditorFactory +QWidget *StringProperty::createEditor(QWidget *parent) { -public: - QWidget *createEditor(Property *property, QWidget *parent) override - { - auto editor = new QLineEdit(parent); - auto syncEditor = [=] { - editor->setText(property->value().toString()); - }; - syncEditor(); + auto editor = new QLineEdit(parent); + auto syncEditor = [=] { + editor->setText(m_get()); + }; + syncEditor(); - QObject::connect(property, &Property::valueChanged, editor, syncEditor); - QObject::connect(editor, &QLineEdit::textEdited, property, &Property::setValue); + QObject::connect(this, &Property::valueChanged, editor, syncEditor); + QObject::connect(editor, &QLineEdit::textEdited, this, m_set); - return editor; - } -}; + return editor; +} -class UrlEditorFactory : public EditorFactory +QWidget *UrlProperty::createEditor(QWidget *parent) { -public: - QWidget *createEditor(Property *property, QWidget *parent) override - { - auto editor = new FileEdit(parent); - editor->setFilter(m_filter); - - auto syncEditor = [=] { - editor->setFileUrl(property->value().toUrl()); - }; - syncEditor(); + auto editor = new FileEdit(parent); + editor->setFilter(m_filter); - QObject::connect(property, &Property::valueChanged, editor, syncEditor); - QObject::connect(editor, &FileEdit::fileUrlChanged, property, &Property::setValue); - - return editor; - } + auto syncEditor = [=] { + editor->setFileUrl(m_get()); + }; + syncEditor(); - void setFilter(const QString &filter) - { - m_filter = filter; - } + QObject::connect(this, &Property::valueChanged, editor, syncEditor); + QObject::connect(editor, &FileEdit::fileUrlChanged, this, m_set); -private: - QString m_filter; -}; + return editor; +} -QWidget *IntEditorFactory::createEditor(Property *property, QWidget *parent) +QWidget *IntProperty::createEditor(QWidget *parent) { auto editor = new SpinBox(parent); auto syncEditor = [=] { const QSignalBlocker blocker(editor); - editor->setValue(property->value().toInt()); + editor->setValue(m_get()); }; syncEditor(); - QObject::connect(property, &Property::valueChanged, editor, syncEditor); + QObject::connect(this, &Property::valueChanged, editor, syncEditor); QObject::connect(editor, qOverload(&SpinBox::valueChanged), - property, &Property::setValue); + this, m_set); return editor; } - -QWidget *FloatEditorFactory::createEditor(Property *property, QWidget *parent) +QWidget *FloatProperty::createEditor(QWidget *parent) { auto editor = new DoubleSpinBox(parent); editor->setSuffix(m_suffix); auto syncEditor = [=] { const QSignalBlocker blocker(editor); - editor->setValue(property->value().toDouble()); + editor->setValue(m_get()); }; syncEditor(); - QObject::connect(property, &Property::valueChanged, editor, syncEditor); + QObject::connect(this, &Property::valueChanged, editor, syncEditor); QObject::connect(editor, qOverload(&DoubleSpinBox::valueChanged), - property, &Property::setValue); + this, m_set); return editor; } +QWidget *BoolProperty::createEditor(QWidget *parent) +{ + auto editor = new QCheckBox(parent); + auto syncEditor = [=] { + const QSignalBlocker blocker(editor); + bool checked = m_get(); + editor->setChecked(checked); + editor->setText(checked ? tr("On") : tr("Off")); + }; + syncEditor(); + + QObject::connect(this, &Property::valueChanged, editor, syncEditor); + QObject::connect(editor, &QCheckBox::toggled, this, [=](bool checked) { + editor->setText(checked ? QObject::tr("On") : QObject::tr("Off")); + m_set(checked); + }); -class BoolEditorFactory : public EditorFactory + return editor; +} + +QWidget *PointProperty::createEditor(QWidget *parent) { -public: - QWidget *createEditor(Property *property, QWidget *parent) override - { - auto editor = new QCheckBox(parent); - auto syncEditor = [=] { - const QSignalBlocker blocker(editor); - bool checked = property->value().toBool(); - editor->setChecked(checked); - editor->setText(checked ? tr("On") : tr("Off")); - }; - syncEditor(); + auto editor = new PointEdit(parent); + auto syncEditor = [this, editor] { + const QSignalBlocker blocker(editor); + editor->setValue(m_get()); + }; + syncEditor(); - QObject::connect(property, &Property::valueChanged, editor, syncEditor); - QObject::connect(editor, &QCheckBox::toggled, property, [=](bool checked) { - editor->setText(checked ? QObject::tr("On") : QObject::tr("Off")); - property->setValue(checked); - }); + QObject::connect(this, &Property::valueChanged, editor, syncEditor); + QObject::connect(editor, &PointEdit::valueChanged, this, + [this, editor] { + m_set(editor->value()); + }); - return editor; - } -}; - -class PointEditorFactory : public EditorFactory -{ -public: - QWidget *createEditor(Property *property, QWidget *parent) override - { - auto editor = new PointEdit(parent); - auto syncEditor = [property, editor] { - const QSignalBlocker blocker(editor); - editor->setValue(property->value().toPoint()); - }; - syncEditor(); - - QObject::connect(property, &Property::valueChanged, editor, syncEditor); - QObject::connect(editor, &PointEdit::valueChanged, property, - [property, editor] { - property->setValue(editor->value()); - }); + return editor; +} - return editor; - } -}; - -class PointFEditorFactory : public EditorFactory -{ -public: - QWidget *createEditor(Property *property, QWidget *parent) override - { - auto editor = new PointFEdit(parent); - auto syncEditor = [property, editor] { - const QSignalBlocker blocker(editor); - editor->setValue(property->value().toPointF()); - }; - syncEditor(); - - QObject::connect(property, &Property::valueChanged, editor, syncEditor); - QObject::connect(editor, &PointFEdit::valueChanged, property, - [property, editor] { - property->setValue(editor->value()); - }); +QWidget *PointFProperty::createEditor(QWidget *parent) +{ + auto editor = new PointFEdit(parent); + auto syncEditor = [this, editor] { + const QSignalBlocker blocker(editor); + editor->setValue(this->value().toPointF()); + }; + syncEditor(); - return editor; - } -}; - -class SizeEditorFactory : public EditorFactory -{ -public: - QWidget *createEditor(Property *property, QWidget *parent) override - { - auto editor = new SizeEdit(parent); - auto syncEditor = [property, editor] { - const QSignalBlocker blocker(editor); - editor->setValue(property->value().toSize()); - }; - syncEditor(); - - QObject::connect(property, &Property::valueChanged, editor, syncEditor); - QObject::connect(editor, &SizeEdit::valueChanged, property, - [property, editor] { - property->setValue(editor->value()); - }); + QObject::connect(this, &Property::valueChanged, editor, syncEditor); + QObject::connect(editor, &PointFEdit::valueChanged, this, + [this, editor] { + this->setValue(editor->value()); + }); - return editor; - } -}; - -class SizeFEditorFactory : public EditorFactory -{ -public: - QWidget *createEditor(Property *property, QWidget *parent) override - { - auto editor = new SizeFEdit(parent); - auto syncEditor = [property, editor] { - const QSignalBlocker blocker(editor); - editor->setValue(property->value().toSizeF()); - }; - syncEditor(); - - QObject::connect(property, &Property::valueChanged, editor, syncEditor); - QObject::connect(editor, &SizeFEdit::valueChanged, property, - [property, editor] { - property->setValue(editor->value()); - }); + return editor; +} - return editor; - } -}; - -class RectEditorFactory : public EditorFactory -{ -public: - QWidget *createEditor(Property *property, QWidget *parent) override - { - auto editor = new RectEdit(parent); - auto syncEditor = [property, editor] { - const QSignalBlocker blocker(editor); - editor->setValue(property->value().toRect()); - }; - syncEditor(); - - QObject::connect(property, &Property::valueChanged, editor, syncEditor); - QObject::connect(editor, &RectEdit::valueChanged, property, - [property, editor] { - property->setValue(editor->value()); - }); +QWidget *SizeProperty::createEditor(QWidget *parent) +{ + auto editor = new SizeEdit(parent); + auto syncEditor = [this, editor] { + const QSignalBlocker blocker(editor); + editor->setValue(m_get()); + }; + syncEditor(); - return editor; - } -}; - -class RectFEditorFactory : public EditorFactory -{ -public: - QWidget *createEditor(Property *property, QWidget *parent) override - { - auto editor = new RectFEdit(parent); - auto syncEditor = [property, editor] { - const QSignalBlocker blocker(editor); - editor->setValue(property->value().toRectF()); - }; - syncEditor(); - - QObject::connect(property, &Property::valueChanged, editor, syncEditor); - QObject::connect(editor, &RectFEdit::valueChanged, property, - [property, editor] { - property->setValue(editor->value()); - }); + QObject::connect(this, &Property::valueChanged, editor, syncEditor); + QObject::connect(editor, &SizeEdit::valueChanged, this, + [this, editor] { + m_set(editor->value()); + }); - return editor; - } -}; + return editor; +} + +QWidget *SizeFProperty::createEditor(QWidget *parent) +{ + auto editor = new SizeFEdit(parent); + auto syncEditor = [this, editor] { + const QSignalBlocker blocker(editor); + editor->setValue(this->value().toSizeF()); + }; + syncEditor(); + + QObject::connect(this, &Property::valueChanged, editor, syncEditor); + QObject::connect(editor, &SizeFEdit::valueChanged, this, + [this, editor] { + this->setValue(editor->value()); + }); + + return editor; +} + +QWidget *RectProperty::createEditor(QWidget *parent) +{ + auto editor = new RectEdit(parent); + auto syncEditor = [this, editor] { + const QSignalBlocker blocker(editor); + editor->setValue(this->value().toRect()); + }; + syncEditor(); + + QObject::connect(this, &Property::valueChanged, editor, syncEditor); + QObject::connect(editor, &RectEdit::valueChanged, this, + [this, editor] { + this->setValue(editor->value()); + }); + + return editor; +} + +QWidget *RectFProperty::createEditor(QWidget *parent) +{ + auto editor = new RectFEdit(parent); + auto syncEditor = [this, editor] { + const QSignalBlocker blocker(editor); + editor->setValue(this->value().toRectF()); + }; + syncEditor(); + + QObject::connect(this, &Property::valueChanged, editor, syncEditor); + QObject::connect(editor, &RectFEdit::valueChanged, this, + [this, editor] { + this->setValue(editor->value()); + }); + + return editor; +} // todo: needs to handle invalid color (unset value) -class ColorEditorFactory : public EditorFactory -{ -public: - QWidget *createEditor(Property *property, QWidget *parent) override - { - auto editor = new ColorButton(parent); - auto syncEditor = [=] { - const QSignalBlocker blocker(editor); - editor->setColor(property->value().value()); - }; - syncEditor(); - - QObject::connect(property, &Property::valueChanged, editor, syncEditor); - QObject::connect(editor, &ColorButton::colorChanged, property, - [property, editor] { - property->setValue(editor->color()); - }); +QWidget *ColorProperty::createEditor(QWidget *parent) +{ + auto editor = new ColorButton(parent); + auto syncEditor = [=] { + const QSignalBlocker blocker(editor); + editor->setColor(this->value().value()); + }; + syncEditor(); - return editor; - } -}; - -class FontEditorFactory : public EditorFactory -{ -public: - QWidget *createEditor(Property *property, QWidget *parent) override - { - auto editor = new QWidget(parent); - auto layout = new QVBoxLayout(editor); - auto fontComboBox = new QFontComboBox(editor); - auto sizeSpinBox = new QSpinBox(editor); - auto boldCheckBox = new QCheckBox(tr("Bold"), editor); - auto italicCheckBox = new QCheckBox(tr("Italic"), editor); - auto underlineCheckBox = new QCheckBox(tr("Underline"), editor); - auto strikeoutCheckBox = new QCheckBox(tr("Strikeout"), editor); - auto kerningCheckBox = new QCheckBox(tr("Kerning"), editor); - sizeSpinBox->setRange(1, 999); - sizeSpinBox->setSuffix(tr(" px")); - sizeSpinBox->setKeyboardTracking(false); - layout->setContentsMargins(QMargins()); - layout->setSpacing(Utils::dpiScaled(3)); - layout->addWidget(fontComboBox); - layout->addWidget(sizeSpinBox); - layout->addWidget(boldCheckBox); - layout->addWidget(italicCheckBox); - layout->addWidget(underlineCheckBox); - layout->addWidget(strikeoutCheckBox); - layout->addWidget(kerningCheckBox); - - auto syncEditor = [=] { - const auto font = property->value().value(); - const QSignalBlocker fontBlocker(fontComboBox); - const QSignalBlocker sizeBlocker(sizeSpinBox); - const QSignalBlocker boldBlocker(boldCheckBox); - const QSignalBlocker italicBlocker(italicCheckBox); - const QSignalBlocker underlineBlocker(underlineCheckBox); - const QSignalBlocker strikeoutBlocker(strikeoutCheckBox); - const QSignalBlocker kerningBlocker(kerningCheckBox); - fontComboBox->setCurrentFont(font); - sizeSpinBox->setValue(font.pixelSize()); - boldCheckBox->setChecked(font.bold()); - italicCheckBox->setChecked(font.italic()); - underlineCheckBox->setChecked(font.underline()); - strikeoutCheckBox->setChecked(font.strikeOut()); - kerningCheckBox->setChecked(font.kerning()); - }; - - auto syncProperty = [=] { - auto font = fontComboBox->currentFont(); - font.setPixelSize(sizeSpinBox->value()); - font.setBold(boldCheckBox->isChecked()); - font.setItalic(italicCheckBox->isChecked()); - font.setUnderline(underlineCheckBox->isChecked()); - font.setStrikeOut(strikeoutCheckBox->isChecked()); - font.setKerning(kerningCheckBox->isChecked()); - property->setValue(font); - }; - - syncEditor(); - - QObject::connect(property, &Property::valueChanged, fontComboBox, syncEditor); - QObject::connect(fontComboBox, &QFontComboBox::currentFontChanged, property, syncProperty); - QObject::connect(sizeSpinBox, qOverload(&QSpinBox::valueChanged), property, syncProperty); - QObject::connect(boldCheckBox, &QCheckBox::toggled, property, syncProperty); - QObject::connect(italicCheckBox, &QCheckBox::toggled, property, syncProperty); - QObject::connect(underlineCheckBox, &QCheckBox::toggled, property, syncProperty); - QObject::connect(strikeoutCheckBox, &QCheckBox::toggled, property, syncProperty); - QObject::connect(kerningCheckBox, &QCheckBox::toggled, property, syncProperty); + QObject::connect(this, &Property::valueChanged, editor, syncEditor); + QObject::connect(editor, &ColorButton::colorChanged, this, + [this, editor] { + this->setValue(editor->color()); + }); - return editor; - } -}; - -class AlignmentEditorFactory : public EditorFactory -{ -public: - QWidget *createEditor(Property *property, QWidget *parent) override - { - auto editor = new QWidget(parent); - auto layout = new QGridLayout(editor); - layout->setContentsMargins(QMargins()); - layout->setSpacing(Utils::dpiScaled(3)); - - auto horizontalLabel = new ElidingLabel(tr("Horizontal"), editor); - layout->addWidget(horizontalLabel, 0, 0); - - auto verticalLabel = new ElidingLabel(tr("Vertical"), editor); - layout->addWidget(verticalLabel, 1, 0); - - auto horizontalComboBox = new QComboBox(editor); - horizontalComboBox->addItem(tr("Left"), Qt::AlignLeft); - horizontalComboBox->addItem(tr("Center"), Qt::AlignHCenter); - horizontalComboBox->addItem(tr("Right"), Qt::AlignRight); - horizontalComboBox->addItem(tr("Justify"), Qt::AlignJustify); - layout->addWidget(horizontalComboBox, 0, 1); - - auto verticalComboBox = new QComboBox(editor); - verticalComboBox->addItem(tr("Top"), Qt::AlignTop); - verticalComboBox->addItem(tr("Center"), Qt::AlignVCenter); - verticalComboBox->addItem(tr("Bottom"), Qt::AlignBottom); - layout->addWidget(verticalComboBox, 1, 1); - - layout->setColumnStretch(1, 1); - - auto syncEditor = [=] { - const QSignalBlocker horizontalBlocker(horizontalComboBox); - const QSignalBlocker verticalBlocker(verticalComboBox); - const auto alignment = property->value().value(); - horizontalComboBox->setCurrentIndex(horizontalComboBox->findData(static_cast(alignment & Qt::AlignHorizontal_Mask))); - verticalComboBox->setCurrentIndex(verticalComboBox->findData(static_cast(alignment & Qt::AlignVertical_Mask))); - }; - - auto syncProperty = [=] { - const Qt::Alignment alignment(horizontalComboBox->currentData().toInt() | - verticalComboBox->currentData().toInt()); - property->setValue(QVariant::fromValue(alignment)); - }; - - syncEditor(); - - QObject::connect(property, &Property::valueChanged, editor, syncEditor); - QObject::connect(horizontalComboBox, qOverload(&QComboBox::currentIndexChanged), property, syncProperty); - QObject::connect(verticalComboBox, qOverload(&QComboBox::currentIndexChanged), property, syncProperty); + return editor; +} - return editor; - } -}; +QWidget *FontProperty::createEditor(QWidget *parent) +{ + auto editor = new QWidget(parent); + auto layout = new QVBoxLayout(editor); + auto fontComboBox = new QFontComboBox(editor); + auto sizeSpinBox = new QSpinBox(editor); + auto boldCheckBox = new QCheckBox(tr("Bold"), editor); + auto italicCheckBox = new QCheckBox(tr("Italic"), editor); + auto underlineCheckBox = new QCheckBox(tr("Underline"), editor); + auto strikeoutCheckBox = new QCheckBox(tr("Strikeout"), editor); + auto kerningCheckBox = new QCheckBox(tr("Kerning"), editor); + sizeSpinBox->setRange(1, 999); + sizeSpinBox->setSuffix(tr(" px")); + sizeSpinBox->setKeyboardTracking(false); + layout->setContentsMargins(QMargins()); + layout->setSpacing(Utils::dpiScaled(3)); + layout->addWidget(fontComboBox); + layout->addWidget(sizeSpinBox); + layout->addWidget(boldCheckBox); + layout->addWidget(italicCheckBox); + layout->addWidget(underlineCheckBox); + layout->addWidget(strikeoutCheckBox); + layout->addWidget(kerningCheckBox); + + auto syncEditor = [=] { + const auto font = this->value().value(); + const QSignalBlocker fontBlocker(fontComboBox); + const QSignalBlocker sizeBlocker(sizeSpinBox); + const QSignalBlocker boldBlocker(boldCheckBox); + const QSignalBlocker italicBlocker(italicCheckBox); + const QSignalBlocker underlineBlocker(underlineCheckBox); + const QSignalBlocker strikeoutBlocker(strikeoutCheckBox); + const QSignalBlocker kerningBlocker(kerningCheckBox); + fontComboBox->setCurrentFont(font); + sizeSpinBox->setValue(font.pixelSize()); + boldCheckBox->setChecked(font.bold()); + italicCheckBox->setChecked(font.italic()); + underlineCheckBox->setChecked(font.underline()); + strikeoutCheckBox->setChecked(font.strikeOut()); + kerningCheckBox->setChecked(font.kerning()); + }; + + auto syncProperty = [=] { + auto font = fontComboBox->currentFont(); + font.setPixelSize(sizeSpinBox->value()); + font.setBold(boldCheckBox->isChecked()); + font.setItalic(italicCheckBox->isChecked()); + font.setUnderline(underlineCheckBox->isChecked()); + font.setStrikeOut(strikeoutCheckBox->isChecked()); + font.setKerning(kerningCheckBox->isChecked()); + this->setValue(font); + }; + + syncEditor(); + + QObject::connect(this, &Property::valueChanged, fontComboBox, syncEditor); + QObject::connect(fontComboBox, &QFontComboBox::currentFontChanged, this, syncProperty); + QObject::connect(sizeSpinBox, qOverload(&QSpinBox::valueChanged), this, syncProperty); + QObject::connect(boldCheckBox, &QCheckBox::toggled, this, syncProperty); + QObject::connect(italicCheckBox, &QCheckBox::toggled, this, syncProperty); + QObject::connect(underlineCheckBox, &QCheckBox::toggled, this, syncProperty); + QObject::connect(strikeoutCheckBox, &QCheckBox::toggled, this, syncProperty); + QObject::connect(kerningCheckBox, &QCheckBox::toggled, this, syncProperty); + + return editor; +} + +QWidget *AlignmentProperty::createEditor(QWidget *parent) +{ + auto editor = new QWidget(parent); + auto layout = new QGridLayout(editor); + layout->setContentsMargins(QMargins()); + layout->setSpacing(Utils::dpiScaled(3)); + + auto horizontalLabel = new ElidingLabel(tr("Horizontal"), editor); + layout->addWidget(horizontalLabel, 0, 0); + + auto verticalLabel = new ElidingLabel(tr("Vertical"), editor); + layout->addWidget(verticalLabel, 1, 0); + + auto horizontalComboBox = new QComboBox(editor); + horizontalComboBox->addItem(tr("Left"), Qt::AlignLeft); + horizontalComboBox->addItem(tr("Center"), Qt::AlignHCenter); + horizontalComboBox->addItem(tr("Right"), Qt::AlignRight); + horizontalComboBox->addItem(tr("Justify"), Qt::AlignJustify); + layout->addWidget(horizontalComboBox, 0, 1); + + auto verticalComboBox = new QComboBox(editor); + verticalComboBox->addItem(tr("Top"), Qt::AlignTop); + verticalComboBox->addItem(tr("Center"), Qt::AlignVCenter); + verticalComboBox->addItem(tr("Bottom"), Qt::AlignBottom); + layout->addWidget(verticalComboBox, 1, 1); + + layout->setColumnStretch(1, 1); + + auto syncEditor = [=] { + const QSignalBlocker horizontalBlocker(horizontalComboBox); + const QSignalBlocker verticalBlocker(verticalComboBox); + const auto alignment = this->value().value(); + horizontalComboBox->setCurrentIndex(horizontalComboBox->findData(static_cast(alignment & Qt::AlignHorizontal_Mask))); + verticalComboBox->setCurrentIndex(verticalComboBox->findData(static_cast(alignment & Qt::AlignVertical_Mask))); + }; + + auto syncProperty = [=] { + const Qt::Alignment alignment(horizontalComboBox->currentData().toInt() | + verticalComboBox->currentData().toInt()); + this->setValue(QVariant::fromValue(alignment)); + }; + + syncEditor(); + + QObject::connect(this, &Property::valueChanged, editor, syncEditor); + QObject::connect(horizontalComboBox, qOverload(&QComboBox::currentIndexChanged), this, syncProperty); + QObject::connect(verticalComboBox, qOverload(&QComboBox::currentIndexChanged), this, syncProperty); + + return editor; +} ValueProperty::ValueProperty(const QString &name, @@ -676,21 +617,6 @@ QWidget *EnumEditorFactory::createEditor(Property *property, QWidget *parent) ValueTypeEditorFactory::ValueTypeEditorFactory() { - // Register some useful default editor factories - registerEditorFactory(QMetaType::Bool, std::make_unique()); - registerEditorFactory(QMetaType::Double, std::make_unique()); - registerEditorFactory(QMetaType::Int, std::make_unique()); - registerEditorFactory(QMetaType::QColor, std::make_unique()); - registerEditorFactory(QMetaType::QFont, std::make_unique()); - registerEditorFactory(QMetaType::QPoint, std::make_unique()); - registerEditorFactory(QMetaType::QPointF, std::make_unique()); - registerEditorFactory(QMetaType::QRect, std::make_unique()); - registerEditorFactory(QMetaType::QRectF, std::make_unique()); - registerEditorFactory(QMetaType::QSize, std::make_unique()); - registerEditorFactory(QMetaType::QSizeF, std::make_unique()); - registerEditorFactory(QMetaType::QString, std::make_unique()); - registerEditorFactory(QMetaType::QUrl, std::make_unique()); - registerEditorFactory(qMetaTypeId(), std::make_unique()); } void ValueTypeEditorFactory::registerEditorFactory(int type, std::unique_ptr factory) @@ -723,10 +649,54 @@ ValueProperty *ValueTypeEditorFactory::createProperty(const QString &name, : nullptr); } -AbstractProperty *ValueTypeEditorFactory::createProperty(const QString &name, - std::function get, - std::function set) +template +Property *createTypedProperty(const QString &name, + std::function get, + std::function set) +{ + return new PropertyClass(name, + [get = std::move(get)] { return get().value(); }, + [set = std::move(set)] (typename PropertyClass::ValueType v) { set(QVariant::fromValue(v)); }); +} + +Property *ValueTypeEditorFactory::createProperty(const QString &name, + std::function get, + std::function set) { + const auto type = get().userType(); + switch (type) { + case QMetaType::QString: + return createTypedProperty(name, get, set); + case QMetaType::QUrl: + return createTypedProperty(name, get, set); + case QMetaType::Int: + return createTypedProperty(name, get, set); + case QMetaType::Double: + return createTypedProperty(name, get, set); + case QMetaType::Bool: + return createTypedProperty(name, get, set); + case QMetaType::QColor: + return createTypedProperty(name, get, set); + case QMetaType::QFont: + return createTypedProperty(name, get, set); + case QMetaType::QPoint: + return createTypedProperty(name, get, set); + case QMetaType::QPointF: + return createTypedProperty(name, get, set); + case QMetaType::QRect: + return createTypedProperty(name, get, set); + case QMetaType::QRectF: + return createTypedProperty(name, get, set); + case QMetaType::QSize: + return createTypedProperty(name, get, set); + case QMetaType::QSizeF: + return createTypedProperty(name, get, set); + default: + if (type == qMetaTypeId()) + return createTypedProperty(name, get, set); + } + + // Fall back to registered factories approach (still used for enums) auto f = m_factories.find(get().userType()); return new GetSetProperty(name, get, set, f != m_factories.end() ? f->second.get() diff --git a/src/tiled/varianteditor.h b/src/tiled/varianteditor.h index 1e05876637..63df00b6d9 100644 --- a/src/tiled/varianteditor.h +++ b/src/tiled/varianteditor.h @@ -101,21 +101,131 @@ class EditorFactory virtual QWidget *createEditor(Property *property, QWidget *parent) = 0; }; -struct IntEditorFactory : EditorFactory +/** + * A helper class for creating a property that wraps a value of a given type. + */ +template +class PropertyTemplate : public Property { - QWidget *createEditor(Property *property, QWidget *parent) override; +public: + using ValueType = Type; + + PropertyTemplate(const QString &name, + std::function get, + std::function set = {}, + QObject *parent = nullptr) + : Property(name, parent) + , m_get(std::move(get)) + , m_set(std::move(set)) + {} + + QVariant value() const override + { + return QVariant::fromValue(m_get()); + } + + void setValue(const QVariant &value) override + { + if (m_set) + m_set(value.value()); + } + +protected: + std::function m_get; + std::function m_set; }; -struct FloatEditorFactory : EditorFactory +struct StringProperty : PropertyTemplate { - QWidget *createEditor(Property *property, QWidget *parent) override; + using PropertyTemplate::PropertyTemplate; + QWidget *createEditor(QWidget *parent) override; +}; - void setSuffix(const QString &suffix) { m_suffix = suffix; } +struct UrlProperty : PropertyTemplate +{ + using PropertyTemplate::PropertyTemplate; + QWidget *createEditor(QWidget *parent) override; + void setFilter(const QString &filter) { m_filter = filter; } +private: + QString m_filter; +}; +struct IntProperty : PropertyTemplate +{ + using PropertyTemplate::PropertyTemplate; + QWidget *createEditor(QWidget *parent) override; +}; + +struct FloatProperty : PropertyTemplate +{ + using PropertyTemplate::PropertyTemplate; + QWidget *createEditor(QWidget *parent) override; + void setSuffix(const QString &suffix) { m_suffix = suffix; } private: QString m_suffix; }; +struct BoolProperty : PropertyTemplate +{ + using PropertyTemplate::PropertyTemplate; + QWidget *createEditor(QWidget *parent) override; +}; + +struct PointProperty : PropertyTemplate +{ + using PropertyTemplate::PropertyTemplate; + QWidget *createEditor(QWidget *parent) override; +}; + +struct PointFProperty : PropertyTemplate +{ + using PropertyTemplate::PropertyTemplate; + QWidget *createEditor(QWidget *parent) override; +}; + +struct SizeProperty : PropertyTemplate +{ + using PropertyTemplate::PropertyTemplate; + QWidget *createEditor(QWidget *parent) override; +}; + +struct SizeFProperty : PropertyTemplate +{ + using PropertyTemplate::PropertyTemplate; + QWidget *createEditor(QWidget *parent) override; +}; + +struct RectProperty : PropertyTemplate +{ + using PropertyTemplate::PropertyTemplate; + QWidget *createEditor(QWidget *parent) override; +}; + +struct RectFProperty : PropertyTemplate +{ + using PropertyTemplate::PropertyTemplate; + QWidget *createEditor(QWidget *parent) override; +}; + +// todo: needs to handle invalid color (unset value) +struct ColorProperty : PropertyTemplate +{ + using PropertyTemplate::PropertyTemplate; + QWidget *createEditor(QWidget *parent) override; +}; + +struct FontProperty : PropertyTemplate +{ + using PropertyTemplate::PropertyTemplate; + QWidget *createEditor(QWidget *parent) override; +}; + +struct AlignmentProperty : PropertyTemplate +{ + using PropertyTemplate::PropertyTemplate; + QWidget *createEditor(QWidget *parent) override; +}; + /** * An editor factory that creates a combo box for enum properties. */ @@ -269,9 +379,9 @@ class ValueTypeEditorFactory : public EditorFactory * property will use the editor factory registered for the type of the * value. */ - AbstractProperty *createProperty(const QString &name, - std::function get, - std::function set); + Property *createProperty(const QString &name, + std::function get, + std::function set); QWidget *createEditor(Property *property, QWidget *parent) override; From f5ef40f2483e97cda667dca3da5ebb6d1406b9b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Fri, 6 Sep 2024 19:26:24 +0200 Subject: [PATCH 19/36] Renamed ValueTypeEditorFactory to PropertyFactory This name is more appropriate for what it actually does. It also no longer derives from EditorFactory. PropertyFactory::createQObjectProperty should be functional again. It now returns an appropriate property, and the QObjectProperty class is gone. This approach is still not used, however. --- src/tiled/propertieswidget.cpp | 152 ++++++++++++++++----------------- src/tiled/propertieswidget.h | 4 +- src/tiled/varianteditor.cpp | 94 +++++++------------- src/tiled/varianteditor.h | 71 +++++---------- 4 files changed, 132 insertions(+), 189 deletions(-) diff --git a/src/tiled/propertieswidget.cpp b/src/tiled/propertieswidget.cpp index 525d1fecbd..a496ba897b 100644 --- a/src/tiled/propertieswidget.cpp +++ b/src/tiled/propertieswidget.cpp @@ -63,7 +63,7 @@ namespace Tiled { PropertiesWidget::PropertiesWidget(QWidget *parent) : QWidget{parent} , mPropertyBrowser(new VariantEditor(this)) - , mDefaultEditorFactory(std::make_unique()) + , mPropertyFactory(std::make_unique()) { mActionAddProperty = new QAction(this); mActionAddProperty->setEnabled(false); @@ -307,11 +307,11 @@ class MapProperties : public ObjectProperties public: MapProperties(MapDocument *document, - ValueTypeEditorFactory *editorFactory, + PropertyFactory *propertyFactory, QObject *parent = nullptr) : ObjectProperties(document, document->map(), parent) { - mOrientationProperty = editorFactory->createProperty( + mOrientationProperty = propertyFactory->createProperty( tr("Orientation"), [this]() { return QVariant::fromValue(map()->orientation()); @@ -367,7 +367,7 @@ class MapProperties : public ObjectProperties value.toInt())); }); - mStaggerAxisProperty = editorFactory->createProperty( + mStaggerAxisProperty = propertyFactory->createProperty( tr("Stagger Axis"), [this]() { return QVariant::fromValue(map()->staggerAxis()); @@ -377,7 +377,7 @@ class MapProperties : public ObjectProperties push(new ChangeMapProperty(mapDocument(), staggerAxis)); }); - mStaggerIndexProperty = editorFactory->createProperty( + mStaggerIndexProperty = propertyFactory->createProperty( tr("Stagger Index"), [this]() { return QVariant::fromValue(map()->staggerIndex()); @@ -387,7 +387,7 @@ class MapProperties : public ObjectProperties push(new ChangeMapProperty(mapDocument(), staggerIndex)); }); - mParallaxOriginProperty = editorFactory->createProperty( + mParallaxOriginProperty = propertyFactory->createProperty( tr("Parallax Origin"), [this]() { return map()->parallaxOrigin(); @@ -396,7 +396,7 @@ class MapProperties : public ObjectProperties push(new ChangeMapProperty(mapDocument(), value.value())); }); - mLayerDataFormatProperty = editorFactory->createProperty( + mLayerDataFormatProperty = propertyFactory->createProperty( tr("Layer Data Format"), [this]() { return QVariant::fromValue(map()->layerDataFormat()); @@ -406,7 +406,7 @@ class MapProperties : public ObjectProperties push(new ChangeMapProperty(mapDocument(), layerDataFormat)); }); - mChunkSizeProperty = editorFactory->createProperty( + mChunkSizeProperty = propertyFactory->createProperty( tr("Output Chunk Size"), [this]() { return map()->chunkSize(); @@ -415,7 +415,7 @@ class MapProperties : public ObjectProperties push(new ChangeMapProperty(mapDocument(), value.toSize())); }); - mRenderOrderProperty = editorFactory->createProperty( + mRenderOrderProperty = propertyFactory->createProperty( tr("Tile Render Order"), [this]() { return QVariant::fromValue(map()->renderOrder()); @@ -425,7 +425,7 @@ class MapProperties : public ObjectProperties push(new ChangeMapProperty(mapDocument(), renderOrder)); }); - mCompressionLevelProperty = editorFactory->createProperty( + mCompressionLevelProperty = propertyFactory->createProperty( tr("Compression Level"), [this]() { return map()->compressionLevel(); @@ -434,7 +434,7 @@ class MapProperties : public ObjectProperties push(new ChangeMapProperty(mapDocument(), value.toInt())); }); - mBackgroundColorProperty = editorFactory->createProperty( + mBackgroundColorProperty = propertyFactory->createProperty( tr("Background Color"), [this]() { return map()->backgroundColor(); @@ -576,12 +576,12 @@ class LayerProperties : public ObjectProperties Q_OBJECT public: - LayerProperties(MapDocument *document, Layer *object, ValueTypeEditorFactory *editorFactory, QObject *parent = nullptr) + LayerProperties(MapDocument *document, Layer *object, PropertyFactory *propertyFactory, QObject *parent = nullptr) : ObjectProperties(document, object, parent) { // todo: would be nicer to avoid the SpinBox and use a custom widget // might also be nice to embed this in the header instead of using a property - mIdProperty = editorFactory->createProperty( + mIdProperty = propertyFactory->createProperty( tr("ID"), [this]() { return layer()->id(); }, [](const QVariant &) {}); @@ -589,21 +589,21 @@ class LayerProperties : public ObjectProperties // todo: the below should be able to apply to all selected layers - mNameProperty = editorFactory->createProperty( + mNameProperty = propertyFactory->createProperty( tr("Name"), [this]() { return layer()->name(); }, [this](const QVariant &value) { push(new SetLayerName(mapDocument(), { layer() }, value.toString())); }); - mVisibleProperty = editorFactory->createProperty( + mVisibleProperty = propertyFactory->createProperty( tr("Visible"), [this]() { return layer()->isVisible(); }, [this](const QVariant &value) { push(new SetLayerVisible(mapDocument(), { layer() }, value.toBool())); }); - mLockedProperty = editorFactory->createProperty( + mLockedProperty = propertyFactory->createProperty( tr("Locked"), [this]() { return layer()->isLocked(); }, [this](const QVariant &value) { @@ -612,21 +612,21 @@ class LayerProperties : public ObjectProperties // todo: value should be between 0 and 1, and would be nice to use a slider (replacing the one in Layers view) // todo: singleStep should be 0.1 - mOpacityProperty = editorFactory->createProperty( + mOpacityProperty = propertyFactory->createProperty( tr("Opacity"), [this]() { return layer()->opacity(); }, [this](const QVariant &value) { push(new SetLayerOpacity(mapDocument(), { layer() }, value.toReal())); }); - mTintColorProperty = editorFactory->createProperty( + mTintColorProperty = propertyFactory->createProperty( tr("Tint Color"), [this]() { return layer()->tintColor(); }, [this](const QVariant &value) { push(new SetLayerTintColor(mapDocument(), { layer() }, value.value())); }); - mOffsetProperty = editorFactory->createProperty( + mOffsetProperty = propertyFactory->createProperty( tr("Offset"), [this]() { return layer()->offset(); }, [this](const QVariant &value) { @@ -634,7 +634,7 @@ class LayerProperties : public ObjectProperties }); // todo: singleStep should be 0.1 - mParallaxFactorProperty = editorFactory->createProperty( + mParallaxFactorProperty = propertyFactory->createProperty( tr("Parallax Factor"), [this]() { return layer()->parallaxFactor(); }, [this](const QVariant &value) { @@ -709,18 +709,18 @@ class ImageLayerProperties : public LayerProperties Q_OBJECT public: - ImageLayerProperties(MapDocument *document, ImageLayer *object, ValueTypeEditorFactory *editorFactory, QObject *parent = nullptr) - : LayerProperties(document, object, editorFactory, parent) + ImageLayerProperties(MapDocument *document, ImageLayer *object, PropertyFactory *propertyFactory, QObject *parent = nullptr) + : LayerProperties(document, object, propertyFactory, parent) { // todo: set a file filter for selecting images (or map files?) - mImageProperty = editorFactory->createProperty( + mImageProperty = propertyFactory->createProperty( tr("Image Source"), [this]() { return imageLayer()->imageSource(); }, [this](const QVariant &value) { push(new ChangeImageLayerImageSource(mapDocument(), { imageLayer() }, value.toUrl())); }); - mTransparentColorProperty = editorFactory->createProperty( + mTransparentColorProperty = propertyFactory->createProperty( tr("Transparent Color"), [this]() { return imageLayer()->transparentColor(); }, [this](const QVariant &value) { @@ -728,14 +728,14 @@ class ImageLayerProperties : public LayerProperties }); // todo: consider merging Repeat X and Y into a single property - mRepeatXProperty = editorFactory->createProperty( + mRepeatXProperty = propertyFactory->createProperty( tr("Repeat X"), [this]() { return imageLayer()->repeatX(); }, [this](const QVariant &value) { push(new ChangeImageLayerRepeatX(mapDocument(), { imageLayer() }, value.toBool())); }); - mRepeatYProperty = editorFactory->createProperty( + mRepeatYProperty = propertyFactory->createProperty( tr("Repeat Y"), [this]() { return imageLayer()->repeatY(); }, [this](const QVariant &value) { @@ -792,17 +792,17 @@ class ObjectGroupProperties : public LayerProperties Q_OBJECT public: - ObjectGroupProperties(MapDocument *document, ObjectGroup *object, ValueTypeEditorFactory *editorFactory, QObject *parent = nullptr) - : LayerProperties(document, object, editorFactory, parent) + ObjectGroupProperties(MapDocument *document, ObjectGroup *object, PropertyFactory *propertyFactory, QObject *parent = nullptr) + : LayerProperties(document, object, propertyFactory, parent) { - mColorProperty = editorFactory->createProperty( + mColorProperty = propertyFactory->createProperty( tr("Color"), [this]() { return objectGroup()->color(); }, [this](const QVariant &value) { push(new ChangeObjectGroupColor(mapDocument(), { objectGroup() }, value.value())); }); - mDrawOrderProperty = editorFactory->createProperty( + mDrawOrderProperty = propertyFactory->createProperty( tr("Draw Order"), [this]() { return QVariant::fromValue(objectGroup()->drawOrder()); }, [this](const QVariant &value) { @@ -852,11 +852,11 @@ class TilesetProperties : public ObjectProperties public: TilesetProperties(TilesetDocument *document, - ValueTypeEditorFactory *editorFactory, + PropertyFactory *propertyFactory, QObject *parent = nullptr) : ObjectProperties(document, document->tileset().data(), parent) { - mNameProperty = editorFactory->createProperty( + mNameProperty = propertyFactory->createProperty( tr("Name"), [this]() { return tilesetDocument()->tileset()->name(); @@ -865,7 +865,7 @@ class TilesetProperties : public ObjectProperties push(new RenameTileset(tilesetDocument(), value.toString())); }); - mObjectAlignmentProperty = editorFactory->createProperty( + mObjectAlignmentProperty = propertyFactory->createProperty( tr("Object Alignment"), [this]() { return QVariant::fromValue(tileset()->objectAlignment()); @@ -875,7 +875,7 @@ class TilesetProperties : public ObjectProperties push(new ChangeTilesetObjectAlignment(tilesetDocument(), objectAlignment)); }); - mTileOffsetProperty = editorFactory->createProperty( + mTileOffsetProperty = propertyFactory->createProperty( tr("Drawing Offset"), [this]() { return tileset()->tileOffset(); @@ -884,7 +884,7 @@ class TilesetProperties : public ObjectProperties push(new ChangeTilesetTileOffset(tilesetDocument(), value.value())); }); - mTileRenderSizeProperty = editorFactory->createProperty( + mTileRenderSizeProperty = propertyFactory->createProperty( tr("Tile Render Size"), [this]() { return QVariant::fromValue(tileset()->tileRenderSize()); @@ -894,7 +894,7 @@ class TilesetProperties : public ObjectProperties push(new ChangeTilesetTileRenderSize(tilesetDocument(), tileRenderSize)); }); - mFillModeProperty = editorFactory->createProperty( + mFillModeProperty = propertyFactory->createProperty( tr("Fill Mode"), [this]() { return QVariant::fromValue(tileset()->fillMode()); @@ -904,7 +904,7 @@ class TilesetProperties : public ObjectProperties push(new ChangeTilesetFillMode(tilesetDocument(), fillMode)); }); - mBackgroundColorProperty = editorFactory->createProperty( + mBackgroundColorProperty = propertyFactory->createProperty( tr("Background Color"), [this]() { return tileset()->backgroundColor(); @@ -913,7 +913,7 @@ class TilesetProperties : public ObjectProperties push(new ChangeTilesetBackgroundColor(tilesetDocument(), value.value())); }); - mOrientationProperty = editorFactory->createProperty( + mOrientationProperty = propertyFactory->createProperty( tr("Orientation"), [this]() { return QVariant::fromValue(tileset()->orientation()); @@ -923,7 +923,7 @@ class TilesetProperties : public ObjectProperties push(new ChangeTilesetOrientation(tilesetDocument(), orientation)); }); - mGridSizeProperty = editorFactory->createProperty( + mGridSizeProperty = propertyFactory->createProperty( tr("Grid Size"), [this]() { return tileset()->gridSize(); @@ -933,7 +933,7 @@ class TilesetProperties : public ObjectProperties }); // todo: needs 1 as minimum value - mColumnCountProperty = editorFactory->createProperty( + mColumnCountProperty = propertyFactory->createProperty( tr("Columns"), [this]() { return tileset()->columnCount(); @@ -943,7 +943,7 @@ class TilesetProperties : public ObjectProperties }); // todo: this needs a custom widget - mAllowedTransformationsProperty = editorFactory->createProperty( + mAllowedTransformationsProperty = propertyFactory->createProperty( tr("Allowed Transformations"), [this]() { return QVariant::fromValue(tileset()->transformationFlags()); @@ -954,7 +954,7 @@ class TilesetProperties : public ObjectProperties }); // todo: this needs a custom widget - mImageProperty = editorFactory->createProperty( + mImageProperty = propertyFactory->createProperty( tr("Image"), [this]() { return tileset()->imageSource().toString(); @@ -1058,7 +1058,7 @@ class MapObjectProperties : public ObjectProperties Q_OBJECT public: - MapObjectProperties(MapDocument *document, MapObject *object, ValueTypeEditorFactory *editorFactory, QObject *parent = nullptr) + MapObjectProperties(MapDocument *document, MapObject *object, PropertyFactory *propertyFactory, QObject *parent = nullptr) : ObjectProperties(document, object, parent) { mIdProperty = new IntProperty( @@ -1075,7 +1075,7 @@ class MapObjectProperties : public ObjectProperties }); mTemplateProperty->setEnabled(false); - mNameProperty = editorFactory->createProperty( + mNameProperty = propertyFactory->createProperty( tr("Name"), [this]() { return mapObject()->name(); @@ -1084,7 +1084,7 @@ class MapObjectProperties : public ObjectProperties changeMapObject(MapObject::NameProperty, value); }); - mVisibleProperty = editorFactory->createProperty( + mVisibleProperty = propertyFactory->createProperty( tr("Visible"), [this]() { return mapObject()->isVisible(); @@ -1093,7 +1093,7 @@ class MapObjectProperties : public ObjectProperties changeMapObject(MapObject::VisibleProperty, value); }); - mPositionProperty = editorFactory->createProperty( + mPositionProperty = propertyFactory->createProperty( tr("Position"), [this]() { return mapObject()->position(); @@ -1102,7 +1102,7 @@ class MapObjectProperties : public ObjectProperties changeMapObject(MapObject::PositionProperty, value); }); - mSizeProperty = editorFactory->createProperty( + mSizeProperty = propertyFactory->createProperty( tr("Size"), [this]() { return mapObject()->size(); @@ -1123,7 +1123,7 @@ class MapObjectProperties : public ObjectProperties mRotationProperty->setSuffix(QStringLiteral("°")); // todo: make this a custom widget with "Horizontal" and "Vertical" checkboxes - mFlippingProperty = editorFactory->createProperty( + mFlippingProperty = propertyFactory->createProperty( tr("Flipping"), [this]() { return mapObject()->cell().flags(); @@ -1146,7 +1146,7 @@ class MapObjectProperties : public ObjectProperties push(command); }); - mTextProperty = editorFactory->createProperty( + mTextProperty = propertyFactory->createProperty( tr("Text"), [this]() { return mapObject()->textData().text; @@ -1155,7 +1155,7 @@ class MapObjectProperties : public ObjectProperties changeMapObject(MapObject::TextProperty, value); }); - mTextAlignmentProperty = editorFactory->createProperty( + mTextAlignmentProperty = propertyFactory->createProperty( tr("Alignment"), [this]() { return QVariant::fromValue(mapObject()->textData().alignment); @@ -1164,7 +1164,7 @@ class MapObjectProperties : public ObjectProperties changeMapObject(MapObject::TextAlignmentProperty, value); }); - mTextFontProperty = editorFactory->createProperty( + mTextFontProperty = propertyFactory->createProperty( tr("Font"), [this]() { return mapObject()->textData().font; @@ -1173,7 +1173,7 @@ class MapObjectProperties : public ObjectProperties changeMapObject(MapObject::TextFontProperty, value); }); - mTextWordWrapProperty = editorFactory->createProperty( + mTextWordWrapProperty = propertyFactory->createProperty( tr("Word Wrap"), [this]() { return mapObject()->textData().wordWrap; @@ -1182,7 +1182,7 @@ class MapObjectProperties : public ObjectProperties changeMapObject(MapObject::TextWordWrapProperty, value); }); - mTextColorProperty = editorFactory->createProperty( + mTextColorProperty = propertyFactory->createProperty( tr("Text Color"), [this]() { return mapObject()->textData().color; @@ -1320,17 +1320,17 @@ class TileProperties : public ObjectProperties Q_OBJECT public: - TileProperties(Document *document, Tile *object, ValueTypeEditorFactory *editorFactory, QObject *parent = nullptr) + TileProperties(Document *document, Tile *object, PropertyFactory *propertyFactory, QObject *parent = nullptr) : ObjectProperties(document, object, parent) { - mIdProperty = editorFactory->createProperty( + mIdProperty = propertyFactory->createProperty( tr("ID"), [this]() { return tile()->id(); }, [](const QVariant &) {}); mIdProperty->setEnabled(false); // todo: apply readableImageFormatsFilter - mImageProperty = editorFactory->createProperty( + mImageProperty = propertyFactory->createProperty( tr("Image"), [this]() { return tile()->imageSource(); }, [this](const QVariant &value) { @@ -1339,7 +1339,7 @@ class TileProperties : public ObjectProperties value.toUrl())); }); - mRectangleProperty = editorFactory->createProperty( + mRectangleProperty = propertyFactory->createProperty( tr("Rectangle"), [this]() { return tile()->imageRect(); }, [this](const QVariant &value) { @@ -1349,7 +1349,7 @@ class TileProperties : public ObjectProperties }); // todo: minimum value should be 0 - mProbabilityProperty = editorFactory->createProperty( + mProbabilityProperty = propertyFactory->createProperty( tr("Probability"), [this]() { return tile()->probability(); }, [this](const QVariant &value) { @@ -1439,17 +1439,17 @@ class WangSetProperties : public ObjectProperties public: WangSetProperties(Document *document, WangSet *object, - ValueTypeEditorFactory *editorFactory, QObject *parent = nullptr) + PropertyFactory *propertyFactory, QObject *parent = nullptr) : ObjectProperties(document, object, parent) { - mNameProperty = editorFactory->createProperty( + mNameProperty = propertyFactory->createProperty( tr("Name"), [this]() { return wangSet()->name(); }, [this](const QVariant &value) { push(new RenameWangSet(tilesetDocument(), wangSet(), value.toString())); }); - mTypeProperty = editorFactory->createProperty( + mTypeProperty = propertyFactory->createProperty( tr("Type"), [this]() { return QVariant::fromValue(wangSet()->type()); }, [this](const QVariant &value) { @@ -1457,7 +1457,7 @@ class WangSetProperties : public ObjectProperties }); // todo: keep between 0 and WangId::MAX_COLOR_COUNT - mColorCountProperty = editorFactory->createProperty( + mColorCountProperty = propertyFactory->createProperty( tr("Color Count"), [this]() { return wangSet()->colorCount(); }, [this](const QVariant &value) { @@ -1536,17 +1536,17 @@ class WangColorProperties : public ObjectProperties public: WangColorProperties(Document *document, WangColor *object, - ValueTypeEditorFactory *editorFactory, QObject *parent = nullptr) + PropertyFactory *propertyFactory, QObject *parent = nullptr) : ObjectProperties(document, object, parent) { - mNameProperty = editorFactory->createProperty( + mNameProperty = propertyFactory->createProperty( tr("Name"), [this]() { return wangColor()->name(); }, [this](const QVariant &value) { push(new ChangeWangColorName(tilesetDocument(), wangColor(), value.toString())); }); - mColorProperty = editorFactory->createProperty( + mColorProperty = propertyFactory->createProperty( tr("Color"), [this]() { return wangColor()->color(); }, [this](const QVariant &value) { @@ -1554,7 +1554,7 @@ class WangColorProperties : public ObjectProperties }); // todo: set 0.01 as minimum - mProbabilityProperty = editorFactory->createProperty( + mProbabilityProperty = propertyFactory->createProperty( tr("Probability"), [this]() { return wangColor()->probability(); }, [this](const QVariant &value) { @@ -1643,48 +1643,48 @@ void PropertiesWidget::currentObjectChanged(Object *object) case Layer::ImageLayerType: mPropertiesObject = new ImageLayerProperties(mapDocument, static_cast(object), - mDefaultEditorFactory.get(), this); + mPropertyFactory.get(), this); break; case Layer::ObjectGroupType: mPropertiesObject = new ObjectGroupProperties(mapDocument, static_cast(object), - mDefaultEditorFactory.get(), this); + mPropertyFactory.get(), this); break; case Layer::TileLayerType: case Layer::GroupLayerType: mPropertiesObject = new LayerProperties(mapDocument, static_cast(object), - mDefaultEditorFactory.get(), this); + mPropertyFactory.get(), this); break; } break; } case Object::MapObjectType: mPropertiesObject = new MapObjectProperties(static_cast(mDocument), - static_cast(object), mDefaultEditorFactory.get(), this); + static_cast(object), mPropertyFactory.get(), this); break; case Object::MapType: mPropertiesObject = new MapProperties(static_cast(mDocument), - mDefaultEditorFactory.get(), this); + mPropertyFactory.get(), this); break; case Object::TilesetType: mPropertiesObject = new TilesetProperties(static_cast(mDocument), - mDefaultEditorFactory.get(), this); + mPropertyFactory.get(), this); break; case Object::TileType: mPropertiesObject = new TileProperties(mDocument, static_cast(object), - mDefaultEditorFactory.get(), this); + mPropertyFactory.get(), this); break; case Object::WangSetType: mPropertiesObject = new WangSetProperties(mDocument, static_cast(object), - mDefaultEditorFactory.get(), this); + mPropertyFactory.get(), this); break; case Object::WangColorType: mPropertiesObject = new WangColorProperties(mDocument, static_cast(object), - mDefaultEditorFactory.get(), this); + mPropertyFactory.get(), this); break; case Object::ProjectType: case Object::WorldType: @@ -2203,7 +2203,7 @@ void PropertiesWidget::registerEditorFactories() void PropertiesWidget::registerEditorFactory(int type, std::unique_ptr factory) { - mDefaultEditorFactory->registerEditorFactory(type, std::move(factory)); + mPropertyFactory->registerEditorFactory(type, std::move(factory)); } void PropertiesWidget::retranslateUi() diff --git a/src/tiled/propertieswidget.h b/src/tiled/propertieswidget.h index d6ad390af1..c3a2bb0088 100644 --- a/src/tiled/propertieswidget.h +++ b/src/tiled/propertieswidget.h @@ -31,7 +31,7 @@ class Object; class Document; class EditorFactory; class ObjectProperties; -class ValueTypeEditorFactory; +class PropertyFactory; class VariantEditor; /** @@ -84,7 +84,7 @@ public slots: Document *mDocument = nullptr; ObjectProperties *mPropertiesObject = nullptr; VariantEditor *mPropertyBrowser; - std::unique_ptr mDefaultEditorFactory; + std::unique_ptr mPropertyFactory; QAction *mActionAddProperty; QAction *mActionRemoveProperty; QAction *mActionRenameProperty; diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index 52ae9513a4..f73b582fb0 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -615,33 +615,48 @@ QWidget *EnumEditorFactory::createEditor(Property *property, QWidget *parent) } -ValueTypeEditorFactory::ValueTypeEditorFactory() -{ -} - -void ValueTypeEditorFactory::registerEditorFactory(int type, std::unique_ptr factory) +void PropertyFactory::registerEditorFactory(int type, std::unique_ptr factory) { m_factories[type] = std::move(factory); } -QObjectProperty *ValueTypeEditorFactory::createQObjectProperty(QObject *qObject, - const char *name, - const QString &displayName) +Property *PropertyFactory::createQObjectProperty(QObject *qObject, + const char *name, + const QString &displayName) { auto metaObject = qObject->metaObject(); auto propertyIndex = metaObject->indexOfProperty(name); if (propertyIndex < 0) return nullptr; - return new QObjectProperty(qObject, - metaObject->property(propertyIndex), - displayName.isEmpty() ? QString::fromUtf8(name) - : displayName, - this); + auto metaProperty = metaObject->property(propertyIndex); + auto property = createProperty(displayName.isEmpty() ? QString::fromUtf8(name) + : displayName, + [=] { + return metaProperty.read(qObject); + }, + [=] (const QVariant &value) { + metaProperty.write(qObject, value); + }); + + // If the property has a notify signal, forward it to valueChanged + auto notify = metaProperty.notifySignal(); + if (notify.isValid()) { + auto propertyMetaObject = property->metaObject(); + auto valuePropertyIndex = propertyMetaObject->indexOfProperty("value"); + auto valueProperty = propertyMetaObject->property(valuePropertyIndex); + auto valueChanged = valueProperty.notifySignal(); + + QObject::connect(qObject, notify, property, valueChanged); + } + + property->setEnabled(metaProperty.isWritable()); + + return property; } -ValueProperty *ValueTypeEditorFactory::createProperty(const QString &name, - const QVariant &value) +ValueProperty *PropertyFactory::createProperty(const QString &name, + const QVariant &value) { auto f = m_factories.find(value.userType()); return new ValueProperty(name, value, @@ -659,9 +674,9 @@ Property *createTypedProperty(const QString &name, [set = std::move(set)] (typename PropertyClass::ValueType v) { set(QVariant::fromValue(v)); }); } -Property *ValueTypeEditorFactory::createProperty(const QString &name, - std::function get, - std::function set) +Property *PropertyFactory::createProperty(const QString &name, + std::function get, + std::function set) { const auto type = get().userType(); switch (type) { @@ -703,49 +718,6 @@ Property *ValueTypeEditorFactory::createProperty(const QString &name, : nullptr); } -QWidget *ValueTypeEditorFactory::createEditor(Property *property, QWidget *parent) -{ - const auto value = property->value(); - const int type = value.userType(); - auto factory = m_factories.find(type); - if (factory != m_factories.end()) - return factory->second->createEditor(property, parent); - return nullptr; -} - - -QObjectProperty::QObjectProperty(QObject *object, - QMetaProperty property, - const QString &displayName, - EditorFactory *editorFactory, - QObject *parent) - : AbstractProperty(displayName, editorFactory, parent) - , m_object(object) - , m_property(property) -{ - // If the property has a notify signal, forward it to valueChanged - auto notify = property.notifySignal(); - if (notify.isValid()) { - auto valuePropertyIndex = metaObject()->indexOfProperty("value"); - auto valueProperty = metaObject()->property(valuePropertyIndex); - auto valueChanged = valueProperty.notifySignal(); - - connect(m_object, notify, this, valueChanged); - } - - setEnabled(m_property.isWritable()); -} - -QVariant QObjectProperty::value() const -{ - return m_property.read(m_object); -} - -void QObjectProperty::setValue(const QVariant &value) -{ - m_property.write(m_object, value); -} - } // namespace Tiled #include "moc_varianteditor.cpp" diff --git a/src/tiled/varianteditor.h b/src/tiled/varianteditor.h index 63df00b6d9..7c257b9bc8 100644 --- a/src/tiled/varianteditor.h +++ b/src/tiled/varianteditor.h @@ -88,19 +88,6 @@ class Property : public QObject bool m_enabled = true; }; -/** - * An editor factory is responsible for creating an editor widget for a given - * property. It can be used to share the configuration of editor widgets - * between different properties. - */ -class EditorFactory -{ - Q_DECLARE_TR_FUNCTIONS(EditorFactory) - -public: - virtual QWidget *createEditor(Property *property, QWidget *parent) = 0; -}; - /** * A helper class for creating a property that wraps a value of a given type. */ @@ -226,6 +213,19 @@ struct AlignmentProperty : PropertyTemplate QWidget *createEditor(QWidget *parent) override; }; +/** + * An editor factory is responsible for creating an editor widget for a given + * property. It can be used to share the configuration of editor widgets + * between different properties. + */ +class EditorFactory +{ + Q_DECLARE_TR_FUNCTIONS(EditorFactory) + +public: + virtual QWidget *createEditor(Property *property, QWidget *parent) = 0; +}; + /** * An editor factory that creates a combo box for enum properties. */ @@ -316,41 +316,15 @@ class ValueProperty : public AbstractProperty QVariant m_value; }; -/** - * A property that wraps a value of a QObject property and uses an editor - * factory to create its editor. - * - * The property does not take ownership of the editor factory. - */ -class QObjectProperty final : public AbstractProperty -{ - Q_OBJECT - -public: - QObjectProperty(QObject *object, - QMetaProperty property, - const QString &displayName, - EditorFactory *editorFactory, - QObject *parent = nullptr); - - QVariant value() const override; - void setValue(const QVariant &value) override; - -private: - QObject *m_object; - QMetaProperty m_property; -}; /** - * An editor factory that selects the appropriate editor factory based on the - * type of the property value. - * - * todo: rename to VariantEditorFactory when the old one is removed + * A property factory that instantiates the appropriate property type based on + * the type of the property value. */ -class ValueTypeEditorFactory : public EditorFactory +class PropertyFactory { public: - ValueTypeEditorFactory(); + PropertyFactory() = default; /** * Register an editor factory for a given type. @@ -361,12 +335,11 @@ class ValueTypeEditorFactory : public EditorFactory void registerEditorFactory(int type, std::unique_ptr factory); /** - * Creates a property that wraps a QObject property and will use the editor - * factory registered for the type of the value. + * Creates a property that wraps a QObject property. */ - QObjectProperty *createQObjectProperty(QObject *qObject, - const char *name, - const QString &displayName = {}); + Property *createQObjectProperty(QObject *qObject, + const char *name, + const QString &displayName = {}); /** * Creates a property with the given name and value. The property will use @@ -383,8 +356,6 @@ class ValueTypeEditorFactory : public EditorFactory std::function get, std::function set); - QWidget *createEditor(Property *property, QWidget *parent) override; - private: std::unordered_map> m_factories; }; From 2b0cb35c606345a887a3b7994b3e7c7b95b5c427 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Mon, 9 Sep 2024 12:59:42 +0200 Subject: [PATCH 20/36] Simplifying or over-complicating things * Removed EditorFactory, EnumEditorFactory, AbstractProperty, ValueProperty and GetSetProperty. * EnumProperty now derives from IntProperty and retrieves the enum meta-data based on its template argument. * Use the typed properties to avoid QVariant when synchronizing between the created editor. For the built-in properties, this definitely simplifies things. But it remains to be seen how custom properties are best handled. Also due to the lack of registering editor factories, QObjectProperty is now broken for enums (but it's unused). --- src/tiled/propertieswidget.cpp | 767 ++++++++++++++++----------------- src/tiled/propertieswidget.h | 8 - src/tiled/varianteditor.cpp | 194 +++------ src/tiled/varianteditor.h | 203 +++------ 4 files changed, 487 insertions(+), 685 deletions(-) diff --git a/src/tiled/propertieswidget.cpp b/src/tiled/propertieswidget.cpp index a496ba897b..e97b0dd15b 100644 --- a/src/tiled/propertieswidget.cpp +++ b/src/tiled/propertieswidget.cpp @@ -60,10 +60,142 @@ namespace Tiled { +template<> EnumData enumData() +{ + return {{ + QCoreApplication::translate("Alignment", "Unspecified"), + QCoreApplication::translate("Alignment", "Top Left"), + QCoreApplication::translate("Alignment", "Top"), + QCoreApplication::translate("Alignment", "Top Right"), + QCoreApplication::translate("Alignment", "Left"), + QCoreApplication::translate("Alignment", "Center"), + QCoreApplication::translate("Alignment", "Right"), + QCoreApplication::translate("Alignment", "Bottom Left"), + QCoreApplication::translate("Alignment", "Bottom"), + QCoreApplication::translate("Alignment", "Bottom Right") + }}; +} + +template<> EnumData enumData() +{ + // We leave out the "Unknown" orientation, because it shouldn't occur here + return {{ + QCoreApplication::translate("Tiled::NewMapDialog", "Orthogonal"), + QCoreApplication::translate("Tiled::NewMapDialog", "Isometric"), + QCoreApplication::translate("Tiled::NewMapDialog", "Isometric (Staggered)"), + QCoreApplication::translate("Tiled::NewMapDialog", "Hexagonal (Staggered)") + }, { + Map::Orthogonal, + Map::Isometric, + Map::Staggered, + Map::Hexagonal, + }}; +} + +template<> EnumData enumData() +{ + return {{ + QCoreApplication::translate("StaggerAxis", "X"), + QCoreApplication::translate("StaggerAxis", "Y") + }}; +} + +template<> EnumData enumData() +{ + return {{ + QCoreApplication::translate("StaggerIndex", "Odd"), + QCoreApplication::translate("StaggerIndex", "Even") + }}; +} + +template<> EnumData enumData() +{ + return {{ + QCoreApplication::translate("RenderOrder", "Right Down"), + QCoreApplication::translate("RenderOrder", "Right Up"), + QCoreApplication::translate("RenderOrder", "Left Down"), + QCoreApplication::translate("RenderOrder", "Left Up") + }}; +} + +template<> EnumData enumData() +{ + QStringList names { + QCoreApplication::translate("PreferencesDialog", "XML (deprecated)"), + QCoreApplication::translate("PreferencesDialog", "Base64 (uncompressed)"), + QCoreApplication::translate("PreferencesDialog", "Base64 (gzip compressed)"), + QCoreApplication::translate("PreferencesDialog", "Base64 (zlib compressed)"), + }; + QList values { + Map::XML, + Map::Base64, + Map::Base64Gzip, + Map::Base64Zlib, + }; + + if (compressionSupported(Zstandard)) { + names.append(QCoreApplication::translate("PreferencesDialog", "Base64 (Zstandard compressed)")); + values.append(Map::Base64Zstandard); + } + + names.append(QCoreApplication::translate("PreferencesDialog", "CSV")); + values.append(Map::CSV); + + return { names, values }; +} + +template<> EnumData enumData() +{ + return {{ + QCoreApplication::translate("Tileset", "Orthogonal"), + QCoreApplication::translate("Tileset", "Isometric"), + }}; +} + +template<> EnumData enumData() +{ + return {{ + QCoreApplication::translate("Tileset", "Tile Size"), + QCoreApplication::translate("Tileset", "Map Grid Size"), + }}; +} + +template<> EnumData enumData() +{ + return {{ + QCoreApplication::translate("Tileset", "Stretch"), + QCoreApplication::translate("Tileset", "Preserve Aspect Ratio"), + }}; +} + +template<> EnumData enumData() +{ + return {{ + QCoreApplication::translate("ObjectGroup", "Top Down"), + QCoreApplication::translate("ObjectGroup", "Index Order"), + }}; +} + +template<> EnumData enumData() +{ + const QStringList names { + QCoreApplication::translate("WangSet", "Corner"), + QCoreApplication::translate("WangSet", "Edge"), + QCoreApplication::translate("WangSet", "Mixed"), + }; + + QMap icons; + icons.insert(WangSet::Corner, wangSetIcon(WangSet::Corner)); + icons.insert(WangSet::Edge, wangSetIcon(WangSet::Edge)); + icons.insert(WangSet::Mixed, wangSetIcon(WangSet::Mixed)); + + return { names, {}, icons }; +} + + PropertiesWidget::PropertiesWidget(QWidget *parent) : QWidget{parent} , mPropertyBrowser(new VariantEditor(this)) - , mPropertyFactory(std::make_unique()) { mActionAddProperty = new QAction(this); mActionAddProperty->setEnabled(false); @@ -109,7 +241,6 @@ PropertiesWidget::PropertiesWidget(QWidget *parent) // connect(mPropertyBrowser, &PropertyBrowser::selectedItemsChanged, // this, &PropertiesWidget::updateActions); - registerEditorFactories(); retranslateUi(); } @@ -201,11 +332,11 @@ class ClassProperty : public StringProperty editor->addItems(classNamesFor(*mObject)); auto syncEditor = [this, editor] { const QSignalBlocker blocker(editor); - editor->setCurrentText(value().toString()); + editor->setCurrentText(value()); }; syncEditor(); connect(this, &Property::valueChanged, editor, syncEditor); - connect(editor, &QComboBox::currentTextChanged, this, &Property::setValue); + connect(editor, &QComboBox::currentTextChanged, this, &StringProperty::setValue); connect(Preferences::instance(), &Preferences::propertyTypesChanged, editor, [this,editor] { editor->clear(); @@ -306,19 +437,16 @@ class MapProperties : public ObjectProperties Q_OBJECT public: - MapProperties(MapDocument *document, - PropertyFactory *propertyFactory, - QObject *parent = nullptr) + MapProperties(MapDocument *document, QObject *parent = nullptr) : ObjectProperties(document, document->map(), parent) { - mOrientationProperty = propertyFactory->createProperty( + mOrientationProperty = new EnumProperty( tr("Orientation"), - [this]() { - return QVariant::fromValue(map()->orientation()); + [this] { + return map()->orientation(); }, - [this](const QVariant &value) { - auto orientation = static_cast(value.toInt()); - push(new ChangeMapProperty(mapDocument(), orientation)); + [this](Map::Orientation value) { + push(new ChangeMapProperty(mapDocument(), value)); }); mSizeProperty = new MapSizeProperty(mapDocument(), this); @@ -347,7 +475,7 @@ class MapProperties : public ObjectProperties mInfiniteProperty = new BoolProperty( tr("Infinite"), - [this]() { + [this] { return map()->infinite(); }, [this](const bool &value) { @@ -358,7 +486,7 @@ class MapProperties : public ObjectProperties mHexSideLengthProperty = new IntProperty( tr("Hex Side Length"), - [this]() { + [this] { return map()->hexSideLength(); }, [this](const QVariant &value) { @@ -367,80 +495,76 @@ class MapProperties : public ObjectProperties value.toInt())); }); - mStaggerAxisProperty = propertyFactory->createProperty( + mStaggerAxisProperty = new EnumProperty( tr("Stagger Axis"), - [this]() { - return QVariant::fromValue(map()->staggerAxis()); + [this] { + return map()->staggerAxis(); }, - [this](const QVariant &value) { - auto staggerAxis = static_cast(value.toInt()); - push(new ChangeMapProperty(mapDocument(), staggerAxis)); + [this](Map::StaggerAxis value) { + push(new ChangeMapProperty(mapDocument(), value)); }); - mStaggerIndexProperty = propertyFactory->createProperty( + mStaggerIndexProperty = new EnumProperty( tr("Stagger Index"), - [this]() { - return QVariant::fromValue(map()->staggerIndex()); + [this] { + return map()->staggerIndex(); }, - [this](const QVariant &value) { - auto staggerIndex = static_cast(value.toInt()); - push(new ChangeMapProperty(mapDocument(), staggerIndex)); + [this](Map::StaggerIndex value) { + push(new ChangeMapProperty(mapDocument(), value)); }); - mParallaxOriginProperty = propertyFactory->createProperty( + mParallaxOriginProperty = new PointFProperty( tr("Parallax Origin"), - [this]() { + [this] { return map()->parallaxOrigin(); }, - [this](const QVariant &value) { - push(new ChangeMapProperty(mapDocument(), value.value())); + [this](const QPointF &value) { + push(new ChangeMapProperty(mapDocument(), value)); }); - mLayerDataFormatProperty = propertyFactory->createProperty( + mLayerDataFormatProperty = new EnumProperty( tr("Layer Data Format"), - [this]() { - return QVariant::fromValue(map()->layerDataFormat()); + [this] { + return map()->layerDataFormat(); }, - [this](const QVariant &value) { - auto layerDataFormat = static_cast(value.toInt()); - push(new ChangeMapProperty(mapDocument(), layerDataFormat)); + [this](Map::LayerDataFormat value) { + push(new ChangeMapProperty(mapDocument(), value)); }); - mChunkSizeProperty = propertyFactory->createProperty( + mChunkSizeProperty = new SizeProperty( tr("Output Chunk Size"), - [this]() { + [this] { return map()->chunkSize(); }, - [this](const QVariant &value) { - push(new ChangeMapProperty(mapDocument(), value.toSize())); + [this](const QSize &value) { + push(new ChangeMapProperty(mapDocument(), value)); }); - mRenderOrderProperty = propertyFactory->createProperty( + mRenderOrderProperty = new EnumProperty( tr("Tile Render Order"), - [this]() { - return QVariant::fromValue(map()->renderOrder()); + [this] { + return map()->renderOrder(); }, - [this](const QVariant &value) { - auto renderOrder = static_cast(value.toInt()); - push(new ChangeMapProperty(mapDocument(), renderOrder)); + [this](Map::RenderOrder value) { + push(new ChangeMapProperty(mapDocument(), value)); }); - mCompressionLevelProperty = propertyFactory->createProperty( + mCompressionLevelProperty = new IntProperty( tr("Compression Level"), - [this]() { + [this] { return map()->compressionLevel(); }, - [this](const QVariant &value) { - push(new ChangeMapProperty(mapDocument(), value.toInt())); + [this](const int &value) { + push(new ChangeMapProperty(mapDocument(), value)); }); - mBackgroundColorProperty = propertyFactory->createProperty( + mBackgroundColorProperty = new ColorProperty( tr("Background Color"), - [this]() { + [this] { return map()->backgroundColor(); }, - [this](const QVariant &value) { - push(new ChangeMapProperty(mapDocument(), value.value())); + [this](const QColor &value) { + push(new ChangeMapProperty(mapDocument(), value)); }); updateEnabledState(); @@ -576,69 +700,68 @@ class LayerProperties : public ObjectProperties Q_OBJECT public: - LayerProperties(MapDocument *document, Layer *object, PropertyFactory *propertyFactory, QObject *parent = nullptr) + LayerProperties(MapDocument *document, Layer *object, QObject *parent = nullptr) : ObjectProperties(document, object, parent) { // todo: would be nicer to avoid the SpinBox and use a custom widget // might also be nice to embed this in the header instead of using a property - mIdProperty = propertyFactory->createProperty( + mIdProperty = new IntProperty( tr("ID"), - [this]() { return layer()->id(); }, - [](const QVariant &) {}); + [this] { return layer()->id(); }); mIdProperty->setEnabled(false); // todo: the below should be able to apply to all selected layers - mNameProperty = propertyFactory->createProperty( + mNameProperty = new StringProperty( tr("Name"), - [this]() { return layer()->name(); }, - [this](const QVariant &value) { - push(new SetLayerName(mapDocument(), { layer() }, value.toString())); + [this] { return layer()->name(); }, + [this](const QString &value) { + push(new SetLayerName(mapDocument(), { layer() }, value)); }); - mVisibleProperty = propertyFactory->createProperty( + mVisibleProperty = new BoolProperty( tr("Visible"), - [this]() { return layer()->isVisible(); }, - [this](const QVariant &value) { - push(new SetLayerVisible(mapDocument(), { layer() }, value.toBool())); + [this] { return layer()->isVisible(); }, + [this](const bool &value) { + push(new SetLayerVisible(mapDocument(), { layer() }, value)); }); - mLockedProperty = propertyFactory->createProperty( + mLockedProperty = new BoolProperty( tr("Locked"), - [this]() { return layer()->isLocked(); }, - [this](const QVariant &value) { - push(new SetLayerLocked(mapDocument(), { layer() }, value.toBool())); + [this] { return layer()->isLocked(); }, + [this](const bool &value) { + push(new SetLayerLocked(mapDocument(), { layer() }, value)); }); // todo: value should be between 0 and 1, and would be nice to use a slider (replacing the one in Layers view) // todo: singleStep should be 0.1 - mOpacityProperty = propertyFactory->createProperty( + mOpacityProperty = new FloatProperty( tr("Opacity"), - [this]() { return layer()->opacity(); }, - [this](const QVariant &value) { - push(new SetLayerOpacity(mapDocument(), { layer() }, value.toReal())); + [this] { return layer()->opacity(); }, + [this](const double &value) { + push(new SetLayerOpacity(mapDocument(), { layer() }, value)); }); - mTintColorProperty = propertyFactory->createProperty( + mTintColorProperty = new ColorProperty( tr("Tint Color"), - [this]() { return layer()->tintColor(); }, - [this](const QVariant &value) { - push(new SetLayerTintColor(mapDocument(), { layer() }, value.value())); + [this] { return layer()->tintColor(); }, + [this](const QColor &value) { + push(new SetLayerTintColor(mapDocument(), { layer() }, value)); }); - mOffsetProperty = propertyFactory->createProperty( + mOffsetProperty = new PointFProperty( tr("Offset"), - [this]() { return layer()->offset(); }, - [this](const QVariant &value) { - push(new SetLayerOffset(mapDocument(), { layer() }, value.value())); + [this] { return layer()->offset(); }, + [this](const QPointF &value) { + push(new SetLayerOffset(mapDocument(), { layer() }, value)); }); // todo: singleStep should be 0.1 - mParallaxFactorProperty = propertyFactory->createProperty( + mParallaxFactorProperty = new PointFProperty( tr("Parallax Factor"), - [this]() { return layer()->parallaxFactor(); }, - [this](const QVariant &value) { - push(new SetLayerParallaxFactor(mapDocument(), { layer() }, value.toPointF())); + [this] { return layer()->parallaxFactor(); }, + [this](const QPointF &value) { + push(new SetLayerParallaxFactor(mapDocument(), { layer() }, value)); }); connect(document, &Document::changed, @@ -709,37 +832,37 @@ class ImageLayerProperties : public LayerProperties Q_OBJECT public: - ImageLayerProperties(MapDocument *document, ImageLayer *object, PropertyFactory *propertyFactory, QObject *parent = nullptr) - : LayerProperties(document, object, propertyFactory, parent) + ImageLayerProperties(MapDocument *document, ImageLayer *object, QObject *parent = nullptr) + : LayerProperties(document, object, parent) { // todo: set a file filter for selecting images (or map files?) - mImageProperty = propertyFactory->createProperty( + mImageProperty = new UrlProperty( tr("Image Source"), - [this]() { return imageLayer()->imageSource(); }, - [this](const QVariant &value) { - push(new ChangeImageLayerImageSource(mapDocument(), { imageLayer() }, value.toUrl())); + [this] { return imageLayer()->imageSource(); }, + [this](const QUrl &value) { + push(new ChangeImageLayerImageSource(mapDocument(), { imageLayer() }, value)); }); - mTransparentColorProperty = propertyFactory->createProperty( + mTransparentColorProperty = new ColorProperty( tr("Transparent Color"), - [this]() { return imageLayer()->transparentColor(); }, - [this](const QVariant &value) { - push(new ChangeImageLayerTransparentColor(mapDocument(), { imageLayer() }, value.value())); + [this] { return imageLayer()->transparentColor(); }, + [this](const QColor &value) { + push(new ChangeImageLayerTransparentColor(mapDocument(), { imageLayer() }, value)); }); // todo: consider merging Repeat X and Y into a single property - mRepeatXProperty = propertyFactory->createProperty( + mRepeatXProperty = new BoolProperty( tr("Repeat X"), - [this]() { return imageLayer()->repeatX(); }, - [this](const QVariant &value) { - push(new ChangeImageLayerRepeatX(mapDocument(), { imageLayer() }, value.toBool())); + [this] { return imageLayer()->repeatX(); }, + [this](const bool &value) { + push(new ChangeImageLayerRepeatX(mapDocument(), { imageLayer() }, value)); }); - mRepeatYProperty = propertyFactory->createProperty( + mRepeatYProperty = new BoolProperty( tr("Repeat Y"), - [this]() { return imageLayer()->repeatY(); }, - [this](const QVariant &value) { - push(new ChangeImageLayerRepeatY(mapDocument(), { imageLayer() }, value.toBool())); + [this] { return imageLayer()->repeatY(); }, + [this](const bool &value) { + push(new ChangeImageLayerRepeatY(mapDocument(), { imageLayer() }, value)); }); } @@ -792,22 +915,21 @@ class ObjectGroupProperties : public LayerProperties Q_OBJECT public: - ObjectGroupProperties(MapDocument *document, ObjectGroup *object, PropertyFactory *propertyFactory, QObject *parent = nullptr) - : LayerProperties(document, object, propertyFactory, parent) + ObjectGroupProperties(MapDocument *document, ObjectGroup *object, QObject *parent = nullptr) + : LayerProperties(document, object, parent) { - mColorProperty = propertyFactory->createProperty( + mColorProperty = new ColorProperty( tr("Color"), - [this]() { return objectGroup()->color(); }, - [this](const QVariant &value) { - push(new ChangeObjectGroupColor(mapDocument(), { objectGroup() }, value.value())); + [this] { return objectGroup()->color(); }, + [this](const QColor &value) { + push(new ChangeObjectGroupColor(mapDocument(), { objectGroup() }, value)); }); - mDrawOrderProperty = propertyFactory->createProperty( + mDrawOrderProperty = new EnumProperty( tr("Draw Order"), - [this]() { return QVariant::fromValue(objectGroup()->drawOrder()); }, - [this](const QVariant &value) { - ObjectGroup::DrawOrder drawOrder = static_cast(value.toInt()); - push(new ChangeObjectGroupDrawOrder(mapDocument(), { objectGroup() }, drawOrder)); + [this] { return objectGroup()->drawOrder(); }, + [this](ObjectGroup::DrawOrder value) { + push(new ChangeObjectGroupDrawOrder(mapDocument(), { objectGroup() }, value)); }); } @@ -851,115 +973,109 @@ class TilesetProperties : public ObjectProperties Q_OBJECT public: - TilesetProperties(TilesetDocument *document, - PropertyFactory *propertyFactory, - QObject *parent = nullptr) + TilesetProperties(TilesetDocument *document, QObject *parent = nullptr) : ObjectProperties(document, document->tileset().data(), parent) { - mNameProperty = propertyFactory->createProperty( + mNameProperty = new StringProperty( tr("Name"), - [this]() { + [this] { return tilesetDocument()->tileset()->name(); }, - [this](const QVariant &value) { - push(new RenameTileset(tilesetDocument(), value.toString())); + [this](const QString &value) { + push(new RenameTileset(tilesetDocument(), value)); }); - mObjectAlignmentProperty = propertyFactory->createProperty( + mObjectAlignmentProperty = new EnumProperty( tr("Object Alignment"), - [this]() { - return QVariant::fromValue(tileset()->objectAlignment()); + [this] { + return tileset()->objectAlignment(); }, - [this](const QVariant &value) { - const auto objectAlignment = static_cast(value.toInt()); - push(new ChangeTilesetObjectAlignment(tilesetDocument(), objectAlignment)); + [this](Alignment value) { + push(new ChangeTilesetObjectAlignment(tilesetDocument(), value)); }); - mTileOffsetProperty = propertyFactory->createProperty( + mTileOffsetProperty = new PointProperty( tr("Drawing Offset"), - [this]() { + [this] { return tileset()->tileOffset(); }, - [this](const QVariant &value) { - push(new ChangeTilesetTileOffset(tilesetDocument(), value.value())); + [this](const QPoint &value) { + push(new ChangeTilesetTileOffset(tilesetDocument(), value)); }); - mTileRenderSizeProperty = propertyFactory->createProperty( + mTileRenderSizeProperty = new EnumProperty( tr("Tile Render Size"), - [this]() { - return QVariant::fromValue(tileset()->tileRenderSize()); + [this] { + return tileset()->tileRenderSize(); }, - [this](const QVariant &value) { - const auto tileRenderSize = static_cast(value.toInt()); - push(new ChangeTilesetTileRenderSize(tilesetDocument(), tileRenderSize)); + [this](Tileset::TileRenderSize value) { + push(new ChangeTilesetTileRenderSize(tilesetDocument(), value)); }); - mFillModeProperty = propertyFactory->createProperty( + mFillModeProperty = new EnumProperty( tr("Fill Mode"), - [this]() { - return QVariant::fromValue(tileset()->fillMode()); + [this] { + return tileset()->fillMode(); }, - [this](const QVariant &value) { - const auto fillMode = static_cast(value.toInt()); - push(new ChangeTilesetFillMode(tilesetDocument(), fillMode)); + [this](Tileset::FillMode value) { + push(new ChangeTilesetFillMode(tilesetDocument(), value)); }); - mBackgroundColorProperty = propertyFactory->createProperty( + mBackgroundColorProperty = new ColorProperty( tr("Background Color"), - [this]() { + [this] { return tileset()->backgroundColor(); }, - [this](const QVariant &value) { - push(new ChangeTilesetBackgroundColor(tilesetDocument(), value.value())); + [this](const QColor &value) { + push(new ChangeTilesetBackgroundColor(tilesetDocument(), value)); }); - mOrientationProperty = propertyFactory->createProperty( + mOrientationProperty = new EnumProperty( tr("Orientation"), - [this]() { - return QVariant::fromValue(tileset()->orientation()); + [this] { + return tileset()->orientation(); }, - [this](const QVariant &value) { - const auto orientation = static_cast(value.toInt()); - push(new ChangeTilesetOrientation(tilesetDocument(), orientation)); + [this](Tileset::Orientation value) { + push(new ChangeTilesetOrientation(tilesetDocument(), value)); }); - mGridSizeProperty = propertyFactory->createProperty( + mGridSizeProperty = new SizeProperty( tr("Grid Size"), - [this]() { + [this] { return tileset()->gridSize(); }, - [this](const QVariant &value) { - push(new ChangeTilesetGridSize(tilesetDocument(), value.toSize())); + [this](const QSize &value) { + push(new ChangeTilesetGridSize(tilesetDocument(), value)); }); // todo: needs 1 as minimum value - mColumnCountProperty = propertyFactory->createProperty( + mColumnCountProperty = new IntProperty( tr("Columns"), - [this]() { + [this] { return tileset()->columnCount(); }, - [this](const QVariant &value) { - push(new ChangeTilesetColumnCount(tilesetDocument(), value.toInt())); + [this](const int &value) { + push(new ChangeTilesetColumnCount(tilesetDocument(), value)); }); // todo: this needs a custom widget - mAllowedTransformationsProperty = propertyFactory->createProperty( + mAllowedTransformationsProperty = new IntProperty( tr("Allowed Transformations"), - [this]() { - return QVariant::fromValue(tileset()->transformationFlags()); + [this] { + return static_cast(tileset()->transformationFlags()); }, - [this](const QVariant &value) { - const auto flags = static_cast(value.toInt()); + [this](const int &value) { + const auto flags = static_cast(value); push(new ChangeTilesetTransformationFlags(tilesetDocument(), flags)); }); // todo: this needs a custom widget - mImageProperty = propertyFactory->createProperty( + mImageProperty = new UrlProperty( tr("Image"), - [this]() { - return tileset()->imageSource().toString(); + [this] { + return tileset()->imageSource(); }, - [](const QVariant &) { + [](const QUrl &) { // push(new ChangeTilesetImage(tilesetDocument(), value.toString())); }); @@ -1058,62 +1174,62 @@ class MapObjectProperties : public ObjectProperties Q_OBJECT public: - MapObjectProperties(MapDocument *document, MapObject *object, PropertyFactory *propertyFactory, QObject *parent = nullptr) + MapObjectProperties(MapDocument *document, MapObject *object, QObject *parent = nullptr) : ObjectProperties(document, object, parent) { mIdProperty = new IntProperty( tr("ID"), - [this]() { return mapObject()->id(); }); + [this] { return mapObject()->id(); }); mIdProperty->setEnabled(false); mTemplateProperty = new UrlProperty( tr("Template"), - [this]() { + [this] { if (auto objectTemplate = mapObject()->objectTemplate()) return QUrl::fromLocalFile(objectTemplate->fileName()); return QUrl(); }); mTemplateProperty->setEnabled(false); - mNameProperty = propertyFactory->createProperty( + mNameProperty = new StringProperty( tr("Name"), - [this]() { + [this] { return mapObject()->name(); }, - [this](const QVariant &value) { + [this](const QString &value) { changeMapObject(MapObject::NameProperty, value); }); - mVisibleProperty = propertyFactory->createProperty( + mVisibleProperty = new BoolProperty( tr("Visible"), - [this]() { + [this] { return mapObject()->isVisible(); }, - [this](const QVariant &value) { + [this](const bool &value) { changeMapObject(MapObject::VisibleProperty, value); }); - mPositionProperty = propertyFactory->createProperty( + mPositionProperty = new PointFProperty( tr("Position"), - [this]() { + [this] { return mapObject()->position(); }, - [this](const QVariant &value) { + [this](const QPointF &value) { changeMapObject(MapObject::PositionProperty, value); }); - mSizeProperty = propertyFactory->createProperty( + mSizeProperty = new SizeFProperty( tr("Size"), - [this]() { + [this] { return mapObject()->size(); }, - [this](const QVariant &value) { + [this](const QSizeF &value) { changeMapObject(MapObject::SizeProperty, value); }); mRotationProperty = new FloatProperty( tr("Rotation"), - [this]() { + [this] { return mapObject()->rotation(); }, [this](const qreal &value) { @@ -1123,13 +1239,13 @@ class MapObjectProperties : public ObjectProperties mRotationProperty->setSuffix(QStringLiteral("°")); // todo: make this a custom widget with "Horizontal" and "Vertical" checkboxes - mFlippingProperty = propertyFactory->createProperty( + mFlippingProperty = new IntProperty( tr("Flipping"), - [this]() { + [this] { return mapObject()->cell().flags(); }, - [this](const QVariant &value) { - const int flippingFlags = value.toInt(); + [this](const int &value) { + const int flippingFlags = value; MapObjectCell mapObjectCell; mapObjectCell.object = mapObject(); @@ -1146,48 +1262,50 @@ class MapObjectProperties : public ObjectProperties push(command); }); - mTextProperty = propertyFactory->createProperty( + // todo: allow opening the multi-line text dialog + mTextProperty = new StringProperty( tr("Text"), - [this]() { + [this] { return mapObject()->textData().text; }, - [this](const QVariant &value) { + [this](const QString &value) { changeMapObject(MapObject::TextProperty, value); }); - mTextAlignmentProperty = propertyFactory->createProperty( + mTextAlignmentProperty = new QtAlignmentProperty( tr("Alignment"), - [this]() { - return QVariant::fromValue(mapObject()->textData().alignment); + [this] { + return mapObject()->textData().alignment; }, - [this](const QVariant &value) { - changeMapObject(MapObject::TextAlignmentProperty, value); + [this](const Qt::Alignment &value) { + changeMapObject(MapObject::TextAlignmentProperty, + QVariant::fromValue(value)); }); - mTextFontProperty = propertyFactory->createProperty( + mTextFontProperty = new FontProperty( tr("Font"), - [this]() { + [this] { return mapObject()->textData().font; }, - [this](const QVariant &value) { + [this](const QFont &value) { changeMapObject(MapObject::TextFontProperty, value); }); - mTextWordWrapProperty = propertyFactory->createProperty( + mTextWordWrapProperty = new BoolProperty( tr("Word Wrap"), - [this]() { + [this] { return mapObject()->textData().wordWrap; }, - [this](const QVariant &value) { + [this](const bool &value) { changeMapObject(MapObject::TextWordWrapProperty, value); }); - mTextColorProperty = propertyFactory->createProperty( + mTextColorProperty = new ColorProperty( tr("Text Color"), - [this]() { + [this] { return mapObject()->textData().color; }, - [this](const QVariant &value) { + [this](const QColor &value) { changeMapObject(MapObject::TextColorProperty, value); }); @@ -1320,42 +1438,41 @@ class TileProperties : public ObjectProperties Q_OBJECT public: - TileProperties(Document *document, Tile *object, PropertyFactory *propertyFactory, QObject *parent = nullptr) + TileProperties(Document *document, Tile *object, QObject *parent = nullptr) : ObjectProperties(document, object, parent) { - mIdProperty = propertyFactory->createProperty( + mIdProperty = new IntProperty( tr("ID"), - [this]() { return tile()->id(); }, - [](const QVariant &) {}); + [this] { return tile()->id(); }); mIdProperty->setEnabled(false); // todo: apply readableImageFormatsFilter - mImageProperty = propertyFactory->createProperty( + mImageProperty = new UrlProperty( tr("Image"), - [this]() { return tile()->imageSource(); }, - [this](const QVariant &value) { + [this] { return tile()->imageSource(); }, + [this](const QUrl &value) { push(new ChangeTileImageSource(tilesetDocument(), tile(), - value.toUrl())); + value)); }); - mRectangleProperty = propertyFactory->createProperty( + mRectangleProperty = new RectProperty( tr("Rectangle"), - [this]() { return tile()->imageRect(); }, - [this](const QVariant &value) { + [this] { return tile()->imageRect(); }, + [this](const QRect &value) { push(new ChangeTileImageRect(tilesetDocument(), { tile() }, - { value.toRect() })); + { value })); }); // todo: minimum value should be 0 - mProbabilityProperty = propertyFactory->createProperty( + mProbabilityProperty = new FloatProperty( tr("Probability"), - [this]() { return tile()->probability(); }, - [this](const QVariant &value) { + [this] { return tile()->probability(); }, + [this](const double &value) { push(new ChangeTileProbability(tilesetDocument(), tilesetDocument()->selectedTiles(), - value.toReal())); + value)); }); mProbabilityProperty->setToolTip(tr("Relative chance this tile will be picked")); @@ -1439,31 +1556,31 @@ class WangSetProperties : public ObjectProperties public: WangSetProperties(Document *document, WangSet *object, - PropertyFactory *propertyFactory, QObject *parent = nullptr) + QObject *parent = nullptr) : ObjectProperties(document, object, parent) { - mNameProperty = propertyFactory->createProperty( + mNameProperty = new StringProperty( tr("Name"), - [this]() { return wangSet()->name(); }, - [this](const QVariant &value) { - push(new RenameWangSet(tilesetDocument(), wangSet(), value.toString())); + [this] { return wangSet()->name(); }, + [this](const QString &value) { + push(new RenameWangSet(tilesetDocument(), wangSet(), value)); }); - mTypeProperty = propertyFactory->createProperty( + mTypeProperty = new EnumProperty( tr("Type"), - [this]() { return QVariant::fromValue(wangSet()->type()); }, - [this](const QVariant &value) { - push(new ChangeWangSetType(tilesetDocument(), wangSet(), static_cast(value.toInt()))); + [this] { return wangSet()->type(); }, + [this](WangSet::Type value) { + push(new ChangeWangSetType(tilesetDocument(), wangSet(), value)); }); // todo: keep between 0 and WangId::MAX_COLOR_COUNT - mColorCountProperty = propertyFactory->createProperty( + mColorCountProperty = new IntProperty( tr("Color Count"), - [this]() { return wangSet()->colorCount(); }, - [this](const QVariant &value) { + [this] { return wangSet()->colorCount(); }, + [this](const int &value) { push(new ChangeWangSetColorCount(tilesetDocument(), wangSet(), - value.toInt())); + value)); }); connect(document, &Document::changed, @@ -1536,29 +1653,29 @@ class WangColorProperties : public ObjectProperties public: WangColorProperties(Document *document, WangColor *object, - PropertyFactory *propertyFactory, QObject *parent = nullptr) + QObject *parent = nullptr) : ObjectProperties(document, object, parent) { - mNameProperty = propertyFactory->createProperty( + mNameProperty = new StringProperty( tr("Name"), - [this]() { return wangColor()->name(); }, - [this](const QVariant &value) { - push(new ChangeWangColorName(tilesetDocument(), wangColor(), value.toString())); + [this] { return wangColor()->name(); }, + [this](const QString &value) { + push(new ChangeWangColorName(tilesetDocument(), wangColor(), value)); }); - mColorProperty = propertyFactory->createProperty( + mColorProperty = new ColorProperty( tr("Color"), - [this]() { return wangColor()->color(); }, - [this](const QVariant &value) { - push(new ChangeWangColorColor(tilesetDocument(), wangColor(), value.value())); + [this] { return wangColor()->color(); }, + [this](const QColor &value) { + push(new ChangeWangColorColor(tilesetDocument(), wangColor(), value)); }); // todo: set 0.01 as minimum - mProbabilityProperty = propertyFactory->createProperty( + mProbabilityProperty = new FloatProperty( tr("Probability"), - [this]() { return wangColor()->probability(); }, - [this](const QVariant &value) { - push(new ChangeWangColorProbability(tilesetDocument(), wangColor(), value.toReal())); + [this] { return wangColor()->probability(); }, + [this](const double &value) { + push(new ChangeWangColorProbability(tilesetDocument(), wangColor(), value)); }); connect(document, &Document::changed, @@ -1643,48 +1760,48 @@ void PropertiesWidget::currentObjectChanged(Object *object) case Layer::ImageLayerType: mPropertiesObject = new ImageLayerProperties(mapDocument, static_cast(object), - mPropertyFactory.get(), this); + this); break; case Layer::ObjectGroupType: mPropertiesObject = new ObjectGroupProperties(mapDocument, static_cast(object), - mPropertyFactory.get(), this); + this); break; case Layer::TileLayerType: case Layer::GroupLayerType: mPropertiesObject = new LayerProperties(mapDocument, static_cast(object), - mPropertyFactory.get(), this); + this); break; } break; } case Object::MapObjectType: mPropertiesObject = new MapObjectProperties(static_cast(mDocument), - static_cast(object), mPropertyFactory.get(), this); + static_cast(object), this); break; case Object::MapType: mPropertiesObject = new MapProperties(static_cast(mDocument), - mPropertyFactory.get(), this); + this); break; case Object::TilesetType: mPropertiesObject = new TilesetProperties(static_cast(mDocument), - mPropertyFactory.get(), this); + this); break; case Object::TileType: mPropertiesObject = new TileProperties(mDocument, static_cast(object), - mPropertyFactory.get(), this); + this); break; case Object::WangSetType: mPropertiesObject = new WangSetProperties(mDocument, static_cast(object), - mPropertyFactory.get(), this); + this); break; case Object::WangColorType: mPropertiesObject = new WangColorProperties(mDocument, static_cast(object), - mPropertyFactory.get(), this); + this); break; case Object::ProjectType: case Object::WorldType: @@ -2076,136 +2193,6 @@ void PropertiesWidget::keyPressEvent(QKeyEvent *event) } } -void PropertiesWidget::registerEditorFactories() -{ - registerEditorFactory(qMetaTypeId(), - std::make_unique( - QStringList { - tr("Unspecified"), - tr("Top Left"), - tr("Top"), - tr("Top Right"), - tr("Left"), - tr("Center"), - tr("Right"), - tr("Bottom Left"), - tr("Bottom"), - tr("Bottom Right"), - })); - - // We leave out the "Unknown" orientation, because it shouldn't occur here - registerEditorFactory(qMetaTypeId(), - std::make_unique( - QStringList { - tr("Orthogonal"), - tr("Isometric"), - tr("Isometric (Staggered)"), - tr("Hexagonal (Staggered)"), - }, - QList { - Map::Orthogonal, - Map::Isometric, - Map::Staggered, - Map::Hexagonal, - })); - - registerEditorFactory(qMetaTypeId(), - std::make_unique( - QStringList { - tr("X"), - tr("Y"), - })); - - registerEditorFactory(qMetaTypeId(), - std::make_unique( - QStringList { - tr("Odd"), - tr("Even"), - })); - - QStringList layerFormatNames = { - QCoreApplication::translate("PreferencesDialog", "XML (deprecated)"), - QCoreApplication::translate("PreferencesDialog", "Base64 (uncompressed)"), - QCoreApplication::translate("PreferencesDialog", "Base64 (gzip compressed)"), - QCoreApplication::translate("PreferencesDialog", "Base64 (zlib compressed)"), - }; - QList layerFormatValues = { - Map::XML, - Map::Base64, - Map::Base64Gzip, - Map::Base64Zlib, - }; - - if (compressionSupported(Zstandard)) { - layerFormatNames.append(QCoreApplication::translate("PreferencesDialog", "Base64 (Zstandard compressed)")); - layerFormatValues.append(Map::Base64Zstandard); - } - - layerFormatNames.append(QCoreApplication::translate("PreferencesDialog", "CSV")); - layerFormatValues.append(Map::CSV); - - registerEditorFactory(qMetaTypeId(), - std::make_unique(layerFormatNames, layerFormatValues)); - - registerEditorFactory(qMetaTypeId(), - std::make_unique( - QStringList { - tr("Right Down"), - tr("Right Up"), - tr("Left Down"), - tr("Left Up"), - })); - - registerEditorFactory(qMetaTypeId(), - std::make_unique( - QStringList { - tr("Orthogonal"), - tr("Isometric"), - })); - - registerEditorFactory(qMetaTypeId(), - std::make_unique( - QStringList { - tr("Tile Size"), - tr("Map Grid Size"), - })); - - registerEditorFactory(qMetaTypeId(), - std::make_unique( - QStringList { - tr("Stretch"), - tr("Preserve Aspect Ratio"), - })); - - registerEditorFactory(qMetaTypeId(), - std::make_unique( - QStringList { - tr("Top Down"), - tr("Index Order"), - })); - - auto wangSetTypeEditorFactory = std::make_unique( - QStringList { - tr("Corner"), - tr("Edge"), - tr("Mixed"), - }); - - QMap mWangSetIcons; - mWangSetIcons.insert(WangSet::Corner, wangSetIcon(WangSet::Corner)); - mWangSetIcons.insert(WangSet::Edge, wangSetIcon(WangSet::Edge)); - mWangSetIcons.insert(WangSet::Mixed, wangSetIcon(WangSet::Mixed)); - wangSetTypeEditorFactory->setEnumIcons(mWangSetIcons); - - registerEditorFactory(qMetaTypeId(), - std::move(wangSetTypeEditorFactory)); -} - -void PropertiesWidget::registerEditorFactory(int type, std::unique_ptr factory) -{ - mPropertyFactory->registerEditorFactory(type, std::move(factory)); -} - void PropertiesWidget::retranslateUi() { mActionAddProperty->setText(QCoreApplication::translate("Tiled::PropertiesDock", "Add Property")); diff --git a/src/tiled/propertieswidget.h b/src/tiled/propertieswidget.h index c3a2bb0088..443529e38a 100644 --- a/src/tiled/propertieswidget.h +++ b/src/tiled/propertieswidget.h @@ -22,16 +22,12 @@ #include -#include - namespace Tiled { class Object; class Document; -class EditorFactory; class ObjectProperties; -class PropertyFactory; class VariantEditor; /** @@ -63,9 +59,6 @@ public slots: void keyPressEvent(QKeyEvent *event) override; private: - void registerEditorFactories(); - void registerEditorFactory(int type, std::unique_ptr factory); - void currentObjectChanged(Object *object); void updateActions(); @@ -84,7 +77,6 @@ public slots: Document *mDocument = nullptr; ObjectProperties *mPropertiesObject = nullptr; VariantEditor *mPropertyBrowser; - std::unique_ptr mPropertyFactory; QAction *mActionAddProperty; QAction *mActionRemoveProperty; QAction *mActionRenameProperty; diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index f73b582fb0..38d9629d0e 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -36,45 +36,19 @@ #include #include #include -#include namespace Tiled { -AbstractProperty::AbstractProperty(const QString &name, - EditorFactory *editorFactory, - QObject *parent) - : Property(name, parent) - , m_editorFactory(editorFactory) -{} - -QWidget *AbstractProperty::createEditor(QWidget *parent) -{ - return m_editorFactory ? m_editorFactory->createEditor(this, parent) - : nullptr; -} - - -GetSetProperty::GetSetProperty(const QString &name, - std::function get, - std::function set, - EditorFactory *editorFactory, - QObject *parent) - : AbstractProperty(name, editorFactory, parent) - , m_get(std::move(get)) - , m_set(std::move(set)) -{} - - QWidget *StringProperty::createEditor(QWidget *parent) { auto editor = new QLineEdit(parent); auto syncEditor = [=] { - editor->setText(m_get()); + editor->setText(value()); }; syncEditor(); QObject::connect(this, &Property::valueChanged, editor, syncEditor); - QObject::connect(editor, &QLineEdit::textEdited, this, m_set); + QObject::connect(editor, &QLineEdit::textEdited, this, &StringProperty::setValue); return editor; } @@ -85,12 +59,12 @@ QWidget *UrlProperty::createEditor(QWidget *parent) editor->setFilter(m_filter); auto syncEditor = [=] { - editor->setFileUrl(m_get()); + editor->setFileUrl(value()); }; syncEditor(); QObject::connect(this, &Property::valueChanged, editor, syncEditor); - QObject::connect(editor, &FileEdit::fileUrlChanged, this, m_set); + QObject::connect(editor, &FileEdit::fileUrlChanged, this, &UrlProperty::setValue); return editor; } @@ -100,13 +74,13 @@ QWidget *IntProperty::createEditor(QWidget *parent) auto editor = new SpinBox(parent); auto syncEditor = [=] { const QSignalBlocker blocker(editor); - editor->setValue(m_get()); + editor->setValue(value()); }; syncEditor(); QObject::connect(this, &Property::valueChanged, editor, syncEditor); QObject::connect(editor, qOverload(&SpinBox::valueChanged), - this, m_set); + this, &IntProperty::setValue); return editor; } @@ -118,13 +92,13 @@ QWidget *FloatProperty::createEditor(QWidget *parent) auto syncEditor = [=] { const QSignalBlocker blocker(editor); - editor->setValue(m_get()); + editor->setValue(value()); }; syncEditor(); QObject::connect(this, &Property::valueChanged, editor, syncEditor); QObject::connect(editor, qOverload(&DoubleSpinBox::valueChanged), - this, m_set); + this, &FloatProperty::setValue); return editor; } @@ -134,7 +108,7 @@ QWidget *BoolProperty::createEditor(QWidget *parent) auto editor = new QCheckBox(parent); auto syncEditor = [=] { const QSignalBlocker blocker(editor); - bool checked = m_get(); + bool checked = value(); editor->setChecked(checked); editor->setText(checked ? tr("On") : tr("Off")); }; @@ -143,7 +117,7 @@ QWidget *BoolProperty::createEditor(QWidget *parent) QObject::connect(this, &Property::valueChanged, editor, syncEditor); QObject::connect(editor, &QCheckBox::toggled, this, [=](bool checked) { editor->setText(checked ? QObject::tr("On") : QObject::tr("Off")); - m_set(checked); + setValue(checked); }); return editor; @@ -154,14 +128,14 @@ QWidget *PointProperty::createEditor(QWidget *parent) auto editor = new PointEdit(parent); auto syncEditor = [this, editor] { const QSignalBlocker blocker(editor); - editor->setValue(m_get()); + editor->setValue(value()); }; syncEditor(); QObject::connect(this, &Property::valueChanged, editor, syncEditor); QObject::connect(editor, &PointEdit::valueChanged, this, [this, editor] { - m_set(editor->value()); + setValue(editor->value()); }); return editor; @@ -172,14 +146,14 @@ QWidget *PointFProperty::createEditor(QWidget *parent) auto editor = new PointFEdit(parent); auto syncEditor = [this, editor] { const QSignalBlocker blocker(editor); - editor->setValue(this->value().toPointF()); + editor->setValue(value()); }; syncEditor(); QObject::connect(this, &Property::valueChanged, editor, syncEditor); QObject::connect(editor, &PointFEdit::valueChanged, this, [this, editor] { - this->setValue(editor->value()); + this->setVariantValue(editor->value()); }); return editor; @@ -190,14 +164,14 @@ QWidget *SizeProperty::createEditor(QWidget *parent) auto editor = new SizeEdit(parent); auto syncEditor = [this, editor] { const QSignalBlocker blocker(editor); - editor->setValue(m_get()); + editor->setValue(value()); }; syncEditor(); QObject::connect(this, &Property::valueChanged, editor, syncEditor); QObject::connect(editor, &SizeEdit::valueChanged, this, [this, editor] { - m_set(editor->value()); + setValue(editor->value()); }); return editor; @@ -208,14 +182,14 @@ QWidget *SizeFProperty::createEditor(QWidget *parent) auto editor = new SizeFEdit(parent); auto syncEditor = [this, editor] { const QSignalBlocker blocker(editor); - editor->setValue(this->value().toSizeF()); + editor->setValue(value()); }; syncEditor(); QObject::connect(this, &Property::valueChanged, editor, syncEditor); QObject::connect(editor, &SizeFEdit::valueChanged, this, [this, editor] { - this->setValue(editor->value()); + setValue(editor->value()); }); return editor; @@ -226,15 +200,15 @@ QWidget *RectProperty::createEditor(QWidget *parent) auto editor = new RectEdit(parent); auto syncEditor = [this, editor] { const QSignalBlocker blocker(editor); - editor->setValue(this->value().toRect()); + editor->setValue(value()); }; syncEditor(); QObject::connect(this, &Property::valueChanged, editor, syncEditor); QObject::connect(editor, &RectEdit::valueChanged, this, [this, editor] { - this->setValue(editor->value()); - }); + setValue(editor->value()); + }); return editor; } @@ -244,14 +218,14 @@ QWidget *RectFProperty::createEditor(QWidget *parent) auto editor = new RectFEdit(parent); auto syncEditor = [this, editor] { const QSignalBlocker blocker(editor); - editor->setValue(this->value().toRectF()); + editor->setValue(value()); }; syncEditor(); QObject::connect(this, &Property::valueChanged, editor, syncEditor); QObject::connect(editor, &RectFEdit::valueChanged, this, [this, editor] { - this->setValue(editor->value()); + setValue(editor->value()); }); return editor; @@ -263,14 +237,14 @@ QWidget *ColorProperty::createEditor(QWidget *parent) auto editor = new ColorButton(parent); auto syncEditor = [=] { const QSignalBlocker blocker(editor); - editor->setColor(this->value().value()); + editor->setColor(value()); }; syncEditor(); QObject::connect(this, &Property::valueChanged, editor, syncEditor); QObject::connect(editor, &ColorButton::colorChanged, this, [this, editor] { - this->setValue(editor->color()); + setValue(editor->color()); }); return editor; @@ -301,7 +275,7 @@ QWidget *FontProperty::createEditor(QWidget *parent) layout->addWidget(kerningCheckBox); auto syncEditor = [=] { - const auto font = this->value().value(); + const auto font = value(); const QSignalBlocker fontBlocker(fontComboBox); const QSignalBlocker sizeBlocker(sizeSpinBox); const QSignalBlocker boldBlocker(boldCheckBox); @@ -326,7 +300,7 @@ QWidget *FontProperty::createEditor(QWidget *parent) font.setUnderline(underlineCheckBox->isChecked()); font.setStrikeOut(strikeoutCheckBox->isChecked()); font.setKerning(kerningCheckBox->isChecked()); - this->setValue(font); + setValue(font); }; syncEditor(); @@ -343,7 +317,7 @@ QWidget *FontProperty::createEditor(QWidget *parent) return editor; } -QWidget *AlignmentProperty::createEditor(QWidget *parent) +QWidget *QtAlignmentProperty::createEditor(QWidget *parent) { auto editor = new QWidget(parent); auto layout = new QGridLayout(editor); @@ -374,15 +348,14 @@ QWidget *AlignmentProperty::createEditor(QWidget *parent) auto syncEditor = [=] { const QSignalBlocker horizontalBlocker(horizontalComboBox); const QSignalBlocker verticalBlocker(verticalComboBox); - const auto alignment = this->value().value(); + const auto alignment = value(); horizontalComboBox->setCurrentIndex(horizontalComboBox->findData(static_cast(alignment & Qt::AlignHorizontal_Mask))); verticalComboBox->setCurrentIndex(verticalComboBox->findData(static_cast(alignment & Qt::AlignVertical_Mask))); }; auto syncProperty = [=] { - const Qt::Alignment alignment(horizontalComboBox->currentData().toInt() | - verticalComboBox->currentData().toInt()); - this->setValue(QVariant::fromValue(alignment)); + setValue(Qt::Alignment(horizontalComboBox->currentData().toInt() | + verticalComboBox->currentData().toInt())); }; syncEditor(); @@ -395,39 +368,6 @@ QWidget *AlignmentProperty::createEditor(QWidget *parent) } -ValueProperty::ValueProperty(const QString &name, - const QVariant &value, - EditorFactory *editorFactory, - QObject *parent) - : AbstractProperty(name, editorFactory, parent) - , m_value(value) -{} - -void ValueProperty::setValue(const QVariant &value) -{ - if (m_value != value) { - m_value = value; - emit valueChanged(); - } -} - - -EnumProperty::EnumProperty(const QString &name, - QObject *parent) - : AbstractProperty(name, &m_editorFactory, parent) -{} - -void EnumProperty::setEnumNames(const QStringList &enumNames) -{ - m_editorFactory.setEnumNames(enumNames); -} - -void EnumProperty::setEnumValues(const QList &enumValues) -{ - m_editorFactory.setEnumValues(enumValues); -} - - VariantEditor::VariantEditor(QWidget *parent) : QScrollArea(parent) { @@ -567,70 +507,45 @@ QWidget *VariantEditor::createEditor(Property *property) } -EnumEditorFactory::EnumEditorFactory(const QStringList &enumNames, - const QList &enumValues) - : m_enumNamesModel(enumNames) - , m_enumValues(enumValues) -{} - -void EnumEditorFactory::setEnumNames(const QStringList &enumNames) -{ - m_enumNamesModel.setStringList(enumNames); -} - -void EnumEditorFactory::setEnumIcons(const QMap &enumIcons) -{ - // todo: add support for showing these icons in the QComboBox - m_enumIcons = enumIcons; -} - -void EnumEditorFactory::setEnumValues(const QList &enumValues) -{ - m_enumValues = enumValues; -} - -QWidget *EnumEditorFactory::createEditor(Property *property, QWidget *parent) +QWidget *createEnumEditor(IntProperty *property, const EnumData &enumData, QWidget *parent) { auto editor = new QComboBox(parent); // This allows the combo box to shrink horizontally. editor->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); - editor->setModel(&m_enumNamesModel); - auto syncEditor = [property, editor, this] { + for (qsizetype i = 0; i < enumData.names.size(); ++i) { + auto value = enumData.values.isEmpty() ? i : enumData.values.value(i); + editor->addItem(enumData.icons[value], + enumData.names[i], + value); + } + + auto syncEditor = [property, editor] { const QSignalBlocker blocker(editor); - if (m_enumValues.isEmpty()) - editor->setCurrentIndex(property->value().toInt()); - else - editor->setCurrentIndex(m_enumValues.indexOf(property->value().toInt())); + editor->setCurrentIndex(editor->findData(property->value())); }; syncEditor(); QObject::connect(property, &Property::valueChanged, editor, syncEditor); QObject::connect(editor, qOverload(&QComboBox::currentIndexChanged), property, - [property, this](int index) { - property->setValue(m_enumValues.isEmpty() ? index : m_enumValues.at(index)); + [editor, property] { + property->setValue(editor->currentData().toInt()); }); return editor; } - -void PropertyFactory::registerEditorFactory(int type, std::unique_ptr factory) -{ - m_factories[type] = std::move(factory); -} - Property *PropertyFactory::createQObjectProperty(QObject *qObject, - const char *name, + const char *propertyName, const QString &displayName) { auto metaObject = qObject->metaObject(); - auto propertyIndex = metaObject->indexOfProperty(name); + auto propertyIndex = metaObject->indexOfProperty(propertyName); if (propertyIndex < 0) return nullptr; auto metaProperty = metaObject->property(propertyIndex); - auto property = createProperty(displayName.isEmpty() ? QString::fromUtf8(name) + auto property = createProperty(displayName.isEmpty() ? QString::fromUtf8(propertyName) : displayName, [=] { return metaProperty.read(qObject); @@ -655,15 +570,6 @@ Property *PropertyFactory::createQObjectProperty(QObject *qObject, return property; } -ValueProperty *PropertyFactory::createProperty(const QString &name, - const QVariant &value) -{ - auto f = m_factories.find(value.userType()); - return new ValueProperty(name, value, - f != m_factories.end() ? f->second.get() - : nullptr); -} - template Property *createTypedProperty(const QString &name, std::function get, @@ -708,14 +614,10 @@ Property *PropertyFactory::createProperty(const QString &name, return createTypedProperty(name, get, set); default: if (type == qMetaTypeId()) - return createTypedProperty(name, get, set); + return createTypedProperty(name, get, set); } - // Fall back to registered factories approach (still used for enums) - auto f = m_factories.find(get().userType()); - return new GetSetProperty(name, get, set, - f != m_factories.end() ? f->second.get() - : nullptr); + return nullptr; } } // namespace Tiled diff --git a/src/tiled/varianteditor.h b/src/tiled/varianteditor.h index 7c257b9bc8..8fdb125ea2 100644 --- a/src/tiled/varianteditor.h +++ b/src/tiled/varianteditor.h @@ -28,9 +28,6 @@ #include #include -#include -#include - class QGridLayout; namespace Tiled { @@ -43,7 +40,7 @@ class Property : public QObject Q_OBJECT Q_PROPERTY(QString name READ name CONSTANT) Q_PROPERTY(QString toolTip READ toolTip WRITE setToolTip NOTIFY toolTipChanged) - Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged) + Q_PROPERTY(QVariant value READ variantValue WRITE setVariantValue NOTIFY valueChanged) Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged) public: @@ -72,8 +69,8 @@ class Property : public QObject } } - virtual QVariant value() const = 0; - virtual void setValue(const QVariant &value) = 0; + virtual QVariant variantValue() const = 0; + virtual void setVariantValue(const QVariant &value) = 0; virtual QWidget *createEditor(QWidget *parent) = 0; @@ -106,18 +103,21 @@ class PropertyTemplate : public Property , m_set(std::move(set)) {} - QVariant value() const override + Type value() const { return m_get(); } + void setValue(const Type &value) { m_set(value); } + + QVariant variantValue() const override { return QVariant::fromValue(m_get()); } - void setValue(const QVariant &value) override + void setVariantValue(const QVariant &value) override { if (m_set) m_set(value.value()); } -protected: +private: std::function m_get; std::function m_set; }; @@ -207,115 +207,12 @@ struct FontProperty : PropertyTemplate QWidget *createEditor(QWidget *parent) override; }; -struct AlignmentProperty : PropertyTemplate +struct QtAlignmentProperty : PropertyTemplate { using PropertyTemplate::PropertyTemplate; QWidget *createEditor(QWidget *parent) override; }; -/** - * An editor factory is responsible for creating an editor widget for a given - * property. It can be used to share the configuration of editor widgets - * between different properties. - */ -class EditorFactory -{ - Q_DECLARE_TR_FUNCTIONS(EditorFactory) - -public: - virtual QWidget *createEditor(Property *property, QWidget *parent) = 0; -}; - -/** - * An editor factory that creates a combo box for enum properties. - */ -class EnumEditorFactory : public EditorFactory -{ -public: - EnumEditorFactory(const QStringList &enumNames = {}, - const QList &enumValues = {}); - - void setEnumNames(const QStringList &enumNames); - void setEnumIcons(const QMap &enumIcons); - void setEnumValues(const QList &enumValues); - - QWidget *createEditor(Property *property, QWidget *parent) override; - -private: - QStringListModel m_enumNamesModel; - QMap m_enumIcons; - QList m_enumValues; -}; - -/** - * A property that uses an editor factory to create its editor, but does not - * store a value itself. - * - * The property does not take ownership of the editor factory. - */ -class AbstractProperty : public Property -{ - Q_OBJECT - -public: - AbstractProperty(const QString &name, - EditorFactory *editorFactory, - QObject *parent = nullptr); - - QWidget *createEditor(QWidget *parent) override; - -private: - EditorFactory *m_editorFactory; -}; - -/** - * A property that uses the given functions to get and set the value and uses - * an editor factory to create its editor. - * - * The property does not take ownership of the editor factory. - */ -class GetSetProperty : public AbstractProperty -{ - Q_OBJECT - -public: - GetSetProperty(const QString &name, - std::function get, - std::function set, - EditorFactory *editorFactory, - QObject *parent = nullptr); - - QVariant value() const override { return m_get(); } - void setValue(const QVariant &value) override { m_set(value); } - -private: - std::function m_get; - std::function m_set; -}; - -/** - * A property that stores a value of a given type and uses an editor factory to - * create its editor. - * - * The property does not take ownership of the editor factory. - */ -class ValueProperty : public AbstractProperty -{ - Q_OBJECT - -public: - ValueProperty(const QString &name, - const QVariant &value, - EditorFactory *editorFactory, - QObject *parent = nullptr); - - QVariant value() const override { return m_value; } - void setValue(const QVariant &value) override; - -private: - QVariant m_value; -}; - /** * A property factory that instantiates the appropriate property type based on @@ -326,57 +223,81 @@ class PropertyFactory public: PropertyFactory() = default; - /** - * Register an editor factory for a given type. - * - * When there is already an editor factory registered for the given type, - * it will be replaced. - */ - void registerEditorFactory(int type, std::unique_ptr factory); - /** * Creates a property that wraps a QObject property. */ Property *createQObjectProperty(QObject *qObject, - const char *name, + const char *propertyName, const QString &displayName = {}); - /** - * Creates a property with the given name and value. The property will use - * the editor factory registered for the type of the value. - */ - ValueProperty *createProperty(const QString &name, const QVariant &value); - /** * Creates a property with the given name and get/set functions. The - * property will use the editor factory registered for the type of the - * value. + * value type determines the kind of property that will be created. */ Property *createProperty(const QString &name, std::function get, std::function set); +}; -private: - std::unordered_map> m_factories; +struct EnumData +{ + EnumData(const QStringList &names, + const QList &values = {}, + const QMap &icons = {}) + : names(names) + , values(values) + , icons(icons) + {} + + QStringList names; + QList values; // optional + QMap icons; // optional }; +template +EnumData enumData() +{ + return {{}}; +} + +QWidget *createEnumEditor(IntProperty *property, + const EnumData &enumData, + QWidget *parent); + /** - * A property that wraps an enum value and uses an editor factory to create - * its editor. + * A property that wraps an enum value and creates a combo box based on the + * given EnumData. */ -class EnumProperty : public AbstractProperty +template +class EnumProperty : public IntProperty { - Q_OBJECT - public: EnumProperty(const QString &name, - QObject *parent = nullptr); + std::function get, + std::function set, + QObject *parent = nullptr) + : IntProperty(name, + [get] { + return static_cast(get()); + }, + set ? [set](const int &value){ set(static_cast(value)); } + : std::function(), + parent) + , m_enumData(enumData()) + {} - void setEnumNames(const QStringList &enumNames); - void setEnumValues(const QList &enumValues); + void setEnumData(const EnumData &enumData) + { + m_enumData = enumData; + } + + QWidget *createEditor(QWidget *parent) override + { + return createEnumEditor(this, m_enumData, parent); + } private: - EnumEditorFactory m_editorFactory; + EnumData m_enumData; }; From 091bc14da33ae760a0a164c3d01dcccdc1df9642 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Mon, 9 Sep 2024 16:56:00 +0200 Subject: [PATCH 21/36] Addressed various property todo items * Apply various minimum and maximum values * Set custom step size where needed * Set file filters where appropriate * Support constraint on RectProperty --- src/tiled/propertieswidget.cpp | 51 ++++++------ src/tiled/propertyeditorwidgets.cpp | 32 ++++++++ src/tiled/propertyeditorwidgets.h | 6 ++ src/tiled/textpropertyedit.h | 1 + src/tiled/varianteditor.cpp | 119 ++++++++++++++++++---------- src/tiled/varianteditor.h | 56 +++++++++++++ 6 files changed, 198 insertions(+), 67 deletions(-) diff --git a/src/tiled/propertieswidget.cpp b/src/tiled/propertieswidget.cpp index e97b0dd15b..0bdb287d6c 100644 --- a/src/tiled/propertieswidget.cpp +++ b/src/tiled/propertieswidget.cpp @@ -472,6 +472,7 @@ class MapProperties : public ObjectProperties } }, this); + mTileSizeProperty->setMinimum(1); mInfiniteProperty = new BoolProperty( tr("Infinite"), @@ -539,6 +540,7 @@ class MapProperties : public ObjectProperties [this](const QSize &value) { push(new ChangeMapProperty(mapDocument(), value)); }); + mChunkSizeProperty->setMinimum(CHUNK_SIZE_MIN); mRenderOrderProperty = new EnumProperty( tr("Tile Render Order"), @@ -682,14 +684,14 @@ class MapProperties : public ObjectProperties Property *mOrientationProperty; Property *mSizeProperty; - Property *mTileSizeProperty; + SizeProperty *mTileSizeProperty; Property *mInfiniteProperty; Property *mHexSideLengthProperty; Property *mStaggerAxisProperty; Property *mStaggerIndexProperty; Property *mParallaxOriginProperty; Property *mLayerDataFormatProperty; - Property *mChunkSizeProperty; + SizeProperty *mChunkSizeProperty; Property *mRenderOrderProperty; Property *mCompressionLevelProperty; Property *mBackgroundColorProperty; @@ -733,14 +735,15 @@ class LayerProperties : public ObjectProperties push(new SetLayerLocked(mapDocument(), { layer() }, value)); }); - // todo: value should be between 0 and 1, and would be nice to use a slider (replacing the one in Layers view) - // todo: singleStep should be 0.1 + // todo: would be nice to use a slider (replacing the one in Layers view) mOpacityProperty = new FloatProperty( tr("Opacity"), [this] { return layer()->opacity(); }, [this](const double &value) { push(new SetLayerOpacity(mapDocument(), { layer() }, value)); }); + mOpacityProperty->setRange(0.0, 1.0); + mOpacityProperty->setSingleStep(0.1); mTintColorProperty = new ColorProperty( tr("Tint Color"), @@ -756,13 +759,13 @@ class LayerProperties : public ObjectProperties push(new SetLayerOffset(mapDocument(), { layer() }, value)); }); - // todo: singleStep should be 0.1 mParallaxFactorProperty = new PointFProperty( tr("Parallax Factor"), [this] { return layer()->parallaxFactor(); }, [this](const QPointF &value) { push(new SetLayerParallaxFactor(mapDocument(), { layer() }, value)); }); + mParallaxFactorProperty->setSingleStep(0.1); connect(document, &Document::changed, this, &LayerProperties::onChanged); @@ -821,10 +824,10 @@ class LayerProperties : public ObjectProperties Property *mNameProperty; Property *mVisibleProperty; Property *mLockedProperty; - Property *mOpacityProperty; + FloatProperty *mOpacityProperty; Property *mTintColorProperty; Property *mOffsetProperty; - Property *mParallaxFactorProperty; + PointFProperty *mParallaxFactorProperty; }; class ImageLayerProperties : public LayerProperties @@ -835,13 +838,13 @@ class ImageLayerProperties : public LayerProperties ImageLayerProperties(MapDocument *document, ImageLayer *object, QObject *parent = nullptr) : LayerProperties(document, object, parent) { - // todo: set a file filter for selecting images (or map files?) mImageProperty = new UrlProperty( tr("Image Source"), [this] { return imageLayer()->imageSource(); }, [this](const QUrl &value) { push(new ChangeImageLayerImageSource(mapDocument(), { imageLayer() }, value)); }); + mImageProperty->setFilter(Utils::readableImageFormatsFilter()); mTransparentColorProperty = new ColorProperty( tr("Transparent Color"), @@ -904,7 +907,7 @@ class ImageLayerProperties : public LayerProperties return static_cast(mObject); } - Property *mImageProperty; + UrlProperty *mImageProperty; Property *mTransparentColorProperty; Property *mRepeatXProperty; Property *mRepeatYProperty; @@ -1047,8 +1050,8 @@ class TilesetProperties : public ObjectProperties [this](const QSize &value) { push(new ChangeTilesetGridSize(tilesetDocument(), value)); }); + mGridSizeProperty->setMinimum(1); - // todo: needs 1 as minimum value mColumnCountProperty = new IntProperty( tr("Columns"), [this] { @@ -1057,6 +1060,7 @@ class TilesetProperties : public ObjectProperties [this](const int &value) { push(new ChangeTilesetColumnCount(tilesetDocument(), value)); }); + mColumnCountProperty->setMinimum(1); // todo: this needs a custom widget mAllowedTransformationsProperty = new IntProperty( @@ -1163,8 +1167,8 @@ class TilesetProperties : public ObjectProperties Property *mFillModeProperty; Property *mBackgroundColorProperty; Property *mOrientationProperty; - Property *mGridSizeProperty; - Property *mColumnCountProperty; + SizeProperty *mGridSizeProperty; + IntProperty *mColumnCountProperty; Property *mAllowedTransformationsProperty; Property *mImageProperty; }; @@ -1262,8 +1266,7 @@ class MapObjectProperties : public ObjectProperties push(command); }); - // todo: allow opening the multi-line text dialog - mTextProperty = new StringProperty( + mTextProperty = new MultilineStringProperty( tr("Text"), [this] { return mapObject()->textData().text; @@ -1446,7 +1449,6 @@ class TileProperties : public ObjectProperties [this] { return tile()->id(); }); mIdProperty->setEnabled(false); - // todo: apply readableImageFormatsFilter mImageProperty = new UrlProperty( tr("Image"), [this] { return tile()->imageSource(); }, @@ -1455,6 +1457,7 @@ class TileProperties : public ObjectProperties tile(), value)); }); + mImageProperty->setFilter(Utils::readableImageFormatsFilter()); mRectangleProperty = new RectProperty( tr("Rectangle"), @@ -1464,8 +1467,8 @@ class TileProperties : public ObjectProperties { tile() }, { value })); }); + mRectangleProperty->setConstraint(object->image().rect()); - // todo: minimum value should be 0 mProbabilityProperty = new FloatProperty( tr("Probability"), [this] { return tile()->probability(); }, @@ -1475,6 +1478,7 @@ class TileProperties : public ObjectProperties value)); }); mProbabilityProperty->setToolTip(tr("Relative chance this tile will be picked")); + mProbabilityProperty->setMinimum(0.0); // annoying... maybe we should somehow always have the relevant TilesetDocument if (auto tilesetDocument = qobject_cast(document)) { @@ -1513,6 +1517,7 @@ class TileProperties : public ObjectProperties { if (tile != this->tile()) return; + mRectangleProperty->setConstraint(tile->image().rect()); emit mImageProperty->valueChanged(); emit mRectangleProperty->valueChanged(); } @@ -1545,9 +1550,9 @@ class TileProperties : public ObjectProperties } Property *mIdProperty; - Property *mImageProperty; - Property *mRectangleProperty; - Property *mProbabilityProperty; + UrlProperty *mImageProperty; + RectProperty *mRectangleProperty; + FloatProperty *mProbabilityProperty; }; class WangSetProperties : public ObjectProperties @@ -1573,7 +1578,6 @@ class WangSetProperties : public ObjectProperties push(new ChangeWangSetType(tilesetDocument(), wangSet(), value)); }); - // todo: keep between 0 and WangId::MAX_COLOR_COUNT mColorCountProperty = new IntProperty( tr("Color Count"), [this] { return wangSet()->colorCount(); }, @@ -1582,6 +1586,7 @@ class WangSetProperties : public ObjectProperties wangSet(), value)); }); + mColorCountProperty->setRange(0, WangId::MAX_COLOR_COUNT); connect(document, &Document::changed, this, &WangSetProperties::onChanged); @@ -1644,7 +1649,7 @@ class WangSetProperties : public ObjectProperties Property *mNameProperty; Property *mTypeProperty; - Property *mColorCountProperty; + IntProperty *mColorCountProperty; }; class WangColorProperties : public ObjectProperties @@ -1670,13 +1675,13 @@ class WangColorProperties : public ObjectProperties push(new ChangeWangColorColor(tilesetDocument(), wangColor(), value)); }); - // todo: set 0.01 as minimum mProbabilityProperty = new FloatProperty( tr("Probability"), [this] { return wangColor()->probability(); }, [this](const double &value) { push(new ChangeWangColorProbability(tilesetDocument(), wangColor(), value)); }); + mProbabilityProperty->setMinimum(0.01); connect(document, &Document::changed, this, &WangColorProperties::onChanged); @@ -1740,7 +1745,7 @@ class WangColorProperties : public ObjectProperties Property *mNameProperty; Property *mColorProperty; - Property *mProbabilityProperty; + FloatProperty *mProbabilityProperty; }; diff --git a/src/tiled/propertyeditorwidgets.cpp b/src/tiled/propertyeditorwidgets.cpp index 848652b8d4..706a91ab48 100644 --- a/src/tiled/propertyeditorwidgets.cpp +++ b/src/tiled/propertyeditorwidgets.cpp @@ -214,6 +214,12 @@ QSize SizeEdit::value() const m_heightSpinBox->value()); } +void SizeEdit::setMinimum(int minimum) +{ + m_widthSpinBox->setMinimum(minimum); + m_heightSpinBox->setMinimum(minimum); +} + SizeFEdit::SizeFEdit(QWidget *parent) : ResponsivePairswiseWidget(parent) @@ -301,6 +307,12 @@ QPointF PointFEdit::value() const m_ySpinBox->value()); } +void PointFEdit::setSingleStep(double step) +{ + m_xSpinBox->setSingleStep(step); + m_ySpinBox->setSingleStep(step); +} + RectEdit::RectEdit(QWidget *parent) : ResponsivePairswiseWidget(parent) @@ -320,6 +332,9 @@ RectEdit::RectEdit(QWidget *parent) { m_heightLabel, m_heightSpinBox }, }); + m_widthSpinBox->setMinimum(0); + m_heightSpinBox->setMinimum(0); + connect(m_xSpinBox, qOverload(&QSpinBox::valueChanged), this, &RectEdit::valueChanged); connect(m_ySpinBox, qOverload(&QSpinBox::valueChanged), this, &RectEdit::valueChanged); connect(m_widthSpinBox, qOverload(&QSpinBox::valueChanged), this, &RectEdit::valueChanged); @@ -342,6 +357,23 @@ QRect RectEdit::value() const m_heightSpinBox->value()); } +void RectEdit::setConstraint(const QRect &constraint) +{ + if (constraint.isNull()) { + m_xSpinBox->setRange(std::numeric_limits::lowest(), + std::numeric_limits::max()); + m_ySpinBox->setRange(std::numeric_limits::lowest(), + std::numeric_limits::max()); + m_widthSpinBox->setRange(0, std::numeric_limits::max()); + m_heightSpinBox->setRange(0, std::numeric_limits::max()); + } else { + m_xSpinBox->setRange(constraint.left(), constraint.right() + 1); + m_ySpinBox->setRange(constraint.top(), constraint.bottom() + 1); + m_widthSpinBox->setRange(0, constraint.width()); + m_heightSpinBox->setRange(0, constraint.height()); + } +} + RectFEdit::RectFEdit(QWidget *parent) : ResponsivePairswiseWidget(parent) diff --git a/src/tiled/propertyeditorwidgets.h b/src/tiled/propertyeditorwidgets.h index dc8c9417f0..84062ad323 100644 --- a/src/tiled/propertyeditorwidgets.h +++ b/src/tiled/propertyeditorwidgets.h @@ -84,6 +84,8 @@ class SizeEdit : public ResponsivePairswiseWidget void setValue(const QSize &size); QSize value() const; + void setMinimum(int minimum); + signals: void valueChanged(); @@ -156,6 +158,8 @@ class PointFEdit : public ResponsivePairswiseWidget void setValue(const QPointF &size); QPointF value() const; + void setSingleStep(double step); + signals: void valueChanged(); @@ -180,6 +184,8 @@ class RectEdit : public ResponsivePairswiseWidget void setValue(const QRect &size); QRect value() const; + void setConstraint(const QRect &constraint); + signals: void valueChanged(); diff --git a/src/tiled/textpropertyedit.h b/src/tiled/textpropertyedit.h index 085a096ae7..4ef2c7360f 100644 --- a/src/tiled/textpropertyedit.h +++ b/src/tiled/textpropertyedit.h @@ -38,6 +38,7 @@ QString escapeNewlines(const QString &string); class TextPropertyEdit : public QWidget { Q_OBJECT + Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged FINAL) public: explicit TextPropertyEdit(QWidget *parent = nullptr); diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index 38d9629d0e..93ec5f6934 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -22,6 +22,7 @@ #include "colorbutton.h" #include "fileedit.h" +#include "textpropertyedit.h" #include "utils.h" #include "propertyeditorwidgets.h" @@ -47,8 +48,23 @@ QWidget *StringProperty::createEditor(QWidget *parent) }; syncEditor(); - QObject::connect(this, &Property::valueChanged, editor, syncEditor); - QObject::connect(editor, &QLineEdit::textEdited, this, &StringProperty::setValue); + connect(this, &Property::valueChanged, editor, syncEditor); + connect(editor, &QLineEdit::textEdited, this, &StringProperty::setValue); + + return editor; +} + +QWidget *MultilineStringProperty::createEditor(QWidget *parent) +{ + auto editor = new TextPropertyEdit(parent); + auto syncEditor = [=] { + const QSignalBlocker blocker(editor); + editor->setText(value()); + }; + syncEditor(); + + connect(this, &StringProperty::valueChanged, editor, syncEditor); + connect(editor, &TextPropertyEdit::textChanged, this, &StringProperty::setValue); return editor; } @@ -63,8 +79,8 @@ QWidget *UrlProperty::createEditor(QWidget *parent) }; syncEditor(); - QObject::connect(this, &Property::valueChanged, editor, syncEditor); - QObject::connect(editor, &FileEdit::fileUrlChanged, this, &UrlProperty::setValue); + connect(this, &Property::valueChanged, editor, syncEditor); + connect(editor, &FileEdit::fileUrlChanged, this, &UrlProperty::setValue); return editor; } @@ -72,15 +88,19 @@ QWidget *UrlProperty::createEditor(QWidget *parent) QWidget *IntProperty::createEditor(QWidget *parent) { auto editor = new SpinBox(parent); + editor->setRange(m_minimum, m_maximum); + editor->setSingleStep(m_singleStep); + editor->setSuffix(m_suffix); + auto syncEditor = [=] { const QSignalBlocker blocker(editor); editor->setValue(value()); }; syncEditor(); - QObject::connect(this, &Property::valueChanged, editor, syncEditor); - QObject::connect(editor, qOverload(&SpinBox::valueChanged), - this, &IntProperty::setValue); + connect(this, &Property::valueChanged, editor, syncEditor); + connect(editor, qOverload(&SpinBox::valueChanged), + this, &IntProperty::setValue); return editor; } @@ -88,6 +108,8 @@ QWidget *IntProperty::createEditor(QWidget *parent) QWidget *FloatProperty::createEditor(QWidget *parent) { auto editor = new DoubleSpinBox(parent); + editor->setRange(m_minimum, m_maximum); + editor->setSingleStep(m_singleStep); editor->setSuffix(m_suffix); auto syncEditor = [=] { @@ -96,9 +118,9 @@ QWidget *FloatProperty::createEditor(QWidget *parent) }; syncEditor(); - QObject::connect(this, &Property::valueChanged, editor, syncEditor); - QObject::connect(editor, qOverload(&DoubleSpinBox::valueChanged), - this, &FloatProperty::setValue); + connect(this, &Property::valueChanged, editor, syncEditor); + connect(editor, qOverload(&DoubleSpinBox::valueChanged), + this, &FloatProperty::setValue); return editor; } @@ -114,8 +136,8 @@ QWidget *BoolProperty::createEditor(QWidget *parent) }; syncEditor(); - QObject::connect(this, &Property::valueChanged, editor, syncEditor); - QObject::connect(editor, &QCheckBox::toggled, this, [=](bool checked) { + connect(this, &Property::valueChanged, editor, syncEditor); + connect(editor, &QCheckBox::toggled, this, [=](bool checked) { editor->setText(checked ? QObject::tr("On") : QObject::tr("Off")); setValue(checked); }); @@ -132,9 +154,8 @@ QWidget *PointProperty::createEditor(QWidget *parent) }; syncEditor(); - QObject::connect(this, &Property::valueChanged, editor, syncEditor); - QObject::connect(editor, &PointEdit::valueChanged, this, - [this, editor] { + connect(this, &Property::valueChanged, editor, syncEditor); + connect(editor, &PointEdit::valueChanged, this, [this, editor] { setValue(editor->value()); }); @@ -144,15 +165,16 @@ QWidget *PointProperty::createEditor(QWidget *parent) QWidget *PointFProperty::createEditor(QWidget *parent) { auto editor = new PointFEdit(parent); + editor->setSingleStep(m_singleStep); + auto syncEditor = [this, editor] { const QSignalBlocker blocker(editor); editor->setValue(value()); }; syncEditor(); - QObject::connect(this, &Property::valueChanged, editor, syncEditor); - QObject::connect(editor, &PointFEdit::valueChanged, this, - [this, editor] { + connect(this, &Property::valueChanged, editor, syncEditor); + connect(editor, &PointFEdit::valueChanged, this, [this, editor] { this->setVariantValue(editor->value()); }); @@ -162,15 +184,16 @@ QWidget *PointFProperty::createEditor(QWidget *parent) QWidget *SizeProperty::createEditor(QWidget *parent) { auto editor = new SizeEdit(parent); + editor->setMinimum(m_minimum); + auto syncEditor = [this, editor] { const QSignalBlocker blocker(editor); editor->setValue(value()); }; syncEditor(); - QObject::connect(this, &Property::valueChanged, editor, syncEditor); - QObject::connect(editor, &SizeEdit::valueChanged, this, - [this, editor] { + connect(this, &Property::valueChanged, editor, syncEditor); + connect(editor, &SizeEdit::valueChanged, this, [this, editor] { setValue(editor->value()); }); @@ -186,9 +209,8 @@ QWidget *SizeFProperty::createEditor(QWidget *parent) }; syncEditor(); - QObject::connect(this, &Property::valueChanged, editor, syncEditor); - QObject::connect(editor, &SizeFEdit::valueChanged, this, - [this, editor] { + connect(this, &Property::valueChanged, editor, syncEditor); + connect(editor, &SizeFEdit::valueChanged, this, [this, editor] { setValue(editor->value()); }); @@ -198,21 +220,32 @@ QWidget *SizeFProperty::createEditor(QWidget *parent) QWidget *RectProperty::createEditor(QWidget *parent) { auto editor = new RectEdit(parent); + editor->setConstraint(m_constraint); + auto syncEditor = [this, editor] { const QSignalBlocker blocker(editor); editor->setValue(value()); }; syncEditor(); - QObject::connect(this, &Property::valueChanged, editor, syncEditor); - QObject::connect(editor, &RectEdit::valueChanged, this, - [this, editor] { + connect(this, &Property::valueChanged, editor, syncEditor); + connect(editor, &RectEdit::valueChanged, this, [this, editor] { setValue(editor->value()); }); + connect(this, &RectProperty::constraintChanged, + editor, &RectEdit::setConstraint); return editor; } +void RectProperty::setConstraint(const QRect &constraint) +{ + if (m_constraint != constraint) { + m_constraint = constraint; + emit constraintChanged(m_constraint); + } +} + QWidget *RectFProperty::createEditor(QWidget *parent) { auto editor = new RectFEdit(parent); @@ -222,9 +255,8 @@ QWidget *RectFProperty::createEditor(QWidget *parent) }; syncEditor(); - QObject::connect(this, &Property::valueChanged, editor, syncEditor); - QObject::connect(editor, &RectFEdit::valueChanged, this, - [this, editor] { + connect(this, &Property::valueChanged, editor, syncEditor); + connect(editor, &RectFEdit::valueChanged, this, [this, editor] { setValue(editor->value()); }); @@ -241,9 +273,8 @@ QWidget *ColorProperty::createEditor(QWidget *parent) }; syncEditor(); - QObject::connect(this, &Property::valueChanged, editor, syncEditor); - QObject::connect(editor, &ColorButton::colorChanged, this, - [this, editor] { + connect(this, &Property::valueChanged, editor, syncEditor); + connect(editor, &ColorButton::colorChanged, this, [this, editor] { setValue(editor->color()); }); @@ -305,14 +336,14 @@ QWidget *FontProperty::createEditor(QWidget *parent) syncEditor(); - QObject::connect(this, &Property::valueChanged, fontComboBox, syncEditor); - QObject::connect(fontComboBox, &QFontComboBox::currentFontChanged, this, syncProperty); - QObject::connect(sizeSpinBox, qOverload(&QSpinBox::valueChanged), this, syncProperty); - QObject::connect(boldCheckBox, &QCheckBox::toggled, this, syncProperty); - QObject::connect(italicCheckBox, &QCheckBox::toggled, this, syncProperty); - QObject::connect(underlineCheckBox, &QCheckBox::toggled, this, syncProperty); - QObject::connect(strikeoutCheckBox, &QCheckBox::toggled, this, syncProperty); - QObject::connect(kerningCheckBox, &QCheckBox::toggled, this, syncProperty); + connect(this, &Property::valueChanged, fontComboBox, syncEditor); + connect(fontComboBox, &QFontComboBox::currentFontChanged, this, syncProperty); + connect(sizeSpinBox, qOverload(&QSpinBox::valueChanged), this, syncProperty); + connect(boldCheckBox, &QCheckBox::toggled, this, syncProperty); + connect(italicCheckBox, &QCheckBox::toggled, this, syncProperty); + connect(underlineCheckBox, &QCheckBox::toggled, this, syncProperty); + connect(strikeoutCheckBox, &QCheckBox::toggled, this, syncProperty); + connect(kerningCheckBox, &QCheckBox::toggled, this, syncProperty); return editor; } @@ -360,9 +391,9 @@ QWidget *QtAlignmentProperty::createEditor(QWidget *parent) syncEditor(); - QObject::connect(this, &Property::valueChanged, editor, syncEditor); - QObject::connect(horizontalComboBox, qOverload(&QComboBox::currentIndexChanged), this, syncProperty); - QObject::connect(verticalComboBox, qOverload(&QComboBox::currentIndexChanged), this, syncProperty); + connect(this, &Property::valueChanged, editor, syncEditor); + connect(horizontalComboBox, qOverload(&QComboBox::currentIndexChanged), this, syncProperty); + connect(verticalComboBox, qOverload(&QComboBox::currentIndexChanged), this, syncProperty); return editor; } diff --git a/src/tiled/varianteditor.h b/src/tiled/varianteditor.h index 8fdb125ea2..6e59c679a8 100644 --- a/src/tiled/varianteditor.h +++ b/src/tiled/varianteditor.h @@ -128,6 +128,12 @@ struct StringProperty : PropertyTemplate QWidget *createEditor(QWidget *parent) override; }; +struct MultilineStringProperty : PropertyTemplate +{ + using PropertyTemplate::PropertyTemplate; + QWidget *createEditor(QWidget *parent) override; +}; + struct UrlProperty : PropertyTemplate { using PropertyTemplate::PropertyTemplate; @@ -141,14 +147,43 @@ struct IntProperty : PropertyTemplate { using PropertyTemplate::PropertyTemplate; QWidget *createEditor(QWidget *parent) override; + + void setMinimum(int minimum) { m_minimum = minimum; } + void setMaximum(int maximum) { m_maximum = maximum; } + void setSingleStep(int singleStep) { m_singleStep = singleStep; } + void setSuffix(const QString &suffix) { m_suffix = suffix; } + void setRange(int minimum, int maximum) + { + setMinimum(minimum); + setMaximum(maximum); + } + +private: + int m_minimum = std::numeric_limits::min(); + int m_maximum = std::numeric_limits::max(); + int m_singleStep = 1; + QString m_suffix; }; struct FloatProperty : PropertyTemplate { using PropertyTemplate::PropertyTemplate; QWidget *createEditor(QWidget *parent) override; + + void setMinimum(double minimum) { m_minimum = minimum; } + void setMaximum(double maximum) { m_maximum = maximum; } + void setSingleStep(double singleStep) { m_singleStep = singleStep; } void setSuffix(const QString &suffix) { m_suffix = suffix; } + void setRange(double minimum, double maximum) + { + setMinimum(minimum); + setMaximum(maximum); + } + private: + double m_minimum = -std::numeric_limits::max(); + double m_maximum = std::numeric_limits::max(); + double m_singleStep = 1.0; QString m_suffix; }; @@ -168,12 +203,22 @@ struct PointFProperty : PropertyTemplate { using PropertyTemplate::PropertyTemplate; QWidget *createEditor(QWidget *parent) override; + + void setSingleStep(double singleStep) { m_singleStep = singleStep; } + +private: + double m_singleStep = 1.0; }; struct SizeProperty : PropertyTemplate { using PropertyTemplate::PropertyTemplate; QWidget *createEditor(QWidget *parent) override; + + void setMinimum(int minimum) { m_minimum = minimum; } + +private: + int m_minimum; }; struct SizeFProperty : PropertyTemplate @@ -184,8 +229,19 @@ struct SizeFProperty : PropertyTemplate struct RectProperty : PropertyTemplate { + Q_OBJECT + +public: using PropertyTemplate::PropertyTemplate; QWidget *createEditor(QWidget *parent) override; + + void setConstraint(const QRect &constraint); + +signals: + void constraintChanged(const QRect &constraint); + +private: + QRect m_constraint; }; struct RectFProperty : PropertyTemplate From a8f2e31b3bca069e0177e020e3b339fc708163a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Mon, 9 Sep 2024 19:07:09 +0200 Subject: [PATCH 22/36] Use tool buttons for text styling in FontProperty Makes font property widget take up less vertical space. --- AUTHORS | 6 ++ .../images/scalable/text-bold-symbolic.svg | 2 + .../images/scalable/text-italic-symbolic.svg | 2 + .../scalable/text-strikethrough-symbolic.svg | 2 + .../scalable/text-underline-symbolic.svg | 2 + src/tiled/varianteditor.cpp | 88 ++++++++++++------- 6 files changed, 71 insertions(+), 31 deletions(-) create mode 100644 src/tiled/resources/images/scalable/text-bold-symbolic.svg create mode 100644 src/tiled/resources/images/scalable/text-italic-symbolic.svg create mode 100644 src/tiled/resources/images/scalable/text-strikethrough-symbolic.svg create mode 100644 src/tiled/resources/images/scalable/text-underline-symbolic.svg diff --git a/AUTHORS b/AUTHORS index 9d7b5369b6..6ff7cbddb7 100644 --- a/AUTHORS +++ b/AUTHORS @@ -362,6 +362,12 @@ Icons from the Elementary icon theme (GPLv3) * src/tiled/images/32/dialog-error.png * src/tiled/images/32/dialog-warning.png +Icons from the GNOME project (CC0 1.0 Universal) +* src/tiled/resources/images/scalable/text-bold-symbolic.svg +* src/tiled/resources/images/scalable/text-italic-symbolic.svg +* src/tiled/resources/images/scalable/text-underline-symbolic.svg +* src/tiled/resources/images/scalable/text-strikethrough-symbolic.svg + Tilesets: diff --git a/src/tiled/resources/images/scalable/text-bold-symbolic.svg b/src/tiled/resources/images/scalable/text-bold-symbolic.svg new file mode 100644 index 0000000000..bdbddea8d2 --- /dev/null +++ b/src/tiled/resources/images/scalable/text-bold-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/src/tiled/resources/images/scalable/text-italic-symbolic.svg b/src/tiled/resources/images/scalable/text-italic-symbolic.svg new file mode 100644 index 0000000000..9fa6e67de6 --- /dev/null +++ b/src/tiled/resources/images/scalable/text-italic-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/src/tiled/resources/images/scalable/text-strikethrough-symbolic.svg b/src/tiled/resources/images/scalable/text-strikethrough-symbolic.svg new file mode 100644 index 0000000000..6df152de47 --- /dev/null +++ b/src/tiled/resources/images/scalable/text-strikethrough-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/src/tiled/resources/images/scalable/text-underline-symbolic.svg b/src/tiled/resources/images/scalable/text-underline-symbolic.svg new file mode 100644 index 0000000000..ff520eb3c7 --- /dev/null +++ b/src/tiled/resources/images/scalable/text-underline-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index 93ec5f6934..abbd8bd2bb 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -37,6 +37,7 @@ #include #include #include +#include namespace Tiled { @@ -284,53 +285,78 @@ QWidget *ColorProperty::createEditor(QWidget *parent) QWidget *FontProperty::createEditor(QWidget *parent) { auto editor = new QWidget(parent); - auto layout = new QVBoxLayout(editor); auto fontComboBox = new QFontComboBox(editor); + auto sizeSpinBox = new QSpinBox(editor); - auto boldCheckBox = new QCheckBox(tr("Bold"), editor); - auto italicCheckBox = new QCheckBox(tr("Italic"), editor); - auto underlineCheckBox = new QCheckBox(tr("Underline"), editor); - auto strikeoutCheckBox = new QCheckBox(tr("Strikeout"), editor); - auto kerningCheckBox = new QCheckBox(tr("Kerning"), editor); sizeSpinBox->setRange(1, 999); sizeSpinBox->setSuffix(tr(" px")); sizeSpinBox->setKeyboardTracking(false); + + auto bold = new QToolButton(editor); + bold->setIcon(QIcon(QStringLiteral("://images/scalable/text-bold-symbolic.svg"))); + bold->setToolTip(tr("Bold")); + bold->setCheckable(true); + + auto italic = new QToolButton(editor); + italic->setIcon(QIcon(QStringLiteral("://images/scalable/text-italic-symbolic.svg"))); + italic->setToolTip(tr("Italic")); + italic->setCheckable(true); + + auto underline = new QToolButton(editor); + underline->setIcon(QIcon(QStringLiteral("://images/scalable/text-underline-symbolic.svg"))); + underline->setToolTip(tr("Underline")); + underline->setCheckable(true); + + auto strikeout = new QToolButton(editor); + strikeout->setIcon(QIcon(QStringLiteral("://images/scalable/text-strikethrough-symbolic.svg"))); + strikeout->setToolTip(tr("Strikethrough")); + strikeout->setCheckable(true); + + auto kerning = new QCheckBox(tr("Kerning"), editor); + + auto layout = new QVBoxLayout(editor); layout->setContentsMargins(QMargins()); layout->setSpacing(Utils::dpiScaled(3)); layout->addWidget(fontComboBox); layout->addWidget(sizeSpinBox); - layout->addWidget(boldCheckBox); - layout->addWidget(italicCheckBox); - layout->addWidget(underlineCheckBox); - layout->addWidget(strikeoutCheckBox); - layout->addWidget(kerningCheckBox); + + auto buttonsLayout = new QHBoxLayout; + buttonsLayout->setContentsMargins(QMargins()); + buttonsLayout->addWidget(bold); + buttonsLayout->addWidget(italic); + buttonsLayout->addWidget(underline); + buttonsLayout->addWidget(strikeout); + buttonsLayout->addStretch(); + + layout->addLayout(buttonsLayout); + layout->addWidget(kerning); auto syncEditor = [=] { const auto font = value(); const QSignalBlocker fontBlocker(fontComboBox); const QSignalBlocker sizeBlocker(sizeSpinBox); - const QSignalBlocker boldBlocker(boldCheckBox); - const QSignalBlocker italicBlocker(italicCheckBox); - const QSignalBlocker underlineBlocker(underlineCheckBox); - const QSignalBlocker strikeoutBlocker(strikeoutCheckBox); - const QSignalBlocker kerningBlocker(kerningCheckBox); + const QSignalBlocker boldBlocker(bold); + const QSignalBlocker italicBlocker(italic); + const QSignalBlocker underlineBlocker(underline); + const QSignalBlocker strikeoutBlocker(strikeout); + const QSignalBlocker kerningBlocker(kerning); fontComboBox->setCurrentFont(font); sizeSpinBox->setValue(font.pixelSize()); - boldCheckBox->setChecked(font.bold()); - italicCheckBox->setChecked(font.italic()); - underlineCheckBox->setChecked(font.underline()); - strikeoutCheckBox->setChecked(font.strikeOut()); - kerningCheckBox->setChecked(font.kerning()); + bold->setChecked(font.bold()); + italic->setChecked(font.italic()); + underline->setChecked(font.underline()); + strikeout->setChecked(font.strikeOut()); + kerning->setChecked(font.kerning()); }; auto syncProperty = [=] { auto font = fontComboBox->currentFont(); font.setPixelSize(sizeSpinBox->value()); - font.setBold(boldCheckBox->isChecked()); - font.setItalic(italicCheckBox->isChecked()); - font.setUnderline(underlineCheckBox->isChecked()); - font.setStrikeOut(strikeoutCheckBox->isChecked()); - font.setKerning(kerningCheckBox->isChecked()); + font.setBold(bold->isChecked()); + font.setItalic(italic->isChecked()); + font.setUnderline(underline->isChecked()); + font.setStrikeOut(strikeout->isChecked()); + font.setKerning(kerning->isChecked()); setValue(font); }; @@ -339,11 +365,11 @@ QWidget *FontProperty::createEditor(QWidget *parent) connect(this, &Property::valueChanged, fontComboBox, syncEditor); connect(fontComboBox, &QFontComboBox::currentFontChanged, this, syncProperty); connect(sizeSpinBox, qOverload(&QSpinBox::valueChanged), this, syncProperty); - connect(boldCheckBox, &QCheckBox::toggled, this, syncProperty); - connect(italicCheckBox, &QCheckBox::toggled, this, syncProperty); - connect(underlineCheckBox, &QCheckBox::toggled, this, syncProperty); - connect(strikeoutCheckBox, &QCheckBox::toggled, this, syncProperty); - connect(kerningCheckBox, &QCheckBox::toggled, this, syncProperty); + connect(bold, &QAbstractButton::toggled, this, syncProperty); + connect(italic, &QAbstractButton::toggled, this, syncProperty); + connect(underline, &QAbstractButton::toggled, this, syncProperty); + connect(strikeout, &QAbstractButton::toggled, this, syncProperty); + connect(kerning, &QAbstractButton::toggled, this, syncProperty); return editor; } From fa195b2430a7d31b6765d96b984bee89e222051e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Thu, 12 Sep 2024 18:29:35 +0200 Subject: [PATCH 23/36] Progress on various property editor features * Added support for editing top-level custom properties (excluding support for classes and enums for now). * Added support for collapsing the property groups, based on a new GroupProperty which creates nested VariantEditor instances. * Removed QVariant based getter/setter from Property, since it doesn't appear to be useful. * Added Property::DisplayMode, which is used to tell apart group properties and separators from regular properties. * VariantEditor is no longer a scroll area itself, to enable nesting. --- src/tiled/fileedit.cpp | 2 +- src/tiled/propertieswidget.cpp | 313 +++++++++++++++++++--------- src/tiled/propertieswidget.h | 3 + src/tiled/propertyeditorwidgets.cpp | 72 +++++++ src/tiled/propertyeditorwidgets.h | 43 ++++ src/tiled/textpropertyedit.cpp | 2 +- src/tiled/varianteditor.cpp | 117 +++++++---- src/tiled/varianteditor.h | 80 ++++--- 8 files changed, 459 insertions(+), 173 deletions(-) diff --git a/src/tiled/fileedit.cpp b/src/tiled/fileedit.cpp index e6804e999b..5e1b09e266 100644 --- a/src/tiled/fileedit.cpp +++ b/src/tiled/fileedit.cpp @@ -45,7 +45,7 @@ FileEdit::FileEdit(QWidget *parent) mOkTextColor = mLineEdit->palette().color(QPalette::Active, QPalette::Text); QToolButton *button = new QToolButton(this); - button->setText(QLatin1String("...")); + button->setText(QStringLiteral("…")); button->setAutoRaise(true); button->setToolTip(tr("Choose")); layout->addWidget(mLineEdit); diff --git a/src/tiled/propertieswidget.cpp b/src/tiled/propertieswidget.cpp index 0bdb287d6c..3f40d76ffe 100644 --- a/src/tiled/propertieswidget.cpp +++ b/src/tiled/propertieswidget.cpp @@ -36,6 +36,7 @@ #include "compression.h" #include "mapdocument.h" #include "objectgroup.h" +#include "objectrefedit.h" #include "objecttemplate.h" #include "preferences.h" #include "propertybrowser.h" @@ -43,6 +44,7 @@ #include "tilesetdocument.h" #include "utils.h" #include "varianteditor.h" +#include "variantpropertymanager.h" #include "wangoverlay.h" #include @@ -193,10 +195,47 @@ template<> EnumData enumData() } +class ObjectRefProperty : public PropertyTemplate +{ + Q_OBJECT + +public: + using PropertyTemplate::PropertyTemplate; + + QWidget *createEditor(QWidget *parent) override + { + auto editor = new ObjectRefEdit(parent); + auto syncEditor = [this, editor] { + const QSignalBlocker blocker(editor); + editor->setValue(value()); + }; + syncEditor(); + connect(this, &Property::valueChanged, editor, syncEditor); + connect(editor, &ObjectRefEdit::valueChanged, + this, [this](const DisplayObjectRef &value) { + setValue(value); + }); + return editor; + } +}; + + PropertiesWidget::PropertiesWidget(QWidget *parent) : QWidget{parent} - , mPropertyBrowser(new VariantEditor(this)) + , mScrollArea(new QScrollArea(this)) { + auto scrollWidget = new QWidget(mScrollArea); + scrollWidget->setBackgroundRole(QPalette::AlternateBase); + + auto verticalLayout = new QVBoxLayout(scrollWidget); + mPropertyBrowser = new VariantEditor(scrollWidget); + verticalLayout->addWidget(mPropertyBrowser); + verticalLayout->addStretch(); + verticalLayout->setContentsMargins(QMargins()); + + mScrollArea->setWidget(scrollWidget); + mScrollArea->setWidgetResizable(true); + mActionAddProperty = new QAction(this); mActionAddProperty->setEnabled(false); mActionAddProperty->setIcon(QIcon(QLatin1String(":/images/16/add.png"))); @@ -231,7 +270,7 @@ PropertiesWidget::PropertiesWidget(QWidget *parent) QVBoxLayout *layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); - layout->addWidget(mPropertyBrowser); + layout->addWidget(mScrollArea); layout->addWidget(toolBar); setLayout(layout); @@ -569,6 +608,26 @@ class MapProperties : public ObjectProperties push(new ChangeMapProperty(mapDocument(), value)); }); + mMapProperties = new GroupProperty(tr("Map")); + mMapProperties->addProperty(mClassProperty); + mMapProperties->addSeparator(); + mMapProperties->addProperty(mOrientationProperty); + mMapProperties->addProperty(mSizeProperty); + mMapProperties->addProperty(mInfiniteProperty); + mMapProperties->addProperty(mTileSizeProperty); + mMapProperties->addProperty(mHexSideLengthProperty); + mMapProperties->addProperty(mStaggerAxisProperty); + mMapProperties->addProperty(mStaggerIndexProperty); + mMapProperties->addSeparator(); + mMapProperties->addProperty(mParallaxOriginProperty); + mMapProperties->addSeparator(); + mMapProperties->addProperty(mLayerDataFormatProperty); + mMapProperties->addProperty(mChunkSizeProperty); + mMapProperties->addProperty(mCompressionLevelProperty); + mMapProperties->addSeparator(); + mMapProperties->addProperty(mRenderOrderProperty); + mMapProperties->addProperty(mBackgroundColorProperty); + updateEnabledState(); connect(document, &Document::changed, this, &MapProperties::onChanged); @@ -576,25 +635,7 @@ class MapProperties : public ObjectProperties void populateEditor(VariantEditor *editor) override { - editor->addHeader(tr("Map")); - editor->addProperty(mClassProperty); - editor->addSeparator(); - editor->addProperty(mOrientationProperty); - editor->addProperty(mSizeProperty); - editor->addProperty(mInfiniteProperty); - editor->addProperty(mTileSizeProperty); - editor->addProperty(mHexSideLengthProperty); - editor->addProperty(mStaggerAxisProperty); - editor->addProperty(mStaggerIndexProperty); - editor->addSeparator(); - editor->addProperty(mParallaxOriginProperty); - editor->addSeparator(); - editor->addProperty(mLayerDataFormatProperty); - editor->addProperty(mChunkSizeProperty); - editor->addProperty(mCompressionLevelProperty); - editor->addSeparator(); - editor->addProperty(mRenderOrderProperty); - editor->addProperty(mBackgroundColorProperty); + editor->addProperty(mMapProperties); } private: @@ -682,6 +723,7 @@ class MapProperties : public ObjectProperties return mapDocument()->map(); } + GroupProperty *mMapProperties; Property *mOrientationProperty; Property *mSizeProperty; SizeProperty *mTileSizeProperty; @@ -767,23 +809,25 @@ class LayerProperties : public ObjectProperties }); mParallaxFactorProperty->setSingleStep(0.1); + mLayerProperties = new GroupProperty(tr("Layer")); + mLayerProperties->addProperty(mIdProperty); + mLayerProperties->addProperty(mNameProperty); + mLayerProperties->addProperty(mClassProperty); + mLayerProperties->addSeparator(); + mLayerProperties->addProperty(mVisibleProperty); + mLayerProperties->addProperty(mLockedProperty); + mLayerProperties->addProperty(mOpacityProperty); + mLayerProperties->addProperty(mTintColorProperty); + mLayerProperties->addProperty(mOffsetProperty); + mLayerProperties->addProperty(mParallaxFactorProperty); + connect(document, &Document::changed, this, &LayerProperties::onChanged); } void populateEditor(VariantEditor *editor) override { - editor->addHeader(tr("Layer")); - editor->addProperty(mIdProperty); - editor->addProperty(mNameProperty); - editor->addProperty(mClassProperty); - editor->addSeparator(); - editor->addProperty(mVisibleProperty); - editor->addProperty(mLockedProperty); - editor->addProperty(mOpacityProperty); - editor->addProperty(mTintColorProperty); - editor->addProperty(mOffsetProperty); - editor->addProperty(mParallaxFactorProperty); + editor->addProperty(mLayerProperties); } protected: @@ -820,6 +864,7 @@ class LayerProperties : public ObjectProperties return static_cast(mObject); } + GroupProperty *mLayerProperties; Property *mIdProperty; Property *mNameProperty; Property *mVisibleProperty; @@ -867,17 +912,19 @@ class ImageLayerProperties : public LayerProperties [this](const bool &value) { push(new ChangeImageLayerRepeatY(mapDocument(), { imageLayer() }, value)); }); + + mImageLayerProperties = new GroupProperty(tr("Image Layer")); + mImageLayerProperties->addProperty(mImageProperty); + mImageLayerProperties->addProperty(mTransparentColorProperty); + mImageLayerProperties->addSeparator(); + mImageLayerProperties->addProperty(mRepeatXProperty); + mImageLayerProperties->addProperty(mRepeatYProperty); } void populateEditor(VariantEditor *editor) override { LayerProperties::populateEditor(editor); - editor->addHeader(tr("Image Layer")); - editor->addProperty(mImageProperty); - editor->addProperty(mTransparentColorProperty); - editor->addSeparator(); - editor->addProperty(mRepeatXProperty); - editor->addProperty(mRepeatYProperty); + editor->addProperty(mImageLayerProperties); } private: @@ -907,6 +954,7 @@ class ImageLayerProperties : public LayerProperties return static_cast(mObject); } + GroupProperty *mImageLayerProperties; UrlProperty *mImageProperty; Property *mTransparentColorProperty; Property *mRepeatXProperty; @@ -1083,6 +1131,21 @@ class TilesetProperties : public ObjectProperties // push(new ChangeTilesetImage(tilesetDocument(), value.toString())); }); + mTilesetProperties = new GroupProperty(tr("Tileset")); + mTilesetProperties->addProperty(mNameProperty); + mTilesetProperties->addProperty(mClassProperty); + mTilesetProperties->addSeparator(); + mTilesetProperties->addProperty(mObjectAlignmentProperty); + mTilesetProperties->addProperty(mTileOffsetProperty); + mTilesetProperties->addProperty(mTileRenderSizeProperty); + mTilesetProperties->addProperty(mFillModeProperty); + mTilesetProperties->addProperty(mBackgroundColorProperty); + mTilesetProperties->addProperty(mOrientationProperty); + mTilesetProperties->addProperty(mGridSizeProperty); + mTilesetProperties->addProperty(mColumnCountProperty); + mTilesetProperties->addProperty(mAllowedTransformationsProperty); + mTilesetProperties->addProperty(mImageProperty); + updateEnabledState(); connect(tilesetDocument(), &Document::changed, this, &TilesetProperties::onChanged); @@ -1099,20 +1162,7 @@ class TilesetProperties : public ObjectProperties void populateEditor(VariantEditor *editor) { - editor->addHeader(tr("Tileset")); - editor->addProperty(mNameProperty); - editor->addProperty(mClassProperty); - editor->addSeparator(); - editor->addProperty(mObjectAlignmentProperty); - editor->addProperty(mTileOffsetProperty); - editor->addProperty(mTileRenderSizeProperty); - editor->addProperty(mFillModeProperty); - editor->addProperty(mBackgroundColorProperty); - editor->addProperty(mOrientationProperty); - editor->addProperty(mGridSizeProperty); - editor->addProperty(mColumnCountProperty); - editor->addProperty(mAllowedTransformationsProperty); - editor->addProperty(mImageProperty); + editor->addProperty(mTilesetProperties); } private: @@ -1160,6 +1210,7 @@ class TilesetProperties : public ObjectProperties return tilesetDocument()->tileset().data(); } + GroupProperty *mTilesetProperties; Property *mNameProperty; Property *mObjectAlignmentProperty; Property *mTileOffsetProperty; @@ -1312,45 +1363,47 @@ class MapObjectProperties : public ObjectProperties changeMapObject(MapObject::TextColorProperty, value); }); - connect(document, &Document::changed, - this, &MapObjectProperties::onChanged); - - updateEnabledState(); - } - - void populateEditor(VariantEditor *editor) override - { - editor->addHeader(tr("Object")); - editor->addProperty(mIdProperty); - editor->addProperty(mTemplateProperty); - editor->addProperty(mNameProperty); - editor->addProperty(mClassProperty); - editor->addSeparator(); + mObjectProperties = new GroupProperty(tr("Object")); + mObjectProperties->addProperty(mIdProperty); + mObjectProperties->addProperty(mTemplateProperty); + mObjectProperties->addProperty(mNameProperty); + mObjectProperties->addProperty(mClassProperty); + mObjectProperties->addSeparator(); if (mapDocument()->allowHidingObjects()) - editor->addProperty(mVisibleProperty); + mObjectProperties->addProperty(mVisibleProperty); - editor->addProperty(mPositionProperty); + mObjectProperties->addProperty(mPositionProperty); if (mapObject()->hasDimensions()) - editor->addProperty(mSizeProperty); + mObjectProperties->addProperty(mSizeProperty); if (mapObject()->canRotate()) - editor->addProperty(mRotationProperty); + mObjectProperties->addProperty(mRotationProperty); if (mapObject()->isTileObject()) { - editor->addSeparator(); - editor->addProperty(mFlippingProperty); + mObjectProperties->addSeparator(); + mObjectProperties->addProperty(mFlippingProperty); } if (mapObject()->shape() == MapObject::Text) { - editor->addSeparator(); - editor->addProperty(mTextProperty); - editor->addProperty(mTextAlignmentProperty); - editor->addProperty(mTextFontProperty); - editor->addProperty(mTextWordWrapProperty); - editor->addProperty(mTextColorProperty); + mObjectProperties->addSeparator(); + mObjectProperties->addProperty(mTextProperty); + mObjectProperties->addProperty(mTextAlignmentProperty); + mObjectProperties->addProperty(mTextFontProperty); + mObjectProperties->addProperty(mTextWordWrapProperty); + mObjectProperties->addProperty(mTextColorProperty); } + + connect(document, &Document::changed, + this, &MapObjectProperties::onChanged); + + updateEnabledState(); + } + + void populateEditor(VariantEditor *editor) override + { + editor->addProperty(mObjectProperties); } private: @@ -1417,6 +1470,7 @@ class MapObjectProperties : public ObjectProperties push(new ChangeMapObject(mapDocument(), mapObject(), property, value)); } + GroupProperty *mObjectProperties; Property *mIdProperty; Property *mTemplateProperty; Property *mNameProperty; @@ -1480,6 +1534,17 @@ class TileProperties : public ObjectProperties mProbabilityProperty->setToolTip(tr("Relative chance this tile will be picked")); mProbabilityProperty->setMinimum(0.0); + mTileProperties = new GroupProperty(tr("Tile")); + mTileProperties->addProperty(mIdProperty); + mTileProperties->addProperty(mClassProperty); + mTileProperties->addSeparator(); + + if (!tile()->imageSource().isEmpty()) + mTileProperties->addProperty(mImageProperty); + + mTileProperties->addProperty(mRectangleProperty); + mTileProperties->addProperty(mProbabilityProperty); + // annoying... maybe we should somehow always have the relevant TilesetDocument if (auto tilesetDocument = qobject_cast(document)) { connect(tilesetDocument, &TilesetDocument::tileImageSourceChanged, @@ -1500,16 +1565,7 @@ class TileProperties : public ObjectProperties void populateEditor(VariantEditor *editor) override { - editor->addHeader(tr("Tile")); - editor->addProperty(mIdProperty); - editor->addProperty(mClassProperty); - editor->addSeparator(); - - if (!tile()->imageSource().isEmpty()) - editor->addProperty(mImageProperty); - - editor->addProperty(mRectangleProperty); - editor->addProperty(mProbabilityProperty); + editor->addProperty(mTileProperties); } private: @@ -1549,6 +1605,7 @@ class TileProperties : public ObjectProperties return static_cast(mObject); } + GroupProperty *mTileProperties; Property *mIdProperty; UrlProperty *mImageProperty; RectProperty *mRectangleProperty; @@ -1588,6 +1645,13 @@ class WangSetProperties : public ObjectProperties }); mColorCountProperty->setRange(0, WangId::MAX_COLOR_COUNT); + mWangSetProperties = new GroupProperty(tr("Terrain Set")); + mWangSetProperties->addProperty(mNameProperty); + mWangSetProperties->addProperty(mClassProperty); + mWangSetProperties->addSeparator(); + mWangSetProperties->addProperty(mTypeProperty); + mWangSetProperties->addProperty(mColorCountProperty); + connect(document, &Document::changed, this, &WangSetProperties::onChanged); @@ -1596,12 +1660,7 @@ class WangSetProperties : public ObjectProperties void populateEditor(VariantEditor *editor) override { - editor->addHeader(tr("Terrain Set")); - editor->addProperty(mNameProperty); - editor->addProperty(mClassProperty); - editor->addSeparator(); - editor->addProperty(mTypeProperty); - editor->addProperty(mColorCountProperty); + editor->addProperty(mWangSetProperties); } private: @@ -1647,6 +1706,7 @@ class WangSetProperties : public ObjectProperties return static_cast(mObject); } + GroupProperty *mWangSetProperties; Property *mNameProperty; Property *mTypeProperty; IntProperty *mColorCountProperty; @@ -1683,6 +1743,13 @@ class WangColorProperties : public ObjectProperties }); mProbabilityProperty->setMinimum(0.01); + mWangColorProperties = new GroupProperty(tr("Terrain")); + mWangColorProperties->addProperty(mNameProperty); + mWangColorProperties->addProperty(mClassProperty); + mWangColorProperties->addSeparator(); + mWangColorProperties->addProperty(mColorProperty); + mWangColorProperties->addProperty(mProbabilityProperty); + connect(document, &Document::changed, this, &WangColorProperties::onChanged); @@ -1691,12 +1758,7 @@ class WangColorProperties : public ObjectProperties void populateEditor(VariantEditor *editor) override { - editor->addHeader(tr("Terrain")); - editor->addProperty(mNameProperty); - editor->addProperty(mClassProperty); - editor->addSeparator(); - editor->addProperty(mColorProperty); - editor->addProperty(mProbabilityProperty); + editor->addProperty(mWangColorProperties); } private: @@ -1743,6 +1805,7 @@ class WangColorProperties : public ObjectProperties return static_cast(mObject); } + GroupProperty *mWangColorProperties; Property *mNameProperty; Property *mColorProperty; FloatProperty *mProbabilityProperty; @@ -1818,6 +1881,56 @@ void PropertiesWidget::currentObjectChanged(Object *object) if (mPropertiesObject) mPropertiesObject->populateEditor(mPropertyBrowser); + GroupProperty *customProperties = new GroupProperty(tr("Custom Properties")); + + QMapIterator it(object ? object->properties() : Properties()); + PropertyFactory factory; + + while (it.hasNext()) { + it.next(); + + const auto &name = it.key(); + const auto &value = it.value(); + + Property *property = nullptr; + + switch (value.userType()) { + case QMetaType::Bool: + case QMetaType::QColor: + case QMetaType::Double: + case QMetaType::Int: + case QMetaType::QString: { + auto get = [object, name] { return object->property(name); }; + auto set = [this, object, name] (const QVariant &value) { + mDocument->undoStack()->push(new SetProperty(mDocument, { object }, name, value)); + }; + property = factory.createProperty(name, std::move(get), std::move(set)); + break; + } + default: + if (value.userType() == filePathTypeId()) { + auto get = [object, name] { return object->property(name).value().url; }; + auto set = [this, object, name](const QUrl &value) { + mDocument->undoStack()->push(new SetProperty(mDocument, { object }, name, QVariant::fromValue(FilePath { value }))); + }; + property = new UrlProperty(name, get, set); + } else if (value.userType() == objectRefTypeId()) { + auto get = [this, object, name] { return DisplayObjectRef(object->property(name).value(), static_cast(mDocument)); }; + auto set = [this, object, name](const DisplayObjectRef &value) { + mDocument->undoStack()->push(new SetProperty(mDocument, { object }, name, QVariant::fromValue(value.ref))); + }; + property = new ObjectRefProperty(name, get, set); + } + // todo: PropertyValue (enum and class values) + break; + } + + if (property) + customProperties->addProperty(property); + } + + mPropertyBrowser->addProperty(customProperties); + bool editingTileset = mDocument && mDocument->type() == Document::TilesetDocumentType; bool isTileset = object && object->isPartOfTileset(); bool enabled = object && (!isTileset || editingTileset); diff --git a/src/tiled/propertieswidget.h b/src/tiled/propertieswidget.h index 443529e38a..bda0cf672e 100644 --- a/src/tiled/propertieswidget.h +++ b/src/tiled/propertieswidget.h @@ -22,6 +22,8 @@ #include +class QScrollArea; + namespace Tiled { class Object; @@ -76,6 +78,7 @@ public slots: Document *mDocument = nullptr; ObjectProperties *mPropertiesObject = nullptr; + QScrollArea *mScrollArea; VariantEditor *mPropertyBrowser; QAction *mActionAddProperty; QAction *mActionRemoveProperty; diff --git a/src/tiled/propertyeditorwidgets.cpp b/src/tiled/propertyeditorwidgets.cpp index 706a91ab48..e779971053 100644 --- a/src/tiled/propertyeditorwidgets.cpp +++ b/src/tiled/propertyeditorwidgets.cpp @@ -1,3 +1,23 @@ +/* + * propertyeditorwidgets.cpp + * Copyright 2024, Thorbjørn Lindeijer + * + * This file is part of Tiled. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + #include "propertyeditorwidgets.h" #include "utils.h" @@ -7,6 +27,7 @@ #include #include #include +#include namespace Tiled { @@ -458,6 +479,57 @@ void ElidingLabel::paintEvent(QPaintEvent *) } +HeaderWidget::HeaderWidget(const QString &text, QWidget *parent) + : ElidingLabel(text, parent) +{ + setBackgroundRole(QPalette::Dark); + setForegroundRole(QPalette::BrightText); + setAutoFillBackground(true); + + const int verticalMargin = Utils::dpiScaled(3); + const int horizontalMargin = Utils::dpiScaled(6); + const int branchIndicatorWidth = Utils::dpiScaled(14); + setContentsMargins(horizontalMargin + branchIndicatorWidth, + verticalMargin, horizontalMargin, verticalMargin); +} + +void HeaderWidget::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + m_checked = !m_checked; + emit toggled(m_checked); + } + + ElidingLabel::mousePressEvent(event); +} + +void HeaderWidget::paintEvent(QPaintEvent *) +{ + const QRect cr = contentsRect(); + const Qt::LayoutDirection dir = text().isRightToLeft() ? Qt::RightToLeft : Qt::LeftToRight; + const int align = QStyle::visualAlignment(dir, {}); + const int flags = align | (dir == Qt::LeftToRight ? Qt::TextForceLeftToRight + : Qt::TextForceRightToLeft); + + QStyleOption branchOption; + branchOption.initFrom(this); + branchOption.rect = QRect(0, 0, contentsMargins().left(), height()); + branchOption.state = QStyle::State_Children; + if (m_checked) + branchOption.state |= QStyle::State_Open; + + QStylePainter p(this); + p.drawPrimitive(QStyle::PE_IndicatorBranch, branchOption); + + QStyleOption opt; + opt.initFrom(this); + + const auto elidedText = opt.fontMetrics.elidedText(text(), Qt::ElideRight, cr.width()); + + p.drawItemText(cr, flags, opt.palette, isEnabled(), elidedText, foregroundRole()); +} + + QSize LineEditLabel::sizeHint() const { auto hint = ElidingLabel::sizeHint(); diff --git a/src/tiled/propertyeditorwidgets.h b/src/tiled/propertyeditorwidgets.h index 84062ad323..9f38b945c6 100644 --- a/src/tiled/propertyeditorwidgets.h +++ b/src/tiled/propertyeditorwidgets.h @@ -1,3 +1,23 @@ +/* + * propertyeditorwidgets.h + * Copyright 2024, Thorbjørn Lindeijer + * + * This file is part of Tiled. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + #pragma once #include @@ -242,12 +262,35 @@ class ElidingLabel : public QLabel ElidingLabel(const QString &text, QWidget *parent = nullptr); QSize minimumSizeHint() const override; + +protected: void paintEvent(QPaintEvent *) override; private: bool m_isElided = false; }; +/** + * A header widget that can be toggled. + */ +class HeaderWidget : public ElidingLabel +{ + Q_OBJECT + +public: + HeaderWidget(const QString &text, QWidget *parent = nullptr); + +signals: + void toggled(bool checked); + +protected: + void mousePressEvent(QMouseEvent *event) override; + void paintEvent(QPaintEvent *) override; + +private: + bool m_checked = true; +}; + /** * A label that matches its preferred height with that of a line edit. */ diff --git a/src/tiled/textpropertyedit.cpp b/src/tiled/textpropertyedit.cpp index 147852cc22..85c131ebab 100644 --- a/src/tiled/textpropertyedit.cpp +++ b/src/tiled/textpropertyedit.cpp @@ -117,7 +117,7 @@ TextPropertyEdit::TextPropertyEdit(QWidget *parent) setFocusProxy(mLineEdit); QToolButton *button = new QToolButton(this); - button->setText(QStringLiteral("...")); + button->setText(QStringLiteral("…")); button->setAutoRaise(true); // Set a validator that replaces newline characters by literal "\\n". diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index abbd8bd2bb..9c8362182d 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -41,6 +41,30 @@ namespace Tiled { +void Property::setToolTip(const QString &toolTip) +{ + if (m_toolTip != toolTip) { + m_toolTip = toolTip; + emit toolTipChanged(toolTip); + } +} + +void Property::setEnabled(bool enabled) +{ + if (m_enabled != enabled) { + m_enabled = enabled; + emit enabledChanged(enabled); + } +} + +QWidget *GroupProperty::createEditor(QWidget *parent) +{ + auto widget = new VariantEditor(parent); + for (auto property : std::as_const(m_subProperties)) + widget->addProperty(property); + return widget; +} + QWidget *StringProperty::createEditor(QWidget *parent) { auto editor = new QLineEdit(parent); @@ -176,7 +200,7 @@ QWidget *PointFProperty::createEditor(QWidget *parent) connect(this, &Property::valueChanged, editor, syncEditor); connect(editor, &PointFEdit::valueChanged, this, [this, editor] { - this->setVariantValue(editor->value()); + this->setValue(editor->value()); }); return editor; @@ -426,20 +450,11 @@ QWidget *QtAlignmentProperty::createEditor(QWidget *parent) VariantEditor::VariantEditor(QWidget *parent) - : QScrollArea(parent) + : QWidget(parent) { - m_widget = new QWidget; - m_widget->setBackgroundRole(QPalette::AlternateBase); - auto verticalLayout = new QVBoxLayout(m_widget); - m_gridLayout = new QGridLayout; - verticalLayout->addLayout(m_gridLayout); - verticalLayout->addStretch(); - verticalLayout->setContentsMargins(0, 0, 0, Utils::dpiScaled(6)); - - setWidget(m_widget); - setWidgetResizable(true); - - m_gridLayout->setContentsMargins(QMargins()); + m_gridLayout = new QGridLayout(this); + + m_gridLayout->setContentsMargins(0, 0, 0, Utils::dpiScaled(3)); m_gridLayout->setSpacing(Utils::dpiScaled(3)); m_gridLayout->setColumnStretch(LabelColumn, 2); @@ -477,24 +492,20 @@ void VariantEditor::clear() m_rowIndex = 0; } -void VariantEditor::addHeader(const QString &text) +HeaderWidget *VariantEditor::addHeader(const QString &text) { - auto label = new ElidingLabel(text, m_widget); - label->setBackgroundRole(QPalette::Dark); - const int verticalMargin = Utils::dpiScaled(3); - const int horizontalMargin = Utils::dpiScaled(6); - label->setContentsMargins(horizontalMargin, verticalMargin, - horizontalMargin, verticalMargin); + auto headerWidget = new HeaderWidget(text, this); - label->setAutoFillBackground(true); + m_gridLayout->addWidget(headerWidget, m_rowIndex, 0, 1, ColumnCount); - m_gridLayout->addWidget(label, m_rowIndex, 0, 1, ColumnCount); ++m_rowIndex; + + return headerWidget; } void VariantEditor::addSeparator() { - auto separator = new QFrame(m_widget); + auto separator = new QFrame(this); separator->setFrameShape(QFrame::HLine); separator->setFrameShadow(QFrame::Plain); separator->setForegroundRole(QPalette::Mid); @@ -504,23 +515,49 @@ void VariantEditor::addSeparator() void VariantEditor::addProperty(Property *property) { - auto label = new LineEditLabel(property->name(), m_widget); - label->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); - label->setToolTip(property->toolTip()); - label->setEnabled(property->isEnabled()); - connect(property, &Property::toolTipChanged, label, &QWidget::setToolTip); - connect(property, &Property::enabledChanged, label, &QLabel::setEnabled); - m_gridLayout->addWidget(label, m_rowIndex, LabelColumn, Qt::AlignTop/* | Qt::AlignRight*/); - - if (auto editor = createEditor(property)) { - editor->setToolTip(property->toolTip()); - editor->setEnabled(property->isEnabled()); - connect(property, &Property::toolTipChanged, editor, &QWidget::setToolTip); - connect(property, &Property::enabledChanged, editor, &QWidget::setEnabled); - m_gridLayout->addWidget(editor, m_rowIndex, WidgetColumn); + switch (property->displayMode()) { + case Property::DisplayMode::Default: { + auto label = new LineEditLabel(property->name(), this); + label->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); + label->setToolTip(property->toolTip()); + label->setEnabled(property->isEnabled()); + connect(property, &Property::toolTipChanged, label, &QWidget::setToolTip); + connect(property, &Property::enabledChanged, label, &QLabel::setEnabled); + m_gridLayout->addWidget(label, m_rowIndex, LabelColumn, Qt::AlignTop/* | Qt::AlignRight*/); + + if (auto editor = createEditor(property)) { + editor->setToolTip(property->toolTip()); + editor->setEnabled(property->isEnabled()); + connect(property, &Property::toolTipChanged, editor, &QWidget::setToolTip); + connect(property, &Property::enabledChanged, editor, &QWidget::setEnabled); + m_gridLayout->addWidget(editor, m_rowIndex, WidgetColumn); + } + + ++m_rowIndex; + break; } + case Property::DisplayMode::Header: { + auto headerWidget = addHeader(property->name()); - ++m_rowIndex; + if (auto editor = createEditor(property)) { + connect(headerWidget, &HeaderWidget::toggled, + editor, [this, editor](bool checked) { + editor->setVisible(checked); + + // needed to avoid flickering when hiding the editor + layout()->activate(); + }); + + m_gridLayout->addWidget(editor, m_rowIndex, 0, 1, ColumnCount); + ++m_rowIndex; + } + + break; + } + case Property::DisplayMode::Separator: + addSeparator(); + break; + } } #if 0 @@ -554,7 +591,7 @@ void VariantEditor::addValue(const QVariant &value) QWidget *VariantEditor::createEditor(Property *property) { - if (const auto editor = property->createEditor(m_widget)) { + if (const auto editor = property->createEditor(this)) { editor->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); return editor; } else { diff --git a/src/tiled/varianteditor.h b/src/tiled/varianteditor.h index 6e59c679a8..37afb146e3 100644 --- a/src/tiled/varianteditor.h +++ b/src/tiled/varianteditor.h @@ -32,6 +32,8 @@ class QGridLayout; namespace Tiled { +class HeaderWidget; + /** * A property represents a named value that can create its own edit widget. */ @@ -40,10 +42,15 @@ class Property : public QObject Q_OBJECT Q_PROPERTY(QString name READ name CONSTANT) Q_PROPERTY(QString toolTip READ toolTip WRITE setToolTip NOTIFY toolTipChanged) - Q_PROPERTY(QVariant value READ variantValue WRITE setVariantValue NOTIFY valueChanged) Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged) public: + enum class DisplayMode { + Default, + Header, + Separator + }; + Property(const QString &name, QObject *parent = nullptr) : QObject(parent) , m_name(name) @@ -52,25 +59,12 @@ class Property : public QObject const QString &name() const { return m_name; } const QString &toolTip() const { return m_toolTip; } - void setToolTip(const QString &toolTip) - { - if (m_toolTip != toolTip) { - m_toolTip = toolTip; - emit toolTipChanged(toolTip); - } - } + void setToolTip(const QString &toolTip); bool isEnabled() const { return m_enabled; } - void setEnabled(bool enabled) - { - if (m_enabled != enabled) { - m_enabled = enabled; - emit enabledChanged(enabled); - } - } + void setEnabled(bool enabled); - virtual QVariant variantValue() const = 0; - virtual void setVariantValue(const QVariant &value) = 0; + virtual DisplayMode displayMode() const { return DisplayMode::Default; } virtual QWidget *createEditor(QWidget *parent) = 0; @@ -85,6 +79,42 @@ class Property : public QObject bool m_enabled = true; }; +class Separator final : public Property +{ + Q_OBJECT + +public: + Separator(QObject *parent = nullptr) + : Property(QString(), parent) + {} + + DisplayMode displayMode() const override { return DisplayMode::Separator; } + + QWidget *createEditor(QWidget */*parent*/) override { return nullptr; } +}; + +class GroupProperty : public Property +{ + Q_OBJECT + +public: + GroupProperty(const QString &name, QObject *parent = nullptr) + : Property(name, parent) + {} + + DisplayMode displayMode() const override { return DisplayMode::Header; } + + QWidget *createEditor(QWidget *parent) override; + + void addProperty(Property *property) { m_subProperties.append(property); } + void addSeparator() { m_subProperties.append(new Separator(this)); } + + const QList &subProperties() const { return m_subProperties; } + +private: + QList m_subProperties; +}; + /** * A helper class for creating a property that wraps a value of a given type. */ @@ -106,17 +136,6 @@ class PropertyTemplate : public Property Type value() const { return m_get(); } void setValue(const Type &value) { m_set(value); } - QVariant variantValue() const override - { - return QVariant::fromValue(m_get()); - } - - void setVariantValue(const QVariant &value) override - { - if (m_set) - m_set(value.value()); - } - private: std::function m_get; std::function m_set; @@ -357,7 +376,7 @@ class EnumProperty : public IntProperty }; -class VariantEditor : public QScrollArea +class VariantEditor : public QWidget { Q_OBJECT @@ -365,7 +384,7 @@ class VariantEditor : public QScrollArea VariantEditor(QWidget *parent = nullptr); void clear(); - void addHeader(const QString &text); + HeaderWidget *addHeader(const QString &text); void addSeparator(); void addProperty(Property *property); // void addValue(const QVariant &value); @@ -382,7 +401,6 @@ class VariantEditor : public QScrollArea ColumnCount, }; - QWidget *m_widget; QGridLayout *m_gridLayout; int m_rowIndex = 0; }; From 5a4a7e9694fcfcc55a584823cad340fc7c113719 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Mon, 16 Sep 2024 13:56:11 +0200 Subject: [PATCH 24/36] Made a special case for the image layer repeat setting Also made some other small tweaks. --- src/libtiled/imagelayer.cpp | 3 +- src/libtiled/imagelayer.h | 43 ++++++++-------- src/tiled/propertieswidget.cpp | 79 +++++++++++++++++++++-------- src/tiled/propertyeditorwidgets.cpp | 26 +++------- src/tiled/varianteditor.cpp | 2 +- 5 files changed, 91 insertions(+), 62 deletions(-) diff --git a/src/libtiled/imagelayer.cpp b/src/libtiled/imagelayer.cpp index 9086d87c8a..ab1169115c 100644 --- a/src/libtiled/imagelayer.cpp +++ b/src/libtiled/imagelayer.cpp @@ -104,8 +104,7 @@ ImageLayer *ImageLayer::initializeClone(ImageLayer *clone) const clone->mImageSource = mImageSource; clone->mTransparentColor = mTransparentColor; clone->mImage = mImage; - clone->mRepeatX = mRepeatX; - clone->mRepeatY = mRepeatY; + clone->mRepetition = mRepetition; return clone; } diff --git a/src/libtiled/imagelayer.h b/src/libtiled/imagelayer.h index 2a63df2071..faf00a4c6b 100644 --- a/src/libtiled/imagelayer.h +++ b/src/libtiled/imagelayer.h @@ -46,6 +46,19 @@ namespace Tiled { class TILEDSHARED_EXPORT ImageLayer : public Layer { public: + enum Repetition { + /** + * Makes the image repeat along the X axis. + */ + RepeatX = 0x1, + + /** + * Makes the image repeat along the Y axis. + */ + RepeatY = 0x2, + }; + Q_DECLARE_FLAGS(RepetitionFlags, Repetition) + ImageLayer(const QString &name, int x, int y); ~ImageLayer() override; @@ -114,25 +127,13 @@ class TILEDSHARED_EXPORT ImageLayer : public Layer */ bool isEmpty() const override; - /** - * Returns true if the image of this layer repeats along the X axis. - */ - bool repeatX() const { return mRepeatX; } - - /** - * Returns true if the image of this layer repeats along the Y axis. - */ - bool repeatY() const { return mRepeatY; } - - /** - * Sets whether the image of this layer repeats along the X axis. - */ - void setRepeatX(bool repeatX) { mRepeatX = repeatX; } + bool repeatX() const { return mRepetition & RepeatX; } + bool repeatY() const { return mRepetition & RepeatY; } + RepetitionFlags repetition() const { return mRepetition; } - /** - * Sets whether the image of this layer repeats along the Y axis. - */ - void setRepeatY(bool repeatY) { mRepeatY = repeatY; } + void setRepeatX(bool repeatX) { mRepetition.setFlag(RepeatX, repeatX); } + void setRepeatY(bool repeatY) { mRepetition.setFlag(RepeatY, repeatY); } + void setRepetition(RepetitionFlags repetition) { mRepetition = repetition; } ImageLayer *clone() const override; @@ -143,8 +144,10 @@ class TILEDSHARED_EXPORT ImageLayer : public Layer QUrl mImageSource; QColor mTransparentColor; QPixmap mImage; - bool mRepeatX = false; - bool mRepeatY = false; + RepetitionFlags mRepetition; }; } // namespace Tiled + +Q_DECLARE_METATYPE(Tiled::ImageLayer::RepetitionFlags) +Q_DECLARE_OPERATORS_FOR_FLAGS(Tiled::ImageLayer::RepetitionFlags) diff --git a/src/tiled/propertieswidget.cpp b/src/tiled/propertieswidget.cpp index 3f40d76ffe..506fa84fd0 100644 --- a/src/tiled/propertieswidget.cpp +++ b/src/tiled/propertieswidget.cpp @@ -48,6 +48,7 @@ #include "wangoverlay.h" #include +#include #include #include #include @@ -220,6 +221,49 @@ class ObjectRefProperty : public PropertyTemplate }; +class ImageLayerRepeatProperty : public PropertyTemplate +{ + Q_OBJECT + +public: + using PropertyTemplate::PropertyTemplate; + + QWidget *createEditor(QWidget *parent) override + { + auto editor = new QWidget(parent); + auto layout = new QHBoxLayout(editor); + auto repeatX = new QCheckBox(tr("X"), editor); + auto repeatY = new QCheckBox(tr("Y"), editor); + layout->setContentsMargins(QMargins()); + layout->addWidget(repeatX); + layout->addWidget(repeatY); + + auto syncEditor = [=] { + const QSignalBlocker xBlocker(repeatX); + const QSignalBlocker yBlocker(repeatY); + const auto v = value(); + repeatX->setChecked(v & ImageLayer::RepeatX); + repeatY->setChecked(v & ImageLayer::RepeatY); + }; + auto syncProperty = [=] { + ImageLayer::RepetitionFlags v; + if (repeatX->isChecked()) + v |= ImageLayer::RepeatX; + if (repeatY->isChecked()) + v |= ImageLayer::RepeatY; + setValue(v); + }; + + syncEditor(); + + connect(this, &Property::valueChanged, editor, syncEditor); + connect(repeatX, &QCheckBox::toggled, this, syncProperty); + connect(repeatY, &QCheckBox::toggled, this, syncProperty); + return editor; + } +}; + + PropertiesWidget::PropertiesWidget(QWidget *parent) : QWidget{parent} , mScrollArea(new QScrollArea(this)) @@ -231,7 +275,7 @@ PropertiesWidget::PropertiesWidget(QWidget *parent) mPropertyBrowser = new VariantEditor(scrollWidget); verticalLayout->addWidget(mPropertyBrowser); verticalLayout->addStretch(); - verticalLayout->setContentsMargins(QMargins()); + verticalLayout->setContentsMargins(0, 0, 0, Utils::dpiScaled(4)); mScrollArea->setWidget(scrollWidget); mScrollArea->setWidgetResizable(true); @@ -402,6 +446,7 @@ class ClassProperty : public StringProperty Object *mObject; }; + class MapSizeProperty : public SizeProperty { Q_OBJECT @@ -898,27 +943,23 @@ class ImageLayerProperties : public LayerProperties push(new ChangeImageLayerTransparentColor(mapDocument(), { imageLayer() }, value)); }); - // todo: consider merging Repeat X and Y into a single property - mRepeatXProperty = new BoolProperty( - tr("Repeat X"), - [this] { return imageLayer()->repeatX(); }, - [this](const bool &value) { - push(new ChangeImageLayerRepeatX(mapDocument(), { imageLayer() }, value)); - }); - - mRepeatYProperty = new BoolProperty( - tr("Repeat Y"), - [this] { return imageLayer()->repeatY(); }, - [this](const bool &value) { - push(new ChangeImageLayerRepeatY(mapDocument(), { imageLayer() }, value)); + mRepeatProperty = new ImageLayerRepeatProperty( + tr("Repeat"), + [this] { return imageLayer()->repetition(); }, + [this](const ImageLayer::RepetitionFlags &value) { + const bool repeatX = value & ImageLayer::RepeatX; + const bool repeatY = value & ImageLayer::RepeatY; + if (repeatX != imageLayer()->repeatX()) + push(new ChangeImageLayerRepeatX(mapDocument(), { imageLayer() }, repeatX)); + if (repeatY != imageLayer()->repeatY()) + push(new ChangeImageLayerRepeatY(mapDocument(), { imageLayer() }, repeatY)); }); mImageLayerProperties = new GroupProperty(tr("Image Layer")); mImageLayerProperties->addProperty(mImageProperty); mImageLayerProperties->addProperty(mTransparentColorProperty); mImageLayerProperties->addSeparator(); - mImageLayerProperties->addProperty(mRepeatXProperty); - mImageLayerProperties->addProperty(mRepeatYProperty); + mImageLayerProperties->addProperty(mRepeatProperty); } void populateEditor(VariantEditor *editor) override @@ -944,8 +985,7 @@ class ImageLayerProperties : public LayerProperties if (layerChange.properties & ImageLayerChangeEvent::TransparentColorProperty) emit mTransparentColorProperty->valueChanged(); if (layerChange.properties & ImageLayerChangeEvent::RepeatProperty) { - emit mRepeatXProperty->valueChanged(); - emit mRepeatYProperty->valueChanged(); + emit mRepeatProperty->valueChanged(); } } @@ -957,8 +997,7 @@ class ImageLayerProperties : public LayerProperties GroupProperty *mImageLayerProperties; UrlProperty *mImageProperty; Property *mTransparentColorProperty; - Property *mRepeatXProperty; - Property *mRepeatYProperty; + Property *mRepeatProperty; }; class ObjectGroupProperties : public LayerProperties diff --git a/src/tiled/propertyeditorwidgets.cpp b/src/tiled/propertyeditorwidgets.cpp index e779971053..b6f3136c1c 100644 --- a/src/tiled/propertyeditorwidgets.cpp +++ b/src/tiled/propertyeditorwidgets.cpp @@ -474,8 +474,8 @@ void ElidingLabel::paintEvent(QPaintEvent *) setToolTip(isElided ? text() : QString()); } - QPainter painter(this); - QWidget::style()->drawItemText(&painter, cr, flags, opt.palette, isEnabled(), elidedText, foregroundRole()); + QStylePainter p(this); + p.drawItemText(cr, flags, opt.palette, isEnabled(), elidedText, foregroundRole()); } @@ -486,11 +486,10 @@ HeaderWidget::HeaderWidget(const QString &text, QWidget *parent) setForegroundRole(QPalette::BrightText); setAutoFillBackground(true); - const int verticalMargin = Utils::dpiScaled(3); - const int horizontalMargin = Utils::dpiScaled(6); + const int spacing = Utils::dpiScaled(4); const int branchIndicatorWidth = Utils::dpiScaled(14); - setContentsMargins(horizontalMargin + branchIndicatorWidth, - verticalMargin, horizontalMargin, verticalMargin); + setContentsMargins(spacing + branchIndicatorWidth, + spacing, spacing, spacing); } void HeaderWidget::mousePressEvent(QMouseEvent *event) @@ -503,13 +502,9 @@ void HeaderWidget::mousePressEvent(QMouseEvent *event) ElidingLabel::mousePressEvent(event); } -void HeaderWidget::paintEvent(QPaintEvent *) +void HeaderWidget::paintEvent(QPaintEvent *event) { - const QRect cr = contentsRect(); - const Qt::LayoutDirection dir = text().isRightToLeft() ? Qt::RightToLeft : Qt::LeftToRight; - const int align = QStyle::visualAlignment(dir, {}); - const int flags = align | (dir == Qt::LeftToRight ? Qt::TextForceLeftToRight - : Qt::TextForceRightToLeft); + ElidingLabel::paintEvent(event); QStyleOption branchOption; branchOption.initFrom(this); @@ -520,13 +515,6 @@ void HeaderWidget::paintEvent(QPaintEvent *) QStylePainter p(this); p.drawPrimitive(QStyle::PE_IndicatorBranch, branchOption); - - QStyleOption opt; - opt.initFrom(this); - - const auto elidedText = opt.fontMetrics.elidedText(text(), Qt::ElideRight, cr.width()); - - p.drawItemText(cr, flags, opt.palette, isEnabled(), elidedText, foregroundRole()); } diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index 9c8362182d..2b2024d65c 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -681,7 +681,7 @@ Property *PropertyFactory::createProperty(const QString &name, const auto type = get().userType(); switch (type) { case QMetaType::QString: - return createTypedProperty(name, get, set); + return createTypedProperty(name, get, set); case QMetaType::QUrl: return createTypedProperty(name, get, set); case QMetaType::Int: From 6cee32b625d806b8eef7821f5076dfcc4b18bce5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Mon, 16 Sep 2024 13:27:07 +0200 Subject: [PATCH 25/36] Added support for "no label" properties, used for bool values Also fixed indentatation of property labels to match the header widget. And fixed the margins on the side for groups of properties without separator (affected Custom Properties). Not entirely sure moving the label of boolean properties is the right thing to do. --- src/tiled/varianteditor.cpp | 66 ++++++++++++++++++------------------- src/tiled/varianteditor.h | 14 ++++---- 2 files changed, 39 insertions(+), 41 deletions(-) diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index 2b2024d65c..0f978e16ee 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -152,20 +152,16 @@ QWidget *FloatProperty::createEditor(QWidget *parent) QWidget *BoolProperty::createEditor(QWidget *parent) { - auto editor = new QCheckBox(parent); + auto editor = new QCheckBox(name(), parent); auto syncEditor = [=] { const QSignalBlocker blocker(editor); bool checked = value(); editor->setChecked(checked); - editor->setText(checked ? tr("On") : tr("Off")); }; syncEditor(); connect(this, &Property::valueChanged, editor, syncEditor); - connect(editor, &QCheckBox::toggled, this, [=](bool checked) { - editor->setText(checked ? QObject::tr("On") : QObject::tr("Off")); - setValue(checked); - }); + connect(editor, &QCheckBox::toggled, this, &BoolProperty::setValue); return editor; } @@ -452,16 +448,10 @@ QWidget *QtAlignmentProperty::createEditor(QWidget *parent) VariantEditor::VariantEditor(QWidget *parent) : QWidget(parent) { - m_gridLayout = new QGridLayout(this); - - m_gridLayout->setContentsMargins(0, 0, 0, Utils::dpiScaled(3)); - m_gridLayout->setSpacing(Utils::dpiScaled(3)); + m_layout = new QVBoxLayout(this); - m_gridLayout->setColumnStretch(LabelColumn, 2); - m_gridLayout->setColumnStretch(WidgetColumn, 3); - m_gridLayout->setColumnMinimumWidth(LeftSpacing, Utils::dpiScaled(3)); - m_gridLayout->setColumnMinimumWidth(MiddleSpacing, Utils::dpiScaled(2)); - m_gridLayout->setColumnMinimumWidth(RightSpacing, Utils::dpiScaled(3)); + m_layout->setContentsMargins(QMargins()); + m_layout->setSpacing(Utils::dpiScaled(4)); // setValue(QVariantMap { // { QStringLiteral("Name"), QVariant(QLatin1String("Hello")) }, @@ -485,7 +475,7 @@ VariantEditor::VariantEditor(QWidget *parent) void VariantEditor::clear() { QLayoutItem *item; - while ((item = m_gridLayout->takeAt(0))) { + while ((item = m_layout->takeAt(0))) { delete item->widget(); delete item; } @@ -496,9 +486,7 @@ HeaderWidget *VariantEditor::addHeader(const QString &text) { auto headerWidget = new HeaderWidget(text, this); - m_gridLayout->addWidget(headerWidget, m_rowIndex, 0, 1, ColumnCount); - - ++m_rowIndex; + m_layout->addWidget(headerWidget); return headerWidget; } @@ -509,31 +497,44 @@ void VariantEditor::addSeparator() separator->setFrameShape(QFrame::HLine); separator->setFrameShadow(QFrame::Plain); separator->setForegroundRole(QPalette::Mid); - m_gridLayout->addWidget(separator, m_rowIndex, 0, 1, ColumnCount); - ++m_rowIndex; + m_layout->addWidget(separator); } void VariantEditor::addProperty(Property *property) { + const int spacing = Utils::dpiScaled(4); + const int branchIndicatorWidth = Utils::dpiScaled(14); + switch (property->displayMode()) { - case Property::DisplayMode::Default: { - auto label = new LineEditLabel(property->name(), this); - label->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); - label->setToolTip(property->toolTip()); - label->setEnabled(property->isEnabled()); - connect(property, &Property::toolTipChanged, label, &QWidget::setToolTip); - connect(property, &Property::enabledChanged, label, &QLabel::setEnabled); - m_gridLayout->addWidget(label, m_rowIndex, LabelColumn, Qt::AlignTop/* | Qt::AlignRight*/); + case Property::DisplayMode::Default: + case Property::DisplayMode::NoLabel: { + auto propertyLayout = new QHBoxLayout; + propertyLayout->setContentsMargins(spacing, 0, spacing, 0); + propertyLayout->setSpacing(spacing); + + if (property->displayMode() == Property::DisplayMode::Default) { + auto label = new LineEditLabel(property->name(), this); + label->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); + label->setToolTip(property->toolTip()); + label->setEnabled(property->isEnabled()); + label->setContentsMargins(branchIndicatorWidth, 0, 0, 0); + connect(property, &Property::toolTipChanged, label, &QWidget::setToolTip); + connect(property, &Property::enabledChanged, label, &QWidget::setEnabled); + propertyLayout->addWidget(label, LabelStretch, Qt::AlignTop); + } else { + propertyLayout->addStretch(LabelStretch); + } if (auto editor = createEditor(property)) { editor->setToolTip(property->toolTip()); editor->setEnabled(property->isEnabled()); connect(property, &Property::toolTipChanged, editor, &QWidget::setToolTip); connect(property, &Property::enabledChanged, editor, &QWidget::setEnabled); - m_gridLayout->addWidget(editor, m_rowIndex, WidgetColumn); + propertyLayout->addWidget(editor, WidgetStretch); } - ++m_rowIndex; + m_layout->addLayout(propertyLayout); + break; } case Property::DisplayMode::Header: { @@ -548,8 +549,7 @@ void VariantEditor::addProperty(Property *property) layout()->activate(); }); - m_gridLayout->addWidget(editor, m_rowIndex, 0, 1, ColumnCount); - ++m_rowIndex; + m_layout->addWidget(editor); } break; diff --git a/src/tiled/varianteditor.h b/src/tiled/varianteditor.h index 37afb146e3..b60e53860b 100644 --- a/src/tiled/varianteditor.h +++ b/src/tiled/varianteditor.h @@ -28,7 +28,7 @@ #include #include -class QGridLayout; +class QVBoxLayout; namespace Tiled { @@ -47,6 +47,7 @@ class Property : public QObject public: enum class DisplayMode { Default, + NoLabel, Header, Separator }; @@ -209,6 +210,7 @@ struct FloatProperty : PropertyTemplate struct BoolProperty : PropertyTemplate { using PropertyTemplate::PropertyTemplate; + DisplayMode displayMode() const override { return DisplayMode::NoLabel; } QWidget *createEditor(QWidget *parent) override; }; @@ -393,15 +395,11 @@ class VariantEditor : public QWidget QWidget *createEditor(Property *property); enum Column { - LeftSpacing, - LabelColumn, - MiddleSpacing, - WidgetColumn, - RightSpacing, - ColumnCount, + LabelStretch = 2, + WidgetStretch = 3, }; - QGridLayout *m_gridLayout; + QVBoxLayout *m_layout; int m_rowIndex = 0; }; From b84553d3b40eee5b3d489224ec3781297742e2e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Mon, 16 Sep 2024 14:19:31 +0200 Subject: [PATCH 26/36] Used Slider for opacity property, replacing the one in Layers view Of course, this means for changing opacity the Properties view should now be open. There is also no longer a way to see the actual value anymore, but that could be added to the widget in the Properties view (maybe as percentage). --- src/tiled/layerdock.cpp | 90 ++-------------------------------- src/tiled/layerdock.h | 9 ---- src/tiled/propertieswidget.cpp | 14 +++--- src/tiled/varianteditor.cpp | 18 +++++++ src/tiled/varianteditor.h | 8 ++- 5 files changed, 34 insertions(+), 105 deletions(-) diff --git a/src/tiled/layerdock.cpp b/src/tiled/layerdock.cpp index 4810d0367d..f6dd6b3abc 100644 --- a/src/tiled/layerdock.cpp +++ b/src/tiled/layerdock.cpp @@ -29,7 +29,6 @@ #include "map.h" #include "mapdocument.h" #include "mapdocumentactionhandler.h" -#include "objectgroup.h" #include "reversingproxymodel.h" #include "utils.h" #include "iconcheckdelegate.h" @@ -39,10 +38,8 @@ #include #include #include -#include #include #include -#include #include #include #include @@ -54,8 +51,6 @@ using namespace Tiled; LayerDock::LayerDock(QWidget *parent): QDockWidget(parent), - mOpacityLabel(new QLabel), - mOpacitySlider(new QSlider(Qt::Horizontal)), mLayerView(new LayerView) { setObjectName(QLatin1String("layerDock")); @@ -64,13 +59,6 @@ LayerDock::LayerDock(QWidget *parent): QVBoxLayout *layout = new QVBoxLayout(widget); layout->setContentsMargins(0, 0, 0, 0); - QHBoxLayout *opacityLayout = new QHBoxLayout; - mOpacitySlider->setRange(0, 100); - mOpacitySlider->setEnabled(false); - opacityLayout->addWidget(mOpacityLabel); - opacityLayout->addWidget(mOpacitySlider); - mOpacityLabel->setBuddy(mOpacitySlider); - MapDocumentActionHandler *handler = MapDocumentActionHandler::instance(); QMenu *newLayerMenu = handler->createNewLayerMenu(this); @@ -99,20 +87,12 @@ LayerDock::LayerDock(QWidget *parent): buttonContainer->addWidget(spacerWidget); buttonContainer->addAction(ActionManager::action("HighlightCurrentLayer")); - QVBoxLayout *listAndToolBar = new QVBoxLayout; - listAndToolBar->setSpacing(0); - listAndToolBar->addWidget(mLayerView); - listAndToolBar->addWidget(buttonContainer); - - layout->addLayout(opacityLayout); - layout->addLayout(listAndToolBar); + layout->setSpacing(0); + layout->addWidget(mLayerView); + layout->addWidget(buttonContainer); setWidget(widget); retranslateUi(); - - connect(mOpacitySlider, &QAbstractSlider::valueChanged, - this, &LayerDock::sliderValueChanged); - updateOpacitySlider(); } void LayerDock::setMapDocument(MapDocument *mapDocument) @@ -126,10 +106,6 @@ void LayerDock::setMapDocument(MapDocument *mapDocument) mMapDocument = mapDocument; if (mMapDocument) { - connect(mMapDocument, &MapDocument::changed, - this, &LayerDock::documentChanged); - connect(mMapDocument, &MapDocument::currentLayerChanged, - this, &LayerDock::updateOpacitySlider); connect(mMapDocument, &MapDocument::editLayerNameRequested, this, &LayerDock::editLayerName); } @@ -145,8 +121,6 @@ void LayerDock::setMapDocument(MapDocument *mapDocument) mLayerView->header()->resizeSection(1, iconSectionWidth); mLayerView->header()->resizeSection(2, iconSectionWidth); } - - updateOpacitySlider(); } void LayerDock::changeEvent(QEvent *e) @@ -162,40 +136,6 @@ void LayerDock::changeEvent(QEvent *e) } } -void LayerDock::updateOpacitySlider() -{ - const bool enabled = mMapDocument && - mMapDocument->currentLayer() != nullptr; - - mOpacitySlider->setEnabled(enabled); - mOpacityLabel->setEnabled(enabled); - - QScopedValueRollback updating(mUpdatingSlider, true); - if (enabled) { - qreal opacity = mMapDocument->currentLayer()->opacity(); - mOpacitySlider->setValue(qRound(opacity * 100)); - } else { - mOpacitySlider->setValue(100); - } -} - -void LayerDock::documentChanged(const ChangeEvent &change) -{ - switch (change.type) { - case ChangeEvent::LayerChanged: { - auto &layerChange = static_cast(change); - - // Don't update the slider when we're the ones changing the layer opacity - if ((layerChange.properties & LayerChangeEvent::OpacityProperty) && !mChangingLayerOpacity) - if (layerChange.layer == mMapDocument->currentLayer()) - updateOpacitySlider(); - break; - } - default: - break; - } -} - void LayerDock::editLayerName() { if (!isVisible()) @@ -208,33 +148,9 @@ void LayerDock::editLayerName() mLayerView->editLayerModelIndex(layerModel->index(currentLayer)); } -void LayerDock::sliderValueChanged(int opacity) -{ - if (!mMapDocument) - return; - - // When the slider changes value just because we're updating it, it - // shouldn't try to set the layer opacity. - if (mUpdatingSlider) - return; - - const auto layer = mMapDocument->currentLayer(); - if (!layer) - return; - - if (static_cast(layer->opacity() * 100) != opacity) { - LayerModel *layerModel = mMapDocument->layerModel(); - QScopedValueRollback updating(mChangingLayerOpacity, true); - layerModel->setData(layerModel->index(layer), - qreal(opacity) / 100, - LayerModel::OpacityRole); - } -} - void LayerDock::retranslateUi() { setWindowTitle(tr("Layers")); - mOpacityLabel->setText(tr("Opacity:")); mNewLayerButton->setToolTip(tr("New Layer")); } diff --git a/src/tiled/layerdock.h b/src/tiled/layerdock.h index 41e286c530..2b1692750d 100644 --- a/src/tiled/layerdock.h +++ b/src/tiled/layerdock.h @@ -28,9 +28,7 @@ #include class QAbstractProxyModel; -class QLabel; class QModelIndex; -class QUndoStack; namespace Tiled { @@ -58,20 +56,13 @@ class LayerDock : public QDockWidget void changeEvent(QEvent *e) override; private: - void updateOpacitySlider(); - void documentChanged(const ChangeEvent &change); void editLayerName(); - void sliderValueChanged(int opacity); void retranslateUi(); - QLabel *mOpacityLabel; - QSlider *mOpacitySlider; QToolButton *mNewLayerButton; LayerView *mLayerView; MapDocument *mMapDocument = nullptr; - bool mUpdatingSlider = false; - bool mChangingLayerOpacity = false; }; /** diff --git a/src/tiled/propertieswidget.cpp b/src/tiled/propertieswidget.cpp index 506fa84fd0..2ce0de1440 100644 --- a/src/tiled/propertieswidget.cpp +++ b/src/tiled/propertieswidget.cpp @@ -822,15 +822,13 @@ class LayerProperties : public ObjectProperties push(new SetLayerLocked(mapDocument(), { layer() }, value)); }); - // todo: would be nice to use a slider (replacing the one in Layers view) - mOpacityProperty = new FloatProperty( + mOpacityProperty = new SliderProperty( tr("Opacity"), - [this] { return layer()->opacity(); }, - [this](const double &value) { - push(new SetLayerOpacity(mapDocument(), { layer() }, value)); + [this] { return qRound(layer()->opacity() * 100); }, + [this](const int &value) { + push(new SetLayerOpacity(mapDocument(), { layer() }, qreal(value) / 100)); }); - mOpacityProperty->setRange(0.0, 1.0); - mOpacityProperty->setSingleStep(0.1); + mOpacityProperty->setRange(0, 100); mTintColorProperty = new ColorProperty( tr("Tint Color"), @@ -914,7 +912,7 @@ class LayerProperties : public ObjectProperties Property *mNameProperty; Property *mVisibleProperty; Property *mLockedProperty; - FloatProperty *mOpacityProperty; + SliderProperty *mOpacityProperty; Property *mTintColorProperty; Property *mOffsetProperty; PointFProperty *mParallaxFactorProperty; diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index 0f978e16ee..4cc734da88 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -130,6 +130,24 @@ QWidget *IntProperty::createEditor(QWidget *parent) return editor; } +QWidget *SliderProperty::createEditor(QWidget *parent) +{ + auto editor = new QSlider(Qt::Horizontal, parent); + editor->setRange(m_minimum, m_maximum); + editor->setSingleStep(m_singleStep); + + auto syncEditor = [=] { + const QSignalBlocker blocker(editor); + editor->setValue(value()); + }; + syncEditor(); + + connect(this, &Property::valueChanged, editor, syncEditor); + connect(editor, &QSlider::valueChanged, this, &SliderProperty::setValue); + + return editor; +} + QWidget *FloatProperty::createEditor(QWidget *parent) { auto editor = new DoubleSpinBox(parent); diff --git a/src/tiled/varianteditor.h b/src/tiled/varianteditor.h index b60e53860b..9501bef042 100644 --- a/src/tiled/varianteditor.h +++ b/src/tiled/varianteditor.h @@ -178,13 +178,19 @@ struct IntProperty : PropertyTemplate setMaximum(maximum); } -private: +protected: int m_minimum = std::numeric_limits::min(); int m_maximum = std::numeric_limits::max(); int m_singleStep = 1; QString m_suffix; }; +struct SliderProperty : IntProperty +{ + using IntProperty::IntProperty; + QWidget *createEditor(QWidget *parent) override; +}; + struct FloatProperty : PropertyTemplate { using PropertyTemplate::PropertyTemplate; From e3b2ee154fd73195ffb607e80b7367f1f6878060 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Mon, 16 Sep 2024 16:37:35 +0200 Subject: [PATCH 27/36] Make slider an IntProperty option rather than a separate property type Also, enabling a slider does not replace the spinbox, but puts a slider next to it. Because I don't think a bare slider is ever what we'd want to use. --- src/tiled/propertieswidget.cpp | 6 ++-- src/tiled/varianteditor.cpp | 55 +++++++++++++++++----------------- src/tiled/varianteditor.h | 8 ++--- 3 files changed, 34 insertions(+), 35 deletions(-) diff --git a/src/tiled/propertieswidget.cpp b/src/tiled/propertieswidget.cpp index 2ce0de1440..f586ab5e68 100644 --- a/src/tiled/propertieswidget.cpp +++ b/src/tiled/propertieswidget.cpp @@ -822,13 +822,15 @@ class LayerProperties : public ObjectProperties push(new SetLayerLocked(mapDocument(), { layer() }, value)); }); - mOpacityProperty = new SliderProperty( + mOpacityProperty = new IntProperty( tr("Opacity"), [this] { return qRound(layer()->opacity() * 100); }, [this](const int &value) { push(new SetLayerOpacity(mapDocument(), { layer() }, qreal(value) / 100)); }); mOpacityProperty->setRange(0, 100); + mOpacityProperty->setSuffix(tr("%")); + mOpacityProperty->setSliderEnabled(true); mTintColorProperty = new ColorProperty( tr("Tint Color"), @@ -912,7 +914,7 @@ class LayerProperties : public ObjectProperties Property *mNameProperty; Property *mVisibleProperty; Property *mLockedProperty; - SliderProperty *mOpacityProperty; + IntProperty *mOpacityProperty; Property *mTintColorProperty; Property *mOffsetProperty; PointFProperty *mParallaxFactorProperty; diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index 4cc734da88..4b87873ef1 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -112,40 +112,41 @@ QWidget *UrlProperty::createEditor(QWidget *parent) QWidget *IntProperty::createEditor(QWidget *parent) { - auto editor = new SpinBox(parent); - editor->setRange(m_minimum, m_maximum); - editor->setSingleStep(m_singleStep); - editor->setSuffix(m_suffix); + auto widget = new QWidget(parent); + auto layout = new QHBoxLayout(widget); + layout->setContentsMargins(QMargins()); - auto syncEditor = [=] { - const QSignalBlocker blocker(editor); - editor->setValue(value()); - }; - syncEditor(); + if (m_sliderEnabled) { + QSlider *slider = new QSlider(Qt::Horizontal, widget); + slider->setRange(m_minimum, m_maximum); + slider->setSingleStep(m_singleStep); + slider->setValue(value()); - connect(this, &Property::valueChanged, editor, syncEditor); - connect(editor, qOverload(&SpinBox::valueChanged), - this, &IntProperty::setValue); + layout->addWidget(slider); - return editor; -} + connect(this, &Property::valueChanged, slider, [this, slider] { + const QSignalBlocker blocker(slider); + slider->setValue(value()); + }); + connect(slider, &QSlider::valueChanged, this, &IntProperty::setValue); + } -QWidget *SliderProperty::createEditor(QWidget *parent) -{ - auto editor = new QSlider(Qt::Horizontal, parent); - editor->setRange(m_minimum, m_maximum); - editor->setSingleStep(m_singleStep); + auto spinBox = new SpinBox(parent); + spinBox->setRange(m_minimum, m_maximum); + spinBox->setSingleStep(m_singleStep); + spinBox->setSuffix(m_suffix); + spinBox->setValue(value()); - auto syncEditor = [=] { - const QSignalBlocker blocker(editor); - editor->setValue(value()); - }; - syncEditor(); + layout->addWidget(spinBox); - connect(this, &Property::valueChanged, editor, syncEditor); - connect(editor, &QSlider::valueChanged, this, &SliderProperty::setValue); + connect(this, &Property::valueChanged, spinBox, [this, spinBox] { + const QSignalBlocker blocker(spinBox); + spinBox->setValue(value()); + }); + connect(spinBox, qOverload(&SpinBox::valueChanged), + this, &IntProperty::setValue); - return editor; + return widget; } QWidget *FloatProperty::createEditor(QWidget *parent) diff --git a/src/tiled/varianteditor.h b/src/tiled/varianteditor.h index 9501bef042..26f5d8cbba 100644 --- a/src/tiled/varianteditor.h +++ b/src/tiled/varianteditor.h @@ -177,18 +177,14 @@ struct IntProperty : PropertyTemplate setMinimum(minimum); setMaximum(maximum); } + void setSliderEnabled(bool enabled) { m_sliderEnabled = enabled; } protected: int m_minimum = std::numeric_limits::min(); int m_maximum = std::numeric_limits::max(); int m_singleStep = 1; QString m_suffix; -}; - -struct SliderProperty : IntProperty -{ - using IntProperty::IntProperty; - QWidget *createEditor(QWidget *parent) override; + bool m_sliderEnabled = false; }; struct FloatProperty : PropertyTemplate From 5dc92116bcd8aef138df446b51ce363b16dcdc49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Mon, 16 Sep 2024 21:25:02 +0200 Subject: [PATCH 28/36] Made the property label indentation shrink when there is very little space --- src/tiled/varianteditor.cpp | 8 ++++++-- src/tiled/varianteditor.h | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index 4b87873ef1..4a088469a8 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -528,15 +528,19 @@ void VariantEditor::addProperty(Property *property) case Property::DisplayMode::Default: case Property::DisplayMode::NoLabel: { auto propertyLayout = new QHBoxLayout; - propertyLayout->setContentsMargins(spacing, 0, spacing, 0); + propertyLayout->setContentsMargins(0, 0, spacing, 0); propertyLayout->setSpacing(spacing); + // Property label indentation, which shrinks when there is very little space + propertyLayout->addSpacerItem(new QSpacerItem(branchIndicatorWidth + spacing, 0, + QSizePolicy::Maximum)); + propertyLayout->setStretch(0, 1); + if (property->displayMode() == Property::DisplayMode::Default) { auto label = new LineEditLabel(property->name(), this); label->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); label->setToolTip(property->toolTip()); label->setEnabled(property->isEnabled()); - label->setContentsMargins(branchIndicatorWidth, 0, 0, 0); connect(property, &Property::toolTipChanged, label, &QWidget::setToolTip); connect(property, &Property::enabledChanged, label, &QWidget::setEnabled); propertyLayout->addWidget(label, LabelStretch, Qt::AlignTop); diff --git a/src/tiled/varianteditor.h b/src/tiled/varianteditor.h index 26f5d8cbba..6abcace58b 100644 --- a/src/tiled/varianteditor.h +++ b/src/tiled/varianteditor.h @@ -397,8 +397,8 @@ class VariantEditor : public QWidget QWidget *createEditor(Property *property); enum Column { - LabelStretch = 2, - WidgetStretch = 3, + LabelStretch = 4, + WidgetStretch = 6, }; QVBoxLayout *m_layout; From 5137671b8fdba02b3fdfda6e4fc38cf8d0710aa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Tue, 17 Sep 2024 13:58:10 +0200 Subject: [PATCH 29/36] Implemented custom widget for allowed transformations --- src/tiled/propertieswidget.cpp | 95 +++++++++++++++++++++++++++-- src/tiled/propertyeditorwidgets.cpp | 2 +- src/tiled/varianteditor.cpp | 4 +- 3 files changed, 92 insertions(+), 9 deletions(-) diff --git a/src/tiled/propertieswidget.cpp b/src/tiled/propertieswidget.cpp index f586ab5e68..a4317f322c 100644 --- a/src/tiled/propertieswidget.cpp +++ b/src/tiled/propertieswidget.cpp @@ -58,6 +58,7 @@ #include #include #include +#include #include #include @@ -264,6 +265,90 @@ class ImageLayerRepeatProperty : public PropertyTemplate +{ + Q_OBJECT + +public: + using PropertyTemplate::PropertyTemplate; + + QWidget *createEditor(QWidget *parent) override + { + QIcon flipHorizontalIcon(QLatin1String(":images/24/flip-horizontal.png")); + QIcon flipVerticalIcon(QLatin1String(":images/24/flip-vertical.png")); + QIcon rotateRightIcon(QLatin1String(":images/24/rotate-right.png")); + + flipHorizontalIcon.addFile(QLatin1String(":images/32/flip-horizontal.png")); + flipVerticalIcon.addFile(QLatin1String(":images/32/flip-vertical.png")); + rotateRightIcon.addFile(QLatin1String(":images/32/rotate-right.png")); + + auto editor = new QWidget(parent); + + auto flipHorizontally = new QToolButton(editor); + flipHorizontally->setToolTip(tr("Flip Horizontally")); + flipHorizontally->setIcon(flipHorizontalIcon); + flipHorizontally->setCheckable(true); + + auto flipVertically = new QToolButton(editor); + flipVertically->setToolTip(tr("Flip Vertically")); + flipVertically->setIcon(flipVerticalIcon); + flipVertically->setCheckable(true); + + auto rotate = new QToolButton(editor); + rotate->setToolTip(tr("Rotate")); + rotate->setIcon(rotateRightIcon); + rotate->setCheckable(true); + + auto preferUntransformed = new QCheckBox(tr("Prefer Untransformed"), editor); + + auto horizontalLayout = new QHBoxLayout; + horizontalLayout->addWidget(flipHorizontally); + horizontalLayout->addWidget(flipVertically); + horizontalLayout->addWidget(rotate); + horizontalLayout->addStretch(); + + auto verticalLayout = new QVBoxLayout(editor); + verticalLayout->setContentsMargins(QMargins()); + verticalLayout->setSpacing(Utils::dpiScaled(4)); + verticalLayout->addLayout(horizontalLayout); + verticalLayout->addWidget(preferUntransformed); + + auto syncEditor = [=] { + const QSignalBlocker horizontalBlocker(flipHorizontally); + const QSignalBlocker verticalBlocker(flipVertically); + const QSignalBlocker rotateBlocker(rotate); + const QSignalBlocker preferUntransformedBlocker(preferUntransformed); + const auto v = value(); + flipHorizontally->setChecked(v & Tileset::AllowFlipHorizontally); + flipVertically->setChecked(v & Tileset::AllowFlipVertically); + rotate->setChecked(v & Tileset::AllowRotate); + preferUntransformed->setChecked(v & Tileset::PreferUntransformed); + }; + auto syncProperty = [=] { + Tileset::TransformationFlags v; + if (flipHorizontally->isChecked()) + v |= Tileset::AllowFlipHorizontally; + if (flipVertically->isChecked()) + v |= Tileset::AllowFlipVertically; + if (rotate->isChecked()) + v |= Tileset::AllowRotate; + if (preferUntransformed->isChecked()) + v |= Tileset::PreferUntransformed; + setValue(v); + }; + + syncEditor(); + + connect(this, &Property::valueChanged, editor, syncEditor); + connect(flipHorizontally, &QAbstractButton::toggled, this, syncProperty); + connect(flipVertically, &QAbstractButton::toggled, this, syncProperty); + connect(rotate, &QAbstractButton::toggled, this, syncProperty); + connect(preferUntransformed, &QAbstractButton::toggled, this, syncProperty); + return editor; + } +}; + + PropertiesWidget::PropertiesWidget(QWidget *parent) : QWidget{parent} , mScrollArea(new QScrollArea(this)) @@ -1149,15 +1234,13 @@ class TilesetProperties : public ObjectProperties }); mColumnCountProperty->setMinimum(1); - // todo: this needs a custom widget - mAllowedTransformationsProperty = new IntProperty( + mAllowedTransformationsProperty = new TransformationFlagsProperty( tr("Allowed Transformations"), [this] { - return static_cast(tileset()->transformationFlags()); + return tileset()->transformationFlags(); }, - [this](const int &value) { - const auto flags = static_cast(value); - push(new ChangeTilesetTransformationFlags(tilesetDocument(), flags)); + [this](const Tileset::TransformationFlags &value) { + push(new ChangeTilesetTransformationFlags(tilesetDocument(), value)); }); // todo: this needs a custom widget diff --git a/src/tiled/propertyeditorwidgets.cpp b/src/tiled/propertyeditorwidgets.cpp index b6f3136c1c..d57d0fcce6 100644 --- a/src/tiled/propertyeditorwidgets.cpp +++ b/src/tiled/propertyeditorwidgets.cpp @@ -121,7 +121,7 @@ ResponsivePairswiseWidget::ResponsivePairswiseWidget(QWidget *parent) auto layout = new QGridLayout(this); layout->setContentsMargins(QMargins()); layout->setColumnStretch(1, 1); - layout->setSpacing(Utils::dpiScaled(3)); + layout->setSpacing(Utils::dpiScaled(4)); } void ResponsivePairswiseWidget::setWidgetPairs(const QVector &widgetPairs) diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index 4a088469a8..c5f96776ac 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -355,7 +355,7 @@ QWidget *FontProperty::createEditor(QWidget *parent) auto layout = new QVBoxLayout(editor); layout->setContentsMargins(QMargins()); - layout->setSpacing(Utils::dpiScaled(3)); + layout->setSpacing(Utils::dpiScaled(4)); layout->addWidget(fontComboBox); layout->addWidget(sizeSpinBox); @@ -418,7 +418,7 @@ QWidget *QtAlignmentProperty::createEditor(QWidget *parent) auto editor = new QWidget(parent); auto layout = new QGridLayout(editor); layout->setContentsMargins(QMargins()); - layout->setSpacing(Utils::dpiScaled(3)); + layout->setSpacing(Utils::dpiScaled(4)); auto horizontalLabel = new ElidingLabel(tr("Horizontal"), editor); layout->addWidget(horizontalLabel, 0, 0); From b81836de50d2302c7769aa80f9e31ed5099ba097 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Tue, 17 Sep 2024 14:10:10 +0200 Subject: [PATCH 30/36] Fixed an issue with VariantEditor::clear Not everything was deleted from the layout because the deletion was lacking support for handling nested layouts. Also removed the only remaining case where a nested layout was used at the top-level. --- src/tiled/propertieswidget.cpp | 9 ++++++--- src/tiled/scriptdialog.cpp | 17 ++--------------- src/tiled/utils.cpp | 17 +++++++++++++++++ src/tiled/utils.h | 3 +++ src/tiled/varianteditor.cpp | 24 +++++------------------- src/tiled/varianteditor.h | 2 -- 6 files changed, 33 insertions(+), 39 deletions(-) diff --git a/src/tiled/propertieswidget.cpp b/src/tiled/propertieswidget.cpp index a4317f322c..58b0f842ca 100644 --- a/src/tiled/propertieswidget.cpp +++ b/src/tiled/propertieswidget.cpp @@ -1106,14 +1106,16 @@ class ObjectGroupProperties : public LayerProperties [this](ObjectGroup::DrawOrder value) { push(new ChangeObjectGroupDrawOrder(mapDocument(), { objectGroup() }, value)); }); + + mObjectGroupProperties = new GroupProperty(tr("Object Layer")); + mObjectGroupProperties->addProperty(mColorProperty); + mObjectGroupProperties->addProperty(mDrawOrderProperty); } void populateEditor(VariantEditor *editor) override { LayerProperties::populateEditor(editor); - editor->addHeader(tr("Object Layer")); - editor->addProperty(mColorProperty); - editor->addProperty(mDrawOrderProperty); + editor->addProperty(mObjectGroupProperties); } private: @@ -1139,6 +1141,7 @@ class ObjectGroupProperties : public LayerProperties return static_cast(mObject); } + GroupProperty *mObjectGroupProperties; Property *mColorProperty; Property *mDrawOrderProperty; }; diff --git a/src/tiled/scriptdialog.cpp b/src/tiled/scriptdialog.cpp index 0c8b64afb3..b92a993ac0 100644 --- a/src/tiled/scriptdialog.cpp +++ b/src/tiled/scriptdialog.cpp @@ -26,6 +26,7 @@ #include "mainwindow.h" #include "scriptimage.h" #include "scriptmanager.h" +#include "utils.h" #include #include @@ -41,8 +42,6 @@ #include #include -#include - static const int leftColumnStretch = 0; // stretch as much as we can so that the left column looks as close to zero width as possible when there is no content static const int rightColumnStretch = 1; @@ -51,18 +50,6 @@ namespace Tiled { QSet ScriptDialog::sDialogInstances; -static void deleteAllFromLayout(QLayout *layout) -{ - while (QLayoutItem *item = layout->takeAt(0)) { - delete item->widget(); - - if (QLayout *layout = item->layout()) - deleteAllFromLayout(layout); - - delete item; - } -} - ScriptImageWidget::ScriptImageWidget(Tiled::ScriptImage *image, QWidget *parent) : QLabel(parent) { @@ -144,7 +131,7 @@ void ScriptDialog::initializeLayout() void ScriptDialog::clear() { - deleteAllFromLayout(layout()); + Utils::deleteAllFromLayout(layout()); initializeLayout(); } diff --git a/src/tiled/utils.cpp b/src/tiled/utils.cpp index be6ae66ad6..ac7538ccc3 100644 --- a/src/tiled/utils.cpp +++ b/src/tiled/utils.cpp @@ -41,6 +41,8 @@ #include #include #include +#include +#include #include #include #include @@ -607,5 +609,20 @@ QString Error::jsonParseError(QJsonParseError error) } +/** + * Recursively deletes all items and their widgets from the given layout. + */ +void deleteAllFromLayout(QLayout *layout) +{ + while (QLayoutItem *item = layout->takeAt(0)) { + delete item->widget(); + + if (QLayout *layout = item->layout()) + deleteAllFromLayout(layout); + + delete item; + } +} + } // namespace Utils } // namespace Tiled diff --git a/src/tiled/utils.h b/src/tiled/utils.h index 3e73500b3a..e7c9721015 100644 --- a/src/tiled/utils.h +++ b/src/tiled/utils.h @@ -33,6 +33,7 @@ class QAction; class QKeyEvent; +class QLayout; class QMenu; namespace Tiled { @@ -111,5 +112,7 @@ namespace Error { QString jsonParseError(QJsonParseError error); } // namespace Error +void deleteAllFromLayout(QLayout *layout); + } // namespace Utils } // namespace Tiled diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index c5f96776ac..598220aed1 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -493,21 +493,7 @@ VariantEditor::VariantEditor(QWidget *parent) void VariantEditor::clear() { - QLayoutItem *item; - while ((item = m_layout->takeAt(0))) { - delete item->widget(); - delete item; - } - m_rowIndex = 0; -} - -HeaderWidget *VariantEditor::addHeader(const QString &text) -{ - auto headerWidget = new HeaderWidget(text, this); - - m_layout->addWidget(headerWidget); - - return headerWidget; + Utils::deleteAllFromLayout(m_layout); } void VariantEditor::addSeparator() @@ -521,7 +507,7 @@ void VariantEditor::addSeparator() void VariantEditor::addProperty(Property *property) { - const int spacing = Utils::dpiScaled(4); + const int spacing = m_layout->spacing(); const int branchIndicatorWidth = Utils::dpiScaled(14); switch (property->displayMode()) { @@ -529,10 +515,9 @@ void VariantEditor::addProperty(Property *property) case Property::DisplayMode::NoLabel: { auto propertyLayout = new QHBoxLayout; propertyLayout->setContentsMargins(0, 0, spacing, 0); - propertyLayout->setSpacing(spacing); // Property label indentation, which shrinks when there is very little space - propertyLayout->addSpacerItem(new QSpacerItem(branchIndicatorWidth + spacing, 0, + propertyLayout->addSpacerItem(new QSpacerItem(spacing + branchIndicatorWidth, 0, QSizePolicy::Maximum)); propertyLayout->setStretch(0, 1); @@ -561,7 +546,8 @@ void VariantEditor::addProperty(Property *property) break; } case Property::DisplayMode::Header: { - auto headerWidget = addHeader(property->name()); + auto headerWidget = new HeaderWidget(property->name(), this); + m_layout->addWidget(headerWidget); if (auto editor = createEditor(property)) { connect(headerWidget, &HeaderWidget::toggled, diff --git a/src/tiled/varianteditor.h b/src/tiled/varianteditor.h index 6abcace58b..3dcdf86dec 100644 --- a/src/tiled/varianteditor.h +++ b/src/tiled/varianteditor.h @@ -388,7 +388,6 @@ class VariantEditor : public QWidget VariantEditor(QWidget *parent = nullptr); void clear(); - HeaderWidget *addHeader(const QString &text); void addSeparator(); void addProperty(Property *property); // void addValue(const QVariant &value); @@ -402,7 +401,6 @@ class VariantEditor : public QWidget }; QVBoxLayout *m_layout; - int m_rowIndex = 0; }; } // namespace Tiled From db138cc37c4754c87521ed164480ffa083a99369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Tue, 17 Sep 2024 17:45:44 +0200 Subject: [PATCH 31/36] Added suffixes, support for custom enums and other progress * Added "px" suffixes to various pixel values. * Added support for custom enum properties (top-level only so far, because there is no support for class properties yet). It's amazing how trivial this was with the new approach (though, it does not support enums with values as flags yet). * Enabled editing of tileset image parameters through a button and made some progress towards allowing a GroupProperty to appear like a normal property (currently it can, but then it no longer shows its children). --- src/tiled/propertieswidget.cpp | 123 ++++++++++++++++++++++++---- src/tiled/propertyeditorwidgets.cpp | 12 +++ src/tiled/propertyeditorwidgets.h | 3 + src/tiled/tilesetparametersedit.cpp | 2 +- src/tiled/varianteditor.cpp | 19 ++--- src/tiled/varianteditor.h | 11 ++- 6 files changed, 141 insertions(+), 29 deletions(-) diff --git a/src/tiled/propertieswidget.cpp b/src/tiled/propertieswidget.cpp index 58b0f842ca..9a8f54a532 100644 --- a/src/tiled/propertieswidget.cpp +++ b/src/tiled/propertieswidget.cpp @@ -42,6 +42,7 @@ #include "propertybrowser.h" #include "tilesetchanges.h" #include "tilesetdocument.h" +#include "tilesetparametersedit.h" #include "utils.h" #include "varianteditor.h" #include "variantpropertymanager.h" @@ -349,6 +350,30 @@ class TransformationFlagsProperty : public PropertyTemplatesetTilesetDocument(mTilesetDocument); + return editor; + } + +private: + TilesetDocument *mTilesetDocument; +}; + + PropertiesWidget::PropertiesWidget(QWidget *parent) : QWidget{parent} , mScrollArea(new QScrollArea(this)) @@ -642,6 +667,7 @@ class MapProperties : public ObjectProperties }, this); mTileSizeProperty->setMinimum(1); + mTileSizeProperty->setSuffix(tr(" px")); mInfiniteProperty = new BoolProperty( tr("Infinite"), @@ -664,6 +690,7 @@ class MapProperties : public ObjectProperties Map::HexSideLengthProperty, value.toInt())); }); + mHexSideLengthProperty->setSuffix(tr(" px")); mStaggerAxisProperty = new EnumProperty( tr("Stagger Axis"), @@ -858,7 +885,7 @@ class MapProperties : public ObjectProperties Property *mSizeProperty; SizeProperty *mTileSizeProperty; Property *mInfiniteProperty; - Property *mHexSideLengthProperty; + IntProperty *mHexSideLengthProperty; Property *mStaggerAxisProperty; Property *mStaggerIndexProperty; Property *mParallaxOriginProperty; @@ -1180,6 +1207,7 @@ class TilesetProperties : public ObjectProperties [this](const QPoint &value) { push(new ChangeTilesetTileOffset(tilesetDocument(), value)); }); + mTileOffsetProperty->setSuffix(tr(" px")); mTileRenderSizeProperty = new EnumProperty( tr("Tile Render Size"), @@ -1226,6 +1254,7 @@ class TilesetProperties : public ObjectProperties push(new ChangeTilesetGridSize(tilesetDocument(), value)); }); mGridSizeProperty->setMinimum(1); + mGridSizeProperty->setSuffix(tr(" px")); mColumnCountProperty = new IntProperty( tr("Columns"), @@ -1246,15 +1275,38 @@ class TilesetProperties : public ObjectProperties push(new ChangeTilesetTransformationFlags(tilesetDocument(), value)); }); - // todo: this needs a custom widget + // todo: sub-properties are not displayed yet and image file name doesn't update in the TilesetParametersEdit + mTilesetImageProperty = new TilesetImageProperty(document, this); + mImageProperty = new UrlProperty( tr("Image"), - [this] { - return tileset()->imageSource(); - }, - [](const QUrl &) { - // push(new ChangeTilesetImage(tilesetDocument(), value.toString())); - }); + [this] { return tileset()->imageSource(); }); + + mTransparentColorProperty = new ColorProperty( + tr("Transparent Color"), + [this] { return tileset()->transparentColor(); }); + + mTileSizeProperty = new SizeProperty( + tr("Tile Size"), + [this] { return tileset()->tileSize(); }); + + mMarginProperty = new IntProperty( + tr("Margin"), + [this] { return tileset()->margin(); }); + + mTileSpacingProperty = new IntProperty( + tr("Spacing"), + [this] { return tileset()->tileSpacing(); }); + + mTileSizeProperty->setSuffix(tr(" px")); + mMarginProperty->setSuffix(tr(" px")); + mTileSpacingProperty->setSuffix(tr(" px")); + + mTilesetImageProperty->addProperty(mImageProperty); + mTilesetImageProperty->addProperty(mTransparentColorProperty); + mTilesetImageProperty->addProperty(mTileSizeProperty); + mTilesetImageProperty->addProperty(mMarginProperty); + mTilesetImageProperty->addProperty(mTileSpacingProperty); mTilesetProperties = new GroupProperty(tr("Tileset")); mTilesetProperties->addProperty(mNameProperty); @@ -1269,7 +1321,9 @@ class TilesetProperties : public ObjectProperties mTilesetProperties->addProperty(mGridSizeProperty); mTilesetProperties->addProperty(mColumnCountProperty); mTilesetProperties->addProperty(mAllowedTransformationsProperty); - mTilesetProperties->addProperty(mImageProperty); + + if (!tileset()->isCollection()) + mTilesetProperties->addProperty(mTilesetImageProperty); updateEnabledState(); connect(tilesetDocument(), &Document::changed, @@ -1316,12 +1370,21 @@ class TilesetProperties : public ObjectProperties emit mColumnCountProperty->valueChanged(); emit mAllowedTransformationsProperty->valueChanged(); emit mImageProperty->valueChanged(); + emit mTransparentColorProperty->valueChanged(); + emit mTileSizeProperty->valueChanged(); + emit mMarginProperty->valueChanged(); + emit mTileSpacingProperty->valueChanged(); } void updateEnabledState() { const bool collection = tileset()->isCollection(); + mTilesetImageProperty->setEnabled(!collection); mImageProperty->setEnabled(!collection); + mTransparentColorProperty->setEnabled(!collection); + mTileSizeProperty->setEnabled(!collection); + mMarginProperty->setEnabled(!collection); + mTileSpacingProperty->setEnabled(!collection); mColumnCountProperty->setEnabled(collection); } @@ -1338,7 +1401,7 @@ class TilesetProperties : public ObjectProperties GroupProperty *mTilesetProperties; Property *mNameProperty; Property *mObjectAlignmentProperty; - Property *mTileOffsetProperty; + PointProperty *mTileOffsetProperty; Property *mTileRenderSizeProperty; Property *mFillModeProperty; Property *mBackgroundColorProperty; @@ -1346,7 +1409,12 @@ class TilesetProperties : public ObjectProperties SizeProperty *mGridSizeProperty; IntProperty *mColumnCountProperty; Property *mAllowedTransformationsProperty; + GroupProperty *mTilesetImageProperty; Property *mImageProperty; + Property *mTransparentColorProperty; + SizeProperty *mTileSizeProperty; + IntProperty *mMarginProperty; + IntProperty *mTileSpacingProperty; }; class MapObjectProperties : public ObjectProperties @@ -2018,8 +2086,9 @@ void PropertiesWidget::currentObjectChanged(Object *object) const auto &value = it.value(); Property *property = nullptr; + auto userType = value.userType(); - switch (value.userType()) { + switch (userType) { case QMetaType::Bool: case QMetaType::QColor: case QMetaType::Double: @@ -2033,20 +2102,44 @@ void PropertiesWidget::currentObjectChanged(Object *object) break; } default: - if (value.userType() == filePathTypeId()) { + if (userType == filePathTypeId()) { auto get = [object, name] { return object->property(name).value().url; }; auto set = [this, object, name](const QUrl &value) { mDocument->undoStack()->push(new SetProperty(mDocument, { object }, name, QVariant::fromValue(FilePath { value }))); }; property = new UrlProperty(name, get, set); - } else if (value.userType() == objectRefTypeId()) { - auto get = [this, object, name] { return DisplayObjectRef(object->property(name).value(), static_cast(mDocument)); }; + } else if (userType == objectRefTypeId()) { + auto get = [this, object, name] { + return DisplayObjectRef(object->property(name).value(), + static_cast(mDocument)); + }; auto set = [this, object, name](const DisplayObjectRef &value) { mDocument->undoStack()->push(new SetProperty(mDocument, { object }, name, QVariant::fromValue(value.ref))); }; property = new ObjectRefProperty(name, get, set); + } else if (userType == propertyValueId()) { + auto propertyValue = value.value(); + if (auto propertyType = propertyValue.type()) { + switch (propertyType->type) { + case PropertyType::PT_Invalid: + break; + case PropertyType::PT_Class: + // todo: class values + break; + case PropertyType::PT_Enum: + auto enumType = static_cast(*propertyType); + // todo: support valuesAsFlags + property = new EnumProperty( + name, + [object, name] { return object->property(name).value().value.toInt(); }, + [=](int value) { + mDocument->undoStack()->push(new SetProperty(mDocument, { object }, name, propertyType->wrap(value))); + }); + static_cast*>(property)->setEnumData(enumType.values); + break; + } + } } - // todo: PropertyValue (enum and class values) break; } diff --git a/src/tiled/propertyeditorwidgets.cpp b/src/tiled/propertyeditorwidgets.cpp index d57d0fcce6..0d6af44cc2 100644 --- a/src/tiled/propertyeditorwidgets.cpp +++ b/src/tiled/propertyeditorwidgets.cpp @@ -241,6 +241,12 @@ void SizeEdit::setMinimum(int minimum) m_heightSpinBox->setMinimum(minimum); } +void SizeEdit::setSuffix(const QString &suffix) +{ + m_widthSpinBox->setSuffix(suffix); + m_heightSpinBox->setSuffix(suffix); +} + SizeFEdit::SizeFEdit(QWidget *parent) : ResponsivePairswiseWidget(parent) @@ -299,6 +305,12 @@ QPoint PointEdit::value() const m_ySpinBox->value()); } +void PointEdit::setSuffix(const QString &suffix) +{ + m_xSpinBox->setSuffix(suffix); + m_ySpinBox->setSuffix(suffix); +} + PointFEdit::PointFEdit(QWidget *parent) : ResponsivePairswiseWidget(parent) diff --git a/src/tiled/propertyeditorwidgets.h b/src/tiled/propertyeditorwidgets.h index 9f38b945c6..493a95a02c 100644 --- a/src/tiled/propertyeditorwidgets.h +++ b/src/tiled/propertyeditorwidgets.h @@ -105,6 +105,7 @@ class SizeEdit : public ResponsivePairswiseWidget QSize value() const; void setMinimum(int minimum); + void setSuffix(const QString &suffix); signals: void valueChanged(); @@ -154,6 +155,8 @@ class PointEdit : public ResponsivePairswiseWidget void setValue(const QPoint &size); QPoint value() const; + void setSuffix(const QString &suffix); + signals: void valueChanged(); diff --git a/src/tiled/tilesetparametersedit.cpp b/src/tiled/tilesetparametersedit.cpp index a5138da7d0..82700fb74b 100644 --- a/src/tiled/tilesetparametersedit.cpp +++ b/src/tiled/tilesetparametersedit.cpp @@ -44,7 +44,7 @@ TilesetParametersEdit::TilesetParametersEdit(QWidget *parent) QToolButton *button = new QToolButton(this); button->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred)); - button->setText(tr("Edit...")); + button->setText(tr("Edit Tileset")); layout->addWidget(mLabel); layout->addWidget(button); diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index 598220aed1..dff1069a4e 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -57,14 +57,6 @@ void Property::setEnabled(bool enabled) } } -QWidget *GroupProperty::createEditor(QWidget *parent) -{ - auto widget = new VariantEditor(parent); - for (auto property : std::as_const(m_subProperties)) - widget->addProperty(property); - return widget; -} - QWidget *StringProperty::createEditor(QWidget *parent) { auto editor = new QLineEdit(parent); @@ -188,6 +180,8 @@ QWidget *BoolProperty::createEditor(QWidget *parent) QWidget *PointProperty::createEditor(QWidget *parent) { auto editor = new PointEdit(parent); + editor->setSuffix(m_suffix); + auto syncEditor = [this, editor] { const QSignalBlocker blocker(editor); editor->setValue(value()); @@ -225,6 +219,7 @@ QWidget *SizeProperty::createEditor(QWidget *parent) { auto editor = new SizeEdit(parent); editor->setMinimum(m_minimum); + editor->setSuffix(m_suffix); auto syncEditor = [this, editor] { const QSignalBlocker blocker(editor); @@ -549,7 +544,11 @@ void VariantEditor::addProperty(Property *property) auto headerWidget = new HeaderWidget(property->name(), this); m_layout->addWidget(headerWidget); - if (auto editor = createEditor(property)) { + if (auto groupProperty = dynamic_cast(property)) { + auto editor = new VariantEditor(this); + for (auto property : groupProperty->subProperties()) + editor->addProperty(property); + connect(headerWidget, &HeaderWidget::toggled, editor, [this, editor](bool checked) { editor->setVisible(checked); @@ -680,7 +679,7 @@ Property *createTypedProperty(const QString &name, { return new PropertyClass(name, [get = std::move(get)] { return get().value(); }, - [set = std::move(set)] (typename PropertyClass::ValueType v) { set(QVariant::fromValue(v)); }); + [set = std::move(set)] (const typename PropertyClass::ValueType &v) { set(QVariant::fromValue(v)); }); } Property *PropertyFactory::createProperty(const QString &name, diff --git a/src/tiled/varianteditor.h b/src/tiled/varianteditor.h index 3dcdf86dec..4032c0800d 100644 --- a/src/tiled/varianteditor.h +++ b/src/tiled/varianteditor.h @@ -90,7 +90,6 @@ class Separator final : public Property {} DisplayMode displayMode() const override { return DisplayMode::Separator; } - QWidget *createEditor(QWidget */*parent*/) override { return nullptr; } }; @@ -104,8 +103,7 @@ class GroupProperty : public Property {} DisplayMode displayMode() const override { return DisplayMode::Header; } - - QWidget *createEditor(QWidget *parent) override; + QWidget *createEditor(QWidget */* parent */) override { return nullptr; } void addProperty(Property *property) { m_subProperties.append(property); } void addSeparator() { m_subProperties.append(new Separator(this)); } @@ -220,6 +218,11 @@ struct PointProperty : PropertyTemplate { using PropertyTemplate::PropertyTemplate; QWidget *createEditor(QWidget *parent) override; + + void setSuffix(const QString &suffix) { m_suffix = suffix; } + +private: + QString m_suffix; }; struct PointFProperty : PropertyTemplate @@ -239,9 +242,11 @@ struct SizeProperty : PropertyTemplate QWidget *createEditor(QWidget *parent) override; void setMinimum(int minimum) { m_minimum = minimum; } + void setSuffix(const QString &suffix) { m_suffix = suffix; } private: int m_minimum; + QString m_suffix; }; struct SizeFProperty : PropertyTemplate From f77fb26e49152563844672994e7f78bc11f11abf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Wed, 18 Sep 2024 11:37:46 +0200 Subject: [PATCH 32/36] Added support for custom enum types that are used as flags It creates a checkbox for each flag. They will need to be able to collapse in case there are lots of flags, but this works for now. --- src/tiled/propertieswidget.cpp | 14 +++++--- src/tiled/varianteditor.cpp | 63 ++++++++++++++++++++++++++------ src/tiled/varianteditor.h | 66 ++++++++++++++++++++-------------- 3 files changed, 100 insertions(+), 43 deletions(-) diff --git a/src/tiled/propertieswidget.cpp b/src/tiled/propertieswidget.cpp index 9a8f54a532..ef004baf99 100644 --- a/src/tiled/propertieswidget.cpp +++ b/src/tiled/propertieswidget.cpp @@ -2126,18 +2126,22 @@ void PropertiesWidget::currentObjectChanged(Object *object) case PropertyType::PT_Class: // todo: class values break; - case PropertyType::PT_Enum: - auto enumType = static_cast(*propertyType); - // todo: support valuesAsFlags - property = new EnumProperty( + case PropertyType::PT_Enum: { + auto enumProperty = new BaseEnumProperty( name, [object, name] { return object->property(name).value().value.toInt(); }, [=](int value) { mDocument->undoStack()->push(new SetProperty(mDocument, { object }, name, propertyType->wrap(value))); }); - static_cast*>(property)->setEnumData(enumType.values); + + auto enumType = static_cast(*propertyType); + enumProperty->setEnumData(enumType.values); + enumProperty->setFlags(enumType.valuesAsFlags); + + property = enumProperty; break; } + } } } break; diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index dff1069a4e..d4225fc4e7 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -609,34 +609,75 @@ QWidget *VariantEditor::createEditor(Property *property) } -QWidget *createEnumEditor(IntProperty *property, const EnumData &enumData, QWidget *parent) +QWidget *BaseEnumProperty::createEnumEditor(QWidget *parent) { auto editor = new QComboBox(parent); // This allows the combo box to shrink horizontally. editor->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); - for (qsizetype i = 0; i < enumData.names.size(); ++i) { - auto value = enumData.values.isEmpty() ? i : enumData.values.value(i); - editor->addItem(enumData.icons[value], - enumData.names[i], + for (qsizetype i = 0; i < m_enumData.names.size(); ++i) { + auto value = m_enumData.values.value(i, i); + editor->addItem(m_enumData.icons[value], + m_enumData.names[i], value); } - auto syncEditor = [property, editor] { + auto syncEditor = [this, editor] { const QSignalBlocker blocker(editor); - editor->setCurrentIndex(editor->findData(property->value())); + editor->setCurrentIndex(editor->findData(value())); }; syncEditor(); - QObject::connect(property, &Property::valueChanged, editor, syncEditor); - QObject::connect(editor, qOverload(&QComboBox::currentIndexChanged), property, - [editor, property] { - property->setValue(editor->currentData().toInt()); + QObject::connect(this, &Property::valueChanged, editor, syncEditor); + QObject::connect(editor, qOverload(&QComboBox::currentIndexChanged), this, + [editor, this] { + setValue(editor->currentData().toInt()); }); return editor; } +QWidget *BaseEnumProperty::createFlagsEditor(QWidget *parent) +{ + auto editor = new QWidget(parent); + auto layout = new QVBoxLayout(editor); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + + for (qsizetype i = 0; i < m_enumData.names.size(); ++i) { + auto checkBox = new QCheckBox(m_enumData.names[i], editor); + layout->addWidget(checkBox); + + QObject::connect(checkBox, &QCheckBox::toggled, this, [=](bool checked) { + const auto enumItemValue = m_enumData.values.value(i, 1 << i); + int flags = value(); + if (checked) + flags |= enumItemValue; + else + flags &= ~enumItemValue; + setValue(flags); + }); + } + + auto syncEditor = [=] { + for (int i = 0; i < layout->count(); ++i) { + auto checkBox = qobject_cast(layout->itemAt(i)->widget()); + if (checkBox) { + const auto enumItemValue = m_enumData.values.value(i, 1 << i); + + QSignalBlocker blocker(checkBox); + checkBox->setChecked((value() & enumItemValue) == enumItemValue); + } + } + }; + + syncEditor(); + + QObject::connect(this, &Property::valueChanged, editor, syncEditor); + + return editor; +} + Property *PropertyFactory::createQObjectProperty(QObject *qObject, const char *propertyName, const QString &displayName) diff --git a/src/tiled/varianteditor.h b/src/tiled/varianteditor.h index 4032c0800d..90ebab10c8 100644 --- a/src/tiled/varianteditor.h +++ b/src/tiled/varianteditor.h @@ -325,7 +325,7 @@ class PropertyFactory struct EnumData { - EnumData(const QStringList &names, + EnumData(const QStringList &names = {}, const QList &values = {}, const QMap &icons = {}) : names(names) @@ -344,44 +344,56 @@ EnumData enumData() return {{}}; } -QWidget *createEnumEditor(IntProperty *property, - const EnumData &enumData, - QWidget *parent); +/** + * A property that wraps an integer value and creates either a combo box or a + * list of checkboxes based on the given EnumData. + */ +class BaseEnumProperty : public IntProperty +{ + Q_OBJECT + +public: + using IntProperty::IntProperty; + + void setEnumData(const EnumData &enumData) { m_enumData = enumData; } + void setFlags(bool flags) { m_flags = flags; } + + QWidget *createEditor(QWidget *parent) override + { + return m_flags ? createFlagsEditor(parent) + : createEnumEditor(parent); + } + +protected: + QWidget *createFlagsEditor(QWidget *parent); + QWidget *createEnumEditor(QWidget *parent); + + EnumData m_enumData; + bool m_flags = false; +}; /** - * A property that wraps an enum value and creates a combo box based on the - * given EnumData. + * A property that wraps an enum value and automatically sets the EnumData + * based on the given type. */ template -class EnumProperty : public IntProperty +class EnumProperty : public BaseEnumProperty { public: EnumProperty(const QString &name, std::function get, std::function set, QObject *parent = nullptr) - : IntProperty(name, - [get] { - return static_cast(get()); - }, - set ? [set](const int &value){ set(static_cast(value)); } - : std::function(), - parent) - , m_enumData(enumData()) - {} - - void setEnumData(const EnumData &enumData) - { - m_enumData = enumData; - } - - QWidget *createEditor(QWidget *parent) override + : BaseEnumProperty(name, + [get] { + return static_cast(get()); + }, + set ? [set](const int &value){ set(static_cast(value)); } + : std::function(), + parent) { - return createEnumEditor(this, m_enumData, parent); + setEnumData(enumData()); } - -private: - EnumData m_enumData; }; From 68d84af411fb438b46454a9729290225545259b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Wed, 18 Sep 2024 17:38:54 +0200 Subject: [PATCH 33/36] Allow expanding the child properties for any GroupProperty Also when it isn't displayed as a header. Also introduced a "level" member of VariantEditor which is passed on to the PropertyLabel, which is used to indent expanded child properties. --- src/tiled/propertieswidget.cpp | 14 ++--- src/tiled/propertyeditorwidgets.cpp | 65 +++++++++++++++++------ src/tiled/propertyeditorwidgets.h | 41 ++++++++------- src/tiled/varianteditor.cpp | 82 +++++++++++++---------------- src/tiled/varianteditor.h | 7 ++- 5 files changed, 120 insertions(+), 89 deletions(-) diff --git a/src/tiled/propertieswidget.cpp b/src/tiled/propertieswidget.cpp index ef004baf99..cbf5661643 100644 --- a/src/tiled/propertieswidget.cpp +++ b/src/tiled/propertieswidget.cpp @@ -1275,7 +1275,7 @@ class TilesetProperties : public ObjectProperties push(new ChangeTilesetTransformationFlags(tilesetDocument(), value)); }); - // todo: sub-properties are not displayed yet and image file name doesn't update in the TilesetParametersEdit + // todo: image file name doesn't update in the TilesetParametersEdit mTilesetImageProperty = new TilesetImageProperty(document, this); mImageProperty = new UrlProperty( @@ -1302,6 +1302,12 @@ class TilesetProperties : public ObjectProperties mMarginProperty->setSuffix(tr(" px")); mTileSpacingProperty->setSuffix(tr(" px")); + mImageProperty->setEnabled(false); + mTransparentColorProperty->setEnabled(false); + mTileSizeProperty->setEnabled(false); + mMarginProperty->setEnabled(false); + mTileSpacingProperty->setEnabled(false); + mTilesetImageProperty->addProperty(mImageProperty); mTilesetImageProperty->addProperty(mTransparentColorProperty); mTilesetImageProperty->addProperty(mTileSizeProperty); @@ -1379,12 +1385,6 @@ class TilesetProperties : public ObjectProperties void updateEnabledState() { const bool collection = tileset()->isCollection(); - mTilesetImageProperty->setEnabled(!collection); - mImageProperty->setEnabled(!collection); - mTransparentColorProperty->setEnabled(!collection); - mTileSizeProperty->setEnabled(!collection); - mMarginProperty->setEnabled(!collection); - mTileSpacingProperty->setEnabled(!collection); mColumnCountProperty->setEnabled(collection); } diff --git a/src/tiled/propertyeditorwidgets.cpp b/src/tiled/propertyeditorwidgets.cpp index 0d6af44cc2..24d2437486 100644 --- a/src/tiled/propertyeditorwidgets.cpp +++ b/src/tiled/propertyeditorwidgets.cpp @@ -491,38 +491,73 @@ void ElidingLabel::paintEvent(QPaintEvent *) } -HeaderWidget::HeaderWidget(const QString &text, QWidget *parent) - : ElidingLabel(text, parent) +PropertyLabel::PropertyLabel(int level, QWidget *parent) + : ElidingLabel(parent) { - setBackgroundRole(QPalette::Dark); - setForegroundRole(QPalette::BrightText); - setAutoFillBackground(true); + setMinimumWidth(Utils::dpiScaled(50)); + setLevel(level); +} + +void PropertyLabel::setLevel(int level) +{ + m_level = level; - const int spacing = Utils::dpiScaled(4); + const int spacing = Utils::dpiScaled(3); const int branchIndicatorWidth = Utils::dpiScaled(14); - setContentsMargins(spacing + branchIndicatorWidth, + setContentsMargins(spacing + branchIndicatorWidth * std::max(m_level, 1), spacing, spacing, spacing); } -void HeaderWidget::mousePressEvent(QMouseEvent *event) +void PropertyLabel::setHeader(bool header) +{ + if (m_header == header) + return; + + m_header = header; + setBackgroundRole(header ? QPalette::Dark : QPalette::NoRole); + setForegroundRole(header ? QPalette::BrightText : QPalette::NoRole); + setAutoFillBackground(header); +} + +void PropertyLabel::setExpandable(bool expandable) +{ + if (m_expandable == expandable) + return; + + m_expandable = expandable; + update(); +} + +void PropertyLabel::setExpanded(bool expanded) +{ + if (m_expanded == expanded) + return; + + m_expanded = expanded; + update(); + emit toggled(m_expanded); +} + +void PropertyLabel::mousePressEvent(QMouseEvent *event) { - if (event->button() == Qt::LeftButton) { - m_checked = !m_checked; - emit toggled(m_checked); + if (m_expandable && event->button() == Qt::LeftButton) { + setExpanded(!m_expanded); + return; } ElidingLabel::mousePressEvent(event); } -void HeaderWidget::paintEvent(QPaintEvent *event) +void PropertyLabel::paintEvent(QPaintEvent *event) { ElidingLabel::paintEvent(event); QStyleOption branchOption; branchOption.initFrom(this); branchOption.rect = QRect(0, 0, contentsMargins().left(), height()); - branchOption.state = QStyle::State_Children; - if (m_checked) + if (m_expandable) + branchOption.state |= QStyle::State_Children; + if (m_expanded) branchOption.state |= QStyle::State_Open; QStylePainter p(this); @@ -530,7 +565,7 @@ void HeaderWidget::paintEvent(QPaintEvent *event) } -QSize LineEditLabel::sizeHint() const +QSize PropertyLabel::sizeHint() const { auto hint = ElidingLabel::sizeHint(); hint.setHeight(m_lineEdit.sizeHint().height()); diff --git a/src/tiled/propertyeditorwidgets.h b/src/tiled/propertyeditorwidgets.h index 493a95a02c..e5355c0f9e 100644 --- a/src/tiled/propertyeditorwidgets.h +++ b/src/tiled/propertyeditorwidgets.h @@ -274,40 +274,41 @@ class ElidingLabel : public QLabel }; /** - * A header widget that can be toggled. + * A property label widget, which can be a header or just be expandable. */ -class HeaderWidget : public ElidingLabel +class PropertyLabel : public ElidingLabel { Q_OBJECT public: - HeaderWidget(const QString &text, QWidget *parent = nullptr); + PropertyLabel(int level, QWidget *parent = nullptr); -signals: - void toggled(bool checked); + void setLevel(int level); -protected: - void mousePressEvent(QMouseEvent *event) override; - void paintEvent(QPaintEvent *) override; + void setHeader(bool header); + bool isHeader() const { return m_header; } -private: - bool m_checked = true; -}; - -/** - * A label that matches its preferred height with that of a line edit. - */ -class LineEditLabel : public ElidingLabel -{ - Q_OBJECT + void setExpandable(bool expandable); + bool isExpandable() const { return m_expandable; } -public: - using ElidingLabel::ElidingLabel; + void setExpanded(bool expanded); + bool isExpanded() const { return m_expanded; } QSize sizeHint() const override; +signals: + void toggled(bool expanded); + +protected: + void mousePressEvent(QMouseEvent *event) override; + void paintEvent(QPaintEvent *) override; + private: QLineEdit m_lineEdit; + int m_level = 0; + bool m_header = false; + bool m_expandable = false; + bool m_expanded = false; }; } // namespace Tiled diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index d4225fc4e7..aed7f6f81c 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -502,31 +502,30 @@ void VariantEditor::addSeparator() void VariantEditor::addProperty(Property *property) { - const int spacing = m_layout->spacing(); - const int branchIndicatorWidth = Utils::dpiScaled(14); + const auto displayMode = property->displayMode(); - switch (property->displayMode()) { - case Property::DisplayMode::Default: - case Property::DisplayMode::NoLabel: { + if (displayMode == Property::DisplayMode::Separator) { + addSeparator(); + return; + } + + auto label = new PropertyLabel(m_level, this); + + if (displayMode != Property::DisplayMode::NoLabel) { + label->setText(property->name()); + label->setToolTip(property->toolTip()); + label->setEnabled(property->isEnabled()); + connect(property, &Property::toolTipChanged, label, &QWidget::setToolTip); + connect(property, &Property::enabledChanged, label, &QWidget::setEnabled); + } + + if (displayMode == Property::DisplayMode::Header) { + label->setHeader(true); + m_layout->addWidget(label); + } else { auto propertyLayout = new QHBoxLayout; - propertyLayout->setContentsMargins(0, 0, spacing, 0); - - // Property label indentation, which shrinks when there is very little space - propertyLayout->addSpacerItem(new QSpacerItem(spacing + branchIndicatorWidth, 0, - QSizePolicy::Maximum)); - propertyLayout->setStretch(0, 1); - - if (property->displayMode() == Property::DisplayMode::Default) { - auto label = new LineEditLabel(property->name(), this); - label->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); - label->setToolTip(property->toolTip()); - label->setEnabled(property->isEnabled()); - connect(property, &Property::toolTipChanged, label, &QWidget::setToolTip); - connect(property, &Property::enabledChanged, label, &QWidget::setEnabled); - propertyLayout->addWidget(label, LabelStretch, Qt::AlignTop); - } else { - propertyLayout->addStretch(LabelStretch); - } + propertyLayout->setContentsMargins(0, 0, m_layout->spacing(), 0); + propertyLayout->addWidget(label, LabelStretch, Qt::AlignTop); if (auto editor = createEditor(property)) { editor->setToolTip(property->toolTip()); @@ -537,34 +536,26 @@ void VariantEditor::addProperty(Property *property) } m_layout->addLayout(propertyLayout); - - break; } - case Property::DisplayMode::Header: { - auto headerWidget = new HeaderWidget(property->name(), this); - m_layout->addWidget(headerWidget); - if (auto groupProperty = dynamic_cast(property)) { - auto editor = new VariantEditor(this); - for (auto property : groupProperty->subProperties()) - editor->addProperty(property); + if (auto groupProperty = dynamic_cast(property)) { + label->setExpandable(true); + label->setExpanded(label->isHeader()); - connect(headerWidget, &HeaderWidget::toggled, - editor, [this, editor](bool checked) { - editor->setVisible(checked); + auto editor = new VariantEditor(this); + editor->setLevel(m_level + 1); + editor->setVisible(label->isExpanded()); + for (auto property : groupProperty->subProperties()) + editor->addProperty(property); - // needed to avoid flickering when hiding the editor - layout()->activate(); - }); + connect(label, &PropertyLabel::toggled, editor, [=](bool expanded) { + editor->setVisible(expanded); - m_layout->addWidget(editor); - } + // needed to avoid flickering when hiding the editor + layout()->activate(); + }); - break; - } - case Property::DisplayMode::Separator: - addSeparator(); - break; + m_layout->addWidget(editor); } } @@ -600,6 +591,7 @@ void VariantEditor::addValue(const QVariant &value) QWidget *VariantEditor::createEditor(Property *property) { if (const auto editor = property->createEditor(this)) { + editor->setMinimumWidth(Utils::dpiScaled(70)); editor->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); return editor; } else { diff --git a/src/tiled/varianteditor.h b/src/tiled/varianteditor.h index 90ebab10c8..43b0235b3f 100644 --- a/src/tiled/varianteditor.h +++ b/src/tiled/varianteditor.h @@ -32,7 +32,7 @@ class QVBoxLayout; namespace Tiled { -class HeaderWidget; +class PropertyLabel; /** * A property represents a named value that can create its own edit widget. @@ -133,7 +133,7 @@ class PropertyTemplate : public Property {} Type value() const { return m_get(); } - void setValue(const Type &value) { m_set(value); } + void setValue(const Type &value) { if (m_set) m_set(value); } private: std::function m_get; @@ -409,6 +409,8 @@ class VariantEditor : public QWidget void addProperty(Property *property); // void addValue(const QVariant &value); + void setLevel(int level) { m_level = level; } + private: QWidget *createEditor(Property *property); @@ -418,6 +420,7 @@ class VariantEditor : public QWidget }; QVBoxLayout *m_layout; + int m_level = 0; }; } // namespace Tiled From 2d18a43b668dbb481596bf250a635cf6d547a613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Tue, 24 Sep 2024 17:08:19 +0200 Subject: [PATCH 34/36] Added support for custom class properties Including changing of nested values. However, several things remain to be done, including: * When the custom properties are refreshed, all properties (and their widgets) are re-created. There should be a way to update their value instead. * There is no way to see yet whether a property is set, nor can its value be reset. --- src/tiled/document.cpp | 1 + src/tiled/document.h | 1 + src/tiled/mapdocument.cpp | 12 +- src/tiled/propertieswidget.cpp | 273 ++++++++++++++++++++-------- src/tiled/propertieswidget.h | 2 + src/tiled/propertyeditorwidgets.cpp | 6 +- src/tiled/tilesetdocument.cpp | 3 + src/tiled/varianteditor.cpp | 150 +++++++++------ src/tiled/varianteditor.h | 63 +++++-- 9 files changed, 363 insertions(+), 148 deletions(-) diff --git a/src/tiled/document.cpp b/src/tiled/document.cpp index 7c546cd1e1..51c3fe7d4c 100644 --- a/src/tiled/document.cpp +++ b/src/tiled/document.cpp @@ -145,6 +145,7 @@ void Document::setCurrentObject(Object *object, Document *owningDocument) emit currentObjectSet(object); emit currentObjectChanged(object); + emit currentObjectsChanged(); } /** diff --git a/src/tiled/document.h b/src/tiled/document.h index a99b8049ac..62c2088bfb 100644 --- a/src/tiled/document.h +++ b/src/tiled/document.h @@ -139,6 +139,7 @@ class Document : public QObject, void currentObjectSet(Object *object); void currentObjectChanged(Object *object); + void currentObjectsChanged(); /** * Makes the Properties window visible and take focus. diff --git a/src/tiled/mapdocument.cpp b/src/tiled/mapdocument.cpp index 06ba3b6d1b..64da85f709 100644 --- a/src/tiled/mapdocument.cpp +++ b/src/tiled/mapdocument.cpp @@ -326,6 +326,9 @@ void MapDocument::setSelectedLayers(const QList &layers) mSelectedLayers = layers; emit selectedLayersChanged(); + + if (currentObject() && currentObject()->typeId() == Object::LayerType) + emit currentObjectsChanged(); } void MapDocument::switchCurrentLayer(Layer *layer) @@ -1277,8 +1280,10 @@ void MapDocument::setSelectedObjects(const QList &selectedObjects) // Make sure the current object is one of the selected ones if (!selectedObjects.isEmpty()) { if (currentObject() && currentObject()->typeId() == Object::MapObjectType) { - if (selectedObjects.contains(static_cast(currentObject()))) + if (selectedObjects.contains(static_cast(currentObject()))) { + emit currentObjectsChanged(); return; + } } setCurrentObject(selectedObjects.first()); @@ -1730,8 +1735,11 @@ void MapDocument::deselectObjects(const QList &objects) removedAboutToBeSelectedObjects += mAboutToBeSelectedObjects.removeAll(object); } - if (removedSelectedObjects > 0) + if (removedSelectedObjects > 0) { emit selectedObjectsChanged(); + if (mCurrentObject && mCurrentObject->typeId() == Object::MapObjectType) + emit currentObjectsChanged(); + } if (removedAboutToBeSelectedObjects > 0) emit aboutToBeSelectedObjectsChanged(mAboutToBeSelectedObjects); } diff --git a/src/tiled/propertieswidget.cpp b/src/tiled/propertieswidget.cpp index cbf5661643..e65ab82234 100644 --- a/src/tiled/propertieswidget.cpp +++ b/src/tiled/propertieswidget.cpp @@ -58,6 +58,7 @@ #include #include #include +#include #include #include #include @@ -374,8 +375,48 @@ class TilesetImageProperty : public GroupProperty }; +class CustomProperties : public GroupProperty +{ + Q_OBJECT + +public: + CustomProperties(QObject *parent = nullptr) + : GroupProperty(tr("Custom Properties"), parent) + {} + + void setDocument(Document *document); + +private: + // todo: optimize + void propertyAdded(Object *, const QString &) { refresh(); } + void propertyRemoved(Object *, const QString &) { refresh(); } + void propertyChanged(Object *, const QString &) { + if (!mUpdating) + refresh(); + } + void propertiesChanged(Object *) { refresh(); } + + void refresh(); + + Property *createProperty(const QStringList &path, + std::function get, + std::function set); + + void createClassMembers(const QStringList &path, GroupProperty *groupProperty, + const ClassPropertyType &classType, + std::function get); + + void setPropertyValue(const QStringList &path, const QVariant &value); + + bool mUpdating = false; + Document *mDocument = nullptr; + Properties mProperties; +}; + + PropertiesWidget::PropertiesWidget(QWidget *parent) : QWidget{parent} + , mCustomProperties(new CustomProperties) , mScrollArea(new QScrollArea(this)) { auto scrollWidget = new QWidget(mScrollArea); @@ -453,6 +494,7 @@ void PropertiesWidget::setDocument(Document *document) mDocument = document; // mPropertyBrowser->setDocument(document); + mCustomProperties->setDocument(document); if (document) { connect(document, &Document::currentObjectChanged, @@ -496,12 +538,12 @@ static QStringList classNamesFor(const Object &object) } // todo: add support for changing multiple objects -class ClassProperty : public StringProperty +class ClassNameProperty : public StringProperty { Q_OBJECT public: - ClassProperty(Document *document, Object *object, QObject *parent = nullptr) + ClassNameProperty(Document *document, Object *object, QObject *parent = nullptr) : StringProperty(tr("Class"), [this] { return mObject->className(); }, [this] (const QString &value) { @@ -515,7 +557,7 @@ class ClassProperty : public StringProperty , mObject(object) { connect(mDocument, &Document::changed, - this, &ClassProperty::onChanged); + this, &ClassNameProperty::onChanged); } QWidget *createEditor(QWidget *parent) override @@ -606,7 +648,7 @@ class ObjectProperties : public QObject , mDocument(document) , mObject(object) { - mClassProperty = new ClassProperty(document, object, this); + mClassProperty = new ClassNameProperty(document, object, this); } virtual void populateEditor(VariantEditor *) @@ -2071,94 +2113,173 @@ void PropertiesWidget::currentObjectChanged(Object *object) } } - if (mPropertiesObject) + if (mPropertiesObject) { mPropertiesObject->populateEditor(mPropertyBrowser); + mPropertyBrowser->addProperty(mCustomProperties); + } + + bool editingTileset = mDocument && mDocument->type() == Document::TilesetDocumentType; + bool isTileset = object && object->isPartOfTileset(); + bool enabled = object && (!isTileset || editingTileset); - GroupProperty *customProperties = new GroupProperty(tr("Custom Properties")); + mPropertyBrowser->setEnabled(object); + mActionAddProperty->setEnabled(enabled); +} + + +void CustomProperties::setDocument(Document *document) +{ + if (mDocument == document) + return; + + if (mDocument) + mDocument->disconnect(this); - QMapIterator it(object ? object->properties() : Properties()); - PropertyFactory factory; + mDocument = document; + + if (document) { + connect(document, &Document::currentObjectsChanged, this, &CustomProperties::refresh); + + connect(document, &Document::propertyAdded, this, &CustomProperties::propertyAdded); + connect(document, &Document::propertyRemoved, this, &CustomProperties::propertyRemoved); + connect(document, &Document::propertyChanged, this, &CustomProperties::propertyChanged); + connect(document, &Document::propertiesChanged, this, &CustomProperties::propertiesChanged); + } + + refresh(); +} + +void CustomProperties::refresh() +{ + clear(); + + if (!mDocument || !mDocument->currentObject()) + return; + mProperties = mDocument->currentObject()->properties(); + + QMapIterator it(mProperties); while (it.hasNext()) { it.next(); + const QString &name = it.key(); + + auto get = [=] { return mProperties.value(name); }; + auto set = [=] (const QVariant &value) { + setPropertyValue({ name }, value); + }; + + if (auto property = createProperty({ name }, std::move(get), std::move(set))) + addProperty(property); + } +} - const auto &name = it.key(); - const auto &value = it.value(); - - Property *property = nullptr; - auto userType = value.userType(); - - switch (userType) { - case QMetaType::Bool: - case QMetaType::QColor: - case QMetaType::Double: - case QMetaType::Int: - case QMetaType::QString: { - auto get = [object, name] { return object->property(name); }; - auto set = [this, object, name] (const QVariant &value) { - mDocument->undoStack()->push(new SetProperty(mDocument, { object }, name, value)); +Property *CustomProperties::createProperty(const QStringList &path, + std::function get, + std::function set) +{ + const auto value = get(); + const auto type = value.userType(); + const auto &name = path.last(); + + switch (type) { + case QMetaType::Bool: + case QMetaType::QColor: + case QMetaType::Double: + case QMetaType::Int: + case QMetaType::QString: + return PropertyFactory::createProperty(name, std::move(get), std::move(set)); + + default: + if (type == filePathTypeId()) { + auto getUrl = [get = std::move(get)] { return get().value().url; }; + auto setUrl = [set = std::move(set)] (const QUrl &value) { + set(QVariant::fromValue(FilePath { value })); }; - property = factory.createProperty(name, std::move(get), std::move(set)); - break; + return new UrlProperty(name, std::move(getUrl), std::move(setUrl)); } - default: - if (userType == filePathTypeId()) { - auto get = [object, name] { return object->property(name).value().url; }; - auto set = [this, object, name](const QUrl &value) { - mDocument->undoStack()->push(new SetProperty(mDocument, { object }, name, QVariant::fromValue(FilePath { value }))); - }; - property = new UrlProperty(name, get, set); - } else if (userType == objectRefTypeId()) { - auto get = [this, object, name] { - return DisplayObjectRef(object->property(name).value(), - static_cast(mDocument)); - }; - auto set = [this, object, name](const DisplayObjectRef &value) { - mDocument->undoStack()->push(new SetProperty(mDocument, { object }, name, QVariant::fromValue(value.ref))); - }; - property = new ObjectRefProperty(name, get, set); - } else if (userType == propertyValueId()) { - auto propertyValue = value.value(); - if (auto propertyType = propertyValue.type()) { - switch (propertyType->type) { - case PropertyType::PT_Invalid: - break; - case PropertyType::PT_Class: - // todo: class values - break; - case PropertyType::PT_Enum: { - auto enumProperty = new BaseEnumProperty( - name, - [object, name] { return object->property(name).value().value.toInt(); }, - [=](int value) { - mDocument->undoStack()->push(new SetProperty(mDocument, { object }, name, propertyType->wrap(value))); - }); - - auto enumType = static_cast(*propertyType); - enumProperty->setEnumData(enumType.values); - enumProperty->setFlags(enumType.valuesAsFlags); - - property = enumProperty; - break; - } - } + + if (type == objectRefTypeId()) { + auto getObjectRef = [get = std::move(get), this] { + return DisplayObjectRef(get().value(), + static_cast(mDocument)); + }; + auto setObjectRef = [set = std::move(set)](const DisplayObjectRef &value) { + set(QVariant::fromValue(value.ref)); + }; + return new ObjectRefProperty(name, std::move(getObjectRef), std::move(setObjectRef)); + } + + if (type == propertyValueId()) { + if (auto propertyType = value.value().type()) { + switch (propertyType->type) { + case PropertyType::PT_Invalid: + break; + case PropertyType::PT_Class: { + auto classType = static_cast(*propertyType); + + auto groupProperty = new GroupProperty(name); + groupProperty->setHeader(false); + + createClassMembers(path, groupProperty, classType, std::move(get)); + + return groupProperty; + } + case PropertyType::PT_Enum: { + auto enumProperty = new BaseEnumProperty( + name, + [get = std::move(get)] { return get().value().value.toInt(); }, + [set = std::move(set), propertyType](int value) { + set(propertyType->wrap(value)); + }); + + auto enumType = static_cast(*propertyType); + enumProperty->setEnumData(enumType.values); + enumProperty->setFlags(enumType.valuesAsFlags); + + return enumProperty; + } } } - break; } - - if (property) - customProperties->addProperty(property); } - mPropertyBrowser->addProperty(customProperties); + return nullptr; +} - bool editingTileset = mDocument && mDocument->type() == Document::TilesetDocumentType; - bool isTileset = object && object->isPartOfTileset(); - bool enabled = object && (!isTileset || editingTileset); +void CustomProperties::createClassMembers(const QStringList &path, + GroupProperty *groupProperty, + const ClassPropertyType &classType, + std::function get) +{ + // Create a sub-property for each member + QMapIterator it(classType.members); + while (it.hasNext()) { + it.next(); + const QString &name = it.key(); - mPropertyBrowser->setEnabled(object); - mActionAddProperty->setEnabled(enabled); + auto childPath = path; + childPath.append(name); + + auto getMember = [=] { + auto def = classType.members.value(name); + return get().value().value.toMap().value(name, def); + }; + auto setMember = [=] (const QVariant &value) { + setPropertyValue(childPath, value); + }; + + if (auto childProperty = createProperty(childPath, std::move(getMember), std::move(setMember))) + groupProperty->addProperty(childProperty); + } +} + +void CustomProperties::setPropertyValue(const QStringList &path, const QVariant &value) +{ + const auto objects = mDocument->currentObjects(); + if (!objects.isEmpty()) { + QScopedValueRollback updating(mUpdating, true); + mDocument->undoStack()->push(new SetProperty(mDocument, objects, path, value)); + } } void PropertiesWidget::updateActions() diff --git a/src/tiled/propertieswidget.h b/src/tiled/propertieswidget.h index bda0cf672e..a38f10ddd4 100644 --- a/src/tiled/propertieswidget.h +++ b/src/tiled/propertieswidget.h @@ -28,6 +28,7 @@ namespace Tiled { class Object; +class CustomProperties; class Document; class ObjectProperties; class VariantEditor; @@ -78,6 +79,7 @@ public slots: Document *mDocument = nullptr; ObjectProperties *mPropertiesObject = nullptr; + CustomProperties *mCustomProperties = nullptr; QScrollArea *mScrollArea; VariantEditor *mPropertyBrowser; QAction *mActionAddProperty; diff --git a/src/tiled/propertyeditorwidgets.cpp b/src/tiled/propertyeditorwidgets.cpp index 24d2437486..aac5955e95 100644 --- a/src/tiled/propertyeditorwidgets.cpp +++ b/src/tiled/propertyeditorwidgets.cpp @@ -552,9 +552,13 @@ void PropertyLabel::paintEvent(QPaintEvent *event) { ElidingLabel::paintEvent(event); + const int spacing = Utils::dpiScaled(3); + const int branchIndicatorWidth = Utils::dpiScaled(14); + QStyleOption branchOption; branchOption.initFrom(this); - branchOption.rect = QRect(0, 0, contentsMargins().left(), height()); + branchOption.rect = QRect(branchIndicatorWidth * std::max(m_level - 1, 0), 0, + branchIndicatorWidth + spacing, height()); if (m_expandable) branchOption.state |= QStyle::State_Children; if (m_expanded) diff --git a/src/tiled/tilesetdocument.cpp b/src/tiled/tilesetdocument.cpp index f378489189..5f74d7bd77 100644 --- a/src/tiled/tilesetdocument.cpp +++ b/src/tiled/tilesetdocument.cpp @@ -371,6 +371,9 @@ void TilesetDocument::setSelectedTiles(const QList &selectedTiles) { mSelectedTiles = selectedTiles; emit selectedTilesChanged(); + + if (currentObject() && currentObject()->typeId() == Object::TileType) + emit currentObjectsChanged(); } QList TilesetDocument::currentObjects() const diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index aed7f6f81c..580efa507f 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -486,79 +486,137 @@ VariantEditor::VariantEditor(QWidget *parent) // }); } +/** + * Removes all properties from the editor. The properties are not deleted. + */ void VariantEditor::clear() { - Utils::deleteAllFromLayout(m_layout); -} - -void VariantEditor::addSeparator() -{ - auto separator = new QFrame(this); - separator->setFrameShape(QFrame::HLine); - separator->setFrameShadow(QFrame::Plain); - separator->setForegroundRole(QPalette::Mid); - m_layout->addWidget(separator); + QHashIterator it(m_propertyWidgets); + while (it.hasNext()) { + it.next(); + auto &widgets = it.value(); + delete widgets.label; + delete widgets.editor; + delete widgets.children; + delete widgets.layout; + + it.key()->disconnect(this); + } + m_propertyWidgets.clear(); } +/** + * Adds the given property to the editor. Does not take ownership of the + * property. + */ void VariantEditor::addProperty(Property *property) { + auto &widgets = m_propertyWidgets[property]; const auto displayMode = property->displayMode(); + connect(property, &Property::destroyed, this, [this](QObject *object) { + removeProperty(static_cast(object)); + }); + if (displayMode == Property::DisplayMode::Separator) { - addSeparator(); + auto separator = new QFrame(this); + separator->setFrameShape(QFrame::HLine); + separator->setFrameShadow(QFrame::Plain); + separator->setForegroundRole(QPalette::Mid); + widgets.editor = separator; + m_layout->addWidget(widgets.editor); return; } - auto label = new PropertyLabel(m_level, this); + widgets.label = new PropertyLabel(m_level, this); if (displayMode != Property::DisplayMode::NoLabel) { - label->setText(property->name()); - label->setToolTip(property->toolTip()); - label->setEnabled(property->isEnabled()); - connect(property, &Property::toolTipChanged, label, &QWidget::setToolTip); - connect(property, &Property::enabledChanged, label, &QWidget::setEnabled); + widgets.label->setText(property->name()); + widgets.label->setToolTip(property->toolTip()); + widgets.label->setEnabled(property->isEnabled()); + connect(property, &Property::toolTipChanged, widgets.label, &QWidget::setToolTip); + connect(property, &Property::enabledChanged, widgets.label, &QWidget::setEnabled); } - if (displayMode == Property::DisplayMode::Header) { - label->setHeader(true); - m_layout->addWidget(label); - } else { + if (displayMode == Property::DisplayMode::Header) + widgets.label->setHeader(true); + + if (const auto editor = property->createEditor(this)) { auto propertyLayout = new QHBoxLayout; propertyLayout->setContentsMargins(0, 0, m_layout->spacing(), 0); - propertyLayout->addWidget(label, LabelStretch, Qt::AlignTop); - - if (auto editor = createEditor(property)) { - editor->setToolTip(property->toolTip()); - editor->setEnabled(property->isEnabled()); - connect(property, &Property::toolTipChanged, editor, &QWidget::setToolTip); - connect(property, &Property::enabledChanged, editor, &QWidget::setEnabled); - propertyLayout->addWidget(editor, WidgetStretch); - } - m_layout->addLayout(propertyLayout); + editor->setMinimumWidth(Utils::dpiScaled(70)); + editor->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); + editor->setToolTip(property->toolTip()); + editor->setEnabled(property->isEnabled()); + connect(property, &Property::toolTipChanged, editor, &QWidget::setToolTip); + connect(property, &Property::enabledChanged, editor, &QWidget::setEnabled); + widgets.editor = editor; + + propertyLayout->addWidget(widgets.label, LabelStretch, Qt::AlignTop); + propertyLayout->addWidget(widgets.editor, WidgetStretch); + + widgets.layout = propertyLayout; + m_layout->addLayout(widgets.layout); + } else { + m_layout->addWidget(widgets.label); } if (auto groupProperty = dynamic_cast(property)) { - label->setExpandable(true); - label->setExpanded(label->isHeader()); + widgets.label->setExpandable(true); + widgets.label->setExpanded(widgets.label->isHeader()); - auto editor = new VariantEditor(this); - editor->setLevel(m_level + 1); - editor->setVisible(label->isExpanded()); + auto children = new VariantEditor(this); + children->setLevel(m_level + 1); + children->setVisible(widgets.label->isExpanded()); for (auto property : groupProperty->subProperties()) - editor->addProperty(property); + children->addProperty(property); + + connect(groupProperty, &GroupProperty::propertyAdded, + children, &VariantEditor::addProperty); - connect(label, &PropertyLabel::toggled, editor, [=](bool expanded) { - editor->setVisible(expanded); + connect(widgets.label, &PropertyLabel::toggled, children, [=](bool expanded) { + children->setVisible(expanded); // needed to avoid flickering when hiding the editor layout()->activate(); }); - m_layout->addWidget(editor); + widgets.children = children; + m_layout->addWidget(widgets.children); } } +/** + * Removes the given property from the editor. The property is not deleted. + */ +void VariantEditor::removeProperty(Property *property) +{ + auto it = m_propertyWidgets.constFind(property); + Q_ASSERT(it != m_propertyWidgets.constEnd()); + + if (it != m_propertyWidgets.end()) { + auto &widgets = it.value(); + delete widgets.label; + delete widgets.editor; + delete widgets.children; + delete widgets.layout; + + m_propertyWidgets.erase(it); + } + + property->disconnect(this); +} + +void VariantEditor::setLevel(int level) +{ + m_level = level; + + setBackgroundRole((m_level % 2) ? QPalette::AlternateBase + : QPalette::Base); + setAutoFillBackground(m_level > 1); +} + #if 0 void VariantEditor::addValue(const QVariant &value) { @@ -588,18 +646,6 @@ void VariantEditor::addValue(const QVariant &value) } #endif -QWidget *VariantEditor::createEditor(Property *property) -{ - if (const auto editor = property->createEditor(this)) { - editor->setMinimumWidth(Utils::dpiScaled(70)); - editor->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); - return editor; - } else { - qDebug() << "No editor for property" << property->name(); - } - return nullptr; -} - QWidget *BaseEnumProperty::createEnumEditor(QWidget *parent) { diff --git a/src/tiled/varianteditor.h b/src/tiled/varianteditor.h index 43b0235b3f..ce9098114b 100644 --- a/src/tiled/varianteditor.h +++ b/src/tiled/varianteditor.h @@ -21,6 +21,7 @@ #pragma once #include +#include #include #include #include @@ -93,6 +94,10 @@ class Separator final : public Property QWidget *createEditor(QWidget */*parent*/) override { return nullptr; } }; +/** + * A property that can have sub-properties. The GroupProperty owns the sub- + * properties and will delete them when it is deleted. + */ class GroupProperty : public Property { Q_OBJECT @@ -102,15 +107,36 @@ class GroupProperty : public Property : Property(name, parent) {} - DisplayMode displayMode() const override { return DisplayMode::Header; } + ~GroupProperty() override { clear(); } + + DisplayMode displayMode() const override + { return m_header ? DisplayMode::Header : DisplayMode::Default; } + QWidget *createEditor(QWidget */* parent */) override { return nullptr; } - void addProperty(Property *property) { m_subProperties.append(property); } - void addSeparator() { m_subProperties.append(new Separator(this)); } + void setHeader(bool header) { m_header = header; } + + void clear() + { + qDeleteAll(m_subProperties); + m_subProperties.clear(); + } + + void addProperty(Property *property) + { + m_subProperties.append(property); + emit propertyAdded(property); + } + + void addSeparator() { addProperty(new Separator(this)); } const QList &subProperties() const { return m_subProperties; } +signals: + void propertyAdded(Property *property); + private: + bool m_header = true; QList m_subProperties; }; @@ -305,22 +331,20 @@ struct QtAlignmentProperty : PropertyTemplate class PropertyFactory { public: - PropertyFactory() = default; - /** * Creates a property that wraps a QObject property. */ - Property *createQObjectProperty(QObject *qObject, - const char *propertyName, - const QString &displayName = {}); + static Property *createQObjectProperty(QObject *qObject, + const char *propertyName, + const QString &displayName = {}); /** * Creates a property with the given name and get/set functions. The * value type determines the kind of property that will be created. */ - Property *createProperty(const QString &name, - std::function get, - std::function set); + static Property *createProperty(const QString &name, + std::function get, + std::function set); }; struct EnumData @@ -405,21 +429,26 @@ class VariantEditor : public QWidget VariantEditor(QWidget *parent = nullptr); void clear(); - void addSeparator(); void addProperty(Property *property); + void removeProperty(Property *property); // void addValue(const QVariant &value); - void setLevel(int level) { m_level = level; } + void setLevel(int level); private: - QWidget *createEditor(Property *property); + static constexpr int LabelStretch = 4; + static constexpr int WidgetStretch = 6; - enum Column { - LabelStretch = 4, - WidgetStretch = 6, + struct PropertyWidgets + { + PropertyLabel *label = nullptr; + QWidget *editor = nullptr; + QWidget *children = nullptr; + QLayout *layout = nullptr; }; QVBoxLayout *m_layout; + QHash m_propertyWidgets; int m_level = 0; }; From b9ca537fc72daafb91d11fbb7ec0fbfc8723c7a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Thu, 26 Sep 2024 18:02:32 +0200 Subject: [PATCH 35/36] Show inherited class name as placeholder text For map objects, which can inherit their class from their tile or their template. --- src/tiled/propertieswidget.cpp | 21 +++++++++++++++++++-- src/tiled/textpropertyedit.h | 2 ++ src/tiled/varianteditor.cpp | 15 +++++++++++++++ src/tiled/varianteditor.h | 16 ++++++++++++++-- 4 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/tiled/propertieswidget.cpp b/src/tiled/propertieswidget.cpp index e65ab82234..2b6d695ed1 100644 --- a/src/tiled/propertieswidget.cpp +++ b/src/tiled/propertieswidget.cpp @@ -556,6 +556,8 @@ class ClassNameProperty : public StringProperty , mDocument(document) , mObject(object) { + updatePlaceholderText(); + connect(mDocument, &Document::changed, this, &ClassNameProperty::onChanged); } @@ -564,6 +566,7 @@ class ClassNameProperty : public StringProperty { auto editor = new QComboBox(parent); editor->setEditable(true); + editor->lineEdit()->setPlaceholderText(placeholderText()); editor->addItems(classNamesFor(*mObject)); auto syncEditor = [this, editor] { const QSignalBlocker blocker(editor); @@ -571,11 +574,15 @@ class ClassNameProperty : public StringProperty }; syncEditor(); connect(this, &Property::valueChanged, editor, syncEditor); + connect(this, &StringProperty::placeholderTextChanged, + editor->lineEdit(), &QLineEdit::setPlaceholderText); connect(editor, &QComboBox::currentTextChanged, this, &StringProperty::setValue); connect(Preferences::instance(), &Preferences::propertyTypesChanged, - editor, [this,editor] { + editor, [=] { + const QSignalBlocker blocker(editor); editor->clear(); editor->addItems(classNamesFor(*mObject)); + syncEditor(); }); return editor; } @@ -590,8 +597,18 @@ class ClassNameProperty : public StringProperty if (!objectsEvent.objects.contains(mObject)) return; - if (objectsEvent.properties & ObjectsChangeEvent::ClassProperty) + if (objectsEvent.properties & ObjectsChangeEvent::ClassProperty) { + updatePlaceholderText(); emit valueChanged(); + } + } + + void updatePlaceholderText() + { + if (mObject->typeId() == Object::MapObjectType && mObject->className().isEmpty()) + setPlaceholderText(static_cast(mObject)->effectiveClassName()); + else + setPlaceholderText(QString()); } Document *mDocument; diff --git a/src/tiled/textpropertyedit.h b/src/tiled/textpropertyedit.h index 4ef2c7360f..a509ab38fc 100644 --- a/src/tiled/textpropertyedit.h +++ b/src/tiled/textpropertyedit.h @@ -45,6 +45,8 @@ class TextPropertyEdit : public QWidget QString text() const; + QLineEdit *lineEdit() const { return mLineEdit; } + public slots: void setText(const QString &text); diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index 580efa507f..d09e163f25 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -57,15 +57,26 @@ void Property::setEnabled(bool enabled) } } +void StringProperty::setPlaceholderText(const QString &placeholderText) +{ + if (m_placeholderText != placeholderText) { + m_placeholderText = placeholderText; + emit placeholderTextChanged(placeholderText); + } +} + QWidget *StringProperty::createEditor(QWidget *parent) { auto editor = new QLineEdit(parent); + editor->setPlaceholderText(m_placeholderText); + auto syncEditor = [=] { editor->setText(value()); }; syncEditor(); connect(this, &Property::valueChanged, editor, syncEditor); + connect(this, &StringProperty::placeholderTextChanged, editor, &QLineEdit::setPlaceholderText); connect(editor, &QLineEdit::textEdited, this, &StringProperty::setValue); return editor; @@ -74,6 +85,8 @@ QWidget *StringProperty::createEditor(QWidget *parent) QWidget *MultilineStringProperty::createEditor(QWidget *parent) { auto editor = new TextPropertyEdit(parent); + editor->lineEdit()->setPlaceholderText(placeholderText()); + auto syncEditor = [=] { const QSignalBlocker blocker(editor); editor->setText(value()); @@ -81,6 +94,8 @@ QWidget *MultilineStringProperty::createEditor(QWidget *parent) syncEditor(); connect(this, &StringProperty::valueChanged, editor, syncEditor); + connect(this, &StringProperty::placeholderTextChanged, + editor->lineEdit(), &QLineEdit::setPlaceholderText); connect(editor, &TextPropertyEdit::textChanged, this, &StringProperty::setValue); return editor; diff --git a/src/tiled/varianteditor.h b/src/tiled/varianteditor.h index ce9098114b..a4e72d4130 100644 --- a/src/tiled/varianteditor.h +++ b/src/tiled/varianteditor.h @@ -168,13 +168,25 @@ class PropertyTemplate : public Property struct StringProperty : PropertyTemplate { + Q_OBJECT + +public: using PropertyTemplate::PropertyTemplate; QWidget *createEditor(QWidget *parent) override; + + void setPlaceholderText(const QString &placeholderText); + const QString &placeholderText() const { return m_placeholderText; } + +signals: + void placeholderTextChanged(const QString &placeholderText); + +private: + QString m_placeholderText; }; -struct MultilineStringProperty : PropertyTemplate +struct MultilineStringProperty : StringProperty { - using PropertyTemplate::PropertyTemplate; + using StringProperty::StringProperty; QWidget *createEditor(QWidget *parent) override; }; From 2339f355a7b5900b3046635bdc3dcc24f75f23ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Fri, 27 Sep 2024 15:56:35 +0200 Subject: [PATCH 36/36] Implemented Remove and Reset buttons for custom properties * Top-level custom properties can now be removed. * Class members can be reset. A "modified" state was added to Property, which is updated as necessary and indicated by using bold font for the name. Further tweaks to the spacing between property widgets and draw line to separate property group headers. A generic VariantMapProperty was split off from the CustomProperties class, since the former can also be a base for editing class members. The ColorButton widget now replaces its icon with "Unset" text when an invalid color is set. When changing the "Class" it now affects all selected objects. Fixed layout flicker when collapsing nested class members. --- src/tiled/colorbutton.cpp | 3 +- src/tiled/propertieswidget.cpp | 379 +++++++++++++++++++--------- src/tiled/propertyeditorwidgets.cpp | 15 ++ src/tiled/propertyeditorwidgets.h | 2 + src/tiled/utils.cpp | 3 + src/tiled/varianteditor.cpp | 199 ++++++++------- src/tiled/varianteditor.h | 64 +++-- 7 files changed, 430 insertions(+), 235 deletions(-) diff --git a/src/tiled/colorbutton.cpp b/src/tiled/colorbutton.cpp index 33f87c5406..25c1d3751e 100644 --- a/src/tiled/colorbutton.cpp +++ b/src/tiled/colorbutton.cpp @@ -40,7 +40,7 @@ ColorButton::ColorButton(QWidget *parent) void ColorButton::setColor(const QColor &color) { - if (mColor == color || !color.isValid()) + if (mColor == color) return; mColor = color; @@ -77,6 +77,7 @@ 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())); + setText(mColor.isValid() ? QString() : tr("Unset")); } #include "moc_colorbutton.cpp" diff --git a/src/tiled/propertieswidget.cpp b/src/tiled/propertieswidget.cpp index 2b6d695ed1..ba11667291 100644 --- a/src/tiled/propertieswidget.cpp +++ b/src/tiled/propertieswidget.cpp @@ -374,43 +374,126 @@ class TilesetImageProperty : public GroupProperty TilesetDocument *mTilesetDocument; }; +static bool propertyValueAffected(Object *currentObject, + Object *changedObject, + const QString &propertyName) +{ + if (currentObject == changedObject) + return true; + + // Changed property may be inherited + if (currentObject && currentObject->typeId() == Object::MapObjectType && changedObject->typeId() == Object::TileType) { + auto tile = static_cast(currentObject)->cell().tile(); + if (tile == changedObject && !currentObject->hasProperty(propertyName)) + return true; + } + + return false; +} + +static bool objectPropertiesRelevant(Document *document, Object *object) +{ + auto currentObject = document->currentObject(); + if (!currentObject) + return false; -class CustomProperties : public GroupProperty + if (currentObject == object) + return true; + + if (currentObject->typeId() == Object::MapObjectType) + if (static_cast(currentObject)->cell().tile() == object) + return true; + + if (document->currentObjects().contains(object)) + return true; + + return false; +} + + +class VariantMapProperty : public GroupProperty { Q_OBJECT public: - CustomProperties(QObject *parent = nullptr) - : GroupProperty(tr("Custom Properties"), parent) - {} + VariantMapProperty(const QString &name, QObject *parent = nullptr) + : GroupProperty(name, parent) + { + } - void setDocument(Document *document); + void setValue(const QVariantMap &value); + const QVariantMap &value() const { return mValue; } -private: - // todo: optimize - void propertyAdded(Object *, const QString &) { refresh(); } - void propertyRemoved(Object *, const QString &) { refresh(); } - void propertyChanged(Object *, const QString &) { - if (!mUpdating) - refresh(); - } - void propertiesChanged(Object *) { refresh(); } +signals: + void memberValueChanged(const QStringList &path, const QVariant &value); - void refresh(); +protected: + Document *mDocument = nullptr; +private: Property *createProperty(const QStringList &path, std::function get, std::function set); - void createClassMembers(const QStringList &path, GroupProperty *groupProperty, + void createClassMembers(const QStringList &path, + GroupProperty *groupProperty, const ClassPropertyType &classType, std::function get); + void updateModifiedRecursively(Property *property, const QVariant &value); + void emitValueChangedRecursively(Property *property); + + QVariantMap mValue; + QHash mPropertyMap; +}; + + +class CustomProperties : public VariantMapProperty +{ + Q_OBJECT + +public: + CustomProperties(QObject *parent = nullptr) + : VariantMapProperty(tr("Custom Properties"), parent) + { + connect(this, &VariantMapProperty::memberValueChanged, + this, &CustomProperties::setPropertyValue); + } + + void setDocument(Document *document); + +private: + // todo: optimize further + void propertyAdded(Object *object, const QString &) { + if (!objectPropertiesRelevant(mDocument, object)) + return; + refresh(); + } + void propertyRemoved(Object *object, const QString &) { + if (mUpdating) + return; + if (!objectPropertiesRelevant(mDocument, object)) + return; + refresh(); + } + void propertyChanged(Object *object, const QString &name) { + if (mUpdating) + return; + if (!propertyValueAffected(mDocument->currentObject(), object, name)) + return; + refresh(); + } + void propertiesChanged(Object *object) { + if (!objectPropertiesRelevant(mDocument, object)) + return; + refresh(); + } + + void refresh(); + void setPropertyValue(const QStringList &path, const QVariant &value); bool mUpdating = false; - Document *mDocument = nullptr; - Properties mProperties; }; @@ -421,6 +504,7 @@ PropertiesWidget::PropertiesWidget(QWidget *parent) { auto scrollWidget = new QWidget(mScrollArea); scrollWidget->setBackgroundRole(QPalette::AlternateBase); + scrollWidget->setMinimumWidth(Utils::dpiScaled(120)); auto verticalLayout = new QVBoxLayout(scrollWidget); mPropertyBrowser = new VariantEditor(scrollWidget); @@ -537,7 +621,6 @@ static QStringList classNamesFor(const Object &object) return names; } -// todo: add support for changing multiple objects class ClassNameProperty : public StringProperty { Q_OBJECT @@ -549,7 +632,7 @@ class ClassNameProperty : public StringProperty [this] (const QString &value) { QUndoStack *undoStack = mDocument->undoStack(); undoStack->push(new ChangeClassName(mDocument, - { mObject }, + mDocument->currentObjects(), value)); }, parent) @@ -2144,129 +2227,117 @@ void PropertiesWidget::currentObjectChanged(Object *object) } -void CustomProperties::setDocument(Document *document) +void VariantMapProperty::setValue(const QVariantMap &value) { - if (mDocument == document) - return; - - if (mDocument) - mDocument->disconnect(this); - - mDocument = document; - - if (document) { - connect(document, &Document::currentObjectsChanged, this, &CustomProperties::refresh); - - connect(document, &Document::propertyAdded, this, &CustomProperties::propertyAdded); - connect(document, &Document::propertyRemoved, this, &CustomProperties::propertyRemoved); - connect(document, &Document::propertyChanged, this, &CustomProperties::propertyChanged); - connect(document, &Document::propertiesChanged, this, &CustomProperties::propertiesChanged); - } - - refresh(); -} + mValue = value; -void CustomProperties::refresh() -{ clear(); - if (!mDocument || !mDocument->currentObject()) - return; - - mProperties = mDocument->currentObject()->properties(); - - QMapIterator it(mProperties); + QMapIterator it(mValue); while (it.hasNext()) { it.next(); const QString &name = it.key(); - auto get = [=] { return mProperties.value(name); }; + auto get = [=] { + return mValue.value(name); + }; auto set = [=] (const QVariant &value) { - setPropertyValue({ name }, value); + mValue.insert(name, value); + emit memberValueChanged({ name }, value); + emit valueChanged(); }; - if (auto property = createProperty({ name }, std::move(get), std::move(set))) + if (auto property = createProperty({ name }, std::move(get), std::move(set))) { + property->setActions(Property::Remove); + + updateModifiedRecursively(property, it.value()); + addProperty(property); + mPropertyMap.insert(name, property); + + connect(property, &Property::removeRequested, this, [=] { + mValue.remove(name); + + if (auto property = mPropertyMap.take(name)) + deleteProperty(property); + + emit memberValueChanged({ name }, QVariant()); + emit valueChanged(); + }); + } } + + emit valueChanged(); } -Property *CustomProperties::createProperty(const QStringList &path, - std::function get, - std::function set) +Property *VariantMapProperty::createProperty(const QStringList &path, + std::function get, + std::function set) { const auto value = get(); const auto type = value.userType(); const auto &name = path.last(); - switch (type) { - case QMetaType::Bool: - case QMetaType::QColor: - case QMetaType::Double: - case QMetaType::Int: - case QMetaType::QString: - return PropertyFactory::createProperty(name, std::move(get), std::move(set)); - - default: - if (type == filePathTypeId()) { - auto getUrl = [get = std::move(get)] { return get().value().url; }; - auto setUrl = [set = std::move(set)] (const QUrl &value) { - set(QVariant::fromValue(FilePath { value })); - }; - return new UrlProperty(name, std::move(getUrl), std::move(setUrl)); - } + if (type == filePathTypeId()) { + auto getUrl = [get = std::move(get)] { return get().value().url; }; + auto setUrl = [set = std::move(set)] (const QUrl &value) { + set(QVariant::fromValue(FilePath { value })); + }; + return new UrlProperty(name, std::move(getUrl), std::move(setUrl)); + } - if (type == objectRefTypeId()) { - auto getObjectRef = [get = std::move(get), this] { - return DisplayObjectRef(get().value(), - static_cast(mDocument)); - }; - auto setObjectRef = [set = std::move(set)](const DisplayObjectRef &value) { - set(QVariant::fromValue(value.ref)); - }; - return new ObjectRefProperty(name, std::move(getObjectRef), std::move(setObjectRef)); - } + if (type == objectRefTypeId()) { + auto getObjectRef = [get = std::move(get), this] { + return DisplayObjectRef(get().value(), + static_cast(mDocument)); + }; + auto setObjectRef = [set = std::move(set)](const DisplayObjectRef &value) { + set(QVariant::fromValue(value.ref)); + }; + return new ObjectRefProperty(name, std::move(getObjectRef), std::move(setObjectRef)); + } - if (type == propertyValueId()) { - if (auto propertyType = value.value().type()) { - switch (propertyType->type) { - case PropertyType::PT_Invalid: - break; - case PropertyType::PT_Class: { - auto classType = static_cast(*propertyType); + if (type == propertyValueId()) { + const auto propertyValue = value.value(); + if (auto propertyType = propertyValue.type()) { + switch (propertyType->type) { + case PropertyType::PT_Invalid: + break; + case PropertyType::PT_Class: { + auto classType = static_cast(*propertyType); - auto groupProperty = new GroupProperty(name); - groupProperty->setHeader(false); + auto groupProperty = new GroupProperty(name); + groupProperty->setHeader(false); - createClassMembers(path, groupProperty, classType, std::move(get)); + createClassMembers(path, groupProperty, classType, std::move(get)); - return groupProperty; - } - case PropertyType::PT_Enum: { - auto enumProperty = new BaseEnumProperty( - name, - [get = std::move(get)] { return get().value().value.toInt(); }, - [set = std::move(set), propertyType](int value) { - set(propertyType->wrap(value)); - }); - - auto enumType = static_cast(*propertyType); - enumProperty->setEnumData(enumType.values); - enumProperty->setFlags(enumType.valuesAsFlags); - - return enumProperty; - } - } + return groupProperty; + } + case PropertyType::PT_Enum: { + auto enumProperty = new BaseEnumProperty( + name, + [get = std::move(get)] { return get().value().value.toInt(); }, + [set = std::move(set), propertyType](int value) { + set(propertyType->wrap(value)); + }); + + auto enumType = static_cast(*propertyType); + enumProperty->setEnumData(enumType.values); + enumProperty->setFlags(enumType.valuesAsFlags); + + return enumProperty; + } } } } - return nullptr; + return createVariantProperty(name, std::move(get), std::move(set)); } -void CustomProperties::createClassMembers(const QStringList &path, - GroupProperty *groupProperty, - const ClassPropertyType &classType, - std::function get) +void VariantMapProperty::createClassMembers(const QStringList &path, + GroupProperty *groupProperty, + const ClassPropertyType &classType, + std::function get) { // Create a sub-property for each member QMapIterator it(classType.members); @@ -2282,23 +2353,101 @@ void CustomProperties::createClassMembers(const QStringList &path, return get().value().value.toMap().value(name, def); }; auto setMember = [=] (const QVariant &value) { - setPropertyValue(childPath, value); + if (setPropertyMemberValue(mValue, childPath, value)) { + const auto &topLevelName = childPath.first(); + updateModifiedRecursively(mPropertyMap.value(topLevelName), + mValue.value(topLevelName)); + + emit memberValueChanged(childPath, value); + emit valueChanged(); + } }; - if (auto childProperty = createProperty(childPath, std::move(getMember), std::move(setMember))) + if (auto childProperty = createProperty(childPath, std::move(getMember), setMember)) { + childProperty->setActions(Property::Reset); groupProperty->addProperty(childProperty); + + connect(childProperty, &Property::resetRequested, this, [=] { + setMember(QVariant()); + emitValueChangedRecursively(childProperty); + }); + } } } +void VariantMapProperty::updateModifiedRecursively(Property *property, + const QVariant &value) +{ + auto groupProperty = dynamic_cast(property); + if (!groupProperty) + return; + + const QVariantMap classValue = value.value().value.toMap(); + + for (auto subProperty : groupProperty->subProperties()) { + const auto &name = subProperty->name(); + const bool isModified = classValue.contains(name); + + if (subProperty->isModified() != isModified || subProperty->isModified()) { + subProperty->setModified(isModified); + updateModifiedRecursively(subProperty, classValue.value(name)); + } + } +} + +void VariantMapProperty::emitValueChangedRecursively(Property *property) +{ + emit property->valueChanged(); + + if (auto groupProperty = dynamic_cast(property)) + for (auto subProperty : groupProperty->subProperties()) + emitValueChangedRecursively(subProperty); +} + + +void CustomProperties::setDocument(Document *document) +{ + if (mDocument == document) + return; + + if (mDocument) + mDocument->disconnect(this); + + mDocument = document; + + if (document) { + connect(document, &Document::currentObjectsChanged, this, &CustomProperties::refresh); + + connect(document, &Document::propertyAdded, this, &CustomProperties::propertyAdded); + connect(document, &Document::propertyRemoved, this, &CustomProperties::propertyRemoved); + connect(document, &Document::propertyChanged, this, &CustomProperties::propertyChanged); + connect(document, &Document::propertiesChanged, this, &CustomProperties::propertiesChanged); + } + + refresh(); +} + +void CustomProperties::refresh() +{ + if (mDocument && mDocument->currentObject()) + setValue(mDocument->currentObject()->properties()); + else + setValue({}); +} + void CustomProperties::setPropertyValue(const QStringList &path, const QVariant &value) { const auto objects = mDocument->currentObjects(); if (!objects.isEmpty()) { QScopedValueRollback updating(mUpdating, true); - mDocument->undoStack()->push(new SetProperty(mDocument, objects, path, value)); + if (path.size() > 1 || value.isValid()) + mDocument->undoStack()->push(new SetProperty(mDocument, objects, path, value)); + else + mDocument->undoStack()->push(new RemoveProperty(mDocument, objects, path.first())); } } + void PropertiesWidget::updateActions() { #if 0 diff --git a/src/tiled/propertyeditorwidgets.cpp b/src/tiled/propertyeditorwidgets.cpp index aac5955e95..284248c9a2 100644 --- a/src/tiled/propertyeditorwidgets.cpp +++ b/src/tiled/propertyeditorwidgets.cpp @@ -538,6 +538,13 @@ void PropertyLabel::setExpanded(bool expanded) emit toggled(m_expanded); } +void PropertyLabel::setModified(bool modified) +{ + auto f = font(); + f.setBold(modified); + setFont(f); +} + void PropertyLabel::mousePressEvent(QMouseEvent *event) { if (m_expandable && event->button() == Qt::LeftButton) { @@ -566,6 +573,14 @@ void PropertyLabel::paintEvent(QPaintEvent *event) QStylePainter p(this); p.drawPrimitive(QStyle::PE_IndicatorBranch, branchOption); + + if (m_header) { + const QColor color = static_cast(p.style()->styleHint(QStyle::SH_Table_GridLineColor, &branchOption)); + p.save(); + p.setPen(QPen(color)); + p.drawLine(0, height() - 1, width(), height() - 1); + p.restore(); + } } diff --git a/src/tiled/propertyeditorwidgets.h b/src/tiled/propertyeditorwidgets.h index e5355c0f9e..cf3330ccd1 100644 --- a/src/tiled/propertyeditorwidgets.h +++ b/src/tiled/propertyeditorwidgets.h @@ -294,6 +294,8 @@ class PropertyLabel : public ElidingLabel void setExpanded(bool expanded); bool isExpanded() const { return m_expanded; } + void setModified(bool modified); + QSize sizeHint() const override; signals: diff --git a/src/tiled/utils.cpp b/src/tiled/utils.cpp index ac7538ccc3..a33c256869 100644 --- a/src/tiled/utils.cpp +++ b/src/tiled/utils.cpp @@ -312,6 +312,9 @@ QIcon themeIcon(const QString &name) QIcon colorIcon(const QColor &color, QSize size) { + if (!color.isValid()) + return QIcon(); + QPixmap pixmap(size); pixmap.fill(color); diff --git a/src/tiled/varianteditor.cpp b/src/tiled/varianteditor.cpp index d09e163f25..a73a3831e1 100644 --- a/src/tiled/varianteditor.cpp +++ b/src/tiled/varianteditor.cpp @@ -57,6 +57,14 @@ void Property::setEnabled(bool enabled) } } +void Property::setModified(bool modified) +{ + if (m_modified != modified) { + m_modified = modified; + emit modifiedChanged(modified); + } +} + void StringProperty::setPlaceholderText(const QString &placeholderText) { if (m_placeholderText != placeholderText) { @@ -183,10 +191,17 @@ QWidget *BoolProperty::createEditor(QWidget *parent) const QSignalBlocker blocker(editor); bool checked = value(); editor->setChecked(checked); + + // Reflect modified state on the checkbox, since we're not showing the + // property label. + auto font = editor->font(); + font.setBold(isModified()); + editor->setFont(font); }; syncEditor(); connect(this, &Property::valueChanged, editor, syncEditor); + connect(this, &Property::modifiedChanged, editor, syncEditor); connect(editor, &QCheckBox::toggled, this, &BoolProperty::setValue); return editor; @@ -480,25 +495,7 @@ VariantEditor::VariantEditor(QWidget *parent) m_layout = new QVBoxLayout(this); m_layout->setContentsMargins(QMargins()); - m_layout->setSpacing(Utils::dpiScaled(4)); - - // setValue(QVariantMap { - // { QStringLiteral("Name"), QVariant(QLatin1String("Hello")) }, - // { QStringLiteral("Position"), QVariant(QPoint(15, 50)) }, - // { QStringLiteral("Size"), QVariant(QSize(35, 400)) }, - // { QStringLiteral("Rectangle"), QVariant(QRectF(15, 50, 35, 400)) }, - // { QStringLiteral("Margin"), QVariant(10) }, - // { QStringLiteral("Opacity"), QVariant(0.5) }, - // { QStringLiteral("Visible"), true }, - // { QStringLiteral("Object Alignment"), QVariant::fromValue(TopLeft) }, - // }); - - - // setValue(QVariantList { - // QVariant(QLatin1String("Hello")), - // QVariant(10), - // QVariant(3.14) - // }); + m_layout->setSpacing(0); } /** @@ -510,10 +507,9 @@ void VariantEditor::clear() while (it.hasNext()) { it.next(); auto &widgets = it.value(); - delete widgets.label; - delete widgets.editor; - delete widgets.children; + Utils::deleteAllFromLayout(widgets.layout); delete widgets.layout; + delete widgets.children; it.key()->disconnect(this); } @@ -533,68 +529,107 @@ void VariantEditor::addProperty(Property *property) removeProperty(static_cast(object)); }); + const auto halfSpacing = Utils::dpiScaled(2); + + widgets.layout = new QHBoxLayout; + widgets.layout->setSpacing(halfSpacing * 2); + if (displayMode == Property::DisplayMode::Separator) { auto separator = new QFrame(this); + widgets.layout->setContentsMargins(0, halfSpacing, 0, halfSpacing); separator->setFrameShape(QFrame::HLine); separator->setFrameShadow(QFrame::Plain); separator->setForegroundRole(QPalette::Mid); - widgets.editor = separator; - m_layout->addWidget(widgets.editor); + widgets.layout->addWidget(separator); + m_layout->addLayout(widgets.layout); return; } - widgets.label = new PropertyLabel(m_level, this); + auto label = new PropertyLabel(m_level, this); if (displayMode != Property::DisplayMode::NoLabel) { - widgets.label->setText(property->name()); - widgets.label->setToolTip(property->toolTip()); - widgets.label->setEnabled(property->isEnabled()); - connect(property, &Property::toolTipChanged, widgets.label, &QWidget::setToolTip); - connect(property, &Property::enabledChanged, widgets.label, &QWidget::setEnabled); + label->setText(property->name()); + label->setToolTip(property->toolTip()); + label->setEnabled(property->isEnabled()); + label->setModified(property->isModified()); + connect(property, &Property::toolTipChanged, label, &QWidget::setToolTip); + connect(property, &Property::enabledChanged, label, &QWidget::setEnabled); + connect(property, &Property::modifiedChanged, label, &PropertyLabel::setModified); } if (displayMode == Property::DisplayMode::Header) - widgets.label->setHeader(true); + label->setHeader(true); + else + widgets.layout->setContentsMargins(0, halfSpacing, halfSpacing * 2, halfSpacing); + + widgets.layout->addWidget(label, LabelStretch, Qt::AlignTop); - if (const auto editor = property->createEditor(this)) { - auto propertyLayout = new QHBoxLayout; - propertyLayout->setContentsMargins(0, 0, m_layout->spacing(), 0); + QHBoxLayout *editorLayout = widgets.layout; + const auto editor = property->createEditor(this); - editor->setMinimumWidth(Utils::dpiScaled(70)); + if (editor && property->actions()) { + editorLayout = new QHBoxLayout; + widgets.layout->addLayout(editorLayout, WidgetStretch); + } + + if (editor) { editor->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); editor->setToolTip(property->toolTip()); editor->setEnabled(property->isEnabled()); connect(property, &Property::toolTipChanged, editor, &QWidget::setToolTip); connect(property, &Property::enabledChanged, editor, &QWidget::setEnabled); - widgets.editor = editor; - propertyLayout->addWidget(widgets.label, LabelStretch, Qt::AlignTop); - propertyLayout->addWidget(widgets.editor, WidgetStretch); + editorLayout->addWidget(editor, WidgetStretch, Qt::AlignTop); + } - widgets.layout = propertyLayout; - m_layout->addLayout(widgets.layout); - } else { - m_layout->addWidget(widgets.label); + if (property->actions()) { + if (property->actions() & Property::Reset) { + auto resetButton = new QToolButton; + resetButton->setToolTip(tr("Reset")); + resetButton->setAutoRaise(true); + resetButton->setEnabled(property->isModified()); + Utils::setThemeIcon(resetButton, "edit-clear"); + editorLayout->addWidget(resetButton, 0, Qt::AlignTop); + connect(resetButton, &QAbstractButton::clicked, property, &Property::resetRequested); + connect(property, &Property::modifiedChanged, resetButton, &QWidget::setEnabled); + } + + if (property->actions() & Property::Remove) { + auto removeButton = new QToolButton; + removeButton->setToolTip(tr("Remove")); + removeButton->setAutoRaise(true); + Utils::setThemeIcon(removeButton, "remove"); + editorLayout->addWidget(removeButton, 0, Qt::AlignTop); + connect(removeButton, &QAbstractButton::clicked, property, &Property::removeRequested); + } } + m_layout->addLayout(widgets.layout); + if (auto groupProperty = dynamic_cast(property)) { - widgets.label->setExpandable(true); - widgets.label->setExpanded(widgets.label->isHeader()); + label->setExpandable(true); + label->setExpanded(label->isHeader()); auto children = new VariantEditor(this); + if (label->isHeader()) + children->setContentsMargins(0, halfSpacing, 0, halfSpacing); children->setLevel(m_level + 1); - children->setVisible(widgets.label->isExpanded()); + children->setVisible(label->isExpanded()); for (auto property : groupProperty->subProperties()) children->addProperty(property); connect(groupProperty, &GroupProperty::propertyAdded, children, &VariantEditor::addProperty); - connect(widgets.label, &PropertyLabel::toggled, children, [=](bool expanded) { + connect(label, &PropertyLabel::toggled, children, [=](bool expanded) { children->setVisible(expanded); // needed to avoid flickering when hiding the editor - layout()->activate(); + QWidget *widget = this; + while (widget && widget->layout()) { + widget->layout()->activate(); + widget = widget->parentWidget(); + } }); widgets.children = children; @@ -612,10 +647,9 @@ void VariantEditor::removeProperty(Property *property) if (it != m_propertyWidgets.end()) { auto &widgets = it.value(); - delete widgets.label; - delete widgets.editor; - delete widgets.children; + Utils::deleteAllFromLayout(widgets.layout); delete widgets.layout; + delete widgets.children; m_propertyWidgets.erase(it); } @@ -632,35 +666,6 @@ void VariantEditor::setLevel(int level) setAutoFillBackground(m_level > 1); } -#if 0 -void VariantEditor::addValue(const QVariant &value) -{ - const int type = value.userType(); - switch (type) { - case QMetaType::QVariantList: { - const auto list = value.toList(); - for (const auto &item : list) - addValue(item); - break; - } - case QMetaType::QVariantMap: { - const auto map = value.toMap(); - for (auto it = map.constBegin(); it != map.constEnd(); ++it) - addValue(it.key(), it.value()); - break; - } - default: { - if (auto editor = createEditor(value)) - m_gridLayout->addWidget(editor, m_rowIndex, LabelColumn, 1, 3); - else - qDebug() << "No editor factory for type" << type; - - ++m_rowIndex; - } - } -} -#endif - QWidget *BaseEnumProperty::createEnumEditor(QWidget *parent) { @@ -731,9 +736,12 @@ QWidget *BaseEnumProperty::createFlagsEditor(QWidget *parent) return editor; } -Property *PropertyFactory::createQObjectProperty(QObject *qObject, - const char *propertyName, - const QString &displayName) +/** + * Creates a property that wraps a QObject property. + */ +Property *createQObjectProperty(QObject *qObject, + const char *propertyName, + const QString &displayName) { auto metaObject = qObject->metaObject(); auto propertyIndex = metaObject->indexOfProperty(propertyName); @@ -741,14 +749,15 @@ Property *PropertyFactory::createQObjectProperty(QObject *qObject, return nullptr; auto metaProperty = metaObject->property(propertyIndex); - auto property = createProperty(displayName.isEmpty() ? QString::fromUtf8(propertyName) - : displayName, - [=] { - return metaProperty.read(qObject); - }, - [=] (const QVariant &value) { - metaProperty.write(qObject, value); - }); + auto property = createVariantProperty( + displayName.isEmpty() ? QString::fromUtf8(propertyName) + : displayName, + [=] { + return metaProperty.read(qObject); + }, + [=] (const QVariant &value) { + metaProperty.write(qObject, value); + }); // If the property has a notify signal, forward it to valueChanged auto notify = metaProperty.notifySignal(); @@ -776,9 +785,13 @@ Property *createTypedProperty(const QString &name, [set = std::move(set)] (const typename PropertyClass::ValueType &v) { set(QVariant::fromValue(v)); }); } -Property *PropertyFactory::createProperty(const QString &name, - std::function get, - std::function set) +/** + * Creates a property with the given name and get/set functions. The + * value type determines the kind of property that will be created. + */ +Property *createVariantProperty(const QString &name, + std::function get, + std::function set) { const auto type = get().userType(); switch (type) { diff --git a/src/tiled/varianteditor.h b/src/tiled/varianteditor.h index a4e72d4130..c4dc66a928 100644 --- a/src/tiled/varianteditor.h +++ b/src/tiled/varianteditor.h @@ -29,6 +29,7 @@ #include #include +class QHBoxLayout; class QVBoxLayout; namespace Tiled { @@ -44,6 +45,7 @@ class Property : public QObject Q_PROPERTY(QString name READ name CONSTANT) Q_PROPERTY(QString toolTip READ toolTip WRITE setToolTip NOTIFY toolTipChanged) Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged) + Q_PROPERTY(bool modified READ isModified WRITE setModified NOTIFY modifiedChanged) public: enum class DisplayMode { @@ -53,6 +55,12 @@ class Property : public QObject Separator }; + enum Action { + Reset = 0x01, + Remove = 0x02, + }; + Q_DECLARE_FLAGS(Actions, Action) + Property(const QString &name, QObject *parent = nullptr) : QObject(parent) , m_name(name) @@ -66,6 +74,12 @@ class Property : public QObject bool isEnabled() const { return m_enabled; } void setEnabled(bool enabled); + bool isModified() const { return m_modified; } + void setModified(bool modified); + + Actions actions() const { return m_actions; } + void setActions(Actions actions) { m_actions = actions; } + virtual DisplayMode displayMode() const { return DisplayMode::Default; } virtual QWidget *createEditor(QWidget *parent) = 0; @@ -74,11 +88,19 @@ class Property : public QObject void toolTipChanged(const QString &toolTip); void valueChanged(); void enabledChanged(bool enabled); + void modifiedChanged(bool modified); + + void resetRequested(); + void removeRequested(); private: + friend class GroupProperty; + QString m_name; QString m_toolTip; bool m_enabled = true; + bool m_modified = false; + Actions m_actions; }; class Separator final : public Property @@ -128,6 +150,12 @@ class GroupProperty : public Property emit propertyAdded(property); } + void deleteProperty(Property *property) + { + m_subProperties.removeOne(property); + delete property; + } + void addSeparator() { addProperty(new Separator(this)); } const QList &subProperties() const { return m_subProperties; } @@ -336,28 +364,13 @@ struct QtAlignmentProperty : PropertyTemplate }; -/** - * A property factory that instantiates the appropriate property type based on - * the type of the property value. - */ -class PropertyFactory -{ -public: - /** - * Creates a property that wraps a QObject property. - */ - static Property *createQObjectProperty(QObject *qObject, - const char *propertyName, - const QString &displayName = {}); - - /** - * Creates a property with the given name and get/set functions. The - * value type determines the kind of property that will be created. - */ - static Property *createProperty(const QString &name, - std::function get, - std::function set); -}; +Property *createQObjectProperty(QObject *qObject, + const char *propertyName, + const QString &displayName = {}); + +Property *createVariantProperty(const QString &name, + std::function get, + std::function set); struct EnumData { @@ -443,7 +456,6 @@ class VariantEditor : public QWidget void clear(); void addProperty(Property *property); void removeProperty(Property *property); - // void addValue(const QVariant &value); void setLevel(int level); @@ -453,10 +465,8 @@ class VariantEditor : public QWidget struct PropertyWidgets { - PropertyLabel *label = nullptr; - QWidget *editor = nullptr; + QHBoxLayout *layout = nullptr; QWidget *children = nullptr; - QLayout *layout = nullptr; }; QVBoxLayout *m_layout; @@ -465,3 +475,5 @@ class VariantEditor : public QWidget }; } // namespace Tiled + +Q_DECLARE_OPERATORS_FOR_FLAGS(Tiled::Property::Actions)