1 /*
<lambda>null2  * 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.material3.adaptive.layout
18 
19 import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
20 import androidx.compose.runtime.Composable
21 import androidx.compose.runtime.Immutable
22 import androidx.compose.runtime.remember
23 import androidx.compose.ui.unit.IntSize
24 
25 /**
26  * The class that provides motion settings for three pane scaffolds like [ListDetailPaneScaffold]
27  * and [SupportingPaneScaffold].
28  */
29 @ExperimentalMaterial3AdaptiveApi
30 @Immutable
31 class ThreePaneMotion
32 internal constructor(
33     private val primaryPaneMotion: PaneMotion,
34     private val secondaryPaneMotion: PaneMotion,
35     private val tertiaryPaneMotion: PaneMotion,
36 ) {
37     /**
38      * Gets the specified [PaneMotion] of a given pane role.
39      *
40      * @param role the specified role of the pane, see [ListDetailPaneScaffoldRole] and
41      *   [SupportingPaneScaffoldRole].
42      */
43     operator fun get(role: ThreePaneScaffoldRole): PaneMotion =
44         when (role) {
45             ThreePaneScaffoldRole.Primary -> primaryPaneMotion
46             ThreePaneScaffoldRole.Secondary -> secondaryPaneMotion
47             ThreePaneScaffoldRole.Tertiary -> tertiaryPaneMotion
48         }
49 
50     override fun equals(other: Any?): Boolean {
51         if (this === other) return true
52         if (other !is ThreePaneMotion) return false
53         if (primaryPaneMotion != other.primaryPaneMotion) return false
54         if (secondaryPaneMotion != other.secondaryPaneMotion) return false
55         if (tertiaryPaneMotion != other.tertiaryPaneMotion) return false
56         return true
57     }
58 
59     override fun hashCode(): Int {
60         var result = primaryPaneMotion.hashCode()
61         result = 31 * result + secondaryPaneMotion.hashCode()
62         result = 31 * result + tertiaryPaneMotion.hashCode()
63         return result
64     }
65 
66     override fun toString(): String {
67         return "ThreePaneMotion(" +
68             "primaryPaneMotion=$primaryPaneMotion, " +
69             "secondaryPaneMotion=$secondaryPaneMotion, " +
70             "tertiaryPaneMotion=$tertiaryPaneMotion)"
71     }
72 
73     companion object {
74         /** A default [ThreePaneMotion] instance that specifies no motions. */
75         val NoMotion =
76             ThreePaneMotion(PaneMotion.NoMotion, PaneMotion.NoMotion, PaneMotion.NoMotion)
77     }
78 }
79 
80 @ExperimentalMaterial3AdaptiveApi
81 @Composable
calculateThreePaneMotionnull82 internal fun ThreePaneScaffoldState.calculateThreePaneMotion(
83     ltrPaneOrder: ThreePaneScaffoldHorizontalOrder
84 ): ThreePaneMotion {
85     class ThreePaneMotionHolder(var value: ThreePaneMotion)
86 
87     val resultHolder = remember { ThreePaneMotionHolder(ThreePaneMotion.NoMotion) }
88     if (currentState != targetState) {
89         // Only update motions when the state changes to prevent unnecessary recomposition at the
90         // end of state transitions.
91         val paneMotions = calculatePaneMotion(currentState, targetState, ltrPaneOrder)
92         resultHolder.value =
93             ThreePaneMotion(
94                 paneMotions[ltrPaneOrder.indexOf(ThreePaneScaffoldRole.Primary)],
95                 paneMotions[ltrPaneOrder.indexOf(ThreePaneScaffoldRole.Secondary)],
96                 paneMotions[ltrPaneOrder.indexOf(ThreePaneScaffoldRole.Tertiary)]
97             )
98     }
99     return resultHolder.value
100 }
101 
102 @OptIn(ExperimentalMaterial3AdaptiveApi::class)
103 @Suppress("PrimitiveInCollection") // No way to get underlying Long of IntSize or IntOffset
104 internal class ThreePaneScaffoldMotionDataProvider :
105     PaneScaffoldMotionDataProvider<ThreePaneScaffoldRole> {
106     private lateinit var ltrOrder: ThreePaneScaffoldHorizontalOrder
107 
108     private val primary = PaneMotionData()
109     private val secondary = PaneMotionData()
110     private val tertiary = PaneMotionData()
111 
112     override var scaffoldSize: IntSize = IntSize.Zero
113 
114     override val count: Int = 3
115 
getRoleAtnull116     override fun getRoleAt(index: Int): ThreePaneScaffoldRole = ltrOrder[index]
117 
118     override fun get(role: ThreePaneScaffoldRole): PaneMotionData =
119         when (role) {
120             ThreePaneScaffoldRole.Primary -> primary
121             ThreePaneScaffoldRole.Secondary -> secondary
122             ThreePaneScaffoldRole.Tertiary -> tertiary
123         }
124 
getnull125     override fun get(index: Int): PaneMotionData = get(getRoleAt(index))
126 
127     internal fun update(
128         threePaneMotion: ThreePaneMotion,
129         ltrOrder: ThreePaneScaffoldHorizontalOrder
130     ) {
131         this.ltrOrder = ltrOrder
132         forEach { role, it ->
133             it.motion = threePaneMotion[role]
134             it.isOriginSizeAndPositionSet = false
135         }
136     }
137 }
138