Native window decorations: show Windows 11 snap layouts menu when hovering the mouse over the maximize button (issues #397 and #407)

This commit is contained in:
Karl Tauber
2021-11-09 15:36:21 +01:00
parent 33b25c1129
commit 54e6cefa67
9 changed files with 180 additions and 64 deletions

View File

@@ -12,6 +12,8 @@ FlatLaf Change Log
- Style classes allow defining style rules at a single place (in UI defaults) - Style classes allow defining style rules at a single place (in UI defaults)
and use them in any component. (PR #388)\ and use them in any component. (PR #388)\
E.g.: `mySlider.putClientProperty( "FlatLaf.styleClass", "myclass" );` E.g.: `mySlider.putClientProperty( "FlatLaf.styleClass", "myclass" );`
- Native window decorations: Show Windows 11 snap layouts menu when hovering the
mouse over the maximize button. (issues #397 and #407)
- TextField, FormattedTextField and PasswordField: Support leading and trailing - TextField, FormattedTextField and PasswordField: Support leading and trailing
icons (set client property `JTextField.leadingIcon` or icons (set client property `JTextField.leadingIcon` or
`JTextField.trailingIcon` to an `Icon`). (PR #378; issue #368) `JTextField.trailingIcon` to an `Icon`). (PR #378; issue #368)

View File

@@ -235,7 +235,7 @@ public class FlatNativeWindowBorder
} }
static void setTitleBarHeightAndHitTestSpots( Window window, int titleBarHeight, static void setTitleBarHeightAndHitTestSpots( Window window, int titleBarHeight,
List<Rectangle> hitTestSpots, Rectangle appIconBounds ) List<Rectangle> hitTestSpots, Rectangle appIconBounds, Rectangle maximizeButtonBounds )
{ {
if( canUseJBRCustomDecorations ) { if( canUseJBRCustomDecorations ) {
JBRCustomDecorations.setTitleBarHeightAndHitTestSpots( window, titleBarHeight, hitTestSpots ); JBRCustomDecorations.setTitleBarHeightAndHitTestSpots( window, titleBarHeight, hitTestSpots );
@@ -245,9 +245,7 @@ public class FlatNativeWindowBorder
if( !isSupported() ) if( !isSupported() )
return; return;
nativeProvider.setTitleBarHeight( window, titleBarHeight ); nativeProvider.updateTitleBarInfo( window, titleBarHeight, hitTestSpots, appIconBounds, maximizeButtonBounds );
nativeProvider.setTitleBarHitTestSpots( window, hitTestSpots );
nativeProvider.setTitleBarAppIconBounds( window, appIconBounds );
} }
static boolean showWindow( Window window, int cmd ) { static boolean showWindow( Window window, int cmd ) {
@@ -292,9 +290,8 @@ public class FlatNativeWindowBorder
{ {
boolean hasCustomDecoration( Window window ); boolean hasCustomDecoration( Window window );
void setHasCustomDecoration( Window window, boolean hasCustomDecoration ); void setHasCustomDecoration( Window window, boolean hasCustomDecoration );
void setTitleBarHeight( Window window, int titleBarHeight ); void updateTitleBarInfo( Window window, int titleBarHeight, List<Rectangle> hitTestSpots,
void setTitleBarHitTestSpots( Window window, List<Rectangle> hitTestSpots ); Rectangle appIconBounds, Rectangle maximizeButtonBounds );
void setTitleBarAppIconBounds( Window window, Rectangle appIconBounds );
// commands for showWindow(); values must match Win32 API // commands for showWindow(); values must match Win32 API
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow

View File

@@ -527,11 +527,17 @@ public class FlatTitlePane
g.drawRect( r.x - offset.x, r.y - offset.y, r.width - 1, r.height - 1 ); g.drawRect( r.x - offset.x, r.y - offset.y, r.width - 1, r.height - 1 );
} }
if( debugAppIconBounds != null ) { if( debugAppIconBounds != null ) {
g.setColor( Color.blue); g.setColor( Color.blue );
Point offset = SwingUtilities.convertPoint( this, 0, 0, window ); Point offset = SwingUtilities.convertPoint( this, 0, 0, window );
Rectangle r = debugAppIconBounds; Rectangle r = debugAppIconBounds;
g.drawRect( r.x - offset.x, r.y - offset.y, r.width - 1, r.height - 1 ); 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 );
}
} }
debug*/ debug*/
@@ -722,6 +728,7 @@ debug*/
List<Rectangle> hitTestSpots = new ArrayList<>(); List<Rectangle> hitTestSpots = new ArrayList<>();
Rectangle appIconBounds = null; Rectangle appIconBounds = null;
if( iconLabel.isVisible() ) { if( iconLabel.isVisible() ) {
// compute real icon size (without insets; 1px wider for easier hitting) // compute real icon size (without insets; 1px wider for easier hitting)
Point location = SwingUtilities.convertPoint( iconLabel, 0, 0, window ); Point location = SwingUtilities.convertPoint( iconLabel, 0, 0, window );
@@ -753,6 +760,13 @@ debug*/
appIconBounds = iconBounds; 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 ); Rectangle r = getNativeHitTestSpot( buttonPanel );
if( r != null ) if( r != null )
hitTestSpots.add( r ); hitTestSpots.add( r );
@@ -787,12 +801,14 @@ debug*/
} }
} }
FlatNativeWindowBorder.setTitleBarHeightAndHitTestSpots( window, titleBarHeight, hitTestSpots, appIconBounds ); FlatNativeWindowBorder.setTitleBarHeightAndHitTestSpots( window, titleBarHeight,
hitTestSpots, appIconBounds, maximizeButtonBounds );
/*debug /*debug
debugTitleBarHeight = titleBarHeight; debugTitleBarHeight = titleBarHeight;
debugHitTestSpots = hitTestSpots; debugHitTestSpots = hitTestSpots;
debugAppIconBounds = appIconBounds; debugAppIconBounds = appIconBounds;
debugMaximizeButtonBounds = maximizeButtonBounds;
repaint(); repaint();
debug*/ debug*/
} }
@@ -815,6 +831,7 @@ debug*/
private int debugTitleBarHeight; private int debugTitleBarHeight;
private List<Rectangle> debugHitTestSpots; private List<Rectangle> debugHitTestSpots;
private Rectangle debugAppIconBounds; private Rectangle debugAppIconBounds;
private Rectangle debugMaximizeButtonBounds;
debug*/ debug*/
//---- class FlatTitlePaneBorder ------------------------------------------ //---- class FlatTitlePaneBorder ------------------------------------------

View File

@@ -54,6 +54,10 @@ import com.formdev.flatlaf.util.SystemInfo;
// https://github.com/oberth/custom-chrome // https://github.com/oberth/custom-chrome
// https://github.com/rossy/borderless-window // https://github.com/rossy/borderless-window
// //
// Windows 11
// https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/apply-snap-layout-menu
// https://github.com/dotnet/wpf/issues/4825#issuecomment-930442736
//
/** /**
* Native window border support for Windows 10 when using custom decorations. * Native window border support for Windows 10 when using custom decorations.
@@ -177,30 +181,17 @@ class FlatWindowsNativeWindowBorder
} }
@Override @Override
public void setTitleBarHeight( Window window, int titleBarHeight ) { public void updateTitleBarInfo( Window window, int titleBarHeight, List<Rectangle> hitTestSpots,
Rectangle appIconBounds, Rectangle maximizeButtonBounds )
{
WndProc wndProc = windowsMap.get( window ); WndProc wndProc = windowsMap.get( window );
if( wndProc == null ) if( wndProc == null )
return; return;
wndProc.titleBarHeight = titleBarHeight; wndProc.titleBarHeight = titleBarHeight;
}
@Override
public void setTitleBarHitTestSpots( Window window, List<Rectangle> hitTestSpots ) {
WndProc wndProc = windowsMap.get( window );
if( wndProc == null )
return;
wndProc.hitTestSpots = hitTestSpots.toArray( new Rectangle[hitTestSpots.size()] ); wndProc.hitTestSpots = hitTestSpots.toArray( new Rectangle[hitTestSpots.size()] );
}
@Override
public void setTitleBarAppIconBounds( Window window, Rectangle appIconBounds ) {
WndProc wndProc = windowsMap.get( window );
if( wndProc == null )
return;
wndProc.appIconBounds = (appIconBounds != null) ? new Rectangle( appIconBounds ) : null; wndProc.appIconBounds = (appIconBounds != null) ? new Rectangle( appIconBounds ) : null;
wndProc.maximizeButtonBounds = (maximizeButtonBounds != null) ? new Rectangle( maximizeButtonBounds ) : null;
} }
@Override @Override
@@ -303,14 +294,17 @@ class FlatWindowsNativeWindowBorder
HTCLIENT = 1, HTCLIENT = 1,
HTCAPTION = 2, HTCAPTION = 2,
HTSYSMENU = 3, HTSYSMENU = 3,
HTMAXBUTTON = 9,
HTTOP = 12; HTTOP = 12;
private Window window; private Window window;
private final long hwnd; private final long hwnd;
// Swing coordinates/values may be scaled on a HiDPI screen
private int titleBarHeight; private int titleBarHeight;
private Rectangle[] hitTestSpots; private Rectangle[] hitTestSpots;
private Rectangle appIconBounds; private Rectangle appIconBounds;
private Rectangle maximizeButtonBounds;
WndProc( Window window ) { WndProc( Window window ) {
this.window = window; this.window = window;
@@ -355,7 +349,7 @@ class FlatWindowsNativeWindowBorder
// invoked from native code // invoked from native code
private int onNcHitTest( int x, int y, boolean isOnResizeBorder ) { private int onNcHitTest( int x, int y, boolean isOnResizeBorder ) {
// scale-down mouse x/y // scale-down mouse x/y because Swing coordinates/values may be scaled on a HiDPI screen
Point pt = scaleDown( x, y ); Point pt = scaleDown( x, y );
int sx = pt.x; int sx = pt.x;
int sy = pt.y; int sy = pt.y;
@@ -366,6 +360,12 @@ class FlatWindowsNativeWindowBorder
if( appIconBounds != null && appIconBounds.contains( sx, sy ) ) if( appIconBounds != null && appIconBounds.contains( sx, sy ) )
return HTSYSMENU; return HTSYSMENU;
// return HTMAXBUTTON if mouse is over maximize/restore button
// - 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 ) )
return HTMAXBUTTON;
boolean isOnTitleBar = (sy < titleBarHeight); boolean isOnTitleBar = (sy < titleBarHeight);
if( isOnTitleBar ) { if( isOnTitleBar ) {

View File

@@ -20,6 +20,6 @@ plugins {
dependencies { dependencies {
implementation( project( ":flatlaf-core" ) ) implementation( project( ":flatlaf-core" ) )
implementation( "net.java.dev.jna:jna:5.7.0" ) implementation( "net.java.dev.jna:jna:5.10.0" )
implementation( "net.java.dev.jna:jna-platform:5.7.0" ) implementation( "net.java.dev.jna:jna-platform:5.10.0" )
} }

View File

@@ -47,7 +47,7 @@ import com.sun.jna.Pointer;
import com.sun.jna.Structure; import com.sun.jna.Structure;
import com.sun.jna.Structure.FieldOrder; import com.sun.jna.Structure.FieldOrder;
import com.sun.jna.platform.win32.Advapi32Util; import com.sun.jna.platform.win32.Advapi32Util;
import com.sun.jna.platform.win32.BaseTSD; import com.sun.jna.platform.win32.BaseTSD.LONG_PTR;
import com.sun.jna.platform.win32.BaseTSD.ULONG_PTR; import com.sun.jna.platform.win32.BaseTSD.ULONG_PTR;
import com.sun.jna.platform.win32.GDI32; import com.sun.jna.platform.win32.GDI32;
import com.sun.jna.platform.win32.Shell32; import com.sun.jna.platform.win32.Shell32;
@@ -77,6 +77,10 @@ import com.sun.jna.win32.W32APIOptions;
// https://github.com/oberth/custom-chrome // https://github.com/oberth/custom-chrome
// https://github.com/rossy/borderless-window // https://github.com/rossy/borderless-window
// //
// Windows 11
// https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/apply-snap-layout-menu
// https://github.com/dotnet/wpf/issues/4825#issuecomment-930442736
//
/** /**
* Native window border support for Windows 10 when using custom decorations. * Native window border support for Windows 10 when using custom decorations.
@@ -160,30 +164,17 @@ public class FlatWindowsNativeWindowBorder
} }
@Override @Override
public void setTitleBarHeight( Window window, int titleBarHeight ) { public void updateTitleBarInfo( Window window, int titleBarHeight, List<Rectangle> hitTestSpots,
Rectangle appIconBounds, Rectangle maximizeButtonBounds )
{
WndProc wndProc = windowsMap.get( window ); WndProc wndProc = windowsMap.get( window );
if( wndProc == null ) if( wndProc == null )
return; return;
wndProc.titleBarHeight = titleBarHeight; wndProc.titleBarHeight = titleBarHeight;
}
@Override
public void setTitleBarHitTestSpots( Window window, List<Rectangle> hitTestSpots ) {
WndProc wndProc = windowsMap.get( window );
if( wndProc == null )
return;
wndProc.hitTestSpots = hitTestSpots.toArray( new Rectangle[hitTestSpots.size()] ); wndProc.hitTestSpots = hitTestSpots.toArray( new Rectangle[hitTestSpots.size()] );
}
@Override
public void setTitleBarAppIconBounds( Window window, Rectangle appIconBounds ) {
WndProc wndProc = windowsMap.get( window );
if( wndProc == null )
return;
wndProc.appIconBounds = (appIconBounds != null) ? new Rectangle( appIconBounds ) : null; wndProc.appIconBounds = (appIconBounds != null) ? new Rectangle( appIconBounds ) : null;
wndProc.maximizeButtonBounds = (maximizeButtonBounds != null) ? new Rectangle( maximizeButtonBounds ) : null;
} }
@Override @Override
@@ -293,7 +284,16 @@ public class FlatWindowsNativeWindowBorder
WM_ERASEBKGND = 0x0014, WM_ERASEBKGND = 0x0014,
WM_NCCALCSIZE = 0x0083, WM_NCCALCSIZE = 0x0083,
WM_NCHITTEST = 0x0084, WM_NCHITTEST = 0x0084,
WM_NCMOUSEMOVE = 0x00A0,
WM_NCLBUTTONDOWN = 0x00A1,
WM_NCLBUTTONUP = 0x00A2,
WM_NCRBUTTONUP = 0x00A5, WM_NCRBUTTONUP = 0x00A5,
WM_MOUSEMOVE= 0x0200,
WM_LBUTTONDOWN = 0x0201,
WM_LBUTTONUP = 0x0202,
WM_DWMCOLORIZATIONCOLORCHANGED = 0x0320; WM_DWMCOLORIZATIONCOLORCHANGED = 0x0320;
// WM_SIZE wParam // WM_SIZE wParam
@@ -306,6 +306,7 @@ public class FlatWindowsNativeWindowBorder
HTCLIENT = 1, HTCLIENT = 1,
HTCAPTION = 2, HTCAPTION = 2,
HTSYSMENU = 3, HTSYSMENU = 3,
HTMAXBUTTON = 9,
HTTOP = 12; HTTOP = 12;
private static final int ABS_AUTOHIDE = 0x0000001; private static final int ABS_AUTOHIDE = 0x0000001;
@@ -328,13 +329,15 @@ public class FlatWindowsNativeWindowBorder
private Window window; private Window window;
private final HWND hwnd; private final HWND hwnd;
private final BaseTSD.LONG_PTR defaultWndProc; private final LONG_PTR defaultWndProc;
private int wmSizeWParam = -1; private int wmSizeWParam = -1;
private HBRUSH background; private HBRUSH background;
// Swing coordinates/values may be scaled on a HiDPI screen
private int titleBarHeight; private int titleBarHeight;
private Rectangle[] hitTestSpots; private Rectangle[] hitTestSpots;
private Rectangle appIconBounds; private Rectangle appIconBounds;
private Rectangle maximizeButtonBounds;
WndProc( Window window ) { WndProc( Window window ) {
this.window = window; this.window = window;
@@ -427,6 +430,26 @@ public class FlatWindowsNativeWindowBorder
case WM_NCHITTEST: case WM_NCHITTEST:
return WmNcHitTest( hwnd, uMsg, wParam, lParam ); return WmNcHitTest( hwnd, uMsg, wParam, lParam );
case WM_NCMOUSEMOVE:
// 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 );
break;
case WM_NCLBUTTONDOWN:
case WM_NCLBUTTONUP:
// if left mouse was pressed/released over maximize button,
// send it also to the client area to allow Swing to process it
// (required for Windows 11 maximize button)
if( wParam.shortValue() == HTMAXBUTTON ) {
int uClientMsg = (uMsg == WM_NCLBUTTONDOWN) ? WM_LBUTTONDOWN : WM_LBUTTONUP;
sendMessageToClientArea( hwnd, uClientMsg, lParam );
return new LRESULT( 0 );
}
break;
case WM_NCRBUTTONUP: case WM_NCRBUTTONUP:
if( wParam.longValue() == HTCAPTION || wParam.longValue() == HTSYSMENU ) if( wParam.longValue() == HTCAPTION || wParam.longValue() == HTSYSMENU )
openSystemMenu( hwnd, GET_X_LPARAM( lParam ), GET_Y_LPARAM( lParam ) ); openSystemMenu( hwnd, GET_X_LPARAM( lParam ), GET_Y_LPARAM( lParam ) );
@@ -522,7 +545,7 @@ public class FlatWindowsNativeWindowBorder
boolean isMaximized = User32Ex.INSTANCE.IsZoomed( hwnd ); boolean isMaximized = User32Ex.INSTANCE.IsZoomed( hwnd );
if( isMaximized && !isFullscreen() ) { if( isMaximized && !isFullscreen() ) {
// When a window is maximized, its size is actually a little bit more // When a window is maximized, its size is actually a little bit larger
// than the monitor's work area. The window is positioned and sized in // than the monitor's work area. The window is positioned and sized in
// such a way that the resize handles are outside of the monitor and // such a way that the resize handles are outside of the monitor and
// then the window is clipped to the monitor so that the resize handle // then the window is clipped to the monitor so that the resize handle
@@ -574,15 +597,12 @@ public class FlatWindowsNativeWindowBorder
if( lResult.longValue() != HTCLIENT ) if( lResult.longValue() != HTCLIENT )
return lResult; return lResult;
// get window rectangle needed to convert mouse x/y from screen to window coordinates
RECT rcWindow = new RECT();
User32.INSTANCE.GetWindowRect( hwnd, rcWindow );
// get mouse x/y in window coordinates // get mouse x/y in window coordinates
int x = GET_X_LPARAM( lParam ) - rcWindow.left; LRESULT xy = screen2windowCoordinates( hwnd, lParam );
int y = GET_Y_LPARAM( lParam ) - rcWindow.top; int x = GET_X_LPARAM( xy );
int y = GET_Y_LPARAM( xy );
// scale-down mouse x/y // scale-down mouse x/y because Swing coordinates/values may be scaled on a HiDPI screen
Point pt = scaleDown( x, y ); Point pt = scaleDown( x, y );
int sx = pt.x; int sx = pt.x;
int sy = pt.y; int sy = pt.y;
@@ -593,6 +613,12 @@ public class FlatWindowsNativeWindowBorder
if( appIconBounds != null && appIconBounds.contains( sx, sy ) ) if( appIconBounds != null && appIconBounds.contains( sx, sy ) )
return new LRESULT( HTSYSMENU ); return new LRESULT( HTSYSMENU );
// return HTMAXBUTTON if mouse is over maximize/restore button
// - 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 ) )
return new LRESULT( HTMAXBUTTON );
int resizeBorderHeight = getResizeHandleHeight(); int resizeBorderHeight = getResizeHandleHeight();
boolean isOnResizeBorder = (y < resizeBorderHeight) && boolean isOnResizeBorder = (y < resizeBorderHeight) &&
(User32.INSTANCE.GetWindowLong( hwnd, GWL_STYLE ) & WS_THICKFRAME) != 0; (User32.INSTANCE.GetWindowLong( hwnd, GWL_STYLE ) & WS_THICKFRAME) != 0;
@@ -612,6 +638,21 @@ public class FlatWindowsNativeWindowBorder
return new LRESULT( isOnResizeBorder ? HTTOP : HTCLIENT ); return new LRESULT( isOnResizeBorder ? HTTOP : HTCLIENT );
} }
/**
* Converts screen coordinates to window coordinates.
*/
private LRESULT screen2windowCoordinates( HWND hwnd, LPARAM lParam ) {
// get window rectangle needed to convert mouse x/y from screen to window coordinates
RECT rcWindow = new RECT();
User32.INSTANCE.GetWindowRect( hwnd, rcWindow );
// get mouse x/y in window coordinates
int x = GET_X_LPARAM( lParam ) - rcWindow.left;
int y = GET_Y_LPARAM( lParam ) - rcWindow.top;
return new LRESULT( MAKELONG( x, y ) );
}
/** /**
* Returns the height of the little space at the top of the window used to * Returns the height of the little space at the top of the window used to
* resize the window. * resize the window.
@@ -678,7 +719,7 @@ public class FlatWindowsNativeWindowBorder
* *
* https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-nchittest#remarks * https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-nchittest#remarks
*/ */
private int GET_X_LPARAM( LPARAM lParam ) { private int GET_X_LPARAM( LONG_PTR lParam ) {
return (short) (lParam.longValue() & 0xffff); return (short) (lParam.longValue() & 0xffff);
} }
@@ -688,10 +729,17 @@ public class FlatWindowsNativeWindowBorder
* *
* https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-nchittest#remarks * https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-nchittest#remarks
*/ */
private int GET_Y_LPARAM( LPARAM lParam ) { private int GET_Y_LPARAM( LONG_PTR lParam ) {
return (short) ((lParam.longValue() >> 16) & 0xffff); return (short) ((lParam.longValue() >> 16) & 0xffff);
} }
/**
* Same implementation as MAKELONG(wLow, wHigh) macro in windef.h.
*/
private long MAKELONG( int low, int high ) {
return (low & 0xffff) | ((high & 0xffff) << 16);
}
/** /**
* Same implementation as RGB(r,g,b) macro in wingdi.h. * Same implementation as RGB(r,g,b) macro in wingdi.h.
*/ */
@@ -699,6 +747,14 @@ public class FlatWindowsNativeWindowBorder
return new DWORD( (r & 0xff) | ((g & 0xff) << 8) | ((b & 0xff) << 16) ); return new DWORD( (r & 0xff) | ((g & 0xff) << 8) | ((b & 0xff) << 16) );
} }
private void sendMessageToClientArea( HWND hwnd, int uMsg, LPARAM lParam ) {
// get mouse x/y in window coordinates
LRESULT xy = screen2windowCoordinates( hwnd, lParam );
// send message
User32.INSTANCE.SendMessage( hwnd, uMsg, new WPARAM(), new LPARAM( xy.longValue() ) );
}
/** /**
* Opens the window's system menu. * Opens the window's system menu.
* The system menu is the menu that opens when the user presses Alt+Space or * The system menu is the menu that opens when the user presses Alt+Space or

View File

@@ -215,6 +215,26 @@ LRESULT CALLBACK FlatWndProc::WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, L
case WM_NCHITTEST: case WM_NCHITTEST:
return WmNcHitTest( hwnd, uMsg, wParam, lParam ); return WmNcHitTest( hwnd, uMsg, wParam, lParam );
case WM_NCMOUSEMOVE:
// 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 );
break;
case WM_NCLBUTTONDOWN:
case WM_NCLBUTTONUP:
// if left mouse was pressed/released over maximize button,
// send it also to the client area to allow Swing to process it
// (required for Windows 11 maximize button)
if( wParam == HTMAXBUTTON ) {
int uClientMsg = (uMsg == WM_NCLBUTTONDOWN) ? WM_LBUTTONDOWN : WM_LBUTTONUP;
sendMessageToClientArea( hwnd, uClientMsg, lParam );
return 0;
}
break;
case WM_NCRBUTTONUP: case WM_NCRBUTTONUP:
if( wParam == HTCAPTION || wParam == HTSYSMENU ) if( wParam == HTCAPTION || wParam == HTSYSMENU )
openSystemMenu( hwnd, GET_X_LPARAM( lParam ), GET_Y_LPARAM( lParam ) ); openSystemMenu( hwnd, GET_X_LPARAM( lParam ), GET_Y_LPARAM( lParam ) );
@@ -305,7 +325,7 @@ LRESULT FlatWndProc::WmNcCalcSize( HWND hwnd, int uMsg, WPARAM wParam, LPARAM lP
bool isMaximized = ::IsZoomed( hwnd ); bool isMaximized = ::IsZoomed( hwnd );
if( isMaximized && !isFullscreen() ) { if( isMaximized && !isFullscreen() ) {
// When a window is maximized, its size is actually a little bit more // When a window is maximized, its size is actually a little bit larger
// than the monitor's work area. The window is positioned and sized in // than the monitor's work area. The window is positioned and sized in
// such a way that the resize handles are outside of the monitor and // such a way that the resize handles are outside of the monitor and
// then the window is clipped to the monitor so that the resize handle // then the window is clipped to the monitor so that the resize handle
@@ -354,6 +374,22 @@ LRESULT FlatWndProc::WmNcHitTest( HWND hwnd, int uMsg, WPARAM wParam, LPARAM lPa
if( lResult != HTCLIENT ) if( lResult != HTCLIENT )
return lResult; return lResult;
// get mouse x/y in window coordinates
LRESULT xy = screen2windowCoordinates( hwnd, lParam );
int x = GET_X_LPARAM( xy );
int y = GET_Y_LPARAM( xy );
int resizeBorderHeight = getResizeHandleHeight();
bool isOnResizeBorder = (y < resizeBorderHeight) &&
(::GetWindowLong( hwnd, GWL_STYLE ) & WS_THICKFRAME) != 0;
return onNcHitTest( x, y, isOnResizeBorder );
}
/**
* Converts screen coordinates to window coordinates.
*/
LRESULT FlatWndProc::screen2windowCoordinates( HWND hwnd, LPARAM lParam ) {
// get window rectangle needed to convert mouse x/y from screen to window coordinates // get window rectangle needed to convert mouse x/y from screen to window coordinates
RECT rcWindow; RECT rcWindow;
::GetWindowRect( hwnd, &rcWindow ); ::GetWindowRect( hwnd, &rcWindow );
@@ -362,11 +398,7 @@ LRESULT FlatWndProc::WmNcHitTest( HWND hwnd, int uMsg, WPARAM wParam, LPARAM lPa
int x = GET_X_LPARAM( lParam ) - rcWindow.left; int x = GET_X_LPARAM( lParam ) - rcWindow.left;
int y = GET_Y_LPARAM( lParam ) - rcWindow.top; int y = GET_Y_LPARAM( lParam ) - rcWindow.top;
int resizeBorderHeight = getResizeHandleHeight(); return MAKELONG( x, y );
bool isOnResizeBorder = (y < resizeBorderHeight) &&
(::GetWindowLong( hwnd, GWL_STYLE ) & WS_THICKFRAME) != 0;
return onNcHitTest( x, y, isOnResizeBorder );
} }
/** /**
@@ -429,6 +461,14 @@ JNIEnv* FlatWndProc::getEnv() {
return env; return env;
} }
void FlatWndProc::sendMessageToClientArea( HWND hwnd, int uMsg, LPARAM lParam ) {
// get mouse x/y in window coordinates
LRESULT xy = screen2windowCoordinates( hwnd, lParam );
// send message
::SendMessage( hwnd, uMsg, 0, xy );
}
/** /**
* Opens the window's system menu. * Opens the window's system menu.
* The system menu is the menu that opens when the user presses Alt+Space or * The system menu is the menu that opens when the user presses Alt+Space or

View File

@@ -54,6 +54,7 @@ private:
LRESULT WmNcCalcSize( HWND hwnd, int uMsg, WPARAM wParam, LPARAM lParam ); LRESULT WmNcCalcSize( HWND hwnd, int uMsg, WPARAM wParam, LPARAM lParam );
LRESULT WmNcHitTest( HWND hwnd, int uMsg, WPARAM wParam, LPARAM lParam ); LRESULT WmNcHitTest( HWND hwnd, int uMsg, WPARAM wParam, LPARAM lParam );
LRESULT screen2windowCoordinates( HWND hwnd, LPARAM lParam );
int getResizeHandleHeight(); int getResizeHandleHeight();
bool hasAutohideTaskbar( UINT edge, RECT rcMonitor ); bool hasAutohideTaskbar( UINT edge, RECT rcMonitor );
BOOL isFullscreen(); BOOL isFullscreen();
@@ -61,6 +62,7 @@ private:
void fireStateChangedLaterOnce(); void fireStateChangedLaterOnce();
JNIEnv* getEnv(); JNIEnv* getEnv();
void sendMessageToClientArea( HWND hwnd, int uMsg, LPARAM lParam );
void openSystemMenu( HWND hwnd, int x, int y ); void openSystemMenu( HWND hwnd, int x, int y );
void setMenuItemState( HMENU systemMenu, int item, bool enabled ); void setMenuItemState( HMENU systemMenu, int item, bool enabled );

View File

@@ -13,6 +13,8 @@ extern "C" {
#define com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTCAPTION 2L #define com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTCAPTION 2L
#undef com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTSYSMENU #undef com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTSYSMENU
#define com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTSYSMENU 3L #define com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTSYSMENU 3L
#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 #undef com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTTOP
#define com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTTOP 12L #define com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTTOP 12L
/* /*