Add array support for icon-padding (#2845)
This commit is contained in:
@@ -413,6 +413,7 @@ list(APPEND INCLUDE_FILES
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/util/logging.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/util/lru_cache.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/util/noncopyable.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/util/padding.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/util/platform.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/util/premultiply.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/util/projection.hpp
|
||||
@@ -972,6 +973,7 @@ list(APPEND SRC_FILES
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/util/mat4.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/util/mat4.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/util/math.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/util/padding.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/util/premultiply.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/util/quaternion.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/util/rapidjson.cpp
|
||||
|
||||
@@ -613,6 +613,7 @@ MLN_CORE_SOURCE = [
|
||||
"src/mbgl/util/mat4.cpp",
|
||||
"src/mbgl/util/mat4.hpp",
|
||||
"src/mbgl/util/math.hpp",
|
||||
"src/mbgl/util/padding.cpp",
|
||||
"src/mbgl/util/premultiply.cpp",
|
||||
"src/mbgl/util/quaternion.cpp",
|
||||
"src/mbgl/util/quaternion.hpp",
|
||||
@@ -835,6 +836,7 @@ MLN_CORE_HEADERS = [
|
||||
"include/mbgl/util/lru_cache.hpp",
|
||||
"include/mbgl/util/monotonic_timer.hpp",
|
||||
"include/mbgl/util/noncopyable.hpp",
|
||||
"include/mbgl/util/padding.hpp",
|
||||
"include/mbgl/util/platform.hpp",
|
||||
"include/mbgl/util/premultiply.hpp",
|
||||
"include/mbgl/util/projection.hpp",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <mbgl/style/types.hpp>
|
||||
#include <mbgl/util/color.hpp>
|
||||
#include <mbgl/util/enum.hpp>
|
||||
#include <mbgl/util/padding.hpp>
|
||||
#include <mbgl/util/string.hpp>
|
||||
|
||||
#include <array>
|
||||
@@ -44,6 +45,11 @@ struct Converter<Color> {
|
||||
std::optional<Color> operator()(const Convertible& value, Error& error) const;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Converter<Padding> {
|
||||
std::optional<Padding> operator()(const Convertible& value, Error& error) const;
|
||||
};
|
||||
|
||||
template <size_t N>
|
||||
struct Converter<std::array<float, N>> {
|
||||
std::optional<std::array<float, N>> operator()(const Convertible& value, Error& error) const;
|
||||
|
||||
@@ -315,6 +315,11 @@ struct ValueFactory<Color> {
|
||||
static Value make(const Color& color) { return color.serialize(); }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ValueFactory<Padding> {
|
||||
static Value make(const Padding& padding) { return padding.serialize(); }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct ValueFactory<T, typename std::enable_if_t<(!std::is_enum_v<T> && !is_linear_container<T>::value)>> {
|
||||
static Value make(const T& arg) { return {arg}; }
|
||||
@@ -361,12 +366,14 @@ Value makeValue(T&& arg) {
|
||||
|
||||
template <typename T>
|
||||
StyleProperty makeStyleProperty(const PropertyValue<T>& value) {
|
||||
return value.match([](const Undefined&) -> StyleProperty { return {}; },
|
||||
[](const Color& c) -> StyleProperty { return {makeValue(c), StyleProperty::Kind::Expression}; },
|
||||
[](const PropertyExpression<T>& fn) -> StyleProperty {
|
||||
return {fn.getExpression().serialize(), StyleProperty::Kind::Expression};
|
||||
},
|
||||
[](const auto& t) -> StyleProperty { return {makeValue(t), StyleProperty::Kind::Constant}; });
|
||||
return value.match(
|
||||
[](const Undefined&) -> StyleProperty { return {}; },
|
||||
[](const Color& c) -> StyleProperty { return {makeValue(c), StyleProperty::Kind::Expression}; },
|
||||
[](const Padding& p) -> StyleProperty { return {makeValue(p), StyleProperty::Kind::Expression}; },
|
||||
[](const PropertyExpression<T>& fn) -> StyleProperty {
|
||||
return {fn.getExpression().serialize(), StyleProperty::Kind::Expression};
|
||||
},
|
||||
[](const auto& t) -> StyleProperty { return {makeValue(t), StyleProperty::Kind::Constant}; });
|
||||
}
|
||||
|
||||
inline StyleProperty makeStyleProperty(const TransitionOptions& value) {
|
||||
|
||||
@@ -42,6 +42,7 @@ std::unique_ptr<Expression> string(std::unique_ptr<Expression>, std::unique_ptr<
|
||||
std::unique_ptr<Expression> boolean(std::unique_ptr<Expression>, std::unique_ptr<Expression> def = nullptr);
|
||||
|
||||
std::unique_ptr<Expression> toColor(std::unique_ptr<Expression>, std::unique_ptr<Expression> def = nullptr);
|
||||
std::unique_ptr<Expression> toPadding(std::unique_ptr<Expression> value, std::unique_ptr<Expression> def = nullptr);
|
||||
std::unique_ptr<Expression> toString(std::unique_ptr<Expression>, std::unique_ptr<Expression> def = nullptr);
|
||||
std::unique_ptr<Expression> toFormatted(std::unique_ptr<Expression>, std::unique_ptr<Expression> def = nullptr);
|
||||
std::unique_ptr<Expression> toImage(std::unique_ptr<Expression>, std::unique_ptr<Expression> def = nullptr);
|
||||
|
||||
@@ -44,6 +44,12 @@ struct ColorType {
|
||||
bool operator==(const ColorType&) const { return true; }
|
||||
};
|
||||
|
||||
struct PaddingType {
|
||||
constexpr PaddingType() = default;
|
||||
std::string getName() const { return "padding"; }
|
||||
bool operator==(const PaddingType&) const { return true; }
|
||||
};
|
||||
|
||||
struct ObjectType {
|
||||
constexpr ObjectType() = default;
|
||||
std::string getName() const { return "object"; }
|
||||
@@ -85,6 +91,7 @@ constexpr NumberType Number;
|
||||
constexpr StringType String;
|
||||
constexpr BooleanType Boolean;
|
||||
constexpr ColorType Color;
|
||||
constexpr PaddingType Padding;
|
||||
constexpr ValueType Value;
|
||||
constexpr ObjectType Object;
|
||||
constexpr CollatorType Collator;
|
||||
@@ -99,6 +106,7 @@ using Type = variant<NullType,
|
||||
BooleanType,
|
||||
StringType,
|
||||
ColorType,
|
||||
PaddingType,
|
||||
ObjectType,
|
||||
ValueType,
|
||||
mapbox::util::recursive_wrapper<Array>,
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <mbgl/util/color.hpp>
|
||||
#include <mbgl/util/enum.hpp>
|
||||
#include <mbgl/util/feature.hpp>
|
||||
#include <mbgl/util/padding.hpp>
|
||||
#include <mbgl/util/variant.hpp>
|
||||
|
||||
#include <array>
|
||||
@@ -30,6 +31,7 @@ using ValueBase = variant<NullValue,
|
||||
Collator,
|
||||
Formatted,
|
||||
Image,
|
||||
Padding,
|
||||
mapbox::util::recursive_wrapper<std::vector<Value>>,
|
||||
mapbox::util::recursive_wrapper<std::unordered_map<std::string, Value>>>;
|
||||
struct Value : ValueBase {
|
||||
|
||||
@@ -52,9 +52,9 @@ public:
|
||||
const PropertyValue<bool>& getIconOptional() const;
|
||||
void setIconOptional(const PropertyValue<bool>&);
|
||||
|
||||
static PropertyValue<float> getDefaultIconPadding();
|
||||
const PropertyValue<float>& getIconPadding() const;
|
||||
void setIconPadding(const PropertyValue<float>&);
|
||||
static PropertyValue<Padding> getDefaultIconPadding();
|
||||
const PropertyValue<Padding>& getIconPadding() const;
|
||||
void setIconPadding(const PropertyValue<Padding>&);
|
||||
|
||||
static PropertyValue<AlignmentType> getDefaultIconPitchAlignment();
|
||||
const PropertyValue<AlignmentType>& getIconPitchAlignment() const;
|
||||
|
||||
@@ -125,6 +125,24 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Interpolator<Padding> {
|
||||
public:
|
||||
Padding operator()(const Padding& a, const Padding& b, const float t) const noexcept {
|
||||
return {interpolate(a.top, b.top, t),
|
||||
interpolate(a.right, b.right, t),
|
||||
interpolate(a.bottom, b.bottom, t),
|
||||
interpolate(a.left, b.left, t)};
|
||||
}
|
||||
|
||||
Padding operator()(const Padding& a, const Padding& b, const double t) const noexcept {
|
||||
return {interpolate(a.top, b.top, t),
|
||||
interpolate(a.right, b.right, t),
|
||||
interpolate(a.bottom, b.bottom, t),
|
||||
interpolate(a.left, b.left, t)};
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Interpolator<style::Rotation> {
|
||||
public:
|
||||
|
||||
72
include/mbgl/util/padding.hpp
Normal file
72
include/mbgl/util/padding.hpp
Normal file
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/util/feature.hpp>
|
||||
#include <array>
|
||||
#include <span>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
// A set of four numbers representing padding around a box.
|
||||
struct Padding {
|
||||
// Empty/zero padding on all sides.
|
||||
Padding() = default;
|
||||
|
||||
// Same padding on all sides.
|
||||
Padding(float value)
|
||||
: top(value),
|
||||
right(value),
|
||||
bottom(value),
|
||||
left(value) {}
|
||||
|
||||
Padding(float t_, float r_, float b_, float l_)
|
||||
: top(t_),
|
||||
right(r_),
|
||||
bottom(b_),
|
||||
left(l_) {}
|
||||
|
||||
// Padding values are in CSS order: top, right, bottom, left.
|
||||
Padding(const std::span<const float>& values) {
|
||||
switch (values.size()) {
|
||||
case 1:
|
||||
top = right = bottom = left = values[0];
|
||||
break;
|
||||
case 2:
|
||||
top = bottom = values[0];
|
||||
right = left = values[1];
|
||||
break;
|
||||
case 3:
|
||||
top = values[0];
|
||||
left = right = values[1];
|
||||
bottom = values[2];
|
||||
break;
|
||||
case 4:
|
||||
top = values[0];
|
||||
right = values[1];
|
||||
bottom = values[2];
|
||||
left = values[3];
|
||||
break;
|
||||
default:
|
||||
throw std::invalid_argument("Padding must have between 1 and 4 elements");
|
||||
}
|
||||
}
|
||||
|
||||
float top = 0;
|
||||
float right = 0;
|
||||
float bottom = 0;
|
||||
float left = 0;
|
||||
|
||||
explicit operator bool() const { return top != 0 || right != 0 || bottom != 0 || left != 0; }
|
||||
|
||||
bool operator==(const Padding&) const = default;
|
||||
|
||||
std::array<float, 4> toArray() const;
|
||||
|
||||
// Used by ValueFactory<Padding>::make()
|
||||
mbgl::Value serialize() const;
|
||||
};
|
||||
|
||||
inline Padding operator*(const Padding& padding, float scale) {
|
||||
return {padding.top * scale, padding.right * scale, padding.bottom * scale, padding.left * scale};
|
||||
}
|
||||
|
||||
} // namespace mbgl
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.9 KiB |
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"version": 8,
|
||||
"metadata": {
|
||||
"test": {
|
||||
"collisionDebug": true,
|
||||
"width": 240,
|
||||
"height": 100
|
||||
}
|
||||
},
|
||||
"sources": {
|
||||
"geojson": {
|
||||
"type": "geojson",
|
||||
"data": {
|
||||
"type": "FeatureCollection",
|
||||
"features": [{
|
||||
"type": "Feature",
|
||||
"properties": { },
|
||||
"geometry": { "type": "Point", "coordinates": [-60, 0] }
|
||||
}, {
|
||||
"type": "Feature",
|
||||
"properties": { "padding": 5 },
|
||||
"geometry": { "type": "Point", "coordinates": [-30, 0] }
|
||||
}, {
|
||||
"type": "Feature",
|
||||
"properties": { "padding": [-20, -5] },
|
||||
"geometry": { "type": "Point", "coordinates": [-0, 0] }
|
||||
}, {
|
||||
"type": "Feature",
|
||||
"properties": { "padding": [2, 2, -10] },
|
||||
"geometry": { "type": "Point", "coordinates": [30, 0] }
|
||||
}, {
|
||||
"type": "Feature",
|
||||
"properties": { "padding": [2, 10, -20, 2] },
|
||||
"geometry": { "type": "Point", "coordinates": [60, 0] }
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sprite": "local://sprites/2x",
|
||||
"layers": [{
|
||||
"id": "bg",
|
||||
"type": "background",
|
||||
"paint": {
|
||||
"background-color": "#ccc"
|
||||
}
|
||||
}, {
|
||||
"id": "symbolA",
|
||||
"type": "symbol",
|
||||
"source": "geojson",
|
||||
"layout": {
|
||||
"icon-image": "icon",
|
||||
"icon-padding": ["coalesce", ["get", "padding"], ["literal", [2]]]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.0 KiB |
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"version": 8,
|
||||
"metadata": {
|
||||
"test": {
|
||||
"collisionDebug": true,
|
||||
"width": 240,
|
||||
"height": 100
|
||||
}
|
||||
},
|
||||
"sources": {
|
||||
"geojson": {
|
||||
"type": "geojson",
|
||||
"data": {
|
||||
"type": "FeatureCollection",
|
||||
"features": [{
|
||||
"type": "Feature",
|
||||
"properties": { },
|
||||
"geometry": { "type": "Point", "coordinates": [-60, 0] }
|
||||
}, {
|
||||
"type": "Feature",
|
||||
"properties": { },
|
||||
"geometry": { "type": "Point", "coordinates": [-40, 0] }
|
||||
}, {
|
||||
"type": "Feature",
|
||||
"properties": { },
|
||||
"geometry": { "type": "Point", "coordinates": [-20, 0] }
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sprite": "local://sprites/2x",
|
||||
"layers": [{
|
||||
"id": "bg",
|
||||
"type": "background",
|
||||
"paint": {
|
||||
"background-color": "#ccc"
|
||||
}
|
||||
}, {
|
||||
"id": "symbolA",
|
||||
"type": "symbol",
|
||||
"source": "geojson",
|
||||
"layout": {
|
||||
"icon-image": "icon",
|
||||
"icon-padding": [10]
|
||||
}
|
||||
}, {
|
||||
"id": "symbolB",
|
||||
"type": "symbol",
|
||||
"source": "geojson",
|
||||
"layout": {
|
||||
"icon-image": "icon",
|
||||
"icon-padding": [2, 2, -20],
|
||||
"icon-offset": [120, 0]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"network": [
|
||||
[
|
||||
"probeNetwork - default - end",
|
||||
2,
|
||||
2094
|
||||
],
|
||||
[
|
||||
"probeNetwork - default - start",
|
||||
0,
|
||||
0
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"network": [
|
||||
[
|
||||
"probeNetwork - default - end",
|
||||
2,
|
||||
2094
|
||||
],
|
||||
[
|
||||
"probeNetwork - default - start",
|
||||
0,
|
||||
0
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "collection.hpp"
|
||||
#include "../style/formatted.hpp"
|
||||
|
||||
#include <mbgl/style/conversion/stringify.hpp>
|
||||
#include <mbgl/util/string.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
@@ -33,6 +34,16 @@ Result<jni::Local<jni::Object<>>> Converter<jni::Local<jni::Object<>>, Color>::o
|
||||
return jni::Make<jni::String>(env, value.stringify());
|
||||
}
|
||||
|
||||
Result<jni::Local<jni::Object<>>> Converter<jni::Local<jni::Object<>>, Padding>::operator()(
|
||||
jni::JNIEnv& env, const Padding& value) const {
|
||||
const auto values = value.toArray();
|
||||
auto result = jni::Array<jni::Float>::New(env, values.size());
|
||||
for (size_t i = 0; i < values.size(); i++) {
|
||||
result.Set(env, i, jni::Box(env, values[i]));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Result<jni::Local<jni::Object<>>> Converter<jni::Local<jni::Object<>>, style::expression::Formatted>::operator()(
|
||||
jni::JNIEnv& env, const style::expression::Formatted& value) const {
|
||||
return Formatted::New(env, value);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "conversion.hpp"
|
||||
|
||||
#include <mbgl/util/color.hpp>
|
||||
#include <mbgl/util/padding.hpp>
|
||||
#include <mbgl/util/enum.hpp>
|
||||
|
||||
#include <mbgl/style/expression/formatted.hpp>
|
||||
@@ -57,6 +58,11 @@ struct Converter<jni::Local<jni::Object<>>, Color> {
|
||||
Result<jni::Local<jni::Object<>>> operator()(jni::JNIEnv& env, const Color& value) const;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Converter<jni::Local<jni::Object<>>, Padding> {
|
||||
Result<jni::Local<jni::Object<>>> operator()(jni::JNIEnv& env, const Padding& value) const;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Converter<jni::Local<jni::Object<>>, style::expression::Formatted> {
|
||||
Result<jni::Local<jni::Object<>>> operator()(jni::JNIEnv& env, const style::expression::Formatted& value) const;
|
||||
|
||||
@@ -3688,6 +3688,20 @@ public class Expression {
|
||||
return new Expression("to-color", input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts input value to a padding.
|
||||
*
|
||||
* If the input is a number or an array of numbers padding is created following
|
||||
* the same pattern as CSS padding. See <a href="https://maplibre.org/maplibre-style-spec/types/#padding">Style specification</a>.
|
||||
* Otherwise, the expression is an error.
|
||||
*
|
||||
* @param input expression input
|
||||
* @return expression
|
||||
*/
|
||||
public static Expression toPadding(@NonNull Expression input) {
|
||||
return new Expression("to-padding", input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds input to named variables,
|
||||
* which can then be referenced in the result expression using {@link #var(String)} or {@link #var(Expression)}.
|
||||
|
||||
@@ -2050,18 +2050,18 @@ The unit is in density-independent pixels only for SDF sprites that were created
|
||||
/**
|
||||
* Size of additional area round the icon bounding box used for detecting symbol collisions.
|
||||
*
|
||||
* @param value a Float value
|
||||
* @return property wrapper around Float
|
||||
* @param value a Float[] value
|
||||
* @return property wrapper around Float[]
|
||||
*/
|
||||
public static PropertyValue<Float> iconPadding(Float value) {
|
||||
public static PropertyValue<Float[]> iconPadding(Float[] value) {
|
||||
return new LayoutPropertyValue<>("icon-padding", value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Size of additional area round the icon bounding box used for detecting symbol collisions.
|
||||
*
|
||||
* @param value a Float value
|
||||
* @return property wrapper around Float
|
||||
* @param value a Float[] value
|
||||
* @return property wrapper around Float[]
|
||||
*/
|
||||
public static PropertyValue<Expression> iconPadding(Expression value) {
|
||||
return new LayoutPropertyValue<>("icon-padding", value);
|
||||
|
||||
@@ -316,13 +316,13 @@ public class SymbolLayer extends Layer {
|
||||
/**
|
||||
* Get the IconPadding property
|
||||
*
|
||||
* @return property wrapper value around Float
|
||||
* @return property wrapper value around Float[]
|
||||
*/
|
||||
@NonNull
|
||||
@SuppressWarnings("unchecked")
|
||||
public PropertyValue<Float> getIconPadding() {
|
||||
public PropertyValue<Float[]> getIconPadding() {
|
||||
checkThread();
|
||||
return (PropertyValue<Float>) new PropertyValue("icon-padding", nativeGetIconPadding());
|
||||
return (PropertyValue<Float[]>) new PropertyValue("icon-padding", nativeGetIconPadding());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1124,6 +1124,14 @@ class ExpressionTest {
|
||||
Assert.assertTrue("expression should match", Arrays.deepEquals(expected, actual))
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun testToPadding() {
|
||||
val expected = arrayOf<Any>("to-padding", "value")
|
||||
val actual = Expression.toPadding(Expression.literal("value")).toArray()
|
||||
Assert.assertTrue("expression should match", Arrays.deepEquals(expected, actual))
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun testLet() {
|
||||
|
||||
@@ -55,11 +55,13 @@ import static org.maplibre.android.style.expressions.Expression.stop;
|
||||
import static org.maplibre.android.style.expressions.Expression.string;
|
||||
import static org.maplibre.android.style.expressions.Expression.switchCase;
|
||||
import static org.maplibre.android.style.expressions.Expression.toColor;
|
||||
import static org.maplibre.android.style.expressions.Expression.toPadding;
|
||||
import static org.maplibre.android.style.expressions.Expression.zoom;
|
||||
import static org.maplibre.android.style.layers.PropertyFactory.circleColor;
|
||||
import static org.maplibre.android.style.layers.PropertyFactory.fillAntialias;
|
||||
import static org.maplibre.android.style.layers.PropertyFactory.fillColor;
|
||||
import static org.maplibre.android.style.layers.PropertyFactory.fillOutlineColor;
|
||||
import static org.maplibre.android.style.layers.PropertyFactory.iconPadding;
|
||||
import static org.maplibre.android.style.layers.PropertyFactory.textColor;
|
||||
import static org.maplibre.android.style.layers.PropertyFactory.textField;
|
||||
import static org.maplibre.android.testapp.action.MapLibreMapAction.invoke;
|
||||
@@ -779,6 +781,93 @@ public class ExpressionTest extends EspressoTest {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToPaddingExpression() {
|
||||
validateTestSetup();
|
||||
invoke(maplibreMap, (uiController, maplibreMap) -> {
|
||||
LatLng latLng = new LatLng(51, 17);
|
||||
maplibreMap.getStyle().addSource(
|
||||
new GeoJsonSource("source", Point.fromLngLat(latLng.getLongitude(), latLng.getLatitude()))
|
||||
);
|
||||
|
||||
SymbolLayer layer = new SymbolLayer("layer", "source");
|
||||
maplibreMap.getStyle().addLayer(layer);
|
||||
|
||||
// Automatic usage with iconPadding property, analogous to testGetExpressionWrapping()
|
||||
{
|
||||
Expression input = get("value");
|
||||
layer.setProperties(iconPadding(input));
|
||||
|
||||
Expression expectedOuput = toPadding(input);
|
||||
Expression output = layer.getIconPadding().getExpression();
|
||||
assertNotNull(output);
|
||||
assertArrayEquals("Expression should match", expectedOuput.toArray(), output.toArray());
|
||||
}
|
||||
|
||||
// Same within interpolate expression
|
||||
{
|
||||
Expression input = interpolate(
|
||||
exponential(0.5f), zoom(),
|
||||
stop(-0.1, get("value")),
|
||||
stop(0, get("value"))
|
||||
);
|
||||
layer.setProperties(iconPadding(input));
|
||||
|
||||
Expression expectedOutput = interpolate(
|
||||
exponential(0.5f), zoom(),
|
||||
stop(-0.1, toPadding(get("value"))),
|
||||
stop(0, toPadding(get("value")))
|
||||
);
|
||||
Expression output = layer.getIconPadding().getExpression();
|
||||
assertNotNull(output);
|
||||
assertArrayEquals("Expression should match", expectedOutput.toArray(), output.toArray());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToPaddingResult() {
|
||||
validateTestSetup();
|
||||
invoke(maplibreMap, (uiController, maplibreMap) -> {
|
||||
LatLng latLng = new LatLng(51, 17);
|
||||
maplibreMap.getStyle().addSource(
|
||||
new GeoJsonSource("source", Point.fromLngLat(latLng.getLongitude(), latLng.getLatitude()))
|
||||
);
|
||||
|
||||
SymbolLayer layer = new SymbolLayer("layer", "source");
|
||||
maplibreMap.getStyle().addLayer(layer);
|
||||
|
||||
Expression input = Expression.toPadding(Expression.literal(new Float[] { 7.5f, 10.0f, 1.0f}));
|
||||
layer.setProperties(iconPadding(input));
|
||||
|
||||
assertNull("Expression should be null", layer.getIconPadding().getExpression());
|
||||
assertArrayEquals(
|
||||
"Padding value should match",
|
||||
new Float[] { 7.5f, 10.0f, 1.0f, 10.0f },
|
||||
layer.getIconPadding().getValue());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToPaddingError() {
|
||||
validateTestSetup();
|
||||
invoke(maplibreMap, (uiController, maplibreMap) -> {
|
||||
LatLng latLng = new LatLng(51, 17);
|
||||
maplibreMap.getStyle().addSource(
|
||||
new GeoJsonSource("source", Point.fromLngLat(latLng.getLongitude(), latLng.getLatitude()))
|
||||
);
|
||||
|
||||
SymbolLayer layer = new SymbolLayer("layer", "source");
|
||||
maplibreMap.getStyle().addLayer(layer);
|
||||
|
||||
Expression input = toPadding(literal("invalid"));
|
||||
layer.setProperties(iconPadding(input));
|
||||
|
||||
assertNull("Expression should be null", layer.getIconPadding().getExpression());
|
||||
assertNull("Padding value should be null", layer.getIconPadding().getValue());
|
||||
});
|
||||
}
|
||||
|
||||
private void setupStyle() {
|
||||
invoke(maplibreMap, (uiController, maplibreMap) -> {
|
||||
// Add a source
|
||||
|
||||
@@ -422,11 +422,24 @@ public class SymbolLayerTest extends BaseLayerTest {
|
||||
assertNull(layer.getIconPadding().getValue());
|
||||
|
||||
// Set and Get
|
||||
Float propertyValue = 0.3f;
|
||||
Float[] propertyValue = {2.0f, 2.0f, 2.0f, 2.0f};
|
||||
layer.setProperties(iconPadding(propertyValue));
|
||||
assertEquals(layer.getIconPadding().getValue(), propertyValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
public void testIconPaddingAsExpression() {
|
||||
Timber.i("icon-padding-expression");
|
||||
assertNotNull(layer);
|
||||
assertNull(layer.getIconPadding().getExpression());
|
||||
|
||||
// Set and Get
|
||||
Expression expression = toPadding(Expression.get("undefined"));
|
||||
layer.setProperties(iconPadding(expression));
|
||||
assertEquals(layer.getIconPadding().getExpression(), expression);
|
||||
}
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
public void testIconKeepUprightAsConstant() {
|
||||
|
||||
@@ -119,6 +119,8 @@ global.propertyType = function propertyType(property) {
|
||||
return 'String';
|
||||
case 'color':
|
||||
return 'String';
|
||||
case 'padding':
|
||||
return 'Float[]'
|
||||
case 'array':
|
||||
return `${propertyType({type:property.value, name: property.name})}[]`;
|
||||
default:
|
||||
@@ -193,6 +195,8 @@ global.propertyNativeType = function (property) {
|
||||
return `${camelize(property.name)}Type`;
|
||||
case 'color':
|
||||
return `Color`;
|
||||
case 'padding':
|
||||
return 'Padding';
|
||||
case 'array':
|
||||
if (property.length) {
|
||||
return `std::array<${propertyType({type: property.value})}, ${property.length}>`;
|
||||
@@ -229,6 +233,8 @@ global.defaultExpressionJava = function(property) {
|
||||
return "string";
|
||||
case 'color':
|
||||
return 'toColor';
|
||||
case 'padding':
|
||||
return 'toPadding';
|
||||
case 'array':
|
||||
return "array";
|
||||
default: return "string";
|
||||
@@ -261,6 +267,8 @@ global.defaultValueJava = function(property) {
|
||||
return snakeCaseUpper(property.name) + "_" + snakeCaseUpper(Object.keys(property.values)[0]);
|
||||
case 'color':
|
||||
return '"rgba(255,128,0,0.7)"';
|
||||
case 'padding':
|
||||
return '{2.0f, 2.0f, 2.0f, 2.0f}';
|
||||
case 'array':
|
||||
switch (property.value) {
|
||||
case 'string':
|
||||
|
||||
@@ -175,6 +175,16 @@ global.testImplementation = function (property, layerType, isFunction) {
|
||||
|
||||
global.objCTestValue = function (property, layerType, arraysAsStructs, indent) {
|
||||
let propertyName = originalPropertyName(property);
|
||||
|
||||
const paddingTestValue = () => {
|
||||
if (arraysAsStructs) {
|
||||
let iosValue = '[NSValue valueWithUIEdgeInsets:UIEdgeInsetsMake(1, 1, 1, 1)]'.indent(indent * 4);
|
||||
let macosValue = '[NSValue valueWithEdgeInsets:NSEdgeInsetsMake(1, 1, 1, 1)]'.indent(indent * 4);
|
||||
return `@"%@",\n#if TARGET_OS_IPHONE\n${iosValue}\n#else\n${macosValue}\n#endif\n${''.indent((indent - 1) * 4)}`;
|
||||
}
|
||||
return '@"{1, 1, 1, 1}"';
|
||||
}
|
||||
|
||||
switch (property.type) {
|
||||
case 'boolean':
|
||||
return property.default ? '@"false"' : '@"true"';
|
||||
@@ -196,6 +206,8 @@ global.objCTestValue = function (property, layerType, arraysAsStructs, indent) {
|
||||
return `@"'${_.last(_.keys(property.values))}'"`;
|
||||
case 'color':
|
||||
return '@"%@", [MLNColor redColor]';
|
||||
case 'padding':
|
||||
return paddingTestValue();
|
||||
case 'array':
|
||||
switch (arrayType(property)) {
|
||||
case 'dasharray':
|
||||
@@ -203,12 +215,7 @@ global.objCTestValue = function (property, layerType, arraysAsStructs, indent) {
|
||||
case 'font':
|
||||
return `@"{'${_.startCase(propertyName)}', '${_.startCase(_.reverse(propertyName.split('')).join(''))}'}"`;
|
||||
case 'padding': {
|
||||
if (arraysAsStructs) {
|
||||
let iosValue = '[NSValue valueWithUIEdgeInsets:UIEdgeInsetsMake(1, 1, 1, 1)]'.indent(indent * 4);
|
||||
let macosValue = '[NSValue valueWithEdgeInsets:NSEdgeInsetsMake(1, 1, 1, 1)]'.indent(indent * 4);
|
||||
return `@"%@",\n#if TARGET_OS_IPHONE\n${iosValue}\n#else\n${macosValue}\n#endif\n${''.indent((indent - 1) * 4)}`;
|
||||
}
|
||||
return '@"{1, 1, 1, 1}"';
|
||||
return paddingTestValue();
|
||||
}
|
||||
case 'offset':
|
||||
case 'translate': {
|
||||
@@ -261,6 +268,8 @@ global.mbglTestValue = function (property, layerType) {
|
||||
}
|
||||
case 'color':
|
||||
return '{ 1, 0, 0, 1 }';
|
||||
case 'padding':
|
||||
return '{ 1, 1, 1, 1 }';
|
||||
case 'array':
|
||||
switch (arrayType(property)) {
|
||||
case 'dasharray':
|
||||
@@ -335,6 +344,8 @@ global.testHelperMessage = function (property, layerType, isFunction) {
|
||||
return `testEnum${fnSuffix}:${objCEnum} type:@encode(${objCType})`;
|
||||
case 'color':
|
||||
return 'testColor' + fnSuffix;
|
||||
case 'padding':
|
||||
return 'testPaddingType' + fnSuffix;
|
||||
case 'array':
|
||||
switch (arrayType(property)) {
|
||||
case 'dasharray':
|
||||
@@ -517,6 +528,8 @@ global.describeType = function (property) {
|
||||
return '`MLN' + camelize(property.name) + '`';
|
||||
case 'color':
|
||||
return '`UIColor`';
|
||||
case 'padding':
|
||||
return '`UIEdgeInsets`';
|
||||
case 'array':
|
||||
switch (arrayType(property)) {
|
||||
case 'padding':
|
||||
@@ -540,7 +553,7 @@ global.describeType = function (property) {
|
||||
}
|
||||
|
||||
global.describeValue = function (value, property, layerType) {
|
||||
if (Array.isArray(value) && property.type !== 'array' && property.type !== 'enum') {
|
||||
if (Array.isArray(value) && property.type !== 'array' && property.type !== 'enum' && property.type !== 'padding') {
|
||||
switch (value[0]) {
|
||||
case 'interpolate': {
|
||||
let curveType = value[1][0];
|
||||
@@ -553,6 +566,21 @@ global.describeValue = function (value, property, layerType) {
|
||||
}
|
||||
}
|
||||
|
||||
const describePadding = () => {
|
||||
let units = property.units || '';
|
||||
if (units) {
|
||||
units = ` ${units}`.replace(/pixel/, 'point');
|
||||
}
|
||||
|
||||
if (value.every(num => num === 0)) {
|
||||
return 'an `NSValue` object containing `UIEdgeInsetsZero`';
|
||||
}
|
||||
if (value.length === 1) {
|
||||
return 'an `NSValue` object containing a `UIEdgeInsets` struct set to' + ` ${formatNumber(value[0])}${units} on all sides`;
|
||||
}
|
||||
return 'an `NSValue` object containing a `UIEdgeInsets` struct set to' + ` ${formatNumber(value[0])}${units} on the top, ${formatNumber(value[3])}${units} on the left, ${formatNumber(value[2])}${units} on the bottom, and ${formatNumber(value[1])}${units} on the right`;
|
||||
}
|
||||
|
||||
switch (property.type) {
|
||||
case 'boolean':
|
||||
return value ? '`YES`' : '`NO`';
|
||||
@@ -595,6 +623,10 @@ global.describeValue = function (value, property, layerType) {
|
||||
return '`UIColor.whiteColor`';
|
||||
}
|
||||
return 'a `UIColor`' + ` object whose RGB value is ${formatNumber(color.r)}, ${formatNumber(color.g)}, ${formatNumber(color.b)} and whose alpha value is ${formatNumber(color.a)}`;
|
||||
|
||||
case 'padding':
|
||||
return describePadding();
|
||||
|
||||
case 'array':
|
||||
let units = property.units || '';
|
||||
if (units) {
|
||||
@@ -602,10 +634,7 @@ global.describeValue = function (value, property, layerType) {
|
||||
}
|
||||
switch (arrayType(property)) {
|
||||
case 'padding':
|
||||
if (value[0] === 0 && value[1] === 0 && value[2] === 0 && value[3] === 0) {
|
||||
return 'an `NSValue` object containing `UIEdgeInsetsZero`';
|
||||
}
|
||||
return 'an `NSValue` object containing a `UIEdgeInsets` struct set to' + ` ${formatNumber(value[0])}${units} on the top, ${formatNumber(value[3])}${units} on the left, ${formatNumber(value[2])}${units} on the bottom, and ${formatNumber(value[1])}${units} on the right`;
|
||||
return describePadding();
|
||||
case 'offset':
|
||||
case 'translate':
|
||||
return 'an `NSValue` object containing a `CGVector` struct set to' + ` ${formatNumber(value[0])}${units} rightward and ${formatNumber(value[1])}${units} downward`;
|
||||
@@ -653,6 +682,8 @@ global.propertyType = function (property) {
|
||||
return 'NSValue *';
|
||||
case 'color':
|
||||
return 'MLNColor *';
|
||||
case 'padding':
|
||||
return 'NSValue *';
|
||||
case 'array':
|
||||
switch (arrayType(property)) {
|
||||
case 'dasharray':
|
||||
@@ -701,6 +732,8 @@ global.valueTransformerArguments = function (property) {
|
||||
return [mbglType(property), 'NSValue *', mbglType(property), `MLN${camelize(property.name)}`];
|
||||
case 'color':
|
||||
return ['mbgl::Color', objCType];
|
||||
case 'padding':
|
||||
return ['mbgl::Padding', objCType];
|
||||
case 'array':
|
||||
switch (arrayType(property)) {
|
||||
case 'dasharray':
|
||||
@@ -756,6 +789,8 @@ global.mbglType = function(property) {
|
||||
}
|
||||
case 'color':
|
||||
return 'mbgl::Color';
|
||||
case 'padding':
|
||||
return 'mbgl::Padding';
|
||||
case 'array':
|
||||
switch (arrayType(property)) {
|
||||
case 'dasharray':
|
||||
|
||||
@@ -201,7 +201,7 @@ class MLNStyleValueTransformer {
|
||||
}
|
||||
}
|
||||
|
||||
// Padding
|
||||
// Padding as array<float, 4>
|
||||
void getMBGLValue(id rawValue, std::array<float, 4> &mbglValue) {
|
||||
if ([rawValue isKindOfClass:[NSValue class]]) {
|
||||
mbglValue = [rawValue mgl_paddingArrayValue];
|
||||
@@ -215,6 +215,27 @@ class MLNStyleValueTransformer {
|
||||
}
|
||||
}
|
||||
|
||||
// Padding type (supports numbers and float arrays w/ sizes 1 to 4)
|
||||
void getMBGLValue(id rawValue, mbgl::Padding &mbglValue) {
|
||||
if ([rawValue isKindOfClass:[NSNumber class]]) {
|
||||
NSNumber *number = (NSNumber *)rawValue;
|
||||
mbglValue = mbgl::Padding(number.floatValue);
|
||||
} else if ([rawValue isKindOfClass:[NSArray class]]) {
|
||||
NSArray *array = (NSArray *)rawValue;
|
||||
if (array.count < 1 || array.count > 4) {
|
||||
[NSException raise:NSInvalidArgumentException
|
||||
format:@"Padding array should have from 1 to 4 elements."];
|
||||
}
|
||||
std::array<float, 4> values;
|
||||
for (size_t i = 0; i < array.count; ++i) {
|
||||
getMBGLValue(array[i], values[i]);
|
||||
}
|
||||
mbglValue = mbgl::Padding(std::span<float>(values.begin(), array.count));
|
||||
} else if ([rawValue isKindOfClass:[NSValue class]]) {
|
||||
mbglValue = mbgl::Padding([rawValue mgl_paddingArrayValue]);
|
||||
}
|
||||
}
|
||||
|
||||
// Color
|
||||
void getMBGLValue(MLNColor *rawValue, mbgl::Color &mbglValue) { mbglValue = rawValue.mgl_color; }
|
||||
|
||||
@@ -285,11 +306,16 @@ class MLNStyleValueTransformer {
|
||||
return [NSValue mgl_valueWithOffsetArray:mbglStopValue];
|
||||
}
|
||||
|
||||
// Padding
|
||||
// Padding as array<float, 4>
|
||||
static NSValue *toMLNRawStyleValue(const std::array<float, 4> &mbglStopValue) {
|
||||
return [NSValue mgl_valueWithPaddingArray:mbglStopValue];
|
||||
}
|
||||
|
||||
// Padding type
|
||||
static NSValue *toMLNRawStyleValue(const mbgl::Padding &mbglStopValue) {
|
||||
return [NSValue mgl_valueWithPaddingArray:mbglStopValue.toArray()];
|
||||
}
|
||||
|
||||
// Color
|
||||
static MLNColor *toMLNRawStyleValue(const mbgl::Color mbglStopValue) {
|
||||
return [MLNColor mgl_colorWithColor:mbglStopValue];
|
||||
|
||||
@@ -45,6 +45,7 @@ type::Type parseType(v8::Local<v8::Object> type) {
|
||||
{"boolean", type::Boolean},
|
||||
{"object", type::Object},
|
||||
{"color", type::Color},
|
||||
{"padding", type::Padding},
|
||||
{"value", type::Value},
|
||||
{"formatted", type::Formatted},
|
||||
{"number-format", type::String},
|
||||
@@ -216,6 +217,13 @@ struct ToValue {
|
||||
static_cast<double>(color.a)});
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> operator()(const mbgl::Padding& padding) {
|
||||
return operator()(std::vector<Value>{static_cast<double>(padding.top),
|
||||
static_cast<double>(padding.right),
|
||||
static_cast<double>(padding.bottom),
|
||||
static_cast<double>(padding.left)});
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> operator()(const std::unordered_map<std::string, Value>& map) {
|
||||
Nan::EscapableHandleScope scope;
|
||||
v8::Local<v8::Object> result = Nan::New<v8::Object>();
|
||||
|
||||
@@ -56,6 +56,8 @@ function expressionType(property) {
|
||||
return 'StringType';
|
||||
case 'color':
|
||||
return `ColorType`;
|
||||
case 'padding':
|
||||
return `PaddingType`;
|
||||
case 'formatted':
|
||||
return `FormattedType`;
|
||||
case 'array':
|
||||
@@ -104,6 +106,8 @@ function evaluatedType(property) {
|
||||
return (isLightProperty(property) ? 'Light' : '') + `${camelize(property.name)}Type`;
|
||||
case 'color':
|
||||
return `Color`;
|
||||
case 'padding':
|
||||
return `Padding`;
|
||||
case 'array':
|
||||
if (property.length) {
|
||||
return `std::array<${evaluatedType({type: property.value, name: property.name})}, ${property.length}>`;
|
||||
@@ -248,6 +252,7 @@ function defaultValue(property) {
|
||||
return `{ ${color} }`;
|
||||
}
|
||||
case 'array':
|
||||
case 'padding':
|
||||
const defaults = (property.default || []).map((/** @type {any} **/ e) => defaultValue({ type: property.value, default: e }));
|
||||
if (property.length) {
|
||||
return `{{${defaults.join(', ')}}}`;
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
import referenceSpec from './style-spec-reference/v8.json' with { type: "json" };
|
||||
|
||||
// https://github.com/maplibre/maplibre-native/issues/2368
|
||||
referenceSpec['layout_symbol']['icon-padding']['type'] = 'number';
|
||||
// @ts-ignore
|
||||
referenceSpec['layout_symbol']['icon-padding']['default'] = 2;
|
||||
|
||||
// https://github.com/maplibre/maplibre-native/issues/2754
|
||||
referenceSpec['layout_symbol']['icon-padding']['property-type'] = 'data constant';
|
||||
|
||||
/** @type {any} */
|
||||
let modifiedReferenceSpec = referenceSpec;
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ SymbolInstance::SymbolInstance(Anchor& anchor_,
|
||||
const SymbolPlacementType textPlacement,
|
||||
const std::array<float, 2>& textOffset_,
|
||||
const float iconBoxScale,
|
||||
const float iconPadding,
|
||||
const Padding iconPadding,
|
||||
const std::array<float, 2>& iconOffset_,
|
||||
const RefIndexedSubfeature& indexedFeature,
|
||||
const std::size_t layoutFeatureIndex_,
|
||||
|
||||
@@ -110,7 +110,7 @@ public:
|
||||
style::SymbolPlacementType textPlacement,
|
||||
const std::array<float, 2>& textOffset,
|
||||
float iconBoxScale,
|
||||
float iconPadding,
|
||||
Padding iconPadding,
|
||||
const std::array<float, 2>& iconOffset,
|
||||
const RefIndexedSubfeature& indexedFeature,
|
||||
std::size_t layoutFeatureIndex,
|
||||
|
||||
@@ -566,7 +566,7 @@ void SymbolLayout::addFeature(const std::size_t layoutFeatureIndex,
|
||||
const float iconBoxScale = tilePixelRatio * layoutIconSize;
|
||||
const float symbolSpacing = tilePixelRatio * layout->get<SymbolSpacing>();
|
||||
const float textPadding = layout->get<TextPadding>() * tilePixelRatio;
|
||||
const float iconPadding = layout->get<IconPadding>() * tilePixelRatio;
|
||||
const Padding iconPadding = layout->evaluate<IconPadding>(zoom, feature, canonicalID) * tilePixelRatio;
|
||||
const float textMaxAngle = util::deg2radf(layout->get<TextMaxAngle>());
|
||||
const float iconRotation = layout->evaluate<IconRotate>(zoom, feature, canonicalID);
|
||||
const float textRotation = layout->evaluate<TextRotate>(zoom, feature, canonicalID);
|
||||
|
||||
@@ -113,6 +113,28 @@ std::optional<Color> Converter<Color>::operator()(const Convertible& value, Erro
|
||||
return *color;
|
||||
}
|
||||
|
||||
std::optional<Padding> Converter<Padding>::operator()(const Convertible& value, Error& error) const {
|
||||
std::optional<Padding> result;
|
||||
if (isArray(value)) {
|
||||
if (arrayLength(value) > 0 && arrayLength(value) <= 4) {
|
||||
auto vector = Converter<std::vector<float>>{}(value, error);
|
||||
if (vector) {
|
||||
result = Padding(*vector);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::optional<float> number = toNumber(value);
|
||||
if (number) {
|
||||
result = Padding(*number);
|
||||
}
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
error.message = "value must be a number or an array of numbers (between 1 and 4 elements)";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
std::optional<std::array<float, N>> Converter<std::array<float, N>>::operator()(const Convertible& value,
|
||||
Error& error) const {
|
||||
|
||||
@@ -131,6 +131,9 @@ template std::optional<PropertyExpression<LineJoinType>> convertFunctionToExpres
|
||||
Error&,
|
||||
bool);
|
||||
template std::optional<PropertyExpression<Color>> convertFunctionToExpression<Color>(const Convertible&, Error&, bool);
|
||||
template std::optional<PropertyExpression<Padding>> convertFunctionToExpression<Padding>(const Convertible&,
|
||||
Error&,
|
||||
bool);
|
||||
template std::optional<PropertyExpression<Position>> convertFunctionToExpression<Position>(const Convertible&,
|
||||
Error&,
|
||||
bool);
|
||||
@@ -209,6 +212,7 @@ enum class FunctionType {
|
||||
bool interpolatable(type::Type type) noexcept {
|
||||
return type.match([&](const type::NumberType&) { return true; },
|
||||
[&](const type::ColorType&) { return true; },
|
||||
[&](const type::PaddingType&) { return true; },
|
||||
[&](const type::Array& array) { return array.N && array.itemType == type::Number; },
|
||||
[&](const auto&) { return false; });
|
||||
}
|
||||
@@ -246,6 +250,13 @@ std::optional<std::unique_ptr<Expression>> convertLiteral(type::Type type,
|
||||
}
|
||||
return literal(*result);
|
||||
},
|
||||
[&](const type::PaddingType&) -> std::optional<std::unique_ptr<Expression>> {
|
||||
auto result = convert<Padding>(value, error);
|
||||
if (!result) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return literal(*result);
|
||||
},
|
||||
[&](const type::Array& array) -> std::optional<std::unique_ptr<Expression>> {
|
||||
if (!isArray(value)) {
|
||||
error.message = "value must be an array";
|
||||
@@ -779,6 +790,9 @@ std::optional<std::unique_ptr<Expression>> convertFunctionToExpression(type::Typ
|
||||
[&](const type::ColorType&) -> std::optional<std::unique_ptr<Expression>> {
|
||||
return toColor(get(literal(*property)), defaultExpr());
|
||||
},
|
||||
[&](const type::PaddingType&) -> std::optional<std::unique_ptr<Expression>> {
|
||||
return toPadding(get(literal(*property)), defaultExpr());
|
||||
},
|
||||
[&](const type::Array& array) -> std::optional<std::unique_ptr<Expression>> {
|
||||
return assertion(array, get(literal(*property)), defaultExpr());
|
||||
},
|
||||
|
||||
@@ -78,6 +78,8 @@ template std::optional<PropertyValue<Color>> Converter<PropertyValue<Color>>::op
|
||||
conversion::Error&,
|
||||
bool,
|
||||
bool) const;
|
||||
template std::optional<PropertyValue<Padding>> Converter<PropertyValue<Padding>>::operator()(
|
||||
conversion::Convertible const&, conversion::Error&, bool, bool) const;
|
||||
template std::optional<PropertyValue<std::string>> Converter<PropertyValue<std::string>>::operator()(
|
||||
conversion::Convertible const&, conversion::Error&, bool, bool) const;
|
||||
template std::optional<PropertyValue<std::vector<std::string>>>
|
||||
|
||||
@@ -85,6 +85,11 @@ void stringify(Writer& writer, const std::array<double, 3>& v) {
|
||||
writer.EndArray();
|
||||
}
|
||||
|
||||
template <class Writer>
|
||||
void stringify(Writer& writer, const Padding& v) {
|
||||
stringify(writer, v.toArray());
|
||||
}
|
||||
|
||||
template <class Writer>
|
||||
void stringify(Writer&, const Value&);
|
||||
|
||||
|
||||
@@ -33,7 +33,8 @@ std::optional<std::string> checkSubtype(const Type& expected, const Type& t) {
|
||||
[&](const ValueType&) -> std::optional<std::string> {
|
||||
if (t.is<ValueType>()) return {};
|
||||
|
||||
const Type members[] = {Null, Boolean, Number, String, Object, Color, Formatted, Image, Array(Value)};
|
||||
const Type members[] = {
|
||||
Null, Boolean, Number, String, Object, Color, Padding, Formatted, Image, Array(Value)};
|
||||
|
||||
for (const auto& member : members) {
|
||||
const auto err = checkSubtype(member, t);
|
||||
|
||||
@@ -72,6 +72,32 @@ EvaluationResult toColor(const Value& colorValue) {
|
||||
});
|
||||
}
|
||||
|
||||
EvaluationResult toPadding(const Value& paddingValue) {
|
||||
return paddingValue.match(
|
||||
[](const Padding& padding) -> EvaluationResult { return padding; },
|
||||
[&](const std::vector<Value>& components) -> EvaluationResult {
|
||||
const std::size_t len = components.size();
|
||||
const bool isNumeric = std::all_of(components.begin(), components.end(), [](const Value& item) -> bool {
|
||||
return item.template is<double>();
|
||||
});
|
||||
if ((len >= 1 && len <= 4) && isNumeric) {
|
||||
float componentsAsFloats[4] = {0};
|
||||
for (std::size_t i = 0; i < len; i++) {
|
||||
componentsAsFloats[i] = static_cast<float>(components[i].template get<double>());
|
||||
}
|
||||
return Padding(std::span<float>(componentsAsFloats, len));
|
||||
} else {
|
||||
return EvaluationError{"Invalid padding value " + stringify(paddingValue) +
|
||||
": expected an array containing from one to four "
|
||||
"numeric values."};
|
||||
}
|
||||
},
|
||||
[](const double number) -> EvaluationResult { return Padding(static_cast<float>(number)); },
|
||||
[&](const auto&) -> EvaluationResult {
|
||||
return EvaluationError{"Could not parse padding from value '" + stringify(paddingValue) + "'"};
|
||||
});
|
||||
}
|
||||
|
||||
EvaluationResult toFormatted(const Value& formattedValue) {
|
||||
return Formatted(toString(formattedValue).c_str());
|
||||
}
|
||||
@@ -89,6 +115,8 @@ CoerceFunction getCoerceFunction(const type::Type& t) {
|
||||
return toBoolean;
|
||||
} else if (t.is<type::ColorType>()) {
|
||||
return toColor;
|
||||
} else if (t.is<type::PaddingType>()) {
|
||||
return toPadding;
|
||||
} else if (t.is<type::NumberType>()) {
|
||||
return toNumber;
|
||||
} else if (t.is<type::StringType>()) {
|
||||
@@ -135,6 +163,7 @@ mbgl::Value Coercion::serialize() const {
|
||||
std::string Coercion::getOperator() const {
|
||||
auto s = getType().match([](const type::BooleanType&) -> std::string_view { return "to-boolean"; },
|
||||
[](const type::ColorType&) -> std::string_view { return "to-color"; },
|
||||
[](const type::PaddingType&) -> std::string_view { return "to-padding"; },
|
||||
[](const type::NumberType&) -> std::string_view { return "to-number"; },
|
||||
[](const type::StringType&) -> std::string_view { return "to-string"; },
|
||||
[](const auto&) noexcept -> std::string_view {
|
||||
@@ -148,6 +177,7 @@ using namespace mbgl::style::conversion;
|
||||
ParseResult Coercion::parse(const Convertible& value, ParsingContext& ctx) {
|
||||
static std::unordered_map<std::string, type::Type> types{{"to-boolean", type::Boolean},
|
||||
{"to-color", type::Color},
|
||||
{"to-padding", type::Padding},
|
||||
{"to-number", type::Number},
|
||||
{"to-string", type::String}};
|
||||
|
||||
@@ -170,8 +200,9 @@ ParseResult Coercion::parse(const Convertible& value, ParsingContext& ctx) {
|
||||
|
||||
/**
|
||||
* Special form for error-coalescing coercion expressions "to-number",
|
||||
* "to-color". Since these coercions can fail at runtime, they accept
|
||||
* multiple arguments, only evaluating one at a time until one succeeds.
|
||||
* "to-color", "to-padding". Since these coercions can fail at runtime,
|
||||
* they accept multiple arguments, only evaluating one at a time until
|
||||
* one succeeds.
|
||||
*/
|
||||
|
||||
std::vector<std::unique_ptr<Expression>> parsed;
|
||||
|
||||
@@ -117,6 +117,10 @@ std::unique_ptr<Expression> toColor(std::unique_ptr<Expression> value, std::uniq
|
||||
return coercion(type::Color, std::move(value), std::move(def));
|
||||
}
|
||||
|
||||
std::unique_ptr<Expression> toPadding(std::unique_ptr<Expression> value, std::unique_ptr<Expression> def) {
|
||||
return coercion(type::Padding, std::move(value), std::move(def));
|
||||
}
|
||||
|
||||
std::unique_ptr<Expression> toString(std::unique_ptr<Expression> value, std::unique_ptr<Expression> def) {
|
||||
return coercion(type::String, std::move(value), std::move(def));
|
||||
}
|
||||
|
||||
@@ -232,6 +232,10 @@ ParseResult createInterpolate(type::Type type,
|
||||
return ParseResult(
|
||||
std::make_unique<InterpolateImpl<Color>>(type, interpolator, std::move(input), std::move(stops)));
|
||||
},
|
||||
[&](const type::PaddingType&) -> ParseResult {
|
||||
return ParseResult(
|
||||
std::make_unique<InterpolateImpl<Padding>>(type, interpolator, std::move(input), std::move(stops)));
|
||||
},
|
||||
[&](const type::Array& arrayType) -> ParseResult {
|
||||
if (arrayType.itemType != type::Number || !arrayType.N) {
|
||||
ctx.error("Type " + toString(type) + " is not interpolatable.");
|
||||
|
||||
@@ -134,6 +134,7 @@ constexpr const auto expressionRegistry = mapbox::eternal::hash_map<mapbox::eter
|
||||
{"string", Assertion::parse},
|
||||
{"to-boolean", Coercion::parse},
|
||||
{"to-color", Coercion::parse},
|
||||
{"to-padding", Coercion::parse},
|
||||
{"to-number", Coercion::parse},
|
||||
{"to-string", Coercion::parse},
|
||||
{"var", Var::parse},
|
||||
@@ -208,6 +209,10 @@ ParseResult ParsingContext::parse(const Convertible& value,
|
||||
(actual == type::Value || actual == type::String)) {
|
||||
parsed = {annotate(
|
||||
std::move(*parsed), std::move(*expected), typeAnnotationOption.value_or(TypeAnnotationOption::coerce))};
|
||||
} else if (*expected == type::Padding &&
|
||||
(actual == type::Value || actual == type::Number || actual.is<type::Array>())) {
|
||||
parsed = {annotate(
|
||||
std::move(*parsed), std::move(*expected), typeAnnotationOption.value_or(TypeAnnotationOption::coerce))};
|
||||
} else {
|
||||
checkType((*parsed)->getType());
|
||||
if (!errors->empty()) {
|
||||
|
||||
@@ -12,6 +12,7 @@ type::Type typeOf(const Value& value) {
|
||||
[&](double) -> type::Type { return type::Number; },
|
||||
[&](const std::string&) -> type::Type { return type::String; },
|
||||
[&](const Color&) -> type::Type { return type::Color; },
|
||||
[&](const Padding&) -> type::Type { return type::Padding; },
|
||||
[&](const Collator&) -> type::Type { return type::Collator; },
|
||||
[&](const Formatted&) -> type::Type { return type::Formatted; },
|
||||
[&](const Image&) -> type::Type { return type::Image; },
|
||||
@@ -53,6 +54,7 @@ void writeJSON(rapidjson::Writer<rapidjson::StringBuffer>& writer, const Value&
|
||||
},
|
||||
[&](const std::string& s) { writer.String(s); },
|
||||
[&](const Color& c) { writer.String(c.stringify()); },
|
||||
[&](const Padding& p) { mbgl::style::conversion::stringify(writer, p); },
|
||||
[&](const Collator&) {
|
||||
// Collators are excluded from constant folding and there's no Literal parser
|
||||
// for them so there shouldn't be any way to serialize this value.
|
||||
@@ -123,6 +125,7 @@ Value ValueConverter<mbgl::Value>::toExpressionValue(const mbgl::Value& value) {
|
||||
mbgl::Value ValueConverter<mbgl::Value>::fromExpressionValue(const Value& value) {
|
||||
return value.match(
|
||||
[&](const Color& color) -> mbgl::Value { return color.serialize(); },
|
||||
[&](const Padding& padding) -> mbgl::Value { return padding.serialize(); },
|
||||
[&](const Collator&) -> mbgl::Value {
|
||||
// fromExpressionValue can't be used for Collator values,
|
||||
// because they have no meaningful representation as an mbgl::Value
|
||||
@@ -309,6 +312,10 @@ type::Type valueTypeToExpressionType<Color>() {
|
||||
return type::Color;
|
||||
}
|
||||
template <>
|
||||
type::Type valueTypeToExpressionType<Padding>() {
|
||||
return type::Padding;
|
||||
}
|
||||
template <>
|
||||
type::Type valueTypeToExpressionType<Collator>() {
|
||||
return type::Collator;
|
||||
}
|
||||
|
||||
@@ -168,15 +168,15 @@ void SymbolLayer::setIconOptional(const PropertyValue<bool>& value) {
|
||||
baseImpl = std::move(impl_);
|
||||
observer->onLayerChanged(*this);
|
||||
}
|
||||
PropertyValue<float> SymbolLayer::getDefaultIconPadding() {
|
||||
PropertyValue<Padding> SymbolLayer::getDefaultIconPadding() {
|
||||
return IconPadding::defaultValue();
|
||||
}
|
||||
|
||||
const PropertyValue<float>& SymbolLayer::getIconPadding() const {
|
||||
const PropertyValue<Padding>& SymbolLayer::getIconPadding() const {
|
||||
return impl().layout.get<IconPadding>();
|
||||
}
|
||||
|
||||
void SymbolLayer::setIconPadding(const PropertyValue<float>& value) {
|
||||
void SymbolLayer::setIconPadding(const PropertyValue<Padding>& value) {
|
||||
if (value == getIconPadding()) return;
|
||||
auto impl_ = mutableImpl();
|
||||
impl_->layout.get<IconPadding>() = value;
|
||||
@@ -1628,39 +1628,15 @@ std::optional<Error> SymbolLayer::setPropertyInternal(const std::string& name, c
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
if (property == Property::IconPadding || property == Property::SymbolSpacing ||
|
||||
property == Property::TextLineHeight || property == Property::TextMaxAngle ||
|
||||
property == Property::TextPadding) {
|
||||
if (property == Property::IconPadding) {
|
||||
Error error;
|
||||
const auto& typedValue = convert<PropertyValue<float>>(value, error, false, false);
|
||||
const auto& typedValue = convert<PropertyValue<Padding>>(value, error, true, false);
|
||||
if (!typedValue) {
|
||||
return error;
|
||||
}
|
||||
|
||||
if (property == Property::IconPadding) {
|
||||
setIconPadding(*typedValue);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (property == Property::SymbolSpacing) {
|
||||
setSymbolSpacing(*typedValue);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (property == Property::TextLineHeight) {
|
||||
setTextLineHeight(*typedValue);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (property == Property::TextMaxAngle) {
|
||||
setTextMaxAngle(*typedValue);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (property == Property::TextPadding) {
|
||||
setTextPadding(*typedValue);
|
||||
return std::nullopt;
|
||||
}
|
||||
setIconPadding(*typedValue);
|
||||
return std::nullopt;
|
||||
}
|
||||
if (property == Property::IconPitchAlignment || property == Property::IconRotationAlignment ||
|
||||
property == Property::TextPitchAlignment || property == Property::TextRotationAlignment) {
|
||||
@@ -1720,6 +1696,34 @@ std::optional<Error> SymbolLayer::setPropertyInternal(const std::string& name, c
|
||||
setSymbolPlacement(*typedValue);
|
||||
return std::nullopt;
|
||||
}
|
||||
if (property == Property::SymbolSpacing || property == Property::TextLineHeight ||
|
||||
property == Property::TextMaxAngle || property == Property::TextPadding) {
|
||||
Error error;
|
||||
const auto& typedValue = convert<PropertyValue<float>>(value, error, false, false);
|
||||
if (!typedValue) {
|
||||
return error;
|
||||
}
|
||||
|
||||
if (property == Property::SymbolSpacing) {
|
||||
setSymbolSpacing(*typedValue);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (property == Property::TextLineHeight) {
|
||||
setTextLineHeight(*typedValue);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (property == Property::TextMaxAngle) {
|
||||
setTextMaxAngle(*typedValue);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (property == Property::TextPadding) {
|
||||
setTextPadding(*typedValue);
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
if (property == Property::SymbolZOrder) {
|
||||
Error error;
|
||||
const auto& typedValue = convert<PropertyValue<SymbolZOrderType>>(value, error, false, false);
|
||||
|
||||
@@ -51,9 +51,9 @@ struct IconOptional : LayoutProperty<bool> {
|
||||
static bool defaultValue() { return false; }
|
||||
};
|
||||
|
||||
struct IconPadding : LayoutProperty<float> {
|
||||
struct IconPadding : DataDrivenLayoutProperty<Padding> {
|
||||
static constexpr const char *name() { return "icon-padding"; }
|
||||
static float defaultValue() { return 2.f; }
|
||||
static Padding defaultValue() { return {2}; }
|
||||
};
|
||||
|
||||
struct IconPitchAlignment : LayoutProperty<AlignmentType> {
|
||||
|
||||
@@ -24,7 +24,7 @@ CollisionFeature::CollisionFeature(const GeometryCoordinates& line,
|
||||
shapedText.right,
|
||||
std::nullopt,
|
||||
boxScale,
|
||||
padding,
|
||||
{padding},
|
||||
overscaling,
|
||||
rotate);
|
||||
}
|
||||
@@ -33,7 +33,7 @@ CollisionFeature::CollisionFeature(const GeometryCoordinates& line,
|
||||
const Anchor& anchor,
|
||||
std::optional<PositionedIcon> shapedIcon,
|
||||
const float boxScale,
|
||||
const float padding,
|
||||
const Padding& padding,
|
||||
const RefIndexedSubfeature& indexedFeature_,
|
||||
const float rotate)
|
||||
: indexedFeature(std::move(indexedFeature_)),
|
||||
@@ -68,15 +68,15 @@ void CollisionFeature::initialize(const GeometryCoordinates& line,
|
||||
float right,
|
||||
const std::optional<Padding>& collisionPadding,
|
||||
float boxScale,
|
||||
float padding,
|
||||
const Padding& padding,
|
||||
float overscaling,
|
||||
float rotate) {
|
||||
if (top == 0 && bottom == 0 && left == 0 && right == 0) return;
|
||||
|
||||
float y1 = top * boxScale - padding;
|
||||
float y2 = bottom * boxScale + padding;
|
||||
float x1 = left * boxScale - padding;
|
||||
float x2 = right * boxScale + padding;
|
||||
float y1 = top * boxScale - padding.top;
|
||||
float y2 = bottom * boxScale + padding.bottom;
|
||||
float x1 = left * boxScale - padding.left;
|
||||
float x2 = right * boxScale + padding.right;
|
||||
|
||||
if (collisionPadding) {
|
||||
x1 -= collisionPadding->left * boxScale;
|
||||
|
||||
@@ -103,7 +103,7 @@ public:
|
||||
const Anchor& anchor,
|
||||
std::optional<PositionedIcon> shapedIcon,
|
||||
const float boxScale,
|
||||
const float padding,
|
||||
const Padding& padding,
|
||||
const RefIndexedSubfeature& indexedFeature_,
|
||||
const float rotate);
|
||||
|
||||
@@ -120,7 +120,7 @@ private:
|
||||
float right,
|
||||
const std::optional<Padding>& collisionPadding,
|
||||
float boxScale,
|
||||
float padding,
|
||||
const Padding& padding,
|
||||
float overscaling,
|
||||
float rotate);
|
||||
void bboxifyLabel(const GeometryCoordinates& line,
|
||||
|
||||
@@ -28,20 +28,6 @@ style::TextJustifyType getAnchorJustification(style::SymbolAnchorType anchor);
|
||||
class SymbolFeature;
|
||||
class BiDi;
|
||||
|
||||
class Padding {
|
||||
public:
|
||||
float left = 0;
|
||||
float top = 0;
|
||||
float right = 0;
|
||||
float bottom = 0;
|
||||
|
||||
explicit operator bool() const { return left != 0 || top != 0 || right != 0 || bottom != 0; }
|
||||
|
||||
bool operator==(const Padding& rhs) const {
|
||||
return left == rhs.left && top == rhs.top && right == rhs.right && bottom == rhs.bottom;
|
||||
}
|
||||
};
|
||||
|
||||
class PositionedIcon {
|
||||
private:
|
||||
PositionedIcon(
|
||||
|
||||
12
src/mbgl/util/padding.cpp
Normal file
12
src/mbgl/util/padding.cpp
Normal file
@@ -0,0 +1,12 @@
|
||||
#include <mbgl/util/padding.hpp>
|
||||
#include <mbgl/util/string.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
std::array<float, 4> Padding::toArray() const {
|
||||
return {{top, right, bottom, left}};
|
||||
}
|
||||
|
||||
mbgl::Value Padding::serialize() const {
|
||||
return std::vector<mbgl::Value>{top, right, bottom, left};
|
||||
}
|
||||
} // namespace mbgl
|
||||
@@ -48,6 +48,7 @@ add_library(
|
||||
${PROJECT_SOURCE_DIR}/test/style/conversion/geojson_options.test.cpp
|
||||
${PROJECT_SOURCE_DIR}/test/style/conversion/layer.test.cpp
|
||||
${PROJECT_SOURCE_DIR}/test/style/conversion/light.test.cpp
|
||||
${PROJECT_SOURCE_DIR}/test/style/conversion/padding.test.cpp
|
||||
${PROJECT_SOURCE_DIR}/test/style/conversion/property_value.test.cpp
|
||||
${PROJECT_SOURCE_DIR}/test/style/conversion/stringify.test.cpp
|
||||
${PROJECT_SOURCE_DIR}/test/style/conversion/tileset.test.cpp
|
||||
@@ -95,6 +96,7 @@ add_library(
|
||||
${PROJECT_SOURCE_DIR}/test/util/memory.test.cpp
|
||||
${PROJECT_SOURCE_DIR}/test/util/merge_lines.test.cpp
|
||||
${PROJECT_SOURCE_DIR}/test/util/number_conversions.test.cpp
|
||||
${PROJECT_SOURCE_DIR}/test/util/padding.test.cpp
|
||||
${PROJECT_SOURCE_DIR}/test/util/position.test.cpp
|
||||
${PROJECT_SOURCE_DIR}/test/util/projection.test.cpp
|
||||
${PROJECT_SOURCE_DIR}/test/util/rotation.test.cpp
|
||||
|
||||
4
test/android/.gitignore
vendored
4
test/android/.gitignore
vendored
@@ -1 +1,3 @@
|
||||
.gradle/
|
||||
.gradle/
|
||||
**/.cxx/
|
||||
app/build
|
||||
102
test/style/conversion/padding.test.cpp
Normal file
102
test/style/conversion/padding.test.cpp
Normal file
@@ -0,0 +1,102 @@
|
||||
#include <mbgl/test/util.hpp>
|
||||
|
||||
#include <mbgl/style/conversion/json.hpp>
|
||||
#include <mbgl/style/conversion/constant.hpp>
|
||||
#include <mbgl/style/conversion_impl.hpp>
|
||||
#include <mbgl/util/padding.hpp>
|
||||
|
||||
#include <array>
|
||||
|
||||
using namespace mbgl;
|
||||
using namespace mbgl::style;
|
||||
using namespace mbgl::style::conversion;
|
||||
|
||||
TEST(StyleConversion, Padding) {
|
||||
Error error;
|
||||
auto parsePadding = [&](const std::string& src) {
|
||||
return convertJSON<Padding>(src, error);
|
||||
};
|
||||
|
||||
{
|
||||
// Single value is applied to all sides
|
||||
auto padding = parsePadding("3");
|
||||
ASSERT_TRUE(padding.has_value());
|
||||
ASSERT_EQ(padding->top, 3);
|
||||
ASSERT_EQ(padding->right, 3);
|
||||
ASSERT_EQ(padding->bottom, 3);
|
||||
ASSERT_EQ(padding->left, 3);
|
||||
}
|
||||
{
|
||||
// Two values apply to [top/bottom, left/right]
|
||||
auto padding = parsePadding("[3.5, 4.5]");
|
||||
ASSERT_TRUE(padding.has_value());
|
||||
ASSERT_EQ(padding->top, 3.5);
|
||||
ASSERT_EQ(padding->bottom, 3.5);
|
||||
ASSERT_EQ(padding->left, 4.5);
|
||||
ASSERT_EQ(padding->right, 4.5);
|
||||
}
|
||||
{
|
||||
// Three values apply to [top, left/right, bottom] e.g. [2, 3, 1];
|
||||
auto padding = parsePadding("[2, 3, 1]");
|
||||
ASSERT_TRUE(padding.has_value());
|
||||
ASSERT_EQ(padding->top, 2);
|
||||
ASSERT_EQ(padding->left, 3);
|
||||
ASSERT_EQ(padding->right, 3);
|
||||
ASSERT_EQ(padding->bottom, 1);
|
||||
}
|
||||
{
|
||||
// Four values apply to [top, right, bottom, left], e.g. [2, 3, 1, 0].
|
||||
auto padding = parsePadding("[-2.5, -3.5, -1.0, -0.5]");
|
||||
ASSERT_TRUE(padding.has_value());
|
||||
ASSERT_EQ(padding->top, -2.5);
|
||||
ASSERT_EQ(padding->right, -3.5);
|
||||
ASSERT_EQ(padding->bottom, -1.0);
|
||||
ASSERT_EQ(padding->left, -0.5);
|
||||
}
|
||||
|
||||
// Invalid values
|
||||
|
||||
const auto expectedError = "value must be a number or an array of numbers (between 1 and 4 elements)";
|
||||
{
|
||||
// null
|
||||
auto padding = parsePadding("null");
|
||||
ASSERT_FALSE(padding.has_value());
|
||||
ASSERT_EQ(error.message, expectedError);
|
||||
}
|
||||
{
|
||||
// bool
|
||||
auto padding = parsePadding("false");
|
||||
ASSERT_FALSE(padding.has_value());
|
||||
ASSERT_EQ(error.message, expectedError);
|
||||
}
|
||||
{
|
||||
// empty array
|
||||
auto padding = parsePadding("[]");
|
||||
ASSERT_FALSE(padding.has_value());
|
||||
ASSERT_EQ(error.message, expectedError);
|
||||
}
|
||||
{
|
||||
// too long array
|
||||
auto padding = parsePadding("[1, 2, 3, 4, 5]");
|
||||
ASSERT_FALSE(padding.has_value());
|
||||
ASSERT_EQ(error.message, expectedError);
|
||||
}
|
||||
{
|
||||
// array of wrong types
|
||||
auto padding = parsePadding("[true, false]");
|
||||
ASSERT_FALSE(padding.has_value());
|
||||
ASSERT_EQ(error.message, expectedError);
|
||||
}
|
||||
{
|
||||
// array with null
|
||||
auto padding = parsePadding("[null]");
|
||||
ASSERT_FALSE(padding.has_value());
|
||||
ASSERT_EQ(error.message, expectedError);
|
||||
}
|
||||
{
|
||||
// object
|
||||
auto padding = parsePadding("{\"foo\": 1}");
|
||||
ASSERT_FALSE(padding.has_value());
|
||||
ASSERT_EQ(error.message, expectedError);
|
||||
}
|
||||
}
|
||||
@@ -128,6 +128,10 @@ TEST(Stringify, Layout) {
|
||||
|
||||
SymbolLayoutProperties::Unevaluated layout;
|
||||
layout.get<SymbolAvoidEdges>() = true;
|
||||
layout.get<IconPadding>() = 2.0;
|
||||
ASSERT_EQ(stringify(layout), "{\"icon-padding\":2.0,\"symbol-avoid-edges\":true}");
|
||||
layout.get<IconPadding>() = {2.f};
|
||||
ASSERT_EQ(stringify(layout), "{\"icon-padding\":[2.0,2.0,2.0,2.0],\"symbol-avoid-edges\":true}");
|
||||
}
|
||||
|
||||
TEST(Stringify, Padding) {
|
||||
ASSERT_EQ("[3.5,7.0,9.0,11.0]", stringify(Padding(3.5, 7, 9, 11)));
|
||||
}
|
||||
115
test/util/padding.test.cpp
Normal file
115
test/util/padding.test.cpp
Normal file
@@ -0,0 +1,115 @@
|
||||
#include <mbgl/test/util.hpp>
|
||||
|
||||
#include <mbgl/renderer/property_evaluator.hpp>
|
||||
#include <mbgl/renderer/property_evaluation_parameters.hpp>
|
||||
|
||||
#include <mbgl/style/types.hpp>
|
||||
#include <mbgl/style/conversion/json.hpp>
|
||||
#include <mbgl/style/expression/dsl.hpp>
|
||||
#include <mbgl/style/property_expression.hpp>
|
||||
|
||||
#include <mbgl/style/conversion/function.hpp>
|
||||
#include <mbgl/style/conversion/property_value.hpp>
|
||||
#include <mbgl/style/conversion_impl.hpp>
|
||||
|
||||
#include <mbgl/test/stub_geometry_tile_feature.hpp>
|
||||
|
||||
using namespace mbgl;
|
||||
using namespace style;
|
||||
using namespace style::expression;
|
||||
using namespace style::expression::dsl;
|
||||
using namespace std::string_literals;
|
||||
|
||||
TEST(Padding, InterpolateExpression) {
|
||||
// Use DLS methods to create an expression
|
||||
{
|
||||
auto expr = PropertyExpression<Padding>(
|
||||
interpolate(linear(), zoom(), 0.0, literal(Padding(0.0)), 1.0, literal(Padding(8.0))));
|
||||
|
||||
auto result = expr.evaluate(0.5f);
|
||||
EXPECT_EQ(Padding(4.0), result);
|
||||
}
|
||||
|
||||
// Parse expression from JSON, verify that expansion from number/array is handled correctly.
|
||||
{
|
||||
auto json =
|
||||
R"(["interpolate", ["linear"], ["zoom"], 0, ["to-padding", 0], 1, ["to-padding", ["literal",[8, 16, -32]]]])";
|
||||
PropertyExpression<Padding> expr(createExpression(json));
|
||||
|
||||
auto result = expr.evaluate(0.5f);
|
||||
EXPECT_EQ(Padding(4.0, 8, -16, 8), result);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Padding, Function) {
|
||||
auto fromFunction = [](const char* json) {
|
||||
conversion::Error error;
|
||||
auto function = conversion::convertJSON<PropertyValue<Padding>>(
|
||||
json, error, /*allowDataExpressions*/ true, /*convertTokens*/ false);
|
||||
return function->asExpression();
|
||||
};
|
||||
|
||||
auto evalInContext = [](const auto& expr, const PropertyMap& properties) {
|
||||
StubGeometryTileFeature feature{{}, FeatureType::Unknown, {}, properties};
|
||||
EvaluationContext context{0, &feature};
|
||||
auto a = expr.evaluate(context);
|
||||
return a;
|
||||
};
|
||||
|
||||
// exponential
|
||||
{
|
||||
auto expr = fromFunction(R"({"type": "exponential", "stops": [[1, 2], [11, [2, 5, 2, 7]]]})");
|
||||
EXPECT_EQ(Padding(2, 2, 2, 2), expr.evaluate(0));
|
||||
EXPECT_EQ(Padding(2, 3.2, 2, 4), expr.evaluate(5));
|
||||
EXPECT_EQ(Padding(2, 5, 2, 7), expr.evaluate(11));
|
||||
}
|
||||
// interval
|
||||
{
|
||||
auto expr = fromFunction(R"({"type": "interval", "stops": [[1, 2], [11, 4]]})");
|
||||
EXPECT_EQ(Padding(2), expr.evaluate(0));
|
||||
EXPECT_EQ(Padding(2), expr.evaluate(10));
|
||||
EXPECT_EQ(Padding(4), expr.evaluate(11));
|
||||
}
|
||||
// categorical
|
||||
{
|
||||
auto expr = fromFunction(
|
||||
R"({"type": "categorical", "property": "foo", "stops": [[0, 2], [1, 4]], "default": 6})");
|
||||
EXPECT_EQ(Padding(2), evalInContext(expr, {{"foo", 0}}));
|
||||
EXPECT_EQ(Padding(4), evalInContext(expr, {{"foo", 1}}));
|
||||
EXPECT_EQ(Padding(6), evalInContext(expr, {{"foo", 2}}));
|
||||
EXPECT_EQ(Padding(6), evalInContext(expr, {{"bar", 0}}));
|
||||
}
|
||||
// identity
|
||||
{
|
||||
auto expr = fromFunction(R"({"type": "identity", "property": "foo", "default": [3, 7, 9, 11]})");
|
||||
EXPECT_EQ(Padding(2, 4, 6, 4), evalInContext(expr, {{"foo", std::vector<mbgl::Value>({2, 4, 6})}}));
|
||||
EXPECT_EQ(Padding(3), evalInContext(expr, {{"foo", 3}}));
|
||||
EXPECT_EQ(Padding(3, 7, 9, 11), evalInContext(expr, {{"bar", 3}}));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Padding, OperatorBool) {
|
||||
Padding padding(0, 0, 0, 0);
|
||||
EXPECT_FALSE(padding);
|
||||
padding.left = 1;
|
||||
EXPECT_TRUE(padding);
|
||||
}
|
||||
|
||||
TEST(Padding, OperatorEqual) {
|
||||
auto a = Padding({{3.5f, 9}});
|
||||
auto b = Padding(3.5, 9, 3.5, 9);
|
||||
EXPECT_TRUE(a == b);
|
||||
EXPECT_FALSE(a != b);
|
||||
|
||||
a.left = 7;
|
||||
EXPECT_FALSE(a == b);
|
||||
EXPECT_TRUE(a != b);
|
||||
}
|
||||
|
||||
TEST(Padding, OperatorMultiply) {
|
||||
auto padding = Padding(1, 2, 3, 4) * 2;
|
||||
EXPECT_EQ(Padding(2, 4, 6, 8), padding);
|
||||
|
||||
padding = padding * 0.0;
|
||||
EXPECT_EQ(Padding(0), padding);
|
||||
}
|
||||
Reference in New Issue
Block a user