[go: nahoru, domu]

blob: 64727fdd6f4780bfbadf6cc4f05f958ead7b0ab2 [file] [log] [blame]
Qingqing Deng6f56a912019-05-13 10:10:37 -07001/*
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
17package androidx.ui.core.selection
18
19import androidx.compose.Ambient
20import androidx.ui.core.LayoutCoordinates
21import androidx.ui.core.PxPosition
22import androidx.ui.core.gesture.DragObserver
23import androidx.ui.core.px
24import androidx.ui.engine.geometry.Rect
25
26/**
27 * An interface handling selection. Get selection from a widget by passing in the start and end of
28 * selection in a selection container as a pair, and the layout coordinates of the selection
29 * container.
30 */
31internal interface TextSelectionHandler {
32 fun getSelection(
33 selectionCoordinates: Pair<PxPosition, PxPosition>,
34 containerLayoutCoordinates: LayoutCoordinates
35 ): Selection?
36}
37
38/**
39 * An interface allowing a Text composable to "register" and "unregister" itself with the class
40 * implementing the interface.
41 */
42internal interface SelectionRegistrar {
43 // TODO(qqd): Replace Any with a type in future.
44 fun subscribe(handler: TextSelectionHandler): Any
45
46 fun unsubscribe(key: Any)
47}
48
49internal class SelectionManager : SelectionRegistrar {
50 /**
51 * The current selection.
52 */
53 var selection: Selection? = null
54
55 /**
56 * The manager will invoke this every time it comes to the conclusion that the selection should
57 * change. The expectation is that this callback will end up causing `setSelection` to get
58 * called. This is what makes this a "controlled component".
59 */
60 var onSelectionChange: (Selection?) -> Unit = {}
61
62 /**
63 * Layout Coordinates of the selection container.
64 */
65 lateinit var containerLayoutCoordinates: LayoutCoordinates
66
67 /**
68 * This is essentially the list of registered components that want
69 * to handle text selection that are below the SelectionContainer.
70 */
71 private val handlers = mutableSetOf<TextSelectionHandler>()
72
73 /**
74 * The beginning position of the drag gesture. Every time a new drag gesture starts, it wil be
75 * recalculated.
76 */
77 private var dragBeginPosition = PxPosition.Origin
78
79 /**
80 * The total distance being dragged of the drag gesture. Every time a new drag gesture starts,
81 * it will be zeroed out.
82 */
83 private var dragTotalDistance = PxPosition.Origin
84
85 /**
86 * Allow a Text composable to "register" itself with the manager
87 */
88 override fun subscribe(handler: TextSelectionHandler): Any {
89 handlers.add(handler)
90 return handler
91 }
92
93 /**
94 * Allow a Text composable to "unregister" itself with the manager
95 */
96 override fun unsubscribe(key: Any) {
97 handlers.remove(key as TextSelectionHandler)
98 }
99
100 fun onPress(position: PxPosition) {
101 var result: Selection? = null
102 for (handler in handlers) {
103 result = handler.getSelection(Pair(position, position), containerLayoutCoordinates)
104 }
105 onSelectionChange(result)
106 }
107
108 // Get the coordinates of a character. Currently, it's the middle point of the left edge of the
109 // bounding box of the character. This is a temporary solution.
110 // TODO(qqd): Read how Android solve this problem.
111 fun getCoordinatesForCharacter(box: Rect): PxPosition {
112 return PxPosition(box.left.px, box.top.px + (box.bottom.px - box.top.px) / 2)
113 }
114
115 fun handleDragObserver(dragStartHandle: Boolean): DragObserver {
116 return object : DragObserver {
117 override fun onStart() {
118 // The LayoutCoordinates of the widget where the drag gesture should begin. This
119 // is used to convert the position of the beginning of the drag gesture from the
120 // widget coordinates to selection container coordinates.
121 val beginLayoutCoordinates =
122 if (dragStartHandle) {
123 selection!!.startLayoutCoordinates!!
124 } else {
125 selection!!.endLayoutCoordinates!!
126 }
127 // The position of the character where the drag gesture should begin. This is in
128 // the widget coordinates.
129 val beginCoordinates =
130 getCoordinatesForCharacter(
131 if (dragStartHandle) {
132 selection!!.startOffset
133 } else {
134 selection!!.endOffset
135 }
136 )
137 // Convert the position where drag gesture begins from widget coordinates to
138 // selection container coordinates.
139 dragBeginPosition = containerLayoutCoordinates.childToLocal(
140 beginLayoutCoordinates,
141 beginCoordinates
142 )
143
144 // Zero out the total distance that being dragged.
145 dragTotalDistance = PxPosition.Origin
146 }
147
148 override fun onDrag(dragDistance: PxPosition): PxPosition {
149 var result = selection
150 dragTotalDistance += dragDistance
151
152 val currentStart =
153 if (dragStartHandle) {
154 dragBeginPosition + dragTotalDistance
155 } else {
156 containerLayoutCoordinates.childToLocal(
157 selection!!.startLayoutCoordinates!!,
158 getCoordinatesForCharacter(selection!!.startOffset)
159 )
160 }
161
162 val currentEnd =
163 if (dragStartHandle) {
164 containerLayoutCoordinates.childToLocal(
165 selection!!.endLayoutCoordinates!!,
166 getCoordinatesForCharacter(selection!!.endOffset)
167 )
168 } else {
169 dragBeginPosition + dragTotalDistance
170 }
171
172 for (handler in handlers) {
173 result = handler.getSelection(
174 Pair(currentStart, currentEnd),
175 containerLayoutCoordinates)
176 }
177 onSelectionChange(result)
178 return dragDistance
179 }
180 }
181 }
182}
183
184/** Ambient of SelectionRegistrar for SelectionManager. */
185internal val SelectionRegistrarAmbient = Ambient.of<SelectionRegistrar> { SelectionManager() }