├── .gitignore
├── README.MD
├── build.gradle.kts
├── composeApp
├── build.gradle.kts
├── composeApp.podspec
└── src
│ ├── androidMain
│ ├── AndroidManifest.xml
│ └── kotlin
│ │ └── com
│ │ └── nezihyilmaz
│ │ └── multiplatform
│ │ └── particles
│ │ └── App.android.kt
│ ├── commonMain
│ └── kotlin
│ │ └── com
│ │ └── nezihyilmaz
│ │ └── multiplatform
│ │ └── particles
│ │ ├── ResourceProvider.kt
│ │ ├── SVGCoordinates.kt
│ │ ├── controller
│ │ └── Engine.kt
│ │ ├── physics
│ │ ├── ForceField.kt
│ │ ├── Particle.kt
│ │ └── Vector2.kt
│ │ └── ui
│ │ ├── Color.kt
│ │ ├── Theme.kt
│ │ ├── composable
│ │ ├── App.kt
│ │ ├── Fabric.kt
│ │ ├── Renderer.kt
│ │ ├── Space.kt
│ │ └── Time.kt
│ │ └── modifier
│ │ └── GestureHandler.kt
│ ├── desktopMain
│ └── kotlin
│ │ ├── com
│ │ └── nezihyilmaz
│ │ │ └── multiplatform
│ │ │ └── particles
│ │ │ └── App.jvm.kt
│ │ └── main.kt
│ ├── iosMain
│ └── kotlin
│ │ ├── com
│ │ └── nezihyilmaz
│ │ │ └── multiplatform
│ │ │ └── particles
│ │ │ └── App.ios.kt
│ │ └── main.kt
│ └── jsMain
│ ├── kotlin
│ ├── BrowserViewportWindow.kt
│ ├── com
│ │ └── nezihyilmaz
│ │ │ └── multiplatform
│ │ │ └── particles
│ │ │ └── App.js.kt
│ └── main.kt
│ └── resources
│ └── index.html
├── gradle.bat
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── iosApp
├── Podfile
├── Podfile.lock
├── iosApp.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ └── contents.xcworkspacedata
├── iosApp.xcworkspace
│ └── contents.xcworkspacedata
└── iosApp
│ ├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── Contents.json
│ ├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
│ └── iosApp.swift
├── screenshot
├── ss_desktop_action.png
└── ss_desktop_idle.png
└── settings.gradle.kts
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | *.iml
3 | .gradle
4 | .idea
5 | .DS_Store
6 | build
7 | */build
8 | captures
9 | .externalNativeBuild
10 | .cxx
11 | local.properties
12 | xcuserdata/
13 | Pods/
14 | *.jks
15 | *yarn.lock
16 |
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 |
2 | *This projects skeleton was created by [Compose Multiplatform Template Wizard](https://terrakok.github.io/Compose-Multiplatform-Wizard/).*
3 |
4 | # Particle Physics Project | Compose Multiplatform
5 |
6 | This project is a basic physics simulation built on Multiplatform Compose Canvas. It has the same capabilities as any other vector driven particle system.
7 |
8 | Showcase example is a text/image rendering using small particles and a radial repelling force (created by pointer).
9 |
10 | 
11 |
12 | 
13 |
14 | ## Before running!
15 | - check your system with (KDoctor)[https://github.com/Kotlin/kdoctor]
16 | - install JDK 8 on your machine
17 | - add `local.properties` file to the project root and set a path to Android SDK there
18 | - run `./gradlew podInstall` in the project root
19 |
20 | ### Android
21 | To run the application on android device/emulator:
22 | - open project in Android Studio and run imported android run configuration
23 |
24 | To build the application bundle:
25 | - run `./gradlew :composeApp:assembleDebug`
26 | - find `.apk` file in `composeApp/build/outputs/apk/debug/composeApp-debug.apk`
27 |
28 | ### Desktop
29 | Run the desktop application: `./gradlew :composeApp:run`
30 |
31 | ### iOS
32 | To run the application on iPhone device/simulator:
33 | - Open `iosApp/iosApp.xcworkspace` in Xcode and run standard configuration
34 | - Or use (Kotlin Multiplatform Mobile plugin)[https://plugins.jetbrains.com/plugin/14936-kotlin-multiplatform-mobile] for Android Studio
35 |
36 | ### Browser
37 | Run the browser application: `./gradlew :composeApp:jsBrowserDevelopmentRun`
38 |
39 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.multiplatform).apply(false)
3 | alias(libs.plugins.compose).apply(false)
4 | alias(libs.plugins.cocoapods).apply(false)
5 | alias(libs.plugins.android.application).apply(false)
6 | alias(libs.plugins.libres).apply(false)
7 | alias(libs.plugins.serialization).apply(false)
8 | }
9 |
--------------------------------------------------------------------------------
/composeApp/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.compose.desktop.application.dsl.TargetFormat
2 |
3 | plugins {
4 | alias(libs.plugins.multiplatform)
5 | alias(libs.plugins.compose)
6 | alias(libs.plugins.cocoapods)
7 | alias(libs.plugins.android.application)
8 | alias(libs.plugins.libres)
9 | alias(libs.plugins.serialization)
10 | }
11 |
12 | kotlin {
13 | android {
14 | compilations.all {
15 | kotlinOptions {
16 | jvmTarget = "1.8"
17 | }
18 | }
19 | }
20 |
21 | jvm("desktop")
22 |
23 | js {
24 | browser()
25 | binaries.executable()
26 | }
27 |
28 | iosX64()
29 | iosArm64()
30 | iosSimulatorArm64()
31 |
32 | cocoapods {
33 | version = "1.0.0"
34 | summary = "Compose application framework"
35 | homepage = "empty"
36 | ios.deploymentTarget = "11.0"
37 | podfile = project.file("../iosApp/Podfile")
38 | framework {
39 | baseName = "ComposeApp"
40 | isStatic = true
41 | }
42 | }
43 |
44 | sourceSets {
45 | val commonMain by getting {
46 | dependencies {
47 | implementation(compose.runtime)
48 | implementation(compose.foundation)
49 | implementation(compose.material)
50 | implementation(libs.libres)
51 | implementation(libs.kotlinx.coroutines.core)
52 | implementation(libs.kotlinx.serialization.json)
53 | }
54 | }
55 |
56 | val commonTest by getting {
57 | dependencies {
58 | implementation(kotlin("test"))
59 | }
60 | }
61 |
62 | val androidMain by getting {
63 | dependencies {
64 | implementation(libs.androidx.appcompat)
65 | implementation(libs.androidx.activityCompose)
66 | implementation(libs.compose.uitooling)
67 | implementation(libs.kotlinx.coroutines.android)
68 | }
69 | }
70 |
71 | val desktopMain by getting {
72 | dependencies {
73 | implementation(compose.desktop.common)
74 | implementation(compose.desktop.currentOs)
75 | }
76 | }
77 |
78 | val jsMain by getting {
79 | dependencies {
80 | implementation(compose.web.core)
81 | }
82 | }
83 |
84 | val iosX64Main by getting
85 | val iosArm64Main by getting
86 | val iosSimulatorArm64Main by getting
87 | val iosMain by creating {
88 | dependsOn(commonMain)
89 | iosX64Main.dependsOn(this)
90 | iosArm64Main.dependsOn(this)
91 | iosSimulatorArm64Main.dependsOn(this)
92 | dependencies {
93 | }
94 | }
95 |
96 | val iosX64Test by getting
97 | val iosArm64Test by getting
98 | val iosSimulatorArm64Test by getting
99 | val iosTest by creating {
100 | dependsOn(commonTest)
101 | iosX64Test.dependsOn(this)
102 | iosArm64Test.dependsOn(this)
103 | iosSimulatorArm64Test.dependsOn(this)
104 | }
105 | }
106 | }
107 |
108 | android {
109 | namespace = "com.nezihyilmaz.multiplatform.particles"
110 | compileSdk = 33
111 |
112 | defaultConfig {
113 | minSdk = 21
114 | targetSdk = 33
115 |
116 | applicationId = "com.nezihyilmaz.multiplatform.particles.androidApp"
117 | versionCode = 1
118 | versionName = "1.0.0"
119 | }
120 | sourceSets["main"].apply {
121 | manifest.srcFile("src/androidMain/AndroidManifest.xml")
122 | res.srcDirs("src/androidMain/resources")
123 | }
124 | compileOptions {
125 | sourceCompatibility = JavaVersion.VERSION_1_8
126 | targetCompatibility = JavaVersion.VERSION_1_8
127 | }
128 | packagingOptions {
129 | resources.excludes.add("META-INF/**")
130 | }
131 | buildTypes {
132 | getByName("release") {
133 | signingConfig = signingConfigs.getByName("debug")
134 | }
135 | }
136 | }
137 |
138 | compose.desktop {
139 | application {
140 | mainClass = "MainKt"
141 |
142 | nativeDistributions {
143 | targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
144 | packageName = "com.nezihyilmaz.multiplatform.particles.desktopApp"
145 | packageVersion = "1.0.0"
146 |
147 | }
148 | }
149 | kotlin {
150 | this.jvmToolchain(8)
151 | }
152 | }
153 |
154 | compose.experimental {
155 | web.application {}
156 | }
157 |
158 | libres {
159 | generatedClassName = "MainRes"
160 | generateNamedArguments = true
161 | camelCaseNamesForAppleFramework = false
162 | }
163 |
164 | tasks.getByPath("desktopProcessResources").dependsOn("libresGenerateResources")
165 | tasks.getByPath("desktopSourcesJar").dependsOn("libresGenerateResources")
166 | tasks.getByPath("jsProcessResources").dependsOn("libresGenerateResources")
167 |
--------------------------------------------------------------------------------
/composeApp/composeApp.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |spec|
2 | spec.name = 'composeApp'
3 | spec.version = '1.0.0'
4 | spec.homepage = 'empty'
5 | spec.source = { :http=> ''}
6 | spec.authors = ''
7 | spec.license = ''
8 | spec.summary = 'Compose application framework'
9 | spec.vendored_frameworks = 'build/cocoapods/framework/ComposeApp.framework'
10 | spec.libraries = 'c++'
11 | spec.ios.deployment_target = '11.0'
12 |
13 |
14 | spec.pod_target_xcconfig = {
15 | 'KOTLIN_PROJECT_PATH' => ':composeApp',
16 | 'PRODUCT_MODULE_NAME' => 'ComposeApp',
17 | }
18 |
19 | spec.script_phases = [
20 | {
21 | :name => 'Build composeApp',
22 | :execution_position => :before_compile,
23 | :shell_path => '/bin/sh',
24 | :script => <<-SCRIPT
25 | if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
26 | echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
27 | exit 0
28 | fi
29 | set -ev
30 | REPO_ROOT="$PODS_TARGET_SRCROOT"
31 | "$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
32 | -Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
33 | -Pkotlin.native.cocoapods.archs="$ARCHS" \
34 | -Pkotlin.native.cocoapods.configuration="$CONFIGURATION"
35 | SCRIPT
36 | }
37 | ]
38 | spec.resource_bundles = {
39 | 'LibresComposeApp' => ['build/generated/libres/apple/resources/images/LibresComposeApp.xcassets']
40 | }
41 | end
--------------------------------------------------------------------------------
/composeApp/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/com/nezihyilmaz/multiplatform/particles/App.android.kt:
--------------------------------------------------------------------------------
1 | package com.nezihyilmaz.multiplatform.particles
2 |
3 | import android.app.Application
4 | import android.graphics.Color
5 | import android.os.Bundle
6 | import androidx.activity.ComponentActivity
7 | import androidx.activity.compose.setContent
8 | import com.nezihyilmaz.multiplatform.particles.ui.composable.App
9 |
10 | class AndroidApp : Application() {
11 | companion object {
12 | lateinit var INSTANCE: AndroidApp
13 | }
14 |
15 | override fun onCreate() {
16 | super.onCreate()
17 | INSTANCE = this
18 | }
19 | }
20 |
21 | class AppActivity : ComponentActivity() {
22 | override fun onCreate(savedInstanceState: Bundle?) {
23 | window.navigationBarColor = Color.BLACK
24 | super.onCreate(savedInstanceState)
25 | setContent { App() }
26 | }
27 | }
28 |
29 | actual val DEFAULT_RESOURCE = FormattedSVGResource.ANDROID_ICON_AND_TEXT
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/nezihyilmaz/multiplatform/particles/ResourceProvider.kt:
--------------------------------------------------------------------------------
1 | package com.nezihyilmaz.multiplatform.particles
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import com.nezihyilmaz.multiplatform.particles.physics.ParticleProperties
5 | import kotlinx.serialization.decodeFromString
6 | import kotlinx.serialization.json.Json
7 |
8 | data class FormattedSVGResource(
9 | val viewportWidth: Float,
10 | val viewportHeight: Float,
11 | val data: String,
12 | val additionalOffsetX: Float = 0f,
13 | val additionalOffsetY: Float = 0f,
14 | val particleProperties: ParticleProperties? = null
15 | ) {
16 | companion object {
17 | val ANDROID_ICON_AND_TEXT = FormattedSVGResource(
18 | 60f,
19 | 60f,
20 | SVG_COORD_ANDROID_LOGO_AND_TEXT,
21 | particleProperties = ParticleProperties(color = Color(28,121,33), radius = 6f)
22 | )
23 | val IOS_ICON_AND_TEXT = FormattedSVGResource(
24 | 60f,
25 | 60f,
26 | SVG_COORDS_IOS_LOGO_AND_TEXT,
27 | particleProperties = ParticleProperties(color = Color.Black, radius = 6f)
28 | )
29 |
30 | val TEXT_PLAY = FormattedSVGResource(
31 | 60f,
32 | 60f,
33 | SVG_COORDS_TEXT_PLAY,
34 | particleProperties = ParticleProperties(color = Color.Magenta, radius = 6f)
35 | )
36 | }
37 | }
38 |
39 | expect val DEFAULT_RESOURCE : FormattedSVGResource
40 |
41 | fun getUIData(
42 | canvasWidth: Float,
43 | canvasHeight: Float,
44 | formattedSVGResource: FormattedSVGResource = DEFAULT_RESOURCE
45 | ): UIData {
46 | val data = Json.decodeFromString>>(formattedSVGResource.data)
47 | val resourceWidth = formattedSVGResource.viewportWidth
48 | val resourceHeight = formattedSVGResource.viewportHeight
49 | val canvasAspectRatio = canvasWidth / canvasHeight
50 | val resourceAspectRatio = resourceWidth / resourceHeight
51 |
52 | val scaleX: Float
53 | val scaleY: Float
54 |
55 | val paddingRatio = 0.1f
56 |
57 | if (resourceAspectRatio > canvasAspectRatio) {
58 | scaleX = (canvasWidth / resourceWidth) * (1 - paddingRatio)
59 | scaleY = scaleX
60 | } else {
61 | scaleY = (canvasHeight / resourceHeight) * (1 - paddingRatio)
62 | scaleX = scaleY
63 | }
64 |
65 | val offsetX =
66 | (canvasWidth - resourceWidth * scaleX) / 2 + formattedSVGResource.additionalOffsetX
67 | val offsetY =
68 | ((canvasHeight - resourceHeight * scaleY) / 2) + formattedSVGResource.additionalOffsetY
69 | val mappedList = data.map {
70 | val x = it[0]
71 | val y = it[1]
72 | val transformedX = x * scaleX + offsetX
73 | val transformedY = y * scaleY + offsetY
74 | Pair(transformedX, transformedY)
75 | }
76 | return UIData(mappedList, formattedSVGResource.particleProperties)
77 | }
78 |
79 | data class UIData(
80 | val coordinates: List>,
81 | val particleProperties: ParticleProperties?
82 | )
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/nezihyilmaz/multiplatform/particles/controller/Engine.kt:
--------------------------------------------------------------------------------
1 | package com.nezihyilmaz.multiplatform.particles.controller
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import com.nezihyilmaz.multiplatform.particles.getUIData
5 | import com.nezihyilmaz.multiplatform.particles.physics.ForceField
6 | import com.nezihyilmaz.multiplatform.particles.physics.Particle
7 | import com.nezihyilmaz.multiplatform.particles.physics.ParticleProperties
8 | import com.nezihyilmaz.multiplatform.particles.physics.Vector2
9 | import kotlin.math.PI
10 | import kotlin.math.pow
11 |
12 | class Engine {
13 |
14 | val pointerForceRadius = 300f
15 | val pointerForceFieldBaseStrength = 30f
16 | val pointerForceFieldStrengthCurveSwapDistanceFractionThreshold = 0.8f
17 | val pointerForceFieldStrengthWaveScaling = 4f
18 |
19 | val instinctForceRadius = 10000f
20 | val instinctForceBaseStrength = 1.5f
21 |
22 | val maxLimit : Float? = 12f
23 |
24 | var particles: MutableList = mutableListOf()
25 | var pointerForceField: ForceField? = null
26 |
27 | fun create(width: Float, height: Float) {
28 |
29 | val uiData = getUIData(width, height)
30 | val particleProperties = uiData.particleProperties ?: ParticleProperties(Color.Red, 6f)
31 | val particleList = uiData.coordinates.map {
32 | val position = Vector2(it.first, it.second)
33 | Particle(position = position,
34 | instinctForce = ForceField(center = position.copy(),
35 | radius = instinctForceRadius,
36 | forceGenerator = { directionUnitVector, distanceFraction, distance ->
37 | val gravity = directionUnitVector.rotate(PI.toFloat()) * (distanceFraction) * instinctForceBaseStrength
38 | gravity
39 | }), particleProperties = particleProperties)
40 | }
41 | particles = particleList.toMutableList()
42 | }
43 |
44 | fun update(time: Float) {
45 |
46 | fun getIdleLimit(distance: Float) : Float? {
47 | if (maxLimit == null) return null
48 | val limit = (distance / pointerForceRadius) * maxLimit
49 | return if (distance > pointerForceRadius) maxLimit else limit
50 | }
51 |
52 | particles.forEach {
53 | val instinctInteraction = it.instinctForce.interact(it)
54 | val fingerInteraction = pointerForceField?.interact(it)
55 | val distanceToCenter = it.position.distanceTo(it.instinctForce.center)
56 | var limit : Float? = if (fingerInteraction?.valid == true) maxLimit else getIdleLimit(distanceToCenter)
57 | it.update(limit)
58 | }
59 |
60 |
61 | }
62 |
63 | fun createForceField(x: Float, y: Float) {
64 | pointerForceField = ForceField(Vector2(x, y),
65 | radius = pointerForceRadius,
66 | forceGenerator = { directionUnitVector, distanceFraction, distance ->
67 | val strength = pointerForceFieldBaseStrength
68 | val swapThreshold = pointerForceFieldStrengthCurveSwapDistanceFractionThreshold
69 | val unitVector = directionUnitVector.copy()
70 | val multiplier = if (distanceFraction <= swapThreshold) {
71 | (pointerForceFieldStrengthWaveScaling * distanceFraction).pow(strength) / pointerForceFieldStrengthWaveScaling
72 | } else {
73 | ((pointerForceFieldStrengthWaveScaling - pointerForceFieldStrengthWaveScaling * distanceFraction).pow(strength)) / pointerForceFieldStrengthWaveScaling + (1 - swapThreshold)
74 | }
75 | unitVector * (multiplier) * strength
76 | })
77 | }
78 |
79 | fun moveForceFieldBy(x: Float, y: Float) {
80 | if (pointerForceField == null) return
81 | pointerForceField!!.center += Vector2(x, y)
82 | }
83 |
84 | fun removeForceField() {
85 | pointerForceField = null
86 | }
87 |
88 |
89 |
90 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/nezihyilmaz/multiplatform/particles/physics/ForceField.kt:
--------------------------------------------------------------------------------
1 | package com.nezihyilmaz.multiplatform.particles.physics
2 |
3 | /**
4 | * A [ForceField] is a region of space that exerts a force on any particle that is inside it.
5 | */
6 | class ForceField(
7 | val center: Vector2,
8 | val radius: Float,
9 | val forceGenerator: (directionUnitVector: Vector2, distanceFraction: Float, distance: Float) -> Vector2,
10 | ) {
11 |
12 | fun interact(particle: Particle) : InteractionResult{
13 | //get the distance vector between the particle and the center of the force field
14 | val distanceVector = particle.position.distanceVector(center)
15 | val distance = distanceVector.magnitude()
16 | if (distance > radius) return InteractionResult.invalidInteraction
17 | //get the fraction of the distance between the particle and the center of the force field relative to the radius
18 | val distanceFraction = 1 - (distance / radius)
19 | //generate the force vector and apply it to the particle
20 | val force = forceGenerator(distanceVector.normalized(), distanceFraction, distance)
21 | particle.applyForce(force)
22 | return InteractionResult(true, force)
23 | }
24 |
25 | }
26 |
27 | data class InteractionResult(val valid: Boolean, val force: Vector2?){
28 | companion object{
29 | val invalidInteraction = InteractionResult(false, null)
30 | }
31 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/nezihyilmaz/multiplatform/particles/physics/Particle.kt:
--------------------------------------------------------------------------------
1 | package com.nezihyilmaz.multiplatform.particles.physics
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | data class Particle(
6 | val position: Vector2,
7 | val velocity: Vector2 = Vector2(0f, 0f),
8 | val acceleration: Vector2 = Vector2(0f, 0f),
9 | val instinctForce: ForceField,
10 | val particleProperties: ParticleProperties
11 | ) {
12 |
13 | fun applyForce(force: Vector2) {
14 | acceleration += force
15 | }
16 |
17 | fun update(limit: Float? = null) {
18 | velocity += acceleration
19 | limit?.let { velocity.limit(it) }
20 | position += velocity
21 | acceleration *= 0f
22 | }
23 | }
24 |
25 | data class ParticleProperties(
26 | val color: Color,
27 | val radius: Float
28 | )
29 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/nezihyilmaz/multiplatform/particles/physics/Vector2.kt:
--------------------------------------------------------------------------------
1 | package com.nezihyilmaz.multiplatform.particles.physics
2 |
3 | import kotlin.math.cos
4 | import kotlin.math.sin
5 | import kotlin.math.sqrt
6 | import kotlin.random.Random
7 |
8 | /**
9 | * A class representing a 2D vector.
10 | * @property x The x component of the vector.
11 | * @property y The y component of the vector.
12 | */
13 | data class Vector2(var x: Float, var y: Float) {
14 |
15 | /**
16 | * Adds the given [vector] to this vector.
17 | */
18 | operator fun plus(vector: Vector2): Vector2 {
19 | x += vector.x
20 | y += vector.y
21 | return this
22 | }
23 |
24 | operator fun plusAssign(vector: Vector2) {
25 | this + vector
26 | }
27 |
28 | /**
29 | * Subtracts the given [vector] from this vector.
30 | */
31 | operator fun minus(vector: Vector2): Vector2{
32 | x -= vector.x
33 | y -= vector.y
34 | return this
35 | }
36 |
37 | operator fun minusAssign(vector: Vector2) {
38 | this - vector
39 | }
40 |
41 | /**
42 | * Multiplies this vector by the given [scalar].
43 | */
44 | operator fun times(scalar: Float): Vector2{
45 | x *= scalar
46 | y *= scalar
47 | return this
48 | }
49 |
50 | operator fun timesAssign(scalar: Float) {
51 | this * scalar
52 | }
53 |
54 | /**
55 | * Divides this vector by the given [scalar].
56 | */
57 | operator fun div(scalar: Float): Vector2{
58 | x /= scalar
59 | y /= scalar
60 | return this
61 | }
62 |
63 | operator fun divAssign(scalar: Float) {
64 | this / scalar
65 | }
66 |
67 | /**
68 | * Returns the magnitude of this vector.
69 | */
70 | fun magnitude(): Float = sqrt(magnitudeSquared())
71 |
72 | /**
73 | * Returns the squared magnitude of this vector.
74 | */
75 | fun magnitudeSquared(): Float = x * x + y * y
76 |
77 |
78 | fun rotate(angle: Float): Vector2 {
79 | val cosAngle = cos(angle)
80 | val sinAngle = sin(angle)
81 | val newX = x * cosAngle - y * sinAngle
82 | val newY = x * sinAngle + y * cosAngle
83 | x = newX
84 | y = newY
85 | return this
86 | }
87 |
88 |
89 | /**
90 | * Returns a normalized copy of this vector.
91 | */
92 | fun normalized(): Vector2 {
93 | if (isZero()) return this.copy()
94 | val mag = magnitude()
95 | return this.copy() / mag
96 | }
97 |
98 | /**
99 | * Normalizes this vector in place.
100 | */
101 | fun normalize() : Vector2{
102 | if (isZero()) return this
103 | val mag = magnitude()
104 | this /= mag
105 | return this
106 | }
107 |
108 | /**
109 | * Limits the magnitude of this vector to the given value.
110 | */
111 | fun limit(max: Float) {
112 | if (magnitudeSquared() > max * max) {
113 | this *= (max / magnitude())
114 | }
115 | }
116 |
117 | /**
118 | * Returns the dot product of this vector and the given vector.
119 | * @param vector The vector to dot product with.
120 | */
121 | fun dot(vector: Vector2): Float = x * vector.x + y * vector.y
122 |
123 | /**
124 | * Returns the distance between this vector and the given vector.
125 | * @param vector The vector to get the distance to.
126 | */
127 | fun distanceTo(vector: Vector2): Float {
128 | val dx = x - vector.x
129 | val dy = y - vector.y
130 | return sqrt((dx * dx + dy * dy))
131 | }
132 |
133 | /**
134 | * Returns the distance vector between this vector and the given vector.
135 | */
136 | fun distanceVector(vector: Vector2): Vector2 {
137 | return this.copy() - vector
138 | }
139 |
140 | fun isZero(): Boolean {
141 | return x == 0f && y == 0f
142 | }
143 |
144 | companion object{
145 | /**
146 | * Returns a random vector within the given [width] and [height].
147 | */
148 | fun random(fromWidth: Float = 0f, width: Float, fromHeight: Float = 0f, height: Float): Vector2 {
149 | return Vector2(
150 | Random.nextDouble(fromWidth.toDouble(), width.toDouble()).toFloat(),
151 | Random.nextDouble(fromHeight.toDouble(), height.toDouble()).toFloat()
152 | )
153 | }
154 |
155 | fun fromAngle(angle: Float, magnitude: Float): Vector2 {
156 | return Vector2(
157 | magnitude * cos(angle),
158 | magnitude * sin(angle)
159 | )
160 | }
161 | }
162 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/nezihyilmaz/multiplatform/particles/ui/Color.kt:
--------------------------------------------------------------------------------
1 | package com.nezihyilmaz.multiplatform.particles.ui
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | //generated by https://m3.material.io/theme-builder#/custom
6 | //Color palette was taken here: https://colorhunt.co/palettes/popular
7 |
8 | internal val md_theme_light_primary = Color(0xFF00687A)
9 | internal val md_theme_light_onPrimary = Color(0xFFFFFFFF)
10 | internal val md_theme_light_primaryContainer = Color(0xFFABEDFF)
11 | internal val md_theme_light_onPrimaryContainer = Color(0xFF001F26)
12 | internal val md_theme_light_secondary = Color(0xFF00696E)
13 | internal val md_theme_light_onSecondary = Color(0xFFFFFFFF)
14 | internal val md_theme_light_secondaryContainer = Color(0xFF6FF6FE)
15 | internal val md_theme_light_onSecondaryContainer = Color(0xFF002022)
16 | internal val md_theme_light_tertiary = Color(0xFF904D00)
17 | internal val md_theme_light_onTertiary = Color(0xFFFFFFFF)
18 | internal val md_theme_light_tertiaryContainer = Color(0xFFFFDCC2)
19 | internal val md_theme_light_onTertiaryContainer = Color(0xFF2E1500)
20 | internal val md_theme_light_error = Color(0xFFBA1A1A)
21 | internal val md_theme_light_errorContainer = Color(0xFFFFDAD6)
22 | internal val md_theme_light_onError = Color(0xFFFFFFFF)
23 | internal val md_theme_light_onErrorContainer = Color(0xFF410002)
24 | internal val md_theme_light_background = Color(0xFFFFFBFF)
25 | internal val md_theme_light_onBackground = Color(0xFF221B00)
26 | internal val md_theme_light_surface = Color(0xFFFFFBFF)
27 | internal val md_theme_light_onSurface = Color(0xFF221B00)
28 | internal val md_theme_light_surfaceVariant = Color(0xFFDBE4E7)
29 | internal val md_theme_light_onSurfaceVariant = Color(0xFF3F484B)
30 | internal val md_theme_light_outline = Color(0xFF70797B)
31 | internal val md_theme_light_inverseOnSurface = Color(0xFFFFF0C0)
32 | internal val md_theme_light_inverseSurface = Color(0xFF3A3000)
33 | internal val md_theme_light_inversePrimary = Color(0xFF55D6F4)
34 | internal val md_theme_light_shadow = Color(0xFF000000)
35 | internal val md_theme_light_surfaceTint = Color(0xFF00687A)
36 | internal val md_theme_light_outlineVariant = Color(0xFFBFC8CB)
37 | internal val md_theme_light_scrim = Color(0xFF000000)
38 |
39 | internal val md_theme_dark_primary = Color(0xFF55D6F4)
40 | internal val md_theme_dark_onPrimary = Color(0xFF003640)
41 | internal val md_theme_dark_primaryContainer = Color(0xFF004E5C)
42 | internal val md_theme_dark_onPrimaryContainer = Color(0xFFABEDFF)
43 | internal val md_theme_dark_secondary = Color(0xFF4CD9E2)
44 | internal val md_theme_dark_onSecondary = Color(0xFF00373A)
45 | internal val md_theme_dark_secondaryContainer = Color(0xFF004F53)
46 | internal val md_theme_dark_onSecondaryContainer = Color(0xFF6FF6FE)
47 | internal val md_theme_dark_tertiary = Color(0xFFFFB77C)
48 | internal val md_theme_dark_onTertiary = Color(0xFF4D2700)
49 | internal val md_theme_dark_tertiaryContainer = Color(0xFF6D3900)
50 | internal val md_theme_dark_onTertiaryContainer = Color(0xFFFFDCC2)
51 | internal val md_theme_dark_error = Color(0xFFFFB4AB)
52 | internal val md_theme_dark_errorContainer = Color(0xFF93000A)
53 | internal val md_theme_dark_onError = Color(0xFF690005)
54 | internal val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
55 | internal val md_theme_dark_background = Color(0xFF221B00)
56 | internal val md_theme_dark_onBackground = Color(0xFFFFE264)
57 | internal val md_theme_dark_surface = Color(0xFF221B00)
58 | internal val md_theme_dark_onSurface = Color(0xFFFFE264)
59 | internal val md_theme_dark_surfaceVariant = Color(0xFF3F484B)
60 | internal val md_theme_dark_onSurfaceVariant = Color(0xFFBFC8CB)
61 | internal val md_theme_dark_outline = Color(0xFF899295)
62 | internal val md_theme_dark_inverseOnSurface = Color(0xFF221B00)
63 | internal val md_theme_dark_inverseSurface = Color(0xFFFFE264)
64 | internal val md_theme_dark_inversePrimary = Color(0xFF00687A)
65 | internal val md_theme_dark_shadow = Color(0xFF000000)
66 | internal val md_theme_dark_surfaceTint = Color(0xFF55D6F4)
67 | internal val md_theme_dark_outlineVariant = Color(0xFF3F484B)
68 | internal val md_theme_dark_scrim = Color(0xFF000000)
69 |
70 |
71 | internal val seed = Color(0xFF2C3639)
72 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/nezihyilmaz/multiplatform/particles/ui/Theme.kt:
--------------------------------------------------------------------------------
1 | package com.nezihyilmaz.multiplatform.particles.ui
2 |
3 | import androidx.compose.foundation.isSystemInDarkTheme
4 | import androidx.compose.material.MaterialTheme
5 | import androidx.compose.material.Surface
6 | import androidx.compose.material.darkColors
7 | import androidx.compose.material.lightColors
8 | import androidx.compose.runtime.Composable
9 |
10 | private val LightColors = lightColors(
11 | primary = md_theme_light_primary,
12 | onPrimary = md_theme_light_onPrimary,
13 | secondary = md_theme_light_secondary,
14 | onSecondary = md_theme_light_onSecondary,
15 | error = md_theme_light_error,
16 | onError = md_theme_light_onError,
17 | background = md_theme_light_background,
18 | onBackground = md_theme_light_onBackground,
19 | surface = md_theme_light_surface,
20 | onSurface = md_theme_light_onSurface,
21 | )
22 |
23 | private val DarkColors = darkColors(
24 | primary = md_theme_dark_primary,
25 | onPrimary = md_theme_dark_onPrimary,
26 | secondary = md_theme_dark_secondary,
27 | onSecondary = md_theme_dark_onSecondary,
28 | error = md_theme_dark_error,
29 | onError = md_theme_dark_onError,
30 | background = md_theme_dark_background,
31 | onBackground = md_theme_dark_onBackground,
32 | surface = md_theme_dark_surface,
33 | onSurface = md_theme_dark_onSurface,
34 | )
35 |
36 | @Composable
37 | internal fun AppTheme(
38 | useDarkTheme: Boolean = isSystemInDarkTheme(),
39 | content: @Composable() () -> Unit
40 | ) {
41 | val colors = if (!useDarkTheme) {
42 | LightColors
43 | } else {
44 | DarkColors
45 | }
46 |
47 | MaterialTheme(
48 | colors = colors,
49 | content = {
50 | Surface(content = content)
51 | }
52 | )
53 | }
54 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/nezihyilmaz/multiplatform/particles/ui/composable/App.kt:
--------------------------------------------------------------------------------
1 | package com.nezihyilmaz.multiplatform.particles.ui.composable
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.nezihyilmaz.multiplatform.particles.controller.Engine
5 | import com.nezihyilmaz.multiplatform.particles.ui.AppTheme
6 |
7 | @Composable
8 | internal fun App() = AppTheme {
9 | Space(Engine())
10 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/nezihyilmaz/multiplatform/particles/ui/composable/Fabric.kt:
--------------------------------------------------------------------------------
1 | package com.nezihyilmaz.multiplatform.particles.ui.composable
2 |
3 | import androidx.compose.foundation.Canvas
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import androidx.compose.ui.geometry.Offset
7 | import androidx.compose.ui.graphics.Brush
8 | import androidx.compose.ui.graphics.Color
9 |
10 |
11 | /**
12 | * A [Fabric] is a grid of lines that is used to visualize the space.
13 | */
14 | @Composable
15 | fun Fabric(modifier: Modifier) {
16 |
17 | Canvas(modifier = modifier) {
18 | drawRect(color = Color.White)
19 | val canvasWidth = size.width
20 | val canvasHeight = size.height
21 | val lineGap = 150f
22 | val strokeWidth = 4f
23 |
24 | val verticalGradient = Brush.verticalGradient(
25 | colors = listOf(Color.Transparent, Color.LightGray, Color.Transparent)
26 | )
27 |
28 | val horizontalGradient = Brush.horizontalGradient(
29 | colors = listOf(Color.Transparent, Color.LightGray, Color.Transparent)
30 | )
31 |
32 | // draw vertical lines
33 | var x = 0f
34 | while (x < canvasWidth) {
35 | drawLine(
36 | brush = verticalGradient,
37 | start = Offset(x, 0f),
38 | end = Offset(x, canvasHeight),
39 | strokeWidth = strokeWidth
40 | )
41 | x += lineGap
42 | }
43 |
44 | // draw horizontal lines
45 | var y = 0f
46 | while (y < canvasHeight) {
47 | drawLine(
48 | brush = horizontalGradient,
49 | start = Offset(0f, y),
50 | end = Offset(canvasWidth, y),
51 | strokeWidth = strokeWidth
52 | )
53 | y += lineGap
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/nezihyilmaz/multiplatform/particles/ui/composable/Renderer.kt:
--------------------------------------------------------------------------------
1 | package com.nezihyilmaz.multiplatform.particles.ui.composable
2 |
3 | import androidx.compose.ui.geometry.Offset
4 | import androidx.compose.ui.graphics.Color
5 | import androidx.compose.ui.graphics.drawscope.DrawScope
6 | import com.nezihyilmaz.multiplatform.particles.physics.Particle
7 |
8 | fun DrawScope.renderParticles(particles: List) {
9 | particles.forEach {
10 | drawCircle(
11 | color = it.particleProperties.color,
12 | radius = it.particleProperties.radius,
13 | center = Offset(it.position.x, it.position.y)
14 | )
15 | }
16 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/nezihyilmaz/multiplatform/particles/ui/composable/Space.kt:
--------------------------------------------------------------------------------
1 | package com.nezihyilmaz.multiplatform.particles.ui.composable
2 |
3 | import androidx.compose.foundation.Canvas
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.fillMaxSize
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.runtime.getValue
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.layout.onSizeChanged
10 | import com.nezihyilmaz.multiplatform.particles.controller.Engine
11 | import com.nezihyilmaz.multiplatform.particles.ui.modifier.handleGestures
12 |
13 | @Composable
14 | fun Space(engine: Engine) {
15 | val time by Time()
16 | Box(
17 | modifier = Modifier.fillMaxSize().onSizeChanged { size ->
18 | engine.create(
19 | size.width.toFloat(),
20 | size.height.toFloat()
21 | )
22 | }.handleGestures(engine)
23 | )
24 | {
25 | Fabric(Modifier.fillMaxSize())
26 | Canvas(modifier = Modifier.fillMaxSize()) {
27 | engine.update(time)
28 | renderParticles(engine.particles)
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/nezihyilmaz/multiplatform/particles/ui/composable/Time.kt:
--------------------------------------------------------------------------------
1 | package com.nezihyilmaz.multiplatform.particles.ui.composable
2 |
3 | import androidx.compose.animation.core.LinearEasing
4 | import androidx.compose.animation.core.animate
5 | import androidx.compose.animation.core.infiniteRepeatable
6 | import androidx.compose.animation.core.tween
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.runtime.LaunchedEffect
9 | import androidx.compose.runtime.MutableState
10 | import androidx.compose.runtime.mutableStateOf
11 | import androidx.compose.runtime.remember
12 |
13 | @Composable
14 | fun Time(): MutableState {
15 | val time = remember { mutableStateOf(0f) }
16 | LaunchedEffect(Unit) {
17 | animate(
18 | initialValue = 0f,
19 | targetValue = 1f,
20 | animationSpec = infiniteRepeatable(
21 | tween(durationMillis = 1000, easing = LinearEasing)
22 | )
23 | ) { value, velocity ->
24 | time.value = value
25 | }
26 | }
27 | return time
28 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/nezihyilmaz/multiplatform/particles/ui/modifier/GestureHandler.kt:
--------------------------------------------------------------------------------
1 | package com.nezihyilmaz.multiplatform.particles.ui.modifier
2 |
3 | import androidx.compose.foundation.gestures.detectDragGestures
4 | import androidx.compose.foundation.gestures.detectTapGestures
5 | import androidx.compose.ui.Modifier
6 | import androidx.compose.ui.input.pointer.pointerInput
7 | import com.nezihyilmaz.multiplatform.particles.controller.Engine
8 |
9 | fun Modifier.handleGestures(engine: Engine): Modifier {
10 | val modifier = pointerInput(Unit) {
11 | detectTapGestures(onPress = { offset ->
12 | engine.createForceField(offset.x, offset.y)
13 | }) {
14 | engine.removeForceField()
15 | }
16 |
17 | }.pointerInput(Unit) {
18 | detectDragGestures(onDragStart = {
19 |
20 | }, onDragEnd = {
21 | engine.removeForceField()
22 | }, onDragCancel = {}) { change, dragAmount ->
23 | change.consume()
24 | dragAmount.x
25 | dragAmount.y
26 | engine.moveForceFieldBy(
27 | dragAmount.x, dragAmount.y
28 | )
29 | }
30 | }
31 | return modifier
32 | }
--------------------------------------------------------------------------------
/composeApp/src/desktopMain/kotlin/com/nezihyilmaz/multiplatform/particles/App.jvm.kt:
--------------------------------------------------------------------------------
1 | package com.nezihyilmaz.multiplatform.particles
2 |
3 | actual val DEFAULT_RESOURCE = FormattedSVGResource.TEXT_PLAY
--------------------------------------------------------------------------------
/composeApp/src/desktopMain/kotlin/main.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.ui.unit.dp
2 | import androidx.compose.ui.window.Window
3 | import androidx.compose.ui.window.application
4 | import androidx.compose.ui.window.rememberWindowState
5 | import com.nezihyilmaz.multiplatform.particles.ui.composable.App
6 |
7 | fun main() = application {
8 | Window(
9 | title = "Multiplatform-Particles",
10 | state = rememberWindowState(width = 800.dp, height = 600.dp),
11 | onCloseRequest = ::exitApplication,
12 | ) { App() }
13 | }
--------------------------------------------------------------------------------
/composeApp/src/iosMain/kotlin/com/nezihyilmaz/multiplatform/particles/App.ios.kt:
--------------------------------------------------------------------------------
1 | package com.nezihyilmaz.multiplatform.particles
2 |
3 | actual val DEFAULT_RESOURCE = FormattedSVGResource.IOS_ICON_AND_TEXT
--------------------------------------------------------------------------------
/composeApp/src/iosMain/kotlin/main.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.ui.window.ComposeUIViewController
2 | import com.nezihyilmaz.multiplatform.particles.ui.composable.App
3 | import platform.UIKit.UIViewController
4 |
5 | fun MainViewController(): UIViewController {
6 | return ComposeUIViewController { App() }
7 | }
8 |
--------------------------------------------------------------------------------
/composeApp/src/jsMain/kotlin/BrowserViewportWindow.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress(
2 | "INVISIBLE_MEMBER",
3 | "INVISIBLE_REFERENCE",
4 | "EXPOSED_PARAMETER_TYPE"
5 | ) // WORKAROUND: ComposeWindow and ComposeLayer are internal
6 |
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.window.ComposeWindow
9 | import kotlinx.browser.document
10 | import kotlinx.browser.window
11 | import org.w3c.dom.HTMLCanvasElement
12 | import org.w3c.dom.HTMLStyleElement
13 | import org.w3c.dom.HTMLTitleElement
14 |
15 | private const val CANVAS_ELEMENT_ID = "ComposeTarget" // Hardwired into ComposeWindow
16 |
17 | /**
18 | * A Skiko/Canvas-based top-level window using the browser's entire viewport. Supports resizing.
19 | * Author: https://github.com/OliverO2
20 | * Source: https://github.com/OliverO2/compose-counting-grid/blob/eb79f7c8be7804d4323114be30ce498cfac6d2b0/src/frontendWebMain/kotlin/BrowserViewportWindow.kt
21 | */
22 | @Suppress("FunctionName")
23 | fun BrowserViewportWindow(
24 | title: String = "Untitled",
25 | content: @Composable ComposeWindow.() -> Unit
26 | ) {
27 | val htmlHeadElement = document.head!!
28 | htmlHeadElement.appendChild(
29 | (document.createElement("style") as HTMLStyleElement).apply {
30 | type = "text/css"
31 | appendChild(
32 | document.createTextNode(
33 | """
34 | html, body {
35 | overflow: hidden;
36 | margin: 0 !important;
37 | padding: 0 !important;
38 | }
39 |
40 | #$CANVAS_ELEMENT_ID {
41 | outline: none;
42 | }
43 | """.trimIndent()
44 | )
45 | )
46 | }
47 | )
48 |
49 | fun HTMLCanvasElement.fillViewportSize() {
50 | setAttribute("width", "${window.innerWidth}")
51 | setAttribute("height", "${window.innerHeight}")
52 | }
53 |
54 | val canvas = (document.getElementById(CANVAS_ELEMENT_ID) as HTMLCanvasElement).apply {
55 | fillViewportSize()
56 | }
57 |
58 | ComposeWindow().apply {
59 | window.addEventListener("resize", {
60 | val scale = layer.layer.contentScale
61 | val density = window.devicePixelRatio.toFloat()
62 | canvas.fillViewportSize()
63 | layer.layer.attachTo(canvas)
64 | layer.layer.needRedraw()
65 | layer.setSize((canvas.width / scale * density).toInt(), (canvas.height / scale * density).toInt())
66 | })
67 |
68 | // WORKAROUND: ComposeWindow does not implement `setTitle(title)`
69 | val htmlTitleElement = (
70 | htmlHeadElement.getElementsByTagName("title").item(0)
71 | ?: document.createElement("title").also { htmlHeadElement.appendChild(it) }
72 | ) as HTMLTitleElement
73 | htmlTitleElement.textContent = title
74 |
75 | setContent {
76 | content(this)
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/composeApp/src/jsMain/kotlin/com/nezihyilmaz/multiplatform/particles/App.js.kt:
--------------------------------------------------------------------------------
1 | package com.nezihyilmaz.multiplatform.particles
2 |
3 | actual val DEFAULT_RESOURCE = FormattedSVGResource.TEXT_PLAY
4 |
5 |
--------------------------------------------------------------------------------
/composeApp/src/jsMain/kotlin/main.kt:
--------------------------------------------------------------------------------
1 | import com.nezihyilmaz.multiplatform.particles.ui.composable.App
2 | import org.jetbrains.skiko.wasm.onWasmReady
3 |
4 | fun main() {
5 | onWasmReady {
6 | BrowserViewportWindow("Multiplatform-Particles") {
7 | App()
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/composeApp/src/jsMain/resources/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Multiplatform-Particles
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/gradle.bat:
--------------------------------------------------------------------------------
1 |
2 | @rem
3 | @rem Copyright 2015 the original author or authors.
4 | @rem
5 | @rem Licensed under the Apache License, Version 2.0 (the "License");
6 | @rem you may not use this file except in compliance with the License.
7 | @rem You may obtain a copy of the License at
8 | @rem
9 | @rem https://www.apache.org/licenses/LICENSE-2.0
10 | @rem
11 | @rem Unless required by applicable law or agreed to in writing, software
12 | @rem distributed under the License is distributed on an "AS IS" BASIS,
13 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | @rem See the License for the specific language governing permissions and
15 | @rem limitations under the License.
16 | @rem
17 |
18 | @if "%DEBUG%" == "" @echo off
19 | @rem ##########################################################################
20 | @rem
21 | @rem Gradle startup script for Windows
22 | @rem
23 | @rem ##########################################################################
24 |
25 | @rem Set local scope for the variables with windows NT shell
26 | if "%OS%"=="Windows_NT" setlocal
27 |
28 | set DIRNAME=%~dp0
29 | if "%DIRNAME%" == "" set DIRNAME=.
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if "%ERRORLEVEL%" == "0" goto execute
45 |
46 | echo.
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
48 | echo.
49 | echo Please set the JAVA_HOME variable in your environment to match the
50 | echo location of your Java installation.
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo.
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
62 | echo.
63 | echo Please set the JAVA_HOME variable in your environment to match the
64 | echo location of your Java installation.
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 |
2 | #Gradle
3 | org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M"
4 |
5 | #Kotlin
6 | kotlin.code.style=official
7 | kotlin.js.compiler=ir
8 |
9 | #MPP
10 | kotlin.mpp.enableCInteropCommonization=true
11 | kotlin.mpp.androidSourceSetLayoutVersion=2
12 |
13 | #Compose
14 | org.jetbrains.compose.experimental.uikit.enabled=true
15 | org.jetbrains.compose.experimental.jscanvas.enabled=true
16 | kotlin.native.cacheKind=none
17 |
18 | #Android
19 | android.useAndroidX=true
20 | android.nonTransitiveRClass=true
21 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 |
3 | kotlin = "1.8.20"
4 | agp = "7.4.2"
5 | compose = "1.4.0"
6 | androidx-appcompat = "1.6.1"
7 | androidx-activityCompose = "1.7.0"
8 | compose-uitooling = "1.4.2"
9 | libres = "1.1.8"
10 | kotlinx-coroutines = "1.6.4"
11 | kotlinx-serialization = "1.8.10"
12 | kotlinx-serialization-json = "1.5.0"
13 |
14 | [libraries]
15 |
16 | androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" }
17 | androidx-activityCompose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" }
18 | compose-uitooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose-uitooling" }
19 | libres = { module = "io.github.skeptick.libres:libres-compose", version.ref = "libres" }
20 | kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
21 | kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
22 | kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" }
23 |
24 | [plugins]
25 |
26 | multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
27 | cocoapods = { id = "org.jetbrains.kotlin.native.cocoapods", version.ref = "kotlin" }
28 | compose = { id = "org.jetbrains.compose", version.ref = "compose" }
29 | android-application = { id = "com.android.application", version.ref = "agp" }
30 | libres = { id = "io.github.skeptick.libres", version.ref = "libres" }
31 | serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlinx-serialization" }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nezih94/Kotlin-Multiplatform-Particles/853480c087dc7c46e5beb773600e99aa9a5fd664/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 |
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 |
2 | #!/usr/bin/env sh
3 |
4 | #
5 | # Copyright 2015 the original author or authors.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # https://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 |
20 | ##############################################################################
21 | ##
22 | ## Gradle start up script for UN*X
23 | ##
24 | ##############################################################################
25 |
26 | # Attempt to set APP_HOME
27 | # Resolve links: ${'$'}0 may be a link
28 | PRG="$0"
29 | # Need this for relative symlinks.
30 | while [ -h "$PRG" ] ; do
31 | ls=`ls -ld "$PRG"`
32 | link=`expr "$ls" : '.*-> \(.*\)${'$'}'`
33 | if expr "$link" : '/.*' > /dev/null; then
34 | PRG="$link"
35 | else
36 | PRG=`dirname "$PRG"`"/$link"
37 | fi
38 | done
39 | SAVED="`pwd`"
40 | cd "`dirname \"$PRG\"`/" >/dev/null
41 | APP_HOME="`pwd -P`"
42 | cd "$SAVED" >/dev/null
43 |
44 | APP_NAME="Gradle"
45 | APP_BASE_NAME=`basename "$0"`
46 |
47 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
48 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
49 |
50 | # Use the maximum available, or set MAX_FD != -1 to use that value.
51 | MAX_FD="maximum"
52 |
53 | warn () {
54 | echo "${'$'}*"
55 | }
56 |
57 | die () {
58 | echo
59 | echo "${'$'}*"
60 | echo
61 | exit 1
62 | }
63 |
64 | # OS specific support (must be 'true' or 'false').
65 | cygwin=false
66 | msys=false
67 | darwin=false
68 | nonstop=false
69 | case "`uname`" in
70 | CYGWIN* )
71 | cygwin=true
72 | ;;
73 | Darwin* )
74 | darwin=true
75 | ;;
76 | MSYS* | MINGW* )
77 | msys=true
78 | ;;
79 | NONSTOP* )
80 | nonstop=true
81 | ;;
82 | esac
83 |
84 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
85 |
86 |
87 | # Determine the Java command to use to start the JVM.
88 | if [ -n "$JAVA_HOME" ] ; then
89 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
90 | # IBM's JDK on AIX uses strange locations for the executables
91 | JAVACMD="$JAVA_HOME/jre/sh/java"
92 | else
93 | JAVACMD="$JAVA_HOME/bin/java"
94 | fi
95 | if [ ! -x "$JAVACMD" ] ; then
96 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
97 |
98 | Please set the JAVA_HOME variable in your environment to match the
99 | location of your Java installation."
100 | fi
101 | else
102 | JAVACMD="java"
103 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
104 |
105 | Please set the JAVA_HOME variable in your environment to match the
106 | location of your Java installation."
107 | fi
108 |
109 | # Increase the maximum file descriptors if we can.
110 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
111 | MAX_FD_LIMIT=`ulimit -H -n`
112 | if [ $? -eq 0 ] ; then
113 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
114 | MAX_FD="$MAX_FD_LIMIT"
115 | fi
116 | ulimit -n $MAX_FD
117 | if [ $? -ne 0 ] ; then
118 | warn "Could not set maximum file descriptor limit: $MAX_FD"
119 | fi
120 | else
121 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
122 | fi
123 | fi
124 |
125 | # For Darwin, add options to specify how the application appears in the dock
126 | if $darwin; then
127 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
128 | fi
129 |
130 | # For Cygwin or MSYS, switch paths to Windows format before running java
131 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
132 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
133 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
134 |
135 | JAVACMD=`cygpath --unix "$JAVACMD"`
136 |
137 | # We build the pattern for arguments to be converted via cygpath
138 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
139 | SEP=""
140 | for dir in $ROOTDIRSRAW ; do
141 | ROOTDIRS="$ROOTDIRS$SEP$dir"
142 | SEP="|"
143 | done
144 | OURCYGPATTERN="(^($ROOTDIRS))"
145 | # Add a user-defined pattern to the cygpath arguments
146 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
147 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
148 | fi
149 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
150 | i=0
151 | for arg in "$@" ; do
152 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
153 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
154 |
155 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
156 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
157 | else
158 | eval `echo args$i`="\"$arg\""
159 | fi
160 | i=`expr $i + 1`
161 | done
162 | case $i in
163 | 0) set -- ;;
164 | 1) set -- "$args0" ;;
165 | 2) set -- "$args0" "$args1" ;;
166 | 3) set -- "$args0" "$args1" "$args2" ;;
167 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
168 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
169 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
170 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
171 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
172 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
173 | esac
174 | fi
175 |
176 | # Escape application args
177 | save () {
178 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
179 | echo " "
180 | }
181 | APP_ARGS=`save "$@"`
182 |
183 | # Collect all arguments for the java command, following the shell quoting and substitution rules
184 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
185 |
186 | exec "$JAVACMD" "$@"
187 |
--------------------------------------------------------------------------------
/iosApp/Podfile:
--------------------------------------------------------------------------------
1 | target 'iosApp' do
2 | use_frameworks!
3 | platform :ios, '16.2'
4 | pod 'composeApp', :path => '../composeApp'
5 | end
6 |
--------------------------------------------------------------------------------
/iosApp/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - composeApp (1.0.0)
3 |
4 | DEPENDENCIES:
5 | - composeApp (from `../composeApp`)
6 |
7 | EXTERNAL SOURCES:
8 | composeApp:
9 | :path: "../composeApp"
10 |
11 | SPEC CHECKSUMS:
12 | composeApp: 5ecf7ce392a8963914a02607119428a19ae416d7
13 |
14 | PODFILE CHECKSUM: 3313a530e4ca4361dd2a9b97cf3c536967731c31
15 |
16 | COCOAPODS: 1.12.0
17 |
--------------------------------------------------------------------------------
/iosApp/iosApp.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | A93A953B29CC810C00F8E227 /* iosApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A93A953A29CC810C00F8E227 /* iosApp.swift */; };
11 | A93A953F29CC810D00F8E227 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A93A953E29CC810D00F8E227 /* Assets.xcassets */; };
12 | A93A954229CC810D00F8E227 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A93A954129CC810D00F8E227 /* Preview Assets.xcassets */; };
13 | D51586C6B134BDB93BE1DFFB /* Pods_iosApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE080622DF072CBDC3D9C873 /* Pods_iosApp.framework */; };
14 | /* End PBXBuildFile section */
15 |
16 | /* Begin PBXFileReference section */
17 | 05E67C72B2BDBC81379103CB /* Pods-iosApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.release.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.release.xcconfig"; sourceTree = ""; };
18 | 73FFDDCA9C728FEE3DFEF2F4 /* Pods-iosApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.debug.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig"; sourceTree = ""; };
19 | A93A953729CC810C00F8E227 /* Multiplatform-Particles.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Multiplatform-Particles.app"; sourceTree = BUILT_PRODUCTS_DIR; };
20 | A93A953A29CC810C00F8E227 /* iosApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iosApp.swift; sourceTree = ""; };
21 | A93A953E29CC810D00F8E227 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
22 | A93A954129CC810D00F8E227 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
23 | EE080622DF072CBDC3D9C873 /* Pods_iosApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iosApp.framework; sourceTree = BUILT_PRODUCTS_DIR; };
24 | /* End PBXFileReference section */
25 |
26 | /* Begin PBXFrameworksBuildPhase section */
27 | A93A953429CC810C00F8E227 /* Frameworks */ = {
28 | isa = PBXFrameworksBuildPhase;
29 | buildActionMask = 2147483647;
30 | files = (
31 | D51586C6B134BDB93BE1DFFB /* Pods_iosApp.framework in Frameworks */,
32 | );
33 | runOnlyForDeploymentPostprocessing = 0;
34 | };
35 | /* End PBXFrameworksBuildPhase section */
36 |
37 | /* Begin PBXGroup section */
38 | 979913F6AE5D271756D4649D /* Pods */ = {
39 | isa = PBXGroup;
40 | children = (
41 | 73FFDDCA9C728FEE3DFEF2F4 /* Pods-iosApp.debug.xcconfig */,
42 | 05E67C72B2BDBC81379103CB /* Pods-iosApp.release.xcconfig */,
43 | );
44 | path = Pods;
45 | sourceTree = "";
46 | };
47 | A93A952E29CC810C00F8E227 = {
48 | isa = PBXGroup;
49 | children = (
50 | A93A953929CC810C00F8E227 /* iosApp */,
51 | A93A953829CC810C00F8E227 /* Products */,
52 | 979913F6AE5D271756D4649D /* Pods */,
53 | C4127409AE3703430489E7BC /* Frameworks */,
54 | );
55 | sourceTree = "";
56 | };
57 | A93A953829CC810C00F8E227 /* Products */ = {
58 | isa = PBXGroup;
59 | children = (
60 | A93A953729CC810C00F8E227 /* Multiplatform-Particles.app */,
61 | );
62 | name = Products;
63 | sourceTree = "";
64 | };
65 | A93A953929CC810C00F8E227 /* iosApp */ = {
66 | isa = PBXGroup;
67 | children = (
68 | A93A953A29CC810C00F8E227 /* iosApp.swift */,
69 | A93A953E29CC810D00F8E227 /* Assets.xcassets */,
70 | A93A954029CC810D00F8E227 /* Preview Content */,
71 | );
72 | path = iosApp;
73 | sourceTree = "";
74 | };
75 | A93A954029CC810D00F8E227 /* Preview Content */ = {
76 | isa = PBXGroup;
77 | children = (
78 | A93A954129CC810D00F8E227 /* Preview Assets.xcassets */,
79 | );
80 | path = "Preview Content";
81 | sourceTree = "";
82 | };
83 | C4127409AE3703430489E7BC /* Frameworks */ = {
84 | isa = PBXGroup;
85 | children = (
86 | EE080622DF072CBDC3D9C873 /* Pods_iosApp.framework */,
87 | );
88 | name = Frameworks;
89 | sourceTree = "";
90 | };
91 | /* End PBXGroup section */
92 |
93 | /* Begin PBXNativeTarget section */
94 | A93A953629CC810C00F8E227 /* iosApp */ = {
95 | isa = PBXNativeTarget;
96 | buildConfigurationList = A93A954529CC810D00F8E227 /* Build configuration list for PBXNativeTarget "iosApp" */;
97 | buildPhases = (
98 | D3665D361753A60B1A6EEEB7 /* [CP] Check Pods Manifest.lock */,
99 | A93A953329CC810C00F8E227 /* Sources */,
100 | A93A953429CC810C00F8E227 /* Frameworks */,
101 | A93A953529CC810C00F8E227 /* Resources */,
102 | 4D7B9BBE13F2636D045A0A84 /* [CP] Copy Pods Resources */,
103 | );
104 | buildRules = (
105 | );
106 | dependencies = (
107 | );
108 | name = iosApp;
109 | productName = iosApp;
110 | productReference = A93A953729CC810C00F8E227 /* Multiplatform-Particles.app */;
111 | productType = "com.apple.product-type.application";
112 | };
113 | /* End PBXNativeTarget section */
114 |
115 | /* Begin PBXProject section */
116 | A93A952F29CC810C00F8E227 /* Project object */ = {
117 | isa = PBXProject;
118 | attributes = {
119 | BuildIndependentTargetsInParallel = 1;
120 | LastSwiftUpdateCheck = 1420;
121 | LastUpgradeCheck = 1420;
122 | TargetAttributes = {
123 | A93A953629CC810C00F8E227 = {
124 | CreatedOnToolsVersion = 14.2;
125 | };
126 | };
127 | };
128 | buildConfigurationList = A93A953229CC810C00F8E227 /* Build configuration list for PBXProject "iosApp" */;
129 | compatibilityVersion = "Xcode 14.0";
130 | developmentRegion = en;
131 | hasScannedForEncodings = 0;
132 | knownRegions = (
133 | en,
134 | Base,
135 | );
136 | mainGroup = A93A952E29CC810C00F8E227;
137 | productRefGroup = A93A953829CC810C00F8E227 /* Products */;
138 | projectDirPath = "";
139 | projectRoot = "";
140 | targets = (
141 | A93A953629CC810C00F8E227 /* iosApp */,
142 | );
143 | };
144 | /* End PBXProject section */
145 |
146 | /* Begin PBXResourcesBuildPhase section */
147 | A93A953529CC810C00F8E227 /* Resources */ = {
148 | isa = PBXResourcesBuildPhase;
149 | buildActionMask = 2147483647;
150 | files = (
151 | A93A954229CC810D00F8E227 /* Preview Assets.xcassets in Resources */,
152 | A93A953F29CC810D00F8E227 /* Assets.xcassets in Resources */,
153 | );
154 | runOnlyForDeploymentPostprocessing = 0;
155 | };
156 | /* End PBXResourcesBuildPhase section */
157 |
158 | /* Begin PBXShellScriptBuildPhase section */
159 | 4D7B9BBE13F2636D045A0A84 /* [CP] Copy Pods Resources */ = {
160 | isa = PBXShellScriptBuildPhase;
161 | buildActionMask = 2147483647;
162 | files = (
163 | );
164 | inputFileListPaths = (
165 | "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources-${CONFIGURATION}-input-files.xcfilelist",
166 | );
167 | name = "[CP] Copy Pods Resources";
168 | outputFileListPaths = (
169 | "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources-${CONFIGURATION}-output-files.xcfilelist",
170 | );
171 | runOnlyForDeploymentPostprocessing = 0;
172 | shellPath = /bin/sh;
173 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources.sh\"\n";
174 | showEnvVarsInLog = 0;
175 | };
176 | D3665D361753A60B1A6EEEB7 /* [CP] Check Pods Manifest.lock */ = {
177 | isa = PBXShellScriptBuildPhase;
178 | buildActionMask = 2147483647;
179 | files = (
180 | );
181 | inputFileListPaths = (
182 | );
183 | inputPaths = (
184 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
185 | "${PODS_ROOT}/Manifest.lock",
186 | );
187 | name = "[CP] Check Pods Manifest.lock";
188 | outputFileListPaths = (
189 | );
190 | outputPaths = (
191 | "$(DERIVED_FILE_DIR)/Pods-iosApp-checkManifestLockResult.txt",
192 | );
193 | runOnlyForDeploymentPostprocessing = 0;
194 | shellPath = /bin/sh;
195 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
196 | showEnvVarsInLog = 0;
197 | };
198 | /* End PBXShellScriptBuildPhase section */
199 |
200 | /* Begin PBXSourcesBuildPhase section */
201 | A93A953329CC810C00F8E227 /* Sources */ = {
202 | isa = PBXSourcesBuildPhase;
203 | buildActionMask = 2147483647;
204 | files = (
205 | A93A953B29CC810C00F8E227 /* iosApp.swift in Sources */,
206 | );
207 | runOnlyForDeploymentPostprocessing = 0;
208 | };
209 | /* End PBXSourcesBuildPhase section */
210 |
211 | /* Begin XCBuildConfiguration section */
212 | A93A954329CC810D00F8E227 /* Debug */ = {
213 | isa = XCBuildConfiguration;
214 | buildSettings = {
215 | ALWAYS_SEARCH_USER_PATHS = NO;
216 | CLANG_ANALYZER_NONNULL = YES;
217 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
218 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
219 | CLANG_ENABLE_MODULES = YES;
220 | CLANG_ENABLE_OBJC_ARC = YES;
221 | CLANG_ENABLE_OBJC_WEAK = YES;
222 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
223 | CLANG_WARN_BOOL_CONVERSION = YES;
224 | CLANG_WARN_COMMA = YES;
225 | CLANG_WARN_CONSTANT_CONVERSION = YES;
226 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
227 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
228 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
229 | CLANG_WARN_EMPTY_BODY = YES;
230 | CLANG_WARN_ENUM_CONVERSION = YES;
231 | CLANG_WARN_INFINITE_RECURSION = YES;
232 | CLANG_WARN_INT_CONVERSION = YES;
233 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
234 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
235 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
236 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
237 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
238 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
239 | CLANG_WARN_STRICT_PROTOTYPES = YES;
240 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
241 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
242 | CLANG_WARN_UNREACHABLE_CODE = YES;
243 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
244 | COPY_PHASE_STRIP = NO;
245 | DEBUG_INFORMATION_FORMAT = dwarf;
246 | ENABLE_STRICT_OBJC_MSGSEND = YES;
247 | ENABLE_TESTABILITY = YES;
248 | GCC_C_LANGUAGE_STANDARD = gnu11;
249 | GCC_DYNAMIC_NO_PIC = NO;
250 | GCC_NO_COMMON_BLOCKS = YES;
251 | GCC_OPTIMIZATION_LEVEL = 0;
252 | GCC_PREPROCESSOR_DEFINITIONS = (
253 | "DEBUG=1",
254 | "$(inherited)",
255 | );
256 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
257 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
258 | GCC_WARN_UNDECLARED_SELECTOR = YES;
259 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
260 | GCC_WARN_UNUSED_FUNCTION = YES;
261 | GCC_WARN_UNUSED_VARIABLE = YES;
262 | IPHONEOS_DEPLOYMENT_TARGET = 16.2;
263 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
264 | MTL_FAST_MATH = YES;
265 | ONLY_ACTIVE_ARCH = YES;
266 | SDKROOT = iphoneos;
267 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
268 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
269 | };
270 | name = Debug;
271 | };
272 | A93A954429CC810D00F8E227 /* Release */ = {
273 | isa = XCBuildConfiguration;
274 | buildSettings = {
275 | ALWAYS_SEARCH_USER_PATHS = NO;
276 | CLANG_ANALYZER_NONNULL = YES;
277 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
278 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
279 | CLANG_ENABLE_MODULES = YES;
280 | CLANG_ENABLE_OBJC_ARC = YES;
281 | CLANG_ENABLE_OBJC_WEAK = YES;
282 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
283 | CLANG_WARN_BOOL_CONVERSION = YES;
284 | CLANG_WARN_COMMA = YES;
285 | CLANG_WARN_CONSTANT_CONVERSION = YES;
286 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
287 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
288 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
289 | CLANG_WARN_EMPTY_BODY = YES;
290 | CLANG_WARN_ENUM_CONVERSION = YES;
291 | CLANG_WARN_INFINITE_RECURSION = YES;
292 | CLANG_WARN_INT_CONVERSION = YES;
293 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
294 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
295 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
296 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
297 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
298 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
299 | CLANG_WARN_STRICT_PROTOTYPES = YES;
300 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
301 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
302 | CLANG_WARN_UNREACHABLE_CODE = YES;
303 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
304 | COPY_PHASE_STRIP = NO;
305 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
306 | ENABLE_NS_ASSERTIONS = NO;
307 | ENABLE_STRICT_OBJC_MSGSEND = YES;
308 | GCC_C_LANGUAGE_STANDARD = gnu11;
309 | GCC_NO_COMMON_BLOCKS = YES;
310 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
311 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
312 | GCC_WARN_UNDECLARED_SELECTOR = YES;
313 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
314 | GCC_WARN_UNUSED_FUNCTION = YES;
315 | GCC_WARN_UNUSED_VARIABLE = YES;
316 | IPHONEOS_DEPLOYMENT_TARGET = 16.2;
317 | MTL_ENABLE_DEBUG_INFO = NO;
318 | MTL_FAST_MATH = YES;
319 | SDKROOT = iphoneos;
320 | SWIFT_COMPILATION_MODE = wholemodule;
321 | SWIFT_OPTIMIZATION_LEVEL = "-O";
322 | VALIDATE_PRODUCT = YES;
323 | };
324 | name = Release;
325 | };
326 | A93A954629CC810D00F8E227 /* Debug */ = {
327 | isa = XCBuildConfiguration;
328 | baseConfigurationReference = 73FFDDCA9C728FEE3DFEF2F4 /* Pods-iosApp.debug.xcconfig */;
329 | buildSettings = {
330 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
331 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
332 | CODE_SIGN_STYLE = Automatic;
333 | CURRENT_PROJECT_VERSION = 1;
334 | DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
335 | ENABLE_PREVIEWS = YES;
336 | GENERATE_INFOPLIST_FILE = YES;
337 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
338 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
339 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
340 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
341 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
342 | LD_RUNPATH_SEARCH_PATHS = (
343 | "$(inherited)",
344 | "@executable_path/Frameworks",
345 | );
346 | MARKETING_VERSION = 1.0;
347 | PRODUCT_BUNDLE_IDENTIFIER = com.nezihyilmaz.multiplatform.particles.iosApp;
348 | PRODUCT_NAME = "Multiplatform-Particles";
349 | SWIFT_EMIT_LOC_STRINGS = YES;
350 | SWIFT_VERSION = 5.0;
351 | TARGETED_DEVICE_FAMILY = "1,2";
352 | };
353 | name = Debug;
354 | };
355 | A93A954729CC810D00F8E227 /* Release */ = {
356 | isa = XCBuildConfiguration;
357 | baseConfigurationReference = 05E67C72B2BDBC81379103CB /* Pods-iosApp.release.xcconfig */;
358 | buildSettings = {
359 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
360 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
361 | CODE_SIGN_STYLE = Automatic;
362 | CURRENT_PROJECT_VERSION = 1;
363 | DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
364 | ENABLE_PREVIEWS = YES;
365 | GENERATE_INFOPLIST_FILE = YES;
366 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
367 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
368 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
369 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
370 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
371 | LD_RUNPATH_SEARCH_PATHS = (
372 | "$(inherited)",
373 | "@executable_path/Frameworks",
374 | );
375 | MARKETING_VERSION = 1.0;
376 | PRODUCT_BUNDLE_IDENTIFIER = com.nezihyilmaz.multiplatform.particles.iosApp;
377 | PRODUCT_NAME = "Multiplatform-Particles";
378 | SWIFT_EMIT_LOC_STRINGS = YES;
379 | SWIFT_VERSION = 5.0;
380 | TARGETED_DEVICE_FAMILY = "1,2";
381 | };
382 | name = Release;
383 | };
384 | /* End XCBuildConfiguration section */
385 |
386 | /* Begin XCConfigurationList section */
387 | A93A953229CC810C00F8E227 /* Build configuration list for PBXProject "iosApp" */ = {
388 | isa = XCConfigurationList;
389 | buildConfigurations = (
390 | A93A954329CC810D00F8E227 /* Debug */,
391 | A93A954429CC810D00F8E227 /* Release */,
392 | );
393 | defaultConfigurationIsVisible = 0;
394 | defaultConfigurationName = Release;
395 | };
396 | A93A954529CC810D00F8E227 /* Build configuration list for PBXNativeTarget "iosApp" */ = {
397 | isa = XCConfigurationList;
398 | buildConfigurations = (
399 | A93A954629CC810D00F8E227 /* Debug */,
400 | A93A954729CC810D00F8E227 /* Release */,
401 | );
402 | defaultConfigurationIsVisible = 0;
403 | defaultConfigurationName = Release;
404 | };
405 | /* End XCConfigurationList section */
406 | };
407 | rootObject = A93A952F29CC810C00F8E227 /* Project object */;
408 | }
409 |
--------------------------------------------------------------------------------
/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/iosApp/iosApp.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "ios",
6 | "size" : "1024x1024"
7 | }
8 | ],
9 | "info" : {
10 | "author" : "xcode",
11 | "version" : 1
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/iosApp/iosApp/iosApp.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import SwiftUI
3 | import ComposeApp
4 |
5 | @main
6 | struct iosApp: App {
7 | var body: some Scene {
8 | WindowGroup {
9 | ContentView()
10 | }
11 | }
12 | }
13 |
14 | struct ContentView: View {
15 | var body: some View {
16 | ComposeView().ignoresSafeArea(.keyboard)
17 | }
18 | }
19 |
20 | struct ComposeView: UIViewControllerRepresentable {
21 | func makeUIViewController(context: Context) -> UIViewController {
22 | MainKt.MainViewController()
23 | }
24 |
25 | func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
26 | }
27 |
--------------------------------------------------------------------------------
/screenshot/ss_desktop_action.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nezih94/Kotlin-Multiplatform-Particles/853480c087dc7c46e5beb773600e99aa9a5fd664/screenshot/ss_desktop_action.png
--------------------------------------------------------------------------------
/screenshot/ss_desktop_idle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nezih94/Kotlin-Multiplatform-Particles/853480c087dc7c46e5beb773600e99aa9a5fd664/screenshot/ss_desktop_idle.png
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "Multiplatform-Particles"
2 | include(":composeApp")
3 |
4 | pluginManagement {
5 | repositories {
6 | google()
7 | gradlePluginPortal()
8 | mavenCentral()
9 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
10 | }
11 | }
12 |
13 | dependencyResolutionManagement {
14 | repositories {
15 | google()
16 | mavenCentral()
17 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
18 | }
19 | }
20 |
--------------------------------------------------------------------------------