@@ -99,13 +99,49 @@ class MapController(
9999 * @param latLng The coordinate to clamp
100100 * @return The clamped coordinate
101101 */
102- private fun clampToTargetBounds (latLng : LatLng ): LatLng {
103- val bounds = cameraTargetBounds ? : return latLng
102+ private fun clampToTargetBounds (latLng : LatLng ): LatLng = clampToTargetBoundsWithEdges(latLng).first
104103
105- val clampedLat = latLng.latitude.coerceIn(bounds.southwest.latitude, bounds.northeast.latitude)
106- val clampedLng = latLng.longitude.coerceIn(bounds.southwest.longitude, bounds.northeast.longitude)
104+ /* *
105+ * Clamps a LatLng coordinate to remain within the camera target bounds,
106+ * and returns which edges were hit during clamping.
107+ *
108+ * If no bounds are set, returns the input unchanged with an empty edge set.
109+ *
110+ * @param latLng The coordinate to clamp
111+ * @return A pair of the clamped coordinate and the set of edges that were hit
112+ */
113+ private fun clampToTargetBoundsWithEdges (latLng : LatLng ): Pair <LatLng , Set <Edge >> {
114+ val bounds = cameraTargetBounds ? : return Pair (latLng, emptySet())
115+
116+ val hitEdges = mutableSetOf<Edge >()
117+
118+ val clampedLat =
119+ when {
120+ latLng.latitude < bounds.southwest.latitude -> {
121+ hitEdges.add(Edge .BOTTOM )
122+ bounds.southwest.latitude
123+ }
124+ latLng.latitude > bounds.northeast.latitude -> {
125+ hitEdges.add(Edge .TOP )
126+ bounds.northeast.latitude
127+ }
128+ else -> latLng.latitude
129+ }
130+
131+ val clampedLng =
132+ when {
133+ latLng.longitude < bounds.southwest.longitude -> {
134+ hitEdges.add(Edge .LEFT )
135+ bounds.southwest.longitude
136+ }
137+ latLng.longitude > bounds.northeast.longitude -> {
138+ hitEdges.add(Edge .RIGHT )
139+ bounds.northeast.longitude
140+ }
141+ else -> latLng.longitude
142+ }
107143
108- return LatLng (clampedLat, clampedLng)
144+ return Pair ( LatLng (clampedLat, clampedLng), hitEdges )
109145 }
110146
111147 private var viewWidth = 0
@@ -301,17 +337,38 @@ class MapController(
301337 * @param overzoomAmount The amount of overzoom attempted (positive for zoom in, negative for zoom out)
302338 */
303339 private fun triggerOverzoomEffect (overzoomAmount : Float ) {
304- if (uiSettings?.isZoomEdgeEffectEnabled != true ) return
340+ if (uiSettings?.isEdgeEffectEnabled != true ) return
305341 if (viewWidth == 0 || viewHeight == 0 ) return
306342
307343 // Calculate glow strength based on overzoom magnitude
308344 val glowStrength = (kotlin.math.abs(overzoomAmount) * 2.0f ).coerceIn(0.3f , 1.0f )
309345
310- // Set glow on all edges
311- edgeGlowTop = glowStrength
312- edgeGlowBottom = glowStrength
313- edgeGlowLeft = glowStrength
314- edgeGlowRight = glowStrength
346+ // Trigger edge effect on all edges
347+ triggerEdgeEffect(setOf (Edge .TOP , Edge .BOTTOM , Edge .LEFT , Edge .RIGHT ), glowStrength)
348+ }
349+
350+ /* *
351+ * Triggers the edge glow effect on specified edges.
352+ *
353+ * This method can be called by app developers from button handlers or other UI controls
354+ * to provide visual feedback when the camera cannot move further in a direction.
355+ *
356+ * @param edges The set of edges to trigger the effect on
357+ * @param intensity The glow intensity from 0.0 (none) to 1.0 (full), default 0.8
358+ */
359+ fun triggerEdgeEffect (
360+ edges : Set <Edge >,
361+ intensity : Float = 0.8f,
362+ ) {
363+ if (uiSettings?.isEdgeEffectEnabled != true ) return
364+ if (viewWidth == 0 || viewHeight == 0 ) return
365+
366+ val clampedIntensity = intensity.coerceIn(0.0f , 1.0f )
367+
368+ if (Edge .TOP in edges) edgeGlowTop = clampedIntensity
369+ if (Edge .BOTTOM in edges) edgeGlowBottom = clampedIntensity
370+ if (Edge .LEFT in edges) edgeGlowLeft = clampedIntensity
371+ if (Edge .RIGHT in edges) edgeGlowRight = clampedIntensity
315372 }
316373
317374 /* *
@@ -1052,6 +1109,8 @@ class MapController(
10521109 * Updates the temporary pan offset during a drag gesture.
10531110 *
10541111 * The offset accumulates until committed via [commitPan].
1112+ * If camera target bounds are set and the pan would exceed them,
1113+ * triggers the edge effect on the appropriate edges.
10551114 *
10561115 * @param dx The horizontal movement in pixels
10571116 * @param dy The vertical movement in pixels
@@ -1060,8 +1119,24 @@ class MapController(
10601119 dx : Float ,
10611120 dy : Float ,
10621121 ) {
1063- panOffsetX - = dx
1064- panOffsetY - = dy
1122+ val newPanOffsetX = panOffsetX - dx
1123+ val newPanOffsetY = panOffsetY - dy
1124+
1125+ // Check if bounds are set and detect edge hits during panning
1126+ if (cameraTargetBounds != null ) {
1127+ val (centerPixelX, centerPixelY) = ProjectionUtils .latLngToPixel(center, zoom.toInt())
1128+ val newCenterPixelX = (centerPixelX + newPanOffsetX).toInt()
1129+ val newCenterPixelY = (centerPixelY + newPanOffsetY).toInt()
1130+ val newCenter = ProjectionUtils .pixelToLatLng(newCenterPixelX, newCenterPixelY, zoom.toInt())
1131+
1132+ val (_, hitEdges) = clampToTargetBoundsWithEdges(newCenter)
1133+ if (hitEdges.isNotEmpty()) {
1134+ triggerEdgeEffect(hitEdges, 0.6f )
1135+ }
1136+ }
1137+
1138+ panOffsetX = newPanOffsetX
1139+ panOffsetY = newPanOffsetY
10651140 }
10661141
10671142 /* *
0 commit comments