Skip to content

Commit 7a13d97

Browse files
committed
Add onKeyEvent and onPreviewKeyEvent parameters to TrayApp for custom key event handling
- Introduced `onKeyEvent` and `onPreviewKeyEvent` parameters to `TrayApp` and related methods, allowing more customizable key event handling. - Updated `DialogWindow` and platform-specific implementations to support the new key event callbacks. - Documented the new parameters with usage examples for enhanced flexibility.
1 parent c34300e commit 7a13d97

1 file changed

Lines changed: 98 additions & 17 deletions

File tree

  • src/commonMain/kotlin/com/kdroid/composetray/tray/api

src/commonMain/kotlin/com/kdroid/composetray/tray/api/TrayApp.kt

Lines changed: 98 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import androidx.compose.ui.graphics.Color
1616
import androidx.compose.ui.graphics.ColorFilter
1717
import androidx.compose.ui.graphics.painter.Painter
1818
import androidx.compose.ui.graphics.vector.ImageVector
19+
import androidx.compose.ui.input.key.KeyEvent
1920
import androidx.compose.ui.unit.DpSize
2021
import androidx.compose.ui.unit.dp
2122
import androidx.compose.ui.window.ApplicationScope
@@ -43,24 +44,29 @@ import java.awt.EventQueue.invokeLater
4344
import java.awt.event.WindowEvent
4445
import 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
6672
fun 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
111140
fun 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
148203
fun 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
203265
fun 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
237302
fun 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

Comments
 (0)