diff --git a/build.gradle.kts b/build.gradle.kts index c3feaafa..5afdeb20 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -30,10 +30,12 @@ mkdocs { } tasks.withType().configureEach { + val releaseVersion = ext["base_tag"].toString().replace("v", "") + val snapshotVersion = "${ext["next_patch_version"]}-SNAPSHOT" extras.set( mapOf( - "next_patch_version" to ext["next_patch_version"].toString(), - "base_version" to ext["base_tag"].toString().replace("v", ""), + "release_version" to releaseVersion, + "snapshot_version" to snapshotVersion, "maplibre_ios_version" to libs.versions.maplibre.ios.get(), ) ) diff --git a/demo-app/src/commonMain/kotlin/dev/sargunv/maplibrecompose/demoapp/demos/AnimatedLayerDemo.kt b/demo-app/src/commonMain/kotlin/dev/sargunv/maplibrecompose/demoapp/demos/AnimatedLayerDemo.kt index 018867d6..96c5a4f8 100644 --- a/demo-app/src/commonMain/kotlin/dev/sargunv/maplibrecompose/demoapp/demos/AnimatedLayerDemo.kt +++ b/demo-app/src/commonMain/kotlin/dev/sargunv/maplibrecompose/demoapp/demos/AnimatedLayerDemo.kt @@ -14,6 +14,8 @@ import dev.sargunv.maplibrecompose.compose.layer.LineLayer import dev.sargunv.maplibrecompose.compose.rememberCameraState import dev.sargunv.maplibrecompose.compose.source.rememberGeoJsonSource import dev.sargunv.maplibrecompose.core.CameraPosition +import dev.sargunv.maplibrecompose.core.expression.LineCap +import dev.sargunv.maplibrecompose.core.expression.LineJoin import dev.sargunv.maplibrecompose.demoapp.DEFAULT_STYLE import dev.sargunv.maplibrecompose.demoapp.Demo import dev.sargunv.maplibrecompose.demoapp.DemoScaffold @@ -60,7 +62,15 @@ object AnimatedLayerDemo : Demo { id = "amtrak-routes", source = routeSource, color = const(animatedColor), - width = const(4.dp), + cap = const(LineCap.Round), + join = const(LineJoin.Round), + width = + interpolate( + type = exponential(const(1.2f)), + input = zoom(), + 7 to const(1.75.dp), + 20 to const(22.dp), + ), ) } } diff --git a/demo-app/src/commonMain/kotlin/dev/sargunv/maplibrecompose/demoapp/docs/Interaction.kt b/demo-app/src/commonMain/kotlin/dev/sargunv/maplibrecompose/demoapp/docs/Interaction.kt new file mode 100644 index 00000000..c7a0699d --- /dev/null +++ b/demo-app/src/commonMain/kotlin/dev/sargunv/maplibrecompose/demoapp/docs/Interaction.kt @@ -0,0 +1,85 @@ +@file:Suppress("unused", "UNUSED_ANONYMOUS_PARAMETER") + +package dev.sargunv.maplibrecompose.demoapp.docs + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Alignment +import androidx.compose.ui.unit.dp +import dev.sargunv.maplibrecompose.compose.ClickResult +import dev.sargunv.maplibrecompose.compose.MaplibreMap +import dev.sargunv.maplibrecompose.compose.rememberCameraState +import dev.sargunv.maplibrecompose.core.CameraPosition +import dev.sargunv.maplibrecompose.core.GestureSettings +import dev.sargunv.maplibrecompose.core.OrnamentSettings +import io.github.dellisd.spatialk.geojson.Position +import kotlin.time.Duration.Companion.seconds + +@Composable +fun Interaction() { + // -8<- [start:gesture-settings] + MaplibreMap( + gestureSettings = + GestureSettings( + isTiltGesturesEnabled = true, + isZoomGesturesEnabled = true, // (1)! + isRotateGesturesEnabled = true, + isScrollGesturesEnabled = true, + ) + ) + // -8<- [end:gesture-settings] + + // -8<- [start:ornament-settings] + MaplibreMap( + ornamentSettings = + OrnamentSettings( + padding = PaddingValues(0.dp), // (1)! + isLogoEnabled = true, // (2)! + logoAlignment = Alignment.BottomStart, // (3)! + isAttributionEnabled = true, // (4)! + attributionAlignment = Alignment.BottomEnd, + isCompassEnabled = true, // (5)! + compassAlignment = Alignment.TopEnd, + ) + ) + // -8<- [end:ornament-settings] + + // -8<- [start:camera] + val camera = + rememberCameraState( + firstPosition = + CameraPosition(target = Position(latitude = 45.521, longitude = -122.675), zoom = 13.0) + ) + MaplibreMap(cameraState = camera) + // -8<- [end:camera] + + // -8<- [start:camera-animate] + LaunchedEffect(Unit) { + camera.animateTo( + finalPosition = + camera.position.copy(target = Position(latitude = 47.607, longitude = -122.342)), + duration = 3.seconds, + ) + } + // -8<- [end:camera-animate] + + // -8<- [start:click-listeners] + MaplibreMap( + cameraState = camera, + onMapClick = { pos, offset -> + val features = camera.queryRenderedFeatures(offset) + if (features.isNotEmpty()) { + println("Clicked on ${features[0].json()}") + ClickResult.Consume // (1)! + } else { + ClickResult.Pass + } + }, + onMapLongClick = { pos, offset -> + println("Long click at $pos") + ClickResult.Pass + }, + ) + // -8<- [end:click-listeners] +} diff --git a/demo-app/src/commonMain/kotlin/dev/sargunv/maplibrecompose/demoapp/docs/Layers.kt b/demo-app/src/commonMain/kotlin/dev/sargunv/maplibrecompose/demoapp/docs/Layers.kt new file mode 100644 index 00000000..be1d82a7 --- /dev/null +++ b/demo-app/src/commonMain/kotlin/dev/sargunv/maplibrecompose/demoapp/docs/Layers.kt @@ -0,0 +1,91 @@ +@file:Suppress("unused") + +package dev.sargunv.maplibrecompose.demoapp.docs + +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import dev.sargunv.maplibrecompose.compose.ClickResult +import dev.sargunv.maplibrecompose.compose.MaplibreMap +import dev.sargunv.maplibrecompose.compose.layer.Anchor +import dev.sargunv.maplibrecompose.compose.layer.CircleLayer +import dev.sargunv.maplibrecompose.compose.layer.LineLayer +import dev.sargunv.maplibrecompose.compose.source.getBaseSource +import dev.sargunv.maplibrecompose.compose.source.rememberGeoJsonSource +import dev.sargunv.maplibrecompose.core.expression.LineCap +import dev.sargunv.maplibrecompose.core.expression.LineJoin +import dev.sargunv.maplibrecompose.demoapp.generated.Res +import org.jetbrains.compose.resources.ExperimentalResourceApi + +@Composable +@OptIn(ExperimentalResourceApi::class) +fun Layers() { + // -8<- [start:simple] + MaplibreMap(styleUrl = "https://tiles.openfreemap.org/styles/liberty") { + val tiles = getBaseSource(id = "openmaptiles") + CircleLayer(id = "example", source = tiles, sourceLayer = "poi") + } + // -8<- [end:simple] + + MaplibreMap { + val amtrakStations = + rememberGeoJsonSource( + id = "amtrak-stations", + dataUrl = Res.getUri("files/data/amtrak_stations.geojson"), + ) + + // -8<- [start:amtrak-1] + val amtrakRoutes = + rememberGeoJsonSource( + id = "amtrak-routes", + dataUrl = Res.getUri("files/data/amtrak_routes.geojson"), + ) + LineLayer( + id = "amtrak-routes-casing", + source = amtrakRoutes, + color = const(Color.White), + width = const(6.dp), + ) + LineLayer( + id = "amtrak-routes", + source = amtrakRoutes, + color = const(Color.Blue), + width = const(4.dp), + ) + // -8<- [end:amtrak-1] + + // -8<- [start:amtrak-2] + LineLayer( + id = "amtrak-routes", + source = amtrakRoutes, + cap = const(LineCap.Round), + join = const(LineJoin.Round), + color = const(Color.Blue), + width = + interpolate( + type = exponential(const(1.2f)), + input = zoom(), + 5 to const(0.4.dp), + 6 to const(0.7.dp), + 7 to const(1.75.dp), + 20 to const(22.dp), + ), + ) + // -8<- [end:amtrak-2] + + // -8<- [start:anchors] + Anchor.Above("road_motorway") { LineLayer(id = "amtrak-routes", source = amtrakRoutes) } + // -8<- [end:anchors] + + // -8<- [start:interaction] + CircleLayer( + id = "amtrak-stations", + source = amtrakStations, + onClick = { features -> + println("Clicked on ${features[0].json()}") + ClickResult.Consume + }, + ) + // -8<- [end:interaction] + } +} diff --git a/demo-app/src/commonMain/kotlin/dev/sargunv/maplibrecompose/demoapp/docs/Styling.kt b/demo-app/src/commonMain/kotlin/dev/sargunv/maplibrecompose/demoapp/docs/Styling.kt index 379782ae..23613a47 100644 --- a/demo-app/src/commonMain/kotlin/dev/sargunv/maplibrecompose/demoapp/docs/Styling.kt +++ b/demo-app/src/commonMain/kotlin/dev/sargunv/maplibrecompose/demoapp/docs/Styling.kt @@ -8,8 +8,8 @@ import dev.sargunv.maplibrecompose.compose.MaplibreMap import dev.sargunv.maplibrecompose.demoapp.generated.Res import org.jetbrains.compose.resources.ExperimentalResourceApi -@OptIn(ExperimentalResourceApi::class) @Composable +@OptIn(ExperimentalResourceApi::class) fun Styling() { // -8<- [start:simple] MaplibreMap(styleUrl = "https://tiles.openfreemap.org/styles/liberty") diff --git a/docs/docs/camera.md b/docs/docs/camera.md deleted file mode 100644 index a6c435f8..00000000 --- a/docs/docs/camera.md +++ /dev/null @@ -1 +0,0 @@ -# Camera diff --git a/docs/docs/controls.md b/docs/docs/controls.md deleted file mode 100644 index c73df589..00000000 --- a/docs/docs/controls.md +++ /dev/null @@ -1 +0,0 @@ -# Controls diff --git a/docs/docs/expressions.md b/docs/docs/expressions.md deleted file mode 100644 index d52b5748..00000000 --- a/docs/docs/expressions.md +++ /dev/null @@ -1 +0,0 @@ -# Expressions diff --git a/docs/docs/getting-started.md b/docs/docs/getting-started.md index 1b07bfa2..7f908ac5 100644 --- a/docs/docs/getting-started.md +++ b/docs/docs/getting-started.md @@ -1,4 +1,4 @@ -# Getting Started +# Getting started This documentation assumes you already have a Compose Multiplatform project set up. If you haven't already, follow [the official JetBrains @@ -11,11 +11,11 @@ This library is published via [Maven Central][maven], and snapshot builds of === "Releases (Maven Central)" - The latest release is **v{{ gradle.base_version }}**. In your Gradle version catalog, add: + The latest release is **v{{ gradle.release_version }}**. In your Gradle version catalog, add: ```toml title="libs.versions.toml" [libraries] - maplibre-compose = { module = "dev.sargunv.maplibre-compose:maplibre-compose", version = "{{ gradle.base_version }}" } + maplibre-compose = { module = "dev.sargunv.maplibre-compose:maplibre-compose", version = "{{ gradle.release_version }}" } ``` === "Snapshots (GitHub Packages)" @@ -41,11 +41,11 @@ This library is published via [Maven Central][maven], and snapshot builds of } ``` - The latest snapshot is **v{{ gradle.next_patch_version }}-SNAPSHOT**. In your Gradle version catalog, add: + The latest snapshot is **v{{ gradle.snapshot_version }}**. In your Gradle version catalog, add: ```toml title="libs.versions.toml" [libraries] - maplibre-compose = { module = "dev.sargunv.maplibre-compose:maplibre-compose", version = "{{ gradle.next_patch_version }}-SNAPSHOT" } + maplibre-compose = { module = "dev.sargunv.maplibre-compose:maplibre-compose", version = "{{ gradle.snapshot_version }}" } ``` In your Gradle build script, add: diff --git a/docs/docs/interaction.md b/docs/docs/interaction.md new file mode 100644 index 00000000..348a8d62 --- /dev/null +++ b/docs/docs/interaction.md @@ -0,0 +1,60 @@ +# Interacting with the map + +## Gestures + +The map supports pan, zoom, rotate, and tilt gestures. Each of these can be +enabled or disabled individually: + +```kotlin +-8<- "demo-app/src/commonMain/kotlin/dev/sargunv/maplibrecompose/demoapp/docs/Interaction.kt:gesture-settings" +``` + +1. Includes pinch, double-tap, and double-tap-and-drag. + +## Ornaments + +Ornaments are UI elements that are displayed on the map, such as a compass or +attribution button. They're implemented by the underlying MapLibre SDK, so may +render differently on different platforms. You can control the visibility and +position of these ornaments: + +```kotlin +-8<- "demo-app/src/commonMain/kotlin/dev/sargunv/maplibrecompose/demoapp/docs/Interaction.kt:ornament-settings" +``` + +1. Insets the ornaments; useful if you have an edge-to-edge map or some UI + elements that cover part of the map. +2. Displays a MapLibre logo +3. Possible alignments are constrained by the underlying SDK. The four corners + are supported across platforms. +4. Displays attribution defined in the map style. +5. Displays a compass control when the map is rotated away from north. + +## Camera + +If you want to read or mutate the camera state, use `rememberCameraState()`. You +can use this to set the start position of the map: + +```kotlin +-8<- "demo-app/src/commonMain/kotlin/dev/sargunv/maplibrecompose/demoapp/docs/Interaction.kt:camera" +``` + +You can now use the `camera` reference to move the camera. For example, +`CameraState` exposes a `suspend fun animateTo` to animate the camera to a new +position: + +```kotlin +-8<- "demo-app/src/commonMain/kotlin/dev/sargunv/maplibrecompose/demoapp/docs/Interaction.kt:camera-animate" +``` + +## Click listeners + +You can listen for clicks on the map. Given a click location, you can use camera +state to query which features are present at that location: + +```kotlin +-8<- "demo-app/src/commonMain/kotlin/dev/sargunv/maplibrecompose/demoapp/docs/Interaction.kt:click-listeners" +``` + +1. Consumes the click event, preventing it from propagating to the individual + layers' click listeners. diff --git a/docs/docs/layers.md b/docs/docs/layers.md index 488c8d16..2dc7b796 100644 --- a/docs/docs/layers.md +++ b/docs/docs/layers.md @@ -1 +1,69 @@ -# Layers +# Adding data to the map + +## Sources and layers + +As covered in [Styling the map](styling.md), the data displayed on the map is +defined by the style, which is provided as a URL to a JSON object. However, this +library provides a way to add additional data to the map at runtime. + +A map style primarily consists of sources and layers. Sources contain the data +included on the map, and layers configure how that data is displayed. With +MapLibre Compose, you can dynamically configure sources and layers with +`@Composable` functions: + +```kotlin +-8<- "demo-app/src/commonMain/kotlin/dev/sargunv/maplibrecompose/demoapp/docs/Layers.kt:simple" +``` + +The above example shows how to add a layer referring to a source from the base +style, but you can also declare new sources: + +```kotlin +-8<- "demo-app/src/commonMain/kotlin/dev/sargunv/maplibrecompose/demoapp/docs/Layers.kt:amtrak-1" +``` + +The full breadth of layer styling options available is out of scope for this +guide; see the [MapLibre Style Specification][spec-layers] for more information. + +## Expressions + +MapLibre styles support expressions, which define a formula for computing the +value of a property at runtime. The [expressions specification][spec-layers] is +based on JSON, but this library provides a Kotlin DSL for expressing these +formulas with some type safety. Where suitable, Compose types like `Dp`, +`Color`, and `DpOffset` are used instead of their corresponding raw JSON-like +types. When passing a constant value instead of a formula, wrap it in `const()` +to turn it into an expression. + +```kotlin +-8<- "demo-app/src/commonMain/kotlin/dev/sargunv/maplibrecompose/demoapp/docs/Layers.kt:amtrak-2" +``` + +## Anchoring layers + +The default behavior for layers defined by Composable functions is to place them +at the top of the map, above the base style layers. However, you can insert them +at other positions in the stack of layers with `Anchor`: + +```kotlin +-8<- "demo-app/src/commonMain/kotlin/dev/sargunv/maplibrecompose/demoapp/docs/Layers.kt:anchors" +``` + +Anchors to insert layers at the `Bottom`, `Top`, `Above` a base layer, `Below` a +base layer, or `Replace` a base layer are provided. + +## Interacting with layers + +Layer composables provide click listeners, similar to the map itself. You can +listen for clicks on a layer and consume or pass those click events: + +```kotlin +-8<- "demo-app/src/commonMain/kotlin/dev/sargunv/maplibrecompose/demoapp/docs/Layers.kt:interaction" +``` + +Click listeners are called on the map first, then in layer order from the top to +the bottom of the map. The first listener to consume the event will prevent it +from propagating to subsequent listeners. + +[spec-layers]: https://maplibre.org/maplibre-style-spec/layers/ +[spec-expressions]: https://maplibre.org/maplibre-style-spec/expressions/ diff --git a/docs/docs/sources.md b/docs/docs/sources.md deleted file mode 100644 index e70306d4..00000000 --- a/docs/docs/sources.md +++ /dev/null @@ -1 +0,0 @@ -# Sources diff --git a/docs/docs/styling.md b/docs/docs/styling.md index c8e491b0..112aaf1d 100644 --- a/docs/docs/styling.md +++ b/docs/docs/styling.md @@ -1,4 +1,4 @@ -# Styling +# Styling the map ## Acquiring a style @@ -10,13 +10,14 @@ visual style editor for MapLibre styles. There are a variety of free and commercial map tile providers available. See the [awesome-maplibre][awesome-maplibre] repository for a list of tile providers. -This documentation primarily uses OpenFreeMap and Protomaps. Both of these are -free tile providers with their own styles. +This documentation primarily uses [OpenFreeMap][openfreemap] and +[Protomaps][protomaps]. Both of these are free tile providers with their own +styles. ## Using a style -To use a style, you can pass the `styleUrl` of the style to the `MaplibreMap` -composable: +To use a style, you can pass the `styleUrl` of your chosen style to the +`MaplibreMap` composable: ```kotlin -8<- "demo-app/src/commonMain/kotlin/dev/sargunv/maplibrecompose/demoapp/docs/Styling.kt:simple" @@ -25,7 +26,8 @@ composable: ## Dark mode You can select a style at runtime based on regular Compose logic. MapLibre will -automatically animate the transition between styles: +automatically animate the transition between styles. This is handy if you have a +light and a dark mode style: ```kotlin -8<- "demo-app/src/commonMain/kotlin/dev/sargunv/maplibrecompose/demoapp/docs/Styling.kt:dynamic" @@ -33,8 +35,8 @@ automatically animate the transition between styles: ## Local styles -While styles are typically hosted on the internet, you can also load a style -from a Compose resource URI: +While styles are typically hosted on the internet, you can also load a local +style with [Compose Multiplatform resources][resources]: ```kotlin -8<- "demo-app/src/commonMain/kotlin/dev/sargunv/maplibrecompose/demoapp/docs/Styling.kt:local" @@ -45,3 +47,5 @@ from a Compose resource URI: https://github.com/maplibre/awesome-maplibre#maptile-providers [openfreemap]: https://openfreemap.org/ [protomaps]: https://protomaps.com/ +[resources]: + https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-multiplatform-resources.html diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index dcb1c1e3..29dd8c8e 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -18,6 +18,19 @@ theme: repo: "fontawesome/brands/github" palette: - media: "(prefers-color-scheme)" + toggle: + icon: material/brightness-auto + name: Switch to light mode + - media: "(prefers-color-scheme: light)" + scheme: default + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + toggle: + icon: material/brightness-4 + name: Switch to system preference features: - content.code.copy - content.code.annotate @@ -79,8 +92,5 @@ nav: - index.md - getting-started.md - styling.md - - controls.md - - camera.md + - interaction.md - layers.md - - sources.md - - expressions.md diff --git a/lib/maplibre-compose/src/commonMain/kotlin/dev/sargunv/maplibrecompose/compose/CameraState.kt b/lib/maplibre-compose/src/commonMain/kotlin/dev/sargunv/maplibrecompose/compose/CameraState.kt index c349d3d6..71ba4793 100644 --- a/lib/maplibre-compose/src/commonMain/kotlin/dev/sargunv/maplibrecompose/compose/CameraState.kt +++ b/lib/maplibre-compose/src/commonMain/kotlin/dev/sargunv/maplibrecompose/compose/CameraState.kt @@ -100,19 +100,18 @@ public class CameraState internal constructor(firstPosition: CameraPosition) { * the list. * * @param offset position from the top-left corner of the map composable to query for - * @param layerIds the ids of the layers to limit the query to. If not specified or empty, - * features in *any* layer are returned + * @param layerIds the ids of the layers to limit the query to. If not specified, features in + * *any* layer are returned * @param predicate expression that has to evaluate to true for a feature to be included in the * result */ public fun queryRenderedFeatures( offset: DpOffset, - layerIds: Set = emptySet(), + layerIds: Set? = null, predicate: Expression = const(true), ): List { - val layerIdsOrNull = layerIds.takeUnless { it.isEmpty() } val predicateOrNull = predicate.takeUnless { it == const(true) } - return map?.queryRenderedFeatures(offset, layerIdsOrNull, predicateOrNull) ?: emptyList() + return map?.queryRenderedFeatures(offset, layerIds, predicateOrNull) ?: emptyList() } /** @@ -121,19 +120,18 @@ public class CameraState internal constructor(firstPosition: CameraPosition) { * is sorted by render order, i.e. the feature in front is first in the list. * * @param rect rectangle to intersect with rendered geometry - * @param layerIds the ids of the layers to limit the query to. If not specified or empty, - * features in *any* layer are returned + * @param layerIds the ids of the layers to limit the query to. If not specified, features in + * *any* layer are returned * @param predicate expression that has to evaluate to true for a feature to be included in the * result */ public fun queryRenderedFeatures( rect: DpRect, - layerIds: Set = emptySet(), + layerIds: Set? = null, predicate: Expression = const(true), ): List { - val layerIdsOrNull = layerIds.takeUnless { it.isEmpty() } val predicateOrNull = predicate.takeUnless { it == const(true) } - return map?.queryRenderedFeatures(rect, layerIdsOrNull, predicateOrNull) ?: emptyList() + return map?.queryRenderedFeatures(rect, layerIds, predicateOrNull) ?: emptyList() } /** diff --git a/lib/maplibre-compose/src/commonMain/kotlin/dev/sargunv/maplibrecompose/compose/util.kt b/lib/maplibre-compose/src/commonMain/kotlin/dev/sargunv/maplibrecompose/compose/util.kt index e4661064..4e0ea1c1 100644 --- a/lib/maplibre-compose/src/commonMain/kotlin/dev/sargunv/maplibrecompose/compose/util.kt +++ b/lib/maplibre-compose/src/commonMain/kotlin/dev/sargunv/maplibrecompose/compose/util.kt @@ -22,6 +22,9 @@ public typealias FeaturesClickHandler = (List) -> ClickResult /** The result of a click event handler. See [MapClickHandler] and [FeaturesClickHandler]. */ public enum class ClickResult(internal val consumed: Boolean) { + /** Consume the click event, preventing it from being passed down to layers below. */ Consume(true), + + /** Pass the click event down to layers below. */ Pass(false), } diff --git a/lib/maplibre-compose/src/commonMain/kotlin/dev/sargunv/maplibrecompose/core/OrnamentSettings.kt b/lib/maplibre-compose/src/commonMain/kotlin/dev/sargunv/maplibrecompose/core/OrnamentSettings.kt index da51a0ca..7739b528 100644 --- a/lib/maplibre-compose/src/commonMain/kotlin/dev/sargunv/maplibrecompose/core/OrnamentSettings.kt +++ b/lib/maplibre-compose/src/commonMain/kotlin/dev/sargunv/maplibrecompose/core/OrnamentSettings.kt @@ -32,5 +32,6 @@ public data class OrnamentSettings( ) { public companion object { public val AllEnabled: OrnamentSettings = OrnamentSettings() + public val AttributionOnly: OrnamentSettings = OrnamentSettings(isCompassEnabled = false) } }