On Mon, 7 Oct 2024 15:26:16 GMT, John Hendrikx <jhendr...@openjdk.org> wrote:

>> This change modifies `ScrollPaneBehavior` to only consume keys that are 
>> targetted at it.  As `KeyEvent`s are in almost all cases only intended for 
>> the targetted node (as visually that's where the user expects the keyboard 
>> input to go, as per normal UI rules) consuming key events that bubble up is 
>> simply incorrect.  When the `ScrollPane` is focused directly (it has the 
>> focused border) then it is fine for it to respond to all kinds of keys.
>> 
>> In FX controls normally there is no need to check if a `Control` is focused 
>> (although they probably should **all** do this) as they do not have children 
>> that could have received the Key Events involved, and Key Events are always 
>> sent to the focused Node.  When `ScrollPane` was developed this was not 
>> taken into account, leading to it consuming keys not intended for it.
>> 
>> This fixes several unexpected problems for custom control builders.  A 
>> custom control normally benefits from standard navigation out of the box 
>> (TAB/shift+TAB) and directional keys.  However, this breaks down as soon as 
>> this custom control is positioned within a `ScrollPane`, which is very 
>> surprising behavior and not at all expected.  This makes it harder than 
>> needed for custom control developers to get the standard navigation for the 
>> directional keys, as they would have to specifically capture those keys 
>> before they reach the `ScrollPane` and trigger the correct navigation action 
>> themselves (for which as of this writing there is no public API).
>> 
>> The same goes for all the other keys captured by `ScrollPane` when it does 
>> not have focus, although not as critical as the UP/DOWN/LEFT/RIGHT keys.
>
> John Hendrikx has updated the pull request incrementally with one additional 
> commit since the last revision:
> 
>   Add information about how ScrollPane acts on key presses.

For users to get back to old behavior, some calculations need to be done as 
`ScrollPane` doesn't offer any helpful programmatic methods to scroll the pane 
(which is perhaps something that could be addressed in a separate issue, I 
filed https://bugs.openjdk.org/browse/JDK-8342654).

The most useful helper is one that scroll by a fraction, shown below:

    static void scrollByFraction(ScrollPane scrollPane, double x, double y) {
        Node content = scrollPane.getContent();

        if (content == null) {
            return;
        }

        Bounds viewportBounds = scrollPane.getViewportBounds();
        Bounds layoutBounds = content.getLayoutBounds();

        if (x != 0) {
            double visibleFraction = viewportBounds.getWidth() / 
layoutBounds.getWidth();
            double range = scrollPane.getHmax() - scrollPane.getHmin();
            double scrollFactor = range * visibleFraction / (1 - 
visibleFraction);

            scrollPane.setHvalue(scrollPane.getHvalue() + x * scrollFactor);
        }

        if (y != 0) {
            double visibleFraction = viewportBounds.getHeight() / 
layoutBounds.getHeight();
            double range = scrollPane.getVmax() - scrollPane.getVmin();
            double scrollFactor = range * visibleFraction / (1 - 
visibleFraction);

            scrollPane.setVvalue(scrollPane.getVvalue() + y * scrollFactor);
        }
    }

Then to get something similar to the old behavior back is to install an event 
handler:

        scrollPane.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
            double x = 0;
            double y = 0;

            switch(e.getCode()) {
                case KeyCode.LEFT -> x = -0.1;
                case KeyCode.RIGHT -> x = 0.1;
                case KeyCode.UP -> y = -0.1;
                case KeyCode.DOWN -> y = 0.1;
                case KeyCode.PAGE_UP -> y = -0.9;
                case KeyCode.PAGE_DOWN -> y = 0.9;
                case KeyCode.SPACE -> y = 0.9;
                case KeyCode.HOME -> x = y = Double.NEGATIVE_INFINITY;
                case KeyCode.END -> x = y = Double.POSITIVE_INFINITY;
                default -> {}
            }

            if (x != 0 || y != 0) {
                scrollByFraction(scrollPane, x, y);
                e.consume();
            }
        });

Note that this doesn't exactly replicate the old behavior, but is probably a 
much more useful implementation than what was provided as standard before.

-------------

PR Comment: https://git.openjdk.org/jfx/pull/1582#issuecomment-2425243570

Reply via email to