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.relocation 18 19 import androidx.compose.ui.geometry.Rect 20 import androidx.compose.ui.geometry.toRect 21 import androidx.compose.ui.layout.LayoutCoordinates 22 import androidx.compose.ui.node.DelegatableNode 23 import androidx.compose.ui.node.Nodes 24 import androidx.compose.ui.node.nearestAncestor 25 import androidx.compose.ui.node.requireLayoutCoordinates 26 import androidx.compose.ui.unit.toSize 27 28 /** 29 * A node that can respond to [bringIntoView] requests from its children by moving or adjusting its 30 * content. 31 */ 32 interface BringIntoViewModifierNode : DelegatableNode { 33 /** 34 * Moves or adjusts this node's content so that [boundsProvider] will be in visible bounds. Must 35 * ensure that the request is propagated up to the parent node. 36 * 37 * This method will not return until this request has been satisfied or interrupted by a newer 38 * request. 39 * 40 * @param childCoordinates The [LayoutCoordinates] of the child node making the request. This 41 * parent can use these [LayoutCoordinates] to translate [boundsProvider] into its own 42 * coordinates. 43 * @param boundsProvider A function returning the rectangle to bring into view, relative to 44 * [childCoordinates]. The function may return a different value over time, if the bounds of 45 * the request change while the request is being processed. If the rectangle cannot be 46 * calculated, e.g. because [childCoordinates] is not attached, return null. 47 */ bringIntoViewnull48 suspend fun bringIntoView(childCoordinates: LayoutCoordinates, boundsProvider: () -> Rect?) 49 } 50 51 /** 52 * Bring this node into visible bounds. Does nothing if the node is not attached. 53 * 54 * This method will not return until this request is satisfied or a newer request interrupts it. If 55 * this call is interrupted by a newer call, this method will throw a 56 * [CancellationException][kotlinx.coroutines.CancellationException]. 57 * 58 * @param bounds provides the bounds (In local coordinates) that should be brought into view. The 59 * function may return a different value over time, if the bounds of the request change while the 60 * request is being processed. If you don't provide bounds, the whole node bounds will be used. 61 */ 62 suspend fun DelegatableNode.bringIntoView(bounds: (() -> Rect?)? = null) { 63 if (!node.isAttached) return 64 val parent = nearestAncestor(Nodes.BringIntoView) ?: return 65 val layoutCoordinates = requireLayoutCoordinates() 66 67 parent.bringIntoView(layoutCoordinates) { 68 // If the rect is not specified, use a rectangle representing the entire composable. 69 // If the coordinates are detached when this call is made, we don't bother even 70 // submitting the request, but if the coordinates become detached while the request 71 // is being handled we just return a null Rect. 72 bounds?.invoke() ?: layoutCoordinates.takeIf { it.isAttached }?.size?.toSize()?.toRect() 73 } 74 } 75