1 /* 2 * Copyright 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.compose.ui.semantics 18 19 import androidx.collection.MutableObjectList 20 import androidx.compose.ui.layout.LayoutInfo 21 22 /** 23 * This is an internal interface that can be used by [SemanticsListener]s to read semantic 24 * information from layout nodes. The root [SemanticsInfo] can be accessed using 25 * [SemanticsOwner.rootInfo], and particular [SemanticsInfo] can be looked up by their [semanticsId] 26 * by using [SemanticsOwner.get]. 27 */ 28 internal interface SemanticsInfo : LayoutInfo { 29 /** The semantics configuration (Semantic properties and actions) associated with this node. */ 30 val semanticsConfiguration: SemanticsConfiguration? 31 32 /** Whether the node is transparent. */ isTransparentnull33 fun isTransparent(): Boolean 34 35 /** 36 * The [SemanticsInfo] of the parent. 37 * 38 * This includes parents that do not have any semantics modifiers. 39 */ 40 override val parentInfo: SemanticsInfo? 41 42 /** 43 * Returns the list of children. 44 * 45 * Please note that this list contains not placed items as well, so you have to manually filter 46 * them. Note that the object is reused so you shouldn't save it for later. 47 */ 48 val childrenInfo: List<SemanticsInfo> 49 } 50 51 /** The semantics parent (nearest ancestor which has semantic properties). */ 52 internal fun SemanticsInfo.nearestParentThatHasSemantics(): SemanticsInfo? { 53 var parent = parentInfo 54 while (parent != null) { 55 if (parent.semanticsConfiguration != null) return parent 56 parent = parent.parentInfo 57 } 58 return null 59 } 60 61 /** The nearest semantics ancestor that is merging descendants. */ findMergingSemanticsParentnull62internal fun SemanticsInfo.findMergingSemanticsParent(): SemanticsInfo? { 63 var parent = parentInfo 64 while (parent != null) { 65 if (parent.semanticsConfiguration?.isMergingSemanticsOfDescendants == true) return parent 66 parent = parent.parentInfo 67 } 68 return null 69 } 70 71 /** Merges the semantics of all the children of this node into a single SemanticsConfiguration. */ mergedSemanticsConfigurationnull72internal fun SemanticsInfo.mergedSemanticsConfiguration(): SemanticsConfiguration? { 73 val unMergedConfig = semanticsConfiguration 74 if ( 75 unMergedConfig == null || 76 !unMergedConfig.isMergingSemanticsOfDescendants || 77 unMergedConfig.isClearingSemantics 78 ) { 79 return unMergedConfig 80 } 81 82 var mergedConfig: SemanticsConfiguration = unMergedConfig.copy() 83 val needsMerging: MutableObjectList<SemanticsInfo> = 84 MutableObjectList<SemanticsInfo>(childrenInfo.size).apply { addAll(childrenInfo) } 85 86 @Suppress("Range") // isNotEmpty ensures removeAt is not called with -1. 87 while (needsMerging.isNotEmpty()) { 88 val childInfo = needsMerging.removeAt(needsMerging.lastIndex) 89 val childConfig = childInfo.semanticsConfiguration 90 91 // Don't merge children that themselves merge all their descendants (because that 92 // indicates they are independently screen-reader-focusable). 93 if (childConfig == null || childConfig.isMergingSemanticsOfDescendants) continue 94 95 // Merge child values. 96 mergedConfig.mergeChild(childConfig) 97 98 // Merge children (unless this child is clearing semantics). 99 if (!childConfig.isClearingSemantics) needsMerging.addAll(childInfo.childrenInfo) 100 } 101 102 return mergedConfig 103 } 104