99import javafx .scene .Node ;
1010import javafx .scene .layout .Region ;
1111import javafx .scene .robot .Robot ;
12- import javafx .stage .Stage ;
1312
1413import java .util .ArrayList ;
1514import java .util .List ;
@@ -61,8 +60,7 @@ class DecorationWindowProcedure implements WinUser.WindowProc {
6160
6261 private final BaseTSD .LONG_PTR defaultWindowsProcedure ;
6362 private final Features features ;
64- private WinDef .POINT ptMaxSize ;
65- private WinDef .POINT ptMaxPosition ;
63+ private HWND hwnd ;
6664
6765 DecorationWindowProcedure (WindowController windowController , BaseTSD .LONG_PTR defaultWindowsProcedure , Features features ) {
6866 this .windowController = windowController ;
@@ -72,7 +70,7 @@ class DecorationWindowProcedure implements WinUser.WindowProc {
7270 references .add (this );
7371 }
7472
75- private static HitTestResult hitTest (Rect window , Point mouse , WindowController controller , boolean allowTopResize ) {
73+ private static HitTestResult hitTest (Rect window , Point mouse , WindowController controller , boolean allowTopResize , HWND hwnd ) {
7674 Region controlBox = controller .controlBox ;
7775 Region titleBar = controller .getTitleBar ();
7876
@@ -99,22 +97,24 @@ private static HitTestResult hitTest(Rect window, Point mouse, WindowController
9997 }
10098 }
10199
102- // Maximized, 'top' is a negative value which needs to be compensated here
103- int topInset = ((Stage ) controller .windowRoot .getScene ().getWindow ()).isMaximized () ? -top : 0 ;
100+ // When maximized, window extends beyond screen bounds
101+ // Using Windows API instead of JavaFX's isMaximized() for accurate multi-monitor
102+ int topInset = getTopInset (hwnd );
103+
104104 if (result == HitTestResult .HTCLIENT && mouse .y <= top + topInset + frameDragHeight ) {
105105 result = HitTestResult .CAPTION ;
106106 }
107107
108108 if (result != HitTestResult .HTCLIENT && (
109- isMouseOn (mouse , controlBox , topInset , window )
110- || isMouseOn (mouse , icon , topInset , window )
111- || controller .getNonCaptionNodes ().stream ().anyMatch (node -> isMouseOn (mouse , node , topInset , window )))) {
109+ isMouseOn (mouse , controlBox )
110+ || isMouseOn (mouse , icon )
111+ || controller .getNonCaptionNodes ().stream ().anyMatch (node -> isMouseOn (mouse , node )))) {
112112 return HitTestResult .HTCLIENT ;
113113 }
114114 return result ;
115115 }
116116
117- private static boolean isMouseOn (Point mouse , Node node , int topInset , Rect window ) {
117+ private static boolean isMouseOn (Point mouse , Node node ) {
118118 if (node == null || !node .isManaged ()) {
119119 return false ;
120120 }
@@ -128,25 +128,56 @@ private static boolean isMouseOn(Point mouse, Node node, int topInset, Rect wind
128128
129129 // Can't use bounds.contains() because it deals with doubles not integers, which causes rounding issues
130130 return scaledMouseX >= bounds .getMinX () && scaledMouseX <= bounds .getMaxX ()
131- && scaledMouseY >= bounds .getMinY () + ( double ) topInset
132- && scaledMouseY <= bounds .getMaxY () + ( double ) topInset ;
131+ && scaledMouseY >= bounds .getMinY ()
132+ && scaledMouseY <= bounds .getMaxY ();
133133 }
134134
135- // I have no idea how to properly detect whether it's maximized because the taskbar will interfere
136- private boolean isMaximized (RECT rect ) {
137- if (ptMaxPosition == null ) {
138- return false ;
135+ private static int getTopInset (HWND hwnd ) {
136+ if (hwnd == null || !user32Ex .IsZoomed (hwnd )) {
137+ return 0 ;
138+ }
139+
140+ WinDef .RECT windowRect = new WinDef .RECT ();
141+ if (!user32Ex .GetWindowRect (hwnd , windowRect )) {
142+ return 0 ;
143+ }
144+
145+ WinDef .RECT clientRect = new WinDef .RECT ();
146+ if (!user32Ex .GetClientRect (hwnd , clientRect )) {
147+ return 0 ;
148+ }
149+
150+ WinDef .POINT clientTopLeft = new WinDef .POINT ();
151+ clientTopLeft .x = clientRect .left ;
152+ clientTopLeft .y = clientRect .top ;
153+ if (!user32Ex .ClientToScreen (hwnd , clientTopLeft )) {
154+ return 0 ;
155+ }
156+
157+ int topInsetPx = clientTopLeft .y - windowRect .top ;
158+ if (topInsetPx <= 0 ) {
159+ return 0 ;
139160 }
140- short count = 0 ;
141- count += (rect .top == ptMaxPosition .y ) ? 1 : 0 ;
142- count += (rect .left == ptMaxPosition .x ) ? 1 : 0 ;
143- count += ((rect .right - ptMaxPosition .x ) == ptMaxSize .x ) ? 1 : 0 ;
144- count += ((rect .bottom - ptMaxPosition .y ) == ptMaxSize .y ) ? 1 : 0 ;
145- return count > 2 ;
161+
162+ int dpi = user32Ex .GetDpiForWindow (hwnd );
163+ if (dpi == 0 ) {
164+ dpi = 96 ;
165+ }
166+ double scale = dpi / 96.0 ;
167+ return (int ) Math .round (topInsetPx / scale );
168+ }
169+
170+ /**
171+ * Uses the Windows API IsZoomed to properly detect if a window is maximized
172+ */
173+ private boolean isMaximized (HWND hwnd ) {
174+ return user32Ex .IsZoomed (hwnd );
146175 }
147176
148177 @ Override
149178 public LRESULT callback (HWND hwnd , int uMsg , WPARAM wparam , LPARAM lParam ) {
179+ this .hwnd = hwnd ;
180+
150181 LRESULT lresult ;
151182
152183 switch (uMsg ) {
@@ -166,15 +197,18 @@ public LRESULT callback(HWND hwnd, int uMsg, WPARAM wparam, LPARAM lParam) {
166197 if (wparam .intValue () != 0 ) {
167198 User32Ex .NCCALCSIZE_PARAMS nCalcSizeParams = new User32Ex .NCCALCSIZE_PARAMS (new Pointer (lParam .longValue ()));
168199 int resizeBorderThickness = windowController .getResizeBorderThickness ();
169- // Window is maximized in which case the stage goes off-screen at the top. To avoid that, we set top to 0
170- // but this must only be done when the window is actually being maximized. When the user drags to restore,
171- // 'top' usually is still negative but in that case 'top' must not be set to 0.
172- if (isMaximized (nCalcSizeParams .rgrc [0 ])) {
173- nCalcSizeParams .rgrc [0 ].top = Math .max (nCalcSizeParams .rgrc [0 ].top , 0 );
200+
201+ if (isMaximized (hwnd )) {
202+ nCalcSizeParams .rgrc [0 ].top += resizeBorderThickness ;
203+ nCalcSizeParams .rgrc [0 ].left += resizeBorderThickness ;
204+ nCalcSizeParams .rgrc [0 ].right -= resizeBorderThickness ;
205+ nCalcSizeParams .rgrc [0 ].bottom -= resizeBorderThickness ;
206+ } else {
207+ nCalcSizeParams .rgrc [0 ].left += resizeBorderThickness ;
208+ nCalcSizeParams .rgrc [0 ].right -= resizeBorderThickness ;
209+ nCalcSizeParams .rgrc [0 ].bottom -= resizeBorderThickness ;
174210 }
175- nCalcSizeParams .rgrc [0 ].right -= resizeBorderThickness ;
176- nCalcSizeParams .rgrc [0 ].bottom -= resizeBorderThickness ;
177- nCalcSizeParams .rgrc [0 ].left += resizeBorderThickness ;
211+
178212 nCalcSizeParams .write ();
179213 return WVR_VALIDRECTS ;
180214 }
@@ -184,8 +218,6 @@ public LRESULT callback(HWND hwnd, int uMsg, WPARAM wparam, LPARAM lParam) {
184218 lresult = user32Ex .CallWindowProc (defaultWindowsProcedure , hwnd , uMsg , wparam , lParam );
185219 User32Ex .MinMaxInfo minMaxInfo = new User32Ex .MinMaxInfo (new Pointer (lParam .longValue ()));
186220 minMaxInfo .read ();
187- ptMaxSize = minMaxInfo .ptMaxSize ;
188- ptMaxPosition = minMaxInfo .ptMaxPosition ;
189221 return lresult ;
190222
191223 default :
@@ -199,6 +231,6 @@ public LRESULT callback(HWND hwnd, int uMsg, WPARAM wparam, LPARAM lParam) {
199231 private LRESULT hitTest () {
200232 Point2D mousePosition = robot .getMousePosition ();
201233
202- return new LRESULT (hitTest (new Rect (windowController .getStage ()), new Point (mousePosition ), windowController , features .isAllowTopResize ()).windowsValue );
234+ return new LRESULT (hitTest (new Rect (windowController .getStage ()), new Point (mousePosition ), windowController , features .isAllowTopResize (), hwnd ).windowsValue );
203235 }
204236}
0 commit comments