Code optimization and cleanup.

This commit is contained in:
Scott Duensing 2026-03-16 20:13:16 -05:00
parent 85f0e5be56
commit d0e308d7cf
27 changed files with 1484 additions and 1628 deletions

View file

@ -16,6 +16,7 @@ SRCS = dvxVideo.c dvxDraw.c dvxComp.c dvxWm.c dvxIcon.c dvxImageWrite.c dvxApp
WSRCS = widgets/widgetAnsiTerm.c \
widgets/widgetClass.c \
widgets/widgetCore.c \
widgets/widgetScrollbar.c \
widgets/widgetLayout.c \
widgets/widgetEvent.c \
widgets/widgetOps.c \
@ -112,6 +113,7 @@ $(WOBJDIR)/widgetStatusBar.o: widgets/widgetStatusBar.c $(WIDGET_DEPS)
$(WOBJDIR)/widgetTabControl.o: widgets/widgetTabControl.c $(WIDGET_DEPS)
$(WOBJDIR)/widgetTextInput.o: widgets/widgetTextInput.c $(WIDGET_DEPS)
$(WOBJDIR)/widgetToolbar.o: widgets/widgetToolbar.c $(WIDGET_DEPS)
$(WOBJDIR)/widgetScrollbar.o: widgets/widgetScrollbar.c $(WIDGET_DEPS)
$(WOBJDIR)/widgetTreeView.o: widgets/widgetTreeView.c $(WIDGET_DEPS)
clean:

View file

@ -53,6 +53,7 @@ static void pollAnsiTermWidgetsWalk(AppContextT *ctx, WidgetT *w, WindowT *win);
static void pollKeyboard(AppContextT *ctx);
static void pollMouse(AppContextT *ctx);
static void refreshMinimizedIcons(AppContextT *ctx);
static void repositionWindow(AppContextT *ctx, WindowT *win, int32_t x, int32_t y, int32_t w, int32_t h);
static void updateCursorShape(AppContextT *ctx);
static void updateTooltip(AppContextT *ctx);
@ -178,19 +179,11 @@ static bool checkAccelTable(AppContextT *ctx, WindowT *win, int32_t key, int32_t
int32_t requiredMods = modifiers & (ACCEL_CTRL | ACCEL_ALT);
AccelTableT *table = win->accelTable;
// Match against pre-normalized keys (Item 6)
for (int32_t i = 0; i < table->count; i++) {
AccelEntryT *e = &table->entries[i];
// Uppercase the entry key too
int32_t entryKey = e->key;
if (entryKey >= 'a' && entryKey <= 'z') {
entryKey = entryKey - 32;
}
int32_t entryMods = e->modifiers & (ACCEL_CTRL | ACCEL_ALT);
if (entryKey == matchKey && entryMods == requiredMods) {
if (e->normKey == matchKey && e->normMods == requiredMods) {
win->onMenu(win, e->cmdId);
return true;
}
@ -313,6 +306,18 @@ static void compositeAndFlush(AppContextT *ctx) {
dirtyListMerge(dl);
// Pre-filter visible, non-minimized windows once (Item 7)
int32_t visibleIdx[MAX_WINDOWS];
int32_t visibleCount = 0;
for (int32_t j = 0; j < ws->count; j++) {
WindowT *win = ws->windows[j];
if (win->visible && !win->minimized) {
visibleIdx[visibleCount++] = j;
}
}
for (int32_t i = 0; i < dl->count; i++) {
RectT *dr = &dl->rects[i];
@ -332,13 +337,9 @@ static void compositeAndFlush(AppContextT *ctx) {
// 2. Draw minimized window icons (under all windows)
wmDrawMinimizedIcons(d, ops, &ctx->colors, ws, dr);
// 3. Walk window stack bottom-to-top
for (int32_t j = 0; j < ws->count; j++) {
WindowT *win = ws->windows[j];
if (!win->visible || win->minimized) {
continue;
}
// 3. Walk pre-filtered visible window list bottom-to-top
for (int32_t vi = 0; vi < visibleCount; vi++) {
WindowT *win = ws->windows[visibleIdx[vi]];
// Check if window intersects this dirty rect
RectT winRect = {win->x, win->y, win->w, win->h};
@ -660,7 +661,7 @@ static void dispatchEvents(AppContextT *ctx) {
// Hover tracking
int32_t relY = my - ctx->sysMenu.popupY - 2;
int32_t itemIdx = relY / ctx->font.charHeight;
int32_t itemIdx = (relY >= 0) ? (int32_t)((uint32_t)relY * ctx->charHeightRecip >> 16) : 0;
if (itemIdx >= 0 && itemIdx < ctx->sysMenu.itemCount && ctx->sysMenu.items[itemIdx].separator) {
itemIdx = -1;
@ -700,7 +701,7 @@ static void dispatchEvents(AppContextT *ctx) {
if (inCurrent) {
// Hover tracking in current level
int32_t relY = my - ctx->popup.popupY - 2;
int32_t itemIdx = relY / ctx->font.charHeight;
int32_t itemIdx = (relY >= 0) ? (int32_t)((uint32_t)relY * ctx->charHeightRecip >> 16) : 0;
if (itemIdx < 0) {
itemIdx = 0;
@ -780,7 +781,7 @@ static void dispatchEvents(AppContextT *ctx) {
// Now current level IS this parent — update hover
int32_t relY = my - ctx->popup.popupY - 2;
int32_t itemIdx = relY / ctx->font.charHeight;
int32_t itemIdx = (relY >= 0) ? (int32_t)((uint32_t)relY * ctx->charHeightRecip >> 16) : 0;
if (itemIdx < 0) {
itemIdx = 0;
@ -1063,10 +1064,19 @@ void dvxAddAccel(AccelTableT *table, int32_t key, int32_t modifiers, int32_t cmd
return;
}
// Pre-normalize key for fast matching (Item 6)
int32_t normKey = key;
if (normKey >= 'a' && normKey <= 'z') {
normKey = normKey - 32;
}
AccelEntryT *e = &table->entries[table->count++];
e->key = key;
e->modifiers = modifiers;
e->cmdId = cmdId;
e->normKey = normKey;
e->normMods = modifiers & (ACCEL_CTRL | ACCEL_ALT);
}
@ -1121,34 +1131,7 @@ void dvxCascadeWindows(AppContextT *ctx) {
continue;
}
// Un-maximize if needed
if (win->maximized) {
win->maximized = false;
}
// Dirty old position
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
win->x = offsetX;
win->y = offsetY;
win->w = winW;
win->h = winH;
wmUpdateContentRect(win);
wmReallocContentBuf(win, &ctx->display);
if (win->onResize) {
win->onResize(win, win->contentW, win->contentH);
}
if (win->onPaint) {
RectT fullRect = {0, 0, win->contentW, win->contentH};
win->onPaint(win, &fullRect);
win->contentDirty = true;
}
// Dirty new position
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
repositionWindow(ctx, win, offsetX, offsetY, winW, winH);
offsetX += step;
offsetY += step;
@ -1316,6 +1299,7 @@ int32_t dvxInit(AppContextT *ctx, int32_t requestedW, int32_t requestedH, int32_
ctx->lastIconClickTime = 0;
ctx->lastCloseClickId = -1;
ctx->lastCloseClickTime = 0;
ctx->charHeightRecip = ((uint32_t)0x10000 + (uint32_t)ctx->font.charHeight - 1) / (uint32_t)ctx->font.charHeight;
// Dirty the entire screen for first paint
dirtyListAdd(&ctx->dirty, 0, 0, ctx->display.width, ctx->display.height);
@ -1573,12 +1557,6 @@ void dvxTileWindows(AppContextT *ctx) {
continue;
}
if (win->maximized) {
win->maximized = false;
}
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
int32_t row = slot / cols;
int32_t col = slot % cols;
@ -1587,25 +1565,7 @@ void dvxTileWindows(AppContextT *ctx) {
int32_t rowCols = (remaining < cols) ? remaining : cols;
int32_t cellW = screenW / rowCols;
win->x = col * cellW;
win->y = row * tileH;
win->w = cellW;
win->h = tileH;
wmUpdateContentRect(win);
wmReallocContentBuf(win, &ctx->display);
if (win->onResize) {
win->onResize(win, win->contentW, win->contentH);
}
if (win->onPaint) {
RectT fullRect = {0, 0, win->contentW, win->contentH};
win->onPaint(win, &fullRect);
win->contentDirty = true;
}
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
repositionWindow(ctx, win, col * cellW, row * tileH, cellW, tileH);
slot++;
}
@ -1653,31 +1613,7 @@ void dvxTileWindowsH(AppContextT *ctx) {
continue;
}
if (win->maximized) {
win->maximized = false;
}
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
win->x = slot * tileW;
win->y = 0;
win->w = tileW;
win->h = screenH;
wmUpdateContentRect(win);
wmReallocContentBuf(win, &ctx->display);
if (win->onResize) {
win->onResize(win, win->contentW, win->contentH);
}
if (win->onPaint) {
RectT fullRect = {0, 0, win->contentW, win->contentH};
win->onPaint(win, &fullRect);
win->contentDirty = true;
}
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
repositionWindow(ctx, win, slot * tileW, 0, tileW, screenH);
slot++;
}
@ -1725,31 +1661,7 @@ void dvxTileWindowsV(AppContextT *ctx) {
continue;
}
if (win->maximized) {
win->maximized = false;
}
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
win->x = 0;
win->y = slot * tileH;
win->w = screenW;
win->h = tileH;
wmUpdateContentRect(win);
wmReallocContentBuf(win, &ctx->display);
if (win->onResize) {
win->onResize(win, win->contentW, win->contentH);
}
if (win->onPaint) {
RectT fullRect = {0, 0, win->contentW, win->contentH};
win->onPaint(win, &fullRect);
win->contentDirty = true;
}
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
repositionWindow(ctx, win, 0, slot * tileH, screenW, tileH);
slot++;
}
@ -3038,6 +2950,42 @@ static void refreshMinimizedIcons(AppContextT *ctx) {
}
// ============================================================
// repositionWindow — move/resize a window, dirty old & new, fire callbacks
// ============================================================
static void repositionWindow(AppContextT *ctx, WindowT *win, int32_t x, int32_t y, int32_t w, int32_t h) {
// Dirty old position
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
// Un-maximize if needed
if (win->maximized) {
win->maximized = false;
}
win->x = x;
win->y = y;
win->w = w;
win->h = h;
wmUpdateContentRect(win);
wmReallocContentBuf(win, &ctx->display);
if (win->onResize) {
win->onResize(win, win->contentW, win->contentH);
}
if (win->onPaint) {
RectT fullRect = {0, 0, win->contentW, win->contentH};
win->onPaint(win, &fullRect);
win->contentDirty = true;
}
// Dirty new position
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
}
// ============================================================
// updateCursorShape
// ============================================================

View file

@ -52,6 +52,7 @@ typedef struct AppContextT {
int32_t tooltipY;
int32_t tooltipW; // size (pre-computed)
int32_t tooltipH;
uint32_t charHeightRecip; // fixed-point 16.16 reciprocal of font.charHeight
} AppContextT;
// Initialize the application (VESA mode, input, etc.)

View file

@ -77,13 +77,15 @@ void dirtyListMerge(DirtyListT *dl) {
return;
}
// Single-pass O(N²): for each rect, try to merge it into an
// earlier rect. When a merge succeeds the merged rect may now
// overlap others, so restart the inner scan for that slot.
// O(N²) with bounded restarts: for each rect, try to merge it
// into an earlier rect. When a merge succeeds the merged rect
// may now overlap others, so restart the inner scan — but cap
// restarts per slot to avoid O(N³) pathological cascades.
for (int32_t i = 0; i < dl->count; i++) {
int32_t restarts = 0;
bool merged = true;
while (merged) {
while (merged && restarts < 3) {
merged = false;
for (int32_t j = i + 1; j < dl->count; j++) {
@ -95,6 +97,8 @@ void dirtyListMerge(DirtyListT *dl) {
merged = true;
}
}
restarts++;
}
}
}

View file

@ -185,24 +185,56 @@ int32_t drawChar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int3
if (x < clipX1) { colStart = clipX1 - x; }
if (x + cw > clipX2) { colEnd = clipX2 - x; }
// Unclipped fast path: full 8-pixel character cell with direct bit
// tests eliminates loop overhead and sGlyphBit[] lookups (Item 4)
bool unclipped = (colStart == 0 && colEnd == cw);
if (opaque) {
// Opaque mode: fill entire cell with bg, then overwrite fg pixels
// This avoids a branch per pixel for the common case
if (unclipped && bpp == 4) {
for (int32_t row = rowStart; row < rowEnd; row++) {
uint32_t *dst32 = (uint32_t *)(d->backBuf + (y + row) * pitch + x * 4);
uint8_t bits = glyph[row];
dst32[0] = (bits & 0x80) ? fg : bg;
dst32[1] = (bits & 0x40) ? fg : bg;
dst32[2] = (bits & 0x20) ? fg : bg;
dst32[3] = (bits & 0x10) ? fg : bg;
dst32[4] = (bits & 0x08) ? fg : bg;
dst32[5] = (bits & 0x04) ? fg : bg;
dst32[6] = (bits & 0x02) ? fg : bg;
dst32[7] = (bits & 0x01) ? fg : bg;
}
} else if (unclipped && bpp == 2) {
uint16_t fg16 = (uint16_t)fg;
uint16_t bg16 = (uint16_t)bg;
for (int32_t row = rowStart; row < rowEnd; row++) {
uint16_t *dst16 = (uint16_t *)(d->backBuf + (y + row) * pitch + x * 2);
uint8_t bits = glyph[row];
dst16[0] = (bits & 0x80) ? fg16 : bg16;
dst16[1] = (bits & 0x40) ? fg16 : bg16;
dst16[2] = (bits & 0x20) ? fg16 : bg16;
dst16[3] = (bits & 0x10) ? fg16 : bg16;
dst16[4] = (bits & 0x08) ? fg16 : bg16;
dst16[5] = (bits & 0x04) ? fg16 : bg16;
dst16[6] = (bits & 0x02) ? fg16 : bg16;
dst16[7] = (bits & 0x01) ? fg16 : bg16;
}
} else {
// Clipped path or 8bpp: spanFill bg then overwrite fg
for (int32_t row = rowStart; row < rowEnd; row++) {
int32_t py = y + row;
uint8_t *dst = d->backBuf + py * pitch + (x + colStart) * bpp;
int32_t span = colEnd - colStart;
// Fill row with background
ops->spanFill(dst, bg, span);
ops->spanFill(dst, bg, colEnd - colStart);
// Overwrite foreground pixels
uint8_t bits = glyph[row];
if (bits == 0) {
continue; // entirely background row — already filled
continue;
}
// Shift bits to account for colStart
dst = d->backBuf + py * pitch + x * bpp;
if (bpp == 2) {
@ -227,8 +259,49 @@ int32_t drawChar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int3
}
}
}
}
} else {
// Transparent mode: only write foreground pixels
if (unclipped && bpp == 4) {
for (int32_t row = rowStart; row < rowEnd; row++) {
uint8_t bits = glyph[row];
if (bits == 0) {
continue;
}
uint32_t *dst32 = (uint32_t *)(d->backBuf + (y + row) * pitch + x * 4);
if (bits & 0x80) { dst32[0] = fg; }
if (bits & 0x40) { dst32[1] = fg; }
if (bits & 0x20) { dst32[2] = fg; }
if (bits & 0x10) { dst32[3] = fg; }
if (bits & 0x08) { dst32[4] = fg; }
if (bits & 0x04) { dst32[5] = fg; }
if (bits & 0x02) { dst32[6] = fg; }
if (bits & 0x01) { dst32[7] = fg; }
}
} else if (unclipped && bpp == 2) {
uint16_t fg16 = (uint16_t)fg;
for (int32_t row = rowStart; row < rowEnd; row++) {
uint8_t bits = glyph[row];
if (bits == 0) {
continue;
}
uint16_t *dst16 = (uint16_t *)(d->backBuf + (y + row) * pitch + x * 2);
if (bits & 0x80) { dst16[0] = fg16; }
if (bits & 0x40) { dst16[1] = fg16; }
if (bits & 0x20) { dst16[2] = fg16; }
if (bits & 0x10) { dst16[3] = fg16; }
if (bits & 0x08) { dst16[4] = fg16; }
if (bits & 0x04) { dst16[5] = fg16; }
if (bits & 0x02) { dst16[6] = fg16; }
if (bits & 0x01) { dst16[7] = fg16; }
}
} else {
// Clipped path or 8bpp
for (int32_t row = rowStart; row < rowEnd; row++) {
uint8_t bits = glyph[row];
if (bits == 0) {
@ -261,6 +334,7 @@ int32_t drawChar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int3
}
}
}
}
return cw;
}
@ -916,14 +990,31 @@ void rectFill(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w,
return;
}
uint8_t *row = d->backBuf + y * d->pitch + x * d->format.bytesPerPixel;
int32_t bpp = d->format.bytesPerPixel;
uint8_t *row = d->backBuf + y * d->pitch + x * bpp;
int32_t pitch = d->pitch;
// Inline rep stosl for 32bpp — avoids function pointer call overhead
// per scanline which is significant for the many 1px fills (Item 10)
if (__builtin_expect(bpp == 4, 1)) {
for (int32_t i = 0; i < h; i++) {
int32_t count = w;
uint8_t *dst = row;
__asm__ __volatile__ (
"rep stosl"
: "+D"(dst), "+c"(count)
: "a"(color)
: "memory"
);
row += pitch;
}
} else {
for (int32_t i = 0; i < h; i++) {
ops->spanFill(row, color, w);
row += pitch;
}
}
}
// ============================================================
@ -1009,11 +1100,13 @@ static void spanFill8(uint8_t *dst, uint32_t color, int32_t count) {
uint8_t c = (uint8_t)color;
uint32_t dword = (uint32_t)c | ((uint32_t)c << 8) | ((uint32_t)c << 16) | ((uint32_t)c << 24);
// Align to 4 bytes
// Align to 4 bytes — skip if already aligned (Item 11)
if (__builtin_expect((uintptr_t)dst & 3, 0)) {
while (((uintptr_t)dst & 3) && count > 0) {
*dst++ = c;
count--;
}
}
if (count >= 4) {
int32_t dwordCount = count >> 2;

View file

@ -184,6 +184,7 @@ struct MenuT {
typedef struct {
MenuT menus[MAX_MENUS];
int32_t menuCount;
bool positionsDirty; // true = barX/barW need recomputation
} MenuBarT;
// ============================================================
@ -243,6 +244,8 @@ typedef struct {
int32_t key; // key code: ASCII char or KEY_Fxx for extended
int32_t modifiers; // ACCEL_CTRL, ACCEL_SHIFT, ACCEL_ALT (OR'd together)
int32_t cmdId; // command ID passed to onMenu
int32_t normKey; // pre-normalized key (uppercased) for fast matching
int32_t normMods; // pre-masked modifiers (CTRL|ALT only) for fast matching
} AccelEntryT;
typedef struct {

View file

@ -132,6 +132,83 @@ typedef enum {
InputMaskedE // format mask (e.g. "(###) ###-####")
} InputModeE;
// ============================================================
// Large widget data (separately allocated to reduce WidgetT size)
// ============================================================
typedef struct {
uint8_t *cells; // character cells: (ch, attr) pairs, cols*rows*2 bytes
int32_t cols; // columns (default 80)
int32_t rows; // rows (default 25)
int32_t cursorRow; // 0-based cursor row
int32_t cursorCol; // 0-based cursor column
bool cursorVisible;
bool wrapMode; // auto-wrap at right margin
bool bold; // SGR bold flag (brightens foreground)
bool originMode; // cursor positioning relative to scroll region
bool csiPrivate; // '?' prefix in CSI sequence
uint8_t curAttr; // current text attribute (fg | bg<<4)
uint8_t parseState; // 0=normal, 1=ESC, 2=CSI
int32_t params[8]; // CSI parameter accumulator
int32_t paramCount; // number of CSI params collected
int32_t savedRow; // saved cursor position (SCP)
int32_t savedCol;
// Scrolling region (0-based, inclusive)
int32_t scrollTop; // top row of scroll region
int32_t scrollBot; // bottom row of scroll region
// Scrollback
uint8_t *scrollback; // circular buffer of scrollback lines
int32_t scrollbackMax; // max lines in scrollback buffer
int32_t scrollbackCount; // current number of lines stored
int32_t scrollbackHead; // write position (circular index)
int32_t scrollPos; // view position (scrollbackCount = live)
// Blink support
bool blinkVisible; // current blink phase (true = text visible)
clock_t blinkTime; // timestamp of last blink toggle
// Cursor blink
bool cursorOn; // current cursor blink phase
clock_t cursorTime; // timestamp of last cursor toggle
// Dirty tracking for fast repaint
uint32_t dirtyRows; // bitmask of rows needing repaint
int32_t lastCursorRow; // cursor row at last repaint
int32_t lastCursorCol; // cursor col at last repaint
// Cached packed palette (avoids packColor per repaint)
uint32_t packedPalette[16];
bool paletteValid;
// Selection (line indices in scrollback+screen space)
int32_t selStartLine;
int32_t selStartCol;
int32_t selEndLine;
int32_t selEndCol;
bool selecting;
// Communications interface (all NULL = disconnected)
void *commCtx;
int32_t (*commRead)(void *ctx, uint8_t *buf, int32_t maxLen);
int32_t (*commWrite)(void *ctx, const uint8_t *buf, int32_t len);
} AnsiTermDataT;
typedef struct {
const ListViewColT *cols;
int32_t colCount;
const char **cellData;
int32_t rowCount;
int32_t selectedIdx;
int32_t scrollPos;
int32_t scrollPosH;
int32_t sortCol;
ListViewSortE sortDir;
int32_t resolvedColW[LISTVIEW_MAX_COLS];
int32_t totalColW;
int32_t *sortIndex;
bool multiSelect;
int32_t anchorIdx;
uint8_t *selBits;
bool reorderable;
int32_t dragIdx;
int32_t dropIdx;
void (*onHeaderClick)(struct WidgetT *w, int32_t col, ListViewSortE dir);
} ListViewDataT;
// ============================================================
// Widget structure
// ============================================================
@ -142,6 +219,7 @@ typedef struct WidgetT {
WidgetTypeE type;
const struct WidgetClassT *wclass;
char name[MAX_WIDGET_NAME];
uint32_t nameHash; // djb2 hash of name, 0 if unnamed
// Tree linkage
struct WidgetT *parent;
@ -372,78 +450,9 @@ typedef struct WidgetT {
int32_t lastY;
} canvas;
struct {
uint8_t *cells; // character cells: (ch, attr) pairs, cols*rows*2 bytes
int32_t cols; // columns (default 80)
int32_t rows; // rows (default 25)
int32_t cursorRow; // 0-based cursor row
int32_t cursorCol; // 0-based cursor column
bool cursorVisible;
bool wrapMode; // auto-wrap at right margin
bool bold; // SGR bold flag (brightens foreground)
bool originMode; // cursor positioning relative to scroll region
bool csiPrivate; // '?' prefix in CSI sequence
uint8_t curAttr; // current text attribute (fg | bg<<4)
uint8_t parseState; // 0=normal, 1=ESC, 2=CSI
int32_t params[8]; // CSI parameter accumulator
int32_t paramCount; // number of CSI params collected
int32_t savedRow; // saved cursor position (SCP)
int32_t savedCol;
// Scrolling region (0-based, inclusive)
int32_t scrollTop; // top row of scroll region
int32_t scrollBot; // bottom row of scroll region
// Scrollback
uint8_t *scrollback; // circular buffer of scrollback lines
int32_t scrollbackMax; // max lines in scrollback buffer
int32_t scrollbackCount; // current number of lines stored
int32_t scrollbackHead; // write position (circular index)
int32_t scrollPos; // view position (scrollbackCount = live)
// Blink support
bool blinkVisible; // current blink phase (true = text visible)
clock_t blinkTime; // timestamp of last blink toggle
// Cursor blink
bool cursorOn; // current cursor blink phase
clock_t cursorTime; // timestamp of last cursor toggle
// Dirty tracking for fast repaint
uint32_t dirtyRows; // bitmask of rows needing repaint
int32_t lastCursorRow; // cursor row at last repaint
int32_t lastCursorCol; // cursor col at last repaint
// Cached packed palette (avoids packColor per repaint)
uint32_t packedPalette[16];
bool paletteValid;
// Selection (line indices in scrollback+screen space)
int32_t selStartLine;
int32_t selStartCol;
int32_t selEndLine;
int32_t selEndCol;
bool selecting;
// Communications interface (all NULL = disconnected)
void *commCtx;
int32_t (*commRead)(void *ctx, uint8_t *buf, int32_t maxLen);
int32_t (*commWrite)(void *ctx, const uint8_t *buf, int32_t len);
} ansiTerm;
AnsiTermDataT *ansiTerm;
struct {
const ListViewColT *cols;
int32_t colCount;
const char **cellData;
int32_t rowCount;
int32_t selectedIdx;
int32_t scrollPos;
int32_t scrollPosH;
int32_t sortCol;
ListViewSortE sortDir;
int32_t resolvedColW[LISTVIEW_MAX_COLS];
int32_t totalColW;
int32_t *sortIndex;
bool multiSelect;
int32_t anchorIdx;
uint8_t *selBits;
bool reorderable;
int32_t dragIdx;
int32_t dropIdx;
void (*onHeaderClick)(struct WidgetT *w, int32_t col, ListViewSortE dir);
} listView;
ListViewDataT *listView;
struct {
int32_t value;
@ -733,6 +742,9 @@ void wgtSetEnabled(WidgetT *w, bool enabled);
// Show/hide a widget
void wgtSetVisible(WidgetT *w, bool visible);
// Set a widget's name (for lookup via wgtFind)
void wgtSetName(WidgetT *w, const char *name);
// Find a widget by name (searches the subtree rooted at root)
WidgetT *wgtFind(WidgetT *root, const char *name);
@ -766,7 +778,7 @@ static inline void wgtSetTooltip(WidgetT *w, const char *text) { w->tooltip = te
// ============================================================
// Draw borders around layout containers in ugly colors
void wgtSetDebugLayout(bool enabled);
void wgtSetDebugLayout(struct AppContextT *ctx, bool enabled);
// ============================================================
// Layout (called internally; available for manual trigger)

View file

@ -64,6 +64,11 @@ static void computeMenuBarPositions(WindowT *win, const BitmapFontT *font) {
return;
}
// Skip recomputation if positions are already up to date (Item 5)
if (!win->menuBar->positionsDirty) {
return;
}
int32_t x = CHROME_TOTAL_SIDE;
for (int32_t i = 0; i < win->menuBar->menuCount; i++) {
@ -74,6 +79,8 @@ static void computeMenuBarPositions(WindowT *win, const BitmapFontT *font) {
menu->barW = labelW;
x += labelW;
}
win->menuBar->positionsDirty = false;
}
@ -284,20 +291,23 @@ static void drawScaledRect(DisplayT *d, int32_t dstX, int32_t dstY, int32_t dstW
return;
}
// Pre-compute source X lookup table (one division per column instead of per pixel)
int32_t srcXTab[ICON_SIZE];
// Pre-compute source lookup tables using fixed-point reciprocals
// to replace per-entry division with multiply+shift (Item 1).
// Static buffers avoid per-call stack allocation (Item 9).
static int32_t srcXTab[ICON_SIZE];
static int32_t srcYTab[ICON_SIZE];
int32_t visibleCols = colEnd - colStart;
for (int32_t dx = 0; dx < visibleCols; dx++) {
srcXTab[dx] = ((colStart + dx) * srcW) / dstW * bpp;
}
// Pre-compute source Y lookup table (one division per row instead of per row)
int32_t srcYTab[ICON_SIZE];
int32_t visibleRows = rowEnd - rowStart;
uint32_t recipW = ((uint32_t)srcW << 16) / (uint32_t)dstW;
uint32_t recipH = ((uint32_t)srcH << 16) / (uint32_t)dstH;
for (int32_t dx = 0; dx < visibleCols; dx++) {
srcXTab[dx] = (int32_t)(((uint32_t)(colStart + dx) * recipW) >> 16) * bpp;
}
for (int32_t dy = 0; dy < visibleRows; dy++) {
srcYTab[dy] = ((rowStart + dy) * srcH) / dstH;
srcYTab[dy] = (int32_t)(((uint32_t)(rowStart + dy) * recipH) >> 16);
}
// Blit with pre-computed lookups — no per-pixel divisions or clip checks
@ -600,6 +610,7 @@ MenuT *wmAddMenu(MenuBarT *bar, const char *label) {
menu->label[MAX_MENU_LABEL - 1] = '\0';
menu->accelKey = accelParse(label);
bar->menuCount++;
bar->positionsDirty = true;
return menu;
}

File diff suppressed because it is too large Load diff

View file

@ -40,9 +40,7 @@ WidgetT *wgtComboBox(WidgetT *parent, int32_t maxLen) {
// ============================================================
int32_t wgtComboBoxGetSelected(const WidgetT *w) {
if (!w || w->type != WidgetComboBoxE) {
return -1;
}
VALIDATE_WIDGET(w, WidgetComboBoxE, -1);
return w->as.comboBox.selectedIdx;
}
@ -53,9 +51,7 @@ int32_t wgtComboBoxGetSelected(const WidgetT *w) {
// ============================================================
void wgtComboBoxSetItems(WidgetT *w, const char **items, int32_t count) {
if (!w || w->type != WidgetComboBoxE) {
return;
}
VALIDATE_WIDGET_VOID(w, WidgetComboBoxE);
w->as.comboBox.items = items;
w->as.comboBox.itemCount = count;
@ -84,9 +80,7 @@ void wgtComboBoxSetItems(WidgetT *w, const char **items, int32_t count) {
// ============================================================
void wgtComboBoxSetSelected(WidgetT *w, int32_t idx) {
if (!w || w->type != WidgetComboBoxE) {
return;
}
VALIDATE_WIDGET_VOID(w, WidgetComboBoxE);
w->as.comboBox.selectedIdx = idx;
@ -402,40 +396,7 @@ void widgetComboBoxPaintPopup(WidgetT *w, DisplayT *d, const BlitOpsT *ops, cons
int32_t popH;
widgetDropdownPopupRect(w, font, d->clipH, &popX, &popY, &popW, &popH);
// Draw popup border
BevelStyleT bevel;
bevel.highlight = colors->windowHighlight;
bevel.shadow = colors->windowShadow;
bevel.face = colors->contentBg;
bevel.width = 2;
drawBevel(d, ops, popX, popY, popW, popH, &bevel);
// Draw items
int32_t itemCount = w->as.comboBox.itemCount;
const char **items = w->as.comboBox.items;
int32_t hoverIdx = w->as.comboBox.hoverIdx;
int32_t scrollPos = w->as.comboBox.listScrollPos;
int32_t visibleItems = popH / font->charHeight;
int32_t textX = popX + TEXT_INPUT_PAD;
int32_t textY = popY + 2;
int32_t textW = popW - TEXT_INPUT_PAD * 2 - 4;
for (int32_t i = 0; i < visibleItems && (scrollPos + i) < itemCount; i++) {
int32_t idx = scrollPos + i;
int32_t iy = textY + i * font->charHeight;
uint32_t ifg = colors->contentFg;
uint32_t ibg = colors->contentBg;
if (idx == hoverIdx) {
ifg = colors->menuHighlightFg;
ibg = colors->menuHighlightBg;
rectFill(d, ops, popX + 2, iy, textW + TEXT_INPUT_PAD * 2, font->charHeight, ibg);
}
drawText(d, ops, font, textX, iy, items[idx], ifg, ibg, false);
}
widgetPaintPopupList(d, ops, font, colors, popX, popY, popW, popH, w->as.comboBox.items, w->as.comboBox.itemCount, w->as.comboBox.hoverIdx, w->as.comboBox.listScrollPos);
}

View file

@ -423,6 +423,96 @@ bool widgetIsHorizContainer(WidgetTypeE type) {
}
// ============================================================
// widgetNavigateIndex
// ============================================================
//
// Shared Up/Down/Home/End/PgUp/PgDn handler for list-like widgets.
// Returns the new index, or -1 if the key is not a navigation key.
int32_t widgetNavigateIndex(int32_t key, int32_t current, int32_t count, int32_t pageSize) {
if (key == (0x50 | 0x100)) {
// Down arrow
if (current < count - 1) {
return current + 1;
}
return current < 0 ? 0 : current;
}
if (key == (0x48 | 0x100)) {
// Up arrow
if (current > 0) {
return current - 1;
}
return current < 0 ? 0 : current;
}
if (key == (0x47 | 0x100)) {
// Home
return 0;
}
if (key == (0x4F | 0x100)) {
// End
return count - 1;
}
if (key == (0x51 | 0x100)) {
// Page Down
int32_t n = current + pageSize;
return n >= count ? count - 1 : n;
}
if (key == (0x49 | 0x100)) {
// Page Up
int32_t n = current - pageSize;
return n < 0 ? 0 : n;
}
return -1;
}
// ============================================================
// widgetPaintPopupList
// ============================================================
//
// Shared popup list painting for Dropdown and ComboBox.
void widgetPaintPopupList(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, int32_t popX, int32_t popY, int32_t popW, int32_t popH, const char **items, int32_t itemCount, int32_t hoverIdx, int32_t scrollPos) {
// Draw popup border
BevelStyleT bevel;
bevel.highlight = colors->windowHighlight;
bevel.shadow = colors->windowShadow;
bevel.face = colors->contentBg;
bevel.width = 2;
drawBevel(d, ops, popX, popY, popW, popH, &bevel);
// Draw items
int32_t visibleItems = popH / font->charHeight;
int32_t textX = popX + TEXT_INPUT_PAD;
int32_t textY = popY + 2;
int32_t textW = popW - TEXT_INPUT_PAD * 2 - 4;
for (int32_t i = 0; i < visibleItems && (scrollPos + i) < itemCount; i++) {
int32_t idx = scrollPos + i;
int32_t iy = textY + i * font->charHeight;
uint32_t ifg = colors->contentFg;
uint32_t ibg = colors->contentBg;
if (idx == hoverIdx) {
ifg = colors->menuHighlightFg;
ibg = colors->menuHighlightBg;
rectFill(d, ops, popX + 2, iy, textW + TEXT_INPUT_PAD * 2, font->charHeight, ibg);
}
drawText(d, ops, font, textX, iy, items[idx], ifg, ibg, false);
}
}
// ============================================================
// widgetScrollbarThumb
// ============================================================

View file

@ -24,9 +24,7 @@ WidgetT *wgtDropdown(WidgetT *parent) {
// ============================================================
int32_t wgtDropdownGetSelected(const WidgetT *w) {
if (!w || w->type != WidgetDropdownE) {
return -1;
}
VALIDATE_WIDGET(w, WidgetDropdownE, -1);
return w->as.dropdown.selectedIdx;
}
@ -37,9 +35,7 @@ int32_t wgtDropdownGetSelected(const WidgetT *w) {
// ============================================================
void wgtDropdownSetItems(WidgetT *w, const char **items, int32_t count) {
if (!w || w->type != WidgetDropdownE) {
return;
}
VALIDATE_WIDGET_VOID(w, WidgetDropdownE);
w->as.dropdown.items = items;
w->as.dropdown.itemCount = count;
@ -68,9 +64,7 @@ void wgtDropdownSetItems(WidgetT *w, const char **items, int32_t count) {
// ============================================================
void wgtDropdownSetSelected(WidgetT *w, int32_t idx) {
if (!w || w->type != WidgetDropdownE) {
return;
}
VALIDATE_WIDGET_VOID(w, WidgetDropdownE);
w->as.dropdown.selectedIdx = idx;
}
@ -254,38 +248,5 @@ void widgetDropdownPaintPopup(WidgetT *w, DisplayT *d, const BlitOpsT *ops, cons
int32_t popH;
widgetDropdownPopupRect(w, font, d->clipH, &popX, &popY, &popW, &popH);
// Draw popup border
BevelStyleT bevel;
bevel.highlight = colors->windowHighlight;
bevel.shadow = colors->windowShadow;
bevel.face = colors->contentBg;
bevel.width = 2;
drawBevel(d, ops, popX, popY, popW, popH, &bevel);
// Draw items
int32_t itemCount = w->as.dropdown.itemCount;
const char **items = w->as.dropdown.items;
int32_t hoverIdx = w->as.dropdown.hoverIdx;
int32_t scrollPos = w->as.dropdown.scrollPos;
int32_t visibleItems = popH / font->charHeight;
int32_t textX = popX + TEXT_INPUT_PAD;
int32_t textY = popY + 2;
int32_t textW = popW - TEXT_INPUT_PAD * 2 - 4;
for (int32_t i = 0; i < visibleItems && (scrollPos + i) < itemCount; i++) {
int32_t idx = scrollPos + i;
int32_t iy = textY + i * font->charHeight;
uint32_t ifg = colors->contentFg;
uint32_t ibg = colors->contentBg;
if (idx == hoverIdx) {
ifg = colors->menuHighlightFg;
ibg = colors->menuHighlightBg;
rectFill(d, ops, popX + 2, iy, textW + TEXT_INPUT_PAD * 2, font->charHeight, ibg);
}
drawText(d, ops, font, textX, iy, items[idx], ifg, ibg, false);
}
widgetPaintPopupList(d, ops, font, colors, popX, popY, popW, popH, w->as.dropdown.items, w->as.dropdown.itemCount, w->as.dropdown.hoverIdx, w->as.dropdown.scrollPos);
}

View file

@ -266,17 +266,17 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
newW = 20;
}
if (newW != sResizeListView->as.listView.resolvedColW[sResizeCol]) {
sResizeListView->as.listView.resolvedColW[sResizeCol] = newW;
if (newW != sResizeListView->as.listView->resolvedColW[sResizeCol]) {
sResizeListView->as.listView->resolvedColW[sResizeCol] = newW;
// Recalculate totalColW
int32_t total = 0;
for (int32_t c = 0; c < sResizeListView->as.listView.colCount; c++) {
total += sResizeListView->as.listView.resolvedColW[c];
for (int32_t c = 0; c < sResizeListView->as.listView->colCount; c++) {
total += sResizeListView->as.listView->resolvedColW[c];
}
sResizeListView->as.listView.totalColW = total;
sResizeListView->as.listView->totalColW = total;
wgtInvalidatePaint(root);
}
@ -651,11 +651,11 @@ void widgetReorderDrop(WidgetT *w) {
w->onChange(w);
}
} else if (w->type == WidgetListViewE) {
int32_t drag = w->as.listView.dragIdx;
int32_t drop = w->as.listView.dropIdx;
int32_t colCnt = w->as.listView.colCount;
w->as.listView.dragIdx = -1;
w->as.listView.dropIdx = -1;
int32_t drag = w->as.listView->dragIdx;
int32_t drop = w->as.listView->dropIdx;
int32_t colCnt = w->as.listView->colCount;
w->as.listView->dragIdx = -1;
w->as.listView->dropIdx = -1;
if (drag < 0 || drop < 0 || drag == drop || drag == drop - 1) {
return;
@ -665,79 +665,79 @@ void widgetReorderDrop(WidgetT *w) {
const char *temp[LISTVIEW_MAX_COLS];
for (int32_t c = 0; c < colCnt; c++) {
temp[c] = w->as.listView.cellData[drag * colCnt + c];
temp[c] = w->as.listView->cellData[drag * colCnt + c];
}
uint8_t selBit = 0;
if (w->as.listView.multiSelect && w->as.listView.selBits) {
selBit = w->as.listView.selBits[drag];
if (w->as.listView->multiSelect && w->as.listView->selBits) {
selBit = w->as.listView->selBits[drag];
}
int32_t sortVal = 0;
if (w->as.listView.sortIndex) {
sortVal = w->as.listView.sortIndex[drag];
if (w->as.listView->sortIndex) {
sortVal = w->as.listView->sortIndex[drag];
}
if (drag < drop) {
for (int32_t i = drag; i < drop - 1; i++) {
for (int32_t c = 0; c < colCnt; c++) {
w->as.listView.cellData[i * colCnt + c] = w->as.listView.cellData[(i + 1) * colCnt + c];
w->as.listView->cellData[i * colCnt + c] = w->as.listView->cellData[(i + 1) * colCnt + c];
}
if (w->as.listView.selBits) {
w->as.listView.selBits[i] = w->as.listView.selBits[i + 1];
if (w->as.listView->selBits) {
w->as.listView->selBits[i] = w->as.listView->selBits[i + 1];
}
if (w->as.listView.sortIndex) {
w->as.listView.sortIndex[i] = w->as.listView.sortIndex[i + 1];
if (w->as.listView->sortIndex) {
w->as.listView->sortIndex[i] = w->as.listView->sortIndex[i + 1];
}
}
int32_t dest = drop - 1;
for (int32_t c = 0; c < colCnt; c++) {
w->as.listView.cellData[dest * colCnt + c] = temp[c];
w->as.listView->cellData[dest * colCnt + c] = temp[c];
}
if (w->as.listView.selBits) {
w->as.listView.selBits[dest] = selBit;
if (w->as.listView->selBits) {
w->as.listView->selBits[dest] = selBit;
}
if (w->as.listView.sortIndex) {
w->as.listView.sortIndex[dest] = sortVal;
if (w->as.listView->sortIndex) {
w->as.listView->sortIndex[dest] = sortVal;
}
w->as.listView.selectedIdx = dest;
w->as.listView->selectedIdx = dest;
} else {
for (int32_t i = drag; i > drop; i--) {
for (int32_t c = 0; c < colCnt; c++) {
w->as.listView.cellData[i * colCnt + c] = w->as.listView.cellData[(i - 1) * colCnt + c];
w->as.listView->cellData[i * colCnt + c] = w->as.listView->cellData[(i - 1) * colCnt + c];
}
if (w->as.listView.selBits) {
w->as.listView.selBits[i] = w->as.listView.selBits[i - 1];
if (w->as.listView->selBits) {
w->as.listView->selBits[i] = w->as.listView->selBits[i - 1];
}
if (w->as.listView.sortIndex) {
w->as.listView.sortIndex[i] = w->as.listView.sortIndex[i - 1];
if (w->as.listView->sortIndex) {
w->as.listView->sortIndex[i] = w->as.listView->sortIndex[i - 1];
}
}
for (int32_t c = 0; c < colCnt; c++) {
w->as.listView.cellData[drop * colCnt + c] = temp[c];
w->as.listView->cellData[drop * colCnt + c] = temp[c];
}
if (w->as.listView.selBits) {
w->as.listView.selBits[drop] = selBit;
if (w->as.listView->selBits) {
w->as.listView->selBits[drop] = selBit;
}
if (w->as.listView.sortIndex) {
w->as.listView.sortIndex[drop] = sortVal;
if (w->as.listView->sortIndex) {
w->as.listView->sortIndex[drop] = sortVal;
}
w->as.listView.selectedIdx = drop;
w->as.listView->selectedIdx = drop;
}
if (w->onChange) {
@ -844,21 +844,21 @@ void widgetReorderUpdate(WidgetT *w, WidgetT *root, int32_t x, int32_t y) {
int32_t innerY = w->y + LISTVIEW_BORDER + headerH;
int32_t innerH = w->h - LISTVIEW_BORDER * 2 - headerH;
int32_t visibleRows = innerH / font->charHeight;
int32_t maxScroll = w->as.listView.rowCount - visibleRows;
int32_t maxScroll = w->as.listView->rowCount - visibleRows;
if (maxScroll < 0) {
maxScroll = 0;
}
// Auto-scroll when dragging near edges
if (y < innerY + font->charHeight && w->as.listView.scrollPos > 0) {
w->as.listView.scrollPos--;
} else if (y > innerY + innerH - font->charHeight && w->as.listView.scrollPos < maxScroll) {
w->as.listView.scrollPos++;
if (y < innerY + font->charHeight && w->as.listView->scrollPos > 0) {
w->as.listView->scrollPos--;
} else if (y > innerY + innerH - font->charHeight && w->as.listView->scrollPos < maxScroll) {
w->as.listView->scrollPos++;
}
int32_t relY = y - innerY;
int32_t row = w->as.listView.scrollPos + relY / font->charHeight;
int32_t row = w->as.listView->scrollPos + relY / font->charHeight;
int32_t halfRow = (relY % font->charHeight) >= font->charHeight / 2 ? 1 : 0;
int32_t drop = row + halfRow;
@ -866,11 +866,11 @@ void widgetReorderUpdate(WidgetT *w, WidgetT *root, int32_t x, int32_t y) {
drop = 0;
}
if (drop > w->as.listView.rowCount) {
drop = w->as.listView.rowCount;
if (drop > w->as.listView->rowCount) {
drop = w->as.listView->rowCount;
}
w->as.listView.dropIdx = drop;
w->as.listView->dropIdx = drop;
} else if (w->type == WidgetTreeViewE) {
int32_t innerY = w->y + TREE_BORDER;
int32_t innerH = w->h - TREE_BORDER * 2;

View file

@ -100,9 +100,7 @@ WidgetT *wgtImageFromFile(WidgetT *parent, const char *path) {
// ============================================================
void wgtImageSetData(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int32_t pitch) {
if (!w || w->type != WidgetImageE) {
return;
}
VALIDATE_WIDGET_VOID(w, WidgetImageE);
free(w->as.image.data);
w->as.image.data = data;

View file

@ -33,9 +33,7 @@ WidgetT *wgtImageButton(WidgetT *parent, uint8_t *data, int32_t w, int32_t h, in
// ============================================================
void wgtImageButtonSetData(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int32_t pitch) {
if (!w || w->type != WidgetImageButtonE) {
return;
}
VALIDATE_WIDGET_VOID(w, WidgetImageButtonE);
free(w->as.imageButton.data);
w->as.imageButton.data = data;

View file

@ -37,6 +37,16 @@ typedef struct WidgetClassT {
extern const WidgetClassT *widgetClassTable[];
// ============================================================
// Validation macros
// ============================================================
#define VALIDATE_WIDGET(w, wtype, retval) \
do { if (!(w) || (w)->type != (wtype)) { return (retval); } } while (0)
#define VALIDATE_WIDGET_VOID(w, wtype) \
do { if (!(w) || (w)->type != (wtype)) { return; } } while (0)
// ============================================================
// Constants
// ============================================================
@ -69,7 +79,7 @@ extern const WidgetClassT *widgetClassTable[];
#define TREE_EXPAND_SIZE 9
#define TREE_ICON_GAP 4
#define TREE_BORDER 2
#define TREE_SB_W 14
#define WGT_SB_W 14
#define TREE_MIN_ROWS 4
#define SB_MIN_THUMB 14
#define SPLITTER_BAR_W 5
@ -137,9 +147,28 @@ WidgetT *widgetHitTest(WidgetT *w, int32_t x, int32_t y);
bool widgetIsFocusable(WidgetTypeE type);
bool widgetIsBoxContainer(WidgetTypeE type);
bool widgetIsHorizContainer(WidgetTypeE type);
int32_t widgetNavigateIndex(int32_t key, int32_t current, int32_t count, int32_t pageSize);
void widgetPaintPopupList(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, int32_t popX, int32_t popY, int32_t popW, int32_t popH, const char **items, int32_t itemCount, int32_t hoverIdx, int32_t scrollPos);
void widgetRemoveChild(WidgetT *parent, WidgetT *child);
void widgetScrollbarThumb(int32_t trackLen, int32_t totalSize, int32_t visibleSize, int32_t scrollPos, int32_t *thumbPos, int32_t *thumbSize);
// ============================================================
// Shared scrollbar functions (widgetScrollbar.c)
// ============================================================
typedef enum {
ScrollHitNoneE,
ScrollHitArrowDecE,
ScrollHitArrowIncE,
ScrollHitPageDecE,
ScrollHitPageIncE,
ScrollHitThumbE
} ScrollHitE;
void widgetDrawScrollbarH(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbW, int32_t totalSize, int32_t visibleSize, int32_t scrollPos);
void widgetDrawScrollbarV(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbH, int32_t totalSize, int32_t visibleSize, int32_t scrollPos);
ScrollHitE widgetScrollbarHitTest(int32_t sbLen, int32_t relPos, int32_t totalSize, int32_t visibleSize, int32_t scrollPos);
// ============================================================
// Layout functions (widgetLayout.c)
// ============================================================

View file

@ -7,7 +7,6 @@
#define LISTBOX_PAD 2
#define LISTBOX_MIN_ROWS 4
#define LISTBOX_SB_W 14
// ============================================================
@ -137,9 +136,7 @@ void wgtListBoxClearSelection(WidgetT *w) {
// ============================================================
int32_t wgtListBoxGetSelected(const WidgetT *w) {
if (!w || w->type != WidgetListBoxE) {
return -1;
}
VALIDATE_WIDGET(w, WidgetListBoxE, -1);
return w->as.listBox.selectedIdx;
}
@ -150,9 +147,7 @@ int32_t wgtListBoxGetSelected(const WidgetT *w) {
// ============================================================
bool wgtListBoxIsItemSelected(const WidgetT *w, int32_t idx) {
if (!w || w->type != WidgetListBoxE) {
return false;
}
VALIDATE_WIDGET(w, WidgetListBoxE, false);
if (!w->as.listBox.multiSelect) {
return idx == w->as.listBox.selectedIdx;
@ -184,9 +179,7 @@ void wgtListBoxSelectAll(WidgetT *w) {
// ============================================================
void wgtListBoxSetItemSelected(WidgetT *w, int32_t idx, bool selected) {
if (!w || w->type != WidgetListBoxE) {
return;
}
VALIDATE_WIDGET_VOID(w, WidgetListBoxE);
if (!w->as.listBox.selBits || idx < 0 || idx >= w->as.listBox.itemCount) {
return;
@ -201,9 +194,7 @@ void wgtListBoxSetItemSelected(WidgetT *w, int32_t idx, bool selected) {
// ============================================================
void wgtListBoxSetItems(WidgetT *w, const char **items, int32_t count) {
if (!w || w->type != WidgetListBoxE) {
return;
}
VALIDATE_WIDGET_VOID(w, WidgetListBoxE);
w->as.listBox.items = items;
w->as.listBox.itemCount = count;
@ -246,9 +237,7 @@ void wgtListBoxSetItems(WidgetT *w, const char **items, int32_t count) {
// ============================================================
void wgtListBoxSetReorderable(WidgetT *w, bool reorderable) {
if (!w || w->type != WidgetListBoxE) {
return;
}
VALIDATE_WIDGET_VOID(w, WidgetListBoxE);
w->as.listBox.reorderable = reorderable;
}
@ -259,9 +248,7 @@ void wgtListBoxSetReorderable(WidgetT *w, bool reorderable) {
// ============================================================
void wgtListBoxSetMultiSelect(WidgetT *w, bool multi) {
if (!w || w->type != WidgetListBoxE) {
return;
}
VALIDATE_WIDGET_VOID(w, WidgetListBoxE);
w->as.listBox.multiSelect = multi;
allocSelBits(w);
@ -278,9 +265,7 @@ void wgtListBoxSetMultiSelect(WidgetT *w, bool multi) {
// ============================================================
void wgtListBoxSetSelected(WidgetT *w, int32_t idx) {
if (!w || w->type != WidgetListBoxE) {
return;
}
VALIDATE_WIDGET_VOID(w, WidgetListBoxE);
w->as.listBox.selectedIdx = idx;
w->as.listBox.anchorIdx = idx;
@ -308,7 +293,7 @@ void widgetListBoxCalcMinSize(WidgetT *w, const BitmapFontT *font) {
maxItemW = minW;
}
w->calcMinW = maxItemW + LISTBOX_PAD * 2 + LISTBOX_BORDER * 2 + LISTBOX_SB_W;
w->calcMinW = maxItemW + LISTBOX_PAD * 2 + LISTBOX_BORDER * 2 + WGT_SB_W;
w->calcMinH = LISTBOX_MIN_ROWS * font->charHeight + LISTBOX_BORDER * 2;
}
@ -349,30 +334,6 @@ void widgetListBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
return;
}
int32_t newSel = sel;
if (key == (0x50 | 0x100)) {
// Down arrow
if (newSel < w->as.listBox.itemCount - 1) {
newSel++;
} else if (newSel < 0) {
newSel = 0;
}
} else if (key == (0x48 | 0x100)) {
// Up arrow
if (newSel > 0) {
newSel--;
} else if (newSel < 0) {
newSel = 0;
}
} else if (key == (0x47 | 0x100)) {
// Home
newSel = 0;
} else if (key == (0x4F | 0x100)) {
// End
newSel = w->as.listBox.itemCount - 1;
} else if (key == (0x51 | 0x100)) {
// Page Down
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData;
const BitmapFontT *font = &ctx->font;
int32_t visibleRows = (w->h - LISTBOX_BORDER * 2) / font->charHeight;
@ -381,27 +342,9 @@ void widgetListBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
visibleRows = 1;
}
newSel += visibleRows;
if (newSel >= w->as.listBox.itemCount) {
newSel = w->as.listBox.itemCount - 1;
}
} else if (key == (0x49 | 0x100)) {
// Page Up
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData;
const BitmapFontT *font = &ctx->font;
int32_t visibleRows = (w->h - LISTBOX_BORDER * 2) / font->charHeight;
if (visibleRows < 1) {
visibleRows = 1;
}
newSel -= visibleRows;
int32_t newSel = widgetNavigateIndex(key, sel, w->as.listBox.itemCount, visibleRows);
if (newSel < 0) {
newSel = 0;
}
} else {
return;
}
@ -454,40 +397,27 @@ void widgetListBoxOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
// Check if click is on the scrollbar
if (needSb) {
int32_t sbX = hit->x + hit->w - LISTBOX_BORDER - LISTBOX_SB_W;
int32_t sbX = hit->x + hit->w - LISTBOX_BORDER - WGT_SB_W;
if (vx >= sbX) {
int32_t sbY = hit->y + LISTBOX_BORDER;
int32_t sbH = innerH;
int32_t relY = vy - sbY;
int32_t trackLen = sbH - LISTBOX_SB_W * 2;
int32_t relY = vy - (hit->y + LISTBOX_BORDER);
ScrollHitE sh = widgetScrollbarHitTest(innerH, relY, hit->as.listBox.itemCount, visibleRows, hit->as.listBox.scrollPos);
if (relY < LISTBOX_SB_W) {
// Up arrow
if (sh == ScrollHitArrowDecE) {
if (hit->as.listBox.scrollPos > 0) {
hit->as.listBox.scrollPos--;
}
} else if (relY >= sbH - LISTBOX_SB_W) {
// Down arrow
} else if (sh == ScrollHitArrowIncE) {
if (hit->as.listBox.scrollPos < maxScroll) {
hit->as.listBox.scrollPos++;
}
} else if (trackLen > 0) {
// Track — page up/down
int32_t thumbPos;
int32_t thumbSize;
widgetScrollbarThumb(trackLen, hit->as.listBox.itemCount, visibleRows, hit->as.listBox.scrollPos, &thumbPos, &thumbSize);
int32_t trackRelY = relY - LISTBOX_SB_W;
if (trackRelY < thumbPos) {
} else if (sh == ScrollHitPageDecE) {
hit->as.listBox.scrollPos -= visibleRows;
hit->as.listBox.scrollPos = clampInt(hit->as.listBox.scrollPos, 0, maxScroll);
} else if (trackRelY >= thumbPos + thumbSize) {
} else if (sh == ScrollHitPageIncE) {
hit->as.listBox.scrollPos += visibleRows;
hit->as.listBox.scrollPos = clampInt(hit->as.listBox.scrollPos, 0, maxScroll);
}
}
hit->focused = true;
return;
@ -564,7 +494,7 @@ void widgetListBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
int32_t contentW = w->w - LISTBOX_BORDER * 2;
if (needSb) {
contentW -= LISTBOX_SB_W;
contentW -= WGT_SB_W;
}
// Sunken border
@ -628,52 +558,9 @@ void widgetListBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
// Draw scrollbar
if (needSb) {
int32_t sbX = w->x + w->w - LISTBOX_BORDER - LISTBOX_SB_W;
int32_t sbX = w->x + w->w - LISTBOX_BORDER - WGT_SB_W;
int32_t sbY = w->y + LISTBOX_BORDER;
int32_t sbH = innerH;
// Trough
BevelStyleT troughBevel = BEVEL_TROUGH(colors);
drawBevel(d, ops, sbX, sbY, LISTBOX_SB_W, sbH, &troughBevel);
// Up arrow button
BevelStyleT btnBevel = BEVEL_RAISED(colors, 1);
drawBevel(d, ops, sbX, sbY, LISTBOX_SB_W, LISTBOX_SB_W, &btnBevel);
// Up arrow triangle
{
int32_t cx = sbX + LISTBOX_SB_W / 2;
int32_t cy = sbY + LISTBOX_SB_W / 2;
for (int32_t i = 0; i < 4; i++) {
drawHLine(d, ops, cx - i, cy - 2 + i, 1 + i * 2, colors->contentFg);
}
}
// Down arrow button
int32_t downY = sbY + sbH - LISTBOX_SB_W;
drawBevel(d, ops, sbX, downY, LISTBOX_SB_W, LISTBOX_SB_W, &btnBevel);
// Down arrow triangle
{
int32_t cx = sbX + LISTBOX_SB_W / 2;
int32_t cy = downY + LISTBOX_SB_W / 2;
for (int32_t i = 0; i < 4; i++) {
drawHLine(d, ops, cx - i, cy + 2 - i, 1 + i * 2, colors->contentFg);
}
}
// Thumb
int32_t trackLen = sbH - LISTBOX_SB_W * 2;
if (trackLen > 0) {
int32_t thumbPos;
int32_t thumbSize;
widgetScrollbarThumb(trackLen, w->as.listBox.itemCount, visibleRows, w->as.listBox.scrollPos, &thumbPos, &thumbSize);
drawBevel(d, ops, sbX, sbY + LISTBOX_SB_W + thumbPos, LISTBOX_SB_W, thumbSize, &btnBevel);
}
widgetDrawScrollbarV(d, ops, colors, sbX, sbY, innerH, w->as.listBox.itemCount, visibleRows, w->as.listBox.scrollPos);
}
if (w->focused) {

File diff suppressed because it is too large Load diff

View file

@ -149,6 +149,43 @@ void wgtDestroy(WidgetT *w) {
}
// ============================================================
// widgetHashName — djb2 hash for widget name lookup
// ============================================================
static uint32_t widgetHashName(const char *s) {
uint32_t h = 5381;
while (*s) {
h = ((h << 5) + h) + (uint8_t)*s;
s++;
}
return h;
}
// ============================================================
// wgtFindByHash — recursive search with hash fast-reject
// ============================================================
static WidgetT *wgtFindByHash(WidgetT *root, const char *name, uint32_t hash) {
if (root->nameHash == hash && root->name[0] && strcmp(root->name, name) == 0) {
return root;
}
for (WidgetT *c = root->firstChild; c; c = c->nextSibling) {
WidgetT *found = wgtFindByHash(c, name, hash);
if (found) {
return found;
}
}
return NULL;
}
// ============================================================
// wgtFind
// ============================================================
@ -158,19 +195,24 @@ WidgetT *wgtFind(WidgetT *root, const char *name) {
return NULL;
}
if (root->name[0] && strcmp(root->name, name) == 0) {
return root;
uint32_t hash = widgetHashName(name);
return wgtFindByHash(root, name, hash);
}
for (WidgetT *c = root->firstChild; c; c = c->nextSibling) {
WidgetT *found = wgtFind(c, name);
if (found) {
return found;
}
// ============================================================
// wgtSetName
// ============================================================
void wgtSetName(WidgetT *w, const char *name) {
if (!w || !name) {
return;
}
return NULL;
strncpy(w->name, name, MAX_WIDGET_NAME - 1);
w->name[MAX_WIDGET_NAME - 1] = '\0';
w->nameHash = widgetHashName(w->name);
}
@ -306,8 +348,16 @@ void wgtPaint(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT
// wgtSetDebugLayout
// ============================================================
void wgtSetDebugLayout(bool enabled) {
void wgtSetDebugLayout(AppContextT *ctx, bool enabled) {
sDebugLayout = enabled;
for (int32_t i = 0; i < ctx->stack.count; i++) {
WindowT *win = ctx->stack.windows[i];
if (win->widgetRoot) {
wgtInvalidate(win->widgetRoot);
}
}
}

View file

@ -42,9 +42,7 @@ WidgetT *wgtProgressBarV(WidgetT *parent) {
// ============================================================
int32_t wgtProgressBarGetValue(const WidgetT *w) {
if (!w || w->type != WidgetProgressBarE) {
return 0;
}
VALIDATE_WIDGET(w, WidgetProgressBarE, 0);
return w->as.progressBar.value;
}
@ -55,9 +53,7 @@ int32_t wgtProgressBarGetValue(const WidgetT *w) {
// ============================================================
void wgtProgressBarSetValue(WidgetT *w, int32_t value) {
if (!w || w->type != WidgetProgressBarE) {
return;
}
VALIDATE_WIDGET_VOID(w, WidgetProgressBarE);
if (value < 0) {
value = 0;

View file

@ -0,0 +1,152 @@
// widgetScrollbar.c — Shared scrollbar painting and hit-testing
#include "widgetInternal.h"
// ============================================================
// widgetDrawScrollbarH
// ============================================================
void widgetDrawScrollbarH(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbW, int32_t totalSize, int32_t visibleSize, int32_t scrollPos) {
if (sbW < WGT_SB_W * 3) {
return;
}
// Trough background
BevelStyleT troughBevel = BEVEL_TROUGH(colors);
drawBevel(d, ops, sbX, sbY, sbW, WGT_SB_W, &troughBevel);
// Left arrow button
BevelStyleT btnBevel = BEVEL_RAISED(colors, 1);
drawBevel(d, ops, sbX, sbY, WGT_SB_W, WGT_SB_W, &btnBevel);
// Left arrow triangle
{
int32_t cx = sbX + WGT_SB_W / 2;
int32_t cy = sbY + WGT_SB_W / 2;
uint32_t fg = colors->contentFg;
for (int32_t i = 0; i < 4; i++) {
drawVLine(d, ops, cx - 2 + i, cy - i, 1 + i * 2, fg);
}
}
// Right arrow button
int32_t rightX = sbX + sbW - WGT_SB_W;
drawBevel(d, ops, rightX, sbY, WGT_SB_W, WGT_SB_W, &btnBevel);
// Right arrow triangle
{
int32_t cx = rightX + WGT_SB_W / 2;
int32_t cy = sbY + WGT_SB_W / 2;
uint32_t fg = colors->contentFg;
for (int32_t i = 0; i < 4; i++) {
drawVLine(d, ops, cx + 2 - i, cy - i, 1 + i * 2, fg);
}
}
// Thumb
int32_t trackLen = sbW - WGT_SB_W * 2;
if (trackLen > 0 && totalSize > 0) {
int32_t thumbPos;
int32_t thumbSize;
widgetScrollbarThumb(trackLen, totalSize, visibleSize, scrollPos, &thumbPos, &thumbSize);
drawBevel(d, ops, sbX + WGT_SB_W + thumbPos, sbY, thumbSize, WGT_SB_W, &btnBevel);
}
}
// ============================================================
// widgetDrawScrollbarV
// ============================================================
void widgetDrawScrollbarV(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbH, int32_t totalSize, int32_t visibleSize, int32_t scrollPos) {
if (sbH < WGT_SB_W * 3) {
return;
}
// Trough background
BevelStyleT troughBevel = BEVEL_TROUGH(colors);
drawBevel(d, ops, sbX, sbY, WGT_SB_W, sbH, &troughBevel);
// Up arrow button
BevelStyleT btnBevel = BEVEL_RAISED(colors, 1);
drawBevel(d, ops, sbX, sbY, WGT_SB_W, WGT_SB_W, &btnBevel);
// Up arrow triangle
{
int32_t cx = sbX + WGT_SB_W / 2;
int32_t cy = sbY + WGT_SB_W / 2;
uint32_t fg = colors->contentFg;
for (int32_t i = 0; i < 4; i++) {
drawHLine(d, ops, cx - i, cy - 2 + i, 1 + i * 2, fg);
}
}
// Down arrow button
int32_t downY = sbY + sbH - WGT_SB_W;
drawBevel(d, ops, sbX, downY, WGT_SB_W, WGT_SB_W, &btnBevel);
// Down arrow triangle
{
int32_t cx = sbX + WGT_SB_W / 2;
int32_t cy = downY + WGT_SB_W / 2;
uint32_t fg = colors->contentFg;
for (int32_t i = 0; i < 4; i++) {
drawHLine(d, ops, cx - i, cy + 2 - i, 1 + i * 2, fg);
}
}
// Thumb
int32_t trackLen = sbH - WGT_SB_W * 2;
if (trackLen > 0 && totalSize > 0) {
int32_t thumbPos;
int32_t thumbSize;
widgetScrollbarThumb(trackLen, totalSize, visibleSize, scrollPos, &thumbPos, &thumbSize);
drawBevel(d, ops, sbX, sbY + WGT_SB_W + thumbPos, WGT_SB_W, thumbSize, &btnBevel);
}
}
// ============================================================
// widgetScrollbarHitTest
// ============================================================
ScrollHitE widgetScrollbarHitTest(int32_t sbLen, int32_t relPos, int32_t totalSize, int32_t visibleSize, int32_t scrollPos) {
if (relPos < WGT_SB_W) {
return ScrollHitArrowDecE;
}
if (relPos >= sbLen - WGT_SB_W) {
return ScrollHitArrowIncE;
}
int32_t trackLen = sbLen - WGT_SB_W * 2;
if (trackLen > 0) {
int32_t thumbPos;
int32_t thumbSize;
widgetScrollbarThumb(trackLen, totalSize, visibleSize, scrollPos, &thumbPos, &thumbSize);
int32_t trackRel = relPos - WGT_SB_W;
if (trackRel < thumbPos) {
return ScrollHitPageDecE;
}
if (trackRel >= thumbPos + thumbSize) {
return ScrollHitPageIncE;
}
return ScrollHitThumbE;
}
return ScrollHitNoneE;
}

View file

@ -27,9 +27,7 @@ WidgetT *wgtSlider(WidgetT *parent, int32_t minVal, int32_t maxVal) {
// ============================================================
int32_t wgtSliderGetValue(const WidgetT *w) {
if (!w || w->type != WidgetSliderE) {
return 0;
}
VALIDATE_WIDGET(w, WidgetSliderE, 0);
return w->as.slider.value;
}
@ -40,9 +38,7 @@ int32_t wgtSliderGetValue(const WidgetT *w) {
// ============================================================
void wgtSliderSetValue(WidgetT *w, int32_t value) {
if (!w || w->type != WidgetSliderE) {
return;
}
VALIDATE_WIDGET_VOID(w, WidgetSliderE);
if (value < w->as.slider.minValue) {
value = w->as.slider.minValue;

View file

@ -458,9 +458,7 @@ WidgetT *wgtSpinner(WidgetT *parent, int32_t minVal, int32_t maxVal, int32_t ste
// ============================================================
int32_t wgtSpinnerGetValue(const WidgetT *w) {
if (!w || w->type != WidgetSpinnerE) {
return 0;
}
VALIDATE_WIDGET(w, WidgetSpinnerE, 0);
return w->as.spinner.value;
}
@ -471,9 +469,7 @@ int32_t wgtSpinnerGetValue(const WidgetT *w) {
// ============================================================
void wgtSpinnerSetRange(WidgetT *w, int32_t minVal, int32_t maxVal) {
if (!w || w->type != WidgetSpinnerE) {
return;
}
VALIDATE_WIDGET_VOID(w, WidgetSpinnerE);
w->as.spinner.minValue = minVal;
w->as.spinner.maxValue = maxVal;
@ -486,9 +482,7 @@ void wgtSpinnerSetRange(WidgetT *w, int32_t minVal, int32_t maxVal) {
// ============================================================
void wgtSpinnerSetStep(WidgetT *w, int32_t step) {
if (!w || w->type != WidgetSpinnerE) {
return;
}
VALIDATE_WIDGET_VOID(w, WidgetSpinnerE);
w->as.spinner.step = step > 0 ? step : 1;
}
@ -499,9 +493,7 @@ void wgtSpinnerSetStep(WidgetT *w, int32_t step) {
// ============================================================
void wgtSpinnerSetValue(WidgetT *w, int32_t value) {
if (!w || w->type != WidgetSpinnerE) {
return;
}
VALIDATE_WIDGET_VOID(w, WidgetSpinnerE);
w->as.spinner.value = value;
spinnerClampAndFormat(w);

View file

@ -137,9 +137,7 @@ WidgetT *wgtTabControl(WidgetT *parent) {
// ============================================================
int32_t wgtTabControlGetActive(const WidgetT *w) {
if (!w || w->type != WidgetTabControlE) {
return 0;
}
VALIDATE_WIDGET(w, WidgetTabControlE, 0);
return w->as.tabControl.activeTab;
}
@ -150,9 +148,7 @@ int32_t wgtTabControlGetActive(const WidgetT *w) {
// ============================================================
void wgtTabControlSetActive(WidgetT *w, int32_t idx) {
if (!w || w->type != WidgetTabControlE) {
return;
}
VALIDATE_WIDGET_VOID(w, WidgetTabControlE);
w->as.tabControl.activeTab = idx;
}

View file

@ -158,23 +158,23 @@ static bool clearSelectionOnWidget(WidgetT *w) {
w->as.comboBox.selStart = -1;
w->as.comboBox.selEnd = -1;
} else if (w->type == WidgetAnsiTermE) {
if (w->as.ansiTerm.selStartLine >= 0 &&
(w->as.ansiTerm.selStartLine != w->as.ansiTerm.selEndLine ||
w->as.ansiTerm.selStartCol != w->as.ansiTerm.selEndCol)) {
w->as.ansiTerm.dirtyRows = 0xFFFFFFFF;
w->as.ansiTerm.selStartLine = -1;
w->as.ansiTerm.selStartCol = -1;
w->as.ansiTerm.selEndLine = -1;
w->as.ansiTerm.selEndCol = -1;
w->as.ansiTerm.selecting = false;
if (w->as.ansiTerm->selStartLine >= 0 &&
(w->as.ansiTerm->selStartLine != w->as.ansiTerm->selEndLine ||
w->as.ansiTerm->selStartCol != w->as.ansiTerm->selEndCol)) {
w->as.ansiTerm->dirtyRows = 0xFFFFFFFF;
w->as.ansiTerm->selStartLine = -1;
w->as.ansiTerm->selStartCol = -1;
w->as.ansiTerm->selEndLine = -1;
w->as.ansiTerm->selEndCol = -1;
w->as.ansiTerm->selecting = false;
return true;
}
w->as.ansiTerm.selStartLine = -1;
w->as.ansiTerm.selStartCol = -1;
w->as.ansiTerm.selEndLine = -1;
w->as.ansiTerm.selEndCol = -1;
w->as.ansiTerm.selecting = false;
w->as.ansiTerm->selStartLine = -1;
w->as.ansiTerm->selStartCol = -1;
w->as.ansiTerm->selEndLine = -1;
w->as.ansiTerm->selEndCol = -1;
w->as.ansiTerm->selecting = false;
}
return false;
@ -1789,8 +1789,8 @@ void widgetTextDragUpdate(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
} else if (w->type == WidgetAnsiTermE) {
int32_t baseX = w->x + 2; // ANSI_BORDER
int32_t baseY = w->y + 2;
int32_t cols = w->as.ansiTerm.cols;
int32_t rows = w->as.ansiTerm.rows;
int32_t cols = w->as.ansiTerm->cols;
int32_t rows = w->as.ansiTerm->rows;
int32_t clickRow = (vy - baseY) / font->charHeight;
int32_t clickCol = (vx - baseX) / font->charWidth;
@ -1811,9 +1811,9 @@ void widgetTextDragUpdate(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
clickCol = cols;
}
w->as.ansiTerm.selEndLine = w->as.ansiTerm.scrollPos + clickRow;
w->as.ansiTerm.selEndCol = clickCol;
w->as.ansiTerm.dirtyRows = 0xFFFFFFFF;
w->as.ansiTerm->selEndLine = w->as.ansiTerm->scrollPos + clickRow;
w->as.ansiTerm->selEndCol = clickCol;
w->as.ansiTerm->dirtyRows = 0xFFFFFFFF;
}
}

View file

@ -9,8 +9,6 @@
static int32_t calcTreeItemsHeight(WidgetT *parent, const BitmapFontT *font);
static int32_t calcTreeItemsMaxWidth(WidgetT *parent, const BitmapFontT *font, int32_t depth);
static void clearAllSelections(WidgetT *parent);
static void drawTreeHScrollbar(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t totalW, int32_t innerW, bool hasVSb);
static void drawTreeVScrollbar(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t totalH, int32_t innerH);
static WidgetT *firstVisibleItem(WidgetT *treeView);
static void layoutTreeItems(WidgetT *parent, const BitmapFontT *font, int32_t x, int32_t *y, int32_t width, int32_t depth);
static WidgetT *nextVisibleItem(WidgetT *item, WidgetT *treeView);
@ -98,131 +96,6 @@ static void clearAllSelections(WidgetT *parent) {
}
// ============================================================
// drawTreeHScrollbar
// ============================================================
static void drawTreeHScrollbar(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t totalW, int32_t innerW, bool hasVSb) {
int32_t sbX = w->x + TREE_BORDER;
int32_t sbY = w->y + w->h - TREE_BORDER - TREE_SB_W;
int32_t sbW = innerW;
if (sbW < TREE_SB_W * 3) {
return;
}
// Trough background
BevelStyleT troughBevel = BEVEL_TROUGH(colors);
drawBevel(d, ops, sbX, sbY, sbW, TREE_SB_W, &troughBevel);
// Left arrow button
BevelStyleT btnBevel = BEVEL_RAISED(colors, 1);
drawBevel(d, ops, sbX, sbY, TREE_SB_W, TREE_SB_W, &btnBevel);
// Left arrow triangle
{
int32_t cx = sbX + TREE_SB_W / 2;
int32_t cy = sbY + TREE_SB_W / 2;
uint32_t fg = colors->contentFg;
for (int32_t i = 0; i < 4; i++) {
drawVLine(d, ops, cx - 2 + i, cy - i, 1 + i * 2, fg);
}
}
// Right arrow button
int32_t rightX = sbX + sbW - TREE_SB_W;
drawBevel(d, ops, rightX, sbY, TREE_SB_W, TREE_SB_W, &btnBevel);
// Right arrow triangle
{
int32_t cx = rightX + TREE_SB_W / 2;
int32_t cy = sbY + TREE_SB_W / 2;
uint32_t fg = colors->contentFg;
for (int32_t i = 0; i < 4; i++) {
drawVLine(d, ops, cx + 2 - i, cy - i, 1 + i * 2, fg);
}
}
// Thumb
int32_t trackLen = sbW - TREE_SB_W * 2;
if (trackLen > 0 && totalW > 0) {
int32_t thumbPos;
int32_t thumbSize;
widgetScrollbarThumb(trackLen, totalW, innerW, w->as.treeView.scrollPosH, &thumbPos, &thumbSize);
drawBevel(d, ops, sbX + TREE_SB_W + thumbPos, sbY, thumbSize, TREE_SB_W, &btnBevel);
}
// Fill dead corner when both scrollbars present
if (hasVSb) {
rectFill(d, ops, sbX + sbW, sbY, TREE_SB_W, TREE_SB_W, colors->windowFace);
}
}
// ============================================================
// drawTreeVScrollbar
// ============================================================
static void drawTreeVScrollbar(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t totalH, int32_t innerH) {
int32_t sbX = w->x + w->w - TREE_BORDER - TREE_SB_W;
int32_t sbY = w->y + TREE_BORDER;
int32_t sbH = innerH;
if (sbH < TREE_SB_W * 3) {
return;
}
// Trough background
BevelStyleT troughBevel = BEVEL_TROUGH(colors);
drawBevel(d, ops, sbX, sbY, TREE_SB_W, sbH, &troughBevel);
// Up arrow button
BevelStyleT btnBevel = BEVEL_RAISED(colors, 1);
drawBevel(d, ops, sbX, sbY, TREE_SB_W, TREE_SB_W, &btnBevel);
// Up arrow triangle
{
int32_t cx = sbX + TREE_SB_W / 2;
int32_t cy = sbY + TREE_SB_W / 2;
uint32_t fg = colors->contentFg;
for (int32_t i = 0; i < 4; i++) {
drawHLine(d, ops, cx - i, cy - 2 + i, 1 + i * 2, fg);
}
}
// Down arrow button
int32_t downY = sbY + sbH - TREE_SB_W;
drawBevel(d, ops, sbX, downY, TREE_SB_W, TREE_SB_W, &btnBevel);
// Down arrow triangle
{
int32_t cx = sbX + TREE_SB_W / 2;
int32_t cy = downY + TREE_SB_W / 2;
uint32_t fg = colors->contentFg;
for (int32_t i = 0; i < 4; i++) {
drawHLine(d, ops, cx - i, cy + 2 - i, 1 + i * 2, fg);
}
}
// Thumb
int32_t trackLen = sbH - TREE_SB_W * 2;
if (trackLen > 0 && totalH > 0) {
int32_t thumbPos;
int32_t thumbSize;
widgetScrollbarThumb(trackLen, totalH, innerH, w->as.treeView.scrollPos, &thumbPos, &thumbSize);
drawBevel(d, ops, sbX, sbY + TREE_SB_W + thumbPos, TREE_SB_W, thumbSize, &btnBevel);
}
}
// ============================================================
// firstVisibleItem
// ============================================================
@ -542,7 +415,7 @@ static void treeCalcScrollbarNeeds(WidgetT *w, const BitmapFontT *font, int32_t
// V scrollbar reduces available width — may trigger H scrollbar
if (needVSb) {
innerW -= TREE_SB_W;
innerW -= WGT_SB_W;
if (!needHSb && totalW > innerW) {
needHSb = true;
@ -551,11 +424,11 @@ static void treeCalcScrollbarNeeds(WidgetT *w, const BitmapFontT *font, int32_t
// H scrollbar reduces available height — may trigger V scrollbar
if (needHSb) {
innerH -= TREE_SB_W;
innerH -= WGT_SB_W;
if (!needVSb && totalH > innerH) {
needVSb = true;
innerW -= TREE_SB_W;
innerW -= WGT_SB_W;
}
}
@ -684,9 +557,7 @@ void widgetTreeItemSetText(WidgetT *w, const char *text) {
// ============================================================
bool wgtTreeItemIsExpanded(const WidgetT *w) {
if (!w || w->type != WidgetTreeItemE) {
return false;
}
VALIDATE_WIDGET(w, WidgetTreeItemE, false);
return w->as.treeItem.expanded;
}
@ -697,9 +568,7 @@ bool wgtTreeItemIsExpanded(const WidgetT *w) {
// ============================================================
void wgtTreeItemSetExpanded(WidgetT *w, bool expanded) {
if (!w || w->type != WidgetTreeItemE) {
return;
}
VALIDATE_WIDGET_VOID(w, WidgetTreeItemE);
w->as.treeItem.expanded = expanded;
}
@ -725,9 +594,7 @@ WidgetT *wgtTreeView(WidgetT *parent) {
// ============================================================
WidgetT *wgtTreeViewGetSelected(const WidgetT *w) {
if (!w || w->type != WidgetTreeViewE) {
return NULL;
}
VALIDATE_WIDGET(w, WidgetTreeViewE, NULL);
return w->as.treeView.selectedItem;
}
@ -738,9 +605,7 @@ WidgetT *wgtTreeViewGetSelected(const WidgetT *w) {
// ============================================================
void wgtTreeViewSetSelected(WidgetT *w, WidgetT *item) {
if (!w || w->type != WidgetTreeViewE) {
return;
}
VALIDATE_WIDGET_VOID(w, WidgetTreeViewE);
w->as.treeView.selectedItem = item;
@ -757,9 +622,7 @@ void wgtTreeViewSetSelected(WidgetT *w, WidgetT *item) {
// ============================================================
void wgtTreeViewSetMultiSelect(WidgetT *w, bool multi) {
if (!w || w->type != WidgetTreeViewE) {
return;
}
VALIDATE_WIDGET_VOID(w, WidgetTreeViewE);
w->as.treeView.multiSelect = multi;
}
@ -770,9 +633,7 @@ void wgtTreeViewSetMultiSelect(WidgetT *w, bool multi) {
// ============================================================
void wgtTreeViewSetReorderable(WidgetT *w, bool reorderable) {
if (!w || w->type != WidgetTreeViewE) {
return;
}
VALIDATE_WIDGET_VOID(w, WidgetTreeViewE);
w->as.treeView.reorderable = reorderable;
}
@ -795,9 +656,7 @@ WidgetT *widgetTreeViewNextVisible(WidgetT *item, WidgetT *treeView) {
// ============================================================
bool wgtTreeItemIsSelected(const WidgetT *w) {
if (!w || w->type != WidgetTreeItemE) {
return false;
}
VALIDATE_WIDGET(w, WidgetTreeItemE, false);
return w->as.treeItem.selected;
}
@ -808,9 +667,7 @@ bool wgtTreeItemIsSelected(const WidgetT *w) {
// ============================================================
void wgtTreeItemSetSelected(WidgetT *w, bool selected) {
if (!w || w->type != WidgetTreeItemE) {
return;
}
VALIDATE_WIDGET_VOID(w, WidgetTreeItemE);
w->as.treeItem.selected = selected;
}
@ -823,7 +680,7 @@ void wgtTreeItemSetSelected(WidgetT *w, bool selected) {
void widgetTreeViewCalcMinSize(WidgetT *w, const BitmapFontT *font) {
int32_t minContentW = TREE_INDENT + TREE_EXPAND_SIZE + TREE_ICON_GAP + 6 * font->charWidth;
w->calcMinW = minContentW + TREE_BORDER * 2 + TREE_SB_W;
w->calcMinW = minContentW + TREE_BORDER * 2 + WGT_SB_W;
w->calcMinH = TREE_MIN_ROWS * font->charHeight + TREE_BORDER * 2;
}
@ -833,9 +690,7 @@ void widgetTreeViewCalcMinSize(WidgetT *w, const BitmapFontT *font) {
// ============================================================
void widgetTreeViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
if (!w || w->type != WidgetTreeViewE) {
return;
}
VALIDATE_WIDGET_VOID(w, WidgetTreeViewE);
bool multi = w->as.treeView.multiSelect;
bool shift = (mod & KEY_MOD_SHIFT) != 0;
@ -1053,41 +908,28 @@ void widgetTreeViewOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy)
// Check if click is on the vertical scrollbar
if (needVSb) {
int32_t sbX = hit->x + hit->w - TREE_BORDER - TREE_SB_W;
int32_t sbX = hit->x + hit->w - TREE_BORDER - WGT_SB_W;
if (vx >= sbX && vy < hit->y + TREE_BORDER + innerH) {
int32_t sbY = hit->y + TREE_BORDER;
int32_t sbH = innerH;
int32_t relY = vy - sbY;
int32_t trackLen = sbH - TREE_SB_W * 2;
int32_t relY = vy - (hit->y + TREE_BORDER);
int32_t pageSize = innerH - font->charHeight;
if (pageSize < font->charHeight) {
pageSize = font->charHeight;
}
if (relY < TREE_SB_W) {
// Up arrow — scroll up one row
ScrollHitE sh = widgetScrollbarHitTest(innerH, relY, totalH, innerH, hit->as.treeView.scrollPos);
if (sh == ScrollHitArrowDecE) {
hit->as.treeView.scrollPos -= font->charHeight;
} else if (relY >= sbH - TREE_SB_W) {
// Down arrow — scroll down one row
} else if (sh == ScrollHitArrowIncE) {
hit->as.treeView.scrollPos += font->charHeight;
} else if (trackLen > 0) {
// Track area — page up/down based on thumb position
int32_t thumbPos;
int32_t thumbSize;
widgetScrollbarThumb(trackLen, totalH, innerH, hit->as.treeView.scrollPos, &thumbPos, &thumbSize);
int32_t trackRelY = relY - TREE_SB_W;
if (trackRelY < thumbPos) {
} else if (sh == ScrollHitPageDecE) {
hit->as.treeView.scrollPos -= pageSize;
} else if (trackRelY >= thumbPos + thumbSize) {
} else if (sh == ScrollHitPageIncE) {
hit->as.treeView.scrollPos += pageSize;
}
}
// Clamp after scroll
hit->as.treeView.scrollPos = clampInt(hit->as.treeView.scrollPos, 0, maxScrollV);
return;
}
@ -1095,41 +937,28 @@ void widgetTreeViewOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy)
// Check if click is on the horizontal scrollbar
if (needHSb) {
int32_t sbY = hit->y + hit->h - TREE_BORDER - TREE_SB_W;
int32_t sbY = hit->y + hit->h - TREE_BORDER - WGT_SB_W;
if (vy >= sbY && vx < hit->x + TREE_BORDER + innerW) {
int32_t sbX = hit->x + TREE_BORDER;
int32_t sbW = innerW;
int32_t relX = vx - sbX;
int32_t trackLen = sbW - TREE_SB_W * 2;
int32_t relX = vx - (hit->x + TREE_BORDER);
int32_t pageSize = innerW - font->charWidth;
if (pageSize < font->charWidth) {
pageSize = font->charWidth;
}
if (relX < TREE_SB_W) {
// Left arrow — scroll left
ScrollHitE sh = widgetScrollbarHitTest(innerW, relX, totalW, innerW, hit->as.treeView.scrollPosH);
if (sh == ScrollHitArrowDecE) {
hit->as.treeView.scrollPosH -= font->charWidth;
} else if (relX >= sbW - TREE_SB_W) {
// Right arrow — scroll right
} else if (sh == ScrollHitArrowIncE) {
hit->as.treeView.scrollPosH += font->charWidth;
} else if (trackLen > 0) {
// Track area — page left/right based on thumb position
int32_t thumbPos;
int32_t thumbSize;
widgetScrollbarThumb(trackLen, totalW, innerW, hit->as.treeView.scrollPosH, &thumbPos, &thumbSize);
int32_t trackRelX = relX - TREE_SB_W;
if (trackRelX < thumbPos) {
} else if (sh == ScrollHitPageDecE) {
hit->as.treeView.scrollPosH -= pageSize;
} else if (trackRelX >= thumbPos + thumbSize) {
} else if (sh == ScrollHitPageIncE) {
hit->as.treeView.scrollPosH += pageSize;
}
}
// Clamp after scroll
hit->as.treeView.scrollPosH = clampInt(hit->as.treeView.scrollPosH, 0, maxScrollH);
return;
}
@ -1137,8 +966,8 @@ void widgetTreeViewOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy)
// Click in dead corner (both scrollbars present) — ignore
if (needVSb && needHSb) {
int32_t cornerX = hit->x + hit->w - TREE_BORDER - TREE_SB_W;
int32_t cornerY = hit->y + hit->h - TREE_BORDER - TREE_SB_W;
int32_t cornerX = hit->x + hit->w - TREE_BORDER - WGT_SB_W;
int32_t cornerY = hit->y + hit->h - TREE_BORDER - WGT_SB_W;
if (vx >= cornerX && vy >= cornerY) {
return;
@ -1313,11 +1142,20 @@ void widgetTreeViewPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
// Draw scrollbars
if (needVSb) {
drawTreeVScrollbar(w, d, ops, colors, totalH, innerH);
int32_t sbX = w->x + w->w - TREE_BORDER - WGT_SB_W;
int32_t sbY = w->y + TREE_BORDER;
widgetDrawScrollbarV(d, ops, colors, sbX, sbY, innerH, totalH, innerH, w->as.treeView.scrollPos);
}
if (needHSb) {
drawTreeHScrollbar(w, d, ops, colors, totalW, innerW, needVSb);
int32_t sbX = w->x + TREE_BORDER;
int32_t sbY = w->y + w->h - TREE_BORDER - WGT_SB_W;
widgetDrawScrollbarH(d, ops, colors, sbX, sbY, innerW, totalW, innerW, w->as.treeView.scrollPosH);
// Fill dead corner when both scrollbars present
if (needVSb) {
rectFill(d, ops, sbX + innerW, sbY, WGT_SB_W, WGT_SB_W, colors->windowFace);
}
}
if (w->focused) {

View file

@ -32,6 +32,7 @@
#define CMD_VIEW_SIZE_SMALL 307
#define CMD_VIEW_SIZE_MED 308
#define CMD_VIEW_SIZE_LARGE 309
#define CMD_VIEW_DEBUG_LYT 310
#define CMD_WIN_CASCADE 350
#define CMD_WIN_TILE_H 351
#define CMD_WIN_TILE_V 352
@ -209,6 +210,13 @@ static void onMenuCb(WindowT *win, int32_t menuId) {
setupControlsWindow(ctx);
break;
case CMD_VIEW_DEBUG_LYT: {
static bool sDebugLyt = false;
sDebugLyt = !sDebugLyt;
wgtSetDebugLayout(ctx, sDebugLyt);
break;
}
case CMD_WIN_CASCADE:
dvxCascadeWindows(ctx);
break;
@ -623,21 +631,21 @@ static void setupControlsWindow(AppContextT *ctx) {
uint8_t *newData = loadBmpPixels(ctx, "new.bmp", &imgW, &imgH, &imgPitch);
if (newData) {
WidgetT *btnNew = wgtImageButton(tb, newData, imgW, imgH, imgPitch);
strncpy(btnNew->name, "New", MAX_WIDGET_NAME);
wgtSetName(btnNew, "New");
btnNew->onClick = onToolbarClick;
}
uint8_t *openData = loadBmpPixels(ctx, "open.bmp", &imgW, &imgH, &imgPitch);
if (openData) {
WidgetT *btnOpen = wgtImageButton(tb, openData, imgW, imgH, imgPitch);
strncpy(btnOpen->name, "Open", MAX_WIDGET_NAME);
wgtSetName(btnOpen, "Open");
btnOpen->onClick = onToolbarClick;
}
uint8_t *saveData = loadBmpPixels(ctx, "save.bmp", &imgW, &imgH, &imgPitch);
if (saveData) {
WidgetT *btnSave = wgtImageButton(tb, saveData, imgW, imgH, imgPitch);
strncpy(btnSave->name, "Save", MAX_WIDGET_NAME);
wgtSetName(btnSave, "Save");
btnSave->onClick = onToolbarClick;
}
@ -814,7 +822,7 @@ static void setupControlsWindow(AppContextT *ctx) {
WidgetT *sb = wgtStatusBar(root);
WidgetT *sbLabel = wgtLabel(sb, "Ready");
sbLabel->weight = 100;
strncpy(sbLabel->name, "advStatus", MAX_WIDGET_NAME);
wgtSetName(sbLabel, "advStatus");
wgtLabel(sb, "Line 1, Col 1");
wgtInvalidate(root);
@ -878,6 +886,9 @@ static void setupMainWindow(AppContextT *ctx) {
wmAddMenuSeparator(zoomMenu);
wmAddMenuItem(zoomMenu, "&Fit to Window", CMD_VIEW_ZOOM_FIT);
}
wmAddMenuSeparator(viewMenu);
wmAddMenuCheckItem(viewMenu, "&Debug Layout", CMD_VIEW_DEBUG_LYT, false);
}
MenuT *winMenu = wmAddMenu(bar, "&Window");
@ -1052,7 +1063,7 @@ static void setupWidgetDemo(AppContextT *ctx) {
// Status label at top
WidgetT *status = wgtLabel(root, "Ready.");
strncpy(status->name, "status", MAX_WIDGET_NAME);
wgtSetName(status, "status");
wgtHSeparator(root);