[go: nahoru, domu]

blob: 63ec2c6f5895d3056e285e7b7dbe525bfd146b6f [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.compose.ui
import androidx.compose.runtime.remember
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.platform.InspectorValueInfo
import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.unit.Density
/**
* A [Modifier.Element] that draws into the space of the layout.
*/
interface DrawModifier : Modifier.Element {
fun ContentDrawScope.draw()
}
/**
* [DrawModifier] implementation that supports building a cache of objects
* to be referenced across draw calls
*/
interface DrawCacheModifier : DrawModifier {
/**
* Callback invoked to re-build objects to be re-used across draw calls.
* This is useful to conditionally recreate objects only if the size of the
* drawing environment changes, or if state parameters that are inputs
* to objects change. This method is guaranteed to be called before
* [DrawModifier.draw].
*
* @param size The current size of the drawing environment
* @param density The current screen density to provide the ability to convert between
* density independent and raw pixel values
*/
fun onBuildCache(size: Size, density: Density)
}
/**
* Draw into a [Canvas] behind the modified content.
*/
fun Modifier.drawBehind(
onDraw: DrawScope.() -> Unit
) = this.then(
DrawBackgroundModifier(
onDraw = onDraw,
inspectorInfo = debugInspectorInfo {
name = "drawBehind"
properties["onDraw"] = onDraw
}
)
)
private class DrawBackgroundModifier(
val onDraw: DrawScope.() -> Unit,
inspectorInfo: InspectorInfo.() -> Unit
) : DrawModifier, InspectorValueInfo(inspectorInfo) {
override fun ContentDrawScope.draw() {
onDraw()
drawContent()
}
}
/**
* Draw into a [DrawScope] with content that is persisted across
* draw calls as long as the size of the drawing area is the same or
* any state objects that are read have not changed. In the event that
* the drawing area changes, or the underlying state values that are being read
* change, this method is invoked again to recreate objects to be used during drawing
*
* For example, a [androidx.compose.ui.graphics.LinearGradient] that is to occupy the full
* bounds of the drawing area can be created once the size has been defined and referenced
* for subsequent draw calls without having to re-allocate.
*
* @sample androidx.compose.ui.samples.DrawWithCacheModifierSample
* @sample androidx.compose.ui.samples.DrawWithCacheModifierStateParameterSample
* @sample androidx.compose.ui.samples.DrawWithCacheContentSample
*/
fun Modifier.drawWithCache(
onBuildDrawCache: CacheDrawScope.() -> DrawResult
) = composed(
inspectorInfo = debugInspectorInfo {
name = "drawWithCache"
properties["onBuildDrawCache"] = onBuildDrawCache
}
) {
val cacheDrawScope = remember { CacheDrawScope() }
this.then(DrawContentCacheModifier(cacheDrawScope, onBuildDrawCache))
}
/**
* Handle to a drawing environment that enables caching of content based on the resolved size.
* Consumers define parameters and refer to them in the captured draw callback provided in
* [onDraw] or [onDrawWithContent].
*
* [onDraw] will draw behind the layout's drawing contents however, [onDrawWithContent] will
* provide the ability to draw before or after the layout's contents
*/
class CacheDrawScope internal constructor(
internal var cachedDrawDensity: Density? = null
) : Density {
internal var drawResult: DrawResult? = null
/**
* Provides the dimensions of the current drawing environment
*/
var size: Size = Size.Unspecified
internal set
/**
* Issue drawing commands to be executed before the layout content is drawn
*/
fun onDrawBehind(block: DrawScope.() -> Unit): DrawResult = onDrawWithContent {
block()
drawContent()
}
/**
* Issue drawing commands before or after the layout's drawing contents
*/
fun onDrawWithContent(block: ContentDrawScope.() -> Unit): DrawResult {
return DrawResult(block).also { drawResult = it }
}
override val density: Float
get() = cachedDrawDensity!!.density
override val fontScale: Float
get() = cachedDrawDensity!!.density
}
/**
* DrawCacheModifier implementation that is used to construct objects that are dependent on
* the drawing area and re-used across draw calls
*/
private data class DrawContentCacheModifier(
val cacheDrawScope: CacheDrawScope,
val onBuildDrawCache: CacheDrawScope.() -> DrawResult
) : DrawCacheModifier {
override fun onBuildCache(size: Size, density: Density) {
cacheDrawScope.apply {
cachedDrawDensity = density
this.size = size
drawResult = null
onBuildDrawCache()
checkNotNull(drawResult) {
"DrawResult not defined, did you forget to call onDraw?"
}
}
}
override fun ContentDrawScope.draw() {
cacheDrawScope.drawResult!!.block(this)
}
}
/**
* Holder to a callback to be invoked during draw operations. This lambda
* captures and reuses parameters defined within the CacheDrawScope receiver scope lambda.
*/
class DrawResult internal constructor(internal var block: ContentDrawScope.() -> Unit)
/**
* Creates a [DrawModifier] that allows the developer to draw before or after the layout's
* contents. It also allows the modifier to adjust the layout's canvas.
*/
// TODO: Inline this function -- it breaks with current compiler
/*inline*/ fun Modifier.drawWithContent(
onDraw: ContentDrawScope.() -> Unit
): Modifier = this.then(
object : DrawModifier, InspectorValueInfo(
debugInspectorInfo {
name = "drawWithContent"
properties["onDraw"] = onDraw
}
) {
override fun ContentDrawScope.draw() {
onDraw()
}
}
)