2019-09-20 20:12:58 +00:00
|
|
|
package razorvine.examplemachines
|
2019-09-15 03:04:57 +00:00
|
|
|
|
2019-09-28 23:26:31 +00:00
|
|
|
import razorvine.ksim65.*
|
2019-09-15 03:04:57 +00:00
|
|
|
import java.awt.*
|
|
|
|
import java.awt.event.*
|
2019-10-05 13:14:26 +00:00
|
|
|
import java.awt.image.BufferedImage
|
2019-09-18 19:14:00 +00:00
|
|
|
import java.util.*
|
2019-10-05 13:14:26 +00:00
|
|
|
import javax.imageio.ImageIO
|
2019-09-15 03:04:57 +00:00
|
|
|
import javax.swing.*
|
2019-10-05 13:14:26 +00:00
|
|
|
import javax.swing.event.MouseInputListener
|
2019-09-15 03:04:57 +00:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Define a monochrome screen that can display 640x480 pixels
|
|
|
|
* and/or 80x30 characters (these are 8x16 pixels).
|
|
|
|
*/
|
|
|
|
object ScreenDefs {
|
|
|
|
const val SCREEN_WIDTH_CHARS = 80
|
|
|
|
const val SCREEN_HEIGHT_CHARS = 30
|
2019-10-12 10:35:18 +00:00
|
|
|
const val SCREEN_WIDTH = SCREEN_WIDTH_CHARS*8
|
|
|
|
const val SCREEN_HEIGHT = SCREEN_HEIGHT_CHARS*16
|
2019-09-30 19:43:57 +00:00
|
|
|
const val DISPLAY_PIXEL_SCALING: Double = 1.5
|
|
|
|
const val BORDER_SIZE = 32
|
|
|
|
|
2019-09-15 03:04:57 +00:00
|
|
|
val BG_COLOR = Color(0, 10, 20)
|
|
|
|
val FG_COLOR = Color(200, 255, 230)
|
|
|
|
val BORDER_COLOR = Color(20, 30, 40)
|
|
|
|
val Characters = loadCharacters()
|
|
|
|
|
|
|
|
private fun loadCharacters(): Array<BufferedImage> {
|
2019-09-16 21:52:25 +00:00
|
|
|
val img = ImageIO.read(javaClass.getResourceAsStream("/charset/unscii8x16.png"))
|
2019-10-14 18:48:48 +00:00
|
|
|
val charactersImage = BufferedImage(img.width, img.height, BufferedImage.TYPE_INT_ARGB)
|
2019-09-15 03:04:57 +00:00
|
|
|
charactersImage.createGraphics().drawImage(img, 0, 0, null)
|
|
|
|
|
|
|
|
val black = Color(0, 0, 0).rgb
|
|
|
|
val foreground = FG_COLOR.rgb
|
|
|
|
val nopixel = Color(0, 0, 0, 0).rgb
|
|
|
|
for (y in 0 until charactersImage.height) {
|
|
|
|
for (x in 0 until charactersImage.width) {
|
|
|
|
val col = charactersImage.getRGB(x, y)
|
2019-10-12 10:35:18 +00:00
|
|
|
if (col == black) charactersImage.setRGB(x, y, nopixel)
|
|
|
|
else charactersImage.setRGB(x, y, foreground)
|
2019-09-15 03:04:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-12 10:35:18 +00:00
|
|
|
val numColumns = charactersImage.width/8
|
2019-09-15 03:04:57 +00:00
|
|
|
val charImages = (0..255).map {
|
2019-10-12 10:35:18 +00:00
|
|
|
val charX = it%numColumns
|
|
|
|
val charY = it/numColumns
|
|
|
|
charactersImage.getSubimage(charX*8, charY*16, 8, 16)
|
2019-09-15 03:04:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return charImages.toTypedArray()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private class BitmapScreenPanel : JPanel() {
|
|
|
|
|
2019-10-01 23:25:16 +00:00
|
|
|
private val image: BufferedImage
|
|
|
|
private val g2d: Graphics2D
|
2019-09-15 03:04:57 +00:00
|
|
|
private var cursorX: Int = 0
|
|
|
|
private var cursorY: Int = 0
|
2019-09-16 21:52:25 +00:00
|
|
|
private var cursorState: Boolean = false
|
2019-09-15 03:04:57 +00:00
|
|
|
|
|
|
|
init {
|
2019-10-01 23:25:16 +00:00
|
|
|
val ge = GraphicsEnvironment.getLocalGraphicsEnvironment()
|
|
|
|
val gd = ge.defaultScreenDevice.defaultConfiguration
|
|
|
|
image = gd.createCompatibleImage(ScreenDefs.SCREEN_WIDTH, ScreenDefs.SCREEN_HEIGHT, Transparency.OPAQUE)
|
|
|
|
g2d = image.graphics as Graphics2D
|
|
|
|
|
2019-10-15 19:39:04 +00:00
|
|
|
val size = Dimension((image.width*ScreenDefs.DISPLAY_PIXEL_SCALING).toInt(),
|
|
|
|
(image.height*ScreenDefs.DISPLAY_PIXEL_SCALING).toInt())
|
2019-09-15 03:04:57 +00:00
|
|
|
minimumSize = size
|
|
|
|
maximumSize = size
|
|
|
|
preferredSize = size
|
|
|
|
isFocusable = true
|
2019-10-01 23:25:16 +00:00
|
|
|
isDoubleBuffered = false
|
2019-09-15 03:04:57 +00:00
|
|
|
requestFocusInWindow()
|
2019-10-01 23:25:16 +00:00
|
|
|
clearScreen()
|
2019-09-15 03:04:57 +00:00
|
|
|
}
|
|
|
|
|
2019-09-30 19:43:57 +00:00
|
|
|
override fun paint(graphics: Graphics) {
|
|
|
|
val g2d = graphics as Graphics2D
|
2019-10-05 13:14:26 +00:00
|
|
|
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR)
|
2019-10-12 10:35:18 +00:00
|
|
|
g2d.drawImage(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()
|
2019-09-16 21:52:25 +00:00
|
|
|
g2d.setXORMode(Color.CYAN)
|
|
|
|
g2d.fillRect(scx, scy, scw, sch)
|
|
|
|
g2d.setPaintMode()
|
|
|
|
}
|
2019-10-05 13:14:26 +00:00
|
|
|
Toolkit.getDefaultToolkit().sync()
|
2019-09-15 03:04:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fun clearScreen() {
|
|
|
|
g2d.background = ScreenDefs.BG_COLOR
|
|
|
|
g2d.clearRect(0, 0, ScreenDefs.SCREEN_WIDTH, ScreenDefs.SCREEN_HEIGHT)
|
2019-09-16 23:18:32 +00:00
|
|
|
cursorPos(0, 0)
|
2019-09-15 03:04:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fun setPixel(x: Int, y: Int, onOff: Boolean) {
|
2019-10-12 10:35:18 +00:00
|
|
|
if (onOff) image.setRGB(x, y, ScreenDefs.FG_COLOR.rgb)
|
|
|
|
else image.setRGB(x, y, ScreenDefs.BG_COLOR.rgb)
|
2019-09-15 03:04:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fun getPixel(x: Int, y: Int) = image.getRGB(x, y) != ScreenDefs.BG_COLOR.rgb
|
|
|
|
|
|
|
|
fun setChar(x: Int, y: Int, character: Char) {
|
2019-10-12 10:35:18 +00:00
|
|
|
g2d.clearRect(8*x, 16*y, 8, 16)
|
2019-09-15 03:04:57 +00:00
|
|
|
val coloredImage = ScreenDefs.Characters[character.toInt()]
|
2019-10-12 10:35:18 +00:00
|
|
|
g2d.drawImage(coloredImage, 8*x, 16*y, null)
|
2019-09-15 03:04:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fun scrollUp() {
|
2019-10-12 10:35:18 +00:00
|
|
|
g2d.copyArea(0, 16, ScreenDefs.SCREEN_WIDTH, ScreenDefs.SCREEN_HEIGHT-16, 0, -16)
|
2019-09-15 03:04:57 +00:00
|
|
|
g2d.background = ScreenDefs.BG_COLOR
|
2019-10-12 10:35:18 +00:00
|
|
|
g2d.clearRect(0, ScreenDefs.SCREEN_HEIGHT-16, ScreenDefs.SCREEN_WIDTH, 16)
|
2019-09-15 03:04:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fun mousePixelPosition(): Point? {
|
|
|
|
val pos = mousePosition ?: return null
|
2019-10-12 10:35:18 +00:00
|
|
|
return Point((pos.x/ScreenDefs.DISPLAY_PIXEL_SCALING).toInt(), (pos.y/ScreenDefs.DISPLAY_PIXEL_SCALING).toInt())
|
2019-09-15 03:04:57 +00:00
|
|
|
}
|
2019-09-16 21:52:25 +00:00
|
|
|
|
2019-09-16 23:18:32 +00:00
|
|
|
fun cursorPos(x: Int, y: Int) {
|
2019-10-12 10:35:18 +00:00
|
|
|
if (x != cursorX || y != cursorY) cursorState = true
|
2019-09-16 21:52:25 +00:00
|
|
|
cursorX = x
|
|
|
|
cursorY = y
|
2019-09-16 23:18:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fun blinkCursor() {
|
2019-09-16 21:52:25 +00:00
|
|
|
cursorState = !cursorState
|
|
|
|
}
|
2019-09-15 03:04:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class MainWindow(title: String) : JFrame(title), KeyListener, MouseInputListener, IHostInterface {
|
|
|
|
private val canvas = BitmapScreenPanel()
|
2019-09-20 20:12:58 +00:00
|
|
|
private val keyboardBuffer = ArrayDeque<Char>()
|
|
|
|
private var mousePos = Point(0, 0)
|
|
|
|
private var leftButton = false
|
|
|
|
private var rightButton = false
|
|
|
|
private var middleButton = false
|
2019-09-15 03:04:57 +00:00
|
|
|
|
|
|
|
init {
|
2019-09-30 19:43:57 +00:00
|
|
|
contentPane.layout = GridBagLayout()
|
2019-09-15 03:04:57 +00:00
|
|
|
defaultCloseOperation = EXIT_ON_CLOSE
|
|
|
|
isResizable = false
|
|
|
|
isFocusable = true
|
2019-09-30 19:43:57 +00:00
|
|
|
contentPane.background = ScreenDefs.BORDER_COLOR
|
|
|
|
val gc = GridBagConstraints()
|
|
|
|
gc.fill = GridBagConstraints.BOTH
|
2019-10-12 10:35:18 +00:00
|
|
|
gc.gridx = 1
|
|
|
|
gc.gridy = 1
|
2019-09-30 19:43:57 +00:00
|
|
|
gc.insets = Insets(ScreenDefs.BORDER_SIZE, ScreenDefs.BORDER_SIZE, ScreenDefs.BORDER_SIZE, ScreenDefs.BORDER_SIZE)
|
|
|
|
contentPane.add(canvas, gc)
|
2019-09-15 03:04:57 +00:00
|
|
|
addKeyListener(this)
|
|
|
|
addMouseMotionListener(this)
|
|
|
|
addMouseListener(this)
|
2019-09-21 13:57:14 +00:00
|
|
|
setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, mutableSetOf())
|
2019-09-15 03:04:57 +00:00
|
|
|
pack()
|
|
|
|
setLocationRelativeTo(null)
|
2019-09-21 13:57:14 +00:00
|
|
|
location = Point(location.x/2, location.y)
|
2019-09-19 19:29:33 +00:00
|
|
|
requestFocusInWindow()
|
2019-09-15 03:04:57 +00:00
|
|
|
}
|
|
|
|
|
2019-10-04 18:39:57 +00:00
|
|
|
fun start(updateRate: Int) {
|
|
|
|
// repaint the screen's back buffer
|
2019-09-16 23:18:32 +00:00
|
|
|
var cursorBlink = 0L
|
2019-10-12 10:35:18 +00:00
|
|
|
val repaintTimer = javax.swing.Timer(1000/updateRate) {
|
2019-09-16 23:18:32 +00:00
|
|
|
repaint()
|
2019-10-12 10:35:18 +00:00
|
|
|
if (it.`when`-cursorBlink > 200L) {
|
2019-09-16 23:18:32 +00:00
|
|
|
cursorBlink = it.`when`
|
|
|
|
canvas.blinkCursor()
|
|
|
|
}
|
|
|
|
}
|
2019-09-15 03:04:57 +00:00
|
|
|
repaintTimer.initialDelay = 0
|
|
|
|
repaintTimer.start()
|
|
|
|
}
|
|
|
|
|
|
|
|
// keyboard events:
|
2019-09-18 18:57:46 +00:00
|
|
|
override fun keyTyped(event: KeyEvent) {
|
|
|
|
keyboardBuffer.add(event.keyChar)
|
2019-10-12 10:35:18 +00:00
|
|
|
while (keyboardBuffer.size > 8) keyboardBuffer.pop()
|
2019-09-15 03:04:57 +00:00
|
|
|
}
|
|
|
|
|
2019-09-18 18:57:46 +00:00
|
|
|
override fun keyPressed(event: KeyEvent) {}
|
|
|
|
override fun keyReleased(event: KeyEvent) {}
|
2019-09-15 03:04:57 +00:00
|
|
|
|
|
|
|
// mouse events:
|
2019-09-18 18:57:46 +00:00
|
|
|
override fun mousePressed(event: MouseEvent) {
|
|
|
|
val pos = canvas.mousePixelPosition()
|
2019-10-12 10:35:18 +00:00
|
|
|
if (pos == null) return
|
2019-09-18 18:57:46 +00:00
|
|
|
else {
|
|
|
|
mousePos = pos
|
|
|
|
leftButton = leftButton or SwingUtilities.isLeftMouseButton(event)
|
|
|
|
rightButton = rightButton or SwingUtilities.isRightMouseButton(event)
|
|
|
|
middleButton = middleButton or SwingUtilities.isMiddleMouseButton(event)
|
|
|
|
}
|
|
|
|
}
|
2019-10-12 10:35:18 +00:00
|
|
|
|
2019-09-18 18:57:46 +00:00
|
|
|
override fun mouseReleased(event: MouseEvent) {
|
2019-09-15 03:04:57 +00:00
|
|
|
val pos = canvas.mousePixelPosition()
|
2019-10-12 10:35:18 +00:00
|
|
|
if (pos == null) return
|
2019-09-15 03:04:57 +00:00
|
|
|
else {
|
|
|
|
mousePos = pos
|
2019-09-18 18:57:46 +00:00
|
|
|
leftButton = leftButton xor SwingUtilities.isLeftMouseButton(event)
|
|
|
|
rightButton = rightButton xor SwingUtilities.isRightMouseButton(event)
|
|
|
|
middleButton = middleButton xor SwingUtilities.isMiddleMouseButton(event)
|
2019-09-15 03:04:57 +00:00
|
|
|
}
|
|
|
|
}
|
2019-10-12 10:35:18 +00:00
|
|
|
|
2019-09-18 18:57:46 +00:00
|
|
|
override fun mouseEntered(event: MouseEvent) {}
|
|
|
|
override fun mouseExited(event: MouseEvent) {}
|
|
|
|
override fun mouseDragged(event: MouseEvent) = mouseMoved(event)
|
|
|
|
override fun mouseClicked(event: MouseEvent) {}
|
2019-09-15 03:04:57 +00:00
|
|
|
|
2019-09-18 18:57:46 +00:00
|
|
|
override fun mouseMoved(event: MouseEvent) {
|
2019-09-15 03:04:57 +00:00
|
|
|
val pos = canvas.mousePixelPosition()
|
2019-10-12 10:35:18 +00:00
|
|
|
if (pos == null) return
|
|
|
|
else mousePos = pos
|
2019-09-15 03:04:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// the overrides required for IHostDisplay:
|
|
|
|
override fun clearScreen() = canvas.clearScreen()
|
2019-10-12 10:35:18 +00:00
|
|
|
|
2019-09-15 03:04:57 +00:00
|
|
|
override fun setPixel(x: Int, y: Int) = canvas.setPixel(x, y, true)
|
|
|
|
override fun clearPixel(x: Int, y: Int) = canvas.setPixel(x, y, false)
|
|
|
|
override fun getPixel(x: Int, y: Int) = canvas.getPixel(x, y)
|
|
|
|
override fun setChar(x: Int, y: Int, character: Char) = canvas.setChar(x, y, character)
|
2019-09-16 23:18:32 +00:00
|
|
|
override fun cursor(x: Int, y: Int) = canvas.cursorPos(x, y)
|
2019-09-15 03:04:57 +00:00
|
|
|
override fun scrollUp() = canvas.scrollUp()
|
2019-09-16 23:18:32 +00:00
|
|
|
override fun mouse() = IHostInterface.MouseInfo(mousePos.x, mousePos.y, leftButton, rightButton, middleButton)
|
2019-09-15 03:04:57 +00:00
|
|
|
|
|
|
|
override fun keyboard(): Char? {
|
2019-10-12 10:35:18 +00:00
|
|
|
return if (keyboardBuffer.isEmpty()) null
|
|
|
|
else keyboardBuffer.pop()
|
2019-09-15 03:04:57 +00:00
|
|
|
}
|
2019-09-16 21:52:25 +00:00
|
|
|
|
2019-09-15 03:04:57 +00:00
|
|
|
}
|