Hi I'm trying to create this background, do you kn...
# compose-desktop
s
Hi I'm trying to create this background, do you know how I create a surface for each part my code:
Copy code
Surface(modifier = Modifier.fillMaxSize()) {
    Box {
        Canvas(Modifier.fillMaxSize()) {
            val midWidth = size.width / 2
            val midSpace = 200f * density
            val leftPoint = midWidth - (midSpace / 2)
            val rightPoint = midWidth + (midSpace / 2)
            val leftPath = Path().apply {
                moveTo(size.width, 0f)
                lineTo(rightPoint, 0f)
                lineTo(leftPoint, size.height)
                lineTo(size.width, size.height)
                close()
            }
            val rightPath = Path().apply {
                moveTo(0f, 0f)
                lineTo(rightPoint, 0f)
                lineTo(leftPoint, size.height)
                lineTo(0f, size.height)
                close()
            }
            drawPath(leftPath, Color.Red)
            drawPath(rightPath, Color.Black)
        }
        val text = "ABILITY TO"
        Text(
            text = text,
            textAlign = TextAlign.Center,
            modifier = Modifier.align(Alignment.Center),
            style = MaterialTheme.typography.h1,
            fontWeight = FontWeight.Bold,
            color = Color.Black
        )

        Text(
            text = text,
            textAlign = TextAlign.Center,
            modifier = Modifier.align(Alignment.Center),
            style = MaterialTheme.typography.h1,
            fontWeight = FontWeight.Bold,
            color = Color.Red,

        )
    }
}
😯 2
o
not sure if fully understand the question. You want to change to color of letters in text using some non-trivial algorithm?
n
@Shalaga44 I cc'ed you on another thread with a similar question. Basically we create a buffer using Modifier.graphicsLayer, draw the wrapped content (in this case the text) and and draw the regions with
BlendMode.Xor
s
@Nader Jawad Can you link that thread here? I’d like to see how to do that.
a
I’ve answered in another thread. Link here.
In this case the background is always black (I suppose) so there’s no need to use
graphicsLayer
though.
n
I think the layer is still necessary in order to mask the pixels. Here's an alternative solution to get a similar effect that leverages gradients to handle the diagonal colors. The background shape can also be achieved using clipping + rotation, or paths (although I prefer the initial or second solutions over path usage)
Copy code
/**
 * Create a linear gradient that begins from the top left to the bottom right of the drawing area.
 * Use manual stops from 0 to 0.5 and 0.5 to 1.0 to create a solid separation instead of
 * a gradual color ramp between the start and end colors
 * Not specifying start/end offsets as we want to size the gradient implicitly with the
 * bounds of the composable
 */
fun createDiagonalBrush(startColor: Color, endColor: Color): Brush =
    Brush.linearGradient(
        0f to startColor,
        0.5f to startColor,
        0.5f to endColor,
        1.0f to endColor
    )

@Composable
fun MaskedText() {
    // Mask text color. Can be anything just make sure it's not transparent
    val textColor = Color.White
    val fontSize = 40.sp
    // Create alternating gradients for the background and the text respectively
    val backgroundGradient = createDiagonalBrush(Color.Black, Color.Red)
    val textGradient = createDiagonalBrush(Color.Red, Color.Black)
    Column(modifier = Modifier.fillMaxSize()
        .background(backgroundGradient)
        // Create a graphics layer with slight alpha to render into an offscreen buffer
        // Note future releases of compose will have a manual userCompositingLayer field instead
        // of using alpha as a side effect for this behavior
        // This offscreen buffer is necessary to leverage blending modes only against
        // the pixels drawn by the text composable and ignore drawing in transparent regions
        .graphicsLayer(alpha = 0.99f)
        .drawWithContent {
            // Draw the text content
            drawContent()
            // Draw a rectangle to cover only the pixels drawn by the Text composable
            // by using the src in blend mode
            drawRect(textGradient, blendMode = BlendMode.SrcIn)
    }, verticalArrangement = Arrangement.Center) {
        Text(
            modifier = Modifier.fillMaxWidth().padding(start = 20.dp),
            textAlign = TextAlign.Left,
            text = "THE",
            color = textColor,
            fontSize = fontSize
        )

        Text(
            modifier = Modifier.fillMaxWidth(),
            textAlign = TextAlign.Center,
            text = "ABILITY",
            color = textColor,
            fontSize = fontSize
        )

        Text(
            modifier = Modifier.fillMaxWidth().padding(end = 20.dp),
            textAlign = TextAlign.Right,
            text = "TO CHANGE",
            color = Color.White,
            fontSize = fontSize
        )
    }
}
Which gets you something pretty close. The font sizes/font/diagonal probably need to be tweaked. To get more control over the sizing of the gradient, you can use a
ShaderBrush
to return an instance of
linearGradientShader
, or you can replace the
Modifier.background
usages to a DrawModifier and create the gradient with the actual size of the composable.
a
Since the background is black, I’m using
drawRect(Color.Unspecified, blendMode = BlendMode.Clear)
to clear the layer before drawing the text, which necessarily makes it black. This is the reason I said
graphicsLayer
is not necessary.
n
There maybe other ways. I found that I still needed the transparent pixels for other blending modes to operate as expected. Might be good to share your solution with other folks too.
The difference in this case vs the others was that we just wanted to flip the colors using BlendMode.Xor however here we need to mask the text with the inverse gradient. The blendmodes here operate on all pixels so the extra layer was used here.
a
I've posted my solution before.