diff --git a/.gitattributes b/.gitattributes index 3ab79411..c3a3cc0a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -15,8 +15,11 @@ # BINARY FILES: # Disable line ending normalize on checkin. +*.dll binary +*.dylib binary *.gif binary *.jar binary *.png binary *.sketch binary +*.so binary *.zip binary diff --git a/.github/workflows/natives.yml b/.github/workflows/natives.yml new file mode 100644 index 00000000..8291ac36 --- /dev/null +++ b/.github/workflows/natives.yml @@ -0,0 +1,56 @@ +# https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle + +name: Native Libraries + +on: + push: + branches: + - '*' + tags: + - '[0-9]*' + paths: + - '**.cpp' + - '**.h' + pull_request: + branches: + - '*' + paths: + - '**.cpp' + - '**.h' + +jobs: + Windows: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v2 + + - uses: gradle/wrapper-validation-action@v1 + + - name: Setup Java 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + + - name: Cache Gradle wrapper + uses: actions/cache@v1 + with: + path: ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }} + + - name: Cache Gradle cache + uses: actions/cache@v2 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }} + restore-keys: ${{ runner.os }}-gradle + + - name: Build with Gradle + run: ./gradlew :flatlaf-natives-windows:build + + - name: Upload artifacts + uses: actions/upload-artifact@v2 + with: + name: FlatLaf-natives-windows-build-artifacts + path: | + flatlaf-natives/flatlaf-natives-windows/build diff --git a/.gitignore b/.gitignore index 34002bf5..b67ae01c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ out/ *.iml *.ipr *.iws +.vs/ +.vscode/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 53ce8cbe..344f6716 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ FlatLaf Change Log ## 1.1-SNAPSHOT +#### New features and improvements + +- Native window decorations for Windows 10 enables dark frame/dialog title bar + and embedded menu bar with all JREs, while still having native Windows 10 + border drop shadows, resize behavior, window snapping and system window menu. + (PR #267) + #### Fixed bugs - IntelliJ Themes: Fixed text color of CheckBoxMenuItem and RadioButtonMenuItem diff --git a/flatlaf-core/build.gradle.kts b/flatlaf-core/build.gradle.kts index 7d6c5038..3854d8ea 100644 --- a/flatlaf-core/build.gradle.kts +++ b/flatlaf-core/build.gradle.kts @@ -27,6 +27,16 @@ java { } tasks { + compileJava { + // generate JNI headers + options.headerOutputDirectory.set( buildDir.resolve( "generated/jni-headers" ) ) + } + + processResources { + // build native libraries + dependsOn( ":flatlaf-natives-windows:assemble" ) + } + jar { archiveBaseName.set( "flatlaf" ) diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java index 84841dd8..2e98e62c 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java @@ -44,7 +44,6 @@ import javax.swing.ImageIcon; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFrame; -import javax.swing.JRootPane; import javax.swing.LookAndFeel; import javax.swing.PopupFactory; import javax.swing.SwingUtilities; @@ -90,9 +89,6 @@ public abstract class FlatLaf private Consumer postInitialization; - private Boolean oldFrameWindowDecorated; - private Boolean oldDialogWindowDecorated; - /** * Sets the application look and feel to the given LaF * using {@link UIManager#setLookAndFeel(javax.swing.LookAndFeel)}. @@ -144,35 +140,20 @@ public abstract class FlatLaf * Returns whether FlatLaf supports custom window decorations. * This depends on the operating system and on the used Java runtime. *

- * To use custom window decorations in your application, enable them with - * following code (before creating any frames or dialogs). - *

-	 * JFrame.setDefaultLookAndFeelDecorated( true );
-	 * JDialog.setDefaultLookAndFeelDecorated( true );
-	 * 
- *

- * Then custom window decorations are only enabled if this method returns {@code true}. - * In this case, when creating a {@link JFrame} or {@link JDialog}, the frame/dialog will be made - * undecorated ({@link JFrame#setUndecorated(boolean)} / {@link JDialog#setUndecorated(boolean)}), - * the window decoration style of the {@link JRootPane} will - * {@link JRootPane#FRAME} / {@link JRootPane#PLAIN_DIALOG} - * (see {@link JRootPane#setWindowDecorationStyle(int)}) and the look and feel - * is responsible for the whole frame/dialog border (including window resizing). - *

* This method returns {@code true} on Windows 10 (see exception below), {@code false} otherwise. *

* Returns also {@code false} on Windows 10 if: *

- * In this case, custom decorations are enabled by the root pane - * if {@link JFrame#isDefaultLookAndFeelDecorated()} or - * {@link JDialog#isDefaultLookAndFeelDecorated()} return {@code true}. + * In this cases, custom decorations are enabled by the root pane. + * Usage of {@link JFrame#setDefaultLookAndFeelDecorated(boolean)} or + * {@link JDialog#setDefaultLookAndFeelDecorated(boolean)} is not necessary. */ @Override public boolean getSupportsWindowDecorations() { @@ -276,16 +257,6 @@ public abstract class FlatLaf String.format( "a, address { color: #%06x; }", linkColor.getRGB() & 0xffffff ) ); } }; - - // enable/disable window decorations, but only if system property is either - // "true" or "false"; in other cases it is not changed - Boolean useWindowDecorations = FlatSystemProperties.getBooleanStrict( FlatSystemProperties.USE_WINDOW_DECORATIONS, null ); - if( useWindowDecorations != null ) { - oldFrameWindowDecorated = JFrame.isDefaultLookAndFeelDecorated(); - oldDialogWindowDecorated = JDialog.isDefaultLookAndFeelDecorated(); - JFrame.setDefaultLookAndFeelDecorated( useWindowDecorations ); - JDialog.setDefaultLookAndFeelDecorated( useWindowDecorations ); - } } @Override @@ -318,14 +289,6 @@ public abstract class FlatLaf new HTMLEditorKit().getStyleSheet().addRule( "a, address { color: blue; }" ); postInitialization = null; - // restore enable/disable window decorations - if( oldFrameWindowDecorated != null ) { - JFrame.setDefaultLookAndFeelDecorated( oldFrameWindowDecorated ); - JDialog.setDefaultLookAndFeelDecorated( oldDialogWindowDecorated ); - oldFrameWindowDecorated = null; - oldDialogWindowDecorated = null; - } - super.uninitialize(); } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatSystemProperties.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatSystemProperties.java index 45a24b2d..07f01f3c 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatSystemProperties.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatSystemProperties.java @@ -16,9 +16,6 @@ package com.formdev.flatlaf; -import javax.swing.JDialog; -import javax.swing.JFrame; - /** * Defines/documents own system properties used in FlatLaf. * @@ -57,38 +54,21 @@ public interface FlatSystemProperties */ String USE_UBUNTU_FONT = "flatlaf.useUbuntuFont"; - /** - * Specifies whether custom look and feel window decorations should be used - * when creating {@code JFrame} or {@code JDialog}. - *

- * If this system property is set, FlatLaf invokes {@link JFrame#setDefaultLookAndFeelDecorated(boolean)} - * and {@link JDialog#setDefaultLookAndFeelDecorated(boolean)} on LaF initialization. - *

- * (requires Window 10) - *

- * Allowed Values {@code false} and {@code true}
- * Default none - */ - String USE_WINDOW_DECORATIONS = "flatlaf.useWindowDecorations"; - /** * Specifies whether FlatLaf native window decorations should be used * when creating {@code JFrame} or {@code JDialog}. - * Requires that {@code flatlaf-natives-jna.jar} is on classpath/modulepath. *

* Setting this to {@code true} forces using FlatLaf native window decorations * even if they are not enabled by the application. *

* Setting this to {@code false} disables using FlatLaf native window decorations. *

- * (requires Window 10) + * (requires Window 10 64-bit) *

* Allowed Values {@code false} and {@code true}
* Default none - * - * @since 1.1 */ - String USE_NATIVE_WINDOW_DECORATIONS = "flatlaf.useNativeWindowDecorations"; + String USE_WINDOW_DECORATIONS = "flatlaf.useWindowDecorations"; /** * Specifies whether JetBrains Runtime custom window decorations should be used 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 63e62ee1..1d5d78c8 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 @@ -20,7 +20,6 @@ import java.awt.Color; import java.awt.Rectangle; import java.awt.Window; import java.beans.PropertyChangeListener; -import java.lang.reflect.Method; import java.util.List; import javax.swing.JDialog; import javax.swing.JFrame; @@ -70,7 +69,7 @@ public class FlatNativeWindowBorder // It could be also be a window that is currently hidden, but may be shown later. Window window = SwingUtilities.windowForComponent( rootPane ); if( window != null && window.isDisplayable() ) { - install( window, FlatSystemProperties.USE_NATIVE_WINDOW_DECORATIONS ); + install( window, FlatSystemProperties.USE_WINDOW_DECORATIONS ); return null; } @@ -80,7 +79,7 @@ public class FlatNativeWindowBorder PropertyChangeListener ancestorListener = e -> { Object newValue = e.getNewValue(); if( newValue instanceof Window ) - install( (Window) newValue, FlatSystemProperties.USE_NATIVE_WINDOW_DECORATIONS ); + install( (Window) newValue, FlatSystemProperties.USE_WINDOW_DECORATIONS ); else if( newValue == null && e.getOldValue() instanceof Window ) uninstall( (Window) e.getOldValue() ); }; @@ -102,7 +101,8 @@ public class FlatNativeWindowBorder // do not enable native window border if JFrame should use system window decorations // and if not forced to use FlatLaf/JBR native window decorations if( !JFrame.isDefaultLookAndFeelDecorated() && - !FlatSystemProperties.getBoolean( systemPropertyKey, false )) + !UIManager.getBoolean( "TitlePane.useWindowDecorations" ) && + !FlatSystemProperties.getBoolean( systemPropertyKey, false ) ) return; // do not enable native window border if frame is undecorated @@ -121,7 +121,8 @@ public class FlatNativeWindowBorder // do not enable native window border if JDialog should use system window decorations // and if not forced to use FlatLaf/JBR native window decorations if( !JDialog.isDefaultLookAndFeelDecorated() && - !FlatSystemProperties.getBoolean( systemPropertyKey, false )) + !UIManager.getBoolean( "TitlePane.useWindowDecorations" ) && + !FlatSystemProperties.getBoolean( systemPropertyKey, false ) ) return; // do not enable native window border if dialog is undecorated @@ -224,13 +225,17 @@ public class FlatNativeWindowBorder if( !SystemInfo.isWindows_10_orLater || !SystemInfo.isX86_64 ) return; - if( !FlatSystemProperties.getBoolean( FlatSystemProperties.USE_NATIVE_WINDOW_DECORATIONS, true ) ) + // check whether disabled via system property + if( !FlatSystemProperties.getBoolean( FlatSystemProperties.USE_WINDOW_DECORATIONS, true ) ) return; try { +/* Class cls = Class.forName( "com.formdev.flatlaf.natives.jna.windows.FlatWindowsNativeWindowBorder" ); Method m = cls.getMethod( "getInstance" ); nativeProvider = (Provider) m.invoke( null ); +*/ + nativeProvider = FlatWindowsNativeWindowBorder.getInstance(); supported = (nativeProvider != null); } catch( Exception ex ) { 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 new file mode 100644 index 00000000..4adc6109 --- /dev/null +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatWindowsNativeWindowBorder.java @@ -0,0 +1,380 @@ +/* + * Copyright 2021 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.formdev.flatlaf.ui; + +import java.awt.Color; +import java.awt.Dialog; +import java.awt.EventQueue; +import java.awt.Frame; +import java.awt.GraphicsConfiguration; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Window; +import java.awt.geom.AffineTransform; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.Timer; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.EventListenerList; +import com.formdev.flatlaf.FlatLaf; +import com.formdev.flatlaf.util.NativeLibrary; +import com.formdev.flatlaf.util.SystemInfo; + +// +// Interesting resources: +// https://github.com/microsoft/terminal/blob/main/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp +// https://docs.microsoft.com/en-us/windows/win32/dwm/customframe +// https://github.com/JetBrains/JetBrainsRuntime/blob/master/src/java.desktop/windows/native/libawt/windows/awt_Frame.cpp +// https://github.com/JetBrains/JetBrainsRuntime/commit/d2820524a1aa211b1c49b30f659b9b4d07a6f96e +// https://github.com/JetBrains/JetBrainsRuntime/pull/18 +// https://medium.com/swlh/customizing-the-title-bar-of-an-application-window-50a4ac3ed27e +// https://github.com/kalbetredev/CustomDecoratedJFrame +// https://github.com/Guerra24/NanoUI-win32 +// https://github.com/oberth/custom-chrome +// https://github.com/rossy/borderless-window +// + +/** + * Native window border support for Windows 10 when using custom decorations. + *

+ * If the application wants to use custom decorations, the Windows 10 title bar is hidden + * (including minimize, maximize and close buttons), but not the resize borders (including drop shadow). + * Windows 10 window snapping functionality will remain unaffected: + * https://support.microsoft.com/en-us/windows/snap-your-windows-885a9b1e-a983-a3b1-16cd-c531795e6241 + * + * @author Karl Tauber + */ +class FlatWindowsNativeWindowBorder + implements FlatNativeWindowBorder.Provider +{ + private final Map windowsMap = Collections.synchronizedMap( new IdentityHashMap<>() ); + private final EventListenerList listenerList = new EventListenerList(); + private Timer fireStateChangedTimer; + + private boolean colorizationUpToDate; + private boolean colorizationColorAffectsBorders; + private Color colorizationColor; + private int colorizationColorBalance; + + private static NativeLibrary nativeLibrary; + private static FlatWindowsNativeWindowBorder instance; + + static FlatNativeWindowBorder.Provider getInstance() { + // requires Windows 10 on x86_64 + if( !SystemInfo.isWindows_10_orLater || !SystemInfo.isX86_64 ) + return null; + + // load native library + if( nativeLibrary == null ) { + if( !SystemInfo.isJava_9_orLater ) { + // In Java 8, load jawt.dll (part of JRE) explicitly because it + // is not found when running application with /bin/java.exe. + // When using /jre/bin/java.exe, it is found. + // jawt.dll is located in /jre/bin/. + // Java 9 and later does not have this problem. + try { + System.loadLibrary( "jawt" ); + } catch( Exception ex ) { + Logger.getLogger( FlatLaf.class.getName() ).log( Level.SEVERE, null, ex ); + } + } + + nativeLibrary = new NativeLibrary( + "com/formdev/flatlaf/natives/flatlaf-windows-x86_64", + FlatWindowsNativeWindowBorder.class.getClassLoader(), true ); + } + + // check whether native library was successfully loaded + if( !nativeLibrary.isLoaded() ) + return null; + + // create new instance + if( instance == null ) + instance = new FlatWindowsNativeWindowBorder(); + return instance; + } + + private FlatWindowsNativeWindowBorder() { + } + + @Override + public boolean hasCustomDecoration( Window window ) { + return windowsMap.containsKey( window ); + } + + /** + * Tell the window whether the application wants use custom decorations. + * If {@code true}, the Windows 10 title bar is hidden (including minimize, + * maximize and close buttons), but not the resize borders (including drop shadow). + */ + @Override + public void setHasCustomDecoration( Window window, boolean hasCustomDecoration ) { + if( hasCustomDecoration ) + install( window ); + else + uninstall( window ); + } + + private void install( Window window ) { + // requires Windows 10 on x86_64 + if( !SystemInfo.isWindows_10_orLater || !SystemInfo.isX86_64 ) + return; + + // only JFrame and JDialog are supported + if( !(window instanceof JFrame) && !(window instanceof JDialog) ) + return; + + // not supported if frame/dialog is undecorated + if( (window instanceof Frame && ((Frame)window).isUndecorated()) || + (window instanceof Dialog && ((Dialog)window).isUndecorated()) ) + return; + + // check whether already installed + if( windowsMap.containsKey( window ) ) + return; + + // install + WndProc wndProc = new WndProc( window ); + if( wndProc.hwnd == 0 ) + return; + + windowsMap.put( window, wndProc ); + } + + private void uninstall( Window window ) { + WndProc wndProc = windowsMap.remove( window ); + if( wndProc != null ) + wndProc.uninstall(); + } + + @Override + public void setTitleBarHeight( Window window, int titleBarHeight ) { + WndProc wndProc = windowsMap.get( window ); + if( wndProc == null ) + return; + + wndProc.titleBarHeight = titleBarHeight; + } + + @Override + public void setTitleBarHitTestSpots( Window window, List hitTestSpots ) { + WndProc wndProc = windowsMap.get( window ); + if( wndProc == null ) + return; + + 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; + } + + @Override + public boolean isColorizationColorAffectsBorders() { + updateColorization(); + return colorizationColorAffectsBorders; + } + + @Override + public Color getColorizationColor() { + updateColorization(); + return colorizationColor; + } + + @Override + public int getColorizationColorBalance() { + updateColorization(); + return colorizationColorBalance; + } + + private void updateColorization() { + if( colorizationUpToDate ) + return; + colorizationUpToDate = true; + + String subKey = "SOFTWARE\\Microsoft\\Windows\\DWM"; + + int value = registryGetIntValue( subKey, "ColorPrevalence", -1 ); + colorizationColorAffectsBorders = (value > 0); + + value = registryGetIntValue( subKey, "ColorizationColor", -1 ); + colorizationColor = (value != -1) ? new Color( value ) : null; + + colorizationColorBalance = registryGetIntValue( subKey, "ColorizationColorBalance", -1 ); + } + + private native static int registryGetIntValue( String key, String valueName, int defaultValue ); + + @Override + public void addChangeListener( ChangeListener l ) { + listenerList.add( ChangeListener.class, l ); + } + + @Override + public void removeChangeListener( ChangeListener l ) { + listenerList.remove( ChangeListener.class, l ); + } + + private void fireStateChanged() { + Object[] listeners = listenerList.getListenerList(); + if( listeners.length == 0 ) + return; + + ChangeEvent e = new ChangeEvent( this ); + for( int i = 0; i < listeners.length; i += 2 ) { + if( listeners[i] == ChangeListener.class ) + ((ChangeListener)listeners[i+1]).stateChanged( e ); + } + } + + /** + * Because there may be sent many WM_DWMCOLORIZATIONCOLORCHANGED messages, + * slightly delay event firing and fire it only once (on the AWT thread). + */ + void fireStateChangedLaterOnce() { + EventQueue.invokeLater( () -> { + if( fireStateChangedTimer != null ) { + fireStateChangedTimer.restart(); + return; + } + + fireStateChangedTimer = new Timer( 300, e -> { + fireStateChangedTimer = null; + colorizationUpToDate = false; + + fireStateChanged(); + } ); + fireStateChangedTimer.setRepeats( false ); + fireStateChangedTimer.start(); + } ); + } + + //---- class WndProc ------------------------------------------------------ + + private class WndProc + { + // WM_NCHITTEST mouse position codes + private static final int + HTCLIENT = 1, + HTCAPTION = 2, + HTSYSMENU = 3, + HTTOP = 12; + + private Window window; + private final long hwnd; + + private int titleBarHeight; + private Rectangle[] hitTestSpots; + private Rectangle appIconBounds; + + WndProc( Window window ) { + this.window = window; + + hwnd = installImpl( window ); + } + + void uninstall() { + uninstallImpl( hwnd ); + + // cleanup + window = null; + } + + private native long installImpl( Window window ); + private native void uninstallImpl( long hwnd ); + + // invoked from native code + private int onNcHitTest( int x, int y, boolean isOnResizeBorder ) { + // scale-down mouse x/y + Point pt = scaleDown( x, y ); + int sx = pt.x; + int sy = pt.y; + + // 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 ) ) + return HTSYSMENU; + + boolean isOnTitleBar = (sy < titleBarHeight); + + if( isOnTitleBar ) { + // use a second reference to the array to avoid that it can be changed + // in another thread while processing the array + Rectangle[] hitTestSpots2 = hitTestSpots; + for( Rectangle spot : hitTestSpots2 ) { + if( spot.contains( sx, sy ) ) + return HTCLIENT; + } + return isOnResizeBorder ? HTTOP : HTCAPTION; + } + + return isOnResizeBorder ? HTTOP : HTCLIENT; + } + + /** + * Scales down in the same way as AWT. + * See AwtWin32GraphicsDevice::ScaleDownX() and ::ScaleDownY() + */ + private Point scaleDown( int x, int y ) { + GraphicsConfiguration gc = window.getGraphicsConfiguration(); + if( gc == null ) + return new Point( x, y ); + + AffineTransform t = gc.getDefaultTransform(); + return new Point( clipRound( x / t.getScaleX() ), clipRound( y / t.getScaleY() ) ); + } + + /** + * Rounds in the same way as AWT. + * See AwtWin32GraphicsDevice::ClipRound() + */ + private int clipRound( double value ) { + value -= 0.5; + if( value < Integer.MIN_VALUE ) + return Integer.MIN_VALUE; + if( value > Integer.MAX_VALUE ) + return Integer.MAX_VALUE; + return (int) Math.ceil( value ); + } + + // invoked from native code + private boolean isFullscreen() { + GraphicsConfiguration gc = window.getGraphicsConfiguration(); + if( gc == null ) + return false; + return gc.getDevice().getFullScreenWindow() == window; + } + + // invoked from native code + private void fireStateChangedLaterOnce() { + FlatWindowsNativeWindowBorder.this.fireStateChangedLaterOnce(); + } + } +} diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/JBRCustomDecorations.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/JBRCustomDecorations.java index ee95f2fb..06a30087 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/JBRCustomDecorations.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/JBRCustomDecorations.java @@ -165,6 +165,7 @@ public class JBRCustomDecorations if( !SystemInfo.isJetBrainsJVM_11_orLater || !SystemInfo.isWindows_10_orLater ) return; + // check whether disabled via system property if( !FlatSystemProperties.getBoolean( FlatSystemProperties.USE_JETBRAINS_CUSTOM_DECORATIONS, true ) ) return; diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/NativeLibrary.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/NativeLibrary.java new file mode 100644 index 00000000..ba62e970 --- /dev/null +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/NativeLibrary.java @@ -0,0 +1,193 @@ +/* + * Copyright 2021 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.formdev.flatlaf.util; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.logging.Level; +import java.util.logging.Logger; +import com.formdev.flatlaf.FlatLaf; + +/** + * Helper class to load native library (.dll, .so or .dylib) stored in Jar. + *

+ * Copies native library to users temporary folder before loading it. + * + * @author Karl Tauber + */ +public class NativeLibrary +{ + private static final String DELETE_SUFFIX = ".delete"; + private static boolean deletedTemporary; + + private final boolean loaded; + + /** + * Load native library from given classloader. + * + * @param libraryName resource name of the native library (without "lib" prefix and without extension) + * @param classLoader the classloader used to locate the library + * @param supported whether the native library is supported on the current platform + */ + public NativeLibrary( String libraryName, ClassLoader classLoader, boolean supported ) { + this.loaded = supported + ? loadLibraryFromJar( libraryName, classLoader ) + : false; + } + + /** + * Returns whether the native library is loaded. + *

+ * Returns {@code false} if not supported on current platform as specified in constructor + * or if loading failed. + */ + public boolean isLoaded() { + return loaded; + } + + private static boolean loadLibraryFromJar( String libraryName, ClassLoader classLoader ) { + // add prefix and suffix to library name + libraryName = decorateLibraryName( libraryName ); + + // find library + URL libraryUrl = classLoader.getResource( libraryName ); + if( libraryUrl == null ) { + log( "Library '" + libraryName + "' not found", null ); + return false; + } + + File tempFile = null; + try { + // for development environment + if( "file".equals( libraryUrl.getProtocol() ) ) { + File libraryFile = new File( libraryUrl.getPath() ); + if( libraryFile.isFile() ) { + // load library without copying + System.load( libraryFile.getCanonicalPath() ); + return true; + } + } + + // create temporary file + Path tempPath = createTempFile( libraryName ); + tempFile = tempPath.toFile(); + + // copy library to temporary file + try( InputStream in = libraryUrl.openStream() ) { + Files.copy( in, tempPath, StandardCopyOption.REPLACE_EXISTING ); + } + + // load library + System.load( tempFile.getCanonicalPath() ); + + // delete library + deleteOrMarkForDeletion( tempFile ); + + return true; + } catch( Throwable ex ) { + log( null, ex ); + + if( tempFile != null ) + deleteOrMarkForDeletion( tempFile ); + return false; + } + } + + private static String decorateLibraryName( String libraryName ) { + if( SystemInfo.isWindows ) + return libraryName.concat( ".dll" ); + + String suffix = SystemInfo.isMacOS ? ".dylib" : ".so"; + + int sep = libraryName.lastIndexOf( '/' ); + return (sep >= 0) + ? libraryName.substring( 0, sep + 1 ) + "lib" + libraryName.substring( sep + 1 ) + suffix + : "lib" + libraryName + suffix; + } + + private static void log( String msg, Throwable thrown ) { + Logger.getLogger( FlatLaf.class.getName() ).log( Level.SEVERE, msg, thrown ); + } + + private static Path createTempFile( String libraryName ) throws IOException { + int sep = libraryName.lastIndexOf( '/' ); + String name = (sep >= 0) ? libraryName.substring( sep + 1 ) : libraryName; + + int dot = name.lastIndexOf( '.' ); + String prefix = ((dot >= 0) ? name.substring( 0, dot ) : name) + '-'; + String suffix = (dot >= 0) ? name.substring( dot ) : ""; + + Path tempDir = getTempDir(); + if( tempDir != null ) { + deleteTemporaryFiles( tempDir ); + + return Files.createTempFile( tempDir, prefix, suffix ); + } else + return Files.createTempFile( prefix, suffix ); + } + + private static Path getTempDir() throws IOException { + if( SystemInfo.isWindows ) { + // On Windows, where File.delete() and File.deleteOnExit() does not work + // for loaded native libraries, they will be deleted on next application startup. + // The default temporary directory may contain hundreds or thousands of files. + // To make searching for "marked for deletion" files as fast as possible, + // use a sub directory that contains only our temporary native libraries. + Path tempDir = Paths.get( System.getProperty( "java.io.tmpdir" ) + "/flatlaf.temp" ); + Files.createDirectories( tempDir ); + return tempDir; + } else + return null; // use standard temporary directory + } + + private static void deleteTemporaryFiles( Path tempDir ) { + if( deletedTemporary ) + return; + deletedTemporary = true; + + File[] markerFiles = tempDir.toFile().listFiles( (dir, name) -> name.endsWith( DELETE_SUFFIX ) ); + if( markerFiles == null ) + return; + + for( File markerFile : markerFiles ) { + File toDeleteFile = new File( markerFile.getParent(), StringUtils.removeTrailing( markerFile.getName(), DELETE_SUFFIX ) ); + if( !toDeleteFile.exists() || toDeleteFile.delete() ) + markerFile.delete(); + } + } + + private static void deleteOrMarkForDeletion( File file ) { + // try to delete the native library + if( file.delete() ) + return; + + // not possible to delete on Windows because native library file is locked + // --> create "to delete" marker file (used at next startup) + try { + File markFile = new File( file.getParent(), file.getName() + DELETE_SUFFIX ); + markFile.createNewFile(); + } catch( IOException ex2 ) { + // ignore + } + } +} diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties index dfe1953b..580ff417 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties @@ -685,6 +685,7 @@ TitledBorder.border = 1,1,1,1,$Separator.foreground #---- TitlePane ---- +TitlePane.useWindowDecorations = true TitlePane.menuBarEmbedded = true TitlePane.iconSize = 16,16 TitlePane.iconMargins = 3,8,3,0 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 new file mode 100644 index 00000000..ff6ddc73 Binary files /dev/null and b/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/flatlaf-windows-x86_64.dll differ diff --git a/flatlaf-demo/build.gradle.kts b/flatlaf-demo/build.gradle.kts index 2d3d4f2f..fd7b511f 100644 --- a/flatlaf-demo/build.gradle.kts +++ b/flatlaf-demo/build.gradle.kts @@ -27,19 +27,19 @@ repositories { dependencies { implementation( project( ":flatlaf-core" ) ) - implementation( project( ":flatlaf-natives-jna" ) ) implementation( project( ":flatlaf-extras" ) ) implementation( project( ":flatlaf-intellij-themes" ) ) implementation( "com.miglayout:miglayout-swing:5.3-SNAPSHOT" ) implementation( "com.jgoodies:jgoodies-forms:1.9.0" ) +// implementation( project( ":flatlaf-natives-jna" ) ) } tasks { jar { dependsOn( ":flatlaf-core:jar" ) - dependsOn( ":flatlaf-natives-jna:jar" ) dependsOn( ":flatlaf-extras:jar" ) dependsOn( ":flatlaf-intellij-themes:jar" ) +// dependsOn( ":flatlaf-natives-jna:jar" ) manifest { attributes( "Main-Class" to "com.formdev.flatlaf.demo.FlatLafDemo" ) diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java index bbb2e8c1..876e3c8e 100644 --- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java +++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java @@ -155,8 +155,7 @@ class DemoFrame menuBarEmbeddedCheckBoxMenuItem.setEnabled( windowDecorations ); // enable/disable window decoration for later created frames/dialogs - JFrame.setDefaultLookAndFeelDecorated( windowDecorations ); - JDialog.setDefaultLookAndFeelDecorated( windowDecorations ); + UIManager.put( "TitlePane.useWindowDecorations", windowDecorations ); } private void menuBarEmbeddedChanged() { diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/FlatLafDemo.java b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/FlatLafDemo.java index c9c88fad..30fd861e 100644 --- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/FlatLafDemo.java +++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/FlatLafDemo.java @@ -17,8 +17,6 @@ package com.formdev.flatlaf.demo; import java.awt.Dimension; -import javax.swing.JDialog; -import javax.swing.JFrame; import javax.swing.SwingUtilities; import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.extras.FlatInspector; @@ -46,10 +44,6 @@ public class FlatLafDemo SwingUtilities.invokeLater( () -> { DemoPrefs.init( PREFS_ROOT_PATH ); - // enable window decorations - JFrame.setDefaultLookAndFeelDecorated( true ); - JDialog.setDefaultLookAndFeelDecorated( true ); - // application specific UI defaults FlatLaf.registerCustomDefaultsSource( "com.formdev.flatlaf.demo" ); diff --git a/flatlaf-natives/README.md b/flatlaf-natives/README.md new file mode 100644 index 00000000..15e8acb8 --- /dev/null +++ b/flatlaf-natives/README.md @@ -0,0 +1,5 @@ +FlatLaf Native Libraries +======================== + +- [Windows 10 Native Library](flatlaf-natives-windows) +- [Natives using JNA](flatlaf-natives-jna) (for development only) diff --git a/flatlaf-natives/flatlaf-natives-jna/README.md b/flatlaf-natives/flatlaf-natives-jna/README.md new file mode 100644 index 00000000..09b357ca --- /dev/null +++ b/flatlaf-natives/flatlaf-natives-jna/README.md @@ -0,0 +1,10 @@ +FlatLaf Natives using JNA +========================= + +This sub-project contains source code that uses +[JNA](https://github.com/java-native-access/jna) to access native operating +system API. + +**Note:** Code in this sub-project is **not used** in FlatLaf libraries. It was +used to develop/test usage of some native operating system API in Java (with the +help of JNA) and was then converted to C++. 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 475b172f..8865b226 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 @@ -207,20 +207,20 @@ public class FlatWindowsNativeWindowBorder String subKey = "SOFTWARE\\Microsoft\\Windows\\DWM"; - int value = RegGetDword( HKEY_CURRENT_USER, subKey, "ColorPrevalence" ); + int value = registryGetIntValue( subKey, "ColorPrevalence", -1 ); colorizationColorAffectsBorders = (value > 0); - value = RegGetDword( HKEY_CURRENT_USER, subKey, "ColorizationColor" ); + value = registryGetIntValue( subKey, "ColorizationColor", -1 ); colorizationColor = (value != -1) ? new Color( value ) : null; - colorizationColorBalance = RegGetDword( HKEY_CURRENT_USER, subKey, "ColorizationColorBalance" ); + colorizationColorBalance = registryGetIntValue( subKey, "ColorizationColorBalance", -1 ); } - private static int RegGetDword( HKEY hkey, String lpSubKey, String lpValue ) { + private static int registryGetIntValue( String key, String valueName, int defaultValue ) { try { - return Advapi32Util.registryGetIntValue( hkey, lpSubKey, lpValue ); + return Advapi32Util.registryGetIntValue( HKEY_CURRENT_USER, key, valueName ); } catch( RuntimeException ex ) { - return -1; + return defaultValue; } } diff --git a/flatlaf-natives/flatlaf-natives-windows/README.md b/flatlaf-natives/flatlaf-natives-windows/README.md new file mode 100644 index 00000000..dfa3b063 --- /dev/null +++ b/flatlaf-natives/flatlaf-natives-windows/README.md @@ -0,0 +1,17 @@ +FlatLaf Windows 10 Native Library +================================= + +This sub-project contains the source code for the FlatLaf Windows 10 native +library (DLL). + +The native library can be built only on Windows and requires a C++ compiler. +Tested only with Microsoft Visual C++ 2019 (comes with Visual Studio 2019). + +To be able to build FlatLaf on any platform, and without C++ compiler, the +pre-built DLL is checked into Git at +`flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/flatlaf-windows-x86_64.dll`. + +This DLL was 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 and the DLL checked +into Git. diff --git a/flatlaf-natives/flatlaf-natives-windows/build.gradle.kts b/flatlaf-natives/flatlaf-natives-windows/build.gradle.kts new file mode 100644 index 00000000..d626f209 --- /dev/null +++ b/flatlaf-natives/flatlaf-natives-windows/build.gradle.kts @@ -0,0 +1,98 @@ +/* + * Copyright 2021 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +plugins { + id( "dev.nokee.jni-library" ) version "0.4.0" + id( "dev.nokee.cpp-language" ) version "0.4.0" +} + +library { + targetMachines.set( listOf( machines.windows.x86_64 ) ) + + variants.configureEach { + // depend on :flatlaf-core:compileJava because this task generates the JNI headers + tasks.named( "compileCpp" ) { + dependsOn( ":flatlaf-core:compileJava" ) + } + + sharedLibrary { + compileTasks.configureEach { + onlyIf { isBuildable } + + doFirst { + println( "Used Tool Chain:" ) + println( " - ${toolChain.get()}" ) + println( "Available Tool Chains:" ) + toolChains.forEach { + println( " - $it" ) + } + + // copy needed JNI headers + copy { + from( project( ":flatlaf-core" ).buildDir.resolve( "generated/jni-headers" ) ) + into( "src/main/headers" ) + include( + "com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder.h", + "com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc.h" + ) + filter( + "eol" to org.apache.tools.ant.filters.FixCrLfFilter.CrLf.newInstance( "lf" ) + ) + } + } + + compilerArgs.addAll( toolChain.map { + when( it ) { + is Gcc, is Clang -> listOf( "-O2" ) + is VisualCpp -> listOf( "/O2", "/Zl", "/GS-" ) + else -> emptyList() + } + } ) + } + + linkTask.configure { + onlyIf { isBuildable } + + val nativesDir = project( ":flatlaf-core" ).projectDir.resolve( "src/main/resources/com/formdev/flatlaf/natives" ) + val libraryName = "flatlaf-windows-x86_64.dll" + + outputs.file( "$nativesDir/$libraryName" ) + + val jawt = "${org.gradle.internal.jvm.Jvm.current().javaHome}/lib/jawt" + linkerArgs.addAll( toolChain.map { + when( it ) { + is Gcc, is Clang -> listOf( "-l${jawt}", "-lUser32", "-lshell32", "-lAdvAPI32", "-lKernel32" ) + is VisualCpp -> listOf( "${jawt}.lib", "User32.lib", "shell32.lib", "AdvAPI32.lib", "Kernel32.lib", "/NODEFAULTLIB" ) + else -> emptyList() + } + } ) + + doLast { + // copy shared library to flatlaf-core resources + copy { + from( linkedFile ) + into( nativesDir ) + rename( "flatlaf-natives-windows.dll", libraryName ) + } + } + } + + tasks.named( "jar" ) { + onlyIf { false } + } + } + } +} diff --git a/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/FlatWndProc.cpp b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/FlatWndProc.cpp new file mode 100644 index 00000000..4fdc6579 --- /dev/null +++ b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/FlatWndProc.cpp @@ -0,0 +1,409 @@ +/* + * Copyright 2021 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// avoid inlining of printf() +#define _NO_CRT_STDIO_INLINE + +#include +#include +#include +#include +#include +#include "FlatWndProc.h" +#include "com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc.h" + +/** + * @author Karl Tauber + */ + +//---- JNI methods ------------------------------------------------------------ + +extern "C" +JNIEXPORT jlong JNICALL Java_com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_00024WndProc_installImpl + ( JNIEnv *env, jobject obj, jobject window ) +{ + return reinterpret_cast( FlatWndProc::install( env, obj, window ) ); +} + +extern "C" +JNIEXPORT void JNICALL Java_com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_00024WndProc_uninstallImpl + ( JNIEnv* env, jobject obj, jlong hwnd ) +{ + FlatWndProc::uninstall( env, obj, reinterpret_cast( hwnd ) ); +} + +//---- class FlatWndProc fields ----------------------------------------------- + +int FlatWndProc::initialized = 0; +jmethodID FlatWndProc::onNcHitTestMID; +jmethodID FlatWndProc::isFullscreenMID; +jmethodID FlatWndProc::fireStateChangedLaterOnceMID; + +HWNDMap* FlatWndProc::hwndMap; + +//---- class FlatWndProc methods ---------------------------------------------- + +FlatWndProc::FlatWndProc() { + jvm = NULL; + env = NULL; + obj = NULL; + hwnd = NULL; + defaultWndProc = NULL; +} + +HWND FlatWndProc::install( JNIEnv *env, jobject obj, jobject window ) { + initIDs( env, obj ); + + if( initialized < 0 ) + return 0; + + // create HWND map + if( hwndMap == NULL ) + hwndMap = new HWNDMap(); + + // get window handle + HWND hwnd = getWindowHandle( env, window ); + if( hwnd == NULL || hwndMap->get( hwnd ) != NULL ) + return 0; + + FlatWndProc* fwp = new FlatWndProc(); + env->GetJavaVM( &fwp->jvm ); + fwp->obj = env->NewGlobalRef( obj ); + fwp->hwnd = hwnd; + hwndMap->put( hwnd, fwp ); + + // replace window procedure + fwp->defaultWndProc = reinterpret_cast( + ::SetWindowLongPtr( hwnd, GWLP_WNDPROC, (LONG_PTR) FlatWndProc::StaticWindowProc ) ); + + // remove the OS window title bar + fwp->updateFrame(); + + return hwnd; +} + +void FlatWndProc::uninstall( JNIEnv *env, jobject obj, HWND hwnd ) { + if( hwnd == NULL ) + return; + + FlatWndProc* fwp = (FlatWndProc*) hwndMap->get( hwnd ); + if( fwp == NULL ) + return; + + hwndMap->remove( hwnd ); + + // restore original window procedure + ::SetWindowLongPtr( hwnd, GWLP_WNDPROC, (LONG_PTR) fwp->defaultWndProc ); + + // show the OS window title bar + fwp->updateFrame(); + + // cleanup + env->DeleteGlobalRef( fwp->obj ); + delete fwp; +} + +void FlatWndProc::initIDs( JNIEnv *env, jobject obj ) { + if( initialized ) + return; + + initialized = -1; + + jclass cls = env->GetObjectClass( obj ); + onNcHitTestMID = env->GetMethodID( cls, "onNcHitTest", "(IIZ)I" ); + isFullscreenMID = env->GetMethodID( cls, "isFullscreen", "()Z" ); + fireStateChangedLaterOnceMID = env->GetMethodID( cls, "fireStateChangedLaterOnce", "()V" ); + + // check whether all IDs were found + if( onNcHitTestMID != NULL && + isFullscreenMID != NULL && + fireStateChangedLaterOnceMID != NULL ) + initialized = 1; +} + +void FlatWndProc::updateFrame() { + // this sends WM_NCCALCSIZE and removes/shows the window title bar + ::SetWindowPos( hwnd, hwnd, 0, 0, 0, 0, + SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER ); +} + +LRESULT CALLBACK FlatWndProc::StaticWindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { + FlatWndProc* fwp = (FlatWndProc*) hwndMap->get( hwnd ); + return fwp->WindowProc( hwnd, uMsg, wParam, lParam ); +} + +/** + * NOTE: This method is invoked on the AWT-Windows thread (not the AWT-EventQueue thread). + */ +LRESULT CALLBACK FlatWndProc::WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { + switch( uMsg ) { + case WM_NCCALCSIZE: + return WmNcCalcSize( hwnd, uMsg, wParam, lParam ); + + case WM_NCHITTEST: + return WmNcHitTest( hwnd, uMsg, wParam, lParam ); + + case WM_NCRBUTTONUP: + if( wParam == HTCAPTION || wParam == HTSYSMENU ) + openSystemMenu( hwnd, GET_X_LPARAM( lParam ), GET_Y_LPARAM( lParam ) ); + break; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + fireStateChangedLaterOnce(); + break; + + case WM_DESTROY: + return WmDestroy( hwnd, uMsg, wParam, lParam ); + } + + return ::CallWindowProc( defaultWndProc, hwnd, uMsg, wParam, lParam ); +} +/** + * Handle WM_DESTROY + * + * https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-destroy + */ +LRESULT FlatWndProc::WmDestroy( HWND hwnd, int uMsg, WPARAM wParam, LPARAM lParam ) { + // restore original window procedure + ::SetWindowLongPtr( hwnd, GWLP_WNDPROC, (LONG_PTR) defaultWndProc ); + + WNDPROC defaultWndProc2 = defaultWndProc; + + // cleanup + getEnv()->DeleteGlobalRef( obj ); + hwndMap->remove( hwnd ); + delete this; + + // call original AWT window procedure because it may fire window closed event in AwtWindow::WmDestroy() + return ::CallWindowProc( defaultWndProc2, hwnd, uMsg, wParam, lParam ); +} + +/** + * Handle WM_NCCALCSIZE + * + * https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize + * + * See also NonClientIslandWindow::_OnNcCalcSize() here: + * https://github.com/microsoft/terminal/blob/main/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp + */ +LRESULT FlatWndProc::WmNcCalcSize( HWND hwnd, int uMsg, WPARAM wParam, LPARAM lParam ) { + if( wParam != TRUE ) + return ::CallWindowProc( defaultWndProc, hwnd, uMsg, wParam, lParam ); + + NCCALCSIZE_PARAMS* params = reinterpret_cast( lParam ); + + // store the original top before the default window proc applies the default frame + int originalTop = params->rgrc[0].top; + + // apply the default frame + LRESULT lResult = ::CallWindowProc( defaultWndProc, hwnd, uMsg, wParam, lParam ); + if( lResult != 0 ) + return lResult; + + // re-apply the original top from before the size of the default frame was applied + params->rgrc[0].top = originalTop; + + bool isMaximized = ::IsZoomed( hwnd ); + if( isMaximized && !isFullscreen() ) { + // When a window is maximized, its size is actually a little bit more + // 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 + // then the window is clipped to the monitor so that the resize handle + // do not appear because you don't need them (because you can't resize + // a window when it's maximized unless you restore it). + params->rgrc[0].top += getResizeHandleHeight(); + + // check whether taskbar is in the autohide state + APPBARDATA autohide{ 0 }; + autohide.cbSize = sizeof( autohide ); + UINT state = (UINT) ::SHAppBarMessage( ABM_GETSTATE, &autohide ); + if( (state & ABS_AUTOHIDE) != 0 ) { + // get monitor info + // (using MONITOR_DEFAULTTONEAREST finds right monitor when restoring from minimized) + HMONITOR hMonitor = ::MonitorFromWindow( hwnd, MONITOR_DEFAULTTONEAREST ); + MONITORINFO monitorInfo{ 0 }; + ::GetMonitorInfo( hMonitor, &monitorInfo ); + + // If there's a taskbar on any side of the monitor, reduce our size + // a little bit on that edge. + if( hasAutohideTaskbar( ABE_TOP, monitorInfo.rcMonitor ) ) + params->rgrc[0].top++; + if( hasAutohideTaskbar( ABE_BOTTOM, monitorInfo.rcMonitor ) ) + params->rgrc[0].bottom--; + if( hasAutohideTaskbar( ABE_LEFT, monitorInfo.rcMonitor ) ) + params->rgrc[0].left++; + if( hasAutohideTaskbar( ABE_RIGHT, monitorInfo.rcMonitor ) ) + params->rgrc[0].right--; + } + } + + return lResult; +} + +/** + * Handle WM_NCHITTEST + * + * https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-nchittest + * + * See also NonClientIslandWindow::_OnNcHitTest() here: + * https://github.com/microsoft/terminal/blob/main/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp + */ +LRESULT FlatWndProc::WmNcHitTest( HWND hwnd, int uMsg, WPARAM wParam, LPARAM lParam ) { + // this will handle the left, right and bottom parts of the frame because we didn't change them + LRESULT lResult = ::CallWindowProc( defaultWndProc, hwnd, uMsg, wParam, lParam ); + if( lResult != HTCLIENT ) + return lResult; + + // get window rectangle needed to convert mouse x/y from screen to window coordinates + RECT rcWindow; + ::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; + + int resizeBorderHeight = getResizeHandleHeight(); + bool isOnResizeBorder = (y < resizeBorderHeight) && + (::GetWindowLong( hwnd, GWL_STYLE ) & WS_THICKFRAME) != 0; + + return onNcHitTest( x, y, isOnResizeBorder ); +} + +/** + * Returns the height of the little space at the top of the window used to + * resize the window. + * + * See also NonClientIslandWindow::_GetResizeHandleHeight() here: + * https://github.com/microsoft/terminal/blob/main/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp + */ +int FlatWndProc::getResizeHandleHeight() { + int dpi = ::GetDpiForWindow( hwnd ); + + // there isn't a SM_CYPADDEDBORDER for the Y axis + return ::GetSystemMetricsForDpi( SM_CXPADDEDBORDER, dpi ) + + ::GetSystemMetricsForDpi( SM_CYSIZEFRAME, dpi ); +} + +/** + * Returns whether there is an autohide taskbar on the given edge. + */ +bool FlatWndProc::hasAutohideTaskbar( UINT edge, RECT rcMonitor ) { + APPBARDATA data{ 0 }; + data.cbSize = sizeof( data ); + data.uEdge = edge; + data.rc = rcMonitor; + HWND hTaskbar = (HWND) ::SHAppBarMessage( ABM_GETAUTOHIDEBAREX, &data ); + return hTaskbar != nullptr; +} + +BOOL FlatWndProc::isFullscreen() { + JNIEnv* env = getEnv(); + if( env == NULL ) + return FALSE; + + return env->CallBooleanMethod( obj, isFullscreenMID ); +} + +int FlatWndProc::onNcHitTest( int x, int y, boolean isOnResizeBorder ) { + JNIEnv* env = getEnv(); + if( env == NULL ) + return isOnResizeBorder ? HTTOP : HTCLIENT; + + return env->CallIntMethod( obj, onNcHitTestMID, (jint) x, (jint) y, (jboolean) isOnResizeBorder ); +} + +void FlatWndProc::fireStateChangedLaterOnce() { + JNIEnv* env = getEnv(); + if( env == NULL ) + return; + + env->CallVoidMethod( obj, fireStateChangedLaterOnceMID ); +} + +// similar to JNU_GetEnv() in jni_util.c +JNIEnv* FlatWndProc::getEnv() { + if( env != NULL ) + return env; + + jvm->GetEnv( (void **) &env, JNI_VERSION_1_2 ); + return env; +} + +/** + * Opens the window's system menu. + * The system menu is the menu that opens when the user presses Alt+Space or + * right clicks on the title bar + */ +void FlatWndProc::openSystemMenu( HWND hwnd, int x, int y ) { + // get system menu + HMENU systemMenu = ::GetSystemMenu( hwnd, false ); + + // update system menu + LONG style = ::GetWindowLong( hwnd, GWL_STYLE ); + bool isMaximized = ::IsZoomed( hwnd ); + setMenuItemState( systemMenu, SC_RESTORE, isMaximized ); + setMenuItemState( systemMenu, SC_MOVE, !isMaximized ); + setMenuItemState( systemMenu, SC_SIZE, (style & WS_THICKFRAME) != 0 && !isMaximized ); + setMenuItemState( systemMenu, SC_MINIMIZE, (style & WS_MINIMIZEBOX) != 0 ); + setMenuItemState( systemMenu, SC_MAXIMIZE, (style & WS_MAXIMIZEBOX) != 0 && !isMaximized ); + setMenuItemState( systemMenu, SC_CLOSE, true ); + + // make "Close" item the default to be consistent with the system menu shown + // when pressing Alt+Space + ::SetMenuDefaultItem( systemMenu, SC_CLOSE, 0 ); + + // show system menu + int ret = ::TrackPopupMenu( systemMenu, TPM_RETURNCMD, x, y, 0, hwnd, nullptr ); + if( ret != 0 ) + ::PostMessage( hwnd, WM_SYSCOMMAND, ret, 0 ); +} + +void FlatWndProc::setMenuItemState( HMENU systemMenu, int item, bool enabled ) { + MENUITEMINFO mii{ 0 }; + mii.cbSize = sizeof( mii ); + mii.fMask = MIIM_STATE; + mii.fType = MFT_STRING; + mii.fState = enabled ? MF_ENABLED : MF_DISABLED; + ::SetMenuItemInfo( systemMenu, item, FALSE, &mii ); +} + +HWND FlatWndProc::getWindowHandle( JNIEnv* env, jobject window ) { + JAWT awt; + awt.version = JAWT_VERSION_1_4; + if( !JAWT_GetAWT( env, &awt ) ) + return 0; + + jawt_DrawingSurface* ds = awt.GetDrawingSurface( env, window ); + if( ds == NULL ) + return 0; + + jint lock = ds->Lock( ds ); + if( (lock & JAWT_LOCK_ERROR) != 0 ) { + awt.FreeDrawingSurface( ds ); + return 0; + } + + JAWT_DrawingSurfaceInfo* dsi = ds->GetDrawingSurfaceInfo( ds ); + JAWT_Win32DrawingSurfaceInfo* wdsi = (JAWT_Win32DrawingSurfaceInfo*) dsi->platformInfo; + + HWND hwnd = wdsi->hwnd; + + ds->FreeDrawingSurfaceInfo( dsi ); + ds->Unlock( ds ); + awt.FreeDrawingSurface( ds ); + + return hwnd; +} diff --git a/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/FlatWndProc.h b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/FlatWndProc.h new file mode 100644 index 00000000..a6d5bfbf --- /dev/null +++ b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/FlatWndProc.h @@ -0,0 +1,64 @@ +/* + * Copyright 2021 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "HWNDMap.h" + +/** + * @author Karl Tauber + */ +class FlatWndProc +{ +public: + static HWND install( JNIEnv *env, jobject obj, jobject window ); + static void uninstall( JNIEnv *env, jobject obj, HWND hwnd ); + +private: + static int initialized; + static jmethodID onNcHitTestMID; + static jmethodID isFullscreenMID; + static jmethodID fireStateChangedLaterOnceMID; + + static HWNDMap* hwndMap; + + JavaVM* jvm; + JNIEnv* env; // attached to AWT-Windows/Win32 thread + jobject obj; + HWND hwnd; + WNDPROC defaultWndProc; + + FlatWndProc(); + static void initIDs( JNIEnv *env, jobject obj ); + void updateFrame(); + + static LRESULT CALLBACK StaticWindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ); + LRESULT CALLBACK WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ); + LRESULT WmDestroy( 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 ); + + int getResizeHandleHeight(); + bool hasAutohideTaskbar( UINT edge, RECT rcMonitor ); + BOOL isFullscreen(); + int onNcHitTest( int x, int y, boolean isOnResizeBorder ); + void fireStateChangedLaterOnce(); + JNIEnv* getEnv(); + + void openSystemMenu( HWND hwnd, int x, int y ); + void setMenuItemState( HMENU systemMenu, int item, bool enabled ); + + static HWND getWindowHandle( JNIEnv* env, jobject window ); +}; diff --git a/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/HWNDMap.cpp b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/HWNDMap.cpp new file mode 100644 index 00000000..ae909ee1 --- /dev/null +++ b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/HWNDMap.cpp @@ -0,0 +1,153 @@ +/* + * Copyright 2021 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// avoid inlining of printf() +#define _NO_CRT_STDIO_INLINE + +#include +#include "HWNDMap.h" + +#define DEFAULT_CAPACITY 20 +#define INCREASE_CAPACITY 10 + +/** + * @author Karl Tauber + */ + +class LOCK { + LPCRITICAL_SECTION lpCriticalSection; + +public: + LOCK( LPCRITICAL_SECTION lpCriticalSection ) { + this->lpCriticalSection = lpCriticalSection; + ::EnterCriticalSection( lpCriticalSection ); + } + ~LOCK() { + ::LeaveCriticalSection( lpCriticalSection ); + } +}; + + +HWNDMap::HWNDMap() { + size = 0; + capacity = DEFAULT_CAPACITY; + table = new Entry[capacity]; + + ::InitializeCriticalSection( &criticalSection ); + +// dump( "" ); +} + +LPVOID HWNDMap::get( HWND key ) { + LOCK lock( &criticalSection ); + + int index = binarySearch( key ); + return (index >= 0) ? table[index].value : NULL; +} + +void HWNDMap::put( HWND key, LPVOID value ) { + LOCK lock( &criticalSection ); + + int index = binarySearch( key ); +// printf( "put %p %p = %d --\n", key, value, index ); + if( index >= 0 ) { + // key already in map --> replace + table[index].value = value; + } else { + // insert new key + ensureCapacity( size + 1 ); + + // make roor for new entry + index = -(index + 1); + for( int i = size - 1; i >= index; i-- ) + table[i + 1] = table[i]; + size++; + + // insert entry + table[index].key = key; + table[index].value = value; + } + +// dump( "put" ); +} + +void HWNDMap::remove( HWND key ) { + LOCK lock( &criticalSection ); + + // search for key + int index = binarySearch( key ); +// printf( "remove %p = %d --\n", key, index ); + if( index < 0 ) + return; + + // remove entry + for( int i = index + 1; i < size; i++ ) + table[i - 1] = table[i]; + size--; + +// dump( "remove" ); +} + +int HWNDMap::binarySearch( HWND key ) { + int low = 0; + int high = size - 1; + + while( low <= high ) { + int mid = (low + high) >> 1; + + HWND midKey = table[mid].key; + int cmp = midKey - key; + if( cmp < 0 ) + low = mid + 1; + else if( cmp > 0 ) + high = mid - 1; + else + return mid; + } + + return -(low + 1); +} + +void HWNDMap::ensureCapacity( int minCapacity ) { + if( minCapacity <= capacity ) + return; + + // allocate new table + int newCapacity = minCapacity + INCREASE_CAPACITY; + Entry* newTable = new Entry[newCapacity]; + + // copy old table to new table + for( int i = 0; i < capacity; i++ ) + newTable[i] = table[i]; + + // delete old table + delete table; + + table = newTable; + capacity = newCapacity; +} + +/* +void HWNDMap::dump( char* msg ) { + printf( "---- %s -----------------------\n", msg ); + printf( "size %d\n", size ); + printf( "capacity %d\n", capacity ); + printf( "table %p\n", table ); + + for( int i = 0; i < capacity; i++ ) + printf( " %d: %p - %p %s\n", i, table[i].key, table[i].value, i >= size ? "UNUSED" : "" ); +} +*/ diff --git a/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/HWNDMap.h b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/HWNDMap.h new file mode 100644 index 00000000..c94e66ec --- /dev/null +++ b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/HWNDMap.h @@ -0,0 +1,53 @@ +/* + * Copyright 2021 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +/** + * A simple map that uses a sorted array to store key/value pairs. + * + * @author Karl Tauber + */ + +struct Entry +{ + HWND key; + LPVOID value; +}; + +class HWNDMap +{ +private: + int size; // used entries in table + int capacity; // total size of table + Entry* table; + + // used to synchronize to make it thread safe + CRITICAL_SECTION criticalSection; + +public: + HWNDMap(); + + LPVOID get( HWND key ); + void put( HWND key, LPVOID value ); + void remove( HWND key ); + +private: + int binarySearch( HWND key ); + void ensureCapacity( int newCapacity ); + +// void dump( char* msg ); +}; diff --git a/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/Registry.cpp b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/Registry.cpp new file mode 100644 index 00000000..594a802c --- /dev/null +++ b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/Registry.cpp @@ -0,0 +1,44 @@ +/* + * Copyright 2021 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include "com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder.h" + +/** + * @author Karl Tauber + */ + +//---- JNI methods ------------------------------------------------------------ + +extern "C" +JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_registryGetIntValue + ( JNIEnv* env, jclass cls, jstring key, jstring valueName, jint defaultValue ) +{ + const char* skey = env->GetStringUTFChars( key, NULL ); + const char* svalueName = env->GetStringUTFChars( valueName, NULL ); + + DWORD data = 0; + DWORD cbData = sizeof( data ); + int rc = ::RegGetValueA( HKEY_CURRENT_USER, skey, svalueName, RRF_RT_DWORD, NULL, &data, &cbData ); + + env->ReleaseStringUTFChars( key, skey ); + env->ReleaseStringUTFChars( valueName, svalueName ); + + return (rc == ERROR_SUCCESS) ? data : defaultValue; +} diff --git a/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/Runtime.cpp b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/Runtime.cpp new file mode 100644 index 00000000..efe5f3ac --- /dev/null +++ b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/Runtime.cpp @@ -0,0 +1,73 @@ +/* + * Copyright 2021 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// avoid inlining of printf() +#define _NO_CRT_STDIO_INLINE + +#include +#include +#include + +/** + * Methods that replace C-runtime methods and allow linking/running without C-runtime. + * + * WARNING: Constructors/destructors of static objects are not invoked! + * + * https://documentation.help/Far-Manager/msdnmag-issues-01-01-hood-default.aspx.html + * www.catch22.net/tuts/win32/reducing-executable-size#the-c-runtime-and-default-libraries + * https://www.mvps.org/user32/nocrt.html + * + * see also LIBCTINY on "Downloads" page here: http://www.wheaty.net/ + * or https://github.com/leepa/libctiny + * + * @author Karl Tauber + */ + +extern "C" +BOOL WINAPI _DllMainCRTStartup( HINSTANCE instance, DWORD reason, LPVOID reserved ) { + return TRUE; +} + +void* __cdecl operator new( size_t cb ) { + return ::HeapAlloc( ::GetProcessHeap(), HEAP_ZERO_MEMORY, cb ); +} + +void* __cdecl operator new[]( size_t cb ) { + return ::HeapAlloc( ::GetProcessHeap(), HEAP_ZERO_MEMORY, cb ); +} + +void __cdecl operator delete( void* pv, size_t cb ) { + if( pv != NULL ) + ::HeapFree( ::GetProcessHeap(), 0, pv ); +} + +/* +extern "C" +int __cdecl printf( const char* format, ... ) { + char szBuff[1024]; + int retValue; + DWORD cbWritten; + va_list argptr; + + va_start( argptr, format ); + retValue = wvsprintfA( szBuff, format, argptr ); + va_end( argptr ); + + WriteFile( GetStdHandle( STD_OUTPUT_HANDLE ), szBuff, retValue, &cbWritten, NULL ); + + return retValue; +} +*/ diff --git a/flatlaf-natives/flatlaf-natives-windows/src/main/headers/com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder.h b/flatlaf-natives/flatlaf-natives-windows/src/main/headers/com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder.h new file mode 100644 index 00000000..952362bc --- /dev/null +++ b/flatlaf-natives/flatlaf-natives-windows/src/main/headers/com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder.h @@ -0,0 +1,21 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder */ + +#ifndef _Included_com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder +#define _Included_com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder + * Method: registryGetIntValue + * Signature: (Ljava/lang/String;Ljava/lang/String;I)I + */ +JNIEXPORT jint JNICALL Java_com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_registryGetIntValue + (JNIEnv *, jclass, jstring, jstring, jint); + +#ifdef __cplusplus +} +#endif +#endif 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 new file mode 100644 index 00000000..a08cf74f --- /dev/null +++ b/flatlaf-natives/flatlaf-natives-windows/src/main/headers/com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc.h @@ -0,0 +1,37 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc */ + +#ifndef _Included_com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc +#define _Included_com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc +#ifdef __cplusplus +extern "C" { +#endif +#undef com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTCLIENT +#define com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTCLIENT 1L +#undef com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTCAPTION +#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_HTTOP +#define com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc_HTTOP 12L +/* + * Class: com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc + * Method: installImpl + * Signature: (Ljava/awt/Window;)J + */ +JNIEXPORT jlong JNICALL Java_com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_00024WndProc_installImpl + (JNIEnv *, jobject, jobject); + +/* + * Class: com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc + * Method: uninstallImpl + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_00024WndProc_uninstallImpl + (JNIEnv *, jobject, jlong); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/flatlaf-testing/build.gradle.kts b/flatlaf-testing/build.gradle.kts index 4cf8b850..4e84247b 100644 --- a/flatlaf-testing/build.gradle.kts +++ b/flatlaf-testing/build.gradle.kts @@ -27,12 +27,12 @@ repositories { dependencies { implementation( project( ":flatlaf-core" ) ) - implementation( project( ":flatlaf-natives-jna" ) ) implementation( project( ":flatlaf-extras" ) ) implementation( project( ":flatlaf-swingx" ) ) implementation( project( ":flatlaf-jide-oss" ) ) implementation( project( ":flatlaf-intellij-themes" ) ) implementation( project( ":flatlaf-demo" ) ) +// implementation( project( ":flatlaf-natives-jna" ) ) implementation( "com.miglayout:miglayout-swing:5.3-SNAPSHOT" ) implementation( "com.jgoodies:jgoodies-forms:1.9.0" ) diff --git a/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0_202.txt b/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0_202.txt index b78e38e2..787ed4df 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0_202.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0_202.txt @@ -1143,6 +1143,7 @@ TitlePane.menuBarEmbedded true TitlePane.menuBarMargins 0,8,0,22 javax.swing.plaf.InsetsUIResource [UI] TitlePane.restoreIcon [lazy] 44,30 com.formdev.flatlaf.icons.FlatWindowRestoreIcon [UI] TitlePane.titleMargins 3,8,3,8 javax.swing.plaf.InsetsUIResource [UI] +TitlePane.useWindowDecorations true #---- TitledBorder ---- diff --git a/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0_202.txt b/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0_202.txt index 5a476a0d..578a317e 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0_202.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0_202.txt @@ -1148,6 +1148,7 @@ TitlePane.menuBarEmbedded true TitlePane.menuBarMargins 0,8,0,22 javax.swing.plaf.InsetsUIResource [UI] TitlePane.restoreIcon [lazy] 44,30 com.formdev.flatlaf.icons.FlatWindowRestoreIcon [UI] TitlePane.titleMargins 3,8,3,8 javax.swing.plaf.InsetsUIResource [UI] +TitlePane.useWindowDecorations true #---- TitledBorder ---- diff --git a/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0_202.txt b/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0_202.txt index 09980acc..c89dd1a6 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0_202.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0_202.txt @@ -1140,6 +1140,7 @@ TitlePane.menuBarEmbedded true TitlePane.menuBarMargins 0,8,0,22 javax.swing.plaf.InsetsUIResource [UI] TitlePane.restoreIcon [lazy] 44,30 com.formdev.flatlaf.icons.FlatWindowRestoreIcon [UI] TitlePane.titleMargins 3,8,3,8 javax.swing.plaf.InsetsUIResource [UI] +TitlePane.useWindowDecorations true #---- TitledBorder ---- diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatNativeWindowBorderTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatNativeWindowBorderTest.java index 792695be..0aae3173 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatNativeWindowBorderTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatNativeWindowBorderTest.java @@ -50,9 +50,6 @@ public class FlatNativeWindowBorderTest FlatLightLaf.install(); FlatInspector.install( "ctrl shift alt X" ); - JFrame.setDefaultLookAndFeelDecorated( true ); - JDialog.setDefaultLookAndFeelDecorated( true ); - mainFrame = showFrame(); } ); } diff --git a/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt b/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt index 87cf05c8..dd90b82a 100644 --- a/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt +++ b/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt @@ -853,6 +853,7 @@ TitlePane.menuBarEmbedded TitlePane.menuBarMargins TitlePane.restoreIcon TitlePane.titleMargins +TitlePane.useWindowDecorations TitledBorder.border TitledBorder.font TitledBorder.titleColor diff --git a/settings.gradle.kts b/settings.gradle.kts index 260ca231..8acdb6d6 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -25,6 +25,7 @@ include( "flatlaf-demo" ) include( "flatlaf-testing" ) include( "flatlaf-theme-editor" ) +includeProject( "flatlaf-natives-windows", "flatlaf-natives/flatlaf-natives-windows" ) includeProject( "flatlaf-natives-jna", "flatlaf-natives/flatlaf-natives-jna" ) fun includeProject( projectPath: String, projectDir: String ) {