Code optimization and cleanup.
This commit is contained in:
parent
85f0e5be56
commit
d0e308d7cf
27 changed files with 1484 additions and 1628 deletions
|
|
@ -16,6 +16,7 @@ SRCS = dvxVideo.c dvxDraw.c dvxComp.c dvxWm.c dvxIcon.c dvxImageWrite.c dvxApp
|
||||||
WSRCS = widgets/widgetAnsiTerm.c \
|
WSRCS = widgets/widgetAnsiTerm.c \
|
||||||
widgets/widgetClass.c \
|
widgets/widgetClass.c \
|
||||||
widgets/widgetCore.c \
|
widgets/widgetCore.c \
|
||||||
|
widgets/widgetScrollbar.c \
|
||||||
widgets/widgetLayout.c \
|
widgets/widgetLayout.c \
|
||||||
widgets/widgetEvent.c \
|
widgets/widgetEvent.c \
|
||||||
widgets/widgetOps.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)/widgetTabControl.o: widgets/widgetTabControl.c $(WIDGET_DEPS)
|
||||||
$(WOBJDIR)/widgetTextInput.o: widgets/widgetTextInput.c $(WIDGET_DEPS)
|
$(WOBJDIR)/widgetTextInput.o: widgets/widgetTextInput.c $(WIDGET_DEPS)
|
||||||
$(WOBJDIR)/widgetToolbar.o: widgets/widgetToolbar.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)
|
$(WOBJDIR)/widgetTreeView.o: widgets/widgetTreeView.c $(WIDGET_DEPS)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
|
|
||||||
194
dvx/dvxApp.c
194
dvx/dvxApp.c
|
|
@ -53,6 +53,7 @@ static void pollAnsiTermWidgetsWalk(AppContextT *ctx, WidgetT *w, WindowT *win);
|
||||||
static void pollKeyboard(AppContextT *ctx);
|
static void pollKeyboard(AppContextT *ctx);
|
||||||
static void pollMouse(AppContextT *ctx);
|
static void pollMouse(AppContextT *ctx);
|
||||||
static void refreshMinimizedIcons(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 updateCursorShape(AppContextT *ctx);
|
||||||
static void updateTooltip(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);
|
int32_t requiredMods = modifiers & (ACCEL_CTRL | ACCEL_ALT);
|
||||||
AccelTableT *table = win->accelTable;
|
AccelTableT *table = win->accelTable;
|
||||||
|
|
||||||
|
// Match against pre-normalized keys (Item 6)
|
||||||
for (int32_t i = 0; i < table->count; i++) {
|
for (int32_t i = 0; i < table->count; i++) {
|
||||||
AccelEntryT *e = &table->entries[i];
|
AccelEntryT *e = &table->entries[i];
|
||||||
|
|
||||||
// Uppercase the entry key too
|
if (e->normKey == matchKey && e->normMods == requiredMods) {
|
||||||
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) {
|
|
||||||
win->onMenu(win, e->cmdId);
|
win->onMenu(win, e->cmdId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -313,6 +306,18 @@ static void compositeAndFlush(AppContextT *ctx) {
|
||||||
|
|
||||||
dirtyListMerge(dl);
|
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++) {
|
for (int32_t i = 0; i < dl->count; i++) {
|
||||||
RectT *dr = &dl->rects[i];
|
RectT *dr = &dl->rects[i];
|
||||||
|
|
||||||
|
|
@ -332,13 +337,9 @@ static void compositeAndFlush(AppContextT *ctx) {
|
||||||
// 2. Draw minimized window icons (under all windows)
|
// 2. Draw minimized window icons (under all windows)
|
||||||
wmDrawMinimizedIcons(d, ops, &ctx->colors, ws, dr);
|
wmDrawMinimizedIcons(d, ops, &ctx->colors, ws, dr);
|
||||||
|
|
||||||
// 3. Walk window stack bottom-to-top
|
// 3. Walk pre-filtered visible window list bottom-to-top
|
||||||
for (int32_t j = 0; j < ws->count; j++) {
|
for (int32_t vi = 0; vi < visibleCount; vi++) {
|
||||||
WindowT *win = ws->windows[j];
|
WindowT *win = ws->windows[visibleIdx[vi]];
|
||||||
|
|
||||||
if (!win->visible || win->minimized) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if window intersects this dirty rect
|
// Check if window intersects this dirty rect
|
||||||
RectT winRect = {win->x, win->y, win->w, win->h};
|
RectT winRect = {win->x, win->y, win->w, win->h};
|
||||||
|
|
@ -660,7 +661,7 @@ static void dispatchEvents(AppContextT *ctx) {
|
||||||
|
|
||||||
// Hover tracking
|
// Hover tracking
|
||||||
int32_t relY = my - ctx->sysMenu.popupY - 2;
|
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) {
|
if (itemIdx >= 0 && itemIdx < ctx->sysMenu.itemCount && ctx->sysMenu.items[itemIdx].separator) {
|
||||||
itemIdx = -1;
|
itemIdx = -1;
|
||||||
|
|
@ -700,7 +701,7 @@ static void dispatchEvents(AppContextT *ctx) {
|
||||||
if (inCurrent) {
|
if (inCurrent) {
|
||||||
// Hover tracking in current level
|
// Hover tracking in current level
|
||||||
int32_t relY = my - ctx->popup.popupY - 2;
|
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) {
|
if (itemIdx < 0) {
|
||||||
itemIdx = 0;
|
itemIdx = 0;
|
||||||
|
|
@ -780,7 +781,7 @@ static void dispatchEvents(AppContextT *ctx) {
|
||||||
|
|
||||||
// Now current level IS this parent — update hover
|
// Now current level IS this parent — update hover
|
||||||
int32_t relY = my - ctx->popup.popupY - 2;
|
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) {
|
if (itemIdx < 0) {
|
||||||
itemIdx = 0;
|
itemIdx = 0;
|
||||||
|
|
@ -1063,10 +1064,19 @@ void dvxAddAccel(AccelTableT *table, int32_t key, int32_t modifiers, int32_t cmd
|
||||||
return;
|
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++];
|
AccelEntryT *e = &table->entries[table->count++];
|
||||||
e->key = key;
|
e->key = key;
|
||||||
e->modifiers = modifiers;
|
e->modifiers = modifiers;
|
||||||
e->cmdId = cmdId;
|
e->cmdId = cmdId;
|
||||||
|
e->normKey = normKey;
|
||||||
|
e->normMods = modifiers & (ACCEL_CTRL | ACCEL_ALT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1121,34 +1131,7 @@ void dvxCascadeWindows(AppContextT *ctx) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Un-maximize if needed
|
repositionWindow(ctx, win, offsetX, offsetY, winW, winH);
|
||||||
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);
|
|
||||||
|
|
||||||
offsetX += step;
|
offsetX += step;
|
||||||
offsetY += step;
|
offsetY += step;
|
||||||
|
|
@ -1316,6 +1299,7 @@ int32_t dvxInit(AppContextT *ctx, int32_t requestedW, int32_t requestedH, int32_
|
||||||
ctx->lastIconClickTime = 0;
|
ctx->lastIconClickTime = 0;
|
||||||
ctx->lastCloseClickId = -1;
|
ctx->lastCloseClickId = -1;
|
||||||
ctx->lastCloseClickTime = 0;
|
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
|
// Dirty the entire screen for first paint
|
||||||
dirtyListAdd(&ctx->dirty, 0, 0, ctx->display.width, ctx->display.height);
|
dirtyListAdd(&ctx->dirty, 0, 0, ctx->display.width, ctx->display.height);
|
||||||
|
|
@ -1573,12 +1557,6 @@ void dvxTileWindows(AppContextT *ctx) {
|
||||||
continue;
|
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 row = slot / cols;
|
||||||
int32_t col = slot % cols;
|
int32_t col = slot % cols;
|
||||||
|
|
||||||
|
|
@ -1587,25 +1565,7 @@ void dvxTileWindows(AppContextT *ctx) {
|
||||||
int32_t rowCols = (remaining < cols) ? remaining : cols;
|
int32_t rowCols = (remaining < cols) ? remaining : cols;
|
||||||
int32_t cellW = screenW / rowCols;
|
int32_t cellW = screenW / rowCols;
|
||||||
|
|
||||||
win->x = col * cellW;
|
repositionWindow(ctx, win, col * cellW, row * tileH, cellW, tileH);
|
||||||
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);
|
|
||||||
|
|
||||||
slot++;
|
slot++;
|
||||||
}
|
}
|
||||||
|
|
@ -1653,31 +1613,7 @@ void dvxTileWindowsH(AppContextT *ctx) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (win->maximized) {
|
repositionWindow(ctx, win, slot * tileW, 0, tileW, screenH);
|
||||||
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);
|
|
||||||
|
|
||||||
slot++;
|
slot++;
|
||||||
}
|
}
|
||||||
|
|
@ -1725,31 +1661,7 @@ void dvxTileWindowsV(AppContextT *ctx) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (win->maximized) {
|
repositionWindow(ctx, win, 0, slot * tileH, screenW, tileH);
|
||||||
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);
|
|
||||||
|
|
||||||
slot++;
|
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
|
// updateCursorShape
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ typedef struct AppContextT {
|
||||||
int32_t tooltipY;
|
int32_t tooltipY;
|
||||||
int32_t tooltipW; // size (pre-computed)
|
int32_t tooltipW; // size (pre-computed)
|
||||||
int32_t tooltipH;
|
int32_t tooltipH;
|
||||||
|
uint32_t charHeightRecip; // fixed-point 16.16 reciprocal of font.charHeight
|
||||||
} AppContextT;
|
} AppContextT;
|
||||||
|
|
||||||
// Initialize the application (VESA mode, input, etc.)
|
// Initialize the application (VESA mode, input, etc.)
|
||||||
|
|
|
||||||
|
|
@ -77,13 +77,15 @@ void dirtyListMerge(DirtyListT *dl) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Single-pass O(N²): for each rect, try to merge it into an
|
// O(N²) with bounded restarts: for each rect, try to merge it
|
||||||
// earlier rect. When a merge succeeds the merged rect may now
|
// into an earlier rect. When a merge succeeds the merged rect
|
||||||
// overlap others, so restart the inner scan for that slot.
|
// 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++) {
|
for (int32_t i = 0; i < dl->count; i++) {
|
||||||
|
int32_t restarts = 0;
|
||||||
bool merged = true;
|
bool merged = true;
|
||||||
|
|
||||||
while (merged) {
|
while (merged && restarts < 3) {
|
||||||
merged = false;
|
merged = false;
|
||||||
|
|
||||||
for (int32_t j = i + 1; j < dl->count; j++) {
|
for (int32_t j = i + 1; j < dl->count; j++) {
|
||||||
|
|
@ -95,6 +97,8 @@ void dirtyListMerge(DirtyListT *dl) {
|
||||||
merged = true;
|
merged = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
restarts++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
111
dvx/dvxDraw.c
111
dvx/dvxDraw.c
|
|
@ -185,24 +185,56 @@ int32_t drawChar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int3
|
||||||
if (x < clipX1) { colStart = clipX1 - x; }
|
if (x < clipX1) { colStart = clipX1 - x; }
|
||||||
if (x + cw > clipX2) { colEnd = clipX2 - 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) {
|
if (opaque) {
|
||||||
// Opaque mode: fill entire cell with bg, then overwrite fg pixels
|
// 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++) {
|
for (int32_t row = rowStart; row < rowEnd; row++) {
|
||||||
int32_t py = y + row;
|
int32_t py = y + row;
|
||||||
uint8_t *dst = d->backBuf + py * pitch + (x + colStart) * bpp;
|
uint8_t *dst = d->backBuf + py * pitch + (x + colStart) * bpp;
|
||||||
int32_t span = colEnd - colStart;
|
|
||||||
|
|
||||||
// Fill row with background
|
ops->spanFill(dst, bg, colEnd - colStart);
|
||||||
ops->spanFill(dst, bg, span);
|
|
||||||
|
|
||||||
// Overwrite foreground pixels
|
|
||||||
uint8_t bits = glyph[row];
|
uint8_t bits = glyph[row];
|
||||||
if (bits == 0) {
|
if (bits == 0) {
|
||||||
continue; // entirely background row — already filled
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shift bits to account for colStart
|
|
||||||
dst = d->backBuf + py * pitch + x * bpp;
|
dst = d->backBuf + py * pitch + x * bpp;
|
||||||
|
|
||||||
if (bpp == 2) {
|
if (bpp == 2) {
|
||||||
|
|
@ -227,8 +259,49 @@ int32_t drawChar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Transparent mode: only write foreground pixels
|
// 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++) {
|
for (int32_t row = rowStart; row < rowEnd; row++) {
|
||||||
uint8_t bits = glyph[row];
|
uint8_t bits = glyph[row];
|
||||||
if (bits == 0) {
|
if (bits == 0) {
|
||||||
|
|
@ -261,6 +334,7 @@ int32_t drawChar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return cw;
|
return cw;
|
||||||
}
|
}
|
||||||
|
|
@ -916,13 +990,30 @@ void rectFill(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w,
|
||||||
return;
|
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;
|
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++) {
|
for (int32_t i = 0; i < h; i++) {
|
||||||
ops->spanFill(row, color, w);
|
ops->spanFill(row, color, w);
|
||||||
row += pitch;
|
row += pitch;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1009,11 +1100,13 @@ static void spanFill8(uint8_t *dst, uint32_t color, int32_t count) {
|
||||||
uint8_t c = (uint8_t)color;
|
uint8_t c = (uint8_t)color;
|
||||||
uint32_t dword = (uint32_t)c | ((uint32_t)c << 8) | ((uint32_t)c << 16) | ((uint32_t)c << 24);
|
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) {
|
while (((uintptr_t)dst & 3) && count > 0) {
|
||||||
*dst++ = c;
|
*dst++ = c;
|
||||||
count--;
|
count--;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (count >= 4) {
|
if (count >= 4) {
|
||||||
int32_t dwordCount = count >> 2;
|
int32_t dwordCount = count >> 2;
|
||||||
|
|
|
||||||
|
|
@ -184,6 +184,7 @@ struct MenuT {
|
||||||
typedef struct {
|
typedef struct {
|
||||||
MenuT menus[MAX_MENUS];
|
MenuT menus[MAX_MENUS];
|
||||||
int32_t menuCount;
|
int32_t menuCount;
|
||||||
|
bool positionsDirty; // true = barX/barW need recomputation
|
||||||
} MenuBarT;
|
} MenuBarT;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -243,6 +244,8 @@ typedef struct {
|
||||||
int32_t key; // key code: ASCII char or KEY_Fxx for extended
|
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 modifiers; // ACCEL_CTRL, ACCEL_SHIFT, ACCEL_ALT (OR'd together)
|
||||||
int32_t cmdId; // command ID passed to onMenu
|
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;
|
} AccelEntryT;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
|
||||||
156
dvx/dvxWidget.h
156
dvx/dvxWidget.h
|
|
@ -132,6 +132,83 @@ typedef enum {
|
||||||
InputMaskedE // format mask (e.g. "(###) ###-####")
|
InputMaskedE // format mask (e.g. "(###) ###-####")
|
||||||
} InputModeE;
|
} 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
|
// Widget structure
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -142,6 +219,7 @@ typedef struct WidgetT {
|
||||||
WidgetTypeE type;
|
WidgetTypeE type;
|
||||||
const struct WidgetClassT *wclass;
|
const struct WidgetClassT *wclass;
|
||||||
char name[MAX_WIDGET_NAME];
|
char name[MAX_WIDGET_NAME];
|
||||||
|
uint32_t nameHash; // djb2 hash of name, 0 if unnamed
|
||||||
|
|
||||||
// Tree linkage
|
// Tree linkage
|
||||||
struct WidgetT *parent;
|
struct WidgetT *parent;
|
||||||
|
|
@ -372,78 +450,9 @@ typedef struct WidgetT {
|
||||||
int32_t lastY;
|
int32_t lastY;
|
||||||
} canvas;
|
} canvas;
|
||||||
|
|
||||||
struct {
|
AnsiTermDataT *ansiTerm;
|
||||||
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;
|
|
||||||
|
|
||||||
struct {
|
ListViewDataT *listView;
|
||||||
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;
|
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
int32_t value;
|
int32_t value;
|
||||||
|
|
@ -733,6 +742,9 @@ void wgtSetEnabled(WidgetT *w, bool enabled);
|
||||||
// Show/hide a widget
|
// Show/hide a widget
|
||||||
void wgtSetVisible(WidgetT *w, bool visible);
|
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)
|
// Find a widget by name (searches the subtree rooted at root)
|
||||||
WidgetT *wgtFind(WidgetT *root, const char *name);
|
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
|
// 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)
|
// Layout (called internally; available for manual trigger)
|
||||||
|
|
|
||||||
31
dvx/dvxWm.c
31
dvx/dvxWm.c
|
|
@ -64,6 +64,11 @@ static void computeMenuBarPositions(WindowT *win, const BitmapFontT *font) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip recomputation if positions are already up to date (Item 5)
|
||||||
|
if (!win->menuBar->positionsDirty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int32_t x = CHROME_TOTAL_SIDE;
|
int32_t x = CHROME_TOTAL_SIDE;
|
||||||
|
|
||||||
for (int32_t i = 0; i < win->menuBar->menuCount; i++) {
|
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;
|
menu->barW = labelW;
|
||||||
x += 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pre-compute source X lookup table (one division per column instead of per pixel)
|
// Pre-compute source lookup tables using fixed-point reciprocals
|
||||||
int32_t srcXTab[ICON_SIZE];
|
// 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;
|
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;
|
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++) {
|
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
|
// 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->label[MAX_MENU_LABEL - 1] = '\0';
|
||||||
menu->accelKey = accelParse(label);
|
menu->accelKey = accelParse(label);
|
||||||
bar->menuCount++;
|
bar->menuCount++;
|
||||||
|
bar->positionsDirty = true;
|
||||||
|
|
||||||
return menu;
|
return menu;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -40,9 +40,7 @@ WidgetT *wgtComboBox(WidgetT *parent, int32_t maxLen) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
int32_t wgtComboBoxGetSelected(const WidgetT *w) {
|
int32_t wgtComboBoxGetSelected(const WidgetT *w) {
|
||||||
if (!w || w->type != WidgetComboBoxE) {
|
VALIDATE_WIDGET(w, WidgetComboBoxE, -1);
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return w->as.comboBox.selectedIdx;
|
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) {
|
void wgtComboBoxSetItems(WidgetT *w, const char **items, int32_t count) {
|
||||||
if (!w || w->type != WidgetComboBoxE) {
|
VALIDATE_WIDGET_VOID(w, WidgetComboBoxE);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
w->as.comboBox.items = items;
|
w->as.comboBox.items = items;
|
||||||
w->as.comboBox.itemCount = count;
|
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) {
|
void wgtComboBoxSetSelected(WidgetT *w, int32_t idx) {
|
||||||
if (!w || w->type != WidgetComboBoxE) {
|
VALIDATE_WIDGET_VOID(w, WidgetComboBoxE);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
w->as.comboBox.selectedIdx = idx;
|
w->as.comboBox.selectedIdx = idx;
|
||||||
|
|
||||||
|
|
@ -402,40 +396,7 @@ void widgetComboBoxPaintPopup(WidgetT *w, DisplayT *d, const BlitOpsT *ops, cons
|
||||||
int32_t popH;
|
int32_t popH;
|
||||||
|
|
||||||
widgetDropdownPopupRect(w, font, d->clipH, &popX, &popY, &popW, &popH);
|
widgetDropdownPopupRect(w, font, d->clipH, &popX, &popY, &popW, &popH);
|
||||||
|
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);
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
// widgetScrollbarThumb
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,7 @@ WidgetT *wgtDropdown(WidgetT *parent) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
int32_t wgtDropdownGetSelected(const WidgetT *w) {
|
int32_t wgtDropdownGetSelected(const WidgetT *w) {
|
||||||
if (!w || w->type != WidgetDropdownE) {
|
VALIDATE_WIDGET(w, WidgetDropdownE, -1);
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return w->as.dropdown.selectedIdx;
|
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) {
|
void wgtDropdownSetItems(WidgetT *w, const char **items, int32_t count) {
|
||||||
if (!w || w->type != WidgetDropdownE) {
|
VALIDATE_WIDGET_VOID(w, WidgetDropdownE);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
w->as.dropdown.items = items;
|
w->as.dropdown.items = items;
|
||||||
w->as.dropdown.itemCount = count;
|
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) {
|
void wgtDropdownSetSelected(WidgetT *w, int32_t idx) {
|
||||||
if (!w || w->type != WidgetDropdownE) {
|
VALIDATE_WIDGET_VOID(w, WidgetDropdownE);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
w->as.dropdown.selectedIdx = idx;
|
w->as.dropdown.selectedIdx = idx;
|
||||||
}
|
}
|
||||||
|
|
@ -254,38 +248,5 @@ void widgetDropdownPaintPopup(WidgetT *w, DisplayT *d, const BlitOpsT *ops, cons
|
||||||
int32_t popH;
|
int32_t popH;
|
||||||
|
|
||||||
widgetDropdownPopupRect(w, font, d->clipH, &popX, &popY, &popW, &popH);
|
widgetDropdownPopupRect(w, font, d->clipH, &popX, &popY, &popW, &popH);
|
||||||
|
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);
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -266,17 +266,17 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
||||||
newW = 20;
|
newW = 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newW != sResizeListView->as.listView.resolvedColW[sResizeCol]) {
|
if (newW != sResizeListView->as.listView->resolvedColW[sResizeCol]) {
|
||||||
sResizeListView->as.listView.resolvedColW[sResizeCol] = newW;
|
sResizeListView->as.listView->resolvedColW[sResizeCol] = newW;
|
||||||
|
|
||||||
// Recalculate totalColW
|
// Recalculate totalColW
|
||||||
int32_t total = 0;
|
int32_t total = 0;
|
||||||
|
|
||||||
for (int32_t c = 0; c < sResizeListView->as.listView.colCount; c++) {
|
for (int32_t c = 0; c < sResizeListView->as.listView->colCount; c++) {
|
||||||
total += sResizeListView->as.listView.resolvedColW[c];
|
total += sResizeListView->as.listView->resolvedColW[c];
|
||||||
}
|
}
|
||||||
|
|
||||||
sResizeListView->as.listView.totalColW = total;
|
sResizeListView->as.listView->totalColW = total;
|
||||||
wgtInvalidatePaint(root);
|
wgtInvalidatePaint(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -651,11 +651,11 @@ void widgetReorderDrop(WidgetT *w) {
|
||||||
w->onChange(w);
|
w->onChange(w);
|
||||||
}
|
}
|
||||||
} else if (w->type == WidgetListViewE) {
|
} else if (w->type == WidgetListViewE) {
|
||||||
int32_t drag = w->as.listView.dragIdx;
|
int32_t drag = w->as.listView->dragIdx;
|
||||||
int32_t drop = w->as.listView.dropIdx;
|
int32_t drop = w->as.listView->dropIdx;
|
||||||
int32_t colCnt = w->as.listView.colCount;
|
int32_t colCnt = w->as.listView->colCount;
|
||||||
w->as.listView.dragIdx = -1;
|
w->as.listView->dragIdx = -1;
|
||||||
w->as.listView.dropIdx = -1;
|
w->as.listView->dropIdx = -1;
|
||||||
|
|
||||||
if (drag < 0 || drop < 0 || drag == drop || drag == drop - 1) {
|
if (drag < 0 || drop < 0 || drag == drop || drag == drop - 1) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -665,79 +665,79 @@ void widgetReorderDrop(WidgetT *w) {
|
||||||
const char *temp[LISTVIEW_MAX_COLS];
|
const char *temp[LISTVIEW_MAX_COLS];
|
||||||
|
|
||||||
for (int32_t c = 0; c < colCnt; c++) {
|
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;
|
uint8_t selBit = 0;
|
||||||
|
|
||||||
if (w->as.listView.multiSelect && w->as.listView.selBits) {
|
if (w->as.listView->multiSelect && w->as.listView->selBits) {
|
||||||
selBit = w->as.listView.selBits[drag];
|
selBit = w->as.listView->selBits[drag];
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t sortVal = 0;
|
int32_t sortVal = 0;
|
||||||
|
|
||||||
if (w->as.listView.sortIndex) {
|
if (w->as.listView->sortIndex) {
|
||||||
sortVal = w->as.listView.sortIndex[drag];
|
sortVal = w->as.listView->sortIndex[drag];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (drag < drop) {
|
if (drag < drop) {
|
||||||
for (int32_t i = drag; i < drop - 1; i++) {
|
for (int32_t i = drag; i < drop - 1; i++) {
|
||||||
for (int32_t c = 0; c < colCnt; c++) {
|
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) {
|
if (w->as.listView->selBits) {
|
||||||
w->as.listView.selBits[i] = w->as.listView.selBits[i + 1];
|
w->as.listView->selBits[i] = w->as.listView->selBits[i + 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (w->as.listView.sortIndex) {
|
if (w->as.listView->sortIndex) {
|
||||||
w->as.listView.sortIndex[i] = w->as.listView.sortIndex[i + 1];
|
w->as.listView->sortIndex[i] = w->as.listView->sortIndex[i + 1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t dest = drop - 1;
|
int32_t dest = drop - 1;
|
||||||
|
|
||||||
for (int32_t c = 0; c < colCnt; c++) {
|
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) {
|
if (w->as.listView->selBits) {
|
||||||
w->as.listView.selBits[dest] = selBit;
|
w->as.listView->selBits[dest] = selBit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (w->as.listView.sortIndex) {
|
if (w->as.listView->sortIndex) {
|
||||||
w->as.listView.sortIndex[dest] = sortVal;
|
w->as.listView->sortIndex[dest] = sortVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
w->as.listView.selectedIdx = dest;
|
w->as.listView->selectedIdx = dest;
|
||||||
} else {
|
} else {
|
||||||
for (int32_t i = drag; i > drop; i--) {
|
for (int32_t i = drag; i > drop; i--) {
|
||||||
for (int32_t c = 0; c < colCnt; c++) {
|
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) {
|
if (w->as.listView->selBits) {
|
||||||
w->as.listView.selBits[i] = w->as.listView.selBits[i - 1];
|
w->as.listView->selBits[i] = w->as.listView->selBits[i - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (w->as.listView.sortIndex) {
|
if (w->as.listView->sortIndex) {
|
||||||
w->as.listView.sortIndex[i] = w->as.listView.sortIndex[i - 1];
|
w->as.listView->sortIndex[i] = w->as.listView->sortIndex[i - 1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int32_t c = 0; c < colCnt; c++) {
|
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) {
|
if (w->as.listView->selBits) {
|
||||||
w->as.listView.selBits[drop] = selBit;
|
w->as.listView->selBits[drop] = selBit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (w->as.listView.sortIndex) {
|
if (w->as.listView->sortIndex) {
|
||||||
w->as.listView.sortIndex[drop] = sortVal;
|
w->as.listView->sortIndex[drop] = sortVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
w->as.listView.selectedIdx = drop;
|
w->as.listView->selectedIdx = drop;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (w->onChange) {
|
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 innerY = w->y + LISTVIEW_BORDER + headerH;
|
||||||
int32_t innerH = w->h - LISTVIEW_BORDER * 2 - headerH;
|
int32_t innerH = w->h - LISTVIEW_BORDER * 2 - headerH;
|
||||||
int32_t visibleRows = innerH / font->charHeight;
|
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) {
|
if (maxScroll < 0) {
|
||||||
maxScroll = 0;
|
maxScroll = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-scroll when dragging near edges
|
// Auto-scroll when dragging near edges
|
||||||
if (y < innerY + font->charHeight && w->as.listView.scrollPos > 0) {
|
if (y < innerY + font->charHeight && w->as.listView->scrollPos > 0) {
|
||||||
w->as.listView.scrollPos--;
|
w->as.listView->scrollPos--;
|
||||||
} else if (y > innerY + innerH - font->charHeight && w->as.listView.scrollPos < maxScroll) {
|
} else if (y > innerY + innerH - font->charHeight && w->as.listView->scrollPos < maxScroll) {
|
||||||
w->as.listView.scrollPos++;
|
w->as.listView->scrollPos++;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t relY = y - innerY;
|
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 halfRow = (relY % font->charHeight) >= font->charHeight / 2 ? 1 : 0;
|
||||||
int32_t drop = row + halfRow;
|
int32_t drop = row + halfRow;
|
||||||
|
|
||||||
|
|
@ -866,11 +866,11 @@ void widgetReorderUpdate(WidgetT *w, WidgetT *root, int32_t x, int32_t y) {
|
||||||
drop = 0;
|
drop = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (drop > w->as.listView.rowCount) {
|
if (drop > w->as.listView->rowCount) {
|
||||||
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) {
|
} else if (w->type == WidgetTreeViewE) {
|
||||||
int32_t innerY = w->y + TREE_BORDER;
|
int32_t innerY = w->y + TREE_BORDER;
|
||||||
int32_t innerH = w->h - TREE_BORDER * 2;
|
int32_t innerH = w->h - TREE_BORDER * 2;
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
void wgtImageSetData(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int32_t pitch) {
|
||||||
if (!w || w->type != WidgetImageE) {
|
VALIDATE_WIDGET_VOID(w, WidgetImageE);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
free(w->as.image.data);
|
free(w->as.image.data);
|
||||||
w->as.image.data = data;
|
w->as.image.data = data;
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
void wgtImageButtonSetData(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int32_t pitch) {
|
||||||
if (!w || w->type != WidgetImageButtonE) {
|
VALIDATE_WIDGET_VOID(w, WidgetImageButtonE);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
free(w->as.imageButton.data);
|
free(w->as.imageButton.data);
|
||||||
w->as.imageButton.data = data;
|
w->as.imageButton.data = data;
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,16 @@ typedef struct WidgetClassT {
|
||||||
|
|
||||||
extern const WidgetClassT *widgetClassTable[];
|
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
|
// Constants
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -69,7 +79,7 @@ extern const WidgetClassT *widgetClassTable[];
|
||||||
#define TREE_EXPAND_SIZE 9
|
#define TREE_EXPAND_SIZE 9
|
||||||
#define TREE_ICON_GAP 4
|
#define TREE_ICON_GAP 4
|
||||||
#define TREE_BORDER 2
|
#define TREE_BORDER 2
|
||||||
#define TREE_SB_W 14
|
#define WGT_SB_W 14
|
||||||
#define TREE_MIN_ROWS 4
|
#define TREE_MIN_ROWS 4
|
||||||
#define SB_MIN_THUMB 14
|
#define SB_MIN_THUMB 14
|
||||||
#define SPLITTER_BAR_W 5
|
#define SPLITTER_BAR_W 5
|
||||||
|
|
@ -137,9 +147,28 @@ WidgetT *widgetHitTest(WidgetT *w, int32_t x, int32_t y);
|
||||||
bool widgetIsFocusable(WidgetTypeE type);
|
bool widgetIsFocusable(WidgetTypeE type);
|
||||||
bool widgetIsBoxContainer(WidgetTypeE type);
|
bool widgetIsBoxContainer(WidgetTypeE type);
|
||||||
bool widgetIsHorizContainer(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 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);
|
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)
|
// Layout functions (widgetLayout.c)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
|
|
||||||
#define LISTBOX_PAD 2
|
#define LISTBOX_PAD 2
|
||||||
#define LISTBOX_MIN_ROWS 4
|
#define LISTBOX_MIN_ROWS 4
|
||||||
#define LISTBOX_SB_W 14
|
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -137,9 +136,7 @@ void wgtListBoxClearSelection(WidgetT *w) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
int32_t wgtListBoxGetSelected(const WidgetT *w) {
|
int32_t wgtListBoxGetSelected(const WidgetT *w) {
|
||||||
if (!w || w->type != WidgetListBoxE) {
|
VALIDATE_WIDGET(w, WidgetListBoxE, -1);
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return w->as.listBox.selectedIdx;
|
return w->as.listBox.selectedIdx;
|
||||||
}
|
}
|
||||||
|
|
@ -150,9 +147,7 @@ int32_t wgtListBoxGetSelected(const WidgetT *w) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
bool wgtListBoxIsItemSelected(const WidgetT *w, int32_t idx) {
|
bool wgtListBoxIsItemSelected(const WidgetT *w, int32_t idx) {
|
||||||
if (!w || w->type != WidgetListBoxE) {
|
VALIDATE_WIDGET(w, WidgetListBoxE, false);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!w->as.listBox.multiSelect) {
|
if (!w->as.listBox.multiSelect) {
|
||||||
return idx == w->as.listBox.selectedIdx;
|
return idx == w->as.listBox.selectedIdx;
|
||||||
|
|
@ -184,9 +179,7 @@ void wgtListBoxSelectAll(WidgetT *w) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void wgtListBoxSetItemSelected(WidgetT *w, int32_t idx, bool selected) {
|
void wgtListBoxSetItemSelected(WidgetT *w, int32_t idx, bool selected) {
|
||||||
if (!w || w->type != WidgetListBoxE) {
|
VALIDATE_WIDGET_VOID(w, WidgetListBoxE);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!w->as.listBox.selBits || idx < 0 || idx >= w->as.listBox.itemCount) {
|
if (!w->as.listBox.selBits || idx < 0 || idx >= w->as.listBox.itemCount) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -201,9 +194,7 @@ void wgtListBoxSetItemSelected(WidgetT *w, int32_t idx, bool selected) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void wgtListBoxSetItems(WidgetT *w, const char **items, int32_t count) {
|
void wgtListBoxSetItems(WidgetT *w, const char **items, int32_t count) {
|
||||||
if (!w || w->type != WidgetListBoxE) {
|
VALIDATE_WIDGET_VOID(w, WidgetListBoxE);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
w->as.listBox.items = items;
|
w->as.listBox.items = items;
|
||||||
w->as.listBox.itemCount = count;
|
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) {
|
void wgtListBoxSetReorderable(WidgetT *w, bool reorderable) {
|
||||||
if (!w || w->type != WidgetListBoxE) {
|
VALIDATE_WIDGET_VOID(w, WidgetListBoxE);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
w->as.listBox.reorderable = reorderable;
|
w->as.listBox.reorderable = reorderable;
|
||||||
}
|
}
|
||||||
|
|
@ -259,9 +248,7 @@ void wgtListBoxSetReorderable(WidgetT *w, bool reorderable) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void wgtListBoxSetMultiSelect(WidgetT *w, bool multi) {
|
void wgtListBoxSetMultiSelect(WidgetT *w, bool multi) {
|
||||||
if (!w || w->type != WidgetListBoxE) {
|
VALIDATE_WIDGET_VOID(w, WidgetListBoxE);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
w->as.listBox.multiSelect = multi;
|
w->as.listBox.multiSelect = multi;
|
||||||
allocSelBits(w);
|
allocSelBits(w);
|
||||||
|
|
@ -278,9 +265,7 @@ void wgtListBoxSetMultiSelect(WidgetT *w, bool multi) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void wgtListBoxSetSelected(WidgetT *w, int32_t idx) {
|
void wgtListBoxSetSelected(WidgetT *w, int32_t idx) {
|
||||||
if (!w || w->type != WidgetListBoxE) {
|
VALIDATE_WIDGET_VOID(w, WidgetListBoxE);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
w->as.listBox.selectedIdx = idx;
|
w->as.listBox.selectedIdx = idx;
|
||||||
w->as.listBox.anchorIdx = idx;
|
w->as.listBox.anchorIdx = idx;
|
||||||
|
|
@ -308,7 +293,7 @@ void widgetListBoxCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||||
maxItemW = minW;
|
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;
|
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;
|
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;
|
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData;
|
||||||
const BitmapFontT *font = &ctx->font;
|
const BitmapFontT *font = &ctx->font;
|
||||||
int32_t visibleRows = (w->h - LISTBOX_BORDER * 2) / font->charHeight;
|
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;
|
visibleRows = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
newSel += visibleRows;
|
int32_t newSel = widgetNavigateIndex(key, sel, w->as.listBox.itemCount, 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;
|
|
||||||
|
|
||||||
if (newSel < 0) {
|
if (newSel < 0) {
|
||||||
newSel = 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -454,40 +397,27 @@ void widgetListBoxOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
||||||
|
|
||||||
// Check if click is on the scrollbar
|
// Check if click is on the scrollbar
|
||||||
if (needSb) {
|
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) {
|
if (vx >= sbX) {
|
||||||
int32_t sbY = hit->y + LISTBOX_BORDER;
|
int32_t relY = vy - (hit->y + LISTBOX_BORDER);
|
||||||
int32_t sbH = innerH;
|
ScrollHitE sh = widgetScrollbarHitTest(innerH, relY, hit->as.listBox.itemCount, visibleRows, hit->as.listBox.scrollPos);
|
||||||
int32_t relY = vy - sbY;
|
|
||||||
int32_t trackLen = sbH - LISTBOX_SB_W * 2;
|
|
||||||
|
|
||||||
if (relY < LISTBOX_SB_W) {
|
if (sh == ScrollHitArrowDecE) {
|
||||||
// Up arrow
|
|
||||||
if (hit->as.listBox.scrollPos > 0) {
|
if (hit->as.listBox.scrollPos > 0) {
|
||||||
hit->as.listBox.scrollPos--;
|
hit->as.listBox.scrollPos--;
|
||||||
}
|
}
|
||||||
} else if (relY >= sbH - LISTBOX_SB_W) {
|
} else if (sh == ScrollHitArrowIncE) {
|
||||||
// Down arrow
|
|
||||||
if (hit->as.listBox.scrollPos < maxScroll) {
|
if (hit->as.listBox.scrollPos < maxScroll) {
|
||||||
hit->as.listBox.scrollPos++;
|
hit->as.listBox.scrollPos++;
|
||||||
}
|
}
|
||||||
} else if (trackLen > 0) {
|
} else if (sh == ScrollHitPageDecE) {
|
||||||
// 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) {
|
|
||||||
hit->as.listBox.scrollPos -= visibleRows;
|
hit->as.listBox.scrollPos -= visibleRows;
|
||||||
hit->as.listBox.scrollPos = clampInt(hit->as.listBox.scrollPos, 0, maxScroll);
|
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 += visibleRows;
|
||||||
hit->as.listBox.scrollPos = clampInt(hit->as.listBox.scrollPos, 0, maxScroll);
|
hit->as.listBox.scrollPos = clampInt(hit->as.listBox.scrollPos, 0, maxScroll);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
hit->focused = true;
|
hit->focused = true;
|
||||||
return;
|
return;
|
||||||
|
|
@ -564,7 +494,7 @@ void widgetListBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
|
||||||
int32_t contentW = w->w - LISTBOX_BORDER * 2;
|
int32_t contentW = w->w - LISTBOX_BORDER * 2;
|
||||||
|
|
||||||
if (needSb) {
|
if (needSb) {
|
||||||
contentW -= LISTBOX_SB_W;
|
contentW -= WGT_SB_W;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sunken border
|
// Sunken border
|
||||||
|
|
@ -628,52 +558,9 @@ void widgetListBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
|
||||||
|
|
||||||
// Draw scrollbar
|
// Draw scrollbar
|
||||||
if (needSb) {
|
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 sbY = w->y + LISTBOX_BORDER;
|
||||||
int32_t sbH = innerH;
|
widgetDrawScrollbarV(d, ops, colors, sbX, sbY, innerH, w->as.listBox.itemCount, visibleRows, w->as.listBox.scrollPos);
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (w->focused) {
|
if (w->focused) {
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
// wgtFind
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -158,19 +195,24 @@ WidgetT *wgtFind(WidgetT *root, const char *name) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root->name[0] && strcmp(root->name, name) == 0) {
|
uint32_t hash = widgetHashName(name);
|
||||||
return root;
|
|
||||||
|
return wgtFindByHash(root, name, hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// wgtSetName
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void wgtSetName(WidgetT *w, const char *name) {
|
||||||
|
if (!w || !name) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (WidgetT *c = root->firstChild; c; c = c->nextSibling) {
|
strncpy(w->name, name, MAX_WIDGET_NAME - 1);
|
||||||
WidgetT *found = wgtFind(c, name);
|
w->name[MAX_WIDGET_NAME - 1] = '\0';
|
||||||
|
w->nameHash = widgetHashName(w->name);
|
||||||
if (found) {
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -306,8 +348,16 @@ void wgtPaint(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT
|
||||||
// wgtSetDebugLayout
|
// wgtSetDebugLayout
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void wgtSetDebugLayout(bool enabled) {
|
void wgtSetDebugLayout(AppContextT *ctx, bool enabled) {
|
||||||
sDebugLayout = 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,9 +42,7 @@ WidgetT *wgtProgressBarV(WidgetT *parent) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
int32_t wgtProgressBarGetValue(const WidgetT *w) {
|
int32_t wgtProgressBarGetValue(const WidgetT *w) {
|
||||||
if (!w || w->type != WidgetProgressBarE) {
|
VALIDATE_WIDGET(w, WidgetProgressBarE, 0);
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return w->as.progressBar.value;
|
return w->as.progressBar.value;
|
||||||
}
|
}
|
||||||
|
|
@ -55,9 +53,7 @@ int32_t wgtProgressBarGetValue(const WidgetT *w) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void wgtProgressBarSetValue(WidgetT *w, int32_t value) {
|
void wgtProgressBarSetValue(WidgetT *w, int32_t value) {
|
||||||
if (!w || w->type != WidgetProgressBarE) {
|
VALIDATE_WIDGET_VOID(w, WidgetProgressBarE);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value < 0) {
|
if (value < 0) {
|
||||||
value = 0;
|
value = 0;
|
||||||
|
|
|
||||||
152
dvx/widgets/widgetScrollbar.c
Normal file
152
dvx/widgets/widgetScrollbar.c
Normal 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;
|
||||||
|
}
|
||||||
|
|
@ -27,9 +27,7 @@ WidgetT *wgtSlider(WidgetT *parent, int32_t minVal, int32_t maxVal) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
int32_t wgtSliderGetValue(const WidgetT *w) {
|
int32_t wgtSliderGetValue(const WidgetT *w) {
|
||||||
if (!w || w->type != WidgetSliderE) {
|
VALIDATE_WIDGET(w, WidgetSliderE, 0);
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return w->as.slider.value;
|
return w->as.slider.value;
|
||||||
}
|
}
|
||||||
|
|
@ -40,9 +38,7 @@ int32_t wgtSliderGetValue(const WidgetT *w) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void wgtSliderSetValue(WidgetT *w, int32_t value) {
|
void wgtSliderSetValue(WidgetT *w, int32_t value) {
|
||||||
if (!w || w->type != WidgetSliderE) {
|
VALIDATE_WIDGET_VOID(w, WidgetSliderE);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value < w->as.slider.minValue) {
|
if (value < w->as.slider.minValue) {
|
||||||
value = w->as.slider.minValue;
|
value = w->as.slider.minValue;
|
||||||
|
|
|
||||||
|
|
@ -458,9 +458,7 @@ WidgetT *wgtSpinner(WidgetT *parent, int32_t minVal, int32_t maxVal, int32_t ste
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
int32_t wgtSpinnerGetValue(const WidgetT *w) {
|
int32_t wgtSpinnerGetValue(const WidgetT *w) {
|
||||||
if (!w || w->type != WidgetSpinnerE) {
|
VALIDATE_WIDGET(w, WidgetSpinnerE, 0);
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return w->as.spinner.value;
|
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) {
|
void wgtSpinnerSetRange(WidgetT *w, int32_t minVal, int32_t maxVal) {
|
||||||
if (!w || w->type != WidgetSpinnerE) {
|
VALIDATE_WIDGET_VOID(w, WidgetSpinnerE);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
w->as.spinner.minValue = minVal;
|
w->as.spinner.minValue = minVal;
|
||||||
w->as.spinner.maxValue = maxVal;
|
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) {
|
void wgtSpinnerSetStep(WidgetT *w, int32_t step) {
|
||||||
if (!w || w->type != WidgetSpinnerE) {
|
VALIDATE_WIDGET_VOID(w, WidgetSpinnerE);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
w->as.spinner.step = step > 0 ? step : 1;
|
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) {
|
void wgtSpinnerSetValue(WidgetT *w, int32_t value) {
|
||||||
if (!w || w->type != WidgetSpinnerE) {
|
VALIDATE_WIDGET_VOID(w, WidgetSpinnerE);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
w->as.spinner.value = value;
|
w->as.spinner.value = value;
|
||||||
spinnerClampAndFormat(w);
|
spinnerClampAndFormat(w);
|
||||||
|
|
|
||||||
|
|
@ -137,9 +137,7 @@ WidgetT *wgtTabControl(WidgetT *parent) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
int32_t wgtTabControlGetActive(const WidgetT *w) {
|
int32_t wgtTabControlGetActive(const WidgetT *w) {
|
||||||
if (!w || w->type != WidgetTabControlE) {
|
VALIDATE_WIDGET(w, WidgetTabControlE, 0);
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return w->as.tabControl.activeTab;
|
return w->as.tabControl.activeTab;
|
||||||
}
|
}
|
||||||
|
|
@ -150,9 +148,7 @@ int32_t wgtTabControlGetActive(const WidgetT *w) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void wgtTabControlSetActive(WidgetT *w, int32_t idx) {
|
void wgtTabControlSetActive(WidgetT *w, int32_t idx) {
|
||||||
if (!w || w->type != WidgetTabControlE) {
|
VALIDATE_WIDGET_VOID(w, WidgetTabControlE);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
w->as.tabControl.activeTab = idx;
|
w->as.tabControl.activeTab = idx;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -158,23 +158,23 @@ static bool clearSelectionOnWidget(WidgetT *w) {
|
||||||
w->as.comboBox.selStart = -1;
|
w->as.comboBox.selStart = -1;
|
||||||
w->as.comboBox.selEnd = -1;
|
w->as.comboBox.selEnd = -1;
|
||||||
} else if (w->type == WidgetAnsiTermE) {
|
} else if (w->type == WidgetAnsiTermE) {
|
||||||
if (w->as.ansiTerm.selStartLine >= 0 &&
|
if (w->as.ansiTerm->selStartLine >= 0 &&
|
||||||
(w->as.ansiTerm.selStartLine != w->as.ansiTerm.selEndLine ||
|
(w->as.ansiTerm->selStartLine != w->as.ansiTerm->selEndLine ||
|
||||||
w->as.ansiTerm.selStartCol != w->as.ansiTerm.selEndCol)) {
|
w->as.ansiTerm->selStartCol != w->as.ansiTerm->selEndCol)) {
|
||||||
w->as.ansiTerm.dirtyRows = 0xFFFFFFFF;
|
w->as.ansiTerm->dirtyRows = 0xFFFFFFFF;
|
||||||
w->as.ansiTerm.selStartLine = -1;
|
w->as.ansiTerm->selStartLine = -1;
|
||||||
w->as.ansiTerm.selStartCol = -1;
|
w->as.ansiTerm->selStartCol = -1;
|
||||||
w->as.ansiTerm.selEndLine = -1;
|
w->as.ansiTerm->selEndLine = -1;
|
||||||
w->as.ansiTerm.selEndCol = -1;
|
w->as.ansiTerm->selEndCol = -1;
|
||||||
w->as.ansiTerm.selecting = false;
|
w->as.ansiTerm->selecting = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
w->as.ansiTerm.selStartLine = -1;
|
w->as.ansiTerm->selStartLine = -1;
|
||||||
w->as.ansiTerm.selStartCol = -1;
|
w->as.ansiTerm->selStartCol = -1;
|
||||||
w->as.ansiTerm.selEndLine = -1;
|
w->as.ansiTerm->selEndLine = -1;
|
||||||
w->as.ansiTerm.selEndCol = -1;
|
w->as.ansiTerm->selEndCol = -1;
|
||||||
w->as.ansiTerm.selecting = false;
|
w->as.ansiTerm->selecting = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -1789,8 +1789,8 @@ void widgetTextDragUpdate(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||||
} else if (w->type == WidgetAnsiTermE) {
|
} else if (w->type == WidgetAnsiTermE) {
|
||||||
int32_t baseX = w->x + 2; // ANSI_BORDER
|
int32_t baseX = w->x + 2; // ANSI_BORDER
|
||||||
int32_t baseY = w->y + 2;
|
int32_t baseY = w->y + 2;
|
||||||
int32_t cols = w->as.ansiTerm.cols;
|
int32_t cols = w->as.ansiTerm->cols;
|
||||||
int32_t rows = w->as.ansiTerm.rows;
|
int32_t rows = w->as.ansiTerm->rows;
|
||||||
|
|
||||||
int32_t clickRow = (vy - baseY) / font->charHeight;
|
int32_t clickRow = (vy - baseY) / font->charHeight;
|
||||||
int32_t clickCol = (vx - baseX) / font->charWidth;
|
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;
|
clickCol = cols;
|
||||||
}
|
}
|
||||||
|
|
||||||
w->as.ansiTerm.selEndLine = w->as.ansiTerm.scrollPos + clickRow;
|
w->as.ansiTerm->selEndLine = w->as.ansiTerm->scrollPos + clickRow;
|
||||||
w->as.ansiTerm.selEndCol = clickCol;
|
w->as.ansiTerm->selEndCol = clickCol;
|
||||||
w->as.ansiTerm.dirtyRows = 0xFFFFFFFF;
|
w->as.ansiTerm->dirtyRows = 0xFFFFFFFF;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,6 @@
|
||||||
static int32_t calcTreeItemsHeight(WidgetT *parent, const BitmapFontT *font);
|
static int32_t calcTreeItemsHeight(WidgetT *parent, const BitmapFontT *font);
|
||||||
static int32_t calcTreeItemsMaxWidth(WidgetT *parent, const BitmapFontT *font, int32_t depth);
|
static int32_t calcTreeItemsMaxWidth(WidgetT *parent, const BitmapFontT *font, int32_t depth);
|
||||||
static void clearAllSelections(WidgetT *parent);
|
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 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 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);
|
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
|
// firstVisibleItem
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -542,7 +415,7 @@ static void treeCalcScrollbarNeeds(WidgetT *w, const BitmapFontT *font, int32_t
|
||||||
|
|
||||||
// V scrollbar reduces available width — may trigger H scrollbar
|
// V scrollbar reduces available width — may trigger H scrollbar
|
||||||
if (needVSb) {
|
if (needVSb) {
|
||||||
innerW -= TREE_SB_W;
|
innerW -= WGT_SB_W;
|
||||||
|
|
||||||
if (!needHSb && totalW > innerW) {
|
if (!needHSb && totalW > innerW) {
|
||||||
needHSb = true;
|
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
|
// H scrollbar reduces available height — may trigger V scrollbar
|
||||||
if (needHSb) {
|
if (needHSb) {
|
||||||
innerH -= TREE_SB_W;
|
innerH -= WGT_SB_W;
|
||||||
|
|
||||||
if (!needVSb && totalH > innerH) {
|
if (!needVSb && totalH > innerH) {
|
||||||
needVSb = true;
|
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) {
|
bool wgtTreeItemIsExpanded(const WidgetT *w) {
|
||||||
if (!w || w->type != WidgetTreeItemE) {
|
VALIDATE_WIDGET(w, WidgetTreeItemE, false);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return w->as.treeItem.expanded;
|
return w->as.treeItem.expanded;
|
||||||
}
|
}
|
||||||
|
|
@ -697,9 +568,7 @@ bool wgtTreeItemIsExpanded(const WidgetT *w) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void wgtTreeItemSetExpanded(WidgetT *w, bool expanded) {
|
void wgtTreeItemSetExpanded(WidgetT *w, bool expanded) {
|
||||||
if (!w || w->type != WidgetTreeItemE) {
|
VALIDATE_WIDGET_VOID(w, WidgetTreeItemE);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
w->as.treeItem.expanded = expanded;
|
w->as.treeItem.expanded = expanded;
|
||||||
}
|
}
|
||||||
|
|
@ -725,9 +594,7 @@ WidgetT *wgtTreeView(WidgetT *parent) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
WidgetT *wgtTreeViewGetSelected(const WidgetT *w) {
|
WidgetT *wgtTreeViewGetSelected(const WidgetT *w) {
|
||||||
if (!w || w->type != WidgetTreeViewE) {
|
VALIDATE_WIDGET(w, WidgetTreeViewE, NULL);
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return w->as.treeView.selectedItem;
|
return w->as.treeView.selectedItem;
|
||||||
}
|
}
|
||||||
|
|
@ -738,9 +605,7 @@ WidgetT *wgtTreeViewGetSelected(const WidgetT *w) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void wgtTreeViewSetSelected(WidgetT *w, WidgetT *item) {
|
void wgtTreeViewSetSelected(WidgetT *w, WidgetT *item) {
|
||||||
if (!w || w->type != WidgetTreeViewE) {
|
VALIDATE_WIDGET_VOID(w, WidgetTreeViewE);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
w->as.treeView.selectedItem = item;
|
w->as.treeView.selectedItem = item;
|
||||||
|
|
||||||
|
|
@ -757,9 +622,7 @@ void wgtTreeViewSetSelected(WidgetT *w, WidgetT *item) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void wgtTreeViewSetMultiSelect(WidgetT *w, bool multi) {
|
void wgtTreeViewSetMultiSelect(WidgetT *w, bool multi) {
|
||||||
if (!w || w->type != WidgetTreeViewE) {
|
VALIDATE_WIDGET_VOID(w, WidgetTreeViewE);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
w->as.treeView.multiSelect = multi;
|
w->as.treeView.multiSelect = multi;
|
||||||
}
|
}
|
||||||
|
|
@ -770,9 +633,7 @@ void wgtTreeViewSetMultiSelect(WidgetT *w, bool multi) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void wgtTreeViewSetReorderable(WidgetT *w, bool reorderable) {
|
void wgtTreeViewSetReorderable(WidgetT *w, bool reorderable) {
|
||||||
if (!w || w->type != WidgetTreeViewE) {
|
VALIDATE_WIDGET_VOID(w, WidgetTreeViewE);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
w->as.treeView.reorderable = reorderable;
|
w->as.treeView.reorderable = reorderable;
|
||||||
}
|
}
|
||||||
|
|
@ -795,9 +656,7 @@ WidgetT *widgetTreeViewNextVisible(WidgetT *item, WidgetT *treeView) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
bool wgtTreeItemIsSelected(const WidgetT *w) {
|
bool wgtTreeItemIsSelected(const WidgetT *w) {
|
||||||
if (!w || w->type != WidgetTreeItemE) {
|
VALIDATE_WIDGET(w, WidgetTreeItemE, false);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return w->as.treeItem.selected;
|
return w->as.treeItem.selected;
|
||||||
}
|
}
|
||||||
|
|
@ -808,9 +667,7 @@ bool wgtTreeItemIsSelected(const WidgetT *w) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void wgtTreeItemSetSelected(WidgetT *w, bool selected) {
|
void wgtTreeItemSetSelected(WidgetT *w, bool selected) {
|
||||||
if (!w || w->type != WidgetTreeItemE) {
|
VALIDATE_WIDGET_VOID(w, WidgetTreeItemE);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
w->as.treeItem.selected = selected;
|
w->as.treeItem.selected = selected;
|
||||||
}
|
}
|
||||||
|
|
@ -823,7 +680,7 @@ void wgtTreeItemSetSelected(WidgetT *w, bool selected) {
|
||||||
void widgetTreeViewCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
void widgetTreeViewCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||||
int32_t minContentW = TREE_INDENT + TREE_EXPAND_SIZE + TREE_ICON_GAP + 6 * font->charWidth;
|
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;
|
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) {
|
void widgetTreeViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
if (!w || w->type != WidgetTreeViewE) {
|
VALIDATE_WIDGET_VOID(w, WidgetTreeViewE);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool multi = w->as.treeView.multiSelect;
|
bool multi = w->as.treeView.multiSelect;
|
||||||
bool shift = (mod & KEY_MOD_SHIFT) != 0;
|
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
|
// Check if click is on the vertical scrollbar
|
||||||
if (needVSb) {
|
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) {
|
if (vx >= sbX && vy < hit->y + TREE_BORDER + innerH) {
|
||||||
int32_t sbY = hit->y + TREE_BORDER;
|
int32_t relY = vy - (hit->y + TREE_BORDER);
|
||||||
int32_t sbH = innerH;
|
|
||||||
int32_t relY = vy - sbY;
|
|
||||||
int32_t trackLen = sbH - TREE_SB_W * 2;
|
|
||||||
int32_t pageSize = innerH - font->charHeight;
|
int32_t pageSize = innerH - font->charHeight;
|
||||||
|
|
||||||
if (pageSize < font->charHeight) {
|
if (pageSize < font->charHeight) {
|
||||||
pageSize = font->charHeight;
|
pageSize = font->charHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (relY < TREE_SB_W) {
|
ScrollHitE sh = widgetScrollbarHitTest(innerH, relY, totalH, innerH, hit->as.treeView.scrollPos);
|
||||||
// Up arrow — scroll up one row
|
|
||||||
|
if (sh == ScrollHitArrowDecE) {
|
||||||
hit->as.treeView.scrollPos -= font->charHeight;
|
hit->as.treeView.scrollPos -= font->charHeight;
|
||||||
} else if (relY >= sbH - TREE_SB_W) {
|
} else if (sh == ScrollHitArrowIncE) {
|
||||||
// Down arrow — scroll down one row
|
|
||||||
hit->as.treeView.scrollPos += font->charHeight;
|
hit->as.treeView.scrollPos += font->charHeight;
|
||||||
} else if (trackLen > 0) {
|
} else if (sh == ScrollHitPageDecE) {
|
||||||
// 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) {
|
|
||||||
hit->as.treeView.scrollPos -= pageSize;
|
hit->as.treeView.scrollPos -= pageSize;
|
||||||
} else if (trackRelY >= thumbPos + thumbSize) {
|
} else if (sh == ScrollHitPageIncE) {
|
||||||
hit->as.treeView.scrollPos += pageSize;
|
hit->as.treeView.scrollPos += pageSize;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Clamp after scroll
|
|
||||||
hit->as.treeView.scrollPos = clampInt(hit->as.treeView.scrollPos, 0, maxScrollV);
|
hit->as.treeView.scrollPos = clampInt(hit->as.treeView.scrollPos, 0, maxScrollV);
|
||||||
return;
|
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
|
// Check if click is on the horizontal scrollbar
|
||||||
if (needHSb) {
|
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) {
|
if (vy >= sbY && vx < hit->x + TREE_BORDER + innerW) {
|
||||||
int32_t sbX = hit->x + TREE_BORDER;
|
int32_t relX = vx - (hit->x + TREE_BORDER);
|
||||||
int32_t sbW = innerW;
|
|
||||||
int32_t relX = vx - sbX;
|
|
||||||
int32_t trackLen = sbW - TREE_SB_W * 2;
|
|
||||||
int32_t pageSize = innerW - font->charWidth;
|
int32_t pageSize = innerW - font->charWidth;
|
||||||
|
|
||||||
if (pageSize < font->charWidth) {
|
if (pageSize < font->charWidth) {
|
||||||
pageSize = font->charWidth;
|
pageSize = font->charWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (relX < TREE_SB_W) {
|
ScrollHitE sh = widgetScrollbarHitTest(innerW, relX, totalW, innerW, hit->as.treeView.scrollPosH);
|
||||||
// Left arrow — scroll left
|
|
||||||
|
if (sh == ScrollHitArrowDecE) {
|
||||||
hit->as.treeView.scrollPosH -= font->charWidth;
|
hit->as.treeView.scrollPosH -= font->charWidth;
|
||||||
} else if (relX >= sbW - TREE_SB_W) {
|
} else if (sh == ScrollHitArrowIncE) {
|
||||||
// Right arrow — scroll right
|
|
||||||
hit->as.treeView.scrollPosH += font->charWidth;
|
hit->as.treeView.scrollPosH += font->charWidth;
|
||||||
} else if (trackLen > 0) {
|
} else if (sh == ScrollHitPageDecE) {
|
||||||
// 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) {
|
|
||||||
hit->as.treeView.scrollPosH -= pageSize;
|
hit->as.treeView.scrollPosH -= pageSize;
|
||||||
} else if (trackRelX >= thumbPos + thumbSize) {
|
} else if (sh == ScrollHitPageIncE) {
|
||||||
hit->as.treeView.scrollPosH += pageSize;
|
hit->as.treeView.scrollPosH += pageSize;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Clamp after scroll
|
|
||||||
hit->as.treeView.scrollPosH = clampInt(hit->as.treeView.scrollPosH, 0, maxScrollH);
|
hit->as.treeView.scrollPosH = clampInt(hit->as.treeView.scrollPosH, 0, maxScrollH);
|
||||||
return;
|
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
|
// Click in dead corner (both scrollbars present) — ignore
|
||||||
if (needVSb && needHSb) {
|
if (needVSb && needHSb) {
|
||||||
int32_t cornerX = hit->x + hit->w - 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 - TREE_SB_W;
|
int32_t cornerY = hit->y + hit->h - TREE_BORDER - WGT_SB_W;
|
||||||
|
|
||||||
if (vx >= cornerX && vy >= cornerY) {
|
if (vx >= cornerX && vy >= cornerY) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -1313,11 +1142,20 @@ void widgetTreeViewPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
||||||
|
|
||||||
// Draw scrollbars
|
// Draw scrollbars
|
||||||
if (needVSb) {
|
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) {
|
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) {
|
if (w->focused) {
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@
|
||||||
#define CMD_VIEW_SIZE_SMALL 307
|
#define CMD_VIEW_SIZE_SMALL 307
|
||||||
#define CMD_VIEW_SIZE_MED 308
|
#define CMD_VIEW_SIZE_MED 308
|
||||||
#define CMD_VIEW_SIZE_LARGE 309
|
#define CMD_VIEW_SIZE_LARGE 309
|
||||||
|
#define CMD_VIEW_DEBUG_LYT 310
|
||||||
#define CMD_WIN_CASCADE 350
|
#define CMD_WIN_CASCADE 350
|
||||||
#define CMD_WIN_TILE_H 351
|
#define CMD_WIN_TILE_H 351
|
||||||
#define CMD_WIN_TILE_V 352
|
#define CMD_WIN_TILE_V 352
|
||||||
|
|
@ -209,6 +210,13 @@ static void onMenuCb(WindowT *win, int32_t menuId) {
|
||||||
setupControlsWindow(ctx);
|
setupControlsWindow(ctx);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case CMD_VIEW_DEBUG_LYT: {
|
||||||
|
static bool sDebugLyt = false;
|
||||||
|
sDebugLyt = !sDebugLyt;
|
||||||
|
wgtSetDebugLayout(ctx, sDebugLyt);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case CMD_WIN_CASCADE:
|
case CMD_WIN_CASCADE:
|
||||||
dvxCascadeWindows(ctx);
|
dvxCascadeWindows(ctx);
|
||||||
break;
|
break;
|
||||||
|
|
@ -623,21 +631,21 @@ static void setupControlsWindow(AppContextT *ctx) {
|
||||||
uint8_t *newData = loadBmpPixels(ctx, "new.bmp", &imgW, &imgH, &imgPitch);
|
uint8_t *newData = loadBmpPixels(ctx, "new.bmp", &imgW, &imgH, &imgPitch);
|
||||||
if (newData) {
|
if (newData) {
|
||||||
WidgetT *btnNew = wgtImageButton(tb, newData, imgW, imgH, imgPitch);
|
WidgetT *btnNew = wgtImageButton(tb, newData, imgW, imgH, imgPitch);
|
||||||
strncpy(btnNew->name, "New", MAX_WIDGET_NAME);
|
wgtSetName(btnNew, "New");
|
||||||
btnNew->onClick = onToolbarClick;
|
btnNew->onClick = onToolbarClick;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t *openData = loadBmpPixels(ctx, "open.bmp", &imgW, &imgH, &imgPitch);
|
uint8_t *openData = loadBmpPixels(ctx, "open.bmp", &imgW, &imgH, &imgPitch);
|
||||||
if (openData) {
|
if (openData) {
|
||||||
WidgetT *btnOpen = wgtImageButton(tb, openData, imgW, imgH, imgPitch);
|
WidgetT *btnOpen = wgtImageButton(tb, openData, imgW, imgH, imgPitch);
|
||||||
strncpy(btnOpen->name, "Open", MAX_WIDGET_NAME);
|
wgtSetName(btnOpen, "Open");
|
||||||
btnOpen->onClick = onToolbarClick;
|
btnOpen->onClick = onToolbarClick;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t *saveData = loadBmpPixels(ctx, "save.bmp", &imgW, &imgH, &imgPitch);
|
uint8_t *saveData = loadBmpPixels(ctx, "save.bmp", &imgW, &imgH, &imgPitch);
|
||||||
if (saveData) {
|
if (saveData) {
|
||||||
WidgetT *btnSave = wgtImageButton(tb, saveData, imgW, imgH, imgPitch);
|
WidgetT *btnSave = wgtImageButton(tb, saveData, imgW, imgH, imgPitch);
|
||||||
strncpy(btnSave->name, "Save", MAX_WIDGET_NAME);
|
wgtSetName(btnSave, "Save");
|
||||||
btnSave->onClick = onToolbarClick;
|
btnSave->onClick = onToolbarClick;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -814,7 +822,7 @@ static void setupControlsWindow(AppContextT *ctx) {
|
||||||
WidgetT *sb = wgtStatusBar(root);
|
WidgetT *sb = wgtStatusBar(root);
|
||||||
WidgetT *sbLabel = wgtLabel(sb, "Ready");
|
WidgetT *sbLabel = wgtLabel(sb, "Ready");
|
||||||
sbLabel->weight = 100;
|
sbLabel->weight = 100;
|
||||||
strncpy(sbLabel->name, "advStatus", MAX_WIDGET_NAME);
|
wgtSetName(sbLabel, "advStatus");
|
||||||
wgtLabel(sb, "Line 1, Col 1");
|
wgtLabel(sb, "Line 1, Col 1");
|
||||||
|
|
||||||
wgtInvalidate(root);
|
wgtInvalidate(root);
|
||||||
|
|
@ -878,6 +886,9 @@ static void setupMainWindow(AppContextT *ctx) {
|
||||||
wmAddMenuSeparator(zoomMenu);
|
wmAddMenuSeparator(zoomMenu);
|
||||||
wmAddMenuItem(zoomMenu, "&Fit to Window", CMD_VIEW_ZOOM_FIT);
|
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");
|
MenuT *winMenu = wmAddMenu(bar, "&Window");
|
||||||
|
|
@ -1052,7 +1063,7 @@ static void setupWidgetDemo(AppContextT *ctx) {
|
||||||
|
|
||||||
// Status label at top
|
// Status label at top
|
||||||
WidgetT *status = wgtLabel(root, "Ready.");
|
WidgetT *status = wgtLabel(root, "Ready.");
|
||||||
strncpy(status->name, "status", MAX_WIDGET_NAME);
|
wgtSetName(status, "status");
|
||||||
|
|
||||||
wgtHSeparator(root);
|
wgtHSeparator(root);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue