1 /* 2 * Copyright 2023 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 17 package androidx.compose.ui.text.input 18 19 import android.view.inputmethod.CursorAnchorInfo 20 import androidx.compose.ui.geometry.Rect 21 import androidx.compose.ui.graphics.Matrix 22 import androidx.compose.ui.graphics.setFrom 23 import androidx.compose.ui.input.pointer.MatrixPositionCalculator 24 import androidx.compose.ui.text.TextLayoutResult 25 26 @Deprecated( 27 "Only exists to support the legacy TextInputService APIs. It is not used by any Compose " + 28 "code. A copy of this class in foundation is used by the legacy BasicTextField." 29 ) 30 internal class CursorAnchorInfoController( 31 private val rootPositionCalculator: MatrixPositionCalculator, 32 @Suppress("DEPRECATION") private val inputMethodManager: InputMethodManager 33 ) { 34 private val lock = Any() 35 36 private var monitorEnabled = false 37 private var hasPendingImmediateRequest = false 38 39 private var includeInsertionMarker = false 40 private var includeCharacterBounds = false 41 private var includeEditorBounds = false 42 private var includeLineBounds = false 43 44 private var textFieldValue: TextFieldValue? = null 45 private var textLayoutResult: TextLayoutResult? = null 46 private var offsetMapping: OffsetMapping? = null <lambda>null47 private var textFieldToRootTransform: (Matrix) -> Unit = {} 48 private var innerTextFieldBounds: Rect? = null 49 private var decorationBoxBounds: Rect? = null 50 51 private val builder = CursorAnchorInfo.Builder() 52 private val matrix = Matrix() 53 private val androidMatrix = android.graphics.Matrix() 54 55 /** 56 * Requests [CursorAnchorInfo] updates to be provided to the [InputMethodManager]. 57 * 58 * Combinations of [immediate] and [monitor] are used to specify when to provide updates. If 59 * these are both false, then no further updates will be provided. 60 * 61 * @param immediate whether to update with the current [CursorAnchorInfo] immediately, or as 62 * soon as available 63 * @param monitor whether to provide [CursorAnchorInfo] updates for all future layout or 64 * position changes 65 * @param includeInsertionMarker whether to include insertion marker (i.e. cursor) location 66 * information 67 * @param includeCharacterBounds whether to include character bounds information for the 68 * composition range 69 * @param includeEditorBounds whether to include editor bounds information 70 * @param includeLineBounds whether to include line bounds information 71 */ requestUpdatenull72 fun requestUpdate( 73 immediate: Boolean, 74 monitor: Boolean, 75 includeInsertionMarker: Boolean, 76 includeCharacterBounds: Boolean, 77 includeEditorBounds: Boolean, 78 includeLineBounds: Boolean 79 ) = 80 synchronized(lock) { 81 this.includeInsertionMarker = includeInsertionMarker 82 this.includeCharacterBounds = includeCharacterBounds 83 this.includeEditorBounds = includeEditorBounds 84 this.includeLineBounds = includeLineBounds 85 86 if (immediate) { 87 hasPendingImmediateRequest = true 88 if (textFieldValue != null) { 89 updateCursorAnchorInfo() 90 } 91 } 92 monitorEnabled = monitor 93 } 94 95 /** 96 * Notify the controller of layout and position changes. 97 * 98 * @param textFieldValue the text field's [TextFieldValue] 99 * @param offsetMapping the offset mapping for the visual transformation 100 * @param textLayoutResult the text field's [TextLayoutResult] 101 * @param textFieldToRootTransform function that modifies a matrix to be a transformation matrix 102 * from local coordinates to the root composable coordinates 103 * @param innerTextFieldBounds visible bounds of the text field in local coordinates, or an 104 * empty rectangle if the text field is not visible 105 * @param decorationBoxBounds visible bounds of the decoration box in local coordinates, or an 106 * empty rectangle if the decoration box is not visible 107 */ updateTextLayoutResultnull108 fun updateTextLayoutResult( 109 textFieldValue: TextFieldValue, 110 offsetMapping: OffsetMapping, 111 textLayoutResult: TextLayoutResult, 112 textFieldToRootTransform: (Matrix) -> Unit, 113 innerTextFieldBounds: Rect, 114 decorationBoxBounds: Rect 115 ) = 116 synchronized(lock) { 117 this.textFieldValue = textFieldValue 118 this.offsetMapping = offsetMapping 119 this.textLayoutResult = textLayoutResult 120 this.textFieldToRootTransform = textFieldToRootTransform 121 this.innerTextFieldBounds = innerTextFieldBounds 122 this.decorationBoxBounds = decorationBoxBounds 123 124 if (hasPendingImmediateRequest || monitorEnabled) { 125 updateCursorAnchorInfo() 126 } 127 } 128 129 /** 130 * Invalidate the last received layout and position data. 131 * 132 * This should be called when the [TextFieldValue] has changed, so the last received layout and 133 * position data is no longer valid. [CursorAnchorInfo] updates will not be sent until new 134 * layout and position data is received. 135 */ invalidatenull136 fun invalidate() = 137 synchronized(lock) { 138 textFieldValue = null 139 offsetMapping = null 140 textLayoutResult = null 141 textFieldToRootTransform = {} 142 innerTextFieldBounds = null 143 decorationBoxBounds = null 144 } 145 updateCursorAnchorInfonull146 private fun updateCursorAnchorInfo() { 147 if (!inputMethodManager.isActive()) return 148 149 // Sets matrix to transform text field local coordinates to the root composable coordinates. 150 textFieldToRootTransform(matrix) 151 // Updates matrix to transform text field local coordinates to screen coordinates. 152 rootPositionCalculator.localToScreen(matrix) 153 androidMatrix.setFrom(matrix) 154 155 @Suppress("DEPRECATION") 156 inputMethodManager.updateCursorAnchorInfo( 157 builder.build( 158 textFieldValue!!, 159 offsetMapping!!, 160 textLayoutResult!!, 161 androidMatrix, 162 innerTextFieldBounds!!, 163 decorationBoxBounds!!, 164 includeInsertionMarker, 165 includeCharacterBounds, 166 includeEditorBounds, 167 includeLineBounds 168 ) 169 ) 170 171 hasPendingImmediateRequest = false 172 } 173 } 174