Oliver.O
08/09/2022, 10:16 PMBrowserViewportWindow
for the initial window. It is easy to use, supports resizing, fills the entire browser viewport, sets the window title correctly and does not require a stylesheet. Inspired by code in @Tlaster's PreCompose. If you want to try: Code in 🧵Oliver.O
08/09/2022, 10:17 PM@file:Suppress(
"INVISIBLE_MEMBER",
"INVISIBLE_REFERENCE",
"EXPOSED_PARAMETER_TYPE"
) // WORKAROUND: ComposeWindow and ComposeLayer are internal
import androidx.compose.runtime.Composable
import androidx.compose.ui.window.ComposeWindow
import kotlinx.browser.document
import kotlinx.browser.window
import org.w3c.dom.HTMLCanvasElement
import org.w3c.dom.HTMLStyleElement
import org.w3c.dom.HTMLTitleElement
private const val CANVAS_ELEMENT_ID = "ComposeTarget" // Hardwired into ComposeWindow
/**
* A Skiko/Canvas-based top-level window using the browser's entire viewport. Supports resizing.
*/
fun BrowserViewportWindow(
title: String = "Untitled",
content: @Composable ComposeWindow.() -> Unit
) {
val htmlHeadElement = document.head!!
htmlHeadElement.appendChild(
(document.createElement("style") as HTMLStyleElement).apply {
type = "text/css"
appendChild(
document.createTextNode(
"""
html, body {
overflow: hidden;
margin: 0 !important;
padding: 0 !important;
}
#$CANVAS_ELEMENT_ID {
outline: none;
}
""".trimIndent()
)
)
}
)
fun HTMLCanvasElement.fillViewportSize() {
setAttribute("width", "${window.innerWidth}")
setAttribute("height", "${window.innerHeight}")
}
var canvas = (document.getElementById(CANVAS_ELEMENT_ID) as HTMLCanvasElement).apply {
fillViewportSize()
}
ComposeWindow().apply {
window.addEventListener("resize", {
val newCanvas = canvas.cloneNode(false) as HTMLCanvasElement
canvas.replaceWith(newCanvas)
canvas = newCanvas
val scale = layer.layer.contentScale
newCanvas.fillViewportSize()
layer.layer.attachTo(newCanvas)
layer.layer.needRedraw()
layer.setSize((newCanvas.width / scale).toInt(), (newCanvas.height / scale).toInt())
})
// WORKAROUND: ComposeWindow does not implement `setTitle(title)`
val htmlTitleElement = (
htmlHeadElement.getElementsByTagName("title").item(0)
?: document.createElement("title").also { htmlHeadElement.appendChild(it) }
) as HTMLTitleElement
htmlTitleElement.textContent = title
setContent {
content(this)
}
}
}
Oliver.O
08/09/2022, 10:19 PMimport androidx.compose.material.Text
import org.jetbrains.skiko.wasm.onWasmReady
fun main() {
onWasmReady {
BrowserViewportWindow("My Compose Application") {
Text("Hello Compose for Web/Canvas!")
}
}
}
darkmoon_uk
08/09/2022, 11:05 PMhfhbd
08/10/2022, 3:45 AMGreg Steckman
08/10/2022, 3:54 AMGreg Steckman
08/10/2022, 3:56 AMOliver.O
08/10/2022, 2:08 PMindex.html
. @Greg Steckman You should be able to safely remove styles.css
and the attributes width="1024" height="768"
from the HTML canvas
element for clarity, as [bB]rowserViewportWindow
would override those anyway.
Apart from that, consider the function an easy, slightly hacky, but viable entry point into Compose for Web on Canvas. I hope that as soon as stuff is easy to use, more people will try. The idea of having the entire Compose UI (non-DOM) infrastructure available on the Web is so enticing (productivity-wise, but also performance-wise). Of course, everything is still at an early stage.Greg Steckman
08/10/2022, 2:28 PMOliver.O
08/10/2022, 2:36 PMMichael Paus
08/11/2022, 12:11 PMMichael Paus
08/11/2022, 12:50 PMOliver.O
08/11/2022, 12:55 PMMichael Paus
08/11/2022, 1:51 PMwindow.addEventListener("resize", {
// val newCanvas = canvas.cloneNode(false) as HTMLCanvasElement
// canvas.replaceWith(newCanvas)
// canvas = newCanvas
val scale = layer.layer.contentScale
canvas.fillViewportSize()
layer.layer.attachTo(canvas)
layer.layer.needRedraw()
layer.setSize((canvas.width / scale).toInt(), (canvas.height / scale).toInt())
})
and my app works as before but I am wondering why you had the commented out part in there. Am I missing something?Oliver.O
08/11/2022, 1:59 PMagrosner
08/19/2022, 4:04 PMagrosner
08/19/2022, 4:04 PMthelumiereguy
10/19/2022, 6:51 PMArkadii Ivanov
02/14/2023, 10:30 AMOliver.O
02/14/2023, 10:36 AMArkadii Ivanov
02/14/2023, 10:41 AMMichael Paus
02/14/2023, 1:39 PMval density = LocalDensity.current
and then you can use
val width = with(density) { someWidthInPixels.toDp() }
to get the normal width in dp.
I also have a Mac and I don’t see the problem you describe when using the above code.Arkadii Ivanov
02/14/2023, 1:42 PMMichael Paus
02/14/2023, 1:47 PMArkadii Ivanov
02/14/2023, 2:59 PMMichael Paus
02/14/2023, 3:12 PMArkadii Ivanov
02/14/2023, 3:35 PMBrowserViewportWindow
and index.html
and my project still has the issue. However, I have just cloned and ran PolySpiralMpp
and it works fine. I still can't find the cause.Arkadii Ivanov
02/14/2023, 3:44 PM1.3.0
and Kotlin from 1.8.0
to 1.3.0-alpha01-dev849
and 1.7.20
respectively solves the issue for me. Wondering whether this is a regression or the implementation of BrowserViewportWindow
should be updated to match some internal changes?Michael Paus
02/14/2023, 3:46 PMArkadii Ivanov
02/14/2023, 3:47 PMdarkmoon_uk
02/20/2023, 11:00 AMlayer.layer.contentScale
and setting scale
to 1.0
fixes it on my MacBook too; which begs the question: Is layer.layer.contentScale
even supposed to be related to screen density or was this perhaps a mistaken assumption?darkmoon_uk
02/20/2023, 11:08 AMdarkmoon_uk
02/20/2023, 11:13 AMOliver.O
02/20/2023, 11:51 AMdarkmoon_uk
02/20/2023, 12:20 PMdarkmoon_uk
02/20/2023, 12:30 PMdarkmoon_uk
02/20/2023, 12:30 PMArjan van Wieringen
02/24/2023, 6:22 PMComposeWindow().apply {
window.addEventListener("resize", {
val newCanvas = canvas.cloneNode(false) as HTMLCanvasElement
val density = window.devicePixelRatio.toFloat() // <-- get density
canvas.replaceWith(newCanvas)
canvas = newCanvas
val scale = layer.layer.contentScale
newCanvas.fillViewportSize()
layer.layer.attachTo(newCanvas)
layer.layer.needRedraw()
// and use it here
layer.setSize((newCanvas.width / scale * density).toInt(), (newCanvas.height / scale * density).toInt())
})
Arjan van Wieringen
02/24/2023, 6:27 PMOliver.O
02/24/2023, 6:45 PMwindow.devicePixelRatio = 1
, so that appears to be the differentiator. Thanks for sharing!Oliver.O
02/24/2023, 6:57 PMZeeshan Syed
02/28/2023, 12:57 PMOliver.O
02/28/2023, 1:28 PMZeeshan Syed
03/01/2023, 6:11 AMArjan van Wieringen
03/04/2023, 7:41 AMOliver.O
03/04/2023, 10:56 AMOliver.O
03/05/2023, 9:09 PMChris Sinco [G]
07/01/2023, 10:39 PMLol, scale = 2 and density = 2, so they cancel each other out.... don't know if they are always the same, but I'll keep this for now.Following up on this, I actually had to remove the scale/density calculation. I believe in web, the pixel dimensions returned from window in JavaScript are density independent already. So the resize listener is even simpler
window.addEventListener("resize", {
canvas.fillViewportSize()
layer.layer.attachTo(canvas)
layer.setSize(canvas.width, canvas.height)
layer.layer.needRedraw()
})
Sunil Kumar
07/30/2023, 7:07 AMIrLinkageError: Property accessor 'layer.<get-layer>' can not be called: Private property accessor declared in module <org.jetbrains.compose.ui:ui> can not be accessed in module <compose-instagram-clone-multiplatform:webApp>\n
we are not able to access layer(ComposeLayer) from ComposeWindow now, Is there any workaround for thisOliver.O
07/30/2023, 9:02 AMSunil Kumar
07/31/2023, 7:20 AMOliver.O
07/31/2023, 8:25 AMBrowserViewportWindow
and AFAIK starring it is the easiest way to get notified about updates.Sunil Kumar
08/03/2023, 7:41 AMOliver.O
11/01/2023, 9:32 PMBrowserViewportWindow
is no longer needed: Compose Multiplatform releases since 1.5.0-beta02 contain CanvasBasedWindow
, a fully featured replacement for all functionality formerly provided by BrowserViewportWindow
. It works identically on js
and jsWasm
targets, and is an integral part of Compose Multiplatform. Say goodbye to BrowserViewportWindow
, we had a fun
time. Hello CanvasBasedWindow
! 😃
compose-counting-grid has been updated accordingly, reflecting the latest changes.Chris Athanas
04/04/2024, 10:31 PM<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"> <!-- note: no meta viewport tag here! -->
<title>Compose App</title>
<script>
// Sets the viewport to the correct size for mobile devices
window.addEventListener("load", function(){ onLoad() } );
function onLoad() {
window.setTimeout(function() {
const meta = document.createElement('meta');
meta.name = "viewport";
meta.content = "width=" + (window.screen.width * window.devicePixelRatio) / 2 + ", initial-scale=1";
document.head.appendChild(meta);
}, 0);
}
</script>
Here’s the project: https://github.com/realityexpander/KMPMensAdventure