diff --git a/apps/progman/progman.c b/apps/progman/progman.c index 8f43308..c5f518c 100644 --- a/apps/progman/progman.c +++ b/apps/progman/progman.c @@ -593,4 +593,5 @@ int32_t appMain(DxeAppContextT *ctx) { void appShutdown(void) { shellUnregisterDesktopUpdate(desktopUpdate); + dvxQuit(sAc); } diff --git a/core/dvxApp.c b/core/dvxApp.c index f2e05af..5b2b6b0 100644 --- a/core/dvxApp.c +++ b/core/dvxApp.c @@ -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; } } diff --git a/core/dvxTypes.h b/core/dvxTypes.h index 742c927..0165596 100644 --- a/core/dvxTypes.h +++ b/core/dvxTypes.h @@ -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) diff --git a/core/dvxWm.c b/core/dvxWm.c index c4f7137..d8d7b73 100644 --- a/core/dvxWm.c +++ b/core/dvxWm.c @@ -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; diff --git a/core/widgetEvent.c b/core/widgetEvent.c index 5e3a93d..44550bc 100644 --- a/core/widgetEvent.c +++ b/core/widgetEvent.c @@ -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 } diff --git a/shell/shellApp.c b/shell/shellApp.c index dcdfbc3..1daf80e 100644 --- a/shell/shellApp.c +++ b/shell/shellApp.c @@ -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--) { diff --git a/widgets/comboBox/widgetComboBox.c b/widgets/comboBox/widgetComboBox.c index 735618c..1f9346b 100644 --- a/widgets/comboBox/widgetComboBox.c +++ b/widgets/comboBox/widgetComboBox.c @@ -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; } diff --git a/widgets/dropdown/widgetDropdown.c b/widgets/dropdown/widgetDropdown.c index 24e3173..dd43639 100644 --- a/widgets/dropdown/widgetDropdown.c +++ b/widgets/dropdown/widgetDropdown.c @@ -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); diff --git a/widgets/radio/widgetRadio.c b/widgets/radio/widgetRadio.c index fb079cf..b79fc6e 100644 --- a/widgets/radio/widgetRadio.c +++ b/widgets/radio/widgetRadio.c @@ -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) { diff --git a/widgets/tabControl/widgetTabControl.c b/widgets/tabControl/widgetTabControl.c index b1905ad..4265468 100644 --- a/widgets/tabControl/widgetTabControl.c +++ b/widgets/tabControl/widgetTabControl.c @@ -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;