Skip to content

SceneView: add scene viewpoint operation #240

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import com.arcgismaps.mapping.Viewpoint
import com.arcgismaps.mapping.view.AnimationCurve
import com.arcgismaps.toolkit.geocompose.MapView
import com.arcgismaps.toolkit.geocompose.MapViewpointOperation
import kotlin.time.Duration.Companion.seconds

/**
* Displays a composable [MapView] and permits setting the viewpoint using options in a dropdown menu.
Expand Down Expand Up @@ -150,7 +151,7 @@ fun SetViewpointDropdownMenu(
val viewpointOperation = when (viewpointOperationName) {
"Animate" -> MapViewpointOperation.Animate(
Viewpoint(sofia, scale),
5f,
5.0.seconds,
AnimationCurve.EaseOutCubic
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ import com.arcgismaps.mapping.Viewpoint
import com.arcgismaps.mapping.view.AnimationCurve
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletableDeferred
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlin.time.DurationUnit

/**
* Defines operations for setting the viewpoint of a composable [MapView].
Expand Down Expand Up @@ -63,13 +66,13 @@ public sealed class MapViewpointOperation {
* navigation.
*
* @property viewpoint the new viewpoint
* @property durationSeconds the duration of the animation in seconds
* @property duration the duration of the animation
* @property curve the animation curve to apply
* @since 200.4.0
*/
public class Animate(
public val viewpoint: Viewpoint,
public val durationSeconds: Float = 0.25f,
public val duration: Duration = 0.25.seconds,
public val curve: AnimationCurve? = null
) : MapViewpointOperation()

Expand Down Expand Up @@ -152,13 +155,13 @@ internal suspend fun MapViewpointOperation.execute(mapView: com.arcgismaps.mappi
val result = if (this.curve != null) {
mapView.setViewpointAnimated(
this.viewpoint,
this.durationSeconds,
this.duration.toDouble(DurationUnit.SECONDS).toFloat(),
this.curve
)
} else {
mapView.setViewpointAnimated(
this.viewpoint,
this.durationSeconds
this.duration.toDouble(DurationUnit.SECONDS).toFloat()
)
}
this.complete(result)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import kotlinx.coroutines.launch
*
* @param modifier Modifier to be applied to the composable SceneView
* @param arcGISScene the [ArcGISScene] to be rendered by this composable SceneView
* @param viewpointOperation a [SceneViewpointOperation] that changes this SceneView to a new viewpoint
* @param graphicsOverlays the [GraphicsOverlayCollection] used by this composable SceneView
* @param sceneViewProxy the [SceneViewProxy] to associate with the composable SceneView
* @param onInteractingChanged lambda invoked when the user starts and ends interacting with the composable SceneView
Expand All @@ -66,6 +67,7 @@ import kotlinx.coroutines.launch
public fun SceneView(
modifier: Modifier = Modifier,
arcGISScene: ArcGISScene? = null,
viewpointOperation: SceneViewpointOperation? = null,
graphicsOverlays: GraphicsOverlayCollection = rememberGraphicsOverlayCollection(),
sceneViewProxy: SceneViewProxy? = null,
onInteractingChanged: ((isInteracting: Boolean) -> Unit)? = null,
Expand Down Expand Up @@ -105,6 +107,8 @@ public fun SceneView(
}
}

ViewpointUpdater(sceneView, viewpointOperation)

GraphicsOverlaysUpdater(graphicsOverlays, sceneView)

SceneViewEventHandler(
Expand All @@ -122,6 +126,22 @@ public fun SceneView(
)
}

/**
* Updates the viewpoint of the provided view-based [sceneView] using the given [viewpointOperation]. This will be
* recomposed when [viewpointOperation] changes.
*
* @since 200.4.0
*/
@Composable
private fun ViewpointUpdater(
sceneView: SceneView,
viewpointOperation: SceneViewpointOperation?
) {
LaunchedEffect(viewpointOperation) {
viewpointOperation?.execute(sceneView)
}
}

/**
* Sets up the callbacks for all the view-based [sceneView] events.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*
*
* Copyright 2023 Esri
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package com.arcgismaps.toolkit.geocompose

import androidx.compose.runtime.Stable
import com.arcgismaps.mapping.Bookmark
import com.arcgismaps.mapping.Viewpoint
import com.arcgismaps.mapping.view.Camera
import com.arcgismaps.mapping.view.SceneView
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletableDeferred
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlin.time.DurationUnit


/**
* Defines operations for setting the viewpoint of a composable [SceneView].
*
* @since 200.4.0
*/
@Stable
public sealed class SceneViewpointOperation {
private val deferred = CompletableDeferred<Result<Boolean>>()

/**
* Awaits the completion of this SceneViewpointOperation.
*
* @return a Result returning a boolean used to indicate if the operation completed successfully or not
* @since 200.4.0
*/
public suspend fun await(): Result<Boolean> = deferred.await()

internal fun complete(result: Result<Boolean>) {
deferred.complete(result)
}

/**
* Changes the scene view to the new viewpoint. The viewpoint is updated instantaneously.
*
* @property viewpoint the new viewpoint
* @since 200.4.0
*/
public class Set(public val viewpoint: Viewpoint) : SceneViewpointOperation()

/**
* Animates the scene view to the new viewpoint, taking the given number of seconds to complete the
* navigation.
*
* @property viewpoint the new viewpoint
* @property duration the duration of the animation
* @since 200.4.0
*/
public class Animate(
public val viewpoint: Viewpoint,
public val duration: Duration = 0.25.seconds
) : SceneViewpointOperation()

/**
* Changes the scene view to the viewpoint specified by the given camera. The viewpoint is updated instantaneously.
*
* @property camera the new camera
* @since 200.4.0
*/
public class SetCamera(public val camera: Camera) : SceneViewpointOperation()

/**
* Animates the scene view to the viewpoint specified by the given camera using the specified duration.
*
* @property camera the new camera
* @property duration the duration of the animation
* @since 200.4.0
*/
public class AnimateCamera(
public val camera: Camera,
public val duration: Duration = 0.25.seconds
) : SceneViewpointOperation()

/**
* Animates the scene view's viewpoint to the viewpoint of the bookmark.
*
* @property bookmark bookmark to set
* @since 200.4.0
*/
public class SetBookmark(public val bookmark: Bookmark) : SceneViewpointOperation()
}

/**
* Executes the [SceneViewpointOperation] on the given view-based [SceneView]. The operation can be awaited using
* [SceneViewpointOperation.await]. If the coroutine on which the viewpoint operation is executed is cancelled,
* for example due to another viewpoint operation being executed, the [SceneViewpointOperation.await] call
* will return a Result with a boolean set to `false`, indicating that the viewpoint operation failed
* to complete.
*
* @param sceneView the view-based SceneView to execute this operation on
* @since 200.4.0
*/
internal suspend fun SceneViewpointOperation.execute(sceneView: SceneView) {
when (this) {
is SceneViewpointOperation.Set -> {
sceneView.setViewpoint(this.viewpoint)
this.complete(Result.success(true))
}
is SceneViewpointOperation.Animate -> {
try {
val result = sceneView.setViewpointAnimated(this.viewpoint, this.duration.toDouble(DurationUnit.SECONDS).toFloat())
this.complete(result)
} catch (e: CancellationException) {
this.complete(Result.success(false))
throw e
}
}
is SceneViewpointOperation.AnimateCamera -> {
try {
val result = sceneView.setViewpointCameraAnimated(this.camera, this.duration.toDouble(DurationUnit.SECONDS).toFloat())
this.complete(result)
} catch (e: CancellationException) {
this.complete(Result.success(false))
throw e
}
}
is SceneViewpointOperation.SetBookmark -> {
try {
val result = sceneView.setBookmark(this.bookmark)
this.complete(result)
} catch (e: CancellationException) {
this.complete(Result.success(false))
throw e
}
}
is SceneViewpointOperation.SetCamera -> {
sceneView.setViewpointCamera(this.camera)
this.complete(Result.success(true))
}
}
}