DecoratedWindow on Tao
Open a Compose Desktop window on the Tao backend with a custom title bar slot and native window controls, without AWT.
DecoratedWindow is the same Composable on every Nucleus backend. On Tao it opens a
native OS window, mounts a Skiko rendering surface, and gives you a TitleBar slot you
fill with Compose content — including per-OS control-button layouts. This page covers the
Tao-specific behavior of DecoratedWindow and the members exposed inside its content lambda.
Add the dependency
plugins {
id("dev.nucleusframework")
}
dependencies {
implementation("dev.nucleusframework:nucleus.nucleus-application:2.4.3")
implementation("dev.nucleusframework:nucleus.decorated-window-tao:2.4.3")
}nucleus-application provides the unified entry point; decorated-window-tao provides
the Tao backend. With both on the classpath, NucleusBackend.Tao is available.
Open a window
Start the runtime with nucleusApplication, then call DecoratedWindow and add a
TitleBar:
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.rememberWindowState
import dev.nucleusframework.application.DecoratedWindow
import dev.nucleusframework.application.NucleusBackend
import dev.nucleusframework.application.nucleusApplication
import dev.nucleusframework.window.NucleusDecoratedWindowTheme
import dev.nucleusframework.window.TitleBar
import dev.nucleusframework.window.macOSLargeCornerRadius
import dev.nucleusframework.window.styling.TitleBarColors
import dev.nucleusframework.window.styling.TitleBarMetrics
import dev.nucleusframework.window.styling.TitleBarStyle
fun main() = nucleusApplication(backend = NucleusBackend.Tao) {
val titleBarStyle = TitleBarStyle(
colors = TitleBarColors(
background = Color(0xFF1A1D24),
inactiveBackground = Color(0xFF15181D),
content = Color(0xFFE6E6E6),
border = Color.Transparent,
),
metrics = TitleBarMetrics(height = 36.dp),
)
NucleusDecoratedWindowTheme(isDark = true, titleBarStyle = titleBarStyle) {
DecoratedWindow(
onCloseRequest = ::exitApplication,
state = rememberWindowState(size = DpSize(1024.dp, 720.dp)),
title = "Tao Demo",
minimumSize = DpSize(640.dp, 480.dp),
) {
TitleBar(modifier = Modifier.macOSLargeCornerRadius()) { state ->
Text("Tao Demo", Modifier.align(Alignment.CenterHorizontally))
}
Box(Modifier.fillMaxSize()) { /* app content */ }
}
}
}The TitleBar slot handles title-bar press-and-drag internally, so the window moves when
you drag empty title-bar space. You don't wire up a drag modifier yourself.
For full-window layouts, custom design-system chrome, macOS glass sidebars, or Windows 11
Mica/Acrylic, use WindowScaffold instead of (or around) the
stock TitleBar. Existing TitleBar call sites stay valid.
Open a window from composition
DecoratedWindow / DecoratedDialog are extensions on NucleusApplicationScope, a
receiver that only exists at the top of main(). Secondary windows usually open from a
navigation destination or a row action, where that receiver is gone.
From 2.3, nucleusApplication provides LocalNucleusApplicationScope on both backends
(and bridges parent locals into each Tao scene). Receiver-less overloads read it:
import androidx.compose.runtime.Composable
import dev.nucleusframework.application.DecoratedWindow
import dev.nucleusframework.application.LocalNucleusApplicationScope
@Composable
fun EditorWindow(onClose: () -> Unit) {
// No application receiver — reads LocalNucleusApplicationScope
DecoratedWindow(onCloseRequest = onClose, title = "Editor") {
EditorContent()
}
}
@Composable
fun MaterialEditorWindow(onClose: () -> Unit) {
// Toolkit wrappers still need the scope as a receiver:
with(LocalNucleusApplicationScope.current) {
MaterialDecoratedWindow(onCloseRequest = onClose) {
EditorContent()
}
}
}Outside nucleusApplication { }, reading the local throws. Prefer the receiver-less
DecoratedWindow / DecoratedDialog when you only need the core APIs; use
LocalNucleusApplicationScope.current explicitly for toolkit wrappers
(MaterialDecoratedWindow, Jewel, Fluent, …) that remain scope extensions.
From 2.3.2, each secondary Tao scene also re-provides LocalTaoWindow and
LocalTitleBarInfo for that window after parent locals are bridged in. Title-bar
drag, double-click maximize, and system controls therefore target the child window, not
the parent that opened it. BasicTitleBar binds drag with
Modifier.windowDragArea(window) so it stays correct even if a parent
LocalTaoWindow is still visible in the composition tree.
Hosted windows
Libraries and navigation layers should not hard-code Compose Desktop's AWT
androidx.compose.ui.window.Window / Dialog (unsupported under Tao) or a specific
design-system window type. From 2.3.2, nucleusApplication also provides
LocalNucleusWindowHost and LocalNucleusDialogHost. The defaults open a plain
DecoratedWindow / DecoratedDialog (full parameter parity, including Tao knobs such as
popupFor, nativePopupLayers, and hiddenFromDock). Call sites use HostedWindow /
HostedDialog:
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.rememberWindowState
import dev.nucleusframework.application.HostedWindow
import dev.nucleusframework.application.LocalNucleusWindowHost
import dev.nucleusframework.window.TitleBar
@Composable
fun DeepSearchDestination(onBack: () -> Unit) {
HostedWindow(
onCloseRequest = onBack,
state = rememberWindowState(size = DpSize(1200.dp, 800.dp)),
title = "Deep search",
) {
TitleBar { /* … */ }
DeepSearchContent()
}
}
// App chrome: one override, every HostedWindow call site picks it up
@Composable
fun AppRoot() {
CompositionLocalProvider(LocalNucleusWindowHost provides myMaterialHost) {
NavigationGraph()
}
}Override the host when the app wraps every secondary window in Material, Jewel, or custom
chrome without teaching every library or navigation destination about that wrapper. Direct
DecoratedWindow / DecoratedDialog calls remain valid when you want the concrete type.
How it works
nucleusApplication { } resolves the backend and exposes DecoratedWindow on
NucleusApplicationScope. On Tao, that overload delegates to
dev.nucleusframework.window.tao.ApplicationScope.DecoratedWindow, which opens a
TaoWindow and mounts a ComposeScene against the native surface.
Inside the content lambda you get a NucleusDecoratedWindowScope. Its
nucleusWindow property returns the backend-agnostic NucleusWindow handle: focus
state, minimized / maximized / fullscreen flows, icon, and minimum size. Reach the raw
TaoWindow through nucleusWindow.unsafe.taoWindow when you need Tao-only behavior.
Title-bar styling is shared with the AWT backends. TitleBarStyle, TitleBarColors,
and TitleBarMetrics live in decorated-window-core, and NucleusDecoratedWindowTheme
provides them through composition locals. The same theme works whether the dependency is
-tao, -jbr, or -jni.
API reference
DecoratedWindow parameters
| Parameter | Type | Notes |
|---|---|---|
onCloseRequest | () -> Unit | Fired by the OS close affordance. |
state | WindowState | Position, size, placement. |
visible | Boolean | Default true. |
title | String | OS-level window title. |
icon | Painter? | Taskbar / dock icon. |
resizable | Boolean | Default true. |
enabled / focusable / alwaysOnTop | Boolean | Standard window flags. |
undecorated | Boolean | Borderless window with no controls. Honored by Tao; ignored by AWT. |
popupFor | NucleusWindow? | Linux/Tao only: attach this window as a popup overlay of another. On native Wayland, positions are in the parent content area (hidden-titlebar CSD origin is applied inside setOuterPosition). |
nativePopupLayers | Boolean | Default false. Materialise Compose Popup layers as native transparent windows (NSPanel / WS_POPUP HWND). Tao only. |
hiddenFromDock | Boolean | Default false. Hide this window from the OS taskbar/Dock. Honored by Tao; ignored by AWT. |
minimumSize | DpSize? | Enforced after the first layout pass. |
onPreviewKeyEvent / onKeyEvent | (KeyEvent) -> Boolean | Return true to consume the event. |
content | @Composable NucleusDecoratedWindowScope.() -> Unit | Title bar slot plus your UI. |
The Tao-native DecoratedWindow (via taoApplication or the Tao ApplicationScope overload)
also accepts transparent: Boolean = false for full-window transparency. At 2.2.0 the umbrella
NucleusApplicationScope.DecoratedWindow does not forward that flag — see
transparent overlays.
Hiding from the taskbar/Dock
hiddenFromDock keeps the window visible and focusable while removing it from the OS-level
window list — useful for HUDs, overlays, and background utility windows that should not clutter
the taskbar or app switcher:
DecoratedWindow(
onCloseRequest = ::exitApplication,
hiddenFromDock = true,
) {
// ...
}The mechanism differs per platform:
- macOS — switches the shared
NSApplicationto the accessory activation policy (no Dock icon, no menu bar). This is app-wide, not per-window: the last window to apply the flag wins. - Windows — sets
WS_EX_TOOLWINDOWon the window, which drops its taskbar button and its Alt+Tab entry. Per-window. - Linux — sets the GTK skip-taskbar/skip-pager hints (
_NET_WM_STATE_SKIP_TASKBAR). Per-window, and only effective on X11 or XWayland.
On native Wayland, hiddenFromDock has no effect — Wayland has no client-side skip-taskbar
protocol (xdg-shell, gtk_shell1, and the staging layer-shell extensions all lack it, and Mutter
rejects wlr-layer-shell). Nucleus logs a warning rather than failing silently. Force XWayland with
NUCLEUS_TAO_LINUX_RENDERER=x11 if you need the window actually hidden on a Wayland session — see
Native Wayland.
Scope members
interface NucleusDecoratedWindowScope : DecoratedWindowScope {
val nucleusWindow: NucleusWindow
}
interface NucleusWindow {
val isFocused: Boolean
val isMinimized: Boolean
val isMaximized: Boolean
val isFullscreen: Boolean
val focusFlow: StateFlow<Boolean>
fun setMaximized(maximized: Boolean)
fun setFullscreen(fullscreen: Boolean)
fun setMinimumSize(size: DpSize?)
fun setIcon(painter: Painter?)
fun close()
val unsafe: NucleusWindowUnsafe // .taoWindow, .taoHandle
}Title bar modifiers
Modifier.macOSLargeCornerRadius()— opt into macOS 26+ rounded corners.Modifier.newFullscreenControls()— relayout the title bar buttons for fullscreen.
Scaffold and chrome (2.2+)
Prefer these when the stock TitleBar layout is too rigid:
WindowScaffold/TitleBarPlacement— full-window and overlay chrome layouts.Modifier.windowDragArea/noWindowDrag— declare (or exclude) native move regions.WindowControls— system min/max/close outsideTitleBar.WindowBackground/WindowAppearance— clear colour and native light/dark from inside the tree.Modifier.windowGlassRegion(macOS) /WindowsBackdrop(Windows 11) — platform materials.
Full reference: Window scaffold and chrome.
TitleBar is a single extension on DecoratedWindowScope. The nucleusApplication scope
(NucleusDecoratedWindowScope) and the Tao-native taoApplication scope
(TaoDecoratedWindowScope) both implement it, so the same TitleBar call works whichever
entry point you start from. The same holds for WindowScaffold and the chrome primitives.
Notes
- macOS requires
-XstartOnFirstThread. The Nucleus Gradle plugin adds it for you. - For multi-window apps, call
DecoratedWindowseveral times from the samenucleusApplicationblock. Each window gets its ownNucleusWindow. - Linux CSD windows draw a native GTK drop shadow (hidden-titlebar pattern) that tracks the window during interactive moves.
NucleusApplicationScopeimplements Compose'sApplicationScope, so libraries scoped to the plain Compose receiver (for example system-tray composables) resolve insidenucleusApplication { }.
What's next
- Window scaffold and chrome — full-window layouts and materials.
- The Tao backend — what the no-AWT backend covers.
- DecoratedWindow across backends — the shared window API.
- Multi-touch and trackpad gestures — read Tao input events.
- Backends — how Nucleus picks Tao or AWT.