New splash screen. Debugger. Starting to debug the debugger.
This commit is contained in:
parent
626befa664
commit
de7027c44e
16 changed files with 1512 additions and 146 deletions
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
BIN
assets/splash.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
|
|
@ -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
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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...");
|
||||
|
|
|
|||
|
|
@ -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
128
tools/bmp2raw.c
Normal 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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue