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; };