~rockorager/vaxis#25: 
Scrollbar does not extend to lowest possible position.

Hi,

I am enjoying vaxis, but found that the scrollbar often does NOT cover the lowest possible position. This is NOT something that can be fixed by setting the "Model" values differently (I've done the maths); it is a direct result of the integer-only maths. The fix (just tried it) is to calculate both barH and barTop using float64, and to math.Round and convert back to int only in the for loop that does the actual drawing.

Best, Ingo

Status
REPORTED
Submitter
ingo.lohmar@posteo.net
Assigned to
No-one
Submitted
a month ago
Updated
a month ago
Labels
No labels applied.

~rockorager a month ago

I'd prefer to avoid floating point math, if possible. I think another solution would be to add a check if the last item is in the view. Something like:

if m.Top + m.ViewHeight >= m.TotalHeight {
  // Anchor to bottom
}

Could you try that out and see if it meets your use case?

ingo.lohmar@posteo.net a month ago · edit

On Fri, Jan 31 2025 22:38 (+0000), ~rockorager wrote:

I'd prefer to avoid floating point math, if possible. I think another solution would be to add a check if the last item is in the view. Something like:

if m.Top + m.ViewHeight >= m.TotalHeight {
  // Anchor to bottom
}

Could you try that out and see if it meets your use case?

Understood. I replaced the barTop line by

var barTop int
	if m.Top+m.ViewHeight >= m.TotalHeight {
		// Anchor to bottom
		barTop = h - barH
	} else {
		barTop = m.Top * h / m.TotalHeight
	}

It works (in the sense that the bar reaches the window bottom), although it can look a bit jerky when scrolling longer: the bar might have just reached the penultimate position right before the last line enters the view, and then immediately the bar is moved again.

That would be good enough for me, BUT when I wrote this reply I realized that I could simply use the standard trick for rounding w/o floating point math, just replace the barH and barTop calculation by

barH := (m.ViewHeight*h + m.TotalHeight/2) / m.TotalHeight
	if barH < 1 {
		barH = 1
	}
	barTop := (m.Top*h + m.TotalHeight/2) / m.TotalHeight

which scrolls smoothly from the very top to the very bottom. Hope this can be included.

~rockorager a month ago

It works (in the sense that the bar reaches the window bottom), although it can look a bit jerky when scrolling longer: the bar might have just reached the penultimate position right before the last line enters the view, and then immediately the bar is moved again.

That would be good enough for me, BUT when I wrote this reply I realized that I could simply use the standard trick for rounding w/o floating point math, just replace the barH and barTop calculation by

barH := (m.ViewHeight*h + m.TotalHeight/2) / m.TotalHeight
	if barH < 1 {
		barH = 1
	}
	barTop := (m.Top*h + m.TotalHeight/2) / m.TotalHeight

which scrolls smoothly from the very top to the very bottom. Hope this can be included.

Hmm - I think your original solution of floating point math is nicer. This is a bit too hacky.

Would you be interested in sending in a patch with the floating point code?

ingo.lohmar@posteo.net a month ago · edit

On Sat, Feb 01 2025 00:01 (+0000), ~rockorager wrote:

barH := (m.ViewHeight*h + m.TotalHeight/2) / m.TotalHeight
	if barH < 1 {
		barH = 1
	}
	barTop := (m.Top*h + m.TotalHeight/2) / m.TotalHeight

which scrolls smoothly from the very top to the very bottom. Hope this can be included.

Hmm - I think your original solution of floating point math is nicer. This is a bit too hacky.

Would you be interested in sending in a patch with the floating point code?

That really surprised me --- I think the code is very clean (maybe with a very short comment like

// effectively rounds (m.ViewHeight * h) / m.TotalHeight)

and very much in the spirit of the original code: That code already relied on a "floor" op implicit in integer division, my change turns that into an implied "round" op.

Here is my first version, starting from the same line (needs a "math" import):

barH := float64(m.ViewHeight)*float64(h) / float64(m.TotalHeight)
	if barH < 1 {
		barH = 1
	}
	barTop := float64(m.Top)*float64(h) / float64(m.TotalHeight)

	if m.Character.Grapheme == "" {
		m.Character = defaultChar
	}
	for i := 0; i < int(math.Round(barH)); i += 1 {
		cell := vaxis.Cell{
			Character: m.Character,
			Style:     m.Style,
		}
		win.SetCell(0, int(math.Round(barTop))+i, cell)
	}

It is, by comparison, extremely ugly, not to mention quite wasteful computing-wise. If you insist to use that, I would not want to be credited ;)

Anyway, both ways work, I should be happy either way!

Register here or Log in to comment, or comment via email.