Skip to content

Commit 4781b6d

Browse files
authored
Merge branch 'JetBrains:jb-main' into webWakeLock
2 parents 5870045 + debecae commit 4781b6d

12 files changed

Lines changed: 667 additions & 283 deletions

File tree

compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/modifiers/SelectionController.desktop.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,4 @@ import androidx.compose.ui.layout.LayoutCoordinates
2323
internal actual fun SelectionRegistrar.makeSelectionModifier(
2424
selectableId: Long,
2525
layoutCoordinates: () -> LayoutCoordinates?
26-
): Modifier = makeDefaultSelectionModifier(selectableId, layoutCoordinates)
26+
): Modifier = makeSkikoSelectionModifier(selectableId, layoutCoordinates)

compose/foundation/foundation/src/iosMain/kotlin/androidx/compose/foundation/text/modifiers/SelectionController.ios.kt

Lines changed: 114 additions & 180 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import androidx.compose.foundation.text.selection.hasSelection
2727
import androidx.compose.foundation.text.selection.isMouseOrTouchPad
2828
import androidx.compose.ui.Modifier
2929
import androidx.compose.ui.geometry.Offset
30+
import androidx.compose.ui.geometry.Rect
31+
import androidx.compose.ui.geometry.Size
3032
import androidx.compose.ui.hapticfeedback.HapticFeedback
3133
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
3234
import androidx.compose.ui.input.pointer.AwaitPointerEventScope
@@ -44,9 +46,12 @@ import androidx.compose.ui.node.ModifierNodeElement
4446
import androidx.compose.ui.node.currentValueOf
4547
import androidx.compose.ui.platform.InspectorInfo
4648
import androidx.compose.ui.platform.LocalHapticFeedback
49+
import androidx.compose.ui.relocation.bringIntoView
4750
import androidx.compose.ui.util.fastAll
4851
import androidx.compose.ui.util.fastForEach
4952
import kotlin.coroutines.cancellation.CancellationException
53+
import kotlinx.coroutines.CoroutineStart
54+
import kotlinx.coroutines.launch
5055

5156
private interface CupertinoTextDragObserver {
5257
fun onStart(startPoint: Offset, selectionAdjustment: SelectionAdjustment)
@@ -59,24 +64,101 @@ internal actual fun SelectionRegistrar.makeSelectionModifier(
5964
selectableId: Long,
6065
layoutCoordinates: () -> LayoutCoordinates?
6166
): Modifier {
62-
val longPressDragObserver = object : CupertinoTextDragObserver {
67+
return CupertinoSelectionModifierElement(
68+
selectionRegistrar = this,
69+
selectableId = selectableId,
70+
layoutCoordinates = layoutCoordinates,
71+
)
72+
}
73+
74+
internal class CupertinoSelectionModifierElement(
75+
private val selectionRegistrar: SelectionRegistrar,
76+
private val selectableId: Long,
77+
private val layoutCoordinates: () -> LayoutCoordinates?,
78+
) : ModifierNodeElement<CupertinoSelectionModifierNode>() {
79+
80+
override fun create() =
81+
CupertinoSelectionModifierNode(
82+
selectionRegistrar = selectionRegistrar,
83+
selectableId = selectableId,
84+
layoutCoordinates = layoutCoordinates,
85+
)
86+
87+
override fun update(node: CupertinoSelectionModifierNode) {
88+
node.update(
89+
selectionRegistrar = selectionRegistrar,
90+
selectableId = selectableId,
91+
layoutCoordinates = layoutCoordinates,
92+
)
93+
}
94+
95+
override fun InspectorInfo.inspectableProperties() {
96+
name = "selection"
97+
properties["selectableId"] = selectableId
98+
}
99+
100+
override fun equals(other: Any?): Boolean {
101+
if (this === other) return true
102+
if (other !is CupertinoSelectionModifierElement) return false
103+
104+
return selectionRegistrar == other.selectionRegistrar &&
105+
selectableId == other.selectableId &&
106+
layoutCoordinates === other.layoutCoordinates
107+
}
108+
109+
override fun hashCode(): Int {
110+
var result = selectableId.hashCode()
111+
result = 31 * result + selectionRegistrar.hashCode()
112+
result = 31 * result + layoutCoordinates.hashCode()
113+
return result
114+
}
115+
}
116+
117+
internal class CupertinoSelectionModifierNode(
118+
private var selectionRegistrar: SelectionRegistrar,
119+
private var selectableId: Long,
120+
private var layoutCoordinates: () -> LayoutCoordinates?,
121+
) : DelegatingNode(), CompositionLocalConsumerModifierNode {
122+
123+
private val pointerInputNode = delegate(
124+
SuspendingPointerInputModifierNode {
125+
val hapticFeedback = currentValueOf(LocalHapticFeedback)
126+
val clicksCounter = ClicksCounter(viewConfiguration)
127+
128+
awaitEachGesture {
129+
val down = awaitDown()
130+
131+
if (
132+
down.isMouseOrTouchPad() &&
133+
down.buttons.isPrimaryPressed &&
134+
down.changes.fastAll { !it.isConsumed }
135+
) {
136+
mouseSelection(mouseSelectionObserver, clicksCounter, down)
137+
} else if (!down.isMouseOrTouchPad()) {
138+
touchSelection(longPressDragObserver, clicksCounter, down, hapticFeedback)
139+
}
140+
}
141+
}
142+
)
143+
144+
private val longPressDragObserver = object : CupertinoTextDragObserver {
63145
/**
64-
* The beginning position of the drag gesture. Every time a new drag gesture starts, it wil be
65-
* recalculated.
146+
* The beginning position of the drag gesture. Every time a new drag gesture starts, it will
147+
* be recalculated.
66148
*/
67149
var lastPosition = Offset.Zero
68150

69151
/**
70-
* The total distance being dragged of the drag gesture. Every time a new drag gesture starts,
71-
* it will be zeroed out.
152+
* The total distance being dragged of the drag gesture. Every time a new drag gesture
153+
* starts, it will be zeroed out.
72154
*/
73155
var dragTotalDistance = Offset.Zero
74156

75157
override fun onStart(startPoint: Offset, selectionAdjustment: SelectionAdjustment) {
76158
layoutCoordinates()?.let {
77159
if (!it.isAttached) return
78160

79-
notifySelectionUpdateStart(
161+
selectionRegistrar.notifySelectionUpdateStart(
80162
layoutCoordinates = it,
81163
startPosition = startPoint,
82164
adjustment = selectionAdjustment,
@@ -86,7 +168,7 @@ internal actual fun SelectionRegistrar.makeSelectionModifier(
86168
lastPosition = startPoint
87169
}
88170
// selection never started
89-
if (!hasSelection(selectableId)) return
171+
if (!selectionRegistrar.hasSelection(selectableId)) return
90172
// Zero out the total distance that being dragged.
91173
dragTotalDistance = Offset.Zero
92174
}
@@ -95,7 +177,7 @@ internal actual fun SelectionRegistrar.makeSelectionModifier(
95177
layoutCoordinates()?.let {
96178
if (!it.isAttached) return
97179
// selection never started, did not consume any drag
98-
if (!hasSelection(selectableId)) return
180+
if (!selectionRegistrar.hasSelection(selectableId)) return
99181

100182
dragTotalDistance += delta
101183
val newPosition = lastPosition + dragTotalDistance
@@ -105,7 +187,7 @@ internal actual fun SelectionRegistrar.makeSelectionModifier(
105187
// long-press is using SelectionAdjustment.WORD or
106188
// SelectionAdjustment.PARAGRAPH that updates the start handle position from
107189
// the dragBeginPosition.
108-
val consumed = notifySelectionUpdate(
190+
val consumed = selectionRegistrar.notifySelectionUpdate(
109191
layoutCoordinates = it,
110192
previousPosition = lastPosition,
111193
newPosition = delta,
@@ -121,193 +203,45 @@ internal actual fun SelectionRegistrar.makeSelectionModifier(
121203
}
122204

123205
override fun onStop() {
124-
if (hasSelection(selectableId)) {
125-
notifySelectionUpdateEnd()
206+
if (selectionRegistrar.hasSelection(selectableId)) {
207+
selectionRegistrar.notifySelectionUpdateEnd()
126208
}
127209
}
128210

129211
override fun onCancel() {
130-
if (hasSelection(selectableId)) {
131-
notifySelectionUpdateEnd()
212+
if (selectionRegistrar.hasSelection(selectableId)) {
213+
selectionRegistrar.notifySelectionUpdateEnd()
132214
}
133215
}
134216
}
135217

136-
// The rest of that method copied from SelectionController.kt
137-
val mouseSelectionObserver = object : MouseSelectionObserver {
138-
var lastPosition = Offset.Zero
139-
140-
override fun onExtend(downPosition: Offset): Boolean {
141-
layoutCoordinates()?.let { layoutCoordinates ->
142-
if (!layoutCoordinates.isAttached) return false
143-
val consumed = notifySelectionUpdate(
144-
layoutCoordinates = layoutCoordinates,
145-
newPosition = downPosition,
146-
previousPosition = lastPosition,
147-
isStartHandle = false,
148-
adjustment = SelectionAdjustment.None,
149-
isInTouchMode = false
150-
)
151-
if (consumed) {
152-
lastPosition = downPosition
153-
}
154-
return hasSelection(selectableId)
155-
}
156-
return false
157-
}
158-
159-
override fun onExtendDrag(dragPosition: Offset): Boolean {
160-
layoutCoordinates()?.let { layoutCoordinates ->
161-
if (!layoutCoordinates.isAttached) return false
162-
if (!hasSelection(selectableId)) return false
163-
164-
val consumed = notifySelectionUpdate(
165-
layoutCoordinates = layoutCoordinates,
166-
newPosition = dragPosition,
167-
previousPosition = lastPosition,
168-
isStartHandle = false,
169-
adjustment = SelectionAdjustment.None,
170-
isInTouchMode = false
171-
)
172-
173-
if (consumed) {
174-
lastPosition = dragPosition
175-
}
176-
}
177-
return true
178-
}
179-
180-
override fun onStart(
181-
downPosition: Offset,
182-
adjustment: SelectionAdjustment,
183-
clickCount: Int,
184-
): Boolean {
185-
layoutCoordinates()?.let {
186-
if (!it.isAttached) return false
218+
private fun createMouseSelectionObserver() = selectionRegistrar.skikoMouseSelectionObserver(
219+
selectableId = selectableId,
220+
layoutCoordinates = layoutCoordinates,
221+
bringIntoView = ::bringIntoView
222+
)
187223

188-
notifySelectionUpdateStart(
189-
layoutCoordinates = it,
190-
startPosition = downPosition,
191-
adjustment = adjustment,
192-
isInTouchMode = false
193-
)
224+
private var mouseSelectionObserver = createMouseSelectionObserver()
194225

195-
lastPosition = downPosition
196-
return hasSelection(selectableId)
226+
private fun bringIntoView(offset: Offset) {
227+
coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) {
228+
bringIntoView {
229+
Rect(offset = offset, size = Size.Zero)
197230
}
198-
199-
return false
200-
}
201-
202-
override fun onDrag(
203-
dragPosition: Offset,
204-
adjustment: SelectionAdjustment
205-
): Boolean {
206-
layoutCoordinates()?.let {
207-
if (!it.isAttached) return false
208-
if (!hasSelection(selectableId)) return false
209-
210-
val consumed = notifySelectionUpdate(
211-
layoutCoordinates = it,
212-
previousPosition = lastPosition,
213-
newPosition = dragPosition,
214-
isStartHandle = false,
215-
adjustment = adjustment,
216-
isInTouchMode = false
217-
)
218-
if (consumed) {
219-
lastPosition = dragPosition
220-
}
221-
}
222-
return true
223-
}
224-
225-
override fun onDragDone() {
226-
notifySelectionUpdateEnd()
227231
}
228232
}
229233

230-
return Modifier.selectionGestureInput(mouseSelectionObserver, longPressDragObserver)
231-
}
232-
233-
private fun Modifier.selectionGestureInput(
234-
mouseSelectionObserver: MouseSelectionObserver,
235-
textDragObserver: CupertinoTextDragObserver,
236-
): Modifier = this.then(
237-
SelectionGestureInputElement(
238-
mouseSelectionObserver = mouseSelectionObserver,
239-
textDragObserver = textDragObserver,
240-
)
241-
)
242-
243-
private class SelectionGestureInputElement(
244-
private val mouseSelectionObserver: MouseSelectionObserver,
245-
private val textDragObserver: CupertinoTextDragObserver,
246-
) : ModifierNodeElement<SelectionGestureInputNode>() {
247-
248-
override fun create(): SelectionGestureInputNode = SelectionGestureInputNode(
249-
mouseSelectionObserver = mouseSelectionObserver,
250-
textDragObserver = textDragObserver,
251-
)
252-
253-
override fun update(node: SelectionGestureInputNode) = node.update(
254-
mouseSelectionObserver = mouseSelectionObserver,
255-
textDragObserver = textDragObserver,
256-
)
257-
258-
override fun InspectorInfo.inspectableProperties() {
259-
name = "selectionGestureInput"
260-
properties["mouseSelectionObserver"] = mouseSelectionObserver
261-
properties["textDragObserver"] = textDragObserver
262-
}
263-
264-
override fun equals(other: Any?): Boolean =
265-
other is SelectionGestureInputElement &&
266-
other !== this &&
267-
mouseSelectionObserver == other.mouseSelectionObserver &&
268-
textDragObserver == other.textDragObserver
269-
270-
override fun hashCode(): Int {
271-
var result = mouseSelectionObserver.hashCode()
272-
result = 31 * result + textDragObserver.hashCode()
273-
return result
274-
}
275-
}
276-
277-
private class SelectionGestureInputNode(
278-
private var mouseSelectionObserver: MouseSelectionObserver,
279-
private var textDragObserver: CupertinoTextDragObserver,
280-
): DelegatingNode(), CompositionLocalConsumerModifierNode {
281-
282-
private val pointerInputNode = delegate(
283-
SuspendingPointerInputModifierNode(
284-
pointerInputEventHandler = {
285-
val hapticFeedback = currentValueOf(LocalHapticFeedback)
286-
val clicksCounter = ClicksCounter(viewConfiguration)
287-
288-
awaitEachGesture {
289-
val down = awaitDown()
290-
291-
if (
292-
down.isMouseOrTouchPad() &&
293-
down.buttons.isPrimaryPressed &&
294-
down.changes.fastAll { !it.isConsumed }
295-
) {
296-
mouseSelection(mouseSelectionObserver, clicksCounter, down)
297-
} else if (!down.isMouseOrTouchPad()) {
298-
touchSelection(textDragObserver, clicksCounter, down, hapticFeedback)
299-
}
300-
}
301-
}
302-
)
303-
)
304-
305234
fun update(
306-
mouseSelectionObserver: MouseSelectionObserver,
307-
textDragObserver: CupertinoTextDragObserver,
235+
selectionRegistrar: SelectionRegistrar,
236+
selectableId: Long,
237+
layoutCoordinates: () -> LayoutCoordinates?,
308238
) {
309-
this.mouseSelectionObserver = mouseSelectionObserver
310-
this.textDragObserver = textDragObserver
239+
this.selectionRegistrar = selectionRegistrar
240+
this.selectableId = selectableId
241+
this.layoutCoordinates = layoutCoordinates
242+
243+
mouseSelectionObserver = createMouseSelectionObserver()
244+
pointerInputNode.resetPointerInputHandler()
311245
}
312246
}
313247

compose/foundation/foundation/src/macosMain/kotlin/androidx/compose/foundation/text/modifiers/SelectionController.macos.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,4 @@ import androidx.compose.ui.layout.LayoutCoordinates
2323
internal actual fun SelectionRegistrar.makeSelectionModifier(
2424
selectableId: Long,
2525
layoutCoordinates: () -> LayoutCoordinates?
26-
): Modifier = makeDefaultSelectionModifier(selectableId, layoutCoordinates)
26+
): Modifier = makeSkikoSelectionModifier(selectableId, layoutCoordinates)

0 commit comments

Comments
 (0)