ksim65/src/main/kotlin/razorvine/ksim65/components/Display.kt

174 lines
6.6 KiB
Kotlin

package razorvine.ksim65.components
import razorvine.examplemachines.ScreenDefs
import razorvine.ksim65.IHostInterface
import kotlin.math.min
/**
* Monochrome Text mode and graphics (bitmap) mode display.
* Note that the character matrix and pixel matrix are NOT memory mapped,
* this display device is controlled by sending char/pixel commands to it.
* Also, the blinking cursor is 'hardware controlled' (by the host display),
* these device registers merely can set it to a different screen position.
*
* Requires a host display to actually view the stuff, obviously.
*
* reg. value
* ---- -----
* 00 char X position
* 01 char Y position
* 02 r/w character at cX,cY (doesn't change cursor position)
* 03 pixel X pos (lsb)
* 04 pixel X pos (msb)
* 05 pixel Y pos (lsb)
* 06 pixel Y pos (msb)
* 07 r/w pixel value at pX, pY
* 08 cursor X position
* 09 cursor Y position
* 0a r/w character at cursor pos, updates cursor position, scrolls up if necessary
* control chars: 8=backspace, 9=tab, 10=newline, 12=form feed (clear screen), 13=carriage return
*/
class Display(startAddress: Address, endAddress: Address, private val host: IHostInterface,
private val charWidth: Int, private val charHeight: Int) :
MemMappedComponent(startAddress, endAddress) {
init {
require(endAddress-startAddress+1 == 11) { "display needs exactly 11 memory bytes" }
}
private var cursorX = 0
private var cursorY = 0
private var charposX = 0
private var charposY = 0
private var pixelX = 0
private var pixelY = 0
private val charMatrix = Array(charHeight) { ShortArray(charWidth) } // matrix[y][x] to access
override fun clock() {
// if the system clock is synced to the display refresh,
// you *could* add a Vertical Blank interrupt here.
// It's usually better to rely on a (real time) timer instead for that,
// to avoid having the Vertical blank tied to the simulation speed
}
override fun reset() {
charMatrix.forEach { it.fill(' '.code.toShort()) }
cursorX = 0
cursorY = 0
charposX = 0
charposY = 0
pixelX = 0
pixelY = 0
host.clearScreen()
}
override operator fun get(offset: Int): UByte {
return when (offset) {
0x00 -> charposX.toShort()
0x01 -> charposY.toShort()
0x02 -> {
if (charposY in 0 until charHeight && charposX in 0 until charWidth) {
charMatrix[charposY][charposX]
} else 0xff
}
0x03 -> (pixelX and 0xff).toShort()
0x04 -> (pixelX ushr 8).toShort()
0x05 -> (pixelY and 0xff).toShort()
0x06 -> (pixelY ushr 8).toShort()
0x07 -> if (host.getPixel(pixelX, pixelY)) 1 else 0
0x08 -> cursorX.toShort()
0x09 -> cursorY.toShort()
0x0a -> {
if (cursorY in 0 until charHeight && cursorX in 0 until charWidth) {
charMatrix[cursorY][cursorX]
} else 0xff
}
else -> return 0xff
}
}
override operator fun set(offset: Int, data: UByte) {
when (offset) {
0x00 -> charposX = data.toInt()
0x01 -> charposY = data.toInt()
0x02 -> {
if (charposY in 0 until charHeight && charposX in 0 until charWidth) {
charMatrix[charposY][charposX] = data
host.setChar(charposX, charposY, data.toInt().toChar())
}
}
0x03 -> pixelX = (pixelX and 0xff00) or data.toInt()
0x04 -> pixelX = (pixelX and 0x00ff) or (data.toInt() shl 8)
0x05 -> pixelY = (pixelY and 0xff00) or data.toInt()
0x06 -> pixelY = (pixelY and 0x00ff) or (data.toInt() shl 8)
0x07 -> {
if (pixelX in 0 until ScreenDefs.COLUMNS*charWidth && pixelY in 0 until ScreenDefs.ROWS*charHeight) {
if (data == 0.toShort()) host.clearPixel(pixelX, pixelY)
else host.setPixel(pixelX, pixelY)
}
}
0x08 -> cursorX = min(data.toInt() and 65535, charWidth-1)
0x09 -> cursorY = min(data.toInt() and 65535, charHeight-1)
0x0a -> {
if (cursorY in 0 until charHeight && cursorX in 0 until charWidth) {
when (data.toInt()) {
0x08 -> {
// backspace
cursorX--
if (cursorX < 0) {
if (cursorY > 0) {
cursorY--
cursorX = charWidth-1
}
}
charMatrix[cursorY][cursorX] = ' '.code.toShort()
host.setChar(cursorX, cursorY, ' ')
}
0x09 -> {
// tab
cursorX = (cursorX and 0b11111000)+8
if (cursorX >= charWidth) {
cursorX = 0
cursorDown()
}
}
0x0a -> {
// newline
cursorX = 0
cursorDown()
}
0x0c -> reset() // clear screen
0x0d -> cursorX = 0 // carriage return
else -> {
// set character on screen
charMatrix[cursorY][cursorX] = data
host.setChar(cursorX, cursorY, data.toInt().toChar())
cursorX++
if (cursorX >= charWidth) {
cursorX = 0
cursorDown()
}
}
}
}
host.cursor(cursorX, cursorY)
}
}
}
private fun cursorDown() {
cursorY++
while (cursorY >= charHeight) {
// scroll up 1 line
for (y in 0..charHeight-2) {
charMatrix[y+1].copyInto(charMatrix[y])
}
for (x in 0 until charWidth) {
charMatrix[charHeight-1][x] = ' '.code.toShort()
}
cursorY--
host.scrollUp()
}
}
}