Browse Source

Add Falling Balls MPP example

Author:Alexander Gorshenev <alexander.gorshenev@jetbrains.com>
Co-authored-by: Alexey Tsvetkov <alexey.tsvetkov@jetbrains.com>
Co-authored-by: Nikolay Igotti <igotti@gmail.com>
pull/1658/head
Alexander Gorshenev 3 years ago committed by Alexey Tsvetkov
parent
commit
b98c88d6fe
  1. 243
      examples/falling-balls-mpp/build.gradle.kts
  2. 5
      examples/falling-balls-mpp/gradle.properties
  3. BIN
      examples/falling-balls-mpp/gradle/wrapper/gradle-wrapper.jar
  4. 5
      examples/falling-balls-mpp/gradle/wrapper/gradle-wrapper.properties
  5. 185
      examples/falling-balls-mpp/gradlew
  6. 29
      examples/falling-balls-mpp/project.yml
  7. 12
      examples/falling-balls-mpp/settings.gradle.kts
  8. 92
      examples/falling-balls-mpp/src/commonMain/kotlin/fallingBalls/FallingBalls.kt
  9. 72
      examples/falling-balls-mpp/src/commonMain/kotlin/fallingBalls/Game.kt
  10. 56
      examples/falling-balls-mpp/src/commonMain/kotlin/fallingBalls/Piece.kt
  11. 34
      examples/falling-balls-mpp/src/desktopMain/kotlin/main.desktop.kt
  12. 27
      examples/falling-balls-mpp/src/jsMain/kotlin/main.js.kt
  13. 32
      examples/falling-balls-mpp/src/jsMain/resources/index.html
  14. 8
      examples/falling-balls-mpp/src/jsMain/resources/styles.css
  15. 27
      examples/falling-balls-mpp/src/macosMain/kotlin/main.macos.kt
  16. 62
      examples/falling-balls-mpp/src/uikitMain/kotlin/main.uikit.kt

243
examples/falling-balls-mpp/build.gradle.kts

@ -0,0 +1,243 @@
import org.jetbrains.compose.compose
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension
buildscript {
repositories {
mavenLocal()
mavenCentral()
google()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
}
plugins {
kotlin("multiplatform") version "1.6.10"
id("org.jetbrains.compose") version "1.1.0-beta04"
}
version = "1.0-SNAPSHOT"
val resourcesDir = "$buildDir/resources"
val skikoWasm by configurations.creating
dependencies {
skikoWasm("org.jetbrains.skiko:skiko-js-wasm-runtime:0.6.9")
}
val unzipTask = tasks.register("unzipWasm", Copy::class) {
destinationDir = file(resourcesDir)
from(skikoWasm.map { zipTree(it) })
}
repositories {
mavenLocal()
mavenCentral()
google()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
kotlin {
jvm("desktop")
js(IR) {
browser()
binaries.executable()
}
macosX64 {
binaries {
executable {
entryPoint = "main"
freeCompilerArgs += listOf(
"-linker-option", "-framework", "-linker-option", "Metal"
)
}
}
}
macosArm64 {
binaries {
executable {
entryPoint = "main"
freeCompilerArgs += listOf(
"-linker-option", "-framework", "-linker-option", "Metal"
)
}
}
}
iosX64("uikitX64") {
binaries {
executable() {
entryPoint = "main"
freeCompilerArgs += listOf(
"-linker-option", "-framework", "-linker-option", "Metal",
"-linker-option", "-framework", "-linker-option", "CoreText",
"-linker-option", "-framework", "-linker-option", "CoreGraphics"
)
// TODO: the current compose binary surprises LLVM, so disable checks for now.
freeCompilerArgs += "-Xdisable-phases=VerifyBitcode"
}
}
}
iosArm64("uikitArm64") {
binaries {
executable() {
entryPoint = "main"
freeCompilerArgs += listOf(
"-linker-option", "-framework", "-linker-option", "Metal",
"-linker-option", "-framework", "-linker-option", "CoreText",
"-linker-option", "-framework", "-linker-option", "CoreGraphics"
)
// TODO: the current compose binary surprises LLVM, so disable checks for now.
freeCompilerArgs += "-Xdisable-phases=VerifyBitcode"
}
}
}
sourceSets {
val commonMain by getting {
dependencies {
implementation(compose.ui)
implementation(compose.foundation)
implementation(compose.material)
implementation(compose.runtime)
implementation("org.jetbrains.skiko:skiko:0.6.7")
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
}
}
val desktopMain by getting {
dependencies {
implementation(compose.desktop.currentOs)
}
}
val jsMain by getting {
resources.setSrcDirs(resources.srcDirs)
resources.srcDirs(unzipTask.map { it.destinationDir })
}
val nativeMain by creating {
dependsOn(commonMain)
}
val macosMain by creating {
dependsOn(nativeMain)
}
val macosX64Main by getting {
dependsOn(macosMain)
}
val macosArm64Main by getting {
dependsOn(macosMain)
}
val uikitMain by creating {
dependsOn(nativeMain)
}
val uikitX64Main by getting {
dependsOn(uikitMain)
}
val uikitArm64Main by getting {
dependsOn(uikitMain)
}
}
}
compose.desktop {
application {
mainClass = "Main_desktopKt"
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "Falling Balls"
packageVersion = "1.0.0"
windows {
menuGroup = "Compose Examples"
// see https://wixtoolset.org/documentation/manual/v3/howtos/general/generate_guids.html
upgradeUuid = "18159995-d967-4CD2-8885-77BFA97CFA9F"
}
}
}
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "11"
}
kotlin {
targets.withType<KotlinNativeTarget> {
binaries.all {
freeCompilerArgs += "-Xdisable-phases=VerifyBitcode"
}
}
}
// a temporary workaround for a bug in jsRun invocation - see https://youtrack.jetbrains.com/issue/KT-48273
afterEvaluate {
rootProject.extensions.configure<NodeJsRootExtension> {
versions.webpackDevServer.version = "4.0.0"
versions.webpackCli.version = "4.9.0"
nodeVersion = "16.0.0"
}
}
enum class Target(val simulator: Boolean, val key: String) {
UIKIT_X64(true, "uikitX64"), UIKIT_ARM64(false, "uikitArm64")
}
if (System.getProperty("os.name") == "Mac OS X") {
// Create Xcode integration tasks.
val sdkName: String? = System.getenv("SDK_NAME")
val target = sdkName.orEmpty().let {
when {
it.startsWith("iphoneos") -> Target.UIKIT_ARM64
it.startsWith("iphonesimulator") -> Target.UIKIT_X64
else -> Target.UIKIT_X64
}
}
val targetBuildDir: String? = System.getenv("TARGET_BUILD_DIR")
val executablePath: String? = System.getenv("EXECUTABLE_PATH")
val buildType = System.getenv("CONFIGURATION")?.let {
org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType.valueOf(it.toUpperCase())
} ?: org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType.DEBUG
val currentTarget = kotlin.targets[target.key] as org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
val kotlinBinary = currentTarget.binaries.getExecutable(buildType)
val xcodeIntegrationGroup = "Xcode integration"
val packForXCode = if (sdkName == null || targetBuildDir == null || executablePath == null) {
// The build is launched not by Xcode ->
// We cannot create a copy task and just show a meaningful error message.
tasks.create("packForXCode").doLast {
throw IllegalStateException("Please run the task from Xcode")
}
} else {
// Otherwise copy the executable into the Xcode output directory.
tasks.create("packForXCode", Copy::class.java) {
dependsOn(kotlinBinary.linkTask)
destinationDir = file(targetBuildDir)
val dsymSource = kotlinBinary.outputFile.absolutePath + ".dSYM"
val dsymDestination = File(executablePath).parentFile.name + ".dSYM"
val oldExecName = kotlinBinary.outputFile.name
val newExecName = File(executablePath).name
from(dsymSource) {
into(dsymDestination)
rename(oldExecName, newExecName)
}
from(kotlinBinary.outputFile) {
rename { executablePath }
}
}
}
}

5
examples/falling-balls-mpp/gradle.properties

@ -0,0 +1,5 @@
kotlin.native.cacheKind=none
kotlin.native.useEmbeddableCompilerJar=true
org.gradle.jvmargs=-Xmx3g
kotlin.mpp.enableGranularSourceSetsMetadata=true
kotlin.native.enableDependencyPropagation=false

BIN
examples/falling-balls-mpp/gradle/wrapper/gradle-wrapper.jar vendored

Binary file not shown.

5
examples/falling-balls-mpp/gradle/wrapper/gradle-wrapper.properties vendored

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
examples/falling-balls-mpp/gradlew vendored

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

29
examples/falling-balls-mpp/project.yml

@ -0,0 +1,29 @@
name: ComposeFallingBalls
options:
bundleIdPrefix: org.jetbrains
settings:
DEVELOPMENT_TEAM: N462MKSJ7M
CODE_SIGN_IDENTITY: "iPhone Developer"
CODE_SIGN_STYLE: Automatic
MARKETING_VERSION: "1.0"
CURRENT_PROJECT_VERSION: "4"
SDKROOT: iphoneos
targets:
ComposeFallingBalls:
type: application
platform: iOS
deploymentTarget: "12.0"
prebuildScripts:
- script: cd "$SRCROOT" && ./gradlew -i -p . packForXCode
name: GradleCompile
info:
path: plists/Ios/Info.plist
properties:
UILaunchStoryboardName: ""
sources:
- "src/"
settings:
LIBRARY_SEARCH_PATHS: "$(inherited)"
ENABLE_BITCODE: "YES"
ONLY_ACTIVE_ARCH: "NO"
VALID_ARCHS: "arm64"

12
examples/falling-balls-mpp/settings.gradle.kts

@ -0,0 +1,12 @@
pluginManagement {
repositories {
mavenLocal()
gradlePluginPortal()
mavenCentral()
maven { url = uri("https://maven.pkg.jetbrains.space/public/p/compose/dev") }
google()
}
}
rootProject.name = "falling-balls-mpp"

92
examples/falling-balls-mpp/src/commonMain/kotlin/fallingBalls/FallingBalls.kt

@ -0,0 +1,92 @@
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.Slider
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.withFrameNanos
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.em
import org.jetbrains.skiko.KotlinBackend
@Composable
fun FallingBalls(game: Game) {
val density = LocalDensity.current
Column {
Text(
"Catch balls!${if (game.finished) " Game over!" else ""}",
fontSize = 1.8f.em,
color = Color(218, 120, 91)
)
Text("Score: ${game.score} Time: ${game.elapsed / 1_000_000} Blocks: ${game.numBlocks}", fontSize = 1.8f.em)
Row {
if (!game.started) {
Slider(
value = game.numBlocks / 20f,
onValueChange = { game.numBlocks = (it * 20f).toInt().coerceAtLeast(1) },
modifier = Modifier.width(100.dp)
)
}
Button(
modifier = Modifier
.border(2.dp, Color(255, 215, 0))
.background(Color.Yellow),
onClick = {
game.started = !game.started
if (game.started) {
game.start()
}
}
) {
Text(if (game.started) "Stop" else "Start", fontSize = 2f.em)
}
if (game.started) {
Button(
modifier = Modifier
.offset(10.dp, 0.dp)
.border(2.dp, Color(255, 215, 0))
.background(Color.Yellow),
onClick = {
game.togglePause()
}) {
Text(if (game.paused) "Resume" else "Pause", fontSize = 2f.em)
}
}
}
if (game.started) {
Box(modifier = Modifier.height(20.dp))
Box(modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(1f)
.onSizeChanged {
with(density) {
if (org.jetbrains.skiko.kotlinBackend == KotlinBackend.JVM) {
// Really ugly hack, until we properly pass geometry.
// TODO: fix me in Compose.
game.width = it.width.toDp()
game.height = it.height.toDp()
}
}
}
) {
game.pieces.forEachIndexed { index, piece -> Piece(index, piece) }
}
}
LaunchedEffect(Unit) {
while (true) {
withFrameNanos {
if (game.started && !game.paused && !game.finished)
game.update(it)
}
}
}
}
}

72
examples/falling-balls-mpp/src/commonMain/kotlin/fallingBalls/Game.kt

@ -0,0 +1,72 @@
/*
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.*
import kotlin.random.Random
interface Time {
fun now(): Long
}
class Game(val time: Time) {
private var previousTimeNanos: Long = Long.MAX_VALUE
private val colors = arrayOf(
Color.Red, Color.Blue, Color.Cyan,
Color.Magenta, Color.Yellow, Color.Black
)
private var startTime = 0L
var width by mutableStateOf(0.dp)
var height by mutableStateOf(0.dp)
var pieces = mutableStateListOf<PieceData>()
private set
var elapsed by mutableStateOf(0L)
var score by mutableStateOf(0)
private var clicked by mutableStateOf(0)
var started by mutableStateOf(false)
var paused by mutableStateOf(false)
var finished by mutableStateOf(false)
var numBlocks by mutableStateOf(5)
fun start() {
previousTimeNanos = time.now()
startTime = previousTimeNanos
clicked = 0
started = true
finished = false
paused = false
pieces.clear()
repeat(numBlocks) { index ->
pieces.add(PieceData(this, index * 1.5f + 5f, colors[index % colors.size]).also { piece ->
piece.position = Random.nextDouble(0.0, 100.0).toFloat()
})
}
}
fun togglePause() {
paused = !paused
previousTimeNanos = time.now()
}
fun update(nanos: Long) {
val dt = (nanos - previousTimeNanos).coerceAtLeast(0)
previousTimeNanos = nanos
elapsed = nanos - startTime
pieces.forEach { it.update(dt) }
}
fun clicked(piece: PieceData) {
score += piece.velocity.toInt()
clicked++
if (clicked == numBlocks) {
finished = true
}
}
}

56
examples/falling-balls-mpp/src/commonMain/kotlin/fallingBalls/Piece.kt

@ -0,0 +1,56 @@
/*
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
@Composable
internal fun Piece(index: Int, piece: PieceData) {
val boxSize = 40.dp
Box(
Modifier
.offset(boxSize * index * 5 / 3, piece.position.dp)
.shadow(30.dp)
.clip(CircleShape)
) {
Box(
Modifier
.size(boxSize, boxSize)
.background(if (piece.clicked) Color.Gray else piece.color)
.clickable(onClick = { piece.click() })
)
}
}
data class PieceData(val game: Game, val velocity: Float, val color: Color) {
var clicked by mutableStateOf(false)
var position by mutableStateOf(0f)
fun update(dt: Long) {
if (clicked) return
val delta = (dt / 1E8 * velocity).toFloat()
position = if (position < game.height.value) position + delta else 0f
}
fun click() {
if (!clicked && !game.paused) {
clicked = true
game.clicked(this)
}
}
}

34
examples/falling-balls-mpp/src/desktopMain/kotlin/main.desktop.kt

@ -0,0 +1,34 @@
/*
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.WindowState
import androidx.compose.ui.window.singleWindowApplication
object JvmTime : Time {
override fun now(): Long = System.nanoTime()
}
@OptIn(ExperimentalComposeUiApi::class)
fun main() =
singleWindowApplication(
title = "Falling Balls",
state = WindowState(size = DpSize(800.dp, 800.dp))
) {
val game = remember { Game(JvmTime) }
FallingBalls(game)
}
@Preview
@Composable
fun GamePreview() {
val game = remember { Game(JvmTime) }
FallingBalls(game)
}

27
examples/falling-balls-mpp/src/jsMain/kotlin/main.js.kt

@ -0,0 +1,27 @@
/*
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
import androidx.compose.runtime.remember
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
import org.jetbrains.skiko.wasm.onWasmReady
object JsTime : Time {
override fun now(): Long = kotlinx.browser.window.performance.now().toLong()
}
fun main() {
onWasmReady {
Window("Falling Balls") {
val game = remember { Game(JsTime).apply {
// Ugly hack, properly pass geometry.
width = 800.dp
height = 800.dp
}}
FallingBalls(game)
}
}
}

32
examples/falling-balls-mpp/src/jsMain/resources/index.html

@ -0,0 +1,32 @@
<!--
~ Copyright 2021 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.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>compose multiplatform web demo</title>
<script src="skiko.js"> </script>
<link type="text/css" rel="stylesheet" href="styles.css" />
</head>
<body>
<h1>compose multiplatform web demo</h1>
<div>
<canvas id="ComposeTarget" width="800" height="600"></canvas>
</div>
<script src="falling-balls-mpp.js"> </script>
</body>
</html>

8
examples/falling-balls-mpp/src/jsMain/resources/styles.css

@ -0,0 +1,8 @@
#root {
width: 100%;
height: 100vh;
}
#root > .compose-web-column > div {
position: relative;
}

27
examples/falling-balls-mpp/src/macosMain/kotlin/main.macos.kt

@ -0,0 +1,27 @@
/*
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
import androidx.compose.ui.window.Window
import androidx.compose.runtime.remember
import androidx.compose.ui.unit.dp
import platform.AppKit.NSApp
import platform.AppKit.NSApplication
object MacosTime : Time {
override fun now(): Long = kotlin.system.getTimeNanos()
}
fun main() {
NSApplication.sharedApplication()
Window("Falling Balls") {
val game = remember { Game(MacosTime).apply {
// TODO: rework, now we do not properly propagate geometry changes.
width = 800.dp
height = 600.dp
} }
FallingBalls(game)
}
NSApp?.run()
}

62
examples/falling-balls-mpp/src/uikitMain/kotlin/main.uikit.kt

@ -0,0 +1,62 @@
/*
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
// Use `xcodegen` first, then `open ./SkikoSample.xcodeproj` and then Run button in XCode.
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Application
import kotlinx.cinterop.*
import platform.UIKit.*
import platform.Foundation.*
object UikitTime : Time {
override fun now(): Long = kotlin.system.getTimeNanos()
}
fun main() {
val args = emptyArray<String>()
memScoped {
val argc = args.size + 1
val argv = (arrayOf("skikoApp") + args).map { it.cstr.ptr }.toCValues()
autoreleasepool {
UIApplicationMain(argc, argv, null, NSStringFromClass(SkikoAppDelegate))
}
}
}
class SkikoAppDelegate : UIResponder, UIApplicationDelegateProtocol {
companion object : UIResponderMeta(), UIApplicationDelegateProtocolMeta
@ObjCObjectBase.OverrideInit
constructor() : super()
private var _window: UIWindow? = null
override fun window() = _window
override fun setWindow(window: UIWindow?) {
_window = window
}
override fun application(application: UIApplication, didFinishLaunchingWithOptions: Map<Any?, *>?): Boolean {
window = UIWindow(frame = UIScreen.mainScreen.bounds)
window!!.rootViewController = Application("Falling Balls") {
val game = remember { Game(UikitTime).apply {
width = 800.dp
height = 800.dp
} }
Column {
// To skip upper part of screen.
Box(modifier = Modifier
.height(100.dp))
FallingBalls(game)
}
}
window!!.makeKeyAndVisible()
return true
}
}
Loading…
Cancel
Save