Browse Source

Graphics2D example (#3770)

COMPOSE-357

- Merged non UI examples visual-effects, falling-balls and minesweeper
into one graphics2d
 - Removed deprecated function calls `kotlin.system.getTimeNanos()`
 - Little bit simplifyed code
 - Used Material3

Co-authored-by: Igor Demin <igordmn@users.noreply.github.com>
pull/3798/head
dima.avdeev 1 year ago committed by GitHub
parent
commit
d7a82a4732
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      examples/README.md
  2. 21
      examples/falling-balls/README.md
  3. 3
      examples/falling-balls/androidApp/src/androidMain/res/values/strings.xml
  4. 19
      examples/falling-balls/desktopApp/src/jvmMain/kotlin/Main.kt
  5. 3
      examples/falling-balls/iosApp/Configuration/Config.xcconfig
  6. 15
      examples/falling-balls/jsApp/src/jsMain/kotlin/main.js.kt
  7. 32
      examples/falling-balls/jsApp/src/jsMain/resources/index.html
  8. 32
      examples/falling-balls/settings.gradle.kts
  9. 125
      examples/falling-balls/shared/build.gradle.kts
  10. 12
      examples/falling-balls/shared/src/androidMain/kotlin/main.android.kt
  11. 24
      examples/falling-balls/shared/src/desktopMain/kotlin/main.desktop.kt
  12. 19
      examples/falling-balls/shared/src/iosMain/kotlin/main.ios.kt
  13. 65
      examples/falling-balls/shared/src/jsMain/kotlin/main.js.kt
  14. 23
      examples/falling-balls/shared/src/macosMain/kotlin/main.macos.kt
  15. 0
      examples/graphics-2d/.gitignore
  16. 0
      examples/graphics-2d/.run/desktopApp.run.xml
  17. 24
      examples/graphics-2d/README.md
  18. 4
      examples/graphics-2d/androidApp/build.gradle.kts
  19. 0
      examples/graphics-2d/androidApp/src/androidMain/AndroidManifest.xml
  20. 2
      examples/graphics-2d/androidApp/src/androidMain/kotlin/org/jetbrains/graphics2d/MainActivity.kt
  21. 3
      examples/graphics-2d/androidApp/src/androidMain/res/values/strings.xml
  22. 0
      examples/graphics-2d/apple-id.png
  23. 0
      examples/graphics-2d/build.gradle.kts
  24. 4
      examples/graphics-2d/desktopApp/build.gradle.kts
  25. 30
      examples/graphics-2d/desktopApp/src/jvmMain/kotlin/Main.kt
  26. 0
      examples/graphics-2d/gradle.properties
  27. 0
      examples/graphics-2d/gradle/wrapper/gradle-wrapper.jar
  28. 0
      examples/graphics-2d/gradle/wrapper/gradle-wrapper.properties
  29. 0
      examples/graphics-2d/gradlew
  30. 0
      examples/graphics-2d/gradlew.bat
  31. 0
      examples/graphics-2d/ios-app.png
  32. 3
      examples/graphics-2d/iosApp/Configuration/Config.xcconfig
  33. 6
      examples/graphics-2d/iosApp/iosApp.xcodeproj/project.pbxproj
  34. 0
      examples/graphics-2d/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json
  35. 0
      examples/graphics-2d/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json
  36. 0
      examples/graphics-2d/iosApp/iosApp/Assets.xcassets/Contents.json
  37. 2
      examples/graphics-2d/iosApp/iosApp/ContentView.swift
  38. 0
      examples/graphics-2d/iosApp/iosApp/Info.plist
  39. 0
      examples/graphics-2d/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json
  40. 0
      examples/graphics-2d/iosApp/iosApp/iOSApp.swift
  41. 2
      examples/graphics-2d/jsApp/build.gradle.kts
  42. 10
      examples/graphics-2d/jsApp/src/jsMain/kotlin/main.js.kt
  43. 0
      examples/graphics-2d/jsApp/src/jsMain/resources/assets/clock.png
  44. 0
      examples/graphics-2d/jsApp/src/jsMain/resources/assets/flag.png
  45. 0
      examples/graphics-2d/jsApp/src/jsMain/resources/assets/mine.png
  46. 0
      examples/graphics-2d/jsApp/src/jsMain/resources/compose-community-primary.xml
  47. 16
      examples/graphics-2d/jsApp/src/jsMain/resources/index.html
  48. 0
      examples/graphics-2d/jsApp/src/jsMain/resources/styles.css
  49. 0
      examples/graphics-2d/run-configurations.png
  50. 2
      examples/graphics-2d/settings.gradle.kts
  51. 9
      examples/graphics-2d/shared/build.gradle.kts
  52. 0
      examples/graphics-2d/shared/src/androidMain/AndroidManifest.xml
  53. 6
      examples/graphics-2d/shared/src/androidMain/kotlin/main.android.kt
  54. 3
      examples/graphics-2d/shared/src/androidMain/kotlin/minesweeper/MineSweeper.android.kt
  55. 2
      examples/graphics-2d/shared/src/androidMain/kotlin/platform/PointerEventKind.android.kt
  56. 110
      examples/graphics-2d/shared/src/commonMain/kotlin/Graphics2D.kt
  57. 32
      examples/graphics-2d/shared/src/commonMain/kotlin/bouncingballs/BouncingBalls.kt
  58. 48
      examples/graphics-2d/shared/src/commonMain/kotlin/fallingballs/FallingBalls.common.kt
  59. 49
      examples/graphics-2d/shared/src/commonMain/kotlin/fallingballs/Game.kt
  60. 7
      examples/graphics-2d/shared/src/commonMain/kotlin/fallingballs/Piece.kt
  61. 4
      examples/graphics-2d/shared/src/commonMain/kotlin/minesweeper/BoardView.kt
  62. 20
      examples/graphics-2d/shared/src/commonMain/kotlin/minesweeper/GameController.kt
  63. 2
      examples/graphics-2d/shared/src/commonMain/kotlin/minesweeper/GameInteraction.kt
  64. 21
      examples/graphics-2d/shared/src/commonMain/kotlin/minesweeper/MineSweeper.common.kt
  65. 6
      examples/graphics-2d/shared/src/commonMain/kotlin/minesweeper/Widgets.kt
  66. 214
      examples/graphics-2d/shared/src/commonMain/kotlin/visualeffects/HappyNY.kt
  67. 14
      examples/graphics-2d/shared/src/commonMain/kotlin/visualeffects/PointerEvent.common.kt
  68. 30
      examples/graphics-2d/shared/src/commonMain/kotlin/visualeffects/RotatingWords.kt
  69. 59
      examples/graphics-2d/shared/src/commonMain/kotlin/visualeffects/WaveEffect.kt
  70. 0
      examples/graphics-2d/shared/src/commonMain/resources/assets/clock.png
  71. 0
      examples/graphics-2d/shared/src/commonMain/resources/assets/flag.png
  72. 0
      examples/graphics-2d/shared/src/commonMain/resources/assets/mine.png
  73. 36
      examples/graphics-2d/shared/src/commonMain/resources/compose-community-primary.xml
  74. 2
      examples/graphics-2d/shared/src/commonTest/kotlin/minesweeper/GameControllerTest.kt
  75. 3
      examples/graphics-2d/shared/src/desktopMain/kotlin/minesweeper/MineSweeper.desktop.kt
  76. 4
      examples/graphics-2d/shared/src/desktopMain/kotlin/visualeffects/PointerEvent.desktop.kt
  77. 6
      examples/graphics-2d/shared/src/iosMain/kotlin/main.ios.kt
  78. 3
      examples/graphics-2d/shared/src/iosMain/kotlin/minesweeper/MineSweeper.ios.kt
  79. 2
      examples/graphics-2d/shared/src/iosMain/kotlin/visualeffects/PointerEvent.ios.kt
  80. 6
      examples/graphics-2d/shared/src/jsMain/kotlin/main.js.kt
  81. 3
      examples/graphics-2d/shared/src/jsMain/kotlin/minesweeper/MineSweeepr.js.kt
  82. 26
      examples/graphics-2d/shared/src/jsMain/kotlin/visualeffects/PointerEvent.js.kt
  83. 11
      examples/graphics-2d/shared/src/macosMain/kotlin/main.macos.kt
  84. 3
      examples/graphics-2d/shared/src/macosMain/kotlin/minesweeper/MineSweeper.macos.kt
  85. 27
      examples/graphics-2d/shared/src/macosMain/kotlin/visualeffects/PointerEventKind.macos.kt
  86. 7
      examples/minesweeper/.gitignore
  87. 28
      examples/minesweeper/.run/desktopApp.run.xml
  88. 25
      examples/minesweeper/README.md
  89. 35
      examples/minesweeper/androidApp/build.gradle.kts
  90. 21
      examples/minesweeper/androidApp/src/androidMain/AndroidManifest.xml
  91. 15
      examples/minesweeper/androidApp/src/androidMain/kotlin/MainActivity.kt
  92. 3
      examples/minesweeper/androidApp/src/androidMain/res/values/strings.xml
  93. 18
      examples/minesweeper/build.gradle.kts
  94. 35
      examples/minesweeper/desktopApp/build.gradle.kts
  95. 22
      examples/minesweeper/desktopApp/src/jvmMain/kotlin/Main.kt
  96. 19
      examples/minesweeper/gradle.properties
  97. BIN
      examples/minesweeper/gradle/wrapper/gradle-wrapper.jar
  98. 5
      examples/minesweeper/gradle/wrapper/gradle-wrapper.properties
  99. 240
      examples/minesweeper/gradlew
  100. 91
      examples/minesweeper/gradlew.bat
  101. Some files were not shown because too many files have changed in this diff Show More

4
examples/README.md

@ -4,9 +4,7 @@
| [Imageviewer](imageviewer) | Image Viewer application | Android, iOS, Desktop | | [Imageviewer](imageviewer) | Image Viewer application | Android, iOS, Desktop |
| [Codeviewer](codeviewer) | File browser and code viewer application | Android, iOS, Desktop | | [Codeviewer](codeviewer) | File browser and code viewer application | Android, iOS, Desktop |
| [Chat](chat) | A simple chat | Android, iOS, Desktop | | [Chat](chat) | A simple chat | Android, iOS, Desktop |
| [Minesweeper](minesweeper) | A simple game where you need to find hidden mines | Android, iOS, Desktop | | [Graphics2D](graphics-2d) | 2D Games and graphics examples | Android, iOS, Desktop |
| [Falling Balls](falling-balls) | A simple game | Android, iOS, Desktop |
| [Visual effects](visual-effects) | Visual effects | Android, iOS, Desktop |
| [Widgets Gallery](widgets-gallery) | Gallery of standard widgets | Android, iOS, Desktop | | [Widgets Gallery](widgets-gallery) | Gallery of standard widgets | Android, iOS, Desktop |
| [Todoapp Lite](todoapp-lite) | A simple todo app fully based on Compose | Android, iOS, Desktop | | [Todoapp Lite](todoapp-lite) | A simple todo app fully based on Compose | Android, iOS, Desktop |
| [Issues tracker](issues) | GitHub issue tracker with an adaptive UI and ktor-client | Android, Desktop | | [Issues tracker](issues) | GitHub issue tracker with an adaptive UI and ktor-client | Android, Desktop |

21
examples/falling-balls/README.md

@ -1,21 +0,0 @@
# Falling Balls game
Game can run on Android, iOS, desktop or in a browser.
## Setting up your development environment
To setup the environment, please consult these [instructions](https://github.com/JetBrains/compose-multiplatform-template#setting-up-your-development-environment).
## How to run
Choose a run configuration for an appropriate target in Android Studio and run it.
![run-configurations.png](run-configurations.png)
## Run on desktop via Gradle
`./gradlew desktopApp:run`
## Run native on MacOS
Choose **shared[macosX64]** or **shared[macosArm64]** configuration in IDE and run it.

3
examples/falling-balls/androidApp/src/androidMain/res/values/strings.xml

@ -1,3 +0,0 @@
<resources>
<string name="app_name">Falling Balls</string>
</resources>

19
examples/falling-balls/desktopApp/src/jvmMain/kotlin/Main.kt

@ -1,19 +0,0 @@
/*
* 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.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
@OptIn(ExperimentalComposeUiApi::class)
fun main() =
singleWindowApplication(
title = "Falling Balls",
state = WindowState(size = DpSize(800.dp, 800.dp))
) {
MainView()
}

3
examples/falling-balls/iosApp/Configuration/Config.xcconfig

@ -1,3 +0,0 @@
TEAM_ID=
BUNDLE_ID=org.jetbrains.FallingBalls
APP_NAME=FallingBalls

15
examples/falling-balls/jsApp/src/jsMain/kotlin/main.js.kt

@ -1,15 +0,0 @@
/*
* 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 org.jetbrains.skiko.wasm.onWasmReady
fun main() {
onWasmReady {
Window("Falling Balls") {
MainView()
}
}
}

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

@ -1,32 +0,0 @@
<!--
~ 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="jsApp.js"> </script>
</body>
</html>

32
examples/falling-balls/settings.gradle.kts

@ -1,32 +0,0 @@
pluginManagement {
repositories {
gradlePluginPortal()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
google()
}
plugins {
val kotlinVersion = extra["kotlin.version"] as String
val agpVersion = extra["agp.version"] as String
val composeVersion = extra["compose.version"] as String
kotlin("jvm").version(kotlinVersion)
kotlin("multiplatform").version(kotlinVersion)
kotlin("android").version(kotlinVersion)
id("com.android.base").version(agpVersion)
id("com.android.application").version(agpVersion)
id("com.android.library").version(agpVersion)
id("org.jetbrains.compose").version(composeVersion)
}
}
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version("0.4.0")
}
rootProject.name = "falling-balls-mpp"
include(":androidApp")
include(":shared")
include(":desktopApp")
include(":jsApp")

125
examples/falling-balls/shared/build.gradle.kts

@ -1,125 +0,0 @@
@file:Suppress("OPT_IN_IS_NOT_ENABLED")
import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler
plugins {
kotlin("multiplatform")
id("com.android.library")
id("org.jetbrains.compose")
}
version = "1.0-SNAPSHOT"
kotlin {
androidTarget()
jvm("desktop")
js(IR) {
browser()
}
macosX64 {
binaries {
executable {
entryPoint = "main"
}
}
}
macosArm64 {
binaries {
executable {
entryPoint = "main"
}
}
}
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "shared"
isStatic = true
}
}
val enableKjsWorkaround = project.properties["workaround.kotlin.js.kt60852"] == "true"
fun KotlinDependencyHandler.addCommonDependencies() {
implementation(compose.ui)
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material)
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
implementation(compose.components.resources)
}
sourceSets {
val commonMain by getting {
dependencies {
if (!enableKjsWorkaround) {
addCommonDependencies()
}
}
}
val androidMain by getting {
dependencies {
api("androidx.activity:activity-compose:1.7.2")
api("androidx.appcompat:appcompat:1.6.1")
api("androidx.core:core-ktx:1.10.1")
}
}
val iosMain by creating {
dependsOn(commonMain)
}
val iosX64Main by getting {
dependsOn(iosMain)
}
val iosArm64Main by getting {
dependsOn(iosMain)
}
val iosSimulatorArm64Main by getting {
dependsOn(iosMain)
}
val desktopMain by getting {
dependencies {
implementation(compose.desktop.common)
}
}
val macosMain by creating {
dependsOn(commonMain)
}
val macosArm64Main by getting {
dependsOn(macosMain)
}
val jsMain by getting {
dependencies {
if (enableKjsWorkaround) {
addCommonDependencies()
}
}
}
}
}
android {
compileSdk = 34
namespace = "org.jetbrains.fallingballs"
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
sourceSets["main"].res.srcDirs("src/androidMain/res")
sourceSets["main"].resources.srcDirs("src/commonMain/resources")
defaultConfig {
minSdk = 26
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlin {
jvmToolchain(17)
}
}

12
examples/falling-balls/shared/src/androidMain/kotlin/main.android.kt

@ -1,12 +0,0 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
object AndroidTime : Time {
override fun now(): Long = System.nanoTime()
}
@Composable
fun MainView() {
val game = remember { Game(AndroidTime) }
FallingBalls(game)
}

24
examples/falling-balls/shared/src/desktopMain/kotlin/main.desktop.kt

@ -1,24 +0,0 @@
/*
* 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
object JvmTime : Time {
override fun now(): Long = System.nanoTime()
}
@Composable
fun MainView() {
val game = remember { Game(JvmTime) }
FallingBalls(game)
}
@Preview
@Composable
fun GamePreview() {
MainView()
}

19
examples/falling-balls/shared/src/iosMain/kotlin/main.ios.kt

@ -1,19 +0,0 @@
/*
* 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.window.ComposeUIViewController
import platform.UIKit.UIViewController
object IosTime : Time {
override fun now(): Long = kotlin.system.getTimeNanos()
}
fun MainViewController() : UIViewController = ComposeUIViewController {
val game = remember { Game(IosTime) }
FallingBalls(game)
}

65
examples/falling-balls/shared/src/jsMain/kotlin/main.js.kt

@ -1,65 +0,0 @@
/*
* 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.layout.*
import androidx.compose.material.RadioButton
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import bouncingBalls.BouncingBallsApp
object JsTime : Time {
override fun now(): Long = kotlinx.browser.window.performance.now().toLong()
}
@Composable
fun MainView() {
val selectedExample = remember { mutableStateOf(Examples.FallingBalls) }
Column(modifier = Modifier.fillMaxSize()) {
ExamplesChooser(selectedExample)
Spacer(modifier = Modifier.height(24.dp))
when (selectedExample.value) {
Examples.FallingBalls -> {
val game = remember { Game(JsTime) }
FallingBalls(game)
}
Examples.BouncingBalls -> {
BouncingBallsApp(10)
}
}
}
}
@Composable
private fun ExamplesChooser(selected: MutableState<Examples>) {
Column {
Row(verticalAlignment = Alignment.CenterVertically) {
Text("Choose an example: ", fontSize = 16.sp)
Examples.values().forEach {
Row(verticalAlignment = Alignment.CenterVertically) {
RadioButton(selected = selected.value == it, onClick = {
selected.value = it
})
Text(it.name)
}
}
}
}
}
private enum class Examples {
FallingBalls,
BouncingBalls
}

23
examples/falling-balls/shared/src/macosMain/kotlin/main.macos.kt

@ -1,23 +0,0 @@
/*
* 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) }
FallingBalls(game)
}
NSApp?.run()
}

0
examples/falling-balls/.gitignore → examples/graphics-2d/.gitignore vendored

0
examples/falling-balls/.run/desktopApp.run.xml → examples/graphics-2d/.run/desktopApp.run.xml

24
examples/graphics-2d/README.md

@ -0,0 +1,24 @@
# Graphics2D
Example can run on Android, iOS, desktop or in a browser.
## Setting up your development environment
To setup the environment, please consult
these [instructions](https://github.com/JetBrains/compose-multiplatform-template#setting-up-your-development-environment).
## How to run
Choose a run configuration for an appropriate target in Android Studio and run it.
![run-configurations.png](run-configurations.png)
## Run on desktop via Gradle
`./gradlew desktopApp:run`
## Run experimental native on MacOS
`./gradlew runDebugExecutableMacosX64` (Works on Intel processors)
`./gradlew runDebugExecutableMacosArm64` (Works on Arm processors)

4
examples/falling-balls/androidApp/build.gradle.kts → examples/graphics-2d/androidApp/build.gradle.kts

@ -17,9 +17,9 @@ kotlin {
android { android {
compileSdk = 34 compileSdk = 34
namespace = "org.jetbrains.fallingballs" namespace = "org.jetbrains.graphics2d"
defaultConfig { defaultConfig {
applicationId = "org.jetbrains.FallingBalls" applicationId = "org.jetbrains.Graphics2D"
minSdk = 26 minSdk = 26
targetSdk = 34 targetSdk = 34
versionCode = 1 versionCode = 1

0
examples/falling-balls/androidApp/src/androidMain/AndroidManifest.xml → examples/graphics-2d/androidApp/src/androidMain/AndroidManifest.xml

2
examples/falling-balls/androidApp/src/androidMain/kotlin/org/jetbrains/fallingballs/MainActivity.kt → examples/graphics-2d/androidApp/src/androidMain/kotlin/org/jetbrains/graphics2d/MainActivity.kt

@ -1,4 +1,4 @@
package org.jetbrains.fallingballs package org.jetbrains.graphics2d
import MainView import MainView
import android.os.Bundle import android.os.Bundle

3
examples/graphics-2d/androidApp/src/androidMain/res/values/strings.xml

@ -0,0 +1,3 @@
<resources>
<string name="app_name">Graphics2D</string>
</resources>

0
examples/falling-balls/apple-id.png → examples/graphics-2d/apple-id.png

Before

Width:  |  Height:  |  Size: 213 KiB

After

Width:  |  Height:  |  Size: 213 KiB

0
examples/falling-balls/build.gradle.kts → examples/graphics-2d/build.gradle.kts

4
examples/falling-balls/desktopApp/build.gradle.kts → examples/graphics-2d/desktopApp/build.gradle.kts

@ -8,7 +8,7 @@ plugins {
kotlin { kotlin {
jvm {} jvm {}
sourceSets { sourceSets {
val jvmMain by getting { val jvmMain by getting {
dependencies { dependencies {
implementation(compose.desktop.currentOs) implementation(compose.desktop.currentOs)
implementation(project(":shared")) implementation(project(":shared"))
@ -23,7 +23,7 @@ compose.desktop {
nativeDistributions { nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "Falling Balls" packageName = "Graphics2D"
packageVersion = "1.0.0" packageVersion = "1.0.0"
windows { windows {

30
examples/graphics-2d/desktopApp/src/jvmMain/kotlin/Main.kt

@ -0,0 +1,30 @@
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.coerceIn
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import androidx.compose.ui.window.rememberWindowState
private val INIT_SIZE = DpSize(800.dp, 800.dp)
fun main() =
application {
val windowState = rememberWindowState(width = 800.dp, height = 800.dp)
Window(
onCloseRequest = ::exitApplication,
resizable = false,
title = "Graphics2D",
state = windowState,
) {
Graphics2D(
requestWindowSize = { w, h ->
windowState.size = windowState.size.copy(
width = w.coerceIn(INIT_SIZE.width, Float.MAX_VALUE.dp),
height = h.coerceIn(INIT_SIZE.height, Float.MAX_VALUE.dp)
)
}
)
}
}

0
examples/falling-balls/gradle.properties → examples/graphics-2d/gradle.properties

0
examples/falling-balls/gradle/wrapper/gradle-wrapper.jar → examples/graphics-2d/gradle/wrapper/gradle-wrapper.jar vendored

0
examples/falling-balls/gradle/wrapper/gradle-wrapper.properties → examples/graphics-2d/gradle/wrapper/gradle-wrapper.properties vendored

0
examples/falling-balls/gradlew → examples/graphics-2d/gradlew vendored

0
examples/falling-balls/gradlew.bat → examples/graphics-2d/gradlew.bat vendored

0
examples/falling-balls/ios-app.png → examples/graphics-2d/ios-app.png

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

3
examples/graphics-2d/iosApp/Configuration/Config.xcconfig

@ -0,0 +1,3 @@
TEAM_ID=
BUNDLE_ID=org.jetbrains.Graphics2D
APP_NAME=Graphics2D

6
examples/falling-balls/iosApp/iosApp.xcodeproj/project.pbxproj → examples/graphics-2d/iosApp/iosApp.xcodeproj/project.pbxproj

@ -17,7 +17,7 @@
058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; }; 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = "<group>"; }; 2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = "<group>"; };
7555FF7B242A565900829871 /* FallingBalls.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FallingBalls.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7555FF7B242A565900829871 /* Graphics2D.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Graphics2D.app; sourceTree = BUILT_PRODUCTS_DIR; };
7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; }; 7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
AB3632DC29227652001CCB65 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; }; AB3632DC29227652001CCB65 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
@ -54,7 +54,7 @@
7555FF7C242A565900829871 /* Products */ = { 7555FF7C242A565900829871 /* Products */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
7555FF7B242A565900829871 /* FallingBalls.app */, 7555FF7B242A565900829871 /* Graphics2D.app */,
); );
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
@ -97,7 +97,7 @@
); );
name = iosApp; name = iosApp;
productName = iosApp; productName = iosApp;
productReference = 7555FF7B242A565900829871 /* FallingBalls.app */; productReference = 7555FF7B242A565900829871 /* Graphics2D.app */;
productType = "com.apple.product-type.application"; productType = "com.apple.product-type.application";
}; };
/* End PBXNativeTarget section */ /* End PBXNativeTarget section */

0
examples/falling-balls/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json → examples/graphics-2d/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json

0
examples/falling-balls/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json → examples/graphics-2d/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json

0
examples/falling-balls/iosApp/iosApp/Assets.xcassets/Contents.json → examples/graphics-2d/iosApp/iosApp/Assets.xcassets/Contents.json

2
examples/falling-balls/iosApp/iosApp/ContentView.swift → examples/graphics-2d/iosApp/iosApp/ContentView.swift

@ -5,7 +5,7 @@ import shared
struct ContentView: View { struct ContentView: View {
var body: some View { var body: some View {
ComposeView() ComposeView()
.ignoresSafeArea(.keyboard) // Compose has own keyboard handler .ignoresSafeArea(.all) // Compose has own keyboard handler
} }
} }

0
examples/falling-balls/iosApp/iosApp/Info.plist → examples/graphics-2d/iosApp/iosApp/Info.plist

0
examples/falling-balls/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json → examples/graphics-2d/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json

0
examples/falling-balls/iosApp/iosApp/iOSApp.swift → examples/graphics-2d/iosApp/iosApp/iOSApp.swift

2
examples/falling-balls/jsApp/build.gradle.kts → examples/graphics-2d/jsApp/build.gradle.kts

@ -9,7 +9,7 @@ kotlin {
binaries.executable() binaries.executable()
} }
sourceSets { sourceSets {
val jsMain by getting { val jsMain by getting {
dependencies { dependencies {
implementation(project(":shared")) implementation(project(":shared"))
implementation(compose.ui) implementation(compose.ui)

10
examples/graphics-2d/jsApp/src/jsMain/kotlin/main.js.kt

@ -0,0 +1,10 @@
import androidx.compose.ui.window.Window
import org.jetbrains.skiko.wasm.onWasmReady
fun main() {
onWasmReady {
Window("Graphics2D") {
MainView()
}
}
}

0
examples/minesweeper/jsApp/src/jsMain/resources/assets/clock.png → examples/graphics-2d/jsApp/src/jsMain/resources/assets/clock.png

Before

Width:  |  Height:  |  Size: 896 B

After

Width:  |  Height:  |  Size: 896 B

0
examples/minesweeper/jsApp/src/jsMain/resources/assets/flag.png → examples/graphics-2d/jsApp/src/jsMain/resources/assets/flag.png

Before

Width:  |  Height:  |  Size: 780 B

After

Width:  |  Height:  |  Size: 780 B

0
examples/minesweeper/jsApp/src/jsMain/resources/assets/mine.png → examples/graphics-2d/jsApp/src/jsMain/resources/assets/mine.png

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

0
examples/visual-effects/shared/src/commonMain/resources/compose-community-primary.xml → examples/graphics-2d/jsApp/src/jsMain/resources/compose-community-primary.xml

16
examples/graphics-2d/jsApp/src/jsMain/resources/index.html

@ -0,0 +1,16 @@
<!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="jsApp.js"> </script>
</body>
</html>

0
examples/falling-balls/jsApp/src/jsMain/resources/styles.css → examples/graphics-2d/jsApp/src/jsMain/resources/styles.css

0
examples/falling-balls/run-configurations.png → examples/graphics-2d/run-configurations.png

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

2
examples/minesweeper/settings.gradle.kts → examples/graphics-2d/settings.gradle.kts

@ -24,7 +24,7 @@ plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version("0.4.0") id("org.gradle.toolchains.foojay-resolver-convention") version("0.4.0")
} }
rootProject.name = "minesweeper" rootProject.name = "graphics-2d"
include(":androidApp") include(":androidApp")
include(":shared") include(":shared")

9
examples/minesweeper/shared/build.gradle.kts → examples/graphics-2d/shared/build.gradle.kts

@ -15,7 +15,6 @@ kotlin {
androidTarget() androidTarget()
jvm("desktop") jvm("desktop")
js(IR) { js(IR) {
browser() browser()
} }
@ -52,7 +51,7 @@ kotlin {
implementation(compose.ui) implementation(compose.ui)
implementation(compose.runtime) implementation(compose.runtime)
implementation(compose.foundation) implementation(compose.foundation)
implementation(compose.material) implementation(compose.material3)
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class) @OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
implementation(compose.components.resources) implementation(compose.components.resources)
} }
@ -95,13 +94,9 @@ kotlin {
implementation(compose.desktop.common) implementation(compose.desktop.common)
} }
} }
val macosMain by creating { val macosMain by creating {
dependsOn(commonMain) dependsOn(commonMain)
} }
val macosX64Main by getting {
dependsOn(macosMain)
}
val macosArm64Main by getting { val macosArm64Main by getting {
dependsOn(macosMain) dependsOn(macosMain)
} }
@ -117,7 +112,7 @@ kotlin {
android { android {
compileSdk = 34 compileSdk = 34
namespace = "org.jetbrains.minesweeper" namespace = "org.jetbrains.Graphics2D"
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
sourceSets["main"].res.srcDirs("src/androidMain/res") sourceSets["main"].res.srcDirs("src/androidMain/res")
sourceSets["main"].resources.srcDirs("src/commonMain/resources") sourceSets["main"].resources.srcDirs("src/commonMain/resources")

0
examples/falling-balls/shared/src/androidMain/AndroidManifest.xml → examples/graphics-2d/shared/src/androidMain/AndroidManifest.xml

6
examples/graphics-2d/shared/src/androidMain/kotlin/main.android.kt

@ -0,0 +1,6 @@
import androidx.compose.runtime.Composable
@Composable
fun MainView() {
Graphics2D()
}

3
examples/graphics-2d/shared/src/androidMain/kotlin/minesweeper/MineSweeper.android.kt

@ -0,0 +1,3 @@
package minesweeper
actual fun hasRightClick() = false

2
examples/visual-effects/shared/src/androidMain/kotlin/platform/PointerEventKind.android.kt → examples/graphics-2d/shared/src/androidMain/kotlin/platform/PointerEventKind.android.kt

@ -1,4 +1,4 @@
package org.jetbrains.compose.demo.visuals.platform package visualeffects
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier

110
examples/graphics-2d/shared/src/commonMain/kotlin/Graphics2D.kt

@ -0,0 +1,110 @@
import androidx.compose.foundation.clickable
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import bouncingballs.BouncingBallsApp
import fallingballs.FallingBalls
import minesweeper.MineSweeper
import visualeffects.NYContent
import visualeffects.RotatingWords
import visualeffects.WaveEffectGrid
private val TOP_APP_BAR_HEIGHT = 100.dp
private val EMPTY_WINDOW_RESIZER: (width: Dp, height: Dp) -> Unit = { w, h -> }
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Graphics2D(requestWindowSize: ((width: Dp, height: Dp) -> Unit) = EMPTY_WINDOW_RESIZER) {
val exampleState: MutableState<Example?> = remember { mutableStateOf(null) }
val example = exampleState.value
MaterialTheme(
colorScheme = if (isSystemInDarkTheme()) darkColorScheme() else lightColorScheme()
) {
Scaffold(
topBar = {
TopAppBar(
navigationIcon = {
if (example != null) {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = "Back",
modifier = Modifier.clickable {
exampleState.value = null
}
)
}
},
title = {
Text(example?.name ?: "Choose example")
}
)
}
) {
Box(Modifier.padding(it)) {
if (example == null) {
LazyColumn(Modifier.padding(horizontal = 16.dp)) {
items(examples) {
Button(onClick = {
exampleState.value = it
}) {
Text(it.name)
}
}
}
} else {
example.content { w, h ->
requestWindowSize(w, h + TOP_APP_BAR_HEIGHT)
}
}
}
}
}
}
private class Example(
val name: String,
val content: @Composable (requestWindowSize: ((width: Dp, height: Dp) -> Unit)) -> Unit
)
private val examples: List<Example> = listOf(
Example("FallingBalls") {
FallingBalls()
},
Example("BouncingBalls") {
BouncingBallsApp()
},
Example("MineSweeper") {
MineSweeper(it)
},
Example("RotatingWords") {
RotatingWords()
},
Example("WaveEffectGrid") {
WaveEffectGrid()
},
Example("Happy New Year!") {
NYContent()
},
)

32
examples/falling-balls/shared/src/commonMain/kotlin/bouncingBalls/BouncingBalls.kt → examples/graphics-2d/shared/src/commonMain/kotlin/bouncingballs/BouncingBalls.kt

@ -1,28 +1,35 @@
/* package bouncingballs
* Copyright 2020-2022 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.
*/
package bouncingBalls
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.* import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.withFrameNanos
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.composed import androidx.compose.ui.composed
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import kotlin.math.PI import kotlin.math.PI
import kotlin.math.cos import kotlin.math.cos
import kotlin.math.max import kotlin.math.max
import kotlin.math.roundToInt
import kotlin.math.sin import kotlin.math.sin
import kotlin.random.Random import kotlin.random.Random
@ -52,17 +59,16 @@ fun BouncingBallsApp(initialBallsCount: Int = 5) {
list list
} }
Box( BoxWithConstraints(
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
.fillMaxHeight() .fillMaxHeight()
.border(width = 1.dp, color = Color.Black) .border(width = 1.dp, color = Color.Black)
.noRippleClickable { .noRippleClickable {
items += BouncingBall.createBouncingBall(offset = it) items += BouncingBall.createBouncingBall(offset = it)
}.onSizeChanged {
areaWidth = it.width
areaHeight = it.height
} }
) { ) {
areaWidth = maxWidth.value.roundToInt()
areaHeight = maxHeight.value.roundToInt()
Balls(items) Balls(items)
} }

48
examples/falling-balls/shared/src/commonMain/kotlin/fallingBalls/FallingBalls.kt → examples/graphics-2d/shared/src/commonMain/kotlin/fallingballs/FallingBalls.common.kt

@ -1,11 +1,20 @@
package fallingballs
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Box
import androidx.compose.material.Button import androidx.compose.foundation.layout.Column
import androidx.compose.material.Slider import androidx.compose.foundation.layout.Row
import androidx.compose.material.Text import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Button
import androidx.compose.material3.Slider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.withFrameNanos import androidx.compose.runtime.withFrameNanos
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
@ -15,7 +24,8 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@Composable @Composable
fun FallingBalls(game: Game) { fun FallingBalls() {
val game = remember { Game() }
val density = LocalDensity.current val density = LocalDensity.current
Column { Column {
Text( Text(
@ -23,7 +33,10 @@ fun FallingBalls(game: Game) {
fontSize = 20.sp, fontSize = 20.sp,
color = Color(218, 120, 91) color = Color(218, 120, 91)
) )
Text("Score: ${game.score} Time: ${game.elapsed / 1_000_000} Blocks: ${game.numBlocks.toInt()}", fontSize = 20.sp) Text(
"Score: ${game.score} Time: ${game.elapsed / 1_000_000} Blocks: ${game.numBlocks.toInt()}",
fontSize = 20.sp
)
Row { Row {
if (!game.started) { if (!game.started) {
Slider( Slider(
@ -33,9 +46,6 @@ fun FallingBalls(game: Game) {
) )
} }
Button( Button(
modifier = Modifier
.border(2.dp, Color(255, 215, 0))
.background(Color.Yellow),
onClick = { onClick = {
game.started = !game.started game.started = !game.started
if (game.started) { if (game.started) {
@ -45,18 +55,6 @@ fun FallingBalls(game: Game) {
) { ) {
Text(if (game.started) "Stop" else "Start", fontSize = 25.sp) Text(if (game.started) "Stop" else "Start", fontSize = 25.sp)
} }
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 = 25.sp)
}
}
} }
if (game.started) { if (game.started) {
Box(modifier = Modifier.height(20.dp)) Box(modifier = Modifier.height(20.dp))
@ -76,12 +74,14 @@ fun FallingBalls(game: Game) {
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
while (true) { while (true) {
var previousTimeNanos = withFrameNanos { it }
withFrameNanos { withFrameNanos {
if (game.started && !game.paused && !game.finished) if (game.started && !game.paused && !game.finished) {
game.update(it) game.update((it - previousTimeNanos).coerceAtLeast(0))
previousTimeNanos = it
}
} }
} }
} }
} }
} }

49
examples/falling-balls/shared/src/commonMain/kotlin/fallingBalls/Game.kt → examples/graphics-2d/shared/src/commonMain/kotlin/fallingballs/Game.kt

@ -1,23 +1,18 @@
/* package fallingballs
* 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.getValue
*/ import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.* import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.* import androidx.compose.ui.unit.dp
import kotlin.random.Random import kotlin.random.Random
interface Time { class Game() {
fun now(): Long
}
class Game(val time: Time) {
private var previousTimeNanos: Long = Long.MAX_VALUE
private val colors = arrayOf( private val colors = arrayOf(
Color.Red, Color.Blue, Color.Cyan, Color.Red, Color.Blue, Color.Cyan,
Color.Magenta, Color.Yellow, Color.Black Color.Magenta, Color.Yellow, Color.Black
) )
private var startTime = 0L
var width by mutableStateOf(0.dp) var width by mutableStateOf(0.dp)
var height by mutableStateOf(0.dp) var height by mutableStateOf(0.dp)
@ -36,30 +31,26 @@ class Game(val time: Time) {
var numBlocks by mutableStateOf(5f) var numBlocks by mutableStateOf(5f)
fun start() { fun start() {
previousTimeNanos = time.now()
startTime = previousTimeNanos
clicked = 0 clicked = 0
started = true started = true
finished = false finished = false
paused = false paused = false
pieces.clear() pieces.clear()
repeat(numBlocks.toInt()) { index -> repeat(numBlocks.toInt()) { index ->
pieces.add(PieceData(this, index * 1.5f + 5f, colors[index % colors.size]).also { piece -> pieces.add(
piece.position = Random.nextDouble(0.0, 100.0).toFloat() PieceData(
}) this,
index * 1.5f + 5f,
colors[index % colors.size]
).also { piece ->
piece.position = Random.nextDouble(0.0, 100.0).toFloat()
})
} }
} }
fun togglePause() { fun update(deltaTimeNanos: Long) {
paused = !paused elapsed += deltaTimeNanos
previousTimeNanos = time.now() pieces.forEach { it.update(deltaTimeNanos) }
}
fun update(nanos: Long) {
val dt = (nanos - previousTimeNanos).coerceAtLeast(0)
previousTimeNanos = nanos
elapsed = nanos - startTime
pieces.forEach { it.update(dt) }
} }
fun clicked(piece: PieceData) { fun clicked(piece: PieceData) {
@ -69,4 +60,4 @@ class Game(val time: Time) {
finished = true finished = true
} }
} }
} }

7
examples/falling-balls/shared/src/commonMain/kotlin/fallingBalls/Piece.kt → examples/graphics-2d/shared/src/commonMain/kotlin/fallingballs/Piece.kt

@ -1,7 +1,4 @@
/* package fallingballs
* 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.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
@ -53,4 +50,4 @@ data class PieceData(val game: Game, val velocity: Float, val color: Color) {
game.clicked(this) game.clicked(this)
} }
} }
} }

4
examples/minesweeper/shared/src/commonMain/kotlin/BoardView.kt → examples/graphics-2d/shared/src/commonMain/kotlin/minesweeper/BoardView.kt

@ -1,3 +1,5 @@
package minesweeper
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@ -43,4 +45,4 @@ fun BoardView(game: GameController) = with(GameStyles) {
} }
private fun GameStyles.getCellColor(cell: Cell): Color = private fun GameStyles.getCellColor(cell: Cell): Color =
if (cell.isOpened) openedCellColor else closedCellColor if (cell.isOpened) openedCellColor else closedCellColor

20
examples/minesweeper/shared/src/commonMain/kotlin/GameController.kt → examples/graphics-2d/shared/src/commonMain/kotlin/minesweeper/GameController.kt

@ -1,36 +1,50 @@
package minesweeper
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import kotlin.random.Random import kotlin.random.Random
class GameController(private val options: GameSettings, private val onWin: (() -> Unit)? = null, private val onLose: (() -> Unit)? = null) { class GameController(
private val options: GameSettings,
private val onWin: (() -> Unit)? = null,
private val onLose: (() -> Unit)? = null
) {
/** Number of rows in current board */ /** Number of rows in current board */
val rows: Int val rows: Int
get() = options.rows get() = options.rows
/** Number of columns in current board */ /** Number of columns in current board */
val columns: Int val columns: Int
get() = options.columns get() = options.columns
/** Number of bombs in current board */ /** Number of bombs in current board */
val bombs: Int val bombs: Int
get() = options.mines get() = options.mines
/** True if current game has started, false if game is finished or until first cell is opened or flagged */ /** True if current game has started, false if game is finished or until first cell is opened or flagged */
var running by mutableStateOf(false) var running by mutableStateOf(false)
private set private set
/** True if game is ended (win or lose) */ /** True if game is ended (win or lose) */
var finished by mutableStateOf(false) var finished by mutableStateOf(false)
private set private set
/** Total number of flags set on cells, used for calculation of number of remaining bombs */ /** Total number of flags set on cells, used for calculation of number of remaining bombs */
var flagsSet by mutableStateOf(0) var flagsSet by mutableStateOf(0)
private set private set
/** Number of remaining cells */ /** Number of remaining cells */
var cellsToOpen by mutableStateOf(options.rows * options.columns - options.mines) var cellsToOpen by mutableStateOf(options.rows * options.columns - options.mines)
private set private set
/** Game timer, increments every second while game is running */ /** Game timer, increments every second while game is running */
var seconds by mutableStateOf(0) var seconds by mutableStateOf(0)
private set private set
/** Global monotonic time, updated with [onTimeTick] */ /** Global monotonic time, updated with [onTimeTick] */
private var time = 0L private var time = 0L
/** The time when user starts the game by opening or flagging any cell */ /** The time when user starts the game by opening or flagging any cell */
private var startTime = 0L private var startTime = 0L
@ -59,7 +73,7 @@ class GameController(private val options: GameSettings, private val onWin: (() -
mines: Collection<Pair<Int, Int>>, mines: Collection<Pair<Int, Int>>,
onWin: (() -> Unit)? = null, onWin: (() -> Unit)? = null,
onLose: (() -> Unit)? = null onLose: (() -> Unit)? = null
) : this(GameSettings(rows, columns, mines.size), onWin, onLose) { ) : this(GameSettings(rows, columns, mines.size), onWin, onLose) {
for (row in cells) { for (row in cells) {
for (cell in row) { for (cell in row) {
cell.hasBomb = false cell.hasBomb = false
@ -290,4 +304,4 @@ class Cell(val row: Int, val column: Int) {
var isOpened by mutableStateOf(false) var isOpened by mutableStateOf(false)
var isFlagged by mutableStateOf(false) var isFlagged by mutableStateOf(false)
var bombsNear = 0 var bombsNear = 0
} }

2
examples/minesweeper/shared/src/commonMain/kotlin/gameInteraction.kt → examples/graphics-2d/shared/src/commonMain/kotlin/minesweeper/GameInteraction.kt

@ -1,3 +1,5 @@
package minesweeper
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable

21
examples/minesweeper/shared/src/commonMain/kotlin/game.kt → examples/graphics-2d/shared/src/commonMain/kotlin/minesweeper/MineSweeper.common.kt

@ -1,12 +1,14 @@
@file:Suppress("FunctionName") @file:Suppress("FunctionName")
import androidx.compose.runtime.* package minesweeper
import androidx.compose.foundation.* import androidx.compose.foundation.*
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material.* import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.* import androidx.compose.ui.*
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.unit.* import androidx.compose.ui.unit.*
import org.jetbrains.compose.resources.ExperimentalResourceApi import org.jetbrains.compose.resources.ExperimentalResourceApi
@ -39,7 +41,7 @@ object GameStyles {
} }
@Composable @Composable
fun Game(requestWindowSize: ((width: Dp, height: Dp) -> Unit)? = null) = MainLayout { fun MineSweeper(requestWindowSize: ((width: Dp, height: Dp) -> Unit)? = null) = MainLayout {
var message by remember { mutableStateOf<String?>(null) } var message by remember { mutableStateOf<String?>(null) }
val onWin = { message = "You win!" } val onWin = { message = "You win!" }
@ -101,9 +103,10 @@ fun Game(requestWindowSize: ((width: Dp, height: Dp) -> Unit)? = null) = MainLay
} }
// Cells // Cells
Box(modifier = Modifier Box(
.border(GameStyles.boardBorderWidth, Color.White) modifier = Modifier
.padding(GameStyles.boardPadding) .border(GameStyles.boardBorderWidth, Color.White)
.padding(GameStyles.boardPadding)
) { ) {
BoardView(game) BoardView(game)
} }
@ -119,6 +122,6 @@ fun Game(requestWindowSize: ((width: Dp, height: Dp) -> Unit)? = null) = MainLay
} }
@Composable @Composable
private fun MainLayout(block:@Composable ColumnScope.() -> Unit) { private fun MainLayout(block: @Composable ColumnScope.() -> Unit) {
Column { block() } Column { block() }
} }

6
examples/minesweeper/shared/src/commonMain/kotlin/widgets.kt → examples/graphics-2d/shared/src/commonMain/kotlin/minesweeper/Widgets.kt

@ -1,11 +1,13 @@
@file:Suppress("FunctionName") @file:Suppress("FunctionName")
package minesweeper
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -80,4 +82,4 @@ fun NewGameButton(text: String, onClick: () -> Unit) {
modifier = Modifier.padding(4.dp) modifier = Modifier.padding(4.dp)
) )
} }
} }

214
examples/visual-effects/shared/src/commonMain/kotlin/HappyNY.kt → examples/graphics-2d/shared/src/commonMain/kotlin/visualeffects/HappyNY.kt

@ -1,25 +1,45 @@
package org.jetbrains.compose.demo.visuals package visualeffects
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Surface import androidx.compose.material3.Surface
import androidx.compose.material.Text import androidx.compose.material3.Text
import androidx.compose.runtime.* import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.runtime.withFrameNanos
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.* import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.draw.scale
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.em import androidx.compose.ui.unit.em
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import org.jetbrains.compose.demo.visuals.platform.nanoTime import kotlin.math.PI
import kotlin.math.* import kotlin.math.abs
import kotlin.math.cos
import kotlin.math.sin
import kotlin.random.Random import kotlin.random.Random
const val width = 1200 const val width = 1200
@ -97,16 +117,16 @@ class DoubleRocket(val particle: Particle) {
state = STATE_SMALL_ROCKETS state = STATE_SMALL_ROCKETS
} }
fun move(time: Long, prevTime: Long) { fun move(timeElapsed: Long, deltaNanos: Long) {
if (rocket.state == rocket.STATE_ROCKET) { if (rocket.state == rocket.STATE_ROCKET) {
rocket.particle.move(time, prevTime) rocket.particle.move(deltaNanos)
rocket.particle.gravity(time, prevTime) rocket.particle.gravity(deltaNanos)
} else { } else {
rocket.rockets.forEach { rocket.rockets.forEach {
it.move(time, prevTime) it.move(timeElapsed, deltaNanos)
} }
} }
rocket.checkState(time) rocket.checkState(timeElapsed)
} }
@Composable @Composable
@ -126,8 +146,8 @@ class Rocket(val particle: Particle, val color: Color, val startTime: Long = 0)
var exploded = false var exploded = false
var parts: Array<Particle> = emptyArray() var parts: Array<Particle> = emptyArray()
fun checkExplode(time: Long) { fun checkExplode(timeElapsed: Long) {
if (time - startTime > 1200000000) { if (timeElapsed - startTime > 1200000000) {
explode() explode()
} }
} }
@ -136,7 +156,14 @@ class Rocket(val particle: Particle, val color: Color, val startTime: Long = 0)
parts = Array(rocketPartsCount) { parts = Array(rocketPartsCount) {
val v = 0.5f + 1.5 * random() val v = 0.5f + 1.5 * random()
val angle = 2 * PI * random() val angle = 2 * PI * random()
Particle(particle.x, particle.y, v * sin(angle) + particle.vx, v * cos(angle) + particle.vy, color, 1) Particle(
particle.x,
particle.y,
v * sin(angle) + particle.vx,
v * cos(angle) + particle.vy,
color,
1
)
} }
exploded = true exploded = true
} }
@ -149,15 +176,15 @@ class Rocket(val particle: Particle, val color: Color, val startTime: Long = 0)
return true return true
} }
fun move(time: Long, prevTime: Long) { fun move(timeElapsed: Long, deltaNanos: Long) {
if (!exploded) { if (!exploded) {
particle.move(time, prevTime) particle.move(deltaNanos)
particle.gravity(time, prevTime) particle.gravity(deltaNanos)
checkExplode(time) checkExplode(timeElapsed)
} else { } else {
parts.forEach { parts.forEach {
it.move(time, prevTime) it.move(deltaNanos)
it.gravity(time, prevTime) it.gravity(deltaNanos)
} }
} }
} }
@ -174,20 +201,30 @@ class Rocket(val particle: Particle, val color: Color, val startTime: Long = 0)
} }
} }
class Particle(var x: Double, var y: Double, var vx: Double, var vy: Double, val color: Color, val type: Int = 0) { class Particle(
fun move(time: Long, prevTime: Long) { var x: Double,
x = (x + vx * (time - prevTime) / 30000000) var y: Double,
y = (y + vy * (time - prevTime) / 30000000) var vx: Double,
var vy: Double,
val color: Color,
val type: Int = 0
) {
fun move(deltaNanos: Long) {
x = (x + vx * deltaNanos / 30000000)
y = (y + vy * deltaNanos / 30000000)
} }
fun gravity(time: Long, prevTime: Long) { fun gravity(deltaNanos: Long) {
vy = vy + 1.0f * (time - prevTime) / 300000000 vy = vy + 1.0f * deltaNanos / 300000000
} }
@Composable @Composable
fun draw() { fun draw() {
val alphaFactor = if (type == 0) 1.0f else 1 / (1 + abs(vy / 5)).toFloat() val alphaFactor = if (type == 0) 1.0f else 1 / (1 + abs(vy / 5)).toFloat()
Box(Modifier.size(5.dp).offset(x.dp, y.dp).alpha(alphaFactor).clip(CircleShape).background(color)) Box(
Modifier.size(5.dp).offset(x.dp, y.dp).alpha(alphaFactor).clip(CircleShape)
.background(color)
)
for (i in 1..5) { for (i in 1..5) {
Box( Box(
Modifier.size(4.dp).offset((x - vx / 2 * i).dp, (y - vy / 2 * i).dp) Modifier.size(4.dp).offset((x - vx / 2 * i).dp, (y - vy / 2 * i).dp)
@ -199,7 +236,10 @@ class Particle(var x: Double, var y: Double, var vx: Double, var vy: Double, val
val rocket = DoubleRocket(Particle(0.0, 1000.0, 2.1, -12.5, Color.White)) val rocket = DoubleRocket(Particle(0.0, 1000.0, 2.1, -12.5, Color.White))
fun prepareStarsAndSnowFlakes(stars: SnapshotStateList<Star>, snowFlakes: SnapshotStateList<SnowFlake>) { fun prepareStarsAndSnowFlakes(
stars: SnapshotStateList<Star>,
snowFlakes: SnapshotStateList<SnowFlake>
) {
for (i in 0..snowCount) { for (i in 0..snowCount) {
snowFlakes.add( snowFlakes.add(
SnowFlake( SnowFlake(
@ -214,7 +254,15 @@ fun prepareStarsAndSnowFlakes(stars: SnapshotStateList<Star>, snowFlakes: Snapsh
) )
) )
} }
val colors = arrayOf(Color.Red, Color.Yellow, Color.Green, Color.Yellow, Color.Cyan, Color.Magenta, Color.White) val colors = arrayOf(
Color.Red,
Color.Yellow,
Color.Green,
Color.Yellow,
Color.Cyan,
Color.Magenta,
Color.White
)
for (i in 0..starCount) { for (i in 0..starCount) {
stars.add( stars.add(
Star( Star(
@ -227,58 +275,58 @@ fun prepareStarsAndSnowFlakes(stars: SnapshotStateList<Star>, snowFlakes: Snapsh
} }
} }
@OptIn(ExperimentalComposeUiApi::class)
@Composable @Composable
fun NYContent() { fun NYContent() {
var time by remember { mutableStateOf(nanoTime()) }
var started by remember { mutableStateOf(false) } var started by remember { mutableStateOf(false) }
var startTime = remember { nanoTime() }
var prevTime by remember { mutableStateOf(nanoTime()) }
val snowFlakes = remember { mutableStateListOf<SnowFlake>() }
val stars = remember { mutableStateListOf<Star>() } val stars = remember { mutableStateListOf<Star>() }
var flickering2 by remember { mutableStateOf(true) } var flickering2 by remember { mutableStateOf(true) }
val snowFlakes = remember { mutableStateListOf<SnowFlake>() }
remember { prepareStarsAndSnowFlakes(stars, snowFlakes) } remember { prepareStarsAndSnowFlakes(stars, snowFlakes) }
var timeElapsedNanos by remember { mutableStateOf(0L) }
Surface( Surface(
modifier = Modifier.fillMaxSize().padding(5.dp).shadow(3.dp, RoundedCornerShape(20.dp)), modifier = Modifier.fillMaxSize().padding(5.dp).shadow(3.dp, RoundedCornerShape(20.dp)),
color = Color.Black, color = Color.Black,
shape = RoundedCornerShape(20.dp) shape = RoundedCornerShape(20.dp)
) { ) {
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
while (true) { while (true) {
var previousTimeNanos = withFrameNanos { it }
withFrameNanos { withFrameNanos {
prevTime = time val deltaTimeNanos = it - previousTimeNanos
time = it timeElapsedNanos += deltaTimeNanos
} previousTimeNanos = it
}
} if (flickering2) {
if (timeElapsedNanos > 15500000000) { //note, that startTime has been updated above
if (!started) { //animation starts with delay, so there is some time to start recording flickering2 = false
if (time - startTime in 7000000001..7099999999) println("ready!") }
if (time - startTime > 10000000000) { }
startTime = time //restarting timer if (started) {
started = true rocket.move(timeElapsedNanos, deltaTimeNanos)
} }
}
if (flickering2) { snowFlakes.forEach {
if (time - startTime > 15500000000) { //note, that startTime has been updated above var y = it.y + ((it.v * deltaTimeNanos) / 30000000).dp
flickering2 = false if (y > (height + 20).dp) {
y = -20.dp
}
it.y = y
}
}
} }
} }
if (started) {
rocket.move(time, prevTime)
}
with(LocalDensity.current) { with(LocalDensity.current) {
Box(Modifier.fillMaxSize()) { Box(Modifier.fillMaxSize()) {
snow(timeElapsedNanos, snowFlakes)
snow(time, prevTime, snowFlakes, startTime)
starrySky(stars) starrySky(stars)
Row(modifier = Modifier.fillMaxSize(), verticalAlignment = Alignment.Bottom, horizontalArrangement = Arrangement.Center) { Row(
modifier = Modifier.fillMaxSize(),
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.Center
) {
Text( Text(
fontSize = 10.em, fontSize = 10.em,
text = "202", text = "202",
@ -287,10 +335,10 @@ fun NYContent() {
color = Color.White color = Color.White
) )
val alpha = if (flickering2) flickeringAlpha(time) else 1.0f val alpha = if (flickering2) flickeringAlpha(timeElapsedNanos) else 1.0f
Text( Text(
fontSize = 10.em, fontSize = 10.em,
text = "3", text = "4",
modifier = Modifier.alpha(alpha).offset(0.dp, -15.dp), modifier = Modifier.alpha(alpha).offset(0.dp, -15.dp),
color = Color.White color = Color.White
) )
@ -300,14 +348,15 @@ fun NYContent() {
//HNY //HNY
var i = 0 var i = 0
val angle = (HNYString.length / 2 * 5) * -1.0f val angle = (HNYString.length / 2 * 5) * -1.0f
val color = colorHNY(time, startTime) val color = colorHNY(timeElapsedNanos)
HNYString.forEach { HNYString.forEach {
val alpha = alphaHNY(i, time, startTime) val alpha = alphaHNY(i, timeElapsedNanos)
Text( Text(
fontSize = 14.sp, fontSize = 14.sp,
text= it.toString(), text = it.toString(),
color = color, color = color,
modifier = Modifier.scale(5f).align(Alignment.Center).offset(0.dp, 85.dp) modifier = Modifier.scale(5f).align(Alignment.Center)
.offset(0.dp, 85.dp)
.rotate((angle + 5.0f * i)).offset(0.dp, -90.dp).alpha(alpha) .rotate((angle + 5.0f * i)).offset(0.dp, -90.dp).alpha(alpha)
) )
i++ i++
@ -326,9 +375,9 @@ fun NYContent() {
} }
} }
fun colorHNY(time: Long, startTime: Long): Color { fun colorHNY(timeElapsed: Long): Color {
val periodLength = 60 val periodLength = 60
val offset = ((time - startTime) / 80000000).toFloat() / periodLength val offset = (timeElapsed.toFloat() / 80000000) / periodLength
val color1 = Color.Red val color1 = Color.Red
val color2 = Color.Yellow val color2 = Color.Yellow
val color3 = Color.Magenta val color3 = Color.Magenta
@ -348,16 +397,16 @@ fun blend(color1: Color, color2: Color, fraction: Float): Color {
) )
} }
fun alphaHNY(i: Int, time: Long, startTime: Long): Float { fun alphaHNY(i: Int, timeElapsed: Long): Float {
val period = period(time, startTime, 200) - i val period = period(timeElapsed, 200) - i
if (period < 0) return 0.0f if (period < 0) return 0.0f
if (period > 10) return 1.0f if (period > 10) return 1.0f
return 0.1f * period return 0.1f * period
} }
fun period(time: Long, startTime: Long, periodLength: Int, speed: Int = 1): Int { fun period(timeElapsed: Long, periodLength: Int, speed: Int = 1): Int {
val period = 200000000 / speed val period = 200000000 / speed
return (((time - startTime) / period) % periodLength).toInt() return ((timeElapsed / period) % periodLength).toInt()
} }
fun flickeringAlpha(time: Long): Float { fun flickeringAlpha(time: Long): Float {
@ -384,17 +433,15 @@ fun star(x: Dp, y: Dp, color: Color = Color.White, size: Dp) {
} }
@Composable @Composable
fun snow(time: Long, prevTime: Long, snowFlakes: SnapshotStateList<SnowFlake>, startTime: Long) { fun snow(timeElapsed: Long, snowFlakes: SnapshotStateList<SnowFlake>) {
val deltaAngle = (time - startTime) / 100000000 val deltaAngle = timeElapsed.toFloat() / 100000000
with(LocalDensity.current) { with(LocalDensity.current) {
snowFlakes.forEach { snowFlakes.forEach {
var y = it.y + ((it.v * (time - prevTime)) / 300000000).dp val x = it.x + (15 * sin(timeElapsed.toDouble() / 3000000000 + it.phase)).dp
if (y > (height + 20).dp) { snowFlake(
y = -20.dp Modifier.offset(x, it.y).scale(it.scale).rotate(it.angle + deltaAngle * it.rotate),
} it.alpha
it.y = y )
val x = it.x + (15 * sin(time.toDouble() / 3000000000 + it.phase)).dp
snowFlake(Modifier.offset(x, y).scale(it.scale).rotate(it.angle + deltaAngle * it.rotate), it.alpha)
} }
} }
} }
@ -416,7 +463,8 @@ fun snowFlake(modifier: Modifier, alpha: Float = 0.8f) {
fun snowFlakeInt(level: Int, angle: Float, shiftX: Dp, shiftY: Dp, alpha: Float) { fun snowFlakeInt(level: Int, angle: Float, shiftX: Dp, shiftY: Dp, alpha: Float) {
if (level > 3) return if (level > 3) return
Box( Box(
Modifier.offset(shiftX, shiftY).rotate(angle).width(100.dp).height(10.dp).scale(0.6f).alpha(1f) Modifier.offset(shiftX, shiftY).rotate(angle).width(100.dp).height(10.dp).scale(0.6f)
.alpha(1f)
.background(Color.White.copy(alpha = alpha)) .background(Color.White.copy(alpha = alpha))
) { ) {
snowFlakeInt(level + 1, 30f, 12.dp, 20.dp, alpha * 0.8f) snowFlakeInt(level + 1, 30f, 12.dp, 20.dp, alpha * 0.8f)

14
examples/visual-effects/shared/src/commonMain/kotlin/platform/PointerEventKind.kt → examples/graphics-2d/shared/src/commonMain/kotlin/visualeffects/PointerEvent.common.kt

@ -1,4 +1,4 @@
package org.jetbrains.compose.demo.visuals.platform package visualeffects
import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.forEachGesture import androidx.compose.foundation.gestures.forEachGesture
@ -6,6 +6,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.PointerEvent import androidx.compose.ui.input.pointer.PointerEvent
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
enum class PointerEventKind { enum class PointerEventKind {
Move, Move,
In, In,
@ -14,7 +15,10 @@ enum class PointerEventKind {
class Position(val x: Int, val y: Int) class Position(val x: Int, val y: Int)
expect fun Modifier.onPointerEvent(eventKind: PointerEventKind, onEvent: Position.() -> Unit): Modifier expect fun Modifier.onPointerEvent(
eventKind: PointerEventKind,
onEvent: Position.() -> Unit
): Modifier
fun Modifier.onPointerEventMobileImpl( fun Modifier.onPointerEventMobileImpl(
eventKind: PointerEventKind, eventKind: PointerEventKind,
@ -37,7 +41,10 @@ fun Modifier.onPointerEventMobileImpl(
val event: PointerEvent = awaitPointerEvent() val event: PointerEvent = awaitPointerEvent()
if (eventKind == PointerEventKind.Move) { if (eventKind == PointerEventKind.Move) {
Position(event.changes.first().position.x.toInt(), event.changes.first().position.y.toInt()).onEvent() Position(
event.changes.first().position.x.toInt(),
event.changes.first().position.y.toInt()
).onEvent()
} }
} while (event.changes.any { it.pressed }) } while (event.changes.any { it.pressed })
@ -49,4 +56,3 @@ fun Modifier.onPointerEventMobileImpl(
} }
} }
} }

30
examples/visual-effects/shared/src/commonMain/kotlin/RotatingWords.kt → examples/graphics-2d/shared/src/commonMain/kotlin/visualeffects/RotatingWords.kt

@ -1,11 +1,11 @@
package org.jetbrains.compose.demo.visuals package visualeffects
import androidx.compose.animation.core.* import androidx.compose.animation.core.*
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@ -62,19 +62,27 @@ fun Words() {
val color3 = Color(0xFD, 0xB6, 0x0D) val color3 = Color(0xFD, 0xB6, 0x0D)
val color4 = Color(0xFC, 0xF8, 0x4A) val color4 = Color(0xFC, 0xF8, 0x4A)
Column(modifier = Modifier Column(
.fillMaxSize() modifier = Modifier
.padding(16.dp) .fillMaxSize()
.padding(16.dp)
) { ) {
Word(position = baseRu, angle = angle, scale = scale, text = "Ваш", color = color1) Word(position = baseRu, angle = angle, scale = scale, text = "Ваш", color = color1)
Word(position = baseEn, angle = angle, scale = scale, text = "Your", color = color2) Word(position = baseEn, angle = angle, scale = scale, text = "Your", color = color2)
Word(position = baseCh, angle = angle, scale = scale, text = "您的", color = color3) Word(position = baseCh, angle = angle, scale = scale, text = "您的", color = color3)
Word(position = baseJa, angle = angle, scale = scale, text = "あなたの", color = color4) Word(position = baseJa, angle = angle, scale = scale, text = "あなたの", color = color4)
Word(position = baseText, angle = 0f, scale = 6f, text = " Compose\nMultiplatform", color = Color(52, 67, 235), Word(
alpha = 0.4f) position = baseText,
angle = 0f,
scale = 6f,
text = " Compose\nMultiplatform",
color = Color(52, 67, 235),
alpha = 0.4f
)
val size = 80.dp * scale val size = 80.dp * scale
Image(logoImg, contentDescription = "Logo", Image(
logoImg, contentDescription = "Logo",
modifier = Modifier modifier = Modifier
.offset(baseLogo.x - size / 2, baseLogo.y - size / 2) .offset(baseLogo.x - size / 2, baseLogo.y - size / 2)
.size(size) .size(size)
@ -84,8 +92,10 @@ fun Words() {
} }
@Composable @Composable
fun Word(position: DpOffset, angle: Float, scale: Float, text: String, fun Word(
color: Color, alpha: Float = 0.8f) { position: DpOffset, angle: Float, scale: Float, text: String,
color: Color, alpha: Float = 0.8f
) {
Text( Text(
modifier = Modifier modifier = Modifier
.offset(position.x, position.y) .offset(position.x, position.y)

59
examples/visual-effects/shared/src/commonMain/kotlin/WaveEffect.kt → examples/graphics-2d/shared/src/commonMain/kotlin/visualeffects/WaveEffect.kt

@ -1,9 +1,9 @@
package org.jetbrains.compose.demo.visuals package visualeffects
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -11,9 +11,6 @@ import androidx.compose.ui.draw.*
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import org.jetbrains.compose.demo.visuals.platform.PointerEventKind
import org.jetbrains.compose.demo.visuals.platform.nanoTime
import org.jetbrains.compose.demo.visuals.platform.onPointerEvent
import kotlin.math.* import kotlin.math.*
@OptIn(ExperimentalComposeUiApi::class) @OptIn(ExperimentalComposeUiApi::class)
@ -25,23 +22,31 @@ fun WaveEffectGrid() {
var centerY by remember { mutableStateOf(900) } var centerY by remember { mutableStateOf(900) }
var vX by remember { mutableStateOf(0) } var vX by remember { mutableStateOf(0) }
var vY by remember { mutableStateOf(0) } var vY by remember { mutableStateOf(0) }
var timeElapsedNanos by remember { mutableStateOf(0L) }
LaunchedEffect(Unit) {
while (true) {
var previousTimeNanos = withFrameNanos { it }
withFrameNanos {
val deltaTimeNanos = it - previousTimeNanos
timeElapsedNanos += deltaTimeNanos
previousTimeNanos = it
if (State.entered) {
centerX = (centerX + vX * deltaTimeNanos / 1000000000).toInt()
if (centerX < -100) centerX = -100
if (centerX > 2600) centerX = 2600
vX =
(vX * (1 - deltaTimeNanos.toDouble() / 500000000) + 10 * (mouseX - centerX) * deltaTimeNanos / 1000000000).toInt()
centerY = (centerY + vY * deltaTimeNanos / 1000000000).toInt()
if (centerY < -100) centerY = -100
if (centerY > 1800) centerY = 1800
vY =
(vY * (1 - deltaTimeNanos.toDouble() / 500000000) + 5 * (mouseY - centerY) * deltaTimeNanos / 1000000000).toInt()
var time by remember { mutableStateOf(nanoTime()) } }
var prevTime by remember { mutableStateOf(nanoTime()) } }
}
if (State.entered) {
centerX = (centerX + vX * (time - prevTime) / 1000000000).toInt()
if (centerX < -100) centerX = -100
if (centerX > 2600) centerX = 2600
vX =
(vX * (1 - (time - prevTime).toDouble() / 500000000) + 10 * (mouseX - centerX) * (time - prevTime) / 1000000000).toInt()
centerY = (centerY + vY * (time - prevTime) / 1000000000).toInt()
if (centerY < -100) centerY = -100
if (centerY > 1800) centerY = 1800
vY =
(vY * (1 - (time - prevTime).toDouble() / 500000000) + 5 * (mouseY - centerY) * (time - prevTime) / 1000000000).toInt()
prevTime = time
} }
Surface( Surface(
@ -71,8 +76,8 @@ fun WaveEffectGrid() {
x = if (evenRow) 10 + shift else 10 x = if (evenRow) 10 + shift else 10
while (x < 1190) { while (x < 1190) {
val size: Int = size(x, y, pointerOffsetX, pointerOffsety) val size: Int = size(x, y, pointerOffsetX, pointerOffsety)
val color = boxColor(x, y, time, pointerOffsetX, pointerOffsety) val color = boxColor(x, y, timeElapsedNanos, pointerOffsetX, pointerOffsety)
Dot(size, Modifier.offset(x.dp, y.dp), color, time) Dot(size, Modifier.offset(x.dp, y.dp), color, timeElapsedNanos)
x += shift * 2 x += shift * 2
} }
y += shift y += shift
@ -81,14 +86,6 @@ fun WaveEffectGrid() {
HighPanel(pointerOffsetX, pointerOffsety) HighPanel(pointerOffsetX, pointerOffsety)
} }
LaunchedEffect(Unit) {
while (true) {
withFrameNanos {
time = it
}
}
}
} }
} }

0
examples/minesweeper/shared/src/commonMain/resources/assets/clock.png → examples/graphics-2d/shared/src/commonMain/resources/assets/clock.png

Before

Width:  |  Height:  |  Size: 896 B

After

Width:  |  Height:  |  Size: 896 B

0
examples/minesweeper/shared/src/commonMain/resources/assets/flag.png → examples/graphics-2d/shared/src/commonMain/resources/assets/flag.png

Before

Width:  |  Height:  |  Size: 780 B

After

Width:  |  Height:  |  Size: 780 B

0
examples/minesweeper/shared/src/commonMain/resources/assets/mine.png → examples/graphics-2d/shared/src/commonMain/resources/assets/mine.png

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

36
examples/graphics-2d/shared/src/commonMain/resources/compose-community-primary.xml

@ -0,0 +1,36 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="600dp"
android:height="600dp"
android:viewportWidth="600"
android:viewportHeight="600">
<path
android:pathData="M301.21,418.53C300.97,418.54 300.73,418.56 300.49,418.56C297.09,418.59 293.74,417.72 290.79,416.05L222.6,377.54C220.63,376.43 219,374.82 217.85,372.88C216.7,370.94 216.09,368.73 216.07,366.47L216.07,288.16C216.06,287.32 216.09,286.49 216.17,285.67C216.38,283.54 216.91,281.5 217.71,279.6L199.29,268.27L177.74,256.19C175.72,260.43 174.73,265.23 174.78,270.22L174.79,387.05C174.85,393.89 178.57,400.2 184.53,403.56L286.26,461.02C290.67,463.51 295.66,464.8 300.73,464.76C300.91,464.76 301.09,464.74 301.27,464.74C301.24,449.84 301.22,439.23 301.22,439.23L301.21,418.53Z"
android:fillColor="#041619"
android:fillType="nonZero"/>
<path
android:pathData="M409.45,242.91L312.64,188.23C303.64,183.15 292.58,183.26 283.68,188.51L187.92,245C183.31,247.73 179.93,251.62 177.75,256.17L177.74,256.19L199.29,268.27L217.71,279.6C217.83,279.32 217.92,279.02 218.05,278.74C218.24,278.36 218.43,277.98 218.64,277.62C219.06,276.88 219.52,276.18 220.04,275.51C221.37,273.8 223.01,272.35 224.87,271.25L289.06,233.39C290.42,232.59 291.87,231.96 293.39,231.51C295.53,230.87 297.77,230.6 300,230.72C302.98,230.88 305.88,231.73 308.47,233.2L373.37,269.85C375.54,271.08 377.49,272.68 379.13,274.57C379.68,275.19 380.18,275.85 380.65,276.53C380.86,276.84 381.05,277.15 381.24,277.47L397.79,266.39L420.34,252.93L420.31,252.88C417.55,248.8 413.77,245.35 409.45,242.91Z"
android:fillColor="#37BF6E"
android:fillType="nonZero"/>
<path
android:pathData="M381.24,277.47C381.51,277.92 381.77,278.38 382.01,278.84C382.21,279.24 382.39,279.65 382.57,280.06C382.91,280.88 383.19,281.73 383.41,282.59C383.74,283.88 383.92,285.21 383.93,286.57L383.93,361.1C383.96,363.95 383.35,366.77 382.16,369.36C381.93,369.86 381.69,370.35 381.42,370.83C379.75,373.79 377.32,376.27 374.39,378L310.2,415.87C307.47,417.48 304.38,418.39 301.21,418.53L301.22,439.23C301.22,439.23 301.24,449.84 301.27,464.74C306.1,464.61 310.91,463.3 315.21,460.75L410.98,404.25C419.88,399 425.31,389.37 425.22,379.03L425.22,267.85C425.17,262.48 423.34,257.34 420.34,252.93L397.79,266.39L381.24,277.47Z"
android:fillColor="#3870B2"
android:fillType="nonZero"/>
<path
android:pathData="M177.75,256.17C179.93,251.62 183.31,247.73 187.92,245L283.68,188.51C292.58,183.26 303.64,183.15 312.64,188.23L409.45,242.91C413.77,245.35 417.55,248.8 420.31,252.88L420.34,252.93L498.59,206.19C494.03,199.46 487.79,193.78 480.67,189.75L320.86,99.49C306.01,91.1 287.75,91.27 273.07,99.95L114.99,193.2C107.39,197.69 101.81,204.11 98.21,211.63L177.74,256.19L177.75,256.17ZM301.27,464.74C301.09,464.74 300.91,464.76 300.73,464.76C295.66,464.8 290.67,463.51 286.26,461.02L184.53,403.56C178.57,400.2 174.85,393.89 174.79,387.05L174.78,270.22C174.73,265.23 175.72,260.43 177.74,256.19L98.21,211.63C94.86,218.63 93.23,226.58 93.31,234.82L93.31,427.67C93.42,438.97 99.54,449.37 109.4,454.92L277.31,549.77C284.6,553.88 292.84,556.01 301.2,555.94L301.2,555.8C301.39,543.78 301.33,495.26 301.27,464.74Z"
android:strokeWidth="10"
android:fillColor="#00000000"
android:strokeColor="#083042"
android:fillType="nonZero"/>
<path
android:pathData="M498.59,206.19L420.34,252.93C423.34,257.34 425.17,262.48 425.22,267.85L425.22,379.03C425.31,389.37 419.88,399 410.98,404.25L315.21,460.75C310.91,463.3 306.1,464.61 301.27,464.74C301.33,495.26 301.39,543.78 301.2,555.8L301.2,555.94C309.48,555.87 317.74,553.68 325.11,549.32L483.18,456.06C497.87,447.39 506.85,431.49 506.69,414.43L506.69,230.91C506.6,222.02 503.57,213.5 498.59,206.19Z"
android:strokeWidth="10"
android:fillColor="#00000000"
android:strokeColor="#083042"
android:fillType="nonZero"/>
<path
android:pathData="M301.2,555.94C292.84,556.01 284.6,553.88 277.31,549.76L109.4,454.92C99.54,449.37 93.42,438.97 93.31,427.67L93.31,234.82C93.23,226.58 94.86,218.63 98.21,211.63C101.81,204.11 107.39,197.69 114.99,193.2L273.07,99.95C287.75,91.27 306.01,91.1 320.86,99.49L480.67,189.75C487.79,193.78 494.03,199.46 498.59,206.19C503.57,213.5 506.6,222.02 506.69,230.91L506.69,414.43C506.85,431.49 497.87,447.39 483.18,456.06L325.11,549.32C317.74,553.68 309.48,555.87 301.2,555.94Z"
android:strokeWidth="10"
android:fillColor="#00000000"
android:strokeColor="#083042"
android:fillType="nonZero"/>
</vector>

2
examples/minesweeper/shared/src/commonTest/kotlin/GameControllerTest.kt → examples/graphics-2d/shared/src/commonTest/kotlin/minesweeper/GameControllerTest.kt

@ -1,3 +1,5 @@
package minesweeper
import kotlin.test.* import kotlin.test.*
class GameControllerTest { class GameControllerTest {

3
examples/graphics-2d/shared/src/desktopMain/kotlin/minesweeper/MineSweeper.desktop.kt

@ -0,0 +1,3 @@
package minesweeper
actual fun hasRightClick() = true

4
examples/visual-effects/shared/src/desktopMain/kotlin/platform/PointerEventKind.desktop.kt → examples/graphics-2d/shared/src/desktopMain/kotlin/visualeffects/PointerEvent.desktop.kt

@ -1,4 +1,4 @@
package org.jetbrains.compose.demo.visuals.platform package visualeffects
import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -20,4 +20,4 @@ actual fun Modifier.onPointerEvent(
return this.onPointerEvent(eventType) { return this.onPointerEvent(eventType) {
Position(it.changes.first().position.x.toInt(), it.changes.first().position.y.toInt()).onEvent() Position(it.changes.first().position.x.toInt(), it.changes.first().position.y.toInt()).onEvent()
} }
} }

6
examples/graphics-2d/shared/src/iosMain/kotlin/main.ios.kt

@ -0,0 +1,6 @@
import androidx.compose.ui.window.ComposeUIViewController
import platform.UIKit.UIViewController
fun MainViewController(): UIViewController = ComposeUIViewController {
Graphics2D()
}

3
examples/graphics-2d/shared/src/iosMain/kotlin/minesweeper/MineSweeper.ios.kt

@ -0,0 +1,3 @@
package minesweeper
actual fun hasRightClick() = false

2
examples/visual-effects/shared/src/iosMain/kotlin/platform/PointerEventKind.ios.kt → examples/graphics-2d/shared/src/iosMain/kotlin/visualeffects/PointerEvent.ios.kt

@ -1,4 +1,4 @@
package org.jetbrains.compose.demo.visuals.platform package visualeffects
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier

6
examples/graphics-2d/shared/src/jsMain/kotlin/main.js.kt

@ -0,0 +1,6 @@
import androidx.compose.runtime.Composable
@Composable
fun MainView() {
Graphics2D()
}

3
examples/graphics-2d/shared/src/jsMain/kotlin/minesweeper/MineSweeepr.js.kt

@ -0,0 +1,3 @@
package minesweeper
actual fun hasRightClick() = false

26
examples/graphics-2d/shared/src/jsMain/kotlin/visualeffects/PointerEvent.js.kt

@ -0,0 +1,26 @@
package visualeffects
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.onPointerEvent
@OptIn(ExperimentalComposeUiApi::class)
actual fun Modifier.onPointerEvent(
eventKind: PointerEventKind,
onEvent: Position.() -> Unit
): Modifier {
val eventType: PointerEventType = when (eventKind) {
PointerEventKind.Move -> PointerEventType.Move
PointerEventKind.In -> PointerEventType.Enter
PointerEventKind.Out -> PointerEventType.Exit
}
return this.onPointerEvent(eventType) {
Position(
it.changes.first().position.x.toInt(),
it.changes.first().position.y.toInt()
).onEvent()
}
}

11
examples/graphics-2d/shared/src/macosMain/kotlin/main.macos.kt

@ -0,0 +1,11 @@
import androidx.compose.ui.window.Window
import platform.AppKit.NSApp
import platform.AppKit.NSApplication
fun main() {
NSApplication.sharedApplication()
Window("Graphics2D") {
Graphics2D()
}
NSApp?.run()
}

3
examples/graphics-2d/shared/src/macosMain/kotlin/minesweeper/MineSweeper.macos.kt

@ -0,0 +1,3 @@
package minesweeper
actual fun hasRightClick() = false

27
examples/graphics-2d/shared/src/macosMain/kotlin/visualeffects/PointerEventKind.macos.kt

@ -0,0 +1,27 @@
package visualeffects
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.onPointerEvent
@OptIn(ExperimentalComposeUiApi::class)
actual fun Modifier.onPointerEvent(
eventKind: PointerEventKind,
onEvent: Position.() -> Unit
): Modifier {
val eventType: PointerEventType = when (eventKind) {
PointerEventKind.Move -> PointerEventType.Move
PointerEventKind.In -> PointerEventType.Enter
PointerEventKind.Out -> PointerEventType.Exit
}
return this.onPointerEvent(eventType) {
Position(
it.changes.first().position.x.toInt(),
it.changes.first().position.y.toInt()
).onEvent()
}
}

7
examples/minesweeper/.gitignore vendored

@ -1,7 +0,0 @@
local.properties
iosApp/Podfile.lock
iosApp/Pods/*
iosApp/iosApp.xcworkspace/*
iosApp/iosApp.xcodeproj/*
!iosApp/iosApp.xcodeproj/project.pbxproj
shared/shared.podspec

28
examples/minesweeper/.run/desktopApp.run.xml

@ -1,28 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="desktopApp" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="env">
<map>
<entry key="DEVELOPER_DIR" value="/Applications/Xcode.app/Contents/Developer" />
</map>
</option>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="desktopApp:run" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration>
</component>

25
examples/minesweeper/README.md

@ -1,25 +0,0 @@
# Minesweeper
A simple game powered by Compose Multiplatform.
Game can run on Android, iOS, desktop or in a browser.
## Setting up your development environment
To setup the environment, please consult these [instructions](https://github.com/JetBrains/compose-multiplatform-template#setting-up-your-development-environment).
## How to run
Choose a run configuration for an appropriate target in Android Studio and run it.
![run-configurations.png](run-configurations.png)
## Run on desktop via Gradle
`./gradlew desktopApp:run`
## Run native on MacOS
`./gradlew runDebugExecutableMacosX64` (Works on Intel processors)
## Credits
<div>Icons made by <a href="https://www.flaticon.com/authors/creaticca-creative-agency" title="Creaticca Creative Agency">Creaticca Creative Agency</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a></div>

35
examples/minesweeper/androidApp/build.gradle.kts

@ -1,35 +0,0 @@
plugins {
kotlin("multiplatform")
id("com.android.application")
id("org.jetbrains.compose")
}
kotlin {
androidTarget()
sourceSets {
val androidMain by getting {
dependencies {
implementation(project(":shared"))
}
}
}
}
android {
compileSdk = 34
namespace = "org.jetbrains.minesweeper"
defaultConfig {
applicationId = "org.jetbrains.ComposeMinesweeper"
minSdk = 26
targetSdk = 34
versionCode = 1
versionName = "1.0"
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlin {
jvmToolchain(17)
}
}

21
examples/minesweeper/androidApp/src/androidMain/AndroidManifest.xml

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
<activity
android:exported="true"
android:name="MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

15
examples/minesweeper/androidApp/src/androidMain/kotlin/MainActivity.kt

@ -1,15 +0,0 @@
package org.jetbrains.minesweeper
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import MainView
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MainView()
}
}
}

3
examples/minesweeper/androidApp/src/androidMain/res/values/strings.xml

@ -1,3 +0,0 @@
<resources>
<string name="app_name">Minesweeper</string>
</resources>

18
examples/minesweeper/build.gradle.kts

@ -1,18 +0,0 @@
plugins {
// this is necessary to avoid the plugins to be loaded multiple times
// in each subproject's classloader
kotlin("jvm") apply false
kotlin("multiplatform") apply false
kotlin("android") apply false
id("com.android.application") apply false
id("com.android.library") apply false
id("org.jetbrains.compose") apply false
}
allprojects {
repositories {
google()
mavenCentral()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
}

35
examples/minesweeper/desktopApp/build.gradle.kts

@ -1,35 +0,0 @@
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
}
kotlin {
jvm {}
sourceSets {
val jvmMain by getting {
dependencies {
implementation(compose.desktop.currentOs)
implementation(project(":shared"))
}
}
}
}
compose.desktop {
application {
mainClass = "MainKt"
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "Minesweeper"
packageVersion = "1.0.0"
windows {
menuGroup = "Compose Examples"
// see https://wixtoolset.org/documentation/manual/v3/howtos/general/generate_guids.html
upgradeUuid = "2bf169f9-d851-49f0-b3a1-308966d473ca"
}
}
}
}

22
examples/minesweeper/desktopApp/src/jvmMain/kotlin/Main.kt

@ -1,22 +0,0 @@
/*
* 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.ExperimentalComposeUiApi
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.window.*
fun main() = application {
val windowState = rememberWindowState()
Window(
onCloseRequest = ::exitApplication,
resizable = false,
title = "Minesweeper",
icon = painterResource("assets/mine.png"),
state = windowState
) {
MainView(windowState)
}
}

19
examples/minesweeper/gradle.properties

@ -1,19 +0,0 @@
kotlin.code.style=official
xcodeproj=./iosApp
android.useAndroidX=true
org.gradle.jvmargs=-Xmx3g
org.jetbrains.compose.experimental.jscanvas.enabled=true
org.jetbrains.compose.experimental.macos.enabled=true
org.jetbrains.compose.experimental.uikit.enabled=true
kotlin.native.useEmbeddableCompilerJar=true
kotlin.mpp.androidSourceSetLayoutVersion=2
# Enable kotlin/native experimental memory model
kotlin.native.binary.memoryModel=experimental
kotlin.version=1.9.10
agp.version=8.0.2
compose.version=1.5.2
# TODO: remove when switching to 1.9.10. See: https://youtrack.jetbrains.com/issue/KT-60852
# usage: ./gradlew :jsApp:jsBrowserRun -Pworkaround.kotlin.js.kt60852=true
# setting it to `true` breaks other targets (see shared/build.gradle.kts), so it should be used to run the web app only.
workaround.kotlin.js.kt60852=false

BIN
examples/minesweeper/gradle/wrapper/gradle-wrapper.jar vendored

Binary file not shown.

5
examples/minesweeper/gradle/wrapper/gradle-wrapper.properties vendored

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

240
examples/minesweeper/gradlew vendored

@ -1,240 +0,0 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original 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 POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${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 "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# 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 ;; #(
MSYS* | 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" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

91
examples/minesweeper/gradlew.bat vendored

@ -1,91 +0,0 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save