Split TrayApp into a separate composenativetray-app module (#418)#419
Split TrayApp into a separate composenativetray-app module (#418)#419kdroidFilter wants to merge 5 commits into
Conversation
Move TrayApp and its state/dismiss-mode/animation helpers into a new :tray-app module, published as composenativetray-app, so apps that only need a basic tray icon no longer pull in the decorated-window-tao windowing backend. - Core (composenativetray) keeps the Tray/menu API and native tray impl unchanged, and drops its direct decorated-window-tao dependency. - TrayScreenGeometry is now windowing-agnostic (injectable scale/work-area providers with fallbacks); tray-app installs the Tao-backed geometry. - NativeTray and TrayScreenGeometry are now public (shared engine used by both modules); the public Tray / TrayApp API is unchanged. - Bump Nucleus to 2.0.5. - README: document the two artifacts and correct the snippets to the current nucleusApplication / SingleInstanceRestoreEffect API.
… screen fallback Addresses the code-review findings on the module split. - Move TrayScreenGeometry + all popup positioning (getTrayWindowPosition / getTrayWindowPositionForInstance, corner detection, persistence) into :tray-app, where the Tao backend is always present. The core artifact no longer fabricates a 1920x1080 screen when no windowing backend is available: the native tray managers just record the raw tray-icon click, and tray-app resolves the corner + window position against real Tao geometry. macOS native status-item queries are reached through typed MacTrayInitializer wrappers so the JNI bridge stays internal. - Move TrayApp/TrayAppState/TrayWindowDismissMode and friends to package dev.nucleusframework.composenativetray.trayapp so no package is split across the two published JARs (unblocks JPMS module-path consumers). - tray-app: declare nucleus.application as api (it is TrayApp's receiver type). - Align the tray-app log timestamp format with the core formatter. - README: document the trayapp package and that getTrayWindowPosition lives in composenativetray-app; getTrayPosition stays in core. Consequence: getTrayPosition() on Linux, when used without composenativetray-app, returns the desktop-environment default corner instead of a click-derived one.
getTrayPosition() and the TrayPosition enum now live in composenativetray-app (package ...trayapp), next to the popup positioning they feed, so the core artifact exposes no tray-corner / positioning API at all. The native tray-region queries are reached through typed wrappers (WindowsTrayInitializer.notificationIconsRegion, MacTrayInitializer.statusItemRegion) so the JNI bridges stay internal to core. Core's TrayPosition.kt is reduced to the raw click tracker (TrayClickTracker + TrayClickPoint) and renamed TrayClickTracker.kt. Demos and README updated for the new package. This also removes the Linux core-only degradation: getTrayPosition now always has Tao geometry available in tray-app.
| @@ -50,7 +50,7 @@ kotlin { | |||
| implementation(libs.nucleus.core.runtime) | |||
| implementation(libs.nucleus.darkmode.detector) | |||
| api(libs.nucleus.application) | |||
There was a problem hiding this comment.
can it be replaced with the core-runtime instead?
There was a problem hiding this comment.
NucleusApplicationScope — the receiver of every Tray(...) overload — is defined in nucleus-application, so we can't type against core-runtime without a breaking API change. The dependency is also effectively free: you can't call Tray without already being inside nucleusApplication {}. The heavy part from #418 (decorated-window-tao) is not pulled transitively — it's compileOnly in nucleus-application and now lives only in composenativetray-app.
There was a problem hiding this comment.
What happens for applications that are not using "NucleusApplication"?
Previously, this was based on the Compose application scope, which made it available to any Compose application. From what I can see, "NucleusApplication" does not provide any additional behavior here (the receiver is unused). Unlike the Compose default tray implementation, which requires access to "awt.Window", the creation logic in this library is already handled on the native side and does not require this receiver.
Using the Nucleus application scope seems to only introduce the dependency itself. This is not a small dependency either, as it pulls in a large part of the Nucleus framework (window decoration, styles, graalvm and other related components), even for applications that do not use those features.
Would it be possible to keep this independent from "NucleusApplication"? Otherwise, applications that do not use Nucleus would need to add a dependency that they cannot actually use.
There was a problem hiding this comment.
The old implementation of TrayApp was based on a massive amount of AWT hacks—code that was highly burdensome to maintain—and despite that, there were still bugs that seemed impossible to resolve. The reason I created my own backend is that I could no longer stand all those AWT hacks I had to deal with on a daily basis. Today's TrayApp implementation has been entirely rewritten and can only function with the Tao backend from Nucleus. I am not forcing the use of my backend, but indeed, others of my libraries—like Compose Media Player—will eventually only work with my backend because it allows me to reduce code complexity by a factor of 10.
I prefer maintaining my own backend with a clean, modern implementation rather than tons of hacks, because AWT is an aging, low-performance framework that hasn't been updated in at least 15 years. The core, however, will continue to function, which is all that is expected from a tray library. TrayApp is a very specific implementation meant solely for creating JetBrains Toolbox-style applications.
There was a problem hiding this comment.
Thanks for the explanation. Just to make sure I understand correctly:
Does this mean the basic "@composable Tray" (not the TrayApp) in v2.0.0 implementation actually depends on the Tao backend at runtime? In other words, if "NucleusApplicationScope" (or the Tao backend) is not present, the basic "Tray" won't work and will fail at runtime. Is that why the "NucleusApplicationScope" receiver remains in the core module, even though it isn't used directly?
My original assumption was that "NucleusApplication" was just an unused receiver, so I was wondering whether it was really necessary to pull a dependency for an unused receiver.
There was a problem hiding this comment.
Oh right, my bad, Yes, I haven't finished this PR yet, but once it's merged, even the simple tray won't use NucleusApplicationReceiver anymore and can be used without Nucleus."
There was a problem hiding this comment.
Happy to hear that. looking forward to the new version🔥
…sent Align :tray-app with 9194bd0 (master): the unconditional signAllPublications() predates the merge and broke publishToMavenLocal without a signatory.
Fixes #418.
Splits the single
composenativetrayartifact so apps that only need a basic system tray icon no longer pull in thedecorated-window-taowindowing backend. Public API and behaviour are unchanged.What changed
:tray-appmodule (dev.nucleusframework:composenativetray-app) holdsTrayAppand itsTrayAppState/TrayWindowDismissMode/ animation helper. It owns thenucleus.decorated-window-taodependency.composenativetraykeeps theTray/ menu DSL and native tray implementation unchanged, and drops its directdecorated-window-taodependency (nucleus.applicationstays — it backs theNucleusApplicationScope.Trayreceiver).TrayScreenGeometryis now windowing-agnostic: it exposes injectable scale / work-area providers (with graceful fallbacks) instead of calling Tao directly.tray-appinstalls the Tao-backed providers at composition, so behaviour withTrayAppis identical.NativeTrayandTrayScreenGeometryare nowpublic(shared engine used by both modules). No change to theTray/TrayApppublic API.2.0.3→2.0.5.nucleusApplication/SingleInstanceRestoreEffectAPI.Consumer impact
composenativetraycoordinate, now lighter.TrayAppusers: add one dependency —implementation("dev.nucleusframework:composenativetray-app:<version>").Verification
composenativetray,:tray-appand:demoall compile; ktlint + detekt + core test pass../gradlew :dependenciesconfirmsdecorated-window-taois absent from the core runtime classpath and present only in:tray-app;nucleus.applicationdoes not pull it transitively.composenativetray-app→ core +nucleus.application+decorated-window-tao.TrayAppDemoend-to-end on macOS: tray created via the cross-moduleNativeTray, popup positioned from the injected Tao geometry (real screen bounds), shown/dismissed, clean exit.publishAndReleaseToMavenCentral) publishes both modules with no change, since the unqualified task runs in the root and every subproject that registers it.