// Generated by `cargo run -p compose-rust --bin codegen`. DO NOT EDIT.
package dev.darkpyonix.composerust.protocol
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.charset.StandardCharsets
enum class WidgetKind { Column, Row, Box, Text, TextField, Button, Spacer, LazyColumn, ScrollColumn, Image, Icon, Checkbox, RadioButton, Switch, Slider, ProgressIndicator, Divider, Card, Surface, Dialog, Menu, Tabs, TopAppBar, LazyRow, Tooltip, Canvas, DatePicker, TimePicker, Dropdown, Navigation, NavigationItem, Sheet, Scaffold, ScaffoldSlot, LazyGrid, FileDropTarget, ScrollRow, Chip, FloatingAction, Badge, SelectionContainer, SplitPane, CodeEditor, AbsoluteBox, LinearProgressIndicator }
enum class PropertyKind { Text, Placeholder, Enabled, Multiline, OnClick, OnValueChange, OnSubmit, OnFocusLost, OnKeyDown, ItemCount, ItemKey, OnRangeRequested, TypeRole, FontSize, FontWeight, LineHeight, LetterSpacing, Color, TextAlign, MaxLines, Overflow, Arrangement, Spacing, SpaceRole, Alignment, Variant, Asset, Checked, Steps, Determinate, Circular, Vertical, Open, OnDismiss, SelectedIndex, Commands, Value, Min, Max, Icon, Slot, Columns, MinColumnWidth, Spans, OnFilesEntered, OnFilesDropped, Section, Count, Collapsible, ButtonKind, Decorations, SyntaxSpans, TabWidth, OnEditRejected, OnHover, OnSave, OnDecorationClick, Folds, GutterDecorations, OnSelectionChange, Font, WordBreak, OverflowWrap, TabSize, AbsoluteSize, SoftWrap, SpanFonts, OnVisibleRangeChange, OnEditorFocusChange, OnFoldChange, OnCompositionChange, OnPointer, PointerKinds, Cursor, UserSelect, IslandZoom, Progress }
enum class Key { Enter }
enum class WindowSizeClass { Compact, Medium, Expanded }
enum class WindowHeightClass { Compact, Medium, Expanded }
enum class ColorRole { Primary, OnPrimary, Secondary, OnSecondary, Surface, OnSurface, SurfaceVariant, OnSurfaceVariant, Background, OnBackground, Outline, OutlineVariant, Error, OnError, SurfaceContainer, Tertiary, OnTertiary, PrimaryContainer, OnPrimaryContainer, SecondaryContainer, OnSecondaryContainer, TertiaryContainer, OnTertiaryContainer, SyntaxKeyword, SyntaxString, SyntaxComment, SyntaxNumber, SyntaxConstant, SyntaxType, SyntaxFunction, SyntaxVariable, SyntaxProperty, SyntaxOperator, SyntaxPunctuation, SyntaxTag, SyntaxAttribute, SyntaxEscape, SyntaxMacro, DiffAdded, DiffRemoved, DiffModified, DiffAddedContainer, DiffRemovedContainer, DiffAddedEmphasis, DiffRemovedEmphasis }
enum class TypeRole { Display, Headline, Title, Subtitle, Body, BodyStrong, Label, Caption, Mono }
enum class ShapeRole { None, ExtraSmall, Small, Medium, Large, Full }
enum class MotionRole { Instant, Quick, Standard, Slow, Emphasized }
enum class TileMode { Clamp, Repeat, Mirror }
enum class MaterialRole { Thin, Regular, Thick, Chrome }
enum class SpaceRole { None, Xs, Sm, Md, Lg, Xl, Xxl }
enum class TextAlign { Start, Center, End, Justify }
enum class TextOverflow { Clip, Ellipsis, Visible }
enum class Arrangement { Start, Center, End, SpaceBetween, SpaceAround, SpaceEvenly }
enum class Alignment { TopStart, TopCenter, TopEnd, CenterStart, Center, CenterEnd, BottomStart, BottomCenter, BottomEnd }
enum class ButtonVariant { Filled, Tonal, Outlined, Text, Operator }
enum class ButtonKind { Standard, ActionKey }
enum class DesignSystem { Material3, Cupertino, Fluent, Gnome, Breeze, Deepin, LiquidGlass }
enum class ColorScheme { Light, Dark, FollowSystem }
enum class AssetKind { Png, Jpeg, Svg, VectorIcon, Font, Brush }
enum class IconRole { Back, Forward, Close, Search, Add, Check, Settings, More, Home, List, Inbox, Menu, History, Send, Mic, Image, Video, Library, Sidebar, Compose, Collapse }
enum class MessageDuration { Short, Long }
enum class Chrome { Modern, System }
enum class TitleBar { Normal, Simple }
enum class SlotRole { TopBar, BottomBar, FloatingAction, Content }
enum class NotificationImportance { Normal, Urgent }
enum class NotificationPresentation { Always, WhenInactive }
enum class NotificationPermission { NotDetermined, Granted, Denied, Unsupported }
enum class WordBreak { Normal, KeepAll, BreakAll }
enum class OverflowWrap { Normal, Anywhere, BreakWord }
enum class GenericFamily { SystemUi, SansSerif, Serif, Monospace }
enum class AnimatedProperty { Alpha, Color, Background, Transform, BorderColor }
enum class PlaybackDirection { Normal, Reverse, Alternate, AlternateReverse }
enum class FillMode { None, Forwards, Backwards, Both }
enum class PlayState { Running, Paused }
enum class ColorInterpolation { SrgbPremultiplied, Oklab }
enum class TimingKind { Linear, CubicBezier, Steps }
enum class StepPosition { JumpStart, JumpEnd, JumpNone, JumpBoth }
enum class TransformFunctionKind { Translate, Rotate, Scale, Skew, Matrix }
enum class AnimationControl { Pause, Resume, Cancel }
enum class AnimationEventKind { Ready, Active, Iteration, End }
enum class ReducedMotion { Off, On, Unknown }
enum class DecorationKind { Underline, CodeLens, HoverAnchor, GhostText }
enum class Severity { Error, Warning, Information, Hint }
enum class HoverPhase { Rest, Leave }
enum class GutterDecorationKind { Dot, Bar, Glyph }
enum class RevealAlign { Nearest, Center, Top }
enum class PointerKind { Move, Enter, Leave, Down, Up, Cancel, Wheel, ContextMenu }
enum class PointerType { Mouse, Touch, Pen }
enum class WheelDeltaMode { Pixel, Line, Page }
enum class Cursor { Auto, Default, Pointer, Text, VerticalText, Crosshair, Move, Grab, Grabbing, NotAllowed, NoDrop, Wait, Progress, Help, ContextMenu, Cell, Copy, Alias, AllScroll, ZoomIn, ZoomOut, ColResize, RowResize, EResize, NResize, NeResize, NwResize, SResize, SeResize, SwResize, WResize, EwResize, NsResize, NeswResize, NwseResize, None }
enum class UserSelect { Auto, None, Text, All }
/**
* The `TypeRole` property of text with no rung of the ladder at all: CSS text, whose
* size, weight and font are its own. Present and zero; an absent property is the
* widget's default rung.
*/
const val TYPE_ROLE_NONE: Int = 0
enum class LoopMode(val wire: Byte) {
/** The Renderer runs the loop and the Host blocks inside it. Desktop. */
Renderer(0),
/** The platform owns the process and the loop. Android, iOS and the web. */
Platform(1),
}
sealed interface Paint {
data class Role(val role: ColorRole) : Paint
/** A literal 0xAARRGGBB colour. */
data class Literal(val argb: Int) : Paint
/**
* A registered brush: a gradient, or a picture laid out as a fill.
*
* An id, because a list of stops does not fit the two words a paint has and because a
* brush has to outlive the frame that draws it.
*/
data class Asset(val assetId: Int) : Paint
}
data class Theme(
val designSystem: DesignSystem,
val fallback: DesignSystem,
val colorScheme: ColorScheme,
val adaptive: Boolean,
/**
* The font asset each type role resolves to, indexed by the role's ordinal, with zero
* where the role keeps the system font.
*
* Per theme rather than per node: an application changes what a role is made of and
* every piece of text in that role changes with it. A node that could name a font
* would be a node deciding typography.
*/
val fonts: List<Int> = List(TypeRole.entries.size) { 0 },
/**
* The application's own colours, laid over whichever design system this resolves to.
*
* Both schemes arrive together, so a switch between light and dark is answered from
* here and never asks the Host. Empty where the application gave none.
*/
val palette: List<PaletteEntry> = emptyList(),
/**
* What was wrong with entries the palette arrived with, one sentence each.
*
* Those entries are already left out of [palette] and the rest of it stands. Whoever
* applies the theme reports each of these as a protocol error: a theme is never thrown
* away whole for one bad entry, because that would open the application with no colours
* at all.
*/
val paletteProblems: List<String> = emptyList(),
) {
/** The font asset for [role], or null where the role keeps the system font. */
fun font(role: TypeRole): Int? = fonts.getOrNull(role.ordinal)?.takeIf { it != 0 }
/** The colour the application gave [role] in this scheme, as 0xAARRGGBB, or null. */
fun paletteColor(role: ColorRole, dark: Boolean): Int? =
palette.firstOrNull { it.role == role && it.dark == dark }?.argb
}
/** One role's colour in one scheme, as the application gave it. */
data class PaletteEntry(val role: ColorRole, val dark: Boolean, val argb: Int)
/**
* What the application asked of its own window.
*
* A zero measurement means the application did not ask, so the choice is the Renderer's.
* This arrives in the first batch, which the Renderer reads before it stands the window
* up; a platform where the window is not ours ignores it.
*/
data class Window(
val chrome: Chrome,
/**
* Which of the two title bars this window has.
*
* `Chrome` says who draws the caption; this says, inside that, what kind of window the
* caption belongs to. What it is worth in pixels is this side's answer: on macOS how
* far in the buttons sit and how round the window is, on Windows and Linux how tall
* the caption is.
*/
val titleBar: TitleBar,
/**
* What the window calls itself.
*
* Empty means the application said nothing and the renderer uses its own name. A
* desktop lists windows by this, so a window with no title of its own is listed
* under whatever the renderer happened to be called.
*/
val title: String,
/**
* The picture the window wears, as an asset id, or zero for none.
*
* An id rather than a path or a name: a path is a fact about the machine the
* application was built on, and a name asks the toolkit to find something it may not
* have. Zero leaves the toolkit's own icon, which is what every window here wore
* until this existed.
*/
val icon: Int,
val width: Int,
val height: Int,
val minWidth: Int,
val minHeight: Int,
val resizable: Boolean,
/**
* The application's zoom level, or null where it left the level to the renderer.
*
* A level here is applied, remembered and reported back the way one chosen with the
* keys is. Always inside -8..8.
*/
val zoomLevel: Int? = null,
)
sealed interface PropertyValue {
data object None : PropertyValue
data class Text(val value: String) : PropertyValue
data class Bool(val value: Boolean) : PropertyValue
data class Integer(val value: Long) : PropertyValue
data class Float(val value: kotlin.Float) : PropertyValue
class Bytes(val value: ByteArray) : PropertyValue
}
/**
* One drawing command. Coordinates are dp from the Canvas's top-left corner.
*
* Colour is a [Paint], so a command may name a ColorRole and the design system
* decides what it looks like.
*/
sealed interface DrawCommand {
val paint: Paint
data class Line(override val paint: Paint, val x1: kotlin.Float, val y1: kotlin.Float, val x2: kotlin.Float, val y2: kotlin.Float, val strokeWidth: kotlin.Float) : DrawCommand
data class Rect(override val paint: Paint, val x: kotlin.Float, val y: kotlin.Float, val width: kotlin.Float, val height: kotlin.Float, val strokeWidth: kotlin.Float) : DrawCommand
data class RoundRect(override val paint: Paint, val x: kotlin.Float, val y: kotlin.Float, val width: kotlin.Float, val height: kotlin.Float, val radius: kotlin.Float, val strokeWidth: kotlin.Float) : DrawCommand
data class Circle(override val paint: Paint, val centerX: kotlin.Float, val centerY: kotlin.Float, val radius: kotlin.Float, val strokeWidth: kotlin.Float) : DrawCommand
data class Arc(override val paint: Paint, val centerX: kotlin.Float, val centerY: kotlin.Float, val radius: kotlin.Float, val startDegrees: kotlin.Float, val sweepDegrees: kotlin.Float, val strokeWidth: kotlin.Float) : DrawCommand
data class PolylineRef(override val paint: Paint, val assetId: Int, val strokeWidth: kotlin.Float) : DrawCommand
data class TextAt(override val paint: Paint, val textOffset: Int, val textLength: Int, val x: kotlin.Float, val y: kotlin.Float, val typeRole: TypeRole) : DrawCommand
}
/** Decodes a Canvas command list: fixed-size records, then the TextAt strings. */
object DrawCommands {
const val COMMAND_LENGTH = 36
fun decode(bytes: ByteArray): List<DrawCommand> {
val buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN)
val commands = ArrayList<DrawCommand>(bytes.size / COMMAND_LENGTH)
var offset = 0
while (offset + COMMAND_LENGTH <= bytes.size) {
val tag = buffer.getShort(offset).toInt() and 0xffff
val length = buffer.getShort(offset + 2).toInt() and 0xffff
// The text region starts where the records stop decoding, so an unreadable
// record ends the list rather than failing the whole batch.
if (length != COMMAND_LENGTH) break
val paintBits = buffer.getLong(offset + 4)
val kind = (paintBits ushr 32).toInt()
if (kind != 1 && kind != 2) break
val paint = if (kind == 1) {
Paint.Role(colorRoleOrNull(paintBits.toInt()) ?: break)
} else {
Paint.Literal(paintBits.toInt())
}
fun word(index: Int): Int = buffer.getInt(offset + 12 + index * 4)
fun real(index: Int): kotlin.Float = kotlin.Float.fromBits(word(index))
val command = when (tag) {
1 -> DrawCommand.Line(paint, real(0), real(1), real(2), real(3), real(4))
2 -> DrawCommand.Rect(paint, real(0), real(1), real(2), real(3), real(4))
3 -> DrawCommand.RoundRect(paint, real(0), real(1), real(2), real(3), real(4), real(5))
4 -> DrawCommand.Circle(paint, real(0), real(1), real(2), real(3))
5 -> DrawCommand.Arc(paint, real(0), real(1), real(2), real(3), real(4), real(5))
6 -> DrawCommand.PolylineRef(paint, word(0), real(1))
7 -> DrawCommand.TextAt(paint, word(0), word(1), real(2), real(3), typeRoleOrNull(word(4)) ?: break)
else -> break
}
commands.add(command)
offset += COMMAND_LENGTH
}
return commands
}
/** The string a TextAt points at, measured from the start of the list. */
fun textOf(bytes: ByteArray, command: DrawCommand.TextAt): String {
val end = command.textOffset.toLong() + command.textLength.toLong()
if (command.textOffset < 0 || command.textLength < 0 || end > bytes.size.toLong()) {
return ""
}
return String(bytes, command.textOffset, command.textLength, StandardCharsets.UTF_8)
}
private fun typeRoleOrNull(tag: Int): TypeRole? = when (tag) {
1 -> TypeRole.Display
2 -> TypeRole.Headline
3 -> TypeRole.Title
4 -> TypeRole.Subtitle
5 -> TypeRole.Body
6 -> TypeRole.BodyStrong
7 -> TypeRole.Label
8 -> TypeRole.Caption
9 -> TypeRole.Mono
else -> null
}
private fun colorRoleOrNull(tag: Int): ColorRole? = when (tag) {
1 -> ColorRole.Primary
2 -> ColorRole.OnPrimary
3 -> ColorRole.Secondary
4 -> ColorRole.OnSecondary
5 -> ColorRole.Surface
6 -> ColorRole.OnSurface
7 -> ColorRole.SurfaceVariant
8 -> ColorRole.OnSurfaceVariant
9 -> ColorRole.Background
10 -> ColorRole.OnBackground
11 -> ColorRole.Outline
12 -> ColorRole.OutlineVariant
13 -> ColorRole.Error
14 -> ColorRole.OnError
15 -> ColorRole.SurfaceContainer
16 -> ColorRole.Tertiary
17 -> ColorRole.OnTertiary
18 -> ColorRole.PrimaryContainer
19 -> ColorRole.OnPrimaryContainer
20 -> ColorRole.SecondaryContainer
21 -> ColorRole.OnSecondaryContainer
22 -> ColorRole.TertiaryContainer
23 -> ColorRole.OnTertiaryContainer
24 -> ColorRole.SyntaxKeyword
25 -> ColorRole.SyntaxString
26 -> ColorRole.SyntaxComment
27 -> ColorRole.SyntaxNumber
28 -> ColorRole.SyntaxConstant
29 -> ColorRole.SyntaxType
30 -> ColorRole.SyntaxFunction
31 -> ColorRole.SyntaxVariable
32 -> ColorRole.SyntaxProperty
33 -> ColorRole.SyntaxOperator
34 -> ColorRole.SyntaxPunctuation
35 -> ColorRole.SyntaxTag
36 -> ColorRole.SyntaxAttribute
37 -> ColorRole.SyntaxEscape
38 -> ColorRole.SyntaxMacro
39 -> ColorRole.DiffAdded
40 -> ColorRole.DiffRemoved
41 -> ColorRole.DiffModified
42 -> ColorRole.DiffAddedContainer
43 -> ColorRole.DiffRemovedContainer
44 -> ColorRole.DiffAddedEmphasis
45 -> ColorRole.DiffRemovedEmphasis
else -> null
}
}
/**
* One run of different treatment inside a Text's string, as the Host wrote it.
*
* Offsets are bytes of the string's UTF-8, because that is what the string is measured in
* on the side that wrote them. A null paint is a run that says nothing about that colour.
*/
data class TextSpanRecord(
val start: Int,
val length: Int,
val typeRole: TypeRole?,
val color: Paint?,
/** What is painted behind this run's letters only. */
val background: Paint?,
val bold: Boolean,
val italic: Boolean,
val underline: Boolean,
val strikethrough: Boolean,
/** The handler a press on this run reports to, or zero where it is not a link. */
val handlerId: Long,
)
/** Decodes the run list a Text carries in its `Spans` property. */
object SpanRecords {
const val SPAN_LENGTH = 36
private const val START_AT = 0
private const val LENGTH_AT = 4
private const val ROLE_AT = 8
private const val FLAGS_AT = 10
private const val COLOR_AT = 12
private const val BACKGROUND_AT = 20
private const val HANDLER_AT = 28
private const val FLAG_BOLD = 1
private const val FLAG_ITALIC = 2
private const val FLAG_UNDERLINE = 4
private const val FLAG_STRIKETHROUGH = 8
/** The runs in [bytes], or null where it is not a whole number of records. */
fun decode(bytes: ByteArray): List<TextSpanRecord>? {
if (bytes.size % SPAN_LENGTH != 0) return null
val buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN)
return decodeAt(buffer, bytes.size, 0, bytes.size / SPAN_LENGTH)
}
/**
* The [count] runs whose records start at [at] in [buffer], or null where they reach
* past [limit]. What a measure request carries, in place.
*/
fun decodeAt(buffer: ByteBuffer, limit: Int, at: Int, count: Int): List<TextSpanRecord>? {
if (count < 0 || at < 0 || at.toLong() + count.toLong() * SPAN_LENGTH > limit.toLong()) {
return null
}
val previous = buffer.order()
buffer.order(ByteOrder.LITTLE_ENDIAN)
try {
return List(count) { index ->
val record = at + index * SPAN_LENGTH
val flags = buffer.getShort(record + FLAGS_AT).toInt() and 0xffff
TextSpanRecord(
start = buffer.getInt(record + START_AT),
length = buffer.getInt(record + LENGTH_AT),
typeRole = typeRoleOrNull(buffer.getShort(record + ROLE_AT).toInt() and 0xffff),
color = paintOrNull(buffer.getLong(record + COLOR_AT)),
background = paintOrNull(buffer.getLong(record + BACKGROUND_AT)),
bold = flags and FLAG_BOLD != 0,
italic = flags and FLAG_ITALIC != 0,
underline = flags and FLAG_UNDERLINE != 0,
strikethrough = flags and FLAG_STRIKETHROUGH != 0,
handlerId = buffer.getLong(record + HANDLER_AT),
)
}
} finally {
buffer.order(previous)
}
}
/** A paint, or null for zero and for anything that does not decode. */
private fun paintOrNull(bits: Long): Paint? {
val value = bits.toInt()
return when ((bits ushr 32).toInt()) {
1 -> colorRoleOrNull(value)?.let { Paint.Role(it) }
2 -> Paint.Literal(value)
3 -> Paint.Asset(value)
else -> null
}
}
private fun colorRoleOrNull(tag: Int): ColorRole? = when (tag) {
1 -> ColorRole.Primary
2 -> ColorRole.OnPrimary
3 -> ColorRole.Secondary
4 -> ColorRole.OnSecondary
5 -> ColorRole.Surface
6 -> ColorRole.OnSurface
7 -> ColorRole.SurfaceVariant
8 -> ColorRole.OnSurfaceVariant
9 -> ColorRole.Background
10 -> ColorRole.OnBackground
11 -> ColorRole.Outline
12 -> ColorRole.OutlineVariant
13 -> ColorRole.Error
14 -> ColorRole.OnError
15 -> ColorRole.SurfaceContainer
16 -> ColorRole.Tertiary
17 -> ColorRole.OnTertiary
18 -> ColorRole.PrimaryContainer
19 -> ColorRole.OnPrimaryContainer
20 -> ColorRole.SecondaryContainer
21 -> ColorRole.OnSecondaryContainer
22 -> ColorRole.TertiaryContainer
23 -> ColorRole.OnTertiaryContainer
24 -> ColorRole.SyntaxKeyword
25 -> ColorRole.SyntaxString
26 -> ColorRole.SyntaxComment
27 -> ColorRole.SyntaxNumber
28 -> ColorRole.SyntaxConstant
29 -> ColorRole.SyntaxType
30 -> ColorRole.SyntaxFunction
31 -> ColorRole.SyntaxVariable
32 -> ColorRole.SyntaxProperty
33 -> ColorRole.SyntaxOperator
34 -> ColorRole.SyntaxPunctuation
35 -> ColorRole.SyntaxTag
36 -> ColorRole.SyntaxAttribute
37 -> ColorRole.SyntaxEscape
38 -> ColorRole.SyntaxMacro
39 -> ColorRole.DiffAdded
40 -> ColorRole.DiffRemoved
41 -> ColorRole.DiffModified
42 -> ColorRole.DiffAddedContainer
43 -> ColorRole.DiffRemovedContainer
44 -> ColorRole.DiffAddedEmphasis
45 -> ColorRole.DiffRemovedEmphasis
else -> null
}
private fun typeRoleOrNull(tag: Int): TypeRole? = when (tag) {
1 -> TypeRole.Display
2 -> TypeRole.Headline
3 -> TypeRole.Title
4 -> TypeRole.Subtitle
5 -> TypeRole.Body
6 -> TypeRole.BodyStrong
7 -> TypeRole.Label
8 -> TypeRole.Caption
9 -> TypeRole.Mono
else -> null
}
}
/** One decoration over a code editor's document: an underline, a lens above a line, a hover anchor or an inline suggestion. The range is in the document at `version`; columns are UTF-16 units. `id` is the application's own name for it, zero where it cannot be pressed. */
data class DecorationRecord(
val version: Int,
val kind: DecorationKind,
val severity: Severity?,
val color: ColorRole?,
val startLine: Int,
val startColumn: Int,
val endLine: Int,
val endColumn: Int,
val id: Long,
val text: String,
)
/** One colour run of a code editor's syntax. The range is in the document at `version`; columns are UTF-16 units. */
data class SyntaxSpanRecord(
val version: Int,
val startLine: Int,
val startColumn: Int,
val endLine: Int,
val endColumn: Int,
val paint: Paint,
)
/** One stretch of a code editor's lines that can be folded. Folding keeps `startLine` and hides the lines after it up to `endLine`; both are in the document at `version`. */
data class FoldRecord(
val version: Int,
val startLine: Int,
val endLine: Int,
val collapsed: Boolean,
)
/** One mark beside a line number of a code editor. The line is in the document at `version`; `glyph` is a Unicode scalar value, used when the kind is a glyph. `id` is the application's own name for it, zero where it cannot be pressed. */
data class GutterDecorationRecord(
val version: Int,
val line: Int,
val kind: GutterDecorationKind,
val color: ColorRole?,
val id: Long,
val glyph: Int,
)
/**
* Reads the blobs a code editor carries.
*
* A record that cannot be read, a kind or a role this side does not know or a text that
* points outside the blob, is reported through `onError` and left out. The rest of the list
* is read regardless.
*/
object CodeEditorRecords {
const val DECORATION_LENGTH = 44
fun decodeDecorations(bytes: ByteArray, onError: (String) -> Unit): List<DecorationRecord> {
val buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN)
val records = ArrayList<DecorationRecord>(bytes.size / DECORATION_LENGTH)
var end = bytes.size
var offset = 0
var index = 0
while (offset + DECORATION_LENGTH <= end) {
val textOffset = buffer.getInt(offset + 36)
val textLength = buffer.getInt(offset + 40)
if (textLength != 0 && textOffset >= 0 && textOffset < end) end = textOffset
readDecorationRecord(buffer, bytes, offset, index, onError)?.let(records::add)
offset += DECORATION_LENGTH
index += 1
}
return records
}
private fun readDecorationRecord(buffer: ByteBuffer, bytes: ByteArray, offset: Int, index: Int, onError: (String) -> Unit): DecorationRecord? {
val version = buffer.getInt(offset + 0)
val kindTag = buffer.getShort(offset + 4).toInt() and 0xffff
val kind = decorationKindOrNull(kindTag) ?: run { onError("record $index has an unknown kind ${kindTag}"); return null }
val severityTag = buffer.getShort(offset + 6).toInt() and 0xffff
val severity = if (severityTag == 0) null else severityOrNull(severityTag) ?: run { onError("record $index has an unknown severity ${severityTag}"); return null }
val colorTag = buffer.getShort(offset + 8).toInt() and 0xffff
val color = if (colorTag == 0) null else colorRoleOrNull(colorTag) ?: run { onError("record $index has an unknown color ${colorTag}"); return null }
val startLine = buffer.getInt(offset + 12)
val startColumn = buffer.getInt(offset + 16)
val endLine = buffer.getInt(offset + 20)
val endColumn = buffer.getInt(offset + 24)
val id = buffer.getLong(offset + 28)
val text = textOf(bytes, buffer.getInt(offset + 36), buffer.getInt(offset + 40)) ?: run { onError("record $index names text outside the list"); return null }
return DecorationRecord(version, kind, severity, color, startLine, startColumn, endLine, endColumn, id, text)
}
const val SYNTAX_SPAN_LENGTH = 28
fun decodeSyntaxSpans(bytes: ByteArray, onError: (String) -> Unit): List<SyntaxSpanRecord> {
val buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN)
val records = ArrayList<SyntaxSpanRecord>(bytes.size / SYNTAX_SPAN_LENGTH)
var end = bytes.size
var offset = 0
var index = 0
while (offset + SYNTAX_SPAN_LENGTH <= end) {
readSyntaxSpanRecord(buffer, bytes, offset, index, onError)?.let(records::add)
offset += SYNTAX_SPAN_LENGTH
index += 1
}
if (offset != bytes.size) {
onError("${bytes.size - offset} bytes after the last whole record")
}
return records
}
private fun readSyntaxSpanRecord(buffer: ByteBuffer, bytes: ByteArray, offset: Int, index: Int, onError: (String) -> Unit): SyntaxSpanRecord? {
val version = buffer.getInt(offset + 0)
val startLine = buffer.getInt(offset + 4)
val startColumn = buffer.getInt(offset + 8)
val endLine = buffer.getInt(offset + 12)
val endColumn = buffer.getInt(offset + 16)
val paint = paintOrNull(buffer.getLong(offset + 20)) ?: run { onError("record $index has an unreadable paint"); return null }
return SyntaxSpanRecord(version, startLine, startColumn, endLine, endColumn, paint)
}
const val FOLD_LENGTH = 16
fun decodeFolds(bytes: ByteArray, onError: (String) -> Unit): List<FoldRecord> {
val buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN)
val records = ArrayList<FoldRecord>(bytes.size / FOLD_LENGTH)
var end = bytes.size
var offset = 0
var index = 0
while (offset + FOLD_LENGTH <= end) {
readFoldRecord(buffer, bytes, offset, index, onError)?.let(records::add)
offset += FOLD_LENGTH
index += 1
}
if (offset != bytes.size) {
onError("${bytes.size - offset} bytes after the last whole record")
}
return records
}
private fun readFoldRecord(buffer: ByteBuffer, bytes: ByteArray, offset: Int, index: Int, onError: (String) -> Unit): FoldRecord? {
val version = buffer.getInt(offset + 0)
val startLine = buffer.getInt(offset + 4)
val endLine = buffer.getInt(offset + 8)
val collapsed = when (buffer.getInt(offset + 12)) { 0 -> false; 1 -> true; else -> run { onError("record $index has a collapsed that is neither 0 nor 1"); return null } }
return FoldRecord(version, startLine, endLine, collapsed)
}
const val GUTTER_DECORATION_LENGTH = 28
fun decodeGutterDecorations(bytes: ByteArray, onError: (String) -> Unit): List<GutterDecorationRecord> {
val buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN)
val records = ArrayList<GutterDecorationRecord>(bytes.size / GUTTER_DECORATION_LENGTH)
var end = bytes.size
var offset = 0
var index = 0
while (offset + GUTTER_DECORATION_LENGTH <= end) {
readGutterDecorationRecord(buffer, bytes, offset, index, onError)?.let(records::add)
offset += GUTTER_DECORATION_LENGTH
index += 1
}
if (offset != bytes.size) {
onError("${bytes.size - offset} bytes after the last whole record")
}
return records
}
private fun readGutterDecorationRecord(buffer: ByteBuffer, bytes: ByteArray, offset: Int, index: Int, onError: (String) -> Unit): GutterDecorationRecord? {
val version = buffer.getInt(offset + 0)
val line = buffer.getInt(offset + 4)
val kindTag = buffer.getShort(offset + 8).toInt() and 0xffff
val kind = gutterDecorationKindOrNull(kindTag) ?: run { onError("record $index has an unknown kind ${kindTag}"); return null }
val colorTag = buffer.getShort(offset + 10).toInt() and 0xffff
val color = if (colorTag == 0) null else colorRoleOrNull(colorTag) ?: run { onError("record $index has an unknown color ${colorTag}"); return null }
val id = buffer.getLong(offset + 12)
val glyph = buffer.getInt(offset + 20)
return GutterDecorationRecord(version, line, kind, color, id, glyph)
}
/** UTF-8 text behind the records, or null where the range leaves the blob or is not text. */
private fun textOf(bytes: ByteArray, offset: Int, length: Int): String? {
if (length == 0) return ""
if (offset < 0 || length < 0 || offset.toLong() + length.toLong() > bytes.size.toLong()) return null
return try {
bytes.decodeToString(offset, offset + length, throwOnInvalidSequence = true)
} catch (error: CharacterCodingException) {
null
}
}
private fun paintOrNull(bits: Long): Paint? {
val value = bits.toInt()
return when ((bits ushr 32).toInt()) {
1 -> colorRoleOrNull(value)?.let { Paint.Role(it) }
2 -> Paint.Literal(value)
3 -> Paint.Asset(value)
else -> null
}
}
private fun colorRoleOrNull(tag: Int): ColorRole? = when (tag) {
1 -> ColorRole.Primary
2 -> ColorRole.OnPrimary
3 -> ColorRole.Secondary
4 -> ColorRole.OnSecondary
5 -> ColorRole.Surface
6 -> ColorRole.OnSurface
7 -> ColorRole.SurfaceVariant
8 -> ColorRole.OnSurfaceVariant
9 -> ColorRole.Background
10 -> ColorRole.OnBackground
11 -> ColorRole.Outline
12 -> ColorRole.OutlineVariant
13 -> ColorRole.Error
14 -> ColorRole.OnError
15 -> ColorRole.SurfaceContainer
16 -> ColorRole.Tertiary
17 -> ColorRole.OnTertiary
18 -> ColorRole.PrimaryContainer
19 -> ColorRole.OnPrimaryContainer
20 -> ColorRole.SecondaryContainer
21 -> ColorRole.OnSecondaryContainer
22 -> ColorRole.TertiaryContainer
23 -> ColorRole.OnTertiaryContainer
24 -> ColorRole.SyntaxKeyword
25 -> ColorRole.SyntaxString
26 -> ColorRole.SyntaxComment
27 -> ColorRole.SyntaxNumber
28 -> ColorRole.SyntaxConstant
29 -> ColorRole.SyntaxType
30 -> ColorRole.SyntaxFunction
31 -> ColorRole.SyntaxVariable
32 -> ColorRole.SyntaxProperty
33 -> ColorRole.SyntaxOperator
34 -> ColorRole.SyntaxPunctuation
35 -> ColorRole.SyntaxTag
36 -> ColorRole.SyntaxAttribute
37 -> ColorRole.SyntaxEscape
38 -> ColorRole.SyntaxMacro
39 -> ColorRole.DiffAdded
40 -> ColorRole.DiffRemoved
41 -> ColorRole.DiffModified
42 -> ColorRole.DiffAddedContainer
43 -> ColorRole.DiffRemovedContainer
44 -> ColorRole.DiffAddedEmphasis
45 -> ColorRole.DiffRemovedEmphasis
else -> null
}
private fun decorationKindOrNull(tag: Int): DecorationKind? = when (tag) {
1 -> DecorationKind.Underline
2 -> DecorationKind.CodeLens
3 -> DecorationKind.HoverAnchor
4 -> DecorationKind.GhostText
else -> null
}
private fun gutterDecorationKindOrNull(tag: Int): GutterDecorationKind? = when (tag) {
1 -> GutterDecorationKind.Dot
2 -> GutterDecorationKind.Bar
3 -> GutterDecorationKind.Glyph
else -> null
}
private fun severityOrNull(tag: Int): Severity? = when (tag) {
1 -> Severity.Error
2 -> Severity.Warning
3 -> Severity.Information
4 -> Severity.Hint
else -> null
}
}
/**
* One font to try for text with no role, in the order a CSS font list gives them. The first
* one the Renderer can resolve is the one the text is set in.
*/
sealed interface FontRef {
/** A font the application registered as an asset. */
data class Asset(val assetId: Int) : FontRef
/** A family installed on the machine, by name. */
data class System(val name: String) : FontRef
/** A family CSS names without naming a font. */
data class Generic(val family: GenericFamily) : FontRef
}
/** Decodes font candidate lists, wherever they sit: a property, a run or a measure request. */
object FontRefRecords {
const val RECORD_LENGTH = 12
const val MAX_REFS = 8
private const val KIND_AT = 0
private const val VALUE_AT = 4
private const val LENGTH_AT = 8
private const val ASSET = 1
private const val SYSTEM = 2
private const val GENERIC = 3
/**
* The [count] candidates whose records start at [at] in [buffer], each name read from
* the same buffer, or null where anything reaches past [limit] or names a kind this
* decoder does not know.
*/
fun decode(buffer: ByteBuffer, limit: Int, at: Int, count: Int): List<FontRef>? {
if (count < 0 || count > MAX_REFS) return null
if (at < 0 || at.toLong() + count.toLong() * RECORD_LENGTH > limit.toLong()) return null
val previous = buffer.order()
buffer.order(ByteOrder.LITTLE_ENDIAN)
try {
val refs = ArrayList<FontRef>(count)
for (index in 0 until count) {
val record = at + index * RECORD_LENGTH
val value = buffer.getInt(record + VALUE_AT)
refs += when (buffer.getInt(record + KIND_AT)) {
ASSET -> FontRef.Asset(value)
SYSTEM -> {
val length = buffer.getInt(record + LENGTH_AT)
if (value < 0 || length < 0 || value.toLong() + length > limit.toLong()) {
return null
}
// Relative rather than absolute: the absolute bulk read is not on
// every Android runtime this has to compile against.
val name = ByteArray(length)
val mark = buffer.position()
buffer.position(value)
buffer.get(name, 0, length)
buffer.position(mark)
FontRef.System(name.decodeToString())
}
GENERIC -> FontRef.Generic(genericFamilyOrNull(value) ?: return null)
else -> return null
}
}
return refs
} finally {
buffer.order(previous)
}
}
/** The list a `Font` property carries: a count, then the records and their names. */
fun decodeBlob(bytes: ByteArray): List<FontRef>? {
if (bytes.isEmpty()) return emptyList()
if (bytes.size < 4) return null
val buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN)
return decode(buffer, bytes.size, 4, buffer.getInt(0))
}
private fun genericFamilyOrNull(tag: Int): GenericFamily? = when (tag) {
1 -> GenericFamily.SystemUi
2 -> GenericFamily.SansSerif
3 -> GenericFamily.Serif
4 -> GenericFamily.Monospace
else -> null
}
}
/**
* Which runs of a Text name fonts of their own, and which: the `SpanFonts` property, or
* the same table inside a measure request. Keyed by the run's index in its run list.
*/
object SpanFontRecords {
const val ENTRY_LENGTH = 12
private const val SPAN_AT = 0
private const val OFFSET_AT = 4
private const val COUNT_AT = 8
/** The [count] entries starting at [at] in [buffer], or null where any is malformed. */
fun decode(buffer: ByteBuffer, limit: Int, at: Int, count: Int): Map<Int, List<FontRef>>? {
if (count < 0 || at < 0 || at.toLong() + count.toLong() * ENTRY_LENGTH > limit.toLong()) {
return null
}
val previous = buffer.order()
buffer.order(ByteOrder.LITTLE_ENDIAN)
val entries = try {
List(count) { index ->
val entry = at + index * ENTRY_LENGTH
Triple(
buffer.getInt(entry + SPAN_AT),
buffer.getInt(entry + OFFSET_AT),
buffer.getInt(entry + COUNT_AT),
)
}
} finally {
buffer.order(previous)
}
val fonts = HashMap<Int, List<FontRef>>(count)
for ((span, offset, refs) in entries) {
fonts[span] = FontRefRecords.decode(buffer, limit, offset, refs) ?: return null
}
return fonts
}
/** The table a `SpanFonts` property carries: a count, then the entries and their lists. */
fun decodeBlob(bytes: ByteArray): Map<Int, List<FontRef>>? {
if (bytes.isEmpty()) return emptyMap()
if (bytes.size < 4) return null
val buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN)
return decode(buffer, bytes.size, 4, buffer.getInt(0))
}
}
/**
* The layout of a measure call: the request records the Host writes, the result records
* the Renderer writes back, and what each status means. The records and their offsets are
* the Host's, generated from its schema, so nobody counts bytes by hand on this side.
*/
object MeasureRecords {
const val RECORD_LENGTH = 72
const val RESULT_LENGTH = 32
const val KIND_TEXT = 1
const val KIND_NODE = 2
const val KIND_AT = 0
const val TEXT_TYPE_ROLE_AT = 2
const val TEXT_TEXT_OFFSET_AT = 4
const val TEXT_TEXT_LENGTH_AT = 8
const val TEXT_SPANS_OFFSET_AT = 12
const val TEXT_SPANS_COUNT_AT = 16
const val TEXT_SPAN_FONTS_OFFSET_AT = 20
const val TEXT_SPAN_FONTS_COUNT_AT = 24
const val TEXT_FONT_OFFSET_AT = 28
const val TEXT_FONT_COUNT_AT = 32
const val TEXT_FONT_SIZE_AT = 36
const val TEXT_FONT_WEIGHT_AT = 40
const val TEXT_ITALIC_AT = 42
const val TEXT_WRAP_AT = 43
const val TEXT_LETTER_SPACING_AT = 44
const val TEXT_LINE_HEIGHT_AT = 48
const val TEXT_MAX_LINES_AT = 52
const val TEXT_TAB_SIZE_AT = 56
const val TEXT_WORD_BREAK_AT = 57
const val TEXT_OVERFLOW_WRAP_AT = 58
const val TEXT_ABSOLUTE_SIZE_AT = 59
const val TEXT_CONSTRAINT_AT = 60
const val TEXT_WIDTH_AT = 64
const val TEXT_ZOOM_AT = 68
const val NODE_ID_AT = 4
const val NODE_MIN_WIDTH_AT = 8
const val NODE_MAX_WIDTH_AT = 12
const val NODE_MIN_HEIGHT_AT = 16
const val NODE_MAX_HEIGHT_AT = 20
const val NODE_ZOOM_AT = 24
const val CONSTRAINT_MIN_CONTENT = 1
const val CONSTRAINT_MAX_CONTENT = 2
const val CONSTRAINT_AT_MOST = 3
const val RESULT_WIDTH_AT = 0
const val RESULT_HEIGHT_AT = 4
const val RESULT_FIRST_BASELINE_AT = 8
const val RESULT_LAST_BASELINE_AT = 12
const val RESULT_LAST_LINE_WIDTH_AT = 16
const val RESULT_LINE_COUNT_AT = 20
const val RESULT_FLAGS_AT = 24
const val RESULT_STATUS_AT = 28
const val FLAG_TRUNCATED = 1
const val STATUS_OK = 0
const val STATUS_UNKNOWN_NODE = 1
const val STATUS_MALFORMED = 2
const val CALL_OK = 0
const val CALL_UNREADABLE = -1
const val CALL_OFF_UI_THREAD = -4
const val CALL_UNAVAILABLE = -5
}
sealed interface Modifier {
data object Empty : Modifier
data class Padding(val value: kotlin.Float) : Modifier
data object FillMaxWidth : Modifier
data object FillMaxHeight : Modifier
data class Width(val value: kotlin.Float) : Modifier
data class Height(val value: kotlin.Float) : Modifier
data class Size(val width: kotlin.Float, val height: kotlin.Float) : Modifier
data class Background(val paint: Paint) : Modifier
data class Clickable(val handlerId: Long) : Modifier
data class PaddingRole(val role: dev.darkpyonix.composerust.protocol.SpaceRole) : Modifier
data class PaddingEach(val start: kotlin.Float, val top: kotlin.Float, val end: kotlin.Float, val bottom: kotlin.Float) : Modifier
data class Weight(val value: kotlin.Float) : Modifier
data class Shape(val topStart: kotlin.Float, val topEnd: kotlin.Float, val bottomEnd: kotlin.Float, val bottomStart: kotlin.Float) : Modifier
data class ShapeRole(val role: dev.darkpyonix.composerust.protocol.ShapeRole) : Modifier
data class Border(val width: kotlin.Float, val paint: Paint) : Modifier
data class Elevation(val value: kotlin.Float) : Modifier
data class ObserveSize(val token: Int) : Modifier
data class Motion(val role: dev.darkpyonix.composerust.protocol.MotionRole) : Modifier
data class Material(val role: dev.darkpyonix.composerust.protocol.MaterialRole) : Modifier
data class Offset(val x: kotlin.Float, val y: kotlin.Float) : Modifier
data class RequiredSize(val width: kotlin.Float, val height: kotlin.Float) : Modifier
data class BorderEach(val top: kotlin.Float, val right: kotlin.Float, val bottom: kotlin.Float, val left: kotlin.Float, val topPaint: Paint, val rightPaint: Paint, val bottomPaint: Paint, val leftPaint: Paint) : Modifier
data class CornerEach(val topLeft: kotlin.Float, val topRight: kotlin.Float, val bottomRight: kotlin.Float, val bottomLeft: kotlin.Float) : Modifier
data class Shadow(val x: kotlin.Float, val y: kotlin.Float, val blur: kotlin.Float, val spread: kotlin.Float, val paint: Paint) : Modifier
data class Clip(val enabled: Boolean) : Modifier
data class Alpha(val value: kotlin.Float) : Modifier
data class Transform(val a: kotlin.Float, val b: kotlin.Float, val c: kotlin.Float, val d: kotlin.Float, val e: kotlin.Float, val f: kotlin.Float, val originX: kotlin.Float, val originY: kotlin.Float) : Modifier
}
/** How the segment from one keyframe to the next is timed. */
sealed interface Timing {
data object Linear : Timing
/** CSS `cubic-bezier(x1, y1, x2, y2)`, with both `x` between 0 and 1. */
data class CubicBezier(val x1: kotlin.Float, val y1: kotlin.Float, val x2: kotlin.Float, val y2: kotlin.Float) : Timing
/** CSS `steps(count, position)`. */
data class Steps(val count: Int, val position: StepPosition) : Timing
}
/**
* One CSS transform function and its values, the unused ones zero: a translation in dp, a
* rotation in degrees (not folded into one turn), a scale, a skew in degrees, or the six
* values of `matrix()`.
*/
data class TransformFunction(
val kind: TransformFunctionKind,
val v0: kotlin.Float,
val v1: kotlin.Float,
val v2: kotlin.Float,
val v3: kotlin.Float,
val v4: kotlin.Float,
val v5: kotlin.Float,
)
/** What one keyframe holds, which is what its animation's property takes. */
sealed interface KeyframeValue {
data class Alpha(val value: kotlin.Float) : KeyframeValue
/** For `Color`, `Background` and `BorderColor`. A role is resolved against the theme every frame. */
data class PaintValue(val paint: Paint) : KeyframeValue
/** The same kinds in the same order in every keyframe of one animation. */
data class Transform(val functions: List<TransformFunction>) : KeyframeValue
}
data class Keyframe(
val offset: kotlin.Float,
val timing: Timing,
/** Start from what the node shows the frame the animation begins. First keyframe only. */
val fromPresented: Boolean,
val value: KeyframeValue,
)
/**
* One animation for the Renderer to play on its own frame clock until it ends.
*
* Keyed by `(nodeId, property, slot)`: a second one under the same key replaces the first,
* and of the ones in effect on one property the highest slot shows. Timing follows Web
* Animations Level 1. `events` is a set of bits: 1 ready, 2 active, 4 iteration, 8 end.
*/
data class Animation(
val nodeId: Int,
val animationId: Int,
val property: AnimatedProperty,
val slot: Int,
val direction: PlaybackDirection,
val fill: FillMode,
val playState: PlayState,
val interpolation: ColorInterpolation?,
val startTimeNanos: Long,
val delayMs: kotlin.Float,
val durationMs: kotlin.Float,
val iterations: kotlin.Float,
val originX: kotlin.Float,
val originY: kotlin.Float,
val events: Int,
val keyframes: List<Keyframe>,
)
sealed interface Mutation {
data class Create(val nodeId: Int, val widget: WidgetKind) : Mutation
data class SetProp(val nodeId: Int, val property: PropertyKind, val value: PropertyValue) : Mutation
data class SetModifier(val nodeId: Int, val index: Int, val modifier: Modifier) : Mutation
data class Insert(val parentId: Int, val nodeId: Int, val index: Int) : Mutation
data class Move(val parentId: Int, val nodeId: Int, val index: Int) : Mutation
data class Remove(val nodeId: Int) : Mutation
data class SetText(val nodeId: Int, val text: String, val selectionStart: Int, val selectionEnd: Int) : Mutation
data class AppendText(val nodeId: Int, val text: String) : Mutation
data class SetTheme(val theme: Theme) : Mutation
data class SetWindow(val window: Window) : Mutation
/**
* The bytes of one asset. `kind` is the raw wire tag rather than an [AssetKind],
* because a kind this Renderer cannot read has to be reported as a protocol error by
* the asset cache instead of stopping the rest of the batch from being applied.
*
* `bytes` is already a copy: the batch buffer is only valid for the call that carried
* it, and an asset has to outlive the frame that draws it.
*/
data class RegisterAsset(val assetId: Int, val kind: Int, val bytes: ByteArray) : Mutation {
override fun equals(other: Any?): Boolean =
this === other ||
(other is RegisterAsset && assetId == other.assetId && kind == other.kind &&
bytes.contentEquals(other.bytes))
override fun hashCode(): Int =
(assetId * 31 + kind) * 31 + bytes.contentHashCode()
}
data class ReleaseAsset(val assetId: Int) : Mutation
/**
* One sentence to say to the user, with an optional thing to do about it.
*
* It names no node because it is not in the tree. The Host says it once; how long it
* stays, where it sits and what happens when a second one arrives while the first is
* still up are the Renderer's to decide.
*
* `handlerId` is 0 when the message has no action, and `action` is then empty.
*/
data class ShowMessage(
val handlerId: Long,
val text: String,
val action: String,
val duration: MessageDuration,
) : Mutation
/**
* One notification to show outside the window.
*
* Not a node: it outlives the component that posted it, and when it goes away is up to
* the user in the notification centre. Posting again under the same `key` replaces it;
* an empty `key` is named by the Renderer and can be neither replaced nor withdrawn. An
* empty action label is no button.
*/
data class PostNotification(
val key: String,
val title: String,
val body: String,
val channel: String,
val action1: String,
val action2: String,
val importance: NotificationImportance,
val presentation: NotificationPresentation,
) : Mutation
/** Takes back the notification posted under `key`, if it is still showing. */
data class WithdrawNotification(val key: String) : Mutation
/** Asks the platform for permission. The answer comes back as an event. */
data object RequestNotificationPermission : Mutation
/**
* An edit the application asks of a code editor: replace a range of the document as it
* stood at `baseVersion` with `text`.
*
* The document is this side's, so this is a request. Where the reader has typed since
* `baseVersion` the range is moved along with what they typed; where they typed in the
* same place the edit is refused and `CodeEditRejected` returns `requestId`. Lines and
* columns count from zero and columns are UTF-16 units.
*/
data class EditCode(
val nodeId: Int,
val requestId: Int,
val baseVersion: Int,
val startLine: Int,
val startColumn: Int,
val endLine: Int,
val endColumn: Int,
val text: String,
) : Mutation
/** One animation to play on the Renderer's own frame clock until it ends. */
data class StartAnimation(val animation: Animation) : Mutation
/**
* Pauses, resumes or cancels the animation under a key, if it is still the one with
* this id. Anything else is left alone: an animation that already ended is not an
* error to stop.
*/
data class ControlAnimation(
val nodeId: Int,
val animationId: Int,
val property: AnimatedProperty,
val slot: Int,
val op: AnimationControl,
val atTimeNanos: Long,
) : Mutation
/**
* Puts a code editor's selection between two places in the document as it stood at
* `baseVersion`. Both ends are moved along with whatever was typed since. An end outside
* the document, or between the two halves of a pair, drops the command and is reported.
*/
data class SetSelection(
val nodeId: Int,
val baseVersion: Int,
val anchorLine: Int,
val anchorColumn: Int,
val activeLine: Int,
val activeColumn: Int,
) : Mutation
/**
* Scrolls a code editor until a range of the document, as it stood at `baseVersion`, is
* on screen. A range inside a folded stretch unfolds it first.
*/
data class RevealRange(
val nodeId: Int,
val baseVersion: Int,
val startLine: Int,
val startColumn: Int,
val endLine: Int,
val endColumn: Int,
val align: RevealAlign,
) : Mutation
/** Folds or unfolds the stretch of a code editor that starts on `startLine`. */
data class SetFolded(val nodeId: Int, val startLine: Int, val collapsed: Boolean) : Mutation
}
sealed interface HostEvent {
val nodeId: Int
val handlerId: Long
data class Clicked(override val nodeId: Int, override val handlerId: Long) : HostEvent
data class TextChanged(override val nodeId: Int, override val handlerId: Long, val text: String) : HostEvent
data class TextSubmitted(override val nodeId: Int, override val handlerId: Long, val text: String) : HostEvent
data class FocusLost(override val nodeId: Int, override val handlerId: Long) : HostEvent
data class ProtocolError(override val nodeId: Int, override val handlerId: Long, val code: Int, val message: String) : HostEvent
data class KeyDown(override val nodeId: Int, override val handlerId: Long, val key: Key, val shiftKey: Boolean, val ctrlKey: Boolean, val altKey: Boolean, val metaKey: Boolean) : HostEvent
data class RangeRequested(override val nodeId: Int, override val handlerId: Long, val start: Int, val count: Int) : HostEvent
data class ValueChanged(override val nodeId: Int, override val handlerId: Long, val value: Double) : HostEvent
data class WindowSizeChanged(override val nodeId: Int, override val handlerId: Long, val widthDp: kotlin.Float, val heightDp: kotlin.Float, val sizeClass: WindowSizeClass, val heightClass: WindowHeightClass) : HostEvent
data class Resync(override val nodeId: Int, override val handlerId: Long) : HostEvent
data class LifecycleStart(override val nodeId: Int, override val handlerId: Long) : HostEvent
data class LifecycleStop(override val nodeId: Int, override val handlerId: Long) : HostEvent
data class DesignSystemResolved(override val nodeId: Int, override val handlerId: Long, val system: DesignSystem) : HostEvent
data class FilesEntered(override val nodeId: Int, override val handlerId: Long) : HostEvent
data class FilesDropped(override val nodeId: Int, override val handlerId: Long, val text: String) : HostEvent
data class NotificationActivated(override val nodeId: Int, override val handlerId: Long, val action: Int, val key: String) : HostEvent
data class NotificationPermissionChanged(override val nodeId: Int, override val handlerId: Long, val state: NotificationPermission) : HostEvent
data class CodeChanged(override val nodeId: Int, override val handlerId: Long, val version: Int, val startLine: Int, val startColumn: Int, val endLine: Int, val endColumn: Int, val text: String) : HostEvent
data class CodeEditRejected(override val nodeId: Int, override val handlerId: Long, val requestId: Int, val baseVersion: Int, val currentVersion: Int, val startLine: Int, val startColumn: Int, val endLine: Int, val endColumn: Int) : HostEvent
data class CodeHovered(override val nodeId: Int, override val handlerId: Long, val decoration: Long, val line: Int, val column: Int, val phase: HoverPhase) : HostEvent
data class CodeSaveRequested(override val nodeId: Int, override val handlerId: Long, val version: Int) : HostEvent
data class DecorationActivated(override val nodeId: Int, override val handlerId: Long, val decoration: Long) : HostEvent
data class AnimationEvent(override val nodeId: Int, override val handlerId: Long, val animationId: Int, val kind: AnimationEventKind, val property: AnimatedProperty, val slot: Int, val iteration: Int, val elapsedMs: kotlin.Float, val timeNanos: Long) : HostEvent
data class ReducedMotionChanged(override val nodeId: Int, override val handlerId: Long, val state: ReducedMotion) : HostEvent
data class Pointer(override val nodeId: Int, override val handlerId: Long, val kind: PointerKind, val pointerId: Int, val pointerType: PointerType, val x: kotlin.Float, val y: kotlin.Float, val buttons: Int, val button: Int, val modifiers: Int, val clickCount: Int, val deltaX: kotlin.Float, val deltaY: kotlin.Float, val deltaMode: WheelDeltaMode, val timestampNanos: Long) : HostEvent
data class ZoomChanged(override val nodeId: Int, override val handlerId: Long, val k: kotlin.Float, val os: kotlin.Float, val level: Int) : HostEvent
data class SelectionChanged(override val nodeId: Int, override val handlerId: Long, val version: Int, val anchorLine: Int, val anchorColumn: Int, val activeLine: Int, val activeColumn: Int) : HostEvent
data class VisibleRangeChanged(override val nodeId: Int, override val handlerId: Long, val version: Int, val firstLine: Int, val lastLine: Int) : HostEvent
data class EditorFocusChanged(override val nodeId: Int, override val handlerId: Long, val focused: Boolean) : HostEvent
data class FoldChanged(override val nodeId: Int, override val handlerId: Long, val startLine: Int, val collapsed: Boolean) : HostEvent
data class CompositionChanged(override val nodeId: Int, override val handlerId: Long, val composing: Boolean) : HostEvent
}
class ProtocolException(message: String, val offset: Int) :
IllegalArgumentException("$message at byte offset $offset")
object Protocol {
const val SCHEMA_HASH: Long = -6175437954945919089L
const val PROTOCOL_VERSION: Int = 1
private const val TAG_ENVELOPE = 0
private const val TAG_CREATE = 1
private const val TAG_SET_PROP = 2
private const val TAG_SET_MODIFIER = 3
private const val TAG_INSERT = 4
private const val TAG_MOVE = 5
private const val TAG_REMOVE = 6
private const val TAG_SET_TEXT = 7
private const val TAG_APPEND_TEXT = 8
private const val TAG_SET_THEME = 9
private const val TAG_REGISTER_ASSET = 10
private const val TAG_RELEASE_ASSET = 11
private const val TAG_SHOW_MESSAGE = 12
private const val TAG_SET_WINDOW = 13
private const val TAG_POST_NOTIFICATION = 14
private const val TAG_WITHDRAW_NOTIFICATION = 15
private const val TAG_REQUEST_NOTIFICATION_PERMISSION = 16
private const val TAG_EDIT_CODE = 17
private const val TAG_START_ANIMATION = 18
private const val TAG_CONTROL_ANIMATION = 19
private const val TAG_SET_SELECTION = 20
private const val TAG_REVEAL_RANGE = 21
private const val TAG_SET_FOLDED = 22
private const val ENVELOPE_LENGTH = 12
private const val ANIMATION_HEADER_LENGTH = 52
private const val KEYFRAME_HEAD_LENGTH = 24
private const val TRANSFORM_FUNCTION_LENGTH = 28
/** Four role tags, one font asset id per type role, then the palette's reference. */
private val THEME_RECORD_LENGTH = 20 + 4 * TypeRole.entries.size
/** Bytes of one palette entry: a role tag, a scheme tag and a colour. */
const val PALETTE_ENTRY_LENGTH = 8
/**
* The high bit of each of eight bytes, which is where UTF-8 stops being ASCII. Written
* as a negative literal because 0x8080808080808080 does not fit a signed `Long`; the
* bits are what matter and they are the same either way.
*/
private const val ASCII_HIGH_BITS = -0x7f7f7f7f7f7f7f80L
private const val VALUE_NONE = 0
private const val VALUE_TEXT = 1
private const val VALUE_BOOL = 2
private const val VALUE_INTEGER = 3
private const val VALUE_FLOAT = 4
private const val VALUE_BYTES = 5
/** Decodes a batch in place. Must not copy the buffer; only strings become Kotlin Strings. */
fun decode(batch: ByteBuffer, onMutation: (Mutation) -> Unit) {
val base = batch.position()
val available = batch.remaining()
val previousOrder = batch.order()
batch.order(ByteOrder.LITTLE_ENDIAN)
try {
requireRange(available, 0, ENVELOPE_LENGTH, 0)
if (readU16(batch, base, available, 0) != TAG_ENVELOPE ||
readU16(batch, base, available, 2) != ENVELOPE_LENGTH
) {
throw ProtocolException("invalid batch envelope", 0)
}
val recordsLengthLong = readU32(batch, base, available, 4)
val countLong = readU32(batch, base, available, 8)
if (recordsLengthLong < ENVELOPE_LENGTH ||
recordsLengthLong > available.toLong() ||
recordsLengthLong % 4L != 0L
) {
throw ProtocolException("invalid records length", 4)
}
if (countLong > Int.MAX_VALUE.toLong()) {
throw ProtocolException("record count is too large", 8)
}
val recordsLength = recordsLengthLong.toInt()
val count = countLong.toInt()
var offset = ENVELOPE_LENGTH
var decoded = 0
while (offset < recordsLength) {
requireRange(recordsLength, offset, 4, offset)
val tag = readU16(batch, base, available, offset)
val length = readU16(batch, base, available, offset + 2)
if (length < 4 || length % 4 != 0 || offset.toLong() + length > recordsLength) {
throw ProtocolException("invalid record length", offset + 2)
}
val mutation = when (tag) {
TAG_CREATE -> {
requireRecordLength(length, 12, offset)
Mutation.Create(
readU32(batch, base, available, offset + 4).toInt(),
widgetKind(readU16(batch, base, available, offset + 8), offset + 8),
)
}
TAG_SET_PROP -> {
requireRecordLength(length, 24, offset)
val value = when (val valueKind = readU16(batch, base, available, offset + 10)) {
VALUE_NONE -> PropertyValue.None
VALUE_TEXT -> PropertyValue.Text(readString(batch, base, available, offset + 12))
VALUE_BOOL -> PropertyValue.Bool(readU64(batch, base, available, offset + 12) != 0L)
VALUE_INTEGER -> PropertyValue.Integer(readU64(batch, base, available, offset + 12))
VALUE_FLOAT -> PropertyValue.Float(
kotlin.Float.fromBits(readU64(batch, base, available, offset + 12).toInt()),
)
VALUE_BYTES -> PropertyValue.Bytes(readBytes(batch, base, available, offset + 12))
else -> throw ProtocolException("unknown property value tag $valueKind", offset + 10)
}
Mutation.SetProp(
readU32(batch, base, available, offset + 4).toInt(),
propertyKind(readU16(batch, base, available, offset + 8), offset + 8),
value,
)
}
TAG_SET_MODIFIER -> {
// The modifier's tag fixes the record's length: 28 bytes, and eight
// more for each word its value takes beyond the first two.
if (length < 28) requireRecordLength(length, 28, offset)
val modifierTag = readU16(batch, base, available, offset + 10)
requireRecordLength(length, 28 + 8 * modifierExtraWords(modifierTag, offset + 10), offset)
Mutation.SetModifier(
readU32(batch, base, available, offset + 4).toInt(),
readU16(batch, base, available, offset + 8),
modifier(modifierTag, batch, base, available, offset + 12, offset + 10),
)
}
TAG_INSERT -> {
requireRecordLength(length, 16, offset)
Mutation.Insert(
readU32(batch, base, available, offset + 4).toInt(),
readU32(batch, base, available, offset + 8).toInt(),
readU32(batch, base, available, offset + 12).toInt(),
)
}
TAG_MOVE -> {
requireRecordLength(length, 16, offset)
Mutation.Move(
readU32(batch, base, available, offset + 4).toInt(),
readU32(batch, base, available, offset + 8).toInt(),
readU32(batch, base, available, offset + 12).toInt(),
)
}
TAG_REMOVE -> {
requireRecordLength(length, 8, offset)
Mutation.Remove(readU32(batch, base, available, offset + 4).toInt())
}
TAG_SET_TEXT -> {
requireRecordLength(length, 24, offset)
Mutation.SetText(
readU32(batch, base, available, offset + 4).toInt(),
readString(batch, base, available, offset + 8),
readU32(batch, base, available, offset + 16).toInt(),
readU32(batch, base, available, offset + 20).toInt(),
)
}
TAG_APPEND_TEXT -> {
requireRecordLength(length, 16, offset)
Mutation.AppendText(
readU32(batch, base, available, offset + 4).toInt(),
readString(batch, base, available, offset + 8),
)
}
TAG_SET_THEME -> {
requireRecordLength(length, THEME_RECORD_LENGTH, offset)
val adaptive = readU16(batch, base, available, offset + 10)
if (adaptive > 1) {
throw ProtocolException("invalid adaptive flag $adaptive", offset + 10)
}
// One slot per type role, in the order the wire fixes them in.
val fonts = List(TypeRole.entries.size) { role ->
readU32(batch, base, available, offset + 12 + 4 * role).toInt()
}
val (entries, problems) = palette(
readBytes(batch, base, available, offset + 12 + 4 * TypeRole.entries.size),
)
Mutation.SetTheme(
Theme(
designSystem(readU16(batch, base, available, offset + 4), offset + 4),
designSystem(readU16(batch, base, available, offset + 6), offset + 6),
colorScheme(readU16(batch, base, available, offset + 8), offset + 8),
adaptive == 1,
fonts,
entries,
problems,
),
)
}
TAG_SET_WINDOW -> {
requireRecordLength(length, 32, offset)
val resizable = readU16(batch, base, available, offset + 16)
if (resizable > 1) {
throw ProtocolException("invalid resizable flag $resizable", offset + 16)
}
// A byte saying whether there is a zoom level, then the level as a
// signed byte.
val zoom = readU16(batch, base, available, offset + 18)
val zoomLevel = when (zoom and 0xFF) {
0 -> null
1 -> (zoom shr 8).toByte().toInt().also { level ->
if (level < -8 || level > 8) {
throw ProtocolException("invalid zoom level $level", offset + 19)
}
}
else -> throw ProtocolException("invalid zoom flag $zoom", offset + 18)
}
Mutation.SetWindow(
Window(
chrome(readU16(batch, base, available, offset + 4), offset + 4),
titleBar(readU16(batch, base, available, offset + 6), offset + 6),
readString(batch, base, available, offset + 20),
readU32(batch, base, available, offset + 28).toInt(),
readU16(batch, base, available, offset + 8),
readU16(batch, base, available, offset + 10),
readU16(batch, base, available, offset + 12),
readU16(batch, base, available, offset + 14),
resizable == 1,
zoomLevel,
),
)
}
TAG_REGISTER_ASSET -> {
requireRecordLength(length, 20, offset)
Mutation.RegisterAsset(
readU32(batch, base, available, offset + 4).toInt(),
readU16(batch, base, available, offset + 8),
readBytes(batch, base, available, offset + 12),
)
}
TAG_RELEASE_ASSET -> {
requireRecordLength(length, 8, offset)
Mutation.ReleaseAsset(readU32(batch, base, available, offset + 4).toInt())
}
TAG_SHOW_MESSAGE -> {
requireRecordLength(length, 32, offset)
Mutation.ShowMessage(
readU64(batch, base, available, offset + 4),
readString(batch, base, available, offset + 12),
readString(batch, base, available, offset + 20),
messageDuration(readU16(batch, base, available, offset + 28), offset + 28),
)
}
TAG_POST_NOTIFICATION -> {
requireRecordLength(length, 56, offset)
Mutation.PostNotification(
readString(batch, base, available, offset + 4),
readString(batch, base, available, offset + 12),
readString(batch, base, available, offset + 20),
readString(batch, base, available, offset + 28),
readString(batch, base, available, offset + 36),
readString(batch, base, available, offset + 44),
notificationImportance(readU16(batch, base, available, offset + 52), offset + 52),
notificationPresentation(readU16(batch, base, available, offset + 54), offset + 54),
)
}
TAG_WITHDRAW_NOTIFICATION -> {
requireRecordLength(length, 12, offset)
Mutation.WithdrawNotification(readString(batch, base, available, offset + 4))
}
TAG_REQUEST_NOTIFICATION_PERMISSION -> {
requireRecordLength(length, 4, offset)
Mutation.RequestNotificationPermission
}
TAG_EDIT_CODE -> {
requireRecordLength(length, 40, offset)
Mutation.EditCode(
readU32(batch, base, available, offset + 4).toInt(),
readU32(batch, base, available, offset + 8).toInt(),
readU32(batch, base, available, offset + 12).toInt(),
readU32(batch, base, available, offset + 16).toInt(),
readU32(batch, base, available, offset + 20).toInt(),
readU32(batch, base, available, offset + 24).toInt(),
readU32(batch, base, available, offset + 28).toInt(),
readString(batch, base, available, offset + 32),
)
}
TAG_START_ANIMATION -> {
if (length < ANIMATION_HEADER_LENGTH) {
requireRecordLength(length, ANIMATION_HEADER_LENGTH, offset)
}
Mutation.StartAnimation(animation(batch, base, available, offset, length))
}
TAG_CONTROL_ANIMATION -> {
requireRecordLength(length, 24, offset)
Mutation.ControlAnimation(
readU32(batch, base, available, offset + 4).toInt(),
readU32(batch, base, available, offset + 8).toInt(),
animatedProperty(readU8(batch, base, available, offset + 12), offset + 12),
readU8(batch, base, available, offset + 13),
animationControl(readU8(batch, base, available, offset + 14), offset + 14),
readU64(batch, base, available, offset + 16),
)
}
TAG_SET_SELECTION -> {
requireRecordLength(length, 28, offset)
Mutation.SetSelection(
readU32(batch, base, available, offset + 4).toInt(),
readU32(batch, base, available, offset + 8).toInt(),
readU32(batch, base, available, offset + 12).toInt(),
readU32(batch, base, available, offset + 16).toInt(),
readU32(batch, base, available, offset + 20).toInt(),
readU32(batch, base, available, offset + 24).toInt(),
)
}
TAG_REVEAL_RANGE -> {
requireRecordLength(length, 32, offset)
Mutation.RevealRange(
readU32(batch, base, available, offset + 4).toInt(),
readU32(batch, base, available, offset + 8).toInt(),
readU32(batch, base, available, offset + 12).toInt(),
readU32(batch, base, available, offset + 16).toInt(),
readU32(batch, base, available, offset + 20).toInt(),
readU32(batch, base, available, offset + 24).toInt(),
revealAlign(readU32(batch, base, available, offset + 28).toInt(), offset + 28),
)
}
TAG_SET_FOLDED -> {
requireRecordLength(length, 16, offset)
Mutation.SetFolded(
readU32(batch, base, available, offset + 4).toInt(),
readU32(batch, base, available, offset + 8).toInt(),
flag(readU32(batch, base, available, offset + 12), offset + 12),
)
}
else -> throw ProtocolException("unknown mutation tag $tag", offset)
}
onMutation(mutation)
decoded += 1
offset += length
}
if (offset != recordsLength || decoded != count) {
throw ProtocolException("record count does not match envelope", offset)
}
} finally {
batch.order(previousOrder)
}
}
/**
* Reads a palette, keeping every sound entry and saying what was wrong with the rest.
*
* Lenient on purpose, unlike the rest of the batch: an unknown role, a scheme that is
* neither light nor dark, a second value for the same role and scheme, or a length
* that is not a whole number of entries is one entry's fault, and the others are
* still what the application asked for.
*/
fun palette(bytes: ByteArray): Pair<List<PaletteEntry>, List<String>> {
if (bytes.isEmpty()) return emptyList<PaletteEntry>() to emptyList()
val buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN)
val entries = ArrayList<PaletteEntry>(bytes.size / PALETTE_ENTRY_LENGTH)
val problems = ArrayList<String>()
if (bytes.size % PALETTE_ENTRY_LENGTH != 0) {
problems += "a palette of ${bytes.size} bytes is not a whole number of " +
"$PALETTE_ENTRY_LENGTH byte entries; the bytes past the last whole one are ignored"
}
for (index in 0 until bytes.size / PALETTE_ENTRY_LENGTH) {
val at = index * PALETTE_ENTRY_LENGTH
val roleTag = buffer.getShort(at).toInt() and 0xffff
val schemeTag = buffer.getShort(at + 2).toInt() and 0xffff
val argb = buffer.getInt(at + 4)
val role = paletteRole(roleTag)
if (role == null) {
problems += "palette entry $index names colour role $roleTag, which does not exist"
continue
}
val dark = when (schemeTag) {
1 -> false
2 -> true
else -> {
problems += "palette entry $index gives $role for scheme $schemeTag; " +
"an entry is for light (1) or dark (2), and following the system is not a scheme"
continue
}
}
if (entries.any { it.role == role && it.dark == dark }) {
problems += "palette entry $index gives $role in ${if (dark) "dark" else "light"} " +
"a second time; the first value stands"
continue
}
entries += PaletteEntry(role, dark, argb)
}
return entries to problems
}
/** Encodes one event into `out` (little-endian), returns bytes written. */
fun encodeEvent(event: HostEvent, out: ByteBuffer): Int {
val start = out.position()
val previousOrder = out.order()
out.order(ByteOrder.LITTLE_ENDIAN)
try {
val text = when (event) {
is HostEvent.Clicked -> null
is HostEvent.TextChanged -> event.text.toByteArray(StandardCharsets.UTF_8)
is HostEvent.TextSubmitted -> event.text.toByteArray(StandardCharsets.UTF_8)
is HostEvent.FocusLost -> null
is HostEvent.ProtocolError -> event.message.toByteArray(StandardCharsets.UTF_8)
is HostEvent.KeyDown -> null
is HostEvent.RangeRequested -> null
is HostEvent.ValueChanged -> null
is HostEvent.WindowSizeChanged -> null
is HostEvent.Resync -> null
is HostEvent.LifecycleStart -> null
is HostEvent.LifecycleStop -> null
is HostEvent.DesignSystemResolved -> null
is HostEvent.FilesEntered -> null
is HostEvent.FilesDropped -> event.text.toByteArray(StandardCharsets.UTF_8)
is HostEvent.NotificationActivated -> event.key.toByteArray(StandardCharsets.UTF_8)
is HostEvent.NotificationPermissionChanged -> null
is HostEvent.CodeChanged -> event.text.toByteArray(StandardCharsets.UTF_8)
is HostEvent.CodeEditRejected -> null
is HostEvent.CodeHovered -> null
is HostEvent.CodeSaveRequested -> null
is HostEvent.DecorationActivated -> null
is HostEvent.AnimationEvent -> null
is HostEvent.ReducedMotionChanged -> null
is HostEvent.Pointer -> null
is HostEvent.ZoomChanged -> null
is HostEvent.SelectionChanged -> null
is HostEvent.VisibleRangeChanged -> null
is HostEvent.EditorFocusChanged -> null
is HostEvent.FoldChanged -> null
is HostEvent.CompositionChanged -> null
}
val recordLength = when (event) {
is HostEvent.Clicked -> 16
is HostEvent.TextChanged -> 24
is HostEvent.TextSubmitted -> 24
is HostEvent.FocusLost -> 16
is HostEvent.ProtocolError -> 28
is HostEvent.KeyDown -> 20
is HostEvent.RangeRequested -> 24
is HostEvent.ValueChanged -> 24
is HostEvent.WindowSizeChanged -> 32
is HostEvent.Resync -> 16
is HostEvent.LifecycleStart -> 16
is HostEvent.LifecycleStop -> 16
is HostEvent.DesignSystemResolved -> 20
is HostEvent.FilesEntered -> 16
is HostEvent.FilesDropped -> 24
is HostEvent.NotificationActivated -> 28
is HostEvent.NotificationPermissionChanged -> 20
is HostEvent.CodeChanged -> 44
is HostEvent.CodeEditRejected -> 44
is HostEvent.CodeHovered -> 36
is HostEvent.CodeSaveRequested -> 24
is HostEvent.DecorationActivated -> 24
is HostEvent.AnimationEvent -> 40
is HostEvent.ReducedMotionChanged -> 20
is HostEvent.Pointer -> 72
is HostEvent.ZoomChanged -> 28
is HostEvent.SelectionChanged -> 36
is HostEvent.VisibleRangeChanged -> 28
is HostEvent.EditorFocusChanged -> 24
is HostEvent.FoldChanged -> 24
is HostEvent.CompositionChanged -> 24
}
val totalLength = recordLength.toLong() + (text?.size ?: 0)
if (totalLength > Int.MAX_VALUE || totalLength > out.remaining().toLong()) {
throw ProtocolException("event output buffer is too small", 0)
}
val tag = when (event) {
is HostEvent.Clicked -> 1
is HostEvent.TextChanged -> 2
is HostEvent.TextSubmitted -> 3
is HostEvent.FocusLost -> 4
is HostEvent.ProtocolError -> 5
is HostEvent.KeyDown -> 6
is HostEvent.RangeRequested -> 7
is HostEvent.ValueChanged -> 16
is HostEvent.WindowSizeChanged -> 17
is HostEvent.Resync -> 18
is HostEvent.LifecycleStart -> 19
is HostEvent.LifecycleStop -> 20
is HostEvent.DesignSystemResolved -> 21
is HostEvent.FilesEntered -> 22
is HostEvent.FilesDropped -> 23
is HostEvent.NotificationActivated -> 24
is HostEvent.NotificationPermissionChanged -> 25
is HostEvent.CodeChanged -> 26
is HostEvent.CodeEditRejected -> 27
is HostEvent.CodeHovered -> 28
is HostEvent.CodeSaveRequested -> 29
is HostEvent.DecorationActivated -> 30
is HostEvent.AnimationEvent -> 31
is HostEvent.ReducedMotionChanged -> 32
is HostEvent.Pointer -> 8
is HostEvent.ZoomChanged -> 33
is HostEvent.SelectionChanged -> 34
is HostEvent.VisibleRangeChanged -> 35
is HostEvent.EditorFocusChanged -> 36
is HostEvent.FoldChanged -> 37
is HostEvent.CompositionChanged -> 38
}
out.putShort(tag.toShort())
out.putShort(recordLength.toShort())
out.putInt(event.nodeId)
out.putLong(event.handlerId)
when (event) {
is HostEvent.Clicked -> Unit
is HostEvent.TextChanged -> writeStringReference(out, recordLength, text!!)
is HostEvent.TextSubmitted -> writeStringReference(out, recordLength, text!!)
is HostEvent.FocusLost -> Unit
is HostEvent.ProtocolError -> {
out.putInt(event.code)
writeStringReference(out, recordLength, text!!)
}
is HostEvent.KeyDown -> {
out.putShort(keyTag(event.key).toShort())
var modifiers = 0
if (event.shiftKey) modifiers = modifiers or 0x01
if (event.ctrlKey) modifiers = modifiers or 0x02
if (event.altKey) modifiers = modifiers or 0x04
if (event.metaKey) modifiers = modifiers or 0x08
out.put(modifiers.toByte())
out.put(0.toByte())
}
is HostEvent.RangeRequested -> {
out.putInt(event.start)
out.putInt(event.count)
}
is HostEvent.ValueChanged -> out.putDouble(event.value)
is HostEvent.WindowSizeChanged -> {
out.putFloat(event.widthDp)
out.putFloat(event.heightDp)
out.putInt(windowSizeClassTag(event.sizeClass))
out.putInt(windowHeightClassTag(event.heightClass))
}
is HostEvent.Resync -> Unit
is HostEvent.LifecycleStart -> Unit
is HostEvent.LifecycleStop -> Unit
is HostEvent.DesignSystemResolved -> {
out.putInt(designSystemTag(event.system))
}
is HostEvent.FilesEntered -> Unit
is HostEvent.FilesDropped -> writeStringReference(out, recordLength, text!!)
is HostEvent.NotificationActivated -> {
out.putInt(event.action)
writeStringReference(out, recordLength, text!!)
}
is HostEvent.NotificationPermissionChanged -> {
out.putInt(notificationPermissionTag(event.state))
}
is HostEvent.CodeChanged -> {
out.putInt(event.version)
out.putInt(event.startLine)
out.putInt(event.startColumn)
out.putInt(event.endLine)
out.putInt(event.endColumn)
writeStringReference(out, recordLength, text!!)
}
is HostEvent.CodeEditRejected -> {
out.putInt(event.requestId)
out.putInt(event.baseVersion)
out.putInt(event.currentVersion)
out.putInt(event.startLine)
out.putInt(event.startColumn)
out.putInt(event.endLine)
out.putInt(event.endColumn)
}
is HostEvent.CodeHovered -> {
out.putLong(event.decoration)
out.putInt(event.line)
out.putInt(event.column)
out.putInt(hoverPhaseTag(event.phase))
}
is HostEvent.CodeSaveRequested -> {
out.putInt(event.version)
out.putInt(0)
}
is HostEvent.DecorationActivated -> out.putLong(event.decoration)
is HostEvent.AnimationEvent -> {
out.putInt(event.animationId)
out.put(animationEventKindTag(event.kind).toByte())
out.put(animatedPropertyTag(event.property).toByte())
out.put(event.slot.toByte())
out.put(0.toByte())
out.putInt(event.iteration)
out.putFloat(event.elapsedMs)
out.putLong(event.timeNanos)
}
is HostEvent.ReducedMotionChanged -> {
out.putInt(reducedMotionTag(event.state))
}
is HostEvent.Pointer -> {
out.putInt(pointerKindTag(event.kind))
out.putInt(event.pointerId)
out.putInt(pointerTypeTag(event.pointerType))
out.putFloat(event.x)
out.putFloat(event.y)
out.putInt(event.buttons)
out.putInt(event.button)
out.putInt(event.modifiers)
out.putInt(event.clickCount)
out.putFloat(event.deltaX)
out.putFloat(event.deltaY)
out.putInt(wheelDeltaModeTag(event.deltaMode))
out.putLong(event.timestampNanos)
}
is HostEvent.ZoomChanged -> {
out.putFloat(event.k)
out.putFloat(event.os)
out.putInt(event.level)
}
is HostEvent.SelectionChanged -> {
out.putInt(event.version)
out.putInt(event.anchorLine)
out.putInt(event.anchorColumn)
out.putInt(event.activeLine)
out.putInt(event.activeColumn)
}
is HostEvent.VisibleRangeChanged -> {
out.putInt(event.version)
out.putInt(event.firstLine)
out.putInt(event.lastLine)
}
is HostEvent.EditorFocusChanged -> {
out.putInt(if (event.focused) 1 else 0)
out.putInt(0)
}
is HostEvent.FoldChanged -> {
out.putInt(event.startLine)
out.putInt(if (event.collapsed) 1 else 0)
}
is HostEvent.CompositionChanged -> {
out.putInt(if (event.composing) 1 else 0)
out.putInt(0)
}
}
if (text != null) out.put(text)
return out.position() - start
} finally {
out.order(previousOrder)
}
}
/**
* Handshake payload the Renderer sends to compose_rust_host_init.
*
* `loopMode` says who owns the frame loop: the Renderer on desktop, the platform on
* Android, iOS and the web.
*/
fun handshake(out: ByteBuffer, loopMode: LoopMode = LoopMode.Renderer): Int {
val start = out.position()
if (out.remaining() < 12) {
throw ProtocolException("handshake output buffer is too small", 0)
}
val previousOrder = out.order()
out.order(ByteOrder.LITTLE_ENDIAN)
try {
out.putLong(SCHEMA_HASH)
out.putShort(PROTOCOL_VERSION.toShort())
out.put(loopMode.wire)
out.put(0.toByte()) // Reserved for alignment.
return out.position() - start
} finally {
out.order(previousOrder)
}
}
private fun writeStringReference(out: ByteBuffer, offset: Int, bytes: ByteArray) {
out.putInt(offset)
out.putInt(bytes.size)
}
private fun widgetKind(tag: Int, offset: Int): WidgetKind = when (tag) {
1 -> WidgetKind.Column
2 -> WidgetKind.Row
3 -> WidgetKind.Box
4 -> WidgetKind.Text
5 -> WidgetKind.TextField
6 -> WidgetKind.Button
7 -> WidgetKind.Spacer
8 -> WidgetKind.LazyColumn
9 -> WidgetKind.ScrollColumn
10 -> WidgetKind.Image
11 -> WidgetKind.Icon
12 -> WidgetKind.Checkbox
13 -> WidgetKind.RadioButton
14 -> WidgetKind.Switch
15 -> WidgetKind.Slider
16 -> WidgetKind.ProgressIndicator
17 -> WidgetKind.Divider
18 -> WidgetKind.Card
19 -> WidgetKind.Surface
20 -> WidgetKind.Dialog
21 -> WidgetKind.Menu
22 -> WidgetKind.Tabs
23 -> WidgetKind.TopAppBar
24 -> WidgetKind.LazyRow
25 -> WidgetKind.Tooltip
26 -> WidgetKind.Canvas
27 -> WidgetKind.DatePicker
28 -> WidgetKind.TimePicker
29 -> WidgetKind.Dropdown
30 -> WidgetKind.Navigation
31 -> WidgetKind.NavigationItem
32 -> WidgetKind.Sheet
33 -> WidgetKind.Scaffold
34 -> WidgetKind.ScaffoldSlot
35 -> WidgetKind.LazyGrid
36 -> WidgetKind.FileDropTarget
37 -> WidgetKind.ScrollRow
38 -> WidgetKind.Chip
39 -> WidgetKind.FloatingAction
40 -> WidgetKind.Badge
41 -> WidgetKind.SelectionContainer
42 -> WidgetKind.SplitPane
43 -> WidgetKind.CodeEditor
44 -> WidgetKind.AbsoluteBox
100 -> WidgetKind.LinearProgressIndicator
else -> throw ProtocolException("unknown widget tag $tag", offset)
}
private fun propertyKind(tag: Int, offset: Int): PropertyKind = when (tag) {
1 -> PropertyKind.Text
2 -> PropertyKind.Placeholder
3 -> PropertyKind.Enabled
4 -> PropertyKind.Multiline
5 -> PropertyKind.OnClick
6 -> PropertyKind.OnValueChange
7 -> PropertyKind.OnSubmit
8 -> PropertyKind.OnFocusLost
9 -> PropertyKind.OnKeyDown
10 -> PropertyKind.ItemCount
11 -> PropertyKind.ItemKey
12 -> PropertyKind.OnRangeRequested
13 -> PropertyKind.TypeRole
14 -> PropertyKind.FontSize
15 -> PropertyKind.FontWeight
16 -> PropertyKind.LineHeight
17 -> PropertyKind.LetterSpacing
18 -> PropertyKind.Color
19 -> PropertyKind.TextAlign
20 -> PropertyKind.MaxLines
21 -> PropertyKind.Overflow
22 -> PropertyKind.Arrangement
23 -> PropertyKind.Spacing
24 -> PropertyKind.SpaceRole
25 -> PropertyKind.Alignment
26 -> PropertyKind.Variant
28 -> PropertyKind.Asset
32 -> PropertyKind.Checked
33 -> PropertyKind.Steps
34 -> PropertyKind.Determinate
35 -> PropertyKind.Circular
36 -> PropertyKind.Vertical
40 -> PropertyKind.Open
41 -> PropertyKind.OnDismiss
42 -> PropertyKind.SelectedIndex
50 -> PropertyKind.Commands
51 -> PropertyKind.Value
52 -> PropertyKind.Min
53 -> PropertyKind.Max
60 -> PropertyKind.Icon
61 -> PropertyKind.Slot
62 -> PropertyKind.Columns
63 -> PropertyKind.MinColumnWidth
64 -> PropertyKind.Spans
65 -> PropertyKind.OnFilesEntered
66 -> PropertyKind.OnFilesDropped
76 -> PropertyKind.Section
80 -> PropertyKind.Count
90 -> PropertyKind.Collapsible
75 -> PropertyKind.ButtonKind
100 -> PropertyKind.Decorations
101 -> PropertyKind.SyntaxSpans
102 -> PropertyKind.TabWidth
103 -> PropertyKind.OnEditRejected
104 -> PropertyKind.OnHover
105 -> PropertyKind.OnSave
106 -> PropertyKind.OnDecorationClick
107 -> PropertyKind.Folds
108 -> PropertyKind.GutterDecorations
109 -> PropertyKind.OnSelectionChange
110 -> PropertyKind.Font
111 -> PropertyKind.WordBreak
112 -> PropertyKind.OverflowWrap
113 -> PropertyKind.TabSize
114 -> PropertyKind.AbsoluteSize
115 -> PropertyKind.SoftWrap
116 -> PropertyKind.SpanFonts
130 -> PropertyKind.OnVisibleRangeChange
131 -> PropertyKind.OnEditorFocusChange
132 -> PropertyKind.OnFoldChange
133 -> PropertyKind.OnCompositionChange
67 -> PropertyKind.OnPointer
140 -> PropertyKind.PointerKinds
141 -> PropertyKind.Cursor
142 -> PropertyKind.UserSelect
120 -> PropertyKind.IslandZoom
27 -> PropertyKind.Progress
else -> throw ProtocolException("unknown property tag $tag", offset)
}
private fun keyTag(key: Key): Int = when (key) {
Key.Enter -> 1
}
private fun windowSizeClassTag(sizeClass: WindowSizeClass): Int = when (sizeClass) {
WindowSizeClass.Compact -> 0
WindowSizeClass.Medium -> 1
WindowSizeClass.Expanded -> 2
}
private fun windowHeightClassTag(heightClass: WindowHeightClass): Int = when (heightClass) {
WindowHeightClass.Compact -> 0
WindowHeightClass.Medium -> 1
WindowHeightClass.Expanded -> 2
}
private fun designSystemTag(system: DesignSystem): Int = when (system) {
DesignSystem.Material3 -> 1
DesignSystem.Cupertino -> 2
DesignSystem.Fluent -> 3
DesignSystem.Gnome -> 4
DesignSystem.Breeze -> 5
DesignSystem.Deepin -> 6
DesignSystem.LiquidGlass -> 7
}
private fun hoverPhaseTag(phase: HoverPhase): Int = when (phase) {
HoverPhase.Rest -> 1
HoverPhase.Leave -> 2
}
private fun notificationPermissionTag(state: NotificationPermission): Int = when (state) {
NotificationPermission.NotDetermined -> 1
NotificationPermission.Granted -> 2
NotificationPermission.Denied -> 3
NotificationPermission.Unsupported -> 4
}
private fun animationEventKindTag(value: AnimationEventKind): Int = when (value) {
AnimationEventKind.Ready -> 1
AnimationEventKind.Active -> 2
AnimationEventKind.Iteration -> 3
AnimationEventKind.End -> 4
}
private fun animatedPropertyTag(value: AnimatedProperty): Int = when (value) {
AnimatedProperty.Alpha -> 1
AnimatedProperty.Color -> 2
AnimatedProperty.Background -> 3
AnimatedProperty.Transform -> 4
AnimatedProperty.BorderColor -> 5
}
private fun reducedMotionTag(value: ReducedMotion): Int = when (value) {
ReducedMotion.Off -> 1
ReducedMotion.On -> 2
ReducedMotion.Unknown -> 3
}
private fun pointerKindTag(value: PointerKind): Int = when (value) {
PointerKind.Move -> 1
PointerKind.Enter -> 2
PointerKind.Leave -> 3
PointerKind.Down -> 4
PointerKind.Up -> 5
PointerKind.Cancel -> 6
PointerKind.Wheel -> 7
PointerKind.ContextMenu -> 8
}
private fun pointerTypeTag(value: PointerType): Int = when (value) {
PointerType.Mouse -> 1
PointerType.Touch -> 2
PointerType.Pen -> 3
}
private fun wheelDeltaModeTag(value: WheelDeltaMode): Int = when (value) {
WheelDeltaMode.Pixel -> 0
WheelDeltaMode.Line -> 1
WheelDeltaMode.Page -> 2
}
private fun paletteRole(tag: Int): ColorRole? = when (tag) {
1 -> ColorRole.Primary
2 -> ColorRole.OnPrimary
3 -> ColorRole.Secondary
4 -> ColorRole.OnSecondary
5 -> ColorRole.Surface
6 -> ColorRole.OnSurface
7 -> ColorRole.SurfaceVariant
8 -> ColorRole.OnSurfaceVariant
9 -> ColorRole.Background
10 -> ColorRole.OnBackground
11 -> ColorRole.Outline
12 -> ColorRole.OutlineVariant
13 -> ColorRole.Error
14 -> ColorRole.OnError
15 -> ColorRole.SurfaceContainer
16 -> ColorRole.Tertiary
17 -> ColorRole.OnTertiary
18 -> ColorRole.PrimaryContainer
19 -> ColorRole.OnPrimaryContainer
20 -> ColorRole.SecondaryContainer
21 -> ColorRole.OnSecondaryContainer
22 -> ColorRole.TertiaryContainer
23 -> ColorRole.OnTertiaryContainer
24 -> ColorRole.SyntaxKeyword
25 -> ColorRole.SyntaxString
26 -> ColorRole.SyntaxComment
27 -> ColorRole.SyntaxNumber
28 -> ColorRole.SyntaxConstant
29 -> ColorRole.SyntaxType
30 -> ColorRole.SyntaxFunction
31 -> ColorRole.SyntaxVariable
32 -> ColorRole.SyntaxProperty
33 -> ColorRole.SyntaxOperator
34 -> ColorRole.SyntaxPunctuation
35 -> ColorRole.SyntaxTag
36 -> ColorRole.SyntaxAttribute
37 -> ColorRole.SyntaxEscape
38 -> ColorRole.SyntaxMacro
39 -> ColorRole.DiffAdded
40 -> ColorRole.DiffRemoved
41 -> ColorRole.DiffModified
42 -> ColorRole.DiffAddedContainer
43 -> ColorRole.DiffRemovedContainer
44 -> ColorRole.DiffAddedEmphasis
45 -> ColorRole.DiffRemovedEmphasis
else -> null
}
private fun colorRole(tag: Int, offset: Int): ColorRole = when (tag) {
1 -> ColorRole.Primary
2 -> ColorRole.OnPrimary
3 -> ColorRole.Secondary
4 -> ColorRole.OnSecondary
5 -> ColorRole.Surface
6 -> ColorRole.OnSurface
7 -> ColorRole.SurfaceVariant
8 -> ColorRole.OnSurfaceVariant
9 -> ColorRole.Background
10 -> ColorRole.OnBackground
11 -> ColorRole.Outline
12 -> ColorRole.OutlineVariant
13 -> ColorRole.Error
14 -> ColorRole.OnError
15 -> ColorRole.SurfaceContainer
16 -> ColorRole.Tertiary
17 -> ColorRole.OnTertiary
18 -> ColorRole.PrimaryContainer
19 -> ColorRole.OnPrimaryContainer
20 -> ColorRole.SecondaryContainer
21 -> ColorRole.OnSecondaryContainer
22 -> ColorRole.TertiaryContainer
23 -> ColorRole.OnTertiaryContainer
24 -> ColorRole.SyntaxKeyword
25 -> ColorRole.SyntaxString
26 -> ColorRole.SyntaxComment
27 -> ColorRole.SyntaxNumber
28 -> ColorRole.SyntaxConstant
29 -> ColorRole.SyntaxType
30 -> ColorRole.SyntaxFunction
31 -> ColorRole.SyntaxVariable
32 -> ColorRole.SyntaxProperty
33 -> ColorRole.SyntaxOperator
34 -> ColorRole.SyntaxPunctuation
35 -> ColorRole.SyntaxTag
36 -> ColorRole.SyntaxAttribute
37 -> ColorRole.SyntaxEscape
38 -> ColorRole.SyntaxMacro
39 -> ColorRole.DiffAdded
40 -> ColorRole.DiffRemoved
41 -> ColorRole.DiffModified
42 -> ColorRole.DiffAddedContainer
43 -> ColorRole.DiffRemovedContainer
44 -> ColorRole.DiffAddedEmphasis
45 -> ColorRole.DiffRemovedEmphasis
else -> throw ProtocolException("unknown ColorRole tag $tag", offset)
}
private fun typeRole(tag: Int, offset: Int): TypeRole = when (tag) {
1 -> TypeRole.Display
2 -> TypeRole.Headline
3 -> TypeRole.Title
4 -> TypeRole.Subtitle
5 -> TypeRole.Body
6 -> TypeRole.BodyStrong
7 -> TypeRole.Label
8 -> TypeRole.Caption
9 -> TypeRole.Mono
else -> throw ProtocolException("unknown TypeRole tag $tag", offset)
}
private fun shapeRole(tag: Int, offset: Int): ShapeRole = when (tag) {
1 -> ShapeRole.None
2 -> ShapeRole.ExtraSmall
3 -> ShapeRole.Small
4 -> ShapeRole.Medium
5 -> ShapeRole.Large
6 -> ShapeRole.Full
else -> throw ProtocolException("unknown ShapeRole tag $tag", offset)
}
private fun motionRole(tag: Int, offset: Int): MotionRole = when (tag) {
1 -> MotionRole.Instant
2 -> MotionRole.Quick
3 -> MotionRole.Standard
4 -> MotionRole.Slow
5 -> MotionRole.Emphasized
else -> throw ProtocolException("unknown MotionRole tag $tag", offset)
}
private fun tileMode(tag: Int, offset: Int): TileMode = when (tag) {
1 -> TileMode.Clamp
2 -> TileMode.Repeat
3 -> TileMode.Mirror
else -> throw ProtocolException("unknown TileMode tag $tag", offset)
}
private fun materialRole(tag: Int, offset: Int): MaterialRole = when (tag) {
1 -> MaterialRole.Thin
2 -> MaterialRole.Regular
3 -> MaterialRole.Thick
4 -> MaterialRole.Chrome
else -> throw ProtocolException("unknown MaterialRole tag $tag", offset)
}
private fun spaceRole(tag: Int, offset: Int): SpaceRole = when (tag) {
1 -> SpaceRole.None
2 -> SpaceRole.Xs
3 -> SpaceRole.Sm
4 -> SpaceRole.Md
5 -> SpaceRole.Lg
6 -> SpaceRole.Xl
7 -> SpaceRole.Xxl
else -> throw ProtocolException("unknown SpaceRole tag $tag", offset)
}
private fun textAlign(tag: Int, offset: Int): TextAlign = when (tag) {
1 -> TextAlign.Start
2 -> TextAlign.Center
3 -> TextAlign.End
4 -> TextAlign.Justify
else -> throw ProtocolException("unknown TextAlign tag $tag", offset)
}
private fun textOverflow(tag: Int, offset: Int): TextOverflow = when (tag) {
1 -> TextOverflow.Clip
2 -> TextOverflow.Ellipsis
3 -> TextOverflow.Visible
else -> throw ProtocolException("unknown TextOverflow tag $tag", offset)
}
private fun arrangement(tag: Int, offset: Int): Arrangement = when (tag) {
1 -> Arrangement.Start
2 -> Arrangement.Center
3 -> Arrangement.End
4 -> Arrangement.SpaceBetween
5 -> Arrangement.SpaceAround
6 -> Arrangement.SpaceEvenly
else -> throw ProtocolException("unknown Arrangement tag $tag", offset)
}
private fun alignment(tag: Int, offset: Int): Alignment = when (tag) {
1 -> Alignment.TopStart
2 -> Alignment.TopCenter
3 -> Alignment.TopEnd
4 -> Alignment.CenterStart
5 -> Alignment.Center
6 -> Alignment.CenterEnd
7 -> Alignment.BottomStart
8 -> Alignment.BottomCenter
9 -> Alignment.BottomEnd
else -> throw ProtocolException("unknown Alignment tag $tag", offset)
}
private fun buttonVariant(tag: Int, offset: Int): ButtonVariant = when (tag) {
1 -> ButtonVariant.Filled
2 -> ButtonVariant.Tonal
3 -> ButtonVariant.Outlined
4 -> ButtonVariant.Text
5 -> ButtonVariant.Operator
else -> throw ProtocolException("unknown ButtonVariant tag $tag", offset)
}
private fun buttonKind(tag: Int, offset: Int): ButtonKind = when (tag) {
1 -> ButtonKind.Standard
2 -> ButtonKind.ActionKey
else -> throw ProtocolException("unknown ButtonKind tag $tag", offset)
}
private fun designSystem(tag: Int, offset: Int): DesignSystem = when (tag) {
1 -> DesignSystem.Material3
2 -> DesignSystem.Cupertino
3 -> DesignSystem.Fluent
4 -> DesignSystem.Gnome
5 -> DesignSystem.Breeze
6 -> DesignSystem.Deepin
7 -> DesignSystem.LiquidGlass
else -> throw ProtocolException("unknown DesignSystem tag $tag", offset)
}
private fun colorScheme(tag: Int, offset: Int): ColorScheme = when (tag) {
1 -> ColorScheme.Light
2 -> ColorScheme.Dark
3 -> ColorScheme.FollowSystem
else -> throw ProtocolException("unknown ColorScheme tag $tag", offset)
}
private fun assetKind(tag: Int, offset: Int): AssetKind = when (tag) {
1 -> AssetKind.Png
2 -> AssetKind.Jpeg
3 -> AssetKind.Svg
4 -> AssetKind.VectorIcon
5 -> AssetKind.Font
6 -> AssetKind.Brush
else -> throw ProtocolException("unknown AssetKind tag $tag", offset)
}
private fun iconRole(tag: Int, offset: Int): IconRole = when (tag) {
1 -> IconRole.Back
2 -> IconRole.Forward
3 -> IconRole.Close
4 -> IconRole.Search
5 -> IconRole.Add
6 -> IconRole.Check
7 -> IconRole.Settings
8 -> IconRole.More
9 -> IconRole.Home
10 -> IconRole.List
11 -> IconRole.Inbox
12 -> IconRole.Menu
13 -> IconRole.History
14 -> IconRole.Send
15 -> IconRole.Mic
16 -> IconRole.Image
17 -> IconRole.Video
18 -> IconRole.Library
19 -> IconRole.Sidebar
20 -> IconRole.Compose
21 -> IconRole.Collapse
else -> throw ProtocolException("unknown IconRole tag $tag", offset)
}
private fun messageDuration(tag: Int, offset: Int): MessageDuration = when (tag) {
1 -> MessageDuration.Short
2 -> MessageDuration.Long
else -> throw ProtocolException("unknown MessageDuration tag $tag", offset)
}
private fun chrome(tag: Int, offset: Int): Chrome = when (tag) {
1 -> Chrome.Modern
2 -> Chrome.System
else -> throw ProtocolException("unknown Chrome tag $tag", offset)
}
private fun titleBar(tag: Int, offset: Int): TitleBar = when (tag) {
1 -> TitleBar.Normal
2 -> TitleBar.Simple
else -> throw ProtocolException("unknown TitleBar tag $tag", offset)
}
private fun slotRole(tag: Int, offset: Int): SlotRole = when (tag) {
1 -> SlotRole.TopBar
2 -> SlotRole.BottomBar
3 -> SlotRole.FloatingAction
4 -> SlotRole.Content
else -> throw ProtocolException("unknown SlotRole tag $tag", offset)
}
private fun notificationImportance(tag: Int, offset: Int): NotificationImportance = when (tag) {
1 -> NotificationImportance.Normal
2 -> NotificationImportance.Urgent
else -> throw ProtocolException("unknown NotificationImportance tag $tag", offset)
}
private fun notificationPresentation(tag: Int, offset: Int): NotificationPresentation = when (tag) {
1 -> NotificationPresentation.Always
2 -> NotificationPresentation.WhenInactive
else -> throw ProtocolException("unknown NotificationPresentation tag $tag", offset)
}
private fun notificationPermission(tag: Int, offset: Int): NotificationPermission = when (tag) {
1 -> NotificationPermission.NotDetermined
2 -> NotificationPermission.Granted
3 -> NotificationPermission.Denied
4 -> NotificationPermission.Unsupported
else -> throw ProtocolException("unknown NotificationPermission tag $tag", offset)
}
private fun wordBreak(tag: Int, offset: Int): WordBreak = when (tag) {
1 -> WordBreak.Normal
2 -> WordBreak.KeepAll
3 -> WordBreak.BreakAll
else -> throw ProtocolException("unknown WordBreak tag $tag", offset)
}
private fun overflowWrap(tag: Int, offset: Int): OverflowWrap = when (tag) {
1 -> OverflowWrap.Normal
2 -> OverflowWrap.Anywhere
3 -> OverflowWrap.BreakWord
else -> throw ProtocolException("unknown OverflowWrap tag $tag", offset)
}
private fun genericFamily(tag: Int, offset: Int): GenericFamily = when (tag) {
1 -> GenericFamily.SystemUi
2 -> GenericFamily.SansSerif
3 -> GenericFamily.Serif
4 -> GenericFamily.Monospace
else -> throw ProtocolException("unknown GenericFamily tag $tag", offset)
}
private fun animatedProperty(tag: Int, offset: Int): AnimatedProperty = when (tag) {
1 -> AnimatedProperty.Alpha
2 -> AnimatedProperty.Color
3 -> AnimatedProperty.Background
4 -> AnimatedProperty.Transform
5 -> AnimatedProperty.BorderColor
else -> throw ProtocolException("unknown AnimatedProperty tag $tag", offset)
}
private fun playbackDirection(tag: Int, offset: Int): PlaybackDirection = when (tag) {
1 -> PlaybackDirection.Normal
2 -> PlaybackDirection.Reverse
3 -> PlaybackDirection.Alternate
4 -> PlaybackDirection.AlternateReverse
else -> throw ProtocolException("unknown PlaybackDirection tag $tag", offset)
}
private fun fillMode(tag: Int, offset: Int): FillMode = when (tag) {
1 -> FillMode.None
2 -> FillMode.Forwards
3 -> FillMode.Backwards
4 -> FillMode.Both
else -> throw ProtocolException("unknown FillMode tag $tag", offset)
}
private fun playState(tag: Int, offset: Int): PlayState = when (tag) {
1 -> PlayState.Running
2 -> PlayState.Paused
else -> throw ProtocolException("unknown PlayState tag $tag", offset)
}
private fun colorInterpolation(tag: Int, offset: Int): ColorInterpolation = when (tag) {
1 -> ColorInterpolation.SrgbPremultiplied
2 -> ColorInterpolation.Oklab
else -> throw ProtocolException("unknown ColorInterpolation tag $tag", offset)
}
private fun timingKind(tag: Int, offset: Int): TimingKind = when (tag) {
1 -> TimingKind.Linear
2 -> TimingKind.CubicBezier
3 -> TimingKind.Steps
else -> throw ProtocolException("unknown TimingKind tag $tag", offset)
}
private fun stepPosition(tag: Int, offset: Int): StepPosition = when (tag) {
1 -> StepPosition.JumpStart
2 -> StepPosition.JumpEnd
3 -> StepPosition.JumpNone
4 -> StepPosition.JumpBoth
else -> throw ProtocolException("unknown StepPosition tag $tag", offset)
}
private fun transformFunctionKind(tag: Int, offset: Int): TransformFunctionKind = when (tag) {
1 -> TransformFunctionKind.Translate
2 -> TransformFunctionKind.Rotate
3 -> TransformFunctionKind.Scale
4 -> TransformFunctionKind.Skew
5 -> TransformFunctionKind.Matrix
else -> throw ProtocolException("unknown TransformFunctionKind tag $tag", offset)
}
private fun animationControl(tag: Int, offset: Int): AnimationControl = when (tag) {
1 -> AnimationControl.Pause
2 -> AnimationControl.Resume
3 -> AnimationControl.Cancel
else -> throw ProtocolException("unknown AnimationControl tag $tag", offset)
}
private fun animationEventKind(tag: Int, offset: Int): AnimationEventKind = when (tag) {
1 -> AnimationEventKind.Ready
2 -> AnimationEventKind.Active
3 -> AnimationEventKind.Iteration
4 -> AnimationEventKind.End
else -> throw ProtocolException("unknown AnimationEventKind tag $tag", offset)
}
private fun reducedMotion(tag: Int, offset: Int): ReducedMotion = when (tag) {
1 -> ReducedMotion.Off
2 -> ReducedMotion.On
3 -> ReducedMotion.Unknown
else -> throw ProtocolException("unknown ReducedMotion tag $tag", offset)
}
private fun decorationKind(tag: Int, offset: Int): DecorationKind = when (tag) {
1 -> DecorationKind.Underline
2 -> DecorationKind.CodeLens
3 -> DecorationKind.HoverAnchor
4 -> DecorationKind.GhostText
else -> throw ProtocolException("unknown DecorationKind tag $tag", offset)
}
private fun severity(tag: Int, offset: Int): Severity = when (tag) {
1 -> Severity.Error
2 -> Severity.Warning
3 -> Severity.Information
4 -> Severity.Hint
else -> throw ProtocolException("unknown Severity tag $tag", offset)
}
private fun hoverPhase(tag: Int, offset: Int): HoverPhase = when (tag) {
1 -> HoverPhase.Rest
2 -> HoverPhase.Leave
else -> throw ProtocolException("unknown HoverPhase tag $tag", offset)
}
private fun gutterDecorationKind(tag: Int, offset: Int): GutterDecorationKind = when (tag) {
1 -> GutterDecorationKind.Dot
2 -> GutterDecorationKind.Bar
3 -> GutterDecorationKind.Glyph
else -> throw ProtocolException("unknown GutterDecorationKind tag $tag", offset)
}
private fun revealAlign(tag: Int, offset: Int): RevealAlign = when (tag) {
1 -> RevealAlign.Nearest
2 -> RevealAlign.Center
3 -> RevealAlign.Top
else -> throw ProtocolException("unknown RevealAlign tag $tag", offset)
}
private fun pointerKind(tag: Int, offset: Int): PointerKind = when (tag) {
1 -> PointerKind.Move
2 -> PointerKind.Enter
3 -> PointerKind.Leave
4 -> PointerKind.Down
5 -> PointerKind.Up
6 -> PointerKind.Cancel
7 -> PointerKind.Wheel
8 -> PointerKind.ContextMenu
else -> throw ProtocolException("unknown PointerKind tag $tag", offset)
}
private fun pointerType(tag: Int, offset: Int): PointerType = when (tag) {
1 -> PointerType.Mouse
2 -> PointerType.Touch
3 -> PointerType.Pen
else -> throw ProtocolException("unknown PointerType tag $tag", offset)
}
private fun wheelDeltaMode(tag: Int, offset: Int): WheelDeltaMode = when (tag) {
0 -> WheelDeltaMode.Pixel
1 -> WheelDeltaMode.Line
2 -> WheelDeltaMode.Page
else -> throw ProtocolException("unknown WheelDeltaMode tag $tag", offset)
}
private fun cursor(tag: Int, offset: Int): Cursor = when (tag) {
0 -> Cursor.Auto
1 -> Cursor.Default
2 -> Cursor.Pointer
3 -> Cursor.Text
4 -> Cursor.VerticalText
5 -> Cursor.Crosshair
6 -> Cursor.Move
7 -> Cursor.Grab
8 -> Cursor.Grabbing
9 -> Cursor.NotAllowed
10 -> Cursor.NoDrop
11 -> Cursor.Wait
12 -> Cursor.Progress
13 -> Cursor.Help
14 -> Cursor.ContextMenu
15 -> Cursor.Cell
16 -> Cursor.Copy
17 -> Cursor.Alias
18 -> Cursor.AllScroll
19 -> Cursor.ZoomIn
20 -> Cursor.ZoomOut
21 -> Cursor.ColResize
22 -> Cursor.RowResize
23 -> Cursor.EResize
24 -> Cursor.NResize
25 -> Cursor.NeResize
26 -> Cursor.NwResize
27 -> Cursor.SResize
28 -> Cursor.SeResize
29 -> Cursor.SwResize
30 -> Cursor.WResize
31 -> Cursor.EwResize
32 -> Cursor.NsResize
33 -> Cursor.NeswResize
34 -> Cursor.NwseResize
35 -> Cursor.None
else -> throw ProtocolException("unknown Cursor tag $tag", offset)
}
private fun userSelect(tag: Int, offset: Int): UserSelect = when (tag) {
0 -> UserSelect.Auto
1 -> UserSelect.None
2 -> UserSelect.Text
3 -> UserSelect.All
else -> throw ProtocolException("unknown UserSelect tag $tag", offset)
}
private fun paint(bits: Long, offset: Int): Paint {
val value = bits.toInt()
return when (val kind = (bits ushr 32).toInt()) {
1 -> Paint.Role(colorRole(value, offset))
2 -> Paint.Literal(value)
3 -> Paint.Asset(value)
else -> throw ProtocolException("unknown paint kind $kind", offset)
}
}
/** A word that says yes or no. Any value but 0 and 1 is a record the sides disagree on. */
private fun flag(word: Long, offset: Int): Boolean = when (word) {
0L -> false
1L -> true
else -> throw ProtocolException("invalid flag $word", offset)
}
/** A value that has to be a number. NaN or an infinity is a record the sides disagree on. */
private fun finite(value: kotlin.Float, offset: Int): kotlin.Float =
if (value.isFinite()) value else throw ProtocolException("$value is not a finite number", offset)
/**
* How many eight byte words a modifier's value takes beyond the two every record has.
* The tag fixes it, so a record of any other length is refused before it is read.
*/
private fun modifierExtraWords(tag: Int, offset: Int): Int = when (tag) {
0 -> 0
1 -> 0
2 -> 0
3 -> 0
4 -> 0
5 -> 0
6 -> 0
7 -> 0
8 -> 0
9 -> 0
10 -> 0
11 -> 0
12 -> 0
13 -> 0
14 -> 0
15 -> 0
16 -> 0
17 -> 0
18 -> 0
19 -> 0
20 -> 0
21 -> 4
22 -> 0
23 -> 1
24 -> 0
25 -> 0
26 -> 2
else -> throw ProtocolException("unknown modifier tag $tag", offset)
}
private fun modifier(tag: Int, batch: ByteBuffer, base: Int, available: Int, at: Int, offset: Int): Modifier {
val first = readU64(batch, base, available, at)
val second = readU64(batch, base, available, at + 8)
return when (tag) {
0 -> Modifier.Empty
1 -> Modifier.Padding(kotlin.Float.fromBits(first.toInt()))
2 -> Modifier.FillMaxWidth
3 -> Modifier.FillMaxHeight
4 -> Modifier.Width(kotlin.Float.fromBits(first.toInt()))
5 -> Modifier.Height(kotlin.Float.fromBits(first.toInt()))
6 -> Modifier.Size(kotlin.Float.fromBits(first.toInt()), kotlin.Float.fromBits(second.toInt()))
7 -> Modifier.Background(paint(first, offset))
8 -> Modifier.Clickable(first)
9 -> Modifier.PaddingRole(spaceRole(first.toInt(), offset))
10 -> Modifier.PaddingEach(kotlin.Float.fromBits(first.toInt()), kotlin.Float.fromBits((first ushr 32).toInt()), kotlin.Float.fromBits(second.toInt()), kotlin.Float.fromBits((second ushr 32).toInt()))
11 -> Modifier.Weight(kotlin.Float.fromBits(first.toInt()))
12 -> Modifier.Shape(kotlin.Float.fromBits(first.toInt()), kotlin.Float.fromBits((first ushr 32).toInt()), kotlin.Float.fromBits(second.toInt()), kotlin.Float.fromBits((second ushr 32).toInt()))
13 -> Modifier.ShapeRole(shapeRole(first.toInt(), offset))
14 -> Modifier.Border(kotlin.Float.fromBits(first.toInt()), paint(second, offset))
15 -> Modifier.Elevation(kotlin.Float.fromBits(first.toInt()))
16 -> Modifier.ObserveSize(first.toInt())
17 -> Modifier.Motion(motionRole(first.toInt(), offset))
18 -> Modifier.Material(materialRole(first.toInt(), offset))
19 -> Modifier.Offset(kotlin.Float.fromBits(first.toInt()), kotlin.Float.fromBits(second.toInt()))
20 -> Modifier.RequiredSize(kotlin.Float.fromBits(first.toInt()), kotlin.Float.fromBits(second.toInt()))
21 -> Modifier.BorderEach(kotlin.Float.fromBits(first.toInt()), kotlin.Float.fromBits((first ushr 32).toInt()), kotlin.Float.fromBits(second.toInt()), kotlin.Float.fromBits((second ushr 32).toInt()), paint(readU64(batch, base, available, at + 16), offset), paint(readU64(batch, base, available, at + 24), offset), paint(readU64(batch, base, available, at + 32), offset), paint(readU64(batch, base, available, at + 40), offset))
22 -> Modifier.CornerEach(kotlin.Float.fromBits(first.toInt()), kotlin.Float.fromBits((first ushr 32).toInt()), kotlin.Float.fromBits(second.toInt()), kotlin.Float.fromBits((second ushr 32).toInt()))
23 -> Modifier.Shadow(kotlin.Float.fromBits(first.toInt()), kotlin.Float.fromBits((first ushr 32).toInt()), kotlin.Float.fromBits(second.toInt()), kotlin.Float.fromBits((second ushr 32).toInt()), paint(readU64(batch, base, available, at + 16), offset))
24 -> Modifier.Clip(flag(first, offset))
25 -> Modifier.Alpha(kotlin.Float.fromBits(first.toInt()))
26 -> Modifier.Transform(finite(kotlin.Float.fromBits(first.toInt()), offset), finite(kotlin.Float.fromBits((first ushr 32).toInt()), offset), finite(kotlin.Float.fromBits(second.toInt()), offset), finite(kotlin.Float.fromBits((second ushr 32).toInt()), offset), finite(kotlin.Float.fromBits(readU64(batch, base, available, at + 16).toInt()), offset), finite(kotlin.Float.fromBits((readU64(batch, base, available, at + 16) ushr 32).toInt()), offset), finite(kotlin.Float.fromBits(readU64(batch, base, available, at + 24).toInt()), offset), finite(kotlin.Float.fromBits((readU64(batch, base, available, at + 24) ushr 32).toInt()), offset))
else -> throw ProtocolException("unknown modifier tag $tag", offset)
}
}
/**
* An arena range with no UTF-8 requirement, used by both a drawing command list and an
* asset's bytes. For an asset this is the one copy this side makes, and it happens at
* registration rather than per frame.
*/
private fun readBytes(batch: ByteBuffer, base: Int, available: Int, referenceOffset: Int): ByteArray {
val offsetLong = readU32(batch, base, available, referenceOffset)
val lengthLong = readU32(batch, base, available, referenceOffset + 4)
if (offsetLong > Int.MAX_VALUE || lengthLong > Int.MAX_VALUE) {
throw ProtocolException("byte range is too large", referenceOffset)
}
val offset = offsetLong.toInt()
val length = lengthLong.toInt()
requireRange(available, offset, length, referenceOffset)
val copy = ByteArray(length)
copyOut(batch, base + offset, copy, length)
return copy
}
/**
* Copies a range of the arena into an array.
*
* The absolute bulk read that says the same thing in one call, `get(index, array,
* offset, length)`, arrived in Java 13 and is not in every Android runtime this has to
* run on. So the read goes through the buffer's own position, which every version has,
* and puts it back afterwards: the arena is read from one thread and a position left
* where the last read ended would make the next one read the wrong bytes.
*
* Nothing is allocated. A `duplicate()` would avoid touching the position and costs an
* object per call on a path that runs per string in every batch.
*/
private fun copyOut(batch: ByteBuffer, at: Int, into: ByteArray, length: Int) {
val mark = batch.position()
batch.position(at)
batch.get(into, 0, length)
batch.position(mark)
}
/**
* The string Compose is handed, and the only copy of the text this side makes.
*
* Validating and building are two passes rather than one because the two things that
* do both are each wrong here. `String(bytes, UTF_8)` replaces a malformed byte with
* U+FFFD, and a malformed string means the two sides disagree about the arena, which
* has to be reported rather than drawn. A `CharsetDecoder` reports it, but assembles
* the text as `char` first, two bytes a character, and then copies that into the
* `String`, and it needs a decoder and two buffer views of its own to do it.
*
* So the bytes are checked where they lie, which allocates nothing, and then copied
* once into a buffer this object keeps and handed to `String`, which by then has
* nothing left to replace. What remains is the one allocation the string itself is.
*/
private fun readString(batch: ByteBuffer, base: Int, available: Int, referenceOffset: Int): String {
val offsetLong = readU32(batch, base, available, referenceOffset)
val lengthLong = readU32(batch, base, available, referenceOffset + 4)
if (offsetLong > Int.MAX_VALUE || lengthLong > Int.MAX_VALUE) {
throw ProtocolException("string range is too large", referenceOffset)
}
val offset = offsetLong.toInt()
val length = lengthLong.toInt()
requireRange(available, offset, length, referenceOffset)
if (length == 0) {
return ""
}
requireUtf8(batch, base + offset, length, offset)
val scratch = stringBytes(length)
copyOut(batch, base + offset, scratch, length)
return String(scratch, 0, length, StandardCharsets.UTF_8)
}
/**
* Reads a range of the arena as UTF-8 without building anything out of it.
*
* The bounds are Unicode's, and the ones that look arbitrary are the point. A two byte
* sequence starts at 0xc2 because 0xc0 and 0xc1 could only encode what one byte
* already encodes; the second byte after 0xe0 starts at 0xa0 and after 0xf0 at 0x90
* for the same reason; 0xed stops at 0x9f because the surrogate halves follow it; and
* four byte sequences stop at 0xf4 0x8f because U+10FFFF is the last code point. The
* platform's decoder refuses every one of those, and this has to refuse them too, or
* a range it called text would come out of `String` as replacement characters.
*/
private fun requireUtf8(batch: ByteBuffer, start: Int, length: Int, errorOffset: Int) {
var index = 0
while (index < length) {
val lead = batch.get(start + index).toInt() and 0xff
if (lead < 0x80) {
index += 1
// Eight bytes at a time for as long as the text stays ASCII, which most
// text is and all of a streaming append usually is: a long with no high
// bit anywhere in it is eight characters that need nothing else checked.
// The mask is the same at both ends, so which order the long is read in
// does not matter. Entered only after an ASCII byte, so text with no
// ASCII in it never pays for the attempt.
while (index + 8 <= length &&
batch.getLong(start + index) and ASCII_HIGH_BITS == 0L
) {
index += 8
}
continue
}
val width = when {
lead >= 0xc2 && lead <= 0xdf -> 2
lead >= 0xe0 && lead <= 0xef -> 3
lead >= 0xf0 && lead <= 0xf4 -> 4
else -> malformedUtf8(errorOffset)
}
if (index + width > length) {
malformedUtf8(errorOffset)
}
val lowest = when (lead) {
0xe0 -> 0xa0
0xf0 -> 0x90
else -> 0x80
}
val highest = when (lead) {
0xed -> 0x9f
0xf4 -> 0x8f
else -> 0xbf
}
val second = batch.get(start + index + 1).toInt() and 0xff
if (second < lowest || second > highest) {
malformedUtf8(errorOffset)
}
var step = 2
while (step < width) {
val continuation = batch.get(start + index + step).toInt() and 0xff
if (continuation < 0x80 || continuation > 0xbf) {
malformedUtf8(errorOffset)
}
step += 1
}
index += width
}
}
private fun malformedUtf8(errorOffset: Int): Nothing =
throw ProtocolException("string is not valid UTF-8", errorOffset)
/**
* The buffer every string is copied through, grown to fit and then kept.
*
* A frame's strings go through it one after another, so a stream of keystrokes costs
* nothing here once it is big enough. It doubles rather than fitting exactly, because
* text that grows a character at a time would otherwise reallocate on every keystroke.
*
* One buffer is enough, and it needs no lock, because a batch is decoded on the
* Renderer's UI thread and on no other: the Host's worker threads update signals and
* ask for a frame, they never call across the boundary themselves. Two threads
* decoding at once would hand each other half a string.
*/
private var stringScratch = ByteArray(256)
private fun stringBytes(length: Int): ByteArray {
val scratch = stringScratch
if (scratch.size >= length) {
return scratch
}
var size = scratch.size
while (size < length) {
size = if (size > Int.MAX_VALUE / 2) length else size + size
}
val grown = ByteArray(size)
stringScratch = grown
return grown
}
/**
* Reads one `StartAnimation` record and refuses anything that is not an animation this
* Renderer could play, asking the same questions the Host's decoder asks of the same
* bytes: known values, at least two keyframes, offsets from 0 to 1 that never go back,
* a duration and an iteration count that are numbers and not negative, values of the
* property's own kind, transform lists lined up across keyframes, and a length that
* holds exactly its keyframes.
*/
private fun animation(batch: ByteBuffer, base: Int, available: Int, start: Int, length: Int): Animation {
val end = start + length
fun float(at: Int): kotlin.Float = kotlin.Float.fromBits(readU32(batch, base, available, at).toInt())
fun bad(message: String, at: Int): Nothing = throw ProtocolException("invalid animation: $message", at)
val property = animatedProperty(readU8(batch, base, available, start + 12), start + 12)
val colour = property == AnimatedProperty.Color || property == AnimatedProperty.Background ||
property == AnimatedProperty.BorderColor
val rawInterpolation = readU8(batch, base, available, start + 17)
val interpolation = when {
colour -> colorInterpolation(rawInterpolation, start + 17)
rawInterpolation == 0 -> null
else -> bad("only a colour has an interpolation", start + 17)
}
val keyframeCount = readU16(batch, base, available, start + 18)
val delayMs = float(start + 28)
val durationMs = float(start + 32)
val iterations = float(start + 36)
val originX = float(start + 40)
val originY = float(start + 44)
val events = readU8(batch, base, available, start + 48)
if (keyframeCount < 2) bad("$keyframeCount keyframes", start + 18)
if (!delayMs.isFinite()) bad("delay $delayMs", start + 28)
if (!durationMs.isFinite() || durationMs < 0f) bad("duration $durationMs", start + 32)
if (iterations.isNaN() || iterations < 0f) bad("iteration count $iterations", start + 36)
if (!originX.isFinite() || !originY.isFinite()) bad("origin", start + 40)
if (events and 0xf.inv() != 0) bad("event bits $events", start + 48)
val keyframes = ArrayList<Keyframe>(keyframeCount)
var at = start + ANIMATION_HEADER_LENGTH
for (index in 0 until keyframeCount) {
if (at + KEYFRAME_HEAD_LENGTH > end) bad("keyframe $index runs past the record", at)
val keyframeOffset = float(at)
val flags = readU16(batch, base, available, at + 6)
val timing = when (timingKind(readU8(batch, base, available, at + 4), at + 4)) {
TimingKind.Linear -> Timing.Linear
TimingKind.CubicBezier -> {
val x1 = float(at + 8)
val y1 = float(at + 12)
val x2 = float(at + 16)
val y2 = float(at + 20)
if (!(x1.isFinite() && y1.isFinite() && x2.isFinite() && y2.isFinite()) ||
x1 < 0f || x1 > 1f || x2 < 0f || x2 > 1f
) {
bad("cubic-bezier($x1, $y1, $x2, $y2)", at + 8)
}
Timing.CubicBezier(x1, y1, x2, y2)
}
TimingKind.Steps -> {
val position = stepPosition(readU8(batch, base, available, at + 5), at + 5)
val count = readU32(batch, base, available, at + 8)
val least = if (position == StepPosition.JumpNone) 2L else 1L
if (count < least || count > Int.MAX_VALUE) bad("steps($count, $position)", at + 8)
Timing.Steps(count.toInt(), position)
}
}
if (flags and 1.inv() != 0 || (index > 0 && flags != 0)) bad("keyframe flags $flags", at + 6)
if (!(keyframeOffset >= 0f && keyframeOffset <= 1f)) bad("keyframe offset $keyframeOffset", at)
val valueAt = at + KEYFRAME_HEAD_LENGTH
val value: KeyframeValue
when (property) {
AnimatedProperty.Alpha -> {
val alpha = float(valueAt)
if (!alpha.isFinite()) bad("alpha $alpha", valueAt)
value = KeyframeValue.Alpha(alpha)
at = valueAt + 4
}
AnimatedProperty.Color, AnimatedProperty.Background, AnimatedProperty.BorderColor -> {
val paint = paint(readU64(batch, base, available, valueAt), valueAt)
if (paint is Paint.Asset) bad("a brush cannot be mixed with another colour", valueAt)
value = KeyframeValue.PaintValue(paint)
at = valueAt + 8
}
AnimatedProperty.Transform -> {
val count = readU16(batch, base, available, valueAt)
val functions = ArrayList<TransformFunction>(count)
for (function in 0 until count) {
val functionAt = valueAt + 4 + TRANSFORM_FUNCTION_LENGTH * function
if (functionAt + TRANSFORM_FUNCTION_LENGTH > end) bad("transform function runs past the record", functionAt)
val rawKind = readU32(batch, base, available, functionAt)
if (rawKind > Int.MAX_VALUE) bad("transform function kind $rawKind", functionAt)
val kind = transformFunctionKind(rawKind.toInt(), functionAt)
val values = FloatArray(6) { float(functionAt + 4 + 4 * it) }
if (!values.all { it.isFinite() }) bad("transform values", functionAt + 4)
functions += TransformFunction(kind, values[0], values[1], values[2], values[3], values[4], values[5])
}
value = KeyframeValue.Transform(functions)
at = valueAt + 4 + TRANSFORM_FUNCTION_LENGTH * count
}
}
keyframes += Keyframe(keyframeOffset, timing, flags != 0, value)
}
if (at != end) bad("the record is $length bytes and its keyframes end at ${at - start}", start + 2)
if (keyframes.first().offset != 0f || keyframes.last().offset != 1f) bad("offsets do not run from 0 to 1", start)
for (index in 1 until keyframes.size) {
if (keyframes[index].offset < keyframes[index - 1].offset) bad("offsets go back", start)
}
val shape = (keyframes.first().value as? KeyframeValue.Transform)?.functions?.map { it.kind }
if (shape != null &&
keyframes.any { (it.value as KeyframeValue.Transform).functions.map { function -> function.kind } != shape }
) {
bad("transform lists differ between keyframes", start)
}
return Animation(
nodeId = readU32(batch, base, available, start + 4).toInt(),
animationId = readU32(batch, base, available, start + 8).toInt(),
property = property,
slot = readU8(batch, base, available, start + 13),
direction = playbackDirection(readU8(batch, base, available, start + 14), start + 14),
fill = fillMode(readU8(batch, base, available, start + 15), start + 15),
playState = playState(readU8(batch, base, available, start + 16), start + 16),
interpolation = interpolation,
startTimeNanos = readU64(batch, base, available, start + 20),
delayMs = delayMs,
durationMs = durationMs,
iterations = iterations,
originX = originX,
originY = originY,
events = events,
keyframes = keyframes,
)
}
private fun readU8(batch: ByteBuffer, base: Int, available: Int, offset: Int): Int {
requireRange(available, offset, 1, offset)
return batch.get(base + offset).toInt() and 0xff
}
private fun readU16(batch: ByteBuffer, base: Int, available: Int, offset: Int): Int {
requireRange(available, offset, 2, offset)
return batch.getShort(base + offset).toInt() and 0xffff
}
private fun readU32(batch: ByteBuffer, base: Int, available: Int, offset: Int): Long {
requireRange(available, offset, 4, offset)
return batch.getInt(base + offset).toLong() and 0xffff_ffffL
}
private fun readU64(batch: ByteBuffer, base: Int, available: Int, offset: Int): Long {
requireRange(available, offset, 8, offset)
return batch.getLong(base + offset)
}
private fun requireRecordLength(actual: Int, expected: Int, offset: Int) {
if (actual != expected) {
throw ProtocolException("invalid record length $actual for tag", offset + 2)
}
}
private fun requireRange(available: Int, offset: Int, length: Int, errorOffset: Int) {
if (offset < 0 || length < 0 || offset.toLong() + length > available.toLong()) {
throw ProtocolException("truncated or out-of-range data", errorOffset)
}
}
}
/** One rung of the type ladder. Sizes are sp, spacing may be negative. */
data class TypeToken(
val size: Float,
val weight: Int,
val lineHeight: Float,
val letterSpacing: Float,
val monospace: Boolean,
)
/**
* The generated half of one design system's tables: colours, type, shapes and spacing.
*
* Arrays are indexed by the role's ordinal, which matches its wire tag minus one.
* The rules that consume them (elevation rendering, ButtonVariant styling, motion) are
* written by hand in the Renderer.
*/
class DesignTokenTable(
val system: DesignSystem,
/** The published guideline these values come from. */
val reference: String,
val defaultFamily: String,
val monospaceFamily: String,
private val lightColors: IntArray,
private val darkColors: IntArray,
private val typeScale: Array<TypeToken>,
private val shapeRadii: FloatArray,
private val spaces: FloatArray,
) {
/** The 0xAARRGGBB value for a role. `dark` is the scheme the Renderer resolved. */
fun color(role: ColorRole, dark: Boolean): Int =
if (dark) darkColors[role.ordinal] else lightColors[role.ordinal]
fun type(role: TypeRole): TypeToken = typeScale[role.ordinal]
/** Corner radius in dp. */
fun radius(role: ShapeRole): Float = shapeRadii[role.ordinal]
/** Spacing in dp. */
fun space(role: SpaceRole): Float = spaces[role.ordinal]
}
object DesignTokens {
val MATERIAL3: DesignTokenTable = DesignTokenTable(
DesignSystem.Material3,
"Material 3 Expressive shape, colour and type scales, m3.material.io, 2025",
"Roboto",
"Roboto Mono",
intArrayOf(
0xff6750a4.toInt(), // Primary
0xffffffff.toInt(), // OnPrimary
0xff625b71.toInt(), // Secondary
0xffffffff.toInt(), // OnSecondary
0xfffef7ff.toInt(), // Surface
0xff1d1b20.toInt(), // OnSurface
0xffe7e0ec.toInt(), // SurfaceVariant
0xff49454f.toInt(), // OnSurfaceVariant
0xfffef7ff.toInt(), // Background
0xff1d1b20.toInt(), // OnBackground
0xff79747e.toInt(), // Outline
0xffcac4d0.toInt(), // OutlineVariant
0xffb3261e.toInt(), // Error
0xffffffff.toInt(), // OnError
0xfff3edf7.toInt(), // SurfaceContainer
0xff7d5260.toInt(), // Tertiary
0xffffffff.toInt(), // OnTertiary
0xffeaddff.toInt(), // PrimaryContainer
0xff21005d.toInt(), // OnPrimaryContainer
0xffe8def8.toInt(), // SecondaryContainer
0xff1d192b.toInt(), // OnSecondaryContainer
0xffffd8e4.toInt(), // TertiaryContainer
0xff31111d.toInt(), // OnTertiaryContainer
0xff6750a4.toInt(), // SyntaxKeyword
0xff7d5260.toInt(), // SyntaxString
0xff49454f.toInt(), // SyntaxComment
0xff31111d.toInt(), // SyntaxNumber
0xff31111d.toInt(), // SyntaxConstant
0xff625b71.toInt(), // SyntaxType
0xff21005d.toInt(), // SyntaxFunction
0xff1d1b20.toInt(), // SyntaxVariable
0xff1d192b.toInt(), // SyntaxProperty
0xff49454f.toInt(), // SyntaxOperator
0xff49454f.toInt(), // SyntaxPunctuation
0xff6750a4.toInt(), // SyntaxTag
0xff625b71.toInt(), // SyntaxAttribute
0xffb3261e.toInt(), // SyntaxEscape
0xff625b71.toInt(), // SyntaxMacro
0xff006d17.toInt(), // DiffAdded
0xffb3261e.toInt(), // DiffRemoved
0xff7d5260.toInt(), // DiffModified
0xffd6dedc.toInt(), // DiffAddedContainer
0xffebd5dd.toInt(), // DiffRemovedContainer
0xffaac7b4.toInt(), // DiffAddedEmphasis
0xffe0b1b6.toInt(), // DiffRemovedEmphasis
),
intArrayOf(
0xffd0bcff.toInt(), // Primary
0xff381e72.toInt(), // OnPrimary
0xffccc2dc.toInt(), // Secondary
0xff332d41.toInt(), // OnSecondary
0xff141218.toInt(), // Surface
0xffe6e0e9.toInt(), // OnSurface
0xff49454f.toInt(), // SurfaceVariant
0xffcac4d0.toInt(), // OnSurfaceVariant
0xff141218.toInt(), // Background
0xffe6e0e9.toInt(), // OnBackground
0xff938f99.toInt(), // Outline
0xff49454f.toInt(), // OutlineVariant
0xfff2b8b5.toInt(), // Error
0xff601410.toInt(), // OnError
0xff211f26.toInt(), // SurfaceContainer
0xffefb8c8.toInt(), // Tertiary
0xff492532.toInt(), // OnTertiary
0xff4f378b.toInt(), // PrimaryContainer
0xffeaddff.toInt(), // OnPrimaryContainer
0xff4a4458.toInt(), // SecondaryContainer
0xffe8def8.toInt(), // OnSecondaryContainer
0xff633b48.toInt(), // TertiaryContainer
0xffffd8e4.toInt(), // OnTertiaryContainer
0xffd0bcff.toInt(), // SyntaxKeyword
0xffefb8c8.toInt(), // SyntaxString
0xffcac4d0.toInt(), // SyntaxComment
0xffffd8e4.toInt(), // SyntaxNumber
0xffffd8e4.toInt(), // SyntaxConstant
0xffccc2dc.toInt(), // SyntaxType
0xffeaddff.toInt(), // SyntaxFunction
0xffe6e0e9.toInt(), // SyntaxVariable
0xffe8def8.toInt(), // SyntaxProperty
0xffcac4d0.toInt(), // SyntaxOperator
0xffcac4d0.toInt(), // SyntaxPunctuation
0xffd0bcff.toInt(), // SyntaxTag
0xffccc2dc.toInt(), // SyntaxAttribute
0xfff2b8b5.toInt(), // SyntaxEscape
0xffccc2dc.toInt(), // SyntaxMacro
0xffb2ceaa.toInt(), // DiffAdded
0xfff2b8b5.toInt(), // DiffRemoved
0xffefb8c8.toInt(), // DiffModified
0xff3e4240.toInt(), // DiffAddedContainer
0xff4b3e43.toInt(), // DiffRemovedContainer
0xff5b655b.toInt(), // DiffAddedEmphasis
0xff755c5f.toInt(), // DiffRemovedEmphasis
),
arrayOf(
TypeToken(57.0f, 400, 64.0f, 0.0f, false), // Display
TypeToken(32.0f, 400, 40.0f, 0.0f, false), // Headline
TypeToken(22.0f, 400, 28.0f, 0.0f, false), // Title
TypeToken(16.0f, 500, 24.0f, 0.15f, false), // Subtitle
TypeToken(16.0f, 400, 24.0f, 0.5f, false), // Body
TypeToken(16.0f, 500, 24.0f, 0.15f, false), // BodyStrong
TypeToken(14.0f, 500, 20.0f, 0.1f, false), // Label
TypeToken(12.0f, 400, 16.0f, 0.4f, false), // Caption
TypeToken(14.0f, 400, 20.0f, 0.0f, true), // Mono
),
floatArrayOf(0.0f, 4.0f, 12.0f, 16.0f, 28.0f, 1000.0f),
floatArrayOf(0.0f, 4.0f, 8.0f, 16.0f, 24.0f, 32.0f, 48.0f),
)
val CUPERTINO: DesignTokenTable = DesignTokenTable(
DesignSystem.Cupertino,
"Apple Human Interface Guidelines, system colors and Dynamic Type, 2024",
"SF Pro",
"SF Mono",
intArrayOf(
0xff007aff.toInt(), // Primary
0xffffffff.toInt(), // OnPrimary
0xff5856d6.toInt(), // Secondary
0xffffffff.toInt(), // OnSecondary
0xffffffff.toInt(), // Surface
0xff000000.toInt(), // OnSurface
0xffe9e9eb.toInt(), // SurfaceVariant
0xff3c3c43.toInt(), // OnSurfaceVariant
0xfff2f2f7.toInt(), // Background
0xff000000.toInt(), // OnBackground
0xffc6c6c8.toInt(), // Outline
0xffe5e5ea.toInt(), // OutlineVariant
0xffff3b30.toInt(), // Error
0xffffffff.toInt(), // OnError
0xffffffff.toInt(), // SurfaceContainer
0xffaf52de.toInt(), // Tertiary
0xffffffff.toInt(), // OnTertiary
0xffd6e4ff.toInt(), // PrimaryContainer
0xff003070.toInt(), // OnPrimaryContainer
0xffe2e0ff.toInt(), // SecondaryContainer
0xff2a1b70.toInt(), // OnSecondaryContainer
0xfff3ddfb.toInt(), // TertiaryContainer
0xff3d0b52.toInt(), // OnTertiaryContainer
0xff9b2393.toInt(), // SyntaxKeyword
0xffc41a16.toInt(), // SyntaxString
0xff5d6c79.toInt(), // SyntaxComment
0xff1c00cf.toInt(), // SyntaxNumber
0xff6c36a9.toInt(), // SyntaxConstant
0xff3900a0.toInt(), // SyntaxType
0xff326d74.toInt(), // SyntaxFunction
0xff000000.toInt(), // SyntaxVariable
0xff326d74.toInt(), // SyntaxProperty
0xff000000.toInt(), // SyntaxOperator
0xff000000.toInt(), // SyntaxPunctuation
0xff0b4f79.toInt(), // SyntaxTag
0xff815f03.toInt(), // SyntaxAttribute
0xff1c00cf.toInt(), // SyntaxEscape
0xff643820.toInt(), // SyntaxMacro
0xff087a2f.toInt(), // DiffAdded
0xffce0014.toInt(), // DiffRemoved
0xff007aff.toInt(), // DiffModified
0xffe1efe6.toInt(), // DiffAddedContainer
0xfff9e0e3.toInt(), // DiffRemovedContainer
0xffb5d7c1.toInt(), // DiffAddedEmphasis
0xfff0b3b9.toInt(), // DiffRemovedEmphasis
),
intArrayOf(
0xff0a84ff.toInt(), // Primary
0xffffffff.toInt(), // OnPrimary
0xff5e5ce6.toInt(), // Secondary
0xffffffff.toInt(), // OnSecondary
0xff1c1c1e.toInt(), // Surface
0xffffffff.toInt(), // OnSurface
0xff2c2c2e.toInt(), // SurfaceVariant
0xffebebf5.toInt(), // OnSurfaceVariant
0xff000000.toInt(), // Background
0xffffffff.toInt(), // OnBackground
0xff38383a.toInt(), // Outline
0xff48484a.toInt(), // OutlineVariant
0xffff453a.toInt(), // Error
0xffffffff.toInt(), // OnError
0xff1c1c1e.toInt(), // SurfaceContainer
0xffbf5af2.toInt(), // Tertiary
0xffffffff.toInt(), // OnTertiary
0xff0a2d52.toInt(), // PrimaryContainer
0xffcfe3ff.toInt(), // OnPrimaryContainer
0xff262663.toInt(), // SecondaryContainer
0xffdedcff.toInt(), // OnSecondaryContainer
0xff3f1a52.toInt(), // TertiaryContainer
0xfff1d9fa.toInt(), // OnTertiaryContainer
0xfffc5fa3.toInt(), // SyntaxKeyword
0xfffc6a5d.toInt(), // SyntaxString
0xff798693.toInt(), // SyntaxComment
0xffd0bf69.toInt(), // SyntaxNumber
0xffa167e6.toInt(), // SyntaxConstant
0xffd0a8ff.toInt(), // SyntaxType
0xff67b7a4.toInt(), // SyntaxFunction
0xffdfdfe0.toInt(), // SyntaxVariable
0xff67b7a4.toInt(), // SyntaxProperty
0xffdfdfe0.toInt(), // SyntaxOperator
0xffdfdfe0.toInt(), // SyntaxPunctuation
0xff5dd8ff.toInt(), // SyntaxTag
0xffbf8555.toInt(), // SyntaxAttribute
0xffd0bf69.toInt(), // SyntaxEscape
0xfffd8f3f.toInt(), // SyntaxMacro
0xff30d158.toInt(), // DiffAdded
0xffff6e66.toInt(), // DiffRemoved
0xff0a84ff.toInt(), // DiffModified
0xff20402a.toInt(), // DiffAddedContainer
0xff492c2c.toInt(), // DiffRemovedContainer
0xff246435.toInt(), // DiffAddedEmphasis
0xff773d3b.toInt(), // DiffRemovedEmphasis
),
arrayOf(
TypeToken(34.0f, 700, 41.0f, 0.37f, false), // Display
TypeToken(28.0f, 400, 34.0f, 0.36f, false), // Headline
TypeToken(22.0f, 400, 28.0f, 0.35f, false), // Title
TypeToken(17.0f, 600, 22.0f, -0.41f, false), // Subtitle
TypeToken(17.0f, 400, 22.0f, -0.41f, false), // Body
TypeToken(17.0f, 600, 22.0f, -0.41f, false), // BodyStrong
TypeToken(15.0f, 400, 20.0f, -0.24f, false), // Label
TypeToken(12.0f, 400, 16.0f, 0.0f, false), // Caption
TypeToken(15.0f, 400, 20.0f, 0.0f, true), // Mono
),
floatArrayOf(0.0f, 4.0f, 8.0f, 10.0f, 14.0f, 1000.0f),
floatArrayOf(0.0f, 4.0f, 8.0f, 16.0f, 20.0f, 32.0f, 44.0f),
)
val FLUENT: DesignTokenTable = DesignTokenTable(
DesignSystem.Fluent,
"WinUI / Fluent 2 web and Windows tokens, fluent2.microsoft.design, 2024",
"Segoe UI Variable",
"Cascadia Mono",
intArrayOf(
0xff0f6cbd.toInt(), // Primary
0xffffffff.toInt(), // OnPrimary
0xffebf3fc.toInt(), // Secondary
0xff0f548c.toInt(), // OnSecondary
0xffffffff.toInt(), // Surface
0xff242424.toInt(), // OnSurface
0xffeaeaea.toInt(), // SurfaceVariant
0xff424242.toInt(), // OnSurfaceVariant
0xfff3f3f3.toInt(), // Background
0xff242424.toInt(), // OnBackground
0xffd1d1d1.toInt(), // Outline
0xffe0e0e0.toInt(), // OutlineVariant
0xffc50f1f.toInt(), // Error
0xffffffff.toInt(), // OnError
0xffffffff.toInt(), // SurfaceContainer
0xff8764b8.toInt(), // Tertiary
0xffffffff.toInt(), // OnTertiary
0xffcfe4fa.toInt(), // PrimaryContainer
0xff0c3b5e.toInt(), // OnPrimaryContainer
0xffb4d6fa.toInt(), // SecondaryContainer
0xff0b3350.toInt(), // OnSecondaryContainer
0xffe8dcf7.toInt(), // TertiaryContainer
0xff341a5e.toInt(), // OnTertiaryContainer
0xff0000ff.toInt(), // SyntaxKeyword
0xffa31515.toInt(), // SyntaxString
0xff008000.toInt(), // SyntaxComment
0xff098658.toInt(), // SyntaxNumber
0xff0070c1.toInt(), // SyntaxConstant
0xff267f99.toInt(), // SyntaxType
0xff795e26.toInt(), // SyntaxFunction
0xff001080.toInt(), // SyntaxVariable
0xff001080.toInt(), // SyntaxProperty
0xff000000.toInt(), // SyntaxOperator
0xff000000.toInt(), // SyntaxPunctuation
0xff800000.toInt(), // SyntaxTag
0xffe50000.toInt(), // SyntaxAttribute
0xffee0000.toInt(), // SyntaxEscape
0xffaf00db.toInt(), // SyntaxMacro
0xff007b50.toInt(), // DiffAdded
0xffa31515.toInt(), // DiffRemoved
0xff0451a5.toInt(), // DiffModified
0xffebf1dd.toInt(), // DiffAddedContainer
0xffffcccc.toInt(), // DiffRemovedContainer
0xffd7e8b1.toInt(), // DiffAddedEmphasis
0xffff9999.toInt(), // DiffRemovedEmphasis
),
intArrayOf(
0xff479ef5.toInt(), // Primary
0xff000000.toInt(), // OnPrimary
0xff0c3b5e.toInt(), // Secondary
0xffffffff.toInt(), // OnSecondary
0xff292929.toInt(), // Surface
0xffffffff.toInt(), // OnSurface
0xff333333.toInt(), // SurfaceVariant
0xffd6d6d6.toInt(), // OnSurfaceVariant
0xff1f1f1f.toInt(), // Background
0xffffffff.toInt(), // OnBackground
0xff666666.toInt(), // Outline
0xff3d3d3d.toInt(), // OutlineVariant
0xffdc626d.toInt(), // Error
0xff000000.toInt(), // OnError
0xff2b2b2b.toInt(), // SurfaceContainer
0xffb18cd9.toInt(), // Tertiary
0xff22103a.toInt(), // OnTertiary
0xff0c3b5e.toInt(), // PrimaryContainer
0xffcfe4fa.toInt(), // OnPrimaryContainer
0xff123d61.toInt(), // SecondaryContainer
0xffb4d6fa.toInt(), // OnSecondaryContainer
0xff3b2159.toInt(), // TertiaryContainer
0xffe8dcf7.toInt(), // OnTertiaryContainer
0xff569cd6.toInt(), // SyntaxKeyword
0xffce9178.toInt(), // SyntaxString
0xff6f9e5a.toInt(), // SyntaxComment
0xffb5cea8.toInt(), // SyntaxNumber
0xff4fc1ff.toInt(), // SyntaxConstant
0xff4ec9b0.toInt(), // SyntaxType
0xffdcdcaa.toInt(), // SyntaxFunction
0xff9cdcfe.toInt(), // SyntaxVariable
0xff9cdcfe.toInt(), // SyntaxProperty
0xffd4d4d4.toInt(), // SyntaxOperator
0xffd4d4d4.toInt(), // SyntaxPunctuation
0xff569cd6.toInt(), // SyntaxTag
0xff9cdcfe.toInt(), // SyntaxAttribute
0xffd7ba7d.toInt(), // SyntaxEscape
0xffc586c0.toInt(), // SyntaxMacro
0xffb5cea8.toInt(), // DiffAdded
0xffce9178.toInt(), // DiffRemoved
0xff569cd6.toInt(), // DiffModified
0xff414733.toInt(), // DiffAddedContainer
0xff552222.toInt(), // DiffRemovedContainer
0xff536232.toInt(), // DiffAddedEmphasis
0xff771b1b.toInt(), // DiffRemovedEmphasis
),
arrayOf(
TypeToken(40.0f, 600, 52.0f, 0.0f, false), // Display
TypeToken(28.0f, 600, 36.0f, 0.0f, false), // Headline
TypeToken(20.0f, 600, 28.0f, 0.0f, false), // Title
TypeToken(16.0f, 600, 22.0f, 0.0f, false), // Subtitle
TypeToken(14.0f, 400, 20.0f, 0.0f, false), // Body
TypeToken(14.0f, 600, 20.0f, 0.0f, false), // BodyStrong
TypeToken(12.0f, 400, 16.0f, 0.0f, false), // Label
TypeToken(12.0f, 400, 16.0f, 0.0f, false), // Caption
TypeToken(13.0f, 400, 18.0f, 0.0f, true), // Mono
),
floatArrayOf(0.0f, 2.0f, 3.0f, 4.0f, 8.0f, 1000.0f),
floatArrayOf(0.0f, 2.0f, 4.0f, 8.0f, 12.0f, 20.0f, 32.0f),
)
val GNOME: DesignTokenTable = DesignTokenTable(
DesignSystem.Gnome,
"GNOME Human Interface Guidelines and the libadwaita named colours, GNOME 50",
"Cantarell",
"Source Code Pro",
intArrayOf(
0xff3584e4.toInt(), // Primary
0xffffffff.toInt(), // OnPrimary
0xff9141ac.toInt(), // Secondary
0xffffffff.toInt(), // OnSecondary
0xffffffff.toInt(), // Surface
0xff2e3436.toInt(), // OnSurface
0xffebebeb.toInt(), // SurfaceVariant
0xff5e5c64.toInt(), // OnSurfaceVariant
0xfffafafa.toInt(), // Background
0xff2e3436.toInt(), // OnBackground
0xffcdc7c2.toInt(), // Outline
0xffe6e3e1.toInt(), // OutlineVariant
0xffe01b24.toInt(), // Error
0xffffffff.toInt(), // OnError
0xffebebeb.toInt(), // SurfaceContainer
0xff2190a4.toInt(), // Tertiary
0xffffffff.toInt(), // OnTertiary
0xffd4e5fb.toInt(), // PrimaryContainer
0xff0d3b70.toInt(), // OnPrimaryContainer
0xffecd9f1.toInt(), // SecondaryContainer
0xff45164f.toInt(), // OnSecondaryContainer
0xffcfe9ed.toInt(), // TertiaryContainer
0xff0a3d45.toInt(), // OnTertiaryContainer
0xffba4100.toInt(), // SyntaxKeyword
0xff007778.toInt(), // SyntaxString
0xff6a6a6e.toInt(), // SyntaxComment
0xff4e57ba.toInt(), // SyntaxNumber
0xff4e57ba.toInt(), // SyntaxConstant
0xff007778.toInt(), // SyntaxType
0xff0067cd.toInt(), // SyntaxFunction
0xff3b7800.toInt(), // SyntaxVariable
0xff504e55.toInt(), // SyntaxProperty
0xff504e55.toInt(), // SyntaxOperator
0xff504e55.toInt(), // SyntaxPunctuation
0xff007778.toInt(), // SyntaxTag
0xffba4100.toInt(), // SyntaxAttribute
0xffd01a2a.toInt(), // SyntaxEscape
0xffba4100.toInt(), // SyntaxMacro
0xff006a6b.toInt(), // DiffAdded
0xffae2d23.toInt(), // DiffRemoved
0xffe66100.toInt(), // DiffModified
0xffcfdcdc.toInt(), // DiffAddedContainer
0xffe4d4d3.toInt(), // DiffRemovedContainer
0xffa5c4c5.toInt(), // DiffAddedEmphasis
0xffd9b2af.toInt(), // DiffRemovedEmphasis
),
intArrayOf(
0xff3584e4.toInt(), // Primary
0xffffffff.toInt(), // OnPrimary
0xffc061cb.toInt(), // Secondary
0xffffffff.toInt(), // OnSecondary
0xff1e1e1e.toInt(), // Surface
0xffffffff.toInt(), // OnSurface
0xff303030.toInt(), // SurfaceVariant
0xffc0bfbc.toInt(), // OnSurfaceVariant
0xff242424.toInt(), // Background
0xffffffff.toInt(), // OnBackground
0xff52514f.toInt(), // Outline
0xff3a3a3a.toInt(), // OutlineVariant
0xffff7b63.toInt(), // Error
0xff2a0a06.toInt(), // OnError
0xff303030.toInt(), // SurfaceContainer
0xff2190a4.toInt(), // Tertiary
0xffffffff.toInt(), // OnTertiary
0xff1b3c5e.toInt(), // PrimaryContainer
0xffcfe0f7.toInt(), // OnPrimaryContainer
0xff44234c.toInt(), // SecondaryContainer
0xffecd9f1.toInt(), // OnSecondaryContainer
0xff134249.toInt(), // TertiaryContainer
0xffcfe9ed.toInt(), // OnTertiaryContainer
0xffffa348.toInt(), // SyntaxKeyword
0xff5bc8af.toInt(), // SyntaxString
0xff999898.toInt(), // SyntaxComment
0xff8794d2.toInt(), // SyntaxNumber
0xff8794d2.toInt(), // SyntaxConstant
0xff5bc8af.toInt(), // SyntaxType
0xff62a0ea.toInt(), // SyntaxFunction
0xff5dab1e.toInt(), // SyntaxVariable
0xffc0bfbc.toInt(), // SyntaxProperty
0xffc0bfbc.toInt(), // SyntaxOperator
0xffc0bfbc.toInt(), // SyntaxPunctuation
0xff33b2a4.toInt(), // SyntaxTag
0xffff7800.toInt(), // SyntaxAttribute
0xfffd6656.toInt(), // SyntaxEscape
0xfffb7018.toInt(), // SyntaxMacro
0xff56cebf.toInt(), // DiffAdded
0xffffa295.toInt(), // DiffRemoved
0xffff7800.toInt(), // DiffModified
0xff38504d.toInt(), // DiffAddedContainer
0xff594744.toInt(), // DiffRemovedContainer
0xff3f6f69.toInt(), // DiffAddedEmphasis
0xff835e58.toInt(), // DiffRemovedEmphasis
),
arrayOf(
TypeToken(44.0f, 800, 52.0f, -0.5f, false), // Display
TypeToken(32.0f, 800, 40.0f, -0.25f, false), // Headline
TypeToken(24.0f, 700, 32.0f, 0.0f, false), // Title
TypeToken(20.0f, 700, 28.0f, 0.0f, false), // Subtitle
TypeToken(15.0f, 400, 22.0f, 0.0f, false), // Body
TypeToken(15.0f, 700, 22.0f, 0.0f, false), // BodyStrong
TypeToken(13.0f, 700, 18.0f, 0.1f, false), // Label
TypeToken(12.0f, 400, 16.0f, 0.0f, false), // Caption
TypeToken(14.0f, 400, 20.0f, 0.0f, true), // Mono
),
floatArrayOf(0.0f, 4.0f, 6.0f, 12.0f, 15.0f, 1000.0f),
floatArrayOf(0.0f, 3.0f, 6.0f, 12.0f, 18.0f, 24.0f, 36.0f),
)
val BREEZE: DesignTokenTable = DesignTokenTable(
DesignSystem.Breeze,
"KDE Breeze colour schemes and the KDE Human Interface Guidelines, Plasma 6",
"Noto Sans",
"Hack",
intArrayOf(
0xff3daee9.toInt(), // Primary
0xff06222e.toInt(), // OnPrimary
0xff16a085.toInt(), // Secondary
0xff03201b.toInt(), // OnSecondary
0xfffcfcfc.toInt(), // Surface
0xff232629.toInt(), // OnSurface
0xffe1e3e5.toInt(), // SurfaceVariant
0xff4d5052.toInt(), // OnSurfaceVariant
0xffeff0f1.toInt(), // Background
0xff232629.toInt(), // OnBackground
0xffbdc3c7.toInt(), // Outline
0xffd8dbdd.toInt(), // OutlineVariant
0xffda4453.toInt(), // Error
0xffffffff.toInt(), // OnError
0xfffcfcfc.toInt(), // SurfaceContainer
0xfff67400.toInt(), // Tertiary
0xff2b1200.toInt(), // OnTertiary
0xffd3ecf9.toInt(), // PrimaryContainer
0xff0b3b52.toInt(), // OnPrimaryContainer
0xffd2ece5.toInt(), // SecondaryContainer
0xff083b31.toInt(), // OnSecondaryContainer
0xfffae0c4.toInt(), // TertiaryContainer
0xff4a2c00.toInt(), // OnTertiaryContainer
0xff1f1c1b.toInt(), // SyntaxKeyword
0xffbf0303.toInt(), // SyntaxString
0xff747473.toInt(), // SyntaxComment
0xff966d00.toInt(), // SyntaxNumber
0xffaa5500.toInt(), // SyntaxConstant
0xff0057ae.toInt(), // SyntaxType
0xff644a9b.toInt(), // SyntaxFunction
0xff0057ae.toInt(), // SyntaxVariable
0xff0057ae.toInt(), // SyntaxProperty
0xffaf48b1.toInt(), // SyntaxOperator
0xff1f1c1b.toInt(), // SyntaxPunctuation
0xff1f1c1b.toInt(), // SyntaxTag
0xff0057ae.toInt(), // SyntaxAttribute
0xff007bac.toInt(), // SyntaxEscape
0xff006e28.toInt(), // SyntaxMacro
0xff0057ae.toInt(), // DiffAdded
0xffbf0303.toInt(), // DiffRemoved
0xfffdbc4b.toInt(), // DiffModified
0xffdee8f3.toInt(), // DiffAddedContainer
0xfff5dede.toInt(), // DiffRemovedContainer
0xffb0cae5.toInt(), // DiffAddedEmphasis
0xffeab1b1.toInt(), // DiffRemovedEmphasis
),
intArrayOf(
0xff3daee9.toInt(), // Primary
0xff06222e.toInt(), // OnPrimary
0xff1abc9c.toInt(), // Secondary
0xff03201b.toInt(), // OnSecondary
0xff1b1e20.toInt(), // Surface
0xfffcfcfc.toInt(), // OnSurface
0xff31363b.toInt(), // SurfaceVariant
0xffbdc3c7.toInt(), // OnSurfaceVariant
0xff232629.toInt(), // Background
0xfffcfcfc.toInt(), // OnBackground
0xff4d5155.toInt(), // Outline
0xff3f4449.toInt(), // OutlineVariant
0xffed8079.toInt(), // Error
0xff2a0806.toInt(), // OnError
0xff1b1e20.toInt(), // SurfaceContainer
0xfff8a44c.toInt(), // Tertiary
0xff2b1200.toInt(), // OnTertiary
0xff123b4f.toInt(), // PrimaryContainer
0xffcde6f5.toInt(), // OnPrimaryContainer
0xff103a32.toInt(), // SecondaryContainer
0xffcfe8e0.toInt(), // OnSecondaryContainer
0xff4a3113.toInt(), // TertiaryContainer
0xfff8dfc3.toInt(), // OnTertiaryContainer
0xffcfcfc2.toInt(), // SyntaxKeyword
0xfff44f4f.toInt(), // SyntaxString
0xff848687.toInt(), // SyntaxComment
0xfff67400.toInt(), // SyntaxNumber
0xff27aeae.toInt(), // SyntaxConstant
0xff3b8dc7.toInt(), // SyntaxType
0xffb266d1.toInt(), // SyntaxFunction
0xff27aeae.toInt(), // SyntaxVariable
0xff27aeae.toInt(), // SyntaxProperty
0xff519269.toInt(), // SyntaxOperator
0xffcfcfc2.toInt(), // SyntaxPunctuation
0xffcfcfc2.toInt(), // SyntaxTag
0xff3b8dc7.toInt(), // SyntaxAttribute
0xff3daee9.toInt(), // SyntaxEscape
0xff27ae60.toInt(), // SyntaxMacro
0xff2cb1b1.toInt(), // DiffAdded
0xffff736e.toInt(), // DiffRemoved
0xffc04900.toInt(), // DiffModified
0xff1e3b3d.toInt(), // DiffAddedContainer
0xff492f30.toInt(), // DiffRemovedContainer
0xff22595a.toInt(), // DiffAddedEmphasis
0xff76403f.toInt(), // DiffRemovedEmphasis
),
arrayOf(
TypeToken(34.0f, 600, 40.0f, 0.0f, false), // Display
TypeToken(26.0f, 600, 32.0f, 0.0f, false), // Headline
TypeToken(19.0f, 600, 24.0f, 0.0f, false), // Title
TypeToken(16.0f, 500, 21.0f, 0.0f, false), // Subtitle
TypeToken(13.0f, 400, 19.0f, 0.0f, false), // Body
TypeToken(13.0f, 600, 19.0f, 0.0f, false), // BodyStrong
TypeToken(12.0f, 500, 16.0f, 0.2f, false), // Label
TypeToken(11.0f, 400, 15.0f, 0.0f, false), // Caption
TypeToken(12.0f, 400, 17.0f, 0.0f, true), // Mono
),
floatArrayOf(0.0f, 2.0f, 3.0f, 4.0f, 6.0f, 1000.0f),
floatArrayOf(0.0f, 2.0f, 4.0f, 8.0f, 12.0f, 18.0f, 24.0f),
)
val DEEPIN: DesignTokenTable = DesignTokenTable(
DesignSystem.Deepin,
"Deepin Design specification and the DTK control defaults, deepin 23",
"Noto Sans",
"Noto Sans Mono",
intArrayOf(
0xff0081ff.toInt(), // Primary
0xffffffff.toInt(), // OnPrimary
0xfff2a13c.toInt(), // Secondary
0xff2b1a05.toInt(), // OnSecondary
0xffffffff.toInt(), // Surface
0xff202020.toInt(), // OnSurface
0xffe6e6e6.toInt(), // SurfaceVariant
0xff5a5a5a.toInt(), // OnSurfaceVariant
0xffffffff.toInt(), // Background
0xff202020.toInt(), // OnBackground
0xffcdcdcd.toInt(), // Outline
0xffe0e0e0.toInt(), // OutlineVariant
0xffff5736.toInt(), // Error
0xffffffff.toInt(), // OnError
0xfff1f1f1.toInt(), // SurfaceContainer
0xff7a5bd6.toInt(), // Tertiary
0xffffffff.toInt(), // OnTertiary
0xffd8ecff.toInt(), // PrimaryContainer
0xff00407f.toInt(), // OnPrimaryContainer
0xfffdf0e1.toInt(), // SecondaryContainer
0xff79501e.toInt(), // OnSecondaryContainer
0xffebe6f8.toInt(), // TertiaryContainer
0xff3d2d6b.toInt(), // OnTertiaryContainer
0xff006bd5.toInt(), // SyntaxKeyword
0xff7758d3.toInt(), // SyntaxString
0xff5a5a5a.toInt(), // SyntaxComment
0xff3d2d6b.toInt(), // SyntaxNumber
0xff3d2d6b.toInt(), // SyntaxConstant
0xff9b5e00.toInt(), // SyntaxType
0xff00407f.toInt(), // SyntaxFunction
0xff202020.toInt(), // SyntaxVariable
0xff79501e.toInt(), // SyntaxProperty
0xff5a5a5a.toInt(), // SyntaxOperator
0xff5a5a5a.toInt(), // SyntaxPunctuation
0xff006bd5.toInt(), // SyntaxTag
0xff9b5e00.toInt(), // SyntaxAttribute
0xffcd3416.toInt(), // SyntaxEscape
0xff9b5e00.toInt(), // SyntaxMacro
0xff007118.toInt(), // DiffAdded
0xffbb2608.toInt(), // DiffRemoved
0xff7a5bd6.toInt(), // DiffModified
0xffd4e2d7.toInt(), // DiffAddedContainer
0xffebd9d5.toInt(), // DiffRemovedContainer
0xffa9cbb0.toInt(), // DiffAddedEmphasis
0xffe1b4ab.toInt(), // DiffRemovedEmphasis
),
intArrayOf(
0xff3ba2ff.toInt(), // Primary
0xff04203a.toInt(), // OnPrimary
0xffffb964.toInt(), // Secondary
0xff33200a.toInt(), // OnSecondary
0xff2a2a2a.toInt(), // Surface
0xfff0f0f0.toInt(), // OnSurface
0xff3a3a3a.toInt(), // SurfaceVariant
0xffb4b4b4.toInt(), // OnSurfaceVariant
0xff1a1a1a.toInt(), // Background
0xfff0f0f0.toInt(), // OnBackground
0xff4d4d4d.toInt(), // Outline
0xff333333.toInt(), // OutlineVariant
0xffff8a73.toInt(), // Error
0xff34110a.toInt(), // OnError
0xff2a2a2a.toInt(), // SurfaceContainer
0xff9f8ae3.toInt(), // Tertiary
0xff1d0f45.toInt(), // OnTertiary
0xff203547.toInt(), // PrimaryContainer
0xff9dd0ff.toInt(), // OnPrimaryContainer
0xff473928.toInt(), // SecondaryContainer
0xffffdcb1.toInt(), // OnSecondaryContainer
0xff343042.toInt(), // TertiaryContainer
0xffcfc4f1.toInt(), // OnTertiaryContainer
0xff3ba2ff.toInt(), // SyntaxKeyword
0xff9f8ae3.toInt(), // SyntaxString
0xffb4b4b4.toInt(), // SyntaxComment
0xffcfc4f1.toInt(), // SyntaxNumber
0xffcfc4f1.toInt(), // SyntaxConstant
0xffffb964.toInt(), // SyntaxType
0xff9dd0ff.toInt(), // SyntaxFunction
0xfff0f0f0.toInt(), // SyntaxVariable
0xffffdcb1.toInt(), // SyntaxProperty
0xffb4b4b4.toInt(), // SyntaxOperator
0xffb4b4b4.toInt(), // SyntaxPunctuation
0xff3ba2ff.toInt(), // SyntaxTag
0xffffb964.toInt(), // SyntaxAttribute
0xffff8a73.toInt(), // SyntaxEscape
0xffffb964.toInt(), // SyntaxMacro
0xff76c270.toInt(), // DiffAdded
0xffff9783.toInt(), // DiffRemoved
0xff9f8ae3.toInt(), // DiffModified
0xff394838.toInt(), // DiffAddedContainer
0xff55403c.toInt(), // DiffRemovedContainer
0xff486746.toInt(), // DiffAddedEmphasis
0xff7f564e.toInt(), // DiffRemovedEmphasis
),
arrayOf(
TypeToken(40.0f, 600, 50.0f, 0.0f, false), // Display
TypeToken(30.0f, 600, 38.0f, 0.0f, false), // Headline
TypeToken(22.0f, 500, 30.0f, 0.0f, false), // Title
TypeToken(18.0f, 500, 26.0f, 0.0f, false), // Subtitle
TypeToken(14.0f, 400, 21.0f, 0.0f, false), // Body
TypeToken(14.0f, 600, 21.0f, 0.0f, false), // BodyStrong
TypeToken(13.0f, 500, 18.0f, 0.3f, false), // Label
TypeToken(12.0f, 400, 17.0f, 0.0f, false), // Caption
TypeToken(13.0f, 400, 19.0f, 0.0f, true), // Mono
),
floatArrayOf(0.0f, 6.0f, 8.0f, 10.0f, 18.0f, 1000.0f),
floatArrayOf(0.0f, 4.0f, 10.0f, 16.0f, 20.0f, 30.0f, 40.0f),
)
val LIQUID_GLASS: DesignTokenTable = DesignTokenTable(
DesignSystem.LiquidGlass,
"Apple Human Interface Guidelines, Liquid Glass, system colors and Dynamic Type, 2026",
"SF Pro",
"SF Mono",
intArrayOf(
0xff007aff.toInt(), // Primary
0xffffffff.toInt(), // OnPrimary
0xff5856d6.toInt(), // Secondary
0xffffffff.toInt(), // OnSecondary
0xffffffff.toInt(), // Surface
0xff000000.toInt(), // OnSurface
0xffe9e9eb.toInt(), // SurfaceVariant
0xff3c3c43.toInt(), // OnSurfaceVariant
0xffffffff.toInt(), // Background
0xff000000.toInt(), // OnBackground
0xffc6c6c8.toInt(), // Outline
0xffe5e5ea.toInt(), // OutlineVariant
0xffff3b30.toInt(), // Error
0xffffffff.toInt(), // OnError
0xfff2f2f7.toInt(), // SurfaceContainer
0xffaf52de.toInt(), // Tertiary
0xffffffff.toInt(), // OnTertiary
0xffd6e4ff.toInt(), // PrimaryContainer
0xff003070.toInt(), // OnPrimaryContainer
0xffe2e0ff.toInt(), // SecondaryContainer
0xff2a1b70.toInt(), // OnSecondaryContainer
0xfff3ddfb.toInt(), // TertiaryContainer
0xff3d0b52.toInt(), // OnTertiaryContainer
0xff9b2393.toInt(), // SyntaxKeyword
0xffc41a16.toInt(), // SyntaxString
0xff5d6c79.toInt(), // SyntaxComment
0xff1c00cf.toInt(), // SyntaxNumber
0xff6c36a9.toInt(), // SyntaxConstant
0xff3900a0.toInt(), // SyntaxType
0xff326d74.toInt(), // SyntaxFunction
0xff000000.toInt(), // SyntaxVariable
0xff326d74.toInt(), // SyntaxProperty
0xff000000.toInt(), // SyntaxOperator
0xff000000.toInt(), // SyntaxPunctuation
0xff0b4f79.toInt(), // SyntaxTag
0xff815f03.toInt(), // SyntaxAttribute
0xff1c00cf.toInt(), // SyntaxEscape
0xff643820.toInt(), // SyntaxMacro
0xff00722a.toInt(), // DiffAdded
0xffc40012.toInt(), // DiffRemoved
0xff007aff.toInt(), // DiffModified
0xffd5e3de.toInt(), // DiffAddedContainer
0xffecd5dc.toInt(), // DiffRemovedContainer
0xffa9ccb9.toInt(), // DiffAddedEmphasis
0xffe4a9b2.toInt(), // DiffRemovedEmphasis
),
intArrayOf(
0xff0a84ff.toInt(), // Primary
0xffffffff.toInt(), // OnPrimary
0xff5e5ce6.toInt(), // Secondary
0xffffffff.toInt(), // OnSecondary
0xff1c1c1e.toInt(), // Surface
0xffffffff.toInt(), // OnSurface
0xff2c2c2e.toInt(), // SurfaceVariant
0xffebebf5.toInt(), // OnSurfaceVariant
0xff000000.toInt(), // Background
0xffffffff.toInt(), // OnBackground
0xff38383a.toInt(), // Outline
0xff48484a.toInt(), // OutlineVariant
0xffff453a.toInt(), // Error
0xffffffff.toInt(), // OnError
0xff1c1c1e.toInt(), // SurfaceContainer
0xffbf5af2.toInt(), // Tertiary
0xffffffff.toInt(), // OnTertiary
0xff0a2d52.toInt(), // PrimaryContainer
0xffcfe3ff.toInt(), // OnPrimaryContainer
0xff262663.toInt(), // SecondaryContainer
0xffdedcff.toInt(), // OnSecondaryContainer
0xff3f1a52.toInt(), // TertiaryContainer
0xfff1d9fa.toInt(), // OnTertiaryContainer
0xfffc5fa3.toInt(), // SyntaxKeyword
0xfffc6a5d.toInt(), // SyntaxString
0xff798693.toInt(), // SyntaxComment
0xffd0bf69.toInt(), // SyntaxNumber
0xffa167e6.toInt(), // SyntaxConstant
0xffd0a8ff.toInt(), // SyntaxType
0xff67b7a4.toInt(), // SyntaxFunction
0xffdfdfe0.toInt(), // SyntaxVariable
0xff67b7a4.toInt(), // SyntaxProperty
0xffdfdfe0.toInt(), // SyntaxOperator
0xffdfdfe0.toInt(), // SyntaxPunctuation
0xff5dd8ff.toInt(), // SyntaxTag
0xffbf8555.toInt(), // SyntaxAttribute
0xffd0bf69.toInt(), // SyntaxEscape
0xfffd8f3f.toInt(), // SyntaxMacro
0xff30d158.toInt(), // DiffAdded
0xffff6e66.toInt(), // DiffRemoved
0xff0a84ff.toInt(), // DiffModified
0xff20402a.toInt(), // DiffAddedContainer
0xff492c2c.toInt(), // DiffRemovedContainer
0xff246435.toInt(), // DiffAddedEmphasis
0xff773d3b.toInt(), // DiffRemovedEmphasis
),
arrayOf(
TypeToken(34.0f, 700, 41.0f, 0.37f, false), // Display
TypeToken(28.0f, 700, 34.0f, 0.36f, false), // Headline
TypeToken(22.0f, 700, 28.0f, 0.35f, false), // Title
TypeToken(17.0f, 600, 22.0f, -0.41f, false), // Subtitle
TypeToken(17.0f, 400, 22.0f, -0.41f, false), // Body
TypeToken(17.0f, 600, 22.0f, -0.41f, false), // BodyStrong
TypeToken(15.0f, 600, 20.0f, -0.24f, false), // Label
TypeToken(12.0f, 500, 16.0f, 0.0f, false), // Caption
TypeToken(15.0f, 400, 20.0f, 0.0f, true), // Mono
),
floatArrayOf(0.0f, 6.0f, 10.0f, 16.0f, 22.0f, 1000.0f),
floatArrayOf(0.0f, 4.0f, 10.0f, 18.0f, 24.0f, 36.0f, 48.0f),
)
fun of(system: DesignSystem): DesignTokenTable = when (system) {
DesignSystem.Material3 -> MATERIAL3
DesignSystem.Cupertino -> CUPERTINO
DesignSystem.Fluent -> FLUENT
DesignSystem.Gnome -> GNOME
DesignSystem.Breeze -> BREEZE
DesignSystem.Deepin -> DEEPIN
DesignSystem.LiquidGlass -> LIQUID_GLASS
}
}