Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e330704
8090456: focus traversal
andy-goryachev-oracle Sep 3, 2024
1cb4567
whitespace
andy-goryachev-oracle Sep 3, 2024
4f16895
Merge remote-tracking branch 'origin/master' into 8090456.focus.trave…
andy-goryachev-oracle Sep 9, 2024
9011ee7
Merge remote-tracking branch 'origin/master' into 8090456.focus.trave…
andy-goryachev-oracle Sep 11, 2024
984deb9
review comments part 1
andy-goryachev-oracle Sep 11, 2024
37ffa72
Merge remote-tracking branch 'origin/master' into 8090456.focus.trave…
andy-goryachev-oracle Sep 13, 2024
ddd3b28
review comments
andy-goryachev-oracle Sep 13, 2024
de00863
Merge remote-tracking branch 'origin/master' into 8090456.focus.trave…
andy-goryachev-oracle Sep 23, 2024
12a54fe
Merge remote-tracking branch 'origin/master' into 8090456.focus.trave…
andy-goryachev-oracle Sep 24, 2024
02a8d26
whitespace
andy-goryachev-oracle Sep 24, 2024
de70ad4
remove bounds
andy-goryachev-oracle Sep 25, 2024
98af7a6
javadoc
andy-goryachev-oracle Sep 25, 2024
d1f3152
removed traversal event
andy-goryachev-oracle Sep 26, 2024
9e1fa79
Merge remote-tracking branch 'origin/master' into 8090456.focus.trave…
andy-goryachev-oracle Oct 2, 2024
5b31a64
Merge remote-tracking branch 'origin/master' into 8090456.focus.trave…
andy-goryachev-oracle Oct 9, 2024
1296bd4
next in line
andy-goryachev-oracle Oct 11, 2024
e5d0594
focus visible
andy-goryachev-oracle Oct 14, 2024
2977063
Merge remote-tracking branch 'origin/master' into 8090456.focus.trave…
andy-goryachev-oracle Oct 14, 2024
da2cef2
whitespace
andy-goryachev-oracle Oct 14, 2024
31a1149
Merge remote-tracking branch 'origin/master' into 8090456.focus.trave…
andy-goryachev-oracle Oct 18, 2024
0d01e63
cleanup
andy-goryachev-oracle Oct 18, 2024
9caeaa1
Merge remote-tracking branch 'origin/master' into 8090456.focus.trave…
andy-goryachev-oracle Oct 18, 2024
69d5e0a
Merge branch 'master' into 8090456.focus.traversal
andy-goryachev-oracle Aug 6, 2025
efb25f0
rm focus traversal
andy-goryachev-oracle Aug 7, 2025
7c5d0c2
cleanup
andy-goryachev-oracle Aug 7, 2025
6223221
cleanup
andy-goryachev-oracle Aug 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -54,6 +54,7 @@
import javafx.scene.AccessibleRole;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.TraversalDirection;
import javafx.scene.control.CheckMenuItem;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.CustomMenuItem;
Expand All @@ -75,7 +76,7 @@
import com.sun.javafx.scene.NodeHelper;
import com.sun.javafx.scene.control.behavior.TwoLevelFocusPopupBehavior;
import com.sun.javafx.scene.control.skin.Utils;
import com.sun.javafx.scene.traversal.Direction;
import com.sun.javafx.scene.traversal.TraversalUtils;

/**
* This is a the SkinBase for ContextMenu based controls so that the CSS parts
Expand Down Expand Up @@ -540,12 +541,12 @@ private void initialize() {
break;
case DOWN:
// move to the next sibling
move(Direction.NEXT);
move(TraversalDirection.NEXT);
ke.consume();
break;
case UP:
// move to previous sibling
move(Direction.PREVIOUS);
move(TraversalDirection.PREVIOUS);
ke.consume();
break;
case SPACE:
Expand Down Expand Up @@ -707,23 +708,23 @@ private void selectMenuItem() {
});
}

private void move(Direction dir) {
private void move(TraversalDirection dir) {
int startIndex = currentFocusedIndex != -1 ? currentFocusedIndex : itemsContainer.getChildren().size();
requestFocusOnIndex(findSibling(dir, startIndex));
}

private int findSibling(final Direction dir, final int startIndex) {
private int findSibling(final TraversalDirection dir, final int startIndex) {
final int childCount = itemsContainer.getChildren().size();
int i = startIndex;
do {
if (dir.isForward() && i >= childCount - 1) {
if (TraversalUtils.isForward(dir) && i >= childCount - 1) {
// loop to zero
i = 0;
} else if (!dir.isForward() && i == 0) {
} else if (!TraversalUtils.isForward(dir) && i == 0) {
// loop to end
i = childCount - 1;
} else {
i += (dir.isForward() ? 1 : -1);
i += (TraversalUtils.isForward(dir) ? 1 : -1);
}

Node n = itemsContainer.getChildren().get(i);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,34 +25,37 @@

package com.sun.javafx.scene.control;

import static com.sun.javafx.PlatformUtil.isMac;
import static java.time.temporal.ChronoField.DAY_OF_WEEK;
import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
import static java.time.temporal.ChronoUnit.DAYS;
import static java.time.temporal.ChronoUnit.MONTHS;
import static java.time.temporal.ChronoUnit.WEEKS;
import static java.time.temporal.ChronoUnit.YEARS;
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.YearMonth;
import java.time.chrono.ChronoLocalDate;
import java.time.chrono.Chronology;
import java.time.format.DateTimeFormatter;
import java.time.format.DecimalStyle;
import java.time.chrono.Chronology;
import java.time.chrono.ChronoLocalDate;
import java.time.temporal.ChronoUnit;
import java.time.temporal.ValueRange;
import java.time.temporal.WeekFields;
import java.time.YearMonth;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import static java.time.temporal.ChronoField.*;
import static java.time.temporal.ChronoUnit.*;

import com.sun.javafx.scene.control.skin.*;
import javafx.application.Platform;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.WeakChangeListener;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.TraversalDirection;
import javafx.scene.control.Button;
import javafx.scene.control.DatePicker;
import javafx.scene.control.DateCell;
import javafx.scene.control.DatePicker;
import javafx.scene.control.Label;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseButton;
Expand All @@ -61,15 +64,11 @@
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.layout.StackPane;

import javafx.scene.layout.VBox;
import com.sun.javafx.scene.control.skin.Utils;
import com.sun.javafx.scene.control.skin.resources.ControlResources;
import com.sun.javafx.scene.traversal.Direction;
import com.sun.javafx.scene.traversal.TraversalMethod;

import static com.sun.javafx.PlatformUtil.*;
import com.sun.javafx.scene.NodeHelper;
import com.sun.javafx.scene.traversal.TraversalUtils;

/**
* The full content for the DatePicker popup. This class could
Expand Down Expand Up @@ -171,7 +170,7 @@ public DatePickerContent(final DatePicker datePicker) {
if (newFocusOwner == gridPane) {
if (oldFocusOwner instanceof DateCell) {
// Backwards traversal, skip gridPane.
NodeHelper.traverse(gridPane, Direction.PREVIOUS, TraversalMethod.DEFAULT);
TraversalUtils.traverse(gridPane, TraversalDirection.PREVIOUS, false);
} else {
// Forwards traversal, pass focus to day cell.
if (lastFocusedDayCell != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -45,6 +45,10 @@
import javafx.event.EventHandler;
import javafx.event.EventTarget;
import javafx.event.EventType;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
import javafx.scene.control.SkinBase;

/**
Expand Down Expand Up @@ -444,4 +448,60 @@ public void handle(T ev) {
}
}
}

/**
* Adds a disonnectable machinery to listen to {@code control}'s Scene and Scene.focusOwner properties,
* for the purpose of invoking the supplied callback when focused node is in the {@code control}'s hierarchy
* or is the control itself.
*
* @param control the control
* @param callback the callback
*/
public void addSceneFocusOwnerListener(Control control, Consumer<Node> callback) {
items.add(new ChLi<Scene>() {
private ChangeListener<Node> focusListener = (s, p, n) -> {
if (isParent(control, n)) {
callback.accept(n);
}
};

{
control.sceneProperty().addListener(this);
Scene scene = control.getScene();
if (scene != null) {
scene.focusOwnerProperty().addListener(focusListener);
}
}

private static boolean isParent(Node parent, Node n) {
while (n != null) {
if (parent == n) {
return true;
}
n = n.getParent();
}
return false;
}

@Override
public void disconnect() {
control.sceneProperty().removeListener(this);
Scene scene = control.getScene();
if (scene != null) {
scene.focusOwnerProperty().removeListener(focusListener);
}
focusListener = null;
}

@Override
public void changed(ObservableValue<? extends Scene> p, Scene old, Scene scene) {
if (old != null) {
old.focusOwnerProperty().removeListener(focusListener);
}
if (scene != null) {
scene.focusOwnerProperty().addListener(focusListener);
}
}
});
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand All @@ -25,23 +25,20 @@

package com.sun.javafx.scene.control.behavior;

import com.sun.javafx.scene.traversal.Direction;
import javafx.geometry.NodeOrientation;
import javafx.scene.Node;
import javafx.scene.control.DateCell;

import com.sun.javafx.scene.control.DatePickerContent;
import com.sun.javafx.scene.control.inputmap.InputMap;

import java.time.temporal.ChronoUnit;

import static javafx.scene.input.KeyCode.DOWN;
import static javafx.scene.input.KeyCode.ENTER;
import static javafx.scene.input.KeyCode.LEFT;
import static javafx.scene.input.KeyCode.RIGHT;
import static javafx.scene.input.KeyCode.SPACE;
import static javafx.scene.input.KeyCode.UP;
import static javafx.scene.input.KeyEvent.*;
import static javafx.scene.input.KeyEvent.KEY_RELEASED;
import java.time.temporal.ChronoUnit;
import javafx.geometry.NodeOrientation;
import javafx.scene.Node;
import javafx.scene.TraversalDirection;
import javafx.scene.control.DateCell;
import com.sun.javafx.scene.control.DatePickerContent;
import com.sun.javafx.scene.control.inputmap.InputMap;

/**
* Behaviors for LocalDate based cells types. Simply defines methods
Expand All @@ -57,10 +54,10 @@ public DateCellBehavior(DateCell dateCell) {

inputMap = createInputMap();
addDefaultMapping(inputMap,
new InputMap.KeyMapping(UP, e -> traverse(dateCell, Direction.UP)),
new InputMap.KeyMapping(DOWN, e -> traverse(dateCell, Direction.DOWN)),
new InputMap.KeyMapping(LEFT, e -> traverse(dateCell, Direction.LEFT)),
new InputMap.KeyMapping(RIGHT, e -> traverse(dateCell, Direction.RIGHT)),
new InputMap.KeyMapping(UP, e -> traverse(dateCell, TraversalDirection.UP)),
new InputMap.KeyMapping(DOWN, e -> traverse(dateCell, TraversalDirection.DOWN)),
new InputMap.KeyMapping(LEFT, e -> traverse(dateCell, TraversalDirection.LEFT)),
new InputMap.KeyMapping(RIGHT, e -> traverse(dateCell, TraversalDirection.RIGHT)),
new InputMap.KeyMapping(ENTER, KEY_RELEASED, e -> selectDate()),
new InputMap.KeyMapping(SPACE, KEY_RELEASED, e -> selectDate())
);
Expand All @@ -76,7 +73,7 @@ private void selectDate() {
dpc.selectDayCell(cell);
}

public void traverse(final DateCell cell, final Direction dir) {
public void traverse(final DateCell cell, final TraversalDirection dir) {
boolean rtl = (cell.getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT);
DatePickerContent dpc = findDatePickerContent(cell);
if (dpc != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand All @@ -24,23 +24,20 @@
*/
package com.sun.javafx.scene.control.behavior;

import com.sun.javafx.scene.NodeHelper;
import com.sun.javafx.scene.traversal.Direction;
import com.sun.javafx.scene.traversal.TraversalMethod;
import javafx.event.EventTarget;
import javafx.scene.Node;
import com.sun.javafx.scene.control.inputmap.InputMap;
import com.sun.javafx.scene.control.inputmap.KeyBinding;
import javafx.scene.input.KeyEvent;

import java.util.List;

import static com.sun.javafx.scene.control.inputmap.InputMap.*;
import static javafx.scene.input.KeyCode.DOWN;
import static javafx.scene.input.KeyCode.LEFT;
import static javafx.scene.input.KeyCode.RIGHT;
import static javafx.scene.input.KeyCode.TAB;
import static javafx.scene.input.KeyCode.UP;
import java.util.List;
import javafx.event.EventTarget;
import javafx.scene.Node;
import javafx.scene.TraversalDirection;
import javafx.scene.input.KeyEvent;
import com.sun.javafx.scene.control.inputmap.InputMap;
import com.sun.javafx.scene.control.inputmap.InputMap.KeyMapping;
import com.sun.javafx.scene.control.inputmap.KeyBinding;
import com.sun.javafx.scene.traversal.TraversalUtils;

public class FocusTraversalInputMap {

Expand Down Expand Up @@ -87,62 +84,62 @@ public static <N extends Node> InputMap<N> createInputMap(N node) {
*
* @param node The node to traverse on
* @param dir The direction to traverse
* @param method The focus traversal method
* @param focusVisible whether the focused Node should visibly indicate focus
*/
public static void traverse(final Node node, final Direction dir, TraversalMethod method) {
public static void traverse(final Node node, final TraversalDirection dir, boolean focusVisible) {
if (node == null) {
throw new IllegalArgumentException("Attempting to traverse on a null Node. " +
"Most probably a KeyEvent has been fired with a null target specified.");
}
NodeHelper.traverse(node, dir, method);
TraversalUtils.traverse(node, dir, focusVisible);
}

/**
* Calls the focus traversal engine and indicates that traversal should
* go the next focusTraversable Node above the current one.
*/
public static final void traverseUp(KeyEvent e) {
traverse(getNode(e), com.sun.javafx.scene.traversal.Direction.UP, TraversalMethod.KEY);
traverse(getNode(e), TraversalDirection.UP, true);
}

/**
* Calls the focus traversal engine and indicates that traversal should
* go the next focusTraversable Node below the current one.
*/
public static final void traverseDown(KeyEvent e) {
traverse(getNode(e), com.sun.javafx.scene.traversal.Direction.DOWN, TraversalMethod.KEY);
traverse(getNode(e), TraversalDirection.DOWN, true);
}

/**
* Calls the focus traversal engine and indicates that traversal should
* go the next focusTraversable Node left of the current one.
*/
public static final void traverseLeft(KeyEvent e) {
traverse(getNode(e), com.sun.javafx.scene.traversal.Direction.LEFT, TraversalMethod.KEY);
traverse(getNode(e), TraversalDirection.LEFT, true);
}

/**
* Calls the focus traversal engine and indicates that traversal should
* go the next focusTraversable Node right of the current one.
*/
public static final void traverseRight(KeyEvent e) {
traverse(getNode(e), com.sun.javafx.scene.traversal.Direction.RIGHT, TraversalMethod.KEY);
traverse(getNode(e), TraversalDirection.RIGHT, true);
}

/**
* Calls the focus traversal engine and indicates that traversal should
* go the next focusTraversable Node in the focus traversal cycle.
*/
public static final void traverseNext(KeyEvent e) {
traverse(getNode(e), com.sun.javafx.scene.traversal.Direction.NEXT, TraversalMethod.KEY);
traverse(getNode(e), TraversalDirection.NEXT, true);
}

/**
* Calls the focus traversal engine and indicates that traversal should
* go the previous focusTraversable Node in the focus traversal cycle.
*/
public static final void traversePrevious(KeyEvent e) {
traverse(getNode(e), com.sun.javafx.scene.traversal.Direction.PREVIOUS, TraversalMethod.KEY);
traverse(getNode(e), TraversalDirection.PREVIOUS, true);
}

private static Node getNode(KeyEvent e) {
Expand Down
Loading