[go: nahoru, domu]

blob: f3fa80c48ee0e93272c1cacab9932ca4574f0a41 [file] [log] [blame]
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.ui.material
import androidx.compose.Ambient
import androidx.compose.Composable
import androidx.compose.Model
import androidx.compose.memo
import androidx.compose.unaryPlus
import androidx.ui.graphics.Color
import kotlin.reflect.KProperty
/**
* Collection of colors in the [Material color specification]
* [https://material.io/design/color/the-color-system.html#color-theme-creation].
*/
interface ColorPalette {
/**
* The primary color is the color displayed most frequently across your app’s screens and
* components.
*/
val primary: Color
/**
* The primary variant color is used to distinguish two elements of the app using the primary
* color, such as the top app bar and the system bar.
*/
val primaryVariant: Color
/**
* The secondary color provides more ways to accent and distinguish your product.
* Secondary colors are best for:
* - Floating action buttons
* - Selection controls, like sliders and switches
* - Highlighting selected text
* - Progress bars
* - Links and headlines
*/
val secondary: Color
/**
* The secondary variant color is used to distinguish two elements of the app using the
* secondary color.
*/
val secondaryVariant: Color
/**
* The background color appears behind scrollable content.
*/
val background: Color
/**
* The surface color is used on surfaces of components, such as cards, sheets and menus.
*/
val surface: Color
/**
* The error color is used to indicate error within components, such as text fields.
*/
val error: Color
/**
* Color used for text and icons displayed on top of the primary color.
*/
val onPrimary: Color
/**
* Color used for text and icons displayed on top of the secondary color.
*/
val onSecondary: Color
/**
* Color used for text and icons displayed on top of the background color.
*/
val onBackground: Color
/**
* Color used for text and icons displayed on top of the surface color.
*/
val onSurface: Color
/**
* Color used for text and icons displayed on top of the error color.
*/
val onError: Color
}
/**
* Creates a complete color definition for the [Material color specification]
* [https://material.io/design/color/the-color-system.html#color-theme-creation].
*/
fun ColorPalette(
primary: Color = Color(0xFF6200EE),
primaryVariant: Color = Color(0xFF3700B3),
secondary: Color = Color(0xFF03DAC6),
secondaryVariant: Color = Color(0xFF018786),
background: Color = Color.White,
surface: Color = Color.White,
error: Color = Color(0xFFB00020),
onPrimary: Color = Color.White,
onSecondary: Color = Color.Black,
onBackground: Color = Color.Black,
onSurface: Color = Color.Black,
onError: Color = Color.White
): ColorPalette = ObservableColorPalette(
primary,
primaryVariant,
secondary,
secondaryVariant,
background,
surface,
error,
onPrimary,
onSecondary,
onBackground,
onSurface,
onError
)
/**
* Default observable backing implementation for [ColorPalette].
*
* Typically we would just change the value of the [ColorAmbient] ambient when the theme changes, but
* in the case of wide-reaching data such as colors in the [MaterialTheme], this will cause almost
* every UI component on a screen to be recomposed. In reality, we only want to recompose
* components that consume the specific color(s) that have been changed - so this default
* implementation is intended to be memoized in the ambient, and then when a new immutable
* [ColorPalette] is provided, we can simply diff and update any values that need to be changed.
* Because the internal values are provided by an @Model delegate class, components consuming the
* specific color will be recomposed, while everything else will remain the same. This allows for
* large performance improvements when the theme is being changed, especially if it is being
* animated.
*/
private class ObservableColorPalette(
primary: Color,
primaryVariant: Color,
secondary: Color,
secondaryVariant: Color,
background: Color,
surface: Color,
error: Color,
onPrimary: Color,
onSecondary: Color,
onBackground: Color,
onSurface: Color,
onError: Color
) : ColorPalette {
constructor(colorPalette: ColorPalette) : this(
primary = colorPalette.primary,
primaryVariant = colorPalette.primaryVariant,
secondary = colorPalette.secondary,
secondaryVariant = colorPalette.secondaryVariant,
background = colorPalette.background,
surface = colorPalette.surface,
error = colorPalette.error,
onPrimary = colorPalette.onPrimary,
onSecondary = colorPalette.onSecondary,
onBackground = colorPalette.onBackground,
onSurface = colorPalette.onSurface,
onError = colorPalette.onError
)
override var primary by ObservableColor(primary)
override var primaryVariant by ObservableColor(primaryVariant)
override var secondary by ObservableColor(secondary)
override var secondaryVariant by ObservableColor(secondaryVariant)
override var background by ObservableColor(background)
override var surface by ObservableColor(surface)
override var error by ObservableColor(error)
override var onPrimary by ObservableColor(onPrimary)
override var onSecondary by ObservableColor(onSecondary)
override var onBackground by ObservableColor(onBackground)
override var onSurface by ObservableColor(onSurface)
override var onError by ObservableColor(onError)
}
@Model
private class ObservableColor(var color: Color) {
operator fun getValue(thisObj: Any?, property: KProperty<*>) = color
operator fun setValue(thisObj: Any?, property: KProperty<*>, next: Color) {
if (color != next) color = next
}
}
/**
* Updates the internal values of the given [ObservableColorPalette] with values from the [other]
* [ColorPalette].
*/
private fun ObservableColorPalette.updateColorsFrom(other: ColorPalette): ObservableColorPalette {
primary = other.primary
primaryVariant = other.primaryVariant
secondary = other.secondary
secondaryVariant = other.secondaryVariant
background = other.background
surface = other.surface
error = other.error
onPrimary = other.onPrimary
onSecondary = other.onSecondary
onBackground = other.onBackground
onSurface = other.onSurface
onError = other.onError
return this
}
/**
* Memoizes and mutates the given [colorPalette] if it is an [ObservableColorPalette], otherwise
* just provides the given palette in the [ColorAmbient] [Ambient].
*/
@Composable
internal fun ProvideColorPalette(colorPalette: ColorPalette, children: @Composable() () -> Unit) {
val palette = when (colorPalette) {
is ObservableColorPalette -> {
(+memo<ObservableColorPalette> {
ObservableColorPalette(colorPalette)
}).updateColorsFrom(colorPalette)
}
else -> colorPalette
}
ColorAmbient.Provider(palette, children)
}
/**
* Ambient used to pass [ColorPalette] down the tree.
*
* To retrieve the current value of this ambient, use [MaterialTheme.colors].
*/
internal val ColorAmbient = Ambient.of { ColorPalette() }