Compare commits

...

37 Commits
3.6 ... 3.6.2

Author SHA1 Message Date
Karl Tauber
36d5685f4c release 3.6.2 2025-10-10 09:13:22 +02:00
Karl Tauber
ddc8d6e29c README.md: new applications using FlatLaf:
- OpenRocket
- Warteschlangensimulator
- Nortantis
- QStudio
- Launch4j
2025-10-07 19:36:39 +02:00
Karl Tauber
119b4a922d fixed loading FlatLaf properties files in NetBeans (broken since commit again; issue #1026)
now using old implementation again (before commit 2ac7234c32), but if that does not find properties file, then fallback to new implementation from commit 2ac7234c32
2025-09-29 21:30:41 +02:00
Karl Tauber
5e4f00f0c8 GitHub Actions: build using Java 25 LTS 2025-09-23 16:15:08 +02:00
Karl Tauber
15cbf28a0d Popup: no longer reuse popup windows for menus to avoid immediately closing dialogs on ChromeOS (issue #1029)
added system property `flatlaf.reuseVisiblePopupWindow`
2025-09-20 12:51:50 +02:00
Karl Tauber
f8e53c9064 README.md:
- replaced maven badges with shields.io because maven-badges.herokuapp.com did not show latest version 3.6.1 (instead shows 3.6)
- also the link target search.maven.org does not show 3.6.1
- now link to central.sonatype.com, which seems to be the successor of search.maven.org
  https://central.sonatype.org/faq/what-happened-to-search-maven-org/
2025-09-10 14:48:27 +02:00
Karl Tauber
b3c9638e47 Popup: no longer use popup.show() for already visible popup window to avoid that inactive owner window becomes active (issue #1037) 2025-09-09 20:00:26 +02:00
Karl Tauber
d079741f94 Extras: FlatAnimatedLafChange: made transition smoother:
- use a single component in layered pane to paint new and old UI snapshots (previously used two components)
- the snapshot layer component is now opaque, which avoids that window component hierarchy is involved when painting snapshots
- snapshots are now painted immediately, which should result in a smoother transition
- changed animation resolution from 30ms to 16ms
2025-09-09 18:54:11 +02:00
Karl Tauber
c051ad5f72 macOS: fixed window "flashing" when switching from a light to a dark theme (or vice versa), especially when using animated theme changer 2025-09-09 17:30:43 +02:00
Karl Tauber
1ed7aeaa45 Tree: removed unused method parameter; reported by Error Prone in commit d388158de7 2025-09-08 14:11:16 +02:00
Karl Tauber
2ac7234c32 support loading FlatLaf properties files from named Java modules without the need to open that package in module-info.java (issue #1026) 2025-09-08 13:03:17 +02:00
Karl Tauber
6f63982054 load properties files using UTF-8 instead of ISO 8859-1 (issue #1031) 2025-09-06 12:45:40 +02:00
Karl Tauber
d388158de7 Tree and List: fixed painting of rounded drop backgrounds (issue #1023) 2025-09-06 00:15:11 +02:00
Karl Tauber
e7a766bf8f added SOPTIM as Gold sponsor 2025-07-18 16:14:59 +02:00
Karl Tauber
97988e90b4 release 3.6.1 2025-07-12 16:44:32 +02:00
Karl Tauber
f71dbb2647 Linux: ensure that old LinuxPopupMenuCanceler window listener is removed before adding a new one (issue #962)
(did not yet happen...)
2025-07-12 16:28:49 +02:00
Karl Tauber
04ad21b5b6 Button: added unit tests for foreground and background colors (issue #1017) 2025-07-12 15:59:56 +02:00
Karl Tauber
ff722c0b34 Popup:
- macOS: Fixed popup flickering after theme change. (issue #1009)
- macOS with JetBrains Runtime: Fixed sometimes empty popups. (issue #1019)
2025-07-07 18:33:49 +02:00
Karl Tauber
34b19f00e4 Window decorations (Windows 10/11 only):
- improved diagonal window resizing on top-left and top-right window corners
- top window resize area now also covers iconify/maximize/close buttons
(issue #1015)
2025-07-03 20:08:40 +02:00
Karl Tauber
286ce15146 ToggleButton: styling selectedForeground did not work if foreground is also styled (issue #1017) 2025-07-02 20:08:55 +02:00
Karl Tauber
abfaf86cd5 change snapshot version from 3.7-SNAPSHOT to 3.6.1-SNAPSHOT 2025-07-02 19:50:51 +02:00
Karl Tauber
bc4c7b25d3 snapshots: publish macOS .dylib native libraries to Maven Central Snapshots (disabled in commit 5575854e68) 2025-06-23 16:56:38 +02:00
Karl Tauber
0863e289a1 Table: add disabled icon for boolean renderer (issue #1008) 2025-06-20 19:56:24 +02:00
Karl Tauber
4945378dd3 macOS themes: fixed rendering of menu bar separator if unified background is disabled (issue #1003) 2025-06-12 10:45:57 +02:00
Karl Tauber
b178450e81 README.md: new applications using FlatLaf:
- SSPlot (issue #1002)
2025-06-12 10:28:24 +02:00
Karl Tauber
e3ffdd3b7c UIDefaultsLoader: improved error reporting and added more unit tests 2025-05-28 00:32:35 +02:00
Karl Tauber
5326971287 Linux: popups appeared in wrong position on multi-screen setup if primary display is located below or right to secondary display (issue https://github.com/apache/netbeans/issues/8532)) 2025-05-27 20:01:04 +02:00
Karl Tauber
cd34c08dc9 README.md: new applications using FlatLaf:
- Zettelkasten
- Convertigo
- EduMIPS64
- OpenPnP
- TrainControl
- Pixelitor
- Gephi
- StarPlan
- Lisheane ERP
- jose
2025-05-24 14:38:31 +02:00
Karl Tauber
edee73e0ea README.md: removed "new" badge from applications added in 2023 (and before) 2025-05-24 13:06:27 +02:00
Karl Tauber
54a53fb527 IntelliJ Themes: fixed logging false errors when loading 3rd party .theme.json files (issue #990) 2025-05-24 13:02:29 +02:00
Karl Tauber
62b96fbccd JideSplitButton: fixed updating popup when switching theme (issue #1000) 2025-05-23 19:31:42 +02:00
Karl Tauber
42cbb0666d README.md: added RedisFront (issue #989) 2025-05-23 01:17:14 +02:00
Karl Tauber
1465fbaabc Merge PR #992: Add Termora to Utilities list 2025-05-23 01:11:48 +02:00
Karl Tauber
5575854e68 migrate from legacy OSSRH to Central Portal
https://central.sonatype.org/faq/what-is-different-between-central-portal-and-legacy-ossrh/

using Portal OSSRH Staging API
https://central.sonatype.org/publish/publish-portal-ossrh-staging-api/
2025-05-23 00:59:32 +02:00
Karl Tauber
640f2ba9a2 update to Gradle 8.14.1 2025-05-23 00:49:02 +02:00
Karl Tauber
b221fd1894 Extras: Support JSVG 2.0.0. Minimum JSVG version is now 1.6.0. (issue #997) 2025-04-30 19:39:25 +02:00
hstyi
b64ab09b88 Add Termora to Utilities list 2025-04-14 15:39:25 +08:00
61 changed files with 1283 additions and 272 deletions

View File

@@ -67,11 +67,10 @@ jobs:
- 8 - 8
- 17 # LTS - 17 # LTS
- 21 # LTS - 21 # LTS
- 23 # latest
toolchain: [""] toolchain: [""]
# include: include:
# - java: 21 - java: 21
# toolchain: 22 # latest toolchain: 25 # LTS
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -105,11 +104,11 @@ jobs:
distribution: temurin # pre-installed on ubuntu-latest distribution: temurin # pre-installed on ubuntu-latest
cache: gradle cache: gradle
- name: Publish snapshot to oss.sonatype.org - name: Publish snapshot to Sonatype Central
run: ./gradlew publish -PskipFonts -Dorg.gradle.internal.publish.checksums.insecure=true -Dorg.gradle.parallel=false run: ./gradlew publish -PskipFonts -Dorg.gradle.internal.publish.checksums.insecure=true -Dorg.gradle.parallel=false
env: env:
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
release: release:
@@ -131,10 +130,10 @@ jobs:
cache: gradle cache: gradle
- name: Release a new stable version to Maven Central - name: Release a new stable version to Maven Central
run: ./gradlew publish :flatlaf-demo:build :flatlaf-theme-editor:build -PskipFonts -Prelease -Dorg.gradle.parallel=false run: ./gradlew publishToSonatype closeSonatypeStagingRepository :flatlaf-demo:build :flatlaf-theme-editor:build -PskipFonts -Prelease -Dorg.gradle.parallel=false
env: env:
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
SIGNING_KEY: ${{ secrets.SIGNING_KEY }} SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}

View File

@@ -45,18 +45,18 @@ jobs:
run: ./gradlew :flatlaf-fonts-${{ matrix.font }}:build run: ./gradlew :flatlaf-fonts-${{ matrix.font }}:build
if: startsWith( github.ref, format( 'refs/tags/fonts/{0}-', matrix.font ) ) != true if: startsWith( github.ref, format( 'refs/tags/fonts/{0}-', matrix.font ) ) != true
- name: Publish snapshot to oss.sonatype.org - name: Publish snapshot to Sonatype Central
run: ./gradlew :flatlaf-fonts-${{ matrix.font }}:publish -Dorg.gradle.internal.publish.checksums.insecure=true run: ./gradlew :flatlaf-fonts-${{ matrix.font }}:publish -Dorg.gradle.internal.publish.checksums.insecure=true
env: env:
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
if: github.ref == 'refs/heads/main' || startsWith( github.ref, 'refs/heads/develop-' ) if: github.ref == 'refs/heads/main' || startsWith( github.ref, 'refs/heads/develop-' )
- name: Release a new stable version to Maven Central - name: Release a new stable version to Maven Central
run: ./gradlew :flatlaf-fonts-${{ matrix.font }}:build :flatlaf-fonts-${{ matrix.font }}:publish -Prelease run: ./gradlew :flatlaf-fonts-${{ matrix.font }}:build :flatlaf-fonts-${{ matrix.font }}:publish -Prelease
env: env:
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
SIGNING_KEY: ${{ secrets.SIGNING_KEY }} SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
if: startsWith( github.ref, format( 'refs/tags/fonts/{0}-', matrix.font ) ) if: startsWith( github.ref, format( 'refs/tags/fonts/{0}-', matrix.font ) )

View File

@@ -28,10 +28,10 @@ jobs:
distribution: temurin # pre-installed on ubuntu-latest distribution: temurin # pre-installed on ubuntu-latest
cache: gradle cache: gradle
- name: Publish PR snapshot to oss.sonatype.org - name: Publish PR snapshot to Sonatype Central
run: > run: >
./gradlew publish -PskipFonts -Dorg.gradle.internal.publish.checksums.insecure=true -Dorg.gradle.parallel=false ./gradlew publish -PskipFonts -Dorg.gradle.internal.publish.checksums.insecure=true -Dorg.gradle.parallel=false
-Pgithub.event.pull_request.number=${{ github.event.pull_request.number }} -Pgithub.event.pull_request.number=${{ github.event.pull_request.number }}
env: env:
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}

View File

@@ -1,6 +1,54 @@
FlatLaf Change Log FlatLaf Change Log
================== ==================
## 3.6.2
#### New features and improvements
- If using `FlatLaf.registerCustomDefaultsSource( "com.myapp.themes" )` and
named Java modules, it is no longer necessary to add `opens com.myapp.themes;`
to `module-info.java`. (issue #1026)
- Extras: Made animated theme change (class `FlatAnimatedLafChange`) smoother.
#### Fixed bugs
- Tree and List: Fixed painting of rounded drop backgrounds. (issue #1023)
- Popup: Showing tooltip in inactive window brought that window to front (made
it active) and potentially hid the previously active window. (issue #1037)
- Popup: No longer reuse popup windows for menus to avoid immediately closing
dialogs on ChromeOS. (issue #1029)
- macOS: Fixed window "flashing" when switching from a light to a dark theme (or
vice versa). Especially when using animated theme changer (see
[FlatLaf Extras](flatlaf-extras)).
#### Incompatibilities
- FlatLaf properties files are now loaded using the UTF-8 character encoding
instead of ISO 8859-1. In usual properties files you will not notice any
difference because they use only ASCII characters, but if you've put localized
(non-English) texts (e.g. German umlauts) into your properties files, you need
to convert them to UTF-8. Properties files created with the FlatLaf Theme
Editor already use UTF-8, including in older versions. (issue #1031)
## 3.6.1
- Extras: Support JSVG 2.0.0. Minimum JSVG version is now 1.6.0. (issue #997)
- FlatLaf window decorations (Windows 10/11 only): Improved diagonal window
resizing on top-left and top-right window corners. Top window resize area now
also covers iconify/maximize/close buttons. (issue #1015)
- ToggleButton: Styling `selectedForeground` did not work if `foreground` is
also styled. (issue #1017)
- JideSplitButton: Fixed updating popup when switching theme. (issue #1000)
- IntelliJ Themes: Fixed logging false errors when loading 3rd party
`.theme.json` files. (issue #990)
- Linux: Popups appeared in wrong position on multi-screen setup if primary
display is located below or right to secondary display. (see
[NetBeans issue #8532](https://github.com/apache/netbeans/issues/8532))
- macOS: Fixed popup flickering after theme change. (issue #1009)
- macOS with JetBrains Runtime: Fixed sometimes empty popups. (issue #1019)
## 3.6 ## 3.6
#### New features and improvements #### New features and improvements

113
README.md
View File

@@ -35,6 +35,8 @@ Sponsors
### Current Sponsors ### Current Sponsors
<a href="https://www.soptim.de/"><img src="https://www.formdev.com/flatlaf/sponsor/soptim.svg" width="200" alt="SOPTIM" title="SOPTIM - your expert in software solutions for the energy industry"></a>
&nbsp; &nbsp; &nbsp; &nbsp;
<a href="https://exocharts.com/"><img src="https://www.formdev.com/flatlaf/sponsor/Exocharts.png" width="200" alt="Exocharts" title="Exocharts - Professional Grade OrderFlow"></a> <a href="https://exocharts.com/"><img src="https://www.formdev.com/flatlaf/sponsor/Exocharts.png" width="200" alt="Exocharts" title="Exocharts - Professional Grade OrderFlow"></a>
<!-- [![None Sponsors](images/none-sponsors.png)](https://www.formdev.com/flatlaf/sponsor/) --> <!-- [![None Sponsors](images/none-sponsors.png)](https://www.formdev.com/flatlaf/sponsor/) -->
@@ -72,7 +74,7 @@ build script:
Otherwise, download `flatlaf-<version>.jar` here: Otherwise, download `flatlaf-<version>.jar` here:
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf/badge.svg?style=flat-square&color=007ec6)](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf) [![Maven Central](https://img.shields.io/maven-central/v/com.formdev/flatlaf?style=flat-square)](https://central.sonatype.com/artifact/com.formdev/flatlaf)
See also See also
[Native Libraries distribution](https://www.formdev.com/flatlaf/native-libraries/) [Native Libraries distribution](https://www.formdev.com/flatlaf/native-libraries/)
@@ -83,10 +85,10 @@ application.
### Snapshots ### Snapshots
FlatLaf snapshot binaries are available on FlatLaf snapshot binaries are available on
[Sonatype OSSRH](https://oss.sonatype.org/content/repositories/snapshots/com/formdev/flatlaf/). [Sonatype Central](https://central.sonatype.com/service/rest/repository/browse/maven-snapshots/com/formdev/flatlaf/).
To access the latest snapshot, change the FlatLaf version in your dependencies To access the latest snapshot, change the FlatLaf version in your dependencies
to `<version>-SNAPSHOT` (e.g. `0.27-SNAPSHOT`) and add the repository to `<version>-SNAPSHOT` (e.g. `3.7-SNAPSHOT`) and add the repository
`https://oss.sonatype.org/content/repositories/snapshots/` to your build (see `https://central.sonatype.com/repository/maven-snapshots/` to your build (see
[Maven](https://maven.apache.org/guides/mini/guide-multiple-repositories.html) [Maven](https://maven.apache.org/guides/mini/guide-multiple-repositories.html)
and and
[Gradle](https://docs.gradle.org/current/userguide/declaring_repositories.html#sec:declaring_custom_repository) [Gradle](https://docs.gradle.org/current/userguide/declaring_repositories.html#sec:declaring_custom_repository)
@@ -184,11 +186,18 @@ Applications using FlatLaf
relational data browsing tool relational data browsing tool
- ![Hot](images/hot.svg) [MagicPlot](https://magicplot.com/) (**commercial**) - - ![Hot](images/hot.svg) [MagicPlot](https://magicplot.com/) (**commercial**) -
Software for nonlinear fitting, plotting and data analysis Software for nonlinear fitting, plotting and data analysis
- ![New](images/new.svg) [Constellation](https://www.constellation-app.com/) - - [Constellation](https://www.constellation-app.com/) - Data Visualization and
Data Visualization and Analytics (based on NetBeans platform) Analytics (based on NetBeans platform)
- ![New](images/new.svg) - [Kafka Visualizer](https://github.com/kumait/kafkavisualizer) - Kafka GUI
[Kafka Visualizer](https://github.com/kumait/kafkavisualizer) - Kafka GUI
client client
- ![New](images/new.svg)
[RedisFront](https://github.com/dromara/RedisFront/blob/master/README_EN.md) -
Cross-platform redis GUI
- ![New](images/new.svg)
[Zettelkasten](https://github.com/Zettelkasten-Team/Zettelkasten) - knowledge
management tool
- ![New](images/new.svg) [QStudio](https://www.timestored.com/qstudio/) - free
SQL editor
### Security ### Security
@@ -197,11 +206,9 @@ Applications using FlatLaf
- ![Hot](images/hot.svg) - ![Hot](images/hot.svg)
[Burp Suite Professional and Community Edition](https://portswigger.net/burp/pro) [Burp Suite Professional and Community Edition](https://portswigger.net/burp/pro)
(**commercial**) - the leading software for web security testing (**commercial**) - the leading software for web security testing
- ![New](images/new.svg) - [Ghidra](https://github.com/NationalSecurityAgency/ghidra) - a software
[Ghidra](https://github.com/NationalSecurityAgency/ghidra) - a software
reverse engineering (SRE) framework reverse engineering (SRE) framework
- ![New](images/new.svg) [jadx](https://github.com/skylot/jadx) - Dex to Java - [jadx](https://github.com/skylot/jadx) - Dex to Java decompiler
decompiler
- [BurpCustomizer](https://github.com/CoreyD97/BurpCustomizer) - adds more - [BurpCustomizer](https://github.com/CoreyD97/BurpCustomizer) - adds more
FlatLaf themes to Burp Suite FlatLaf themes to Burp Suite
- [Total Validator](https://www.totalvalidator.com/) (**commercial**) - checks - [Total Validator](https://www.totalvalidator.com/) (**commercial**) - checks
@@ -213,13 +220,12 @@ Applications using FlatLaf
- [jclasslib bytecode viewer](https://github.com/ingokegel/jclasslib) - [jclasslib bytecode viewer](https://github.com/ingokegel/jclasslib)
- [KeyStore Explorer](https://keystore-explorer.org/) - [KeyStore Explorer](https://keystore-explorer.org/)
- ![New](images/new.svg) - [muCommander](https://github.com/mucommander/mucommander) - lightweight
[muCommander](https://github.com/mucommander/mucommander) - lightweight
cross-platform file manager cross-platform file manager
- ![New](images/new.svg) [Guiffy](https://www.guiffy.com/) (**commercial**) - - [Guiffy](https://www.guiffy.com/) (**commercial**) - advanced cross-platform
advanced cross-platform Diff/Merge Diff/Merge
- ![New](images/new.svg) [HashGarten](https://github.com/jonelo/HashGarten) - - [HashGarten](https://github.com/jonelo/HashGarten) - cross-platform Swing GUI
cross-platform Swing GUI for Jacksum for Jacksum
- [Pseudo Assembler IDE](https://github.com/tomasz-herman/PseudoAssemblerIDE) - - [Pseudo Assembler IDE](https://github.com/tomasz-herman/PseudoAssemblerIDE) -
IDE for Pseudo-Assembler IDE for Pseudo-Assembler
- [Linotte](https://github.com/cpc6128/LangageLinotte) - French programming - [Linotte](https://github.com/cpc6128/LangageLinotte) - French programming
@@ -228,6 +234,12 @@ Applications using FlatLaf
systems development platform systems development platform
- ![New](images/new.svg) [Consulo](https://github.com/consulo/consulo) - open - ![New](images/new.svg) [Consulo](https://github.com/consulo/consulo) - open
source cross-platform multi-language IDE (Java, .NET, JS, etc) source cross-platform multi-language IDE (Java, .NET, JS, etc)
- [Convertigo](https://github.com/convertigo/convertigo) - low code & no code
mobile & web platform
- ![New](images/new.svg) [EduMIPS64](https://github.com/EduMIPS64/edumips64) -
visual MIPS64 CPU simulator
- ![New](images/new.svg) [Launch4j](https://launch4j.sourceforge.net/) -
cross-platform Java executable wrapper
### Electrical ### Electrical
@@ -235,6 +247,11 @@ Applications using FlatLaf
designing, simulating and explaining digital circuits designing, simulating and explaining digital circuits
- [Logisim-evolution](https://github.com/logisim-evolution/logisim-evolution) - - [Logisim-evolution](https://github.com/logisim-evolution/logisim-evolution) -
Digital logic design tool and simulator Digital logic design tool and simulator
- ![New](images/new.svg) [OpenPnP](https://github.com/openpnp/openpnp) - SMT
Pick and Place Hardware and Software
- ![New](images/new.svg)
[TrainControl](https://github.com/bob123456678/TrainControl) - control Marklin
/ Trix / DCC digital model train layout
- [Makelangelo Software](https://github.com/MarginallyClever/Makelangelo-software) - - [Makelangelo Software](https://github.com/MarginallyClever/Makelangelo-software) -
for plotters, especially the wall-hanging polargraph for plotters, especially the wall-hanging polargraph
- [GUIslice Builder](https://github.com/ImpulseAdventure/GUIslice-Builder) - GUI - [GUIslice Builder](https://github.com/ImpulseAdventure/GUIslice-Builder) - GUI
@@ -249,8 +266,10 @@ Applications using FlatLaf
- ![Hot](images/hot.svg) [jAlbum](https://jalbum.net/) (**commercial**) - - ![Hot](images/hot.svg) [jAlbum](https://jalbum.net/) (**commercial**) -
creates photo album websites creates photo album websites
- ![New](images/new.svg) [MediathekView](https://mediathekview.de/) - search in - [MediathekView](https://mediathekview.de/) - search in media libraries of
media libraries of various German broadcasters various German broadcasters
- ![New](images/new.svg) [Pixelitor](https://github.com/lbalazscs/Pixelitor) -
image editor
- [Cinecred](https://loadingbyte.com/cinecred/) - create beautiful film credit - [Cinecred](https://loadingbyte.com/cinecred/) - create beautiful film credit
sequences sequences
- [tinyMediaManager](https://www.tinymediamanager.org/) (**commercial**) - a - [tinyMediaManager](https://www.tinymediamanager.org/) (**commercial**) - a
@@ -266,19 +285,31 @@ Applications using FlatLaf
from any webnovel and lightnovel site from any webnovel and lightnovel site
- [lectureStudio](https://www.lecturestudio.org/) - digitize your lectures with - [lectureStudio](https://www.lecturestudio.org/) - digitize your lectures with
ease ease
- ![New](images/new.svg) [Nortantis](https://jandjheydorn.com/nortantis) -
fantasy map generator and editor
### Modelling ### Modelling / Planning
- ![New](images/new.svg) [Astah](https://astah.net/) (**commercial**) - create - ![New](images/new.svg) [OpenRocket](https://github.com/openrocket/openrocket) -
UML, ER Diagram, Flowchart, Data Flow Diagram, Requirement Diagram, SysML model-rocketry aerodynamics and trajectory simulation software
diagrams and more - ![New](images/new.svg)
[Warteschlangensimulator](https://github.com/A-Herzog/Warteschlangensimulator) -
discrete-event stochastic simulator
- ![New](images/new.svg) [Gephi](https://github.com/gephi/gephi) - the Open
Graph Viz Platform
- [Astah](https://astah.net/) (**commercial**) - create UML, ER Diagram,
Flowchart, Data Flow Diagram, Requirement Diagram, SysML diagrams and more
- [IGMAS+](https://www.gfz-potsdam.de/igmas) - Interactive Gravity and Magnetic - [IGMAS+](https://www.gfz-potsdam.de/igmas) - Interactive Gravity and Magnetic
Application System Application System
- ![New](images/new.svg) [StarPlan](https://www.progotec.de/) (**commercial**) -
die Stundenplan Software für Bildungseinrichtungen
- ![New](images/new.svg) [SSPlot](https://github.com/babaissarkar/ssplot) -
plotting utility for plotting CSV data
### Documents ### Documents
- ![New](images/new.svg) [Big Faceless (BFO) PDF Viewer](https://bfo.com/) - [Big Faceless (BFO) PDF Viewer](https://bfo.com/) (**commercial**) - Swing PDF
(**commercial**) - Swing PDF Viewer Viewer
- [PDF Studio](https://www.qoppa.com/pdfstudio/) (**commercial**) - create, - [PDF Studio](https://www.qoppa.com/pdfstudio/) (**commercial**) - create,
review and edit PDF documents review and edit PDF documents
- [XMLmind XML Editor](https://www.xmlmind.com/xmleditor/) (**commercial**) - [XMLmind XML Editor](https://www.xmlmind.com/xmleditor/) (**commercial**)
@@ -296,6 +327,9 @@ Applications using FlatLaf
### Business / Legal ### Business / Legal
- ![New](images/new.svg) ![Sponsor](images/sponsor.svg)
[Lisheane ERP](https://www.lisheane.ch/) (**commercial**) - backoffice
applikation
- ![Sponsor](images/sponsor.svg) - ![Sponsor](images/sponsor.svg)
[j-lawyer](https://github.com/jlawyerorg/j-lawyer-org) - Kanzleisoftware [j-lawyer](https://github.com/jlawyerorg/j-lawyer-org) - Kanzleisoftware
- ![Sponsor](images/sponsor.svg) [Jeyla Studio](https://www.jeylastudio.com/) - - ![Sponsor](images/sponsor.svg) [Jeyla Studio](https://www.jeylastudio.com/) -
@@ -312,20 +346,20 @@ Applications using FlatLaf
### Messaging ### Messaging
- ![New](images/new.svg) [Spark](https://github.com/igniterealtime/Spark) - - [Spark](https://github.com/igniterealtime/Spark) - cross-platform IM client
cross-platform IM client optimized for businesses and organizations optimized for businesses and organizations
- ![New](images/new.svg) [Chatty](https://github.com/chatty/chatty) - Twitch - [Chatty](https://github.com/chatty/chatty) - Twitch Chat Client
Chat Client
### Gaming ### Gaming
- ![New](images/new.svg) ![Sponsor](images/sponsor.svg) - ![Sponsor](images/sponsor.svg) [BGBlitz](https://www.bgblitz.com/)
[BGBlitz](https://www.bgblitz.com/) (**commercial**) - professional Backgammon (**commercial**) - professional Backgammon
- ![New](images/new.svg) [MCreator](https://github.com/MCreator/MCreator) - - ![New](images/new.svg) [josé](https://peteschaefer.github.io/jose/) - a
software used to make Minecraft Java Edition mods, Minecraft Bedrock Edition Add-Ons, graphical chess tool
and data packs without programming knowledge - ![New](images/new.svg) [MCreator](https://github.com/MCreator/MCreator) - make
- ![New](images/new.svg) [MapTool](https://github.com/RPTools/maptool) - virtual Minecraft Java Edition mods, Minecraft Bedrock Edition Add-Ons, and data packs
Tabletop for playing role-playing games - [MapTool](https://github.com/RPTools/maptool) - virtual Tabletop for playing
role-playing games
- [MegaMek](https://github.com/MegaMek/megamek), - [MegaMek](https://github.com/MegaMek/megamek),
[MegaMekLab](https://github.com/MegaMek/megameklab) and [MegaMekLab](https://github.com/MegaMek/megameklab) and
[MekHQ](https://github.com/MegaMek/mekhq) - a sci-fi tabletop BattleTech [MekHQ](https://github.com/MegaMek/mekhq) - a sci-fi tabletop BattleTech
@@ -337,8 +371,7 @@ Applications using FlatLaf
- [MooInfo](https://github.com/rememberber/MooInfo) - visual implementation of - [MooInfo](https://github.com/rememberber/MooInfo) - visual implementation of
OSHI, to view information about the system and hardware OSHI, to view information about the system and hardware
- ![New](images/new.svg) - [Linux Task Manager (LTM)](https://github.com/ajee10x/LTM-LinuxTaskManager) -
[Linux Task Manager (LTM)](https://github.com/ajee10x/LTM-LinuxTaskManager) -
GUI for monitoring and managing various aspects of a Linux system GUI for monitoring and managing various aspects of a Linux system
- [Rest Suite](https://github.com/supanadit/restsuite) - Rest API testing - [Rest Suite](https://github.com/supanadit/restsuite) - Rest API testing
- [SpringRemote](https://github.com/HaleyWang/SpringRemote) - remote Linux SSH - [SpringRemote](https://github.com/HaleyWang/SpringRemote) - remote Linux SSH
@@ -347,6 +380,8 @@ Applications using FlatLaf
easy easy
- [Android Tool](https://github.com/fast-geek/Android-Tool) - makes popular adb - [Android Tool](https://github.com/fast-geek/Android-Tool) - makes popular adb
and fastboot commands easier to use and fastboot commands easier to use
- ![New](images/new.svg) [Termora](https://github.com/TermoraDev/termora) -
Terminal emulator and SSH client
### Miscellaneous ### Miscellaneous

View File

@@ -16,6 +16,7 @@
import net.ltgt.gradle.errorprone.errorprone import net.ltgt.gradle.errorprone.errorprone
group = "com.formdev"
version = property( if( hasProperty( "release" ) ) "flatlaf.releaseVersion" else "flatlaf.developmentVersion" ) as String version = property( if( hasProperty( "release" ) ) "flatlaf.releaseVersion" else "flatlaf.developmentVersion" ) as String
// for PR snapshots change version to 'PR-<pr_number>-SNAPSHOT' // for PR snapshots change version to 'PR-<pr_number>-SNAPSHOT'
@@ -49,6 +50,7 @@ println()
plugins { plugins {
alias( libs.plugins.gradle.nexus.publish.plugin )
alias( libs.plugins.errorprone ) apply false alias( libs.plugins.errorprone ) apply false
} }
@@ -143,3 +145,20 @@ allprojects {
} }
} }
} }
nexusPublishing {
repositories {
sonatype {
// see https://central.sonatype.org/publish/publish-portal-ossrh-staging-api/
nexusUrl = uri( "https://ossrh-staging-api.central.sonatype.com/service/local/" )
snapshotRepositoryUrl = uri( "https://central.sonatype.com/repository/maven-snapshots/" )
// get from gradle.properties
val sonatypeUsername: String? by project
val sonatypePassword: String? by project
username = System.getenv( "SONATYPE_USERNAME" ) ?: sonatypeUsername
password = System.getenv( "SONATYPE_PASSWORD" ) ?: sonatypePassword
}
}
}

View File

@@ -86,24 +86,26 @@ publishing {
} }
} }
/*
repositories { repositories {
maven { maven {
name = "OSSRH" name = "MavenCentral"
val releasesRepoUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2/" val releasesRepoUrl = "https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/"
val snapshotsRepoUrl = "https://oss.sonatype.org/content/repositories/snapshots/" val snapshotsRepoUrl = "https://central.sonatype.com/repository/maven-snapshots/"
url = uri( if( rootProject.hasProperty( "release" ) ) releasesRepoUrl else snapshotsRepoUrl ) url = uri( if( rootProject.hasProperty( "release" ) ) releasesRepoUrl else snapshotsRepoUrl )
credentials { credentials {
// get from gradle.properties // get from gradle.properties
val ossrhUsername: String? by project val sonatypeUsername: String? by project
val ossrhPassword: String? by project val sonatypePassword: String? by project
username = System.getenv( "OSSRH_USERNAME" ) ?: ossrhUsername username = System.getenv( "SONATYPE_USERNAME" ) ?: sonatypeUsername
password = System.getenv( "OSSRH_PASSWORD" ) ?: ossrhPassword password = System.getenv( "SONATYPE_PASSWORD" ) ?: sonatypePassword
} }
} }
} }
*/
} }
signing { signing {

View File

@@ -1,5 +1,5 @@
#Signature file v4.1 #Signature file v4.1
#Version 3.6 #Version 3.6.2
CLSS public abstract interface com.formdev.flatlaf.FlatClientProperties CLSS public abstract interface com.formdev.flatlaf.FlatClientProperties
fld public final static java.lang.String BUTTON_TYPE = "JButton.buttonType" fld public final static java.lang.String BUTTON_TYPE = "JButton.buttonType"
@@ -297,6 +297,7 @@ CLSS public abstract interface com.formdev.flatlaf.FlatSystemProperties
fld public final static java.lang.String ANIMATION = "flatlaf.animation" fld public final static java.lang.String ANIMATION = "flatlaf.animation"
fld public final static java.lang.String MENUBAR_EMBEDDED = "flatlaf.menuBarEmbedded" fld public final static java.lang.String MENUBAR_EMBEDDED = "flatlaf.menuBarEmbedded"
fld public final static java.lang.String NATIVE_LIBRARY_PATH = "flatlaf.nativeLibraryPath" fld public final static java.lang.String NATIVE_LIBRARY_PATH = "flatlaf.nativeLibraryPath"
fld public final static java.lang.String REUSE_VISIBLE_POPUP_WINDOW = "flatlaf.reuseVisiblePopupWindow"
fld public final static java.lang.String UI_SCALE = "flatlaf.uiScale" fld public final static java.lang.String UI_SCALE = "flatlaf.uiScale"
fld public final static java.lang.String UI_SCALE_ALLOW_SCALE_DOWN = "flatlaf.uiScale.allowScaleDown" fld public final static java.lang.String UI_SCALE_ALLOW_SCALE_DOWN = "flatlaf.uiScale.allowScaleDown"
fld public final static java.lang.String UI_SCALE_ENABLED = "flatlaf.uiScale.enabled" fld public final static java.lang.String UI_SCALE_ENABLED = "flatlaf.uiScale.enabled"
@@ -324,7 +325,7 @@ meth public static boolean setup(java.io.InputStream)
meth public static com.formdev.flatlaf.FlatLaf createLaf(com.formdev.flatlaf.IntelliJTheme) meth public static com.formdev.flatlaf.FlatLaf createLaf(com.formdev.flatlaf.IntelliJTheme)
meth public static com.formdev.flatlaf.FlatLaf createLaf(java.io.InputStream) throws java.io.IOException meth public static com.formdev.flatlaf.FlatLaf createLaf(java.io.InputStream) throws java.io.IOException
supr java.lang.Object supr java.lang.Object
hfds checkboxDuplicateColors,checkboxKeyMapping,jsonColors,jsonIcons,jsonUI,namedColors,uiKeyCopying,uiKeyDoNotOverride,uiKeyExcludes,uiKeyInverseMapping,uiKeyMapping hfds checkboxDuplicateColors,checkboxKeyMapping,jsonColors,jsonIcons,jsonUI,namedColors,uiKeyCopying,uiKeyDoNotOverride,uiKeyExcludesContains,uiKeyExcludesStartsWith,uiKeyInverseMapping,uiKeyMapping
CLSS public static com.formdev.flatlaf.IntelliJTheme$ThemeLaf CLSS public static com.formdev.flatlaf.IntelliJTheme$ThemeLaf
outer com.formdev.flatlaf.IntelliJTheme outer com.formdev.flatlaf.IntelliJTheme

View File

@@ -911,8 +911,7 @@ public abstract class FlatLaf
* <p> * <p>
* Invoke this method before setting the look and feel. * Invoke this method before setting the look and feel.
* <p> * <p>
* If using Java modules, the package must be opened in {@code module-info.java}. * If using Java modules, it is not necessary to open the package in {@code module-info.java}.
* Otherwise, use {@link #registerCustomDefaultsSource(URL)}.
* *
* @param packageName a package name (e.g. "com.myapp.resources") * @param packageName a package name (e.g. "com.myapp.resources")
*/ */
@@ -959,9 +958,9 @@ public abstract class FlatLaf
* <p> * <p>
* See {@link #registerCustomDefaultsSource(String)} for details. * See {@link #registerCustomDefaultsSource(String)} for details.
* <p> * <p>
* This method is useful if using Java modules and the package containing the properties files
* is not opened in {@code module-info.java}.
* E.g. {@code FlatLaf.registerCustomDefaultsSource( MyApp.class.getResource( "/com/myapp/themes/" ) )}. * E.g. {@code FlatLaf.registerCustomDefaultsSource( MyApp.class.getResource( "/com/myapp/themes/" ) )}.
* <p>
* If using Java modules, it is not necessary to open the package in {@code module-info.java}.
* *
* @param packageUrl a package URL * @param packageUrl a package URL
* @since 2 * @since 2

View File

@@ -20,6 +20,9 @@ import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Locale; import java.util.Locale;
import java.util.Properties; import java.util.Properties;
@@ -62,8 +65,8 @@ public class FlatPropertiesLaf
throws IOException throws IOException
{ {
Properties properties = new Properties(); Properties properties = new Properties();
try( InputStream in2 = in ) { try( Reader reader = new InputStreamReader( in, StandardCharsets.UTF_8 )) {
properties.load( in2 ); properties.load( reader );
} }
return properties; return properties;
} }

View File

@@ -16,6 +16,7 @@
package com.formdev.flatlaf; package com.formdev.flatlaf;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
@@ -147,6 +148,25 @@ public interface FlatSystemProperties
*/ */
String USE_ROUNDED_POPUP_BORDER = "flatlaf.useRoundedPopupBorder"; String USE_ROUNDED_POPUP_BORDER = "flatlaf.useRoundedPopupBorder";
/**
* Species whether popup windows may be reused without (temporary) hiding them.
* E.g. if "moving" a tooltip to follow the mouse pointer, normally it is necessary
* to hide the tooltip and show it again at the new location, which causes some
* flicker with heavy-weight popup windows that FlatLaf uses on all platforms.
* <p>
* If {@code true}, hiding popup window is deferred for an event cycle,
* which allows reusing still visible popup window and avoids flicker when "moving" the popup.
* <p>
* Note that {@link JPopupMenu} popup windows (menus and combobox lists) are newer reused.
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> {@code true}
*
* @since 3.6.2
*/
String REUSE_VISIBLE_POPUP_WINDOW = "flatlaf.reuseVisiblePopupWindow";
/** /**
* Specifies whether vertical text position is corrected when UI is scaled on HiDPI screens. * Specifies whether vertical text position is corrected when UI is scaled on HiDPI screens.
* <p> * <p>

View File

@@ -413,22 +413,37 @@ public class IntelliJTheme
key.equals( "Tree.rightChildIndent" ) ) key.equals( "Tree.rightChildIndent" ) )
return; // ignore return; // ignore
// ignore icons
if( key.endsWith( "Icon" ) )
return; // ignore
// map keys // map keys
key = uiKeyMapping.getOrDefault( key, key ); key = uiKeyMapping.getOrDefault( key, key );
if( key.isEmpty() ) if( key.isEmpty() )
return; // ignore key return; // ignore key
// exclude properties // exclude properties (1st level)
int dot = key.indexOf( '.' ); int dot = key.indexOf( '.' );
if( dot > 0 && uiKeyExcludes.contains( key.substring( 0, dot + 1 ) ) ) if( dot > 0 && uiKeyExcludesStartsWith.contains( key.substring( 0, dot + 1 ) ) )
return; return;
// exclude properties (2st level)
int dot2 = (dot > 0) ? key.indexOf( '.', dot + 1 ) : -1;
if( dot2 > 0 && uiKeyExcludesStartsWith.contains( key.substring( 0, dot2 + 1 ) ) )
return;
// exclude properties (contains)
for( String s : uiKeyExcludesContains ) {
if( key.contains( s ) )
return;
}
if( uiKeyDoNotOverride.contains( key ) && jsonUIKeys.contains( key ) ) if( uiKeyDoNotOverride.contains( key ) && jsonUIKeys.contains( key ) )
return; return;
jsonUIKeys.add( key ); jsonUIKeys.add( key );
String valueStr = value.toString(); String valueStr = value.toString().trim();
// map named colors // map named colors
String uiValue = namedColors.get( valueStr ); String uiValue = namedColors.get( valueStr );
@@ -657,7 +672,8 @@ public class IntelliJTheme
} }
} }
private static final Set<String> uiKeyExcludes; private static final Set<String> uiKeyExcludesStartsWith;
private static final String[] uiKeyExcludesContains;
private static final Set<String> uiKeyDoNotOverride; private static final Set<String> uiKeyDoNotOverride;
/** Rename UI default keys (key --> value). */ /** Rename UI default keys (key --> value). */
private static final Map<String, String> uiKeyMapping = new HashMap<>(); private static final Map<String, String> uiKeyMapping = new HashMap<>();
@@ -669,7 +685,7 @@ public class IntelliJTheme
static { static {
// IntelliJ UI properties that are not used in FlatLaf // IntelliJ UI properties that are not used in FlatLaf
uiKeyExcludes = new HashSet<>( Arrays.asList( uiKeyExcludesStartsWith = new HashSet<>( Arrays.asList(
"ActionButton.", "ActionToolbar.", "ActionsList.", "AppInspector.", "AssignedMnemonic.", "Autocomplete.", "ActionButton.", "ActionToolbar.", "ActionsList.", "AppInspector.", "AssignedMnemonic.", "Autocomplete.",
"AvailableMnemonic.", "AvailableMnemonic.",
"Badge.", "Banner.", "BigSpinner.", "Bookmark.", "BookmarkIcon.", "BookmarkMnemonicAssigned.", "BookmarkMnemonicAvailable.", "Badge.", "Banner.", "BigSpinner.", "Bookmark.", "BookmarkIcon.", "BookmarkMnemonicAssigned.", "BookmarkMnemonicAvailable.",
@@ -703,14 +719,24 @@ public class IntelliJTheme
// possible typos in .theme.json files // possible typos in .theme.json files
"Checkbox.", "Toolbar.", "Tooltip.", "UiDesigner.", "link." "Checkbox.", "Toolbar.", "Tooltip.", "UiDesigner.", "link."
) ); ) );
uiKeyExcludesContains = new String[] {
".darcula."
};
uiKeyDoNotOverride = new HashSet<>( Arrays.asList( uiKeyDoNotOverride = new HashSet<>( Arrays.asList(
"TabbedPane.selectedForeground" "TabbedPane.selectedForeground"
) ); ) );
// "*."
uiKeyMapping.put( "*.fontFace", "" ); // ignore (used in OnePauintxi themes)
uiKeyMapping.put( "*.fontSize", "" ); // ignore (used in OnePauintxi themes)
// Button // Button
uiKeyMapping.put( "Button.minimumSize", "" ); // ignore (used in Material Theme UI Lite) uiKeyMapping.put( "Button.minimumSize", "" ); // ignore (used in Material Theme UI Lite)
// CheckBox.iconSize
uiKeyMapping.put( "CheckBox.iconSize", "" ); // ignore (used in Rider themes)
// ComboBox // ComboBox
uiKeyMapping.put( "ComboBox.background", "" ); // ignore uiKeyMapping.put( "ComboBox.background", "" ); // ignore
uiKeyMapping.put( "ComboBox.buttonBackground", "" ); // ignore uiKeyMapping.put( "ComboBox.buttonBackground", "" ); // ignore
@@ -751,6 +777,9 @@ public class IntelliJTheme
uiKeyMapping.put( "ProgressBar.trackColor", "ProgressBar.background" ); uiKeyMapping.put( "ProgressBar.trackColor", "ProgressBar.background" );
uiKeyMapping.put( "ProgressBar.progressColor", "ProgressBar.foreground" ); uiKeyMapping.put( "ProgressBar.progressColor", "ProgressBar.foreground" );
// RadioButton
uiKeyMapping.put( "RadioButton.iconSize", "" ); // ignore (used in Rider themes)
// ScrollBar // ScrollBar
uiKeyMapping.put( "ScrollBar.trackColor", "ScrollBar.track" ); uiKeyMapping.put( "ScrollBar.trackColor", "ScrollBar.track" );
uiKeyMapping.put( "ScrollBar.thumbColor", "ScrollBar.thumb" ); uiKeyMapping.put( "ScrollBar.thumbColor", "ScrollBar.thumb" );

View File

@@ -70,6 +70,8 @@ class LinuxPopupMenuCanceler
} }
private void addWindowListeners( MenuElement selected ) { private void addWindowListeners( MenuElement selected ) {
removeWindowListeners();
// see BasicPopupMenuUI.MouseGrabber.grabWindow() // see BasicPopupMenuUI.MouseGrabber.grabWindow()
Component invoker = selected.getComponent(); Component invoker = selected.getComponent();
if( invoker instanceof JPopupMenu ) if( invoker instanceof JPopupMenu )

View File

@@ -25,12 +25,15 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StreamTokenizer; import java.io.StreamTokenizer;
import java.io.StringReader; import java.io.StringReader;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Executable; import java.lang.reflect.Executable;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.net.URL; import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@@ -112,6 +115,14 @@ class UIDefaultsLoader
Set<String> specialPrefixes = FlatLaf.getUIKeySpecialPrefixes(); Set<String> specialPrefixes = FlatLaf.getUIKeySpecialPrefixes();
return new Properties() { return new Properties() {
@Override
public void load( InputStream in ) throws IOException {
// use UTF-8 to load properties file
try( Reader reader = new InputStreamReader( in, StandardCharsets.UTF_8 )) {
super.load( reader );
}
}
@Override @Override
public synchronized Object put( Object k, Object value ) { public synchronized Object put( Object k, Object value ) {
// process key prefixes (while loading properties files) // process key prefixes (while loading properties files)
@@ -198,16 +209,46 @@ class UIDefaultsLoader
if( classLoader == null ) if( classLoader == null )
classLoader = FlatLaf.class.getClassLoader(); classLoader = FlatLaf.class.getClassLoader();
boolean found = false;
for( Class<?> lafClass : lafClasses ) { for( Class<?> lafClass : lafClasses ) {
String propertiesName = packageName + '/' + simpleClassName( lafClass ) + ".properties"; String propertiesName = packageName + '/' + simpleClassName( lafClass ) + ".properties";
try( InputStream in = classLoader.getResourceAsStream( propertiesName ) ) { try( InputStream in = classLoader.getResourceAsStream( propertiesName ) ) {
if( in != null ) if( in != null ) {
properties.load( in ); properties.load( in );
found = true;
}
}
}
// fallback for named Java modules
if( !found ) {
// Get package URL using ClassLoader.getResource(...) because this works
// also in named Java modules, even without opening the package in module-info.java.
// This extra step is necessary because ClassLoader.getResource("<package>/<file>.properties")
// does not work for named Java modules.
URL url = classLoader.getResource( packageName );
if( url == null )
continue;
String packageUrl = url.toExternalForm();
if( !packageUrl.endsWith( "/" ) )
packageUrl = packageUrl.concat( "/" );
for( Class<?> lafClass : lafClasses ) {
URL propertiesUrl = new URL( packageUrl + simpleClassName( lafClass ) + ".properties" );
try( InputStream in = propertiesUrl.openStream() ) {
properties.load( in );
} catch( FileNotFoundException ex ) {
// ignore
}
} }
} }
} else if( source instanceof URL ) { } else if( source instanceof URL ) {
// load from package URL // load from package URL
URL packageUrl = (URL) source; String packageUrl = ((URL)source).toExternalForm();
if( !packageUrl.endsWith( "/" ) )
packageUrl = packageUrl.concat( "/" );
for( Class<?> lafClass : lafClasses ) { for( Class<?> lafClass : lafClasses ) {
URL propertiesUrl = new URL( packageUrl + simpleClassName( lafClass ) + ".properties" ); URL propertiesUrl = new URL( packageUrl + simpleClassName( lafClass ) + ".properties" );
@@ -649,6 +690,7 @@ class UIDefaultsLoader
if( value.indexOf( ',' ) >= 0 ) { if( value.indexOf( ',' ) >= 0 ) {
// Syntax: top,left,bottom,right[,lineColor[,lineThickness[,arc]]] // Syntax: top,left,bottom,right[,lineColor[,lineThickness[,arc]]]
List<String> parts = splitFunctionParams( value, ',' ); List<String> parts = splitFunctionParams( value, ',' );
try {
Insets insets = parseInsets( value ); Insets insets = parseInsets( value );
ColorUIResource lineColor = (parts.size() >= 5 && !parts.get( 4 ).isEmpty()) ColorUIResource lineColor = (parts.size() >= 5 && !parts.get( 4 ).isEmpty())
? (ColorUIResource) parseColorOrFunction( resolver.apply( parts.get( 4 ) ), resolver ) ? (ColorUIResource) parseColorOrFunction( resolver.apply( parts.get( 4 ) ), resolver )
@@ -665,6 +707,9 @@ class UIDefaultsLoader
? new FlatLineBorder( insets, lineColor, lineThickness, arc ) ? new FlatLineBorder( insets, lineColor, lineThickness, arc )
: new FlatEmptyBorder( insets ); : new FlatEmptyBorder( insets );
}; };
} catch( RuntimeException ex ) {
throw new IllegalArgumentException( "invalid border '" + value + "' (" + ex.getMessage() + ")" );
}
} else } else
return parseInstance( value, resolver, addonClassLoaders ); return parseInstance( value, resolver, addonClassLoaders );
} }
@@ -735,7 +780,7 @@ class UIDefaultsLoader
Integer.parseInt( numbers.get( 1 ) ), Integer.parseInt( numbers.get( 1 ) ),
Integer.parseInt( numbers.get( 2 ) ), Integer.parseInt( numbers.get( 2 ) ),
Integer.parseInt( numbers.get( 3 ) ) ); Integer.parseInt( numbers.get( 3 ) ) );
} catch( NumberFormatException ex ) { } catch( NumberFormatException | IndexOutOfBoundsException ex ) {
throw new IllegalArgumentException( "invalid insets '" + value + "'" ); throw new IllegalArgumentException( "invalid insets '" + value + "'" );
} }
} }
@@ -748,7 +793,7 @@ class UIDefaultsLoader
return new DimensionUIResource( return new DimensionUIResource(
Integer.parseInt( numbers.get( 0 ) ), Integer.parseInt( numbers.get( 0 ) ),
Integer.parseInt( numbers.get( 1 ) ) ); Integer.parseInt( numbers.get( 1 ) ) );
} catch( NumberFormatException ex ) { } catch( NumberFormatException | IndexOutOfBoundsException ex ) {
throw new IllegalArgumentException( "invalid size '" + value + "'" ); throw new IllegalArgumentException( "invalid size '" + value + "'" );
} }
} }
@@ -1379,17 +1424,17 @@ class UIDefaultsLoader
break; break;
} }
} }
} catch( IOException ex ) { } catch( RuntimeException | IOException ex ) {
throw new IllegalArgumentException( ex ); throw new IllegalArgumentException( "invalid font '" + value + "' (" + ex.getMessage() + ")" );
} }
if( style != -1 && styleChange != 0 ) if( style != -1 && styleChange != 0 )
throw new IllegalArgumentException( "can not mix absolute style (e.g. 'bold') with derived style (e.g. '+italic') in '" + value + "'" ); throw new IllegalArgumentException( "invalid font '" + value + "': can not mix absolute style (e.g. 'bold') with derived style (e.g. '+italic')" );
if( styleChange != 0 ) { if( styleChange != 0 ) {
if( (styleChange & Font.BOLD) != 0 && (styleChange & (Font.BOLD << 16)) != 0 ) if( (styleChange & Font.BOLD) != 0 && (styleChange & (Font.BOLD << 16)) != 0 )
throw new IllegalArgumentException( "can not use '+bold' and '-bold' in '" + value + "'" ); throw new IllegalArgumentException( "invalid font '" + value + "': can not use '+bold' and '-bold'" );
if( (styleChange & Font.ITALIC) != 0 && (styleChange & (Font.ITALIC << 16)) != 0 ) if( (styleChange & Font.ITALIC) != 0 && (styleChange & (Font.ITALIC << 16)) != 0 )
throw new IllegalArgumentException( "can not use '+italic' and '-italic' in '" + value + "'" ); throw new IllegalArgumentException( "invalid font '" + value + "': can not use '+italic' and '-italic'" );
} }
font = new FlatLaf.ActiveFont( baseFontKey, families, style, styleChange, absoluteSize, relativeSize, scaleSize ); font = new FlatLaf.ActiveFont( baseFontKey, families, style, styleChange, absoluteSize, relativeSize, scaleSize );
@@ -1529,7 +1574,7 @@ class UIDefaultsLoader
return (LazyValue) t -> { return (LazyValue) t -> {
return new GrayFilter( brightness, contrast, alpha ); return new GrayFilter( brightness, contrast, alpha );
}; };
} catch( NumberFormatException ex ) { } catch( NumberFormatException | IndexOutOfBoundsException ex ) {
throw new IllegalArgumentException( "invalid gray filter '" + value + "'" ); throw new IllegalArgumentException( "invalid gray filter '" + value + "'" );
} }
} }

View File

@@ -381,6 +381,12 @@ public class FlatButtonUI
return ((FlatHelpButtonIcon)helpButtonIcon).applyStyleProperty( key, value ); return ((FlatHelpButtonIcon)helpButtonIcon).applyStyleProperty( key, value );
} }
// update internal values; otherwise isCustomBackground() and isCustomForeground() would return wrong results
switch( key ) {
case "background": background = (Color) value; break;
case "foreground": foreground = (Color) value; break;
}
if( "iconTextGap".equals( key ) && value instanceof Integer ) if( "iconTextGap".equals( key ) && value instanceof Integer )
value = UIScale.scale( (Integer) value ); value = UIScale.scale( (Integer) value );

View File

@@ -882,7 +882,7 @@ public class FlatComboBoxUI
GraphicsConfiguration gc = comboBox.getGraphicsConfiguration(); GraphicsConfiguration gc = comboBox.getGraphicsConfiguration();
if( gc != null ) { if( gc != null ) {
Rectangle screenBounds = gc.getBounds(); Rectangle screenBounds = gc.getBounds();
Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets( gc ); Insets screenInsets = FlatUIUtils.getScreenInsets( gc );
displayWidth = Math.min( displayWidth, screenBounds.width - screenInsets.left - screenInsets.right ); displayWidth = Math.min( displayWidth, screenBounds.width - screenInsets.left - screenInsets.right );
} else { } else {
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();

View File

@@ -302,6 +302,7 @@ public class FlatListUI
ListModel dataModel, ListSelectionModel selModel, int leadIndex ) ListModel dataModel, ListSelectionModel selModel, int leadIndex )
{ {
boolean isSelected = selModel.isSelectedIndex( row ); boolean isSelected = selModel.isSelectedIndex( row );
boolean isDropRow = isDropRow( row );
// paint alternating rows // paint alternating rows
if( alternateRowColor != null && row % 2 != 0 && if( alternateRowColor != null && row % 2 != 0 &&
@@ -335,7 +336,7 @@ public class FlatListUI
} }
// rounded selection or selection insets // rounded selection or selection insets
if( isSelected && if( (isSelected || isDropRow) &&
!isFileList && // rounded selection is not supported for file list !isFileList && // rounded selection is not supported for file list
(rendererComponent instanceof DefaultListCellRenderer || (rendererComponent instanceof DefaultListCellRenderer ||
rendererComponent instanceof BasicComboBoxRenderer) && rendererComponent instanceof BasicComboBoxRenderer) &&
@@ -376,6 +377,21 @@ public class FlatListUI
this.getColor() == rendererComponent.getBackground() ) this.getColor() == rendererComponent.getBackground() )
{ {
inPaintSelection = true; inPaintSelection = true;
if( isDropRow ) {
// for rounded drop background, it is necessary to first
// paint selection background because may be not rounded on some corners
if( isSelected ) {
Color oldColor = getColor();
setColor( list.getSelectionBackground() );
paintCellSelection( this, row, x, y, width, height );
setColor( oldColor );
}
// paint drop background
float arc = UIScale.scale( selectionArc / 2f );
FlatUIUtils.paintSelection( this, x, y, width, height,
UIScale.scale( selectionInsets ), arc, arc, arc, arc, 0 );
} else
paintCellSelection( this, row, x, y, width, height ); paintCellSelection( this, row, x, y, width, height );
inPaintSelection = false; inPaintSelection = false;
} else } else
@@ -475,4 +491,15 @@ public class FlatListUI
FlatListUI ui = (FlatListUI) list.getUI(); FlatListUI ui = (FlatListUI) list.getUI();
ui.paintCellSelection( g, row, x, y, width, height ); ui.paintCellSelection( g, row, x, y, width, height );
} }
/**
* Checks whether dropping on a row.
* See DefaultListCellRenderer.getListCellRendererComponent().
*/
private boolean isDropRow( int row ) {
JList.DropLocation dropLocation = list.getDropLocation();
return dropLocation != null &&
!dropLocation.isInsert() &&
dropLocation.getIndex() == row;
}
} }

View File

@@ -32,7 +32,6 @@ import java.awt.Panel;
import java.awt.Point; import java.awt.Point;
import java.awt.PointerInfo; import java.awt.PointerInfo;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window; import java.awt.Window;
import java.awt.event.ComponentEvent; import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener; import java.awt.event.ComponentListener;
@@ -312,7 +311,7 @@ public class FlatPopupFactory
return null; return null;
Rectangle screenBounds = gc.getBounds(); Rectangle screenBounds = gc.getBounds();
Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets( gc ); Insets screenInsets = FlatUIUtils.getScreenInsets( gc );
int screenTop = screenBounds.y + screenInsets.top; int screenTop = screenBounds.y + screenInsets.top;
// place tooltip above mouse location if there is enough space // place tooltip above mouse location if there is enough space
@@ -547,7 +546,15 @@ public class FlatPopupFactory
int x = popupWindow.getX(); int x = popupWindow.getX();
int y = popupWindow.getY(); int y = popupWindow.getY();
if( !popupWindow.isVisible() )
popup.show(); popup.show();
else {
// if the popup window is already visible (because it is reused),
// do not invoke Popup.show() because this would invoke Window.toFront(),
// which may have the side effect that an inactive owner window
// would be also moved to front and maybe hide previously active window
popupWindow.pack();
}
// restore popup window location if it has changed // restore popup window location if it has changed
// (probably scaled when screens use different scale factors) // (probably scaled when screens use different scale factors)
@@ -619,6 +626,29 @@ public class FlatPopupFactory
void showImpl() { void showImpl() {
if( delegate != null ) { if( delegate != null ) {
// On macOS and Linux, the empty popup window is shown in popup.show()
// (in peer.setVisible(true) invoked from Component.show()),
// but the popup content is painted later via repaint manager.
// This may cause some flicker, especially during JVM warm-up or
// when running JVM in interpreter mode (option -Xint).
// To reduce flicker, immediately paint popup content as soon as popup window becomes visible.
// This also fixes a problem with JetBrainsRuntime JVM, where sometimes the popups were empty.
if( (SystemInfo.isMacOS || SystemInfo.isLinux) && popupWindow instanceof JWindow ) {
HierarchyListener l = e -> {
if( e.getID() == HierarchyEvent.HIERARCHY_CHANGED &&
(e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0 )
{
((JWindow)popupWindow).getRootPane().paintImmediately(
0, 0, popupWindow.getWidth(), popupWindow.getHeight() );
}
};
popupWindow.addHierarchyListener( l );
try {
showPopupAndFixLocation( delegate, popupWindow );
} finally {
popupWindow.removeHierarchyListener( l );
}
} else
showPopupAndFixLocation( delegate, popupWindow ); showPopupAndFixLocation( delegate, popupWindow );
// increase tooltip size if necessary because it may be too small on HiDPI screens // increase tooltip size if necessary because it may be too small on HiDPI screens
@@ -646,8 +676,11 @@ public class FlatPopupFactory
return; return;
disposed = true; disposed = true;
// immediately hide non-heavy weight popups or combobox popups // immediately hide non-heavy weight popups, popup menus and combobox popups
if( !(popupWindow instanceof JWindow) || contents instanceof BasicComboPopup ) { // of if system property is false
if( !(popupWindow instanceof JWindow) || contents instanceof JPopupMenu ||
!FlatSystemProperties.getBoolean( FlatSystemProperties.REUSE_VISIBLE_POPUP_WINDOW, true ) )
{
hideImpl(); hideImpl();
return; return;
} }
@@ -678,6 +711,21 @@ public class FlatPopupFactory
// restore background so that it can not affect other LaFs (when switching) // restore background so that it can not affect other LaFs (when switching)
// because popup windows are cached and reused // because popup windows are cached and reused
popupWindow.setBackground( oldPopupWindowBackground ); popupWindow.setBackground( oldPopupWindowBackground );
// On macOS, popupWindow.setBackground(...) invoked from constructor,
// has no affect if the popup window peer (a NSWindow) is already created,
// which is the case when reusing a cached popup window
// (see class PopupFactory.HeavyWeightPopup).
// This may result in flicker when e.g. showing a popup in a light theme,
// then switching to a dark theme and again showing a popup,
// because the underling NSWindow still has a light background,
// which may be shown shortly before the actual dark popup content is shown.
// To fix this, dispose the popup window, which disposes the NSWindow.
// The AWT popup window stays in the popup cache, but when reusing it later,
// a new peer and a new NSWindow is created and gets the correct background.
if( SystemInfo.isMacOS )
popupWindow.dispose();
popupWindow = null; popupWindow = null;
} }
} }

View File

@@ -246,7 +246,7 @@ public class FlatPopupMenuUI
// (always subtract screen insets because there is no API to detect whether // (always subtract screen insets because there is no API to detect whether
// the popup can overlap the taskbar; see JPopupMenu.canPopupOverlapTaskBar()) // the popup can overlap the taskbar; see JPopupMenu.canPopupOverlapTaskBar())
Rectangle screenBounds = gc.getBounds(); Rectangle screenBounds = gc.getBounds();
Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets( gc ); Insets screenInsets = FlatUIUtils.getScreenInsets( gc );
return FlatUIUtils.subtractInsets( screenBounds, screenInsets ); return FlatUIUtils.subtractInsets( screenBounds, screenInsets );
} }

View File

@@ -20,6 +20,7 @@ import java.awt.Color;
import java.awt.Component; import java.awt.Component;
import java.awt.Container; import java.awt.Container;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Frame; import java.awt.Frame;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
@@ -153,8 +154,28 @@ public class FlatRootPaneUI
Container parent = c.getParent(); Container parent = c.getParent();
if( parent instanceof JFrame || parent instanceof JDialog ) { if( parent instanceof JFrame || parent instanceof JDialog ) {
Color background = parent.getBackground(); Color background = parent.getBackground();
if( background == null || background instanceof UIResource ) if( background == null || background instanceof UIResource ) {
if( SystemInfo.isMacOS ) {
// Setting window background on macOS immediately fills the whole window
// with that color, and slightly delayed, the Swing repaint manager
// repaints the actual window content. This results in some flashing
// when switching from a light to a dark theme (or vice versa).
// --> delay setting window background and immediately repaint window content
Runnable r = () -> {
parent.setBackground( UIManager.getColor( "control" ) ); parent.setBackground( UIManager.getColor( "control" ) );
c.paintImmediately( 0, 0, c.getWidth(), c.getHeight() );
};
// for class FlatAnimatedLafChange:
// if animated Laf change is in progress, set background color when
// animation has finished to avoid/reduce flashing
if( c.getClientProperty( "FlatLaf.internal.animatedLafChange" ) != null )
c.putClientProperty( "FlatLaf.internal.animatedLafChange.runWhenFinished", r );
else
EventQueue.invokeLater( r );
} else
parent.setBackground( UIManager.getColor( "control" ) );
}
} }
macClearBackgroundForTranslucentWindow( c ); macClearBackgroundForTranslucentWindow( c );

View File

@@ -39,6 +39,7 @@ import java.beans.PropertyChangeListener;
import java.util.Map; import java.util.Map;
import javax.swing.Action; import javax.swing.Action;
import javax.swing.ActionMap; import javax.swing.ActionMap;
import javax.swing.Icon;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JScrollPane; import javax.swing.JScrollPane;
import javax.swing.JTable; import javax.swing.JTable;
@@ -989,12 +990,14 @@ public class FlatTableUI
FlatBooleanRenderer() { FlatBooleanRenderer() {
setHorizontalAlignment( SwingConstants.CENTER ); setHorizontalAlignment( SwingConstants.CENTER );
setIcon( new FlatCheckBoxIcon() { Icon icon = new FlatCheckBoxIcon() {
@Override @Override
protected boolean isSelected( Component c ) { protected boolean isSelected( Component c ) {
return selected; return selected;
} }
} ); };
setIcon( icon );
setDisabledIcon( icon );
} }
@Override @Override

View File

@@ -914,7 +914,7 @@ public class FlatTitlePane
// screen insets are in physical size, except for Java 15+ // screen insets are in physical size, except for Java 15+
// (see https://bugs.openjdk.java.net/browse/JDK-8243925) // (see https://bugs.openjdk.java.net/browse/JDK-8243925)
// and except for Java 8 on secondary screens where primary screen is scaled // and except for Java 8 on secondary screens where primary screen is scaled
Insets screenInsets = window.getToolkit().getScreenInsets( gc ); Insets screenInsets = FlatUIUtils.getScreenInsets( gc );
// maximized bounds are required in physical size, except for Java 15+ // maximized bounds are required in physical size, except for Java 15+
// (see https://bugs.openjdk.java.net/browse/JDK-8231564 and // (see https://bugs.openjdk.java.net/browse/JDK-8231564 and

View File

@@ -577,7 +577,6 @@ public class FlatTreeUI
boolean isEditing = (editingComponent != null && editingRow == row); boolean isEditing = (editingComponent != null && editingRow == row);
boolean isSelected = tree.isRowSelected( row ); boolean isSelected = tree.isRowSelected( row );
boolean isDropRow = isDropRow( row ); boolean isDropRow = isDropRow( row );
boolean needsSelectionPainting = (isSelected || isDropRow) && isPaintSelection();
// paint alternating rows // paint alternating rows
if( alternateRowColor != null && row % 2 != 0 ) { if( alternateRowColor != null && row % 2 != 0 ) {
@@ -608,7 +607,7 @@ public class FlatTreeUI
if( isSelected && isWideSelection() ) { if( isSelected && isWideSelection() ) {
Color oldColor = g.getColor(); Color oldColor = g.getColor();
g.setColor( selectionInactiveBackground ); g.setColor( selectionInactiveBackground );
paintWideSelection( g, bounds, row ); paintWideSelection( g, bounds, row, false );
g.setColor( oldColor ); g.setColor( oldColor );
} }
return; return;
@@ -628,7 +627,7 @@ public class FlatTreeUI
// renderer background/foreground // renderer background/foreground
Color oldBackgroundSelectionColor = null; Color oldBackgroundSelectionColor = null;
if( isSelected && !hasFocus && !isDropRow ) { if( isSelected && !hasFocus ) {
// apply inactive selection background/foreground if tree is not focused // apply inactive selection background/foreground if tree is not focused
oldBackgroundSelectionColor = setRendererBackgroundSelectionColor( rendererComponent, selectionInactiveBackground ); oldBackgroundSelectionColor = setRendererBackgroundSelectionColor( rendererComponent, selectionInactiveBackground );
setRendererForeground( rendererComponent, selectionInactiveForeground ); setRendererForeground( rendererComponent, selectionInactiveForeground );
@@ -655,26 +654,12 @@ public class FlatTreeUI
} }
// paint selection background // paint selection background
if( needsSelectionPainting ) { if( isSelected && isPaintSelection() ) {
// set selection color Color selectionColor = rendererComponent instanceof DefaultTreeCellRenderer
Color oldColor = g.getColor();
g.setColor( isDropRow
? UIManager.getColor( "Tree.dropCellBackground" )
: (rendererComponent instanceof DefaultTreeCellRenderer
? ((DefaultTreeCellRenderer)rendererComponent).getBackgroundSelectionColor() ? ((DefaultTreeCellRenderer)rendererComponent).getBackgroundSelectionColor()
: (hasFocus ? selectionBackground : selectionInactiveBackground)) ); : (hasFocus ? selectionBackground : selectionInactiveBackground);
if( isWideSelection() ) { paintRowSelection( g, selectionColor, rendererComponent, bounds, row, false );
// wide selection
paintWideSelection( g, bounds, row );
} else {
// non-wide selection
paintCellBackground( g, rendererComponent, bounds, row, true );
}
// this is actually not necessary because renderer should always set color
// before painting, but doing anyway to avoid any side effect (in bad renderers)
g.setColor( oldColor );
} else { } else {
// paint cell background if DefaultTreeCellRenderer.getBackgroundNonSelectionColor() is set // paint cell background if DefaultTreeCellRenderer.getBackgroundNonSelectionColor() is set
if( rendererComponent instanceof DefaultTreeCellRenderer ) { if( rendererComponent instanceof DefaultTreeCellRenderer ) {
@@ -683,12 +668,19 @@ public class FlatTreeUI
if( bg != null && !bg.equals( defaultCellNonSelectionBackground ) ) { if( bg != null && !bg.equals( defaultCellNonSelectionBackground ) ) {
Color oldColor = g.getColor(); Color oldColor = g.getColor();
g.setColor( bg ); g.setColor( bg );
paintCellBackground( g, rendererComponent, bounds, row, false ); paintCellBackground( g, rendererComponent, bounds, row, false, false );
g.setColor( oldColor ); g.setColor( oldColor );
} }
} }
} }
// paint drop background
// (this needs to be an extra step for rounded selection)
if( isDropRow && isPaintSelection() ) {
paintRowSelection( g, UIManager.getColor( "Tree.dropCellBackground" ),
rendererComponent, bounds, row, true );
}
// paint renderer // paint renderer
rendererPane.paintComponent( g, rendererComponent, tree, bounds.x, bounds.y, bounds.width, bounds.height, true ); rendererPane.paintComponent( g, rendererComponent, tree, bounds.x, bounds.y, bounds.width, bounds.height, true );
@@ -699,6 +691,26 @@ public class FlatTreeUI
((DefaultTreeCellRenderer)rendererComponent).setBorderSelectionColor( oldBorderSelectionColor ); ((DefaultTreeCellRenderer)rendererComponent).setBorderSelectionColor( oldBorderSelectionColor );
} }
private void paintRowSelection( Graphics g, Color color, Component rendererComponent,
Rectangle bounds, int row, boolean paintDropSelection )
{
// set selection color
Color oldColor = g.getColor();
g.setColor( color );
if( isWideSelection() ) {
// wide selection
paintWideSelection( g, bounds, row, paintDropSelection );
} else {
// non-wide selection
paintCellBackground( g, rendererComponent, bounds, row, true, paintDropSelection );
}
// this is actually not necessary because renderer should always set color
// before painting, but doing anyway to avoid any side effect (in bad renderers)
g.setColor( oldColor );
}
private Color setRendererBackgroundSelectionColor( Component rendererComponent, Color color ) { private Color setRendererBackgroundSelectionColor( Component rendererComponent, Color color ) {
Color oldColor = null; Color oldColor = null;
@@ -735,11 +747,11 @@ public class FlatTreeUI
return oldColor; return oldColor;
} }
private void paintWideSelection( Graphics g, Rectangle bounds, int row ) { private void paintWideSelection( Graphics g, Rectangle bounds, int row, boolean paintDropSelection ) {
float arcTop, arcBottom; float arcTop, arcBottom;
arcTop = arcBottom = UIScale.scale( selectionArc / 2f ); arcTop = arcBottom = UIScale.scale( selectionArc / 2f );
if( useUnitedRoundedSelection() ) { if( useUnitedRoundedSelection() && !paintDropSelection ) {
if( row > 0 && tree.isRowSelected( row - 1 ) ) if( row > 0 && tree.isRowSelected( row - 1 ) )
arcTop = 0; arcTop = 0;
if( row < tree.getRowCount() - 1 && tree.isRowSelected( row + 1 ) ) if( row < tree.getRowCount() - 1 && tree.isRowSelected( row + 1 ) )
@@ -751,7 +763,7 @@ public class FlatTreeUI
} }
private void paintCellBackground( Graphics g, Component rendererComponent, Rectangle bounds, private void paintCellBackground( Graphics g, Component rendererComponent, Rectangle bounds,
int row, boolean paintSelection ) int row, boolean paintSelection, boolean paintDropSelection )
{ {
int xOffset = 0; int xOffset = 0;
int imageOffset = 0; int imageOffset = 0;
@@ -769,7 +781,7 @@ public class FlatTreeUI
float arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight; float arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight;
arcTopLeft = arcTopRight = arcBottomLeft = arcBottomRight = UIScale.scale( selectionArc / 2f ); arcTopLeft = arcTopRight = arcBottomLeft = arcBottomRight = UIScale.scale( selectionArc / 2f );
if( useUnitedRoundedSelection() ) { if( useUnitedRoundedSelection() && !paintDropSelection ) {
if( row > 0 && tree.isRowSelected( row - 1 ) ) { if( row > 0 && tree.isRowSelected( row - 1 ) ) {
Rectangle r = getPathBounds( tree, tree.getPathForRow( row - 1 ) ); Rectangle r = getPathBounds( tree, tree.getPathForRow( row - 1 ) );
arcTopLeft = Math.min( arcTopLeft, r.x - bounds.x ); arcTopLeft = Math.min( arcTopLeft, r.x - bounds.x );

View File

@@ -26,6 +26,7 @@ import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration; import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice; import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Insets; import java.awt.Insets;
import java.awt.KeyboardFocusManager; import java.awt.KeyboardFocusManager;
import java.awt.Paint; import java.awt.Paint;
@@ -34,6 +35,7 @@ import java.awt.RenderingHints;
import java.awt.Shape; import java.awt.Shape;
import java.awt.Stroke; import java.awt.Stroke;
import java.awt.SystemColor; import java.awt.SystemColor;
import java.awt.Toolkit;
import java.awt.Window; import java.awt.Window;
import java.awt.event.FocusEvent; import java.awt.event.FocusEvent;
import java.awt.event.FocusListener; import java.awt.event.FocusListener;
@@ -414,6 +416,17 @@ public class FlatUIUtils
return (fullScreenWindow != null && fullScreenWindow == SwingUtilities.windowForComponent( c )); return (fullScreenWindow != null && fullScreenWindow == SwingUtilities.windowForComponent( c ));
} }
/** @since 3.6.1 */
public static Insets getScreenInsets( GraphicsConfiguration gc ) {
// on Linux, getScreenInsets() may report wrong values in multi-screen setups
// see https://github.com/apache/netbeans/issues/8532#issuecomment-2909687016
if( SystemInfo.isLinux &&
GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices().length > 1 )
return new Insets( 0, 0, 0, 0 );
return Toolkit.getDefaultToolkit().getScreenInsets( gc );
}
public static Boolean isRoundRect( Component c ) { public static Boolean isRoundRect( Component c ) {
return (c instanceof JComponent) return (c instanceof JComponent)
? FlatClientProperties.clientPropertyBooleanStrict( ? FlatClientProperties.clientPropertyBooleanStrict(

View File

@@ -331,7 +331,7 @@ public abstract class FlatWindowResizer
protected Rectangle getParentBounds() { protected Rectangle getParentBounds() {
GraphicsConfiguration gc = window.getGraphicsConfiguration(); GraphicsConfiguration gc = window.getGraphicsConfiguration();
Rectangle bounds = gc.getBounds(); Rectangle bounds = gc.getBounds();
Insets insets = window.getToolkit().getScreenInsets( gc ); Insets insets = FlatUIUtils.getScreenInsets( gc );
return new Rectangle( bounds.x + insets.left, bounds.y + insets.top, return new Rectangle( bounds.x + insets.left, bounds.y + insets.top,
bounds.width - insets.left - insets.right, bounds.width - insets.left - insets.right,
bounds.height - insets.top - insets.bottom ); bounds.height - insets.top - insets.bottom );

View File

@@ -21,6 +21,7 @@ import java.awt.Dialog;
import java.awt.EventQueue; import java.awt.EventQueue;
import java.awt.Frame; import java.awt.Frame;
import java.awt.GraphicsConfiguration; import java.awt.GraphicsConfiguration;
import java.awt.Insets;
import java.awt.Point; import java.awt.Point;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.Window; import java.awt.Window;
@@ -39,6 +40,7 @@ import javax.swing.event.ChangeListener;
import javax.swing.event.EventListenerList; import javax.swing.event.EventListenerList;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale;
// //
// Interesting resources: // Interesting resources:
@@ -282,6 +284,8 @@ class FlatWindowsNativeWindowBorder
HTMINBUTTON = 8, HTMINBUTTON = 8,
HTMAXBUTTON = 9, HTMAXBUTTON = 9,
HTTOP = 12, HTTOP = 12,
HTTOPLEFT = 13,
HTTOPRIGHT = 14,
HTCLOSE = 20; HTCLOSE = 20;
private Window window; private Window window;
@@ -341,6 +345,31 @@ class FlatWindowsNativeWindowBorder
// scale-down mouse x/y because Swing coordinates/values may be scaled on a HiDPI screen // scale-down mouse x/y because Swing coordinates/values may be scaled on a HiDPI screen
Point pt = scaleDown( x, y ); Point pt = scaleDown( x, y );
// limit top resize border to 4px, which seems to be standard for modern WinUI apps
if( isOnResizeBorder && pt.y > UIScale.scale( 4 ) )
isOnResizeBorder = false;
if( isOnResizeBorder ) {
Insets insets = window.getInsets();
// return HTTOPLEFT if mouse is over top resize border on left side
// - hovering mouse shows top-left resize cursor
// - left-click-and-drag resizes window
if( pt.x <= insets.left + UIScale.scale( 12 ) )
return HTTOPLEFT;
// return HTTOPRIGHT if mouse is over top resize border on right side
// - hovering mouse shows top-right resize cursor
// - left-click-and-drag resizes window
if( pt.x >= window.getWidth() - insets.right - UIScale.scale( 12 ) )
return HTTOPRIGHT;
// return HTTOP if mouse is over top resize border
// - hovering mouse shows vertical resize cursor
// - left-click-and-drag vertically resizes window
return HTTOP;
}
// return HTSYSMENU if mouse is over application icon // return HTSYSMENU if mouse is over application icon
// - left-click on HTSYSMENU area shows system menu // - left-click on HTSYSMENU area shows system menu
// - double-left-click sends WM_CLOSE // - double-left-click sends WM_CLOSE
@@ -364,12 +393,6 @@ class FlatWindowsNativeWindowBorder
if( contains( closeButtonBounds, pt ) ) if( contains( closeButtonBounds, pt ) )
return HTCLOSE; return HTCLOSE;
// return HTTOP if mouse is over top resize border
// - hovering mouse shows vertical resize cursor
// - left-click and drag vertically resizes window
if( isOnResizeBorder )
return HTTOP;
boolean isOnTitleBar = (pt.y < titleBarHeight); boolean isOnTitleBar = (pt.y < titleBarHeight);
if( isOnTitleBar ) { if( isOnTitleBar ) {
// return HTCLIENT if mouse is over any Swing component in title bar // return HTCLIENT if mouse is over any Swing component in title bar

View File

@@ -184,6 +184,7 @@ MenuBar.selectionEmbeddedInsets = 3,0,3,0
MenuBar.selectionArc = 8 MenuBar.selectionArc = 8
MenuBar.selectionBackground = lighten(@menuBackground,15%,derived) MenuBar.selectionBackground = lighten(@menuBackground,15%,derived)
MenuBar.selectionForeground = @foreground MenuBar.selectionForeground = @foreground
MenuBar.borderColor = over($Separator.foreground,$MenuBar.background)
#---- MenuItem ---- #---- MenuItem ----

View File

@@ -184,6 +184,7 @@ MenuBar.selectionEmbeddedInsets = 3,0,3,0
MenuBar.selectionArc = 8 MenuBar.selectionArc = 8
MenuBar.selectionBackground = darken(@menuBackground,15%,derived) MenuBar.selectionBackground = darken(@menuBackground,15%,derived)
MenuBar.selectionForeground = @foreground MenuBar.selectionForeground = @foreground
MenuBar.borderColor = over($Separator.foreground,$MenuBar.background)
#---- MenuItem ---- #---- MenuItem ----

View File

@@ -18,6 +18,7 @@ package com.formdev.flatlaf;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import java.awt.Color; import java.awt.Color;
import java.awt.Dimension; import java.awt.Dimension;
@@ -29,6 +30,7 @@ import javax.swing.border.Border;
import javax.swing.UIDefaults.ActiveValue; import javax.swing.UIDefaults.ActiveValue;
import javax.swing.UIDefaults.LazyValue; import javax.swing.UIDefaults.LazyValue;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import com.formdev.flatlaf.ui.FlatEmptyBorder; import com.formdev.flatlaf.ui.FlatEmptyBorder;
import com.formdev.flatlaf.ui.FlatLineBorder; import com.formdev.flatlaf.ui.FlatLineBorder;
import com.formdev.flatlaf.util.DerivedColor; import com.formdev.flatlaf.util.DerivedColor;
@@ -452,6 +454,73 @@ public class TestUIDefaultsLoader
return ((LazyValue)v).createValue( null ); return ((LazyValue)v).createValue( null );
} }
//---- invalid values -----------------------------------------------------
@Test
void parseInvalidValue() {
assertThrows( new IllegalArgumentException( "invalid character 'abc'" ), () -> UIDefaultsLoader.parseValue( "dummyChar", "abc", null ) );
assertThrows( new NumberFormatException( "invalid integer or float '123abc'" ), () -> UIDefaultsLoader.parseValue( "dummyWidth", "123abc", null ) );
assertThrows( new NumberFormatException( "invalid integer or float '1.23abc'" ), () -> UIDefaultsLoader.parseValue( "dummyWidth", "1.23abc", null ) );
assertThrows( new IllegalArgumentException( "invalid insets '1,abc,3,4'" ), () -> UIDefaultsLoader.parseValue( "dummyInsets", "1,abc,3,4", null ) );
assertThrows( new IllegalArgumentException( "invalid insets '1,2,3'" ), () -> UIDefaultsLoader.parseValue( "dummyInsets", "1,2,3", null ) );
assertThrows( new IllegalArgumentException( "invalid size '1abc'" ), () -> UIDefaultsLoader.parseValue( "dummySize", "1abc", null ) );
assertThrows( new IllegalArgumentException( "invalid size '1'" ), () -> UIDefaultsLoader.parseValue( "dummySize", "1", null ) );
assertThrows( new IllegalArgumentException( "invalid color '#f0'" ), () -> UIDefaultsLoader.parseValue( "dummy", "#f0", null ) );
assertThrows( new IllegalArgumentException( "invalid color '#f0'" ), () -> UIDefaultsLoader.parseValue( "dummyColor", "#f0", null ) );
}
@Test
void parseInvalidValueWithJavaType() {
assertThrows( new IllegalArgumentException( "invalid boolean 'falseyy'" ), () -> UIDefaultsLoader.parseValue( "dummy", "falseyy", boolean.class ) );
assertThrows( new IllegalArgumentException( "invalid boolean 'falseyy'" ), () -> UIDefaultsLoader.parseValue( "dummy", "falseyy", Boolean.class ) );
assertThrows( new IllegalArgumentException( "invalid character 'abc'" ), () -> UIDefaultsLoader.parseValue( "dummyChar", "abc", char.class ) );
assertThrows( new IllegalArgumentException( "invalid character 'abc'" ), () -> UIDefaultsLoader.parseValue( "dummyChar", "abc", Character.class ) );
assertThrows( new NumberFormatException( "invalid integer '123abc'" ), () -> UIDefaultsLoader.parseValue( "dummyWidth", "123abc", int.class ) );
assertThrows( new NumberFormatException( "invalid integer '123abc'" ), () -> UIDefaultsLoader.parseValue( "dummyWidth", "123abc", Integer.class ) );
assertThrows( new NumberFormatException( "invalid float '1.23abc'" ), () -> UIDefaultsLoader.parseValue( "dummyWidth", "1.23abc", float.class ) );
assertThrows( new NumberFormatException( "invalid float '1.23abc'" ), () -> UIDefaultsLoader.parseValue( "dummyWidth", "1.23abc", Float.class ) );
assertThrows( new IllegalArgumentException( "invalid insets '1,abc,3'" ), () -> UIDefaultsLoader.parseValue( "dummyInsets", "1,abc,3", Insets.class ) );
assertThrows( new IllegalArgumentException( "invalid insets '1,2,3'" ), () -> UIDefaultsLoader.parseValue( "dummyInsets", "1,2,3", Insets.class ) );
assertThrows( new IllegalArgumentException( "invalid size '1abc'" ), () -> UIDefaultsLoader.parseValue( "dummySize", "1abc", Dimension.class ) );
assertThrows( new IllegalArgumentException( "invalid size '1'" ), () -> UIDefaultsLoader.parseValue( "dummySize", "1", Dimension.class ) );
assertThrows( new IllegalArgumentException( "invalid color '#f0'" ), () -> UIDefaultsLoader.parseValue( "dummy", "#f0", Color.class ) );
assertThrows( new IllegalArgumentException( "invalid color '#f0'" ), () -> UIDefaultsLoader.parseValue( "dummyColor", "#f0", Color.class ) );
}
@Test
void parseInvalidBorders() {
assertThrows( new IllegalArgumentException( "invalid border '1,abc,3,4' (invalid insets '1,abc,3,4')" ), () -> UIDefaultsLoader.parseValue( "dummyBorder", "1,abc,3,4", null ) );
assertThrows( new IllegalArgumentException( "invalid border '1,2,3' (invalid insets '1,2,3')" ), () -> UIDefaultsLoader.parseValue( "dummyBorder", "1,2,3", null ) );
assertThrows( new IllegalArgumentException( "invalid border '1,2,3,,,' (invalid insets '1,2,3,,,')" ), () -> UIDefaultsLoader.parseValue( "dummyBorder", "1,2,3,,,", null ) );
assertThrows( new IllegalArgumentException( "invalid border '1,2,3,4,#f0' (invalid color '#f0')" ), () -> UIDefaultsLoader.parseValue( "dummyBorder", "1,2,3,4,#f0", null ) );
assertThrows( new IllegalArgumentException( "invalid border '1,2,3,4,#f00,2.5abc' (invalid float '2.5abc')" ), () -> UIDefaultsLoader.parseValue( "dummyBorder", "1,2,3,4,#f00,2.5abc", null ) );
assertThrows( new IllegalArgumentException( "invalid border '1,2,3,4,#f00,2.5,6abc' (invalid integer '6abc')" ), () -> UIDefaultsLoader.parseValue( "dummyBorder", "1,2,3,4,#f00,2.5,6abc", null ) );
}
@Test
void parseInvalidFonts() {
// size
assertThrows( new IllegalArgumentException( "invalid font '12abc' (invalid integer '12abc')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "12abc", null ) );
assertThrows( new IllegalArgumentException( "invalid font '+12abc' (invalid integer '+12abc')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "+12abc", null ) );
assertThrows( new IllegalArgumentException( "invalid font '+3abc' (invalid integer '+3abc')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "+3abc", null ) );
assertThrows( new IllegalArgumentException( "invalid font '-4abc' (invalid integer '-4abc')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "-4abc", null ) );
assertThrows( new IllegalArgumentException( "invalid font '150abc%' (invalid integer '150abc')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "150abc%", null ) );
assertThrows( new IllegalArgumentException( "invalid font 'bold 13abc Monospaced' (invalid integer '13abc')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "bold 13abc Monospaced", null ) );
// invalid combinations of styles
assertThrows( new IllegalArgumentException( "invalid font 'bold +italic': can not mix absolute style (e.g. 'bold') with derived style (e.g. '+italic')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "bold +italic", null ) );
assertThrows( new IllegalArgumentException( "invalid font '+bold -bold': can not use '+bold' and '-bold'" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "+bold -bold", null ) );
assertThrows( new IllegalArgumentException( "invalid font '+italic -italic': can not use '+italic' and '-italic'" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "+italic -italic", null ) );
}
private void assertThrows( Throwable expected, Executable executable ) {
Throwable actual = assertThrowsExactly( expected.getClass(), executable );
assertEquals( expected.getMessage(), actual.getMessage() );
}
//---- class TestInstance ------------------------------------------------- //---- class TestInstance -------------------------------------------------
@SuppressWarnings( "EqualsHashCode" ) // Error Prone @SuppressWarnings( "EqualsHashCode" ) // Error Prone

View File

@@ -0,0 +1,404 @@
/*
* Copyright 2025 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 static org.junit.jupiter.api.Assertions.assertEquals;
import java.awt.Color;
import java.util.HashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JRootPane;
import javax.swing.UIManager;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatLaf;
/**
* @author Karl Tauber
*/
public class TestFlatButton
{
@BeforeAll
static void setup() {
String[] defs = {
"Button.background", "#000001",
"Button.foreground", "#000002",
"Button.focusedBackground", "#000003",
"Button.focusedForeground", "#000004",
"Button.hoverBackground", "#000005",
"Button.hoverForeground", "#000006",
"Button.pressedBackground", "#000007",
"Button.pressedForeground", "#000008",
"Button.selectedBackground", "#000009",
"Button.selectedForeground", "#00000a",
"Button.disabledBackground", "#00000b",
"Button.disabledText", "#00000c",
"Button.disabledSelectedBackground", "#00000d",
"Button.disabledSelectedForeground", "#00000e",
"Button.default.background", "#000101",
"Button.default.foreground", "#000102",
"Button.default.focusedBackground", "#000103",
"Button.default.focusedForeground", "#000104",
"Button.default.hoverBackground", "#000105",
"Button.default.hoverForeground", "#000106",
"Button.default.pressedBackground", "#000107",
"Button.default.pressedForeground", "#000108",
"Button.toolbar.hoverBackground", "#000201",
"Button.toolbar.hoverForeground", "#000202",
"Button.toolbar.pressedBackground", "#000203",
"Button.toolbar.pressedForeground", "#000204",
"Button.toolbar.selectedBackground", "#000205",
"Button.toolbar.selectedForeground", "#000206",
"Button.toolbar.disabledSelectedBackground", "#000207",
"Button.toolbar.disabledSelectedForeground", "#000208",
};
HashMap<String, String> globalExtraDefaults = new HashMap<>();
for( int i = 0; i < defs.length; i += 2 )
globalExtraDefaults.put( defs[i], defs[i+1] );
FlatLaf.setGlobalExtraDefaults( globalExtraDefaults );
TestUtils.setup( false );
}
@AfterAll
static void cleanup() {
TestUtils.cleanup();
}
@Test
void background() {
JButton b = new JButton();
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
UIManager.getColor( "Button.background" ),
UIManager.getColor( "Button.disabledBackground" ),
UIManager.getColor( "Button.focusedBackground" ),
UIManager.getColor( "Button.hoverBackground" ),
UIManager.getColor( "Button.pressedBackground" ) );
// selected
b.setSelected( true );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
UIManager.getColor( "Button.selectedBackground" ),
UIManager.getColor( "Button.disabledSelectedBackground" ),
null,
null,
UIManager.getColor( "Button.pressedBackground" ) );
b.setSelected( false );
// default
JRootPane rootPane = new JRootPane();
rootPane.getContentPane().add( b );
rootPane.setDefaultButton( b );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
UIManager.getColor( "Button.default.background" ),
UIManager.getColor( "Button.disabledBackground" ),
UIManager.getColor( "Button.default.focusedBackground" ),
UIManager.getColor( "Button.default.hoverBackground" ),
UIManager.getColor( "Button.default.pressedBackground" ) );
rootPane.getContentPane().remove( b );
}
@Test
void foreground() {
JButton b = new JButton();
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
UIManager.getColor( "Button.foreground" ),
UIManager.getColor( "Button.disabledText" ),
UIManager.getColor( "Button.focusedForeground" ),
UIManager.getColor( "Button.hoverForeground" ),
UIManager.getColor( "Button.pressedForeground" ) );
// selected
b.setSelected( true );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
UIManager.getColor( "Button.selectedForeground" ),
FlatUIUtils.getUIColor( "Button.disabledSelectedForeground", "Button.disabledText" ),
null,
null,
UIManager.getColor( "Button.pressedForeground" ) );
b.setSelected( false );
// default
JRootPane rootPane = new JRootPane();
rootPane.getContentPane().add( b );
rootPane.setDefaultButton( b );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
UIManager.getColor( "Button.default.foreground" ),
UIManager.getColor( "Button.disabledText" ),
UIManager.getColor( "Button.default.focusedForeground" ),
UIManager.getColor( "Button.default.hoverForeground" ),
UIManager.getColor( "Button.default.pressedForeground" ) );
rootPane.getContentPane().remove( b );
}
@Test
void backgroundExplicit() {
JButton b = new JButton();
Color c = new Color( 0x020001 );
b.setBackground( c );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
c,
UIManager.getColor( "Button.disabledBackground" ),
null,
UIManager.getColor( "Button.hoverBackground" ),
UIManager.getColor( "Button.pressedBackground" ) );
// selected
b.setSelected( true );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
UIManager.getColor( "Button.selectedBackground" ),
UIManager.getColor( "Button.disabledSelectedBackground" ),
null,
null,
UIManager.getColor( "Button.pressedBackground" ) );
b.setSelected( false );
// default
JRootPane rootPane = new JRootPane();
rootPane.getContentPane().add( b );
rootPane.setDefaultButton( b );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
c,
UIManager.getColor( "Button.disabledBackground" ),
null,
UIManager.getColor( "Button.default.hoverBackground" ),
UIManager.getColor( "Button.default.pressedBackground" ) );
rootPane.getContentPane().remove( b );
}
@Test
void foregroundExplicit() {
JButton b = new JButton();
Color c = new Color( 0x020001 );
b.setForeground( c );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
c,
UIManager.getColor( "Button.disabledText" ),
null,
UIManager.getColor( "Button.hoverForeground" ),
UIManager.getColor( "Button.pressedForeground" ) );
// selected
b.setSelected( true );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
c,
FlatUIUtils.getUIColor( "Button.disabledSelectedForeground", "Button.disabledText" ),
null,
null,
UIManager.getColor( "Button.pressedForeground" ) );
b.setSelected( false );
// default
JRootPane rootPane = new JRootPane();
rootPane.getContentPane().add( b );
rootPane.setDefaultButton( b );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
c,
UIManager.getColor( "Button.disabledText" ),
null,
UIManager.getColor( "Button.default.hoverForeground" ),
UIManager.getColor( "Button.default.pressedForeground" ) );
rootPane.getContentPane().remove( b );
}
@Test
void backgroundStyled() {
JButton b = new JButton();
b.putClientProperty( FlatClientProperties.STYLE,
"background: #020001;" +
"disabledBackground: #020002;" +
"focusedBackground: #020003;" +
"hoverBackground: #020004;" +
"pressedBackground: #020005;" +
"selectedBackground: #020006;" +
"disabledSelectedBackground: #020007;" );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
new Color( 0x020001 ),
new Color( 0x020002 ),
new Color( 0x020003 ),
new Color( 0x020004 ),
new Color( 0x020005 ) );
// selected
b.setSelected( true );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
new Color( 0x020006 ),
new Color( 0x020007 ),
null,
null,
new Color( 0x020005 ) );
b.setSelected( false );
Color c = new Color( 0x0a0001 );
b.setBackground( c );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
c,
new Color( 0x020002 ),
c,
new Color( 0x020004 ),
new Color( 0x020005 ) );
b.setSelected( true );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
new Color( 0x020006 ),
new Color( 0x020007 ),
null,
null,
new Color( 0x020005 ) );
b.setSelected( false );
b = new JButton();
b.putClientProperty( FlatClientProperties.STYLE,
"default.background: #020101;" +
"disabledBackground: #020102;" +
"default.focusedBackground: #020103;" +
"default.hoverBackground: #020104;" +
"default.pressedBackground: #020105;" );
// default
JRootPane rootPane = new JRootPane();
rootPane.getContentPane().add( b );
rootPane.setDefaultButton( b );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
new Color( 0x020101 ),
new Color( 0x020102 ),
new Color( 0x020103 ),
new Color( 0x020104 ),
new Color( 0x020105 ) );
rootPane.getContentPane().remove( b );
}
@Test
void foregroundStyled() {
JButton b = new JButton();
b.putClientProperty( FlatClientProperties.STYLE,
"foreground: #020001;" +
"disabledText: #020002;" +
"focusedForeground: #020003;" +
"hoverForeground: #020004;" +
"pressedForeground: #020005;" +
"selectedForeground: #020006;" +
"disabledSelectedForeground: #020007;" );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
new Color( 0x020001 ),
new Color( 0x020002 ),
new Color( 0x020003 ),
new Color( 0x020004 ),
new Color( 0x020005 ) );
// selected
b.setSelected( true );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
new Color( 0x020006 ),
new Color( 0x020007 ),
null,
null,
new Color( 0x020005 ) );
b.setSelected( false );
Color c = new Color( 0x0a0001 );
b.setForeground( c );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
c,
new Color( 0x020002 ),
c,
new Color( 0x020004 ),
new Color( 0x020005 ) );
b.setSelected( true );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
c,
new Color( 0x020007 ),
null,
null,
new Color( 0x020005 ) );
b.setSelected( false );
b = new JButton();
b.putClientProperty( FlatClientProperties.STYLE,
"default.foreground: #020101;" +
"disabledText: #020102;" +
"default.focusedForeground: #020103;" +
"default.hoverForeground: #020104;" +
"default.pressedForeground: #020105;" );
// default
JRootPane rootPane = new JRootPane();
rootPane.getContentPane().add( b );
rootPane.setDefaultButton( b );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
new Color( 0x020101 ),
new Color( 0x020102 ),
new Color( 0x020103 ),
new Color( 0x020104 ),
new Color( 0x020105 ) );
rootPane.getContentPane().remove( b );
}
private void testButtonColors( JButton b, Function<JButton, Color> f,
Color expectedEnabled, Color expectedDisabled, Color expectedFocused,
Color expectedHover, Color expectedPressed
)
{
assertEquals( expectedEnabled, f.apply( b ) );
// disabled
b.setEnabled( false );
assertEquals( expectedDisabled, f.apply( b ) );
b.setEnabled( true );
// focused
if( expectedFocused != null ) {
b.putClientProperty( FlatClientProperties.COMPONENT_FOCUS_OWNER, (Predicate<JComponent>) c -> true );
assertEquals( expectedFocused, f.apply( b ) );
b.putClientProperty( FlatClientProperties.COMPONENT_FOCUS_OWNER, null );
}
// hover
if( expectedHover != null ) {
b.getModel().setRollover( true );
assertEquals( expectedHover, f.apply( b ) );
b.getModel().setRollover( false );
}
// pressed
if( expectedPressed != null ) {
b.getModel().setPressed( true );
assertEquals( expectedPressed, f.apply( b ) );
b.getModel().setPressed( false );
}
}
}

View File

@@ -35,11 +35,16 @@ build script:
Otherwise, download `flatlaf-extras-<version>.jar` here: Otherwise, download `flatlaf-extras-<version>.jar` here:
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf-extras/badge.svg?style=flat-square&color=007ec6)](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf-extras) [![Maven Central](https://img.shields.io/maven-central/v/com.formdev/flatlaf-extras?style=flat-square)](https://central.sonatype.com/artifact/com.formdev/flatlaf-extras)
If SVG classes are used, `jsvg-<version>.jar` is also required: If SVG classes are used, `jsvg-<version>.jar` is also required:
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.weisj/jsvg/badge.svg?style=flat-square&color=007ec6)](https://maven-badges.herokuapp.com/maven-central/com.github.weisj/jsvg) [![Maven Central](https://img.shields.io/maven-central/v/com.github.weisj/jsvg?style=flat-square)](https://central.sonatype.com/artifact/com.github.weisj/jsvg)
Supported JSVG versions:
- FlatLaf 3.6.1+ supports JSVG 1.6.0 and later.
- FlatLaf 3.6- supports only JSVG 1.x (but not 2.x).
Tools Tools

View File

@@ -26,6 +26,7 @@ import java.util.Map;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JLayeredPane; import javax.swing.JLayeredPane;
import javax.swing.JRootPane;
import javax.swing.RootPaneContainer; import javax.swing.RootPaneContainer;
import com.formdev.flatlaf.FlatSystemProperties; import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.util.Animator; import com.formdev.flatlaf.util.Animator;
@@ -52,15 +53,13 @@ public class FlatAnimatedLafChange
public static int duration = 160; public static int duration = 160;
/** /**
* The resolution of the animation in milliseconds. Default is 30 ms. * The resolution of the animation in milliseconds. Default is 16 ms.
*/ */
public static int resolution = 30; public static int resolution = 16;
private static Animator animator; private static Animator animator;
private static final Map<JLayeredPane, JComponent> oldUIsnapshots = new WeakHashMap<>(); private static final Map<JLayeredPane, SnapshotLayer> snapshots = new WeakHashMap<>();
private static final Map<JLayeredPane, JComponent> newUIsnapshots = new WeakHashMap<>();
private static float alpha; private static float alpha;
private static boolean inShowSnapshot;
/** /**
* Create a snapshot of the old UI and shows it on top of the UI. * Create a snapshot of the old UI and shows it on top of the UI.
@@ -77,59 +76,52 @@ public class FlatAnimatedLafChange
alpha = 1; alpha = 1;
// show snapshot of old UI // show snapshot of old UI
showSnapshot( true, oldUIsnapshots ); showSnapshot( true );
} }
private static void showSnapshot( boolean useAlpha, Map<JLayeredPane, JComponent> map ) { private static void showSnapshot( boolean old ) {
inShowSnapshot = true;
// create snapshots for all shown windows // create snapshots for all shown windows
Window[] windows = Window.getWindows(); Window[] windows = Window.getWindows();
for( Window window : windows ) { for( Window window : windows ) {
if( !(window instanceof RootPaneContainer) || !window.isShowing() ) if( !(window instanceof RootPaneContainer) || !window.isShowing() )
continue; continue;
JLayeredPane layeredPane = ((RootPaneContainer)window).getLayeredPane();
// create snapshot image // create snapshot image
// (using volatile image to have correct sub-pixel text rendering on Java 9+) // (using volatile image to have correct sub-pixel text rendering on Java 9+)
VolatileImage snapshot = window.createVolatileImage( window.getWidth(), window.getHeight() ); VolatileImage snapshotImage = layeredPane.createVolatileImage( layeredPane.getWidth(), layeredPane.getHeight() );
if( snapshot == null ) if( snapshotImage == null )
continue; continue;
// paint window to snapshot image // paint window to snapshot image
JLayeredPane layeredPane = ((RootPaneContainer)window).getLayeredPane(); layeredPane.paint( snapshotImage.getGraphics() );
layeredPane.paint( snapshot.getGraphics() );
if( old ) {
// create snapshot layer, which is added to layered pane and paints // create snapshot layer, which is added to layered pane and paints
// snapshot with animated alpha // snapshot with animated alpha
JComponent snapshotLayer = new JComponent() { SnapshotLayer snapshotLayer = new SnapshotLayer();
@Override
public void paint( Graphics g ) {
if( inShowSnapshot || snapshot.contentsLost() )
return;
if( useAlpha )
((Graphics2D)g).setComposite( AlphaComposite.getInstance( AlphaComposite.SRC_OVER, alpha ) );
g.drawImage( snapshot, 0, 0, null );
}
@Override
public void removeNotify() {
super.removeNotify();
// release system resources used by volatile image
snapshot.flush();
}
};
if( !useAlpha )
snapshotLayer.setOpaque( true ); snapshotLayer.setOpaque( true );
snapshotLayer.setSize( layeredPane.getSize() ); snapshotLayer.setSize( layeredPane.getSize() );
snapshotLayer.oldSnapshotImage = snapshotImage;
// add image layer to layered pane snapshots.put( layeredPane, snapshotLayer );
layeredPane.add( snapshotLayer, Integer.valueOf( JLayeredPane.DRAG_LAYER + (useAlpha ? 2 : 1) ) ); } else {
map.put( layeredPane, snapshotLayer ); SnapshotLayer snapshotLayer = snapshots.get( layeredPane );
if( snapshotLayer == null ) {
snapshotImage.flush();
continue;
} }
inShowSnapshot = false; snapshotLayer.newSnapshotImage = snapshotImage;
// add snapshot layer to layered pane
layeredPane.add( snapshotLayer, Integer.valueOf( JLayeredPane.DRAG_LAYER + 1 ) );
// let FlatRootPaneUI know that animated Laf change is in progress
layeredPane.getRootPane().putClientProperty( "FlatLaf.internal.animatedLafChange", true );
}
}
} }
/** /**
@@ -141,23 +133,22 @@ public class FlatAnimatedLafChange
if( !FlatSystemProperties.getBoolean( "flatlaf.animatedLafChange", true ) ) if( !FlatSystemProperties.getBoolean( "flatlaf.animatedLafChange", true ) )
return; return;
if( oldUIsnapshots.isEmpty() ) if( snapshots.isEmpty() )
return; return;
// show snapshot of new UI // show snapshot of new UI
showSnapshot( false, newUIsnapshots ); showSnapshot( false );
// create animator // create animator
animator = new Animator( duration, fraction -> { animator = new Animator( duration, fraction -> {
if( fraction < 0.1 || fraction > 0.9 )
return; // ignore initial and last events
alpha = 1f - fraction; alpha = 1f - fraction;
// repaint snapshots // repaint snapshots
for( Map.Entry<JLayeredPane, JComponent> e : oldUIsnapshots.entrySet() ) { for( Map.Entry<JLayeredPane, SnapshotLayer> e : snapshots.entrySet() ) {
if( e.getKey().isShowing() ) if( e.getKey().isShowing() ) {
e.getValue().repaint(); SnapshotLayer snapshotLayer = e.getValue();
snapshotLayer.paintImmediately( 0, 0, snapshotLayer.getWidth(),snapshotLayer.getHeight() );
}
} }
Toolkit.getDefaultToolkit().sync(); Toolkit.getDefaultToolkit().sync();
@@ -171,18 +162,27 @@ public class FlatAnimatedLafChange
} }
private static void hideSnapshot() { private static void hideSnapshot() {
hideSnapshot( oldUIsnapshots );
hideSnapshot( newUIsnapshots );
}
private static void hideSnapshot( Map<JLayeredPane, JComponent> map ) {
// remove snapshots // remove snapshots
for( Map.Entry<JLayeredPane, JComponent> e : map.entrySet() ) { for( Map.Entry<JLayeredPane, SnapshotLayer> e : snapshots.entrySet() ) {
e.getKey().remove( e.getValue() ); JLayeredPane layeredPane = e.getKey();
e.getKey().repaint(); SnapshotLayer snapshotLayer = e.getValue();
layeredPane.remove( snapshotLayer );
layeredPane.repaint();
snapshotLayer.flushSnapshotImages();
// run Runnable that FlatRootPaneUI put into client properties
JRootPane rootPane = layeredPane.getRootPane();
rootPane.putClientProperty( "FlatLaf.internal.animatedLafChange", null );
Runnable r = (Runnable) rootPane.getClientProperty( "FlatLaf.internal.animatedLafChange.runWhenFinished" );
if( r != null ) {
rootPane.putClientProperty( "FlatLaf.internal.animatedLafChange.runWhenFinished", null );
r.run();
}
} }
map.clear(); snapshots.clear();
} }
/** /**
@@ -194,4 +194,40 @@ public class FlatAnimatedLafChange
else else
hideSnapshot(); hideSnapshot();
} }
//---- class SnapshotLayer ------------------------------------------------
private static class SnapshotLayer
extends JComponent
{
VolatileImage oldSnapshotImage;
VolatileImage newSnapshotImage;
@Override
public void paint( Graphics g ) {
if( oldSnapshotImage.contentsLost() ||
newSnapshotImage == null || newSnapshotImage.contentsLost() )
return;
// draw new UI snapshot
g.drawImage( newSnapshotImage, 0, 0, null );
// draw old UI snapshot
((Graphics2D)g).setComposite( AlphaComposite.getInstance( AlphaComposite.SRC_OVER, alpha ) );
g.drawImage( oldSnapshotImage, 0, 0, null );
}
@Override
public void removeNotify() {
super.removeNotify();
flushSnapshotImages();
}
void flushSnapshotImages() {
// release system resources used by volatile image
oldSnapshotImage.flush();
if( newSnapshotImage != null )
newSnapshotImage.flush();
}
}
} }

View File

@@ -25,6 +25,7 @@ import java.awt.Image;
import java.awt.Paint; import java.awt.Paint;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.RenderingHints; import java.awt.RenderingHints;
import java.awt.geom.Dimension2D;
import java.awt.LinearGradientPaint; import java.awt.LinearGradientPaint;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.awt.image.RGBImageFilter; import java.awt.image.RGBImageFilter;
@@ -52,7 +53,7 @@ import com.formdev.flatlaf.util.MultiResolutionImageSupport;
import com.formdev.flatlaf.util.SoftCache; import com.formdev.flatlaf.util.SoftCache;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
import com.github.weisj.jsvg.SVGDocument; import com.github.weisj.jsvg.SVGDocument;
import com.github.weisj.jsvg.geometry.size.FloatSize; import com.github.weisj.jsvg.parser.LoaderContext;
import com.github.weisj.jsvg.parser.SVGLoader; import com.github.weisj.jsvg.parser.SVGLoader;
/** /**
@@ -271,7 +272,7 @@ public class FlatSVGIcon
this( null, -1, -1, 1, false, null, null ); this( null, -1, -1, 1, false, null, null );
try( InputStream in2 = in ) { try( InputStream in2 = in ) {
document = svgLoader.load( in2 ); document = svgLoader.load( in2, null, LoaderContext.createDefault() );
if( document == null ) { if( document == null ) {
loadFailed = true; loadFailed = true;
@@ -620,9 +621,9 @@ public class FlatSVGIcon
UIScale.scaleGraphics( g ); UIScale.scaleGraphics( g );
if( width > 0 || height > 0 ) { if( width > 0 || height > 0 ) {
FloatSize svgSize = document.size(); Dimension2D svgSize = document.size();
double sx = (width > 0) ? width / svgSize.width : 1; double sx = (width > 0) ? width / svgSize.getWidth() : 1;
double sy = (height > 0) ? height / svgSize.height : 1; double sy = (height > 0) ? height / svgSize.getHeight() : 1;
if( sx != 1 || sy != 1 ) if( sx != 1 || sy != 1 )
g.scale( sx, sy ); g.scale( sx, sy );
} }

View File

@@ -19,6 +19,7 @@ package com.formdev.flatlaf.extras;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Image; import java.awt.Image;
import java.awt.geom.Dimension2D;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.net.URL; import java.net.URL;
import java.util.Arrays; import java.util.Arrays;
@@ -28,7 +29,6 @@ import javax.swing.JWindow;
import com.formdev.flatlaf.util.MultiResolutionImageSupport; import com.formdev.flatlaf.util.MultiResolutionImageSupport;
import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.SystemInfo;
import com.github.weisj.jsvg.SVGDocument; import com.github.weisj.jsvg.SVGDocument;
import com.github.weisj.jsvg.geometry.size.FloatSize;
/** /**
* Utility methods for SVG. * Utility methods for SVG.
@@ -180,9 +180,9 @@ public class FlatSVGUtils
*/ */
public static BufferedImage svg2image( URL svgUrl, float scaleFactor ) { public static BufferedImage svg2image( URL svgUrl, float scaleFactor ) {
SVGDocument document = FlatSVGIcon.loadSVG( svgUrl ); SVGDocument document = FlatSVGIcon.loadSVG( svgUrl );
FloatSize size = document.size(); Dimension2D size = document.size();
int width = (int) (size.width * scaleFactor); int width = (int) (size.getWidth() * scaleFactor);
int height = (int) (size.height * scaleFactor); int height = (int) (size.getHeight() * scaleFactor);
return svg2image( document, width, height ); return svg2image( document, width, height );
} }
@@ -202,9 +202,9 @@ public class FlatSVGUtils
try { try {
FlatSVGIcon.setRenderingHints( g ); FlatSVGIcon.setRenderingHints( g );
FloatSize size = document.size(); Dimension2D size = document.size();
double sx = width / size.width; double sx = width / size.getWidth();
double sy = height / size.height; double sy = height / size.getHeight();
if( sx != 1 || sy != 1 ) if( sx != 1 || sy != 1 )
g.scale( sx, sy ); g.scale( sx, sy );

View File

@@ -102,4 +102,4 @@ build script:
Otherwise, download `flatlaf-fonts-inter-<version>.jar` here: Otherwise, download `flatlaf-fonts-inter-<version>.jar` here:
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf-fonts-inter/badge.svg?style=flat-square&color=007ec6)](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf-fonts-inter) [![Maven Central](https://img.shields.io/maven-central/v/com.formdev/flatlaf-fonts-inter?style=flat-square)](https://central.sonatype.com/artifact/com.formdev/flatlaf-fonts-inter)

View File

@@ -83,4 +83,4 @@ build script:
Otherwise, download `flatlaf-fonts-jetbrains-mono-<version>.jar` here: Otherwise, download `flatlaf-fonts-jetbrains-mono-<version>.jar` here:
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf-fonts-jetbrains-mono/badge.svg?style=flat-square&color=007ec6)](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf-fonts-jetbrains-mono) [![Maven Central](https://img.shields.io/maven-central/v/com.formdev/flatlaf-fonts-jetbrains-mono?style=flat-square)](https://central.sonatype.com/artifact/com.formdev/flatlaf-fonts-jetbrains-mono)

View File

@@ -83,4 +83,4 @@ build script:
Otherwise, download `flatlaf-fonts-roboto-mono-<version>.jar` here: Otherwise, download `flatlaf-fonts-roboto-mono-<version>.jar` here:
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf-fonts-roboto-mono/badge.svg?style=flat-square&color=007ec6)](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf-fonts-roboto-mono) [![Maven Central](https://img.shields.io/maven-central/v/com.formdev/flatlaf-fonts-roboto-mono?style=flat-square)](https://central.sonatype.com/artifact/com.formdev/flatlaf-fonts-roboto-mono)

View File

@@ -99,4 +99,4 @@ build script:
Otherwise, download `flatlaf-fonts-roboto-<version>.jar` here: Otherwise, download `flatlaf-fonts-roboto-<version>.jar` here:
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf-fonts-roboto/badge.svg?style=flat-square&color=007ec6)](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf-fonts-roboto) [![Maven Central](https://img.shields.io/maven-central/v/com.formdev/flatlaf-fonts-roboto?style=flat-square)](https://central.sonatype.com/artifact/com.formdev/flatlaf-fonts-roboto)

View File

@@ -22,7 +22,7 @@ build script:
Otherwise, download `flatlaf-intellij-themes-<version>.jar` here: Otherwise, download `flatlaf-intellij-themes-<version>.jar` here:
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf-intellij-themes/badge.svg?style=flat-square&color=007ec6)](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf-intellij-themes) [![Maven Central](https://img.shields.io/maven-central/v/com.formdev/flatlaf-intellij-themes?style=flat-square)](https://central.sonatype.com/artifact/com.formdev/flatlaf-intellij-themes)
How to use? How to use?

View File

@@ -37,10 +37,10 @@ build script:
Otherwise, download `flatlaf-jide-oss-<version>.jar` here: Otherwise, download `flatlaf-jide-oss-<version>.jar` here:
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf-jide-oss/badge.svg?style=flat-square&color=007ec6)](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf-jide-oss) [![Maven Central](https://img.shields.io/maven-central/v/com.formdev/flatlaf-jide-oss?style=flat-square)](https://central.sonatype.com/artifact/com.formdev/flatlaf-jide-oss)
JIDE Common Layer library `jide-oss-<version>.jar` (or JIDE Common Layer library `jide-oss-<version>.jar` (or
`jide-common-<version>.jar`) is also required: `jide-common-<version>.jar`) is also required:
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.formdev/jide-oss/badge.svg?style=flat-square&color=007ec6)](https://maven-badges.herokuapp.com/maven-central/com.formdev/jide-oss) [![Maven Central](https://img.shields.io/maven-central/v/com.formdev/jide-oss?style=flat-square)](https://central.sonatype.com/artifact/com.formdev/jide-oss)

View File

@@ -25,9 +25,11 @@ import java.awt.geom.Rectangle2D;
import javax.swing.ButtonModel; import javax.swing.ButtonModel;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JMenuItem; import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.SwingConstants; import javax.swing.SwingConstants;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.PopupMenuUI;
import com.formdev.flatlaf.ui.FlatUIUtils; import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
import com.jidesoft.plaf.LookAndFeelFactory; import com.jidesoft.plaf.LookAndFeelFactory;
@@ -54,6 +56,14 @@ public class FlatJideSplitButtonUI
// but it does not because FlatLaf already has added the UI class to the UI defaults // but it does not because FlatLaf already has added the UI class to the UI defaults
LookAndFeelFactory.installJideExtension(); LookAndFeelFactory.installJideExtension();
// workaround for bug in JideSplitButton, which overrides JMenu.updateUI(),
// but does not invoke super.updateUI() to update UI of JMenu.popupMenu field
if( c instanceof JideSplitButton ) {
JPopupMenu popupMenu = ((JideSplitButton)c).getPopupMenu();
if( popupMenu != null )
popupMenu.setUI( (PopupMenuUI) UIManager.getUI( popupMenu ) );
}
return new FlatJideSplitButtonUI(); return new FlatJideSplitButtonUI();
} }

View File

@@ -24,6 +24,7 @@ import java.awt.Dialog;
import java.awt.EventQueue; import java.awt.EventQueue;
import java.awt.Frame; import java.awt.Frame;
import java.awt.GraphicsConfiguration; import java.awt.GraphicsConfiguration;
import java.awt.Insets;
import java.awt.Point; import java.awt.Point;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.Window; import java.awt.Window;
@@ -42,6 +43,7 @@ import javax.swing.event.ChangeListener;
import javax.swing.event.EventListenerList; import javax.swing.event.EventListenerList;
import com.formdev.flatlaf.ui.FlatNativeWindowBorder; import com.formdev.flatlaf.ui.FlatNativeWindowBorder;
import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale;
import com.sun.jna.Native; import com.sun.jna.Native;
import com.sun.jna.Pointer; import com.sun.jna.Pointer;
import com.sun.jna.Structure; import com.sun.jna.Structure;
@@ -326,6 +328,8 @@ public class FlatWindowsNativeWindowBorder
HTMINBUTTON = 8, HTMINBUTTON = 8,
HTMAXBUTTON = 9, HTMAXBUTTON = 9,
HTTOP = 12, HTTOP = 12,
HTTOPLEFT = 13,
HTTOPRIGHT = 14,
HTCLOSE = 20; HTCLOSE = 20;
private static final int ABS_AUTOHIDE = 0x0000001; private static final int ABS_AUTOHIDE = 0x0000001;
@@ -674,6 +678,35 @@ public class FlatWindowsNativeWindowBorder
// scale-down mouse x/y because Swing coordinates/values may be scaled on a HiDPI screen // scale-down mouse x/y because Swing coordinates/values may be scaled on a HiDPI screen
Point pt = scaleDown( x, y ); Point pt = scaleDown( x, y );
int resizeBorderHeight = getResizeHandleHeight();
boolean isOnResizeBorder = (y < resizeBorderHeight) &&
(User32.INSTANCE.GetWindowLong( hwnd, GWL_STYLE ) & WS_THICKFRAME) != 0;
// limit top resize border to 4px, which seems to be standard for modern WinUI apps
if( isOnResizeBorder && pt.y > UIScale.scale( 4 ) )
isOnResizeBorder = false;
if( isOnResizeBorder ) {
Insets insets = window.getInsets();
// return HTTOPLEFT if mouse is over top resize border on left side
// - hovering mouse shows top-left resize cursor
// - left-click-and-drag resizes window
if( pt.x <= insets.left + UIScale.scale( 12 ) )
return new LRESULT( HTTOPLEFT );
// return HTTOPRIGHT if mouse is over top resize border on right side
// - hovering mouse shows top-right resize cursor
// - left-click-and-drag resizes window
if( pt.x >= window.getWidth() - insets.right - UIScale.scale( 12 ) )
return new LRESULT( HTTOPRIGHT );
// return HTTOP if mouse is over top resize border
// - hovering mouse shows vertical resize cursor
// - left-click-and-drag vertically resizes window
return new LRESULT( HTTOP );
}
// return HTSYSMENU if mouse is over application icon // return HTSYSMENU if mouse is over application icon
// - left-click on HTSYSMENU area shows system menu // - left-click on HTSYSMENU area shows system menu
// - double-left-click sends WM_CLOSE // - double-left-click sends WM_CLOSE
@@ -697,16 +730,6 @@ public class FlatWindowsNativeWindowBorder
if( contains( closeButtonBounds, pt ) ) if( contains( closeButtonBounds, pt ) )
return new LRESULT( HTCLOSE ); return new LRESULT( HTCLOSE );
int resizeBorderHeight = getResizeHandleHeight();
boolean isOnResizeBorder = (y < resizeBorderHeight) &&
(User32.INSTANCE.GetWindowLong( hwnd, GWL_STYLE ) & WS_THICKFRAME) != 0;
// return HTTOP if mouse is over top resize border
// - hovering mouse shows vertical resize cursor
// - left-click and drag vertically resizes window
if( isOnResizeBorder )
return new LRESULT( HTTOP );
boolean isOnTitleBar = (pt.y < titleBarHeight); boolean isOnTitleBar = (pt.y < titleBarHeight);
if( isOnTitleBar ) { if( isOnTitleBar ) {
// return HTCLIENT if mouse is over any Swing component in title bar // return HTCLIENT if mouse is over any Swing component in title bar

View File

@@ -38,9 +38,9 @@ build script:
Otherwise, download `flatlaf-swingx-<version>.jar` here: Otherwise, download `flatlaf-swingx-<version>.jar` here:
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf-swingx/badge.svg?style=flat-square&color=007ec6)](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf-swingx) [![Maven Central](https://img.shields.io/maven-central/v/com.formdev/flatlaf-swingx?style=flat-square)](https://central.sonatype.com/artifact/com.formdev/flatlaf-swingx)
SwingX library `swingx-all-<version>.jar` is also required: SwingX library `swingx-all-<version>.jar` is also required:
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.swinglabs.swingx/swingx-all/badge.svg?style=flat-square&color=007ec6)](https://maven-badges.herokuapp.com/maven-central/org.swinglabs.swingx/swingx-all) [![Maven Central](https://img.shields.io/maven-central/v/org.swinglabs.swingx/swingx-all?style=flat-square)](https://central.sonatype.com/artifact/org.swinglabs.swingx/swingx-all)

View File

@@ -7,6 +7,8 @@
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES"> <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="4"/> <listEntry value="4"/>
</listAttribute> </listAttribute>
<stringAttribute key="org.eclipse.debug.core.source_locator_id" value="org.eclipse.jdt.launching.sourceLocator.JavaSourceLookupDirector"/>
<stringAttribute key="org.eclipse.debug.core.source_locator_memento" value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;sourceLookupDirector&gt;&#13;&#10; &lt;sourceContainers duplicates=&quot;false&quot;&gt;&#13;&#10; &lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#13;&amp;#10;&amp;lt;javaProject name=&amp;quot;flatlaf-core&amp;quot;/&amp;gt;&amp;#13;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#13;&#10; &lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#13;&amp;#10;&amp;lt;javaProject name=&amp;quot;flatlaf-testing-modular-app&amp;quot;/&amp;gt;&amp;#13;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#13;&#10; &lt;/sourceContainers&gt;&#13;&#10;&lt;/sourceLookupDirector&gt;&#13;&#10;"/>
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_ATTR_USE_ARGFILE" value="false"/> <booleanAttribute key="org.eclipse.jdt.launching.ATTR_ATTR_USE_ARGFILE" value="false"/>
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_EXCLUDE_TEST_CODE" value="true"/> <booleanAttribute key="org.eclipse.jdt.launching.ATTR_EXCLUDE_TEST_CODE" value="true"/>
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_SHOW_CODEDETAILS_IN_EXCEPTION_MESSAGES" value="true"/> <booleanAttribute key="org.eclipse.jdt.launching.ATTR_SHOW_CODEDETAILS_IN_EXCEPTION_MESSAGES" value="true"/>
@@ -17,11 +19,11 @@
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="-Ddummy=dummy"/> <stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="-Ddummy=dummy"/>
<listAttribute key="org.eclipse.jdt.launching.MODULEPATH"> <listAttribute key="org.eclipse.jdt.launching.MODULEPATH">
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/jdk-17/&quot; path=&quot;4&quot; type=&quot;4&quot;/&gt;&#13;&#10;"/> <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/jdk-17/&quot; path=&quot;4&quot; type=&quot;4&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/flatlaf-testing-modular-app/build/libs/flatlaf-testing-modular-app-3.0-SNAPSHOT.jar&quot; path=&quot;4&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/> <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/flatlaf-testing-modular-app/run/flatlaf-testing-modular-app-999-SNAPSHOT.jar&quot; path=&quot;4&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/flatlaf-core/build/libs/flatlaf-3.0-SNAPSHOT.jar&quot; path=&quot;4&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/> <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/flatlaf-testing-modular-app/run/flatlaf-999-SNAPSHOT.jar&quot; path=&quot;4&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/flatlaf-extras/build/libs/flatlaf-extras-3.0-SNAPSHOT.jar&quot; path=&quot;4&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/> <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/flatlaf-testing-modular-app/run/flatlaf-extras-999-SNAPSHOT.jar&quot; path=&quot;4&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/flatlaf-fonts-inter/build/libs/flatlaf-fonts-inter-3.19-SNAPSHOT.jar&quot; path=&quot;4&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/> <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/flatlaf-testing-modular-app/run/flatlaf-fonts-inter-999-SNAPSHOT.jar&quot; path=&quot;4&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry externalArchive=&quot;C:/Users/charly/.gradle/caches/modules-2/files-2.1/com.formdev/svgSalamander/1.1.4/e61742cb8baaf9ecf57a9779763d1de21ca9db5a/svgSalamander-1.1.4.jar&quot; path=&quot;4&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/> <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/flatlaf-testing-modular-app/run/jsvg-999.jar&quot; path=&quot;4&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
</listAttribute> </listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.MODULE_NAME" value="flatlaf-testing-modular-app"/> <stringAttribute key="org.eclipse.jdt.launching.MODULE_NAME" value="flatlaf-testing-modular-app"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-m com.formdev.flatlaf.testing.modular.app/com.formdev.flatlaf.testing.modular.app.FlatModularAppTest"/> <stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-m com.formdev.flatlaf.testing.modular.app/com.formdev.flatlaf.testing.modular.app.FlatModularAppTest"/>

View File

@@ -30,3 +30,20 @@ flatlafModuleInfo {
dependsOn( ":flatlaf-extras:jar" ) dependsOn( ":flatlaf-extras:jar" )
dependsOn( ":flatlaf-fonts-inter:jar" ) dependsOn( ":flatlaf-fonts-inter:jar" )
} }
tasks {
register( "build-for-debugging" ) {
group = "build"
dependsOn( "build" )
doLast {
copy {
from( project.tasks["jar"].outputs )
from( configurations.runtimeClasspath )
into( "run" )
rename( "-[0-9][0-9.]*[0-9]", "-999" )
}
}
}
}

View File

@@ -38,8 +38,12 @@ public class FlatModularAppTest
SwingUtilities.invokeLater( () -> { SwingUtilities.invokeLater( () -> {
FlatInterFont.installBasic(); FlatInterFont.installBasic();
FlatLaf.registerCustomDefaultsSource( "com.formdev.flatlaf.testing.modular.app.themes" );
FlatLaf.registerCustomDefaultsSource( "com.formdev.flatlaf.testing.modular.app.themes2",
FlatModularAppTest.class.getClassLoader() );
FlatLaf.registerCustomDefaultsSource( FlatLaf.registerCustomDefaultsSource(
FlatModularAppTest.class.getResource( "/com/formdev/flatlaf/testing/modular/app/themes/" ) ); FlatModularAppTest.class.getResource( "/com/formdev/flatlaf/testing/modular/app/themes3" ) );
FlatLightLaf.setup(); FlatLightLaf.setup();
JButton button1 = new JButton( "Hello" ); JButton button1 = new JButton( "Hello" );

View File

@@ -14,7 +14,4 @@
# limitations under the License. # limitations under the License.
# #
@background = #fff
@foreground = #f00 @foreground = #f00
ButtonUI = com.formdev.flatlaf.testing.modular.app.plaf.MyButtonUI

View File

@@ -0,0 +1,17 @@
#
# Copyright 2025 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.
#
@background = #efe

View File

@@ -0,0 +1,17 @@
#
# Copyright 2025 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.
#
ButtonUI = com.formdev.flatlaf.testing.modular.app.plaf.MyButtonUI

View File

@@ -162,6 +162,11 @@ public class FlatComponents2Test
for( JTree tree : allTrees ) for( JTree tree : allTrees )
expandTree( tree ); expandTree( tree );
// drop mode
dropModeComboBox.init( DropMode.class, false );
dropModeComboBox.setSelectedItem( DropMode.ON_OR_INSERT );
dropModeChanged();
} }
private void initTableEditors( JTable table ) { private void initTableEditors( JTable table ) {
@@ -311,13 +316,6 @@ public class FlatComponents2Test
xTable1.setDragEnabled( dnd ); xTable1.setDragEnabled( dnd );
xTreeTable1.setDragEnabled( dnd ); xTreeTable1.setDragEnabled( dnd );
DropMode dropMode = dnd ? DropMode.ON_OR_INSERT : DropMode.USE_SELECTION;
list1.setDropMode( dropMode );
tree1.setDropMode( dropMode );
table1.setDropMode( dropMode );
xTable1.setDropMode( dropMode );
xTreeTable1.setDropMode( dropMode );
String key = "FlatLaf.oldTransferHandler"; String key = "FlatLaf.oldTransferHandler";
if( dnd ) { if( dnd ) {
list1.putClientProperty( key, list1.getTransferHandler() ); list1.putClientProperty( key, list1.getTransferHandler() );
@@ -341,6 +339,32 @@ public class FlatComponents2Test
} }
} }
private void dropModeChanged() {
DropMode dropMode = dropModeComboBox.getSelectedValue();
DropMode dropMode2;
switch( dropMode ) {
case INSERT_ROWS:
case INSERT_COLS:
dropMode2 = DropMode.INSERT;
break;
case ON_OR_INSERT_ROWS:
case ON_OR_INSERT_COLS:
dropMode2 = DropMode.ON_OR_INSERT;
break;
default:
dropMode2 = dropMode;
break;
}
list1.setDropMode( dropMode2 );
tree1.setDropMode( dropMode2 );
table1.setDropMode( dropMode );
xTable1.setDropMode( dropMode );
xTreeTable1.setDropMode( dropMode );
}
private void tableHeaderButtonChanged() { private void tableHeaderButtonChanged() {
tableHeaderButtonChanged( table1ScrollPane ); tableHeaderButtonChanged( table1ScrollPane );
tableHeaderButtonChanged( xTable1ScrollPane ); tableHeaderButtonChanged( xTable1ScrollPane );
@@ -618,6 +642,7 @@ public class FlatComponents2Test
super.updateUI(); super.updateUI();
EventQueue.invokeLater( () -> { EventQueue.invokeLater( () -> {
dropModeChanged();
showHorizontalLinesChanged(); showHorizontalLinesChanged();
showVerticalLinesChanged(); showVerticalLinesChanged();
intercellSpacingChanged(); intercellSpacingChanged();
@@ -673,6 +698,7 @@ public class FlatComponents2Test
leftSelectionInsetsCheckBox = new JCheckBox(); leftSelectionInsetsCheckBox = new JCheckBox();
rightSelectionInsetsCheckBox = new JCheckBox(); rightSelectionInsetsCheckBox = new JCheckBox();
dndCheckBox = new JCheckBox(); dndCheckBox = new JCheckBox();
dropModeComboBox = new FlatTestEnumComboBox<>();
listOptionsPanel = new JPanel(); listOptionsPanel = new JPanel();
JLabel listRendererLabel = new JLabel(); JLabel listRendererLabel = new JLabel();
listRendererComboBox = new JComboBox<>(); listRendererComboBox = new JComboBox<>();
@@ -963,6 +989,7 @@ public class FlatComponents2Test
"[]0" + "[]0" +
"[]0" + "[]0" +
"[]rel" + "[]rel" +
"[]" +
"[]")); "[]"));
//---- roundedSelectionCheckBox ---- //---- roundedSelectionCheckBox ----
@@ -1004,6 +1031,10 @@ public class FlatComponents2Test
dndCheckBox.setMnemonic('D'); dndCheckBox.setMnemonic('D');
dndCheckBox.addActionListener(e -> dndChanged()); dndCheckBox.addActionListener(e -> dndChanged());
generalOptionsPanel.add(dndCheckBox, "cell 0 4"); generalOptionsPanel.add(dndCheckBox, "cell 0 4");
//---- dropModeComboBox ----
dropModeComboBox.addActionListener(e -> dropModeChanged());
generalOptionsPanel.add(dropModeComboBox, "cell 0 5");
} }
add(generalOptionsPanel, "cell 0 4 4 1"); add(generalOptionsPanel, "cell 0 4 4 1");
@@ -1272,6 +1303,7 @@ public class FlatComponents2Test
private JCheckBox leftSelectionInsetsCheckBox; private JCheckBox leftSelectionInsetsCheckBox;
private JCheckBox rightSelectionInsetsCheckBox; private JCheckBox rightSelectionInsetsCheckBox;
private JCheckBox dndCheckBox; private JCheckBox dndCheckBox;
private FlatTestEnumComboBox<DropMode> dropModeComboBox;
private JPanel listOptionsPanel; private JPanel listOptionsPanel;
private JComboBox<String> listRendererComboBox; private JComboBox<String> listRendererComboBox;
private JComboBox<String> listLayoutOrientationField; private JComboBox<String> listLayoutOrientationField;

View File

@@ -299,7 +299,7 @@ new FormModel {
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "insets 8,hidemode 3" "$layoutConstraints": "insets 8,hidemode 3"
"$columnConstraints": "[left]" "$columnConstraints": "[left]"
"$rowConstraints": "[][]0[]0[]rel[]" "$rowConstraints": "[][]0[]0[]rel[][]"
} ) { } ) {
name: "generalOptionsPanel" name: "generalOptionsPanel"
"border": new javax.swing.border.TitledBorder( "General Control" ) "border": new javax.swing.border.TitledBorder( "General Control" )
@@ -379,6 +379,16 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 4" "value": "cell 0 4"
} ) } )
add( new FormComponent( "com.formdev.flatlaf.testing.FlatTestEnumComboBox" ) {
name: "dropModeComboBox"
auxiliary() {
"JavaCodeGenerator.variableLocal": false
"JavaCodeGenerator.typeParameters": "DropMode"
}
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "dropModeChanged", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 5"
} )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 4 4 1" "value": "cell 0 4 4 1"
} ) } )

View File

@@ -22,6 +22,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.StringReader; import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
@@ -177,7 +178,7 @@ class FlatCompletionProvider
try { try {
try( InputStream in = getClass().getResourceAsStream( "/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt" ) ) { try( InputStream in = getClass().getResourceAsStream( "/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt" ) ) {
if( in != null ) { if( in != null ) {
try( BufferedReader reader = new BufferedReader( new InputStreamReader( in, "UTF-8" ) ) ) { try( BufferedReader reader = new BufferedReader( new InputStreamReader( in, StandardCharsets.UTF_8 ) ) ) {
String key; String key;
while( (key = reader.readLine()) != null ) { while( (key = reader.readLine()) != null ) {
if( !isIgnored( key ) ) if( !isIgnored( key ) )

View File

@@ -23,6 +23,7 @@ import java.awt.Window;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets;
import javax.swing.Action; import javax.swing.Action;
import javax.swing.BorderFactory; import javax.swing.BorderFactory;
import javax.swing.InputMap; import javax.swing.InputMap;
@@ -211,7 +212,7 @@ class FlatThemeEditorPane
void load( File file ) throws IOException { void load( File file ) throws IOException {
this.file = file; this.file = file;
textArea.load( FileLocation.create( file ), "UTF-8" ); textArea.load( FileLocation.create( file ), StandardCharsets.UTF_8 );
} }
boolean reloadIfNecessary() { boolean reloadIfNecessary() {

View File

@@ -39,6 +39,7 @@ import java.io.OutputStreamWriter;
import java.io.Writer; import java.io.Writer;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.time.Year; import java.time.Year;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -675,7 +676,7 @@ class FlatThemeFileEditor
{ {
try( try(
FileOutputStream out = new FileOutputStream( file ); FileOutputStream out = new FileOutputStream( file );
Writer writer = new OutputStreamWriter( out, "UTF-8" ); Writer writer = new OutputStreamWriter( out, StandardCharsets.UTF_8 );
) { ) {
writer.write( content ); writer.write( content );
} }

View File

@@ -19,6 +19,9 @@ package com.formdev.flatlaf.themeeditor;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@@ -109,8 +112,11 @@ class FlatThemePropertiesBaseManager
String propertiesName = '/' + lafClass.getName().replace( '.', '/' ) + ".properties"; String propertiesName = '/' + lafClass.getName().replace( '.', '/' ) + ".properties";
try( InputStream in = lafClass.getResourceAsStream( propertiesName ) ) { try( InputStream in = lafClass.getResourceAsStream( propertiesName ) ) {
Properties properties = new Properties(); Properties properties = new Properties();
if( in != null ) if( in != null ) {
try( Reader reader = new InputStreamReader( in, StandardCharsets.UTF_8 )) {
properties.load( in ); properties.load( in );
}
}
coreThemes.put( lafClass.getSimpleName(), properties ); coreThemes.put( lafClass.getSimpleName(), properties );
} catch( IOException ex ) { } catch( IOException ex ) {
ex.printStackTrace(); ex.printStackTrace();

View File

@@ -14,7 +14,7 @@
# limitations under the License. # limitations under the License.
# #
flatlaf.releaseVersion = 3.6 flatlaf.releaseVersion = 3.6.2
flatlaf.developmentVersion = 3.7-SNAPSHOT flatlaf.developmentVersion = 3.7-SNAPSHOT
org.gradle.parallel = true org.gradle.parallel = true

View File

@@ -24,7 +24,7 @@ junit = "5.10.2"
sigtest = "org.netbeans.tools:sigtest-maven-plugin:1.7" sigtest = "org.netbeans.tools:sigtest-maven-plugin:1.7"
# flatlaf-extras # flatlaf-extras
jsvg = "com.github.weisj:jsvg:1.4.0" jsvg = "com.github.weisj:jsvg:2.0.0"
# flatlaf-jide-oss # flatlaf-jide-oss
jide-oss = "com.formdev:jide-oss:3.7.15" jide-oss = "com.formdev:jide-oss:3.7.15"
@@ -58,4 +58,5 @@ errorprone = "com.google.errorprone:error_prone_core:2.20.0"
[plugins] [plugins]
gradle-nexus-publish-plugin = { id = "io.github.gradle-nexus.publish-plugin", version = "2.0.0" }
errorprone = { id = "net.ltgt.errorprone", version = "3.1.0" } errorprone = { id = "net.ltgt.errorprone", version = "3.1.0" }

Binary file not shown.

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

6
gradlew vendored
View File

@@ -114,7 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;; NONSTOP* ) nonstop=true ;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
@@ -205,7 +205,7 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command: # Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped. # and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line. # treated as '${Hostname}' itself on the command line.
@@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \ set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \ "-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \ -classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@" "$@"
# Stop when "xargs" is not available. # Stop when "xargs" is not available.

4
gradlew.bat vendored
View File

@@ -70,11 +70,11 @@ goto fail
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar set CLASSPATH=
@rem Execute Gradle @rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell