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
|
var style = None
|
||||||
|
|
||||||
constructor(char: Char) : this(char.toString())
|
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 interface DrawModifier : Modifier.Element {
|
||||||
public fun ContentDrawScope.draw()
|
public fun ContentDrawScope.draw()
|
||||||
|
// Force subclasses to add a debugging implementation.
|
||||||
|
override fun toString(): String
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun Modifier.drawBehind(
|
public fun Modifier.drawBehind(
|
||||||
|
|||||||
@ -21,7 +21,18 @@ import com.jakewharton.mosaic.ui.Color
|
|||||||
import com.jakewharton.mosaic.ui.TextStyle
|
import com.jakewharton.mosaic.ui.TextStyle
|
||||||
|
|
||||||
public interface DrawScope {
|
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,
|
row: Int,
|
||||||
column: Int,
|
column: Int,
|
||||||
string: String,
|
string: String,
|
||||||
@ -33,8 +44,24 @@ public interface DrawScope {
|
|||||||
|
|
||||||
internal open class TextCanvasDrawScope(
|
internal open class TextCanvasDrawScope(
|
||||||
private val canvas: TextCanvas,
|
private val canvas: TextCanvas,
|
||||||
|
override val width: Int,
|
||||||
|
override val height: Int,
|
||||||
): DrawScope {
|
): 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,
|
row: Int,
|
||||||
column: Int,
|
column: Int,
|
||||||
string: String,
|
string: String,
|
||||||
|
|||||||
@ -46,6 +46,9 @@ public interface LayoutModifier : Modifier.Element {
|
|||||||
public fun MeasureScope.measure(
|
public fun MeasureScope.measure(
|
||||||
measurable: Measurable,
|
measurable: Measurable,
|
||||||
): MeasureResult
|
): MeasureResult
|
||||||
|
|
||||||
|
// Force subclasses to add a debugging implementation.
|
||||||
|
override fun toString(): String
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -70,54 +70,18 @@ internal class MosaicNode(
|
|||||||
var debugPolicy: DebugPolicy,
|
var debugPolicy: DebugPolicy,
|
||||||
val onStaticDraw: (() -> Unit)?,
|
val onStaticDraw: (() -> Unit)?,
|
||||||
) : Measurable {
|
) : Measurable {
|
||||||
private val isStatic = onStaticDraw != null
|
val isStatic get() = onStaticDraw != null
|
||||||
val children = mutableListOf<MosaicNode>()
|
val children = mutableListOf<MosaicNode>()
|
||||||
|
|
||||||
private val bottomLayer: MosaicNodeLayer = object : AbstractMosaicNodeLayer(null, isStatic) {
|
private val bottomLayer: MosaicNodeLayer = BottomLayer(this)
|
||||||
override fun doMeasure(): MeasureResult {
|
var topLayer: MosaicNodeLayer = bottomLayer
|
||||||
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
|
|
||||||
|
|
||||||
var modifiers: Modifier = Modifier
|
var modifiers: Modifier = Modifier
|
||||||
set(value) {
|
set(value) {
|
||||||
topLayer = value.foldOut(bottomLayer) { element, lowerLayer ->
|
topLayer = value.foldOut(bottomLayer) { element, lowerLayer ->
|
||||||
when (element) {
|
when (element) {
|
||||||
is LayoutModifier -> {
|
is LayoutModifier -> LayoutLayer(element, lowerLayer)
|
||||||
object : AbstractMosaicNodeLayer(lowerLayer, false) {
|
is DrawModifier -> DrawLayer(element, lowerLayer)
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> lowerLayer
|
else -> lowerLayer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -162,3 +126,48 @@ internal class MosaicNode(
|
|||||||
|
|
||||||
override fun toString() = debugPolicy.run { renderDebug() }
|
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 fg: Int,
|
||||||
internal val bg: Int,
|
internal val bg: Int,
|
||||||
) {
|
) {
|
||||||
|
override fun toString(): String = "Color($fg)"
|
||||||
|
|
||||||
public companion object {
|
public companion object {
|
||||||
@Stable
|
@Stable
|
||||||
public val Black: Color = Color(30, 40)
|
public val Black: Color = Color(30, 40)
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import kotlin.jvm.JvmName
|
|||||||
@Composable
|
@Composable
|
||||||
public fun Text(
|
public fun Text(
|
||||||
value: String,
|
value: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
color: Color? = null,
|
color: Color? = null,
|
||||||
background: Color? = null,
|
background: Color? = null,
|
||||||
style: TextStyle? = null,
|
style: TextStyle? = null,
|
||||||
@ -27,9 +28,9 @@ public fun Text(
|
|||||||
layout.measure()
|
layout.measure()
|
||||||
layout(layout.width, layout.height)
|
layout(layout.width, layout.height)
|
||||||
},
|
},
|
||||||
modifiers = Modifier.drawBehind {
|
modifiers = modifier.drawBehind {
|
||||||
layout.lines.forEachIndexed { row, line ->
|
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("..")
|
Text("..")
|
||||||
Layout(modifiers = Modifier.drawBehind {
|
Layout(modifiers = Modifier.drawBehind {
|
||||||
repeat(4) { row ->
|
repeat(4) { row ->
|
||||||
write(row, 0, "XXXX")
|
drawText(row, 0, "XXXX")
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
layout(2, 2)
|
layout(2, 2)
|
||||||
|
|||||||
@ -10,6 +10,9 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.runtime.snapshots.Snapshot
|
import androidx.compose.runtime.snapshots.Snapshot
|
||||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
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.runMosaicBlocking
|
||||||
import com.jakewharton.mosaic.ui.Color.Companion.Black
|
import com.jakewharton.mosaic.ui.Color.Companion.Black
|
||||||
import com.jakewharton.mosaic.ui.Color.Companion.BrightBlack
|
import com.jakewharton.mosaic.ui.Color.Companion.BrightBlack
|
||||||
@ -96,13 +99,18 @@ fun TestRow(test: Test) {
|
|||||||
Pass -> Green
|
Pass -> Green
|
||||||
Fail -> Red
|
Fail -> Red
|
||||||
}
|
}
|
||||||
// TODO use start/end padding of 1 to achieve spacing
|
|
||||||
val state = when (test.state) {
|
val state = when (test.state) {
|
||||||
Running -> " RUNS "
|
Running -> "RUNS"
|
||||||
Pass -> " PASS "
|
Pass -> "PASS"
|
||||||
Fail -> " FAIL "
|
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 dir = test.path.substringBeforeLast('/')
|
||||||
val name = test.path.substringAfterLast('/')
|
val name = test.path.substringAfterLast('/')
|
||||||
|
|||||||
Reference in New Issue
Block a user