Skip to content

New Window and DialogWindow APIs#2938

Merged
Alexander Maryanovsky (m-sasha) merged 75 commits into
jb-mainfrom
m-sasha/rework-window-state
May 5, 2026
Merged

New Window and DialogWindow APIs#2938
Alexander Maryanovsky (m-sasha) merged 75 commits into
jb-mainfrom
m-sasha/rework-window-state

Conversation

@m-sasha

@m-sasha Alexander Maryanovsky (m-sasha) commented Mar 30, 2026

Copy link
Copy Markdown

This PR introduces a new WindowState API, and corresponding composable functions.

The main improvements relative to the current API:

  1. Add screen selection and observing.
  2. Add the ability for custom window positioning and sizing.
  3. Add the ability to specify minimum and maximum window size.
  4. Clear API for sizing the window according to its intrinsic/preferred size (previously via DpSize.Unspecified).
  5. Allow sizing the window to its intrinsic size, and have the content fill the window. Previously when using DpSize.Unspecified with a fillMaxSize() top-level modifier would cause the window to fill the screen. Requested in CMP-2923 Add the ability to "pack" a window while allowing its content to match the window size
  6. Improve WindowState saving/restoring (see CMP-1535 Revisit WindowState/DialogState API).
  7. Improve window position API (see CMP-9260 Design problem with WindowPosition (x, y, isSpecified)).
  8. Allow positioning dialogs relative to their parent window.

Quick-start

The new APIs reside in androidx.compose.ui.window.v2.

  • Use rememberWindowState(initialScreenProvider=...) or WindowState.requestScreen to specify the screen on which the window may be placed. The actual screen (which may change over time) is observable via WindowState.screenId.
  • Use rememberWindowState(initialBoundsProvider=...) or WindowState.requestBounds to change the bounds of the window. The actual bounds (which may be different, and which will change over time) are observable via WindowState.bounds.
  • DialogWindow has a similar API, also in androidx.compose.ui.window.v2.

Fixes https://youtrack.jetbrains.com/issue/CMP-1535
Fixes https://youtrack.jetbrains.com/issue/CMP-9260
Fixes https://youtrack.jetbrains.com/issue/CMP-2923
Fixes https://youtrack.jetbrains.com/issue/CMP-1873

Testing

The new API is tested via all the tests of the previous one, plus some additional tests.

This should be tested by QA

Release Notes

Features - Desktop

  • Implemented a new, experimental, WindowState API.

Comment thread compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/Utils.desktop.kt Outdated
Comment thread compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/Utils.desktop.kt Outdated
Comment on lines +39 to +41
internal fun Dimension.toDpSize() = DpSize(width.dp, height.dp)
internal fun Point.toDpOffset() = DpOffset(x.dp, y.dp)
internal fun Rectangle.toDpRect() = DpRect(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to change it? Currently, it's aligned with the similar API across other platforms

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me, Object.asSomething has the connotation of casting, or wrapping an object, like in Arrays.asList().

Object.toSomething(), on the other hand, has the connotation of converting, and creating a completely independent new object. Like in Array.toList()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, it's aligned with the similar API across other platforms

If you really want to change this, please unify the naming across all platform-helpers (not only desktop) in a separate PR

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, reverted the naming here.

*/
@ExperimentalComposeUiApi
@Immutable
class DpInsets(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We must not introduce such API one more time as desktop only. Please reuse the existing one

cc Vendula Švastalová (@svastven)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The one in ios is internal; this one is public (but experimental).

We could change ios to use this one, but I wouldn't say it's a "must". There are plenty of places where we have internal copies of code because we don't want to create a dependency.

Up to the ios team...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's public and in skikoMain

/**
* This class represents platform insets.
*/
@InternalComposeUiApi
interface PlatformInsets {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@InternalComposeUiApi is about using only inside our library. If you need really user-faced API - it should be existing foundation/commonMain one

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PlatformInsets is not the same as DpInsets.
What I thought you meant was this:

internal data class DpInsets(val left: Dp, val top: Dp, val right: Dp, val bottom: Dp)

@svastven Vendula Švastalová (svastven) Apr 8, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DpInsets is currently internal to iosMain as convenience so that we don’t introduce an API that isn’t used anywhere else unless it’s actually needed.

Personally, I’m fine with having DpInsets as a dp-based convenience type alongside PlatformInsets, which is px-based and also internal API. I don’t see this as duplication, as long as DpInsets remains internal. If moved to skikoMain, then we can remove the one in iosMain.

If this new API is being used for window/platform insets, then I think we should use the existing WindowInsets / PlatformInsets APIs.

If we decide to introduce a public dp-based insets API, then I think it should live in common, at the same level as something like DpRect. But I am not sure this is the case.

As for PlatformInsets it is a px-based API. Its purpose is to represent the platform backing to WindowInsets, which are also px-based. The developer can then choose the PlatformInsets behavior they need for their particular case, for example dynamic getters, packed values, introduce new ones, etc.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved DpInsets to skikoMain. Not sure what else should be done here.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/**
* A representation of window insets that tracks access to enable recomposition, relayout, and
* redrawing when values change. These values should not be read during composition to avoid doing
* composition for every frame of an animation. Use methods like [Modifier.windowInsetsPadding],
* [Modifier.systemBarsPadding], and [Modifier.windowInsetsTopHeight] for Modifiers that will not
* cause recomposition when values change.
*
* Use the [WindowInsets.Companion] extensions to retrieve [WindowInsets] for the current window.
*/
@Stable
interface WindowInsets {

/**
* Contains rulers used for window insets. The [current] values are available as well as values when
* the insets are [fully visible][maximum].
*
* Other animation properties can be retrieved with [getAnimation].
*/
sealed interface WindowInsetsRulers {

WindowInsets is related to DpInsets mostly just by name, and WindowRulers is even less related.

These are types for a concrete purpose, not generic geometry types like, for example, DpRect.

So I see no reason to forcibly use WindowInsets instead of a simple holder of 4 named Dp values. Also, WindowInsets is in foundation, while this new API is in ui.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we have an issue with DpInsets, there is an option to remove it and keep only availableBounds public - insets can be derived from it.

I am also fine with keeping the current DpInsets public.

Another option is to rename them to DpScreenInsets

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see anything wrong with DpInsets.

@m-sasha Alexander Maryanovsky (m-sasha) force-pushed the m-sasha/rework-window-state branch 2 times, most recently from 7abe766 to d0f6910 Compare April 8, 2026 18:43
/**
* The list of screens on which the window can be placed.
*/
val screens: List<Screen> = devices.map { Screen(it) }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it be state-backed? How to be notified about a new screen appearing?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is part of WindowScreenProviderScope, which is a very short-lived object. It's created for, and only exists, during the call to WindowScreenProvider.getScreen().

Also, there's no AWT API to monitor screens...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add an explicit note about the fact that it's not state backed and you won't be notified about the change

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no need to add a note here because the API shape is such that it's not possible to be notified of such changes even if it were technically possible. WindowScreenProviderScope is a temporary scope object with which WindowScreenProvider is invoked once.

@igordmn Igor Demin (igordmn) left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve for the current public API.

I also looked at the implementation, but not deeply.

@m-sasha Alexander Maryanovsky (m-sasha) force-pushed the m-sasha/rework-window-state branch 3 times, most recently from 5502d13 to 7619a90 Compare April 28, 2026 13:42
@m-sasha Alexander Maryanovsky (m-sasha) marked this pull request as ready for review May 1, 2026 15:17
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch

// TODO(demin): fix mouse hover after opening a dialog.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a link to YT task

@m-sasha Alexander Maryanovsky (m-sasha) May 5, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a new TODO, it's just copied from the (v1) SwingDialog; likewise for all the others in this PR.
Igor can create a YT ticket when he gets back, if necessary.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ComponentUpdater.desktop.kt

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a new file.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ComposeAccessible.desktop.kt‎

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a new file either.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screen.desktop.kt

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done



/**
* Represents a user's screen.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

user's? Maybe is it better to re-word that in terms of system/environment/etc?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to "Represents a screen (a graphical device on which windows can be rendered)."


@Test
fun `set window min intrinsic width`() = runApplicationTest {
assumeTrue(!isLinux) // Flaky on our CI

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YT issue?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a new problem, and hardly deserves its own ticket, IMO.


@Test
fun `showing a window should measure content specified size`() = runApplicationTest {
// TODO fix on Linux https://github.com/JetBrains/compose-multiplatform/issues/1297

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but it's not a new issue; it's a copy paste from WindowTest.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MeasurableRootContent.skiko.kt‎

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DpInsets.skiko.kt

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's just data structure (not window-insets or so), probably it should be part of ui-unit module

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This WIP PR introduces a new experimental desktop windowing API under androidx.compose.ui.window.v2, adding screen-aware window state, bounds/size providers, intrinsic sizing, and updated save/restore behavior. It fits into the desktop Compose window stack by plumbing measurable content and geometry state from scene/container layers up through AWT wrappers and the public window/dialog APIs.

Changes:

  • Adds new experimental WindowState/DialogState v2 APIs, geometry providers, screen abstractions, and public Window/DialogWindow composables.
  • Exposes measurable root content and new geometry/inset helpers so window sizing can depend on intrinsic content size before first show.
  • Expands desktop test coverage for the v2 APIs and splits low-level ComposeWindow/ComposeDialog focus-clearing tests into dedicated files.

Reviewed changes

Copilot reviewed 49 out of 50 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/unit/Geometry.skiko.kt Adds shared dp geometry helpers used by new window sizing/state code.
compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/unit/DpInsets.kt Introduces public experimental DpInsets and related operators.
compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/scene/ComposeScene.skiko.kt Trivial formatting/no-op around unconstrained size helper.
compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/layout/MeasurableRootContent.kt Promotes measurable root content interface to experimental public API.
compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/WindowTypingLocationTest.kt Normalizes package for existing desktop typing-location tests.
compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/WindowTypeTest.kt Normalizes package for existing desktop type tests.
compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/WindowTestUtils.kt Normalizes package for shared legacy window test helpers.
compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/WindowTest.kt Cleans imports and removes low-level ComposeWindow focus-clearing tests.
compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/WindowStateTest.kt Normalizes package/imports for legacy window state tests.
compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/WindowParameterTest.kt Normalizes package/imports for legacy parameter tests.
compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/WindowInputEventTest.kt Normalizes package/imports for legacy input-event tests.
compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/v2/WindowV2Test.kt Adds broad behavioral coverage for v2 window composition and lifecycle.
compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/v2/WindowV2StateTest.kt Adds extensive state/bounds/sizing tests for v2 windows.
compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/v2/WindowTestUtils.kt Adds v2-specific intrinsic sizing and content-size test helpers.
compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/v2/DialogWindowV2Test.kt Adds broad behavioral coverage for v2 dialogs.
compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/v2/DialogWindowV2StateTest.kt Adds extensive state/bounds/sizing tests for v2 dialogs.
compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/TestUtils.kt Adds helper for launching v2 window tests in existing test harness.
compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/DialogWindowTest.kt Cleans imports and removes low-level ComposeDialog focus-clearing tests.
compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/ComposeWindowTest.kt New dedicated low-level ComposeWindow focus-clearing tests.
compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/ComposeDialogTest.kt New dedicated low-level ComposeDialog focus-clearing tests.
compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/BaseWindowTextFieldTest.kt Normalizes package/imports for shared text-field window tests.
compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/WindowState.desktop.kt Minor import cleanup in legacy window state implementation.
compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/WindowPosition.desktop.kt Adds deprecation suppression to legacy aligned position factory.
compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/WindowPlacement.desktop.kt.kt Minor documentation wording fixes for legacy placement enum.
compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/WindowLocationTracker.desktop.kt Refactors cascade placement to be screen-aware and reusable by v2.
compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/Window.desktop.kt Exposes single-window scope helper for reuse and updates TODO comment.
compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/v2/WindowState.desktop.kt Adds new experimental v2 window state and save/restore API.
compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/v2/WindowGeometryProviders.kt Adds v2 screen/bounds/position/size provider model and intrinsic sizing logic.
compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/v2/Window.desktop.kt Adds new experimental v2 public window composable and single-window entry point.
compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/v2/Screen.kt Adds screen abstraction used by v2 screen-selection APIs.
compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/v2/DialogWindow.desktop.kt Adds new experimental v2 public dialog composable.
compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/v2/DialogState.desktop.kt Adds new experimental v2 dialog state and save/restore API.
compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/LayoutConfiguration.desktop.kt Renames desktop geometry conversion helpers.
compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/Geometry.desktop.kt Adds desktop geometry/inset conversion helpers used by v2 code.
compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/util/Windows.desktop.kt Extracts screen-alignment location helper for reuse.
compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/util/ComponentUpdater.kt Small generic/doc cleanup in shared updater utility.
compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/scene/ComposeSceneMediator.desktop.kt Exposes measurable scene content and renames geometry helpers.
compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/scene/ComposeContainer.desktop.kt Exposes measurable content from desktop compose container.
compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/PlatformWindowContext.desktop.kt Renames desktop geometry helpers and related imports.
compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/AwtDragAndDropManager.desktop.kt Renames desktop point-to-dp helper usage.
compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/a11y/ComposeAccessible.kt Renames desktop point-to-dp helper usage in accessibility layer.
compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/v2/SwingWindow.desktop.kt Adds AWT-backed v2 window implementation and state synchronization.
compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/v2/SwingDialog.desktop.kt Adds AWT-backed v2 dialog implementation and state synchronization.
compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/Utils.desktop.kt Adds rectangle conversion helpers used by v2 bounds handling.
compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/SwingWindow.desktop.kt Ensures first render validates before immediate draw.
compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/SwingDialog.desktop.kt Ensures first render validates before immediate draw.
compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindowPanel.desktop.kt Exposes measurable content from ComposeWindowPanel.
compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindow.desktop.kt Exposes measurable content from public ComposeWindow.
compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeDialog.desktop.kt Exposes measurable content from public ComposeDialog.
compose/ui/ui/api/ui.klib.api Partially updates API dump for newly exposed/public types.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

val env = GraphicsEnvironment.getLocalGraphicsEnvironment()
val devices = env.screenDevices
val actualDefaultDevice = defaultDevice
?: devices.firstOrNull { it.iDstring === lastActiveConfig?.device?.iDstring }

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Comment on lines +355 to +360
fun requestPosition(positionProvider: WindowPositionProvider) {
boundsRequests.trySend(
WindowBoundsProvider(
positionProvider = positionProvider,
)
)
Comment on lines +402 to +407
fun requestSize(sizeProvider: WindowSizeProvider) {
boundsRequests.trySend(
WindowBoundsProvider(
sizeProvider = sizeProvider,
)
)
Comment thread compose/ui/ui/api/ui.klib.api
@m-sasha Alexander Maryanovsky (m-sasha) changed the title [WIP] New WindowState API New Window and DialogWindow APIs May 5, 2026
@m-sasha Alexander Maryanovsky (m-sasha) merged commit 84e92bf into jb-main May 5, 2026
25 checks passed
@m-sasha Alexander Maryanovsky (m-sasha) deleted the m-sasha/rework-window-state branch May 5, 2026 12:05
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.

7 participants