Skip to content

Conversation

@m-sasha
Copy link
Member

@m-sasha m-sasha commented May 27, 2025

It is currently impossible to configure Window/Dialog properties which may only be set before the window/dialog is displayable. This PR adds SwingWindow and SwingDialog functions which take the same arguments as Window and DialogWindow, but also add an init block which is called before the window/dialog is made displayable.

Fixes https://youtrack.jetbrains.com/issue/CMP-6949/window.type-Window.Type.UTILITY-is-not-available

Testing

Added tests and also tested Window.setType manually.

This could be tested by QA

Release Notes

Features - Desktop

  • Added SwingFrame and SwingDialog composables that allow configuring the window/dialog before it is shown.

@m-sasha m-sasha requested a review from igordmn May 27, 2025 11:26
*/
@ExperimentalComposeUiApi
@Composable
fun SwingFrame(
Copy link
Collaborator

@igordmn igordmn May 27, 2025

Choose a reason for hiding this comment

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

Suggested change
fun SwingFrame(
fun Window(

We need to be consistent with the current names, even if they aren't good.

The current name for creating ComposeWindow is also Window:
image

I.e., at the moment there are 2 Window - one tries to hide ComposeWindow as implementation details, one is an additional overload, specifically for ComposeWindow. In this PR we add one more overload for it.

Copy link
Member Author

Choose a reason for hiding this comment

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

I thought about it, but

  1. From experience, it becomes very confusing to select the right overload. It's already pretty confusing with the overloads we have.
  2. I would also like to strongly indicate to the user that this is an API that is lower-level than Window.
  3. Having Window for creating ComposeWindow, as you mentioned yourself, is possibly a mistake. We would prefer Window to be an API that's not tied to AWT (except it's too late now).

Copy link
Member Author

Choose a reason for hiding this comment

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

As an existing example, we have AwtWindow...

Copy link
Collaborator

@igordmn igordmn May 27, 2025

Choose a reason for hiding this comment

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

Having Window for creating ComposeWindow, as you mentioned yourself, is possibly a mistake.

It was about FrameWindowScope.window available in the AWT-independent arguments overload.

The another overload that has create, update, dispose tied to ComposeWindow is intentional and isn't a mistake. For example, we could add a JavaFX overload:
image

Copy link
Collaborator

Choose a reason for hiding this comment

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

Overall, consistency is more important than the right names.

If we want the right names, we also need to rename Window(create= to ComposableJFrame, and ComposeWindow to ComposeJFrame, with deprecation of the old names. We can do that, but we should do that in a separate PR.

Copy link
Collaborator

Choose a reason for hiding this comment

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

As an existing example, we have AwtWindow...

It is a mess, but the new function is related to the function I posted in the screenshot, not to AwtWindow

*/
@ExperimentalComposeUiApi
@Composable
fun SwingFrame(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Having 2 functions with just one additional argument isn't good for maintenance.

The semantic of these functions is the same. It is only the parameter init is different.

Better to add init to the original functions, and add an old method with Deprecated(HIDDEN) for backward compatibility.

Copy link
Member Author

Choose a reason for hiding this comment

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

From our discussion, I thought we agreed that we want to give indication to the user that this is a lower-level, AWT-specific API. I specifically don't want to expose the user to init, unless he has a specific need to go low-level.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Actually, may be I am wrong. If, for example, we wanted to add JavaFX window overload, we couldn't do that 🤔. Will think

Copy link
Collaborator

Choose a reason for hiding this comment

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

that we want to give indication to the user that this is a lower-level

In my thoughts, this is not a question about if the function is low-level, it is only about the specific argument init that is low-level. Either way if we have 1 or 2 functions, we have to describe all that only in the init parameter description.

Copy link
Collaborator

@igordmn igordmn May 27, 2025

Choose a reason for hiding this comment

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

Actually, may be I am wrong. If, for example, we wanted to add JavaFX window overload, we couldn't do that 🤔. Will think

Ok, I am definitely wrong wanting init in the main function, as:

  • it is tied to a specific implementation
  • we might want to add another implementation.

So, let's keep separately, as you have.

Copy link
Collaborator

@igordmn igordmn May 27, 2025

Choose a reason for hiding this comment

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

But let's add a link to a new function in the main function in // comment, so developers don't forget to add new parameters there.

@igordmn
Copy link
Collaborator

igordmn commented May 27, 2025

As it is a new API, please add a second reviewer.

@m-sasha m-sasha requested a review from MatkovIvan May 27, 2025 11:53
onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
onKeyEvent: (KeyEvent) -> Boolean = { false },
create: () -> ComposeWindow,
init: (ComposeWindow) -> Unit,
Copy link
Member

Choose a reason for hiding this comment

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

What's the semantic difference between init and create here? Cannot be init just a part of create?

Copy link
Member Author

Choose a reason for hiding this comment

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

There's a difference both in the intent and in the behavior.

The intent is that configuration (init) is separate from creation of the window.

The behavior is that init is called after all the "create" parts completed. It can't be folded into create itself because create is called first, then this function does stuff (setting rootForTestListener etc.) and then init is called.

Copy link
Member

@MatkovIvan MatkovIvan May 27, 2025

Choose a reason for hiding this comment

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

I don't fully follow why it cannot be folded and why setting listeners should be done before "init"

Can we keep this reverted dependency to avoid replicating AWT/Swing API? I mean providing overload to receive AWT/Swing object outside (or lambda factory) and deal with already constructed/initialized object here?

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't fully follow why it cannot be folder and why setting listeners should be done before "init"

I don't think it actually does matter now, but it could matter in the future. That code may want to set some property of the window that the user may want to override, so its best to first call all of our code, and then the user code.

It's not a big deal, but I think it would actually be best to refactor this:

  1. Add an init argument to AwtWindow.
  2. Move all the code configuring the window into init, leaving only the creation itself in create.

@igordmn What do you think?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think it actually does matter now, but it could matter in the future.

For me init was always part of creation process, unless something else is specified initContext(adapter), etc.

Having both init and create: () -> is confusing for me.

That code may want to set some property of the window that the user may want to override, so its best to first call all of our code

What is set inside AwtWindow/SwingWindow (rootForTestListener, compositionLocalContext, exceptionHandler, and isVisible) is either needed for proper work of these Composable, or is read from the outside world (from locals). So, either it is wrong wanting to override the properties (it breaks the Composable), or there is another way (override the locals). All other types of properties shouldn't be set inside these functions.

Copy link
Member Author

Choose a reason for hiding this comment

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

What is set inside AwtWindow/SwingWindow (rootForTestListener, compositionLocalContext, exceptionHandler, and isVisible) is either needed for proper work of these Composable, or is read from the outside world (from locals).

That's true now, but won't necessarily be true in the future.

Anyway, I don't have a strong conviction about this, so I'll fold init into create if you both think it's the right choice.

Copy link
Collaborator

Choose a reason for hiding this comment

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

but won't necessarily be true in the future.

I wrote that it shouldn't be true:

All other types of properties shouldn't be set inside these functions.

If we add such properties, it will be a mistake.

@m-sasha m-sasha force-pushed the m-sasha/add-window-and-dialog-init branch from 5cf1e66 to c59e272 Compare June 4, 2025 08:09
@m-sasha
Copy link
Member Author

m-sasha commented Jun 4, 2025

Rebased on jb-main and removed init from the functions that take create.

Please re-review.

@m-sasha m-sasha requested a review from MatkovIvan June 4, 2025 08:10
Copy link
Member

@MatkovIvan MatkovIvan left a comment

Choose a reason for hiding this comment

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

No objections from my side, but the main review / approval is up to @igordmn

@m-sasha m-sasha merged commit 0cec97e into jb-main Jun 5, 2025
10 checks passed
@m-sasha m-sasha deleted the m-sasha/add-window-and-dialog-init branch June 5, 2025 07:38
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.

3 participants