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