George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 1 | /* |
shepshapard | 8a808e9 | 2018-12-19 15:10:30 -0800 | [diff] [blame] | 2 | * Copyright 2019 The Android Open Source Project |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 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 | */ |
Louis Pullen-Freilich | a03fd6c | 2020-07-24 23:26:29 +0100 | [diff] [blame] | 16 | package androidx.compose.ui.node |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 17 | |
Louis Pullen-Freilich | 1f10a59 | 2020-07-24 16:35:14 +0100 | [diff] [blame] | 18 | import androidx.compose.runtime.collection.MutableVector |
| 19 | import androidx.compose.runtime.collection.mutableVectorOf |
Doris Liu | 894c911 | 2021-08-09 16:12:47 -0700 | [diff] [blame] | 20 | import androidx.compose.ui.ExperimentalComposeUiApi |
Louis Pullen-Freilich | a03fd6c | 2020-07-24 23:26:29 +0100 | [diff] [blame] | 21 | import androidx.compose.ui.Modifier |
Matvei Malkov | 0eb9589 | 2020-11-27 18:35:01 +0000 | [diff] [blame] | 22 | import androidx.compose.ui.draw.DrawModifier |
Ralston Da Silva | a19b9ec | 2020-12-03 16:09:45 -0800 | [diff] [blame] | 23 | import androidx.compose.ui.focus.FocusEventModifier |
Andrey Kulikov | 554f736 | 2021-01-28 15:35:39 +0000 | [diff] [blame] | 24 | import androidx.compose.ui.focus.FocusModifier |
Ralston Da Silva | c549cdd | 2020-12-17 14:27:19 -0800 | [diff] [blame] | 25 | import androidx.compose.ui.focus.FocusOrderModifier |
Andrey Kulikov | 554f736 | 2021-01-28 15:35:39 +0000 | [diff] [blame] | 26 | import androidx.compose.ui.focus.FocusRequesterModifier |
Louis Pullen-Freilich | f434a13 | 2020-07-22 14:19:24 +0100 | [diff] [blame] | 27 | import androidx.compose.ui.geometry.Offset |
Louis Pullen-Freilich | 4dc4dac | 2020-07-22 14:39:14 +0100 | [diff] [blame] | 28 | import androidx.compose.ui.graphics.Canvas |
Louis Pullen-Freilich | 534385a | 2020-08-14 14:10:12 +0100 | [diff] [blame] | 29 | import androidx.compose.ui.input.key.KeyInputModifier |
Andrey Kulikov | 31c1124 | 2021-03-02 13:21:43 +0000 | [diff] [blame] | 30 | import androidx.compose.ui.input.nestedscroll.NestedScrollDelegatingWrapper |
| 31 | import androidx.compose.ui.input.nestedscroll.NestedScrollModifier |
Louis Pullen-Freilich | 534385a | 2020-08-14 14:10:12 +0100 | [diff] [blame] | 32 | import androidx.compose.ui.input.pointer.PointerInputFilter |
| 33 | import androidx.compose.ui.input.pointer.PointerInputModifier |
Mihai Popa | 68db1e3 | 2020-10-09 16:17:44 +0100 | [diff] [blame] | 34 | import androidx.compose.ui.layout.AlignmentLine |
Louis Pullen-Freilich | a03fd6c | 2020-07-24 23:26:29 +0100 | [diff] [blame] | 35 | import androidx.compose.ui.layout.IntrinsicMeasurable |
| 36 | import androidx.compose.ui.layout.IntrinsicMeasureScope |
| 37 | import androidx.compose.ui.layout.LayoutCoordinates |
Andrey Kulikov | 5fb82da | 2020-12-03 15:24:33 +0000 | [diff] [blame] | 38 | import androidx.compose.ui.layout.LayoutInfo |
Mihai Popa | 68db1e3 | 2020-10-09 16:17:44 +0100 | [diff] [blame] | 39 | import androidx.compose.ui.layout.LayoutModifier |
| 40 | import androidx.compose.ui.layout.Measurable |
Mihai Popa | 4958475 | 2021-01-25 12:38:07 +0000 | [diff] [blame] | 41 | import androidx.compose.ui.layout.MeasurePolicy |
Mihai Popa | af03ea3 | 2020-10-19 14:47:36 +0100 | [diff] [blame] | 42 | import androidx.compose.ui.layout.MeasureResult |
Andrey Kulikov | d82950b | 2020-11-11 15:23:18 +0000 | [diff] [blame] | 43 | import androidx.compose.ui.layout.MeasureScope |
Andrey Kulikov | 5fb82da | 2020-12-03 15:24:33 +0000 | [diff] [blame] | 44 | import androidx.compose.ui.layout.ModifierInfo |
Andrey Kulikov | d82950b | 2020-11-11 15:23:18 +0000 | [diff] [blame] | 45 | import androidx.compose.ui.layout.OnGloballyPositionedModifier |
Doris Liu | 894c911 | 2021-08-09 16:12:47 -0700 | [diff] [blame] | 46 | import androidx.compose.ui.layout.OnPlacedModifier |
Andrey Kulikov | d82950b | 2020-11-11 15:23:18 +0000 | [diff] [blame] | 47 | import androidx.compose.ui.layout.OnRemeasuredModifier |
Mihai Popa | 68db1e3 | 2020-10-09 16:17:44 +0100 | [diff] [blame] | 48 | import androidx.compose.ui.layout.ParentDataModifier |
Andrey Kulikov | d82950b | 2020-11-11 15:23:18 +0000 | [diff] [blame] | 49 | import androidx.compose.ui.layout.Placeable |
| 50 | import androidx.compose.ui.layout.Remeasurement |
| 51 | import androidx.compose.ui.layout.RemeasurementModifier |
Ralston Da Silva | e8035a6 | 2021-09-01 16:20:16 -0700 | [diff] [blame] | 52 | import androidx.compose.ui.modifier.ModifierLocalConsumer |
| 53 | import androidx.compose.ui.modifier.ModifierLocalProvider |
Andrey Kulikov | fc91b88 | 2020-09-24 13:31:29 +0100 | [diff] [blame] | 54 | import androidx.compose.ui.node.LayoutNode.LayoutState.LayingOut |
Louis Pullen-Freilich | 534385a | 2020-08-14 14:10:12 +0100 | [diff] [blame] | 55 | import androidx.compose.ui.node.LayoutNode.LayoutState.Measuring |
| 56 | import androidx.compose.ui.node.LayoutNode.LayoutState.NeedsRelayout |
| 57 | import androidx.compose.ui.node.LayoutNode.LayoutState.NeedsRemeasure |
| 58 | import androidx.compose.ui.node.LayoutNode.LayoutState.Ready |
George Mount | d1bb6b8 | 2021-07-23 22:52:24 +0000 | [diff] [blame] | 59 | import androidx.compose.ui.platform.ViewConfiguration |
Nader Jawad | 793e86c | 2020-12-09 18:32:17 -0800 | [diff] [blame] | 60 | import androidx.compose.ui.platform.nativeClass |
Louis Pullen-Freilich | a03fd6c | 2020-07-24 23:26:29 +0100 | [diff] [blame] | 61 | import androidx.compose.ui.platform.simpleIdentityToString |
Louis Pullen-Freilich | 534385a | 2020-08-14 14:10:12 +0100 | [diff] [blame] | 62 | import androidx.compose.ui.semantics.SemanticsModifier |
| 63 | import androidx.compose.ui.semantics.SemanticsWrapper |
| 64 | import androidx.compose.ui.semantics.outerSemantics |
Louis Pullen-Freilich | a7eeb10 | 2020-07-22 17:54:24 +0100 | [diff] [blame] | 65 | import androidx.compose.ui.unit.Constraints |
| 66 | import androidx.compose.ui.unit.Density |
George Mount | d1bb6b8 | 2021-07-23 22:52:24 +0000 | [diff] [blame] | 67 | import androidx.compose.ui.unit.DpSize |
Louis Pullen-Freilich | a7eeb10 | 2020-07-22 17:54:24 +0100 | [diff] [blame] | 68 | import androidx.compose.ui.unit.LayoutDirection |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 69 | |
| 70 | /** |
George Mount | 17d6fe5 | 2020-05-15 08:08:23 -0700 | [diff] [blame] | 71 | * Enable to log changes to the LayoutNode tree. This logging is quite chatty. |
Ryan Mentley | 7865a63 | 2019-08-20 20:10:29 -0700 | [diff] [blame] | 72 | */ |
| 73 | private const val DebugChanges = false |
| 74 | |
| 75 | /** |
George Mount | 5469e25 | 2020-06-17 10:01:42 -0700 | [diff] [blame] | 76 | * An element in the layout hierarchy, built with compose UI. |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 77 | */ |
Ralston Da Silva | 6d0c432 | 2021-10-19 18:31:23 -0700 | [diff] [blame] | 78 | internal class LayoutNode( |
Andrey Kulikov | 2ca0701 | 2020-07-13 16:14:03 +0100 | [diff] [blame] | 79 | // Virtual LayoutNode is the temporary concept allows us to a node which is not a real node, |
| 80 | // but just a holder for its children - allows us to combine some children into something we |
| 81 | // can subcompose in(LayoutNode) without being required to define it as a real layout - we |
| 82 | // don't want to define the layout strategy for such nodes, instead the children of the |
Ralston Da Silva | e8035a6 | 2021-09-01 16:20:16 -0700 | [diff] [blame] | 83 | // virtual nodes will be treated as the direct children of the virtual node parent. |
Andrey Kulikov | 2ca0701 | 2020-07-13 16:14:03 +0100 | [diff] [blame] | 84 | // This whole concept will be replaced with a proper subcomposition logic which allows to |
| 85 | // subcompose multiple times into the same LayoutNode and define offsets. |
Ralston Da Silva | 6d0c432 | 2021-10-19 18:31:23 -0700 | [diff] [blame] | 86 | private val isVirtual: Boolean = false |
| 87 | ) : Measurable, Remeasurement, OwnerScope, LayoutInfo, ComposeUiNode { |
Andrey Kulikov | 2ca0701 | 2020-07-13 16:14:03 +0100 | [diff] [blame] | 88 | |
| 89 | private var virtualChildrenCount = 0 |
| 90 | |
| 91 | // the list of nodes containing the virtual children as is |
| 92 | private val _foldedChildren = mutableVectorOf<LayoutNode>() |
| 93 | internal val foldedChildren: List<LayoutNode> get() = _foldedChildren.asMutableList() |
| 94 | |
| 95 | // the list of nodes where the virtual children are unfolded (their children are represented |
| 96 | // as our direct children) |
Andrey Kulikov | 1f07d0fbb5 | 2021-03-19 17:59:07 +0000 | [diff] [blame] | 97 | private var _unfoldedChildren: MutableVector<LayoutNode>? = null |
Andrey Kulikov | 2ca0701 | 2020-07-13 16:14:03 +0100 | [diff] [blame] | 98 | |
| 99 | private fun recreateUnfoldedChildrenIfDirty() { |
| 100 | if (unfoldedVirtualChildrenListDirty) { |
| 101 | unfoldedVirtualChildrenListDirty = false |
Andrey Kulikov | 1f07d0fbb5 | 2021-03-19 17:59:07 +0000 | [diff] [blame] | 102 | val unfoldedChildren = _unfoldedChildren ?: mutableVectorOf<LayoutNode>().also { |
| 103 | _unfoldedChildren = it |
| 104 | } |
| 105 | unfoldedChildren.clear() |
Andrey Kulikov | 2ca0701 | 2020-07-13 16:14:03 +0100 | [diff] [blame] | 106 | _foldedChildren.forEach { |
| 107 | if (it.isVirtual) { |
Andrey Kulikov | 1f07d0fbb5 | 2021-03-19 17:59:07 +0000 | [diff] [blame] | 108 | unfoldedChildren.addAll(it._children) |
Andrey Kulikov | 2ca0701 | 2020-07-13 16:14:03 +0100 | [diff] [blame] | 109 | } else { |
Andrey Kulikov | 1f07d0fbb5 | 2021-03-19 17:59:07 +0000 | [diff] [blame] | 110 | unfoldedChildren.add(it) |
Andrey Kulikov | 2ca0701 | 2020-07-13 16:14:03 +0100 | [diff] [blame] | 111 | } |
| 112 | } |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | // when the list of our children is modified it will be set to true if we are a virtual node |
| 117 | // or it will be set to true on a parent if the parent is a virtual node |
| 118 | private var unfoldedVirtualChildrenListDirty = false |
| 119 | private fun invalidateUnfoldedVirtualChildren() { |
| 120 | if (virtualChildrenCount > 0) { |
| 121 | unfoldedVirtualChildrenListDirty = true |
| 122 | } |
| 123 | if (isVirtual) { |
George Mount | fdd4f0f | 2020-11-19 23:42:29 +0000 | [diff] [blame] | 124 | this.parent?.unfoldedVirtualChildrenListDirty = true |
Andrey Kulikov | 2ca0701 | 2020-07-13 16:14:03 +0100 | [diff] [blame] | 125 | } |
| 126 | } |
| 127 | |
Ralston Da Silva | 6d0c432 | 2021-10-19 18:31:23 -0700 | [diff] [blame] | 128 | @Suppress("PropertyName") |
George Mount | 1e6c7ff | 2020-07-24 15:22:40 -0700 | [diff] [blame] | 129 | internal val _children: MutableVector<LayoutNode> |
Andrey Kulikov | 2ca0701 | 2020-07-13 16:14:03 +0100 | [diff] [blame] | 130 | get() = if (virtualChildrenCount == 0) { |
| 131 | _foldedChildren |
| 132 | } else { |
| 133 | recreateUnfoldedChildrenIfDirty() |
Andrey Kulikov | 1f07d0fbb5 | 2021-03-19 17:59:07 +0000 | [diff] [blame] | 134 | _unfoldedChildren!! |
Andrey Kulikov | 2ca0701 | 2020-07-13 16:14:03 +0100 | [diff] [blame] | 135 | } |
George Mount | 104b438 | 2019-06-11 08:11:18 -0700 | [diff] [blame] | 136 | |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 137 | /** |
George Mount | 17d6fe5 | 2020-05-15 08:08:23 -0700 | [diff] [blame] | 138 | * The children of this LayoutNode, controlled by [insertAt], [move], and [removeAt]. |
| 139 | */ |
Andrey Kulikov | 171309a | 2020-12-07 12:48:08 +0000 | [diff] [blame] | 140 | internal val children: List<LayoutNode> get() = _children.asMutableList() |
George Mount | 17d6fe5 | 2020-05-15 08:08:23 -0700 | [diff] [blame] | 141 | |
| 142 | /** |
George Mount | fdd4f0f | 2020-11-19 23:42:29 +0000 | [diff] [blame] | 143 | * The parent node in the LayoutNode hierarchy. This is `null` when the [LayoutNode] |
Andrey Kulikov | 804e0e6 | 2021-04-16 17:51:44 +0100 | [diff] [blame] | 144 | * is not attached to a hierarchy or is the root of the hierarchy. |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 145 | */ |
Alexandre Elias | e8b274d | 2021-06-03 19:38:18 -0700 | [diff] [blame] | 146 | private var _foldedParent: LayoutNode? = null |
| 147 | |
| 148 | /* |
| 149 | * The parent node in the LayoutNode hierarchy, skipping over virtual nodes. |
| 150 | */ |
| 151 | internal val parent: LayoutNode? |
Andrey Kulikov | 2ca0701 | 2020-07-13 16:14:03 +0100 | [diff] [blame] | 152 | get() { |
Alexandre Elias | e8b274d | 2021-06-03 19:38:18 -0700 | [diff] [blame] | 153 | return if (_foldedParent?.isVirtual == true) _foldedParent?.parent else _foldedParent |
Andrey Kulikov | 2ca0701 | 2020-07-13 16:14:03 +0100 | [diff] [blame] | 154 | } |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 155 | |
| 156 | /** |
| 157 | * The view system [Owner]. This `null` until [attach] is called |
| 158 | */ |
Andrey Kulikov | 171309a | 2020-12-07 12:48:08 +0000 | [diff] [blame] | 159 | internal var owner: Owner? = null |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 160 | private set |
| 161 | |
| 162 | /** |
Andrey Kulikov | 5fb82da | 2020-12-03 15:24:33 +0000 | [diff] [blame] | 163 | * Returns true if this [LayoutNode] currently has an [LayoutNode.owner]. Semantically, |
| 164 | * this means that the LayoutNode is currently a part of a component tree. |
| 165 | */ |
| 166 | override val isAttached: Boolean get() = owner != null |
| 167 | |
| 168 | /** |
George Mount | fdd4f0f | 2020-11-19 23:42:29 +0000 | [diff] [blame] | 169 | * The tree depth of the [LayoutNode]. This is valid only when it is attached to a hierarchy. |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 170 | */ |
Andrey Kulikov | 5fb82da | 2020-12-03 15:24:33 +0000 | [diff] [blame] | 171 | internal var depth: Int = 0 |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 172 | |
| 173 | /** |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 174 | * The layout state the node is currently in. |
| 175 | */ |
Andrey Kulikov | 2ca0701 | 2020-07-13 16:14:03 +0100 | [diff] [blame] | 176 | internal var layoutState = Ready |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 177 | |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 178 | /** |
George Mount | a2e46b0 | 2020-06-15 10:33:15 -0700 | [diff] [blame] | 179 | * A cache of modifiers to be used when setting and reusing previous modifiers. |
| 180 | */ |
George Mount | dfc9ad7 | 2020-07-07 13:03:02 -0700 | [diff] [blame] | 181 | private var wrapperCache = mutableVectorOf<DelegatingLayoutNodeWrapper<*>>() |
George Mount | a2e46b0 | 2020-06-15 10:33:15 -0700 | [diff] [blame] | 182 | |
| 183 | /** |
Andrey Kulikov | 804e0e6 | 2021-04-16 17:51:44 +0100 | [diff] [blame] | 184 | * [requestRemeasure] calls will be ignored while this flag is true. |
| 185 | */ |
| 186 | private var ignoreRemeasureRequests = false |
| 187 | |
| 188 | /** |
George Mount | 17d6fe5 | 2020-05-15 08:08:23 -0700 | [diff] [blame] | 189 | * Inserts a child [LayoutNode] at a particular index. If this LayoutNode [owner] is not `null` |
George Mount | 1bcb07c | 2019-03-15 17:29:01 -0700 | [diff] [blame] | 190 | * then [instance] will become [attach]ed also. [instance] must have a `null` [parent]. |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 191 | */ |
George Mount | fdd4f0f | 2020-11-19 23:42:29 +0000 | [diff] [blame] | 192 | internal fun insertAt(index: Int, instance: LayoutNode) { |
Alexandre Elias | e8b274d | 2021-06-03 19:38:18 -0700 | [diff] [blame] | 193 | check(instance._foldedParent == null) { |
| 194 | "Cannot insert $instance because it already has a parent." + |
| 195 | " This tree: " + debugTreeToString() + |
| 196 | " Other tree: " + instance._foldedParent?.debugTreeToString() |
George Mount | e11880a | 2020-07-17 15:28:22 -0700 | [diff] [blame] | 197 | } |
| 198 | check(instance.owner == null) { |
Alexandre Elias | e8b274d | 2021-06-03 19:38:18 -0700 | [diff] [blame] | 199 | "Cannot insert $instance because it already has an owner." + |
| 200 | " This tree: " + debugTreeToString() + |
| 201 | " Other tree: " + instance.debugTreeToString() |
George Mount | e11880a | 2020-07-17 15:28:22 -0700 | [diff] [blame] | 202 | } |
Ryan Mentley | 7865a63 | 2019-08-20 20:10:29 -0700 | [diff] [blame] | 203 | |
| 204 | if (DebugChanges) { |
| 205 | println("$instance added to $this at index $index") |
| 206 | } |
| 207 | |
Alexandre Elias | e8b274d | 2021-06-03 19:38:18 -0700 | [diff] [blame] | 208 | instance._foldedParent = this |
Andrey Kulikov | 2ca0701 | 2020-07-13 16:14:03 +0100 | [diff] [blame] | 209 | _foldedChildren.add(index, instance) |
Andrey Kulikov | e80baea | 2021-05-18 13:57:40 +0100 | [diff] [blame] | 210 | onZSortedChildrenInvalidated() |
Andrey Kulikov | 2ca0701 | 2020-07-13 16:14:03 +0100 | [diff] [blame] | 211 | |
| 212 | if (instance.isVirtual) { |
| 213 | require(!isVirtual) { "Virtual LayoutNode can't be added into a virtual parent" } |
| 214 | virtualChildrenCount++ |
| 215 | } |
| 216 | invalidateUnfoldedVirtualChildren() |
George Mount | 104b438 | 2019-06-11 08:11:18 -0700 | [diff] [blame] | 217 | |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 218 | instance.outerLayoutNodeWrapper.wrappedBy = innerLayoutNodeWrapper |
George Mount | d4741ec | 2020-01-23 07:30:38 -0800 | [diff] [blame] | 219 | |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 220 | val owner = this.owner |
| 221 | if (owner != null) { |
| 222 | instance.attach(owner) |
| 223 | } |
| 224 | } |
| 225 | |
Andrey Kulikov | e80baea | 2021-05-18 13:57:40 +0100 | [diff] [blame] | 226 | private fun onZSortedChildrenInvalidated() { |
| 227 | if (isVirtual) { |
| 228 | parent?.onZSortedChildrenInvalidated() |
| 229 | } else { |
| 230 | zSortedChildrenInvalidated = true |
| 231 | } |
| 232 | } |
| 233 | |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 234 | /** |
| 235 | * Removes one or more children, starting at [index]. |
| 236 | */ |
George Mount | fdd4f0f | 2020-11-19 23:42:29 +0000 | [diff] [blame] | 237 | internal fun removeAt(index: Int, count: Int) { |
George Mount | e11880a | 2020-07-17 15:28:22 -0700 | [diff] [blame] | 238 | require(count >= 0) { |
| 239 | "count ($count) must be greater than 0" |
| 240 | } |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 241 | val attached = owner != null |
George Mount | 104b438 | 2019-06-11 08:11:18 -0700 | [diff] [blame] | 242 | for (i in index + count - 1 downTo index) { |
Andrey Kulikov | 2ca0701 | 2020-07-13 16:14:03 +0100 | [diff] [blame] | 243 | val child = _foldedChildren.removeAt(i) |
Andrey Kulikov | e80baea | 2021-05-18 13:57:40 +0100 | [diff] [blame] | 244 | onZSortedChildrenInvalidated() |
Ryan Mentley | 7865a63 | 2019-08-20 20:10:29 -0700 | [diff] [blame] | 245 | if (DebugChanges) { |
| 246 | println("$child removed from $this at index $i") |
| 247 | } |
| 248 | |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 249 | if (attached) { |
| 250 | child.detach() |
| 251 | } |
Alexandre Elias | e8b274d | 2021-06-03 19:38:18 -0700 | [diff] [blame] | 252 | child._foldedParent = null |
Andrey Kulikov | 2ca0701 | 2020-07-13 16:14:03 +0100 | [diff] [blame] | 253 | |
| 254 | if (child.isVirtual) { |
| 255 | virtualChildrenCount-- |
| 256 | } |
| 257 | invalidateUnfoldedVirtualChildren() |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 258 | } |
| 259 | } |
| 260 | |
George Mount | 3fec87b | 2020-03-31 14:33:02 -0700 | [diff] [blame] | 261 | /** |
Adam Powell | 8dec9b7 | 2020-06-19 14:24:14 -0700 | [diff] [blame] | 262 | * Removes all children. |
| 263 | */ |
George Mount | fdd4f0f | 2020-11-19 23:42:29 +0000 | [diff] [blame] | 264 | internal fun removeAll() { |
Adam Powell | 8dec9b7 | 2020-06-19 14:24:14 -0700 | [diff] [blame] | 265 | val attached = owner != null |
Andrey Kulikov | 2ca0701 | 2020-07-13 16:14:03 +0100 | [diff] [blame] | 266 | for (i in _foldedChildren.size - 1 downTo 0) { |
| 267 | val child = _foldedChildren[i] |
Adam Powell | 8dec9b7 | 2020-06-19 14:24:14 -0700 | [diff] [blame] | 268 | if (attached) { |
| 269 | child.detach() |
| 270 | } |
Alexandre Elias | e8b274d | 2021-06-03 19:38:18 -0700 | [diff] [blame] | 271 | child._foldedParent = null |
Adam Powell | 8dec9b7 | 2020-06-19 14:24:14 -0700 | [diff] [blame] | 272 | } |
Andrey Kulikov | 2ca0701 | 2020-07-13 16:14:03 +0100 | [diff] [blame] | 273 | _foldedChildren.clear() |
Andrey Kulikov | e80baea | 2021-05-18 13:57:40 +0100 | [diff] [blame] | 274 | onZSortedChildrenInvalidated() |
Andrey Kulikov | 2ca0701 | 2020-07-13 16:14:03 +0100 | [diff] [blame] | 275 | |
| 276 | virtualChildrenCount = 0 |
| 277 | invalidateUnfoldedVirtualChildren() |
Adam Powell | 8dec9b7 | 2020-06-19 14:24:14 -0700 | [diff] [blame] | 278 | } |
| 279 | |
| 280 | /** |
George Mount | 3fec87b | 2020-03-31 14:33:02 -0700 | [diff] [blame] | 281 | * Moves [count] elements starting at index [from] to index [to]. The [to] index is related to |
| 282 | * the position before the change, so, for example, to move an element at position 1 to after |
| 283 | * the element at position 2, [from] should be `1` and [to] should be `3`. If the elements |
George Mount | 17d6fe5 | 2020-05-15 08:08:23 -0700 | [diff] [blame] | 284 | * were LayoutNodes A B C D E, calling `move(1, 3, 1)` would result in the LayoutNodes |
George Mount | 3fec87b | 2020-03-31 14:33:02 -0700 | [diff] [blame] | 285 | * being reordered to A C B D E. |
| 286 | */ |
George Mount | fdd4f0f | 2020-11-19 23:42:29 +0000 | [diff] [blame] | 287 | internal fun move(from: Int, to: Int, count: Int) { |
George Mount | 104b438 | 2019-06-11 08:11:18 -0700 | [diff] [blame] | 288 | if (from == to) { |
| 289 | return // nothing to do |
| 290 | } |
Alexandre Elias | f17ba99 | 2020-03-23 20:22:20 -0700 | [diff] [blame] | 291 | |
George Mount | 104b438 | 2019-06-11 08:11:18 -0700 | [diff] [blame] | 292 | for (i in 0 until count) { |
| 293 | // if "from" is after "to," the from index moves because we're inserting before it |
| 294 | val fromIndex = if (from > to) from + i else from |
| 295 | val toIndex = if (from > to) to + i else to + count - 2 |
Andrey Kulikov | 2ca0701 | 2020-07-13 16:14:03 +0100 | [diff] [blame] | 296 | val child = _foldedChildren.removeAt(fromIndex) |
Ryan Mentley | 7865a63 | 2019-08-20 20:10:29 -0700 | [diff] [blame] | 297 | |
| 298 | if (DebugChanges) { |
| 299 | println("$child moved in $this from index $fromIndex to $toIndex") |
| 300 | } |
| 301 | |
Andrey Kulikov | 2ca0701 | 2020-07-13 16:14:03 +0100 | [diff] [blame] | 302 | _foldedChildren.add(toIndex, child) |
Ryan Mentley | 7865a63 | 2019-08-20 20:10:29 -0700 | [diff] [blame] | 303 | } |
Andrey Kulikov | e80baea | 2021-05-18 13:57:40 +0100 | [diff] [blame] | 304 | onZSortedChildrenInvalidated() |
Ryan Mentley | 7865a63 | 2019-08-20 20:10:29 -0700 | [diff] [blame] | 305 | |
Andrey Kulikov | 2ca0701 | 2020-07-13 16:14:03 +0100 | [diff] [blame] | 306 | invalidateUnfoldedVirtualChildren() |
George Mount | 17d6fe5 | 2020-05-15 08:08:23 -0700 | [diff] [blame] | 307 | requestRemeasure() |
George Mount | 104b438 | 2019-06-11 08:11:18 -0700 | [diff] [blame] | 308 | } |
| 309 | |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 310 | /** |
George Mount | 17d6fe5 | 2020-05-15 08:08:23 -0700 | [diff] [blame] | 311 | * Set the [Owner] of this LayoutNode. This LayoutNode must not already be attached. |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 312 | * [owner] must match its [parent].[owner]. |
| 313 | */ |
Andrey Kulikov | 5fb82da | 2020-12-03 15:24:33 +0000 | [diff] [blame] | 314 | internal fun attach(owner: Owner) { |
George Mount | e11880a | 2020-07-17 15:28:22 -0700 | [diff] [blame] | 315 | check(this.owner == null) { |
Alexandre Elias | e8b274d | 2021-06-03 19:38:18 -0700 | [diff] [blame] | 316 | "Cannot attach $this as it already is attached. Tree: " + debugTreeToString() |
| 317 | } |
| 318 | check(_foldedParent == null || _foldedParent?.owner == owner) { |
| 319 | "Attaching to a different owner($owner) than the parent's owner(${parent?.owner})." + |
| 320 | " This tree: " + debugTreeToString() + |
| 321 | " Parent tree: " + _foldedParent?.debugTreeToString() |
George Mount | e11880a | 2020-07-17 15:28:22 -0700 | [diff] [blame] | 322 | } |
George Mount | fdd4f0f | 2020-11-19 23:42:29 +0000 | [diff] [blame] | 323 | val parent = this.parent |
Andrey Kulikov | 44e7828 | 2020-10-12 14:43:02 +0100 | [diff] [blame] | 324 | if (parent == null) { |
| 325 | // it is a root node and attached root nodes are always placed (as there is no parent |
| 326 | // to place them explicitly) |
| 327 | isPlaced = true |
| 328 | } |
| 329 | |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 330 | this.owner = owner |
| 331 | this.depth = (parent?.depth ?: -1) + 1 |
yingleiw | 6070eac | 2020-07-28 19:09:58 -0700 | [diff] [blame] | 332 | if (outerSemantics != null) { |
| 333 | owner.onSemanticsChange() |
| 334 | } |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 335 | owner.onAttach(this) |
Andrey Kulikov | 2ca0701 | 2020-07-13 16:14:03 +0100 | [diff] [blame] | 336 | _foldedChildren.forEach { child -> |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 337 | child.attach(owner) |
| 338 | } |
George Mount | 17d6fe5 | 2020-05-15 08:08:23 -0700 | [diff] [blame] | 339 | |
| 340 | requestRemeasure() |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 341 | parent?.requestRemeasure() |
Andrey Kulikov | 1102527 | 2020-11-18 19:23:35 +0000 | [diff] [blame] | 342 | innerLayoutNodeWrapper.attach() |
George Mount | a2e46b0 | 2020-06-15 10:33:15 -0700 | [diff] [blame] | 343 | forEachDelegate { it.attach() } |
George Mount | 17d6fe5 | 2020-05-15 08:08:23 -0700 | [diff] [blame] | 344 | onAttach?.invoke(owner) |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 345 | } |
| 346 | |
| 347 | /** |
George Mount | 17d6fe5 | 2020-05-15 08:08:23 -0700 | [diff] [blame] | 348 | * Remove the LayoutNode from the [Owner]. The [owner] must not be `null` before this call |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 349 | * and its [parent]'s [owner] must be `null` before calling this. This will also [detach] all |
| 350 | * children. After executing, the [owner] will be `null`. |
| 351 | */ |
Andrey Kulikov | 5fb82da | 2020-12-03 15:24:33 +0000 | [diff] [blame] | 352 | internal fun detach() { |
George Mount | e11880a | 2020-07-17 15:28:22 -0700 | [diff] [blame] | 353 | val owner = owner |
| 354 | checkNotNull(owner) { |
Alexandre Elias | e8b274d | 2021-06-03 19:38:18 -0700 | [diff] [blame] | 355 | "Cannot detach node that is already detached! Tree: " + parent?.debugTreeToString() |
George Mount | e11880a | 2020-07-17 15:28:22 -0700 | [diff] [blame] | 356 | } |
George Mount | fdd4f0f | 2020-11-19 23:42:29 +0000 | [diff] [blame] | 357 | val parent = this.parent |
| 358 | if (parent != null) { |
| 359 | parent.invalidateLayer() |
| 360 | parent.requestRemeasure() |
George Mount | 17d6fe5 | 2020-05-15 08:08:23 -0700 | [diff] [blame] | 361 | } |
Mihai Popa | 79815ff | 2021-04-27 17:05:22 +0100 | [diff] [blame] | 362 | alignmentLines.reset() |
George Mount | 17d6fe5 | 2020-05-15 08:08:23 -0700 | [diff] [blame] | 363 | onDetach?.invoke(owner) |
George Mount | a2e46b0 | 2020-06-15 10:33:15 -0700 | [diff] [blame] | 364 | forEachDelegate { it.detach() } |
Andrey Kulikov | 1102527 | 2020-11-18 19:23:35 +0000 | [diff] [blame] | 365 | innerLayoutNodeWrapper.detach() |
George Mount | 17d6fe5 | 2020-05-15 08:08:23 -0700 | [diff] [blame] | 366 | |
yingleiw | 8049eaa | 2020-04-30 15:05:57 -0700 | [diff] [blame] | 367 | if (outerSemantics != null) { |
| 368 | owner.onSemanticsChange() |
| 369 | } |
George Mount | 0b45f78 | 2019-07-09 13:30:28 -0700 | [diff] [blame] | 370 | owner.onDetach(this) |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 371 | this.owner = null |
| 372 | depth = 0 |
Andrey Kulikov | 2ca0701 | 2020-07-13 16:14:03 +0100 | [diff] [blame] | 373 | _foldedChildren.forEach { child -> |
Andrey Kulikov | 9771c6d | 2020-03-04 17:19:19 +0000 | [diff] [blame] | 374 | child.detach() |
| 375 | } |
Andrey Kulikov | fc91b88 | 2020-09-24 13:31:29 +0100 | [diff] [blame] | 376 | placeOrder = NotPlacedPlaceOrder |
Andrey Kulikov | 4aa63ec | 2021-05-24 18:06:35 +0100 | [diff] [blame] | 377 | previousPlaceOrder = NotPlacedPlaceOrder |
Andrey Kulikov | fc91b88 | 2020-09-24 13:31:29 +0100 | [diff] [blame] | 378 | isPlaced = false |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 379 | } |
Ryan Mentley | 7865a63 | 2019-08-20 20:10:29 -0700 | [diff] [blame] | 380 | |
Andrey Kulikov | fc91b88 | 2020-09-24 13:31:29 +0100 | [diff] [blame] | 381 | private val _zSortedChildren = mutableVectorOf<LayoutNode>() |
Andrey Kulikov | fa21583 | 2020-11-06 16:43:55 +0000 | [diff] [blame] | 382 | private var zSortedChildrenInvalidated = true |
Andrey Kulikov | d3b694d | 2020-03-30 19:20:34 +0100 | [diff] [blame] | 383 | |
| 384 | /** |
Andrey Kulikov | fc91b88 | 2020-09-24 13:31:29 +0100 | [diff] [blame] | 385 | * Returns the children list sorted by their [LayoutNode.zIndex] first (smaller first) and the |
| 386 | * order they were placed via [Placeable.placeAt] by parent (smaller first). |
| 387 | * Please note that this list contains not placed items as well, so you have to manually |
| 388 | * filter them. |
| 389 | * |
Andrey Kulikov | d3b694d | 2020-03-30 19:20:34 +0100 | [diff] [blame] | 390 | * Note that the object is reused so you shouldn't save it for later. |
| 391 | */ |
| 392 | @PublishedApi |
Andrey Kulikov | fc91b88 | 2020-09-24 13:31:29 +0100 | [diff] [blame] | 393 | internal val zSortedChildren: MutableVector<LayoutNode> |
Andrey Kulikov | d3b694d | 2020-03-30 19:20:34 +0100 | [diff] [blame] | 394 | get() { |
Andrey Kulikov | fa21583 | 2020-11-06 16:43:55 +0000 | [diff] [blame] | 395 | if (zSortedChildrenInvalidated) { |
| 396 | _zSortedChildren.clear() |
| 397 | _zSortedChildren.addAll(_children) |
| 398 | _zSortedChildren.sortWith(ZComparator) |
Andrey Kulikov | e80baea | 2021-05-18 13:57:40 +0100 | [diff] [blame] | 399 | zSortedChildrenInvalidated = false |
Andrey Kulikov | fa21583 | 2020-11-06 16:43:55 +0000 | [diff] [blame] | 400 | } |
Andrey Kulikov | fc91b88 | 2020-09-24 13:31:29 +0100 | [diff] [blame] | 401 | return _zSortedChildren |
Andrey Kulikov | d3b694d | 2020-03-30 19:20:34 +0100 | [diff] [blame] | 402 | } |
| 403 | |
George Mount | 669bd40 | 2020-09-02 18:12:10 -0700 | [diff] [blame] | 404 | override val isValid: Boolean |
Andrey Kulikov | 5fb82da | 2020-12-03 15:24:33 +0000 | [diff] [blame] | 405 | get() = isAttached |
George Mount | 669bd40 | 2020-09-02 18:12:10 -0700 | [diff] [blame] | 406 | |
Ryan Mentley | 7865a63 | 2019-08-20 20:10:29 -0700 | [diff] [blame] | 407 | override fun toString(): String { |
Andrey Kulikov | 2ca0701 | 2020-07-13 16:14:03 +0100 | [diff] [blame] | 408 | return "${simpleIdentityToString(this, null)} children: ${children.size} " + |
Mihai Popa | 4958475 | 2021-01-25 12:38:07 +0000 | [diff] [blame] | 409 | "measurePolicy: $measurePolicy" |
Ryan Mentley | 7865a63 | 2019-08-20 20:10:29 -0700 | [diff] [blame] | 410 | } |
| 411 | |
| 412 | /** |
George Mount | 17d6fe5 | 2020-05-15 08:08:23 -0700 | [diff] [blame] | 413 | * Call this method from the debugger to see a dump of the LayoutNode tree structure |
Ryan Mentley | 7865a63 | 2019-08-20 20:10:29 -0700 | [diff] [blame] | 414 | */ |
Ralston Da Silva | 228085e | 2020-08-12 03:34:16 -0700 | [diff] [blame] | 415 | @Suppress("unused") |
Ryan Mentley | 7865a63 | 2019-08-20 20:10:29 -0700 | [diff] [blame] | 416 | private fun debugTreeToString(depth: Int = 0): String { |
| 417 | val tree = StringBuilder() |
| 418 | for (i in 0 until depth) { |
| 419 | tree.append(" ") |
| 420 | } |
| 421 | tree.append("|-") |
| 422 | tree.append(toString()) |
| 423 | tree.append('\n') |
| 424 | |
George Mount | dfc9ad7 | 2020-07-07 13:03:02 -0700 | [diff] [blame] | 425 | _children.forEach { child -> |
Ryan Mentley | 7865a63 | 2019-08-20 20:10:29 -0700 | [diff] [blame] | 426 | tree.append(child.debugTreeToString(depth + 1)) |
| 427 | } |
| 428 | |
Ryan Mentley | 4b5c355 | 2021-01-20 04:22:32 -0800 | [diff] [blame] | 429 | var treeString = tree.toString() |
Ryan Mentley | 7865a63 | 2019-08-20 20:10:29 -0700 | [diff] [blame] | 430 | if (depth == 0) { |
| 431 | // Delete trailing newline |
Ryan Mentley | 4b5c355 | 2021-01-20 04:22:32 -0800 | [diff] [blame] | 432 | treeString = treeString.substring(0, treeString.length - 1) |
Ryan Mentley | 7865a63 | 2019-08-20 20:10:29 -0700 | [diff] [blame] | 433 | } |
Ryan Mentley | 4b5c355 | 2021-01-20 04:22:32 -0800 | [diff] [blame] | 434 | |
| 435 | return treeString |
Ryan Mentley | 7865a63 | 2019-08-20 20:10:29 -0700 | [diff] [blame] | 436 | } |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 437 | |
Mihai Popa | 4958475 | 2021-01-25 12:38:07 +0000 | [diff] [blame] | 438 | internal abstract class NoIntrinsicsMeasurePolicy(private val error: String) : MeasurePolicy { |
| 439 | override fun IntrinsicMeasureScope.minIntrinsicWidth( |
Andrey Kulikov | bd5cf9e | 2019-11-21 20:46:48 +0000 | [diff] [blame] | 440 | measurables: List<IntrinsicMeasurable>, |
Mihai Popa | 4958475 | 2021-01-25 12:38:07 +0000 | [diff] [blame] | 441 | height: Int |
Andrey Kulikov | bd5cf9e | 2019-11-21 20:46:48 +0000 | [diff] [blame] | 442 | ) = error(error) |
| 443 | |
Mihai Popa | 4958475 | 2021-01-25 12:38:07 +0000 | [diff] [blame] | 444 | override fun IntrinsicMeasureScope.minIntrinsicHeight( |
Andrey Kulikov | bd5cf9e | 2019-11-21 20:46:48 +0000 | [diff] [blame] | 445 | measurables: List<IntrinsicMeasurable>, |
Mihai Popa | 4958475 | 2021-01-25 12:38:07 +0000 | [diff] [blame] | 446 | width: Int |
Andrey Kulikov | bd5cf9e | 2019-11-21 20:46:48 +0000 | [diff] [blame] | 447 | ) = error(error) |
| 448 | |
Mihai Popa | 4958475 | 2021-01-25 12:38:07 +0000 | [diff] [blame] | 449 | override fun IntrinsicMeasureScope.maxIntrinsicWidth( |
Andrey Kulikov | bd5cf9e | 2019-11-21 20:46:48 +0000 | [diff] [blame] | 450 | measurables: List<IntrinsicMeasurable>, |
Mihai Popa | 4958475 | 2021-01-25 12:38:07 +0000 | [diff] [blame] | 451 | height: Int |
Andrey Kulikov | bd5cf9e | 2019-11-21 20:46:48 +0000 | [diff] [blame] | 452 | ) = error(error) |
| 453 | |
Mihai Popa | 4958475 | 2021-01-25 12:38:07 +0000 | [diff] [blame] | 454 | override fun IntrinsicMeasureScope.maxIntrinsicHeight( |
Andrey Kulikov | bd5cf9e | 2019-11-21 20:46:48 +0000 | [diff] [blame] | 455 | measurables: List<IntrinsicMeasurable>, |
Mihai Popa | 4958475 | 2021-01-25 12:38:07 +0000 | [diff] [blame] | 456 | width: Int |
Andrey Kulikov | bd5cf9e | 2019-11-21 20:46:48 +0000 | [diff] [blame] | 457 | ) = error(error) |
| 458 | } |
| 459 | |
Mihai Popa | 24925ab | 2019-10-09 17:13:41 +0100 | [diff] [blame] | 460 | /** |
| 461 | * Blocks that define the measurement and intrinsic measurement of the layout. |
| 462 | */ |
Mihai Popa | 4958475 | 2021-01-25 12:38:07 +0000 | [diff] [blame] | 463 | override var measurePolicy: MeasurePolicy = ErrorMeasurePolicy |
George Mount | 31c3edc | 2019-08-14 15:30:26 -0700 | [diff] [blame] | 464 | set(value) { |
Mihai Popa | ca1c4b9 | 2019-10-03 19:16:06 +0100 | [diff] [blame] | 465 | if (field != value) { |
| 466 | field = value |
Mihai Popa | f411f76 | 2021-06-07 18:21:24 +0100 | [diff] [blame] | 467 | intrinsicsPolicy.updateFrom(measurePolicy) |
Mihai Popa | ca1c4b9 | 2019-10-03 19:16:06 +0100 | [diff] [blame] | 468 | requestRemeasure() |
| 469 | } |
George Mount | 31c3edc | 2019-08-14 15:30:26 -0700 | [diff] [blame] | 470 | } |
| 471 | |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 472 | /** |
Mihai Popa | f411f76 | 2021-06-07 18:21:24 +0100 | [diff] [blame] | 473 | * The intrinsic measurements of this layout, backed up by states to trigger |
| 474 | * correct remeasurement for layouts using the intrinsics of this layout |
| 475 | * when the [measurePolicy] is changing. |
| 476 | */ |
| 477 | internal val intrinsicsPolicy = IntrinsicsPolicy(this) |
| 478 | |
| 479 | /** |
Mihai Popa | c297be1 | 2020-08-13 14:05:35 +0100 | [diff] [blame] | 480 | * The screen density to be used by this layout. |
| 481 | */ |
Andrey Kulikov | 057998a | 2021-01-28 18:02:50 +0000 | [diff] [blame] | 482 | override var density: Density = Density(1f) |
Andrey Kulikov | 51a67e5 | 2021-05-13 18:19:25 +0100 | [diff] [blame] | 483 | set(value) { |
| 484 | if (field != value) { |
| 485 | field = value |
| 486 | onDensityOrLayoutDirectionChanged() |
| 487 | } |
| 488 | } |
Mihai Popa | c297be1 | 2020-08-13 14:05:35 +0100 | [diff] [blame] | 489 | |
| 490 | /** |
Mihai Popa | 4958475 | 2021-01-25 12:38:07 +0000 | [diff] [blame] | 491 | * The scope used to [measure][MeasurePolicy.measure] children. |
Mihai Popa | 24925ab | 2019-10-09 17:13:41 +0100 | [diff] [blame] | 492 | */ |
George Mount | fdd4f0f | 2020-11-19 23:42:29 +0000 | [diff] [blame] | 493 | internal val measureScope: MeasureScope = object : MeasureScope, Density { |
Mihai Popa | c297be1 | 2020-08-13 14:05:35 +0100 | [diff] [blame] | 494 | override val density: Float get() = this@LayoutNode.density.density |
| 495 | override val fontScale: Float get() = this@LayoutNode.density.fontScale |
Mihai Popa | fc355b1 | 2020-04-15 17:34:17 +0100 | [diff] [blame] | 496 | override val layoutDirection: LayoutDirection get() = this@LayoutNode.layoutDirection |
Mihai Popa | 24925ab | 2019-10-09 17:13:41 +0100 | [diff] [blame] | 497 | } |
| 498 | |
| 499 | /** |
Anastasia Soboleva | 9474ff8 | 2020-02-19 19:02:15 +0000 | [diff] [blame] | 500 | * The layout direction of the layout node. |
| 501 | */ |
Andrey Kulikov | 057998a | 2021-01-28 18:02:50 +0000 | [diff] [blame] | 502 | override var layoutDirection: LayoutDirection = LayoutDirection.Ltr |
Anastasia Soboleva | 2833c36 | 2020-07-23 20:19:13 +0100 | [diff] [blame] | 503 | set(value) { |
| 504 | if (field != value) { |
| 505 | field = value |
Andrey Kulikov | 51a67e5 | 2021-05-13 18:19:25 +0100 | [diff] [blame] | 506 | onDensityOrLayoutDirectionChanged() |
Anastasia Soboleva | 2833c36 | 2020-07-23 20:19:13 +0100 | [diff] [blame] | 507 | } |
| 508 | } |
Anastasia Soboleva | 9474ff8 | 2020-02-19 19:02:15 +0000 | [diff] [blame] | 509 | |
George Mount | d1bb6b8 | 2021-07-23 22:52:24 +0000 | [diff] [blame] | 510 | override var viewConfiguration: ViewConfiguration = DummyViewConfiguration |
| 511 | |
Andrey Kulikov | 51a67e5 | 2021-05-13 18:19:25 +0100 | [diff] [blame] | 512 | private fun onDensityOrLayoutDirectionChanged() { |
| 513 | // measure/layout modifiers on the node |
| 514 | requestRemeasure() |
| 515 | // draw modifiers on the node |
| 516 | parent?.invalidateLayer() |
| 517 | // and draw modifiers after graphics layers on the node |
| 518 | invalidateLayers() |
| 519 | } |
| 520 | |
Anastasia Soboleva | 9474ff8 | 2020-02-19 19:02:15 +0000 | [diff] [blame] | 521 | /** |
Adam Powell | 384bbaa | 2019-07-11 14:44:14 -0700 | [diff] [blame] | 522 | * The measured width of this layout and all of its [modifier]s. Shortcut for `size.width`. |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 523 | */ |
Andrey Kulikov | 5fb82da | 2020-12-03 15:24:33 +0000 | [diff] [blame] | 524 | override val width: Int get() = outerMeasurablePlaceable.width |
George Mount | 4844e6c | 2019-02-25 13:43:44 -0800 | [diff] [blame] | 525 | |
| 526 | /** |
Adam Powell | 384bbaa | 2019-07-11 14:44:14 -0700 | [diff] [blame] | 527 | * The measured height of this layout and all of its [modifier]s. Shortcut for `size.height`. |
George Mount | 4844e6c | 2019-02-25 13:43:44 -0800 | [diff] [blame] | 528 | */ |
Andrey Kulikov | 5fb82da | 2020-12-03 15:24:33 +0000 | [diff] [blame] | 529 | override val height: Int get() = outerMeasurablePlaceable.height |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 530 | |
| 531 | /** |
Mihai Popa | f411f76 | 2021-06-07 18:21:24 +0100 | [diff] [blame] | 532 | * State corresponding to the alignment lines of this layout, inherited + intrinsic. |
Mihai Popa | 42aa757 | 2019-07-30 15:46:36 +0100 | [diff] [blame] | 533 | */ |
Mihai Popa | f411f76 | 2021-06-07 18:21:24 +0100 | [diff] [blame] | 534 | internal val alignmentLines = LayoutNodeAlignmentLines(this) |
Mihai Popa | 42aa757 | 2019-07-30 15:46:36 +0100 | [diff] [blame] | 535 | |
Igor Demin | 4c1c15e | 2021-08-09 15:15:10 +0300 | [diff] [blame] | 536 | internal val mDrawScope: LayoutNodeDrawScope |
| 537 | get() = requireOwner().sharedDrawScope |
Nader Jawad | 83a44b7 | 2020-05-11 14:32:09 -0700 | [diff] [blame] | 538 | |
Mihai Popa | 42aa757 | 2019-07-30 15:46:36 +0100 | [diff] [blame] | 539 | /** |
George Mount | fdd4f0f | 2020-11-19 23:42:29 +0000 | [diff] [blame] | 540 | * Whether or not this [LayoutNode] and all of its parents have been placed in the hierarchy. |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 541 | */ |
Andrey Kulikov | 5fb82da | 2020-12-03 15:24:33 +0000 | [diff] [blame] | 542 | override var isPlaced: Boolean = false |
Andrey Kulikov | 44e7828 | 2020-10-12 14:43:02 +0100 | [diff] [blame] | 543 | private set |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 544 | |
George Mount | 0311322 | 2019-03-25 14:30:21 -0700 | [diff] [blame] | 545 | /** |
Andrey Kulikov | fc91b88 | 2020-09-24 13:31:29 +0100 | [diff] [blame] | 546 | * The order in which this node was placed by its parent during the previous [layoutChildren]. |
| 547 | * Before the placement the order is set to [NotPlacedPlaceOrder] to all the children. Then |
| 548 | * every placed node assigns this variable to [parent]s [nextChildPlaceOrder] and increments |
| 549 | * this counter. Not placed items will still have [NotPlacedPlaceOrder] set. |
Andrey Kulikov | 308929c | 2020-09-03 17:07:40 +0300 | [diff] [blame] | 550 | */ |
Andrey Kulikov | a3b5661 | 2021-05-20 20:57:53 +0100 | [diff] [blame] | 551 | internal var placeOrder: Int = NotPlacedPlaceOrder |
| 552 | private set |
Andrey Kulikov | fc91b88 | 2020-09-24 13:31:29 +0100 | [diff] [blame] | 553 | |
| 554 | /** |
Andrey Kulikov | 4aa63ec | 2021-05-24 18:06:35 +0100 | [diff] [blame] | 555 | * The value [placeOrder] had during the previous parent [layoutChildren]. Helps us to |
| 556 | * understand if the order did change. |
| 557 | */ |
| 558 | private var previousPlaceOrder: Int = NotPlacedPlaceOrder |
| 559 | |
| 560 | /** |
Andrey Kulikov | fc91b88 | 2020-09-24 13:31:29 +0100 | [diff] [blame] | 561 | * The counter on a parent node which is used by its children to understand the order in which |
| 562 | * they were placed. |
| 563 | * @see placeOrder |
| 564 | */ |
| 565 | private var nextChildPlaceOrder: Int = 0 |
Andrey Kulikov | 308929c | 2020-09-03 17:07:40 +0300 | [diff] [blame] | 566 | |
| 567 | /** |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 568 | * Remembers how the node was measured by the parent. |
George Mount | ef4bb05 | 2019-04-11 11:30:53 -0700 | [diff] [blame] | 569 | */ |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 570 | internal var measuredByParent: UsageByParent = UsageByParent.NotUsed |
Mihai Popa | 42aa757 | 2019-07-30 15:46:36 +0100 | [diff] [blame] | 571 | |
Mihai Popa | 73815e2 | 2019-11-19 16:26:59 +0000 | [diff] [blame] | 572 | @Deprecated("Temporary API to support ConstraintLayout prototyping.") |
Andrey Kulikov | 5fb82da | 2020-12-03 15:24:33 +0000 | [diff] [blame] | 573 | internal var canMultiMeasure: Boolean = false |
Mihai Popa | 73815e2 | 2019-11-19 16:26:59 +0000 | [diff] [blame] | 574 | |
George Mount | d4741ec | 2020-01-23 07:30:38 -0800 | [diff] [blame] | 575 | internal val innerLayoutNodeWrapper: LayoutNodeWrapper = InnerPlaceable(this) |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 576 | private val outerMeasurablePlaceable = OuterMeasurablePlaceable(this, innerLayoutNodeWrapper) |
| 577 | internal val outerLayoutNodeWrapper: LayoutNodeWrapper |
| 578 | get() = outerMeasurablePlaceable.outerWrapper |
George Mount | 31c3edc | 2019-08-14 15:30:26 -0700 | [diff] [blame] | 579 | |
Adam Powell | 384bbaa | 2019-07-11 14:44:14 -0700 | [diff] [blame] | 580 | /** |
Andrey Kulikov | d3b694d | 2020-03-30 19:20:34 +0100 | [diff] [blame] | 581 | * zIndex defines the drawing order of the LayoutNode. Children with larger zIndex are drawn |
Andrey Kulikov | fa21583 | 2020-11-06 16:43:55 +0000 | [diff] [blame] | 582 | * on top of others (the original order is used for the nodes with the same zIndex). |
| 583 | * Default zIndex is 0. We use sum of the values passed as zIndex to place() by the |
| 584 | * parent layout and all the applied modifiers. |
Andrey Kulikov | d3b694d | 2020-03-30 19:20:34 +0100 | [diff] [blame] | 585 | */ |
Andrey Kulikov | fa21583 | 2020-11-06 16:43:55 +0000 | [diff] [blame] | 586 | private var zIndex: Float = 0f |
George Mount | c350977 | 2020-03-18 23:38:09 +0000 | [diff] [blame] | 587 | |
| 588 | /** |
Andrey Kulikov | 671baa5 | 2021-12-23 17:41:40 +0000 | [diff] [blame] | 589 | * The inner state associated with [androidx.compose.ui.layout.SubcomposeLayout]. |
| 590 | */ |
| 591 | internal var subcompositionsState: Any? = null |
| 592 | |
| 593 | /** |
George Mount | 2762fd9 | 2020-05-07 15:50:23 -0700 | [diff] [blame] | 594 | * The inner-most layer wrapper. Used for performance for LayoutNodeWrapper.findLayer(). |
| 595 | */ |
Andrey Kulikov | 1102527 | 2020-11-18 19:23:35 +0000 | [diff] [blame] | 596 | private var _innerLayerWrapper: LayoutNodeWrapper? = null |
Andrey Kulikov | 9e6f525 | 2020-12-04 13:08:38 +0000 | [diff] [blame] | 597 | internal var innerLayerWrapperIsDirty = true |
Ralston Da Silva | 6d0c432 | 2021-10-19 18:31:23 -0700 | [diff] [blame] | 598 | private val innerLayerWrapper: LayoutNodeWrapper? get() { |
Andrey Kulikov | 9e6f525 | 2020-12-04 13:08:38 +0000 | [diff] [blame] | 599 | if (innerLayerWrapperIsDirty) { |
| 600 | var delegate: LayoutNodeWrapper? = innerLayoutNodeWrapper |
| 601 | val final = outerLayoutNodeWrapper.wrappedBy |
| 602 | _innerLayerWrapper = null |
| 603 | while (delegate != final) { |
| 604 | if (delegate?.layer != null) { |
| 605 | _innerLayerWrapper = delegate |
| 606 | break |
| 607 | } |
| 608 | delegate = delegate?.wrappedBy |
| 609 | } |
| 610 | } |
Andrey Kulikov | 1102527 | 2020-11-18 19:23:35 +0000 | [diff] [blame] | 611 | val layerWrapper = _innerLayerWrapper |
| 612 | if (layerWrapper != null) { |
| 613 | requireNotNull(layerWrapper.layer) |
| 614 | } |
| 615 | return layerWrapper |
| 616 | } |
George Mount | 2762fd9 | 2020-05-07 15:50:23 -0700 | [diff] [blame] | 617 | |
| 618 | /** |
George Mount | 47330e9 | 2020-09-01 11:00:24 -0700 | [diff] [blame] | 619 | * Invalidates the inner-most layer as part of this LayoutNode or from the containing |
| 620 | * LayoutNode. This is added for performance so that LayoutNodeWrapper.invalidateLayer() can be |
| 621 | * faster. |
George Mount | 2762fd9 | 2020-05-07 15:50:23 -0700 | [diff] [blame] | 622 | */ |
George Mount | 47330e9 | 2020-09-01 11:00:24 -0700 | [diff] [blame] | 623 | internal fun invalidateLayer() { |
| 624 | val innerLayerWrapper = innerLayerWrapper |
| 625 | if (innerLayerWrapper != null) { |
| 626 | innerLayerWrapper.invalidateLayer() |
| 627 | } else { |
George Mount | fdd4f0f | 2020-11-19 23:42:29 +0000 | [diff] [blame] | 628 | val parent = this.parent |
George Mount | 47330e9 | 2020-09-01 11:00:24 -0700 | [diff] [blame] | 629 | parent?.invalidateLayer() |
| 630 | } |
George Mount | 2762fd9 | 2020-05-07 15:50:23 -0700 | [diff] [blame] | 631 | } |
| 632 | |
| 633 | /** |
Adam Powell | 384bbaa | 2019-07-11 14:44:14 -0700 | [diff] [blame] | 634 | * The [Modifier] currently applied to this node. |
| 635 | */ |
Doris Liu | 894c911 | 2021-08-09 16:12:47 -0700 | [diff] [blame] | 636 | @OptIn(ExperimentalComposeUiApi::class) |
Andrey Kulikov | 057998a | 2021-01-28 18:02:50 +0000 | [diff] [blame] | 637 | override var modifier: Modifier = Modifier |
Adam Powell | 384bbaa | 2019-07-11 14:44:14 -0700 | [diff] [blame] | 638 | set(value) { |
| 639 | if (value == field) return |
Andrey Kulikov | 2ca0701 | 2020-07-13 16:14:03 +0100 | [diff] [blame] | 640 | if (modifier != Modifier) { |
| 641 | require(!isVirtual) { "Modifiers are not supported on virtual LayoutNodes" } |
| 642 | } |
Adam Powell | 384bbaa | 2019-07-11 14:44:14 -0700 | [diff] [blame] | 643 | field = value |
| 644 | |
George Mount | af94176 | 2020-07-06 17:42:34 -0700 | [diff] [blame] | 645 | val invalidateParentLayer = shouldInvalidateParentLayer() |
George Mount | af94176 | 2020-07-06 17:42:34 -0700 | [diff] [blame] | 646 | |
George Mount | a2e46b0 | 2020-06-15 10:33:15 -0700 | [diff] [blame] | 647 | copyWrappersToCache() |
George Mount | 4058e84 | 2020-12-07 23:18:21 +0000 | [diff] [blame] | 648 | markReusedModifiers(value) |
George Mount | a2e46b0 | 2020-06-15 10:33:15 -0700 | [diff] [blame] | 649 | |
George Mount | fdd4f0f | 2020-11-19 23:42:29 +0000 | [diff] [blame] | 650 | // Rebuild LayoutNodeWrapper |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 651 | val oldOuterWrapper = outerMeasurablePlaceable.outerWrapper |
Andrey Kulikov | 5fb82da | 2020-12-03 15:24:33 +0000 | [diff] [blame] | 652 | if (outerSemantics != null && isAttached) { |
yingleiw | 8049eaa | 2020-04-30 15:05:57 -0700 | [diff] [blame] | 653 | owner!!.onSemanticsChange() |
| 654 | } |
George Mount | fb2c353 | 2020-03-06 12:47:54 -0800 | [diff] [blame] | 655 | val addedCallback = hasNewPositioningCallback() |
Andrey Kulikov | 1f07d0fbb5 | 2021-03-19 17:59:07 +0000 | [diff] [blame] | 656 | onPositionedCallbacks?.clear() |
George Mount | a2e46b0 | 2020-06-15 10:33:15 -0700 | [diff] [blame] | 657 | |
George Mount | 6090475 | 2021-11-12 15:57:59 -0800 | [diff] [blame] | 658 | innerLayoutNodeWrapper.onInitialize() |
| 659 | |
George Mount | a2e46b0 | 2020-06-15 10:33:15 -0700 | [diff] [blame] | 660 | // Create a new chain of LayoutNodeWrappers, reusing existing ones from wrappers |
| 661 | // when possible. |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 662 | val outerWrapper = modifier.foldOut(innerLayoutNodeWrapper) { mod, toWrap -> |
Andrey Kulikov | 839b35c | 2020-07-08 18:55:13 +0100 | [diff] [blame] | 663 | if (mod is RemeasurementModifier) { |
| 664 | mod.onRemeasurementAvailable(this) |
| 665 | } |
George Mount | a2e46b0 | 2020-06-15 10:33:15 -0700 | [diff] [blame] | 666 | |
George Mount | 6090475 | 2021-11-12 15:57:59 -0800 | [diff] [blame] | 667 | if (mod is DrawModifier) { |
| 668 | val drawEntity = DrawEntity(toWrap, mod) |
| 669 | drawEntity.next = toWrap.drawEntityHead |
| 670 | toWrap.drawEntityHead = drawEntity |
| 671 | drawEntity.onInitialize() |
| 672 | } |
| 673 | |
Ralston Da Silva | 6d0c432 | 2021-10-19 18:31:23 -0700 | [diff] [blame] | 674 | // Re-use the layoutNodeWrapper if possible. |
| 675 | reuseLayoutNodeWrapper(mod, toWrap)?.let { |
| 676 | return@foldOut it |
| 677 | } |
| 678 | |
| 679 | // The order in which the following blocks occur matters. For example, the |
| 680 | // DrawModifier block should be before the LayoutModifier block so that a |
| 681 | // Modifier that implements both DrawModifier and LayoutModifier will have |
| 682 | // it's draw bounds reflect the dimensions defined by the LayoutModifier. |
| 683 | // Please ensure that ModifierLocalProvider is the first item here so that |
| 684 | // other layoutNodeWrappers don't accidentally use values that they provided. |
| 685 | // Also ensure that ModifierLocalConsumer is the next item here, so that it is |
| 686 | // created after all the other LayoutNodeWrappers are created, (So that the |
| 687 | // other layoutNodeWrappers are initialized by the time |
| 688 | // onModifierLocalsUpdated() is called. |
| 689 | var wrapper = toWrap |
| 690 | if (mod is ModifierLocalProvider<*>) { |
| 691 | wrapper = ModifierLocalProviderNode(wrapper, mod) |
| 692 | .initialize() |
| 693 | .assignChained(toWrap) |
| 694 | } |
| 695 | if (mod is ModifierLocalConsumer) { |
| 696 | wrapper = ModifierLocalConsumerNode(wrapper, mod) |
| 697 | .initialize() |
| 698 | .assignChained(toWrap) |
| 699 | } |
Ralston Da Silva | 6d0c432 | 2021-10-19 18:31:23 -0700 | [diff] [blame] | 700 | if (mod is FocusModifier) { |
| 701 | wrapper = ModifiedFocusNode(wrapper, mod) |
| 702 | .initialize() |
| 703 | .assignChained(toWrap) |
| 704 | } |
| 705 | if (mod is FocusEventModifier) { |
| 706 | wrapper = ModifiedFocusEventNode(wrapper, mod) |
| 707 | .initialize() |
| 708 | .assignChained(toWrap) |
| 709 | } |
| 710 | if (mod is FocusRequesterModifier) { |
| 711 | wrapper = ModifiedFocusRequesterNode(wrapper, mod) |
| 712 | .initialize() |
| 713 | .assignChained(toWrap) |
| 714 | } |
| 715 | if (mod is FocusOrderModifier) { |
| 716 | wrapper = ModifiedFocusOrderNode(wrapper, mod) |
| 717 | .initialize() |
| 718 | .assignChained(toWrap) |
| 719 | } |
| 720 | if (mod is KeyInputModifier) { |
| 721 | wrapper = ModifiedKeyInputNode(wrapper, mod) |
| 722 | .initialize() |
| 723 | .assignChained(toWrap) |
| 724 | } |
| 725 | if (mod is PointerInputModifier) { |
| 726 | wrapper = PointerInputDelegatingWrapper(wrapper, mod) |
| 727 | .initialize() |
| 728 | .assignChained(toWrap) |
| 729 | } |
| 730 | if (mod is NestedScrollModifier) { |
| 731 | wrapper = NestedScrollDelegatingWrapper(wrapper, mod) |
| 732 | .initialize() |
| 733 | .assignChained(toWrap) |
| 734 | } |
| 735 | if (mod is LayoutModifier) { |
| 736 | wrapper = ModifiedLayoutNode(wrapper, mod) |
| 737 | .initialize() |
| 738 | .assignChained(toWrap) |
| 739 | } |
| 740 | if (mod is ParentDataModifier) { |
| 741 | wrapper = ModifiedParentDataNode(wrapper, mod) |
| 742 | .initialize() |
| 743 | .assignChained(toWrap) |
| 744 | } |
| 745 | if (mod is SemanticsModifier) { |
| 746 | wrapper = SemanticsWrapper(wrapper, mod) |
| 747 | .initialize() |
| 748 | .assignChained(toWrap) |
| 749 | } |
| 750 | if (mod is OnRemeasuredModifier) { |
| 751 | wrapper = RemeasureModifierWrapper(wrapper, mod) |
| 752 | .initialize() |
| 753 | .assignChained(toWrap) |
| 754 | } |
Doris Liu | 894c911 | 2021-08-09 16:12:47 -0700 | [diff] [blame] | 755 | if (mod is OnPlacedModifier) { |
| 756 | wrapper = OnPlacedModifierWrapper(wrapper, mod) |
| 757 | .initialize() |
| 758 | .assignChained(toWrap) |
| 759 | } |
Ralston Da Silva | 6d0c432 | 2021-10-19 18:31:23 -0700 | [diff] [blame] | 760 | if (mod is OnGloballyPositionedModifier) { |
| 761 | wrapper = OnGloballyPositionedModifierWrapper(wrapper, mod) |
| 762 | .initialize() |
| 763 | .assignChained(toWrap) |
George Mount | a2e46b0 | 2020-06-15 10:33:15 -0700 | [diff] [blame] | 764 | } |
George Mount | f220d66 | 2019-12-20 15:17:50 -0800 | [diff] [blame] | 765 | wrapper |
Adam Powell | 384bbaa | 2019-07-11 14:44:14 -0700 | [diff] [blame] | 766 | } |
George Mount | a2e46b0 | 2020-06-15 10:33:15 -0700 | [diff] [blame] | 767 | |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 768 | outerWrapper.wrappedBy = parent?.innerLayoutNodeWrapper |
| 769 | outerMeasurablePlaceable.outerWrapper = outerWrapper |
George Mount | a2e46b0 | 2020-06-15 10:33:15 -0700 | [diff] [blame] | 770 | |
Andrey Kulikov | 5fb82da | 2020-12-03 15:24:33 +0000 | [diff] [blame] | 771 | if (isAttached) { |
George Mount | a2e46b0 | 2020-06-15 10:33:15 -0700 | [diff] [blame] | 772 | // call detach() on all removed LayoutNodeWrappers |
Andrey Kulikov | 1102527 | 2020-11-18 19:23:35 +0000 | [diff] [blame] | 773 | wrapperCache.forEach { |
| 774 | it.detach() |
Andrey Kulikov | 1102527 | 2020-11-18 19:23:35 +0000 | [diff] [blame] | 775 | } |
George Mount | a2e46b0 | 2020-06-15 10:33:15 -0700 | [diff] [blame] | 776 | |
| 777 | // attach() all new LayoutNodeWrappers |
| 778 | forEachDelegate { |
| 779 | if (!it.isAttached) { |
| 780 | it.attach() |
| 781 | } |
| 782 | } |
| 783 | } |
George Mount | dfc9ad7 | 2020-07-07 13:03:02 -0700 | [diff] [blame] | 784 | wrapperCache.clear() |
George Mount | a2e46b0 | 2020-06-15 10:33:15 -0700 | [diff] [blame] | 785 | |
| 786 | // call onModifierChanged() on all LayoutNodeWrappers |
| 787 | forEachDelegate { it.onModifierChanged() } |
| 788 | |
Adam Powell | 384bbaa | 2019-07-11 14:44:14 -0700 | [diff] [blame] | 789 | // Optimize the case where the layout itself is not modified. A common reason for |
| 790 | // this is if no wrapping actually occurs above because no LayoutModifiers are |
| 791 | // present in the modifier chain. |
George Mount | a2e46b0 | 2020-06-15 10:33:15 -0700 | [diff] [blame] | 792 | if (oldOuterWrapper != innerLayoutNodeWrapper || |
Ralston Da Silva | ca1c1f85 | 2020-07-20 13:29:18 -0700 | [diff] [blame] | 793 | outerWrapper != innerLayoutNodeWrapper |
| 794 | ) { |
Adam Powell | 384bbaa | 2019-07-11 14:44:14 -0700 | [diff] [blame] | 795 | requestRemeasure() |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 796 | } else if (layoutState == Ready && addedCallback) { |
George Mount | fb2c353 | 2020-03-06 12:47:54 -0800 | [diff] [blame] | 797 | // We need to notify the callbacks of a change in position since there's |
| 798 | // a new one. |
| 799 | requestRemeasure() |
Adam Powell | 384bbaa | 2019-07-11 14:44:14 -0700 | [diff] [blame] | 800 | } |
Mihai Popa | 8b37d190 | 2020-10-06 12:47:41 +0100 | [diff] [blame] | 801 | // If the parent data has changed, the parent needs remeasurement. |
| 802 | val oldParentData = parentData |
| 803 | outerMeasurablePlaceable.recalculateParentData() |
| 804 | if (oldParentData != parentData) { |
| 805 | parent?.requestRemeasure() |
| 806 | } |
Andrey Kulikov | fa21583 | 2020-11-06 16:43:55 +0000 | [diff] [blame] | 807 | if (invalidateParentLayer || shouldInvalidateParentLayer()) { |
George Mount | 47330e9 | 2020-09-01 11:00:24 -0700 | [diff] [blame] | 808 | parent?.invalidateLayer() |
George Mount | af94176 | 2020-07-06 17:42:34 -0700 | [diff] [blame] | 809 | } |
Adam Powell | 384bbaa | 2019-07-11 14:44:14 -0700 | [diff] [blame] | 810 | } |
| 811 | |
George Mount | d4741ec | 2020-01-23 07:30:38 -0800 | [diff] [blame] | 812 | /** |
George Mount | fdd4f0f | 2020-11-19 23:42:29 +0000 | [diff] [blame] | 813 | * Coordinates of just the contents of the [LayoutNode], after being affected by all modifiers. |
George Mount | d4741ec | 2020-01-23 07:30:38 -0800 | [diff] [blame] | 814 | */ |
Andrey Kulikov | 5fb82da | 2020-12-03 15:24:33 +0000 | [diff] [blame] | 815 | override val coordinates: LayoutCoordinates |
George Mount | d4741ec | 2020-01-23 07:30:38 -0800 | [diff] [blame] | 816 | get() = innerLayoutNodeWrapper |
Andrey Kulikov | 9c10dea | 2019-09-02 19:32:29 +0100 | [diff] [blame] | 817 | |
Mihai Popa | eb38a34 | 2019-11-27 13:17:49 +0000 | [diff] [blame] | 818 | /** |
| 819 | * Callback to be executed whenever the [LayoutNode] is attached to a new [Owner]. |
| 820 | */ |
George Mount | fdd4f0f | 2020-11-19 23:42:29 +0000 | [diff] [blame] | 821 | internal var onAttach: ((Owner) -> Unit)? = null |
Mihai Popa | eb38a34 | 2019-11-27 13:17:49 +0000 | [diff] [blame] | 822 | |
Mihai Popa | eb38a34 | 2019-11-27 13:17:49 +0000 | [diff] [blame] | 823 | /** |
| 824 | * Callback to be executed whenever the [LayoutNode] is detached from an [Owner]. |
| 825 | */ |
George Mount | fdd4f0f | 2020-11-19 23:42:29 +0000 | [diff] [blame] | 826 | internal var onDetach: ((Owner) -> Unit)? = null |
Mihai Popa | eb38a34 | 2019-11-27 13:17:49 +0000 | [diff] [blame] | 827 | |
George Mount | fb2c353 | 2020-03-06 12:47:54 -0800 | [diff] [blame] | 828 | /** |
| 829 | * List of all OnPositioned callbacks in the modifier chain. |
| 830 | */ |
Andrey Kulikov | eef6d91 | 2021-05-14 13:12:45 +0100 | [diff] [blame] | 831 | private var onPositionedCallbacks: MutableVector<OnGloballyPositionedModifierWrapper>? = null |
| 832 | |
Ralston Da Silva | 6d0c432 | 2021-10-19 18:31:23 -0700 | [diff] [blame] | 833 | internal fun getOrCreateOnPositionedCallbacks() = onPositionedCallbacks |
Andrey Kulikov | eef6d91 | 2021-05-14 13:12:45 +0100 | [diff] [blame] | 834 | ?: mutableVectorOf<OnGloballyPositionedModifierWrapper>().also { |
| 835 | onPositionedCallbacks = it |
| 836 | } |
George Mount | fb2c353 | 2020-03-06 12:47:54 -0800 | [diff] [blame] | 837 | |
| 838 | /** |
George Mount | 1e6c7ff | 2020-07-24 15:22:40 -0700 | [diff] [blame] | 839 | * Flag used by [OnPositionedDispatcher] to identify LayoutNodes that have already |
George Mount | 43d2036 | 2020-09-21 13:40:43 -0700 | [diff] [blame] | 840 | * had their [OnGloballyPositionedModifier]'s dispatch called so that they aren't called |
George Mount | 1e6c7ff | 2020-07-24 15:22:40 -0700 | [diff] [blame] | 841 | * multiple times. |
| 842 | */ |
| 843 | internal var needsOnPositionedDispatch = false |
| 844 | |
Andrey Kulikov | 5fb82da | 2020-12-03 15:24:33 +0000 | [diff] [blame] | 845 | internal fun place(x: Int, y: Int) { |
Mihai Popa | 36b705d | 2020-09-14 11:59:30 +0100 | [diff] [blame] | 846 | Placeable.PlacementScope.executeWithRtlMirroringValues( |
| 847 | outerMeasurablePlaceable.measuredWidth, |
| 848 | layoutDirection |
| 849 | ) { |
Anastasia Soboleva | 63cab53 | 2020-08-06 11:06:37 +0100 | [diff] [blame] | 850 | outerMeasurablePlaceable.placeRelative(x, y) |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 851 | } |
| 852 | } |
| 853 | |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 854 | /** |
| 855 | * Place this layout node again on the same position it was placed last time |
| 856 | */ |
| 857 | internal fun replace() { |
Andrey Kulikov | 1fe98b8 | 2021-12-08 19:23:00 +0000 | [diff] [blame] | 858 | try { |
| 859 | relayoutWithoutParentInProgress = true |
| 860 | outerMeasurablePlaceable.replace() |
| 861 | } finally { |
| 862 | relayoutWithoutParentInProgress = false |
| 863 | } |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 864 | } |
| 865 | |
Andrey Kulikov | 1fe98b8 | 2021-12-08 19:23:00 +0000 | [diff] [blame] | 866 | /** |
| 867 | * Is true during [replace] invocation. Helps to differentiate between the cases when our |
| 868 | * parent is measuring us during the measure block, and when we are remeasured individually |
| 869 | * because of some change. This could be useful to know if we need to record the placing order. |
| 870 | */ |
| 871 | private var relayoutWithoutParentInProgress = false |
| 872 | |
George Mount | fdd4f0f | 2020-11-19 23:42:29 +0000 | [diff] [blame] | 873 | internal fun draw(canvas: Canvas) = outerLayoutNodeWrapper.draw(canvas) |
George Mount | 932dc5d | 2019-10-28 08:47:10 -0700 | [diff] [blame] | 874 | |
George Mount | d4741ec | 2020-01-23 07:30:38 -0800 | [diff] [blame] | 875 | /** |
Shep Shapard | ae9da847 | 2020-02-06 11:33:08 -0800 | [diff] [blame] | 876 | * Carries out a hit test on the [PointerInputModifier]s associated with this [LayoutNode] and |
| 877 | * all [PointerInputModifier]s on all descendant [LayoutNode]s. |
| 878 | * |
George Mount | f1cab88 | 2021-01-22 23:38:32 +0000 | [diff] [blame] | 879 | * If [pointerPosition] is within the bounds of any tested |
George Mount | d1bb6b8 | 2021-07-23 22:52:24 +0000 | [diff] [blame] | 880 | * [PointerInputModifier]s, the [PointerInputModifier] is added to [hitTestResult] |
Shep Shapard | ae9da847 | 2020-02-06 11:33:08 -0800 | [diff] [blame] | 881 | * and true is returned. |
| 882 | * |
George Mount | f1cab88 | 2021-01-22 23:38:32 +0000 | [diff] [blame] | 883 | * @param pointerPosition The tested pointer position, which is relative to |
| 884 | * the LayoutNode. |
George Mount | d1bb6b8 | 2021-07-23 22:52:24 +0000 | [diff] [blame] | 885 | * @param hitTestResult The collection that the hit [PointerInputFilter]s will be |
Shep Shapard | ae9da847 | 2020-02-06 11:33:08 -0800 | [diff] [blame] | 886 | * added to if hit. |
Shep Shapard | ae9da847 | 2020-02-06 11:33:08 -0800 | [diff] [blame] | 887 | */ |
George Mount | fdd4f0f | 2020-11-19 23:42:29 +0000 | [diff] [blame] | 888 | internal fun hitTest( |
George Mount | f1cab88 | 2021-01-22 23:38:32 +0000 | [diff] [blame] | 889 | pointerPosition: Offset, |
George Mount | d1bb6b8 | 2021-07-23 22:52:24 +0000 | [diff] [blame] | 890 | hitTestResult: HitTestResult<PointerInputFilter>, |
George Mount | e4f4cac | 2021-11-08 16:33:01 -0800 | [diff] [blame] | 891 | isTouchEvent: Boolean = false, |
| 892 | isInLayer: Boolean = true |
Shep Shapard | 78f4289 | 2020-05-20 16:02:06 -0700 | [diff] [blame] | 893 | ) { |
George Mount | f1cab88 | 2021-01-22 23:38:32 +0000 | [diff] [blame] | 894 | val positionInWrapped = outerLayoutNodeWrapper.fromParentPosition(pointerPosition) |
Alexandre Elias | 5370dbc | 2021-06-01 19:35:18 -0700 | [diff] [blame] | 895 | outerLayoutNodeWrapper.hitTest( |
| 896 | positionInWrapped, |
George Mount | d1bb6b8 | 2021-07-23 22:52:24 +0000 | [diff] [blame] | 897 | hitTestResult, |
George Mount | e4f4cac | 2021-11-08 16:33:01 -0800 | [diff] [blame] | 898 | isTouchEvent, |
| 899 | isInLayer |
Alexandre Elias | 5370dbc | 2021-06-01 19:35:18 -0700 | [diff] [blame] | 900 | ) |
| 901 | } |
| 902 | |
George Mount | d1bb6b8 | 2021-07-23 22:52:24 +0000 | [diff] [blame] | 903 | @Suppress("UNUSED_PARAMETER") |
Alexandre Elias | 5370dbc | 2021-06-01 19:35:18 -0700 | [diff] [blame] | 904 | internal fun hitTestSemantics( |
| 905 | pointerPosition: Offset, |
George Mount | d1bb6b8 | 2021-07-23 22:52:24 +0000 | [diff] [blame] | 906 | hitSemanticsWrappers: HitTestResult<SemanticsWrapper>, |
George Mount | e4f4cac | 2021-11-08 16:33:01 -0800 | [diff] [blame] | 907 | isTouchEvent: Boolean = true, |
| 908 | isInLayer: Boolean = true |
Alexandre Elias | 5370dbc | 2021-06-01 19:35:18 -0700 | [diff] [blame] | 909 | ) { |
| 910 | val positionInWrapped = outerLayoutNodeWrapper.fromParentPosition(pointerPosition) |
| 911 | outerLayoutNodeWrapper.hitTestSemantics( |
| 912 | positionInWrapped, |
George Mount | e4f4cac | 2021-11-08 16:33:01 -0800 | [diff] [blame] | 913 | hitSemanticsWrappers, |
| 914 | isInLayer |
Alexandre Elias | 5370dbc | 2021-06-01 19:35:18 -0700 | [diff] [blame] | 915 | ) |
Shep Shapard | ae9da847 | 2020-02-06 11:33:08 -0800 | [diff] [blame] | 916 | } |
| 917 | |
| 918 | /** |
George Mount | 43d2036 | 2020-09-21 13:40:43 -0700 | [diff] [blame] | 919 | * Return true if there is a new [OnGloballyPositionedModifier] assigned to this Layout. |
George Mount | fb2c353 | 2020-03-06 12:47:54 -0800 | [diff] [blame] | 920 | */ |
| 921 | private fun hasNewPositioningCallback(): Boolean { |
Andrey Kulikov | 1f07d0fbb5 | 2021-03-19 17:59:07 +0000 | [diff] [blame] | 922 | val onPositionedCallbacks = onPositionedCallbacks |
Andrey Kulikov | eef6d91 | 2021-05-14 13:12:45 +0100 | [diff] [blame] | 923 | return modifier.foldOut(false) { mod, hasNewCallback -> |
| 924 | hasNewCallback || mod is OnGloballyPositionedModifier && |
| 925 | (onPositionedCallbacks?.firstOrNull { mod == it.modifier } == null) |
George Mount | fb2c353 | 2020-03-06 12:47:54 -0800 | [diff] [blame] | 926 | } |
| 927 | } |
| 928 | |
Andrey Kulikov | 308929c | 2020-09-03 17:07:40 +0300 | [diff] [blame] | 929 | /** |
| 930 | * Invoked when the parent placed the node. It will trigger the layout. |
| 931 | */ |
| 932 | internal fun onNodePlaced() { |
Andrey Kulikov | fc91b88 | 2020-09-24 13:31:29 +0100 | [diff] [blame] | 933 | val parent = parent |
| 934 | |
Andrey Kulikov | 1102527 | 2020-11-18 19:23:35 +0000 | [diff] [blame] | 935 | var newZIndex = innerLayoutNodeWrapper.zIndex |
Andrey Kulikov | fa21583 | 2020-11-06 16:43:55 +0000 | [diff] [blame] | 936 | forEachDelegate { |
| 937 | newZIndex += it.zIndex |
| 938 | } |
| 939 | if (newZIndex != zIndex) { |
| 940 | zIndex = newZIndex |
Andrey Kulikov | e80baea | 2021-05-18 13:57:40 +0100 | [diff] [blame] | 941 | parent?.onZSortedChildrenInvalidated() |
Andrey Kulikov | fa21583 | 2020-11-06 16:43:55 +0000 | [diff] [blame] | 942 | parent?.invalidateLayer() |
| 943 | } |
| 944 | |
Andrey Kulikov | 308929c | 2020-09-03 17:07:40 +0300 | [diff] [blame] | 945 | if (!isPlaced) { |
Andrey Kulikov | 308929c | 2020-09-03 17:07:40 +0300 | [diff] [blame] | 946 | // when the visibility of a child has been changed we need to invalidate |
| 947 | // parents inner layer - the layer in which this child will be drawn |
| 948 | parent?.invalidateLayer() |
Andrey Kulikov | a3b5661 | 2021-05-20 20:57:53 +0100 | [diff] [blame] | 949 | markNodeAndSubtreeAsPlaced() |
Andrey Kulikov | 308929c | 2020-09-03 17:07:40 +0300 | [diff] [blame] | 950 | } |
Andrey Kulikov | fc91b88 | 2020-09-24 13:31:29 +0100 | [diff] [blame] | 951 | |
| 952 | if (parent != null) { |
Andrey Kulikov | 1fe98b8 | 2021-12-08 19:23:00 +0000 | [diff] [blame] | 953 | if (!relayoutWithoutParentInProgress && parent.layoutState == LayingOut) { |
Andrey Kulikov | fc91b88 | 2020-09-24 13:31:29 +0100 | [diff] [blame] | 954 | // the parent is currently placing its children |
| 955 | check(placeOrder == NotPlacedPlaceOrder) { |
| 956 | "Place was called on a node which was placed already" |
| 957 | } |
| 958 | placeOrder = parent.nextChildPlaceOrder |
| 959 | parent.nextChildPlaceOrder++ |
| 960 | } |
Andrey Kulikov | 1fe98b8 | 2021-12-08 19:23:00 +0000 | [diff] [blame] | 961 | // if relayoutWithoutParentInProgress is true we were asked to be relaid out without |
| 962 | // affecting the parent. this means our placeOrder didn't change since the last time |
| 963 | // parent placed us. |
Andrey Kulikov | fc91b88 | 2020-09-24 13:31:29 +0100 | [diff] [blame] | 964 | } else { |
| 965 | // parent is null for the root node |
| 966 | placeOrder = 0 |
| 967 | } |
| 968 | |
Andrey Kulikov | 308929c | 2020-09-03 17:07:40 +0300 | [diff] [blame] | 969 | layoutChildren() |
| 970 | } |
| 971 | |
Mihai Popa | 79815ff | 2021-04-27 17:05:22 +0100 | [diff] [blame] | 972 | internal fun layoutChildren() { |
| 973 | alignmentLines.recalculateQueryOwner() |
| 974 | |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 975 | if (layoutState == NeedsRelayout) { |
| 976 | onBeforeLayoutChildren() |
| 977 | } |
| 978 | // as a result of the previous operation we can figure out a child has been resized |
| 979 | // and we need to be remeasured, not relaid out |
| 980 | if (layoutState == NeedsRelayout) { |
George Mount | fdd4f0f | 2020-11-19 23:42:29 +0000 | [diff] [blame] | 981 | layoutState = LayingOut |
Andrey Kulikov | 9ce118c | 2019-10-14 19:18:41 +0100 | [diff] [blame] | 982 | val owner = requireOwner() |
Andrey Kulikov | d82950b | 2020-11-11 15:23:18 +0000 | [diff] [blame] | 983 | owner.snapshotObserver.observeLayoutSnapshotReads(this) { |
Andrey Kulikov | fc91b88 | 2020-09-24 13:31:29 +0100 | [diff] [blame] | 984 | // reset the place order counter which will be used by the children |
| 985 | nextChildPlaceOrder = 0 |
George Mount | dfc9ad7 | 2020-07-07 13:03:02 -0700 | [diff] [blame] | 986 | _children.forEach { child -> |
Andrey Kulikov | fc91b88 | 2020-09-24 13:31:29 +0100 | [diff] [blame] | 987 | // and reset the place order for all the children before placing them |
Andrey Kulikov | 4aa63ec | 2021-05-24 18:06:35 +0100 | [diff] [blame] | 988 | child.previousPlaceOrder = child.placeOrder |
Andrey Kulikov | fc91b88 | 2020-09-24 13:31:29 +0100 | [diff] [blame] | 989 | child.placeOrder = NotPlacedPlaceOrder |
Mihai Popa | 79815ff | 2021-04-27 17:05:22 +0100 | [diff] [blame] | 990 | child.alignmentLines.usedDuringParentLayout = false |
Andrey Kulikov | 1fe98b8 | 2021-12-08 19:23:00 +0000 | [diff] [blame] | 991 | // before rerunning the user's layout block reset previous measuredByParent |
| 992 | // for children which we measured in the layout block during the last run. |
| 993 | if (child.measuredByParent == UsageByParent.InLayoutBlock) { |
| 994 | child.measuredByParent = UsageByParent.NotUsed |
| 995 | } |
Mihai Popa | e517835 | 2019-09-03 17:04:13 +0100 | [diff] [blame] | 996 | } |
Mihai Popa | 79815ff | 2021-04-27 17:05:22 +0100 | [diff] [blame] | 997 | |
Anastasia Soboleva | 2833c36 | 2020-07-23 20:19:13 +0100 | [diff] [blame] | 998 | innerLayoutNodeWrapper.measureResult.placeChildren() |
George Mount | dfc9ad7 | 2020-07-07 13:03:02 -0700 | [diff] [blame] | 999 | _children.forEach { child -> |
Andrey Kulikov | fc91b88 | 2020-09-24 13:31:29 +0100 | [diff] [blame] | 1000 | // we set `placeOrder` to NotPlacedPlaceOrder for all the children, then |
| 1001 | // during the placeChildren() invocation the real order will be assigned for |
| 1002 | // all the placed children. |
Andrey Kulikov | 4aa63ec | 2021-05-24 18:06:35 +0100 | [diff] [blame] | 1003 | if (child.previousPlaceOrder != child.placeOrder) { |
| 1004 | onZSortedChildrenInvalidated() |
Andrey Kulikov | 6f0b4cb | 2021-03-30 21:40:19 +0100 | [diff] [blame] | 1005 | invalidateLayer() |
Andrey Kulikov | 4aa63ec | 2021-05-24 18:06:35 +0100 | [diff] [blame] | 1006 | if (child.placeOrder == NotPlacedPlaceOrder) { |
| 1007 | child.markSubtreeAsNotPlaced() |
| 1008 | } |
Andrey Kulikov | 308929c | 2020-09-03 17:07:40 +0300 | [diff] [blame] | 1009 | } |
Mihai Popa | 79815ff | 2021-04-27 17:05:22 +0100 | [diff] [blame] | 1010 | child.alignmentLines.previousUsedDuringParentLayout = |
| 1011 | child.alignmentLines.usedDuringParentLayout |
George Mount | 6623eb4 | 2019-12-04 14:38:44 -0800 | [diff] [blame] | 1012 | } |
Andrey Kulikov | 9c10dea | 2019-09-02 19:32:29 +0100 | [diff] [blame] | 1013 | } |
Mihai Popa | 42aa757 | 2019-07-30 15:46:36 +0100 | [diff] [blame] | 1014 | |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 1015 | layoutState = Ready |
| 1016 | } |
Mihai Popa | 79815ff | 2021-04-27 17:05:22 +0100 | [diff] [blame] | 1017 | |
| 1018 | if (alignmentLines.usedDuringParentLayout) { |
| 1019 | alignmentLines.previousUsedDuringParentLayout = true |
| 1020 | } |
| 1021 | if (alignmentLines.dirty && alignmentLines.required) alignmentLines.recalculate() |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 1022 | } |
| 1023 | |
Andrey Kulikov | a3b5661 | 2021-05-20 20:57:53 +0100 | [diff] [blame] | 1024 | private fun markNodeAndSubtreeAsPlaced() { |
| 1025 | isPlaced = true |
| 1026 | // invalidate all the nodes layers that were invalidated while the node was not placed |
| 1027 | forEachDelegateIncludingInner { |
| 1028 | if (it.lastLayerDrawingWasSkipped) { |
| 1029 | it.invalidateLayer() |
Andrey Kulikov | 308929c | 2020-09-03 17:07:40 +0300 | [diff] [blame] | 1030 | } |
| 1031 | } |
Andrey Kulikov | a3b5661 | 2021-05-20 20:57:53 +0100 | [diff] [blame] | 1032 | _children.forEach { |
| 1033 | // this child was placed during the previous parent's layoutChildren(). this means that |
| 1034 | // before the parent became not placed this child was placed. we need to restore that |
| 1035 | if (it.placeOrder != NotPlacedPlaceOrder) { |
| 1036 | it.markNodeAndSubtreeAsPlaced() |
| 1037 | rescheduleRemeasureOrRelayout(it) |
| 1038 | } |
| 1039 | } |
| 1040 | } |
| 1041 | |
| 1042 | private fun rescheduleRemeasureOrRelayout(it: LayoutNode) { |
| 1043 | when (val state = it.layoutState) { |
| 1044 | NeedsRemeasure, NeedsRelayout -> { |
| 1045 | // we need to reset the state before requesting as otherwise the request |
| 1046 | // would be ignored. |
| 1047 | it.layoutState = Ready |
| 1048 | // this node was scheduled for remeasure or relayout while it was not |
| 1049 | // placed. such requests are ignored for non-placed nodes so we have to |
| 1050 | // re-schedule remeasure or relayout. |
| 1051 | if (state == NeedsRemeasure) { |
| 1052 | it.requestRemeasure() |
| 1053 | } else { |
| 1054 | it.requestRelayout() |
| 1055 | } |
| 1056 | } |
| 1057 | Ready -> { |
| 1058 | // no extra work required and node is ready to be displayed |
| 1059 | } |
| 1060 | else -> throw IllegalStateException("Unexpected state ${it.layoutState}") |
| 1061 | } |
Andrey Kulikov | 308929c | 2020-09-03 17:07:40 +0300 | [diff] [blame] | 1062 | } |
| 1063 | |
| 1064 | private fun markSubtreeAsNotPlaced() { |
| 1065 | if (isPlaced) { |
| 1066 | isPlaced = false |
| 1067 | _children.forEach { |
Andrey Kulikov | 7e1446e | 2020-11-16 20:52:40 +0000 | [diff] [blame] | 1068 | it.markSubtreeAsNotPlaced() |
Andrey Kulikov | 308929c | 2020-09-03 17:07:40 +0300 | [diff] [blame] | 1069 | } |
| 1070 | } |
| 1071 | } |
| 1072 | |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 1073 | /** |
| 1074 | * The callback to be executed before running layoutChildren. |
| 1075 | * |
| 1076 | * There are possible cases when we run layoutChildren() on the parent node, but some of its |
| 1077 | * children are not yet measured even if they are supposed to be measured in the measure |
| 1078 | * block of our parent. |
| 1079 | * |
| 1080 | * Example: |
| 1081 | * val child = Layout(...) |
Ralston Da Silva | 6d0c432 | 2021-10-19 18:31:23 -0700 | [diff] [blame] | 1082 | * Layout(child) { measurable, constraints -> |
| 1083 | * val placeable = measurable.first().measure(constraints) |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 1084 | * layout(placeable.width, placeable.height) { |
| 1085 | * placeable.place(0, 0) |
| 1086 | * } |
| 1087 | * } |
| 1088 | * And now some set of changes scheduled remeasure for child and relayout for parent. |
| 1089 | * |
| 1090 | * During the [MeasureAndLayoutDelegate.measureAndLayout] we will start with the parent as it |
| 1091 | * has lower depth. Inside the layout block we will call placeable.width which is currently |
| 1092 | * dirty as the child was scheduled to remeasure. This callback will ensure it never happens |
| 1093 | * and pre-remeasure everything required for this layoutChildren(). |
| 1094 | */ |
| 1095 | private fun onBeforeLayoutChildren() { |
George Mount | dfc9ad7 | 2020-07-07 13:03:02 -0700 | [diff] [blame] | 1096 | _children.forEach { |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 1097 | if (it.layoutState == NeedsRemeasure && |
| 1098 | it.measuredByParent == UsageByParent.InMeasureBlock |
| 1099 | ) { |
| 1100 | if (it.remeasure()) { |
| 1101 | requestRemeasure() |
| 1102 | } |
| 1103 | } |
| 1104 | } |
| 1105 | } |
| 1106 | |
| 1107 | internal fun onAlignmentsChanged() { |
Mihai Popa | 79815ff | 2021-04-27 17:05:22 +0100 | [diff] [blame] | 1108 | if (alignmentLines.dirty) return |
| 1109 | alignmentLines.dirty = true |
| 1110 | |
| 1111 | val parent = parent ?: return |
| 1112 | if (alignmentLines.usedDuringParentMeasurement) { |
| 1113 | parent.requestRemeasure() |
| 1114 | } else if (alignmentLines.previousUsedDuringParentLayout) { |
| 1115 | parent.requestRelayout() |
Mihai Popa | 42aa757 | 2019-07-30 15:46:36 +0100 | [diff] [blame] | 1116 | } |
Mihai Popa | 79815ff | 2021-04-27 17:05:22 +0100 | [diff] [blame] | 1117 | if (alignmentLines.usedByModifierMeasurement) { |
| 1118 | requestRemeasure() |
| 1119 | } |
| 1120 | if (alignmentLines.usedByModifierLayout) { |
| 1121 | parent.requestRelayout() |
| 1122 | } |
| 1123 | parent.onAlignmentsChanged() |
Mihai Popa | 42aa757 | 2019-07-30 15:46:36 +0100 | [diff] [blame] | 1124 | } |
| 1125 | |
George Mount | 8f23757 | 2020-04-30 12:08:30 -0700 | [diff] [blame] | 1126 | internal fun calculateAlignmentLines(): Map<AlignmentLine, Int> { |
Mihai Popa | 79815ff | 2021-04-27 17:05:22 +0100 | [diff] [blame] | 1127 | if (!outerMeasurablePlaceable.duringAlignmentLinesQuery) { |
| 1128 | alignmentLinesQueriedByModifier() |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 1129 | } |
Mihai Popa | 79815ff | 2021-04-27 17:05:22 +0100 | [diff] [blame] | 1130 | layoutChildren() |
| 1131 | return alignmentLines.getLastCalculation() |
| 1132 | } |
| 1133 | |
| 1134 | private fun alignmentLinesQueriedByModifier() { |
| 1135 | if (layoutState == Measuring) { |
| 1136 | alignmentLines.usedByModifierMeasurement = true |
| 1137 | // We quickly transition to NeedsRelayout as we need the alignment lines now. |
| 1138 | // Later we will see that we also laid out as part of measurement and will skip layout. |
| 1139 | if (alignmentLines.dirty) layoutState = NeedsRelayout |
| 1140 | } else { |
| 1141 | // Note this can also happen for onGloballyPositioned queries. |
| 1142 | alignmentLines.usedByModifierLayout = true |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 1143 | } |
George Mount | 31c3edc | 2019-08-14 15:30:26 -0700 | [diff] [blame] | 1144 | } |
| 1145 | |
Mihai Popa | af03ea3 | 2020-10-19 14:47:36 +0100 | [diff] [blame] | 1146 | internal fun handleMeasureResult(measureResult: MeasureResult) { |
Mihai Popa | 155d722 | 2020-03-27 15:02:29 +0000 | [diff] [blame] | 1147 | innerLayoutNodeWrapper.measureResult = measureResult |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 1148 | } |
Mihai Popa | 8d4dcaa | 2019-03-12 17:05:24 +0000 | [diff] [blame] | 1149 | |
| 1150 | /** |
Mihai Popa | eb38a34 | 2019-11-27 13:17:49 +0000 | [diff] [blame] | 1151 | * Used to request a new measurement + layout pass from the owner. |
Mihai Popa | 8d4dcaa | 2019-03-12 17:05:24 +0000 | [diff] [blame] | 1152 | */ |
George Mount | fdd4f0f | 2020-11-19 23:42:29 +0000 | [diff] [blame] | 1153 | internal fun requestRemeasure() { |
Mihai Popa | f411f76 | 2021-06-07 18:21:24 +0100 | [diff] [blame] | 1154 | val owner = owner ?: return |
Andrey Kulikov | 56217e2 | 2021-06-03 20:02:31 +0100 | [diff] [blame] | 1155 | if (!ignoreRemeasureRequests && !isVirtual) { |
Mihai Popa | f411f76 | 2021-06-07 18:21:24 +0100 | [diff] [blame] | 1156 | owner.onRequestMeasure(this) |
Andrey Kulikov | 804e0e6 | 2021-04-16 17:51:44 +0100 | [diff] [blame] | 1157 | } |
| 1158 | } |
| 1159 | |
| 1160 | internal inline fun ignoreRemeasureRequests(block: () -> Unit) { |
| 1161 | ignoreRemeasureRequests = true |
| 1162 | block() |
| 1163 | ignoreRemeasureRequests = false |
Adam Powell | 384bbaa | 2019-07-11 14:44:14 -0700 | [diff] [blame] | 1164 | } |
George Mount | 31c3edc | 2019-08-14 15:30:26 -0700 | [diff] [blame] | 1165 | |
Andrey Kulikov | bd5cf9e | 2019-11-21 20:46:48 +0000 | [diff] [blame] | 1166 | /** |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 1167 | * Used to request a new layout pass from the owner. |
| 1168 | */ |
George Mount | fdd4f0f | 2020-11-19 23:42:29 +0000 | [diff] [blame] | 1169 | internal fun requestRelayout() { |
Andrey Kulikov | 56217e2 | 2021-06-03 20:02:31 +0100 | [diff] [blame] | 1170 | if (!isVirtual) { |
| 1171 | owner?.onRequestRelayout(this) |
| 1172 | } |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 1173 | } |
| 1174 | |
| 1175 | /** |
Andrey Kulikov | bd5cf9e | 2019-11-21 20:46:48 +0000 | [diff] [blame] | 1176 | * Execute your code within the [block] if you want some code to not be observed for the |
| 1177 | * model reads even if you are currently inside some observed scope like measuring. |
| 1178 | */ |
Andrey Kulikov | 915e3f2 | 2021-02-04 17:05:19 +0000 | [diff] [blame] | 1179 | internal fun withNoSnapshotReadObservation(block: () -> Unit) { |
| 1180 | requireOwner().snapshotObserver.withNoSnapshotReadObservation(block) |
Andrey Kulikov | bd5cf9e | 2019-11-21 20:46:48 +0000 | [diff] [blame] | 1181 | } |
| 1182 | |
Andrey Kulikov | 9c10dea | 2019-09-02 19:32:29 +0100 | [diff] [blame] | 1183 | internal fun dispatchOnPositionedCallbacks() { |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 1184 | if (layoutState != Ready) { |
| 1185 | return // it hasn't yet been properly positioned, so don't make a call |
George Mount | a04d2fa | 2020-03-26 17:04:49 -0700 | [diff] [blame] | 1186 | } |
Andrey Kulikov | b87ddf6 | 2020-03-31 17:27:27 +0100 | [diff] [blame] | 1187 | if (!isPlaced) { |
| 1188 | return // it hasn't been placed, so don't make a call |
| 1189 | } |
Andrey Kulikov | eef6d91 | 2021-05-14 13:12:45 +0100 | [diff] [blame] | 1190 | onPositionedCallbacks?.forEach { |
| 1191 | it.modifier.onGloballyPositioned(it) |
| 1192 | } |
George Mount | 31c3edc | 2019-08-14 15:30:26 -0700 | [diff] [blame] | 1193 | } |
| 1194 | |
George Mount | 2db95b0 | 2020-04-13 15:19:34 -0700 | [diff] [blame] | 1195 | /** |
| 1196 | * This returns a new List of Modifiers and the coordinates and any extra information |
| 1197 | * that may be useful. This is used for tooling to retrieve layout modifier and layer |
| 1198 | * information. |
| 1199 | */ |
Andrey Kulikov | 5fb82da | 2020-12-03 15:24:33 +0000 | [diff] [blame] | 1200 | override fun getModifierInfo(): List<ModifierInfo> { |
George Mount | dfc9ad7 | 2020-07-07 13:03:02 -0700 | [diff] [blame] | 1201 | val infoList = mutableVectorOf<ModifierInfo>() |
George Mount | a2e46b0 | 2020-06-15 10:33:15 -0700 | [diff] [blame] | 1202 | forEachDelegate { wrapper -> |
Andrey Kulikov | 1102527 | 2020-11-18 19:23:35 +0000 | [diff] [blame] | 1203 | wrapper as DelegatingLayoutNodeWrapper<*> |
George Mount | 6090475 | 2021-11-12 15:57:59 -0800 | [diff] [blame] | 1204 | val layer = wrapper.layer |
| 1205 | val info = ModifierInfo(wrapper.modifier, wrapper, layer) |
George Mount | 2db95b0 | 2020-04-13 15:19:34 -0700 | [diff] [blame] | 1206 | infoList += info |
George Mount | 6090475 | 2021-11-12 15:57:59 -0800 | [diff] [blame] | 1207 | var node = wrapper.drawEntityHead // head |
| 1208 | while (node != null) { |
| 1209 | infoList += ModifierInfo(node.modifier, wrapper, layer) |
| 1210 | node = node.next |
| 1211 | } |
| 1212 | } |
| 1213 | var innerNode = innerLayoutNodeWrapper.drawEntityHead |
| 1214 | while (innerNode != null) { |
| 1215 | infoList += ModifierInfo( |
| 1216 | innerNode.modifier, |
| 1217 | innerLayoutNodeWrapper, |
| 1218 | innerLayoutNodeWrapper.layer |
| 1219 | ) |
| 1220 | innerNode = innerNode.next |
George Mount | 2db95b0 | 2020-04-13 15:19:34 -0700 | [diff] [blame] | 1221 | } |
George Mount | dfc9ad7 | 2020-07-07 13:03:02 -0700 | [diff] [blame] | 1222 | return infoList.asMutableList() |
George Mount | 2db95b0 | 2020-04-13 15:19:34 -0700 | [diff] [blame] | 1223 | } |
| 1224 | |
George Mount | a2e46b0 | 2020-06-15 10:33:15 -0700 | [diff] [blame] | 1225 | /** |
George Mount | 4de8f5a | 2020-10-15 16:09:41 -0700 | [diff] [blame] | 1226 | * Invalidates layers defined on this LayoutNode. |
| 1227 | */ |
| 1228 | internal fun invalidateLayers() { |
| 1229 | forEachDelegate { wrapper -> |
Andrey Kulikov | 1102527 | 2020-11-18 19:23:35 +0000 | [diff] [blame] | 1230 | wrapper.layer?.invalidate() |
George Mount | 4de8f5a | 2020-10-15 16:09:41 -0700 | [diff] [blame] | 1231 | } |
George Mount | a41ac4c | 2021-01-14 18:57:01 +0000 | [diff] [blame] | 1232 | innerLayoutNodeWrapper.layer?.invalidate() |
George Mount | 4de8f5a | 2020-10-15 16:09:41 -0700 | [diff] [blame] | 1233 | } |
| 1234 | |
| 1235 | /** |
George Mount | a2e46b0 | 2020-06-15 10:33:15 -0700 | [diff] [blame] | 1236 | * Reuses a [DelegatingLayoutNodeWrapper] from [wrapperCache] if one matches the class |
| 1237 | * type of [modifier]. This walks backward through the [wrapperCache] and |
| 1238 | * extracts all [DelegatingLayoutNodeWrapper]s that are |
| 1239 | * [chained][DelegatingLayoutNodeWrapper.isChained] together. |
| 1240 | * If none can be reused, `null` is returned. |
| 1241 | */ |
| 1242 | private fun reuseLayoutNodeWrapper( |
| 1243 | modifier: Modifier.Element, |
| 1244 | wrapper: LayoutNodeWrapper |
| 1245 | ): DelegatingLayoutNodeWrapper<*>? { |
George Mount | dfc9ad7 | 2020-07-07 13:03:02 -0700 | [diff] [blame] | 1246 | if (wrapperCache.isEmpty()) { |
George Mount | a2e46b0 | 2020-06-15 10:33:15 -0700 | [diff] [blame] | 1247 | return null |
| 1248 | } |
George Mount | 4058e84 | 2020-12-07 23:18:21 +0000 | [diff] [blame] | 1249 | // Look for exact match |
Ralston Da Silva | 6d0c432 | 2021-10-19 18:31:23 -0700 | [diff] [blame] | 1250 | var lastIndex = wrapperCache.indexOfLast { |
George Mount | 4058e84 | 2020-12-07 23:18:21 +0000 | [diff] [blame] | 1251 | it.toBeReusedForSameModifier && it.modifier === modifier |
| 1252 | } |
| 1253 | |
Ralston Da Silva | 6d0c432 | 2021-10-19 18:31:23 -0700 | [diff] [blame] | 1254 | if (lastIndex < 0) { |
George Mount | 4058e84 | 2020-12-07 23:18:21 +0000 | [diff] [blame] | 1255 | // Look for class match |
Ralston Da Silva | 6d0c432 | 2021-10-19 18:31:23 -0700 | [diff] [blame] | 1256 | lastIndex = wrapperCache.indexOfLast { |
George Mount | 4058e84 | 2020-12-07 23:18:21 +0000 | [diff] [blame] | 1257 | !it.toBeReusedForSameModifier && it.modifier.nativeClass() == modifier.nativeClass() |
| 1258 | } |
George Mount | dfc9ad7 | 2020-07-07 13:03:02 -0700 | [diff] [blame] | 1259 | } |
George Mount | a2e46b0 | 2020-06-15 10:33:15 -0700 | [diff] [blame] | 1260 | |
Ralston Da Silva | 6d0c432 | 2021-10-19 18:31:23 -0700 | [diff] [blame] | 1261 | if (lastIndex < 0) { |
George Mount | a2e46b0 | 2020-06-15 10:33:15 -0700 | [diff] [blame] | 1262 | return null |
| 1263 | } |
| 1264 | |
Ralston Da Silva | 6d0c432 | 2021-10-19 18:31:23 -0700 | [diff] [blame] | 1265 | val endWrapper = wrapperCache.removeAt(lastIndex--) |
George Mount | a2e46b0 | 2020-06-15 10:33:15 -0700 | [diff] [blame] | 1266 | endWrapper.wrapped = wrapper |
Ralston Da Silva | 6d0c432 | 2021-10-19 18:31:23 -0700 | [diff] [blame] | 1267 | endWrapper.setModifierTo(modifier) |
| 1268 | endWrapper.initialize() |
| 1269 | |
| 1270 | var startWrapper = endWrapper |
| 1271 | while (startWrapper.isChained) { |
| 1272 | startWrapper = wrapperCache.removeAt(lastIndex--) |
| 1273 | startWrapper.setModifierTo(modifier) |
| 1274 | startWrapper.initialize() |
| 1275 | } |
George Mount | a2e46b0 | 2020-06-15 10:33:15 -0700 | [diff] [blame] | 1276 | return startWrapper |
| 1277 | } |
| 1278 | |
George Mount | a2e46b0 | 2020-06-15 10:33:15 -0700 | [diff] [blame] | 1279 | /** |
| 1280 | * Copies all [DelegatingLayoutNodeWrapper]s currently in use and returns them in a new |
| 1281 | * Array. |
| 1282 | */ |
| 1283 | private fun copyWrappersToCache() { |
George Mount | a2e46b0 | 2020-06-15 10:33:15 -0700 | [diff] [blame] | 1284 | forEachDelegate { |
George Mount | dfc9ad7 | 2020-07-07 13:03:02 -0700 | [diff] [blame] | 1285 | wrapperCache += it as DelegatingLayoutNodeWrapper<*> |
George Mount | 6090475 | 2021-11-12 15:57:59 -0800 | [diff] [blame] | 1286 | it.drawEntityHead = null |
George Mount | a2e46b0 | 2020-06-15 10:33:15 -0700 | [diff] [blame] | 1287 | } |
George Mount | 6090475 | 2021-11-12 15:57:59 -0800 | [diff] [blame] | 1288 | innerLayoutNodeWrapper.drawEntityHead = null |
George Mount | a2e46b0 | 2020-06-15 10:33:15 -0700 | [diff] [blame] | 1289 | } |
| 1290 | |
George Mount | 4058e84 | 2020-12-07 23:18:21 +0000 | [diff] [blame] | 1291 | private fun markReusedModifiers(modifier: Modifier) { |
| 1292 | wrapperCache.forEach { |
| 1293 | it.toBeReusedForSameModifier = false |
| 1294 | } |
| 1295 | |
| 1296 | modifier.foldIn(Unit) { _, mod -> |
Leland Richardson | b6c89a5 | 2021-03-24 14:20:56 -0700 | [diff] [blame] | 1297 | var wrapper = wrapperCache.lastOrNull { |
| 1298 | it.modifier === mod && !it.toBeReusedForSameModifier |
| 1299 | } |
| 1300 | // we want to walk up the chain up all LayoutNodeWrappers for the same modifier |
| 1301 | while (wrapper != null) { |
| 1302 | wrapper.toBeReusedForSameModifier = true |
| 1303 | wrapper = if (wrapper.isChained) |
| 1304 | wrapper.wrappedBy as? DelegatingLayoutNodeWrapper<*> |
| 1305 | else |
| 1306 | null |
| 1307 | } |
George Mount | 4058e84 | 2020-12-07 23:18:21 +0000 | [diff] [blame] | 1308 | } |
| 1309 | } |
| 1310 | |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 1311 | // Delegation from Measurable to measurableAndPlaceable |
Anastasia Soboleva | 2833c36 | 2020-07-23 20:19:13 +0100 | [diff] [blame] | 1312 | override fun measure(constraints: Constraints) = |
| 1313 | outerMeasurablePlaceable.measure(constraints) |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 1314 | |
| 1315 | /** |
| 1316 | * Return true if the measured size has been changed |
| 1317 | */ |
| 1318 | internal fun remeasure( |
Andrey Kulikov | a8cd564 | 2021-06-17 18:03:08 +0100 | [diff] [blame] | 1319 | constraints: Constraints? = outerMeasurablePlaceable.lastConstraints |
| 1320 | ): Boolean { |
| 1321 | return if (constraints != null) { |
| 1322 | outerMeasurablePlaceable.remeasure(constraints) |
| 1323 | } else { |
| 1324 | false |
| 1325 | } |
| 1326 | } |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 1327 | |
| 1328 | override val parentData: Any? get() = outerMeasurablePlaceable.parentData |
| 1329 | |
Anastasia Soboleva | 2833c36 | 2020-07-23 20:19:13 +0100 | [diff] [blame] | 1330 | override fun minIntrinsicWidth(height: Int): Int = |
| 1331 | outerMeasurablePlaceable.minIntrinsicWidth(height) |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 1332 | |
Anastasia Soboleva | 2833c36 | 2020-07-23 20:19:13 +0100 | [diff] [blame] | 1333 | override fun maxIntrinsicWidth(height: Int): Int = |
| 1334 | outerMeasurablePlaceable.maxIntrinsicWidth(height) |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 1335 | |
Anastasia Soboleva | 2833c36 | 2020-07-23 20:19:13 +0100 | [diff] [blame] | 1336 | override fun minIntrinsicHeight(width: Int): Int = |
| 1337 | outerMeasurablePlaceable.minIntrinsicHeight(width) |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 1338 | |
Anastasia Soboleva | 2833c36 | 2020-07-23 20:19:13 +0100 | [diff] [blame] | 1339 | override fun maxIntrinsicHeight(width: Int): Int = |
| 1340 | outerMeasurablePlaceable.maxIntrinsicHeight(width) |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 1341 | |
Andrey Kulikov | 839b35c | 2020-07-08 18:55:13 +0100 | [diff] [blame] | 1342 | override fun forceRemeasure() { |
| 1343 | requestRemeasure() |
| 1344 | owner?.measureAndLayout() |
| 1345 | } |
| 1346 | |
George Mount | a2e46b0 | 2020-06-15 10:33:15 -0700 | [diff] [blame] | 1347 | /** |
| 1348 | * Calls [block] on all [DelegatingLayoutNodeWrapper]s in the LayoutNodeWrapper chain. |
| 1349 | */ |
| 1350 | private inline fun forEachDelegate(block: (LayoutNodeWrapper) -> Unit) { |
| 1351 | var delegate = outerLayoutNodeWrapper |
| 1352 | val inner = innerLayoutNodeWrapper |
| 1353 | while (delegate != inner) { |
| 1354 | block(delegate) |
| 1355 | delegate = delegate.wrapped!! |
| 1356 | } |
| 1357 | } |
| 1358 | |
Andrey Kulikov | 1102527 | 2020-11-18 19:23:35 +0000 | [diff] [blame] | 1359 | /** |
| 1360 | * Calls [block] on all [DelegatingLayoutNodeWrapper]s in the LayoutNodeWrapper chain. |
| 1361 | */ |
| 1362 | private inline fun forEachDelegateIncludingInner(block: (LayoutNodeWrapper) -> Unit) { |
| 1363 | var delegate: LayoutNodeWrapper? = outerLayoutNodeWrapper |
| 1364 | val final = innerLayoutNodeWrapper.wrapped |
| 1365 | while (delegate != final && delegate != null) { |
| 1366 | block(delegate) |
| 1367 | delegate = delegate.wrapped |
| 1368 | } |
| 1369 | } |
| 1370 | |
George Mount | af94176 | 2020-07-06 17:42:34 -0700 | [diff] [blame] | 1371 | private fun shouldInvalidateParentLayer(): Boolean { |
Andrey Kulikov | 1102527 | 2020-11-18 19:23:35 +0000 | [diff] [blame] | 1372 | forEachDelegateIncludingInner { |
Andrey Kulikov | 9e6f525 | 2020-12-04 13:08:38 +0000 | [diff] [blame] | 1373 | if (it.layer != null) { |
George Mount | af94176 | 2020-07-06 17:42:34 -0700 | [diff] [blame] | 1374 | return false |
George Mount | 6090475 | 2021-11-12 15:57:59 -0800 | [diff] [blame] | 1375 | } else if (it.drawEntityHead != null) { |
Andrey Kulikov | 9e6f525 | 2020-12-04 13:08:38 +0000 | [diff] [blame] | 1376 | return true |
George Mount | af94176 | 2020-07-06 17:42:34 -0700 | [diff] [blame] | 1377 | } |
| 1378 | } |
Andrey Kulikov | 9e6f525 | 2020-12-04 13:08:38 +0000 | [diff] [blame] | 1379 | return true |
George Mount | af94176 | 2020-07-06 17:42:34 -0700 | [diff] [blame] | 1380 | } |
| 1381 | |
Andrey Kulikov | fc91b88 | 2020-09-24 13:31:29 +0100 | [diff] [blame] | 1382 | /** |
| 1383 | * Comparator allowing to sort nodes by zIndex and placement order. |
| 1384 | */ |
| 1385 | private val ZComparator = Comparator<LayoutNode> { node1, node2 -> |
| 1386 | if (node1.zIndex == node2.zIndex) { |
| 1387 | // if zIndex is the same we use the placement order |
| 1388 | node1.placeOrder.compareTo(node2.placeOrder) |
| 1389 | } else { |
| 1390 | node1.zIndex.compareTo(node2.zIndex) |
| 1391 | } |
| 1392 | } |
| 1393 | |
Andrey Kulikov | 5fb82da | 2020-12-03 15:24:33 +0000 | [diff] [blame] | 1394 | override val parentInfo: LayoutInfo? |
| 1395 | get() = parent |
| 1396 | |
George Mount | 31c3edc | 2019-08-14 15:30:26 -0700 | [diff] [blame] | 1397 | internal companion object { |
Mihai Popa | 4958475 | 2021-01-25 12:38:07 +0000 | [diff] [blame] | 1398 | private val ErrorMeasurePolicy: NoIntrinsicsMeasurePolicy = |
| 1399 | object : NoIntrinsicsMeasurePolicy( |
Nikolay Igotti | 438fdbf | 2020-06-25 12:05:58 +0300 | [diff] [blame] | 1400 | error = "Undefined intrinsics block and it is required" |
| 1401 | ) { |
Mihai Popa | 4958475 | 2021-01-25 12:38:07 +0000 | [diff] [blame] | 1402 | override fun MeasureScope.measure( |
Nikolay Igotti | 438fdbf | 2020-06-25 12:05:58 +0300 | [diff] [blame] | 1403 | measurables: List<Measurable>, |
Anastasia Soboleva | 2833c36 | 2020-07-23 20:19:13 +0100 | [diff] [blame] | 1404 | constraints: Constraints |
Nikolay Igotti | 438fdbf | 2020-06-25 12:05:58 +0300 | [diff] [blame] | 1405 | ) = error("Undefined measure and it is required") |
Ralston Da Silva | ca1c1f85 | 2020-07-20 13:29:18 -0700 | [diff] [blame] | 1406 | } |
Andrey Kulikov | fc91b88 | 2020-09-24 13:31:29 +0100 | [diff] [blame] | 1407 | |
| 1408 | /** |
| 1409 | * Constant used by [placeOrder]. |
| 1410 | */ |
Andrey Kulikov | a3b5661 | 2021-05-20 20:57:53 +0100 | [diff] [blame] | 1411 | internal const val NotPlacedPlaceOrder = Int.MAX_VALUE |
Andrey Kulikov | 057998a | 2021-01-28 18:02:50 +0000 | [diff] [blame] | 1412 | |
| 1413 | /** |
| 1414 | * Pre-allocated constructor to be used with ComposeNode |
| 1415 | */ |
| 1416 | internal val Constructor: () -> LayoutNode = { LayoutNode() } |
George Mount | d1bb6b8 | 2021-07-23 22:52:24 +0000 | [diff] [blame] | 1417 | |
| 1418 | /** |
| 1419 | * All of these values are only used in tests. The real ViewConfiguration should |
| 1420 | * be set in Layout() |
| 1421 | */ |
| 1422 | internal val DummyViewConfiguration = object : ViewConfiguration { |
| 1423 | override val longPressTimeoutMillis: Long |
| 1424 | get() = 400L |
| 1425 | override val doubleTapTimeoutMillis: Long |
| 1426 | get() = 300L |
| 1427 | override val doubleTapMinTimeMillis: Long |
| 1428 | get() = 40L |
| 1429 | override val touchSlop: Float |
| 1430 | get() = 16f |
| 1431 | override val minimumTouchTargetSize: DpSize |
| 1432 | get() = DpSize.Zero |
| 1433 | } |
George Mount | 31c3edc | 2019-08-14 15:30:26 -0700 | [diff] [blame] | 1434 | } |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 1435 | |
| 1436 | /** |
| 1437 | * Describes the current state the [LayoutNode] is in. |
| 1438 | */ |
Andrey Kulikov | 2ca0701 | 2020-07-13 16:14:03 +0100 | [diff] [blame] | 1439 | internal enum class LayoutState { |
Andrey Kulikov | b33a380 | 2020-05-21 20:02:53 +0100 | [diff] [blame] | 1440 | /** |
| 1441 | * Request remeasure was called on the node. |
| 1442 | */ |
| 1443 | NeedsRemeasure, |
| 1444 | /** |
| 1445 | * Node is currently being measured. |
| 1446 | */ |
| 1447 | Measuring, |
| 1448 | /** |
| 1449 | * Request relayout was called on the node or the node was just measured and is going to |
| 1450 | * layout soon (measure stage is always being followed by the layout stage). |
| 1451 | */ |
| 1452 | NeedsRelayout, |
| 1453 | /** |
| 1454 | * Node is currently being laid out. |
| 1455 | */ |
| 1456 | LayingOut, |
| 1457 | /** |
| 1458 | * Node is measured and laid out or not yet attached to the [Owner] (see [LayoutNode.owner]). |
| 1459 | */ |
| 1460 | Ready |
| 1461 | } |
| 1462 | |
| 1463 | internal enum class UsageByParent { |
| 1464 | InMeasureBlock, |
| 1465 | InLayoutBlock, |
| 1466 | NotUsed, |
| 1467 | } |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 1468 | } |
| 1469 | |
| 1470 | /** |
George Mount | 17d6fe5 | 2020-05-15 08:08:23 -0700 | [diff] [blame] | 1471 | * Returns [LayoutNode.owner] or throws if it is null. |
Andrey Kulikov | 8d7bb4a | 2019-05-13 17:53:55 +0100 | [diff] [blame] | 1472 | */ |
George Mount | e11880a | 2020-07-17 15:28:22 -0700 | [diff] [blame] | 1473 | internal fun LayoutNode.requireOwner(): Owner { |
| 1474 | val owner = owner |
| 1475 | checkNotNull(owner) { |
| 1476 | "LayoutNode should be attached to an owner" |
| 1477 | } |
| 1478 | return owner |
| 1479 | } |
Andrey Kulikov | 8d7bb4a | 2019-05-13 17:53:55 +0100 | [diff] [blame] | 1480 | |
| 1481 | /** |
Andrey Kulikov | 5fb82da | 2020-12-03 15:24:33 +0000 | [diff] [blame] | 1482 | * Inserts a child [LayoutNode] at a last index. If this LayoutNode [LayoutNode.isAttached] |
| 1483 | * then [child] will become [LayoutNode.isAttached] also. [child] must have a `null` |
| 1484 | * [LayoutNode.parent]. |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 1485 | */ |
George Mount | 17d6fe5 | 2020-05-15 08:08:23 -0700 | [diff] [blame] | 1486 | internal fun LayoutNode.add(child: LayoutNode) { |
| 1487 | insertAt(children.size, child) |
George Mount | 652bd5d | 2018-10-25 15:45:02 -0700 | [diff] [blame] | 1488 | } |
| 1489 | |
Andrey Kulikov | 3d6c1be | 2019-01-14 15:49:07 +0000 | [diff] [blame] | 1490 | /** |
Ralston Da Silva | 228085e | 2020-08-12 03:34:16 -0700 | [diff] [blame] | 1491 | * Sets [DelegatingLayoutNodeWrapper#isChained] to `true` of the [wrapped][this.wrapped] when it |
George Mount | a2e46b0 | 2020-06-15 10:33:15 -0700 | [diff] [blame] | 1492 | * is part of a chain of LayoutNodes for the same modifier. |
| 1493 | * |
| 1494 | * @param originalWrapper The LayoutNodeWrapper that the modifier chain should be wrapping. |
| 1495 | */ |
| 1496 | @Suppress("NOTHING_TO_INLINE") |
| 1497 | private inline fun <T : DelegatingLayoutNodeWrapper<*>> T.assignChained( |
| 1498 | originalWrapper: LayoutNodeWrapper |
| 1499 | ): T { |
| 1500 | if (originalWrapper !== wrapped) { |
Ralston Da Silva | 228085e | 2020-08-12 03:34:16 -0700 | [diff] [blame] | 1501 | val wrapper = wrapped as DelegatingLayoutNodeWrapper<*> |
George Mount | a2e46b0 | 2020-06-15 10:33:15 -0700 | [diff] [blame] | 1502 | wrapper.isChained = true |
| 1503 | } |
| 1504 | return this |
George Mount | d1bb6b8 | 2021-07-23 22:52:24 +0000 | [diff] [blame] | 1505 | } |
Ralston Da Silva | 6d0c432 | 2021-10-19 18:31:23 -0700 | [diff] [blame] | 1506 | |
| 1507 | @Suppress("NOTHING_TO_INLINE") |
| 1508 | private inline fun <T : DelegatingLayoutNodeWrapper<*>> T.initialize(): T { |
| 1509 | onInitialize() |
| 1510 | return this |
| 1511 | } |