[go: nahoru, domu]

blob: 17090debb95d8984337370b86d69161964a985d3 [file] [log] [blame]
/*
* Copyright 2020 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.graphics
import androidx.ui.util.annotation.FloatRange
import androidx.ui.geometry.Offset
import androidx.ui.geometry.RRect
import androidx.ui.geometry.Radius
import androidx.ui.geometry.Rect
import androidx.ui.geometry.Size
import androidx.ui.graphics.drawscope.DrawScope
import androidx.ui.graphics.drawscope.DrawStyle
import androidx.ui.graphics.drawscope.Fill
/**
* Defines a simple shape, used for bounding graphical regions.
*
* Can be used for defining a shape of the component background, a shape of
* shadows cast by the component, or to clip the contents.
*/
sealed class Outline {
/**
* Rectangular area.
*/
data class Rectangle(val rect: Rect) : Outline()
/**
* Rectangular area with rounded corners.
*/
data class Rounded(val rrect: RRect) : Outline() {
/**
* Optional Path to be created for the RRect if the corner radii are not identical
* This is because Canvas has a built in API for drawing round rectangles with the
* same corner radii in all 4 corners. However, if each corner has a different
* corner radii, a path must be drawn instead
*/
internal val roundRectPath: Path?
init {
roundRectPath = if (!rrect.hasSameCornerRadius()) {
Path().apply { addRRect(rrect) }
} else {
null
}
}
}
/**
* An area defined as a path.
*
* Note that only convex paths can be used for drawing the shadow. See [Path.isConvex].
*/
data class Generic(val path: Path) : Outline()
}
/**
* Adds the [outline] to the [Path].
*/
fun Path.addOutline(outline: Outline) = when (outline) {
is Outline.Rectangle -> addRect(outline.rect)
is Outline.Rounded -> addRRect(outline.rrect)
is Outline.Generic -> addPath(outline.path)
}
/**
* Draws the [Outline] on a [DrawScope].
*
* @param outline the outline to draw.
* @param color Color applied to the outline when it is drawn
* @param alpha Opacity to be applied to outline from 0.0f to 1.0f representing
* fully transparent to fully opaque respectively
* @param style Specifies whether the outline is stroked or filled in
* @param colorFilter: ColorFilter to apply to the [color] when drawn into the destination
* @param blendMode: Blending algorithm to be applied to the outline
*/
fun DrawScope.drawOutline(
outline: Outline,
color: Color,
@FloatRange(from = 0.0, to = 1.0) alpha: Float = 1.0f,
style: DrawStyle = Fill,
colorFilter: ColorFilter? = null,
blendMode: BlendMode = DrawScope.DefaultBlendMode
) = drawOutlineHelper(
outline,
{ rect ->
drawRect(color, rect.topLeft(), rect.size(), alpha, style, colorFilter, blendMode)
},
{ rrect ->
val radius = rrect.bottomLeftRadiusX
drawRoundRect(
color = color,
topLeft = rrect.topLeft(),
size = rrect.size(),
radius = Radius(radius),
alpha = alpha,
style = style,
colorFilter = colorFilter,
blendMode = blendMode
)
},
{ path -> drawPath(path, color, alpha, style, colorFilter, blendMode) }
)
/**
* Draws the [Outline] on a [DrawScope].
*
* @param outline the outline to draw.
* @param brush Brush applied to the outline when it is drawn
* @param alpha Opacity to be applied to outline from 0.0f to 1.0f representing
* fully transparent to fully opaque respectively
* @param style Specifies whether the outline is stroked or filled in
* @param colorFilter: ColorFilter to apply to the [Brush] when drawn into the destination
* @param blendMode: Blending algorithm to be applied to the outline
*/
fun DrawScope.drawOutline(
outline: Outline,
brush: Brush,
@FloatRange(from = 0.0, to = 1.0) alpha: Float = 1.0f,
style: DrawStyle = Fill,
colorFilter: ColorFilter? = null,
blendMode: BlendMode = DrawScope.DefaultBlendMode
) = drawOutlineHelper(
outline,
{ rect ->
drawRect(brush, rect.topLeft(), rect.size(), alpha, style, colorFilter, blendMode)
},
{ rrect ->
val radius = rrect.bottomLeftRadiusX
drawRoundRect(
brush = brush,
topLeft = rrect.topLeft(),
size = rrect.size(),
radius = Radius(radius),
alpha = alpha,
style = style,
colorFilter = colorFilter,
blendMode = blendMode
)
},
{ path -> drawPath(path, brush, alpha, style, colorFilter, blendMode) }
)
/**
* Convenience method to obtain an Offset from the Rect's top and left parameters
*/
private fun Rect.topLeft(): Offset = Offset(left, top)
/**
* Convenience method to obtain a Size from the Rect's width and height
*/
private fun Rect.size(): Size = Size(width, height)
/**
* Convenience method to obtain an Offset from the RRect's top and left parameters
*/
private fun RRect.topLeft(): Offset = Offset(left, top)
/**
* Convenience method to obtain a Size from the RRect's width and height parameters
*/
private fun RRect.size(): Size = Size(width, height)
/**
* Helper method that allows for delegation of appropriate drawing call based on type of
* underlying outline shape
*/
private inline fun DrawScope.drawOutlineHelper(
outline: Outline,
drawRectBlock: DrawScope.(rect: Rect) -> Unit,
drawRoundedRectBlock: DrawScope.(rrect: RRect) -> Unit,
drawPathBlock: DrawScope.(path: Path) -> Unit
) = when (outline) {
is Outline.Rectangle -> drawRectBlock(outline.rect)
is Outline.Rounded -> {
val path = outline.roundRectPath
// If the rounded rect has a path, then the corner radii are not the same across
// each of the corners, so we draw the given path.
// If there is no path available, then the corner radii are identical so call the
// Canvas primitive for drawing a rounded rectangle
if (path != null) {
drawPathBlock(path)
} else {
drawRoundedRectBlock(outline.rrect)
}
}
is Outline.Generic -> drawPathBlock(outline.path)
}
/**
* Draws the [Outline] on a [Canvas].
*
* @param outline the outline to draw.
* @param paint the paint used for the drawing.
*/
fun Canvas.drawOutline(outline: Outline, paint: Paint) = when (outline) {
is Outline.Rectangle -> drawRect(outline.rect, paint)
is Outline.Rounded -> {
val path = outline.roundRectPath
// If the rounded rect has a path, then the corner radii are not the same across
// each of the corners, so we draw the given path.
// If there is no path available, then the corner radii are identical so call the
// Canvas primitive for drawing a rounded rectangle
if (path != null) {
drawPath(path, paint)
} else {
drawRoundRect(
left = outline.rrect.left,
top = outline.rrect.top,
right = outline.rrect.right,
bottom = outline.rrect.bottom,
radiusX = outline.rrect.bottomLeftRadiusX,
radiusY = outline.rrect.bottomLeftRadiusY,
paint = paint)
}
}
is Outline.Generic -> drawPath(outline.path, paint)
}
/**
* Convenience method to determine if the corner radii of the RRect are identical
* in each of the corners. That is the x radius and the y radius are the same for each corner,
* however, the x and y can be different
*/
private fun RRect.hasSameCornerRadius(): Boolean {
val sameRadiusX = bottomLeftRadiusX == bottomRightRadiusX &&
bottomRightRadiusX == topRightRadiusX &&
topRightRadiusX == topLeftRadiusX
val sameRadiusY = bottomLeftRadiusY == bottomRightRadiusY &&
bottomRightRadiusY == topRightRadiusY &&
topRightRadiusY == topLeftRadiusY
return sameRadiusX && sameRadiusY
}