mirror of https://github.com/irmen/ksim65.git
174 lines
6.6 KiB
Kotlin
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()
|
|
}
|
|
}
|
|
}
|