mirror of
https://github.com/JakeWharton/mosaic.git
synced 2025-11-03 05:46:10 +08:00
Add background modifier, fix multimodifier positioning (#180)
This commit is contained in:
@ -96,4 +96,20 @@ internal class TextPixel(var value: String) {
|
||||
var style = None
|
||||
|
||||
constructor(char: Char) : this(char.toString())
|
||||
|
||||
override fun toString() = buildString {
|
||||
append("TextPixel(\"")
|
||||
append(value)
|
||||
append("\"")
|
||||
if (background != null) {
|
||||
append(" bg=")
|
||||
append(background)
|
||||
}
|
||||
if (foreground != null) {
|
||||
append(" fg=")
|
||||
append(foreground)
|
||||
}
|
||||
// TODO style
|
||||
append(')')
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
package com.jakewharton.mosaic.layout
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.jakewharton.mosaic.modifier.Modifier
|
||||
import com.jakewharton.mosaic.ui.Color
|
||||
|
||||
@Stable
|
||||
public fun Modifier.background(
|
||||
color: Color,
|
||||
): Modifier = this.then(
|
||||
BackgroundModifier(
|
||||
color = color,
|
||||
)
|
||||
)
|
||||
|
||||
private class BackgroundModifier(
|
||||
private val color: Color,
|
||||
) : DrawModifier {
|
||||
override fun ContentDrawScope.draw() {
|
||||
drawRect(color)
|
||||
drawContent()
|
||||
}
|
||||
|
||||
override fun toString() = "Background($color)"
|
||||
}
|
||||
@ -20,6 +20,8 @@ import com.jakewharton.mosaic.modifier.Modifier
|
||||
|
||||
public interface DrawModifier : Modifier.Element {
|
||||
public fun ContentDrawScope.draw()
|
||||
// Force subclasses to add a debugging implementation.
|
||||
override fun toString(): String
|
||||
}
|
||||
|
||||
public fun Modifier.drawBehind(
|
||||
|
||||
@ -21,7 +21,18 @@ import com.jakewharton.mosaic.ui.Color
|
||||
import com.jakewharton.mosaic.ui.TextStyle
|
||||
|
||||
public interface DrawScope {
|
||||
public fun write(
|
||||
public val width: Int
|
||||
public val height: Int
|
||||
|
||||
public fun drawRect(
|
||||
color: Color,
|
||||
row: Int = 0,
|
||||
column: Int = 0,
|
||||
width: Int = this.width,
|
||||
height: Int = this.height,
|
||||
)
|
||||
|
||||
public fun drawText(
|
||||
row: Int,
|
||||
column: Int,
|
||||
string: String,
|
||||
@ -33,8 +44,24 @@ public interface DrawScope {
|
||||
|
||||
internal open class TextCanvasDrawScope(
|
||||
private val canvas: TextCanvas,
|
||||
override val width: Int,
|
||||
override val height: Int,
|
||||
): DrawScope {
|
||||
override fun write(
|
||||
override fun drawRect(
|
||||
color: Color,
|
||||
row: Int,
|
||||
column: Int,
|
||||
width: Int,
|
||||
height: Int,
|
||||
) {
|
||||
for (y in row until row + height) {
|
||||
for (x in column until column + width) {
|
||||
canvas[y, x].background = color
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun drawText(
|
||||
row: Int,
|
||||
column: Int,
|
||||
string: String,
|
||||
|
||||
@ -46,6 +46,9 @@ public interface LayoutModifier : Modifier.Element {
|
||||
public fun MeasureScope.measure(
|
||||
measurable: Measurable,
|
||||
): MeasureResult
|
||||
|
||||
// Force subclasses to add a debugging implementation.
|
||||
override fun toString(): String
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -70,54 +70,18 @@ internal class MosaicNode(
|
||||
var debugPolicy: DebugPolicy,
|
||||
val onStaticDraw: (() -> Unit)?,
|
||||
) : Measurable {
|
||||
private val isStatic = onStaticDraw != null
|
||||
val isStatic get() = onStaticDraw != null
|
||||
val children = mutableListOf<MosaicNode>()
|
||||
|
||||
private val bottomLayer: MosaicNodeLayer = object : AbstractMosaicNodeLayer(null, isStatic) {
|
||||
override fun doMeasure(): MeasureResult {
|
||||
return measurePolicy.run { measure(children) }
|
||||
}
|
||||
|
||||
override fun drawTo(canvas: TextCanvas) {
|
||||
for (child in children) {
|
||||
if (child.width != 0 && child.height != 0) {
|
||||
child.topLayer.drawTo(canvas)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var topLayer = bottomLayer
|
||||
private val bottomLayer: MosaicNodeLayer = BottomLayer(this)
|
||||
var topLayer: MosaicNodeLayer = bottomLayer
|
||||
|
||||
var modifiers: Modifier = Modifier
|
||||
set(value) {
|
||||
topLayer = value.foldOut(bottomLayer) { element, lowerLayer ->
|
||||
when (element) {
|
||||
is LayoutModifier -> {
|
||||
object : AbstractMosaicNodeLayer(lowerLayer, false) {
|
||||
override fun doMeasure(): MeasureResult {
|
||||
return element.run { measure(lowerLayer) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is DrawModifier -> {
|
||||
object : AbstractMosaicNodeLayer(lowerLayer, false) {
|
||||
override fun drawTo(canvas: TextCanvas) {
|
||||
canvas.translationX += x
|
||||
canvas.translationY += y
|
||||
val scope = object : TextCanvasDrawScope(canvas), ContentDrawScope {
|
||||
override fun drawContent() {
|
||||
lowerLayer.drawTo(canvas)
|
||||
}
|
||||
}
|
||||
element.run { scope.draw() }
|
||||
canvas.translationX -= x
|
||||
canvas.translationY -= y
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is LayoutModifier -> LayoutLayer(element, lowerLayer)
|
||||
is DrawModifier -> DrawLayer(element, lowerLayer)
|
||||
else -> lowerLayer
|
||||
}
|
||||
}
|
||||
@ -162,3 +126,48 @@ internal class MosaicNode(
|
||||
|
||||
override fun toString() = debugPolicy.run { renderDebug() }
|
||||
}
|
||||
|
||||
private class BottomLayer(
|
||||
private val node: MosaicNode,
|
||||
) : AbstractMosaicNodeLayer(null, node.isStatic) {
|
||||
override fun doMeasure(): MeasureResult {
|
||||
return node.measurePolicy.run { measure(node.children) }
|
||||
}
|
||||
|
||||
override fun drawTo(canvas: TextCanvas) {
|
||||
for (child in node.children) {
|
||||
if (child.width != 0 && child.height != 0) {
|
||||
child.topLayer.drawTo(canvas)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class LayoutLayer(
|
||||
private val element: LayoutModifier,
|
||||
private val lowerLayer: MosaicNodeLayer,
|
||||
) : AbstractMosaicNodeLayer(lowerLayer, false) {
|
||||
override fun doMeasure(): MeasureResult {
|
||||
return element.run { measure(lowerLayer) }
|
||||
}
|
||||
}
|
||||
|
||||
private class DrawLayer(
|
||||
private val element: DrawModifier,
|
||||
private val lowerLayer: MosaicNodeLayer,
|
||||
) : AbstractMosaicNodeLayer(lowerLayer, false) {
|
||||
override fun drawTo(canvas: TextCanvas) {
|
||||
val oldX = canvas.translationX
|
||||
val oldY = canvas.translationY
|
||||
canvas.translationX = x
|
||||
canvas.translationY = y
|
||||
val scope = object : TextCanvasDrawScope(canvas, width, height), ContentDrawScope {
|
||||
override fun drawContent() {
|
||||
lowerLayer.drawTo(canvas)
|
||||
}
|
||||
}
|
||||
element.run { scope.draw() }
|
||||
canvas.translationX = oldX
|
||||
canvas.translationY = oldY
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,8 @@ public class Color private constructor(
|
||||
internal val fg: Int,
|
||||
internal val bg: Int,
|
||||
) {
|
||||
override fun toString(): String = "Color($fg)"
|
||||
|
||||
public companion object {
|
||||
@Stable
|
||||
public val Black: Color = Color(30, 40)
|
||||
|
||||
@ -12,6 +12,7 @@ import kotlin.jvm.JvmName
|
||||
@Composable
|
||||
public fun Text(
|
||||
value: String,
|
||||
modifier: Modifier = Modifier,
|
||||
color: Color? = null,
|
||||
background: Color? = null,
|
||||
style: TextStyle? = null,
|
||||
@ -27,9 +28,9 @@ public fun Text(
|
||||
layout.measure()
|
||||
layout(layout.width, layout.height)
|
||||
},
|
||||
modifiers = Modifier.drawBehind {
|
||||
modifiers = modifier.drawBehind {
|
||||
layout.lines.forEachIndexed { row, line ->
|
||||
write(row, 0, line, color, background, style)
|
||||
drawText(row, 0, line, color, background, style)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@ -103,7 +103,7 @@ class LayoutTest {
|
||||
Text("..")
|
||||
Layout(modifiers = Modifier.drawBehind {
|
||||
repeat(4) { row ->
|
||||
write(row, 0, "XXXX")
|
||||
drawText(row, 0, "XXXX")
|
||||
}
|
||||
}) {
|
||||
layout(2, 2)
|
||||
|
||||
@ -10,6 +10,9 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshots.Snapshot
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
import com.jakewharton.mosaic.layout.background
|
||||
import com.jakewharton.mosaic.layout.padding
|
||||
import com.jakewharton.mosaic.modifier.Modifier
|
||||
import com.jakewharton.mosaic.runMosaicBlocking
|
||||
import com.jakewharton.mosaic.ui.Color.Companion.Black
|
||||
import com.jakewharton.mosaic.ui.Color.Companion.BrightBlack
|
||||
@ -96,13 +99,18 @@ fun TestRow(test: Test) {
|
||||
Pass -> Green
|
||||
Fail -> Red
|
||||
}
|
||||
// TODO use start/end padding of 1 to achieve spacing
|
||||
val state = when (test.state) {
|
||||
Running -> " RUNS "
|
||||
Pass -> " PASS "
|
||||
Fail -> " FAIL "
|
||||
Running -> "RUNS"
|
||||
Pass -> "PASS"
|
||||
Fail -> "FAIL"
|
||||
}
|
||||
Text(state, color = Black, background = bg)
|
||||
Text(
|
||||
state,
|
||||
modifier = Modifier
|
||||
.background(bg)
|
||||
.padding(horizontal = 1),
|
||||
color = Black
|
||||
)
|
||||
|
||||
val dir = test.path.substringBeforeLast('/')
|
||||
val name = test.path.substringAfterLast('/')
|
||||
|
||||
Reference in New Issue
Block a user