@@ -16,6 +16,7 @@ import androidx.compose.ui.graphics.Color
1616import androidx.compose.ui.graphics.ColorFilter
1717import androidx.compose.ui.graphics.painter.Painter
1818import androidx.compose.ui.graphics.vector.ImageVector
19+ import androidx.compose.ui.input.key.KeyEvent
1920import androidx.compose.ui.unit.DpSize
2021import androidx.compose.ui.unit.dp
2122import androidx.compose.ui.window.ApplicationScope
@@ -43,24 +44,29 @@ import java.awt.EventQueue.invokeLater
4344import java.awt.event.WindowEvent
4445import java.awt.event.WindowFocusListener
4546
47+
4648/* *
47- * TrayApp – state-preserving tray popup with platform-tuned anchoring.
48- *
49- * Linux and macOS behave differently on initial placement:
50- * - macOS/Windows: the original logic (delayed first frame + polling until a non-default
51- * anchor) works reliably with NSStatusItem and Win tray.
52- * - Linux (GNOME/KDE/etc.): the newer approach (pre-seed DialogState.position, re-apply
53- * right before show, and re-anchor while visible) is more robust against KWin/GNOME races.
49+ * Creates a tray-based desktop application with support for customizable icons, tooltips, menus,
50+ * and composable content. The application integrates with the system's tray or menu bar and
51+ * supports optional window functionality.
5452 *
55- * This file keeps one public API but internally routes to platform-specific implementations:
56- * - Linux -> ImplLinux (new logic)
57- * - mac/Win -> ImplOriginal (previous logic)
58- *
59- * All code comments are in English by user preference.
53+ * @param icon The primary icon to display in the system tray or menu bar.
54+ * @param tint Optional color tint for the icon. Defaults to null, using the appropriate dark or light mode color.
55+ * @param iconRenderProperties Properties for rendering the icon, with defaults based on the operating system.
56+ * @param tooltip Text to be displayed as a tooltip when hovering over the tray icon.
57+ * @param state Optional state for managing the tray application's properties or lifecycle.
58+ * @param windowSize Optional initial size of the window, if a window is displayed.
59+ * @param visibleOnStart Whether the application's window should be visible on startup. Defaults to false.
60+ * @param fadeDurationMs Duration in milliseconds for fade animations. Defaults to 200ms.
61+ * @param animationSpec Animation specification for fading effects. Defaults to a smooth easing with the specified duration.
62+ * @param transparent Whether the application's window should be transparent. Defaults to true.
63+ * @param windowsTitle Title of the window for Windows operating systems. Defaults to an empty string.
64+ * @param windowIcon Optional icon to be used for the application's window.
65+ * @param onPreviewKeyEvent Handler for previewing key events before they are processed. Returns `true` if the event is consumed.
66+ * @param onKeyEvent Handler for processing key events. Returns `true` if the event is consumed.
67+ * @param menu Optional builder block for defining the application's tray menu. Defaults to null if no menu is needed.
68+ * @param content Composable content to be displayed within the application's window.
6069 */
61-
62- // --------------------- Overloads (public API kept stable) ---------------------
63-
6470@ExperimentalTrayAppApi
6571@Composable
6672fun ApplicationScope.TrayApp (
@@ -76,6 +82,8 @@ fun ApplicationScope.TrayApp(
7682 transparent : Boolean = true,
7783 windowsTitle : String = "",
7884 windowIcon : Painter ? = null,
85+ onPreviewKeyEvent : (KeyEvent ) -> Boolean = { false },
86+ onKeyEvent : (KeyEvent ) -> Boolean = { false },
7987 menu : (TrayMenuBuilder .() -> Unit )? = null,
8088 content : @Composable () -> Unit ,
8189) {
@@ -101,11 +109,32 @@ fun ApplicationScope.TrayApp(
101109 transparent = transparent,
102110 windowsTitle = windowsTitle,
103111 windowIcon = windowIcon,
112+ onPreviewKeyEvent = onPreviewKeyEvent,
113+ onKeyEvent = onKeyEvent,
104114 menu = menu,
105115 content = content,
106116 )
107117}
108118
119+ /* *
120+ * Creates a system tray application with the provided configuration.
121+ *
122+ * @param icon The icon displayed in the system tray for this application.
123+ * @param iconRenderProperties The properties defining how the icon is rendered. Default is based on the current operating system.
124+ * @param tooltip The tooltip text displayed when hovering over the tray icon.
125+ * @param state The state of the tray app, which can be used to manage its visibility and behavior. Defaults to `null`.
126+ * @param windowSize The size of the application window when displayed. Defaults to `null`.
127+ * @param visibleOnStart Determines whether the application window is visible when the app starts. Defaults to `false`.
128+ * @param fadeDurationMs The duration of the fade animation (in milliseconds) when showing or hiding the window. Defaults to `200`.
129+ * @param animationSpec The animation specification used for window fade transitions. Defaults to an easing `tween` animation.
130+ * @param transparent Indicates if the application window background should be transparent. Defaults to `true`.
131+ * @param windowsTitle The title of the window displayed in the task manager or window list on Windows systems. Defaults to an empty string.
132+ * @param windowIcon The icon displayed for the application window (if any). Can be `null`.
133+ * @param onPreviewKeyEvent A callback invoked before key events are dispatched. It can intercept and handle key events. Defaults to returning `false`.
134+ * @param onKeyEvent A callback invoked to handle key events. Defaults to returning `false`.
135+ * @param menu An optional builder block to define the tray menu items.
136+ * @param content The content displayed in the tray application's main window.
137+ */
109138@ExperimentalTrayAppApi
110139@Composable
111140fun ApplicationScope.TrayApp (
@@ -120,6 +149,8 @@ fun ApplicationScope.TrayApp(
120149 transparent : Boolean = true,
121150 windowsTitle : String = "",
122151 windowIcon : Painter ? = null,
152+ onPreviewKeyEvent : (KeyEvent ) -> Boolean = { false },
153+ onKeyEvent : (KeyEvent ) -> Boolean = { false },
123154 menu : (TrayMenuBuilder .() -> Unit )? = null,
124155 content : @Composable () -> Unit ,
125156) {
@@ -138,11 +169,35 @@ fun ApplicationScope.TrayApp(
138169 transparent = transparent,
139170 windowsTitle = windowsTitle,
140171 windowIcon = windowIcon,
172+ onPreviewKeyEvent = onPreviewKeyEvent,
173+ onKeyEvent = onKeyEvent,
141174 menu = menu,
142175 content = content,
143176 )
144177}
145178
179+ /* *
180+ * Composable function for displaying a system tray application with a GUI window.
181+ * This function differs behavior based on the user's operating system.
182+ *
183+ * @param windowsIcon Icon displayed in the system tray on Windows systems.
184+ * @param macLinuxIcon Icon displayed in the system tray on macOS and Linux systems.
185+ * @param tint Optional tint applied to the macOS and Linux tray icon.
186+ * @param iconRenderProperties Properties determining how the icon should be rendered.
187+ * @param tooltip Text displayed as a tooltip when hovering over the tray icon.
188+ * @param state Optional state for managing the tray application window (visibility, etc.).
189+ * @param windowSize Desired size of the window when opened, if applicable.
190+ * @param visibleOnStart Whether the window should be visible immediately after starting the app.
191+ * @param fadeDurationMs Duration of the fade-in and fade-out animations for showing/hiding the window, in milliseconds.
192+ * @param animationSpec Animation specification for fade effects.
193+ * @param transparent Whether the window's background should be transparent.
194+ * @param windowsTitle Title of the GUI window on Windows.
195+ * @param windowIcon Icon displayed in the top-left corner of the window on Windows.
196+ * @param onPreviewKeyEvent Lambda for handling preview key events invoked before onKeyEvent. Defaults to ignoring key events.
197+ * @param onKeyEvent Lambda for handling key events when the window has focus. Defaults to ignoring key events.
198+ * @param menu Optional lambda for building the context menu attached to the tray icon.
199+ * @param content Composable content displayed within the GUI window.
200+ */
146201@ExperimentalTrayAppApi
147202@Composable
148203fun ApplicationScope.TrayApp (
@@ -159,6 +214,8 @@ fun ApplicationScope.TrayApp(
159214 transparent : Boolean = true,
160215 windowsTitle : String = "",
161216 windowIcon : Painter ? = null,
217+ onPreviewKeyEvent : (KeyEvent ) -> Boolean = { false },
218+ onKeyEvent : (KeyEvent ) -> Boolean = { false },
162219 menu : (TrayMenuBuilder .() -> Unit )? = null,
163220 content : @Composable () -> Unit ,
164221) {
@@ -175,6 +232,8 @@ fun ApplicationScope.TrayApp(
175232 transparent = transparent,
176233 windowsTitle = windowsTitle,
177234 windowIcon = windowIcon,
235+ onPreviewKeyEvent = onPreviewKeyEvent,
236+ onKeyEvent = onKeyEvent,
178237 menu = menu,
179238 content = content,
180239 )
@@ -183,6 +242,8 @@ fun ApplicationScope.TrayApp(
183242 icon = macLinuxIcon,
184243 tint = tint,
185244 iconRenderProperties = iconRenderProperties,
245+ onPreviewKeyEvent = onPreviewKeyEvent,
246+ onKeyEvent = onKeyEvent,
186247 tooltip = tooltip,
187248 state = state,
188249 windowSize = windowSize,
@@ -198,6 +259,7 @@ fun ApplicationScope.TrayApp(
198259 }
199260}
200261
262+
201263@ExperimentalTrayAppApi
202264@Composable
203265fun ApplicationScope.TrayApp (
@@ -212,6 +274,8 @@ fun ApplicationScope.TrayApp(
212274 transparent : Boolean = true,
213275 windowsTitle : String = "",
214276 windowIcon : Painter ? = null,
277+ onPreviewKeyEvent : (KeyEvent ) -> Boolean = { false },
278+ onKeyEvent : (KeyEvent ) -> Boolean = { false },
215279 menu : (TrayMenuBuilder .() -> Unit )? = null,
216280 content : @Composable () -> Unit ,
217281) {
@@ -232,6 +296,7 @@ fun ApplicationScope.TrayApp(
232296 )
233297}
234298
299+
235300@ExperimentalTrayAppApi
236301@Composable
237302fun ApplicationScope.TrayApp (
@@ -248,6 +313,8 @@ fun ApplicationScope.TrayApp(
248313 transparent : Boolean = true,
249314 windowsTitle : String = "",
250315 windowIcon : Painter ? = null,
316+ onPreviewKeyEvent : (KeyEvent ) -> Boolean = { false },
317+ onKeyEvent : (KeyEvent ) -> Boolean = { false },
251318 menu : (TrayMenuBuilder .() -> Unit )? = null,
252319 content : @Composable () -> Unit ,
253320) {
@@ -264,6 +331,8 @@ fun ApplicationScope.TrayApp(
264331 transparent = transparent,
265332 windowsTitle = windowsTitle,
266333 windowIcon = windowIcon,
334+ onPreviewKeyEvent = onPreviewKeyEvent,
335+ onKeyEvent = onKeyEvent,
267336 menu = menu,
268337 content = content,
269338 )
@@ -272,6 +341,8 @@ fun ApplicationScope.TrayApp(
272341 icon = macLinuxIcon,
273342 tint = tint,
274343 iconRenderProperties = iconRenderProperties,
344+ onPreviewKeyEvent = onPreviewKeyEvent,
345+ onKeyEvent = onKeyEvent,
275346 tooltip = tooltip,
276347 state = state,
277348 windowSize = windowSize,
@@ -303,17 +374,19 @@ fun ApplicationScope.TrayApp(
303374 transparent : Boolean = true,
304375 windowsTitle : String = "",
305376 windowIcon : Painter ? = null,
377+ onPreviewKeyEvent : (KeyEvent ) -> Boolean = { false },
378+ onKeyEvent : (KeyEvent ) -> Boolean = { false },
306379 menu : (TrayMenuBuilder .() -> Unit )? = null,
307380 content : @Composable () -> Unit ,
308381) {
309382 when (getOperatingSystem()) {
310383 OperatingSystem .LINUX -> TrayAppImplLinux (
311384 iconContent, iconRenderProperties, tooltip, state, windowSize,
312- visibleOnStart, fadeDurationMs, animationSpec, transparent, windowsTitle, windowIcon, menu, content
385+ visibleOnStart, fadeDurationMs, animationSpec, transparent, windowsTitle, windowIcon, onPreviewKeyEvent, onKeyEvent, menu, content
313386 )
314387 else -> TrayAppImplOriginal (
315388 iconContent, iconRenderProperties, tooltip, state, windowSize,
316- visibleOnStart, fadeDurationMs, animationSpec, transparent, windowsTitle, windowIcon, menu, content
389+ visibleOnStart, fadeDurationMs, animationSpec, transparent, windowsTitle, windowIcon, onPreviewKeyEvent, onKeyEvent, menu, content
317390 )
318391 }
319392}
@@ -333,6 +406,8 @@ private fun ApplicationScope.TrayAppImplOriginal(
333406 transparent : Boolean ,
334407 windowsTitle : String ,
335408 windowIcon : Painter ? ,
409+ onPreviewKeyEvent : (KeyEvent ) -> Boolean ,
410+ onKeyEvent : (KeyEvent ) -> Boolean ,
336411 menu : (TrayMenuBuilder .() -> Unit )? ,
337412 content : @Composable () -> Unit ,
338413) {
@@ -476,6 +551,8 @@ private fun ApplicationScope.TrayAppImplOriginal(
476551 transparent = transparent,
477552 visible = shouldShowWindow,
478553 state = dialogState,
554+ onPreviewKeyEvent = onPreviewKeyEvent,
555+ onKeyEvent = onKeyEvent,
479556 ) {
480557 DisposableEffect (shouldShowWindow, dismissMode) {
481558 if (! shouldShowWindow) return @DisposableEffect onDispose { }
@@ -550,6 +627,8 @@ private fun ApplicationScope.TrayAppImplLinux(
550627 transparent : Boolean ,
551628 windowsTitle : String ,
552629 windowIcon : Painter ? ,
630+ onPreviewKeyEvent : (KeyEvent ) -> Boolean ,
631+ onKeyEvent : (KeyEvent ) -> Boolean ,
553632 menu : (TrayMenuBuilder .() -> Unit )? ,
554633 content : @Composable () -> Unit ,
555634) {
@@ -668,6 +747,8 @@ private fun ApplicationScope.TrayAppImplLinux(
668747 transparent = transparent,
669748 visible = shouldShowWindow,
670749 state = dialogState,
750+ onPreviewKeyEvent = onPreviewKeyEvent,
751+ onKeyEvent = onKeyEvent,
671752 ) {
672753 DisposableEffect (shouldShowWindow, dismissMode) {
673754 if (! shouldShowWindow) return @DisposableEffect onDispose { }
0 commit comments