1 /*
2  * Copyright 2021 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.selection
18 
19 import androidx.compose.ui.geometry.Offset
20 import androidx.compose.ui.geometry.Rect
21 import androidx.compose.ui.layout.LayoutCoordinates
22 import androidx.compose.ui.text.AnnotatedString
23 import androidx.compose.ui.text.MultiParagraph
24 import androidx.compose.ui.text.TextLayoutInput
25 import androidx.compose.ui.text.TextLayoutResult
26 import androidx.compose.ui.text.TextRange
27 import androidx.compose.ui.unit.Constraints
28 
29 /**
30  * Provides [Selection] information for a composable to SelectionContainer. Composables who can be
31  * selected should subscribe to [SelectionRegistrar] using this interface.
32  */
33 internal interface Selectable {
34     /**
35      * An ID used by [SelectionRegistrar] to identify this [Selectable]. This value should not be
36      * [SelectionRegistrar.InvalidSelectableId]. When a [Selectable] is created, it can request an
37      * ID from [SelectionRegistrar] by calling [SelectionRegistrar.nextSelectableId].
38      *
39      * @see SelectionRegistrar.nextSelectableId
40      */
41     val selectableId: Long
42 
43     /**
44      * A function which adds [SelectableInfo] representing this [Selectable] to the
45      * [SelectionLayoutBuilder].
46      */
appendSelectableInfoToBuildernull47     fun appendSelectableInfoToBuilder(builder: SelectionLayoutBuilder)
48 
49     /**
50      * Returns selectAll [Selection] information for a selectable composable. If no selection can be
51      * provided null should be returned.
52      *
53      * @return selectAll [Selection] information for a selectable composable. If no selection can be
54      *   provided null should be returned.
55      */
56     fun getSelectAllSelection(): Selection?
57 
58     /**
59      * Return the [Offset] of a [SelectionHandle].
60      *
61      * @param selection [Selection] contains the [SelectionHandle]
62      * @param isStartHandle true if it's the start handle, false if it's the end handle.
63      * @return [Offset] of this handle, based on which the [SelectionHandle] will be drawn.
64      */
65     fun getHandlePosition(selection: Selection, isStartHandle: Boolean): Offset
66 
67     /**
68      * Return the [LayoutCoordinates] of the [Selectable].
69      *
70      * @return [LayoutCoordinates] of the [Selectable]. This could be null if called before
71      *   composing.
72      */
73     fun getLayoutCoordinates(): LayoutCoordinates?
74 
75     /**
76      * Return the [TextLayoutResult] of the selectable.
77      *
78      * @return [TextLayoutResult] of the [Selectable]. This could be null if called before
79      *   composing.
80      */
81     fun textLayoutResult(): TextLayoutResult?
82 
83     /**
84      * Return the [AnnotatedString] of the [Selectable].
85      *
86      * @return text content as [AnnotatedString] of the [Selectable].
87      */
88     fun getText(): AnnotatedString
89 
90     /**
91      * Return the bounding box of the character for given character offset. This is currently for
92      * text. In future when we implemented other selectable Composables, we can return the bounding
93      * box of the wanted rectangle. For example, for an image selectable, this should return the
94      * bounding box of the image.
95      *
96      * @param offset a character offset
97      * @return the bounding box for the character in [Rect], or [Rect.Zero] if the selectable is
98      *   empty.
99      */
100     fun getBoundingBox(offset: Int): Rect
101 
102     /**
103      * Returns the left x coordinate of the line for the given offset.
104      *
105      * @param offset a character offset
106      * @return the line left x coordinate for the given offset
107      */
108     fun getLineLeft(offset: Int): Float
109 
110     /**
111      * Returns the right x coordinate of the line for the given offset.
112      *
113      * @param offset a character offset
114      * @return the line right x coordinate for the given offset
115      */
116     fun getLineRight(offset: Int): Float
117 
118     /**
119      * Returns the center y coordinate of the line on which the specified text offset appears.
120      *
121      * If you ask for a position before 0, you get the center of the first line; if you ask for a
122      * position beyond the end of the text, you get the center of the last line.
123      *
124      * @param offset a character offset
125      * @return the line center y coordinate of the line containing [offset]
126      */
127     fun getCenterYForOffset(offset: Int): Float
128 
129     /**
130      * Return the offsets of the start and end of the line containing [offset], or [TextRange.Zero]
131      * if the selectable is empty. These offsets are in the same "coordinate space" as
132      * [getBoundingBox], and despite being returned in a [TextRange], may not refer to offsets in
133      * actual text if the selectable contains other types of content. This function returns the last
134      * visible line's boundaries if offset is larger than text length or the character at given
135      * offset would fall on a line which is hidden by maxLines or Constraints.
136      */
137     fun getRangeOfLineContaining(offset: Int): TextRange
138 
139     /**
140      * Returns the last visible character's offset. Some lines can be hidden due to either
141      * [TextLayoutInput.maxLines] or [Constraints.maxHeight] being smaller than
142      * [MultiParagraph.height]. If overflow is set to clip and a line is partially visible, it
143      * counts as the last visible line.
144      */
145     fun getLastVisibleOffset(): Int
146 
147     /**
148      * Returns the text line height for the given offset.
149      *
150      * @param offset a character offset
151      * @return the line height for the given offset
152      */
153     fun getLineHeight(offset: Int): Float
154 }
155