diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowBorder.java index 2197c46a..47adcc2b 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowBorder.java @@ -235,7 +235,8 @@ public class FlatNativeWindowBorder } static void setTitleBarHeightAndHitTestSpots( Window window, int titleBarHeight, - List hitTestSpots, Rectangle appIconBounds, Rectangle maximizeButtonBounds ) + List hitTestSpots, Rectangle appIconBounds, Rectangle minimizeButtonBounds, + Rectangle maximizeButtonBounds, Rectangle closeButtonBounds ) { if( canUseJBRCustomDecorations ) { JBRCustomDecorations.setTitleBarHeightAndHitTestSpots( window, titleBarHeight, hitTestSpots ); @@ -245,7 +246,8 @@ public class FlatNativeWindowBorder if( !isSupported() ) return; - nativeProvider.updateTitleBarInfo( window, titleBarHeight, hitTestSpots, appIconBounds, maximizeButtonBounds ); + nativeProvider.updateTitleBarInfo( window, titleBarHeight, hitTestSpots, + appIconBounds, minimizeButtonBounds, maximizeButtonBounds, closeButtonBounds ); } static boolean showWindow( Window window, int cmd ) { @@ -266,7 +268,7 @@ public class FlatNativeWindowBorder try { /* Class cls = Class.forName( "com.formdev.flatlaf.natives.jna.windows.FlatWindowsNativeWindowBorder" ); - Method m = cls.getMethod( "getInstance" ); + java.lang.reflect.Method m = cls.getMethod( "getInstance" ); setNativeProvider( (Provider) m.invoke( null ) ); */ setNativeProvider( FlatWindowsNativeWindowBorder.getInstance() ); @@ -291,7 +293,8 @@ public class FlatNativeWindowBorder boolean hasCustomDecoration( Window window ); void setHasCustomDecoration( Window window, boolean hasCustomDecoration ); void updateTitleBarInfo( Window window, int titleBarHeight, List hitTestSpots, - Rectangle appIconBounds, Rectangle maximizeButtonBounds ); + Rectangle appIconBounds, Rectangle minimizeButtonBounds, Rectangle maximizeButtonBounds, + Rectangle closeButtonBounds ); // commands for showWindow(); values must match Win32 API // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTitlePane.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTitlePane.java index 9a637ce4..02c0b464 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTitlePane.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTitlePane.java @@ -521,23 +521,23 @@ public class FlatTitlePane g.drawLine( 0, debugTitleBarHeight, getWidth(), debugTitleBarHeight ); } if( debugHitTestSpots != null ) { - g.setColor( Color.red ); - Point offset = SwingUtilities.convertPoint( this, 0, 0, window ); for( Rectangle r : debugHitTestSpots ) - g.drawRect( r.x - offset.x, r.y - offset.y, r.width - 1, r.height - 1 ); - } - if( debugAppIconBounds != null ) { - g.setColor( Color.blue ); - Point offset = SwingUtilities.convertPoint( this, 0, 0, window ); - Rectangle r = debugAppIconBounds; - g.drawRect( r.x - offset.x, r.y - offset.y, r.width - 1, r.height - 1 ); - } - if( debugMaximizeButtonBounds != null ) { - g.setColor( Color.blue ); - Point offset = SwingUtilities.convertPoint( this, 0, 0, window ); - Rectangle r = debugMaximizeButtonBounds; - g.drawRect( r.x - offset.x, r.y - offset.y, r.width - 1, r.height - 1 ); + paintRect( g, Color.red, r ); } + paintRect( g, Color.cyan, debugCloseButtonBounds ); + paintRect( g, Color.blue, debugAppIconBounds ); + paintRect( g, Color.magenta, debugMinimizeButtonBounds ); + paintRect( g, Color.blue, debugMaximizeButtonBounds ); + paintRect( g, Color.cyan, debugCloseButtonBounds ); + } + + private void paintRect( Graphics g, Color color, Rectangle r ) { + if( r == null ) + return; + + g.setColor( color ); + Point offset = SwingUtilities.convertPoint( this, 0, 0, window ); + g.drawRect( r.x - offset.x, r.y - offset.y, r.width - 1, r.height - 1 ); } debug*/ @@ -760,13 +760,6 @@ debug*/ appIconBounds = iconBounds; } - JButton maxButton = maximizeButton.isVisible() - ? maximizeButton - : (restoreButton.isVisible() ? restoreButton : null); - Rectangle maximizeButtonBounds = (maxButton != null) - ? SwingUtilities.convertRectangle( maxButton.getParent(), maxButton.getBounds(), window ) - : null; - Rectangle r = getNativeHitTestSpot( buttonPanel ); if( r != null ) hitTestSpots.add( r ); @@ -801,18 +794,30 @@ debug*/ } } + Rectangle minimizeButtonBounds = boundsInWindow( iconifyButton ); + Rectangle maximizeButtonBounds = boundsInWindow( maximizeButton.isVisible() ? maximizeButton : restoreButton ); + Rectangle closeButtonBounds = boundsInWindow( closeButton ); + FlatNativeWindowBorder.setTitleBarHeightAndHitTestSpots( window, titleBarHeight, - hitTestSpots, appIconBounds, maximizeButtonBounds ); + hitTestSpots, appIconBounds, minimizeButtonBounds, maximizeButtonBounds, closeButtonBounds ); /*debug debugTitleBarHeight = titleBarHeight; debugHitTestSpots = hitTestSpots; debugAppIconBounds = appIconBounds; + debugMinimizeButtonBounds = minimizeButtonBounds; debugMaximizeButtonBounds = maximizeButtonBounds; + debugCloseButtonBounds = closeButtonBounds; repaint(); debug*/ } + private Rectangle boundsInWindow( JComponent c ) { + return c.isVisible() + ? SwingUtilities.convertRectangle( c.getParent(), c.getBounds(), window ) + : null; + } + protected Rectangle getNativeHitTestSpot( JComponent c ) { Dimension size = c.getSize(); if( size.width <= 0 || size.height <= 0 ) @@ -831,7 +836,9 @@ debug*/ private int debugTitleBarHeight; private List debugHitTestSpots; private Rectangle debugAppIconBounds; + private Rectangle debugMinimizeButtonBounds; private Rectangle debugMaximizeButtonBounds; + private Rectangle debugCloseButtonBounds; debug*/ //---- class FlatTitlePaneBorder ------------------------------------------ diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatWindowsNativeWindowBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatWindowsNativeWindowBorder.java index 0ee34766..2f76e6f5 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatWindowsNativeWindowBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatWindowsNativeWindowBorder.java @@ -182,7 +182,8 @@ class FlatWindowsNativeWindowBorder @Override public void updateTitleBarInfo( Window window, int titleBarHeight, List hitTestSpots, - Rectangle appIconBounds, Rectangle maximizeButtonBounds ) + Rectangle appIconBounds, Rectangle minimizeButtonBounds, Rectangle maximizeButtonBounds, + Rectangle closeButtonBounds ) { WndProc wndProc = windowsMap.get( window ); if( wndProc == null ) @@ -190,8 +191,14 @@ class FlatWindowsNativeWindowBorder wndProc.titleBarHeight = titleBarHeight; wndProc.hitTestSpots = hitTestSpots.toArray( new Rectangle[hitTestSpots.size()] ); - wndProc.appIconBounds = (appIconBounds != null) ? new Rectangle( appIconBounds ) : null; - wndProc.maximizeButtonBounds = (maximizeButtonBounds != null) ? new Rectangle( maximizeButtonBounds ) : null; + wndProc.appIconBounds = cloneRectange( appIconBounds ); + wndProc.minimizeButtonBounds = cloneRectange( minimizeButtonBounds ); + wndProc.maximizeButtonBounds = cloneRectange( maximizeButtonBounds ); + wndProc.closeButtonBounds = cloneRectange( closeButtonBounds ); + } + + private static Rectangle cloneRectange( Rectangle rect ) { + return (rect != null) ? new Rectangle( rect ) : null; } @Override @@ -294,8 +301,10 @@ class FlatWindowsNativeWindowBorder HTCLIENT = 1, HTCAPTION = 2, HTSYSMENU = 3, + HTMINBUTTON = 8, HTMAXBUTTON = 9, - HTTOP = 12; + HTTOP = 12, + HTCLOSE = 20; private Window window; private final long hwnd; @@ -304,7 +313,9 @@ class FlatWindowsNativeWindowBorder private int titleBarHeight; private Rectangle[] hitTestSpots; private Rectangle appIconBounds; + private Rectangle minimizeButtonBounds; private Rectangle maximizeButtonBounds; + private Rectangle closeButtonBounds; WndProc( Window window ) { this.window = window; @@ -357,15 +368,26 @@ class FlatWindowsNativeWindowBorder // return HTSYSMENU if mouse is over application icon // - left-click on HTSYSMENU area shows system menu // - double-left-click sends WM_CLOSE - if( appIconBounds != null && appIconBounds.contains( sx, sy ) ) + if( contains( appIconBounds, sx, sy ) ) return HTSYSMENU; + // return HTMINBUTTON if mouse is over minimize button + // - hovering mouse over HTMINBUTTON area shows tooltip on Windows 10/11 + if( contains( minimizeButtonBounds, sx, sy ) ) + return HTMINBUTTON; + // return HTMAXBUTTON if mouse is over maximize/restore button + // - hovering mouse over HTMAXBUTTON area shows tooltip on Windows 10 // - hovering mouse over HTMAXBUTTON area shows snap layouts menu on Windows 11 // https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/apply-snap-layout-menu - if( maximizeButtonBounds != null && maximizeButtonBounds.contains( sx, sy ) ) + if( contains( maximizeButtonBounds, sx, sy ) ) return HTMAXBUTTON; + // return HTCLOSE if mouse is over close button + // - hovering mouse over HTCLOSE area shows tooltip on Windows 10/11 + if( contains( closeButtonBounds, sx, sy ) ) + return HTCLOSE; + boolean isOnTitleBar = (sy < titleBarHeight); if( isOnTitleBar ) { @@ -382,6 +404,10 @@ class FlatWindowsNativeWindowBorder return isOnResizeBorder ? HTTOP : HTCLIENT; } + private boolean contains( Rectangle rect, int x, int y ) { + return (rect != null && rect.contains( x, y ) ); + } + /** * Scales down in the same way as AWT. * See AwtWin32GraphicsDevice::ScaleDownX() and ::ScaleDownY() diff --git a/flatlaf-natives/flatlaf-natives-jna/src/main/java/com/formdev/flatlaf/natives/jna/windows/FlatWindowsNativeWindowBorder.java b/flatlaf-natives/flatlaf-natives-jna/src/main/java/com/formdev/flatlaf/natives/jna/windows/FlatWindowsNativeWindowBorder.java index fe8e21b6..5bc8bf45 100644 --- a/flatlaf-natives/flatlaf-natives-jna/src/main/java/com/formdev/flatlaf/natives/jna/windows/FlatWindowsNativeWindowBorder.java +++ b/flatlaf-natives/flatlaf-natives-jna/src/main/java/com/formdev/flatlaf/natives/jna/windows/FlatWindowsNativeWindowBorder.java @@ -165,7 +165,8 @@ public class FlatWindowsNativeWindowBorder @Override public void updateTitleBarInfo( Window window, int titleBarHeight, List hitTestSpots, - Rectangle appIconBounds, Rectangle maximizeButtonBounds ) + Rectangle appIconBounds, Rectangle minimizeButtonBounds, Rectangle maximizeButtonBounds, + Rectangle closeButtonBounds ) { WndProc wndProc = windowsMap.get( window ); if( wndProc == null ) @@ -173,8 +174,14 @@ public class FlatWindowsNativeWindowBorder wndProc.titleBarHeight = titleBarHeight; wndProc.hitTestSpots = hitTestSpots.toArray( new Rectangle[hitTestSpots.size()] ); - wndProc.appIconBounds = (appIconBounds != null) ? new Rectangle( appIconBounds ) : null; - wndProc.maximizeButtonBounds = (maximizeButtonBounds != null) ? new Rectangle( maximizeButtonBounds ) : null; + wndProc.appIconBounds = cloneRectange( appIconBounds ); + wndProc.minimizeButtonBounds = cloneRectange( minimizeButtonBounds ); + wndProc.maximizeButtonBounds = cloneRectange( maximizeButtonBounds ); + wndProc.closeButtonBounds = cloneRectange( closeButtonBounds ); + } + + private static Rectangle cloneRectange( Rectangle rect ) { + return (rect != null) ? new Rectangle( rect ) : null; } @Override @@ -306,8 +313,10 @@ public class FlatWindowsNativeWindowBorder HTCLIENT = 1, HTCAPTION = 2, HTSYSMENU = 3, + HTMINBUTTON = 8, HTMAXBUTTON = 9, - HTTOP = 12; + HTTOP = 12, + HTCLOSE = 20; private static final int ABS_AUTOHIDE = 0x0000001; private static final int ABM_GETAUTOHIDEBAREX = 0x0000000b; @@ -337,7 +346,9 @@ public class FlatWindowsNativeWindowBorder private int titleBarHeight; private Rectangle[] hitTestSpots; private Rectangle appIconBounds; + private Rectangle minimizeButtonBounds; private Rectangle maximizeButtonBounds; + private Rectangle closeButtonBounds; WndProc( Window window ) { this.window = window; @@ -423,6 +434,7 @@ public class FlatWindowsNativeWindowBorder */ @Override public LRESULT callback( HWND hwnd, int uMsg, WPARAM wParam, LPARAM lParam ) { + long wparam = wParam.longValue(); switch( uMsg ) { case WM_NCCALCSIZE: return WmNcCalcSize( hwnd, uMsg, wParam, lParam ); @@ -434,16 +446,17 @@ public class FlatWindowsNativeWindowBorder // if mouse is moved over some non-client areas, // send it also to the client area to allow Swing to process it // (required for Windows 11 maximize button) - if( wParam.longValue() == HTMAXBUTTON || wParam.longValue() == HTCAPTION || wParam.longValue() == HTSYSMENU ) - sendMessageToClientArea( hwnd, WM_MOUSEMOVE, lParam ); + if( wparam == HTMINBUTTON || wparam == HTMAXBUTTON || wparam == HTCLOSE || + wparam == HTCAPTION || wparam == HTSYSMENU ) + sendMessageToClientArea( hwnd, WM_MOUSEMOVE, lParam ); break; case WM_NCLBUTTONDOWN: case WM_NCLBUTTONUP: - // if left mouse was pressed/released over maximize button, + // if left mouse was pressed/released over minimize/maximize/close button, // send it also to the client area to allow Swing to process it // (required for Windows 11 maximize button) - if( wParam.shortValue() == HTMAXBUTTON ) { + if( wparam == HTMINBUTTON || wparam == HTMAXBUTTON || wparam == HTCLOSE ) { int uClientMsg = (uMsg == WM_NCLBUTTONDOWN) ? WM_LBUTTONDOWN : WM_LBUTTONUP; sendMessageToClientArea( hwnd, uClientMsg, lParam ); return new LRESULT( 0 ); @@ -451,7 +464,7 @@ public class FlatWindowsNativeWindowBorder break; case WM_NCRBUTTONUP: - if( wParam.longValue() == HTCAPTION || wParam.longValue() == HTSYSMENU ) + if( wparam == HTCAPTION || wparam == HTSYSMENU ) openSystemMenu( hwnd, GET_X_LPARAM( lParam ), GET_Y_LPARAM( lParam ) ); break; @@ -610,15 +623,26 @@ public class FlatWindowsNativeWindowBorder // return HTSYSMENU if mouse is over application icon // - left-click on HTSYSMENU area shows system menu // - double-left-click sends WM_CLOSE - if( appIconBounds != null && appIconBounds.contains( sx, sy ) ) + if( contains( appIconBounds, sx, sy ) ) return new LRESULT( HTSYSMENU ); + // return HTMINBUTTON if mouse is over minimize button + // - hovering mouse over HTMINBUTTON area shows tooltip on Windows 10/11 + if( contains( minimizeButtonBounds, sx, sy ) ) + return new LRESULT( HTMINBUTTON ); + // return HTMAXBUTTON if mouse is over maximize/restore button + // - hovering mouse over HTMAXBUTTON area shows tooltip on Windows 10 // - hovering mouse over HTMAXBUTTON area shows snap layouts menu on Windows 11 // https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/apply-snap-layout-menu - if( maximizeButtonBounds != null && maximizeButtonBounds.contains( sx, sy ) ) + if( contains( maximizeButtonBounds, sx, sy ) ) return new LRESULT( HTMAXBUTTON ); + // return HTCLOSE if mouse is over close button + // - hovering mouse over HTCLOSE area shows tooltip on Windows 10/11 + if( contains( closeButtonBounds, sx, sy ) ) + return new LRESULT( HTCLOSE ); + int resizeBorderHeight = getResizeHandleHeight(); boolean isOnResizeBorder = (y < resizeBorderHeight) && (User32.INSTANCE.GetWindowLong( hwnd, GWL_STYLE ) & WS_THICKFRAME) != 0; @@ -638,6 +662,10 @@ public class FlatWindowsNativeWindowBorder return new LRESULT( isOnResizeBorder ? HTTOP : HTCLIENT ); } + private boolean contains( Rectangle rect, int x, int y ) { + return (rect != null && rect.contains( x, y ) ); + } + /** * Converts screen coordinates to window coordinates. */ diff --git a/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/FlatWndProc.cpp b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/FlatWndProc.cpp index f745917b..38d4550d 100644 --- a/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/FlatWndProc.cpp +++ b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/FlatWndProc.cpp @@ -219,16 +219,17 @@ LRESULT CALLBACK FlatWndProc::WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, L // if mouse is moved over some non-client areas, // send it also to the client area to allow Swing to process it // (required for Windows 11 maximize button) - if( wParam == HTMAXBUTTON || wParam == HTCAPTION || wParam == HTSYSMENU ) - sendMessageToClientArea( hwnd, WM_MOUSEMOVE, lParam ); + if( wParam == HTMINBUTTON || wParam == HTMAXBUTTON || wParam == HTCLOSE || + wParam == HTCAPTION || wParam == HTSYSMENU ) + sendMessageToClientArea( hwnd, WM_MOUSEMOVE, lParam ); break; case WM_NCLBUTTONDOWN: case WM_NCLBUTTONUP: - // if left mouse was pressed/released over maximize button, + // if left mouse was pressed/released over minimize/maximize/close button, // send it also to the client area to allow Swing to process it // (required for Windows 11 maximize button) - if( wParam == HTMAXBUTTON ) { + if( wParam == HTMINBUTTON || wParam == HTMAXBUTTON || wParam == HTCLOSE ) { int uClientMsg = (uMsg == WM_NCLBUTTONDOWN) ? WM_LBUTTONDOWN : WM_LBUTTONUP; sendMessageToClientArea( hwnd, uClientMsg, lParam ); return 0; diff --git a/flatlaf-natives/flatlaf-natives-windows/src/main/headers/com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc.h b/flatlaf-natives/flatlaf-natives-windows/src/main/headers/com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc.h index 65c9c35a..baf58f83 100644 --- a/flatlaf-natives/flatlaf-natives-windows/src/main/headers/com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc.h +++ b/flatlaf-natives/flatlaf-natives-windows/src/main/headers/com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc.h @@ -13,10 +13,14 @@ extern "C" { #define com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTCAPTION 2L #undef com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTSYSMENU #define com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTSYSMENU 3L +#undef com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTMINBUTTON +#define com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTMINBUTTON 8L #undef com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTMAXBUTTON #define com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTMAXBUTTON 9L #undef com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTTOP #define com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTTOP 12L +#undef com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTCLOSE +#define com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTCLOSE 20L /* * Class: com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc * Method: installImpl