Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.kdroid.composetray.demo

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import com.kdroid.composetray.tray.api.Tray
import com.kdroid.composetray.utils.SingleInstanceManager
import composenativetray.demo.generated.resources.Res
import composenativetray.demo.generated.resources.icon
import composenativetray.demo.generated.resources.icon2
import org.jetbrains.compose.resources.painterResource

/**
* Showcases the @Composable menu DSL. No need to hoist `painterResource(...)` above
* `application { … }` — every menu/submenu lambda is composable.
*/
fun main() = application {
var isWindowVisible by remember { mutableStateOf(true) }
var enableHeavyMode by remember { mutableStateOf(false) }

val isSingleInstance = SingleInstanceManager.isSingleInstance(onRestoreRequest = {
isWindowVisible = true
})
if (!isSingleInstance) {
exitApplication()
return@application
}

Tray(
icon = painterResource(Res.drawable.icon),
tooltip = "Composable Menu Demo",
primaryAction = { isWindowVisible = true },
menuContent = {
Item(label = "Open", icon = painterResource(Res.drawable.icon)) {
isWindowVisible = true
}

SubMenu(
label = "Advanced",
icon = painterResource(Res.drawable.icon2),
) {
Item(label = "Reload", icon = painterResource(Res.drawable.icon)) {
println("Reload")
}
Item(label = "Reset", icon = painterResource(Res.drawable.icon2)) {
println("Reset")
}
}

Divider()

CheckableItem(
label = "Heavy mode",
icon = painterResource(Res.drawable.icon2),
checked = enableHeavyMode,
onCheckedChange = { enableHeavyMode = it },
)

Divider()

Item(label = "Exit", icon = painterResource(Res.drawable.icon2)) {
dispose()
exitApplication()
}
},
)

Window(
onCloseRequest = { isWindowVisible = false },
title = "Composable Menu Demo",
visible = isWindowVisible,
icon = painterResource(Res.drawable.icon),
) {
window.toFront()
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package com.kdroid.composetray.menu.api

import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import com.kdroid.composetray.utils.IconRenderProperties
import org.jetbrains.compose.resources.DrawableResource

/**
* Composable-aware DSL for declaring tray menus.
*
* Unlike [TrayMenuBuilder] — whose submenu lambdas are plain Kotlin lambdas executed by the
* native impls outside of any composition — every method here is `@Composable`. You can call
* `painterResource(...)`, read state, etc., directly inside menu and submenu bodies without
* hoisting:
*
* ```
* Tray(icon = Res.drawable.icon, tooltip = "App") {
* SubMenu(label = "Advanced", icon = painterResource(Res.drawable.advanced)) {
* Item(label = "Reload", icon = painterResource(Res.drawable.reload)) { ... }
* }
* }
* ```
*
* Internally, calls are recorded during composition and replayed into the [TrayMenuBuilder]
* consumed by the native menu impls. Recomposition triggered by state reads inside the lambda
* transparently rebuilds the menu.
*/
interface ComposableTrayMenuScope {
@Composable
fun Item(
label: String,
isEnabled: Boolean = true,
shortcut: KeyShortcut? = null,
onClick: () -> Unit = {},
)

@Composable
fun Item(
label: String,
icon: Painter,
iconRenderProperties: IconRenderProperties = IconRenderProperties.forMenuItem(),
isEnabled: Boolean = true,
shortcut: KeyShortcut? = null,
onClick: () -> Unit = {},
)

@Composable
fun Item(
label: String,
icon: ImageVector,
iconTint: Color? = null,
iconRenderProperties: IconRenderProperties = IconRenderProperties.forMenuItem(),
isEnabled: Boolean = true,
shortcut: KeyShortcut? = null,
onClick: () -> Unit = {},
)

@Composable
fun CheckableItem(
label: String,
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
isEnabled: Boolean = true,
shortcut: KeyShortcut? = null,
)

@Composable
fun CheckableItem(
label: String,
icon: Painter,
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
iconRenderProperties: IconRenderProperties = IconRenderProperties.forMenuItem(),
isEnabled: Boolean = true,
shortcut: KeyShortcut? = null,
)

@Composable
fun CheckableItem(
label: String,
icon: ImageVector,
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
iconTint: Color? = null,
iconRenderProperties: IconRenderProperties = IconRenderProperties.forMenuItem(),
isEnabled: Boolean = true,
shortcut: KeyShortcut? = null,
)

@Composable
fun SubMenu(
label: String,
isEnabled: Boolean = true,
submenuContent: @Composable ComposableTrayMenuScope.() -> Unit,
)

@Composable
fun SubMenu(
label: String,
icon: Painter,
iconRenderProperties: IconRenderProperties = IconRenderProperties.forMenuItem(),
isEnabled: Boolean = true,
submenuContent: @Composable ComposableTrayMenuScope.() -> Unit,
)

@Composable
fun SubMenu(
label: String,
icon: ImageVector,
iconTint: Color? = null,
iconRenderProperties: IconRenderProperties = IconRenderProperties.forMenuItem(),
isEnabled: Boolean = true,
submenuContent: @Composable ComposableTrayMenuScope.() -> Unit,
)

@Composable
fun Item(
label: String,
icon: DrawableResource,
iconRenderProperties: IconRenderProperties = IconRenderProperties.forMenuItem(),
isEnabled: Boolean = true,
shortcut: KeyShortcut? = null,
onClick: () -> Unit = {},
)

@Composable
fun CheckableItem(
label: String,
icon: DrawableResource,
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
iconRenderProperties: IconRenderProperties = IconRenderProperties.forMenuItem(),
isEnabled: Boolean = true,
shortcut: KeyShortcut? = null,
)

@Composable
fun SubMenu(
label: String,
icon: DrawableResource,
iconRenderProperties: IconRenderProperties = IconRenderProperties.forMenuItem(),
isEnabled: Boolean = true,
submenuContent: @Composable ComposableTrayMenuScope.() -> Unit,
)

@Composable
fun Divider()

/**
* Backward-compatibility shim. Under the composable DSL, the tray lifecycle is owned by the
* surrounding `DisposableEffect` in [Tray], so this is a no-op. Existing menus that call
* `dispose()` before `exitApplication()` keep compiling without behavior change.
*/
fun dispose() {
// no-op: lifecycle managed by Tray composable
}

@Deprecated(
message = "Use CheckableItem with separate checked and onCheckedChange parameters",
replaceWith = ReplaceWith("CheckableItem(label, checked, onToggle, isEnabled)"),
)
@Composable
fun CheckableItem(
label: String,
checked: Boolean = false,
isEnabled: Boolean = true,
onToggle: (Boolean) -> Unit,
) {
CheckableItem(label, checked, onToggle, isEnabled, null)
}
}
Loading
Loading