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.foundation.text.modifiers 18 19 import androidx.compose.foundation.internal.requirePreconditionNotNull 20 import androidx.compose.foundation.text.DefaultMinLines 21 import androidx.compose.foundation.text.TextAutoSize 22 import androidx.compose.ui.geometry.Rect 23 import androidx.compose.ui.graphics.ColorProducer 24 import androidx.compose.ui.graphics.drawscope.ContentDrawScope 25 import androidx.compose.ui.layout.IntrinsicMeasurable 26 import androidx.compose.ui.layout.IntrinsicMeasureScope 27 import androidx.compose.ui.layout.LayoutCoordinates 28 import androidx.compose.ui.layout.Measurable 29 import androidx.compose.ui.layout.MeasureResult 30 import androidx.compose.ui.layout.MeasureScope 31 import androidx.compose.ui.node.DelegatingNode 32 import androidx.compose.ui.node.DrawModifierNode 33 import androidx.compose.ui.node.GlobalPositionAwareModifierNode 34 import androidx.compose.ui.node.LayoutModifierNode 35 import androidx.compose.ui.node.invalidateMeasurement 36 import androidx.compose.ui.text.AnnotatedString 37 import androidx.compose.ui.text.Placeholder 38 import androidx.compose.ui.text.TextLayoutResult 39 import androidx.compose.ui.text.TextStyle 40 import androidx.compose.ui.text.font.FontFamily 41 import androidx.compose.ui.text.style.TextOverflow 42 import androidx.compose.ui.unit.Constraints 43 44 /** 45 * Node for any text that is in a selection container. 46 * 47 * This adds [GlobalPositionAwareModifierNode]. 48 */ 49 internal class SelectableTextAnnotatedStringNode( 50 text: AnnotatedString, 51 style: TextStyle, 52 fontFamilyResolver: FontFamily.Resolver, 53 onTextLayout: ((TextLayoutResult) -> Unit)? = null, 54 overflow: TextOverflow = TextOverflow.Clip, 55 softWrap: Boolean = true, 56 maxLines: Int = Int.MAX_VALUE, 57 minLines: Int = DefaultMinLines, 58 placeholders: List<AnnotatedString.Range<Placeholder>>? = null, 59 onPlaceholderLayout: ((List<Rect?>) -> Unit)? = null, 60 private var selectionController: SelectionController? = null, 61 overrideColor: ColorProducer? = null, 62 autoSize: TextAutoSize? = null, 63 private var onShowTranslation: ((TextAnnotatedStringNode.TextSubstitutionValue) -> Unit)? = null 64 ) : DelegatingNode(), LayoutModifierNode, DrawModifierNode, GlobalPositionAwareModifierNode { 65 override val shouldAutoInvalidate: Boolean 66 get() = false 67 68 private val textAnnotatedStringNode = 69 delegate( 70 TextAnnotatedStringNode( 71 text = text, 72 style = style, 73 fontFamilyResolver = fontFamilyResolver, 74 onTextLayout = onTextLayout, 75 overflow = overflow, 76 softWrap = softWrap, 77 maxLines = maxLines, 78 minLines = minLines, 79 placeholders = placeholders, 80 onPlaceholderLayout = onPlaceholderLayout, 81 selectionController = selectionController, 82 overrideColor = overrideColor, 83 autoSize = autoSize, 84 onShowTranslation = onShowTranslation 85 ) 86 ) 87 88 init { <lambda>null89 requirePreconditionNotNull(selectionController) { 90 "Do not use SelectionCapableStaticTextModifier unless selectionController != null" 91 } 92 } 93 onGloballyPositionednull94 override fun onGloballyPositioned(coordinates: LayoutCoordinates) { 95 selectionController?.updateGlobalPosition(coordinates) 96 } 97 drawnull98 override fun ContentDrawScope.draw() = textAnnotatedStringNode.drawNonExtension(this) 99 100 override fun MeasureScope.measure( 101 measurable: Measurable, 102 constraints: Constraints 103 ): MeasureResult = textAnnotatedStringNode.measureNonExtension(this, measurable, constraints) 104 105 override fun IntrinsicMeasureScope.minIntrinsicWidth( 106 measurable: IntrinsicMeasurable, 107 height: Int 108 ): Int = textAnnotatedStringNode.minIntrinsicWidthNonExtension(this, measurable, height) 109 110 override fun IntrinsicMeasureScope.minIntrinsicHeight( 111 measurable: IntrinsicMeasurable, 112 width: Int 113 ): Int = textAnnotatedStringNode.minIntrinsicHeightNonExtension(this, measurable, width) 114 115 override fun IntrinsicMeasureScope.maxIntrinsicWidth( 116 measurable: IntrinsicMeasurable, 117 height: Int 118 ): Int = textAnnotatedStringNode.maxIntrinsicWidthNonExtension(this, measurable, height) 119 120 override fun IntrinsicMeasureScope.maxIntrinsicHeight( 121 measurable: IntrinsicMeasurable, 122 width: Int 123 ): Int = textAnnotatedStringNode.maxIntrinsicHeightNonExtension(this, measurable, width) 124 125 fun update( 126 text: AnnotatedString, 127 style: TextStyle, 128 placeholders: List<AnnotatedString.Range<Placeholder>>?, 129 minLines: Int, 130 maxLines: Int, 131 softWrap: Boolean, 132 fontFamilyResolver: FontFamily.Resolver, 133 overflow: TextOverflow, 134 onTextLayout: ((TextLayoutResult) -> Unit)?, 135 onPlaceholderLayout: ((List<Rect?>) -> Unit)?, 136 selectionController: SelectionController?, 137 color: ColorProducer?, 138 autoSize: TextAutoSize? 139 ) { 140 textAnnotatedStringNode.doInvalidations( 141 drawChanged = textAnnotatedStringNode.updateDraw(color, style), 142 textChanged = textAnnotatedStringNode.updateText(text = text), 143 layoutChanged = 144 textAnnotatedStringNode.updateLayoutRelatedArgs( 145 style = style, 146 placeholders = placeholders, 147 minLines = minLines, 148 maxLines = maxLines, 149 softWrap = softWrap, 150 fontFamilyResolver = fontFamilyResolver, 151 overflow = overflow, 152 autoSize = autoSize 153 ), 154 callbacksChanged = 155 textAnnotatedStringNode.updateCallbacks( 156 onTextLayout = onTextLayout, 157 onPlaceholderLayout = onPlaceholderLayout, 158 selectionController = selectionController, 159 onShowTranslation = onShowTranslation 160 ), 161 ) 162 this.selectionController = selectionController 163 // we always relayout when we're selectable 164 invalidateMeasurement() 165 } 166 } 167