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() } } }