15
15
*/
16
16
package androidx.compose.ui.awt
17
17
18
+ import androidx.compose.foundation.ScrollState
18
19
import androidx.compose.foundation.layout.Box
20
+ import androidx.compose.foundation.layout.Column
19
21
import androidx.compose.foundation.layout.fillMaxSize
22
+ import androidx.compose.foundation.layout.fillMaxWidth
23
+ import androidx.compose.foundation.layout.height
20
24
import androidx.compose.foundation.layout.requiredSize
21
25
import androidx.compose.foundation.layout.size
22
26
import androidx.compose.foundation.layout.sizeIn
23
27
import androidx.compose.foundation.lazy.LazyColumn
28
+ import androidx.compose.foundation.verticalScroll
24
29
import androidx.compose.material.Text
25
30
import androidx.compose.runtime.Composable
26
31
import androidx.compose.runtime.LaunchedEffect
@@ -31,12 +36,15 @@ import androidx.compose.runtime.saveable.rememberSaveable
31
36
import androidx.compose.runtime.setValue
32
37
import androidx.compose.ui.ExperimentalComposeUiApi
33
38
import androidx.compose.ui.Modifier
39
+ import androidx.compose.ui.background
34
40
import androidx.compose.ui.geometry.Size
41
+ import androidx.compose.ui.graphics.Color
35
42
import androidx.compose.ui.input.pointer.PointerEventType
36
43
import androidx.compose.ui.input.pointer.onPointerEvent
37
44
import androidx.compose.ui.layout.layout
38
45
import androidx.compose.ui.layout.onGloballyPositioned
39
46
import androidx.compose.ui.sendMouseEvent
47
+ import androidx.compose.ui.sendMouseWheelEvent
40
48
import androidx.compose.ui.unit.Constraints
41
49
import androidx.compose.ui.unit.dp
42
50
import androidx.compose.ui.unit.toSize
@@ -49,8 +57,11 @@ import java.awt.BorderLayout
49
57
import java.awt.Dimension
50
58
import java.awt.GraphicsEnvironment
51
59
import java.awt.event.MouseEvent
60
+ import javax.swing.BoxLayout
52
61
import javax.swing.JFrame
53
62
import javax.swing.JPanel
63
+ import javax.swing.JScrollPane
64
+ import javax.swing.ScrollPaneConstants
54
65
import junit.framework.TestCase.assertTrue
55
66
import kotlin.test.assertEquals
56
67
import kotlin.test.assertFalse
@@ -545,4 +556,128 @@ class ComposePanelTest {
545
556
window.dispose()
546
557
}
547
558
}
559
+
560
+ @Test
561
+ fun `ComposePanel propagates unconsumed mouse wheel scroll events to parent` () {
562
+ val originalFlagValue = System .setProperty(" compose.swing.redispatchMouseWheelEvents" , " true" )
563
+ try {
564
+ runApplicationTest {
565
+ val composePanel = ComposePanel ()
566
+ composePanel.preferredSize = Dimension (200 , 200 )
567
+ val scrollState = ScrollState (0 )
568
+ composePanel.setContent {
569
+ Box (Modifier .size(200 .dp).verticalScroll(scrollState).background(Color .Yellow )) {
570
+ Column (Modifier .fillMaxWidth().height(400 .dp)) {
571
+ Text (" Hello World" )
572
+ Text (" Hello World" )
573
+ Text (" Hello World" )
574
+ Text (" Hello World" )
575
+ Text (" Hello World" )
576
+ }
577
+ }
578
+ }
579
+
580
+ val window = JFrame ()
581
+ try {
582
+ window.size = Dimension (200 , 200 )
583
+ val scrollPane = JScrollPane (
584
+ JPanel ().apply {
585
+ layout = BoxLayout (this , BoxLayout .Y_AXIS )
586
+ add(composePanel)
587
+ add(javax.swing.Box .createVerticalStrut(1000 ), BorderLayout .CENTER )
588
+ }
589
+ )
590
+ scrollPane.horizontalScrollBarPolicy = ScrollPaneConstants .HORIZONTAL_SCROLLBAR_NEVER
591
+ window.contentPane.add(scrollPane, BorderLayout .CENTER )
592
+ window.isVisible = true
593
+
594
+ awaitIdle()
595
+
596
+ // Scroll a little and check that compose content was scrolled
597
+ composePanel.sendMouseWheelEvent(wheelRotation = 1.0 )
598
+ awaitIdle()
599
+ assertThat(scrollState.value).isGreaterThan(0 )
600
+
601
+ // Scroll a lot and check that the Swing JScrollPane was scrolled
602
+ // Note that we need two scroll events for now because Compose can't partially consume
603
+ // scroll events. So one event is needed to scroll Compose content to the end, and
604
+ // another one to scroll JScrollPane.
605
+ window.sendMouseWheelEvent(wheelRotation = 1000.0 )
606
+ awaitIdle()
607
+ window.sendMouseWheelEvent(wheelRotation = 1000.0 )
608
+ assertThat(scrollPane.viewport.viewPosition.y).isGreaterThan(0 )
609
+ } finally {
610
+ window.dispose()
611
+ }
612
+ }
613
+ } finally {
614
+ System .setProperty(" compose.swing.redispatchMouseWheelEvents" , originalFlagValue ? : " false" )
615
+ }
616
+ }
617
+
618
+ @Test
619
+ fun `ComposePanel propagates unconsumed mouse wheel scroll events to sibling` () {
620
+ val originalFlagValue = System .setProperty(" compose.swing.redispatchMouseWheelEvents" , " true" )
621
+ try {
622
+ runApplicationTest {
623
+ val composePanel = ComposePanel ()
624
+ val scrollState = ScrollState (0 )
625
+ composePanel.setContent {
626
+ Box (Modifier .size(200 .dp).verticalScroll(scrollState).background(Color .Green )) {
627
+ Column (Modifier .fillMaxWidth().height(400 .dp)) {
628
+ Text (" Hello World" )
629
+ Text (" Hello World" )
630
+ Text (" Hello World" )
631
+ Text (" Hello World" )
632
+ Text (" Hello World" )
633
+ }
634
+ }
635
+ }
636
+
637
+ val container = JPanel (null )
638
+ container.size = Dimension (200 , 200 )
639
+
640
+ val scrollPane = JScrollPane (
641
+ JPanel ().apply {
642
+ layout = BoxLayout (this , BoxLayout .Y_AXIS )
643
+ add(javax.swing.Box .createVerticalStrut(1000 ), BorderLayout .CENTER )
644
+ }
645
+ )
646
+ scrollPane.horizontalScrollBarPolicy = ScrollPaneConstants .HORIZONTAL_SCROLLBAR_NEVER
647
+
648
+ composePanel.size = Dimension (200 , 200 )
649
+ scrollPane.size = Dimension (200 , 400 )
650
+
651
+ val window = JFrame ()
652
+ try {
653
+ window.size = Dimension (200 , 400 )
654
+ container.add(composePanel)
655
+ container.add(scrollPane)
656
+
657
+ window.contentPane.add(container, BorderLayout .CENTER )
658
+ window.isVisible = true
659
+
660
+ awaitIdle()
661
+
662
+ // Scroll a little and check that compose content was scrolled
663
+ composePanel.sendMouseWheelEvent(wheelRotation = 1.0 )
664
+ awaitIdle()
665
+ assertThat(scrollState.value).isGreaterThan(0 )
666
+
667
+ // Scroll a lot and check that the Swing JScrollPane was scrolled
668
+ // Note that we need two scroll events for now because Compose can't partially consume
669
+ // scroll events. So one event is needed to scroll Compose content to the end, and
670
+ // another one to scroll JScrollPane.
671
+ composePanel.sendMouseWheelEvent(wheelRotation = 1000.0 )
672
+ awaitIdle()
673
+ window.sendMouseWheelEvent(wheelRotation = 1000.0 )
674
+ assertThat(scrollPane.viewport.viewPosition.y).isGreaterThan(0 )
675
+ } finally {
676
+ window.dispose()
677
+ }
678
+ }
679
+ } finally {
680
+ System .setProperty(" compose.swing.redispatchMouseWheelEvents" , originalFlagValue ? : " false" )
681
+ }
682
+ }
548
683
}
0 commit comments