JetBrains / compose-multiplatform

Compose Multiplatform, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable.
https://jetbrains.com/lp/compose-multiplatform
Apache License 2.0
15.88k stars 1.15k forks source link

SwingPanel breaks (vertical) scrolling #1062

Open felixdivo opened 3 years ago

felixdivo commented 3 years ago

I implemented lazy scrolling accourding to the tutorial. I'm using org.jetbrains.compose @ 1.0.0-alpha4-build310.

Minimal example to reproduce follows. One cannot scroll while hovering over the SwingPanel but one can if close enough to the scrollbar on the right. (Somehow, the colors are not applied):

import androidx.compose.desktop.DesktopMaterialTheme
import androidx.compose.foundation.VerticalScrollbar
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollbarAdapter
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.awt.SwingPanel
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import androidx.compose.ui.window.rememberWindowState
import com.arkivanov.decompose.ExperimentalDecomposeApi
import com.arkivanov.decompose.extensions.compose.jetbrains.lifecycle.LifecycleController
import com.arkivanov.essenty.lifecycle.LifecycleRegistry
import javax.swing.JLabel

@OptIn(ExperimentalComposeUiApi::class, ExperimentalDecomposeApi::class)
fun main() {
    val lifecycle = LifecycleRegistry()

    application {
        val windowState = rememberWindowState()

        LifecycleController(lifecycle, windowState)

        Window(
            onCloseRequest = ::exitApplication,
            state = windowState,
        ) {

            Surface(modifier = Modifier.fillMaxSize()) {
                DesktopMaterialTheme {
                    actualContent()
                }
            }
        }
    }
}

@Composable
private fun actualContent() {

    Box(modifier = Modifier.fillMaxSize()) {
        val scrollState = rememberLazyListState()

        LazyColumn(Modifier.fillMaxSize(), scrollState) {
            items(25) { scope ->

                Row {
                    SwingPanel(
                        modifier = Modifier
                            .background(Color.Green)
                            .weight(1f)
                            .height(64.dp),
                        factory = {
                            JLabel(scope.toString())
                        },
                    )
                    Spacer(Modifier.width(20.dp).background(Color.Blue))
                }
            }
        }
        VerticalScrollbar(
            modifier = Modifier.fillMaxHeight().align(Alignment.CenterEnd),
            adapter = rememberScrollbarAdapter(scrollState)
        )
    }
}

Should look like this:

image

HuixingWong commented 2 years ago

I implemented lazy scrolling accourding to the tutorial. I'm using org.jetbrains.compose @ 1.0.0-alpha4-build310.

Minimal example to reproduce follows. One cannot scroll while hovering over the SwingPanel but one can if close enough to the scrollbar on the right. (Somehow, the colors are not applied):

import androidx.compose.desktop.DesktopMaterialTheme
import androidx.compose.foundation.VerticalScrollbar
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollbarAdapter
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.awt.SwingPanel
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import androidx.compose.ui.window.rememberWindowState
import com.arkivanov.decompose.ExperimentalDecomposeApi
import com.arkivanov.decompose.extensions.compose.jetbrains.lifecycle.LifecycleController
import com.arkivanov.essenty.lifecycle.LifecycleRegistry
import javax.swing.JLabel

@OptIn(ExperimentalComposeUiApi::class, ExperimentalDecomposeApi::class)
fun main() {
    val lifecycle = LifecycleRegistry()

    application {
        val windowState = rememberWindowState()

        LifecycleController(lifecycle, windowState)

        Window(
            onCloseRequest = ::exitApplication,
            state = windowState,
        ) {

            Surface(modifier = Modifier.fillMaxSize()) {
                DesktopMaterialTheme {
                    actualContent()
                }
            }
        }
    }
}

@Composable
private fun actualContent() {

    Box(modifier = Modifier.fillMaxSize()) {
        val scrollState = rememberLazyListState()

        LazyColumn(Modifier.fillMaxSize(), scrollState) {
            items(25) { scope ->

                Row {
                    SwingPanel(
                        modifier = Modifier
                            .background(Color.Green)
                            .weight(1f)
                            .height(64.dp),
                        factory = {
                            JLabel(scope.toString())
                        },
                    )
                    Spacer(Modifier.width(20.dp).background(Color.Blue))
                }
            }
        }
        VerticalScrollbar(
            modifier = Modifier.fillMaxHeight().align(Alignment.CenterEnd),
            adapter = rememberScrollbarAdapter(scrollState)
        )
    }
}

Should look like this:

image I also encountered this problem, do you have any solution?

felixdivo commented 2 years ago

Unfortunately, no. In addition, I found another bug where the SwingPanel is overdrawn over its borders while scrolling.

All these bugs (I opened a few regarding the SwingPanel) are really problematic for my project but I did not find a solution yet.

HuixingWong commented 2 years ago

Unfortunately, no. In addition, I found another bug where the SwingPanel is overdrawn over its borders while scrolling.

All these bugs (I opened a few regarding the SwingPanel) are really problematic for my project but I did not find a solution yet.

I found it possible to pass scrolling through the interface by adding a mouse listener inside swing, but it's not elegant enough

felixdivo commented 2 years ago

@HuixingWong Could you share a snippet of how you did that?

felixdivo commented 2 years ago

I found another bug where the SwingPanel is overdrawn over its borders while scrolling

I opened #1202 for that, btw.

HuixingWong commented 2 years ago

@HuixingWong Could you share a snippet of how you did that?

https://gist.github.com/HuixingWong/56608736a0a08e84ac7a75d20c4af15e maybe u can try this code

felixdivo commented 2 years ago

Ah I see.

but it's not elegant enough

🤷

rwst commented 1 year ago

@HuixingWong Could you share a snippet of how you did that?

https://gist.github.com/HuixingWong/56608736a0a08e84ac7a75d20c4af15e maybe u can try this code

That gist is no longer available, apparently.

dima-avdeev-jb commented 12 months ago

@rwst Gist content:

val listState = rememberLazyListState()

        val scope = rememberCoroutineScope()

        LazyColumn(
            state = listState
        ) {
            for (i in 0..10) {
                item {
                    SwingPanel(
                        background = Color.Red,
                        modifier = Modifier.width(300.dp).height(300.dp),
                        factory = {
                            JButton().apply {
                                addMouseWheelListener {
                                    scope.launch {
                                        listState.scrollBy(
                                            it.unitsToScroll.toFloat()
                                        )
                                    }
                                }
                            }
                        }
                    )
                }
            }
        }
dima-avdeev-jb commented 12 months ago

Anothere one reproducer of this bug:

import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.awt.SwingPanel
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import kotlinx.coroutines.delay
import java.awt.BorderLayout
import javax.swing.JPanel
import javax.swing.JTextArea

fun main() = application {
    Window(onCloseRequest = ::exitApplication) {
        App()
    }
}

@Composable
fun App() {
    Scaffold(topBar = { TopAppBar { Text("TopAppBar") } }) {
        val state = rememberLazyListState()
        LaunchedEffect(Unit) {
            while (true) {
                delay(16)
                state.scrollBy(1f)
            }
        }
        LazyColumn(Modifier, state) {
            items(1000) {
                val panel = remember {
                    JPanel().apply {
                        layout = BorderLayout()
                        add(JTextArea("JPanel $it"))
                    }
                }
                Row {
                    SwingPanel(
                        factory = { panel },
                        modifier = Modifier.height(panel.preferredSize.height.dp).fillMaxWidth(0.5f),
                    )
                    Box(Modifier.fillMaxWidth().height(panel.preferredSize.height.dp).background(Color.Blue))
                }
            }
        }
    }
}
okushnikov commented 1 month ago

Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.