Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 7 additions & 9 deletions api/android-sdk-compose.api
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
public class com.mapconductor.compose.MapViewScope {
public com.mapconductor.compose.MapViewScope();
public final com.mapconductor.core.OverlayCollector<com.mapconductor.core.marker.MarkerState, com.mapconductor.core.marker.MarkerFingerPrint> getMarkerCollector();
public final com.mapconductor.core.OverlayCollector<com.mapconductor.core.marker.MarkerState> getMarkerCollector();
public final kotlinx.coroutines.flow.MutableStateFlow<java.util.Map<java.lang.String, com.mapconductor.compose.info.InfoBubbleEntry>> getBubbleFlow();
public final kotlinx.coroutines.flow.MutableStateFlow<java.util.Map<java.lang.String, com.mapconductor.core.marker.MarkerAnimationOverlayEntry>> getMarkerAnimationFlow();
public final com.mapconductor.core.OverlayCollector<com.mapconductor.core.polyline.PolylineState, com.mapconductor.core.polyline.PolylineFingerPrint> getPolylineCollector();
public final com.mapconductor.core.OverlayCollector<com.mapconductor.core.circle.CircleState, com.mapconductor.core.circle.CircleFingerPrint> getCircleCollector();
public final com.mapconductor.core.OverlayCollector<com.mapconductor.core.polygon.PolygonState, com.mapconductor.core.polygon.PolygonFingerPrint> getPolygonCollector();
public final com.mapconductor.core.OverlayCollector<com.mapconductor.core.groundimage.GroundImageState, com.mapconductor.core.groundimage.GroundImageFingerPrint> getGroundImageCollector();
public final com.mapconductor.core.OverlayCollector<com.mapconductor.core.raster.RasterLayerState, com.mapconductor.core.raster.RasterLayerFingerPrint> getRasterLayerCollector();
public final com.mapconductor.core.OverlayCollector<com.mapconductor.core.polyline.PolylineState> getPolylineCollector();
public final com.mapconductor.core.OverlayCollector<com.mapconductor.core.circle.CircleState> getCircleCollector();
public final com.mapconductor.core.OverlayCollector<com.mapconductor.core.polygon.PolygonState> getPolygonCollector();
public final com.mapconductor.core.OverlayCollector<com.mapconductor.core.groundimage.GroundImageState> getGroundImageCollector();
public final com.mapconductor.core.OverlayCollector<com.mapconductor.core.raster.RasterLayerState> getRasterLayerCollector();
public final com.mapconductor.core.map.MapOverlayRegistry buildRegistry();
}
public final class com.mapconductor.compose.OverlayProviderKt {
Expand All @@ -29,6 +29,7 @@ public final class com.mapconductor.compose.groundimage.LocalGroundImageCollecto
}
public final class com.mapconductor.compose.info.DrawInfoBubbleKt {
public static final void DrawInfoBubble-5cZf5Bw(androidx.compose.ui.Modifier, long, long, float, float, float, float, kotlin.jvm.functions.Function2<? super androidx.compose.runtime.Composer, ? super java.lang.Integer, kotlin.Unit>, androidx.compose.runtime.Composer, int);
public static final boolean isInsideBubble-M_7yMNQ(long, float, float, float);
}
public final class com.mapconductor.compose.info.InfoBubbleComposeKt {
public static final void InfoBubble-j2PKIZY(com.mapconductor.compose.MapViewScope, com.mapconductor.core.marker.MarkerState, long, long, float, float, float, float, kotlin.jvm.functions.Function2<? super androidx.compose.runtime.Composer, ? super java.lang.Integer, kotlin.Unit>, androidx.compose.runtime.Composer, int, int);
Expand All @@ -55,9 +56,6 @@ public abstract class com.mapconductor.compose.map.BaseMapViewSaver<T extends co
public final androidx.compose.runtime.saveable.Saver<T, android.os.Bundle> createSaver();
protected abstract java.lang.String getStateId(T);
}
public final class com.mapconductor.compose.map.CollectAndUpdateEachKt {
public static final <T extends com.mapconductor.core.ComponentState, FingerPrint> void CollectAndUpdateEach-NcHsxvU(kotlinx.coroutines.flow.StateFlow<? extends java.util.Map<java.lang.String, T>>, long, kotlin.jvm.functions.Function1<? super T, ? extends kotlinx.coroutines.flow.Flow<? extends FingerPrint>>, kotlin.jvm.functions.Function2<? super T, ? super kotlin.coroutines.Continuation<? super kotlin.Unit>, ? extends java.lang.Object>, androidx.compose.runtime.Composer, int);
}
public final class com.mapconductor.compose.map.MapAttributionOverlayKt {
public static final void MapAttributionOverlay(java.util.List<java.lang.String>, androidx.compose.runtime.Composer, int);
}
Expand Down
3 changes: 3 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ kotlin {
project.property("jvmTarget").toString(),
),
)
// ドライバー実装点(@InternalMapConductorApi)を使うためのオプトイン。
// この層はオーバーレイ収集の配線そのものなので、モジュール単位で許可する。
optIn.add("com.mapconductor.core.InternalMapConductorApi")
}
}

Expand Down
182 changes: 85 additions & 97 deletions src/main/java/com/mapconductor/compose/OverlayProvider.kt
Original file line number Diff line number Diff line change
@@ -1,97 +1,85 @@
package com.mapconductor.compose

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import com.mapconductor.compose.info.InfoBubbleEntry
import com.mapconductor.core.OverlayCollector
import com.mapconductor.core.circle.CircleFingerPrint
import com.mapconductor.core.circle.CircleOverlay
import com.mapconductor.core.circle.CircleState
import com.mapconductor.core.controller.MapViewControllerInterface
import com.mapconductor.core.groundimage.GroundImageFingerPrint
import com.mapconductor.core.groundimage.GroundImageOverlay
import com.mapconductor.core.groundimage.GroundImageState
import com.mapconductor.core.map.MapOverlayInterface
import com.mapconductor.core.map.MapOverlayRegistry
import com.mapconductor.core.marker.MarkerAnimationOverlayEntry
import com.mapconductor.core.marker.MarkerFingerPrint
import com.mapconductor.core.marker.MarkerOverlay
import com.mapconductor.core.marker.MarkerState
import com.mapconductor.core.polygon.PolygonFingerPrint
import com.mapconductor.core.polygon.PolygonOverlay
import com.mapconductor.core.polygon.PolygonState
import com.mapconductor.core.polyline.PolylineFingerPrint
import com.mapconductor.core.polyline.PolylineOverlay
import com.mapconductor.core.polyline.PolylineState
import com.mapconductor.core.raster.RasterLayerFingerPrint
import com.mapconductor.core.raster.RasterLayerOverlay
import com.mapconductor.core.raster.RasterLayerState
import com.mapconductor.settings.Settings
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.MutableStateFlow

open class MapViewScope {
val markerCollector =
OverlayCollector<MarkerState, MarkerFingerPrint>(
fingerPrintOf = { it.fingerPrint() },
updateDebounce = Settings.Default.composeEventDebounce,
)
val bubbleFlow = MutableStateFlow<MutableMap<String, InfoBubbleEntry>>(mutableMapOf())

/** Active screen-space marker animations, keyed by marker id. */
val markerAnimationFlow = MutableStateFlow<Map<String, MarkerAnimationOverlayEntry>>(emptyMap())
val polylineCollector =
OverlayCollector<PolylineState, PolylineFingerPrint>(
fingerPrintOf = { it.fingerPrint() },
updateDebounce = Settings.Default.composeEventDebounce,
)
val circleCollector =
OverlayCollector<CircleState, CircleFingerPrint>(
fingerPrintOf = { it.fingerPrint() },
updateDebounce = Settings.Default.composeEventDebounce,
)
val polygonCollector =
OverlayCollector<PolygonState, PolygonFingerPrint>(
fingerPrintOf = { it.fingerPrint() },
updateDebounce = Settings.Default.composeEventDebounce,
)
val groundImageCollector =
OverlayCollector<GroundImageState, GroundImageFingerPrint>(
fingerPrintOf = { it.fingerPrint() },
updateDebounce = Settings.Default.composeEventDebounce,
)
val rasterLayerCollector =
OverlayCollector<RasterLayerState, RasterLayerFingerPrint>(
fingerPrintOf = { it.fingerPrint() },
updateDebounce = Settings.Default.composeEventDebounce,
)

fun buildRegistry(): MapOverlayRegistry {
val registry = MapOverlayRegistry()
registry.register(MarkerOverlay(markerCollector.flow))
registry.register(CircleOverlay(circleCollector.flow))
registry.register(PolylineOverlay(polylineCollector.flow))
registry.register(PolygonOverlay(polygonCollector.flow))
registry.register(GroundImageOverlay(groundImageCollector.flow))
registry.register(RasterLayerOverlay(rasterLayerCollector.flow))
return registry
}
}

@OptIn(FlowPreview::class)
@Composable
fun CollectAndRenderOverlays(
registry: MapOverlayRegistry,
controller: MapViewControllerInterface,
) {
registry.getAll().forEach { overlay ->
@Suppress("UNCHECKED_CAST")
val typedOverlay = overlay as MapOverlayInterface<Any>
val flowState = typedOverlay.flow.collectAsState()

LaunchedEffect(flowState.value) {
typedOverlay.render(flowState.value, controller)
}
}
}
package com.mapconductor.compose

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import com.mapconductor.compose.info.InfoBubbleEntry
import com.mapconductor.core.OverlayCollector
import com.mapconductor.core.circle.CircleOverlay
import com.mapconductor.core.circle.CircleState
import com.mapconductor.core.controller.MapViewControllerInterface
import com.mapconductor.core.groundimage.GroundImageOverlay
import com.mapconductor.core.groundimage.GroundImageState
import com.mapconductor.core.map.MapOverlayInterface
import com.mapconductor.core.map.MapOverlayRegistry
import com.mapconductor.core.marker.MarkerAnimationOverlayEntry
import com.mapconductor.core.marker.MarkerOverlay
import com.mapconductor.core.marker.MarkerState
import com.mapconductor.core.polygon.PolygonOverlay
import com.mapconductor.core.polygon.PolygonState
import com.mapconductor.core.polyline.PolylineOverlay
import com.mapconductor.core.polyline.PolylineState
import com.mapconductor.core.raster.RasterLayerOverlay
import com.mapconductor.core.raster.RasterLayerState
import com.mapconductor.settings.Settings
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.MutableStateFlow

open class MapViewScope {
val markerCollector =
OverlayCollector<MarkerState>(
updateDebounce = Settings.Default.composeEventDebounce,
)
val bubbleFlow = MutableStateFlow<MutableMap<String, InfoBubbleEntry>>(mutableMapOf())

/** Active screen-space marker animations, keyed by marker id. */
val markerAnimationFlow = MutableStateFlow<Map<String, MarkerAnimationOverlayEntry>>(emptyMap())
val polylineCollector =
OverlayCollector<PolylineState>(
updateDebounce = Settings.Default.composeEventDebounce,
)
val circleCollector =
OverlayCollector<CircleState>(
updateDebounce = Settings.Default.composeEventDebounce,
)
val polygonCollector =
OverlayCollector<PolygonState>(
updateDebounce = Settings.Default.composeEventDebounce,
)
val groundImageCollector =
OverlayCollector<GroundImageState>(
updateDebounce = Settings.Default.composeEventDebounce,
)
val rasterLayerCollector =
OverlayCollector<RasterLayerState>(
updateDebounce = Settings.Default.composeEventDebounce,
)

fun buildRegistry(): MapOverlayRegistry {
val registry = MapOverlayRegistry()
registry.register(MarkerOverlay(markerCollector.flow))
registry.register(CircleOverlay(circleCollector.flow))
registry.register(PolylineOverlay(polylineCollector.flow))
registry.register(PolygonOverlay(polygonCollector.flow))
registry.register(GroundImageOverlay(groundImageCollector.flow))
registry.register(RasterLayerOverlay(rasterLayerCollector.flow))
return registry
}
}

@OptIn(FlowPreview::class)
@Composable
fun CollectAndRenderOverlays(
registry: MapOverlayRegistry,
controller: MapViewControllerInterface,
) {
registry.getAll().forEach { overlay ->
@Suppress("UNCHECKED_CAST")
val typedOverlay = overlay as MapOverlayInterface<Any>
val flowState = typedOverlay.flow.collectAsState()

LaunchedEffect(flowState.value) {
typedOverlay.render(flowState.value, controller)
}
}
}
55 changes: 54 additions & 1 deletion src/main/java/com/mapconductor/compose/info/DrawInfoBubble.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.Fill
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.unit.Dp

@Composable
Expand All @@ -27,7 +28,37 @@ internal fun DrawInfoBubble(
tailSize: Dp,
content: @Composable () -> Unit,
) {
Box(modifier = modifier.wrapContentSize()) {
Box(
modifier =
modifier
.wrapContentSize()
.pointerInput(tailSize) {
// 吹き出しの上のタップを地図へ落とさない。
//
// 地図は下に敷かれた AndroidView で、誰も消費しなかった
// イベントはそこへ届く。吹き出しの余白を叩くと地図のタップ
// として扱われ、下にいたマーカーが選ばれたり吹き出しが閉じ
// たりする。「当たったが誰も使わなかった」と「当たらなかっ
// た」は別なので、前者をここで止める。
//
// 消費は Main パスで行う。Compose は Main を子から親へ配る
// ため、ここへ来た時点で中身のボタンや clickable は既に自分
// の分を受け取っている。Initial パスで消費すると、それらが
// 動かなくなる。
val tailPx = tailSize.toPx()
val boxHeight = size.height.toFloat()
val boxWidth = size.width.toFloat()
awaitPointerEventScope {
while (true) {
awaitPointerEvent().changes.forEach { change ->
if (isInsideBubble(change.position, boxHeight, boxWidth, tailPx)) {
change.consume()
}
}
}
}
},
) {
Canvas(modifier = Modifier.matchParentSize()) {
val width = size.width
val height = size.height
Expand Down Expand Up @@ -110,3 +141,25 @@ internal fun DrawInfoBubble(
}
}
}

/**
* 吹き出しの本体の内側か。
*
* Box はしっぽを含む外接矩形なので、しっぽの左右の角は箱の中だが吹き出しでは
* ない。そこまで吹き出し扱いにすると、地図が見えている場所を叩いても何も起き
* ない。
*
* しっぽ自体も外している。既定 8dp の三角形で、外して困るのは先端を狙って
* 叩いた場合だけ。そのとき起きるのは下の地図が反応することで、狙っていた
* ものが動かないのとは違う。角の丸めも同じ理由で無視している。
*/
internal fun isInsideBubble(
point: Offset,
height: Float,
width: Float,
tailPx: Float,
): Boolean =
point.x >= 0f &&
point.x <= width &&
point.y >= 0f &&
point.y <= height - tailPx
29 changes: 0 additions & 29 deletions src/main/java/com/mapconductor/compose/map/CollectAndUpdateEach.kt

This file was deleted.

7 changes: 6 additions & 1 deletion src/main/java/com/mapconductor/compose/map/MapViewBase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,13 @@ import com.mapconductor.core.map.MapCameraPosition
import com.mapconductor.core.map.MapCameraPositionInterface
import com.mapconductor.core.map.MapDesignTypeInterface
import com.mapconductor.core.map.MapOverlayRegistry
import com.mapconductor.core.map.MapPaddings
import com.mapconductor.core.map.MapViewHolderInterface
import com.mapconductor.core.map.MapViewStateInterface
import com.mapconductor.core.map.ScreenProjectionRequirement
import com.mapconductor.core.map.resolveMapAttributions
import com.mapconductor.core.marker.MarkerCapableInterface
import com.mapconductor.core.map.VectorStyleSupportKey
import com.mapconductor.core.marker.MarkerRenderingSupportKey
import com.mapconductor.core.polygon.PolygonCapableInterface
import com.mapconductor.core.polyline.PolylineCapableInterface
Expand Down Expand Up @@ -120,7 +122,10 @@ fun <
// 登録側は各プロバイダ(コントローラ生成時)。content の合成より前に登録される必要があり、
// `MarkerClusterGroup` は登録が無ければその場で return するため、ここで put はしない。
DisposableEffect(state) {
onDispose { state.serviceRegistry.remove(MarkerRenderingSupportKey) }
onDispose {
state.serviceRegistry.remove(MarkerRenderingSupportKey)
state.serviceRegistry.remove(VectorStyleSupportKey)
}
}

controller?.also {
Expand Down
Loading
Loading