1 /*
<lambda>null2  * 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.ui.node
18 
19 import androidx.compose.ui.geometry.Offset
20 import androidx.compose.ui.layout.AlignmentLine
21 import androidx.compose.ui.layout.HorizontalAlignmentLine
22 import androidx.compose.ui.layout.merge
23 import androidx.compose.ui.unit.toOffset
24 import androidx.compose.ui.util.fastRoundToInt
25 
26 internal sealed class AlignmentLines(val alignmentLinesOwner: AlignmentLinesOwner) {
27     /** `true` when the alignment lines needs to be recalculated because they might have changed. */
28     internal var dirty = true
29 
30     /** `true` when the alignment lines were used by the parent during measurement. */
31     internal var usedDuringParentMeasurement = false
32 
33     /**
34      * `true` when the alignment lines have been used by the parent during the current layout (or
35      * previous layout if there is no layout in progress).
36      */
37     internal var usedDuringParentLayout = false
38 
39     /** `true` when the alignment lines were used by the parent during the last completed layout. */
40     internal var previousUsedDuringParentLayout = false
41 
42     /** `true` when the alignment lines were used by the modifier of the node during measurement. */
43     internal var usedByModifierMeasurement = false
44 
45     /** `true` when the alignment lines were used by the modifier of the node during measurement. */
46     internal var usedByModifierLayout = false
47 
48     /** `true` when the direct parent or our modifier relies on our alignment lines. */
49     internal val queried
50         get() =
51             usedDuringParentMeasurement ||
52                 previousUsedDuringParentLayout ||
53                 usedByModifierMeasurement ||
54                 usedByModifierLayout
55 
56     /**
57      * The closest layout node ancestor who was asked for alignment lines, either by the parent or
58      * their own modifier. If the owner stops being queried for alignment lines, we have to
59      * [recalculateQueryOwner] to find the new owner if one exists.
60      */
61     private var queryOwner: AlignmentLinesOwner? = null
62 
63     /**
64      * Whether the alignment lines of this node are relevant (whether an ancestor depends on them).
65      */
66     internal val required: Boolean
67         get() {
68             recalculateQueryOwner()
69             return queryOwner != null
70         }
71 
72     /**
73      * Updates the alignment lines query owner according to the current values of the
74      * alignmentUsedBy* of the layout nodes in the hierarchy.
75      */
76     fun recalculateQueryOwner() {
77         queryOwner =
78             if (queried) {
79                 alignmentLinesOwner
80             } else {
81                 val parent = alignmentLinesOwner.parentAlignmentLinesOwner ?: return
82                 val parentQueryOwner = parent.alignmentLines.queryOwner
83                 if (parentQueryOwner != null && parentQueryOwner.alignmentLines.queried) {
84                     parentQueryOwner
85                 } else {
86                     val owner = queryOwner
87                     if (owner == null || owner.alignmentLines.queried) return
88                     owner.parentAlignmentLinesOwner?.alignmentLines?.recalculateQueryOwner()
89                     owner.parentAlignmentLinesOwner?.alignmentLines?.queryOwner
90                 }
91             }
92     }
93 
94     /** The alignment lines of this layout, inherited + intrinsic */
95     private val alignmentLineMap: MutableMap<AlignmentLine, Int> = hashMapOf()
96 
97     fun getLastCalculation(): Map<AlignmentLine, Int> = alignmentLineMap
98 
99     protected abstract val NodeCoordinator.alignmentLinesMap: Map<AlignmentLine, Int>
100 
101     protected abstract fun NodeCoordinator.getPositionFor(alignmentLine: AlignmentLine): Int
102 
103     /**
104      * Returns the alignment line value for a given alignment line without affecting whether the
105      * flag for whether the alignment line was read.
106      */
107     private fun addAlignmentLine(
108         alignmentLine: AlignmentLine,
109         initialPosition: Int,
110         initialCoordinator: NodeCoordinator
111     ) {
112         var position = Offset(initialPosition.toFloat(), initialPosition.toFloat())
113         var coordinator = initialCoordinator
114         while (true) {
115             position = coordinator.calculatePositionInParent(position)
116             coordinator = coordinator.wrappedBy!!
117             if (coordinator == alignmentLinesOwner.innerCoordinator) break
118             if (alignmentLine in coordinator.alignmentLinesMap) {
119                 val newPosition = coordinator.getPositionFor(alignmentLine)
120                 position = Offset(newPosition.toFloat(), newPosition.toFloat())
121             }
122         }
123 
124         val positionInContainer =
125             (if (alignmentLine is HorizontalAlignmentLine) {
126                     position.y
127                 } else {
128                     position.x
129                 })
130                 .fastRoundToInt()
131         // If the line was already provided by a previous child, merge the values.
132         alignmentLineMap[alignmentLine] =
133             if (alignmentLine in alignmentLineMap) {
134                 alignmentLine.merge(alignmentLineMap.getValue(alignmentLine), positionInContainer)
135             } else {
136                 positionInContainer
137             }
138     }
139 
140     /** Recalculate alignment lines from all the children. */
141     fun recalculate() {
142         alignmentLineMap.clear()
143         alignmentLinesOwner.forEachChildAlignmentLinesOwner { childOwner ->
144             if (!childOwner.isPlaced) return@forEachChildAlignmentLinesOwner
145             if (childOwner.alignmentLines.dirty) {
146                 // It did not need relayout, but we still call layout to recalculate
147                 // alignment lines.
148                 childOwner.layoutChildren()
149             }
150             // Add alignment lines on the child node.
151             childOwner.alignmentLines.alignmentLineMap.forEach { (childLine, linePosition) ->
152                 addAlignmentLine(childLine, linePosition, childOwner.innerCoordinator)
153             }
154 
155             // Add alignment lines on the modifier of the child.
156             var coordinator = childOwner.innerCoordinator.wrappedBy!!
157             while (coordinator != alignmentLinesOwner.innerCoordinator) {
158                 coordinator.alignmentLinesMap.keys.forEach { childLine ->
159                     addAlignmentLine(childLine, coordinator.getPositionFor(childLine), coordinator)
160                 }
161                 coordinator = coordinator.wrappedBy!!
162             }
163         }
164         alignmentLineMap += alignmentLinesOwner.innerCoordinator.alignmentLinesMap
165         dirty = false
166     }
167 
168     /** Reset all the internal states. */
169     internal fun reset() {
170         dirty = true
171         usedDuringParentMeasurement = false
172         previousUsedDuringParentLayout = false
173         usedDuringParentLayout = false
174         usedByModifierMeasurement = false
175         usedByModifierLayout = false
176         queryOwner = null
177     }
178 
179     fun onAlignmentsChanged() {
180         dirty = true
181 
182         val parent = alignmentLinesOwner.parentAlignmentLinesOwner ?: return
183         if (usedDuringParentMeasurement) {
184             parent.requestMeasure()
185         } else if (previousUsedDuringParentLayout || usedDuringParentLayout) {
186             parent.requestLayout()
187         }
188         if (usedByModifierMeasurement) {
189             alignmentLinesOwner.requestMeasure()
190         }
191         if (usedByModifierLayout) {
192             alignmentLinesOwner.requestLayout()
193         }
194         parent.alignmentLines.onAlignmentsChanged()
195     }
196 
197     protected abstract fun NodeCoordinator.calculatePositionInParent(position: Offset): Offset
198 }
199 
200 /** AlignmentLines impl that are specific to non-lookahead pass. */
201 internal class LayoutNodeAlignmentLines(alignmentLinesOwner: AlignmentLinesOwner) :
202     AlignmentLines(alignmentLinesOwner) {
203 
204     override val NodeCoordinator.alignmentLinesMap: Map<AlignmentLine, Int>
205         get() = measureResult.alignmentLines
206 
getPositionFornull207     override fun NodeCoordinator.getPositionFor(alignmentLine: AlignmentLine): Int =
208         get(alignmentLine)
209 
210     override fun NodeCoordinator.calculatePositionInParent(position: Offset): Offset =
211         toParentPosition(position)
212 }
213 
214 /** AlignmentLines impl that are specific to lookahead pass. */
215 internal class LookaheadAlignmentLines(alignmentLinesOwner: AlignmentLinesOwner) :
216     AlignmentLines(alignmentLinesOwner) {
217 
218     override val NodeCoordinator.alignmentLinesMap: Map<AlignmentLine, Int>
219         get() = lookaheadDelegate!!.measureResult.alignmentLines
220 
221     override fun NodeCoordinator.getPositionFor(alignmentLine: AlignmentLine): Int =
222         lookaheadDelegate!![alignmentLine]
223 
224     override fun NodeCoordinator.calculatePositionInParent(position: Offset): Offset =
225         this.lookaheadDelegate!!.position.toOffset() + position
226 }
227