15
15
*/
16
16
package androidx.compose.ui.awt
17
17
18
+ import androidx.compose.foundation.ScrollState
18
19
import androidx.compose.foundation.focusable
19
20
import androidx.compose.foundation.layout.Box
21
+ import androidx.compose.foundation.layout.Column
20
22
import androidx.compose.foundation.layout.fillMaxSize
23
+ import androidx.compose.foundation.layout.fillMaxWidth
24
+ import androidx.compose.foundation.layout.height
21
25
import androidx.compose.foundation.layout.requiredSize
22
26
import androidx.compose.foundation.layout.size
23
27
import androidx.compose.foundation.layout.sizeIn
24
28
import androidx.compose.foundation.lazy.LazyColumn
25
29
import androidx.compose.foundation.text.input.rememberTextFieldState
30
+ import androidx.compose.foundation.verticalScroll
26
31
import androidx.compose.material.Text
27
32
import androidx.compose.material3.TextField
28
33
import androidx.compose.runtime.Composable
@@ -51,6 +56,7 @@ import androidx.compose.ui.layout.onGloballyPositioned
51
56
import androidx.compose.ui.sendCharTypedEvents
52
57
import androidx.compose.ui.sendKeyEvent
53
58
import androidx.compose.ui.sendMouseEvent
59
+ import androidx.compose.ui.sendMouseWheelEvent
54
60
import androidx.compose.ui.unit.Constraints
55
61
import androidx.compose.ui.unit.dp
56
62
import androidx.compose.ui.unit.toSize
@@ -65,8 +71,11 @@ import java.awt.BorderLayout
65
71
import java.awt.Dimension
66
72
import java.awt.GraphicsEnvironment
67
73
import java.awt.event.MouseEvent
74
+ import javax.swing.BoxLayout
68
75
import javax.swing.JFrame
69
76
import javax.swing.JPanel
77
+ import javax.swing.JScrollPane
78
+ import javax.swing.ScrollPaneConstants
70
79
import junit.framework.TestCase.assertTrue
71
80
import kotlin.test.assertEquals
72
81
import kotlin.test.assertFalse
@@ -713,4 +722,113 @@ class ComposePanelTest {
713
722
}
714
723
}
715
724
725
+ @Test
726
+ fun `ComposePanel propagates unconsumed mouse wheel scroll events to parent` () = runApplicationTest {
727
+ val composePanel = ComposePanel ()
728
+ composePanel.preferredSize = Dimension (200 , 200 )
729
+ val scrollState = ScrollState (0 )
730
+ composePanel.setContent {
731
+ Box (Modifier .size(200 .dp).verticalScroll(scrollState).background(Color .Yellow )) {
732
+ Column (Modifier .fillMaxWidth().height(400 .dp)) {
733
+ Text (" Hello World" )
734
+ Text (" Hello World" )
735
+ Text (" Hello World" )
736
+ Text (" Hello World" )
737
+ Text (" Hello World" )
738
+ }
739
+ }
740
+ }
741
+
742
+ val window = JFrame ()
743
+ try {
744
+ window.size = Dimension (200 , 200 )
745
+ val scrollPane = JScrollPane (
746
+ JPanel ().apply {
747
+ layout = BoxLayout (this , BoxLayout .Y_AXIS )
748
+ add(composePanel)
749
+ add(javax.swing.Box .createVerticalStrut(1000 ), BorderLayout .CENTER )
750
+ }
751
+ )
752
+ scrollPane.horizontalScrollBarPolicy = ScrollPaneConstants .HORIZONTAL_SCROLLBAR_NEVER
753
+ window.contentPane.add(scrollPane, BorderLayout .CENTER )
754
+ window.isVisible = true
755
+
756
+ awaitIdle()
757
+
758
+ // Scroll a little and check that compose content was scrolled
759
+ composePanel.sendMouseWheelEvent(wheelRotation = 1.0 )
760
+ awaitIdle()
761
+ assertThat(scrollState.value).isGreaterThan(0 )
762
+
763
+ // Scroll a lot and check that the Swing JScrollPane was scrolled
764
+ // Note that we need two scroll events for now because Compose can't partially consume
765
+ // scroll events. So one event is needed to scroll Compose content to the end, and
766
+ // another one to scroll JScrollPane.
767
+ window.sendMouseWheelEvent(wheelRotation = 1000.0 )
768
+ awaitIdle()
769
+ window.sendMouseWheelEvent(wheelRotation = 1000.0 )
770
+ assertThat(scrollPane.viewport.viewPosition.y).isGreaterThan(0 )
771
+ } finally {
772
+ window.dispose()
773
+ }
774
+ }
775
+
776
+ @Test
777
+ fun `ComposePanel propagates unconsumed mouse wheel scroll events to sibling` () = runApplicationTest {
778
+ val composePanel = ComposePanel ()
779
+ val scrollState = ScrollState (0 )
780
+ composePanel.setContent {
781
+ Box (Modifier .size(200 .dp).verticalScroll(scrollState).background(Color .Green )) {
782
+ Column (Modifier .fillMaxWidth().height(400 .dp)) {
783
+ Text (" Hello World" )
784
+ Text (" Hello World" )
785
+ Text (" Hello World" )
786
+ Text (" Hello World" )
787
+ Text (" Hello World" )
788
+ }
789
+ }
790
+ }
791
+
792
+ val container = JPanel (null )
793
+ container.size = Dimension (200 , 200 )
794
+
795
+ val scrollPane = JScrollPane (
796
+ JPanel ().apply {
797
+ layout = BoxLayout (this , BoxLayout .Y_AXIS )
798
+ add(javax.swing.Box .createVerticalStrut(1000 ), BorderLayout .CENTER )
799
+ }
800
+ )
801
+ scrollPane.horizontalScrollBarPolicy = ScrollPaneConstants .HORIZONTAL_SCROLLBAR_NEVER
802
+
803
+ composePanel.size = Dimension (200 , 200 )
804
+ scrollPane.size = Dimension (200 , 400 )
805
+
806
+ val window = JFrame ()
807
+ try {
808
+ window.size = Dimension (200 , 400 )
809
+ container.add(composePanel)
810
+ container.add(scrollPane)
811
+
812
+ window.contentPane.add(container, BorderLayout .CENTER )
813
+ window.isVisible = true
814
+
815
+ awaitIdle()
816
+
817
+ // Scroll a little and check that compose content was scrolled
818
+ composePanel.sendMouseWheelEvent(wheelRotation = 1.0 )
819
+ awaitIdle()
820
+ assertThat(scrollState.value).isGreaterThan(0 )
821
+
822
+ // Scroll a lot and check that the Swing JScrollPane was scrolled
823
+ // Note that we need two scroll events for now because Compose can't partially consume
824
+ // scroll events. So one event is needed to scroll Compose content to the end, and
825
+ // another one to scroll JScrollPane.
826
+ composePanel.sendMouseWheelEvent(wheelRotation = 1000.0 )
827
+ awaitIdle()
828
+ window.sendMouseWheelEvent(wheelRotation = 1000.0 )
829
+ assertThat(scrollPane.viewport.viewPosition.y).isGreaterThan(0 )
830
+ } finally {
831
+ window.dispose()
832
+ }
833
+ }
716
834
}
0 commit comments