jfx
jfx copied to clipboard
8313424: JavaFX controls in the title bar
This PR is a new take on a highly requested feature: JavaFX controls in the header bar (see also #594 for an earlier iteration).
This is a feature with many possible ways to skin the cat, and it has taken quite a bit of effort to come up with a good user model. In contrast to the previous iteration, the focus has shifted from providing an entirely undecorated window to providing a window with a user-configurable header bar.
The customizable header bar is a new layout container: javafx.scene.layout.HeaderBar. It has three areas that accept child nodes: leading, center, and trailing. HeaderBar also automatically adjusts for the placement of the default window buttons (minimize, maximize, close) on the left or right side of the window.
The customizable header bar is combined with a new EXTENDED stage style, which extends the client area into the header bar area. The new extended stage style is supported on Windows, macOS, and Linux. For platforms that don't support this stage style, it automatically downgrades to DECORATED.
This is how it looks like on each of the three operating systems:
The window buttons (minimize, maximize, close) are provided by JavaFX, not by the application developer. This makes it easier to get basic window functionality without recreating the entirety of the window controls for all platforms.
Usage
This is a minimal example that uses a custom header bar with a TextField in the center area. HeaderBar is usually placed in the top area of a BorderPane root container:
public class MyApp extends Application {
@Override
public void start(Stage stage) {
var headerBar = new HeaderBar();
headerBar.setCenter(new TextField());
var root = new BorderPane();
root.setTop(headerBar);
stage.setScene(new Scene(root));
stage.initStyle(StageStyle.EXTENDED);
stage.show();
}
}
To learn more about the details of the API, refer to the documentation of StageStyle.EXTENDED and HeaderBar.
Platform integration
The implementation varies per platform, and ranges from pretty easy to quite involved:
- macOS: The window buttons are provided by macOS, we just leave an empty area where the window buttons will appear. The client area is extended to cover the entire window by setting the
NSWindowStyleMaskFullSizeContentViewflag. A click-and-drag operation is initiated withperformWindowDragWithEvent.
For both Windows and Linux, there is no practical way to draw custom content into the system-provided title bar, and it is also not an option to overlay the system-provided window buttons on top of a custom window. Both implementations therefore hide the system title bar, and draw a JavaFX recreation of the system window buttons into an overlay layer on top of the window. Since there is no title bar, the entire window is available for JavaFX.
- Windows: We get a "barebones" window by handling
WM_NCCALCSIZEto extend the client area, andWM_NCHITTESTto get click-and-drag and snap layouts. The system-provided resize border around the window, shadows under the window, and window animations are retained (this differentiates it from anUNDECORATEDwindow). - Linux: Since GTK really doesn't want to share rendering with JavaFX, we can't use
GtkHeaderBarto get the default window buttons. In contrast to Windows, which has only one look-and-feel, Linux has many. We ship two versions of window buttons: one that looks Gnome-like, and one that looks KDE-like. JavaFX then chooses the version that fits best. Making matters more complicated, an undecorated GTK window has no system-provided resize border. We therefore need to introduce a virtual resize border around the inside of the window, and handle click-and-drag and resizing withgtk_window_begin_move_drag()andgtk_window_begin_resize_drag(), respectively.
Final thoughts
This feature is quite complex, and it introduces APIs that are hard to change after introduction (like the HeaderBar layout container). We should consider making this a preview feature.
Progress
- [x] Change must not contain extraneous whitespace
- [ ] Change requires a CSR request matching fixVersion jfx24 to be approved (needs to be created)
- [x] Commit message must refer to an issue
- [ ] Change must be properly reviewed (2 reviews required, with at least 2 Reviewers)
Issue
- JDK-8313424: JavaFX controls in the title bar (Enhancement - P4)
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
Webrev
:wave: 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.
@mstr2 This change now passes all automated pre-integration checks.
ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details.
After integration, the commit message for the final commit will be:
8313424: JavaFX controls in the title bar (Preview)
Reviewed-by: angorya, mmack, kcr
You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed.
At the time when this comment was updated there had been no new commits pushed to the master branch. If another commit should be pushed before you perform the /integrate command, your PR will be automatically rebased. If you prefer to avoid any potential automatic rebasing, please check the documentation for the /integrate command for further details.
➡️ To integrate this PR with the above commit message to the master branch, type /integrate in a new comment.
Webrevs
- 85: Full - Incremental (4a7e57f5)
- 84: Full - Incremental (0bb24e18)
- 83: Full (3fdc465f)
- 82: Full - Incremental (cdb24bce)
- 81: Full - Incremental (96a0d796)
- 80: Full - Incremental (e17fe8bf)
- 79: Full - Incremental (56868485)
- 78: Full - Incremental (06ce89a4)
- 77: Full - Incremental (3b8fd536)
- 76: Full (b947e33f)
- 75: Full (b85f72f3)
- 74: Full (dcbb68d0)
- 73: Full - Incremental (fe7b6c7a)
- 72: Full (e09adfe5)
- 71: Full (18ab411d)
- 70: Full - Incremental (c602f504)
- 69: Full - Incremental (544d62eb)
- 68: Full (6a2f05d6)
- 67: Full - Incremental (d1aa6b3b)
- 66: Full - Incremental (7dc8f5f1)
- 65: Full (394a039a)
- 64: Full - Incremental (379c01ec)
- 63: Full - Incremental (e9aeaefd)
- 62: Full (3622b05b)
- 61: Full - Incremental (a3497b26)
- 60: Full - Incremental (63215fc8)
- 59: Full (88e50163)
- 58: Full - Incremental (50f9c75a)
- 57: Full - Incremental (a2b5df12)
- 56: Full - Incremental (f8704937)
- 55: Full - Incremental (30eb29b7)
- 54: Full - Incremental (8a5defde)
- 53: Full (0985e9ce)
- 52: Full - Incremental (df90ca13)
- 51: Full (e6e81001)
- 50: Full - Incremental (394c95f2)
- 49: Full - Incremental (cd9c2a94)
- 48: Full - Incremental (1f95e9db)
- 47: Full (00b9be76)
- 46: Full - Incremental (c30eb603)
- 45: Full - Incremental (7cb591fa)
- 44: Full - Incremental (600c721b)
- 43: Full - Incremental (cadf1c2c)
- 42: Full - Incremental (3efd7827)
- 41: Full - Incremental (e874d21a)
- 40: Full - Incremental (2ab12947)
- 39: Full - Incremental (fcec7c8a)
- 38: Full - Incremental (356a78ef)
- 37: Full (49a81d32)
- 36: Full (cbb32165)
- 35: Full - Incremental (eaafd9f0)
- 34: Full - Incremental (8d5d7b87)
- 33: Full - Incremental (65230730)
- 32: Full (af35dce8)
- 31: Full - Incremental (8649f917)
- 30: Full (743626f2)
- 29: Full (485a9d95)
- 28: Full - Incremental (003e9d56)
- 27: Full (26b81b85)
- 26: Full (65f095ef)
- 25: Full (6a165368)
- 24: Full - Incremental (4336735a)
- 23: Full - Incremental (8e77a220)
- 22: Full - Incremental (ca9b325a)
- 21: Full - Incremental (8974c141)
- 20: Full - Incremental (d1c388bf)
- 19: Full - Incremental (e7febc54)
- 18: Full (9b63892d)
- 17: Full (15dc3ffb)
- 16: Full - Incremental (1c4ecc11)
- 15: Full - Incremental (f5e3121f)
- 14: Full - Incremental (804d0be7)
- 13: Full - Incremental (cd5d4434)
- 12: Full - Incremental (d7f88c33)
- 11: Full - Incremental (d9c0fe2e)
- 10: Full - Incremental (95736df7)
- 09: Full - Incremental (3b468fe5)
- 08: Full - Incremental (778e6c1f)
- 07: Full - Incremental (fef8cfc0)
- 06: Full - Incremental (f02e7e90)
- 05: Full - Incremental (f973e8c5)
- 04: Full - Incremental (ba02e8f7)
- 03: Full - Incremental (a2895725)
- 02: Full - Incremental (79696225)
- 01: Full - Incremental (0ddd63d4)
- 00: Full (3d19b872)
/reviewers 2 reviewers /csr
@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).
@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.
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.
Very nice. I'll test on Linux and report back.
Hey, I'm glad to see this PR, but do you have any ideas for continuing to provide
UNDECORATED_INTERACTIVEin the future? I thinkUNDECORATED_INTERACTIVEis more useful thanEXTENDEDfor 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.
A few points observed on Linux:
- 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. - It's not possible to move the window if the cursor is over a control. Maybe you should just
gtk_window_begin_move_dragwhen a drag is detected, not on click. That would be onWindowContextBase::process_mouse_motion; - 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 - Alt + F8 is working (it's a desktop shortcut for resizing the window) - just pointing out to include in manual testing;
- I think rounded edges should be supported on the HeaderBar, since it's the default on gnome. For that to work,
EXTENDEDshould be also transparent on Linux. It would loose the window drop shadow which can be added on the JavaFX side.gdk_window_set_shadow_widthshould 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.
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?
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:
- CSS support:
HeaderBaris a normal part of the scene graph, so it fully supports CSS styling. - Height limitation: the height of
HeaderBaris user-configurable, just like any layout container. It can be as large as you want. - User-defined color/accents/transparency: Again, since
HeaderBaris 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.
Popular demand is good, but I would like to hear from the tech leads and the maintainers.
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?
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:
- A resize border.
- Minimize, maximize, and close buttons (this includes all states these buttons can be in: available/deactivated/disabled).
- Click-and-drag on the header bar.
- 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:
~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.
@tsayao Thanks for the comments. I'll have a look at the bugs that you found.
- It's not possible to move the window if the cursor is over a control. Maybe you should just
gtk_window_begin_move_dragwhen a drag is detected, not on click. That would be onWindowContextBase::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).
- I think rounded edges should be supported on the HeaderBar, since it's the default on gnome. For that to work,
EXTENDEDshould be also transparent on Linux. It would loose the window drop shadow which can be added on the JavaFX side.gdk_window_set_shadow_widthshould 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).
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>
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
Nautilus
Chrome
Amberol (music player).
App Center (Ubuntu Software Store) - This one uses flutter
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.
Adding some more examples.
Vivaldi browser actually allows you to switch between two modes:
which can be switched with
to
So in the first mode, the menu is compressed vertically into the Vivaldi button.
Here is Discord:
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.
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?
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?
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?
Gtk does work on Mac and Windows, maybe we can see how it handles it's HeaderBar, for reference.
Another aspect is whether this should be a conditional feature. If not, how will it be supported on Android/iOS? RaspPI?
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:
-
EXTENDEDis likeUNDECORATEDin 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 ownHeaderBarand their own system menu (if they so desire). -
EXTENDEDis likeDECORATEDin 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.
Another aspect is whether this should be a conditional feature. If not, how will it be supported on Android/iOS? RaspPI?
I answered this question in the opening post of this PR.
StageStyle.EXTENDED is a conditional feature that downgrades to StageStyle.DECORATED on platforms that don't support it.
@mstr2 thank you for clarifications.
You are proposing a substantial change, I think it needs a JEP to communicate the new feature in a more human-oriented fashion. I think everyone will benefit from that, not only the reviewers.
@mstr2 thank you for clarifications.
You are proposing a substantial change, I think it needs a JEP to communicate the new feature in a more human-oriented fashion. I think everyone will benefit from that, not only the reviewers.
I'll prepare a document for that.
A few points observed on Linux:
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.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
These bugs are now fixed.
I've found bug on resize cursors. Tried to investigate it but didn't find an reasonable explanation:
If you go do a resize border triggering the cursor change and then move the cursor through the window without clicking to resize, the cursor will remain in the resize state. Even weirder, if the window is placed above a text editor, for example, the cursor will inherit from the window behind.