From 8550fbbbf25a558a880ae32c0a862991b3982d70 Mon Sep 17 00:00:00 2001 From: Scott Duensing Date: Wed, 18 Mar 2026 19:09:29 -0500 Subject: [PATCH] Fixed scrollbar thumb dragging and ALT-SPACE. --- dvx/dvxApp.c | 3 +- dvx/widgets/widgetCore.c | 13 +- dvx/widgets/widgetEvent.c | 14 ++ dvx/widgets/widgetInternal.h | 4 + dvx/widgets/widgetListBox.c | 9 + dvx/widgets/widgetListView.c | 18 ++ dvx/widgets/widgetScrollPane.c | 10 + dvx/widgets/widgetScrollbar.c | 397 +++++++++++++++++++++++++++++++++ dvx/widgets/widgetTextInput.c | 10 + dvx/widgets/widgetTreeView.c | 18 ++ 10 files changed, 492 insertions(+), 4 deletions(-) diff --git a/dvx/dvxApp.c b/dvx/dvxApp.c index d99e9ce..6ac8c77 100644 --- a/dvx/dvxApp.c +++ b/dvx/dvxApp.c @@ -2936,7 +2936,8 @@ static void pollKeyboard(AppContextT *ctx) { // Alt+Space — open/close system menu // Enhanced INT 16h: Alt+Space returns scancode 0x39, ascii 0x20 - if (scancode == 0x39 && ascii == 0x20) { + // Must check Alt modifier (bit 3) to distinguish from plain Space + if (scancode == 0x39 && ascii == 0x20 && (shiftFlags & 0x08)) { if (ctx->sysMenu.active) { closeSysMenu(ctx); } else if (ctx->stack.focusedIdx >= 0) { diff --git a/dvx/widgets/widgetCore.c b/dvx/widgets/widgetCore.c index f6bd906..6d0d552 100644 --- a/dvx/widgets/widgetCore.c +++ b/dvx/widgets/widgetCore.c @@ -49,9 +49,12 @@ WidgetT *sResizeListView = NULL; // ListView undergoing column resize int32_t sResizeCol = -1; // which column is being resized int32_t sResizeStartX = 0; // mouse X at resize start int32_t sResizeOrigW = 0; // column width at resize start -WidgetT *sDragSplitter = NULL; // splitter being dragged -int32_t sDragSplitStart = 0; // mouse offset from splitter edge at drag start -WidgetT *sDragReorder = NULL; // list/tree widget in drag-reorder mode +WidgetT *sDragSplitter = NULL; // splitter being dragged +int32_t sDragSplitStart = 0; // mouse offset from splitter edge at drag start +WidgetT *sDragReorder = NULL; // list/tree widget in drag-reorder mode +WidgetT *sDragScrollbar = NULL; // widget whose scrollbar thumb is being dragged +int32_t sDragScrollbarOff = 0; // mouse offset within thumb at drag start +int32_t sDragScrollbarOrient = 0; // 0=vertical, 1=horizontal // ============================================================ @@ -201,6 +204,10 @@ void widgetDestroyChildren(WidgetT *w) { sResizeCol = -1; } + if (sDragScrollbar == child) { + sDragScrollbar = NULL; + } + free(child); child = next; } diff --git a/dvx/widgets/widgetEvent.c b/dvx/widgets/widgetEvent.c index d88ac96..b5e569b 100644 --- a/dvx/widgets/widgetEvent.c +++ b/dvx/widgets/widgetEvent.c @@ -388,6 +388,20 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) { return; } + // Handle scrollbar thumb drag release + if (sDragScrollbar && !(buttons & MOUSE_LEFT)) { + sDragScrollbar = NULL; + wgtInvalidatePaint(root); + return; + } + + // Handle scrollbar thumb drag (mouse move while pressed) + if (sDragScrollbar && (buttons & MOUSE_LEFT)) { + widgetScrollbarDragUpdate(sDragScrollbar, sDragScrollbarOrient, sDragScrollbarOff, x, y); + wgtInvalidatePaint(root); + return; + } + // Handle button press release if (sPressedButton && !(buttons & MOUSE_LEFT)) { if (sPressedButton->type == WidgetImageButtonE) { diff --git a/dvx/widgets/widgetInternal.h b/dvx/widgets/widgetInternal.h index 477a876..a0240b3 100644 --- a/dvx/widgets/widgetInternal.h +++ b/dvx/widgets/widgetInternal.h @@ -191,6 +191,9 @@ extern int32_t sResizeOrigW; // original column width at start of resize extern WidgetT *sDragSplitter; // splitter being dragged extern int32_t sDragSplitStart; // mouse position at start of splitter drag extern WidgetT *sDragReorder; // listbox/treeview item being drag-reordered +extern WidgetT *sDragScrollbar; // widget whose scrollbar thumb is being dragged +extern int32_t sDragScrollbarOff; // mouse offset within thumb at drag start +extern int32_t sDragScrollbarOrient; // 0=vertical, 1=horizontal // ============================================================ // Core functions (widgetCore.c) @@ -260,6 +263,7 @@ typedef enum { void widgetDrawScrollbarH(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbW, int32_t totalSize, int32_t visibleSize, int32_t scrollPos); void widgetDrawScrollbarV(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbH, int32_t totalSize, int32_t visibleSize, int32_t scrollPos); +void widgetScrollbarDragUpdate(WidgetT *w, int32_t orient, int32_t dragOff, int32_t mouseX, int32_t mouseY); ScrollHitE widgetScrollbarHitTest(int32_t sbLen, int32_t relPos, int32_t totalSize, int32_t visibleSize, int32_t scrollPos); // ============================================================ diff --git a/dvx/widgets/widgetListBox.c b/dvx/widgets/widgetListBox.c index a7cbffb..959f6c4 100644 --- a/dvx/widgets/widgetListBox.c +++ b/dvx/widgets/widgetListBox.c @@ -471,6 +471,15 @@ void widgetListBoxOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) { } else if (sh == ScrollHitPageIncE) { hit->as.listBox.scrollPos += visibleRows; hit->as.listBox.scrollPos = clampInt(hit->as.listBox.scrollPos, 0, maxScroll); + } else if (sh == ScrollHitThumbE) { + int32_t trackLen = innerH - WGT_SB_W * 2; + int32_t thumbPos; + int32_t thumbSize; + widgetScrollbarThumb(trackLen, hit->as.listBox.itemCount, visibleRows, hit->as.listBox.scrollPos, &thumbPos, &thumbSize); + + sDragScrollbar = hit; + sDragScrollbarOrient = 0; + sDragScrollbarOff = relY - WGT_SB_W - thumbPos; } hit->focused = true; diff --git a/dvx/widgets/widgetListView.c b/dvx/widgets/widgetListView.c index f3b6df3..cf79c70 100644 --- a/dvx/widgets/widgetListView.c +++ b/dvx/widgets/widgetListView.c @@ -817,6 +817,15 @@ void widgetListViewOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) } else if (sh == ScrollHitPageIncE) { hit->as.listView->scrollPos += visibleRows; hit->as.listView->scrollPos = clampInt(hit->as.listView->scrollPos, 0, maxScrollV); + } else if (sh == ScrollHitThumbE) { + int32_t trackLen = innerH - WGT_SB_W * 2; + int32_t thumbPos; + int32_t thumbSize; + widgetScrollbarThumb(trackLen, hit->as.listView->rowCount, visibleRows, hit->as.listView->scrollPos, &thumbPos, &thumbSize); + + sDragScrollbar = hit; + sDragScrollbarOrient = 0; + sDragScrollbarOff = relY - WGT_SB_W - thumbPos; } return; @@ -846,6 +855,15 @@ void widgetListViewOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) hit->as.listView->scrollPosH -= pageSize; } else if (sh == ScrollHitPageIncE) { hit->as.listView->scrollPosH += pageSize; + } else if (sh == ScrollHitThumbE) { + int32_t trackLen = innerW - WGT_SB_W * 2; + int32_t thumbPos; + int32_t thumbSize; + widgetScrollbarThumb(trackLen, totalColW, innerW, hit->as.listView->scrollPosH, &thumbPos, &thumbSize); + + sDragScrollbar = hit; + sDragScrollbarOrient = 1; + sDragScrollbarOff = relX - WGT_SB_W - thumbPos; } hit->as.listView->scrollPosH = clampInt(hit->as.listView->scrollPosH, 0, maxScrollH); diff --git a/dvx/widgets/widgetScrollPane.c b/dvx/widgets/widgetScrollPane.c index ab8a28d..29f45bd 100644 --- a/dvx/widgets/widgetScrollPane.c +++ b/dvx/widgets/widgetScrollPane.c @@ -499,6 +499,11 @@ void widgetScrollPaneOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy hit->as.scrollPane.scrollPosV -= pageSize; } else if (trackRelY >= thumbPos + thumbSize) { hit->as.scrollPane.scrollPosV += pageSize; + } else { + sDragScrollbar = hit; + sDragScrollbarOrient = 0; + sDragScrollbarOff = trackRelY - thumbPos; + return; } } @@ -538,6 +543,11 @@ void widgetScrollPaneOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy hit->as.scrollPane.scrollPosH -= pageSize; } else if (trackRelX >= thumbPos + thumbSize) { hit->as.scrollPane.scrollPosH += pageSize; + } else { + sDragScrollbar = hit; + sDragScrollbarOrient = 1; + sDragScrollbarOff = trackRelX - thumbPos; + return; } } diff --git a/dvx/widgets/widgetScrollbar.c b/dvx/widgets/widgetScrollbar.c index be66c9c..44597b8 100644 --- a/dvx/widgets/widgetScrollbar.c +++ b/dvx/widgets/widgetScrollbar.c @@ -22,6 +22,16 @@ #include "widgetInternal.h" +// Constants duplicated from widgetTextInput.c and widgetScrollPane.c +// for use in widgetScrollbarDragUpdate. These are file-local in their +// source files, so we repeat the values here to avoid cross-file +// coupling. They must stay in sync with the originals. +#define TEXTAREA_BORDER 2 +#define TEXTAREA_PAD 2 +#define TEXTAREA_SB_W 14 +#define SP_BORDER 2 +#define SP_SB_W 14 + // ============================================================ // widgetDrawScrollbarH @@ -135,6 +145,393 @@ void widgetDrawScrollbarV(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT * } +// ============================================================ +// widgetScrollbarDragUpdate +// ============================================================ + +// Handles ongoing scrollbar thumb drag for widget-internal scrollbars. +// Converts the mouse pixel position into a scroll value using linear +// interpolation, matching the WM-level wmScrollbarDrag logic. +// orient: 0=vertical, 1=horizontal. +// dragOff: mouse offset within thumb captured at drag start. +// +// Each widget type stores scroll state differently (row counts vs +// pixel offsets, different struct fields), so this function switches +// on widget type to extract the scrollbar geometry and update the +// correct scroll field. +void widgetScrollbarDragUpdate(WidgetT *w, int32_t orient, int32_t dragOff, int32_t mouseX, int32_t mouseY) { + // Determine scrollbar geometry per widget type. + // sbOrigin: screen coordinate of the scrollbar's top/left edge + // sbLen: total length of the scrollbar (including arrow buttons) + // totalSize: total content size (items, pixels, or columns) + // visibleSize: visible portion of content + // scrollPos: current scroll position (pointer to update) + // maxScroll: maximum scroll value + int32_t sbOrigin = 0; + int32_t sbLen = 0; + int32_t totalSize = 0; + int32_t visibleSize = 0; + int32_t maxScroll = 0; + int32_t sbWidth = WGT_SB_W; + + if (w->type == WidgetTextAreaE) { + sbWidth = TEXTAREA_SB_W; + + if (orient == 0) { + // Vertical scrollbar + AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData; + const BitmapFontT *font = &ctx->font; + + int32_t innerW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_PAD * 2 - TEXTAREA_SB_W; + int32_t visCols = innerW / font->charWidth; + int32_t maxLL = 0; + + // Compute max line length by scanning lines + const char *buf = w->as.textArea.buf; + int32_t len = w->as.textArea.len; + int32_t lineStart = 0; + + for (int32_t i = 0; i <= len; i++) { + if (i == len || buf[i] == '\n') { + int32_t ll = i - lineStart; + + if (ll > maxLL) { + maxLL = ll; + } + + lineStart = i + 1; + } + } + + bool needHSb = (maxLL > visCols); + int32_t innerH = w->h - TEXTAREA_BORDER * 2 - (needHSb ? TEXTAREA_SB_W : 0); + int32_t visRows = innerH / font->charHeight; + + if (visRows < 1) { + visRows = 1; + } + + // Count total lines + int32_t totalLines = 1; + + for (int32_t i = 0; i < len; i++) { + if (buf[i] == '\n') { + totalLines++; + } + } + + sbOrigin = w->y + TEXTAREA_BORDER; + sbLen = innerH; + totalSize = totalLines; + visibleSize = visRows; + maxScroll = totalLines - visRows; + } else { + // Horizontal scrollbar + AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData; + const BitmapFontT *font = &ctx->font; + + int32_t innerW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_PAD * 2 - TEXTAREA_SB_W; + int32_t visCols = innerW / font->charWidth; + int32_t maxLL = 0; + + const char *buf = w->as.textArea.buf; + int32_t len = w->as.textArea.len; + int32_t lineStart = 0; + + for (int32_t i = 0; i <= len; i++) { + if (i == len || buf[i] == '\n') { + int32_t ll = i - lineStart; + + if (ll > maxLL) { + maxLL = ll; + } + + lineStart = i + 1; + } + } + + int32_t hsbW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_SB_W; + + sbOrigin = w->x + TEXTAREA_BORDER; + sbLen = hsbW; + totalSize = maxLL; + visibleSize = visCols; + maxScroll = maxLL - visCols; + } + } else if (w->type == WidgetListBoxE) { + // Vertical only + AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData; + const BitmapFontT *font = &ctx->font; + + int32_t innerH = w->h - LISTBOX_BORDER * 2; + int32_t visibleRows = innerH / font->charHeight; + + sbOrigin = w->y + LISTBOX_BORDER; + sbLen = innerH; + totalSize = w->as.listBox.itemCount; + visibleSize = visibleRows; + maxScroll = totalSize - visibleSize; + } else if (w->type == WidgetListViewE) { + AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData; + const BitmapFontT *font = &ctx->font; + + int32_t headerH = font->charHeight + 4; + int32_t innerH = w->h - LISTVIEW_BORDER * 2 - headerH; + int32_t innerW = w->w - LISTVIEW_BORDER * 2; + int32_t visibleRows = innerH / font->charHeight; + int32_t totalColW = w->as.listView->totalColW; + bool needVSb = (w->as.listView->rowCount > visibleRows); + + if (needVSb) { + innerW -= WGT_SB_W; + } + + if (totalColW > innerW) { + innerH -= WGT_SB_W; + visibleRows = innerH / font->charHeight; + + if (!needVSb && w->as.listView->rowCount > visibleRows) { + innerW -= WGT_SB_W; + } + } + + if (visibleRows < 1) { + visibleRows = 1; + } + + if (orient == 0) { + // Vertical + sbOrigin = w->y + LISTVIEW_BORDER + headerH; + sbLen = innerH; + totalSize = w->as.listView->rowCount; + visibleSize = visibleRows; + maxScroll = totalSize - visibleSize; + } else { + // Horizontal + sbOrigin = w->x + LISTVIEW_BORDER; + sbLen = innerW; + totalSize = totalColW; + visibleSize = innerW; + maxScroll = totalColW - innerW; + } + } else if (w->type == WidgetTreeViewE) { + AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData; + const BitmapFontT *font = &ctx->font; + + int32_t totalH; + int32_t totalW; + int32_t innerH; + int32_t innerW; + bool needVSb; + + // Walk the visible tree to compute total content dimensions. + // This duplicates treeCalcScrollbarNeeds which is file-static, + // so we compute it inline using the same logic. + int32_t treeH = 0; + int32_t treeW = 0; + + // Count visible items and max width + for (WidgetT *c = w->firstChild; c; c = c->nextSibling) { + if (c->type == WidgetTreeItemE && c->visible) { + // Walk visible items + WidgetT *item = c; + + while (item) { + treeH += font->charHeight; + + // Compute depth + int32_t depth = 0; + WidgetT *p = item->parent; + + while (p && p != w) { + depth++; + p = p->parent; + } + + int32_t itemW = depth * TREE_INDENT + TREE_EXPAND_SIZE + TREE_ICON_GAP; + + if (item->as.treeItem.text) { + itemW += (int32_t)strlen(item->as.treeItem.text) * font->charWidth; + } + + if (itemW > treeW) { + treeW = itemW; + } + + item = widgetTreeViewNextVisible(item, w); + } + + break; // Only process first top-level visible chain + } + } + + totalH = treeH; + totalW = treeW; + innerH = w->h - TREE_BORDER * 2; + innerW = w->w - TREE_BORDER * 2; + needVSb = (totalH > innerH); + + if (needVSb) { + innerW -= WGT_SB_W; + } + + if (totalW > innerW) { + innerH -= WGT_SB_W; + + if (!needVSb && totalH > innerH) { + needVSb = true; + innerW -= WGT_SB_W; + } + } + + if (orient == 0) { + // Vertical (pixel-based scroll) + sbOrigin = w->y + TREE_BORDER; + sbLen = innerH; + totalSize = totalH; + visibleSize = innerH; + maxScroll = totalH - innerH; + } else { + // Horizontal (pixel-based scroll) + sbOrigin = w->x + TREE_BORDER; + sbLen = innerW; + totalSize = totalW; + visibleSize = innerW; + maxScroll = totalW - innerW; + } + } else if (w->type == WidgetScrollPaneE) { + sbWidth = SP_SB_W; + + AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData; + const BitmapFontT *font = &ctx->font; + + // Compute content min size by measuring children + int32_t contentMinW = 0; + int32_t contentMinH = 0; + + for (WidgetT *c = w->firstChild; c; c = c->nextSibling) { + if (c->visible) { + if (c->wclass && c->wclass->calcMinSize) { + c->wclass->calcMinSize(c, font); + } + + if (c->calcMinW > contentMinW) { + contentMinW = c->calcMinW; + } + + contentMinH += c->calcMinH; + } + } + + int32_t innerH = w->h - SP_BORDER * 2; + int32_t innerW = w->w - SP_BORDER * 2; + bool needVSb = (contentMinH > innerH); + + if (needVSb) { + innerW -= SP_SB_W; + } + + if (contentMinW > innerW) { + innerH -= SP_SB_W; + + if (!needVSb && contentMinH > innerH) { + needVSb = true; + innerW -= SP_SB_W; + } + } + + if (orient == 0) { + // Vertical + sbOrigin = w->y + SP_BORDER; + sbLen = innerH; + totalSize = contentMinH; + visibleSize = innerH; + maxScroll = contentMinH - innerH; + } else { + // Horizontal + sbOrigin = w->x + SP_BORDER; + sbLen = innerW; + totalSize = contentMinW; + visibleSize = innerW; + maxScroll = contentMinW - innerW; + } + } else { + return; + } + + if (maxScroll < 0) { + maxScroll = 0; + } + + if (maxScroll == 0) { + return; + } + + // Compute thumb geometry + int32_t trackLen = sbLen - sbWidth * 2; + + if (trackLen <= 0) { + return; + } + + int32_t thumbPos; + int32_t thumbSize; + widgetScrollbarThumb(trackLen, totalSize, visibleSize, 0, &thumbPos, &thumbSize); + + if (trackLen <= thumbSize) { + return; + } + + // Convert mouse position to scroll value + int32_t mousePos; + + if (orient == 0) { + mousePos = mouseY - sbOrigin - sbWidth - dragOff; + } else { + mousePos = mouseX - sbOrigin - sbWidth - dragOff; + } + + int32_t newScroll = (mousePos * maxScroll) / (trackLen - thumbSize); + + if (newScroll < 0) { + newScroll = 0; + } + + if (newScroll > maxScroll) { + newScroll = maxScroll; + } + + // Update the widget's scroll position + if (w->type == WidgetTextAreaE) { + if (orient == 0) { + w->as.textArea.scrollRow = newScroll; + } else { + w->as.textArea.scrollCol = newScroll; + } + } else if (w->type == WidgetListBoxE) { + w->as.listBox.scrollPos = newScroll; + } else if (w->type == WidgetListViewE) { + if (orient == 0) { + w->as.listView->scrollPos = newScroll; + } else { + w->as.listView->scrollPosH = newScroll; + } + } else if (w->type == WidgetTreeViewE) { + if (orient == 0) { + w->as.treeView.scrollPos = newScroll; + } else { + w->as.treeView.scrollPosH = newScroll; + } + } else if (w->type == WidgetScrollPaneE) { + if (orient == 0) { + w->as.scrollPane.scrollPosV = newScroll; + } else { + w->as.scrollPane.scrollPosH = newScroll; + } + } +} + + // ============================================================ // widgetScrollbarHitTest // ============================================================ diff --git a/dvx/widgets/widgetTextInput.c b/dvx/widgets/widgetTextInput.c index 8c8c7c8..91c99f7 100644 --- a/dvx/widgets/widgetTextInput.c +++ b/dvx/widgets/widgetTextInput.c @@ -1850,6 +1850,11 @@ void widgetTextAreaOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) { } else if (trackRelX >= thumbPos + thumbSize) { w->as.textArea.scrollCol += visCols; w->as.textArea.scrollCol = clampInt(w->as.textArea.scrollCol, 0, maxHScroll); + } else { + sDragScrollbar = w; + sDragScrollbarOrient = 1; + sDragScrollbarOff = trackRelX - thumbPos; + return; } } @@ -1889,6 +1894,11 @@ void widgetTextAreaOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) { } else if (trackRelY >= thumbPos + thumbSize) { w->as.textArea.scrollRow += visRows; w->as.textArea.scrollRow = clampInt(w->as.textArea.scrollRow, 0, maxScroll); + } else { + sDragScrollbar = w; + sDragScrollbarOrient = 0; + sDragScrollbarOff = trackRelY - thumbPos; + return; } } diff --git a/dvx/widgets/widgetTreeView.c b/dvx/widgets/widgetTreeView.c index ee7a341..56ba52a 100644 --- a/dvx/widgets/widgetTreeView.c +++ b/dvx/widgets/widgetTreeView.c @@ -1056,6 +1056,15 @@ void widgetTreeViewOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) hit->as.treeView.scrollPos -= pageSize; } else if (sh == ScrollHitPageIncE) { hit->as.treeView.scrollPos += pageSize; + } else if (sh == ScrollHitThumbE) { + int32_t trackLen = innerH - WGT_SB_W * 2; + int32_t thumbPos; + int32_t thumbSize; + widgetScrollbarThumb(trackLen, totalH, innerH, hit->as.treeView.scrollPos, &thumbPos, &thumbSize); + + sDragScrollbar = hit; + sDragScrollbarOrient = 0; + sDragScrollbarOff = relY - WGT_SB_W - thumbPos; } hit->as.treeView.scrollPos = clampInt(hit->as.treeView.scrollPos, 0, maxScrollV); @@ -1085,6 +1094,15 @@ void widgetTreeViewOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) hit->as.treeView.scrollPosH -= pageSize; } else if (sh == ScrollHitPageIncE) { hit->as.treeView.scrollPosH += pageSize; + } else if (sh == ScrollHitThumbE) { + int32_t trackLen = innerW - WGT_SB_W * 2; + int32_t thumbPos; + int32_t thumbSize; + widgetScrollbarThumb(trackLen, totalW, innerW, hit->as.treeView.scrollPosH, &thumbPos, &thumbSize); + + sDragScrollbar = hit; + sDragScrollbarOrient = 1; + sDragScrollbarOff = relX - WGT_SB_W - thumbPos; } hit->as.treeView.scrollPosH = clampInt(hit->as.treeView.scrollPosH, 0, maxScrollH);