inital commit кек

This commit is contained in:
SashegDev
2026-06-04 03:12:17 +00:00
parent 82675f402d
commit f2888dea3a
190 changed files with 18421 additions and 21 deletions
+188
View File
@@ -0,0 +1,188 @@
plugins {
id 'application'
}
mainClassName = 'com.cbe.gui.Main'
dependencies {
implementation project(':modules:core')
implementation project(':modules:loader')
implementation project(':modules:cbecc')
compileOnly 'com.google.code.findbugs:jsr305:3.0.2'
}
application {
applicationName = 'cbe-emu'
mainModule = 'com.cbe.gui'
}
jar {
manifest {
attributes 'Main-Class': 'com.cbe.gui.Main'
}
}
// Build a fat-jar with all dependencies for packaging with jpackage
// Also embeds example .cbeplugin files so the jar can run with no arguments
tasks.register('fatJar', Jar) {
archiveClassifier.set('all')
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
manifest {
attributes 'Main-Class': 'com.cbe.gui.Main'
}
from sourceSets.main.output
dependsOn configurations.runtimeClasspath
from {
configurations.runtimeClasspath.findAll { it.name.endsWith('jar') }.collect { zipTree(it) }
}
exclude 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'module-info.class'
// Embed pre-compiled .cbeplugin files so the jar runs standalone
doFirst {
def buildDir = file("$rootDir/build")
buildDir.mkdirs()
def cbeccCp = configurations.runtimeClasspath.asPath
// Map: plugin basename -> source folder suffix
def plugins = [
'tiny-cpu' : 'cpu',
'basic-ram' : 'ram',
'vga-display' : 'gpu',
'basic-kbd' : 'kbd',
'basic-snd' : 'snd',
'tiny-bios' : 'bios',
]
plugins.each { name, ext ->
def src = file("$rootDir/examples/${name}.${ext}")
def dst = new File(buildDir, "${name}.cbeplugin")
if (src.exists()) {
exec {
commandLine 'java', '-cp', cbeccCp, 'com.cbe.cbecc.Main', 'build', src.absolutePath, '-o', dst.absolutePath
}
}
}
}
from("$rootDir/build") {
include 'tiny-cpu.cbeplugin'
include 'basic-ram.cbeplugin'
include 'vga-display.cbeplugin'
include 'basic-kbd.cbeplugin'
include 'basic-snd.cbeplugin'
include 'tiny-bios.cbeplugin'
into 'embedded/'
}
}
// Stage the fat-jar and required runtime jars in a single directory for jpackage
tasks.register('stage', Copy) {
dependsOn fatJar
from("$buildDir/libs") {
include 'gui-0.1.0-all.jar'
rename 'gui-0.1.0-all.jar', 'cbe-emu.jar'
}
into "$buildDir/stage"
doLast {
println ""
println "==> Staged: $buildDir/stage"
file("$buildDir/stage").eachFileMatch(~/.*\.jar/) { println " " + it.name }
}
}
// Build a native installer using JDK jpackage
// Usage: ./gradlew :modules:gui:packageImage -PjpackageType=app-image
// ./gradlew :modules:gui:packageImage -PjpackageType=msi
// ./gradlew :modules:gui:packageImage -PjpackageType=exe
// (requires JDK 14+ on the host running the build)
tasks.register('packageImage', Exec) {
dependsOn stage
def stageDir = file("$buildDir/stage")
def outDir = file("$buildDir/installer")
def type = hasProperty('jpackageType') ? property('jpackageType') : 'app-image'
doFirst {
outDir.deleteDir()
outDir.mkdirs()
}
// 'app-image' = folder with launcher inside (no JRE bundled) - works on any host
// 'msi' = Windows MSI installer (with bundled JRE) - requires WiX
// 'exe' = Windows EXE installer (with bundled JRE) - requires Inno Setup
// 'deb'/'rpm' = Linux installer
// 'dmg'/'pkg' = macOS installer
commandLine 'jpackage',
'--input', stageDir.absolutePath,
'--dest', outDir.absolutePath,
'--name', 'CBE-Emulator',
'--main-jar', 'cbe-emu.jar',
'--main-class', 'com.cbe.gui.Main',
'--app-version', '0.1.0',
'--vendor', 'CBE Project',
'--description', 'CBE Platform - Emulator',
'--type', type,
'--java-options', '-Xmx256m'
}
// Build a portable Windows package: fat-jar + bundled JRE + .bat launcher
// Usage: ./gradlew :modules:gui:portableWindowsZip -PwinJre=/mnt/e/ZernMC/lib/jre21
// Property: -PwinJre=<path> - REQUIRED: path to a Windows JRE (must contain bin/java.exe)
tasks.register('portableWindows', Exec) {
group = 'distribution'
description = 'Builds dist/windows-portable/ - .bat launcher + bundled JRE + fat-jar + plugins'
dependsOn stage
def winJre = findProperty('winJre')
if (winJre == null) {
throw new GradleException("portableWindows requires -PwinJre=<path-to-Windows-JRE>")
}
def winJreFile = file(winJre)
if (!new File(winJreFile, 'bin/java.exe').exists()) {
throw new GradleException("-PwinJre=$winJre does not contain bin/java.exe (not a Windows JRE?)")
}
def outDir = file("$rootDir/dist/windows-portable")
commandLine 'bash', '-c', """
set -e
rm -rf '${outDir.absolutePath}'
mkdir -p '${outDir.absolutePath}/runtime'
mkdir -p '${outDir.absolutePath}/app'
mkdir -p '${outDir.absolutePath}/app/examples'
# Copy Windows JRE
cp -rL '${winJreFile.absolutePath}/.' '${outDir.absolutePath}/runtime/'
# Copy fat jar (with embedded plugins)
cp '${rootDir}/modules/gui/build/stage/cbe-emu.jar' '${outDir.absolutePath}/app/'
# Copy launchers
cp '${rootDir}/modules/gui/src/launcher/CBE-Emulator.bat' '${outDir.absolutePath}/'
cp '${rootDir}/modules/gui/src/launcher/CBE-Emulator.vbs' '${outDir.absolutePath}/'
# Create CBE-Emulator.exe as a copy of javaw.exe (real Windows PE binary)
# Use the bundled runtime's javaw.exe
cp '${outDir.absolutePath}/runtime/bin/javaw.exe' '${outDir.absolutePath}/CBE-Emulator.exe'
# Compile example plugins (strip source type extension: foo.ram -> foo)
CBECC_CP='${rootDir}/modules/cbecc/build/libs/cbecc-0.1.0.jar:${rootDir}/modules/loader/build/libs/loader-0.1.0.jar:${rootDir}/modules/core/build/libs/core-0.1.0.jar'
for src in '${rootDir}/examples'/*/; do
fullname=\$(basename "\$src")
shortname=\${fullname%.*} # remove .ram / .gpu / .cpu suffix
java -cp "\$CBECC_CP" com.cbe.cbecc.Main build "\$src" -o "${outDir.absolutePath}/app/examples/\$shortname.cbeplugin" 2>/dev/null || true
done
echo ''
echo '==> Built portable package: ${outDir.absolutePath}'
du -sh '${outDir.absolutePath}'
ls -la '${outDir.absolutePath}/' | head -10
"""
}
tasks.register('portableWindowsZip', Exec) {
group = 'distribution'
description = 'Builds dist/CBE-Emulator-windows-portable.zip'
dependsOn portableWindows
commandLine 'bash', '-c', """
set -e
cd '${rootDir}/dist'
rm -f CBE-Emulator-windows-portable.zip
python3 -c "
import zipfile, os
with zipfile.ZipFile('CBE-Emulator-windows-portable.zip', 'w', zipfile.ZIP_DEFLATED, compresslevel=6) as z:
for root, dirs, files in os.walk('windows-portable'):
for f in files:
full = os.path.join(root, f)
rel = os.path.relpath(full, 'windows-portable')
z.write(full, rel)
"
ls -la CBE-Emulator-windows-portable.zip
"""
}
+27
View File
@@ -0,0 +1,27 @@
@echo off
REM ===========================================================
REM CBE Emulator launcher
REM Double-click to run with embedded plugins.
REM No arguments needed - all plugins are inside cbe-emu.jar
REM ===========================================================
setlocal
cd /d "%~dp0"
set "JAVA_EXE=%~dp0runtime\bin\javaw.exe"
set "JAR=%~dp0app\cbe-emu.jar"
if not exist "%JAVA_EXE%" (
echo [ERROR] JRE not found at %JAVA_EXE%
pause
exit /b 1
)
if not exist "%JAR%" (
echo [ERROR] cbe-emu.jar not found at %JAR%
pause
exit /b 1
)
REM Launch GUI. Use %* to forward any extra args.
"%JAVA_EXE%" -Xmx256m -jar "%JAR%" %*
+40
View File
@@ -0,0 +1,40 @@
' ===========================================================
' CBE Emulator launcher (VBScript - no console window)
' Double-click to run with embedded plugins, silently.
' ===========================================================
Option Explicit
Dim shell, fso, javaExe, jar, baseDir, cmd
Set shell = CreateObject("WScript.Shell")
Set fso = CreateObject("Scripting.FileSystemObject")
baseDir = fso.GetParentFolderName(WScript.ScriptFullName)
javaExe = baseDir & "\runtime\bin\javaw.exe"
jar = baseDir & "\app\cbe-emu.jar"
If Not fso.FileExists(javaExe) Then
MsgBox "JRE not found at:" & vbCrLf & javaExe, 16, "CBE Emulator"
WScript.Quit 1
End If
If Not fso.FileExists(jar) Then
MsgBox "cbe-emu.jar not found at:" & vbCrLf & jar, 16, "CBE Emulator"
WScript.Quit 1
End If
' Collect extra args after the .vbs filename
Dim extra
extra = ""
Dim i
For i = 0 To WScript.Arguments.Count - 1
extra = extra & " """ & WScript.Arguments(i) & """"
Next
' Build command. 0 = hide window, False = don't wait for return
cmd = """" & javaExe & """ -Xmx256m -jar """ & jar & """ " & extra
shell.Run cmd, 0, False
Set shell = Nothing
Set fso = Nothing
@@ -0,0 +1,262 @@
package com.cbe.gui;
import com.cbe.gui.internal.EmulatorWindow;
import com.cbe.cbecc.Compiler;
import com.cbe.loader.Engine;
import com.cbe.loader.ModuleLoadException;
import com.cbe.loader.ModuleLoader;
import com.cbe.loader.SimpleRegisters;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
// Simple arg parsing
Map<String, String> argMap = new HashMap<String, String>();
for (int i = 0; i < args.length; i++) {
String a = args[i];
if (a.startsWith("--")) {
if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
argMap.put(a, args[++i]);
} else {
argMap.put(a, "true");
}
} else if ("--nogui".equals(a) || "build".equals(a) || "run".equals(a)) {
argMap.put(a, "true");
}
}
if (argMap.containsKey("build")) {
runBuildCommand(argMap);
return;
}
if (args.length == 0) {
// No args: try embedded plugins (from the fat-jar)
if (loadEmbeddedPlugins(argMap)) {
runGui(argMap);
return;
}
printUsage();
System.exit(1);
}
// If no --cpu/--ram/--gpu provided, try to fall back to embedded plugins
if (!argMap.containsKey("--cpu") && !argMap.containsKey("--ram") && !argMap.containsKey("--gpu")) {
loadEmbeddedPlugins(argMap);
}
if (argMap.containsKey("--nogui") && !argMap.containsKey("run")) {
runConsole(argMap);
return;
}
runGui(argMap);
}
private static boolean loadEmbeddedPlugins(Map<String, String> argMap) {
try {
String cpu = findEmbedded("embedded/tiny-cpu.cbeplugin");
String ram = findEmbedded("embedded/basic-ram.cbeplugin");
String gpu = findEmbedded("embedded/vga-display.cbeplugin");
String kbd = findEmbedded("embedded/basic-kbd.cbeplugin");
String snd = findEmbedded("embedded/basic-snd.cbeplugin");
String bios = findEmbedded("embedded/tiny-bios.cbeplugin");
if (cpu == null) return false;
argMap.put("--cpu", cpu);
if (ram != null) argMap.put("--ram", ram);
if (gpu != null) argMap.put("--gpu", gpu);
if (kbd != null) argMap.put("--kbd", kbd);
if (snd != null) argMap.put("--snd", snd);
if (bios != null) argMap.put("--bios", bios);
return true;
} catch (Exception e) {
return false;
}
}
private static String findEmbedded(String resourcePath) throws java.io.IOException {
java.io.InputStream is = Main.class.getClassLoader().getResourceAsStream(resourcePath);
if (is == null) return null;
// Extract to a temp file so the loaders can use Path-based API
java.io.File tmp = java.io.File.createTempFile("cbe-", ".cbeplugin");
tmp.deleteOnExit();
try (java.io.FileOutputStream out = new java.io.FileOutputStream(tmp)) {
byte[] buf = new byte[8192];
int n;
while ((n = is.read(buf)) > 0) out.write(buf, 0, n);
}
return tmp.getAbsolutePath();
}
private static void runBuildCommand(Map<String, String> args) {
String sourceDir = args.get("--source");
String output = args.get("-o");
if (sourceDir == null || output == null) {
System.err.println("Usage: cbecc build --source <dir> -o <output.cbeplugin>");
System.exit(1);
}
try {
new Compiler().compile(Paths.get(sourceDir), Paths.get(output));
System.out.println("Compiled: " + sourceDir + " -> " + output);
} catch (Exception e) {
System.err.println("Build failed: " + e.getMessage());
System.exit(1);
}
}
private static void runConsole(Map<String, String> args) {
Engine engine = new Engine();
try {
attachModules(engine, args);
} catch (Exception e) {
System.err.println("Module load failed: " + e.getMessage());
System.exit(1);
}
SimpleRegisters regs = engine.createRegisters();
regs.write("pc", 0);
regs.write("sp", 0x80);
long total = 0;
long lastReport = 0;
try {
while (true) {
boolean running = engine.step(regs);
total++;
if (!running) {
System.out.println("Halted after " + total + " instructions");
return;
}
if (total - lastReport >= 100_000) {
System.out.println("Running... " + total + " instructions" +
" | Loop detection: active (infinite loops never halt)");
lastReport = total;
}
}
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
} finally {
System.out.println("Total: " + total + " instructions");
}
}
private static void runGui(Map<String, String> args) {
Engine engine = new Engine();
try {
attachModules(engine, args);
} catch (ModuleLoadException e) {
System.err.println("Module load failed: " + e.getMessage());
System.exit(1);
}
// Write a simple test program to GPU if no program loader mechanism is present
if (engine.hasGpu() && args.containsKey("--program")) {
String program = args.get("--program");
// For now, write directly to GPU. Later, CPU will do this through bus.
if (engine.getSourceGpu() != null) {
engine.getSourceGpu().writeString(program);
} else if (engine.getCompiledGpu() != null) {
engine.getCompiledGpu().writeString(program);
}
}
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ignored) {}
SwingUtilities.invokeLater(() -> {
EmulatorWindow window = new EmulatorWindow(engine, args);
window.setVisible(true);
});
}
private static void attachModules(Engine engine, Map<String, String> args) throws ModuleLoadException {
ModuleLoader loader = new ModuleLoader();
String cpuPath = args.get("--cpu");
String ramPath = args.get("--ram");
String gpuPath = args.get("--gpu");
String kbdPath = args.get("--kbd");
String sndPath = args.get("--snd");
String biosPath = args.get("--bios");
String diskPath = args.get("--disk");
if (biosPath != null) {
Path p = resolvePath(biosPath);
if (p.toString().endsWith(".cbeplugin")) engine.loadCompiledBios(p);
else engine.loadBios(p);
}
if (cpuPath != null) {
Path p = resolvePath(cpuPath);
if (p.toString().endsWith(".cbeplugin")) engine.loadCompiledCpu(p);
else engine.loadCpu(p);
}
if (ramPath != null) {
Path p = resolvePath(ramPath);
int base = parseIntOrDefault(args.get("--ram-base"), 0);
int size = parseIntOrDefault(args.get("--ram-size"), 256);
if (p.toString().endsWith(".cbeplugin")) engine.loadCompiledRam(p, base);
else engine.loadRam(p, base);
}
if (gpuPath != null) {
Path p = resolvePath(gpuPath);
if (p.toString().endsWith(".cbeplugin")) engine.loadCompiledGpu(p);
else engine.loadGpu(p);
}
if (kbdPath != null) {
Path p = resolvePath(kbdPath);
if (p.toString().endsWith(".cbeplugin")) engine.loadCompiledKbd(p);
else engine.loadKbd(p);
}
if (sndPath != null) {
Path p = resolvePath(sndPath);
if (p.toString().endsWith(".cbeplugin")) engine.loadCompiledSnd(p);
else engine.loadSnd(p);
}
if (diskPath != null) {
Path p = resolvePath(diskPath);
if (p.toString().endsWith(".cbeplugin")) engine.loadCompiledDisk(p);
else engine.loadDisk(p);
}
}
private static Path resolvePath(String path) {
Path p = Paths.get(path);
if (Files.exists(p)) return p;
Path alt = Paths.get("examples").resolve(path);
if (Files.exists(alt)) return alt;
Path alt2 = Paths.get("build").resolve(path);
if (Files.exists(alt2)) return alt2;
return p;
}
private static int parseIntOrDefault(String s, int def) {
if (s == null) return def;
try { return Integer.parseInt(s); } catch (NumberFormatException e) { return def; }
}
private static void printUsage() {
System.out.println("CBE Emulator");
System.out.println();
System.out.println("Usage:");
System.out.println(" cbe-emu --cpu <path> [--ram <path> --ram-base <n>] [--gpu <path>] [--program <text>]");
System.out.println(" cbecc build --source <dir> -o <output.cbeplugin>");
System.out.println();
System.out.println("Options:");
System.out.println(" --cpu <path> CPU module file (.cbeplugin or source dir)");
System.out.println(" --ram <path> RAM module file");
System.out.println(" --ram-base <n> Base address for RAM (default 0)");
System.out.println(" --ram-size <n> RAM size in bytes (default 256)");
System.out.println(" --gpu <path> GPU module file");
System.out.println(" --program <text> Initial text to display on GPU");
System.out.println(" --nogui Run in console mode (no window)");
System.out.println();
System.out.println("Examples:");
System.out.println(" cbe-emu --cpu build/tiny-cpu.cbeplugin --ram build/basic-ram.cbeplugin --gpu examples/vga-display.gpu --program \"Hello, CBE!\"");
}
}
@@ -0,0 +1,509 @@
package com.cbe.gui.internal;
import com.cbe.core.ModuleInstance;
import com.cbe.core.OpcodeResult;
import com.cbe.loader.Engine;
import com.cbe.loader.SimpleRegisters;
import javax.swing.*;
import javax.swing.border.TitledBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import java.util.Map;
public class EmulatorWindow extends JFrame {
private final Engine engine;
private final Map<String, String> args;
private final ScreenPanel screenPanel;
private final PostCodePanel postCodePanel;
private final JLabel systemInfoLabel;
private final JTextArea logArea;
private final JTextArea regsArea;
private final JTextArea vramArea;
private JList<String> moduleList;
private DefaultListModel<String> moduleListModel;
private JLabel statusLabel;
private JSpinner stepDelaySpinner;
private SimpleRegisters regs;
private Timer autoRunTimer;
private int totalSteps;
// Dark theme colors
private static final Color DARK_BG = new Color(30, 30, 36);
private static final Color DARK_BG2 = new Color(38, 38, 46);
private static final Color DARK_BORDER = new Color(50, 50, 60);
private static final Color DARK_TEXT = new Color(200, 200, 210);
private static final Color DARK_TEXT_BRIGHT = new Color(220, 220, 240);
private static final Color DARK_ACCENT = new Color(80, 160, 255);
private static final Color DARK_GREEN = new Color(80, 220, 120);
private static final Color DARK_ORANGE = new Color(255, 160, 60);
private static final Color DARK_RED = new Color(220, 80, 80);
private boolean keyboardCaptureEnabled;
private JLabel kbdIndicator;
public EmulatorWindow(Engine engine, Map<String, String> args) {
this.engine = engine;
this.args = args;
this.totalSteps = 0;
this.regs = engine.createRegisters();
setTitle("CBE Emulator");
setSize(1100, 750);
setMinimumSize(new Dimension(900, 600));
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(new BorderLayout(4, 4));
getContentPane().setBackground(DARK_BG);
try {
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
} catch (Exception ignored) {}
applyDarkUIManager();
// ======= TOP: Module list (slot board) =======
JPanel topPanel = createTopPanel();
add(topPanel, BorderLayout.NORTH);
// ======= CENTER: Split pane =======
JSplitPane centerSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
centerSplit.setDividerLocation(720);
centerSplit.setBackground(DARK_BG);
centerSplit.setBorder(BorderFactory.createLineBorder(DARK_BORDER));
// LEFT: Screen (like QEMU window)
JPanel leftPanel = new JPanel(new BorderLayout(2, 2));
leftPanel.setBackground(DARK_BG);
leftPanel.setBorder(BorderFactory.createTitledBorder(
BorderFactory.createLineBorder(DARK_BORDER), "Display (QEMU-style)",
TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.DEFAULT_POSITION,
new Font(Font.MONOSPACED, Font.BOLD, 11), DARK_ACCENT));
screenPanel = new ScreenPanel();
leftPanel.add(screenPanel, BorderLayout.CENTER);
leftPanel.add(createScreenControls(), BorderLayout.SOUTH);
// ======= TOP-CENTER: POST code panel (above the screen) =======
postCodePanel = new PostCodePanel();
engine.addPostListener(postCodePanel);
systemInfoLabel = new JLabel(" ");
systemInfoLabel.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 10));
systemInfoLabel.setForeground(DARK_GREEN);
systemInfoLabel.setBackground(DARK_BG2);
systemInfoLabel.setOpaque(true);
systemInfoLabel.setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 4));
JPanel postStack = new JPanel(new BorderLayout());
postStack.setBackground(DARK_BG);
postStack.add(postCodePanel, BorderLayout.CENTER);
postStack.add(systemInfoLabel, BorderLayout.SOUTH);
leftPanel.add(postStack, BorderLayout.NORTH);
// RIGHT: Debug
JPanel rightPanel = new JPanel(new GridLayout(3, 1, 2, 2));
rightPanel.setBackground(DARK_BG);
rightPanel.setBorder(BorderFactory.createTitledBorder(
BorderFactory.createLineBorder(DARK_BORDER), "Debug",
TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.DEFAULT_POSITION,
new Font(Font.MONOSPACED, Font.BOLD, 11), DARK_ACCENT));
regsArea = new JTextArea();
regsArea.setEditable(false);
styleTextArea(regsArea);
JScrollPane regsScroll = new JScrollPane(regsArea);
styleScrollPane(regsScroll, "Registers");
vramArea = new JTextArea();
vramArea.setEditable(false);
styleTextArea(vramArea);
JScrollPane vramScroll = new JScrollPane(vramArea);
styleScrollPane(vramScroll, "VRAM (hex)");
logArea = new JTextArea();
logArea.setEditable(false);
styleTextArea(logArea);
JScrollPane logScroll = new JScrollPane(logArea);
styleScrollPane(logScroll, "Log");
rightPanel.add(regsScroll);
rightPanel.add(vramScroll);
rightPanel.add(logScroll);
centerSplit.setLeftComponent(leftPanel);
centerSplit.setRightComponent(rightPanel);
add(centerSplit, BorderLayout.CENTER);
// ======= BOTTOM: Status & controls =======
JPanel bottomPanel = createBottomPanel();
add(bottomPanel, BorderLayout.SOUTH);
// Initial population
moduleListModel.clear();
for (ModuleInstance m : engine.getModules()) {
moduleListModel.addElement(formatModule(m));
}
if (engine.getCpu() == null) {
appendLog("No CPU loaded. Use --cpu to specify a CPU module.");
} else {
appendLog("CPU: " + engine.getCpu().getName() + " (" + engine.getCpu().getMetadata().getArch() + ")");
}
if (engine.getRam() != null) {
appendLog("RAM: " + engine.getRam().getName());
}
if (engine.hasGpu()) {
appendLog("GPU: " + (engine.getSourceGpu() != null ? engine.getSourceGpu().getName() : engine.getCompiledGpu().getName()));
}
// Run BIOS POST once before the first draw, so the GPU shows the boot screen
if (engine.getCpu() != null) {
engine.step(regs); // triggers runDiagnostics() on first step
}
// Periodically refresh screen & debug views
Timer refreshTimer = new Timer(80, e -> refreshViews());
refreshTimer.start();
// Forward key events to the engine's KBD module. We use KeyboardFocusManager
// so keys are caught regardless of which sub-component has focus.
setFocusable(true);
requestFocusInWindow();
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(e -> {
if (keyboardCaptureEnabled && e.getID() == java.awt.event.KeyEvent.KEY_PRESSED && isFocused()) {
int k = e.getKeyCode();
engine.pushKey(k);
}
return false;
});
// Enable keyboard capture by default only if screenPanel is focused
keyboardCaptureEnabled = false;
screenPanel.addMouseListener(new java.awt.event.MouseAdapter() {
@Override
public void mouseClicked(java.awt.event.MouseEvent e) {
keyboardCaptureEnabled = true;
updateKbdIndicator();
screenPanel.requestFocusInWindow();
}
});
addMouseListener(new java.awt.event.MouseAdapter() {
@Override
public void mouseClicked(java.awt.event.MouseEvent e) {
if (keyboardCaptureEnabled) {
keyboardCaptureEnabled = false;
updateKbdIndicator();
}
}
});
updateRegs();
updateVram();
updateStatus();
}
private void applyDarkUIManager() {
UIManager.put("Panel.background", DARK_BG);
UIManager.put("OptionPane.background", DARK_BG);
UIManager.put("OptionPane.messageForeground", DARK_TEXT);
UIManager.put("TextField.background", DARK_BG2);
UIManager.put("TextField.foreground", DARK_TEXT);
UIManager.put("TextField.caretForeground", DARK_TEXT);
UIManager.put("TextArea.background", DARK_BG2);
UIManager.put("TextArea.foreground", DARK_TEXT);
UIManager.put("TextArea.caretForeground", DARK_TEXT);
UIManager.put("List.background", DARK_BG2);
UIManager.put("List.foreground", DARK_TEXT);
UIManager.put("ScrollPane.background", DARK_BG);
UIManager.put("Viewport.background", DARK_BG2);
UIManager.put("Label.foreground", DARK_TEXT);
UIManager.put("Button.background", DARK_BG2);
UIManager.put("Button.foreground", DARK_TEXT_BRIGHT);
UIManager.put("Button.select", DARK_ACCENT);
UIManager.put("TitledBorder.titleColor", DARK_ACCENT);
UIManager.put("SplitPane.background", DARK_BG);
UIManager.put("Spinner.background", DARK_BG2);
UIManager.put("Spinner.foreground", DARK_TEXT);
UIManager.put("EditorPane.background", DARK_BG2);
UIManager.put("EditorPane.foreground", DARK_TEXT);
}
private void styleTextArea(JTextArea area) {
area.setBackground(DARK_BG2);
area.setForeground(DARK_GREEN);
area.setCaretColor(DARK_TEXT);
area.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 11));
area.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
}
private void styleScrollPane(JScrollPane scroll, String title) {
scroll.getViewport().setBackground(DARK_BG2);
scroll.setBorder(BorderFactory.createTitledBorder(
BorderFactory.createLineBorder(DARK_BORDER), title,
TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.DEFAULT_POSITION,
new Font(Font.MONOSPACED, Font.BOLD, 10), DARK_ACCENT));
}
private JPanel createTopPanel() {
JPanel panel = new JPanel(new BorderLayout(4, 4));
panel.setBackground(DARK_BG);
panel.setBorder(BorderFactory.createEmptyBorder(4, 4, 0, 4));
JLabel titleLabel = new JLabel("Motherboard: CBE Emulator \u2014 Loaded Modules");
titleLabel.setFont(titleLabel.getFont().deriveFont(Font.BOLD, 12f));
titleLabel.setForeground(DARK_TEXT_BRIGHT);
panel.add(titleLabel, BorderLayout.NORTH);
moduleListModel = new DefaultListModel<String>();
moduleList = new JList<String>(moduleListModel);
moduleList.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 11));
moduleList.setBackground(DARK_BG2);
moduleList.setForeground(DARK_TEXT);
JScrollPane scroll = new JScrollPane(moduleList);
scroll.setPreferredSize(new Dimension(0, 90));
scroll.getViewport().setBackground(DARK_BG2);
panel.add(scroll, BorderLayout.CENTER);
return panel;
}
private JPanel createScreenControls() {
JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 4, 2));
panel.setBackground(DARK_BG2);
JButton clearBtn = createStyledButton("Clear GPU");
clearBtn.addActionListener(e -> {
if (engine.getSourceGpu() != null) engine.getSourceGpu().clear();
if (engine.getCompiledGpu() != null) engine.getCompiledGpu().clear();
});
panel.add(clearBtn);
JButton helloBtn = createStyledButton("Print Hello");
helloBtn.addActionListener(e -> {
if (engine.getSourceGpu() != null) engine.getSourceGpu().writeString("Hello, CBE Platform!\n");
if (engine.getCompiledGpu() != null) engine.getCompiledGpu().writeString("Hello, CBE Platform!\n");
});
panel.add(helloBtn);
JLabel delayLabel = new JLabel("Step delay (ms):");
delayLabel.setForeground(DARK_TEXT);
panel.add(delayLabel);
stepDelaySpinner = new JSpinner(new SpinnerNumberModel(100, 10, 5000, 50));
stepDelaySpinner.setBackground(DARK_BG2);
stepDelaySpinner.getEditor().getComponent(0).setBackground(DARK_BG2);
stepDelaySpinner.getEditor().getComponent(0).setForeground(DARK_TEXT);
panel.add(stepDelaySpinner);
return panel;
}
private JButton createStyledButton(String text) {
JButton btn = new JButton(text);
btn.setBackground(new Color(50, 50, 65));
btn.setForeground(DARK_TEXT_BRIGHT);
btn.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(70, 70, 85)),
BorderFactory.createEmptyBorder(2, 8, 2, 8)));
btn.setFocusPainted(false);
return btn;
}
private JPanel createBottomPanel() {
JPanel panel = new JPanel(new BorderLayout(4, 4));
panel.setBackground(DARK_BG);
panel.setBorder(BorderFactory.createEmptyBorder(0, 4, 4, 4));
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 4, 2));
buttonPanel.setBackground(DARK_BG2);
JButton stepBtn = createStyledButton("Step");
stepBtn.addActionListener(e -> doStep());
buttonPanel.add(stepBtn);
JButton runBtn = createStyledButton("Run");
runBtn.addActionListener(e -> toggleAutoRun(runBtn));
buttonPanel.add(runBtn);
JButton stopBtn = createStyledButton("Stop");
stopBtn.addActionListener(e -> {
if (autoRunTimer != null) autoRunTimer.stop();
runBtn.setText("Run");
});
buttonPanel.add(stopBtn);
JButton resetBtn = createStyledButton("Reset");
resetBtn.addActionListener(e -> {
totalSteps = 0;
regs = engine.createRegisters();
engine.reset();
appendLog("--- RESET ---");
});
buttonPanel.add(resetBtn);
JButton testBtn = createStyledButton("Test ADD a,b");
testBtn.addActionListener(e -> runTestAdd());
buttonPanel.add(testBtn);
kbdIndicator = new JLabel("KBD: OFF");
kbdIndicator.setForeground(DARK_RED);
kbdIndicator.setFont(new Font(Font.MONOSPACED, Font.BOLD, 11));
kbdIndicator.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(DARK_RED),
BorderFactory.createEmptyBorder(2, 6, 2, 6)));
buttonPanel.add(Box.createHorizontalStrut(8));
buttonPanel.add(kbdIndicator);
panel.add(buttonPanel, BorderLayout.WEST);
statusLabel = new JLabel(" Ready");
statusLabel.setForeground(DARK_TEXT);
statusLabel.setBackground(DARK_BG2);
statusLabel.setOpaque(true);
statusLabel.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(DARK_BORDER),
BorderFactory.createEmptyBorder(2, 8, 2, 8)));
panel.add(statusLabel, BorderLayout.CENTER);
return panel;
}
private void toggleAutoRun(JButton runBtn) {
if (autoRunTimer != null && autoRunTimer.isRunning()) {
autoRunTimer.stop();
runBtn.setText("Run");
} else {
int delay = (Integer) stepDelaySpinner.getValue();
autoRunTimer = new Timer(delay, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
doStep();
}
});
autoRunTimer.start();
runBtn.setText("Pause");
}
}
private void doStep() {
if (engine.getCpu() == null) {
appendLog("Cannot step: no CPU loaded");
return;
}
boolean running = engine.step(regs);
totalSteps++;
updateRegs();
updateVram();
updateStatus();
if (!running) {
if (autoRunTimer != null) autoRunTimer.stop();
appendLog("CPU halted at step " + totalSteps);
}
}
private void runTestAdd() {
if (engine.getCpu() == null) {
appendLog("Cannot run: no CPU loaded");
return;
}
regs.write("a", 3);
regs.write("b", 4);
OpcodeResult r = engine.getCpu().executeOpcode(0x03, regs, engine.getBus());
totalSteps++;
appendLog("Test ADD: a(3) + b(4) = " + regs.read("a") + " (cycles=" + r.getCyclesConsumed() + ")");
updateRegs();
}
private void refreshViews() {
screenPanel.refresh(engine);
updateVram();
updateStatus();
String info = engine.getSystemInfo();
if (info != null) systemInfoLabel.setText(info);
}
private void updateRegs() {
StringBuilder sb = new StringBuilder();
for (String name : regs.names()) {
int v = regs.read(name) & 0xFF;
sb.append(String.format("%-6s = 0x%02X (%3d)%n", name, v, v));
}
regsArea.setText(sb.toString());
}
private void updateVram() {
byte[] vram = null;
int rows = 25, cols = 80;
if (engine.getSourceGpu() != null) {
vram = engine.getSourceGpu().getVram();
rows = engine.getSourceGpu().getRows();
cols = engine.getSourceGpu().getCols();
} else if (engine.getCompiledGpu() != null) {
vram = engine.getCompiledGpu().getVram();
rows = engine.getCompiledGpu().getRows();
cols = engine.getCompiledGpu().getCols();
}
if (vram == null) {
vramArea.setText("(no GPU)\n");
return;
}
if (vram.length == 0) {
vramArea.setText("(empty VRAM)\n");
return;
}
StringBuilder sb = new StringBuilder();
sb.append("VRAM size: ").append(vram.length).append(" bytes\n");
sb.append("Cursor: (").append(getGpuCursorX()).append(",").append(getGpuCursorY()).append(")\n\n");
sb.append("Hex dump:\n");
for (int row = 0; row < rows; row++) {
sb.append(String.format("%04X: ", row * cols));
for (int col = 0; col < cols; col++) {
sb.append(String.format("%02X ", vram[row * cols + col] & 0xFF));
}
sb.append("\n");
}
vramArea.setText(sb.toString());
}
private int getGpuCursorX() {
if (engine.getSourceGpu() != null) return engine.getSourceGpu().getCursorX();
if (engine.getCompiledGpu() != null) return engine.getCompiledGpu().getCursorX();
return 0;
}
private int getGpuCursorY() {
if (engine.getSourceGpu() != null) return engine.getSourceGpu().getCursorY();
if (engine.getCompiledGpu() != null) return engine.getCompiledGpu().getCursorY();
return 0;
}
private void updateStatus() {
statusLabel.setText(String.format(" Steps: %d | Bus clock: %d | Modules: %d",
totalSteps, engine.getBus().clock(), engine.getModules().size()));
}
private void updateKbdIndicator() {
if (keyboardCaptureEnabled) {
kbdIndicator.setText("KBD: ON (click outside to disable)");
kbdIndicator.setForeground(DARK_GREEN);
kbdIndicator.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(DARK_GREEN),
BorderFactory.createEmptyBorder(2, 6, 2, 6)));
} else {
kbdIndicator.setText("KBD: OFF (click VGA screen to enable)");
kbdIndicator.setForeground(DARK_RED);
kbdIndicator.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(DARK_RED),
BorderFactory.createEmptyBorder(2, 6, 2, 6)));
}
}
private void appendLog(String line) {
logArea.append(line + "\n");
logArea.setCaretPosition(logArea.getDocument().getLength());
}
private String formatModule(ModuleInstance m) {
return String.format("[%s] %s (%s) @ arch=%s",
m.getMetadata().getType(), m.getName(), m.getMetadata().getName(), m.getMetadata().getArch());
}
}
@@ -0,0 +1,217 @@
package com.cbe.gui.internal;
import com.cbe.core.PostCode;
import javax.swing.*;
import javax.swing.border.TitledBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class PostCodePanel extends JPanel implements com.cbe.loader.Engine.PostListener {
private final SevenSegmentDigit highDigit;
private final SevenSegmentDigit lowDigit;
private final JLabel descLabel;
private final Led[] leds = new Led[8];
private final Timer refreshTimer;
private static final Color DARK_BG = new Color(22, 22, 28);
private static final Color DARK_BORDER = new Color(50, 50, 60);
private static final Color DARK_ACCENT = new Color(80, 160, 255);
private static final Color DARK_GREEN = new Color(80, 220, 120);
public PostCodePanel() {
setLayout(new BorderLayout(4, 4));
setBackground(DARK_BG);
setBorder(BorderFactory.createTitledBorder(
BorderFactory.createLineBorder(DARK_BORDER), "POST (Power-On Self-Test)",
TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.DEFAULT_POSITION,
new Font(Font.MONOSPACED, Font.BOLD, 11), DARK_ACCENT));
JPanel digitsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 4, 4));
digitsPanel.setBackground(DARK_BG);
highDigit = new SevenSegmentDigit();
lowDigit = new SevenSegmentDigit();
digitsPanel.add(highDigit);
digitsPanel.add(lowDigit);
descLabel = new JLabel(" ");
descLabel.setFont(new Font(Font.MONOSPACED, Font.BOLD, 11));
descLabel.setForeground(DARK_GREEN);
descLabel.setBackground(DARK_BG);
descLabel.setOpaque(true);
descLabel.setBorder(BorderFactory.createEmptyBorder(2, 6, 2, 6));
descLabel.setPreferredSize(new Dimension(360, 22));
JPanel leftCol = new JPanel();
leftCol.setBackground(DARK_BG);
leftCol.setLayout(new BoxLayout(leftCol, BoxLayout.Y_AXIS));
leftCol.add(digitsPanel);
leftCol.add(descLabel);
add(leftCol, BorderLayout.CENTER);
JPanel ledsPanel = new JPanel(new GridLayout(2, 4, 3, 3));
ledsPanel.setBackground(DARK_BG);
ledsPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
String[] labels = {"PWR", "CPU", "MEM", "VID", "KBD", "SND", "DSK", "CLK"};
for (int i = 0; i < 8; i++) {
leds[i] = new Led(labels[i]);
ledsPanel.add(leds[i]);
}
add(ledsPanel, BorderLayout.EAST);
updateDisplay(0, 0, "Idle");
refreshTimer = new Timer(120, e -> repaint());
refreshTimer.start();
}
public void shutdown() {
refreshTimer.stop();
}
@Override
public void onPostChange(int code, int leds, String description) {
updateDisplay(code, leds, description);
}
private void updateDisplay(int code, int ledsMask, String description) {
int hi = (code >> 4) & 0x0F;
int lo = code & 0x0F;
highDigit.setValue(hi);
lowDigit.setValue(lo);
descLabel.setText(" 0x" + Integer.toHexString(code).toUpperCase() + " " + description);
for (int i = 0; i < 8; i++) {
leds[i].setOn((ledsMask & (1 << i)) != 0);
}
}
// ====== 7-segment digit ======
static class SevenSegmentDigit extends JPanel {
private int value = 0;
private static final Color ON = new Color(255, 100, 20);
private static final Color OFF = new Color(50, 20, 0);
public SevenSegmentDigit() {
setPreferredSize(new Dimension(44, 60));
setBackground(DARK_BG);
}
public void setValue(int v) { this.value = v & 0x0F; repaint(); }
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int w = getWidth();
int h = getHeight();
int pad = 4;
int segW = (w - 2 * pad) / 2;
int segH = (h - 3 * pad) / 4;
int cx = w / 2;
boolean[] segOn = segmentsFor(value);
drawHSeg(g2, cx, pad, segW, segH / 2, segOn[0]);
drawHSeg(g2, cx, h / 2 - segH / 4, segW, segH / 2, segOn[6]);
drawHSeg(g2, cx, h - pad - segH / 2, segW, segH / 2, segOn[3]);
drawVSeg(g2, cx - segW, pad + segH, segW / 2, segH, segOn[5]);
drawVSeg(g2, cx + segW / 2, pad + segH, segW / 2, segH, segOn[1]);
drawVSeg(g2, cx - segW, h / 2 + segH / 4, segW / 2, segH, segOn[4]);
drawVSeg(g2, cx + segW / 2, h / 2 + segH / 4, segW / 2, segH, segOn[2]);
g2.dispose();
}
private void drawHSeg(Graphics2D g2, int cx, int y, int w, int h, boolean on) {
int x = cx - w / 2;
Polygon p = new Polygon();
p.addPoint(x + 2, y);
p.addPoint(x + w - 2, y);
p.addPoint(x + w - 4, y + h);
p.addPoint(x + 2, y + h);
p.addPoint(x + 4, y + h / 2);
p.addPoint(x + 4, y + h / 2);
g2.setColor(on ? ON : OFF);
g2.fillPolygon(p);
}
private void drawVSeg(Graphics2D g2, int x, int y, int w, int h, boolean on) {
Polygon p = new Polygon();
p.addPoint(x, y);
p.addPoint(x + w, y + 1);
p.addPoint(x + w, y + h - 2);
p.addPoint(x, y + h);
p.addPoint(x + w / 2, y + h / 2);
g2.setColor(on ? ON : OFF);
g2.fillPolygon(p);
}
private boolean[] segmentsFor(int v) {
switch (v & 0x0F) {
case 0x0: return new boolean[]{true, true, true, true, true, true, false};
case 0x1: return new boolean[]{false, true, true, false, false, false, false};
case 0x2: return new boolean[]{true, true, false, true, true, false, true};
case 0x3: return new boolean[]{true, true, true, true, false, false, true};
case 0x4: return new boolean[]{false, true, true, false, false, true, true};
case 0x5: return new boolean[]{true, false, true, true, false, true, true};
case 0x6: return new boolean[]{true, false, true, true, true, true, true};
case 0x7: return new boolean[]{true, true, true, false, false, false, false};
case 0x8: return new boolean[]{true, true, true, true, true, true, true};
case 0x9: return new boolean[]{true, true, true, true, false, true, true};
case 0xA: return new boolean[]{true, true, true, false, true, true, true};
case 0xB: return new boolean[]{false, false, true, true, true, true, true};
case 0xC: return new boolean[]{true, false, false, true, true, true, false};
case 0xD: return new boolean[]{false, true, true, true, true, false, true};
case 0xE: return new boolean[]{true, false, false, true, true, true, true};
case 0xF: return new boolean[]{true, false, false, false, true, true, true};
default: return new boolean[7];
}
}
}
// ====== LED ======
static class Led extends JPanel {
private final String label;
private boolean on = false;
public Led(String label) {
this.label = label;
setPreferredSize(new Dimension(48, 36));
setBackground(DARK_BG);
}
public void setOn(boolean on) {
if (this.on != on) {
this.on = on;
repaint();
}
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
int size = Math.min(getWidth(), getHeight()) - 12;
int x = (getWidth() - size) / 2;
int y = 2;
Color c = on ? new Color(60, 255, 80) : new Color(30, 50, 30);
g2.setColor(c);
g2.fillOval(x, y, size, size);
if (on) {
g2.setColor(new Color(60, 255, 80, 60));
g2.fillOval(x - 2, y - 2, size + 4, size + 4);
}
g2.setColor(new Color(180, 180, 190));
g2.setFont(g2.getFont().deriveFont(9f));
FontMetrics fm = g2.getFontMetrics();
int tx = (getWidth() - fm.stringWidth(label)) / 2;
g2.drawString(label, tx, getHeight() - 2);
g2.dispose();
}
}
}
@@ -0,0 +1,135 @@
package com.cbe.gui.internal;
import com.cbe.loader.CompiledModuleLoader;
import com.cbe.loader.Engine;
import com.cbe.loader.SourceModuleLoader;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
public class ScreenPanel extends JPanel {
private static final int CHAR_W = 8;
private static final int CHAR_H = 14;
private static final int COLS = 80;
private static final int ROWS = 25;
private static final int SCREEN_W = COLS * CHAR_W;
private static final int SCREEN_H = ROWS * CHAR_H;
private static final int PADDING = 8;
private BufferedImage screenImage;
private Graphics2D screenG2d;
private byte[] lastVram;
private static final Color DARK_BG = new Color(18, 18, 24);
private static final Color DARK_FRAME_OUTER = new Color(50, 50, 65);
private static final Color DARK_FRAME_INNER = new Color(25, 25, 35);
private static final Color DARK_SCREEN_BG = new Color(10, 10, 18);
private static final Color DARK_GREEN = new Color(140, 230, 140);
private static final Color DARK_CURSOR = new Color(140, 230, 140, 70);
private static final Color DARK_SCANLINE = new Color(0, 0, 0, 25);
private static final Color DARK_PLACEHOLDER = new Color(130, 130, 140);
public ScreenPanel() {
setBackground(DARK_BG);
setDoubleBuffered(true);
screenImage = new BufferedImage(SCREEN_W, SCREEN_H, BufferedImage.TYPE_INT_RGB);
screenG2d = screenImage.createGraphics();
screenG2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
screenG2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
}
public void refresh(Engine engine) {
byte[] vram = null;
int cursorX = 0, cursorY = 0;
if (engine.getSourceGpu() != null) {
SourceModuleLoader.GpuModuleInstance g = engine.getSourceGpu();
vram = g.getVram();
cursorX = g.getCursorX();
cursorY = g.getCursorY();
} else if (engine.getCompiledGpu() != null) {
CompiledModuleLoader.CompiledGpuInstance g = engine.getCompiledGpu();
vram = g.getVram();
cursorX = g.getCursorX();
cursorY = g.getCursorY();
}
if (vram == null) {
screenG2d.setColor(DARK_SCREEN_BG);
screenG2d.fillRect(0, 0, SCREEN_W, SCREEN_H);
screenG2d.setColor(DARK_PLACEHOLDER);
screenG2d.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
String msg = "(no GPU module loaded)";
int w = screenG2d.getFontMetrics().stringWidth(msg);
screenG2d.drawString(msg, (SCREEN_W - w) / 2, SCREEN_H / 2);
repaint();
return;
}
if (vram == lastVram) {
}
lastVram = vram;
screenG2d.setColor(DARK_SCREEN_BG);
screenG2d.fillRect(0, 0, SCREEN_W, SCREEN_H);
screenG2d.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 13));
screenG2d.setColor(DARK_GREEN);
for (int row = 0; row < ROWS; row++) {
for (int col = 0; col < COLS; col++) {
int offset = row * COLS + col;
int b = offset < vram.length ? vram[offset] & 0xFF : 0;
char c = (char) (b & 0x7F);
if (c == 0) c = ' ';
int x = col * CHAR_W;
int y = row * CHAR_H + CHAR_H - 2;
screenG2d.drawString(String.valueOf(c), x, y);
}
}
if ((System.currentTimeMillis() / 500) % 2 == 0) {
int cx = cursorX * CHAR_W;
int cy = cursorY * CHAR_H;
screenG2d.setColor(DARK_CURSOR);
screenG2d.fillRect(cx, cy, CHAR_W, CHAR_H);
}
repaint();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
int pw = getWidth();
int ph = getHeight();
g2.setColor(DARK_FRAME_OUTER);
g2.fillRoundRect(0, 0, pw, ph, 12, 12);
g2.setColor(DARK_FRAME_INNER);
g2.fillRoundRect(4, 4, pw - 8, ph - 8, 8, 8);
double scale = Math.min((double) (pw - 16) / SCREEN_W, (double) (ph - 16) / SCREEN_H);
int sw = (int) (SCREEN_W * scale);
int sh = (int) (SCREEN_H * scale);
int sx = (pw - sw) / 2;
int sy = (ph - sh) / 2;
g2.drawImage(screenImage, sx, sy, sw, sh, null);
g2.setColor(DARK_SCANLINE);
for (int y = sy; y < sy + sh; y += 2) {
g2.drawLine(sx, y, sx + sw, y);
}
g2.dispose();
}
}