Skip to content

[WIP] New WindowState API#2938

Draft
m-sasha wants to merge 64 commits intojb-mainfrom
m-sasha/rework-window-state
Draft

[WIP] New WindowState API#2938
m-sasha wants to merge 64 commits intojb-mainfrom
m-sasha/rework-window-state

Conversation

@m-sasha
Copy link
Copy Markdown

@m-sasha m-sasha commented Mar 30, 2026

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
    • CMP-7377 [JVM] Resize Window() size to fit content
  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.bounds.
  • 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.

Plan

The plan is to introduce this API as experimental, gather feedback from users and then deprecate the old one, replacing it with the new one.

Note that currently the PR only includes the window state API, but a similar one will be added for dialogs, once the window one has been generally approved by reviewers.

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-7377
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.

@m-sasha m-sasha marked this pull request as draft March 30, 2026 22:46
@m-sasha m-sasha requested a review from kropp March 31, 2026 14:11
@m-sasha m-sasha force-pushed the m-sasha/rework-window-state branch 2 times, most recently from 5f80465 to 44d9b6c Compare April 2, 2026 21:15
@m-sasha m-sasha requested a review from MatkovIvan April 7, 2026 10:54
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()

*/
@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 @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)

Copy link
Copy Markdown

@svastven svastven Apr 8, 2026

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

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 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
@m-sasha m-sasha force-pushed the m-sasha/rework-window-state branch from 48f8bc8 to fb106d0 Compare April 10, 2026 07:39
*
* Note that the actual placement is set asynchronously.
*/
fun requestPlacement(placement: WindowPlacement) {
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.

How does it work with Wayland?

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.

In the end, the implementation goes through the same API as the old Window API, so it works the same way...

/**
* 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...

*
* @see androidx.compose.ui.window.v2.Window
*/
@ExperimentalComposeUiApi
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Please add a KDoc note to each function that it is planned to be moved to androidx.compose.ui.awt after stabilization.

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.

You mean to every v2 function/class?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Yes

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actually, we probably have to keep classes under "v2".

What we discussed earlier was about Composable functions

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Discussed that keeping it forever in "v2" is also an option.

Copy link
Copy Markdown

@igordmn igordmn left a comment

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 m-sasha force-pushed the m-sasha/rework-window-state branch 2 times, most recently from 49a2341 to 5502d13 Compare April 27, 2026 16:56
m-sasha added 29 commits April 28, 2026 16:20
…ate to avoid accidentally using it.

Add size and position properties to WindowState and DialogState.
@m-sasha m-sasha force-pushed the m-sasha/rework-window-state branch from 5502d13 to 7619a90 Compare April 28, 2026 13:42
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.

5 participants