diff --git a/build.gradle.kts b/build.gradle.kts index dfeadde..406dd28 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,6 +10,7 @@ plugins { id("org.jetbrains.dokka") version "0.9.18" id("com.jfrog.bintray") version "1.7.3" id("maven-publish") + id("application") } val versionProps = Properties().also { @@ -39,6 +40,11 @@ dependencies { testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.1.0") } +application { + applicationName = "ksim65vm" + mainClassName = "razorvine.examplemachine.SystemMainKt" +} + tasks.named("test") { // Enable JUnit 5 (Gradle 4.6+). useJUnitPlatform() diff --git a/src/main/kotlin/razorvine/examplemachine/GUI.kt b/src/main/kotlin/razorvine/examplemachine/GUI.kt index cbb7eb8..9cc5da3 100644 --- a/src/main/kotlin/razorvine/examplemachine/GUI.kt +++ b/src/main/kotlin/razorvine/examplemachine/GUI.kt @@ -28,7 +28,7 @@ object ScreenDefs { val Characters = loadCharacters() private fun loadCharacters(): Array { - val img = ImageIO.read(javaClass.getResource("/charset/unscii8x16.png")) + val img = ImageIO.read(javaClass.getResourceAsStream("/charset/unscii8x16.png")) val charactersImage = BufferedImage(img.width, img.height, BufferedImage.TYPE_INT_ARGB) charactersImage.createGraphics().drawImage(img, 0, 0, null) @@ -62,6 +62,7 @@ private class BitmapScreenPanel : JPanel() { private val g2d = image.graphics as Graphics2D private var cursorX: Int = 0 private var cursorY: Int = 0 + private var cursorState: Boolean = false init { val size = Dimension( @@ -85,6 +86,16 @@ private class BitmapScreenPanel : JPanel() { image, 0, 0, (image.width * ScreenDefs.DISPLAY_PIXEL_SCALING).toInt(), (image.height * ScreenDefs.DISPLAY_PIXEL_SCALING).toInt(), null ) + if(cursorState) { + val scx = (cursorX * ScreenDefs.DISPLAY_PIXEL_SCALING * 8).toInt() + val scy = (cursorY * ScreenDefs.DISPLAY_PIXEL_SCALING * 16).toInt() + val scw = (8 * ScreenDefs.DISPLAY_PIXEL_SCALING).toInt() + val sch = (16 * ScreenDefs.DISPLAY_PIXEL_SCALING).toInt() + g2d.setXORMode(Color.CYAN) + g2d.fillRect(scx, scy, scw, sch) + g2d.setPaintMode() + } + } fun clearScreen() { @@ -122,6 +133,12 @@ private class BitmapScreenPanel : JPanel() { (pos.y / ScreenDefs.DISPLAY_PIXEL_SCALING).toInt() ) } + + fun blinkCursor(x: Int, y: Int) { + cursorX = x + cursorY = y + cursorState = !cursorState + } } class DebugWindow(val vm: VirtualMachine) : JFrame("debugger"), ActionListener { @@ -368,4 +385,8 @@ class MainWindow(title: String) : JFrame(title), KeyListener, MouseInputListener else keyboardBuffer.pop() } + + override fun blinkCursor(x: Int, y: Int) { + canvas.blinkCursor(x, y) + } } diff --git a/src/main/kotlin/razorvine/examplemachine/systemMain.kt b/src/main/kotlin/razorvine/examplemachine/systemMain.kt index ee77d7f..6daad94 100644 --- a/src/main/kotlin/razorvine/examplemachine/systemMain.kt +++ b/src/main/kotlin/razorvine/examplemachine/systemMain.kt @@ -28,7 +28,7 @@ class VirtualMachine(title: String) { init { ram[Cpu6502.RESET_vector] = 0x00 ram[Cpu6502.RESET_vector + 1] = 0x10 - ram.loadPrg(javaClass.getResource("/vmdemo.prg").toURI()) + ram.loadPrg(javaClass.getResourceAsStream("/vmdemo.prg")) bus += rtc bus += timer @@ -57,7 +57,7 @@ class VirtualMachine(title: String) { val timer = java.util.Timer("clock", true) timer.scheduleAtFixedRate(1, 1) { if(!paused) { - repeat(5) { + repeat(10) { stepInstruction() } debugWindow.updateCpu(cpu) diff --git a/src/main/kotlin/razorvine/ksim65/IHostInterface.kt b/src/main/kotlin/razorvine/ksim65/IHostInterface.kt index be84d87..707e352 100644 --- a/src/main/kotlin/razorvine/ksim65/IHostInterface.kt +++ b/src/main/kotlin/razorvine/ksim65/IHostInterface.kt @@ -13,6 +13,7 @@ interface IHostInterface { fun scrollUp() fun mouse(): MouseInfo fun keyboard(): Char? + fun blinkCursor(x: Int, y: Int) class MouseInfo(val x: Int, val y: Int, val left: Boolean, val right: Boolean, val middle: Boolean) } diff --git a/src/main/kotlin/razorvine/ksim65/components/Display.kt b/src/main/kotlin/razorvine/ksim65/components/Display.kt index d212c27..d6586bd 100644 --- a/src/main/kotlin/razorvine/ksim65/components/Display.kt +++ b/src/main/kotlin/razorvine/ksim65/components/Display.kt @@ -8,6 +8,8 @@ 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. + * 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. * @@ -25,8 +27,6 @@ import kotlin.math.min * 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, @@ -49,10 +49,16 @@ class Display( private var pixelX = 0 private var pixelY = 0 private val charMatrix = Array(charHeight) { ShortArray(charWidth) } // matrix[y][x] to access + private var cursorCycles = 0 override fun clock() { // if the system clock is synced to the display refresh, // you *could* add a Vertical Blank interrupt here. + // for now, only the cursor blinking is controlled by this. + cursorCycles++ + if(cursorCycles % 8000 == 0) { + host.blinkCursor(cursorX, cursorY) + } } override fun reset() { diff --git a/src/main/kotlin/razorvine/ksim65/components/Ram.kt b/src/main/kotlin/razorvine/ksim65/components/Ram.kt index 09ac4c8..e1b4fb9 100644 --- a/src/main/kotlin/razorvine/ksim65/components/Ram.kt +++ b/src/main/kotlin/razorvine/ksim65/components/Ram.kt @@ -1,9 +1,8 @@ package razorvine.ksim65.components import java.io.File -import java.net.URI +import java.io.InputStream import java.net.URL -import java.nio.file.Paths /** * A RAM chip with read/write memory. @@ -33,14 +32,14 @@ class Ram(startAddress: Address, endAddress: Address) : MemoryComponent(startAdd * Load a c64-style prg program. This file type has the load address as the first two bytes. */ fun loadPrg(filename: String) { - loadPrg(Paths.get(filename).toUri()) + loadPrg(File(filename).inputStream()) } /** * Load a c64-style prg program. This file type has the load address as the first two bytes. */ - fun loadPrg(file: URI) { - val bytes = File(file).readBytes() + fun loadPrg(stream: InputStream) { + val bytes = stream.readAllBytes() val loadAddress = (bytes[0].toInt() or (bytes[1].toInt() shl 8)) and 65535 val baseAddress = loadAddress - startAddress bytes.drop(2).forEachIndexed { index, byte ->