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