summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libs/WindowManager/Shell/shared/res/values/dimen.xml1
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZone.kt24
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt98
-rw-r--r--libs/WindowManager/Shell/tests/unittest/Android.bp1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt210
5 files changed, 298 insertions, 36 deletions
diff --git a/libs/WindowManager/Shell/shared/res/values/dimen.xml b/libs/WindowManager/Shell/shared/res/values/dimen.xml
index 5f013c52d70d..11a6f32d7454 100644
--- a/libs/WindowManager/Shell/shared/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/shared/res/values/dimen.xml
@@ -38,6 +38,7 @@
<dimen name="drag_zone_v_split_from_expanded_view_height_fold_short">100dp</dimen>
<!-- Bubble drop target dimensions -->
+ <dimen name="drop_target_elevation">1dp</dimen>
<dimen name="drop_target_full_screen_padding">20dp</dimen>
<dimen name="drop_target_desktop_window_padding_small">100dp</dimen>
<dimen name="drop_target_desktop_window_padding_large">130dp</dimen>
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZone.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZone.kt
index 5d346c047123..6eff75c9a479 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZone.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZone.kt
@@ -31,29 +31,41 @@ sealed interface DragZone {
/** The bounds of this drag zone. */
val bounds: Rect
+ /** The bounds of the drop target associated with this drag zone. */
+ val dropTarget: Rect?
fun contains(x: Int, y: Int) = bounds.contains(x, y)
/** Represents the bubble drag area on the screen. */
- sealed class Bubble(override val bounds: Rect) : DragZone {
- data class Left(override val bounds: Rect, val dropTarget: Rect) : Bubble(bounds)
- data class Right(override val bounds: Rect, val dropTarget: Rect) : Bubble(bounds)
+ sealed class Bubble(override val bounds: Rect, override val dropTarget: Rect) : DragZone {
+ data class Left(override val bounds: Rect, override val dropTarget: Rect) :
+ Bubble(bounds, dropTarget)
+
+ data class Right(override val bounds: Rect, override val dropTarget: Rect) :
+ Bubble(bounds, dropTarget)
}
/** Represents dragging to Desktop Window. */
- data class DesktopWindow(override val bounds: Rect, val dropTarget: Rect) : DragZone
+ data class DesktopWindow(override val bounds: Rect, override val dropTarget: Rect) : DragZone
/** Represents dragging to Full Screen. */
- data class FullScreen(override val bounds: Rect, val dropTarget: Rect) : DragZone
+ data class FullScreen(override val bounds: Rect, override val dropTarget: Rect) : DragZone
/** Represents dragging to dismiss. */
- data class Dismiss(override val bounds: Rect) : DragZone
+ data class Dismiss(override val bounds: Rect) : DragZone {
+ override val dropTarget: Rect? = null
+ }
/** Represents dragging to enter Split or replace a Split app. */
sealed class Split(override val bounds: Rect) : DragZone {
+ override val dropTarget: Rect? = null
+
data class Left(override val bounds: Rect) : Split(bounds)
+
data class Right(override val bounds: Rect) : Split(bounds)
+
data class Top(override val bounds: Rect) : Split(bounds)
+
data class Bottom(override val bounds: Rect) : Split(bounds)
}
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt
index 29ce8d90e66f..2dc183f3f707 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt
@@ -16,22 +16,54 @@
package com.android.wm.shell.shared.bubbles
+import android.content.Context
+import android.graphics.Rect
+import android.view.View
+import android.widget.FrameLayout
+import androidx.core.animation.Animator
+import androidx.core.animation.AnimatorListenerAdapter
+import androidx.core.animation.ValueAnimator
+
/**
* Manages animating drop targets in response to dragging bubble icons or bubble expanded views
* across different drag zones.
*/
class DropTargetManager(
+ context: Context,
+ private val container: FrameLayout,
private val isLayoutRtl: Boolean,
- private val dragZoneChangedListener: DragZoneChangedListener
+ private val dragZoneChangedListener: DragZoneChangedListener,
) {
private var state: DragState? = null
+ private val dropTargetView = View(context)
+ private var animator: ValueAnimator? = null
+
+ private companion object {
+ const val ANIMATION_DURATION_MS = 250L
+ }
/** Must be called when a drag gesture is starting. */
fun onDragStarted(draggedObject: DraggedObject, dragZones: List<DragZone>) {
val state = DragState(dragZones, draggedObject)
dragZoneChangedListener.onInitialDragZoneSet(state.initialDragZone)
this.state = state
+ animator?.cancel()
+ setupDropTarget()
+ }
+
+ private fun setupDropTarget() {
+ if (dropTargetView.parent != null) container.removeView(dropTargetView)
+ container.addView(dropTargetView, 0)
+ // TODO b/393173014: set elevation and background
+ dropTargetView.alpha = 0f
+ dropTargetView.scaleX = 1f
+ dropTargetView.scaleY = 1f
+ dropTargetView.translationX = 0f
+ dropTargetView.translationY = 0f
+ // the drop target is added with a width and height of 1 pixel. when it gets resized, we use
+ // set its scale to the width and height of the bounds it should have to avoid layout passes
+ dropTargetView.layoutParams = FrameLayout.LayoutParams(/* width= */ 1, /* height= */ 1)
}
/** Called when the user drags to a new location. */
@@ -42,14 +74,67 @@ class DropTargetManager(
state.currentDragZone = newDragZone
if (oldDragZone != newDragZone) {
dragZoneChangedListener.onDragZoneChanged(from = oldDragZone, to = newDragZone)
+ updateDropTarget()
}
}
/** Called when the drag ended. */
fun onDragEnded() {
+ startFadeAnimation(from = dropTargetView.alpha, to = 0f) {
+ container.removeView(dropTargetView)
+ }
state = null
}
+ private fun updateDropTarget() {
+ val currentDragZone = state?.currentDragZone ?: return
+ val dropTargetBounds = currentDragZone.dropTarget
+ when {
+ dropTargetBounds == null -> startFadeAnimation(from = dropTargetView.alpha, to = 0f)
+ dropTargetView.alpha == 0f -> {
+ dropTargetView.translationX = dropTargetBounds.exactCenterX()
+ dropTargetView.translationY = dropTargetBounds.exactCenterY()
+ dropTargetView.scaleX = dropTargetBounds.width().toFloat()
+ dropTargetView.scaleY = dropTargetBounds.height().toFloat()
+ startFadeAnimation(from = 0f, to = 1f)
+ }
+ else -> startMorphAnimation(dropTargetBounds)
+ }
+ }
+
+ private fun startFadeAnimation(from: Float, to: Float, onEnd: (() -> Unit)? = null) {
+ animator?.cancel()
+ val animator = ValueAnimator.ofFloat(from, to).setDuration(ANIMATION_DURATION_MS)
+ animator.addUpdateListener { _ -> dropTargetView.alpha = animator.animatedValue as Float }
+ if (onEnd != null) {
+ animator.doOnEnd(onEnd)
+ }
+ this.animator = animator
+ animator.start()
+ }
+
+ private fun startMorphAnimation(bounds: Rect) {
+ animator?.cancel()
+ val startAlpha = dropTargetView.alpha
+ val startTx = dropTargetView.translationX
+ val startTy = dropTargetView.translationY
+ val startScaleX = dropTargetView.scaleX
+ val startScaleY = dropTargetView.scaleY
+ val animator = ValueAnimator.ofFloat(0f, 1f).setDuration(ANIMATION_DURATION_MS)
+ animator.addUpdateListener { _ ->
+ val fraction = animator.animatedValue as Float
+ dropTargetView.alpha = startAlpha + (1 - startAlpha) * fraction
+ dropTargetView.translationX = startTx + (bounds.exactCenterX() - startTx) * fraction
+ dropTargetView.translationY = startTy + (bounds.exactCenterY() - startTy) * fraction
+ dropTargetView.scaleX =
+ startScaleX + (bounds.width().toFloat() - startScaleX) * fraction
+ dropTargetView.scaleY =
+ startScaleY + (bounds.height().toFloat() - startScaleY) * fraction
+ }
+ this.animator = animator
+ animator.start()
+ }
+
/** Stores the current drag state. */
private inner class DragState(
private val dragZones: List<DragZone>,
@@ -72,7 +157,18 @@ class DropTargetManager(
interface DragZoneChangedListener {
/** An initial drag zone was set. Called when a drag starts. */
fun onInitialDragZoneSet(dragZone: DragZone)
+
/** Called when the object was dragged to a different drag zone. */
fun onDragZoneChanged(from: DragZone, to: DragZone)
}
+
+ private fun Animator.doOnEnd(onEnd: () -> Unit) {
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ onEnd()
+ }
+ }
+ )
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index bf5e374c7607..bff12d026b93 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -45,6 +45,7 @@ android_test {
"androidx.test.rules",
"androidx.test.ext.junit",
"androidx.datastore_datastore",
+ "androidx.core_core-animation-testing",
"kotlinx_coroutines_test",
"androidx.dynamicanimation_dynamicanimation",
"dagger2",
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt
index efb91c5fbfda..180a6915b45f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt
@@ -16,23 +16,33 @@
package com.android.wm.shell.shared.bubbles
+import android.content.Context
import android.graphics.Rect
+import android.view.View
+import android.widget.FrameLayout
+import androidx.core.animation.AnimatorTestRule
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFails
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import kotlin.test.assertFails
/** Unit tests for [DropTargetManager]. */
@SmallTest
@RunWith(AndroidJUnit4::class)
class DropTargetManagerTest {
+ @get:Rule val animatorTestRule = AnimatorTestRule()
+
+ private val context = getApplicationContext<Context>()
private lateinit var dropTargetManager: DropTargetManager
private lateinit var dragZoneChangedListener: FakeDragZoneChangedListener
- private val dropTarget = Rect(0, 0, 0, 0)
+ private lateinit var container: FrameLayout
// create 3 drop zones that are horizontally next to each other
// -------------------------------------------------
@@ -43,15 +53,20 @@ class DropTargetManagerTest {
// | | | |
// -------------------------------------------------
private val bubbleLeftDragZone =
- DragZone.Bubble.Left(bounds = Rect(0, 0, 100, 100), dropTarget = dropTarget)
+ DragZone.Bubble.Left(bounds = Rect(0, 0, 100, 100), dropTarget = Rect(0, 0, 50, 200))
private val dismissDragZone = DragZone.Dismiss(bounds = Rect(100, 0, 200, 100))
private val bubbleRightDragZone =
- DragZone.Bubble.Right(bounds = Rect(200, 0, 300, 100), dropTarget = dropTarget)
+ DragZone.Bubble.Right(bounds = Rect(200, 0, 300, 100), dropTarget = Rect(200, 0, 280, 150))
+
+ private val dropTargetView: View
+ get() = container.getChildAt(0)
@Before
fun setUp() {
+ container = FrameLayout(context)
dragZoneChangedListener = FakeDragZoneChangedListener()
- dropTargetManager = DropTargetManager(isLayoutRtl = false, dragZoneChangedListener)
+ dropTargetManager =
+ DropTargetManager(context, container, isLayoutRtl = false, dragZoneChangedListener)
}
@Test
@@ -79,17 +94,21 @@ class DropTargetManagerTest {
DraggedObject.Bubble(BubbleBarLocation.LEFT),
listOf(bubbleLeftDragZone, bubbleRightDragZone, dismissDragZone)
)
- dropTargetManager.onDragUpdated(
- bubbleRightDragZone.bounds.centerX(),
- bubbleRightDragZone.bounds.centerY()
- )
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ dropTargetManager.onDragUpdated(
+ bubbleRightDragZone.bounds.centerX(),
+ bubbleRightDragZone.bounds.centerY()
+ )
+ }
assertThat(dragZoneChangedListener.fromDragZone).isEqualTo(bubbleLeftDragZone)
assertThat(dragZoneChangedListener.toDragZone).isEqualTo(bubbleRightDragZone)
- dropTargetManager.onDragUpdated(
- dismissDragZone.bounds.centerX(),
- dismissDragZone.bounds.centerY()
- )
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ dropTargetManager.onDragUpdated(
+ dismissDragZone.bounds.centerX(),
+ dismissDragZone.bounds.centerY()
+ )
+ }
assertThat(dragZoneChangedListener.fromDragZone).isEqualTo(bubbleRightDragZone)
assertThat(dragZoneChangedListener.toDragZone).isEqualTo(dismissDragZone)
}
@@ -100,10 +119,12 @@ class DropTargetManagerTest {
DraggedObject.Bubble(BubbleBarLocation.LEFT),
listOf(bubbleLeftDragZone, bubbleRightDragZone, dismissDragZone)
)
- dropTargetManager.onDragUpdated(
- bubbleLeftDragZone.bounds.centerX(),
- bubbleLeftDragZone.bounds.centerY()
- )
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ dropTargetManager.onDragUpdated(
+ bubbleLeftDragZone.bounds.centerX(),
+ bubbleLeftDragZone.bounds.centerY()
+ )
+ }
assertThat(dragZoneChangedListener.fromDragZone).isNull()
assertThat(dragZoneChangedListener.toDragZone).isNull()
}
@@ -118,7 +139,9 @@ class DropTargetManagerTest {
val pointY = 200
assertThat(bubbleLeftDragZone.contains(pointX, pointY)).isFalse()
assertThat(bubbleRightDragZone.contains(pointX, pointY)).isFalse()
- dropTargetManager.onDragUpdated(pointX, pointY)
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ dropTargetManager.onDragUpdated(pointX, pointY)
+ }
assertThat(dragZoneChangedListener.fromDragZone).isNull()
assertThat(dragZoneChangedListener.toDragZone).isNull()
}
@@ -135,27 +158,30 @@ class DropTargetManagerTest {
// drag to a point that is within both the bubble right zone and split zone
val (pointX, pointY) =
- Pair(
- bubbleRightDragZone.bounds.centerX(),
- bubbleRightDragZone.bounds.centerY()
- )
+ Pair(bubbleRightDragZone.bounds.centerX(), bubbleRightDragZone.bounds.centerY())
assertThat(splitDragZone.contains(pointX, pointY)).isTrue()
- dropTargetManager.onDragUpdated(pointX, pointY)
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ dropTargetManager.onDragUpdated(pointX, pointY)
+ }
// verify we dragged to the bubble right zone because that has higher priority than split
assertThat(dragZoneChangedListener.fromDragZone).isEqualTo(bubbleLeftDragZone)
assertThat(dragZoneChangedListener.toDragZone).isEqualTo(bubbleRightDragZone)
- dropTargetManager.onDragUpdated(
- bubbleRightDragZone.bounds.centerX(),
- 150 // below the bubble and dismiss drag zones but within split
- )
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ dropTargetManager.onDragUpdated(
+ bubbleRightDragZone.bounds.centerX(),
+ 150 // below the bubble and dismiss drag zones but within split
+ )
+ }
assertThat(dragZoneChangedListener.fromDragZone).isEqualTo(bubbleRightDragZone)
assertThat(dragZoneChangedListener.toDragZone).isEqualTo(splitDragZone)
val (dismissPointX, dismissPointY) =
Pair(dismissDragZone.bounds.centerX(), dismissDragZone.bounds.centerY())
assertThat(splitDragZone.contains(dismissPointX, dismissPointY)).isTrue()
- dropTargetManager.onDragUpdated(dismissPointX, dismissPointY)
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ dropTargetManager.onDragUpdated(dismissPointX, dismissPointY)
+ }
assertThat(dragZoneChangedListener.fromDragZone).isEqualTo(splitDragZone)
assertThat(dragZoneChangedListener.toDragZone).isEqualTo(dismissDragZone)
}
@@ -166,7 +192,9 @@ class DropTargetManagerTest {
DraggedObject.Bubble(BubbleBarLocation.LEFT),
listOf(bubbleLeftDragZone, bubbleRightDragZone, dismissDragZone)
)
- dropTargetManager.onDragEnded()
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ dropTargetManager.onDragEnded()
+ }
dropTargetManager.onDragUpdated(
bubbleRightDragZone.bounds.centerX(),
bubbleRightDragZone.bounds.centerY()
@@ -175,6 +203,129 @@ class DropTargetManagerTest {
assertThat(dragZoneChangedListener.toDragZone).isNull()
}
+ @Test
+ fun onDragStarted_dropTargetAddedToContainer() {
+ dropTargetManager.onDragStarted(
+ DraggedObject.Bubble(BubbleBarLocation.LEFT),
+ listOf(bubbleLeftDragZone, bubbleRightDragZone)
+ )
+ assertThat(container.childCount).isEqualTo(1)
+ assertThat(dropTargetView.alpha).isEqualTo(0)
+ }
+
+ @Test
+ fun onDragEnded_dropTargetRemovedFromContainer() {
+ dropTargetManager.onDragStarted(
+ DraggedObject.Bubble(BubbleBarLocation.LEFT),
+ listOf(bubbleLeftDragZone, bubbleRightDragZone)
+ )
+ assertThat(container.childCount).isEqualTo(1)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ dropTargetManager.onDragEnded()
+ animatorTestRule.advanceTimeBy(250)
+ }
+ assertThat(container.childCount).isEqualTo(0)
+ }
+
+ @Test
+ fun startNewDrag_beforeDropTargetRemoved() {
+ dropTargetManager.onDragStarted(
+ DraggedObject.Bubble(BubbleBarLocation.LEFT),
+ listOf(bubbleLeftDragZone, bubbleRightDragZone)
+ )
+ assertThat(container.childCount).isEqualTo(1)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ dropTargetManager.onDragEnded()
+ // advance the timer by 100ms so the animation doesn't complete
+ animatorTestRule.advanceTimeBy(100)
+ }
+ assertThat(container.childCount).isEqualTo(1)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ dropTargetManager.onDragStarted(
+ DraggedObject.Bubble(BubbleBarLocation.LEFT),
+ listOf(bubbleLeftDragZone, bubbleRightDragZone)
+ )
+ }
+ assertThat(container.childCount).isEqualTo(1)
+ }
+
+ @Test
+ fun updateDragZone_withDropTarget_dropTargetUpdated() {
+ dropTargetManager.onDragStarted(
+ DraggedObject.Bubble(BubbleBarLocation.LEFT),
+ listOf(dismissDragZone, bubbleLeftDragZone, bubbleRightDragZone)
+ )
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ dropTargetManager.onDragUpdated(
+ bubbleRightDragZone.bounds.centerX(),
+ bubbleRightDragZone.bounds.centerY()
+ )
+ animatorTestRule.advanceTimeBy(250)
+ }
+
+ assertThat(dropTargetView.alpha).isEqualTo(1)
+ verifyDropTargetPosition(bubbleRightDragZone.dropTarget)
+ }
+
+ @Test
+ fun updateDragZone_withoutDropTarget_dropTargetHidden() {
+ dropTargetManager.onDragStarted(
+ DraggedObject.Bubble(BubbleBarLocation.LEFT),
+ listOf(dismissDragZone, bubbleLeftDragZone, bubbleRightDragZone)
+ )
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ dropTargetManager.onDragUpdated(
+ dismissDragZone.bounds.centerX(),
+ dismissDragZone.bounds.centerY()
+ )
+ animatorTestRule.advanceTimeBy(250)
+ }
+
+ assertThat(dropTargetView.alpha).isEqualTo(0)
+ }
+
+ @Test
+ fun updateDragZone_betweenZonesWithDropTarget_dropTargetUpdated() {
+ dropTargetManager.onDragStarted(
+ DraggedObject.Bubble(BubbleBarLocation.LEFT),
+ listOf(dismissDragZone, bubbleLeftDragZone, bubbleRightDragZone)
+ )
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ dropTargetManager.onDragUpdated(
+ bubbleRightDragZone.bounds.centerX(),
+ bubbleRightDragZone.bounds.centerY()
+ )
+ animatorTestRule.advanceTimeBy(250)
+ }
+
+ assertThat(dropTargetView.alpha).isEqualTo(1)
+ verifyDropTargetPosition(bubbleRightDragZone.dropTarget)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ dropTargetManager.onDragUpdated(
+ bubbleLeftDragZone.bounds.centerX(),
+ bubbleLeftDragZone.bounds.centerY()
+ )
+ animatorTestRule.advanceTimeBy(250)
+ }
+
+ assertThat(dropTargetView.alpha).isEqualTo(1)
+ verifyDropTargetPosition(bubbleLeftDragZone.dropTarget)
+ }
+
+ private fun verifyDropTargetPosition(rect: Rect) {
+ assertThat(dropTargetView.scaleX).isEqualTo(rect.width())
+ assertThat(dropTargetView.scaleY).isEqualTo(rect.height())
+ assertThat(dropTargetView.translationX).isEqualTo(rect.exactCenterX())
+ assertThat(dropTargetView.translationY).isEqualTo(rect.exactCenterY())
+ }
+
private class FakeDragZoneChangedListener : DropTargetManager.DragZoneChangedListener {
var initialDragZone: DragZone? = null
var fromDragZone: DragZone? = null
@@ -183,6 +334,7 @@ class DropTargetManagerTest {
override fun onInitialDragZoneSet(dragZone: DragZone) {
initialDragZone = dragZone
}
+
override fun onDragZoneChanged(from: DragZone, to: DragZone) {
fromDragZone = from
toDragZone = to