Skip to content

feat(menu): add @Composable tray menu DSL#403

Open
kdroidFilter wants to merge 1 commit into
masterfrom
feat/composable-tray-menu-dsl
Open

feat(menu): add @Composable tray menu DSL#403
kdroidFilter wants to merge 1 commit into
masterfrom
feat/composable-tray-menu-dsl

Conversation

@kdroidFilter
Copy link
Copy Markdown
Owner

Summary

Lets users call painterResource() and other @Composable functions directly inside Tray { … } and SubMenu { … } — no more hoisting val icon = painterResource(...) above application { … }.

application {
    Tray(icon = painterResource(Res.drawable.icon), tooltip = "App") {
        SubMenu(label = "Advanced", icon = painterResource(Res.drawable.advanced)) {
            Item(label = "Reload", icon = painterResource(Res.drawable.reload)) { /**/ }
        }
    }
}

How it works

  • ComposableTrayMenuScope — new public interface. Every method is @Composable and submenu lambdas are @Composable too, so composable calls work at every level.
  • RecordingComposableScope — captures the DSL calls during composition into a flat MenuOp tree, then replays them into the existing TrayMenuBuilder consumed by the native (Windows / Linux / macOS / AWT) impls. The native side is unchanged.
  • New Tray() composable overloads for Painter, ImageVector, DrawableResource, iconContent, and the polymorphic Windows-vs-mac-linux variants — all accepting @Composable ComposableTrayMenuScope.() -> Unit.
  • @LowPriorityInOverloadResolution on the existing Tray() composables. The new scope is a strict superset of TrayMenuBuilder (adds a no-op dispose(), DrawableResource overloads, legacy CheckableItem(onToggle)), so existing demos auto-resolve to the new API without source changes.
  • PainterResourceWorkaroundDemoDemoComposableMenu — the workaround the old demo taught is no longer needed.

Notes

  • Uses kotlin.internal.LowPriorityInOverloadResolution with @file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") — internal stdlib annotation, widely used in practice but officially unsupported.
  • Recording introduces a small per-composition cost (running the user lambda twice — once for hashing, once for replay). Negligible for tray menus.

Test plan

  • ./gradlew build passes (main + demo modules + ktlint)
  • Run DemoComposableMenu on Linux — menu opens, icons render in items and submenus, click handlers fire
  • Run DemoComposableMenu on Windows — same
  • Run DemoComposableMenu on macOS — same
  • Spot-check an existing demo (e.g. DemoWithDrawableResources) — confirms backward-compat resolution to new API still renders icons correctly
  • State-driven menu rebuild: toggle CheckableItem and verify the native menu reflects the new state on next open

Lets users call painterResource() and other @composable functions
directly inside Tray { … } and SubMenu { … } without hoisting them
above application { … }.

- ComposableTrayMenuScope: new public interface, every method is
  @composable and submenu lambdas are @composable too.
- RecordingComposableScope: captures DSL calls during composition into
  a flat MenuOp tree, then replays them into the existing
  TrayMenuBuilder consumed by the native (Windows/Linux/Mac/Awt) impls.
- New Tray() composable overloads for Painter / ImageVector /
  DrawableResource / iconContent / polymorphic Windows-vs-mac-linux,
  all taking @composable ComposableTrayMenuScope.() -> Unit.
- @LowPriorityInOverloadResolution on the existing Tray() composables
  so existing demos auto-resolve to the new API transparently — the
  new scope is a strict superset (dispose() no-op, DrawableResource
  overloads, legacy CheckableItem(onToggle)).
- Replace PainterResourceWorkaroundDemo with DemoComposableMenu; the
  workaround it taught is no longer needed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant