• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2025 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.systemui.scene.ui.viewmodel
18 
19 import androidx.compose.foundation.gestures.Orientation
20 import androidx.compose.ui.unit.Density
21 import androidx.compose.ui.unit.Dp
22 import androidx.compose.ui.unit.IntOffset
23 import androidx.compose.ui.unit.IntSize
24 import androidx.compose.ui.unit.LayoutDirection
25 import com.android.compose.animation.scene.Edge
26 import com.android.compose.animation.scene.FixedSizeEdgeDetector
27 import com.android.compose.animation.scene.SwipeSource
28 import com.android.compose.animation.scene.SwipeSourceDetector
29 
30 /** Identifies an area of the [SceneContainer] to detect swipe gestures on. */
31 sealed class SceneContainerArea(private val resolveArea: (LayoutDirection) -> Resolved) :
32     SwipeSource {
33     data object StartEdge :
34         SceneContainerArea(
<lambda>null35             resolveArea = {
36                 if (it == LayoutDirection.Ltr) Resolved.LeftEdge else Resolved.RightEdge
37             }
38         )
39 
40     data object StartHalf :
41         SceneContainerArea(
<lambda>null42             resolveArea = {
43                 if (it == LayoutDirection.Ltr) Resolved.LeftHalf else Resolved.RightHalf
44             }
45         )
46 
47     data object EndEdge :
48         SceneContainerArea(
<lambda>null49             resolveArea = {
50                 if (it == LayoutDirection.Ltr) Resolved.RightEdge else Resolved.LeftEdge
51             }
52         )
53 
54     data object EndHalf :
55         SceneContainerArea(
<lambda>null56             resolveArea = {
57                 if (it == LayoutDirection.Ltr) Resolved.RightHalf else Resolved.LeftHalf
58             }
59         )
60 
61     data object TopEdgeStartHalf :
62         SceneContainerArea(
<lambda>null63             resolveArea = {
64                 if (it == LayoutDirection.Ltr) Resolved.TopEdgeLeftHalf
65                 else Resolved.TopEdgeRightHalf
66             }
67         )
68 
69     data object TopEdgeEndHalf :
70         SceneContainerArea(
<lambda>null71             resolveArea = {
72                 if (it == LayoutDirection.Ltr) Resolved.TopEdgeRightHalf
73                 else Resolved.TopEdgeLeftHalf
74             }
75         )
76 
resolvenull77     override fun resolve(layoutDirection: LayoutDirection): Resolved {
78         return resolveArea(layoutDirection)
79     }
80 
81     sealed interface Resolved : SwipeSource.Resolved {
82         data object LeftEdge : Resolved
83 
84         data object LeftHalf : Resolved
85 
86         data object BottomEdge : Resolved
87 
88         data object RightEdge : Resolved
89 
90         data object RightHalf : Resolved
91 
92         /** The left half of the top edge of the display. */
93         data object TopEdgeLeftHalf : Resolved
94 
95         /** The right half of the top edge of the display. */
96         data object TopEdgeRightHalf : Resolved
97     }
98 }
99 
100 /**
101  * A [SwipeSourceDetector] that detects edges similarly to [FixedSizeEdgeDetector], but additionally
102  * detects the left and right halves of the screen (besides the edges).
103  *
104  * Corner cases (literally): A vertical swipe on the top-left corner of the screen will be resolved
105  * to [SceneContainerArea.Resolved.LeftHalf], whereas a horizontal swipe in the same position will
106  * be resolved to [SceneContainerArea.Resolved.LeftEdge]. The behavior is similar on the top-right
107  * corner of the screen.
108  *
109  * Callers who need to detect the start and end edges based on the layout direction (LTR vs RTL)
110  * should subscribe to [SceneContainerArea.StartEdge] and [SceneContainerArea.EndEdge] instead.
111  * These will be resolved at runtime to [SceneContainerArea.Resolved.LeftEdge] and
112  * [SceneContainerArea.Resolved.RightEdge] appropriately. Similarly, [SceneContainerArea.StartHalf]
113  * and [SceneContainerArea.EndHalf] will be resolved appropriately to
114  * [SceneContainerArea.Resolved.LeftHalf] and [SceneContainerArea.Resolved.RightHalf].
115  *
116  * @param edgeSize The fixed size of each edge.
117  */
118 class SceneContainerSwipeDetector(val edgeSize: Dp) : SwipeSourceDetector {
119 
120     private val fixedEdgeDetector = FixedSizeEdgeDetector(edgeSize)
121 
sourcenull122     override fun source(
123         layoutSize: IntSize,
124         position: IntOffset,
125         density: Density,
126         orientation: Orientation,
127     ): SceneContainerArea.Resolved {
128         val fixedEdge = fixedEdgeDetector.source(layoutSize, position, density, orientation)
129         return when (fixedEdge) {
130             Edge.Resolved.Left -> SceneContainerArea.Resolved.LeftEdge
131             Edge.Resolved.Bottom -> SceneContainerArea.Resolved.BottomEdge
132             Edge.Resolved.Right -> SceneContainerArea.Resolved.RightEdge
133             Edge.Resolved.Top -> {
134                 if (position.x < layoutSize.width * 0.5f) {
135                     SceneContainerArea.Resolved.TopEdgeLeftHalf
136                 } else {
137                     SceneContainerArea.Resolved.TopEdgeRightHalf
138                 }
139             }
140             null -> {
141                 if (position.x < layoutSize.width * 0.5f) {
142                     SceneContainerArea.Resolved.LeftHalf
143                 } else {
144                     SceneContainerArea.Resolved.RightHalf
145                 }
146             }
147         }
148     }
149 }
150