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.7")
implementation("dev.nucleusframework:nucleus.decorated-window-tao:2.4.7")
}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. An axis set to Dp.Unspecified wraps to content after the first composition. |
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. 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.
- Windowed Windows CSD (no Mica/Acrylic) clears the DWM border fill so the Compose frame stroke is visible, and draws the Win11 8px rounded contour through DWM so corners are not clipped. Backdrop windows keep the system frame.
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.