1 /*
2  * Copyright (C) 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 com.android.compose.animation.scene
18 
19 import com.android.compose.animation.scene.content.state.TransitionState
20 import com.android.compose.animation.scene.transformation.SharedElementTransformation
21 import com.android.compose.animation.scene.transformation.TransformationWithRange
22 
23 /**
24  * Whether this element should be rendered by the given [content]. This method returns true only for
25  * exactly one content at any given time.
26  */
shouldBeRenderedBynull27 internal fun Element.shouldBeRenderedBy(content: ContentKey): Boolean {
28     // The current strategy is that always the content with the lowest nestingDepth has authority.
29     // This content is supposed to render the shared element because this is also the level at which
30     // the transition is running. If the [renderAuthority.size] is 1 it means that that this element
31     // is currently composed only in one nesting level, which means that the render authority
32     // is determined by "classic" shared element code.
33     return renderAuthority.size == 1 || renderAuthority.first() == content
34 }
35 
36 /**
37  * Whether this element is currently composed in multiple [SceneTransitionLayout]s.
38  *
39  * Note: Shared elements across [NestedSceneTransitionLayout]s side-by-side are not supported.
40  */
isPresentInMultipleStlsnull41 internal fun Element.isPresentInMultipleStls(): Boolean {
42     return renderAuthority.size > 1
43 }
44 
shouldPlaceSharedElementnull45 internal fun shouldPlaceSharedElement(
46     layoutImpl: SceneTransitionLayoutImpl,
47     content: ContentKey,
48     elementKey: ElementKey,
49     transition: TransitionState.Transition,
50 ): Boolean {
51     val element = layoutImpl.elements.getValue(elementKey)
52     if (element.isPresentInMultipleStls()) {
53         // If the element is present in multiple STLs we require the highest STL to render it and
54         // we don't want contentPicker to potentially return false for the highest STL.
55         return element.shouldBeRenderedBy(content)
56     }
57 
58     val overscrollContent = transition.currentOverscrollSpec?.content
59     if (overscrollContent != null) {
60         return when (transition) {
61             // If we are overscrolling between scenes, only place/compose the element in the
62             // overscrolling scene.
63             is TransitionState.Transition.ChangeScene -> content == overscrollContent
64 
65             // If we are overscrolling an overlay, place/compose the element if [content] is the
66             // overscrolling content or if [content] is the current scene and the overscrolling
67             // overlay does not contain the element.
68             is TransitionState.Transition.ReplaceOverlay,
69             is TransitionState.Transition.ShowOrHideOverlay ->
70                 content == overscrollContent ||
71                     (content == transition.currentScene &&
72                         overscrollContent !in element.stateByContent)
73         }
74     }
75 
76     val scenePicker = elementKey.contentPicker
77     val pickedScene =
78         scenePicker.contentDuringTransition(
79             element = elementKey,
80             transition = transition,
81             fromContentZIndex = layoutImpl.content(transition.fromContent).zIndex,
82             toContentZIndex = layoutImpl.content(transition.toContent).zIndex,
83         )
84 
85     return pickedScene == content
86 }
87 
isSharedElementEnablednull88 internal fun isSharedElementEnabled(
89     element: ElementKey,
90     transition: TransitionState.Transition,
91 ): Boolean {
92     return sharedElementTransformation(element, transition)?.transformation?.enabled ?: true
93 }
94 
sharedElementTransformationnull95 internal fun sharedElementTransformation(
96     element: ElementKey,
97     transition: TransitionState.Transition,
98 ): TransformationWithRange<SharedElementTransformation>? {
99     val transformationSpec = transition.transformationSpec
100     val sharedInFromContent =
101         transformationSpec.transformations(element, transition.fromContent).shared
102     val sharedInToContent = transformationSpec.transformations(element, transition.toContent).shared
103 
104     // The sharedElement() transformation must either be null or be the same in both contents.
105     if (sharedInFromContent != sharedInToContent) {
106         error(
107             "Different sharedElement() transformations matched $element " +
108                 "(from=$sharedInFromContent to=$sharedInToContent)"
109         )
110     }
111 
112     return sharedInFromContent
113 }
114