From 0ad3180b10341623ce3cf41a481e024b3f96469f Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Thu, 9 Jan 2025 12:04:25 +0100 Subject: [PATCH 01/11] FileChooser: improved performance when navigating to large directories with thousands of files (issue #953) --- CHANGELOG.md | 5 ++ .../formdev/flatlaf/ui/FlatFileChooserUI.java | 77 ++++++++++++++----- 2 files changed, 62 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e24a6398..27e020a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,11 @@ FlatLaf Change Log - Extras: `FlatSVGIcon` color filters now can access painting component to implement component state based color mappings. (issue #906) +#### Fixed bugs + +- FileChooser: Improved performance when navigating to large directories with + thousands of files. (issue #953) + ## 3.5.4 diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatFileChooserUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatFileChooserUI.java index 59f360ad..9f88aee7 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatFileChooserUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatFileChooserUI.java @@ -376,31 +376,68 @@ public class FlatFileChooserUI if( icon != null ) return icon; - // get system icon - if( f != null ) { - try { - icon = getFileChooser().getFileSystemView().getSystemIcon( f ); - } catch( NullPointerException ex ) { - // Java 21 may throw a NPE for exe files that use default Windows exe icon - } + // new proxy icon + // + // Note: Since this is a super light weight icon object, we do not add it + // to the icon cache here. This keeps cache small in case of large directories + // with thousands of files when icons of all files are only needed to compute + // the layout of list/table, but never painted because located outside of visible area. + // When an icon needs to be painted, the proxy adds it to the icon cache + // and loads the real icon. + return new FlatFileViewIcon( f ); + } - if( icon != null ) { - if( icon instanceof ImageIcon ) - icon = new ScaledImageIcon( (ImageIcon) icon ); - cacheIcon( f, icon ); - return icon; - } + //---- class FlatFileViewIcon ----------------------------------------- + + /** + * A proxy icon that has a fixed (scaled) width/height (16x16) and + * gets/loads the real (system) icon only for painting. + * Avoids unnecessary getting/loading system icons. + */ + private class FlatFileViewIcon + implements Icon + { + private final File f; + private Icon realIcon; + + FlatFileViewIcon( File f ) { + this.f = f; } - // get default icon - icon = super.getIcon( f ); - - if( icon instanceof ImageIcon ) { - icon = new ScaledImageIcon( (ImageIcon) icon ); - cacheIcon( f, icon ); + @Override + public int getIconWidth() { + return UIScale.scale( 16 ); } - return icon; + @Override + public int getIconHeight() { + return UIScale.scale( 16 ); + } + + @Override + public void paintIcon( Component c, Graphics g, int x, int y ) { + // get icon on demand + if( realIcon == null ) { + // get system icon + try { + if( f != null ) + realIcon = getFileChooser().getFileSystemView().getSystemIcon( f ); + } catch( NullPointerException ex ) { + // Java 21 may throw a NPE for exe files that use default Windows exe icon + } + + // get default icon + if( realIcon == null ) + realIcon = FlatFileView.super.getIcon( f ); + + if( realIcon instanceof ImageIcon ) + realIcon = new ScaledImageIcon( (ImageIcon) realIcon ); + + cacheIcon( f, this ); + } + + realIcon.paintIcon( c, g, x, y ); + } } } From d17fffb82ab86afb1d8386cc872419b7e9e58b70 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Thu, 9 Jan 2025 12:26:57 +0100 Subject: [PATCH 02/11] PopupFactory: fixed NPE on Windows 10 when `owner` is `null` (issue #952) --- CHANGELOG.md | 1 + .../main/java/com/formdev/flatlaf/ui/FlatPopupFactory.java | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27e020a4..ff72f13f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ FlatLaf Change Log - FileChooser: Improved performance when navigating to large directories with thousands of files. (issue #953) +- PopupFactory: Fixed NPE on Windows 10 when `owner` is `null`. (issue #952) ## 3.5.4 diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupFactory.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupFactory.java index c5621132..7ab53ece 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupFactory.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPopupFactory.java @@ -138,7 +138,7 @@ public class FlatPopupFactory // create drop shadow popup Popup popupForScreenOfOwner = getPopupForScreenOfOwner( owner, contents, x, y, forceHeavyWeight ); - GraphicsConfiguration gc = owner.getGraphicsConfiguration(); + GraphicsConfiguration gc = (owner != null) ? owner.getGraphicsConfiguration() : null; return (gc != null && gc.isTranslucencyCapable()) ? new DropShadowPopup( popupForScreenOfOwner, owner, contents ) : new NonFlashingPopup( popupForScreenOfOwner, owner, contents ); @@ -306,7 +306,7 @@ public class FlatPopupFactory break; } } - if( gc == null ) + if( gc == null && owner != null ) gc = owner.getGraphicsConfiguration(); if( gc == null ) return null; From 8dc6242889f95b579bdfc9c5a600b65a09449e62 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Thu, 9 Jan 2025 14:19:44 +0100 Subject: [PATCH 03/11] FlatLaf window decorations: minimize and maximize icons were not shown for custom scale factors less than 100% (e.g. `-Dflatlaf.uiScale=75%) (issue #951) --- CHANGELOG.md | 3 +++ .../java/com/formdev/flatlaf/icons/FlatWindowCloseIcon.java | 2 +- .../java/com/formdev/flatlaf/icons/FlatWindowIconifyIcon.java | 2 +- .../java/com/formdev/flatlaf/icons/FlatWindowMaximizeIcon.java | 2 +- .../java/com/formdev/flatlaf/icons/FlatWindowRestoreIcon.java | 2 +- 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff72f13f..8ec1db4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ FlatLaf Change Log - FileChooser: Improved performance when navigating to large directories with thousands of files. (issue #953) - PopupFactory: Fixed NPE on Windows 10 when `owner` is `null`. (issue #952) +- FlatLaf window decorations: Minimize and maximize icons were not shown for + custom scale factors less than 100% (e.g. `-Dflatlaf.uiScale=75%`). (issue + #951) ## 3.5.4 diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatWindowCloseIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatWindowCloseIcon.java index 99625d75..a4fd835d 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatWindowCloseIcon.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatWindowCloseIcon.java @@ -63,7 +63,7 @@ public class FlatWindowCloseIcon int iy = y + ((height - iwh) / 2); int ix2 = ix + iwh - 1; int iy2 = iy + iwh - 1; - float thickness = SystemInfo.isWindows_11_orLater ? (float) scaleFactor : (int) scaleFactor; + float thickness = Math.max( SystemInfo.isWindows_11_orLater ? (float) scaleFactor : (int) scaleFactor, 1 ); Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD, 4 ); path.moveTo( ix, iy ); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatWindowIconifyIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatWindowIconifyIcon.java index 35d32c20..e7d11cda 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatWindowIconifyIcon.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatWindowIconifyIcon.java @@ -38,7 +38,7 @@ public class FlatWindowIconifyIcon @Override protected void paintIconAt1x( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) { int iw = (int) (getSymbolHeight() * scaleFactor); - int ih = (int) scaleFactor; + int ih = Math.max( (int) scaleFactor, 1 ); int ix = x + ((width - iw) / 2); int iy = y + ((height - ih) / 2); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatWindowMaximizeIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatWindowMaximizeIcon.java index f95f379d..a731c21b 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatWindowMaximizeIcon.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatWindowMaximizeIcon.java @@ -42,7 +42,7 @@ public class FlatWindowMaximizeIcon int iwh = (int) (getSymbolHeight() * scaleFactor); int ix = x + ((width - iwh) / 2); int iy = y + ((height - iwh) / 2); - float thickness = SystemInfo.isWindows_11_orLater ? (float) scaleFactor : (int) scaleFactor; + float thickness = Math.max( SystemInfo.isWindows_11_orLater ? (float) scaleFactor : (int) scaleFactor, 1 ); int arc = Math.max( (int) (1.5 * scaleFactor), 2 ); g.fill( SystemInfo.isWindows_11_orLater diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatWindowRestoreIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatWindowRestoreIcon.java index da2e18bb..4e216495 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatWindowRestoreIcon.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatWindowRestoreIcon.java @@ -45,7 +45,7 @@ public class FlatWindowRestoreIcon int iwh = (int) (getSymbolHeight() * scaleFactor); int ix = x + ((width - iwh) / 2); int iy = y + ((height - iwh) / 2); - float thickness = SystemInfo.isWindows_11_orLater ? (float) scaleFactor : (int) scaleFactor; + float thickness = Math.max( SystemInfo.isWindows_11_orLater ? (float) scaleFactor : (int) scaleFactor, 1 ); int arc = Math.max( (int) (1.5 * scaleFactor), 2 ); int arcOuter = (int) (arc + (1.5 * scaleFactor)); From a238fd4505f0e42647cc3f2567d23702b4896c08 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Thu, 9 Jan 2025 14:38:36 +0100 Subject: [PATCH 04/11] Button: fixed background and foreground colors for `borderless` and `toolBarButton` style default buttons (`JButton.isDefaultButton()` is `true`) (issue #947) --- CHANGELOG.md | 3 +++ .../main/java/com/formdev/flatlaf/ui/FlatButtonUI.java | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ec1db4c..2435529e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,9 @@ FlatLaf Change Log #### Fixed bugs +- Button: Fixed background and foreground colors for `borderless` and + `toolBarButton` style default buttons (`JButton.isDefaultButton()` is `true`). + (issue #947) - FileChooser: Improved performance when navigating to large directories with thousands of files. (issue #953) - PopupFactory: Fixed NPE on Windows 10 when `owner` is `null`. (issue #952) diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatButtonUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatButtonUI.java index 41691c1b..db9f6838 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatButtonUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatButtonUI.java @@ -653,7 +653,8 @@ public class FlatButtonUI } protected Color getBackground( JComponent c ) { - boolean toolBarButton = isToolBarButton( c ) || isBorderlessButton( c ); + boolean def = isDefaultButton( c ); + boolean toolBarButton = !def && (isToolBarButton( c ) || isBorderlessButton( c )); // selected state if( ((AbstractButton)c).isSelected() ) { @@ -681,7 +682,6 @@ public class FlatButtonUI toolbarPressedBackground ); } - boolean def = isDefaultButton( c ); return buttonStateColor( c, getBackgroundBase( c, def ), disabledBackground, @@ -733,7 +733,8 @@ public class FlatButtonUI protected Color getForeground( JComponent c ) { Color fg = c.getForeground(); - boolean toolBarButton = isToolBarButton( c ) || isBorderlessButton( c ); + boolean def = isDefaultButton( c ); + boolean toolBarButton = !def && (isToolBarButton( c ) || isBorderlessButton( c )); // selected state if( ((AbstractButton)c).isSelected() ) { @@ -759,7 +760,6 @@ public class FlatButtonUI toolbarPressedForeground ); } - boolean def = isDefaultButton( c ); return buttonStateColor( c, getForegroundBase( c, def ), disabledText, From ac0cceb09b6406aa83ab294f97bfe12cb0226ffb Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Thu, 9 Jan 2025 15:19:47 +0100 Subject: [PATCH 05/11] added Exocharts as Gold sponsor --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fe7dfefa..514b72db 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,9 @@ Sponsors ### Current Sponsors -[![None Sponsors](images/none-sponsors.png)](https://www.formdev.com/flatlaf/sponsor/) +Exocharts + + [Become a Sponsor](https://www.formdev.com/flatlaf/sponsor/) From f11f68282b9a0677bb36dacf86e3553efc5165c8 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Thu, 9 Jan 2025 15:23:49 +0100 Subject: [PATCH 06/11] update to Gradle 8.12 --- .github/workflows/ci.yml | 2 +- .github/workflows/natives.yml | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 3 +-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d1f0e1e9..57acad88 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: gradle/actions/wrapper-validation@v3 + - uses: gradle/actions/wrapper-validation@v4 if: matrix.java == '8' - name: Setup Java ${{ matrix.java }} diff --git a/.github/workflows/natives.yml b/.github/workflows/natives.yml index bae5cb2a..cf314dcc 100644 --- a/.github/workflows/natives.yml +++ b/.github/workflows/natives.yml @@ -30,7 +30,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: gradle/actions/wrapper-validation@v3 + - uses: gradle/actions/wrapper-validation@v4 - name: Setup Java 11 uses: actions/setup-java@v4 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e2847c82..cea7a793 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index f5feea6d..f3b75f3b 100755 --- a/gradlew +++ b/gradlew @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum From ddee4ef52653cb4b691b94f54e94ebe09c236628 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Thu, 9 Jan 2025 15:57:46 +0100 Subject: [PATCH 07/11] GitHub Actions: natives.yml: install `libxt-dev` to fix build error on ubuntu 24.04 (did work without this on ubuntu 22.04) --- .github/workflows/natives.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/natives.yml b/.github/workflows/natives.yml index cf314dcc..b575eed8 100644 --- a/.github/workflows/natives.yml +++ b/.github/workflows/natives.yml @@ -32,6 +32,10 @@ jobs: - uses: gradle/actions/wrapper-validation@v4 + - name: install libxt-dev + if: matrix.os == 'ubuntu' + run: sudo apt install libxt-dev + - name: Setup Java 11 uses: actions/setup-java@v4 with: From 6c77b81277df4725c1aeb507723e9b87bb1907c5 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Thu, 16 Jan 2025 20:10:57 +0100 Subject: [PATCH 08/11] Linux: support building native library for ARM64 when Gradle build runs on ARM64 Linux (issue #899) --- .../flatlaf-natives-linux/build.gradle.kts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/flatlaf-natives/flatlaf-natives-linux/build.gradle.kts b/flatlaf-natives/flatlaf-natives-linux/build.gradle.kts index a81d6643..737385e3 100644 --- a/flatlaf-natives/flatlaf-natives-linux/build.gradle.kts +++ b/flatlaf-natives/flatlaf-natives-linux/build.gradle.kts @@ -28,11 +28,14 @@ flatlafJniHeaders { } library { - targetMachines = listOf( machines.linux.x86_64 ) + targetMachines = listOf( + machines.linux.x86_64, + machines.linux.architecture( "aarch64" ), + ) } var javaHome = System.getProperty( "java.home" ) -if( javaHome.endsWith( "jre" ) ) +if( javaHome.endsWith( "jre" ) && !file( "${javaHome}/include" ).exists() ) javaHome += "/.." tasks { @@ -40,8 +43,13 @@ tasks { group = "build" description = "Builds natives" - if( org.gradle.internal.os.OperatingSystem.current().isLinux ) - dependsOn( "linkRelease" ) + if( org.gradle.internal.os.OperatingSystem.current().isLinux ) { + val osArch = System.getProperty( "os.arch" ) + if( osArch == "amd64" || osArch == "x86-64" ) + dependsOn( "linkReleaseX86-64" ) + if( osArch == "aarch64" ) + dependsOn( "linkReleaseAarch64" ) + } } withType().configureEach { @@ -67,7 +75,7 @@ tasks { onlyIf { name.contains( "Release" ) } val nativesDir = project( ":flatlaf-core" ).projectDir.resolve( "src/main/resources/com/formdev/flatlaf/natives" ) - val libraryName = "libflatlaf-linux-x86_64.so" + val libraryName = if( name.contains( "X86-64" ) ) "libflatlaf-linux-x86_64.so" else "libflatlaf-linux-arm64.so" val jawt = "jawt" var jawtPath = "${javaHome}/lib" if( JavaVersion.current() == JavaVersion.VERSION_1_8 ) From 0ea188f8db576186f4e4b7f21827d072a7f273c2 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sat, 18 Jan 2025 14:45:02 +0100 Subject: [PATCH 09/11] Linux: support cross-compile native library for ARM64 on x86_64 Linux (issue #899) --- .github/workflows/natives.yml | 4 ++ .../flatlaf-natives-linux/README.md | 25 +++++-- .../flatlaf-natives-linux/build.gradle.kts | 68 +++++++++++++++++- .../flatlaf-natives-linux/lib/README.md | 4 ++ .../lib/aarch64/libjawt.so | Bin 0 -> 69544 bytes .../flatlaf-natives-windows/README.md | 4 +- 6 files changed, 95 insertions(+), 10 deletions(-) create mode 100644 flatlaf-natives/flatlaf-natives-linux/lib/README.md create mode 100644 flatlaf-natives/flatlaf-natives-linux/lib/aarch64/libjawt.so diff --git a/.github/workflows/natives.yml b/.github/workflows/natives.yml index b575eed8..96f2a44d 100644 --- a/.github/workflows/natives.yml +++ b/.github/workflows/natives.yml @@ -36,6 +36,10 @@ jobs: if: matrix.os == 'ubuntu' run: sudo apt install libxt-dev + - name: install g++-aarch64-linux-gnu + if: matrix.os == 'ubuntu' + run: sudo apt install g++-aarch64-linux-gnu + - name: Setup Java 11 uses: actions/setup-java@v4 with: diff --git a/flatlaf-natives/flatlaf-natives-linux/README.md b/flatlaf-natives/flatlaf-natives-linux/README.md index 3dd00c68..4be74776 100644 --- a/flatlaf-natives/flatlaf-natives-linux/README.md +++ b/flatlaf-natives/flatlaf-natives-linux/README.md @@ -5,12 +5,15 @@ This sub-project contains the source code for the FlatLaf Linux native library. The native library can be built only on Linux and requires a C++ compiler. +The native library is available for following CPU architectures: `x86_64` (or +`amd64`) and `arm64` (or `aarch64`). + To be able to build FlatLaf on any platform, and without C++ compiler, the -pre-built native library is checked into Git at +pre-built native libraries are checked into Git at [flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/](https://github.com/JFormDesigner/FlatLaf/tree/main/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives). -The native library was built on a GitHub server with the help of GitHub Actions. -See: +The native libraries were built on a GitHub server with the help of GitHub +Actions. See: [Native Libraries](https://github.com/JFormDesigner/FlatLaf/actions/workflows/natives.yml) workflow. Then the produced Artifacts ZIP was downloaded and the native library checked into Git. @@ -18,19 +21,27 @@ checked into Git. ## Development -To build the library on Linux, some packages needs to be installed. +To build the library on Linux, some packages needs to be installed: + +- `build-essential` - GCC and development tools +- `libxt-dev` - X11 toolkit development headers +- `g++-aarch64-linux-gnu` - GNU C++ compiler for the arm64 architecture (only on + x86_64 Linux for cross-compiling for arm64 architecture) ### Ubuntu -`build-essential` contains GCC and development tools. `libxt-dev` contains the -X11 toolkit development headers. - ~~~ sudo apt update sudo apt install build-essential libxt-dev ~~~ +Only on x86_64 Linux for cross-compiling for arm64 architecture: + +~~~ +sudo apt install g++-aarch64-linux-gnu +~~~ + ### CentOS diff --git a/flatlaf-natives/flatlaf-natives-linux/build.gradle.kts b/flatlaf-natives/flatlaf-natives-linux/build.gradle.kts index 737385e3..abfad042 100644 --- a/flatlaf-natives/flatlaf-natives-linux/build.gradle.kts +++ b/flatlaf-natives/flatlaf-natives-linux/build.gradle.kts @@ -45,8 +45,11 @@ tasks { if( org.gradle.internal.os.OperatingSystem.current().isLinux ) { val osArch = System.getProperty( "os.arch" ) - if( osArch == "amd64" || osArch == "x86-64" ) + if( osArch == "amd64" ) { dependsOn( "linkReleaseX86-64" ) + if( file( "/usr/bin/aarch64-linux-gnu-gcc" ).exists() ) + dependsOn( "linkCrossAarch64" ) + } if( osArch == "aarch64" ) dependsOn( "linkReleaseAarch64" ) } @@ -97,4 +100,67 @@ tasks { } } } + + if( org.gradle.internal.os.OperatingSystem.current().isLinux && + System.getProperty( "os.arch" ) == "amd64" && + file( "/usr/bin/aarch64-linux-gnu-gcc" ).exists() ) + { + register( "compileCrossAarch64Cpp" ) { + val include = layout.projectDirectory.dir( "src/main/headers" ) + val src = layout.projectDirectory.dir( "src/main/cpp" ) + workingDir = file( layout.buildDirectory.dir( "obj/main/release/aarch64-cross" ) ) + + doFirst { + workingDir.mkdirs() + } + + commandLine = listOf( + "aarch64-linux-gnu-gcc", + "-c", + "-fPIC", + "-fvisibility=hidden", + "-O3", + "-I", "${javaHome}/include", + "-I", "${javaHome}/include/linux", + "-I", "$include", + + "$src/ApiVersion.cpp", + "$src/X11WmUtils.cpp", + ) + } + + register( "linkCrossAarch64" ) { + dependsOn( "compileCrossAarch64Cpp" ) + + val nativesDir = project( ":flatlaf-core" ).projectDir.resolve( "src/main/resources/com/formdev/flatlaf/natives" ) + val libraryName = "libflatlaf-linux-arm64.so" + val outDir = file( layout.buildDirectory.dir( "lib/main/release/aarch64-cross" ) ) + val objDir = file( layout.buildDirectory.dir( "obj/main/release/aarch64-cross" ) ) + + doFirst { + outDir.mkdirs() + } + + commandLine = listOf( + "aarch64-linux-gnu-gcc", + "-shared", + "-Wl,-soname,$libraryName", + "-o", "$outDir/$libraryName", + + "$objDir/ApiVersion.o", + "$objDir/X11WmUtils.o", + + "-L${layout.projectDirectory}/lib/aarch64", + "-ljawt", + ) + + doLast { + // copy shared library to flatlaf-core resources + copy { + from( "$outDir/$libraryName" ) + into( nativesDir ) + } + } + } + } } diff --git a/flatlaf-natives/flatlaf-natives-linux/lib/README.md b/flatlaf-natives/flatlaf-natives-linux/lib/README.md new file mode 100644 index 00000000..f0f72077 --- /dev/null +++ b/flatlaf-natives/flatlaf-natives-linux/lib/README.md @@ -0,0 +1,4 @@ +Contains libraries used to compile FlatLaf native libraries. + +- `aarch64/libjawt.so` is `/lib/libjawt.so` from Eclipse Temurin 11.0.25+9 aarch64, + which is required to cross build aarch64/arm64 .so on x86_64 diff --git a/flatlaf-natives/flatlaf-natives-linux/lib/aarch64/libjawt.so b/flatlaf-natives/flatlaf-natives-linux/lib/aarch64/libjawt.so new file mode 100644 index 0000000000000000000000000000000000000000..1c03d7df1523c62187a4c15fd9d25e4794dfed8d GIT binary patch literal 69544 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 MzS5{@JIVb20?{8XYXATM literal 0 HcmV?d00001 diff --git a/flatlaf-natives/flatlaf-natives-windows/README.md b/flatlaf-natives/flatlaf-natives-windows/README.md index c164e381..f8504fb8 100644 --- a/flatlaf-natives/flatlaf-natives-windows/README.md +++ b/flatlaf-natives/flatlaf-natives-windows/README.md @@ -8,8 +8,8 @@ The native library can be built only on Windows and requires a C++ compiler. Tested with Microsoft Visual C++ (MSVC) 2019 and 2022 (comes with Visual Studio 2019 and 2022). -The native library is available for folloging CPU architectures: `x86_64` (or -`amd64`), `x86` and `arm64`. +The native library is available for following CPU architectures: `x86_64` (or +`amd64`), `x86` and `arm64` (or `aarch64`). To be able to build FlatLaf on any platform, and without C++ compiler, the pre-built DLLs are checked into Git at From 91f19bf94c2e65db9b75be3afb2edcd5c3a5e8b6 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sat, 18 Jan 2025 16:34:43 +0100 Subject: [PATCH 10/11] Native Libraries: - Linux: added dumps - macOS: replaced deprecated Gradle `exec` --- .../flatlaf-natives-linux/build.gradle.kts | 36 ++++++++++++++++++- .../flatlaf-natives-macos/build.gradle.kts | 17 +++++---- .../flatlaf-natives-windows/build.gradle.kts | 2 +- 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/flatlaf-natives/flatlaf-natives-linux/build.gradle.kts b/flatlaf-natives/flatlaf-natives-linux/build.gradle.kts index abfad042..dc9de1e5 100644 --- a/flatlaf-natives/flatlaf-natives-linux/build.gradle.kts +++ b/flatlaf-natives/flatlaf-natives-linux/build.gradle.kts @@ -14,6 +14,8 @@ * limitations under the License. */ +import java.io.FileOutputStream + plugins { `cpp-library` `flatlaf-cpp-library` @@ -96,8 +98,10 @@ tasks { copy { from( linkedFile ) into( nativesDir ) - rename( "libflatlaf-natives-linux.so", libraryName ) + rename( linkedFile.get().asFile.name, libraryName ) } + +// dump( linkedFile.asFile.get(), true ) } } @@ -160,7 +164,37 @@ tasks { from( "$outDir/$libraryName" ) into( nativesDir ) } + +// dump( file( "$outDir/$libraryName" ), false ) } } } } + +/*dump +interface InjectedExecOps { @get:Inject val execOps: ExecOperations } +val injected = project.objects.newInstance() + +fun dump( dylib: File, disassemble: Boolean ) { + + val dylibDir = dylib.parent + injected.execOps.exec { commandLine( "size", dylib ); standardOutput = FileOutputStream( "$dylibDir/size.txt" ) } + injected.execOps.exec { + commandLine( "objdump", + // commands + "--archive-headers", + "--section-headers", + "--private-headers", + "--reloc", + "--dynamic-reloc", + "--syms", + // files + dylib ) + standardOutput = FileOutputStream( "$dylibDir/objdump.txt" ) + } + if( disassemble ) + injected.execOps.exec { commandLine( "objdump", "--disassemble-all", dylib ); standardOutput = FileOutputStream( "$dylibDir/disassemble.txt" ) } + injected.execOps.exec { commandLine( "objdump", "--full-contents", dylib ); standardOutput = FileOutputStream( "$dylibDir/full-contents.txt" ) } + injected.execOps.exec { commandLine( "hexdump", dylib ); standardOutput = FileOutputStream( "$dylibDir/hexdump.txt" ) } +} +dump*/ diff --git a/flatlaf-natives/flatlaf-natives-macos/build.gradle.kts b/flatlaf-natives/flatlaf-natives-macos/build.gradle.kts index 5b542d3a..961121d8 100644 --- a/flatlaf-natives/flatlaf-natives-macos/build.gradle.kts +++ b/flatlaf-natives/flatlaf-natives-macos/build.gradle.kts @@ -43,6 +43,9 @@ var javaHome = System.getProperty( "java.home" ) if( javaHome.endsWith( "jre" ) ) javaHome += "/.." +interface InjectedExecOps { @get:Inject val execOps: ExecOperations } +val injected = project.objects.newInstance() + tasks { register( "build-natives" ) { group = "build" @@ -95,21 +98,21 @@ tasks { doLast { // sign shared library -// exec { commandLine( "codesign", "-s", "FormDev Software GmbH", "--timestamp", "${linkedFile.asFile.get()}" ) } +// injected.execOps.exec { commandLine( "codesign", "-s", "FormDev Software GmbH", "--timestamp", "${linkedFile.asFile.get()}" ) } // copy shared library to flatlaf-core resources copy { from( linkedFile ) into( nativesDir ) - rename( "libflatlaf-natives-macos.dylib", libraryName ) + rename( linkedFile.get().asFile.name, libraryName ) } /*dump val dylib = linkedFile.asFile.get() val dylibDir = dylib.parent - exec { commandLine( "size", dylib ) } - exec { commandLine( "size", "-m", dylib ) } - exec { + injected.execOps.exec { commandLine( "size", dylib ); standardOutput = FileOutputStream( "$dylibDir/size.txt" ) } + injected.execOps.exec { commandLine( "size", "-m", dylib ); standardOutput = FileOutputStream( "$dylibDir/size-m.txt" ) } + injected.execOps.exec { commandLine( "objdump", // commands "--archive-headers", @@ -127,8 +130,8 @@ tasks { dylib ) standardOutput = FileOutputStream( "$dylibDir/objdump.txt" ) } - exec { commandLine( "objdump", "--disassemble-all", dylib ); standardOutput = FileOutputStream( "$dylibDir/disassemble.txt" ) } - exec { commandLine( "objdump", "--full-contents", dylib ); standardOutput = FileOutputStream( "$dylibDir/full-contents.txt" ) } + injected.execOps.exec { commandLine( "objdump", "--disassemble-all", dylib ); standardOutput = FileOutputStream( "$dylibDir/disassemble.txt" ) } + injected.execOps.exec { commandLine( "objdump", "--full-contents", dylib ); standardOutput = FileOutputStream( "$dylibDir/full-contents.txt" ) } dump*/ } } diff --git a/flatlaf-natives/flatlaf-natives-windows/build.gradle.kts b/flatlaf-natives/flatlaf-natives-windows/build.gradle.kts index e297703e..0a7eee0d 100644 --- a/flatlaf-natives/flatlaf-natives-windows/build.gradle.kts +++ b/flatlaf-natives/flatlaf-natives-windows/build.gradle.kts @@ -91,7 +91,7 @@ tasks { copy { from( linkedFile ) into( nativesDir ) - rename( "flatlaf-natives-windows.dll", libraryName ) + rename( linkedFile.get().asFile.name, libraryName ) } } } From 28904c34ccceed075292c8b10250e5743df8ec93 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sat, 18 Jan 2025 17:09:32 +0100 Subject: [PATCH 11/11] Linux: added `libflatlaf-linux-arm64.so` for Linux on ARM64 (issue #899) built by GitHub Actions: https://github.com/JFormDesigner/FlatLaf/actions/runs/12845430366 --- CHANGELOG.md | 1 + flatlaf-core/build.gradle.kts | 1 + .../formdev/flatlaf/ui/FlatNativeLibrary.java | 6 +++--- .../flatlaf/natives/libflatlaf-linux-arm64.so | Bin 0 -> 70840 bytes 4 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-linux-arm64.so diff --git a/CHANGELOG.md b/CHANGELOG.md index 2435529e..166c56d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ FlatLaf Change Log - Tree: Support wide cell renderer. (issue #922) - Extras: `FlatSVGIcon` color filters now can access painting component to implement component state based color mappings. (issue #906) +- Linux: Added `libflatlaf-linux-arm64.so` for Linux on ARM64. (issue #899) #### Fixed bugs diff --git a/flatlaf-core/build.gradle.kts b/flatlaf-core/build.gradle.kts index 11dd19b7..f02a413d 100644 --- a/flatlaf-core/build.gradle.kts +++ b/flatlaf-core/build.gradle.kts @@ -156,5 +156,6 @@ flatlafPublish { NativeArtifact( "${natives}/libflatlaf-macos-arm64.dylib", "macos-arm64", "dylib" ), NativeArtifact( "${natives}/libflatlaf-macos-x86_64.dylib", "macos-x86_64", "dylib" ), NativeArtifact( "${natives}/libflatlaf-linux-x86_64.so", "linux-x86_64", "so" ), + NativeArtifact( "${natives}/libflatlaf-linux-arm64.so", "linux-arm64", "so" ), ) } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeLibrary.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeLibrary.java index 3c169c28..a9768cd4 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeLibrary.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeLibrary.java @@ -90,10 +90,10 @@ class FlatNativeLibrary classifier = SystemInfo.isAARCH64 ? "macos-arm64" : "macos-x86_64"; ext = "dylib"; - } else if( SystemInfo.isLinux && SystemInfo.isX86_64 ) { - // Linux: requires x86_64 + } else if( SystemInfo.isLinux && (SystemInfo.isX86_64 || SystemInfo.isAARCH64)) { + // Linux: requires x86_64 or aarch64 - classifier = "linux-x86_64"; + classifier = SystemInfo.isAARCH64 ? "linux-arm64" : "linux-x86_64"; ext = "so"; // Load libjawt.so (part of JRE) explicitly because it is not found diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-linux-arm64.so b/flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-linux-arm64.so new file mode 100644 index 0000000000000000000000000000000000000000..c1a663f3874c96bcdc4643e629b232a01112a88a GIT binary patch literal 70840 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 n*W$R--0kr-i>wh|cXZOY