I'm trying to draw an outline to a shape, but even...
# compose
a
I'm trying to draw an outline to a shape, but even though I'm using the same shape in both border and background, i get this weird spacing between. code in 🧵
Copy code
fun Modifier.outline(width: Dp, color: Color, shape: Shape): Modifier {
    return border(width, color, shape).padding(width)
}
And I use it like this:
Copy code
Modifier
    .padding(16.dp)
    .outline(2.dp, ComposeTheme.colors[outline].copy(0.5f), ComposeTheme.shapes[medium])
    .background(ComposeTheme.colors[dialog], shape = ComposeTheme.shapes[medium])
the border + padding is obviously a naive way to do this but im trying to figure out why that spacing happens is the roundness affected by the sizing of the shape rendered maybe?
ok took me a while to figure why this is happening the outside of the border does not have the same radius as the inner of the border. so it's practically 2 different shapes. this is more obvious here, using
RoundedCornerShape(12.dp)
Fixed it with visual h4x
Copy code
@Composable
fun Modifier.outline(thickness: Dp, color: Color, shape: Shape): Modifier {
    return this.then(
        Modifier
            .background(color, shape = shape)
            .padding(thickness)
            .clip(shape)
    )
}
👍🏻 1
👍 1
o
there is a well known pattern for this in web dev, outer radius = inner radius + padding
a
doesnt css support outlines out of the box?
o
I was more speaking about broader use case to share info, it could be useful formula outside this specific use case
a
Someone suggested on X to apply a different inner shape to make it look nicer, and it did
Hacky impl:
Copy code
@Composable
fun Modifier.outline(width: Dp, color: Color, shape: Shape): Modifier {
    val outer = shape as RoundedCornerShape

    val density = LocalDensity.current

    val innerShape = object : CornerBasedShape(
        topStart = outer.topStart,
        topEnd = outer.topEnd,
        bottomStart = outer.bottomStart,
        bottomEnd = outer.bottomEnd
    ) {
        override fun createOutline(
            size: Size,
            topStart: Float,
            topEnd: Float,
            bottomEnd: Float,
            bottomStart: Float,
            layoutDirection: LayoutDirection,
        ): Outline {
            return if (topStart + topEnd + bottomEnd + bottomStart == 0.0f) {
                Outline.Rectangle(size.toRect())
            } else {
                val thickness = with(density) { width.toPx() }
                Outline.Rounded(
                    RoundRect(
                        rect = size.toRect(),
                        topLeft = CornerRadius((if (layoutDirection == Ltr) topStart else topEnd) - thickness),
                        topRight = CornerRadius((if (layoutDirection == Ltr) topEnd else topStart) - thickness),
                        bottomRight =
                            CornerRadius((if (layoutDirection == Ltr) bottomEnd else bottomStart) - thickness),
                        bottomLeft =
                            CornerRadius((if (layoutDirection == Ltr) bottomStart else bottomEnd) - thickness)
                    )
                )
            }
        }

        override fun copy(
            topStart: CornerSize,
            topEnd: CornerSize,
            bottomEnd: CornerSize,
            bottomStart: CornerSize,
        ): CornerBasedShape {
            return shape.copy(topStart, topEnd, bottomEnd, bottomStart)
        }
    }

    return background(color = color, shape = shape)
        .padding(width)
        .clip(innerShape)
}
a
Better yet, opened a feature request to have
outline()
in the framework: You know the drill: https://issuetracker.google.com/issues/423073237
r
Why can't you use border()?
a
because i would have to use border + padding so that I don't draw on top of in front of my object. if the border is transparent, the end result is wrong
r
I thought we added a border style to choose inside/outside/centered but maybe I'm misremembering
1
a
Would love that. Not currently in CMP (1.8.1) it seems
s
I thought we added a border style to choose inside/outside/centered but maybe I'm misremembering
This would be amazing to have 👏
s
https://kotlinlang.slack.com/archives/CJLTWPH7S/p1749289492052149?thread_ts=1749213342.374719&cid=CJLTWPH7S You can refer to the Layer method I mentioned above. The Layer method is used to implement the basic implementation of Fluent Design's Toggle Button. It contains two states: background when selected and background + outline border when unselected. My LayerShapeHelper is used to handle the calculation of inner corner radius in case of background + outline border