Compose Multiplatform是 JetBrains 開發的聲明式 UI 框架,可讓您為 Android、iOS、桌面和 Web 開發共享 UI。將 Compose Multiplatform 集成到您的 Kotlin Multiplatform 項目中,即可更快地交付您的應用和功能,而無需維護多個 UI 實現。
在(2025.06.05) Compose Multiplatform 中對于 Desktop 的開發,如果使用了托盤,會發現托盤中的中文竟然是亂碼。為了解決這個問題,只能重新實現一個系統托盤,因此該托盤具備了以下特性。
- 解決中文亂碼
- 更多的Swing 組件可以被放到托盤
- 允許你監聽單擊事件,并獲取單擊位置。方便你繪制類似于 Toolbox 的窗體
- 亂序的菜單項,除非你手動指定菜單順序
package io.github.zimoyin.xianyukefuimport androidx.compose.runtime.*
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.graphics.toAwtImage
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.window.Notification
import androidx.compose.ui.window.TrayState
import androidx.compose.ui.window.rememberTrayState
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.awt.*
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.awt.event.MouseListener
import java.util.*
import javax.swing.*
import javax.swing.border.Border/*** 托盤窗口* 使用 JDialog 作為 JPopupMenu 載體。實現托盤菜單。* 允許在里面設置復雜菜單項,并解決了中文亂碼問題。* 使用方式與 Tray() 接近** @param icon 圖標* @param tooltip 提示* @param state 控制托盤和顯示通知的狀態* @param onClick 菜單被鼠標單擊時觸發,無論是左鍵還是右鍵* @param onAction 菜單被雙擊時觸發* @param onVisible 菜單顯示時觸發* @param onInvisible 菜單隱藏時觸發* @param isSort 是否對菜單進行排序,默認為 false* @param setLookAndFeel 設置Swing 的皮膚。如果使用系統的皮膚請使用 UIManager.getSystemLookAndFeelClassName() 獲取值* @param content 菜單內容*/
@Composable
fun TrayWindow(icon: Painter,tooltip: String? = null,state: TrayState = rememberTrayState(),onClick: (TrayClickEvent) -> Unit = {},isSort: Boolean = false,onAction: () -> Unit = {},onVisible: () -> Unit = {},onInvisible: () -> Unit = {},style: ComponentStyle = ComponentStyle(),setLookAndFeel: String? = null,content: @Composable MenuScope.() -> Unit = {},
) {setLookAndFeel?.let { UIManager.setLookAndFeel(it) }val awtIcon = remember(icon) {icon.toAwtImage(GlobalDensity, GlobalLayoutDirection, iconSize)}val menuWindow = remember { JDialog() }.apply {isUndecorated = true//作為菜單載體不需要存在可以視的窗體setSize(0, 0)}val coroutineScopeR = rememberCoroutineScope()val onClickR by rememberUpdatedState(onClick)val onActionR by rememberUpdatedState(onAction)val contentR by rememberUpdatedState(content)val onVisibleR by rememberUpdatedState(onVisible)val onInvisibleR by rememberUpdatedState(onInvisible)//創建JPopupMenuval menu: JPopupMenu = remember {TrayMenu(onVisible = {menuWindow.isVisible = trueonVisibleR()},onInvisible = {menuWindow.isVisible = falseonInvisibleR()})}.apply {style.setStyle2(this)}val menuScopeR by rememberUpdatedState(MenuScope(menu, isSort = isSort))//重繪菜單menu.removeAll()contentR(menuScopeR)val menuSizeR = calculationMenuSize(menu)val trayIcon = remember {TrayIcon(awtIcon).apply {isImageAutoSize = true//給托盤圖標添加鼠標監聽addMouseListener(object : MouseAdapter() {override fun mouseReleased(e: MouseEvent) {val pointer = MouseInfo.getPointerInfo().locationonClickR(TrayClickEvent(e.x,e.y,pointer.x,pointer.y,ButtonType.createButtonType(e.button),e.isPopupTrigger,e))if (e.button == 3 && e.isPopupTrigger) {openMenu(pointer, menuWindow, menu, menuSizeR)}}})addActionListener {onActionR()}}}.apply {if (toolTip != tooltip) toolTip = tooltip}DisposableEffect(Unit) {// 將托盤圖標添加到系統的托盤實例中SystemTray.getSystemTray().add(trayIcon)state.notificationFlow.onEach(trayIcon::displayMessage).launchIn(coroutineScopeR)onDispose {menuWindow.dispose()SystemTray.getSystemTray().remove(trayIcon)}}
}private fun TrayIcon.displayMessage(notification: Notification) {val messageType = when (notification.type) {Notification.Type.None -> TrayIcon.MessageType.NONENotification.Type.Info -> TrayIcon.MessageType.INFONotification.Type.Warning -> TrayIcon.MessageType.WARNINGNotification.Type.Error -> TrayIcon.MessageType.ERROR}displayMessage(notification.title, notification.message, messageType)
}/*** 彈出菜單* @param menuWindow 菜單綁定的容器* @param menu 菜單*/
private fun openMenu(pointer: Point, menuWindow: JDialog, menu: JPopupMenu, menuSize: Dimension) {val x = pointer.xval y = pointer.y//右鍵點擊彈出JPopupMenu綁定的載體以及JPopupMenumenuWindow.setLocation(x, y)menuWindow.isVisible = truemenu.show(menuWindow, 3, 0 - (menuSize.height + 3))
}/*** 點擊事件*/
data class TrayClickEvent(val x: Int,val y: Int,val mouseX: Int,val mouseY: Int,val buttonType: ButtonType,val isPopupTrigger: Boolean,val awtEvent: MouseEvent,
)/*** 按鈕類型*/
enum class ButtonType {LEFT,RIGHT,UNDEFINED;companion object {fun createButtonType(button: Int): ButtonType = when (button) {1 -> LEFT3 -> RIGHTelse -> UNDEFINED}}
}/*** 計算菜單的尺寸*/
fun calculationMenuSize(menu: JPopupMenu): Dimension {var menuHeight = 0var menuWidth = 0for (component in menu.components) {if (component is JMenuItem && component.isVisible) {val size = component.getPreferredSize()menuHeight += size.heightmenuWidth += size.width}}return Dimension(menuWidth, menuHeight)
}/*** 菜單域,用于添加控件*/
class MenuScope(val menu: JPopupMenu, val menuItem: JMenu? = null, var isSort: Boolean = false) {private fun Painter.toAwtImageIcon(): ImageIcon {return ImageIcon(toAwtImage(GlobalDensity, GlobalLayoutDirection))}companion object {private val orderMap = HashMap<Int, Int>()private val COM = HashMap<Int, HashSet<Order>>()}data class Order(val key: UUID,var order: Int,) {override fun equals(other: Any?): Boolean {if (this === other) return trueif (other !is Order) return falseif (key != other.key) return falsereturn true}override fun hashCode(): Int {return key.hashCode()}}fun getItemCount(): Int {return menuItem?.itemCount ?: menu.componentCount}private fun getOrderKey(): Int {return menuItem?.hashCode() ?: menu.hashCode()}@Composableprivate fun rememberOrder(): Int {if (!isSort) return -1val orderKey = getOrderKey()val key by remember { mutableStateOf(UUID.randomUUID()) }val list = COM.getOrPut(orderKey) {hashSetOf()}var order = list.lastOrNull { it.key == key }if (order == null) {order = Order(key, list.size)if (order.order <= getItemCount()) list.add(order)else order.order -= 1}// println("${if (menuItem != null) "menuItem" else "menu"} : $order itemCount: ${getItemCount()} key: $key")return order.order}private fun removeOrder(order: Int) {if (order == -1) returnval orderKey = getOrderKey()val list = COM[orderKey] ?: returnif (list.isEmpty()) returnlist.removeIf {it.order == order}val result = list.filter {it.order >= order}.map {Order(it.key, it.order - 1)}result.forEach { rus ->list.removeIf {it.key == rus.key}}list.addAll(result)}/*** 通用菜單項** @param text 菜單項文本內容,默認為 null* @param icon 菜單項圖標,默認為 null* @param enabled 是否啟用,默認為 true* @param mnemonic 快捷鍵字符,默認為 null* @param style 組件樣式,默認為 [ComponentStyle]* @param orderIndex 菜單項排序索引,默認為 -1* @param onClick 點擊菜單項時的回調函數*/@Composablefun Item(text: String? = null,icon: Painter? = null,enabled: Boolean = true,mnemonic: Char? = null,style: ComponentStyle = ComponentStyle(),orderIndex: Int = -1,onClick: () -> Unit = {},) {val scope = rememberCoroutineScope()val order = if (orderIndex >= 0) {scope.launch { withContext(Dispatchers.IO) { initCustomSorting() } }if (isSort) orderIndex else -1} else {rememberOrder()}fun createItem() = JMenuItem(text, icon?.toAwtImageIcon()).apply {addActionListener {if (isEnabled) onClick()}if (mnemonic != null) this.accelerator = KeyStroke.getKeyStroke(mnemonic.uppercaseChar())isEnabled = enabledstyle.setStyle(this)
// println("text: $text order: $order sort:$isSort")menuItem?.add(this, order) ?: menu.add(this, order)}var item by remember { mutableStateOf(createItem()) }LaunchedEffect(icon, text, enabled, onClick, style.id(), mnemonic, order) {menuItem?.remove(item) ?: menu.remove(item)item = createItem()}if (menuItem != null) {menuItem.remove(item)menuItem.add(item, order)}DisposableEffect(Unit) {onDispose {menuItem?.remove(item) ?: menu.remove(item)removeOrder(order)}}}/*** 文字標簽** @param text 標簽文本內容* @param enabled 是否啟用,默認為 true* @param mnemonic 快捷鍵字符,默認為 null* @param style 組件樣式,默認為 [ComponentStyle]* @param orderIndex 標簽排序索引,默認為 -1* @param onClick 點擊標簽時的回調函數*/@Composablefun Label(text: String,enabled: Boolean = true,mnemonic: Char? = null,style: ComponentStyle = ComponentStyle(),orderIndex: Int = -1,onClick: () -> Unit = {},) {Item(text, enabled = enabled, mnemonic = mnemonic, style = style, onClick = onClick, orderIndex = orderIndex)}/*** 分割線* @param orderIndex 排序序號,-1表示默認排序*/@Composablefun Separator(orderIndex: Int = -1) {check(menuItem == null) { "Separator only support menu" }val scope = rememberCoroutineScope()val order = if (orderIndex >= 0) {scope.launch { withContext(Dispatchers.IO) { initCustomSorting() } }if (isSort) orderIndex else -1} else {rememberOrder()}val jSeparator = remember {JSeparator(SwingConstants.HORIZONTAL).apply {menu.add(this, order)}}DisposableEffect(Unit) {onDispose {menu.remove(jSeparator)removeOrder(order)}}}/*** 垂直分割線* @param orderIndex 排序序號,-1表示默認排序*/@Composablefun VerticalSeparator(orderIndex: Int = -1) {check(menuItem == null) { "VerticalSeparator only support menu" }val scope = rememberCoroutineScope()val order = if (orderIndex >= 0) {scope.launch { withContext(Dispatchers.IO) { initCustomSorting() } }if (isSort) orderIndex else -1} else {rememberOrder()}val jSeparator = remember {JSeparator(SwingConstants.VERTICAL).apply {menu.add(this, order)removeOrder(order)}}DisposableEffect(Unit) {onDispose {menu.remove(jSeparator)removeOrder(order)}}}/*** 復選框菜單項** @param text 菜單項文本內容,默認為 null* @param icon 菜單項圖標,默認為 null* @param selected 是否選中,默認為 false* @param enabled 是否啟用,默認為 true* @param mnemonic 快捷鍵字符,默認為 null* @param style 組件樣式,默認為 [ComponentStyle]* @param orderIndex 菜單項排序索引,默認為 -1* @param onCheckedChange 復選框狀態變化時的回調函數*/@Composablefun CheckboxItem(text: String? = null,icon: Painter? = null,selected: Boolean = false,enabled: Boolean = true,mnemonic: Char? = null,style: ComponentStyle = ComponentStyle(),orderIndex: Int = -1,onCheckedChange: (Boolean) -> Unit = {},) {val scope = rememberCoroutineScope()val order = if (orderIndex >= 0) {scope.launch { withContext(Dispatchers.IO) { initCustomSorting() } }if (isSort) orderIndex else -1} else {rememberOrder()}fun createItem() = JCheckBoxMenuItem(text, icon?.toAwtImageIcon(), selected).apply {addActionListener {onCheckedChange(isSelected)}if (mnemonic != null) this.accelerator = KeyStroke.getKeyStroke(mnemonic.uppercaseChar())isEnabled = enabledstyle.setStyle(this)menuItem?.add(this, order) ?: menu.add(this, order)}var item by remember { mutableStateOf(createItem()) }LaunchedEffect(icon, text, enabled, selected, style.id(), mnemonic, onCheckedChange, orderIndex) {menuItem?.remove(item) ?: menu.remove(item)item = createItem()}DisposableEffect(Unit) {onDispose {menuItem?.remove(item) ?: menu.remove(item)removeOrder(order)}}}/*** 單選按鈕菜單項** @param text 菜單項文本內容,默認為 null* @param icon 菜單項圖標,默認為 null* @param selected 是否選中,默認為 false* @param enabled 是否啟用,默認為 true* @param style 組件樣式,默認為 [ComponentStyle]* @param orderIndex 菜單項排序索引,默認為 -1* @param onCheckedChange 單選按鈕狀態變化時的回調函數**/@Composablefun RadioButtonItem(text: String? = null,icon: Painter? = null,selected: Boolean = false,enabled: Boolean = true,style: ComponentStyle = ComponentStyle(),orderIndex: Int = -1,onCheckedChange: (Boolean) -> Unit = {},) {val scope = rememberCoroutineScope()val order = if (orderIndex >= 0) {scope.launch { withContext(Dispatchers.IO) { initCustomSorting() } }if (isSort) orderIndex else -1} else {rememberOrder()}fun createItem() = JRadioButton(text, icon?.toAwtImageIcon(), selected).apply {addActionListener {onCheckedChange(isSelected)}isEnabled = enabledstyle.setStyle(this)menuItem?.add(this, order) ?: menu.add(this, order)}var item by remember {mutableStateOf(createItem())}LaunchedEffect(icon, text, enabled, selected, style.id(), onCheckedChange, orderIndex) {menuItem?.remove(item) ?: menu.remove(item)item = createItem()}DisposableEffect(Unit) {onDispose {menuItem?.remove(item) ?: menu.remove(item)removeOrder(order)}}}/*** 子菜單** @param text 子菜單名稱* @param visible 是否可見,默認為 true* @param enabled 是否啟用,默認為 true* @param mnemonic 快捷鍵字符,默認為 null* @param style 組件樣式,默認為 [ComponentStyle]* @param orderIndex 菜單項排序索引,默認為 -1* @param content 菜單內容的組合構建器**/@Composablefun Menu(text: String = "子菜單",visible: Boolean = true,enabled: Boolean = true,mnemonic: Char? = null,style: ComponentStyle = ComponentStyle(),orderIndex: Int = -1,content: @Composable MenuScope.() -> Unit,) {val scope = rememberCoroutineScope()val order = if (orderIndex >= 0) {scope.launch { withContext(Dispatchers.IO) { initCustomSorting() } }if (isSort) orderIndex else -1} else {rememberOrder()}fun createItem() = JMenu(text).apply {isVisible = visibleisEnabled = enabledif (mnemonic != null) this.accelerator = KeyStroke.getKeyStroke(mnemonic.uppercaseChar())style.setStyle(this)menuItem?.add(this, order) ?: menu.add(this, order)}var item by remember {mutableStateOf(createItem())}MenuScope(menu, item, isSort = isSort).apply {content(this)}LaunchedEffect(text, enabled, visible, style.id(), content, mnemonic, orderIndex) {menuItem?.remove(item) ?: menu.remove(item)item = createItem()}DisposableEffect(Unit) {onDispose {menuItem?.remove(item) ?: menu.remove(item)removeOrder(order)}}}@Deprecated("可能存在bug")@Composablefun Component(orderIndex: Int = -1,content: @Composable MenuScope.() -> Component,) {val scope = rememberCoroutineScope()val order = if (orderIndex >= 0) {scope.launch { withContext(Dispatchers.IO) { initCustomSorting() } }if (isSort) orderIndex else -1} else {rememberOrder()}val item by rememberUpdatedState(content())DisposableEffect(order, content) {menuItem?.add(item, order) ?: menu.add(item, order)onDispose {menuItem?.remove(item) ?: menu.remove(item)removeOrder(order)}}}@Deprecated("可能存在bug")@Composablefun Component(orderIndex: Int = -1,component: Component,) {val scope = rememberCoroutineScope()val order = if (orderIndex >= 0) {scope.launch { withContext(Dispatchers.IO) { initCustomSorting() } }if (isSort) orderIndex else -1} else {rememberOrder()}val item = remember { component }menuItem?.add(item, order) ?: menu.add(item, order)DisposableEffect(orderIndex, component) {onDispose {menuItem?.remove(item) ?: menu.remove(item)removeOrder(order)}}}/*** 初始化菜單排序*/private fun initCustomSorting() {if (!isSort) returnif (menu.components.count { !it.isVisible } <= 9) {for (i in 0..10) {menu.add(JMenuItem("Null").apply {isVisible = false})}}if (menuItem != null) {var count = 0var composeCount = 0for (i in 0 until menuItem.itemCount) {if (!menuItem.getItem(i).isVisible) {count++} else {composeCount++}}if (count <= 9) {for (i in 0..10) {menuItem.add(JMenuItem("Null").apply {isVisible = false})}}}}}/*** 菜單主體*/
internal class TrayMenu(val onInvisible: () -> Unit = {},val onVisible: () -> Unit = {},
) : JPopupMenu() {init {setSize(100, 30)}override fun firePopupMenuWillBecomeInvisible() {onInvisible()}override fun firePopupMenuWillBecomeVisible() {super.firePopupMenuWillBecomeVisible()onVisible()}
}/*** 組件樣式*/
data class ComponentStyle(/*** 組件字體*/val font: Font? = null,/*** 組件背景色*/val background: androidx.compose.ui.graphics.Color? = null,/*** 組件文字顏色*/val foreground: androidx.compose.ui.graphics.Color? = null,/*** 組件邊框*/val border: Border? = null,/*** 組件邊距*/val margin: Insets? = null,/*** 組件位置*/val bounds: Rectangle? = null,/*** 組件位置*/val location: Point? = null,/*** 組件大小*/val size: Dimension? = null,
) {private var color: Color? = background?.toAwtColor()/*** 鼠標進入事件*/val onMouseEnter: (MouseEvent) -> Unit = {color = it.component.backgroundit.component.background = color}/*** 鼠標離開事件*/val onMouseExit: (MouseEvent) -> Unit = {it.component.background = color ?: Color.white}/*** 鼠標點擊事件*/val onMouseClick: (MouseEvent) -> Unit = {}/*** 鼠標按下事件*/val onMousePressed: (MouseEvent) -> Unit = {}/*** 鼠標釋放事件*/val onMouseReleased: (MouseEvent) -> Unit = {}/*** 計算組件樣式的唯一標識,注意部分樣式未能計算到*/fun id(): Int {val s = font?.hashCode().toString() +background?.toArgb().toString() +foreground?.toArgb().toString() +margin?.top.toString() + margin?.left?.toString() + margin?.bottom?.toString() + margin?.right?.toString() +bounds?.x?.toString() + bounds?.y.toString() + bounds?.height.toString() + bounds?.width.toString() +location?.x.toString() + location?.y.toString() +size?.height.toString() + size?.width.toString()return s.hashCode()}fun setStyle(component: AbstractButton) {val style = thisif (font != null) component.font = fontif (foreground != null) component.foreground = foreground.toAwtColor()if (background != null) component.background = background.toAwtColor()if (border != null) component.border = borderif (size != null) component.size = this.sizeif (location != null) component.location = this.locationif (margin != null) component.margin = marginif (bounds != null) component.bounds = boundscomponent.addMouseListener(object : MouseListener {override fun mouseClicked(e: MouseEvent) {style.onMouseClick(e)}override fun mousePressed(e: MouseEvent) {style.onMousePressed(e)}override fun mouseReleased(e: MouseEvent) {style.onMouseReleased(e)}override fun mouseEntered(e: MouseEvent) {style.onMouseEnter(e)}override fun mouseExited(e: MouseEvent) {style.onMouseExit(e)}})}fun setStyle2(component: JComponent) {val style = thisif (font != null) component.font = fontif (foreground != null) component.foreground = foreground.toAwtColor()if (background != null) component.background = background.toAwtColor()if (border != null) component.border = borderif (size != null) component.size = this.sizeif (location != null) component.location = this.locationif (bounds != null) component.bounds = boundscomponent.addMouseListener(object : MouseListener {override fun mouseClicked(e: MouseEvent) {style.onMouseClick(e)}override fun mousePressed(e: MouseEvent) {style.onMousePressed(e)}override fun mouseReleased(e: MouseEvent) {style.onMouseReleased(e)}override fun mouseEntered(e: MouseEvent) {style.onMouseEnter(e)}override fun mouseExited(e: MouseEvent) {style.onMouseExit(e)}})}
}// 輔助函數
// 來自于 Compose 內部的函數,不確定是否會引發問題
internal val GlobalDensityget() = GraphicsEnvironment.getLocalGraphicsEnvironment().defaultScreenDevice.defaultConfiguration.density
private val GraphicsConfiguration.density: Densityget() = Density(defaultTransform.scaleX.toFloat(),fontScale = 1f)internal val GlobalLayoutDirection get() = Locale.getDefault().layoutDirection
internal val Locale.layoutDirection: LayoutDirectionget() = ComponentOrientation.getOrientation(this).layoutDirection
internal val ComponentOrientation.layoutDirection: LayoutDirectionget() = when {isLeftToRight -> LayoutDirection.LtrisHorizontal -> LayoutDirection.Rtlelse -> LayoutDirection.Ltr}internal val iconSize = when (DesktopPlatform.Current) {// https://doc.qt.io/qt-5/qtwidgets-desktop-systray-example.html (search 22x22)DesktopPlatform.Linux -> Size(22f, 22f)// https://doc.qt.io/qt-5/qtwidgets-desktop-systray-example.html (search 16x16)DesktopPlatform.Windows -> Size(16f, 16f)// https://medium.com/@acwrightdesign/creating-a-macos-menu-bar-application-using-swiftui-54572a5d5f87DesktopPlatform.MacOS -> Size(22f, 22f)DesktopPlatform.Unknown -> Size(32f, 32f)
}enum class DesktopPlatform {Linux,Windows,MacOS,Unknown;companion object {/*** Identify OS on which the application is currently running.*/val Current: DesktopPlatform by lazy {val name = System.getProperty("os.name")when {name?.startsWith("Linux") == true -> Linuxname?.startsWith("Win") == true -> Windowsname == "Mac OS X" -> MacOSelse -> Unknown}}}
}private fun androidx.compose.ui.graphics.Color.toAwtColor(): Color = Color(this.red, this.green, this.blue, this.alpha)
使用示例
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Button
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.WindowState
import androidx.compose.ui.window.application
import androidx.compose.ui.window.rememberNotification
import androidx.compose.ui.window.rememberTrayState
import androidx.compose.ui.window.rememberWindowState
import io.github.zimoyin.xianyukefu.ButtonType
import io.github.zimoyin.xianyukefu.TrayWindow
import javax.swing.JButtonfun main() = application {var count by remember { mutableStateOf(0) }val WindowState = rememberWindowState()val isWindowShow = remember { mutableStateOf(true) }val trayState = rememberTrayState()val notification = rememberNotification("Notification", "Message from MyApp!")TrayWindow(state = trayState,icon = TrayIcon,onAction = {if (!isWindowShow.value) isWindowShow.value = trueWindowState.isMinimized = false},onClick = {if (!isWindowShow.value) isWindowShow.value = trueWindowState.isMinimized = false}) {Box {Text("23123")}Item("增加值") {count++}Item("發送通知") {trayState.sendNotification(notification)}Item("退出") {exitApplication()}// Item// Label// Separator// VerticalSeparator// CheckboxItem// RadioButtonItem// Menu// Component // 用于添加 JWT 的組件}Window(onCloseRequest = {isWindowShow.value},icon = MyAppIcon,state = WindowState) {// 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))}
}