diff --git a/CHANGELOG.md b/CHANGELOG.md index e52959f2..da6680bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ FlatLaf Change Log - FlatLaf window decorations: - Windows: Fixed possible deadlock with TabbedPane in window title area in "full window content" mode. (issue #909) + - Windows: Fixed wrong layout in maximized frame after changing screen scale + factor. (issue #904) - Linux: Fixed continuous cursor toggling between resize and standard cursor when resizing window. (issue #907) diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/flatlaf-windows-arm64.dll b/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/flatlaf-windows-arm64.dll index b91c3210..d9ebcfb5 100644 Binary files a/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/flatlaf-windows-arm64.dll and b/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/flatlaf-windows-arm64.dll differ diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/flatlaf-windows-x86.dll b/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/flatlaf-windows-x86.dll index ba214bf0..ab1cbfd2 100644 Binary files a/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/flatlaf-windows-x86.dll and b/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/flatlaf-windows-x86.dll differ diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/flatlaf-windows-x86_64.dll b/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/flatlaf-windows-x86_64.dll index d4e2233a..9d31677b 100644 Binary files a/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/flatlaf-windows-x86_64.dll and b/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/flatlaf-windows-x86_64.dll differ 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 77dac5cb..19c20796 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 @@ -309,6 +309,8 @@ public class FlatWindowsNativeWindowBorder WM_ENTERSIZEMOVE = 0x0231, WM_EXITSIZEMOVE = 0x0232, + WM_DPICHANGED = 0x02E0, + WM_DWMCOLORIZATIONCOLORCHANGED = 0x0320; // WM_SIZE wParam @@ -501,6 +503,22 @@ public class FlatWindowsNativeWindowBorder isMoving = true; break; + case WM_DPICHANGED: + LRESULT lResult = User32Ex.INSTANCE.CallWindowProc( defaultWndProc, hwnd, uMsg, wParam, lParam ); + + // if window is maximized and DPI/scaling changed, then Windows + // does not send a subsequent WM_SIZE message and Java window bounds, + // which depend on scale factor, are not updated + boolean isMaximized = User32Ex.INSTANCE.IsZoomed( hwnd ); + if( isMaximized ) { + MyRECT r = new MyRECT( new Pointer( lParam.longValue() ) ); + int width = r.right - r.left; + int height = r.bottom - r.top; + User32Ex.INSTANCE.CallWindowProc( defaultWndProc, hwnd, WM_SIZE, new WPARAM( SIZE_MAXIMIZED ), MAKELPARAM( width, height ) ); + } + + return lResult; + case WM_ERASEBKGND: // do not erase background while the user is moving the window, // otherwise there may be rendering artifacts on HiDPI screens with Java 9+ @@ -818,6 +836,13 @@ public class FlatWindowsNativeWindowBorder return (low & 0xffff) | ((high & 0xffff) << 16); } + /** + * Same implementation as MAKELPARAM(l, h) macro in winuser.h. + */ + private LPARAM MAKELPARAM( int low, int high ) { + return new LPARAM( MAKELONG( low, high ) ); + } + /** * Same implementation as RGB(r,g,b) macro in wingdi.h. */ @@ -937,6 +962,23 @@ public class FlatWindowsNativeWindowBorder } } + //---- class MyRECT ------------------------------------------------------- + + @FieldOrder( { "left", "top", "right", "bottom" } ) + public static class MyRECT + extends Structure + { + public int left; + public int top; + public int right; + public int bottom; + + public MyRECT( Pointer pointer ) { + super( pointer ); + read(); + } + } + //---- class MENUITEMINFO ------------------------------------------------- @FieldOrder( { "cbSize", "fMask", "fType", "fState", "wID", "hSubMenu", diff --git a/flatlaf-natives/flatlaf-natives-windows/README.md b/flatlaf-natives/flatlaf-natives-windows/README.md index 3d0e4d32..c164e381 100644 --- a/flatlaf-natives/flatlaf-natives-windows/README.md +++ b/flatlaf-natives/flatlaf-natives-windows/README.md @@ -19,3 +19,32 @@ The DLLs were built on a GitHub server with the help of GitHub Actions. See: [Native Libraries](https://github.com/JFormDesigner/FlatLaf/actions/workflows/natives.yml) workflow. Then the produced Artifacts ZIP was downloaded, signed DLLs with FormDev Software code signing certificate and committed the DLLs to Git. + + +## Development + +To build the library on Windows using Gradle, (parts of) +[Visual Studio Community +2022](https://visualstudio.microsoft.com/downloads/) +needs to be installed. After downloading and running `VisualStudioSetup.exe` the +**Visual Studio Installer** is installed and started. Once running, it shows the +**Workloads** tab that allows you to install additional packages. Either choose +**Desktop development with C++**, or to save some disk space switch to the +**Single Components** tab and choose following components (newest versions): + +- MSVC v143 - VS 2022 C++ x64/x86 Buildtools +- MSVC v143 - VS 2022 C++ ARM64/ARM64EC Buildtools +- Windows 11 SDK + +Note that the Visual Studio Installer shows many similar named components for +MSVC. Make sure to choose exactly those components listed above. + +Using +[Build Tools for Visual Studio 2022](https://visualstudio.microsoft.com/downloads/#remote-tools-for-visual-studio-2022) +(installs only compiler and SDKs) instead of +[Visual Studio Community +2022](https://visualstudio.microsoft.com/downloads/) +does not work with Gradle. + +[Visual Studio Code](https://code.visualstudio.com/) with **C/C++** extension +can be used for C++ code editing. 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 6ce6af91..d45f758a 100644 --- a/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/FlatWndProc.cpp +++ b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/FlatWndProc.cpp @@ -288,6 +288,23 @@ LRESULT CALLBACK FlatWndProc::WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, L isMoving = true; break; + case WM_DPICHANGED: { + LRESULT lResult = ::CallWindowProc( defaultWndProc, hwnd, uMsg, wParam, lParam ); + + // if window is maximized and DPI/scaling changed, then Windows + // does not send a subsequent WM_SIZE message and Java window bounds, + // which depend on scale factor, are not updated + bool isMaximized = ::IsZoomed( hwnd ); + if( isMaximized ) { + RECT* r = reinterpret_cast( lParam ); + int width = r->right - r->left; + int height = r->bottom - r->top; + ::CallWindowProc( defaultWndProc, hwnd, WM_SIZE, SIZE_MAXIMIZED, MAKELPARAM( width, height ) ); + } + + return lResult; + } + case WM_ERASEBKGND: // do not erase background while the user is moving the window, // otherwise there may be rendering artifacts on HiDPI screens with Java 9+ diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowDecorationsTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowDecorationsTest.java index 5e659bc2..2f962068 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowDecorationsTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowDecorationsTest.java @@ -25,6 +25,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import javax.swing.*; import javax.swing.border.TitledBorder; import com.formdev.flatlaf.FlatClientProperties; @@ -74,6 +76,7 @@ public class FlatWindowDecorationsTest } private List images; + private Timer refreshStateTimer; FlatWindowDecorationsTest() { initComponents(); @@ -132,6 +135,49 @@ public class FlatWindowDecorationsTest fullWindowContentButtonsBoundsField.setEnabled( bounds != null ); } ); } + + if( window instanceof Frame ) { + AtomicInteger lastState = new AtomicInteger( -1 ); + AtomicReference lastFullScreenWindow = new AtomicReference<>(); + refreshStateTimer = new Timer( 500, e -> { + Frame frame = (Frame) window; + int state = frame.getExtendedState(); + Window fullScreenWindow = window.getGraphicsConfiguration().getDevice().getFullScreenWindow(); + if( state != lastState.get() || fullScreenWindow != lastFullScreenWindow.get() ) { + lastState.set( state ); + lastFullScreenWindow.set( fullScreenWindow ); + + String s = ""; + if( (state & Frame.ICONIFIED) != 0 ) + s += "iconified "; + if( (state & Frame.MAXIMIZED_BOTH) == Frame.MAXIMIZED_BOTH ) + s += "maximized "; + else { + if( (state & Frame.MAXIMIZED_HORIZ) != 0 ) + s += "maximized-horizontal "; + if( (state & Frame.MAXIMIZED_VERT) != 0 ) + s += "maximized-vertical "; + } + if( fullScreenWindow == window ) + s += "full-screen "; + if( s.isEmpty() ) + s = "normal"; + + frameStateField.setText( s ); + } + } ); + refreshStateTimer.start(); + } + } + + @Override + public void removeNotify() { + super.removeNotify(); + + if( refreshStateTimer != null ) { + refreshStateTimer.stop(); + refreshStateTimer = null; + } } private void updateDecorationStyleRadioButtons( JRootPane rootPane ) { @@ -653,6 +699,9 @@ debug*/ typeUtilityRadioButton = new JRadioButton(); typeSmallRadioButton = new JRadioButton(); showRectanglesCheckBox = new JCheckBox(); + JPanel hSpacer1 = new JPanel(null); + JLabel frameStateLabel = new JLabel(); + frameStateField = new JLabel(); menuBar = new JMenuBar(); JMenu fileMenu = new JMenu(); JMenuItem newMenuItem = new JMenuItem(); @@ -1092,6 +1141,16 @@ debug*/ showRectanglesCheckBox.setSelected(true); showRectanglesCheckBox.addActionListener(e -> showRectangles()); add(showRectanglesCheckBox, "cell 0 3"); + add(hSpacer1, "cell 2 3 2 1"); + + //---- frameStateLabel ---- + frameStateLabel.setText("Frame state:"); + add(frameStateLabel, "cell 2 3 2 1,alignx right,growx 0"); + + //---- frameStateField ---- + frameStateField.setText("n/a"); + frameStateField.setFont(frameStateField.getFont().deriveFont(frameStateField.getFont().getStyle() | Font.BOLD)); + add(frameStateField, "cell 2 3 2 1,alignx right,growx 0"); //======== menuBar ======== { @@ -1341,6 +1400,7 @@ debug*/ private JRadioButton typeUtilityRadioButton; private JRadioButton typeSmallRadioButton; private JCheckBox showRectanglesCheckBox; + private JLabel frameStateField; private JMenuBar menuBar; // JFormDesigner - End of variables declaration //GEN-END:variables } diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowDecorationsTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowDecorationsTest.jfd index 78e40beb..c5c29202 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowDecorationsTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatWindowDecorationsTest.jfd @@ -637,6 +637,27 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 3" } ) + add( new FormComponent( "com.jformdesigner.designer.wrapper.HSpacer" ) { + name: "hSpacer1" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 3 2 1" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "frameStateLabel" + "text": "Frame state:" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 3 2 1,alignx right,growx 0" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "frameStateField" + "text": "n/a" + "font": new com.jformdesigner.model.SwingDerivedFont( null, 1, 0, false ) + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 3 2 1,alignx right,growx 0" + } ) }, new FormLayoutConstraints( null ) { "location": new java.awt.Point( 0, 0 ) "size": new java.awt.Dimension( 960, 570 ) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 241b8eb2..4d9d326c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -46,8 +46,8 @@ glazedlists = "com.glazedlists:glazedlists:1.11.0" netbeans-api-awt = "org.netbeans.api:org-openide-awt:RELEASE112" # flatlaf-natives-jna -jna = "net.java.dev.jna:jna:5.12.1" -jna-platform = "net.java.dev.jna:jna-platform:5.12.1" +jna = "net.java.dev.jna:jna:5.15.0" +jna-platform = "net.java.dev.jna:jna-platform:5.15.0" # junit junit = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }