diff --git a/modules/javafx.controls/src/test/java/test/com/sun/javafx/scene/control/infrastructure/MouseEventFirer.java b/modules/javafx.controls/src/test/java/test/com/sun/javafx/scene/control/infrastructure/MouseEventFirer.java index 1699a6f48fe..5b609156efb 100644 --- a/modules/javafx.controls/src/test/java/test/com/sun/javafx/scene/control/infrastructure/MouseEventFirer.java +++ b/modules/javafx.controls/src/test/java/test/com/sun/javafx/scene/control/infrastructure/MouseEventFirer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022, 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 @@ -60,6 +60,9 @@ public final class MouseEventFirer { public MouseEventFirer(EventTarget target) { this.target = target; + // Use the alternative creation path for MouseEvent by default, see JDK-8253769 + this.alternative = true; + // Force the target node onto a stage so that it is accessible if (target instanceof Node) { Node n = (Node)target; diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java b/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java index 22cefa750d3..5264c5bc8f9 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java @@ -360,6 +360,18 @@ private Scene(Parent root, double width, double height, Paint fill, setRoot(root); init(width, height); setFill(fill); + + // Any mouse or touch press on the scene will clear the focusVisible flag of + // the current focus owner, if there is one. + EventHandler pressedHandler = event -> { + Node focusOwner = getFocusOwner(); + if (focusOwner != null) { + getKeyHandler().setFocusVisible(focusOwner, false); + } + }; + + addEventFilter(MouseEvent.MOUSE_PRESSED, pressedHandler); + addEventFilter(TouchEvent.TOUCH_PRESSED, pressedHandler); } static { @@ -4036,7 +4048,9 @@ private PickResult pickNode(PickRay pickRay) { class KeyHandler { boolean focusVisible; - private void setFocusOwner(final Node value) { + private void setFocusOwner(Node value, boolean focusVisible) { + this.focusVisible = focusVisible; + // Cancel IM composition if there is one in progress. // This needs to be done before the focus owner is switched as it // generates event that needs to be delivered to the old focus owner. @@ -4053,6 +4067,7 @@ private void setFocusOwner(final Node value) { } private void setFocusVisible(Node node, boolean focusVisible) { + this.focusVisible = focusVisible; node.focusVisible.set(focusVisible); node.focusVisible.notifyListeners(); } @@ -4100,11 +4115,10 @@ private void process(KeyEvent e) { private void requestFocus(Node node, boolean focusVisible) { if (node == null) { - setFocusOwner(null); + setFocusOwner(null, false); } else if (node.isCanReceiveFocus()) { if (node != getFocusOwner()) { - this.focusVisible = focusVisible; - setFocusOwner(node); + setFocusOwner(node, focusVisible); } else { setFocusVisible(node, focusVisible); } diff --git a/modules/javafx.graphics/src/test/java/test/javafx/scene/FocusTest.java b/modules/javafx.graphics/src/test/java/test/javafx/scene/FocusTest.java index ff0057a3663..919360bc836 100644 --- a/modules/javafx.graphics/src/test/java/test/javafx/scene/FocusTest.java +++ b/modules/javafx.graphics/src/test/java/test/javafx/scene/FocusTest.java @@ -28,6 +28,7 @@ import com.sun.javafx.scene.SceneHelper; import javafx.event.Event; +import javafx.event.EventTarget; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import test.com.sun.javafx.pgstub.StubScene; @@ -41,6 +42,7 @@ import static org.junit.Assert.fail; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import javafx.scene.Group; @@ -49,6 +51,11 @@ import javafx.scene.Scene; import javafx.scene.SceneShim; +import javafx.scene.input.MouseButton; +import javafx.scene.input.MouseEvent; +import javafx.scene.input.PickResult; +import javafx.scene.input.TouchEvent; +import javafx.scene.input.TouchPoint; import javafx.scene.shape.Rectangle; import javafx.stage.Stage; @@ -775,6 +782,25 @@ private void fireTabKeyEvent(Node node) { Event.fireEvent(node, new KeyEvent(KeyEvent.KEY_RELEASED, null, null, KeyCode.TAB, false, false, false, false)); } + private void fireMousePressedEvent(EventTarget target) { + double x = 10, y = 10; + PickResult pickResult = new PickResult(target, x, y); + Event.fireEvent(target, new MouseEvent( + MouseEvent.MOUSE_PRESSED, x, y, x, y, MouseButton.PRIMARY, 1, + false, false, false, false, + true, false, false, + false, false, false, pickResult)); + } + + private void fireTouchPressedEvent(EventTarget target) { + double x = 10, y = 10; + PickResult pickResult = new PickResult(scene, x, y); + Event.fireEvent(target, new TouchEvent( + TouchEvent.TOUCH_PRESSED, + new TouchPoint(0, TouchPoint.State.PRESSED, x, y, x, y, target, pickResult), + Collections.emptyList(), 0, false, false, false, false)); + } + /** * If a node acquires focus by calling {@link Node#requestFocus()}, it does not acquire visible focus. */ @@ -848,6 +874,44 @@ private void fireTabKeyEvent(Node node) { assertNotFocusVisible(node2); } + /** + * When any region of the window is clicked, the focus owner loses visible focus + * even when the focus owner doesn't change. + */ + @Test public void testMousePressedClearsFocusVisible() { + Node node1 = n(), node2 = n(); + Group g = new Group(node1, node2); + scene.setRoot(g); + fireTabKeyEvent(g); + + assertIsFocused(scene, node1); + assertIsFocusVisible(node1); + + fireMousePressedEvent(scene); + + assertIsFocused(scene, node1); + assertNotFocusVisible(node1); + } + + /** + * When any region of the window is touched, the focus owner loses visible focus + * even when the focus owner doesn't change. + */ + @Test public void testTouchPressedClearsFocusVisible() { + Node node1 = n(), node2 = n(); + Group g = new Group(node1, node2); + scene.setRoot(g); + fireTabKeyEvent(g); + + assertIsFocused(scene, node1); + assertIsFocusVisible(node1); + + fireTouchPressedEvent(scene); + + assertIsFocused(scene, node1); + assertNotFocusVisible(node1); + } + /** * When a node acquires focus, the focusWithin property is set on the node * and all of its parents.