1
0
mirror of https://github.com/irmen/ksim65.git synced 2024-06-26 02:29:38 +00:00
ksim65/src/main/kotlin/razorvine/c64emu/GUI.kt

209 lines
8.2 KiB
Kotlin
Raw Normal View History

2019-09-19 19:29:33 +00:00
package razorvine.c64emu
import razorvine.ksim65.Cpu6502
2019-09-19 19:29:33 +00:00
import razorvine.ksim65.components.MemoryComponent
import java.awt.*
import java.awt.image.BufferedImage
2019-10-01 23:25:16 +00:00
import java.awt.image.VolatileImage
2019-09-19 19:29:33 +00:00
import java.awt.event.*
import javax.swing.*
/**
* Define the C64 character screen matrix: 320x200 pixels,
* 40x25 characters (of 8x8 pixels), and a colored border.
*/
object ScreenDefs {
const val SCREEN_WIDTH_CHARS = 40
const val SCREEN_HEIGHT_CHARS = 25
const val SCREEN_WIDTH = SCREEN_WIDTH_CHARS * 8
const val SCREEN_HEIGHT = SCREEN_HEIGHT_CHARS * 8
const val DISPLAY_PIXEL_SCALING: Double = 3.0
const val BORDER_SIZE = 24
2019-09-19 19:29:33 +00:00
val colorPalette = listOf( // this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/
Color(0x000000), // 0 = black
Color(0xFFFFFF), // 1 = white
Color(0x813338), // 2 = red
Color(0x75cec8), // 3 = cyan
Color(0x8e3c97), // 4 = purple
Color(0x56ac4d), // 5 = green
Color(0x2e2c9b), // 6 = blue
Color(0xedf171), // 7 = yellow
Color(0x8e5029), // 8 = orange
Color(0x553800), // 9 = brown
Color(0xc46c71), // 10 = light red
Color(0x4a4a4a), // 11 = dark grey
Color(0x7b7b7b), // 12 = medium grey
Color(0xa9ff9f), // 13 = light green
Color(0x706deb), // 14 = light blue
Color(0xb2b2b2) // 15 = light grey
)
}
private class BitmapScreenPanel(val chargenData: ByteArray, val ram: MemoryComponent) : JPanel() {
2019-10-01 23:25:16 +00:00
private val fullscreenImage: VolatileImage
private val fullscreenG2d: Graphics2D
2019-09-19 19:29:33 +00:00
private val normalCharacters = loadCharacters(false)
2019-09-21 15:25:11 +00:00
private val shiftedCharacters = loadCharacters(true)
2019-09-19 19:29:33 +00:00
init {
2019-10-01 23:25:16 +00:00
val ge = GraphicsEnvironment.getLocalGraphicsEnvironment()
val gd = ge.defaultScreenDevice.defaultConfiguration
fullscreenImage = gd.createCompatibleVolatileImage(ScreenDefs.SCREEN_WIDTH + 2*ScreenDefs.BORDER_SIZE,
ScreenDefs.SCREEN_HEIGHT + 2*ScreenDefs.BORDER_SIZE, Transparency.OPAQUE)
fullscreenG2d = fullscreenImage.graphics as Graphics2D
2019-09-19 19:29:33 +00:00
val size = Dimension(
fullscreenImage.width * ScreenDefs.DISPLAY_PIXEL_SCALING.toInt(),
fullscreenImage.height*ScreenDefs.DISPLAY_PIXEL_SCALING.toInt()
2019-09-19 19:29:33 +00:00
)
minimumSize = size
maximumSize = size
preferredSize = size
isFocusable = true
2019-10-01 23:25:16 +00:00
isDoubleBuffered = false
2019-09-19 19:29:33 +00:00
requestFocusInWindow()
}
private fun loadCharacters(shifted: Boolean): Array<BufferedImage> {
val chars = Array(256) { BufferedImage(8, 8, BufferedImage.TYPE_BYTE_BINARY) }
val offset = if (shifted) 256 * 8 else 0
2019-09-19 19:29:33 +00:00
// val color = ScreenDefs.colorPalette[14].rgb
for (char in 0..255) {
for (line in 0..7) {
val charbyte = chargenData[offset + char * 8 + line].toInt()
for (x in 0..7) {
if (charbyte and (0b10000000 ushr x) != 0)
2019-09-19 19:29:33 +00:00
chars[char].setRGB(x, line, 0xffffff)
}
}
}
return chars
}
2019-10-01 23:25:16 +00:00
override fun paint(graphics: Graphics) {
// draw the background color
fullscreenG2d.background = ScreenDefs.colorPalette[ram[0xd021].toInt() and 15]
fullscreenG2d.clearRect(ScreenDefs.BORDER_SIZE, ScreenDefs.BORDER_SIZE, ScreenDefs.SCREEN_WIDTH, ScreenDefs.SCREEN_HEIGHT)
// draw the characters
2019-09-19 19:29:33 +00:00
redrawCharacters()
2019-10-01 23:25:16 +00:00
// draw the screen border
fullscreenG2d.background = ScreenDefs.colorPalette[ram[0xd020].toInt() and 15]
fullscreenG2d.clearRect(0, 0, ScreenDefs.SCREEN_WIDTH + 2*ScreenDefs.BORDER_SIZE, ScreenDefs.BORDER_SIZE)
fullscreenG2d.clearRect(0, ScreenDefs.SCREEN_HEIGHT+ScreenDefs.BORDER_SIZE, ScreenDefs.SCREEN_WIDTH + 2*ScreenDefs.BORDER_SIZE, ScreenDefs.BORDER_SIZE)
fullscreenG2d.clearRect(0, ScreenDefs.BORDER_SIZE, ScreenDefs.BORDER_SIZE, ScreenDefs.SCREEN_HEIGHT)
fullscreenG2d.clearRect(ScreenDefs.SCREEN_WIDTH+ScreenDefs.BORDER_SIZE, ScreenDefs.BORDER_SIZE, ScreenDefs.BORDER_SIZE, ScreenDefs.SCREEN_HEIGHT)
// scale and draw the image to the window
2019-09-25 21:35:21 +00:00
val g2d = graphics as Graphics2D
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR)
2019-09-19 19:29:33 +00:00
g2d.drawImage(
fullscreenImage, 0, 0, (fullscreenImage.width * ScreenDefs.DISPLAY_PIXEL_SCALING).toInt(),
(fullscreenImage.height * ScreenDefs.DISPLAY_PIXEL_SCALING).toInt(), null
2019-09-19 19:29:33 +00:00
)
2019-09-25 21:35:21 +00:00
// simulate a slight scan line effect
g2d.color = Color(0, 0, 0, 40)
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR)
val width = fullscreenImage.width * ScreenDefs.DISPLAY_PIXEL_SCALING.toInt()
val height = fullscreenImage.height * ScreenDefs.DISPLAY_PIXEL_SCALING.toInt()
for (y in 0 until height step ScreenDefs.DISPLAY_PIXEL_SCALING.toInt()) {
g2d.drawLine(0, y, width, y)
2019-09-25 21:35:21 +00:00
}
Toolkit.getDefaultToolkit().sync()
2019-09-19 19:29:33 +00:00
}
private fun redrawCharacters() {
val screen = 0x0400
val colors = 0xd800
2019-09-21 15:25:11 +00:00
val shifted = (ram[0xd018].toInt() and 0b00000010) != 0
for (y in 0 until ScreenDefs.SCREEN_HEIGHT_CHARS) {
for (x in 0 until ScreenDefs.SCREEN_WIDTH_CHARS) {
val char = ram[screen + x + y * ScreenDefs.SCREEN_WIDTH_CHARS].toInt()
val color = ram[colors + x + y * ScreenDefs.SCREEN_WIDTH_CHARS].toInt()
2019-09-21 15:25:11 +00:00
drawColoredChar(x, y, char, color and 15, shifted)
2019-09-19 19:29:33 +00:00
}
}
}
2019-09-21 15:25:11 +00:00
private val coloredCharacters = mutableMapOf<Triple<Int, Int, Boolean>, BufferedImage>()
2019-09-19 19:29:33 +00:00
2019-09-21 15:25:11 +00:00
private fun drawColoredChar(x: Int, y: Int, char: Int, color: Int, shifted: Boolean) {
var cached = coloredCharacters[Triple(char, color, shifted)]
if (cached == null) {
cached = if (shifted) shiftedCharacters[char] else normalCharacters[char]
val colored = fullscreenG2d.deviceConfiguration.createCompatibleImage(8, 8, BufferedImage.BITMASK)
2019-09-19 19:29:33 +00:00
val sourceRaster = cached.raster
val coloredRaster = colored.raster
val pixelArray = IntArray(4)
val javaColor = ScreenDefs.colorPalette[color]
val coloredPixel = listOf(javaColor.red, javaColor.green, javaColor.blue, javaColor.alpha).toIntArray()
for (pixelY in 0..7) {
for (pixelX in 0..7) {
2019-09-19 19:41:44 +00:00
val source = sourceRaster.getPixel(pixelX, pixelY, pixelArray)
if (source[0] != 0) {
2019-09-19 19:41:44 +00:00
coloredRaster.setPixel(pixelX, pixelY, coloredPixel)
2019-09-19 19:29:33 +00:00
}
}
}
2019-09-21 15:25:11 +00:00
coloredCharacters[Triple(char, color, shifted)] = colored
2019-09-19 19:29:33 +00:00
cached = colored
}
fullscreenG2d.drawImage(cached, x * 8 + ScreenDefs.BORDER_SIZE, y * 8 + ScreenDefs.BORDER_SIZE, null)
2019-09-19 19:29:33 +00:00
}
}
class MainC64Window(
title: String,
chargenData: ByteArray,
val ram: MemoryComponent,
val cpu: Cpu6502,
val keypressCia: Cia
) : JFrame(title), KeyListener {
2019-09-19 19:29:33 +00:00
private val canvas = BitmapScreenPanel(chargenData, ram)
init {
defaultCloseOperation = EXIT_ON_CLOSE
isResizable = false
isFocusable = true
add(canvas)
2019-09-19 19:29:33 +00:00
addKeyListener(this)
pack()
setLocationRelativeTo(null)
location = Point(location.x / 2, location.y)
2019-09-19 19:29:33 +00:00
setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, mutableSetOf())
setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, mutableSetOf())
requestFocusInWindow()
}
2019-10-04 18:39:57 +00:00
fun start(updateRate: Int) {
// repaint the screen's back buffer
val repaintTimer = Timer(1000 / updateRate) {
2019-09-19 19:29:33 +00:00
repaint()
}
repaintTimer.initialDelay = 0
repaintTimer.start()
}
// keyboard events:
override fun keyTyped(event: KeyEvent) {}
override fun keyPressed(event: KeyEvent) {
// '\' is mapped as RESTORE, this causes a NMI on the cpu
if (event.keyChar == '\\') {
cpu.nmi()
} else {
keypressCia.hostKeyPressed(event)
}
}
override fun keyReleased(event: KeyEvent) {
keypressCia.hostKeyPressed(event)
}
2019-09-19 19:29:33 +00:00
}