1 /*
<lambda>null2 * Copyright 2022 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.collection.MutableObjectFloatMap
20 import androidx.collection.MutableScatterMap
21 import androidx.collection.MutableScatterSet
22 import androidx.collection.mutableObjectIntMapOf
23 import androidx.compose.ui.graphics.GraphicsLayerScope
24 import androidx.compose.ui.internal.checkPrecondition
25 import androidx.compose.ui.internal.throwIllegalStateExceptionForNullCheck
26 import androidx.compose.ui.layout.AlignmentLine
27 import androidx.compose.ui.layout.LayoutCoordinates
28 import androidx.compose.ui.layout.LookaheadLayoutCoordinates
29 import androidx.compose.ui.layout.Measurable
30 import androidx.compose.ui.layout.MeasureResult
31 import androidx.compose.ui.layout.Placeable
32 import androidx.compose.ui.layout.PlacementScope
33 import androidx.compose.ui.layout.Ruler
34 import androidx.compose.ui.layout.RulerScope
35 import androidx.compose.ui.layout.VerticalAlignmentLine
36 import androidx.compose.ui.layout.VerticalRuler
37 import androidx.compose.ui.unit.Constraints
38 import androidx.compose.ui.unit.IntOffset
39 import androidx.compose.ui.unit.IntSize
40 import androidx.compose.ui.unit.LayoutDirection
41
42 /**
43 * This is the base class for NodeCoordinator and LookaheadDelegate. The common functionalities
44 * between the two are extracted here.
45 */
46 internal abstract class LookaheadCapablePlaceable :
47 Placeable(), MeasureScopeWithLayoutNode, MotionReferencePlacementDelegate {
48 abstract val position: IntOffset
49 abstract val child: LookaheadCapablePlaceable?
50 abstract val parent: LookaheadCapablePlaceable?
51 abstract val hasMeasureResult: Boolean
52 abstract override val layoutNode: LayoutNode
53 abstract val coordinates: LayoutCoordinates
54 private var _rulerScope: RulerScope? = null
55
56 /**
57 * Indicates whether the [Placeable] was placed under a motion frame of reference.
58 *
59 * This means, that its offset may be excluded from calculation with
60 * `includeMotionFrameOfReference = false` in [LookaheadLayoutCoordinates.localPositionOf].
61 */
62 override var isPlacedUnderMotionFrameOfReference: Boolean = false
63
64 override fun updatePlacedUnderMotionFrameOfReference(newMFR: Boolean) {
65 val parentNode = parent?.layoutNode
66 if (parentNode == layoutNode) {
67 isPlacedUnderMotionFrameOfReference = newMFR
68 } else {
69 // This node is the beginning of the chain (i.e. outerCoordinator), check if this
70 // placement call comes from the parent
71 if (
72 parentNode?.layoutState == LayoutNode.LayoutState.LayingOut ||
73 parentNode?.layoutState == LayoutNode.LayoutState.LookaheadLayingOut
74 ) {
75 isPlacedUnderMotionFrameOfReference = newMFR
76 }
77 // If the node is simply being replaced without parent, we need to maintain the flag
78 // from last time when `placeChildren` lambda was run. Therefore no op.
79 }
80 }
81
82 val rulerScope: RulerScope
83 get() {
84 return _rulerScope
85 ?: object : RulerScope {
86 override val coordinates: LayoutCoordinates
87 get() {
88 this@LookaheadCapablePlaceable.layoutNode.layoutDelegate
89 .onCoordinatesUsed()
90 return this@LookaheadCapablePlaceable.coordinates
91 }
92
93 override fun Ruler.provides(value: Float) {
94 this@LookaheadCapablePlaceable.provideRulerValue(this, value)
95 }
96
97 override fun VerticalRuler.providesRelative(value: Float) {
98 this@LookaheadCapablePlaceable.provideRelativeRulerValue(this, value)
99 }
100
101 override val density: Float
102 get() = this@LookaheadCapablePlaceable.density
103
104 override val fontScale: Float
105 get() = this@LookaheadCapablePlaceable.fontScale
106 }
107 }
108
109 final override fun get(alignmentLine: AlignmentLine): Int {
110 if (!hasMeasureResult) return AlignmentLine.Unspecified
111 val measuredPosition = calculateAlignmentLine(alignmentLine)
112 if (measuredPosition == AlignmentLine.Unspecified) return AlignmentLine.Unspecified
113 return measuredPosition +
114 if (alignmentLine is VerticalAlignmentLine) {
115 apparentToRealOffset.x
116 } else {
117 apparentToRealOffset.y
118 }
119 }
120
121 abstract fun calculateAlignmentLine(alignmentLine: AlignmentLine): Int
122
123 /**
124 * True when the coordinator is running its own placing block to obtain the position in parent,
125 * but is not interested in the position of children.
126 */
127 internal var isShallowPlacing: Boolean = false
128 internal abstract val measureResult: MeasureResult
129
130 internal abstract fun replace()
131
132 abstract val alignmentLinesOwner: AlignmentLinesOwner
133
134 /**
135 * Used to indicate that this placement pass is for the purposes of calculating an alignment
136 * line. If it is, then [LayoutNodeLayoutDelegate.coordinatesAccessedDuringPlacement] will be
137 * changed when [Placeable.PlacementScope.coordinates] is accessed to indicate that the
138 * placement is not finalized and must be run again.
139 */
140 internal var isPlacingForAlignment = false
141
142 /** [PlacementScope] used to place children. */
143 val placementScope = PlacementScope(this)
144
145 protected fun NodeCoordinator.invalidateAlignmentLinesFromPositionChange() {
146 if (wrapped?.layoutNode != layoutNode) {
147 alignmentLinesOwner.alignmentLines.onAlignmentsChanged()
148 } else {
149 alignmentLinesOwner.parentAlignmentLinesOwner?.alignmentLines?.onAlignmentsChanged()
150 }
151 }
152
153 override val isLookingAhead: Boolean
154 get() = false
155
156 private var rulerValues: MutableObjectFloatMap<Ruler>? = null
157
158 // For comparing before and after running the ruler lambda
159 private var rulerValuesCache: MutableObjectFloatMap<Ruler>? = null
160 private var rulerReaders:
161 MutableScatterMap<Ruler, MutableScatterSet<WeakReference<LayoutNode>>>? =
162 null
163
164 fun findRulerValue(ruler: Ruler, defaultValue: Float): Float {
165 if (isPlacingForAlignment) {
166 return defaultValue
167 }
168 var p: LookaheadCapablePlaceable = this
169 while (true) {
170 val rulerValue = p.rulerValues?.getOrDefault(ruler, Float.NaN) ?: Float.NaN
171 if (!rulerValue.isNaN()) {
172 p.addRulerReader(layoutNode, ruler)
173 return ruler.calculateCoordinate(rulerValue, p.coordinates, coordinates)
174 }
175 val parent = p.parent
176 if (parent == null) {
177 p.addRulerReader(layoutNode, ruler)
178 return defaultValue
179 }
180 p = parent
181 }
182 }
183
184 private fun addRulerReader(layoutNode: LayoutNode, ruler: Ruler) {
185 rulerReaders?.forEachValue { set -> set.removeIf { it.get()?.isAttached != true } }
186 rulerReaders?.removeIf { _, value -> value.isEmpty() }
187 val readerMap =
188 rulerReaders
189 ?: MutableScatterMap<Ruler, MutableScatterSet<WeakReference<LayoutNode>>>().also {
190 rulerReaders = it
191 }
192 val readers = readerMap.getOrPut(ruler) { MutableScatterSet() }
193 readers += WeakReference(layoutNode)
194 }
195
196 private fun findAncestorRulerDefiner(ruler: Ruler): LookaheadCapablePlaceable {
197 var p: LookaheadCapablePlaceable = this
198 while (true) {
199 if (p.rulerValues?.contains(ruler) == true) {
200 return p
201 }
202 val parent = p.parent ?: return p
203 p = parent
204 }
205 }
206
207 private fun LayoutNode.isLayoutNodeAncestor(ancestor: LayoutNode): Boolean {
208 if (this === ancestor) {
209 return true
210 }
211 return parent?.isLayoutNodeAncestor(ancestor) ?: false
212 }
213
214 private fun invalidateChildrenOfDefiningRuler(ruler: Ruler) {
215 val definer = findAncestorRulerDefiner(ruler)
216 val readers = definer.rulerReaders?.remove(ruler)
217 if (readers != null) {
218 notifyRulerValueChange(readers)
219 }
220 }
221
222 @Suppress("PrimitiveInCollection")
223 override fun layout(
224 width: Int,
225 height: Int,
226 alignmentLines: Map<AlignmentLine, Int>,
227 rulers: (RulerScope.() -> Unit)?,
228 placementBlock: PlacementScope.() -> Unit
229 ): MeasureResult {
230 checkMeasuredSize(width, height)
231 return object : MeasureResult {
232 override val width: Int
233 get() = width
234
235 override val height: Int
236 get() = height
237
238 override val alignmentLines: Map<AlignmentLine, Int>
239 get() = alignmentLines
240
241 override val rulers: (RulerScope.() -> Unit)?
242 get() = rulers
243
244 override fun placeChildren() {
245 placementScope.placementBlock()
246 }
247 }
248 }
249
250 internal fun captureRulersIfNeeded(result: MeasureResult?) {
251 val rulerReaders = rulerReaders
252 if (result != null) {
253 if (isPlacingForAlignment) {
254 return
255 }
256 val rulerLambda = result.rulers
257 if (rulerLambda == null) {
258 // Notify anything that read a value it must have a relayout
259 if (rulerReaders != null) {
260 rulerReaders.forEachValue { notifyRulerValueChange(it) }
261 rulerReaders.clear()
262 }
263 } else {
264 // NOTE: consider using a mutable PlaceableResult to be reused for this purpose
265 captureRulers(PlaceableResult(result, this))
266 }
267 } else {
268 rulerReaders?.forEachValue { notifyRulerValueChange(it) }
269 rulerReaders?.clear()
270 rulerValues?.clear()
271 }
272 }
273
274 private fun captureRulers(
275 placeableResult: PlaceableResult,
276 ) {
277 val rulerReaders = rulerReaders
278 val oldValues =
279 rulerValuesCache ?: MutableObjectFloatMap<Ruler>().also { rulerValuesCache = it }
280 val newValues = rulerValues ?: MutableObjectFloatMap<Ruler>().also { rulerValues = it }
281 oldValues.putAll(newValues)
282 newValues.clear()
283 // capture the new values
284 layoutNode.owner?.snapshotObserver?.observeReads(placeableResult, onCommitAffectingRuler) {
285 placeableResult.result.rulers?.invoke(rulerScope)
286 }
287 // compare the old values to the new ones
288 if (rulerReaders != null) {
289 // Notify any LayoutNode that got a value that the value has changed
290 oldValues.forEach { ruler, oldValue ->
291 val newValue = newValues.getOrDefault(ruler, Float.NaN)
292 if (newValue != oldValue) {
293 // Either the value has changed or it stopped being provided.
294 // Notify all watchers of that value that it has changed.
295 val readers = rulerReaders.remove(ruler)
296 if (readers != null) {
297 notifyRulerValueChange(readers)
298 }
299 }
300 }
301 }
302 // Notify everything that might want to read new values
303 newValues.forEachKey { ruler ->
304 if (ruler !in oldValues) {
305 parent?.invalidateChildrenOfDefiningRuler(ruler)
306 }
307 }
308 oldValues.clear()
309 }
310
311 private fun captureRulersIfNeeded(placeableResult: PlaceableResult) {
312 if (isPlacingForAlignment) {
313 return
314 }
315 val rulerLambda = placeableResult.result.rulers
316 val rulerReaders = rulerReaders
317 if (rulerLambda == null) {
318 // Notify anything that read a value it must have a relayout
319 if (rulerReaders != null) {
320 rulerReaders.forEachValue { notifyRulerValueChange(it) }
321 rulerReaders.clear()
322 }
323 } else {
324 captureRulers(placeableResult)
325 }
326 }
327
328 private fun notifyRulerValueChange(layoutNodes: MutableScatterSet<WeakReference<LayoutNode>>) {
329 layoutNodes.forEach { layoutNodeRef ->
330 layoutNodeRef.get()?.let { layoutNode ->
331 if (isLookingAhead) {
332 layoutNode.requestLookaheadRelayout(false)
333 } else {
334 layoutNode.requestRelayout(false)
335 }
336 }
337 }
338 }
339
340 fun provideRulerValue(ruler: Ruler, value: Float) {
341 val rulerValues = rulerValues ?: MutableObjectFloatMap<Ruler>().also { rulerValues = it }
342 rulerValues[ruler] = value
343 }
344
345 fun provideRelativeRulerValue(ruler: Ruler, value: Float) {
346 val rulerValues = rulerValues ?: MutableObjectFloatMap<Ruler>().also { rulerValues = it }
347 rulerValues[ruler] =
348 if (layoutDirection == LayoutDirection.Ltr) {
349 value
350 } else {
351 width - value
352 }
353 }
354
355 companion object {
356 private val onCommitAffectingRuler: (PlaceableResult) -> Unit = { result ->
357 if (result.isValidOwnerScope) {
358 result.placeable.captureRulersIfNeeded(result)
359 }
360 }
361 }
362 }
363
364 private data class PlaceableResult(
365 val result: MeasureResult,
366 val placeable: LookaheadCapablePlaceable
367 ) : OwnerScope {
368 override val isValidOwnerScope: Boolean
369 get() = placeable.coordinates.isAttached
370 }
371
372 // This is about 16 million pixels. That should be big enough. We'll treat anything bigger as an
373 // error.
374 private const val MaxLayoutDimension = (1 shl 24) - 1
375 private const val MaxLayoutMask: Int = 0xFF00_0000.toInt()
376
377 @Suppress("NOTHING_TO_INLINE")
checkMeasuredSizenull378 internal inline fun checkMeasuredSize(width: Int, height: Int) {
379 checkPrecondition(width and MaxLayoutMask == 0 && height and MaxLayoutMask == 0) {
380 "Size($width x $height) is out of range. Each dimension must be between 0 and " +
381 "$MaxLayoutDimension."
382 }
383 }
384
385 internal abstract class LookaheadDelegate(
386 val coordinator: NodeCoordinator,
387 ) : Measurable, LookaheadCapablePlaceable() {
388 override val child: LookaheadCapablePlaceable?
389 get() = coordinator.wrapped?.lookaheadDelegate
390
391 override val hasMeasureResult: Boolean
392 get() = _measureResult != null
393
394 override var position = IntOffset.Zero
395 private var oldAlignmentLines: MutableMap<AlignmentLine, Int>? = null
396 override val measureResult: MeasureResult
397 get() =
398 _measureResult
399 ?: throwIllegalStateExceptionForNullCheck(
400 "LookaheadDelegate has not been measured yet when measureResult is requested."
401 )
402
403 override val isLookingAhead: Boolean
404 get() = true
405
406 override val layoutDirection: LayoutDirection
407 get() = coordinator.layoutDirection
408
409 override val density: Float
410 get() = coordinator.density
411
412 override val fontScale: Float
413 get() = coordinator.fontScale
414
415 override val parent: LookaheadCapablePlaceable?
416 get() = coordinator.wrappedBy?.lookaheadDelegate
417
418 override val layoutNode: LayoutNode
419 get() = coordinator.layoutNode
420
421 override val coordinates: LayoutCoordinates
422 get() = lookaheadLayoutCoordinates
423
424 internal val size: IntSize
425 get() = IntSize(width, height)
426
427 internal val constraints: Constraints
428 get() = measurementConstraints
429
430 val lookaheadLayoutCoordinates = LookaheadLayoutCoordinates(this)
431 override val alignmentLinesOwner: AlignmentLinesOwner
432 get() = coordinator.layoutNode.layoutDelegate.lookaheadAlignmentLinesOwner!!
433
434 private var _measureResult: MeasureResult? = null
435 set(result) {
<lambda>null436 result?.let { measuredSize = IntSize(it.width, it.height) }
<lambda>null437 ?: run { measuredSize = IntSize.Zero }
438 if (field != result && result != null) {
439 // We do not simply compare against old.alignmentLines in case this is a
440 // MutableStateMap and the same instance might be passed.
441 if (
442 (!oldAlignmentLines.isNullOrEmpty() || result.alignmentLines.isNotEmpty()) &&
443 result.alignmentLines != oldAlignmentLines
444 ) {
445 alignmentLinesOwner.alignmentLines.onAlignmentsChanged()
446
447 @Suppress("PrimitiveInCollection")
448 val oldLines =
449 oldAlignmentLines
<lambda>null450 ?: (mutableMapOf<AlignmentLine, Int>().also { oldAlignmentLines = it })
451 oldLines.clear()
452 oldLines.putAll(result.alignmentLines)
453 }
454 }
455 field = result
456 }
457
458 protected val cachedAlignmentLinesMap = mutableObjectIntMapOf<AlignmentLine>()
459
getCachedAlignmentLinenull460 internal fun getCachedAlignmentLine(alignmentLine: AlignmentLine): Int =
461 cachedAlignmentLinesMap.getOrDefault(alignmentLine, AlignmentLine.Unspecified)
462
463 override fun replace() {
464 placeAt(position, 0f, null)
465 }
466
placeAtnull467 final override fun placeAt(
468 position: IntOffset,
469 zIndex: Float,
470 layerBlock: (GraphicsLayerScope.() -> Unit)?
471 ) {
472 placeSelf(position)
473 if (isShallowPlacing) return
474 placeChildren()
475 }
476
placeSelfnull477 private fun placeSelf(position: IntOffset) {
478 if (this.position != position) {
479 this.position = position
480 layoutNode.layoutDelegate.lookaheadPassDelegate
481 ?.notifyChildrenUsingLookaheadCoordinatesWhilePlacing()
482 coordinator.invalidateAlignmentLinesFromPositionChange()
483 }
484 if (!isPlacingForAlignment) {
485 captureRulersIfNeeded(measureResult)
486 }
487 }
488
placeSelfApparentToRealOffsetnull489 internal fun placeSelfApparentToRealOffset(position: IntOffset) {
490 placeSelf(position + apparentToRealOffset)
491 }
492
placeChildrennull493 protected open fun placeChildren() {
494 measureResult.placeChildren()
495 }
496
performingMeasurenull497 inline fun performingMeasure(constraints: Constraints, block: () -> MeasureResult): Placeable {
498 measurementConstraints = constraints
499 _measureResult = block()
500 return this
501 }
502
503 override val parentData: Any?
504 get() = coordinator.parentData
505
minIntrinsicWidthnull506 override fun minIntrinsicWidth(height: Int): Int {
507 return coordinator.wrapped!!.lookaheadDelegate!!.minIntrinsicWidth(height)
508 }
509
maxIntrinsicWidthnull510 override fun maxIntrinsicWidth(height: Int): Int {
511 return coordinator.wrapped!!.lookaheadDelegate!!.maxIntrinsicWidth(height)
512 }
513
minIntrinsicHeightnull514 override fun minIntrinsicHeight(width: Int): Int {
515 return coordinator.wrapped!!.lookaheadDelegate!!.minIntrinsicHeight(width)
516 }
517
maxIntrinsicHeightnull518 override fun maxIntrinsicHeight(width: Int): Int {
519 return coordinator.wrapped!!.lookaheadDelegate!!.maxIntrinsicHeight(width)
520 }
521
positionInnull522 internal fun positionIn(
523 ancestor: LookaheadDelegate,
524 excludingAgnosticOffset: Boolean,
525 ): IntOffset {
526 var aggregatedOffset = IntOffset.Zero
527 var lookaheadDelegate = this
528 while (lookaheadDelegate != ancestor) {
529 if (
530 !lookaheadDelegate.isPlacedUnderMotionFrameOfReference || !excludingAgnosticOffset
531 ) {
532 aggregatedOffset += lookaheadDelegate.position
533 }
534 lookaheadDelegate = lookaheadDelegate.coordinator.wrappedBy!!.lookaheadDelegate!!
535 }
536 return aggregatedOffset
537 }
538 }
539