Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more info about lwjgl integration #1738

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 50 additions & 1 deletion experimental/lwjgl-integration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,53 @@ Note that:
- not all features are implemented
- not all features are currently supported (Accessibility, Input Methods)
- to pass some event information it is needed to pass it via AWT events (java.awt.KeyEvent and java.awt.MouseEvent). In the future versions of Compose we plan to get rid of the need of AWT events.
- it has bugs (it doesn't show cursor in TextField)



## Problems

### Cursor In TextField
This is easy, you need to provide `androidx.compose.ui.platform.WindowInfo` in CompositionLocal

For example:
```kotlin
CompositionLocalProvider(
LocalWindowInfo provides object : WindowInfo { override val isWindowFocused: Boolean = true }
) {
// Your App
}
```


### Popup
Since Compose still use some AWT events (https://github.com/JetBrains/compose-jb/issues/1736), you can provide a fake contaniner.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Since Compose still use some AWT events (https://github.com/JetBrains/compose-jb/issues/1736), you can provide a fake contaniner.
Since Compose still use some AWT events (https://github.com/JetBrains/compose-jb/issues/1736), you can provide a fake container.


```kotlin
val awtContainer = object : Container() {}

// call it when your custom compose app changes window position
fun onWindowUpdate(x: Int, y: Int, width: Int, height: Int) {
awtContainer.setBounds(x, y, width, height)
}

CompositionLocalProvider(
LocalLayerContainer provides awtContainer
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LocalLayerContainer is internal. Please describe in this doc how you use it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rememberCursorPosition is using AWT to get mouse position, in an app with ComposeSence, currently, the only way to make cursor related feature (ContextMenu, CursorPopup, Tooltip) work is to provide a fake AWT Container and update window bound and position for the fake container, and you need to implement your own window region detection, render scaling, etc.

This can be removed or changed if we could have something LocalMouseLocationProvider mentioned in #1736

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, how do you able to use/access LocalLayerContainer? It is marked as internal, so you can't access it in default Gradle/Kotlin configuration. Do you add some arguments to Kotlin compiler?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I just create a fake class and override in the classpath(Gradle default duplicate strategy should work). My current workaround is to create a package androidx.compose.ui.awt and create a file LocalLayerContainer.desktop.kt

import androidx.compose.runtime.staticCompositionLocalOf
import your.package.awtContainer

val LocalLayerContainer: androidx.compose.runtime.ProvidableCompositionLocal<java.awt.Container> =
    staticCompositionLocalOf { awtContainer }

I am also trying to make a kotlin compiler plugin to remove all access modifiers and make everything public open.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just create a fake class and override in the classpath

Smart :). Could you add this into the doc?

Also, could you describe in ## Problems that this section contains only temporary workarounds, that won't needed when Compose resolves these problems.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. The file should contain the package:
package androidx.compose.ui.awt
  1. Better to write this:
package androidx.compose.ui.awt

import androidx.compose.runtime.compositionLocalOf
import java.awt.Container

internal val LocalLayerContainer = compositionLocalOf<Container> {
    error("CompositionLocal LayerContainer not provided")
}

because we use CompositionLocalProvider to pass awtContainer

  1. I have an exception (on Windows):
Exception in thread "main" java.lang.NullPointerException: graphicsConfiguration must not be null
	at androidx.compose.ui.window.LayoutConfiguration_desktopKt.getDensity(LayoutConfiguration.desktop.kt:39)
	at androidx.compose.ui.window.DesktopPopup_desktopKt.rememberCursorPositionProvider-B5uucgQ(DesktopPopup.desktop.kt:236)
	at androidx.compose.foundation.TooltipPlacement$CursorPoint.positionProvider(TooltipArea.desktop.kt:227)
	at androidx.compose.foundation.TooltipArea_desktopKt.TooltipArea(TooltipArea.desktop.kt:151)

when I try to run:

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.TooltipArea
import androidx.compose.foundation.TooltipPlacement
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Button
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.awt.LocalLayerContainer
import androidx.compose.ui.awt.awtContainer
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun App() {
    CompositionLocalProvider(LocalLayerContainer provides awtContainer) {
        val buttons =
            listOf("Button A", "Button B", "Button C", "Button D", "Button E", "Button F")
        Column(Modifier.fillMaxSize(), Arrangement.spacedBy(5.dp)) {
            buttons.forEachIndexed { index, name ->
                // wrap button in BoxWithTooltip
                TooltipArea(
                    tooltip = {
                        // composable tooltip content
                        Surface(
                            modifier = Modifier.shadow(4.dp),
                            color = Color(255, 255, 210),
                            shape = RoundedCornerShape(4.dp)
                        ) {
                            Text(
                                text = "Tooltip for ${name}",
                                modifier = Modifier.padding(10.dp)
                            )
                        }
                    },
                    modifier = Modifier.padding(start = 40.dp),
                    delayMillis = 600, // in milliseconds
                    tooltipPlacement = TooltipPlacement.CursorPoint(
                        alignment = Alignment.BottomEnd,
                        offset = if (index % 2 == 0) DpOffset(
                            -16.dp,
                            0.dp
                        ) else DpOffset.Zero // tooltip offset
                    )
                ) {
                    Button(onClick = {}) { Text(text = name) }
                }
            }
        }
    }
}

How have you solved it?

  1. Add a note to ## Problems:
This section describes the current issues with LWJGL integration, and suggests temporary workarounds. These workarounds won't be needed when these issues are fixed in Compose.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, weird. I didn't run into a null pointer error (with ComposeSence).

It might be caused by AWT headless property, try System.setProperty("java.awt.headless", "false")

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works for me:

val awtContainer = object : Container() {
    // Note that this can cause some issues with multiple displays with different densities
    // (if some widget relies on the density of this container) 
    override fun getGraphicsConfiguration() =
        GraphicsEnvironment.getLocalGraphicsEnvironment()
            .defaultScreenDevice
            .defaultConfiguration
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can avoid classpath hacks with just using @Suppress("INVISIBLE_MEMBER") on use-site.

) {
// Your App
}
```

`LocalLayerContainer` is internal, so you need to use a trick.

Create a package `androidx.compose.ui.awt` and create a file `LocalLayerContainer.desktop.kt`

Put following code inside
```kotlin
import androidx.compose.runtime.staticCompositionLocalOf
import your.package.yourFakeAwtContainer

val LocalLayerContainer: androidx.compose.runtime.ProvidableCompositionLocal<java.awt.Container> =
staticCompositionLocalOf { yourFakeAwtContainer }
```

Then you would be able to access the internal `LocalLayerContainer`