diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index d49272b..43dc4c3 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -24,6 +24,7 @@ set(MLNQtCore_Headers style/style_parameter.hpp style/filter_parameter.hpp + style/image_parameter.hpp style/layer_parameter.hpp style/source_parameter.hpp ) @@ -68,6 +69,7 @@ target_sources( style/style_parameter.cpp style/filter_parameter.cpp + style/image_parameter.cpp style/layer_parameter.cpp style/source_parameter.cpp diff --git a/src/core/style/image_parameter.cpp b/src/core/style/image_parameter.cpp new file mode 100644 index 0000000..234a691 --- /dev/null +++ b/src/core/style/image_parameter.cpp @@ -0,0 +1,61 @@ +// Copyright (C) 2023 MapLibre contributors + +// SPDX-License-Identifier: BSD-2-Clause + +#include "image_parameter.hpp" + +#include "style_parameter.hpp" + +namespace QMapLibre { + +/*! + \class ImageParameter + \brief A helper utility to manage image sources. + \ingroup QMapLibre + + \headerfile image_parameter.hpp +*/ + +/*! + \brief Default constructor +*/ +ImageParameter::ImageParameter(QObject *parent) + : StyleParameter(parent) {} + +ImageParameter::~ImageParameter() = default; + +/*! + \fn void ImageParameter::sourceUpdated() + \brief Signal emitted when the image source is updated. +*/ + +/*! + \brief Get the image source. + \return image source as \c QString. +*/ +QString ImageParameter::source() const { + return m_source; +} + +/*! + \brief Set the image source. + \param source image source to set. + + \ref sourceUpdated() signal is emitted when the image source is updated. +*/ +void ImageParameter::setSource(const QString &source) { + if (m_source == source) { + return; + } + + m_source = source; + + Q_EMIT sourceUpdated(); +} + +/*! + \var ImageParameter::m_source + \brief image source +*/ + +} // namespace QMapLibre diff --git a/src/core/style/image_parameter.hpp b/src/core/style/image_parameter.hpp new file mode 100644 index 0000000..38835dd --- /dev/null +++ b/src/core/style/image_parameter.hpp @@ -0,0 +1,37 @@ +// Copyright (C) 2023 MapLibre contributors + +// SPDX-License-Identifier: BSD-2-Clause + +#ifndef QMAPLIBRE_IMAGE_PARAMETER_H +#define QMAPLIBRE_IMAGE_PARAMETER_H + +#include "style_parameter.hpp" + +#include + +#include +#include + +namespace QMapLibre { + +class Q_MAPLIBRE_CORE_EXPORT ImageParameter : public StyleParameter { + Q_OBJECT +public: + explicit ImageParameter(QObject *parent = nullptr); + ~ImageParameter() override; + + [[nodiscard]] QString source() const; + void setSource(const QString &source); + +Q_SIGNALS: + void sourceUpdated(); + +protected: + QString m_source; + + Q_DISABLE_COPY(ImageParameter) +}; + +} // namespace QMapLibre + +#endif // QMAPLIBRE_IMAGE_PARAMETER_H diff --git a/src/core/style/image_style_change.cpp b/src/core/style/image_style_change.cpp index a778dd3..a5df217 100644 --- a/src/core/style/image_style_change.cpp +++ b/src/core/style/image_style_change.cpp @@ -11,9 +11,17 @@ namespace QMapLibre { /*! \cond PRIVATE */ // StyleAddImage -StyleAddImage::StyleAddImage(const StyleParameter *parameter) - : m_id(parameter->property("id").toString()), - m_sprite(QImage(parameter->property("sprite").toString())) {} +StyleAddImage::StyleAddImage(QString id, const QString &spriteUrl) + : m_id(std::move(id)), + m_sprite(QImage(spriteUrl)) {} + +StyleAddImage::StyleAddImage(QString id, QImage sprite) + : m_id(std::move(id)), + m_sprite(std::move(sprite)) {} + +StyleAddImage::StyleAddImage(const ImageParameter *parameter) + : m_id(parameter->styleId()), + m_sprite(QImage(parameter->source())) {} void StyleAddImage::apply(Map *map) { if (map == nullptr) { @@ -27,8 +35,8 @@ void StyleAddImage::apply(Map *map) { StyleRemoveImage::StyleRemoveImage(QString id) : m_id(std::move(id)) {} -StyleRemoveImage::StyleRemoveImage(const StyleParameter *parameter) - : m_id(parameter->property("id").toString()) {} +StyleRemoveImage::StyleRemoveImage(const ImageParameter *parameter) + : m_id(parameter->styleId()) {} void StyleRemoveImage::apply(Map *map) { if (map == nullptr) { diff --git a/src/core/style/image_style_change_p.hpp b/src/core/style/image_style_change_p.hpp index 1679ee0..be181c8 100644 --- a/src/core/style/image_style_change_p.hpp +++ b/src/core/style/image_style_change_p.hpp @@ -5,8 +5,8 @@ #pragma once #include "export_core.hpp" +#include "image_parameter.hpp" #include "style_change_p.hpp" -#include "style_parameter.hpp" #include @@ -16,7 +16,9 @@ class Map; class Q_MAPLIBRE_CORE_EXPORT StyleAddImage : public StyleChange { public: - explicit StyleAddImage(const StyleParameter *parameter); + explicit StyleAddImage(QString id, const QString &spriteUrl); + explicit StyleAddImage(QString id, QImage sprite); + explicit StyleAddImage(const ImageParameter *parameter); void apply(Map *map) override; @@ -28,7 +30,7 @@ class Q_MAPLIBRE_CORE_EXPORT StyleAddImage : public StyleChange { class Q_MAPLIBRE_CORE_EXPORT StyleRemoveImage : public StyleChange { public: explicit StyleRemoveImage(QString id); - explicit StyleRemoveImage(const StyleParameter *parameter); + explicit StyleRemoveImage(const ImageParameter *parameter); void apply(Map *map) override; diff --git a/src/core/style/source_style_change.cpp b/src/core/style/source_style_change.cpp index a72830d..6533a32 100644 --- a/src/core/style/source_style_change.cpp +++ b/src/core/style/source_style_change.cpp @@ -4,7 +4,6 @@ #include "source_style_change_p.hpp" #include "style/source_parameter.hpp" -#include "style/style_parameter.hpp" #include @@ -50,7 +49,7 @@ StyleAddSource::StyleAddSource(const SourceParameter *parameter) } break; case 3: { // geojson - const QString data = parameter->property("data").toString(); + const QString data = parameter->parsedProperty("data").toString(); if (data.startsWith(':')) { QFile geojson(data); geojson.open(QIODevice::ReadOnly); diff --git a/src/core/style/style_change.cpp b/src/core/style/style_change.cpp index a6ffa54..b74c9f5 100644 --- a/src/core/style/style_change.cpp +++ b/src/core/style/style_change.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: BSD-2-Clause +#include "image_style_change_p.hpp" #include "layer_style_change_p.hpp" #include "source_style_change_p.hpp" #include "style_change_p.hpp" @@ -49,6 +50,12 @@ std::vector> StyleChange::addParameter(const StyleP return changes; } + const auto *imageParameter = qobject_cast(parameter); + if (imageParameter != nullptr) { + changes.push_back(std::make_unique(imageParameter)); + return changes; + } + const auto *filterParameter = qobject_cast(parameter); if (filterParameter != nullptr) { changes.push_back(std::make_unique(filterParameter)); @@ -73,6 +80,12 @@ std::vector> StyleChange::removeParameter(const Sty return changes; } + const auto *imageParameter = qobject_cast(parameter); + if (imageParameter != nullptr) { + changes.push_back(std::make_unique(imageParameter)); + return changes; + } + const auto *filterParameter = qobject_cast(parameter); if (filterParameter != nullptr) { changes.push_back(std::make_unique(filterParameter->styleId(), QVariantList())); diff --git a/src/core/style/style_parameter.cpp b/src/core/style/style_parameter.cpp index 7066e62..4d7ff49 100644 --- a/src/core/style/style_parameter.cpp +++ b/src/core/style/style_parameter.cpp @@ -15,7 +15,8 @@ namespace QMapLibre { \ingroup QMapLibre Should usually not be used directly, but rather through one of its subclasses - such as \ref SourceParameter or \ref LayerParameter. + such as \ref SourceParameter, \ref LayerParameter, \ref ImageParameter + or \ref FilterParameter. */ /*! @@ -51,6 +52,18 @@ bool StyleParameter::operator==(const StyleParameter &other) const { \return \c true if the style parameter is ready, \c false otherwise. */ +/*! + \brief Get the property value + \param propertyName Name of the property to get. + \return Property value as \c QVariant or invalid \c QVariant if the property does not exist. + + By default this directly reads the property value from the object. + It can be reimplemented from subclasses to provide custom parsing. +*/ +QVariant StyleParameter::parsedProperty(const char *propertyName) const { + return property(propertyName); +} + /*! \brief Check for property existence. \param propertyName Name of the property to check. diff --git a/src/core/style/style_parameter.hpp b/src/core/style/style_parameter.hpp index 53fa860..5d9c14e 100644 --- a/src/core/style/style_parameter.hpp +++ b/src/core/style/style_parameter.hpp @@ -23,6 +23,7 @@ class Q_MAPLIBRE_CORE_EXPORT StyleParameter : public QObject { [[nodiscard]] inline bool isReady() const { return m_ready; }; + [[nodiscard]] virtual QVariant parsedProperty(const char *propertyName) const; bool hasProperty(const char *propertyName) const; void updateProperty(const char *propertyName, const QVariant &value); diff --git a/src/location/plugins/CMakeLists.txt b/src/location/plugins/CMakeLists.txt index 38e543b..bbde029 100644 --- a/src/location/plugins/CMakeLists.txt +++ b/src/location/plugins/CMakeLists.txt @@ -89,6 +89,7 @@ set(Plugin_Sources declarative_style.cpp declarative_style.hpp declarative_style_parameter.hpp declarative_filter_parameter.cpp declarative_filter_parameter.hpp + declarative_image_parameter.cpp declarative_image_parameter.hpp declarative_layer_parameter.cpp declarative_layer_parameter.hpp declarative_source_parameter.cpp declarative_source_parameter.hpp diff --git a/src/location/plugins/declarative_image_parameter.cpp b/src/location/plugins/declarative_image_parameter.cpp new file mode 100644 index 0000000..a28983e --- /dev/null +++ b/src/location/plugins/declarative_image_parameter.cpp @@ -0,0 +1,12 @@ +// Copyright (C) 2023 MapLibre contributors + +// SPDX-License-Identifier: BSD-2-Clause + +#include "declarative_image_parameter.hpp" + +namespace QMapLibre { + +DeclarativeImageParameter::DeclarativeImageParameter(QObject *parent) + : ImageParameter(parent) {} + +} // namespace QMapLibre diff --git a/src/location/plugins/declarative_image_parameter.hpp b/src/location/plugins/declarative_image_parameter.hpp new file mode 100644 index 0000000..1ef6f11 --- /dev/null +++ b/src/location/plugins/declarative_image_parameter.hpp @@ -0,0 +1,36 @@ +// Copyright (C) 2023 MapLibre contributors + +// SPDX-License-Identifier: BSD-2-Clause + +#pragma once + +#include "declarative_style_parameter.hpp" + +#include + +#include +#include + +namespace QMapLibre { + +class DeclarativeImageParameter : public ImageParameter, public QQmlParserStatus { + Q_OBJECT + QML_NAMED_ELEMENT(ImageParameter) +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + QML_ADDED_IN_VERSION(3, 0) +#endif + Q_INTERFACES(QQmlParserStatus) + // from base class + Q_PROPERTY(QString styleId READ styleId WRITE setStyleId) + Q_PROPERTY(QString source READ source WRITE setSource NOTIFY sourceUpdated) + // this type must not declare any additional properties +public: + explicit DeclarativeImageParameter(QObject *parent = nullptr); + ~DeclarativeImageParameter() override = default; + +private: + // QQmlParserStatus implementation + MLN_DECLARATIVE_PARSER(DeclarativeImageParameter) +}; + +} // namespace QMapLibre diff --git a/src/location/plugins/declarative_source_parameter.cpp b/src/location/plugins/declarative_source_parameter.cpp index f8883e8..94c3375 100644 --- a/src/location/plugins/declarative_source_parameter.cpp +++ b/src/location/plugins/declarative_source_parameter.cpp @@ -4,9 +4,33 @@ #include "declarative_source_parameter.hpp" +#include + namespace QMapLibre { DeclarativeSourceParameter::DeclarativeSourceParameter(QObject *parent) : SourceParameter(parent) {} +QVariant DeclarativeSourceParameter::parsedProperty(const char *propertyName) const { + if (!hasProperty(propertyName)) { + return StyleParameter::parsedProperty(propertyName); + } + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + if (property(propertyName).typeId() < QMetaType::User) { +#else + if (property(propertyName).type() < QVariant::UserType) { +#endif + return StyleParameter::parsedProperty(propertyName); + } + + // explicitly handle only "data" for now + if (qstrcmp(propertyName, "data") != 0) { + return StyleParameter::parsedProperty(propertyName); + } + + // convert QJSValue to JSON string + return QJsonDocument::fromVariant(property(propertyName).value().toVariant()).toJson(); +} + } // namespace QMapLibre diff --git a/src/location/plugins/declarative_source_parameter.hpp b/src/location/plugins/declarative_source_parameter.hpp index b168f4e..d0bfa8b 100644 --- a/src/location/plugins/declarative_source_parameter.hpp +++ b/src/location/plugins/declarative_source_parameter.hpp @@ -28,6 +28,8 @@ class DeclarativeSourceParameter : public SourceParameter, public QQmlParserStat explicit DeclarativeSourceParameter(QObject *parent = nullptr); ~DeclarativeSourceParameter() override = default; + [[nodiscard]] QVariant parsedProperty(const char *propertyName) const override; + private: // QQmlParserStatus implementation MLN_DECLARATIVE_PARSER(DeclarativeSourceParameter) diff --git a/test/qml/CMakeLists.txt b/test/qml/CMakeLists.txt index d385b7a..b0c7015 100644 --- a/test/qml/CMakeLists.txt +++ b/test/qml/CMakeLists.txt @@ -1,5 +1,11 @@ if(COMMAND qt_add_executable) qt_add_executable(test_mln_qml test_qml.cpp) + + qt_add_resources(test_mln_qml test_fixtures + PREFIX "/" + BASE "../../vendor/maplibre-native/metrics/integration/sprites" + FILES "../../vendor/maplibre-native/metrics/integration/sprites/1x.png" + ) else() add_executable(test_mln_qml test_qml.cpp) endif() diff --git a/test/qml/qt6/tst_style_parameters.qml b/test/qml/qt6/tst_style_parameters.qml index 8a46489..3d4b98a 100644 --- a/test/qml/qt6/tst_style_parameters.qml +++ b/test/qml/qt6/tst_style_parameters.qml @@ -164,7 +164,7 @@ Item { } `, style, - "sourceParamSnipper" + "sourceParamSnippet" ) style.addParameter(sourceParam) @@ -187,5 +187,69 @@ Item { style.removeParameter(sourceParam) wait(1000) } + + function test_style_8_image() { + let imageParam = Qt.createQmlObject(` + import MapLibre 3.0 + + ImageParameter { + styleId: "locationImage" + source: ":/1x.png" + } + `, + style, + "imageParamSnippet") + style.addParameter(imageParam) + + let sourceParam = Qt.createQmlObject(` + import MapLibre 3.0 + + SourceParameter { + styleId: "pointSource" + type: "geojson" + property var data: { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [-80, 45] + } + } + ] + } + } + `, + style, + "sourceParamSnippet" + ) + style.addParameter(sourceParam) + + let layerParam = Qt.createQmlObject(` + import MapLibre 3.0 + + LayerParameter { + styleId: "pointLayer" + type: "symbol" + property string source: "pointSource" + + layout: { + "icon-image": "locationImage", + "icon-size": 2.5 + } + } + `, + style, + "layerParamSnippet") + style.addParameter(layerParam) + + wait(1000) + + style.removeParameter(layerParam) + style.removeParameter(sourceParam) + style.removeParameter(imageParam) + wait(1000) + } } }