Skip to content

Commit 8d35fe8

Browse files
authored
Merge pull request #495 from icerockdev/#489-macos-compose-colors-crash
#489 fix color reading in compose on native macos
2 parents 7e129e6 + cd4b69b commit 8d35fe8

10 files changed

Lines changed: 252 additions & 18 deletions

File tree

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ buildscript {
6262
}
6363
6464
dependencies {
65-
classpath "dev.icerock.moko:resources-generator:0.22.2"
65+
classpath "dev.icerock.moko:resources-generator:0.22.3"
6666
}
6767
}
6868
@@ -80,10 +80,10 @@ project build.gradle
8080
apply plugin: "dev.icerock.mobile.multiplatform-resources"
8181
8282
dependencies {
83-
commonMainApi("dev.icerock.moko:resources:0.22.2")
84-
commonMainApi("dev.icerock.moko:resources-compose:0.22.2") // for compose multiplatform
83+
commonMainApi("dev.icerock.moko:resources:0.22.3")
84+
commonMainApi("dev.icerock.moko:resources-compose:0.22.3") // for compose multiplatform
8585
86-
commonTestImplementation("dev.icerock.moko:resources-test:0.22.2")
86+
commonTestImplementation("dev.icerock.moko:resources-test:0.22.3")
8787
}
8888
8989
multiplatformResources {
@@ -102,7 +102,7 @@ should [add `export` declarations](https://kotlinlang.org/docs/multiplatform-bui
102102

103103
```
104104
framework {
105-
export("dev.icerock.moko:resources:0.22.2")
105+
export("dev.icerock.moko:resources:0.22.3")
106106
export("dev.icerock.moko:graphics:0.9.0") // toUIColor here
107107
}
108108
```

gradle/moko.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[versions]
2-
resourcesVersion = "0.22.2"
2+
resourcesVersion = "0.22.3"
33

44
[libraries]
55
resources = { module = "dev.icerock.moko:resources", version.ref = "resourcesVersion" }

resources-compose/src/macosMain/kotlin/dev/icerock/moko/resources/compose/ColorResource.kt

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,23 @@ import androidx.compose.ui.graphics.Color
1111
import dev.icerock.moko.resources.ColorResource
1212
import dev.icerock.moko.resources.getNSColor
1313
import platform.AppKit.NSColor
14+
import platform.AppKit.NSColorSpace.Companion.deviceRGBColorSpace
1415

1516
@Composable
1617
actual fun colorResource(resource: ColorResource): Color {
1718
// TODO https://github.com/icerockdev/moko-resources/issues/443
1819
// recompose when appearance changed (now not works in runtime!)
1920
val darkMode: Boolean = isSystemInDarkTheme()
20-
return remember(resource) {
21-
val ciColor: NSColor = resource.getNSColor()
21+
return remember(resource, darkMode) {
22+
val nsColor: NSColor = resource.getNSColor()
23+
val deviceColor: NSColor = nsColor.colorUsingColorSpace(deviceRGBColorSpace)
24+
?: error("can't convert $nsColor to deviceRGBColorSpace")
25+
2226
Color(
23-
red = ciColor.redComponent.toFloat(),
24-
green = ciColor.greenComponent.toFloat(),
25-
blue = ciColor.blueComponent.toFloat(),
26-
alpha = ciColor.alphaComponent.toFloat()
27+
red = deviceColor.redComponent.toFloat(),
28+
green = deviceColor.greenComponent.toFloat(),
29+
blue = deviceColor.blueComponent.toFloat(),
30+
alpha = deviceColor.alphaComponent.toFloat()
2731
)
2832
}
2933
}

samples/compose-resources-gallery/androidApp/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ plugins {
66

77
kotlin {
88
android()
9+
10+
@Suppress("UNUSED_VARIABLE")
911
sourceSets {
1012
val androidMain by getting {
1113
dependencies {

samples/compose-resources-gallery/iosApp/iosApp.xcodeproj/project.pbxproj

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@
112112
7555FF77242A565900829871 /* Sources */,
113113
7555FF79242A565900829871 /* Resources */,
114114
F85CB1118929364A9C6EFABC /* Frameworks */,
115-
420F7168B5294CA0336A327D /* [CP] Embed Pods Frameworks */,
115+
77F733FF1C0E0F137B1CD22F /* [CP] Copy Pods Resources */,
116116
);
117117
buildRules = (
118118
);
@@ -167,21 +167,21 @@
167167
/* End PBXResourcesBuildPhase section */
168168

169169
/* Begin PBXShellScriptBuildPhase section */
170-
420F7168B5294CA0336A327D /* [CP] Embed Pods Frameworks */ = {
170+
77F733FF1C0E0F137B1CD22F /* [CP] Copy Pods Resources */ = {
171171
isa = PBXShellScriptBuildPhase;
172172
buildActionMask = 2147483647;
173173
files = (
174174
);
175175
inputFileListPaths = (
176-
"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks-${CONFIGURATION}-input-files.xcfilelist",
176+
"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources-${CONFIGURATION}-input-files.xcfilelist",
177177
);
178-
name = "[CP] Embed Pods Frameworks";
178+
name = "[CP] Copy Pods Resources";
179179
outputFileListPaths = (
180-
"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks-${CONFIGURATION}-output-files.xcfilelist",
180+
"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources-${CONFIGURATION}-output-files.xcfilelist",
181181
);
182182
runOnlyForDeploymentPostprocessing = 0;
183183
shellPath = /bin/sh;
184-
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks.sh\"\n";
184+
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources.sh\"\n";
185185
showEnvVarsInLog = 0;
186186
};
187187
98D614C51D2DA07C614CC46E /* [CP] Check Pods Manifest.lock */ = {
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink
2+
import java.io.File
3+
import kotlin.reflect.full.declaredMemberProperties
4+
5+
plugins {
6+
kotlin("multiplatform")
7+
id("org.jetbrains.compose")
8+
id("dev.icerock.mobile.multiplatform-resources")
9+
}
10+
11+
version = "0.1.0"
12+
13+
kotlin {
14+
macosX64 {
15+
binaries {
16+
executable {
17+
entryPoint = "main"
18+
freeCompilerArgs += listOf(
19+
"-linker-option", "-framework", "-linker-option", "Metal"
20+
)
21+
}
22+
}
23+
}
24+
25+
macosArm64 {
26+
binaries {
27+
executable {
28+
entryPoint = "main"
29+
freeCompilerArgs += listOf(
30+
"-linker-option", "-framework", "-linker-option", "Metal"
31+
)
32+
}
33+
}
34+
}
35+
36+
@Suppress("UNUSED_VARIABLE")
37+
sourceSets {
38+
val commonMain by getting {
39+
dependencies {
40+
api(project(":shared"))
41+
}
42+
}
43+
}
44+
}
45+
46+
compose.desktop.nativeApplication {
47+
targets(kotlin.targets.getByName("macosX64"), kotlin.targets.getByName("macosArm64"))
48+
distributions {
49+
targetFormats(org.jetbrains.compose.desktop.application.dsl.TargetFormat.Dmg)
50+
packageName = "dev.icerock.moko.resources.sample"
51+
packageVersion = "0.1.0"
52+
}
53+
}
54+
55+
kotlin {
56+
targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget> {
57+
binaries.all {
58+
// TODO: the current compose binary surprises LLVM, so disable checks for now.
59+
freeCompilerArgs += "-Xdisable-phases=VerifyBitcode"
60+
}
61+
}
62+
}
63+
64+
multiplatformResources {
65+
multiplatformResourcesPackage = "dev.icerock.moko.resources.sample"
66+
}
67+
68+
// TODO move to moko-resources gradle plugin
69+
// copy .bundle from all .klib to .kexe
70+
tasks.withType<KotlinNativeLink>()
71+
.configureEach {
72+
val linkTask: KotlinNativeLink = this
73+
val outputDir: File = this.outputFile.get().parentFile
74+
75+
@Suppress("ObjectLiteralToLambda") // lambda broke up-to-date
76+
val action = object : Action<Task> {
77+
override fun execute(t: Task) {
78+
(linkTask.libraries + linkTask.sources)
79+
.filter { library -> library.extension == "klib" }
80+
.filter(File::exists)
81+
.forEach { inputFile ->
82+
val klibKonan = org.jetbrains.kotlin.konan.file.File(inputFile.path)
83+
val klib = org.jetbrains.kotlin.library.impl.KotlinLibraryLayoutImpl(
84+
klib = klibKonan,
85+
component = "default"
86+
)
87+
val layout = klib.extractingToTemp
88+
89+
// extracting bundles
90+
layout
91+
.resourcesDir
92+
.absolutePath
93+
.let(::File)
94+
.listFiles { file: File -> file.extension == "bundle" }
95+
// copying bundles to app
96+
?.forEach {
97+
logger.info("${it.absolutePath} copying to $outputDir")
98+
it.copyRecursively(
99+
target = File(outputDir, it.name),
100+
overwrite = true
101+
)
102+
}
103+
}
104+
}
105+
}
106+
doLast(action)
107+
}
108+
109+
// TODO move to moko-resources gradle plugin
110+
// copy .bundle from .kexe to .app
111+
tasks.withType<org.jetbrains.compose.experimental.uikit.tasks.ExperimentalPackComposeApplicationForXCodeTask>()
112+
.configureEach {
113+
val packTask: org.jetbrains.compose.experimental.uikit.tasks.ExperimentalPackComposeApplicationForXCodeTask =
114+
this
115+
116+
val kclass =
117+
org.jetbrains.compose.experimental.uikit.tasks.ExperimentalPackComposeApplicationForXCodeTask::class
118+
val kotlinBinaryField =
119+
kclass.declaredMemberProperties.single { it.name == "kotlinBinary" }
120+
val destinationDirField =
121+
kclass.declaredMemberProperties.single { it.name == "destinationDir" }
122+
val executablePathField =
123+
kclass.declaredMemberProperties.single { it.name == "executablePath" }
124+
125+
@Suppress("ObjectLiteralToLambda") // lambda broke up-to-date
126+
val action = object : Action<Task> {
127+
override fun execute(t: Task) {
128+
val kotlinBinary: RegularFile =
129+
(kotlinBinaryField.get(packTask) as RegularFileProperty).get()
130+
val destinationDir: Directory =
131+
(destinationDirField.get(packTask) as DirectoryProperty).get()
132+
val executablePath: String =
133+
(executablePathField.get(packTask) as Provider<*>).get().toString()
134+
135+
val outputDir: File = File(destinationDir.asFile, executablePath).parentFile
136+
137+
val bundleSearchDir: File = kotlinBinary.asFile.parentFile
138+
bundleSearchDir
139+
.listFiles { file: File -> file.extension == "bundle" }
140+
?.forEach { file ->
141+
file.copyRecursively(File(outputDir, file.name), true)
142+
}
143+
}
144+
}
145+
doLast(action)
146+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import androidx.compose.ui.window.Window
2+
import com.icerockdev.library.MR
3+
import dev.icerock.moko.resources.desc.desc
4+
import kotlinx.cinterop.staticCFunction
5+
import platform.AppKit.NSApplication
6+
import platform.AppKit.NSApplicationActivationPolicy
7+
import platform.AppKit.NSApplicationDelegateProtocol
8+
import platform.darwin.NSObject
9+
import platform.objc.objc_setUncaughtExceptionHandler
10+
11+
@Suppress("UNUSED_PARAMETER")
12+
fun main(args: Array<String>) {
13+
setUnhandledExceptionHook {
14+
it.printStackTrace()
15+
}
16+
17+
objc_setUncaughtExceptionHandler(staticCFunction<Any?, Unit> {
18+
println(it)
19+
})
20+
21+
val app = NSApplication.sharedApplication()
22+
app.setActivationPolicy(NSApplicationActivationPolicy.NSApplicationActivationPolicyRegular)
23+
24+
app.delegate = object : NSObject(), NSApplicationDelegateProtocol {
25+
override fun applicationShouldTerminateAfterLastWindowClosed(sender: NSApplication): Boolean {
26+
return true
27+
}
28+
}
29+
30+
Window(MR.strings.hello_world.desc().localized()) {
31+
MacosApp()
32+
}
33+
34+
app.run()
35+
}

samples/compose-resources-gallery/settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,4 @@ include(":androidApp")
4040
include(":shared")
4141
include(":desktopApp")
4242
include(":webApp")
43+
include(":macosApp")

samples/compose-resources-gallery/shared/build.gradle.kts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import org.jetbrains.kotlin.gradle.tasks.DummyFrameworkTask
2+
13
plugins {
24
kotlin("multiplatform")
35
kotlin("native.cocoapods")
@@ -20,16 +22,23 @@ kotlin {
2022
browser()
2123
}
2224

25+
macosArm64()
26+
macosX64()
27+
2328
cocoapods {
2429
summary = "Some description for the Shared Module"
2530
homepage = "Link to the Shared Module homepage"
2631
ios.deploymentTarget = "14.1"
2732
podfile = project.file("../iosApp/Podfile")
2833
framework {
2934
baseName = "shared"
35+
isStatic = true
3036
}
37+
// TODO move to gradle plugin
38+
extraSpecAttributes["resource"] = "'build/cocoapods/framework/shared.framework/*.bundle'"
3139
}
3240

41+
@Suppress("UNUSED_VARIABLE")
3342
sourceSets {
3443
val commonMain by getting {
3544
dependencies {
@@ -61,6 +70,15 @@ kotlin {
6170
implementation(compose.html.core)
6271
}
6372
}
73+
val macosMain by creating {
74+
dependsOn(commonMain)
75+
}
76+
val macosX64Main by getting {
77+
dependsOn(macosMain)
78+
}
79+
val macosArm64Main by getting {
80+
dependsOn(macosMain)
81+
}
6482
}
6583
}
6684

@@ -83,3 +101,23 @@ android {
83101
multiplatformResources {
84102
multiplatformResourcesPackage = "com.icerockdev.library"
85103
}
104+
105+
// TODO move to gradle plugin
106+
tasks.withType<DummyFrameworkTask>().configureEach {
107+
@Suppress("ObjectLiteralToLambda")
108+
doLast(object : Action<Task> {
109+
override fun execute(task: Task) {
110+
task as DummyFrameworkTask
111+
112+
val frameworkDir = File(task.destinationDir, task.frameworkName.get() + ".framework")
113+
114+
listOf(
115+
"compose-resources-gallery:shared.bundle"
116+
).forEach { bundleName ->
117+
val bundleDir = File(frameworkDir, bundleName)
118+
bundleDir.mkdir()
119+
File(bundleDir, "dummyFile").writeText("dummy")
120+
}
121+
}
122+
})
123+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import androidx.compose.runtime.Composable
2+
3+
actual fun getPlatformName(): String = "macOS"
4+
5+
@Composable
6+
fun MacosApp() {
7+
App()
8+
}

0 commit comments

Comments
 (0)