No more hard coded limits. All dynamic.
This commit is contained in:
parent
a793941357
commit
97503080a5
20 changed files with 387 additions and 310 deletions
|
|
@ -478,7 +478,7 @@ static void compositeAndFlush(AppContextT *ctx) {
|
|||
|
||||
// Pre-filter visible, non-minimized windows once to avoid
|
||||
// re-checking visibility in the inner dirty-rect loop
|
||||
int32_t visibleIdx[MAX_WINDOWS];
|
||||
int32_t visibleIdx[ws->count > 0 ? ws->count : 1];
|
||||
int32_t visibleCount = 0;
|
||||
|
||||
for (int32_t j = 0; j < ws->count; j++) {
|
||||
|
|
@ -1446,10 +1446,22 @@ WindowT *dvxCreateWindowCentered(AppContextT *ctx, const char *title, int32_t w,
|
|||
// integer compare per entry.
|
||||
|
||||
void dvxAddAccel(AccelTableT *table, int32_t key, int32_t modifiers, int32_t cmdId) {
|
||||
if (!table || table->count >= MAX_ACCEL_ENTRIES) {
|
||||
if (!table) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (table->count >= table->cap) {
|
||||
int32_t newCap = table->cap ? table->cap * 2 : 8;
|
||||
AccelEntryT *newBuf = (AccelEntryT *)realloc(table->entries, newCap * sizeof(AccelEntryT));
|
||||
|
||||
if (!newBuf) {
|
||||
return;
|
||||
}
|
||||
|
||||
table->entries = newBuf;
|
||||
table->cap = newCap;
|
||||
}
|
||||
|
||||
int32_t normKey = key;
|
||||
|
||||
if (normKey >= 'a' && normKey <= 'z') {
|
||||
|
|
@ -1877,6 +1889,10 @@ void dvxFitWindow(AppContextT *ctx, WindowT *win) {
|
|||
// ============================================================
|
||||
|
||||
void dvxFreeAccelTable(AccelTableT *table) {
|
||||
if (table) {
|
||||
free(table->entries);
|
||||
}
|
||||
|
||||
free(table);
|
||||
}
|
||||
|
||||
|
|
@ -2755,6 +2771,20 @@ void dvxShutdown(AppContextT *ctx) {
|
|||
wmDestroyWindow(&ctx->stack, ctx->stack.windows[ctx->stack.count - 1]);
|
||||
}
|
||||
|
||||
free(ctx->stack.windows);
|
||||
ctx->stack.windows = NULL;
|
||||
ctx->stack.count = 0;
|
||||
ctx->stack.cap = 0;
|
||||
|
||||
free(ctx->popup.parentStack);
|
||||
ctx->popup.parentStack = NULL;
|
||||
ctx->popup.parentCap = 0;
|
||||
|
||||
free(ctx->dirty.rects);
|
||||
ctx->dirty.rects = NULL;
|
||||
ctx->dirty.count = 0;
|
||||
ctx->dirty.cap = 0;
|
||||
|
||||
free(ctx->wallpaperBuf);
|
||||
ctx->wallpaperBuf = NULL;
|
||||
arrfree(ctx->videoModes);
|
||||
|
|
@ -3434,8 +3464,6 @@ static void openPopupAtMenu(AppContextT *ctx, WindowT *win, int32_t menuIdx) {
|
|||
// Pushes the current popup state onto parentStack and opens the submenu
|
||||
// as the new current level. The submenu is positioned at the right edge
|
||||
// of the current popup, vertically aligned with the hovered item.
|
||||
// MAX_SUBMENU_DEPTH prevents runaway nesting from overflowing the stack.
|
||||
|
||||
static void openSubMenu(AppContextT *ctx) {
|
||||
if (!ctx->popup.active || !ctx->popup.menu) {
|
||||
return;
|
||||
|
|
@ -3453,10 +3481,19 @@ static void openSubMenu(AppContextT *ctx) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (ctx->popup.depth >= MAX_SUBMENU_DEPTH) {
|
||||
// Grow parent stack if needed
|
||||
if (ctx->popup.depth >= ctx->popup.parentCap) {
|
||||
int32_t newCap = ctx->popup.parentCap ? ctx->popup.parentCap * 2 : 4;
|
||||
PopupLevelT *newBuf = (PopupLevelT *)realloc(ctx->popup.parentStack, newCap * sizeof(PopupLevelT));
|
||||
|
||||
if (!newBuf) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx->popup.parentStack = newBuf;
|
||||
ctx->popup.parentCap = newCap;
|
||||
}
|
||||
|
||||
// Push current state to parent stack
|
||||
PopupLevelT *pl = &ctx->popup.parentStack[ctx->popup.depth];
|
||||
pl->menu = ctx->popup.menu;
|
||||
|
|
@ -4256,15 +4293,14 @@ static void pollKeyboard(AppContextT *ctx) {
|
|||
if (win->widgetRoot) {
|
||||
// Find currently focused widget
|
||||
WidgetT *current = NULL;
|
||||
WidgetT *fstack[64];
|
||||
int32_t ftop = 0;
|
||||
fstack[ftop++] = win->widgetRoot;
|
||||
WidgetT **fstack = NULL;
|
||||
arrput(fstack, win->widgetRoot);
|
||||
|
||||
while (ftop > 0) {
|
||||
WidgetT *w = fstack[--ftop];
|
||||
while (arrlen(fstack) > 0) {
|
||||
WidgetT *w = fstack[arrlen(fstack) - 1];
|
||||
arrsetlen(fstack, arrlen(fstack) - 1);
|
||||
|
||||
if (w->focused && widgetIsFocusable(w->type)) {
|
||||
// Don't tab out of widgets that swallow Tab
|
||||
if (w->wclass && (w->wclass->flags & WCLASS_SWALLOWS_TAB)) {
|
||||
current = NULL;
|
||||
break;
|
||||
|
|
@ -4275,21 +4311,21 @@ static void pollKeyboard(AppContextT *ctx) {
|
|||
}
|
||||
|
||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
||||
if (c->visible && ftop < 64) {
|
||||
fstack[ftop++] = c;
|
||||
if (c->visible) {
|
||||
arrput(fstack, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Terminal swallowed Tab -- send to widget system instead
|
||||
if (current == NULL) {
|
||||
// Check if a terminal is focused
|
||||
ftop = 0;
|
||||
fstack[ftop++] = win->widgetRoot;
|
||||
arrsetlen(fstack, 0);
|
||||
arrput(fstack, win->widgetRoot);
|
||||
bool termFocused = false;
|
||||
|
||||
while (ftop > 0) {
|
||||
WidgetT *w = fstack[--ftop];
|
||||
while (arrlen(fstack) > 0) {
|
||||
WidgetT *w = fstack[arrlen(fstack) - 1];
|
||||
arrsetlen(fstack, arrlen(fstack) - 1);
|
||||
|
||||
if (w->focused && w->wclass && (w->wclass->flags & WCLASS_SWALLOWS_TAB)) {
|
||||
termFocused = true;
|
||||
|
|
@ -4297,8 +4333,8 @@ static void pollKeyboard(AppContextT *ctx) {
|
|||
}
|
||||
|
||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
||||
if (c->visible && ftop < 64) {
|
||||
fstack[ftop++] = c;
|
||||
if (c->visible) {
|
||||
arrput(fstack, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4309,6 +4345,7 @@ static void pollKeyboard(AppContextT *ctx) {
|
|||
win->onKey(win, ascii ? ascii : (scancode | 0x100), shiftFlags);
|
||||
}
|
||||
|
||||
arrfree(fstack);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
@ -4366,6 +4403,8 @@ static void pollKeyboard(AppContextT *ctx) {
|
|||
|
||||
wgtInvalidate(win->widgetRoot);
|
||||
}
|
||||
|
||||
arrfree(fstack);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,9 @@
|
|||
#include "dvxComp.h"
|
||||
#include "dvxPlatform.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "dvxMem.h"
|
||||
|
||||
// Rects within this many pixels of each other get merged even if they don't
|
||||
// overlap. A small gap tolerance absorbs jitter from mouse movement and
|
||||
|
|
@ -42,32 +44,24 @@ static inline void rectUnion(const RectT *a, const RectT *b, RectT *result);
|
|||
// dirtyListAdd
|
||||
// ============================================================
|
||||
//
|
||||
// Appends a dirty rect to the list. Uses a fixed-size array (MAX_DIRTY_RECTS
|
||||
// = 128) rather than a dynamic allocation -- this is called on every UI
|
||||
// mutation (drag, repaint, focus change) so allocation overhead must be zero.
|
||||
//
|
||||
// When the list fills up, an eager merge pass tries to consolidate rects.
|
||||
// If the list is STILL full after merging (pathological scatter), the
|
||||
// nuclear option collapses everything into one bounding box. This guarantees
|
||||
// the list never overflows, at the cost of potentially over-painting a large
|
||||
// rect. In practice the merge pass almost always frees enough slots because
|
||||
// GUI mutations tend to cluster spatially.
|
||||
// Appends a dirty rect to the list. The array grows dynamically but a
|
||||
// merge pass fires at 128 entries to keep the list short. If the list
|
||||
// is still long after merging (pathological scatter), everything collapses
|
||||
// into one bounding box. In practice the merge pass almost always frees
|
||||
// enough slots because GUI mutations tend to cluster spatially.
|
||||
|
||||
#define DIRTY_SOFT_CAP 128
|
||||
|
||||
void dirtyListAdd(DirtyListT *dl, int32_t x, int32_t y, int32_t w, int32_t h) {
|
||||
// Branch hint: degenerate rects are rare -- callers usually validate first
|
||||
if (__builtin_expect(w <= 0 || h <= 0, 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Overflow path: try merging, then fall back to a single bounding rect
|
||||
if (__builtin_expect(dl->count >= MAX_DIRTY_RECTS, 0)) {
|
||||
// Soft cap: merge when we accumulate too many rects to keep composite fast
|
||||
if (__builtin_expect(dl->count >= DIRTY_SOFT_CAP, 0)) {
|
||||
dirtyListMerge(dl);
|
||||
|
||||
if (dl->count >= MAX_DIRTY_RECTS) {
|
||||
// Still full -- collapse the entire list plus the new rect into one
|
||||
// bounding box. This is a last resort; it means the next flush will
|
||||
// repaint a potentially large region, but at least we won't lose
|
||||
// dirty information or crash.
|
||||
if (dl->count >= DIRTY_SOFT_CAP) {
|
||||
RectT merged = dl->rects[0];
|
||||
|
||||
for (int32_t i = 1; i < dl->count; i++) {
|
||||
|
|
@ -83,6 +77,19 @@ void dirtyListAdd(DirtyListT *dl, int32_t x, int32_t y, int32_t w, int32_t h) {
|
|||
}
|
||||
}
|
||||
|
||||
// Grow array if needed
|
||||
if (dl->count >= dl->cap) {
|
||||
int32_t newCap = dl->cap ? dl->cap * 2 : DIRTY_SOFT_CAP;
|
||||
RectT *newBuf = (RectT *)realloc(dl->rects, newCap * sizeof(RectT));
|
||||
|
||||
if (!newBuf) {
|
||||
return;
|
||||
}
|
||||
|
||||
dl->rects = newBuf;
|
||||
dl->cap = newCap;
|
||||
}
|
||||
|
||||
dl->rects[dl->count].x = x;
|
||||
dl->rects[dl->count].y = y;
|
||||
dl->rects[dl->count].w = w;
|
||||
|
|
|
|||
|
|
@ -25,8 +25,7 @@
|
|||
// Zero the dirty rect count. Called at the start of each frame.
|
||||
void dirtyListInit(DirtyListT *dl);
|
||||
|
||||
// Enqueue a dirty rectangle. If the list is full (MAX_DIRTY_RECTS),
|
||||
// this should degrade gracefully (e.g. expand an existing rect).
|
||||
// Enqueue a dirty rectangle. Grows dynamically; merges at a soft cap.
|
||||
void dirtyListAdd(DirtyListT *dl, int32_t x, int32_t y, int32_t w, int32_t h);
|
||||
|
||||
// Consolidate the dirty list by merging overlapping and adjacent rects.
|
||||
|
|
|
|||
|
|
@ -629,7 +629,6 @@ static int32_t wordWrapHeight(const BitmapFontT *font, const char *text, int32_t
|
|||
// listbox to distinguish them from files, following the DOS convention.
|
||||
|
||||
// FD_MAX_PATH is 260 to match DOS MAX_PATH (including null terminator)
|
||||
#define FD_MAX_ENTRIES 512
|
||||
#define FD_MAX_PATH 260
|
||||
#define FD_NAME_LEN 64
|
||||
|
||||
|
|
@ -642,10 +641,10 @@ typedef struct {
|
|||
const FileFilterT *filters; // caller-provided filter list
|
||||
int32_t filterCount;
|
||||
int32_t activeFilter; // index into filters[]
|
||||
char *entryNames[FD_MAX_ENTRIES]; // heap-allocated, freed by fdFreeEntries
|
||||
bool entryIsDir[FD_MAX_ENTRIES];
|
||||
char **entryNames; // dynamic array of heap-allocated strings
|
||||
bool *entryIsDir; // dynamic array, parallel to entryNames
|
||||
int32_t entryCount;
|
||||
const char *listItems[FD_MAX_ENTRIES]; // pointers into entryNames, for listbox API
|
||||
const char **listItems; // dynamic array, pointers into entryNames
|
||||
WidgetT *fileList;
|
||||
WidgetT *pathInput;
|
||||
WidgetT *nameInput;
|
||||
|
|
@ -745,9 +744,14 @@ static bool fdFilterMatch(const char *name, const char *pattern) {
|
|||
static void fdFreeEntries(void) {
|
||||
for (int32_t i = 0; i < sFd.entryCount; i++) {
|
||||
free(sFd.entryNames[i]);
|
||||
sFd.entryNames[i] = NULL;
|
||||
}
|
||||
|
||||
free(sFd.entryNames);
|
||||
free(sFd.entryIsDir);
|
||||
free(sFd.listItems);
|
||||
sFd.entryNames = NULL;
|
||||
sFd.entryIsDir = NULL;
|
||||
sFd.listItems = NULL;
|
||||
sFd.entryCount = 0;
|
||||
}
|
||||
|
||||
|
|
@ -803,7 +807,7 @@ static void fdLoadDir(void) {
|
|||
|
||||
struct dirent *ent;
|
||||
|
||||
while ((ent = readdir(dir)) != NULL && sFd.entryCount < FD_MAX_ENTRIES) {
|
||||
while ((ent = readdir(dir)) != NULL) {
|
||||
// Skip "."
|
||||
if (strcmp(ent->d_name, ".") == 0) {
|
||||
continue;
|
||||
|
|
@ -826,7 +830,15 @@ static void fdLoadDir(void) {
|
|||
continue;
|
||||
}
|
||||
|
||||
// Grow arrays
|
||||
int32_t idx = sFd.entryCount;
|
||||
sFd.entryNames = (char **)realloc(sFd.entryNames, (idx + 1) * sizeof(char *));
|
||||
sFd.entryIsDir = (bool *)realloc(sFd.entryIsDir, (idx + 1) * sizeof(bool));
|
||||
|
||||
if (!sFd.entryNames || !sFd.entryIsDir) {
|
||||
break;
|
||||
}
|
||||
|
||||
sFd.entryIsDir[idx] = isDir;
|
||||
|
||||
if (isDir) {
|
||||
|
|
@ -843,8 +855,17 @@ static void fdLoadDir(void) {
|
|||
|
||||
closedir(dir);
|
||||
|
||||
if (sFd.entryCount == 0) {
|
||||
wgtListBoxSetItems(sFd.fileList, NULL, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort: build index array, sort, reorder
|
||||
int32_t sortIdx[FD_MAX_ENTRIES];
|
||||
int32_t *sortIdx = (int32_t *)malloc(sFd.entryCount * sizeof(int32_t));
|
||||
|
||||
if (!sortIdx) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int32_t i = 0; i < sFd.entryCount; i++) {
|
||||
sortIdx[i] = i;
|
||||
|
|
@ -853,9 +874,10 @@ static void fdLoadDir(void) {
|
|||
qsort(sortIdx, sFd.entryCount, sizeof(int32_t), fdEntryCompare);
|
||||
|
||||
// Rebuild arrays in sorted order
|
||||
char *tmpNames[FD_MAX_ENTRIES];
|
||||
bool tmpIsDir[FD_MAX_ENTRIES];
|
||||
char **tmpNames = (char **)malloc(sFd.entryCount * sizeof(char *));
|
||||
bool *tmpIsDir = (bool *)malloc(sFd.entryCount * sizeof(bool));
|
||||
|
||||
if (tmpNames && tmpIsDir) {
|
||||
for (int32_t i = 0; i < sFd.entryCount; i++) {
|
||||
tmpNames[i] = sFd.entryNames[sortIdx[i]];
|
||||
tmpIsDir[i] = sFd.entryIsDir[sortIdx[i]];
|
||||
|
|
@ -863,8 +885,15 @@ static void fdLoadDir(void) {
|
|||
|
||||
memcpy(sFd.entryNames, tmpNames, sizeof(char *) * sFd.entryCount);
|
||||
memcpy(sFd.entryIsDir, tmpIsDir, sizeof(bool) * sFd.entryCount);
|
||||
}
|
||||
|
||||
free(sortIdx);
|
||||
free(tmpNames);
|
||||
free(tmpIsDir);
|
||||
|
||||
// Build listItems pointer array for the listbox
|
||||
sFd.listItems = (const char **)realloc(sFd.listItems, sFd.entryCount * sizeof(const char *));
|
||||
|
||||
for (int32_t i = 0; i < sFd.entryCount; i++) {
|
||||
sFd.listItems[i] = sFd.entryNames[i];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,7 +52,9 @@
|
|||
#include "dvxDraw.h"
|
||||
#include "dvxPlatform.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "dvxMem.h"
|
||||
|
||||
// ============================================================
|
||||
// Prototypes
|
||||
|
|
|
|||
|
|
@ -30,3 +30,6 @@
|
|||
#include "thirdparty/stb_image.h"
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "dvxMem.h"
|
||||
|
|
|
|||
|
|
@ -18,3 +18,6 @@
|
|||
#include "thirdparty/stb_image_write.h"
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "dvxMem.h"
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
#ifndef DVX_MEM_H
|
||||
#define DVX_MEM_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
extern int32_t *dvxMemAppIdPtr;
|
||||
|
|
@ -38,11 +39,10 @@ void dvxMemSnapshotLoad(int32_t appId);
|
|||
uint32_t dvxMemGetAppUsage(int32_t appId);
|
||||
void dvxMemResetApp(int32_t appId);
|
||||
|
||||
// Redirect standard allocator calls to tracking wrappers.
|
||||
// This MUST appear after <stdlib.h> so the real prototypes exist.
|
||||
#define malloc(s) dvxMalloc(s)
|
||||
#define calloc(n, s) dvxCalloc((n), (s))
|
||||
#define realloc(p, s) dvxRealloc((p), (s))
|
||||
#define free(p) dvxFree(p)
|
||||
// The dvxMalloc/dvxFree functions are passthrough wrappers.
|
||||
// Header-based per-allocation tracking was attempted but is unsafe
|
||||
// in the DXE3 environment (loader-compiled code like stb_ds uses
|
||||
// libc malloc while DXE code would use the wrapped version).
|
||||
// Per-app memory is tracked via DPMI snapshots instead.
|
||||
|
||||
#endif // DVX_MEM_H
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "dvxMem.h"
|
||||
|
||||
// stb_ds dynamic arrays (implementation lives in libtasks.a)
|
||||
#include "thirdparty/stb_ds.h"
|
||||
|
|
|
|||
|
|
@ -259,11 +259,10 @@ typedef struct {
|
|||
// cache-friendly at this size. If the list fills up, the compositor
|
||||
// can merge aggressively or fall back to a full-screen repaint.
|
||||
|
||||
#define MAX_DIRTY_RECTS 128
|
||||
|
||||
typedef struct {
|
||||
RectT rects[MAX_DIRTY_RECTS];
|
||||
RectT *rects; // dynamic array (realloc'd on demand)
|
||||
int32_t count;
|
||||
int32_t cap;
|
||||
} DirtyListT;
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -305,8 +304,6 @@ typedef struct {
|
|||
// The forward declaration of MenuT is needed because MenuItemT contains
|
||||
// a pointer to its parent type (circular reference between item and menu).
|
||||
|
||||
#define MAX_MENU_ITEMS 16
|
||||
#define MAX_MENUS 8
|
||||
#define MAX_MENU_LABEL 32
|
||||
|
||||
// Forward declaration for submenu pointers
|
||||
|
|
@ -333,8 +330,9 @@ typedef struct {
|
|||
// needs a forward pointer to it for cascading submenus.
|
||||
struct MenuT {
|
||||
char label[MAX_MENU_LABEL]; // menu bar label (e.g. "File")
|
||||
MenuItemT items[MAX_MENU_ITEMS];
|
||||
MenuItemT *items; // dynamic array (realloc'd on demand)
|
||||
int32_t itemCount;
|
||||
int32_t itemCap;
|
||||
int32_t barX; // computed position on menu bar
|
||||
int32_t barW; // computed width on menu bar
|
||||
char accelKey; // lowercase accelerator character, 0 if none
|
||||
|
|
@ -344,8 +342,9 @@ struct MenuT {
|
|||
// menu bar is actually drawn, avoiding redundant work when multiple
|
||||
// menus are added in sequence during window setup.
|
||||
typedef struct {
|
||||
MenuT menus[MAX_MENUS];
|
||||
MenuT *menus; // dynamic array (realloc'd on demand)
|
||||
int32_t menuCount;
|
||||
int32_t menuCap;
|
||||
int32_t activeIdx; // menu bar item with open popup (-1 = none)
|
||||
bool positionsDirty; // true = barX/barW need recomputation
|
||||
} MenuBarT;
|
||||
|
|
@ -394,7 +393,6 @@ typedef struct {
|
|||
// match in O(n) with just two integer comparisons per entry, avoiding
|
||||
// case-folding and modifier normalization on every keystroke.
|
||||
|
||||
#define MAX_ACCEL_ENTRIES 32
|
||||
|
||||
// Modifier flags for accelerators (match BIOS shift state bits)
|
||||
#define ACCEL_SHIFT 0x03
|
||||
|
|
@ -431,8 +429,9 @@ typedef struct {
|
|||
} AccelEntryT;
|
||||
|
||||
typedef struct {
|
||||
AccelEntryT entries[MAX_ACCEL_ENTRIES];
|
||||
AccelEntryT *entries; // dynamic array (realloc'd on demand)
|
||||
int32_t count;
|
||||
int32_t cap;
|
||||
} AccelTableT;
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -453,7 +452,6 @@ typedef struct {
|
|||
// RESIZE_xxx flags are bitfields so that corner resizes can combine two
|
||||
// edges (e.g. RESIZE_LEFT | RESIZE_TOP for the top-left corner).
|
||||
|
||||
#define MAX_WINDOWS 64
|
||||
#define MAX_TITLE_LEN 128
|
||||
|
||||
#define RESIZE_NONE 0
|
||||
|
|
@ -577,8 +575,9 @@ typedef struct WindowT {
|
|||
// you can't drag two windows simultaneously with a single mouse.
|
||||
|
||||
typedef struct {
|
||||
WindowT *windows[MAX_WINDOWS];
|
||||
WindowT **windows; // dynamic array (realloc'd on demand)
|
||||
int32_t count;
|
||||
int32_t cap;
|
||||
int32_t focusedIdx;
|
||||
int32_t dragWindow;
|
||||
int32_t dragOffX;
|
||||
|
|
@ -629,15 +628,14 @@ typedef struct {
|
|||
// Only one popup chain can be active at a time (menus are modal).
|
||||
// Cascading submenus are tracked as a stack of PopupLevelT frames,
|
||||
// where each level saves the parent menu's state so it can be restored
|
||||
// when the submenu closes. MAX_SUBMENU_DEPTH of 4 allows File > Recent >
|
||||
// Category > Item style nesting, which is deeper than any sane DOS UI needs.
|
||||
// when the submenu closes. The stack grows dynamically to support
|
||||
// arbitrary nesting depth.
|
||||
//
|
||||
// The popup's screen coordinates are stored directly rather than computed
|
||||
// relative to the window, because the popup is painted on top of
|
||||
// everything during the compositor's overlay pass and needs absolute
|
||||
// screen positioning.
|
||||
|
||||
#define MAX_SUBMENU_DEPTH 4
|
||||
|
||||
// Saved parent popup state when a submenu is open
|
||||
typedef struct {
|
||||
|
|
@ -662,7 +660,8 @@ typedef struct {
|
|||
int32_t hoverItem; // highlighted item in current popup (-1 = none)
|
||||
MenuT *menu; // direct pointer to current menu (avoids lookup for submenus)
|
||||
int32_t depth; // 0 = top-level only, 1+ = submenu depth
|
||||
PopupLevelT parentStack[MAX_SUBMENU_DEPTH];
|
||||
PopupLevelT *parentStack; // dynamic array (realloc'd on demand)
|
||||
int32_t parentCap;
|
||||
} PopupStateT;
|
||||
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -45,7 +45,9 @@
|
|||
#include "dvxPlatform.h"
|
||||
#include "dvxPalette.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "dvxMem.h"
|
||||
|
||||
|
||||
// ============================================================
|
||||
|
|
|
|||
64
core/dvxWm.c
64
core/dvxWm.c
|
|
@ -11,7 +11,7 @@
|
|||
// = front). This was chosen over a linked list because hit-testing walks
|
||||
// the stack front-to-back every mouse event, and array iteration has
|
||||
// better cache behavior on 486/Pentium. Raising a window is O(N) shift
|
||||
// but N is bounded by MAX_WINDOWS=64 and raise is infrequent.
|
||||
// but N is bounded by the window count and raise is infrequent.
|
||||
//
|
||||
// - Window chrome uses a Motif/GEOS Ensemble visual style with fixed 4px
|
||||
// outer bevels and 2px inner bevels. The fixed bevel widths avoid per-
|
||||
|
|
@ -39,6 +39,7 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include "dvxMem.h"
|
||||
|
||||
// ============================================================
|
||||
// Constants
|
||||
|
|
@ -712,6 +713,11 @@ static void freeMenuRecursive(MenuT *menu) {
|
|||
menu->items[i].subMenu = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
free(menu->items);
|
||||
menu->items = NULL;
|
||||
menu->itemCount = 0;
|
||||
menu->itemCap = 0;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -861,7 +867,7 @@ ScrollbarT *wmAddHScrollbar(WindowT *win, int32_t min, int32_t max, int32_t page
|
|||
// ============================================================
|
||||
//
|
||||
// Adds a top-level menu to a window's menu bar. The MenuT struct is
|
||||
// stored inline in the MenuBarT's fixed-size array (MAX_MENUS=8) rather
|
||||
// stored inline in the MenuBarT's dynamic array rather
|
||||
// than heap-allocated, since menus are created once at window setup and
|
||||
// the count is small. The positionsDirty flag is set so that the next
|
||||
// paint triggers computeMenuBarPositions to lay out all labels.
|
||||
|
|
@ -871,11 +877,37 @@ ScrollbarT *wmAddHScrollbar(WindowT *win, int32_t min, int32_t max, int32_t page
|
|||
// stored on the MenuT so the event loop can match keyboard shortcuts
|
||||
// without re-parsing the label string on every keypress.
|
||||
|
||||
static bool menuGrowItems(MenuT *menu) {
|
||||
if (menu->itemCount < menu->itemCap) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t newCap = menu->itemCap ? menu->itemCap * 2 : 8;
|
||||
MenuItemT *newBuf = (MenuItemT *)realloc(menu->items, newCap * sizeof(MenuItemT));
|
||||
|
||||
if (!newBuf) {
|
||||
return false;
|
||||
}
|
||||
|
||||
menu->items = newBuf;
|
||||
menu->itemCap = newCap;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
MenuT *wmAddMenu(MenuBarT *bar, const char *label) {
|
||||
if (bar->menuCount >= MAX_MENUS) {
|
||||
if (bar->menuCount >= bar->menuCap) {
|
||||
int32_t newCap = bar->menuCap ? bar->menuCap * 2 : 8;
|
||||
MenuT *newBuf = (MenuT *)realloc(bar->menus, newCap * sizeof(MenuT));
|
||||
|
||||
if (!newBuf) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bar->menus = newBuf;
|
||||
bar->menuCap = newCap;
|
||||
}
|
||||
|
||||
MenuT *menu = &bar->menus[bar->menuCount];
|
||||
memset(menu, 0, sizeof(*menu));
|
||||
strncpy(menu->label, label, MAX_MENU_LABEL - 1);
|
||||
|
|
@ -895,7 +927,7 @@ MenuT *wmAddMenu(MenuBarT *bar, const char *label) {
|
|||
// Allocates and attaches a menu bar to a window. The menu bar is heap-
|
||||
// allocated separately from the window because most windows don't have
|
||||
// one, and keeping it out of WindowT saves ~550 bytes per window
|
||||
// (MAX_MENUS * sizeof(MenuT)). wmUpdateContentRect is called to shrink
|
||||
// wmUpdateContentRect is called to shrink
|
||||
// the content area by CHROME_MENU_HEIGHT to make room for the bar.
|
||||
|
||||
MenuBarT *wmAddMenuBar(WindowT *win) {
|
||||
|
|
@ -918,7 +950,7 @@ MenuBarT *wmAddMenuBar(WindowT *win) {
|
|||
// ============================================================
|
||||
|
||||
void wmAddMenuItem(MenuT *menu, const char *label, int32_t id) {
|
||||
if (menu->itemCount >= MAX_MENU_ITEMS) {
|
||||
if (!menuGrowItems(menu)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -940,7 +972,7 @@ void wmAddMenuItem(MenuT *menu, const char *label, int32_t id) {
|
|||
// ============================================================
|
||||
|
||||
void wmAddMenuCheckItem(MenuT *menu, const char *label, int32_t id, bool checked) {
|
||||
if (menu->itemCount >= MAX_MENU_ITEMS) {
|
||||
if (!menuGrowItems(menu)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -962,7 +994,7 @@ void wmAddMenuCheckItem(MenuT *menu, const char *label, int32_t id, bool checked
|
|||
// ============================================================
|
||||
|
||||
void wmAddMenuRadioItem(MenuT *menu, const char *label, int32_t id, bool checked) {
|
||||
if (menu->itemCount >= MAX_MENU_ITEMS) {
|
||||
if (!menuGrowItems(menu)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -984,7 +1016,7 @@ void wmAddMenuRadioItem(MenuT *menu, const char *label, int32_t id, bool checked
|
|||
// ============================================================
|
||||
|
||||
void wmAddMenuSeparator(MenuT *menu) {
|
||||
if (menu->itemCount >= MAX_MENU_ITEMS) {
|
||||
if (!menuGrowItems(menu)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1007,7 +1039,7 @@ void wmAddMenuSeparator(MenuT *menu) {
|
|||
// item opens the child rather than firing a command.
|
||||
|
||||
MenuT *wmAddSubMenu(MenuT *parentMenu, const char *label) {
|
||||
if (parentMenu->itemCount >= MAX_MENU_ITEMS) {
|
||||
if (!menuGrowItems(parentMenu)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
@ -1084,11 +1116,19 @@ ScrollbarT *wmAddVScrollbar(WindowT *win, int32_t min, int32_t max, int32_t page
|
|||
// and wmRaiseWindow if desired.
|
||||
|
||||
WindowT *wmCreateWindow(WindowStackT *stack, DisplayT *d, const char *title, int32_t x, int32_t y, int32_t w, int32_t h, bool resizable) {
|
||||
if (stack->count >= MAX_WINDOWS) {
|
||||
fprintf(stderr, "WM: Maximum windows (%d) reached\n", MAX_WINDOWS);
|
||||
if (stack->count >= stack->cap) {
|
||||
int32_t newCap = stack->cap ? stack->cap * 2 : 16;
|
||||
WindowT **newBuf = (WindowT **)realloc(stack->windows, newCap * sizeof(WindowT *));
|
||||
|
||||
if (!newBuf) {
|
||||
fprintf(stderr, "WM: Failed to grow window stack\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
stack->windows = newBuf;
|
||||
stack->cap = newCap;
|
||||
}
|
||||
|
||||
WindowT *win = (WindowT *)malloc(sizeof(WindowT));
|
||||
|
||||
if (!win) {
|
||||
|
|
@ -1188,6 +1228,7 @@ void wmDestroyWindow(WindowStackT *stack, WindowT *win) {
|
|||
freeMenuRecursive(&win->menuBar->menus[i]);
|
||||
}
|
||||
|
||||
free(win->menuBar->menus);
|
||||
free(win->menuBar);
|
||||
}
|
||||
|
||||
|
|
@ -2626,6 +2667,7 @@ void wmFreeMenu(MenuT *menu) {
|
|||
}
|
||||
}
|
||||
|
||||
free(menu->items);
|
||||
free(menu);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ void wmAddMenuRadioItem(MenuT *menu, const char *label, int32_t id, bool checked
|
|||
void wmAddMenuSeparator(MenuT *menu);
|
||||
|
||||
// Create a cascading submenu attached to the parent menu. Returns the
|
||||
// child MenuT to populate, or NULL if MAX_MENU_ITEMS is exhausted.
|
||||
// child MenuT to populate, or NULL on allocation failure.
|
||||
// The child MenuT is heap-allocated and freed when the parent window
|
||||
// is destroyed.
|
||||
MenuT *wmAddSubMenu(MenuT *parentMenu, const char *label);
|
||||
|
|
|
|||
|
|
@ -1113,147 +1113,91 @@ bool platformGetMemoryInfo(uint32_t *totalKb, uint32_t *freeKb) {
|
|||
|
||||
|
||||
// ============================================================
|
||||
// Per-app memory tracking (header-based)
|
||||
// Per-app memory tracking (DPMI snapshot)
|
||||
// ============================================================
|
||||
//
|
||||
// Every DXE .c file includes dvxMem.h which #defines malloc/free/
|
||||
// calloc/realloc to dvxMalloc/dvxFree/dvxCalloc/dvxRealloc. This
|
||||
// file does NOT include dvxMem.h, so calls to malloc/free here go
|
||||
// directly to libc with no recursion.
|
||||
// Tracks per-app memory by taking DPMI free-memory snapshots at
|
||||
// app load time. The difference between the snapshot and current
|
||||
// free memory is a coarse estimate of the app's heap footprint.
|
||||
//
|
||||
// Each tracked allocation has a 16-byte header prepended. dvxFree
|
||||
// checks the magic before adjusting the pointer -- if it doesn't
|
||||
// match (pointer came from libc internals or loader code), it
|
||||
// falls through to the real free() unchanged.
|
||||
|
||||
#define DVX_ALLOC_MAGIC 0xDEADBEEFUL
|
||||
|
||||
typedef struct {
|
||||
uint32_t magic;
|
||||
int32_t appId;
|
||||
uint32_t size;
|
||||
uint32_t pad;
|
||||
} DvxAllocHeaderT;
|
||||
// Header-based malloc wrapping (prepending a tracking header to
|
||||
// each allocation) was attempted but is unsafe in the DJGPP/DXE3
|
||||
// environment: loader-compiled code (stb_ds internals, libc
|
||||
// functions like strdup/localtime) allocates with libc's malloc,
|
||||
// but DXE code frees with the wrapped free. Reading 16 bytes
|
||||
// before an arbitrary libc pointer to check for a tracking magic
|
||||
// value is undefined behavior that causes faults on 86Box/DPMI.
|
||||
//
|
||||
// dvxMalloc/dvxFree/etc. are still exported for dvxMem.h compat
|
||||
// but pass straight through to libc.
|
||||
|
||||
int32_t *dvxMemAppIdPtr = NULL;
|
||||
static uint32_t *sAppMemUsed = NULL;
|
||||
static uint32_t *sAppMemAtLoad = NULL;
|
||||
static int32_t sAppMemCap = 0;
|
||||
|
||||
|
||||
// Passthrough allocators (dvxMem.h #defines redirect here)
|
||||
void *dvxMalloc(size_t size) {
|
||||
return malloc(size);
|
||||
}
|
||||
|
||||
|
||||
void *dvxCalloc(size_t nmemb, size_t size) {
|
||||
return calloc(nmemb, size);
|
||||
}
|
||||
|
||||
|
||||
void dvxFree(void *ptr) {
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
|
||||
void *dvxRealloc(void *ptr, size_t size) {
|
||||
return realloc(ptr, size);
|
||||
}
|
||||
|
||||
|
||||
static uint32_t dpmiGetFreeKb(void) {
|
||||
__dpmi_free_mem_info memInfo;
|
||||
|
||||
if (__dpmi_get_free_memory_information(&memInfo) != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (memInfo.total_number_of_free_pages == 0xFFFFFFFFUL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return memInfo.total_number_of_free_pages * 4;
|
||||
}
|
||||
|
||||
|
||||
static void dvxMemGrow(int32_t appId) {
|
||||
if (appId < sAppMemCap) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t newCap = appId + 16;
|
||||
uint32_t *newArr = (uint32_t *)realloc(sAppMemUsed, newCap * sizeof(uint32_t));
|
||||
uint32_t *newArr = (uint32_t *)realloc(sAppMemAtLoad, newCap * sizeof(uint32_t));
|
||||
|
||||
if (!newArr) {
|
||||
return;
|
||||
}
|
||||
|
||||
memset(newArr + sAppMemCap, 0, (newCap - sAppMemCap) * sizeof(uint32_t));
|
||||
sAppMemUsed = newArr;
|
||||
sAppMemAtLoad = newArr;
|
||||
sAppMemCap = newCap;
|
||||
}
|
||||
|
||||
|
||||
void *dvxMalloc(size_t size) {
|
||||
int32_t appId = dvxMemAppIdPtr ? *dvxMemAppIdPtr : 0;
|
||||
DvxAllocHeaderT *hdr = (DvxAllocHeaderT *)malloc(sizeof(DvxAllocHeaderT) + size);
|
||||
|
||||
if (!hdr) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
hdr->magic = DVX_ALLOC_MAGIC;
|
||||
hdr->appId = appId;
|
||||
hdr->size = (uint32_t)size;
|
||||
hdr->pad = 0;
|
||||
|
||||
void dvxMemSnapshotLoad(int32_t appId) {
|
||||
if (appId >= 0) {
|
||||
dvxMemGrow(appId);
|
||||
if (appId < sAppMemCap) { sAppMemUsed[appId] += (uint32_t)size; }
|
||||
|
||||
if (appId < sAppMemCap) {
|
||||
sAppMemAtLoad[appId] = dpmiGetFreeKb();
|
||||
}
|
||||
|
||||
return hdr + 1;
|
||||
}
|
||||
|
||||
|
||||
void *dvxCalloc(size_t nmemb, size_t size) {
|
||||
size_t total = nmemb * size;
|
||||
void *ptr = dvxMalloc(total);
|
||||
|
||||
if (ptr) {
|
||||
memset(ptr, 0, total);
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
|
||||
void dvxFree(void *ptr) {
|
||||
if (!ptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
DvxAllocHeaderT *hdr = (DvxAllocHeaderT *)ptr - 1;
|
||||
|
||||
if (hdr->magic != DVX_ALLOC_MAGIC) {
|
||||
// Not a tracked allocation (libc, stb_ds, loader) -- pass through
|
||||
free(ptr);
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t appId = hdr->appId;
|
||||
|
||||
if (appId >= 0 && appId < sAppMemCap) {
|
||||
sAppMemUsed[appId] -= hdr->size;
|
||||
}
|
||||
|
||||
hdr->magic = 0;
|
||||
free(hdr);
|
||||
}
|
||||
|
||||
|
||||
void *dvxRealloc(void *ptr, size_t size) {
|
||||
if (!ptr) {
|
||||
return dvxMalloc(size);
|
||||
}
|
||||
|
||||
if (size == 0) {
|
||||
dvxFree(ptr);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DvxAllocHeaderT *hdr = (DvxAllocHeaderT *)ptr - 1;
|
||||
|
||||
if (hdr->magic != DVX_ALLOC_MAGIC) {
|
||||
// Not tracked -- pass through
|
||||
return realloc(ptr, size);
|
||||
}
|
||||
|
||||
int32_t appId = hdr->appId;
|
||||
uint32_t oldSize = hdr->size;
|
||||
|
||||
DvxAllocHeaderT *newHdr = (DvxAllocHeaderT *)realloc(hdr, sizeof(DvxAllocHeaderT) + size);
|
||||
|
||||
if (!newHdr) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (appId >= 0 && appId < sAppMemCap) {
|
||||
sAppMemUsed[appId] -= oldSize;
|
||||
sAppMemUsed[appId] += (uint32_t)size;
|
||||
}
|
||||
|
||||
newHdr->size = (uint32_t)size;
|
||||
return newHdr + 1;
|
||||
}
|
||||
|
||||
|
||||
void dvxMemSnapshotLoad(int32_t appId) {
|
||||
(void)appId;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1262,13 +1206,25 @@ uint32_t dvxMemGetAppUsage(int32_t appId) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
return sAppMemUsed[appId];
|
||||
uint32_t atLoad = sAppMemAtLoad[appId];
|
||||
|
||||
if (atLoad == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t nowFree = dpmiGetFreeKb();
|
||||
|
||||
if (nowFree >= atLoad) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (atLoad - nowFree) * 1024;
|
||||
}
|
||||
|
||||
|
||||
void dvxMemResetApp(int32_t appId) {
|
||||
if (appId >= 0 && appId < sAppMemCap) {
|
||||
sAppMemUsed[appId] = 0;
|
||||
sAppMemAtLoad[appId] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,9 +49,9 @@ WidgetT *sOpenPopup = NULL; // dropdown/combobox with open popup list
|
|||
WidgetT *sDragWidget = NULL; // widget being dragged (any drag type)
|
||||
|
||||
// Shared clipboard -- process-wide, not per-widget.
|
||||
#define CLIPBOARD_MAX 4096
|
||||
static char sClipboard[CLIPBOARD_MAX];
|
||||
static char *sClipboard = NULL;
|
||||
static int32_t sClipboardLen = 0;
|
||||
static int32_t sClipboardCap = 0;
|
||||
|
||||
// Multi-click state (used by widgetEvent.c for universal dbl-click detection)
|
||||
static clock_t sLastClickTime = 0;
|
||||
|
|
@ -69,8 +69,16 @@ void clipboardCopy(const char *text, int32_t len) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (len > CLIPBOARD_MAX - 1) {
|
||||
len = CLIPBOARD_MAX - 1;
|
||||
if (len + 1 > sClipboardCap) {
|
||||
int32_t newCap = len + 1;
|
||||
char *newBuf = (char *)realloc(sClipboard, newCap);
|
||||
|
||||
if (!newBuf) {
|
||||
return;
|
||||
}
|
||||
|
||||
sClipboard = newBuf;
|
||||
sClipboardCap = newCap;
|
||||
}
|
||||
|
||||
memcpy(sClipboard, text, len);
|
||||
|
|
@ -97,7 +105,7 @@ const char *clipboardGet(int32_t *outLen) {
|
|||
// ============================================================
|
||||
|
||||
int32_t clipboardMaxLen(void) {
|
||||
return CLIPBOARD_MAX - 1;
|
||||
return 65536;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -378,59 +386,47 @@ WidgetT *widgetFindNextFocusable(WidgetT *root, WidgetT *after) {
|
|||
// ============================================================
|
||||
//
|
||||
// Shift+Tab navigation: finds the previous focusable widget.
|
||||
// Unlike findNextFocusable which can short-circuit during traversal,
|
||||
// finding the PREVIOUS widget requires knowing the full order.
|
||||
// So this collects all focusable widgets into an array, finds the
|
||||
// target's index, and returns index-1 (with wraparound).
|
||||
//
|
||||
// The explicit stack-based DFS (rather than recursion) is used here
|
||||
// because we need to push children in reverse order to get the same
|
||||
// left-to-right depth-first ordering as the recursive version.
|
||||
// Fixed-size arrays (128 widgets, 64 stack depth) are adequate for
|
||||
// any reasonable dialog layout and avoid dynamic allocation.
|
||||
// Collects all focusable widgets via DFS, then returns the one
|
||||
// before 'before' (with wraparound). Uses stb_ds dynamic arrays
|
||||
// so there's no fixed limit on widget count or tree depth.
|
||||
|
||||
WidgetT *widgetFindPrevFocusable(WidgetT *root, WidgetT *before) {
|
||||
WidgetT *list[128];
|
||||
int32_t count = 0;
|
||||
WidgetT **list = NULL;
|
||||
WidgetT **stack = NULL;
|
||||
|
||||
// Collect all focusable widgets via depth-first traversal
|
||||
WidgetT *stack[64];
|
||||
int32_t top = 0;
|
||||
stack[top++] = root;
|
||||
arrput(stack, root);
|
||||
|
||||
while (top > 0) {
|
||||
WidgetT *w = stack[--top];
|
||||
while (arrlen(stack) > 0) {
|
||||
WidgetT *w = stack[arrlen(stack) - 1];
|
||||
arrsetlen(stack, arrlen(stack) - 1);
|
||||
|
||||
if (!w->visible || !w->enabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (widgetIsFocusable(w->type) && count < 128) {
|
||||
list[count++] = w;
|
||||
if (widgetIsFocusable(w->type)) {
|
||||
arrput(list, w);
|
||||
}
|
||||
|
||||
// Push children in reverse order so first child is processed first
|
||||
WidgetT *children[64];
|
||||
int32_t childCount = 0;
|
||||
// Walk to end of sibling list, then push backwards
|
||||
WidgetT **children = NULL;
|
||||
|
||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
||||
if (childCount < 64) {
|
||||
children[childCount++] = c;
|
||||
}
|
||||
arrput(children, c);
|
||||
}
|
||||
|
||||
for (int32_t i = childCount - 1; i >= 0; i--) {
|
||||
if (top < 64) {
|
||||
stack[top++] = children[i];
|
||||
}
|
||||
}
|
||||
for (int32_t i = arrlen(children) - 1; i >= 0; i--) {
|
||||
arrput(stack, children[i]);
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
return NULL;
|
||||
arrfree(children);
|
||||
}
|
||||
|
||||
// Find 'before' in the list
|
||||
WidgetT *result = NULL;
|
||||
int32_t count = arrlen(list);
|
||||
|
||||
if (count > 0) {
|
||||
int32_t idx = -1;
|
||||
|
||||
for (int32_t i = 0; i < count; i++) {
|
||||
|
|
@ -440,11 +436,12 @@ WidgetT *widgetFindPrevFocusable(WidgetT *root, WidgetT *before) {
|
|||
}
|
||||
}
|
||||
|
||||
if (idx <= 0) {
|
||||
return list[count - 1]; // Wrap to last
|
||||
result = (idx <= 0) ? list[count - 1] : list[idx - 1];
|
||||
}
|
||||
|
||||
return list[idx - 1];
|
||||
arrfree(list);
|
||||
arrfree(stack);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -172,14 +172,8 @@ void widgetOnKey(WindowT *win, int32_t key, int32_t mod) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Attribute allocations during event handling to the owning app
|
||||
AppContextT *ctx = (AppContextT *)root->userData;
|
||||
int32_t prevAppId = ctx->currentAppId;
|
||||
ctx->currentAppId = win->appId;
|
||||
|
||||
// Dispatch to per-widget onKey handler via vtable
|
||||
wclsOnKey(focus, key, mod);
|
||||
|
||||
ctx->currentAppId = prevAppId;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -201,8 +195,6 @@ void widgetOnKey(WindowT *win, int32_t key, int32_t mod) {
|
|||
// are also in content-buffer space (set during layout), so no
|
||||
// coordinate transform is needed for hit testing.
|
||||
|
||||
static void widgetOnMouseInner(WindowT *win, WidgetT *root, int32_t x, int32_t y, int32_t buttons);
|
||||
|
||||
void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
||||
WidgetT *root = win->widgetRoot;
|
||||
sClosedPopup = NULL;
|
||||
|
|
@ -210,19 +202,6 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
|||
if (!root) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Attribute allocations during event handling to the owning app
|
||||
AppContextT *ctx = (AppContextT *)root->userData;
|
||||
int32_t prevAppId = ctx->currentAppId;
|
||||
ctx->currentAppId = win->appId;
|
||||
|
||||
widgetOnMouseInner(win, root, x, y, buttons);
|
||||
|
||||
ctx->currentAppId = prevAppId;
|
||||
}
|
||||
|
||||
|
||||
static void widgetOnMouseInner(WindowT *win, WidgetT *root, int32_t x, int32_t y, int32_t buttons) {
|
||||
// Close popups from other windows
|
||||
if (sOpenPopup && sOpenPopup->window != win) {
|
||||
wclsClosePopup(sOpenPopup);
|
||||
|
|
|
|||
|
|
@ -82,11 +82,15 @@ static int32_t allocSlot(void) {
|
|||
// this function never completes -- the task is killed externally via
|
||||
// shellForceKillApp + tsKill.
|
||||
static void appTaskWrapper(void *arg) {
|
||||
ShellAppT *app = (ShellAppT *)arg;
|
||||
|
||||
app->dxeCtx.shellCtx->currentAppId = app->appId;
|
||||
app->entryFn(&app->dxeCtx);
|
||||
app->dxeCtx.shellCtx->currentAppId = 0;
|
||||
// Look up the app by ID, not by pointer. The sApps array may have
|
||||
// been reallocated between tsCreate and the first time this task runs
|
||||
// (e.g. another app was loaded in the meantime), which would
|
||||
// invalidate a direct ShellAppT pointer.
|
||||
int32_t appId = (int32_t)(intptr_t)arg;
|
||||
ShellAppT *app = &sApps[appId];
|
||||
app->dxeCtx->shellCtx->currentAppId = app->appId;
|
||||
app->entryFn(app->dxeCtx);
|
||||
app->dxeCtx->shellCtx->currentAppId = 0;
|
||||
|
||||
// App returned from its main loop -- mark for reaping
|
||||
app->state = AppStateTerminatingE;
|
||||
|
|
@ -261,6 +265,8 @@ void shellForceKillApp(AppContextT *ctx, ShellAppT *app) {
|
|||
}
|
||||
|
||||
cleanupTempFile(app);
|
||||
free(app->dxeCtx);
|
||||
app->dxeCtx = NULL;
|
||||
dvxMemResetApp(app->appId);
|
||||
app->state = AppStateFreeE;
|
||||
dvxLog("Shell: force-killed app '%s'", app->name);
|
||||
|
|
@ -399,29 +405,39 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path) {
|
|||
app->shutdownFn = shutdown;
|
||||
app->state = AppStateLoadedE;
|
||||
|
||||
// Set up the context passed to appMain
|
||||
app->dxeCtx.shellCtx = ctx;
|
||||
app->dxeCtx.appId = id;
|
||||
// Heap-allocate the DxeAppContextT so its address is stable across
|
||||
// sApps reallocs. Apps save this pointer in their static globals.
|
||||
app->dxeCtx = (DxeAppContextT *)calloc(1, sizeof(DxeAppContextT));
|
||||
|
||||
if (!app->dxeCtx) {
|
||||
ctx->currentAppId = 0;
|
||||
dlclose(handle);
|
||||
app->state = AppStateFreeE;
|
||||
return -1;
|
||||
}
|
||||
|
||||
app->dxeCtx->shellCtx = ctx;
|
||||
app->dxeCtx->appId = id;
|
||||
|
||||
// Derive app directory from path (everything up to the last separator).
|
||||
// This lets apps load resources relative to their own location rather
|
||||
// than the shell's working directory.
|
||||
snprintf(app->dxeCtx.appDir, sizeof(app->dxeCtx.appDir), "%s", path);
|
||||
snprintf(app->dxeCtx->appDir, sizeof(app->dxeCtx->appDir), "%s", path);
|
||||
|
||||
char *sep = platformPathDirEnd(app->dxeCtx.appDir);
|
||||
char *sep = platformPathDirEnd(app->dxeCtx->appDir);
|
||||
|
||||
if (sep) {
|
||||
*sep = '\0';
|
||||
} else {
|
||||
app->dxeCtx.appDir[0] = '.';
|
||||
app->dxeCtx.appDir[1] = '\0';
|
||||
app->dxeCtx->appDir[0] = '.';
|
||||
app->dxeCtx->appDir[1] = '\0';
|
||||
}
|
||||
|
||||
// Derive config directory: replace the APPS/ prefix with CONFIG/.
|
||||
// e.g. "APPS/GAMES/TETRIS" -> "CONFIG/GAMES/TETRIS"
|
||||
// If the path doesn't start with "apps/" or "APPS/", fall back to
|
||||
// "CONFIG/<appname>" using the descriptor name.
|
||||
const char *appDirStr = app->dxeCtx.appDir;
|
||||
const char *appDirStr = app->dxeCtx->appDir;
|
||||
const char *appsPrefix = NULL;
|
||||
|
||||
if (strncasecmp(appDirStr, "apps/", 5) == 0 || strncasecmp(appDirStr, "apps\\", 5) == 0) {
|
||||
|
|
@ -431,9 +447,9 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path) {
|
|||
}
|
||||
|
||||
if (appsPrefix && appsPrefix[0]) {
|
||||
snprintf(app->dxeCtx.configDir, sizeof(app->dxeCtx.configDir), "CONFIG/%s", appsPrefix);
|
||||
snprintf(app->dxeCtx->configDir, sizeof(app->dxeCtx->configDir), "CONFIG/%s", appsPrefix);
|
||||
} else {
|
||||
snprintf(app->dxeCtx.configDir, sizeof(app->dxeCtx.configDir), "CONFIG/%s", desc->name);
|
||||
snprintf(app->dxeCtx->configDir, sizeof(app->dxeCtx->configDir), "CONFIG/%s", desc->name);
|
||||
}
|
||||
|
||||
// Launch. Set currentAppId before any app code runs so that
|
||||
|
|
@ -444,12 +460,14 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path) {
|
|||
uint32_t stackSize = desc->stackSize > 0 ? (uint32_t)desc->stackSize : TS_DEFAULT_STACK_SIZE;
|
||||
int32_t priority = desc->priority;
|
||||
|
||||
int32_t taskId = tsCreate(desc->name, appTaskWrapper, app, stackSize, priority);
|
||||
int32_t taskId = tsCreate(desc->name, appTaskWrapper, (void *)(intptr_t)id, stackSize, priority);
|
||||
|
||||
if (taskId < 0) {
|
||||
ctx->currentAppId = 0;
|
||||
dvxMessageBox(ctx, "Error", "Failed to create task for application.", MB_OK | MB_ICONERROR);
|
||||
dlclose(handle);
|
||||
free(app->dxeCtx);
|
||||
app->dxeCtx = NULL;
|
||||
app->state = AppStateFreeE;
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -460,7 +478,7 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path) {
|
|||
// The app creates its windows and returns. From this point on,
|
||||
// the app lives entirely through event callbacks dispatched by
|
||||
// the shell's dvxUpdate loop. No separate task or stack needed.
|
||||
app->entryFn(&app->dxeCtx);
|
||||
app->entryFn(app->dxeCtx);
|
||||
}
|
||||
|
||||
ctx->currentAppId = 0;
|
||||
|
|
@ -511,6 +529,8 @@ void shellReapApp(AppContextT *ctx, ShellAppT *app) {
|
|||
}
|
||||
|
||||
cleanupTempFile(app);
|
||||
free(app->dxeCtx);
|
||||
app->dxeCtx = NULL;
|
||||
dvxMemResetApp(app->appId);
|
||||
dvxLog("Shell: reaped app '%s'", app->name);
|
||||
app->state = AppStateFreeE;
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ typedef struct {
|
|||
uint32_t mainTaskId; // task ID if hasMainLoop, else 0
|
||||
int32_t (*entryFn)(DxeAppContextT *);
|
||||
void (*shutdownFn)(void); // may be NULL
|
||||
DxeAppContextT dxeCtx; // context passed to appMain
|
||||
DxeAppContextT *dxeCtx; // heap-allocated; address stable across sApps realloc
|
||||
} ShellAppT;
|
||||
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -374,7 +374,7 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_
|
|||
// Ctrl+Z -- undo
|
||||
if (key == 26 && undoBuf && pUndoLen && pUndoCursor) {
|
||||
// Swap current and undo
|
||||
char tmpBuf[clipboardMaxLen() + 1];
|
||||
char tmpBuf[*pLen + 1];
|
||||
int32_t tmpLen = *pLen;
|
||||
int32_t tmpCursor = *pCursor;
|
||||
int32_t copyLen = tmpLen < (int32_t)sizeof(tmpBuf) - 1 ? tmpLen : (int32_t)sizeof(tmpBuf) - 1;
|
||||
|
|
|
|||
|
|
@ -111,7 +111,6 @@ typedef struct {
|
|||
#define TEXTAREA_SB_W 14
|
||||
#define TEXTAREA_MIN_ROWS 4
|
||||
#define TEXTAREA_MIN_COLS 20
|
||||
#define CLIPBOARD_MAX 4096
|
||||
// Match the ANSI terminal cursor blink rate (CURSOR_MS in widgetAnsiTerm.c)
|
||||
#define CURSOR_BLINK_MS 250
|
||||
|
||||
|
|
@ -950,7 +949,7 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
if (key == 26) {
|
||||
if (ta->undoBuf && ta->undoLen >= 0) {
|
||||
// Swap current and undo
|
||||
char tmpBuf[CLIPBOARD_MAX];
|
||||
char tmpBuf[*pLen + 1];
|
||||
int32_t tmpLen = *pLen;
|
||||
int32_t tmpCursor = CUR_OFF();
|
||||
int32_t copyLen = tmpLen < (int32_t)sizeof(tmpBuf) - 1 ? tmpLen : (int32_t)sizeof(tmpBuf) - 1;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue