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