Skip to content

Commit f810e5e

Browse files
authored
Merge pull request #305 from kdroidFilter/tray-app-improvement
Add TrayAppState for programmatic control of TrayApp
2 parents 0f1739d + ef0aef6 commit f810e5e

File tree

5 files changed

+537
-155
lines changed

5 files changed

+537
-155
lines changed

README.md

Lines changed: 192 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
- Improves the appearance of the tray on Linux, which previously resembled Windows 95.
2828
- Adds support for checkable items, dividers, and submenus, including nested submenus.
2929
- Supports primary action for Windows, macOS, and Linux.
30-
- On Windows and macOS, the primary action is triggered by a left-click on the tray icon.
31-
- On Linux, on GNOME the primary action is triggered by a double left-click on the tray icon, while on the majority of other environments, primarily KDE Plasma, it is triggered by a single left-click, similar to Windows and macOS.
30+
- On Windows and macOS, the primary action is triggered by a left-click on the tray icon.
31+
- On Linux, on GNOME the primary action is triggered by a double left-click on the tray icon, while on the majority of other environments, primarily KDE Plasma, it is triggered by a single left-click, similar to Windows and macOS.
3232
- **Single Instance Management**: Ensures that only one instance of the application can run at a time and allows restoring focus to the running instance when another instance is attempted.
3333
- **Tray Position Detection**: Allows determining the position of the system tray, which helps in positioning related windows appropriately.
3434
- **Compose Recomposition Support**: The tray supports Compose recomposition, making it possible to dynamically show or hide the tray icon, for example:
@@ -45,24 +45,25 @@
4545
- [⚡ Installation](#-installation)
4646
- [🚀 Quick Start](#-quick-start)
4747
- [📚 Usage Guide](#-usage-guide)
48-
- [🎨 Creating the System Tray Icon](#-creating-the-system-tray-icon)
49-
- [🖱️ Primary Action](#️-primary-action)
50-
- [📋 Building the Menu](#-building-the-menu)
51-
- [Icons with painterResource](#icons-with-painterresource)
52-
- [New: Icons with DrawableResource](#new-icons-with-drawableresource-in-menu-items)
48+
- [🎨 Creating the System Tray Icon](#-creating-the-system-tray-icon)
49+
- [🖱️ Primary Action](#️-primary-action)
50+
- [📋 Building the Menu](#-building-the-menu)
51+
- [Icons with painterResource](#icons-with-painterresource)
52+
- [New: Icons with DrawableResource](#new-icons-with-drawableresource-in-menu-items)
5353
- [🔧 Advanced Features](#-advanced-features)
54-
- [🔄 Fully Reactive System Menu](#-fully-reactive-system-menu)
55-
- [🔒 Single Instance Management](#-single-instance-management)
56-
- [📍 Position Detection](#-position-detection)
57-
- [🌓 Dark Mode Detection](#-dark-mode-detection)
58-
- [🎨 Icon Rendering Customization](#-icon-rendering-customization)
54+
- [🔄 Fully Reactive System Menu](#-fully-reactive-system-menu)
55+
- [🔑 Single Instance Management](#-single-instance-management)
56+
- [📍 Position Detection](#-position-detection)
57+
- [🌓 Dark Mode Detection](#-dark-mode-detection)
58+
- [🎨 Icon Rendering Customization](#-icon-rendering-customization)
5959
- [⚠️ Platform-Specific Notes](#️-platform-specific-notes)
60-
- [Icon Limitations](#icon-limitations)
61-
- [Theme Behavior](#theme-behavior)
60+
- [Icon Limitations](#icon-limitations)
61+
- [Theme Behavior](#theme-behavior)
6262
- [🧪 TrayApp (Experimental)](#-trayapp-experimental)
6363
- [Overview](#overview)
64-
- [Parameters](#parameters)
65-
- [Examples](#examples)
64+
- [Basic Usage](#basic-usage)
65+
- [TrayAppState API](#trayappstate-api)
66+
- [Advanced Examples](#advanced-examples)
6667
- [📄 License](#-license)
6768
- [🤝 Contribution](#-contribution)
6869
- [👨‍💻 Author](#-author)
@@ -126,9 +127,9 @@ application {
126127
> **💡 Recommendation**: It is highly recommended to check out the demo examples in the project's `demo` directory. These examples showcase various implementation patterns and features that will help you better understand how to use the library effectively.
127128
>
128129
> Notable demos:
129-
> - DemoWithDrawableResources.kt shows using DrawableResource directly for Tray and menu icons
130-
> - PainterResourceWorkaroundDemo.kt demonstrates the painterResource variable workaround
131-
> - DemoWithoutContextMenu.kt minimalist tray with primary action only
130+
> - DemoWithDrawableResources.kt shows using DrawableResource directly for Tray and menu icons
131+
> - PainterResourceWorkaroundDemo.kt demonstrates the painterResource variable workaround
132+
> - DemoWithoutContextMenu.kt minimalist tray with primary action only
132133
133134
## 📚 Usage Guide
134135

@@ -403,7 +404,7 @@ application {
403404

404405
All menu properties (icon, labels, states, item visibility) are reactive and update automatically when application states change, without requiring manual recreation of the menu.
405406

406-
### 🔒 Single Instance Management
407+
### 🔑 Single Instance Management
407408

408409
Prevent multiple instances of your application:
409410

@@ -565,41 +566,197 @@ By default, icons are optimized by OS: 32x32px (Windows), 44x44px (macOS), 24x24
565566
### Overview
566567
TrayApp is a high-level API that creates a system tray icon and an undecorated popup window that toggles when the tray icon is clicked. The popup auto-hides when it loses focus or when you click outside it (macOS/Linux watchers supported) and can fade in/out.
567568

568-
Use TrayApp when you want a compact companion window (like a quick settings or mini dashboard) anchored to the system tray, in addition to or instead of your main window ideal for building apps in the style of JetBrains Toolbox.
569+
Use TrayApp when you want a compact companion window (like a quick settings or mini dashboard) anchored to the system tray, in addition to or instead of your main window ideal for building apps in the style of JetBrains Toolbox.
569570

570-
### Parameters
571-
- icon / windowsIcon / macLinuxIcon / iconContent: the tray icon source.
572-
- tint: optional tint (macOS/Linux ImageVector convenience).
573-
- tooltip: text shown on hover.
574-
- windowSize: popup size (default 300x200dp).
575-
- visibleOnStart: if true, shows the popup shortly after startup with OS-specific handling. On Linux, this is not recommended because there is no system API to retrieve the tray position; the library records the position from the first user click, so on the first launch the popup position will be approximate. After that, the position is saved and persists even after a cold boot.
576-
- menu: optional tray context menu (see Tray menu DSL).
577-
- content: the composable content of the popup window.
578-
579-
### Example
571+
### Basic Usage
580572

581573
```kotlin
582574
@OptIn(ExperimentalTrayAppApi::class)
583575
application {
576+
// Create TrayAppState to control the popup
577+
val trayAppState = rememberTrayAppState(
578+
initialWindowSize = DpSize(300.dp, 500.dp),
579+
initiallyVisible = true // Show on startup
580+
)
581+
584582
TrayApp(
583+
state = trayAppState, // Required: pass the state
585584
icon = Icons.Default.Book,
586585
tooltip = "My App",
587-
windowSize = DpSize(300.dp, 500.dp),
588-
visibleOnStart = true,
589586
menu = {
590587
Item("Open") { /* ... */ }
591588
Divider()
592589
Item("Quit") { exitApplication() }
593590
}
594591
) {
595592
// Popup content
596-
MaterialTheme { /* ... */ }
593+
MaterialTheme {
594+
Text("Quick Settings Panel")
595+
}
596+
}
597+
}
598+
```
599+
600+
### TrayAppState API
601+
602+
TrayAppState provides comprehensive control over the popup window:
603+
604+
#### Creating State
605+
```kotlin
606+
val trayAppState = rememberTrayAppState(
607+
initialWindowSize = DpSize(300.dp, 400.dp),
608+
initiallyVisible = false // Hidden by default
609+
)
610+
```
611+
612+
#### Controlling Visibility
613+
```kotlin
614+
// Show the popup
615+
trayAppState.show()
616+
617+
// Hide the popup
618+
trayAppState.hide()
619+
620+
// Toggle visibility
621+
trayAppState.toggle()
622+
```
623+
624+
#### Observing State
625+
```kotlin
626+
// Observe visibility as State
627+
val isVisible by trayAppState.isVisible.collectAsState()
628+
629+
// Observe window size
630+
val windowSize by trayAppState.windowSize.collectAsState()
631+
632+
// Callback for visibility changes
633+
LaunchedEffect(trayAppState) {
634+
trayAppState.onVisibilityChanged { visible ->
635+
println("Popup is now ${if (visible) "visible" else "hidden"}")
636+
}
637+
}
638+
```
639+
640+
#### Dynamic Window Resizing
641+
```kotlin
642+
// Change size programmatically
643+
trayAppState.setWindowSize(400.dp, 600.dp)
644+
645+
// Or using DpSize
646+
trayAppState.setWindowSize(DpSize(350.dp, 500.dp))
647+
```
648+
649+
### Advanced Examples
650+
651+
#### Example 1: Control from Main Window
652+
```kotlin
653+
@OptIn(ExperimentalTrayAppApi::class)
654+
application {
655+
val trayAppState = rememberTrayAppState()
656+
var isMainWindowVisible by remember { mutableStateOf(true) }
657+
658+
// Tray with popup
659+
TrayApp(
660+
state = trayAppState,
661+
icon = Icons.Default.Settings,
662+
tooltip = "Quick Settings"
663+
) {
664+
// Popup content
665+
Column {
666+
Text("Quick Settings")
667+
Button(onClick = {
668+
isMainWindowVisible = true
669+
trayAppState.hide()
670+
}) {
671+
Text("Open Main Window")
672+
}
673+
}
674+
}
675+
676+
// Main window can control the popup
677+
if (isMainWindowVisible) {
678+
Window(onCloseRequest = { isMainWindowVisible = false }) {
679+
Column {
680+
Button(onClick = { trayAppState.show() }) {
681+
Text("Show Quick Settings")
682+
}
683+
684+
Button(onClick = {
685+
trayAppState.setWindowSize(250.dp, 350.dp)
686+
}) {
687+
Text("Make Popup Smaller")
688+
}
689+
}
690+
}
597691
}
598692
}
599693
```
600694

601-
See full demo: demo/src/jvmMain/kotlin/com/kdroid/composetray/demo/TrayAppDemo.kt
695+
#### Example 2: Reactive UI Based on State
696+
```kotlin
697+
@OptIn(ExperimentalTrayAppApi::class)
698+
TrayApp(
699+
state = trayAppState,
700+
icon = Icons.Default.Dashboard,
701+
tooltip = "Dashboard",
702+
menu = {
703+
val isVisible by trayAppState.isVisible.collectAsState()
704+
705+
Item(
706+
label = if (isVisible) "Hide Dashboard" else "Show Dashboard",
707+
icon = if (isVisible) Icons.Default.VisibilityOff else Icons.Default.Visibility
708+
) {
709+
trayAppState.toggle()
710+
}
711+
712+
SubMenu("Window Size") {
713+
Item("Small (250x350)") {
714+
trayAppState.setWindowSize(250.dp, 350.dp)
715+
}
716+
Item("Medium (350x500)") {
717+
trayAppState.setWindowSize(350.dp, 500.dp)
718+
}
719+
Item("Large (450x600)") {
720+
trayAppState.setWindowSize(450.dp, 600.dp)
721+
}
722+
}
723+
}
724+
) {
725+
// Popup content
726+
val windowSize by trayAppState.windowSize.collectAsState()
727+
Text("Window size: ${windowSize.width} x ${windowSize.height}")
728+
}
729+
```
602730

731+
#### Example 3: Integration with Application State
732+
```kotlin
733+
@OptIn(ExperimentalTrayAppApi::class)
734+
application {
735+
val trayAppState = rememberTrayAppState()
736+
val appViewModel = remember { AppViewModel() }
737+
738+
// React to app events
739+
LaunchedEffect(appViewModel.hasNotification) {
740+
if (appViewModel.hasNotification) {
741+
trayAppState.show() // Show popup when notification arrives
742+
}
743+
}
744+
745+
TrayApp(
746+
state = trayAppState,
747+
icon = Icons.Default.Notifications,
748+
tooltip = "Notifications"
749+
) {
750+
NotificationPanel(
751+
notifications = appViewModel.notifications,
752+
onClear = {
753+
appViewModel.clearNotifications()
754+
trayAppState.hide()
755+
}
756+
)
757+
}
758+
}
759+
```
603760
## 📄 License
604761

605762
This library is licensed under the MIT License. The Linux module uses Apache 2.0

0 commit comments

Comments
 (0)