All week now I’ve been wanting to have a try with JetBrain’s Compose Multiplatform Desktop. The demos I’ve found looked great. As a heads up though, those demos are all showing OSX as the run target. I am aware that Compose for Desktop can run against Windows, and (interesting to me) Linux.
What I was most interested in was the “Menu, tray, notifications” demo:
You can find the code for this (sort of) over on GitHub.
Whilst “basic”, I think it looks quite nice. Especially the slide in notification.
In my head, I had it that it would look kinda similar on Linux (specifically Ubuntu, which I use day-to-day), and that it would use the big notifications area:
I should definitely give a heavy caveat to all of this by saying I am an absolute newb at Java / Kotlin. What follows is literally me following tutorials and not truly understanding much of anything.
New Project Problems
Right, the first issue I faced was selecting the wrong project type.
I went with Kotlin Multiplatform, and I should have selected Compose Multiplatform.
I’m still not entirely sure what the difference is, if I’m honest.
What I do know is the resulting Gradle files are different, and migrating from one to the other was confusing enough that I simply created a new project and thought… this seems quite confusing.
But onwards.
What The Java?
Next up, the docs tell you that you need to update build.gradle.kts
as follows:
plugins {
kotlin("jvm") version "1.8.20"
id("org.jetbrains.compose") version "1.4.0"
}
Code language: Kotlin (kotlin)
The confusing part to this is that, on a fresh creation of the project using the JetBrains IntelliJ IDE, the build.gradle.kts
file looks like this:
plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
}
Code language: Kotlin (kotlin)
And the actual versions themselves are declared in the gradle.properties
file:
kotlin.code.style=official
kotlin.version=1.8.20
agp.version=7.3.0
compose.version=1.4.0
Fortunately I figured this part out myself.
But this wouldn’t actually work.
When I built the project and tried to run the main
function in Main.kt
, I got this wild error:
This literally takes up the full width of the screen, pretty nuts.
And Googling this error comes up… well, short.
/home/chris/.sdkman/candidates/java/20-open/bin/java -Djava.library.path=/home/chris/Development/compose-multiplatform-demo/build/classes/kotlin/jvm/main -javaagent:/home/chris/.local/share/JetBrains/Toolbox/apps/IDEA-U/ch-0/231.8109.175/lib/idea_rt.jar=38217:/home/chris/.local/share/JetBrains/Toolbox/apps/IDEA-U/ch-0/231.8109.175/bin -Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.stderr.encoding=UTF-8 -classpath /home/chris/Development/compose-multiplatform-demo/build/classes/kotlin/jvm/main:/home/chris/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk8/1.8.0/ed04f49e186a116753ad70d34f0ac2925d1d8020/kotlin-stdlib-jdk8-1.8.0.jar:/home/chris/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk7/1.8.0/3c91271347f678c239607abb676d4032a7898427/kotlin-stdlib-jdk7-1.8.0.jar:/home/chris/.gradle/caches/modules-2/files-2.1/org.jetbrains.skiko/skiko-awt-runtime-linux-x64/0.7.50/c80b3ef8abdd4db24fa9905d6e5d821e2a1f7ab5/skiko-awt-runtime-linux-x64-0.7.50.jar:/home/chris/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.8.0/1796921c7a3e2e2665a83e6c8d33399336cd39bc/kotlin-stdlib-1.8.0.jar:/home/chris/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.desktop/desktop-jvm/1.3.0/e2b2c3df64b02dab8673daba012294d9ef5efb49/desktop-jvm-1.3.0.jar:/home/chris/.gradle/caches/modules-2/files-2.1/org.jetbrains/annotations/13.0/919f0dfe192fb4e063e7dacadee7f8bb9a2672a9/annotations-13.0.jar:/home/chris/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.material/material-desktop/1.3.0/aee6d5f9630e14e42be059bb1e0a1a409aa8076/material-desktop-1.3.0.jar:/home/chris/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.foundation/foundation-desktop/1.3.0/22ab58187358e52e14714a61b0224317fc9af567/foundation-desktop-1.3.0.jar:/home/chris/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.ui/ui-desktop/1.3.0/33fe21065b205dc251cdee9d457e45658c26ca29/ui-desktop-1.3.0.jar:/home/chris/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.ui/ui-tooling-preview-desktop/1.3.0/9eaed02ecbf2a1be3ef5ff0481ce7691c5a4b399/ui-tooling-preview-desktop-1.3.0.jar:/home/chris/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.runtime/runtime-desktop/1.3.0/8b826174d5658059d552099a45ad33a964c79da9/runtime-desktop-1.3.0.jar:/home/chris/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.material/material-ripple-desktop/1.3.0/4544b518c966fb89e1d7d343646e8199f466ce56/material-ripple-desktop-1.3.0.jar:/home/chris/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.material/material-icons-core-desktop/1.3.0/587f4283e9874bc7c998a29c561d4ae4ae2165d4/material-icons-core-desktop-1.3.0.jar:/home/chris/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.ui/ui-text-desktop/1.3.0/b3fc15b133950effcc96a6c9d95daba6e4ae8ce2/ui-text-desktop-1.3.0.jar:/home/chris/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.animation/animation-core-desktop/1.3.0/b262c47b70599fd97b05afec477399e74749f64d/animation-core-desktop-1.3.0.jar:/home/chris/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.ui/ui-util-desktop/1.3.0/23411174bc22e0aa269121791c5814f0a57692c2/ui-util-desktop-1.3.0.jar:/home/chris/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.animation/animation-desktop/1.3.0/a821316519b968a53791011940dd216af36497aa/animation-desktop-1.3.0.jar:/home/chris/.gradle/caches/modules-2/files-2.1/org.jetbrains.skiko/skiko-awt/0.7.50/fa48f851a947ba3571133eedb28feecd3871d313/skiko-awt-0.7.50.jar:/home/chris/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.ui/ui-graphics-desktop/1.3.0/9f2375d3f9edc384590d3296c55bcf86eaee30ee/ui-graphics-desktop-1.3.0.jar:/home/chris/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.runtime/runtime-saveable-desktop/1.3.0/e07d563675823b53669d729eeaca1e0c5ccee30f/runtime-saveable-desktop-1.3.0.jar:/home/chris/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.ui/ui-unit-desktop/1.3.0/4211a519058e924e8b90f9658e0d0e9cf2088ae4/ui-unit-desktop-1.3.0.jar:/home/chris/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.ui/ui-geometry-desktop/1.3.0/339029b8e0b6396839297148b4ff7810c5326f54/ui-geometry-desktop-1.3.0.jar:/home/chris/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-core-jvm/1.6.4/2c997cd1c0ef33f3e751d3831929aeff1390cb30/kotlinx-coroutines-core-jvm-1.6.4.jar:/home/chris/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.foundation/foundation-layout-desktop/1.3.0/b7a7013707e561800bee39dc10e47079364ea561/foundation-layout-desktop-1.3.0.jar:/home/chris/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/atomicfu-jvm/0.17.2/7f2eae498c00cd33ef2a8eba4be72b284270aa16/atomicfu-jvm-0.17.2.jar MainKt
Exception in thread "main" java.lang.NoSuchMethodError: 'void androidx.compose.material.TextKt.Text--4IGK_g(java.lang.String, androidx.compose.ui.Modifier, long, long, androidx.compose.ui.text.font.FontStyle, androidx.compose.ui.text.font.FontWeight, androidx.compose.ui.text.font.FontFamily, long, androidx.compose.ui.text.style.TextDecoration, androidx.compose.ui.text.style.TextAlign, long, int, boolean, int, int, kotlin.jvm.functions.Function1, androidx.compose.ui.text.TextStyle, androidx.compose.runtime.Composer, int, int, int)'
at ComposableSingletons$MainKt$lambda-1$1$3.invoke(Main.kt:65)
at ComposableSingletons$MainKt$lambda-1$1$3.invoke(Main.kt:59)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:116)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
at androidx.compose.ui.awt.ComposeWindow$setContent$5.invoke(ComposeWindow.desktop.kt:126)
at androidx.compose.ui.awt.ComposeWindow$setContent$5.invoke(ComposeWindow.desktop.kt:125)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
at androidx.compose.ui.awt.ComposeWindowDelegate$setContent$3$1.invoke(ComposeWindowDelegate.desktop.kt:146)
at androidx.compose.ui.awt.ComposeWindowDelegate$setContent$3$1.invoke(ComposeWindowDelegate.desktop.kt:145)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
at androidx.compose.ui.awt.ComposeWindowDelegate$setContent$3.invoke(ComposeWindowDelegate.desktop.kt:142)
at androidx.compose.ui.awt.ComposeWindowDelegate$setContent$3.invoke(ComposeWindowDelegate.desktop.kt:141)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
at androidx.compose.ui.ComposeScene$setContent$5.invoke(ComposeScene.skiko.kt:371)
at androidx.compose.ui.ComposeScene$setContent$5.invoke(ComposeScene.skiko.kt:370)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
at androidx.compose.ui.platform.CompositionLocalsKt.ProvideCommonCompositionLocals(CompositionLocals.kt:177)
at androidx.compose.ui.platform.Wrapper_skikoKt$setContent$2$1.invoke(Wrapper.skiko.kt:47)
at androidx.compose.ui.platform.Wrapper_skikoKt$setContent$2$1.invoke(Wrapper.skiko.kt:46)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:248)
at androidx.compose.ui.platform.Wrapper_skikoKt.provide(Wrapper.skiko.kt:61)
at androidx.compose.ui.platform.Wrapper_skikoKt.access$provide(Wrapper.skiko.kt:1)
at androidx.compose.ui.platform.Wrapper_skikoKt$setContent$2.invoke(Wrapper.skiko.kt:46)
at androidx.compose.ui.platform.Wrapper_skikoKt$setContent$2.invoke(Wrapper.skiko.kt:45)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
at androidx.compose.runtime.ActualJvm_jvmKt.invokeComposable(ActualJvm.jvm.kt:73)
at androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:3248)
at androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:3238)
at androidx.compose.runtime.SnapshotStateKt__DerivedStateKt.observeDerivedStateRecalculations(DerivedState.kt:341)
at androidx.compose.runtime.SnapshotStateKt.observeDerivedStateRecalculations(Unknown Source)
at androidx.compose.runtime.ComposerImpl.doCompose(Composer.kt:3238)
at androidx.compose.runtime.ComposerImpl.composeContent$runtime(Composer.kt:3173)
at androidx.compose.runtime.CompositionImpl.composeContent(Composition.kt:587)
at androidx.compose.runtime.Recomposer.composeInitial$runtime(Recomposer.kt:950)
at androidx.compose.runtime.CompositionImpl.setContent(Composition.kt:519)
at androidx.compose.ui.platform.Wrapper_skikoKt.setContent(Wrapper.skiko.kt:45)
at androidx.compose.ui.ComposeScene.setContent$ui(ComposeScene.skiko.kt:367)
at androidx.compose.ui.ComposeScene.setContent$ui$default(ComposeScene.skiko.kt:346)
at androidx.compose.ui.awt.ComposeLayer$setContent$3$1.invoke(ComposeLayer.desktop.kt:435)
at androidx.compose.ui.awt.ComposeLayer$setContent$3$1.invoke(ComposeLayer.desktop.kt:434)
at androidx.compose.ui.awt.ComposeLayer.catchExceptions(ComposeLayer.desktop.kt:109)
at androidx.compose.ui.awt.ComposeLayer.access$catchExceptions(ComposeLayer.desktop.kt:87)
at androidx.compose.ui.awt.ComposeLayer$setContent$3.invoke(ComposeLayer.desktop.kt:434)
at androidx.compose.ui.awt.ComposeLayer$setContent$3.invoke(ComposeLayer.desktop.kt:433)
at androidx.compose.ui.awt.ComposeLayer.initContent(ComposeLayer.desktop.kt:449)
at androidx.compose.ui.awt.ComposeLayer.access$initContent(ComposeLayer.desktop.kt:87)
at androidx.compose.ui.awt.ComposeLayer$ComponentImpl.addNotify(ComposeLayer.desktop.kt:226)
at java.desktop/java.awt.Container.addNotify(Container.java:2804)
at java.desktop/javax.swing.JComponent.addNotify(JComponent.java:4846)
at androidx.compose.ui.awt.ComposeWindowDelegate$_pane$1.addNotify(ComposeWindowDelegate.desktop.kt:80)
at java.desktop/java.awt.Container.addNotify(Container.java:2804)
at java.desktop/javax.swing.JComponent.addNotify(JComponent.java:4846)
at java.desktop/java.awt.Container.addNotify(Container.java:2804)
at java.desktop/javax.swing.JComponent.addNotify(JComponent.java:4846)
at java.desktop/java.awt.Container.addNotify(Container.java:2804)
at java.desktop/javax.swing.JComponent.addNotify(JComponent.java:4846)
at java.desktop/javax.swing.JRootPane.addNotify(JRootPane.java:721)
at java.desktop/java.awt.Container.addNotify(Container.java:2804)
at java.desktop/java.awt.Window.addNotify(Window.java:791)
at java.desktop/java.awt.Frame.addNotify(Frame.java:495)
at java.desktop/java.awt.Window.pack(Window.java:829)
at androidx.compose.ui.util.Windows_desktopKt.makeDisplayable(Windows.desktop.kt:160)
at androidx.compose.ui.window.Window_desktopKt$Window$11$1.invoke(Window.desktop.kt:392)
at androidx.compose.ui.window.Window_desktopKt$Window$11$1.invoke(Window.desktop.kt:385)
at androidx.compose.ui.window.AwtWindow_desktopKt$AwtWindow$3.invoke(AwtWindow.desktop.kt:83)
at androidx.compose.ui.window.AwtWindow_desktopKt$AwtWindow$3.invoke(AwtWindow.desktop.kt:81)
at androidx.compose.ui.util.UpdateEffect_desktopKt$UpdateEffect$2$performUpdate$1.invoke(UpdateEffect.desktop.kt:59)
at androidx.compose.ui.util.UpdateEffect_desktopKt$UpdateEffect$2$performUpdate$1.invoke(UpdateEffect.desktop.kt:55)
at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2140)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$observeReads$1$1.invoke(SnapshotStateObserver.kt:134)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$observeReads$1$1.invoke(SnapshotStateObserver.kt:130)
at androidx.compose.runtime.SnapshotStateKt__DerivedStateKt.observeDerivedStateRecalculations(DerivedState.kt:341)
at androidx.compose.runtime.SnapshotStateKt.observeDerivedStateRecalculations(Unknown Source)
at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:130)
at androidx.compose.ui.util.UpdateEffect_desktopKt$UpdateEffect$2.invoke$performUpdate(UpdateEffect.desktop.kt:55)
at androidx.compose.ui.util.UpdateEffect_desktopKt$UpdateEffect$2.invoke(UpdateEffect.desktop.kt:64)
at androidx.compose.ui.util.UpdateEffect_desktopKt$UpdateEffect$2.invoke(UpdateEffect.desktop.kt:47)
at androidx.compose.runtime.DisposableEffectImpl.onRemembered(Effects.kt:81)
at androidx.compose.runtime.CompositionImpl$RememberEventDispatcher.dispatchRememberObservers(Composition.kt:1091)
at androidx.compose.runtime.CompositionImpl.applyChangesInLocked(Composition.kt:818)
at androidx.compose.runtime.CompositionImpl.applyChanges(Composition.kt:839)
at androidx.compose.runtime.Recomposer.composeInitial$runtime(Recomposer.kt:978)
at androidx.compose.runtime.CompositionImpl.setContent(Composition.kt:519)
at androidx.compose.ui.window.Application_desktopKt$awaitApplication$2$1$2.invokeSuspend(Application.desktop.kt:219)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:318)
at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:773)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:720)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:714)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:400)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:87)
at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:742)
at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)
Code language: PHP (php)
Always helpful to a newb, a huge stack trace and nothing on Google that directly answers the question.
At this point I ended up uninstalling the Temurin Java 17 (I think it was), and then installing Open JDK Java 20. I was fairly sure this was not the issue, and I was proven right in this, but I put it here as I did do this:
For what it’s worth, I’m using the SDK Man tool there. I guess that’s like nvm
for Node.
But as I say, that still errored.
What it took to fix this was to downgrade the versions I was using back in the gradle.properties
file:
kotlin.code.style=official
kotlin.version=1.8.10
agp.version=7.3.0
compose.version=1.3.1
You have to downgrade both, as they appear to work in lockstep. For example if you try to run Kotlin 1.18.20
with compose version 1.3.1
you get this:
This version of Compose Multiplatform doesn't support Kotlin 1.8.20. Please see https://github.com/JetBrains/compose-jb/blob/master/VERSIONING.md#kotlin-compatibility to know the latest supported version of Kotlin.
No big deal.
Once they were both downgraded, the project did build and run! Great success.
How This Looks On Linux
How does this look on Ubuntu?
Not the prettiest:
So you’ve got the big empty window, which is fine enough. That’s how this is setup. I’m not sure why it’s the size it is, I guess that too is a default. Maybe that’s 640×480 or something.
And then the little penguin icon (?) on the left is what you get in the taskbar. Notice it shows two little red balls next to it, indicating that there are two active windows for some reason:
Here’s the code for reference – it’s literally a copy / paste from the example GitHub linked above:
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Text
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.window.Tray
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import androidx.compose.ui.window.rememberNotification
import androidx.compose.ui.window.rememberTrayState
fun main() = application {
var count by remember { mutableStateOf(0) }
var isOpen by remember { mutableStateOf(true) }
if (isOpen) {
val trayState = rememberTrayState()
val notification = rememberNotification("Notification", "Message from MyApp!")
Tray(
state = trayState,
icon = TrayIcon,
menu = {
Item(
"Increment value",
onClick = {
count++
}
)
Item(
"Send notification",
onClick = {
trayState.sendNotification(notification)
}
)
Item(
"Exit",
onClick = {
isOpen = false
}
)
}
)
Window(
onCloseRequest = {
isOpen = false
},
icon = MyAppIcon
) {
// content
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(text = "Value: $count")
}
}
}
}
object MyAppIcon : Painter() {
override val intrinsicSize = Size(256f, 256f)
override fun DrawScope.onDraw() {
drawOval(Color.Green, Offset(size.width / 4, 0f), Size(size.width / 2f, size.height))
drawOval(Color.Blue, Offset(0f, size.height / 4), Size(size.width, size.height / 2f))
drawOval(Color.Red, Offset(size.width / 4, size.height / 4), Size(size.width / 2f, size.height / 2f))
}
}
object TrayIcon : Painter() {
override val intrinsicSize = Size(256f, 256f)
override fun DrawScope.onDraw() {
drawOval(Color(0xFFFFA500))
}
}
Code language: Kotlin (kotlin)
What’s more unusual is that in the very first gif in this post, the OSX version seems to have a large yellow icon in the System Tray, which doesn’t appear to be there on Linux.
This confused the hell out of me.
And on my running Linux desktop:
A bit confusing there. For reference the icons shown are the JetBrains toolbox, then the 3 standard Ubuntu icons for network, sound, and power.
No yellow ball, though.
Balls To It
After killing the app, trying some other demos, and then coming back to this one, it turns out that the system tray icon is actually there.
It’s just very hard to see:
If you mouse over it, it is there!
Magic.
And these menu items work as expected.
A couple of clicks on ‘Increment value’:
But we don’t quite get the nice OSX experience for the notification:
Yikes.
Not only does it not show up in the main Ubuntu Notifications area, but it … looks ugly as all heck.
Though it does work. So that’s definitely a plus.
Honestly, I’m not sure where to go with this now. I got it working. It’s not what I expected, and it’s running an older version.
Small victories?