Skip to content

Instantly share code, notes, and snippets.

@Peanuuutz
Created April 6, 2023 14:55
Show Gist options
  • Save Peanuuutz/ec414e2cd8f49934620f91e6cd5f358e to your computer and use it in GitHub Desktop.
Save Peanuuutz/ec414e2cd8f49934620f91e6cd5f358e to your computer and use it in GitHub Desktop.
Compose Border with Effect
@file:OptIn(ExperimentalComposeUiApi::class)
package net.peanuuutz.compose.desktop
import androidx.compose.runtime.Stable
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.DrawStyle
import androidx.compose.ui.graphics.drawscope.Fill
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.unit.Dp
import kotlin.math.ceil
import kotlin.math.min
/**
* Adds a border to the modified element with appearance specified with a
* [width], a [brush], an [effect] and a [shape]. The border will be inside the
* bounds of the element.
*
* @param width width of the border. Use [Dp.Hairline] for a hairline border.
* @param brush brush to paint the border with.
* @param effect [PathEffect] applied to the border stroke.
* @param shape shape of the border.
*/
@Stable
fun Modifier.borderWithEffect(
width: Dp,
brush: Brush,
effect: PathEffect?,
shape: Shape = RectangleShape
): Modifier {
val element = BorderWithEffectElement(
width = width,
brush = brush,
effect = effect,
shape = shape
)
return this then element
}
// ======== Internal ========
private data class BorderWithEffectElement(
val width: Dp,
val brush: Brush,
val effect: PathEffect?,
val shape: Shape
) : ModifierNodeElement<BorderWithEffectNode>() {
override fun create(): BorderWithEffectNode {
return BorderWithEffectNode(
width = width,
brush = brush,
effect = effect,
shape = shape
)
}
override fun update(node: BorderWithEffectNode): BorderWithEffectNode {
node.width = width
node.brush = brush
node.effect = effect
node.shape = shape
return node
}
}
private class BorderWithEffectNode(
var width: Dp,
var brush: Brush,
var effect: PathEffect?,
var shape: Shape // TODO Implementation
) : Modifier.Node(), DrawModifierNode {
override fun ContentDrawScope.draw() {
drawContent()
drawBorder()
}
private fun DrawScope.drawBorder() {
val definedStrokeWidth = width
val canvasSize = size
val canvasMinDimension = canvasSize.minDimension
if (definedStrokeWidth.toPx() < 0.0f || canvasMinDimension <= 0.0f) {
return
}
val resolvedDefinedStrokeWidth = if (definedStrokeWidth != Dp.Hairline) {
definedStrokeWidth.toPx()
} else {
1.0f
}
val strokeWidth = min(resolvedDefinedStrokeWidth, ceil(canvasMinDimension / 2))
val borderTopLeft: Offset
val borderSize: Size
val style: DrawStyle
if (strokeWidth * 2 <= canvasMinDimension) {
borderTopLeft = Offset(
x = strokeWidth / 2,
y = strokeWidth / 2
)
borderSize = Size(
width = canvasSize.width - strokeWidth,
height = canvasSize.height - strokeWidth
)
style = Stroke(
width = strokeWidth,
pathEffect = effect
)
} else {
borderTopLeft = Offset.Zero
borderSize = canvasSize
style = Fill
}
drawRect(
brush = brush,
topLeft = borderTopLeft,
size = borderSize,
style = style
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment