Skip to content

Commit 7a2f4c4

Browse files
committed
Change the handling of out-of-bounds canvas
1 parent ace6671 commit 7a2f4c4

File tree

1 file changed

+57
-7
lines changed
  • mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic

1 file changed

+57
-7
lines changed

mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/canvas.kt

+57-7
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,58 @@ internal interface TextCanvas {
1919
operator fun get(row: Int, column: Int): TextPixel
2020
}
2121

22-
private val blankPixel = TextPixel(' ')
23-
2422
internal class TextSurface(
25-
override val width: Int,
26-
override val height: Int,
23+
initialWidth: Int,
24+
initialHeight: Int,
2725
) : TextCanvas {
26+
private var realWidth: Int = initialWidth
27+
private var realHeight: Int = initialHeight
28+
29+
override val width: Int get() = realWidth
30+
override val height: Int get() = realHeight
31+
2832
override var translationX = 0
2933
override var translationY = 0
3034

31-
private val rows = Array(height) { Array(width) { TextPixel(' ') } }
35+
private val rows = MutableList(height) { createBlankRow(width) }
36+
37+
override operator fun get(row: Int, column: Int): TextPixel {
38+
val x = translationX + column
39+
val y = translationY + row
40+
if (x < 0 || y < 0) {
41+
return reusableDirtyPixel
42+
}
43+
val widthDiff = x - width + 1
44+
if (widthDiff > 0) {
45+
if (widthDiff == 1) {
46+
rows.forEach { it.add(newBlankPixel) }
47+
} else {
48+
rows.forEach { it.addAll(createBlankRow(widthDiff)) }
49+
}
50+
realWidth += widthDiff
51+
}
52+
val heightDiff = y - height + 1
53+
if (heightDiff > 0) {
54+
if (heightDiff == 1) {
55+
rows.add(createBlankRow(width))
56+
} else {
57+
rows.addAll(MutableList(heightDiff) { createBlankRow(width) })
58+
}
59+
realHeight += heightDiff
60+
}
61+
return rows[y][x]
62+
}
3263

33-
override operator fun get(row: Int, column: Int) = rows[translationY + row][translationX + column]
64+
private inline fun createBlankRow(size: Int): MutableList<TextPixel> {
65+
return MutableList(size) { newBlankPixel }
66+
}
3467

3568
fun appendRowTo(appendable: Appendable, row: Int) {
3669
// Reused heap allocation for building ANSI attributes inside the loop.
3770
val attributes = mutableListOf<Int>()
3871

3972
val rowPixels = rows[row]
40-
var lastPixel = blankPixel
73+
var lastPixel = reusableBlankPixel
4174
for (columnIndex in 0 until width) {
4275
val pixel = rowPixels[columnIndex]
4376
if (pixel.foreground != lastPixel.foreground) {
@@ -91,6 +124,23 @@ internal class TextSurface(
91124
}
92125
}
93126

127+
/**
128+
* Returns always a new blank [TextPixel].
129+
*/
130+
private val newBlankPixel: TextPixel get() = TextPixel(' ')
131+
132+
/**
133+
* It is used in places where it is important that the [TextPixel]
134+
* has its original state and **will not change**.
135+
*/
136+
private val reusableBlankPixel: TextPixel = newBlankPixel
137+
138+
/**
139+
* It is used in places where the [TextPixel] state is not important
140+
* and it can change.
141+
*/
142+
private val reusableDirtyPixel: TextPixel = newBlankPixel
143+
94144
internal class TextPixel(var value: String) {
95145
var background: Color? = null
96146
var foreground: Color? = null

0 commit comments

Comments
 (0)