Nucleus
Tao backend

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

build.gradle.kts
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

ParameterTypeNotes
onCloseRequest() -> UnitFired by the OS close affordance.
stateWindowStatePosition, size, placement. An axis set to Dp.Unspecified wraps to content after the first composition.
visibleBooleanDefault true.
titleStringOS-level window title.
iconPainter?Taskbar / dock icon.
resizableBooleanDefault true.
enabled / focusable / alwaysOnTopBooleanStandard window flags.
undecoratedBooleanBorderless window with no controls. Honored by Tao; ignored by AWT.
popupForNucleusWindow?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).
nativePopupLayersBooleanDefault false. Materialise Compose Popup layers as native transparent windows (NSPanel / WS_POPUP HWND). Tao only.
hiddenFromDockBooleanDefault false. Hide this window from the OS taskbar/Dock. Honored by Tao; ignored by AWT.
minimumSizeDpSize?Enforced after the first layout pass.
onPreviewKeyEvent / onKeyEvent(KeyEvent) -> BooleanReturn true to consume the event.
content@Composable NucleusDecoratedWindowScope.() -> UnitTitle 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 NSApplication to 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_TOOLWINDOW on 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 outside TitleBar.
  • 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 DecoratedWindow several times from the same nucleusApplication block. Each window gets its own NucleusWindow.
  • 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.
  • NucleusApplicationScope implements Compose's ApplicationScope, so libraries scoped to the plain Compose receiver (for example system-tray composables) resolve inside nucleusApplication { }.

What's next