mojira.dev
MC-172989

Chat scrolling is asymmetric for deltas of magnitude less than 1

To reproduce, enable the "Discrete Scrolling" option to guarantee consistent scroll inputs.

1. Run /help to fill the chat
2. Open chat and scroll all the way up
3. Scroll down once
4. Scroll up once
5. Observe that chat position is now one line lower than before

Probably affects other scrollable interfaces as well (the code analysis mentions command suggestions).

Related issues

Comments

violine1101

I'm unable to reproduce this on Windows 10.

migrated

Might be OS X-specific then. Updating description.

pokechu22

I'm able to reproduce this (on Windows). Any of these should be sufficient to reproduce:

  1. Enable discrete scrolling

  2. Set scroll sensitivity to .99 (note: the scroll sensitivity slider refuses to be set to 1.0; it goes between .99 (0.9919250847151082) and 1.02 (1.01634757088168). So, if you've messed with scroll sensitivity in the past and then tried to reset it, it's easy to get into this state.)

  3. Fill chat using /help

  4. Scroll chat to the top

  5. Scroll down once

  6. Scroll up once

  7. Observe that chat position is now one line lower than before

or

  1. Follow steps 1-4 from above

  2. While holding shift (which, as a relic from the past, normally reduces the number of lines scrolled by from 7 to 1), try scrolling down. It will scroll down a line

  3. While holding shift, try scrolling up

  4. No matter how hard you try, it will refuse to go up a line

or

  1. Enable discrete scrolling

  2. Set scroll sensitivity to .14 or less

  3. Fill chat using /help

  4. Scroll chat up a bit using the page up key

  5. Observe that scrolling up does not work, but scrolling down does work

or

  1. Discrete scrolling can be whatever

  2. Set scroll sensitivity to .01

  3. Fill chat using /help

  4. Scroll chat up a bit using the page up key

  5. Observe that scrolling up does not work, but scrolling down does work

Discrete scrolling is not actually required to reproduce this issue, but without it, it does become hardware dependent. If your mouse generates deltas with magnitudes of around .1 in some cases, as is the case for OSX, you'll run into the inability to scroll up (MC-122641). Otherwise, as long as the delta is less than 1 and not an integer when multiplied by 7 (which is pretty much guaranteed to be the case unless the delta was already an integer e.g. from discrete scrolling with a sensitivity of less than 1), these asymmetries will start to show up. And if the sensitivity itself is a weird number less than 1, then it's easy for deltas to take non-integer values.

It's also worth noting that the command suggestions UI has the same issue, except with it always being possible to scroll up and harder to scroll down. It doesn't multiply by 7, so the behavior might be a bit more obvious.

Why does this happen?

The scroll position in the chat UI is an integer for the number of lines, with 0 being scrolled to the topmost line and lower lines being larger numbers.

Scrolling up generates a negative delta, while scrolling down generates a positive delta. When discrete scrolling is enabled, this delta is replaced with its sign (so any negative number goes to -1, and any positive one goes to +1). After this, scroll sensitivity is applied, which means that when discrete scrolling is enabled, scrolling up generates a delta of -sensitivity, while scrolling down generates one of +sensitivity. This is sane behavior (it makes a lot more sense than ignoring scroll sensitivity in the discrete scrolling case, at least).

The code responsible for handling chat scrolling looks like this (inlining from several different files and eliminating irrelevant details such as state related to scrolling when new lines come in):

public void onScroll(double verticalDelta) {
	// This first clamp prevents the asymmetry from showing up if the magnitude
	// is larger than 1 (which prevents issues with discrete scrolling and a
	// scroll sensitivity of 1 or greater (e.g. 1.02))
	verticalDelta = clamp(verticalDelta, -1.0, 1.0);

	if (isMouseOverSuggestions()) {
		// This second clamp is only hit on the command block UI
		verticalDelta = clamp(verticalDelta, -1.0, 1.0);

		// Possibly written as just chatScroll -= verticalDelta;
		// these casts happen implicitly in that case (JLS §15.26.2)
		suggestionScroll = (int)((double)(suggestionScroll) - verticalDelta);
		suggestionScroll = clamp(suggestionScroll, 0, Math.max(suggestionCount - suggestionsToDisplay, 0));
	} else {
		if (!isShiftDown()) {
			verticalDelta = verticalDelta * 7;
		}
		// Possibly written as just chatScroll += verticalDelta;
		// these casts happen implicitly in that case (JLS §15.26.2)
		chatScroll = (int)((double)(chatScroll) + verticalDelta);
		chatScroll = clamp(chatScroll, 0, Math.max(lineCount - linesToDisplay, 0));
	}
}

There's a few things to note. First, a = (int)((double)(a) + b) is equivalent to a += b (with the conversions happening implicitly per JLS §15.26.2). Secondly, conversion from a double to an integer is round-to-0, per JLS §4.2.4 and §5.1.3. This means that adding .99 will leave the scroll position unchanged, but subtracting .1 will change it. Demo. (This assumes a positive scroll position, which is the case here.)

Fix

The asymmetry can be fixed by using chatScroll = Math.round(chatScroll + verticalDelta), as Math.round rounds to the nearest number. However, using Math.round means that if an individual delta is not enough to change lines, it would never change (instead of only working in one direction). (There also would still be asymmetry if the result ends in .5 though (Math.round rounds to the larger number in that case.) Alternatively, the scroll position could be accumulated as a double and the actual position only changed if the value itself is at least ±1 (this is currently done for hotbar scrolling). This does become a bit awkward with the multiplication by 7 for chat lines, though. There are probably several other ways to resolve the asymmetry.

Once the asymmetry is fixed (regardless of how that happens), it should be possible to remove the clamps between -1 and 1, allowing higher sensitivities than 1 to also influence chat/suggestion scrolling (as they do for GUIs).

migrated

(Unassigned)

Confirmed

Input

1.15.2, 20w08a, 1.21.1, 24w40a

Retrieved