Native window decorations: initial implementation in C++ using JNI

This commit is contained in:
Karl Tauber
2021-03-05 10:22:52 +01:00
parent a6815574f7
commit 144d65c776
18 changed files with 1245 additions and 13 deletions

3
.gitattributes vendored
View File

@@ -15,8 +15,11 @@
# BINARY FILES: # BINARY FILES:
# Disable line ending normalize on checkin. # Disable line ending normalize on checkin.
*.dll binary
*.dylib binary
*.gif binary *.gif binary
*.jar binary *.jar binary
*.png binary *.png binary
*.sketch binary *.sketch binary
*.so binary
*.zip binary *.zip binary

56
.github/workflows/natives.yml vendored Normal file
View File

@@ -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

View File

@@ -27,6 +27,16 @@ java {
} }
tasks { tasks {
compileJava {
// generate JNI headers
options.headerOutputDirectory.set( buildDir.resolve( "generated/jni-headers" ) )
}
processResources {
// build native libraries
dependsOn( ":flatlaf-natives-windows:assemble" )
}
jar { jar {
archiveBaseName.set( "flatlaf" ) archiveBaseName.set( "flatlaf" )

View File

@@ -165,7 +165,7 @@ public abstract class FlatLaf
* <p> * <p>
* Returns also {@code false} on Windows 10 if: * Returns also {@code false} on Windows 10 if:
* <ul> * <ul>
* <li>FlatLaf native window border support is available (requires {@code flatlaf-natives-jna.jar})</li> * <li>FlatLaf native window border support is available (requires Windows 10 64bit)</li>
* <li>running in * <li>running in
* <a href="https://confluence.jetbrains.com/display/JBR/JetBrains+Runtime">JetBrains Runtime 11 (or later)</a> * <a href="https://confluence.jetbrains.com/display/JBR/JetBrains+Runtime">JetBrains Runtime 11 (or later)</a>
* (<a href="https://github.com/JetBrains/JetBrainsRuntime">source code on github</a>) * (<a href="https://github.com/JetBrains/JetBrainsRuntime">source code on github</a>)

View File

@@ -74,14 +74,13 @@ public interface FlatSystemProperties
/** /**
* Specifies whether FlatLaf native window decorations should be used * Specifies whether FlatLaf native window decorations should be used
* when creating {@code JFrame} or {@code JDialog}. * when creating {@code JFrame} or {@code JDialog}.
* Requires that {@code flatlaf-natives-jna.jar} is on classpath/modulepath.
* <p> * <p>
* Setting this to {@code true} forces using FlatLaf native window decorations * Setting this to {@code true} forces using FlatLaf native window decorations
* even if they are not enabled by the application. * even if they are not enabled by the application.
* <p> * <p>
* Setting this to {@code false} disables using FlatLaf native window decorations. * Setting this to {@code false} disables using FlatLaf native window decorations.
* <p> * <p>
* (requires Window 10) * (requires Window 10 64bit)
* <p> * <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br> * <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> none * <strong>Default</strong> none

View File

@@ -20,7 +20,6 @@ import java.awt.Color;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.Window; import java.awt.Window;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.lang.reflect.Method;
import java.util.List; import java.util.List;
import javax.swing.JDialog; import javax.swing.JDialog;
import javax.swing.JFrame; import javax.swing.JFrame;
@@ -228,9 +227,12 @@ public class FlatNativeWindowBorder
return; return;
try { try {
/*
Class<?> cls = Class.forName( "com.formdev.flatlaf.natives.jna.windows.FlatWindowsNativeWindowBorder" ); Class<?> cls = Class.forName( "com.formdev.flatlaf.natives.jna.windows.FlatWindowsNativeWindowBorder" );
Method m = cls.getMethod( "getInstance" ); Method m = cls.getMethod( "getInstance" );
nativeProvider = (Provider) m.invoke( null ); nativeProvider = (Provider) m.invoke( null );
*/
nativeProvider = FlatWindowsNativeWindowBorder.getInstance();
supported = (nativeProvider != null); supported = (nativeProvider != null);
} catch( Exception ex ) { } catch( Exception ex ) {

View File

@@ -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.
* <p>
* 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<Window, WndProc> 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 <jdk>/bin/java.exe.
// When using <jdk>/jre/bin/java.exe, it is found.
// jawt.dll is located in <jdk>/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<Rectangle> 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();
}
}
}

View File

@@ -0,0 +1,127 @@
/*
* 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.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
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.
* <p>
* Copies native library to users temporary folder before loading it.
*
* @author Karl Tauber
*/
public class NativeLibrary
{
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.
* <p>
* 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;
}
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 = Files.createTempFile( "jni", basename( libraryName ) );
File tempFile = tempPath.toFile();
//TODO this does not work on Windows
tempFile.deleteOnExit();
// copy library to temporary file
try( InputStream in = libraryUrl.openStream() ) {
Files.copy( in, tempPath, StandardCopyOption.REPLACE_EXISTING );
}
// load library
System.load( tempFile.getCanonicalPath() );
return true;
} catch( Throwable ex ) {
log( null, ex );
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 String basename( String libName ) {
int sep = libName.lastIndexOf( '/' );
return (sep >= 0) ? libName.substring( sep + 1 ) : libName;
}
private static void log( String msg, Throwable thrown ) {
Logger.getLogger( FlatLaf.class.getName() ).log( Level.SEVERE, msg, thrown );
}
}

View File

@@ -27,19 +27,19 @@ repositories {
dependencies { dependencies {
implementation( project( ":flatlaf-core" ) ) implementation( project( ":flatlaf-core" ) )
implementation( project( ":flatlaf-natives-jna" ) )
implementation( project( ":flatlaf-extras" ) ) implementation( project( ":flatlaf-extras" ) )
implementation( project( ":flatlaf-intellij-themes" ) ) implementation( project( ":flatlaf-intellij-themes" ) )
implementation( "com.miglayout:miglayout-swing:5.3-SNAPSHOT" ) implementation( "com.miglayout:miglayout-swing:5.3-SNAPSHOT" )
implementation( "com.jgoodies:jgoodies-forms:1.9.0" ) implementation( "com.jgoodies:jgoodies-forms:1.9.0" )
// implementation( project( ":flatlaf-natives-jna" ) )
} }
tasks { tasks {
jar { jar {
dependsOn( ":flatlaf-core:jar" ) dependsOn( ":flatlaf-core:jar" )
dependsOn( ":flatlaf-natives-jna:jar" )
dependsOn( ":flatlaf-extras:jar" ) dependsOn( ":flatlaf-extras:jar" )
dependsOn( ":flatlaf-intellij-themes:jar" ) dependsOn( ":flatlaf-intellij-themes:jar" )
// dependsOn( ":flatlaf-natives-jna:jar" )
manifest { manifest {
attributes( "Main-Class" to "com.formdev.flatlaf.demo.FlatLafDemo" ) attributes( "Main-Class" to "com.formdev.flatlaf.demo.FlatLafDemo" )

View File

@@ -207,20 +207,20 @@ public class FlatWindowsNativeWindowBorder
String subKey = "SOFTWARE\\Microsoft\\Windows\\DWM"; String subKey = "SOFTWARE\\Microsoft\\Windows\\DWM";
int value = RegGetDword( HKEY_CURRENT_USER, subKey, "ColorPrevalence" ); int value = registryGetIntValue( subKey, "ColorPrevalence", -1 );
colorizationColorAffectsBorders = (value > 0); colorizationColorAffectsBorders = (value > 0);
value = RegGetDword( HKEY_CURRENT_USER, subKey, "ColorizationColor" ); value = registryGetIntValue( subKey, "ColorizationColor", -1 );
colorizationColor = (value != -1) ? new Color( value ) : null; 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 { try {
return Advapi32Util.registryGetIntValue( hkey, lpSubKey, lpValue ); return Advapi32Util.registryGetIntValue( HKEY_CURRENT_USER, key, valueName );
} catch( RuntimeException ex ) { } catch( RuntimeException ex ) {
return -1; return defaultValue;
} }
} }

View File

@@ -0,0 +1,90 @@
/*
* 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 {
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<org.apache.tools.ant.filters.FixCrLfFilter>(
"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" )
else -> emptyList()
}
} )
}
linkTask.configure {
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" )
is VisualCpp -> listOf( "${jawt}.lib", "User32.lib", "shell32.lib", "AdvAPI32.lib" )
else -> emptyList()
}
} )
doLast {
// copy shared library to flatlaf-core resources
copy {
from( linkedFile )
into( nativesDir )
rename( "flatlaf-natives-windows.dll", libraryName )
}
}
}
}
}
}

View File

@@ -0,0 +1,399 @@
/*
* 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 <windows.h>
#include <windowsx.h>
#include <shellapi.h>
#include <jawt.h>
#include <jawt_md.h>
#include <map>
#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<jlong>( 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>( hwnd ) );
}
//---- class FlatWndProc fields -----------------------------------------------
int FlatWndProc::initialized = 0;
jmethodID FlatWndProc::onNcHitTestMID;
jmethodID FlatWndProc::isFullscreenMID;
jmethodID FlatWndProc::fireStateChangedLaterOnceMID;
std::map<HWND, FlatWndProc*> FlatWndProc::hwndMap = std::map<HWND, FlatWndProc*>();
//---- 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;
// get window handle
HWND hwnd = getWindowHandle( env, window );
if( hwnd == NULL || hwndMap.count( hwnd ) > 0 )
return 0;
FlatWndProc* fwp = new FlatWndProc();
env->GetJavaVM( &fwp->jvm );
fwp->obj = env->NewGlobalRef( obj );
fwp->hwnd = hwnd;
hwndMap[hwnd] = fwp;
// replace window procedure
fwp->defaultWndProc = reinterpret_cast<WNDPROC>(
::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 || hwndMap.count( hwnd ) == 0 )
return;
FlatWndProc* fwp = hwndMap[hwnd];
hwndMap.erase( 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 = hwndMap[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.erase( 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<NCCALCSIZE_PARAMS*>( 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
bool isMaximized = ::IsZoomed( hwnd );
setMenuItemState( systemMenu, SC_RESTORE, isMaximized );
setMenuItemState( systemMenu, SC_MOVE, !isMaximized );
setMenuItemState( systemMenu, SC_SIZE, !isMaximized );
setMenuItemState( systemMenu, SC_MINIMIZE, true );
setMenuItemState( systemMenu, SC_MAXIMIZE, !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;
}

View File

@@ -0,0 +1,63 @@
/*
* 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 <windows.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 std::map<HWND, FlatWndProc*> 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 );
};

View File

@@ -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 <windows.h>
#include <winreg.h>
#include <winerror.h>
#include <jni.h>
#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;
}

View File

@@ -0,0 +1,21 @@
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* 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

View File

@@ -0,0 +1,37 @@
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* 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

View File

@@ -27,12 +27,12 @@ repositories {
dependencies { dependencies {
implementation( project( ":flatlaf-core" ) ) implementation( project( ":flatlaf-core" ) )
implementation( project( ":flatlaf-natives-jna" ) )
implementation( project( ":flatlaf-extras" ) ) implementation( project( ":flatlaf-extras" ) )
implementation( project( ":flatlaf-swingx" ) ) implementation( project( ":flatlaf-swingx" ) )
implementation( project( ":flatlaf-jide-oss" ) ) implementation( project( ":flatlaf-jide-oss" ) )
implementation( project( ":flatlaf-intellij-themes" ) ) implementation( project( ":flatlaf-intellij-themes" ) )
implementation( project( ":flatlaf-demo" ) ) implementation( project( ":flatlaf-demo" ) )
// implementation( project( ":flatlaf-natives-jna" ) )
implementation( "com.miglayout:miglayout-swing:5.3-SNAPSHOT" ) implementation( "com.miglayout:miglayout-swing:5.3-SNAPSHOT" )
implementation( "com.jgoodies:jgoodies-forms:1.9.0" ) implementation( "com.jgoodies:jgoodies-forms:1.9.0" )

View File

@@ -25,6 +25,7 @@ include( "flatlaf-demo" )
include( "flatlaf-testing" ) include( "flatlaf-testing" )
include( "flatlaf-theme-editor" ) include( "flatlaf-theme-editor" )
includeProject( "flatlaf-natives-windows", "flatlaf-natives/flatlaf-natives-windows" )
includeProject( "flatlaf-natives-jna", "flatlaf-natives/flatlaf-natives-jna" ) includeProject( "flatlaf-natives-jna", "flatlaf-natives/flatlaf-natives-jna" )
fun includeProject( projectPath: String, projectDir: String ) { fun includeProject( projectPath: String, projectDir: String ) {