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

168 lines
6.0 KiB
Kotlin

package razorvine.ksim65.components
import razorvine.ksim65.IHostInterface
import kotlin.math.min
/**
* 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.
*
* 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 read or write pixel value at pX, pY
* 08 cursor X position (r/w)
* 09 cursor Y position (r/w)
* 0a read or write character at cursor pos, updates cursor position, scrolls up if necessary
* control characters: 0x08=backspace, 0x09=tab, 0x0a=newline, 0x0c=formfeed(clear screen), 0x0d=carriagereturn
*
* TODO: cursor blinking, blink speed (0=off)
*/
class Display(
startAddress: Address, endAddress: Address,
private val host: IHostInterface,
private val charWidth: Int,
private val charHeight: Int,
private val pixelWidth: Int,
private val pixelHeight: 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<ShortArray>(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.
}
override fun reset() {
charMatrix.forEach { it.fill(' '.toShort()) }
cursorX = 0
cursorY = 0
charposX = 0
charposY = 0
pixelX = 0
pixelY = 0
host.clearScreen()
}
override operator fun get(address: Address): UByte {
return when(address-startAddress) {
0x02 -> {
if(charposY in 0 until charHeight && charposX in 0 until charWidth) {
charMatrix[charposY][charposX]
} else 0xff
}
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(address: Address, data: UByte) {
when(address-startAddress) {
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.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(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] = ' '.toShort()
host.setChar(cursorX, cursorY, ' ')
}
0x09 -> {
// tab
cursorX = (cursorX and 248) + 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.toChar())
cursorX++
if(cursorX >= charWidth) {
cursorX = 0
cursorDown()
}
}
}
}
}
}
}
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] = ' '.toShort()
}
cursorY--
host.scrollUp()
}
}
}