// widgetScrollbar.c — Shared scrollbar painting and hit-testing // // These are not widgets themselves -- they are stateless rendering and // hit-testing utilities shared by ScrollPane, TreeView, TextArea, // ListBox, and ListView. Each owning widget stores its own scroll // position; these functions are purely geometric. // // The scrollbar model uses three parts: two arrow buttons (one at each // end) and a proportional thumb in the track between them. Thumb size // is proportional to (visibleSize / totalSize), clamped to SB_MIN_THUMB // to remain grabbable even when content is very large. Thumb position // maps linearly from scrollPos to track position. // // Arrow triangles are drawn with simple loop-based scanlines (4 rows), // producing 7-pixel-wide arrow glyphs. This avoids any font or bitmap // dependency for the scrollbar chrome. // // The minimum scrollbar length guard (sbW < WGT_SB_W * 3) ensures // there is at least room for both arrow buttons plus a minimal track. // If the container is too small, the scrollbar is simply not drawn // rather than rendering a corrupted mess. #include "widgetInternal.h" // ============================================================ // widgetDrawScrollbarH // ============================================================ 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) { if (sbW < WGT_SB_W * 3) { return; } // Trough background BevelStyleT troughBevel = BEVEL_TROUGH(colors); drawBevel(d, ops, sbX, sbY, sbW, WGT_SB_W, &troughBevel); // Left arrow button BevelStyleT btnBevel = BEVEL_RAISED(colors, 1); drawBevel(d, ops, sbX, sbY, WGT_SB_W, WGT_SB_W, &btnBevel); // Left arrow triangle { int32_t cx = sbX + WGT_SB_W / 2; int32_t cy = sbY + WGT_SB_W / 2; uint32_t fg = colors->contentFg; for (int32_t i = 0; i < 4; i++) { drawVLine(d, ops, cx - 2 + i, cy - i, 1 + i * 2, fg); } } // Right arrow button int32_t rightX = sbX + sbW - WGT_SB_W; drawBevel(d, ops, rightX, sbY, WGT_SB_W, WGT_SB_W, &btnBevel); // Right arrow triangle { int32_t cx = rightX + WGT_SB_W / 2; int32_t cy = sbY + WGT_SB_W / 2; uint32_t fg = colors->contentFg; for (int32_t i = 0; i < 4; i++) { drawVLine(d, ops, cx + 2 - i, cy - i, 1 + i * 2, fg); } } // Thumb int32_t trackLen = sbW - WGT_SB_W * 2; if (trackLen > 0 && totalSize > 0) { int32_t thumbPos; int32_t thumbSize; widgetScrollbarThumb(trackLen, totalSize, visibleSize, scrollPos, &thumbPos, &thumbSize); drawBevel(d, ops, sbX + WGT_SB_W + thumbPos, sbY, thumbSize, WGT_SB_W, &btnBevel); } } // ============================================================ // widgetDrawScrollbarV // ============================================================ 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) { if (sbH < WGT_SB_W * 3) { return; } // Trough background BevelStyleT troughBevel = BEVEL_TROUGH(colors); drawBevel(d, ops, sbX, sbY, WGT_SB_W, sbH, &troughBevel); // Up arrow button BevelStyleT btnBevel = BEVEL_RAISED(colors, 1); drawBevel(d, ops, sbX, sbY, WGT_SB_W, WGT_SB_W, &btnBevel); // Up arrow triangle { int32_t cx = sbX + WGT_SB_W / 2; int32_t cy = sbY + WGT_SB_W / 2; uint32_t fg = colors->contentFg; for (int32_t i = 0; i < 4; i++) { drawHLine(d, ops, cx - i, cy - 2 + i, 1 + i * 2, fg); } } // Down arrow button int32_t downY = sbY + sbH - WGT_SB_W; drawBevel(d, ops, sbX, downY, WGT_SB_W, WGT_SB_W, &btnBevel); // Down arrow triangle { int32_t cx = sbX + WGT_SB_W / 2; int32_t cy = downY + WGT_SB_W / 2; uint32_t fg = colors->contentFg; for (int32_t i = 0; i < 4; i++) { drawHLine(d, ops, cx - i, cy + 2 - i, 1 + i * 2, fg); } } // Thumb int32_t trackLen = sbH - WGT_SB_W * 2; if (trackLen > 0 && totalSize > 0) { int32_t thumbPos; int32_t thumbSize; widgetScrollbarThumb(trackLen, totalSize, visibleSize, scrollPos, &thumbPos, &thumbSize); drawBevel(d, ops, sbX, sbY + WGT_SB_W + thumbPos, WGT_SB_W, thumbSize, &btnBevel); } } // ============================================================ // widgetScrollbarHitTest // ============================================================ // Axis-agnostic hit test. The caller converts (vx,vy) into a 1D // position along the scrollbar axis (relPos) and the scrollbar // length (sbLen). Returns which zone was hit: arrow buttons, // page-up/page-down trough regions, or the thumb itself. // This factoring lets all scrollbar-owning widgets share the same // logic without duplicating per-axis code. ScrollHitE widgetScrollbarHitTest(int32_t sbLen, int32_t relPos, int32_t totalSize, int32_t visibleSize, int32_t scrollPos) { if (relPos < WGT_SB_W) { return ScrollHitArrowDecE; } if (relPos >= sbLen - WGT_SB_W) { return ScrollHitArrowIncE; } int32_t trackLen = sbLen - WGT_SB_W * 2; if (trackLen > 0) { int32_t thumbPos; int32_t thumbSize; widgetScrollbarThumb(trackLen, totalSize, visibleSize, scrollPos, &thumbPos, &thumbSize); int32_t trackRel = relPos - WGT_SB_W; if (trackRel < thumbPos) { return ScrollHitPageDecE; } if (trackRel >= thumbPos + thumbSize) { return ScrollHitPageIncE; } return ScrollHitThumbE; } return ScrollHitNoneE; }