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

CfW: let ComposeWindow accept a custom canvas id (html canvas element id) #626

Merged
merged 2 commits into from
Jul 7, 2023
Merged
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
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
package androidx.compose.mpp.demo

import androidx.compose.runtime.remember
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.window.CanvasBasedWindow
import androidx.compose.ui.window.Window
import kotlinx.browser.document
import org.jetbrains.skiko.GenericSkikoView
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.wasm.onWasmReady
import org.w3c.dom.HTMLCanvasElement

@OptIn(ExperimentalComposeUiApi::class)
fun main() {
onWasmReady {
Window("Compose/JS sample") {
CanvasBasedWindow("Compose/JS sample", canvasElementId = "canvas1") {
val app = remember { App() }
app.Content()
}
Expand Down
11 changes: 10 additions & 1 deletion compose/mpp/demo/src/jsMain/resources/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,21 @@
<title>compose multiplatform web demo</title>
<script src="skiko.js"> </script>
<link type="text/css" rel="stylesheet" href="styles.css">
<style>
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>

<body>
<h1>compose multiplatform web demo</h1>
<div>
<canvas id="ComposeTarget" width="800" height="600"></canvas>
<canvas id="canvas1" width="800" height="600"></canvas>
</div>
<script src="demo.js"> </script>
</body>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2022 The Android Open Source Project
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,19 +17,28 @@
package androidx.compose.ui.window

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.createSkiaLayer
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.native.ComposeLayer
import androidx.compose.ui.platform.JSTextInputService
import androidx.compose.ui.platform.Platform
import androidx.compose.ui.platform.ViewConfiguration
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import kotlinx.browser.document
import kotlinx.browser.window
import kotlinx.coroutines.isActive
import org.w3c.dom.HTMLCanvasElement
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.Channel.Factory.CONFLATED
import kotlinx.coroutines.delay

internal actual class ComposeWindow actual constructor() {
internal actual class ComposeWindow(val canvasId: String) {

actual constructor(): this(defaultCanvasElementId)

private val density: Density = Density(
density = window.devicePixelRatio.toFloat(),
Expand All @@ -53,14 +62,22 @@ internal actual class ComposeWindow actual constructor() {
input = jsTextInputService.input
)

// TODO: generalize me.
val canvas = document.getElementById("ComposeTarget") as HTMLCanvasElement
val canvas = document.getElementById(canvasId) as HTMLCanvasElement

init {
layer.layer.attachTo(canvas)
canvas.setAttribute("tabindex", "0")
layer.layer.needRedraw()

layer.setSize(canvas.width, canvas.height)
}

fun resize(newSize: IntSize) {
canvas.width = newSize.width
canvas.height = newSize.height
layer.layer.attachTo(canvas)
layer.setSize(canvas.width, canvas.height)
layer.layer.needRedraw()
}

/**
Expand All @@ -82,3 +99,64 @@ internal actual class ComposeWindow actual constructor() {
layer.dispose()
}
}

private val defaultCanvasElementId = "ComposeTarget"

@ExperimentalComposeUiApi
/**
* EXPERIMENTAL! Might be deleted or changed in the future!
*
* Initializes the composition in HTML canvas identified by [canvasElementId].
*
* It can be resized by providing [requestResize].
* By default, it will listen to the window resize events.
*/
fun CanvasBasedWindow(
title: String = "JetpackNativeWindow",
canvasElementId: String = defaultCanvasElementId,
requestResize: (suspend () -> IntSize)? = null,
content: @Composable () -> Unit = { }
) {

val actualRequestResize: suspend () -> IntSize = if (requestResize != null) {
requestResize
} else {
// we use Channel instead of suspendCancellableCoroutine,
// because we want to drop old resize events
val channel = Channel<IntSize>(capacity = CONFLATED)

// we subscribe to 'resize' only once and never unsubscribe,
// because the default behaviour expects that the Canvas takes the entire window space,
// so the app has the same lifecycle as the browser tab.
window.addEventListener("resize", { _ ->
dima-avdeev-jb marked this conversation as resolved.
Show resolved Hide resolved
val w = document.documentElement?.clientWidth ?: 0
val h = document.documentElement?.clientHeight ?: 0
channel.trySend(IntSize(w, h))
})

suspend {
channel.receive()
}
}

if (requestResize == null) {
(document.getElementById(canvasElementId) as? HTMLCanvasElement)?.let {
it.width = document.documentElement?.clientWidth ?: 0
it.height = document.documentElement?.clientHeight ?: 0
}
}

ComposeWindow(canvasId = canvasElementId).apply {
val composeWindow = this
setContent {
content()
LaunchedEffect(Unit) {
while (isActive) {
val newSize = actualRequestResize()
composeWindow.resize(newSize)
delay(100) // throttle
}
}
}
}
}