From 3d19b872f5ffaec6b326c11f91d7302af4a3bc0b Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Sat, 19 Oct 2024 21:33:32 +0200 Subject: [PATCH 01/73] WIP --- buildSrc/win.gradle | 4 +- .../test/java/test/util/ReflectionUtils.java | 41 ++ .../java/com/sun/glass/events/MouseEvent.java | 33 +- .../java/com/sun/glass/ui/Application.java | 6 + .../com/sun/glass/ui/NonClientHandler.java | 43 ++ .../src/main/java/com/sun/glass/ui/View.java | 34 +- .../main/java/com/sun/glass/ui/Window.java | 313 ++-------- .../sun/glass/ui/WindowControlsOverlay.java | 540 ++++++++++++++++++ .../sun/glass/ui/WindowOverlayMetrics.java | 45 ++ .../com/sun/glass/ui/gtk/GtkApplication.java | 5 + .../java/com/sun/glass/ui/gtk/GtkWindow.java | 70 ++- .../glass/ui/gtk/PlatformThemeObserver.java | 64 +++ .../glass/ui/gtk/WindowDecorationTheme.java | 92 +++ .../com/sun/glass/ui/gtk/WindowManager.java | 76 +++ .../com/sun/glass/ui/ios/IosApplication.java | 5 + .../com/sun/glass/ui/mac/MacApplication.java | 5 + .../java/com/sun/glass/ui/mac/MacWindow.java | 50 +- .../glass/ui/monocle/MonocleApplication.java | 5 + .../com/sun/glass/ui/win/WinApplication.java | 5 + .../java/com/sun/glass/ui/win/WinWindow.java | 104 +++- .../java/com/sun/javafx/scene/NodeHelper.java | 11 + .../main/java/com/sun/javafx/tk/TKScene.java | 8 +- .../com/sun/javafx/tk/TKSceneListener.java | 11 +- .../tk/quantum/GlassViewEventHandler.java | 14 +- .../sun/javafx/tk/quantum/OverlayWarning.java | 46 +- .../tk/quantum/OverlayWarningHelper.java | 79 --- .../sun/javafx/tk/quantum/QuantumToolkit.java | 2 + .../com/sun/javafx/tk/quantum/ViewScene.java | 74 ++- .../javafx/tk/quantum/ViewSceneOverlay.java | 110 ++++ .../sun/javafx/tk/quantum/WindowStage.java | 46 +- .../main/java/com/sun/javafx/util/Utils.java | 30 +- .../application/ConditionalFeature.java | 11 +- .../src/main/java/javafx/scene/Node.java | 10 + .../src/main/java/javafx/scene/Scene.java | 39 ++ .../java/javafx/scene/layout/HeaderBar.java | 483 ++++++++++++++++ .../javafx/scene/layout/HeaderBarBase.java | 191 +++++++ .../main/java/javafx/stage/StageStyle.java | 45 +- .../native-glass/gtk/GlassApplication.cpp | 3 +- .../src/main/native-glass/gtk/GlassWindow.cpp | 5 +- .../main/native-glass/gtk/glass_general.cpp | 6 +- .../src/main/native-glass/gtk/glass_general.h | 5 +- .../main/native-glass/gtk/glass_window.cpp | 183 +++++- .../src/main/native-glass/gtk/glass_window.h | 13 +- .../src/main/native-glass/ios/GlassWindow.m | 2 +- .../main/native-glass/mac/GlassViewDelegate.h | 4 +- .../main/native-glass/mac/GlassViewDelegate.m | 5 + .../src/main/native-glass/mac/GlassWindow.m | 60 +- .../src/main/native-glass/win/GlassWindow.cpp | 178 +++++- .../src/main/native-glass/win/GlassWindow.h | 9 +- .../src/main/native-glass/win/Utils.h | 3 + .../main/native-glass/win/ViewContainer.cpp | 124 +++- .../src/main/native-glass/win/ViewContainer.h | 1 + .../src/main/native-glass/win/common.h | 4 +- .../glass/ui/gtk/WindowDecorationGnome.css | 126 ++++ .../sun/glass/ui/gtk/WindowDecorationKDE.css | 111 ++++ .../com/sun/glass/ui/win/WindowDecoration.css | 112 ++++ modules/javafx.graphics/src/test/addExports | 2 + .../glass/ui/WindowControlsOverlayTest.java | 349 +++++++++++ .../test/com/sun/javafx/util/UtilsTest.java | 48 ++ .../javafx/scene/layout/HeaderBarTest.java | 438 ++++++++++++++ 60 files changed, 4055 insertions(+), 486 deletions(-) create mode 100644 modules/javafx.graphics/src/main/java/com/sun/glass/ui/NonClientHandler.java create mode 100644 modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java create mode 100644 modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowOverlayMetrics.java create mode 100644 modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/PlatformThemeObserver.java create mode 100644 modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/WindowDecorationTheme.java create mode 100644 modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/WindowManager.java delete mode 100644 modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/OverlayWarningHelper.java create mode 100644 modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/ViewSceneOverlay.java create mode 100644 modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java create mode 100644 modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java create mode 100644 modules/javafx.graphics/src/main/resources/com/sun/glass/ui/gtk/WindowDecorationGnome.css create mode 100644 modules/javafx.graphics/src/main/resources/com/sun/glass/ui/gtk/WindowDecorationKDE.css create mode 100644 modules/javafx.graphics/src/main/resources/com/sun/glass/ui/win/WindowDecoration.css create mode 100644 modules/javafx.graphics/src/test/java/test/com/sun/glass/ui/WindowControlsOverlayTest.java create mode 100644 modules/javafx.graphics/src/test/java/test/javafx/scene/layout/HeaderBarTest.java diff --git a/buildSrc/win.gradle b/buildSrc/win.gradle index 83a97645a27..db34da5ad61 100644 --- a/buildSrc/win.gradle +++ b/buildSrc/win.gradle @@ -324,9 +324,9 @@ WIN.glass.rcFlags = [ WIN.glass.ccFlags = [ccFlags].flatten() WIN.glass.linker = linker WIN.glass.linkFlags = (IS_STATIC_BUILD ? [linkFlags] : [linkFlags, "delayimp.lib", "gdi32.lib", "urlmon.lib", "Comdlg32.lib", - "winmm.lib", "imm32.lib", "shell32.lib", "Uiautomationcore.lib", "dwmapi.lib", + "winmm.lib", "imm32.lib", "shell32.lib", "Uiautomationcore.lib", "dwmapi.lib", "uxtheme.lib", "/DELAYLOAD:user32.dll", "/DELAYLOAD:urlmon.dll", "/DELAYLOAD:winmm.dll", "/DELAYLOAD:shell32.dll", - "/DELAYLOAD:Uiautomationcore.dll", "/DELAYLOAD:dwmapi.dll"]).flatten() + "/DELAYLOAD:Uiautomationcore.dll", "/DELAYLOAD:dwmapi.dll", "/DELAYLOAD:uxtheme.dll"]).flatten() WIN.glass.lib = "glass" WIN.decora = [:] diff --git a/modules/javafx.base/src/test/java/test/util/ReflectionUtils.java b/modules/javafx.base/src/test/java/test/util/ReflectionUtils.java index 0e4793c5983..838fb8de323 100644 --- a/modules/javafx.base/src/test/java/test/util/ReflectionUtils.java +++ b/modules/javafx.base/src/test/java/test/util/ReflectionUtils.java @@ -26,10 +26,14 @@ package test.util; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.function.Function; public final class ReflectionUtils { + private ReflectionUtils() {} + /** * Returns the value of a potentially private field of the specified object. * The field can be declared on any of the object's inherited classes. @@ -61,4 +65,41 @@ public static Object getFieldValue(Object object, String fieldName) { throw new AssertionError("Field not found: " + fieldName); } + + /** + * Invokes the specified method on the object, and returns a value. + * The method can be declared on any of the object's inherited classes. + * + * @param object the object on which the method will be invoked + * @param methodName the method name + * @param args the arguments + * @return the return value + */ + public static Object invokeMethod(Object object, String methodName, Class[] parameterTypes, Object... args) { + Function, Method> getMethod = cls -> { + try { + var method = cls.getDeclaredMethod(methodName, parameterTypes); + method.setAccessible(true); + return method; + } catch (NoSuchMethodException e) { + return null; + } + }; + + Class cls = object.getClass(); + while (cls != null) { + Method method = getMethod.apply(cls); + if (method != null) { + try { + return method.invoke(object, args); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new AssertionError(e); + } + } + + cls = cls.getSuperclass(); + } + + throw new AssertionError("Method not found: " + methodName); + } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/events/MouseEvent.java b/modules/javafx.graphics/src/main/java/com/sun/glass/events/MouseEvent.java index d1cfe4370e1..b9a5651db14 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/events/MouseEvent.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/events/MouseEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,6 +24,8 @@ */ package com.sun.glass.events; +import com.sun.glass.ui.WindowControlsOverlay; +import javafx.stage.StageStyle; import java.lang.annotation.Native; public class MouseEvent { @@ -49,4 +51,33 @@ public class MouseEvent { * This identifier is required for internal purposes. */ @Native final static public int WHEEL = 228; + + /** + * Non-client events are only natively produced on the Windows platform as a result of + * handling the {@code WM_NCHITTEST} message for an {@link StageStyle#EXTENDED} window. + *

+ * They are never sent to applications, but are processed by {@link WindowControlsOverlay}. + */ + @Native final static public int NC_DOWN = 230; + @Native final static public int NC_UP = 231; + @Native final static public int NC_DRAG = 232; + @Native final static public int NC_MOVE = 233; + @Native final static public int NC_ENTER = 234; + @Native final static public int NC_EXIT = 235; + + public static boolean isNonClientEvent(int event) { + return event >= NC_DOWN && event <= NC_EXIT; + } + + public static int toNonClientEvent(int event) { + return switch (event) { + case DOWN -> NC_DOWN; + case UP -> NC_UP; + case DRAG -> NC_DRAG; + case MOVE -> NC_MOVE; + case ENTER -> NC_ENTER; + case EXIT -> NC_EXIT; + default -> event; + }; + } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Application.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Application.java index 8d5f981030c..d0703da38ec 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Application.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Application.java @@ -714,6 +714,12 @@ public final boolean supportsUnifiedWindows() { return _supportsUnifiedWindows(); } + protected abstract boolean _supportsExtendedWindows(); + public final boolean supportsExtendedWindows() { + checkEventThread(); + return _supportsExtendedWindows(); + } + protected boolean _supportsSystemMenu() { // Overridden in subclasses return false; diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/NonClientHandler.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/NonClientHandler.java new file mode 100644 index 00000000000..d0a1dbf7da5 --- /dev/null +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/NonClientHandler.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.glass.ui; + +import javafx.stage.StageStyle; + +/** + * A non-client handler is used in some implementations of windows with the {@link StageStyle#EXTENDED} style. + * It can inspect a mouse event before it is sent to FX, and decide to consume it if it affects + * a non-client part of the window (for example, minimize/maximize/close buttons). + */ +public interface NonClientHandler { + + /** + * Handles the event. + * + * @return {@code true} if the event was handled, {@code false} otherwise + */ + boolean handleMouseEvent(int type, int button, int x, int y, int xAbs, int yAbs, int clickCount); +} diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/View.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/View.java index 5ee833e479b..97846bc8fa6 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/View.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/View.java @@ -365,6 +365,10 @@ public void handleSwipeGestureEvent(View view, long time, int type, int yAbs) { } + public boolean handleDragAreaHitTestEvent(double x, double y) { + return false; + } + public Accessible getSceneAccessible() { return null; } @@ -395,6 +399,7 @@ protected void _finishInputMethodComposition(long ptr) { */ private volatile long ptr; // Native handle (NSView*, or internal structure pointer) private Window window; // parent window + private NonClientHandler nonClientHandler; private EventHandler eventHandler; private int width = -1; // not set @@ -494,6 +499,12 @@ void setWindow(Window window) { this.window = window; _setParent(this.ptr, window == null ? 0L : window.getNativeHandle()); this.isValid = this.ptr != 0 && window != null; + + if (this.isValid && window.isExtendedWindow()) { + this.nonClientHandler = window.getNonClientHandler(); + } else { + this.nonClientHandler = null; + } } // package private @@ -913,15 +924,6 @@ protected void notifyMenu(int x, int y, int xAbs, int yAbs, boolean isKeyboardTr protected void notifyMouse(int type, int button, int x, int y, int xAbs, int yAbs, int modifiers, boolean isPopupTrigger, boolean isSynthesized) { - // gznote: optimize - only call for undecorated Windows! - if (this.window != null) { - // handled by window (programmatical move/resize) - if (this.window.handleMouseEvent(type, button, x, y, xAbs, yAbs)) { - // The evnet has been processed by Glass - return; - } - } - long now = System.nanoTime(); if (type == MouseEvent.DOWN) { View lastClickedView = View.lastClickedView == null ? null : View.lastClickedView.get(); @@ -945,6 +947,20 @@ protected void notifyMouse(int type, int button, int x, int y, int xAbs, lastClickedTime = now; } + // If we have a non-client handler, we give it the first chance to handle the event. + // Note that a full-screen window has no non-client area, and thus the non-client handler + // is not notified. + if (!inFullscreen + && nonClientHandler != null + && nonClientHandler.handleMouseEvent(type, button, x, y, xAbs, yAbs, clickCount)) { + return; + } + + // We never send non-client events to the application. + if (MouseEvent.isNonClientEvent(type)) { + return; + } + handleMouseEvent(now, type, button, x, y, xAbs, yAbs, modifiers, isPopupTrigger, isSynthesized); diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java index 20e28d47109..bbbcd2389eb 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java @@ -24,9 +24,10 @@ */ package com.sun.glass.ui; -import com.sun.glass.events.MouseEvent; import com.sun.glass.events.WindowEvent; import com.sun.prism.impl.PrismSettings; +import javafx.beans.value.ObservableValue; +import javafx.scene.Parent; import java.lang.annotation.Native; @@ -116,6 +117,7 @@ static protected void remove(Window window) { public static final int UNTITLED = 0; public static final int TITLED = 1 << 0; public static final int TRANSPARENT = 1 << 1; + public static final int EXTENDED = 1 << 2; // functional type: mutually exclusive /** @@ -130,7 +132,7 @@ static protected void remove(Window window) { * Often used for floating toolbars. It has smaller than usual decorations * and doesn't display a taskbar button. */ - @Native public static final int UTILITY = 1 << 2; + @Native public static final int UTILITY = 1 << 3; /** * A popup window. * @@ -138,18 +140,18 @@ static protected void remove(Window window) { * default it may display a task-bar button. To hide it the window must be * owned. */ - @Native public static final int POPUP = 1 << 3; + @Native public static final int POPUP = 1 << 4; // These affect window decorations as well as system menu actions, // so applicable to both decorated and undecorated windows - @Native public static final int CLOSABLE = 1 << 4; - @Native public static final int MINIMIZABLE = 1 << 5; - @Native public static final int MAXIMIZABLE = 1 << 6; + @Native public static final int CLOSABLE = 1 << 5; + @Native public static final int MINIMIZABLE = 1 << 6; + @Native public static final int MAXIMIZABLE = 1 << 7; /** * Indicates that the window trim will draw from right to left. */ - @Native public static final int RIGHT_TO_LEFT = 1 << 7; + @Native public static final int RIGHT_TO_LEFT = 1 << 8; /** * Indicates that a window will have a client area textured the same way as the platform decorations @@ -157,12 +159,12 @@ static protected void remove(Window window) { * This is supported not on all platforms, the client should check if the feature is supported by using * {@link com.sun.glass.ui.Application#supportsUnifiedWindows()} */ - @Native public static final int UNIFIED = 1 << 8; + @Native public static final int UNIFIED = 1 << 9; /** * Indicates that the window is modal which affects whether the window is minimizable. */ - @Native public static final int MODAL = 1 << 9; + @Native public static final int MODAL = 1 << 10; final static public class State { @Native public static final int NORMAL = 1; @@ -197,13 +199,11 @@ public static final class Level { private final int styleMask; private final boolean isDecorated; private final boolean isPopup; - private boolean shouldStartUndecoratedMove = false; protected View view = null; protected Screen screen = null; private MenuBar menubar = null; private String title = ""; - private UndecoratedMoveResizeHelper helper = null; private int state = State.NORMAL; private int level = Level.NORMAL; @@ -237,13 +237,14 @@ public static final class Level { protected abstract long _createWindow(long ownerPtr, long screenPtr, int mask); protected Window(Window owner, Screen screen, int styleMask) { Application.checkEventThread(); - switch (styleMask & (TITLED | TRANSPARENT)) { + switch (styleMask & (TITLED | TRANSPARENT | EXTENDED)) { case UNTITLED: case TITLED: case TRANSPARENT: + case EXTENDED: break; default: - throw new RuntimeException("The visual kind should be UNTITLED, TITLED, or TRANSPARENT, but not a combination of these"); + throw new RuntimeException("The visual kind should be UNTITLED, TITLED, TRANSPARENT, or EXTENDED, but not a combination of these"); } switch (styleMask & (POPUP | UTILITY)) { case NORMAL: @@ -254,6 +255,10 @@ protected Window(Window owner, Screen screen, int styleMask) { throw new RuntimeException("The functional type should be NORMAL, POPUP, or UTILITY, but not a combination of these"); } + if ((styleMask & UNIFIED) != 0 && (styleMask & EXTENDED) != 0) { + throw new RuntimeException("UNIFIED and EXTENDED cannot be combined"); + } + if (((styleMask & UNIFIED) != 0) && !Application.GetApplication().supportsUnifiedWindows()) { styleMask &= ~UNIFIED; @@ -264,7 +269,6 @@ protected Window(Window owner, Screen screen, int styleMask) { styleMask &= ~TRANSPARENT; } - this.owner = owner; this.styleMask = styleMask; this.isDecorated = (this.styleMask & Window.TITLED) != 0; @@ -375,9 +379,6 @@ public void setView(final View view) { // after we call view.setWindow(this); otherwise with UI scaling different than // 100% some platforms might display scenes wrong after Window was shown. _updateViewSize(this.ptr); - if (this.isDecorated == false) { - this.helper = new UndecoratedMoveResizeHelper(); - } } else { _setView(this.ptr, null); this.view = null; @@ -422,6 +423,33 @@ public void setMenuBar(final MenuBar menubar) { } } + /** + * Returns metrics of the window-provided overlay controls. + * + * @return the overlay metrics, or {@code null} if the window type does not support metrics + */ + public ObservableValue windowOverlayMetrics() { + return null; + } + + /** + * Returns the window-provided overlay controls, which are rendered above all application content. + * + * @return the overlay, or {@code null} if the window type does not provide overlay controls + */ + public Parent getWindowOverlay() { + return null; + } + + /** + * Returns the window-provided non-client event handler. + * + * @return the non-client event handler, or {@code null} + */ + public NonClientHandler getNonClientHandler() { + return null; + } + public boolean isDecorated() { Application.checkEventThread(); return this.isDecorated; @@ -674,6 +702,10 @@ public boolean isUnifiedWindow() { return (this.styleMask & Window.UNIFIED) != 0; } + public boolean isExtendedWindow() { + return (this.styleMask & Window.EXTENDED) != 0; + } + public boolean isTransparentWindow() { //The TRANSPARENT flag is set only if it is supported return (this.styleMask & Window.TRANSPARENT) != 0; @@ -1148,15 +1180,6 @@ public void setEventHandler(EventHandler eventHandler) { this.eventHandler = eventHandler; } - /** - * Enables unconditional start of window move operation when - * mouse is dragged in the client area. - */ - public void setShouldStartUndecoratedMove(boolean v) { - Application.checkEventThread(); - this.shouldStartUndecoratedMove = v; - } - // ***************************************************** // notification callbacks // ***************************************************** @@ -1214,11 +1237,6 @@ protected void notifyResize(final int type, final int width, final int height) { } this.width = width; this.height = height; - - // update moveRect/resizeRect - if (this.helper != null){ - this.helper.updateRectangles(); - } } handleWindowEvent(System.nanoTime(), type); @@ -1269,102 +1287,6 @@ protected void handleWindowEvent(long time, int type) { } } - // ***************************************************** - // programmatical move/resize - // ***************************************************** - /** Sets "programmatical move" rectangle. - * The rectangle is measured from top of the View: - * width is View.width, height is size. - * - * throws RuntimeException for decorated window. - */ - public void setUndecoratedMoveRectangle(int size) { - Application.checkEventThread(); - if (this.isDecorated == true) { - //throw new RuntimeException("setUndecoratedMoveRectangle is only valid for Undecorated Window"); - System.err.println("Glass Window.setUndecoratedMoveRectangle is only valid for Undecorated Window. In the future this will be hard error."); - Thread.dumpStack(); - return; - } - - if (this.helper != null) { - this.helper.setMoveRectangle(size); - } - } - /** The method called only for undecorated windows - * x, y: mouse coordinates (in View space). - * - * throws RuntimeException for decorated window. - */ - public boolean shouldStartUndecoratedMove(final int x, final int y) { - Application.checkEventThread(); - if (this.shouldStartUndecoratedMove == true) { - return true; - } - if (this.isDecorated == true) { - return false; - } - - if (this.helper != null) { - return this.helper.shouldStartMove(x, y); - } else { - return false; - } - } - - /** Sets "programmatical resize" rectangle. - * The rectangle is measured from top of the View: - * width is View.width, height is size. - * - * throws RuntimeException for decorated window. - */ - public void setUndecoratedResizeRectangle(int size) { - Application.checkEventThread(); - if ((this.isDecorated == true) || (this.isResizable == false)) { - //throw new RuntimeException("setUndecoratedMoveRectangle is only valid for Undecorated Resizable Window"); - System.err.println("Glass Window.setUndecoratedResizeRectangle is only valid for Undecorated Resizable Window. In the future this will be hard error."); - Thread.dumpStack(); - return; - } - - if (this.helper != null) { - this.helper.setResizeRectangle(size); - } - } - - /** The method called only for undecorated windows - * x, y: mouse coordinates (in View space). - * - * throws RuntimeException for decorated window. - */ - public boolean shouldStartUndecoratedResize(final int x, final int y) { - Application.checkEventThread(); - if ((this.isDecorated == true) || (this.isResizable == false)) { - return false; - } - - if (this.helper != null) { - return this.helper.shouldStartResize(x, y); - } else { - return false; - } - } - - /** Mouse event handler for processing programmatical resize/move - * (for undecorated windows only). - * Must be called by View. - * x & y are View coordinates. - * NOTE: it's package private! - * @return true if the event is processed by the window, - * false if it has to be delivered to the app - */ - boolean handleMouseEvent(int type, int button, int x, int y, int xAbs, int yAbs) { - if (this.isDecorated == false) { - return this.helper.handleMouseEvent(type, button, x, y, xAbs, yAbs); - } - return false; - } - @Override public String toString() { Application.checkEventThread(); @@ -1400,143 +1322,6 @@ protected void notifyLevelChanged(int level) { } } - private class UndecoratedMoveResizeHelper { - TrackingRectangle moveRect = null; - TrackingRectangle resizeRect = null; - - boolean inMove = false; // we are in "move" mode - boolean inResize = false; // we are in "resize" mode - - int startMouseX, startMouseY; // start mouse coords - int startX, startY; // start window location (for move) - int startWidth, startHeight; // start window size (for resize) - - UndecoratedMoveResizeHelper() { - this.moveRect = new TrackingRectangle(); - this.resizeRect = new TrackingRectangle(); - } - - void setMoveRectangle(final int size) { - this.moveRect.size = size; - - this.moveRect.x = 0; - this.moveRect.y = 0; - this.moveRect.width = getWidth(); - this.moveRect.height = this.moveRect.size; - } - - boolean shouldStartMove(final int x, final int y) { - return this.moveRect.contains(x, y); - } - - boolean inMove() { - return this.inMove; - } - - void startMove(final int x, final int y) { - this.inMove = true; - - this.startMouseX = x; - this.startMouseY = y; - - this.startX = getX(); - this.startY = getY(); - } - - void deltaMove(final int x, final int y) { - int deltaX = x - this.startMouseX; - int deltaY = y - this.startMouseY; - - setPosition(this.startX + deltaX, this.startY + deltaY); - } - - void stopMove() { - this.inMove = false; - } - - void setResizeRectangle(final int size) { - this.resizeRect.size = size; - - // set the rect (bottom right corner of the Window) - this.resizeRect.x = getWidth() - this.resizeRect.size; - this.resizeRect.y = getHeight() - this.resizeRect.size; - this.resizeRect.width = this.resizeRect.size; - this.resizeRect.height = this.resizeRect.size; - } - - boolean shouldStartResize(final int x, final int y) { - return this.resizeRect.contains(x, y); - } - - boolean inResize() { - return this.inResize; - } - - void startResize(final int x, final int y) { - this.inResize = true; - - this.startMouseX = x; - this.startMouseY = y; - - this.startWidth = getWidth(); - this.startHeight = getHeight(); - } - - void deltaResize(final int x, final int y) { - int deltaX = x - this.startMouseX; - int deltaY = y - this.startMouseY; - - setSize(this.startWidth + deltaX, this.startHeight + deltaY); - } - - protected void stopResize() { - this.inResize = false; - } - - void updateRectangles() { - if (this.moveRect.size > 0) { - setMoveRectangle(this.moveRect.size); - } - if (this.resizeRect.size > 0) { - setResizeRectangle(this.resizeRect.size); - } - } - - boolean handleMouseEvent(final int type, final int button, final int x, final int y, final int xAbs, final int yAbs) { - switch (type) { - case MouseEvent.DOWN: - if (button == MouseEvent.BUTTON_LEFT) { - if (shouldStartUndecoratedMove(x, y) == true) { - startMove(xAbs, yAbs); - return true; - } else if (shouldStartUndecoratedResize(x, y) == true) { - startResize(xAbs, yAbs); - return true; - } - } - break; - - case MouseEvent.MOVE: - case MouseEvent.DRAG: - if (inMove() == true) { - deltaMove(xAbs, yAbs); - return true; - } else if (inResize() == true) { - deltaResize(xAbs, yAbs); - return true; - } - break; - - case MouseEvent.UP: - boolean wasProcessed = inMove() || inResize(); - stopResize(); - stopMove(); - return wasProcessed; - } - return false; - } - } - /** * Requests text input in form of native keyboard for text component * contained by this Window. Native text input component is drawn on the place diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java new file mode 100644 index 00000000000..ab476f16424 --- /dev/null +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java @@ -0,0 +1,540 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.glass.ui; + +import com.sun.glass.events.MouseEvent; +import com.sun.javafx.util.Utils; +import javafx.application.Platform; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ObservableValue; +import javafx.css.CssMetaData; +import javafx.css.PseudoClass; +import javafx.css.SimpleStyleableBooleanProperty; +import javafx.css.SimpleStyleableIntegerProperty; +import javafx.css.SimpleStyleableObjectProperty; +import javafx.css.StyleConverter; +import javafx.css.Styleable; +import javafx.css.StyleableBooleanProperty; +import javafx.css.StyleableIntegerProperty; +import javafx.css.StyleableObjectProperty; +import javafx.css.StyleableProperty; +import javafx.geometry.Dimension2D; +import javafx.geometry.HPos; +import javafx.geometry.HorizontalDirection; +import javafx.geometry.NodeOrientation; +import javafx.geometry.VPos; +import javafx.scene.Node; +import javafx.scene.Scene; +import javafx.scene.layout.Region; +import javafx.scene.paint.Paint; +import javafx.stage.Stage; +import javafx.stage.StageStyle; +import javafx.util.Subscription; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Stream; + +/** + * Contains the visuals and behaviors for the minimize/maximize/close buttons on an {@link StageStyle#EXTENDED} + * window for platforms that use client-side decorations (Windows and Linux/GTK). This control supports + * left-to-right and right-to-left orientations, as well as a customizable layout order of buttons. + * + *

Substructure

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
CSS properties of {@code window-button-container}
CSS propertyValuesDefaultComment
-fx-button-placement[ left | right ]right + * Specifies the placement of the window buttons on the left or the right side of the window. + *
-fx-allow-rtl<boolean>true + * Specifies whether the minimize/maximize/close buttons support right-to-left orientations. + *
+ * + * + * + * + * + * + * + * + * + * + *
CSS properties of {@code minimize-button}, {@code maximize-button}, {@code close-button}
CSS propertyValuesDefaultComment
-fx-button-order<integer>0/1/2 + * Specifies the layout order of a button relative to the other buttons. + * Lower values are laid out before higher values.
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Conditional style classes of window buttons
Style classApplies toComment
.darkall buttons + * This style class will be present if the brightness of {@link Scene#fillProperty()} + * as determined by {@link Utils#calculateAverageBrightness(Paint)} is less than 0.5 + *
.restore{@code maximize-button} + * This style class will be present if {@link Stage#isMaximized()} is {@code true}
+ */ +public final class WindowControlsOverlay extends Region { + + private static final CssMetaData BUTTON_PLACEMENT_METADATA = + new CssMetaData<>("-fx-button-placement", + StyleConverter.getEnumConverter(HorizontalDirection.class), + HorizontalDirection.RIGHT) { + @Override + public boolean isSettable(WindowControlsOverlay overlay) { + return true; + } + + @Override + public StyleableProperty getStyleableProperty(WindowControlsOverlay overlay) { + return overlay.buttonPlacement; + } + }; + + private static final CssMetaData ALLOW_RTL_METADATA = + new CssMetaData<>("-fx-allow-rtl", StyleConverter.getBooleanConverter(), true) { + @Override + public boolean isSettable(WindowControlsOverlay overlay) { + return true; + } + + @Override + public StyleableProperty getStyleableProperty(WindowControlsOverlay overlay) { + return overlay.allowRtl; + } + }; + + private static final List> METADATA = + Stream.concat(getClassCssMetaData().stream(), + Stream.of(BUTTON_PLACEMENT_METADATA, ALLOW_RTL_METADATA)).toList(); + + private static final PseudoClass HOVER_PSEUDOCLASS = PseudoClass.getPseudoClass("hover"); + private static final PseudoClass PRESSED_PSEUDOCLASS = PseudoClass.getPseudoClass("pressed"); + private static final PseudoClass ACTIVE_PSEUDOCLASS = PseudoClass.getPseudoClass("active"); + private static final String DARK_STYLE_CLASS = "dark"; + private static final String RESTORE_STYLE_CLASS = "restore"; + + /** + * The metrics (placement and size) of the window buttons. + */ + private final ObjectProperty metrics = new SimpleObjectProperty<>( + this, "metrics", new WindowOverlayMetrics(HorizontalDirection.RIGHT, new Dimension2D(0, 0))); + + /** + * Specifies the placement of the window buttons on the left or the right side of the window. + *

+ * This property corresponds to the {@code -fx-button-placement} CSS property. + */ + private final StyleableObjectProperty buttonPlacement = + new SimpleStyleableObjectProperty<>( + BUTTON_PLACEMENT_METADATA, this, "buttonPlacement", HorizontalDirection.RIGHT) { + @Override + protected void invalidated() { + requestLayout(); + } + }; + + /** + * Specifies whether the minimize/maximize/close buttons support right-to-left orientations. + *

+ * If this property is {@code true} and the effective node orientation is right-to-left, the + * window buttons are mirrored to the other side of the window. + *

+ * This property corresponds to the {@code -fx-allow-rtl} CSS property. + */ + private final StyleableBooleanProperty allowRtl = + new SimpleStyleableBooleanProperty(ALLOW_RTL_METADATA, this, "allowRtl", true) { + @Override + protected void invalidated() { + requestLayout(); + } + }; + + /** + * Contains the buttons in the order as they will appear on the window. + * This list is automatically updated by the implementation of {@link ButtonRegion#buttonOrder}. + */ + private final List orderedButtons = new ArrayList<>(3); + private final ButtonRegion minimizeButton = new ButtonRegion(ButtonType.MINIMIZE, "minimize-button", 0); + private final ButtonRegion maximizeButton = new ButtonRegion(ButtonType.MAXIMIZE, "maximize-button", 1); + private final ButtonRegion closeButton = new ButtonRegion(ButtonType.CLOSE, "close-button", 2); + private final Subscription subscriptions; + + private Node buttonAtMouseDown; + + public WindowControlsOverlay(ObservableValue stylesheet) { + var stage = sceneProperty() + .flatMap(Scene::windowProperty) + .map(w -> w instanceof Stage ? (Stage)w : null); + + var focusedSubscription = stage + .flatMap(Stage::focusedProperty) + .orElse(true) + .subscribe(this::onFocusedChanged); + + var resizableSubscription = stage + .flatMap(Stage::resizableProperty) + .orElse(true) + .subscribe(this::onResizableChanged); + + var maximizedSubscription = stage + .flatMap(Stage::maximizedProperty) + .orElse(false) + .subscribe(this::onMaximizedChanged); + + var updateStylesheetSubscription = sceneProperty() + .flatMap(Scene::fillProperty) + .map(this::isDarkBackground) + .orElse(false) + .subscribe(x -> updateStyleClass()); // use a value subscriber, not an invalidation subscriber + + subscriptions = Subscription.combine( + focusedSubscription, + resizableSubscription, + maximizedSubscription, + updateStylesheetSubscription, + stylesheet.subscribe(this::updateStylesheet)); + + getStyleClass().setAll("window-button-container"); + getChildren().addAll(minimizeButton, maximizeButton, closeButton); + } + + public void dispose() { + subscriptions.unsubscribe(); + } + + public ReadOnlyObjectProperty metricsProperty() { + return metrics; + } + + /** + * Classifies and returns the button type at the specified coordinate, or returns + * {@code null} if the specified coordinate does not intersect a button. + * + * @param x the X coordinate, in pixels relative to the window + * @param y the Y coordinate, in pixels relative to the window + * @return the {@code ButtonType} or {@code null} + */ + public ButtonType buttonAt(double x, double y) { + for (var button : orderedButtons) { + if (button.isVisible() && button.getBoundsInParent().contains(x, y)) { + return button.getButtonType(); + } + } + + return null; + } + + /** + * Handles the specified mouse event. + * + * @param type the event type + * @param button the button type + * @param x the X coordinate, in pixels relative to the window + * @param y the Y coordinate, in pixels relative to the window + * @return {@code true} if the event was handled, {@code false} otherwise + */ + public boolean handleMouseEvent(int type, int button, double x, double y) { + ButtonType buttonType = buttonAt(x, y); + Node node = buttonType != null ? switch (buttonType) { + case MINIMIZE -> minimizeButton; + case MAXIMIZE -> maximizeButton; + case CLOSE -> closeButton; + } : null; + + if (type == MouseEvent.NC_ENTER || type == MouseEvent.NC_MOVE || type == MouseEvent.NC_DRAG) { + handleMouseOver(node); + } else if (type == MouseEvent.NC_EXIT) { + handleMouseExit(); + } else if (type == MouseEvent.NC_UP && button == MouseEvent.BUTTON_LEFT) { + handleMouseUp(node, buttonType); + } else if (node != null && type == MouseEvent.NC_DOWN && button == MouseEvent.BUTTON_LEFT) { + handleMouseDown(node); + } + + return node != null || buttonAtMouseDown != null; + } + + private void handleMouseOver(Node button) { + minimizeButton.pseudoClassStateChanged(HOVER_PSEUDOCLASS, button == minimizeButton); + maximizeButton.pseudoClassStateChanged(HOVER_PSEUDOCLASS, button == maximizeButton); + closeButton.pseudoClassStateChanged(HOVER_PSEUDOCLASS, button == closeButton); + + if (buttonAtMouseDown != null && buttonAtMouseDown != button) { + buttonAtMouseDown.pseudoClassStateChanged(PRESSED_PSEUDOCLASS, false); + } + } + + private void handleMouseExit() { + buttonAtMouseDown = null; + + for (var node : new Node[] {minimizeButton, maximizeButton, closeButton}) { + node.pseudoClassStateChanged(HOVER_PSEUDOCLASS, false); + node.pseudoClassStateChanged(PRESSED_PSEUDOCLASS, false); + } + } + + private void handleMouseDown(Node node) { + buttonAtMouseDown = node; + + if (!node.isDisabled()) { + node.pseudoClassStateChanged(PRESSED_PSEUDOCLASS, true); + } + } + + private void handleMouseUp(Node node, ButtonType buttonType) { + boolean releasedOnButton = buttonAtMouseDown == node; + buttonAtMouseDown = null; + Scene scene = getScene(); + + if (node == null || node.isDisabled() + || scene == null || !(scene.getWindow() instanceof Stage stage)) { + return; + } + + node.pseudoClassStateChanged(PRESSED_PSEUDOCLASS, false); + + if (releasedOnButton) { + switch (buttonType) { + case MINIMIZE -> stage.setIconified(true); + case MAXIMIZE -> stage.setMaximized(!stage.isMaximized()); + case CLOSE -> stage.close(); + } + } + } + + private void onFocusedChanged(boolean focused) { + for (var node : new Node[] {minimizeButton, maximizeButton, closeButton}) { + node.pseudoClassStateChanged(ACTIVE_PSEUDOCLASS, focused); + } + } + + private void onResizableChanged(boolean resizable) { + maximizeButton.setDisable(!resizable); + } + + private void onMaximizedChanged(boolean maximized) { + toggleStyleClass(maximizeButton, RESTORE_STYLE_CLASS, maximized); + } + + private void updateStyleClass() { + boolean darkScene = isDarkBackground(getScene() != null ? getScene().getFill() : null); + toggleStyleClass(minimizeButton, DARK_STYLE_CLASS, darkScene); + toggleStyleClass(maximizeButton, DARK_STYLE_CLASS, darkScene); + toggleStyleClass(closeButton, DARK_STYLE_CLASS, darkScene); + } + + private void updateStylesheet(String stylesheet) { + getStylesheets().setAll(stylesheet); + } + + private void toggleStyleClass(Node node, String styleClass, boolean enabled) { + if (enabled && !node.getStyleClass().contains(styleClass)) { + node.getStyleClass().add(styleClass); + } else if (!enabled) { + node.getStyleClass().remove(styleClass); + } + } + + private boolean isDarkBackground(Paint paint) { + return paint != null && Utils.calculateAverageBrightness(paint) < 0.5; + } + + @Override + protected void layoutChildren() { + boolean left; + Node button1, button2, button3; + + if (allowRtl.get() && getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT) { + button1 = orderedButtons.get(2); + button2 = orderedButtons.get(1); + button3 = orderedButtons.get(0); + left = buttonPlacement.get() != HorizontalDirection.LEFT; + } else { + button1 = orderedButtons.get(0); + button2 = orderedButtons.get(1); + button3 = orderedButtons.get(2); + left = buttonPlacement.get() == HorizontalDirection.LEFT; + } + + double width = getWidth(); + double button1Width = boundedWidth(button1); + double button2Width = boundedWidth(button2); + double button3Width = boundedWidth(button3); + double button1Height = boundedHeight(button1); + double button2Height = boundedHeight(button2); + double button3Height = boundedHeight(button3); + double button1X = left ? 0 : width - button1Width - button2Width - button3Width; + double button2X = left ? button1Width : width - button3Width - button2Width; + double button3X = left ? button1Width + button2Width : width - button3Width; + double totalWidth = button1Width + button2Width + button3Width; + double totalHeight = Math.max(button1Height, Math.max(button2Height, button3Height)); + Dimension2D currentSize = metrics.get().size(); + + if (currentSize.getWidth() != totalWidth || currentSize.getHeight() != totalHeight) { + var newMetrics = new WindowOverlayMetrics( + left ? HorizontalDirection.LEFT : HorizontalDirection.RIGHT, + new Dimension2D(totalWidth, totalHeight)); + + // Don't change the metrics during layout, since we don't know who might be listening. + Platform.runLater(() -> metrics.set(newMetrics)); + } + + layoutInArea(button1, button1X, 0, button1Width, button1Height, + BASELINE_OFFSET_SAME_AS_HEIGHT, HPos.LEFT, VPos.TOP); + + layoutInArea(button2, button2X, 0, button2Width, button2Height, + BASELINE_OFFSET_SAME_AS_HEIGHT, HPos.LEFT, VPos.TOP); + + layoutInArea(button3, button3X, 0, button3Width, button3Height, + BASELINE_OFFSET_SAME_AS_HEIGHT, HPos.LEFT, VPos.TOP); + } + + @Override + public boolean usesMirroring() { + return false; + } + + @Override + public List> getCssMetaData() { + return METADATA; + } + + private static double boundedWidth(Node node) { + return node.isManaged() ? boundedSize(node.minWidth(-1), node.prefWidth(-1), node.maxWidth(-1)) : 0; + } + + private static double boundedHeight(Node node) { + return node.isManaged() ? boundedSize(node.minHeight(-1), node.prefHeight(-1), node.maxHeight(-1)) : 0; + } + + private static double boundedSize(double min, double pref, double max) { + return Math.min(Math.max(pref, min), Math.max(min, max)); + } + + public enum ButtonType { + MINIMIZE, + MAXIMIZE, + CLOSE + } + + private class ButtonRegion extends Region { + + private static final CssMetaData BUTTON_ORDER_METADATA = + new CssMetaData<>("-fx-button-order", StyleConverter.getSizeConverter()) { + @Override + public boolean isSettable(ButtonRegion node) { + return true; + } + + @Override + public StyleableProperty getStyleableProperty(ButtonRegion region) { + return region.buttonOrder; + } + }; + + private static final List> METADATA = + Stream.concat(getClassCssMetaData().stream(), Stream.of(BUTTON_ORDER_METADATA)).toList(); + + /** + * Specifies the layout order of this button relative to the other buttons. + * Buttons with a lower value are laid out before buttons with a higher value. + *

+ * This property corresponds to the {@code -fx-button-order} CSS property. + */ + private final StyleableIntegerProperty buttonOrder = + new SimpleStyleableIntegerProperty(BUTTON_ORDER_METADATA, this, "buttonOrder") { + @Override + protected void invalidated() { + requestParentLayout(); + + WindowControlsOverlay.this.orderedButtons.sort( + Comparator.comparing(ButtonRegion::getButtonOrder)); + } + }; + + private final Region glyph = new Region(); + private final ButtonType type; + + ButtonRegion(ButtonType type, String styleClass, int order) { + this.type = type; + orderedButtons.add(this); + buttonOrder.set(order); + glyph.getStyleClass().setAll("glyph"); + getChildren().add(glyph); + getStyleClass().setAll("window-button", styleClass); + } + + public ButtonType getButtonType() { + return type; + } + + public int getButtonOrder() { + return buttonOrder.get(); + } + + @Override + protected void layoutChildren() { + layoutInArea(glyph, 0, 0, getWidth(), getHeight(), 0, HPos.LEFT, VPos.TOP); + } + + @Override + public List> getCssMetaData() { + return METADATA; + } + } +} diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowOverlayMetrics.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowOverlayMetrics.java new file mode 100644 index 00000000000..15614172e27 --- /dev/null +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowOverlayMetrics.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.glass.ui; + +import javafx.geometry.Dimension2D; +import javafx.geometry.HorizontalDirection; +import javafx.stage.StageStyle; +import java.util.Objects; + +/** + * Provides metrics about the window buttons of {@link StageStyle#EXTENDED} windows. + * + * @param placement the placement of the window buttons + * @param size the size of the window buttons + */ +public record WindowOverlayMetrics(HorizontalDirection placement, Dimension2D size) { + + public WindowOverlayMetrics { + Objects.requireNonNull(placement, "placement cannot be null"); + Objects.requireNonNull(size, "size cannot be null"); + } +} diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkApplication.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkApplication.java index fb1a1f8797d..8f0da0f8c71 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkApplication.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkApplication.java @@ -462,6 +462,11 @@ protected boolean _supportsInputMethods() { return false; } + @Override + protected boolean _supportsExtendedWindows() { + return true; + } + @Override protected native int _getKeyCodeForChar(char c, int hint); diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java index 002d698398f..d02c4ffe390 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,12 +24,17 @@ */ package com.sun.glass.ui.gtk; +import com.sun.glass.events.MouseEvent; import com.sun.glass.ui.Cursor; import com.sun.glass.events.WindowEvent; +import com.sun.glass.ui.NonClientHandler; import com.sun.glass.ui.Pixels; import com.sun.glass.ui.Screen; import com.sun.glass.ui.View; import com.sun.glass.ui.Window; +import com.sun.glass.ui.WindowControlsOverlay; +import com.sun.glass.ui.WindowOverlayMetrics; +import javafx.beans.value.ObservableValue; class GtkWindow extends Window { @@ -198,4 +203,67 @@ public long getRawHandle() { long ptr = super.getRawHandle(); return ptr == 0L ? 0L : _getNativeWindowImpl(ptr); } + + private WindowControlsOverlay windowControlsOverlay; + + @Override + public ObservableValue windowOverlayMetrics() { + var overlay = getWindowOverlay(); + return overlay != null ? overlay.metricsProperty() : null; + } + + @Override + public WindowControlsOverlay getWindowOverlay() { + if (windowControlsOverlay == null && isExtendedWindow()) { + windowControlsOverlay = new WindowControlsOverlay( + PlatformThemeObserver.getInstance().stylesheetProperty()); + } + + return windowControlsOverlay; + } + + @Override + public NonClientHandler getNonClientHandler() { + var overlay = getWindowOverlay(); + if (overlay == null) { + return null; + } + + return (type, button, x, y, xAbs, yAbs, clickCount) -> { + // In contrast to Windows, GTK doesn't produce non-client events. We convert regular + // mouse events to non-client events since that's what WindowControlsOverlay expects. + return overlay.handleMouseEvent( + MouseEvent.toNonClientEvent(type), button, x / platformScaleX, y / platformScaleY); + }; + } + + /** + * Returns whether the window is draggable at the specified coordinate. + *

+ * This method is called from native code. + * + * @param x the X coordinate in physical pixels + * @param y the Y coordinate in physical pixels + */ + @SuppressWarnings("unused") + private boolean dragAreaHitTest(int x, int y) { + // A full-screen window has no draggable area. + if (view == null || view.isInFullscreen() || !isExtendedWindow()) { + return false; + } + + double wx = x / platformScaleX; + double wy = y / platformScaleY; + + if (windowControlsOverlay != null && windowControlsOverlay.buttonAt(wx, wy) != null) { + return false; + } + + View.EventHandler eventHandler = view.getEventHandler(); + if (eventHandler == null) { + return false; + } + + return eventHandler.handleDragAreaHitTestEvent(wx, wy); + } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/PlatformThemeObserver.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/PlatformThemeObserver.java new file mode 100644 index 00000000000..583a6653985 --- /dev/null +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/PlatformThemeObserver.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.glass.ui.gtk; + +import com.sun.javafx.application.PlatformImpl; +import javafx.beans.property.ReadOnlyStringProperty; +import javafx.beans.property.ReadOnlyStringWrapper; +import javafx.collections.MapChangeListener; + +final class PlatformThemeObserver { + + private static final String THEME_NAME_KEY = "GTK.theme_name"; + + private final ReadOnlyStringWrapper stylesheet = new ReadOnlyStringWrapper(this, "stylesheet"); + + private PlatformThemeObserver() { + PlatformImpl.getPlatformPreferences().addListener((MapChangeListener) change -> { + if (THEME_NAME_KEY.equals(change.getKey())) { + updateThemeStylesheets(); + } + }); + + updateThemeStylesheets(); + } + + public static PlatformThemeObserver getInstance() { + class Holder { + static final PlatformThemeObserver instance = new PlatformThemeObserver(); + } + + return Holder.instance; + } + + public ReadOnlyStringProperty stylesheetProperty() { + return stylesheet.getReadOnlyProperty(); + } + + private void updateThemeStylesheets() { + stylesheet.set(WindowDecorationTheme.findBestTheme().getStylesheet()); + } +} diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/WindowDecorationTheme.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/WindowDecorationTheme.java new file mode 100644 index 00000000000..007a2e22864 --- /dev/null +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/WindowDecorationTheme.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.glass.ui.gtk; + +import com.sun.javafx.application.PlatformImpl; +import javafx.stage.StageStyle; +import java.util.Locale; +import java.util.Map; + +/** + * The client-side window decoration theme used for {@link StageStyle#EXTENDED} windows. + */ +enum WindowDecorationTheme { + + GNOME("WindowDecorationGnome.css"), + KDE("WindowDecorationKDE.css"); + + WindowDecorationTheme(String stylesheet) { + this.stylesheet = stylesheet; + } + + private static final String THEME_NAME_KEY = "GTK.theme_name"; + + /** + * A mapping of platform theme names to the most similar window decoration theme. + */ + private static final Map SIMILAR_THEMES = Map.of( + "adwaita", WindowDecorationTheme.GNOME, + "yaru", WindowDecorationTheme.GNOME, + "breeze", WindowDecorationTheme.KDE + ); + + private final String stylesheet; + + /** + * Determines the best window decoration theme for the current window manager theme. + *

+ * Since we can't ship decorations for all possible window manager themes, we need to choose the + * theme most similar to the native window manager theme. If we can't choose a theme by name, we + * fall back to choosing a theme by determining the current window manager. + */ + public static WindowDecorationTheme findBestTheme() { + return PlatformImpl.getPlatformPreferences() + .getString(THEME_NAME_KEY) + .map(name -> { + for (Map.Entry entry : SIMILAR_THEMES.entrySet()) { + if (name.toLowerCase(Locale.ROOT).startsWith(entry.getKey())) { + return entry.getValue(); + } + } + + return null; + }) + .orElse(switch (WindowManager.current()) { + case GNOME -> WindowDecorationTheme.GNOME; + case KDE -> WindowDecorationTheme.KDE; + default -> WindowDecorationTheme.GNOME; + }); + } + + public String getStylesheet() { + var url = getClass().getResource(stylesheet); + if (url == null) { + throw new RuntimeException("Resource not found: " + stylesheet); + } + + return url.toExternalForm(); + } +} diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/WindowManager.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/WindowManager.java new file mode 100644 index 00000000000..43f9ca04b44 --- /dev/null +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/WindowManager.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.glass.ui.gtk; + +import java.util.Locale; + +/** + * The window manager of the current desktop environment. + */ +enum WindowManager { + UNKNOWN, + GNOME, + KDE; + + /** + * Returns the window manager of the current desktop environment. + */ + public static WindowManager current() { + var result = parse(System.getenv("XDG_CURRENT_DESKTOP")); + if (result != UNKNOWN) { + return result; + } + + result = parse(System.getenv("GDMSESSION")); + if (result != UNKNOWN) { + return result; + } + + if (System.getenv("KDE_FULL_SESSION") != null) { + return KDE; + } + + return UNKNOWN; + } + + private static WindowManager parse(String value) { + if (value == null) { + return UNKNOWN; + } + + String v = value.toLowerCase(Locale.ROOT); + + if (v.contains("gnome")) { + return GNOME; + } + + if (v.contains("kde")) { + return KDE; + } + + return UNKNOWN; + } +} diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/ios/IosApplication.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/ios/IosApplication.java index 27e32634776..877566ad971 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/ios/IosApplication.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/ios/IosApplication.java @@ -228,6 +228,11 @@ protected boolean _supportsTransparentWindows() { return false; } + @Override + protected boolean _supportsExtendedWindows() { + return false; + } + /** * Hides / Shows iOS status bar. * @param hidden diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacApplication.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacApplication.java index fedd40bef09..d8efc945157 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacApplication.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacApplication.java @@ -407,6 +407,11 @@ protected boolean _supportsTransparentWindows() { return true; } + @Override + protected boolean _supportsExtendedWindows() { + return true; + } + @Override native protected boolean _supportsSystemMenu(); // NOTE: this will not return a valid result until the native _runloop diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java index 01c1a402012..501991e4d1a 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,12 +24,19 @@ */ package com.sun.glass.ui.mac; +import com.sun.glass.events.MouseEvent; import com.sun.glass.events.WindowEvent; import com.sun.glass.ui.Cursor; +import com.sun.glass.ui.NonClientHandler; import com.sun.glass.ui.Pixels; import com.sun.glass.ui.Screen; import com.sun.glass.ui.View; import com.sun.glass.ui.Window; +import com.sun.glass.ui.WindowOverlayMetrics; +import com.sun.javafx.binding.ObjectConstant; +import javafx.beans.value.ObservableValue; +import javafx.geometry.Dimension2D; +import javafx.geometry.HorizontalDirection; import java.nio.ByteBuffer; /** @@ -150,5 +157,46 @@ protected void _requestInput(long ptr, String text, int type, double width, doub protected void _releaseInput(long ptr) { throw new UnsupportedOperationException("Not supported yet."); } + + private native void _performWindowDrag(long ptr); + + @Override + public NonClientHandler getNonClientHandler() { + return (type, button, x, y, xAbs, yAbs, clickCount) -> { + if (type == MouseEvent.DOWN) { + double wx = x / platformScaleX; + double wy = y / platformScaleY; + + View.EventHandler eventHandler = view != null ? view.getEventHandler() : null; + if (eventHandler != null && eventHandler.handleDragAreaHitTestEvent(wx, wy)) { + if (clickCount == 2) { + maximize(!isMaximized()); + } else if (clickCount == 1) { + _performWindowDrag(getRawHandle()); + } + } + } + + return false; + }; + } + + private native boolean _isRightToLeftLayoutDirection(); + + private ObservableValue windowOverlayMetrics; + + @Override + public ObservableValue windowOverlayMetrics() { + if (windowOverlayMetrics == null) { + HorizontalDirection direction = _isRightToLeftLayoutDirection() + ? HorizontalDirection.RIGHT + : HorizontalDirection.LEFT; + + windowOverlayMetrics = ObjectConstant.valueOf( + new WindowOverlayMetrics(direction, new Dimension2D(78, 38))); + } + + return windowOverlayMetrics; + } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/monocle/MonocleApplication.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/monocle/MonocleApplication.java index 3b5618d5195..fbbfefe6cb2 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/monocle/MonocleApplication.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/monocle/MonocleApplication.java @@ -306,6 +306,11 @@ protected boolean _supportsUnifiedWindows() { return false; } + @Override + protected boolean _supportsExtendedWindows() { + return false; + } + @Override public boolean hasTwoLevelFocus() { return deviceFlags[DEVICE_PC_KEYBOARD] == 0 && deviceFlags[DEVICE_5WAY] > 0; diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinApplication.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinApplication.java index 2c7b3ff7c2c..7686671437a 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinApplication.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinApplication.java @@ -352,6 +352,11 @@ protected boolean _supportsTransparentWindows() { @Override native protected boolean _supportsUnifiedWindows(); + @Override + protected boolean _supportsExtendedWindows() { + return true; + } + @Override public String getDataDirectory() { checkEventThread(); diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java index 4e1bd82093b..52c35f88dc6 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,10 +25,15 @@ package com.sun.glass.ui.win; import com.sun.glass.ui.Cursor; +import com.sun.glass.ui.WindowControlsOverlay; +import com.sun.glass.ui.NonClientHandler; import com.sun.glass.ui.Pixels; import com.sun.glass.ui.Screen; import com.sun.glass.ui.View; import com.sun.glass.ui.Window; +import com.sun.glass.ui.WindowOverlayMetrics; +import com.sun.javafx.binding.StringConstant; +import javafx.beans.value.ObservableValue; /** * MS Windows platform implementation class for Window. @@ -40,10 +45,12 @@ class WinWindow extends Window { public static final long ANCHOR_NO_CAPTURE = (1L << 63); - float fxReqWidth; - float fxReqHeight; - int pfReqWidth; - int pfReqHeight; + private static final String WINDOW_DECORATION_STYLESHEET = "WindowDecoration.css"; + + private float fxReqWidth; + private float fxReqHeight; + private int pfReqWidth; + private int pfReqHeight; private native static void _initIDs(); static { @@ -115,6 +122,10 @@ public void setBounds(float x, float y, boolean xSet, boolean ySet, } fxReqHeight = fx_ch; + int maxW = getMaximumWidth(), maxH = getMaximumHeight(); + pw = Math.max(Math.min(pw, maxW > 0 ? maxW : Integer.MAX_VALUE), getMinimumWidth()); + ph = Math.max(Math.min(ph, maxH > 0 ? maxH : Integer.MAX_VALUE), getMinimumHeight()); + long anchor = _getAnchor(getRawHandle()); int resizeMode = (anchor == ANCHOR_NO_CAPTURE) ? RESIZE_TO_FX_ORIGIN @@ -315,10 +326,93 @@ void setDeferredClosing(boolean dc) { @Override public void close() { if (!deferredClosing) { + if (windowControlsOverlay != null) { + windowControlsOverlay.dispose(); + } + super.close(); } else { closingRequested = true; setVisible(false); } } + + private WindowControlsOverlay windowControlsOverlay; + + @Override + public ObservableValue windowOverlayMetrics() { + var overlay = getWindowOverlay(); + return overlay != null ? overlay.metricsProperty() : null; + } + + @Override + public WindowControlsOverlay getWindowOverlay() { + if (windowControlsOverlay == null && isExtendedWindow()) { + var url = getClass().getResource(WINDOW_DECORATION_STYLESHEET); + if (url == null) { + throw new RuntimeException("Resource not found: " + WINDOW_DECORATION_STYLESHEET); + } + + windowControlsOverlay = new WindowControlsOverlay(StringConstant.valueOf(url.toExternalForm())); + } + + return windowControlsOverlay; + } + + @Override + public NonClientHandler getNonClientHandler() { + var overlay = getWindowOverlay(); + if (overlay == null) { + return null; + } + + return (type, button, x, y, xAbs, yAbs, clickCount) -> { + double wx = x / platformScaleX; + double wy = y / platformScaleY; + return overlay.handleMouseEvent(type, button, wx, wy); + }; + } + + /** + * Classifies the window region at the specified physical coordinate. + *

+ * This method is called from native code. + * + * @param x the X coordinate in physical pixels + * @param y the Y coordinate in physical pixels + */ + @SuppressWarnings("unused") + private int nonClientHitTest(int x, int y) { + // https://learn.microsoft.com/en-us/windows/win32/inputdev/wm-nchittest + enum HT { + CLIENT(1), CAPTION(2), MINBUTTON(8), MAXBUTTON(9), CLOSE(20); + HT(int value) { this.value = value; } + final int value; + } + + // A full-screen window has no non-client area. + if (view == null || view.isInFullscreen() || !isExtendedWindow()) { + return HT.CLIENT.value; + } + + double wx = x / platformScaleX; + double wy = y / platformScaleY; + + // If the cursor is over one of the window buttons (minimize, maximize, close), we need to + // report the value of HTMINBUTTON, HTMAXBUTTON, or HTCLOSE back to the native layer. + switch (windowControlsOverlay != null ? windowControlsOverlay.buttonAt(wx, wy) : null) { + case MINIMIZE: return HT.MINBUTTON.value; + case MAXIMIZE: return HT.MAXBUTTON.value; + case CLOSE: return HT.CLOSE.value; + case null: break; + } + + // Otherwise, test if the cursor is over a draggable area and return HTCAPTION. + View.EventHandler eventHandler = view.getEventHandler(); + if (eventHandler != null && eventHandler.handleDragAreaHitTestEvent(wx, wy)) { + return HT.CAPTION.value; + } + + return HT.CLIENT.value; + } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/NodeHelper.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/NodeHelper.java index 41b1fd62244..94372a6bd12 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/NodeHelper.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/NodeHelper.java @@ -46,6 +46,7 @@ import javafx.css.StyleableProperty; import javafx.geometry.Bounds; import javafx.scene.Node; +import javafx.scene.Scene; import javafx.scene.SubScene; import javafx.scene.shape.Shape; import javafx.scene.shape.Shape3D; @@ -208,6 +209,14 @@ public static boolean isDirtyEmpty(Node node) { return nodeAccessor.isDirtyEmpty(node); } + public static void setScenes(Node node, Scene newScene, SubScene newSubScene) { + nodeAccessor.setScenes(node, newScene, newSubScene); + } + + public static void updateBounds(Node node) { + nodeAccessor.updateBounds(node); + } + public static void syncPeer(Node node) { nodeAccessor.syncPeer(node); } @@ -372,6 +381,8 @@ boolean doComputeIntersects(Node node, PickRay pickRay, void doProcessCSS(Node node); boolean isDirty(Node node, DirtyBits dirtyBit); boolean isDirtyEmpty(Node node); + void setScenes(Node node, Scene newScene, SubScene newSubScene); + void updateBounds(Node node); void syncPeer(Node node);

P getPeer(Node node); void layoutBoundsChanged(Node node); diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKScene.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKScene.java index 3d3358c8446..9cfe483499b 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKScene.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKScene.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -89,4 +89,10 @@ public interface TKScene { @SuppressWarnings("removal") public AccessControlContext getAccessControlContext(); + + default void processOverlayCSS() {} + + default void layoutOverlay() {} + + default void synchronizeOverlay() {} } diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKSceneListener.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKSceneListener.java index 116bff3d590..7a5cdfe8ac1 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKSceneListener.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKSceneListener.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -120,4 +120,13 @@ public void touchEventNext( public void touchEventEnd(); public Accessible getSceneAccessible(); + + /** + * Tests whether the specified coordinate identifies a draggable area. + * + * @param x the X coordinate relative to the scene + * @param y the Y coordinate relative to the scene + * @return {@code true} if the area is draggable, {@code false} otherwise + */ + public boolean dragAreaHitTest(double x, double y); } diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/GlassViewEventHandler.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/GlassViewEventHandler.java index a263853789e..7d5f8b90ce8 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/GlassViewEventHandler.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/GlassViewEventHandler.java @@ -892,8 +892,7 @@ public Void run() { final Window w = view.getWindow(); float pScaleX = (w == null) ? 1.0f : w.getPlatformScaleX(); float pScaleY = (w == null) ? 1.0f : w.getPlatformScaleY(); - scene.sceneListener.changedSize(view.getWidth() / pScaleX, - view.getHeight() / pScaleY); + scene.setViewSize(view.getWidth() / pScaleX, view.getHeight() / pScaleY); scene.entireSceneNeedsRepaint(); QuantumToolkit.runWithRenderLock(() -> { scene.updateSceneState(); @@ -1407,4 +1406,15 @@ public Accessible getSceneAccessible() { } return null; } + + @Override + public boolean handleDragAreaHitTestEvent(double x, double y) { + return QuantumToolkit.runWithoutRenderLock(() -> { + if (scene.sceneListener != null) { + return scene.sceneListener.dragAreaHitTest(x, y); + } + + return false; + }); + } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/OverlayWarning.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/OverlayWarning.java index c4fb8f70f58..445c6b7a8e8 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/OverlayWarning.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/OverlayWarning.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,15 +25,12 @@ package com.sun.javafx.tk.quantum; -import com.sun.javafx.scene.DirtyBits; -import com.sun.javafx.scene.NodeHelper; import javafx.animation.Animation.Status; import javafx.animation.FadeTransition; import javafx.animation.PauseTransition; import javafx.animation.SequentialTransition; import javafx.geometry.Rectangle2D; import javafx.scene.Group; -import javafx.scene.Node; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.scene.text.Font; @@ -42,22 +39,6 @@ import javafx.util.Duration; public class OverlayWarning extends Group { - static { - // This is used by classes in different packages to get access to - // private and package private methods. - OverlayWarningHelper.setOverlayWarningAccessor( - new OverlayWarningHelper.OverlayWarningAccessor() { - @Override - public void doUpdatePeer(Node node) { - ((OverlayWarning) node).doUpdatePeer(); - } - - @Override - public void doMarkDirty(Node node, DirtyBits dirtyBit) { - ((OverlayWarning) node).doMarkDirty(dirtyBit); - } - }); - } private static final float PAD = 40f; private static final float RECTW = 600f; @@ -69,11 +50,6 @@ public void doMarkDirty(Node node, DirtyBits dirtyBit) { private SequentialTransition overlayTransition; private boolean warningTransition; - { - // To initialize the class helper at the begining each constructor of this class - OverlayWarningHelper.initHelper(this); - } - public OverlayWarning(final ViewScene vs) { view = vs; @@ -170,24 +146,4 @@ private Rectangle createBackground(Text text, Rectangle2D screen) { return rectangle; } - - /* - * Note: This method MUST only be called via its accessor method. - */ - private void doUpdatePeer() { - NodeHelper.updatePeer(text); - NodeHelper.updatePeer(background); - } - - @Override - protected void updateBounds() { - super.updateBounds(); - } - - /* - * Note: This method MUST only be called via its accessor method. - */ - private void doMarkDirty(DirtyBits dirtyBit) { - view.synchroniseOverlayWarning(); - } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/OverlayWarningHelper.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/OverlayWarningHelper.java deleted file mode 100644 index d6f317eca5e..00000000000 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/OverlayWarningHelper.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package com.sun.javafx.tk.quantum; - -import com.sun.javafx.scene.DirtyBits; -import com.sun.javafx.scene.GroupHelper; -import com.sun.javafx.util.Utils; -import javafx.scene.Node; - -/** - * Used to access internal methods of OverlayWarning. - */ -public class OverlayWarningHelper extends GroupHelper { - - private static final OverlayWarningHelper theInstance; - private static OverlayWarningAccessor overlayWarningAccessor; - - static { - theInstance = new OverlayWarningHelper(); - Utils.forceInit(OverlayWarning.class); - } - - private static OverlayWarningHelper getInstance() { - return theInstance; - } - - public static void initHelper(OverlayWarning overlayWarning) { - setHelper(overlayWarning, getInstance()); - } - - @Override - protected void updatePeerImpl(Node node) { - overlayWarningAccessor.doUpdatePeer(node); - super.updatePeerImpl(node); - } - - @Override - protected void markDirtyImpl(Node node, DirtyBits dirtyBit) { - super.markDirtyImpl(node, dirtyBit); - overlayWarningAccessor.doMarkDirty(node, dirtyBit); - } - - public static void setOverlayWarningAccessor(final OverlayWarningAccessor newAccessor) { - if (overlayWarningAccessor != null) { - throw new IllegalStateException(); - } - - overlayWarningAccessor = newAccessor; - } - - public interface OverlayWarningAccessor { - void doMarkDirty(Node node, DirtyBits dirtyBit); - void doUpdatePeer(Node node); - } - -} diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java index 96f00d09a75..031f80bba41 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java @@ -1256,6 +1256,8 @@ public boolean isSupported(ConditionalFeature feature) { return Application.GetApplication().supportsTransparentWindows(); case UNIFIED_WINDOW: return Application.GetApplication().supportsUnifiedWindows(); + case EXTENDED_WINDOW: + return Application.GetApplication().supportsExtendedWindows(); case TWO_LEVEL_FOCUS: return Application.GetApplication().hasTwoLevelFocus(); case VIRTUAL_KEYBOARD: diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/ViewScene.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/ViewScene.java index e781830f8d6..83f0a62f058 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/ViewScene.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/ViewScene.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,24 +32,27 @@ import com.sun.glass.ui.View; import com.sun.glass.ui.Window; import com.sun.javafx.cursor.CursorFrame; -import com.sun.javafx.scene.NodeHelper; import com.sun.javafx.sg.prism.NGNode; import com.sun.javafx.tk.Toolkit; import com.sun.prism.GraphicsPipeline; +import javafx.scene.Parent; class ViewScene extends GlassScene { private static final String UNSUPPORTED_FORMAT = "Transparent windows only supported for BYTE_BGRA_PRE format on LITTLE_ENDIAN machines"; + private final javafx.scene.Scene fxScene; private View platformView; private ViewPainter painter; - private PaintRenderJob paintRenderJob; + private ViewSceneOverlay viewSceneOverlay; + private Parent overlayRoot; - public ViewScene(boolean depthBuffer, boolean msaa) { + public ViewScene(javafx.scene.Scene fxScene, boolean depthBuffer, boolean msaa) { super(depthBuffer, msaa); + this.fxScene = fxScene; this.platformView = Application.GetApplication().createView(); this.platformView.setEventHandler(new GlassViewEventHandler(this)); } @@ -81,6 +84,14 @@ public void setStage(GlassStage stage) { } else { painter = new PresentingPainter(this); } + + if (fxScene != null) { + viewSceneOverlay = new ViewSceneOverlay(fxScene, painter); + viewSceneOverlay.setRoot(overlayRoot); + } else { + viewSceneOverlay = null; + } + painter.setRoot(getRoot()); paintRenderJob = new PaintRenderJob(this, PaintCollector.getInstance().getRendered(), painter); } @@ -101,6 +112,7 @@ public void dispose() { updateSceneState(); painter = null; paintRenderJob = null; + viewSceneOverlay = null; return null; }); } @@ -152,26 +164,46 @@ public void finishInputMethodComposition() { platformView.finishInputMethodComposition(); } - @Override public String toString() { - View view = getPlatformView(); - return (" scene: " + hashCode() + " @ (" + view.getWidth() + "," + view.getHeight() + ")"); + @Override + public void processOverlayCSS() { + if (viewSceneOverlay != null) { + viewSceneOverlay.processCSS(); + } } - void synchroniseOverlayWarning() { - try { - waitForSynchronization(); - OverlayWarning warning = getWindowStage().getWarning(); - if (warning == null) { - painter.setOverlayRoot(null); - } else { - painter.setOverlayRoot(NodeHelper.getPeer(warning)); - warning.updateBounds(); - NodeHelper.updatePeer(warning); - } - } finally { - releaseSynchronization(true); - entireSceneNeedsRepaint(); + @Override + public void layoutOverlay() { + if (viewSceneOverlay != null) { + viewSceneOverlay.layout(); + } + } + + @Override + public void synchronizeOverlay() { + if (viewSceneOverlay != null) { + viewSceneOverlay.synchronize(); + } + } + + public void setViewSize(float width, float height) { + sceneListener.changedSize(width, height); + + if (viewSceneOverlay != null) { + viewSceneOverlay.resize(width, height); } } + + public void setOverlay(Parent root) { + overlayRoot = root; + + if (viewSceneOverlay != null) { + viewSceneOverlay.setRoot(root); + } + } + + @Override public String toString() { + View view = getPlatformView(); + return (" scene: " + hashCode() + " @ (" + view.getWidth() + "," + view.getHeight() + ")"); + } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/ViewSceneOverlay.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/ViewSceneOverlay.java new file mode 100644 index 00000000000..fe9d18540b2 --- /dev/null +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/ViewSceneOverlay.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.javafx.tk.quantum; + +import com.sun.javafx.scene.NodeHelper; +import javafx.scene.Node; +import javafx.scene.Parent; +import javafx.scene.SubScene; + +/** + * Shows an overlay over a {@link javafx.scene.Scene}. + * The overlay is not part of the scene graph, and not accessible by applications. + */ +final class ViewSceneOverlay { + + private final javafx.scene.Scene fxScene; + private final ViewPainter painter; + private Parent root; + private double width, height; + + ViewSceneOverlay(javafx.scene.Scene fxScene, ViewPainter painter) { + this.fxScene = fxScene; + this.painter = painter; + } + + public void processCSS() { + if (root != null) { + NodeHelper.processCSS(root); + } + } + + public void resize(double width, double height) { + this.width = width; + this.height = height; + } + + public void layout() { + if (fxScene == null) { + return; + } + + var window = fxScene.getWindow(); + + if (root != null && window != null) { + root.resize(width, height); + root.layout(); + NodeHelper.updateBounds(root); + } + } + + public void setRoot(Parent root) { + if (this.root == root) { + return; + } + + this.root = root; + + if (root != null) { + NodeHelper.setScenes(root, fxScene, null); + painter.setOverlayRoot(NodeHelper.getPeer(root)); + } else { + painter.setOverlayRoot(null); + } + } + + public void synchronize() { + if (root != null && !NodeHelper.isDirtyEmpty(root)) { + syncPeer(root); + } + } + + private void syncPeer(Node node) { + NodeHelper.syncPeer(node); + + if (node instanceof Parent parent) { + for (Node child : parent.getChildrenUnmodifiable()) { + syncPeer(child); + } + } else if (node instanceof SubScene subScene) { + syncPeer(subScene.getRoot()); + } + + if (node.getClip() != null) { + syncPeer(node.getClip()); + } + } +} diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/WindowStage.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/WindowStage.java index eef1ce1742b..5c76cde4d77 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/WindowStage.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/WindowStage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -154,20 +154,25 @@ private void initPlatformWindow() { } focusable = false; } else { + // Downgrade conditional stage styles if not supported + if (style == StageStyle.UNIFIED && !app.supportsUnifiedWindows()) { + style = StageStyle.DECORATED; + } else if (style == StageStyle.EXTENDED && !app.supportsExtendedWindows()) { + style = StageStyle.DECORATED; + } + switch (style) { case UNIFIED: - if (app.supportsUnifiedWindows()) { - windowMask |= Window.UNIFIED; - } + windowMask |= Window.UNIFIED; // fall through case DECORATED: windowMask |= Window.TITLED | Window.CLOSABLE | Window.MINIMIZABLE | Window.MAXIMIZABLE; - if (ownerWindow != null || modality != Modality.NONE) { - windowMask &= - ~(Window.MINIMIZABLE | Window.MAXIMIZABLE); - } + resizable = true; + break; + case EXTENDED: + windowMask |= Window.EXTENDED | Window.CLOSABLE | Window.MINIMIZABLE | Window.MAXIMIZABLE; resizable = true; break; case UTILITY: @@ -178,6 +183,10 @@ private void initPlatformWindow() { (transparent ? Window.TRANSPARENT : Window.UNTITLED) | Window.CLOSABLE; break; } + + if (ownerWindow != null || modality != Modality.NONE) { + windowMask &= ~(Window.MINIMIZABLE | Window.MAXIMIZABLE); + } } if (modality != Modality.NONE) { windowMask |= Window.MODAL; @@ -244,8 +253,14 @@ StageStyle getStyle() { } @Override public TKScene createTKScene(boolean depthBuffer, boolean msaa, @SuppressWarnings("removal") AccessControlContext acc) { - ViewScene scene = new ViewScene(depthBuffer, msaa); + ViewScene scene = new ViewScene(fxStage != null ? fxStage.getScene() : null, depthBuffer, msaa); scene.setSecurityContext(acc); + + // The window-provided overlay is not visible in full-screen mode. + if (!isInFullScreen) { + scene.setOverlay(platformWindow.getWindowOverlay()); + } + return scene; } @@ -696,8 +711,9 @@ private void applyFullScreen() { } else { if (warning != null) { warning.cancel(); - setWarning(null); } + + setWarning(null); v.exitFullscreen(false); } // Reset flag once we are done process fullscreen @@ -711,11 +727,11 @@ private void applyFullScreen() { void setWarning(OverlayWarning newWarning) { this.warning = newWarning; - getViewScene().synchroniseOverlayWarning(); - } - - OverlayWarning getWarning() { - return warning; + if (newWarning != null) { + getViewScene().setOverlay(newWarning); + } else if (!isInFullScreen) { + getViewScene().setOverlay(platformWindow.getWindowOverlay()); + } } @Override public void setFullScreen(boolean fullScreen) { diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/util/Utils.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/util/Utils.java index 495c0aac45f..02c35b79632 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/util/Utils.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/util/Utils.java @@ -38,6 +38,9 @@ import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.paint.Color; +import javafx.scene.paint.LinearGradient; +import javafx.scene.paint.Paint; +import javafx.scene.paint.RadialGradient; import javafx.scene.paint.Stop; import javafx.stage.Screen; import javafx.stage.Stage; @@ -219,12 +222,37 @@ public static boolean contains(String src, String s) { **************************************************************************/ /** - * Calculates a perceptual brightness for a color between 0.0 black and 1.0 while + * Calculates a perceptual brightness for a color between 0.0 (black) and 1.0 (white). */ public static double calculateBrightness(Color color) { return (0.3*color.getRed()) + (0.59*color.getGreen()) + (0.11*color.getBlue()); } + /** + * Calculates an average perceptual brightness for a paint between 0.0 (black) and 1.0 (white). + *

+ * The average brightness of gradient paints only takes into account the colors of gradient stops, + * but not the distribution of the gradient stops across the paint area. + *

+ * The brightness of {@code ImagePattern} paints is 1.0 by convention. + */ + public static double calculateAverageBrightness(Paint paint) { + return switch (paint) { + case Color color -> calculateBrightness(color); + case LinearGradient gradient -> calculateAverageGradientBrightness(gradient.getStops()); + case RadialGradient gradient -> calculateAverageGradientBrightness(gradient.getStops()); + default -> 1.0; + }; + } + + private static double calculateAverageGradientBrightness(List stops) { + return stops.stream() + .map(Stop::getColor) + .mapToDouble(Utils::calculateBrightness) + .average() + .orElse(1.0); + } + /** * Derives a lighter or darker of a given color. * diff --git a/modules/javafx.graphics/src/main/java/javafx/application/ConditionalFeature.java b/modules/javafx.graphics/src/main/java/javafx/application/ConditionalFeature.java index 61aadb3d662..ef60e66740f 100644 --- a/modules/javafx.graphics/src/main/java/javafx/application/ConditionalFeature.java +++ b/modules/javafx.graphics/src/main/java/javafx/application/ConditionalFeature.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -159,6 +159,15 @@ public enum ConditionalFeature { */ UNIFIED_WINDOW, + /** + * Indicates that a system supports {@link javafx.stage.StageStyle#EXTENDED}. + *

+ * This feature is currently supported on Windows, Linux, and macOS. + * + * @since 24 + */ + EXTENDED_WINDOW, + /** * Indicates whether or not controls should use two-level focus. Two-level * focus is when separate operations are needed in some controls to first diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/Node.java b/modules/javafx.graphics/src/main/java/javafx/scene/Node.java index 00064b6a6fe..2935a3dfed5 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/Node.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/Node.java @@ -498,6 +498,16 @@ public boolean isDirtyEmpty(Node node) { return node.isDirtyEmpty(); } + @Override + public void setScenes(Node node, Scene newScene, SubScene newSubScene) { + node.setScenes(newScene, newSubScene); + } + + @Override + public void updateBounds(Node node) { + node.updateBounds(); + } + @Override public void syncPeer(Node node) { node.syncPeer(); diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java b/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java index d9f2a0acd9a..02a367697e0 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java @@ -76,6 +76,7 @@ import javafx.geometry.*; import javafx.scene.image.WritableImage; import javafx.scene.input.*; +import javafx.scene.layout.HeaderBarBase; import javafx.scene.paint.Color; import javafx.scene.paint.Paint; import javafx.stage.PopupWindow; @@ -566,6 +567,10 @@ void addToDirtyList(Node n) { } private void doCSSPass() { + if (peer != null) { + peer.processOverlayCSS(); + } + final Parent sceneRoot = getRoot(); // // RT-17547: when the tree is synchronized, the dirty bits are @@ -592,6 +597,10 @@ private void doCSSPass() { } void doLayoutPass() { + if (peer != null) { + peer.layoutOverlay(); + } + final Parent r = getRoot(); if (r != null) { r.layout(); @@ -2630,6 +2639,7 @@ public void pulse() { if (PULSE_LOGGING_ENABLED) { PulseLogger.newPhase("Copy state to render graph"); } + peer.synchronizeOverlay(); syncLights(); synchronizeSceneProperties(); // Run the synchronizer @@ -2989,6 +2999,35 @@ public void touchEventEnd() { } } + private final PickRay pickRay = new PickRay(); + + @Override + public boolean dragAreaHitTest(double x, double y) { + Node root = Scene.this.getRoot(); + if (root != null) { + pickRay.set(x, y, 1, 0, Double.POSITIVE_INFINITY); + var pickResultChooser = new PickResultChooser(); + root.pickNode(pickRay, pickResultChooser); + var intersectedNode = pickResultChooser.getIntersectedNode(); + + if (intersectedNode instanceof HeaderBarBase) { + return true; + } + + while (intersectedNode != null) { + if (HeaderBarBase.isDraggable(intersectedNode) instanceof Boolean value) { + return value; + } + + intersectedNode = intersectedNode.getParent(); + } + + return false; + } + + return false; + } + @Override public Accessible getSceneAccessible() { return getAccessible(); diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java new file mode 100644 index 00000000000..bca236a68db --- /dev/null +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java @@ -0,0 +1,483 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javafx.scene.layout; + +import com.sun.javafx.geom.Vec2d; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ObjectPropertyBase; +import javafx.geometry.HPos; +import javafx.geometry.Insets; +import javafx.geometry.NodeOrientation; +import javafx.geometry.Orientation; +import javafx.geometry.Pos; +import javafx.geometry.VPos; +import javafx.scene.Node; +import javafx.stage.StageStyle; + +/** + * A client-area header bar that is used as a replacement for the system-provided header bar in stages + * with the {@link StageStyle#EXTENDED} style. This class enables the drag to move and + * double-click to maximize behaviors that are usually afforded by system-provided header bars. + * The entire {@code HeaderBar} background is draggable by default, but its content is not. Applications + * can specify draggable content nodes of the {@code HeaderBar} with the {@link #setDraggable} method. + *

+ * {@code HeaderBar} is a layout container that allows applications to place scene graph nodes + * in three areas: {@link #leadingProperty() leading}, {@link #centerProperty() center}, and + * {@link #trailingProperty() trailing}. {@code HeaderBar} ensures that the leading and trailing areas + * account for the default window buttons (minimize, maximize, close). If a child is configured to be + * centered in the {@code center} area, it is laid out with respect to the stage, and not with respect + * to the {@code center} area. This ensures that the child will appear centered in the stage regardless + * of leading or trailing children or the platform-specific placement of default window buttons. + *

+ * All children will be resized to their preferred widths and extend the height of the {@code HeaderBar}. + * {@code HeaderBar} honors the minimum, preferred, and maximum sizes of its children. If the child's + * resizable range prevents it from be resized to fit within its position, it will be vertically centered + * relative to the available space; this alignment can be customized with a layout constraint. + * + *

Layout constraints

+ * An application may set constraints on individual children to customize their layout. + * For each constraint, {@code HeaderBar} provides static getter and setter methods. + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Layout constraints of {@code HeaderBar}
ConstraintTypeDescription
alignment{@link Pos}The alignment of the child within its area of the {@code HeaderBar}.
margin{@link Insets}Margin space around the outside of the child.
+ * + *

Example

+ *
{@code
+ *     var button = new Button("My button");
+ *     HeaderBar.setAlignment(button, Pos.CENTER_LEFT);
+ *     HeaderBar.setMargin(button, new Insets(5));
+ *     myHeaderBar.setCenter(button);
+ * }
+ * + * @see HeaderBarBase + * @since 24 + */ +public class HeaderBar extends HeaderBarBase { + + private static final String ALIGNMENT = "headerbar-alignment"; + private static final String MARGIN = "headerbar-margin"; + + /** + * Sets the alignment for the child when contained in a {@code HeaderBar}. + * If set, will override the header bar's default alignment for the child's position. + * Setting the value to {@code null} will remove the constraint. + * + * @param child the child node + * @param value the alignment position + */ + public static void setAlignment(Node child, Pos value) { + Pane.setConstraint(child, ALIGNMENT, value); + } + + /** + * Returns the child's alignment in the {@code HeaderBar}. + * + * @param child the child node + * @return the alignment position for the child, or {@code null} if no alignment was set + */ + public static Pos getAlignment(Node child) { + return (Pos)Pane.getConstraint(child, ALIGNMENT); + } + + /** + * Sets the margin for the child when contained in a {@code HeaderBar}. + * If set, the header bar will lay it out with the margin space around it. + * Setting the value to {@code null} will remove the constraint. + * + * @param child the child node + * @param value the margin of space around the child + */ + public static void setMargin(Node child, Insets value) { + Pane.setConstraint(child, MARGIN, value); + } + + /** + * Returns the child's margin. + * + * @param child the child node + * @return the margin for the child, or {@code null} if no margin was set + */ + public static Insets getMargin(Node child) { + return (Insets)Pane.getConstraint(child, MARGIN); + } + + private static Insets getNodeMargin(Node child) { + Insets margin = getMargin(child); + return margin != null ? margin : Insets.EMPTY; + } + + /** + * Creates a new {@code HeaderBar}. + */ + public HeaderBar() { + } + + /** + * Creates a new {@code HeaderBar} with the specified children. + * + * @param leading the leading node + * @param center the center node + * @param trailing the trailing node + */ + public HeaderBar(Node leading, Node center, Node trailing) { + setLeading(leading); + setCenter(center); + setTrailing(trailing); + } + + /** + * The leading area of the {@code HeaderBar}. + *

+ * Usually, this corresponds to the left side of the header bar; with right-to-left orientation, + * this corresponds to the right side of the header bar. + */ + private final ObjectProperty leading = new NodeProperty() { + @Override + public String getName() { + return "leading"; + } + }; + + public final ObjectProperty leadingProperty() { + return leading; + } + + public final Node getLeading() { + return leading.get(); + } + + public final void setLeading(Node value) { + leading.set(value); + } + + /** + * The center area of the {@code HeaderBar}. + */ + private final ObjectProperty center = new NodeProperty() { + @Override + public String getName() { + return "center"; + } + }; + + public final ObjectProperty centerProperty() { + return center; + } + + public final Node getCenter() { + return center.get(); + } + + public final void setCenter(Node value) { + center.set(value); + } + + /** + * The trailing area of the {@code HeaderBar}. + *

+ * Usually, this corresponds to the right side of the header bar; with right-to-left orientation, + * this corresponds to the left side of the header bar. + */ + private final ObjectProperty trailing = new NodeProperty() { + @Override + public String getName() { + return "trailing"; + } + }; + + public final ObjectProperty trailingProperty() { + return trailing; + } + + public final Node getTrailing() { + return trailing.get(); + } + + public final void setTrailing(Node value) { + trailing.set(value); + } + + @Override + protected double computeMinWidth(double height) { + Node leading = getLeading(), center = getCenter(), trailing = getTrailing(); + Insets insets = getInsets(); + double leftPrefWidth; + double rightPrefWidth; + double centerMinWidth; + + if (height != -1 + && (childHasContentBias(leading, Orientation.VERTICAL) || + childHasContentBias(trailing, Orientation.VERTICAL) || + childHasContentBias(center, Orientation.VERTICAL))) { + double areaHeight = Math.max(0, height); + leftPrefWidth = getAreaWidth(leading, areaHeight, false); + rightPrefWidth = getAreaWidth(trailing, areaHeight, false); + centerMinWidth = getAreaWidth(center, areaHeight, true); + } else { + leftPrefWidth = getAreaWidth(leading, -1, false); + rightPrefWidth = getAreaWidth(trailing, -1, false); + centerMinWidth = getAreaWidth(center, -1, true); + } + + return insets.getLeft() + + leftPrefWidth + + centerMinWidth + + rightPrefWidth + + insets.getRight() + + getLeftSystemInset().getWidth() + + getRightSystemInset().getWidth(); + } + + @Override + protected double computeMinHeight(double width) { + Node leading = getLeading(), center = getCenter(), trailing = getTrailing(); + Insets insets = getInsets(); + double leadingMinHeight = getAreaHeight(leading, -1, true); + double trailingMinHeight = getAreaHeight(trailing, -1, true); + double centerMinHeight; + + if (width != -1 && childHasContentBias(center, Orientation.HORIZONTAL)) { + double leadingPrefWidth = getAreaWidth(leading, -1, false); + double trailingPrefWidth = getAreaWidth(trailing, -1, false); + centerMinHeight = getAreaHeight(center, Math.max(0, width - leadingPrefWidth - trailingPrefWidth), true); + } else { + centerMinHeight = getAreaHeight(center, -1, true); + } + + return insets.getTop() + + insets.getBottom() + + Math.max(centerMinHeight, Math.max(trailingMinHeight, leadingMinHeight)); + } + + @Override + protected double computePrefHeight(double width) { + Node leading = getLeading(), center = getCenter(), trailing = getTrailing(); + Insets insets = getInsets(); + double leadingPrefHeight = getAreaHeight(leading, -1, false); + double trailingPrefHeight = getAreaHeight(trailing, -1, false); + double centerPrefHeight; + + if (width != -1 && childHasContentBias(center, Orientation.HORIZONTAL)) { + double leadingPrefWidth = getAreaWidth(leading, -1, false); + double trailingPrefWidth = getAreaWidth(trailing, -1, false); + centerPrefHeight = getAreaHeight(center, Math.max(0, width - leadingPrefWidth - trailingPrefWidth), false); + } else { + centerPrefHeight = getAreaHeight(center, -1, false); + } + + return insets.getTop() + + insets.getBottom() + + Math.max(centerPrefHeight, Math.max(trailingPrefHeight, leadingPrefHeight)); + } + + @Override + public boolean usesMirroring() { + return false; + } + + @Override + protected void layoutChildren() { + Node center = getCenter(); + Node left, right; + Insets insets = getInsets(); + boolean rtl = getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT; + double width = Math.max(getWidth(), minWidth(-1)); + double height = Math.max(getHeight(), minHeight(-1)); + double leftSystemInset = getLeftSystemInset().getWidth(); + double rightSystemInset = getRightSystemInset().getWidth(); + double leftWidth = 0; + double rightWidth = 0; + double insideY = insets.getTop(); + double insideHeight = height - insideY - insets.getBottom(); + double insideX, insideWidth; + + if (rtl) { + left = getTrailing(); + right = getLeading(); + insideX = insets.getRight() + leftSystemInset; + insideWidth = width - insideX - insets.getLeft() - rightSystemInset; + } else { + left = getLeading(); + right = getTrailing(); + insideX = insets.getLeft() + leftSystemInset; + insideWidth = width - insideX - insets.getRight() - rightSystemInset; + } + + if (left != null && left.isManaged()) { + Insets leftMargin = adjustMarginForRTL(getNodeMargin(left), rtl); + double adjustedWidth = adjustWidthByMargin(insideWidth, leftMargin); + Vec2d childSize = resizeChild(left, adjustedWidth, insideHeight, leftMargin); + leftWidth = snapSpaceX(leftMargin.getLeft()) + childSize.x + snapSpaceX(leftMargin.getRight()); + Pos alignment = getAlignment(left); + + positionInArea( + left, insideX, insideY, + leftWidth, insideHeight, 0, + leftMargin, + alignment != null ? alignment.getHpos() : HPos.CENTER, + alignment != null ? alignment.getVpos() : VPos.CENTER, + isSnapToPixel()); + } + + if (right != null && right.isManaged()) { + Insets rightMargin = adjustMarginForRTL(getNodeMargin(right), rtl); + double adjustedWidth = adjustWidthByMargin(insideWidth - leftWidth, rightMargin); + Vec2d childSize = resizeChild(right, adjustedWidth, insideHeight, rightMargin); + rightWidth = snapSpaceX(rightMargin.getLeft()) + childSize.x + snapSpaceX(rightMargin.getRight()); + Pos alignment = getAlignment(right); + + positionInArea( + right, insideX + insideWidth - rightWidth, insideY, + rightWidth, insideHeight, 0, + rightMargin, + alignment != null ? alignment.getHpos() : HPos.CENTER, + alignment != null ? alignment.getVpos() : VPos.CENTER, + isSnapToPixel()); + } + + if (center != null && center.isManaged()) { + Insets centerMargin = adjustMarginForRTL(getNodeMargin(center), rtl); + double adjustedWidth = adjustWidthByMargin(insideWidth - leftWidth - rightWidth, centerMargin); + Vec2d childSize = resizeChild(center, adjustedWidth, insideHeight, centerMargin); + double centerWidth = childSize.x; + Pos alignment = getAlignment(center); + + if (alignment == null || alignment.getHpos() == HPos.CENTER) { + double idealX = width / 2 - centerWidth / 2; + double minX = insideX + leftWidth + centerMargin.getLeft(); + double maxX = insideX + insideWidth - rightWidth - centerMargin.getRight(); + double adjustedX; + + if (idealX < minX) { + adjustedX = minX; + } else if (idealX + centerWidth > maxX) { + adjustedX = maxX - centerWidth; + } else { + adjustedX = idealX; + } + + positionInArea( + center, + adjustedX, insideY, + centerWidth, insideHeight, 0, + new Insets(centerMargin.getTop(), 0, centerMargin.getBottom(), 0), + HPos.LEFT, alignment != null ? alignment.getVpos() : VPos.CENTER, + isSnapToPixel()); + } else { + positionInArea( + center, + insideX + leftWidth, insideY, + insideWidth - leftWidth - rightWidth, insideHeight, 0, + centerMargin, + alignment.getHpos(), alignment.getVpos(), + isSnapToPixel()); + } + } + } + + private Insets adjustMarginForRTL(Insets margin, boolean rtl) { + if (margin == null) { + return null; + } + + return rtl + ? new Insets(margin.getTop(), margin.getLeft(), margin.getBottom(), margin.getRight()) + : margin; + } + + private boolean childHasContentBias(Node child, Orientation orientation) { + if (child != null && child.isManaged()) { + return child.getContentBias() == orientation; + } + + return false; + } + + private Vec2d resizeChild(Node child, double adjustedWidth, double insideHeight, Insets margin) { + double adjustedHeight = adjustHeightByMargin(insideHeight, margin); + double childWidth = Math.min(snapSizeX(child.prefWidth(adjustedHeight)), adjustedWidth); + Vec2d size = boundedNodeSizeWithBias(child, childWidth, adjustedHeight, false, true, TEMP_VEC2D); + size.x = snapSizeX(size.x); + size.y = snapSizeX(size.y); + child.resize(size.x, size.y); + return size; + } + + private double getAreaWidth(Node child, double height, boolean minimum) { + if (child != null && child.isManaged()) { + Insets margin = getNodeMargin(child); + return minimum + ? computeChildMinAreaWidth(child, -1, margin, height, false) + : computeChildPrefAreaWidth(child, -1, margin, height, false); + } + + return 0; + } + + private double getAreaHeight(Node child, double width, boolean minimum) { + if (child != null && child.isManaged()) { + Insets margin = getNodeMargin(child); + return minimum + ? computeChildMinAreaHeight(child, -1, margin, width) + : computeChildPrefAreaHeight(child, -1, margin, width); + } + + return 0; + } + + private abstract class NodeProperty extends ObjectPropertyBase { + private Node value; + + @Override + public Object getBean() { + return HeaderBar.this; + } + + @Override + protected void invalidated() { + if (value != null) { + getChildren().remove(value); + } + + value = get(); + + if (value != null) { + getChildren().add(value); + } + } + } +} diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java new file mode 100644 index 00000000000..f5932eb65d1 --- /dev/null +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javafx.scene.layout; + +import com.sun.glass.ui.WindowOverlayMetrics; +import com.sun.javafx.stage.StageHelper; +import com.sun.javafx.tk.quantum.WindowStage; +import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.beans.value.ObservableValue; +import javafx.geometry.Dimension2D; +import javafx.geometry.HorizontalDirection; +import javafx.scene.Node; +import javafx.scene.Scene; +import javafx.stage.Stage; +import javafx.stage.StageStyle; +import javafx.stage.Window; +import javafx.util.Subscription; + +/** + * Base class for a client-area header bar that is used as a replacement for the system-provided header bar + * in stages with the {@link StageStyle#EXTENDED} style. This class is intended for application developers + * to use as a starting point for custom header bar implementations, and it enables the drag to move + * and double-click to maximize behaviors that are usually afforded by system-provided header bars. + * The entire {@code HeaderBarBase} background is draggable by default, but its content is not. Applications + * can specify draggable content nodes of the {@code HeaderBarBase} with the {@link #setDraggable} method. + *

+ * Most application developers should use the {@link HeaderBar} implementation instead of + * creating a custom header bar. + * + * @see HeaderBar + * @since 24 + */ +public abstract class HeaderBarBase extends Region { + + private static final String DRAGGABLE = "headerbar-draggable"; + + /** + * Specifies whether the child and its subtree is a draggable part of the {@code HeaderBar}. + *

+ * If set to a non-null value, the value will apply for the entire subtree of the child unless + * another node in the subtree specifies a different value. Setting the value to {@code null} + * will remove the flag. + * + * @param child the child node + * @param value a {@code Boolean} value indicating whether the child and its subtree is draggable, + * or {@code null} to remove the flag + */ + public static void setDraggable(Node child, Boolean value) { + Pane.setConstraint(child, DRAGGABLE, value); + } + + /** + * Returns whether the child and its subtree is a draggable part of the {@code HeaderBar}. + * + * @param child the child node + * @return a {@code Boolean} value indicating whether the child and its subtree is draggable, + * or {@code null} if not set + */ + public static Boolean isDraggable(Node child) { + return (Boolean)Pane.getConstraint(child, DRAGGABLE); + } + + private Subscription metricsSubscription; + private WindowOverlayMetrics currentMetrics; + private boolean currentFullScreen; + + /** + * Constructor called by subclasses. + */ + protected HeaderBarBase() { + var stage = sceneProperty() + .flatMap(Scene::windowProperty) + .map(w -> w instanceof Stage s ? s : null); + + stage.flatMap(Window::showingProperty) + .orElse(false) + .subscribe(this::onShowingChanged); + + stage.flatMap(Stage::fullScreenProperty) + .orElse(false) + .subscribe(this::onFullScreenChanged); + } + + private void onShowingChanged(boolean showing) { + if (!showing) { + if (metricsSubscription != null) { + metricsSubscription.unsubscribe(); + metricsSubscription = null; + } + } else if (getScene().getWindow() instanceof Stage stage + && StageHelper.getPeer(stage) instanceof WindowStage windowStage) { + ObservableValue metrics = + windowStage.getPlatformWindow().windowOverlayMetrics(); + + if (metrics != null) { + metricsSubscription = metrics.subscribe(this::onMetricsChanged); + } + } + } + + private void onMetricsChanged(WindowOverlayMetrics metrics) { + currentMetrics = metrics; + updateInsets(); + } + + private void onFullScreenChanged(boolean fullScreen) { + currentFullScreen = fullScreen; + updateInsets(); + } + + private void updateInsets() { + if (currentFullScreen || currentMetrics == null) { + leftSystemInset.set(new Dimension2D(0, 0)); + rightSystemInset.set(new Dimension2D(0, 0)); + } else if (currentMetrics.placement() == HorizontalDirection.LEFT) { + leftSystemInset.set(currentMetrics.size()); + rightSystemInset.set(new Dimension2D(0, 0)); + } else if (currentMetrics.placement() == HorizontalDirection.RIGHT) { + leftSystemInset.set(new Dimension2D(0, 0)); + rightSystemInset.set(currentMetrics.size()); + } else { + leftSystemInset.set(new Dimension2D(0, 0)); + rightSystemInset.set(new Dimension2D(0, 0)); + } + } + + /** + * Describes the size of the left system inset, which is an area reserved for the + * minimize, maximize, and close window buttons. If there are no window buttons on + * the left side of the window, the returned area is empty. + */ + private final ReadOnlyObjectWrapper leftSystemInset = + new ReadOnlyObjectWrapper<>(this, "leftInset", new Dimension2D(0, 0)) { + @Override + protected void invalidated() { + requestLayout(); + } + }; + + public final ReadOnlyObjectWrapper leftSystemInsetProperty() { + return leftSystemInset; + } + + public final Dimension2D getLeftSystemInset() { + return leftSystemInset.get(); + } + + /** + * Describes the size of the right system inset, which is an area reserved for the + * minimize, maximize, and close window buttons. If there are no window buttons on + * the right side of the window, the returned area is empty. + */ + private final ReadOnlyObjectWrapper rightSystemInset = + new ReadOnlyObjectWrapper<>(this, "rightInset", new Dimension2D(0, 0)) { + @Override + protected void invalidated() { + requestLayout(); + } + }; + + public final ReadOnlyObjectWrapper rightSystemInsetProperty() { + return rightSystemInset; + } + + public final Dimension2D getRightSystemInset() { + return rightSystemInset.get(); + } +} diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java b/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java index baed126e9e4..26a040f77a2 100644 --- a/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java +++ b/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,12 @@ package javafx.stage; +import javafx.application.ConditionalFeature; +import javafx.application.Platform; +import javafx.scene.Scene; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HeaderBar; + /** * This enum defines the possible styles for a {@code Stage}. * @since JavaFX 2.0 @@ -66,5 +72,40 @@ public enum StageStyle { * NOTE: To see the effect, the {@code Scene} covering the {@code Stage} should have {@code Color.TRANSPARENT} * @since JavaFX 8.0 */ - UNIFIED + UNIFIED, + + /** + * Defines a {@code Stage} style in which the client area is extended into the header bar area, removing + * the separation between the two areas and allowing applications to place scene graph nodes in the header + * bar area of the stage. + *

+ * An extended window has the default window buttons (minimize, maximize, close), but no system-provided + * draggable header bar. Applications need to provide their own header bar by placing the {@link HeaderBar} + * control in the scene graph. Usually, this is combined with a {@link BorderPane} root container: + *

{@code
+     * public class MyApp extends Application {
+     *     @Override
+     *     public void start(Stage stage) {
+     *         var headerBar = new HeaderBar();
+     *         var root = new BorderPane();
+     *         root.setTop(headerBar);
+     *
+     *         stage.setScene(new Scene(root));
+     *         stage.initStyle(StageStyle.EXTENDED);
+     *         stage.show();
+     *     }
+     * }
+     * }
+ *

+ * The color scheme of the default window buttons is adjusted to the {@link Scene#fillProperty() fill} + * of the {@code Scene} to remain easily recognizable. Applications should set the scene fill to a color + * that matches the brightness of the user interface, even if the scene fill is not visible because it + * is obscured by other controls. + *

+ * This is a conditional feature, to check if it is supported see {@link Platform#isSupported(ConditionalFeature)}. + * If the feature is not supported by the platform, this style downgrades to {@link StageStyle#DECORATED}. + * + * @since 24 + */ + EXTENDED } diff --git a/modules/javafx.graphics/src/main/native-glass/gtk/GlassApplication.cpp b/modules/javafx.graphics/src/main/native-glass/gtk/GlassApplication.cpp index 8413aeaf474..66e807aa537 100644 --- a/modules/javafx.graphics/src/main/native-glass/gtk/GlassApplication.cpp +++ b/modules/javafx.graphics/src/main/native-glass/gtk/GlassApplication.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -520,6 +520,7 @@ static void process_events(GdkEvent* event, gpointer data) gtk_main_do_event(event); break; case GDK_BUTTON_PRESS: + case GDK_2BUTTON_PRESS: case GDK_BUTTON_RELEASE: ctx->process_mouse_button(&event->button); break; diff --git a/modules/javafx.graphics/src/main/native-glass/gtk/GlassWindow.cpp b/modules/javafx.graphics/src/main/native-glass/gtk/GlassWindow.cpp index 3bd5bd38c1c..aa66a878fdd 100644 --- a/modules/javafx.graphics/src/main/native-glass/gtk/GlassWindow.cpp +++ b/modules/javafx.graphics/src/main/native-glass/gtk/GlassWindow.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -41,6 +41,9 @@ static WindowFrameType glass_mask_to_window_frame_type(jint mask) { if (mask & com_sun_glass_ui_gtk_GtkWindow_TITLED) { return TITLED; } + if (mask & com_sun_glass_ui_gtk_GtkWindow_EXTENDED) { + return EXTENDED; + } return UNTITLED; } diff --git a/modules/javafx.graphics/src/main/native-glass/gtk/glass_general.cpp b/modules/javafx.graphics/src/main/native-glass/gtk/glass_general.cpp index b75b6fde6ed..d9df201be24 100644 --- a/modules/javafx.graphics/src/main/native-glass/gtk/glass_general.cpp +++ b/modules/javafx.graphics/src/main/native-glass/gtk/glass_general.cpp @@ -83,6 +83,7 @@ jfieldID jWindowPtr; jfieldID jCursorPtr; jmethodID jGtkWindowNotifyStateChanged; +jmethodID jGtkWindowDragAreaHitTest; jmethodID jClipboardContentChanged; @@ -274,8 +275,9 @@ JNI_OnLoad(JavaVM *jvm, void *reserved) clazz = env->FindClass("com/sun/glass/ui/gtk/GtkWindow"); if (env->ExceptionCheck()) return JNI_ERR; - jGtkWindowNotifyStateChanged = - env->GetMethodID(clazz, "notifyStateChanged", "(I)V"); + jGtkWindowNotifyStateChanged = env->GetMethodID(clazz, "notifyStateChanged", "(I)V"); + if (env->ExceptionCheck()) return JNI_ERR; + jGtkWindowDragAreaHitTest = env->GetMethodID(clazz, "dragAreaHitTest", "(II)Z"); if (env->ExceptionCheck()) return JNI_ERR; clazz = env->FindClass("com/sun/glass/ui/Clipboard"); diff --git a/modules/javafx.graphics/src/main/native-glass/gtk/glass_general.h b/modules/javafx.graphics/src/main/native-glass/gtk/glass_general.h index e406550cb8c..f47e32fc952 100644 --- a/modules/javafx.graphics/src/main/native-glass/gtk/glass_general.h +++ b/modules/javafx.graphics/src/main/native-glass/gtk/glass_general.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -195,7 +195,8 @@ struct jni_exception: public std::exception { extern jfieldID jWindowPtr; // com.sun.glass.ui.Window#ptr extern jfieldID jCursorPtr; // com.sun.glass.ui.Cursor#ptr - extern jmethodID jGtkWindowNotifyStateChanged; // com.sun.glass.ui.GtkWindow#notifyStateChanged (I)V + extern jmethodID jGtkWindowNotifyStateChanged; // com.sun.glass.ui.gtk.GtkWindow#notifyStateChanged (I)V + extern jmethodID jGtkWindowDragAreaHitTest; //com.sun.glass.ui.gtk.GtkWindow#dragAreaHitTest (II)Z extern jmethodID jClipboardContentChanged; // com.sun.glass.ui.Clipboard#contentChanged ()V diff --git a/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.cpp b/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.cpp index e11fc79991a..8058e52dcf5 100644 --- a/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.cpp +++ b/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.cpp @@ -51,6 +51,9 @@ #define MOUSE_BACK_BTN 8 #define MOUSE_FORWARD_BTN 9 +// Resize border width of EXTENDED windows +#define RESIZE_BORDER_WIDTH 5 + WindowContext * WindowContextBase::sm_grab_window = NULL; WindowContext * WindowContextBase::sm_mouse_drag_window = NULL; @@ -260,6 +263,11 @@ static inline jint gtk_button_number_to_mouse_button(guint button) { } void WindowContextBase::process_mouse_button(GdkEventButton* event) { + // We only handle single press/release events here. + if (event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) { + return; + } + bool press = event->type == GDK_BUTTON_PRESS; guint state = event->state; guint mask = 0; @@ -648,7 +656,26 @@ void WindowContextBase::set_cursor(GdkCursor* cursor) { WindowContextBase::sm_grab_window->get_gdk_window(), cursor, TRUE); } } - gdk_window_set_cursor(gdk_window, cursor); + + gdk_cursor = cursor; + + if (gdk_cursor_override == NULL) { + gdk_window_set_cursor(gdk_window, cursor); + } +} + +void WindowContextBase::set_cursor_override(GdkCursor* cursor) { + if (gdk_cursor_override == cursor) { + return; + } + + gdk_cursor_override = cursor; + + if (cursor != NULL) { + gdk_window_set_cursor(gdk_window, cursor); + } else { + gdk_window_set_cursor(gdk_window, gdk_cursor); + } } void WindowContextBase::set_background(float r, float g, float b) { @@ -656,6 +683,10 @@ void WindowContextBase::set_background(float r, float g, float b) { gtk_widget_override_background_color(gtk_widget, GTK_STATE_FLAG_NORMAL, &rgba); } +bool WindowContextBase::get_window_edge(int x, int y, GdkWindowEdge* window_edge) { + return false; +} + WindowContextBase::~WindowContextBase() { if (xim.ic) { XDestroyIC(xim.ic); @@ -1366,6 +1397,156 @@ void WindowContextTop::notify_window_move() { } } +/* + * Handles mouse button events of EXTENDED windows and adds the window behaviors for non-client + * regions that are usually provided by the window manager. Note that a full-screen window has + * no non-client regions. + */ +void WindowContextTop::process_mouse_button(GdkEventButton* event) { + // Non-EXTENDED or full-screen windows don't have additional behaviors, so we delegate + // directly to the base implementation. + if (is_fullscreen || frame_type != EXTENDED || jwindow == NULL) { + WindowContextBase::process_mouse_button(event); + return; + } + + // Double-clicking on the drag area maximizes the window (or restores its size). + if (event->type == GDK_2BUTTON_PRESS) { + jboolean dragArea = mainEnv->CallBooleanMethod( + jwindow, jGtkWindowDragAreaHitTest, (jint)event->x, (jint)event->y); + CHECK_JNI_EXCEPTION(mainEnv); + + if (dragArea) { + set_maximized(!is_maximized); + } + + // We don't process the GDK_2BUTTON_PRESS event in the base implementation. + return; + } + + if (event->button == 1 && event->type == GDK_BUTTON_PRESS) { + GdkWindowEdge edge; + bool shouldStartResizeDrag = !is_maximized && get_window_edge(event->x, event->y, &edge); + + // Clicking on a window edge starts a move-resize operation. + if (shouldStartResizeDrag) { + // Send a synthetic PRESS + RELEASE to FX. This allows FX to do things that need to be done + // prior to resizing the window, like closing a popup menu. We do this because we won't be + // sending events to FX once the resize operation has started. + WindowContextBase::process_mouse_button(event); + event->type = GDK_BUTTON_RELEASE; + WindowContextBase::process_mouse_button(event); + + gint rx = 0, ry = 0; + gdk_window_get_root_coords(get_gdk_window(), event->x, event->y, &rx, &ry); + gtk_window_begin_resize_drag(get_gtk_window(), edge, 1, rx, ry, event->time); + return; + } + + bool shouldStartMoveDrag = mainEnv->CallBooleanMethod( + jwindow, jGtkWindowDragAreaHitTest, (jint)event->x, (jint)event->y); + CHECK_JNI_EXCEPTION(mainEnv); + + // Clicking on a draggable area starts a move-drag operation. + if (shouldStartMoveDrag) { + // Send a synthetic PRESS + RELEASE to FX. + WindowContextBase::process_mouse_button(event); + event->type = GDK_BUTTON_RELEASE; + WindowContextBase::process_mouse_button(event); + + gint rx = 0, ry = 0; + gdk_window_get_root_coords(get_gdk_window(), event->x, event->y, &rx, &ry); + gtk_window_begin_move_drag(get_gtk_window(), 1, rx, ry, event->time); + return; + } + } + + // Call the base implementation for client area events. + WindowContextBase::process_mouse_button(event); +} + +/* + * Handles mouse motion events of EXTENDED windows and changes the cursor when it is on top + * of the internal resize border. Note that a full-screen window or maximized window has no + * resize border. + */ +void WindowContextTop::process_mouse_motion(GdkEventMotion* event) { + GdkWindowEdge edge; + + // Call the base implementation for client area events. + if (is_fullscreen + || is_maximized + || frame_type != EXTENDED + || !get_window_edge(event->x, event->y, &edge)) { + set_cursor_override(NULL); + WindowContextBase::process_mouse_motion(event); + return; + } + + static const struct Cursors { + GdkCursor* NORTH = gdk_cursor_new(GDK_TOP_SIDE); + GdkCursor* NORTH_EAST = gdk_cursor_new(GDK_TOP_RIGHT_CORNER); + GdkCursor* EAST = gdk_cursor_new(GDK_RIGHT_SIDE); + GdkCursor* SOUTH_EAST = gdk_cursor_new(GDK_BOTTOM_RIGHT_CORNER); + GdkCursor* SOUTH = gdk_cursor_new(GDK_BOTTOM_SIDE); + GdkCursor* SOUTH_WEST = gdk_cursor_new(GDK_BOTTOM_LEFT_CORNER); + GdkCursor* WEST = gdk_cursor_new(GDK_LEFT_SIDE); + GdkCursor* NORTH_WEST = gdk_cursor_new(GDK_TOP_LEFT_CORNER); + } cursors; + + GdkCursor* cursor = NULL; + + switch (edge) { + case GDK_WINDOW_EDGE_NORTH: cursor = cursors.NORTH; break; + case GDK_WINDOW_EDGE_NORTH_EAST: cursor = cursors.NORTH_EAST; break; + case GDK_WINDOW_EDGE_EAST: cursor = cursors.EAST; break; + case GDK_WINDOW_EDGE_SOUTH_EAST: cursor = cursors.SOUTH_EAST; break; + case GDK_WINDOW_EDGE_SOUTH: cursor = cursors.SOUTH; break; + case GDK_WINDOW_EDGE_SOUTH_WEST: cursor = cursors.SOUTH_WEST; break; + case GDK_WINDOW_EDGE_WEST: cursor = cursors.WEST; break; + case GDK_WINDOW_EDGE_NORTH_WEST: cursor = cursors.NORTH_WEST; break; + } + + set_cursor_override(cursor); + + // If the cursor is not on a resize border, call the base handler. + if (cursor == NULL) { + WindowContextBase::process_mouse_motion(event); + } +} + +/* + * Determines the GdkWindowEdge at the specified coordinate; returns true if the coordinate + * identifies a window edge, false otherwise. + */ +bool WindowContextTop::get_window_edge(int x, int y, GdkWindowEdge* window_edge) { + GdkWindowEdge edge; + gint width, height; + gtk_window_get_size(get_gtk_window(), &width, &height); + + if (x <= RESIZE_BORDER_WIDTH) { + if (y <= 2 * RESIZE_BORDER_WIDTH) edge = GDK_WINDOW_EDGE_NORTH_WEST; + else if (y >= height - 2 * RESIZE_BORDER_WIDTH) edge = GDK_WINDOW_EDGE_SOUTH_WEST; + else edge = GDK_WINDOW_EDGE_WEST; + } else if (x >= width - RESIZE_BORDER_WIDTH) { + if (y <= 2 * RESIZE_BORDER_WIDTH) edge = GDK_WINDOW_EDGE_NORTH_EAST; + else if (y >= height - 2 * RESIZE_BORDER_WIDTH) edge = GDK_WINDOW_EDGE_SOUTH_EAST; + else edge = GDK_WINDOW_EDGE_EAST; + } else if (y <= RESIZE_BORDER_WIDTH) { + edge = GDK_WINDOW_EDGE_NORTH; + } else if (y >= height - RESIZE_BORDER_WIDTH) { + edge = GDK_WINDOW_EDGE_SOUTH; + } else { + return false; + } + + if (window_edge != NULL) { + *window_edge = edge; + } + + return true; +} + void WindowContextTop::process_destroy() { if (owner) { owner->remove_child(this); diff --git a/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.h b/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.h index a7094d98eec..9e2ee1cfe50 100644 --- a/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.h +++ b/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -44,7 +44,8 @@ enum WindowManager { enum WindowFrameType { TITLED, UNTITLED, - TRANSPARENT + TRANSPARENT, + EXTENDED }; enum WindowType { @@ -162,6 +163,7 @@ class WindowContext : public DeletedMemDebug<0xCC> { virtual void increment_events_counter() = 0; virtual void decrement_events_counter() = 0; virtual size_t get_events_count() = 0; + virtual bool get_window_edge(int x, int y, GdkWindowEdge*) = 0; virtual bool is_dead() = 0; virtual ~WindowContext() {} }; @@ -182,6 +184,8 @@ class WindowContextBase: public WindowContext { jobject jview; GtkWidget* gtk_widget; GdkWindow* gdk_window = NULL; + GdkCursor* gdk_cursor = NULL; + GdkCursor* gdk_cursor_override = NULL; GdkWMFunction gdk_windowManagerFunctions; bool is_iconified; @@ -228,6 +232,7 @@ class WindowContextBase: public WindowContext { void ungrab_focus(); void ungrab_mouse_drag_focus(); void set_cursor(GdkCursor*); + void set_cursor_override(GdkCursor*); void set_level(int) {} void set_background(float, float, float); @@ -247,6 +252,7 @@ class WindowContextBase: public WindowContext { void increment_events_counter(); void decrement_events_counter(); size_t get_events_count(); + bool get_window_edge(int x, int y, GdkWindowEdge*); bool is_dead(); ~WindowContextBase(); @@ -284,6 +290,8 @@ class WindowContextTop: public WindowContextBase { void process_state(GdkEventWindowState*); void process_configure(GdkEventConfigure*); void process_destroy(); + void process_mouse_motion(GdkEventMotion*); + void process_mouse_button(GdkEventButton*); void work_around_compiz_state(); WindowFrameExtents get_frame_extents(); @@ -331,6 +339,7 @@ class WindowContextTop: public WindowContextBase { bool effective_on_top(); void notify_window_move(); void notify_window_resize(); + bool get_window_edge(int x, int y, GdkWindowEdge*); WindowContextTop(WindowContextTop&); WindowContextTop& operator= (const WindowContextTop&); }; diff --git a/modules/javafx.graphics/src/main/native-glass/ios/GlassWindow.m b/modules/javafx.graphics/src/main/native-glass/ios/GlassWindow.m index 5b37d31e210..b7425c75d36 100644 --- a/modules/javafx.graphics/src/main/native-glass/ios/GlassWindow.m +++ b/modules/javafx.graphics/src/main/native-glass/ios/GlassWindow.m @@ -816,7 +816,7 @@ jlong _1createWindow(JNIEnv *env, jobject jWindow, jlong jOwnerPtr, jlong jScree BOOL hidden = YES; if (jOwnerPtr == 0L) { // no owner means it is the primary stage; Decorated primary stage shows status bar by default - hidden = ((jStyleMask & com_sun_glass_ui_Window_TITLED) == 0); + hidden = ((jStyleMask & com_sun_glass_ui_Window_DECORATED) == 0); NSObject * values = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIStatusBarHidden"]; //we prefer explicit settings from .plist diff --git a/modules/javafx.graphics/src/main/native-glass/mac/GlassViewDelegate.h b/modules/javafx.graphics/src/main/native-glass/mac/GlassViewDelegate.h index e8cbfc2be57..191cc55cc93 100644 --- a/modules/javafx.graphics/src/main/native-glass/mac/GlassViewDelegate.h +++ b/modules/javafx.graphics/src/main/native-glass/mac/GlassViewDelegate.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -115,4 +115,6 @@ typedef enum GestureMaskType { - (GlassAccessible*)getAccessible; +- (NSEvent*)lastEvent; + @end diff --git a/modules/javafx.graphics/src/main/native-glass/mac/GlassViewDelegate.m b/modules/javafx.graphics/src/main/native-glass/mac/GlassViewDelegate.m index 1b84e04fdba..1b443d7bbd0 100644 --- a/modules/javafx.graphics/src/main/native-glass/mac/GlassViewDelegate.m +++ b/modules/javafx.graphics/src/main/native-glass/mac/GlassViewDelegate.m @@ -1333,4 +1333,9 @@ - (GlassAccessible*)getAccessible return (GlassAccessible*)jlong_to_ptr(accessible); } +- (NSEvent*)lastEvent +{ + return lastEvent; +} + @end diff --git a/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m b/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m index a054ac12c69..dda2b3241bd 100644 --- a/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m +++ b/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m @@ -429,6 +429,10 @@ static jlong _createWindowCommonDo(JNIEnv *env, jobject jWindow, jlong jOwnerPtr styleMask = styleMask|NSWindowStyleMaskMiniaturizable; } + if ((jStyleMask&com_sun_glass_ui_Window_EXTENDED) != 0) { + styleMask = styleMask | NSWindowStyleMaskTitled | NSWindowStyleMaskFullSizeContentView; + } + if ((jStyleMask&com_sun_glass_ui_Window_UNIFIED) != 0) { styleMask = styleMask|NSWindowStyleMaskTexturedBackground; } @@ -454,6 +458,12 @@ static jlong _createWindowCommonDo(JNIEnv *env, jobject jWindow, jlong jOwnerPtr NSScreen *screen = (NSScreen*)jlong_to_ptr(jScreenPtr); window = [[GlassWindow alloc] _initWithContentRect:NSMakeRect(x, y, w, h) styleMask:styleMask screen:screen jwindow:jWindow]; + if ((jStyleMask & com_sun_glass_ui_Window_EXTENDED) != 0) { + [window->nsWindow setTitlebarAppearsTransparent:YES]; + [window->nsWindow setToolbar:[NSToolbar new]]; + [window->nsWindow setToolbarStyle:NSWindowToolbarStyleUnifiedCompact]; + } + if ((jStyleMask & com_sun_glass_ui_Window_UNIFIED) != 0) { //Prevent the textured effect from disappearing on border thickness recalculation [window->nsWindow setAutorecalculatesContentBorderThickness:NO forEdge:NSMaxYEdge]; @@ -472,7 +482,8 @@ static jlong _createWindowCommonDo(JNIEnv *env, jobject jWindow, jlong jOwnerPtr window->owner = getGlassWindow(env, jOwnerPtr)->nsWindow; // not retained (use weak reference?) } window->isResizable = NO; - window->isDecorated = (jStyleMask&com_sun_glass_ui_Window_TITLED) != 0; + window->isDecorated = (jStyleMask&com_sun_glass_ui_Window_TITLED) != 0 || + (jStyleMask&com_sun_glass_ui_Window_EXTENDED) != 0; /* 10.7 full screen window support */ if ([NSWindow instancesRespondToSelector:@selector(toggleFullScreen:)]) { NSWindowCollectionBehavior behavior = [window->nsWindow collectionBehavior]; @@ -1468,3 +1479,50 @@ static jlong _createWindowCommonDo(JNIEnv *env, jobject jWindow, jlong jOwnerPtr GLASS_POOL_EXIT; GLASS_CHECK_EXCEPTION(env); } + +/* + * Class: com_sun_glass_ui_mac_MacWindow + * Method: _performWindowDrag + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_com_sun_glass_ui_mac_MacWindow__1performWindowDrag +(JNIEnv *env, jobject jWindow, jlong jPtr) +{ + LOG("Java_com_sun_glass_ui_mac_MacWindow__1performWindowDrag"); + if (!jPtr) return; + + GLASS_ASSERT_MAIN_JAVA_THREAD(env); + GLASS_POOL_ENTER; + { + GlassWindow *window = getGlassWindow(env, jPtr); + GlassViewDelegate* delegate = [window->view delegate]; + [window->nsWindow performWindowDragWithEvent:[delegate lastEvent]]; + } + GLASS_POOL_EXIT; + GLASS_CHECK_EXCEPTION(env); +} + +/* + * Class: com_sun_glass_ui_mac_MacWindow + * Method: _isRightToLeftLayoutDirection + * Signature: ()Z; + */ +JNIEXPORT jboolean JNICALL Java_com_sun_glass_ui_mac_MacWindow__1isRightToLeftLayoutDirection +(JNIEnv *env, jobject self) +{ + LOG("Java_com_sun_glass_ui_mac_MacWindow__1isRightToLeftLayoutDirection"); + jboolean result = false; + + GLASS_ASSERT_MAIN_JAVA_THREAD(env); + GLASS_POOL_ENTER; + { + NSString* preferredLanguage = [[NSLocale preferredLanguages] objectAtIndex:0]; + NSLocale* locale = [NSLocale localeWithLocaleIdentifier:preferredLanguage]; + NSString* languageCode = [locale objectForKey:NSLocaleLanguageCode]; + result = [NSLocale characterDirectionForLanguage:languageCode] == NSLocaleLanguageDirectionRightToLeft; + } + GLASS_POOL_EXIT; + GLASS_CHECK_EXCEPTION(env); + + return result; +} diff --git a/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp b/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp index 99b2db7f543..02a962c92e4 100644 --- a/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp +++ b/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -41,6 +41,7 @@ #include "com_sun_glass_ui_Window_Level.h" #include "com_sun_glass_ui_win_WinWindow.h" +#define ABM_GETAUTOHIDEBAREX 0x0000000b // multimon aware autohide bars // Helper LEAVE_MAIN_THREAD for GlassWindow #define LEAVE_MAIN_THREAD_WITH_hWnd \ @@ -62,7 +63,8 @@ HHOOK GlassWindow::sm_hCBTFilter = NULL; HWND GlassWindow::sm_grabWindow = NULL; static HWND activeTouchWindow = NULL; -GlassWindow::GlassWindow(jobject jrefThis, bool isTransparent, bool isDecorated, bool isUnified, HWND parentOrOwner) +GlassWindow::GlassWindow(jobject jrefThis, bool isTransparent, bool isDecorated, bool isUnified, + bool isExtended, HWND parentOrOwner) : BaseWnd(parentOrOwner), ViewContainer(), m_winChangingReason(Unknown), @@ -74,6 +76,7 @@ GlassWindow::GlassWindow(jobject jrefThis, bool isTransparent, bool isDecorated, m_isTransparent(isTransparent), m_isDecorated(isDecorated), m_isUnified(isUnified), + m_isExtended(isExtended), m_hMenu(NULL), m_alpha(255), m_isEnabled(true), @@ -452,7 +455,18 @@ LRESULT GlassWindow::WindowProc(UINT msg, WPARAM wParam, LPARAM lParam) // p->rgrc[0].bottom++; // return WVR_VALIDRECTS; // } + + if (BOOL(wParam) && m_isExtended) { + return HandleNCCalcSizeEvent(msg, wParam, lParam); + } + break; + case WM_NCHITTEST: { + LRESULT res; + if (m_isExtended && HandleNCHitTestEvent(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), res)) { + return res; + } break; + } case WM_PAINT: HandleViewPaintEvent(GetHWND(), msg, wParam, lParam); break; @@ -477,25 +491,11 @@ LRESULT GlassWindow::WindowProc(UINT msg, WPARAM wParam, LPARAM lParam) case WM_MOUSEHWHEEL: case WM_MOUSELEAVE: case WM_MOUSEMOVE: - if (IsEnabled()) { - if (msg == WM_MOUSELEAVE && GetDelegateWindow()) { - // Skip generating MouseEvent.EXIT when entering FullScreen - return 0; - } - BOOL handled = HandleViewMouseEvent(GetHWND(), msg, wParam, lParam); - if (handled && msg == WM_RBUTTONUP) { - // By default, DefWindowProc() sends WM_CONTEXTMENU from WM_LBUTTONUP - // Since DefWindowProc() is not called, call the mouse menu handler directly - HandleViewMenuEvent(GetHWND(), WM_CONTEXTMENU, (WPARAM) GetHWND(), ::GetMessagePos ()); - //::DefWindowProc(GetHWND(), msg, wParam, lParam); - } - if (handled) { - // Do not call the DefWindowProc() for mouse events that were handled - return 0; - } - } else { + if (!IsEnabled()) { HandleFocusDisabledEvent(); return 0; + } else if (HandleMouseEvents(msg, wParam, lParam)) { + return 0; } break; case WM_CAPTURECHANGED: @@ -546,8 +546,37 @@ LRESULT GlassWindow::WindowProc(UINT msg, WPARAM wParam, LPARAM lParam) case WM_NCXBUTTONDOWN: UngrabFocus(); // ungrab itself CheckUngrab(); // check if other owned windows hierarchy holds the grab + + if (m_isExtended) { + HandleViewNonClientMouseEvent(GetHWND(), msg, wParam, lParam); + + // We need to handle clicks on the min/max/close regions, as otherwise Windows will + // draw very ugly buttons on top of our window. + if (wParam == HTMINBUTTON || wParam == HTMAXBUTTON || wParam == HTCLOSE) { + return 0; + } + } + // Pass the event to DefWindowProc() break; + case WM_NCLBUTTONUP: + case WM_NCLBUTTONDBLCLK: + case WM_NCRBUTTONUP: + case WM_NCRBUTTONDBLCLK: + case WM_NCMBUTTONUP: + case WM_NCMBUTTONDBLCLK: + case WM_NCXBUTTONUP: + case WM_NCXBUTTONDBLCLK: + case WM_NCMOUSELEAVE: + case WM_NCMOUSEMOVE: + if (m_isExtended) { + HandleViewNonClientMouseEvent(GetHWND(), msg, wParam, lParam); + + if (wParam == HTMINBUTTON || wParam == HTMAXBUTTON || wParam == HTCLOSE) { + return 0; + } + } + break; case WM_TOUCH: if (IsEnabled()) { if (activeTouchWindow == 0 || activeTouchWindow == GetHWND()) { @@ -573,6 +602,29 @@ LRESULT GlassWindow::WindowProc(UINT msg, WPARAM wParam, LPARAM lParam) return ::DefWindowProc(GetHWND(), msg, wParam, lParam); } +bool GlassWindow::HandleMouseEvents(UINT msg, WPARAM wParam, LPARAM lParam) +{ + if (msg == WM_MOUSELEAVE && GetDelegateWindow()) { + // Skip generating MouseEvent.EXIT when entering FullScreen + return true; + } + + BOOL handled = HandleViewMouseEvent(GetHWND(), msg, wParam, lParam); + if (handled && msg == WM_RBUTTONUP) { + // By default, DefWindowProc() sends WM_CONTEXTMENU from WM_LBUTTONUP + // Since DefWindowProc() is not called, call the mouse menu handler directly + HandleViewMenuEvent(GetHWND(), WM_CONTEXTMENU, (WPARAM) GetHWND(), ::GetMessagePos ()); + //::DefWindowProc(GetHWND(), msg, wParam, lParam); + } + + if (handled) { + // Do not call the DefWindowProc() for mouse events that were handled + return true; + } + + return false; +} + void GlassWindow::HandleCloseEvent() { JNIEnv* env = GetEnv(); @@ -763,6 +815,85 @@ void GlassWindow::HandleFocusDisabledEvent() CheckAndClearException(env); } +LRESULT GlassWindow::HandleNCCalcSizeEvent(UINT msg, WPARAM wParam, LPARAM lParam) +{ + // Capture the top and size before DefWindowProc applies the default frame. + NCCALCSIZE_PARAMS *p = (NCCALCSIZE_PARAMS*)lParam; + LONG originalTop = p->rgrc[0].top; + RECT originalSize = p->rgrc[0]; + + // Apply the default window frame. + LRESULT res = DefWindowProc(GetHWND(), msg, wParam, lParam); + if (res != 0) { + return res; + } + + // Restore the original top, which might have been overwritten by DefWindowProc. + RECT newSize = p->rgrc[0]; + newSize.top = originalTop; + + // A maximized window extends slightly beyond the screen, so we need to account for that + // by adding the border width to the top. + bool maximized = (::GetWindowLong(GetHWND(), GWL_STYLE) & WS_MAXIMIZE) != 0; + if (maximized && !m_isInFullScreen) { + newSize.top += ::GetSystemMetrics(SM_CXPADDEDBORDER) + ::GetSystemMetrics(SM_CYSIZEFRAME); + } + + // If we have an auto-hide taskbar, we need to reduce the size of a maximized or fullscreen + // window a little bit where the taskbar is located, as otherwise the taskbar cannot be + // summoned. + HMONITOR monitor = ::MonitorFromWindow(GetHWND(), MONITOR_DEFAULTTONEAREST); + if (monitor && (maximized || m_isInFullScreen)) { + MONITORINFO monitorInfo = { 0 }; + monitorInfo.cbSize = sizeof(MONITORINFO); + ::GetMonitorInfo(monitor, &monitorInfo); + + APPBARDATA data = { 0 }; + data.cbSize = sizeof(data); + + if ((::SHAppBarMessage(ABM_GETSTATE, &data) & ABS_AUTOHIDE) == ABS_AUTOHIDE) { + data.rc = monitorInfo.rcMonitor; + DWORD appBarMsg = ::IsWindows8OrGreater() ? ABM_GETAUTOHIDEBAREX : ABM_GETAUTOHIDEBAR; + + // Reduce the window size by one pixel on the taskbar side. + if ((data.uEdge = ABE_TOP), ::SHAppBarMessage(appBarMsg, &data) != NULL) { + newSize.top += 1; + } else if ((data.uEdge = ABE_BOTTOM), ::SHAppBarMessage(appBarMsg, &data) != NULL) { + newSize.bottom -= 1; + } else if ((data.uEdge = ABE_LEFT), ::SHAppBarMessage(appBarMsg, &data) != NULL) { + newSize.left += 1; + } else if ((data.uEdge = ABE_RIGHT), ::SHAppBarMessage(appBarMsg, &data) != NULL) { + newSize.right -= 1; + } + } + } + + p->rgrc[0] = newSize; + return 0; +} + +// Handling this message tells Windows which parts of the window are non-client regions. +// This enables window behaviors like dragging or snap layouts. +BOOL GlassWindow::HandleNCHitTestEvent(SHORT x, SHORT y, LRESULT& result) +{ + if (::DefWindowProc(GetHWND(), WM_NCHITTEST, 0, MAKELONG(x, y)) != HTCLIENT) { + return FALSE; + } + + POINT pt = { x, y }; + + if (!::ScreenToClient(GetHWND(), &pt)) { + return FALSE; + } + + JNIEnv* env = GetEnv(); + jint res = env->CallIntMethod(m_grefThis, javaIDs.WinWindow.nonClientHitTest, pt.x, pt.y); + CheckAndClearException(env); + result = LRESULT(res); + + return TRUE; +} + bool GlassWindow::HandleCommand(WORD cmdID) { return HandleMenuCommand(GetHWND(), cmdID); } @@ -1145,6 +1276,10 @@ JNIEXPORT void JNICALL Java_com_sun_glass_ui_win_WinWindow__1initIDs javaIDs.Window.notifyDelegatePtr = env->GetMethodID(cls, "notifyDelegatePtr", "(J)V"); ASSERT(javaIDs.Window.notifyDelegatePtr); if (env->ExceptionCheck()) return; + + javaIDs.WinWindow.nonClientHitTest = env->GetMethodID(cls, "nonClientHitTest", "(II)I"); + ASSERT(javaIDs.WinWindow.nonClientHitTest); + if (env->ExceptionCheck()) return; } /* @@ -1164,6 +1299,10 @@ JNIEXPORT jlong JNICALL Java_com_sun_glass_ui_win_WinWindow__1createWindow dwStyle = WS_CLIPCHILDREN | WS_SYSMENU; closeable = (mask & com_sun_glass_ui_Window_CLOSABLE) != 0; + if (mask & com_sun_glass_ui_Window_EXTENDED) { + mask |= com_sun_glass_ui_Window_TITLED; + } + if (mask & com_sun_glass_ui_Window_TITLED) { dwExStyle = WS_EX_WINDOWEDGE; dwStyle |= WS_CAPTION; @@ -1206,6 +1345,7 @@ JNIEXPORT jlong JNICALL Java_com_sun_glass_ui_win_WinWindow__1createWindow (mask & com_sun_glass_ui_Window_TRANSPARENT) != 0, (mask & com_sun_glass_ui_Window_TITLED) != 0, (mask & com_sun_glass_ui_Window_UNIFIED) != 0, + (mask & com_sun_glass_ui_Window_EXTENDED) != 0, owner); HWND hWnd = pWindow->Create(dwStyle, dwExStyle, hMonitor, owner); diff --git a/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.h b/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.h index a56b060f340..72c986ccc9a 100644 --- a/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.h +++ b/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,7 +32,8 @@ class GlassWindow : public BaseWnd, public ViewContainer { public: - GlassWindow(jobject jrefThis, bool isTransparent, bool isDecorated, bool isUnified, HWND parentOrOwner); + GlassWindow(jobject jrefThis, bool isTransparent, bool isDecorated, bool isUnified, + bool isExtended, HWND parentOrOwner); virtual ~GlassWindow(); static GlassWindow* FromHandle(HWND hWnd) { @@ -144,6 +145,7 @@ class GlassWindow : public BaseWnd, public ViewContainer { const bool m_isTransparent; const bool m_isDecorated; const bool m_isUnified; + const bool m_isExtended; bool m_isResizable; @@ -184,6 +186,9 @@ class GlassWindow : public BaseWnd, public ViewContainer { void HandleDPIEvent(WPARAM wParam, LPARAM lParam); bool HandleCommand(WORD cmdID); void HandleFocusDisabledEvent(); + bool HandleMouseEvents(UINT msg, WPARAM wParam, LPARAM lParam); + LRESULT HandleNCCalcSizeEvent(UINT msg, WPARAM wParam, LPARAM lParam); + BOOL HandleNCHitTestEvent(SHORT, SHORT, LRESULT&); }; diff --git a/modules/javafx.graphics/src/main/native-glass/win/Utils.h b/modules/javafx.graphics/src/main/native-glass/win/Utils.h index 3ff428aabae..1c757c739cb 100644 --- a/modules/javafx.graphics/src/main/native-glass/win/Utils.h +++ b/modules/javafx.graphics/src/main/native-glass/win/Utils.h @@ -490,6 +490,9 @@ typedef struct _tagJavaIDs { jmethodID notifyDestroy; jmethodID notifyDelegatePtr; } Window; + struct { + jmethodID nonClientHitTest; + } WinWindow; struct { jmethodID notifyResize; jmethodID notifyRepaint; diff --git a/modules/javafx.graphics/src/main/native-glass/win/ViewContainer.cpp b/modules/javafx.graphics/src/main/native-glass/win/ViewContainer.cpp index 97fdce3cfec..73e36b6893a 100644 --- a/modules/javafx.graphics/src/main/native-glass/win/ViewContainer.cpp +++ b/modules/javafx.graphics/src/main/native-glass/win/ViewContainer.cpp @@ -934,6 +934,128 @@ BOOL ViewContainer::HandleViewMouseEvent(HWND hwnd, UINT msg, WPARAM wParam, LPA return TRUE; } +void ViewContainer::HandleViewNonClientMouseEvent(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + if (!GetGlassView()) { + return; + } + + int type = 0; + int button = com_sun_glass_events_MouseEvent_BUTTON_NONE; + POINT pt; // client coords + + if (msg == WM_NCMOUSELEAVE) { + type = com_sun_glass_events_MouseEvent_NC_EXIT; + pt.x = GET_X_LPARAM(lParam); + pt.y = GET_Y_LPARAM(lParam); + ::MapWindowPoints(NULL, hwnd, &pt, 1); + m_bTrackingMouse = FALSE; + m_lastMouseMovePosition = -1; + } else if (msg >= WM_NCMOUSEMOVE && msg <= WM_NCXBUTTONDBLCLK) { + pt.x = GET_X_LPARAM(lParam); + pt.y = GET_Y_LPARAM(lParam); + ::MapWindowPoints(NULL, hwnd, &pt, 1); + + switch (msg) { + case WM_NCMOUSEMOVE: + if (lParam == m_lastMouseMovePosition) { + // Avoid sending synthetic NC_MOVE events if + // the pointer hasn't moved actually. + // Just consume the messages. + return; + } + + m_lastMouseMovePosition = lParam; + type = com_sun_glass_events_MouseEvent_NC_MOVE; + break; + case WM_NCLBUTTONDOWN: + type = com_sun_glass_events_MouseEvent_NC_DOWN; + button = com_sun_glass_events_MouseEvent_BUTTON_LEFT; + break; + case WM_NCLBUTTONUP: + type = com_sun_glass_events_MouseEvent_NC_UP; + button = com_sun_glass_events_MouseEvent_BUTTON_LEFT; + break; + case WM_NCRBUTTONDOWN: + type = com_sun_glass_events_MouseEvent_NC_DOWN; + button = com_sun_glass_events_MouseEvent_BUTTON_RIGHT; + break; + case WM_NCRBUTTONUP: + type = com_sun_glass_events_MouseEvent_NC_UP; + button = com_sun_glass_events_MouseEvent_BUTTON_RIGHT; + break; + case WM_NCMBUTTONDOWN: + type = com_sun_glass_events_MouseEvent_NC_DOWN; + button = com_sun_glass_events_MouseEvent_BUTTON_OTHER; + break; + case WM_NCMBUTTONUP: + type = com_sun_glass_events_MouseEvent_NC_UP; + button = com_sun_glass_events_MouseEvent_BUTTON_OTHER; + break; + case WM_NCXBUTTONDOWN: + type = com_sun_glass_events_MouseEvent_NC_DOWN; + button = GET_XBUTTON_WPARAM(wParam) == XBUTTON1 + ? com_sun_glass_events_MouseEvent_BUTTON_BACK + : com_sun_glass_events_MouseEvent_BUTTON_FORWARD; + break; + case WM_NCXBUTTONUP: + type = com_sun_glass_events_MouseEvent_NC_UP; + button = GET_XBUTTON_WPARAM(wParam) == XBUTTON1 + ? com_sun_glass_events_MouseEvent_BUTTON_BACK + : com_sun_glass_events_MouseEvent_BUTTON_FORWARD; + break; + } + } + + // Event was not handled + if (type == 0) { + return; + } + + // get screen coords + POINT ptAbs = pt; + ::ClientToScreen(hwnd, &ptAbs); + + // unmirror the x coordinate + LONG style = ::GetWindowLong(hwnd, GWL_EXSTYLE); + if (style & WS_EX_LAYOUTRTL) { + RECT rect = {0}; + ::GetClientRect(hwnd, &rect); + pt.x = max(0, rect.right - rect.left) - pt.x; + } + + jint jModifiers = GetModifiers(); + jboolean isSynthesized = jboolean(IsTouchEvent()); + JNIEnv* env = GetEnv(); + + if (!m_bTrackingMouse && type != com_sun_glass_events_MouseEvent_NC_EXIT) { + TRACKMOUSEEVENT trackData; + trackData.cbSize = sizeof(trackData); + trackData.dwFlags = TME_LEAVE | TME_NONCLIENT; + trackData.hwndTrack = hwnd; + trackData.dwHoverTime = HOVER_DEFAULT; + + if (::TrackMouseEvent(&trackData)) { + // Mouse tracking will be canceled automatically upon receiving WM_NCMOUSELEAVE + m_bTrackingMouse = TRUE; + } + + env->CallVoidMethod(GetView(), javaIDs.View.notifyMouse, + com_sun_glass_events_MouseEvent_NC_ENTER, + com_sun_glass_events_MouseEvent_BUTTON_NONE, + pt.x, pt.y, ptAbs.x, ptAbs.y, + jModifiers, JNI_FALSE, isSynthesized); + CheckAndClearException(env); + } + + env->CallVoidMethod(GetView(), javaIDs.View.notifyMouse, + type, button, pt.x, pt.y, ptAbs.x, ptAbs.y, + jModifiers, + type == com_sun_glass_events_MouseEvent_NC_UP && button == com_sun_glass_events_MouseEvent_BUTTON_RIGHT, + isSynthesized); + CheckAndClearException(env); +} + void ViewContainer::NotifyCaptureChanged(HWND hwnd, HWND to) { m_mouseButtonDownCounter = 0; @@ -948,7 +1070,7 @@ void ViewContainer::ResetMouseTracking(HWND hwnd) // We don't expect WM_MOUSELEAVE anymore, so we cancel mouse tracking manually TRACKMOUSEEVENT trackData; trackData.cbSize = sizeof(trackData); - trackData.dwFlags = TME_LEAVE | TME_CANCEL; + trackData.dwFlags = TME_LEAVE | TME_NONCLIENT | TME_CANCEL; trackData.hwndTrack = hwnd; trackData.dwHoverTime = HOVER_DEFAULT; ::TrackMouseEvent(&trackData); diff --git a/modules/javafx.graphics/src/main/native-glass/win/ViewContainer.h b/modules/javafx.graphics/src/main/native-glass/win/ViewContainer.h index 5d9de1436c1..0eea989d0c0 100644 --- a/modules/javafx.graphics/src/main/native-glass/win/ViewContainer.h +++ b/modules/javafx.graphics/src/main/native-glass/win/ViewContainer.h @@ -70,6 +70,7 @@ class ViewContainer { void HandleViewDeadKeyEvent(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); void HandleViewTypedEvent(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); BOOL HandleViewMouseEvent(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + void HandleViewNonClientMouseEvent(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); BOOL HandleViewInputMethodEvent(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); LRESULT HandleViewGetAccessible(HWND hwnd, WPARAM wParam, LPARAM lParam); diff --git a/modules/javafx.graphics/src/main/native-glass/win/common.h b/modules/javafx.graphics/src/main/native-glass/win/common.h index 6b237da61de..67ca2bbc49d 100644 --- a/modules/javafx.graphics/src/main/native-glass/win/common.h +++ b/modules/javafx.graphics/src/main/native-glass/win/common.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -59,6 +59,8 @@ #include #include #include +#include +#include #include "Utils.h" #include "OleUtils.h" diff --git a/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/gtk/WindowDecorationGnome.css b/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/gtk/WindowDecorationGnome.css new file mode 100644 index 00000000000..8573e492ea0 --- /dev/null +++ b/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/gtk/WindowDecorationGnome.css @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +.window-button-container { + -fx-button-placement: right; + -fx-allow-rtl: true; +} + +.minimize-button, +.maximize-button, +.close-button { + -fx-background-color: #00000009; + -fx-background-radius: 15; + -fx-background-insets: 6; + -fx-pref-width: 36; + -fx-pref-height: 36; +} + +.minimize-button.dark, +.maximize-button.dark, +.close-button.dark { + -fx-background-color: #ffffff09; +} + +.minimize-button:active, +.maximize-button:active, +.close-button:active { + -fx-background-color: #00000015; +} + +.minimize-button.dark:active, +.maximize-button.dark:active, +.close-button.dark:active { + -fx-background-color: #ffffff15 +} + +.minimize-button:active:hover, +.maximize-button:active:hover, +.close-button:active:hover { + -fx-background-color: #00000025; +} + +.minimize-button.dark:active:hover, +.maximize-button.dark:active:hover, +.close-button.dark:active:hover { + -fx-background-color: #ffffff25; +} + +.minimize-button:pressed, +.maximize-button:pressed, +.close-button:pressed { + -fx-background-color: #00000035 !important; +} + +.minimize-button.dark:pressed, +.maximize-button.dark:pressed, +.close-button.dark:pressed { + -fx-background-color: #ffffff35 !important; +} + +.minimize-button > .glyph, +.maximize-button > .glyph, +.close-button > .glyph { + -fx-background-color: #777; + -fx-background-insets: 6 -6 -6 6; + -fx-scale-shape: false; + -fx-position-shape: false; +} + +.minimize-button:active > .glyph, +.maximize-button:active > .glyph, +.close-button:active > .glyph { + -fx-background-color: #333; +} + +.minimize-button.dark:active > .glyph, +.maximize-button.dark:active > .glyph, +.close-button.dark:active > .glyph { + -fx-background-color: white; +} + +.maximize-button:disabled { + -fx-background-color: transparent; +} + +.maximize-button:disabled > .glyph { + -fx-background-color: #777; +} + +.minimize-button > .glyph { + -fx-shape: "m 8,13 v 1 h 8 v -1 z"; +} + +.maximize-button > .glyph { + -fx-shape: "M 15,8.934 V 15 H 9 V 8.934 Z M 8,8 v 8 h 8 V 8 Z"; +} + +.maximize-button.restore > .glyph { + -fx-shape: "M 10 7 L 10 8 L 16 8 L 16 14 L 17 14 L 17 7 L 10 7 z M 8 9 L 8 16 L 15 16 L 15 9 L 8 9 z M 9 9.9238281 L 14 9.9238281 L 14 15 L 9 15 L 9 9.9238281 z"; +} + +.close-button > .glyph { + -fx-shape: "m 8.1464844,8.1464844 a 0.5,0.5 0 0 0 0,0.7070312 L 11.292969,12 8.1464844,15.146484 a 0.5,0.5 0 0 0 0,0.707032 0.5,0.5 0 0 0 0.7070312,0 L 12,12.707031 l 3.146484,3.146485 a 0.5,0.5 0 0 0 0.707032,0 0.5,0.5 0 0 0 0,-0.707032 L 12.707031,12 15.853516,8.8535156 a 0.5,0.5 0 0 0 0,-0.7070312 0.5,0.5 0 0 0 -0.707032,0 L 12,11.292969 8.8535156,8.1464844 a 0.5,0.5 0 0 0 -0.7070312,0 z"; +} diff --git a/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/gtk/WindowDecorationKDE.css b/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/gtk/WindowDecorationKDE.css new file mode 100644 index 00000000000..c9caacc6fd8 --- /dev/null +++ b/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/gtk/WindowDecorationKDE.css @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +.window-button-container { + -fx-button-placement: right; + -fx-allow-rtl: true; +} + +.minimize-button, +.maximize-button, +.close-button { + -fx-background-color: transparent; + -fx-background-radius: 13; + -fx-background-insets: 8; + -fx-pref-width: 36; + -fx-pref-height: 36; +} + +.minimize-button > .glyph, +.maximize-button > .glyph, +.close-button > .glyph { + -fx-background-color: #1d1d1e; + -fx-background-insets: 8 -8 -8 8; + -fx-scale-shape: false; + -fx-position-shape: false; +} + +.minimize-button.dark > .glyph, +.maximize-button.dark > .glyph, +.close-button.dark > .glyph { + -fx-background-color: #ced0d6; +} + +.minimize-button:hover, +.maximize-button:hover, +.close-button:hover { + -fx-background-color: #27292d; +} + +.minimize-button.dark:hover, +.maximize-button.dark:hover, +.close-button.dark:hover { + -fx-background-color: #dfe1e6; +} + +.minimize-button:hover > .glyph, +.maximize-button:hover > .glyph, +.close-button:hover > .glyph { + -fx-background-color: #ced0d6; +} + +.minimize-button.dark:hover > .glyph, +.maximize-button.dark:hover > .glyph, +.close-button.dark:hover > .glyph { + -fx-background-color: #393b40; +} + +.minimize-button:pressed, +.maximize-button:pressed, +.close-button:pressed { + -fx-background-color: #474b52 !important; +} + +.minimize-button.dark:pressed, +.maximize-button.dark:pressed, +.close-button.dark:pressed { + -fx-background-color: #6c7076 !important; +} + +.maximize-button:disabled { + -fx-managed: false; + visibility: hidden; +} + +.minimize-button > .glyph { + -fx-shape: "m 5,7.5 a 0.5,0.5 0 0 0 -0.3535156,0.1464844 0.5,0.5 0 0 0 0,0.7070312 l 5,5.0000004 a 0.50005,0.50005 0 0 0 0.7070316,0 l 5,-5.0000004 a 0.5,0.5 0 0 0 0,-0.7070312 0.5,0.5 0 0 0 -0.707032,0 L 10,12.292969 5.3535156,7.6464844 A 0.5,0.5 0 0 0 5,7.5 Z"; +} + +.maximize-button > .glyph { + -fx-shape: "m 9.6464844,6.6464844 -5,4.9999996 a 0.5,0.5 0 0 0 0,0.707032 0.5,0.5 0 0 0 0.7070312,0 L 10,7.7070312 14.646484,12.353516 a 0.5,0.5 0 0 0 0.707032,0 0.5,0.5 0 0 0 0,-0.707032 l -5,-4.9999996 a 0.50005,0.50005 0 0 0 -0.7070316,0 z"; +} + +.maximize-button.restore > .glyph { + -fx-shape: "m 9.6464844,5.1464844 -4.5,4.5 a 0.50005,0.50005 0 0 0 0,0.7070316 l 4.5,4.5 a 0.50005,0.50005 0 0 0 0.7070316,0 l 4.5,-4.5 a 0.50005,0.50005 0 0 0 0,-0.7070316 l -4.5,-4.5 a 0.50005,0.50005 0 0 0 -0.7070316,0 z M 10,6.2070312 13.792969,10 10,13.792969 6.2070312,10 Z"; +} + +.close-button > .glyph { + -fx-shape: "m 6,5.5 a 0.5,0.5 0 0 0 -0.3535156,0.1464844 0.5,0.5 0 0 0 0,0.7070312 L 9.2929688,10 5.6464844,13.646484 a 0.5,0.5 0 0 0 0,0.707032 0.5,0.5 0 0 0 0.7070312,0 L 10,10.707031 l 3.646484,3.646485 a 0.5,0.5 0 0 0 0.707032,0 0.5,0.5 0 0 0 0,-0.707032 L 10.707031,10 14.353516,6.3535156 a 0.5,0.5 0 0 0 0,-0.7070312 0.5,0.5 0 0 0 -0.707032,0 L 10,9.2929688 6.3535156,5.6464844 A 0.5,0.5 0 0 0 6,5.5 Z"; +} diff --git a/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/win/WindowDecoration.css b/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/win/WindowDecoration.css new file mode 100644 index 00000000000..15b918f803e --- /dev/null +++ b/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/win/WindowDecoration.css @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +.window-button-container { + -fx-button-placement: right; + -fx-allow-rtl: true; +} + +.minimize-button, +.maximize-button, +.close-button { + -fx-background-color: transparent; + -fx-pref-width: 46; + -fx-pref-height: 29; +} + +.minimize-button:hover, +.maximize-button:hover { + -fx-background-color: #00000015; +} + +.minimize-button.dark:hover, +.maximize-button.dark:hover { + -fx-background-color: #ffffff15; +} + +.close-button:hover { + -fx-background-color: #c42b1c; +} + +.minimize-button:pressed, +.maximize-button:pressed, +.close-button:pressed { + -fx-opacity: 0.8; +} + +.minimize-button > .glyph, +.maximize-button > .glyph, +.close-button > .glyph { + -fx-background-color: #777; + -fx-scale-shape: false; +} + +.minimize-button:active > .glyph, +.maximize-button:active > .glyph, +.close-button:active > .glyph { + -fx-background-color: black; +} + +.minimize-button.dark:active > .glyph, +.maximize-button.dark:active > .glyph, +.close-button.dark:active > .glyph { + -fx-background-color: white; +} + +.close-button:hover > .glyph { + -fx-background-color: white; +} + +.minimize-button.dark:pressed > .glyph { + -fx-background-color: white; +} + +.maximize-button:disabled { + -fx-background-color: transparent !important; +} + +.maximize-button:disabled > .glyph { + -fx-background-color: #00000020 !important; +} + +.maximize-button.dark:disabled > .glyph { + -fx-background-color: #ffffff20 !important; +} + +.minimize-button > .glyph { + -fx-shape: "m 0.42342443,4.6755 q -0.0871756,0 -0.16397319,-0.0332 Q 0.18265374,4.6091 0.1245366,4.551 0.06641952,4.4929 0.03320975,4.41608 0,4.33925 0,4.25208 0,4.16488 0.03320975,4.0881 0.06641952,4.0113 0.1245366,3.95112 0.1826537,3.89092 0.25945124,3.85772 0.33624881,3.8245 0.42342443,3.8245 H 8.078274 q 0.087175,0 0.1639731,0.0332 0.076797,0.0332 0.1349147,0.0934 0.058117,0.0602 0.091327,0.13698 0.033209,0.0768 0.033209,0.16397 0,0.0872 -0.03321,0.16397 -0.03321,0.0768 -0.091327,0.13492 -0.058117,0.0581 -0.1349147,0.0913 -0.076797,0.0332 -0.1639731,0.0332 z"; +} + +.maximize-button > .glyph { + -fx-shape: "M 1.253418,8.5 Q 1.0043945,8.5 0.77612305,8.3983154 0.54785156,8.2966309 0.37561035,8.1243896 0.20336914,7.9521484 0.10168457,7.723877 0,7.4956055 0,7.246582 V 1.253418 Q 0,1.0043945 0.10168457,0.77612305 0.20336914,0.54785156 0.37561035,0.37561035 0.54785156,0.20336914 0.77612305,0.10168457 1.0043945,0 1.253418,0 H 7.246582 Q 7.4956055,0 7.723877,0.10168457 7.9521484,0.20336914 8.1243896,0.37561035 8.2966309,0.54785156 8.3983154,0.77612305 8.5,1.0043945 8.5,1.253418 V 7.246582 Q 8.5,7.4956055 8.3983154,7.723877 8.2966309,7.9521484 8.1243896,8.1243896 7.9521484,8.2966309 7.723877,8.3983154 7.4956055,8.5 7.246582,8.5 Z M 7.2258301,7.6491699 q 0.087158,0 0.1639404,-0.033203 0.076782,-0.033203 0.1348877,-0.091309 0.058105,-0.058105 0.091309,-0.1348877 0.033203,-0.076782 0.033203,-0.1639404 V 1.2741699 q 0,-0.087158 -0.033203,-0.1639404 Q 7.5827637,1.0334473 7.5246582,0.9753418 7.4665527,0.91723633 7.3897705,0.8840332 7.3129883,0.85083008 7.2258301,0.85083008 H 1.2741699 q -0.087158,0 -0.1639404,0.0332031 -0.076782,0.0332031 -0.1348877,0.0913086 Q 0.9172363,1.0334473 0.8840332,1.1102295 0.8508301,1.1870115 0.8508301,1.2741699 v 5.9516602 q 0,0.087158 0.0332031,0.1639404 0.0332031,0.076782 0.0913086,0.1348877 0.0581055,0.058105 0.1348877,0.091309 0.076782,0.033203 0.1639404,0.033203 z"; +} + +.maximize-button.restore > .glyph { + -fx-shape: "m 7.6491699,2.5192871 q 0,-0.3444824 -0.1369629,-0.6495361 Q 7.3752441,1.5646973 7.1407471,1.338501 6.90625,1.1123047 6.5970459,0.98156738 6.2878418,0.85083008 5.9475098,0.85083008 H 1.7722168 Q 1.838623,0.65991211 1.9589844,0.50219727 2.0793457,0.34448242 2.2370605,0.23242188 2.3947754,0.12036133 2.5836182,0.06018066 2.7724609,0 2.9758301,0 H 5.9475098 Q 6.4746094,0 6.9394531,0.20129395 7.4042969,0.40258789 7.7508545,0.74707031 8.0974121,1.0915527 8.2987061,1.5563965 8.5,2.0212402 8.5,2.5483398 V 5.5241699 Q 8.5,5.7275391 8.4398193,5.9163818 8.3796387,6.1052246 8.2675781,6.2629395 8.1555176,6.4206543 7.9978027,6.5410156 7.8400879,6.661377 7.6491699,6.7277832 Z M 1.253418,8.5 Q 1.0043945,8.5 0.77612305,8.3983154 0.54785156,8.2966309 0.37561035,8.1243896 0.20336914,7.9521484 0.10168457,7.723877 0,7.4956055 0,7.246582 V 2.9550781 Q 0,2.7019043 0.10168457,2.475708 0.20336914,2.2495117 0.37561035,2.0772705 0.54785156,1.9050293 0.77404785,1.8033447 1.0002441,1.7016602 1.253418,1.7016602 h 4.2915039 q 0.2531738,0 0.4814453,0.1016845 0.2282715,0.1016846 0.3984375,0.2718506 0.170166,0.170166 0.2718506,0.3984375 0.1016845,0.2282715 0.1016845,0.4814453 V 7.246582 q 0,0.2531739 -0.1016845,0.4793701 Q 6.5949707,7.9521484 6.4227295,8.1243896 6.2504883,8.2966309 6.024292,8.3983154 5.7980957,8.5 5.5449219,8.5 Z M 5.5241699,7.6491699 q 0.087158,0 0.1639405,-0.033203 0.076782,-0.033203 0.1369628,-0.091309 0.060181,-0.058105 0.093384,-0.1348877 0.033203,-0.076782 0.033203,-0.1639404 v -4.25 q 0,-0.087158 -0.033203,-0.1660156 Q 5.8852539,2.730957 5.8271484,2.6728516 5.769043,2.6147461 5.6901855,2.581543 5.6113281,2.5483398 5.5241699,2.5483398 h -4.25 q -0.087158,0 -0.1639404,0.033203 -0.076782,0.033203 -0.1348877,0.093384 -0.0581055,0.060181 -0.0913086,0.1369628 -0.0332031,0.076782 -0.0332031,0.1639405 v 4.25 q 0,0.087158 0.0332031,0.1639404 0.0332031,0.076782 0.0913086,0.1348877 0.0581055,0.058105 0.1348877,0.091309 0.076782,0.033203 0.1639404,0.033203 z"; +} + +.close-button > .glyph { + -fx-shape: "M 4.25,4.8518066 0.7263184,8.3754883 Q 0.6018066,8.5 0.4274902,8.5 0.2448731,8.5 0.1224365,8.3775635 0,8.255127 0,8.0725098 0,7.8981934 0.1245117,7.7736816 L 3.6481934,4.25 0.1245117,0.72631836 Q 0,0.60180664 0,0.42333984 0,0.33618164 0.033203,0.25732422 0.066406,0.17846682 0.124512,0.12243652 0.182617,0.06640625 0.2614749,0.03320312 0.340332,0 0.4274902,0 0.6018066,0 0.7263184,0.12451172 L 4.25,3.6481934 7.7736816,0.12451172 Q 7.8981934,0 8.0766602,0 q 0.087158,0 0.1639404,0.03320312 0.076782,0.03320313 0.1348877,0.0913086 0.058105,0.0581055 0.091309,0.13488769 Q 8.5,0.33618164 8.5,0.42333984 8.5,0.60180664 8.3754883,0.72631836 L 4.8518066,4.25 8.3754883,7.7736816 Q 8.5,7.8981934 8.5,8.0725098 8.5,8.1596678 8.466797,8.2385254 8.433594,8.3173824 8.377564,8.3754883 8.321534,8.4335933 8.2426763,8.4667973 8.1638193,8.5 8.0766607,8.5 7.8981939,8.5 7.7736821,8.3754883 Z"; +} diff --git a/modules/javafx.graphics/src/test/addExports b/modules/javafx.graphics/src/test/addExports index b34406f8919..4f2137a9e60 100644 --- a/modules/javafx.graphics/src/test/addExports +++ b/modules/javafx.graphics/src/test/addExports @@ -1,6 +1,7 @@ --add-exports javafx.base/com.sun.javafx.collections=ALL-UNNAMED --add-exports javafx.base/com.sun.javafx.property=ALL-UNNAMED --add-exports javafx.base/com.sun.javafx=ALL-UNNAMED +--add-exports javafx.base/com.sun.javafx.binding=ALL-UNNAMED --add-exports javafx.base/com.sun.javafx.event=ALL-UNNAMED --add-exports javafx.base/com.sun.javafx.logging=ALL-UNNAMED # @@ -53,6 +54,7 @@ --add-opens javafx.graphics/javafx.scene.robot=ALL-UNNAMED --add-opens javafx.graphics/javafx.scene.layout=ALL-UNNAMED --add-opens javafx.graphics/javafx.scene.paint=ALL-UNNAMED +--add-opens javafx.graphics/javafx.stage=ALL-UNNAMED # # compile time additions --add-exports=javafx.base/com.sun.javafx.runtime=ALL-UNNAMED diff --git a/modules/javafx.graphics/src/test/java/test/com/sun/glass/ui/WindowControlsOverlayTest.java b/modules/javafx.graphics/src/test/java/test/com/sun/glass/ui/WindowControlsOverlayTest.java new file mode 100644 index 00000000000..a03afc65ca3 --- /dev/null +++ b/modules/javafx.graphics/src/test/java/test/com/sun/glass/ui/WindowControlsOverlayTest.java @@ -0,0 +1,349 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package test.com.sun.glass.ui; + +import com.sun.glass.ui.WindowControlsOverlay; +import com.sun.javafx.binding.ObjectConstant; +import javafx.beans.value.ObservableValue; +import javafx.geometry.Dimension2D; +import javafx.geometry.HorizontalDirection; +import javafx.geometry.NodeOrientation; +import javafx.scene.Node; +import javafx.scene.Scene; +import javafx.scene.paint.Color; +import javafx.stage.Stage; +import org.junit.jupiter.api.Test; +import test.util.ReflectionUtils; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +import static org.junit.jupiter.api.Assertions.*; + +public class WindowControlsOverlayTest { + + /** + * Asserts that the buttons are laid out on the right side of the control (left-to-right orientation). + */ + @Test + void rightPlacement() { + var overlay = new WindowControlsOverlay(getStylesheet(""" + .window-button-container { -fx-button-placement: right; } + .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } + """)); + + var unused = new Scene(overlay); + var children = overlay.getChildrenUnmodifiable(); + overlay.resize(200, 100); + overlay.applyCss(); + overlay.layout(); + + assertSize(overlay, 200, 100); + assertLayoutBounds(children.get(0), 140, 0, 20, 10); + assertLayoutBounds(children.get(1), 160, 0, 20, 10); + assertLayoutBounds(children.get(2), 180, 0, 20, 10); + assertEquals(HorizontalDirection.RIGHT, overlay.metricsProperty().get().placement()); + assertEquals(new Dimension2D(60, 10), overlay.metricsProperty().get().size()); + } + + /** + * Asserts that the buttons are laid out on the left side of the control (right-to-left orientation). + */ + @Test + void rightPlacement_rightToLeft() { + var overlay = new WindowControlsOverlay(getStylesheet(""" + .window-button-container { -fx-button-placement: right; } + .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } + """)); + + var unused = new Scene(overlay); + var children = overlay.getChildrenUnmodifiable(); + overlay.setNodeOrientation(NodeOrientation.RIGHT_TO_LEFT); + overlay.resize(200, 100); + overlay.applyCss(); + overlay.layout(); + + assertSize(overlay, 200, 100); + assertLayoutBounds(children.get(0), 40, 0, 20, 10); + assertLayoutBounds(children.get(1), 20, 0, 20, 10); + assertLayoutBounds(children.get(2), 0, 0, 20, 10); + assertEquals(HorizontalDirection.LEFT, overlay.metricsProperty().get().placement()); + assertEquals(new Dimension2D(60, 10), overlay.metricsProperty().get().size()); + } + + /** + * Asserts that the buttons are laid out on the left side of the control (left-to-right orientation). + */ + @Test + void leftPlacement() { + var overlay = new WindowControlsOverlay(getStylesheet(""" + .window-button-container { -fx-button-placement: left; } + .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } + """)); + + var unused = new Scene(overlay); + var children = overlay.getChildrenUnmodifiable(); + overlay.resize(200, 100); + overlay.applyCss(); + overlay.layout(); + + assertSize(overlay, 200, 100); + assertLayoutBounds(children.get(0), 0, 0, 20, 10); + assertLayoutBounds(children.get(1), 20, 0, 20, 10); + assertLayoutBounds(children.get(2), 40, 0, 20, 10); + assertEquals(HorizontalDirection.LEFT, overlay.metricsProperty().get().placement()); + assertEquals(new Dimension2D(60, 10), overlay.metricsProperty().get().size()); + } + + /** + * Asserts that the buttons are laid out on the right side of the control (right-to-left orientation). + */ + @Test + void leftPlacement_rightToLeft() { + var overlay = new WindowControlsOverlay(getStylesheet(""" + .window-button-container { -fx-button-placement: left; } + .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } + """)); + + var unused = new Scene(overlay); + var children = overlay.getChildrenUnmodifiable(); + overlay.setNodeOrientation(NodeOrientation.RIGHT_TO_LEFT); + overlay.resize(200, 100); + overlay.applyCss(); + overlay.layout(); + + assertSize(overlay, 200, 100); + assertLayoutBounds(children.get(0), 180, 0, 20, 10); + assertLayoutBounds(children.get(1), 160, 0, 20, 10); + assertLayoutBounds(children.get(2), 140, 0, 20, 10); + assertEquals(HorizontalDirection.RIGHT, overlay.metricsProperty().get().placement()); + assertEquals(new Dimension2D(60, 10), overlay.metricsProperty().get().size()); + } + + /** + * Asserts that the buttons are laid out in a custom order (left-to-right orientation). + */ + @Test + void customButtonOrder() { + var overlay = new WindowControlsOverlay(getStylesheet(""" + .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } + .minimize-button { -fx-button-order: 5; } + .maximize-button { -fx-button-order: 1; } + .close-button { -fx-button-order: 3; } + """)); + + var unused = new Scene(overlay); + var children = overlay.getChildrenUnmodifiable(); + overlay.resize(200, 100); + overlay.applyCss(); + overlay.layout(); + + assertTrue(children.get(0).getStyleClass().contains("minimize-button")); + assertLayoutBounds(children.get(0), 180, 0, 20, 10); + assertTrue(children.get(1).getStyleClass().contains("maximize-button")); + assertLayoutBounds(children.get(1), 140, 0, 20, 10); + assertTrue(children.get(2).getStyleClass().contains("close-button")); + assertLayoutBounds(children.get(2), 160, 0, 20, 10); + } + + /** + * Asserts that the buttons are laid out in a custom order (right-to-left orientation). + */ + @Test + void customButtonOrder_rightToLeft() { + var overlay = new WindowControlsOverlay(getStylesheet(""" + .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } + .minimize-button { -fx-button-order: 5; } + .maximize-button { -fx-button-order: 1; } + .close-button { -fx-button-order: 3; } + """)); + + var unused = new Scene(overlay); + var children = overlay.getChildrenUnmodifiable(); + overlay.setNodeOrientation(NodeOrientation.RIGHT_TO_LEFT); + overlay.resize(200, 100); + overlay.applyCss(); + overlay.layout(); + + assertTrue(children.get(0).getStyleClass().contains("minimize-button")); + assertLayoutBounds(children.get(0), 0, 0, 20, 10); + assertTrue(children.get(1).getStyleClass().contains("maximize-button")); + assertLayoutBounds(children.get(1), 40, 0, 20, 10); + assertTrue(children.get(2).getStyleClass().contains("close-button")); + assertLayoutBounds(children.get(2), 20, 0, 20, 10); + } + + /** + * Asserts that the buttons are laid out on the right, even though the node orientation is right-to-left. + */ + @Test + void disallowRightToLeft() { + var overlay = new WindowControlsOverlay(getStylesheet(""" + .window-button-container { -fx-button-placement: right; -fx-allow-rtl: false; } + .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } + """)); + + var unused = new Scene(overlay); + var children = overlay.getChildrenUnmodifiable(); + overlay.setNodeOrientation(NodeOrientation.RIGHT_TO_LEFT); + overlay.resize(200, 100); + overlay.applyCss(); + overlay.layout(); + + assertLayoutBounds(children.get(0), 140, 0, 20, 10); + assertLayoutBounds(children.get(1), 160, 0, 20, 10); + assertLayoutBounds(children.get(2), 180, 0, 20, 10); + assertEquals(HorizontalDirection.RIGHT, overlay.metricsProperty().get().placement()); + assertEquals(new Dimension2D(60, 10), overlay.metricsProperty().get().size()); + } + + @Test + void activePseudoClassCorrespondsToStageFocusedProperty() { + var overlay = new WindowControlsOverlay(getStylesheet(""" + .window-button-container { -fx-button-placement: right; } + .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } + """)); + + var scene = new Scene(overlay); + var stage = new Stage(); + stage.setScene(scene); + stage.show(); + + assertTrue(stage.isFocused()); + assertTrue(overlay.getChildrenUnmodifiable().get(0).getPseudoClassStates().stream().anyMatch( + pc -> pc.getPseudoClassName().equals("active"))); + + ReflectionUtils.invokeMethod(stage, "setFocused", new Class[] { boolean.class }, false); + + assertFalse(stage.isFocused()); + assertTrue(overlay.getChildrenUnmodifiable().get(0).getPseudoClassStates().stream().noneMatch( + pc -> pc.getPseudoClassName().equals("active"))); + } + + /** + * Asserts that the maximize button is disabled when the stage is not resizable. + */ + @Test + void maximizeButtonIsDisabledWhenStageIsNotResizable() { + var overlay = new WindowControlsOverlay(getStylesheet(""" + .window-button-container { -fx-button-placement: right; } + .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } + """)); + + var scene = new Scene(overlay); + var stage = new Stage(); + stage.setScene(scene); + stage.show(); + + var maxButton = overlay.getChildrenUnmodifiable().get(1); + assertTrue(maxButton.getStyleClass().contains("maximize-button")); + assertTrue(stage.isResizable()); + assertFalse(maxButton.isDisabled()); + + stage.setResizable(false); + assertTrue(maxButton.isDisabled()); + } + + /** + * Asserts that the .restore style class is added to the maximize button when the stage is maximized. + */ + @Test + void restoreStyleClassIsPresentWhenStageIsMaximized() { + var overlay = new WindowControlsOverlay(getStylesheet(""" + .window-button-container { -fx-button-placement: right; } + .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } + """)); + + var scene = new Scene(overlay); + var stage = new Stage(); + stage.setScene(scene); + stage.show(); + + var maxButton = overlay.getChildrenUnmodifiable().get(1); + assertTrue(maxButton.getStyleClass().contains("maximize-button")); + assertFalse(maxButton.getStyleClass().contains("restore")); + + stage.setMaximized(true); + assertTrue(maxButton.getStyleClass().contains("restore")); + } + + /** + * Asserts that the .dark style class is added to all buttons when {@link Scene#getFill()} is dark. + */ + @Test + void darkStyleClassIsPresentWhenSceneFillIsDark() { + var overlay = new WindowControlsOverlay(getStylesheet(""" + .window-button-container { -fx-button-placement: right; } + .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } + """)); + + var scene = new Scene(overlay); + + scene.setFill(Color.WHITE); + assertTrue(overlay.getChildrenUnmodifiable().stream().noneMatch(b -> b.getStyleClass().contains("dark"))); + + scene.setFill(Color.BLACK); + assertTrue(overlay.getChildrenUnmodifiable().stream().allMatch(b -> b.getStyleClass().contains("dark"))); + } + + /** + * Tests button picking using {@link WindowControlsOverlay#buttonAt(double, double)}. + */ + @Test + void pickButtonAtCoordinates() { + var overlay = new WindowControlsOverlay(getStylesheet(""" + .window-button-container { -fx-button-placement: right; } + .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } + """)); + + var unused = new Scene(overlay); + overlay.resize(200, 100); + overlay.applyCss(); + overlay.layout(); + + assertNull(overlay.buttonAt(139, 5)); + assertEquals(WindowControlsOverlay.ButtonType.MINIMIZE, overlay.buttonAt(140, 0)); + assertEquals(WindowControlsOverlay.ButtonType.MAXIMIZE, overlay.buttonAt(165, 5)); + assertEquals(WindowControlsOverlay.ButtonType.CLOSE, overlay.buttonAt(181, 10)); + } + + private static ObservableValue getStylesheet(String text) { + String stylesheet = "data:text/css;base64," + + Base64.getEncoder().encodeToString(text.getBytes(StandardCharsets.UTF_8)); + + return ObjectConstant.valueOf(stylesheet); + } + + private static void assertLayoutBounds(Node node, double x, double y, double width, double height) { + assertEquals(x, node.getLayoutX()); + assertEquals(y, node.getLayoutY()); + assertSize(node, width, height); + } + + private static void assertSize(Node node, double width, double height) { + assertEquals(width, node.getLayoutBounds().getWidth()); + assertEquals(height, node.getLayoutBounds().getHeight()); + } +} diff --git a/modules/javafx.graphics/src/test/java/test/com/sun/javafx/util/UtilsTest.java b/modules/javafx.graphics/src/test/java/test/com/sun/javafx/util/UtilsTest.java index ca19162733c..d4f0d740332 100644 --- a/modules/javafx.graphics/src/test/java/test/com/sun/javafx/util/UtilsTest.java +++ b/modules/javafx.graphics/src/test/java/test/com/sun/javafx/util/UtilsTest.java @@ -35,7 +35,14 @@ import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.SubScene; +import javafx.scene.image.Image; import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.scene.paint.CycleMethod; +import javafx.scene.paint.ImagePattern; +import javafx.scene.paint.LinearGradient; +import javafx.scene.paint.RadialGradient; +import javafx.scene.paint.Stop; import javafx.scene.shape.Rectangle; import javafx.stage.Stage; @@ -181,4 +188,45 @@ public void testPointRelativeTo_InSubScene() { assertEquals(70, res.getY(), 1e-1); } + + @Test + void testAveragePerceptualBrightness_LinearGradient() { + var gradient = new LinearGradient( + 0, 0, 1, 1, true, CycleMethod.NO_CYCLE, + new Stop(0, Color.RED), new Stop(0.5, Color.GREEN), new Stop(1, Color.BLUE)); + + double actual = Utils.calculateAverageBrightness(gradient); + double expect = (Utils.calculateBrightness(Color.RED) + + Utils.calculateBrightness(Color.GREEN) + + Utils.calculateBrightness(Color.BLUE)) / 3; + + assertEquals(expect, actual); + } + + @Test + void testAveragePerceptualBrightness_RadialGradient() { + var gradient = new RadialGradient( + 0, 0, 0, 0, 1, true, CycleMethod.NO_CYCLE, + new Stop(0, Color.RED), new Stop(0.5, Color.GREEN), new Stop(1, Color.BLUE)); + + double actual = Utils.calculateAverageBrightness(gradient); + double expect = (Utils.calculateBrightness(Color.RED) + + Utils.calculateBrightness(Color.GREEN) + + Utils.calculateBrightness(Color.BLUE)) / 3; + + assertEquals(expect, actual); + } + + @Test + void testAveragePerceptualBrightness_ImagePattern() { + var pattern = new ImagePattern(new Image("test")); + assertEquals(1, Utils.calculateAverageBrightness(pattern)); + } + + @Test + void testAveragePerceptualBrightness_Color() { + var actual = Utils.calculateAverageBrightness(Color.RED); + var expect = Utils.calculateBrightness(Color.RED); + assertEquals(expect, actual); + } } diff --git a/modules/javafx.graphics/src/test/java/test/javafx/scene/layout/HeaderBarTest.java b/modules/javafx.graphics/src/test/java/test/javafx/scene/layout/HeaderBarTest.java new file mode 100644 index 00000000000..839f72c428c --- /dev/null +++ b/modules/javafx.graphics/src/test/java/test/javafx/scene/layout/HeaderBarTest.java @@ -0,0 +1,438 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package test.javafx.scene.layout; + +import javafx.beans.property.ObjectProperty; +import javafx.geometry.Dimension2D; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.layout.HeaderBar; +import javafx.scene.shape.Rectangle; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import test.util.ReflectionUtils; + +import static org.junit.jupiter.api.Assertions.*; + +public class HeaderBarTest { + + HeaderBar headerBar; + + @BeforeEach + void setup() { + headerBar = new HeaderBar(); + } + + @Test + void emptyHeaderBar() { + assertNull(headerBar.getLeading()); + assertNull(headerBar.getCenter()); + assertNull(headerBar.getTrailing()); + } + + @ParameterizedTest + @ValueSource(strings = { + "TOP_LEFT, 10, 10, 100, 80", + "TOP_CENTER, 10, 10, 100, 80", + "TOP_RIGHT, 10, 10, 100, 80", + "CENTER_LEFT, 10, 10, 100, 80", + "CENTER, 10, 10, 100, 80", + "CENTER_RIGHT, 10, 10, 100, 80", + "BOTTOM_LEFT, 10, 10, 100, 80", + "BOTTOM_CENTER, 10, 10, 100, 80", + "BOTTOM_RIGHT, 10, 10, 100, 80" + }) + void alignmentOfLeadingChildOnly_resizable(String arg) { + String[] args = arg.split(","); + var content = new MockResizable(100, 50); + HeaderBar.setAlignment(content, Pos.valueOf(args[0])); + HeaderBar.setMargin(content, new Insets(10)); + headerBar.setLeading(content); + headerBar.resize(1000, 100); + headerBar.layout(); + + assertBounds( + Double.parseDouble(args[1]), + Double.parseDouble(args[2]), + Double.parseDouble(args[3]), + Double.parseDouble(args[4]), + content); + } + + @ParameterizedTest + @ValueSource(strings = { + "TOP_LEFT, 10, 10, 100, 50", + "TOP_CENTER, 10, 10, 100, 50", + "TOP_RIGHT, 10, 10, 100, 50", + "CENTER_LEFT, 10, 25, 100, 50", + "CENTER, 10, 25, 100, 50", + "CENTER_RIGHT, 10, 25, 100, 50", + "BOTTOM_LEFT, 10, 40, 100, 50", + "BOTTOM_CENTER, 10, 40, 100, 50", + "BOTTOM_RIGHT, 10, 40, 100, 50" + }) + void alignmentOfLeadingChildOnly_notResizable(String arg) { + String[] args = arg.split(","); + var content = new Rectangle(100, 50); + HeaderBar.setAlignment(content, Pos.valueOf(args[0])); + HeaderBar.setMargin(content, new Insets(10)); + headerBar.setLeading(content); + headerBar.resize(1000, 100); + headerBar.layout(); + + assertBounds( + Double.parseDouble(args[1]), + Double.parseDouble(args[2]), + Double.parseDouble(args[3]), + Double.parseDouble(args[4]), + content); + } + + @ParameterizedTest + @ValueSource(strings = { + "TOP_LEFT, 890, 10, 100, 80", + "TOP_CENTER, 890, 10, 100, 80", + "TOP_RIGHT, 890, 10, 100, 80", + "CENTER_LEFT, 890, 10, 100, 80", + "CENTER, 890, 10, 100, 80", + "CENTER_RIGHT, 890, 10, 100, 80", + "BOTTOM_LEFT, 890, 10, 100, 80", + "BOTTOM_CENTER, 890, 10, 100, 80", + "BOTTOM_RIGHT, 890, 10, 100, 80" + }) + void alignmentOfTrailingChildOnly_resizable(String arg) { + String[] args = arg.split(","); + var content = new MockResizable(100, 50); + HeaderBar.setAlignment(content, Pos.valueOf(args[0])); + HeaderBar.setMargin(content, new Insets(10)); + headerBar.setTrailing(content); + headerBar.resize(1000, 100); + headerBar.layout(); + + assertBounds( + Double.parseDouble(args[1]), + Double.parseDouble(args[2]), + Double.parseDouble(args[3]), + Double.parseDouble(args[4]), + content); + } + + @ParameterizedTest + @ValueSource(strings = { + "TOP_LEFT, 890, 10, 100, 50", + "TOP_CENTER, 890, 10, 100, 50", + "TOP_RIGHT, 890, 10, 100, 50", + "CENTER_LEFT, 890, 25, 100, 50", + "CENTER, 890, 25, 100, 50", + "CENTER_RIGHT, 890, 25, 100, 50", + "BOTTOM_LEFT, 890, 40, 100, 50", + "BOTTOM_CENTER, 890, 40, 100, 50", + "BOTTOM_RIGHT, 890, 40, 100, 50" + }) + void alignmentOfTrailingChildOnly_notResizable(String arg) { + String[] args = arg.split(","); + var content = new Rectangle(100, 50); + HeaderBar.setAlignment(content, Pos.valueOf(args[0])); + HeaderBar.setMargin(content, new Insets(10)); + headerBar.setTrailing(content); + headerBar.resize(1000, 100); + headerBar.layout(); + + assertBounds( + Double.parseDouble(args[1]), + Double.parseDouble(args[2]), + Double.parseDouble(args[3]), + Double.parseDouble(args[4]), + content); + } + + @ParameterizedTest + @ValueSource(strings = { + "TOP_LEFT, 10, 10, 100, 80", + "TOP_CENTER, 450, 10, 100, 80", + "TOP_RIGHT, 890, 10, 100, 80", + "CENTER_LEFT, 10, 10, 100, 80", + "CENTER, 450, 10, 100, 80", + "CENTER_RIGHT, 890, 10, 100, 80", + "BOTTOM_LEFT, 10, 10, 100, 80", + "BOTTOM_CENTER, 450, 10, 100, 80", + "BOTTOM_RIGHT, 890, 10, 100, 80" + }) + void alignmentOfCenterChildOnly_resizable(String arg) { + String[] args = arg.split(","); + var content = new MockResizable(100, 50); + HeaderBar.setAlignment(content, Pos.valueOf(args[0])); + HeaderBar.setMargin(content, new Insets(10)); + headerBar.setCenter(content); + headerBar.resize(1000, 100); + headerBar.layout(); + + assertBounds( + Double.parseDouble(args[1]), + Double.parseDouble(args[2]), + Double.parseDouble(args[3]), + Double.parseDouble(args[4]), + content); + } + + @ParameterizedTest + @ValueSource(strings = { + "TOP_LEFT, 10, 10, 100, 50", + "TOP_CENTER, 450, 10, 100, 50", + "TOP_RIGHT, 890, 10, 100, 50", + "CENTER_LEFT, 10, 25, 100, 50", + "CENTER, 450, 25, 100, 50", + "CENTER_RIGHT, 890, 25, 100, 50", + "BOTTOM_LEFT, 10, 40, 100, 50", + "BOTTOM_CENTER, 450, 40, 100, 50", + "BOTTOM_RIGHT, 890, 40, 100, 50" + }) + void alignmentOfCenterChildOnly_notResizable(String arg) { + String[] args = arg.split(","); + var content = new Rectangle(100, 50); + HeaderBar.setAlignment(content, Pos.valueOf(args[0])); + HeaderBar.setMargin(content, new Insets(10)); + headerBar.setCenter(content); + headerBar.resize(1000, 100); + headerBar.layout(); + + assertBounds( + Double.parseDouble(args[1]), + Double.parseDouble(args[2]), + Double.parseDouble(args[3]), + Double.parseDouble(args[4]), + content); + } + + @ParameterizedTest + @ValueSource(strings = { + "TOP_LEFT, 60, 10, 100, 80", + "TOP_CENTER, 450, 10, 100, 80", + "TOP_RIGHT, 740, 10, 100, 80", + "CENTER_LEFT, 60, 10, 100, 80", + "CENTER, 450, 10, 100, 80", + "CENTER_RIGHT, 740, 10, 100, 80", + "BOTTOM_LEFT, 60, 10, 100, 80", + "BOTTOM_CENTER, 450, 10, 100, 80", + "BOTTOM_RIGHT, 740, 10, 100, 80" + }) + void alignmentOfCenterChild_resizable_withNonEmptyLeadingAndTrailingChild(String arg) { + String[] args = arg.split(","); + var leading = new MockResizable(50, 50); + var center = new MockResizable(100, 50); + var trailing = new MockResizable(150, 50); + HeaderBar.setAlignment(center, Pos.valueOf(args[0])); + HeaderBar.setMargin(center, new Insets(10)); + headerBar.setLeading(leading); + headerBar.setCenter(center); + headerBar.setTrailing(trailing); + headerBar.resize(1000, 100); + headerBar.layout(); + + assertBounds( + Double.parseDouble(args[1]), + Double.parseDouble(args[2]), + Double.parseDouble(args[3]), + Double.parseDouble(args[4]), + center); + } + + @ParameterizedTest + @ValueSource(strings = { + "TOP_LEFT, 60, 10, 100, 50", + "TOP_CENTER, 450, 10, 100, 50", + "TOP_RIGHT, 740, 10, 100, 50", + "CENTER_LEFT, 60, 25, 100, 50", + "CENTER, 450, 25, 100, 50", + "CENTER_RIGHT, 740, 25, 100, 50", + "BOTTOM_LEFT, 60, 40, 100, 50", + "BOTTOM_CENTER, 450, 40, 100, 50", + "BOTTOM_RIGHT, 740, 40, 100, 50" + }) + void alignmentOfCenterChild_notResizable_withNonEmptyLeadingAndTrailingChild(String arg) { + String[] args = arg.split(","); + var leading = new Rectangle(50, 50); + var center = new Rectangle(100, 50); + var trailing = new Rectangle(150, 50); + HeaderBar.setAlignment(center, Pos.valueOf(args[0])); + HeaderBar.setMargin(center, new Insets(10)); + headerBar.setLeading(leading); + headerBar.setCenter(center); + headerBar.setTrailing(trailing); + headerBar.resize(1000, 100); + headerBar.layout(); + + assertBounds( + Double.parseDouble(args[1]), + Double.parseDouble(args[2]), + Double.parseDouble(args[3]), + Double.parseDouble(args[4]), + center); + } + + @ParameterizedTest + @SuppressWarnings("unchecked") + @ValueSource(strings = { + "TOP_LEFT, 160, 10, 100, 80", + "TOP_CENTER, 450, 10, 100, 80", + "TOP_RIGHT, 740, 10, 100, 80", + "CENTER_LEFT, 160, 10, 100, 80", + "CENTER, 450, 10, 100, 80", + "CENTER_RIGHT, 740, 10, 100, 80", + "BOTTOM_LEFT, 160, 10, 100, 80", + "BOTTOM_CENTER, 450, 10, 100, 80", + "BOTTOM_RIGHT, 740, 10, 100, 80" + }) + void alignmentOfCenterChild_withLeftSystemInset(String arg) { + String[] args = arg.split(","); + var leftSystemInset = (ObjectProperty)ReflectionUtils.getFieldValue(headerBar, "leftSystemInset"); + leftSystemInset.set(new Dimension2D(100, 100)); + var leading = new MockResizable(50, 50); + var center = new MockResizable(100, 50); + var trailing = new MockResizable(150, 50); + HeaderBar.setAlignment(center, Pos.valueOf(args[0])); + HeaderBar.setMargin(center, new Insets(10)); + headerBar.setLeading(leading); + headerBar.setCenter(center); + headerBar.setTrailing(trailing); + headerBar.resize(1000, 100); + headerBar.layout(); + + assertBounds( + Double.parseDouble(args[1]), + Double.parseDouble(args[2]), + Double.parseDouble(args[3]), + Double.parseDouble(args[4]), + center); + } + + @ParameterizedTest + @SuppressWarnings("unchecked") + @ValueSource(strings = { + "TOP_LEFT, 60, 10, 100, 80", + "TOP_CENTER, 450, 10, 100, 80", + "TOP_RIGHT, 640, 10, 100, 80", + "CENTER_LEFT, 60, 10, 100, 80", + "CENTER, 450, 10, 100, 80", + "CENTER_RIGHT, 640, 10, 100, 80", + "BOTTOM_LEFT, 60, 10, 100, 80", + "BOTTOM_CENTER, 450, 10, 100, 80", + "BOTTOM_RIGHT, 640, 10, 100, 80" + }) + void alignmentOfCenterChild_withRightSystemInset(String arg) { + String[] args = arg.split(","); + var rightSystemInset = (ObjectProperty)ReflectionUtils.getFieldValue(headerBar, "rightSystemInset"); + rightSystemInset.set(new Dimension2D(100, 100)); + var leading = new MockResizable(50, 50); + var center = new MockResizable(100, 50); + var trailing = new MockResizable(150, 50); + HeaderBar.setAlignment(center, Pos.valueOf(args[0])); + HeaderBar.setMargin(center, new Insets(10)); + headerBar.setLeading(leading); + headerBar.setCenter(center); + headerBar.setTrailing(trailing); + headerBar.resize(1000, 100); + headerBar.layout(); + + assertBounds( + Double.parseDouble(args[1]), + Double.parseDouble(args[2]), + Double.parseDouble(args[3]), + Double.parseDouble(args[4]), + center); + } + + @ParameterizedTest + @SuppressWarnings("unchecked") + @ValueSource(strings = { + "TOP_CENTER, 260, 10, 80, 80", + "CENTER, 260, 10, 80, 80", + "BOTTOM_CENTER, 260, 10, 80, 80" + }) + void alignmentOfCenterChild_withLeftSystemInset_andOffsetCausedByInsufficientHorizontalSpace(String arg) { + String[] args = arg.split(","); + var leftSystemInset = (ObjectProperty)ReflectionUtils.getFieldValue(headerBar, "leftSystemInset"); + leftSystemInset.set(new Dimension2D(200, 100)); + var leading = new MockResizable(50, 50); + var center = new MockResizable(100, 50); + var trailing = new MockResizable(150, 50); + HeaderBar.setAlignment(center, Pos.valueOf(args[0])); + HeaderBar.setMargin(center, new Insets(10)); + headerBar.setLeading(leading); + headerBar.setCenter(center); + headerBar.setTrailing(trailing); + headerBar.resize(500, 100); + headerBar.layout(); + + assertBounds( + Double.parseDouble(args[1]), + Double.parseDouble(args[2]), + Double.parseDouble(args[3]), + Double.parseDouble(args[4]), + center); + } + + @ParameterizedTest + @SuppressWarnings("unchecked") + @ValueSource(strings = { + "TOP_CENTER, 60, 10, 80, 80", + "CENTER, 60, 10, 80, 80", + "BOTTOM_CENTER, 60, 10, 80, 80" + }) + void alignmentOfCenterChild_withRightSystemInset_andOffsetCausedByInsufficientHorizontalSpace(String arg) { + String[] args = arg.split(","); + var rightSystemInset = (ObjectProperty)ReflectionUtils.getFieldValue(headerBar, "rightSystemInset"); + rightSystemInset.set(new Dimension2D(200, 100)); + var leading = new MockResizable(50, 50); + var center = new MockResizable(100, 50); + var trailing = new MockResizable(150, 50); + HeaderBar.setAlignment(center, Pos.valueOf(args[0])); + HeaderBar.setMargin(center, new Insets(10)); + headerBar.setLeading(leading); + headerBar.setCenter(center); + headerBar.setTrailing(trailing); + headerBar.resize(500, 100); + headerBar.layout(); + + assertBounds( + Double.parseDouble(args[1]), + Double.parseDouble(args[2]), + Double.parseDouble(args[3]), + Double.parseDouble(args[4]), + center); + } + + private void assertBounds(double x, double y, double width, double height, Node node) { + var bounds = node.getLayoutBounds(); + assertEquals(x, node.getLayoutX()); + assertEquals(y, node.getLayoutY()); + assertEquals(width, bounds.getWidth()); + assertEquals(height, bounds.getHeight()); + } +} From 0ddd63d47a0a0f632c377f9656bac0c74b684c7a Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Sun, 20 Oct 2024 02:53:11 +0200 Subject: [PATCH 02/73] doc change --- .../src/main/java/javafx/scene/layout/HeaderBar.java | 2 +- .../src/main/java/javafx/scene/layout/HeaderBarBase.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java index bca236a68db..d566101f914 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java @@ -39,7 +39,7 @@ /** * A client-area header bar that is used as a replacement for the system-provided header bar in stages - * with the {@link StageStyle#EXTENDED} style. This class enables the drag to move and + * with the {@link StageStyle#EXTENDED} style. This class enables the click-and-drag and * double-click to maximize behaviors that are usually afforded by system-provided header bars. * The entire {@code HeaderBar} background is draggable by default, but its content is not. Applications * can specify draggable content nodes of the {@code HeaderBar} with the {@link #setDraggable} method. diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java index f5932eb65d1..6b99d463615 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java @@ -42,7 +42,7 @@ /** * Base class for a client-area header bar that is used as a replacement for the system-provided header bar * in stages with the {@link StageStyle#EXTENDED} style. This class is intended for application developers - * to use as a starting point for custom header bar implementations, and it enables the drag to move + * to use as a starting point for custom header bar implementations, and it enables the click-and-drag * and double-click to maximize behaviors that are usually afforded by system-provided header bars. * The entire {@code HeaderBarBase} background is draggable by default, but its content is not. Applications * can specify draggable content nodes of the {@code HeaderBarBase} with the {@link #setDraggable} method. From 79696225c7dc4db985f2cc84e949a93bd30a79e1 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Sun, 20 Oct 2024 03:19:58 +0200 Subject: [PATCH 03/73] revert unintended change --- modules/javafx.graphics/src/main/native-glass/ios/GlassWindow.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/javafx.graphics/src/main/native-glass/ios/GlassWindow.m b/modules/javafx.graphics/src/main/native-glass/ios/GlassWindow.m index b7425c75d36..5b37d31e210 100644 --- a/modules/javafx.graphics/src/main/native-glass/ios/GlassWindow.m +++ b/modules/javafx.graphics/src/main/native-glass/ios/GlassWindow.m @@ -816,7 +816,7 @@ jlong _1createWindow(JNIEnv *env, jobject jWindow, jlong jOwnerPtr, jlong jScree BOOL hidden = YES; if (jOwnerPtr == 0L) { // no owner means it is the primary stage; Decorated primary stage shows status bar by default - hidden = ((jStyleMask & com_sun_glass_ui_Window_DECORATED) == 0); + hidden = ((jStyleMask & com_sun_glass_ui_Window_TITLED) == 0); NSObject * values = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIStatusBarHidden"]; //we prefer explicit settings from .plist From ba02e8f71e4d177e4cee029ba4dfff21199325a6 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Thu, 24 Oct 2024 22:00:38 +0200 Subject: [PATCH 04/73] Improve HeaderBar documentation --- .../java/javafx/scene/layout/HeaderBar.java | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java index d566101f914..e5bac22da28 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java @@ -76,13 +76,35 @@ * * *

Example

+ * Usually, {@code HeaderBar} is placed in a root container like {@code BorderPane} to align it + * with the top of the scene: *
{@code
- *     var button = new Button("My button");
- *     HeaderBar.setAlignment(button, Pos.CENTER_LEFT);
- *     HeaderBar.setMargin(button, new Insets(5));
- *     myHeaderBar.setCenter(button);
+ * public class MyApp extends Application {
+ *     @Override
+ *     public void start(Stage stage) {
+ *         var button = new Button("My button");
+ *         HeaderBar.setAlignment(button, Pos.CENTER_LEFT);
+ *         HeaderBar.setMargin(button, new Insets(5));
+ *
+ *         var headerBar = new HeaderBar();
+ *         headerBar.setCenter(button);
+ *
+ *         var root = new BorderPane();
+ *         root.setTop(headerBar);
+ *
+ *         stage.setScene(new Scene(root));
+ *         stage.initStyle(StageStyle.EXTENDED);
+ *         stage.show();
+ *     }
+ * }
  * }
* + * @apiNote An application should only add a single {@code HeaderBar} to the scene graph, and it should + * be located at the top of the scene. While it is technically possible to add multiple header + * bars to the scene graph, or place a header bar in another area of the scene, the resulting + * user experience is not what users typically expect from JavaFX applications and should + * therefore be avoided. + * * @see HeaderBarBase * @since 24 */ From f973e8c55a8d81d2e9ba5c9d2f61d3f11f072005 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Fri, 25 Oct 2024 18:36:31 +0200 Subject: [PATCH 05/73] improve documentation --- .../java/javafx/scene/layout/HeaderBarBase.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java index 6b99d463615..af15f431042 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java @@ -46,10 +46,9 @@ * and double-click to maximize behaviors that are usually afforded by system-provided header bars. * The entire {@code HeaderBarBase} background is draggable by default, but its content is not. Applications * can specify draggable content nodes of the {@code HeaderBarBase} with the {@link #setDraggable} method. - *

- * Most application developers should use the {@link HeaderBar} implementation instead of - * creating a custom header bar. * + * @apiNote Most application developers should use the {@link HeaderBar} implementation instead of + * creating a custom header bar. * @see HeaderBar * @since 24 */ @@ -150,7 +149,10 @@ private void updateInsets() { /** * Describes the size of the left system inset, which is an area reserved for the * minimize, maximize, and close window buttons. If there are no window buttons on - * the left side of the window, the returned area is empty. + * the left side of the window, the returned area is an empty {@code Dimension2D}. + *

+ * Note that the left system inset refers to the physical left side of the window, + * independent of layout orientation. */ private final ReadOnlyObjectWrapper leftSystemInset = new ReadOnlyObjectWrapper<>(this, "leftInset", new Dimension2D(0, 0)) { @@ -171,7 +173,10 @@ public final Dimension2D getLeftSystemInset() { /** * Describes the size of the right system inset, which is an area reserved for the * minimize, maximize, and close window buttons. If there are no window buttons on - * the right side of the window, the returned area is empty. + * the right side of the window, the returned area is an empty {@code Dimension2D}. + *

+ * Note that the right system inset refers to the physical right side of the window, + * independent of layout orientation. */ private final ReadOnlyObjectWrapper rightSystemInset = new ReadOnlyObjectWrapper<>(this, "rightInset", new Dimension2D(0, 0)) { From f02e7e9034caa66ba373495f148ba3d78ef982e3 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Sat, 26 Oct 2024 00:44:48 +0200 Subject: [PATCH 06/73] Windows: add system menu --- .../java/com/sun/glass/ui/win/WinWindow.java | 14 +++- .../src/main/native-glass/win/GlassWindow.cpp | 65 +++++++++++++++++++ .../src/main/native-glass/win/GlassWindow.h | 1 + 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java index 52c35f88dc6..d4495c09861 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java @@ -24,6 +24,7 @@ */ package com.sun.glass.ui.win; +import com.sun.glass.events.MouseEvent; import com.sun.glass.ui.Cursor; import com.sun.glass.ui.WindowControlsOverlay; import com.sun.glass.ui.NonClientHandler; @@ -268,6 +269,7 @@ protected boolean _setBackground(long ptr, float r, float g, float b) { native private long _getInsets(long ptr); native private long _getAnchor(long ptr); + native private void _showSystemMenu(long ptr, int x, int y); @Override native protected long _createWindow(long ownerPtr, long screenPtr, int mask); @Override native protected boolean _close(long ptr); @Override native protected boolean _setView(long ptr, View view); @@ -369,7 +371,17 @@ public NonClientHandler getNonClientHandler() { return (type, button, x, y, xAbs, yAbs, clickCount) -> { double wx = x / platformScaleX; double wy = y / platformScaleY; - return overlay.handleMouseEvent(type, button, wx, wy); + boolean handled = overlay.handleMouseEvent(type, button, wx, wy); + + // A right click on a non-client header bar area opens the system menu. + if (!handled && type == MouseEvent.NC_UP && button == MouseEvent.BUTTON_RIGHT) { + View.EventHandler eventHandler = view != null ? view.getEventHandler() : null; + if (eventHandler != null && eventHandler.handleDragAreaHitTestEvent(wx, wy)) { + _showSystemMenu(getRawHandle(), x, y); + } + } + + return handled; }; } diff --git a/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp b/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp index 02a962c92e4..1eeaa6318f3 100644 --- a/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp +++ b/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp @@ -1218,6 +1218,48 @@ void GlassWindow::SetIcon(HICON hIcon) m_hIcon = hIcon; } +void GlassWindow::ShowSystemMenu(int x, int y) +{ + WINDOWPLACEMENT placement; + if (!::GetWindowPlacement(GetHWND(), &placement)) { + return; + } + + HMENU systemMenu = GetSystemMenu(GetHWND(), FALSE); + bool maximized = placement.showCmd == SW_SHOWMAXIMIZED; + + MENUITEMINFO menuItemInfo { sizeof(MENUITEMINFO) }; + menuItemInfo.fMask = MIIM_STATE; + menuItemInfo.fType = MFT_STRING; + + menuItemInfo.fState = maximized ? MF_ENABLED : MF_DISABLED; + SetMenuItemInfo(systemMenu, SC_RESTORE, FALSE, &menuItemInfo); + + menuItemInfo.fState = maximized ? MF_DISABLED : MF_ENABLED; + SetMenuItemInfo(systemMenu, SC_MOVE, FALSE, &menuItemInfo); + + menuItemInfo.fState = maximized ? MF_DISABLED : MF_ENABLED; + SetMenuItemInfo(systemMenu, SC_SIZE, FALSE, &menuItemInfo); + + menuItemInfo.fState = MF_ENABLED; + SetMenuItemInfo(systemMenu, SC_MINIMIZE, FALSE, &menuItemInfo); + + menuItemInfo.fState = maximized ? MF_DISABLED : MF_ENABLED; + SetMenuItemInfo(systemMenu, SC_MAXIMIZE, FALSE, &menuItemInfo); + + menuItemInfo.fState = MF_ENABLED; + SetMenuItemInfo(systemMenu, SC_CLOSE, FALSE, &menuItemInfo); + SetMenuDefaultItem(systemMenu, UINT_MAX, FALSE); + + POINT ptAbs = { x, y }; + ::ClientToScreen(GetHWND(), &ptAbs); + + BOOL menuItem = TrackPopupMenu(systemMenu, TPM_RETURNCMD, ptAbs.x, ptAbs.y, 0, GetHWND(), NULL); + if (menuItem != 0) { + PostMessage(GetHWND(), WM_SYSCOMMAND, menuItem, 0); + } +} + /* * JNI methods section * @@ -2048,4 +2090,27 @@ JNIEXPORT void JNICALL Java_com_sun_glass_ui_win_WinWindow__1setCursor PERFORM(); } +/* + * Class: com_sun_glass_ui_win_WinWindow + * Method: _showSystemMenu + * Signature: (JII)V + */ +JNIEXPORT void JNICALL Java_com_sun_glass_ui_win_WinWindow__1showSystemMenu + (JNIEnv *env, jobject jThis, jlong ptr, jint x, jint y) +{ + ENTER_MAIN_THREAD() + { + GlassWindow *pWindow = GlassWindow::FromHandle(hWnd); + if (pWindow) { + pWindow->ShowSystemMenu(x, y); + } + } + jint x, y; + LEAVE_MAIN_THREAD_WITH_hWnd; + + ARG(x) = x; + ARG(y) = y; + PERFORM(); +} + } // extern "C" diff --git a/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.h b/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.h index 72c986ccc9a..8d766e30ab6 100644 --- a/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.h +++ b/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.h @@ -103,6 +103,7 @@ class GlassWindow : public BaseWnd, public ViewContainer { void SetIcon(HICON hIcon); void HandleWindowPosChangedEvent(); + void ShowSystemMenu(int x, int y); protected: virtual LRESULT WindowProc(UINT msg, WPARAM wParam, LPARAM lParam); From fef8cfc0b03a41902bd90a7ad01bf589c758600e Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Sat, 26 Oct 2024 05:42:42 +0200 Subject: [PATCH 07/73] Windows: custom context menu overrides system menu --- .../src/main/java/com/sun/glass/ui/View.java | 31 ++++++---- .../java/com/sun/glass/ui/gtk/GtkWindow.java | 2 +- .../java/com/sun/glass/ui/mac/MacWindow.java | 2 +- .../java/com/sun/glass/ui/win/WinView.java | 24 +++++++- .../java/com/sun/glass/ui/win/WinWindow.java | 25 ++++---- .../com/sun/javafx/tk/TKSceneListener.java | 10 +-- .../tk/quantum/GlassViewEventHandler.java | 61 ++++++++++--------- .../src/main/java/javafx/scene/Scene.java | 43 +++++++------ .../src/main/native-glass/win/GlassWindow.cpp | 18 +++++- .../src/main/native-glass/win/GlassWindow.h | 1 + 10 files changed, 136 insertions(+), 81 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/View.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/View.java index 97846bc8fa6..e2fbb338b2a 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/View.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/View.java @@ -26,7 +26,7 @@ import com.sun.glass.events.MouseEvent; import com.sun.glass.events.ViewEvent; - +import javafx.scene.Node; import java.lang.annotation.Native; import java.lang.ref.WeakReference; import java.security.AccessController; @@ -69,8 +69,9 @@ public boolean handleKeyEvent(View view, long time, int action, int keyCode, char[] keyChars, int modifiers) { return false; } - public void handleMenuEvent(View view, int x, int y, int xAbs, + public boolean handleMenuEvent(View view, int x, int y, int xAbs, int yAbs, boolean isKeyboardTrigger) { + return false; } public void handleMouseEvent(View view, long time, int type, int button, int x, int y, int xAbs, int yAbs, @@ -365,8 +366,16 @@ public void handleSwipeGestureEvent(View view, long time, int type, int yAbs) { } - public boolean handleDragAreaHitTestEvent(double x, double y) { - return false; + /** + * Returns the draggable area node at the specified coordinates, or {@code null} + * if the specified coordinates do not intersect with a draggable area. + * + * @param x the X coordinate + * @param y the Y coordinate + * @return the draggable area node, or {@code null} + */ + public Node pickDragAreaNode(double x, double y) { + return null; } public Accessible getSceneAccessible() { @@ -576,10 +585,11 @@ private void handleMouseEvent(long time, int type, int button, int x, int y, } } - private void handleMenuEvent(int x, int y, int xAbs, int yAbs, boolean isKeyboardTrigger) { + protected boolean handleMenuEvent(int x, int y, int xAbs, int yAbs, boolean isKeyboardTrigger) { if (shouldHandleEvent()) { - this.eventHandler.handleMenuEvent(this, x, y, xAbs, yAbs, isKeyboardTrigger); + return this.eventHandler.handleMenuEvent(this, x, y, xAbs, yAbs, isKeyboardTrigger); } + return false; } public void handleBeginTouchEvent(View view, long time, int modifiers, @@ -950,14 +960,11 @@ protected void notifyMouse(int type, int button, int x, int y, int xAbs, // If we have a non-client handler, we give it the first chance to handle the event. // Note that a full-screen window has no non-client area, and thus the non-client handler // is not notified. - if (!inFullscreen - && nonClientHandler != null - && nonClientHandler.handleMouseEvent(type, button, x, y, xAbs, yAbs, clickCount)) { - return; - } + boolean handled = !inFullscreen && nonClientHandler != null + && nonClientHandler.handleMouseEvent(type, button, x, y, xAbs, yAbs, clickCount); // We never send non-client events to the application. - if (MouseEvent.isNonClientEvent(type)) { + if (handled || MouseEvent.isNonClientEvent(type)) { return; } diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java index d02c4ffe390..cd3ef7701c4 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java @@ -264,6 +264,6 @@ private boolean dragAreaHitTest(int x, int y) { return false; } - return eventHandler.handleDragAreaHitTestEvent(wx, wy); + return eventHandler.pickDragAreaNode(wx, wy) != null; } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java index 501991e4d1a..9f05f9744e6 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java @@ -168,7 +168,7 @@ public NonClientHandler getNonClientHandler() { double wy = y / platformScaleY; View.EventHandler eventHandler = view != null ? view.getEventHandler() : null; - if (eventHandler != null && eventHandler.handleDragAreaHitTestEvent(wx, wy)) { + if (eventHandler != null && eventHandler.pickDragAreaNode(wx, wy) != null) { if (clickCount == 2) { maximize(!isMaximized()); } else if (clickCount == 1) { diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinView.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinView.java index ddf233c67be..c8b7af84cb8 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinView.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinView.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -95,5 +95,27 @@ protected void notifyResize(int width, int height) { // to be recalculated. updateLocation(); } + + @Override + protected void notifyMenu(int x, int y, int xAbs, int yAbs, boolean isKeyboardTrigger) { + // If all of the following conditions are satisfied, we open a system menu at the specified coordinates: + // 1. The application didn't consume the menu event. + // 2. The window is an EXTENDED window. + // 3. The menu event occurred on a draggable area. + if (!handleMenuEvent(x, y, xAbs, yAbs, isKeyboardTrigger)) { + var window = (WinWindow)getWindow(); + if (!window.isExtendedWindow()) { + return; + } + + double wx = x / window.getPlatformScaleX(); + double wy = y / window.getPlatformScaleY(); + + EventHandler eventHandler = getEventHandler(); + if (eventHandler != null && eventHandler.pickDragAreaNode(wx, wy) != null) { + window.showSystemMenu(x, y); + } + } + } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java index d4495c09861..6895c5a0500 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java @@ -24,7 +24,6 @@ */ package com.sun.glass.ui.win; -import com.sun.glass.events.MouseEvent; import com.sun.glass.ui.Cursor; import com.sun.glass.ui.WindowControlsOverlay; import com.sun.glass.ui.NonClientHandler; @@ -371,20 +370,22 @@ public NonClientHandler getNonClientHandler() { return (type, button, x, y, xAbs, yAbs, clickCount) -> { double wx = x / platformScaleX; double wy = y / platformScaleY; - boolean handled = overlay.handleMouseEvent(type, button, wx, wy); - // A right click on a non-client header bar area opens the system menu. - if (!handled && type == MouseEvent.NC_UP && button == MouseEvent.BUTTON_RIGHT) { - View.EventHandler eventHandler = view != null ? view.getEventHandler() : null; - if (eventHandler != null && eventHandler.handleDragAreaHitTestEvent(wx, wy)) { - _showSystemMenu(getRawHandle(), x, y); - } - } - - return handled; + // Give the window button overlay the first chance to handle the event. + return overlay.handleMouseEvent(type, button, wx, wy); }; } + /** + * Opens a system menu at the specified coordinates. + * + * @param x the X coordinate in physical pixels + * @param y the Y coordinate in physical pixels + */ + public void showSystemMenu(int x, int y) { + _showSystemMenu(getRawHandle(), x, y); + } + /** * Classifies the window region at the specified physical coordinate. *

@@ -421,7 +422,7 @@ enum HT { // Otherwise, test if the cursor is over a draggable area and return HTCAPTION. View.EventHandler eventHandler = view.getEventHandler(); - if (eventHandler != null && eventHandler.handleDragAreaHitTestEvent(wx, wy)) { + if (eventHandler != null && eventHandler.pickDragAreaNode(wx, wy) != null) { return HT.CAPTION.value; } diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKSceneListener.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKSceneListener.java index 7a5cdfe8ac1..8593f08c220 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKSceneListener.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKSceneListener.java @@ -28,6 +28,7 @@ import com.sun.glass.ui.Accessible; import javafx.collections.ObservableList; import javafx.event.EventType; +import javafx.scene.Node; import javafx.scene.input.*; /** @@ -84,7 +85,7 @@ public void scrollEvent( boolean _altDown, boolean _metaDown, boolean _direct, boolean _inertia); - public void menuEvent(double x, double y, double xAbs, double yAbs, + public boolean menuEvent(double x, double y, double xAbs, double yAbs, boolean isKeyboardTrigger); public void zoomEvent( @@ -122,11 +123,12 @@ public void touchEventNext( public Accessible getSceneAccessible(); /** - * Tests whether the specified coordinate identifies a draggable area. + * Returns the draggable area node at the specified coordinates, or {@code null} + * if the specified coordinates do not intersect with a draggable area. * * @param x the X coordinate relative to the scene * @param y the Y coordinate relative to the scene - * @return {@code true} if the area is draggable, {@code false} otherwise + * @return the draggable area node, or {@code null} */ - public boolean dragAreaHitTest(double x, double y); + public Node pickDragAreaNode(double x, double y); } diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/GlassViewEventHandler.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/GlassViewEventHandler.java index 7d5f8b90ce8..c53275456ec 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/GlassViewEventHandler.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/GlassViewEventHandler.java @@ -48,6 +48,7 @@ import javafx.event.EventType; import javafx.geometry.Point2D; +import javafx.scene.Node; import javafx.scene.input.InputMethodEvent; import javafx.scene.input.InputMethodHighlight; import javafx.scene.input.InputMethodTextRun; @@ -455,9 +456,9 @@ public void handleMouseEvent(View view, long time, int type, int button, } @SuppressWarnings("removal") - @Override public void handleMenuEvent(final View view, - final int x, final int y, final int xAbs, final int yAbs, - final boolean isKeyboardTrigger) + @Override public boolean handleMenuEvent(final View view, + final int x, final int y, final int xAbs, final int yAbs, + final boolean isKeyboardTrigger) { if (PULSE_LOGGING_ENABLED) { PulseLogger.newInput("MENU_EVENT"); @@ -467,33 +468,35 @@ public void handleMouseEvent(View view, long time, int type, int button, if (stage != null) { stage.setInAllowedEventHandler(true); } - QuantumToolkit.runWithoutRenderLock(() -> { - return AccessController.doPrivileged((PrivilegedAction) () -> { - if (scene.sceneListener != null) { - double pScaleX, pScaleY, spx, spy, sx, sy; - final Window w = view.getWindow(); - if (w != null) { - pScaleX = w.getPlatformScaleX(); - pScaleY = w.getPlatformScaleY(); - Screen scr = w.getScreen(); - if (scr != null) { - spx = scr.getPlatformX(); - spy = scr.getPlatformY(); - sx = scr.getX(); - sy = scr.getY(); - } else { - spx = spy = sx = sy = 0.0; - } + return QuantumToolkit.runWithoutRenderLock(() -> { + return AccessController.doPrivileged((PrivilegedAction) () -> { + if (scene.sceneListener == null) { + return false; + } + + double pScaleX, pScaleY, spx, spy, sx, sy; + final Window w = view.getWindow(); + if (w != null) { + pScaleX = w.getPlatformScaleX(); + pScaleY = w.getPlatformScaleY(); + Screen scr = w.getScreen(); + if (scr != null) { + spx = scr.getPlatformX(); + spy = scr.getPlatformY(); + sx = scr.getX(); + sy = scr.getY(); } else { - pScaleX = pScaleY = 1.0; spx = spy = sx = sy = 0.0; } - scene.sceneListener.menuEvent(x / pScaleX, y / pScaleY, - sx + (xAbs - spx) / pScaleX, - sy + (yAbs - spy) / pScaleY, - isKeyboardTrigger); + } else { + pScaleX = pScaleY = 1.0; + spx = spy = sx = sy = 0.0; } - return null; + + return scene.sceneListener.menuEvent(x / pScaleX, y / pScaleY, + sx + (xAbs - spx) / pScaleX, + sy + (yAbs - spy) / pScaleY, + isKeyboardTrigger); }, scene.getAccessControlContext()); }); } finally { @@ -1408,13 +1411,13 @@ public Accessible getSceneAccessible() { } @Override - public boolean handleDragAreaHitTestEvent(double x, double y) { + public Node pickDragAreaNode(double x, double y) { return QuantumToolkit.runWithoutRenderLock(() -> { if (scene.sceneListener != null) { - return scene.sceneListener.dragAreaHitTest(x, y); + return scene.sceneListener.pickDragAreaNode(x, y); } - return false; + return null; }); } } diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java b/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java index 02a367697e0..c69e674f392 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java @@ -1903,7 +1903,7 @@ void processMouseEvent(MouseEvent e) { mouseHandler.process(e, false); } - private void processMenuEvent(double x2, double y2, double xAbs, double yAbs, boolean isKeyboardTrigger) { + private boolean processMenuEvent(double x2, double y2, double xAbs, double yAbs, boolean isKeyboardTrigger) { EventTarget eventTarget = null; Scene.inMousePick = true; if (isKeyboardTrigger) { @@ -1937,12 +1937,16 @@ private void processMenuEvent(double x2, double y2, double xAbs, double yAbs, bo } } + boolean handled = false; + if (eventTarget != null) { ContextMenuEvent context = new ContextMenuEvent(ContextMenuEvent.CONTEXT_MENU_REQUESTED, x2, y2, xAbs, yAbs, isKeyboardTrigger, res); - Event.fireEvent(eventTarget, context); + handled = EventUtil.fireEvent(eventTarget, context) == null; } Scene.inMousePick = false; + + return handled; } private void processGestureEvent(GestureEvent e, TouchGesture gesture) { @@ -2742,9 +2746,9 @@ public void inputMethodEvent(EventType type, } @Override - public void menuEvent(double x, double y, double xAbs, double yAbs, + public boolean menuEvent(double x, double y, double xAbs, double yAbs, boolean isKeyboardTrigger) { - Scene.this.processMenuEvent(x, y, xAbs,yAbs, isKeyboardTrigger); + return Scene.this.processMenuEvent(x, y, xAbs,yAbs, isKeyboardTrigger); } @Override @@ -3002,30 +3006,31 @@ public void touchEventEnd() { private final PickRay pickRay = new PickRay(); @Override - public boolean dragAreaHitTest(double x, double y) { + public Node pickDragAreaNode(double x, double y) { Node root = Scene.this.getRoot(); - if (root != null) { - pickRay.set(x, y, 1, 0, Double.POSITIVE_INFINITY); - var pickResultChooser = new PickResultChooser(); - root.pickNode(pickRay, pickResultChooser); - var intersectedNode = pickResultChooser.getIntersectedNode(); + if (root == null) { + return null; + } + + pickRay.set(x, y, 1, 0, Double.POSITIVE_INFINITY); + var pickResultChooser = new PickResultChooser(); + root.pickNode(pickRay, pickResultChooser); + Node intersectedNode = pickResultChooser.getIntersectedNode(); + Boolean draggable = intersectedNode instanceof HeaderBarBase ? true : null; + while (intersectedNode != null) { if (intersectedNode instanceof HeaderBarBase) { - return true; + return draggable == Boolean.TRUE ? intersectedNode : null; } - while (intersectedNode != null) { - if (HeaderBarBase.isDraggable(intersectedNode) instanceof Boolean value) { - return value; - } - - intersectedNode = intersectedNode.getParent(); + if (draggable == null && HeaderBarBase.isDraggable(intersectedNode) instanceof Boolean value) { + draggable = value; } - return false; + intersectedNode = intersectedNode.getParent(); } - return false; + return null; } @Override diff --git a/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp b/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp index 1eeaa6318f3..1f9707aaeaf 100644 --- a/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp +++ b/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp @@ -548,7 +548,7 @@ LRESULT GlassWindow::WindowProc(UINT msg, WPARAM wParam, LPARAM lParam) CheckUngrab(); // check if other owned windows hierarchy holds the grab if (m_isExtended) { - HandleViewNonClientMouseEvent(GetHWND(), msg, wParam, lParam); + HandleNonClientMouseEvents(msg, wParam, lParam); // We need to handle clicks on the min/max/close regions, as otherwise Windows will // draw very ugly buttons on top of our window. @@ -570,7 +570,7 @@ LRESULT GlassWindow::WindowProc(UINT msg, WPARAM wParam, LPARAM lParam) case WM_NCMOUSELEAVE: case WM_NCMOUSEMOVE: if (m_isExtended) { - HandleViewNonClientMouseEvent(GetHWND(), msg, wParam, lParam); + HandleNonClientMouseEvents(msg, wParam, lParam); if (wParam == HTMINBUTTON || wParam == HTMAXBUTTON || wParam == HTCLOSE) { return 0; @@ -625,6 +625,20 @@ bool GlassWindow::HandleMouseEvents(UINT msg, WPARAM wParam, LPARAM lParam) return false; } +void GlassWindow::HandleNonClientMouseEvents(UINT msg, WPARAM wParam, LPARAM lParam) +{ + HandleViewNonClientMouseEvent(GetHWND(), msg, wParam, lParam); + LRESULT result; + + // If the right mouse button was released on a HTCAPTION area, we synthesize a WM_CONTEXTMENU event. + // This allows JavaFX applications to respond to context menu events in the non-client header bar area. + if (msg == WM_NCRBUTTONUP + && HandleNCHitTestEvent(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), result) + && result == HTCAPTION) { + HandleViewMenuEvent(GetHWND(), WM_CONTEXTMENU, (WPARAM)GetHWND(), ::GetMessagePos()); + } +} + void GlassWindow::HandleCloseEvent() { JNIEnv* env = GetEnv(); diff --git a/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.h b/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.h index 8d766e30ab6..7cad2254c9d 100644 --- a/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.h +++ b/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.h @@ -188,6 +188,7 @@ class GlassWindow : public BaseWnd, public ViewContainer { bool HandleCommand(WORD cmdID); void HandleFocusDisabledEvent(); bool HandleMouseEvents(UINT msg, WPARAM wParam, LPARAM lParam); + void HandleNonClientMouseEvents(UINT msg, WPARAM wParam, LPARAM lParam); LRESULT HandleNCCalcSizeEvent(UINT msg, WPARAM wParam, LPARAM lParam); BOOL HandleNCHitTestEvent(SHORT, SHORT, LRESULT&); }; From 778e6c1f22dd531d4a064b09636286265c19277c Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Sat, 26 Oct 2024 22:21:23 +0200 Subject: [PATCH 08/73] GTK: prevent resizing below window button size, fix crash --- .../src/main/java/com/sun/glass/ui/View.java | 7 ++++- .../java/com/sun/glass/ui/gtk/GtkWindow.java | 10 +++++++ .../src/main/native-glass/gtk/GlassWindow.cpp | 17 +++++++++++ .../main/native-glass/gtk/glass_window.cpp | 29 ++++++++++++------- .../src/main/native-glass/gtk/glass_window.h | 11 ++++--- 5 files changed, 58 insertions(+), 16 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/View.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/View.java index e2fbb338b2a..cb62a4df94d 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/View.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/View.java @@ -960,7 +960,12 @@ protected void notifyMouse(int type, int button, int x, int y, int xAbs, // If we have a non-client handler, we give it the first chance to handle the event. // Note that a full-screen window has no non-client area, and thus the non-client handler // is not notified. - boolean handled = !inFullscreen && nonClientHandler != null + // Some implementations (like GTK) can fire synthesized events when they receive a mouse + // button event on the resize border. These events, even though happening on non-client + // regions, must not be processed by the non-client handler. For example, if a mouse click + // happens on the resize border that straddles the window close button, we don't want the + // close button to act on this click, because we just started a resize-drag operation. + boolean handled = !isSynthesized && !inFullscreen && nonClientHandler != null && nonClientHandler.handleMouseEvent(type, button, x, y, xAbs, yAbs, clickCount); // We never send non-client events to the application. diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java index cd3ef7701c4..1fb0aff1d72 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java @@ -96,6 +96,8 @@ protected boolean _setMenubar(long ptr, long menubarPtr) { @Override protected native void _setEnabled(long ptr, boolean enabled); + private native boolean _setSystemMinimumSize(long ptr, int width, int height); + @Override protected native boolean _setMinimumSize(long ptr, int width, int height); @@ -217,6 +219,14 @@ public WindowControlsOverlay getWindowOverlay() { if (windowControlsOverlay == null && isExtendedWindow()) { windowControlsOverlay = new WindowControlsOverlay( PlatformThemeObserver.getInstance().stylesheetProperty()); + + // Set the system-defined absolute minimum size to the size of the window buttons area, + // regardless of whether the application has specified a smaller minimum size. + windowControlsOverlay.metricsProperty().addListener((_, _, metrics) -> { + int width = (int)(metrics.size().getWidth() * platformScaleX); + int height = (int)(metrics.size().getHeight() * platformScaleY); + _setSystemMinimumSize(super.getRawHandle(), width, height); + }); } return windowControlsOverlay; diff --git a/modules/javafx.graphics/src/main/native-glass/gtk/GlassWindow.cpp b/modules/javafx.graphics/src/main/native-glass/gtk/GlassWindow.cpp index aa66a878fdd..885554e01f0 100644 --- a/modules/javafx.graphics/src/main/native-glass/gtk/GlassWindow.cpp +++ b/modules/javafx.graphics/src/main/native-glass/gtk/GlassWindow.cpp @@ -378,6 +378,23 @@ JNIEXPORT jboolean JNICALL Java_com_sun_glass_ui_gtk_GtkWindow__1setMinimumSize return JNI_TRUE; } +/* + * Class: com_sun_glass_ui_gtk_GtkWindow + * Method: _setSystemMinimumSize + * Signature: (JII)Z + */ +JNIEXPORT jboolean JNICALL Java_com_sun_glass_ui_gtk_GtkWindow__1setSystemMinimumSize + (JNIEnv * env, jobject obj, jlong ptr, jint w, jint h) +{ + (void)env; + (void)obj; + + WindowContext* ctx = JLONG_TO_WINDOW_CTX(ptr); + if (w < 0 || h < 0) return JNI_FALSE; + ctx->set_system_minimum_size(w, h); + return JNI_TRUE; +} + /* * Class: com_sun_glass_ui_gtk_GtkWindow * Method: _setMaximumSize diff --git a/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.cpp b/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.cpp index 8058e52dcf5..736747b599c 100644 --- a/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.cpp +++ b/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.cpp @@ -262,7 +262,7 @@ static inline jint gtk_button_number_to_mouse_button(guint button) { } } -void WindowContextBase::process_mouse_button(GdkEventButton* event) { +void WindowContextBase::process_mouse_button(GdkEventButton* event, bool synthesized) { // We only handle single press/release events here. if (event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) { return; @@ -332,7 +332,7 @@ void WindowContextBase::process_mouse_button(GdkEventButton* event) { (jint) event->x_root, (jint) event->y_root, gdk_modifier_mask_to_glass(state), (event->button == 3 && press) ? JNI_TRUE : JNI_FALSE, - JNI_FALSE); + synthesized); CHECK_JNI_EXCEPTION(mainEnv) if (jview && event->button == 3 && press) { @@ -1068,10 +1068,11 @@ void WindowContextTop::update_window_constraints() { GdkGeometry hints; if (resizable.value && !is_disabled) { - int min_w = (resizable.minw == -1) ? 1 - : resizable.minw - geometry.extents.left - geometry.extents.right; - int min_h = (resizable.minh == -1) ? 1 - : resizable.minh - geometry.extents.top - geometry.extents.bottom; + int w = std::max(resizable.sysminw, resizable.minw); + int h = std::max(resizable.sysminh, resizable.minh); + + int min_w = (w == -1) ? 1 : w - geometry.extents.left - geometry.extents.right; + int min_h = (h == -1) ? 1 : h - geometry.extents.top - geometry.extents.bottom; hints.min_width = (min_w < 1) ? 1 : min_w; hints.min_height = (min_h < 1) ? 1 : min_h; @@ -1247,6 +1248,12 @@ void WindowContextTop::set_enabled(bool enabled) { update_window_constraints(); } +void WindowContextTop::set_system_minimum_size(int w, int h) { + resizable.sysminw = w; + resizable.sysminh = h; + update_window_constraints(); +} + void WindowContextTop::set_minimum_size(int w, int h) { resizable.minw = (w <= 0) ? 1 : w; resizable.minh = (h <= 0) ? 1 : h; @@ -1402,7 +1409,7 @@ void WindowContextTop::notify_window_move() { * regions that are usually provided by the window manager. Note that a full-screen window has * no non-client regions. */ -void WindowContextTop::process_mouse_button(GdkEventButton* event) { +void WindowContextTop::process_mouse_button(GdkEventButton* event, bool synthesized) { // Non-EXTENDED or full-screen windows don't have additional behaviors, so we delegate // directly to the base implementation. if (is_fullscreen || frame_type != EXTENDED || jwindow == NULL) { @@ -1433,9 +1440,9 @@ void WindowContextTop::process_mouse_button(GdkEventButton* event) { // Send a synthetic PRESS + RELEASE to FX. This allows FX to do things that need to be done // prior to resizing the window, like closing a popup menu. We do this because we won't be // sending events to FX once the resize operation has started. - WindowContextBase::process_mouse_button(event); + WindowContextBase::process_mouse_button(event, true); event->type = GDK_BUTTON_RELEASE; - WindowContextBase::process_mouse_button(event); + WindowContextBase::process_mouse_button(event, true); gint rx = 0, ry = 0; gdk_window_get_root_coords(get_gdk_window(), event->x, event->y, &rx, &ry); @@ -1450,9 +1457,9 @@ void WindowContextTop::process_mouse_button(GdkEventButton* event) { // Clicking on a draggable area starts a move-drag operation. if (shouldStartMoveDrag) { // Send a synthetic PRESS + RELEASE to FX. - WindowContextBase::process_mouse_button(event); + WindowContextBase::process_mouse_button(event, true); event->type = GDK_BUTTON_RELEASE; - WindowContextBase::process_mouse_button(event); + WindowContextBase::process_mouse_button(event, true); gint rx = 0, ry = 0; gdk_window_get_root_coords(get_gdk_window(), event->x, event->y, &rx, &ry); diff --git a/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.h b/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.h index 9e2ee1cfe50..56b3c08d77c 100644 --- a/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.h +++ b/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.h @@ -120,6 +120,7 @@ class WindowContext : public DeletedMemDebug<0xCC> { virtual void set_title(const char*) = 0; virtual void set_alpha(double) = 0; virtual void set_enabled(bool) = 0; + virtual void set_system_minimum_size(int, int) = 0; virtual void set_minimum_size(int, int) = 0; virtual void set_maximum_size(int, int) = 0; virtual void set_minimized(bool) = 0; @@ -139,7 +140,7 @@ class WindowContext : public DeletedMemDebug<0xCC> { virtual void process_destroy() = 0; virtual void process_delete() = 0; virtual void process_expose(GdkEventExpose*) = 0; - virtual void process_mouse_button(GdkEventButton*) = 0; + virtual void process_mouse_button(GdkEventButton*, bool synthesized = false) = 0; virtual void process_mouse_motion(GdkEventMotion*) = 0; virtual void process_mouse_scroll(GdkEventScroll*) = 0; virtual void process_mouse_cross(GdkEventCrossing*) = 0; @@ -240,7 +241,7 @@ class WindowContextBase: public WindowContext { void process_destroy(); void process_delete(); void process_expose(GdkEventExpose*); - void process_mouse_button(GdkEventButton*); + void process_mouse_button(GdkEventButton*, bool synthesized = false); void process_mouse_motion(GdkEventMotion*); void process_mouse_scroll(GdkEventScroll*); void process_mouse_cross(GdkEventCrossing*); @@ -270,9 +271,10 @@ class WindowContextTop: public WindowContextBase { WindowGeometry geometry; struct _Resizable {// we can't use set/get gtk_window_resizable function _Resizable(): value(true), - minw(-1), minh(-1), maxw(-1), maxh(-1) {} + minw(-1), minh(-1), maxw(-1), maxh(-1), sysminw(-1), sysminh(-1) {} bool value; //actual value of resizable for a window int minw, minh, maxw, maxh; //minimum and maximum window width/height; + int sysminw, sysminh; // size of window button area of EXTENDED windows } resizable; bool on_top; @@ -291,7 +293,7 @@ class WindowContextTop: public WindowContextBase { void process_configure(GdkEventConfigure*); void process_destroy(); void process_mouse_motion(GdkEventMotion*); - void process_mouse_button(GdkEventButton*); + void process_mouse_button(GdkEventButton*, bool synthesized = false); void work_around_compiz_state(); WindowFrameExtents get_frame_extents(); @@ -305,6 +307,7 @@ class WindowContextTop: public WindowContextBase { void set_title(const char*); void set_alpha(double); void set_enabled(bool); + void set_system_minimum_size(int, int); void set_minimum_size(int, int); void set_maximum_size(int, int); void set_icon(GdkPixbuf*); From 3b468fe556365444a81fa84ef1831d29a089d0ff Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Sat, 26 Oct 2024 23:29:16 +0200 Subject: [PATCH 09/73] GTK: add system menu --- .../java/com/sun/glass/ui/gtk/GtkView.java | 22 ++++++++++++++++ .../java/com/sun/glass/ui/gtk/GtkWindow.java | 12 +++++++++ .../src/main/native-glass/gtk/GlassWindow.cpp | 15 +++++++++++ .../main/native-glass/gtk/glass_window.cpp | 26 +++++++++++++++++++ .../src/main/native-glass/gtk/glass_window.h | 2 ++ 5 files changed, 77 insertions(+) diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkView.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkView.java index 2100216bf7d..12ff513efc0 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkView.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkView.java @@ -215,4 +215,26 @@ protected void notifyInputMethodCaret(int pos, int direction, int style) { } notifyInputMethod(preedit.toString(), null, null, null, 0, lastCaret, 0); } + + @Override + protected void notifyMenu(int x, int y, int xAbs, int yAbs, boolean isKeyboardTrigger) { + // If all of the following conditions are satisfied, we open a system menu at the specified coordinates: + // 1. The application didn't consume the menu event. + // 2. The window is an EXTENDED window. + // 3. The menu event occurred on a draggable area. + if (!handleMenuEvent(x, y, xAbs, yAbs, isKeyboardTrigger)) { + var window = (GtkWindow)getWindow(); + if (!window.isExtendedWindow()) { + return; + } + + double wx = x / window.getPlatformScaleX(); + double wy = y / window.getPlatformScaleY(); + + EventHandler eventHandler = getEventHandler(); + if (eventHandler != null && eventHandler.pickDragAreaNode(wx, wy) != null) { + window.showSystemMenu(x, y); + } + } + } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java index 1fb0aff1d72..c690c1b22a1 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java @@ -124,6 +124,8 @@ protected boolean _setMenubar(long ptr, long menubarPtr) { protected native long _getNativeWindowImpl(long ptr); + private native void _showSystemMenu(long ptr, int x, int y); + private native boolean isVisible(long ptr); @Override @@ -247,6 +249,16 @@ public NonClientHandler getNonClientHandler() { }; } + /** + * Opens a system menu at the specified coordinates. + * + * @param x the X coordinate in physical pixels + * @param y the Y coordinate in physical pixels + */ + public void showSystemMenu(int x, int y) { + _showSystemMenu(super.getRawHandle(), x, y); + } + /** * Returns whether the window is draggable at the specified coordinate. *

diff --git a/modules/javafx.graphics/src/main/native-glass/gtk/GlassWindow.cpp b/modules/javafx.graphics/src/main/native-glass/gtk/GlassWindow.cpp index 885554e01f0..f0f6e39cbe6 100644 --- a/modules/javafx.graphics/src/main/native-glass/gtk/GlassWindow.cpp +++ b/modules/javafx.graphics/src/main/native-glass/gtk/GlassWindow.cpp @@ -544,6 +544,21 @@ JNIEXPORT void JNICALL Java_com_sun_glass_ui_gtk_GtkWindow__1setCustomCursor ctx->set_cursor(cursor); } +/* + * Class: com_sun_glass_ui_gtk_GtkWindow + * Method: _showSystemMenu + * Signature: (JII)V + */ +JNIEXPORT void JNICALL Java_com_sun_glass_ui_gtk_GtkWindow__1showSystemMenu + (JNIEnv * env, jobject obj, jlong ptr, jint x, jint y) +{ + (void)env; + (void)obj; + + WindowContext* ctx = JLONG_TO_WINDOW_CTX(ptr); + ctx->show_system_menu(x, y); +} + /* * Class: com_sun_glass_ui_gtk_GtkWindow * Method: isVisible diff --git a/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.cpp b/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.cpp index 736747b599c..c4fd26fcbcc 100644 --- a/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.cpp +++ b/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.cpp @@ -1404,6 +1404,32 @@ void WindowContextTop::notify_window_move() { } } +void WindowContextTop::show_system_menu(int x, int y) { + GdkDisplay* display = gdk_display_get_default(); + if (!display) { + return; + } + + GdkSeat* seat = gdk_display_get_default_seat(display); + GdkDevice* device = gdk_seat_get_pointer(seat); + if (!device) { + return; + } + + gint rx = 0, ry = 0; + gdk_window_get_root_coords(gdk_window, x, y, &rx, &ry); + + GdkEvent* event = (GdkEvent*)gdk_event_new(GDK_BUTTON_PRESS); + GdkEventButton* buttonEvent = (GdkEventButton*)event; + buttonEvent->x_root = rx; + buttonEvent->y_root = ry; + buttonEvent->window = g_object_ref(gdk_window); + buttonEvent->device = g_object_ref(device); + + gdk_window_show_window_menu(gdk_window, event); + gdk_event_free(event); +} + /* * Handles mouse button events of EXTENDED windows and adds the window behaviors for non-client * regions that are usually provided by the window manager. Note that a full-screen window has diff --git a/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.h b/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.h index 56b3c08d77c..413e90fcdcf 100644 --- a/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.h +++ b/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.h @@ -105,6 +105,7 @@ class WindowContext : public DeletedMemDebug<0xCC> { virtual void paint(void* data, jint width, jint height) = 0; virtual WindowFrameExtents get_frame_extents() = 0; + virtual void show_system_menu(int x, int y) = 0; virtual void enter_fullscreen() = 0; virtual void exit_fullscreen() = 0; virtual void set_visible(bool) = 0; @@ -320,6 +321,7 @@ class WindowContextTop: public WindowContextBase { void update_view_size(); void notify_view_resize(); + void show_system_menu(int x, int y); void enter_fullscreen(); void exit_fullscreen(); From 0526edbae806510c44da32227e5119f816d3ad22 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Mon, 28 Oct 2024 06:32:31 +0100 Subject: [PATCH 10/73] small code changes --- .../main/java/com/sun/glass/ui/Window.java | 20 +++++++++++++------ .../java/com/sun/glass/ui/gtk/GtkWindow.java | 10 ++-------- .../java/com/sun/glass/ui/mac/MacWindow.java | 18 ++++++----------- .../java/com/sun/glass/ui/win/WinWindow.java | 9 +-------- .../javafx/scene/layout/HeaderBarBase.java | 19 ++++++++---------- 5 files changed, 31 insertions(+), 45 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java index bbbcd2389eb..f0a5db0ed38 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java @@ -25,12 +25,16 @@ package com.sun.glass.ui; import com.sun.glass.events.WindowEvent; +import com.sun.javafx.binding.ObjectConstant; import com.sun.prism.impl.PrismSettings; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import javafx.scene.Parent; - +import javafx.scene.layout.Region; import java.lang.annotation.Native; - import java.util.Collections; import java.util.LinkedList; import java.util.List; @@ -233,6 +237,10 @@ public static final class Level { private int maximumWidth = Integer.MAX_VALUE, maximumHeight = Integer.MAX_VALUE; private EventHandler eventHandler; + private ObservableList nonClientAreas; + + protected final ObjectProperty windowOverlayMetrics = + new SimpleObjectProperty<>(this, "windowOverlayMetrics"); protected abstract long _createWindow(long ownerPtr, long screenPtr, int mask); protected Window(Window owner, Screen screen, int styleMask) { @@ -426,16 +434,16 @@ public void setMenuBar(final MenuBar menubar) { /** * Returns metrics of the window-provided overlay controls. * - * @return the overlay metrics, or {@code null} if the window type does not support metrics + * @return the overlay metrics */ - public ObservableValue windowOverlayMetrics() { - return null; + public ObservableValue getWindowOverlayMetrics() { + return windowOverlayMetrics; } /** * Returns the window-provided overlay controls, which are rendered above all application content. * - * @return the overlay, or {@code null} if the window type does not provide overlay controls + * @return the overlay, or {@code null} if the window does not provide an overlay */ public Parent getWindowOverlay() { return null; diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java index c690c1b22a1..61732aa9569 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java @@ -33,8 +33,6 @@ import com.sun.glass.ui.View; import com.sun.glass.ui.Window; import com.sun.glass.ui.WindowControlsOverlay; -import com.sun.glass.ui.WindowOverlayMetrics; -import javafx.beans.value.ObservableValue; class GtkWindow extends Window { @@ -210,12 +208,6 @@ public long getRawHandle() { private WindowControlsOverlay windowControlsOverlay; - @Override - public ObservableValue windowOverlayMetrics() { - var overlay = getWindowOverlay(); - return overlay != null ? overlay.metricsProperty() : null; - } - @Override public WindowControlsOverlay getWindowOverlay() { if (windowControlsOverlay == null && isExtendedWindow()) { @@ -229,6 +221,8 @@ public WindowControlsOverlay getWindowOverlay() { int height = (int)(metrics.size().getHeight() * platformScaleY); _setSystemMinimumSize(super.getRawHandle(), width, height); }); + + windowOverlayMetrics.bind(windowControlsOverlay.metricsProperty()); } return windowControlsOverlay; diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java index 9f05f9744e6..9e0962ebc1e 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java @@ -33,7 +33,6 @@ import com.sun.glass.ui.View; import com.sun.glass.ui.Window; import com.sun.glass.ui.WindowOverlayMetrics; -import com.sun.javafx.binding.ObjectConstant; import javafx.beans.value.ObservableValue; import javafx.geometry.Dimension2D; import javafx.geometry.HorizontalDirection; @@ -183,18 +182,13 @@ public NonClientHandler getNonClientHandler() { private native boolean _isRightToLeftLayoutDirection(); - private ObservableValue windowOverlayMetrics; - @Override - public ObservableValue windowOverlayMetrics() { - if (windowOverlayMetrics == null) { - HorizontalDirection direction = _isRightToLeftLayoutDirection() - ? HorizontalDirection.RIGHT - : HorizontalDirection.LEFT; - - windowOverlayMetrics = ObjectConstant.valueOf( - new WindowOverlayMetrics(direction, new Dimension2D(78, 38))); - } + public ObservableValue getWindowOverlayMetrics() { + HorizontalDirection direction = _isRightToLeftLayoutDirection() + ? HorizontalDirection.RIGHT + : HorizontalDirection.LEFT; + + windowOverlayMetrics.set(new WindowOverlayMetrics(direction, new Dimension2D(78, 38))); return windowOverlayMetrics; } diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java index 6895c5a0500..d669846bd97 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java @@ -31,9 +31,7 @@ import com.sun.glass.ui.Screen; import com.sun.glass.ui.View; import com.sun.glass.ui.Window; -import com.sun.glass.ui.WindowOverlayMetrics; import com.sun.javafx.binding.StringConstant; -import javafx.beans.value.ObservableValue; /** * MS Windows platform implementation class for Window. @@ -340,12 +338,6 @@ void setDeferredClosing(boolean dc) { private WindowControlsOverlay windowControlsOverlay; - @Override - public ObservableValue windowOverlayMetrics() { - var overlay = getWindowOverlay(); - return overlay != null ? overlay.metricsProperty() : null; - } - @Override public WindowControlsOverlay getWindowOverlay() { if (windowControlsOverlay == null && isExtendedWindow()) { @@ -355,6 +347,7 @@ public WindowControlsOverlay getWindowOverlay() { } windowControlsOverlay = new WindowControlsOverlay(StringConstant.valueOf(url.toExternalForm())); + windowOverlayMetrics.bind(windowControlsOverlay.metricsProperty()); } return windowControlsOverlay; diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java index af15f431042..8a0049d00fb 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java @@ -29,7 +29,6 @@ import com.sun.javafx.stage.StageHelper; import com.sun.javafx.tk.quantum.WindowStage; import javafx.beans.property.ReadOnlyObjectWrapper; -import javafx.beans.value.ObservableValue; import javafx.geometry.Dimension2D; import javafx.geometry.HorizontalDirection; import javafx.scene.Node; @@ -82,7 +81,7 @@ public static Boolean isDraggable(Node child) { return (Boolean)Pane.getConstraint(child, DRAGGABLE); } - private Subscription metricsSubscription; + private Subscription subscription; private WindowOverlayMetrics currentMetrics; private boolean currentFullScreen; @@ -105,18 +104,16 @@ protected HeaderBarBase() { private void onShowingChanged(boolean showing) { if (!showing) { - if (metricsSubscription != null) { - metricsSubscription.unsubscribe(); - metricsSubscription = null; + if (subscription != null) { + subscription.unsubscribe(); + subscription = null; } } else if (getScene().getWindow() instanceof Stage stage && StageHelper.getPeer(stage) instanceof WindowStage windowStage) { - ObservableValue metrics = - windowStage.getPlatformWindow().windowOverlayMetrics(); - - if (metrics != null) { - metricsSubscription = metrics.subscribe(this::onMetricsChanged); - } + subscription = windowStage + .getPlatformWindow() + .getWindowOverlayMetrics() + .subscribe(this::onMetricsChanged); } } From 95736df772d33bda0ec0f116a5d3404df58b6ef1 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Mon, 28 Oct 2024 06:34:36 +0100 Subject: [PATCH 11/73] remove unused code --- .../javafx.graphics/src/main/java/com/sun/glass/ui/Window.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java index f0a5db0ed38..57e44b8ac89 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java @@ -25,7 +25,6 @@ package com.sun.glass.ui; import com.sun.glass.events.WindowEvent; -import com.sun.javafx.binding.ObjectConstant; import com.sun.prism.impl.PrismSettings; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; @@ -33,7 +32,6 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.Parent; -import javafx.scene.layout.Region; import java.lang.annotation.Native; import java.util.Collections; import java.util.LinkedList; @@ -237,7 +235,6 @@ public static final class Level { private int maximumWidth = Integer.MAX_VALUE, maximumHeight = Integer.MAX_VALUE; private EventHandler eventHandler; - private ObservableList nonClientAreas; protected final ObjectProperty windowOverlayMetrics = new SimpleObjectProperty<>(this, "windowOverlayMetrics"); From c0b588f80ca76565735117424b81bfd65d692d77 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Mon, 28 Oct 2024 20:26:38 +0100 Subject: [PATCH 12/73] set minHeight to native height of title bar --- .../java/javafx/scene/layout/HeaderBar.java | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java index e5bac22da28..bb0003ce349 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java @@ -28,6 +28,7 @@ import com.sun.javafx.geom.Vec2d; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectPropertyBase; +import javafx.css.StyleableDoubleProperty; import javafx.geometry.HPos; import javafx.geometry.Insets; import javafx.geometry.NodeOrientation; @@ -157,15 +158,16 @@ public static Insets getMargin(Node child) { return (Insets)Pane.getConstraint(child, MARGIN); } - private static Insets getNodeMargin(Node child) { - Insets margin = getMargin(child); - return margin != null ? margin : Insets.EMPTY; - } - /** * Creates a new {@code HeaderBar}. */ public HeaderBar() { + // Inflate the minHeight property. This is important so that we can track whether a stylesheet or + // user code changes the property value before we set it to the height of the native title bar. + minHeightProperty(); + + leftSystemInsetProperty().subscribe(this::updateDefaultMinHeight); + rightSystemInsetProperty().subscribe(this::updateDefaultMinHeight); } /** @@ -176,6 +178,7 @@ public HeaderBar() { * @param trailing the trailing node */ public HeaderBar(Node leading, Node center, Node trailing) { + this(); setLeading(leading); setCenter(center); setTrailing(trailing); @@ -481,6 +484,21 @@ private double getAreaHeight(Node child, double width, boolean minimum) { return 0; } + private Insets getNodeMargin(Node child) { + Insets margin = getMargin(child); + return margin != null ? margin : Insets.EMPTY; + } + + private void updateDefaultMinHeight() { + var minHeight = (StyleableDoubleProperty)minHeightProperty(); + + // Only change the default minHeight if it was not set by a stylesheet or application code. + if (minHeight.getStyleOrigin() == null) { + double height = Math.max(getLeftSystemInset().getHeight(), getRightSystemInset().getHeight()); + ((StyleableDoubleProperty)minHeightProperty()).applyStyle(null, height); + } + } + private abstract class NodeProperty extends ObjectPropertyBase { private Node value; From d7f88c3312d861c824f86b10777c8d0feb40db0d Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Mon, 28 Oct 2024 20:38:02 +0100 Subject: [PATCH 13/73] better documentation --- .../src/main/java/javafx/scene/layout/HeaderBar.java | 3 +++ .../src/main/java/javafx/stage/StageStyle.java | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java index bb0003ce349..277f1f52af4 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java @@ -57,6 +57,9 @@ * {@code HeaderBar} honors the minimum, preferred, and maximum sizes of its children. If the child's * resizable range prevents it from be resized to fit within its position, it will be vertically centered * relative to the available space; this alignment can be customized with a layout constraint. + *

+ * The default {@link #minHeightProperty() minHeight} of the {@code HeaderBar} is set to match the height + * of the platform-specific default window buttons. * *

Layout constraints

* An application may set constraints on individual children to customize their layout. diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java b/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java index 26a040f77a2..2e216ee2615 100644 --- a/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java +++ b/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java @@ -80,8 +80,11 @@ public enum StageStyle { * bar area of the stage. *

* An extended window has the default window buttons (minimize, maximize, close), but no system-provided - * draggable header bar. Applications need to provide their own header bar by placing the {@link HeaderBar} - * control in the scene graph. Usually, this is combined with a {@link BorderPane} root container: + * draggable header bar. Applications need to provide their own header bar by placing a single {@link HeaderBar} + * control in the scene graph. The {@code HeaderBar} control should be positioned at the top of the window + * and its width should extend the entire width of the window, as otherwise the layout of the default window + * buttons and the header bar content might not be aligned. Usually, {@code HeaderBar} is combined with a + * {@link BorderPane} root container: *

{@code
      * public class MyApp extends Application {
      *     @Override

From 9de46940a7c60409c92fb43639b7e59933ddc2a3 Mon Sep 17 00:00:00 2001
From: mstr2 <43553916+mstr2@users.noreply.github.com>
Date: Mon, 28 Oct 2024 20:54:28 +0100
Subject: [PATCH 14/73] macOS: hide window title

---
 modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m | 1 +
 1 file changed, 1 insertion(+)

diff --git a/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m b/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m
index dda2b3241bd..ccfcca4869d 100644
--- a/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m
+++ b/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m
@@ -459,6 +459,7 @@ static jlong _createWindowCommonDo(JNIEnv *env, jobject jWindow, jlong jOwnerPtr
         window = [[GlassWindow alloc] _initWithContentRect:NSMakeRect(x, y, w, h) styleMask:styleMask screen:screen jwindow:jWindow];
 
         if ((jStyleMask & com_sun_glass_ui_Window_EXTENDED) != 0) {
+            [window->nsWindow setTitleVisibility:NSWindowTitleHidden];
             [window->nsWindow setTitlebarAppearsTransparent:YES];
             [window->nsWindow setToolbar:[NSToolbar new]];
             [window->nsWindow setToolbarStyle:NSWindowToolbarStyleUnifiedCompact];

From cd5d4434c70f9539f4631059d632290e501b9c9d Mon Sep 17 00:00:00 2001
From: mstr2 <43553916+mstr2@users.noreply.github.com>
Date: Mon, 28 Oct 2024 21:01:03 +0100
Subject: [PATCH 15/73] improve title text documentation

---
 .../src/main/java/javafx/stage/StageStyle.java               | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java b/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java
index 2e216ee2615..d6084661839 100644
--- a/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java
+++ b/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java
@@ -105,6 +105,11 @@ public enum StageStyle {
      * that matches the brightness of the user interface, even if the scene fill is not visible because it
      * is obscured by other controls.
      * 

+ * An extended stage has no title text. Applications that require title text need to provide their own + * implementation by placing a {@code Label} or a similar control in the custom header bar. + * Note that the value of {@link Stage#titleProperty()} may still be used by the platform, one example + * may be the title of miniaturized preview windows. + *

* This is a conditional feature, to check if it is supported see {@link Platform#isSupported(ConditionalFeature)}. * If the feature is not supported by the platform, this style downgrades to {@link StageStyle#DECORATED}. * From bc48ae0b9c8106220074c70ff7739abd9ad2c794 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Mon, 28 Oct 2024 22:49:06 +0100 Subject: [PATCH 16/73] fix peer access outside of synchronizer --- .../com/sun/javafx/tk/quantum/ViewSceneOverlay.java | 13 ++++++++++--- .../src/main/java/javafx/scene/Scene.java | 3 ++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/ViewSceneOverlay.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/ViewSceneOverlay.java index fe9d18540b2..d35bf440951 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/ViewSceneOverlay.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/ViewSceneOverlay.java @@ -76,13 +76,14 @@ public void setRoot(Parent root) { return; } + if (this.root != null) { + NodeHelper.setScenes(this.root, null, null); + } + this.root = root; if (root != null) { NodeHelper.setScenes(root, fxScene, null); - painter.setOverlayRoot(NodeHelper.getPeer(root)); - } else { - painter.setOverlayRoot(null); } } @@ -93,6 +94,12 @@ public void synchronize() { } private void syncPeer(Node node) { + if (root != null) { + painter.setOverlayRoot(NodeHelper.getPeer(root)); + } else { + painter.setOverlayRoot(null); + } + NodeHelper.syncPeer(node); if (node instanceof Parent parent) { diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java b/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java index c69e674f392..6618bfb9882 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java @@ -2485,6 +2485,8 @@ private void synchronizeSceneNodes() { Scene.inSynchronizer = true; + peer.synchronizeOverlay(); + // if dirtyNodes is null then that means this Scene has not yet been // synchronized, and so we will simply synchronize every node in the // scene and then create the dirty nodes array list @@ -2643,7 +2645,6 @@ public void pulse() { if (PULSE_LOGGING_ENABLED) { PulseLogger.newPhase("Copy state to render graph"); } - peer.synchronizeOverlay(); syncLights(); synchronizeSceneProperties(); // Run the synchronizer From 804d0be703abb208bca4c54a8c46418958989689 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Mon, 28 Oct 2024 22:56:02 +0100 Subject: [PATCH 17/73] NPE --- modules/javafx.graphics/src/main/java/javafx/scene/Scene.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java b/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java index 6618bfb9882..df1752bff08 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java @@ -2485,7 +2485,9 @@ private void synchronizeSceneNodes() { Scene.inSynchronizer = true; - peer.synchronizeOverlay(); + if (peer != null) { + peer.synchronizeOverlay(); + } // if dirtyNodes is null then that means this Scene has not yet been // synchronized, and so we will simply synchronize every node in the From f5e3121fa814b027f23981ecfb8cd3554d08bceb Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Tue, 29 Oct 2024 00:03:19 +0100 Subject: [PATCH 18/73] fix header bar height flicker --- .../src/main/java/com/sun/glass/ui/WindowControlsOverlay.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java index ab476f16424..1ceb0f35860 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java @@ -27,7 +27,6 @@ import com.sun.glass.events.MouseEvent; import com.sun.javafx.util.Utils; -import javafx.application.Platform; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.SimpleObjectProperty; @@ -430,8 +429,7 @@ protected void layoutChildren() { left ? HorizontalDirection.LEFT : HorizontalDirection.RIGHT, new Dimension2D(totalWidth, totalHeight)); - // Don't change the metrics during layout, since we don't know who might be listening. - Platform.runLater(() -> metrics.set(newMetrics)); + metrics.set(newMetrics); } layoutInArea(button1, button1X, 0, button1Width, button1Height, From 1c4ecc11b72c4a477a24dbeb6059f7a9af7c73ea Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Tue, 29 Oct 2024 06:13:54 +0100 Subject: [PATCH 19/73] macOS: dynamically adapt toolbar style to headerbar height --- .../main/java/com/sun/glass/ui/Window.java | 40 ++++++++++++++++- .../sun/glass/ui/WindowControlsOverlay.java | 5 ++- .../sun/glass/ui/WindowOverlayMetrics.java | 3 +- .../java/com/sun/glass/ui/mac/MacWindow.java | 45 ++++++++++++++++--- .../java/javafx/scene/layout/HeaderBar.java | 11 ++--- .../javafx/scene/layout/HeaderBarBase.java | 44 +++++++++++++++--- .../src/main/native-glass/mac/GlassWindow.m | 23 ++++++++++ 7 files changed, 147 insertions(+), 24 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java index 57e44b8ac89..605cdc68dd1 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java @@ -29,13 +29,14 @@ import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableValue; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; import javafx.scene.Parent; +import javafx.scene.layout.Region; +import javafx.util.Subscription; import java.lang.annotation.Native; import java.util.Collections; import java.util.LinkedList; import java.util.List; +import java.util.Objects; public abstract class Window { @@ -235,6 +236,7 @@ public static final class Level { private int maximumWidth = Integer.MAX_VALUE, maximumHeight = Integer.MAX_VALUE; private EventHandler eventHandler; + private Region headerBar; protected final ObjectProperty windowOverlayMetrics = new SimpleObjectProperty<>(this, "windowOverlayMetrics"); @@ -455,6 +457,40 @@ public NonClientHandler getNonClientHandler() { return null; } + /** + * Registers a user-provided header bar with this window. + *

+ * The registration will be accepted, but ignored if the window is not an extended + * window, or if another header bar is already registered. + * + * @param headerBar the header bar + * @return a {@code Subscription} to unregister + */ + public final Subscription registerHeaderBar(Region headerBar) { + Objects.requireNonNull(headerBar); + + if (!isExtendedWindow() || this.headerBar != null) { + return Subscription.EMPTY; + } + + this.headerBar = headerBar; + + Subscription subscription = headerBar.heightProperty().subscribe( + value -> onHeaderBarHeightChanged(value.doubleValue())); + + return () -> { + this.headerBar = null; + subscription.unsubscribe(); + }; + } + + /** + * Called when the height of a registered user-provided header bar has changed. + * + * @param height the height of the header bar + */ + protected void onHeaderBarHeightChanged(double height) {} + public boolean isDecorated() { Application.checkEventThread(); return this.isDecorated; diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java index 1ceb0f35860..3289479e284 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java @@ -171,7 +171,7 @@ public StyleableProperty getStyleableProperty(WindowControlsOverlay ove * The metrics (placement and size) of the window buttons. */ private final ObjectProperty metrics = new SimpleObjectProperty<>( - this, "metrics", new WindowOverlayMetrics(HorizontalDirection.RIGHT, new Dimension2D(0, 0))); + this, "metrics", new WindowOverlayMetrics(HorizontalDirection.RIGHT, new Dimension2D(0, 0), 0)); /** * Specifies the placement of the window buttons on the left or the right side of the window. @@ -424,10 +424,11 @@ protected void layoutChildren() { double totalHeight = Math.max(button1Height, Math.max(button2Height, button3Height)); Dimension2D currentSize = metrics.get().size(); + // Update the overlay metrics if they have changed. if (currentSize.getWidth() != totalWidth || currentSize.getHeight() != totalHeight) { var newMetrics = new WindowOverlayMetrics( left ? HorizontalDirection.LEFT : HorizontalDirection.RIGHT, - new Dimension2D(totalWidth, totalHeight)); + new Dimension2D(totalWidth, totalHeight), totalHeight); metrics.set(newMetrics); } diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowOverlayMetrics.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowOverlayMetrics.java index 15614172e27..95b90928476 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowOverlayMetrics.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowOverlayMetrics.java @@ -35,8 +35,9 @@ * * @param placement the placement of the window buttons * @param size the size of the window buttons + * @param minHeight the minimum height of the window buttons */ -public record WindowOverlayMetrics(HorizontalDirection placement, Dimension2D size) { +public record WindowOverlayMetrics(HorizontalDirection placement, Dimension2D size, double minHeight) { public WindowOverlayMetrics { Objects.requireNonNull(placement, "placement cannot be null"); diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java index 9e0962ebc1e..0cd8bc94d82 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java @@ -33,7 +33,6 @@ import com.sun.glass.ui.View; import com.sun.glass.ui.Window; import com.sun.glass.ui.WindowOverlayMetrics; -import javafx.beans.value.ObservableValue; import javafx.geometry.Dimension2D; import javafx.geometry.HorizontalDirection; import java.nio.ByteBuffer; @@ -50,6 +49,11 @@ final class MacWindow extends Window { protected MacWindow(Window owner, Screen screen, int styleMask) { super(owner, screen, styleMask); + + if (isExtendedWindow()) { + // The default window metrics correspond to a small toolbar style. + updateWindowOverlayMetrics(NSWindowToolbarStyle.SMALL); + } } @Override native protected long _createWindow(long ownerPtr, long screenPtr, int mask); @@ -182,15 +186,42 @@ public NonClientHandler getNonClientHandler() { private native boolean _isRightToLeftLayoutDirection(); + private native void _setToolbarStyle(long ptr, int style); + @Override - public ObservableValue getWindowOverlayMetrics() { - HorizontalDirection direction = _isRightToLeftLayoutDirection() - ? HorizontalDirection.RIGHT - : HorizontalDirection.LEFT; + protected void onHeaderBarHeightChanged(double height) { + var toolbarStyle = NSWindowToolbarStyle.ofHeight(height); + _setToolbarStyle(getRawHandle(), toolbarStyle.style); + updateWindowOverlayMetrics(toolbarStyle); + } + + private void updateWindowOverlayMetrics(NSWindowToolbarStyle toolbarStyle) { + windowOverlayMetrics.set(new WindowOverlayMetrics( + _isRightToLeftLayoutDirection() + ? HorizontalDirection.RIGHT + : HorizontalDirection.LEFT, + toolbarStyle.size, + NSWindowToolbarStyle.SMALL.size.getHeight())); + } + + private enum NSWindowToolbarStyle { + SMALL(68, 28, 1), // NSWindowToolbarStyleExpanded + MEDIUM(78, 38, 4), // NSWindowToolbarStyleUnifiedCompact + LARGE(90, 52, 3); // NSWindowToolbarStyleUnified - windowOverlayMetrics.set(new WindowOverlayMetrics(direction, new Dimension2D(78, 38))); + NSWindowToolbarStyle(double width, double height, int style) { + this.size = new Dimension2D(width, height); + this.style = style; + } - return windowOverlayMetrics; + final Dimension2D size; + final int style; + + static NSWindowToolbarStyle ofHeight(double height) { + if (height >= LARGE.size.getHeight()) return LARGE; + if (height >= MEDIUM.size.getHeight()) return MEDIUM; + return SMALL; + } } } diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java index 277f1f52af4..8c6220775fe 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java @@ -168,9 +168,7 @@ public HeaderBar() { // Inflate the minHeight property. This is important so that we can track whether a stylesheet or // user code changes the property value before we set it to the height of the native title bar. minHeightProperty(); - - leftSystemInsetProperty().subscribe(this::updateDefaultMinHeight); - rightSystemInsetProperty().subscribe(this::updateDefaultMinHeight); + minSystemHeightProperty().subscribe(this::updateMinHeight); } /** @@ -492,13 +490,12 @@ private Insets getNodeMargin(Node child) { return margin != null ? margin : Insets.EMPTY; } - private void updateDefaultMinHeight() { + private void updateMinHeight() { var minHeight = (StyleableDoubleProperty)minHeightProperty(); - // Only change the default minHeight if it was not set by a stylesheet or application code. + // Only change minHeight if it was not set by a stylesheet or application code. if (minHeight.getStyleOrigin() == null) { - double height = Math.max(getLeftSystemInset().getHeight(), getRightSystemInset().getHeight()); - ((StyleableDoubleProperty)minHeightProperty()).applyStyle(null, height); + ((StyleableDoubleProperty)minHeightProperty()).applyStyle(null, getMinSystemHeight()); } } diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java index 8a0049d00fb..81a53ddb487 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java @@ -28,6 +28,8 @@ import com.sun.glass.ui.WindowOverlayMetrics; import com.sun.javafx.stage.StageHelper; import com.sun.javafx.tk.quantum.WindowStage; +import javafx.beans.property.ReadOnlyDoubleProperty; +import javafx.beans.property.ReadOnlyDoubleWrapper; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.geometry.Dimension2D; import javafx.geometry.HorizontalDirection; @@ -110,10 +112,14 @@ private void onShowingChanged(boolean showing) { } } else if (getScene().getWindow() instanceof Stage stage && StageHelper.getPeer(stage) instanceof WindowStage windowStage) { - subscription = windowStage - .getPlatformWindow() - .getWindowOverlayMetrics() - .subscribe(this::onMetricsChanged); + subscription = Subscription.combine( + windowStage + .getPlatformWindow() + .getWindowOverlayMetrics() + .subscribe(this::onMetricsChanged), + windowStage + .getPlatformWindow() + .registerHeaderBar(this)); } } @@ -131,7 +137,11 @@ private void updateInsets() { if (currentFullScreen || currentMetrics == null) { leftSystemInset.set(new Dimension2D(0, 0)); rightSystemInset.set(new Dimension2D(0, 0)); - } else if (currentMetrics.placement() == HorizontalDirection.LEFT) { + minSystemHeight.set(0); + return; + } + + if (currentMetrics.placement() == HorizontalDirection.LEFT) { leftSystemInset.set(currentMetrics.size()); rightSystemInset.set(new Dimension2D(0, 0)); } else if (currentMetrics.placement() == HorizontalDirection.RIGHT) { @@ -141,6 +151,8 @@ private void updateInsets() { leftSystemInset.set(new Dimension2D(0, 0)); rightSystemInset.set(new Dimension2D(0, 0)); } + + minSystemHeight.set(currentMetrics.minHeight()); } /** @@ -190,4 +202,26 @@ public final ReadOnlyObjectWrapper rightSystemInsetProperty() { public final Dimension2D getRightSystemInset() { return rightSystemInset.get(); } + + /** + * The absolute minimum height of {@link #leftSystemInsetProperty() leftSystemInset} and + * {@link #rightSystemInsetProperty() rightSystemInset}. This is a platform-dependent value + * that a {@code HeaderBarBase} implementation can use to define a reasonable minimum height + * for the header bar area. + */ + private final ReadOnlyDoubleWrapper minSystemHeight = + new ReadOnlyDoubleWrapper(this, "minSystemHeight") { + @Override + protected void invalidated() { + requestLayout(); + } + }; + + public final ReadOnlyDoubleProperty minSystemHeightProperty() { + return minSystemHeight.getReadOnlyProperty(); + } + + public final double getMinSystemHeight() { + return minSystemHeight.get(); + } } diff --git a/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m b/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m index ccfcca4869d..d41b9c2cca0 100644 --- a/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m +++ b/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m @@ -1503,6 +1503,29 @@ static jlong _createWindowCommonDo(JNIEnv *env, jobject jWindow, jlong jOwnerPtr GLASS_CHECK_EXCEPTION(env); } +/* + * Class: com_sun_glass_ui_mac_MacWindow + * Method: _setToolbarStyle + * Signature: (JI)V + */ +JNIEXPORT void JNICALL Java_com_sun_glass_ui_mac_MacWindow__1setToolbarStyle +(JNIEnv *env, jobject jWindow, jlong jPtr, jint style) +{ + LOG("Java_com_sun_glass_ui_mac_MacWindow__1setToolbarStyle"); + if (!jPtr) return; + + GLASS_ASSERT_MAIN_JAVA_THREAD(env); + GLASS_POOL_ENTER; + { + GlassWindow *window = getGlassWindow(env, jPtr); + if (window && window->nsWindow) { + [window->nsWindow setToolbarStyle:style]; + } + } + GLASS_POOL_EXIT; + GLASS_CHECK_EXCEPTION(env); +} + /* * Class: com_sun_glass_ui_mac_MacWindow * Method: _isRightToLeftLayoutDirection From e7febc54e03d54ae6cf4f99d6226320e0f135373 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Tue, 5 Nov 2024 01:53:49 +0100 Subject: [PATCH 20/73] fix mirroring/unmirroring of X coord in win-glass --- .../src/main/native-glass/win/GlassWindow.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp b/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp index 1f9707aaeaf..b2a4cd763f3 100644 --- a/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp +++ b/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp @@ -900,6 +900,14 @@ BOOL GlassWindow::HandleNCHitTestEvent(SHORT x, SHORT y, LRESULT& result) return FALSE; } + // Unmirror the X coordinate we send to JavaFX if this is a RTL window. + LONG style = ::GetWindowLong(GetHWND(), GWL_EXSTYLE); + if (style & WS_EX_LAYOUTRTL) { + RECT rect = {0}; + ::GetClientRect(GetHWND(), &rect); + pt.x = max(0, rect.right - rect.left) - pt.x; + } + JNIEnv* env = GetEnv(); jint res = env->CallIntMethod(m_grefThis, javaIDs.WinWindow.nonClientHitTest, pt.x, pt.y); CheckAndClearException(env); @@ -1239,6 +1247,14 @@ void GlassWindow::ShowSystemMenu(int x, int y) return; } + // Mirror the X coordinate we get from JavaFX if this is a RTL window. + LONG style = ::GetWindowLong(GetHWND(), GWL_EXSTYLE); + if (style & WS_EX_LAYOUTRTL) { + RECT rect = {0}; + ::GetClientRect(GetHWND(), &rect); + x = max(0, rect.right - rect.left) - x; + } + HMENU systemMenu = GetSystemMenu(GetHWND(), FALSE); bool maximized = placement.showCmd == SW_SHOWMAXIMIZED; From d1c388bffa03e11709c5a70d1486e8f8aa1ce9d5 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Tue, 5 Nov 2024 02:28:04 +0100 Subject: [PATCH 21/73] stylistic changes --- .../main/java/com/sun/glass/ui/WindowControlsOverlay.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java index 3289479e284..5cc42756b71 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java @@ -336,7 +336,7 @@ private void handleMouseDown(Node node) { } private void handleMouseUp(Node node, ButtonType buttonType) { - boolean releasedOnButton = buttonAtMouseDown == node; + boolean releasedOnButton = (buttonAtMouseDown == node); buttonAtMouseDown = null; Scene scene = getScene(); @@ -357,9 +357,9 @@ private void handleMouseUp(Node node, ButtonType buttonType) { } private void onFocusedChanged(boolean focused) { - for (var node : new Node[] {minimizeButton, maximizeButton, closeButton}) { - node.pseudoClassStateChanged(ACTIVE_PSEUDOCLASS, focused); - } + minimizeButton.pseudoClassStateChanged(ACTIVE_PSEUDOCLASS, focused); + maximizeButton.pseudoClassStateChanged(ACTIVE_PSEUDOCLASS, focused); + closeButton.pseudoClassStateChanged(ACTIVE_PSEUDOCLASS, focused); } private void onResizableChanged(boolean resizable) { From 8c9fbbdfc00f30ec7056f68b41768d1dadd68237 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Tue, 5 Nov 2024 22:00:01 +0100 Subject: [PATCH 22/73] use CsvSource in HeaderBarTest --- .../javafx/scene/layout/HeaderBarTest.java | 174 ++++++------------ 1 file changed, 53 insertions(+), 121 deletions(-) diff --git a/modules/javafx.graphics/src/test/java/test/javafx/scene/layout/HeaderBarTest.java b/modules/javafx.graphics/src/test/java/test/javafx/scene/layout/HeaderBarTest.java index 839f72c428c..43fe317ec97 100644 --- a/modules/javafx.graphics/src/test/java/test/javafx/scene/layout/HeaderBarTest.java +++ b/modules/javafx.graphics/src/test/java/test/javafx/scene/layout/HeaderBarTest.java @@ -35,7 +35,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; +import org.junit.jupiter.params.provider.CsvSource; import test.util.ReflectionUtils; import static org.junit.jupiter.api.Assertions.*; @@ -57,7 +57,7 @@ void emptyHeaderBar() { } @ParameterizedTest - @ValueSource(strings = { + @CsvSource({ "TOP_LEFT, 10, 10, 100, 80", "TOP_CENTER, 10, 10, 100, 80", "TOP_RIGHT, 10, 10, 100, 80", @@ -68,25 +68,19 @@ void emptyHeaderBar() { "BOTTOM_CENTER, 10, 10, 100, 80", "BOTTOM_RIGHT, 10, 10, 100, 80" }) - void alignmentOfLeadingChildOnly_resizable(String arg) { - String[] args = arg.split(","); + void alignmentOfLeadingChildOnly_resizable(Pos pos, double x, double y, double width, double height) { var content = new MockResizable(100, 50); - HeaderBar.setAlignment(content, Pos.valueOf(args[0])); + HeaderBar.setAlignment(content, pos); HeaderBar.setMargin(content, new Insets(10)); headerBar.setLeading(content); headerBar.resize(1000, 100); headerBar.layout(); - assertBounds( - Double.parseDouble(args[1]), - Double.parseDouble(args[2]), - Double.parseDouble(args[3]), - Double.parseDouble(args[4]), - content); + assertBounds(x, y, width, height, content); } @ParameterizedTest - @ValueSource(strings = { + @CsvSource({ "TOP_LEFT, 10, 10, 100, 50", "TOP_CENTER, 10, 10, 100, 50", "TOP_RIGHT, 10, 10, 100, 50", @@ -97,25 +91,19 @@ void alignmentOfLeadingChildOnly_resizable(String arg) { "BOTTOM_CENTER, 10, 40, 100, 50", "BOTTOM_RIGHT, 10, 40, 100, 50" }) - void alignmentOfLeadingChildOnly_notResizable(String arg) { - String[] args = arg.split(","); + void alignmentOfLeadingChildOnly_notResizable(Pos pos, double x, double y, double width, double height) { var content = new Rectangle(100, 50); - HeaderBar.setAlignment(content, Pos.valueOf(args[0])); + HeaderBar.setAlignment(content, pos); HeaderBar.setMargin(content, new Insets(10)); headerBar.setLeading(content); headerBar.resize(1000, 100); headerBar.layout(); - assertBounds( - Double.parseDouble(args[1]), - Double.parseDouble(args[2]), - Double.parseDouble(args[3]), - Double.parseDouble(args[4]), - content); + assertBounds(x, y, width, height, content); } @ParameterizedTest - @ValueSource(strings = { + @CsvSource({ "TOP_LEFT, 890, 10, 100, 80", "TOP_CENTER, 890, 10, 100, 80", "TOP_RIGHT, 890, 10, 100, 80", @@ -126,25 +114,19 @@ void alignmentOfLeadingChildOnly_notResizable(String arg) { "BOTTOM_CENTER, 890, 10, 100, 80", "BOTTOM_RIGHT, 890, 10, 100, 80" }) - void alignmentOfTrailingChildOnly_resizable(String arg) { - String[] args = arg.split(","); + void alignmentOfTrailingChildOnly_resizable(Pos pos, double x, double y, double width, double height) { var content = new MockResizable(100, 50); - HeaderBar.setAlignment(content, Pos.valueOf(args[0])); + HeaderBar.setAlignment(content, pos); HeaderBar.setMargin(content, new Insets(10)); headerBar.setTrailing(content); headerBar.resize(1000, 100); headerBar.layout(); - assertBounds( - Double.parseDouble(args[1]), - Double.parseDouble(args[2]), - Double.parseDouble(args[3]), - Double.parseDouble(args[4]), - content); + assertBounds(x, y, width, height, content); } @ParameterizedTest - @ValueSource(strings = { + @CsvSource({ "TOP_LEFT, 890, 10, 100, 50", "TOP_CENTER, 890, 10, 100, 50", "TOP_RIGHT, 890, 10, 100, 50", @@ -155,25 +137,19 @@ void alignmentOfTrailingChildOnly_resizable(String arg) { "BOTTOM_CENTER, 890, 40, 100, 50", "BOTTOM_RIGHT, 890, 40, 100, 50" }) - void alignmentOfTrailingChildOnly_notResizable(String arg) { - String[] args = arg.split(","); + void alignmentOfTrailingChildOnly_notResizable(Pos pos, double x, double y, double width, double height) { var content = new Rectangle(100, 50); - HeaderBar.setAlignment(content, Pos.valueOf(args[0])); + HeaderBar.setAlignment(content, pos); HeaderBar.setMargin(content, new Insets(10)); headerBar.setTrailing(content); headerBar.resize(1000, 100); headerBar.layout(); - assertBounds( - Double.parseDouble(args[1]), - Double.parseDouble(args[2]), - Double.parseDouble(args[3]), - Double.parseDouble(args[4]), - content); + assertBounds(x, y, width, height, content); } @ParameterizedTest - @ValueSource(strings = { + @CsvSource({ "TOP_LEFT, 10, 10, 100, 80", "TOP_CENTER, 450, 10, 100, 80", "TOP_RIGHT, 890, 10, 100, 80", @@ -184,25 +160,19 @@ void alignmentOfTrailingChildOnly_notResizable(String arg) { "BOTTOM_CENTER, 450, 10, 100, 80", "BOTTOM_RIGHT, 890, 10, 100, 80" }) - void alignmentOfCenterChildOnly_resizable(String arg) { - String[] args = arg.split(","); + void alignmentOfCenterChildOnly_resizable(Pos pos, double x, double y, double width, double height) { var content = new MockResizable(100, 50); - HeaderBar.setAlignment(content, Pos.valueOf(args[0])); + HeaderBar.setAlignment(content, pos); HeaderBar.setMargin(content, new Insets(10)); headerBar.setCenter(content); headerBar.resize(1000, 100); headerBar.layout(); - assertBounds( - Double.parseDouble(args[1]), - Double.parseDouble(args[2]), - Double.parseDouble(args[3]), - Double.parseDouble(args[4]), - content); + assertBounds(x, y, width, height, content); } @ParameterizedTest - @ValueSource(strings = { + @CsvSource({ "TOP_LEFT, 10, 10, 100, 50", "TOP_CENTER, 450, 10, 100, 50", "TOP_RIGHT, 890, 10, 100, 50", @@ -213,25 +183,19 @@ void alignmentOfCenterChildOnly_resizable(String arg) { "BOTTOM_CENTER, 450, 40, 100, 50", "BOTTOM_RIGHT, 890, 40, 100, 50" }) - void alignmentOfCenterChildOnly_notResizable(String arg) { - String[] args = arg.split(","); + void alignmentOfCenterChildOnly_notResizable(Pos pos, double x, double y, double width, double height) { var content = new Rectangle(100, 50); - HeaderBar.setAlignment(content, Pos.valueOf(args[0])); + HeaderBar.setAlignment(content, pos); HeaderBar.setMargin(content, new Insets(10)); headerBar.setCenter(content); headerBar.resize(1000, 100); headerBar.layout(); - assertBounds( - Double.parseDouble(args[1]), - Double.parseDouble(args[2]), - Double.parseDouble(args[3]), - Double.parseDouble(args[4]), - content); + assertBounds(x, y, width, height, content); } @ParameterizedTest - @ValueSource(strings = { + @CsvSource({ "TOP_LEFT, 60, 10, 100, 80", "TOP_CENTER, 450, 10, 100, 80", "TOP_RIGHT, 740, 10, 100, 80", @@ -242,12 +206,12 @@ void alignmentOfCenterChildOnly_notResizable(String arg) { "BOTTOM_CENTER, 450, 10, 100, 80", "BOTTOM_RIGHT, 740, 10, 100, 80" }) - void alignmentOfCenterChild_resizable_withNonEmptyLeadingAndTrailingChild(String arg) { - String[] args = arg.split(","); + void alignmentOfCenterChild_resizable_withNonEmptyLeadingAndTrailingChild( + Pos pos, double x, double y, double width, double height) { var leading = new MockResizable(50, 50); var center = new MockResizable(100, 50); var trailing = new MockResizable(150, 50); - HeaderBar.setAlignment(center, Pos.valueOf(args[0])); + HeaderBar.setAlignment(center, pos); HeaderBar.setMargin(center, new Insets(10)); headerBar.setLeading(leading); headerBar.setCenter(center); @@ -255,16 +219,11 @@ void alignmentOfCenterChild_resizable_withNonEmptyLeadingAndTrailingChild(String headerBar.resize(1000, 100); headerBar.layout(); - assertBounds( - Double.parseDouble(args[1]), - Double.parseDouble(args[2]), - Double.parseDouble(args[3]), - Double.parseDouble(args[4]), - center); + assertBounds(x, y, width, height, center); } @ParameterizedTest - @ValueSource(strings = { + @CsvSource({ "TOP_LEFT, 60, 10, 100, 50", "TOP_CENTER, 450, 10, 100, 50", "TOP_RIGHT, 740, 10, 100, 50", @@ -275,12 +234,12 @@ void alignmentOfCenterChild_resizable_withNonEmptyLeadingAndTrailingChild(String "BOTTOM_CENTER, 450, 40, 100, 50", "BOTTOM_RIGHT, 740, 40, 100, 50" }) - void alignmentOfCenterChild_notResizable_withNonEmptyLeadingAndTrailingChild(String arg) { - String[] args = arg.split(","); + void alignmentOfCenterChild_notResizable_withNonEmptyLeadingAndTrailingChild( + Pos pos, double x, double y, double width, double height) { var leading = new Rectangle(50, 50); var center = new Rectangle(100, 50); var trailing = new Rectangle(150, 50); - HeaderBar.setAlignment(center, Pos.valueOf(args[0])); + HeaderBar.setAlignment(center, pos); HeaderBar.setMargin(center, new Insets(10)); headerBar.setLeading(leading); headerBar.setCenter(center); @@ -288,17 +247,12 @@ void alignmentOfCenterChild_notResizable_withNonEmptyLeadingAndTrailingChild(Str headerBar.resize(1000, 100); headerBar.layout(); - assertBounds( - Double.parseDouble(args[1]), - Double.parseDouble(args[2]), - Double.parseDouble(args[3]), - Double.parseDouble(args[4]), - center); + assertBounds(x, y, width, height, center); } @ParameterizedTest @SuppressWarnings("unchecked") - @ValueSource(strings = { + @CsvSource({ "TOP_LEFT, 160, 10, 100, 80", "TOP_CENTER, 450, 10, 100, 80", "TOP_RIGHT, 740, 10, 100, 80", @@ -309,14 +263,13 @@ void alignmentOfCenterChild_notResizable_withNonEmptyLeadingAndTrailingChild(Str "BOTTOM_CENTER, 450, 10, 100, 80", "BOTTOM_RIGHT, 740, 10, 100, 80" }) - void alignmentOfCenterChild_withLeftSystemInset(String arg) { - String[] args = arg.split(","); + void alignmentOfCenterChild_withLeftSystemInset(Pos pos, double x, double y, double width, double height) { var leftSystemInset = (ObjectProperty)ReflectionUtils.getFieldValue(headerBar, "leftSystemInset"); leftSystemInset.set(new Dimension2D(100, 100)); var leading = new MockResizable(50, 50); var center = new MockResizable(100, 50); var trailing = new MockResizable(150, 50); - HeaderBar.setAlignment(center, Pos.valueOf(args[0])); + HeaderBar.setAlignment(center, pos); HeaderBar.setMargin(center, new Insets(10)); headerBar.setLeading(leading); headerBar.setCenter(center); @@ -324,17 +277,12 @@ void alignmentOfCenterChild_withLeftSystemInset(String arg) { headerBar.resize(1000, 100); headerBar.layout(); - assertBounds( - Double.parseDouble(args[1]), - Double.parseDouble(args[2]), - Double.parseDouble(args[3]), - Double.parseDouble(args[4]), - center); + assertBounds(x, y, width, height, center); } @ParameterizedTest @SuppressWarnings("unchecked") - @ValueSource(strings = { + @CsvSource({ "TOP_LEFT, 60, 10, 100, 80", "TOP_CENTER, 450, 10, 100, 80", "TOP_RIGHT, 640, 10, 100, 80", @@ -345,14 +293,13 @@ void alignmentOfCenterChild_withLeftSystemInset(String arg) { "BOTTOM_CENTER, 450, 10, 100, 80", "BOTTOM_RIGHT, 640, 10, 100, 80" }) - void alignmentOfCenterChild_withRightSystemInset(String arg) { - String[] args = arg.split(","); + void alignmentOfCenterChild_withRightSystemInset(Pos pos, double x, double y, double width, double height) { var rightSystemInset = (ObjectProperty)ReflectionUtils.getFieldValue(headerBar, "rightSystemInset"); rightSystemInset.set(new Dimension2D(100, 100)); var leading = new MockResizable(50, 50); var center = new MockResizable(100, 50); var trailing = new MockResizable(150, 50); - HeaderBar.setAlignment(center, Pos.valueOf(args[0])); + HeaderBar.setAlignment(center, pos); HeaderBar.setMargin(center, new Insets(10)); headerBar.setLeading(leading); headerBar.setCenter(center); @@ -360,29 +307,24 @@ void alignmentOfCenterChild_withRightSystemInset(String arg) { headerBar.resize(1000, 100); headerBar.layout(); - assertBounds( - Double.parseDouble(args[1]), - Double.parseDouble(args[2]), - Double.parseDouble(args[3]), - Double.parseDouble(args[4]), - center); + assertBounds(x, y, width, height, center); } @ParameterizedTest @SuppressWarnings("unchecked") - @ValueSource(strings = { + @CsvSource({ "TOP_CENTER, 260, 10, 80, 80", "CENTER, 260, 10, 80, 80", "BOTTOM_CENTER, 260, 10, 80, 80" }) - void alignmentOfCenterChild_withLeftSystemInset_andOffsetCausedByInsufficientHorizontalSpace(String arg) { - String[] args = arg.split(","); + void alignmentOfCenterChild_withLeftSystemInset_andOffsetCausedByInsufficientHorizontalSpace( + Pos pos, double x, double y, double width, double height) { var leftSystemInset = (ObjectProperty)ReflectionUtils.getFieldValue(headerBar, "leftSystemInset"); leftSystemInset.set(new Dimension2D(200, 100)); var leading = new MockResizable(50, 50); var center = new MockResizable(100, 50); var trailing = new MockResizable(150, 50); - HeaderBar.setAlignment(center, Pos.valueOf(args[0])); + HeaderBar.setAlignment(center, pos); HeaderBar.setMargin(center, new Insets(10)); headerBar.setLeading(leading); headerBar.setCenter(center); @@ -390,29 +332,24 @@ void alignmentOfCenterChild_withLeftSystemInset_andOffsetCausedByInsufficientHor headerBar.resize(500, 100); headerBar.layout(); - assertBounds( - Double.parseDouble(args[1]), - Double.parseDouble(args[2]), - Double.parseDouble(args[3]), - Double.parseDouble(args[4]), - center); + assertBounds(x, y, width, height, center); } @ParameterizedTest @SuppressWarnings("unchecked") - @ValueSource(strings = { + @CsvSource({ "TOP_CENTER, 60, 10, 80, 80", "CENTER, 60, 10, 80, 80", "BOTTOM_CENTER, 60, 10, 80, 80" }) - void alignmentOfCenterChild_withRightSystemInset_andOffsetCausedByInsufficientHorizontalSpace(String arg) { - String[] args = arg.split(","); + void alignmentOfCenterChild_withRightSystemInset_andOffsetCausedByInsufficientHorizontalSpace( + Pos pos, double x, double y, double width, double height) { var rightSystemInset = (ObjectProperty)ReflectionUtils.getFieldValue(headerBar, "rightSystemInset"); rightSystemInset.set(new Dimension2D(200, 100)); var leading = new MockResizable(50, 50); var center = new MockResizable(100, 50); var trailing = new MockResizable(150, 50); - HeaderBar.setAlignment(center, Pos.valueOf(args[0])); + HeaderBar.setAlignment(center, pos); HeaderBar.setMargin(center, new Insets(10)); headerBar.setLeading(leading); headerBar.setCenter(center); @@ -420,12 +357,7 @@ void alignmentOfCenterChild_withRightSystemInset_andOffsetCausedByInsufficientHo headerBar.resize(500, 100); headerBar.layout(); - assertBounds( - Double.parseDouble(args[1]), - Double.parseDouble(args[2]), - Double.parseDouble(args[3]), - Double.parseDouble(args[4]), - center); + assertBounds(x, y, width, height, center); } private void assertBounds(double x, double y, double width, double height, Node node) { From 3660a29325dbc08831845f7026b94be6caa381e5 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Tue, 5 Nov 2024 22:02:42 +0100 Subject: [PATCH 23/73] EMPTY Dimension2D constant --- .../java/javafx/scene/layout/HeaderBarBase.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java index 81a53ddb487..37264927bb7 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java @@ -55,6 +55,7 @@ */ public abstract class HeaderBarBase extends Region { + private static final Dimension2D EMPTY = new Dimension2D(0, 0); private static final String DRAGGABLE = "headerbar-draggable"; /** @@ -135,21 +136,21 @@ private void onFullScreenChanged(boolean fullScreen) { private void updateInsets() { if (currentFullScreen || currentMetrics == null) { - leftSystemInset.set(new Dimension2D(0, 0)); - rightSystemInset.set(new Dimension2D(0, 0)); + leftSystemInset.set(EMPTY); + rightSystemInset.set(EMPTY); minSystemHeight.set(0); return; } if (currentMetrics.placement() == HorizontalDirection.LEFT) { leftSystemInset.set(currentMetrics.size()); - rightSystemInset.set(new Dimension2D(0, 0)); + rightSystemInset.set(EMPTY); } else if (currentMetrics.placement() == HorizontalDirection.RIGHT) { - leftSystemInset.set(new Dimension2D(0, 0)); + leftSystemInset.set(EMPTY); rightSystemInset.set(currentMetrics.size()); } else { - leftSystemInset.set(new Dimension2D(0, 0)); - rightSystemInset.set(new Dimension2D(0, 0)); + leftSystemInset.set(EMPTY); + rightSystemInset.set(EMPTY); } minSystemHeight.set(currentMetrics.minHeight()); @@ -188,7 +189,7 @@ public final Dimension2D getLeftSystemInset() { * independent of layout orientation. */ private final ReadOnlyObjectWrapper rightSystemInset = - new ReadOnlyObjectWrapper<>(this, "rightInset", new Dimension2D(0, 0)) { + new ReadOnlyObjectWrapper<>(this, "rightInset", EMPTY) { @Override protected void invalidated() { requestLayout(); From 8974c1413a299956f4a38f784a89f7a4491a4af8 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Tue, 5 Nov 2024 22:28:59 +0100 Subject: [PATCH 24/73] HeaderBar changes --- .../java/javafx/scene/layout/HeaderBar.java | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java index 8c6220775fe..abaf328d59d 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java @@ -54,9 +54,10 @@ * of leading or trailing children or the platform-specific placement of default window buttons. *

* All children will be resized to their preferred widths and extend the height of the {@code HeaderBar}. - * {@code HeaderBar} honors the minimum, preferred, and maximum sizes of its children. If the child's - * resizable range prevents it from be resized to fit within its position, it will be vertically centered - * relative to the available space; this alignment can be customized with a layout constraint. + * {@code HeaderBar} honors the minimum, preferred, and maximum sizes of its children. As a consequence, + * its computed minimum width is sufficient to accommodate all of its children. If a child's resizable + * range prevents it from be resized to fit within its position, it will be vertically centered relative + * to the available space; this alignment can be customized with a layout constraint. *

* The default {@link #minHeightProperty() minHeight} of the {@code HeaderBar} is set to match the height * of the platform-specific default window buttons. @@ -259,7 +260,9 @@ public final void setTrailing(Node value) { @Override protected double computeMinWidth(double height) { - Node leading = getLeading(), center = getCenter(), trailing = getTrailing(); + Node leading = getLeading(); + Node center = getCenter(); + Node trailing = getTrailing(); Insets insets = getInsets(); double leftPrefWidth; double rightPrefWidth; @@ -290,7 +293,9 @@ protected double computeMinWidth(double height) { @Override protected double computeMinHeight(double width) { - Node leading = getLeading(), center = getCenter(), trailing = getTrailing(); + Node leading = getLeading(); + Node center = getCenter(); + Node trailing = getTrailing(); Insets insets = getInsets(); double leadingMinHeight = getAreaHeight(leading, -1, true); double trailingMinHeight = getAreaHeight(trailing, -1, true); @@ -311,7 +316,9 @@ protected double computeMinHeight(double width) { @Override protected double computePrefHeight(double width) { - Node leading = getLeading(), center = getCenter(), trailing = getTrailing(); + Node leading = getLeading(); + Node center = getCenter(); + Node trailing = getTrailing(); Insets insets = getInsets(); double leadingPrefHeight = getAreaHeight(leading, -1, false); double trailingPrefHeight = getAreaHeight(trailing, -1, false); From ca9b325ad503cf6ab8da3ed9a939ae8b4fd67638 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Wed, 6 Nov 2024 19:33:39 +0100 Subject: [PATCH 25/73] refactor performWindowDrag --- .../src/main/native-glass/mac/GlassViewDelegate.h | 4 ++-- .../src/main/native-glass/mac/GlassViewDelegate.m | 12 +++++++----- .../src/main/native-glass/mac/GlassWindow.m | 3 +-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/modules/javafx.graphics/src/main/native-glass/mac/GlassViewDelegate.h b/modules/javafx.graphics/src/main/native-glass/mac/GlassViewDelegate.h index 191cc55cc93..cc3b7387246 100644 --- a/modules/javafx.graphics/src/main/native-glass/mac/GlassViewDelegate.h +++ b/modules/javafx.graphics/src/main/native-glass/mac/GlassViewDelegate.h @@ -102,6 +102,8 @@ typedef enum GestureMaskType { - (BOOL)suppressMouseEnterExitOnMouseDown; +- (void)performWindowDrag; + - (void)enterFullscreenWithAnimate:(BOOL)animate withKeepRatio:(BOOL)keepRatio withHideCursor:(BOOL)hideCursor; - (void)exitFullscreenWithAnimate:(BOOL)animate; - (void)sendJavaFullScreenEvent:(BOOL)entered withNativeWidget:(BOOL)isNative; @@ -115,6 +117,4 @@ typedef enum GestureMaskType { - (GlassAccessible*)getAccessible; -- (NSEvent*)lastEvent; - @end diff --git a/modules/javafx.graphics/src/main/native-glass/mac/GlassViewDelegate.m b/modules/javafx.graphics/src/main/native-glass/mac/GlassViewDelegate.m index 1b443d7bbd0..244b6b70989 100644 --- a/modules/javafx.graphics/src/main/native-glass/mac/GlassViewDelegate.m +++ b/modules/javafx.graphics/src/main/native-glass/mac/GlassViewDelegate.m @@ -1143,6 +1143,13 @@ - (BOOL)suppressMouseEnterExitOnMouseDown return YES; } +- (void)performWindowDrag +{ + if (lastEvent != nil) { + [[nsView window] performWindowDragWithEvent:lastEvent]; + } +} + static jstring convertNSStringToJString(id aString, int length) { GET_MAIN_JENV; @@ -1333,9 +1340,4 @@ - (GlassAccessible*)getAccessible return (GlassAccessible*)jlong_to_ptr(accessible); } -- (NSEvent*)lastEvent -{ - return lastEvent; -} - @end diff --git a/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m b/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m index d41b9c2cca0..19c62bb5b8f 100644 --- a/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m +++ b/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m @@ -1496,8 +1496,7 @@ static jlong _createWindowCommonDo(JNIEnv *env, jobject jWindow, jlong jOwnerPtr GLASS_POOL_ENTER; { GlassWindow *window = getGlassWindow(env, jPtr); - GlassViewDelegate* delegate = [window->view delegate]; - [window->nsWindow performWindowDragWithEvent:[delegate lastEvent]]; + [[window->view delegate] performWindowDrag]; } GLASS_POOL_EXIT; GLASS_CHECK_EXCEPTION(env); From 8e77a220efd03ed9fa155dde9b98c57aa10cc787 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Thu, 7 Nov 2024 04:29:20 +0100 Subject: [PATCH 26/73] HeaderBar javadoc change --- .../main/java/javafx/scene/layout/HeaderBar.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java index abaf328d59d..9ccdb5d15e6 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java @@ -47,15 +47,16 @@ *

* {@code HeaderBar} is a layout container that allows applications to place scene graph nodes * in three areas: {@link #leadingProperty() leading}, {@link #centerProperty() center}, and - * {@link #trailingProperty() trailing}. {@code HeaderBar} ensures that the leading and trailing areas - * account for the default window buttons (minimize, maximize, close). If a child is configured to be - * centered in the {@code center} area, it is laid out with respect to the stage, and not with respect - * to the {@code center} area. This ensures that the child will appear centered in the stage regardless - * of leading or trailing children or the platform-specific placement of default window buttons. + * {@link #trailingProperty() trailing}. All areas can be {@code null}. {@code HeaderBar} ensures that + * the leading and trailing areas account for the default window buttons (minimize, maximize, close). + * If a child is configured to be centered in the {@code center} area, it is laid out with respect to + * the stage, and not with respect to the {@code center} area. This ensures that the child will appear + * centered in the stage regardless of leading or trailing children or the platform-specific placement + * of default window buttons. *

* All children will be resized to their preferred widths and extend the height of the {@code HeaderBar}. * {@code HeaderBar} honors the minimum, preferred, and maximum sizes of its children. As a consequence, - * its computed minimum width is sufficient to accommodate all of its children. If a child's resizable + * its computed minimum size is sufficient to accommodate all of its children. If a child's resizable * range prevents it from be resized to fit within its position, it will be vertically centered relative * to the available space; this alignment can be customized with a layout constraint. *

From 4336735a457c2318b527ef77a153d9ab6c54fe9a Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Thu, 7 Nov 2024 04:52:54 +0100 Subject: [PATCH 27/73] WindowControlsOverlay snapping --- .../sun/glass/ui/WindowControlsOverlay.java | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java index 5cc42756b71..93ee8e07e89 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java @@ -45,6 +45,7 @@ import javafx.geometry.Dimension2D; import javafx.geometry.HPos; import javafx.geometry.HorizontalDirection; +import javafx.geometry.Insets; import javafx.geometry.NodeOrientation; import javafx.geometry.VPos; import javafx.scene.Node; @@ -411,17 +412,17 @@ protected void layoutChildren() { } double width = getWidth(); - double button1Width = boundedWidth(button1); - double button2Width = boundedWidth(button2); - double button3Width = boundedWidth(button3); - double button1Height = boundedHeight(button1); - double button2Height = boundedHeight(button2); - double button3Height = boundedHeight(button3); - double button1X = left ? 0 : width - button1Width - button2Width - button3Width; - double button2X = left ? button1Width : width - button3Width - button2Width; - double button3X = left ? button1Width + button2Width : width - button3Width; - double totalWidth = button1Width + button2Width + button3Width; - double totalHeight = Math.max(button1Height, Math.max(button2Height, button3Height)); + double button1Width = snapSizeX(boundedWidth(button1)); + double button2Width = snapSizeX(boundedWidth(button2)); + double button3Width = snapSizeX(boundedWidth(button3)); + double button1Height = snapSizeY(boundedHeight(button1)); + double button2Height = snapSizeY(boundedHeight(button2)); + double button3Height = snapSizeY(boundedHeight(button3)); + double button1X = snapPositionX(left ? 0 : width - button1Width - button2Width - button3Width); + double button2X = snapPositionX(left ? button1Width : width - button3Width - button2Width); + double button3X = snapPositionX(left ? button1Width + button2Width : width - button3Width); + double totalWidth = snapSizeX(button1Width + button2Width + button3Width); + double totalHeight = snapSizeY(Math.max(button1Height, Math.max(button2Height, button3Height))); Dimension2D currentSize = metrics.get().size(); // Update the overlay metrics if they have changed. @@ -433,14 +434,14 @@ protected void layoutChildren() { metrics.set(newMetrics); } - layoutInArea(button1, button1X, 0, button1Width, button1Height, - BASELINE_OFFSET_SAME_AS_HEIGHT, HPos.LEFT, VPos.TOP); + layoutInArea(button1, button1X, 0, button1Width, button1Height, BASELINE_OFFSET_SAME_AS_HEIGHT, + Insets.EMPTY, true, true, HPos.LEFT, VPos.TOP, false); - layoutInArea(button2, button2X, 0, button2Width, button2Height, - BASELINE_OFFSET_SAME_AS_HEIGHT, HPos.LEFT, VPos.TOP); + layoutInArea(button2, button2X, 0, button2Width, button2Height, BASELINE_OFFSET_SAME_AS_HEIGHT, + Insets.EMPTY, true, true, HPos.LEFT, VPos.TOP, false); - layoutInArea(button3, button3X, 0, button3Width, button3Height, - BASELINE_OFFSET_SAME_AS_HEIGHT, HPos.LEFT, VPos.TOP); + layoutInArea(button3, button3X, 0, button3Width, button3Height, BASELINE_OFFSET_SAME_AS_HEIGHT, + Insets.EMPTY, true, true, HPos.LEFT, VPos.TOP, false); } @Override From a9178b78a2d2ce0b49ee80e317095ad30e7b0cf1 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Thu, 7 Nov 2024 05:08:05 +0100 Subject: [PATCH 28/73] add system menu documentation --- .../src/main/java/javafx/scene/layout/HeaderBar.java | 7 ++++++- .../main/java/javafx/scene/layout/HeaderBarBase.java | 11 ++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java index 9ccdb5d15e6..d3c94f2d5f7 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java @@ -36,15 +36,20 @@ import javafx.geometry.Pos; import javafx.geometry.VPos; import javafx.scene.Node; +import javafx.scene.input.ContextMenuEvent; import javafx.stage.StageStyle; /** * A client-area header bar that is used as a replacement for the system-provided header bar in stages - * with the {@link StageStyle#EXTENDED} style. This class enables the click-and-drag and + * with the {@link StageStyle#EXTENDED} style. This class enables the click-and-drag to move and * double-click to maximize behaviors that are usually afforded by system-provided header bars. * The entire {@code HeaderBar} background is draggable by default, but its content is not. Applications * can specify draggable content nodes of the {@code HeaderBar} with the {@link #setDraggable} method. *

+ * Some platforms support a system menu that can be summoned by right-clicking the draggable area. + * This platform-provided menu will only be shown if the {@link ContextMenuEvent#CONTEXT_MENU_REQUESTED} + * event that is targeted at {@code HeaderBarBase} is not consumed by the application. + *

* {@code HeaderBar} is a layout container that allows applications to place scene graph nodes * in three areas: {@link #leadingProperty() leading}, {@link #centerProperty() center}, and * {@link #trailingProperty() trailing}. All areas can be {@code null}. {@code HeaderBar} ensures that diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java index 37264927bb7..896dae17d9d 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java @@ -35,18 +35,23 @@ import javafx.geometry.HorizontalDirection; import javafx.scene.Node; import javafx.scene.Scene; +import javafx.scene.input.ContextMenuEvent; import javafx.stage.Stage; import javafx.stage.StageStyle; import javafx.stage.Window; import javafx.util.Subscription; /** - * Base class for a client-area header bar that is used as a replacement for the system-provided header bar - * in stages with the {@link StageStyle#EXTENDED} style. This class is intended for application developers - * to use as a starting point for custom header bar implementations, and it enables the click-and-drag + * Base class for a client-area header bar that is used as a replacement for the system-provided header bar in + * stages with the {@link StageStyle#EXTENDED} style. This class is intended for application developers to use + * as a starting point for custom header bar implementations, and it enables the click-and-drag to move * and double-click to maximize behaviors that are usually afforded by system-provided header bars. * The entire {@code HeaderBarBase} background is draggable by default, but its content is not. Applications * can specify draggable content nodes of the {@code HeaderBarBase} with the {@link #setDraggable} method. + *

+ * Some platforms support a system menu that can be summoned by right-clicking the draggable area. + * This platform-provided menu will only be shown if the {@link ContextMenuEvent#CONTEXT_MENU_REQUESTED} + * event that is targeted at {@code HeaderBarBase} is not consumed by the application. * * @apiNote Most application developers should use the {@link HeaderBar} implementation instead of * creating a custom header bar. From 26b81b858f68d9b752a0f88ad3de9bc717396d36 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Thu, 14 Nov 2024 18:06:48 +0100 Subject: [PATCH 29/73] remove unneeded dll --- buildSrc/win.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/win.gradle b/buildSrc/win.gradle index db34da5ad61..83a97645a27 100644 --- a/buildSrc/win.gradle +++ b/buildSrc/win.gradle @@ -324,9 +324,9 @@ WIN.glass.rcFlags = [ WIN.glass.ccFlags = [ccFlags].flatten() WIN.glass.linker = linker WIN.glass.linkFlags = (IS_STATIC_BUILD ? [linkFlags] : [linkFlags, "delayimp.lib", "gdi32.lib", "urlmon.lib", "Comdlg32.lib", - "winmm.lib", "imm32.lib", "shell32.lib", "Uiautomationcore.lib", "dwmapi.lib", "uxtheme.lib", + "winmm.lib", "imm32.lib", "shell32.lib", "Uiautomationcore.lib", "dwmapi.lib", "/DELAYLOAD:user32.dll", "/DELAYLOAD:urlmon.dll", "/DELAYLOAD:winmm.dll", "/DELAYLOAD:shell32.dll", - "/DELAYLOAD:Uiautomationcore.dll", "/DELAYLOAD:dwmapi.dll", "/DELAYLOAD:uxtheme.dll"]).flatten() + "/DELAYLOAD:Uiautomationcore.dll", "/DELAYLOAD:dwmapi.dll"]).flatten() WIN.glass.lib = "glass" WIN.decora = [:] From 003e9d5628cb916a4eb1ad21b6b803aadf35ae50 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Thu, 14 Nov 2024 18:46:04 +0100 Subject: [PATCH 30/73] macOS: double-click action + fullscreen toolbar --- .../java/com/sun/glass/ui/mac/MacWindow.java | 4 ++- .../main/native-glass/mac/GlassViewDelegate.m | 4 +-- .../native-glass/mac/GlassWindow+Overrides.m | 8 ++++++ .../src/main/native-glass/mac/GlassWindow.m | 27 +++++++++++++++++++ 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java index 0cd8bc94d82..94eaa640957 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java @@ -163,6 +163,8 @@ protected void _releaseInput(long ptr) { private native void _performWindowDrag(long ptr); + private native void _performTitleBarDoubleClickAction(long ptr); + @Override public NonClientHandler getNonClientHandler() { return (type, button, x, y, xAbs, yAbs, clickCount) -> { @@ -173,7 +175,7 @@ public NonClientHandler getNonClientHandler() { View.EventHandler eventHandler = view != null ? view.getEventHandler() : null; if (eventHandler != null && eventHandler.pickDragAreaNode(wx, wy) != null) { if (clickCount == 2) { - maximize(!isMaximized()); + _performTitleBarDoubleClickAction(getRawHandle()); } else if (clickCount == 1) { _performWindowDrag(getRawHandle()); } diff --git a/modules/javafx.graphics/src/main/native-glass/mac/GlassViewDelegate.m b/modules/javafx.graphics/src/main/native-glass/mac/GlassViewDelegate.m index 244b6b70989..a3c466890fc 100644 --- a/modules/javafx.graphics/src/main/native-glass/mac/GlassViewDelegate.m +++ b/modules/javafx.graphics/src/main/native-glass/mac/GlassViewDelegate.m @@ -1145,9 +1145,7 @@ - (BOOL)suppressMouseEnterExitOnMouseDown - (void)performWindowDrag { - if (lastEvent != nil) { - [[nsView window] performWindowDragWithEvent:lastEvent]; - } + [[nsView window] performWindowDragWithEvent:[NSApp currentEvent]]; } static jstring convertNSStringToJString(id aString, int length) diff --git a/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow+Overrides.m b/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow+Overrides.m index 982a5b295da..e4b14661f44 100644 --- a/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow+Overrides.m +++ b/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow+Overrides.m @@ -204,6 +204,10 @@ - (void)windowWillEnterFullScreen:(NSNotification *)notification NSUInteger mask = [self->nsWindow styleMask]; self->isWindowResizable = ((mask & NSWindowStyleMaskResizable) != 0); [[self->view delegate] setResizableForFullscreen:YES]; + + if (nsWindow.toolbar != nil) { + nsWindow.toolbar.visible = NO; + } } - (void)windowDidEnterFullScreen:(NSNotification *)notification @@ -222,6 +226,10 @@ - (void)windowDidExitFullScreen:(NSNotification *)notification { //NSLog(@"windowDidExitFullScreen"); + if (nsWindow.toolbar != nil) { + nsWindow.toolbar.visible = YES; + } + GlassViewDelegate* delegate = (GlassViewDelegate*)[self->view delegate]; [delegate setResizableForFullscreen:self->isWindowResizable]; diff --git a/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m b/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m index 19c62bb5b8f..8daf919708b 100644 --- a/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m +++ b/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m @@ -1502,6 +1502,33 @@ static jlong _createWindowCommonDo(JNIEnv *env, jobject jWindow, jlong jOwnerPtr GLASS_CHECK_EXCEPTION(env); } +/* + * Class: com_sun_glass_ui_mac_MacWindow + * Method: _performTitleBarDoubleClickAction + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_com_sun_glass_ui_mac_MacWindow__1performTitleBarDoubleClickAction +(JNIEnv *env, jobject jWindow, jlong jPtr) +{ + LOG("Java_com_sun_glass_ui_mac_MacWindow__1performTitleBarDoubleClickAction"); + if (!jPtr) return; + + GLASS_ASSERT_MAIN_JAVA_THREAD(env); + GLASS_POOL_ENTER; + { + GlassWindow *window = getGlassWindow(env, jPtr); + NSString* action = [NSUserDefaults.standardUserDefaults stringForKey:@"AppleActionOnDoubleClick"]; + + if ([action isEqualToString:@"Minimize"]) { + [window->nsWindow performMiniaturize:nil]; + } else if ([action isEqualToString:@"Maximize"]) { + [window->nsWindow performZoom:nil]; + } + } + GLASS_POOL_EXIT; + GLASS_CHECK_EXCEPTION(env); +} + /* * Class: com_sun_glass_ui_mac_MacWindow * Method: _setToolbarStyle From d9b82c853a97cf70fb0bb1aa05320fccf3fd1cbd Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Fri, 6 Dec 2024 17:23:42 +0100 Subject: [PATCH 31/73] Add HeaderBar.overlappingSystemInset property --- .../java/javafx/scene/layout/HeaderBar.java | 168 +++++++++++++----- .../javafx/scene/layout/HeaderBarBase.java | 8 +- .../javafx/scene/layout/HeaderBarTest.java | 52 +++++- 3 files changed, 178 insertions(+), 50 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java index d3c94f2d5f7..93935fc1c62 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java @@ -28,6 +28,10 @@ import com.sun.javafx.geom.Vec2d; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectPropertyBase; +import javafx.css.CssMetaData; +import javafx.css.StyleConverter; +import javafx.css.Styleable; +import javafx.css.StyleableBooleanProperty; import javafx.css.StyleableDoubleProperty; import javafx.geometry.HPos; import javafx.geometry.Insets; @@ -38,6 +42,8 @@ import javafx.scene.Node; import javafx.scene.input.ContextMenuEvent; import javafx.stage.StageStyle; +import java.util.List; +import java.util.stream.Stream; /** * A client-area header bar that is used as a replacement for the system-provided header bar in stages @@ -48,25 +54,24 @@ *

* Some platforms support a system menu that can be summoned by right-clicking the draggable area. * This platform-provided menu will only be shown if the {@link ContextMenuEvent#CONTEXT_MENU_REQUESTED} - * event that is targeted at {@code HeaderBarBase} is not consumed by the application. + * event that is targeted at the header bar is not consumed by the application. *

- * {@code HeaderBar} is a layout container that allows applications to place scene graph nodes - * in three areas: {@link #leadingProperty() leading}, {@link #centerProperty() center}, and - * {@link #trailingProperty() trailing}. All areas can be {@code null}. {@code HeaderBar} ensures that - * the leading and trailing areas account for the default window buttons (minimize, maximize, close). - * If a child is configured to be centered in the {@code center} area, it is laid out with respect to - * the stage, and not with respect to the {@code center} area. This ensures that the child will appear - * centered in the stage regardless of leading or trailing children or the platform-specific placement - * of default window buttons. + * {@code HeaderBar} is a layout container that allows applications to place scene graph nodes in three areas: + * {@link #leadingProperty() leading}, {@link #centerProperty() center}, and {@link #trailingProperty() trailing}. + * All areas can be {@code null}. The {@link #overlappingSystemInsetProperty() overlappingSystemInset} property + * controls whether the leading and trailing areas account for the default window buttons (minimize, maximize, + * close). If a child is configured to be centered in the {@code center} area, it is laid out with respect to the + * stage, and not with respect to the {@code center} area. This ensures that the child will appear centered in the + * stage regardless of leading or trailing children or the platform-specific placement of default window buttons. *

* All children will be resized to their preferred widths and extend the height of the {@code HeaderBar}. - * {@code HeaderBar} honors the minimum, preferred, and maximum sizes of its children. As a consequence, - * its computed minimum size is sufficient to accommodate all of its children. If a child's resizable - * range prevents it from be resized to fit within its position, it will be vertically centered relative - * to the available space; this alignment can be customized with a layout constraint. + * {@code HeaderBar} honors the minimum, preferred, and maximum sizes of its children. As a consequence, its + * computed minimum size is sufficient to accommodate all of its children. If a child's resizable range prevents + * it from be resized to fit within its position, it will be vertically centered relative to the available space; + * this alignment can be customized with a layout constraint. *

- * The default {@link #minHeightProperty() minHeight} of the {@code HeaderBar} is set to match the height - * of the platform-specific default window buttons. + * The default {@link #minHeightProperty() minHeight} of the {@code HeaderBar} is set to match the height of the + * platform-specific default window buttons. * *

Layout constraints

* An application may set constraints on individual children to customize their layout. @@ -198,12 +203,7 @@ public HeaderBar(Node leading, Node center, Node trailing) { * Usually, this corresponds to the left side of the header bar; with right-to-left orientation, * this corresponds to the right side of the header bar. */ - private final ObjectProperty leading = new NodeProperty() { - @Override - public String getName() { - return "leading"; - } - }; + private final ObjectProperty leading = new NodeProperty("leading"); public final ObjectProperty leadingProperty() { return leading; @@ -220,12 +220,7 @@ public final void setLeading(Node value) { /** * The center area of the {@code HeaderBar}. */ - private final ObjectProperty center = new NodeProperty() { - @Override - public String getName() { - return "center"; - } - }; + private final ObjectProperty center = new NodeProperty("center"); public final ObjectProperty centerProperty() { return center; @@ -245,12 +240,7 @@ public final void setCenter(Node value) { * Usually, this corresponds to the right side of the header bar; with right-to-left orientation, * this corresponds to the left side of the header bar. */ - private final ObjectProperty trailing = new NodeProperty() { - @Override - public String getName() { - return "trailing"; - } - }; + private final ObjectProperty trailing = new NodeProperty("trailing"); public final ObjectProperty trailingProperty() { return trailing; @@ -264,6 +254,44 @@ public final void setTrailing(Node value) { trailing.set(value); } + /** + * Specifies whether the system-provided window buttons overlap the content of this {@code HeaderBar}, + * or whether they are laid out next to the content of the header bar, taking up space in its layout. + */ + private final StyleableBooleanProperty overlappingSystemInset = new StyleableBooleanProperty() { + @Override + public Object getBean() { + return HeaderBar.this; + } + + @Override + public String getName() { + return "systemInset"; + } + + @Override + protected void invalidated() { + requestLayout(); + } + + @Override + public CssMetaData getCssMetaData() { + return OVERLAPPING_SYSTEM_INSET; + } + }; + + public final StyleableBooleanProperty overlappingSystemInsetProperty() { + return overlappingSystemInset; + } + + public final boolean isOverlappingSystemInset() { + return overlappingSystemInset.get(); + } + + public final void setOverlappingSystemInset(boolean value) { + overlappingSystemInset.set(value); + } + @Override protected double computeMinWidth(double height) { Node leading = getLeading(); @@ -273,6 +301,7 @@ protected double computeMinWidth(double height) { double leftPrefWidth; double rightPrefWidth; double centerMinWidth; + double systemInsetWidth; if (height != -1 && (childHasContentBias(leading, Orientation.VERTICAL) || @@ -288,13 +317,18 @@ protected double computeMinWidth(double height) { centerMinWidth = getAreaWidth(center, -1, true); } + if (isOverlappingSystemInset()) { + systemInsetWidth = 0; + } else { + systemInsetWidth = getLeftSystemInset().getWidth() + getRightSystemInset().getWidth(); + } + return insets.getLeft() + leftPrefWidth + centerMinWidth + rightPrefWidth + insets.getRight() - + getLeftSystemInset().getWidth() - + getRightSystemInset().getWidth(); + + systemInsetWidth; } @Override @@ -356,24 +390,30 @@ protected void layoutChildren() { boolean rtl = getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT; double width = Math.max(getWidth(), minWidth(-1)); double height = Math.max(getHeight(), minHeight(-1)); - double leftSystemInset = getLeftSystemInset().getWidth(); - double rightSystemInset = getRightSystemInset().getWidth(); double leftWidth = 0; double rightWidth = 0; double insideY = insets.getTop(); double insideHeight = height - insideY - insets.getBottom(); double insideX, insideWidth; + double leftSystemInsetWidth, rightSystemInsetWidth; + + if (isOverlappingSystemInset()) { + leftSystemInsetWidth = rightSystemInsetWidth = 0; + } else { + leftSystemInsetWidth = getLeftSystemInset().getWidth(); + rightSystemInsetWidth = getRightSystemInset().getWidth(); + } if (rtl) { left = getTrailing(); right = getLeading(); - insideX = insets.getRight() + leftSystemInset; - insideWidth = width - insideX - insets.getLeft() - rightSystemInset; + insideX = insets.getRight() + leftSystemInsetWidth; + insideWidth = width - insideX - insets.getLeft() - rightSystemInsetWidth; } else { left = getLeading(); right = getTrailing(); - insideX = insets.getLeft() + leftSystemInset; - insideWidth = width - insideX - insets.getRight() - rightSystemInset; + insideX = insets.getLeft() + leftSystemInsetWidth; + insideWidth = width - insideX - insets.getRight() - rightSystemInsetWidth; } if (left != null && left.isManaged()) { @@ -512,14 +552,24 @@ private void updateMinHeight() { } } - private abstract class NodeProperty extends ObjectPropertyBase { + private final class NodeProperty extends ObjectPropertyBase { + private final String name; private Node value; + NodeProperty(String name) { + this.name = name; + } + @Override public Object getBean() { return HeaderBar.this; } + @Override + public String getName() { + return name; + } + @Override protected void invalidated() { if (value != null) { @@ -533,4 +583,38 @@ protected void invalidated() { } } } + + private static final CssMetaData OVERLAPPING_SYSTEM_INSET = new CssMetaData<>( + "-fx-overlapping-system-inset", StyleConverter.getBooleanConverter(), false) { + @Override + public boolean isSettable(HeaderBar headerBar) { + return headerBar == null || !headerBar.overlappingSystemInset.isBound(); + } + + @Override + public StyleableBooleanProperty getStyleableProperty(HeaderBar headerBar) { + return headerBar.overlappingSystemInset; + } + }; + + private static final List> METADATA = + Stream.concat( + Region.getClassCssMetaData().stream(), + Stream.of(OVERLAPPING_SYSTEM_INSET)) + .toList(); + + @Override + public List> getCssMetaData() { + return METADATA; + } + + /** + * Gets the {@code CssMetaData} associated with this class, which includes the + * {@code CssMetaData} of its superclasses. + * + * @return the {@code CssMetaData} + */ + public static List> getClassCssMetaData() { + return METADATA; + } } diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java index 896dae17d9d..57922622ca0 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java @@ -210,10 +210,10 @@ public final Dimension2D getRightSystemInset() { } /** - * The absolute minimum height of {@link #leftSystemInsetProperty() leftSystemInset} and - * {@link #rightSystemInsetProperty() rightSystemInset}. This is a platform-dependent value - * that a {@code HeaderBarBase} implementation can use to define a reasonable minimum height - * for the header bar area. + * The system-provided reasonable minimum height of {@link #leftSystemInsetProperty() leftSystemInset} + * {@link #rightSystemInsetProperty() rightSystemInset}. This is a platform-dependent value that a + * {@code HeaderBarBase} implementation can use to define a reasonable minimum height for the header + * bar area. */ private final ReadOnlyDoubleWrapper minSystemHeight = new ReadOnlyDoubleWrapper(this, "minSystemHeight") { diff --git a/modules/javafx.graphics/src/test/java/test/javafx/scene/layout/HeaderBarTest.java b/modules/javafx.graphics/src/test/java/test/javafx/scene/layout/HeaderBarTest.java index 43fe317ec97..c17efde58e0 100644 --- a/modules/javafx.graphics/src/test/java/test/javafx/scene/layout/HeaderBarTest.java +++ b/modules/javafx.graphics/src/test/java/test/javafx/scene/layout/HeaderBarTest.java @@ -251,7 +251,6 @@ void alignmentOfCenterChild_notResizable_withNonEmptyLeadingAndTrailingChild( } @ParameterizedTest - @SuppressWarnings("unchecked") @CsvSource({ "TOP_LEFT, 160, 10, 100, 80", "TOP_CENTER, 450, 10, 100, 80", @@ -264,6 +263,7 @@ void alignmentOfCenterChild_notResizable_withNonEmptyLeadingAndTrailingChild( "BOTTOM_RIGHT, 740, 10, 100, 80" }) void alignmentOfCenterChild_withLeftSystemInset(Pos pos, double x, double y, double width, double height) { + @SuppressWarnings("unchecked") var leftSystemInset = (ObjectProperty)ReflectionUtils.getFieldValue(headerBar, "leftSystemInset"); leftSystemInset.set(new Dimension2D(100, 100)); var leading = new MockResizable(50, 50); @@ -281,7 +281,6 @@ void alignmentOfCenterChild_withLeftSystemInset(Pos pos, double x, double y, dou } @ParameterizedTest - @SuppressWarnings("unchecked") @CsvSource({ "TOP_LEFT, 60, 10, 100, 80", "TOP_CENTER, 450, 10, 100, 80", @@ -294,6 +293,7 @@ void alignmentOfCenterChild_withLeftSystemInset(Pos pos, double x, double y, dou "BOTTOM_RIGHT, 640, 10, 100, 80" }) void alignmentOfCenterChild_withRightSystemInset(Pos pos, double x, double y, double width, double height) { + @SuppressWarnings("unchecked") var rightSystemInset = (ObjectProperty)ReflectionUtils.getFieldValue(headerBar, "rightSystemInset"); rightSystemInset.set(new Dimension2D(100, 100)); var leading = new MockResizable(50, 50); @@ -311,7 +311,6 @@ void alignmentOfCenterChild_withRightSystemInset(Pos pos, double x, double y, do } @ParameterizedTest - @SuppressWarnings("unchecked") @CsvSource({ "TOP_CENTER, 260, 10, 80, 80", "CENTER, 260, 10, 80, 80", @@ -319,6 +318,7 @@ void alignmentOfCenterChild_withRightSystemInset(Pos pos, double x, double y, do }) void alignmentOfCenterChild_withLeftSystemInset_andOffsetCausedByInsufficientHorizontalSpace( Pos pos, double x, double y, double width, double height) { + @SuppressWarnings("unchecked") var leftSystemInset = (ObjectProperty)ReflectionUtils.getFieldValue(headerBar, "leftSystemInset"); leftSystemInset.set(new Dimension2D(200, 100)); var leading = new MockResizable(50, 50); @@ -336,7 +336,6 @@ void alignmentOfCenterChild_withLeftSystemInset_andOffsetCausedByInsufficientHor } @ParameterizedTest - @SuppressWarnings("unchecked") @CsvSource({ "TOP_CENTER, 60, 10, 80, 80", "CENTER, 60, 10, 80, 80", @@ -344,6 +343,7 @@ void alignmentOfCenterChild_withLeftSystemInset_andOffsetCausedByInsufficientHor }) void alignmentOfCenterChild_withRightSystemInset_andOffsetCausedByInsufficientHorizontalSpace( Pos pos, double x, double y, double width, double height) { + @SuppressWarnings("unchecked") var rightSystemInset = (ObjectProperty)ReflectionUtils.getFieldValue(headerBar, "rightSystemInset"); rightSystemInset.set(new Dimension2D(200, 100)); var leading = new MockResizable(50, 50); @@ -360,6 +360,50 @@ void alignmentOfCenterChild_withRightSystemInset_andOffsetCausedByInsufficientHo assertBounds(x, y, width, height, center); } + @ParameterizedTest + @CsvSource({ + "TOP_LEFT, 10, 10, 50, 50", + "CENTER, 10, 25, 50, 50", + "BOTTOM_LEFT, 10, 40, 50, 50" + }) + void alignmentOfLeadingChild_notResizable_withOverlappingLeftSystemInset( + Pos pos, double x, double y, double width, double height) { + @SuppressWarnings("unchecked") + var leftSystemInset = (ObjectProperty)ReflectionUtils.getFieldValue(headerBar, "leftSystemInset"); + leftSystemInset.set(new Dimension2D(100, 100)); + var leading = new Rectangle(50, 50); + HeaderBar.setAlignment(leading, pos); + HeaderBar.setMargin(leading, new Insets(10)); + headerBar.setOverlappingSystemInset(true); + headerBar.setLeading(leading); + headerBar.resize(1000, 100); + headerBar.layout(); + + assertBounds(x, y, width, height, leading); + } + + @ParameterizedTest + @CsvSource({ + "TOP_RIGHT, 940, 10, 50, 50", + "CENTER, 940, 25, 50, 50", + "BOTTOM_RIGHT, 940, 40, 50, 50" + }) + void alignmentOfTrailingChild_notResizable_withOverlappingRightSystemInset( + Pos pos, double x, double y, double width, double height) { + @SuppressWarnings("unchecked") + var rightSystemInset = (ObjectProperty)ReflectionUtils.getFieldValue(headerBar, "rightSystemInset"); + rightSystemInset.set(new Dimension2D(100, 100)); + var trailing = new Rectangle(50, 50); + HeaderBar.setAlignment(trailing, pos); + HeaderBar.setMargin(trailing, new Insets(10)); + headerBar.setOverlappingSystemInset(true); + headerBar.setTrailing(trailing); + headerBar.resize(1000, 100); + headerBar.layout(); + + assertBounds(x, y, width, height, trailing); + } + private void assertBounds(double x, double y, double width, double height, Node node) { var bounds = node.getLayoutBounds(); assertEquals(x, node.getLayoutX()); From 8649f917509a01f50b62c54afd75b28dbca4413c Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Fri, 6 Dec 2024 17:25:24 +0100 Subject: [PATCH 32/73] StyleableBooleanProperty -> BooleanProperty --- .../src/main/java/javafx/scene/layout/HeaderBar.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java index 93935fc1c62..f2d2d4ce16e 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java @@ -26,6 +26,7 @@ package javafx.scene.layout; import com.sun.javafx.geom.Vec2d; +import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectPropertyBase; import javafx.css.CssMetaData; @@ -280,7 +281,7 @@ public CssMetaData getCssMetaData() { } }; - public final StyleableBooleanProperty overlappingSystemInsetProperty() { + public final BooleanProperty overlappingSystemInsetProperty() { return overlappingSystemInset; } From 65230730eb3a8220d88a4b96bd9b2d06d77c51b0 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Tue, 10 Dec 2024 19:45:15 +0100 Subject: [PATCH 33/73] fix system menu for non-resizeable extended window --- .../src/main/native-glass/win/GlassWindow.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp b/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp index e852562096e..cd8748fa0b3 100644 --- a/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp +++ b/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp @@ -1248,8 +1248,8 @@ void GlassWindow::ShowSystemMenu(int x, int y) } // Mirror the X coordinate we get from JavaFX if this is a RTL window. - LONG style = ::GetWindowLong(GetHWND(), GWL_EXSTYLE); - if (style & WS_EX_LAYOUTRTL) { + LONG exStyle = ::GetWindowLong(GetHWND(), GWL_EXSTYLE); + if (exStyle & WS_EX_LAYOUTRTL) { RECT rect = {0}; ::GetClientRect(GetHWND(), &rect); x = max(0, rect.right - rect.left) - x; @@ -1258,6 +1258,10 @@ void GlassWindow::ShowSystemMenu(int x, int y) HMENU systemMenu = GetSystemMenu(GetHWND(), FALSE); bool maximized = placement.showCmd == SW_SHOWMAXIMIZED; + LONG style = ::GetWindowLong(GetHWND(), GWL_STYLE); + bool canMinimize = (style & WS_MINIMIZEBOX) && !(exStyle & WS_EX_TOOLWINDOW); + bool canMaximize = (style & WS_MAXIMIZEBOX) && !maximized; + MENUITEMINFO menuItemInfo { sizeof(MENUITEMINFO) }; menuItemInfo.fMask = MIIM_STATE; menuItemInfo.fType = MFT_STRING; @@ -1268,13 +1272,13 @@ void GlassWindow::ShowSystemMenu(int x, int y) menuItemInfo.fState = maximized ? MF_DISABLED : MF_ENABLED; SetMenuItemInfo(systemMenu, SC_MOVE, FALSE, &menuItemInfo); - menuItemInfo.fState = maximized ? MF_DISABLED : MF_ENABLED; + menuItemInfo.fState = !m_isResizable || maximized ? MF_DISABLED : MF_ENABLED; SetMenuItemInfo(systemMenu, SC_SIZE, FALSE, &menuItemInfo); - menuItemInfo.fState = MF_ENABLED; + menuItemInfo.fState = canMinimize ? MF_ENABLED : MF_DISABLED; SetMenuItemInfo(systemMenu, SC_MINIMIZE, FALSE, &menuItemInfo); - menuItemInfo.fState = maximized ? MF_DISABLED : MF_ENABLED; + menuItemInfo.fState = canMaximize ? MF_ENABLED : MF_DISABLED; SetMenuItemInfo(systemMenu, SC_MAXIMIZE, FALSE, &menuItemInfo); menuItemInfo.fState = MF_ENABLED; From 8d5d7b877c1611a346a67834cb4b65faa40c48cd Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Tue, 10 Dec 2024 20:09:38 +0100 Subject: [PATCH 34/73] add StageStyle.EXTENDED_UTILITY --- .../main/java/com/sun/glass/ui/Window.java | 4 +++ .../sun/glass/ui/WindowControlsOverlay.java | 27 +++++++++++--- .../java/com/sun/glass/ui/gtk/GtkWindow.java | 2 +- .../java/com/sun/glass/ui/win/WinWindow.java | 4 ++- .../sun/javafx/tk/quantum/WindowStage.java | 5 +++ .../application/ConditionalFeature.java | 6 ++-- .../main/java/javafx/stage/StageStyle.java | 12 ++++++- .../com/sun/glass/ui/win/WindowDecoration.css | 4 +++ .../glass/ui/WindowControlsOverlayTest.java | 35 ++++++++++++------- 9 files changed, 77 insertions(+), 22 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java index 605cdc68dd1..519cee346ab 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java @@ -747,6 +747,10 @@ public boolean isExtendedWindow() { return (this.styleMask & Window.EXTENDED) != 0; } + public boolean isUtilityWindow() { + return (this.styleMask & Window.UTILITY) != 0; + } + public boolean isTransparentWindow() { //The TRANSPARENT flag is set only if it is supported return (this.styleMask & Window.TRANSPARENT) != 0; diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java index 93ee8e07e89..0ab50e2bc66 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java @@ -26,6 +26,7 @@ package com.sun.glass.ui; import com.sun.glass.events.MouseEvent; +import com.sun.javafx.binding.ObjectConstant; import com.sun.javafx.util.Utils; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyObjectProperty; @@ -167,6 +168,7 @@ public StyleableProperty getStyleableProperty(WindowControlsOverlay ove private static final PseudoClass ACTIVE_PSEUDOCLASS = PseudoClass.getPseudoClass("active"); private static final String DARK_STYLE_CLASS = "dark"; private static final String RESTORE_STYLE_CLASS = "restore"; + private static final String UTILITY_STYLE_CLASS = "utility"; /** * The metrics (placement and size) of the window buttons. @@ -213,10 +215,13 @@ protected void invalidated() { private final ButtonRegion maximizeButton = new ButtonRegion(ButtonType.MAXIMIZE, "maximize-button", 1); private final ButtonRegion closeButton = new ButtonRegion(ButtonType.CLOSE, "close-button", 2); private final Subscription subscriptions; + private final boolean utility; private Node buttonAtMouseDown; - public WindowControlsOverlay(ObservableValue stylesheet) { + public WindowControlsOverlay(ObservableValue stylesheet, boolean utility) { + this.utility = utility; + var stage = sceneProperty() .flatMap(Scene::windowProperty) .map(w -> w instanceof Stage ? (Stage)w : null); @@ -250,7 +255,15 @@ public WindowControlsOverlay(ObservableValue stylesheet) { stylesheet.subscribe(this::updateStylesheet)); getStyleClass().setAll("window-button-container"); - getChildren().addAll(minimizeButton, maximizeButton, closeButton); + + if (utility) { + minimizeButton.managedProperty().bind(ObjectConstant.valueOf(false)); + maximizeButton.managedProperty().bind(ObjectConstant.valueOf(false)); + getChildren().add(closeButton); + getStyleClass().add(UTILITY_STYLE_CLASS); + } else { + getChildren().addAll(minimizeButton, maximizeButton, closeButton); + } } public void dispose() { @@ -270,10 +283,14 @@ public ReadOnlyObjectProperty metricsProperty() { * @return the {@code ButtonType} or {@code null} */ public ButtonType buttonAt(double x, double y) { - for (var button : orderedButtons) { - if (button.isVisible() && button.getBoundsInParent().contains(x, y)) { - return button.getButtonType(); + if (!utility) { + for (var button : orderedButtons) { + if (button.isVisible() && button.getBoundsInParent().contains(x, y)) { + return button.getButtonType(); + } } + } else if (closeButton.isVisible() && closeButton.getBoundsInParent().contains(x, y)) { + return ButtonType.CLOSE; } return null; diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java index 61732aa9569..17d165d1f86 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java @@ -212,7 +212,7 @@ public long getRawHandle() { public WindowControlsOverlay getWindowOverlay() { if (windowControlsOverlay == null && isExtendedWindow()) { windowControlsOverlay = new WindowControlsOverlay( - PlatformThemeObserver.getInstance().stylesheetProperty()); + PlatformThemeObserver.getInstance().stylesheetProperty(), isUtilityWindow()); // Set the system-defined absolute minimum size to the size of the window buttons area, // regardless of whether the application has specified a smaller minimum size. diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java index d669846bd97..64dba9878b1 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java @@ -346,7 +346,9 @@ public WindowControlsOverlay getWindowOverlay() { throw new RuntimeException("Resource not found: " + WINDOW_DECORATION_STYLESHEET); } - windowControlsOverlay = new WindowControlsOverlay(StringConstant.valueOf(url.toExternalForm())); + windowControlsOverlay = new WindowControlsOverlay( + StringConstant.valueOf(url.toExternalForm()), isUtilityWindow()); + windowOverlayMetrics.bind(windowControlsOverlay.metricsProperty()); } diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/WindowStage.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/WindowStage.java index ccae34285c7..e035d42057e 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/WindowStage.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/WindowStage.java @@ -144,6 +144,8 @@ private void initPlatformWindow() { style = StageStyle.DECORATED; } else if (style == StageStyle.EXTENDED && !app.supportsExtendedWindows()) { style = StageStyle.DECORATED; + } else if (style == StageStyle.EXTENDED_UTILITY && !app.supportsExtendedWindows()) { + style = StageStyle.UTILITY; } switch (style) { @@ -160,6 +162,9 @@ private void initPlatformWindow() { windowMask |= Window.EXTENDED | Window.CLOSABLE | Window.MINIMIZABLE | Window.MAXIMIZABLE; resizable = true; break; + case EXTENDED_UTILITY: + windowMask |= Window.EXTENDED | Window.CLOSABLE | Window.UTILITY; + break; case UTILITY: windowMask |= Window.TITLED | Window.UTILITY | Window.CLOSABLE; break; diff --git a/modules/javafx.graphics/src/main/java/javafx/application/ConditionalFeature.java b/modules/javafx.graphics/src/main/java/javafx/application/ConditionalFeature.java index 8b7e3ebf480..5028cfd0d8f 100644 --- a/modules/javafx.graphics/src/main/java/javafx/application/ConditionalFeature.java +++ b/modules/javafx.graphics/src/main/java/javafx/application/ConditionalFeature.java @@ -25,6 +25,8 @@ package javafx.application; +import javafx.stage.StageStyle; + /** * Defines a set of conditional (optional) features. These features * may not be available on all platforms. An application that wants to @@ -139,7 +141,7 @@ public enum ConditionalFeature { TRANSPARENT_WINDOW, /** - * Indicates that a system supports {@link javafx.stage.StageStyle#UNIFIED} + * Indicates that a system supports {@link StageStyle#UNIFIED} *

* NOTE: Currently, supported on: *

    @@ -151,7 +153,7 @@ public enum ConditionalFeature { UNIFIED_WINDOW, /** - * Indicates that a system supports {@link javafx.stage.StageStyle#EXTENDED}. + * Indicates that a system supports {@link StageStyle#EXTENDED} and {@link StageStyle#EXTENDED_UTILITY}. *

    * This feature is currently supported on Windows, Linux, and macOS. * diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java b/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java index d6084661839..a00e3ed71f0 100644 --- a/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java +++ b/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java @@ -115,5 +115,15 @@ public enum StageStyle { * * @since 24 */ - EXTENDED + EXTENDED, + + /** + * Defines a {@code Stage} style with the semantics of {@link #UTILITY} and the appearance of {@link #EXTENDED}. + *

    + * This is a conditional feature, to check if it is supported see {@link Platform#isSupported(ConditionalFeature)}. + * If the feature is not supported by the platform, this style downgrades to {@link StageStyle#UTILITY}. + * + * @since 24 + */ + EXTENDED_UTILITY } diff --git a/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/win/WindowDecoration.css b/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/win/WindowDecoration.css index 15b918f803e..4a9f9df3ad3 100644 --- a/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/win/WindowDecoration.css +++ b/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/win/WindowDecoration.css @@ -36,6 +36,10 @@ -fx-pref-height: 29; } +.window-button-container.utility > .close-button { + -fx-pref-width: 29; +} + .minimize-button:hover, .maximize-button:hover { -fx-background-color: #00000015; diff --git a/modules/javafx.graphics/src/test/java/test/com/sun/glass/ui/WindowControlsOverlayTest.java b/modules/javafx.graphics/src/test/java/test/com/sun/glass/ui/WindowControlsOverlayTest.java index a03afc65ca3..533b819b986 100644 --- a/modules/javafx.graphics/src/test/java/test/com/sun/glass/ui/WindowControlsOverlayTest.java +++ b/modules/javafx.graphics/src/test/java/test/com/sun/glass/ui/WindowControlsOverlayTest.java @@ -52,7 +52,7 @@ void rightPlacement() { var overlay = new WindowControlsOverlay(getStylesheet(""" .window-button-container { -fx-button-placement: right; } .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } - """)); + """), false); var unused = new Scene(overlay); var children = overlay.getChildrenUnmodifiable(); @@ -76,7 +76,7 @@ void rightPlacement_rightToLeft() { var overlay = new WindowControlsOverlay(getStylesheet(""" .window-button-container { -fx-button-placement: right; } .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } - """)); + """), false); var unused = new Scene(overlay); var children = overlay.getChildrenUnmodifiable(); @@ -101,7 +101,7 @@ void leftPlacement() { var overlay = new WindowControlsOverlay(getStylesheet(""" .window-button-container { -fx-button-placement: left; } .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } - """)); + """), false); var unused = new Scene(overlay); var children = overlay.getChildrenUnmodifiable(); @@ -125,7 +125,7 @@ void leftPlacement_rightToLeft() { var overlay = new WindowControlsOverlay(getStylesheet(""" .window-button-container { -fx-button-placement: left; } .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } - """)); + """), false); var unused = new Scene(overlay); var children = overlay.getChildrenUnmodifiable(); @@ -152,7 +152,7 @@ void customButtonOrder() { .minimize-button { -fx-button-order: 5; } .maximize-button { -fx-button-order: 1; } .close-button { -fx-button-order: 3; } - """)); + """), false); var unused = new Scene(overlay); var children = overlay.getChildrenUnmodifiable(); @@ -178,7 +178,7 @@ void customButtonOrder_rightToLeft() { .minimize-button { -fx-button-order: 5; } .maximize-button { -fx-button-order: 1; } .close-button { -fx-button-order: 3; } - """)); + """), false); var unused = new Scene(overlay); var children = overlay.getChildrenUnmodifiable(); @@ -195,6 +195,17 @@ void customButtonOrder_rightToLeft() { assertLayoutBounds(children.get(2), 20, 0, 20, 10); } + @Test + void utilityDecorationIsOnlyCloseButton() { + var overlay = new WindowControlsOverlay(getStylesheet(""" + .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } + """), true); + + var children = overlay.getChildrenUnmodifiable(); + assertEquals(1, children.size()); + assertTrue(children.getFirst().getStyleClass().contains("close-button")); + } + /** * Asserts that the buttons are laid out on the right, even though the node orientation is right-to-left. */ @@ -203,7 +214,7 @@ void disallowRightToLeft() { var overlay = new WindowControlsOverlay(getStylesheet(""" .window-button-container { -fx-button-placement: right; -fx-allow-rtl: false; } .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } - """)); + """), false); var unused = new Scene(overlay); var children = overlay.getChildrenUnmodifiable(); @@ -224,7 +235,7 @@ void activePseudoClassCorrespondsToStageFocusedProperty() { var overlay = new WindowControlsOverlay(getStylesheet(""" .window-button-container { -fx-button-placement: right; } .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } - """)); + """), false); var scene = new Scene(overlay); var stage = new Stage(); @@ -250,7 +261,7 @@ void maximizeButtonIsDisabledWhenStageIsNotResizable() { var overlay = new WindowControlsOverlay(getStylesheet(""" .window-button-container { -fx-button-placement: right; } .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } - """)); + """), false); var scene = new Scene(overlay); var stage = new Stage(); @@ -274,7 +285,7 @@ void restoreStyleClassIsPresentWhenStageIsMaximized() { var overlay = new WindowControlsOverlay(getStylesheet(""" .window-button-container { -fx-button-placement: right; } .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } - """)); + """), false); var scene = new Scene(overlay); var stage = new Stage(); @@ -297,7 +308,7 @@ void darkStyleClassIsPresentWhenSceneFillIsDark() { var overlay = new WindowControlsOverlay(getStylesheet(""" .window-button-container { -fx-button-placement: right; } .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } - """)); + """), false); var scene = new Scene(overlay); @@ -316,7 +327,7 @@ void pickButtonAtCoordinates() { var overlay = new WindowControlsOverlay(getStylesheet(""" .window-button-container { -fx-button-placement: right; } .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } - """)); + """), false); var unused = new Scene(overlay); overlay.resize(200, 100); From eaafd9f025c3cdde1d1466f32baee4597babca2d Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Tue, 10 Dec 2024 23:08:50 +0100 Subject: [PATCH 35/73] fix resizable states for GTK windows --- .../main/java/com/sun/glass/ui/Window.java | 46 +++++++++---------- .../sun/javafx/tk/quantum/WindowStage.java | 15 ++---- .../main/native-glass/gtk/glass_window.cpp | 17 +++++-- .../src/main/native-glass/gtk/glass_window.h | 3 ++ 4 files changed, 42 insertions(+), 39 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java index 519cee346ab..6dfdd402c18 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java @@ -694,29 +694,27 @@ private void synthesizeViewMoveEvent() { protected abstract boolean _setVisible(long ptr, boolean visible); public void setVisible(final boolean visible) { Application.checkEventThread(); - if (this.isVisible != visible) { - if (!visible) { - if (getView() != null) { - getView().setVisible(visible); - } - // Avoid native call if the window has been closed already - if (this.ptr != 0L) { - this.isVisible = _setVisible(this.ptr, visible); - } else { - this.isVisible = visible; - } - remove(this); - } else { - checkNotClosed(); + if (!visible) { + if (getView() != null) { + getView().setVisible(visible); + } + // Avoid native call if the window has been closed already + if (this.ptr != 0L) { this.isVisible = _setVisible(this.ptr, visible); + } else { + this.isVisible = visible; + } + remove(this); + } else { + checkNotClosed(); + this.isVisible = _setVisible(this.ptr, visible); - if (getView() != null) { - getView().setVisible(this.isVisible); - } - add(this); - - synthesizeViewMoveEvent(); + if (getView() != null) { + getView().setVisible(this.isVisible); } + add(this); + + synthesizeViewMoveEvent(); } } @@ -724,11 +722,9 @@ public void setVisible(final boolean visible) { public boolean setResizable(final boolean resizable) { Application.checkEventThread(); checkNotClosed(); - if (this.isResizable != resizable) { - if (_setResizable(this.ptr, resizable)) { - this.isResizable = resizable; - synthesizeViewMoveEvent(); - } + if (_setResizable(this.ptr, resizable)) { + this.isResizable = resizable; + synthesizeViewMoveEvent(); } return isResizable; } diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/WindowStage.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/WindowStage.java index e035d42057e..2b6a7175f4c 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/WindowStage.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/WindowStage.java @@ -129,7 +129,7 @@ private void initPlatformWindow() { if (owner instanceof WindowStage) { ownerWindow = ((WindowStage)owner).platformWindow; } - boolean resizable = false; + boolean resizable = fxStage != null && fxStage.isResizable(); boolean focusable = true; int windowMask = rtl ? Window.RIGHT_TO_LEFT : 0; if (isPopupStage) { // TODO: make it a stage style? @@ -138,6 +138,7 @@ private void initPlatformWindow() { windowMask |= Window.TRANSPARENT; } focusable = false; + resizable = false; } else { // Downgrade conditional stage styles if not supported if (style == StageStyle.UNIFIED && !app.supportsUnifiedWindows()) { @@ -153,14 +154,10 @@ private void initPlatformWindow() { windowMask |= Window.UNIFIED; // fall through case DECORATED: - windowMask |= - Window.TITLED | Window.CLOSABLE | - Window.MINIMIZABLE | Window.MAXIMIZABLE; - resizable = true; + windowMask |= Window.TITLED | Window.CLOSABLE | Window.MINIMIZABLE | Window.MAXIMIZABLE; break; case EXTENDED: windowMask |= Window.EXTENDED | Window.CLOSABLE | Window.MINIMIZABLE | Window.MAXIMIZABLE; - resizable = true; break; case EXTENDED_UTILITY: windowMask |= Window.EXTENDED | Window.CLOSABLE | Window.UTILITY; @@ -169,8 +166,7 @@ private void initPlatformWindow() { windowMask |= Window.TITLED | Window.UTILITY | Window.CLOSABLE; break; default: - windowMask |= - (transparent ? Window.TRANSPARENT : Window.UNTITLED) | Window.CLOSABLE; + windowMask |= (transparent ? Window.TRANSPARENT : Window.UNTITLED) | Window.CLOSABLE; break; } @@ -181,8 +177,7 @@ private void initPlatformWindow() { if (modality != Modality.NONE) { windowMask |= Window.MODAL; } - platformWindow = - app.createWindow(ownerWindow, Screen.getMainScreen(), windowMask); + platformWindow = app.createWindow(ownerWindow, Screen.getMainScreen(), windowMask); platformWindow.setResizable(resizable); platformWindow.setFocusable(focusable); if (fxStage != null && fxStage.getScene() != null) { diff --git a/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.cpp b/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.cpp index 132109a74fe..baa9bb32cdd 100644 --- a/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.cpp +++ b/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.cpp @@ -585,6 +585,10 @@ bool WindowContextBase::is_visible() { return gtk_widget_get_visible(gtk_widget); } +bool WindowContextBase::is_resizable() { + return false; +} + bool WindowContextBase::set_view(jobject view) { if (jview) { mainEnv->CallVoidMethod(jview, jViewNotifyMouse, @@ -781,7 +785,7 @@ WindowContextTop::WindowContextTop(jobject _jwindow, WindowContext* _owner, long } } - if (type == UTILITY) { + if (type == UTILITY && frame_type != EXTENDED) { gtk_window_set_type_hint(GTK_WINDOW(gtk_widget), GDK_WINDOW_TYPE_HINT_UTILITY); } @@ -1067,7 +1071,7 @@ void WindowContextTop::update_window_constraints() { GdkGeometry hints; - if (resizable.value && !is_disabled) { + if (is_resizable() && !is_disabled) { int w = std::max(resizable.sysminw, resizable.minw); int h = std::max(resizable.sysminh, resizable.minh); @@ -1101,6 +1105,10 @@ void WindowContextTop::set_resizable(bool res) { update_window_constraints(); } +bool WindowContextTop::is_resizable() { + return resizable.value; +} + void WindowContextTop::set_visible(bool visible) { WindowContextBase::set_visible(visible); @@ -1449,7 +1457,7 @@ void WindowContextTop::process_mouse_button(GdkEventButton* event, bool synthesi } // Double-clicking on the drag area maximizes the window (or restores its size). - if (event->type == GDK_2BUTTON_PRESS) { + if (is_resizable() && event->type == GDK_2BUTTON_PRESS) { jboolean dragArea = mainEnv->CallBooleanMethod( jwindow, jGtkWindowDragAreaHitTest, (jint)event->x, (jint)event->y); CHECK_JNI_EXCEPTION(mainEnv); @@ -1464,7 +1472,7 @@ void WindowContextTop::process_mouse_button(GdkEventButton* event, bool synthesi if (event->button == 1 && event->type == GDK_BUTTON_PRESS) { GdkWindowEdge edge; - bool shouldStartResizeDrag = !is_maximized && get_window_edge(event->x, event->y, &edge); + bool shouldStartResizeDrag = is_resizable() && !is_maximized && get_window_edge(event->x, event->y, &edge); // Clicking on a window edge starts a move-resize operation. if (shouldStartResizeDrag) { @@ -1515,6 +1523,7 @@ void WindowContextTop::process_mouse_motion(GdkEventMotion* event) { if (is_fullscreen || is_maximized || frame_type != EXTENDED + || !is_resizable() || !get_window_edge(event->x, event->y, &edge)) { set_cursor_override(NULL); WindowContextBase::process_mouse_motion(event); diff --git a/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.h b/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.h index 413e90fcdcf..4db405e1cda 100644 --- a/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.h +++ b/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.h @@ -112,6 +112,7 @@ class WindowContext : public DeletedMemDebug<0xCC> { virtual bool is_visible() = 0; virtual void set_bounds(int, int, bool, bool, int, int, int, int, float, float) = 0; virtual void set_resizable(bool) = 0; + virtual bool is_resizable() = 0; virtual void request_focus() = 0; virtual void set_focusable(bool)= 0; virtual bool grab_focus() = 0; @@ -228,6 +229,7 @@ class WindowContextBase: public WindowContext { void remove_child(WindowContextTop*); void set_visible(bool); bool is_visible(); + bool is_resizable(); bool set_view(jobject); bool grab_focus(); bool grab_mouse_drag_focus(); @@ -303,6 +305,7 @@ class WindowContextTop: public WindowContextBase { void set_maximized(bool); void set_bounds(int, int, bool, bool, int, int, int, int, float, float); void set_resizable(bool); + bool is_resizable(); void request_focus(); void set_focusable(bool); void set_title(const char*); From 63bd0580d6512a8f67959c075453c4c155d33e87 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Sat, 25 Jan 2025 21:01:11 +0100 Subject: [PATCH 36/73] Refactoring, added HeaderBar.leadingSystemPadding/trailingSystemPadding --- .../test/java/test/util/ReflectionUtils.java | 5 +- .../com/sun/glass/ui/NonClientHandler.java | 43 ---- .../src/main/java/com/sun/glass/ui/View.java | 43 +++- .../main/java/com/sun/glass/ui/Window.java | 55 ++--- ...etrics.java => WindowControlsMetrics.java} | 25 +- .../sun/glass/ui/WindowControlsOverlay.java | 45 ++-- .../java/com/sun/glass/ui/gtk/GtkView.java | 20 ++ .../java/com/sun/glass/ui/gtk/GtkWindow.java | 31 +-- .../java/com/sun/glass/ui/mac/MacView.java | 23 ++ .../java/com/sun/glass/ui/mac/MacWindow.java | 46 ++-- .../java/com/sun/glass/ui/win/WinView.java | 22 +- .../java/com/sun/glass/ui/win/WinWindow.java | 25 +- .../sun/javafx/tk/quantum/WindowStage.java | 4 +- .../java/javafx/scene/layout/HeaderBar.java | 222 ++++++++++-------- .../javafx/scene/layout/HeaderBarBase.java | 62 ++--- .../main/java/javafx/stage/StageStyle.java | 6 +- .../glass/ui/WindowControlsOverlayTest.java | 51 ++-- .../javafx/scene/layout/HeaderBarTest.java | 26 +- 18 files changed, 388 insertions(+), 366 deletions(-) delete mode 100644 modules/javafx.graphics/src/main/java/com/sun/glass/ui/NonClientHandler.java rename modules/javafx.graphics/src/main/java/com/sun/glass/ui/{WindowOverlayMetrics.java => WindowControlsMetrics.java} (69%) diff --git a/modules/javafx.base/src/test/java/test/util/ReflectionUtils.java b/modules/javafx.base/src/test/java/test/util/ReflectionUtils.java index 838fb8de323..aa6bda04c37 100644 --- a/modules/javafx.base/src/test/java/test/util/ReflectionUtils.java +++ b/modules/javafx.base/src/test/java/test/util/ReflectionUtils.java @@ -38,7 +38,8 @@ private ReflectionUtils() {} * Returns the value of a potentially private field of the specified object. * The field can be declared on any of the object's inherited classes. */ - public static Object getFieldValue(Object object, String fieldName) { + @SuppressWarnings("unchecked") + public static T getFieldValue(Object object, String fieldName) { Function, Field> getField = cls -> { try { var field = cls.getDeclaredField(fieldName); @@ -54,7 +55,7 @@ public static Object getFieldValue(Object object, String fieldName) { Field field = getField.apply(cls); if (field != null) { try { - return field.get(object); + return (T)field.get(object); } catch (IllegalAccessException e) { throw new AssertionError(e); } diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/NonClientHandler.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/NonClientHandler.java deleted file mode 100644 index d0a1dbf7da5..00000000000 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/NonClientHandler.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package com.sun.glass.ui; - -import javafx.stage.StageStyle; - -/** - * A non-client handler is used in some implementations of windows with the {@link StageStyle#EXTENDED} style. - * It can inspect a mouse event before it is sent to FX, and decide to consume it if it affects - * a non-client part of the window (for example, minimize/maximize/close buttons). - */ -public interface NonClientHandler { - - /** - * Handles the event. - * - * @return {@code true} if the event was handled, {@code false} otherwise - */ - boolean handleMouseEvent(int type, int button, int x, int y, int xAbs, int yAbs, int clickCount); -} diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/View.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/View.java index 6615851fb70..43482442d13 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/View.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/View.java @@ -381,6 +381,21 @@ public Accessible getSceneAccessible() { } } + /** + * A non-client event handler is used in some implementations of windows with the {@link Window#EXTENDED} + * style. It can inspect a mouse event before it is sent to FX, and decide to consume it if it affects a + * non-client part of the window (for example, minimize/maximize/close buttons). + */ + public interface NonClientEventHandler { + + /** + * Handles the event. + * + * @return {@code true} if the event was handled, {@code false} otherwise + */ + boolean handleMouseEvent(int type, int button, int x, int y, int xAbs, int yAbs, int clickCount); + } + public static long getMultiClickTime() { Application.checkEventThread(); return Application.GetApplication().staticView_getMultiClickTime(); @@ -406,8 +421,8 @@ protected void _finishInputMethodComposition(long ptr) { */ private volatile long ptr; // Native handle (NSView*, or internal structure pointer) private Window window; // parent window - private NonClientHandler nonClientHandler; private EventHandler eventHandler; + private NonClientEventHandler nonClientEventHandler; private int width = -1; // not set private int height = -1; // not set @@ -497,6 +512,10 @@ public int getHeight() { return this.height; } + protected NonClientEventHandler createNonClientEventHandler() { + return null; + } + protected abstract void _setParent(long ptr, long parentPtr); // Window calls the method from Window.setView() // package private @@ -508,9 +527,9 @@ void setWindow(Window window) { this.isValid = this.ptr != 0 && window != null; if (this.isValid && window.isExtendedWindow()) { - this.nonClientHandler = window.getNonClientHandler(); + this.nonClientEventHandler = createNonClientEventHandler(); } else { - this.nonClientHandler = null; + this.nonClientEventHandler = null; } } @@ -955,16 +974,16 @@ protected void notifyMouse(int type, int button, int x, int y, int xAbs, lastClickedTime = now; } - // If we have a non-client handler, we give it the first chance to handle the event. - // Note that a full-screen window has no non-client area, and thus the non-client handler + // If we have a non-client event handler, we give it the first chance to handle the event. + // Note that a full-screen window has no non-client area, and thus the non-client event handler // is not notified. - // Some implementations (like GTK) can fire synthesized events when they receive a mouse - // button event on the resize border. These events, even though happening on non-client - // regions, must not be processed by the non-client handler. For example, if a mouse click - // happens on the resize border that straddles the window close button, we don't want the - // close button to act on this click, because we just started a resize-drag operation. - boolean handled = !isSynthesized && !inFullscreen && nonClientHandler != null - && nonClientHandler.handleMouseEvent(type, button, x, y, xAbs, yAbs, clickCount); + // Some implementations (like GTK) can fire synthesized events when they receive a mouse button + // event on the resize border. These events, even though happening on non-client regions, must + // not be processed by the non-client event handler. For example, if a mouse click happens on + // the resize border that straddles the window close button, we don't want the close button to + // act on this click, because we just started a resize-drag operation. + boolean handled = !isSynthesized && !inFullscreen && nonClientEventHandler != null + && nonClientEventHandler.handleMouseEvent(type, button, x, y, xAbs, yAbs, clickCount); // We never send non-client events to the application. if (handled || MouseEvent.isNonClientEvent(type)) { diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java index 6dfdd402c18..a5228cf6fec 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java @@ -27,13 +27,15 @@ import com.sun.glass.events.WindowEvent; import com.sun.prism.impl.PrismSettings; import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.SimpleObjectProperty; -import javafx.beans.value.ObservableValue; import javafx.scene.Parent; import javafx.scene.layout.Region; import javafx.util.Subscription; import java.lang.annotation.Native; +import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.LinkedList; import java.util.List; import java.util.Objects; @@ -236,10 +238,11 @@ public static final class Level { private int maximumWidth = Integer.MAX_VALUE, maximumHeight = Integer.MAX_VALUE; private EventHandler eventHandler; - private Region headerBar; - protected final ObjectProperty windowOverlayMetrics = - new SimpleObjectProperty<>(this, "windowOverlayMetrics"); + private final List headerBars = new ArrayList<>(); + + protected final ObjectProperty windowControlsMetrics = + new SimpleObjectProperty<>(this, "windowControlsMetrics"); protected abstract long _createWindow(long ownerPtr, long screenPtr, int mask); protected Window(Window owner, Screen screen, int styleMask) { @@ -435,59 +438,57 @@ public void setMenuBar(final MenuBar menubar) { * * @return the overlay metrics */ - public ObservableValue getWindowOverlayMetrics() { - return windowOverlayMetrics; + public final ReadOnlyObjectProperty windowControlsMetricsProperty() { + return windowControlsMetrics; } /** - * Returns the window-provided overlay controls, which are rendered above all application content. + * Returns the window-provided non-client overlay, which is rendered above all application content. * * @return the overlay, or {@code null} if the window does not provide an overlay */ - public Parent getWindowOverlay() { - return null; - } - - /** - * Returns the window-provided non-client event handler. - * - * @return the non-client event handler, or {@code null} - */ - public NonClientHandler getNonClientHandler() { + public Parent getNonClientOverlay() { return null; } /** * Registers a user-provided header bar with this window. - *

    - * The registration will be accepted, but ignored if the window is not an extended - * window, or if another header bar is already registered. * * @param headerBar the header bar - * @return a {@code Subscription} to unregister + * @return a {@code Subscription} to unregister, or an empty subscription if this window + * is not an {@link #isExtendedWindow()} */ public final Subscription registerHeaderBar(Region headerBar) { Objects.requireNonNull(headerBar); - if (!isExtendedWindow() || this.headerBar != null) { + if (!isExtendedWindow()) { return Subscription.EMPTY; } - this.headerBar = headerBar; + headerBars.add(headerBar); - Subscription subscription = headerBar.heightProperty().subscribe( - value -> onHeaderBarHeightChanged(value.doubleValue())); + Subscription subscription = headerBar.heightProperty().subscribe(this::updateHeaderBarHeight); return () -> { - this.headerBar = null; + headerBars.remove(headerBar); subscription.unsubscribe(); + updateHeaderBarHeight(); }; } + private void updateHeaderBarHeight() { + double maxHeight = headerBars.stream() + .max(Comparator.comparingDouble(Region::getHeight)) + .map(Region::getHeight) + .orElse(0.0); + + onHeaderBarHeightChanged(maxHeight); + } + /** * Called when the height of a registered user-provided header bar has changed. * - * @param height the height of the header bar + * @param height the maximum height of all registered header bars */ protected void onHeaderBarHeightChanged(double height) {} diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowOverlayMetrics.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsMetrics.java similarity index 69% rename from modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowOverlayMetrics.java rename to modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsMetrics.java index 95b90928476..29c9109bac5 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowOverlayMetrics.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsMetrics.java @@ -26,21 +26,32 @@ package com.sun.glass.ui; import javafx.geometry.Dimension2D; -import javafx.geometry.HorizontalDirection; import javafx.stage.StageStyle; import java.util.Objects; /** * Provides metrics about the window buttons of {@link StageStyle#EXTENDED} windows. * - * @param placement the placement of the window buttons - * @param size the size of the window buttons + * @param leftInset the size of the left inset + * @param rightInset the size of the right inset * @param minHeight the minimum height of the window buttons */ -public record WindowOverlayMetrics(HorizontalDirection placement, Dimension2D size, double minHeight) { +public record WindowControlsMetrics(Dimension2D leftInset, Dimension2D rightInset, double minHeight) { - public WindowOverlayMetrics { - Objects.requireNonNull(placement, "placement cannot be null"); - Objects.requireNonNull(size, "size cannot be null"); + public WindowControlsMetrics { + Objects.requireNonNull(leftInset); + Objects.requireNonNull(rightInset); + + if (minHeight < 0) { + throw new IllegalArgumentException("minHeight cannot be negative"); + } + } + + public double totalInsetWidth() { + return leftInset.getWidth() + rightInset.getWidth(); + } + + public double maxInsetHeight() { + return Math.max(leftInset.getHeight(), rightInset.getHeight()); } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java index 0ab50e2bc66..9df9c4da506 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java @@ -45,9 +45,7 @@ import javafx.css.StyleableProperty; import javafx.geometry.Dimension2D; import javafx.geometry.HPos; -import javafx.geometry.HorizontalDirection; import javafx.geometry.Insets; -import javafx.geometry.NodeOrientation; import javafx.geometry.VPos; import javafx.scene.Node; import javafx.scene.Scene; @@ -131,17 +129,17 @@ */ public final class WindowControlsOverlay extends Region { - private static final CssMetaData BUTTON_PLACEMENT_METADATA = + private static final CssMetaData BUTTON_PLACEMENT_METADATA = new CssMetaData<>("-fx-button-placement", - StyleConverter.getEnumConverter(HorizontalDirection.class), - HorizontalDirection.RIGHT) { + StyleConverter.getEnumConverter(ButtonPlacement.class), + ButtonPlacement.RIGHT) { @Override public boolean isSettable(WindowControlsOverlay overlay) { return true; } @Override - public StyleableProperty getStyleableProperty(WindowControlsOverlay overlay) { + public StyleableProperty getStyleableProperty(WindowControlsOverlay overlay) { return overlay.buttonPlacement; } }; @@ -173,17 +171,17 @@ public StyleableProperty getStyleableProperty(WindowControlsOverlay ove /** * The metrics (placement and size) of the window buttons. */ - private final ObjectProperty metrics = new SimpleObjectProperty<>( - this, "metrics", new WindowOverlayMetrics(HorizontalDirection.RIGHT, new Dimension2D(0, 0), 0)); + private final ObjectProperty metrics = new SimpleObjectProperty<>( + this, "metrics", new WindowControlsMetrics(new Dimension2D(0, 0), new Dimension2D(0, 0), 0)); /** * Specifies the placement of the window buttons on the left or the right side of the window. *

    * This property corresponds to the {@code -fx-button-placement} CSS property. */ - private final StyleableObjectProperty buttonPlacement = + private final StyleableObjectProperty buttonPlacement = new SimpleStyleableObjectProperty<>( - BUTTON_PLACEMENT_METADATA, this, "buttonPlacement", HorizontalDirection.RIGHT) { + BUTTON_PLACEMENT_METADATA, this, "buttonPlacement", ButtonPlacement.RIGHT) { @Override protected void invalidated() { requestLayout(); @@ -216,11 +214,13 @@ protected void invalidated() { private final ButtonRegion closeButton = new ButtonRegion(ButtonType.CLOSE, "close-button", 2); private final Subscription subscriptions; private final boolean utility; + private final boolean rightToLeft; private Node buttonAtMouseDown; - public WindowControlsOverlay(ObservableValue stylesheet, boolean utility) { + public WindowControlsOverlay(ObservableValue stylesheet, boolean utility, boolean rightToLeft) { this.utility = utility; + this.rightToLeft = rightToLeft; var stage = sceneProperty() .flatMap(Scene::windowProperty) @@ -245,7 +245,7 @@ public WindowControlsOverlay(ObservableValue stylesheet, boolean utility .flatMap(Scene::fillProperty) .map(this::isDarkBackground) .orElse(false) - .subscribe(x -> updateStyleClass()); // use a value subscriber, not an invalidation subscriber + .subscribe(_ -> updateStyleClass()); // use a value subscriber, not an invalidation subscriber subscriptions = Subscription.combine( focusedSubscription, @@ -270,7 +270,7 @@ public void dispose() { subscriptions.unsubscribe(); } - public ReadOnlyObjectProperty metricsProperty() { + public ReadOnlyObjectProperty metricsProperty() { return metrics; } @@ -416,16 +416,16 @@ protected void layoutChildren() { boolean left; Node button1, button2, button3; - if (allowRtl.get() && getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT) { + if (allowRtl.get() && rightToLeft) { button1 = orderedButtons.get(2); button2 = orderedButtons.get(1); button3 = orderedButtons.get(0); - left = buttonPlacement.get() != HorizontalDirection.LEFT; + left = buttonPlacement.get() != ButtonPlacement.LEFT; } else { button1 = orderedButtons.get(0); button2 = orderedButtons.get(1); button3 = orderedButtons.get(2); - left = buttonPlacement.get() == HorizontalDirection.LEFT; + left = buttonPlacement.get() == ButtonPlacement.LEFT; } double width = getWidth(); @@ -440,14 +440,13 @@ protected void layoutChildren() { double button3X = snapPositionX(left ? button1Width + button2Width : width - button3Width); double totalWidth = snapSizeX(button1Width + button2Width + button3Width); double totalHeight = snapSizeY(Math.max(button1Height, Math.max(button2Height, button3Height))); - Dimension2D currentSize = metrics.get().size(); + Dimension2D currentSize = left ? metrics.get().leftInset() : metrics.get().rightInset(); // Update the overlay metrics if they have changed. if (currentSize.getWidth() != totalWidth || currentSize.getHeight() != totalHeight) { - var newMetrics = new WindowOverlayMetrics( - left ? HorizontalDirection.LEFT : HorizontalDirection.RIGHT, - new Dimension2D(totalWidth, totalHeight), totalHeight); - + WindowControlsMetrics newMetrics = left + ? new WindowControlsMetrics(new Dimension2D(totalWidth, totalHeight), new Dimension2D(0, 0), totalHeight) + : new WindowControlsMetrics(new Dimension2D(0, 0), new Dimension2D(totalWidth, totalHeight), totalHeight); metrics.set(newMetrics); } @@ -554,4 +553,8 @@ protected void layoutChildren() { return METADATA; } } + + private enum ButtonPlacement { + LEFT, RIGHT + } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkView.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkView.java index 12ff513efc0..6c2c84d5c79 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkView.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkView.java @@ -24,6 +24,7 @@ */ package com.sun.glass.ui.gtk; +import com.sun.glass.events.MouseEvent; import com.sun.glass.ui.Pixels; import com.sun.glass.ui.View; import java.nio.Buffer; @@ -237,4 +238,23 @@ protected void notifyMenu(int x, int y, int xAbs, int yAbs, boolean isKeyboardTr } } } + + @Override + protected NonClientEventHandler createNonClientEventHandler() { + var window = (GtkWindow)getWindow(); + var overlay = window.getNonClientOverlay(); + if (overlay == null) { + return null; + } + + return (type, button, x, y, xAbs, yAbs, clickCount) -> { + // In contrast to Windows, GTK doesn't produce non-client events. We convert regular + // mouse events to non-client events since that's what WindowControlsOverlay expects. + return overlay.handleMouseEvent( + MouseEvent.toNonClientEvent(type), + button, + x / window.getPlatformScaleX(), + y / window.getPlatformScaleY()); + }; + } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java index 17d165d1f86..de6c516ee0b 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java @@ -24,10 +24,8 @@ */ package com.sun.glass.ui.gtk; -import com.sun.glass.events.MouseEvent; import com.sun.glass.ui.Cursor; import com.sun.glass.events.WindowEvent; -import com.sun.glass.ui.NonClientHandler; import com.sun.glass.ui.Pixels; import com.sun.glass.ui.Screen; import com.sun.glass.ui.View; @@ -209,40 +207,27 @@ public long getRawHandle() { private WindowControlsOverlay windowControlsOverlay; @Override - public WindowControlsOverlay getWindowOverlay() { + public WindowControlsOverlay getNonClientOverlay() { if (windowControlsOverlay == null && isExtendedWindow()) { windowControlsOverlay = new WindowControlsOverlay( - PlatformThemeObserver.getInstance().stylesheetProperty(), isUtilityWindow()); + PlatformThemeObserver.getInstance().stylesheetProperty(), + isUtilityWindow(), + (getStyleMask() & RIGHT_TO_LEFT) != 0); // Set the system-defined absolute minimum size to the size of the window buttons area, // regardless of whether the application has specified a smaller minimum size. - windowControlsOverlay.metricsProperty().addListener((_, _, metrics) -> { - int width = (int)(metrics.size().getWidth() * platformScaleX); - int height = (int)(metrics.size().getHeight() * platformScaleY); + windowControlsOverlay.metricsProperty().subscribe(metrics -> { + int width = (int)(metrics.totalInsetWidth() * platformScaleX); + int height = (int)(metrics.maxInsetHeight() * platformScaleY); _setSystemMinimumSize(super.getRawHandle(), width, height); }); - windowOverlayMetrics.bind(windowControlsOverlay.metricsProperty()); + windowControlsMetrics.bind(windowControlsOverlay.metricsProperty()); } return windowControlsOverlay; } - @Override - public NonClientHandler getNonClientHandler() { - var overlay = getWindowOverlay(); - if (overlay == null) { - return null; - } - - return (type, button, x, y, xAbs, yAbs, clickCount) -> { - // In contrast to Windows, GTK doesn't produce non-client events. We convert regular - // mouse events to non-client events since that's what WindowControlsOverlay expects. - return overlay.handleMouseEvent( - MouseEvent.toNonClientEvent(type), button, x / platformScaleX, y / platformScaleY); - }; - } - /** * Opens a system menu at the specified coordinates. * diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacView.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacView.java index 84cb95dcb98..23c83c83593 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacView.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacView.java @@ -24,6 +24,7 @@ */ package com.sun.glass.ui.mac; +import com.sun.glass.events.MouseEvent; import com.sun.glass.ui.Pixels; import com.sun.glass.ui.View; import com.sun.glass.ui.Window; @@ -182,5 +183,27 @@ protected void notifyInputMethodMac(String str, int attrib, int length, } } } + + @Override + protected NonClientEventHandler createNonClientEventHandler() { + return (type, button, x, y, xAbs, yAbs, clickCount) -> { + if (type == MouseEvent.DOWN) { + var window = (MacWindow)getWindow(); + double wx = x / window.getPlatformScaleX(); + double wy = y / window.getPlatformScaleY(); + + View.EventHandler eventHandler = getEventHandler(); + if (eventHandler != null && eventHandler.pickDragAreaNode(wx, wy) != null) { + if (clickCount == 2) { + window.performTitleBarDoubleClickAction(); + } else if (clickCount == 1) { + window.performWindowDrag(); + } + } + } + + return false; + }; + } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java index 94eaa640957..9a78089e17b 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java @@ -24,17 +24,14 @@ */ package com.sun.glass.ui.mac; -import com.sun.glass.events.MouseEvent; import com.sun.glass.events.WindowEvent; import com.sun.glass.ui.Cursor; -import com.sun.glass.ui.NonClientHandler; import com.sun.glass.ui.Pixels; import com.sun.glass.ui.Screen; import com.sun.glass.ui.View; import com.sun.glass.ui.Window; -import com.sun.glass.ui.WindowOverlayMetrics; +import com.sun.glass.ui.WindowControlsMetrics; import javafx.geometry.Dimension2D; -import javafx.geometry.HorizontalDirection; import java.nio.ByteBuffer; /** @@ -161,31 +158,18 @@ protected void _releaseInput(long ptr) { throw new UnsupportedOperationException("Not supported yet."); } + public void performWindowDrag() { + _performWindowDrag(getRawHandle()); + } + + public void performTitleBarDoubleClickAction() { + _performTitleBarDoubleClickAction(getRawHandle()); + } + private native void _performWindowDrag(long ptr); private native void _performTitleBarDoubleClickAction(long ptr); - @Override - public NonClientHandler getNonClientHandler() { - return (type, button, x, y, xAbs, yAbs, clickCount) -> { - if (type == MouseEvent.DOWN) { - double wx = x / platformScaleX; - double wy = y / platformScaleY; - - View.EventHandler eventHandler = view != null ? view.getEventHandler() : null; - if (eventHandler != null && eventHandler.pickDragAreaNode(wx, wy) != null) { - if (clickCount == 2) { - _performTitleBarDoubleClickAction(getRawHandle()); - } else if (clickCount == 1) { - _performWindowDrag(getRawHandle()); - } - } - } - - return false; - }; - } - private native boolean _isRightToLeftLayoutDirection(); private native void _setToolbarStyle(long ptr, int style); @@ -198,12 +182,12 @@ protected void onHeaderBarHeightChanged(double height) { } private void updateWindowOverlayMetrics(NSWindowToolbarStyle toolbarStyle) { - windowOverlayMetrics.set(new WindowOverlayMetrics( - _isRightToLeftLayoutDirection() - ? HorizontalDirection.RIGHT - : HorizontalDirection.LEFT, - toolbarStyle.size, - NSWindowToolbarStyle.SMALL.size.getHeight())); + double minHeight = NSWindowToolbarStyle.SMALL.size.getHeight(); + WindowControlsMetrics metrics = _isRightToLeftLayoutDirection() + ? new WindowControlsMetrics(new Dimension2D(0, 0), toolbarStyle.size, minHeight) + : new WindowControlsMetrics(toolbarStyle.size, new Dimension2D(0, 0), minHeight); + + windowControlsMetrics.set(metrics); } private enum NSWindowToolbarStyle { diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinView.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinView.java index c8b7af84cb8..5c90d66ac19 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinView.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinView.java @@ -117,5 +117,25 @@ protected void notifyMenu(int x, int y, int xAbs, int yAbs, boolean isKeyboardTr } } } -} + @Override + protected NonClientEventHandler createNonClientEventHandler() { + var window = (WinWindow)getWindow(); + if (!window.isExtendedWindow()) { + return null; + } + + var overlay = window.getNonClientOverlay(); + if (overlay == null) { + return null; + } + + return (type, button, x, y, xAbs, yAbs, clickCount) -> { + double wx = x / window.getPlatformScaleX(); + double wy = y / window.getPlatformScaleY(); + + // Give the window button overlay the first chance to handle the event. + return overlay.handleMouseEvent(type, button, wx, wy); + }; + } +} diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java index 64dba9878b1..4e841785ef3 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java @@ -26,7 +26,6 @@ import com.sun.glass.ui.Cursor; import com.sun.glass.ui.WindowControlsOverlay; -import com.sun.glass.ui.NonClientHandler; import com.sun.glass.ui.Pixels; import com.sun.glass.ui.Screen; import com.sun.glass.ui.View; @@ -339,7 +338,7 @@ void setDeferredClosing(boolean dc) { private WindowControlsOverlay windowControlsOverlay; @Override - public WindowControlsOverlay getWindowOverlay() { + public WindowControlsOverlay getNonClientOverlay() { if (windowControlsOverlay == null && isExtendedWindow()) { var url = getClass().getResource(WINDOW_DECORATION_STYLESHEET); if (url == null) { @@ -347,30 +346,16 @@ public WindowControlsOverlay getWindowOverlay() { } windowControlsOverlay = new WindowControlsOverlay( - StringConstant.valueOf(url.toExternalForm()), isUtilityWindow()); + StringConstant.valueOf(url.toExternalForm()), + isUtilityWindow(), + (getStyleMask() & RIGHT_TO_LEFT) != 0); - windowOverlayMetrics.bind(windowControlsOverlay.metricsProperty()); + windowControlsMetrics.bind(windowControlsOverlay.metricsProperty()); } return windowControlsOverlay; } - @Override - public NonClientHandler getNonClientHandler() { - var overlay = getWindowOverlay(); - if (overlay == null) { - return null; - } - - return (type, button, x, y, xAbs, yAbs, clickCount) -> { - double wx = x / platformScaleX; - double wy = y / platformScaleY; - - // Give the window button overlay the first chance to handle the event. - return overlay.handleMouseEvent(type, button, wx, wy); - }; - } - /** * Opens a system menu at the specified coordinates. * diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/WindowStage.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/WindowStage.java index 2b6a7175f4c..b9433801ca1 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/WindowStage.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/WindowStage.java @@ -239,7 +239,7 @@ StageStyle getStyle() { // The window-provided overlay is not visible in full-screen mode. if (!isInFullScreen) { - scene.setOverlay(platformWindow.getWindowOverlay()); + scene.setOverlay(platformWindow.getNonClientOverlay()); } return scene; @@ -667,7 +667,7 @@ void setWarning(OverlayWarning newWarning) { if (newWarning != null) { getViewScene().setOverlay(newWarning); } else if (!isInFullScreen) { - getViewScene().setOverlay(platformWindow.getWindowOverlay()); + getViewScene().setOverlay(platformWindow.getNonClientOverlay()); } } diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java index f2d2d4ce16e..05b0a9ae389 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java @@ -27,12 +27,9 @@ import com.sun.javafx.geom.Vec2d; import javafx.beans.property.BooleanProperty; +import javafx.beans.property.BooleanPropertyBase; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectPropertyBase; -import javafx.css.CssMetaData; -import javafx.css.StyleConverter; -import javafx.css.Styleable; -import javafx.css.StyleableBooleanProperty; import javafx.css.StyleableDoubleProperty; import javafx.geometry.HPos; import javafx.geometry.Insets; @@ -43,8 +40,6 @@ import javafx.scene.Node; import javafx.scene.input.ContextMenuEvent; import javafx.stage.StageStyle; -import java.util.List; -import java.util.stream.Stream; /** * A client-area header bar that is used as a replacement for the system-provided header bar in stages @@ -53,28 +48,44 @@ * The entire {@code HeaderBar} background is draggable by default, but its content is not. Applications * can specify draggable content nodes of the {@code HeaderBar} with the {@link #setDraggable} method. *

    - * Some platforms support a system menu that can be summoned by right-clicking the draggable area. - * This platform-provided menu will only be shown if the {@link ContextMenuEvent#CONTEXT_MENU_REQUESTED} - * event that is targeted at the header bar is not consumed by the application. - *

    * {@code HeaderBar} is a layout container that allows applications to place scene graph nodes in three areas: * {@link #leadingProperty() leading}, {@link #centerProperty() center}, and {@link #trailingProperty() trailing}. - * All areas can be {@code null}. The {@link #overlappingSystemInsetProperty() overlappingSystemInset} property - * controls whether the leading and trailing areas account for the default window buttons (minimize, maximize, - * close). If a child is configured to be centered in the {@code center} area, it is laid out with respect to the - * stage, and not with respect to the {@code center} area. This ensures that the child will appear centered in the - * stage regardless of leading or trailing children or the platform-specific placement of default window buttons. + * All areas can be {@code null}. + * If a child is configured to be centered in the {@code center} area, it is laid out with respect to the + * entire header bar, and not with respect to the {@code center} area. This ensures that the child will appear + * centered in the header bar regardless of leading or trailing children or the platform-specific placement of + * default window buttons. *

    + * The default {@link #minHeightProperty() minHeight} of the {@code HeaderBar} is set to match the height of the + * platform-specific default window buttons. + * + *

    Single header bar

    + * Most applications should only add a single {@code HeaderBar} to the scene graph, placed at the top of the + * scene and extending its entire width. This ensures that the reported values for + * {@link #leftSystemInsetProperty() leftSystemInset} and {@link #rightSystemInsetProperty() rightSystemInset}, + * which describe the area reserved for the system-provided window buttons, correctly align with the location + * of the {@code HeaderBar} and are taken into account when the contents of the {@code HeaderBar} are laid out. + * + *

    Multiple header bars

    + * Applications that use multiple header bars might need to configure the additional padding inserted into the + * layout to account for the system-reserved areas. For example, when two header bars are placed next to each + * other in the horizontal direction, the default configuration incorrectly adds additional padding between the + * two header bars. In this case, the {@link #leadingSystemPaddingProperty() leadingSystemPadding} and + * {@link #trailingSystemPaddingProperty() trailingSystemPadding} properties can be used to remove the padding + * that is not needed. + * + *

    System menu

    + * Some platforms support a system menu that can be summoned by right-clicking the draggable area. + * This platform-provided menu will only be shown if the {@link ContextMenuEvent#CONTEXT_MENU_REQUESTED} + * event that is targeted at the header bar is not consumed by the application. + * + *

    Layout constraints

    * All children will be resized to their preferred widths and extend the height of the {@code HeaderBar}. * {@code HeaderBar} honors the minimum, preferred, and maximum sizes of its children. As a consequence, its * computed minimum size is sufficient to accommodate all of its children. If a child's resizable range prevents * it from be resized to fit within its position, it will be vertically centered relative to the available space; * this alignment can be customized with a layout constraint. *

    - * The default {@link #minHeightProperty() minHeight} of the {@code HeaderBar} is set to match the height of the - * platform-specific default window buttons. - * - *

    Layout constraints

    * An application may set constraints on individual children to customize their layout. * For each constraint, {@code HeaderBar} provides static getter and setter methods. * @@ -116,14 +127,8 @@ * } * } * - * @apiNote An application should only add a single {@code HeaderBar} to the scene graph, and it should - * be located at the top of the scene. While it is technically possible to add multiple header - * bars to the scene graph, or place a header bar in another area of the scene, the resulting - * user experience is not what users typically expect from JavaFX applications and should - * therefore be avoided. - * * @see HeaderBarBase - * @since 24 + * @since 25 */ public class HeaderBar extends HeaderBarBase { @@ -201,8 +206,10 @@ public HeaderBar(Node leading, Node center, Node trailing) { /** * The leading area of the {@code HeaderBar}. *

    - * Usually, this corresponds to the left side of the header bar; with right-to-left orientation, - * this corresponds to the right side of the header bar. + * The leading area corresponds to the left area in a left-to-right layout, and to the right area + * in a right-to-left layout. + * + * @defaultValue {@code null} */ private final ObjectProperty leading = new NodeProperty("leading"); @@ -220,6 +227,8 @@ public final void setLeading(Node value) { /** * The center area of the {@code HeaderBar}. + * + * @defaultValue {@code null} */ private final ObjectProperty center = new NodeProperty("center"); @@ -238,8 +247,10 @@ public final void setCenter(Node value) { /** * The trailing area of the {@code HeaderBar}. *

    - * Usually, this corresponds to the right side of the header bar; with right-to-left orientation, - * this corresponds to the left side of the header bar. + * The trailing area corresponds to the right area in a left-to-right layout, and to the left area + * in a right-to-left layout. + * + * @defaultValue {@code null} */ private final ObjectProperty trailing = new NodeProperty("trailing"); @@ -256,10 +267,19 @@ public final void setTrailing(Node value) { } /** - * Specifies whether the system-provided window buttons overlap the content of this {@code HeaderBar}, - * or whether they are laid out next to the content of the header bar, taking up space in its layout. + * Specifies whether additional padding should be added to the leading side of the {@code HeaderBar}. + * The size of the additional padding corresponds to the size of the system-reserved area that contains + * the default window buttons (minimize, maximize, and close). If the system-reserved area contains no + * window buttons, no additional padding is added to the leading side of the {@code HeaderBar}. + *

    + * Applications that use a single {@code HeaderBar} extending the entire width of the window should + * set this property to {@code true} to prevent the window buttons from overlapping the content of the + * {@code HeaderBar}. + * + * @defaultValue {@code true} + * @see #trailingSystemPaddingProperty() trailingSystemPadding */ - private final StyleableBooleanProperty overlappingSystemInset = new StyleableBooleanProperty() { + private final BooleanProperty leadingSystemPadding = new BooleanPropertyBase(true) { @Override public Object getBean() { return HeaderBar.this; @@ -267,30 +287,77 @@ public Object getBean() { @Override public String getName() { - return "systemInset"; + return "leadingSystemPadding"; } @Override protected void invalidated() { requestLayout(); } + }; + + public final BooleanProperty leadingSystemPaddingProperty() { + return leadingSystemPadding; + } + + public final boolean getLeadingSystemPadding() { + return leadingSystemPadding.get(); + } + + public final void setLeadingSystemPadding(boolean value) { + leadingSystemPadding.set(value); + } + /** + * Specifies whether additional padding should be added to the trailing side of the {@code HeaderBar}. + * The size of the additional padding corresponds to the size of the system-reserved area that contains + * the default window buttons (minimize, maximize, and close). If the system-reserved area contains no + * window buttons, no additional padding is added to the trailing side of the {@code HeaderBar}. + *

    + * Applications that use a single {@code HeaderBar} extending the entire width of the window should + * set this property to {@code true} to prevent the window buttons from overlapping the content of the + * {@code HeaderBar}. + * + * @defaultValue {@code true} + * @see #leadingSystemPaddingProperty() leadingSystemPadding + */ + private final BooleanProperty trailingSystemPadding = new BooleanPropertyBase(true) { @Override - public CssMetaData getCssMetaData() { - return OVERLAPPING_SYSTEM_INSET; + public Object getBean() { + return HeaderBar.this; + } + + @Override + public String getName() { + return "trailingSystemPadding"; + } + + @Override + protected void invalidated() { + requestLayout(); } }; - public final BooleanProperty overlappingSystemInsetProperty() { - return overlappingSystemInset; + public final BooleanProperty trailingSystemPaddingProperty() { + return trailingSystemPadding; } - public final boolean isOverlappingSystemInset() { - return overlappingSystemInset.get(); + public final boolean getTrailingSystemPadding() { + return trailingSystemPadding.get(); } - public final void setOverlappingSystemInset(boolean value) { - overlappingSystemInset.set(value); + public final void setTrailingSystemPadding(boolean value) { + trailingSystemPadding.set(value); + } + + private boolean isLeftSystemPadding(NodeOrientation nodeOrientation) { + return nodeOrientation == NodeOrientation.LEFT_TO_RIGHT && getLeadingSystemPadding() + || nodeOrientation == NodeOrientation.RIGHT_TO_LEFT && getTrailingSystemPadding(); + } + + private boolean isRightSystemPadding(NodeOrientation nodeOrientation) { + return nodeOrientation == NodeOrientation.LEFT_TO_RIGHT && getTrailingSystemPadding() + || nodeOrientation == NodeOrientation.RIGHT_TO_LEFT && getLeadingSystemPadding(); } @Override @@ -302,7 +369,7 @@ protected double computeMinWidth(double height) { double leftPrefWidth; double rightPrefWidth; double centerMinWidth; - double systemInsetWidth; + double systemPaddingWidth = 0; if (height != -1 && (childHasContentBias(leading, Orientation.VERTICAL) || @@ -318,10 +385,14 @@ protected double computeMinWidth(double height) { centerMinWidth = getAreaWidth(center, -1, true); } - if (isOverlappingSystemInset()) { - systemInsetWidth = 0; - } else { - systemInsetWidth = getLeftSystemInset().getWidth() + getRightSystemInset().getWidth(); + NodeOrientation nodeOrientation = getEffectiveNodeOrientation(); + + if (isLeftSystemPadding(nodeOrientation)) { + systemPaddingWidth += getLeftSystemInset().getWidth(); + } + + if (isRightSystemPadding(nodeOrientation)) { + systemPaddingWidth += getRightSystemInset().getWidth(); } return insets.getLeft() @@ -329,7 +400,7 @@ protected double computeMinWidth(double height) { + centerMinWidth + rightPrefWidth + insets.getRight() - + systemInsetWidth; + + systemPaddingWidth; } @Override @@ -388,7 +459,8 @@ protected void layoutChildren() { Node center = getCenter(); Node left, right; Insets insets = getInsets(); - boolean rtl = getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT; + NodeOrientation nodeOrientation = getEffectiveNodeOrientation(); + boolean rtl = nodeOrientation == NodeOrientation.RIGHT_TO_LEFT; double width = Math.max(getWidth(), minWidth(-1)); double height = Math.max(getHeight(), minHeight(-1)); double leftWidth = 0; @@ -396,25 +468,19 @@ protected void layoutChildren() { double insideY = insets.getTop(); double insideHeight = height - insideY - insets.getBottom(); double insideX, insideWidth; - double leftSystemInsetWidth, rightSystemInsetWidth; - - if (isOverlappingSystemInset()) { - leftSystemInsetWidth = rightSystemInsetWidth = 0; - } else { - leftSystemInsetWidth = getLeftSystemInset().getWidth(); - rightSystemInsetWidth = getRightSystemInset().getWidth(); - } + double leftSystemPaddingWidth = isLeftSystemPadding(nodeOrientation) ? getLeftSystemInset().getWidth() : 0; + double rightSystemPaddingWidth = isRightSystemPadding(nodeOrientation) ? getRightSystemInset().getWidth() : 0; if (rtl) { left = getTrailing(); right = getLeading(); - insideX = insets.getRight() + leftSystemInsetWidth; - insideWidth = width - insideX - insets.getLeft() - rightSystemInsetWidth; + insideX = insets.getRight() + leftSystemPaddingWidth; + insideWidth = width - insideX - insets.getLeft() - rightSystemPaddingWidth; } else { left = getLeading(); right = getTrailing(); - insideX = insets.getLeft() + leftSystemInsetWidth; - insideWidth = width - insideX - insets.getRight() - rightSystemInsetWidth; + insideX = insets.getLeft() + leftSystemPaddingWidth; + insideWidth = width - insideX - insets.getRight() - rightSystemPaddingWidth; } if (left != null && left.isManaged()) { @@ -584,38 +650,4 @@ protected void invalidated() { } } } - - private static final CssMetaData OVERLAPPING_SYSTEM_INSET = new CssMetaData<>( - "-fx-overlapping-system-inset", StyleConverter.getBooleanConverter(), false) { - @Override - public boolean isSettable(HeaderBar headerBar) { - return headerBar == null || !headerBar.overlappingSystemInset.isBound(); - } - - @Override - public StyleableBooleanProperty getStyleableProperty(HeaderBar headerBar) { - return headerBar.overlappingSystemInset; - } - }; - - private static final List> METADATA = - Stream.concat( - Region.getClassCssMetaData().stream(), - Stream.of(OVERLAPPING_SYSTEM_INSET)) - .toList(); - - @Override - public List> getCssMetaData() { - return METADATA; - } - - /** - * Gets the {@code CssMetaData} associated with this class, which includes the - * {@code CssMetaData} of its superclasses. - * - * @return the {@code CssMetaData} - */ - public static List> getClassCssMetaData() { - return METADATA; - } } diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java index 57922622ca0..6ecd85dec60 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java @@ -25,14 +25,14 @@ package javafx.scene.layout; -import com.sun.glass.ui.WindowOverlayMetrics; +import com.sun.glass.ui.WindowControlsMetrics; import com.sun.javafx.stage.StageHelper; import com.sun.javafx.tk.quantum.WindowStage; import javafx.beans.property.ReadOnlyDoubleProperty; import javafx.beans.property.ReadOnlyDoubleWrapper; +import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.geometry.Dimension2D; -import javafx.geometry.HorizontalDirection; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.input.ContextMenuEvent; @@ -56,7 +56,7 @@ * @apiNote Most application developers should use the {@link HeaderBar} implementation instead of * creating a custom header bar. * @see HeaderBar - * @since 24 + * @since 25 */ public abstract class HeaderBarBase extends Region { @@ -90,7 +90,7 @@ public static Boolean isDraggable(Node child) { } private Subscription subscription; - private WindowOverlayMetrics currentMetrics; + private WindowControlsMetrics currentMetrics; private boolean currentFullScreen; /** @@ -121,7 +121,7 @@ private void onShowingChanged(boolean showing) { subscription = Subscription.combine( windowStage .getPlatformWindow() - .getWindowOverlayMetrics() + .windowControlsMetricsProperty() .subscribe(this::onMetricsChanged), windowStage .getPlatformWindow() @@ -129,7 +129,7 @@ private void onShowingChanged(boolean showing) { } } - private void onMetricsChanged(WindowOverlayMetrics metrics) { + private void onMetricsChanged(WindowControlsMetrics metrics) { currentMetrics = metrics; updateInsets(); } @@ -144,41 +144,30 @@ private void updateInsets() { leftSystemInset.set(EMPTY); rightSystemInset.set(EMPTY); minSystemHeight.set(0); - return; - } - - if (currentMetrics.placement() == HorizontalDirection.LEFT) { - leftSystemInset.set(currentMetrics.size()); - rightSystemInset.set(EMPTY); - } else if (currentMetrics.placement() == HorizontalDirection.RIGHT) { - leftSystemInset.set(EMPTY); - rightSystemInset.set(currentMetrics.size()); } else { - leftSystemInset.set(EMPTY); - rightSystemInset.set(EMPTY); + leftSystemInset.set(currentMetrics.leftInset()); + rightSystemInset.set(currentMetrics.rightInset()); + minSystemHeight.set(currentMetrics.minHeight()); } - - minSystemHeight.set(currentMetrics.minHeight()); } /** - * Describes the size of the left system inset, which is an area reserved for the - * minimize, maximize, and close window buttons. If there are no window buttons on - * the left side of the window, the returned area is an empty {@code Dimension2D}. + * Describes the size of the left system-reserved inset, which is an area reserved for the minimize, maximize, + * and close window buttons. If there are no window buttons on the left side of the window, the returned area + * is an empty {@code Dimension2D}. *

    - * Note that the left system inset refers to the physical left side of the window, - * independent of layout orientation. + * Note that the left system inset refers to the left side of the window, independent of layout orientation. */ private final ReadOnlyObjectWrapper leftSystemInset = - new ReadOnlyObjectWrapper<>(this, "leftInset", new Dimension2D(0, 0)) { + new ReadOnlyObjectWrapper<>(this, "leftSystemInset", EMPTY) { @Override protected void invalidated() { requestLayout(); } }; - public final ReadOnlyObjectWrapper leftSystemInsetProperty() { - return leftSystemInset; + public final ReadOnlyObjectProperty leftSystemInsetProperty() { + return leftSystemInset.getReadOnlyProperty(); } public final Dimension2D getLeftSystemInset() { @@ -186,23 +175,22 @@ public final Dimension2D getLeftSystemInset() { } /** - * Describes the size of the right system inset, which is an area reserved for the - * minimize, maximize, and close window buttons. If there are no window buttons on - * the right side of the window, the returned area is an empty {@code Dimension2D}. + * Describes the size of the right system-reserved inset, which is an area reserved for the minimize, maximize, + * and close window buttons. If there are no window buttons on the right side of the window, the returned area + * is an empty {@code Dimension2D}. *

    - * Note that the right system inset refers to the physical right side of the window, - * independent of layout orientation. + * Note that the right system inset refers to the right side of the window, independent of layout orientation. */ private final ReadOnlyObjectWrapper rightSystemInset = - new ReadOnlyObjectWrapper<>(this, "rightInset", EMPTY) { + new ReadOnlyObjectWrapper<>(this, "rightSystemInset", EMPTY) { @Override protected void invalidated() { requestLayout(); } }; - public final ReadOnlyObjectWrapper rightSystemInsetProperty() { - return rightSystemInset; + public final ReadOnlyObjectProperty rightSystemInsetProperty() { + return rightSystemInset.getReadOnlyProperty(); } public final Dimension2D getRightSystemInset() { @@ -211,8 +199,8 @@ public final Dimension2D getRightSystemInset() { /** * The system-provided reasonable minimum height of {@link #leftSystemInsetProperty() leftSystemInset} - * {@link #rightSystemInsetProperty() rightSystemInset}. This is a platform-dependent value that a - * {@code HeaderBarBase} implementation can use to define a reasonable minimum height for the header + * and {@link #rightSystemInsetProperty() rightSystemInset}. This is a platform-dependent value that + * a {@code HeaderBarBase} implementation can use to choose a reasonable minimum height for the header * bar area. */ private final ReadOnlyDoubleWrapper minSystemHeight = diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java b/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java index a00e3ed71f0..cc79c5f4339 100644 --- a/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java +++ b/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java @@ -80,11 +80,11 @@ public enum StageStyle { * bar area of the stage. *

    * An extended window has the default window buttons (minimize, maximize, close), but no system-provided - * draggable header bar. Applications need to provide their own header bar by placing a single {@link HeaderBar} + * draggable header bar. Applications need to provide their own header bar by placing a {@link HeaderBar} * control in the scene graph. The {@code HeaderBar} control should be positioned at the top of the window * and its width should extend the entire width of the window, as otherwise the layout of the default window - * buttons and the header bar content might not be aligned. Usually, {@code HeaderBar} is combined with a - * {@link BorderPane} root container: + * buttons and the header bar content might not be aligned correctly. Usually, {@code HeaderBar} is combined + * with a {@link BorderPane} root container: *

    {@code
          * public class MyApp extends Application {
          *     @Override
    diff --git a/modules/javafx.graphics/src/test/java/test/com/sun/glass/ui/WindowControlsOverlayTest.java b/modules/javafx.graphics/src/test/java/test/com/sun/glass/ui/WindowControlsOverlayTest.java
    index 533b819b986..37cb7a54e44 100644
    --- a/modules/javafx.graphics/src/test/java/test/com/sun/glass/ui/WindowControlsOverlayTest.java
    +++ b/modules/javafx.graphics/src/test/java/test/com/sun/glass/ui/WindowControlsOverlayTest.java
    @@ -29,7 +29,6 @@
     import com.sun.javafx.binding.ObjectConstant;
     import javafx.beans.value.ObservableValue;
     import javafx.geometry.Dimension2D;
    -import javafx.geometry.HorizontalDirection;
     import javafx.geometry.NodeOrientation;
     import javafx.scene.Node;
     import javafx.scene.Scene;
    @@ -44,6 +43,8 @@
     
     public class WindowControlsOverlayTest {
     
    +    private static final Dimension2D EMPTY = new Dimension2D(0, 0);
    +
         /**
          * Asserts that the buttons are laid out on the right side of the control (left-to-right orientation).
          */
    @@ -52,7 +53,7 @@ void rightPlacement() {
             var overlay = new WindowControlsOverlay(getStylesheet("""
                     .window-button-container { -fx-button-placement: right; }
                     .window-button { -fx-pref-width: 20; -fx-pref-height: 10; }
    -            """), false);
    +            """), false, false);
     
             var unused = new Scene(overlay);
             var children = overlay.getChildrenUnmodifiable();
    @@ -64,8 +65,8 @@ void rightPlacement() {
             assertLayoutBounds(children.get(0), 140, 0, 20, 10);
             assertLayoutBounds(children.get(1), 160, 0, 20, 10);
             assertLayoutBounds(children.get(2), 180, 0, 20, 10);
    -        assertEquals(HorizontalDirection.RIGHT, overlay.metricsProperty().get().placement());
    -        assertEquals(new Dimension2D(60, 10), overlay.metricsProperty().get().size());
    +        assertEquals(EMPTY, overlay.metricsProperty().get().leftInset());
    +        assertEquals(new Dimension2D(60, 10), overlay.metricsProperty().get().rightInset());
         }
     
         /**
    @@ -76,11 +77,10 @@ void rightPlacement_rightToLeft() {
             var overlay = new WindowControlsOverlay(getStylesheet("""
                     .window-button-container { -fx-button-placement: right; }
                     .window-button { -fx-pref-width: 20; -fx-pref-height: 10; }
    -            """), false);
    +            """), false, true);
     
             var unused = new Scene(overlay);
             var children = overlay.getChildrenUnmodifiable();
    -        overlay.setNodeOrientation(NodeOrientation.RIGHT_TO_LEFT);
             overlay.resize(200, 100);
             overlay.applyCss();
             overlay.layout();
    @@ -89,8 +89,8 @@ void rightPlacement_rightToLeft() {
             assertLayoutBounds(children.get(0), 40, 0, 20, 10);
             assertLayoutBounds(children.get(1), 20, 0, 20, 10);
             assertLayoutBounds(children.get(2), 0, 0, 20, 10);
    -        assertEquals(HorizontalDirection.LEFT, overlay.metricsProperty().get().placement());
    -        assertEquals(new Dimension2D(60, 10), overlay.metricsProperty().get().size());
    +        assertEquals(new Dimension2D(60, 10), overlay.metricsProperty().get().leftInset());
    +        assertEquals(EMPTY, overlay.metricsProperty().get().rightInset());
         }
     
         /**
    @@ -101,7 +101,7 @@ void leftPlacement() {
             var overlay = new WindowControlsOverlay(getStylesheet("""
                     .window-button-container { -fx-button-placement: left; }
                     .window-button { -fx-pref-width: 20; -fx-pref-height: 10; }
    -            """), false);
    +            """), false, false);
     
             var unused = new Scene(overlay);
             var children = overlay.getChildrenUnmodifiable();
    @@ -113,8 +113,8 @@ void leftPlacement() {
             assertLayoutBounds(children.get(0), 0, 0, 20, 10);
             assertLayoutBounds(children.get(1), 20, 0, 20, 10);
             assertLayoutBounds(children.get(2), 40, 0, 20, 10);
    -        assertEquals(HorizontalDirection.LEFT, overlay.metricsProperty().get().placement());
    -        assertEquals(new Dimension2D(60, 10), overlay.metricsProperty().get().size());
    +        assertEquals(new Dimension2D(60, 10), overlay.metricsProperty().get().leftInset());
    +        assertEquals(EMPTY, overlay.metricsProperty().get().rightInset());
         }
     
         /**
    @@ -125,7 +125,7 @@ void leftPlacement_rightToLeft() {
             var overlay = new WindowControlsOverlay(getStylesheet("""
                     .window-button-container { -fx-button-placement: left; }
                     .window-button { -fx-pref-width: 20; -fx-pref-height: 10; }
    -            """), false);
    +            """), false, true);
     
             var unused = new Scene(overlay);
             var children = overlay.getChildrenUnmodifiable();
    @@ -138,8 +138,8 @@ void leftPlacement_rightToLeft() {
             assertLayoutBounds(children.get(0), 180, 0, 20, 10);
             assertLayoutBounds(children.get(1), 160, 0, 20, 10);
             assertLayoutBounds(children.get(2), 140, 0, 20, 10);
    -        assertEquals(HorizontalDirection.RIGHT, overlay.metricsProperty().get().placement());
    -        assertEquals(new Dimension2D(60, 10), overlay.metricsProperty().get().size());
    +        assertEquals(EMPTY, overlay.metricsProperty().get().leftInset());
    +        assertEquals(new Dimension2D(60, 10), overlay.metricsProperty().get().rightInset());
         }
     
         /**
    @@ -152,7 +152,7 @@ void customButtonOrder() {
                     .minimize-button { -fx-button-order: 5; }
                     .maximize-button { -fx-button-order: 1; }
                     .close-button { -fx-button-order: 3; }
    -            """), false);
    +            """), false, false);
     
             var unused = new Scene(overlay);
             var children = overlay.getChildrenUnmodifiable();
    @@ -178,7 +178,7 @@ void customButtonOrder_rightToLeft() {
                     .minimize-button { -fx-button-order: 5; }
                     .maximize-button { -fx-button-order: 1; }
                     .close-button { -fx-button-order: 3; }
    -            """), false);
    +            """), false, true);
     
             var unused = new Scene(overlay);
             var children = overlay.getChildrenUnmodifiable();
    @@ -199,7 +199,7 @@ void customButtonOrder_rightToLeft() {
         void utilityDecorationIsOnlyCloseButton() {
             var overlay = new WindowControlsOverlay(getStylesheet("""
                     .window-button { -fx-pref-width: 20; -fx-pref-height: 10; }
    -            """), true);
    +            """), true, false);
     
             var children = overlay.getChildrenUnmodifiable();
             assertEquals(1, children.size());
    @@ -214,11 +214,10 @@ void disallowRightToLeft() {
             var overlay = new WindowControlsOverlay(getStylesheet("""
                     .window-button-container { -fx-button-placement: right; -fx-allow-rtl: false; }
                     .window-button { -fx-pref-width: 20; -fx-pref-height: 10; }
    -            """), false);
    +            """), false, true);
     
             var unused = new Scene(overlay);
             var children = overlay.getChildrenUnmodifiable();
    -        overlay.setNodeOrientation(NodeOrientation.RIGHT_TO_LEFT);
             overlay.resize(200, 100);
             overlay.applyCss();
             overlay.layout();
    @@ -226,8 +225,8 @@ void disallowRightToLeft() {
             assertLayoutBounds(children.get(0), 140, 0, 20, 10);
             assertLayoutBounds(children.get(1), 160, 0, 20, 10);
             assertLayoutBounds(children.get(2), 180, 0, 20, 10);
    -        assertEquals(HorizontalDirection.RIGHT, overlay.metricsProperty().get().placement());
    -        assertEquals(new Dimension2D(60, 10), overlay.metricsProperty().get().size());
    +        assertEquals(EMPTY, overlay.metricsProperty().get().leftInset());
    +        assertEquals(new Dimension2D(60, 10), overlay.metricsProperty().get().rightInset());
         }
     
         @Test
    @@ -235,7 +234,7 @@ void activePseudoClassCorrespondsToStageFocusedProperty() {
             var overlay = new WindowControlsOverlay(getStylesheet("""
                     .window-button-container { -fx-button-placement: right; }
                     .window-button { -fx-pref-width: 20; -fx-pref-height: 10; }
    -            """), false);
    +            """), false, false);
     
             var scene = new Scene(overlay);
             var stage = new Stage();
    @@ -261,7 +260,7 @@ void maximizeButtonIsDisabledWhenStageIsNotResizable() {
             var overlay = new WindowControlsOverlay(getStylesheet("""
                     .window-button-container { -fx-button-placement: right; }
                     .window-button { -fx-pref-width: 20; -fx-pref-height: 10; }
    -            """), false);
    +            """), false, false);
     
             var scene = new Scene(overlay);
             var stage = new Stage();
    @@ -285,7 +284,7 @@ void restoreStyleClassIsPresentWhenStageIsMaximized() {
             var overlay = new WindowControlsOverlay(getStylesheet("""
                     .window-button-container { -fx-button-placement: right; }
                     .window-button { -fx-pref-width: 20; -fx-pref-height: 10; }
    -            """), false);
    +            """), false, false);
     
             var scene = new Scene(overlay);
             var stage = new Stage();
    @@ -308,7 +307,7 @@ void darkStyleClassIsPresentWhenSceneFillIsDark() {
             var overlay = new WindowControlsOverlay(getStylesheet("""
                     .window-button-container { -fx-button-placement: right; }
                     .window-button { -fx-pref-width: 20; -fx-pref-height: 10; }
    -            """), false);
    +            """), false, false);
     
             var scene = new Scene(overlay);
     
    @@ -327,7 +326,7 @@ void pickButtonAtCoordinates() {
             var overlay = new WindowControlsOverlay(getStylesheet("""
                     .window-button-container { -fx-button-placement: right; }
                     .window-button { -fx-pref-width: 20; -fx-pref-height: 10; }
    -            """), false);
    +            """), false, false);
     
             var unused = new Scene(overlay);
             overlay.resize(200, 100);
    diff --git a/modules/javafx.graphics/src/test/java/test/javafx/scene/layout/HeaderBarTest.java b/modules/javafx.graphics/src/test/java/test/javafx/scene/layout/HeaderBarTest.java
    index c17efde58e0..fb3f6670248 100644
    --- a/modules/javafx.graphics/src/test/java/test/javafx/scene/layout/HeaderBarTest.java
    +++ b/modules/javafx.graphics/src/test/java/test/javafx/scene/layout/HeaderBarTest.java
    @@ -263,8 +263,7 @@ void alignmentOfCenterChild_notResizable_withNonEmptyLeadingAndTrailingChild(
             "BOTTOM_RIGHT, 740, 10, 100, 80"
         })
         void alignmentOfCenterChild_withLeftSystemInset(Pos pos, double x, double y, double width, double height) {
    -        @SuppressWarnings("unchecked")
    -        var leftSystemInset = (ObjectProperty)ReflectionUtils.getFieldValue(headerBar, "leftSystemInset");
    +        ObjectProperty leftSystemInset = ReflectionUtils.getFieldValue(headerBar, "leftSystemInset");
             leftSystemInset.set(new Dimension2D(100, 100));
             var leading = new MockResizable(50, 50);
             var center = new MockResizable(100, 50);
    @@ -293,8 +292,7 @@ void alignmentOfCenterChild_withLeftSystemInset(Pos pos, double x, double y, dou
             "BOTTOM_RIGHT, 640, 10, 100, 80"
         })
         void alignmentOfCenterChild_withRightSystemInset(Pos pos, double x, double y, double width, double height) {
    -        @SuppressWarnings("unchecked")
    -        var rightSystemInset = (ObjectProperty)ReflectionUtils.getFieldValue(headerBar, "rightSystemInset");
    +        ObjectProperty rightSystemInset = ReflectionUtils.getFieldValue(headerBar, "rightSystemInset");
             rightSystemInset.set(new Dimension2D(100, 100));
             var leading = new MockResizable(50, 50);
             var center = new MockResizable(100, 50);
    @@ -318,8 +316,7 @@ void alignmentOfCenterChild_withRightSystemInset(Pos pos, double x, double y, do
         })
         void alignmentOfCenterChild_withLeftSystemInset_andOffsetCausedByInsufficientHorizontalSpace(
                 Pos pos, double x, double y, double width, double height) {
    -        @SuppressWarnings("unchecked")
    -        var leftSystemInset = (ObjectProperty)ReflectionUtils.getFieldValue(headerBar, "leftSystemInset");
    +        ObjectProperty leftSystemInset = ReflectionUtils.getFieldValue(headerBar, "leftSystemInset");
             leftSystemInset.set(new Dimension2D(200, 100));
             var leading = new MockResizable(50, 50);
             var center = new MockResizable(100, 50);
    @@ -343,8 +340,7 @@ void alignmentOfCenterChild_withLeftSystemInset_andOffsetCausedByInsufficientHor
         })
         void alignmentOfCenterChild_withRightSystemInset_andOffsetCausedByInsufficientHorizontalSpace(
                 Pos pos, double x, double y, double width, double height) {
    -        @SuppressWarnings("unchecked")
    -        var rightSystemInset = (ObjectProperty)ReflectionUtils.getFieldValue(headerBar, "rightSystemInset");
    +        ObjectProperty rightSystemInset = ReflectionUtils.getFieldValue(headerBar, "rightSystemInset");
             rightSystemInset.set(new Dimension2D(200, 100));
             var leading = new MockResizable(50, 50);
             var center = new MockResizable(100, 50);
    @@ -366,15 +362,14 @@ void alignmentOfCenterChild_withRightSystemInset_andOffsetCausedByInsufficientHo
             "CENTER, 10, 25, 50, 50",
             "BOTTOM_LEFT, 10, 40, 50, 50"
         })
    -    void alignmentOfLeadingChild_notResizable_withOverlappingLeftSystemInset(
    +    void alignmentOfLeadingChild_notResizable_withoutReservedArea(
                 Pos pos, double x, double y, double width, double height) {
    -        @SuppressWarnings("unchecked")
    -        var leftSystemInset = (ObjectProperty)ReflectionUtils.getFieldValue(headerBar, "leftSystemInset");
    +        ObjectProperty leftSystemInset = ReflectionUtils.getFieldValue(headerBar, "leftSystemInset");
             leftSystemInset.set(new Dimension2D(100, 100));
             var leading = new Rectangle(50, 50);
             HeaderBar.setAlignment(leading, pos);
             HeaderBar.setMargin(leading, new Insets(10));
    -        headerBar.setOverlappingSystemInset(true);
    +        headerBar.setLeadingSystemPadding(false);
             headerBar.setLeading(leading);
             headerBar.resize(1000, 100);
             headerBar.layout();
    @@ -388,15 +383,14 @@ void alignmentOfLeadingChild_notResizable_withOverlappingLeftSystemInset(
             "CENTER, 940, 25, 50, 50",
             "BOTTOM_RIGHT, 940, 40, 50, 50"
         })
    -    void alignmentOfTrailingChild_notResizable_withOverlappingRightSystemInset(
    +    void alignmentOfTrailingChild_notResizable_withoutReservedArea(
                 Pos pos, double x, double y, double width, double height) {
    -        @SuppressWarnings("unchecked")
    -        var rightSystemInset = (ObjectProperty)ReflectionUtils.getFieldValue(headerBar, "rightSystemInset");
    +        ObjectProperty rightSystemInset = ReflectionUtils.getFieldValue(headerBar, "rightSystemInset");
             rightSystemInset.set(new Dimension2D(100, 100));
             var trailing = new Rectangle(50, 50);
             HeaderBar.setAlignment(trailing, pos);
             HeaderBar.setMargin(trailing, new Insets(10));
    -        headerBar.setOverlappingSystemInset(true);
    +        headerBar.setTrailingSystemPadding(false);
             headerBar.setTrailing(trailing);
             headerBar.resize(1000, 100);
             headerBar.layout();
    
    From 49a81d3272d987f53f7af36cc17d201616a18fae Mon Sep 17 00:00:00 2001
    From: mstr2 <43553916+mstr2@users.noreply.github.com>
    Date: Mon, 27 Jan 2025 07:35:02 +0100
    Subject: [PATCH 37/73] added custom header buttons
    
    ---
     .../java/com/sun/glass/events/MouseEvent.java |   33 +-
     .../src/main/java/com/sun/glass/ui/View.java  |   65 +-
     .../main/java/com/sun/glass/ui/Window.java    |   11 +-
     .../sun/glass/ui/WindowControlsOverlay.java   |   14 +-
     .../java/com/sun/glass/ui/gtk/GtkView.java    |   21 +-
     .../java/com/sun/glass/ui/gtk/GtkWindow.java  |    7 +-
     .../java/com/sun/glass/ui/mac/MacView.java    |   36 +-
     .../java/com/sun/glass/ui/win/WinView.java    |   31 +-
     .../java/com/sun/glass/ui/win/WinWindow.java  |   16 +-
     .../java/com/sun/javafx/tk/DummyToolkit.java  |    6 +-
     .../com/sun/javafx/tk/HeaderAreaType.java     |   36 +
     .../com/sun/javafx/tk/TKSceneListener.java    |   11 +-
     .../main/java/com/sun/javafx/tk/Toolkit.java  |    5 +-
     .../tk/quantum/GlassViewEventHandler.java     |    8 +-
     .../sun/javafx/tk/quantum/QuantumToolkit.java |   10 +-
     .../sun/javafx/tk/quantum/WindowStage.java    |   14 +-
     .../application/ConditionalFeature.java       |    4 +-
     .../src/main/java/javafx/scene/Scene.java     |   15 +-
     .../java/javafx/scene/layout/HeaderBar.java   |    2 +-
     .../javafx/scene/layout/HeaderBarBase.java    |   23 +-
     .../javafx/scene/layout/HeaderButtonType.java |   67 ++
     .../src/main/java/javafx/stage/Stage.java     |   34 +-
     .../main/java/javafx/stage/StageStyle.java    |   34 +-
     .../native-glass/win/FullScreenWindow.cpp     | 1062 ++++++++---------
     .../src/main/native-glass/win/GlassWindow.cpp |    6 +-
     .../main/native-glass/win/ViewContainer.cpp   |  123 +-
     .../src/main/native-glass/win/ViewContainer.h |    4 +-
     .../com/sun/javafx/pgstub/StubToolkit.java    |    7 +-
     28 files changed, 968 insertions(+), 737 deletions(-)
     create mode 100644 modules/javafx.graphics/src/main/java/com/sun/javafx/tk/HeaderAreaType.java
     create mode 100644 modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderButtonType.java
    
    diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/events/MouseEvent.java b/modules/javafx.graphics/src/main/java/com/sun/glass/events/MouseEvent.java
    index b9a5651db14..d1cfe4370e1 100644
    --- a/modules/javafx.graphics/src/main/java/com/sun/glass/events/MouseEvent.java
    +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/events/MouseEvent.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved.
    + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved.
      * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      *
      * This code is free software; you can redistribute it and/or modify it
    @@ -24,8 +24,6 @@
      */
     package com.sun.glass.events;
     
    -import com.sun.glass.ui.WindowControlsOverlay;
    -import javafx.stage.StageStyle;
     import java.lang.annotation.Native;
     
     public class MouseEvent {
    @@ -51,33 +49,4 @@ public class MouseEvent {
          * This identifier is required for internal purposes.
          */
         @Native final static public int WHEEL           = 228;
    -
    -    /**
    -     * Non-client events are only natively produced on the Windows platform as a result of
    -     * handling the {@code WM_NCHITTEST} message for an {@link StageStyle#EXTENDED} window.
    -     * 

    - * They are never sent to applications, but are processed by {@link WindowControlsOverlay}. - */ - @Native final static public int NC_DOWN = 230; - @Native final static public int NC_UP = 231; - @Native final static public int NC_DRAG = 232; - @Native final static public int NC_MOVE = 233; - @Native final static public int NC_ENTER = 234; - @Native final static public int NC_EXIT = 235; - - public static boolean isNonClientEvent(int event) { - return event >= NC_DOWN && event <= NC_EXIT; - } - - public static int toNonClientEvent(int event) { - return switch (event) { - case DOWN -> NC_DOWN; - case UP -> NC_UP; - case DRAG -> NC_DRAG; - case MOVE -> NC_MOVE; - case ENTER -> NC_ENTER; - case EXIT -> NC_EXIT; - default -> event; - }; - } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/View.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/View.java index 43482442d13..e9da9942af4 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/View.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/View.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,7 +26,7 @@ import com.sun.glass.events.MouseEvent; import com.sun.glass.events.ViewEvent; -import javafx.scene.Node; +import com.sun.javafx.tk.HeaderAreaType; import java.lang.annotation.Native; import java.lang.ref.WeakReference; import java.util.Map; @@ -365,14 +365,14 @@ public void handleSwipeGestureEvent(View view, long time, int type, } /** - * Returns the draggable area node at the specified coordinates, or {@code null} - * if the specified coordinates do not intersect with a draggable area. + * Returns the header area type at the specified coordinates, or {@code null} + * if the specified coordinates do not intersect with a header area. * * @param x the X coordinate * @param y the Y coordinate - * @return the draggable area node, or {@code null} + * @return the header area type, or {@code null} */ - public Node pickDragAreaNode(double x, double y) { + public HeaderAreaType pickHeaderArea(double x, double y) { return null; } @@ -381,21 +381,6 @@ public Accessible getSceneAccessible() { } } - /** - * A non-client event handler is used in some implementations of windows with the {@link Window#EXTENDED} - * style. It can inspect a mouse event before it is sent to FX, and decide to consume it if it affects a - * non-client part of the window (for example, minimize/maximize/close buttons). - */ - public interface NonClientEventHandler { - - /** - * Handles the event. - * - * @return {@code true} if the event was handled, {@code false} otherwise - */ - boolean handleMouseEvent(int type, int button, int x, int y, int xAbs, int yAbs, int clickCount); - } - public static long getMultiClickTime() { Application.checkEventThread(); return Application.GetApplication().staticView_getMultiClickTime(); @@ -422,7 +407,6 @@ protected void _finishInputMethodComposition(long ptr) { private volatile long ptr; // Native handle (NSView*, or internal structure pointer) private Window window; // parent window private EventHandler eventHandler; - private NonClientEventHandler nonClientEventHandler; private int width = -1; // not set private int height = -1; // not set @@ -512,10 +496,6 @@ public int getHeight() { return this.height; } - protected NonClientEventHandler createNonClientEventHandler() { - return null; - } - protected abstract void _setParent(long ptr, long parentPtr); // Window calls the method from Window.setView() // package private @@ -525,12 +505,6 @@ void setWindow(Window window) { this.window = window; _setParent(this.ptr, window == null ? 0L : window.getNativeHandle()); this.isValid = this.ptr != 0 && window != null; - - if (this.isValid && window.isExtendedWindow()) { - this.nonClientEventHandler = createNonClientEventHandler(); - } else { - this.nonClientEventHandler = null; - } } // package private @@ -566,7 +540,7 @@ public void setEventHandler(EventHandler eventHandler) { this.eventHandler = eventHandler; } - private boolean shouldHandleEvent() { + protected boolean shouldHandleEvent() { // Don't send any more events if the application has shutdown if (Application.GetApplication() == null) { return false; @@ -591,10 +565,10 @@ private boolean handleKeyEvent(long time, int action, return false; } - private void handleMouseEvent(long time, int type, int button, int x, int y, - int xAbs, int yAbs, - int modifiers, boolean isPopupTrigger, - boolean isSynthesized) { + protected void handleMouseEvent(long time, int type, int button, int x, int y, + int xAbs, int yAbs, + int modifiers, boolean isPopupTrigger, + boolean isSynthesized) { if (shouldHandleEvent()) { eventHandler.handleMouseEvent(this, time, type, button, x, y, xAbs, yAbs, modifiers, @@ -602,6 +576,11 @@ private void handleMouseEvent(long time, int type, int button, int x, int y, } } + protected boolean handleNonClientMouseEvent(long time, int type, int button, int x, int y, + int xAbs, int yAbs, int modifiers, int clickCount) { + return false; + } + protected boolean handleMenuEvent(int x, int y, int xAbs, int yAbs, boolean isKeyboardTrigger) { if (shouldHandleEvent()) { return this.eventHandler.handleMenuEvent(this, x, y, xAbs, yAbs, isKeyboardTrigger); @@ -974,7 +953,7 @@ protected void notifyMouse(int type, int button, int x, int y, int xAbs, lastClickedTime = now; } - // If we have a non-client event handler, we give it the first chance to handle the event. + // If this is an extended window, we give the non-client handler the first chance to handle the event. // Note that a full-screen window has no non-client area, and thus the non-client event handler // is not notified. // Some implementations (like GTK) can fire synthesized events when they receive a mouse button @@ -982,11 +961,13 @@ protected void notifyMouse(int type, int button, int x, int y, int xAbs, // not be processed by the non-client event handler. For example, if a mouse click happens on // the resize border that straddles the window close button, we don't want the close button to // act on this click, because we just started a resize-drag operation. - boolean handled = !isSynthesized && !inFullscreen && nonClientEventHandler != null - && nonClientEventHandler.handleMouseEvent(type, button, x, y, xAbs, yAbs, clickCount); + boolean handled = window.isExtendedWindow() + && !isSynthesized + && !inFullscreen + && shouldHandleEvent() + && handleNonClientMouseEvent(now, type, button, x, y, xAbs, yAbs, modifiers, clickCount); - // We never send non-client events to the application. - if (handled || MouseEvent.isNonClientEvent(type)) { + if (handled) { return; } diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java index a5228cf6fec..bb3c1455036 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -171,6 +171,11 @@ static protected void remove(Window window) { */ @Native public static final int MODAL = 1 << 10; + /** + * Indicates that the window has non-client overlay controls. + */ + public static final int NON_CLIENT_OVERLAY = 1 << 11; + final static public class State { @Native public static final int NORMAL = 1; @Native public static final int MINIMIZED = 2; @@ -753,6 +758,10 @@ public boolean isTransparentWindow() { return (this.styleMask & Window.TRANSPARENT) != 0; } + public boolean isUsingNonClientOverlay() { + return isExtendedWindow() && (styleMask & Window.NON_CLIENT_OVERLAY) != 0; + } + public boolean isFocused() { Application.checkEventThread(); return this.isFocused; diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java index 9df9c4da506..ff92372f6c6 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -313,16 +313,20 @@ public boolean handleMouseEvent(int type, int button, double x, double y) { case CLOSE -> closeButton; } : null; - if (type == MouseEvent.NC_ENTER || type == MouseEvent.NC_MOVE || type == MouseEvent.NC_DRAG) { + if (type == MouseEvent.ENTER || type == MouseEvent.MOVE || type == MouseEvent.DRAG) { handleMouseOver(node); - } else if (type == MouseEvent.NC_EXIT) { + } else if (type == MouseEvent.EXIT) { handleMouseExit(); - } else if (type == MouseEvent.NC_UP && button == MouseEvent.BUTTON_LEFT) { + } else if (type == MouseEvent.UP && button == MouseEvent.BUTTON_LEFT) { handleMouseUp(node, buttonType); - } else if (node != null && type == MouseEvent.NC_DOWN && button == MouseEvent.BUTTON_LEFT) { + } else if (node != null && type == MouseEvent.DOWN && button == MouseEvent.BUTTON_LEFT) { handleMouseDown(node); } + if (type == MouseEvent.ENTER || type == MouseEvent.EXIT) { + return false; + } + return node != null || buttonAtMouseDown != null; } diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkView.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkView.java index 7c33cc72413..80bac2c7b99 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkView.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkView.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,9 +24,9 @@ */ package com.sun.glass.ui.gtk; -import com.sun.glass.events.MouseEvent; import com.sun.glass.ui.Pixels; import com.sun.glass.ui.View; +import com.sun.javafx.tk.HeaderAreaType; import java.nio.Buffer; import java.nio.ByteBuffer; @@ -154,28 +154,21 @@ protected void notifyMenu(int x, int y, int xAbs, int yAbs, boolean isKeyboardTr double wy = y / window.getPlatformScaleY(); EventHandler eventHandler = getEventHandler(); - if (eventHandler != null && eventHandler.pickDragAreaNode(wx, wy) != null) { + if (eventHandler != null && eventHandler.pickHeaderArea(wx, wy) == HeaderAreaType.DRAGBAR) { window.showSystemMenu(x, y); } } } @Override - protected NonClientEventHandler createNonClientEventHandler() { + protected boolean handleNonClientMouseEvent(long time, int type, int button, int x, int y, int xAbs, int yAbs, + int modifiers, int clickCount) { var window = (GtkWindow)getWindow(); var overlay = window.getNonClientOverlay(); if (overlay == null) { - return null; + return false; } - return (type, button, x, y, xAbs, yAbs, clickCount) -> { - // In contrast to Windows, GTK doesn't produce non-client events. We convert regular - // mouse events to non-client events since that's what WindowControlsOverlay expects. - return overlay.handleMouseEvent( - MouseEvent.toNonClientEvent(type), - button, - x / window.getPlatformScaleX(), - y / window.getPlatformScaleY()); - }; + return overlay.handleMouseEvent(type, button, x / window.getPlatformScaleX(), y / window.getPlatformScaleY()); } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java index de6c516ee0b..43152cbfa28 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,6 +31,7 @@ import com.sun.glass.ui.View; import com.sun.glass.ui.Window; import com.sun.glass.ui.WindowControlsOverlay; +import com.sun.javafx.tk.HeaderAreaType; class GtkWindow extends Window { @@ -208,7 +209,7 @@ public long getRawHandle() { @Override public WindowControlsOverlay getNonClientOverlay() { - if (windowControlsOverlay == null && isExtendedWindow()) { + if (windowControlsOverlay == null && isUsingNonClientOverlay()) { windowControlsOverlay = new WindowControlsOverlay( PlatformThemeObserver.getInstance().stylesheetProperty(), isUtilityWindow(), @@ -265,6 +266,6 @@ private boolean dragAreaHitTest(int x, int y) { return false; } - return eventHandler.pickDragAreaNode(wx, wy) != null; + return eventHandler.pickHeaderArea(wx, wy) == HeaderAreaType.DRAGBAR; } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacView.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacView.java index 23c83c83593..862bdcf57e5 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacView.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacView.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,6 +28,8 @@ import com.sun.glass.ui.Pixels; import com.sun.glass.ui.View; import com.sun.glass.ui.Window; +import com.sun.javafx.tk.HeaderAreaType; + import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.IntBuffer; @@ -185,25 +187,23 @@ protected void notifyInputMethodMac(String str, int attrib, int length, } @Override - protected NonClientEventHandler createNonClientEventHandler() { - return (type, button, x, y, xAbs, yAbs, clickCount) -> { - if (type == MouseEvent.DOWN) { - var window = (MacWindow)getWindow(); - double wx = x / window.getPlatformScaleX(); - double wy = y / window.getPlatformScaleY(); - - View.EventHandler eventHandler = getEventHandler(); - if (eventHandler != null && eventHandler.pickDragAreaNode(wx, wy) != null) { - if (clickCount == 2) { - window.performTitleBarDoubleClickAction(); - } else if (clickCount == 1) { - window.performWindowDrag(); - } + protected boolean handleNonClientMouseEvent(long time, int type, int button, int x, int y, int xAbs, int yAbs, + int modifiers, int clickCount) { + if (shouldHandleEvent() && type == MouseEvent.DOWN) { + var window = (MacWindow)getWindow(); + double wx = x / window.getPlatformScaleX(); + double wy = y / window.getPlatformScaleY(); + + View.EventHandler eventHandler = getEventHandler(); + if (eventHandler != null && eventHandler.pickHeaderArea(wx, wy) == HeaderAreaType.DRAGBAR) { + if (clickCount == 2) { + window.performTitleBarDoubleClickAction(); + } else if (clickCount == 1) { + window.performWindowDrag(); } } + } - return false; - }; + return false; } } - diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinView.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinView.java index 5c90d66ac19..7b83c6bac7d 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinView.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinView.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,6 +26,8 @@ import com.sun.glass.ui.Pixels; import com.sun.glass.ui.View; +import com.sun.javafx.tk.HeaderAreaType; + import java.util.Map; /** @@ -112,30 +114,33 @@ protected void notifyMenu(int x, int y, int xAbs, int yAbs, boolean isKeyboardTr double wy = y / window.getPlatformScaleY(); EventHandler eventHandler = getEventHandler(); - if (eventHandler != null && eventHandler.pickDragAreaNode(wx, wy) != null) { + if (eventHandler != null && eventHandler.pickHeaderArea(wx, wy) == HeaderAreaType.DRAGBAR) { window.showSystemMenu(x, y); } } } @Override - protected NonClientEventHandler createNonClientEventHandler() { - var window = (WinWindow)getWindow(); - if (!window.isExtendedWindow()) { - return null; + protected boolean handleNonClientMouseEvent(long time, int type, int button, int x, int y, int xAbs, int yAbs, + int modifiers, int clickCount) { + if (!shouldHandleEvent()) { + return false; } + var window = (WinWindow)getWindow(); var overlay = window.getNonClientOverlay(); - if (overlay == null) { - return null; - } - - return (type, button, x, y, xAbs, yAbs, clickCount) -> { + if (overlay != null) { double wx = x / window.getPlatformScaleX(); double wy = y / window.getPlatformScaleY(); // Give the window button overlay the first chance to handle the event. - return overlay.handleMouseEvent(type, button, wx, wy); - }; + if (overlay.handleMouseEvent(type, button, wx, wy)) { + return true; + } + } + + // If the overlay didn't handle the event, we pass it down to the application. + handleMouseEvent(time, type, button, x, y, xAbs, yAbs, modifiers, false, false); + return true; } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java index 4e841785ef3..501cebc67d6 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -339,7 +339,7 @@ void setDeferredClosing(boolean dc) { @Override public WindowControlsOverlay getNonClientOverlay() { - if (windowControlsOverlay == null && isExtendedWindow()) { + if (windowControlsOverlay == null && isUsingNonClientOverlay()) { var url = getClass().getResource(WINDOW_DECORATION_STYLESHEET); if (url == null) { throw new RuntimeException("Resource not found: " + WINDOW_DECORATION_STYLESHEET); @@ -402,10 +402,12 @@ enum HT { // Otherwise, test if the cursor is over a draggable area and return HTCAPTION. View.EventHandler eventHandler = view.getEventHandler(); - if (eventHandler != null && eventHandler.pickDragAreaNode(wx, wy) != null) { - return HT.CAPTION.value; - } - - return HT.CLIENT.value; + return switch (eventHandler != null ? eventHandler.pickHeaderArea(wx, wy) : null) { + case DRAGBAR -> HT.CAPTION.value; + case MINIMIZE -> HT.MINBUTTON.value; + case MAXIMIZE -> HT.MAXBUTTON.value; + case CLOSE -> HT.CLOSE.value; + case null -> HT.CLIENT.value; + }; } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/DummyToolkit.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/DummyToolkit.java index 0d214e91a31..e1fd688d203 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/DummyToolkit.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/DummyToolkit.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -101,7 +101,9 @@ public void exitAllNestedEventLoops() { } @Override - public TKStage createTKStage(Window peerWindow, StageStyle stageStyle, boolean primary, Modality modality, TKStage owner, boolean rtl) { + public TKStage createTKStage(Window peerWindow, StageStyle stageStyle, boolean primary, + boolean nonClientOverlay, Modality modality, TKStage owner, + boolean rtl) { throw new UnsupportedOperationException("Not supported yet."); } diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/HeaderAreaType.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/HeaderAreaType.java new file mode 100644 index 00000000000..403a7a333f1 --- /dev/null +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/HeaderAreaType.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.javafx.tk; + +/** + * Identifies the semantic parts of the header area. + */ +public enum HeaderAreaType { + DRAGBAR, + MINIMIZE, + MAXIMIZE, + CLOSE +} diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKSceneListener.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKSceneListener.java index 8593f08c220..988bb0db211 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKSceneListener.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKSceneListener.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,7 +28,6 @@ import com.sun.glass.ui.Accessible; import javafx.collections.ObservableList; import javafx.event.EventType; -import javafx.scene.Node; import javafx.scene.input.*; /** @@ -123,12 +122,12 @@ public void touchEventNext( public Accessible getSceneAccessible(); /** - * Returns the draggable area node at the specified coordinates, or {@code null} - * if the specified coordinates do not intersect with a draggable area. + * Returns the header area type at the specified coordinates, or {@code null} + * if the specified coordinates do not intersect with a header area. * * @param x the X coordinate relative to the scene * @param y the Y coordinate relative to the scene - * @return the draggable area node, or {@code null} + * @return the header area type, or {@code null} */ - public Node pickDragAreaNode(double x, double y); + public HeaderAreaType pickHeaderArea(double x, double y); } diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/Toolkit.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/Toolkit.java index d76cfbdc04e..f1ec5bef5fc 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/Toolkit.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/Toolkit.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -360,7 +360,8 @@ protected Toolkit() { public abstract boolean isNestedLoopRunning(); - public abstract TKStage createTKStage(Window peerWindow, StageStyle stageStyle, boolean primary, Modality modality, TKStage owner, boolean rtl); + public abstract TKStage createTKStage(Window peerWindow, StageStyle stageStyle, boolean primary, + boolean nonClientOverlay, Modality modality, TKStage owner, boolean rtl); public abstract TKStage createTKPopupStage(Window peerWindow, StageStyle popupStyle, TKStage owner); public abstract TKStage createTKEmbeddedStage(HostInterface host); diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/GlassViewEventHandler.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/GlassViewEventHandler.java index f5c66aac3dd..b8a9ee20da1 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/GlassViewEventHandler.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/GlassViewEventHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -42,13 +42,13 @@ import com.sun.javafx.logging.PulseLogger; import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGING_ENABLED; import com.sun.javafx.scene.input.KeyCodeMap; +import com.sun.javafx.tk.HeaderAreaType; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.event.EventType; import javafx.geometry.Point2D; -import javafx.scene.Node; import javafx.scene.input.InputMethodEvent; import javafx.scene.input.InputMethodHighlight; import javafx.scene.input.InputMethodTextRun; @@ -1248,10 +1248,10 @@ public Accessible getSceneAccessible() { } @Override - public Node pickDragAreaNode(double x, double y) { + public HeaderAreaType pickHeaderArea(double x, double y) { return QuantumToolkit.runWithoutRenderLock(() -> { if (scene.sceneListener != null) { - return scene.sceneListener.pickDragAreaNode(x, y); + return scene.sceneListener.pickHeaderArea(x, y); } return null; diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java index 4c8b2e7c7d5..389e581b7b9 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -608,9 +608,11 @@ void vsyncHint() { } } - @Override public TKStage createTKStage(Window peerWindow, StageStyle stageStyle, boolean primary, Modality modality, TKStage owner, boolean rtl) { + @Override public TKStage createTKStage(Window peerWindow, StageStyle stageStyle, boolean primary, + boolean nonClientOverlay, Modality modality, TKStage owner, + boolean rtl) { assertToolkitRunning(); - WindowStage stage = new WindowStage(peerWindow, stageStyle, modality, owner); + WindowStage stage = new WindowStage(peerWindow, stageStyle, nonClientOverlay, modality, owner); if (primary) { stage.setIsPrimary(); } @@ -681,7 +683,7 @@ void vsyncHint() { @Override public TKStage createTKPopupStage(Window peerWindow, StageStyle popupStyle, TKStage owner) { assertToolkitRunning(); - WindowStage stage = new WindowStage(peerWindow, popupStyle, null, owner); + WindowStage stage = new WindowStage(peerWindow, popupStyle, false, null, owner); stage.setIsPopup(); stage.init(systemMenu); return stage; diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/WindowStage.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/WindowStage.java index b9433801ca1..07ea252ce02 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/WindowStage.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/WindowStage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -59,6 +59,7 @@ public class WindowStage extends GlassStage { private StageStyle style; private GlassStage owner = null; private Modality modality = Modality.NONE; + private boolean nonClientOverlay; private OverlayWarning warning = null; private boolean rtl = false; @@ -83,10 +84,12 @@ public class WindowStage extends GlassStage { ".QuantumMessagesBundle", LOCALE); - public WindowStage(javafx.stage.Window peerWindow, final StageStyle stageStyle, Modality modality, TKStage owner) { + public WindowStage(javafx.stage.Window peerWindow, StageStyle stageStyle, boolean nonClientOverlay, + Modality modality, TKStage owner) { this.style = stageStyle; this.owner = (GlassStage)owner; this.modality = modality; + this.nonClientOverlay = nonClientOverlay; if (peerWindow instanceof javafx.stage.Stage) { fxStage = (Stage)peerWindow; @@ -174,12 +177,19 @@ private void initPlatformWindow() { windowMask &= ~(Window.MINIMIZABLE | Window.MAXIMIZABLE); } } + if (modality != Modality.NONE) { windowMask |= Window.MODAL; } + + if (nonClientOverlay) { + windowMask |= Window.NON_CLIENT_OVERLAY; + } + platformWindow = app.createWindow(ownerWindow, Screen.getMainScreen(), windowMask); platformWindow.setResizable(resizable); platformWindow.setFocusable(focusable); + if (fxStage != null && fxStage.getScene() != null) { javafx.scene.paint.Paint paint = fxStage.getScene().getFill(); if (paint instanceof javafx.scene.paint.Color) { diff --git a/modules/javafx.graphics/src/main/java/javafx/application/ConditionalFeature.java b/modules/javafx.graphics/src/main/java/javafx/application/ConditionalFeature.java index 5028cfd0d8f..300e36d76da 100644 --- a/modules/javafx.graphics/src/main/java/javafx/application/ConditionalFeature.java +++ b/modules/javafx.graphics/src/main/java/javafx/application/ConditionalFeature.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -157,7 +157,7 @@ public enum ConditionalFeature { *

    * This feature is currently supported on Windows, Linux, and macOS. * - * @since 24 + * @since 25 */ EXTENDED_WINDOW, diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java b/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java index 3a1fd3d11c3..8a08806f3c0 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -79,6 +79,7 @@ import javafx.scene.image.WritableImage; import javafx.scene.input.*; import javafx.scene.layout.HeaderBarBase; +import javafx.scene.layout.HeaderButtonType; import javafx.scene.paint.Color; import javafx.scene.paint.Paint; import javafx.stage.PopupWindow; @@ -3058,7 +3059,7 @@ public void touchEventEnd() { private final PickRay pickRay = new PickRay(); @Override - public Node pickDragAreaNode(double x, double y) { + public HeaderAreaType pickHeaderArea(double x, double y) { Node root = Scene.this.getRoot(); if (root == null) { return null; @@ -3072,7 +3073,15 @@ public Node pickDragAreaNode(double x, double y) { while (intersectedNode != null) { if (intersectedNode instanceof HeaderBarBase) { - return draggable == Boolean.TRUE ? intersectedNode : null; + return draggable == Boolean.TRUE ? HeaderAreaType.DRAGBAR : null; + } + + if (HeaderBarBase.getHeaderButtonType(intersectedNode) instanceof HeaderButtonType type) { + return switch (type) { + case MINIMIZE -> HeaderAreaType.MINIMIZE; + case MAXIMIZE -> HeaderAreaType.MAXIMIZE; + case CLOSE -> HeaderAreaType.CLOSE; + }; } if (draggable == null && HeaderBarBase.isDraggable(intersectedNode) instanceof Boolean value) { diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java index 05b0a9ae389..439243e2e57 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java index 6ecd85dec60..5719fe80a72 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -62,6 +62,7 @@ public abstract class HeaderBarBase extends Region { private static final Dimension2D EMPTY = new Dimension2D(0, 0); private static final String DRAGGABLE = "headerbar-draggable"; + private static final String HEADER_BUTTON_TYPE = "header-button-type"; /** * Specifies whether the child and its subtree is a draggable part of the {@code HeaderBar}. @@ -89,6 +90,26 @@ public static Boolean isDraggable(Node child) { return (Boolean)Pane.getConstraint(child, DRAGGABLE); } + /** + * Specifies the {@code HeaderButtonType} of the child, indicating its semantic use in the header bar. + * + * @param child the child node + * @param value the {@code HeaderButtonType}, or {@code null} + */ + public static void setHeaderButtonType(Node child, HeaderButtonType value) { + Pane.setConstraint(child, HEADER_BUTTON_TYPE, value); + } + + /** + * Returns the {@code HeaderButtonType} of the specified child. + * + * @param child the child node + * @return the {@code HeaderButtonType}, or {@code null} + */ + public static HeaderButtonType getHeaderButtonType(Node child) { + return (HeaderButtonType)Pane.getConstraint(child, HEADER_BUTTON_TYPE); + } + private Subscription subscription; private WindowControlsMetrics currentMetrics; private boolean currentFullScreen; diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderButtonType.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderButtonType.java new file mode 100644 index 00000000000..334544ccf30 --- /dev/null +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderButtonType.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javafx.scene.layout; + +import javafx.scene.Node; +import javafx.stage.Stage; + +/** + * Identifies the semantic type of a button in a custom {@link HeaderBar}, which enables integrations + * with the platform window manager. For example, hovering over a {@link #MAXIMIZE} button on Windows + * will summon snap layouts. + *

    + * This property can be set on any {@link Node}. Applications are still required to provide their own + * click handlers to programmatically trigger the actions associated with header buttons, i.e. call + * appropriate stage methods like {@link Stage#setIconified(boolean)}, {@link Stage#setMaximized(boolean)} + * or {@link Stage#close()}. + * + * @since 25 + * @see HeaderBarBase#setHeaderButtonType(Node, HeaderButtonType) + */ +public enum HeaderButtonType { + /** + * Identifies the minimize button. + * + * @see Stage#isIconified() + * @see Stage#setIconified(boolean) + */ + MINIMIZE, + + /** + * Identifies the maximize button. + * + * @see Stage#isMaximized() + * @see Stage#setMaximized(boolean) + */ + MAXIMIZE, + + /** + * Identifies the close button. + * + * @see Stage#close() + */ + CLOSE +} diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java b/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java index adf6f92622a..7aca35ad8ff 100644 --- a/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java +++ b/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -36,9 +36,12 @@ import javafx.collections.ListChangeListener.Change; import javafx.collections.ObservableList; import javafx.geometry.NodeOrientation; +import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.image.Image; import javafx.scene.input.KeyCombination; +import javafx.scene.layout.HeaderBarBase; +import javafx.scene.layout.HeaderButtonType; import com.sun.javafx.collections.VetoableListDecorator; import com.sun.javafx.collections.TrackableObservableList; @@ -496,6 +499,33 @@ public final Modality getModality() { return modality; } + private boolean defaultHeaderButtons = true; + + /** + * Specifies whether a stage with a client-side header bar uses the default platform-provided header buttons. + *

    + * If {@code false} is specified, an application must provide its own header buttons and mark them with + * {@link HeaderBarBase#setHeaderButtonType(Node, HeaderButtonType)} to enable integration with the platform + * window manager. + *

    + * This property is only relevant for {@link StageStyle#EXTENDED} or {@link StageStyle#EXTENDED_UTILITY} + * stages, and is ignored otherwise. + * + * @param enabled {@code true} if the stage uses default header buttons, {@code false} otherwise + * @since 25 + */ + public final void initDefaultHeaderButtons(boolean enabled) { + if (hasBeenVisible) { + throw new IllegalStateException("Cannot set chrome once stage has been set visible"); + } + + this.defaultHeaderButtons = enabled; + } + + public final boolean isDefaultHeaderButtons() { + return defaultHeaderButtons; + } + private Window owner = null; /** @@ -1086,7 +1116,7 @@ private void doVisibleChanging(boolean value) { boolean rtl = scene != null && scene.getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT; StageStyle stageStyle = getStyle(); - setPeer(toolkit.createTKStage(this, stageStyle, isPrimary(), + setPeer(toolkit.createTKStage(this, stageStyle, isPrimary(), isDefaultHeaderButtons(), getModality(), tkStage, rtl)); getPeer().setMinimumSize((int) Math.ceil(getMinWidth()), (int) Math.ceil(getMinHeight())); diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java b/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java index cc79c5f4339..76f53bed496 100644 --- a/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java +++ b/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,6 +30,7 @@ import javafx.scene.Scene; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HeaderBar; +import javafx.scene.layout.HeaderButtonType; /** * This enum defines the possible styles for a {@code Stage}. @@ -79,7 +80,11 @@ public enum StageStyle { * the separation between the two areas and allowing applications to place scene graph nodes in the header * bar area of the stage. *

    - * An extended window has the default window buttons (minimize, maximize, close), but no system-provided + * This is a conditional feature, to check if it is supported see {@link Platform#isSupported(ConditionalFeature)}. + * If the feature is not supported by the platform, this style downgrades to {@link StageStyle#DECORATED}. + * + *

    Usage

    + * An extended window has the default header buttons (minimize, maximize, close), but no system-provided * draggable header bar. Applications need to provide their own header bar by placing a {@link HeaderBar} * control in the scene graph. The {@code HeaderBar} control should be positioned at the top of the window * and its width should extend the entire width of the window, as otherwise the layout of the default window @@ -99,21 +104,26 @@ public enum StageStyle { * } * } * }
    - *

    - * The color scheme of the default window buttons is adjusted to the {@link Scene#fillProperty() fill} + * + *

    Color scheme

    + * The color scheme of the default header buttons is adjusted to the {@link Scene#fillProperty() fill} * of the {@code Scene} to remain easily recognizable. Applications should set the scene fill to a color * that matches the brightness of the user interface, even if the scene fill is not visible because it * is obscured by other controls. - *

    + * + *

    Custom header buttons

    + * If more control over the header buttons is desired, applications can opt out of the default header buttons + * by setting the {@link Stage#initDefaultHeaderButtons(boolean)} property to {@code false} and provide custom + * header buttons instead. Any JavaFX control can be used as a custom header button by setting its appropriate + * {@link HeaderButtonType}. + * + *

    Title text

    * An extended stage has no title text. Applications that require title text need to provide their own * implementation by placing a {@code Label} or a similar control in the custom header bar. - * Note that the value of {@link Stage#titleProperty()} may still be used by the platform, one example - * may be the title of miniaturized preview windows. - *

    - * This is a conditional feature, to check if it is supported see {@link Platform#isSupported(ConditionalFeature)}. - * If the feature is not supported by the platform, this style downgrades to {@link StageStyle#DECORATED}. + * Note that the value of {@link Stage#titleProperty()} may still be used by the platform, for example + * in the title of miniaturized preview windows. * - * @since 24 + * @since 25 */ EXTENDED, @@ -123,7 +133,7 @@ public enum StageStyle { * This is a conditional feature, to check if it is supported see {@link Platform#isSupported(ConditionalFeature)}. * If the feature is not supported by the platform, this style downgrades to {@link StageStyle#UTILITY}. * - * @since 24 + * @since 25 */ EXTENDED_UTILITY } diff --git a/modules/javafx.graphics/src/main/native-glass/win/FullScreenWindow.cpp b/modules/javafx.graphics/src/main/native-glass/win/FullScreenWindow.cpp index 48f507b4ae4..4fce26cddb1 100644 --- a/modules/javafx.graphics/src/main/native-glass/win/FullScreenWindow.cpp +++ b/modules/javafx.graphics/src/main/native-glass/win/FullScreenWindow.cpp @@ -1,531 +1,531 @@ -/* - * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -#include "common.h" - -#include "GlassApplication.h" -#include "FullScreenWindow.h" -#include "GlassView.h" -#include "GlassDnD.h" -#include "GlassWindow.h" - -#include "com_sun_glass_events_WindowEvent.h" - -//TODO: is that possible to move all the code to the shared level? - -static LPCTSTR szFullScreenWindowClassName = TEXT("FullScreenWindowClass"); -static LPCTSTR szBackgroundWindowClassName = TEXT("BackgroundWindowClass"); - -static const UINT ANIMATION_MAX_ITERATION = 30; -static const UINT ANIMATION_TIMER_ELAPSE = USER_TIMER_MINIMUM; // 0xA ms - -FullScreenWindow::FullScreenWindow() : - BaseWnd(), - ViewContainer() -{ - m_animationStage = 0; - m_bgWindow = NULL; -} - -FullScreenWindow::~FullScreenWindow() -{ -} - -HWND FullScreenWindow::Create() -{ - m_bgWindow = new BackgroundWindow(); - m_bgWindow->Create(); - - DWORD dwStyle = WS_POPUP | WS_CLIPCHILDREN; - DWORD dwExStyle = 0; - - HWND hwnd = BaseWnd::Create(NULL, 0, 0, 0, 0, - TEXT(""), dwExStyle, dwStyle, NULL); - - ViewContainer::InitDropTarget(hwnd); - ViewContainer::InitManipProcessor(hwnd); - - return hwnd; -} - -void FullScreenWindow::Close() -{ - if (m_bgWindow) { - m_bgWindow->Close(); - m_bgWindow = NULL; - } - - ViewContainer::ReleaseDropTarget(); - ViewContainer::ReleaseManipProcessor(); - - ::DestroyWindow(GetHWND()); -} - -/* static */ -void FullScreenWindow::ClientRectInScreen(HWND hwnd, RECT * rect) -{ - ::GetClientRect(hwnd, rect); - ::MapWindowPoints(hwnd, (HWND)NULL, (LPPOINT)rect, (sizeof(RECT)/sizeof(POINT))); -} - -void FullScreenWindow::AttachView(GlassView * view, BOOL keepRatio) -{ - SetGlassView(view); - - m_oldViewParent = GetGlassView()->GetHostHwnd(); - - FullScreenWindow::ClientRectInScreen(m_oldViewParent, &m_viewRect); - - InitWindowRect(keepRatio); - - GlassWindow * window = GlassWindow::FromHandle(m_oldViewParent); - if (window) { - window->SetDelegateWindow(GetHWND()); - } - - ::ShowWindow(m_oldViewParent, SW_HIDE); - GetGlassView()->SetHostHwnd(GetHWND()); -} - -void FullScreenWindow::DetachView() -{ - RECT r; - HWND oldWnd = m_oldViewParent; - GlassView* view = GetGlassView(); - - m_oldViewParent = NULL; - - view->SetHostHwnd(oldWnd); - - ::ShowWindow(oldWnd, SW_SHOW); - ::SetForegroundWindow(oldWnd); - ::SetFocus(oldWnd); - - GlassWindow * window = GlassWindow::FromHandle(oldWnd); - if (window) { - window->SetDelegateWindow(NULL); - } - - ::GetClientRect(oldWnd, &r); - JNIEnv* env = GetEnv(); - env->CallVoidMethod(GetView(), javaIDs.View.notifyResize, - r.right-r.left, r.bottom - r.top); - CheckAndClearException(env); - - SetGlassView(NULL); -} - -/* static */ -void FullScreenWindow::CalculateBounds(HWND hwnd, RECT * screenRect, - RECT * contentRect, BOOL keepRatio, const RECT & viewRect) -{ - MONITORINFOEX mix; - HMONITOR hMonitor = ::MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY); - - memset(&mix, 0, sizeof(MONITORINFOEX)); - mix.cbSize = sizeof(MONITORINFOEX); - ::GetMonitorInfo(hMonitor, &mix); - - ::CopyRect(screenRect, &mix.rcMonitor); - ::CopyRect(contentRect, &mix.rcMonitor); - - if (keepRatio) { - int viewWidth = viewRect.right - viewRect.left; - int viewHeight = viewRect.bottom - viewRect.top; - int screenWidth = screenRect->right - screenRect->left; - int screenHeight = screenRect->bottom - screenRect->top; - - float ratioWidth = (float)viewWidth / (float)screenWidth; - float ratioHeight = (float)viewHeight / (float)screenHeight; - - if (ratioWidth > ratioHeight) { - float ratio = (float)viewWidth / (float)viewHeight; - int height = (int)(screenWidth / ratio); - contentRect->top += (screenHeight - height) / 2; - contentRect->bottom = contentRect->top + height; - } else { - float ratio = (float)viewHeight / (float)viewWidth; - int width = (int)(screenHeight / ratio); - contentRect->left += (screenWidth - width) / 2; - contentRect->right = contentRect->left + width; - } - } -} - -void FullScreenWindow::InitWindowRect(BOOL keepRatio) -{ - RECT screenRect; - - FullScreenWindow::CalculateBounds(m_oldViewParent, &screenRect, - &m_windowRect, keepRatio, m_viewRect); - - m_bgWindow->SetWindowRect(&screenRect); -} - -void FullScreenWindow::ShowWindow(BOOL animate) -{ - m_bgWindow->ShowWindow(animate); - - RECT rect; - if (animate) { - CopyRect(&rect, &m_viewRect); - } else { - CopyRect(&rect, &m_windowRect); - } - - ::SetWindowPos(GetHWND(), HWND_TOPMOST, rect.left, rect.top, - rect.right - rect.left, rect.bottom - rect.top, - SWP_SHOWWINDOW); - ::SetForegroundWindow(GetHWND()); -} - -void FullScreenWindow::HideWindow() -{ - m_bgWindow->HideWindow(); - ::ShowWindow(GetHWND(), SW_HIDE); -} - -void FullScreenWindow::HandleSizeEvent() -{ -} - -void FullScreenWindow::HandleViewTimerEvent(HWND hwnd, UINT_PTR timerID) -{ - switch (timerID) { - case IDT_GLASS_ANIMATION_ENTER: - case IDT_GLASS_ANIMATION_EXIT: - break; - default: - ViewContainer::HandleViewTimerEvent(hwnd, timerID); - return; - } - - if (timerID == IDT_GLASS_ANIMATION_ENTER) { - if (m_animationStage > ANIMATION_MAX_ITERATION) { - StopAnimation(TRUE); - return; - } - } else if (timerID == IDT_GLASS_ANIMATION_EXIT) { - if (m_animationStage < 1) { - StopAnimation(FALSE); - return; - } - } - - m_bgWindow->UpdateAnimationOpacity(m_animationStage); - UpdateAnimationRect(); - - if (timerID == IDT_GLASS_ANIMATION_ENTER) { - m_animationStage++; - } else if (timerID == IDT_GLASS_ANIMATION_EXIT) { - m_animationStage--; - } -} - -LRESULT FullScreenWindow::WindowProc(UINT msg, WPARAM wParam, LPARAM lParam) -{ - MessageResult commonResult = BaseWnd::CommonWindowProc(msg, wParam, lParam); - if (commonResult.processed) { - return commonResult.result; - } - - switch (msg) { - case WM_TIMER: - HandleViewTimerEvent(GetHWND(), wParam); - break; - case WM_SIZE: - if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED) { - HandleSizeEvent(); - } - HandleViewSizeEvent(GetHWND(), msg, wParam, lParam); - break; - case WM_ACTIVATE: - { - // The fActive shouldn't be WA_INACTIVE && the window shouldn't be minimized: - const bool isFocusGained = LOWORD(wParam) != WA_INACTIVE && HIWORD(wParam) == 0; - - if (!isFocusGained && IsCommonDialogOwner()) { - // Remain in full screen while a file dialog is showing - break; - } - - HWND hWndInsertAfter = isFocusGained ? HWND_TOPMOST : HWND_BOTTOM; - - if (m_bgWindow) { - ::SetWindowPos(m_bgWindow->GetHWND(), hWndInsertAfter, 0, 0, 0, 0, - SWP_ASYNCWINDOWPOS | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOSIZE); - } - ::SetWindowPos(GetHWND(), hWndInsertAfter, 0, 0, 0, 0, - SWP_ASYNCWINDOWPOS | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOSIZE); - - GlassWindow * window = GlassWindow::FromHandle(m_oldViewParent); - if (window) { - window->HandleActivateEvent(isFocusGained ? - com_sun_glass_events_WindowEvent_FOCUS_GAINED : - com_sun_glass_events_WindowEvent_FOCUS_LOST); - - // Child windows don't have a taskbar button, therefore - // we force exiting from the FS mode if the window looses - // focus. - if (!isFocusGained) { - ExitFullScreenMode(FALSE); - } - } - } - break; - case WM_CLOSE: - { - GlassWindow * window = GlassWindow::FromHandle(m_oldViewParent); - ExitFullScreenMode(FALSE); - if (window) { - window->HandleCloseEvent(); - } - } - return 0; - case WM_INPUTLANGCHANGE: - HandleViewInputLangChange(GetHWND(), msg, wParam, lParam); - return 0; - case WM_PAINT: - HandleViewPaintEvent(GetHWND(), msg, wParam, lParam); - break; - case WM_CONTEXTMENU: - HandleViewMenuEvent(GetHWND(), msg, wParam, lParam); - break; - case WM_MOUSEMOVE: - case WM_LBUTTONDOWN: - case WM_LBUTTONUP: - case WM_LBUTTONDBLCLK: - case WM_RBUTTONDOWN: - case WM_RBUTTONUP: - case WM_RBUTTONDBLCLK: - case WM_MBUTTONDOWN: - case WM_MBUTTONUP: - case WM_MBUTTONDBLCLK: - case WM_XBUTTONDOWN: - case WM_XBUTTONUP: - case WM_XBUTTONDBLCLK: - case WM_MOUSEWHEEL: - case WM_MOUSEHWHEEL: - case WM_MOUSELEAVE: { - BOOL handled = HandleViewMouseEvent(GetHWND(), msg, wParam, lParam); - if (handled && msg == WM_RBUTTONUP) { - // By default, DefWindowProc() sends WM_CONTEXTMENU from WM_LBUTTONUP - // Since DefWindowProc() is not called, call the mouse menu handler directly - HandleViewMenuEvent(GetHWND(), WM_CONTEXTMENU, (WPARAM) GetHWND(), ::GetMessagePos ()); - //::DefWindowProc(GetHWND(), msg, wParam, lParam); - } - if (handled) { - // Do not call the DefWindowProc() for mouse events that were handled - return 0; - } - break; - } - case WM_CAPTURECHANGED: - ViewContainer::NotifyCaptureChanged(GetHWND(), (HWND)lParam); - break; - case WM_SYSKEYDOWN: - case WM_SYSKEYUP: - case WM_KEYDOWN: - case WM_KEYUP: - HandleViewKeyEvent(GetHWND(), msg, wParam, lParam); - // Always pass the message down to the DefWindowProc() to handle - // system keys (Alt+F4, etc.) - break; - case WM_DEADCHAR: - HandleViewDeadKeyEvent(GetHWND(), msg, wParam, lParam); - break; - case WM_CHAR: - case WM_IME_CHAR: - HandleViewTypedEvent(GetHWND(), msg, wParam, lParam); - return 0; - case WM_IME_COMPOSITION: - case WM_IME_ENDCOMPOSITION: - case WM_IME_NOTIFY: - case WM_IME_STARTCOMPOSITION: - if (HandleViewInputMethodEvent(GetHWND(), msg, wParam, lParam)) { - return 0; - } - break; - case WM_TOUCH: - HandleViewTouchEvent(GetHWND(), msg, wParam, lParam); - return 0; - case WM_GETOBJECT: { - LRESULT lr = HandleViewGetAccessible(GetHWND(), wParam, lParam); - if (lr) return lr; - break; - } - } - - return ::DefWindowProc(GetHWND(), msg, wParam, lParam); -} - -LPCTSTR FullScreenWindow::GetWindowClassNameSuffix() -{ - return szFullScreenWindowClassName; -} - -BOOL FullScreenWindow::EnterFullScreenMode(GlassView * view, BOOL animate, BOOL keepRatio) -{ - if (IsAnimationInProcess()) { - return TRUE; - } - - AttachView(view, keepRatio); - ShowWindow(animate); - - if (animate) { - StartAnimation(TRUE); - } - - return TRUE; -} - -void FullScreenWindow::StartAnimation(BOOL enter) -{ - m_animationStage = (enter ? 1 : ANIMATION_MAX_ITERATION); - UINT_PTR eventID = (enter ? IDT_GLASS_ANIMATION_ENTER : IDT_GLASS_ANIMATION_EXIT); - ::SetTimer(GetHWND(), eventID, ANIMATION_TIMER_ELAPSE, NULL); -} - -void FullScreenWindow::ExitFullScreenMode(BOOL animate) -{ - if (IsAnimationInProcess()) { - //TODO: the animation should be terminated - return; - } - - - if (animate) { - StartAnimation(FALSE); - } else { - GlassView * view = GetGlassView(); - DetachView(); - HideWindow(); - Close(); - } -} - -void FullScreenWindow::StopAnimation(BOOL enter) -{ - UINT_PTR eventID = (enter ? IDT_GLASS_ANIMATION_ENTER : IDT_GLASS_ANIMATION_EXIT); - ::KillTimer(GetHWND(), eventID); - - if (!enter) { - GlassView * view = GetGlassView(); - DetachView(); - HideWindow(); - Close(); - } -} - -BOOL FullScreenWindow::IsAnimationInProcess() -{ - if (m_animationStage >= 1 && m_animationStage <= ANIMATION_MAX_ITERATION) { - return true; - } - return false; -} - -void FullScreenWindow::UpdateAnimationRect() -{ - RECT rect; - float stage = (float)m_animationStage / (float)ANIMATION_MAX_ITERATION; - rect.left = m_viewRect.left + (long)((m_windowRect.left - m_viewRect.left) * stage); ; - rect.top = m_viewRect.top + (long)((m_windowRect.top - m_viewRect.top) * stage); - rect.right = m_viewRect.right + (long)((m_windowRect.right - m_viewRect.right) * stage); - rect.bottom = m_viewRect.bottom + (long)((m_windowRect.bottom - m_viewRect.bottom) * stage); - - ::SetWindowPos(GetHWND(), NULL, rect.left, rect.top, - rect.right - rect.left, rect.bottom - rect.top, - SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSENDCHANGING | SWP_DEFERERASE); -} - -// Transparent background window - -BackgroundWindow::BackgroundWindow() : BaseWnd() -{ - m_rect.left = 0; - m_rect.top = 0; - m_rect.right = 0; - m_rect.bottom = 0; -} - -BackgroundWindow::~BackgroundWindow() -{ -} - -HWND BackgroundWindow::Create() -{ - DWORD dwStyle = WS_POPUP | WS_CLIPCHILDREN; - DWORD dwExStyle = WS_EX_LAYERED | WS_EX_TOOLWINDOW; - - return BaseWnd::Create(NULL, 0, 0, 0, 0, - TEXT(""), dwExStyle, dwStyle, (HBRUSH)::GetStockObject(BLACK_BRUSH)); -} - -void BackgroundWindow::Close() -{ - ::DestroyWindow(GetHWND()); -} - -LRESULT BackgroundWindow::WindowProc(UINT msg, WPARAM wParam, LPARAM lParam) -{ - switch (msg) { - case WM_MOUSEACTIVATE: { - return MA_NOACTIVATE; - } - } - - return ::DefWindowProc(GetHWND(), msg, wParam, lParam); -} - -LPCTSTR BackgroundWindow::GetWindowClassNameSuffix() -{ - return szBackgroundWindowClassName; -} - -void BackgroundWindow::SetWindowRect(RECT * rect) -{ - ::CopyRect(&m_rect, rect); -} - -void BackgroundWindow::ShowWindow(BOOL animate) { - BYTE opacity = (animate ? 0x0 : 0xFF); - ::SetLayeredWindowAttributes(GetHWND(), RGB(0, 0, 0), opacity, LWA_ALPHA); - ::SetWindowPos(GetHWND(), HWND_TOPMOST, m_rect.left, m_rect.top, - m_rect.right - m_rect.left, m_rect.bottom - m_rect.top, - SWP_SHOWWINDOW | SWP_NOACTIVATE); -} - -void BackgroundWindow::HideWindow() -{ - ::ShowWindow(GetHWND(), SW_HIDE); -} - -void BackgroundWindow::UpdateAnimationOpacity(int animationStage) -{ - BYTE opacity = ((int)0xFF * animationStage) / ANIMATION_MAX_ITERATION; - ::SetLayeredWindowAttributes(GetHWND(), RGB(0, 0, 0), opacity, LWA_ALPHA); -} +/* + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "common.h" + +#include "GlassApplication.h" +#include "FullScreenWindow.h" +#include "GlassView.h" +#include "GlassDnD.h" +#include "GlassWindow.h" + +#include "com_sun_glass_events_WindowEvent.h" + +//TODO: is that possible to move all the code to the shared level? + +static LPCTSTR szFullScreenWindowClassName = TEXT("FullScreenWindowClass"); +static LPCTSTR szBackgroundWindowClassName = TEXT("BackgroundWindowClass"); + +static const UINT ANIMATION_MAX_ITERATION = 30; +static const UINT ANIMATION_TIMER_ELAPSE = USER_TIMER_MINIMUM; // 0xA ms + +FullScreenWindow::FullScreenWindow() : + BaseWnd(), + ViewContainer() +{ + m_animationStage = 0; + m_bgWindow = NULL; +} + +FullScreenWindow::~FullScreenWindow() +{ +} + +HWND FullScreenWindow::Create() +{ + m_bgWindow = new BackgroundWindow(); + m_bgWindow->Create(); + + DWORD dwStyle = WS_POPUP | WS_CLIPCHILDREN; + DWORD dwExStyle = 0; + + HWND hwnd = BaseWnd::Create(NULL, 0, 0, 0, 0, + TEXT(""), dwExStyle, dwStyle, NULL); + + ViewContainer::InitDropTarget(hwnd); + ViewContainer::InitManipProcessor(hwnd); + + return hwnd; +} + +void FullScreenWindow::Close() +{ + if (m_bgWindow) { + m_bgWindow->Close(); + m_bgWindow = NULL; + } + + ViewContainer::ReleaseDropTarget(); + ViewContainer::ReleaseManipProcessor(); + + ::DestroyWindow(GetHWND()); +} + +/* static */ +void FullScreenWindow::ClientRectInScreen(HWND hwnd, RECT * rect) +{ + ::GetClientRect(hwnd, rect); + ::MapWindowPoints(hwnd, (HWND)NULL, (LPPOINT)rect, (sizeof(RECT)/sizeof(POINT))); +} + +void FullScreenWindow::AttachView(GlassView * view, BOOL keepRatio) +{ + SetGlassView(view); + + m_oldViewParent = GetGlassView()->GetHostHwnd(); + + FullScreenWindow::ClientRectInScreen(m_oldViewParent, &m_viewRect); + + InitWindowRect(keepRatio); + + GlassWindow * window = GlassWindow::FromHandle(m_oldViewParent); + if (window) { + window->SetDelegateWindow(GetHWND()); + } + + ::ShowWindow(m_oldViewParent, SW_HIDE); + GetGlassView()->SetHostHwnd(GetHWND()); +} + +void FullScreenWindow::DetachView() +{ + RECT r; + HWND oldWnd = m_oldViewParent; + GlassView* view = GetGlassView(); + + m_oldViewParent = NULL; + + view->SetHostHwnd(oldWnd); + + ::ShowWindow(oldWnd, SW_SHOW); + ::SetForegroundWindow(oldWnd); + ::SetFocus(oldWnd); + + GlassWindow * window = GlassWindow::FromHandle(oldWnd); + if (window) { + window->SetDelegateWindow(NULL); + } + + ::GetClientRect(oldWnd, &r); + JNIEnv* env = GetEnv(); + env->CallVoidMethod(GetView(), javaIDs.View.notifyResize, + r.right-r.left, r.bottom - r.top); + CheckAndClearException(env); + + SetGlassView(NULL); +} + +/* static */ +void FullScreenWindow::CalculateBounds(HWND hwnd, RECT * screenRect, + RECT * contentRect, BOOL keepRatio, const RECT & viewRect) +{ + MONITORINFOEX mix; + HMONITOR hMonitor = ::MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY); + + memset(&mix, 0, sizeof(MONITORINFOEX)); + mix.cbSize = sizeof(MONITORINFOEX); + ::GetMonitorInfo(hMonitor, &mix); + + ::CopyRect(screenRect, &mix.rcMonitor); + ::CopyRect(contentRect, &mix.rcMonitor); + + if (keepRatio) { + int viewWidth = viewRect.right - viewRect.left; + int viewHeight = viewRect.bottom - viewRect.top; + int screenWidth = screenRect->right - screenRect->left; + int screenHeight = screenRect->bottom - screenRect->top; + + float ratioWidth = (float)viewWidth / (float)screenWidth; + float ratioHeight = (float)viewHeight / (float)screenHeight; + + if (ratioWidth > ratioHeight) { + float ratio = (float)viewWidth / (float)viewHeight; + int height = (int)(screenWidth / ratio); + contentRect->top += (screenHeight - height) / 2; + contentRect->bottom = contentRect->top + height; + } else { + float ratio = (float)viewHeight / (float)viewWidth; + int width = (int)(screenHeight / ratio); + contentRect->left += (screenWidth - width) / 2; + contentRect->right = contentRect->left + width; + } + } +} + +void FullScreenWindow::InitWindowRect(BOOL keepRatio) +{ + RECT screenRect; + + FullScreenWindow::CalculateBounds(m_oldViewParent, &screenRect, + &m_windowRect, keepRatio, m_viewRect); + + m_bgWindow->SetWindowRect(&screenRect); +} + +void FullScreenWindow::ShowWindow(BOOL animate) +{ + m_bgWindow->ShowWindow(animate); + + RECT rect; + if (animate) { + CopyRect(&rect, &m_viewRect); + } else { + CopyRect(&rect, &m_windowRect); + } + + ::SetWindowPos(GetHWND(), HWND_TOPMOST, rect.left, rect.top, + rect.right - rect.left, rect.bottom - rect.top, + SWP_SHOWWINDOW); + ::SetForegroundWindow(GetHWND()); +} + +void FullScreenWindow::HideWindow() +{ + m_bgWindow->HideWindow(); + ::ShowWindow(GetHWND(), SW_HIDE); +} + +void FullScreenWindow::HandleSizeEvent() +{ +} + +void FullScreenWindow::HandleViewTimerEvent(HWND hwnd, UINT_PTR timerID) +{ + switch (timerID) { + case IDT_GLASS_ANIMATION_ENTER: + case IDT_GLASS_ANIMATION_EXIT: + break; + default: + ViewContainer::HandleViewTimerEvent(hwnd, timerID); + return; + } + + if (timerID == IDT_GLASS_ANIMATION_ENTER) { + if (m_animationStage > ANIMATION_MAX_ITERATION) { + StopAnimation(TRUE); + return; + } + } else if (timerID == IDT_GLASS_ANIMATION_EXIT) { + if (m_animationStage < 1) { + StopAnimation(FALSE); + return; + } + } + + m_bgWindow->UpdateAnimationOpacity(m_animationStage); + UpdateAnimationRect(); + + if (timerID == IDT_GLASS_ANIMATION_ENTER) { + m_animationStage++; + } else if (timerID == IDT_GLASS_ANIMATION_EXIT) { + m_animationStage--; + } +} + +LRESULT FullScreenWindow::WindowProc(UINT msg, WPARAM wParam, LPARAM lParam) +{ + MessageResult commonResult = BaseWnd::CommonWindowProc(msg, wParam, lParam); + if (commonResult.processed) { + return commonResult.result; + } + + switch (msg) { + case WM_TIMER: + HandleViewTimerEvent(GetHWND(), wParam); + break; + case WM_SIZE: + if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED) { + HandleSizeEvent(); + } + HandleViewSizeEvent(GetHWND(), msg, wParam, lParam); + break; + case WM_ACTIVATE: + { + // The fActive shouldn't be WA_INACTIVE && the window shouldn't be minimized: + const bool isFocusGained = LOWORD(wParam) != WA_INACTIVE && HIWORD(wParam) == 0; + + if (!isFocusGained && IsCommonDialogOwner()) { + // Remain in full screen while a file dialog is showing + break; + } + + HWND hWndInsertAfter = isFocusGained ? HWND_TOPMOST : HWND_BOTTOM; + + if (m_bgWindow) { + ::SetWindowPos(m_bgWindow->GetHWND(), hWndInsertAfter, 0, 0, 0, 0, + SWP_ASYNCWINDOWPOS | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOSIZE); + } + ::SetWindowPos(GetHWND(), hWndInsertAfter, 0, 0, 0, 0, + SWP_ASYNCWINDOWPOS | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOSIZE); + + GlassWindow * window = GlassWindow::FromHandle(m_oldViewParent); + if (window) { + window->HandleActivateEvent(isFocusGained ? + com_sun_glass_events_WindowEvent_FOCUS_GAINED : + com_sun_glass_events_WindowEvent_FOCUS_LOST); + + // Child windows don't have a taskbar button, therefore + // we force exiting from the FS mode if the window looses + // focus. + if (!isFocusGained) { + ExitFullScreenMode(FALSE); + } + } + } + break; + case WM_CLOSE: + { + GlassWindow * window = GlassWindow::FromHandle(m_oldViewParent); + ExitFullScreenMode(FALSE); + if (window) { + window->HandleCloseEvent(); + } + } + return 0; + case WM_INPUTLANGCHANGE: + HandleViewInputLangChange(GetHWND(), msg, wParam, lParam); + return 0; + case WM_PAINT: + HandleViewPaintEvent(GetHWND(), msg, wParam, lParam); + break; + case WM_CONTEXTMENU: + HandleViewMenuEvent(GetHWND(), msg, wParam, lParam); + break; + case WM_MOUSEMOVE: + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_LBUTTONDBLCLK: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + case WM_RBUTTONDBLCLK: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + case WM_MBUTTONDBLCLK: + case WM_XBUTTONDOWN: + case WM_XBUTTONUP: + case WM_XBUTTONDBLCLK: + case WM_MOUSEWHEEL: + case WM_MOUSEHWHEEL: + case WM_MOUSELEAVE: { + BOOL handled = HandleViewMouseEvent(GetHWND(), msg, wParam, lParam, FALSE); + if (handled && msg == WM_RBUTTONUP) { + // By default, DefWindowProc() sends WM_CONTEXTMENU from WM_LBUTTONUP + // Since DefWindowProc() is not called, call the mouse menu handler directly + HandleViewMenuEvent(GetHWND(), WM_CONTEXTMENU, (WPARAM) GetHWND(), ::GetMessagePos ()); + //::DefWindowProc(GetHWND(), msg, wParam, lParam); + } + if (handled) { + // Do not call the DefWindowProc() for mouse events that were handled + return 0; + } + break; + } + case WM_CAPTURECHANGED: + ViewContainer::NotifyCaptureChanged(GetHWND(), (HWND)lParam); + break; + case WM_SYSKEYDOWN: + case WM_SYSKEYUP: + case WM_KEYDOWN: + case WM_KEYUP: + HandleViewKeyEvent(GetHWND(), msg, wParam, lParam); + // Always pass the message down to the DefWindowProc() to handle + // system keys (Alt+F4, etc.) + break; + case WM_DEADCHAR: + HandleViewDeadKeyEvent(GetHWND(), msg, wParam, lParam); + break; + case WM_CHAR: + case WM_IME_CHAR: + HandleViewTypedEvent(GetHWND(), msg, wParam, lParam); + return 0; + case WM_IME_COMPOSITION: + case WM_IME_ENDCOMPOSITION: + case WM_IME_NOTIFY: + case WM_IME_STARTCOMPOSITION: + if (HandleViewInputMethodEvent(GetHWND(), msg, wParam, lParam)) { + return 0; + } + break; + case WM_TOUCH: + HandleViewTouchEvent(GetHWND(), msg, wParam, lParam); + return 0; + case WM_GETOBJECT: { + LRESULT lr = HandleViewGetAccessible(GetHWND(), wParam, lParam); + if (lr) return lr; + break; + } + } + + return ::DefWindowProc(GetHWND(), msg, wParam, lParam); +} + +LPCTSTR FullScreenWindow::GetWindowClassNameSuffix() +{ + return szFullScreenWindowClassName; +} + +BOOL FullScreenWindow::EnterFullScreenMode(GlassView * view, BOOL animate, BOOL keepRatio) +{ + if (IsAnimationInProcess()) { + return TRUE; + } + + AttachView(view, keepRatio); + ShowWindow(animate); + + if (animate) { + StartAnimation(TRUE); + } + + return TRUE; +} + +void FullScreenWindow::StartAnimation(BOOL enter) +{ + m_animationStage = (enter ? 1 : ANIMATION_MAX_ITERATION); + UINT_PTR eventID = (enter ? IDT_GLASS_ANIMATION_ENTER : IDT_GLASS_ANIMATION_EXIT); + ::SetTimer(GetHWND(), eventID, ANIMATION_TIMER_ELAPSE, NULL); +} + +void FullScreenWindow::ExitFullScreenMode(BOOL animate) +{ + if (IsAnimationInProcess()) { + //TODO: the animation should be terminated + return; + } + + + if (animate) { + StartAnimation(FALSE); + } else { + GlassView * view = GetGlassView(); + DetachView(); + HideWindow(); + Close(); + } +} + +void FullScreenWindow::StopAnimation(BOOL enter) +{ + UINT_PTR eventID = (enter ? IDT_GLASS_ANIMATION_ENTER : IDT_GLASS_ANIMATION_EXIT); + ::KillTimer(GetHWND(), eventID); + + if (!enter) { + GlassView * view = GetGlassView(); + DetachView(); + HideWindow(); + Close(); + } +} + +BOOL FullScreenWindow::IsAnimationInProcess() +{ + if (m_animationStage >= 1 && m_animationStage <= ANIMATION_MAX_ITERATION) { + return true; + } + return false; +} + +void FullScreenWindow::UpdateAnimationRect() +{ + RECT rect; + float stage = (float)m_animationStage / (float)ANIMATION_MAX_ITERATION; + rect.left = m_viewRect.left + (long)((m_windowRect.left - m_viewRect.left) * stage); ; + rect.top = m_viewRect.top + (long)((m_windowRect.top - m_viewRect.top) * stage); + rect.right = m_viewRect.right + (long)((m_windowRect.right - m_viewRect.right) * stage); + rect.bottom = m_viewRect.bottom + (long)((m_windowRect.bottom - m_viewRect.bottom) * stage); + + ::SetWindowPos(GetHWND(), NULL, rect.left, rect.top, + rect.right - rect.left, rect.bottom - rect.top, + SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSENDCHANGING | SWP_DEFERERASE); +} + +// Transparent background window + +BackgroundWindow::BackgroundWindow() : BaseWnd() +{ + m_rect.left = 0; + m_rect.top = 0; + m_rect.right = 0; + m_rect.bottom = 0; +} + +BackgroundWindow::~BackgroundWindow() +{ +} + +HWND BackgroundWindow::Create() +{ + DWORD dwStyle = WS_POPUP | WS_CLIPCHILDREN; + DWORD dwExStyle = WS_EX_LAYERED | WS_EX_TOOLWINDOW; + + return BaseWnd::Create(NULL, 0, 0, 0, 0, + TEXT(""), dwExStyle, dwStyle, (HBRUSH)::GetStockObject(BLACK_BRUSH)); +} + +void BackgroundWindow::Close() +{ + ::DestroyWindow(GetHWND()); +} + +LRESULT BackgroundWindow::WindowProc(UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_MOUSEACTIVATE: { + return MA_NOACTIVATE; + } + } + + return ::DefWindowProc(GetHWND(), msg, wParam, lParam); +} + +LPCTSTR BackgroundWindow::GetWindowClassNameSuffix() +{ + return szBackgroundWindowClassName; +} + +void BackgroundWindow::SetWindowRect(RECT * rect) +{ + ::CopyRect(&m_rect, rect); +} + +void BackgroundWindow::ShowWindow(BOOL animate) { + BYTE opacity = (animate ? 0x0 : 0xFF); + ::SetLayeredWindowAttributes(GetHWND(), RGB(0, 0, 0), opacity, LWA_ALPHA); + ::SetWindowPos(GetHWND(), HWND_TOPMOST, m_rect.left, m_rect.top, + m_rect.right - m_rect.left, m_rect.bottom - m_rect.top, + SWP_SHOWWINDOW | SWP_NOACTIVATE); +} + +void BackgroundWindow::HideWindow() +{ + ::ShowWindow(GetHWND(), SW_HIDE); +} + +void BackgroundWindow::UpdateAnimationOpacity(int animationStage) +{ + BYTE opacity = ((int)0xFF * animationStage) / ANIMATION_MAX_ITERATION; + ::SetLayeredWindowAttributes(GetHWND(), RGB(0, 0, 0), opacity, LWA_ALPHA); +} diff --git a/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp b/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp index cd8748fa0b3..3f180e047d8 100644 --- a/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp +++ b/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -550,7 +550,7 @@ LRESULT GlassWindow::WindowProc(UINT msg, WPARAM wParam, LPARAM lParam) if (m_isExtended) { HandleNonClientMouseEvents(msg, wParam, lParam); - // We need to handle clicks on the min/max/close regions, as otherwise Windows will + // We need to return 0 for clicks on the min/max/close regions, as otherwise Windows will // draw very ugly buttons on top of our window. if (wParam == HTMINBUTTON || wParam == HTMAXBUTTON || wParam == HTCLOSE) { return 0; @@ -609,7 +609,7 @@ bool GlassWindow::HandleMouseEvents(UINT msg, WPARAM wParam, LPARAM lParam) return true; } - BOOL handled = HandleViewMouseEvent(GetHWND(), msg, wParam, lParam); + BOOL handled = HandleViewMouseEvent(GetHWND(), msg, wParam, lParam, m_isExtended); if (handled && msg == WM_RBUTTONUP) { // By default, DefWindowProc() sends WM_CONTEXTMENU from WM_LBUTTONUP // Since DefWindowProc() is not called, call the mouse menu handler directly diff --git a/modules/javafx.graphics/src/main/native-glass/win/ViewContainer.cpp b/modules/javafx.graphics/src/main/native-glass/win/ViewContainer.cpp index f2ab8222131..8eb09ebc3a6 100644 --- a/modules/javafx.graphics/src/main/native-glass/win/ViewContainer.cpp +++ b/modules/javafx.graphics/src/main/native-glass/win/ViewContainer.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -690,7 +690,7 @@ void ViewContainer::HandleViewTypedEvent(HWND hwnd, UINT msg, WPARAM wParam, LPA SendViewTypedEvent(repCount, wChar); } -BOOL ViewContainer::HandleViewMouseEvent(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +BOOL ViewContainer::HandleViewMouseEvent(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, BOOL extendedWindow) { if (!GetGlassView()) { return FALSE; @@ -701,7 +701,54 @@ BOOL ViewContainer::HandleViewMouseEvent(HWND hwnd, UINT msg, WPARAM wParam, LPA POINT pt; // client coords jdouble wheelRotation = 0.0; - if (msg == WM_MOUSELEAVE) { + // Windows with the EXTENDED style have an unusual anatomy: the entire window (excluding borders) comprises + // the client area with regards to geometry, but not with regards to hit testing. The title bar is classified + // as a non-client hit-testing area (by responding to the WM_NCHITTEST message), which enables window manager + // interactions (dragging, snap layouts, etc). + // When the mouse cursor moves from the client area to the title bar, we technically receive a WM_MOUSELEAVE + // message that would normally cause a MouseEvent.EXIT event to be emitted. However, from the point of view + // of the JavaFX application, the mouse cursor is still on the client-side title bar and therefore we wouldn't + // expect to receive an MouseEvent.EXIT event. The following code detects this situation and prevents the + // MouseEvent.EXIT event from being fired. + if (msg == WM_MOUSELEAVE && extendedWindow) { + DWORD msgPos = ::GetMessagePos(); // screen coordinates + pt.x = GET_X_LPARAM(msgPos); + pt.y = GET_Y_LPARAM(msgPos); + RECT windowRect; + + // We know that the cursor has moved from the client area to the title bar when the following two + // conditions are met: + // 1. The window under the cursor is our own window. This allows us to disambiguate the situation + // when the cursor was moved to another overlapping window that just happens to be placed over + // our title bar. + // 2. The cursor position is still within the client area of our window. This allows us to detect + // when the cursor was moved to the resize border of our window, which isn't part of the client + // area and should therefore emit an EXIT event. + if (::ChildWindowFromPointEx(::GetDesktopWindow(), pt, CWP_SKIPINVISIBLE) == hwnd + && ::GetClientRect(hwnd, &windowRect) + && ::ClientToScreen(hwnd, reinterpret_cast(&windowRect.left)) + && ::ClientToScreen(hwnd, reinterpret_cast(&windowRect.right)) + && ::PtInRect(&windowRect, pt)) { // pt is still in screen coordinates here + TRACKMOUSEEVENT trackData; + trackData.cbSize = sizeof(trackData); + trackData.dwFlags = TME_LEAVE | TME_NONCLIENT; + trackData.hwndTrack = hwnd; + trackData.dwHoverTime = HOVER_DEFAULT; + + // The cursor is now on the non-client hit-testing area of our window, and we need to enable + // non-client mouse tracking to get a WM_NCMOUSELEAVE message when the cursor leaves the + // non-client hit-testing area. + if (::TrackMouseEvent(&trackData)) { + m_bTrackingMouse = TRUE; + } + } else { + type = com_sun_glass_events_MouseEvent_EXIT; + m_bTrackingMouse = FALSE; + m_lastMouseMovePosition = -1; + } + + ::ScreenToClient(hwnd, &pt); + } else if (msg == WM_MOUSELEAVE) { type = com_sun_glass_events_MouseEvent_EXIT; // get the coords (the message does not contain them) lParam = ::GetMessagePos(); @@ -944,14 +991,46 @@ void ViewContainer::HandleViewNonClientMouseEvent(HWND hwnd, UINT msg, WPARAM wP int button = com_sun_glass_events_MouseEvent_BUTTON_NONE; POINT pt; // client coords + // Windows with the EXTENDED style have an unusual anatomy: the entire window (excluding borders) comprises + // the client area with regards to geometry, but not with regards to hit testing. The title bar is classified + // as a non-client hit-testing area (by responding to the WM_NCHITTEST message), which enables window manager + // interactions (dragging, snap layouts, etc). + // When the mouse cursor moves from the title bar to the client area, we technically receive a WM_NCMOUSELEAVE + // message that would normally cause a MouseEvent.EXIT event to be emitted. However, from the point of view + // of the JavaFX application, the mouse cursor is still on the window's client area and therefore we wouldn't + // expect to receive an MouseEvent.EXIT event. The following code detects this situation and prevents the + // MouseEvent.EXIT event from being fired. if (msg == WM_NCMOUSELEAVE) { - type = com_sun_glass_events_MouseEvent_NC_EXIT; - pt.x = GET_X_LPARAM(lParam); - pt.y = GET_Y_LPARAM(lParam); - ::MapWindowPoints(NULL, hwnd, &pt, 1); - m_bTrackingMouse = FALSE; - m_lastMouseMovePosition = -1; - } else if (msg >= WM_NCMOUSEMOVE && msg <= WM_NCXBUTTONDBLCLK) { + DWORD msgPos = ::GetMessagePos(); // screen coordinates + pt.x = GET_X_LPARAM(msgPos); + pt.y = GET_Y_LPARAM(msgPos); + + // Skip the MouseEvent.EXIT event when the cursor is now directly over our window. In contrast to + // similar code in ViewContainer::HandleViewMouseEvent(), we don't need to test whether the cursor + // is within the client area. If the cursor has left the non-client area, but is still directly over + // our window, it can't be anywhere else but in the client area. + if (::ChildWindowFromPointEx(::GetDesktopWindow(), pt, CWP_SKIPINVISIBLE) == hwnd) { + TRACKMOUSEEVENT trackData; + trackData.cbSize = sizeof(trackData); + trackData.dwFlags = TME_LEAVE; + trackData.hwndTrack = hwnd; + trackData.dwHoverTime = HOVER_DEFAULT; + + // Since the cursor is now on the client area of our window, we need to enable mouse tracking + // to get a WM_MOUSELEAVE message when the cursor leaves the client area. + if (::TrackMouseEvent(&trackData)) { + m_bTrackingMouse = TRUE; + } + } else { + type = com_sun_glass_events_MouseEvent_EXIT; + m_bTrackingMouse = FALSE; + m_lastMouseMovePosition = -1; + } + + ::ScreenToClient(hwnd, &pt); + } else if (msg >= WM_NCMOUSEMOVE + && msg <= WM_NCXBUTTONDBLCLK + && (wParam == HTCAPTION || wParam == HTMINBUTTON || wParam == HTMAXBUTTON || wParam == HTCLOSE)) { pt.x = GET_X_LPARAM(lParam); pt.y = GET_Y_LPARAM(lParam); ::MapWindowPoints(NULL, hwnd, &pt, 1); @@ -966,40 +1045,40 @@ void ViewContainer::HandleViewNonClientMouseEvent(HWND hwnd, UINT msg, WPARAM wP } m_lastMouseMovePosition = lParam; - type = com_sun_glass_events_MouseEvent_NC_MOVE; + type = com_sun_glass_events_MouseEvent_MOVE; break; case WM_NCLBUTTONDOWN: - type = com_sun_glass_events_MouseEvent_NC_DOWN; + type = com_sun_glass_events_MouseEvent_DOWN; button = com_sun_glass_events_MouseEvent_BUTTON_LEFT; break; case WM_NCLBUTTONUP: - type = com_sun_glass_events_MouseEvent_NC_UP; + type = com_sun_glass_events_MouseEvent_UP; button = com_sun_glass_events_MouseEvent_BUTTON_LEFT; break; case WM_NCRBUTTONDOWN: - type = com_sun_glass_events_MouseEvent_NC_DOWN; + type = com_sun_glass_events_MouseEvent_DOWN; button = com_sun_glass_events_MouseEvent_BUTTON_RIGHT; break; case WM_NCRBUTTONUP: - type = com_sun_glass_events_MouseEvent_NC_UP; + type = com_sun_glass_events_MouseEvent_UP; button = com_sun_glass_events_MouseEvent_BUTTON_RIGHT; break; case WM_NCMBUTTONDOWN: - type = com_sun_glass_events_MouseEvent_NC_DOWN; + type = com_sun_glass_events_MouseEvent_DOWN; button = com_sun_glass_events_MouseEvent_BUTTON_OTHER; break; case WM_NCMBUTTONUP: - type = com_sun_glass_events_MouseEvent_NC_UP; + type = com_sun_glass_events_MouseEvent_UP; button = com_sun_glass_events_MouseEvent_BUTTON_OTHER; break; case WM_NCXBUTTONDOWN: - type = com_sun_glass_events_MouseEvent_NC_DOWN; + type = com_sun_glass_events_MouseEvent_DOWN; button = GET_XBUTTON_WPARAM(wParam) == XBUTTON1 ? com_sun_glass_events_MouseEvent_BUTTON_BACK : com_sun_glass_events_MouseEvent_BUTTON_FORWARD; break; case WM_NCXBUTTONUP: - type = com_sun_glass_events_MouseEvent_NC_UP; + type = com_sun_glass_events_MouseEvent_UP; button = GET_XBUTTON_WPARAM(wParam) == XBUTTON1 ? com_sun_glass_events_MouseEvent_BUTTON_BACK : com_sun_glass_events_MouseEvent_BUTTON_FORWARD; @@ -1028,7 +1107,7 @@ void ViewContainer::HandleViewNonClientMouseEvent(HWND hwnd, UINT msg, WPARAM wP jboolean isSynthesized = jboolean(IsTouchEvent()); JNIEnv* env = GetEnv(); - if (!m_bTrackingMouse && type != com_sun_glass_events_MouseEvent_NC_EXIT) { + if (!m_bTrackingMouse && type != com_sun_glass_events_MouseEvent_EXIT) { TRACKMOUSEEVENT trackData; trackData.cbSize = sizeof(trackData); trackData.dwFlags = TME_LEAVE | TME_NONCLIENT; @@ -1041,7 +1120,7 @@ void ViewContainer::HandleViewNonClientMouseEvent(HWND hwnd, UINT msg, WPARAM wP } env->CallVoidMethod(GetView(), javaIDs.View.notifyMouse, - com_sun_glass_events_MouseEvent_NC_ENTER, + com_sun_glass_events_MouseEvent_ENTER, com_sun_glass_events_MouseEvent_BUTTON_NONE, pt.x, pt.y, ptAbs.x, ptAbs.y, jModifiers, JNI_FALSE, isSynthesized); @@ -1051,7 +1130,7 @@ void ViewContainer::HandleViewNonClientMouseEvent(HWND hwnd, UINT msg, WPARAM wP env->CallVoidMethod(GetView(), javaIDs.View.notifyMouse, type, button, pt.x, pt.y, ptAbs.x, ptAbs.y, jModifiers, - type == com_sun_glass_events_MouseEvent_NC_UP && button == com_sun_glass_events_MouseEvent_BUTTON_RIGHT, + type == com_sun_glass_events_MouseEvent_UP && button == com_sun_glass_events_MouseEvent_BUTTON_RIGHT, isSynthesized); CheckAndClearException(env); } diff --git a/modules/javafx.graphics/src/main/native-glass/win/ViewContainer.h b/modules/javafx.graphics/src/main/native-glass/win/ViewContainer.h index 0eea989d0c0..ca44e3594da 100644 --- a/modules/javafx.graphics/src/main/native-glass/win/ViewContainer.h +++ b/modules/javafx.graphics/src/main/native-glass/win/ViewContainer.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -69,7 +69,7 @@ class ViewContainer { void HandleViewKeyEvent(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); void HandleViewDeadKeyEvent(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); void HandleViewTypedEvent(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); - BOOL HandleViewMouseEvent(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + BOOL HandleViewMouseEvent(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, BOOL extendedWindow); void HandleViewNonClientMouseEvent(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); BOOL HandleViewInputMethodEvent(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); LRESULT HandleViewGetAccessible(HWND hwnd, WPARAM wParam, LPARAM lParam); diff --git a/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubToolkit.java b/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubToolkit.java index 4c84b92f661..c46565d1192 100644 --- a/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubToolkit.java +++ b/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubToolkit.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -119,8 +119,9 @@ public boolean init() { } @Override - public TKStage createTKStage(Window peerWindow, StageStyle stageStyle, boolean primary, Modality modality, TKStage owner, boolean rtl) { - + public TKStage createTKStage(Window peerWindow, StageStyle stageStyle, boolean primary, + boolean nonClientOverlay, Modality modality, TKStage owner, + boolean rtl) { return new StubStage(); } From 356a78ef7ef36db385fc1ef5661ca70d8819d103 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Mon, 27 Jan 2025 07:45:28 +0100 Subject: [PATCH 38/73] fix line endings --- .../native-glass/win/FullScreenWindow.cpp | 1062 ++++++++--------- 1 file changed, 531 insertions(+), 531 deletions(-) diff --git a/modules/javafx.graphics/src/main/native-glass/win/FullScreenWindow.cpp b/modules/javafx.graphics/src/main/native-glass/win/FullScreenWindow.cpp index 4fce26cddb1..5286f72aebb 100644 --- a/modules/javafx.graphics/src/main/native-glass/win/FullScreenWindow.cpp +++ b/modules/javafx.graphics/src/main/native-glass/win/FullScreenWindow.cpp @@ -1,531 +1,531 @@ -/* - * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -#include "common.h" - -#include "GlassApplication.h" -#include "FullScreenWindow.h" -#include "GlassView.h" -#include "GlassDnD.h" -#include "GlassWindow.h" - -#include "com_sun_glass_events_WindowEvent.h" - -//TODO: is that possible to move all the code to the shared level? - -static LPCTSTR szFullScreenWindowClassName = TEXT("FullScreenWindowClass"); -static LPCTSTR szBackgroundWindowClassName = TEXT("BackgroundWindowClass"); - -static const UINT ANIMATION_MAX_ITERATION = 30; -static const UINT ANIMATION_TIMER_ELAPSE = USER_TIMER_MINIMUM; // 0xA ms - -FullScreenWindow::FullScreenWindow() : - BaseWnd(), - ViewContainer() -{ - m_animationStage = 0; - m_bgWindow = NULL; -} - -FullScreenWindow::~FullScreenWindow() -{ -} - -HWND FullScreenWindow::Create() -{ - m_bgWindow = new BackgroundWindow(); - m_bgWindow->Create(); - - DWORD dwStyle = WS_POPUP | WS_CLIPCHILDREN; - DWORD dwExStyle = 0; - - HWND hwnd = BaseWnd::Create(NULL, 0, 0, 0, 0, - TEXT(""), dwExStyle, dwStyle, NULL); - - ViewContainer::InitDropTarget(hwnd); - ViewContainer::InitManipProcessor(hwnd); - - return hwnd; -} - -void FullScreenWindow::Close() -{ - if (m_bgWindow) { - m_bgWindow->Close(); - m_bgWindow = NULL; - } - - ViewContainer::ReleaseDropTarget(); - ViewContainer::ReleaseManipProcessor(); - - ::DestroyWindow(GetHWND()); -} - -/* static */ -void FullScreenWindow::ClientRectInScreen(HWND hwnd, RECT * rect) -{ - ::GetClientRect(hwnd, rect); - ::MapWindowPoints(hwnd, (HWND)NULL, (LPPOINT)rect, (sizeof(RECT)/sizeof(POINT))); -} - -void FullScreenWindow::AttachView(GlassView * view, BOOL keepRatio) -{ - SetGlassView(view); - - m_oldViewParent = GetGlassView()->GetHostHwnd(); - - FullScreenWindow::ClientRectInScreen(m_oldViewParent, &m_viewRect); - - InitWindowRect(keepRatio); - - GlassWindow * window = GlassWindow::FromHandle(m_oldViewParent); - if (window) { - window->SetDelegateWindow(GetHWND()); - } - - ::ShowWindow(m_oldViewParent, SW_HIDE); - GetGlassView()->SetHostHwnd(GetHWND()); -} - -void FullScreenWindow::DetachView() -{ - RECT r; - HWND oldWnd = m_oldViewParent; - GlassView* view = GetGlassView(); - - m_oldViewParent = NULL; - - view->SetHostHwnd(oldWnd); - - ::ShowWindow(oldWnd, SW_SHOW); - ::SetForegroundWindow(oldWnd); - ::SetFocus(oldWnd); - - GlassWindow * window = GlassWindow::FromHandle(oldWnd); - if (window) { - window->SetDelegateWindow(NULL); - } - - ::GetClientRect(oldWnd, &r); - JNIEnv* env = GetEnv(); - env->CallVoidMethod(GetView(), javaIDs.View.notifyResize, - r.right-r.left, r.bottom - r.top); - CheckAndClearException(env); - - SetGlassView(NULL); -} - -/* static */ -void FullScreenWindow::CalculateBounds(HWND hwnd, RECT * screenRect, - RECT * contentRect, BOOL keepRatio, const RECT & viewRect) -{ - MONITORINFOEX mix; - HMONITOR hMonitor = ::MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY); - - memset(&mix, 0, sizeof(MONITORINFOEX)); - mix.cbSize = sizeof(MONITORINFOEX); - ::GetMonitorInfo(hMonitor, &mix); - - ::CopyRect(screenRect, &mix.rcMonitor); - ::CopyRect(contentRect, &mix.rcMonitor); - - if (keepRatio) { - int viewWidth = viewRect.right - viewRect.left; - int viewHeight = viewRect.bottom - viewRect.top; - int screenWidth = screenRect->right - screenRect->left; - int screenHeight = screenRect->bottom - screenRect->top; - - float ratioWidth = (float)viewWidth / (float)screenWidth; - float ratioHeight = (float)viewHeight / (float)screenHeight; - - if (ratioWidth > ratioHeight) { - float ratio = (float)viewWidth / (float)viewHeight; - int height = (int)(screenWidth / ratio); - contentRect->top += (screenHeight - height) / 2; - contentRect->bottom = contentRect->top + height; - } else { - float ratio = (float)viewHeight / (float)viewWidth; - int width = (int)(screenHeight / ratio); - contentRect->left += (screenWidth - width) / 2; - contentRect->right = contentRect->left + width; - } - } -} - -void FullScreenWindow::InitWindowRect(BOOL keepRatio) -{ - RECT screenRect; - - FullScreenWindow::CalculateBounds(m_oldViewParent, &screenRect, - &m_windowRect, keepRatio, m_viewRect); - - m_bgWindow->SetWindowRect(&screenRect); -} - -void FullScreenWindow::ShowWindow(BOOL animate) -{ - m_bgWindow->ShowWindow(animate); - - RECT rect; - if (animate) { - CopyRect(&rect, &m_viewRect); - } else { - CopyRect(&rect, &m_windowRect); - } - - ::SetWindowPos(GetHWND(), HWND_TOPMOST, rect.left, rect.top, - rect.right - rect.left, rect.bottom - rect.top, - SWP_SHOWWINDOW); - ::SetForegroundWindow(GetHWND()); -} - -void FullScreenWindow::HideWindow() -{ - m_bgWindow->HideWindow(); - ::ShowWindow(GetHWND(), SW_HIDE); -} - -void FullScreenWindow::HandleSizeEvent() -{ -} - -void FullScreenWindow::HandleViewTimerEvent(HWND hwnd, UINT_PTR timerID) -{ - switch (timerID) { - case IDT_GLASS_ANIMATION_ENTER: - case IDT_GLASS_ANIMATION_EXIT: - break; - default: - ViewContainer::HandleViewTimerEvent(hwnd, timerID); - return; - } - - if (timerID == IDT_GLASS_ANIMATION_ENTER) { - if (m_animationStage > ANIMATION_MAX_ITERATION) { - StopAnimation(TRUE); - return; - } - } else if (timerID == IDT_GLASS_ANIMATION_EXIT) { - if (m_animationStage < 1) { - StopAnimation(FALSE); - return; - } - } - - m_bgWindow->UpdateAnimationOpacity(m_animationStage); - UpdateAnimationRect(); - - if (timerID == IDT_GLASS_ANIMATION_ENTER) { - m_animationStage++; - } else if (timerID == IDT_GLASS_ANIMATION_EXIT) { - m_animationStage--; - } -} - -LRESULT FullScreenWindow::WindowProc(UINT msg, WPARAM wParam, LPARAM lParam) -{ - MessageResult commonResult = BaseWnd::CommonWindowProc(msg, wParam, lParam); - if (commonResult.processed) { - return commonResult.result; - } - - switch (msg) { - case WM_TIMER: - HandleViewTimerEvent(GetHWND(), wParam); - break; - case WM_SIZE: - if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED) { - HandleSizeEvent(); - } - HandleViewSizeEvent(GetHWND(), msg, wParam, lParam); - break; - case WM_ACTIVATE: - { - // The fActive shouldn't be WA_INACTIVE && the window shouldn't be minimized: - const bool isFocusGained = LOWORD(wParam) != WA_INACTIVE && HIWORD(wParam) == 0; - - if (!isFocusGained && IsCommonDialogOwner()) { - // Remain in full screen while a file dialog is showing - break; - } - - HWND hWndInsertAfter = isFocusGained ? HWND_TOPMOST : HWND_BOTTOM; - - if (m_bgWindow) { - ::SetWindowPos(m_bgWindow->GetHWND(), hWndInsertAfter, 0, 0, 0, 0, - SWP_ASYNCWINDOWPOS | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOSIZE); - } - ::SetWindowPos(GetHWND(), hWndInsertAfter, 0, 0, 0, 0, - SWP_ASYNCWINDOWPOS | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOSIZE); - - GlassWindow * window = GlassWindow::FromHandle(m_oldViewParent); - if (window) { - window->HandleActivateEvent(isFocusGained ? - com_sun_glass_events_WindowEvent_FOCUS_GAINED : - com_sun_glass_events_WindowEvent_FOCUS_LOST); - - // Child windows don't have a taskbar button, therefore - // we force exiting from the FS mode if the window looses - // focus. - if (!isFocusGained) { - ExitFullScreenMode(FALSE); - } - } - } - break; - case WM_CLOSE: - { - GlassWindow * window = GlassWindow::FromHandle(m_oldViewParent); - ExitFullScreenMode(FALSE); - if (window) { - window->HandleCloseEvent(); - } - } - return 0; - case WM_INPUTLANGCHANGE: - HandleViewInputLangChange(GetHWND(), msg, wParam, lParam); - return 0; - case WM_PAINT: - HandleViewPaintEvent(GetHWND(), msg, wParam, lParam); - break; - case WM_CONTEXTMENU: - HandleViewMenuEvent(GetHWND(), msg, wParam, lParam); - break; - case WM_MOUSEMOVE: - case WM_LBUTTONDOWN: - case WM_LBUTTONUP: - case WM_LBUTTONDBLCLK: - case WM_RBUTTONDOWN: - case WM_RBUTTONUP: - case WM_RBUTTONDBLCLK: - case WM_MBUTTONDOWN: - case WM_MBUTTONUP: - case WM_MBUTTONDBLCLK: - case WM_XBUTTONDOWN: - case WM_XBUTTONUP: - case WM_XBUTTONDBLCLK: - case WM_MOUSEWHEEL: - case WM_MOUSEHWHEEL: - case WM_MOUSELEAVE: { - BOOL handled = HandleViewMouseEvent(GetHWND(), msg, wParam, lParam, FALSE); - if (handled && msg == WM_RBUTTONUP) { - // By default, DefWindowProc() sends WM_CONTEXTMENU from WM_LBUTTONUP - // Since DefWindowProc() is not called, call the mouse menu handler directly - HandleViewMenuEvent(GetHWND(), WM_CONTEXTMENU, (WPARAM) GetHWND(), ::GetMessagePos ()); - //::DefWindowProc(GetHWND(), msg, wParam, lParam); - } - if (handled) { - // Do not call the DefWindowProc() for mouse events that were handled - return 0; - } - break; - } - case WM_CAPTURECHANGED: - ViewContainer::NotifyCaptureChanged(GetHWND(), (HWND)lParam); - break; - case WM_SYSKEYDOWN: - case WM_SYSKEYUP: - case WM_KEYDOWN: - case WM_KEYUP: - HandleViewKeyEvent(GetHWND(), msg, wParam, lParam); - // Always pass the message down to the DefWindowProc() to handle - // system keys (Alt+F4, etc.) - break; - case WM_DEADCHAR: - HandleViewDeadKeyEvent(GetHWND(), msg, wParam, lParam); - break; - case WM_CHAR: - case WM_IME_CHAR: - HandleViewTypedEvent(GetHWND(), msg, wParam, lParam); - return 0; - case WM_IME_COMPOSITION: - case WM_IME_ENDCOMPOSITION: - case WM_IME_NOTIFY: - case WM_IME_STARTCOMPOSITION: - if (HandleViewInputMethodEvent(GetHWND(), msg, wParam, lParam)) { - return 0; - } - break; - case WM_TOUCH: - HandleViewTouchEvent(GetHWND(), msg, wParam, lParam); - return 0; - case WM_GETOBJECT: { - LRESULT lr = HandleViewGetAccessible(GetHWND(), wParam, lParam); - if (lr) return lr; - break; - } - } - - return ::DefWindowProc(GetHWND(), msg, wParam, lParam); -} - -LPCTSTR FullScreenWindow::GetWindowClassNameSuffix() -{ - return szFullScreenWindowClassName; -} - -BOOL FullScreenWindow::EnterFullScreenMode(GlassView * view, BOOL animate, BOOL keepRatio) -{ - if (IsAnimationInProcess()) { - return TRUE; - } - - AttachView(view, keepRatio); - ShowWindow(animate); - - if (animate) { - StartAnimation(TRUE); - } - - return TRUE; -} - -void FullScreenWindow::StartAnimation(BOOL enter) -{ - m_animationStage = (enter ? 1 : ANIMATION_MAX_ITERATION); - UINT_PTR eventID = (enter ? IDT_GLASS_ANIMATION_ENTER : IDT_GLASS_ANIMATION_EXIT); - ::SetTimer(GetHWND(), eventID, ANIMATION_TIMER_ELAPSE, NULL); -} - -void FullScreenWindow::ExitFullScreenMode(BOOL animate) -{ - if (IsAnimationInProcess()) { - //TODO: the animation should be terminated - return; - } - - - if (animate) { - StartAnimation(FALSE); - } else { - GlassView * view = GetGlassView(); - DetachView(); - HideWindow(); - Close(); - } -} - -void FullScreenWindow::StopAnimation(BOOL enter) -{ - UINT_PTR eventID = (enter ? IDT_GLASS_ANIMATION_ENTER : IDT_GLASS_ANIMATION_EXIT); - ::KillTimer(GetHWND(), eventID); - - if (!enter) { - GlassView * view = GetGlassView(); - DetachView(); - HideWindow(); - Close(); - } -} - -BOOL FullScreenWindow::IsAnimationInProcess() -{ - if (m_animationStage >= 1 && m_animationStage <= ANIMATION_MAX_ITERATION) { - return true; - } - return false; -} - -void FullScreenWindow::UpdateAnimationRect() -{ - RECT rect; - float stage = (float)m_animationStage / (float)ANIMATION_MAX_ITERATION; - rect.left = m_viewRect.left + (long)((m_windowRect.left - m_viewRect.left) * stage); ; - rect.top = m_viewRect.top + (long)((m_windowRect.top - m_viewRect.top) * stage); - rect.right = m_viewRect.right + (long)((m_windowRect.right - m_viewRect.right) * stage); - rect.bottom = m_viewRect.bottom + (long)((m_windowRect.bottom - m_viewRect.bottom) * stage); - - ::SetWindowPos(GetHWND(), NULL, rect.left, rect.top, - rect.right - rect.left, rect.bottom - rect.top, - SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSENDCHANGING | SWP_DEFERERASE); -} - -// Transparent background window - -BackgroundWindow::BackgroundWindow() : BaseWnd() -{ - m_rect.left = 0; - m_rect.top = 0; - m_rect.right = 0; - m_rect.bottom = 0; -} - -BackgroundWindow::~BackgroundWindow() -{ -} - -HWND BackgroundWindow::Create() -{ - DWORD dwStyle = WS_POPUP | WS_CLIPCHILDREN; - DWORD dwExStyle = WS_EX_LAYERED | WS_EX_TOOLWINDOW; - - return BaseWnd::Create(NULL, 0, 0, 0, 0, - TEXT(""), dwExStyle, dwStyle, (HBRUSH)::GetStockObject(BLACK_BRUSH)); -} - -void BackgroundWindow::Close() -{ - ::DestroyWindow(GetHWND()); -} - -LRESULT BackgroundWindow::WindowProc(UINT msg, WPARAM wParam, LPARAM lParam) -{ - switch (msg) { - case WM_MOUSEACTIVATE: { - return MA_NOACTIVATE; - } - } - - return ::DefWindowProc(GetHWND(), msg, wParam, lParam); -} - -LPCTSTR BackgroundWindow::GetWindowClassNameSuffix() -{ - return szBackgroundWindowClassName; -} - -void BackgroundWindow::SetWindowRect(RECT * rect) -{ - ::CopyRect(&m_rect, rect); -} - -void BackgroundWindow::ShowWindow(BOOL animate) { - BYTE opacity = (animate ? 0x0 : 0xFF); - ::SetLayeredWindowAttributes(GetHWND(), RGB(0, 0, 0), opacity, LWA_ALPHA); - ::SetWindowPos(GetHWND(), HWND_TOPMOST, m_rect.left, m_rect.top, - m_rect.right - m_rect.left, m_rect.bottom - m_rect.top, - SWP_SHOWWINDOW | SWP_NOACTIVATE); -} - -void BackgroundWindow::HideWindow() -{ - ::ShowWindow(GetHWND(), SW_HIDE); -} - -void BackgroundWindow::UpdateAnimationOpacity(int animationStage) -{ - BYTE opacity = ((int)0xFF * animationStage) / ANIMATION_MAX_ITERATION; - ::SetLayeredWindowAttributes(GetHWND(), RGB(0, 0, 0), opacity, LWA_ALPHA); -} +/* + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "common.h" + +#include "GlassApplication.h" +#include "FullScreenWindow.h" +#include "GlassView.h" +#include "GlassDnD.h" +#include "GlassWindow.h" + +#include "com_sun_glass_events_WindowEvent.h" + +//TODO: is that possible to move all the code to the shared level? + +static LPCTSTR szFullScreenWindowClassName = TEXT("FullScreenWindowClass"); +static LPCTSTR szBackgroundWindowClassName = TEXT("BackgroundWindowClass"); + +static const UINT ANIMATION_MAX_ITERATION = 30; +static const UINT ANIMATION_TIMER_ELAPSE = USER_TIMER_MINIMUM; // 0xA ms + +FullScreenWindow::FullScreenWindow() : + BaseWnd(), + ViewContainer() +{ + m_animationStage = 0; + m_bgWindow = NULL; +} + +FullScreenWindow::~FullScreenWindow() +{ +} + +HWND FullScreenWindow::Create() +{ + m_bgWindow = new BackgroundWindow(); + m_bgWindow->Create(); + + DWORD dwStyle = WS_POPUP | WS_CLIPCHILDREN; + DWORD dwExStyle = 0; + + HWND hwnd = BaseWnd::Create(NULL, 0, 0, 0, 0, + TEXT(""), dwExStyle, dwStyle, NULL); + + ViewContainer::InitDropTarget(hwnd); + ViewContainer::InitManipProcessor(hwnd); + + return hwnd; +} + +void FullScreenWindow::Close() +{ + if (m_bgWindow) { + m_bgWindow->Close(); + m_bgWindow = NULL; + } + + ViewContainer::ReleaseDropTarget(); + ViewContainer::ReleaseManipProcessor(); + + ::DestroyWindow(GetHWND()); +} + +/* static */ +void FullScreenWindow::ClientRectInScreen(HWND hwnd, RECT * rect) +{ + ::GetClientRect(hwnd, rect); + ::MapWindowPoints(hwnd, (HWND)NULL, (LPPOINT)rect, (sizeof(RECT)/sizeof(POINT))); +} + +void FullScreenWindow::AttachView(GlassView * view, BOOL keepRatio) +{ + SetGlassView(view); + + m_oldViewParent = GetGlassView()->GetHostHwnd(); + + FullScreenWindow::ClientRectInScreen(m_oldViewParent, &m_viewRect); + + InitWindowRect(keepRatio); + + GlassWindow * window = GlassWindow::FromHandle(m_oldViewParent); + if (window) { + window->SetDelegateWindow(GetHWND()); + } + + ::ShowWindow(m_oldViewParent, SW_HIDE); + GetGlassView()->SetHostHwnd(GetHWND()); +} + +void FullScreenWindow::DetachView() +{ + RECT r; + HWND oldWnd = m_oldViewParent; + GlassView* view = GetGlassView(); + + m_oldViewParent = NULL; + + view->SetHostHwnd(oldWnd); + + ::ShowWindow(oldWnd, SW_SHOW); + ::SetForegroundWindow(oldWnd); + ::SetFocus(oldWnd); + + GlassWindow * window = GlassWindow::FromHandle(oldWnd); + if (window) { + window->SetDelegateWindow(NULL); + } + + ::GetClientRect(oldWnd, &r); + JNIEnv* env = GetEnv(); + env->CallVoidMethod(GetView(), javaIDs.View.notifyResize, + r.right-r.left, r.bottom - r.top); + CheckAndClearException(env); + + SetGlassView(NULL); +} + +/* static */ +void FullScreenWindow::CalculateBounds(HWND hwnd, RECT * screenRect, + RECT * contentRect, BOOL keepRatio, const RECT & viewRect) +{ + MONITORINFOEX mix; + HMONITOR hMonitor = ::MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY); + + memset(&mix, 0, sizeof(MONITORINFOEX)); + mix.cbSize = sizeof(MONITORINFOEX); + ::GetMonitorInfo(hMonitor, &mix); + + ::CopyRect(screenRect, &mix.rcMonitor); + ::CopyRect(contentRect, &mix.rcMonitor); + + if (keepRatio) { + int viewWidth = viewRect.right - viewRect.left; + int viewHeight = viewRect.bottom - viewRect.top; + int screenWidth = screenRect->right - screenRect->left; + int screenHeight = screenRect->bottom - screenRect->top; + + float ratioWidth = (float)viewWidth / (float)screenWidth; + float ratioHeight = (float)viewHeight / (float)screenHeight; + + if (ratioWidth > ratioHeight) { + float ratio = (float)viewWidth / (float)viewHeight; + int height = (int)(screenWidth / ratio); + contentRect->top += (screenHeight - height) / 2; + contentRect->bottom = contentRect->top + height; + } else { + float ratio = (float)viewHeight / (float)viewWidth; + int width = (int)(screenHeight / ratio); + contentRect->left += (screenWidth - width) / 2; + contentRect->right = contentRect->left + width; + } + } +} + +void FullScreenWindow::InitWindowRect(BOOL keepRatio) +{ + RECT screenRect; + + FullScreenWindow::CalculateBounds(m_oldViewParent, &screenRect, + &m_windowRect, keepRatio, m_viewRect); + + m_bgWindow->SetWindowRect(&screenRect); +} + +void FullScreenWindow::ShowWindow(BOOL animate) +{ + m_bgWindow->ShowWindow(animate); + + RECT rect; + if (animate) { + CopyRect(&rect, &m_viewRect); + } else { + CopyRect(&rect, &m_windowRect); + } + + ::SetWindowPos(GetHWND(), HWND_TOPMOST, rect.left, rect.top, + rect.right - rect.left, rect.bottom - rect.top, + SWP_SHOWWINDOW); + ::SetForegroundWindow(GetHWND()); +} + +void FullScreenWindow::HideWindow() +{ + m_bgWindow->HideWindow(); + ::ShowWindow(GetHWND(), SW_HIDE); +} + +void FullScreenWindow::HandleSizeEvent() +{ +} + +void FullScreenWindow::HandleViewTimerEvent(HWND hwnd, UINT_PTR timerID) +{ + switch (timerID) { + case IDT_GLASS_ANIMATION_ENTER: + case IDT_GLASS_ANIMATION_EXIT: + break; + default: + ViewContainer::HandleViewTimerEvent(hwnd, timerID); + return; + } + + if (timerID == IDT_GLASS_ANIMATION_ENTER) { + if (m_animationStage > ANIMATION_MAX_ITERATION) { + StopAnimation(TRUE); + return; + } + } else if (timerID == IDT_GLASS_ANIMATION_EXIT) { + if (m_animationStage < 1) { + StopAnimation(FALSE); + return; + } + } + + m_bgWindow->UpdateAnimationOpacity(m_animationStage); + UpdateAnimationRect(); + + if (timerID == IDT_GLASS_ANIMATION_ENTER) { + m_animationStage++; + } else if (timerID == IDT_GLASS_ANIMATION_EXIT) { + m_animationStage--; + } +} + +LRESULT FullScreenWindow::WindowProc(UINT msg, WPARAM wParam, LPARAM lParam) +{ + MessageResult commonResult = BaseWnd::CommonWindowProc(msg, wParam, lParam); + if (commonResult.processed) { + return commonResult.result; + } + + switch (msg) { + case WM_TIMER: + HandleViewTimerEvent(GetHWND(), wParam); + break; + case WM_SIZE: + if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED) { + HandleSizeEvent(); + } + HandleViewSizeEvent(GetHWND(), msg, wParam, lParam); + break; + case WM_ACTIVATE: + { + // The fActive shouldn't be WA_INACTIVE && the window shouldn't be minimized: + const bool isFocusGained = LOWORD(wParam) != WA_INACTIVE && HIWORD(wParam) == 0; + + if (!isFocusGained && IsCommonDialogOwner()) { + // Remain in full screen while a file dialog is showing + break; + } + + HWND hWndInsertAfter = isFocusGained ? HWND_TOPMOST : HWND_BOTTOM; + + if (m_bgWindow) { + ::SetWindowPos(m_bgWindow->GetHWND(), hWndInsertAfter, 0, 0, 0, 0, + SWP_ASYNCWINDOWPOS | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOSIZE); + } + ::SetWindowPos(GetHWND(), hWndInsertAfter, 0, 0, 0, 0, + SWP_ASYNCWINDOWPOS | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOSIZE); + + GlassWindow * window = GlassWindow::FromHandle(m_oldViewParent); + if (window) { + window->HandleActivateEvent(isFocusGained ? + com_sun_glass_events_WindowEvent_FOCUS_GAINED : + com_sun_glass_events_WindowEvent_FOCUS_LOST); + + // Child windows don't have a taskbar button, therefore + // we force exiting from the FS mode if the window looses + // focus. + if (!isFocusGained) { + ExitFullScreenMode(FALSE); + } + } + } + break; + case WM_CLOSE: + { + GlassWindow * window = GlassWindow::FromHandle(m_oldViewParent); + ExitFullScreenMode(FALSE); + if (window) { + window->HandleCloseEvent(); + } + } + return 0; + case WM_INPUTLANGCHANGE: + HandleViewInputLangChange(GetHWND(), msg, wParam, lParam); + return 0; + case WM_PAINT: + HandleViewPaintEvent(GetHWND(), msg, wParam, lParam); + break; + case WM_CONTEXTMENU: + HandleViewMenuEvent(GetHWND(), msg, wParam, lParam); + break; + case WM_MOUSEMOVE: + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_LBUTTONDBLCLK: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + case WM_RBUTTONDBLCLK: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + case WM_MBUTTONDBLCLK: + case WM_XBUTTONDOWN: + case WM_XBUTTONUP: + case WM_XBUTTONDBLCLK: + case WM_MOUSEWHEEL: + case WM_MOUSEHWHEEL: + case WM_MOUSELEAVE: { + BOOL handled = HandleViewMouseEvent(GetHWND(), msg, wParam, lParam, FALSE); + if (handled && msg == WM_RBUTTONUP) { + // By default, DefWindowProc() sends WM_CONTEXTMENU from WM_LBUTTONUP + // Since DefWindowProc() is not called, call the mouse menu handler directly + HandleViewMenuEvent(GetHWND(), WM_CONTEXTMENU, (WPARAM) GetHWND(), ::GetMessagePos ()); + //::DefWindowProc(GetHWND(), msg, wParam, lParam); + } + if (handled) { + // Do not call the DefWindowProc() for mouse events that were handled + return 0; + } + break; + } + case WM_CAPTURECHANGED: + ViewContainer::NotifyCaptureChanged(GetHWND(), (HWND)lParam); + break; + case WM_SYSKEYDOWN: + case WM_SYSKEYUP: + case WM_KEYDOWN: + case WM_KEYUP: + HandleViewKeyEvent(GetHWND(), msg, wParam, lParam); + // Always pass the message down to the DefWindowProc() to handle + // system keys (Alt+F4, etc.) + break; + case WM_DEADCHAR: + HandleViewDeadKeyEvent(GetHWND(), msg, wParam, lParam); + break; + case WM_CHAR: + case WM_IME_CHAR: + HandleViewTypedEvent(GetHWND(), msg, wParam, lParam); + return 0; + case WM_IME_COMPOSITION: + case WM_IME_ENDCOMPOSITION: + case WM_IME_NOTIFY: + case WM_IME_STARTCOMPOSITION: + if (HandleViewInputMethodEvent(GetHWND(), msg, wParam, lParam)) { + return 0; + } + break; + case WM_TOUCH: + HandleViewTouchEvent(GetHWND(), msg, wParam, lParam); + return 0; + case WM_GETOBJECT: { + LRESULT lr = HandleViewGetAccessible(GetHWND(), wParam, lParam); + if (lr) return lr; + break; + } + } + + return ::DefWindowProc(GetHWND(), msg, wParam, lParam); +} + +LPCTSTR FullScreenWindow::GetWindowClassNameSuffix() +{ + return szFullScreenWindowClassName; +} + +BOOL FullScreenWindow::EnterFullScreenMode(GlassView * view, BOOL animate, BOOL keepRatio) +{ + if (IsAnimationInProcess()) { + return TRUE; + } + + AttachView(view, keepRatio); + ShowWindow(animate); + + if (animate) { + StartAnimation(TRUE); + } + + return TRUE; +} + +void FullScreenWindow::StartAnimation(BOOL enter) +{ + m_animationStage = (enter ? 1 : ANIMATION_MAX_ITERATION); + UINT_PTR eventID = (enter ? IDT_GLASS_ANIMATION_ENTER : IDT_GLASS_ANIMATION_EXIT); + ::SetTimer(GetHWND(), eventID, ANIMATION_TIMER_ELAPSE, NULL); +} + +void FullScreenWindow::ExitFullScreenMode(BOOL animate) +{ + if (IsAnimationInProcess()) { + //TODO: the animation should be terminated + return; + } + + + if (animate) { + StartAnimation(FALSE); + } else { + GlassView * view = GetGlassView(); + DetachView(); + HideWindow(); + Close(); + } +} + +void FullScreenWindow::StopAnimation(BOOL enter) +{ + UINT_PTR eventID = (enter ? IDT_GLASS_ANIMATION_ENTER : IDT_GLASS_ANIMATION_EXIT); + ::KillTimer(GetHWND(), eventID); + + if (!enter) { + GlassView * view = GetGlassView(); + DetachView(); + HideWindow(); + Close(); + } +} + +BOOL FullScreenWindow::IsAnimationInProcess() +{ + if (m_animationStage >= 1 && m_animationStage <= ANIMATION_MAX_ITERATION) { + return true; + } + return false; +} + +void FullScreenWindow::UpdateAnimationRect() +{ + RECT rect; + float stage = (float)m_animationStage / (float)ANIMATION_MAX_ITERATION; + rect.left = m_viewRect.left + (long)((m_windowRect.left - m_viewRect.left) * stage); ; + rect.top = m_viewRect.top + (long)((m_windowRect.top - m_viewRect.top) * stage); + rect.right = m_viewRect.right + (long)((m_windowRect.right - m_viewRect.right) * stage); + rect.bottom = m_viewRect.bottom + (long)((m_windowRect.bottom - m_viewRect.bottom) * stage); + + ::SetWindowPos(GetHWND(), NULL, rect.left, rect.top, + rect.right - rect.left, rect.bottom - rect.top, + SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSENDCHANGING | SWP_DEFERERASE); +} + +// Transparent background window + +BackgroundWindow::BackgroundWindow() : BaseWnd() +{ + m_rect.left = 0; + m_rect.top = 0; + m_rect.right = 0; + m_rect.bottom = 0; +} + +BackgroundWindow::~BackgroundWindow() +{ +} + +HWND BackgroundWindow::Create() +{ + DWORD dwStyle = WS_POPUP | WS_CLIPCHILDREN; + DWORD dwExStyle = WS_EX_LAYERED | WS_EX_TOOLWINDOW; + + return BaseWnd::Create(NULL, 0, 0, 0, 0, + TEXT(""), dwExStyle, dwStyle, (HBRUSH)::GetStockObject(BLACK_BRUSH)); +} + +void BackgroundWindow::Close() +{ + ::DestroyWindow(GetHWND()); +} + +LRESULT BackgroundWindow::WindowProc(UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_MOUSEACTIVATE: { + return MA_NOACTIVATE; + } + } + + return ::DefWindowProc(GetHWND(), msg, wParam, lParam); +} + +LPCTSTR BackgroundWindow::GetWindowClassNameSuffix() +{ + return szBackgroundWindowClassName; +} + +void BackgroundWindow::SetWindowRect(RECT * rect) +{ + ::CopyRect(&m_rect, rect); +} + +void BackgroundWindow::ShowWindow(BOOL animate) { + BYTE opacity = (animate ? 0x0 : 0xFF); + ::SetLayeredWindowAttributes(GetHWND(), RGB(0, 0, 0), opacity, LWA_ALPHA); + ::SetWindowPos(GetHWND(), HWND_TOPMOST, m_rect.left, m_rect.top, + m_rect.right - m_rect.left, m_rect.bottom - m_rect.top, + SWP_SHOWWINDOW | SWP_NOACTIVATE); +} + +void BackgroundWindow::HideWindow() +{ + ::ShowWindow(GetHWND(), SW_HIDE); +} + +void BackgroundWindow::UpdateAnimationOpacity(int animationStage) +{ + BYTE opacity = ((int)0xFF * animationStage) / ANIMATION_MAX_ITERATION; + ::SetLayeredWindowAttributes(GetHWND(), RGB(0, 0, 0), opacity, LWA_ALPHA); +} From fcec7c8a4f1de25e893bf1c780ca4d039c73ee7c Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Mon, 27 Jan 2025 07:54:23 +0100 Subject: [PATCH 39/73] update javadoc --- .../javafx.graphics/src/main/java/javafx/stage/Stage.java | 6 ++++++ .../src/main/java/javafx/stage/StageStyle.java | 8 ++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java b/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java index 7aca35ad8ff..674d0ab50ee 100644 --- a/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java +++ b/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java @@ -522,6 +522,12 @@ public final void initDefaultHeaderButtons(boolean enabled) { this.defaultHeaderButtons = enabled; } + /** + * Returns whether a stage with a client-side header bar uses the default platform-provided header buttons. + * + * @return {@code true} if the stage uses default header buttons, {@code false} otherwise + * @since 25 + */ public final boolean isDefaultHeaderButtons() { return defaultHeaderButtons; } diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java b/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java index 76f53bed496..80e1007fd15 100644 --- a/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java +++ b/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java @@ -83,7 +83,7 @@ public enum StageStyle { * This is a conditional feature, to check if it is supported see {@link Platform#isSupported(ConditionalFeature)}. * If the feature is not supported by the platform, this style downgrades to {@link StageStyle#DECORATED}. * - *

    Usage

    + *

    Usage

    * An extended window has the default header buttons (minimize, maximize, close), but no system-provided * draggable header bar. Applications need to provide their own header bar by placing a {@link HeaderBar} * control in the scene graph. The {@code HeaderBar} control should be positioned at the top of the window @@ -105,19 +105,19 @@ public enum StageStyle { * } * } * - *

    Color scheme

    + *

    Color scheme

    * The color scheme of the default header buttons is adjusted to the {@link Scene#fillProperty() fill} * of the {@code Scene} to remain easily recognizable. Applications should set the scene fill to a color * that matches the brightness of the user interface, even if the scene fill is not visible because it * is obscured by other controls. * - *

    Custom header buttons

    + *

    Custom header buttons

    * If more control over the header buttons is desired, applications can opt out of the default header buttons * by setting the {@link Stage#initDefaultHeaderButtons(boolean)} property to {@code false} and provide custom * header buttons instead. Any JavaFX control can be used as a custom header button by setting its appropriate * {@link HeaderButtonType}. * - *

    Title text

    + *

    Title text

    * An extended stage has no title text. Applications that require title text need to provide their own * implementation by placing a {@code Label} or a similar control in the custom header bar. * Note that the value of {@link Stage#titleProperty()} may still be used by the platform, for example From 2ab12947acd3c02204d5f99eed6e95989e72814b Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Mon, 27 Jan 2025 23:53:37 +0100 Subject: [PATCH 40/73] hide default window buttons on macOS --- .../src/main/java/com/sun/glass/ui/View.java | 3 ++- .../src/main/java/com/sun/glass/ui/mac/MacWindow.java | 11 +++++++---- .../src/main/native-glass/mac/GlassWindow.m | 9 +++++++-- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/View.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/View.java index e9da9942af4..a54a8b254f8 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/View.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/View.java @@ -961,7 +961,8 @@ protected void notifyMouse(int type, int button, int x, int y, int xAbs, // not be processed by the non-client event handler. For example, if a mouse click happens on // the resize border that straddles the window close button, we don't want the close button to // act on this click, because we just started a resize-drag operation. - boolean handled = window.isExtendedWindow() + boolean handled = window != null + && window.isExtendedWindow() && !isSynthesized && !inFullscreen && shouldHandleEvent() diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java index 9a78089e17b..ef86fff0617 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -183,9 +183,12 @@ protected void onHeaderBarHeightChanged(double height) { private void updateWindowOverlayMetrics(NSWindowToolbarStyle toolbarStyle) { double minHeight = NSWindowToolbarStyle.SMALL.size.getHeight(); - WindowControlsMetrics metrics = _isRightToLeftLayoutDirection() - ? new WindowControlsMetrics(new Dimension2D(0, 0), toolbarStyle.size, minHeight) - : new WindowControlsMetrics(toolbarStyle.size, new Dimension2D(0, 0), minHeight); + var empty = new Dimension2D(0, 0); + WindowControlsMetrics metrics = isUsingNonClientOverlay() + ? _isRightToLeftLayoutDirection() + ? new WindowControlsMetrics(empty, toolbarStyle.size, minHeight) + : new WindowControlsMetrics(toolbarStyle.size, empty, minHeight) + : new WindowControlsMetrics(empty, empty, minHeight); windowControlsMetrics.set(metrics); } diff --git a/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m b/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m index 38b91d45655..1100c9980a0 100644 --- a/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m +++ b/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -433,7 +433,12 @@ static jlong _createWindowCommonDo(JNIEnv *env, jobject jWindow, jlong jOwnerPtr [window->nsWindow setTitleVisibility:NSWindowTitleHidden]; [window->nsWindow setTitlebarAppearsTransparent:YES]; [window->nsWindow setToolbar:[NSToolbar new]]; - [window->nsWindow setToolbarStyle:NSWindowToolbarStyleUnifiedCompact]; + + if ((jStyleMask & com_sun_glass_ui_Window_NON_CLIENT_OVERLAY) == 0) { + [[window->nsWindow standardWindowButton:NSWindowCloseButton] setHidden:YES]; + [[window->nsWindow standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES]; + [[window->nsWindow standardWindowButton:NSWindowZoomButton] setHidden:YES]; + } } if ((jStyleMask & com_sun_glass_ui_Window_UNIFIED) != 0) { From 8a73f5ee14a3a1467e17e3e6224b820b54625027 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Tue, 28 Jan 2025 02:32:09 +0100 Subject: [PATCH 41/73] typo --- modules/javafx.graphics/src/main/java/javafx/stage/Stage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java b/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java index 674d0ab50ee..10db1b585e8 100644 --- a/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java +++ b/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java @@ -516,7 +516,7 @@ public final Modality getModality() { */ public final void initDefaultHeaderButtons(boolean enabled) { if (hasBeenVisible) { - throw new IllegalStateException("Cannot set chrome once stage has been set visible"); + throw new IllegalStateException("Cannot set default header buttons once stage has been set visible"); } this.defaultHeaderButtons = enabled; From e874d21a410f78e6d522dc85c4542ca5d8cafd80 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Tue, 28 Jan 2025 06:12:20 +0100 Subject: [PATCH 42/73] add samples in MonkeyTester --- .../tools/fx/monkey/ExtendedWindow.java | 171 ++++++++++++++++++ .../oracle/tools/fx/monkey/MainWindow.java | 31 +++- 2 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 tests/manual/monkey/src/com/oracle/tools/fx/monkey/ExtendedWindow.java diff --git a/tests/manual/monkey/src/com/oracle/tools/fx/monkey/ExtendedWindow.java b/tests/manual/monkey/src/com/oracle/tools/fx/monkey/ExtendedWindow.java new file mode 100644 index 00000000000..e87518f5bed --- /dev/null +++ b/tests/manual/monkey/src/com/oracle/tools/fx/monkey/ExtendedWindow.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.tools.fx.monkey; + +import javafx.application.Platform; +import javafx.geometry.Insets; +import javafx.geometry.NodeOrientation; +import javafx.geometry.Pos; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.CheckBox; +import javafx.scene.control.SplitPane; +import javafx.scene.layout.Background; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HeaderBar; +import javafx.scene.layout.HeaderButtonType; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.stage.Modality; +import javafx.stage.Stage; +import javafx.stage.StageStyle; +import javafx.stage.Window; + +public class ExtendedWindow extends Stage { + + public static void showSimpleHeaderBar(StageStyle style, NodeOrientation orientation) { + var stage = new ExtendedWindow(); + + var headerBar = new HeaderBar(); + headerBar.setBackground(Background.fill(Color.LIGHTSKYBLUE)); + + var resizable = new CheckBox("Resizable"); + resizable.selectedProperty().bindBidirectional(stage.resizableProperty()); + + var fullScreen = new Button("Full-screen"); + fullScreen.setOnAction(e -> stage.setFullScreen(!stage.isFullScreen())); + + var content = new HBox(resizable, fullScreen); + content.setSpacing(10); + content.setAlignment(Pos.CENTER); + headerBar.setCenter(content); + HeaderBar.setAlignment(content, Pos.CENTER); + + var root = new BorderPane(); + root.setTop(headerBar); + + var scene = new Scene(root); + scene.setNodeOrientation(orientation); + + stage.initStyle(style); + stage.setScene(scene); + stage.setWidth(800); + stage.setHeight(500); + stage.show(); + } + + public static void showSplitHeaderBar(StageStyle style, NodeOrientation orientation) { + var stage = new ExtendedWindow(); + + var leftHeaderBar = new HeaderBar(); + leftHeaderBar.setBackground(Background.fill(Color.VIOLET)); + leftHeaderBar.setTrailingSystemPadding(false); + + var rightHeaderBar = new HeaderBar(); + rightHeaderBar.setBackground(Background.fill(Color.LIGHTSKYBLUE)); + rightHeaderBar.setLeadingSystemPadding(false); + + var resizable = new CheckBox("Resizable"); + resizable.selectedProperty().bindBidirectional(stage.resizableProperty()); + + var fullScreen = new Button("Full-screen"); + fullScreen.setOnAction(e -> stage.setFullScreen(!stage.isFullScreen())); + + var content = new HBox(resizable, fullScreen); + content.setSpacing(10); + content.setAlignment(Pos.CENTER); + rightHeaderBar.setCenter(content); + HeaderBar.setAlignment(content, Pos.CENTER); + + var left = new BorderPane(); + left.setTop(leftHeaderBar); + + var right = new BorderPane(); + right.setTop(rightHeaderBar); + + var root = new SplitPane(left, right); + + var scene = new Scene(root); + scene.setNodeOrientation(orientation); + + stage.initStyle(style); + stage.setScene(scene); + stage.setWidth(800); + stage.setHeight(500); + stage.show(); + } + + public static void showCustomHeaderButtons(StageStyle style, NodeOrientation orientation) { + var stage = new ExtendedWindow(); + + var headerBar = new HeaderBar(); + headerBar.setBackground(Background.fill(Color.LIGHTSKYBLUE)); + headerBar.setMinHeight(40); + + var resizable = new CheckBox("Resizable"); + resizable.selectedProperty().bindBidirectional(stage.resizableProperty()); + + var fullScreen = new Button("Full-screen"); + fullScreen.setOnAction(e -> stage.setFullScreen(!stage.isFullScreen())); + + var minimize = new Button("Minimize"); + minimize.setOnAction(e -> stage.setIconified(true)); + HeaderBar.setHeaderButtonType(minimize, HeaderButtonType.MINIMIZE); + + var maximize = new Button("Maximize/restore"); + maximize.setOnAction(e -> stage.setMaximized(!stage.isMaximized())); + HeaderBar.setHeaderButtonType(maximize, HeaderButtonType.MAXIMIZE); + + var close = new Button("Close"); + close.setOnAction(e -> stage.close()); + HeaderBar.setHeaderButtonType(close, HeaderButtonType.CLOSE); + + var headerButtons = new HBox(minimize, maximize, close); + headerButtons.setAlignment(Pos.CENTER); + headerButtons.setSpacing(5); + headerBar.setTrailing(headerButtons); + + var content = new HBox(resizable, fullScreen); + content.setSpacing(10); + content.setAlignment(Pos.CENTER); + headerBar.setCenter(content); + HeaderBar.setAlignment(content, Pos.CENTER); + + var root = new BorderPane(); + root.setTop(headerBar); + + var scene = new Scene(root); + scene.setNodeOrientation(orientation); + + stage.initStyle(style); + stage.initDefaultHeaderButtons(false); + stage.setScene(scene); + stage.setWidth(800); + stage.setHeight(500); + stage.show(); + } +} diff --git a/tests/manual/monkey/src/com/oracle/tools/fx/monkey/MainWindow.java b/tests/manual/monkey/src/com/oracle/tools/fx/monkey/MainWindow.java index 5c6354a5b21..af5d132cd8e 100644 --- a/tests/manual/monkey/src/com/oracle/tools/fx/monkey/MainWindow.java +++ b/tests/manual/monkey/src/com/oracle/tools/fx/monkey/MainWindow.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -37,12 +37,14 @@ import javafx.scene.control.CheckMenuItem; import javafx.scene.control.Label; import javafx.scene.control.ListView; +import javafx.scene.control.Menu; import javafx.scene.control.MenuBar; import javafx.scene.control.SplitPane; import javafx.scene.layout.BorderPane; import javafx.scene.layout.GridPane; import javafx.scene.layout.Priority; import javafx.stage.Stage; +import javafx.stage.StageStyle; import com.oracle.tools.fx.monkey.pages.DemoPage; import com.oracle.tools.fx.monkey.settings.FxSettings; import com.oracle.tools.fx.monkey.tools.ClipboardViewer; @@ -155,6 +157,33 @@ private MenuBar createMenu() { FX.item(m, orientation); FX.separator(m); FX.item(m, "Open Modal Window", this::openModalWindow); + var stageMenu = new Menu("Open Extended Window"); + FX.item(stageMenu, "Single HeaderBar, EXTENDED", + () -> ExtendedWindow.showSimpleHeaderBar(StageStyle.EXTENDED, NodeOrientation.INHERIT)); + FX.item(stageMenu, "Single HeaderBar, EXTENDED, RIGHT__TO__LEFT", + () -> ExtendedWindow.showSimpleHeaderBar(StageStyle.EXTENDED, NodeOrientation.RIGHT_TO_LEFT)); + FX.item(stageMenu, "Single HeaderBar, EXTENDED__UTILITY", + () -> ExtendedWindow.showSimpleHeaderBar(StageStyle.EXTENDED_UTILITY, NodeOrientation.INHERIT)); + FX.item(stageMenu, "Single HeaderBar, EXTENDED__UTILITY, RIGHT__TO__LEFT", + () -> ExtendedWindow.showSimpleHeaderBar(StageStyle.EXTENDED_UTILITY, NodeOrientation.RIGHT_TO_LEFT)); + FX.item(stageMenu, "Split HeaderBar, EXTENDED", + () -> ExtendedWindow.showSplitHeaderBar(StageStyle.EXTENDED, NodeOrientation.INHERIT)); + FX.item(stageMenu, "Split HeaderBar, EXTENDED, RIGHT__TO__LEFT", + () -> ExtendedWindow.showSplitHeaderBar(StageStyle.EXTENDED, NodeOrientation.RIGHT_TO_LEFT)); + FX.item(stageMenu, "Split HeaderBar, EXTENDED__UTILITY", + () -> ExtendedWindow.showSplitHeaderBar(StageStyle.EXTENDED_UTILITY, NodeOrientation.INHERIT)); + FX.item(stageMenu, "Split HeaderBar, EXTENDED__UTILITY, RIGHT__TO__LEFT", + () -> ExtendedWindow.showSplitHeaderBar(StageStyle.EXTENDED_UTILITY, NodeOrientation.RIGHT_TO_LEFT)); + FX.item(stageMenu, "HeaderBar with custom buttons, EXTENDED", + () -> ExtendedWindow.showCustomHeaderButtons(StageStyle.EXTENDED, NodeOrientation.INHERIT)); + FX.item(stageMenu, "HeaderBar with custom buttons, EXTENDED, RIGHT__TO__LEFT", + () -> ExtendedWindow.showCustomHeaderButtons(StageStyle.EXTENDED, NodeOrientation.RIGHT_TO_LEFT)); + FX.item(stageMenu, "HeaderBar with custom buttons, EXTENDED__UTILITY", + () -> ExtendedWindow.showCustomHeaderButtons(StageStyle.EXTENDED_UTILITY, NodeOrientation.INHERIT)); + FX.item(stageMenu, "HeaderBar with custom buttons, EXTENDED__UTILITY, RIGHT__TO__LEFT", + () -> ExtendedWindow.showCustomHeaderButtons(StageStyle.EXTENDED_UTILITY, NodeOrientation.RIGHT_TO_LEFT)); + FX.item(m, stageMenu); + return m; } From 3efd78276ede37caa6cb9096d812bd155c14799f Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Tue, 28 Jan 2025 06:17:53 +0100 Subject: [PATCH 43/73] small MonkeyTester refactor --- .../src/com/oracle/tools/fx/monkey/ExtendedWindow.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/manual/monkey/src/com/oracle/tools/fx/monkey/ExtendedWindow.java b/tests/manual/monkey/src/com/oracle/tools/fx/monkey/ExtendedWindow.java index e87518f5bed..8f569179ab5 100644 --- a/tests/manual/monkey/src/com/oracle/tools/fx/monkey/ExtendedWindow.java +++ b/tests/manual/monkey/src/com/oracle/tools/fx/monkey/ExtendedWindow.java @@ -45,10 +45,12 @@ import javafx.stage.StageStyle; import javafx.stage.Window; -public class ExtendedWindow extends Stage { +public final class ExtendedWindow { + + private ExtendedWindow() {} public static void showSimpleHeaderBar(StageStyle style, NodeOrientation orientation) { - var stage = new ExtendedWindow(); + var stage = new Stage(); var headerBar = new HeaderBar(); headerBar.setBackground(Background.fill(Color.LIGHTSKYBLUE)); @@ -79,7 +81,7 @@ public static void showSimpleHeaderBar(StageStyle style, NodeOrientation orienta } public static void showSplitHeaderBar(StageStyle style, NodeOrientation orientation) { - var stage = new ExtendedWindow(); + var stage = new Stage(); var leftHeaderBar = new HeaderBar(); leftHeaderBar.setBackground(Background.fill(Color.VIOLET)); @@ -120,7 +122,7 @@ public static void showSplitHeaderBar(StageStyle style, NodeOrientation orientat } public static void showCustomHeaderButtons(StageStyle style, NodeOrientation orientation) { - var stage = new ExtendedWindow(); + var stage = new Stage(); var headerBar = new HeaderBar(); headerBar.setBackground(Background.fill(Color.LIGHTSKYBLUE)); From cadf1c2c95c9a2ff043655a51e1a7adf430a24c1 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Sat, 1 Feb 2025 08:16:43 +0100 Subject: [PATCH 44/73] added StageTester to MonkeyTester --- .../tools/fx/monkey/ExtendedWindow.java | 173 ----------- .../oracle/tools/fx/monkey/MainWindow.java | 34 +-- .../oracle/tools/fx/monkey/ModalWindow.java | 66 ----- .../tools/fx/monkey/StageTesterWindow.java | 276 ++++++++++++++++++ 4 files changed, 279 insertions(+), 270 deletions(-) delete mode 100644 tests/manual/monkey/src/com/oracle/tools/fx/monkey/ExtendedWindow.java delete mode 100644 tests/manual/monkey/src/com/oracle/tools/fx/monkey/ModalWindow.java create mode 100644 tests/manual/monkey/src/com/oracle/tools/fx/monkey/StageTesterWindow.java diff --git a/tests/manual/monkey/src/com/oracle/tools/fx/monkey/ExtendedWindow.java b/tests/manual/monkey/src/com/oracle/tools/fx/monkey/ExtendedWindow.java deleted file mode 100644 index 8f569179ab5..00000000000 --- a/tests/manual/monkey/src/com/oracle/tools/fx/monkey/ExtendedWindow.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.oracle.tools.fx.monkey; - -import javafx.application.Platform; -import javafx.geometry.Insets; -import javafx.geometry.NodeOrientation; -import javafx.geometry.Pos; -import javafx.scene.Parent; -import javafx.scene.Scene; -import javafx.scene.control.Button; -import javafx.scene.control.CheckBox; -import javafx.scene.control.SplitPane; -import javafx.scene.layout.Background; -import javafx.scene.layout.BorderPane; -import javafx.scene.layout.HeaderBar; -import javafx.scene.layout.HeaderButtonType; -import javafx.scene.layout.HBox; -import javafx.scene.layout.VBox; -import javafx.scene.paint.Color; -import javafx.stage.Modality; -import javafx.stage.Stage; -import javafx.stage.StageStyle; -import javafx.stage.Window; - -public final class ExtendedWindow { - - private ExtendedWindow() {} - - public static void showSimpleHeaderBar(StageStyle style, NodeOrientation orientation) { - var stage = new Stage(); - - var headerBar = new HeaderBar(); - headerBar.setBackground(Background.fill(Color.LIGHTSKYBLUE)); - - var resizable = new CheckBox("Resizable"); - resizable.selectedProperty().bindBidirectional(stage.resizableProperty()); - - var fullScreen = new Button("Full-screen"); - fullScreen.setOnAction(e -> stage.setFullScreen(!stage.isFullScreen())); - - var content = new HBox(resizable, fullScreen); - content.setSpacing(10); - content.setAlignment(Pos.CENTER); - headerBar.setCenter(content); - HeaderBar.setAlignment(content, Pos.CENTER); - - var root = new BorderPane(); - root.setTop(headerBar); - - var scene = new Scene(root); - scene.setNodeOrientation(orientation); - - stage.initStyle(style); - stage.setScene(scene); - stage.setWidth(800); - stage.setHeight(500); - stage.show(); - } - - public static void showSplitHeaderBar(StageStyle style, NodeOrientation orientation) { - var stage = new Stage(); - - var leftHeaderBar = new HeaderBar(); - leftHeaderBar.setBackground(Background.fill(Color.VIOLET)); - leftHeaderBar.setTrailingSystemPadding(false); - - var rightHeaderBar = new HeaderBar(); - rightHeaderBar.setBackground(Background.fill(Color.LIGHTSKYBLUE)); - rightHeaderBar.setLeadingSystemPadding(false); - - var resizable = new CheckBox("Resizable"); - resizable.selectedProperty().bindBidirectional(stage.resizableProperty()); - - var fullScreen = new Button("Full-screen"); - fullScreen.setOnAction(e -> stage.setFullScreen(!stage.isFullScreen())); - - var content = new HBox(resizable, fullScreen); - content.setSpacing(10); - content.setAlignment(Pos.CENTER); - rightHeaderBar.setCenter(content); - HeaderBar.setAlignment(content, Pos.CENTER); - - var left = new BorderPane(); - left.setTop(leftHeaderBar); - - var right = new BorderPane(); - right.setTop(rightHeaderBar); - - var root = new SplitPane(left, right); - - var scene = new Scene(root); - scene.setNodeOrientation(orientation); - - stage.initStyle(style); - stage.setScene(scene); - stage.setWidth(800); - stage.setHeight(500); - stage.show(); - } - - public static void showCustomHeaderButtons(StageStyle style, NodeOrientation orientation) { - var stage = new Stage(); - - var headerBar = new HeaderBar(); - headerBar.setBackground(Background.fill(Color.LIGHTSKYBLUE)); - headerBar.setMinHeight(40); - - var resizable = new CheckBox("Resizable"); - resizable.selectedProperty().bindBidirectional(stage.resizableProperty()); - - var fullScreen = new Button("Full-screen"); - fullScreen.setOnAction(e -> stage.setFullScreen(!stage.isFullScreen())); - - var minimize = new Button("Minimize"); - minimize.setOnAction(e -> stage.setIconified(true)); - HeaderBar.setHeaderButtonType(minimize, HeaderButtonType.MINIMIZE); - - var maximize = new Button("Maximize/restore"); - maximize.setOnAction(e -> stage.setMaximized(!stage.isMaximized())); - HeaderBar.setHeaderButtonType(maximize, HeaderButtonType.MAXIMIZE); - - var close = new Button("Close"); - close.setOnAction(e -> stage.close()); - HeaderBar.setHeaderButtonType(close, HeaderButtonType.CLOSE); - - var headerButtons = new HBox(minimize, maximize, close); - headerButtons.setAlignment(Pos.CENTER); - headerButtons.setSpacing(5); - headerBar.setTrailing(headerButtons); - - var content = new HBox(resizable, fullScreen); - content.setSpacing(10); - content.setAlignment(Pos.CENTER); - headerBar.setCenter(content); - HeaderBar.setAlignment(content, Pos.CENTER); - - var root = new BorderPane(); - root.setTop(headerBar); - - var scene = new Scene(root); - scene.setNodeOrientation(orientation); - - stage.initStyle(style); - stage.initDefaultHeaderButtons(false); - stage.setScene(scene); - stage.setWidth(800); - stage.setHeight(500); - stage.show(); - } -} diff --git a/tests/manual/monkey/src/com/oracle/tools/fx/monkey/MainWindow.java b/tests/manual/monkey/src/com/oracle/tools/fx/monkey/MainWindow.java index af5d132cd8e..06fcaeb8c73 100644 --- a/tests/manual/monkey/src/com/oracle/tools/fx/monkey/MainWindow.java +++ b/tests/manual/monkey/src/com/oracle/tools/fx/monkey/MainWindow.java @@ -37,14 +37,12 @@ import javafx.scene.control.CheckMenuItem; import javafx.scene.control.Label; import javafx.scene.control.ListView; -import javafx.scene.control.Menu; import javafx.scene.control.MenuBar; import javafx.scene.control.SplitPane; import javafx.scene.layout.BorderPane; import javafx.scene.layout.GridPane; import javafx.scene.layout.Priority; import javafx.stage.Stage; -import javafx.stage.StageStyle; import com.oracle.tools.fx.monkey.pages.DemoPage; import com.oracle.tools.fx.monkey.settings.FxSettings; import com.oracle.tools.fx.monkey.tools.ClipboardViewer; @@ -156,33 +154,7 @@ private MenuBar createMenu() { FX.menu(m, "_Window"); FX.item(m, orientation); FX.separator(m); - FX.item(m, "Open Modal Window", this::openModalWindow); - var stageMenu = new Menu("Open Extended Window"); - FX.item(stageMenu, "Single HeaderBar, EXTENDED", - () -> ExtendedWindow.showSimpleHeaderBar(StageStyle.EXTENDED, NodeOrientation.INHERIT)); - FX.item(stageMenu, "Single HeaderBar, EXTENDED, RIGHT__TO__LEFT", - () -> ExtendedWindow.showSimpleHeaderBar(StageStyle.EXTENDED, NodeOrientation.RIGHT_TO_LEFT)); - FX.item(stageMenu, "Single HeaderBar, EXTENDED__UTILITY", - () -> ExtendedWindow.showSimpleHeaderBar(StageStyle.EXTENDED_UTILITY, NodeOrientation.INHERIT)); - FX.item(stageMenu, "Single HeaderBar, EXTENDED__UTILITY, RIGHT__TO__LEFT", - () -> ExtendedWindow.showSimpleHeaderBar(StageStyle.EXTENDED_UTILITY, NodeOrientation.RIGHT_TO_LEFT)); - FX.item(stageMenu, "Split HeaderBar, EXTENDED", - () -> ExtendedWindow.showSplitHeaderBar(StageStyle.EXTENDED, NodeOrientation.INHERIT)); - FX.item(stageMenu, "Split HeaderBar, EXTENDED, RIGHT__TO__LEFT", - () -> ExtendedWindow.showSplitHeaderBar(StageStyle.EXTENDED, NodeOrientation.RIGHT_TO_LEFT)); - FX.item(stageMenu, "Split HeaderBar, EXTENDED__UTILITY", - () -> ExtendedWindow.showSplitHeaderBar(StageStyle.EXTENDED_UTILITY, NodeOrientation.INHERIT)); - FX.item(stageMenu, "Split HeaderBar, EXTENDED__UTILITY, RIGHT__TO__LEFT", - () -> ExtendedWindow.showSplitHeaderBar(StageStyle.EXTENDED_UTILITY, NodeOrientation.RIGHT_TO_LEFT)); - FX.item(stageMenu, "HeaderBar with custom buttons, EXTENDED", - () -> ExtendedWindow.showCustomHeaderButtons(StageStyle.EXTENDED, NodeOrientation.INHERIT)); - FX.item(stageMenu, "HeaderBar with custom buttons, EXTENDED, RIGHT__TO__LEFT", - () -> ExtendedWindow.showCustomHeaderButtons(StageStyle.EXTENDED, NodeOrientation.RIGHT_TO_LEFT)); - FX.item(stageMenu, "HeaderBar with custom buttons, EXTENDED__UTILITY", - () -> ExtendedWindow.showCustomHeaderButtons(StageStyle.EXTENDED_UTILITY, NodeOrientation.INHERIT)); - FX.item(stageMenu, "HeaderBar with custom buttons, EXTENDED__UTILITY, RIGHT__TO__LEFT", - () -> ExtendedWindow.showCustomHeaderButtons(StageStyle.EXTENDED_UTILITY, NodeOrientation.RIGHT_TO_LEFT)); - FX.item(m, stageMenu); + FX.item(m, "Stage Tester", this::openStageTesterWindow); return m; } @@ -242,8 +214,8 @@ public int compare(DemoPage a, DemoPage b) { return pages; } - private void openModalWindow() { - new ModalWindow(this).show(); + private void openStageTesterWindow() { + new StageTesterWindow(this).show(); } private void openNative2Ascii() { diff --git a/tests/manual/monkey/src/com/oracle/tools/fx/monkey/ModalWindow.java b/tests/manual/monkey/src/com/oracle/tools/fx/monkey/ModalWindow.java deleted file mode 100644 index dfd5125e0cd..00000000000 --- a/tests/manual/monkey/src/com/oracle/tools/fx/monkey/ModalWindow.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.oracle.tools.fx.monkey; - -import javafx.application.Platform; -import javafx.scene.Scene; -import javafx.scene.control.Button; -import javafx.scene.layout.BorderPane; -import javafx.scene.layout.HBox; -import javafx.stage.Modality; -import javafx.stage.Stage; -import javafx.stage.Window; - -/** - * Test Modal Window - */ -public class ModalWindow extends Stage { - public ModalWindow(Window owner) { - Button b1 = new Button("Does Nothing"); - b1.setDefaultButton(false); - - Button b2 = new Button("Platform.exit()"); - b2.setDefaultButton(false); - b2.setOnAction((ev) -> Platform.exit()); - - Button b3 = new Button("OK"); - b3.setOnAction((ev) -> hide()); - - HBox bp = new HBox(b1, b2, b3); - // FIX BUG: default button property ignored on macOS, ENTER goes to the first button - b3.setDefaultButton(true); - - BorderPane p = new BorderPane(); - p.setBottom(bp); - System.out.println(b2.isDefaultButton() + " " + b3.isDefaultButton()); - - setTitle("Modal Window"); - setScene(new Scene(p)); - initModality(Modality.APPLICATION_MODAL); - initOwner(owner); - setWidth(500); - setHeight(200); - } -} diff --git a/tests/manual/monkey/src/com/oracle/tools/fx/monkey/StageTesterWindow.java b/tests/manual/monkey/src/com/oracle/tools/fx/monkey/StageTesterWindow.java new file mode 100644 index 00000000000..20cde519e78 --- /dev/null +++ b/tests/manual/monkey/src/com/oracle/tools/fx/monkey/StageTesterWindow.java @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.tools.fx.monkey; + +import javafx.application.Platform; +import javafx.collections.FXCollections; +import javafx.geometry.Insets; +import javafx.geometry.NodeOrientation; +import javafx.geometry.Pos; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.CheckBox; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.control.SplitPane; +import javafx.scene.control.TextField; +import javafx.scene.layout.Background; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HeaderBar; +import javafx.scene.layout.HeaderBarBase; +import javafx.scene.layout.HeaderButtonType; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.stage.Modality; +import javafx.stage.Stage; +import javafx.stage.StageStyle; +import javafx.stage.Window; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +public final class StageTesterWindow extends Stage { + + public StageTesterWindow(Stage owner) { + var pane = new GridPane(); + pane.setHgap(10); + pane.setVgap(10); + + pane.add(new Label("Title"), 0, 0); + var titleTextField = new TextField("My Stage"); + pane.add(titleTextField, 1, 0); + + pane.add(new Label("Modality"), 0, 1); + var modalities = Arrays.stream(Modality.values()).map(Enum::name).toList(); + var modalityComboBox = new ComboBox<>(FXCollections.observableArrayList(modalities)); + modalityComboBox.getSelectionModel().select(0); + pane.add(modalityComboBox, 1, 1); + + pane.add(new Label("StageStyle"), 0, 2); + var stageStyles = Arrays.stream(StageStyle.values()).map(Enum::name).toList(); + var stageStyleComboBox = new ComboBox<>(FXCollections.observableArrayList(stageStyles)); + stageStyleComboBox.getSelectionModel().select(0); + pane.add(stageStyleComboBox, 1, 2); + + pane.add(new Label("NodeOrientation"), 0, 3); + var nodeOrientations = Arrays.stream(NodeOrientation.values()).map(Enum::name).toList(); + var nodeOrientationComboBox = new ComboBox<>(FXCollections.observableArrayList(nodeOrientations)); + nodeOrientationComboBox.getSelectionModel().select(2); + pane.add(nodeOrientationComboBox, 1, 3); + + pane.add(new Label("HeaderBar"), 0, 4); + var headerBarComboBox = new ComboBox<>(FXCollections.observableArrayList("None", "Simple", "Split")); + headerBarComboBox.getSelectionModel().select(0); + pane.add(headerBarComboBox, 1, 4); + + pane.add(new Label("DefaultHeaderButtons"), 0, 5); + var defaultHeaderButtonsCheckBox = new CheckBox(); + defaultHeaderButtonsCheckBox.setSelected(true); + pane.add(defaultHeaderButtonsCheckBox, 1, 5); + + pane.add(new Label("AlwaysOnTop"), 0, 6); + var alwaysOnTopCheckBox = new CheckBox(); + pane.add(alwaysOnTopCheckBox, 1, 6); + + pane.add(new Label("Resizable"), 0, 7); + var resizableCheckBox = new CheckBox(); + resizableCheckBox.setSelected(true); + pane.add(resizableCheckBox, 1, 7); + + pane.add(new Label("Iconified"), 0, 8); + var iconifiedCheckBox = new CheckBox(); + pane.add(iconifiedCheckBox, 1, 8); + + pane.add(new Label("Maximized"), 0, 9); + var maximizedCheckBox = new CheckBox(); + pane.add(maximizedCheckBox, 1, 9); + + pane.add(new Label("FullScreen"), 0, 10); + var fullScreenCheckBox = new CheckBox(); + pane.add(fullScreenCheckBox, 1, 10); + + pane.add(new Label("FullScreenExitHint"), 0, 11); + var fullScreenExitHintTextField = new TextField(); + pane.add(fullScreenExitHintTextField, 1, 11); + + var showStageButton = new Button("Show Stage"); + showStageButton.setOnAction(event -> { + var newStage = new Stage(); + newStage.initStyle(StageStyle.valueOf(stageStyleComboBox.getValue())); + newStage.initModality(Modality.valueOf(modalityComboBox.getValue())); + newStage.initDefaultHeaderButtons(defaultHeaderButtonsCheckBox.isSelected()); + newStage.setTitle(titleTextField.getText()); + newStage.setAlwaysOnTop(alwaysOnTopCheckBox.isSelected()); + newStage.setResizable(resizableCheckBox.isSelected()); + newStage.setIconified(iconifiedCheckBox.isSelected()); + newStage.setMaximized(maximizedCheckBox.isSelected()); + newStage.setFullScreen(fullScreenCheckBox.isSelected()); + newStage.setFullScreenExitHint(fullScreenExitHintTextField.getText().isEmpty() + ? null : fullScreenExitHintTextField.getText()); + + if (newStage.getModality() != Modality.NONE) { + newStage.initOwner(StageTesterWindow.this); + } + + Parent root = switch (headerBarComboBox.getValue().toLowerCase(Locale.ROOT)) { + case "simple" -> createSimpleHeaderBarRoot(newStage, !defaultHeaderButtonsCheckBox.isSelected()); + case "split" -> createSplitHeaderBarRoot(newStage, !defaultHeaderButtonsCheckBox.isSelected()); + default -> new BorderPane(createWindowActions(newStage)); + }; + + var scene = new Scene(root); + scene.setNodeOrientation(NodeOrientation.valueOf(nodeOrientationComboBox.getValue())); + + newStage.setWidth(800); + newStage.setHeight(500); + newStage.setScene(scene); + newStage.show(); + }); + + var root = new BorderPane(pane); + root.setPadding(new Insets(20)); + root.setBottom(showStageButton); + BorderPane.setAlignment(showStageButton, Pos.CENTER); + BorderPane.setMargin(showStageButton, new Insets(30, 0, 0, 0)); + + initModality(Modality.APPLICATION_MODAL); + initOwner(owner); + setScene(new Scene(root)); + setTitle("Stage Tester"); + } + + private Parent createSimpleHeaderBarRoot(Stage stage, boolean customWindowButtons) { + var headerBar = new HeaderBar(); + headerBar.setBackground(Background.fill(Color.LIGHTSKYBLUE)); + headerBar.setCenter(new TextField() {{ setPromptText("Search..."); }}); + + var sizeComboBox = new ComboBox<>(FXCollections.observableArrayList("Small", "Medium", "Large")); + sizeComboBox.getSelectionModel().select(0); + + Runnable updateMinHeight = () -> headerBar.setMinHeight( + switch (sizeComboBox.getValue().toLowerCase(Locale.ROOT)) { + case "large" -> 80; + case "medium" -> 40; + default -> headerBar.getMinSystemHeight(); + }); + + sizeComboBox.valueProperty().subscribe(event -> updateMinHeight.run()); + headerBar.minSystemHeightProperty().subscribe(event -> updateMinHeight.run()); + headerBar.setLeading(new Button("✨")); + + var trailingNodes = new HBox(sizeComboBox); + trailingNodes.setAlignment(Pos.CENTER); + trailingNodes.setSpacing(5); + headerBar.setTrailing(trailingNodes); + + if (customWindowButtons) { + trailingNodes.getChildren().addAll(createCustomWindowButtons()); + } + + var borderPane = new BorderPane(); + borderPane.setTop(headerBar); + borderPane.setCenter(createWindowActions(stage)); + + return borderPane; + } + + private Parent createSplitHeaderBarRoot(Stage stage, boolean customWindowButtons) { + var leftHeaderBar = new HeaderBar(); + leftHeaderBar.setBackground(Background.fill(Color.VIOLET)); + leftHeaderBar.setLeading(new Button("✨")); + leftHeaderBar.setCenter(new TextField() {{ setPromptText("Search..."); }}); + leftHeaderBar.setTrailingSystemPadding(false); + + var rightHeaderBar = new HeaderBar(); + rightHeaderBar.setBackground(Background.fill(Color.LIGHTSKYBLUE)); + rightHeaderBar.setLeadingSystemPadding(false); + + var sizeComboBox = new ComboBox<>(FXCollections.observableArrayList("Small", "Medium", "Large")); + sizeComboBox.getSelectionModel().select(0); + + Runnable updateMinHeight = () -> rightHeaderBar.setMinHeight( + switch (sizeComboBox.getValue().toLowerCase(Locale.ROOT)) { + case "large" -> 80; + case "medium" -> 40; + default -> rightHeaderBar.getMinSystemHeight(); + }); + + sizeComboBox.valueProperty().subscribe(event -> updateMinHeight.run()); + rightHeaderBar.minSystemHeightProperty().subscribe(event -> updateMinHeight.run()); + + var trailingNodes = new HBox(sizeComboBox); + trailingNodes.setAlignment(Pos.CENTER); + trailingNodes.setSpacing(5); + rightHeaderBar.setTrailing(trailingNodes); + + if (customWindowButtons) { + trailingNodes.getChildren().addAll(createCustomWindowButtons()); + } + + rightHeaderBar.setTrailing(trailingNodes); + + var left = new BorderPane(); + left.setTop(leftHeaderBar); + left.setCenter(createWindowActions(stage)); + + var right = new BorderPane(); + right.setTop(rightHeaderBar); + + return new SplitPane(left, right); + } + + private List createCustomWindowButtons() { + var iconifyButton = new Button("Iconify"); + var maximizeButton = new Button("Maximize"); + var closeButton = new Button("Close"); + HeaderBarBase.setHeaderButtonType(iconifyButton, HeaderButtonType.MINIMIZE); + HeaderBarBase.setHeaderButtonType(maximizeButton, HeaderButtonType.MAXIMIZE); + HeaderBarBase.setHeaderButtonType(closeButton, HeaderButtonType.CLOSE); + return List.of(iconifyButton, maximizeButton, closeButton); + } + + private Parent createWindowActions(Stage stage) { + var toggleFullScreenButton = new Button("Enter/Exit Full Screen"); + toggleFullScreenButton.setOnAction(event -> stage.setFullScreen(!stage.isFullScreen())); + + var toggleMaximizedButton = new Button("Maximize/Restore"); + toggleMaximizedButton.setOnAction(event -> stage.setMaximized(!stage.isMaximized())); + + var toggleIconifiedButton = new Button("Iconify"); + toggleIconifiedButton.setOnAction(event -> stage.setIconified(true)); + + var closeButton = new Button("Close"); + closeButton.setOnAction(event -> stage.close()); + + var root = new VBox(toggleFullScreenButton, toggleMaximizedButton, toggleIconifiedButton, closeButton); + root.setSpacing(10); + root.setAlignment(Pos.CENTER); + return root; + } +} From 600c721bf30eab73296361b0e5be86a360d9b670 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Mon, 3 Feb 2025 15:11:22 +0100 Subject: [PATCH 45/73] macOS bugfixes, default button behavior --- .../java/com/sun/glass/ui/mac/MacWindow.java | 8 +- .../scene/layout/HeaderButtonBehavior.java | 115 ++++++++++++++++++ .../src/main/java/javafx/scene/Scene.java | 2 +- .../javafx/scene/layout/HeaderBarBase.java | 16 +++ .../javafx/scene/layout/HeaderButtonType.java | 17 +-- .../native-glass/mac/GlassWindow+Overrides.m | 12 ++ .../src/main/native-glass/mac/GlassWindow.h | 3 +- .../src/main/native-glass/mac/GlassWindow.m | 64 ++++++---- .../tools/fx/monkey/StageTesterWindow.java | 2 +- 9 files changed, 206 insertions(+), 33 deletions(-) create mode 100644 modules/javafx.graphics/src/main/java/com/sun/javafx/scene/layout/HeaderButtonBehavior.java diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java index ef86fff0617..e6aa944fd4c 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java @@ -184,10 +184,12 @@ protected void onHeaderBarHeightChanged(double height) { private void updateWindowOverlayMetrics(NSWindowToolbarStyle toolbarStyle) { double minHeight = NSWindowToolbarStyle.SMALL.size.getHeight(); var empty = new Dimension2D(0, 0); + var size = isUtilityWindow() ? toolbarStyle.utilitySize : toolbarStyle.size; + WindowControlsMetrics metrics = isUsingNonClientOverlay() ? _isRightToLeftLayoutDirection() - ? new WindowControlsMetrics(empty, toolbarStyle.size, minHeight) - : new WindowControlsMetrics(toolbarStyle.size, empty, minHeight) + ? new WindowControlsMetrics(empty, size, minHeight) + : new WindowControlsMetrics(size, empty, minHeight) : new WindowControlsMetrics(empty, empty, minHeight); windowControlsMetrics.set(metrics); @@ -200,10 +202,12 @@ private enum NSWindowToolbarStyle { NSWindowToolbarStyle(double width, double height, int style) { this.size = new Dimension2D(width, height); + this.utilitySize = new Dimension2D(height, height); // width intentionally set to height this.style = style; } final Dimension2D size; + final Dimension2D utilitySize; final int style; static NSWindowToolbarStyle ofHeight(double height) { diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/layout/HeaderButtonBehavior.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/layout/HeaderButtonBehavior.java new file mode 100644 index 00000000000..d038a9c6b7e --- /dev/null +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/layout/HeaderButtonBehavior.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.javafx.scene.layout; + +import com.sun.javafx.PlatformUtil; +import javafx.beans.value.ObservableValue; +import javafx.event.EventHandler; +import javafx.scene.Node; +import javafx.scene.Scene; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.HeaderButtonType; +import javafx.stage.Modality; +import javafx.stage.Stage; +import javafx.util.Subscription; +import java.util.Objects; +import java.util.Optional; + +public final class HeaderButtonBehavior implements EventHandler { + + private final Node node; + private final HeaderButtonType type; + private final Subscription subscription; + + public HeaderButtonBehavior(Node node, HeaderButtonType type) { + this.node = Objects.requireNonNull(node); + this.type = Objects.requireNonNull(type); + + ObservableValue stage = node.sceneProperty() + .flatMap(Scene::windowProperty) + .map(w -> w instanceof Stage s ? s : null); + + subscription = Subscription.combine( + type == HeaderButtonType.MAXIMIZE + ? stage.flatMap(Stage::resizableProperty).subscribe(this::onResizableChanged) + : Subscription.EMPTY, + stage.flatMap(Stage::fullScreenProperty).subscribe(this::onFullScreenChanged), + () -> node.removeEventHandler(MouseEvent.MOUSE_RELEASED, this) + ); + + node.addEventHandler(MouseEvent.MOUSE_RELEASED, this); + node.setFocusTraversable(false); + } + + public void dispose() { + subscription.unsubscribe(); + } + + @Override + public void handle(MouseEvent event) { + if (!node.getLayoutBounds().contains(event.getX(), event.getY())) { + return; + } + + switch (type) { + case CLOSE -> getStage().ifPresent(Stage::close); + case ICONIFY -> getStage().ifPresent(stage -> stage.setIconified(true)); + case MAXIMIZE -> getStage().ifPresent(stage -> { + // On macOS, a non-modal window is put into full-screen mode when the maximize button is clicked, + // but enlarged to cover the desktop when the option key is pressed at the same time. + if (PlatformUtil.isMac() && stage.getModality() == Modality.NONE && !event.isAltDown()) { + stage.setFullScreen(!stage.isFullScreen()); + } else { + stage.setMaximized(!stage.isMaximized()); + } + }); + } + } + + private Optional getStage() { + Scene scene = node.getScene(); + if (scene == null) { + return Optional.empty(); + } + + return scene.getWindow() instanceof Stage stage + ? Optional.of(stage) + : Optional.empty(); + } + + private void onResizableChanged(Boolean resizable) { + if (!node.disableProperty().isBound()) { + node.setDisable(resizable == Boolean.FALSE); + } + } + + private void onFullScreenChanged(Boolean fullScreen) { + if (!node.visibleProperty().isBound() && !node.managedProperty().isBound()) { + node.setVisible(fullScreen != Boolean.TRUE); + node.setManaged(fullScreen != Boolean.TRUE); + } + } +} diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java b/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java index 8a08806f3c0..9ceefa8d7c3 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java @@ -3078,7 +3078,7 @@ public HeaderAreaType pickHeaderArea(double x, double y) { if (HeaderBarBase.getHeaderButtonType(intersectedNode) instanceof HeaderButtonType type) { return switch (type) { - case MINIMIZE -> HeaderAreaType.MINIMIZE; + case ICONIFY -> HeaderAreaType.MINIMIZE; case MAXIMIZE -> HeaderAreaType.MAXIMIZE; case CLOSE -> HeaderAreaType.CLOSE; }; diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java index 5719fe80a72..d892d7c7c19 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java @@ -26,6 +26,7 @@ package javafx.scene.layout; import com.sun.glass.ui.WindowControlsMetrics; +import com.sun.javafx.scene.layout.HeaderButtonBehavior; import com.sun.javafx.stage.StageHelper; import com.sun.javafx.tk.quantum.WindowStage; import javafx.beans.property.ReadOnlyDoubleProperty; @@ -36,6 +37,7 @@ import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.input.ContextMenuEvent; +import javafx.scene.input.MouseEvent; import javafx.stage.Stage; import javafx.stage.StageStyle; import javafx.stage.Window; @@ -92,12 +94,26 @@ public static Boolean isDraggable(Node child) { /** * Specifies the {@code HeaderButtonType} of the child, indicating its semantic use in the header bar. + *

    + * This property can be set on any {@link Node}. Specifying a header button type also provides the behavior + * associated with the button type. If the default behavior is not desired, applications can register an + * event filter on the child node that consumes the {@link MouseEvent#MOUSE_RELEASED} event. * * @param child the child node * @param value the {@code HeaderButtonType}, or {@code null} */ public static void setHeaderButtonType(Node child, HeaderButtonType value) { Pane.setConstraint(child, HEADER_BUTTON_TYPE, value); + + if (child.getProperties().get(HeaderButtonBehavior.class) instanceof HeaderButtonBehavior behavior) { + behavior.dispose(); + } + + if (value != null) { + child.getProperties().put(HeaderButtonBehavior.class, new HeaderButtonBehavior(child, value)); + } else { + child.getProperties().remove(HeaderButtonBehavior.class); + } } /** diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderButtonType.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderButtonType.java index 334544ccf30..a4bf192869a 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderButtonType.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderButtonType.java @@ -32,29 +32,32 @@ * Identifies the semantic type of a button in a custom {@link HeaderBar}, which enables integrations * with the platform window manager. For example, hovering over a {@link #MAXIMIZE} button on Windows * will summon snap layouts. - *

    - * This property can be set on any {@link Node}. Applications are still required to provide their own - * click handlers to programmatically trigger the actions associated with header buttons, i.e. call - * appropriate stage methods like {@link Stage#setIconified(boolean)}, {@link Stage#setMaximized(boolean)} - * or {@link Stage#close()}. * * @since 25 * @see HeaderBarBase#setHeaderButtonType(Node, HeaderButtonType) */ public enum HeaderButtonType { + /** - * Identifies the minimize button. + * Identifies the iconify button. * * @see Stage#isIconified() * @see Stage#setIconified(boolean) */ - MINIMIZE, + ICONIFY, /** * Identifies the maximize button. + *

    + * This button toggles the {@link Stage#isMaximized()} or {@link Stage#isFullScreen()} property, + * depending on platform-specific invocation semantics. For example, on macOS the button will + * put the window into full-screen mode by default, but maximize it to cover the desktop when + * the option key is pressed. * * @see Stage#isMaximized() * @see Stage#setMaximized(boolean) + * @see Stage#isFullScreen() + * @see Stage#setFullScreen(boolean) */ MAXIMIZE, diff --git a/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow+Overrides.m b/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow+Overrides.m index 8b71b9d16f6..e3d95097fe3 100644 --- a/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow+Overrides.m +++ b/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow+Overrides.m @@ -195,6 +195,11 @@ - (void)windowWillEnterFullScreen:(NSNotification *)notification self->isWindowResizable = ((mask & NSWindowStyleMaskResizable) != 0); [[self->view delegate] setResizableForFullscreen:YES]; + // When we switch to full-screen mode, we always need the standard window buttons to be shown. + [[self->nsWindow standardWindowButton:NSWindowCloseButton] setHidden:NO]; + [[self->nsWindow standardWindowButton:NSWindowMiniaturizeButton] setHidden:NO]; + [[self->nsWindow standardWindowButton:NSWindowZoomButton] setHidden:NO]; + if (nsWindow.toolbar != nil) { nsWindow.toolbar.visible = NO; } @@ -210,6 +215,13 @@ - (void)windowDidEnterFullScreen:(NSNotification *)notification - (void)windowWillExitFullScreen:(NSNotification *)notification { //NSLog(@"windowWillExitFullScreen"); + + // When we exit full-screen mode, hide the standard window buttons if they were previously hidden. + if (!self->isStandardButtonsVisible) { + [[self->nsWindow standardWindowButton:NSWindowCloseButton] setHidden:YES]; + [[self->nsWindow standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES]; + [[self->nsWindow standardWindowButton:NSWindowZoomButton] setHidden:YES]; + } } - (void)windowDidExitFullScreen:(NSNotification *)notification diff --git a/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.h b/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.h index cd7477a592d..33006c83ff1 100644 --- a/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.h +++ b/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -50,6 +50,7 @@ BOOL isTransparent; BOOL isDecorated; BOOL isResizable; + BOOL isStandardButtonsVisible; BOOL suppressWindowMoveEvent; BOOL suppressWindowResizeEvent; diff --git a/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m b/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m index 1100c9980a0..1df41f64151 100644 --- a/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m +++ b/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m @@ -374,43 +374,58 @@ static jlong _createWindowCommonDo(JNIEnv *env, jobject jWindow, jlong jOwnerPtr NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; { + bool isTitled = (jStyleMask & com_sun_glass_ui_Window_TITLED) != 0; + bool isClosable = (jStyleMask & com_sun_glass_ui_Window_CLOSABLE) != 0; + bool isMinimizable = (jStyleMask & com_sun_glass_ui_Window_MINIMIZABLE) != 0; + bool isMaximizable = (jStyleMask & com_sun_glass_ui_Window_MAXIMIZABLE) != 0; + bool isTransparent = (jStyleMask & com_sun_glass_ui_Window_TRANSPARENT) != 0; + bool isUtility = (jStyleMask & com_sun_glass_ui_Window_UTILITY) != 0; + bool isPopup = (jStyleMask & com_sun_glass_ui_Window_POPUP) != 0; + bool isUnified = (jStyleMask & com_sun_glass_ui_Window_UNIFIED) != 0; + bool isExtended = (jStyleMask & com_sun_glass_ui_Window_EXTENDED) != 0; + bool isNonClientOverlay = (jStyleMask & com_sun_glass_ui_Window_NON_CLIENT_OVERLAY) != 0; + NSUInteger styleMask = NSWindowStyleMaskBorderless; // only titled windows get title - if ((jStyleMask&com_sun_glass_ui_Window_TITLED) != 0) + if (isTitled) { styleMask = styleMask|NSWindowStyleMaskTitled; } - bool isUtility = (jStyleMask & com_sun_glass_ui_Window_UTILITY) != 0; - bool isPopup = (jStyleMask & com_sun_glass_ui_Window_POPUP) != 0; - // only nontransparent windows get decorations - if ((jStyleMask&com_sun_glass_ui_Window_TRANSPARENT) == 0) + if (!isTransparent) { - if ((jStyleMask&com_sun_glass_ui_Window_CLOSABLE) != 0) + if (isClosable) { styleMask = styleMask|NSWindowStyleMaskClosable; } - if (((jStyleMask&com_sun_glass_ui_Window_MINIMIZABLE) != 0) || - ((jStyleMask&com_sun_glass_ui_Window_MAXIMIZABLE) != 0)) + if (isMinimizable || isMaximizable) { // on Mac OS X there is one set for min/max buttons, // so if clients requests either one, we turn them both on styleMask = styleMask|NSWindowStyleMaskMiniaturizable; } - if ((jStyleMask&com_sun_glass_ui_Window_EXTENDED) != 0) { + if (isExtended) { styleMask = styleMask | NSWindowStyleMaskTitled | NSWindowStyleMaskFullSizeContentView; } - if ((jStyleMask&com_sun_glass_ui_Window_UNIFIED) != 0) { + if (isUnified) { styleMask = styleMask|NSWindowStyleMaskTexturedBackground; } if (isUtility) { - styleMask = styleMask | NSWindowStyleMaskUtilityWindow | NSWindowStyleMaskNonactivatingPanel; + styleMask = styleMask | NSWindowStyleMaskNonactivatingPanel; + + // The NSWindowStyleMaskUtilityWindow style makes the close button appear very small (because the + // title bar is thinner than normal). This doesn't work well with client-side title bars in extended + // windows: the point of a client-side title bar is its ability to host custom controls, so it can't + // be very thin. We therefore only add this style for non-extended windows. + if (!isExtended) { + styleMask |= NSWindowStyleMaskUtilityWindow; + } } } @@ -428,27 +443,35 @@ static jlong _createWindowCommonDo(JNIEnv *env, jobject jWindow, jlong jOwnerPtr NSScreen *screen = (NSScreen*)jlong_to_ptr(jScreenPtr); window = [[GlassWindow alloc] _initWithContentRect:NSMakeRect(x, y, w, h) styleMask:styleMask screen:screen jwindow:jWindow]; + window->isStandardButtonsVisible = YES; - if ((jStyleMask & com_sun_glass_ui_Window_EXTENDED) != 0) { + if (isExtended) { [window->nsWindow setTitleVisibility:NSWindowTitleHidden]; [window->nsWindow setTitlebarAppearsTransparent:YES]; [window->nsWindow setToolbar:[NSToolbar new]]; - if ((jStyleMask & com_sun_glass_ui_Window_NON_CLIENT_OVERLAY) == 0) { + // An extended window without non-client controls has no visible standard window buttons. + if (!isNonClientOverlay) { [[window->nsWindow standardWindowButton:NSWindowCloseButton] setHidden:YES]; [[window->nsWindow standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES]; [[window->nsWindow standardWindowButton:NSWindowZoomButton] setHidden:YES]; + window->isStandardButtonsVisible = NO; } } - if ((jStyleMask & com_sun_glass_ui_Window_UNIFIED) != 0) { + if (isUnified) { //Prevent the textured effect from disappearing on border thickness recalculation [window->nsWindow setAutorecalculatesContentBorderThickness:NO forEdge:NSMaxYEdge]; } - if ((jStyleMask & com_sun_glass_ui_Window_UTILITY) != 0) { - [[window->nsWindow standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES]; - [[window->nsWindow standardWindowButton:NSWindowZoomButton] setHidden:YES]; + if (isUtility) { + // When we hide the standard window buttons, they are still part of the button group that activates + // the hover appearance (the icons inside the buttons) when the cursor is over any of the buttons. + // This leads to the close button receiving the hover appearance when the mouse cursor is over one + // of the hidden buttons. Setting the hidden buttons' frame to an empty rectangle fixes this. + [[window->nsWindow standardWindowButton:NSWindowMiniaturizeButton] setFrame:CGRectMake(0, 0, 0, 0)]; + [[window->nsWindow standardWindowButton:NSWindowZoomButton] setFrame:CGRectMake(0, 0, 0, 0)]; + if (!jOwnerPtr) { [window->nsWindow setLevel:NSNormalWindowLevel]; } @@ -459,8 +482,7 @@ static jlong _createWindowCommonDo(JNIEnv *env, jobject jWindow, jlong jOwnerPtr window->owner = getGlassWindow(env, jOwnerPtr)->nsWindow; // not retained (use weak reference?) } window->isResizable = NO; - window->isDecorated = (jStyleMask&com_sun_glass_ui_Window_TITLED) != 0 || - (jStyleMask&com_sun_glass_ui_Window_EXTENDED) != 0; + window->isDecorated = isTitled || isExtended; /* 10.7 full screen window support */ if ([NSWindow instancesRespondToSelector:@selector(toggleFullScreen:)]) { NSWindowCollectionBehavior behavior = [window->nsWindow collectionBehavior]; @@ -478,8 +500,7 @@ static jlong _createWindowCommonDo(JNIEnv *env, jobject jWindow, jlong jOwnerPtr [window->nsWindow setCollectionBehavior: behavior]; } - window->isTransparent = (jStyleMask & com_sun_glass_ui_Window_TRANSPARENT) != 0; - if (window->isTransparent == YES) + if (isTransparent) { [window->nsWindow setBackgroundColor:[NSColor clearColor]]; [window->nsWindow setHasShadow:NO]; @@ -491,6 +512,7 @@ static jlong _createWindowCommonDo(JNIEnv *env, jobject jWindow, jlong jOwnerPtr [window->nsWindow setOpaque:YES]; } + window->isTransparent = isTransparent; window->isSizeAssigned = NO; window->isLocationAssigned = NO; diff --git a/tests/manual/monkey/src/com/oracle/tools/fx/monkey/StageTesterWindow.java b/tests/manual/monkey/src/com/oracle/tools/fx/monkey/StageTesterWindow.java index 20cde519e78..78b2468e02f 100644 --- a/tests/manual/monkey/src/com/oracle/tools/fx/monkey/StageTesterWindow.java +++ b/tests/manual/monkey/src/com/oracle/tools/fx/monkey/StageTesterWindow.java @@ -249,7 +249,7 @@ private List createCustomWindowButtons() { var iconifyButton = new Button("Iconify"); var maximizeButton = new Button("Maximize"); var closeButton = new Button("Close"); - HeaderBarBase.setHeaderButtonType(iconifyButton, HeaderButtonType.MINIMIZE); + HeaderBarBase.setHeaderButtonType(iconifyButton, HeaderButtonType.ICONIFY); HeaderBarBase.setHeaderButtonType(maximizeButton, HeaderButtonType.MAXIMIZE); HeaderBarBase.setHeaderButtonType(closeButton, HeaderButtonType.CLOSE); return List.of(iconifyButton, maximizeButton, closeButton); From 7cb591fa0ed9169ff660c245ad0536f90e4c6564 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Tue, 4 Feb 2025 13:19:36 +0100 Subject: [PATCH 46/73] move StageTester to Tools menu --- .../monkey/src/com/oracle/tools/fx/monkey/MainWindow.java | 4 ++-- .../oracle/tools/fx/monkey/{ => tools}/StageTesterWindow.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename tests/manual/monkey/src/com/oracle/tools/fx/monkey/{ => tools}/StageTesterWindow.java (99%) diff --git a/tests/manual/monkey/src/com/oracle/tools/fx/monkey/MainWindow.java b/tests/manual/monkey/src/com/oracle/tools/fx/monkey/MainWindow.java index 06fcaeb8c73..425834488bb 100644 --- a/tests/manual/monkey/src/com/oracle/tools/fx/monkey/MainWindow.java +++ b/tests/manual/monkey/src/com/oracle/tools/fx/monkey/MainWindow.java @@ -51,6 +51,7 @@ import com.oracle.tools.fx.monkey.tools.EmbeddedJTextAreaWindow; import com.oracle.tools.fx.monkey.tools.KeyboardEventViewer; import com.oracle.tools.fx.monkey.tools.Native2AsciiPane; +import com.oracle.tools.fx.monkey.tools.StageTesterWindow; import com.oracle.tools.fx.monkey.tools.SystemInfoViewer; import com.oracle.tools.fx.monkey.util.FX; import com.oracle.tools.fx.monkey.util.HasSkinnable; @@ -146,6 +147,7 @@ private MenuBar createMenu() { FX.item(m, "JTextArea/JTextField Embedded in SwingNode", this::openJTextArea); FX.item(m, "Keyboard Event Viewer", this::openKeyboardViewer); FX.item(m, "Native to ASCII", this::openNative2Ascii); + FX.item(m, "Stage Tester", this::openStageTesterWindow); FX.item(m, "System Info", this::openSystemInfo); // Logs FX.menu(m, "_Logging"); @@ -153,8 +155,6 @@ private MenuBar createMenu() { // Window FX.menu(m, "_Window"); FX.item(m, orientation); - FX.separator(m); - FX.item(m, "Stage Tester", this::openStageTesterWindow); return m; } diff --git a/tests/manual/monkey/src/com/oracle/tools/fx/monkey/StageTesterWindow.java b/tests/manual/monkey/src/com/oracle/tools/fx/monkey/tools/StageTesterWindow.java similarity index 99% rename from tests/manual/monkey/src/com/oracle/tools/fx/monkey/StageTesterWindow.java rename to tests/manual/monkey/src/com/oracle/tools/fx/monkey/tools/StageTesterWindow.java index 78b2468e02f..8f9dbfe550b 100644 --- a/tests/manual/monkey/src/com/oracle/tools/fx/monkey/StageTesterWindow.java +++ b/tests/manual/monkey/src/com/oracle/tools/fx/monkey/tools/StageTesterWindow.java @@ -23,7 +23,7 @@ * questions. */ -package com.oracle.tools.fx.monkey; +package com.oracle.tools.fx.monkey.tools; import javafx.application.Platform; import javafx.collections.FXCollections; From c30eb6034cf7398a1e415fe7fb167da1deb2b118 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Tue, 4 Feb 2025 13:36:31 +0100 Subject: [PATCH 47/73] add "maximized" pseudo-class for custom maximize button --- .../scene/layout/HeaderButtonBehavior.java | 32 ++++++++++++++----- .../javafx/scene/layout/HeaderButtonType.java | 2 ++ .../fx/monkey/tools/StageTesterWindow.java | 12 ++++++- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/layout/HeaderButtonBehavior.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/layout/HeaderButtonBehavior.java index d038a9c6b7e..e787cbdbb63 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/layout/HeaderButtonBehavior.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/layout/HeaderButtonBehavior.java @@ -27,6 +27,7 @@ import com.sun.javafx.PlatformUtil; import javafx.beans.value.ObservableValue; +import javafx.css.PseudoClass; import javafx.event.EventHandler; import javafx.scene.Node; import javafx.scene.Scene; @@ -40,6 +41,8 @@ public final class HeaderButtonBehavior implements EventHandler { + private static final PseudoClass MAXIMIZED_PSEUDO_CLASS = PseudoClass.getPseudoClass("maximized"); + private final Node node; private final HeaderButtonType type; private final Subscription subscription; @@ -52,16 +55,25 @@ public HeaderButtonBehavior(Node node, HeaderButtonType type) { .flatMap(Scene::windowProperty) .map(w -> w instanceof Stage s ? s : null); - subscription = Subscription.combine( - type == HeaderButtonType.MAXIMIZE - ? stage.flatMap(Stage::resizableProperty).subscribe(this::onResizableChanged) - : Subscription.EMPTY, - stage.flatMap(Stage::fullScreenProperty).subscribe(this::onFullScreenChanged), - () -> node.removeEventHandler(MouseEvent.MOUSE_RELEASED, this) - ); + if (type == HeaderButtonType.MAXIMIZE) { + subscription = Subscription.combine( + stage.flatMap(Stage::resizableProperty).subscribe(this::onResizableChanged), + stage.flatMap(Stage::fullScreenProperty).subscribe(this::onFullScreenChanged), + stage.flatMap(Stage::maximizedProperty).subscribe(this::onMaximizedChanged), + () -> node.removeEventHandler(MouseEvent.MOUSE_RELEASED, this) + ); + } else { + subscription = Subscription.combine( + stage.flatMap(Stage::fullScreenProperty).subscribe(this::onFullScreenChanged), + () -> node.removeEventHandler(MouseEvent.MOUSE_RELEASED, this) + ); + } node.addEventHandler(MouseEvent.MOUSE_RELEASED, this); - node.setFocusTraversable(false); + + if (!node.focusTraversableProperty().isBound()) { + node.setFocusTraversable(false); + } } public void dispose() { @@ -112,4 +124,8 @@ private void onFullScreenChanged(Boolean fullScreen) { node.setManaged(fullScreen != Boolean.TRUE); } } + + private void onMaximizedChanged(Boolean maximized) { + node.pseudoClassStateChanged(MAXIMIZED_PSEUDO_CLASS, maximized == Boolean.TRUE); + } } diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderButtonType.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderButtonType.java index a4bf192869a..bde90b5cc9a 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderButtonType.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderButtonType.java @@ -53,6 +53,8 @@ public enum HeaderButtonType { * depending on platform-specific invocation semantics. For example, on macOS the button will * put the window into full-screen mode by default, but maximize it to cover the desktop when * the option key is pressed. + *

    + * If the window is maximized, the button will have the {@code maximized} pseudo-class. * * @see Stage#isMaximized() * @see Stage#setMaximized(boolean) diff --git a/tests/manual/monkey/src/com/oracle/tools/fx/monkey/tools/StageTesterWindow.java b/tests/manual/monkey/src/com/oracle/tools/fx/monkey/tools/StageTesterWindow.java index 8f9dbfe550b..1cb7ec91bb1 100644 --- a/tests/manual/monkey/src/com/oracle/tools/fx/monkey/tools/StageTesterWindow.java +++ b/tests/manual/monkey/src/com/oracle/tools/fx/monkey/tools/StageTesterWindow.java @@ -27,6 +27,7 @@ import javafx.application.Platform; import javafx.collections.FXCollections; +import javafx.css.PseudoClass; import javafx.geometry.Insets; import javafx.geometry.NodeOrientation; import javafx.geometry.Pos; @@ -246,9 +247,18 @@ private Parent createSplitHeaderBarRoot(Stage stage, boolean customWindowButtons } private List createCustomWindowButtons() { + var closeButton = new Button("Close"); var iconifyButton = new Button("Iconify"); var maximizeButton = new Button("Maximize"); - var closeButton = new Button("Close"); + + maximizeButton.getPseudoClassStates().subscribe(() -> { + if (maximizeButton.getPseudoClassStates().contains(PseudoClass.getPseudoClass("maximized"))) { + maximizeButton.setText("Restore"); + } else { + maximizeButton.setText("Maximize"); + } + }); + HeaderBarBase.setHeaderButtonType(iconifyButton, HeaderButtonType.ICONIFY); HeaderBarBase.setHeaderButtonType(maximizeButton, HeaderButtonType.MAXIMIZE); HeaderBarBase.setHeaderButtonType(closeButton, HeaderButtonType.CLOSE); From 94ce7d4cea74c6de06d7e9c8fe1148e8851c4cc7 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Thu, 13 Feb 2025 03:59:30 +0100 Subject: [PATCH 48/73] Added HeaderBarBase.prefButtonHeight, removed Stage.initDefaultHeaderButtons --- ...sMetrics.java => HeaderButtonMetrics.java} | 10 +- ...sOverlay.java => HeaderButtonOverlay.java} | 269 ++++++++++++++---- .../main/java/com/sun/glass/ui/Window.java | 133 ++++----- .../java/com/sun/glass/ui/gtk/GtkView.java | 11 +- .../java/com/sun/glass/ui/gtk/GtkWindow.java | 83 ++++-- .../java/com/sun/glass/ui/mac/MacWindow.java | 30 +- .../java/com/sun/glass/ui/win/WinView.java | 8 +- .../java/com/sun/glass/ui/win/WinWindow.java | 85 ++++-- .../com/sun/javafx/stage/StageHelper.java | 23 +- .../sun/javafx/stage/StagePeerListener.java | 8 +- .../java/com/sun/javafx/tk/DummyToolkit.java | 6 +- .../main/java/com/sun/javafx/tk/TKStage.java | 4 +- .../main/java/com/sun/javafx/tk/Toolkit.java | 5 +- .../com/sun/javafx/tk/quantum/GlassStage.java | 5 +- .../sun/javafx/tk/quantum/QuantumToolkit.java | 10 +- .../javafx/tk/quantum/ViewSceneOverlay.java | 24 +- .../sun/javafx/tk/quantum/WindowStage.java | 44 ++- .../src/main/java/javafx/scene/Scene.java | 2 +- .../java/javafx/scene/layout/HeaderBar.java | 31 +- .../javafx/scene/layout/HeaderBarBase.java | 81 ++++-- .../javafx/scene/layout/HeaderButtonType.java | 2 +- .../src/main/java/javafx/stage/Stage.java | 86 +++--- .../main/java/javafx/stage/StageStyle.java | 9 +- .../src/main/native-glass/mac/GlassWindow.m | 30 +- .../glass/ui/gtk/WindowDecorationGnome.css | 31 +- .../sun/glass/ui/gtk/WindowDecorationKDE.css | 27 +- .../com/sun/glass/ui/win/WindowDecoration.css | 27 +- ...Test.java => HeaderButtonOverlayTest.java} | 95 ++++--- .../test/com/sun/javafx/pgstub/StubStage.java | 6 +- .../com/sun/javafx/pgstub/StubToolkit.java | 7 +- .../fx/monkey/tools/StageTesterWindow.java | 75 +++-- 31 files changed, 783 insertions(+), 484 deletions(-) rename modules/javafx.graphics/src/main/java/com/sun/glass/ui/{WindowControlsMetrics.java => HeaderButtonMetrics.java} (83%) rename modules/javafx.graphics/src/main/java/com/sun/glass/ui/{WindowControlsOverlay.java => HeaderButtonOverlay.java} (63%) rename modules/javafx.graphics/src/test/java/test/com/sun/glass/ui/{WindowControlsOverlayTest.java => HeaderButtonOverlayTest.java} (78%) diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsMetrics.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/HeaderButtonMetrics.java similarity index 83% rename from modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsMetrics.java rename to modules/javafx.graphics/src/main/java/com/sun/glass/ui/HeaderButtonMetrics.java index 29c9109bac5..da8da8dc8bf 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsMetrics.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/HeaderButtonMetrics.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,15 +30,17 @@ import java.util.Objects; /** - * Provides metrics about the window buttons of {@link StageStyle#EXTENDED} windows. + * Provides metrics about the header buttons of {@link StageStyle#EXTENDED} windows. * * @param leftInset the size of the left inset * @param rightInset the size of the right inset * @param minHeight the minimum height of the window buttons */ -public record WindowControlsMetrics(Dimension2D leftInset, Dimension2D rightInset, double minHeight) { +public record HeaderButtonMetrics(Dimension2D leftInset, Dimension2D rightInset, double minHeight) { - public WindowControlsMetrics { + public static HeaderButtonMetrics EMPTY = new HeaderButtonMetrics(new Dimension2D(0, 0), new Dimension2D(0, 0), 0); + + public HeaderButtonMetrics { Objects.requireNonNull(leftInset); Objects.requireNonNull(rightInset); diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/HeaderButtonOverlay.java similarity index 63% rename from modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java rename to modules/javafx.graphics/src/main/java/com/sun/glass/ui/HeaderButtonOverlay.java index ff92372f6c6..3037ccea0c0 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/WindowControlsOverlay.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/HeaderButtonOverlay.java @@ -28,18 +28,22 @@ import com.sun.glass.events.MouseEvent; import com.sun.javafx.binding.ObjectConstant; import com.sun.javafx.util.Utils; +import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableValue; import javafx.css.CssMetaData; import javafx.css.PseudoClass; import javafx.css.SimpleStyleableBooleanProperty; +import javafx.css.SimpleStyleableDoubleProperty; import javafx.css.SimpleStyleableIntegerProperty; import javafx.css.SimpleStyleableObjectProperty; import javafx.css.StyleConverter; import javafx.css.Styleable; import javafx.css.StyleableBooleanProperty; +import javafx.css.StyleableDoubleProperty; import javafx.css.StyleableIntegerProperty; import javafx.css.StyleableObjectProperty; import javafx.css.StyleableProperty; @@ -49,6 +53,8 @@ import javafx.geometry.VPos; import javafx.scene.Node; import javafx.scene.Scene; +import javafx.scene.layout.HeaderBarBase; +import javafx.scene.layout.HeaderButtonType; import javafx.scene.layout.Region; import javafx.scene.paint.Paint; import javafx.stage.Stage; @@ -66,16 +72,16 @@ * *

    Substructure

    *
      - *
    • {@link Region} — {@code window-button-container} + *
    • {@link Region} — {@code header-button-container} *
        - *
      • {@link Region} — {@code window-button}, {@code minimize-button} - *
      • {@link Region} — {@code window-button}, {@code maximize-button} - *
      • {@link Region} — {@code window-button}, {@code close-button} + *
      • {@link Region} — {@code header-button}, {@code iconify-button} + *
      • {@link Region} — {@code header-button}, {@code maximize-button} + *
      • {@link Region} — {@code header-button}, {@code close-button} *
      *
    * *
    - * + * * * * @@ -83,20 +89,34 @@ * * * + * + * + * + * + * + * + * + * * * * * * * *
    CSS properties of {@code window-button-container}CSS properties of {@code header-button-container}
    CSS propertyValuesDefaultComment
    -fx-button-placement[ left | right ]right - * Specifies the placement of the window buttons on the left or the right side of the window. + * Specifies the placement of the header buttons on the left or the right side of the window. + *
    -fx-button-vertical-alignment[ center | stretch ]center + * Specifies the vertical alignment of the header buttons, either centering the buttons + * within the preferred height or stretching the buttons to fill the preferred height. + *
    -fx-button-default-height<double>0 + * Specifies the default height of the header buttons, which is used when the application + * does not specify a preferred button height. *
    -fx-allow-rtl<boolean>true - * Specifies whether the minimize/maximize/close buttons support right-to-left orientations. + * Specifies whether the iconify/maximize/close buttons support right-to-left orientations. *
    * * - * + * * * * @@ -127,39 +147,70 @@ * *
    CSS properties of {@code minimize-button}, {@code maximize-button}, {@code close-button}CSS properties of {@code iconify-button}, {@code maximize-button}, {@code close-button}
    CSS propertyValuesDefaultComment
    */ -public final class WindowControlsOverlay extends Region { +public final class HeaderButtonOverlay extends Region { + + private static final CssMetaData BUTTON_DEFAULT_HEIGHT_METADATA = + new CssMetaData<>("-fx-button-default-height", StyleConverter.getSizeConverter()) { + @Override + public boolean isSettable(HeaderButtonOverlay overlay) { + return true; + } + + @Override + public StyleableProperty getStyleableProperty(HeaderButtonOverlay overlay) { + return overlay.buttonDefaultHeight; + } + }; - private static final CssMetaData BUTTON_PLACEMENT_METADATA = + private static final CssMetaData BUTTON_PLACEMENT_METADATA = new CssMetaData<>("-fx-button-placement", StyleConverter.getEnumConverter(ButtonPlacement.class), ButtonPlacement.RIGHT) { @Override - public boolean isSettable(WindowControlsOverlay overlay) { + public boolean isSettable(HeaderButtonOverlay overlay) { return true; } @Override - public StyleableProperty getStyleableProperty(WindowControlsOverlay overlay) { + public StyleableProperty getStyleableProperty(HeaderButtonOverlay overlay) { return overlay.buttonPlacement; } }; - private static final CssMetaData ALLOW_RTL_METADATA = + private static final CssMetaData BUTTON_VERTICAL_ALIGNMENT_METADATA = + new CssMetaData<>("-fx-button-vertical-alignment", + StyleConverter.getEnumConverter(ButtonVerticalAlignment.class), + ButtonVerticalAlignment.CENTER) { + @Override + public boolean isSettable(HeaderButtonOverlay overlay) { + return true; + } + + @Override + public StyleableProperty getStyleableProperty(HeaderButtonOverlay overlay) { + return overlay.buttonVerticalAlignment; + } + }; + + private static final CssMetaData ALLOW_RTL_METADATA = new CssMetaData<>("-fx-allow-rtl", StyleConverter.getBooleanConverter(), true) { @Override - public boolean isSettable(WindowControlsOverlay overlay) { + public boolean isSettable(HeaderButtonOverlay overlay) { return true; } @Override - public StyleableProperty getStyleableProperty(WindowControlsOverlay overlay) { + public StyleableProperty getStyleableProperty(HeaderButtonOverlay overlay) { return overlay.allowRtl; } }; private static final List> METADATA = Stream.concat(getClassCssMetaData().stream(), - Stream.of(BUTTON_PLACEMENT_METADATA, ALLOW_RTL_METADATA)).toList(); + Stream.of(BUTTON_DEFAULT_HEIGHT_METADATA, + BUTTON_PLACEMENT_METADATA, + BUTTON_VERTICAL_ALIGNMENT_METADATA, + ALLOW_RTL_METADATA)).toList(); private static final PseudoClass HOVER_PSEUDOCLASS = PseudoClass.getPseudoClass("hover"); private static final PseudoClass PRESSED_PSEUDOCLASS = PseudoClass.getPseudoClass("pressed"); @@ -169,13 +220,30 @@ public StyleableProperty getStyleableProperty(WindowControlsOverlay ove private static final String UTILITY_STYLE_CLASS = "utility"; /** - * The metrics (placement and size) of the window buttons. + * The metrics (placement and size) of header buttons. + */ + private final ObjectProperty metrics = new SimpleObjectProperty<>( + this, "metrics", new HeaderButtonMetrics(new Dimension2D(0, 0), new Dimension2D(0, 0), 0)); + + /** + * Specifies the preferred height of header buttons. + *

    + * Negative values are interpreted as "no preference", which causes the buttons to be laid out + * with their preferred height set to the value of {@link #buttonDefaultHeight}. + */ + private final DoubleProperty prefButtonHeight = new SimpleDoubleProperty( + this, "prefButtonHeight", HeaderBarBase.USE_DEFAULT_SIZE); + + /** + * Specifies the default height of header buttons. + *

    + * This property corresponds to the {@code -fx-button-default-height} CSS property. */ - private final ObjectProperty metrics = new SimpleObjectProperty<>( - this, "metrics", new WindowControlsMetrics(new Dimension2D(0, 0), new Dimension2D(0, 0), 0)); + private final StyleableDoubleProperty buttonDefaultHeight = new SimpleStyleableDoubleProperty( + BUTTON_DEFAULT_HEIGHT_METADATA, this, "buttonDefaultHeight"); /** - * Specifies the placement of the window buttons on the left or the right side of the window. + * Specifies the placement of the header buttons on the left or the right side of the window. *

    * This property corresponds to the {@code -fx-button-placement} CSS property. */ @@ -189,10 +257,25 @@ protected void invalidated() { }; /** - * Specifies whether the minimize/maximize/close buttons support right-to-left orientations. + * Specifies the vertical alignment of the header buttons. + *

    + * This property corresponds to the {@code -fx-button-vertical-alignment} CSS property. + */ + private final StyleableObjectProperty buttonVerticalAlignment = + new SimpleStyleableObjectProperty<>( + BUTTON_VERTICAL_ALIGNMENT_METADATA, this, "buttonVerticalAlignment", + ButtonVerticalAlignment.CENTER) { + @Override + protected void invalidated() { + requestLayout(); + } + }; + + /** + * Specifies whether the iconify/maximize/close buttons support right-to-left orientations. *

    * If this property is {@code true} and the effective node orientation is right-to-left, the - * window buttons are mirrored to the other side of the window. + * header buttons are mirrored to the other side of the window. *

    * This property corresponds to the {@code -fx-allow-rtl} CSS property. */ @@ -209,16 +292,16 @@ protected void invalidated() { * This list is automatically updated by the implementation of {@link ButtonRegion#buttonOrder}. */ private final List orderedButtons = new ArrayList<>(3); - private final ButtonRegion minimizeButton = new ButtonRegion(ButtonType.MINIMIZE, "minimize-button", 0); - private final ButtonRegion maximizeButton = new ButtonRegion(ButtonType.MAXIMIZE, "maximize-button", 1); - private final ButtonRegion closeButton = new ButtonRegion(ButtonType.CLOSE, "close-button", 2); + private final ButtonRegion iconifyButton = new ButtonRegion(HeaderButtonType.ICONIFY, "iconify-button", 0); + private final ButtonRegion maximizeButton = new ButtonRegion(HeaderButtonType.MAXIMIZE, "maximize-button", 1); + private final ButtonRegion closeButton = new ButtonRegion(HeaderButtonType.CLOSE, "close-button", 2); private final Subscription subscriptions; private final boolean utility; private final boolean rightToLeft; private Node buttonAtMouseDown; - public WindowControlsOverlay(ObservableValue stylesheet, boolean utility, boolean rightToLeft) { + public HeaderButtonOverlay(ObservableValue stylesheet, boolean utility, boolean rightToLeft) { this.utility = utility; this.rightToLeft = rightToLeft; @@ -252,17 +335,19 @@ public WindowControlsOverlay(ObservableValue stylesheet, boolean utility resizableSubscription, maximizedSubscription, updateStylesheetSubscription, - stylesheet.subscribe(this::updateStylesheet)); + stylesheet.subscribe(this::updateStylesheet), + prefButtonHeight.subscribe(this::requestLayout), + buttonDefaultHeight.subscribe(this::requestLayout)); - getStyleClass().setAll("window-button-container"); + getStyleClass().setAll("header-button-container"); if (utility) { - minimizeButton.managedProperty().bind(ObjectConstant.valueOf(false)); + iconifyButton.managedProperty().bind(ObjectConstant.valueOf(false)); maximizeButton.managedProperty().bind(ObjectConstant.valueOf(false)); getChildren().add(closeButton); getStyleClass().add(UTILITY_STYLE_CLASS); } else { - getChildren().addAll(minimizeButton, maximizeButton, closeButton); + getChildren().addAll(iconifyButton, maximizeButton, closeButton); } } @@ -270,10 +355,14 @@ public void dispose() { subscriptions.unsubscribe(); } - public ReadOnlyObjectProperty metricsProperty() { + public ReadOnlyObjectProperty metricsProperty() { return metrics; } + public DoubleProperty prefButtonHeightProperty() { + return prefButtonHeight; + } + /** * Classifies and returns the button type at the specified coordinate, or returns * {@code null} if the specified coordinate does not intersect a button. @@ -282,7 +371,7 @@ public ReadOnlyObjectProperty metricsProperty() { * @param y the Y coordinate, in pixels relative to the window * @return the {@code ButtonType} or {@code null} */ - public ButtonType buttonAt(double x, double y) { + public HeaderButtonType buttonAt(double x, double y) { if (!utility) { for (var button : orderedButtons) { if (button.isVisible() && button.getBoundsInParent().contains(x, y)) { @@ -290,7 +379,7 @@ public ButtonType buttonAt(double x, double y) { } } } else if (closeButton.isVisible() && closeButton.getBoundsInParent().contains(x, y)) { - return ButtonType.CLOSE; + return HeaderButtonType.CLOSE; } return null; @@ -306,9 +395,9 @@ public ButtonType buttonAt(double x, double y) { * @return {@code true} if the event was handled, {@code false} otherwise */ public boolean handleMouseEvent(int type, int button, double x, double y) { - ButtonType buttonType = buttonAt(x, y); + HeaderButtonType buttonType = buttonAt(x, y); Node node = buttonType != null ? switch (buttonType) { - case MINIMIZE -> minimizeButton; + case ICONIFY -> iconifyButton; case MAXIMIZE -> maximizeButton; case CLOSE -> closeButton; } : null; @@ -331,7 +420,7 @@ public boolean handleMouseEvent(int type, int button, double x, double y) { } private void handleMouseOver(Node button) { - minimizeButton.pseudoClassStateChanged(HOVER_PSEUDOCLASS, button == minimizeButton); + iconifyButton.pseudoClassStateChanged(HOVER_PSEUDOCLASS, button == iconifyButton); maximizeButton.pseudoClassStateChanged(HOVER_PSEUDOCLASS, button == maximizeButton); closeButton.pseudoClassStateChanged(HOVER_PSEUDOCLASS, button == closeButton); @@ -343,7 +432,7 @@ private void handleMouseOver(Node button) { private void handleMouseExit() { buttonAtMouseDown = null; - for (var node : new Node[] {minimizeButton, maximizeButton, closeButton}) { + for (var node : new Node[] {iconifyButton, maximizeButton, closeButton}) { node.pseudoClassStateChanged(HOVER_PSEUDOCLASS, false); node.pseudoClassStateChanged(PRESSED_PSEUDOCLASS, false); } @@ -357,7 +446,7 @@ private void handleMouseDown(Node node) { } } - private void handleMouseUp(Node node, ButtonType buttonType) { + private void handleMouseUp(Node node, HeaderButtonType buttonType) { boolean releasedOnButton = (buttonAtMouseDown == node); buttonAtMouseDown = null; Scene scene = getScene(); @@ -371,7 +460,7 @@ private void handleMouseUp(Node node, ButtonType buttonType) { if (releasedOnButton) { switch (buttonType) { - case MINIMIZE -> stage.setIconified(true); + case ICONIFY -> stage.setIconified(true); case MAXIMIZE -> stage.setMaximized(!stage.isMaximized()); case CLOSE -> stage.close(); } @@ -379,7 +468,7 @@ private void handleMouseUp(Node node, ButtonType buttonType) { } private void onFocusedChanged(boolean focused) { - minimizeButton.pseudoClassStateChanged(ACTIVE_PSEUDOCLASS, focused); + iconifyButton.pseudoClassStateChanged(ACTIVE_PSEUDOCLASS, focused); maximizeButton.pseudoClassStateChanged(ACTIVE_PSEUDOCLASS, focused); closeButton.pseudoClassStateChanged(ACTIVE_PSEUDOCLASS, focused); } @@ -394,7 +483,7 @@ private void onMaximizedChanged(boolean maximized) { private void updateStyleClass() { boolean darkScene = isDarkBackground(getScene() != null ? getScene().getFill() : null); - toggleStyleClass(minimizeButton, DARK_STYLE_CLASS, darkScene); + toggleStyleClass(iconifyButton, DARK_STYLE_CLASS, darkScene); toggleStyleClass(maximizeButton, DARK_STYLE_CLASS, darkScene); toggleStyleClass(closeButton, DARK_STYLE_CLASS, darkScene); } @@ -415,10 +504,28 @@ private boolean isDarkBackground(Paint paint) { return paint != null && Utils.calculateAverageBrightness(paint) < 0.5; } + private double getEffectiveButtonHeight() { + double prefHeight = prefButtonHeight.get(); + return prefHeight >= 0 ? prefHeight : buttonDefaultHeight.get(); + } + + private double getButtonOffsetY(double buttonHeight) { + return switch (buttonVerticalAlignment.get()) { + case STRETCH -> 0; + case CENTER -> (getEffectiveButtonHeight() - buttonHeight) / 2; + }; + } + + private void ensureRegionPrefHeight(Region region, double prefHeight) { + if (region.getPrefHeight() != prefHeight) { + region.setPrefHeight(prefHeight); + } + } + @Override protected void layoutChildren() { boolean left; - Node button1, button2, button3; + Region button1, button2, button3; if (allowRtl.get() && rightToLeft) { button1 = orderedButtons.get(2); @@ -432,6 +539,15 @@ protected void layoutChildren() { left = buttonPlacement.get() == ButtonPlacement.LEFT; } + double buttonHeight = switch (buttonVerticalAlignment.get()) { + case STRETCH -> getEffectiveButtonHeight(); + case CENTER -> buttonDefaultHeight.get(); + }; + + ensureRegionPrefHeight(button1, buttonHeight); + ensureRegionPrefHeight(button2, buttonHeight); + ensureRegionPrefHeight(button3, buttonHeight); + double width = getWidth(); double button1Width = snapSizeX(boundedWidth(button1)); double button2Width = snapSizeX(boundedWidth(button2)); @@ -443,25 +559,54 @@ protected void layoutChildren() { double button2X = snapPositionX(left ? button1Width : width - button3Width - button2Width); double button3X = snapPositionX(left ? button1Width + button2Width : width - button3Width); double totalWidth = snapSizeX(button1Width + button2Width + button3Width); - double totalHeight = snapSizeY(Math.max(button1Height, Math.max(button2Height, button3Height))); + double totalHeight; + + // A centered button doesn't stretch to fill the preferred height. Instead, we center the button + // vertically within the preferred height, and also add a horizontal offset so that the buttons + // have the same distance from the top and the left/right side of the window. + if (buttonVerticalAlignment.get() == ButtonVerticalAlignment.CENTER) { + if (left) { + double offset = getButtonOffsetY(button1Height); + button1X = snapPositionX(button1X + offset); + button2X = snapPositionX(button2X + offset); + button3X = snapPositionX(button3X + offset); + totalWidth = snapSizeX(totalWidth + offset * 2); + } else { + double offset = getButtonOffsetY(button3Height); + button1X = snapPositionX(button1X - offset); + button2X = snapPositionX(button2X - offset); + button3X = snapPositionX(button3X - offset); + totalWidth = snapSizeX(totalWidth + offset * 2); + } + + totalHeight = snapSizeY(getEffectiveButtonHeight()); + } else { + totalHeight = snapSizeY(Math.max(button1Height, Math.max(button2Height, button3Height))); + } + Dimension2D currentSize = left ? metrics.get().leftInset() : metrics.get().rightInset(); // Update the overlay metrics if they have changed. if (currentSize.getWidth() != totalWidth || currentSize.getHeight() != totalHeight) { - WindowControlsMetrics newMetrics = left - ? new WindowControlsMetrics(new Dimension2D(totalWidth, totalHeight), new Dimension2D(0, 0), totalHeight) - : new WindowControlsMetrics(new Dimension2D(0, 0), new Dimension2D(totalWidth, totalHeight), totalHeight); + var empty = new Dimension2D(0, 0); + var size = new Dimension2D(totalWidth, totalHeight); + HeaderButtonMetrics newMetrics = left + ? new HeaderButtonMetrics(size, empty, buttonDefaultHeight.get()) + : new HeaderButtonMetrics(empty, size, buttonDefaultHeight.get()); metrics.set(newMetrics); } - layoutInArea(button1, button1X, 0, button1Width, button1Height, BASELINE_OFFSET_SAME_AS_HEIGHT, - Insets.EMPTY, true, true, HPos.LEFT, VPos.TOP, false); + layoutInArea(button1, button1X, getButtonOffsetY(button1Height), button1Width, button1Height, + BASELINE_OFFSET_SAME_AS_HEIGHT, Insets.EMPTY, true, true, + HPos.LEFT, VPos.TOP, false); - layoutInArea(button2, button2X, 0, button2Width, button2Height, BASELINE_OFFSET_SAME_AS_HEIGHT, - Insets.EMPTY, true, true, HPos.LEFT, VPos.TOP, false); + layoutInArea(button2, button2X, getButtonOffsetY(button2Height), button2Width, button2Height, + BASELINE_OFFSET_SAME_AS_HEIGHT, Insets.EMPTY, true, true, + HPos.LEFT, VPos.TOP, false); - layoutInArea(button3, button3X, 0, button3Width, button3Height, BASELINE_OFFSET_SAME_AS_HEIGHT, - Insets.EMPTY, true, true, HPos.LEFT, VPos.TOP, false); + layoutInArea(button3, button3X, getButtonOffsetY(button3Height), button3Width, button3Height, + BASELINE_OFFSET_SAME_AS_HEIGHT, Insets.EMPTY, true, true, + HPos.LEFT, VPos.TOP, false); } @Override @@ -486,12 +631,6 @@ private static double boundedSize(double min, double pref, double max) { return Math.min(Math.max(pref, min), Math.max(min, max)); } - public enum ButtonType { - MINIMIZE, - MAXIMIZE, - CLOSE - } - private class ButtonRegion extends Region { private static final CssMetaData BUTTON_ORDER_METADATA = @@ -522,24 +661,24 @@ public StyleableProperty getStyleableProperty(ButtonRegion region) { protected void invalidated() { requestParentLayout(); - WindowControlsOverlay.this.orderedButtons.sort( + HeaderButtonOverlay.this.orderedButtons.sort( Comparator.comparing(ButtonRegion::getButtonOrder)); } }; private final Region glyph = new Region(); - private final ButtonType type; + private final HeaderButtonType type; - ButtonRegion(ButtonType type, String styleClass, int order) { + ButtonRegion(HeaderButtonType type, String styleClass, int order) { this.type = type; orderedButtons.add(this); buttonOrder.set(order); glyph.getStyleClass().setAll("glyph"); getChildren().add(glyph); - getStyleClass().setAll("window-button", styleClass); + getStyleClass().setAll("header-button", styleClass); } - public ButtonType getButtonType() { + public HeaderButtonType getButtonType() { return type; } @@ -561,4 +700,8 @@ protected void layoutChildren() { private enum ButtonPlacement { LEFT, RIGHT } + + private enum ButtonVerticalAlignment { + STRETCH, CENTER + } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java index bb3c1455036..ba06dc944db 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java @@ -26,19 +26,21 @@ import com.sun.glass.events.WindowEvent; import com.sun.prism.impl.PrismSettings; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.ReadOnlyDoubleProperty; import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleObjectProperty; -import javafx.scene.Parent; +import javafx.scene.layout.HeaderBarBase; import javafx.scene.layout.Region; -import javafx.util.Subscription; import java.lang.annotation.Native; -import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.LinkedList; import java.util.List; -import java.util.Objects; public abstract class Window { @@ -171,11 +173,6 @@ static protected void remove(Window window) { */ @Native public static final int MODAL = 1 << 10; - /** - * Indicates that the window has non-client overlay controls. - */ - public static final int NON_CLIENT_OVERLAY = 1 << 11; - final static public class State { @Native public static final int NORMAL = 1; @Native public static final int MINIMIZED = 2; @@ -244,11 +241,6 @@ public static final class Level { private EventHandler eventHandler; - private final List headerBars = new ArrayList<>(); - - protected final ObjectProperty windowControlsMetrics = - new SimpleObjectProperty<>(this, "windowControlsMetrics"); - protected abstract long _createWindow(long ownerPtr, long screenPtr, int mask); protected Window(Window owner, Screen screen, int styleMask) { Application.checkEventThread(); @@ -304,6 +296,54 @@ protected Window(Window owner, Screen screen, int styleMask) { } } + /** + * Specifies the preferred header button height. Sub-classes can use this value in their header button + * visualization, but they are not required to accommodate the preferred height. + *

    + * Implementations should choose a sensible default height for their header button visualization if + * {@link HeaderBarBase#USE_DEFAULT_SIZE} is specified. + */ + private final DoubleProperty prefHeaderButtonHeight = + new SimpleDoubleProperty(this, "prefHeaderButtonHeight", HeaderBarBase.USE_DEFAULT_SIZE); + + public final ReadOnlyDoubleProperty prefHeaderButtonHeightProperty() { + return prefHeaderButtonHeight; + } + + /** + * Sets the preferred header button height. + *

    + * Note: Sub-classes must not use this method to change the preferred header button height. + * It is only invoked by the toolkit's {@link com.sun.javafx.tk.TKStage} implementation. + */ + public final void setPrefHeaderButtonHeight(double height) { + prefHeaderButtonHeight.set(height); + } + + /** + * Specifies the header button overlay. This property is managed by sub-classes that provide an overlay + * for header buttons. It is not required to use an overlay for header buttons; implementations can also + * use other ways of visualizing header buttons. + */ + protected final ObjectProperty headerButtonOverlay = + new SimpleObjectProperty<>(this, "headerButtonOverlay"); + + public final ReadOnlyObjectProperty headerButtonOverlayProperty() { + return headerButtonOverlay; + } + + /** + * Specifies the metrics for header buttons, which applications can use for layout purposes. This property + * is managed by sub-classes that support header buttons. Implementations are required to provide metrics + * for header buttons even if they don't use a header button overlay. + */ + protected final ObjectProperty headerButtonMetrics = + new SimpleObjectProperty<>(this, "headerButtonMetrics", HeaderButtonMetrics.EMPTY); + + public final ReadOnlyObjectProperty headerButtonMetricsProperty() { + return headerButtonMetrics; + } + public boolean isClosed() { Application.checkEventThread(); return this.ptr == 0L; @@ -438,65 +478,6 @@ public void setMenuBar(final MenuBar menubar) { } } - /** - * Returns metrics of the window-provided overlay controls. - * - * @return the overlay metrics - */ - public final ReadOnlyObjectProperty windowControlsMetricsProperty() { - return windowControlsMetrics; - } - - /** - * Returns the window-provided non-client overlay, which is rendered above all application content. - * - * @return the overlay, or {@code null} if the window does not provide an overlay - */ - public Parent getNonClientOverlay() { - return null; - } - - /** - * Registers a user-provided header bar with this window. - * - * @param headerBar the header bar - * @return a {@code Subscription} to unregister, or an empty subscription if this window - * is not an {@link #isExtendedWindow()} - */ - public final Subscription registerHeaderBar(Region headerBar) { - Objects.requireNonNull(headerBar); - - if (!isExtendedWindow()) { - return Subscription.EMPTY; - } - - headerBars.add(headerBar); - - Subscription subscription = headerBar.heightProperty().subscribe(this::updateHeaderBarHeight); - - return () -> { - headerBars.remove(headerBar); - subscription.unsubscribe(); - updateHeaderBarHeight(); - }; - } - - private void updateHeaderBarHeight() { - double maxHeight = headerBars.stream() - .max(Comparator.comparingDouble(Region::getHeight)) - .map(Region::getHeight) - .orElse(0.0); - - onHeaderBarHeightChanged(maxHeight); - } - - /** - * Called when the height of a registered user-provided header bar has changed. - * - * @param height the maximum height of all registered header bars - */ - protected void onHeaderBarHeightChanged(double height) {} - public boolean isDecorated() { Application.checkEventThread(); return this.isDecorated; @@ -758,10 +739,6 @@ public boolean isTransparentWindow() { return (this.styleMask & Window.TRANSPARENT) != 0; } - public boolean isUsingNonClientOverlay() { - return isExtendedWindow() && (styleMask & Window.NON_CLIENT_OVERLAY) != 0; - } - public boolean isFocused() { Application.checkEventThread(); return this.isFocused; diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkView.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkView.java index 80bac2c7b99..6aa25eb95c6 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkView.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkView.java @@ -24,6 +24,7 @@ */ package com.sun.glass.ui.gtk; +import com.sun.glass.ui.HeaderButtonOverlay; import com.sun.glass.ui.Pixels; import com.sun.glass.ui.View; import com.sun.javafx.tk.HeaderAreaType; @@ -163,12 +164,8 @@ protected void notifyMenu(int x, int y, int xAbs, int yAbs, boolean isKeyboardTr @Override protected boolean handleNonClientMouseEvent(long time, int type, int button, int x, int y, int xAbs, int yAbs, int modifiers, int clickCount) { - var window = (GtkWindow)getWindow(); - var overlay = window.getNonClientOverlay(); - if (overlay == null) { - return false; - } - - return overlay.handleMouseEvent(type, button, x / window.getPlatformScaleX(), y / window.getPlatformScaleY()); + return getWindow() instanceof GtkWindow window + && window.headerButtonOverlayProperty().get() instanceof HeaderButtonOverlay overlay + && overlay.handleMouseEvent(type, button, x / window.getPlatformScaleX(), y / window.getPlatformScaleY()); } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java index 43152cbfa28..4bc3e1b1f39 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkWindow.java @@ -26,17 +26,22 @@ import com.sun.glass.ui.Cursor; import com.sun.glass.events.WindowEvent; +import com.sun.glass.ui.HeaderButtonMetrics; import com.sun.glass.ui.Pixels; import com.sun.glass.ui.Screen; import com.sun.glass.ui.View; import com.sun.glass.ui.Window; -import com.sun.glass.ui.WindowControlsOverlay; +import com.sun.glass.ui.HeaderButtonOverlay; import com.sun.javafx.tk.HeaderAreaType; class GtkWindow extends Window { public GtkWindow(Window owner, Screen screen, int styleMask) { super(owner, screen, styleMask); + + if (isExtendedWindow()) { + prefHeaderButtonHeightProperty().subscribe(this::onPrefHeaderButtonHeightChanged); + } } @Override @@ -205,30 +210,6 @@ public long getRawHandle() { return ptr == 0L ? 0L : _getNativeWindowImpl(ptr); } - private WindowControlsOverlay windowControlsOverlay; - - @Override - public WindowControlsOverlay getNonClientOverlay() { - if (windowControlsOverlay == null && isUsingNonClientOverlay()) { - windowControlsOverlay = new WindowControlsOverlay( - PlatformThemeObserver.getInstance().stylesheetProperty(), - isUtilityWindow(), - (getStyleMask() & RIGHT_TO_LEFT) != 0); - - // Set the system-defined absolute minimum size to the size of the window buttons area, - // regardless of whether the application has specified a smaller minimum size. - windowControlsOverlay.metricsProperty().subscribe(metrics -> { - int width = (int)(metrics.totalInsetWidth() * platformScaleX); - int height = (int)(metrics.maxInsetHeight() * platformScaleY); - _setSystemMinimumSize(super.getRawHandle(), width, height); - }); - - windowControlsMetrics.bind(windowControlsOverlay.metricsProperty()); - } - - return windowControlsOverlay; - } - /** * Opens a system menu at the specified coordinates. * @@ -239,6 +220,56 @@ public void showSystemMenu(int x, int y) { _showSystemMenu(super.getRawHandle(), x, y); } + /** + * Creates or disposes the {@link HeaderButtonOverlay} when the preferred header button height has changed. + *

    + * If the preferred height is zero, the overlay is disposed; if the preferred height is non-zero, the + * {@link #headerButtonOverlay} and {@link #headerButtonMetrics} properties will hold the overlay and + * its metrics. + * + * @param height the preferred header button height + */ + private void onPrefHeaderButtonHeightChanged(Number height) { + // Return early if we can keep the existing overlay instance. + if (height.doubleValue() != 0 && headerButtonOverlay.get() != null) { + return; + } + + if (headerButtonOverlay.get() instanceof HeaderButtonOverlay overlay) { + overlay.dispose(); + } + + if (height.doubleValue() == 0) { + headerButtonOverlay.set(null); + headerButtonMetrics.set(HeaderButtonMetrics.EMPTY); + } else { + HeaderButtonOverlay overlay = createHeaderButtonOverlay(); + overlay.metricsProperty().subscribe(headerButtonMetrics::set); + headerButtonOverlay.set(overlay); + } + } + + /** + * Creates a new {@code HeaderButtonOverlay} instance. + */ + private HeaderButtonOverlay createHeaderButtonOverlay() { + var overlay = new HeaderButtonOverlay( + PlatformThemeObserver.getInstance().stylesheetProperty(), + isUtilityWindow(), + (getStyleMask() & RIGHT_TO_LEFT) != 0); + + // Set the system-defined absolute minimum size to the size of the window buttons area, + // regardless of whether the application has specified a smaller minimum size. + overlay.metricsProperty().subscribe(metrics -> { + int w = (int)(metrics.totalInsetWidth() * platformScaleX); + int h = (int)(metrics.maxInsetHeight() * platformScaleY); + _setSystemMinimumSize(super.getRawHandle(), w, h); + }); + + overlay.prefButtonHeightProperty().bind(prefHeaderButtonHeightProperty()); + return overlay; + } + /** * Returns whether the window is draggable at the specified coordinate. *

    @@ -257,7 +288,7 @@ private boolean dragAreaHitTest(int x, int y) { double wx = x / platformScaleX; double wy = y / platformScaleY; - if (windowControlsOverlay != null && windowControlsOverlay.buttonAt(wx, wy) != null) { + if (headerButtonOverlay.get() instanceof HeaderButtonOverlay overlay && overlay.buttonAt(wx, wy) != null) { return false; } diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java index e6aa944fd4c..c163ab2c7d4 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java @@ -26,12 +26,13 @@ import com.sun.glass.events.WindowEvent; import com.sun.glass.ui.Cursor; +import com.sun.glass.ui.HeaderButtonMetrics; import com.sun.glass.ui.Pixels; import com.sun.glass.ui.Screen; import com.sun.glass.ui.View; import com.sun.glass.ui.Window; -import com.sun.glass.ui.WindowControlsMetrics; import javafx.geometry.Dimension2D; +import javafx.scene.layout.HeaderBarBase; import java.nio.ByteBuffer; /** @@ -48,8 +49,7 @@ protected MacWindow(Window owner, Screen screen, int styleMask) { super(owner, screen, styleMask); if (isExtendedWindow()) { - // The default window metrics correspond to a small toolbar style. - updateWindowOverlayMetrics(NSWindowToolbarStyle.SMALL); + prefHeaderButtonHeightProperty().subscribe(this::onPrefHeaderButtonHeightChanged); } } @@ -172,27 +172,27 @@ public void performTitleBarDoubleClickAction() { private native boolean _isRightToLeftLayoutDirection(); - private native void _setToolbarStyle(long ptr, int style); + private native void _setWindowButtonStyle(long ptr, int toolbarStyle, boolean buttonsVisible); - @Override - protected void onHeaderBarHeightChanged(double height) { - var toolbarStyle = NSWindowToolbarStyle.ofHeight(height); - _setToolbarStyle(getRawHandle(), toolbarStyle.style); - updateWindowOverlayMetrics(toolbarStyle); + private void onPrefHeaderButtonHeightChanged(Number height) { + double h = height != null ? height.doubleValue() : HeaderBarBase.USE_DEFAULT_SIZE; + var toolbarStyle = NSWindowToolbarStyle.ofHeight(h); + _setWindowButtonStyle(getRawHandle(), toolbarStyle.style, h != 0); + updateHeaderButtonMetrics(toolbarStyle, h); } - private void updateWindowOverlayMetrics(NSWindowToolbarStyle toolbarStyle) { + private void updateHeaderButtonMetrics(NSWindowToolbarStyle toolbarStyle, double prefButtonHeight) { double minHeight = NSWindowToolbarStyle.SMALL.size.getHeight(); var empty = new Dimension2D(0, 0); var size = isUtilityWindow() ? toolbarStyle.utilitySize : toolbarStyle.size; - WindowControlsMetrics metrics = isUsingNonClientOverlay() + HeaderButtonMetrics metrics = prefButtonHeight != 0 ? _isRightToLeftLayoutDirection() - ? new WindowControlsMetrics(empty, size, minHeight) - : new WindowControlsMetrics(size, empty, minHeight) - : new WindowControlsMetrics(empty, empty, minHeight); + ? new HeaderButtonMetrics(empty, size, minHeight) + : new HeaderButtonMetrics(size, empty, minHeight) + : new HeaderButtonMetrics(empty, empty, minHeight); - windowControlsMetrics.set(metrics); + headerButtonMetrics.set(metrics); } private enum NSWindowToolbarStyle { diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinView.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinView.java index 7b83c6bac7d..0a7e84d1e72 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinView.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinView.java @@ -24,6 +24,7 @@ */ package com.sun.glass.ui.win; +import com.sun.glass.ui.HeaderButtonOverlay; import com.sun.glass.ui.Pixels; import com.sun.glass.ui.View; import com.sun.javafx.tk.HeaderAreaType; @@ -127,13 +128,12 @@ protected boolean handleNonClientMouseEvent(long time, int type, int button, int return false; } - var window = (WinWindow)getWindow(); - var overlay = window.getNonClientOverlay(); - if (overlay != null) { + if (getWindow() instanceof WinWindow window && + window.headerButtonOverlayProperty().get() instanceof HeaderButtonOverlay overlay) { double wx = x / window.getPlatformScaleX(); double wy = y / window.getPlatformScaleY(); - // Give the window button overlay the first chance to handle the event. + // Give the header button overlay the first chance to handle the event. if (overlay.handleMouseEvent(type, button, wx, wy)) { return true; } diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java index 501cebc67d6..40b633dec70 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java @@ -25,7 +25,8 @@ package com.sun.glass.ui.win; import com.sun.glass.ui.Cursor; -import com.sun.glass.ui.WindowControlsOverlay; +import com.sun.glass.ui.HeaderButtonMetrics; +import com.sun.glass.ui.HeaderButtonOverlay; import com.sun.glass.ui.Pixels; import com.sun.glass.ui.Screen; import com.sun.glass.ui.View; @@ -42,7 +43,7 @@ class WinWindow extends Window { public static final long ANCHOR_NO_CAPTURE = (1L << 63); - private static final String WINDOW_DECORATION_STYLESHEET = "WindowDecoration.css"; + private static final String HEADER_BUTTONS_STYLESHEET = "WindowDecoration.css"; private float fxReqWidth; private float fxReqHeight; @@ -56,6 +57,10 @@ class WinWindow extends Window { protected WinWindow(Window owner, Screen screen, int styleMask) { super(owner, screen, styleMask); + + if (isExtendedWindow()) { + prefHeaderButtonHeightProperty().subscribe(this::onPrefHeaderButtonHeightChanged); + } } @Override @@ -324,8 +329,8 @@ void setDeferredClosing(boolean dc) { @Override public void close() { if (!deferredClosing) { - if (windowControlsOverlay != null) { - windowControlsOverlay.dispose(); + if (headerButtonOverlay.get() instanceof HeaderButtonOverlay overlay) { + overlay.dispose(); } super.close(); @@ -335,27 +340,6 @@ void setDeferredClosing(boolean dc) { } } - private WindowControlsOverlay windowControlsOverlay; - - @Override - public WindowControlsOverlay getNonClientOverlay() { - if (windowControlsOverlay == null && isUsingNonClientOverlay()) { - var url = getClass().getResource(WINDOW_DECORATION_STYLESHEET); - if (url == null) { - throw new RuntimeException("Resource not found: " + WINDOW_DECORATION_STYLESHEET); - } - - windowControlsOverlay = new WindowControlsOverlay( - StringConstant.valueOf(url.toExternalForm()), - isUtilityWindow(), - (getStyleMask() & RIGHT_TO_LEFT) != 0); - - windowControlsMetrics.bind(windowControlsOverlay.metricsProperty()); - } - - return windowControlsOverlay; - } - /** * Opens a system menu at the specified coordinates. * @@ -366,6 +350,53 @@ public void showSystemMenu(int x, int y) { _showSystemMenu(getRawHandle(), x, y); } + /** + * Creates or disposes the {@link HeaderButtonOverlay} when the preferred header button height has changed. + *

    + * If the preferred height is zero, the overlay is disposed; if the preferred height is non-zero, the + * {@link #headerButtonOverlay} and {@link #headerButtonMetrics} properties will hold the overlay and + * its metrics. + * + * @param height the preferred header button height + */ + private void onPrefHeaderButtonHeightChanged(Number height) { + // Return early if we can keep the existing overlay instance. + if (height.doubleValue() != 0 && headerButtonOverlay.get() != null) { + return; + } + + if (headerButtonOverlay.get() instanceof HeaderButtonOverlay overlay) { + overlay.dispose(); + } + + if (height.doubleValue() == 0) { + headerButtonOverlay.set(null); + headerButtonMetrics.set(HeaderButtonMetrics.EMPTY); + } else { + HeaderButtonOverlay overlay = createHeaderButtonOverlay(); + overlay.metricsProperty().subscribe(headerButtonMetrics::set); + headerButtonOverlay.set(overlay); + } + } + + /** + * Creates a new {@code HeaderButtonOverlay} instance. + */ + private HeaderButtonOverlay createHeaderButtonOverlay() { + var url = getClass().getResource(HEADER_BUTTONS_STYLESHEET); + if (url == null) { + throw new RuntimeException("Resource not found: " + HEADER_BUTTONS_STYLESHEET); + } + + var overlay = new HeaderButtonOverlay( + StringConstant.valueOf(url.toExternalForm()), + isUtilityWindow(), + (getStyleMask() & RIGHT_TO_LEFT) != 0); + + overlay.prefButtonHeightProperty().bind(prefHeaderButtonHeightProperty()); + return overlay; + } + /** * Classifies the window region at the specified physical coordinate. *

    @@ -393,8 +424,8 @@ enum HT { // If the cursor is over one of the window buttons (minimize, maximize, close), we need to // report the value of HTMINBUTTON, HTMAXBUTTON, or HTCLOSE back to the native layer. - switch (windowControlsOverlay != null ? windowControlsOverlay.buttonAt(wx, wy) : null) { - case MINIMIZE: return HT.MINBUTTON.value; + switch (headerButtonOverlay.get() instanceof HeaderButtonOverlay overlay ? overlay.buttonAt(wx, wy) : null) { + case ICONIFY: return HT.MINBUTTON.value; case MAXIMIZE: return HT.MAXBUTTON.value; case CLOSE: return HT.CLOSE.value; case null: break; diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/stage/StageHelper.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/stage/StageHelper.java index 576999b89b0..7f9d7d086f3 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/stage/StageHelper.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/stage/StageHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,7 +25,9 @@ package com.sun.javafx.stage; +import com.sun.glass.ui.HeaderButtonMetrics; import com.sun.javafx.util.Utils; +import javafx.beans.value.ObservableValue; import javafx.stage.Stage; import javafx.stage.Window; @@ -71,6 +73,18 @@ public static void setImportant(Stage stage, boolean important) { stageAccessor.setImportant(stage, important); } + public static void setPrefHeaderButtonHeight(Stage stage, double height) { + stageAccessor.setPrefHeaderButtonHeight(stage, height); + } + + public static double getPrefHeaderButtonHeight(Stage stage) { + return stageAccessor.getPrefHeaderButtonHeight(stage); + } + + public static ObservableValue getHeaderButtonMetrics(Stage stage) { + return stageAccessor.getHeaderButtonMetrics(stage); + } + public static void setStageAccessor(StageAccessor a) { if (stageAccessor != null) { System.out.println("Warning: Stage accessor already set: " + stageAccessor); @@ -86,7 +100,10 @@ public static StageAccessor getStageAccessor() { public static interface StageAccessor { void doVisibleChanging(Window window, boolean visible); void doVisibleChanged(Window window, boolean visible); - public void setPrimary(Stage stage, boolean primary); - public void setImportant(Stage stage, boolean important); + void setPrimary(Stage stage, boolean primary); + void setImportant(Stage stage, boolean important); + void setPrefHeaderButtonHeight(Stage stage, double height); + double getPrefHeaderButtonHeight(Stage stage); + ObservableValue getHeaderButtonMetrics(Stage stage); } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/stage/StagePeerListener.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/stage/StagePeerListener.java index 664eff49ca2..95ecdffb12b 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/stage/StagePeerListener.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/stage/StagePeerListener.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,7 @@ package com.sun.javafx.stage; +import com.sun.glass.ui.HeaderButtonMetrics; import javafx.stage.Stage; @@ -38,6 +39,7 @@ public static interface StageAccessor { public void setResizable(Stage stage, boolean resizable); public void setFullScreen(Stage stage, boolean fs); public void setAlwaysOnTop(Stage stage, boolean aot); + public void setHeaderButtonMetrics(Stage stage, HeaderButtonMetrics metrics); } public StagePeerListener(Stage stage, StageAccessor stageAccessor) { @@ -72,5 +74,7 @@ public void changedAlwaysOnTop(boolean aot) { stageAccessor.setAlwaysOnTop(stage, aot); } - + public void changedHeaderButtonMetrics(HeaderButtonMetrics metrics) { + stageAccessor.setHeaderButtonMetrics(stage, metrics); + } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/DummyToolkit.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/DummyToolkit.java index e1fd688d203..0d214e91a31 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/DummyToolkit.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/DummyToolkit.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -101,9 +101,7 @@ public void exitAllNestedEventLoops() { } @Override - public TKStage createTKStage(Window peerWindow, StageStyle stageStyle, boolean primary, - boolean nonClientOverlay, Modality modality, TKStage owner, - boolean rtl) { + public TKStage createTKStage(Window peerWindow, StageStyle stageStyle, boolean primary, Modality modality, TKStage owner, boolean rtl) { throw new UnsupportedOperationException("Not supported yet."); } diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKStage.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKStage.java index 6bd02811633..44ebf3cff76 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKStage.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKStage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -122,6 +122,8 @@ public void setBounds(float x, float y, boolean xSet, boolean ySet, public void setFullScreen(boolean fullScreen); + public void setPrefHeaderButtonHeight(double height); + // ================================================================================================================= // Functions diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/Toolkit.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/Toolkit.java index f1ec5bef5fc..d76cfbdc04e 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/Toolkit.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/Toolkit.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -360,8 +360,7 @@ protected Toolkit() { public abstract boolean isNestedLoopRunning(); - public abstract TKStage createTKStage(Window peerWindow, StageStyle stageStyle, boolean primary, - boolean nonClientOverlay, Modality modality, TKStage owner, boolean rtl); + public abstract TKStage createTKStage(Window peerWindow, StageStyle stageStyle, boolean primary, Modality modality, TKStage owner, boolean rtl); public abstract TKStage createTKPopupStage(Window peerWindow, StageStyle popupStyle, TKStage owner); public abstract TKStage createTKEmbeddedStage(HostInterface host); diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/GlassStage.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/GlassStage.java index 9045f1161a4..105595bbbbd 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/GlassStage.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/GlassStage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -74,6 +74,9 @@ protected GlassStage() { this.stageListener = listener; } + @Override + public void setPrefHeaderButtonHeight(double height) {} + protected final GlassScene getScene() { return scene; } diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java index 389e581b7b9..4c8b2e7c7d5 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -608,11 +608,9 @@ void vsyncHint() { } } - @Override public TKStage createTKStage(Window peerWindow, StageStyle stageStyle, boolean primary, - boolean nonClientOverlay, Modality modality, TKStage owner, - boolean rtl) { + @Override public TKStage createTKStage(Window peerWindow, StageStyle stageStyle, boolean primary, Modality modality, TKStage owner, boolean rtl) { assertToolkitRunning(); - WindowStage stage = new WindowStage(peerWindow, stageStyle, nonClientOverlay, modality, owner); + WindowStage stage = new WindowStage(peerWindow, stageStyle, modality, owner); if (primary) { stage.setIsPrimary(); } @@ -683,7 +681,7 @@ void vsyncHint() { @Override public TKStage createTKPopupStage(Window peerWindow, StageStyle popupStyle, TKStage owner) { assertToolkitRunning(); - WindowStage stage = new WindowStage(peerWindow, popupStyle, false, null, owner); + WindowStage stage = new WindowStage(peerWindow, popupStyle, null, owner); stage.setIsPopup(); stage.init(systemMenu); return stage; diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/ViewSceneOverlay.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/ViewSceneOverlay.java index d35bf440951..f146f663cf4 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/ViewSceneOverlay.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/ViewSceneOverlay.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,6 +26,7 @@ package com.sun.javafx.tk.quantum; import com.sun.javafx.scene.NodeHelper; +import com.sun.javafx.scene.SceneHelper; import javafx.scene.Node; import javafx.scene.Parent; import javafx.scene.SubScene; @@ -39,6 +40,7 @@ final class ViewSceneOverlay { private final javafx.scene.Scene fxScene; private final ViewPainter painter; private Parent root; + private boolean rootDirty; private double width, height; ViewSceneOverlay(javafx.scene.Scene fxScene, ViewPainter painter) { @@ -85,21 +87,25 @@ public void setRoot(Parent root) { if (root != null) { NodeHelper.setScenes(root, fxScene, null); } + + rootDirty = true; } public void synchronize() { - if (root != null && !NodeHelper.isDirtyEmpty(root)) { - syncPeer(root); + if (rootDirty || (root != null && !NodeHelper.isDirtyEmpty(root))) { + rootDirty = false; + + if (root != null) { + syncPeer(root); + painter.setOverlayRoot(NodeHelper.getPeer(root)); + } else { + painter.setOverlayRoot(null); + SceneHelper.getPeer(fxScene).entireSceneNeedsRepaint(); + } } } private void syncPeer(Node node) { - if (root != null) { - painter.setOverlayRoot(NodeHelper.getPeer(root)); - } else { - painter.setOverlayRoot(null); - } - NodeHelper.syncPeer(node); if (node instanceof Parent parent) { diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/WindowStage.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/WindowStage.java index 07ea252ce02..db977620055 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/WindowStage.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/WindowStage.java @@ -42,9 +42,11 @@ import com.sun.javafx.PlatformUtil; import com.sun.javafx.iio.common.PushbroomScaler; import com.sun.javafx.iio.common.ScalerFactory; +import com.sun.javafx.stage.StagePeerListener; import com.sun.javafx.tk.FocusCause; import com.sun.javafx.tk.TKScene; import com.sun.javafx.tk.TKStage; +import com.sun.javafx.tk.TKStageListener; import com.sun.prism.Image; import com.sun.prism.PixelFormat; import java.util.Locale; @@ -59,7 +61,6 @@ public class WindowStage extends GlassStage { private StageStyle style; private GlassStage owner = null; private Modality modality = Modality.NONE; - private boolean nonClientOverlay; private OverlayWarning warning = null; private boolean rtl = false; @@ -84,12 +85,10 @@ public class WindowStage extends GlassStage { ".QuantumMessagesBundle", LOCALE); - public WindowStage(javafx.stage.Window peerWindow, StageStyle stageStyle, boolean nonClientOverlay, - Modality modality, TKStage owner) { + public WindowStage(javafx.stage.Window peerWindow, final StageStyle stageStyle, Modality modality, TKStage owner) { this.style = stageStyle; this.owner = (GlassStage)owner; this.modality = modality; - this.nonClientOverlay = nonClientOverlay; if (peerWindow instanceof javafx.stage.Stage) { fxStage = (Stage)peerWindow; @@ -182,14 +181,21 @@ private void initPlatformWindow() { windowMask |= Window.MODAL; } - if (nonClientOverlay) { - windowMask |= Window.NON_CLIENT_OVERLAY; - } - platformWindow = app.createWindow(ownerWindow, Screen.getMainScreen(), windowMask); platformWindow.setResizable(resizable); platformWindow.setFocusable(focusable); + if (platformWindow.isExtendedWindow()) { + platformWindow.headerButtonOverlayProperty().subscribe(overlay -> { + ViewScene scene = getViewScene(); + if (scene != null) { + scene.setOverlay(isInFullScreen ? null : overlay); + } + }); + + platformWindow.headerButtonMetricsProperty().subscribe(this::notifyHeaderButtonMetricsChanged); + } + if (fxStage != null && fxStage.getScene() != null) { javafx.scene.paint.Paint paint = fxStage.getScene().getFill(); if (paint instanceof javafx.scene.paint.Color) { @@ -224,6 +230,12 @@ private void computeAndSetBackground(List stops) { } } + private void notifyHeaderButtonMetricsChanged() { + if (stageListener instanceof StagePeerListener listener && platformWindow != null) { + listener.changedHeaderButtonMetrics(platformWindow.headerButtonMetricsProperty().get()); + } + } + public final Window getPlatformWindow() { return platformWindow; } @@ -244,12 +256,18 @@ StageStyle getStyle() { return style; } + @Override + public void setTKStageListener(TKStageListener listener) { + super.setTKStageListener(listener); + notifyHeaderButtonMetricsChanged(); + } + @Override public TKScene createTKScene(boolean depthBuffer, boolean msaa) { ViewScene scene = new ViewScene(fxStage != null ? fxStage.getScene() : null, depthBuffer, msaa); // The window-provided overlay is not visible in full-screen mode. if (!isInFullScreen) { - scene.setOverlay(platformWindow.getNonClientOverlay()); + scene.setOverlay(platformWindow.headerButtonOverlayProperty().get()); } return scene; @@ -677,7 +695,7 @@ void setWarning(OverlayWarning newWarning) { if (newWarning != null) { getViewScene().setOverlay(newWarning); } else if (!isInFullScreen) { - getViewScene().setOverlay(platformWindow.getNonClientOverlay()); + getViewScene().setOverlay(platformWindow.headerButtonOverlayProperty().get()); } } @@ -882,4 +900,10 @@ public void releaseInput() { rtl = b; } + @Override + public void setPrefHeaderButtonHeight(double height) { + if (platformWindow != null) { + platformWindow.setPrefHeaderButtonHeight(height); + } + } } diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java b/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java index 9ceefa8d7c3..449b6457014 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java @@ -3076,7 +3076,7 @@ public HeaderAreaType pickHeaderArea(double x, double y) { return draggable == Boolean.TRUE ? HeaderAreaType.DRAGBAR : null; } - if (HeaderBarBase.getHeaderButtonType(intersectedNode) instanceof HeaderButtonType type) { + if (HeaderBarBase.getButtonType(intersectedNode) instanceof HeaderButtonType type) { return switch (type) { case ICONIFY -> HeaderAreaType.MINIMIZE; case MAXIMIZE -> HeaderAreaType.MAXIMIZE; diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java index 439243e2e57..d16ab856b9c 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java @@ -39,6 +39,7 @@ import javafx.geometry.VPos; import javafx.scene.Node; import javafx.scene.input.ContextMenuEvent; +import javafx.stage.Stage; import javafx.stage.StageStyle; /** @@ -74,6 +75,12 @@ * {@link #trailingSystemPaddingProperty() trailingSystemPadding} properties can be used to remove the padding * that is not needed. * + *

    Custom header buttons

    + * If more control over the header buttons is desired, applications can opt out of the default header buttons + * by setting {@link #setPrefButtonHeight(Stage, double)} to zero and provide custom header buttons instead. + * Any JavaFX control can be used as a custom header button by setting its semantic type with the + * {@link #setButtonType(Node, HeaderButtonType)} method. + * *

    System menu

    * Some platforms support a system menu that can be summoned by right-clicking the draggable area. * This platform-provided menu will only be shown if the {@link ContextMenuEvent#CONTEXT_MENU_REQUESTED} @@ -269,11 +276,11 @@ public final void setTrailing(Node value) { /** * Specifies whether additional padding should be added to the leading side of the {@code HeaderBar}. * The size of the additional padding corresponds to the size of the system-reserved area that contains - * the default window buttons (minimize, maximize, and close). If the system-reserved area contains no - * window buttons, no additional padding is added to the leading side of the {@code HeaderBar}. + * the default header buttons (iconify, maximize, and close). If the system-reserved area contains no + * header buttons, no additional padding is added to the leading side of the {@code HeaderBar}. *

    * Applications that use a single {@code HeaderBar} extending the entire width of the window should - * set this property to {@code true} to prevent the window buttons from overlapping the content of the + * set this property to {@code true} to prevent the header buttons from overlapping the content of the * {@code HeaderBar}. * * @defaultValue {@code true} @@ -300,7 +307,7 @@ public final BooleanProperty leadingSystemPaddingProperty() { return leadingSystemPadding; } - public final boolean getLeadingSystemPadding() { + public final boolean isLeadingSystemPadding() { return leadingSystemPadding.get(); } @@ -311,11 +318,11 @@ public final void setLeadingSystemPadding(boolean value) { /** * Specifies whether additional padding should be added to the trailing side of the {@code HeaderBar}. * The size of the additional padding corresponds to the size of the system-reserved area that contains - * the default window buttons (minimize, maximize, and close). If the system-reserved area contains no - * window buttons, no additional padding is added to the trailing side of the {@code HeaderBar}. + * the default header buttons (iconify, maximize, and close). If the system-reserved area contains no + * header buttons, no additional padding is added to the trailing side of the {@code HeaderBar}. *

    * Applications that use a single {@code HeaderBar} extending the entire width of the window should - * set this property to {@code true} to prevent the window buttons from overlapping the content of the + * set this property to {@code true} to prevent the header buttons from overlapping the content of the * {@code HeaderBar}. * * @defaultValue {@code true} @@ -342,7 +349,7 @@ public final BooleanProperty trailingSystemPaddingProperty() { return trailingSystemPadding; } - public final boolean getTrailingSystemPadding() { + public final boolean isTrailingSystemPadding() { return trailingSystemPadding.get(); } @@ -351,13 +358,13 @@ public final void setTrailingSystemPadding(boolean value) { } private boolean isLeftSystemPadding(NodeOrientation nodeOrientation) { - return nodeOrientation == NodeOrientation.LEFT_TO_RIGHT && getLeadingSystemPadding() - || nodeOrientation == NodeOrientation.RIGHT_TO_LEFT && getTrailingSystemPadding(); + return nodeOrientation == NodeOrientation.LEFT_TO_RIGHT && isLeadingSystemPadding() + || nodeOrientation == NodeOrientation.RIGHT_TO_LEFT && isTrailingSystemPadding(); } private boolean isRightSystemPadding(NodeOrientation nodeOrientation) { - return nodeOrientation == NodeOrientation.LEFT_TO_RIGHT && getTrailingSystemPadding() - || nodeOrientation == NodeOrientation.RIGHT_TO_LEFT && getLeadingSystemPadding(); + return nodeOrientation == NodeOrientation.LEFT_TO_RIGHT && isTrailingSystemPadding() + || nodeOrientation == NodeOrientation.RIGHT_TO_LEFT && isLeadingSystemPadding(); } @Override diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java index d892d7c7c19..f2903a15909 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java @@ -25,14 +25,14 @@ package javafx.scene.layout; -import com.sun.glass.ui.WindowControlsMetrics; +import com.sun.glass.ui.HeaderButtonMetrics; import com.sun.javafx.scene.layout.HeaderButtonBehavior; import com.sun.javafx.stage.StageHelper; -import com.sun.javafx.tk.quantum.WindowStage; import javafx.beans.property.ReadOnlyDoubleProperty; import javafx.beans.property.ReadOnlyDoubleWrapper; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.beans.value.ObservableValue; import javafx.geometry.Dimension2D; import javafx.scene.Node; import javafx.scene.Scene; @@ -40,7 +40,6 @@ import javafx.scene.input.MouseEvent; import javafx.stage.Stage; import javafx.stage.StageStyle; -import javafx.stage.Window; import javafx.util.Subscription; /** @@ -102,7 +101,7 @@ public static Boolean isDraggable(Node child) { * @param child the child node * @param value the {@code HeaderButtonType}, or {@code null} */ - public static void setHeaderButtonType(Node child, HeaderButtonType value) { + public static void setButtonType(Node child, HeaderButtonType value) { Pane.setConstraint(child, HEADER_BUTTON_TYPE, value); if (child.getProperties().get(HeaderButtonBehavior.class) instanceof HeaderButtonBehavior behavior) { @@ -122,51 +121,75 @@ public static void setHeaderButtonType(Node child, HeaderButtonType value) { * @param child the child node * @return the {@code HeaderButtonType}, or {@code null} */ - public static HeaderButtonType getHeaderButtonType(Node child) { + public static HeaderButtonType getButtonType(Node child) { return (HeaderButtonType)Pane.getConstraint(child, HEADER_BUTTON_TYPE); } - private Subscription subscription; - private WindowControlsMetrics currentMetrics; + /** + * Sentinel value that can be used for {@link #setPrefButtonHeight(Stage, double)} to indicate that + * the platform should choose the platform-specific default button height. + */ + public static final double USE_DEFAULT_SIZE = -1; + + /** + * Specifies the preferred height of the system-provided header buttons of the specified stage. + *

    + * Any value except zero and {@link #USE_DEFAULT_SIZE} is only a hint for the platform window toolkit. + * The platform might accommodate the preferred height in various ways, such as by stretching the header + * buttons (fully or partially) to fill the preferred height, or centering the header buttons (fully or + * partially) within the preferred height. Some platforms might only accommodate the preferred height + * within platform-specific constraints, or ignore it entirely. + *

    + * Setting the preferred height to zero hides the system-provided header buttons, allowing applications to + * use custom header buttons instead (see {@link #setButtonType(Node, HeaderButtonType)}). + *

    + * The default value {@code #USE_DEFAULT_SIZE} indicates that the platform should choose the button height. + * + * @param stage the {@code Stage} + * @param height the preferred height, or 0 to hide the system-provided header buttons + */ + public static void setPrefButtonHeight(Stage stage, double height) { + StageHelper.setPrefHeaderButtonHeight(stage, height); + } + + /** + * Returns the preferred height of the system-provided header buttons of the specified stage. + * + * @param stage the {@code Stage} + * @return the preferred height of the system-provided header buttons + */ + public static double getPrefButtonHeight(Stage stage) { + return StageHelper.getPrefHeaderButtonHeight(stage); + } + + private Subscription subscription = Subscription.EMPTY; + private HeaderButtonMetrics currentMetrics; private boolean currentFullScreen; /** * Constructor called by subclasses. */ protected HeaderBarBase() { - var stage = sceneProperty() + ObservableValue stage = sceneProperty() .flatMap(Scene::windowProperty) .map(w -> w instanceof Stage s ? s : null); - stage.flatMap(Window::showingProperty) - .orElse(false) - .subscribe(this::onShowingChanged); - stage.flatMap(Stage::fullScreenProperty) .orElse(false) .subscribe(this::onFullScreenChanged); + + stage.subscribe(this::onStageChanged); } - private void onShowingChanged(boolean showing) { - if (!showing) { - if (subscription != null) { - subscription.unsubscribe(); - subscription = null; - } - } else if (getScene().getWindow() instanceof Stage stage - && StageHelper.getPeer(stage) instanceof WindowStage windowStage) { - subscription = Subscription.combine( - windowStage - .getPlatformWindow() - .windowControlsMetricsProperty() - .subscribe(this::onMetricsChanged), - windowStage - .getPlatformWindow() - .registerHeaderBar(this)); + private void onStageChanged(Stage stage) { + subscription.unsubscribe(); + + if (stage != null) { + subscription = StageHelper.getHeaderButtonMetrics(stage).subscribe(this::onMetricsChanged); } } - private void onMetricsChanged(WindowControlsMetrics metrics) { + private void onMetricsChanged(HeaderButtonMetrics metrics) { currentMetrics = metrics; updateInsets(); } diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderButtonType.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderButtonType.java index bde90b5cc9a..40ff0d5f0f7 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderButtonType.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderButtonType.java @@ -34,7 +34,7 @@ * will summon snap layouts. * * @since 25 - * @see HeaderBarBase#setHeaderButtonType(Node, HeaderButtonType) + * @see HeaderBarBase#setButtonType(Node, HeaderButtonType) */ public enum HeaderButtonType { diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java b/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java index 10db1b585e8..9ff457ca5c7 100644 --- a/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java +++ b/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java @@ -36,13 +36,11 @@ import javafx.collections.ListChangeListener.Change; import javafx.collections.ObservableList; import javafx.geometry.NodeOrientation; -import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.image.Image; import javafx.scene.input.KeyCombination; -import javafx.scene.layout.HeaderBarBase; -import javafx.scene.layout.HeaderButtonType; +import com.sun.glass.ui.HeaderButtonMetrics; import com.sun.javafx.collections.VetoableListDecorator; import com.sun.javafx.collections.TrackableObservableList; import com.sun.javafx.scene.SceneHelper; @@ -58,6 +56,7 @@ import javafx.beans.property.ReadOnlyBooleanWrapper; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableValue; +import javafx.scene.layout.HeaderBarBase; /** * The JavaFX {@code Stage} class is the top level JavaFX container. @@ -201,6 +200,21 @@ public void setPrimary(Stage stage, boolean primary) { public void setImportant(Stage stage, boolean important) { stage.setImportant(important); } + + @Override + public void setPrefHeaderButtonHeight(Stage stage, double height) { + stage.setPrefHeaderButtonHeight(height); + } + + @Override + public double getPrefHeaderButtonHeight(Stage stage) { + return stage.getPrefHeaderButtonHeight(); + } + + @Override + public ObservableValue getHeaderButtonMetrics(Stage stage) { + return stage.headerButtonMetricsProperty(); + } }); } @@ -230,6 +244,11 @@ public void setFullScreen(Stage stage, boolean fs) { public void setAlwaysOnTop(Stage stage, boolean aot) { stage.alwaysOnTopPropertyImpl().set(aot); } + + @Override + public void setHeaderButtonMetrics(Stage stage, HeaderButtonMetrics metrics) { + stage.headerButtonMetricsProperty().set(metrics); + } }; /** @@ -499,39 +518,6 @@ public final Modality getModality() { return modality; } - private boolean defaultHeaderButtons = true; - - /** - * Specifies whether a stage with a client-side header bar uses the default platform-provided header buttons. - *

    - * If {@code false} is specified, an application must provide its own header buttons and mark them with - * {@link HeaderBarBase#setHeaderButtonType(Node, HeaderButtonType)} to enable integration with the platform - * window manager. - *

    - * This property is only relevant for {@link StageStyle#EXTENDED} or {@link StageStyle#EXTENDED_UTILITY} - * stages, and is ignored otherwise. - * - * @param enabled {@code true} if the stage uses default header buttons, {@code false} otherwise - * @since 25 - */ - public final void initDefaultHeaderButtons(boolean enabled) { - if (hasBeenVisible) { - throw new IllegalStateException("Cannot set default header buttons once stage has been set visible"); - } - - this.defaultHeaderButtons = enabled; - } - - /** - * Returns whether a stage with a client-side header bar uses the default platform-provided header buttons. - * - * @return {@code true} if the stage uses default header buttons, {@code false} otherwise - * @since 25 - */ - public final boolean isDefaultHeaderButtons() { - return defaultHeaderButtons; - } - private Window owner = null; /** @@ -1122,12 +1108,13 @@ private void doVisibleChanging(boolean value) { boolean rtl = scene != null && scene.getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT; StageStyle stageStyle = getStyle(); - setPeer(toolkit.createTKStage(this, stageStyle, isPrimary(), isDefaultHeaderButtons(), + setPeer(toolkit.createTKStage(this, stageStyle, isPrimary(), getModality(), tkStage, rtl)); getPeer().setMinimumSize((int) Math.ceil(getMinWidth()), (int) Math.ceil(getMinHeight())); getPeer().setMaximumSize((int) Math.floor(getMaxWidth()), (int) Math.floor(getMaxHeight())); + getPeer().setPrefHeaderButtonHeight(getPrefHeaderButtonHeight()); setPeerListener(new StagePeerListener(this, STAGE_ACCESSOR)); } } @@ -1290,4 +1277,29 @@ public final String getFullScreenExitHint() { public final ObjectProperty fullScreenExitHintProperty() { return fullScreenExitHint; } + + private ObjectProperty headerButtonMetrics; + + private ObjectProperty headerButtonMetricsProperty() { + if (headerButtonMetrics == null) { + headerButtonMetrics = new SimpleObjectProperty<>(this, "headerButtonMetrics"); + } + + return headerButtonMetrics; + } + + private double prefHeaderButtonHeight = HeaderBarBase.USE_DEFAULT_SIZE; + + private double getPrefHeaderButtonHeight() { + return prefHeaderButtonHeight; + } + + private void setPrefHeaderButtonHeight(double height) { + prefHeaderButtonHeight = height; + + TKStage peer = getPeer(); + if (peer != null) { + peer.setPrefHeaderButtonHeight(height); + } + } } diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java b/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java index 80e1007fd15..dcc1efe100b 100644 --- a/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java +++ b/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java @@ -27,6 +27,7 @@ import javafx.application.ConditionalFeature; import javafx.application.Platform; +import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HeaderBar; @@ -84,7 +85,7 @@ public enum StageStyle { * If the feature is not supported by the platform, this style downgrades to {@link StageStyle#DECORATED}. * *

    Usage

    - * An extended window has the default header buttons (minimize, maximize, close), but no system-provided + * An extended window has the default header buttons (iconify, maximize, close), but no system-provided * draggable header bar. Applications need to provide their own header bar by placing a {@link HeaderBar} * control in the scene graph. The {@code HeaderBar} control should be positioned at the top of the window * and its width should extend the entire width of the window, as otherwise the layout of the default window @@ -113,9 +114,9 @@ public enum StageStyle { * *

    Custom header buttons

    * If more control over the header buttons is desired, applications can opt out of the default header buttons - * by setting the {@link Stage#initDefaultHeaderButtons(boolean)} property to {@code false} and provide custom - * header buttons instead. Any JavaFX control can be used as a custom header button by setting its appropriate - * {@link HeaderButtonType}. + * by setting {@link HeaderBar#setPrefButtonHeight(Stage, double)} to zero and providing custom header buttons + * instead. Any JavaFX control can be used as a custom header button by setting its semantic type with the + * {@link HeaderBar#setButtonType(Node, HeaderButtonType)} method. * *

    Title text

    * An extended stage has no title text. Applications that require title text need to provide their own diff --git a/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m b/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m index 1df41f64151..44d945f274d 100644 --- a/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m +++ b/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow.m @@ -383,7 +383,6 @@ static jlong _createWindowCommonDo(JNIEnv *env, jobject jWindow, jlong jOwnerPtr bool isPopup = (jStyleMask & com_sun_glass_ui_Window_POPUP) != 0; bool isUnified = (jStyleMask & com_sun_glass_ui_Window_UNIFIED) != 0; bool isExtended = (jStyleMask & com_sun_glass_ui_Window_EXTENDED) != 0; - bool isNonClientOverlay = (jStyleMask & com_sun_glass_ui_Window_NON_CLIENT_OVERLAY) != 0; NSUInteger styleMask = NSWindowStyleMaskBorderless; // only titled windows get title @@ -449,14 +448,6 @@ static jlong _createWindowCommonDo(JNIEnv *env, jobject jWindow, jlong jOwnerPtr [window->nsWindow setTitleVisibility:NSWindowTitleHidden]; [window->nsWindow setTitlebarAppearsTransparent:YES]; [window->nsWindow setToolbar:[NSToolbar new]]; - - // An extended window without non-client controls has no visible standard window buttons. - if (!isNonClientOverlay) { - [[window->nsWindow standardWindowButton:NSWindowCloseButton] setHidden:YES]; - [[window->nsWindow standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES]; - [[window->nsWindow standardWindowButton:NSWindowZoomButton] setHidden:YES]; - window->isStandardButtonsVisible = NO; - } } if (isUnified) { @@ -1527,21 +1518,28 @@ static jlong _createWindowCommonDo(JNIEnv *env, jobject jWindow, jlong jOwnerPtr /* * Class: com_sun_glass_ui_mac_MacWindow - * Method: _setToolbarStyle - * Signature: (JI)V + * Method: _setWindowButtonStyle + * Signature: (JIZ)V */ -JNIEXPORT void JNICALL Java_com_sun_glass_ui_mac_MacWindow__1setToolbarStyle -(JNIEnv *env, jobject jWindow, jlong jPtr, jint style) +JNIEXPORT void JNICALL Java_com_sun_glass_ui_mac_MacWindow__1setWindowButtonStyle +(JNIEnv *env, jobject jWindow, jlong jPtr, jint toolbarStyle, jboolean buttonsVisible) { - LOG("Java_com_sun_glass_ui_mac_MacWindow__1setToolbarStyle"); + LOG("Java_com_sun_glass_ui_mac_MacWindow__1setWindowButtonStyle"); if (!jPtr) return; GLASS_ASSERT_MAIN_JAVA_THREAD(env); GLASS_POOL_ENTER; { GlassWindow *window = getGlassWindow(env, jPtr); - if (window && window->nsWindow) { - [window->nsWindow setToolbarStyle:style]; + if (window) { + window->isStandardButtonsVisible = buttonsVisible; + + if (window->nsWindow) { + [window->nsWindow setToolbarStyle:toolbarStyle]; + [[window->nsWindow standardWindowButton:NSWindowCloseButton] setHidden:!buttonsVisible]; + [[window->nsWindow standardWindowButton:NSWindowMiniaturizeButton] setHidden:!buttonsVisible]; + [[window->nsWindow standardWindowButton:NSWindowZoomButton] setHidden:!buttonsVisible]; + } } } GLASS_POOL_EXIT; diff --git a/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/gtk/WindowDecorationGnome.css b/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/gtk/WindowDecorationGnome.css index 8573e492ea0..beb58f95af4 100644 --- a/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/gtk/WindowDecorationGnome.css +++ b/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/gtk/WindowDecorationGnome.css @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,64 +23,65 @@ * questions. */ -.window-button-container { +.header-button-container { -fx-button-placement: right; + -fx-button-vertical-alignment: center; + -fx-button-default-height: 36; -fx-allow-rtl: true; } -.minimize-button, +.iconify-button, .maximize-button, .close-button { -fx-background-color: #00000009; -fx-background-radius: 15; -fx-background-insets: 6; -fx-pref-width: 36; - -fx-pref-height: 36; } -.minimize-button.dark, +.iconify-button.dark, .maximize-button.dark, .close-button.dark { -fx-background-color: #ffffff09; } -.minimize-button:active, +.iconify-button:active, .maximize-button:active, .close-button:active { -fx-background-color: #00000015; } -.minimize-button.dark:active, +.iconify-button.dark:active, .maximize-button.dark:active, .close-button.dark:active { -fx-background-color: #ffffff15 } -.minimize-button:active:hover, +.iconify-button:active:hover, .maximize-button:active:hover, .close-button:active:hover { -fx-background-color: #00000025; } -.minimize-button.dark:active:hover, +.iconify-button.dark:active:hover, .maximize-button.dark:active:hover, .close-button.dark:active:hover { -fx-background-color: #ffffff25; } -.minimize-button:pressed, +.iconify-button:pressed, .maximize-button:pressed, .close-button:pressed { -fx-background-color: #00000035 !important; } -.minimize-button.dark:pressed, +.iconify-button.dark:pressed, .maximize-button.dark:pressed, .close-button.dark:pressed { -fx-background-color: #ffffff35 !important; } -.minimize-button > .glyph, +.iconify-button > .glyph, .maximize-button > .glyph, .close-button > .glyph { -fx-background-color: #777; @@ -89,13 +90,13 @@ -fx-position-shape: false; } -.minimize-button:active > .glyph, +.iconify-button:active > .glyph, .maximize-button:active > .glyph, .close-button:active > .glyph { -fx-background-color: #333; } -.minimize-button.dark:active > .glyph, +.iconify-button.dark:active > .glyph, .maximize-button.dark:active > .glyph, .close-button.dark:active > .glyph { -fx-background-color: white; @@ -109,7 +110,7 @@ -fx-background-color: #777; } -.minimize-button > .glyph { +.iconify-button > .glyph { -fx-shape: "m 8,13 v 1 h 8 v -1 z"; } diff --git a/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/gtk/WindowDecorationKDE.css b/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/gtk/WindowDecorationKDE.css index c9caacc6fd8..08091128800 100644 --- a/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/gtk/WindowDecorationKDE.css +++ b/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/gtk/WindowDecorationKDE.css @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,22 +23,23 @@ * questions. */ -.window-button-container { +.header-button-container { -fx-button-placement: right; + -fx-button-vertical-alignment: center; + -fx-button-default-height: 36; -fx-allow-rtl: true; } -.minimize-button, +.iconify-button, .maximize-button, .close-button { -fx-background-color: transparent; -fx-background-radius: 13; -fx-background-insets: 8; -fx-pref-width: 36; - -fx-pref-height: 36; } -.minimize-button > .glyph, +.iconify-button > .glyph, .maximize-button > .glyph, .close-button > .glyph { -fx-background-color: #1d1d1e; @@ -47,43 +48,43 @@ -fx-position-shape: false; } -.minimize-button.dark > .glyph, +.iconify-button.dark > .glyph, .maximize-button.dark > .glyph, .close-button.dark > .glyph { -fx-background-color: #ced0d6; } -.minimize-button:hover, +.iconify-button:hover, .maximize-button:hover, .close-button:hover { -fx-background-color: #27292d; } -.minimize-button.dark:hover, +.iconify-button.dark:hover, .maximize-button.dark:hover, .close-button.dark:hover { -fx-background-color: #dfe1e6; } -.minimize-button:hover > .glyph, +.iconify-button:hover > .glyph, .maximize-button:hover > .glyph, .close-button:hover > .glyph { -fx-background-color: #ced0d6; } -.minimize-button.dark:hover > .glyph, +.iconify-button.dark:hover > .glyph, .maximize-button.dark:hover > .glyph, .close-button.dark:hover > .glyph { -fx-background-color: #393b40; } -.minimize-button:pressed, +.iconify-button:pressed, .maximize-button:pressed, .close-button:pressed { -fx-background-color: #474b52 !important; } -.minimize-button.dark:pressed, +.iconify-button.dark:pressed, .maximize-button.dark:pressed, .close-button.dark:pressed { -fx-background-color: #6c7076 !important; @@ -94,7 +95,7 @@ visibility: hidden; } -.minimize-button > .glyph { +.iconify-button > .glyph { -fx-shape: "m 5,7.5 a 0.5,0.5 0 0 0 -0.3535156,0.1464844 0.5,0.5 0 0 0 0,0.7070312 l 5,5.0000004 a 0.50005,0.50005 0 0 0 0.7070316,0 l 5,-5.0000004 a 0.5,0.5 0 0 0 0,-0.7070312 0.5,0.5 0 0 0 -0.707032,0 L 10,12.292969 5.3535156,7.6464844 A 0.5,0.5 0 0 0 5,7.5 Z"; } diff --git a/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/win/WindowDecoration.css b/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/win/WindowDecoration.css index 4a9f9df3ad3..4366093b54c 100644 --- a/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/win/WindowDecoration.css +++ b/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/win/WindowDecoration.css @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,29 +23,30 @@ * questions. */ -.window-button-container { +.header-button-container { -fx-button-placement: right; + -fx-button-vertical-alignment: stretch; + -fx-button-default-height: 29; -fx-allow-rtl: true; } -.minimize-button, +.iconify-button, .maximize-button, .close-button { -fx-background-color: transparent; -fx-pref-width: 46; - -fx-pref-height: 29; } -.window-button-container.utility > .close-button { +.header-button-container.utility > .close-button { -fx-pref-width: 29; } -.minimize-button:hover, +.iconify-button:hover, .maximize-button:hover { -fx-background-color: #00000015; } -.minimize-button.dark:hover, +.iconify-button.dark:hover, .maximize-button.dark:hover { -fx-background-color: #ffffff15; } @@ -54,26 +55,26 @@ -fx-background-color: #c42b1c; } -.minimize-button:pressed, +.iconify-button:pressed, .maximize-button:pressed, .close-button:pressed { -fx-opacity: 0.8; } -.minimize-button > .glyph, +.iconify-button > .glyph, .maximize-button > .glyph, .close-button > .glyph { -fx-background-color: #777; -fx-scale-shape: false; } -.minimize-button:active > .glyph, +.iconify-button:active > .glyph, .maximize-button:active > .glyph, .close-button:active > .glyph { -fx-background-color: black; } -.minimize-button.dark:active > .glyph, +.iconify-button.dark:active > .glyph, .maximize-button.dark:active > .glyph, .close-button.dark:active > .glyph { -fx-background-color: white; @@ -83,7 +84,7 @@ -fx-background-color: white; } -.minimize-button.dark:pressed > .glyph { +.iconify-button.dark:pressed > .glyph { -fx-background-color: white; } @@ -99,7 +100,7 @@ -fx-background-color: #ffffff20 !important; } -.minimize-button > .glyph { +.iconify-button > .glyph { -fx-shape: "m 0.42342443,4.6755 q -0.0871756,0 -0.16397319,-0.0332 Q 0.18265374,4.6091 0.1245366,4.551 0.06641952,4.4929 0.03320975,4.41608 0,4.33925 0,4.25208 0,4.16488 0.03320975,4.0881 0.06641952,4.0113 0.1245366,3.95112 0.1826537,3.89092 0.25945124,3.85772 0.33624881,3.8245 0.42342443,3.8245 H 8.078274 q 0.087175,0 0.1639731,0.0332 0.076797,0.0332 0.1349147,0.0934 0.058117,0.0602 0.091327,0.13698 0.033209,0.0768 0.033209,0.16397 0,0.0872 -0.03321,0.16397 -0.03321,0.0768 -0.091327,0.13492 -0.058117,0.0581 -0.1349147,0.0913 -0.076797,0.0332 -0.1639731,0.0332 z"; } diff --git a/modules/javafx.graphics/src/test/java/test/com/sun/glass/ui/WindowControlsOverlayTest.java b/modules/javafx.graphics/src/test/java/test/com/sun/glass/ui/HeaderButtonOverlayTest.java similarity index 78% rename from modules/javafx.graphics/src/test/java/test/com/sun/glass/ui/WindowControlsOverlayTest.java rename to modules/javafx.graphics/src/test/java/test/com/sun/glass/ui/HeaderButtonOverlayTest.java index 37cb7a54e44..0b8225e7f3e 100644 --- a/modules/javafx.graphics/src/test/java/test/com/sun/glass/ui/WindowControlsOverlayTest.java +++ b/modules/javafx.graphics/src/test/java/test/com/sun/glass/ui/HeaderButtonOverlayTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,13 +25,14 @@ package test.com.sun.glass.ui; -import com.sun.glass.ui.WindowControlsOverlay; +import com.sun.glass.ui.HeaderButtonOverlay; import com.sun.javafx.binding.ObjectConstant; import javafx.beans.value.ObservableValue; import javafx.geometry.Dimension2D; import javafx.geometry.NodeOrientation; import javafx.scene.Node; import javafx.scene.Scene; +import javafx.scene.layout.HeaderButtonType; import javafx.scene.paint.Color; import javafx.stage.Stage; import org.junit.jupiter.api.Test; @@ -41,7 +42,7 @@ import static org.junit.jupiter.api.Assertions.*; -public class WindowControlsOverlayTest { +public class HeaderButtonOverlayTest { private static final Dimension2D EMPTY = new Dimension2D(0, 0); @@ -50,9 +51,9 @@ public class WindowControlsOverlayTest { */ @Test void rightPlacement() { - var overlay = new WindowControlsOverlay(getStylesheet(""" - .window-button-container { -fx-button-placement: right; } - .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } + var overlay = new HeaderButtonOverlay(getStylesheet(""" + .header-button-container { -fx-button-placement: right; } + .header-button { -fx-pref-width: 20; -fx-pref-height: 10; } """), false, false); var unused = new Scene(overlay); @@ -74,9 +75,9 @@ void rightPlacement() { */ @Test void rightPlacement_rightToLeft() { - var overlay = new WindowControlsOverlay(getStylesheet(""" - .window-button-container { -fx-button-placement: right; } - .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } + var overlay = new HeaderButtonOverlay(getStylesheet(""" + .header-button-container { -fx-button-placement: right; } + .header-button { -fx-pref-width: 20; -fx-pref-height: 10; } """), false, true); var unused = new Scene(overlay); @@ -98,9 +99,9 @@ void rightPlacement_rightToLeft() { */ @Test void leftPlacement() { - var overlay = new WindowControlsOverlay(getStylesheet(""" - .window-button-container { -fx-button-placement: left; } - .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } + var overlay = new HeaderButtonOverlay(getStylesheet(""" + .header-button-container { -fx-button-placement: left; } + .header-button { -fx-pref-width: 20; -fx-pref-height: 10; } """), false, false); var unused = new Scene(overlay); @@ -122,9 +123,9 @@ void leftPlacement() { */ @Test void leftPlacement_rightToLeft() { - var overlay = new WindowControlsOverlay(getStylesheet(""" - .window-button-container { -fx-button-placement: left; } - .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } + var overlay = new HeaderButtonOverlay(getStylesheet(""" + .header-button-container { -fx-button-placement: left; } + .header-button { -fx-pref-width: 20; -fx-pref-height: 10; } """), false, true); var unused = new Scene(overlay); @@ -147,9 +148,9 @@ void leftPlacement_rightToLeft() { */ @Test void customButtonOrder() { - var overlay = new WindowControlsOverlay(getStylesheet(""" - .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } - .minimize-button { -fx-button-order: 5; } + var overlay = new HeaderButtonOverlay(getStylesheet(""" + .header-button { -fx-pref-width: 20; -fx-pref-height: 10; } + .iconify-button { -fx-button-order: 5; } .maximize-button { -fx-button-order: 1; } .close-button { -fx-button-order: 3; } """), false, false); @@ -160,7 +161,7 @@ void customButtonOrder() { overlay.applyCss(); overlay.layout(); - assertTrue(children.get(0).getStyleClass().contains("minimize-button")); + assertTrue(children.get(0).getStyleClass().contains("iconify-button")); assertLayoutBounds(children.get(0), 180, 0, 20, 10); assertTrue(children.get(1).getStyleClass().contains("maximize-button")); assertLayoutBounds(children.get(1), 140, 0, 20, 10); @@ -173,9 +174,9 @@ void customButtonOrder() { */ @Test void customButtonOrder_rightToLeft() { - var overlay = new WindowControlsOverlay(getStylesheet(""" - .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } - .minimize-button { -fx-button-order: 5; } + var overlay = new HeaderButtonOverlay(getStylesheet(""" + .header-button { -fx-pref-width: 20; -fx-pref-height: 10; } + .iconify-button { -fx-button-order: 5; } .maximize-button { -fx-button-order: 1; } .close-button { -fx-button-order: 3; } """), false, true); @@ -187,7 +188,7 @@ void customButtonOrder_rightToLeft() { overlay.applyCss(); overlay.layout(); - assertTrue(children.get(0).getStyleClass().contains("minimize-button")); + assertTrue(children.get(0).getStyleClass().contains("iconify-button")); assertLayoutBounds(children.get(0), 0, 0, 20, 10); assertTrue(children.get(1).getStyleClass().contains("maximize-button")); assertLayoutBounds(children.get(1), 40, 0, 20, 10); @@ -197,8 +198,8 @@ void customButtonOrder_rightToLeft() { @Test void utilityDecorationIsOnlyCloseButton() { - var overlay = new WindowControlsOverlay(getStylesheet(""" - .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } + var overlay = new HeaderButtonOverlay(getStylesheet(""" + .header-button { -fx-pref-width: 20; -fx-pref-height: 10; } """), true, false); var children = overlay.getChildrenUnmodifiable(); @@ -211,9 +212,9 @@ void utilityDecorationIsOnlyCloseButton() { */ @Test void disallowRightToLeft() { - var overlay = new WindowControlsOverlay(getStylesheet(""" - .window-button-container { -fx-button-placement: right; -fx-allow-rtl: false; } - .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } + var overlay = new HeaderButtonOverlay(getStylesheet(""" + .header-button-container { -fx-button-placement: right; -fx-allow-rtl: false; } + .header-button { -fx-pref-width: 20; -fx-pref-height: 10; } """), false, true); var unused = new Scene(overlay); @@ -231,9 +232,9 @@ void disallowRightToLeft() { @Test void activePseudoClassCorrespondsToStageFocusedProperty() { - var overlay = new WindowControlsOverlay(getStylesheet(""" - .window-button-container { -fx-button-placement: right; } - .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } + var overlay = new HeaderButtonOverlay(getStylesheet(""" + .header-button-container { -fx-button-placement: right; } + .header-button { -fx-pref-width: 20; -fx-pref-height: 10; } """), false, false); var scene = new Scene(overlay); @@ -257,9 +258,9 @@ void activePseudoClassCorrespondsToStageFocusedProperty() { */ @Test void maximizeButtonIsDisabledWhenStageIsNotResizable() { - var overlay = new WindowControlsOverlay(getStylesheet(""" - .window-button-container { -fx-button-placement: right; } - .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } + var overlay = new HeaderButtonOverlay(getStylesheet(""" + .header-button-container { -fx-button-placement: right; } + .header-button { -fx-pref-width: 20; -fx-pref-height: 10; } """), false, false); var scene = new Scene(overlay); @@ -281,9 +282,9 @@ void maximizeButtonIsDisabledWhenStageIsNotResizable() { */ @Test void restoreStyleClassIsPresentWhenStageIsMaximized() { - var overlay = new WindowControlsOverlay(getStylesheet(""" - .window-button-container { -fx-button-placement: right; } - .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } + var overlay = new HeaderButtonOverlay(getStylesheet(""" + .header-button-container { -fx-button-placement: right; } + .header-button { -fx-pref-width: 20; -fx-pref-height: 10; } """), false, false); var scene = new Scene(overlay); @@ -304,9 +305,9 @@ void restoreStyleClassIsPresentWhenStageIsMaximized() { */ @Test void darkStyleClassIsPresentWhenSceneFillIsDark() { - var overlay = new WindowControlsOverlay(getStylesheet(""" - .window-button-container { -fx-button-placement: right; } - .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } + var overlay = new HeaderButtonOverlay(getStylesheet(""" + .header-button-container { -fx-button-placement: right; } + .header-button { -fx-pref-width: 20; -fx-pref-height: 10; } """), false, false); var scene = new Scene(overlay); @@ -319,13 +320,13 @@ void darkStyleClassIsPresentWhenSceneFillIsDark() { } /** - * Tests button picking using {@link WindowControlsOverlay#buttonAt(double, double)}. + * Tests button picking using {@link HeaderButtonOverlay#buttonAt(double, double)}. */ @Test void pickButtonAtCoordinates() { - var overlay = new WindowControlsOverlay(getStylesheet(""" - .window-button-container { -fx-button-placement: right; } - .window-button { -fx-pref-width: 20; -fx-pref-height: 10; } + var overlay = new HeaderButtonOverlay(getStylesheet(""" + .header-button-container { -fx-button-placement: right; } + .header-button { -fx-pref-width: 20; -fx-pref-height: 10; } """), false, false); var unused = new Scene(overlay); @@ -334,9 +335,9 @@ void pickButtonAtCoordinates() { overlay.layout(); assertNull(overlay.buttonAt(139, 5)); - assertEquals(WindowControlsOverlay.ButtonType.MINIMIZE, overlay.buttonAt(140, 0)); - assertEquals(WindowControlsOverlay.ButtonType.MAXIMIZE, overlay.buttonAt(165, 5)); - assertEquals(WindowControlsOverlay.ButtonType.CLOSE, overlay.buttonAt(181, 10)); + assertEquals(HeaderButtonType.ICONIFY, overlay.buttonAt(140, 0)); + assertEquals(HeaderButtonType.MAXIMIZE, overlay.buttonAt(165, 5)); + assertEquals(HeaderButtonType.CLOSE, overlay.buttonAt(181, 10)); } private static ObservableValue getStylesheet(String text) { diff --git a/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubStage.java b/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubStage.java index fbaff8d8df2..f33e0262421 100644 --- a/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubStage.java +++ b/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubStage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -219,6 +219,10 @@ public void setFullScreen(boolean fullScreen) { notificationSender.changedFullscreen(fullScreen); } + @Override + public void setPrefHeaderButtonHeight(double height) { + } + @Override public void requestFocus() { notificationSender.changedFocused(true, FocusCause.ACTIVATED); diff --git a/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubToolkit.java b/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubToolkit.java index c46565d1192..4c84b92f661 100644 --- a/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubToolkit.java +++ b/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubToolkit.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -119,9 +119,8 @@ public boolean init() { } @Override - public TKStage createTKStage(Window peerWindow, StageStyle stageStyle, boolean primary, - boolean nonClientOverlay, Modality modality, TKStage owner, - boolean rtl) { + public TKStage createTKStage(Window peerWindow, StageStyle stageStyle, boolean primary, Modality modality, TKStage owner, boolean rtl) { + return new StubStage(); } diff --git a/tests/manual/monkey/src/com/oracle/tools/fx/monkey/tools/StageTesterWindow.java b/tests/manual/monkey/src/com/oracle/tools/fx/monkey/tools/StageTesterWindow.java index 1cb7ec91bb1..6e429dd9149 100644 --- a/tests/manual/monkey/src/com/oracle/tools/fx/monkey/tools/StageTesterWindow.java +++ b/tests/manual/monkey/src/com/oracle/tools/fx/monkey/tools/StageTesterWindow.java @@ -86,46 +86,41 @@ public StageTesterWindow(Stage owner) { pane.add(nodeOrientationComboBox, 1, 3); pane.add(new Label("HeaderBar"), 0, 4); - var headerBarComboBox = new ComboBox<>(FXCollections.observableArrayList("None", "Simple", "Split")); + var headerBarComboBox = new ComboBox<>(FXCollections.observableArrayList( + "None", "Simple", "Simple / custom buttons", "Split", "Split / custom buttons")); headerBarComboBox.getSelectionModel().select(0); pane.add(headerBarComboBox, 1, 4); - pane.add(new Label("DefaultHeaderButtons"), 0, 5); - var defaultHeaderButtonsCheckBox = new CheckBox(); - defaultHeaderButtonsCheckBox.setSelected(true); - pane.add(defaultHeaderButtonsCheckBox, 1, 5); - - pane.add(new Label("AlwaysOnTop"), 0, 6); + pane.add(new Label("AlwaysOnTop"), 0, 5); var alwaysOnTopCheckBox = new CheckBox(); - pane.add(alwaysOnTopCheckBox, 1, 6); + pane.add(alwaysOnTopCheckBox, 1, 5); - pane.add(new Label("Resizable"), 0, 7); + pane.add(new Label("Resizable"), 0, 6); var resizableCheckBox = new CheckBox(); resizableCheckBox.setSelected(true); - pane.add(resizableCheckBox, 1, 7); + pane.add(resizableCheckBox, 1, 6); - pane.add(new Label("Iconified"), 0, 8); + pane.add(new Label("Iconified"), 0, 7); var iconifiedCheckBox = new CheckBox(); - pane.add(iconifiedCheckBox, 1, 8); + pane.add(iconifiedCheckBox, 1, 7); - pane.add(new Label("Maximized"), 0, 9); + pane.add(new Label("Maximized"), 0, 8); var maximizedCheckBox = new CheckBox(); - pane.add(maximizedCheckBox, 1, 9); + pane.add(maximizedCheckBox, 1, 8); - pane.add(new Label("FullScreen"), 0, 10); + pane.add(new Label("FullScreen"), 0, 9); var fullScreenCheckBox = new CheckBox(); - pane.add(fullScreenCheckBox, 1, 10); + pane.add(fullScreenCheckBox, 1, 9); - pane.add(new Label("FullScreenExitHint"), 0, 11); + pane.add(new Label("FullScreenExitHint"), 0, 10); var fullScreenExitHintTextField = new TextField(); - pane.add(fullScreenExitHintTextField, 1, 11); + pane.add(fullScreenExitHintTextField, 1, 10); var showStageButton = new Button("Show Stage"); showStageButton.setOnAction(event -> { var newStage = new Stage(); newStage.initStyle(StageStyle.valueOf(stageStyleComboBox.getValue())); newStage.initModality(Modality.valueOf(modalityComboBox.getValue())); - newStage.initDefaultHeaderButtons(defaultHeaderButtonsCheckBox.isSelected()); newStage.setTitle(titleTextField.getText()); newStage.setAlwaysOnTop(alwaysOnTopCheckBox.isSelected()); newStage.setResizable(resizableCheckBox.isSelected()); @@ -140,8 +135,10 @@ public StageTesterWindow(Stage owner) { } Parent root = switch (headerBarComboBox.getValue().toLowerCase(Locale.ROOT)) { - case "simple" -> createSimpleHeaderBarRoot(newStage, !defaultHeaderButtonsCheckBox.isSelected()); - case "split" -> createSplitHeaderBarRoot(newStage, !defaultHeaderButtonsCheckBox.isSelected()); + case "simple" -> createSimpleHeaderBarRoot(newStage, false); + case "simple / custom buttons" -> createSimpleHeaderBarRoot(newStage, true); + case "split" -> createSplitHeaderBarRoot(newStage, false); + case "split / custom buttons" -> createSplitHeaderBarRoot(newStage, true); default -> new BorderPane(createWindowActions(newStage)); }; @@ -177,13 +174,34 @@ private Parent createSimpleHeaderBarRoot(Stage stage, boolean customWindowButton Runnable updateMinHeight = () -> headerBar.setMinHeight( switch (sizeComboBox.getValue().toLowerCase(Locale.ROOT)) { case "large" -> 80; - case "medium" -> 40; + case "medium" -> 50; default -> headerBar.getMinSystemHeight(); }); sizeComboBox.valueProperty().subscribe(event -> updateMinHeight.run()); headerBar.minSystemHeightProperty().subscribe(event -> updateMinHeight.run()); - headerBar.setLeading(new Button("✨")); + + if (customWindowButtons) { + HeaderBar.setPrefButtonHeight(stage, 0); + } else { + var headerButtonHeight = new CheckBox("Set button height to bar height"); + + headerBar.heightProperty().subscribe(h -> { + if (headerButtonHeight.isSelected()) { + HeaderBar.setPrefButtonHeight(stage, h.doubleValue()); + } + }); + + headerButtonHeight.selectedProperty().subscribe(value -> { + if (value) { + HeaderBar.setPrefButtonHeight(stage, headerBar.getHeight()); + } else { + HeaderBar.setPrefButtonHeight(stage, HeaderBar.USE_DEFAULT_SIZE); + } + }); + + headerBar.setLeading(headerButtonHeight); + } var trailingNodes = new HBox(sizeComboBox); trailingNodes.setAlignment(Pos.CENTER); @@ -204,7 +222,7 @@ private Parent createSimpleHeaderBarRoot(Stage stage, boolean customWindowButton private Parent createSplitHeaderBarRoot(Stage stage, boolean customWindowButtons) { var leftHeaderBar = new HeaderBar(); leftHeaderBar.setBackground(Background.fill(Color.VIOLET)); - leftHeaderBar.setLeading(new Button("✨")); + leftHeaderBar.setLeading(new Button("\u2728")); leftHeaderBar.setCenter(new TextField() {{ setPromptText("Search..."); }}); leftHeaderBar.setTrailingSystemPadding(false); @@ -218,7 +236,7 @@ private Parent createSplitHeaderBarRoot(Stage stage, boolean customWindowButtons Runnable updateMinHeight = () -> rightHeaderBar.setMinHeight( switch (sizeComboBox.getValue().toLowerCase(Locale.ROOT)) { case "large" -> 80; - case "medium" -> 40; + case "medium" -> 50; default -> rightHeaderBar.getMinSystemHeight(); }); @@ -232,6 +250,7 @@ private Parent createSplitHeaderBarRoot(Stage stage, boolean customWindowButtons if (customWindowButtons) { trailingNodes.getChildren().addAll(createCustomWindowButtons()); + HeaderBar.setPrefButtonHeight(stage, 0); } rightHeaderBar.setTrailing(trailingNodes); @@ -259,9 +278,9 @@ private List createCustomWindowButtons() { } }); - HeaderBarBase.setHeaderButtonType(iconifyButton, HeaderButtonType.ICONIFY); - HeaderBarBase.setHeaderButtonType(maximizeButton, HeaderButtonType.MAXIMIZE); - HeaderBarBase.setHeaderButtonType(closeButton, HeaderButtonType.CLOSE); + HeaderBarBase.setButtonType(iconifyButton, HeaderButtonType.ICONIFY); + HeaderBarBase.setButtonType(maximizeButton, HeaderButtonType.MAXIMIZE); + HeaderBarBase.setButtonType(closeButton, HeaderButtonType.CLOSE); return List.of(iconifyButton, maximizeButton, closeButton); } From a04d9468f33afdf9395ed2e555979f50f92c18a5 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Thu, 13 Feb 2025 19:16:16 +0100 Subject: [PATCH 49/73] Remove HeaderBarBase --- .../com/sun/glass/ui/HeaderButtonOverlay.java | 12 +- .../main/java/com/sun/glass/ui/Window.java | 6 +- .../java/com/sun/glass/ui/mac/MacWindow.java | 4 +- .../java/com/sun/glass/ui/win/WinWindow.java | 2 +- .../com/sun/javafx/tk/HeaderAreaType.java | 2 +- .../src/main/java/javafx/scene/Scene.java | 12 +- .../java/javafx/scene/layout/HeaderBar.java | 322 ++++++-- .../javafx/scene/layout/HeaderBarBase.java | 281 ------- .../javafx/scene/layout/HeaderButtonType.java | 2 +- .../src/main/java/javafx/stage/Stage.java | 4 +- .../sun/glass/ui/HeaderButtonOverlayTest.java | 184 ++++- .../javafx/scene/layout/HeaderBarTest.java | 721 +++++++++--------- .../fx/monkey/tools/StageTesterWindow.java | 19 +- 13 files changed, 836 insertions(+), 735 deletions(-) delete mode 100644 modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/HeaderButtonOverlay.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/HeaderButtonOverlay.java index 3037ccea0c0..a4ba1c35994 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/HeaderButtonOverlay.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/HeaderButtonOverlay.java @@ -53,7 +53,7 @@ import javafx.geometry.VPos; import javafx.scene.Node; import javafx.scene.Scene; -import javafx.scene.layout.HeaderBarBase; +import javafx.scene.layout.HeaderBar; import javafx.scene.layout.HeaderButtonType; import javafx.scene.layout.Region; import javafx.scene.paint.Paint; @@ -232,7 +232,7 @@ public StyleableProperty getStyleableProperty(HeaderButtonOverlay overl * with their preferred height set to the value of {@link #buttonDefaultHeight}. */ private final DoubleProperty prefButtonHeight = new SimpleDoubleProperty( - this, "prefButtonHeight", HeaderBarBase.USE_DEFAULT_SIZE); + this, "prefButtonHeight", HeaderBar.USE_DEFAULT_SIZE); /** * Specifies the default height of header buttons. @@ -516,9 +516,11 @@ private double getButtonOffsetY(double buttonHeight) { }; } - private void ensureRegionPrefHeight(Region region, double prefHeight) { - if (region.getPrefHeight() != prefHeight) { - region.setPrefHeight(prefHeight); + private void ensureRegionPrefHeight(Region region, double height) { + var prefHeight = (StyleableDoubleProperty)region.prefHeightProperty(); + + if (prefHeight.getStyleOrigin() == null) { + prefHeight.applyStyle(null, height); } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java index ba06dc944db..4a9df6eb404 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Window.java @@ -35,7 +35,7 @@ import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleObjectProperty; -import javafx.scene.layout.HeaderBarBase; +import javafx.scene.layout.HeaderBar; import javafx.scene.layout.Region; import java.lang.annotation.Native; import java.util.Collections; @@ -301,10 +301,10 @@ protected Window(Window owner, Screen screen, int styleMask) { * visualization, but they are not required to accommodate the preferred height. *

    * Implementations should choose a sensible default height for their header button visualization if - * {@link HeaderBarBase#USE_DEFAULT_SIZE} is specified. + * {@link HeaderBar#USE_DEFAULT_SIZE} is specified. */ private final DoubleProperty prefHeaderButtonHeight = - new SimpleDoubleProperty(this, "prefHeaderButtonHeight", HeaderBarBase.USE_DEFAULT_SIZE); + new SimpleDoubleProperty(this, "prefHeaderButtonHeight", HeaderBar.USE_DEFAULT_SIZE); public final ReadOnlyDoubleProperty prefHeaderButtonHeightProperty() { return prefHeaderButtonHeight; diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java index c163ab2c7d4..49adc4c0f61 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacWindow.java @@ -32,7 +32,7 @@ import com.sun.glass.ui.View; import com.sun.glass.ui.Window; import javafx.geometry.Dimension2D; -import javafx.scene.layout.HeaderBarBase; +import javafx.scene.layout.HeaderBar; import java.nio.ByteBuffer; /** @@ -175,7 +175,7 @@ public void performTitleBarDoubleClickAction() { private native void _setWindowButtonStyle(long ptr, int toolbarStyle, boolean buttonsVisible); private void onPrefHeaderButtonHeightChanged(Number height) { - double h = height != null ? height.doubleValue() : HeaderBarBase.USE_DEFAULT_SIZE; + double h = height != null ? height.doubleValue() : HeaderBar.USE_DEFAULT_SIZE; var toolbarStyle = NSWindowToolbarStyle.ofHeight(h); _setWindowButtonStyle(getRawHandle(), toolbarStyle.style, h != 0); updateHeaderButtonMetrics(toolbarStyle, h); diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java index 40b633dec70..3a5f06bffd5 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java @@ -435,7 +435,7 @@ enum HT { View.EventHandler eventHandler = view.getEventHandler(); return switch (eventHandler != null ? eventHandler.pickHeaderArea(wx, wy) : null) { case DRAGBAR -> HT.CAPTION.value; - case MINIMIZE -> HT.MINBUTTON.value; + case ICONIFY -> HT.MINBUTTON.value; case MAXIMIZE -> HT.MAXBUTTON.value; case CLOSE -> HT.CLOSE.value; case null -> HT.CLIENT.value; diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/HeaderAreaType.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/HeaderAreaType.java index 403a7a333f1..aa36000be5c 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/HeaderAreaType.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/HeaderAreaType.java @@ -30,7 +30,7 @@ */ public enum HeaderAreaType { DRAGBAR, - MINIMIZE, + ICONIFY, MAXIMIZE, CLOSE } diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java b/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java index 449b6457014..c419c9666b1 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java @@ -78,7 +78,7 @@ import javafx.geometry.*; import javafx.scene.image.WritableImage; import javafx.scene.input.*; -import javafx.scene.layout.HeaderBarBase; +import javafx.scene.layout.HeaderBar; import javafx.scene.layout.HeaderButtonType; import javafx.scene.paint.Color; import javafx.scene.paint.Paint; @@ -3069,22 +3069,22 @@ public HeaderAreaType pickHeaderArea(double x, double y) { var pickResultChooser = new PickResultChooser(); root.pickNode(pickRay, pickResultChooser); Node intersectedNode = pickResultChooser.getIntersectedNode(); - Boolean draggable = intersectedNode instanceof HeaderBarBase ? true : null; + Boolean draggable = intersectedNode instanceof HeaderBar ? true : null; while (intersectedNode != null) { - if (intersectedNode instanceof HeaderBarBase) { + if (intersectedNode instanceof HeaderBar) { return draggable == Boolean.TRUE ? HeaderAreaType.DRAGBAR : null; } - if (HeaderBarBase.getButtonType(intersectedNode) instanceof HeaderButtonType type) { + if (HeaderBar.getButtonType(intersectedNode) instanceof HeaderButtonType type) { return switch (type) { - case ICONIFY -> HeaderAreaType.MINIMIZE; + case ICONIFY -> HeaderAreaType.ICONIFY; case MAXIMIZE -> HeaderAreaType.MAXIMIZE; case CLOSE -> HeaderAreaType.CLOSE; }; } - if (draggable == null && HeaderBarBase.isDraggable(intersectedNode) instanceof Boolean value) { + if (draggable == null && HeaderBar.isDraggable(intersectedNode) instanceof Boolean value) { draggable = value; } diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java index d16ab856b9c..85e708e0466 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java @@ -25,12 +25,21 @@ package javafx.scene.layout; +import com.sun.glass.ui.HeaderButtonMetrics; import com.sun.javafx.geom.Vec2d; +import com.sun.javafx.scene.layout.HeaderButtonBehavior; +import com.sun.javafx.stage.StageHelper; import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanPropertyBase; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectPropertyBase; +import javafx.beans.property.ReadOnlyDoubleProperty; +import javafx.beans.property.ReadOnlyDoubleWrapper; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.beans.value.ObservableValue; import javafx.css.StyleableDoubleProperty; +import javafx.geometry.Dimension2D; import javafx.geometry.HPos; import javafx.geometry.Insets; import javafx.geometry.NodeOrientation; @@ -38,9 +47,12 @@ import javafx.geometry.Pos; import javafx.geometry.VPos; import javafx.scene.Node; +import javafx.scene.Scene; import javafx.scene.input.ContextMenuEvent; +import javafx.scene.input.MouseEvent; import javafx.stage.Stage; import javafx.stage.StageStyle; +import javafx.util.Subscription; /** * A client-area header bar that is used as a replacement for the system-provided header bar in stages @@ -51,14 +63,8 @@ *

    * {@code HeaderBar} is a layout container that allows applications to place scene graph nodes in three areas: * {@link #leadingProperty() leading}, {@link #centerProperty() center}, and {@link #trailingProperty() trailing}. - * All areas can be {@code null}. - * If a child is configured to be centered in the {@code center} area, it is laid out with respect to the - * entire header bar, and not with respect to the {@code center} area. This ensures that the child will appear - * centered in the header bar regardless of leading or trailing children or the platform-specific placement of - * default window buttons. - *

    - * The default {@link #minHeightProperty() minHeight} of the {@code HeaderBar} is set to match the height of the - * platform-specific default window buttons. + * All areas can be {@code null}. The default {@link #minHeightProperty() minHeight} of the {@code HeaderBar} is + * set to match the height of the platform-specific default header buttons. * *

    Single header bar

    * Most applications should only add a single {@code HeaderBar} to the scene graph, placed at the top of the @@ -75,10 +81,16 @@ * {@link #trailingSystemPaddingProperty() trailingSystemPadding} properties can be used to remove the padding * that is not needed. * + *

    Header button height

    + * Applications can specify the preferred height for system-provided header buttons by setting the static + * {@link #setPrefButtonHeight(Stage, double)} property on the {@code Stage} associated with the header bar. + * This can be used to achieve a more cohesive visual appearance by having the system-provided header buttons + * match the height of the header bar. + * *

    Custom header buttons

    - * If more control over the header buttons is desired, applications can opt out of the default header buttons - * by setting {@link #setPrefButtonHeight(Stage, double)} to zero and provide custom header buttons instead. - * Any JavaFX control can be used as a custom header button by setting its semantic type with the + * If more control over the header buttons is desired, applications can opt out of the system-provided header + * buttons by setting {@link #setPrefButtonHeight(Stage, double)} to zero and provide custom header buttons + * instead. Any JavaFX control can be used as a custom header button by setting its semantic type with the * {@link #setButtonType(Node, HeaderButtonType)} method. * *

    System menu

    @@ -87,11 +99,11 @@ * event that is targeted at the header bar is not consumed by the application. * *

    Layout constraints

    - * All children will be resized to their preferred widths and extend the height of the {@code HeaderBar}. - * {@code HeaderBar} honors the minimum, preferred, and maximum sizes of its children. As a consequence, its - * computed minimum size is sufficient to accommodate all of its children. If a child's resizable range prevents - * it from be resized to fit within its position, it will be vertically centered relative to the available space; - * this alignment can be customized with a layout constraint. + * The {@code leading} and {@code trailing} children will be resized to their preferred widths and extend the + * height of the {@code HeaderBar}. The {@code center} child will be resized to fill the available space. + * {@code HeaderBar} honors the minimum, preferred, and maximum sizes of its children. If a child's resizable + * range prevents it from be resized to fit within its position, it will be vertically centered relative to the + * available space; this alignment can be customized with a layout constraint. *

    * An application may set constraints on individual children to customize their layout. * For each constraint, {@code HeaderBar} provides static getter and setter methods. @@ -110,6 +122,17 @@ * * * + *

    Special layout of centered child

    + * If a child is configured to be centered in the {@link #centerProperty() center} area (i.e. its {@code alignment} + * constraint is either {@code null}, {@link Pos#CENTER}, {@link Pos#TOP_CENTER}, or {@link Pos#BOTTOM_CENTER}), + * it will be centered with respect to the entire header bar, and not with respect to the {@code center} area only. + * This means that, for a header bar that extends the entire width of the {@code Stage}, the child will appear to + * be horizontally centered within the {@code Stage}. + *

    + * If a child should instead be centered with respect to the {@code center} area only, a possible solution is to + * place another layout container like {@link BorderPane} in the {@code center} area, and then center the child + * within the other layout container. + * *

    Example

    * Usually, {@code HeaderBar} is placed in a root container like {@code BorderPane} to align it * with the top of the scene: @@ -134,14 +157,113 @@ * } * }
* - * @see HeaderBarBase * @since 25 */ -public class HeaderBar extends HeaderBarBase { +public class HeaderBar extends Region { + private static final Dimension2D EMPTY = new Dimension2D(0, 0); + private static final String DRAGGABLE = "headerbar-draggable"; + private static final String BUTTON_TYPE = "headerbar-button-type"; private static final String ALIGNMENT = "headerbar-alignment"; private static final String MARGIN = "headerbar-margin"; + /** + * Specifies whether the child and its subtree is a draggable part of the {@code HeaderBar}. + *

+ * If set to a non-null value, the value will apply for the entire subtree of the child unless + * another node in the subtree specifies a different value. Setting the value to {@code null} + * will remove the flag. + * + * @param child the child node + * @param value a {@code Boolean} value indicating whether the child and its subtree is draggable, + * or {@code null} to remove the flag + */ + public static void setDraggable(Node child, Boolean value) { + Pane.setConstraint(child, DRAGGABLE, value); + } + + /** + * Returns whether the child and its subtree is a draggable part of the {@code HeaderBar}. + * + * @param child the child node + * @return a {@code Boolean} value indicating whether the child and its subtree is draggable, + * or {@code null} if not set + */ + public static Boolean isDraggable(Node child) { + return (Boolean)Pane.getConstraint(child, DRAGGABLE); + } + + /** + * Specifies the {@code HeaderButtonType} of the child, indicating its semantic use in the header bar. + *

+ * This property can be set on any {@link Node}. Specifying a header button type also provides the behavior + * associated with the button type. If the default behavior is not desired, applications can register an + * event filter on the child node that consumes the {@link MouseEvent#MOUSE_RELEASED} event. + * + * @param child the child node + * @param value the {@code HeaderButtonType}, or {@code null} + */ + public static void setButtonType(Node child, HeaderButtonType value) { + Pane.setConstraint(child, BUTTON_TYPE, value); + + if (child.getProperties().get(HeaderButtonBehavior.class) instanceof HeaderButtonBehavior behavior) { + behavior.dispose(); + } + + if (value != null) { + child.getProperties().put(HeaderButtonBehavior.class, new HeaderButtonBehavior(child, value)); + } else { + child.getProperties().remove(HeaderButtonBehavior.class); + } + } + + /** + * Returns the {@code HeaderButtonType} of the specified child. + * + * @param child the child node + * @return the {@code HeaderButtonType}, or {@code null} + */ + public static HeaderButtonType getButtonType(Node child) { + return (HeaderButtonType)Pane.getConstraint(child, BUTTON_TYPE); + } + + /** + * Sentinel value that can be used for {@link #setPrefButtonHeight(Stage, double)} to indicate that + * the platform should choose the platform-specific default button height. + */ + public static final double USE_DEFAULT_SIZE = -1; + + /** + * Specifies the preferred height of the system-provided header buttons of the specified stage. + *

+ * Any value except zero and {@link #USE_DEFAULT_SIZE} is only a hint for the platform window toolkit. + * The platform might accommodate the preferred height in various ways, such as by stretching the header + * buttons (fully or partially) to fill the preferred height, or centering the header buttons (fully or + * partially) within the preferred height. Some platforms might only accommodate the preferred height + * within platform-specific constraints, or ignore it entirely. + *

+ * Setting the preferred height to zero hides the system-provided header buttons, allowing applications to + * use custom header buttons instead (see {@link #setButtonType(Node, HeaderButtonType)}). + *

+ * The default value {@code #USE_DEFAULT_SIZE} indicates that the platform should choose the button height. + * + * @param stage the {@code Stage} + * @param height the preferred height, or 0 to hide the system-provided header buttons + */ + public static void setPrefButtonHeight(Stage stage, double height) { + StageHelper.setPrefHeaderButtonHeight(stage, height); + } + + /** + * Returns the preferred height of the system-provided header buttons of the specified stage. + * + * @param stage the {@code Stage} + * @return the preferred height of the system-provided header buttons + */ + public static double getPrefButtonHeight(Stage stage) { + return StageHelper.getPrefHeaderButtonHeight(stage); + } + /** * Sets the alignment for the child when contained in a {@code HeaderBar}. * If set, will override the header bar's default alignment for the child's position. @@ -186,6 +308,10 @@ public static Insets getMargin(Node child) { return (Insets)Pane.getConstraint(child, MARGIN); } + private Subscription subscription = Subscription.EMPTY; + private HeaderButtonMetrics currentMetrics; + private boolean currentFullScreen; + /** * Creates a new {@code HeaderBar}. */ @@ -193,7 +319,16 @@ public HeaderBar() { // Inflate the minHeight property. This is important so that we can track whether a stylesheet or // user code changes the property value before we set it to the height of the native title bar. minHeightProperty(); - minSystemHeightProperty().subscribe(this::updateMinHeight); + + ObservableValue stage = sceneProperty() + .flatMap(Scene::windowProperty) + .map(w -> w instanceof Stage s ? s : null); + + stage.flatMap(Stage::fullScreenProperty) + .orElse(false) + .subscribe(this::onFullScreenChanged); + + stage.subscribe(this::onStageChanged); } /** @@ -210,6 +345,112 @@ public HeaderBar(Node leading, Node center, Node trailing) { setTrailing(trailing); } + private void onStageChanged(Stage stage) { + subscription.unsubscribe(); + + if (stage != null) { + subscription = StageHelper.getHeaderButtonMetrics(stage).subscribe(this::onMetricsChanged); + } + } + + private void onMetricsChanged(HeaderButtonMetrics metrics) { + currentMetrics = metrics; + updateInsets(); + } + + private void onFullScreenChanged(boolean fullScreen) { + currentFullScreen = fullScreen; + updateInsets(); + } + + private void updateInsets() { + if (currentFullScreen || currentMetrics == null) { + leftSystemInset.set(EMPTY); + rightSystemInset.set(EMPTY); + minSystemHeight.set(0); + } else { + leftSystemInset.set(currentMetrics.leftInset()); + rightSystemInset.set(currentMetrics.rightInset()); + minSystemHeight.set(currentMetrics.minHeight()); + } + } + + /** + * Describes the size of the left system-reserved inset, which is an area reserved for the minimize, maximize, + * and close window buttons. If there are no window buttons on the left side of the window, the returned area + * is an empty {@code Dimension2D}. + *

+ * Note that the left system inset refers to the left side of the window, independent of layout orientation. + */ + private final ReadOnlyObjectWrapper leftSystemInset = + new ReadOnlyObjectWrapper<>(this, "leftSystemInset", EMPTY) { + @Override + protected void invalidated() { + requestLayout(); + } + }; + + public final ReadOnlyObjectProperty leftSystemInsetProperty() { + return leftSystemInset.getReadOnlyProperty(); + } + + public final Dimension2D getLeftSystemInset() { + return leftSystemInset.get(); + } + + /** + * Describes the size of the right system-reserved inset, which is an area reserved for the minimize, maximize, + * and close window buttons. If there are no window buttons on the right side of the window, the returned area + * is an empty {@code Dimension2D}. + *

+ * Note that the right system inset refers to the right side of the window, independent of layout orientation. + */ + private final ReadOnlyObjectWrapper rightSystemInset = + new ReadOnlyObjectWrapper<>(this, "rightSystemInset", EMPTY) { + @Override + protected void invalidated() { + requestLayout(); + } + }; + + public final ReadOnlyObjectProperty rightSystemInsetProperty() { + return rightSystemInset.getReadOnlyProperty(); + } + + public final Dimension2D getRightSystemInset() { + return rightSystemInset.get(); + } + + /** + * The system-provided minimum recommended height for the {@code HeaderBar}, which usually corresponds + * to the height of the default header buttons. Applications can use this value as a sensible lower limit + * for the height of the {@code HeaderBar}. + *

+ * By default, {@link #minHeightProperty() minHeight} is set to the value of {@code minSystemHeight}, + * unless {@code minHeight} is explicitly set by a stylesheet or application code. + */ + private final ReadOnlyDoubleWrapper minSystemHeight = + new ReadOnlyDoubleWrapper(this, "minSystemHeight") { + @Override + protected void invalidated() { + double height = get(); + var minHeight = (StyleableDoubleProperty)minHeightProperty(); + + // Only change minHeight if it was not set by a stylesheet or application code. + if (minHeight.getStyleOrigin() == null) { + minHeight.applyStyle(null, height); + } + } + }; + + public final ReadOnlyDoubleProperty minSystemHeightProperty() { + return minSystemHeight.getReadOnlyProperty(); + } + + public final double getMinSystemHeight() { + return minSystemHeight.get(); + } + /** * The leading area of the {@code HeaderBar}. *

@@ -493,8 +734,8 @@ protected void layoutChildren() { if (left != null && left.isManaged()) { Insets leftMargin = adjustMarginForRTL(getNodeMargin(left), rtl); double adjustedWidth = adjustWidthByMargin(insideWidth, leftMargin); - Vec2d childSize = resizeChild(left, adjustedWidth, insideHeight, leftMargin); - leftWidth = snapSpaceX(leftMargin.getLeft()) + childSize.x + snapSpaceX(leftMargin.getRight()); + double childWidth = resizeChild(left, adjustedWidth, false, insideHeight, leftMargin); + leftWidth = snapSpaceX(leftMargin.getLeft()) + childWidth + snapSpaceX(leftMargin.getRight()); Pos alignment = getAlignment(left); positionInArea( @@ -509,8 +750,8 @@ protected void layoutChildren() { if (right != null && right.isManaged()) { Insets rightMargin = adjustMarginForRTL(getNodeMargin(right), rtl); double adjustedWidth = adjustWidthByMargin(insideWidth - leftWidth, rightMargin); - Vec2d childSize = resizeChild(right, adjustedWidth, insideHeight, rightMargin); - rightWidth = snapSpaceX(rightMargin.getLeft()) + childSize.x + snapSpaceX(rightMargin.getRight()); + double childWidth = resizeChild(right, adjustedWidth, false, insideHeight, rightMargin); + rightWidth = snapSpaceX(rightMargin.getLeft()) + childWidth + snapSpaceX(rightMargin.getRight()); Pos alignment = getAlignment(right); positionInArea( @@ -524,21 +765,20 @@ protected void layoutChildren() { if (center != null && center.isManaged()) { Insets centerMargin = adjustMarginForRTL(getNodeMargin(center), rtl); - double adjustedWidth = adjustWidthByMargin(insideWidth - leftWidth - rightWidth, centerMargin); - Vec2d childSize = resizeChild(center, adjustedWidth, insideHeight, centerMargin); - double centerWidth = childSize.x; Pos alignment = getAlignment(center); if (alignment == null || alignment.getHpos() == HPos.CENTER) { - double idealX = width / 2 - centerWidth / 2; + double adjustedWidth = adjustWidthByMargin(insideWidth - leftWidth - rightWidth, centerMargin); + double childWidth = resizeChild(center, adjustedWidth, true, insideHeight, centerMargin); + double idealX = width / 2 - childWidth / 2; double minX = insideX + leftWidth + centerMargin.getLeft(); double maxX = insideX + insideWidth - rightWidth - centerMargin.getRight(); double adjustedX; if (idealX < minX) { adjustedX = minX; - } else if (idealX + centerWidth > maxX) { - adjustedX = maxX - centerWidth; + } else if (idealX + childWidth > maxX) { + adjustedX = maxX - childWidth; } else { adjustedX = idealX; } @@ -546,18 +786,17 @@ protected void layoutChildren() { positionInArea( center, adjustedX, insideY, - centerWidth, insideHeight, 0, + childWidth, insideHeight, 0, new Insets(centerMargin.getTop(), 0, centerMargin.getBottom(), 0), HPos.LEFT, alignment != null ? alignment.getVpos() : VPos.CENTER, isSnapToPixel()); } else { - positionInArea( + layoutInArea( center, insideX + leftWidth, insideY, insideWidth - leftWidth - rightWidth, insideHeight, 0, centerMargin, - alignment.getHpos(), alignment.getVpos(), - isSnapToPixel()); + alignment.getHpos(), alignment.getVpos()); } } } @@ -580,14 +819,14 @@ private boolean childHasContentBias(Node child, Orientation orientation) { return false; } - private Vec2d resizeChild(Node child, double adjustedWidth, double insideHeight, Insets margin) { + private double resizeChild(Node child, double adjustedWidth, boolean fillWidth, double insideHeight, Insets margin) { double adjustedHeight = adjustHeightByMargin(insideHeight, margin); - double childWidth = Math.min(snapSizeX(child.prefWidth(adjustedHeight)), adjustedWidth); - Vec2d size = boundedNodeSizeWithBias(child, childWidth, adjustedHeight, false, true, TEMP_VEC2D); + double childWidth = fillWidth ? adjustedWidth : Math.min(snapSizeX(child.prefWidth(adjustedHeight)), adjustedWidth); + Vec2d size = boundedNodeSizeWithBias(child, childWidth, adjustedHeight, true, true, TEMP_VEC2D); size.x = snapSizeX(size.x); size.y = snapSizeX(size.y); child.resize(size.x, size.y); - return size; + return size.x; } private double getAreaWidth(Node child, double height, boolean minimum) { @@ -617,15 +856,6 @@ private Insets getNodeMargin(Node child) { return margin != null ? margin : Insets.EMPTY; } - private void updateMinHeight() { - var minHeight = (StyleableDoubleProperty)minHeightProperty(); - - // Only change minHeight if it was not set by a stylesheet or application code. - if (minHeight.getStyleOrigin() == null) { - ((StyleableDoubleProperty)minHeightProperty()).applyStyle(null, getMinSystemHeight()); - } - } - private final class NodeProperty extends ObjectPropertyBase { private final String name; private Node value; diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java deleted file mode 100644 index f2903a15909..00000000000 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBarBase.java +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package javafx.scene.layout; - -import com.sun.glass.ui.HeaderButtonMetrics; -import com.sun.javafx.scene.layout.HeaderButtonBehavior; -import com.sun.javafx.stage.StageHelper; -import javafx.beans.property.ReadOnlyDoubleProperty; -import javafx.beans.property.ReadOnlyDoubleWrapper; -import javafx.beans.property.ReadOnlyObjectProperty; -import javafx.beans.property.ReadOnlyObjectWrapper; -import javafx.beans.value.ObservableValue; -import javafx.geometry.Dimension2D; -import javafx.scene.Node; -import javafx.scene.Scene; -import javafx.scene.input.ContextMenuEvent; -import javafx.scene.input.MouseEvent; -import javafx.stage.Stage; -import javafx.stage.StageStyle; -import javafx.util.Subscription; - -/** - * Base class for a client-area header bar that is used as a replacement for the system-provided header bar in - * stages with the {@link StageStyle#EXTENDED} style. This class is intended for application developers to use - * as a starting point for custom header bar implementations, and it enables the click-and-drag to move - * and double-click to maximize behaviors that are usually afforded by system-provided header bars. - * The entire {@code HeaderBarBase} background is draggable by default, but its content is not. Applications - * can specify draggable content nodes of the {@code HeaderBarBase} with the {@link #setDraggable} method. - *

- * Some platforms support a system menu that can be summoned by right-clicking the draggable area. - * This platform-provided menu will only be shown if the {@link ContextMenuEvent#CONTEXT_MENU_REQUESTED} - * event that is targeted at {@code HeaderBarBase} is not consumed by the application. - * - * @apiNote Most application developers should use the {@link HeaderBar} implementation instead of - * creating a custom header bar. - * @see HeaderBar - * @since 25 - */ -public abstract class HeaderBarBase extends Region { - - private static final Dimension2D EMPTY = new Dimension2D(0, 0); - private static final String DRAGGABLE = "headerbar-draggable"; - private static final String HEADER_BUTTON_TYPE = "header-button-type"; - - /** - * Specifies whether the child and its subtree is a draggable part of the {@code HeaderBar}. - *

- * If set to a non-null value, the value will apply for the entire subtree of the child unless - * another node in the subtree specifies a different value. Setting the value to {@code null} - * will remove the flag. - * - * @param child the child node - * @param value a {@code Boolean} value indicating whether the child and its subtree is draggable, - * or {@code null} to remove the flag - */ - public static void setDraggable(Node child, Boolean value) { - Pane.setConstraint(child, DRAGGABLE, value); - } - - /** - * Returns whether the child and its subtree is a draggable part of the {@code HeaderBar}. - * - * @param child the child node - * @return a {@code Boolean} value indicating whether the child and its subtree is draggable, - * or {@code null} if not set - */ - public static Boolean isDraggable(Node child) { - return (Boolean)Pane.getConstraint(child, DRAGGABLE); - } - - /** - * Specifies the {@code HeaderButtonType} of the child, indicating its semantic use in the header bar. - *

- * This property can be set on any {@link Node}. Specifying a header button type also provides the behavior - * associated with the button type. If the default behavior is not desired, applications can register an - * event filter on the child node that consumes the {@link MouseEvent#MOUSE_RELEASED} event. - * - * @param child the child node - * @param value the {@code HeaderButtonType}, or {@code null} - */ - public static void setButtonType(Node child, HeaderButtonType value) { - Pane.setConstraint(child, HEADER_BUTTON_TYPE, value); - - if (child.getProperties().get(HeaderButtonBehavior.class) instanceof HeaderButtonBehavior behavior) { - behavior.dispose(); - } - - if (value != null) { - child.getProperties().put(HeaderButtonBehavior.class, new HeaderButtonBehavior(child, value)); - } else { - child.getProperties().remove(HeaderButtonBehavior.class); - } - } - - /** - * Returns the {@code HeaderButtonType} of the specified child. - * - * @param child the child node - * @return the {@code HeaderButtonType}, or {@code null} - */ - public static HeaderButtonType getButtonType(Node child) { - return (HeaderButtonType)Pane.getConstraint(child, HEADER_BUTTON_TYPE); - } - - /** - * Sentinel value that can be used for {@link #setPrefButtonHeight(Stage, double)} to indicate that - * the platform should choose the platform-specific default button height. - */ - public static final double USE_DEFAULT_SIZE = -1; - - /** - * Specifies the preferred height of the system-provided header buttons of the specified stage. - *

- * Any value except zero and {@link #USE_DEFAULT_SIZE} is only a hint for the platform window toolkit. - * The platform might accommodate the preferred height in various ways, such as by stretching the header - * buttons (fully or partially) to fill the preferred height, or centering the header buttons (fully or - * partially) within the preferred height. Some platforms might only accommodate the preferred height - * within platform-specific constraints, or ignore it entirely. - *

- * Setting the preferred height to zero hides the system-provided header buttons, allowing applications to - * use custom header buttons instead (see {@link #setButtonType(Node, HeaderButtonType)}). - *

- * The default value {@code #USE_DEFAULT_SIZE} indicates that the platform should choose the button height. - * - * @param stage the {@code Stage} - * @param height the preferred height, or 0 to hide the system-provided header buttons - */ - public static void setPrefButtonHeight(Stage stage, double height) { - StageHelper.setPrefHeaderButtonHeight(stage, height); - } - - /** - * Returns the preferred height of the system-provided header buttons of the specified stage. - * - * @param stage the {@code Stage} - * @return the preferred height of the system-provided header buttons - */ - public static double getPrefButtonHeight(Stage stage) { - return StageHelper.getPrefHeaderButtonHeight(stage); - } - - private Subscription subscription = Subscription.EMPTY; - private HeaderButtonMetrics currentMetrics; - private boolean currentFullScreen; - - /** - * Constructor called by subclasses. - */ - protected HeaderBarBase() { - ObservableValue stage = sceneProperty() - .flatMap(Scene::windowProperty) - .map(w -> w instanceof Stage s ? s : null); - - stage.flatMap(Stage::fullScreenProperty) - .orElse(false) - .subscribe(this::onFullScreenChanged); - - stage.subscribe(this::onStageChanged); - } - - private void onStageChanged(Stage stage) { - subscription.unsubscribe(); - - if (stage != null) { - subscription = StageHelper.getHeaderButtonMetrics(stage).subscribe(this::onMetricsChanged); - } - } - - private void onMetricsChanged(HeaderButtonMetrics metrics) { - currentMetrics = metrics; - updateInsets(); - } - - private void onFullScreenChanged(boolean fullScreen) { - currentFullScreen = fullScreen; - updateInsets(); - } - - private void updateInsets() { - if (currentFullScreen || currentMetrics == null) { - leftSystemInset.set(EMPTY); - rightSystemInset.set(EMPTY); - minSystemHeight.set(0); - } else { - leftSystemInset.set(currentMetrics.leftInset()); - rightSystemInset.set(currentMetrics.rightInset()); - minSystemHeight.set(currentMetrics.minHeight()); - } - } - - /** - * Describes the size of the left system-reserved inset, which is an area reserved for the minimize, maximize, - * and close window buttons. If there are no window buttons on the left side of the window, the returned area - * is an empty {@code Dimension2D}. - *

- * Note that the left system inset refers to the left side of the window, independent of layout orientation. - */ - private final ReadOnlyObjectWrapper leftSystemInset = - new ReadOnlyObjectWrapper<>(this, "leftSystemInset", EMPTY) { - @Override - protected void invalidated() { - requestLayout(); - } - }; - - public final ReadOnlyObjectProperty leftSystemInsetProperty() { - return leftSystemInset.getReadOnlyProperty(); - } - - public final Dimension2D getLeftSystemInset() { - return leftSystemInset.get(); - } - - /** - * Describes the size of the right system-reserved inset, which is an area reserved for the minimize, maximize, - * and close window buttons. If there are no window buttons on the right side of the window, the returned area - * is an empty {@code Dimension2D}. - *

- * Note that the right system inset refers to the right side of the window, independent of layout orientation. - */ - private final ReadOnlyObjectWrapper rightSystemInset = - new ReadOnlyObjectWrapper<>(this, "rightSystemInset", EMPTY) { - @Override - protected void invalidated() { - requestLayout(); - } - }; - - public final ReadOnlyObjectProperty rightSystemInsetProperty() { - return rightSystemInset.getReadOnlyProperty(); - } - - public final Dimension2D getRightSystemInset() { - return rightSystemInset.get(); - } - - /** - * The system-provided reasonable minimum height of {@link #leftSystemInsetProperty() leftSystemInset} - * and {@link #rightSystemInsetProperty() rightSystemInset}. This is a platform-dependent value that - * a {@code HeaderBarBase} implementation can use to choose a reasonable minimum height for the header - * bar area. - */ - private final ReadOnlyDoubleWrapper minSystemHeight = - new ReadOnlyDoubleWrapper(this, "minSystemHeight") { - @Override - protected void invalidated() { - requestLayout(); - } - }; - - public final ReadOnlyDoubleProperty minSystemHeightProperty() { - return minSystemHeight.getReadOnlyProperty(); - } - - public final double getMinSystemHeight() { - return minSystemHeight.get(); - } -} diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderButtonType.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderButtonType.java index 40ff0d5f0f7..84518ae1654 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderButtonType.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderButtonType.java @@ -34,7 +34,7 @@ * will summon snap layouts. * * @since 25 - * @see HeaderBarBase#setButtonType(Node, HeaderButtonType) + * @see HeaderBar#setButtonType(Node, HeaderButtonType) */ public enum HeaderButtonType { diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java b/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java index 9ff457ca5c7..ca1d7dfa7f1 100644 --- a/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java +++ b/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java @@ -39,6 +39,7 @@ import javafx.scene.Scene; import javafx.scene.image.Image; import javafx.scene.input.KeyCombination; +import javafx.scene.layout.HeaderBar; import com.sun.glass.ui.HeaderButtonMetrics; import com.sun.javafx.collections.VetoableListDecorator; @@ -56,7 +57,6 @@ import javafx.beans.property.ReadOnlyBooleanWrapper; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableValue; -import javafx.scene.layout.HeaderBarBase; /** * The JavaFX {@code Stage} class is the top level JavaFX container. @@ -1288,7 +1288,7 @@ private ObjectProperty headerButtonMetricsProperty() { return headerButtonMetrics; } - private double prefHeaderButtonHeight = HeaderBarBase.USE_DEFAULT_SIZE; + private double prefHeaderButtonHeight = HeaderBar.USE_DEFAULT_SIZE; private double getPrefHeaderButtonHeight() { return prefHeaderButtonHeight; diff --git a/modules/javafx.graphics/src/test/java/test/com/sun/glass/ui/HeaderButtonOverlayTest.java b/modules/javafx.graphics/src/test/java/test/com/sun/glass/ui/HeaderButtonOverlayTest.java index 0b8225e7f3e..8ed4fb4571d 100644 --- a/modules/javafx.graphics/src/test/java/test/com/sun/glass/ui/HeaderButtonOverlayTest.java +++ b/modules/javafx.graphics/src/test/java/test/com/sun/glass/ui/HeaderButtonOverlayTest.java @@ -42,6 +42,7 @@ import static org.junit.jupiter.api.Assertions.*; +@SuppressWarnings("unused") public class HeaderButtonOverlayTest { private static final Dimension2D EMPTY = new Dimension2D(0, 0); @@ -50,9 +51,65 @@ public class HeaderButtonOverlayTest { * Asserts that the buttons are laid out on the right side of the control (left-to-right orientation). */ @Test - void rightPlacement() { + void rightPlacement_stretchAlignment() { var overlay = new HeaderButtonOverlay(getStylesheet(""" - .header-button-container { -fx-button-placement: right; } + .header-button-container { -fx-button-placement: right; + -fx-button-default-height: 20; + -fx-button-vertical-alignment: stretch; } + .header-button { -fx-pref-width: 20; } + """), false, false); + + var unused = new Scene(overlay); + var children = overlay.getChildrenUnmodifiable(); + overlay.resize(200, 100); + overlay.applyCss(); + overlay.layout(); + + assertSize(overlay, 200, 100); + assertLayoutBounds(children.get(0), 140, 0, 20, 20); + assertLayoutBounds(children.get(1), 160, 0, 20, 20); + assertLayoutBounds(children.get(2), 180, 0, 20, 20); + assertEquals(EMPTY, overlay.metricsProperty().get().leftInset()); + assertEquals(new Dimension2D(60, 20), overlay.metricsProperty().get().rightInset()); + } + + /** + * Asserts that the buttons are laid out on the right side of the control (right-to-left orientation). + */ + @Test + void rightPlacement_stretchAlignment_rightToLeft() { + var overlay = new HeaderButtonOverlay(getStylesheet(""" + .header-button-container { -fx-button-placement: right; + -fx-button-default-height: 20; + -fx-button-vertical-alignment: stretch; } + .header-button { -fx-pref-width: 20; } + """), false, true); + + var unused = new Scene(overlay); + var children = overlay.getChildrenUnmodifiable(); + overlay.setNodeOrientation(NodeOrientation.RIGHT_TO_LEFT); + overlay.resize(200, 100); + overlay.applyCss(); + overlay.layout(); + + assertSize(overlay, 200, 100); + assertLayoutBounds(children.get(0), 40, 0, 20, 20); + assertLayoutBounds(children.get(1), 20, 0, 20, 20); + assertLayoutBounds(children.get(2), 0, 0, 20, 20); + assertEquals(new Dimension2D(60, 20), overlay.metricsProperty().get().leftInset()); + assertEquals(EMPTY, overlay.metricsProperty().get().rightInset()); + } + + /** + * Asserts that the buttons are laid out on the right side of the control (left-to-right orientation) + * with center alignment (including offsets caused by center alignment). + */ + @Test + void rightPlacement_centerAlignment() { + var overlay = new HeaderButtonOverlay(getStylesheet(""" + .header-button-container { -fx-button-placement: right; + -fx-button-default-height: 20; + -fx-button-vertical-alignment: center; } .header-button { -fx-pref-width: 20; -fx-pref-height: 10; } """), false, false); @@ -63,34 +120,38 @@ void rightPlacement() { overlay.layout(); assertSize(overlay, 200, 100); - assertLayoutBounds(children.get(0), 140, 0, 20, 10); - assertLayoutBounds(children.get(1), 160, 0, 20, 10); - assertLayoutBounds(children.get(2), 180, 0, 20, 10); + assertLayoutBounds(children.get(0), 135, 5, 20, 10); + assertLayoutBounds(children.get(1), 155, 5, 20, 10); + assertLayoutBounds(children.get(2), 175, 5, 20, 10); assertEquals(EMPTY, overlay.metricsProperty().get().leftInset()); - assertEquals(new Dimension2D(60, 10), overlay.metricsProperty().get().rightInset()); + assertEquals(new Dimension2D(70, 20), overlay.metricsProperty().get().rightInset()); } /** - * Asserts that the buttons are laid out on the left side of the control (right-to-left orientation). + * Asserts that the buttons are laid out on the left side of the control (right-to-left orientation) + * with center alignment (including offsets caused by center alignment). */ @Test - void rightPlacement_rightToLeft() { + void rightPlacement_centerAlignment_rightToLeft() { var overlay = new HeaderButtonOverlay(getStylesheet(""" - .header-button-container { -fx-button-placement: right; } + .header-button-container { -fx-button-placement: right; + -fx-button-default-height: 20; + -fx-button-vertical-alignment: center; } .header-button { -fx-pref-width: 20; -fx-pref-height: 10; } """), false, true); var unused = new Scene(overlay); var children = overlay.getChildrenUnmodifiable(); + overlay.setNodeOrientation(NodeOrientation.RIGHT_TO_LEFT); overlay.resize(200, 100); overlay.applyCss(); overlay.layout(); assertSize(overlay, 200, 100); - assertLayoutBounds(children.get(0), 40, 0, 20, 10); - assertLayoutBounds(children.get(1), 20, 0, 20, 10); - assertLayoutBounds(children.get(2), 0, 0, 20, 10); - assertEquals(new Dimension2D(60, 10), overlay.metricsProperty().get().leftInset()); + assertLayoutBounds(children.get(0), 45, 5, 20, 10); + assertLayoutBounds(children.get(1), 25, 5, 20, 10); + assertLayoutBounds(children.get(2), 5, 5, 20, 10); + assertEquals(new Dimension2D(70, 20), overlay.metricsProperty().get().leftInset()); assertEquals(EMPTY, overlay.metricsProperty().get().rightInset()); } @@ -98,9 +159,65 @@ void rightPlacement_rightToLeft() { * Asserts that the buttons are laid out on the left side of the control (left-to-right orientation). */ @Test - void leftPlacement() { + void leftPlacement_stretchAlignment() { var overlay = new HeaderButtonOverlay(getStylesheet(""" - .header-button-container { -fx-button-placement: left; } + .header-button-container { -fx-button-placement: left; + -fx-button-default-height: 20; + -fx-button-vertical-alignment: stretch; } + .header-button { -fx-pref-width: 20; } + """), false, false); + + var unused = new Scene(overlay); + var children = overlay.getChildrenUnmodifiable(); + overlay.resize(200, 100); + overlay.applyCss(); + overlay.layout(); + + assertSize(overlay, 200, 100); + assertLayoutBounds(children.get(0), 0, 0, 20, 20); + assertLayoutBounds(children.get(1), 20, 0, 20, 20); + assertLayoutBounds(children.get(2), 40, 0, 20, 20); + assertEquals(new Dimension2D(60, 20), overlay.metricsProperty().get().leftInset()); + assertEquals(EMPTY, overlay.metricsProperty().get().rightInset()); + } + + /** + * Asserts that the buttons are laid out on the left side of the control (right-to-left orientation). + */ + @Test + void leftPlacement_stretchAlignment_rightToLeft() { + var overlay = new HeaderButtonOverlay(getStylesheet(""" + .header-button-container { -fx-button-placement: left; + -fx-button-default-height: 20; + -fx-button-vertical-alignment: stretch; } + .header-button { -fx-pref-width: 20; } + """), false, true); + + var unused = new Scene(overlay); + var children = overlay.getChildrenUnmodifiable(); + overlay.setNodeOrientation(NodeOrientation.RIGHT_TO_LEFT); + overlay.resize(200, 100); + overlay.applyCss(); + overlay.layout(); + + assertSize(overlay, 200, 100); + assertLayoutBounds(children.get(0), 180, 0, 20, 20); + assertLayoutBounds(children.get(1), 160, 0, 20, 20); + assertLayoutBounds(children.get(2), 140, 0, 20, 20); + assertEquals(EMPTY, overlay.metricsProperty().get().leftInset()); + assertEquals(new Dimension2D(60, 20), overlay.metricsProperty().get().rightInset()); + } + + /** + * Asserts that the buttons are laid out on the left side of the control (left-to-right orientation) + * with center alignment (including offsets caused by center alignment). + */ + @Test + void leftPlacement_centerAlignment() { + var overlay = new HeaderButtonOverlay(getStylesheet(""" + .header-button-container { -fx-button-placement: left; + -fx-button-default-height: 20; + -fx-button-vertical-alignment: center; } .header-button { -fx-pref-width: 20; -fx-pref-height: 10; } """), false, false); @@ -111,20 +228,23 @@ void leftPlacement() { overlay.layout(); assertSize(overlay, 200, 100); - assertLayoutBounds(children.get(0), 0, 0, 20, 10); - assertLayoutBounds(children.get(1), 20, 0, 20, 10); - assertLayoutBounds(children.get(2), 40, 0, 20, 10); - assertEquals(new Dimension2D(60, 10), overlay.metricsProperty().get().leftInset()); + assertLayoutBounds(children.get(0), 5, 5, 20, 10); + assertLayoutBounds(children.get(1), 25, 5, 20, 10); + assertLayoutBounds(children.get(2), 45, 5, 20, 10); + assertEquals(new Dimension2D(70, 20), overlay.metricsProperty().get().leftInset()); assertEquals(EMPTY, overlay.metricsProperty().get().rightInset()); } /** - * Asserts that the buttons are laid out on the right side of the control (right-to-left orientation). + * Asserts that the buttons are laid out on the left side of the control (right-to-left orientation) + * with center alignment (including offsets caused by center alignment). */ @Test - void leftPlacement_rightToLeft() { + void leftPlacement_centerAlignment_rightToLeft() { var overlay = new HeaderButtonOverlay(getStylesheet(""" - .header-button-container { -fx-button-placement: left; } + .header-button-container { -fx-button-placement: left; + -fx-button-default-height: 20; + -fx-button-vertical-alignment: center; } .header-button { -fx-pref-width: 20; -fx-pref-height: 10; } """), false, true); @@ -136,11 +256,11 @@ void leftPlacement_rightToLeft() { overlay.layout(); assertSize(overlay, 200, 100); - assertLayoutBounds(children.get(0), 180, 0, 20, 10); - assertLayoutBounds(children.get(1), 160, 0, 20, 10); - assertLayoutBounds(children.get(2), 140, 0, 20, 10); + assertLayoutBounds(children.get(0), 175, 5, 20, 10); + assertLayoutBounds(children.get(1), 155, 5, 20, 10); + assertLayoutBounds(children.get(2), 135, 5, 20, 10); assertEquals(EMPTY, overlay.metricsProperty().get().leftInset()); - assertEquals(new Dimension2D(60, 10), overlay.metricsProperty().get().rightInset()); + assertEquals(new Dimension2D(70, 20), overlay.metricsProperty().get().rightInset()); } /** @@ -149,6 +269,7 @@ void leftPlacement_rightToLeft() { @Test void customButtonOrder() { var overlay = new HeaderButtonOverlay(getStylesheet(""" + .header-button-container { -fx-button-vertical-alignment: stretch; } .header-button { -fx-pref-width: 20; -fx-pref-height: 10; } .iconify-button { -fx-button-order: 5; } .maximize-button { -fx-button-order: 1; } @@ -175,6 +296,7 @@ void customButtonOrder() { @Test void customButtonOrder_rightToLeft() { var overlay = new HeaderButtonOverlay(getStylesheet(""" + .header-button-container { -fx-button-vertical-alignment: stretch; } .header-button { -fx-pref-width: 20; -fx-pref-height: 10; } .iconify-button { -fx-button-order: 5; } .maximize-button { -fx-button-order: 1; } @@ -213,7 +335,9 @@ void utilityDecorationIsOnlyCloseButton() { @Test void disallowRightToLeft() { var overlay = new HeaderButtonOverlay(getStylesheet(""" - .header-button-container { -fx-button-placement: right; -fx-allow-rtl: false; } + .header-button-container { -fx-button-placement: right; + -fx-button-vertical-alignment: stretch; + -fx-allow-rtl: false; } .header-button { -fx-pref-width: 20; -fx-pref-height: 10; } """), false, true); @@ -243,13 +367,13 @@ void activePseudoClassCorrespondsToStageFocusedProperty() { stage.show(); assertTrue(stage.isFocused()); - assertTrue(overlay.getChildrenUnmodifiable().get(0).getPseudoClassStates().stream().anyMatch( + assertTrue(overlay.getChildrenUnmodifiable().getFirst().getPseudoClassStates().stream().anyMatch( pc -> pc.getPseudoClassName().equals("active"))); ReflectionUtils.invokeMethod(stage, "setFocused", new Class[] { boolean.class }, false); assertFalse(stage.isFocused()); - assertTrue(overlay.getChildrenUnmodifiable().get(0).getPseudoClassStates().stream().noneMatch( + assertTrue(overlay.getChildrenUnmodifiable().getFirst().getPseudoClassStates().stream().noneMatch( pc -> pc.getPseudoClassName().equals("active"))); } @@ -325,7 +449,7 @@ void darkStyleClassIsPresentWhenSceneFillIsDark() { @Test void pickButtonAtCoordinates() { var overlay = new HeaderButtonOverlay(getStylesheet(""" - .header-button-container { -fx-button-placement: right; } + .header-button-container { -fx-button-placement: right; -fx-button-vertical-alignment: stretch; } .header-button { -fx-pref-width: 20; -fx-pref-height: 10; } """), false, false); diff --git a/modules/javafx.graphics/src/test/java/test/javafx/scene/layout/HeaderBarTest.java b/modules/javafx.graphics/src/test/java/test/javafx/scene/layout/HeaderBarTest.java index fb3f6670248..52a71684051 100644 --- a/modules/javafx.graphics/src/test/java/test/javafx/scene/layout/HeaderBarTest.java +++ b/modules/javafx.graphics/src/test/java/test/javafx/scene/layout/HeaderBarTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,7 @@ package test.javafx.scene.layout; +import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; import javafx.geometry.Dimension2D; import javafx.geometry.Insets; @@ -33,6 +34,7 @@ import javafx.scene.layout.HeaderBar; import javafx.scene.shape.Rectangle; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -56,353 +58,378 @@ void emptyHeaderBar() { assertNull(headerBar.getTrailing()); } - @ParameterizedTest - @CsvSource({ - "TOP_LEFT, 10, 10, 100, 80", - "TOP_CENTER, 10, 10, 100, 80", - "TOP_RIGHT, 10, 10, 100, 80", - "CENTER_LEFT, 10, 10, 100, 80", - "CENTER, 10, 10, 100, 80", - "CENTER_RIGHT, 10, 10, 100, 80", - "BOTTOM_LEFT, 10, 10, 100, 80", - "BOTTOM_CENTER, 10, 10, 100, 80", - "BOTTOM_RIGHT, 10, 10, 100, 80" - }) - void alignmentOfLeadingChildOnly_resizable(Pos pos, double x, double y, double width, double height) { - var content = new MockResizable(100, 50); - HeaderBar.setAlignment(content, pos); - HeaderBar.setMargin(content, new Insets(10)); - headerBar.setLeading(content); - headerBar.resize(1000, 100); - headerBar.layout(); - - assertBounds(x, y, width, height, content); - } - - @ParameterizedTest - @CsvSource({ - "TOP_LEFT, 10, 10, 100, 50", - "TOP_CENTER, 10, 10, 100, 50", - "TOP_RIGHT, 10, 10, 100, 50", - "CENTER_LEFT, 10, 25, 100, 50", - "CENTER, 10, 25, 100, 50", - "CENTER_RIGHT, 10, 25, 100, 50", - "BOTTOM_LEFT, 10, 40, 100, 50", - "BOTTOM_CENTER, 10, 40, 100, 50", - "BOTTOM_RIGHT, 10, 40, 100, 50" - }) - void alignmentOfLeadingChildOnly_notResizable(Pos pos, double x, double y, double width, double height) { - var content = new Rectangle(100, 50); - HeaderBar.setAlignment(content, pos); - HeaderBar.setMargin(content, new Insets(10)); - headerBar.setLeading(content); - headerBar.resize(1000, 100); - headerBar.layout(); - - assertBounds(x, y, width, height, content); - } - - @ParameterizedTest - @CsvSource({ - "TOP_LEFT, 890, 10, 100, 80", - "TOP_CENTER, 890, 10, 100, 80", - "TOP_RIGHT, 890, 10, 100, 80", - "CENTER_LEFT, 890, 10, 100, 80", - "CENTER, 890, 10, 100, 80", - "CENTER_RIGHT, 890, 10, 100, 80", - "BOTTOM_LEFT, 890, 10, 100, 80", - "BOTTOM_CENTER, 890, 10, 100, 80", - "BOTTOM_RIGHT, 890, 10, 100, 80" - }) - void alignmentOfTrailingChildOnly_resizable(Pos pos, double x, double y, double width, double height) { - var content = new MockResizable(100, 50); - HeaderBar.setAlignment(content, pos); - HeaderBar.setMargin(content, new Insets(10)); - headerBar.setTrailing(content); - headerBar.resize(1000, 100); - headerBar.layout(); - - assertBounds(x, y, width, height, content); - } - - @ParameterizedTest - @CsvSource({ - "TOP_LEFT, 890, 10, 100, 50", - "TOP_CENTER, 890, 10, 100, 50", - "TOP_RIGHT, 890, 10, 100, 50", - "CENTER_LEFT, 890, 25, 100, 50", - "CENTER, 890, 25, 100, 50", - "CENTER_RIGHT, 890, 25, 100, 50", - "BOTTOM_LEFT, 890, 40, 100, 50", - "BOTTOM_CENTER, 890, 40, 100, 50", - "BOTTOM_RIGHT, 890, 40, 100, 50" - }) - void alignmentOfTrailingChildOnly_notResizable(Pos pos, double x, double y, double width, double height) { - var content = new Rectangle(100, 50); - HeaderBar.setAlignment(content, pos); - HeaderBar.setMargin(content, new Insets(10)); - headerBar.setTrailing(content); - headerBar.resize(1000, 100); - headerBar.layout(); - - assertBounds(x, y, width, height, content); - } - - @ParameterizedTest - @CsvSource({ - "TOP_LEFT, 10, 10, 100, 80", - "TOP_CENTER, 450, 10, 100, 80", - "TOP_RIGHT, 890, 10, 100, 80", - "CENTER_LEFT, 10, 10, 100, 80", - "CENTER, 450, 10, 100, 80", - "CENTER_RIGHT, 890, 10, 100, 80", - "BOTTOM_LEFT, 10, 10, 100, 80", - "BOTTOM_CENTER, 450, 10, 100, 80", - "BOTTOM_RIGHT, 890, 10, 100, 80" - }) - void alignmentOfCenterChildOnly_resizable(Pos pos, double x, double y, double width, double height) { - var content = new MockResizable(100, 50); - HeaderBar.setAlignment(content, pos); - HeaderBar.setMargin(content, new Insets(10)); - headerBar.setCenter(content); - headerBar.resize(1000, 100); - headerBar.layout(); - - assertBounds(x, y, width, height, content); - } - - @ParameterizedTest - @CsvSource({ - "TOP_LEFT, 10, 10, 100, 50", - "TOP_CENTER, 450, 10, 100, 50", - "TOP_RIGHT, 890, 10, 100, 50", - "CENTER_LEFT, 10, 25, 100, 50", - "CENTER, 450, 25, 100, 50", - "CENTER_RIGHT, 890, 25, 100, 50", - "BOTTOM_LEFT, 10, 40, 100, 50", - "BOTTOM_CENTER, 450, 40, 100, 50", - "BOTTOM_RIGHT, 890, 40, 100, 50" - }) - void alignmentOfCenterChildOnly_notResizable(Pos pos, double x, double y, double width, double height) { - var content = new Rectangle(100, 50); - HeaderBar.setAlignment(content, pos); - HeaderBar.setMargin(content, new Insets(10)); - headerBar.setCenter(content); - headerBar.resize(1000, 100); - headerBar.layout(); - - assertBounds(x, y, width, height, content); - } - - @ParameterizedTest - @CsvSource({ - "TOP_LEFT, 60, 10, 100, 80", - "TOP_CENTER, 450, 10, 100, 80", - "TOP_RIGHT, 740, 10, 100, 80", - "CENTER_LEFT, 60, 10, 100, 80", - "CENTER, 450, 10, 100, 80", - "CENTER_RIGHT, 740, 10, 100, 80", - "BOTTOM_LEFT, 60, 10, 100, 80", - "BOTTOM_CENTER, 450, 10, 100, 80", - "BOTTOM_RIGHT, 740, 10, 100, 80" - }) - void alignmentOfCenterChild_resizable_withNonEmptyLeadingAndTrailingChild( - Pos pos, double x, double y, double width, double height) { - var leading = new MockResizable(50, 50); - var center = new MockResizable(100, 50); - var trailing = new MockResizable(150, 50); - HeaderBar.setAlignment(center, pos); - HeaderBar.setMargin(center, new Insets(10)); - headerBar.setLeading(leading); - headerBar.setCenter(center); - headerBar.setTrailing(trailing); - headerBar.resize(1000, 100); - headerBar.layout(); - - assertBounds(x, y, width, height, center); - } - - @ParameterizedTest - @CsvSource({ - "TOP_LEFT, 60, 10, 100, 50", - "TOP_CENTER, 450, 10, 100, 50", - "TOP_RIGHT, 740, 10, 100, 50", - "CENTER_LEFT, 60, 25, 100, 50", - "CENTER, 450, 25, 100, 50", - "CENTER_RIGHT, 740, 25, 100, 50", - "BOTTOM_LEFT, 60, 40, 100, 50", - "BOTTOM_CENTER, 450, 40, 100, 50", - "BOTTOM_RIGHT, 740, 40, 100, 50" - }) - void alignmentOfCenterChild_notResizable_withNonEmptyLeadingAndTrailingChild( - Pos pos, double x, double y, double width, double height) { - var leading = new Rectangle(50, 50); - var center = new Rectangle(100, 50); - var trailing = new Rectangle(150, 50); - HeaderBar.setAlignment(center, pos); - HeaderBar.setMargin(center, new Insets(10)); - headerBar.setLeading(leading); - headerBar.setCenter(center); - headerBar.setTrailing(trailing); - headerBar.resize(1000, 100); - headerBar.layout(); - - assertBounds(x, y, width, height, center); - } - - @ParameterizedTest - @CsvSource({ - "TOP_LEFT, 160, 10, 100, 80", - "TOP_CENTER, 450, 10, 100, 80", - "TOP_RIGHT, 740, 10, 100, 80", - "CENTER_LEFT, 160, 10, 100, 80", - "CENTER, 450, 10, 100, 80", - "CENTER_RIGHT, 740, 10, 100, 80", - "BOTTOM_LEFT, 160, 10, 100, 80", - "BOTTOM_CENTER, 450, 10, 100, 80", - "BOTTOM_RIGHT, 740, 10, 100, 80" - }) - void alignmentOfCenterChild_withLeftSystemInset(Pos pos, double x, double y, double width, double height) { - ObjectProperty leftSystemInset = ReflectionUtils.getFieldValue(headerBar, "leftSystemInset"); - leftSystemInset.set(new Dimension2D(100, 100)); - var leading = new MockResizable(50, 50); - var center = new MockResizable(100, 50); - var trailing = new MockResizable(150, 50); - HeaderBar.setAlignment(center, pos); - HeaderBar.setMargin(center, new Insets(10)); - headerBar.setLeading(leading); - headerBar.setCenter(center); - headerBar.setTrailing(trailing); - headerBar.resize(1000, 100); - headerBar.layout(); - - assertBounds(x, y, width, height, center); - } - - @ParameterizedTest - @CsvSource({ - "TOP_LEFT, 60, 10, 100, 80", - "TOP_CENTER, 450, 10, 100, 80", - "TOP_RIGHT, 640, 10, 100, 80", - "CENTER_LEFT, 60, 10, 100, 80", - "CENTER, 450, 10, 100, 80", - "CENTER_RIGHT, 640, 10, 100, 80", - "BOTTOM_LEFT, 60, 10, 100, 80", - "BOTTOM_CENTER, 450, 10, 100, 80", - "BOTTOM_RIGHT, 640, 10, 100, 80" - }) - void alignmentOfCenterChild_withRightSystemInset(Pos pos, double x, double y, double width, double height) { - ObjectProperty rightSystemInset = ReflectionUtils.getFieldValue(headerBar, "rightSystemInset"); - rightSystemInset.set(new Dimension2D(100, 100)); - var leading = new MockResizable(50, 50); - var center = new MockResizable(100, 50); - var trailing = new MockResizable(150, 50); - HeaderBar.setAlignment(center, pos); - HeaderBar.setMargin(center, new Insets(10)); - headerBar.setLeading(leading); - headerBar.setCenter(center); - headerBar.setTrailing(trailing); - headerBar.resize(1000, 100); - headerBar.layout(); - - assertBounds(x, y, width, height, center); - } - - @ParameterizedTest - @CsvSource({ - "TOP_CENTER, 260, 10, 80, 80", - "CENTER, 260, 10, 80, 80", - "BOTTOM_CENTER, 260, 10, 80, 80" - }) - void alignmentOfCenterChild_withLeftSystemInset_andOffsetCausedByInsufficientHorizontalSpace( - Pos pos, double x, double y, double width, double height) { - ObjectProperty leftSystemInset = ReflectionUtils.getFieldValue(headerBar, "leftSystemInset"); - leftSystemInset.set(new Dimension2D(200, 100)); - var leading = new MockResizable(50, 50); - var center = new MockResizable(100, 50); - var trailing = new MockResizable(150, 50); - HeaderBar.setAlignment(center, pos); - HeaderBar.setMargin(center, new Insets(10)); - headerBar.setLeading(leading); - headerBar.setCenter(center); - headerBar.setTrailing(trailing); - headerBar.resize(500, 100); - headerBar.layout(); - - assertBounds(x, y, width, height, center); - } - - @ParameterizedTest - @CsvSource({ - "TOP_CENTER, 60, 10, 80, 80", - "CENTER, 60, 10, 80, 80", - "BOTTOM_CENTER, 60, 10, 80, 80" - }) - void alignmentOfCenterChild_withRightSystemInset_andOffsetCausedByInsufficientHorizontalSpace( - Pos pos, double x, double y, double width, double height) { - ObjectProperty rightSystemInset = ReflectionUtils.getFieldValue(headerBar, "rightSystemInset"); - rightSystemInset.set(new Dimension2D(200, 100)); - var leading = new MockResizable(50, 50); - var center = new MockResizable(100, 50); - var trailing = new MockResizable(150, 50); - HeaderBar.setAlignment(center, pos); - HeaderBar.setMargin(center, new Insets(10)); - headerBar.setLeading(leading); - headerBar.setCenter(center); - headerBar.setTrailing(trailing); - headerBar.resize(500, 100); - headerBar.layout(); - - assertBounds(x, y, width, height, center); - } - - @ParameterizedTest - @CsvSource({ - "TOP_LEFT, 10, 10, 50, 50", - "CENTER, 10, 25, 50, 50", - "BOTTOM_LEFT, 10, 40, 50, 50" - }) - void alignmentOfLeadingChild_notResizable_withoutReservedArea( - Pos pos, double x, double y, double width, double height) { - ObjectProperty leftSystemInset = ReflectionUtils.getFieldValue(headerBar, "leftSystemInset"); - leftSystemInset.set(new Dimension2D(100, 100)); - var leading = new Rectangle(50, 50); - HeaderBar.setAlignment(leading, pos); - HeaderBar.setMargin(leading, new Insets(10)); - headerBar.setLeadingSystemPadding(false); - headerBar.setLeading(leading); - headerBar.resize(1000, 100); - headerBar.layout(); - - assertBounds(x, y, width, height, leading); - } - - @ParameterizedTest - @CsvSource({ - "TOP_RIGHT, 940, 10, 50, 50", - "CENTER, 940, 25, 50, 50", - "BOTTOM_RIGHT, 940, 40, 50, 50" - }) - void alignmentOfTrailingChild_notResizable_withoutReservedArea( - Pos pos, double x, double y, double width, double height) { - ObjectProperty rightSystemInset = ReflectionUtils.getFieldValue(headerBar, "rightSystemInset"); - rightSystemInset.set(new Dimension2D(100, 100)); - var trailing = new Rectangle(50, 50); - HeaderBar.setAlignment(trailing, pos); - HeaderBar.setMargin(trailing, new Insets(10)); - headerBar.setTrailingSystemPadding(false); - headerBar.setTrailing(trailing); - headerBar.resize(1000, 100); - headerBar.layout(); - - assertBounds(x, y, width, height, trailing); + @Test + void minHeight_correspondsToMinSystemHeight_ifNotSetByUser() { + DoubleProperty minSystemHeight = ReflectionUtils.getFieldValue(headerBar, "minSystemHeight"); + minSystemHeight.set(100); + assertEquals(100, headerBar.minHeight(-1)); + + headerBar.setMinHeight(50); + minSystemHeight.set(200); + assertEquals(50, headerBar.minHeight(-1)); } - private void assertBounds(double x, double y, double width, double height, Node node) { - var bounds = node.getLayoutBounds(); - assertEquals(x, node.getLayoutX()); - assertEquals(y, node.getLayoutY()); - assertEquals(width, bounds.getWidth()); - assertEquals(height, bounds.getHeight()); + @Nested + class LayoutTest { + @ParameterizedTest + @CsvSource({ + "TOP_LEFT, 10, 10, 100, 80", + "TOP_CENTER, 10, 10, 100, 80", + "TOP_RIGHT, 10, 10, 100, 80", + "CENTER_LEFT, 10, 10, 100, 80", + "CENTER, 10, 10, 100, 80", + "CENTER_RIGHT, 10, 10, 100, 80", + "BOTTOM_LEFT, 10, 10, 100, 80", + "BOTTOM_CENTER, 10, 10, 100, 80", + "BOTTOM_RIGHT, 10, 10, 100, 80" + }) + void alignmentOfLeadingChildOnly_resizable(Pos pos, double x, double y, double width, double height) { + var content = new MockResizable(100, 50); + HeaderBar.setAlignment(content, pos); + HeaderBar.setMargin(content, new Insets(10)); + headerBar.setLeading(content); + headerBar.resize(1000, 100); + headerBar.layout(); + + assertBounds(x, y, width, height, content); + } + + @ParameterizedTest + @CsvSource({ + "TOP_LEFT, 10, 10, 100, 50", + "TOP_CENTER, 10, 10, 100, 50", + "TOP_RIGHT, 10, 10, 100, 50", + "CENTER_LEFT, 10, 25, 100, 50", + "CENTER, 10, 25, 100, 50", + "CENTER_RIGHT, 10, 25, 100, 50", + "BOTTOM_LEFT, 10, 40, 100, 50", + "BOTTOM_CENTER, 10, 40, 100, 50", + "BOTTOM_RIGHT, 10, 40, 100, 50" + }) + void alignmentOfLeadingChildOnly_notResizable(Pos pos, double x, double y, double width, double height) { + var content = new Rectangle(100, 50); + HeaderBar.setAlignment(content, pos); + HeaderBar.setMargin(content, new Insets(10)); + headerBar.setLeading(content); + headerBar.resize(1000, 100); + headerBar.layout(); + + assertBounds(x, y, width, height, content); + } + + @ParameterizedTest + @CsvSource({ + "TOP_LEFT, 890, 10, 100, 80", + "TOP_CENTER, 890, 10, 100, 80", + "TOP_RIGHT, 890, 10, 100, 80", + "CENTER_LEFT, 890, 10, 100, 80", + "CENTER, 890, 10, 100, 80", + "CENTER_RIGHT, 890, 10, 100, 80", + "BOTTOM_LEFT, 890, 10, 100, 80", + "BOTTOM_CENTER, 890, 10, 100, 80", + "BOTTOM_RIGHT, 890, 10, 100, 80" + }) + void alignmentOfTrailingChildOnly_resizable(Pos pos, double x, double y, double width, double height) { + var content = new MockResizable(100, 50); + HeaderBar.setAlignment(content, pos); + HeaderBar.setMargin(content, new Insets(10)); + headerBar.setTrailing(content); + headerBar.resize(1000, 100); + headerBar.layout(); + + assertBounds(x, y, width, height, content); + } + + @ParameterizedTest + @CsvSource({ + "TOP_LEFT, 890, 10, 100, 50", + "TOP_CENTER, 890, 10, 100, 50", + "TOP_RIGHT, 890, 10, 100, 50", + "CENTER_LEFT, 890, 25, 100, 50", + "CENTER, 890, 25, 100, 50", + "CENTER_RIGHT, 890, 25, 100, 50", + "BOTTOM_LEFT, 890, 40, 100, 50", + "BOTTOM_CENTER, 890, 40, 100, 50", + "BOTTOM_RIGHT, 890, 40, 100, 50" + }) + void alignmentOfTrailingChildOnly_notResizable(Pos pos, double x, double y, double width, double height) { + var content = new Rectangle(100, 50); + HeaderBar.setAlignment(content, pos); + HeaderBar.setMargin(content, new Insets(10)); + headerBar.setTrailing(content); + headerBar.resize(1000, 100); + headerBar.layout(); + + assertBounds(x, y, width, height, content); + } + + @ParameterizedTest + @CsvSource({ + "TOP_LEFT, 10, 10, 200, 80", + "TOP_CENTER, 400, 10, 200, 80", + "TOP_RIGHT, 790, 10, 200, 80", + "CENTER_LEFT, 10, 10, 200, 80", + "CENTER, 400, 10, 200, 80", + "CENTER_RIGHT, 790, 10, 200, 80", + "BOTTOM_LEFT, 10, 10, 200, 80", + "BOTTOM_CENTER, 400, 10, 200, 80", + "BOTTOM_RIGHT, 790, 10, 200, 80" + }) + void alignmentOfCenterChildOnly_resizable( + Pos pos, double x, double y, double width, double height) { + var content = new MockResizable(0, 0, 100, 50, 200, 100); + HeaderBar.setAlignment(content, pos); + HeaderBar.setMargin(content, new Insets(10)); + headerBar.setCenter(content); + headerBar.resize(1000, 100); + headerBar.layout(); + + assertBounds(x, y, width, height, content); + } + + @ParameterizedTest + @CsvSource({ + "TOP_LEFT, 10, 10, 100, 50", + "TOP_CENTER, 450, 10, 100, 50", + "TOP_RIGHT, 890, 10, 100, 50", + "CENTER_LEFT, 10, 25, 100, 50", + "CENTER, 450, 25, 100, 50", + "CENTER_RIGHT, 890, 25, 100, 50", + "BOTTOM_LEFT, 10, 40, 100, 50", + "BOTTOM_CENTER, 450, 40, 100, 50", + "BOTTOM_RIGHT, 890, 40, 100, 50" + }) + void alignmentOfCenterChildOnly_notResizable(Pos pos, double x, double y, double width, double height) { + var content = new Rectangle(100, 50); + HeaderBar.setAlignment(content, pos); + HeaderBar.setMargin(content, new Insets(10)); + headerBar.setCenter(content); + headerBar.resize(1000, 100); + headerBar.layout(); + + assertBounds(x, y, width, height, content); + } + + @ParameterizedTest + @CsvSource({ + "TOP_LEFT, 60, 10, 200, 80", + "TOP_CENTER, 400, 10, 200, 80", + "TOP_RIGHT, 640, 10, 200, 80", + "CENTER_LEFT, 60, 10, 200, 80", + "CENTER, 400, 10, 200, 80", + "CENTER_RIGHT, 640, 10, 200, 80", + "BOTTOM_LEFT, 60, 10, 200, 80", + "BOTTOM_CENTER, 400, 10, 200, 80", + "BOTTOM_RIGHT, 640, 10, 200, 80" + }) + void alignmentOfCenterChild_resizable_withNonEmptyLeadingAndTrailingChild( + Pos pos, double x, double y, double width, double height) { + var leading = new MockResizable(50, 50); + var center = new MockResizable(0, 0, 100, 50, 200, 100); + var trailing = new MockResizable(150, 50); + HeaderBar.setAlignment(center, pos); + HeaderBar.setMargin(center, new Insets(10)); + headerBar.setLeading(leading); + headerBar.setCenter(center); + headerBar.setTrailing(trailing); + headerBar.resize(1000, 100); + headerBar.layout(); + + assertBounds(x, y, width, height, center); + } + + @ParameterizedTest + @CsvSource({ + "TOP_LEFT, 60, 10, 100, 50", + "TOP_CENTER, 450, 10, 100, 50", + "TOP_RIGHT, 740, 10, 100, 50", + "CENTER_LEFT, 60, 25, 100, 50", + "CENTER, 450, 25, 100, 50", + "CENTER_RIGHT, 740, 25, 100, 50", + "BOTTOM_LEFT, 60, 40, 100, 50", + "BOTTOM_CENTER, 450, 40, 100, 50", + "BOTTOM_RIGHT, 740, 40, 100, 50" + }) + void alignmentOfCenterChild_notResizable_withNonEmptyLeadingAndTrailingChild( + Pos pos, double x, double y, double width, double height) { + var leading = new Rectangle(50, 50); + var center = new Rectangle(100, 50); + var trailing = new Rectangle(150, 50); + HeaderBar.setAlignment(center, pos); + HeaderBar.setMargin(center, new Insets(10)); + headerBar.setLeading(leading); + headerBar.setCenter(center); + headerBar.setTrailing(trailing); + headerBar.resize(1000, 100); + headerBar.layout(); + + assertBounds(x, y, width, height, center); + } + + @ParameterizedTest + @CsvSource({ + "TOP_LEFT, 160, 10, 680, 80", + "TOP_CENTER, 160, 10, 680, 80", + "TOP_RIGHT, 160, 10, 680, 80", + "CENTER_LEFT, 160, 10, 680, 80", + "CENTER, 160, 10, 680, 80", + "CENTER_RIGHT, 160, 10, 680, 80", + "BOTTOM_LEFT, 160, 10, 680, 80", + "BOTTOM_CENTER, 160, 10, 680, 80", + "BOTTOM_RIGHT, 160, 10, 680, 80" + }) + void alignmentOfCenterChild_withLeftSystemInset(Pos pos, double x, double y, double width, double height) { + ObjectProperty leftSystemInset = ReflectionUtils.getFieldValue(headerBar, "leftSystemInset"); + leftSystemInset.set(new Dimension2D(100, 100)); + alignmentOfCenterChildImpl(pos, 1000, 1000, x, y, width, height); + } + + @ParameterizedTest + @CsvSource({ + "TOP_LEFT, 160, 10, 100, 80", + "TOP_CENTER, 450, 10, 100, 80", + "TOP_RIGHT, 740, 10, 100, 80", + "CENTER_LEFT, 160, 10, 100, 80", + "CENTER, 450, 10, 100, 80", + "CENTER_RIGHT, 740, 10, 100, 80", + "BOTTOM_LEFT, 160, 10, 100, 80", + "BOTTOM_CENTER, 450, 10, 100, 80", + "BOTTOM_RIGHT, 740, 10, 100, 80" + }) + void alignmentOfCenterChild_withLeftSystemInset_andMaxWidthConstraint( + Pos pos, double x, double y, double width, double height) { + ObjectProperty leftSystemInset = ReflectionUtils.getFieldValue(headerBar, "leftSystemInset"); + leftSystemInset.set(new Dimension2D(100, 100)); + alignmentOfCenterChildImpl(pos, 1000, 100, x, y, width, height); + } + + @ParameterizedTest + @CsvSource({ + "TOP_LEFT, 60, 10, 680, 80", + "TOP_CENTER, 60, 10, 680, 80", + "TOP_RIGHT, 60, 10, 680, 80", + "CENTER_LEFT, 60, 10, 680, 80", + "CENTER, 60, 10, 680, 80", + "CENTER_RIGHT, 60, 10, 680, 80", + "BOTTOM_LEFT, 60, 10, 680, 80", + "BOTTOM_CENTER, 60, 10, 680, 80", + "BOTTOM_RIGHT, 60, 10, 680, 80" + }) + void alignmentOfCenterChild_withRightSystemInset(Pos pos, double x, double y, double width, double height) { + ObjectProperty rightSystemInset = ReflectionUtils.getFieldValue(headerBar, "rightSystemInset"); + rightSystemInset.set(new Dimension2D(100, 100)); + alignmentOfCenterChildImpl(pos, 1000, 1000, x, y, width, height); + } + + @ParameterizedTest + @CsvSource({ + "TOP_LEFT, 60, 10, 100, 80", + "TOP_CENTER, 450, 10, 100, 80", + "TOP_RIGHT, 640, 10, 100, 80", + "CENTER_LEFT, 60, 10, 100, 80", + "CENTER, 450, 10, 100, 80", + "CENTER_RIGHT, 640, 10, 100, 80", + "BOTTOM_LEFT, 60, 10, 100, 80", + "BOTTOM_CENTER, 450, 10, 100, 80", + "BOTTOM_RIGHT, 640, 10, 100, 80" + }) + void alignmentOfCenterChild_withRightSystemInset_andMaxWidthConstraint( + Pos pos, double x, double y, double width, double height) { + ObjectProperty rightSystemInset = ReflectionUtils.getFieldValue(headerBar, "rightSystemInset"); + rightSystemInset.set(new Dimension2D(100, 100)); + alignmentOfCenterChildImpl(pos, 1000, 100, x, y, width, height); + } + + @ParameterizedTest + @CsvSource({ + "TOP_CENTER, 260, 10, 80, 80", + "CENTER, 260, 10, 80, 80", + "BOTTOM_CENTER, 260, 10, 80, 80" + }) + void alignmentOfCenterChild_withLeftSystemInset_andOffsetCausedByInsufficientHorizontalSpace( + Pos pos, double x, double y, double width, double height) { + ObjectProperty leftSystemInset = ReflectionUtils.getFieldValue(headerBar, "leftSystemInset"); + leftSystemInset.set(new Dimension2D(200, 100)); + alignmentOfCenterChildImpl(pos, 500, 100, x, y, width, height); + } + + @ParameterizedTest + @CsvSource({ + "TOP_CENTER, 60, 10, 80, 80", + "CENTER, 60, 10, 80, 80", + "BOTTOM_CENTER, 60, 10, 80, 80" + }) + void alignmentOfCenterChild_withRightSystemInset_andOffsetCausedByInsufficientHorizontalSpace( + Pos pos, double x, double y, double width, double height) { + ObjectProperty rightSystemInset = ReflectionUtils.getFieldValue(headerBar, "rightSystemInset"); + rightSystemInset.set(new Dimension2D(200, 100)); + alignmentOfCenterChildImpl(pos, 500, 100, x, y, width, height); + } + + private void alignmentOfCenterChildImpl(Pos pos, double headerBarWidth, double maxWidth, + double x, double y, double width, double height) { + var leading = new MockResizable(50, 50); + var center = new MockResizable(0, 0, 100, 50, maxWidth, 100); + var trailing = new MockResizable(150, 50); + HeaderBar.setAlignment(center, pos); + HeaderBar.setMargin(center, new Insets(10)); + headerBar.setLeading(leading); + headerBar.setCenter(center); + headerBar.setTrailing(trailing); + headerBar.resize(headerBarWidth, 100); + headerBar.layout(); + + assertBounds(x, y, width, height, center); + } + + @ParameterizedTest + @CsvSource({ + "TOP_LEFT, 10, 10, 50, 50", + "CENTER, 10, 25, 50, 50", + "BOTTOM_LEFT, 10, 40, 50, 50" + }) + void alignmentOfLeadingChild_notResizable_withoutReservedArea( + Pos pos, double x, double y, double width, double height) { + ObjectProperty leftSystemInset = ReflectionUtils.getFieldValue(headerBar, "leftSystemInset"); + leftSystemInset.set(new Dimension2D(100, 100)); + var leading = new Rectangle(50, 50); + HeaderBar.setAlignment(leading, pos); + HeaderBar.setMargin(leading, new Insets(10)); + headerBar.setLeadingSystemPadding(false); + headerBar.setLeading(leading); + headerBar.resize(1000, 100); + headerBar.layout(); + + assertBounds(x, y, width, height, leading); + } + + @ParameterizedTest + @CsvSource({ + "TOP_RIGHT, 940, 10, 50, 50", + "CENTER, 940, 25, 50, 50", + "BOTTOM_RIGHT, 940, 40, 50, 50" + }) + void alignmentOfTrailingChild_notResizable_withoutReservedArea( + Pos pos, double x, double y, double width, double height) { + ObjectProperty rightSystemInset = ReflectionUtils.getFieldValue(headerBar, "rightSystemInset"); + rightSystemInset.set(new Dimension2D(100, 100)); + var trailing = new Rectangle(50, 50); + HeaderBar.setAlignment(trailing, pos); + HeaderBar.setMargin(trailing, new Insets(10)); + headerBar.setTrailingSystemPadding(false); + headerBar.setTrailing(trailing); + headerBar.resize(1000, 100); + headerBar.layout(); + + assertBounds(x, y, width, height, trailing); + } + + private void assertBounds(double x, double y, double width, double height, Node node) { + var bounds = node.getLayoutBounds(); + assertEquals(x, node.getLayoutX()); + assertEquals(y, node.getLayoutY()); + assertEquals(width, bounds.getWidth()); + assertEquals(height, bounds.getHeight()); + } } } diff --git a/tests/manual/monkey/src/com/oracle/tools/fx/monkey/tools/StageTesterWindow.java b/tests/manual/monkey/src/com/oracle/tools/fx/monkey/tools/StageTesterWindow.java index 6e429dd9149..ece3bb7084f 100644 --- a/tests/manual/monkey/src/com/oracle/tools/fx/monkey/tools/StageTesterWindow.java +++ b/tests/manual/monkey/src/com/oracle/tools/fx/monkey/tools/StageTesterWindow.java @@ -43,7 +43,6 @@ import javafx.scene.layout.BorderPane; import javafx.scene.layout.GridPane; import javafx.scene.layout.HeaderBar; -import javafx.scene.layout.HeaderBarBase; import javafx.scene.layout.HeaderButtonType; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; @@ -166,7 +165,7 @@ public StageTesterWindow(Stage owner) { private Parent createSimpleHeaderBarRoot(Stage stage, boolean customWindowButtons) { var headerBar = new HeaderBar(); headerBar.setBackground(Background.fill(Color.LIGHTSKYBLUE)); - headerBar.setCenter(new TextField() {{ setPromptText("Search..."); }}); + headerBar.setCenter(new TextField() {{ setPromptText("Search..."); setMaxWidth(300); }}); var sizeComboBox = new ComboBox<>(FXCollections.observableArrayList("Small", "Medium", "Large")); sizeComboBox.getSelectionModel().select(0); @@ -184,15 +183,15 @@ private Parent createSimpleHeaderBarRoot(Stage stage, boolean customWindowButton if (customWindowButtons) { HeaderBar.setPrefButtonHeight(stage, 0); } else { - var headerButtonHeight = new CheckBox("Set button height to bar height"); + var adaptiveButtonHeight = new CheckBox("Adaptive button height"); headerBar.heightProperty().subscribe(h -> { - if (headerButtonHeight.isSelected()) { + if (adaptiveButtonHeight.isSelected()) { HeaderBar.setPrefButtonHeight(stage, h.doubleValue()); } }); - headerButtonHeight.selectedProperty().subscribe(value -> { + adaptiveButtonHeight.selectedProperty().subscribe(value -> { if (value) { HeaderBar.setPrefButtonHeight(stage, headerBar.getHeight()); } else { @@ -200,7 +199,7 @@ private Parent createSimpleHeaderBarRoot(Stage stage, boolean customWindowButton } }); - headerBar.setLeading(headerButtonHeight); + headerBar.setLeading(adaptiveButtonHeight); } var trailingNodes = new HBox(sizeComboBox); @@ -223,7 +222,7 @@ private Parent createSplitHeaderBarRoot(Stage stage, boolean customWindowButtons var leftHeaderBar = new HeaderBar(); leftHeaderBar.setBackground(Background.fill(Color.VIOLET)); leftHeaderBar.setLeading(new Button("\u2728")); - leftHeaderBar.setCenter(new TextField() {{ setPromptText("Search..."); }}); + leftHeaderBar.setCenter(new TextField() {{ setPromptText("Search..."); setMaxWidth(200); }}); leftHeaderBar.setTrailingSystemPadding(false); var rightHeaderBar = new HeaderBar(); @@ -278,9 +277,9 @@ private List createCustomWindowButtons() { } }); - HeaderBarBase.setButtonType(iconifyButton, HeaderButtonType.ICONIFY); - HeaderBarBase.setButtonType(maximizeButton, HeaderButtonType.MAXIMIZE); - HeaderBarBase.setButtonType(closeButton, HeaderButtonType.CLOSE); + HeaderBar.setButtonType(iconifyButton, HeaderButtonType.ICONIFY); + HeaderBar.setButtonType(maximizeButton, HeaderButtonType.MAXIMIZE); + HeaderBar.setButtonType(closeButton, HeaderButtonType.CLOSE); return List.of(iconifyButton, maximizeButton, closeButton); } From 9a7246bd6893d628735130679b3a791967a17074 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Thu, 13 Feb 2025 20:04:44 +0100 Subject: [PATCH 50/73] update copyright headers --- .../javafx.base/src/test/java/test/util/ReflectionUtils.java | 2 +- .../src/main/java/com/sun/glass/ui/Application.java | 2 +- .../src/main/java/com/sun/glass/ui/gtk/GtkApplication.java | 2 +- .../main/java/com/sun/glass/ui/gtk/PlatformThemeObserver.java | 2 +- .../main/java/com/sun/glass/ui/gtk/WindowDecorationTheme.java | 2 +- .../src/main/java/com/sun/glass/ui/gtk/WindowManager.java | 2 +- .../src/main/java/com/sun/glass/ui/ios/IosApplication.java | 2 +- .../src/main/java/com/sun/glass/ui/mac/MacApplication.java | 2 +- .../java/com/sun/glass/ui/monocle/MonocleApplication.java | 2 +- .../src/main/java/com/sun/glass/ui/win/WinApplication.java | 2 +- .../src/main/java/com/sun/javafx/scene/NodeHelper.java | 2 +- .../src/main/java/com/sun/javafx/tk/TKScene.java | 2 +- .../main/java/com/sun/javafx/tk/quantum/OverlayWarning.java | 2 +- .../main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java | 2 +- .../src/main/java/com/sun/javafx/tk/quantum/ViewScene.java | 2 +- .../src/main/java/com/sun/javafx/util/Utils.java | 2 +- modules/javafx.graphics/src/main/java/javafx/scene/Node.java | 2 +- .../src/main/java/javafx/scene/layout/HeaderBar.java | 4 ++-- .../src/main/native-glass/gtk/GlassApplication.cpp | 2 +- .../javafx.graphics/src/main/native-glass/gtk/GlassWindow.cpp | 2 +- .../src/main/native-glass/gtk/glass_general.cpp | 2 +- .../javafx.graphics/src/main/native-glass/gtk/glass_general.h | 2 +- .../src/main/native-glass/gtk/glass_window.cpp | 2 +- .../javafx.graphics/src/main/native-glass/gtk/glass_window.h | 2 +- .../src/main/native-glass/mac/GlassViewDelegate.h | 2 +- .../src/main/native-glass/mac/GlassViewDelegate.m | 2 +- .../src/main/native-glass/mac/GlassWindow+Overrides.m | 2 +- .../javafx.graphics/src/main/native-glass/win/GlassWindow.h | 2 +- modules/javafx.graphics/src/main/native-glass/win/Utils.h | 2 +- modules/javafx.graphics/src/main/native-glass/win/common.h | 2 +- .../src/test/java/test/com/sun/javafx/util/UtilsTest.java | 2 +- 31 files changed, 32 insertions(+), 32 deletions(-) diff --git a/modules/javafx.base/src/test/java/test/util/ReflectionUtils.java b/modules/javafx.base/src/test/java/test/util/ReflectionUtils.java index aa6bda04c37..645fb3e15f5 100644 --- a/modules/javafx.base/src/test/java/test/util/ReflectionUtils.java +++ b/modules/javafx.base/src/test/java/test/util/ReflectionUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Application.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Application.java index 2f2e819f5b8..8029cd75d7a 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Application.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/Application.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkApplication.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkApplication.java index ad635164d56..f2a33641fdd 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkApplication.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkApplication.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/PlatformThemeObserver.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/PlatformThemeObserver.java index 583a6653985..2ad5bbb3b87 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/PlatformThemeObserver.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/PlatformThemeObserver.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/WindowDecorationTheme.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/WindowDecorationTheme.java index 007a2e22864..fe0986359b4 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/WindowDecorationTheme.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/WindowDecorationTheme.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/WindowManager.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/WindowManager.java index 43f9ca04b44..8c436dbb4b1 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/WindowManager.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/WindowManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/ios/IosApplication.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/ios/IosApplication.java index 7d362f565fc..8ccab58d9c9 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/ios/IosApplication.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/ios/IosApplication.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacApplication.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacApplication.java index f939c4fc385..b2813abcf9c 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacApplication.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacApplication.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/monocle/MonocleApplication.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/monocle/MonocleApplication.java index 9008049fc97..7732bb36248 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/monocle/MonocleApplication.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/monocle/MonocleApplication.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinApplication.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinApplication.java index 908b9462b3d..68e401d2181 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinApplication.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinApplication.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/NodeHelper.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/NodeHelper.java index e6ddf0fe5f0..98184395cad 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/NodeHelper.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/NodeHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKScene.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKScene.java index aefda32755f..c44463cff65 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKScene.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKScene.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/OverlayWarning.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/OverlayWarning.java index 445c6b7a8e8..f4182777bf4 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/OverlayWarning.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/OverlayWarning.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 20245, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java index 4c8b2e7c7d5..3543e66f451 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/ViewScene.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/ViewScene.java index 83f0a62f058..e95be547afb 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/ViewScene.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/ViewScene.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/util/Utils.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/util/Utils.java index 056617dd386..5f093e8eec1 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/util/Utils.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/util/Utils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/Node.java b/modules/javafx.graphics/src/main/java/javafx/scene/Node.java index 9a92155c1ec..4f137dd2206 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/Node.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/Node.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java index 85e708e0466..aad7397a1e1 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java @@ -376,7 +376,7 @@ private void updateInsets() { } /** - * Describes the size of the left system-reserved inset, which is an area reserved for the minimize, maximize, + * Describes the size of the left system-reserved inset, which is an area reserved for the iconify, maximize, * and close window buttons. If there are no window buttons on the left side of the window, the returned area * is an empty {@code Dimension2D}. *

@@ -399,7 +399,7 @@ public final Dimension2D getLeftSystemInset() { } /** - * Describes the size of the right system-reserved inset, which is an area reserved for the minimize, maximize, + * Describes the size of the right system-reserved inset, which is an area reserved for the iconify, maximize, * and close window buttons. If there are no window buttons on the right side of the window, the returned area * is an empty {@code Dimension2D}. *

diff --git a/modules/javafx.graphics/src/main/native-glass/gtk/GlassApplication.cpp b/modules/javafx.graphics/src/main/native-glass/gtk/GlassApplication.cpp index 9244ba14a01..cfa220381a5 100644 --- a/modules/javafx.graphics/src/main/native-glass/gtk/GlassApplication.cpp +++ b/modules/javafx.graphics/src/main/native-glass/gtk/GlassApplication.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/modules/javafx.graphics/src/main/native-glass/gtk/GlassWindow.cpp b/modules/javafx.graphics/src/main/native-glass/gtk/GlassWindow.cpp index f0f6e39cbe6..16f033357ed 100644 --- a/modules/javafx.graphics/src/main/native-glass/gtk/GlassWindow.cpp +++ b/modules/javafx.graphics/src/main/native-glass/gtk/GlassWindow.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/modules/javafx.graphics/src/main/native-glass/gtk/glass_general.cpp b/modules/javafx.graphics/src/main/native-glass/gtk/glass_general.cpp index 8df734321f8..ea08f79bdb6 100644 --- a/modules/javafx.graphics/src/main/native-glass/gtk/glass_general.cpp +++ b/modules/javafx.graphics/src/main/native-glass/gtk/glass_general.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/modules/javafx.graphics/src/main/native-glass/gtk/glass_general.h b/modules/javafx.graphics/src/main/native-glass/gtk/glass_general.h index 9eb8898b91e..24b339a026d 100644 --- a/modules/javafx.graphics/src/main/native-glass/gtk/glass_general.h +++ b/modules/javafx.graphics/src/main/native-glass/gtk/glass_general.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.cpp b/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.cpp index 58e585af0a9..07f44d3ae32 100644 --- a/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.cpp +++ b/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.h b/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.h index 60348950c09..c65888550e2 100644 --- a/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.h +++ b/modules/javafx.graphics/src/main/native-glass/gtk/glass_window.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/modules/javafx.graphics/src/main/native-glass/mac/GlassViewDelegate.h b/modules/javafx.graphics/src/main/native-glass/mac/GlassViewDelegate.h index 1cb1b50c818..db5e1894dca 100644 --- a/modules/javafx.graphics/src/main/native-glass/mac/GlassViewDelegate.h +++ b/modules/javafx.graphics/src/main/native-glass/mac/GlassViewDelegate.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/modules/javafx.graphics/src/main/native-glass/mac/GlassViewDelegate.m b/modules/javafx.graphics/src/main/native-glass/mac/GlassViewDelegate.m index 0e37cf99458..c2b4c9c4f54 100644 --- a/modules/javafx.graphics/src/main/native-glass/mac/GlassViewDelegate.m +++ b/modules/javafx.graphics/src/main/native-glass/mac/GlassViewDelegate.m @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow+Overrides.m b/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow+Overrides.m index e3d95097fe3..0e6bb4bee4b 100644 --- a/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow+Overrides.m +++ b/modules/javafx.graphics/src/main/native-glass/mac/GlassWindow+Overrides.m @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.h b/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.h index 7cad2254c9d..f88dbdd29dd 100644 --- a/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.h +++ b/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/modules/javafx.graphics/src/main/native-glass/win/Utils.h b/modules/javafx.graphics/src/main/native-glass/win/Utils.h index 1c757c739cb..3e1fbfd1b6d 100644 --- a/modules/javafx.graphics/src/main/native-glass/win/Utils.h +++ b/modules/javafx.graphics/src/main/native-glass/win/Utils.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/modules/javafx.graphics/src/main/native-glass/win/common.h b/modules/javafx.graphics/src/main/native-glass/win/common.h index 67ca2bbc49d..09c033057c3 100644 --- a/modules/javafx.graphics/src/main/native-glass/win/common.h +++ b/modules/javafx.graphics/src/main/native-glass/win/common.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/modules/javafx.graphics/src/test/java/test/com/sun/javafx/util/UtilsTest.java b/modules/javafx.graphics/src/test/java/test/com/sun/javafx/util/UtilsTest.java index d4f0d740332..3f8ee5abbf2 100644 --- a/modules/javafx.graphics/src/test/java/test/com/sun/javafx/util/UtilsTest.java +++ b/modules/javafx.graphics/src/test/java/test/com/sun/javafx/util/UtilsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it From 1f95e9db757035f522a65532c38a8fd13c601824 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Thu, 13 Feb 2025 20:06:13 +0100 Subject: [PATCH 51/73] typo --- .../src/main/java/com/sun/javafx/tk/quantum/OverlayWarning.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/OverlayWarning.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/OverlayWarning.java index f4182777bf4..9d23d8aa92e 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/OverlayWarning.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/OverlayWarning.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 20245, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it From cd9c2a943ae8b7223014c9de59b973aa9bdf3474 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Sat, 15 Feb 2025 00:31:45 +0100 Subject: [PATCH 52/73] minify button glyphs --- .../com/sun/glass/ui/gtk/WindowDecorationGnome.css | 4 ++-- .../com/sun/glass/ui/gtk/WindowDecorationKDE.css | 8 ++++---- .../resources/com/sun/glass/ui/win/WindowDecoration.css | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/gtk/WindowDecorationGnome.css b/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/gtk/WindowDecorationGnome.css index beb58f95af4..6c33976fdc3 100644 --- a/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/gtk/WindowDecorationGnome.css +++ b/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/gtk/WindowDecorationGnome.css @@ -119,9 +119,9 @@ } .maximize-button.restore > .glyph { - -fx-shape: "M 10 7 L 10 8 L 16 8 L 16 14 L 17 14 L 17 7 L 10 7 z M 8 9 L 8 16 L 15 16 L 15 9 L 8 9 z M 9 9.9238281 L 14 9.9238281 L 14 15 L 9 15 L 9 9.9238281 z"; + -fx-shape: "M 10 7 L 10 8 L 16 8 L 16 14 L 17 14 L 17 7 L 10 7 z M 8 9 L 8 16 L 15 16 L 15 9 L 8 9 z M 9 9.9238 L 14 9.9238 L 14 15 L 9 15 L 9 9.9238 z"; } .close-button > .glyph { - -fx-shape: "m 8.1464844,8.1464844 a 0.5,0.5 0 0 0 0,0.7070312 L 11.292969,12 8.1464844,15.146484 a 0.5,0.5 0 0 0 0,0.707032 0.5,0.5 0 0 0 0.7070312,0 L 12,12.707031 l 3.146484,3.146485 a 0.5,0.5 0 0 0 0.707032,0 0.5,0.5 0 0 0 0,-0.707032 L 12.707031,12 15.853516,8.8535156 a 0.5,0.5 0 0 0 0,-0.7070312 0.5,0.5 0 0 0 -0.707032,0 L 12,11.292969 8.8535156,8.1464844 a 0.5,0.5 0 0 0 -0.7070312,0 z"; + -fx-shape: "m 8.1465,8.1465 a 0.5,0.5 0 0 0 0,0.707 L 11.293,12 8.1465,15.1465 a 0.5,0.5 0 0 0 0,0.707 0.5,0.5 0 0 0 0.707,0 L 12,12.707 l 3.1465,3.1465 a 0.5,0.5 0 0 0 0.707,0 0.5,0.5 0 0 0 0,-0.707 L 12.707,12 15.8535,8.8535 a 0.5,0.5 0 0 0 0,-0.707 0.5,0.5 0 0 0 -0.707,0 L 12,11.293 8.8535,8.1465 a 0.5,0.5 0 0 0 -0.707,0 z"; } diff --git a/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/gtk/WindowDecorationKDE.css b/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/gtk/WindowDecorationKDE.css index 08091128800..684c7908679 100644 --- a/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/gtk/WindowDecorationKDE.css +++ b/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/gtk/WindowDecorationKDE.css @@ -96,17 +96,17 @@ } .iconify-button > .glyph { - -fx-shape: "m 5,7.5 a 0.5,0.5 0 0 0 -0.3535156,0.1464844 0.5,0.5 0 0 0 0,0.7070312 l 5,5.0000004 a 0.50005,0.50005 0 0 0 0.7070316,0 l 5,-5.0000004 a 0.5,0.5 0 0 0 0,-0.7070312 0.5,0.5 0 0 0 -0.707032,0 L 10,12.292969 5.3535156,7.6464844 A 0.5,0.5 0 0 0 5,7.5 Z"; + -fx-shape: "m 5,7.5 a 0.5,0.5 0 0 0 -0.3535,0.1465 0.5,0.5 0 0 0 0,0.707 l 5,5 a 0.5001,0.5001 0 0 0 0.707,0 l 5,-5 a 0.5,0.5 0 0 0 0,-0.707 0.5,0.5 0 0 0 -0.707,0 L 10,12.293 5.3535,7.6465 A 0.5,0.5 0 0 0 5,7.5 Z"; } .maximize-button > .glyph { - -fx-shape: "m 9.6464844,6.6464844 -5,4.9999996 a 0.5,0.5 0 0 0 0,0.707032 0.5,0.5 0 0 0 0.7070312,0 L 10,7.7070312 14.646484,12.353516 a 0.5,0.5 0 0 0 0.707032,0 0.5,0.5 0 0 0 0,-0.707032 l -5,-4.9999996 a 0.50005,0.50005 0 0 0 -0.7070316,0 z"; + -fx-shape: "m 9.6465,6.6465 -5,5 a 0.5,0.5 0 0 0 0,0.707 0.5,0.5 0 0 0 0.707,0 L 10,7.707 14.6465,12.3535 a 0.5,0.5 0 0 0 0.707,0 0.5,0.5 0 0 0 0,-0.707 l -5,-5 a 0.5001,0.5001 0 0 0 -0.707,0 z"; } .maximize-button.restore > .glyph { - -fx-shape: "m 9.6464844,5.1464844 -4.5,4.5 a 0.50005,0.50005 0 0 0 0,0.7070316 l 4.5,4.5 a 0.50005,0.50005 0 0 0 0.7070316,0 l 4.5,-4.5 a 0.50005,0.50005 0 0 0 0,-0.7070316 l -4.5,-4.5 a 0.50005,0.50005 0 0 0 -0.7070316,0 z M 10,6.2070312 13.792969,10 10,13.792969 6.2070312,10 Z"; + -fx-shape: "m 9.6465,5.1465 -4.5,4.5 a 0.5001,0.5001 0 0 0 0,0.707 l 4.5,4.5 a 0.5001,0.5001 0 0 0 0.707,0 l 4.5,-4.5 a 0.5001,0.5001 0 0 0 0,-0.707 l -4.5,-4.5 a 0.5001,0.5001 0 0 0 -0.707,0 z M 10,6.207 13.793,10 10,13.793 6.207,10 Z"; } .close-button > .glyph { - -fx-shape: "m 6,5.5 a 0.5,0.5 0 0 0 -0.3535156,0.1464844 0.5,0.5 0 0 0 0,0.7070312 L 9.2929688,10 5.6464844,13.646484 a 0.5,0.5 0 0 0 0,0.707032 0.5,0.5 0 0 0 0.7070312,0 L 10,10.707031 l 3.646484,3.646485 a 0.5,0.5 0 0 0 0.707032,0 0.5,0.5 0 0 0 0,-0.707032 L 10.707031,10 14.353516,6.3535156 a 0.5,0.5 0 0 0 0,-0.7070312 0.5,0.5 0 0 0 -0.707032,0 L 10,9.2929688 6.3535156,5.6464844 A 0.5,0.5 0 0 0 6,5.5 Z"; + -fx-shape: "m 6,5.5 a 0.5,0.5 0 0 0 -0.3535,0.1465 0.5,0.5 0 0 0 0,0.707 L 9.293,10 5.6465,13.6465 a 0.5,0.5 0 0 0 0,0.707 0.5,0.5 0 0 0 0.707,0 L 10,10.707 l 3.6465,3.6465 a 0.5,0.5 0 0 0 0.707,0 0.5,0.5 0 0 0 0,-0.707 L 10.707,10 14.3535,6.3535 a 0.5,0.5 0 0 0 0,-0.707 0.5,0.5 0 0 0 -0.707,0 L 10,9.293 6.3535,5.6465 A 0.5,0.5 0 0 0 6,5.5 Z"; } diff --git a/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/win/WindowDecoration.css b/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/win/WindowDecoration.css index 4366093b54c..0e10fd5ad64 100644 --- a/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/win/WindowDecoration.css +++ b/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/win/WindowDecoration.css @@ -101,17 +101,17 @@ } .iconify-button > .glyph { - -fx-shape: "m 0.42342443,4.6755 q -0.0871756,0 -0.16397319,-0.0332 Q 0.18265374,4.6091 0.1245366,4.551 0.06641952,4.4929 0.03320975,4.41608 0,4.33925 0,4.25208 0,4.16488 0.03320975,4.0881 0.06641952,4.0113 0.1245366,3.95112 0.1826537,3.89092 0.25945124,3.85772 0.33624881,3.8245 0.42342443,3.8245 H 8.078274 q 0.087175,0 0.1639731,0.0332 0.076797,0.0332 0.1349147,0.0934 0.058117,0.0602 0.091327,0.13698 0.033209,0.0768 0.033209,0.16397 0,0.0872 -0.03321,0.16397 -0.03321,0.0768 -0.091327,0.13492 -0.058117,0.0581 -0.1349147,0.0913 -0.076797,0.0332 -0.1639731,0.0332 z"; + -fx-shape: "m 0.4234,4.6755 q -0.0872,0 -0.164,-0.0332 Q 0.1827,4.6091 0.1245,4.551 0.0664,4.4929 0.0332,4.4161 0,4.3393 0,4.2521 0,4.1649 0.0332,4.0881 0.0664,4.0113 0.1245,3.9511 0.1827,3.8909 0.2595,3.8577 0.3362,3.8245 0.4234,3.8245 H 8.0783 q 0.0872,0 0.164,0.0332 0.0768,0.0332 0.1349,0.0934 0.0581,0.0602 0.0913,0.137 0.0332,0.0768 0.0332,0.164 0,0.0872 -0.0332,0.164 -0.0332,0.0768 -0.0913,0.1349 -0.0581,0.0581 -0.1349,0.0913 -0.0768,0.0332 -0.164,0.0332 z"; } .maximize-button > .glyph { - -fx-shape: "M 1.253418,8.5 Q 1.0043945,8.5 0.77612305,8.3983154 0.54785156,8.2966309 0.37561035,8.1243896 0.20336914,7.9521484 0.10168457,7.723877 0,7.4956055 0,7.246582 V 1.253418 Q 0,1.0043945 0.10168457,0.77612305 0.20336914,0.54785156 0.37561035,0.37561035 0.54785156,0.20336914 0.77612305,0.10168457 1.0043945,0 1.253418,0 H 7.246582 Q 7.4956055,0 7.723877,0.10168457 7.9521484,0.20336914 8.1243896,0.37561035 8.2966309,0.54785156 8.3983154,0.77612305 8.5,1.0043945 8.5,1.253418 V 7.246582 Q 8.5,7.4956055 8.3983154,7.723877 8.2966309,7.9521484 8.1243896,8.1243896 7.9521484,8.2966309 7.723877,8.3983154 7.4956055,8.5 7.246582,8.5 Z M 7.2258301,7.6491699 q 0.087158,0 0.1639404,-0.033203 0.076782,-0.033203 0.1348877,-0.091309 0.058105,-0.058105 0.091309,-0.1348877 0.033203,-0.076782 0.033203,-0.1639404 V 1.2741699 q 0,-0.087158 -0.033203,-0.1639404 Q 7.5827637,1.0334473 7.5246582,0.9753418 7.4665527,0.91723633 7.3897705,0.8840332 7.3129883,0.85083008 7.2258301,0.85083008 H 1.2741699 q -0.087158,0 -0.1639404,0.0332031 -0.076782,0.0332031 -0.1348877,0.0913086 Q 0.9172363,1.0334473 0.8840332,1.1102295 0.8508301,1.1870115 0.8508301,1.2741699 v 5.9516602 q 0,0.087158 0.0332031,0.1639404 0.0332031,0.076782 0.0913086,0.1348877 0.0581055,0.058105 0.1348877,0.091309 0.076782,0.033203 0.1639404,0.033203 z"; + -fx-shape: "M 1.2534,8.5 Q 1.0044,8.5 0.7761,8.3983 0.5479,8.2966 0.3756,8.1244 0.2034,7.9521 0.1017,7.7239 0,7.4956 0,7.2466 V 1.2534 Q 0,1.0044 0.1017,0.7761 0.2034,0.5479 0.3756,0.3756 0.5479,0.2034 0.7761,0.1017 1.0044,0 1.2534,0 H 7.2466 Q 7.4956,0 7.7239,0.1017 7.9521,0.2034 8.1244,0.3756 8.2966,0.5479 8.3983,0.7761 8.5,1.0044 8.5,1.2534 V 7.2466 Q 8.5,7.4956 8.3983,7.7239 8.2966,7.9521 8.1244,8.1244 7.9521,8.2966 7.7239,8.3983 7.4956,8.5 7.2466,8.5 Z M 7.2258,7.6492 q 0.0872,0 0.1639,-0.0332 0.0768,-0.0332 0.1349,-0.0913 0.0581,-0.0581 0.0913,-0.1349 0.0332,-0.0768 0.0332,-0.1639 V 1.2742 q 0,-0.0872 -0.0332,-0.1639 Q 7.5828,1.0334 7.5247,0.9753 7.4666,0.9172 7.3898,0.884 7.313,0.8508 7.2258,0.8508 H 1.2742 q -0.0872,0 -0.1639,0.0332 -0.0768,0.0332 -0.1349,0.0913 Q 0.9172,1.0334 0.884,1.1102 0.8508,1.187 0.8508,1.2742 v 5.9517 q 0,0.0872 0.0332,0.1639 0.0332,0.0768 0.0913,0.1349 0.0581,0.0581 0.1349,0.0913 0.0768,0.0332 0.1639,0.0332 z"; } .maximize-button.restore > .glyph { - -fx-shape: "m 7.6491699,2.5192871 q 0,-0.3444824 -0.1369629,-0.6495361 Q 7.3752441,1.5646973 7.1407471,1.338501 6.90625,1.1123047 6.5970459,0.98156738 6.2878418,0.85083008 5.9475098,0.85083008 H 1.7722168 Q 1.838623,0.65991211 1.9589844,0.50219727 2.0793457,0.34448242 2.2370605,0.23242188 2.3947754,0.12036133 2.5836182,0.06018066 2.7724609,0 2.9758301,0 H 5.9475098 Q 6.4746094,0 6.9394531,0.20129395 7.4042969,0.40258789 7.7508545,0.74707031 8.0974121,1.0915527 8.2987061,1.5563965 8.5,2.0212402 8.5,2.5483398 V 5.5241699 Q 8.5,5.7275391 8.4398193,5.9163818 8.3796387,6.1052246 8.2675781,6.2629395 8.1555176,6.4206543 7.9978027,6.5410156 7.8400879,6.661377 7.6491699,6.7277832 Z M 1.253418,8.5 Q 1.0043945,8.5 0.77612305,8.3983154 0.54785156,8.2966309 0.37561035,8.1243896 0.20336914,7.9521484 0.10168457,7.723877 0,7.4956055 0,7.246582 V 2.9550781 Q 0,2.7019043 0.10168457,2.475708 0.20336914,2.2495117 0.37561035,2.0772705 0.54785156,1.9050293 0.77404785,1.8033447 1.0002441,1.7016602 1.253418,1.7016602 h 4.2915039 q 0.2531738,0 0.4814453,0.1016845 0.2282715,0.1016846 0.3984375,0.2718506 0.170166,0.170166 0.2718506,0.3984375 0.1016845,0.2282715 0.1016845,0.4814453 V 7.246582 q 0,0.2531739 -0.1016845,0.4793701 Q 6.5949707,7.9521484 6.4227295,8.1243896 6.2504883,8.2966309 6.024292,8.3983154 5.7980957,8.5 5.5449219,8.5 Z M 5.5241699,7.6491699 q 0.087158,0 0.1639405,-0.033203 0.076782,-0.033203 0.1369628,-0.091309 0.060181,-0.058105 0.093384,-0.1348877 0.033203,-0.076782 0.033203,-0.1639404 v -4.25 q 0,-0.087158 -0.033203,-0.1660156 Q 5.8852539,2.730957 5.8271484,2.6728516 5.769043,2.6147461 5.6901855,2.581543 5.6113281,2.5483398 5.5241699,2.5483398 h -4.25 q -0.087158,0 -0.1639404,0.033203 -0.076782,0.033203 -0.1348877,0.093384 -0.0581055,0.060181 -0.0913086,0.1369628 -0.0332031,0.076782 -0.0332031,0.1639405 v 4.25 q 0,0.087158 0.0332031,0.1639404 0.0332031,0.076782 0.0913086,0.1348877 0.0581055,0.058105 0.1348877,0.091309 0.076782,0.033203 0.1639404,0.033203 z"; + -fx-shape: "m 7.6492,2.5193 q 0,-0.3445 -0.1370,-0.6495 Q 7.3752,1.5647 7.1407,1.3385 6.9063,1.1123 6.5970,0.9816 6.2878,0.8508 5.9475,0.8508 H 1.7722 Q 1.8386,0.6599 1.9590,0.5022 2.0793,0.3445 2.2371,0.2324 2.3948,0.1204 2.5836,0.0602 2.7725,0 2.9758,0 H 5.9475 Q 6.4746,0 6.9395,0.2013 7.4043,0.4026 7.7509,0.7471 8.0974,1.0916 8.2987,1.5564 8.5,2.0212 8.5,2.5483 V 5.5242 Q 8.5,5.7275 8.4398,5.9164 8.3796,6.1052 8.2676,6.2629 8.1555,6.4207 7.9978,6.541 7.8401,6.6614 7.6492,6.7278 Z M 1.2534,8.5 Q 1.0044,8.5 0.7761,8.3983 0.5479,8.2966 0.3756,8.1244 0.2034,7.9521 0.1017,7.7239 0,7.4956 0,7.2466 V 2.9551 Q 0,2.7019 0.1017,2.4757 0.2034,2.2495 0.3756,2.0773 0.5479,1.905 0.774,1.8033 1.0002,1.7017 1.2534,1.7017 h 4.2915 q 0.2532,0 0.4814,0.1017 0.2283,0.1017 0.3984,0.2719 0.1702,0.1702 0.2719,0.3984 0.1017,0.2283 0.1017,0.4814 V 7.2466 q 0,0.2532 -0.1017,0.4794 Q 6.595,7.9521 6.4227,8.1244 6.2505,8.2966 6.0243,8.3983 5.7981,8.5 5.5449,8.5 Z M 5.5242,7.6492 q 0.0872,0 0.1639,-0.0332 0.0768,-0.0332 0.1370,-0.0913 0.0602,-0.0581 0.0934,-0.1349 0.0332,-0.0768 0.0332,-0.1639 v -4.25 q 0,-0.0872 -0.0332,-0.166 Q 5.8853,2.731 5.8271,2.6729 5.769,2.6147 5.6902,2.5815 5.6113,2.5483 5.5242,2.5483 h -4.25 q -0.0872,0 -0.1639,0.0332 -0.0768,0.0332 -0.1349,0.0934 -0.0581,0.0602 -0.0913,0.137 -0.0332,0.0768 -0.0332,0.1639 v 4.25 q 0,0.0872 0.0332,0.1639 0.0332,0.0768 0.0913,0.1349 0.0581,0.0581 0.1349,0.0913 0.0768,0.0332 0.1639,0.0332 z"; } .close-button > .glyph { - -fx-shape: "M 4.25,4.8518066 0.7263184,8.3754883 Q 0.6018066,8.5 0.4274902,8.5 0.2448731,8.5 0.1224365,8.3775635 0,8.255127 0,8.0725098 0,7.8981934 0.1245117,7.7736816 L 3.6481934,4.25 0.1245117,0.72631836 Q 0,0.60180664 0,0.42333984 0,0.33618164 0.033203,0.25732422 0.066406,0.17846682 0.124512,0.12243652 0.182617,0.06640625 0.2614749,0.03320312 0.340332,0 0.4274902,0 0.6018066,0 0.7263184,0.12451172 L 4.25,3.6481934 7.7736816,0.12451172 Q 7.8981934,0 8.0766602,0 q 0.087158,0 0.1639404,0.03320312 0.076782,0.03320313 0.1348877,0.0913086 0.058105,0.0581055 0.091309,0.13488769 Q 8.5,0.33618164 8.5,0.42333984 8.5,0.60180664 8.3754883,0.72631836 L 4.8518066,4.25 8.3754883,7.7736816 Q 8.5,7.8981934 8.5,8.0725098 8.5,8.1596678 8.466797,8.2385254 8.433594,8.3173824 8.377564,8.3754883 8.321534,8.4335933 8.2426763,8.4667973 8.1638193,8.5 8.0766607,8.5 7.8981939,8.5 7.7736821,8.3754883 Z"; + -fx-shape: "M 4.25,4.8518 0.7263,8.3755 Q 0.6018,8.5 0.4275,8.5 0.2449,8.5 0.1224,8.3776 0,8.2551 0,8.0725 0,7.8982 0.1245,7.7737 L 3.6482,4.25 0.1245,0.7263 Q 0,0.6018 0,0.4233 0,0.3362 0.0332,0.2573 0.0664,0.1785 0.1245,0.1224 0.1826,0.0664 0.2615,0.0332 0.3403,0 0.4275,0 0.6018,0 0.7263,0.1245 L 4.25,3.6482 7.7737,0.1245 Q 7.8982,0 8.0767,0 q 0.0872,0 0.1639,0.0332 0.0768,0.0332 0.1349,0.0913 0.0581,0.0581 0.0913,0.1349 Q 8.5,0.3362 8.5,0.4233 8.5,0.6018 8.3755,0.7263 L 4.8518,4.25 8.3755,7.7737 Q 8.5,7.8982 8.5,8.0725 8.5,8.1597 8.4668,8.2385 8.4336,8.3174 8.3776,8.3755 8.3215,8.4336 8.2427,8.4668 8.1638,8.5 8.0767,8.5 7.8982,8.5 7.7737,8.3755 Z"; } From 394c95f25c7cdc30069ff3553c845dc2e9a63568 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Fri, 21 Feb 2025 03:39:18 +0100 Subject: [PATCH 53/73] remove HeaderButtonOverlay.allowRtl --- .../com/sun/glass/ui/HeaderButtonOverlay.java | 42 +------------------ .../glass/ui/gtk/WindowDecorationGnome.css | 1 - .../sun/glass/ui/gtk/WindowDecorationKDE.css | 1 - .../com/sun/glass/ui/win/WindowDecoration.css | 1 - .../sun/glass/ui/HeaderButtonOverlayTest.java | 25 ----------- 5 files changed, 2 insertions(+), 68 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/HeaderButtonOverlay.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/HeaderButtonOverlay.java index a4ba1c35994..955b095ec31 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/HeaderButtonOverlay.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/HeaderButtonOverlay.java @@ -36,13 +36,11 @@ import javafx.beans.value.ObservableValue; import javafx.css.CssMetaData; import javafx.css.PseudoClass; -import javafx.css.SimpleStyleableBooleanProperty; import javafx.css.SimpleStyleableDoubleProperty; import javafx.css.SimpleStyleableIntegerProperty; import javafx.css.SimpleStyleableObjectProperty; import javafx.css.StyleConverter; import javafx.css.Styleable; -import javafx.css.StyleableBooleanProperty; import javafx.css.StyleableDoubleProperty; import javafx.css.StyleableIntegerProperty; import javafx.css.StyleableObjectProperty; @@ -106,12 +104,6 @@ * does not specify a preferred button height. * * - * - * -fx-allow-rtl<boolean>true - * - * Specifies whether the iconify/maximize/close buttons support right-to-left orientations. - * - * * * * @@ -192,25 +184,11 @@ public StyleableProperty getStyleableProperty(HeaderBut } }; - private static final CssMetaData ALLOW_RTL_METADATA = - new CssMetaData<>("-fx-allow-rtl", StyleConverter.getBooleanConverter(), true) { - @Override - public boolean isSettable(HeaderButtonOverlay overlay) { - return true; - } - - @Override - public StyleableProperty getStyleableProperty(HeaderButtonOverlay overlay) { - return overlay.allowRtl; - } - }; - private static final List> METADATA = Stream.concat(getClassCssMetaData().stream(), Stream.of(BUTTON_DEFAULT_HEIGHT_METADATA, BUTTON_PLACEMENT_METADATA, - BUTTON_VERTICAL_ALIGNMENT_METADATA, - ALLOW_RTL_METADATA)).toList(); + BUTTON_VERTICAL_ALIGNMENT_METADATA)).toList(); private static final PseudoClass HOVER_PSEUDOCLASS = PseudoClass.getPseudoClass("hover"); private static final PseudoClass PRESSED_PSEUDOCLASS = PseudoClass.getPseudoClass("pressed"); @@ -271,22 +249,6 @@ protected void invalidated() { } }; - /** - * Specifies whether the iconify/maximize/close buttons support right-to-left orientations. - *

- * If this property is {@code true} and the effective node orientation is right-to-left, the - * header buttons are mirrored to the other side of the window. - *

- * This property corresponds to the {@code -fx-allow-rtl} CSS property. - */ - private final StyleableBooleanProperty allowRtl = - new SimpleStyleableBooleanProperty(ALLOW_RTL_METADATA, this, "allowRtl", true) { - @Override - protected void invalidated() { - requestLayout(); - } - }; - /** * Contains the buttons in the order as they will appear on the window. * This list is automatically updated by the implementation of {@link ButtonRegion#buttonOrder}. @@ -529,7 +491,7 @@ protected void layoutChildren() { boolean left; Region button1, button2, button3; - if (allowRtl.get() && rightToLeft) { + if (rightToLeft) { button1 = orderedButtons.get(2); button2 = orderedButtons.get(1); button3 = orderedButtons.get(0); diff --git a/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/gtk/WindowDecorationGnome.css b/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/gtk/WindowDecorationGnome.css index 6c33976fdc3..2cd5dc09c7c 100644 --- a/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/gtk/WindowDecorationGnome.css +++ b/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/gtk/WindowDecorationGnome.css @@ -27,7 +27,6 @@ -fx-button-placement: right; -fx-button-vertical-alignment: center; -fx-button-default-height: 36; - -fx-allow-rtl: true; } .iconify-button, diff --git a/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/gtk/WindowDecorationKDE.css b/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/gtk/WindowDecorationKDE.css index 684c7908679..50820947548 100644 --- a/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/gtk/WindowDecorationKDE.css +++ b/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/gtk/WindowDecorationKDE.css @@ -27,7 +27,6 @@ -fx-button-placement: right; -fx-button-vertical-alignment: center; -fx-button-default-height: 36; - -fx-allow-rtl: true; } .iconify-button, diff --git a/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/win/WindowDecoration.css b/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/win/WindowDecoration.css index 0e10fd5ad64..5f91ee9cfc2 100644 --- a/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/win/WindowDecoration.css +++ b/modules/javafx.graphics/src/main/resources/com/sun/glass/ui/win/WindowDecoration.css @@ -27,7 +27,6 @@ -fx-button-placement: right; -fx-button-vertical-alignment: stretch; -fx-button-default-height: 29; - -fx-allow-rtl: true; } .iconify-button, diff --git a/modules/javafx.graphics/src/test/java/test/com/sun/glass/ui/HeaderButtonOverlayTest.java b/modules/javafx.graphics/src/test/java/test/com/sun/glass/ui/HeaderButtonOverlayTest.java index 8ed4fb4571d..b061239713c 100644 --- a/modules/javafx.graphics/src/test/java/test/com/sun/glass/ui/HeaderButtonOverlayTest.java +++ b/modules/javafx.graphics/src/test/java/test/com/sun/glass/ui/HeaderButtonOverlayTest.java @@ -329,31 +329,6 @@ void utilityDecorationIsOnlyCloseButton() { assertTrue(children.getFirst().getStyleClass().contains("close-button")); } - /** - * Asserts that the buttons are laid out on the right, even though the node orientation is right-to-left. - */ - @Test - void disallowRightToLeft() { - var overlay = new HeaderButtonOverlay(getStylesheet(""" - .header-button-container { -fx-button-placement: right; - -fx-button-vertical-alignment: stretch; - -fx-allow-rtl: false; } - .header-button { -fx-pref-width: 20; -fx-pref-height: 10; } - """), false, true); - - var unused = new Scene(overlay); - var children = overlay.getChildrenUnmodifiable(); - overlay.resize(200, 100); - overlay.applyCss(); - overlay.layout(); - - assertLayoutBounds(children.get(0), 140, 0, 20, 10); - assertLayoutBounds(children.get(1), 160, 0, 20, 10); - assertLayoutBounds(children.get(2), 180, 0, 20, 10); - assertEquals(EMPTY, overlay.metricsProperty().get().leftInset()); - assertEquals(new Dimension2D(60, 10), overlay.metricsProperty().get().rightInset()); - } - @Test void activePseudoClassCorrespondsToStageFocusedProperty() { var overlay = new HeaderButtonOverlay(getStylesheet(""" From df90ca1333a4af83adbb3446ecdfaf3fbcbca36b Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Wed, 12 Mar 2025 09:02:21 +0100 Subject: [PATCH 54/73] tweak header button glyph scaling --- .../com/sun/glass/ui/HeaderButtonOverlay.java | 10 +- .../glass/ui/win/WinHeaderButtonOverlay.java | 107 ++++++++++++++++++ .../java/com/sun/glass/ui/win/WinWindow.java | 11 +- 3 files changed, 117 insertions(+), 11 deletions(-) create mode 100644 modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinHeaderButtonOverlay.java diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/HeaderButtonOverlay.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/HeaderButtonOverlay.java index 955b095ec31..6fed67941d8 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/HeaderButtonOverlay.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/HeaderButtonOverlay.java @@ -139,7 +139,7 @@ * * */ -public final class HeaderButtonOverlay extends Region { +public class HeaderButtonOverlay extends Region { private static final CssMetaData BUTTON_DEFAULT_HEIGHT_METADATA = new CssMetaData<>("-fx-button-default-height", StyleConverter.getSizeConverter()) { @@ -325,6 +325,14 @@ public DoubleProperty prefButtonHeightProperty() { return prefButtonHeight; } + protected Region getButtonGlyph(HeaderButtonType buttonType) { + return (Region)(switch (buttonType) { + case ICONIFY -> iconifyButton; + case MAXIMIZE -> maximizeButton; + case CLOSE -> closeButton; + }).getChildrenUnmodifiable().getFirst(); + } + /** * Classifies and returns the button type at the specified coordinate, or returns * {@code null} if the specified coordinate does not intersect a button. diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinHeaderButtonOverlay.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinHeaderButtonOverlay.java new file mode 100644 index 00000000000..96a0d8f0484 --- /dev/null +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinHeaderButtonOverlay.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.glass.ui.win; + +import com.sun.glass.ui.HeaderButtonOverlay; +import com.sun.javafx.binding.StringConstant; +import javafx.beans.value.ObservableValue; +import javafx.scene.Scene; +import javafx.scene.layout.HeaderButtonType; +import javafx.stage.Window; + +/** + * Windows-specific version of {@link HeaderButtonOverlay} that tweaks the scaling of header button glyphs. + */ +public class WinHeaderButtonOverlay extends HeaderButtonOverlay { + + private static final String HEADER_BUTTONS_STYLESHEET = "WindowDecoration.css"; + + /** + * These are additional scale factors for the header button glyphs at various DPI scales to account + * for differences in the way the glyphs are rendered by JavaFX and Windows. Slightly adjusting + * the scaling makes the JavaFX glyphs look more similar to the native glyphs drawn by Windows. + * The values must be listed in 25% increments. DPI scales outside of the listed range default + * to an additional scaling factor of 1. + */ + private static final double[][] SCALE_FACTORS = new double[][] { + { 1.0, 1.0 }, + { 1.25, 1.1 }, + { 1.5, 1.15 }, + { 1.75, 1.0 }, + { 2.0, 1.15 }, + { 2.25, 1.05 }, + { 2.5, 0.95 }, + }; + + public WinHeaderButtonOverlay(boolean utility, boolean rightToLeft) { + super(getStylesheet(), utility, rightToLeft); + + var windowProperty = sceneProperty().flatMap(Scene::windowProperty); + + windowProperty + .flatMap(Window::renderScaleXProperty) + .orElse(1.0) + .map(v -> getGlyphScaleFactor(v.doubleValue())) + .subscribe(this::updateGlyphScaleX); + + windowProperty + .flatMap(Window::renderScaleYProperty) + .orElse(1.0) + .map(v -> getGlyphScaleFactor(v.doubleValue())) + .subscribe(this::updateGlyphScaleY); + } + + private double getGlyphScaleFactor(double scale) { + for (double[] mapping : SCALE_FACTORS) { + if (scale >= (mapping[0] - 0.125) && scale <= (mapping[0] + 0.125)) { + return mapping[1]; + } + } + + return 1.0; + } + + private void updateGlyphScaleX(double scale) { + for (var buttonType : HeaderButtonType.values()) { + getButtonGlyph(buttonType).setScaleX(scale); + } + } + + private void updateGlyphScaleY(double scale) { + for (var buttonType : HeaderButtonType.values()) { + getButtonGlyph(buttonType).setScaleY(scale); + } + } + + private static ObservableValue getStylesheet() { + var url = WinHeaderButtonOverlay.class.getResource(HEADER_BUTTONS_STYLESHEET); + if (url == null) { + throw new RuntimeException("Resource not found: " + HEADER_BUTTONS_STYLESHEET); + } + + return StringConstant.valueOf(url.toExternalForm()); + } +} diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java index 3a5f06bffd5..7a48e6e82c1 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinWindow.java @@ -31,7 +31,6 @@ import com.sun.glass.ui.Screen; import com.sun.glass.ui.View; import com.sun.glass.ui.Window; -import com.sun.javafx.binding.StringConstant; /** * MS Windows platform implementation class for Window. @@ -43,8 +42,6 @@ class WinWindow extends Window { public static final long ANCHOR_NO_CAPTURE = (1L << 63); - private static final String HEADER_BUTTONS_STYLESHEET = "WindowDecoration.css"; - private float fxReqWidth; private float fxReqHeight; private int pfReqWidth; @@ -383,13 +380,7 @@ private void onPrefHeaderButtonHeightChanged(Number height) { * Creates a new {@code HeaderButtonOverlay} instance. */ private HeaderButtonOverlay createHeaderButtonOverlay() { - var url = getClass().getResource(HEADER_BUTTONS_STYLESHEET); - if (url == null) { - throw new RuntimeException("Resource not found: " + HEADER_BUTTONS_STYLESHEET); - } - - var overlay = new HeaderButtonOverlay( - StringConstant.valueOf(url.toExternalForm()), + var overlay = new WinHeaderButtonOverlay( isUtilityWindow(), (getStyleMask() & RIGHT_TO_LEFT) != 0); From 0985e9ce1d325cd16ab265c33552c17a5e078363 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Mon, 17 Mar 2025 18:37:14 +0100 Subject: [PATCH 55/73] add preview feature --- .../src/main/java/com/sun/javafx/PreviewFeature.java | 3 ++- .../src/main/java/javafx/stage/Stage.java | 10 +++++++--- .../src/main/java/javafx/stage/StageStyle.java | 4 ++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/modules/javafx.base/src/main/java/com/sun/javafx/PreviewFeature.java b/modules/javafx.base/src/main/java/com/sun/javafx/PreviewFeature.java index 9219d35869c..86679d8560f 100644 --- a/modules/javafx.base/src/main/java/com/sun/javafx/PreviewFeature.java +++ b/modules/javafx.base/src/main/java/com/sun/javafx/PreviewFeature.java @@ -38,7 +38,8 @@ public enum PreviewFeature { // Add preview feature constants here: // TEST_FEATURE("Test Feature") - ; + STAGE_STYLE_EXTENDED("StageStyle.EXTENDED"), + STAGE_STYLE_EXTENDED_UTILITY("StageStyle.EXTENDED_UTILITY"); PreviewFeature(String featureName) { this.featureName = featureName; diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java b/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java index d5670df7e47..64786a25bdb 100644 --- a/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java +++ b/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java @@ -42,6 +42,7 @@ import javafx.scene.layout.HeaderBar; import com.sun.glass.ui.HeaderButtonMetrics; +import com.sun.javafx.PreviewFeature; import com.sun.javafx.collections.VetoableListDecorator; import com.sun.javafx.collections.TrackableObservableList; import com.sun.javafx.scene.SceneHelper; @@ -454,9 +455,7 @@ public void showAndWait() { private StageStyle style; // default is set in constructor /** - * Specifies the style for this stage. This must be done prior to making - * the stage visible. The style is one of: StageStyle.DECORATED, - * StageStyle.UNDECORATED, StageStyle.TRANSPARENT, or StageStyle.UTILITY. + * Specifies the style for this stage. This must be done prior to making the stage visible. * * @param style the style for this stage. * @@ -465,7 +464,12 @@ public void showAndWait() { * * @defaultValue StageStyle.DECORATED */ + @SuppressWarnings("deprecation") public final void initStyle(StageStyle style) { + switch (style) { + case StageStyle.EXTENDED -> PreviewFeature.STAGE_STYLE_EXTENDED.checkEnabled(); + case StageStyle.EXTENDED_UTILITY -> PreviewFeature.STAGE_STYLE_EXTENDED_UTILITY.checkEnabled(); + } if (hasBeenVisible) { throw new IllegalStateException("Cannot set style once stage has been set visible"); } diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java b/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java index dcc1efe100b..26e21914690 100644 --- a/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java +++ b/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java @@ -125,7 +125,9 @@ public enum StageStyle { * in the title of miniaturized preview windows. * * @since 25 + * @deprecated This is a preview feature which may be changed or removed in a future release. */ + @Deprecated(since = "25") EXTENDED, /** @@ -135,6 +137,8 @@ public enum StageStyle { * If the feature is not supported by the platform, this style downgrades to {@link StageStyle#UTILITY}. * * @since 25 + * @deprecated This is a preview feature which may be changed or removed in a future release. */ + @Deprecated(since = "25") EXTENDED_UTILITY } From 8a5defde9236978b69d4435534eeab0d23a5745f Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Mon, 17 Mar 2025 18:56:24 +0100 Subject: [PATCH 56/73] add preview feature --- .../src/main/java/com/sun/javafx/PreviewFeature.java | 3 ++- .../src/main/java/javafx/application/ConditionalFeature.java | 2 ++ .../src/main/java/javafx/scene/layout/HeaderBar.java | 5 +++++ .../src/main/java/javafx/scene/layout/HeaderButtonType.java | 2 ++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/modules/javafx.base/src/main/java/com/sun/javafx/PreviewFeature.java b/modules/javafx.base/src/main/java/com/sun/javafx/PreviewFeature.java index 86679d8560f..811021a1240 100644 --- a/modules/javafx.base/src/main/java/com/sun/javafx/PreviewFeature.java +++ b/modules/javafx.base/src/main/java/com/sun/javafx/PreviewFeature.java @@ -39,7 +39,8 @@ public enum PreviewFeature { // Add preview feature constants here: // TEST_FEATURE("Test Feature") STAGE_STYLE_EXTENDED("StageStyle.EXTENDED"), - STAGE_STYLE_EXTENDED_UTILITY("StageStyle.EXTENDED_UTILITY"); + STAGE_STYLE_EXTENDED_UTILITY("StageStyle.EXTENDED_UTILITY"), + HEADER_BAR("HeaderBar"); PreviewFeature(String featureName) { this.featureName = featureName; diff --git a/modules/javafx.graphics/src/main/java/javafx/application/ConditionalFeature.java b/modules/javafx.graphics/src/main/java/javafx/application/ConditionalFeature.java index 300e36d76da..49d62fbf08a 100644 --- a/modules/javafx.graphics/src/main/java/javafx/application/ConditionalFeature.java +++ b/modules/javafx.graphics/src/main/java/javafx/application/ConditionalFeature.java @@ -158,7 +158,9 @@ public enum ConditionalFeature { * This feature is currently supported on Windows, Linux, and macOS. * * @since 25 + * @deprecated This is a preview feature which may be changed or removed in a future release. */ + @Deprecated(since = "25") EXTENDED_WINDOW, /** diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java index aad7397a1e1..9a8c39a4730 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java @@ -26,6 +26,7 @@ package javafx.scene.layout; import com.sun.glass.ui.HeaderButtonMetrics; +import com.sun.javafx.PreviewFeature; import com.sun.javafx.geom.Vec2d; import com.sun.javafx.scene.layout.HeaderButtonBehavior; import com.sun.javafx.stage.StageHelper; @@ -158,7 +159,9 @@ * } * * @since 25 + * @deprecated This is a preview feature which may be changed or removed in a future release. */ +@Deprecated(since = "25") public class HeaderBar extends Region { private static final Dimension2D EMPTY = new Dimension2D(0, 0); @@ -316,6 +319,8 @@ public static Insets getMargin(Node child) { * Creates a new {@code HeaderBar}. */ public HeaderBar() { + PreviewFeature.HEADER_BAR.checkEnabled(); + // Inflate the minHeight property. This is important so that we can track whether a stylesheet or // user code changes the property value before we set it to the height of the native title bar. minHeightProperty(); diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderButtonType.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderButtonType.java index 84518ae1654..0136715ebe5 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderButtonType.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderButtonType.java @@ -34,8 +34,10 @@ * will summon snap layouts. * * @since 25 + * @deprecated This is a preview feature which may be changed or removed in a future release. * @see HeaderBar#setButtonType(Node, HeaderButtonType) */ +@Deprecated(since = "25") public enum HeaderButtonType { /** From 30eb29b733c631822b62a73f0dc9c437ed84e11a Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Mon, 17 Mar 2025 19:48:52 +0100 Subject: [PATCH 57/73] enable preview features in tests --- .../main/java/com/sun/javafx/PreviewFeature.java | 14 +++++++++++++- .../test/javafx/scene/layout/HeaderBarTest.java | 3 +++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/modules/javafx.base/src/main/java/com/sun/javafx/PreviewFeature.java b/modules/javafx.base/src/main/java/com/sun/javafx/PreviewFeature.java index 811021a1240..7eb748a0e37 100644 --- a/modules/javafx.base/src/main/java/com/sun/javafx/PreviewFeature.java +++ b/modules/javafx.base/src/main/java/com/sun/javafx/PreviewFeature.java @@ -54,6 +54,16 @@ public enum PreviewFeature { private static final boolean enabled = Boolean.getBoolean(ENABLE_PREVIEW_PROPERTY); private static final boolean suppressWarning = Boolean.getBoolean(SUPPRESS_WARNING_PROPERTY); private static final Set enabledFeatures = new HashSet<>(); + private static boolean enabledForTesting; + + /** + * Enables preview features and suppresses the warning. + *

+ * This method is only used for testing purposes. + */ + public static void enableForTesting() { + enabledForTesting = true; + } /** * Verifies that preview features are enabled, and throws an exception otherwise. @@ -64,7 +74,9 @@ public enum PreviewFeature { * @throws RuntimeException if preview features are not enabled */ public void checkEnabled() { - if (!enabled) { + if (enabledForTesting) { + // do nothing + } else if (!enabled) { throw new RuntimeException(""" %s is a preview feature of JavaFX %s. Preview features may be removed in a future release, or upgraded to permanent features of JavaFX. diff --git a/modules/javafx.graphics/src/test/java/test/javafx/scene/layout/HeaderBarTest.java b/modules/javafx.graphics/src/test/java/test/javafx/scene/layout/HeaderBarTest.java index 52a71684051..ca5b0983f84 100644 --- a/modules/javafx.graphics/src/test/java/test/javafx/scene/layout/HeaderBarTest.java +++ b/modules/javafx.graphics/src/test/java/test/javafx/scene/layout/HeaderBarTest.java @@ -25,6 +25,7 @@ package test.javafx.scene.layout; +import com.sun.javafx.PreviewFeature; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; import javafx.geometry.Dimension2D; @@ -42,12 +43,14 @@ import static org.junit.jupiter.api.Assertions.*; +@SuppressWarnings("deprecation") public class HeaderBarTest { HeaderBar headerBar; @BeforeEach void setup() { + PreviewFeature.enableForTesting(); headerBar = new HeaderBar(); } From f870493746287f4c45d2a98af92eaf362655a71d Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Sun, 23 Mar 2025 09:42:57 +0100 Subject: [PATCH 58/73] tweak header button scaling at 100% --- .../main/java/com/sun/glass/ui/win/WinHeaderButtonOverlay.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinHeaderButtonOverlay.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinHeaderButtonOverlay.java index 96a0d8f0484..95a557ddd55 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinHeaderButtonOverlay.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinHeaderButtonOverlay.java @@ -47,7 +47,7 @@ public class WinHeaderButtonOverlay extends HeaderButtonOverlay { * to an additional scaling factor of 1. */ private static final double[][] SCALE_FACTORS = new double[][] { - { 1.0, 1.0 }, + { 1.0, 1.15 }, { 1.25, 1.1 }, { 1.5, 1.15 }, { 1.75, 1.0 }, From a2b5df12a3876f736dcc55eb6fdfbb04bfbf4d5b Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Sun, 23 Mar 2025 10:07:19 +0100 Subject: [PATCH 59/73] documentation --- .../src/main/java/com/sun/glass/ui/HeaderButtonMetrics.java | 3 +++ .../src/main/java/com/sun/glass/ui/HeaderButtonOverlay.java | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/HeaderButtonMetrics.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/HeaderButtonMetrics.java index da8da8dc8bf..1860cc9f24d 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/HeaderButtonMetrics.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/HeaderButtonMetrics.java @@ -26,6 +26,7 @@ package com.sun.glass.ui; import javafx.geometry.Dimension2D; +import javafx.scene.layout.HeaderBar; import javafx.stage.StageStyle; import java.util.Objects; @@ -35,6 +36,8 @@ * @param leftInset the size of the left inset * @param rightInset the size of the right inset * @param minHeight the minimum height of the window buttons + * @see HeaderButtonOverlay + * @see HeaderBar */ public record HeaderButtonMetrics(Dimension2D leftInset, Dimension2D rightInset, double minHeight) { diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/HeaderButtonOverlay.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/HeaderButtonOverlay.java index 6fed67941d8..859242d1296 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/HeaderButtonOverlay.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/HeaderButtonOverlay.java @@ -26,6 +26,8 @@ package com.sun.glass.ui; import com.sun.glass.events.MouseEvent; +import com.sun.glass.ui.gtk.GtkWindow; +import com.sun.glass.ui.win.WinWindow; import com.sun.javafx.binding.ObjectConstant; import com.sun.javafx.util.Utils; import javafx.beans.property.DoubleProperty; @@ -138,6 +140,10 @@ * * * + * + * @implNote This control is used by the {@link WinWindow#createHeaderButtonOverlay()} and + * {@link GtkWindow#createHeaderButtonOverlay()} implementations for {@link StageStyle#EXTENDED} + * windows. It is not used by the macOS implementation. */ public class HeaderButtonOverlay extends Region { From 50f9c75a60b6aa22ae0edc49b7cbb467a12f92c2 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Sun, 23 Mar 2025 10:16:53 +0100 Subject: [PATCH 60/73] documentation --- .../src/main/java/com/sun/glass/ui/HeaderButtonOverlay.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/HeaderButtonOverlay.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/HeaderButtonOverlay.java index 859242d1296..54a2d14a910 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/HeaderButtonOverlay.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/HeaderButtonOverlay.java @@ -26,8 +26,6 @@ package com.sun.glass.ui; import com.sun.glass.events.MouseEvent; -import com.sun.glass.ui.gtk.GtkWindow; -import com.sun.glass.ui.win.WinWindow; import com.sun.javafx.binding.ObjectConstant; import com.sun.javafx.util.Utils; import javafx.beans.property.DoubleProperty; @@ -141,8 +139,8 @@ * * * - * @implNote This control is used by the {@link WinWindow#createHeaderButtonOverlay()} and - * {@link GtkWindow#createHeaderButtonOverlay()} implementations for {@link StageStyle#EXTENDED} + * @implNote This control is used by the WinWindow.createHeaderButtonOverlay() and + * GtkWindow.createHeaderButtonOverlay() implementations for {@link StageStyle#EXTENDED} * windows. It is not used by the macOS implementation. */ public class HeaderButtonOverlay extends Region { From 88e501638fff9227e929c60dcc60f130c3ec33ec Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Sun, 6 Apr 2025 11:23:16 +0200 Subject: [PATCH 61/73] doc update --- .../java/javafx/scene/layout/HeaderBar.java | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java index 9a8c39a4730..d3a123d04ff 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java @@ -56,11 +56,12 @@ import javafx.util.Subscription; /** - * A client-area header bar that is used as a replacement for the system-provided header bar in stages - * with the {@link StageStyle#EXTENDED} style. This class enables the click-and-drag to move and - * double-click to maximize behaviors that are usually afforded by system-provided header bars. - * The entire {@code HeaderBar} background is draggable by default, but its content is not. Applications - * can specify draggable content nodes of the {@code HeaderBar} with the {@link #setDraggable} method. + * A client-area header bar that is used as a replacement for the system-provided header bar in + * {@link StageStyle#EXTENDED} or {@link StageStyle#EXTENDED_UTILITY} stages. This class enables the + * click-and-drag to move and double-click to maximize behaviors that are usually afforded + * by system-provided header bars. The entire {@code HeaderBar} background is draggable by default, but its + * content is not. Applications can specify draggable content nodes of the {@code HeaderBar} with the + * {@link #setDraggable} method. *

* {@code HeaderBar} is a layout container that allows applications to place scene graph nodes in three areas: * {@link #leadingProperty() leading}, {@link #centerProperty() center}, and {@link #trailingProperty() trailing}. @@ -86,13 +87,13 @@ * Applications can specify the preferred height for system-provided header buttons by setting the static * {@link #setPrefButtonHeight(Stage, double)} property on the {@code Stage} associated with the header bar. * This can be used to achieve a more cohesive visual appearance by having the system-provided header buttons - * match the height of the header bar. + * match the height of the client-area header bar. * *

Custom header buttons

* If more control over the header buttons is desired, applications can opt out of the system-provided header - * buttons by setting {@link #setPrefButtonHeight(Stage, double)} to zero and provide custom header buttons - * instead. Any JavaFX control can be used as a custom header button by setting its semantic type with the - * {@link #setButtonType(Node, HeaderButtonType)} method. + * buttons by setting {@link #setPrefButtonHeight(Stage, double)} to zero and place custom header buttons in + * the JavaFX scene graph instead. Any JavaFX control can be used as a custom header button by setting its + * semantic type with the {@link #setButtonType(Node, HeaderButtonType)} method. * *

System menu

* Some platforms support a system menu that can be summoned by right-clicking the draggable area. @@ -849,8 +850,8 @@ private double getAreaHeight(Node child, double width, boolean minimum) { if (child != null && child.isManaged()) { Insets margin = getNodeMargin(child); return minimum - ? computeChildMinAreaHeight(child, -1, margin, width) - : computeChildPrefAreaHeight(child, -1, margin, width); + ? computeChildMinAreaHeight(child, -1, margin, width, false) + : computeChildPrefAreaHeight(child, -1, margin, width, false); } return 0; From 63215fc8f8ab9bf8700df1adcc1bd700a3486d80 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Mon, 7 Apr 2025 21:38:59 +0200 Subject: [PATCH 62/73] remove import --- .../manual/monkey/src/com/oracle/tools/fx/monkey/MainWindow.java | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/manual/monkey/src/com/oracle/tools/fx/monkey/MainWindow.java b/tests/manual/monkey/src/com/oracle/tools/fx/monkey/MainWindow.java index d1a512a4ccd..3ec673415a7 100644 --- a/tests/manual/monkey/src/com/oracle/tools/fx/monkey/MainWindow.java +++ b/tests/manual/monkey/src/com/oracle/tools/fx/monkey/MainWindow.java @@ -51,7 +51,6 @@ import com.oracle.tools.fx.monkey.tools.EmbeddedFxTextArea; import com.oracle.tools.fx.monkey.tools.EmbeddedJTextAreaWindow; import com.oracle.tools.fx.monkey.tools.KeyboardEventViewer; -import com.oracle.tools.fx.monkey.tools.ModalWindow; import com.oracle.tools.fx.monkey.tools.Native2AsciiPane; import com.oracle.tools.fx.monkey.tools.StageTesterWindow; import com.oracle.tools.fx.monkey.tools.SystemInfoViewer; From a3497b26a3caf32156f6bb89cf8932156cf5d989 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Wed, 9 Apr 2025 09:32:11 +0200 Subject: [PATCH 63/73] Remove StageStyle.EXTENDED_UTILITY --- .../main/java/com/sun/javafx/PreviewFeature.java | 1 - .../com/sun/javafx/tk/quantum/WindowStage.java | 5 ----- .../javafx/application/ConditionalFeature.java | 2 +- .../main/java/javafx/scene/layout/HeaderBar.java | 11 +++++------ .../src/main/java/javafx/stage/Stage.java | 5 ++--- .../src/main/java/javafx/stage/StageStyle.java | 14 +------------- 6 files changed, 9 insertions(+), 29 deletions(-) diff --git a/modules/javafx.base/src/main/java/com/sun/javafx/PreviewFeature.java b/modules/javafx.base/src/main/java/com/sun/javafx/PreviewFeature.java index 7eb748a0e37..6b371fc0f97 100644 --- a/modules/javafx.base/src/main/java/com/sun/javafx/PreviewFeature.java +++ b/modules/javafx.base/src/main/java/com/sun/javafx/PreviewFeature.java @@ -39,7 +39,6 @@ public enum PreviewFeature { // Add preview feature constants here: // TEST_FEATURE("Test Feature") STAGE_STYLE_EXTENDED("StageStyle.EXTENDED"), - STAGE_STYLE_EXTENDED_UTILITY("StageStyle.EXTENDED_UTILITY"), HEADER_BAR("HeaderBar"); PreviewFeature(String featureName) { diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/WindowStage.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/WindowStage.java index db977620055..2fd0651a487 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/WindowStage.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/WindowStage.java @@ -147,8 +147,6 @@ private void initPlatformWindow() { style = StageStyle.DECORATED; } else if (style == StageStyle.EXTENDED && !app.supportsExtendedWindows()) { style = StageStyle.DECORATED; - } else if (style == StageStyle.EXTENDED_UTILITY && !app.supportsExtendedWindows()) { - style = StageStyle.UTILITY; } switch (style) { @@ -161,9 +159,6 @@ private void initPlatformWindow() { case EXTENDED: windowMask |= Window.EXTENDED | Window.CLOSABLE | Window.MINIMIZABLE | Window.MAXIMIZABLE; break; - case EXTENDED_UTILITY: - windowMask |= Window.EXTENDED | Window.CLOSABLE | Window.UTILITY; - break; case UTILITY: windowMask |= Window.TITLED | Window.UTILITY | Window.CLOSABLE; break; diff --git a/modules/javafx.graphics/src/main/java/javafx/application/ConditionalFeature.java b/modules/javafx.graphics/src/main/java/javafx/application/ConditionalFeature.java index 49d62fbf08a..260f21928c8 100644 --- a/modules/javafx.graphics/src/main/java/javafx/application/ConditionalFeature.java +++ b/modules/javafx.graphics/src/main/java/javafx/application/ConditionalFeature.java @@ -153,7 +153,7 @@ public enum ConditionalFeature { UNIFIED_WINDOW, /** - * Indicates that a system supports {@link StageStyle#EXTENDED} and {@link StageStyle#EXTENDED_UTILITY}. + * Indicates that a system supports {@link StageStyle#EXTENDED}. *

* This feature is currently supported on Windows, Linux, and macOS. * diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java index d3a123d04ff..1fde6af4c53 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java @@ -56,12 +56,11 @@ import javafx.util.Subscription; /** - * A client-area header bar that is used as a replacement for the system-provided header bar in - * {@link StageStyle#EXTENDED} or {@link StageStyle#EXTENDED_UTILITY} stages. This class enables the - * click-and-drag to move and double-click to maximize behaviors that are usually afforded - * by system-provided header bars. The entire {@code HeaderBar} background is draggable by default, but its - * content is not. Applications can specify draggable content nodes of the {@code HeaderBar} with the - * {@link #setDraggable} method. + * A client-area header bar that is used as a replacement for the system-provided header bar in stages + * with the {@link StageStyle#EXTENDED} style. This class enables the click-and-drag to move and + * double-click to maximize behaviors that are usually afforded by system-provided header bars. + * The entire {@code HeaderBar} background is draggable by default, but its content is not. Applications + * can specify draggable content nodes of the {@code HeaderBar} with the {@link #setDraggable} method. *

* {@code HeaderBar} is a layout container that allows applications to place scene graph nodes in three areas: * {@link #leadingProperty() leading}, {@link #centerProperty() center}, and {@link #trailingProperty() trailing}. diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java b/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java index ff189942d27..3731503e31c 100644 --- a/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java +++ b/modules/javafx.graphics/src/main/java/javafx/stage/Stage.java @@ -468,9 +468,8 @@ public void showAndWait() { */ @SuppressWarnings("deprecation") public final void initStyle(StageStyle style) { - switch (style) { - case StageStyle.EXTENDED -> PreviewFeature.STAGE_STYLE_EXTENDED.checkEnabled(); - case StageStyle.EXTENDED_UTILITY -> PreviewFeature.STAGE_STYLE_EXTENDED_UTILITY.checkEnabled(); + if (style == StageStyle.EXTENDED) { + PreviewFeature.STAGE_STYLE_EXTENDED.checkEnabled(); } if (hasBeenVisible) { throw new IllegalStateException("Cannot set style once stage has been set visible"); diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java b/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java index 26e21914690..aa05ec4a2ad 100644 --- a/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java +++ b/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java @@ -128,17 +128,5 @@ public enum StageStyle { * @deprecated This is a preview feature which may be changed or removed in a future release. */ @Deprecated(since = "25") - EXTENDED, - - /** - * Defines a {@code Stage} style with the semantics of {@link #UTILITY} and the appearance of {@link #EXTENDED}. - *

- * This is a conditional feature, to check if it is supported see {@link Platform#isSupported(ConditionalFeature)}. - * If the feature is not supported by the platform, this style downgrades to {@link StageStyle#UTILITY}. - * - * @since 25 - * @deprecated This is a preview feature which may be changed or removed in a future release. - */ - @Deprecated(since = "25") - EXTENDED_UTILITY + EXTENDED } From e9aeaefdd6a36e15c120b8d4c50bf46ee10e311a Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Sat, 12 Apr 2025 16:50:36 +0200 Subject: [PATCH 64/73] don't show a right-click system menu in full-screen mode --- .../src/main/java/com/sun/glass/ui/gtk/GtkView.java | 4 ++-- .../src/main/java/com/sun/glass/ui/win/WinView.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkView.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkView.java index 6aa25eb95c6..f020fd1bcbe 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkView.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkView.java @@ -143,11 +143,11 @@ protected void notifyInputMethodLinux(String str, int commitLength, int cursor, protected void notifyMenu(int x, int y, int xAbs, int yAbs, boolean isKeyboardTrigger) { // If all of the following conditions are satisfied, we open a system menu at the specified coordinates: // 1. The application didn't consume the menu event. - // 2. The window is an EXTENDED window. + // 2. The window is an EXTENDED window and is not in full-screen mode. // 3. The menu event occurred on a draggable area. if (!handleMenuEvent(x, y, xAbs, yAbs, isKeyboardTrigger)) { var window = (GtkWindow)getWindow(); - if (!window.isExtendedWindow()) { + if (!window.isExtendedWindow() || isInFullscreen()) { return; } diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinView.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinView.java index 0a7e84d1e72..52ed202126d 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinView.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/win/WinView.java @@ -103,11 +103,11 @@ protected void notifyResize(int width, int height) { protected void notifyMenu(int x, int y, int xAbs, int yAbs, boolean isKeyboardTrigger) { // If all of the following conditions are satisfied, we open a system menu at the specified coordinates: // 1. The application didn't consume the menu event. - // 2. The window is an EXTENDED window. + // 2. The window is an EXTENDED window and is not in full-screen mode. // 3. The menu event occurred on a draggable area. if (!handleMenuEvent(x, y, xAbs, yAbs, isKeyboardTrigger)) { var window = (WinWindow)getWindow(); - if (!window.isExtendedWindow()) { + if (!window.isExtendedWindow() || isInFullscreen()) { return; } From 379c01ec83cd3317ccc226610c7f6a8ef2f11db5 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Tue, 15 Apr 2025 12:52:56 +0200 Subject: [PATCH 65/73] documentation --- .../src/main/java/javafx/scene/layout/HeaderBar.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java index 1fde6af4c53..3d3ad1098fc 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java @@ -40,6 +40,7 @@ import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.value.ObservableValue; import javafx.css.StyleableDoubleProperty; +import javafx.event.Event; import javafx.geometry.Dimension2D; import javafx.geometry.HPos; import javafx.geometry.Insets; @@ -96,8 +97,12 @@ * *

System menu

* Some platforms support a system menu that can be summoned by right-clicking the draggable area. - * This platform-provided menu will only be shown if the {@link ContextMenuEvent#CONTEXT_MENU_REQUESTED} - * event that is targeted at the header bar is not consumed by the application. + * The system menu will not be shown when: + *
    + *
  1. the {@code Stage} is in {@link Stage#fullScreenProperty() full-screen mode}, or + *
  2. the {@code HeaderBar} has {@link Event#consume() consumed} the + * {@link ContextMenuEvent#CONTEXT_MENU_REQUESTED} event. + *
* *

Layout constraints

* The {@code leading} and {@code trailing} children will be resized to their preferred widths and extend the From 394a039a0c0c7648f094c86fa8e5a5f0dfe60d19 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Thu, 24 Apr 2025 16:16:51 +0200 Subject: [PATCH 66/73] review comments --- .../scene/layout/HeaderButtonBehavior.java | 36 ++++++++++--------- .../com/sun/javafx/tk/TKSceneListener.java | 5 +++ .../src/main/native-glass/win/GlassWindow.cpp | 2 +- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/layout/HeaderButtonBehavior.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/layout/HeaderButtonBehavior.java index e787cbdbb63..31689e48d07 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/layout/HeaderButtonBehavior.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/layout/HeaderButtonBehavior.java @@ -37,7 +37,6 @@ import javafx.stage.Stage; import javafx.util.Subscription; import java.util.Objects; -import java.util.Optional; public final class HeaderButtonBehavior implements EventHandler { @@ -86,30 +85,33 @@ public void handle(MouseEvent event) { return; } + Stage stage = getStage(); + if (stage == null) { + return; + } + switch (type) { - case CLOSE -> getStage().ifPresent(Stage::close); - case ICONIFY -> getStage().ifPresent(stage -> stage.setIconified(true)); - case MAXIMIZE -> getStage().ifPresent(stage -> { - // On macOS, a non-modal window is put into full-screen mode when the maximize button is clicked, - // but enlarged to cover the desktop when the option key is pressed at the same time. - if (PlatformUtil.isMac() && stage.getModality() == Modality.NONE && !event.isAltDown()) { - stage.setFullScreen(!stage.isFullScreen()); - } else { - stage.setMaximized(!stage.isMaximized()); - } - }); + case CLOSE -> stage.close(); + case ICONIFY -> stage.setIconified(true); + case MAXIMIZE -> { + // On macOS, a non-modal window is put into full-screen mode when the maximize button is clicked, + // but enlarged to cover the desktop when the option key is pressed at the same time. + if (PlatformUtil.isMac() && stage.getModality() == Modality.NONE && !event.isAltDown()) { + stage.setFullScreen(!stage.isFullScreen()); + } else { + stage.setMaximized(!stage.isMaximized()); + } + } } } - private Optional getStage() { + private Stage getStage() { Scene scene = node.getScene(); if (scene == null) { - return Optional.empty(); + return null; } - return scene.getWindow() instanceof Stage stage - ? Optional.of(stage) - : Optional.empty(); + return scene.getWindow() instanceof Stage stage ? stage : null; } private void onResizableChanged(Boolean resizable) { diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKSceneListener.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKSceneListener.java index 988bb0db211..f497c5769be 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKSceneListener.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKSceneListener.java @@ -84,6 +84,11 @@ public void scrollEvent( boolean _altDown, boolean _metaDown, boolean _direct, boolean _inertia); + /** + * Pass a menu event to the scene to handle. + * + * @return {@code true} if the event was handled by the scene, {@code false} otherwise + */ public boolean menuEvent(double x, double y, double xAbs, double yAbs, boolean isKeyboardTrigger); diff --git a/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp b/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp index 3f180e047d8..85cd71825ce 100644 --- a/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp +++ b/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp @@ -613,7 +613,7 @@ bool GlassWindow::HandleMouseEvents(UINT msg, WPARAM wParam, LPARAM lParam) if (handled && msg == WM_RBUTTONUP) { // By default, DefWindowProc() sends WM_CONTEXTMENU from WM_LBUTTONUP // Since DefWindowProc() is not called, call the mouse menu handler directly - HandleViewMenuEvent(GetHWND(), WM_CONTEXTMENU, (WPARAM) GetHWND(), ::GetMessagePos ()); + HandleViewMenuEvent(GetHWND(), WM_CONTEXTMENU, (WPARAM) GetHWND(), ::GetMessagePos()); //::DefWindowProc(GetHWND(), msg, wParam, lParam); } From b6f8f9bc970b63fd6d524b2116ad5839dd42cd1c Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Fri, 25 Apr 2025 08:13:43 +0200 Subject: [PATCH 67/73] documentation --- .../src/main/java/javafx/stage/StageStyle.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java b/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java index ecac9c45d82..3f58f428475 100644 --- a/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java +++ b/modules/javafx.graphics/src/main/java/javafx/stage/StageStyle.java @@ -115,10 +115,10 @@ public enum StageStyle { * } * *

Color scheme

- * The color scheme of the default header buttons is adjusted to the {@link Scene#fillProperty() fill} - * of the {@code Scene} to remain easily recognizable. Applications should set the scene fill to a color - * that matches the brightness of the user interface, even if the scene fill is not visible because it - * is obscured by other controls. + * The color scheme of the default header buttons is automatically adjusted to remain easily recognizable + * by inspecting the {@link Scene#fillProperty() Scene.fill} property to gauge the brightness of the user + * interface. Applications should set the scene fill to a color that matches the user interface of the header + * bar area, even if the scene fill is not visible because it is obscured by other controls. * *

Custom header buttons

* If more control over the header buttons is desired, applications can opt out of the default header buttons From 7dc8f5f158018594f4ff08ef87323572dac08f59 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Fri, 25 Apr 2025 08:35:24 +0200 Subject: [PATCH 68/73] add MenuBar to stage tester --- .../fx/monkey/tools/StageTesterWindow.java | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/tests/manual/monkey/src/com/oracle/tools/fx/monkey/tools/StageTesterWindow.java b/tests/manual/monkey/src/com/oracle/tools/fx/monkey/tools/StageTesterWindow.java index ece3bb7084f..a9006db05ab 100644 --- a/tests/manual/monkey/src/com/oracle/tools/fx/monkey/tools/StageTesterWindow.java +++ b/tests/manual/monkey/src/com/oracle/tools/fx/monkey/tools/StageTesterWindow.java @@ -37,6 +37,9 @@ import javafx.scene.control.CheckBox; import javafx.scene.control.ComboBox; import javafx.scene.control.Label; +import javafx.scene.control.Menu; +import javafx.scene.control.MenuBar; +import javafx.scene.control.MenuItem; import javafx.scene.control.SplitPane; import javafx.scene.control.TextField; import javafx.scene.layout.Background; @@ -180,6 +183,23 @@ private Parent createSimpleHeaderBarRoot(Stage stage, boolean customWindowButton sizeComboBox.valueProperty().subscribe(event -> updateMinHeight.run()); headerBar.minSystemHeightProperty().subscribe(event -> updateMinHeight.run()); + var menuBar = new MenuBar( + new Menu("File", null, + new MenuItem("New"), + new MenuItem("Open"), + new MenuItem("Save"), + new MenuItem("Close")), + new Menu("Edit", null, + new MenuItem("Undo"), + new MenuItem("Redo"), + new MenuItem("Copy"), + new MenuItem("Paste"))); + + var leadingContent = new HBox(menuBar); + HeaderBar.setDraggable(leadingContent, true); + HeaderBar.setDraggable(menuBar, false); + headerBar.setLeading(leadingContent); + if (customWindowButtons) { HeaderBar.setPrefButtonHeight(stage, 0); } else { @@ -199,7 +219,9 @@ private Parent createSimpleHeaderBarRoot(Stage stage, boolean customWindowButton } }); - headerBar.setLeading(adaptiveButtonHeight); + HBox.setMargin(adaptiveButtonHeight, new Insets(4)); + HeaderBar.setDraggable(adaptiveButtonHeight, false); + leadingContent.getChildren().add(adaptiveButtonHeight); } var trailingNodes = new HBox(sizeComboBox); From d1aa6b3b2cd0a6aa5e73e6002a737aa80b801485 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Sat, 26 Apr 2025 21:32:31 +0200 Subject: [PATCH 69/73] add HeaderDragType --- .../src/main/java/javafx/scene/Scene.java | 30 +++- .../java/javafx/scene/layout/HeaderBar.java | 27 ++-- .../javafx/scene/layout/HeaderDragType.java | 62 ++++++++ .../javafx/scene/layout/HeaderBarTest.java | 148 ++++++++++++++++++ .../fx/monkey/tools/StageTesterWindow.java | 5 +- 5 files changed, 246 insertions(+), 26 deletions(-) create mode 100644 modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderDragType.java diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java b/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java index c419c9666b1..e2c7fc3657a 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java @@ -80,8 +80,10 @@ import javafx.scene.input.*; import javafx.scene.layout.HeaderBar; import javafx.scene.layout.HeaderButtonType; +import javafx.scene.layout.HeaderDragType; import javafx.scene.paint.Color; import javafx.scene.paint.Paint; +import javafx.scene.transform.NonInvertibleTransformException; import javafx.stage.PopupWindow; import javafx.stage.Stage; import javafx.stage.StageStyle; @@ -3065,15 +3067,25 @@ public HeaderAreaType pickHeaderArea(double x, double y) { return null; } - pickRay.set(x, y, 1, 0, Double.POSITIVE_INFINITY); - var pickResultChooser = new PickResultChooser(); - root.pickNode(pickRay, pickResultChooser); - Node intersectedNode = pickResultChooser.getIntersectedNode(); - Boolean draggable = intersectedNode instanceof HeaderBar ? true : null; + try { + Point2D p = root.getLocalToSceneTransform().inverseTransform(x, y); + pickRay.set(p.getX(), p.getY(), 1, 0, Double.POSITIVE_INFINITY); + } catch (NonInvertibleTransformException e) { + return null; + } + + var chooser = new PickResultChooser(); + root.pickNode(pickRay, chooser); + Node intersectedNode = chooser.getIntersectedNode(); + HeaderDragType dragType = intersectedNode instanceof HeaderBar ? HeaderDragType.DRAGGABLE : null; while (intersectedNode != null) { if (intersectedNode instanceof HeaderBar) { - return draggable == Boolean.TRUE ? HeaderAreaType.DRAGBAR : null; + return dragType == HeaderDragType.DRAGGABLE_SUBTREE + || dragType == HeaderDragType.DRAGGABLE + || HeaderBar.getDragType(chooser.getIntersectedNode()) == HeaderDragType.DRAGGABLE + ? HeaderAreaType.DRAGBAR + : null; } if (HeaderBar.getButtonType(intersectedNode) instanceof HeaderButtonType type) { @@ -3084,8 +3096,10 @@ public HeaderAreaType pickHeaderArea(double x, double y) { }; } - if (draggable == null && HeaderBar.isDraggable(intersectedNode) instanceof Boolean value) { - draggable = value; + if (dragType == null + && HeaderBar.getDragType(intersectedNode) instanceof HeaderDragType type + && type != HeaderDragType.DRAGGABLE) { + dragType = type; } intersectedNode = intersectedNode.getParent(); diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java index 3d3ad1098fc..af567149c25 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java @@ -61,7 +61,8 @@ * with the {@link StageStyle#EXTENDED} style. This class enables the click-and-drag to move and * double-click to maximize behaviors that are usually afforded by system-provided header bars. * The entire {@code HeaderBar} background is draggable by default, but its content is not. Applications - * can specify draggable content nodes of the {@code HeaderBar} with the {@link #setDraggable} method. + * can specify draggable content nodes of the {@code HeaderBar} with the {@link #setDragType(Node, HeaderDragType)} + * method. *

* {@code HeaderBar} is a layout container that allows applications to place scene graph nodes in three areas: * {@link #leadingProperty() leading}, {@link #centerProperty() center}, and {@link #trailingProperty() trailing}. @@ -170,35 +171,31 @@ public class HeaderBar extends Region { private static final Dimension2D EMPTY = new Dimension2D(0, 0); - private static final String DRAGGABLE = "headerbar-draggable"; + private static final String DRAG_TYPE = "headerbar-drag-type"; private static final String BUTTON_TYPE = "headerbar-button-type"; private static final String ALIGNMENT = "headerbar-alignment"; private static final String MARGIN = "headerbar-margin"; /** - * Specifies whether the child and its subtree is a draggable part of the {@code HeaderBar}. + * Specifies whether the child is a draggable part of the {@code HeaderBar}. *

- * If set to a non-null value, the value will apply for the entire subtree of the child unless - * another node in the subtree specifies a different value. Setting the value to {@code null} - * will remove the flag. + * Setting the value to {@code null} will remove the flag. * * @param child the child node - * @param value a {@code Boolean} value indicating whether the child and its subtree is draggable, - * or {@code null} to remove the flag + * @param value the {@code HeaderDragType}, or {@code null} to remove the flag */ - public static void setDraggable(Node child, Boolean value) { - Pane.setConstraint(child, DRAGGABLE, value); + public static void setDragType(Node child, HeaderDragType value) { + Pane.setConstraint(child, DRAG_TYPE, value); } /** - * Returns whether the child and its subtree is a draggable part of the {@code HeaderBar}. + * Returns whether the child is a draggable part of the {@code HeaderBar}. * * @param child the child node - * @return a {@code Boolean} value indicating whether the child and its subtree is draggable, - * or {@code null} if not set + * @return the {@code HeaderDragType}, or {@code null} if not set */ - public static Boolean isDraggable(Node child) { - return (Boolean)Pane.getConstraint(child, DRAGGABLE); + public static HeaderDragType getDragType(Node child) { + return (HeaderDragType)Pane.getConstraint(child, DRAG_TYPE); } /** diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderDragType.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderDragType.java new file mode 100644 index 00000000000..409489e8485 --- /dev/null +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderDragType.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javafx.scene.layout; + +import javafx.scene.Node; + +/** + * Specifies whether a node is a draggable part of a {@link HeaderBar}. + * + * @since 25 + * @deprecated This is a preview feature which may be changed or removed in a future release. + * @see HeaderBar#setDragType(Node, HeaderDragType) + */ +@Deprecated(since = "25") +public enum HeaderDragType { + + /** + * The node is not a draggable part of the {@code HeaderBar}. + *

+ * If the node inherits {@link #DRAGGABLE_SUBTREE} from its parent, the inheritance stops and + * descendants of the node will not inherit {@code DRAGGABLE_SUBTREE}. + */ + NONE, + + /** + * The node is a draggable part of the {@code HeaderBar}. + *

+ * This drag type does not apply to descendants of the node. However, it does not stop an inherited + * {@link #DRAGGABLE_SUBTREE} drag type from being inherited by descendants of the node. + */ + DRAGGABLE, + + /** + * The node and its descendants are a draggable part of the {@code HeaderBar}. + *

+ * This drag type is inherited by descendants of the node until a descendant specifies {@link #NONE}. + */ + DRAGGABLE_SUBTREE +} diff --git a/modules/javafx.graphics/src/test/java/test/javafx/scene/layout/HeaderBarTest.java b/modules/javafx.graphics/src/test/java/test/javafx/scene/layout/HeaderBarTest.java index ca5b0983f84..59c3cdc5fcc 100644 --- a/modules/javafx.graphics/src/test/java/test/javafx/scene/layout/HeaderBarTest.java +++ b/modules/javafx.graphics/src/test/java/test/javafx/scene/layout/HeaderBarTest.java @@ -26,19 +26,27 @@ package test.javafx.scene.layout; import com.sun.javafx.PreviewFeature; +import com.sun.javafx.scene.SceneHelper; +import com.sun.javafx.tk.HeaderAreaType; +import com.sun.javafx.tk.TKSceneListener; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; import javafx.geometry.Dimension2D; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Node; +import javafx.scene.Scene; import javafx.scene.layout.HeaderBar; +import javafx.scene.layout.HeaderDragType; +import javafx.scene.layout.StackPane; import javafx.scene.shape.Rectangle; +import javafx.stage.Stage; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; +import test.com.sun.javafx.pgstub.StubScene; import test.util.ReflectionUtils; import static org.junit.jupiter.api.Assertions.*; @@ -435,4 +443,144 @@ private void assertBounds(double x, double y, double width, double height, Node assertEquals(height, bounds.getHeight()); } } + + @Nested + class PickingTest { + /** + * For picking tests, we use a header bar with four nested boxes, arranged from left to right. + * + *

+         *     0        50       100      150      200
+         *     ┌────────┬───────────────────────────────────┐
+         *     │  (HB)  │  box1  ┌──────────────────────────┤
+         *     │        │        │  box2  ┌─────────────────┤
+         *     │        │        │        │  box3  ┌────────┤
+         *     │        │        │        │        │  box4  │
+         *     ╞════════╧════════╧════════╧════════╧════════╡
+         *     │                                            │
+         * 
+ */ + private static class TestHeaderBar extends HeaderBar { + final Box box4 = new Box(null, 50, 0, 50, 100); + final Box box3 = new Box(box4, 50, 0, 100, 100); + final Box box2 = new Box(box3, 50, 0, 150, 100); + final Box box1 = new Box(box2, 50, 0, 200, 100); + + TestHeaderBar() { + resize(250, 100); + setCenter(box1); + } + } + + private static class Box extends StackPane { + Box(Node child, double x, double y, double width, double height) { + setManaged(false); + resizeRelocate(x, y, width, height); + + if (child != null) { + getChildren().add(child); + } + } + } + + @Test + void pickDraggableNode() { + var headerBar = new TestHeaderBar(); + HeaderBar.setDragType(headerBar.box1, HeaderDragType.DRAGGABLE); + + var scene = new Scene(headerBar, 250, 200); + var stage = new Stage(); + stage.setScene(scene); + stage.show(); + + // 1. HeaderBar is always draggable + assertEquals(HeaderAreaType.DRAGBAR, pickHeaderArea(scene, 10, 10)); + + // 2. box1 is draggable because its drag type is DRAGGABLE + assertEquals(HeaderAreaType.DRAGBAR, pickHeaderArea(scene, 60, 10)); + + // 3. box2/box3/box4 are not draggable, because they don't inherit DRAGGABLE from box1 + assertNull(pickHeaderArea(scene, 110, 10)); + assertNull(pickHeaderArea(scene, 160, 10)); + assertNull(pickHeaderArea(scene, 210, 10)); + } + + @Test + void pickDraggableNodeInSubtree() { + var headerBar = new TestHeaderBar(); + HeaderBar.setDragType(headerBar.box1, HeaderDragType.DRAGGABLE_SUBTREE); + + var scene = new Scene(headerBar, 250, 200); + var stage = new Stage(); + stage.setScene(scene); + stage.show(); + + // 1. HeaderBar is always draggable + assertEquals(HeaderAreaType.DRAGBAR, pickHeaderArea(scene, 10, 10)); + + // 2. box1 is draggable because its drag type is DRAGGABLE_SUBTREE + assertEquals(HeaderAreaType.DRAGBAR, pickHeaderArea(scene, 60, 10)); + + // 3. box2/box3/box4 are draggable, because they inherit DRAGGABLE_SUBTREE from box1 + assertEquals(HeaderAreaType.DRAGBAR, pickHeaderArea(scene, 110, 10)); + assertEquals(HeaderAreaType.DRAGBAR, pickHeaderArea(scene, 160, 10)); + assertEquals(HeaderAreaType.DRAGBAR, pickHeaderArea(scene, 210, 10)); + } + + @Test + void stopInheritanceOfDraggableSubtree() { + var headerBar = new TestHeaderBar(); + HeaderBar.setDragType(headerBar.box1, HeaderDragType.DRAGGABLE_SUBTREE); + HeaderBar.setDragType(headerBar.box3, HeaderDragType.NONE); + + var scene = new Scene(headerBar, 250, 200); + var stage = new Stage(); + stage.setScene(scene); + stage.show(); + + // 1. HeaderBar is always draggable + assertEquals(HeaderAreaType.DRAGBAR, pickHeaderArea(scene, 10, 10)); + + // 2. box1 is draggable because its drag type is DRAGGABLE_SUBTREE + assertEquals(HeaderAreaType.DRAGBAR, pickHeaderArea(scene, 60, 10)); + + // 3. box2 is draggable, because it inherits DRAGGABLE_SUBTREE from box1 + assertEquals(HeaderAreaType.DRAGBAR, pickHeaderArea(scene, 110, 10)); + + // 4. box3/box4 are not draggable, because NONE stops the inherited DRAGGABLE_SUBTREE + assertNull(pickHeaderArea(scene, 160, 10)); + assertNull(pickHeaderArea(scene, 210, 10)); + } + + @Test + void draggableNodeDoesNotStopInheritanceOfDraggableSubtree() { + var headerBar = new TestHeaderBar(); + HeaderBar.setDragType(headerBar.box1, HeaderDragType.DRAGGABLE_SUBTREE); + HeaderBar.setDragType(headerBar.box3, HeaderDragType.DRAGGABLE); + + var scene = new Scene(headerBar, 250, 200); + var stage = new Stage(); + stage.setScene(scene); + stage.show(); + + // 1. HeaderBar is always draggable + assertEquals(HeaderAreaType.DRAGBAR, pickHeaderArea(scene, 10, 10)); + + // 2. box1 is draggable because its drag type is DRAGGABLE_SUBTREE + assertEquals(HeaderAreaType.DRAGBAR, pickHeaderArea(scene, 60, 10)); + + // 3. box2 is draggable, because it inherits DRAGGABLE_SUBTREE from box1 + assertEquals(HeaderAreaType.DRAGBAR, pickHeaderArea(scene, 110, 10)); + + // 4. box3/box4 are draggable, because DRAGGABLE doesn't stop the inherited DRAGGABLE_SUBTREE from box1 + assertEquals(HeaderAreaType.DRAGBAR, pickHeaderArea(scene, 160, 10)); + assertEquals(HeaderAreaType.DRAGBAR, pickHeaderArea(scene, 210, 10)); + } + + private static HeaderAreaType pickHeaderArea(Scene scene, double x, double y) { + var peer = (StubScene)SceneHelper.getPeer(scene); + TKSceneListener listener = ReflectionUtils.getFieldValue(peer, "listener"); + return listener.pickHeaderArea(x, y); + } + } } diff --git a/tests/manual/monkey/src/com/oracle/tools/fx/monkey/tools/StageTesterWindow.java b/tests/manual/monkey/src/com/oracle/tools/fx/monkey/tools/StageTesterWindow.java index a9006db05ab..5f6aa5e77f5 100644 --- a/tests/manual/monkey/src/com/oracle/tools/fx/monkey/tools/StageTesterWindow.java +++ b/tests/manual/monkey/src/com/oracle/tools/fx/monkey/tools/StageTesterWindow.java @@ -47,6 +47,7 @@ import javafx.scene.layout.GridPane; import javafx.scene.layout.HeaderBar; import javafx.scene.layout.HeaderButtonType; +import javafx.scene.layout.HeaderDragType; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; @@ -196,8 +197,7 @@ private Parent createSimpleHeaderBarRoot(Stage stage, boolean customWindowButton new MenuItem("Paste"))); var leadingContent = new HBox(menuBar); - HeaderBar.setDraggable(leadingContent, true); - HeaderBar.setDraggable(menuBar, false); + HeaderBar.setDragType(leadingContent, HeaderDragType.DRAGGABLE); headerBar.setLeading(leadingContent); if (customWindowButtons) { @@ -220,7 +220,6 @@ private Parent createSimpleHeaderBarRoot(Stage stage, boolean customWindowButton }); HBox.setMargin(adaptiveButtonHeight, new Insets(4)); - HeaderBar.setDraggable(adaptiveButtonHeight, false); leadingContent.getChildren().add(adaptiveButtonHeight); } From 94d61ef08bf7ff71f94f526c4e7c6010a0080d2d Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Mon, 28 Apr 2025 09:43:36 +0200 Subject: [PATCH 70/73] doc change --- .../src/main/java/javafx/scene/layout/HeaderBar.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java index af567149c25..084216618d3 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/layout/HeaderBar.java @@ -177,7 +177,8 @@ public class HeaderBar extends Region { private static final String MARGIN = "headerbar-margin"; /** - * Specifies whether the child is a draggable part of the {@code HeaderBar}. + * Specifies the {@code HeaderDragType} of the child, indicating whether it is a draggable + * part of the {@code HeaderBar}. *

* Setting the value to {@code null} will remove the flag. * @@ -189,7 +190,7 @@ public static void setDragType(Node child, HeaderDragType value) { } /** - * Returns whether the child is a draggable part of the {@code HeaderBar}. + * Returns the {@code HeaderDragType} of the specified child. * * @param child the child node * @return the {@code HeaderDragType}, or {@code null} if not set From 544d62eb6dff5ba90e27158dad8a0abe23db8fdf Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Tue, 29 Apr 2025 14:42:14 +0200 Subject: [PATCH 71/73] Fix top resize border on Windows --- .../src/main/native-glass/win/GlassWindow.cpp | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp b/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp index 85cd71825ce..74216836442 100644 --- a/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp +++ b/modules/javafx.graphics/src/main/native-glass/win/GlassWindow.cpp @@ -850,6 +850,7 @@ LRESULT GlassWindow::HandleNCCalcSizeEvent(UINT msg, WPARAM wParam, LPARAM lPara // by adding the border width to the top. bool maximized = (::GetWindowLong(GetHWND(), GWL_STYLE) & WS_MAXIMIZE) != 0; if (maximized && !m_isInFullScreen) { + // Note: there is no SM_CYPADDEDBORDER newSize.top += ::GetSystemMetrics(SM_CXPADDEDBORDER) + ::GetSystemMetrics(SM_CYSIZEFRAME); } @@ -911,8 +912,25 @@ BOOL GlassWindow::HandleNCHitTestEvent(SHORT x, SHORT y, LRESULT& result) JNIEnv* env = GetEnv(); jint res = env->CallIntMethod(m_grefThis, javaIDs.WinWindow.nonClientHitTest, pt.x, pt.y); CheckAndClearException(env); - result = LRESULT(res); + // The left, right, and bottom resize borders are outside of the client area and are provided for free. + // In contrast, the top resize border is not outside, but inside the client area and below user controls. + // For example, if a control extends to the top of the client area, it covers the resize border at that + // location. We know that the cursor is on top of the caption area (and not on top of a control) when + // the nonClientHitTest() function returns HTCAPTION (instead of HTCLIENT). In this case, we apply the + // default resize border. + if (res == HTCAPTION) { + // Note: there is no SM_CYPADDEDBORDER + int topBorderHeight = ::GetSystemMetrics(SM_CXPADDEDBORDER) + ::GetSystemMetrics(SM_CYSIZEFRAME); + RECT windowRect; + + if (::GetWindowRect(GetHWND(), &windowRect) && y < windowRect.top + topBorderHeight) { + result = LRESULT(HTTOP); + return TRUE; + } + } + + result = LRESULT(res); return TRUE; } From c602f504d0886fd0ad4a7623d52e66ee5911d8a2 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Fri, 2 May 2025 08:58:56 +0200 Subject: [PATCH 72/73] simplify header area picking --- .../src/main/java/javafx/scene/Scene.java | 22 +++---------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java b/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java index e2c7fc3657a..0c073b8815a 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java @@ -83,7 +83,6 @@ import javafx.scene.layout.HeaderDragType; import javafx.scene.paint.Color; import javafx.scene.paint.Paint; -import javafx.scene.transform.NonInvertibleTransformException; import javafx.stage.PopupWindow; import javafx.stage.Stage; import javafx.stage.StageStyle; @@ -3058,32 +3057,17 @@ public void touchEventEnd() { } } - private final PickRay pickRay = new PickRay(); - @Override public HeaderAreaType pickHeaderArea(double x, double y) { - Node root = Scene.this.getRoot(); - if (root == null) { - return null; - } - - try { - Point2D p = root.getLocalToSceneTransform().inverseTransform(x, y); - pickRay.set(p.getX(), p.getY(), 1, 0, Double.POSITIVE_INFINITY); - } catch (NonInvertibleTransformException e) { - return null; - } - - var chooser = new PickResultChooser(); - root.pickNode(pickRay, chooser); - Node intersectedNode = chooser.getIntersectedNode(); + PickResult result = pick(x, y); + Node intersectedNode = result.getIntersectedNode(); HeaderDragType dragType = intersectedNode instanceof HeaderBar ? HeaderDragType.DRAGGABLE : null; while (intersectedNode != null) { if (intersectedNode instanceof HeaderBar) { return dragType == HeaderDragType.DRAGGABLE_SUBTREE || dragType == HeaderDragType.DRAGGABLE - || HeaderBar.getDragType(chooser.getIntersectedNode()) == HeaderDragType.DRAGGABLE + || HeaderBar.getDragType(result.getIntersectedNode()) == HeaderDragType.DRAGGABLE ? HeaderAreaType.DRAGBAR : null; } From 18ab411d234a3c849382515e9d1d02ace985ed50 Mon Sep 17 00:00:00 2001 From: mstr2 <43553916+mstr2@users.noreply.github.com> Date: Sun, 4 May 2025 16:30:25 +0200 Subject: [PATCH 73/73] reapply overlay CSS --- .../main/java/com/sun/javafx/tk/TKScene.java | 2 ++ .../com/sun/javafx/tk/quantum/ViewScene.java | 7 +++++++ .../sun/javafx/tk/quantum/ViewSceneOverlay.java | 6 ++++++ .../src/main/java/javafx/scene/Scene.java | 17 ++++++++--------- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKScene.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKScene.java index c44463cff65..ecb1730e582 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKScene.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/TKScene.java @@ -86,6 +86,8 @@ public interface TKScene { public TKClipboard createDragboard(boolean isDragSource); + default void reapplyOverlayCSS() {} + default void processOverlayCSS() {} default void layoutOverlay() {} diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/ViewScene.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/ViewScene.java index e95be547afb..055f4fa0c7d 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/ViewScene.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/ViewScene.java @@ -164,6 +164,13 @@ public void finishInputMethodComposition() { platformView.finishInputMethodComposition(); } + @Override + public void reapplyOverlayCSS() { + if (viewSceneOverlay != null) { + viewSceneOverlay.reapplyCSS(); + } + } + @Override public void processOverlayCSS() { if (viewSceneOverlay != null) { diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/ViewSceneOverlay.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/ViewSceneOverlay.java index f146f663cf4..6b85a29afc6 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/ViewSceneOverlay.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/ViewSceneOverlay.java @@ -48,6 +48,12 @@ final class ViewSceneOverlay { this.painter = painter; } + public void reapplyCSS() { + if (root != null) { + NodeHelper.reapplyCSS(root); + } + } + public void processCSS() { if (root != null) { NodeHelper.processCSS(root); diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java b/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java index 0c073b8815a..76a3cb69ec9 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java @@ -1672,16 +1672,11 @@ public Node lookup(String selector) { @Override protected void onChanged(Change c) { StyleManager.getInstance().stylesheetsChanged(Scene.this, c); - // JDK-8110059 - if stylesheet is removed, reset styled properties to - // their initial value. - c.reset(); - while(c.next()) { - if (c.wasRemoved() == false) { - continue; - } - break; // no point in resetting more than once... - } getRoot().reapplyCSS(); + + if (peer != null) { + peer.reapplyOverlayCSS(); + } } }; @@ -1745,6 +1740,10 @@ public final ObjectProperty userAgentStylesheetProperty() { @Override protected void invalidated() { StyleManager.getInstance().forget(Scene.this); getRoot().reapplyCSS(); + + if (peer != null) { + peer.reapplyOverlayCSS(); + } } }; }