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