From a34723ff4a53d9622f7e42fc3f10197d5acb94bd Mon Sep 17 00:00:00 2001 From: Brandon Roeder Date: Tue, 4 Aug 2026 11:17:53 -0500 Subject: [PATCH] fix(core): repaint feature-state-driven symbol paint properties (#4445) --- CHANGELOG.md | 1 + .../feature-state/symbol-paint/expected.png | Bin 0 -> 616 bytes .../feature-state/symbol-paint/style.json | 106 ++++++++++++++++++ .../android/docs/styling/feature-state.md | 2 + src/mbgl/renderer/buckets/symbol_bucket.cpp | 12 ++ src/mbgl/renderer/buckets/symbol_bucket.hpp | 1 + 6 files changed, 122 insertions(+) create mode 100644 metrics/integration/render-tests/feature-state/symbol-paint/expected.png create mode 100644 metrics/integration/render-tests/feature-state/symbol-paint/style.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d4259879..6f6ea7745 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ ### 🐞 Bug fixes - *...Add new stuff here...* +- [core] Repaint data-driven symbol paint properties after feature-state updates. - [core] Accept alpha values in legacy comma-separated `hsl()` colors ([#4434](https://github.com/maplibre/maplibre-native/issues/4434)). - [core] Fix thread-unsafe headless OpenGL display singleton initialization [#4332](https://github.com/maplibre/maplibre-native/pull/4332) - [macos] Fix `mlt-cpp` and `mbgl-vendor-icu` not being included in the amalgamation diff --git a/metrics/integration/render-tests/feature-state/symbol-paint/expected.png b/metrics/integration/render-tests/feature-state/symbol-paint/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..67e2bab655cc6820f77455fb44ca4ff5fd659603 GIT binary patch literal 616 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEVAA(=aSW-L^LFN5?bbwzc7H>U zS3f#L4y z!Ft7oVQa%Ijs;j1?PBQfFR0}$FaPjM>+%Qwgvs;fwoVf}k+yIlBO6;nwe-FFcS~!S z=ge_PfBrnw*xo%=b@_wRtPZ`BOamoMje zwZ%7V-MU{rfAR9;UzbTR-dR^t!0_~GJ^PGVMZrdYm~Je$viR})Np9qwyQRTKHPa7X z4u0`U%kF0vS43RdRl`5>E$h}Lq_NGKy>J!Rt5*xw^KaYs;O*I1_wO2iJ}^w5UQisI z)PLxI52l|kvTWG9|B%3A`3L{x+}sl@EP5vjM#QvqbC-)Awr{Zgef^qKwRKPb;R^yL zW=_%4bLQ@R{etPt*`@XMW@cuW)_u4%X?LHQL*ruQ73KEa8oZY!95{dMj;UHqEbp&P8^E zu&_(_9{k$1yHCwQkNxxK`1iK9j`{i}<%i#{UENpDaGSx_#z+0=eM@`))dxOrUhVfs gG6))Zs`3w*+c%wG-74&64@^M}p00i_>zopr0IdiT>Hq)$ literal 0 HcmV?d00001 diff --git a/metrics/integration/render-tests/feature-state/symbol-paint/style.json b/metrics/integration/render-tests/feature-state/symbol-paint/style.json new file mode 100644 index 000000000..f86c6779f --- /dev/null +++ b/metrics/integration/render-tests/feature-state/symbol-paint/style.json @@ -0,0 +1,106 @@ +{ + "version": 8, + "metadata": { + "test": { + "width": 64, + "height": 64, + "operations": [ + [ + "wait" + ], + [ + "setFeatureState", + { + "source": "geojson", + "id": "red" + }, + { + "color": "red" + } + ], + [ + "wait" + ] + ] + } + }, + "zoom": 2, + "sprite": "local://sprites/sprite", + "glyphs": "local://glyphs/{fontstack}/{range}.pbf", + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "id": "red", + "geometry": { + "type": "Point", + "coordinates": [ + 0, + 0 + ] + }, + "properties": { + "label": "R" + } + } + ] + } + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "symbol", + "type": "symbol", + "source": "geojson", + "layout": { + "icon-image": "dot.sdf", + "icon-allow-overlap": true, + "icon-ignore-placement": true, + "text-field": [ + "get", + "label" + ], + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ], + "text-size": 14, + "text-offset": [ + 0, + 1.5 + ], + "text-allow-overlap": true, + "text-ignore-placement": true + }, + "paint": { + "icon-color": [ + "coalesce", + [ + "feature-state", + "color" + ], + "black" + ], + "text-color": [ + "coalesce", + [ + "feature-state", + "color" + ], + "black" + ] + } + } + ] +} diff --git a/platform/android/docs/styling/feature-state.md b/platform/android/docs/styling/feature-state.md index fa63df723..e04f94ac8 100644 --- a/platform/android/docs/styling/feature-state.md +++ b/platform/android/docs/styling/feature-state.md @@ -35,3 +35,5 @@ Tapping a state toggles its `selected` feature-state key and shows a toast: - Use `getFeatureState()` on the source to inspect the current state for one feature. - Use `removeFeatureState()` or `resetFeatureStates()` on the source to clear state. - For `VectorSource`, all feature-state methods require a `sourceLayerId` parameter. +- Feature-state expressions update paint properties, including symbol paint properties such as `icon-color`, `icon-opacity`, `text-color`, and `text-opacity`. +- Feature-state expressions are not supported in layout properties such as `icon-image` or `text-field`. diff --git a/src/mbgl/renderer/buckets/symbol_bucket.cpp b/src/mbgl/renderer/buckets/symbol_bucket.cpp index 8f2922b76..2e72613e3 100644 --- a/src/mbgl/renderer/buckets/symbol_bucket.cpp +++ b/src/mbgl/renderer/buckets/symbol_bucket.cpp @@ -96,6 +96,18 @@ bool SymbolBucket::hasData() const { hasTextCollisionBoxData() || hasIconCollisionCircleData() || hasTextCollisionCircleData(); } +void SymbolBucket::update(const FeatureStates& states, + const GeometryTileLayer& layer, + const std::string& layerID, + const ImagePositions& imagePositions) { + auto it = paintProperties.find(layerID); + if (it != paintProperties.end()) { + it->second.iconBinders.updateVertexVectors(states, layer, imagePositions); + it->second.textBinders.updateVertexVectors(states, layer, imagePositions); + uploaded = false; + } +} + bool SymbolBucket::hasTextData() const { return !text.segments.empty(); } diff --git a/src/mbgl/renderer/buckets/symbol_bucket.hpp b/src/mbgl/renderer/buckets/symbol_bucket.hpp index de406d975..5fe95c70c 100644 --- a/src/mbgl/renderer/buckets/symbol_bucket.hpp +++ b/src/mbgl/renderer/buckets/symbol_bucket.hpp @@ -228,6 +228,7 @@ public: void upload(gfx::UploadPass&) override; bool hasData() const override; + void update(const FeatureStates&, const GeometryTileLayer&, const std::string&, const ImagePositions&) override; std::pair registerAtCrossTileIndex(CrossTileSymbolLayerIndex&, const RenderTile&) override; void place(Placement&, const BucketPlacementData&, std::set&) override; void updateVertices(