Qingqing Deng | b745a94 | 2019-06-05 18:26:52 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2019 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
Louis Pullen-Freilich | ca6eca2 | 2020-07-20 00:31:45 +0100 | [diff] [blame] | 17 | package androidx.compose.foundation.text.selection |
Qingqing Deng | b745a94 | 2019-06-05 18:26:52 -0700 | [diff] [blame] | 18 | |
Nader Jawad | 22f5978 | 2020-05-14 17:07:11 -0700 | [diff] [blame] | 19 | import androidx.ui.geometry.Rect |
Nader Jawad | 6df0612 | 2020-06-03 15:27:08 -0700 | [diff] [blame] | 20 | import androidx.ui.geometry.Offset |
Qingqing Deng | b745a94 | 2019-06-05 18:26:52 -0700 | [diff] [blame] | 21 | |
| 22 | /** |
| 23 | * The enum class allows user to decide the selection mode. |
| 24 | */ |
Qingqing Deng | 608b8e7 | 2019-11-18 11:45:41 -0800 | [diff] [blame] | 25 | internal enum class SelectionMode { |
Qingqing Deng | b745a94 | 2019-06-05 18:26:52 -0700 | [diff] [blame] | 26 | /** |
Louis Pullen-Freilich | 5da28bd | 2019-10-15 17:05:07 +0100 | [diff] [blame] | 27 | * When selection handles are dragged across composables, selection extends by row, for example, |
Qingqing Deng | b745a94 | 2019-06-05 18:26:52 -0700 | [diff] [blame] | 28 | * when the end selection handle is dragged down, upper rows will be selected first, and the |
| 29 | * lower rows. |
| 30 | */ |
| 31 | Vertical { |
| 32 | override fun isSelected( |
Nader Jawad | 22f5978 | 2020-05-14 17:07:11 -0700 | [diff] [blame] | 33 | bounds: Rect, |
Nader Jawad | 6df0612 | 2020-06-03 15:27:08 -0700 | [diff] [blame] | 34 | start: Offset, |
| 35 | end: Offset |
Qingqing Deng | b745a94 | 2019-06-05 18:26:52 -0700 | [diff] [blame] | 36 | ): Boolean { |
Louis Pullen-Freilich | 5da28bd | 2019-10-15 17:05:07 +0100 | [diff] [blame] | 37 | // When the end of the selection is above the top of the composable, the composable is outside |
Qingqing Deng | b745a94 | 2019-06-05 18:26:52 -0700 | [diff] [blame] | 38 | // of the selection range. |
Nader Jawad | e6a9b33 | 2020-05-21 13:49:20 -0700 | [diff] [blame] | 39 | if (end.y < bounds.top) return false |
Qingqing Deng | b745a94 | 2019-06-05 18:26:52 -0700 | [diff] [blame] | 40 | |
Louis Pullen-Freilich | 5da28bd | 2019-10-15 17:05:07 +0100 | [diff] [blame] | 41 | // When the end of the selection is on the left of the composable, and not below the bottom |
| 42 | // of composable, the composable is outside of the selection range. |
Nader Jawad | e6a9b33 | 2020-05-21 13:49:20 -0700 | [diff] [blame] | 43 | if (end.x < bounds.left && end.y < bounds.bottom) return false |
Qingqing Deng | b745a94 | 2019-06-05 18:26:52 -0700 | [diff] [blame] | 44 | |
Louis Pullen-Freilich | 5da28bd | 2019-10-15 17:05:07 +0100 | [diff] [blame] | 45 | // When the start of the selection is below the bottom of the composable, the composable is |
Qingqing Deng | b745a94 | 2019-06-05 18:26:52 -0700 | [diff] [blame] | 46 | // outside of the selection range. |
Nader Jawad | e6a9b33 | 2020-05-21 13:49:20 -0700 | [diff] [blame] | 47 | if (start.y >= bounds.bottom) return false |
Qingqing Deng | b745a94 | 2019-06-05 18:26:52 -0700 | [diff] [blame] | 48 | |
Louis Pullen-Freilich | 5da28bd | 2019-10-15 17:05:07 +0100 | [diff] [blame] | 49 | // When the start of the selection is on the right of the composable, and not above the top |
| 50 | // of the composable, the composable is outside of the selection range. |
Nader Jawad | e6a9b33 | 2020-05-21 13:49:20 -0700 | [diff] [blame] | 51 | if (start.x >= bounds.right && start.y >= bounds.top) return false |
Qingqing Deng | b745a94 | 2019-06-05 18:26:52 -0700 | [diff] [blame] | 52 | |
| 53 | return true |
| 54 | } |
Qingqing Deng | a5d8095 | 2019-10-11 16:46:52 -0700 | [diff] [blame] | 55 | |
| 56 | override fun areHandlesCrossed( |
Nader Jawad | 22f5978 | 2020-05-14 17:07:11 -0700 | [diff] [blame] | 57 | bounds: Rect, |
Nader Jawad | 6df0612 | 2020-06-03 15:27:08 -0700 | [diff] [blame] | 58 | start: Offset, |
| 59 | end: Offset |
Qingqing Deng | a5d8095 | 2019-10-11 16:46:52 -0700 | [diff] [blame] | 60 | ): Boolean { |
Nader Jawad | e6a9b33 | 2020-05-21 13:49:20 -0700 | [diff] [blame] | 61 | if (start.y >= bounds.top && start.y < bounds.bottom && |
| 62 | end.y >= bounds.top && end.y < bounds.bottom |
Qingqing Deng | a5d8095 | 2019-10-11 16:46:52 -0700 | [diff] [blame] | 63 | ) { |
| 64 | // When the start and end of the selection are in the same row of widgets, check if |
| 65 | // x coordinates of the start and end are crossed each other. |
| 66 | return start.x > end.x |
| 67 | } else { |
| 68 | // When the start and end of the selection are not in the same row of widgets, check |
| 69 | // if y coordinates of the start and end are crossed each other. |
| 70 | return start.y > end.y |
| 71 | } |
| 72 | } |
Qingqing Deng | b745a94 | 2019-06-05 18:26:52 -0700 | [diff] [blame] | 73 | }, |
| 74 | |
| 75 | /** |
Louis Pullen-Freilich | 5da28bd | 2019-10-15 17:05:07 +0100 | [diff] [blame] | 76 | * When selection handles are dragged across composables, selection extends by column, for example, |
Qingqing Deng | b745a94 | 2019-06-05 18:26:52 -0700 | [diff] [blame] | 77 | * when the end selection handle is dragged to the right, left columns will be selected first, |
| 78 | * and the right rows. |
| 79 | */ |
| 80 | Horizontal { |
| 81 | override fun isSelected( |
Nader Jawad | 22f5978 | 2020-05-14 17:07:11 -0700 | [diff] [blame] | 82 | bounds: Rect, |
Nader Jawad | 6df0612 | 2020-06-03 15:27:08 -0700 | [diff] [blame] | 83 | start: Offset, |
| 84 | end: Offset |
Qingqing Deng | b745a94 | 2019-06-05 18:26:52 -0700 | [diff] [blame] | 85 | ): Boolean { |
Louis Pullen-Freilich | 5da28bd | 2019-10-15 17:05:07 +0100 | [diff] [blame] | 86 | // When the end of the selection is on the left of the composable, the composable is outside of |
Qingqing Deng | b745a94 | 2019-06-05 18:26:52 -0700 | [diff] [blame] | 87 | // the selection range. |
Nader Jawad | e6a9b33 | 2020-05-21 13:49:20 -0700 | [diff] [blame] | 88 | if (end.x < bounds.left) return false |
Qingqing Deng | b745a94 | 2019-06-05 18:26:52 -0700 | [diff] [blame] | 89 | |
Louis Pullen-Freilich | 5da28bd | 2019-10-15 17:05:07 +0100 | [diff] [blame] | 90 | // When the end of the selection is on the top of the composable, and the not on the right |
| 91 | // of the composable, the composable is outside of the selection range. |
Nader Jawad | e6a9b33 | 2020-05-21 13:49:20 -0700 | [diff] [blame] | 92 | if (end.y < bounds.top && end.x < bounds.right) return false |
Qingqing Deng | b745a94 | 2019-06-05 18:26:52 -0700 | [diff] [blame] | 93 | |
Louis Pullen-Freilich | 5da28bd | 2019-10-15 17:05:07 +0100 | [diff] [blame] | 94 | // When the start of the selection is on the right of the composable, the composable is outside |
Qingqing Deng | b745a94 | 2019-06-05 18:26:52 -0700 | [diff] [blame] | 95 | // of the selection range. |
Nader Jawad | e6a9b33 | 2020-05-21 13:49:20 -0700 | [diff] [blame] | 96 | if (start.x >= bounds.right) return false |
Qingqing Deng | b745a94 | 2019-06-05 18:26:52 -0700 | [diff] [blame] | 97 | |
Louis Pullen-Freilich | 5da28bd | 2019-10-15 17:05:07 +0100 | [diff] [blame] | 98 | // When the start of the selection is below the composable, and not on the left of the |
| 99 | // composable, the composable is outside of the selection range. |
Nader Jawad | e6a9b33 | 2020-05-21 13:49:20 -0700 | [diff] [blame] | 100 | if (start.y >= bounds.bottom && start.x >= bounds.left) return false |
Qingqing Deng | b745a94 | 2019-06-05 18:26:52 -0700 | [diff] [blame] | 101 | |
| 102 | return true |
| 103 | } |
Qingqing Deng | a5d8095 | 2019-10-11 16:46:52 -0700 | [diff] [blame] | 104 | |
| 105 | override fun areHandlesCrossed( |
Nader Jawad | 22f5978 | 2020-05-14 17:07:11 -0700 | [diff] [blame] | 106 | bounds: Rect, |
Nader Jawad | 6df0612 | 2020-06-03 15:27:08 -0700 | [diff] [blame] | 107 | start: Offset, |
| 108 | end: Offset |
Qingqing Deng | a5d8095 | 2019-10-11 16:46:52 -0700 | [diff] [blame] | 109 | ): Boolean { |
Nader Jawad | e6a9b33 | 2020-05-21 13:49:20 -0700 | [diff] [blame] | 110 | if (start.x >= bounds.left && start.x < bounds.right && |
| 111 | end.x >= bounds.left && end.x < bounds.right |
Qingqing Deng | a5d8095 | 2019-10-11 16:46:52 -0700 | [diff] [blame] | 112 | ) { |
| 113 | // When the start and end of the selection are in the same column of widgets, |
| 114 | // check if y coordinates of the start and end are crossed each other. |
| 115 | return start.y > end.y |
| 116 | } else { |
| 117 | // When the start and end of the selection are not in the same column of widgets, |
| 118 | // check if x coordinates of the start and end are crossed each other. |
| 119 | return start.x > end.x |
| 120 | } |
| 121 | } |
Qingqing Deng | b745a94 | 2019-06-05 18:26:52 -0700 | [diff] [blame] | 122 | }; |
| 123 | |
Siyamed Sinir | 6228275 | 2019-10-20 22:34:30 -0700 | [diff] [blame] | 124 | /** |
| 125 | * Decides if Composable which has [bounds], should be accepted by the selection and |
| 126 | * change its selected state for a selection that starts at [start] and ends at [end]. |
| 127 | * |
Qingqing Deng | a5d8095 | 2019-10-11 16:46:52 -0700 | [diff] [blame] | 128 | * @param bounds Composable bounds of the widget to be checked. |
| 129 | * @param start The start coordinates of the selection, in SelectionContainer range. |
| 130 | * @param end The end coordinates of the selection, in SelectionContainer range. |
Siyamed Sinir | 6228275 | 2019-10-20 22:34:30 -0700 | [diff] [blame] | 131 | */ |
Qingqing Deng | b745a94 | 2019-06-05 18:26:52 -0700 | [diff] [blame] | 132 | internal abstract fun isSelected( |
Nader Jawad | 22f5978 | 2020-05-14 17:07:11 -0700 | [diff] [blame] | 133 | bounds: Rect, |
Nader Jawad | 6df0612 | 2020-06-03 15:27:08 -0700 | [diff] [blame] | 134 | start: Offset, |
| 135 | end: Offset |
Qingqing Deng | b745a94 | 2019-06-05 18:26:52 -0700 | [diff] [blame] | 136 | ): Boolean |
Qingqing Deng | a5d8095 | 2019-10-11 16:46:52 -0700 | [diff] [blame] | 137 | |
| 138 | /** |
| 139 | * Decides if the [start] and [end] handles of the selection are crossed around a Composable |
| 140 | * which has [bounds]. |
| 141 | * When the end handle is visually crossed the start handle, return true. |
| 142 | * |
| 143 | * @param bounds Composable bounds of the widget to be checked. |
| 144 | * @param start The start coordinates of the selection, in SelectionContainer range. |
| 145 | * @param end The end coordinates of the selection, in SelectionContainer range. |
| 146 | */ |
| 147 | internal abstract fun areHandlesCrossed( |
Nader Jawad | 22f5978 | 2020-05-14 17:07:11 -0700 | [diff] [blame] | 148 | bounds: Rect, |
Nader Jawad | 6df0612 | 2020-06-03 15:27:08 -0700 | [diff] [blame] | 149 | start: Offset, |
| 150 | end: Offset |
Qingqing Deng | a5d8095 | 2019-10-11 16:46:52 -0700 | [diff] [blame] | 151 | ): Boolean |
Qingqing Deng | b745a94 | 2019-06-05 18:26:52 -0700 | [diff] [blame] | 152 | } |