1 /*
2 * Copyright 2021 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
18
19 import androidx.compose.foundation.BorderStroke
20 import androidx.compose.foundation.background
21 import androidx.compose.foundation.border
22 import androidx.compose.foundation.clickable
23 import androidx.compose.foundation.interaction.Interaction
24 import androidx.compose.foundation.interaction.MutableInteractionSource
25 import androidx.compose.foundation.layout.Box
26 import androidx.compose.foundation.selection.selectable
27 import androidx.compose.foundation.selection.toggleable
28 import androidx.compose.material3.internal.childSemantics
29 import androidx.compose.runtime.Composable
30 import androidx.compose.runtime.CompositionLocalProvider
31 import androidx.compose.runtime.NonRestartableComposable
32 import androidx.compose.runtime.Stable
33 import androidx.compose.runtime.compositionLocalOf
34 import androidx.compose.runtime.remember
35 import androidx.compose.ui.Modifier
36 import androidx.compose.ui.draw.clip
37 import androidx.compose.ui.graphics.Color
38 import androidx.compose.ui.graphics.RectangleShape
39 import androidx.compose.ui.graphics.Shape
40 import androidx.compose.ui.graphics.graphicsLayer
41 import androidx.compose.ui.input.pointer.pointerInput
42 import androidx.compose.ui.platform.LocalDensity
43 import androidx.compose.ui.semantics.isContainer
44 import androidx.compose.ui.semantics.semantics
45 import androidx.compose.ui.unit.Dp
46 import androidx.compose.ui.unit.dp
47
48 // TODO(b/197880751): Add url to spec on Material.io.
49 /**
50 * Material surface is the central metaphor in material design. Each surface exists at a given
51 * elevation, which influences how that piece of surface visually relates to other surfaces and how
52 * that surface is modified by tonal variance.
53 *
54 * See the other overloads for clickable, selectable, and toggleable surfaces.
55 *
56 * The Surface is responsible for:
57 * 1) Clipping: Surface clips its children to the shape specified by [shape]
58 * 2) Borders: If [shape] has a border, then it will also be drawn.
59 * 3) Background: Surface fills the shape specified by [shape] with the [color]. If [color] is
60 * [ColorScheme.surface] a color overlay will be applied. The color of the overlay depends on the
61 * [tonalElevation] of this Surface, and the [LocalAbsoluteTonalElevation] set by any parent
62 * surfaces. This ensures that a Surface never appears to have a lower elevation overlay than its
63 * ancestors, by summing the elevation of all previous Surfaces.
64 * 4) Content color: Surface uses [contentColor] to specify a preferred color for the content of
65 * this surface - this is used by the [Text] and [Icon] components as a default color.
66 *
67 * If no [contentColor] is set, this surface will try and match its background color to a color
68 * defined in the theme [ColorScheme], and return the corresponding content color. For example, if
69 * the [color] of this surface is [ColorScheme.surface], [contentColor] will be set to
70 * [ColorScheme.onSurface]. If [color] is not part of the theme palette, [contentColor] will keep
71 * the same value set above this Surface.
72 *
73 * To manually retrieve the content color inside a surface, use [LocalContentColor].
74 * 5) Blocking touch propagation behind the surface.
75 *
76 * Surface sample:
77 *
78 * @sample androidx.compose.material3.samples.SurfaceSample
79 * @param modifier Modifier to be applied to the layout corresponding to the surface
80 * @param shape Defines the surface's shape as well its shadow.
81 * @param color The background color. Use [Color.Transparent] to have no color.
82 * @param contentColor The preferred content color provided by this Surface to its children.
83 * Defaults to either the matching content color for [color], or if [color] is not a color from
84 * the theme, this will keep the same value set above this Surface.
85 * @param tonalElevation When [color] is [ColorScheme.surface], a higher the elevation will result
86 * in a darker color in light theme and lighter color in dark theme.
87 * @param shadowElevation The size of the shadow below the surface. To prevent shadow creep, only
88 * apply shadow elevation when absolutely necessary, such as when the surface requires visual
89 * separation from a patterned background. Note that It will not affect z index of the Surface. If
90 * you want to change the drawing order you can use `Modifier.zIndex`.
91 * @param border Optional border to draw on top of the surface
92 * @param content The content to be displayed on this Surface
93 */
94 @Composable
95 @NonRestartableComposable
Surfacenull96 fun Surface(
97 modifier: Modifier = Modifier,
98 shape: Shape = RectangleShape,
99 color: Color = MaterialTheme.colorScheme.surface,
100 contentColor: Color = contentColorFor(color),
101 tonalElevation: Dp = 0.dp,
102 shadowElevation: Dp = 0.dp,
103 border: BorderStroke? = null,
104 content: @Composable () -> Unit
105 ) {
106 val absoluteElevation = LocalAbsoluteTonalElevation.current + tonalElevation
107 CompositionLocalProvider(
108 LocalContentColor provides contentColor,
109 LocalAbsoluteTonalElevation provides absoluteElevation
110 ) {
111 Box(
112 modifier =
113 modifier
114 .surface(
115 shape = shape,
116 backgroundColor =
117 surfaceColorAtElevation(color = color, elevation = absoluteElevation),
118 border = border,
119 shadowElevation = with(LocalDensity.current) { shadowElevation.toPx() }
120 )
121 .semantics(mergeDescendants = false) {
122 // TODO(b/347038246): replace `isContainer` with `isTraversalGroup` with new
123 // pruning API.
124 @Suppress("DEPRECATION")
125 isContainer = true
126 }
127 .pointerInput(Unit) {},
128 propagateMinConstraints = true
129 ) {
130 content()
131 }
132 }
133 }
134
135 /**
136 * Material surface is the central metaphor in material design. Each surface exists at a given
137 * elevation, which influences how that piece of surface visually relates to other surfaces and how
138 * that surface is modified by tonal variance.
139 *
140 * This version of Surface is responsible for a click handling as well as everything else that a
141 * regular Surface does:
142 *
143 * This clickable Surface is responsible for:
144 * 1) Clipping: Surface clips its children to the shape specified by [shape]
145 * 2) Borders: If [shape] has a border, then it will also be drawn.
146 * 3) Background: Surface fills the shape specified by [shape] with the [color]. If [color] is
147 * [ColorScheme.surface] a color overlay may be applied. The color of the overlay depends on the
148 * [tonalElevation] of this Surface, and the [LocalAbsoluteTonalElevation] set by any parent
149 * surfaces. This ensures that a Surface never appears to have a lower elevation overlay than its
150 * ancestors, by summing the elevation of all previous Surfaces.
151 * 4) Content color: Surface uses [contentColor] to specify a preferred color for the content of
152 * this surface - this is used by the [Text] and [Icon] components as a default color. If no
153 * [contentColor] is set, this surface will try and match its background color to a color defined
154 * in the theme [ColorScheme], and return the corresponding content color. For example, if the
155 * [color] of this surface is [ColorScheme.surface], [contentColor] will be set to
156 * [ColorScheme.onSurface]. If [color] is not part of the theme palette, [contentColor] will keep
157 * the same value set above this Surface.
158 * 5) Click handling. This version of surface will react to the clicks, calling [onClick] lambda,
159 * updating the [interactionSource] when [PressInteraction] occurs, and showing ripple indication
160 * in response to press events. If you don't need click handling, consider using the Surface
161 * function that doesn't require [onClick] param. If you need to set a custom label for the
162 * [onClick], apply a `Modifier.semantics { onClick(label = "YOUR_LABEL", action = null) }` to
163 * the Surface.
164 * 6) Semantics for clicks. Just like with [Modifier.clickable], clickable version of Surface will
165 * produce semantics to indicate that it is clicked. No semantic role is set by default, you may
166 * specify one by passing a desired [Role] with a [Modifier.semantics].
167 *
168 * To manually retrieve the content color inside a surface, use [LocalContentColor].
169 *
170 * Clickable surface sample:
171 *
172 * @sample androidx.compose.material3.samples.ClickableSurfaceSample
173 * @param onClick callback to be called when the surface is clicked
174 * @param modifier Modifier to be applied to the layout corresponding to the surface
175 * @param enabled Controls the enabled state of the surface. When `false`, this surface will not be
176 * clickable
177 * @param shape Defines the surface's shape as well its shadow. A shadow is only displayed if the
178 * [tonalElevation] is greater than zero.
179 * @param color The background color. Use [Color.Transparent] to have no color.
180 * @param contentColor The preferred content color provided by this Surface to its children.
181 * Defaults to either the matching content color for [color], or if [color] is not a color from
182 * the theme, this will keep the same value set above this Surface.
183 * @param border Optional border to draw on top of the surface
184 * @param tonalElevation When [color] is [ColorScheme.surface], a higher the elevation will result
185 * in a darker color in light theme and lighter color in dark theme.
186 * @param shadowElevation The size of the shadow below the surface. Note that It will not affect z
187 * index of the Surface. If you want to change the drawing order you can use `Modifier.zIndex`.
188 * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
189 * emitting [Interaction]s for this surface. You can use this to change the surface's appearance
190 * or preview the surface in different states. Note that if `null` is provided, interactions will
191 * still happen internally.
192 * @param content The content to be displayed on this Surface
193 */
194 @Composable
195 @NonRestartableComposable
Surfacenull196 fun Surface(
197 onClick: () -> Unit,
198 modifier: Modifier = Modifier,
199 enabled: Boolean = true,
200 shape: Shape = RectangleShape,
201 color: Color = MaterialTheme.colorScheme.surface,
202 contentColor: Color = contentColorFor(color),
203 tonalElevation: Dp = 0.dp,
204 shadowElevation: Dp = 0.dp,
205 border: BorderStroke? = null,
206 interactionSource: MutableInteractionSource? = null,
207 content: @Composable () -> Unit
208 ) {
209 @Suppress("NAME_SHADOWING")
210 val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
211 val absoluteElevation = LocalAbsoluteTonalElevation.current + tonalElevation
212 CompositionLocalProvider(
213 LocalContentColor provides contentColor,
214 LocalAbsoluteTonalElevation provides absoluteElevation
215 ) {
216 Box(
217 modifier =
218 modifier
219 .minimumInteractiveComponentSize()
220 .surface(
221 shape = shape,
222 backgroundColor =
223 surfaceColorAtElevation(color = color, elevation = absoluteElevation),
224 border = border,
225 shadowElevation = with(LocalDensity.current) { shadowElevation.toPx() }
226 )
227 .clickable(
228 interactionSource = interactionSource,
229 indication = ripple(),
230 enabled = enabled,
231 onClick = onClick
232 )
233 .childSemantics(),
234 propagateMinConstraints = true
235 ) {
236 content()
237 }
238 }
239 }
240
241 /**
242 * Material surface is the central metaphor in material design. Each surface exists at a given
243 * elevation, which influences how that piece of surface visually relates to other surfaces and how
244 * that surface is modified by tonal variance.
245 *
246 * This version of Surface is responsible for a selection handling as well as everything else that a
247 * regular Surface does:
248 *
249 * This selectable Surface is responsible for:
250 * 1) Clipping: Surface clips its children to the shape specified by [shape]
251 * 2) Borders: If [shape] has a border, then it will also be drawn.
252 * 3) Background: Surface fills the shape specified by [shape] with the [color]. If [color] is
253 * [ColorScheme.surface] a color overlay may be applied. The color of the overlay depends on the
254 * [tonalElevation] of this Surface, and the [LocalAbsoluteTonalElevation] set by any parent
255 * surfaces. This ensures that a Surface never appears to have a lower elevation overlay than its
256 * ancestors, by summing the elevation of all previous Surfaces.
257 * 4) Content color: Surface uses [contentColor] to specify a preferred color for the content of
258 * this surface - this is used by the [Text] and [Icon] components as a default color. If no
259 * [contentColor] is set, this surface will try and match its background color to a color defined
260 * in the theme [ColorScheme], and return the corresponding content color. For example, if the
261 * [color] of this surface is [ColorScheme.surface], [contentColor] will be set to
262 * [ColorScheme.onSurface]. If [color] is not part of the theme palette, [contentColor] will keep
263 * the same value set above this Surface.
264 * 5) Click handling. This version of surface will react to the clicks, calling [onClick] lambda,
265 * updating the [interactionSource] when [PressInteraction] occurs, and showing ripple indication
266 * in response to press events. If you don't need click handling, consider using the Surface
267 * function that doesn't require [onClick] param.
268 * 6) Semantics for selection. Just like with [Modifier.selectable], selectable version of Surface
269 * will produce semantics to indicate that it is selected. No semantic role is set by default,
270 * you may specify one by passing a desired [Role] with a [Modifier.semantics].
271 *
272 * To manually retrieve the content color inside a surface, use [LocalContentColor].
273 *
274 * Selectable surface sample:
275 *
276 * @sample androidx.compose.material3.samples.SelectableSurfaceSample
277 * @param selected whether or not this Surface is selected
278 * @param onClick callback to be called when the surface is clicked
279 * @param modifier Modifier to be applied to the layout corresponding to the surface
280 * @param enabled Controls the enabled state of the surface. When `false`, this surface will not be
281 * clickable
282 * @param shape Defines the surface's shape as well its shadow. A shadow is only displayed if the
283 * [tonalElevation] is greater than zero.
284 * @param color The background color. Use [Color.Transparent] to have no color.
285 * @param contentColor The preferred content color provided by this Surface to its children.
286 * Defaults to either the matching content color for [color], or if [color] is not a color from
287 * the theme, this will keep the same value set above this Surface.
288 * @param border Optional border to draw on top of the surface
289 * @param tonalElevation When [color] is [ColorScheme.surface], a higher the elevation will result
290 * in a darker color in light theme and lighter color in dark theme.
291 * @param shadowElevation The size of the shadow below the surface. Note that It will not affect z
292 * index of the Surface. If you want to change the drawing order you can use `Modifier.zIndex`.
293 * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
294 * emitting [Interaction]s for this surface. You can use this to change the surface's appearance
295 * or preview the surface in different states. Note that if `null` is provided, interactions will
296 * still happen internally.
297 * @param content The content to be displayed on this Surface
298 */
299 @Composable
300 @NonRestartableComposable
Surfacenull301 fun Surface(
302 selected: Boolean,
303 onClick: () -> Unit,
304 modifier: Modifier = Modifier,
305 enabled: Boolean = true,
306 shape: Shape = RectangleShape,
307 color: Color = MaterialTheme.colorScheme.surface,
308 contentColor: Color = contentColorFor(color),
309 tonalElevation: Dp = 0.dp,
310 shadowElevation: Dp = 0.dp,
311 border: BorderStroke? = null,
312 interactionSource: MutableInteractionSource? = null,
313 content: @Composable () -> Unit
314 ) {
315 @Suppress("NAME_SHADOWING")
316 val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
317 val absoluteElevation = LocalAbsoluteTonalElevation.current + tonalElevation
318 CompositionLocalProvider(
319 LocalContentColor provides contentColor,
320 LocalAbsoluteTonalElevation provides absoluteElevation
321 ) {
322 Box(
323 modifier =
324 modifier
325 .minimumInteractiveComponentSize()
326 .surface(
327 shape = shape,
328 backgroundColor =
329 surfaceColorAtElevation(color = color, elevation = absoluteElevation),
330 border = border,
331 shadowElevation = with(LocalDensity.current) { shadowElevation.toPx() }
332 )
333 .selectable(
334 selected = selected,
335 interactionSource = interactionSource,
336 indication = ripple(),
337 enabled = enabled,
338 onClick = onClick
339 )
340 .childSemantics(),
341 propagateMinConstraints = true
342 ) {
343 content()
344 }
345 }
346 }
347
348 /**
349 * Material surface is the central metaphor in material design. Each surface exists at a given
350 * elevation, which influences how that piece of surface visually relates to other surfaces and how
351 * that surface is modified by tonal variance.
352 *
353 * This version of Surface is responsible for a toggling its checked state as well as everything
354 * else that a regular Surface does:
355 *
356 * This toggleable Surface is responsible for:
357 * 1) Clipping: Surface clips its children to the shape specified by [shape]
358 * 2) Borders: If [shape] has a border, then it will also be drawn.
359 * 3) Background: Surface fills the shape specified by [shape] with the [color]. If [color] is
360 * [ColorScheme.surface] a color overlay may be applied. The color of the overlay depends on the
361 * [tonalElevation] of this Surface, and the [LocalAbsoluteTonalElevation] set by any parent
362 * surfaces. This ensures that a Surface never appears to have a lower elevation overlay than its
363 * ancestors, by summing the elevation of all previous Surfaces.
364 * 4) Content color: Surface uses [contentColor] to specify a preferred color for the content of
365 * this surface - this is used by the [Text] and [Icon] components as a default color. If no
366 * [contentColor] is set, this surface will try and match its background color to a color defined
367 * in the theme [ColorScheme], and return the corresponding content color. For example, if the
368 * [color] of this surface is [ColorScheme.surface], [contentColor] will be set to
369 * [ColorScheme.onSurface]. If [color] is not part of the theme palette, [contentColor] will keep
370 * the same value set above this Surface.
371 * 5) Click handling. This version of surface will react to the check toggles, calling
372 * [onCheckedChange] lambda, updating the [interactionSource] when [PressInteraction] occurs, and
373 * showing ripple indication in response to press events. If you don't need check handling,
374 * consider using a Surface function that doesn't require [onCheckedChange] param.
375 * 6) Semantics for toggle. Just like with [Modifier.toggleable], toggleable version of Surface will
376 * produce semantics to indicate that it is checked. No semantic role is set by default, you may
377 * specify one by passing a desired [Role] with a [Modifier.semantics].
378 *
379 * To manually retrieve the content color inside a surface, use [LocalContentColor].
380 *
381 * Toggleable surface sample:
382 *
383 * @sample androidx.compose.material3.samples.ToggleableSurfaceSample
384 * @param checked whether or not this Surface is toggled on or off
385 * @param onCheckedChange callback to be invoked when the toggleable Surface is clicked
386 * @param modifier Modifier to be applied to the layout corresponding to the surface
387 * @param enabled Controls the enabled state of the surface. When `false`, this surface will not be
388 * clickable
389 * @param shape Defines the surface's shape as well its shadow. A shadow is only displayed if the
390 * [tonalElevation] is greater than zero.
391 * @param color The background color. Use [Color.Transparent] to have no color.
392 * @param contentColor The preferred content color provided by this Surface to its children.
393 * Defaults to either the matching content color for [color], or if [color] is not a color from
394 * the theme, this will keep the same value set above this Surface.
395 * @param border Optional border to draw on top of the surface
396 * @param tonalElevation When [color] is [ColorScheme.surface], a higher the elevation will result
397 * in a darker color in light theme and lighter color in dark theme.
398 * @param shadowElevation The size of the shadow below the surface. Note that It will not affect z
399 * index of the Surface. If you want to change the drawing order you can use `Modifier.zIndex`.
400 * @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
401 * emitting [Interaction]s for this surface. You can use this to change the surface's appearance
402 * or preview the surface in different states. Note that if `null` is provided, interactions will
403 * still happen internally.
404 * @param content The content to be displayed on this Surface
405 */
406 @Composable
407 @NonRestartableComposable
Surfacenull408 fun Surface(
409 checked: Boolean,
410 onCheckedChange: (Boolean) -> Unit,
411 modifier: Modifier = Modifier,
412 enabled: Boolean = true,
413 shape: Shape = RectangleShape,
414 color: Color = MaterialTheme.colorScheme.surface,
415 contentColor: Color = contentColorFor(color),
416 tonalElevation: Dp = 0.dp,
417 shadowElevation: Dp = 0.dp,
418 border: BorderStroke? = null,
419 interactionSource: MutableInteractionSource? = null,
420 content: @Composable () -> Unit
421 ) {
422 @Suppress("NAME_SHADOWING")
423 val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
424 val absoluteElevation = LocalAbsoluteTonalElevation.current + tonalElevation
425 CompositionLocalProvider(
426 LocalContentColor provides contentColor,
427 LocalAbsoluteTonalElevation provides absoluteElevation
428 ) {
429 Box(
430 modifier =
431 modifier
432 .minimumInteractiveComponentSize()
433 .surface(
434 shape = shape,
435 backgroundColor =
436 surfaceColorAtElevation(color = color, elevation = absoluteElevation),
437 border = border,
438 shadowElevation = with(LocalDensity.current) { shadowElevation.toPx() }
439 )
440 .toggleable(
441 value = checked,
442 interactionSource = interactionSource,
443 indication = ripple(),
444 enabled = enabled,
445 onValueChange = onCheckedChange
446 )
447 .childSemantics(),
448 propagateMinConstraints = true
449 ) {
450 content()
451 }
452 }
453 }
454
455 @Stable
surfacenull456 private fun Modifier.surface(
457 shape: Shape,
458 backgroundColor: Color,
459 border: BorderStroke?,
460 shadowElevation: Float,
461 ) =
462 this.then(
463 if (shadowElevation > 0f) {
464 Modifier.graphicsLayer(
465 shadowElevation = shadowElevation,
466 shape = shape,
467 clip = false
468 )
469 } else {
470 Modifier
471 }
472 )
473 .then(if (border != null) Modifier.border(border, shape) else Modifier)
474 .background(color = backgroundColor, shape = shape)
475 .clip(shape)
476
477 @Composable
surfaceColorAtElevationnull478 private fun surfaceColorAtElevation(color: Color, elevation: Dp): Color =
479 MaterialTheme.colorScheme.applyTonalElevation(color, elevation)
480
481 /**
482 * CompositionLocal containing the current absolute elevation provided by [Surface] components. This
483 * absolute elevation is a sum of all the previous elevations. Absolute elevation is only used for
484 * calculating surface tonal colors, and is *not* used for drawing the shadow in a [Surface].
485 */
486 // TODO(b/179787782): Add sample after catalog app lands in aosp.
487 val LocalAbsoluteTonalElevation = compositionLocalOf { 0.dp }
488