Skip to content

Commit 5020c93

Browse files
committed
Fix scrollbar thumb/arrow overlap and clamp issues across all controls
- Reserve arrow slots in geometry so thumb never overlaps arrows - Always show arrows with thumb color regardless of scroll position - Clamp thumb size to track bounds (Math.Clamp) in all controls - Clamp thumb position with Math.Min to prevent rounding overshoot - Fix MultilineEdit vertical helper formula mismatch with inline code
1 parent 68209fd commit 5020c93

4 files changed

Lines changed: 20 additions & 16 deletions

File tree

SharpConsoleUI/Controls/MultilineEdit/MultilineEditControl.Rendering.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -174,12 +174,13 @@ public override void PaintDOM(CharacterBuffer buffer, LayoutRect bounds, LayoutR
174174
// Reserve arrow positions so thumb never overlaps them
175175
int arrowSlots = effectiveViewport >= 3 ? 2 : 0;
176176
int thumbTrackHeight = effectiveViewport - arrowSlots;
177-
vThumbHeight = Math.Max(1, (int)(thumbTrackHeight * ((double)effectiveViewport / Math.Max(1, totalWrappedLineCount))));
177+
vThumbHeight = Math.Clamp((int)(thumbTrackHeight * ((double)effectiveViewport / Math.Max(1, totalWrappedLineCount))), 1, thumbTrackHeight);
178178
vThumbPos = arrowSlots > 0 ? 1 : 0; // start after top arrow
179179
if (totalWrappedLineCount > effectiveViewport)
180180
{
181+
int maxThumbPos = thumbTrackHeight - vThumbHeight;
181182
double scrollRatio = (double)_verticalScrollOffset / (totalWrappedLineCount - effectiveViewport);
182-
vThumbPos += (int)Math.Round((thumbTrackHeight - vThumbHeight) * scrollRatio);
183+
vThumbPos += Math.Min((int)Math.Round(maxThumbPos * scrollRatio), maxThumbPos);
183184
}
184185
}
185186

@@ -509,12 +510,12 @@ public override void PaintDOM(CharacterBuffer buffer, LayoutRect bounds, LayoutR
509510
// Reserve arrow positions so thumb never overlaps them
510511
int hArrowSlots = effectiveWidth >= 3 ? 2 : 0;
511512
int hThumbTrackWidth = effectiveWidth - hArrowSlots;
512-
int thumbWidth = Math.Max(1, (hThumbTrackWidth * hThumbTrackWidth) / Math.Max(1, maxLineLength));
513+
int thumbWidth = Math.Clamp((hThumbTrackWidth * hThumbTrackWidth) / Math.Max(1, maxLineLength), 1, hThumbTrackWidth);
513514
int thumbPos = hArrowSlots > 0 ? 1 : 0;
514515
if (maxLineLength > effectiveWidth)
515516
{
516517
int maxThumbPos = hThumbTrackWidth - thumbWidth;
517-
thumbPos += (int)Math.Round((double)_horizontalScrollOffset / (maxLineLength - effectiveWidth) * maxThumbPos);
518+
thumbPos += Math.Min((int)Math.Round((double)_horizontalScrollOffset / (maxLineLength - effectiveWidth) * maxThumbPos), maxThumbPos);
518519
}
519520

520521
for (int x = 0; x < effectiveWidth; x++)
@@ -616,12 +617,12 @@ private int GetGutterWidth()
616617
// Reserve arrow positions so thumb never overlaps them
617618
int arrowSlots = effectiveViewport >= 3 ? 2 : 0;
618619
int thumbTrackHeight = effectiveViewport - arrowSlots;
619-
int thumbHeight = Math.Max(1, (thumbTrackHeight * thumbTrackHeight) / Math.Max(1, totalLines));
620+
int thumbHeight = Math.Clamp((int)(thumbTrackHeight * ((double)effectiveViewport / Math.Max(1, totalLines))), 1, thumbTrackHeight);
620621
int thumbY = arrowSlots > 0 ? 1 : 0;
621622
if (totalLines > effectiveViewport)
622623
{
623624
int maxThumbPos = thumbTrackHeight - thumbHeight;
624-
thumbY += (int)Math.Round((double)_verticalScrollOffset / (totalLines - effectiveViewport) * maxThumbPos);
625+
thumbY += Math.Min((int)Math.Round((double)_verticalScrollOffset / (totalLines - effectiveViewport) * maxThumbPos), maxThumbPos);
625626
}
626627
return (effectiveViewport, thumbY, thumbHeight);
627628
}
@@ -636,12 +637,12 @@ private int GetGutterWidth()
636637
// Reserve arrow positions so thumb never overlaps them
637638
int arrowSlots = _effectiveWidth >= 3 ? 2 : 0;
638639
int thumbTrackWidth = _effectiveWidth - arrowSlots;
639-
int thumbWidth = Math.Max(1, (thumbTrackWidth * thumbTrackWidth) / Math.Max(1, maxLineLength));
640+
int thumbWidth = Math.Clamp((thumbTrackWidth * thumbTrackWidth) / Math.Max(1, maxLineLength), 1, thumbTrackWidth);
640641
int thumbX = arrowSlots > 0 ? 1 : 0;
641642
if (maxLineLength > _effectiveWidth)
642643
{
643644
int maxThumbPos = thumbTrackWidth - thumbWidth;
644-
thumbX += (int)Math.Round((double)_horizontalScrollOffset / (maxLineLength - _effectiveWidth) * maxThumbPos);
645+
thumbX += Math.Min((int)Math.Round((double)_horizontalScrollOffset / (maxLineLength - _effectiveWidth) * maxThumbPos), maxThumbPos);
645646
}
646647
return (_effectiveWidth, thumbX, thumbWidth);
647648
}

SharpConsoleUI/Controls/ScrollablePanelControl/ScrollablePanelControl.Rendering.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,13 +201,13 @@ public override void PaintDOM(CharacterBuffer buffer, LayoutRect bounds, LayoutR
201201
int arrowSlots = scrollbarHeight >= 3 ? 2 : 0;
202202
int thumbTrackHeight = scrollbarHeight - arrowSlots;
203203
double viewportRatio = (double)_viewportHeight / _contentHeight;
204-
int thumbHeight = Math.Max(1, (int)(thumbTrackHeight * viewportRatio));
204+
int thumbHeight = Math.Clamp((int)(thumbTrackHeight * viewportRatio), 1, thumbTrackHeight);
205205
int thumbY = arrowSlots > 0 ? 1 : 0;
206206
if (_contentHeight > _viewportHeight)
207207
{
208208
double scrollRatio = (double)_verticalScrollOffset / (_contentHeight - _viewportHeight);
209209
int maxThumbPos = thumbTrackHeight - thumbHeight;
210-
thumbY += (int)Math.Round(maxThumbPos * scrollRatio);
210+
thumbY += Math.Min((int)Math.Round(maxThumbPos * scrollRatio), maxThumbPos);
211211
}
212212

213213
return (scrollbarRelX, scrollbarTop, scrollbarHeight, thumbY, thumbHeight);

SharpConsoleUI/Controls/TableControl/TableControl.Scrolling.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -192,10 +192,11 @@ internal bool ShouldShowHorizontalScrollbar(int totalColumnsWidth, int viewportW
192192
if (thumbTrackHeight <= 0) return (trackTop, trackHeight, 0, trackHeight);
193193

194194
double viewportRatio = (double)visibleRows / totalRows;
195-
int thumbHeight = Math.Max(1, (int)(thumbTrackHeight * viewportRatio));
195+
int thumbHeight = Math.Clamp((int)(thumbTrackHeight * viewportRatio), 1, thumbTrackHeight);
196196
double scrollRatio = (double)_scrollOffset / Math.Max(1, totalRows - visibleRows);
197197
int thumbY = arrowSlots > 0 ? 1 : 0; // start after top arrow
198-
thumbY += (int)((thumbTrackHeight - thumbHeight) * scrollRatio);
198+
int maxThumbPos = thumbTrackHeight - thumbHeight;
199+
thumbY += Math.Min((int)(maxThumbPos * scrollRatio), maxThumbPos);
199200

200201
return (trackTop, trackHeight, thumbY, thumbHeight);
201202
}
@@ -216,11 +217,12 @@ internal bool ShouldShowHorizontalScrollbar(int totalColumnsWidth, int viewportW
216217
if (thumbTrackWidth <= 0) return (trackLeft, trackWidth, 0, trackWidth);
217218

218219
double viewportRatio = (double)contentAreaWidth / totalColumnsWidth;
219-
int thumbWidth = Math.Max(1, (int)(thumbTrackWidth * viewportRatio));
220+
int thumbWidth = Math.Clamp((int)(thumbTrackWidth * viewportRatio), 1, thumbTrackWidth);
220221
int maxHScroll = totalColumnsWidth - contentAreaWidth;
221222
double scrollRatio = maxHScroll > 0 ? (double)_horizontalScrollOffset / maxHScroll : 0;
222223
int thumbX = arrowSlots > 0 ? 1 : 0; // start after left arrow
223-
thumbX += (int)((thumbTrackWidth - thumbWidth) * scrollRatio);
224+
int maxThumbPos = thumbTrackWidth - thumbWidth;
225+
thumbX += Math.Min((int)(maxThumbPos * scrollRatio), maxThumbPos);
224226

225227
return (trackLeft, trackWidth, thumbX, thumbWidth);
226228
}

SharpConsoleUI/Helpers/ScrollbarHelper.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,11 @@ public static (int trackTop, int trackHeight, int thumbY, int thumbHeight)
5959
if (thumbTrackHeight <= 0) return (trackTop, trackHeight, 0, trackHeight);
6060

6161
double viewportRatio = (double)visibleItems / totalItems;
62-
int thumbHeight = Math.Max(1, (int)(thumbTrackHeight * viewportRatio));
62+
int thumbHeight = Math.Clamp((int)(thumbTrackHeight * viewportRatio), 1, thumbTrackHeight);
6363
double scrollRatio = (double)scrollOffset / Math.Max(1, totalItems - visibleItems);
6464
int thumbY = arrowSlots > 0 ? 1 : 0; // start after top arrow
65-
thumbY += (int)((thumbTrackHeight - thumbHeight) * scrollRatio);
65+
int maxThumbPos = thumbTrackHeight - thumbHeight;
66+
thumbY += Math.Min((int)(maxThumbPos * scrollRatio), maxThumbPos);
6667

6768
return (trackTop, trackHeight, thumbY, thumbHeight);
6869
}

0 commit comments

Comments
 (0)