From 70c646b2d533bc60c7e96cb90c0d9c3c7a060685 Mon Sep 17 00:00:00 2001 From: Ivan Persidsky Date: Tue, 16 Apr 2024 09:51:39 +0300 Subject: [PATCH] Allow to assign slots to 2D and 3D Puck (#2107) --- .../Testing Examples/PuckPlayground.swift | 11 +++++++++++ CHANGELOG.md | 1 + .../Foundation/Extensions/Core/Slot.swift | 4 +++- .../Location/Puck/Puck2DRenderer.swift | 6 +++++- .../Location/Puck/Puck3DRenderer.swift | 1 + Sources/MapboxMaps/Location/Puck/PuckType.swift | 11 ++++++++++- Sources/MapboxMaps/SwiftUI/Builders/Puck2D.swift | 8 ++++++++ Sources/MapboxMaps/SwiftUI/Builders/Puck3D.swift | 8 ++++++++ .../Location/Puck/Puck2DRendererTests.swift | 16 ++++++++++++++++ .../Location/Puck/Puck3DRendererTests.swift | 1 + 10 files changed, 64 insertions(+), 3 deletions(-) diff --git a/Apps/Examples/Examples/SwiftUI Examples/Testing Examples/PuckPlayground.swift b/Apps/Examples/Examples/SwiftUI Examples/Testing Examples/PuckPlayground.swift index 541aa2f2dbd0..fafd599438f9 100644 --- a/Apps/Examples/Examples/SwiftUI Examples/Testing Examples/PuckPlayground.swift +++ b/Apps/Examples/Examples/SwiftUI Examples/Testing Examples/PuckPlayground.swift @@ -19,6 +19,7 @@ struct PuckPlayground: View { @State private var puckType = PuckType.d2 @State private var bearingType = PuckBearing.heading @State private var opacity = 1.0 + @State private var slot: Slot? @State private var puck3dSettings = Puck3DSettings() @State private var puck2dSettings = Puck2DSettings() @State private var mapStyle = MapStyle.standard(lightPreset: .day) @@ -33,11 +34,13 @@ struct PuckPlayground: View { .showsAccuracyRing(puck2dSettings.accuracyRing) .opacity(opacity) .topImage(puck2dSettings.topImage.asPuckTopImage) + .slot(slot) case .d3: Puck3D(model: puck3dSettings.modelType.model, bearing: bearingType) .modelScale(puck3dSettings.modelScale) .modelOpacity(opacity) .modelEmissiveStrength(puck3dSettings.emission) + .slot(slot) } } .mapStyle(mapStyle) @@ -70,6 +73,14 @@ struct PuckPlayground: View { RadioButtonSettingView(title: "Puck Type", value: $puckType) RadioButtonSettingView(title: "Bearing", value: $bearingType) SliderSettingView(title: "Opacity", value: $opacity, range: 0...1, step: 0.1) + HStack { + Text("Slot") + Picker("Slot", selection: $slot) { + ForEach([Slot.bottom, .middle, .top, nil], id: \.self) { t in + Text(t?.rawValue ?? "nil").tag(t) + } + }.pickerStyle(.segmented) + } switch puckType { case .d2: diff --git a/CHANGELOG.md b/CHANGELOG.md index fdfc02ca15a2..3bf6f026057f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Mapbox welcomes participation and contributions from everyone. ## main +* Allow to assign slot to 2D and 3D location indicators. ## 11.3.0 - 10 April, 2024 diff --git a/Sources/MapboxMaps/Foundation/Extensions/Core/Slot.swift b/Sources/MapboxMaps/Foundation/Extensions/Core/Slot.swift index 7c548ff39914..8d62430d9190 100644 --- a/Sources/MapboxMaps/Foundation/Extensions/Core/Slot.swift +++ b/Sources/MapboxMaps/Foundation/Extensions/Core/Slot.swift @@ -2,7 +2,9 @@ import Foundation /// A pre-specified location in the style where layer will be added to /// (such as on top of existing land layers, but below all labels). -public struct Slot: Equatable, Codable, RawRepresentable, ExpressibleByStringLiteral { +/// +/// - SeeAlso: More information about slots in [Mapbox Style Specification](https://docs.mapbox.com/style-spec/reference/slots). +public struct Slot: Hashable, Codable, RawRepresentable, ExpressibleByStringLiteral { /// Above POI labels and behind Place and Transit labels public static let top = Slot(rawValue: "top") diff --git a/Sources/MapboxMaps/Location/Puck/Puck2DRenderer.swift b/Sources/MapboxMaps/Location/Puck/Puck2DRenderer.swift index 39c49d4ddc60..c577b43d40cb 100644 --- a/Sources/MapboxMaps/Location/Puck/Puck2DRenderer.swift +++ b/Sources/MapboxMaps/Location/Puck/Puck2DRenderer.swift @@ -98,7 +98,7 @@ final class Puck2DRenderer: PuckRenderer { // MARK: Layer - // swiftlint:disable:next function_body_length + // swiftlint:disable:next function_body_length cyclomatic_complexity private func updateLayer(newState: PuckRendererState, oldState: PuckRendererState?) throws { let newConfiguration = newState.configuration var newLayerLayoutProperties = [LocationIndicatorLayer.LayoutCodingKeys: Any]() @@ -235,6 +235,10 @@ final class Puck2DRenderer: PuckRenderer { // https://github.com/mapbox/mapbox-maps-ios/issues/860 try updateImages(newConfiguration: newConfiguration, oldConfiguration: oldState?.configuration) + if newConfiguration.slot != oldState?.configuration.slot { + allLayerProperties[LocationIndicatorLayer.RootCodingKeys.slot.rawValue] = newConfiguration.slot?.rawValue ?? "" + } + // Update or add the layer if style.layerExists(withId: Self.layerID) { try style.setLayerProperties(for: Self.layerID, properties: allLayerProperties) diff --git a/Sources/MapboxMaps/Location/Puck/Puck3DRenderer.swift b/Sources/MapboxMaps/Location/Puck/Puck3DRenderer.swift index 9bbc1bfe95b6..050c01913b5b 100644 --- a/Sources/MapboxMaps/Location/Puck/Puck3DRenderer.swift +++ b/Sources/MapboxMaps/Location/Puck/Puck3DRenderer.swift @@ -88,6 +88,7 @@ final class Puck3DRenderer: PuckRenderer { modelLayer.modelReceiveShadows = newConfiguration.modelReceiveShadows modelLayer.modelScaleMode = newConfiguration.modelScaleMode modelLayer.modelEmissiveStrength = newConfiguration.modelEmissiveStrength + modelLayer.slot = newConfiguration.slot do { // create the layer if needed diff --git a/Sources/MapboxMaps/Location/Puck/PuckType.swift b/Sources/MapboxMaps/Location/Puck/PuckType.swift index 6a0a43feaf05..04d61d8a920c 100644 --- a/Sources/MapboxMaps/Location/Puck/PuckType.swift +++ b/Sources/MapboxMaps/Location/Puck/PuckType.swift @@ -73,6 +73,11 @@ public struct Puck2DConfiguration: Equatable { /// The color of the accuracy ring border. public var accuracyRingBorderColor: UIColor + /// The ``Slot`` where to put puck layers. + /// + /// If specified, and a slot with that name exists, it will be placed at that position in the layer order. + public var slot: Slot? + /// Initialize a `Puck2D` object with a top image, bearing image, shadow image, scale, opacity and accuracy ring visibility. /// - Parameters: /// - topImage: The image to use as the top layer for the location indicator. @@ -171,9 +176,13 @@ public struct Puck3DConfiguration: Equatable { /// There is no emission for value 0. For value 1.0, only emissive component (no shading) is displayed and values above 1.0 produce light contribution to surrounding area, for some of the parts (e.g. windows). /// /// Default value is 1. - @_documentation(visibility: public) public var modelEmissiveStrength: Value? + /// The ``Slot`` where to put puck layers. + /// + /// If specified, and a slot with that name exists, it will be placed at that position in the layer order. + public var slot: Slot? + /// Initialize a `Puck3DConfiguration` with a model, scale and rotation. /// - Parameters: /// - model: The `gltf` model to use for the puck. diff --git a/Sources/MapboxMaps/SwiftUI/Builders/Puck2D.swift b/Sources/MapboxMaps/SwiftUI/Builders/Puck2D.swift index 79ee665eb574..e8483e0bc6ef 100644 --- a/Sources/MapboxMaps/SwiftUI/Builders/Puck2D.swift +++ b/Sources/MapboxMaps/SwiftUI/Builders/Puck2D.swift @@ -85,6 +85,14 @@ public struct Puck2D: PrimitiveMapContent { copyAssigned(self, \.configuration.accuracyRingBorderColor, accuracyRingBorderColor) } + /// The ``Slot`` where to put puck layers. + /// + /// If specified, and a slot with that name exists, it will be placed at that position in the layer order. + @_documentation(visibility: public) + public func slot(_ slot: Slot?) -> Puck2D { + copyAssigned(self, \.configuration.slot, slot) + } + func _visit(_ visitor: MapContentVisitor) { visitor.add(locationOptions: LocationOptions( puckType: .puck2D(configuration), diff --git a/Sources/MapboxMaps/SwiftUI/Builders/Puck3D.swift b/Sources/MapboxMaps/SwiftUI/Builders/Puck3D.swift index 61ee302cf055..82349fbb5213 100644 --- a/Sources/MapboxMaps/SwiftUI/Builders/Puck3D.swift +++ b/Sources/MapboxMaps/SwiftUI/Builders/Puck3D.swift @@ -116,6 +116,14 @@ public struct Puck3D: PrimitiveMapContent { copyAssigned(self, \.configuration.modelEmissiveStrength, .expression(modelEmissiveStrength)) } + /// The ``Slot`` where to put puck layers. + /// + /// If specified, and a slot with that name exists, it will be placed at that position in the layer order. + @_documentation(visibility: public) + public func slot(_ slot: Slot?) -> Puck3D { + copyAssigned(self, \.configuration.slot, slot) + } + func _visit(_ visitor: MapContentVisitor) { visitor.add(locationOptions: LocationOptions( puckType: .puck3D(configuration), diff --git a/Tests/MapboxMapsTests/Location/Puck/Puck2DRendererTests.swift b/Tests/MapboxMapsTests/Location/Puck/Puck2DRendererTests.swift index 71fa4f357841..edfce1b4de0d 100644 --- a/Tests/MapboxMapsTests/Location/Puck/Puck2DRendererTests.swift +++ b/Tests/MapboxMapsTests/Location/Puck/Puck2DRendererTests.swift @@ -164,6 +164,10 @@ final class Puck2DRendererTests: XCTestCase { expectedProperties["id"] = "puck" expectedProperties["type"] = "location-indicator" + if let slot = state.configuration.slot?.rawValue { + expectedProperties["slot"] = slot + } + return expectedProperties } @@ -609,4 +613,16 @@ final class Puck2DRendererTests: XCTestCase { XCTAssertEqual(StyleColor(expectedColor.withAlphaComponent(0)).rawValue, color) XCTAssertEqual(expectedRadius, radius) } + + func testSlot() throws { + style.layerExistsStub.defaultReturnValue = false + + var config = Puck2DConfiguration() + config.slot = "some-slot" + let state = updateState(configuration: config) + + let expectedProperties = makeExpectedLayerProperties(with: state) + let actualProperties = try XCTUnwrap(style.addPersistentLayerWithPropertiesStub.invocations.first?.parameters.properties) + XCTAssertEqual(actualProperties as NSDictionary, expectedProperties as NSDictionary) + } } diff --git a/Tests/MapboxMapsTests/Location/Puck/Puck3DRendererTests.swift b/Tests/MapboxMapsTests/Location/Puck/Puck3DRendererTests.swift index bb83f0d2d314..5cd6da998bfb 100644 --- a/Tests/MapboxMapsTests/Location/Puck/Puck3DRendererTests.swift +++ b/Tests/MapboxMapsTests/Location/Puck/Puck3DRendererTests.swift @@ -149,6 +149,7 @@ final class Puck3DRendererTests: XCTestCase { XCTAssertEqual(actualLayer.modelCastShadows, configuration.modelCastShadows) XCTAssertEqual(actualLayer.modelReceiveShadows, configuration.modelReceiveShadows) XCTAssertEqual(actualLayer.modelEmissiveStrength, configuration.modelEmissiveStrength) + XCTAssertEqual(actualLayer.slot, configuration.slot) } private func assertLayerUpdated(configuration: Puck3DConfiguration) throws {