Code cleanup, some optimizations. Cut/Copy/Paste seems to be working as expected.
This commit is contained in:
parent
05bcfb4a4c
commit
54d9180d3e
17 changed files with 438 additions and 180 deletions
29
dvx/dvxApp.c
29
dvx/dvxApp.c
|
|
@ -321,7 +321,8 @@ static bool dispatchAccelKey(AppContextT *ctx, char key) {
|
||||||
if (target) {
|
if (target) {
|
||||||
switch (target->type) {
|
switch (target->type) {
|
||||||
case WidgetButtonE:
|
case WidgetButtonE:
|
||||||
widgetClearFocus(win->widgetRoot);
|
if (sFocusedWidget) { sFocusedWidget->focused = false; }
|
||||||
|
sFocusedWidget = target;
|
||||||
target->focused = true;
|
target->focused = true;
|
||||||
target->as.button.pressed = true;
|
target->as.button.pressed = true;
|
||||||
sKeyPressedBtn = target;
|
sKeyPressedBtn = target;
|
||||||
|
|
@ -329,19 +330,22 @@ static bool dispatchAccelKey(AppContextT *ctx, char key) {
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case WidgetCheckboxE:
|
case WidgetCheckboxE:
|
||||||
widgetClearFocus(win->widgetRoot);
|
if (sFocusedWidget) { sFocusedWidget->focused = false; }
|
||||||
widgetCheckboxOnMouse(target, win->widgetRoot, 0, 0);
|
widgetCheckboxOnMouse(target, win->widgetRoot, 0, 0);
|
||||||
|
sFocusedWidget = target;
|
||||||
wgtInvalidate(target);
|
wgtInvalidate(target);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case WidgetRadioE:
|
case WidgetRadioE:
|
||||||
widgetClearFocus(win->widgetRoot);
|
if (sFocusedWidget) { sFocusedWidget->focused = false; }
|
||||||
widgetRadioOnMouse(target, win->widgetRoot, 0, 0);
|
widgetRadioOnMouse(target, win->widgetRoot, 0, 0);
|
||||||
|
sFocusedWidget = target;
|
||||||
wgtInvalidate(target);
|
wgtInvalidate(target);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case WidgetImageButtonE:
|
case WidgetImageButtonE:
|
||||||
widgetClearFocus(win->widgetRoot);
|
if (sFocusedWidget) { sFocusedWidget->focused = false; }
|
||||||
|
sFocusedWidget = target;
|
||||||
target->focused = true;
|
target->focused = true;
|
||||||
target->as.imageButton.pressed = true;
|
target->as.imageButton.pressed = true;
|
||||||
sKeyPressedBtn = target;
|
sKeyPressedBtn = target;
|
||||||
|
|
@ -385,7 +389,8 @@ static bool dispatchAccelKey(AppContextT *ctx, char key) {
|
||||||
target->as.dropdown.open = true;
|
target->as.dropdown.open = true;
|
||||||
target->as.dropdown.hoverIdx = target->as.dropdown.selectedIdx;
|
target->as.dropdown.hoverIdx = target->as.dropdown.selectedIdx;
|
||||||
sOpenPopup = target;
|
sOpenPopup = target;
|
||||||
widgetClearFocus(win->widgetRoot);
|
if (sFocusedWidget) { sFocusedWidget->focused = false; }
|
||||||
|
sFocusedWidget = target;
|
||||||
target->focused = true;
|
target->focused = true;
|
||||||
wgtInvalidate(win->widgetRoot);
|
wgtInvalidate(win->widgetRoot);
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -394,7 +399,8 @@ static bool dispatchAccelKey(AppContextT *ctx, char key) {
|
||||||
target->as.comboBox.open = true;
|
target->as.comboBox.open = true;
|
||||||
target->as.comboBox.hoverIdx = target->as.comboBox.selectedIdx;
|
target->as.comboBox.hoverIdx = target->as.comboBox.selectedIdx;
|
||||||
sOpenPopup = target;
|
sOpenPopup = target;
|
||||||
widgetClearFocus(win->widgetRoot);
|
if (sFocusedWidget) { sFocusedWidget->focused = false; }
|
||||||
|
sFocusedWidget = target;
|
||||||
target->focused = true;
|
target->focused = true;
|
||||||
wgtInvalidate(win->widgetRoot);
|
wgtInvalidate(win->widgetRoot);
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -406,7 +412,8 @@ static bool dispatchAccelKey(AppContextT *ctx, char key) {
|
||||||
WidgetT *next = widgetFindNextFocusable(win->widgetRoot, target);
|
WidgetT *next = widgetFindNextFocusable(win->widgetRoot, target);
|
||||||
|
|
||||||
if (next) {
|
if (next) {
|
||||||
widgetClearFocus(win->widgetRoot);
|
if (sFocusedWidget) { sFocusedWidget->focused = false; }
|
||||||
|
sFocusedWidget = next;
|
||||||
next->focused = true;
|
next->focused = true;
|
||||||
|
|
||||||
// Open dropdown/combobox if that's the focused target
|
// Open dropdown/combobox if that's the focused target
|
||||||
|
|
@ -429,7 +436,8 @@ static bool dispatchAccelKey(AppContextT *ctx, char key) {
|
||||||
default:
|
default:
|
||||||
// For focusable widgets, just focus them
|
// For focusable widgets, just focus them
|
||||||
if (widgetIsFocusable(target->type)) {
|
if (widgetIsFocusable(target->type)) {
|
||||||
widgetClearFocus(win->widgetRoot);
|
if (sFocusedWidget) { sFocusedWidget->focused = false; }
|
||||||
|
sFocusedWidget = target;
|
||||||
target->focused = true;
|
target->focused = true;
|
||||||
wgtInvalidate(win->widgetRoot);
|
wgtInvalidate(win->widgetRoot);
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -1956,7 +1964,10 @@ static void pollKeyboard(AppContextT *ctx) {
|
||||||
|
|
||||||
if (next) {
|
if (next) {
|
||||||
sOpenPopup = NULL;
|
sOpenPopup = NULL;
|
||||||
widgetClearFocus(win->widgetRoot);
|
if (sFocusedWidget) {
|
||||||
|
sFocusedWidget->focused = false;
|
||||||
|
}
|
||||||
|
sFocusedWidget = next;
|
||||||
next->focused = true;
|
next->focused = true;
|
||||||
|
|
||||||
// Scroll the widget into view if needed
|
// Scroll the widget into view if needed
|
||||||
|
|
|
||||||
|
|
@ -138,20 +138,22 @@ void flushRect(DisplayT *d, const RectT *r) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Partial scanlines — copy row by row with rep movsd
|
// Partial scanlines — copy row by row with rep movsd
|
||||||
|
int32_t dwords = rowBytes >> 2;
|
||||||
|
int32_t remainder = rowBytes & 3;
|
||||||
for (int32_t i = 0; i < h; i++) {
|
for (int32_t i = 0; i < h; i++) {
|
||||||
int32_t dwords = rowBytes >> 2;
|
int32_t dc = dwords;
|
||||||
int32_t remainder = rowBytes & 3;
|
uint8_t *s = src;
|
||||||
uint8_t *s = src;
|
uint8_t *dd = dst;
|
||||||
uint8_t *dd = dst;
|
|
||||||
__asm__ __volatile__ (
|
__asm__ __volatile__ (
|
||||||
"rep movsl"
|
"rep movsl"
|
||||||
: "+D"(dd), "+S"(s), "+c"(dwords)
|
: "+D"(dd), "+S"(s), "+c"(dc)
|
||||||
:
|
:
|
||||||
: "memory"
|
: "memory"
|
||||||
);
|
);
|
||||||
// Trailing bytes (dd and s already advanced by rep movsl)
|
// Trailing bytes (dd and s already advanced by rep movsl)
|
||||||
if (__builtin_expect(remainder > 0, 0)) {
|
if (__builtin_expect(remainder > 0, 0)) {
|
||||||
while (remainder-- > 0) {
|
int32_t rem = remainder;
|
||||||
|
while (rem-- > 0) {
|
||||||
*dd++ = *s++;
|
*dd++ = *s++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,10 @@ static void spanFill8(uint8_t *dst, uint32_t color, int32_t count);
|
||||||
static void spanFill16(uint8_t *dst, uint32_t color, int32_t count);
|
static void spanFill16(uint8_t *dst, uint32_t color, int32_t count);
|
||||||
static void spanFill32(uint8_t *dst, uint32_t color, int32_t count);
|
static void spanFill32(uint8_t *dst, uint32_t color, int32_t count);
|
||||||
|
|
||||||
|
// Bit lookup tables — avoids per-pixel shift on 486 (40+ cycle savings per shift)
|
||||||
|
static const uint8_t sGlyphBit[8] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
|
||||||
|
static const uint16_t sMaskBit[16] = {0x8000, 0x4000, 0x2000, 0x1000, 0x0800, 0x0400, 0x0200, 0x0100, 0x0080, 0x0040, 0x0020, 0x0010, 0x0008, 0x0004, 0x0002, 0x0001};
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// accelParse
|
// accelParse
|
||||||
|
|
@ -204,20 +208,20 @@ int32_t drawChar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int3
|
||||||
if (bpp == 2) {
|
if (bpp == 2) {
|
||||||
uint16_t fg16 = (uint16_t)fg;
|
uint16_t fg16 = (uint16_t)fg;
|
||||||
for (int32_t col = colStart; col < colEnd; col++) {
|
for (int32_t col = colStart; col < colEnd; col++) {
|
||||||
if (bits & (0x80 >> col)) {
|
if (bits & sGlyphBit[col]) {
|
||||||
*(uint16_t *)(dst + col * 2) = fg16;
|
*(uint16_t *)(dst + col * 2) = fg16;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (bpp == 4) {
|
} else if (bpp == 4) {
|
||||||
for (int32_t col = colStart; col < colEnd; col++) {
|
for (int32_t col = colStart; col < colEnd; col++) {
|
||||||
if (bits & (0x80 >> col)) {
|
if (bits & sGlyphBit[col]) {
|
||||||
*(uint32_t *)(dst + col * 4) = fg;
|
*(uint32_t *)(dst + col * 4) = fg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
uint8_t fg8 = (uint8_t)fg;
|
uint8_t fg8 = (uint8_t)fg;
|
||||||
for (int32_t col = colStart; col < colEnd; col++) {
|
for (int32_t col = colStart; col < colEnd; col++) {
|
||||||
if (bits & (0x80 >> col)) {
|
if (bits & sGlyphBit[col]) {
|
||||||
dst[col] = fg8;
|
dst[col] = fg8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -237,20 +241,20 @@ int32_t drawChar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int3
|
||||||
if (bpp == 2) {
|
if (bpp == 2) {
|
||||||
uint16_t fg16 = (uint16_t)fg;
|
uint16_t fg16 = (uint16_t)fg;
|
||||||
for (int32_t col = colStart; col < colEnd; col++) {
|
for (int32_t col = colStart; col < colEnd; col++) {
|
||||||
if (bits & (0x80 >> col)) {
|
if (bits & sGlyphBit[col]) {
|
||||||
*(uint16_t *)(dst + col * 2) = fg16;
|
*(uint16_t *)(dst + col * 2) = fg16;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (bpp == 4) {
|
} else if (bpp == 4) {
|
||||||
for (int32_t col = colStart; col < colEnd; col++) {
|
for (int32_t col = colStart; col < colEnd; col++) {
|
||||||
if (bits & (0x80 >> col)) {
|
if (bits & sGlyphBit[col]) {
|
||||||
*(uint32_t *)(dst + col * 4) = fg;
|
*(uint32_t *)(dst + col * 4) = fg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
uint8_t fg8 = (uint8_t)fg;
|
uint8_t fg8 = (uint8_t)fg;
|
||||||
for (int32_t col = colStart; col < colEnd; col++) {
|
for (int32_t col = colStart; col < colEnd; col++) {
|
||||||
if (bits & (0x80 >> col)) {
|
if (bits & sGlyphBit[col]) {
|
||||||
dst[col] = fg8;
|
dst[col] = fg8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -386,17 +390,19 @@ void drawMaskedBitmap(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, in
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pre-compute column mask once (loop-invariant)
|
||||||
|
uint16_t colMask = 0;
|
||||||
|
for (int32_t col = colStart; col < colEnd; col++) {
|
||||||
|
colMask |= sMaskBit[col];
|
||||||
|
}
|
||||||
|
|
||||||
for (int32_t row = rowStart; row < rowEnd; row++) {
|
for (int32_t row = rowStart; row < rowEnd; row++) {
|
||||||
uint16_t mask = andMask[row];
|
uint16_t mask = andMask[row];
|
||||||
uint16_t data = xorData[row];
|
uint16_t data = xorData[row];
|
||||||
|
|
||||||
// Skip fully transparent rows
|
// Skip fully transparent rows
|
||||||
uint16_t colMask = 0;
|
|
||||||
for (int32_t col = colStart; col < colEnd; col++) {
|
|
||||||
colMask |= (0x8000 >> col);
|
|
||||||
}
|
|
||||||
if ((mask & colMask) == colMask) {
|
if ((mask & colMask) == colMask) {
|
||||||
continue; // all visible columns are transparent
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t py = y + row;
|
int32_t py = y + row;
|
||||||
|
|
@ -406,14 +412,14 @@ void drawMaskedBitmap(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, in
|
||||||
uint16_t fg16 = (uint16_t)fgColor;
|
uint16_t fg16 = (uint16_t)fgColor;
|
||||||
uint16_t bg16 = (uint16_t)bgColor;
|
uint16_t bg16 = (uint16_t)bgColor;
|
||||||
for (int32_t col = colStart; col < colEnd; col++) {
|
for (int32_t col = colStart; col < colEnd; col++) {
|
||||||
uint16_t bit = 0x8000 >> col;
|
uint16_t bit = sMaskBit[col];
|
||||||
if (!(mask & bit)) {
|
if (!(mask & bit)) {
|
||||||
*(uint16_t *)(dst + col * 2) = (data & bit) ? fg16 : bg16;
|
*(uint16_t *)(dst + col * 2) = (data & bit) ? fg16 : bg16;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (bpp == 4) {
|
} else if (bpp == 4) {
|
||||||
for (int32_t col = colStart; col < colEnd; col++) {
|
for (int32_t col = colStart; col < colEnd; col++) {
|
||||||
uint16_t bit = 0x8000 >> col;
|
uint16_t bit = sMaskBit[col];
|
||||||
if (!(mask & bit)) {
|
if (!(mask & bit)) {
|
||||||
*(uint32_t *)(dst + col * 4) = (data & bit) ? fgColor : bgColor;
|
*(uint32_t *)(dst + col * 4) = (data & bit) ? fgColor : bgColor;
|
||||||
}
|
}
|
||||||
|
|
@ -422,7 +428,7 @@ void drawMaskedBitmap(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, in
|
||||||
uint8_t fg8 = (uint8_t)fgColor;
|
uint8_t fg8 = (uint8_t)fgColor;
|
||||||
uint8_t bg8 = (uint8_t)bgColor;
|
uint8_t bg8 = (uint8_t)bgColor;
|
||||||
for (int32_t col = colStart; col < colEnd; col++) {
|
for (int32_t col = colStart; col < colEnd; col++) {
|
||||||
uint16_t bit = 0x8000 >> col;
|
uint16_t bit = sMaskBit[col];
|
||||||
if (!(mask & bit)) {
|
if (!(mask & bit)) {
|
||||||
dst[col] = (data & bit) ? fg8 : bg8;
|
dst[col] = (data & bit) ? fg8 : bg8;
|
||||||
}
|
}
|
||||||
|
|
@ -540,7 +546,7 @@ void drawTermRow(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int3
|
||||||
uint8_t bits = glyph ? glyph[row] : 0;
|
uint8_t bits = glyph ? glyph[row] : 0;
|
||||||
|
|
||||||
for (int32_t p = cStart; p < cEnd; p++) {
|
for (int32_t p = cStart; p < cEnd; p++) {
|
||||||
dst[p] = (bits & (0x80 >> p)) ? fg16 : bg16;
|
dst[p] = (bits & sGlyphBit[p]) ? fg16 : bg16;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (bpp == 4) {
|
} else if (bpp == 4) {
|
||||||
|
|
@ -549,7 +555,7 @@ void drawTermRow(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int3
|
||||||
uint8_t bits = glyph ? glyph[row] : 0;
|
uint8_t bits = glyph ? glyph[row] : 0;
|
||||||
|
|
||||||
for (int32_t p = cStart; p < cEnd; p++) {
|
for (int32_t p = cStart; p < cEnd; p++) {
|
||||||
dst[p] = (bits & (0x80 >> p)) ? fg : bg;
|
dst[p] = (bits & sGlyphBit[p]) ? fg : bg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -561,7 +567,7 @@ void drawTermRow(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int3
|
||||||
uint8_t bits = glyph ? glyph[row] : 0;
|
uint8_t bits = glyph ? glyph[row] : 0;
|
||||||
|
|
||||||
for (int32_t p = cStart; p < cEnd; p++) {
|
for (int32_t p = cStart; p < cEnd; p++) {
|
||||||
dst[p] = (bits & (0x80 >> p)) ? fg8 : bg8;
|
dst[p] = (bits & sGlyphBit[p]) ? fg8 : bg8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -215,6 +215,8 @@ typedef struct WidgetT {
|
||||||
char *undoBuf;
|
char *undoBuf;
|
||||||
int32_t undoLen;
|
int32_t undoLen;
|
||||||
int32_t undoCursor; // byte offset at time of snapshot
|
int32_t undoCursor; // byte offset at time of snapshot
|
||||||
|
int32_t cachedLines; // cached line count (-1 = dirty)
|
||||||
|
int32_t cachedMaxLL; // cached max line length (-1 = dirty)
|
||||||
} textArea;
|
} textArea;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
|
|
@ -222,6 +224,7 @@ typedef struct WidgetT {
|
||||||
int32_t itemCount;
|
int32_t itemCount;
|
||||||
int32_t selectedIdx;
|
int32_t selectedIdx;
|
||||||
int32_t scrollPos;
|
int32_t scrollPos;
|
||||||
|
int32_t maxItemLen; // cached max strlen of items
|
||||||
} listBox;
|
} listBox;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
|
|
@ -241,6 +244,7 @@ typedef struct WidgetT {
|
||||||
bool open;
|
bool open;
|
||||||
int32_t hoverIdx;
|
int32_t hoverIdx;
|
||||||
int32_t scrollPos;
|
int32_t scrollPos;
|
||||||
|
int32_t maxItemLen; // cached max strlen of items
|
||||||
} dropdown;
|
} dropdown;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
|
|
@ -260,6 +264,7 @@ typedef struct WidgetT {
|
||||||
bool open;
|
bool open;
|
||||||
int32_t hoverIdx;
|
int32_t hoverIdx;
|
||||||
int32_t listScrollPos;
|
int32_t listScrollPos;
|
||||||
|
int32_t maxItemLen; // cached max strlen of items
|
||||||
} comboBox;
|
} comboBox;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
|
|
@ -314,6 +319,7 @@ typedef struct WidgetT {
|
||||||
int32_t canvasW;
|
int32_t canvasW;
|
||||||
int32_t canvasH;
|
int32_t canvasH;
|
||||||
int32_t canvasPitch;
|
int32_t canvasPitch;
|
||||||
|
int32_t canvasBpp; // cached bytes per pixel (avoids pitch/w division)
|
||||||
uint32_t penColor;
|
uint32_t penColor;
|
||||||
int32_t penSize;
|
int32_t penSize;
|
||||||
int32_t lastX;
|
int32_t lastX;
|
||||||
|
|
|
||||||
10
dvx/dvxWm.c
10
dvx/dvxWm.c
|
|
@ -286,9 +286,17 @@ static void drawScaledRect(DisplayT *d, int32_t dstX, int32_t dstY, int32_t dstW
|
||||||
srcXTab[dx] = ((colStart + dx) * srcW) / dstW * bpp;
|
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;
|
||||||
|
|
||||||
|
for (int32_t dy = 0; dy < visibleRows; dy++) {
|
||||||
|
srcYTab[dy] = ((rowStart + dy) * srcH) / dstH;
|
||||||
|
}
|
||||||
|
|
||||||
// Blit with pre-computed lookups — no per-pixel divisions or clip checks
|
// Blit with pre-computed lookups — no per-pixel divisions or clip checks
|
||||||
for (int32_t dy = rowStart; dy < rowEnd; dy++) {
|
for (int32_t dy = rowStart; dy < rowEnd; dy++) {
|
||||||
int32_t sy = (dy * srcH) / dstH;
|
int32_t sy = srcYTab[dy - rowStart];
|
||||||
uint8_t *dstRow = d->backBuf + (dstY + dy) * d->pitch + (dstX + colStart) * bpp;
|
uint8_t *dstRow = d->backBuf + (dstY + dy) * d->pitch + (dstX + colStart) * bpp;
|
||||||
const uint8_t *srcRow = src + sy * srcPitch;
|
const uint8_t *srcRow = src + sy * srcPitch;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ static inline void canvasPutPixel(uint8_t *dst, uint32_t color, int32_t bpp) {
|
||||||
// Draw a filled circle of diameter penSize at (cx, cy) in canvas coords.
|
// Draw a filled circle of diameter penSize at (cx, cy) in canvas coords.
|
||||||
|
|
||||||
static void canvasDrawDot(WidgetT *w, int32_t cx, int32_t cy) {
|
static void canvasDrawDot(WidgetT *w, int32_t cx, int32_t cy) {
|
||||||
int32_t bpp = w->as.canvas.canvasPitch / w->as.canvas.canvasW;
|
int32_t bpp = w->as.canvas.canvasBpp;
|
||||||
int32_t pitch = w->as.canvas.canvasPitch;
|
int32_t pitch = w->as.canvas.canvasPitch;
|
||||||
uint8_t *data = w->as.canvas.data;
|
uint8_t *data = w->as.canvas.data;
|
||||||
int32_t cw = w->as.canvas.canvasW;
|
int32_t cw = w->as.canvas.canvasW;
|
||||||
|
|
@ -71,7 +71,7 @@ static void canvasDrawDot(WidgetT *w, int32_t cx, int32_t cy) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filled circle via bounding box + radius check
|
// Filled circle via per-row horizontal span
|
||||||
int32_t r2 = rad * rad;
|
int32_t r2 = rad * rad;
|
||||||
|
|
||||||
for (int32_t dy = -rad; dy <= rad; dy++) {
|
for (int32_t dy = -rad; dy <= rad; dy++) {
|
||||||
|
|
@ -81,17 +81,41 @@ static void canvasDrawDot(WidgetT *w, int32_t cx, int32_t cy) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int32_t dx = -rad; dx <= rad; dx++) {
|
// Compute horizontal half-span: dx² <= r² - dy²
|
||||||
int32_t px = cx + dx;
|
int32_t dy2 = dy * dy;
|
||||||
|
int32_t rem = r2 - dy2;
|
||||||
|
int32_t hspan = 0;
|
||||||
|
|
||||||
if (px < 0 || px >= cw) {
|
// Integer sqrt via Newton's method
|
||||||
continue;
|
if (rem > 0) {
|
||||||
|
hspan = rad;
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < 8; i++) {
|
||||||
|
hspan = (hspan + rem / hspan) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dx * dx + dy * dy <= r2) {
|
if (hspan * hspan > rem) {
|
||||||
uint8_t *dst = data + py * pitch + px * bpp;
|
hspan--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t x0 = cx - hspan;
|
||||||
|
int32_t x1 = cx + hspan;
|
||||||
|
|
||||||
|
if (x0 < 0) {
|
||||||
|
x0 = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x1 >= cw) {
|
||||||
|
x1 = cw - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x0 <= x1) {
|
||||||
|
uint8_t *dst = data + py * pitch + x0 * bpp;
|
||||||
|
|
||||||
|
for (int32_t px = x0; px <= x1; px++) {
|
||||||
canvasPutPixel(dst, color, bpp);
|
canvasPutPixel(dst, color, bpp);
|
||||||
|
dst += bpp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -196,15 +220,13 @@ WidgetT *wgtCanvas(WidgetT *parent, int32_t w, int32_t h) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill with white
|
// Fill with white using span fill for performance
|
||||||
uint32_t white = packColor(d, 255, 255, 255);
|
uint32_t white = packColor(d, 255, 255, 255);
|
||||||
|
BlitOpsT canvasOps;
|
||||||
|
drawInit(&canvasOps, d);
|
||||||
|
|
||||||
for (int32_t y = 0; y < h; y++) {
|
for (int32_t y = 0; y < h; y++) {
|
||||||
for (int32_t x = 0; x < w; x++) {
|
canvasOps.spanFill(data + y * pitch, white, w);
|
||||||
uint8_t *dst = data + y * pitch + x * bpp;
|
|
||||||
|
|
||||||
canvasPutPixel(dst, white, bpp);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WidgetT *wgt = widgetAlloc(parent, WidgetCanvasE);
|
WidgetT *wgt = widgetAlloc(parent, WidgetCanvasE);
|
||||||
|
|
@ -214,6 +236,7 @@ WidgetT *wgtCanvas(WidgetT *parent, int32_t w, int32_t h) {
|
||||||
wgt->as.canvas.canvasW = w;
|
wgt->as.canvas.canvasW = w;
|
||||||
wgt->as.canvas.canvasH = h;
|
wgt->as.canvas.canvasH = h;
|
||||||
wgt->as.canvas.canvasPitch = pitch;
|
wgt->as.canvas.canvasPitch = pitch;
|
||||||
|
wgt->as.canvas.canvasBpp = bpp;
|
||||||
wgt->as.canvas.penColor = packColor(d, 0, 0, 0);
|
wgt->as.canvas.penColor = packColor(d, 0, 0, 0);
|
||||||
wgt->as.canvas.penSize = 2;
|
wgt->as.canvas.penSize = 2;
|
||||||
wgt->as.canvas.lastX = -1;
|
wgt->as.canvas.lastX = -1;
|
||||||
|
|
@ -235,17 +258,22 @@ void wgtCanvasClear(WidgetT *w, uint32_t color) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t bpp = w->as.canvas.canvasPitch / w->as.canvas.canvasW;
|
// Find BlitOps for span fill
|
||||||
|
WidgetT *root = w;
|
||||||
|
while (root->parent) {
|
||||||
|
root = root->parent;
|
||||||
|
}
|
||||||
|
AppContextT *ctx = (AppContextT *)root->userData;
|
||||||
|
if (!ctx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int32_t pitch = w->as.canvas.canvasPitch;
|
int32_t pitch = w->as.canvas.canvasPitch;
|
||||||
int32_t cw = w->as.canvas.canvasW;
|
int32_t cw = w->as.canvas.canvasW;
|
||||||
int32_t ch = w->as.canvas.canvasH;
|
int32_t ch = w->as.canvas.canvasH;
|
||||||
|
|
||||||
for (int32_t y = 0; y < ch; y++) {
|
for (int32_t y = 0; y < ch; y++) {
|
||||||
for (int32_t x = 0; x < cw; x++) {
|
ctx->blitOps.spanFill(w->as.canvas.data + y * pitch, color, cw);
|
||||||
uint8_t *dst = w->as.canvas.data + y * pitch + x * bpp;
|
|
||||||
|
|
||||||
canvasPutPixel(dst, color, bpp);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -280,7 +308,7 @@ void wgtCanvasDrawRect(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t bpp = w->as.canvas.canvasPitch / w->as.canvas.canvasW;
|
int32_t bpp = w->as.canvas.canvasBpp;
|
||||||
int32_t pitch = w->as.canvas.canvasPitch;
|
int32_t pitch = w->as.canvas.canvasPitch;
|
||||||
uint8_t *data = w->as.canvas.data;
|
uint8_t *data = w->as.canvas.data;
|
||||||
int32_t cw = w->as.canvas.canvasW;
|
int32_t cw = w->as.canvas.canvasW;
|
||||||
|
|
@ -346,7 +374,7 @@ void wgtCanvasFillCircle(WidgetT *w, int32_t cx, int32_t cy, int32_t radius) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t bpp = w->as.canvas.canvasPitch / w->as.canvas.canvasW;
|
int32_t bpp = w->as.canvas.canvasBpp;
|
||||||
int32_t pitch = w->as.canvas.canvasPitch;
|
int32_t pitch = w->as.canvas.canvasPitch;
|
||||||
uint8_t *data = w->as.canvas.data;
|
uint8_t *data = w->as.canvas.data;
|
||||||
int32_t cw = w->as.canvas.canvasW;
|
int32_t cw = w->as.canvas.canvasW;
|
||||||
|
|
@ -361,17 +389,41 @@ void wgtCanvasFillCircle(WidgetT *w, int32_t cx, int32_t cy, int32_t radius) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int32_t dx = -radius; dx <= radius; dx++) {
|
// Compute horizontal half-span: dx² <= r² - dy²
|
||||||
int32_t px = cx + dx;
|
int32_t dy2 = dy * dy;
|
||||||
|
int32_t rem = r2 - dy2;
|
||||||
|
int32_t hspan = 0;
|
||||||
|
|
||||||
if (px < 0 || px >= cw) {
|
// Integer sqrt via Newton's method
|
||||||
continue;
|
if (rem > 0) {
|
||||||
|
hspan = radius;
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < 8; i++) {
|
||||||
|
hspan = (hspan + rem / hspan) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dx * dx + dy * dy <= r2) {
|
if (hspan * hspan > rem) {
|
||||||
uint8_t *dst = data + py * pitch + px * bpp;
|
hspan--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t x0 = cx - hspan;
|
||||||
|
int32_t x1 = cx + hspan;
|
||||||
|
|
||||||
|
if (x0 < 0) {
|
||||||
|
x0 = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x1 >= cw) {
|
||||||
|
x1 = cw - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x0 <= x1) {
|
||||||
|
uint8_t *dst = data + py * pitch + x0 * bpp;
|
||||||
|
|
||||||
|
for (int32_t px = x0; px <= x1; px++) {
|
||||||
canvasPutPixel(dst, color, bpp);
|
canvasPutPixel(dst, color, bpp);
|
||||||
|
dst += bpp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -393,7 +445,7 @@ void wgtCanvasFillRect(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t bpp = w->as.canvas.canvasPitch / w->as.canvas.canvasW;
|
int32_t bpp = w->as.canvas.canvasBpp;
|
||||||
int32_t pitch = w->as.canvas.canvasPitch;
|
int32_t pitch = w->as.canvas.canvasPitch;
|
||||||
uint8_t *data = w->as.canvas.data;
|
uint8_t *data = w->as.canvas.data;
|
||||||
int32_t cw = w->as.canvas.canvasW;
|
int32_t cw = w->as.canvas.canvasW;
|
||||||
|
|
@ -405,12 +457,28 @@ void wgtCanvasFillRect(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t
|
||||||
int32_t y0 = y < 0 ? 0 : y;
|
int32_t y0 = y < 0 ? 0 : y;
|
||||||
int32_t x1 = x + width > cw ? cw : x + width;
|
int32_t x1 = x + width > cw ? cw : x + width;
|
||||||
int32_t y1 = y + height > ch ? ch : y + height;
|
int32_t y1 = y + height > ch ? ch : y + height;
|
||||||
|
int32_t fillW = x1 - x0;
|
||||||
|
|
||||||
for (int32_t py = y0; py < y1; py++) {
|
if (fillW <= 0) {
|
||||||
for (int32_t px = x0; px < x1; px++) {
|
return;
|
||||||
uint8_t *dst = data + py * pitch + px * bpp;
|
}
|
||||||
|
|
||||||
canvasPutPixel(dst, color, bpp);
|
// Find BlitOps for span fill
|
||||||
|
WidgetT *root = w;
|
||||||
|
while (root->parent) {
|
||||||
|
root = root->parent;
|
||||||
|
}
|
||||||
|
AppContextT *ctx = (AppContextT *)root->userData;
|
||||||
|
|
||||||
|
if (ctx) {
|
||||||
|
for (int32_t py = y0; py < y1; py++) {
|
||||||
|
ctx->blitOps.spanFill(data + py * pitch + x0 * bpp, color, fillW);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int32_t py = y0; py < y1; py++) {
|
||||||
|
for (int32_t px = x0; px < x1; px++) {
|
||||||
|
canvasPutPixel(data + py * pitch + px * bpp, color, bpp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -429,7 +497,7 @@ uint32_t wgtCanvasGetPixel(const WidgetT *w, int32_t x, int32_t y) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t bpp = w->as.canvas.canvasPitch / w->as.canvas.canvasW;
|
int32_t bpp = w->as.canvas.canvasBpp;
|
||||||
const uint8_t *src = w->as.canvas.data + y * w->as.canvas.canvasPitch + x * bpp;
|
const uint8_t *src = w->as.canvas.data + y * w->as.canvas.canvasPitch + x * bpp;
|
||||||
|
|
||||||
return canvasGetPixel(src, bpp);
|
return canvasGetPixel(src, bpp);
|
||||||
|
|
@ -495,6 +563,7 @@ int32_t wgtCanvasLoad(WidgetT *w, const char *path) {
|
||||||
w->as.canvas.canvasW = imgW;
|
w->as.canvas.canvasW = imgW;
|
||||||
w->as.canvas.canvasH = imgH;
|
w->as.canvas.canvasH = imgH;
|
||||||
w->as.canvas.canvasPitch = pitch;
|
w->as.canvas.canvasPitch = pitch;
|
||||||
|
w->as.canvas.canvasBpp = bpp;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -587,7 +656,7 @@ void wgtCanvasSetPixel(WidgetT *w, int32_t x, int32_t y, uint32_t color) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t bpp = w->as.canvas.canvasPitch / w->as.canvas.canvasW;
|
int32_t bpp = w->as.canvas.canvasBpp;
|
||||||
uint8_t *dst = w->as.canvas.data + y * w->as.canvas.canvasPitch + x * bpp;
|
uint8_t *dst = w->as.canvas.data + y * w->as.canvas.canvasPitch + x * bpp;
|
||||||
|
|
||||||
canvasPutPixel(dst, color, bpp);
|
canvasPutPixel(dst, color, bpp);
|
||||||
|
|
|
||||||
|
|
@ -118,10 +118,10 @@ void widgetCheckboxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
||||||
// Draw label
|
// Draw label
|
||||||
int32_t labelX = w->x + CHECKBOX_BOX_SIZE + CHECKBOX_GAP;
|
int32_t labelX = w->x + CHECKBOX_BOX_SIZE + CHECKBOX_GAP;
|
||||||
int32_t labelY = w->y + (w->h - font->charHeight) / 2;
|
int32_t labelY = w->y + (w->h - font->charHeight) / 2;
|
||||||
|
int32_t labelW = textWidthAccel(font, w->as.checkbox.text);
|
||||||
drawTextAccel(d, ops, font, labelX, labelY, w->as.checkbox.text, fg, bg, false);
|
drawTextAccel(d, ops, font, labelX, labelY, w->as.checkbox.text, fg, bg, false);
|
||||||
|
|
||||||
if (w->focused) {
|
if (w->focused) {
|
||||||
int32_t labelW = textWidthAccel(font, w->as.checkbox.text);
|
|
||||||
drawFocusRect(d, ops, labelX - 1, labelY - 1, labelW + 2, font->charHeight + 2, fg);
|
drawFocusRect(d, ops, labelX - 1, labelY - 1, labelW + 2, font->charHeight + 2, fg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,18 @@ WidgetT *wgtComboBox(WidgetT *parent, int32_t maxLen) {
|
||||||
if (w) {
|
if (w) {
|
||||||
int32_t bufSize = maxLen > 0 ? maxLen + 1 : 256;
|
int32_t bufSize = maxLen > 0 ? maxLen + 1 : 256;
|
||||||
w->as.comboBox.buf = (char *)malloc(bufSize);
|
w->as.comboBox.buf = (char *)malloc(bufSize);
|
||||||
|
w->as.comboBox.undoBuf = (char *)malloc(bufSize);
|
||||||
w->as.comboBox.bufSize = bufSize;
|
w->as.comboBox.bufSize = bufSize;
|
||||||
|
|
||||||
if (w->as.comboBox.buf) {
|
if (!w->as.comboBox.buf || !w->as.comboBox.undoBuf) {
|
||||||
|
free(w->as.comboBox.buf);
|
||||||
|
free(w->as.comboBox.undoBuf);
|
||||||
|
w->as.comboBox.buf = NULL;
|
||||||
|
w->as.comboBox.undoBuf = NULL;
|
||||||
|
} else {
|
||||||
w->as.comboBox.buf[0] = '\0';
|
w->as.comboBox.buf[0] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
w->as.comboBox.undoBuf = (char *)malloc(bufSize);
|
|
||||||
w->as.comboBox.selStart = -1;
|
w->as.comboBox.selStart = -1;
|
||||||
w->as.comboBox.selEnd = -1;
|
w->as.comboBox.selEnd = -1;
|
||||||
w->as.comboBox.selectedIdx = -1;
|
w->as.comboBox.selectedIdx = -1;
|
||||||
|
|
@ -55,6 +60,19 @@ void wgtComboBoxSetItems(WidgetT *w, const char **items, int32_t count) {
|
||||||
w->as.comboBox.items = items;
|
w->as.comboBox.items = items;
|
||||||
w->as.comboBox.itemCount = count;
|
w->as.comboBox.itemCount = count;
|
||||||
|
|
||||||
|
// Cache max item strlen to avoid recomputing in calcMinSize
|
||||||
|
int32_t maxLen = 0;
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < count; i++) {
|
||||||
|
int32_t slen = (int32_t)strlen(items[i]);
|
||||||
|
|
||||||
|
if (slen > maxLen) {
|
||||||
|
maxLen = slen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w->as.comboBox.maxItemLen = maxLen;
|
||||||
|
|
||||||
if (w->as.comboBox.selectedIdx >= count) {
|
if (w->as.comboBox.selectedIdx >= count) {
|
||||||
w->as.comboBox.selectedIdx = -1;
|
w->as.comboBox.selectedIdx = -1;
|
||||||
}
|
}
|
||||||
|
|
@ -90,14 +108,11 @@ void wgtComboBoxSetSelected(WidgetT *w, int32_t idx) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void widgetComboBoxCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
void widgetComboBoxCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||||
int32_t maxItemW = font->charWidth * 8;
|
int32_t maxItemW = w->as.comboBox.maxItemLen * font->charWidth;
|
||||||
|
int32_t minW = font->charWidth * 8;
|
||||||
|
|
||||||
for (int32_t i = 0; i < w->as.comboBox.itemCount; i++) {
|
if (maxItemW < minW) {
|
||||||
int32_t iw = (int32_t)strlen(w->as.comboBox.items[i]) * font->charWidth;
|
maxItemW = minW;
|
||||||
|
|
||||||
if (iw > maxItemW) {
|
|
||||||
maxItemW = iw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
w->calcMinW = maxItemW + DROPDOWN_BTN_WIDTH + TEXT_INPUT_PAD * 2 + 4;
|
w->calcMinW = maxItemW + DROPDOWN_BTN_WIDTH + TEXT_INPUT_PAD * 2 + 4;
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,11 @@
|
||||||
// Global state for drag and popup tracking
|
// Global state for drag and popup tracking
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
bool sDebugLayout = false;
|
bool sDebugLayout = false;
|
||||||
WidgetT *sOpenPopup = NULL;
|
WidgetT *sFocusedWidget = NULL;
|
||||||
WidgetT *sPressedButton = NULL;
|
WidgetT *sOpenPopup = NULL;
|
||||||
WidgetT *sDragSlider = NULL;
|
WidgetT *sPressedButton = NULL;
|
||||||
|
WidgetT *sDragSlider = NULL;
|
||||||
WidgetT *sDrawingCanvas = NULL;
|
WidgetT *sDrawingCanvas = NULL;
|
||||||
WidgetT *sDragTextSelect = NULL;
|
WidgetT *sDragTextSelect = NULL;
|
||||||
int32_t sDragOffset = 0;
|
int32_t sDragOffset = 0;
|
||||||
|
|
@ -108,7 +109,11 @@ void widgetDestroyChildren(WidgetT *w) {
|
||||||
child->wclass->destroy(child);
|
child->wclass->destroy(child);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear popup/drag references if they point to destroyed widgets
|
// Clear static references if they point to destroyed widgets
|
||||||
|
if (sFocusedWidget == child) {
|
||||||
|
sFocusedWidget = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
if (sOpenPopup == child) {
|
if (sOpenPopup == child) {
|
||||||
sOpenPopup = NULL;
|
sOpenPopup = NULL;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,19 @@ void wgtDropdownSetItems(WidgetT *w, const char **items, int32_t count) {
|
||||||
w->as.dropdown.items = items;
|
w->as.dropdown.items = items;
|
||||||
w->as.dropdown.itemCount = count;
|
w->as.dropdown.itemCount = count;
|
||||||
|
|
||||||
|
// Cache max item strlen to avoid recomputing in calcMinSize
|
||||||
|
int32_t maxLen = 0;
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < count; i++) {
|
||||||
|
int32_t slen = (int32_t)strlen(items[i]);
|
||||||
|
|
||||||
|
if (slen > maxLen) {
|
||||||
|
maxLen = slen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w->as.dropdown.maxItemLen = maxLen;
|
||||||
|
|
||||||
if (w->as.dropdown.selectedIdx >= count) {
|
if (w->as.dropdown.selectedIdx >= count) {
|
||||||
w->as.dropdown.selectedIdx = -1;
|
w->as.dropdown.selectedIdx = -1;
|
||||||
}
|
}
|
||||||
|
|
@ -82,14 +95,11 @@ const char *widgetDropdownGetText(const WidgetT *w) {
|
||||||
|
|
||||||
void widgetDropdownCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
void widgetDropdownCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||||
// Width: widest item + button width + border
|
// Width: widest item + button width + border
|
||||||
int32_t maxItemW = font->charWidth * 8;
|
int32_t maxItemW = w->as.dropdown.maxItemLen * font->charWidth;
|
||||||
|
int32_t minW = font->charWidth * 8;
|
||||||
|
|
||||||
for (int32_t i = 0; i < w->as.dropdown.itemCount; i++) {
|
if (maxItemW < minW) {
|
||||||
int32_t iw = (int32_t)strlen(w->as.dropdown.items[i]) * font->charWidth;
|
maxItemW = minW;
|
||||||
|
|
||||||
if (iw > maxItemW) {
|
|
||||||
maxItemW = iw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
w->calcMinW = maxItemW + DROPDOWN_BTN_WIDTH + TEXT_INPUT_PAD * 2 + 4;
|
w->calcMinW = maxItemW + DROPDOWN_BTN_WIDTH + TEXT_INPUT_PAD * 2 + 4;
|
||||||
|
|
|
||||||
|
|
@ -114,29 +114,10 @@ void widgetOnKey(WindowT *win, int32_t key, int32_t mod) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the focused widget
|
// Use cached focus pointer — O(1) instead of O(n) tree walk
|
||||||
WidgetT *focus = NULL;
|
WidgetT *focus = sFocusedWidget;
|
||||||
|
|
||||||
WidgetT *stack[64];
|
if (!focus || !focus->focused || focus->window != win) {
|
||||||
int32_t top = 0;
|
|
||||||
stack[top++] = root;
|
|
||||||
|
|
||||||
while (top > 0) {
|
|
||||||
WidgetT *w = stack[--top];
|
|
||||||
|
|
||||||
if (w->focused && widgetIsFocusable(w->type)) {
|
|
||||||
focus = w;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
|
||||||
if (c->visible && top < 64) {
|
|
||||||
stack[top++] = c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!focus) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -183,7 +164,26 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
||||||
int32_t vx = x + scrollX;
|
int32_t vx = x + scrollX;
|
||||||
int32_t vy = y + scrollY;
|
int32_t vy = y + scrollY;
|
||||||
widgetTextDragUpdate(sDragTextSelect, root, vx, vy);
|
widgetTextDragUpdate(sDragTextSelect, root, vx, vy);
|
||||||
wgtInvalidate(root);
|
|
||||||
|
if (sDragTextSelect->type == WidgetAnsiTermE) {
|
||||||
|
// Fast path: repaint only dirty terminal rows into the
|
||||||
|
// content buffer, then dirty just that screen stripe.
|
||||||
|
int32_t dirtyY = 0;
|
||||||
|
int32_t dirtyH = 0;
|
||||||
|
|
||||||
|
if (wgtAnsiTermRepaint(sDragTextSelect, &dirtyY, &dirtyH) > 0) {
|
||||||
|
AppContextT *ctx = (AppContextT *)root->userData;
|
||||||
|
int32_t scrollY2 = win->vScroll ? win->vScroll->value : 0;
|
||||||
|
int32_t rectX = win->x + win->contentX;
|
||||||
|
int32_t rectY = win->y + win->contentY + dirtyY - scrollY2;
|
||||||
|
int32_t rectW = win->contentW;
|
||||||
|
win->contentDirty = true;
|
||||||
|
dirtyListAdd(&ctx->dirty, rectX, rectY, rectW, dirtyH);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
wgtInvalidate(root);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -396,20 +396,10 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear focus from all widgets, set focus on clicked widget
|
// Clear focus from previously focused widget (O(1) instead of tree walk)
|
||||||
WidgetT *fstack[64];
|
if (sFocusedWidget) {
|
||||||
int32_t ftop = 0;
|
sFocusedWidget->focused = false;
|
||||||
fstack[ftop++] = root;
|
sFocusedWidget = NULL;
|
||||||
|
|
||||||
while (ftop > 0) {
|
|
||||||
WidgetT *w = fstack[--ftop];
|
|
||||||
w->focused = false;
|
|
||||||
|
|
||||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
|
||||||
if (ftop < 64) {
|
|
||||||
fstack[ftop++] = c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dispatch to per-widget mouse handler via vtable
|
// Dispatch to per-widget mouse handler via vtable
|
||||||
|
|
@ -417,6 +407,11 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
||||||
hit->wclass->onMouse(hit, root, vx, vy);
|
hit->wclass->onMouse(hit, root, vx, vy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cache the newly focused widget
|
||||||
|
if (hit->focused) {
|
||||||
|
sFocusedWidget = hit;
|
||||||
|
}
|
||||||
|
|
||||||
wgtInvalidate(root);
|
wgtInvalidate(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -454,17 +449,20 @@ void widgetOnPaint(WindowT *win, RectT *dirtyArea) {
|
||||||
// Clear background
|
// Clear background
|
||||||
rectFill(&cd, &ctx->blitOps, 0, 0, win->contentW, win->contentH, ctx->colors.contentBg);
|
rectFill(&cd, &ctx->blitOps, 0, 0, win->contentW, win->contentH, ctx->colors.contentBg);
|
||||||
|
|
||||||
// Apply scroll offset — layout at virtual size, positioned at -scroll
|
// Apply scroll offset and re-layout at virtual size
|
||||||
int32_t scrollX = win->hScroll ? win->hScroll->value : 0;
|
int32_t scrollX = win->hScroll ? win->hScroll->value : 0;
|
||||||
int32_t scrollY = win->vScroll ? win->vScroll->value : 0;
|
int32_t scrollY = win->vScroll ? win->vScroll->value : 0;
|
||||||
int32_t layoutW = DVX_MAX(win->contentW, root->calcMinW);
|
int32_t layoutW = DVX_MAX(win->contentW, root->calcMinW);
|
||||||
int32_t layoutH = DVX_MAX(win->contentH, root->calcMinH);
|
int32_t layoutH = DVX_MAX(win->contentH, root->calcMinH);
|
||||||
|
|
||||||
root->x = -scrollX;
|
// Only re-layout if root position or size actually changed
|
||||||
root->y = -scrollY;
|
if (root->x != -scrollX || root->y != -scrollY || root->w != layoutW || root->h != layoutH) {
|
||||||
root->w = layoutW;
|
root->x = -scrollX;
|
||||||
root->h = layoutH;
|
root->y = -scrollY;
|
||||||
widgetLayoutChildren(root, &ctx->font);
|
root->w = layoutW;
|
||||||
|
root->h = layoutH;
|
||||||
|
widgetLayoutChildren(root, &ctx->font);
|
||||||
|
}
|
||||||
|
|
||||||
// Paint widget tree (clip rect limits drawing to visible area)
|
// Paint widget tree (clip rect limits drawing to visible area)
|
||||||
wgtPaint(root, &cd, &ctx->blitOps, &ctx->font, &ctx->colors);
|
wgtPaint(root, &cd, &ctx->blitOps, &ctx->font, &ctx->colors);
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,7 @@ static inline int32_t clampInt(int32_t val, int32_t lo, int32_t hi) {
|
||||||
|
|
||||||
extern bool sDebugLayout;
|
extern bool sDebugLayout;
|
||||||
extern WidgetT *sClosedPopup;
|
extern WidgetT *sClosedPopup;
|
||||||
|
extern WidgetT *sFocusedWidget;
|
||||||
extern WidgetT *sKeyPressedBtn;
|
extern WidgetT *sKeyPressedBtn;
|
||||||
extern WidgetT *sOpenPopup;
|
extern WidgetT *sOpenPopup;
|
||||||
extern WidgetT *sPressedButton;
|
extern WidgetT *sPressedButton;
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,19 @@ void wgtListBoxSetItems(WidgetT *w, const char **items, int32_t count) {
|
||||||
w->as.listBox.items = items;
|
w->as.listBox.items = items;
|
||||||
w->as.listBox.itemCount = count;
|
w->as.listBox.itemCount = count;
|
||||||
|
|
||||||
|
// Cache max item strlen to avoid recomputing in calcMinSize
|
||||||
|
int32_t maxLen = 0;
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < count; i++) {
|
||||||
|
int32_t slen = (int32_t)strlen(items[i]);
|
||||||
|
|
||||||
|
if (slen > maxLen) {
|
||||||
|
maxLen = slen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w->as.listBox.maxItemLen = maxLen;
|
||||||
|
|
||||||
if (w->as.listBox.selectedIdx >= count) {
|
if (w->as.listBox.selectedIdx >= count) {
|
||||||
w->as.listBox.selectedIdx = count > 0 ? 0 : -1;
|
w->as.listBox.selectedIdx = count > 0 ? 0 : -1;
|
||||||
}
|
}
|
||||||
|
|
@ -76,14 +89,11 @@ void wgtListBoxSetSelected(WidgetT *w, int32_t idx) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void widgetListBoxCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
void widgetListBoxCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||||
int32_t maxItemW = font->charWidth * 8;
|
int32_t maxItemW = w->as.listBox.maxItemLen * font->charWidth;
|
||||||
|
int32_t minW = font->charWidth * 8;
|
||||||
|
|
||||||
for (int32_t i = 0; i < w->as.listBox.itemCount; i++) {
|
if (maxItemW < minW) {
|
||||||
int32_t iw = (int32_t)strlen(w->as.listBox.items[i]) * font->charWidth;
|
maxItemW = minW;
|
||||||
|
|
||||||
if (iw > maxItemW) {
|
|
||||||
maxItemW = iw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
w->calcMinW = maxItemW + LISTBOX_PAD * 2 + LISTBOX_BORDER * 2 + LISTBOX_SB_W;
|
w->calcMinW = maxItemW + LISTBOX_PAD * 2 + LISTBOX_BORDER * 2 + LISTBOX_SB_W;
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,10 @@ void wgtDestroy(WidgetT *w) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear static references
|
// Clear static references
|
||||||
|
if (sFocusedWidget == w) {
|
||||||
|
sFocusedWidget = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
if (sOpenPopup == w) {
|
if (sOpenPopup == w) {
|
||||||
sOpenPopup = NULL;
|
sOpenPopup = NULL;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,7 @@ void widgetRadioOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
if (next) {
|
if (next) {
|
||||||
w->focused = false;
|
w->focused = false;
|
||||||
next->focused = true;
|
next->focused = true;
|
||||||
|
sFocusedWidget = next;
|
||||||
next->parent->as.radioGroup.selectedIdx = next->as.radio.index;
|
next->parent->as.radioGroup.selectedIdx = next->as.radio.index;
|
||||||
|
|
||||||
if (next->parent->onChange) {
|
if (next->parent->onChange) {
|
||||||
|
|
@ -131,6 +132,7 @@ void widgetRadioOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
if (prev) {
|
if (prev) {
|
||||||
w->focused = false;
|
w->focused = false;
|
||||||
prev->focused = true;
|
prev->focused = true;
|
||||||
|
sFocusedWidget = prev;
|
||||||
prev->parent->as.radioGroup.selectedIdx = prev->as.radio.index;
|
prev->parent->as.radioGroup.selectedIdx = prev->as.radio.index;
|
||||||
|
|
||||||
if (prev->parent->onChange) {
|
if (prev->parent->onChange) {
|
||||||
|
|
@ -190,10 +192,10 @@ void widgetRadioPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitmap
|
||||||
|
|
||||||
int32_t labelX = w->x + CHECKBOX_BOX_SIZE + CHECKBOX_GAP;
|
int32_t labelX = w->x + CHECKBOX_BOX_SIZE + CHECKBOX_GAP;
|
||||||
int32_t labelY = w->y + (w->h - font->charHeight) / 2;
|
int32_t labelY = w->y + (w->h - font->charHeight) / 2;
|
||||||
|
int32_t labelW = textWidthAccel(font, w->as.radio.text);
|
||||||
drawTextAccel(d, ops, font, labelX, labelY, w->as.radio.text, fg, bg, false);
|
drawTextAccel(d, ops, font, labelX, labelY, w->as.radio.text, fg, bg, false);
|
||||||
|
|
||||||
if (w->focused) {
|
if (w->focused) {
|
||||||
int32_t labelW = textWidthAccel(font, w->as.radio.text);
|
|
||||||
drawFocusRect(d, ops, labelX - 1, labelY - 1, labelW + 2, font->charHeight + 2, fg);
|
drawFocusRect(d, ops, labelX - 1, labelY - 1, labelW + 2, font->charHeight + 2, fg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,10 @@ static int32_t maskPrevSlot(const char *mask, int32_t pos);
|
||||||
static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod);
|
static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||||
static int32_t textAreaCountLines(const char *buf, int32_t len);
|
static int32_t textAreaCountLines(const char *buf, int32_t len);
|
||||||
static int32_t textAreaCursorToOff(const char *buf, int32_t len, int32_t row, int32_t col);
|
static int32_t textAreaCursorToOff(const char *buf, int32_t len, int32_t row, int32_t col);
|
||||||
|
static inline void textAreaDirtyCache(WidgetT *w);
|
||||||
static void textAreaEnsureVisible(WidgetT *w, int32_t visRows, int32_t visCols);
|
static void textAreaEnsureVisible(WidgetT *w, int32_t visRows, int32_t visCols);
|
||||||
|
static int32_t textAreaGetLineCount(WidgetT *w);
|
||||||
|
static int32_t textAreaGetMaxLineLen(WidgetT *w);
|
||||||
static int32_t textAreaLineLen(const char *buf, int32_t len, int32_t row);
|
static int32_t textAreaLineLen(const char *buf, int32_t len, int32_t row);
|
||||||
static int32_t textAreaLineStart(const char *buf, int32_t len, int32_t row);
|
static int32_t textAreaLineStart(const char *buf, int32_t len, int32_t row);
|
||||||
static int32_t textAreaMaxLineLen(const char *buf, int32_t len);
|
static int32_t textAreaMaxLineLen(const char *buf, int32_t len);
|
||||||
|
|
@ -42,6 +45,10 @@ static int32_t sClipboardLen = 0;
|
||||||
|
|
||||||
|
|
||||||
void clipboardCopy(const char *text, int32_t len) {
|
void clipboardCopy(const char *text, int32_t len) {
|
||||||
|
if (!text || len <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (len > CLIPBOARD_MAX - 1) {
|
if (len > CLIPBOARD_MAX - 1) {
|
||||||
len = CLIPBOARD_MAX - 1;
|
len = CLIPBOARD_MAX - 1;
|
||||||
}
|
}
|
||||||
|
|
@ -260,16 +267,23 @@ WidgetT *wgtTextArea(WidgetT *parent, int32_t maxLen) {
|
||||||
if (w) {
|
if (w) {
|
||||||
int32_t bufSize = maxLen > 0 ? maxLen + 1 : 256;
|
int32_t bufSize = maxLen > 0 ? maxLen + 1 : 256;
|
||||||
w->as.textArea.buf = (char *)malloc(bufSize);
|
w->as.textArea.buf = (char *)malloc(bufSize);
|
||||||
|
w->as.textArea.undoBuf = (char *)malloc(bufSize);
|
||||||
w->as.textArea.bufSize = bufSize;
|
w->as.textArea.bufSize = bufSize;
|
||||||
|
|
||||||
if (w->as.textArea.buf) {
|
if (!w->as.textArea.buf || !w->as.textArea.undoBuf) {
|
||||||
|
free(w->as.textArea.buf);
|
||||||
|
free(w->as.textArea.undoBuf);
|
||||||
|
w->as.textArea.buf = NULL;
|
||||||
|
w->as.textArea.undoBuf = NULL;
|
||||||
|
} else {
|
||||||
w->as.textArea.buf[0] = '\0';
|
w->as.textArea.buf[0] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
w->as.textArea.undoBuf = (char *)malloc(bufSize);
|
w->as.textArea.selAnchor = -1;
|
||||||
w->as.textArea.selAnchor = -1;
|
w->as.textArea.selCursor = -1;
|
||||||
w->as.textArea.selCursor = -1;
|
w->as.textArea.desiredCol = 0;
|
||||||
w->as.textArea.desiredCol = 0;
|
w->as.textArea.cachedLines = -1;
|
||||||
|
w->as.textArea.cachedMaxLL = -1;
|
||||||
w->weight = 100;
|
w->weight = 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -419,13 +433,21 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
|
|
||||||
// Ctrl+C — copy formatted text
|
// Ctrl+C — copy formatted text
|
||||||
if (key == 3) {
|
if (key == 3) {
|
||||||
int32_t selLo = -1;
|
|
||||||
int32_t selHi = -1;
|
|
||||||
|
|
||||||
if (w->as.textInput.selStart >= 0 && w->as.textInput.selEnd >= 0 && w->as.textInput.selStart != w->as.textInput.selEnd) {
|
if (w->as.textInput.selStart >= 0 && w->as.textInput.selEnd >= 0 && w->as.textInput.selStart != w->as.textInput.selEnd) {
|
||||||
selLo = w->as.textInput.selStart < w->as.textInput.selEnd ? w->as.textInput.selStart : w->as.textInput.selEnd;
|
int32_t selLo = w->as.textInput.selStart < w->as.textInput.selEnd ? w->as.textInput.selStart : w->as.textInput.selEnd;
|
||||||
selHi = w->as.textInput.selStart < w->as.textInput.selEnd ? w->as.textInput.selEnd : w->as.textInput.selStart;
|
int32_t selHi = w->as.textInput.selStart < w->as.textInput.selEnd ? w->as.textInput.selEnd : w->as.textInput.selStart;
|
||||||
clipboardCopy(buf + selLo, selHi - selLo);
|
|
||||||
|
if (selLo < 0) {
|
||||||
|
selLo = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selHi > maskLen) {
|
||||||
|
selHi = maskLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selHi > selLo) {
|
||||||
|
clipboardCopy(buf + selLo, selHi - selLo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
@ -463,7 +485,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
*pCur = slotPos;
|
*pCur = slotPos <= maskLen ? slotPos : maskLen;
|
||||||
w->as.textInput.selStart = -1;
|
w->as.textInput.selStart = -1;
|
||||||
w->as.textInput.selEnd = -1;
|
w->as.textInput.selEnd = -1;
|
||||||
|
|
||||||
|
|
@ -478,12 +500,18 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
|
|
||||||
// Ctrl+X — copy and clear selected slots
|
// Ctrl+X — copy and clear selected slots
|
||||||
if (key == 24) {
|
if (key == 24) {
|
||||||
int32_t selLo = -1;
|
|
||||||
int32_t selHi = -1;
|
|
||||||
|
|
||||||
if (w->as.textInput.selStart >= 0 && w->as.textInput.selEnd >= 0 && w->as.textInput.selStart != w->as.textInput.selEnd) {
|
if (w->as.textInput.selStart >= 0 && w->as.textInput.selEnd >= 0 && w->as.textInput.selStart != w->as.textInput.selEnd) {
|
||||||
selLo = w->as.textInput.selStart < w->as.textInput.selEnd ? w->as.textInput.selStart : w->as.textInput.selEnd;
|
int32_t selLo = w->as.textInput.selStart < w->as.textInput.selEnd ? w->as.textInput.selStart : w->as.textInput.selEnd;
|
||||||
selHi = w->as.textInput.selStart < w->as.textInput.selEnd ? w->as.textInput.selEnd : w->as.textInput.selStart;
|
int32_t selHi = w->as.textInput.selStart < w->as.textInput.selEnd ? w->as.textInput.selEnd : w->as.textInput.selStart;
|
||||||
|
|
||||||
|
if (selLo < 0) {
|
||||||
|
selLo = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selHi > maskLen) {
|
||||||
|
selHi = maskLen;
|
||||||
|
}
|
||||||
|
|
||||||
clipboardCopy(buf + selLo, selHi - selLo);
|
clipboardCopy(buf + selLo, selHi - selLo);
|
||||||
|
|
||||||
if (w->as.textInput.undoBuf) {
|
if (w->as.textInput.undoBuf) {
|
||||||
|
|
@ -510,14 +538,15 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
|
|
||||||
// Ctrl+Z — undo
|
// Ctrl+Z — undo
|
||||||
if (key == 26 && w->as.textInput.undoBuf) {
|
if (key == 26 && w->as.textInput.undoBuf) {
|
||||||
char tmpBuf[CLIPBOARD_MAX];
|
char tmpBuf[256];
|
||||||
|
int32_t tmpLen = maskLen + 1 < (int32_t)sizeof(tmpBuf) ? maskLen + 1 : (int32_t)sizeof(tmpBuf);
|
||||||
int32_t tmpCursor = *pCur;
|
int32_t tmpCursor = *pCur;
|
||||||
memcpy(tmpBuf, buf, maskLen + 1);
|
memcpy(tmpBuf, buf, tmpLen);
|
||||||
|
|
||||||
memcpy(buf, w->as.textInput.undoBuf, maskLen + 1);
|
memcpy(buf, w->as.textInput.undoBuf, maskLen + 1);
|
||||||
*pCur = w->as.textInput.undoCursor < maskLen ? w->as.textInput.undoCursor : maskLen;
|
*pCur = w->as.textInput.undoCursor < maskLen ? w->as.textInput.undoCursor : maskLen;
|
||||||
|
|
||||||
memcpy(w->as.textInput.undoBuf, tmpBuf, maskLen + 1);
|
memcpy(w->as.textInput.undoBuf, tmpBuf, tmpLen);
|
||||||
w->as.textInput.undoCursor = tmpCursor;
|
w->as.textInput.undoCursor = tmpCursor;
|
||||||
|
|
||||||
w->as.textInput.selStart = -1;
|
w->as.textInput.selStart = -1;
|
||||||
|
|
@ -689,6 +718,15 @@ static int32_t textAreaCountLines(const char *buf, int32_t len) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int32_t textAreaGetLineCount(WidgetT *w) {
|
||||||
|
if (w->as.textArea.cachedLines < 0) {
|
||||||
|
w->as.textArea.cachedLines = textAreaCountLines(w->as.textArea.buf, w->as.textArea.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
return w->as.textArea.cachedLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static int32_t textAreaLineStart(const char *buf, int32_t len, int32_t row) {
|
static int32_t textAreaLineStart(const char *buf, int32_t len, int32_t row) {
|
||||||
(void)len;
|
(void)len;
|
||||||
int32_t off = 0;
|
int32_t off = 0;
|
||||||
|
|
@ -742,6 +780,21 @@ static int32_t textAreaMaxLineLen(const char *buf, int32_t len) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int32_t textAreaGetMaxLineLen(WidgetT *w) {
|
||||||
|
if (w->as.textArea.cachedMaxLL < 0) {
|
||||||
|
w->as.textArea.cachedMaxLL = textAreaMaxLineLen(w->as.textArea.buf, w->as.textArea.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
return w->as.textArea.cachedMaxLL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static inline void textAreaDirtyCache(WidgetT *w) {
|
||||||
|
w->as.textArea.cachedLines = -1;
|
||||||
|
w->as.textArea.cachedMaxLL = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static int32_t textAreaCursorToOff(const char *buf, int32_t len, int32_t row, int32_t col) {
|
static int32_t textAreaCursorToOff(const char *buf, int32_t len, int32_t row, int32_t col) {
|
||||||
int32_t start = textAreaLineStart(buf, len, row);
|
int32_t start = textAreaLineStart(buf, len, row);
|
||||||
int32_t lineL = textAreaLineLen(buf, len, row);
|
int32_t lineL = textAreaLineLen(buf, len, row);
|
||||||
|
|
@ -844,7 +897,7 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
const BitmapFontT *font = &ctx->font;
|
const BitmapFontT *font = &ctx->font;
|
||||||
int32_t innerW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_PAD * 2 - TEXTAREA_SB_W;
|
int32_t innerW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_PAD * 2 - TEXTAREA_SB_W;
|
||||||
int32_t visCols = innerW / font->charWidth;
|
int32_t visCols = innerW / font->charWidth;
|
||||||
int32_t maxLL = textAreaMaxLineLen(buf, *pLen);
|
int32_t maxLL = textAreaGetMaxLineLen(w);
|
||||||
bool needHSb = (maxLL > visCols);
|
bool needHSb = (maxLL > visCols);
|
||||||
int32_t innerH = w->h - TEXTAREA_BORDER * 2 - (needHSb ? TEXTAREA_SB_W : 0);
|
int32_t innerH = w->h - TEXTAREA_BORDER * 2 - (needHSb ? TEXTAREA_SB_W : 0);
|
||||||
int32_t visRows = innerH / font->charHeight;
|
int32_t visRows = innerH / font->charHeight;
|
||||||
|
|
@ -857,7 +910,7 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
visCols = 1;
|
visCols = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t totalLines = textAreaCountLines(buf, *pLen);
|
int32_t totalLines = textAreaGetLineCount(w);
|
||||||
|
|
||||||
// Helper macros for cursor offset
|
// Helper macros for cursor offset
|
||||||
#define CUR_OFF() textAreaCursorToOff(buf, *pLen, *pRow, *pCol)
|
#define CUR_OFF() textAreaCursorToOff(buf, *pLen, *pRow, *pCol)
|
||||||
|
|
@ -877,6 +930,17 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
#define SEL_LO() (*pSA < *pSC ? *pSA : *pSC)
|
#define SEL_LO() (*pSA < *pSC ? *pSA : *pSC)
|
||||||
#define SEL_HI() (*pSA < *pSC ? *pSC : *pSA)
|
#define SEL_HI() (*pSA < *pSC ? *pSC : *pSA)
|
||||||
|
|
||||||
|
// Clamp selection to buffer bounds
|
||||||
|
if (HAS_SEL()) {
|
||||||
|
if (*pSA > *pLen) {
|
||||||
|
*pSA = *pLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*pSC > *pLen) {
|
||||||
|
*pSC = *pLen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Ctrl+A — select all
|
// Ctrl+A — select all
|
||||||
if (key == 1) {
|
if (key == 1) {
|
||||||
*pSA = 0;
|
*pSA = 0;
|
||||||
|
|
@ -927,6 +991,8 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
if (w->onChange) {
|
if (w->onChange) {
|
||||||
w->onChange(w);
|
w->onChange(w);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textAreaDirtyCache(w);
|
||||||
}
|
}
|
||||||
|
|
||||||
textAreaEnsureVisible(w, visRows, visCols);
|
textAreaEnsureVisible(w, visRows, visCols);
|
||||||
|
|
@ -952,6 +1018,8 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
if (w->onChange) {
|
if (w->onChange) {
|
||||||
w->onChange(w);
|
w->onChange(w);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textAreaDirtyCache(w);
|
||||||
}
|
}
|
||||||
|
|
||||||
textAreaEnsureVisible(w, visRows, visCols);
|
textAreaEnsureVisible(w, visRows, visCols);
|
||||||
|
|
@ -994,6 +1062,8 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
if (w->onChange) {
|
if (w->onChange) {
|
||||||
w->onChange(w);
|
w->onChange(w);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textAreaDirtyCache(w);
|
||||||
}
|
}
|
||||||
|
|
||||||
textAreaEnsureVisible(w, visRows, visCols);
|
textAreaEnsureVisible(w, visRows, visCols);
|
||||||
|
|
@ -1030,6 +1100,8 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
if (w->onChange) {
|
if (w->onChange) {
|
||||||
w->onChange(w);
|
w->onChange(w);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textAreaDirtyCache(w);
|
||||||
}
|
}
|
||||||
|
|
||||||
textAreaEnsureVisible(w, visRows, visCols);
|
textAreaEnsureVisible(w, visRows, visCols);
|
||||||
|
|
@ -1049,6 +1121,7 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
*pSA = -1;
|
*pSA = -1;
|
||||||
*pSC = -1;
|
*pSC = -1;
|
||||||
w->as.textArea.desiredCol = *pCol;
|
w->as.textArea.desiredCol = *pCol;
|
||||||
|
textAreaDirtyCache(w);
|
||||||
|
|
||||||
if (w->onChange) {
|
if (w->onChange) {
|
||||||
w->onChange(w);
|
w->onChange(w);
|
||||||
|
|
@ -1062,6 +1135,7 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
(*pLen)--;
|
(*pLen)--;
|
||||||
textAreaOffToRowCol(buf, off - 1, pRow, pCol);
|
textAreaOffToRowCol(buf, off - 1, pRow, pCol);
|
||||||
w->as.textArea.desiredCol = *pCol;
|
w->as.textArea.desiredCol = *pCol;
|
||||||
|
textAreaDirtyCache(w);
|
||||||
|
|
||||||
if (w->onChange) {
|
if (w->onChange) {
|
||||||
w->onChange(w);
|
w->onChange(w);
|
||||||
|
|
@ -1086,6 +1160,7 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
*pSA = -1;
|
*pSA = -1;
|
||||||
*pSC = -1;
|
*pSC = -1;
|
||||||
w->as.textArea.desiredCol = *pCol;
|
w->as.textArea.desiredCol = *pCol;
|
||||||
|
textAreaDirtyCache(w);
|
||||||
|
|
||||||
if (w->onChange) {
|
if (w->onChange) {
|
||||||
w->onChange(w);
|
w->onChange(w);
|
||||||
|
|
@ -1097,6 +1172,7 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
textEditSaveUndo(buf, *pLen, off, w->as.textArea.undoBuf, &w->as.textArea.undoLen, &w->as.textArea.undoCursor, bufSize);
|
textEditSaveUndo(buf, *pLen, off, w->as.textArea.undoBuf, &w->as.textArea.undoLen, &w->as.textArea.undoCursor, bufSize);
|
||||||
memmove(buf + off, buf + off + 1, *pLen - off);
|
memmove(buf + off, buf + off + 1, *pLen - off);
|
||||||
(*pLen)--;
|
(*pLen)--;
|
||||||
|
textAreaDirtyCache(w);
|
||||||
|
|
||||||
if (w->onChange) {
|
if (w->onChange) {
|
||||||
w->onChange(w);
|
w->onChange(w);
|
||||||
|
|
@ -1277,6 +1353,8 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
w->as.textArea.desiredCol = *pCol;
|
w->as.textArea.desiredCol = *pCol;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textAreaDirtyCache(w);
|
||||||
|
|
||||||
if (w->onChange) {
|
if (w->onChange) {
|
||||||
w->onChange(w);
|
w->onChange(w);
|
||||||
}
|
}
|
||||||
|
|
@ -1311,7 +1389,7 @@ void widgetTextAreaOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||||
int32_t innerY = w->y + TEXTAREA_BORDER;
|
int32_t innerY = w->y + TEXTAREA_BORDER;
|
||||||
int32_t innerW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_PAD * 2 - TEXTAREA_SB_W;
|
int32_t innerW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_PAD * 2 - TEXTAREA_SB_W;
|
||||||
int32_t visCols = innerW / font->charWidth;
|
int32_t visCols = innerW / font->charWidth;
|
||||||
int32_t maxLL = textAreaMaxLineLen(w->as.textArea.buf, w->as.textArea.len);
|
int32_t maxLL = textAreaGetMaxLineLen(w);
|
||||||
bool needHSb = (maxLL > visCols);
|
bool needHSb = (maxLL > visCols);
|
||||||
int32_t innerH = w->h - TEXTAREA_BORDER * 2 - (needHSb ? TEXTAREA_SB_W : 0);
|
int32_t innerH = w->h - TEXTAREA_BORDER * 2 - (needHSb ? TEXTAREA_SB_W : 0);
|
||||||
int32_t visRows = innerH / font->charHeight;
|
int32_t visRows = innerH / font->charHeight;
|
||||||
|
|
@ -1529,11 +1607,11 @@ void widgetTextDragUpdate(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||||
int32_t innerY = w->y + TEXTAREA_BORDER;
|
int32_t innerY = w->y + TEXTAREA_BORDER;
|
||||||
int32_t innerW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_PAD * 2 - TEXTAREA_SB_W;
|
int32_t innerW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_PAD * 2 - TEXTAREA_SB_W;
|
||||||
int32_t visCols = innerW / font->charWidth;
|
int32_t visCols = innerW / font->charWidth;
|
||||||
int32_t maxLL = textAreaMaxLineLen(w->as.textArea.buf, w->as.textArea.len);
|
int32_t maxLL = textAreaGetMaxLineLen(w);
|
||||||
bool needHSb = (maxLL > visCols);
|
bool needHSb = (maxLL > visCols);
|
||||||
int32_t innerH = w->h - TEXTAREA_BORDER * 2 - (needHSb ? TEXTAREA_SB_W : 0);
|
int32_t innerH = w->h - TEXTAREA_BORDER * 2 - (needHSb ? TEXTAREA_SB_W : 0);
|
||||||
int32_t visRows = innerH / font->charHeight;
|
int32_t visRows = innerH / font->charHeight;
|
||||||
int32_t totalLines = textAreaCountLines(w->as.textArea.buf, w->as.textArea.len);
|
int32_t totalLines = textAreaGetLineCount(w);
|
||||||
|
|
||||||
if (visRows < 1) {
|
if (visRows < 1) {
|
||||||
visRows = 1;
|
visRows = 1;
|
||||||
|
|
@ -1656,11 +1734,11 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
||||||
int32_t len = w->as.textArea.len;
|
int32_t len = w->as.textArea.len;
|
||||||
int32_t innerW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_PAD * 2 - TEXTAREA_SB_W;
|
int32_t innerW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_PAD * 2 - TEXTAREA_SB_W;
|
||||||
int32_t visCols = innerW / font->charWidth;
|
int32_t visCols = innerW / font->charWidth;
|
||||||
int32_t maxLL = textAreaMaxLineLen(buf, len);
|
int32_t maxLL = textAreaGetMaxLineLen(w);
|
||||||
bool needHSb = (maxLL > visCols);
|
bool needHSb = (maxLL > visCols);
|
||||||
int32_t innerH = w->h - TEXTAREA_BORDER * 2 - (needHSb ? TEXTAREA_SB_W : 0);
|
int32_t innerH = w->h - TEXTAREA_BORDER * 2 - (needHSb ? TEXTAREA_SB_W : 0);
|
||||||
int32_t visRows = innerH / font->charHeight;
|
int32_t visRows = innerH / font->charHeight;
|
||||||
int32_t totalLines = textAreaCountLines(buf, len);
|
int32_t totalLines = textAreaGetLineCount(w);
|
||||||
bool needVSb = (totalLines > visRows);
|
bool needVSb = (totalLines > visRows);
|
||||||
|
|
||||||
// Sunken border
|
// Sunken border
|
||||||
|
|
@ -1694,9 +1772,10 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
||||||
selHi = w->as.textArea.selAnchor < w->as.textArea.selCursor ? w->as.textArea.selCursor : w->as.textArea.selAnchor;
|
selHi = w->as.textArea.selAnchor < w->as.textArea.selCursor ? w->as.textArea.selCursor : w->as.textArea.selAnchor;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw lines
|
// Draw lines — compute first visible line offset once, then advance incrementally
|
||||||
int32_t textX = w->x + TEXTAREA_BORDER + TEXTAREA_PAD;
|
int32_t textX = w->x + TEXTAREA_BORDER + TEXTAREA_PAD;
|
||||||
int32_t textY = w->y + TEXTAREA_BORDER;
|
int32_t textY = w->y + TEXTAREA_BORDER;
|
||||||
|
int32_t lineOff = textAreaLineStart(buf, len, w->as.textArea.scrollRow);
|
||||||
|
|
||||||
for (int32_t i = 0; i < visRows; i++) {
|
for (int32_t i = 0; i < visRows; i++) {
|
||||||
int32_t row = w->as.textArea.scrollRow + i;
|
int32_t row = w->as.textArea.scrollRow + i;
|
||||||
|
|
@ -1705,9 +1784,12 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t lineOff = textAreaLineStart(buf, len, row);
|
// Compute line length by scanning from lineOff (not from buffer start)
|
||||||
int32_t lineL = textAreaLineLen(buf, len, row);
|
int32_t lineL = 0;
|
||||||
int32_t drawY = textY + i * font->charHeight;
|
while (lineOff + lineL < len && buf[lineOff + lineL] != '\n') {
|
||||||
|
lineL++;
|
||||||
|
}
|
||||||
|
int32_t drawY = textY + i * font->charHeight;
|
||||||
|
|
||||||
for (int32_t j = 0; j < visCols; j++) {
|
for (int32_t j = 0; j < visCols; j++) {
|
||||||
int32_t col = w->as.textArea.scrollCol + j;
|
int32_t col = w->as.textArea.scrollCol + j;
|
||||||
|
|
@ -1740,6 +1822,12 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
||||||
rectFill(d, ops, drawX, drawY, font->charWidth, font->charHeight, cbgc);
|
rectFill(d, ops, drawX, drawY, font->charWidth, font->charHeight, cbgc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Advance lineOff to the next line
|
||||||
|
lineOff += lineL;
|
||||||
|
if (lineOff < len && buf[lineOff] == '\n') {
|
||||||
|
lineOff++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw cursor
|
// Draw cursor
|
||||||
|
|
@ -1873,14 +1961,16 @@ void widgetTextAreaSetText(WidgetT *w, const char *text) {
|
||||||
if (w->as.textArea.buf) {
|
if (w->as.textArea.buf) {
|
||||||
strncpy(w->as.textArea.buf, text, w->as.textArea.bufSize - 1);
|
strncpy(w->as.textArea.buf, text, w->as.textArea.bufSize - 1);
|
||||||
w->as.textArea.buf[w->as.textArea.bufSize - 1] = '\0';
|
w->as.textArea.buf[w->as.textArea.bufSize - 1] = '\0';
|
||||||
w->as.textArea.len = (int32_t)strlen(w->as.textArea.buf);
|
w->as.textArea.len = (int32_t)strlen(w->as.textArea.buf);
|
||||||
w->as.textArea.cursorRow = 0;
|
w->as.textArea.cursorRow = 0;
|
||||||
w->as.textArea.cursorCol = 0;
|
w->as.textArea.cursorCol = 0;
|
||||||
w->as.textArea.scrollRow = 0;
|
w->as.textArea.scrollRow = 0;
|
||||||
w->as.textArea.scrollCol = 0;
|
w->as.textArea.scrollCol = 0;
|
||||||
w->as.textArea.desiredCol = 0;
|
w->as.textArea.desiredCol = 0;
|
||||||
w->as.textArea.selAnchor = -1;
|
w->as.textArea.selAnchor = -1;
|
||||||
w->as.textArea.selCursor = -1;
|
w->as.textArea.selCursor = -1;
|
||||||
|
w->as.textArea.cachedLines = -1;
|
||||||
|
w->as.textArea.cachedMaxLL = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2097,6 +2187,23 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_
|
||||||
int32_t selLo = hasSel ? (*pSelStart < *pSelEnd ? *pSelStart : *pSelEnd) : -1;
|
int32_t selLo = hasSel ? (*pSelStart < *pSelEnd ? *pSelStart : *pSelEnd) : -1;
|
||||||
int32_t selHi = hasSel ? (*pSelStart < *pSelEnd ? *pSelEnd : *pSelStart) : -1;
|
int32_t selHi = hasSel ? (*pSelStart < *pSelEnd ? *pSelEnd : *pSelStart) : -1;
|
||||||
|
|
||||||
|
// Clamp selection to buffer bounds
|
||||||
|
if (hasSel) {
|
||||||
|
if (selLo < 0) {
|
||||||
|
selLo = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selHi > *pLen) {
|
||||||
|
selHi = *pLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selLo >= selHi) {
|
||||||
|
hasSel = false;
|
||||||
|
selLo = -1;
|
||||||
|
selHi = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Ctrl+A — select all
|
// Ctrl+A — select all
|
||||||
if (key == 1 && pSelStart && pSelEnd) {
|
if (key == 1 && pSelStart && pSelEnd) {
|
||||||
*pSelStart = 0;
|
*pSelStart = 0;
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,11 @@ LIBDIR = ../lib
|
||||||
SRCS = demo.c
|
SRCS = demo.c
|
||||||
OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS))
|
OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS))
|
||||||
TARGET = $(BINDIR)/demo.exe
|
TARGET = $(BINDIR)/demo.exe
|
||||||
|
BMPS = $(wildcard *.bmp)
|
||||||
|
|
||||||
.PHONY: all clean lib
|
.PHONY: all clean lib
|
||||||
|
|
||||||
all: lib $(TARGET)
|
all: lib $(TARGET) $(addprefix $(BINDIR)/,$(BMPS))
|
||||||
|
|
||||||
lib:
|
lib:
|
||||||
$(MAKE) -C ../dvx
|
$(MAKE) -C ../dvx
|
||||||
|
|
@ -37,6 +38,9 @@ $(OBJDIR):
|
||||||
$(BINDIR):
|
$(BINDIR):
|
||||||
mkdir -p $(BINDIR)
|
mkdir -p $(BINDIR)
|
||||||
|
|
||||||
|
$(BINDIR)/%.bmp: %.bmp | $(BINDIR)
|
||||||
|
cp $< $@
|
||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
$(OBJDIR)/demo.o: demo.c ../dvx/dvxApp.h ../dvx/dvxWidget.h
|
$(OBJDIR)/demo.o: demo.c ../dvx/dvxApp.h ../dvx/dvxWidget.h
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue