[go: nahoru, domu]

[Text Selection] Cross-widget Selection.

Bug: 134533989
Test: ./gradlew test
Change-Id: I133c6f3f264937c259a761429b60ac76d4fe4b60
diff --git a/ui/framework/api/1.0.0-alpha01.txt b/ui/framework/api/1.0.0-alpha01.txt
index 104650a..eb13089 100644
--- a/ui/framework/api/1.0.0-alpha01.txt
+++ b/ui/framework/api/1.0.0-alpha01.txt
@@ -264,13 +264,22 @@
 
   public final class SelectionContainerKt {
     ctor public SelectionContainerKt();
-    method public static void SelectionContainer(androidx.ui.core.selection.Selection? selection, kotlin.jvm.functions.Function1<? super androidx.ui.core.selection.Selection,kotlin.Unit> onSelectionChange, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+    method public static void SelectionContainer(androidx.ui.core.selection.Selection? selection, kotlin.jvm.functions.Function1<? super androidx.ui.core.selection.Selection,kotlin.Unit> onSelectionChange, androidx.ui.core.selection.SelectionMode mode = SelectionMode.Vertical, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+  }
+
+  public final class SelectionKt {
+    ctor public SelectionKt();
   }
 
   public final class SelectionManagerKt {
     ctor public SelectionManagerKt();
   }
 
+  public enum SelectionMode {
+    enum_constant public static final androidx.ui.core.selection.SelectionMode Horizontal;
+    enum_constant public static final androidx.ui.core.selection.SelectionMode Vertical;
+  }
+
 }
 
 package androidx.ui.core.vectorgraphics {
diff --git a/ui/framework/api/current.txt b/ui/framework/api/current.txt
index 104650a..eb13089 100644
--- a/ui/framework/api/current.txt
+++ b/ui/framework/api/current.txt
@@ -264,13 +264,22 @@
 
   public final class SelectionContainerKt {
     ctor public SelectionContainerKt();
-    method public static void SelectionContainer(androidx.ui.core.selection.Selection? selection, kotlin.jvm.functions.Function1<? super androidx.ui.core.selection.Selection,kotlin.Unit> onSelectionChange, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+    method public static void SelectionContainer(androidx.ui.core.selection.Selection? selection, kotlin.jvm.functions.Function1<? super androidx.ui.core.selection.Selection,kotlin.Unit> onSelectionChange, androidx.ui.core.selection.SelectionMode mode = SelectionMode.Vertical, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+  }
+
+  public final class SelectionKt {
+    ctor public SelectionKt();
   }
 
   public final class SelectionManagerKt {
     ctor public SelectionManagerKt();
   }
 
+  public enum SelectionMode {
+    enum_constant public static final androidx.ui.core.selection.SelectionMode Horizontal;
+    enum_constant public static final androidx.ui.core.selection.SelectionMode Vertical;
+  }
+
 }
 
 package androidx.ui.core.vectorgraphics {
diff --git a/ui/framework/src/main/java/androidx/ui/core/Text.kt b/ui/framework/src/main/java/androidx/ui/core/Text.kt
index b039494..e1f7fc1 100644
--- a/ui/framework/src/main/java/androidx/ui/core/Text.kt
+++ b/ui/framework/src/main/java/androidx/ui/core/Text.kt
@@ -19,7 +19,6 @@
 import androidx.ui.engine.geometry.Offset
 import androidx.ui.engine.text.TextAlign
 import androidx.ui.engine.text.TextDirection
-import androidx.ui.engine.text.TextPosition
 import androidx.ui.graphics.Color
 import androidx.ui.painting.TextSpan
 import androidx.ui.painting.TextStyle
@@ -37,10 +36,8 @@
 import androidx.compose.memo
 import androidx.compose.onDispose
 import androidx.compose.unaryPlus
-import androidx.ui.core.selection.Selection
 import androidx.ui.core.selection.SelectionRegistrarAmbient
-import androidx.ui.core.selection.TextSelectionHandler
-import androidx.ui.engine.text.TextAffinity
+import androidx.ui.core.selection.TextSelectionHandlerImpl
 import androidx.ui.painting.TextPainter
 
 private val DefaultTextAlign: TextAlign = TextAlign.Start
@@ -206,54 +203,12 @@
         })
 
         +onCommit(textPainter) {
-            val id = registrar.subscribe(object : TextSelectionHandler {
-                // Get selection for the start and end coordinates pair.
-                override fun getSelection(
-                    selectionCoordinates: Pair<PxPosition, PxPosition>,
-                    containerLayoutCoordinates: LayoutCoordinates
-                ): Selection? {
-                    val relativePosition = containerLayoutCoordinates.childToLocal(
-                        layoutCoordinates.value!!, PxPosition.Origin
-                    )
-                    val startPx = selectionCoordinates.first - relativePosition
-                    val endPx = selectionCoordinates.second - relativePosition
-
-                    val startOffset = Offset(startPx.x.value, startPx.y.value)
-                    val endOffset = Offset(endPx.x.value, endPx.y.value)
-
-                    var selectionStart = textPainter.getPositionForOffset(startOffset)
-                    var selectionEnd = textPainter.getPositionForOffset(endOffset)
-
-                    if (selectionStart.offset == selectionEnd.offset) {
-                        val wordBoundary = textPainter.getWordBoundary(selectionStart)
-                        selectionStart =
-                            TextPosition(wordBoundary.start, selectionStart.affinity)
-                        selectionEnd = TextPosition(wordBoundary.end, selectionEnd.affinity)
-                    } else {
-                        // Currently on Android, selection end is the offset after last character.
-                        // But when dragging happens, current Crane Text Selection end is the offset
-                        // of the last character. Thus before calling drawing selection background,
-                        // make the selection end matches Android behaviour.
-                        selectionEnd = TextPosition(selectionEnd.offset + 1, TextAffinity.upstream)
-                    }
-
-                    internalSelection.value =
-                        TextSelection(selectionStart.offset, selectionEnd.offset)
-
-                    // In Crane Text Selection, the selection end should be the last character, thus
-                    // make the selection end matches Crane behaviour.
-                    selectionEnd = TextPosition(selectionEnd.offset - 1, TextAffinity.upstream)
-
-                    return Selection(
-                        startOffset =
-                        textPainter.getBoundingBoxForTextPosition(selectionStart),
-                        endOffset =
-                        textPainter.getBoundingBoxForTextPosition(selectionEnd),
-                        startLayoutCoordinates = layoutCoordinates.value!!,
-                        endLayoutCoordinates = layoutCoordinates.value!!
-                    )
-                }
-            })
+            val id = registrar.subscribe(
+                TextSelectionHandlerImpl(
+                    textPainter = textPainter,
+                    layoutCoordinates = layoutCoordinates.value,
+                     internalSelection.value = it })
+            )
             onDispose {
                 registrar.unsubscribe(id)
             }
diff --git a/ui/framework/src/main/java/androidx/ui/core/selection/Selection.kt b/ui/framework/src/main/java/androidx/ui/core/selection/Selection.kt
index fd376e0..786a3d6 100644
--- a/ui/framework/src/main/java/androidx/ui/core/selection/Selection.kt
+++ b/ui/framework/src/main/java/androidx/ui/core/selection/Selection.kt
@@ -45,4 +45,28 @@
      * does not contain the end of the selection, this should be null.
      */
     val endLayoutCoordinates: LayoutCoordinates?
-)
\ No newline at end of file
+) {
+    internal fun merge(other: Selection): Selection {
+        // TODO: combine two selections' contents with styles together.
+        var currentSelection = this.copy()
+        if (other.startLayoutCoordinates != null) {
+            currentSelection = currentSelection.copy(
+                startOffset = other.startOffset,
+                startLayoutCoordinates = other.startLayoutCoordinates
+            )
+        }
+        if (other.endLayoutCoordinates != null) {
+            currentSelection = currentSelection.copy(
+                endOffset = other.endOffset,
+                endLayoutCoordinates = other.endLayoutCoordinates
+            )
+        }
+        return currentSelection
+    }
+}
+
+internal operator fun Selection?.plus(rhs: Selection?): Selection? {
+    if (this == null) return rhs
+    if (rhs == null) return this
+    return merge(rhs)
+}
diff --git a/ui/framework/src/main/java/androidx/ui/core/selection/SelectionContainer.kt b/ui/framework/src/main/java/androidx/ui/core/selection/SelectionContainer.kt
index 84d9793..3b6ab5a 100644
--- a/ui/framework/src/main/java/androidx/ui/core/selection/SelectionContainer.kt
+++ b/ui/framework/src/main/java/androidx/ui/core/selection/SelectionContainer.kt
@@ -51,6 +51,8 @@
     selection: Selection?,
     /** A function containing customized behaviour when selection changes. */
     onSelectionChange: (Selection?) -> Unit,
+    /** Selection mode. The default mode is Vertical. */
+    mode: SelectionMode = SelectionMode.Vertical,
     @Children children: @Composable() () -> Unit
 ) {
     val manager = +memo { SelectionManager() }
@@ -60,6 +62,7 @@
     // +memo(onSelectionChange) { manager. }
     manager.selection = selection
     manager.>
+    manager.mode = mode
 
     SelectionRegistrarAmbient.Provider(value = manager) {
         val content = @Composable {
diff --git a/ui/framework/src/main/java/androidx/ui/core/selection/SelectionManager.kt b/ui/framework/src/main/java/androidx/ui/core/selection/SelectionManager.kt
index 64727fd..53c9ef5 100644
--- a/ui/framework/src/main/java/androidx/ui/core/selection/SelectionManager.kt
+++ b/ui/framework/src/main/java/androidx/ui/core/selection/SelectionManager.kt
@@ -23,29 +23,6 @@
 import androidx.ui.core.px
 import androidx.ui.engine.geometry.Rect
 
-/**
- * An interface handling selection. Get selection from a widget by passing in the start and end of
- * selection in a selection container as a pair, and the layout coordinates of the selection
- * container.
- */
-internal interface TextSelectionHandler {
-    fun getSelection(
-        selectionCoordinates: Pair<PxPosition, PxPosition>,
-        containerLayoutCoordinates: LayoutCoordinates
-    ): Selection?
-}
-
-/**
- *  An interface allowing a Text composable to "register" and "unregister" itself with the class
- *  implementing the interface.
- */
-internal interface SelectionRegistrar {
-    // TODO(qqd): Replace Any with a type in future.
-    fun subscribe(handler: TextSelectionHandler): Any
-
-    fun unsubscribe(key: Any)
-}
-
 internal class SelectionManager : SelectionRegistrar {
     /**
      * The current selection.
@@ -60,6 +37,11 @@
     var onSelectionChange: (Selection?) -> Unit = {}
 
     /**
+     * The selection mode. The default value is Vertical.
+     */
+    var mode: SelectionMode = SelectionMode.Vertical
+
+    /**
      * Layout Coordinates of the selection container.
      */
     lateinit var containerLayoutCoordinates: LayoutCoordinates
@@ -100,7 +82,10 @@
     fun onPress(position: PxPosition) {
         var result: Selection? = null
         for (handler in handlers) {
-            result = handler.getSelection(Pair(position, position), containerLayoutCoordinates)
+            result += handler.getSelection(
+                Pair(position, position),
+                containerLayoutCoordinates,
+                mode)
         }
         onSelectionChange(result)
     }
@@ -170,9 +155,10 @@
                     }
 
                 for (handler in handlers) {
-                    result = handler.getSelection(
+                    result += handler.getSelection(
                         Pair(currentStart, currentEnd),
-                        containerLayoutCoordinates)
+                        containerLayoutCoordinates,
+                        mode)
                 }
                 onSelectionChange(result)
                 return dragDistance
diff --git a/ui/framework/src/main/java/androidx/ui/core/selection/SelectionMode.kt b/ui/framework/src/main/java/androidx/ui/core/selection/SelectionMode.kt
new file mode 100644
index 0000000..0240940b
--- /dev/null
+++ b/ui/framework/src/main/java/androidx/ui/core/selection/SelectionMode.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * 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 androidx.ui.core.selection
+
+import androidx.ui.core.PxPosition
+import androidx.ui.core.px
+import androidx.ui.engine.geometry.Rect
+
+/**
+ * The enum class allows user to decide the selection mode.
+ */
+enum class SelectionMode {
+    /**
+     * When selection handles are dragged across widgets, selection extends by row, for example,
+     * when the end selection handle is dragged down, upper rows will be selected first, and the
+     * lower rows.
+     */
+    Vertical {
+        override fun isSelected(
+            box: Rect,
+            start: PxPosition,
+            end: PxPosition
+        ): Boolean {
+            // When the end of the selection is above the top of the widget, the widget is outside
+            // of the selection range.
+            if (end.y < box.top.px) return false
+
+            // When the end of the selection is on the left of the widget, and not below the bottom
+            // of widget, the widget is outside of the selection range.
+            if (end.x < box.left.px && end.y <= box.bottom.px) return false
+
+            // When the start of the selection is below the bottom of the widget, the widget is
+            // outside of the selection range.
+            if (start.y > box.bottom.px) return false
+
+            // When the start of the selection is on the right of the widget, and not above the top
+            // of the widget, the widget is outside of the selection range.
+            if (start.x > box.right.px && start.y >= box.top.px) return false
+
+            return true
+        }
+    },
+
+    /**
+     * When selection handles are dragged across widgets, selection extends by column, for example,
+     * when the end selection handle is dragged to the right, left columns will be selected first,
+     * and the right rows.
+     */
+    Horizontal {
+        override fun isSelected(
+            box: Rect,
+            start: PxPosition,
+            end: PxPosition
+        ): Boolean {
+            // When the end of the selection is on the left of the widget, the widget is outside of
+            // the selection range.
+            if (end.x < box.left.px) return false
+
+            // When the end of the selection is on the top of the widget, and the not on the right
+            // of the widget, the widget is outside of the selection range.
+            if (end.y < box.top.px && end.x <= box.right.px) return false
+
+            // When the start of the selection is on the right of the widget, the widget is outside
+            // of the selection range.
+            if (start.x > box.right.px) return false
+
+            // When the start of the selection is below the widget, and not on the left of the
+            // widget, the widget is outside of the selection range.
+            if (start.y > box.bottom.px && start.x >= box.left.px) return false
+
+            return true
+        }
+    };
+
+    internal abstract fun isSelected(
+        box: Rect,
+        start: PxPosition,
+        end: PxPosition
+    ): Boolean
+}
diff --git a/ui/framework/src/main/java/androidx/ui/core/selection/SelectionRegistrar.kt b/ui/framework/src/main/java/androidx/ui/core/selection/SelectionRegistrar.kt
new file mode 100644
index 0000000..598dea6
--- /dev/null
+++ b/ui/framework/src/main/java/androidx/ui/core/selection/SelectionRegistrar.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * 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 androidx.ui.core.selection
+
+/**
+ *  An interface allowing a Text composable to "register" and "unregister" itself with the class
+ *  implementing the interface.
+ */
+internal interface SelectionRegistrar {
+    // TODO(qqd): Replace Any with a type in future.
+    fun subscribe(handler: TextSelectionHandler): Any
+
+    fun unsubscribe(key: Any)
+}
diff --git a/ui/framework/src/main/java/androidx/ui/core/selection/TextSelectionHandler.kt b/ui/framework/src/main/java/androidx/ui/core/selection/TextSelectionHandler.kt
new file mode 100644
index 0000000..d58caf5
--- /dev/null
+++ b/ui/framework/src/main/java/androidx/ui/core/selection/TextSelectionHandler.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * 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 androidx.ui.core.selection
+
+import androidx.ui.core.LayoutCoordinates
+import androidx.ui.core.PxPosition
+
+/**
+ * An interface handling selection. Get selection from a widget by passing in the start and end of
+ * selection in a selection container as a pair, and the layout coordinates of the selection
+ * container.
+ */
+internal interface TextSelectionHandler {
+    fun getSelection(
+        selectionCoordinates: Pair<PxPosition, PxPosition>,
+        containerLayoutCoordinates: LayoutCoordinates,
+        mode: SelectionMode
+    ): Selection?
+}
diff --git a/ui/framework/src/main/java/androidx/ui/core/selection/TextSelectionHandlerImpl.kt b/ui/framework/src/main/java/androidx/ui/core/selection/TextSelectionHandlerImpl.kt
new file mode 100644
index 0000000..0a70040
--- /dev/null
+++ b/ui/framework/src/main/java/androidx/ui/core/selection/TextSelectionHandlerImpl.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * 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 androidx.ui.core.selection
+
+import androidx.ui.core.LayoutCoordinates
+import androidx.ui.core.PxPosition
+import androidx.ui.core.px
+import androidx.ui.engine.geometry.Offset
+import androidx.ui.engine.geometry.Rect
+import androidx.ui.engine.text.TextAffinity
+import androidx.ui.engine.text.TextPosition
+import androidx.ui.painting.TextPainter
+import androidx.ui.services.text_editing.TextSelection
+import kotlin.math.max
+import kotlin.math.min
+
+/**
+ * A class contains all the logic for text selection.
+ */
+internal class TextSelectionHandlerImpl(
+    val textPainter: TextPainter,
+    val layoutCoordinates: LayoutCoordinates?,
+    var onSelectionChange: (TextSelection?) -> Unit = {}
+) : TextSelectionHandler {
+
+    /** Last TextPosition of the text in text widget. */
+    private val lastTextPosition: Int
+    /** Bounding box of the text widget. */
+    private val box: Rect
+
+    init {
+        lastTextPosition = textPainter.text?.let { it.toPlainText().length - 1 } ?: 0
+        box = Rect(
+            top = 0f,
+            bottom = textPainter.height,
+            left = 0f,
+            right = textPainter.width
+        )
+    }
+
+    // Get selection for the start and end coordinates pair.
+    override fun getSelection(
+        selectionCoordinates: Pair<PxPosition, PxPosition>,
+        containerLayoutCoordinates: LayoutCoordinates,
+        mode: SelectionMode
+    ): Selection? {
+        val relativePosition = containerLayoutCoordinates.childToLocal(
+            layoutCoordinates!!, PxPosition.Origin
+        )
+        val startPx = selectionCoordinates.first - relativePosition
+        val endPx = selectionCoordinates.second - relativePosition
+
+        if (!mode.isSelected(box, start = startPx, end = endPx)) {
+            onSelectionChange(null)
+            return null
+        }
+
+        var textSelectionStart = getSelectionBorder(startPx, true).first
+        var startLayoutCoordinates = getSelectionBorder(startPx, true).second
+
+        var textSelectionEnd = getSelectionBorder(endPx, false).first
+        var endLayoutCoordinates = getSelectionBorder(endPx, false).second
+
+        if (textSelectionStart.offset == textSelectionEnd.offset) {
+            val wordBoundary = textPainter.getWordBoundary(textSelectionStart)
+            textSelectionStart =
+                TextPosition(wordBoundary.start, textSelectionStart.affinity)
+            textSelectionEnd = TextPosition(wordBoundary.end, textSelectionEnd.affinity)
+        } else {
+            // Currently on Android, selection end is the offset after last character.
+            // But when dragging happens, current Crane Text Selection end is the offset
+            // of the last character. Thus before calling drawing selection background,
+            // make the selection end matches Android behaviour.
+            textSelectionEnd =
+                TextPosition(textSelectionEnd.offset + 1, TextAffinity.upstream)
+        }
+
+        onSelectionChange(TextSelection(textSelectionStart.offset, textSelectionEnd.offset))
+
+        // In Crane Text Selection, the selection end should be the last character, thus
+        // make the selection end matches Crane behaviour.
+        textSelectionEnd =
+            TextPosition(textSelectionEnd.offset - 1, TextAffinity.upstream)
+
+        return Selection(
+            startOffset =
+            textPainter.getBoundingBoxForTextPosition(textSelectionStart),
+            endOffset =
+            textPainter.getBoundingBoxForTextPosition(textSelectionEnd),
+            startLayoutCoordinates = startLayoutCoordinates,
+            endLayoutCoordinates = endLayoutCoordinates
+        )
+    }
+
+    /**
+     * This function gets the border of the text selection. Border means either start or end of the
+     * selection.
+     */
+    private fun getSelectionBorder(
+        position: PxPosition,
+        isStart: Boolean
+    ): Pair<TextPosition, LayoutCoordinates?> {
+        // The text position of the border of selection. The default value is set to the beginning
+        // of the text widget for the start border, and the very last position of the text widget
+        // for the end border. If the widget contains the whole selection's border, this value will
+        // be reset.
+        var selectionBorder = TextPosition(
+            offset = if (isStart) 0 else lastTextPosition,
+            affinity = TextAffinity.upstream
+        )
+        // This LayoutCoordinates is for the widget which contains the whole selection's border. If
+        // the current widget does not contain the whole selection's border, this default null value
+        // will be returned.
+        var selectionBorderLayoutCoordinates: LayoutCoordinates? = null
+
+        // If the current text widget contains the whole selection's border, then find the exact
+        // text position of the border, and the LayoutCoordinates of the current widget will be
+        // returned.
+        if (position.x >= box.left.px &&
+            position.x <= box.right.px &&
+            position.y >= box.top.px &&
+            position.y <= box.bottom.px
+        ) {
+            val offset = Offset(position.x.value, position.y.value)
+            // Constrain the position of the selection border to be within the text range of the
+            // current widget.
+            val constrainedSelectionBorderPosition =
+                min(max(textPainter.getPositionForOffset(offset).offset, 0), lastTextPosition)
+            selectionBorder = TextPosition(
+                offset = constrainedSelectionBorderPosition,
+                affinity = TextAffinity.upstream
+            )
+            selectionBorderLayoutCoordinates = layoutCoordinates
+        }
+        return Pair(selectionBorder, selectionBorderLayoutCoordinates)
+    }
+}
diff --git a/ui/text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneText.kt b/ui/text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneText.kt
index 99f8585..be6506e 100644
--- a/ui/text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneText.kt
+++ b/ui/text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneText.kt
@@ -24,6 +24,7 @@
 import androidx.ui.core.px
 import androidx.ui.core.selection.Selection
 import androidx.ui.core.selection.SelectionContainer
+import androidx.ui.core.selection.SelectionMode
 import androidx.ui.engine.geometry.Offset
 import androidx.ui.engine.text.BaselineShift
 import androidx.ui.engine.text.FontStyle
@@ -97,6 +98,10 @@
                 EditLine()
                 TagLine(tag = "selection")
                 TextDemoSelection()
+                TagLine(tag = "selection in 2D Array Vertical")
+                TextDemoSelection2DArrayVertical()
+                TagLine(tag = "selection in 2D Array Horizontal")
+                TextDemoSelection2DArrayHorizontal()
                 TagLine(tag = "composable textspan")
                 TextDemoComposableTextSpan()
                 TagLine(tag = "fontSizeScale")
@@ -581,6 +586,93 @@
 }
 
 @Composable
+fun TextDemoSelection2DArrayVertical() {
+    var text = ""
+    for (i in 1..3) {
+        text = "$text$displayText" + "\n"
+    }
+
+    val colorList = listOf(
+        Color(0xFFFF0000.toInt()),
+        Color(0xFF00FF00.toInt()),
+        Color(0xFF0000FF.toInt()),
+        Color(0xFF00FFFF.toInt()),
+        Color(0xFFFF00FF.toInt()),
+        Color(0xFFFFFF00.toInt()),
+        Color(0xFF0000FF.toInt()),
+        Color(0xFF00FF00.toInt()),
+        Color(0xFFFF0000.toInt())
+        )
+
+    val selection = +state<Selection?> { null }
+    SelectionContainer(
+        selection = selection.value,
+         selection.value = it }) {
+        Column {
+            for (i in 0..2) {
+                Row {
+                    for (j in 0..2) {
+                        Text {
+                            Span(
+                                text = text,
+                                style = TextStyle(
+                                    color = colorList[i * 3 + j],
+                                    fontSize = fontSize6
+                                )
+                            )
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+@Composable
+fun TextDemoSelection2DArrayHorizontal() {
+    var text = ""
+    for (i in 1..3) {
+        text = "$text$displayText" + "\n"
+    }
+
+    val colorList = listOf(
+        Color(0xFFFF0000.toInt()),
+        Color(0xFF00FF00.toInt()),
+        Color(0xFF0000FF.toInt()),
+        Color(0xFF00FFFF.toInt()),
+        Color(0xFFFF00FF.toInt()),
+        Color(0xFFFFFF00.toInt()),
+        Color(0xFF0000FF.toInt()),
+        Color(0xFF00FF00.toInt()),
+        Color(0xFFFF0000.toInt())
+    )
+
+    val selection = +state<Selection?> { null }
+    SelectionContainer(
+        selection = selection.value,
+         selection.value = it },
+        mode = SelectionMode.Horizontal) {
+        Column {
+            for (i in 0..2) {
+                Row {
+                    for (j in 0..2) {
+                        Text {
+                            Span(
+                                text = text,
+                                style = TextStyle(
+                                    color = colorList[i * 3 + j],
+                                    fontSize = fontSize6
+                                )
+                            )
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+@Composable
 fun TextDemoComposableTextSpan() {
     Text {
         Span(text = "This is a ", style = TextStyle(fontSize = fontSize8)) {