From 3d10f9338a9b326742ad9ff362df7a96055fce60 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Wed, 2 Nov 2022 22:03:47 +0900 Subject: [PATCH] reading config from config.json --- .../src/net/torvald/tsvm/VMGUI.kt | 14 +- .../src/net/torvald/terrarum/KVHashMap.kt | 102 +++++++ .../torvald/terrarum/serialise/WriteConfig.kt | 69 +++++ .../src/net/torvald/tsvm/DefaultConfig.kt | 15 + .../src/net/torvald/tsvm/ProfilesMenu.kt | 62 +++- .../src/net/torvald/tsvm/TsvmEmulator.java | 282 +++++++++++++++++- .../src/net/torvald/tsvm/VMEmuExecutable.kt | 14 +- 7 files changed, 535 insertions(+), 23 deletions(-) create mode 100644 tsvm_executable/src/net/torvald/terrarum/KVHashMap.kt create mode 100644 tsvm_executable/src/net/torvald/terrarum/serialise/WriteConfig.kt create mode 100644 tsvm_executable/src/net/torvald/tsvm/DefaultConfig.kt diff --git a/TerranBASICexecutable/src/net/torvald/tsvm/VMGUI.kt b/TerranBASICexecutable/src/net/torvald/tsvm/VMGUI.kt index 5018753..e548465 100644 --- a/TerranBASICexecutable/src/net/torvald/tsvm/VMGUI.kt +++ b/TerranBASICexecutable/src/net/torvald/tsvm/VMGUI.kt @@ -71,13 +71,13 @@ class VMGUI(val loaderInfo: EmulInstance, val viewportWidth: Int, val viewportHe val loadedClassInstance = loadedClassConstructor.newInstance("./assets", vm) gpu = (loadedClassInstance as GraphicsAdapter) - vm.getIO().blockTransferPorts[0].attachDevice(TestDiskDrive(vm, 0, File(loaderInfo.diskPath))) + vm.getIO().blockTransferPorts[0].attachDevice(TestDiskDrive(vm, 0, loaderInfo.diskPath)) vm.peripheralTable[1] = PeripheralEntry( gpu, - GraphicsAdapter.VRAM_SIZE, - 16, - 0 +// GraphicsAdapter.VRAM_SIZE, +// 16, +// 0 ) vm.getPrintStream = { gpu!!.getPrintStream() } @@ -99,9 +99,9 @@ class VMGUI(val loaderInfo: EmulInstance, val viewportWidth: Int, val viewportHe vm.peripheralTable[port] = PeripheralEntry( loadedClassInstance as PeriBase, - peri.memsize, - peri.mmioSize, - peri.interruptCount +// peri.memsize, +// peri.mmioSize, +// peri.interruptCount ) } diff --git a/tsvm_executable/src/net/torvald/terrarum/KVHashMap.kt b/tsvm_executable/src/net/torvald/terrarum/KVHashMap.kt new file mode 100644 index 0000000..67d149c --- /dev/null +++ b/tsvm_executable/src/net/torvald/terrarum/KVHashMap.kt @@ -0,0 +1,102 @@ +package net.torvald.terrarum + +/** + * Created by minjaesong on 2015-12-30. + */ +open class KVHashMap { + + constructor() { + hashMap = HashMap() + } + + protected constructor(newMap: HashMap) { + hashMap = newMap + } + + var hashMap: HashMap + + /** + * Add key-value pair to the configuration table. + * If key does not exist on the table, new key will be generated. + * If key already exists, the value will be overwritten. + + * @param key case insensitive + * * + * @param value + */ + open operator fun set(key: String, value: Any) { + hashMap.put(key.toLowerCase(), value) + } + + /** + * Get value using key from configuration table. + + * @param key case insensitive + * * + * @return Object value + */ + operator fun get(key: String): Any? { + return hashMap[key.toLowerCase()] + } + + fun getAsInt(key: String): Int? { + val value = get(key) + + if (value == null) return null + + return value as Int + } + + fun getAsDouble(key: String): Double? { + val value = get(key) + + if (value == null) return null + + if (value is Int) + return value.toDouble() + + return value as Double + } + + fun getAsFloat(key: String): Float? { + val value = get(key) + + if (value == null) return null + + if (value is Float) return value as Float + + return getAsDouble(key)?.toFloat() + } + + fun getAsString(key: String): String? { + val value = get(key) + + if (value == null) return null + + return value as String + } + + fun getAsBoolean(key: String): Boolean? { + val value = get(key) + + if (value == null) return null + + return value as Boolean + } + + fun hasKey(key: String) = hashMap.containsKey(key) + + val keySet: Set + get() = hashMap.keys + + open fun remove(key: String) { + if (hashMap[key] != null) { + hashMap.remove(key, hashMap[key]!!) + } + } + + open fun clone(): KVHashMap { + val cloneOfMap = hashMap.clone() as HashMap + return KVHashMap(cloneOfMap) + } +} \ No newline at end of file diff --git a/tsvm_executable/src/net/torvald/terrarum/serialise/WriteConfig.kt b/tsvm_executable/src/net/torvald/terrarum/serialise/WriteConfig.kt new file mode 100644 index 0000000..76d7f40 --- /dev/null +++ b/tsvm_executable/src/net/torvald/terrarum/serialise/WriteConfig.kt @@ -0,0 +1,69 @@ +package net.torvald.terrarum.serialise + +import com.badlogic.gdx.utils.Json +import com.badlogic.gdx.utils.JsonValue +import com.badlogic.gdx.utils.JsonWriter +import net.torvald.terrarum.KVHashMap +import net.torvald.terrarum.utils.JsonFetcher +import net.torvald.tsvm.TsvmEmulator + +/** + * Created by minjaesong on 2021-09-19. + */ +object WriteConfig { + + private val jsoner = Json(JsonWriter.OutputType.json) + + init { + jsoner.ignoreUnknownFields = true + jsoner.setUsePrototypes(false) + jsoner.setIgnoreDeprecated(false) + + // KVHashMap + jsoner.setSerializer(KVHashMap::class.java, object : Json.Serializer { + override fun write(json: Json, obj: KVHashMap, knownType: Class<*>?) { + json.writeObjectStart() + obj.hashMap.toSortedMap().forEach { (k, v) -> + json.writeValue(k, v) + } + json.writeObjectEnd() + } + + override fun read(json: Json, jsonData: JsonValue, type: Class<*>?): KVHashMap { + val map = KVHashMap() + JsonFetcher.forEachSiblings(jsonData) { key, obj -> + map[key] = json.readValue(null, obj) + } + return map + } + }) + + } + + /*fun getJson(): String { + val sb = StringBuilder() + + App.gameConfig.hashMap.toSortedMap().forEach { (k, v) -> + sb.append("$k:") + + when (v) { + is DoubleArray -> { sb.append("[${v.joinToString(",")}]") } + is IntArray -> { sb.append("[${v.joinToString(",")}]") } + is Array<*> -> { sb.append("[${v.joinToString(",")}]") } + else -> { sb.append("$v") } + } + + sb.append("\n") + } + + return "{\n$sb}" + }*/ + + operator fun invoke() { + val writer = java.io.FileWriter(TsvmEmulator.configDir, false) + //writer.write(getJson()) + writer.write(jsoner.prettyPrint(TsvmEmulator.gameConfig)) + writer.close() + } + +} \ No newline at end of file diff --git a/tsvm_executable/src/net/torvald/tsvm/DefaultConfig.kt b/tsvm_executable/src/net/torvald/tsvm/DefaultConfig.kt new file mode 100644 index 0000000..bab2b5e --- /dev/null +++ b/tsvm_executable/src/net/torvald/tsvm/DefaultConfig.kt @@ -0,0 +1,15 @@ +package net.torvald.tsvm + +/** + * Created by minjaesong on 2022-11-02. + */ +object DefaultConfig { + + val hashMap = hashMapOf( + "viewport_width" to 640, + "viewport_height" to 480, + "viewports_rows" to 2, + "viewports_cols" to 3 + ) + +} \ No newline at end of file diff --git a/tsvm_executable/src/net/torvald/tsvm/ProfilesMenu.kt b/tsvm_executable/src/net/torvald/tsvm/ProfilesMenu.kt index f4d17e0..f8ccb8f 100644 --- a/tsvm_executable/src/net/torvald/tsvm/ProfilesMenu.kt +++ b/tsvm_executable/src/net/torvald/tsvm/ProfilesMenu.kt @@ -1,5 +1,7 @@ package net.torvald.tsvm +import com.badlogic.gdx.Gdx +import com.badlogic.gdx.Input.Buttons import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.g2d.SpriteBatch import net.torvald.tsvm.VMEmuExecutableWrapper.Companion.FONT @@ -32,12 +34,33 @@ class ProfilesMenu(parent: VMEmuExecutable, x: Int, y: Int, w: Int, h: Int) : Em } override fun update() { + + if (Gdx.input.isButtonPressed(Buttons.LEFT)) { + val mx = Gdx.input.x - x + val my = Gdx.input.y - y + + if (mx in 10 until 10+228) { + if (my in 11 until 11+446) { + val li = (my - 11) / (2*FONT.H) + + if (li < profileNames.size - profilesScroll) + selectedProfileIndex = li + profilesScroll + else + selectedProfileIndex = null + } + } + } + } override fun render(batch: SpriteBatch) { batch.inUse { - batch.color = EmulatorGuiToolkit.Theme.COL_WELL - it.fillRect(10, 11, 228, 446) + // draw list of installed profiles + for (i in 0 until PROFILES_ROWS) { + batch.color = if (i % 2 == 0) EmulatorGuiToolkit.Theme.COL_WELL + else EmulatorGuiToolkit.Theme.COL_WELL2 + batch.fillRect(10, 11 + i*2*FONT.H, 228, 2*FONT.H) + } for (i in 0 until Math.min(PROFILES_ROWS, profileNames.size)) { val index = profilesScroll + i @@ -47,6 +70,8 @@ class ProfilesMenu(parent: VMEmuExecutable, x: Int, y: Int, w: Int, h: Int) : Em else EmulatorGuiToolkit.Theme.COL_WELL2 val colFore = if (index == selectedProfileIndex) EmulatorGuiToolkit.Theme.COL_ACTIVE else EmulatorGuiToolkit.Theme.COL_ACTIVE2 + val colFore2 = if (index == selectedProfileIndex) EmulatorGuiToolkit.Theme.COL_ACTIVE3 + else EmulatorGuiToolkit.Theme.COL_INACTIVE3 val theVM = parent.getVMbyProfileName(profileNames[index]) val isVMrunning = if (theVM != null) !theVM.disposed && theVM.startTime >= 0 else false @@ -56,14 +81,43 @@ class ProfilesMenu(parent: VMEmuExecutable, x: Int, y: Int, w: Int, h: Int) : Em val vmViewportText = if (vmViewport != null) "on viewport #${vmViewport+1}" else "and hidden" batch.color = colBack - it.fillRect(10, 11 + i*2*FONT.H, 228, 26) + batch.fillRect(10, 11 + i*2*FONT.H, 228, 2*FONT.H) batch.color = colFore FONT.draw(batch, profileNames[index], 12f, 11f + i*2*FONT.H) - batch.color = EmulatorGuiToolkit.Theme.COL_ACTIVE3 + batch.color = colFore2 FONT.draw(batch, "$vmRunStatusText $vmViewportText", 12f, 11f+FONT.H + i*2*FONT.H) + } + // draw profile detals view + batch.color = EmulatorGuiToolkit.Theme.COL_WELL2 + batch.fillRect(251, 11, 375, 403) + batch.fillRect(251, 427, 375, 26) + if (selectedProfileIndex != null) profileNames[selectedProfileIndex!!].let { profileName -> + val profile = parent.profiles[profileName]!! + + val ramsize = profile.getLong("ramsize") + val cardslots = profile.getInt("cardslots") + val roms = profile.get("roms").iterator().map { it } + val extraRomCount = roms.size - 1 + val coms = (1..4).map { profile.get("com$it")?.getString("cls") } // full classname of the COM device + val cards = (1 until cardslots).map { profile.get("card$it")?.getString("cls") } // full classname of the cards + + batch.color = Color.WHITE + FONT.draw(batch, "Memory: $ramsize bytes", 253f, 11f) + FONT.draw(batch, "Card Slots: $cardslots", 253f, 11f + 1*FONT.H) + FONT.draw(batch, "Extra ROMs: ${if (extraRomCount == 0) "none" else extraRomCount}", 253f, 11f + 2*FONT.H) + FONT.draw(batch, "COM Ports:", 253f, 11f + 4*FONT.H) + for (i in 1..4) { + FONT.draw(batch, "$i) ${coms[i-1]?.let { it.substring(it.lastIndexOf('.')+1) } ?: ""}", 253f, 11f + (4+i)*FONT.H) + } + + FONT.draw(batch, "Peripherals (cards):", 253f, 11f + 10*FONT.H) + FONT.draw(batch, "1) [Emulated Reference Graphics Adapter]", 253f, 11f + 11*FONT.H) + for (i in 2 until cardslots) { + FONT.draw(batch, "$i) ${cards[i-1]?.let { it.substring(it.lastIndexOf('.')+1) } ?: ""}", 253f, 11f + (10+i)*FONT.H) + } } } } diff --git a/tsvm_executable/src/net/torvald/tsvm/TsvmEmulator.java b/tsvm_executable/src/net/torvald/tsvm/TsvmEmulator.java index db1e18d..67e3cc3 100644 --- a/tsvm_executable/src/net/torvald/tsvm/TsvmEmulator.java +++ b/tsvm_executable/src/net/torvald/tsvm/TsvmEmulator.java @@ -5,6 +5,15 @@ import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.utils.JsonValue; +import net.torvald.terrarum.KVHashMap; +import net.torvald.terrarum.serialise.WriteConfig; +import net.torvald.terrarum.utils.JsonFetcher; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; /** * Created by minjaesong on 2022-10-22. @@ -15,14 +24,64 @@ public class TsvmEmulator { public static String appTitle = "tsvm"; public static Lwjgl3ApplicationConfiguration appConfig; - public static int WIDTH = 640 * 2; - public static int HEIGHT = 480 * 2; + public static int PANELS_X = 2; + public static int PANELS_Y = 2; + public static int VIEWPORT_W = 640; + public static int VIEWPORT_H = 480; + public static int WIDTH = VIEWPORT_W * PANELS_X; + public static int HEIGHT = VIEWPORT_H * PANELS_Y; + public static String OSName = System.getProperty("os.name"); + public static String OSVersion = System.getProperty("os.version"); + public static String operationSystem; + /** %appdata%/Terrarum, without trailing slash */ + public static String defaultDir; + /** defaultDir + "/config.json" */ + public static String configDir; + /** defaultDir + "/profiles.json" */ + public static String profilesDir; + + public static KVHashMap gameConfig = new KVHashMap(); public static void main(String[] args) { ShaderProgram.pedantic = false; + getDefaultDirectory(); + + // initialise the game config + for (Map.Entry entry : DefaultConfig.INSTANCE.getHashMap().entrySet()) { + gameConfig.set(entry.getKey(), entry.getValue()); + } + + // actually read the config.json + try { + // read from disk and build config from it + JsonValue map = JsonFetcher.INSTANCE.invoke(configDir); + + // make config + for (JsonValue entry = map.child; entry != null; entry = entry.next) { + setToGameConfigForced(entry, null); + } + } + catch (IOException e) { + // write default config to game dir. Call th.is method again to read config from it. + try { + createConfigJson(); + } + catch (IOException e1) { + System.out.println("[AppLoader] Unable to write config.json file"); + e.printStackTrace(); + } + } + + PANELS_X = Math.max(2, getConfigInt("viewports_cols")); + PANELS_Y = Math.max(2, getConfigInt("viewports_rows")); + VIEWPORT_W = Math.max(560, getConfigInt("viewport_width")); + VIEWPORT_H = Math.max(448, getConfigInt("viewport_height")); + WIDTH = VIEWPORT_W * PANELS_X; + HEIGHT = VIEWPORT_H * PANELS_Y; + appConfig = new Lwjgl3ApplicationConfiguration(); appConfig.setIdleFPS(60); appConfig.setForegroundFPS(60); @@ -32,7 +91,224 @@ public class TsvmEmulator { appConfig.setWindowedMode(WIDTH, HEIGHT); - new Lwjgl3Application(new VMEmuExecutableWrapper(640, 480, 2, 2,"assets/"), appConfig); + new Lwjgl3Application(new VMEmuExecutableWrapper(VIEWPORT_W, VIEWPORT_H, PANELS_X, PANELS_Y,"assets/"), appConfig); } + private static void getDefaultDirectory() { + String OS = OSName.toUpperCase(); + if (OS.contains("WIN")) { + operationSystem = "WINDOWS"; + defaultDir = System.getenv("APPDATA") + "/tsvmdevenv"; + } + else if (OS.contains("OS X") || OS.contains("MACOS")) { // OpenJDK for mac will still report "Mac OS X" with version number "10.16", even on Big Sur and beyond + operationSystem = "OSX"; + defaultDir = System.getProperty("user.home") + "/Library/Application Support/tsvmdevenv"; + } + else if (OS.contains("NUX") || OS.contains("NIX") || OS.contains("BSD")) { + operationSystem = "LINUX"; + defaultDir = System.getProperty("user.home") + "/.tsvmdevenv"; + } + else if (OS.contains("SUNOS")) { + operationSystem = "SOLARIS"; + defaultDir = System.getProperty("user.home") + "/.tsvmdevenv"; + } + else { + operationSystem = "UNKNOWN"; + defaultDir = System.getProperty("user.home") + "/.tsvmdevenv"; + } + + configDir = defaultDir + "/config.json"; + profilesDir = defaultDir + "/profiles.json"; + + System.out.println(String.format("os.name = %s (with identifier %s)", OSName, operationSystem)); + System.out.println(String.format("os.version = %s", OSVersion)); + System.out.println(String.format("default directory: %s", defaultDir)); + System.out.println(String.format("java version = %s", System.getProperty("java.version"))); + } + + + private static void createConfigJson() throws IOException { + File configFile = new File(configDir); + + if (!configFile.exists() || configFile.length() == 0L) { + WriteConfig.INSTANCE.invoke(); + } + } + + /** + * Will forcibly overwrite previously loaded config value. + * + * Key naming convention will be 'modName:propertyName'; if modName is null, the key will be just propertyName. + * + * @param value JsonValue (the key-value pair) + * @param modName module name, nullable + */ + public static void setToGameConfigForced(JsonValue value, String modName) { + gameConfig.set((modName == null) ? value.name : modName+":"+value.name, + value.isArray() ? value.asDoubleArray() : + value.isDouble() ? value.asDouble() : + value.isBoolean() ? value.asBoolean() : + value.isLong() ? value.asInt() : + value.asString() + ); + } + + /** + * Will not overwrite previously loaded config value. + * + * Key naming convention will be 'modName:propertyName'; if modName is null, the key will be just propertyName. + * + * @param value JsonValue (the key-value pair) + * @param modName module name, nullable + */ + public static void setToGameConfig(JsonValue value, String modName) { + String key = (modName == null) ? value.name : modName+":"+value.name; + if (gameConfig.get(key) == null) { + gameConfig.set(key, + value.isArray() ? value.asDoubleArray() : + value.isDouble() ? value.asDouble() : + value.isBoolean() ? value.asBoolean() : + value.isLong() ? value.asInt() : + value.asString() + ); + } + } + + /** + * + * @return true on successful, false on failure. + */ + private static Boolean readConfigJson() { + try { + // read from disk and build config from it + JsonValue map = JsonFetcher.INSTANCE.invoke(configDir); + + // make config + for (JsonValue entry = map.child; entry != null; entry = entry.next) { + setToGameConfigForced(entry, null); + } + + return true; + } + catch (IOException e) { + // write default config to game dir. Call th.is method again to read config from it. + try { + createConfigJson(); + } + catch (IOException e1) { + System.out.println("[AppLoader] Unable to write config.json file"); + e.printStackTrace(); + } + + return false; + } + + } + + /** + * Return config from config set. If the config does not exist, default value will be returned. + * @param key + * * + * @return Config from config set or default config if it does not exist. + * * + * @throws NullPointerException if the specified config simply does not exist. + */ + public static int getConfigInt(String key) { + Object cfg = getConfigMaster(key); + + if (cfg instanceof Integer) return ((int) cfg); + + double value = (double) cfg; + + if (Math.abs(value % 1.0) < 0.00000001) + return (int) Math.round(value); + return ((int) cfg); + } + + /** + * Return config from config set. If the config does not exist, default value will be returned. + * @param key + * * + * @return Config from config set or default config if it does not exist. + * * + * @throws NullPointerException if the specified config simply does not exist. + */ + public static double getConfigDouble(String key) { + Object cfg = getConfigMaster(key); + return (cfg instanceof Integer) ? (((Integer) cfg) * 1.0) : ((double) (cfg)); + } + + /** + * Return config from config set. If the config does not exist, default value will be returned. + * @param key + * * + * @return Config from config set or default config if it does not exist. + * * + * @throws NullPointerException if the specified config simply does not exist. + */ + public static String getConfigString(String key) { + Object cfg = getConfigMaster(key); + return ((String) cfg); + } + + /** + * Return config from config set. If the config does not exist, default value will be returned. + * @param key + * * + * @return Config from config set or default config if it does not exist. If the default value is undefined, will return false. + */ + public static boolean getConfigBoolean(String key) { + try { + Object cfg = getConfigMaster(key); + return ((boolean) cfg); + } + catch (NullPointerException keyNotFound) { + return false; + } + } + + /** + * Get config from config file. If the entry does not exist, get from defaults; if the entry is not in the default, NullPointerException will be thrown + */ + private static HashMap getDefaultConfig() { + return DefaultConfig.INSTANCE.getHashMap(); + } + + private static Object getConfigMaster(String key1) { + String key = key1.toLowerCase(); + + Object config; + try { + config = gameConfig.get(key); + } + catch (NullPointerException e) { + config = null; + } + + Object defaults; + try { + defaults = getDefaultConfig().get(key); + } + catch (NullPointerException e) { + defaults = null; + } + + if (config == null) { + if (defaults == null) { + throw new NullPointerException("key not found: '" + key + "'"); + } + else { + return defaults; + } + } + else { + return config; + } + } + + public static void setConfig(String key, Object value) { + gameConfig.set(key.toLowerCase(), value); + } + + } diff --git a/tsvm_executable/src/net/torvald/tsvm/VMEmuExecutable.kt b/tsvm_executable/src/net/torvald/tsvm/VMEmuExecutable.kt index bc68b02..5cbda4b 100644 --- a/tsvm_executable/src/net/torvald/tsvm/VMEmuExecutable.kt +++ b/tsvm_executable/src/net/torvald/tsvm/VMEmuExecutable.kt @@ -5,7 +5,6 @@ import com.badlogic.gdx.Gdx import com.badlogic.gdx.files.FileHandle import com.badlogic.gdx.graphics.* import com.badlogic.gdx.graphics.g2d.SpriteBatch -import com.badlogic.gdx.utils.Json import com.badlogic.gdx.utils.JsonReader import com.badlogic.gdx.utils.JsonValue import com.badlogic.gdx.utils.JsonWriter @@ -79,14 +78,10 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX: var coroutineJobs = HashMap() // companion object { - val APPDATADIR = System.getProperty("os.name").toUpperCase().let { - if (it.contains("WIN")) System.getenv("APPDATA") + "/tsvmdevenv" - else if (it.contains("OS X") || it.contains("MACOS")) System.getProperty("user.home") + "/Library/Application Support/tsvmdevenv" - else System.getProperty("user.home") + "/.tsvmdevenv" - } + val APPDATADIR = TsvmEmulator.defaultDir - val FILE_CONFIG = Gdx.files.absolute("$APPDATADIR/config.json") - val FILE_PROFILES = Gdx.files.absolute("$APPDATADIR/profiles.json") + val FILE_CONFIG = Gdx.files.absolute(TsvmEmulator.configDir) + val FILE_PROFILES = Gdx.files.absolute(TsvmEmulator.profilesDir) } val fullscreenQuad = Mesh( @@ -571,9 +566,10 @@ object EmulatorGuiToolkit { object Theme { val COL_INACTIVE = Color(0x858585ff.toInt()) val COL_INACTIVE2 = Color(0x5a5a5fff.toInt()) + val COL_INACTIVE3 = Color.WHITE val COL_ACTIVE = Color(0x23ff00ff.toInt()) // neon green val COL_ACTIVE2 = Color(0xfff600ff.toInt()) // yellow - val COL_ACTIVE3 = Color.WHITE + val COL_ACTIVE3 = Color(0x5ff8ffff.toInt()) // cyan val COL_HIGHLIGHT = Color(0xe43380ff.toInt()) // magenta val COL_DISABLED = Color(0xaaaaaaff.toInt())