11/*
2- * Copyright (c) 2000, 2024 , Oracle and/or its affiliates. All rights reserved.
2+ * Copyright (c) 2000, 2026 , Oracle and/or its affiliates. All rights reserved.
33 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44 *
55 * This code is free software; you can redistribute it and/or modify it
2626package test .javafx .scene .input ;
2727
2828import com .sun .javafx .scene .SceneHelper ;
29+ import com .sun .javafx .tk .Toolkit ;
30+ import javafx .event .EventType ;
31+ import org .junit .jupiter .api .AfterEach ;
32+ import org .junit .jupiter .api .BeforeEach ;
2933import test .com .sun .javafx .pgstub .StubScene ;
34+ import test .com .sun .javafx .pgstub .StubToolkit ;
3035import test .com .sun .javafx .test .MouseEventGenerator ;
3136import javafx .event .Event ;
3237import javafx .geometry .Point3D ;
4045import javafx .stage .Stage ;
4146
4247import org .junit .jupiter .api .Test ;
48+
49+ import java .util .ArrayList ;
50+ import java .util .List ;
51+
4352import static org .junit .jupiter .api .Assertions .assertEquals ;
4453import static org .junit .jupiter .api .Assertions .assertFalse ;
4554import static org .junit .jupiter .api .Assertions .assertNotNull ;
@@ -51,6 +60,18 @@ public class ScrollEventTest {
5160 private boolean scrolled ;
5261 private boolean scrolled2 ;
5362 private PickResult pickRes ;
63+ private Stage stage ;
64+
65+ @ BeforeEach
66+ public void setUp () {
67+ stage = new Stage ();
68+ stage .show ();
69+ }
70+
71+ @ AfterEach
72+ public void tearDown () {
73+ stage .hide ();
74+ }
5475
5576 @ Test public void testShortConstructor () {
5677 Rectangle node = new Rectangle ();
@@ -821,6 +842,192 @@ public void unknownLocalShouldBeFixedByMousePosition() {
821842 assertFalse (s .isEmpty ());
822843 }
823844
845+ /**
846+ * Verifies that scroll inertia events are retargeted to the picked node when the original gesture target node
847+ * has been removed from the scene graph.
848+ */
849+ @ Test
850+ public void testInertiaScrollRetargetedToPickedNodeWhenTargetRemovedFromScene () {
851+ final StubToolkit toolkit = (StubToolkit ) Toolkit .getToolkit ();
852+
853+ Rectangle background = new Rectangle (0 , 0 , 400 , 400 );
854+ Rectangle scrollTarget = new Rectangle (100 , 100 , 200 , 200 );
855+ Group root = new Group (background , scrollTarget );
856+ Scene scene = new Scene (root , 400 , 400 );
857+ stage .setScene (scene );
858+ toolkit .firePulse ();
859+
860+ List <ScrollEvent > receivedScrollEvents = new ArrayList <>();
861+ scene .addEventFilter (ScrollEvent .SCROLL , receivedScrollEvents ::add );
862+
863+ StubScene stub = (StubScene ) SceneHelper .getPeer (scene );
864+
865+ // 1. Start a scroll gesture over scrollTarget.
866+ scrollEvent (stub , ScrollEvent .SCROLL_STARTED , 200 , 200 , false );
867+
868+ // 2. Send a normal scroll event.
869+ scrollEvent (stub , ScrollEvent .SCROLL , 200 , 200 , false );
870+
871+ assertEquals (1 , receivedScrollEvents .size ());
872+ assertSame (scrollTarget , receivedScrollEvents .getFirst ().getTarget ());
873+
874+ // 3. Finish the scroll event.
875+ scrollEvent (stub , ScrollEvent .SCROLL_FINISHED , 200 , 200 , false );
876+
877+ // 4. Remove the target.
878+ root .getChildren ().remove (scrollTarget );
879+ toolkit .firePulse ();
880+ receivedScrollEvents .clear ();
881+
882+ // 5. Inertia scroll arrives after the target has been removed.
883+ scrollEvent (stub , ScrollEvent .SCROLL , 200 , 200 , true );
884+
885+ assertEquals (1 , receivedScrollEvents .size (),
886+ "Inertia Scroll must be delivered after the original target is removed from the Scene" );
887+ assertSame (background , receivedScrollEvents .getFirst ().getTarget (),
888+ "Inertia Scroll should be retargeted to the Node that was picked at the event coordinates" );
889+ assertTrue (receivedScrollEvents .getFirst ().isInertia ());
890+ }
891+
892+ /**
893+ * Verifies that scroll inertia events are retargeted to the Scene when the original gesture target node
894+ * has been removed from the scene graph and there is no other node to pick underneath.
895+ */
896+ @ Test
897+ public void testInertiaScrollRetargetedToSceneWhenTargetRemovedFromScene () {
898+ final StubToolkit toolkit = (StubToolkit ) Toolkit .getToolkit ();
899+
900+ Rectangle background = new Rectangle (0 , 0 , 400 , 400 );
901+ Group root = new Group (background );
902+ Scene scene = new Scene (root , 400 , 400 );
903+ stage .setScene (scene );
904+ toolkit .firePulse ();
905+
906+ List <ScrollEvent > receivedScrollEvents = new ArrayList <>();
907+ scene .addEventFilter (ScrollEvent .SCROLL , receivedScrollEvents ::add );
908+
909+ StubScene stub = (StubScene ) SceneHelper .getPeer (scene );
910+
911+ // 1. Start a scroll gesture over background.
912+ scrollEvent (stub , ScrollEvent .SCROLL_STARTED , 200 , 200 , false );
913+
914+ // 2. Send a normal scroll event.
915+ scrollEvent (stub , ScrollEvent .SCROLL , 200 , 200 , false );
916+
917+ assertEquals (1 , receivedScrollEvents .size ());
918+ assertSame (background , receivedScrollEvents .getFirst ().getTarget ());
919+
920+ // 3. Finish the scroll event.
921+ scrollEvent (stub , ScrollEvent .SCROLL_FINISHED , 200 , 200 , false );
922+
923+ // 4. Remove the target.
924+ root .getChildren ().remove (background );
925+ toolkit .firePulse ();
926+ receivedScrollEvents .clear ();
927+
928+ // 5. Inertia scroll arrives after the target has been removed.
929+ scrollEvent (stub , ScrollEvent .SCROLL , 200 , 200 , true );
930+
931+ assertEquals (1 , receivedScrollEvents .size (),
932+ "Inertia Scroll must be delivered after the original target is removed from the Scene" );
933+ assertSame (scene , receivedScrollEvents .getFirst ().getTarget (),
934+ "Inertia Scroll should be retargeted to the Scene as there is no Node to pick" );
935+ assertTrue (receivedScrollEvents .getFirst ().isInertia ());
936+ }
937+
938+ /**
939+ * Verifies that scroll events are retargeted to the picked node when the original gesture target node
940+ * has been removed from the scene graph.
941+ */
942+ @ Test
943+ public void testScrollRetargetedToPickedNodeWhenTargetRemovedFromScene () {
944+ final StubToolkit toolkit = (StubToolkit ) Toolkit .getToolkit ();
945+
946+ Rectangle background = new Rectangle (0 , 0 , 400 , 400 );
947+ Rectangle scrollTarget = new Rectangle (100 , 100 , 200 , 200 );
948+ Group root = new Group (background , scrollTarget );
949+ Scene scene = new Scene (root , 400 , 400 );
950+ stage .setScene (scene );
951+ toolkit .firePulse ();
952+
953+ List <ScrollEvent > receivedScrollEvents = new ArrayList <>();
954+ scene .addEventFilter (ScrollEvent .SCROLL , receivedScrollEvents ::add );
955+
956+ StubScene stub = (StubScene ) SceneHelper .getPeer (scene );
957+
958+ // 1. Start a scroll gesture over scrollTarget.
959+ scrollEvent (stub , ScrollEvent .SCROLL_STARTED , 200 , 200 , false );
960+
961+ // 2. Send a normal scroll event.
962+ scrollEvent (stub , ScrollEvent .SCROLL , 200 , 200 , false );
963+
964+ assertEquals (1 , receivedScrollEvents .size ());
965+ assertSame (scrollTarget , receivedScrollEvents .getFirst ().getTarget ());
966+
967+ // 3. Remove the target.
968+ root .getChildren ().remove (scrollTarget );
969+ toolkit .firePulse ();
970+ receivedScrollEvents .clear ();
971+
972+ // 4. Send another normal scroll event.
973+ scrollEvent (stub , ScrollEvent .SCROLL , 200 , 200 , false );
974+
975+ // 3. Finish the scroll event.
976+ scrollEvent (stub , ScrollEvent .SCROLL_FINISHED , 200 , 200 , false );
977+
978+ assertEquals (1 , receivedScrollEvents .size (),
979+ "Scroll must be delivered after the original target is removed from the Scene" );
980+ assertSame (background , receivedScrollEvents .getFirst ().getTarget (),
981+ "Scroll should be retargeted to the Node that was picked at the event coordinates" );
982+ assertFalse (receivedScrollEvents .getFirst ().isInertia ());
983+ }
984+
985+ /**
986+ * Verifies that scroll events are retargeted to the Scene when the original gesture target node
987+ * has been removed from the scene graph and there is no other node to pick underneath.
988+ */
989+ @ Test
990+ public void testScrollRetargetedToSceneWhenTargetRemovedFromScene () {
991+ final StubToolkit toolkit = (StubToolkit ) Toolkit .getToolkit ();
992+
993+ Rectangle background = new Rectangle (0 , 0 , 400 , 400 );
994+ Group root = new Group (background );
995+ Scene scene = new Scene (root , 400 , 400 );
996+ stage .setScene (scene );
997+ toolkit .firePulse ();
998+
999+ List <ScrollEvent > receivedScrollEvents = new ArrayList <>();
1000+ scene .addEventFilter (ScrollEvent .SCROLL , receivedScrollEvents ::add );
1001+
1002+ StubScene stub = (StubScene ) SceneHelper .getPeer (scene );
1003+
1004+ // 1. Start a scroll gesture over background.
1005+ scrollEvent (stub , ScrollEvent .SCROLL_STARTED , 200 , 200 , false );
1006+
1007+ // 2. Send a normal scroll event.
1008+ scrollEvent (stub , ScrollEvent .SCROLL , 200 , 200 , false );
1009+
1010+ assertEquals (1 , receivedScrollEvents .size ());
1011+ assertSame (background , receivedScrollEvents .getFirst ().getTarget ());
1012+
1013+ // 3. Remove the target.
1014+ root .getChildren ().remove (background );
1015+ toolkit .firePulse ();
1016+ receivedScrollEvents .clear ();
1017+
1018+ // 4. Send another normal scroll event.
1019+ scrollEvent (stub , ScrollEvent .SCROLL , 200 , 200 , false );
1020+
1021+ // 3. Finish the scroll event.
1022+ scrollEvent (stub , ScrollEvent .SCROLL_FINISHED , 200 , 200 , false );
1023+
1024+ assertEquals (1 , receivedScrollEvents .size (),
1025+ "Scroll must be delivered after the original target is removed from the Scene" );
1026+ assertSame (scene , receivedScrollEvents .getFirst ().getTarget (),
1027+ "Scroll should be retargeted to the Scene as there is no Node to pick" );
1028+ assertFalse (receivedScrollEvents .getFirst ().isInertia ());
1029+ }
1030+
8241031 private Scene createScene () {
8251032 final Group root = new Group ();
8261033
@@ -837,4 +1044,12 @@ private Scene createScene() {
8371044
8381045 return scene ;
8391046 }
1047+
1048+ private void scrollEvent (StubScene stub , EventType <ScrollEvent > event , double x , double y , boolean inertia ) {
1049+ stub .getListener ().scrollEvent (
1050+ event ,
1051+ 0 , 0 , 0 , 0 , 1 , 1 , 0 , 0 , 0 , 0 , 0 ,
1052+ x , y , x , y ,
1053+ false , false , false , false , false , inertia );
1054+ }
8401055}
0 commit comments