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