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) {
shellUnregisterDesktopUpdate(desktopUpdate);
dvxQuit(sAc);
}

View file

@ -1858,13 +1858,7 @@ static void handleMouseButton(AppContextT *ctx, int32_t mx, int32_t my, int32_t
} else {
ctx->lastTitleClickTime = now;
ctx->lastTitleClickId = win->id;
// 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);
}
wmDragBegin(&ctx->stack, hitIdx, mx, my);
}
}
break;
@ -2889,7 +2883,7 @@ static void pollKeyboard(AppContextT *ctx) {
wclsClosePopup(closing);
wgtInvalidatePaint(closing);
wgtInvalidate(closing);
continue;
}
@ -3011,6 +3005,7 @@ static void pollKeyboard(AppContextT *ctx) {
if (p->wclass && (p->wclass->flags & WCLASS_SCROLL_CONTAINER) &&
wclsHas(p, WGT_METHOD_SCROLL_CHILD_INTO_VIEW)) {
wclsScrollChildIntoView(p, next);
wgtInvalidate(p);
break;
}
}

View file

@ -588,6 +588,9 @@ typedef struct {
int32_t dragWindow;
int32_t dragOffX;
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 resizeEdge;
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->dragOffX = mouseX - stack->windows[idx]->x;
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
// 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) {
if (stack->dragWindow < 0 || stack->dragWindow >= stack->count) {
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];
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.
// The widget may keep the popup open (e.g. scrollbar click)
// or close it (item selection sets sOpenPopup = NULL).
wclsOnMouse(sOpenPopup, root, x, y);
wgtInvalidatePaint(root);
WidgetT *popupWidget = sOpenPopup;
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;
}
@ -348,7 +357,7 @@ static void widgetOnMouseInner(WindowT *win, WidgetT *root, int32_t x, int32_t y
wclsClosePopup(sOpenPopup);
sOpenPopup = NULL;
wgtInvalidatePaint(root);
wgtInvalidate(root);
// Fall through to normal click handling
}

View file

@ -243,6 +243,15 @@ void shellForceKillApp(AppContextT *ctx, ShellAppT *app) {
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
// dvxDestroyWindow removes the window from the stack, shifting indices.
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->open = false;
d->open = false;
sOpenPopup = NULL;
if (w->onChange) {
w->onChange(w);
}
wgtInvalidatePaint(w);
wgtInvalidate(w);
return;
}

View file

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

View file

@ -44,6 +44,7 @@ typedef struct {
// Prototypes
// ============================================================
static void invalidateOldSelection(WidgetT *group, int32_t oldIdx);
static void widgetRadioDestroy(WidgetT *w);
static void widgetRadioGroupDestroy(WidgetT *w);
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) {
RadioDataT *nd = (RadioDataT *)next->data;
RadioGroupDataT *gd = (RadioGroupDataT *)next->parent->data;
invalidateOldSelection(w->parent, gd->selectedIdx);
sFocusedWidget = next;
gd->selectedIdx = nd->index;
@ -174,6 +176,7 @@ void widgetRadioOnKey(WidgetT *w, int32_t key, int32_t mod) {
if (prev) {
RadioDataT *pd = (RadioDataT *)prev->data;
RadioGroupDataT *gd = (RadioGroupDataT *)prev->parent->data;
invalidateOldSelection(w->parent, gd->selectedIdx);
sFocusedWidget = prev;
gd->selectedIdx = pd->index;
@ -192,6 +195,21 @@ void widgetRadioOnKey(WidgetT *w, int32_t key, int32_t mod) {
// 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)root;
(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) {
RadioDataT *rd = (RadioDataT *)w->data;
RadioGroupDataT *gd = (RadioGroupDataT *)w->parent->data;
if (gd->selectedIdx != rd->index) {
invalidateOldSelection(w->parent, gd->selectedIdx);
}
gd->selectedIdx = rd->index;
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);
uint32_t fg = colors->contentFg;
bool canScrollLeft = (td->scrollOffset > 0);
bool canScrollRight = (td->scrollOffset < maxOff);
BevelStyleT btnBevel = BEVEL_RAISED(colors, 1);
// Left arrow button
drawBevel(d, ops, w->x, w->y, TAB_ARROW_W, tabH, &btnBevel);
{
int32_t cx = w->x + TAB_ARROW_W / 2;
int32_t cy = w->y + tabH / 2;
uint32_t arrowFg = canScrollLeft ? colors->contentFg : colors->windowShadow;
int32_t cx = w->x + TAB_ARROW_W / 2;
int32_t cy = w->y + tabH / 2;
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;
drawBevel(d, ops, rx, w->y, TAB_ARROW_W, tabH, &btnBevel);
{
int32_t cx = rx + TAB_ARROW_W / 2;
int32_t cy = w->y + tabH / 2;
uint32_t arrowFg = canScrollRight ? colors->contentFg : colors->windowShadow;
int32_t cx = rx + TAB_ARROW_W / 2;
int32_t cy = w->y + tabH / 2;
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;
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 tabIdx = 0;