New splash screen. Debugger. Starting to debug the debugger.

This commit is contained in:
Scott Duensing 2026-04-05 19:01:44 -05:00
parent 626befa664
commit de7027c44e
16 changed files with 1512 additions and 146 deletions

View file

@ -41,6 +41,23 @@ uint16_t basAddConstant(BasCodeGenT *cg, const char *text, int32_t len) {
}
// ============================================================
// basCodeGenAddDebugVar
// ============================================================
void basCodeGenAddDebugVar(BasCodeGenT *cg, const char *name, uint8_t scope, uint8_t dataType, int32_t index, int32_t procIndex) {
BasDebugVarT dv;
memset(&dv, 0, sizeof(dv));
snprintf(dv.name, BAS_MAX_PROC_NAME, "%s", name);
dv.scope = scope;
dv.dataType = dataType;
dv.index = index;
dv.procIndex = procIndex;
arrput(cg->debugVars, dv);
cg->debugVarCount = (int32_t)arrlen(cg->debugVars);
}
// ============================================================
// basCodeGenBuildModule
// ============================================================
@ -111,6 +128,17 @@ BasModuleT *basCodeGenBuildModule(BasCodeGenT *cg) {
mod->formVarInfoCount = cg->formVarInfoCount;
}
// Copy debug variable info
if (cg->debugVarCount > 0) {
mod->debugVars = (BasDebugVarT *)malloc(cg->debugVarCount * sizeof(BasDebugVarT));
if (mod->debugVars) {
memcpy(mod->debugVars, cg->debugVars, cg->debugVarCount * sizeof(BasDebugVarT));
}
mod->debugVarCount = cg->debugVarCount;
}
return mod;
}
@ -160,6 +188,7 @@ BasModuleT *basCodeGenBuildModuleWithProcs(BasCodeGenT *cg, void *symtab) {
p->name[BAS_MAX_PROC_NAME - 1] = '\0';
p->codeAddr = s->codeAddr;
p->paramCount = s->paramCount;
p->localCount = s->localCount;
p->returnType = s->dataType;
p->isFunction = (s->kind == SYM_FUNCTION);
}
@ -187,14 +216,17 @@ void basCodeGenFree(BasCodeGenT *cg) {
arrfree(cg->constants);
arrfree(cg->dataPool);
arrfree(cg->formVarInfo);
arrfree(cg->debugVars);
cg->code = NULL;
cg->constants = NULL;
cg->dataPool = NULL;
cg->formVarInfo = NULL;
cg->debugVars = NULL;
cg->constCount = 0;
cg->dataCount = 0;
cg->codeLen = 0;
cg->formVarInfoCount = 0;
cg->debugVarCount = 0;
}
@ -332,6 +364,7 @@ void basModuleFree(BasModuleT *mod) {
free(mod->procs);
free(mod->formVarInfo);
free(mod->debugVars);
free(mod);
}

View file

@ -29,6 +29,9 @@ typedef struct {
int32_t dataCount;
BasFormVarInfoT *formVarInfo; // stb_ds dynamic array
int32_t formVarInfoCount;
BasDebugVarT *debugVars; // stb_ds dynamic array
int32_t debugVarCount;
int32_t debugProcCount; // incremented per SUB/FUNCTION for debug var tracking
} BasCodeGenT;
// ============================================================
@ -76,6 +79,9 @@ BasModuleT *basCodeGenBuildModuleWithProcs(BasCodeGenT *cg, void *symtab);
// Free a module built by basCodeGenBuildModule.
void basModuleFree(BasModuleT *mod);
// Add a debug variable entry (for debugger variable display).
void basCodeGenAddDebugVar(BasCodeGenT *cg, const char *name, uint8_t scope, uint8_t dataType, int32_t index, int32_t procIndex);
// Find a procedure by name in a module's procedure table.
// Case-insensitive. Returns NULL if not found.
const BasProcEntryT *basModuleFindProc(const BasModuleT *mod, const char *name);

View file

@ -342,6 +342,9 @@
#define OP_INI_READ 0xE0 // pop default, pop key, pop section, pop file, push string
#define OP_INI_WRITE 0xE1 // pop value, pop key, pop section, pop file
// Debug
#define OP_LINE 0xE2 // [uint16 lineNum] set current source line for debugger
// ============================================================
// Halt
// ============================================================

View file

@ -575,6 +575,43 @@ static uint8_t resolveTypeName(BasParserT *p) {
}
// Snapshot local variables for the debugger before leaving local scope.
// procIndex is the index into the proc table for the current procedure.
// Also saves the local count on the proc symbol for BasProcEntryT.
static void collectDebugLocals(BasParserT *p, int32_t procIndex) {
// Save localCount on the proc symbol
if (p->currentProc[0]) {
BasSymbolT *procSym = basSymTabFind(&p->sym, p->currentProc);
if (procSym) {
procSym->localCount = p->sym.nextLocalIdx;
}
}
for (int32_t i = 0; i < p->sym.count; i++) {
BasSymbolT *s = &p->sym.symbols[i];
if (s->scope == SCOPE_LOCAL && s->kind == SYM_VARIABLE) {
basCodeGenAddDebugVar(&p->cg, s->name, SCOPE_LOCAL, s->dataType, s->index, procIndex);
}
}
}
// Snapshot global variables for the debugger at the end of compilation.
static void collectDebugGlobals(BasParserT *p) {
for (int32_t i = 0; i < p->sym.count; i++) {
BasSymbolT *s = &p->sym.symbols[i];
if (s->scope == SCOPE_GLOBAL && s->kind == SYM_VARIABLE) {
basCodeGenAddDebugVar(&p->cg, s->name, SCOPE_GLOBAL, s->dataType, s->index, -1);
} else if (s->scope == SCOPE_FORM && s->kind == SYM_VARIABLE) {
basCodeGenAddDebugVar(&p->cg, s->name, SCOPE_FORM, s->dataType, s->index, -1);
}
}
}
static void skipNewlines(BasParserT *p) {
while (!p->hasError && check(p, TOK_NEWLINE)) {
advance(p);
@ -1120,17 +1157,17 @@ static void parsePrimary(BasParserT *p) {
advance(p);
expect(p, TOK_DOT);
if (checkIdent(p, "Path")) {
if (checkKeyword(p,"Path")) {
advance(p);
basEmit8(&p->cg, OP_APP_PATH);
} else if (checkIdent(p, "Config")) {
} else if (checkKeyword(p,"Config")) {
advance(p);
basEmit8(&p->cg, OP_APP_CONFIG);
} else if (checkIdent(p, "Data")) {
} else if (checkKeyword(p,"Data")) {
advance(p);
basEmit8(&p->cg, OP_APP_DATA);
} else {
parserError(p, "Expected 'Path', 'Config', or 'Data' after 'App.'");
error(p, "Expected 'Path', 'Config', or 'Data' after 'App.'");
}
return;
@ -2710,6 +2747,7 @@ static void parseDef(BasParserT *p) {
parseExpression(p);
basEmit8(&p->cg, OP_RET_VAL);
collectDebugLocals(p, p->cg.debugProcCount++);
basSymTabLeaveLocal(&p->sym);
uint8_t returnType = suffixToType(name);
@ -3338,6 +3376,7 @@ static void parseFunction(BasParserT *p) {
basEmit8(&p->cg, OP_RET_VAL);
// Leave local scope
collectDebugLocals(p, p->cg.debugProcCount++);
basSymTabLeaveLocal(&p->sym);
p->currentProc[0] = '\0';
@ -4553,6 +4592,10 @@ static void parseStatement(BasParserT *p) {
return;
}
// Emit source line number for debugger (before statement code)
basEmit8(&p->cg, OP_LINE);
basEmitU16(&p->cg, (uint16_t)p->lex.token.line);
BasTokenTypeE tt = p->lex.token.type;
switch (tt) {
@ -5233,6 +5276,7 @@ static void parseSub(BasParserT *p) {
basEmit8(&p->cg, OP_RET);
// Leave local scope
collectDebugLocals(p, p->cg.debugProcCount++);
basSymTabLeaveLocal(&p->sym);
p->currentProc[0] = '\0';
@ -5581,6 +5625,9 @@ BasModuleT *basParserBuildModule(BasParserT *p) {
return NULL;
}
// Collect global and form-scope variables for the debugger
collectDebugGlobals(p);
p->cg.globalCount = p->sym.nextGlobalIdx;
return basCodeGenBuildModuleWithProcs(&p->cg, &p->sym);
}

View file

@ -58,6 +58,7 @@ typedef struct {
uint8_t dataType; // BAS_TYPE_* for variables/functions
int32_t index; // slot index (local or global)
int32_t codeAddr; // PC address for SUB/FUNCTION/LABEL
int32_t localCount; // number of local variables (for SUB/FUNCTION, set on leave)
bool isDefined; // false = forward-declared
bool isArray;
bool isShared;

File diff suppressed because it is too large Load diff

View file

@ -61,6 +61,15 @@ static bool runSubLoop(BasVmT *vm, int32_t savedPc, int32_t savedCallDepth, bool
break;
}
if (result == BAS_VM_BREAKPOINT) {
// Pause the VM so the host's debug loop can take over.
// Don't restore savedPc/savedRunning — the VM stays at
// the breakpoint location. The host will resume execution
// which will eventually return here and complete the sub.
vm->running = false;
return true;
}
if (result != BAS_VM_OK) {
vm->pc = savedPc;
vm->callDepth = savedCallDepth;
@ -223,7 +232,10 @@ BasVmT *basVmCreate(void) {
return NULL;
}
vm->printFn = defaultPrint;
vm->printFn = defaultPrint;
vm->stepOverDepth = -1;
vm->stepOutDepth = -1;
vm->runToCursorLine = -1;
basStringSystemInit();
return vm;
}
@ -282,6 +294,53 @@ const char *basVmGetError(const BasVmT *vm) {
}
// ============================================================
// Debugger API
// ============================================================
void basVmSetBreakpoints(BasVmT *vm, int32_t *lines, int32_t count) {
if (!vm) {
return;
}
vm->breakpoints = lines;
vm->breakpointCount = count;
}
void basVmStepInto(BasVmT *vm) {
if (vm) {
vm->debugBreak = true;
}
}
void basVmStepOver(BasVmT *vm) {
if (vm) {
vm->stepOverDepth = vm->callDepth;
}
}
void basVmStepOut(BasVmT *vm) {
if (vm) {
vm->stepOutDepth = vm->callDepth;
}
}
void basVmRunToCursor(BasVmT *vm, int32_t line) {
if (vm) {
vm->runToCursorLine = line;
}
}
int32_t basVmGetCurrentLine(const BasVmT *vm) {
return vm ? vm->currentLine : 0;
}
// ============================================================
// basVmLoadModule
// ============================================================
@ -3271,6 +3330,44 @@ BasVmResultE basVmStep(BasVmT *vm) {
break;
}
case OP_LINE: {
uint16_t lineNum = readUint16(vm);
vm->currentLine = lineNum;
// Step into: break at any OP_LINE
if (vm->debugBreak) {
vm->debugBreak = false;
return BAS_VM_BREAKPOINT;
}
// Step over: break when call depth returns to target level
if (vm->stepOverDepth >= 0 && vm->callDepth <= vm->stepOverDepth) {
vm->stepOverDepth = -1;
return BAS_VM_BREAKPOINT;
}
// Step out: break when call depth drops below target
if (vm->stepOutDepth >= 0 && vm->callDepth < vm->stepOutDepth) {
vm->stepOutDepth = -1;
return BAS_VM_BREAKPOINT;
}
// Run to cursor
if (vm->runToCursorLine >= 0 && (int32_t)lineNum == vm->runToCursorLine) {
vm->runToCursorLine = -1;
return BAS_VM_BREAKPOINT;
}
// Breakpoint check (linear scan, typically < 20 entries)
for (int32_t i = 0; i < vm->breakpointCount; i++) {
if (vm->breakpoints[i] == (int32_t)lineNum) {
return BAS_VM_BREAKPOINT;
}
}
break;
}
case OP_APP_PATH: {
push(vm, basValStringFromC(vm->appPath));
break;
@ -4943,5 +5040,10 @@ static uint16_t readUint16(BasVmT *vm) {
static void runtimeError(BasVmT *vm, int32_t errNum, const char *msg) {
vm->errorNumber = errNum;
snprintf(vm->errorMsg, sizeof(vm->errorMsg), "Runtime error %d at PC %d: %s", (int)errNum, (int)vm->pc, msg);
if (vm->currentLine > 0) {
snprintf(vm->errorMsg, sizeof(vm->errorMsg), "Runtime error %d on line %d: %s", (int)errNum, (int)vm->currentLine, msg);
} else {
snprintf(vm->errorMsg, sizeof(vm->errorMsg), "Runtime error %d at PC %d: %s", (int)errNum, (int)vm->pc, msg);
}
}

View file

@ -50,7 +50,8 @@ typedef enum {
BAS_VM_FILE_ERROR,
BAS_VM_SUBSCRIPT_RANGE,
BAS_VM_USER_ERROR, // ON ERROR raised
BAS_VM_STEP_LIMIT // step limit reached (not an error)
BAS_VM_STEP_LIMIT, // step limit reached (not an error)
BAS_VM_BREAKPOINT // hit breakpoint or step completed (not an error)
} BasVmResultE;
// ============================================================
@ -243,10 +244,20 @@ typedef struct {
char name[BAS_MAX_PROC_NAME]; // SUB/FUNCTION name (case-preserved)
int32_t codeAddr; // entry point in code[]
int32_t paramCount; // number of parameters
int32_t localCount; // number of local variables (for debugger)
uint8_t returnType; // BAS_TYPE_* (0 for SUB)
bool isFunction; // true = FUNCTION, false = SUB
} BasProcEntryT;
// Debug variable info (preserved in module for debugger display)
typedef struct {
char name[BAS_MAX_PROC_NAME];
uint8_t scope; // SCOPE_GLOBAL, SCOPE_LOCAL, SCOPE_FORM
uint8_t dataType; // BAS_TYPE_*
int32_t index; // variable slot index
int32_t procIndex; // -1 for globals, else index into procs[]
} BasDebugVarT;
// ============================================================
// Per-form variable info (how many form-scoped vars each form needs)
// ============================================================
@ -275,6 +286,8 @@ typedef struct {
int32_t procCount;
BasFormVarInfoT *formVarInfo; // per-form variable counts
int32_t formVarInfoCount;
BasDebugVarT *debugVars; // variable names for debugger
int32_t debugVarCount;
} BasModuleT;
// ============================================================
@ -292,6 +305,15 @@ typedef struct {
bool yielded;
int32_t stepLimit; // max steps per basVmRun (0 = unlimited)
int32_t stepCount; // steps executed in last basVmRun
int32_t currentLine; // source line from last OP_LINE (debugger)
// Debug state
int32_t *breakpoints; // sorted line numbers (host-managed)
int32_t breakpointCount;
bool debugBreak; // break at next OP_LINE (step into)
int32_t stepOverDepth; // call depth target for step over (-1 = off)
int32_t stepOutDepth; // call depth target for step out (-1 = off)
int32_t runToCursorLine; // target line for run-to-cursor (-1 = off)
// Evaluation stack
BasValueT stack[BAS_VM_STACK_SIZE];
@ -407,6 +429,26 @@ bool basVmPop(BasVmT *vm, BasValueT *val);
// Get the current error message.
const char *basVmGetError(const BasVmT *vm);
// ---- Debugger API ----
// Set the breakpoint list (sorted array of source line numbers, host-owned).
void basVmSetBreakpoints(BasVmT *vm, int32_t *lines, int32_t count);
// Step into: break at the next OP_LINE instruction.
void basVmStepInto(BasVmT *vm);
// Step over: break when call depth returns to current level.
void basVmStepOver(BasVmT *vm);
// Step out: break when call depth drops below current level.
void basVmStepOut(BasVmT *vm);
// Run to cursor: break when reaching the specified source line.
void basVmRunToCursor(BasVmT *vm, int32_t line);
// Get the current source line (from the last OP_LINE instruction).
int32_t basVmGetCurrentLine(const BasVmT *vm);
// Call a SUB by code address from the host.
// Pushes a call frame, runs until the SUB returns, then restores
// the previous execution state. Returns true if the SUB was called

BIN
assets/splash.bmp (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -312,6 +312,23 @@ void platformInstallCrashHandler(jmp_buf *recoveryBuf, volatile int *crashSignal
// also available for manual invocation if needed.
void platformLogCrashDetail(int sig, PlatformLogFnT logFn);
// ============================================================
// VGA splash screen (mode 13h, 320x200, 256-color)
// ============================================================
// Enter VGA mode 13h (320x200x256).
void platformSplashInit(void);
// Return to text mode 03h.
void platformSplashShutdown(void);
// Load and display a raw splash file (768 bytes palette + 64000 bytes pixels).
// Returns true on success, false if the file could not be loaded.
bool platformSplashLoadRaw(const char *path);
// Fill a rectangle on the VGA mode 13h screen.
void platformSplashFillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t color);
// ============================================================
// DXE symbol overrides
// ============================================================

View file

@ -2761,3 +2761,83 @@ DXE_EXPORT_END
void platformRegisterDxeExports(void) {
dlregsym(sDxeExportTable);
}
// ============================================================
// VGA splash screen (mode 13h)
// ============================================================
#define SPLASH_VGA_W 320
#define SPLASH_VGA_H 200
#define SPLASH_VGA_ADDR 0xA0000
void platformSplashInit(void) {
__dpmi_regs r;
memset(&r, 0, sizeof(r));
r.x.ax = 0x0013;
__dpmi_int(0x10, &r);
}
void platformSplashShutdown(void) {
__dpmi_regs r;
memset(&r, 0, sizeof(r));
r.x.ax = 0x0003;
__dpmi_int(0x10, &r);
}
void platformSplashFillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t color) {
for (int32_t row = y; row < y + h; row++) {
if (row < 0 || row >= SPLASH_VGA_H) {
continue;
}
int32_t x0 = x < 0 ? 0 : x;
int32_t x1 = (x + w) > SPLASH_VGA_W ? SPLASH_VGA_W : (x + w);
for (int32_t col = x0; col < x1; col++) {
_farpokeb(_dos_ds, SPLASH_VGA_ADDR + row * SPLASH_VGA_W + col, color);
}
}
}
bool platformSplashLoadRaw(const char *path) {
FILE *fp = fopen(path, "rb");
if (!fp) {
return false;
}
// Read and set palette (768 bytes: 256 x RGB, 6-bit VGA values)
uint8_t pal[768];
if (fread(pal, 1, 768, fp) != 768) {
fclose(fp);
return false;
}
outportb(0x3C8, 0);
for (int32_t i = 0; i < 768; i++) {
outportb(0x3C9, pal[i]);
}
// Read pixels directly into VGA memory (64000 bytes: 320x200)
uint8_t rowBuf[SPLASH_VGA_W];
for (int32_t y = 0; y < SPLASH_VGA_H; y++) {
if (fread(rowBuf, 1, SPLASH_VGA_W, fp) != SPLASH_VGA_W) {
fclose(fp);
return false;
}
for (int32_t x = 0; x < SPLASH_VGA_W; x++) {
_farpokeb(_dos_ds, SPLASH_VGA_ADDR + y * SPLASH_VGA_W + x, rowBuf[x]);
}
}
fclose(fp);
return true;
}

View file

@ -15,14 +15,11 @@
#include <ctype.h>
#include <dirent.h>
#include <dlfcn.h>
#include <dpmi.h>
#include <go32.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/farptr.h>
#include <sys/stat.h>
// Route stb_ds allocations through the tracking wrappers so that
@ -43,137 +40,30 @@ extern void dvxFree(void *ptr);
#define LOG_PATH "dvx.log"
// ============================================================
// VGA mode 13h splash screen (320x200, 256-color)
// Splash screen (delegates to platformSplash* in dvxPlatformDos.c)
// ============================================================
#define VGA_WIDTH 320
#define VGA_HEIGHT 200
#define VGA_ADDR 0xA0000
#define VGA_SIZE (VGA_WIDTH * VGA_HEIGHT)
// Palette indices for splash colors
#define SPLASH_BG 0 // black
#define SPLASH_FG 15 // white
#define SPLASH_BAR_BG 8 // dark gray
#define SPLASH_BAR_FG 11 // cyan
#define SPLASH_DIM 7 // light gray
// Palette indices for progress bar (indices into SPLASH.RAW palette)
#define SPLASH_BAR_BG 50 // dark gray (RGB 68,68,68)
#define SPLASH_BAR_FG 135 // light gray (RGB 168,168,168)
#define SPLASH_BAR_OUT 45 // darker gray (RGB 60,60,60)
static int32_t sSplashActive = 0;
static int32_t sSplashTotal = 0;
static int32_t sSplashLoaded = 0;
// Progress bar geometry
#define PBAR_X 60
#define PBAR_Y 140
#define PBAR_W 200
#define PBAR_H 10
static void splashSetMode13h(void) {
__dpmi_regs r;
memset(&r, 0, sizeof(r));
r.x.ax = 0x0013;
__dpmi_int(0x10, &r);
sSplashActive = 1;
}
static void splashRestoreTextMode(void) {
__dpmi_regs r;
memset(&r, 0, sizeof(r));
r.x.ax = 0x0003;
__dpmi_int(0x10, &r);
sSplashActive = 0;
}
static void splashPutPixel(int32_t x, int32_t y, uint8_t color) {
if (x >= 0 && x < VGA_WIDTH && y >= 0 && y < VGA_HEIGHT) {
_farpokeb(_dos_ds, VGA_ADDR + y * VGA_WIDTH + x, color);
}
}
static void splashFillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t color) {
for (int32_t row = y; row < y + h; row++) {
if (row < 0 || row >= VGA_HEIGHT) {
continue;
}
int32_t x0 = x < 0 ? 0 : x;
int32_t x1 = (x + w) > VGA_WIDTH ? VGA_WIDTH : (x + w);
for (int32_t col = x0; col < x1; col++) {
_farpokeb(_dos_ds, VGA_ADDR + row * VGA_WIDTH + col, color);
}
}
}
// Get the VGA ROM 8x8 font address via INT 10h AH=11h BH=03h
static uint32_t splashGetFontAddr(void) {
__dpmi_regs r;
memset(&r, 0, sizeof(r));
r.x.ax = 0x1130;
r.h.bh = 0x03; // 8x8 ROM font
__dpmi_int(0x10, &r);
return (uint32_t)r.x.es * 16 + r.x.bp;
}
static uint32_t sFontAddr = 0;
static void splashDrawChar(int32_t x, int32_t y, char ch, uint8_t color) {
if (!sFontAddr) {
sFontAddr = splashGetFontAddr();
}
uint32_t charAddr = sFontAddr + (uint8_t)ch * 8;
for (int32_t row = 0; row < 8; row++) {
uint8_t bits = _farpeekb(_dos_ds, charAddr + row);
for (int32_t col = 0; col < 8; col++) {
if (bits & (0x80 >> col)) {
splashPutPixel(x + col, y + row, color);
}
}
}
}
static void splashDrawText(int32_t x, int32_t y, const char *text, uint8_t color) {
for (const char *c = text; *c; c++) {
splashDrawChar(x, y, *c, color);
x += 8;
}
}
static void splashDrawTextCentered(int32_t y, const char *text, uint8_t color) {
int32_t len = (int32_t)strlen(text);
int32_t x = (VGA_WIDTH - len * 8) / 2;
splashDrawText(x, y, text, color);
}
#define PBAR_X 10
#define PBAR_Y 188
#define PBAR_W 300
#define PBAR_H 6
static void splashDrawScreen(void) {
// Clear to black
splashFillRect(0, 0, VGA_WIDTH, VGA_HEIGHT, SPLASH_BG);
// Title
splashDrawTextCentered(60, "DOS Visual eXecutive", SPLASH_FG);
splashDrawTextCentered(75, "(DVX)", SPLASH_DIM);
// Copyright
splashDrawTextCentered(100, "Copyright 2026 Scott Duensing", SPLASH_DIM);
// Loading text
splashDrawTextCentered(125, "Loading...", SPLASH_FG);
// Progress bar outline
splashFillRect(PBAR_X - 1, PBAR_Y - 1, PBAR_W + 2, PBAR_H + 2, SPLASH_DIM);
splashFillRect(PBAR_X, PBAR_Y, PBAR_W, PBAR_H, SPLASH_BAR_BG);
if (platformSplashLoadRaw("CONFIG/SPLASH.RAW")) {
platformSplashFillRect(PBAR_X - 1, PBAR_Y - 1, PBAR_W + 2, PBAR_H + 2, SPLASH_BAR_OUT);
platformSplashFillRect(PBAR_X, PBAR_Y, PBAR_W, PBAR_H, SPLASH_BAR_BG);
}
}
@ -188,7 +78,7 @@ static void splashUpdateProgress(void) {
fillW = PBAR_W;
}
splashFillRect(PBAR_X, PBAR_Y, fillW, PBAR_H, SPLASH_BAR_FG);
platformSplashFillRect(PBAR_X, PBAR_Y, fillW, PBAR_H, SPLASH_BAR_FG);
}
// ============================================================
@ -437,7 +327,8 @@ static void loadInOrder(ModuleT *mods) {
if (!mods[i].handle) {
const char *err = dlerror();
dvxLog(" FAILED: %s", err ? err : "(unknown)");
splashRestoreTextMode();
platformSplashShutdown();
sSplashActive = 0;
fprintf(stderr, "FATAL: Failed to load %s\n %s\n", mods[i].path, err ? err : "(unknown error)");
exit(1);
}
@ -623,7 +514,8 @@ int main(int argc, char *argv[]) {
platformInit();
// Switch to VGA mode 13h and show graphical splash
splashSetMode13h();
platformSplashInit();
sSplashActive = 1;
splashDrawScreen();
dvxLog("DVX Loader starting...");

View file

@ -10,7 +10,9 @@ BINDIR = ../bin
.PHONY: all clean
all: $(BINDIR)/dvxres $(BINDIR)/mkicon $(BINDIR)/mktbicon $(BINDIR)/mkwgticon
CONFIGDIR = ../bin/config
all: $(BINDIR)/dvxres $(BINDIR)/mkicon $(BINDIR)/mktbicon $(BINDIR)/mkwgticon $(BINDIR)/bmp2raw $(CONFIGDIR)/SPLASH.RAW
$(BINDIR)/dvxres: dvxres.c ../core/dvxResource.c ../core/dvxResource.h | $(BINDIR)
$(CC) $(CFLAGS) -o $@ dvxres.c ../core/dvxResource.c
@ -24,8 +26,17 @@ $(BINDIR)/mktbicon: mktbicon.c | $(BINDIR)
$(BINDIR)/mkwgticon: mkwgticon.c | $(BINDIR)
$(CC) $(CFLAGS) -o $@ mkwgticon.c
$(BINDIR)/bmp2raw: bmp2raw.c | $(BINDIR)
$(CC) $(CFLAGS) -o $@ bmp2raw.c
$(CONFIGDIR)/SPLASH.RAW: $(BINDIR)/bmp2raw ../assets/splash.bmp | $(CONFIGDIR)
$(BINDIR)/bmp2raw ../assets/splash.bmp $@
$(BINDIR):
mkdir -p $(BINDIR)
$(CONFIGDIR):
mkdir -p $(CONFIGDIR)
clean:
rm -f $(BINDIR)/dvxres

128
tools/bmp2raw.c Normal file
View file

@ -0,0 +1,128 @@
// bmp2raw.c -- Convert a 320x200x256 BMP to raw VGA splash format
//
// Output format:
// 768 bytes: palette (256 x 3 bytes RGB, 6-bit VGA values)
// 64000 bytes: pixels (320x200, top-to-bottom scanline order)
//
// Total output: 64768 bytes
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define VGA_W 320
#define VGA_H 200
#define PALETTE_ENTRIES 256
#define PIXEL_COUNT (VGA_W * VGA_H)
int main(int argc, char **argv) {
if (argc != 3) {
fprintf(stderr, "Usage: bmp2raw <input.bmp> <output.raw>\n");
return 1;
}
FILE *fp = fopen(argv[1], "rb");
if (!fp) {
fprintf(stderr, "Cannot open %s\n", argv[1]);
return 1;
}
// Read BMP header
uint8_t hdr[54];
if (fread(hdr, 1, 54, fp) != 54) {
fprintf(stderr, "Bad BMP header\n");
fclose(fp);
return 1;
}
if (hdr[0] != 'B' || hdr[1] != 'M') {
fprintf(stderr, "Not a BMP file\n");
fclose(fp);
return 1;
}
uint32_t pixelOffset = hdr[10] | (hdr[11] << 8) | (hdr[12] << 16) | (hdr[13] << 24);
int32_t width = hdr[18] | (hdr[19] << 8) | (hdr[20] << 16) | (hdr[21] << 24);
int32_t height = hdr[22] | (hdr[23] << 8) | (hdr[24] << 16) | (hdr[25] << 24);
uint16_t bpp = hdr[28] | (hdr[29] << 8);
if (width != VGA_W || abs(height) != VGA_H || bpp != 8) {
fprintf(stderr, "BMP must be %dx%d 8bpp (got %dx%d %dbpp)\n", VGA_W, VGA_H, width, abs(height), bpp);
fclose(fp);
return 1;
}
bool topDown = (height < 0);
if (height < 0) {
height = -height;
}
// Read palette (starts at offset 54 for BITMAPINFOHEADER, may vary)
uint32_t dibSize = hdr[14] | (hdr[15] << 8) | (hdr[16] << 16) | (hdr[17] << 24);
uint32_t palOffset = 14 + dibSize; // BMP file header (14) + DIB header
fseek(fp, palOffset, SEEK_SET);
uint8_t bmpPal[PALETTE_ENTRIES * 4];
if (fread(bmpPal, 4, PALETTE_ENTRIES, fp) != PALETTE_ENTRIES) {
fprintf(stderr, "Cannot read palette\n");
fclose(fp);
return 1;
}
// Convert palette: BMP is BGRA 8-bit, VGA is RGB 6-bit
uint8_t vgaPal[PALETTE_ENTRIES * 3];
for (int i = 0; i < PALETTE_ENTRIES; i++) {
vgaPal[i * 3 + 0] = bmpPal[i * 4 + 2] >> 2; // R
vgaPal[i * 3 + 1] = bmpPal[i * 4 + 1] >> 2; // G
vgaPal[i * 3 + 2] = bmpPal[i * 4 + 0] >> 2; // B
}
// Read pixel data
fseek(fp, pixelOffset, SEEK_SET);
// BMP rows are padded to 4-byte boundaries (320 is already aligned)
uint8_t pixels[PIXEL_COUNT];
if (topDown) {
// Already top-to-bottom
if (fread(pixels, 1, PIXEL_COUNT, fp) != PIXEL_COUNT) {
fprintf(stderr, "Cannot read pixel data\n");
fclose(fp);
return 1;
}
} else {
// Bottom-to-top — flip
for (int y = VGA_H - 1; y >= 0; y--) {
if (fread(pixels + y * VGA_W, 1, VGA_W, fp) != VGA_W) {
fprintf(stderr, "Cannot read row %d\n", VGA_H - 1 - y);
fclose(fp);
return 1;
}
}
}
fclose(fp);
// Write output
FILE *out = fopen(argv[2], "wb");
if (!out) {
fprintf(stderr, "Cannot create %s\n", argv[2]);
return 1;
}
fwrite(vgaPal, 1, sizeof(vgaPal), out);
fwrite(pixels, 1, PIXEL_COUNT, out);
fclose(out);
printf("Converted %s -> %s (%d bytes)\n", argv[1], argv[2], (int)(sizeof(vgaPal) + PIXEL_COUNT));
return 0;
}

View file

@ -113,6 +113,15 @@ typedef struct {
// The callback fills colors[0..lineLen-1].
void (*colorize)(const char *line, int32_t lineLen, uint8_t *colors, void *ctx);
void *colorizeCtx;
// Line decorator callback (optional). Called for each visible line during paint.
// Returns background color override (0 = use default). Sets *gutterColor to a
// non-zero color to draw a filled circle in the gutter for breakpoints.
uint32_t (*lineDecorator)(int32_t lineNum, uint32_t *gutterColor, void *ctx);
void *lineDecoratorCtx;
// Gutter click callback (optional). Fired when user clicks in the gutter.
void (*onGutterClick)(WidgetT *w, int32_t lineNum);
} TextAreaDataT;
#include <ctype.h>
@ -1580,6 +1589,19 @@ void widgetTextAreaOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
return;
}
// Click on gutter — toggle breakpoint
if (gutterW > 0 && vx < innerX && ta->onGutterClick) {
int32_t relY = vy - innerY;
int32_t clickRow = ta->scrollRow + relY / font->charHeight;
if (clickRow >= 0 && clickRow < totalLines) {
ta->onGutterClick(w, clickRow + 1); // 1-based line number
}
wgtInvalidatePaint(w);
return;
}
// Click on text area -- place cursor
int32_t relX = vx - innerX;
int32_t relY = vy - innerY;
@ -1792,14 +1814,44 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
}
int32_t drawY = textY + i * font->charHeight;
// Line decorator: background highlight and gutter indicators
uint32_t lineBg = bg;
uint32_t gutterColor = 0;
if (ta->lineDecorator) {
uint32_t decBg = ta->lineDecorator(row + 1, &gutterColor, ta->lineDecoratorCtx);
if (decBg) {
lineBg = decBg;
rectFill(d, ops, textX, drawY, innerW, font->charHeight, lineBg);
}
}
// Draw line number in gutter
if (gutterW > 0) {
// Gutter indicator (breakpoint dot)
if (gutterColor) {
int32_t dotR = font->charHeight / 4;
int32_t dotX = gutterX + 2 + dotR;
int32_t dotY = drawY + font->charHeight / 2;
for (int32_t dy = -dotR; dy <= dotR; dy++) {
int32_t hw = dotR - (dy < 0 ? -dy : dy);
drawHLine(d, ops, dotX - hw, dotY + dy, hw * 2 + 1, gutterColor);
}
}
char numBuf[12];
int32_t numLen = snprintf(numBuf, sizeof(numBuf), "%d", (int)(row + 1));
int32_t numX = gutterX + gutterW - (numLen + 1) * font->charWidth;
drawTextN(d, ops, font, numX, drawY, numBuf, numLen, colors->windowShadow, colors->windowFace, true);
}
// Override bg for this line if the decorator set a custom background.
// This ensures text character backgrounds match the highlighted line.
uint32_t savedBg = bg;
bg = lineBg;
// Visible range within line
int32_t scrollCol = ta->scrollCol;
int32_t visStart = scrollCol;
@ -1887,6 +1939,9 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
}
}
// Restore bg for next line
bg = savedBg;
// Advance lineOff to the next line
lineOff += lineL;
if (lineOff < len && buf[lineOff] == '\n') {
@ -2635,6 +2690,37 @@ void wgtTextAreaSetColorize(WidgetT *w, void (*fn)(const char *, int32_t, uint8_
}
void wgtTextAreaSetGutterClickCallback(WidgetT *w, void (*fn)(WidgetT *, int32_t)) {
if (!w || w->type != sTextAreaTypeId) {
return;
}
TextAreaDataT *ta = (TextAreaDataT *)w->data;
ta->onGutterClick = fn;
}
int32_t wgtTextAreaGetCursorLine(const WidgetT *w) {
if (!w || w->type != sTextAreaTypeId) {
return 1;
}
const TextAreaDataT *ta = (const TextAreaDataT *)w->data;
return ta->cursorRow + 1; // 0-based to 1-based
}
void wgtTextAreaSetLineDecorator(WidgetT *w, uint32_t (*fn)(int32_t, uint32_t *, void *), void *ctx) {
if (!w || w->type != sTextAreaTypeId) {
return;
}
TextAreaDataT *ta = (TextAreaDataT *)w->data;
ta->lineDecorator = fn;
ta->lineDecoratorCtx = ctx;
}
void wgtTextAreaSetShowLineNumbers(WidgetT *w, bool show) {
if (!w || w->type != sTextAreaTypeId) {
return;
@ -2853,6 +2939,9 @@ static const struct {
void (*setUseTabChar)(WidgetT *w, bool useChar);
bool (*findNext)(WidgetT *w, const char *needle, bool caseSensitive, bool forward);
int32_t (*replaceAll)(WidgetT *w, const char *needle, const char *replacement, bool caseSensitive);
void (*setLineDecorator)(WidgetT *w, uint32_t (*fn)(int32_t, uint32_t *, void *), void *ctx);
int32_t (*getCursorLine)(const WidgetT *w);
void (*setGutterClick)(WidgetT *w, void (*fn)(WidgetT *, int32_t));
} sApi = {
.create = wgtTextInput,
.password = wgtPasswordInput,
@ -2866,7 +2955,10 @@ static const struct {
.setTabWidth = wgtTextAreaSetTabWidth,
.setUseTabChar = wgtTextAreaSetUseTabChar,
.findNext = wgtTextAreaFindNext,
.replaceAll = wgtTextAreaReplaceAll
.replaceAll = wgtTextAreaReplaceAll,
.setLineDecorator = wgtTextAreaSetLineDecorator,
.getCursorLine = wgtTextAreaGetCursorLine,
.setGutterClick = wgtTextAreaSetGutterClickCallback
};
// Per-type APIs for the designer

View file

@ -27,6 +27,9 @@ typedef struct {
void (*setUseTabChar)(WidgetT *w, bool useChar);
bool (*findNext)(WidgetT *w, const char *needle, bool caseSensitive, bool forward);
int32_t (*replaceAll)(WidgetT *w, const char *needle, const char *replacement, bool caseSensitive);
void (*setLineDecorator)(WidgetT *w, uint32_t (*fn)(int32_t, uint32_t *, void *), void *ctx);
int32_t (*getCursorLine)(const WidgetT *w);
void (*setGutterClick)(WidgetT *w, void (*fn)(WidgetT *, int32_t));
} TextInputApiT;
static inline const TextInputApiT *dvxTextInputApi(void) {
@ -48,5 +51,8 @@ static inline const TextInputApiT *dvxTextInputApi(void) {
#define wgtTextAreaSetUseTabChar(w, useChar) dvxTextInputApi()->setUseTabChar(w, useChar)
#define wgtTextAreaFindNext(w, needle, caseSens, fwd) dvxTextInputApi()->findNext(w, needle, caseSens, fwd)
#define wgtTextAreaReplaceAll(w, needle, repl, caseSens) dvxTextInputApi()->replaceAll(w, needle, repl, caseSens)
#define wgtTextAreaSetLineDecorator(w, fn, ctx) dvxTextInputApi()->setLineDecorator(w, fn, ctx)
#define wgtTextAreaGetCursorLine(w) dvxTextInputApi()->getCursorLine(w)
#define wgtTextAreaSetGutterClick(w, fn) dvxTextInputApi()->setGutterClick(w, fn)
#endif // WIDGET_TEXTINPUT_H