Skip to content
Open
Show file tree
Hide file tree
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
5 changes: 3 additions & 2 deletions experimental/lwjgl-integration/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import org.jetbrains.compose.compose
import org.jetbrains.compose.desktop.application.dsl.TargetFormat

plugins {
kotlin("jvm") version "1.5.31"
id("org.jetbrains.compose") version "1.0.0"
kotlin("jvm") version "2.1.10"
id("org.jetbrains.kotlin.plugin.compose") version "2.1.10"
id("org.jetbrains.compose") version "1.8.0-alpha02"
}

repositories {
Expand Down
11 changes: 5 additions & 6 deletions experimental/lwjgl-integration/src/main/kotlin/App.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import androidx.compose.foundation.VerticalScrollbar
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollbarAdapter
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
Expand Down
64 changes: 50 additions & 14 deletions experimental/lwjgl-integration/src/main/kotlin/GlfwEvents.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import androidx.compose.ui.ComposeScene
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.InternalComposeUiApi
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.scene.ComposeScene
import androidx.compose.ui.unit.Density
import org.lwjgl.glfw.GLFW.*
import java.awt.Component
Expand All @@ -11,7 +13,7 @@ import java.awt.event.MouseEvent
import java.awt.event.MouseWheelEvent
import java.awt.event.KeyEvent as AwtKeyEvent

@OptIn(ExperimentalComposeUiApi::class)
@OptIn(InternalComposeUiApi::class)
fun ComposeScene.subscribeToGLFWEvents(windowHandle: Long) {
glfwSetMouseButtonCallback(windowHandle) { _, button, action, mods ->
sendPointerEvent(
Expand All @@ -21,23 +23,23 @@ fun ComposeScene.subscribeToGLFWEvents(windowHandle: Long) {
GLFW_RELEASE -> PointerEventType.Release
else -> PointerEventType.Unknown
},
nativeEvent = MouseEvent(getAwtMods(windowHandle))
nativeEvent = MouseEvent(getAwtMods(windowHandle))
)
}

glfwSetCursorPosCallback(windowHandle) { _, xpos, ypos ->
sendPointerEvent(
position = Offset(xpos.toFloat(), ypos.toFloat()),
eventType = PointerEventType.Move,
nativeEvent = MouseEvent(getAwtMods(windowHandle))
nativeEvent = MouseEvent(getAwtMods(windowHandle))
)
}

glfwSetCursorEnterCallback(windowHandle) { _, entered ->
sendPointerEvent(
position = glfwGetCursorPos(windowHandle),
eventType = if (entered) PointerEventType.Enter else PointerEventType.Exit,
nativeEvent = MouseEvent(getAwtMods(windowHandle))
nativeEvent = MouseEvent(getAwtMods(windowHandle))
)
}

Expand All @@ -46,7 +48,7 @@ fun ComposeScene.subscribeToGLFWEvents(windowHandle: Long) {
eventType = PointerEventType.Scroll,
position = glfwGetCursorPos(windowHandle),
scrollDelta = Offset(xoffset.toFloat(), -yoffset.toFloat()),
nativeEvent = MouseWheelEvent(getAwtMods(windowHandle))
nativeEvent = MouseWheelEvent(getAwtMods(windowHandle))
)
}

Expand All @@ -62,13 +64,31 @@ fun ComposeScene.subscribeToGLFWEvents(windowHandle: Long) {

// Note that we don't distinguish between Left/Right Shift, Del from numpad or not, etc.
// To distinguish we should change `location` parameter
sendKeyEvent(KeyEvent(awtId, time, getAwtMods(windowHandle), awtKey, 0.toChar(), AwtKeyEvent.KEY_LOCATION_STANDARD))
sendKeyEvent(
KeyEvent(
awtId,
time,
getAwtMods(windowHandle),
awtKey,
0.toChar(),
AwtKeyEvent.KEY_LOCATION_STANDARD
)
)
}

glfwSetCharCallback(windowHandle) { _, codepoint ->
for (char in Character.toChars(codepoint)) {
val time = System.nanoTime() / 1_000_000
sendKeyEvent(KeyEvent(AwtKeyEvent.KEY_TYPED, time, getAwtMods(windowHandle), 0, char, AwtKeyEvent.KEY_LOCATION_UNKNOWN))
sendKeyEvent(
KeyEvent(
AwtKeyEvent.KEY_TYPED,
time,
getAwtMods(windowHandle),
0,
char,
AwtKeyEvent.KEY_LOCATION_UNKNOWN
)
)
}
}

Expand All @@ -87,8 +107,12 @@ private fun glfwGetCursorPos(window: Long): Offset {
// in the future versions of Compose we plan to get rid of the need of AWT events/components
val awtComponent = object : Component() {}

@OptIn(InternalComposeUiApi::class)
private fun KeyEvent(awtId: Int, time: Long, awtMods: Int, key: Int, char: Char, location: Int) = KeyEvent(
AwtKeyEvent(awtComponent, awtId, time, awtMods, key, char, location)
key = Key(key),
codePoint = char.code,
type = if (awtId == AwtKeyEvent.KEY_PRESSED) KeyEventType.KeyDown else if (awtId == AwtKeyEvent.KEY_RELEASED) KeyEventType.KeyUp else KeyEventType.Unknown,
nativeEvent = AwtKeyEvent(awtComponent, awtId, time, awtMods, key, char, location)
)

private fun MouseEvent(awtMods: Int) = MouseEvent(
Expand All @@ -111,11 +135,23 @@ private fun getAwtMods(windowHandle: Long): Int {
awtMods = awtMods or (1 shl 14)
if (glfwGetMouseButton(windowHandle, GLFW_MOUSE_BUTTON_5) == GLFW_PRESS)
awtMods = awtMods or (1 shl 15)
if (glfwGetKey(windowHandle, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS || glfwGetKey(windowHandle, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS)
if (glfwGetKey(windowHandle, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS || glfwGetKey(
windowHandle,
GLFW_KEY_RIGHT_CONTROL
) == GLFW_PRESS
)
awtMods = awtMods or InputEvent.CTRL_DOWN_MASK
if (glfwGetKey(windowHandle, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS || glfwGetKey(windowHandle, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS)
if (glfwGetKey(windowHandle, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS || glfwGetKey(
windowHandle,
GLFW_KEY_RIGHT_SHIFT
) == GLFW_PRESS
)
awtMods = awtMods or InputEvent.SHIFT_DOWN_MASK
if (glfwGetKey(windowHandle, GLFW_KEY_LEFT_ALT) == GLFW_PRESS || glfwGetKey(windowHandle, GLFW_KEY_RIGHT_ALT) == GLFW_PRESS)
if (glfwGetKey(windowHandle, GLFW_KEY_LEFT_ALT) == GLFW_PRESS || glfwGetKey(
windowHandle,
GLFW_KEY_RIGHT_ALT
) == GLFW_PRESS
)
awtMods = awtMods or InputEvent.ALT_DOWN_MASK
return awtMods
}
}
26 changes: 18 additions & 8 deletions experimental/lwjgl-integration/src/main/kotlin/main.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import androidx.compose.ui.ComposeScene
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.InternalComposeUiApi
import androidx.compose.ui.graphics.asComposeCanvas
import androidx.compose.ui.scene.CanvasLayersComposeScene
import androidx.compose.ui.scene.ComposeScene
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
import org.jetbrains.skia.*
import org.jetbrains.skia.FramebufferFormat.Companion.GR_GL_RGBA8
import org.jetbrains.skiko.FrameDispatcher
Expand All @@ -11,6 +14,7 @@ import org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_BINDING
import org.lwjgl.system.MemoryUtil.NULL
import kotlin.system.exitProcess

@OptIn(InternalComposeUiApi::class)
fun main() {
var width = 640
var height = 480
Expand All @@ -35,9 +39,10 @@ fun main() {
lateinit var composeScene: ComposeScene

fun render() {
surface.canvas.clear(Color.WHITE)
composeScene.constraints = Constraints(maxWidth = width, maxHeight = height)
composeScene.render(surface.canvas, System.nanoTime())
surface ?: return
surface!!.canvas.clear(Color.WHITE)
composeScene.size = IntSize(width, height)
composeScene.render(surface!!.canvas.asComposeCanvas(), System.nanoTime())

context.flush()
glfwSwapBuffers(windowHandle)
Expand All @@ -46,12 +51,17 @@ fun main() {
val frameDispatcher = FrameDispatcher(glfwDispatcher) { render() }

val density = Density(glfwGetWindowContentScale(windowHandle))
composeScene = ComposeScene(glfwDispatcher, density, invalidate = frameDispatcher::scheduleFrame)
composeScene =
CanvasLayersComposeScene(
density = density,
coroutineContext = glfwDispatcher,
invalidate = frameDispatcher::scheduleFrame
)

glfwSetWindowSizeCallback(windowHandle) { _, windowWidth, windowHeight ->
width = windowWidth
height = windowHeight
surface.close()
surface?.close()
surface = createSurface(width, height, context)

glfwSwapInterval(0)
Expand All @@ -71,7 +81,7 @@ fun main() {
exitProcess(0)
}

private fun createSurface(width: Int, height: Int, context: DirectContext): Surface {
private fun createSurface(width: Int, height: Int, context: DirectContext): Surface? {
val fbId = GL11.glGetInteger(GL_FRAMEBUFFER_BINDING)
val renderTarget = BackendRenderTarget.makeGL(width, height, 0, 8, fbId, GR_GL_RGBA8)
return Surface.makeFromBackendRenderTarget(
Expand Down