Several widget fixes after optimizing repainting.

This commit is contained in:
Scott Duensing 2026-04-13 20:39:59 -05:00
parent 5f305dd14c
commit 094b263c36
10 changed files with 89 additions and 21 deletions

View file

@ -593,4 +593,5 @@ int32_t appMain(DxeAppContextT *ctx) {
void appShutdown(void) { void appShutdown(void) {
shellUnregisterDesktopUpdate(desktopUpdate); shellUnregisterDesktopUpdate(desktopUpdate);
dvxQuit(sAc);
} }

View file

@ -1858,13 +1858,7 @@ static void handleMouseButton(AppContextT *ctx, int32_t mx, int32_t my, int32_t
} else { } else {
ctx->lastTitleClickTime = now; ctx->lastTitleClickTime = now;
ctx->lastTitleClickId = win->id; ctx->lastTitleClickId = win->id;
wmDragBegin(&ctx->stack, hitIdx, mx, my);
// Don't start a drag on a maximized window --
// dragging clears the maximized flag, which
// prevents the double-click restore from working.
if (!win->maximized) {
wmDragBegin(&ctx->stack, hitIdx, mx, my);
}
} }
} }
break; break;
@ -2889,7 +2883,7 @@ static void pollKeyboard(AppContextT *ctx) {
wclsClosePopup(closing); wclsClosePopup(closing);
wgtInvalidatePaint(closing); wgtInvalidate(closing);
continue; continue;
} }
@ -3011,6 +3005,7 @@ static void pollKeyboard(AppContextT *ctx) {
if (p->wclass && (p->wclass->flags & WCLASS_SCROLL_CONTAINER) && if (p->wclass && (p->wclass->flags & WCLASS_SCROLL_CONTAINER) &&
wclsHas(p, WGT_METHOD_SCROLL_CHILD_INTO_VIEW)) { wclsHas(p, WGT_METHOD_SCROLL_CHILD_INTO_VIEW)) {
wclsScrollChildIntoView(p, next); wclsScrollChildIntoView(p, next);
wgtInvalidate(p);
break; break;
} }
} }

View file

@ -588,6 +588,9 @@ typedef struct {
int32_t dragWindow; int32_t dragWindow;
int32_t dragOffX; int32_t dragOffX;
int32_t dragOffY; int32_t dragOffY;
int32_t dragStartX; // mouse position at drag begin (for deadzone)
int32_t dragStartY;
bool dragActive; // true once mouse moved past deadzone
int32_t resizeWindow; int32_t resizeWindow;
int32_t resizeEdge; int32_t resizeEdge;
int32_t scrollWindow; // window being scroll-dragged (HIT_NONE = none) int32_t scrollWindow; // window being scroll-dragged (HIT_NONE = none)

View file

@ -1407,6 +1407,9 @@ void wmDragBegin(WindowStackT *stack, int32_t idx, int32_t mouseX, int32_t mouse
stack->dragWindow = idx; stack->dragWindow = idx;
stack->dragOffX = mouseX - stack->windows[idx]->x; stack->dragOffX = mouseX - stack->windows[idx]->x;
stack->dragOffY = mouseY - stack->windows[idx]->y; stack->dragOffY = mouseY - stack->windows[idx]->y;
stack->dragStartX = mouseX;
stack->dragStartY = mouseY;
stack->dragActive = false;
} }
@ -1435,11 +1438,28 @@ void wmDragEnd(WindowStackT *stack) {
// we don't need to ask the app to repaint during the drag, just blit from // we don't need to ask the app to repaint during the drag, just blit from
// its buffer at the new position. // its buffer at the new position.
#define DRAG_DEADZONE 4
void wmDragMove(WindowStackT *stack, DirtyListT *dl, int32_t mouseX, int32_t mouseY, int32_t screenW, int32_t screenH) { void wmDragMove(WindowStackT *stack, DirtyListT *dl, int32_t mouseX, int32_t mouseY, int32_t screenW, int32_t screenH) {
if (stack->dragWindow < 0 || stack->dragWindow >= stack->count) { if (stack->dragWindow < 0 || stack->dragWindow >= stack->count) {
return; return;
} }
// Don't move until the mouse has moved past the deadzone from the
// initial click point. This allows double-clicks on the title bar
// without accidentally starting a drag.
if (!stack->dragActive) {
int32_t dx = mouseX - stack->dragStartX;
int32_t dy = mouseY - stack->dragStartY;
if (dx < -DRAG_DEADZONE || dx > DRAG_DEADZONE ||
dy < -DRAG_DEADZONE || dy > DRAG_DEADZONE) {
stack->dragActive = true;
} else {
return;
}
}
WindowT *win = stack->windows[stack->dragWindow]; WindowT *win = stack->windows[stack->dragWindow];
int32_t minVisible = 50; int32_t minVisible = 50;

View file

@ -338,8 +338,17 @@ static void widgetOnMouseInner(WindowT *win, WidgetT *root, int32_t x, int32_t y
// Click on popup area -- dispatch to widget's onMouse. // Click on popup area -- dispatch to widget's onMouse.
// The widget may keep the popup open (e.g. scrollbar click) // The widget may keep the popup open (e.g. scrollbar click)
// or close it (item selection sets sOpenPopup = NULL). // or close it (item selection sets sOpenPopup = NULL).
wclsOnMouse(sOpenPopup, root, x, y); WidgetT *popupWidget = sOpenPopup;
wgtInvalidatePaint(root); wclsOnMouse(popupWidget, root, x, y);
// If the popup closed, need a full repaint to erase the
// overlay (it's painted outside widget bounds).
if (!sOpenPopup) {
wgtInvalidate(root);
} else {
wgtInvalidatePaint(root);
}
return; return;
} }
@ -348,7 +357,7 @@ static void widgetOnMouseInner(WindowT *win, WidgetT *root, int32_t x, int32_t y
wclsClosePopup(sOpenPopup); wclsClosePopup(sOpenPopup);
sOpenPopup = NULL; sOpenPopup = NULL;
wgtInvalidatePaint(root); wgtInvalidate(root);
// Fall through to normal click handling // Fall through to normal click handling
} }

View file

@ -243,6 +243,15 @@ void shellForceKillApp(AppContextT *ctx, ShellAppT *app) {
return; return;
} }
// Call shutdown hook so the app can unregister callbacks, close
// handles, etc. Without this, dangling function pointers (e.g.
// shellDesktopUpdate callbacks) cause crashes after dlclose.
if (app->shutdownFn) {
ctx->currentAppId = app->appId;
app->shutdownFn();
ctx->currentAppId = 0;
}
// Destroy all windows belonging to this app. Walk backwards because // Destroy all windows belonging to this app. Walk backwards because
// dvxDestroyWindow removes the window from the stack, shifting indices. // dvxDestroyWindow removes the window from the stack, shifting indices.
for (int32_t i = ctx->stack.count - 1; i >= 0; i--) { for (int32_t i = ctx->stack.count - 1; i >= 0; i--) {

View file

@ -156,14 +156,14 @@ void widgetComboBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
d->selEnd = -1; d->selEnd = -1;
} }
d->open = false; d->open = false;
sOpenPopup = NULL; sOpenPopup = NULL;
if (w->onChange) { if (w->onChange) {
w->onChange(w); w->onChange(w);
} }
wgtInvalidatePaint(w); wgtInvalidate(w);
return; return;
} }

View file

@ -130,11 +130,13 @@ void widgetDropdownOnKey(WidgetT *w, int32_t key, int32_t mod) {
} else if (key == 0x0D || key == ' ') { } else if (key == 0x0D || key == ' ') {
d->selectedIdx = d->hoverIdx; d->selectedIdx = d->hoverIdx;
d->open = false; d->open = false;
sOpenPopup = NULL; sOpenPopup = NULL;
if (w->onChange) { if (w->onChange) {
w->onChange(w); w->onChange(w);
} }
wgtInvalidate(w);
} else if (key >= 0x20 && key < 0x7F) { } else if (key >= 0x20 && key < 0x7F) {
int32_t found = widgetTypeAheadSearch((char)key, d->items, d->itemCount, d->hoverIdx); int32_t found = widgetTypeAheadSearch((char)key, d->items, d->itemCount, d->hoverIdx);

View file

@ -44,6 +44,7 @@ typedef struct {
// Prototypes // Prototypes
// ============================================================ // ============================================================
static void invalidateOldSelection(WidgetT *group, int32_t oldIdx);
static void widgetRadioDestroy(WidgetT *w); static void widgetRadioDestroy(WidgetT *w);
static void widgetRadioGroupDestroy(WidgetT *w); static void widgetRadioGroupDestroy(WidgetT *w);
void widgetRadioOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy); void widgetRadioOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
@ -150,6 +151,7 @@ void widgetRadioOnKey(WidgetT *w, int32_t key, int32_t mod) {
if (next) { if (next) {
RadioDataT *nd = (RadioDataT *)next->data; RadioDataT *nd = (RadioDataT *)next->data;
RadioGroupDataT *gd = (RadioGroupDataT *)next->parent->data; RadioGroupDataT *gd = (RadioGroupDataT *)next->parent->data;
invalidateOldSelection(w->parent, gd->selectedIdx);
sFocusedWidget = next; sFocusedWidget = next;
gd->selectedIdx = nd->index; gd->selectedIdx = nd->index;
@ -174,6 +176,7 @@ void widgetRadioOnKey(WidgetT *w, int32_t key, int32_t mod) {
if (prev) { if (prev) {
RadioDataT *pd = (RadioDataT *)prev->data; RadioDataT *pd = (RadioDataT *)prev->data;
RadioGroupDataT *gd = (RadioGroupDataT *)prev->parent->data; RadioGroupDataT *gd = (RadioGroupDataT *)prev->parent->data;
invalidateOldSelection(w->parent, gd->selectedIdx);
sFocusedWidget = prev; sFocusedWidget = prev;
gd->selectedIdx = pd->index; gd->selectedIdx = pd->index;
@ -192,6 +195,21 @@ void widgetRadioOnKey(WidgetT *w, int32_t key, int32_t mod) {
// widgetRadioOnMouse // widgetRadioOnMouse
// ============================================================ // ============================================================
// Find the currently selected radio in the group and invalidate it
static void invalidateOldSelection(WidgetT *group, int32_t oldIdx) {
for (WidgetT *c = group->firstChild; c; c = c->nextSibling) {
if (c->type == sRadioTypeId) {
RadioDataT *rd = (RadioDataT *)c->data;
if (rd->index == oldIdx) {
wgtInvalidatePaint(c);
return;
}
}
}
}
void widgetRadioOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) { void widgetRadioOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
(void)root; (void)root;
(void)vx; (void)vx;
@ -201,6 +219,11 @@ void widgetRadioOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
if (w->parent && w->parent->type == sRadioGroupTypeId) { if (w->parent && w->parent->type == sRadioGroupTypeId) {
RadioDataT *rd = (RadioDataT *)w->data; RadioDataT *rd = (RadioDataT *)w->data;
RadioGroupDataT *gd = (RadioGroupDataT *)w->parent->data; RadioGroupDataT *gd = (RadioGroupDataT *)w->parent->data;
if (gd->selectedIdx != rd->index) {
invalidateOldSelection(w->parent, gd->selectedIdx);
}
gd->selectedIdx = rd->index; gd->selectedIdx = rd->index;
if (w->parent->onChange) { if (w->parent->onChange) {

View file

@ -414,17 +414,19 @@ void widgetTabControlPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const B
td->scrollOffset = clampInt(td->scrollOffset, 0, maxOff); td->scrollOffset = clampInt(td->scrollOffset, 0, maxOff);
uint32_t fg = colors->contentFg; bool canScrollLeft = (td->scrollOffset > 0);
bool canScrollRight = (td->scrollOffset < maxOff);
BevelStyleT btnBevel = BEVEL_RAISED(colors, 1); BevelStyleT btnBevel = BEVEL_RAISED(colors, 1);
// Left arrow button // Left arrow button
drawBevel(d, ops, w->x, w->y, TAB_ARROW_W, tabH, &btnBevel); drawBevel(d, ops, w->x, w->y, TAB_ARROW_W, tabH, &btnBevel);
{ {
int32_t cx = w->x + TAB_ARROW_W / 2; uint32_t arrowFg = canScrollLeft ? colors->contentFg : colors->windowShadow;
int32_t cy = w->y + tabH / 2; int32_t cx = w->x + TAB_ARROW_W / 2;
int32_t cy = w->y + tabH / 2;
for (int32_t i = 0; i < 4; i++) { for (int32_t i = 0; i < 4; i++) {
drawVLine(d, ops, cx - 2 + i, cy - i, 1 + i * 2, fg); drawVLine(d, ops, cx - 2 + i, cy - i, 1 + i * 2, arrowFg);
} }
} }
@ -432,11 +434,12 @@ void widgetTabControlPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const B
int32_t rx = w->x + w->w - TAB_ARROW_W; int32_t rx = w->x + w->w - TAB_ARROW_W;
drawBevel(d, ops, rx, w->y, TAB_ARROW_W, tabH, &btnBevel); drawBevel(d, ops, rx, w->y, TAB_ARROW_W, tabH, &btnBevel);
{ {
int32_t cx = rx + TAB_ARROW_W / 2; uint32_t arrowFg = canScrollRight ? colors->contentFg : colors->windowShadow;
int32_t cy = w->y + tabH / 2; int32_t cx = rx + TAB_ARROW_W / 2;
int32_t cy = w->y + tabH / 2;
for (int32_t i = 0; i < 4; i++) { for (int32_t i = 0; i < 4; i++) {
drawVLine(d, ops, cx + 2 - i, cy - i, 1 + i * 2, fg); drawVLine(d, ops, cx + 2 - i, cy - i, 1 + i * 2, arrowFg);
} }
} }
} }
@ -451,6 +454,9 @@ void widgetTabControlPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const B
int32_t oldClipH = d->clipH; int32_t oldClipH = d->clipH;
setClipRect(d, headerLeft, w->y, headerRight - headerLeft, tabH + 2); setClipRect(d, headerLeft, w->y, headerRight - headerLeft, tabH + 2);
// Clear the header strip so scrolled-away tab pixels are erased
rectFill(d, ops, headerLeft, w->y, headerRight - headerLeft, tabH + 2, colors->contentBg);
int32_t tabX = headerLeft - td->scrollOffset; int32_t tabX = headerLeft - td->scrollOffset;
int32_t tabIdx = 0; int32_t tabIdx = 0;