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
16.15k stars 1.17k forks source link

Crash when I use JFXPanel #519

Closed gtf35 closed 3 years ago

gtf35 commented 3 years ago

I want to use JavaFX in Compose-jb, but Compose-jb only support swing, so I use JFXPanel

But,,,,, kotlin.UninitializedPropertyAccessException: lateinit property layout has not been initialized when I use JFXPanel

My Code

    @Composable
    private fun createMainSwingScreen() {
        SwingPanel(
            background = Color.White,
            modifier = Modifier.fillMaxSize(),
            factory = {
                JFXPanel().also { jfxPanel ->
                    PlatformImpl.startup {

                    }
                }
            }
        )
    }
Error log

    Exception in thread "AWT-EventQueue-0 @coroutine#5" kotlin.UninitializedPropertyAccessException: lateinit property layout has not been initialized
    at androidx.compose.desktop.ComponentInfo.getLayout(SwingPanel.desktop.kt:113)
    at androidx.compose.desktop.SwingPanel_desktopKt$SwingPanel-euL9pac$$inlined$onGloballyPositioned$1.onGloballyPositioned(OnGloballyPositionedModifier.kt:60)
    at androidx.compose.ui.node.LayoutNode.dispatchOnPositionedCallbacks$ui(LayoutNode.kt:1095)
    at androidx.compose.ui.node.OnPositionedDispatcher.dispatchHierarchy(OnPositionedDispatcher.kt:51)
    at androidx.compose.ui.node.OnPositionedDispatcher.dispatchHierarchy(OnPositionedDispatcher.kt:55)
    at androidx.compose.ui.node.OnPositionedDispatcher.dispatch(OnPositionedDispatcher.kt:44)
    at androidx.compose.ui.node.MeasureAndLayoutDelegate.dispatchOnPositionedCallbacks(MeasureAndLayoutDelegate.kt:253)
    at androidx.compose.ui.node.MeasureAndLayoutDelegate.dispatchOnPositionedCallbacks$default(MeasureAndLayoutDelegate.kt:249)
    at androidx.compose.ui.platform.DesktopOwner.measureAndLayout(DesktopOwner.desktop.kt:227)
    at androidx.compose.ui.platform.DesktopOwner.render(DesktopOwner.desktop.kt:207)
    at androidx.compose.ui.platform.DesktopOwners.onFrame(DesktopOwners.desktop.kt:121)
    at androidx.compose.desktop.ComposeLayer$1.onRender(ComposeLayer.desktop.kt:123)
    at org.jetbrains.skiko.SkiaLayer.update$skiko(SkiaLayer.kt:117)
    at org.jetbrains.skiko.redrawer.Direct3DRedrawer.update(Direct3DRedrawer.kt:42)
    at org.jetbrains.skiko.redrawer.Direct3DRedrawer.redrawImmediately(Direct3DRedrawer.kt:37)
    at org.jetbrains.skiko.SkiaLayer$redraw$1.run(SkiaLayer.kt:82)
    at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:316)
    at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:770)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
    at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:740)
    at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
    at java.desktop/java.awt.WaitDispatchSupport$2.run(WaitDispatchSupport.java:188)
    at java.desktop/java.awt.WaitDispatchSupport$4.run(WaitDispatchSupport.java:235)
    at java.desktop/java.awt.WaitDispatchSupport$4.run(WaitDispatchSupport.java:233)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:312)
    at java.desktop/java.awt.WaitDispatchSupport.enter(WaitDispatchSupport.java:233)
    at javafx.embed.swing.JFXPanel.initFx(JFXPanel.java:241)
    at javafx.embed.swing.JFXPanel.(JFXPanel.java:267)
    at top.gtf35.nekohelper.ui.file_manager.FileManagerScreen$createMainSwingScreen$1.invoke(FileManagerScreen.kt:92)
    at top.gtf35.nekohelper.ui.file_manager.FileManagerScreen$createMainSwingScreen$1.invoke(FileManagerScreen.kt:91)
    at androidx.compose.desktop.SwingPanel_desktopKt$SwingPanel$4.invoke(SwingPanel.desktop.kt:84)
    at androidx.compose.desktop.SwingPanel_desktopKt$SwingPanel$4.invoke(SwingPanel.desktop.kt:83)
    at androidx.compose.runtime.DisposableEffectImpl.onRemembered(Effects.kt:81)
    at androidx.compose.runtime.ComposerImpl$RememberEventDispatcher.dispatchRememberObservers(Composer.kt:1344)
    at androidx.compose.runtime.ComposerImpl.applyChanges$runtime(Composer.kt:1409)
    at androidx.compose.runtime.CompositionImpl.applyChanges(Composition.kt:414)
    at androidx.compose.runtime.Recomposer.composeInitial$runtime(Recomposer.kt:699)
    at androidx.compose.runtime.CompositionImpl.setContent(Composition.kt:304)
    at androidx.compose.ui.platform.Wrapper_desktopKt.setContent(Wrapper.desktop.kt:39)
    at androidx.compose.desktop.ComposeLayer.initOwner(ComposeLayer.desktop.kt:257)
    at androidx.compose.desktop.ComposeLayer.access$initOwner(ComposeLayer.desktop.kt:50)
    at androidx.compose.desktop.ComposeLayer$Wrapped.init(ComposeLayer.desktop.kt:83)
    at org.jetbrains.skiko.HardwareLayer.checkIsShowing(HardwareLayer.kt:30)
    at org.jetbrains.skiko.HardwareLayer.access$checkIsShowing(HardwareLayer.kt:7)
    at org.jetbrains.skiko.HardwareLayer$1.hierarchyChanged(HardwareLayer.kt:22)
    at java.desktop/java.awt.Component.processHierarchyEvent(Component.java:6781)
    at java.desktop/java.awt.Component.processEvent(Component.java:6400)
    at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:4990)
    at java.desktop/java.awt.Component.dispatchEvent(Component.java:4822)
    at java.desktop/java.awt.Component.createHierarchyEvents(Component.java:5628)
    at java.desktop/java.awt.Container.createHierarchyEvents(Container.java:1466)
    at java.desktop/java.awt.Container.createHierarchyEvents(Container.java:1466)
    at java.desktop/java.awt.Container.createHierarchyEvents(Container.java:1466)
    at java.desktop/java.awt.Container.createHierarchyEvents(Container.java:1466)
    at java.desktop/java.awt.Container.createHierarchyEvents(Container.java:1466)
    at java.desktop/java.awt.Component.show(Component.java:1680)
    at java.desktop/java.awt.Window.show(Window.java:1056)
    at java.desktop/java.awt.Component.show(Component.java:1717)
    at java.desktop/java.awt.Component.setVisible(Component.java:1664)
    at java.desktop/java.awt.Window.setVisible(Window.java:1028)
    at androidx.compose.desktop.ComposeWindow.setVisible(ComposeWindow.desktop.kt:85)
    at androidx.compose.desktop.AppWindow.show(AppWindow.desktop.kt:446)
    at androidx.compose.desktop.AppWindow.show$default(AppWindow.desktop.kt:432)
    at top.gtf35.nekohelper.ui.file_manager.FileManagerScreen.dispatch(FileManagerScreen.kt:78)
    at top.gtf35.nekohelper.ui.file_manager.FileManagerScreen$dispatch$3.invoke(FileManagerScreen.kt)
    at top.gtf35.nekohelper.ui.file_manager.FileManagerScreen$dispatch$3.invoke(FileManagerScreen.kt)
    at androidx.compose.runtime.RecomposeScopeImpl.compose(RecomposeScopeImpl.kt:91)
    at androidx.compose.runtime.ComposerImpl.recomposeToGroupEnd(Composer.kt:2252)
    at androidx.compose.runtime.ComposerImpl.skipCurrentGroup(Composer.kt:2499)
    at androidx.compose.runtime.ComposerImpl.recompose$runtime(Composer.kt:2625)
    at androidx.compose.runtime.CompositionImpl.recompose(Composition.kt:406)
    at androidx.compose.runtime.Recomposer.performRecompose(Recomposer.kt:724)
    at androidx.compose.runtime.Recomposer.access$performRecompose(Recomposer.kt:100)
    at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:437)
    at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:411)
    at androidx.compose.runtime.BroadcastFrameClock$FrameAwaiter.resume(BroadcastFrameClock.kt:41)
    at androidx.compose.runtime.BroadcastFrameClock.sendFrame(BroadcastFrameClock.kt:62)
    at androidx.compose.ui.platform.DesktopOwners.onFrame(DesktopOwners.desktop.kt:116)
    at androidx.compose.desktop.ComposeLayer$1.onRender(ComposeLayer.desktop.kt:123)
    at org.jetbrains.skiko.SkiaLayer.update$skiko(SkiaLayer.kt:117)
    at org.jetbrains.skiko.redrawer.Direct3DRedrawer.update(Direct3DRedrawer.kt:42)
    at org.jetbrains.skiko.redrawer.Direct3DRedrawer.access$update(Direct3DRedrawer.kt:11)
    at org.jetbrains.skiko.redrawer.Direct3DRedrawer$frameDispatcher$1.invokeSuspend(Direct3DRedrawer.kt:20)
    at org.jetbrains.skiko.redrawer.Direct3DRedrawer$frameDispatcher$1.invoke(Direct3DRedrawer.kt)
    at org.jetbrains.skiko.FrameDispatcher$job$1.invokeSuspend(FrameDispatcher.kt:32)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
    at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:316)
    at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:770)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
    at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:740)
    at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
    at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)
Rsedaikin commented 3 years ago

Hello! It looks like the initialization of the JFXPanel happens before the initialization of the SwingPanel, because both Swing and JavaFX have their own thread to dispatch events. So, if you try the following approach, it should solve your problem:

import androidx.compose.desktop.LocalAppWindow
import androidx.compose.desktop.Window
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.size
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.round
import java.awt.Container
import java.awt.BorderLayout
import javafx.embed.swing.JFXPanel
import javafx.application.Platform
import javafx.scene.Group
import javafx.scene.Scene
import javax.swing.JPanel
import javafx.scene.paint.Color as JFXColor
import javafx.scene.text.Font as JFXFont
import javafx.scene.text.Text as JFXText

fun main() = Window(
        title = "MyApp",
        size = IntSize(600, 550)
) {
    // JavaFX components
    val jfxpanel = remember { JFXPanel() }
    val jfxtext = remember { JFXText() }

    // The current container (depending on how you are using the CFD,
    // this could be ComposeWindow or ComposePanel)
    val container = LocalAppWindow.current.window // ComposeWindow

    val counter = remember { mutableStateOf(0) }
    val inc: () -> Unit = {
        counter.value++
        // update JavaFX text component
        Platform.runLater {
            jfxtext.text = "Welcome JavaFX! ${counter.value}"
        }
    }

    Box(
        modifier = Modifier.fillMaxWidth().height(60.dp).padding(top = 20.dp),
        contentAlignment = Alignment.Center
    ) {
        Text("Counter: ${counter.value}")
    }

    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        Column(
            modifier = Modifier.padding(top = 80.dp, bottom = 20.dp)
        ) {
            Button("1. Compose Button: increment", inc)
            Spacer(modifier = Modifier.height(20.dp))
            // The "Box" is strictly necessary to properly sizing and positioning the JFXPanel container. 
            Box(
                modifier = Modifier.height(200.dp).fillMaxWidth()
            ) {
                JavaFXPanel(
                    root = container,
                    panel = jfxpanel,
                    // function to initialize JFXPanel, Group, Scene
                    onCreate = {
                        Platform.runLater {
                            val root = Group()
                            val scene = Scene(root, JFXColor.GRAY)
                            jfxtext.x = 40.0
                            jfxtext.y = 40.0
                            jfxtext.font = JFXFont(25.0)
                            jfxtext.text = "Welcome JavaFX! ${counter.value}"
                            root.children.add(jfxtext)
                            jfxpanel.scene = scene
                        }
                    }
                )
            }
            Spacer(modifier = Modifier.height(20.dp))
        }
    }
}

@Composable
fun Button(text: String = "", action: (() -> Unit)? = null) {
    Button(
        modifier = Modifier.size(270.dp, 40.dp),
        onClick = { action?.invoke() }
    ) {
        Text(text)
    }
}

@Composable
public fun JavaFXPanel(
    root: Container,
    panel: JFXPanel,
    onCreate: () -> Unit
) {
    val container = remember { JPanel() }
    val density = LocalDensity.current.density

    Layout(
        content = {},
        modifier = Modifier.onGloballyPositioned { childCoordinates ->
            val coordinates = childCoordinates.parentCoordinates!!
            val location = coordinates.localToWindow(Offset.Zero).round()
            val size = coordinates.size
            container.setBounds(
                (location.x / density).toInt(),
                (location.y / density).toInt(),
                (size.width / density).toInt(),
                (size.height / density).toInt()
            )
            container.validate()
            container.repaint()
        },
        measurePolicy = { _, _ ->
            layout(0, 0) {}
        }
    )

    DisposableEffect(Unit) {
        container.apply {
            setLayout(BorderLayout(0, 0))
            add(panel)
        }
        root.add(container)
        onCreate.invoke()
        onDispose {
            root.remove(container)
        }
    }
}
gtf35 commented 3 years ago

Looks so coooool! Running well! Thank you very much for providing the code that is easy to reuse 💖 Thanks a lot!

gtf35 commented 3 years ago

Can JavaFXPanel be used in each item in the Compose list?

That will create a lot of JFXPanel().

Is it okay to do this?

Rsedaikin commented 3 years ago

I think this is ok, but also I think that if you need to create a list from a large number of JavaFX items, you can use one JavaFXPanel as a container for a JavaFX ListView (or any other suitable component, because I am not very good at JavaFX framework) and fill it with JavaFX components with the proper styling to match your current CFD style.

gtf35 commented 3 years ago

I see ~, thanks a lot 💕

ximplia-paolo-pasianot commented 2 years ago

how do i solve "LocalAppWindow.current.window" in compose 1.0.0-beta5?

yuchuangu85 commented 2 years ago

@Rsedaikin
Thank you .

TreeFrogApps commented 2 years ago

@Rsedaikin

Great work with this example - currently using it with a WebView and Mapbox.js - works very well and I'm pleased with the performance.

I have noticed an implicit problem with using the JavaFXPanel.

Scenerio :

1) Display a JavaFXPanel composable using above code. 2) Remove the composable (feasible if replacing window content) 3) DisposableEffect::onDispose called as expected, JPanel which hosts the JFXPanel removed from the ComponentWindow 4) Display the JavaFXPanel again back in the same application session.

Expected : JFXPanel displayed as before Result : JFXPanel is not displayed, only the JFrame

Digging deeper :

It appears the first time you add a JavaFXPanel it will attempt to register a PlatformImpl.FinishListener method call : JFXPanel::registerFinishListener. This is then proxied to PlatformImpl::addListener(finishListener).

So far all good, however when this is called root.remove(container) from the onDispose lambda invocation the JPanel will also notify the JFXPanel that it has been removed from the window. This then invokes JFXPanel::deregisterFinishListener which proxies a call to : PlatformImpl.removeListener(finishListener).

Once this method is called it will remove the listener, which makes sense for the lifecycle of this JFXPanel. However in removing the listener this method is called PlatformImpl::checkIdle. If the PlatformImpl.FinishListener count hits 0 PlatformImpl::tkExit is called and this exits all event loops.

The end result is the compose application is still running but any calls to Platform::runLater do nothing for the rest of the application session.

Workaround :

Register a PlatformImpl.FinishListener manually for the lifetime of the application to prevent the event loop from exiting when finished listeners are unregistered keeping the count of registered listeners at least 1 example :

fun main() = application(exitProcessOnExit = true) {
    val finishListener = object : PlatformImpl.FinishListener {
        override fun idle(implicitExit: Boolean) {}
        override fun exitCalled() {}
    }
    PlatformImpl.addListener(finishListener)

    Window(
        title = "My App",
        icon = icon,
        resizable = false,
        state = WindowState(
            placement = Floating,
            size = size),
        onCloseRequest = {
            PlatformImpl.removeListener(finishListener)
            exitApplication()
         },
        content = { ..... })
}
theone55 commented 2 years ago

It's work when i run the project via IDEA but after build distributive seems like webview doesn't work at all =( Maybe you know how to fix it?

gtf35 commented 2 years ago

It's work when i run the project via IDEA but after build distributive seems like webview doesn't work at all =( Maybe you know how to fix it?

Sorry, I've found that I'll have a lot less trouble using swing , so I've given up on JavaFX

theone55 commented 2 years ago

It's work when i run the project via IDEA but after build distributive seems like webview doesn't work at all =( Maybe you know how to fix it?

Sorry, I've found that I'll have a lot less trouble using swing , so I've given up on JavaFX

but anyway it will use WEbView from JavaFX, or you know some other webview implementations without javafx ? (i wonna create chat with youtube player or site preview inside, but javaXF webview only one solution what i found... =( )

aro311 commented 2 years ago

It's work when i run the project via IDEA but after build distributive seems like webview doesn't work at all =( Maybe you know how to fix it?

Same behavior happens to me. @theone55 Did you find solutions for this?

jasonsparc commented 1 year ago

Hi @aro311 @theone55

It seems that the problem was some ClassNotFoundException. However, Compose simply swallows any exception, which is why your app won't crash and it would simply appear that the WebView didn't load.

To fix that, I followed the tutorial at, Configuring included JDK modules | Native distributions & local execution | JetBrains/compose-jb, and as suggested by that tutorial, I ran the suggestModules gradle task on my project. It suggested that I add modules("java.instrument", "java.net.http", "jdk.jfr", "jdk.jsobject", "jdk.unsupported", "jdk.unsupported.desktop", "jdk.xml.dom") under compose.desktop.application.nativeDistributions.


Demo

Using the provided desktop-template, I made the following modifications to ./build.gradle.kts and ./src/main/kotlin/main.kt, and then I added a new file ./src/main/kotlin/JFXWebView.kt (sources below):

For ./build.gradle.kts:

import org.jetbrains.compose.desktop.application.dsl.TargetFormat

plugins {
    kotlin("jvm")
    id("org.jetbrains.compose")
    id("org.openjfx.javafxplugin") version "0.0.13"
}

repositories {
    mavenCentral()
    maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
    google()
}

javafx {
    version = "19"
    modules("javafx.swing", "javafx.web")
}

dependencies {
    // Note, if you develop a library, you should use compose.desktop.common.
    // compose.desktop.currentOs should be used in launcher-sourceSet
    // (in a separate module for demo project and in testMain).
    // With compose.desktop.common you will also lose @Preview functionality
    implementation(compose.desktop.currentOs)
}

compose.desktop {
    application {
        mainClass = "MainKt"
        nativeDistributions {
            modules("java.instrument", "java.net.http", "jdk.jfr", "jdk.jsobject", "jdk.unsupported", "jdk.unsupported.desktop", "jdk.xml.dom")
            targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
            packageName = "KotlinJvmComposeDesktopApplication"
            packageVersion = "1.0.0"
        }
    }
}

For ./src/main/kotlin/main.kt:

import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Modifier
import androidx.compose.ui.awt.SwingPanel
import androidx.compose.ui.window.singleWindowApplication

fun main() = singleWindowApplication {
     SwingPanel(
          factory = { JFXWebView() },
          modifier = Modifier.fillMaxSize(),
     )
}

For ./src/main/kotlin/JFXWebView.kt:

import javafx.application.Platform
import javafx.embed.swing.JFXPanel
import javafx.scene.Scene
import javafx.scene.web.WebView

/**
  * From, https://stackoverflow.com/a/26028556
  */
class JFXWebView : JFXPanel() {
     init {
          Platform.runLater(::initialiseJavaFXScene)
     }

     private fun initialiseJavaFXScene() {
          val webView = WebView()
          val webEngine = webView.engine
          webEngine.load("https://html5test.com/")
          val scene = Scene(webView)
          setScene(scene)
     }
}

Then run ./gradlew packageDistributionForCurrentOS, install the resulting native distribution, and upon running, you should get something like:

Capture


P.S. Not sure if we should also worry about firewall settings, which may prevent the JavaFX WebView from also working, but then other parts of your app that connects to the internet might also stop working.

Edit: The simplified JFXWebView class is not my original idea. Credits goes to Luke Quinane.

ionull commented 1 year ago

Any idea Apple Sign In shows "Failed to verify your identity. Tray again." with JFX Webview?

Kashif-E commented 1 year ago

This is how i am using webviews, but its not getting disposed properly it opens for the first time but the second time it shows blank screen.


@Composable
fun DesktopWebView(
    modifier: Modifier,
    url: String,
) {
    val jPanel: JPanel = remember { JPanel() }
    val jfxPanel = JFXPanel()

    SwingPanel(
        factory = {
            jfxPanel.apply { buildWebView(url) }
            jPanel.add(jfxPanel)
        },
        modifier = modifier,
    )

    DisposableEffect(url) { onDispose { jPanel.remove(jfxPanel) } }
}

private fun JFXPanel.buildWebView(url: String) {

    Platform.runLater {
        val webView = WebView()
        val webEngine = webView.engine

        // Set the user agent to simulate a browser for YouTube
        webEngine.userAgent =
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"

        // Enable JavaScript support for YouTube embed player
        webEngine.isJavaScriptEnabled = true

        // Load the YouTube video using the embed URL
        webEngine.load(url)
        val scene = Scene(webView)
        setScene(scene)
    }
}
ferren-qf commented 3 months ago

Thanks, the solve my probleam @Rsedaikin

And if someone take version hight, maybe find val container = LocalAppWindow.current.window compile fail

You can replace to val container = this.window

Then is run

@ximplia-paolo-pasianot

okushnikov commented 3 months ago

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