Initial commit

This commit is contained in:
LeshaInc 2019-01-04 20:47:26 +02:00
commit cd50101510
No known key found for this signature in database
GPG Key ID: B4855290FC36DE72
40 changed files with 10544 additions and 0 deletions

43
.gitignore vendored Normal file
View File

@ -0,0 +1,43 @@
# SBT
target/
# Gradle
/.gradle
/build
# Eclipse
/.classpath
/.project
/.settings
/bin
# IntelliJ IDEA
/*.iml
/*.ipr
/*.iws
/.idea
/out
# Debugging
/eclipse
/run
# Windows...
**/Thumbs.db
# OS X
.DS_Store
# VSCode
/.vscode
# Local proxy settings
cacerts
# Temp files
/*.dll
/*.so
/*.dylib
# Log files
*.log

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "lib/ocelot-brain"]
path = lib/ocelot-brain
url = https://gitlab.com/cc-ru/ocelot/ocelot-brain

30
build.sbt Normal file
View File

@ -0,0 +1,30 @@
name := "ocelot-desktop"
version := "0.1"
scalaVersion := "2.12.8"
lazy val root = project.in(file("."))
.dependsOn(brain % "compile->compile")
.aggregate(brain)
lazy val brain = ProjectRef(file("lib/ocelot-brain"), "ocelot-brain")
libraryDependencies += "org.apache.logging.log4j" % "log4j-core" % "2.11.1"
libraryDependencies += "org.apache.logging.log4j" % "log4j-api" % "2.11.1"
libraryDependencies += "org.apache.logging.log4j" %% "log4j-api-scala" % "11.0"
val lwjglVersion = "3.2.1"
libraryDependencies += "org.lwjgl" % "lwjgl" % lwjglVersion
libraryDependencies += "org.lwjgl" % "lwjgl" % lwjglVersion classifier "natives-linux"
libraryDependencies += "org.lwjgl" % "lwjgl" % lwjglVersion classifier "natives-windows"
libraryDependencies += "org.lwjgl" % "lwjgl-glfw" % lwjglVersion
libraryDependencies += "org.lwjgl" % "lwjgl-glfw" % lwjglVersion classifier "natives-linux"
libraryDependencies += "org.lwjgl" % "lwjgl-glfw" % lwjglVersion classifier "natives-windows"
libraryDependencies += "org.lwjgl" % "lwjgl-opengl" % lwjglVersion
libraryDependencies += "org.lwjgl" % "lwjgl-opengl" % lwjglVersion classifier "natives-linux"
libraryDependencies += "org.lwjgl" % "lwjgl-opengl" % lwjglVersion classifier "natives-windows"
assemblyMergeStrategy in assembly := {
case PathList("META-INF", "MANIFEST.MF") => MergeStrategy.discard
case _ => MergeStrategy.first
}

1
lib/ocelot-brain Submodule

@ -0,0 +1 @@
Subproject commit 13c7448fadb34c76e1ae06ef75ad44efd785829a

1
project/assembly.sbt Normal file
View File

@ -0,0 +1 @@
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.9")

1
project/build.properties Normal file
View File

@ -0,0 +1 @@
sbt.version = 1.2.7

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</Console>
<File name="MyFile" fileName="ocelot-desktop.log" immediateFlush="false" append="false">
<PatternLayout pattern="%d{yyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</File>
</Appenders>
<Loggers>
<Root level="debug">
<AppenderRef ref="Console" />
<AppenderRef ref="MyFile"/>
</Root>
</Loggers>
</Configuration>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,13 @@
#version 140
#extension GL_ARB_separate_shader_objects : enable
in vec4 fColor;
in vec2 fUV;
out vec4 outColor;
uniform sampler2D uTexture;
void main() {
outColor = fColor;
}

View File

@ -0,0 +1,30 @@
#version 140
#extension GL_ARB_separate_shader_objects : enable
layout(location = 0) in vec2 inPos;
layout(location = 1) in vec2 inUV;
layout(location = 2) in vec4 inColor;
layout(location = 3) in vec3 inTransform0;
layout(location = 4) in vec3 inTransform1;
layout(location = 5) in vec3 inUVTransform0;
layout(location = 6) in vec3 inUVTransform1;
layout(location = 7) in float inZ;
out vec4 fColor;
out vec2 fUV;
uniform mat3 uProj;
void main() {
fColor = inColor;
mat3 modelMatrix = transpose(mat3(inTransform0, inTransform1, vec3(0.0, 0.0, 1.0)));
vec3 transformed = uProj * modelMatrix * vec3(inPos, 1.0);
gl_Position = vec4(transformed.xy, 1.0 - inZ / 8000000.0, 1.0);
mat3 uvMatrix = transpose(mat3(inUVTransform0, inUVTransform1, vec3(0.0, 0.0, 1.0)));
fUV = (uvMatrix * vec3(inUV, 1.0)).xy;
}

View File

@ -0,0 +1,14 @@
#version 140
#extension GL_ARB_separate_shader_objects : enable
in vec4 fColor;
in vec2 fUV;
out vec4 outColor;
uniform sampler2D uTexture;
void main() {
float alpha = texture(uTexture, fUV).r;
outColor = vec4(fColor.rgb, fColor.a * alpha);
}

View File

@ -0,0 +1,30 @@
#version 140
#extension GL_ARB_separate_shader_objects : enable
layout(location = 0) in vec2 inPos;
layout(location = 1) in vec2 inUV;
layout(location = 2) in vec4 inColor;
layout(location = 3) in vec3 inTransform0;
layout(location = 4) in vec3 inTransform1;
layout(location = 5) in vec3 inUVTransform0;
layout(location = 6) in vec3 inUVTransform1;
layout(location = 7) in float inZ;
out vec4 fColor;
out vec2 fUV;
uniform mat3 uProj;
void main() {
fColor = inColor;
mat3 modelMatrix = transpose(mat3(inTransform0, inTransform1, vec3(0.0, 0.0, 1.0)));
vec3 transformed = uProj * modelMatrix * vec3(inPos, 1.0);
gl_Position = vec4(transformed.xy, 1.0 - inZ / 8000000.0, 1.0);
mat3 uvMatrix = transpose(mat3(inUVTransform0, inUVTransform1, vec3(0.0, 0.0, 1.0)));
fUV = (uvMatrix * vec3(inUV, 1.0)).xy;
}

View File

@ -0,0 +1,256 @@
package ocelot.desktop
import java.io.{PrintWriter, StringWriter}
import ocelot.desktop.graphics.Graphics
import ocelot.desktop.util.{FontLoader, KeyMapping, ResourceManager}
import ocelot.desktop.widget.ScreenWidget
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.scala.Logging
import org.lwjgl.Version
import org.lwjgl.glfw.{GLFW, GLFWErrorCallback}
import org.lwjgl.opengl._
import org.lwjgl.system.MemoryUtil
import org.lwjgl.system.MemoryUtil.NULL
import totoro.ocelot.brain.Ocelot
import totoro.ocelot.brain.entity.{APU, Cable, Case, Keyboard, Memory, Screen}
import totoro.ocelot.brain.event._
import totoro.ocelot.brain.loot.Loot
import totoro.ocelot.brain.network.Network
import totoro.ocelot.brain.user.User
import totoro.ocelot.brain.util.Tier
import scala.collection.mutable.ArrayBuffer
object OcelotDesktop extends Logging {
private var window: Long = _
var windowWidth: Int = 0
var windowHeight: Int = 0
var graphics: Graphics = _
var redraw = true
def mainInner(): Unit = {
logger.info("Starting up Ocelot Desktop")
logger.info(s"Using LWJGL ${Version.getVersion}")
init()
loop()
logger.info("Cleaning up")
ResourceManager.freeResources()
GLFW.glfwTerminate()
GLFW.glfwSetErrorCallback(null).free()
logger.info("Thanks for using Ocelot Desktop")
}
def main(args: Array[String]): Unit = {
try mainInner()
catch {
case e: Exception =>
val sw = new StringWriter
val pw = new PrintWriter(sw)
e.printStackTrace(pw)
logger.error(s"${sw.toString}")
}
}
private def init(): Unit = {
GLFWErrorCallback.createPrint(System.err).set
if (!GLFW.glfwInit()) throw new IllegalStateException("Unable to initialize GLFW")
GLFW.glfwDefaultWindowHints()
GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, GLFW.GLFW_FALSE)
GLFW.glfwWindowHint(GLFW.GLFW_RESIZABLE, GLFW.GLFW_FALSE)
GLFW.glfwWindowHint(GLFW.GLFW_SAMPLES, 16)
window = GLFW.glfwCreateWindow(640, 400, "Ocelot Desktop", NULL, NULL)
if (window == NULL) throw new RuntimeException("Failed to create GLFW window")
windowWidth = 640
windowHeight = 400
GLFW.glfwMakeContextCurrent(window)
GL.createCapabilities()
GLFW.glfwSetKeyCallback(window, handleKey)
GLFW.glfwSetCharCallback(window, handleChar)
GLFW.glfwSetWindowSizeCallback(window, handleResize)
GLFW.glfwSetWindowRefreshCallback(window, handleRefresh)
GLFW.glfwSetInputMode(window, GLFW.GLFW_STICKY_KEYS, GLFW.GLFW_FALSE)
GLFW.glfwSwapInterval(1)
GLFW.glfwShowWindow(window)
GL11.glEnable(GL13.GL_MULTISAMPLE)
val s = GL11.glGetInteger(GL13.GL_SAMPLES)
logger.info(s"Created window with $s MSAA samples")
logger.info("OpenGL vendor: " + GL11.glGetString(GL11.GL_VENDOR))
logger.info("OpenGL renderer: " + GL11.glGetString(GL11.GL_RENDERER))
logger.info("OpenGL extensions: " + GL11.glGetString(GL11.GL_EXTENSIONS))
// GL11.glEnable(GL43.GL_DEBUG_OUTPUT)
// GL43.glDebugMessageCallback((_: Int, _: Int, _: Int, _: Int, length: Int, messagePtr: Long, _: Long) => {
// val message = MemoryUtil.memUTF8(MemoryUtil.memByteBuffer(messagePtr, length))
// logger.info(s"OpenGL: $message")
// }, 0)
FontLoader.init()
Ocelot.initialize(LogManager.getLogger(Ocelot))
createWorkspace()
graphics = new Graphics
}
var computer: Case = _
var screen: Screen = _
private def createWorkspace(): Unit = {
val network = new Network()
val cable = new Cable()
network.connect(cable)
computer = new Case(Tier.Four)
cable.connect(computer)
val cpu = new APU(Tier.Two)
computer.add(cpu)
val memory = new Memory(Tier.Six)
computer.add(memory)
computer.add(Loot.AdvLoaderEEPROM.create())
computer.add(Loot.OpenOsFloppy.create())
screen = new Screen(Tier.Three)
cable.connect(screen)
val keyboard = new Keyboard
screen.connect(keyboard)
EventBus.listenTo(classOf[BeepEvent], { case event: BeepEvent =>
println(s"[EVENT] Beep (address = ${event.address}, frequency = ${event.frequency}, duration = ${event.duration})")
})
EventBus.listenTo(classOf[BeepPatternEvent], { case event: BeepPatternEvent =>
println(s"[EVENT] Beep (address = ${event.address}, pattern = ${event.pattern})")
})
EventBus.listenTo(classOf[MachineCrashEvent], { case event: MachineCrashEvent =>
println(s"[EVENT] Machine crash! (address = ${event.address}, ${event.message})")
})
EventBus.listenTo(classOf[TextBufferSetEvent], { case event: TextBufferSetEvent =>
screenWidget.set(event.x, event.y, event.value, event.vertical)
})
EventBus.listenTo(classOf[TextBufferFillEvent], { case event: TextBufferFillEvent =>
screenWidget.fill(event.x, event.y, event.width, event.height, event.value)
})
EventBus.listenTo(classOf[TextBufferCopyEvent], { case event: TextBufferCopyEvent =>
screenWidget.copy(event.x, event.y, event.width, event.height, event.horizontalTranslation, event.verticalTranslation)
})
EventBus.listenTo(classOf[TextBufferSetForegroundColorEvent], { case event: TextBufferSetForegroundColorEvent =>
screenWidget.foreground = event.color
})
EventBus.listenTo(classOf[TextBufferSetBackgroundColorEvent], { case event: TextBufferSetBackgroundColorEvent =>
screenWidget.background = event.color
})
computer.turnOn()
}
val screenWidget = new ScreenWidget
private var lastUpdate = System.currentTimeMillis()
private def draw(): Unit = {
val time = System.currentTimeMillis()
if (time - lastUpdate >= 50) {
computer.update()
lastUpdate = time
}
graphics.clear()
screenWidget.draw(graphics)
graphics.commit()
}
private def handleRefresh(window: Long): Unit = {
redraw = true
}
private def handleResize(window: Long, width: Int, height: Int): Unit = {
GL11.glViewport(0, 0, width, height)
windowWidth = width
windowHeight = height
}
case class KeyAction(var action: Int, var code: Int, var char: Char)
val keyActions: ArrayBuffer[KeyAction] = ArrayBuffer()
private def handleKey(window: Long, key: Int, scancode: Int, action: Int, mods: Int): Unit = {
keyActions += KeyAction(action, KeyMapping.map(key), '\0')
}
private def handleChar(window: Long, codepoint: Int): Unit = {
keyActions.head.char = codepoint.asInstanceOf[Char]
}
private def loop(): Unit = {
var prevTime = System.currentTimeMillis()
var numFrames = 0
while (!GLFW.glfwWindowShouldClose(window)) {
graphics.setViewport(windowWidth, windowHeight)
if (redraw) {
draw()
redraw = true // FIXME
}
GLFW.glfwSwapBuffers(window)
GLFW.glfwPollEvents()
for (action <- keyActions) {
if (action.action == GLFW.GLFW_PRESS || action.action == GLFW.GLFW_REPEAT)
screen.keyDown(action.char, action.code, User("myself"))
else {
screen.keyUp(action.char, action.code, User("myself"))
}
}
keyActions.clear()
numFrames += 1
val currTime = System.currentTimeMillis()
val dt = currTime - prevTime
if (dt > 5000) {
prevTime = currTime
logger.info(s"FPS: ${numFrames.asInstanceOf[Float] / (dt.asInstanceOf[Float] / 1000.0)}")
numFrames = 0
}
}
}
}

View File

@ -0,0 +1,7 @@
package ocelot.desktop.color
trait Color {
def toRGBA: RGBAColor
def toRGBANorm: RGBAColorNorm
}

View File

@ -0,0 +1,13 @@
package ocelot.desktop.color
case class IntColor(color: Int) extends Color {
override def toRGBA: RGBAColor = {
RGBAColor(
(color >> 16).asInstanceOf[Short],
((color >> 8) & 0xFF).asInstanceOf[Short],
(color & 0xFF).asInstanceOf[Short], 255
)
}
override def toRGBANorm: RGBAColorNorm = toRGBA.toRGBANorm
}

View File

@ -0,0 +1,12 @@
package ocelot.desktop.color
case class RGBAColor(r: Short, g: Short, b: Short, a: Short) extends Color {
assert(0 <= r && r <= 255, "Invalid RED channel")
assert(0 <= g && g <= 255, "Invalid GREEN channel")
assert(0 <= b && b <= 255, "Invalid BLUE channel")
assert(0 <= a && a <= 255, "Invalid ALPHA channel")
override def toRGBA: RGBAColor = this
override def toRGBANorm: RGBAColorNorm = RGBAColorNorm(r.asInstanceOf[Float] / 255f, g.asInstanceOf[Float] / 255f, b.asInstanceOf[Float] / 255f, a.asInstanceOf[Float] / 255f)
}

View File

@ -0,0 +1,14 @@
package ocelot.desktop.color
case class RGBAColorNorm(r: Float, g: Float, b: Float, a: Float) extends Color {
assert(0 <= r && r <= 1.0f, "Invalid RED channel")
assert(0 <= g && g <= 1.0f, "Invalid GREEN channel")
assert(0 <= b && b <= 1.0f, "Invalid BLUE channel")
assert(0 <= a && a <= 1.0f, "Invalid ALPHA channel")
def array: Array[Float] = Array(r, g, b, a)
override def toRGBA: RGBAColor = RGBAColor((r * 255).asInstanceOf, (g * 255).asInstanceOf, (b * 255).asInstanceOf, (a * 255).asInstanceOf)
override def toRGBANorm: RGBAColorNorm = this
}

View File

@ -0,0 +1,58 @@
package ocelot.desktop.geometry
object Transform2D {
def identity: Transform2D = Transform2D(
1, 0, 0,
0, 1, 0
)
def scale(x: Float, y: Float): Transform2D = Transform2D(
x, 0, 0,
0, y, 0
)
def scale(a: Float): Transform2D = Transform2D.scale(a, a)
def translate(x: Float, y: Float): Transform2D = Transform2D(
1, 0, x,
0, 1, y
)
def viewport(width: Float, height: Float): Transform2D =
Transform2D.translate(-1f, 1f) >> Transform2D.scale(2f / width, -2f / height)
def rotate(angle: Float): Transform2D = {
val (s, c) = (math.sin(angle).asInstanceOf[Float], math.cos(angle).asInstanceOf[Float])
Transform2D(
c, -s, 0,
s, c, 0
)
}
}
case class Transform2D(m11: Float, m12: Float, m13: Float, m21: Float, m22: Float, m23: Float) {
def array: Array[Float] = Array(m11, m12, m13, m21, m22, m23)
// :|
def >>(that: Transform2D): Transform2D = Transform2D(
m11 * that.m11 + m12 * that.m21, m11 * that.m12 + m12 * that.m22, m11 * that.m13 + m12 * that.m23 + m13,
m21 * that.m11 + m22 * that.m21, m21 * that.m12 + m22 * that.m22, m21 * that.m13 + m22 * that.m23 + m23,
)
// (__)
def <<(that: Transform2D): Transform2D = Transform2D(
m11 * that.m11 + m21 * that.m12, m12 * that.m11 + m22 * that.m12, m13 * that.m11 + m23 * that.m12 + that.m13,
m11 * that.m21 + m21 * that.m22, m12 * that.m21 + m22 * that.m22, m13 * that.m21 + m23 * that.m22 + that.m23
)
def *(that: Vector2D): Vector2D = Vector2D(
m11 * that.x + m12 * that.y + m13,
m21 * that.x + m22 * that.y + m23
)
override def toString: String =
f"""Matrix2D [$m11%6.3f $m12%6.3f $m13%6.3f]
| [$m21%6.3f $m22%6.3f $m23%6.3f]
""".stripMargin
}

View File

@ -0,0 +1,70 @@
package ocelot.desktop.geometry
object Vector2D {
def apply(x: Double, y: Double): Vector2D = Vector2D(x.asInstanceOf[Float], y.asInstanceOf[Float])
}
case class Vector2D(var x: Float, var y: Float) {
def +(that: Vector2D): Vector2D = Vector2D(x + that.x, y + that.y)
def +=(that: Vector2D): Unit = {
x += that.x
y += that.y
}
def -(that: Vector2D): Vector2D = Vector2D(x - that.x, y - that.y)
def -=(that: Vector2D): Unit = {
x -= that.x
y -= that.y
}
def *(scalar: Float): Vector2D = Vector2D(x * scalar, y * scalar)
def *=(scalar: Float): Unit = {
x *= scalar
y *= scalar
}
def /(scalar: Float): Vector2D = Vector2D(x / scalar, y / scalar)
def /=(scalar: Float): Unit = {
x /= scalar
y /= scalar
}
def unary_-(): Vector2D = Vector2D(-x, -y)
def *(that: Vector2D): Float = x * that.x + y * that.y
def length: Float = math.sqrt(lengthSquared).asInstanceOf
def lengthSquared: Float = this.x * this.x + this.y * this.y
def normalized: Vector2D = {
val l = this.length
if (l > 0) this / this.length else this
}
def normalize(): Unit = {
val l = this.length
if (l > 0) this /= this.length
}
def angle: Float = math.atan2(this.y, this.x).asInstanceOf
def angle(that: Vector2D): Float = angle - that.angle
def rotated(angle: Float): Vector2D = {
val (s, c) = (math.sin(angle), math.cos(angle))
Vector2D(c * x - s * y, s * x + c * y)
}
def rotate(angle: Float): Unit = {
val (s, c) = (math.sin(angle), math.cos(angle))
x = (c * x - s * y).asInstanceOf
y = (s * x + c * y).asInstanceOf
}
override def toString: String = s"Vector2D [$x, $y]"
}

View File

@ -0,0 +1,134 @@
package ocelot.desktop.graphics
import ocelot.desktop.color.{Color, RGBAColorNorm}
import ocelot.desktop.geometry.Transform2D
import ocelot.desktop.graphics.mesh.{Mesh, MeshInstance}
import ocelot.desktop.graphics.render.InstanceRenderer
import ocelot.desktop.util.FontLoader
import org.apache.logging.log4j.scala.Logging
import org.lwjgl.opengl.GL11
class Graphics extends Logging {
private var projection = Transform2D.viewport(800, 600)
private var z = 0f
private val generalShaderProgram = new ShaderProgram("general")
private val rectRenderer = new InstanceRenderer(Mesh.quad)
private val textShaderProgram = new ShaderProgram("text")
private val textRenderer = new InstanceRenderer(Mesh.quad)
private val fontAtlas = FontLoader.makeTexture()
private var _foreground = RGBAColorNorm(0f, 0f, 0f, 1f)
private var _background = RGBAColorNorm(0f, 0f, 0f, 1f)
private var _fontSize: Float = 16f
GL11.glEnable(GL11.GL_DEPTH_TEST)
GL11.glDepthFunc(GL11.GL_LEQUAL)
GL11.glEnable(GL11.GL_BLEND)
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA)
def setViewport(width: Int, height: Int): Unit = {
projection = Transform2D.viewport(width, height)
generalShaderProgram.set("uProj", projection)
textShaderProgram.set("uProj", projection)
}
def clear(): Unit = {
GL11.glClearColor(0.95f, 0.95f, 0.95f, 1.0f)
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT)
}
def setScissor(x: Int, y: Int, width: Int, height: Int): Unit = {
GL11.glScissor(x, y, width, height)
GL11.glEnable(GL11.GL_SCISSOR_TEST)
}
def setScissor(): Unit = {
GL11.glDisable(GL11.GL_SCISSOR_TEST)
}
def clear(x: Int, y: Int, width: Int, height: Int): Unit = {
setScissor(x, y, width, height)
clear()
setScissor()
}
def foreground: RGBAColorNorm = _foreground
def foreground_=(col: Color): Unit = {
_foreground = col.toRGBANorm
}
def background: RGBAColorNorm = _background
def background_=(col: Color): Unit = {
_background = col.toRGBANorm
}
def fontSize: Float = _fontSize
def fontSize_=(value: Float): Unit = {
_fontSize = value
}
def text(x: Float, y: Float, text: String): Unit = {
var ox = x
for (c <- text) {
char(ox, y, c)
if (FontLoader.isCharWide(c))
ox += fontSize
else
ox += fontSize / 2f
}
}
def char(x: Float, y: Float, c: Char): Unit = {
val rect = FontLoader.map.getOrElse(c, FontLoader.map('?'))
val width = if (FontLoader.isCharWide(c)) fontSize else fontSize / 2f
val height = fontSize
val uvTransform = Transform2D.translate(
rect.x,
rect.y
) >> Transform2D.scale(
rect.w,
rect.h - 0.3f / FontLoader.AtlasHeight.asInstanceOf[Float]
// ^ dirty hack to avoid edge bleeding, somehow works
)
rectRenderer.schedule(MeshInstance(_background, z, Transform2D.translate(x, y) >> Transform2D.scale(width, height)))
textRenderer.schedule(MeshInstance(_foreground, z + 1, Transform2D.translate(x, y) >> Transform2D.scale(width, height), uvTransform))
z += 2
}
def rect(x: Float, y: Float, width: Float, height: Float): Unit = {
rectRenderer.schedule(MeshInstance(_background, z, Transform2D.translate(x, y) >> Transform2D.scale(width, height)))
z += 1
}
def flush(): Unit = {
generalShaderProgram.bind()
rectRenderer.flush()
textShaderProgram.bind()
fontAtlas.bind()
textRenderer.flush()
}
def commit(): Unit = {
flush()
rectRenderer.commit()
textRenderer.commit()
z = 0
setScissor()
}
}

View File

@ -0,0 +1,81 @@
package ocelot.desktop.graphics
import ocelot.desktop.geometry.Transform2D
import ocelot.desktop.util.{Resource, ResourceManager}
import org.apache.logging.log4j.scala.Logging
import org.lwjgl.opengl.GL20
import scala.io.Source
class ShaderProgram(name: String) extends Logging with Resource {
logger.info(s"Loading shader program ($name)")
val fragmentShader: Int = createShader(Source.fromResource(s"ocelot/desktop/shader/$name.frag", getClass.getClassLoader), GL20.GL_FRAGMENT_SHADER, "fragment")
val vertexShader: Int = createShader(Source.fromResource(s"ocelot/desktop/shader/$name.vert", getClass.getClassLoader), GL20.GL_VERTEX_SHADER, "vertex")
val shaderProgram: Int = GL20.glCreateProgram()
GL20.glAttachShader(shaderProgram, vertexShader)
GL20.glAttachShader(shaderProgram, fragmentShader)
GL20.glLinkProgram(shaderProgram)
{
val success = Array(0)
GL20.glGetProgramiv(shaderProgram, GL20.GL_LINK_STATUS, success)
if (success.head == 0) {
val info = GL20.glGetProgramInfoLog(shaderProgram)
logger.error(s"Failed to link shader program ($name)\n$info")
throw new RuntimeException("Shader linkage failed")
}
}
ResourceManager.registerResource(this)
def freeResource(): Unit = {
logger.debug(s"Destroyed shader program ($name)")
GL20.glDeleteProgram(shaderProgram)
GL20.glDeleteShader(vertexShader)
GL20.glDeleteShader(fragmentShader)
}
def bind(): Unit = {
GL20.glUseProgram(shaderProgram)
}
def set(name: String, transform: Transform2D): Unit = {
bind()
val array = transform.array ++ Array(0f, 0f, 1f)
GL20.glUniformMatrix3fv(getLocation(name), true, array)
}
def set(name: String, v: Int): Unit = {
bind()
GL20.glUniform1i(getLocation(name), v)
}
private def getLocation(name: String): Int = {
GL20.glGetUniformLocation(shaderProgram, name)
}
private def createShader(source: Source, ty: Int, s: String): Int = {
val shader = GL20.glCreateShader(ty)
GL20.glShaderSource(shader, source.mkString)
GL20.glCompileShader(shader)
val success = Array(0)
GL20.glGetShaderiv(shader, GL20.GL_COMPILE_STATUS, success)
if (success.head == 0) {
val info = GL20.glGetShaderInfoLog(shader)
logger.error(s"Failed to compile shader ($s)\n$info")
throw new RuntimeException("Shader compilation failed")
}
source.close()
shader
}
}

View File

@ -0,0 +1,51 @@
package ocelot.desktop.graphics
import java.nio.ByteBuffer
import ocelot.desktop.util.{Resource, ResourceManager}
import org.apache.logging.log4j.scala.Logging
import org.lwjgl.opengl._
import org.lwjgl.system.MemoryUtil.NULL
class Texture extends Logging with Resource {
val texture: Int = GL11.glGenTextures()
ResourceManager.registerResource(this)
def freeResource(): Unit = {
logger.debug(s"Destroyed texture (ID: $texture)")
GL11.glDeleteTextures(texture)
}
bind()
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST)
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST)
def this(width: Int, height: Int, format: Int, dataType: Int) = {
this()
bind()
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, format, width, height, 0, format, dataType, NULL)
}
def set(width: Int, height: Int, format: Int, dataType: Int, buf: ByteBuffer): Unit = {
bind()
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, format, width, height, 0, format, dataType, buf)
GL30.glGenerateMipmap(GL11.GL_TEXTURE_2D)
}
def this(width: Int, height: Int, format: Int, dataType: Int, buf: ByteBuffer) = {
this()
set(width, height, format, dataType, buf)
}
def write(x: Int, y: Int, w: Int, h: Int, format: Int, dataType: Int, buf: ByteBuffer): Unit = {
bind()
GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, x, y, w, h, format, dataType, buf)
GL30.glGenerateMipmap(GL11.GL_TEXTURE_2D)
}
def bind(unit: Int = 0): Unit = {
GL13.glActiveTexture(GL13.GL_TEXTURE0 + unit)
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture)
}
}

View File

@ -0,0 +1,74 @@
package ocelot.desktop.graphics.buffer
import java.nio.ByteBuffer
import ocelot.desktop.util.Resource
import org.apache.logging.log4j.scala.Logging
import org.lwjgl.BufferUtils
import org.lwjgl.opengl.GL15
import org.lwjgl.system.MemoryUtil.NULL
class Buffer[T <: BufferPut] extends Logging with Resource {
val target: Int = GL15.GL_ARRAY_BUFFER
var buffer: Int = GL15.glGenBuffers()
var capacity: Int = _
var stride: Int = _
def freeResource(): Unit = {
logger.debug(s"Destroyed buffer (ID: $buffer) of ${capacity * stride} bytes")
GL15.glDeleteBuffers(buffer)
}
def this(elements: Seq[T]) = {
this()
createStatic(elements)
}
def write(offset: Int, buf: ByteBuffer): Unit = {
bind()
GL15.glBufferSubData(target, offset * stride, buf)
}
def resize(newCap: Int): Unit = {
createDynamic(this.stride, newCap)
}
protected def createDynamic(stride: Int, cap: Int): Unit = {
bind()
GL15.nglBufferData(target, stride * cap, NULL, GL15.GL_DYNAMIC_DRAW)
this.stride = stride
capacity = cap
}
protected def createStatic(elements: Seq[T]): Unit = {
assert(elements.nonEmpty, "Zero size immutable buffer")
extractMeta(elements.head)
bind()
val buf = storeElements(elements)
GL15.glBufferData(target, buf, GL15.GL_STATIC_DRAW)
capacity = elements.length
}
protected def storeElements(elements: Seq[T]): ByteBuffer = {
val data = BufferUtils.createByteBuffer(elements.length * elements.head.stride)
for (element <- elements) {
element.put(data)
}
data.flip
data
}
protected def extractMeta(head: T): Unit = {
stride = head.stride
}
def bind(): Unit = {
GL15.glBindBuffer(target, buffer)
}
}

View File

@ -0,0 +1,9 @@
package ocelot.desktop.graphics.buffer
import java.nio.ByteBuffer
trait BufferPut {
def stride: Int
def put(buf: ByteBuffer): Unit
}

View File

@ -0,0 +1,11 @@
package ocelot.desktop.graphics.buffer
import java.nio.ByteBuffer
case class Index(i: Int) extends BufferPut {
override def stride: Int = 4
override def put(buf: ByteBuffer): Unit = {
buf.putInt(i)
}
}

View File

@ -0,0 +1,12 @@
package ocelot.desktop.graphics.buffer
import org.lwjgl.opengl.GL15
class IndexBuffer extends Buffer[Index] {
override val target: Int = GL15.GL_ELEMENT_ARRAY_BUFFER
def this(elements: Seq[Index]) = {
this()
createStatic(elements)
}
}

View File

@ -0,0 +1,24 @@
package ocelot.desktop.graphics.buffer
import ocelot.desktop.graphics.mesh.{Vertex, VertexType}
class VertexBuffer[V <: Vertex] extends Buffer[V] {
var ty: VertexType = _
def this(elements: Seq[V]) = {
this()
createStatic(elements)
}
def this(vertexType: VertexType, cap: Int) = {
this()
ty = vertexType
createDynamic(ty.stride, cap)
}
override protected def extractMeta(head: V): Unit = {
ty = head.vertexType
super.extractMeta(head)
}
}

View File

@ -0,0 +1,13 @@
package ocelot.desktop.graphics.mesh
import ocelot.desktop.graphics.buffer.Index
import ocelot.desktop.geometry.Vector2D
object Mesh {
val quad: Mesh = Mesh(
Array(Vector2D(0f, 0f), Vector2D(1f, 0f), Vector2D(1f, 1f), Vector2D(0f, 1f)).map(v => MeshVertex(v, v)),
Some(Array(0, 1, 2, 2, 3, 0).map(i => Index(i)))
)
}
case class Mesh(vertices: Seq[MeshVertex], indices: Option[Seq[Index]] = None)

View File

@ -0,0 +1,33 @@
package ocelot.desktop.graphics.mesh
import java.nio.ByteBuffer
import ocelot.desktop.color.Color
import ocelot.desktop.geometry.Transform2D
import org.lwjgl.opengl.GL11
object MeshInstance extends VertexType {
override val stride: Int = 68
override val attributes: Seq[Attribute] = Array(
Attribute(4, GL11.GL_FLOAT, normalized = false, stride, 0),
Attribute(3, GL11.GL_FLOAT, normalized = false, stride, 16),
Attribute(3, GL11.GL_FLOAT, normalized = false, stride, 28),
Attribute(3, GL11.GL_FLOAT, normalized = false, stride, 40),
Attribute(3, GL11.GL_FLOAT, normalized = false, stride, 52),
Attribute(1, GL11.GL_FLOAT, normalized = false, stride, 64),
)
}
case class MeshInstance(color: Color, z: Float, transform: Transform2D, uvTransform: Transform2D = Transform2D.identity) extends Vertex {
override def stride: Int = MeshInstance.stride
override def vertexType: VertexType = MeshInstance
override def put(buf: ByteBuffer): Unit = {
color.toRGBANorm.array.foreach(f => buf.putFloat(f))
transform.array.foreach(f => buf.putFloat(f))
uvTransform.array.foreach(f => buf.putFloat(f))
buf.putFloat(z)
}
}

View File

@ -0,0 +1,28 @@
package ocelot.desktop.graphics.mesh
import java.nio.ByteBuffer
import ocelot.desktop.geometry.Vector2D
import org.lwjgl.opengl.GL11
object MeshVertex extends VertexType {
override val stride: Int = 16
override val attributes: Seq[Attribute] = Array(
Attribute(2, GL11.GL_FLOAT, normalized = false, stride, 0),
Attribute(2, GL11.GL_FLOAT, normalized = false, stride, 8)
)
}
case class MeshVertex(pos: Vector2D, uv: Vector2D) extends Vertex {
override def stride: Int = MeshVertex.stride
override def vertexType: VertexType = MeshVertex
override def put(buf: ByteBuffer): Unit = {
buf.putFloat(pos.x)
buf.putFloat(pos.y)
buf.putFloat(uv.x)
buf.putFloat(uv.y)
}
}

View File

@ -0,0 +1,7 @@
package ocelot.desktop.graphics.mesh
import ocelot.desktop.graphics.buffer.BufferPut
trait Vertex extends BufferPut {
def vertexType: VertexType
}

View File

@ -0,0 +1,40 @@
package ocelot.desktop.graphics.mesh
import ocelot.desktop.graphics.buffer.{IndexBuffer, VertexBuffer}
import ocelot.desktop.util.{Resource, ResourceManager}
import org.apache.logging.log4j.scala.Logging
import org.lwjgl.opengl.{GL20, GL30, GL33}
class VertexArray extends Logging with Resource {
val array: Int = GL30.glGenVertexArrays()
var location = 0
ResourceManager.registerResource(this)
def freeResource(): Unit = {
logger.debug(s"Destroyed VAO (ID: $array)")
GL30.glDeleteVertexArrays(array)
}
def addVertexBuffer[V <: Vertex](vbuf: VertexBuffer[V], instanced: Boolean = false): Unit = {
bind()
for (attr <- vbuf.ty.attributes) {
GL20.glEnableVertexAttribArray(location)
vbuf.bind()
GL20.glVertexAttribPointer(location, attr.size, attr.ty, attr.normalized, attr.stride, attr.pointer)
if (instanced) GL33.glVertexAttribDivisor(location, 1)
location += 1
}
}
def addIndexBuffer(ibuf: IndexBuffer): Unit = {
bind()
ibuf.bind()
}
def bind(): Unit = GL30.glBindVertexArray(array)
}

View File

@ -0,0 +1,8 @@
package ocelot.desktop.graphics.mesh
trait VertexType {
val stride: Int
val attributes: Seq[Attribute]
}
case class Attribute(size: Int, ty: Int, normalized: Boolean, stride: Int, pointer: Long)

View File

@ -0,0 +1,101 @@
package ocelot.desktop.graphics.render
import java.nio.ByteBuffer
import ocelot.desktop.graphics.buffer.{IndexBuffer, VertexBuffer}
import ocelot.desktop.graphics.mesh.{Mesh, MeshInstance, MeshVertex, VertexArray}
import org.apache.logging.log4j.scala.Logging
import org.lwjgl.BufferUtils
import org.lwjgl.opengl._
import scala.collection.mutable.ArrayBuffer
class InstanceRenderer(mesh: Mesh) extends Logging {
val InitialCapacity: Int = 4096
val vertexBuffer = new VertexBuffer[MeshVertex](mesh.vertices)
val indexBuffer: Option[IndexBuffer] = mesh.indices.map(indices => new IndexBuffer(indices))
val instanceBuffer = new VertexBuffer[MeshInstance](MeshInstance, InitialCapacity)
val vertexArray = new VertexArray
vertexArray.addVertexBuffer(vertexBuffer)
indexBuffer.foreach(ib => vertexArray.addIndexBuffer(ib))
vertexArray.addVertexBuffer(instanceBuffer, instanced = true)
private val instances = new ArrayBuffer[MeshInstance]
private var position = 0
private var writeStart = 0
private var writeLen = 0
private var drawStart = 0
private var drawLen = 0
def schedule(instance: MeshInstance): Unit = {
if (writeLen > 0 || instances.length <= position || instances(position) != instance) {
if (writeLen == 0) writeStart = position
writeLen += 1
if (instances.length <= position) {
instances += instance
} else {
instances(position) = instance
}
}
position += 1
drawLen += 1
}
def flush(): Unit = {
write()
draw()
}
def commit(): Unit = {
flush()
position = 0
writeStart = 0
writeLen = 0
drawStart = 0
drawLen = 0
}
private def draw(): Unit = {
if (drawLen == 0) return
vertexArray.bind()
indexBuffer match {
case Some(ib) => ARBBaseInstance.glDrawElementsInstancedBaseInstance(GL11.GL_TRIANGLES, ib.capacity, GL11.GL_UNSIGNED_INT, 0, drawLen, drawStart)
case None => ARBBaseInstance.glDrawArraysInstancedBaseInstance(GL11.GL_TRIANGLES, 0, vertexBuffer.capacity, drawLen, drawStart)
}
drawStart += drawLen
drawLen = 0
}
private def write(): Unit = {
if (instances.length > instanceBuffer.capacity) {
instanceBuffer.resize(instances.length * 2)
instanceBuffer.write(0, packInstances(0, instances.length))
} else if (writeLen > 0) {
instanceBuffer.write(writeStart, packInstances(writeStart, writeLen))
writeLen = 0
}
}
private def packInstances(start: Int, len: Int): ByteBuffer = {
val data = BufferUtils.createByteBuffer(len * MeshInstance.stride)
for (element <- instances.slice(start, start + len)) {
element.put(data)
}
data.flip
data
}
}

View File

@ -0,0 +1,22 @@
package ocelot.desktop.graphics.render
import ocelot.desktop.color.RGBAColorNorm
import ocelot.desktop.graphics.ShaderProgram
import ocelot.desktop.graphics.mesh.Mesh
class TextRenderer {
private val shaderProgram = new ShaderProgram("text")
private val renderer = new InstanceRenderer(Mesh.quad)
var bg: RGBAColorNorm = RGBAColorNorm(0f, 0f, 0f, 0f)
var fg: RGBAColorNorm = RGBAColorNorm(0f, 0f, 0f, 1f)
var scale: Float = 16
def scheduleChar(x: Float, y: Float, char: Char): Unit = {
// renderer.schedule(MeshInstance(RGBAColorNorm(1.0f, 0.0f, 0.0f, 1.0f), Transform2D.translate(x * 10 + 1, y * 20 + 1) >> Transform2D.scale(8f, 18f)))
}
def flush(): Unit = renderer.flush()
def commit(): Unit = renderer.commit()
}

View File

@ -0,0 +1,130 @@
package ocelot.desktop.util
import java.awt.Color
import java.awt.image.{BufferedImage, DataBufferByte, IndexColorModel}
import java.io.InputStream
import java.nio.ByteBuffer
import java.util
import ocelot.desktop.graphics.Texture
import org.apache.logging.log4j.scala.Logging
import org.lwjgl.opengl.GL11
import scala.collection.mutable
import scala.io.{Codec, Source}
object FontLoader extends Logging {
val GlyphHeight = 16
val AtlasWidth = 1536
val AtlasHeight = 1536
var glyphCount = 0
var currentColor = 0xFFFFFF
var atlas: BufferedImage = {
val icmArr = Array(0.toByte, 0xff.toByte)
val icm = new IndexColorModel(1, 2, icmArr, icmArr, icmArr, icmArr)
new BufferedImage(AtlasWidth, AtlasHeight, BufferedImage.TYPE_BYTE_BINARY, icm)
}
var map = new mutable.HashMap[Char, Rect]
def isCharWide(char: Char): Boolean = map.get(char).exists(rect => rect.w == rect.h)
def setColor(rgb: Int): Unit = {
if (rgb == currentColor) return
currentColor = rgb
val c = new Color(rgb)
val icm = new IndexColorModel(1, 2,
Array(0.toByte, c.getRed.toByte),
Array(0.toByte, c.getGreen.toByte),
Array(0.toByte, c.getBlue.toByte),
Array(0.toByte, 0xff.toByte))
atlas = new BufferedImage(icm, atlas.getRaster, false, new util.Hashtable())
}
def init(): Unit = {
logger.info("Loading font.hex")
var ox = 0
var oy = 0
val resource = getClass.getResource("/ocelot/desktop/font.hex")
val source = Source.fromInputStream(resource.getContent.asInstanceOf[InputStream])(Codec.UTF8)
for (line <- source.getLines()) {
val colon = line.indexOf(':')
val char = Integer.parseInt(line.substring(0, colon), 16).toChar
val width = (line.length - colon - 1) / 4
if (ox + width > AtlasWidth) {
ox = 0
oy += GlyphHeight
}
map(char) = Rect(
ox.asInstanceOf[Float] / AtlasWidth.asInstanceOf[Float],
oy.asInstanceOf[Float] / AtlasHeight.asInstanceOf[Float],
width.asInstanceOf[Float] / AtlasWidth.asInstanceOf[Float],
GlyphHeight.asInstanceOf[Float] / AtlasHeight.asInstanceOf[Float]
)
var x = 0
var y = 0
for (char <- line.substring(colon + 1)) {
for (i <- 3 to 0 by -1) {
val bit = (Character.digit(char, 16) >> i) & 1
atlas.setRGB(ox + x, oy + y, bit match {
case 0 => 0x00000000
case 1 => 0xFFFFFFFF
})
x += 1
if (x == width) {
x = 0
y += 1
}
}
}
ox += width
glyphCount += 1
}
val size = atlas.getRaster.getDataBuffer.getSize
logger.info(s"Packed $glyphCount glyphs into ${AtlasWidth}x$AtlasHeight 1-bit texture ($size bytes)")
}
def makeTexture(): Texture = {
val bytes = atlas.getRaster.getDataBuffer.asInstanceOf[DataBufferByte]
val buf = ByteBuffer.allocateDirect(AtlasWidth * AtlasHeight * 1)
for (b <- bytes.getData) { // :/
buf.put(((b >> 7 & 1) * 255).asInstanceOf[Byte])
buf.put(((b >> 6 & 1) * 255).asInstanceOf[Byte])
buf.put(((b >> 5 & 1) * 255).asInstanceOf[Byte])
buf.put(((b >> 4 & 1) * 255).asInstanceOf[Byte])
buf.put(((b >> 3 & 1) * 255).asInstanceOf[Byte])
buf.put(((b >> 2 & 1) * 255).asInstanceOf[Byte])
buf.put(((b >> 1 & 1) * 255).asInstanceOf[Byte])
buf.put(((b >> 0 & 1) * 255).asInstanceOf[Byte])
}
buf.flip()
val tex = new Texture()
tex.bind()
GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1)
tex.set(AtlasWidth, AtlasHeight, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, buf)
tex
}
}
case class Rect(x: Float, y: Float, w: Float, h: Float)

View File

@ -0,0 +1,93 @@
package ocelot.desktop.util
import org.lwjgl.glfw.GLFW
object KeyMapping {
def map(code: Int): Int = {
code match {
case GLFW.GLFW_KEY_ESCAPE => 1
case x if GLFW.GLFW_KEY_1 <= x && x <= GLFW.GLFW_KEY_9 => x - GLFW.GLFW_KEY_1 + 2
case GLFW.GLFW_KEY_0 => 11
case GLFW.GLFW_KEY_MINUS => 12
case GLFW.GLFW_KEY_EQUAL => 13
case GLFW.GLFW_KEY_BACKSPACE => 14
case GLFW.GLFW_KEY_TAB => 15
case GLFW.GLFW_KEY_Q => 16
case GLFW.GLFW_KEY_W => 17
case GLFW.GLFW_KEY_E => 18
case GLFW.GLFW_KEY_R => 19
case GLFW.GLFW_KEY_T => 20
case GLFW.GLFW_KEY_Y => 21
case GLFW.GLFW_KEY_U => 22
case GLFW.GLFW_KEY_I => 23
case GLFW.GLFW_KEY_O => 24
case GLFW.GLFW_KEY_P => 25
case GLFW.GLFW_KEY_LEFT_BRACKET => 26
case GLFW.GLFW_KEY_RIGHT_BRACKET => 27
case GLFW.GLFW_KEY_ENTER => 28
case GLFW.GLFW_KEY_LEFT_CONTROL => 29
case GLFW.GLFW_KEY_A => 30
case GLFW.GLFW_KEY_S => 31
case GLFW.GLFW_KEY_D => 32
case GLFW.GLFW_KEY_F => 33
case GLFW.GLFW_KEY_G => 34
case GLFW.GLFW_KEY_H => 35
case GLFW.GLFW_KEY_J => 36
case GLFW.GLFW_KEY_K => 37
case GLFW.GLFW_KEY_L => 38
case GLFW.GLFW_KEY_SEMICOLON => 39
case GLFW.GLFW_KEY_Q => 40
// case GLFW.GLFW_KEY_BACK_QUOTE => 41
// case GLFW.GLFW_KEY_SHIFT => 42
// case GLFW.GLFW_KEY_BACK_SLASH => 43
case GLFW.GLFW_KEY_Z => 44
case GLFW.GLFW_KEY_X => 45
case GLFW.GLFW_KEY_C => 46
case GLFW.GLFW_KEY_V => 47
case GLFW.GLFW_KEY_B => 48
case GLFW.GLFW_KEY_N => 49
case GLFW.GLFW_KEY_M => 50
case GLFW.GLFW_KEY_COMMA => 51
case GLFW.GLFW_KEY_PERIOD => 52
case GLFW.GLFW_KEY_SLASH => 53
// case GLFW.GLFW_KEY_MULTIPLY => 55
// case GLFW.GLFW_KEY_ALT => 56
case GLFW.GLFW_KEY_SPACE => 57
case GLFW.GLFW_KEY_CAPS_LOCK => 58
case x if GLFW.GLFW_KEY_F1 <= x && x <= GLFW.GLFW_KEY_F10 => x - GLFW.GLFW_KEY_F1 + 59
case GLFW.GLFW_KEY_NUM_LOCK => 69
case GLFW.GLFW_KEY_SCROLL_LOCK => 70
// case x if GLFW.GLFW_KEY_NUMPAD7 <= x && x <= GLFW.GLFW_KEY_NUMPAD9 => x - GLFW.GLFW_KEY_NUMPAD7 + 71
// case GLFW.GLFW_KEY_SUBTRACT => 74
// case x if GLFW.GLFW_KEY_NUMPAD4 <= x && x <= GLFW.GLFW_KEY_NUMPAD6 => x - GLFW.GLFW_KEY_NUMPAD4 + 75
// case GLFW.GLFW_KEY_ADD => 78
// case x if GLFW.GLFW_KEY_NUMPAD1 <= x && x <= GLFW.GLFW_KEY_NUMPAD3 => x - GLFW.GLFW_KEY_NUMPAD1 + 79
// case GLFW.GLFW_KEY_NUMPAD0 => 82
// case GLFW.GLFW_KEY_DECIMAL => 83
// case x if GLFW.GLFW_KEY_F11 <= x && x <= GLFW.GLFW_KEY_F18 => x - GLFW.GLFW_KEY_F11 + 87
// case GLFW.GLFW_KEY_KANA => 112
// case GLFW.GLFW_KEY_CONVERT => 121
// case GLFW.GLFW_KEY_NONCONVERT => 123
// case GLFW.GLFW_KEY_CIRCUMFLEX => 144
// case GLFW.GLFW_KEY_AT => 145
// case GLFW.GLFW_KEY_COLON => 146
// case GLFW.GLFW_KEY_UNDERSCORE => 147
// case GLFW.GLFW_KEY_KANJI => 148
// case GLFW.GLFW_KEY_STOP => 149
// case GLFW.GLFW_KEY_DIVIDE => 181
case GLFW.GLFW_KEY_PAUSE => 197
case GLFW.GLFW_KEY_HOME => 199
case GLFW.GLFW_KEY_UP => 200
case GLFW.GLFW_KEY_PAGE_UP => 201
case GLFW.GLFW_KEY_LEFT => 203
case GLFW.GLFW_KEY_RIGHT => 205
case GLFW.GLFW_KEY_END => 207
case GLFW.GLFW_KEY_DOWN => 208
case GLFW.GLFW_KEY_PAGE_DOWN => 209
case GLFW.GLFW_KEY_INSERT => 210
case GLFW.GLFW_KEY_DELETE => 211
// case GLFW.GLFW_KEY_META => 219
case _ => 0
}
}
}

View File

@ -0,0 +1,5 @@
package ocelot.desktop.util
trait Resource {
def freeResource(): Unit
}

View File

@ -0,0 +1,23 @@
package ocelot.desktop.util
import scala.collection.mutable.ArrayBuffer
object ResourceManager {
private val resources = new ArrayBuffer[Resource]
def registerResource(resource: Resource): Unit = {
resources += resource
}
def freeResource(resource: Resource): Unit = {
resource.freeResource()
resources -= resource
}
def freeResources(): Unit = {
for (resource <- resources)
resource.freeResource()
resources.clear()
}
}

View File

@ -0,0 +1,90 @@
package ocelot.desktop.widget
import ocelot.desktop.color.IntColor
import ocelot.desktop.graphics.Graphics
import ocelot.desktop.util.FontLoader
import org.apache.logging.log4j.scala.Logging
class ScreenWidget extends Logging {
private val fontSize = 16f
val width = 80
val height = 25
var background: Int = 0x000000
var foreground: Int = 0xFFFFFF
private val data: Array[Cell] = Array.fill(width * height)(Cell(' ', background, foreground))
def draw(g: Graphics): Unit = {
g.clear(0, 0, math.round(fontSize * width / 2f), math.round(fontSize * height))
g.fontSize = fontSize
for (y <- 0 until height) {
for (x <- 0 until width) {
val Cell(char, bg, fg) = data(y * width + x)
g.background = IntColor(bg)
g.foreground = IntColor(fg)
g.char(x * fontSize / 2f, y * fontSize, char)
}
}
}
def set(x: Int, y: Int, text: String, vertical: Boolean): Unit = {
var ox = x
var oy = y
for (char <- text) {
setChar(ox, oy, char)
if (vertical)
oy += 1
else
ox += 1
}
}
def fill(x: Int, y: Int, w: Int, h: Int, c: Char): Unit = {
for (sx <- x until x + w)
for (sy <- y until y + h)
setChar(sx, sy, c)
}
def copy(x: Int, y: Int, w: Int, h: Int, tx: Int, ty: Int): Unit = {
val dataClone = data.clone
for (sx <- x until x + w) {
for (sy <- y until y + h) {
val nx = sx + tx
val ny = sy + ty
if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
val src = dataClone(sy * width + sx)
val dst = data((sy + ty) * width + sx + tx)
dst.char = src.char
dst.bg = src.bg
dst.fg = src.fg
}
}
}
}
def setChar(x: Int, y: Int, c: Char): Unit = {
val isWide = FontLoader.isCharWide(c)
if (isWide && x == width - 1)
return
if (isWide)
setChar(x + 1, y, ' ')
val cell = data(y * width + x)
cell.char = c
cell.bg = background
cell.fg = foreground
}
case class Cell(var char: Char, var bg: Int, var fg: Int)
}