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.material3.benchmark
18 
19 import androidx.compose.foundation.layout.Spacer
20 import androidx.compose.foundation.layout.size
21 import androidx.compose.material.icons.Icons
22 import androidx.compose.material.icons.outlined.Edit
23 import androidx.compose.material.icons.outlined.KeyboardArrowDown
24 import androidx.compose.material3.ButtonDefaults
25 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
26 import androidx.compose.material3.Icon
27 import androidx.compose.material3.MaterialTheme
28 import androidx.compose.material3.SplitButtonDefaults
29 import androidx.compose.material3.SplitButtonLayout
30 import androidx.compose.material3.Text
31 import androidx.compose.runtime.Composable
32 import androidx.compose.runtime.mutableStateOf
33 import androidx.compose.testutils.LayeredComposeTestCase
34 import androidx.compose.testutils.ToggleableTestCase
35 import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
36 import androidx.compose.testutils.benchmark.benchmarkFirstCompose
37 import androidx.compose.testutils.benchmark.benchmarkFirstDraw
38 import androidx.compose.testutils.benchmark.benchmarkFirstLayout
39 import androidx.compose.testutils.benchmark.benchmarkFirstMeasure
40 import androidx.compose.testutils.benchmark.benchmarkToFirstPixel
41 import androidx.compose.ui.Modifier
42 import androidx.test.filters.LargeTest
43 import org.junit.Ignore
44 import org.junit.Rule
45 import org.junit.Test
46 import org.junit.runner.RunWith
47 import org.junit.runners.Parameterized
48 
49 @LargeTest
50 @RunWith(Parameterized::class)
51 class SplitButtonBenchmark(private val type: SplitButtonType) {
52     companion object {
53         @Parameterized.Parameters(name = "{0}")
54         @JvmStatic
parametersnull55         fun parameters() = SplitButtonType.values()
56     }
57 
58     @get:Rule val benchmarkRule = ComposeBenchmarkRule()
59 
60     private val splitButtonTestCaseFactory = { SplitButtonTestCase(type) }
61 
62     @Ignore
63     @Test
splitButton_first_composenull64     fun splitButton_first_compose() {
65         benchmarkRule.benchmarkFirstCompose(splitButtonTestCaseFactory)
66     }
67 
68     @Ignore
69     @Test
splitButton_measurenull70     fun splitButton_measure() {
71         benchmarkRule.benchmarkFirstMeasure(splitButtonTestCaseFactory)
72     }
73 
74     @Ignore
75     @Test
splitButton_layoutnull76     fun splitButton_layout() {
77         benchmarkRule.benchmarkFirstLayout(splitButtonTestCaseFactory)
78     }
79 
80     @Ignore
81     @Test
splitButton_drawnull82     fun splitButton_draw() {
83         benchmarkRule.benchmarkFirstDraw(splitButtonTestCaseFactory)
84     }
85 
86     @Test
splitButton_firstPixelnull87     fun splitButton_firstPixel() {
88         benchmarkRule.benchmarkToFirstPixel(splitButtonTestCaseFactory)
89     }
90 }
91 
92 @OptIn(ExperimentalMaterial3ExpressiveApi::class)
93 internal class SplitButtonTestCase(private val type: SplitButtonType) :
94     LayeredComposeTestCase(), ToggleableTestCase {
95     private var trailingButtonChecked = mutableStateOf(false)
96 
97     @Composable
MeasuredContentnull98     override fun MeasuredContent() {
99         when (type) {
100             SplitButtonType.Filled ->
101                 SplitButtonLayout(
102                     leadingButton = {
103                         SplitButtonDefaults.LeadingButton(
104                             onClick = { /* Do Nothing */ },
105                         ) {
106                             leadingContent()
107                         }
108                     },
109                     trailingButton = {
110                         SplitButtonDefaults.TrailingButton(
111                             checked = trailingButtonChecked.value,
112                             onCheckedChange = { /* Do Nothing */ },
113                         ) {
114                             trailingContent()
115                         }
116                     }
117                 )
118             SplitButtonType.Tonal ->
119                 SplitButtonLayout(
120                     leadingButton = {
121                         SplitButtonDefaults.TonalLeadingButton(
122                             onClick = { /* Do Nothing */ },
123                         ) {
124                             leadingContent()
125                         }
126                     },
127                     trailingButton = {
128                         SplitButtonDefaults.TonalTrailingButton(
129                             checked = trailingButtonChecked.value,
130                             onCheckedChange = { /* Do Nothing */ },
131                         ) {
132                             trailingContent()
133                         }
134                     }
135                 )
136             SplitButtonType.Elevated ->
137                 SplitButtonLayout(
138                     leadingButton = {
139                         SplitButtonDefaults.ElevatedLeadingButton(
140                             onClick = { /* Do Nothing */ },
141                         ) {
142                             leadingContent()
143                         }
144                     },
145                     trailingButton = {
146                         SplitButtonDefaults.ElevatedTrailingButton(
147                             checked = trailingButtonChecked.value,
148                             onCheckedChange = { /* Do Nothing */ },
149                         ) {
150                             trailingContent()
151                         }
152                     }
153                 )
154             SplitButtonType.Outlined ->
155                 SplitButtonLayout(
156                     leadingButton = {
157                         SplitButtonDefaults.OutlinedLeadingButton(
158                             onClick = { /* Do Nothing */ },
159                         ) {
160                             leadingContent()
161                         }
162                     },
163                     trailingButton = {
164                         SplitButtonDefaults.OutlinedTrailingButton(
165                             checked = trailingButtonChecked.value,
166                             onCheckedChange = { /* Do Nothing */ },
167                         ) {
168                             trailingContent()
169                         }
170                     }
171                 )
172         }
173     }
174 
175     @Composable
176     override fun ContentWrappers(content: @Composable () -> Unit) {
<lambda>null177         MaterialTheme { content() }
178     }
179 
toggleStatenull180     override fun toggleState() {
181         trailingButtonChecked.value = !trailingButtonChecked.value
182     }
183 }
184 
185 @Composable
leadingContentnull186 private fun leadingContent() {
187     Icon(
188         Icons.Outlined.Edit,
189         contentDescription = "Localized description",
190     )
191     Spacer(Modifier.size(ButtonDefaults.IconSpacing))
192     Text("My Button")
193 }
194 
195 @Composable
trailingContentnull196 private fun trailingContent() {
197     Icon(Icons.Outlined.KeyboardArrowDown, contentDescription = "Localized description")
198 }
199 
200 enum class SplitButtonType {
201     Filled,
202     Tonal,
203     Elevated,
204     Outlined,
205 }
206