Skip to content

Commit

Permalink
Support true color palette (#356)
Browse files Browse the repository at this point in the history
Also update rrtop sample with several color palettes
  • Loading branch information
EpicDima committed May 16, 2024
1 parent eb76d4c commit 6efdb15
Show file tree
Hide file tree
Showing 27 changed files with 1,307 additions and 250 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
New:
- Add `linuxArm64` target.
- Add `rrtop` sample.
- Support true color palette.

Changed:
- Disable klib signature clash checks for JS compilations. These occasionally occur as a result of Compose compiler behavior, and are safe to disable (the first-party JetBrains Compose Gradle plugin also disables them).
- Remove `Terminal$Size` and use `IntSize` instead in `Terminal#size` for optimization purposes.
- Remove Color.Bright* constants. Use Color to create the desired color.

Fixed:
- Use CRLF line endings to fix rendering when a terminal is in raw mode.
Expand Down
69 changes: 36 additions & 33 deletions mosaic-runtime/api/mosaic-runtime.api
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public final class com/jakewharton/mosaic/layout/AspectRatioKt {
}

public final class com/jakewharton/mosaic/layout/BackgroundKt {
public static final fun background (Lcom/jakewharton/mosaic/modifier/Modifier;Lcom/jakewharton/mosaic/ui/Color;)Lcom/jakewharton/mosaic/modifier/Modifier;
public static final fun background-fCupJr8 (Lcom/jakewharton/mosaic/modifier/Modifier;I)Lcom/jakewharton/mosaic/modifier/Modifier;
}

public abstract interface class com/jakewharton/mosaic/layout/ContentDrawScope : com/jakewharton/mosaic/layout/DrawScope {
Expand All @@ -47,12 +47,12 @@ public final class com/jakewharton/mosaic/layout/DrawModifierKt {
}

public abstract interface class com/jakewharton/mosaic/layout/DrawScope {
public abstract fun drawRect (Lcom/jakewharton/mosaic/ui/Color;IIII)V
public static synthetic fun drawRect$default (Lcom/jakewharton/mosaic/layout/DrawScope;Lcom/jakewharton/mosaic/ui/Color;IIIIILjava/lang/Object;)V
public abstract fun drawText (IILcom/jakewharton/mosaic/text/AnnotatedString;Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/TextStyle;)V
public abstract fun drawText (IILjava/lang/String;Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/TextStyle;)V
public static synthetic fun drawText$default (Lcom/jakewharton/mosaic/layout/DrawScope;IILcom/jakewharton/mosaic/text/AnnotatedString;Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/TextStyle;ILjava/lang/Object;)V
public static synthetic fun drawText$default (Lcom/jakewharton/mosaic/layout/DrawScope;IILjava/lang/String;Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/TextStyle;ILjava/lang/Object;)V
public abstract fun drawRect-e5kUBSE (IIIII)V
public static synthetic fun drawRect-e5kUBSE$default (Lcom/jakewharton/mosaic/layout/DrawScope;IIIIIILjava/lang/Object;)V
public abstract fun drawText-HJtvjdw (IILcom/jakewharton/mosaic/text/AnnotatedString;Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/TextStyle;)V
public abstract fun drawText-HJtvjdw (IILjava/lang/String;Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/TextStyle;)V
public static synthetic fun drawText-HJtvjdw$default (Lcom/jakewharton/mosaic/layout/DrawScope;IILcom/jakewharton/mosaic/text/AnnotatedString;Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/TextStyle;ILjava/lang/Object;)V
public static synthetic fun drawText-HJtvjdw$default (Lcom/jakewharton/mosaic/layout/DrawScope;IILjava/lang/String;Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/TextStyle;ILjava/lang/Object;)V
public abstract fun getHeight ()I
public abstract fun getWidth ()I
}
Expand Down Expand Up @@ -268,14 +268,13 @@ public final class com/jakewharton/mosaic/text/AnnotatedStringKt {

public final class com/jakewharton/mosaic/text/SpanStyle {
public static final field $stable I
public fun <init> ()V
public fun <init> (Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/TextStyle;Lcom/jakewharton/mosaic/ui/Color;)V
public synthetic fun <init> (Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/TextStyle;Lcom/jakewharton/mosaic/ui/Color;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun copy (Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/TextStyle;Lcom/jakewharton/mosaic/ui/Color;)Lcom/jakewharton/mosaic/text/SpanStyle;
public static synthetic fun copy$default (Lcom/jakewharton/mosaic/text/SpanStyle;Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/TextStyle;Lcom/jakewharton/mosaic/ui/Color;ILjava/lang/Object;)Lcom/jakewharton/mosaic/text/SpanStyle;
public synthetic fun <init> (Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/TextStyle;Lcom/jakewharton/mosaic/ui/Color;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun copy-0C4kZ_4 (Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/TextStyle;Lcom/jakewharton/mosaic/ui/Color;)Lcom/jakewharton/mosaic/text/SpanStyle;
public static synthetic fun copy-0C4kZ_4$default (Lcom/jakewharton/mosaic/text/SpanStyle;Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/TextStyle;Lcom/jakewharton/mosaic/ui/Color;ILjava/lang/Object;)Lcom/jakewharton/mosaic/text/SpanStyle;
public fun equals (Ljava/lang/Object;)Z
public final fun getBackground ()Lcom/jakewharton/mosaic/ui/Color;
public final fun getColor ()Lcom/jakewharton/mosaic/ui/Color;
public final fun getBackground-xbxrsts ()Lcom/jakewharton/mosaic/ui/Color;
public final fun getColor-xbxrsts ()Lcom/jakewharton/mosaic/ui/Color;
public final fun getTextStyle ()Lcom/jakewharton/mosaic/ui/TextStyle;
public fun hashCode ()I
public final fun merge (Lcom/jakewharton/mosaic/text/SpanStyle;)Lcom/jakewharton/mosaic/text/SpanStyle;
Expand Down Expand Up @@ -404,28 +403,32 @@ public abstract interface class com/jakewharton/mosaic/ui/BoxScope {
}

public final class com/jakewharton/mosaic/ui/Color {
public static final field $stable I
public static final field Companion Lcom/jakewharton/mosaic/ui/Color$Companion;
public static final synthetic fun box-impl (I)Lcom/jakewharton/mosaic/ui/Color;
public fun equals (Ljava/lang/Object;)Z
public static fun equals-impl (ILjava/lang/Object;)Z
public static final fun equals-impl0 (II)Z
public fun hashCode ()I
public static fun hashCode-impl (I)I
public fun toString ()Ljava/lang/String;
public static fun toString-impl (I)Ljava/lang/String;
public final synthetic fun unbox-impl ()I
}

public final class com/jakewharton/mosaic/ui/Color$Companion {
public final fun getBlack ()Lcom/jakewharton/mosaic/ui/Color;
public final fun getBlue ()Lcom/jakewharton/mosaic/ui/Color;
public final fun getBrightBlack ()Lcom/jakewharton/mosaic/ui/Color;
public final fun getBrightBlue ()Lcom/jakewharton/mosaic/ui/Color;
public final fun getBrightCyan ()Lcom/jakewharton/mosaic/ui/Color;
public final fun getBrightGreen ()Lcom/jakewharton/mosaic/ui/Color;
public final fun getBrightMagenta ()Lcom/jakewharton/mosaic/ui/Color;
public final fun getBrightRed ()Lcom/jakewharton/mosaic/ui/Color;
public final fun getBrightWhite ()Lcom/jakewharton/mosaic/ui/Color;
public final fun getBrightYellow ()Lcom/jakewharton/mosaic/ui/Color;
public final fun getCyan ()Lcom/jakewharton/mosaic/ui/Color;
public final fun getGreen ()Lcom/jakewharton/mosaic/ui/Color;
public final fun getMagenta ()Lcom/jakewharton/mosaic/ui/Color;
public final fun getRed ()Lcom/jakewharton/mosaic/ui/Color;
public final fun getWhite ()Lcom/jakewharton/mosaic/ui/Color;
public final fun getYellow ()Lcom/jakewharton/mosaic/ui/Color;
public final fun getBlack-OrXnJU4 ()I
public final fun getBlue-OrXnJU4 ()I
public final fun getCyan-OrXnJU4 ()I
public final fun getGreen-OrXnJU4 ()I
public final fun getMagenta-OrXnJU4 ()I
public final fun getRed-OrXnJU4 ()I
public final fun getWhite-OrXnJU4 ()I
public final fun getYellow-OrXnJU4 ()I
}

public final class com/jakewharton/mosaic/ui/ColorKt {
public static final fun Color (FFF)I
public static final fun Color (III)I
}

public final class com/jakewharton/mosaic/ui/Column {
Expand Down Expand Up @@ -460,7 +463,7 @@ public final class com/jakewharton/mosaic/ui/ComposableSingletons$SpacerKt {
}

public final class com/jakewharton/mosaic/ui/FillerKt {
public static final fun Filler (CLcom/jakewharton/mosaic/modifier/Modifier;Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/TextStyle;Landroidx/compose/runtime/Composer;II)V
public static final fun Filler-g8kd68k (CLcom/jakewharton/mosaic/modifier/Modifier;Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/TextStyle;Landroidx/compose/runtime/Composer;II)V
}

public final class com/jakewharton/mosaic/ui/Layout {
Expand Down Expand Up @@ -492,8 +495,8 @@ public final class com/jakewharton/mosaic/ui/Static {
}

public final class com/jakewharton/mosaic/ui/Text {
public static final fun Text (Lcom/jakewharton/mosaic/text/AnnotatedString;Lcom/jakewharton/mosaic/modifier/Modifier;Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/TextStyle;Landroidx/compose/runtime/Composer;II)V
public static final fun Text (Ljava/lang/String;Lcom/jakewharton/mosaic/modifier/Modifier;Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/TextStyle;Landroidx/compose/runtime/Composer;II)V
public static final fun Text-g8kd68k (Lcom/jakewharton/mosaic/text/AnnotatedString;Lcom/jakewharton/mosaic/modifier/Modifier;Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/TextStyle;Landroidx/compose/runtime/Composer;II)V
public static final fun Text-g8kd68k (Ljava/lang/String;Lcom/jakewharton/mosaic/modifier/Modifier;Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/Color;Lcom/jakewharton/mosaic/ui/TextStyle;Landroidx/compose/runtime/Composer;II)V
}

public final class com/jakewharton/mosaic/ui/TextStyle {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,71 @@
package com.jakewharton.mosaic

internal const val clearLine = "\u001B[K"
internal const val cursorUp = "\u001B[F"
import com.github.ajalt.mordant.rendering.AnsiLevel as MordantAnsiLevel
import com.jakewharton.mosaic.ui.AnsiLevel
import com.jakewharton.mosaic.ui.Color
import kotlin.math.roundToInt

private const val ESC = "\u001B"
internal const val CSI = "$ESC["

internal const val ansiReset = "${CSI}0"
internal const val clearLine = "${CSI}K"
internal const val cursorUp = "${CSI}F"

internal const val ansiSeparator = ";"
internal const val ansiClosingCharacter = "m"

internal const val ansiFgColorSelector = 38
internal const val ansiFgColorReset = 39
internal const val ansiFgColorOffset = 0

internal const val ansiBgColorSelector = 48
internal const val ansiBgColorReset = 49
internal const val ansiBgColorOffset = 10

internal const val ansiSelectorColor256 = 5
internal const val ansiSelectorColorRgb = 2

internal fun MordantAnsiLevel.toMosaicAnsiLevel(): AnsiLevel {
return when (this) {
MordantAnsiLevel.NONE -> AnsiLevel.NONE
MordantAnsiLevel.ANSI16 -> AnsiLevel.ANSI16
MordantAnsiLevel.ANSI256 -> AnsiLevel.ANSI256
MordantAnsiLevel.TRUECOLOR -> AnsiLevel.TRUECOLOR
}
}

// simpler version without full conversion to HSV
// https://github.com/ajalt/colormath/blob/4a0cc9796c743cb4965407204ee63b40aaf22fca/colormath/src/commonMain/kotlin/com/github/ajalt/colormath/model/RGB.kt#L301
internal fun Color.toAnsi16Code(): Int {
val value = (maxOf(redFloat, greenFloat, blueFloat) * 100).roundToInt()
if (value == 30) {
return 30
}
val v = value / 50
val ansiCode = 30 + (
(blueFloat.roundToInt() * 4)
or (greenFloat.roundToInt() * 2)
or redFloat.roundToInt()
)
return if (v == 2) ansiCode + 60 else ansiCode
}

// https://github.com/ajalt/colormath/blob/4a0cc9796c743cb4965407204ee63b40aaf22fca/colormath/src/commonMain/kotlin/com/github/ajalt/colormath/model/RGB.kt#L310
internal fun Color.toAnsi256Code(): Int {
val ri = redInt
val gi = greenInt
val bi = blueInt
// grayscale
return if (ri == gi && gi == bi) {
when {
ri < 8 -> 16
ri > 248 -> 231
else -> (((ri - 8) / 247.0) * 24.0).roundToInt() + 232
}
} else {
16 + (36 * (redFloat * 5).roundToInt()) +
(6 * (greenFloat * 5).roundToInt()) +
(blueFloat * 5).roundToInt()
}
}
109 changes: 84 additions & 25 deletions mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/canvas.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.jakewharton.mosaic

import com.jakewharton.mosaic.ui.AnsiLevel
import com.jakewharton.mosaic.ui.Color
import com.jakewharton.mosaic.ui.TextStyle
import com.jakewharton.mosaic.ui.TextStyle.Companion.Bold
Expand All @@ -24,6 +25,7 @@ private val blankPixel = TextPixel(' ')
internal class TextSurface(
override val width: Int,
override val height: Int,
private val ansiLevel: AnsiLevel,
) : TextCanvas {
override var translationX = 0
override var translationY = 0
Expand All @@ -40,33 +42,53 @@ internal class TextSurface(
var lastPixel = blankPixel
for (columnIndex in 0 until width) {
val pixel = rowPixels[columnIndex]
if (pixel.foreground != lastPixel.foreground) {
attributes += pixel.foreground?.fg ?: 39
}
if (pixel.background != lastPixel.background) {
attributes += pixel.background?.bg ?: 49
}

fun maybeToggleStyle(style: TextStyle, on: Int, off: Int) {
if (style in pixel.style) {
if (style !in lastPixel.style) {
attributes += on
if (ansiLevel != AnsiLevel.NONE) {
if (pixel.foreground != lastPixel.foreground) {
attributes.addColor(
pixel.foreground,
ansiLevel,
ansiFgColorSelector,
ansiFgColorReset,
ansiFgColorOffset,
)
}
if (pixel.background != lastPixel.background) {
attributes.addColor(
pixel.background,
ansiLevel,
ansiBgColorSelector,
ansiBgColorReset,
ansiBgColorOffset,
)
}

fun maybeToggleStyle(style: TextStyle, on: Int, off: Int) {
if (style in pixel.style) {
if (style !in lastPixel.style) {
attributes += on
}
} else if (style in lastPixel.style) {
attributes += off
}
} else if (style in lastPixel.style) {
attributes += off
}
}
if (pixel.style != lastPixel.style) {
maybeToggleStyle(Bold, 1, 22)
maybeToggleStyle(Dim, 2, 22)
maybeToggleStyle(Italic, 3, 23)
maybeToggleStyle(Underline, 4, 24)
maybeToggleStyle(Invert, 7, 27)
maybeToggleStyle(Strikethrough, 9, 29)
}
if (attributes.isNotEmpty()) {
attributes.joinTo(appendable, separator = ";", prefix = "\u001B[", postfix = "m")
attributes.clear() // This list is reused!
if (pixel.style != lastPixel.style) {
maybeToggleStyle(Bold, 1, 22)
maybeToggleStyle(Dim, 2, 22)
maybeToggleStyle(Italic, 3, 23)
maybeToggleStyle(Underline, 4, 24)
maybeToggleStyle(Invert, 7, 27)
maybeToggleStyle(Strikethrough, 9, 29)
}
if (attributes.isNotEmpty()) {
attributes.joinTo(
appendable,
separator = ansiSeparator,
prefix = CSI,
postfix = ansiClosingCharacter,
)
attributes.clear() // This list is reused!
}
}

appendable.append(pixel.value)
Expand All @@ -77,7 +99,44 @@ internal class TextSurface(
lastPixel.foreground != null ||
lastPixel.style != None
) {
appendable.append("\u001B[0m")
appendable.append(ansiReset)
appendable.append(ansiClosingCharacter)
}
}

private fun MutableList<Int>.addColor(
color: Color?,
ansiLevel: AnsiLevel,
select: Int,
reset: Int,
offset: Int,
) {
if (color == null) {
add(reset)
return
}
when (ansiLevel) {
AnsiLevel.NONE -> add(reset)
AnsiLevel.ANSI16 -> {
val ansi16Code = color.toAnsi16Code()
if (ansi16Code == ansiFgColorReset || ansi16Code == ansiBgColorReset) {
add(reset)
} else {
add(ansi16Code + offset)
}
}
AnsiLevel.ANSI256 -> {
add(select)
add(ansiSelectorColor256)
add(color.toAnsi256Code())
}
AnsiLevel.TRUECOLOR -> {
add(select)
add(ansiSelectorColorRgb)
add(color.redInt)
add(color.greenInt)
add(color.blueInt)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.jakewharton.mosaic.TextCanvas
import com.jakewharton.mosaic.TextSurface
import com.jakewharton.mosaic.layout.Placeable.PlacementScope
import com.jakewharton.mosaic.modifier.Modifier
import com.jakewharton.mosaic.ui.AnsiLevel
import com.jakewharton.mosaic.ui.unit.Constraints

internal fun interface DebugPolicy {
Expand Down Expand Up @@ -135,8 +136,8 @@ internal class MosaicNode(
* Draw this node to a [TextSurface].
* A call to [measureAndPlace] must precede calls to this function.
*/
fun paint(): TextSurface {
val surface = TextSurface(width, height)
fun paint(ansiLevel: AnsiLevel): TextSurface {
val surface = TextSurface(width, height, ansiLevel)
topLayer.drawTo(surface)
return surface
}
Expand All @@ -145,12 +146,12 @@ internal class MosaicNode(
* Append any static [TextSurfaces][TextSurface] to [statics].
* A call to [measureAndPlace] must precede calls to this function.
*/
fun paintStatics(statics: MutableList<TextSurface>) {
fun paintStatics(statics: MutableList<TextSurface>, ansiLevel: AnsiLevel) {
for (child in children) {
if (isStatic) {
statics += child.paint()
statics += child.paint(ansiLevel)
}
child.paintStatics(statics)
child.paintStatics(statics, ansiLevel)
}
onStaticDraw?.invoke()
}
Expand Down

0 comments on commit 6efdb15

Please sign in to comment.