Skip to content

8313424: JavaFX controls in the title bar #1605

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 91 commits into
base: master
Choose a base branch
from

Conversation

mstr2
Copy link
Collaborator

@mstr2 mstr2 commented Oct 20, 2024

Implementation of StageStyle.EXTENDED.


Progress

  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change requires CSR request JDK-8356115 to be approved
  • Change must be properly reviewed (2 reviews required, with at least 2 Reviewers)

Issues

  • JDK-8313424: JavaFX controls in the title bar (Enhancement - P4)
  • JDK-8356115: JavaFX controls in the title bar (CSR)

Reviewers

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jfx.git pull/1605/head:pull/1605
$ git checkout pull/1605

Update a local copy of the PR:
$ git checkout pull/1605
$ git pull https://git.openjdk.org/jfx.git pull/1605/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 1605

View PR using the GUI difftool:
$ git pr show -t 1605

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jfx/pull/1605.diff

Using Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Oct 20, 2024

👋 Welcome back mstrauss! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Oct 20, 2024

❗ This change is not yet ready to be integrated.
See the Progress checklist in the description for automated requirements.

@openjdk openjdk bot added the rfr Ready for review label Oct 20, 2024
@mlbridge
Copy link

mlbridge bot commented Oct 20, 2024

Webrevs

@mstr2
Copy link
Collaborator Author

mstr2 commented Oct 20, 2024

/reviewers 2 reviewers
/csr

@openjdk
Copy link

openjdk bot commented Oct 20, 2024

@mstr2
The total number of required reviews for this PR (including the jcheck configuration and the last /reviewers command) is now set to 2 (with at least 2 Reviewers).

@openjdk openjdk bot added the csr Need approved CSR to integrate pull request label Oct 20, 2024
@openjdk
Copy link

openjdk bot commented Oct 20, 2024

@mstr2 has indicated that a compatibility and specification (CSR) request is needed for this pull request.

@mstr2 please create a CSR request for issue JDK-8313424 with the correct fix version. This pull request cannot be integrated until the CSR request is approved.

@Glavo
Copy link
Contributor

Glavo commented Oct 20, 2024

Hey, I'm glad to see this PR, but do you have any ideas for continuing to provide UNDECORATED_INTERACTIVE in the future? I think UNDECORATED_INTERACTIVE is more useful than EXTENDED for users who want to provide a consistent UI on different platforms.

@tsayao
Copy link
Collaborator

tsayao commented Oct 20, 2024

Very nice. I'll test on Linux and report back.

@mstr2
Copy link
Collaborator Author

mstr2 commented Oct 20, 2024

Hey, I'm glad to see this PR, but do you have any ideas for continuing to provide UNDECORATED_INTERACTIVE in the future? I think UNDECORATED_INTERACTIVE is more useful than EXTENDED for users who want to provide a consistent UI on different platforms.

The only difference between the two would be whether the default window buttons are provided. I don't see how a window without default window buttons would be more useful. Even heavily stylized apps like Spotify use window buttons that feel at home on the OS, that doesn't take away from a consistent look and feel.

@tsayao
Copy link
Collaborator

tsayao commented Oct 21, 2024

A few points observed on Linux:

  1. It's possible to resize it to 1px using the provided functionality with gtk_window_begin_resize_drag. An then it's not possible to resize back. The Headerbar should block resizing to the size of window controls.
  2. It's not possible to move the window if the cursor is over a control. Maybe you should just gtk_window_begin_move_drag when a drag is detected, not on click. That would be on WindowContextBase::process_mouse_motion;
  3. The application is closing if the click happens on the top right corner (that's because it's triggering the close button instead of resizing (I think 2 should solve it as well). It closes with:
    (java:16179): Gtk-CRITICAL **: 07:34:26.721: gtk_window_begin_resize_drag: assertion 'gtk_widget_get_visible (widget)' failed
  4. Alt + F8 is working (it's a desktop shortcut for resizing the window) - just pointing out to include in manual testing;
  5. I think rounded edges should be supported on the HeaderBar, since it's the default on gnome. For that to work, EXTENDED should be also transparent on Linux. It would loose the window drop shadow which can be added on the JavaFX side. gdk_window_set_shadow_width should be called with the drop shadow size, so the desktop will know the correct window bounds.

Added later:
6) It should have a "focused" state/pseudo class because on gnome (maybe others) the focused window has a different background on the HeaderBar which is darker.
7) Suggestion: Maybe make window states stylable on the HeaderBar with pseudo-classes like :maximized, :fullscreen, :focused, :solid (when it does not have rounded corners). Then it would be possible to CSS style it.
8) Maybe integrate with platform preferences and provide a way to CSS style it when it's Light or Dark?

Nice cleanup on Window.java . UndecoratedMoveResizeHelper was not going to work on Linux anyways.

@andy-goryachev-oracle
Copy link
Contributor

I suggest we convert this PR to Draft and first discuss this in the mailing list.

There are so many issues with the proposal that need to be ironed first: CSS support, height limitation (what happens when the app or CSS places too large of the component?), user-defined color/accents/transparency on Windows, to name just a few.

This change also may add a significant maintenance burden to the platform, for what I feel is a very small payout.

What do you think?

@mstr2
Copy link
Collaborator Author

mstr2 commented Oct 21, 2024

I suggest we convert this PR to Draft and first discuss this in the mailing list.

This has already been discussed at various points in time, and was always received positively. The previous implementation is one of the most-upvoted feature proposals since OpenJFX moved to GitHub.

There are so many issues with the proposal that need to be ironed first: CSS support, height limitation (what happens when the app or CSS places too large of the component?), user-defined color/accents/transparency on Windows, to name just a few.

What about these things? I don't understand the question, but let me try to give some answers nontheless:

  1. CSS support: HeaderBar is a normal part of the scene graph, so it fully supports CSS styling.
  2. Height limitation: the height of HeaderBar is user-configurable, just like any layout container. It can be as large as you want.
  3. User-defined color/accents/transparency: Again, since HeaderBar is a part of the scene graph, all rules are the same.

This change also may add a significant maintenance burden to the platform, for what I feel is a very small payout.

Popular demand says otherwise.

@andy-goryachev-oracle
Copy link
Contributor

Popular demand is good, but I would like to hear from the tech leads and the maintainers.

@andy-goryachev-oracle
Copy link
Contributor

To clarify about height: do all the platforms support arbitrary height? What if size set by the app/CSS exceeds the height supported by the platform?

About platform preferences: will it support all the attributes of the window decorations provided by the platform?

@mstr2
Copy link
Collaborator Author

mstr2 commented Oct 21, 2024

To clarify about height: do all the platforms support arbitrary height? What if size set by the app/CSS exceeds the height supported by the platform?

Yes, all platforms support header bars of arbitrary height. You can make the header bar the size of the entire window, in which case everything is draggable.

About platform preferences: will it support all the attributes of the window decorations provided by the platform?

If by "attributes" you mean the default behavior of platform decorations, then the anwer is mostly. These are the behaviors that are available on every platform:

  1. A resize border.
  2. Minimize, maximize, and close buttons (this includes all states these buttons can be in: available/deactivated/disabled).
  3. Click-and-drag on the header bar.
  4. Double-click to maximize on the header bar.

On Windows, native window decorations also have the "system menu", which is this thing that appears when you click on the program icon:
Ml7Pf

EXTENDED windows have no system menu, because they have no program icon.
Edit: they now have a system menu that opens with a right click on the header bar.

@mstr2
Copy link
Collaborator Author

mstr2 commented Oct 21, 2024

@tsayao Thanks for the comments. I'll have a look at the bugs that you found.

  1. It's not possible to move the window if the cursor is over a control. Maybe you should just gtk_window_begin_move_drag when a drag is detected, not on click. That would be on WindowContextBase::process_mouse_motion;

I know that dragging on interactive controls is a thing on Linux, but I don't think that we should be doing that. JavaFX applications are multi-platform apps, which means that their behavior should be consistent across platforms. Stealing mouse interactions on interactive controls is not a thing on Windows and macOS, and this has the potential to cause problems for application developers.

If you want, you can declare any node in the header bar to be draggable on all platforms with HeaderBar.setDraggable(Node, boolean).

  1. I think rounded edges should be supported on the HeaderBar, since it's the default on gnome. For that to work, EXTENDED should be also transparent on Linux. It would loose the window drop shadow which can be added on the JavaFX side. gdk_window_set_shadow_width should be called with the drop shadow size, so the desktop will know the correct window bounds.

I'll have to look into that, but in general an EXTENDED window should work out of the box for all platforms, without platform-specific changes on the JavaFX side.

Added later: 6) It should have a "focused" state/pseudo class because on gnome (maybe others) the focused window has a different background on the HeaderBar which is darker. 7) Suggestion: Maybe make window states stylable on the HeaderBar with pseudo-classes like :maximized, :fullscreen, :focused, :solid (when it does not have rounded corners). Then it would be possible to CSS style it. 8) Maybe integrate with platform preferences and provide a way to CSS style it when it's Light or Dark?

While that sounds useful at first, I don't think it carries its own weight. Many platforms use different styling for windows that are focused vs. windows that are not. This not only includes the header bar, but many other parts of the user interface as well. I don't think that we should be adding what would essentially be ad-hoc pseudo-classes only to HeaderBar.

In addition to that, it is extremely easy for an application to do this by adding a listener to Stage.focused and then toggling pseudo-classes on all relevant controls.

We should definitely not do pseudo-classes for light vs. dark mode. The correct way to solve this problem is with media queries (prefers-color-scheme).

@mlbridge
Copy link

mlbridge bot commented Oct 22, 2024

Mailing list message from quizynox at gmail.com on openjfx-dev:

Hello,

Thank you so much for your effort! I'm really glad this hasn't been
forgotten. I wouldn't say it's just popular demand; it's an absolute must.
Here are a few thoughts, if you don't mind.

Every modern platform supports this feature: Electron, Tauri, Wails, Qt,
and even Swing via FlatLaf. If you use IntelliJ or VSCode, you can see it
for yourself. It's a popular design trend, which is why there's so much
demand.

Unfortunately, the current UNDECORATED stage implementation lacks two
important things: shadows and smooth resizing. Implementing shadows is
tricky but possible. However, achieving smooth resizing with Java code
alone is not feasible. There are several implementations on StackOverflow,
but they tend to be jerky and not very performant.

That's why the implementation should be handled on the native side, which
isn't something an app developer can do. We can only patiently wait for
this feature to be integrated into the core JavaFX platform.

It's indeed a complex feature. For that reason, I believe the
implementation shouldn't provide platform-dependent window controls. It
should be left to app developers to dodge theming issues. In Linux, for
example, it's common to install 3rd-party themes or decorations. You never
know what decorations the end user will use, and OS developers can change
themes over time too. It's just simpler to support this feature as a
separate library, which I'm sure will be developed.

??, 22 ???. 2024??. ? 03:24, Michael Strau? <mstrauss at openjdk.org>:

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/openjfx-dev/attachments/20241022/367f812e/attachment-0001.htm>

@tsayao
Copy link
Collaborator

tsayao commented Oct 22, 2024

I think we should look at use cases and design a simple solution that can be extended.

Doing this with JavaFX as it is now will be hacky, since it would need to touch internals that are not exposed by default.

Intellij Idea
image

Nautilus
image

Chrome
image

Amberol (music player).
image

App Center (Ubuntu Software Store) - This one uses flutter
image

Some of them has no title at all. Some fuses the HeaderBar with the body, like nautilus. Even chrome, the tabs on the HeaderBar are not "isolated" from the body.

On modern Gnome, everything is client side decorated. Server side decoration is legacy. It's better for rendering since the window manager does not need to calculate, draw and sync with the window. It's probably less flickery.

Since JavaFx accounts the title as part of the window size, the current glass implementation is very hacky because it needs to request the decoration sizes from the window manager and then recalculate it. Having it on the client side is better, because no hacky solution is required.

@nlisker
Copy link
Collaborator

nlisker commented Oct 22, 2024

Adding some more examples.

Vivaldi browser actually allows you to switch between two modes:
image

which can be switched with
image

to
image

So in the first mode, the menu is compressed vertically into the Vivaldi button.

Here is Discord:
image

Very compact.

As for the payout of this feature, while personally I don't have a need for it, I will probably use it if it's not too much trouble. Regardless of my own opinion, this has been one of the most requested features, along with tray icon support, and it's available in the main "competition" frameworks. I would say that a very strong case will need to be made for this to not be added. The question that remains, as it often does, is if this is the right implementation. Hopefully the review process will figure that out.

@andy-goryachev-oracle
Copy link
Contributor

andy-goryachev-oracle commented Oct 22, 2024

Continuing on window attributes. I would like to know what is and is not supported by platform, by feature.

For example:

Windows

  • translucency
  • color gradient
  • system menu on right click, on the toolbar and on the app components
  • color accent on hover over window buttons
  • window borders
  • round corners
  • double click to maximize
  • drag to reposition the window
  • dark/light theme
  • accessibility: keyboard focus, accessible focus, narrator

Did I miss anything?

@andy-goryachev-oracle
Copy link
Contributor

For the HeaderBar, I would like to see the explanation of different layout options, including the cases when all the information does not fit the window width. Which parts are contracted? How is overflow handled?

@andy-goryachev-oracle
Copy link
Contributor

andy-goryachev-oracle commented Oct 22, 2024

May be I am late to the party, but I would suggest to discuss the JEP first. I would like to see the summary by platform by feature, I would like to see details of the layout, and the description if and how the proposed design responds to user preferences changes dynamically.

I would also like to see alternatives. Perhaps the app developers has all the tools already (for example, creating an overlay transparent scene on top of the platform title bar?), or maybe this functionality should be rather implemented in a library.

Lastly, there is a concern of adding a huge maintenance burden on the platform. Next time the major OS vendor changes the L&F or adds something to the window decorations, we are on the hook for supporting that.

I am not even qualified to access the impact of this feature in the Linux world. There are so many frameworks and opinions - how do you propose to handle that? Is it going to be supported on Wayland?

@tsayao
Copy link
Collaborator

tsayao commented Oct 22, 2024

Gtk does work on Mac and Windows, maybe we can see how it handles it's HeaderBar, for reference.

@andy-goryachev-oracle
Copy link
Contributor

Another aspect is whether this should be a conditional feature.
If not, how will it be supported on Android/iOS? RaspPI?

@mstr2
Copy link
Collaborator Author

mstr2 commented Oct 22, 2024

Continuing on window attributes. I would like to know what is and is not supported by platform, by feature.

For example:

Windows

  • translucency
  • color gradient
  • system menu on right click, on the toolbar and on the app components
  • color accent on hover over window buttons
  • window borders
  • round corners
  • double click to maximize
  • drag to reposition the window
  • dark/light theme
  • accessibility: keyboard focus, accessible focus, narrator

Did I miss anything?

I'll eagerly invite you to dissect this PR for its merits, its pros and cons. However, your questions lead me to conclude that you haven't really looked at what I'm proposing. Half of your questions are answered just by looking at the documentation for StageStyle.EXTENDED and HeaderBar. These are the two primary APIs that I've mentioned over and over again in this PR.

Let me try to explain StageStyle.EXTENDED in different terms:

  1. EXTENDED is like UNDECORATED in the following ways: The application controls the entirety of the window. There is no non-client area (i.e. no title bar), and you can place scene graph nodes everywhere. Since there is no non-client title bar, applications will have to provide their own HeaderBar and their own system menu (if they so desire).

  2. EXTENDED is like DECORATED in the following ways: The window has a resize border, shadows, and window animations. It has the platform window buttons (minimize, maximize, close) superimposed over the application window. Just as with a decorated window, applications have no control over whether corners are rounded, how borders look like, and so on.

Since there is no non-client title bar, all questions regarding the appearance or accessibility of the HeaderBar (color gradient, dark/light theme, etc.) are left to the purview of application developers. HeaderBar is a control just like any other JavaFX control, and application developers will decide how it looks like. Notably, there is no translucency, because JavaFX does not support window-level translucency. This has nothing to do with this feature proposal, and should be discussed on its own merits.

@mstr2
Copy link
Collaborator Author

mstr2 commented Apr 25, 2025

  • On Windows, when resizing the window horizontally to small sizes, the header button symbols are drawn over any other content in the header bar's center region, which looks broken. Can the layout be made stricter to prevent this? Or always draw the background behind these symbols?

HeaderBar honors the minimum sizes of its children, so we can't size it down any further. We could potentially clip the children to an area that excludes the window buttons. To do that, we'd set a default clip node via Node.setClip(). On the other hand, that may be best left to application developers.

@mstr2
Copy link
Collaborator Author

mstr2 commented Apr 26, 2025

Yes, the controls are in an HBox with HeaderBar.draggable=true. So this is working as documented in HeaderBar.draggable. I still wonder if there's something that can be done to prevent controls from being broken by becoming draggable, especially if this happens inadvertently by "inheriting" this bit from its container.

Some ideas:

  1. Let every Node in the subtree inherit the draggable=true bit, except those that are Controls
  2. Add code to all affected controls to make them work while being draggable
  3. Prevent or ignore the draggable bit on all or all affected controls
  4. Don't apply draggable=true to the sub-tree

I've decided to replace the draggable bool flag with a new HeaderDragType enum, which is one of the following values:

  1. DRAGGABLE: Marks a single node as draggable.
  2. DRAGGABLE_SUBTREE: Marks a node and all of its descendants as draggable.
  3. NONE: Cancels an inherited DRAGGABLE_SUBTREE flag.

This should make your example easier, since you only need to mark the HBox as DRAGGABLE, without worrying about the controls.

@drmarmac
Copy link
Contributor

Good idea, this looks like the most flexible option without complicating things too much. I can confirm this works well in my tests. Code changes & documentation look good as well.

@mstr2
Copy link
Collaborator Author

mstr2 commented Apr 28, 2025

@kevinrushforth
After several rounds of revision, I think the API is in a good shape by now. Do you have a chance to take a look at it, so that I can continue with the CSR?

@kevinrushforth
Copy link
Member

@kevinrushforth After several rounds of revision, I think the API is in a good shape by now. Do you have a chance to take a look at it, so that I can continue with the CSR?

Yes, I'll put it on my review queue for this week.

@credmond
Copy link

credmond commented Apr 29, 2025

An issue whereby the top border was not showing re-size cursors when using EXTENDED + HeaderBar, was fixed in 544d62e

However, a similar bug is still present when using EXTENDED with no HeaderBar, these cursors are missing and it's not possible to re-size using the top border/corners. Easy to reproduce in Monkey Tester.

It's also impossible to click and drag the window from the header area, in this configuration, which felt like non-native behaviour that could be enabled by default. Maybe it's intentional.

@mstr2
Copy link
Collaborator Author

mstr2 commented Apr 29, 2025

However, a similar bug is still present when using EXTENDED with no HeaderBar, these cursors are missing and it's not possible to re-size using the top border/corners. Easy to reproduce in Monkey Tester.

It's also impossible to click and drag the window from the header area, in this configuration, which felt like non-native behaviour that could be enabled by default. Maybe it's intentional.

I don't think EXTENDED without HeaderBar is a supported configuration. The header bar defines the draggable area, and without it, the window doesn't seem to be useful (this would be a resizable but non-movable window). Do you have a specific use case in mind?

@credmond
Copy link

However, a similar bug is still present when using EXTENDED with no HeaderBar, these cursors are missing and it's not possible to re-size using the top border/corners. Easy to reproduce in Monkey Tester.
It's also impossible to click and drag the window from the header area, in this configuration, which felt like non-native behaviour that could be enabled by default. Maybe it's intentional.

I don't think EXTENDED without HeaderBar is a supported configuration. The header bar defines the draggable area, and without it, the window doesn't seem to be useful (this would be a resizable but non-movable window). Do you have a specific use case in mind?

No use case (that I care about), but I was able to create it (Monkey Tester), and it had the minimise/maximise/quit buttons, so it seemed like a "valid" minimalist setup.

@credmond
Copy link

Another question, can there be some defaults set to prevent this (icons overlapping controls when window is small):

image

E.g., look what happens when I make IntelliJ as small as possible -- the windowing icons are always visible, never overlap:

image

Same with most apps such as Chrome, etc... everything else shrinks (tabs), but not the windowing icons.

I'm sure a developer can workaround this (I haven't tinkered yet), but it would be good if they didn't need to worry about it...?

@mstr2
Copy link
Collaborator Author

mstr2 commented May 2, 2025

Several people have commented on an artifact that can happen when the window is resized down below the minimum size of the header bar. In this case, the default window buttons will be drawn over the header bar content. Obviously, this doesn't look good.

I've experimented with a potential solution, where I added a new HeaderBar.clipSystemInset boolean property. If set to true, the content of the header bar is clipped to always exclude the side of the header bar that contains the window buttons (the red rectangle indicates the clipping rect):

This works, but there are a few aspects to consider:

  1. Clipping the header bar content requires me to put the content inside another container, and then clip this inner container. So the structure of the header bar looks like this: HeaderBar -> ContentContainer -> [children]. This is unusual for a layout container, which usually doesn't have an internal substructure.
  2. The clipping approach works in trivial cases, but it doesn't work as easily in more complex cases. Consider an application like Chrome, where the header bar could conceivably be the entire region marked with a red rectangle:
    The naive approach of clipping away the entire side of the header bar that contains the default window buttons wouldn't work here. We would probably need a more complex clipping shape that fits the design semantics of the application's header bar.
  3. Then there's the question of whether we really need this feature. It adds to the complexity of the implementation, and its value is debatable: an application should be designed so it doesn't need to rely on naive content clipping. It should define an appropriate minimum window size, and the window contents should gracefully adapt to small window sizes.

In fact, Chrome doesn't clip its header bar content, but scales it down to small icons and then prevents sizing the window below a reasonable minimum size. The same is true for Windows Explorer, Notepad, Spotify, and many other apps. The only application that comes to mind that clips its header bar content is IntelliJ.

@drmarmac
Copy link
Contributor

drmarmac commented May 2, 2025

Would it be feasible to ensure the minium window width is large enough by default, and allowing to turn this off when manual clipping is implemented instead?

@credmond
Copy link

credmond commented May 2, 2025

Apologies on my comment on the subject, I had not realised until after, that it was already discussed.

I would agree that "naive" clipping is probably more confusing than intuitive.

Anyway, note that even Windows Explorer doesn't do a great job.

E.g., depending on how fast you re-size the Window (in this case, with two tabs open), you can get this:

image

...or this if you resize slower (no indication that two tabs are open -- but less "ugly"):

image

I also prefer your suggestion that there should simply be a minimum window size.

But I can imagine a lot of people will want a menu in the top header bar; some maybe with too many items to be reasonably fit into a "minimum" size.

Look at SceneBuilder (which has no min size), when resized, the menu items all get ellipsed -- not beautiful but at least it's something:

image

Would this behave similarly in HeaderBar, or would it stay fullsize, and the window icons overlay it? (I am unable to test it at the moment)

@mstr2
Copy link
Collaborator Author

mstr2 commented May 2, 2025

Would it be feasible to ensure the minium window width is large enough by default, and allowing to turn this off when manual clipping is implemented instead?

It's probably a bit hard with the API around Stage.minWidth and Stage.minHeight. Deriving a minimum size from the window content is probably easy (just measure the minimum size of the root node), but currently the stage doesn't reach into the scene graph to set its minimum size. The problem is also not exclusive to HeaderBar, as the window can currently be resized below the minimum size of the rest of the scene graph, too. Maybe we could have an option for Stage to always be large enough to accomodate the minimum scene graph size.

@mstr2
Copy link
Collaborator Author

mstr2 commented May 2, 2025

But I can imagine a lot of people will want a menu in the top header bar; some maybe with too many items to be reasonably fit into a "minimum" size.

Look at SceneBuilder (which has no min size), when resized, the menu items all get ellipsed -- not beautiful but at least it's something:

Would this behave similarly in HeaderBar, or would it stay fullsize, and the window icons overlay it? (I am unable to test it at the moment)

MenuBar seems to just stay full size. Maybe the best option would be to enhance MenuBar with an overflow area, where its menus will appear when there's not enough space to lay the menus out next to each other?

@drmarmac
Copy link
Contributor

drmarmac commented May 2, 2025

Would it be feasible to ensure the minium window width is large enough by default, and allowing to turn this off when manual clipping is implemented instead?

It's probably a bit hard with the API around Stage.minWidth and Stage.minHeight. Deriving a minimum size from the window content is probably easy (just measure the minimum size of the root node), but currently the stage doesn't reach into the scene graph to set its minimum size. The problem is also not exclusive to HeaderBar, as the window can currently be resized below the minimum size of the rest of the scene graph, too. Maybe we could have an option for Stage to always be large enough to accomodate the minimum scene graph size.

Ok, that would need to be done elsewhere then.
I'd be fine with either adding documentation that this window size issue needs to be handled somehow by the app, or going with the automatic content clipping with the HeaderBar.clipSystemInset flag as proposed, which can be turned off when the more complex cases arise.

@kevinrushforth
Copy link
Member

The API looks good to me. I want to do a second pass over the javadocs once I have time to try a few things, but I doubt I'll request any significant changes.

Go ahead and create the CSR when you are ready.

@credmond
Copy link

credmond commented May 2, 2025

I imagine most people will want HeaderBar's leading node to be an app icon with the usual left-click context menu behaviour (i.e., at least restore, minimise, close), much like it is for DECORATED today.

Today, you can add a bunch of icons to a stage with stage.getIcons().addAll(myLogoList)), and the window's header will contain a suitable icon; never too big, never too small. The developer doesn't worry about it.

I really feel a utility to add an equivalent app icon to the HeaderBar, should be provided out-of-the-box, using the same logic. E.g., let HeaderBar choose the most suitable icon, and choose the correct size for the platform, and any padding, etc. Otherwise, the developer needs to think about it too much.

A lot of this logic used by DECORATED, seems to be buried in WindowStage in terms of picking what icons to display in the title bar, etc.

@mstr2
Copy link
Collaborator Author

mstr2 commented May 3, 2025

@credmond
I think we run the risk of overloading HeaderBar with too many narrow features. Here are a few observations:

  1. Modern applications tend to not use program icons. Windows Explorer, Calendar, Weather, Settings, and also Spotify or Chrome don't use it. Program icons have become more of a Win32-era relict, and it's mostly applications that use system-decorated windows that have it.
  2. Program icons are really only a thing on Windows. It doesn't seem to make much sense to have a Windows-only API on a multi-platform toolkit. It certainly doesn't work with macOS, as we have the traffic lights in front of the leading node of the HeaderBar.
  3. It's very easy to create a program icon with standard JavaFX controls. Since its size will never change, you can just use a static image with an ImageView and a ContextMenu. JavaFX already supports multi-resolution images out of the box (with the @Nx file name convention).
  4. If you choose to have a program icon, you most likely want it to align nicely with the rest of your custom header bar. Even in apps that have a program icon, it's not a one-size-fits-all (for example, the IntelliJ program icon looks significantly different than a standard system-decorated program icon). Once you start to customize the program icon, you're really better off just using standard JavaFX controls to do the job.

@credmond
Copy link

credmond commented May 3, 2025

Thanks for the detailed reply @mstr2. I can't really argue with any of your points, I also agree.

@credmond
Copy link

credmond commented May 3, 2025

I was testing the HeaderBar, and noticed the styles of the windows buttons were sometimes not applied, or were getting stuck in a particular state (i.e., hover state). I thought I tracked this down to a benign issue where my app was applying duplicate stylesheets, in quick succession (i.e., twice) -- it should not matter usually, at all, but for some reason affected the window decoration icons in particular.

I fixed that edge-case issue. However, I then quickly noticed the styling will stop being applied after using a common controlsfx notification popup. It can be very easily reproduced:

package com.certak.kafkio.gui;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HeaderBar;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import org.controlsfx.control.Notifications;

import java.time.LocalDateTime;

public class WindowDecorationsStylesGoMissing extends Application {

    @Override
    public void start(Stage primaryStage) {
        primaryStage.initStyle(StageStyle.EXTENDED);
        BorderPane borderPane = new BorderPane();
        borderPane.setTop(new HeaderBar());

        Button click = new Button("Click");
        click.setOnAction(event -> {
            notify("Clicked at: " + LocalDateTime.now(), primaryStage);
        });
        borderPane.setCenter(click);

        Scene scene = new Scene(borderPane, 400, 300);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public void notify(String message, Stage mainWindow) {
        Notifications.create()
                .text(message)
                .owner(mainWindow)
                .show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

Run this, click the button and hover over the window icons, and see that no styling is applied anymore. I have not dived into this yet to understand why, but I know from memory that controlsfx does some re-arranging of nodes in scene usually.

Either way, the styling of HeaderBar seems quite unreliable/brittle given the various ways it can seemingly break. I don't know why, or what makes it different. But the other styles in my app appear unaffected by application bugs and/or quirks of controlsfx.

@credmond
Copy link

credmond commented May 3, 2025

Actually, it can easily be reproduced with plain Java + JavaFX; no libraries.

Turns out, the issue is down to doing:

primaryStage.getScene().getStylesheets().addFirst(stylesheetUrl);

package com.certak.kafkio.gui;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HeaderBar;
import javafx.stage.Stage;
import javafx.stage.StageStyle;

import java.util.Objects;

public class WindowDecorationsStylesGoMissing extends Application {

    @Override
    public void start(Stage primaryStage) {
        primaryStage.initStyle(StageStyle.EXTENDED);
        BorderPane borderPane = new BorderPane();
        borderPane.setTop(new HeaderBar());

        Button click = new Button("Click");
        click.setOnAction(event -> {
            String stylesheetUrl = Objects.requireNonNull(AppSplashScreen.class.getResource("splash.css")).toExternalForm();
            primaryStage.getScene().getStylesheets().addFirst(stylesheetUrl);
        });
        borderPane.setCenter(click);

        Scene scene = new Scene(borderPane, 400, 300);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

@credmond
Copy link

credmond commented May 4, 2025

An even more concise example. Create c:\empty.css and run this, then hover over the window icons and note no style is applied (or is partially applied):

 @Override
    public void start(Stage primaryStage) {
        BorderPane borderPane = new BorderPane();
        borderPane.setTop(new HeaderBar());
        Scene scene = new Scene(borderPane, 400, 300);
        primaryStage.setScene(scene);
        primaryStage.initStyle(StageStyle.EXTENDED);
        primaryStage.show();
        // Calling this once shown, breaks the window decoration (minimise/restore/close) CSS
        scene.getStylesheets().add(Paths.get("C:\\empty.css").toUri().toString());
        
        // Even reproducible on a non-existing file
        // scene.getStylesheets().add(Paths.get("C:\\i_dont_even_need_to_exist.css").toUri().toString());
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
csr Need approved CSR to integrate pull request rfr Ready for review
Development

Successfully merging this pull request may close these issues.