Skip to content
Merged
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
2 changes: 0 additions & 2 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,10 @@ kotlin {
implementation(compose.ui)
implementation(compose.components.resources)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.coroutines.swing)
implementation(libs.nucleus.core.runtime)
implementation(libs.nucleus.darkmode.detector)
api(libs.nucleus.application)
implementation(libs.nucleus.decorated.window.tao)
}
jvmTest.dependencies {
implementation(kotlin("test"))
Expand Down
2 changes: 2 additions & 0 deletions demo/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ kotlin {
implementation(libs.nucleus.core.runtime)
implementation(libs.nucleus.darkmode.detector)
implementation(libs.nucleus.graalvm.runtime)
implementation(libs.nucleus.decorated.window.tao)
implementation(libs.nucleus.decorated.window.material3)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,35 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Icon
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Notifications
import androidx.compose.material3.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.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.WindowState
import androidx.compose.ui.window.application
import androidx.compose.ui.window.rememberWindowState
import dev.nucleusframework.application.DecoratedWindow
import dev.nucleusframework.application.nucleusApplication
import dev.nucleusframework.composenativetray.tray.api.Tray
import dev.nucleusframework.composenativetray.utils.ComposeNativeTrayLoggingLevel
import dev.nucleusframework.core.runtime.SingleInstanceManager
import dev.nucleusframework.composenativetray.utils.allowComposeNativeTrayLogging
import dev.nucleusframework.composenativetray.utils.composeNativeTrayLoggingLevel
import dev.nucleusframework.composenativetray.utils.getTrayPosition
import dev.nucleusframework.composenativetray.utils.getTrayWindowPosition
import dev.nucleusframework.darkmodedetector.isSystemInDarkMode
import dev.nucleusframework.window.NucleusDecoratedWindowTheme
import dev.nucleusframework.window.TitleBar
import composenativetray.demo.generated.resources.Res
import composenativetray.demo.generated.resources.icon
import org.jetbrains.compose.resources.painterResource

fun main() = application {
fun main() = nucleusApplication(enableSingleInstance = false) {
allowComposeNativeTrayLogging = false
composeNativeTrayLoggingLevel = ComposeNativeTrayLoggingLevel.DEBUG
val logTag = "NativeTray"

println("$logTag: TrayPosition: ${getTrayPosition()}")

var isWindowVisible by remember { mutableStateOf(true) }
Expand All @@ -40,16 +42,6 @@ fun main() = application {
var notificationsEnabled by remember { mutableStateOf(false) }
var initialChecked by remember { mutableStateOf(true) }

val isSingleInstance = SingleInstanceManager.isSingleInstance(onRestoreRequest = {
isWindowVisible = true
})

if (!isSingleInstance) {
exitApplication()
return@application
}


// Always create the Tray composable, but make it conditional on visibility
// This ensures it's recomposed when alwaysShowTray changes
val showTray = alwaysShowTray || !isWindowVisible
Expand Down Expand Up @@ -146,26 +138,29 @@ fun main() = application {
val windowHeight = 600
val windowPosition = getTrayWindowPosition(windowWidth, windowHeight)

Window(
onCloseRequest = {
if (hideOnClose) {
isWindowVisible = false
} else {
exitApplication()
NucleusDecoratedWindowTheme(isDark = isSystemInDarkMode()) {
DecoratedWindow(
onCloseRequest = {
if (hideOnClose) {
isWindowVisible = false
} else {
exitApplication()
}
},
state = WindowState(
width = windowWidth.dp,
height = windowHeight.dp,
position = windowPosition
),
title = "Compose Desktop Application with Two Screens",
visible = isWindowVisible,
icon = painterResource(Res.drawable.icon) // Optional: Set window icon
) {
TitleBar { Text("Compose Desktop Application with Two Screens") }
App(textVisible, alwaysShowTray, hideOnClose) { alwaysShow, hideOnCloseState ->
alwaysShowTray = alwaysShow
hideOnClose = hideOnCloseState
}
},
state = WindowState(
width = windowWidth.dp,
height = windowHeight.dp,
position = windowPosition
),
title = "Compose Desktop Application with Two Screens",
visible = isWindowVisible,
icon = painterResource(Res.drawable.icon) // Optional: Set window icon
) {
App(textVisible, alwaysShowTray, hideOnClose) { alwaysShow, hideOnCloseState ->
alwaysShowTray = alwaysShow
hideOnClose = hideOnCloseState
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,34 +1,30 @@
package dev.nucleusframework.composenativetray.demo

import androidx.compose.material3.Text
import androidx.compose.runtime.LaunchedEffect
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 dev.nucleusframework.application.DecoratedWindow
import dev.nucleusframework.application.nucleusApplication
import dev.nucleusframework.composenativetray.tray.api.Tray
import dev.nucleusframework.core.runtime.SingleInstanceManager
import dev.nucleusframework.darkmodedetector.isSystemInDarkMode
import dev.nucleusframework.window.NucleusDecoratedWindowTheme
import dev.nucleusframework.window.TitleBar
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.
* `nucleusApplication { … }` — every menu/submenu lambda is composable.
*/
fun main() = application {
fun main() = nucleusApplication(enableSingleInstance = false) {
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",
Expand Down Expand Up @@ -68,12 +64,20 @@ fun main() = application {
},
)

Window(
onCloseRequest = { isWindowVisible = false },
title = "Composable Menu Demo",
visible = isWindowVisible,
icon = painterResource(Res.drawable.icon),
) {
window.toFront()
NucleusDecoratedWindowTheme(isDark = isSystemInDarkMode()) {
DecoratedWindow(
onCloseRequest = { isWindowVisible = false },
title = "Composable Menu Demo",
visible = isWindowVisible,
icon = painterResource(Res.drawable.icon),
) {
TitleBar { Text("Composable Menu Demo") }
val window = nucleusWindow
LaunchedEffect(isWindowVisible) {
if (isWindowVisible) {
window.toFront()
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import dev.nucleusframework.application.DecoratedWindow
import dev.nucleusframework.application.nucleusApplication
import dev.nucleusframework.composenativetray.tray.api.Tray
import dev.nucleusframework.darkmodedetector.isSystemInDarkMode
import dev.nucleusframework.window.NucleusDecoratedWindowTheme
import dev.nucleusframework.window.TitleBar
import dev.nucleusframework.composenativetray.utils.ComposeNativeTrayLoggingLevel
import dev.nucleusframework.composenativetray.utils.allowComposeNativeTrayLogging
import dev.nucleusframework.composenativetray.utils.composeNativeTrayLoggingLevel
Expand All @@ -27,7 +30,7 @@ import org.jetbrains.compose.resources.painterResource
* the presence of a single menu item. When the flag is false, the menuContent
* is intentionally left empty.
*/
fun main() = application {
fun main() = nucleusApplication(enableSingleInstance = false) {
// Enable logging to help diagnose behavior on Linux
allowComposeNativeTrayLogging = false
composeNativeTrayLoggingLevel = ComposeNativeTrayLoggingLevel.DEBUG
Expand Down Expand Up @@ -61,7 +64,13 @@ fun main() = application {


}
Window( onCloseRequest = ::exitApplication) {
Text("Compose Native Tray Demo")
NucleusDecoratedWindowTheme(isDark = isSystemInDarkMode()) {
DecoratedWindow(
onCloseRequest = ::exitApplication,
title = "Compose Native Tray Demo"
) {
TitleBar { Text("Compose Native Tray Demo") }
Text("Compose Native Tray Demo")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,27 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import dev.nucleusframework.application.DecoratedWindow
import dev.nucleusframework.application.nucleusApplication
import dev.nucleusframework.composenativetray.tray.api.Tray
import dev.nucleusframework.darkmodedetector.isSystemInDarkMode
import dev.nucleusframework.window.NucleusDecoratedWindowTheme
import composenativetray.demo.generated.resources.Res
import composenativetray.demo.generated.resources.icon
import org.jetbrains.compose.resources.painterResource

/**
* DemoTransparentWindow
*
* A minimal example showing a simple system tray with a transparent, undecorated window.
* A minimal example showing a simple system tray with an undecorated window.
* Left click (primaryAction) on the tray icon shows the window. Use the tray menu to
* hide or exit. The window content itself is a rounded card floating on a transparent background.
* hide or exit. The window content itself is a rounded card. Note: per-pixel
* transparency is not supported on the Tao backend.
*/
fun main() = application {
fun main() = nucleusApplication(enableSingleInstance = false) {
var isWindowVisible by remember { mutableStateOf(true) }

// Simple tray with primary action to show the transparent window
// Simple tray with primary action to show the window
Tray(
icon = painterResource(Res.drawable.icon),
tooltip = "Transparent Window Demo",
Expand All @@ -55,45 +58,48 @@ fun main() = application {
}
}

Window(
onCloseRequest = { isWindowVisible = false },
visible = isWindowVisible,
undecorated = true,
transparent = true,
alwaysOnTop = false,
resizable = false,
title = "Transparent Window Demo",
icon = painterResource(Res.drawable.icon)
) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(24.dp)
NucleusDecoratedWindowTheme(isDark = isSystemInDarkMode()) {
// Note: per-pixel transparency is not supported on the Tao backend,
// so the window is only undecorated here.
DecoratedWindow(
onCloseRequest = { isWindowVisible = false },
visible = isWindowVisible,
undecorated = true,
alwaysOnTop = false,
resizable = false,
title = "Transparent Window Demo",
icon = painterResource(Res.drawable.icon)
) {
val shape = RoundedCornerShape(16.dp)
Column(
Box(
modifier = Modifier
.align(Alignment.Center)
.shadow(24.dp, shape)
.clip(shape)
.border(1.dp, MaterialTheme.colorScheme.outlineVariant, shape)
.background(MaterialTheme.colorScheme.surface.copy(alpha = 0.85f))
.padding(20.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalAlignment = Alignment.CenterHorizontally
.fillMaxSize()
.padding(24.dp)
) {
Text(
text = "Transparent window",
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.headlineSmall
)
Text(
text = "This window is undecorated and the background is transparent.",
color = MaterialTheme.colorScheme.onSurface
)
Spacer(Modifier.height(8.dp))
Button(onClick = { isWindowVisible = false }) {
Text("Hide")
val shape = RoundedCornerShape(16.dp)
Column(
modifier = Modifier
.align(Alignment.Center)
.shadow(24.dp, shape)
.clip(shape)
.border(1.dp, MaterialTheme.colorScheme.outlineVariant, shape)
.background(MaterialTheme.colorScheme.surface.copy(alpha = 0.85f))
.padding(20.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Transparent window",
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.headlineSmall
)
Text(
text = "This window is undecorated. Per-pixel transparency is not supported on the Tao backend.",
color = MaterialTheme.colorScheme.onSurface
)
Spacer(Modifier.height(8.dp))
Button(onClick = { isWindowVisible = false }) {
Text("Hide")
}
}
}
}
Expand Down
Loading
Loading