Compare commits

...

107 Commits
3.2.1 ... 3.4

Author SHA1 Message Date
Karl Tauber
bfbd25012a release 3.4 2024-02-19 23:29:00 +01:00
Karl Tauber
063fff2ab4 gradle: fixed dependency of task ':flatlaf-core:signMavenPublication' (type 'Sign') 2024-02-19 23:28:31 +01:00
Karl Tauber
fbdc8d5b99 Merge branch 'windows-full-window-content' 2024-02-19 22:25:04 +01:00
Karl Tauber
625c0a3321 Tree: detect tree cell editor in FlatUIUtils.isCellEditor() 2024-02-19 22:12:29 +01:00
Karl Tauber
2972300112 Table: select all text in cell editor when starting editing using F2 key; can be disabled using Table.editorSelectAllOnStartEditing (issue 652)
also added missing `Table.paintOutsideAlternateRows`
2024-02-19 22:03:19 +01:00
Karl Tauber
a8e71895ee gradle: use AbstractPublishToMaven instead of PublishToMavenRepository to support publishing to local Maven repo (PublishToMavenLocal) 2024-02-19 18:47:48 +01:00
Karl Tauber
d7a76081e3 FileChooser: extended FlatFileChooserTest to support testing various locales (issue #795) 2024-02-12 12:05:01 +01:00
Karl Tauber
fd925a6718 FileChooser: extended FlatFileChooserTest to support testing all JFileChooser properties (issue #795) 2024-02-10 19:35:53 +01:00
Karl Tauber
4fc890a77c Testing: split FlatChooserTest into FlatFileChooserTest and FlatChooserTest 2024-02-10 14:16:45 +01:00
Karl Tauber
b804463b73 Window decorations:
- fixed updating of client property `FULL_WINDOW_CONTENT_BUTTONS_BOUNDS` when resizing window
- fixed title bar buttons placeholder debug painting
2024-02-06 11:30:11 +01:00
Karl Tauber
8f161b4b5a introduced FlatUIAction 2024-02-05 19:03:29 +01:00
Karl Tauber
c6338169f3 Testing: updated lafs.properties 2024-02-05 14:20:21 +01:00
Karl Tauber
6cea24ed9e GitHub Actions: updated versions of used actions 2024-02-05 11:34:58 +01:00
Karl Tauber
3d8eb9eb66 UIDefaultsKeysDump: exclude unused UI properties 2024-02-05 11:33:25 +01:00
Karl Tauber
a84aceb1ba Window decorations: improved caption hit testing to better support TabbedPane, SplitPane and ToolBar in title bar area (e.g. for fullWindowContent mode) 2024-02-04 16:30:38 +01:00
Karl Tauber
8adb7e3021 Native libraries: support Gradle cache when running in development environment (issue #800) 2024-02-02 19:02:56 +01:00
Karl Tauber
bc0d5dc9b5 Native libraries: publish flatlaf-<version>-no-natives.jar to Maven Central; this JAR is equal to flatlaf-<version>.jar, except that it does not contain the FlatLaf native libraries (issue #800) 2024-02-02 17:51:06 +01:00
Karl Tauber
1d935d6659 Window decorations: support fullWindowContent mode on Windows and Linux 2024-02-02 10:06:25 +01:00
Karl Tauber
445466acd0 Demo: removed toolbar button listeners used to test PR #779 2024-01-28 12:14:28 +01:00
Karl Tauber
30af74f806 macOS: setting window background (of undecorated window) to translucent color (alpha < 255) did not show the window translucent (issue #705) 2024-01-28 11:57:17 +01:00
Karl Tauber
16ddd100d3 RootPane: undone commits 7b248427f0 and c6d1ed91a7 because it seems to have no effect anymore
- on Windows with FlatLaf window decorations, maybe because of 7f02eb9cf0
- on Windows with standard window decorations, it seems not to work (still shows "white lines" @2.25x on Windows 10; Java 17)
- macOS and Linux do not support fractional scaling and "white lines" shown never be shown on that platforms
2024-01-27 11:19:18 +01:00
Karl Tauber
c946ec170d macOS window buttons spacing:
- uninstall when switching from FlatLaf to another Laf
- install when switching from another Laf to FlatLaf

(for PR #779)
2024-01-27 11:09:16 +01:00
Karl Tauber
ca514dd76e Merge PR #779: macOS: window title bar close/minimize/zoom buttons spacing 2024-01-25 14:06:38 +01:00
Karl Tauber
cf44a5c50b GitHub Actions: publish shapshots of PRs to Sonatype OSSRH 2024-01-25 13:26:31 +01:00
Karl Tauber
91b8c02c7f GitHub Actions: ignore changes to files that are not related to builds (e.g. readmes, etc.) 2024-01-24 19:23:47 +01:00
Karl Tauber
ca3b2b4b07 Fonts: updated Inter to v4.0 2024-01-24 15:45:43 +01:00
Karl Tauber
722dde63df Native libraries: system property flatlaf.nativeLibraryPath now supports loading native libraries named the same as on Maven central; improved log messages for loading fails (issue #797) 2024-01-24 01:02:44 +01:00
Karl Tauber
c85baf4dc6 flatlaf-natives-windows: fixed invocation of GetMonitorInfo() (issue #798)
also removed unnecessary setting `mii.fType`
2024-01-23 15:54:44 +01:00
Karl Tauber
96b7770ab2 CHANGELOG.md: added PR #794 2024-01-23 14:29:22 +01:00
rogerbj
0c00117820 Remove the dependency with JMenuBar to support, for example, the CommandMenuBar in JIDE OSS
(cherry picked from commit 4d4b90c989)
2024-01-23 13:47:12 +01:00
Karl Tauber
3465fa68b4 macOS window buttons spacing:
- renamed client property `MACOS_WINDOW_BUTTON_STYLE` to `MACOS_WINDOW_BUTTONS_SPACING`
- no longer allow value `true` for that client property
- enable using `MACOS_WINDOW_BUTTONS_SPACING` without `apple.awt.fullWindowContent`
- remove client property `FULL_WINDOW_CONTENT_BUTTONS_BOUNDS` when `apple.awt.fullWindowContent` is set to false or null
- added placeholder options `zeroInFullScreen`, `leftToRight` and `rightToLeft`
- hide close/min/max buttons during the transition from full-screen to non-full-screen to avoid that they "jump" when the nsToolbar is made visible
- fixed: full-screen listeners where added multiple times
- updated macOS native libraries
- added `FlatMacOSTest`
2024-01-22 00:31:40 +01:00
Karl Tauber
28278a75a7 macOS fullWindowContent mode:
- added title bar buttons placeholder
- added client property to root pane that contains title bar buttons bounds
- undone toolbar extensions from commit ea2447dcb7
2024-01-21 19:19:46 +01:00
Karl Tauber
f68a871dd6 macOS window button style: fixed javadoc 2024-01-12 22:49:49 +01:00
Karl Tauber
93d424cfe1 macOS native: added FlatNativeMacLibrary.windowToggleFullScreen() for easier testing 2024-01-12 22:49:49 +01:00
Karl Tauber
a1adde0888 macOS window button style: support NSWindowToolbarStyleUnified (availaible since macOS 11+; standard in macOS Finder, etc) to allow even larger space around close/minimize/zoom buttons 2024-01-12 22:48:31 +01:00
Karl Tauber
13528b49cb macOS large title bar: fixed wrong "main" JToolBar height and left inset after leaving full screen 2024-01-12 22:48:31 +01:00
Karl Tauber
f3be3f2d1c macOS large title bar: added client property FlatClientProperties.MACOS_LARGE_WINDOW_TITLE_BAR (or "FlatLaf.macOS.largeWindowTitleBar") 2024-01-12 22:48:31 +01:00
Karl Tauber
241fe855cc macOS large title bar: hide NSToolbar of window title bar when window becomes full screen 2024-01-12 22:47:33 +01:00
Karl Tauber
ea2447dcb7 macOS large title bar: main JToolBar automatically:
- uses height of macOS window title bar
- adds left insets for close/minimize/zoom buttons (except if full screen, where those buttons are hidden)
2024-01-12 22:47:33 +01:00
Karl Tauber
f40baed65e macOS large title bar: add NSToolbar to NSWindow 2024-01-12 22:45:54 +01:00
Karl Tauber
eed11d211b GitHub Actions: updated versions of used actions 2024-01-11 23:07:21 +01:00
Karl Tauber
19f27a8d56 release 3.3 2024-01-11 18:14:17 +01:00
Karl Tauber
cf3fa17666 fixed typos and grammar 2024-01-11 18:11:09 +01:00
Karl Tauber
6fdc56f2d3 Merge PR #790: Table header background is never restored after hover when the renderer background is null 2024-01-10 19:26:22 +01:00
Karl Tauber
9f17a5b26d TableHeader: restore also renderer foreground after hover 2024-01-10 19:19:01 +01:00
Karl Tauber
fa53e90847 Merge PR #788: Support for hover/pressed on SplitPane divider 2024-01-10 16:43:28 +01:00
Karl Tauber
50c630f403 Table: fixed border arc of components in complex table cell editors (issue #786) 2024-01-10 16:37:32 +01:00
Dar
5630c161ea fix: restore the renderer background after hover
The table header background is never restored after hover when the label renderer is opaque
2024-01-09 16:58:21 +01:00
Karl Tauber
7d16ff9e79 SplitPane:
- removed `SplitPaneDivider.showHover` (hover is now enabled if hover color is specified)
- added `SplitPaneDivider.pressedColor` (for pressed/dragging in continuous layout)
- fixed painting of dragging divider in non-continuous layout (was 1px too small; added grip)
- updated styling unit tests
2024-01-08 22:39:01 +01:00
Karl Tauber
a9ea9daec3 FileChooser: catch NPE in Java 21 when getting icon for .exe files that use default Windows exe icon (see https://bugs.openjdk.org/browse/JDK-8320692) 2024-01-08 20:29:20 +01:00
Karl Tauber
45bdd40dce Viewport: use method handle, instead of reflection, to get view UI faster 2024-01-08 20:12:26 +01:00
Karl Tauber
a2bca88eec removed support for JetBrains custom decorations 2024-01-08 16:22:20 +01:00
Karl Tauber
c0dd02ee13 TabbedPane: paint rounded tab area background for rounded cards (issue #717) 2024-01-08 13:22:59 +01:00
Karl Tauber
97495a6093 TabbedPane: avoid "jumping" tab area when switching L&F or when mouse wheel scrolling to last tab (regression in commit 4ad45088c4) 2023-12-31 12:56:43 +01:00
Karl Tauber
4ad45088c4 TabbedPane: fixed "endless" layouting and repainting when using nested tabbed panes (top and bottom tab placement) and RSyntaxTextArea (with enabled line-wrapping) as tab content (see https://github.com/skylot/jadx/issues/2030)
instead of using `BasicTabbedPaneUI.TabbedPaneLayout.layoutContainer()`, now layouting all components ourself and avoid double moving/resizing of some components
2023-12-30 12:56:58 +01:00
Dar
d6d1d4b1b7 fix: missing properties in the expected map 2023-12-29 15:12:06 +01:00
Dar
6e453c170f new: support for hover on splitpane divider 2023-12-29 12:38:34 +01:00
Karl Tauber
beb2deee52 TabbedPane: further reduced duplicate code in nested classes FlatTabbedPaneLayout and FlatTabbedPaneScrollLayout 2023-12-23 14:06:47 +01:00
Karl Tauber
9c69043b2c TabbedPane: reduced duplicate code in nested classes FlatTabbedPaneLayout and FlatTabbedPaneScrollLayout 2023-12-23 00:28:44 +01:00
Karl Tauber
4df34b3f9d TableHeader: no longer temporary replace header cell renderer while painting (issue https://github.com/apache/netbeans/issues/6835) 2023-12-22 14:08:49 +01:00
Karl Tauber
ee01756188 TabbedPane:
- avoid unnecessary repainting whole tabbed pane content area when layouting leading/trailing components
- avoid unnecessary repainting of selected tab on temporary changes
2023-12-21 18:59:11 +01:00
Karl Tauber
0386aaa18b Merge PR #758: TabbedPane: support rotated/vertical tabs 2023-12-21 17:41:49 +01:00
Karl Tauber
92c4230cde Merge PR #713: Scrollpane rounded border 2023-12-21 17:40:00 +01:00
Karl Tauber
26165999e0 Table: fixed background of boolean columns when using alternating row colors (issue #780) 2023-12-21 16:58:49 +01:00
Karl Tauber
38b2641078 NativeLibrary: in development environment, load native library from 'src' folder 2023-12-14 15:29:02 +01:00
Karl Tauber
4e19169312 Merge PR #772: macOS: native rounded borders for popups 2023-12-14 15:19:01 +01:00
Karl Tauber
46de81c1c9 macOS native:
- removed `FlatNativeMacLibrary.getWindowPtr()` because it is too dangerous to use `windowPtr` (which is `NSWindow*`) in Java (using invalid window pointer would crash app)
- made `getNSWindow()` 20x faster
- catch exceptions in `getNSWindow()`
- digitally signed dylibs
2023-12-14 14:39:26 +01:00
Karl Tauber
9bf4da7acf Native macOS libraries updated for PR #772
built by GitHub Actions:
https://github.com/JFormDesigner/FlatLaf/actions/runs/7151969281
2023-12-09 17:04:03 +01:00
Karl Tauber
6f32236fb7 macOS: native rounded borders for popups (issue #715) 2023-12-09 16:12:35 +01:00
Karl Tauber
c25d857e78 UI defaults dumps updated on macOS for commits 9fef2f9d05 and ce527329a6 2023-12-08 19:07:32 +01:00
Karl Tauber
8cc2925fd0 FlatPopupFactory: reordered some methods (nothing else changed) 2023-12-08 17:53:51 +01:00
Karl Tauber
2b87d3c4db Native window libraries: updated Windows DLLs for commit 7f6f366744
built by GitHub Actions:
https://github.com/JFormDesigner/FlatLaf/actions/runs/7132082543

locally signed Windows DLLs with FormDev Software code signing certificate
2023-12-07 19:02:43 +01:00
Karl Tauber
7f6f366744 flatlaf-natives-windows: support DWM attributes DWMWA_USE_IMMERSIVE_DARK_MODE, DWMWA_CAPTION_COLOR and DWMWA_TEXT_COLOR (all unused in FlatLaf core) 2023-12-07 18:53:45 +01:00
Karl Tauber
b1fdbde5cd ScrollPane: allow specifying scroll pane border arc for multi-line text components, lists, tables and trees 2023-11-25 23:29:39 +01:00
Karl Tauber
417f0f5f1c fixed broken rendering after resizing window to minimum size and then increasing size again (issue #767) 2023-11-25 19:14:39 +01:00
Karl Tauber
ec7673790c Demo: added macOS themes to control bar combobox (F5 and F6) 2023-11-14 11:42:54 +01:00
Karl Tauber
7d0bdf3b9e OptionPane: fixed styling custom panel background in JOptionPane (issue #761) 2023-11-14 10:38:28 +01:00
Karl Tauber
2ef5270095 TabbedPane: support rotated/vertical tabs (issue #633) 2023-11-04 19:55:01 +01:00
Karl Tauber
61ba011c3b Testing: introduced FlatTestEnumSelector (a toolbar with a single button group) for easier testing 2023-11-01 14:53:06 +01:00
Karl Tauber
8d8b9f3e98 macOS themes: fixed ToolBar.hoverButtonGroupArc 2023-11-01 14:52:17 +01:00
Karl Tauber
69899ec29f ToolBar: added styling properties separatorWidth and separatorColor 2023-11-01 12:55:06 +01:00
Karl Tauber
5063621c95 junit updated to 5.10.0 2023-11-01 12:07:03 +01:00
Karl Tauber
030177f739 Theme Editore: updated rsyntaxtextarea and autocomplete to latest versions 2023-10-31 16:58:32 +01:00
Karl Tauber
808f5a6381 Button and ToggleButton: selected buttons did not use explicitly set foreground color (issue #756) 2023-10-31 16:14:48 +01:00
Karl Tauber
9602b191a9 jide-oss: updated to 3.7.14 2023-10-22 17:56:36 +02:00
Karl Tauber
34bd2d781c Table: switching theme looses table grid and intercell spacing (issues #733 and #750) 2023-10-22 17:55:10 +02:00
Karl Tauber
cf364c1264 release 3.2.5 2023-10-21 23:53:46 +02:00
Karl Tauber
a997820bb6 Merge PR #754: Fixing NPE when showing a popup without an invoker on Windows 10 2023-10-21 23:52:02 +02:00
Eduwardo Horibe
b8fabd59c0 Fixing NPE when showing a popup without an invoker on Windows 10 2023-10-21 15:26:55 -03:00
Karl Tauber
97d290795e release 3.2.4 2023-10-21 18:23:17 +02:00
Karl Tauber
2a237ff5fc Popup: fixed potential NPE in (unusual) case that the popup invoker is null (only on Linux with Wayland and Java 21; regression in 3.2.3) (issue #752) 2023-10-21 18:21:59 +02:00
Karl Tauber
13a418f74e release 3.2.3 2023-10-21 17:11:32 +02:00
Karl Tauber
5c56dbfed6 Popup: popups that request focus were not shown on Linux with Wayland and Java 21 (issue #752) 2023-10-21 17:04:22 +02:00
Karl Tauber
0d2f37e1da release 3.2.2 2023-10-15 18:12:11 +02:00
Karl Tauber
0494c2161c FileChooser: avoid unnecessary logging of InaccessibleObjectException when running in Java 16 (issue #741) 2023-10-15 18:00:33 +02:00
Karl Tauber
635a620439 jsvg: updated to 1.2.0 2023-10-15 16:42:15 +02:00
Karl Tauber
0a7c76ec72 GitHub Actions:
- build using Java 21 (use toolchain because Gradle 8.4 does not support running on Java 21)
- no longer build on Java 19 and 20
2023-10-15 16:38:28 +02:00
Karl Tauber
9ad8fb38e8 update to Gradle 8.4 2023-10-15 16:34:42 +02:00
Karl Tauber
1dbe968952 TabbedPane: fixed NPE when using focusable component as tab component and switching theme (issue #745) 2023-10-15 11:43:58 +02:00
Karl Tauber
460b6492cb Button: fixed painting icon and text at wrong location when using HTML text, left/right vertical alignment and running in Java 19+ (issue #746) 2023-10-14 19:16:23 +02:00
Karl Tauber
67b0faa9ae Merge PR #738: Fix typo 2023-09-28 16:25:11 +02:00
Karl Tauber
5553425a1a CheckBox and RadioButton: fixed cut off right side when border is removed and horizontal alignment is set to right (issue #734) 2023-09-28 16:22:52 +02:00
valerakostin
8ff516e43a Fix typo 2023-09-22 11:14:50 +02:00
Karl Tauber
0c604b1023 ScrollPane: increase viewport width for rounded border to remove/reduce gap between view and vertical scrollbar 2023-08-27 18:14:03 +02:00
Karl Tauber
40418607e5 ScrollPane: fixed lost styling on ScrollPane border if using Table as view component 2023-08-13 23:23:03 +02:00
Karl Tauber
5436ea88d8 ScrollPane: improved/fixed calculation of left/right padding for rounded border 2023-08-13 17:01:56 +02:00
Karl Tauber
7bec5ec6dc ScrollPane: support rounded border 2023-08-13 14:01:09 +02:00
184 changed files with 10665 additions and 3105 deletions

View File

@@ -9,6 +9,14 @@ on:
- '*'
tags:
- '[0-9]*'
paths-ignore:
- '**.md'
- '.*'
- '**/.settings/**'
- 'flatlaf-core/svg/**'
- 'flatlaf-testing/dumps/**'
- 'flatlaf-testing/misc/**'
- 'images/**'
jobs:
build:
@@ -19,25 +27,24 @@ jobs:
# test against
# - Java 8 (minimum requirement)
# - Java LTS versions (11, 17, ...)
# - lastest Java version(s)
# - latest Java version(s)
java:
- 8
- 11 # LTS
- 17 # LTS
- 19
toolchain: [""]
include:
- java: 17
toolchain: 20 # latest
toolchain: 21 # latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: gradle/wrapper-validation-action@v1
- uses: gradle/wrapper-validation-action@v2
if: matrix.java == '8'
- name: Setup Java ${{ matrix.java }}
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java }}
distribution: temurin # Java 8, 11 and 17 are pre-installed on ubuntu-latest
@@ -51,7 +58,7 @@ jobs:
run: ./gradlew build -Dtoolchain=${{ matrix.toolchain }}
- name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
if: matrix.java == '11'
with:
name: FlatLaf-build-artifacts
@@ -71,10 +78,10 @@ jobs:
github.repository == 'JFormDesigner/FlatLaf'
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup Java 11
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
java-version: 11
distribution: temurin # pre-installed on ubuntu-latest
@@ -107,10 +114,10 @@ jobs:
github.repository == 'JFormDesigner/FlatLaf'
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup Java 11
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
java-version: 11
distribution: temurin # pre-installed on ubuntu-latest

View File

@@ -13,6 +13,8 @@ on:
- 'flatlaf-fonts/**'
- '.github/workflows/fonts.yml'
- 'gradle/wrapper/gradle-wrapper.properties'
- '!**.md'
- '!**/.settings/**'
jobs:
Fonts:
@@ -30,10 +32,10 @@ jobs:
github.repository == 'JFormDesigner/FlatLaf'
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup Java 11
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
java-version: 11
distribution: temurin # pre-installed on ubuntu-latest

View File

@@ -13,6 +13,8 @@ on:
- 'flatlaf-natives/**'
- '.github/workflows/natives.yml'
- 'gradle/wrapper/gradle-wrapper.properties'
- '!**.md'
- '!**/.settings/**'
jobs:
Natives:
@@ -20,17 +22,18 @@ jobs:
matrix:
os:
- windows
- macos
- ubuntu
runs-on: ${{ matrix.os }}-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: gradle/wrapper-validation-action@v1
- uses: gradle/wrapper-validation-action@v2
- name: Setup Java 11
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
java-version: 11
distribution: temurin
@@ -42,7 +45,7 @@ jobs:
run: ./gradlew build-natives --no-daemon
- name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: FlatLaf-natives-build-artifacts-${{ matrix.os }}
path: |

37
.github/workflows/pr-snapshots.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions
name: PR Snapshots
on:
pull_request:
paths-ignore:
- '**.md'
- '.*'
- '**/.settings/**'
- 'flatlaf-core/svg/**'
- 'flatlaf-testing/dumps/**'
- 'flatlaf-testing/misc/**'
- 'images/**'
jobs:
snapshot:
runs-on: ubuntu-latest
if: github.repository == 'JFormDesigner/FlatLaf'
steps:
- uses: actions/checkout@v4
- name: Setup Java 11
uses: actions/setup-java@v4
with:
java-version: 11
distribution: temurin # pre-installed on ubuntu-latest
cache: gradle
- name: Publish PR snapshot to oss.sonatype.org
run: >
./gradlew publish -PskipFonts -Dorg.gradle.internal.publish.checksums.insecure=true -Dorg.gradle.parallel=false
-Pgithub.event.pull_request.number=${{ github.event.pull_request.number }}
env:
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}

View File

@@ -1,6 +1,134 @@
FlatLaf Change Log
==================
## 3.4
#### New features and improvements
- FlatLaf window decorations (Windows 10/11 and Linux): Support "full window
content" mode, which allows you to extend the content into the window title
bar. (PR #801)
- macOS: Support larger window title bar close/minimize/zoom buttons spacing in
[full window content](https://www.formdev.com/flatlaf/macos/#full_window_content)
mode and introduced "buttons placeholder". (PR #779)
- Native libraries:
- System property `flatlaf.nativeLibraryPath` now supports loading native
libraries named the same as on Maven central.
- Published `flatlaf-<version>-no-natives.jar` to Maven Central. This JAR is
equal to `flatlaf-<version>.jar`, except that it does not contain the
FlatLaf native libraries. The Maven "classifier" to use this JAR is
`no-natives`. You need to distribute the FlatLaf native libraries with your
application.
See https://www.formdev.com/flatlaf/native-libraries/ for more details.
- Improved log messages for loading fails.
- Fonts: Updated **Inter** to
[v4.0](https://github.com/rsms/inter/releases/tag/v4.0).
- Table: Select all text in cell editor when starting editing using `F2` key.
(issue 652)
#### Fixed bugs
- macOS: Setting window background (of undecorated window) to translucent color
(alpha < 255) did not show the window translucent. (issue #705)
- JIDE CommandMenuBar: Fixed `ClassCastException` when JIDE command bar displays
`JideMenu` in popup. (PR #794)
## 3.3
#### New features and improvements
- macOS (10.14+): Popups (`JPopupMenu`, `JComboBox`, `JToolTip`, etc.) now use
native macOS rounded borders. (PR #772; issue #715)
- Native libraries: Added `libflatlaf-macos-arm64.dylib` and
`libflatlaf-macos-x86_64.dylib`. See also
https://www.formdev.com/flatlaf/native-libraries/.
- ScrollPane: Support rounded border. (PR #713)
- SplitPane: Support divider hover and pressed background colors. (PR #788)
- TabbedPane: Support vertical tabs. (PR #758, issue #633)
- TabbedPane: Paint rounded tab area background for rounded cards. (issue #717)
- ToolBar: Added styling properties `separatorWidth` and `separatorColor`.
#### Fixed bugs
- Button and ToggleButton: Selected buttons did not use explicitly set
foreground color. (issue 756)
- FileChooser: Catch NPE in Java 21 when getting icon for `.exe` files that use
default Windows exe icon. (see
[JDK-8320692](https://bugs.openjdk.org/browse/JDK-8320692))
- OptionPane: Fixed styling custom panel background in `JOptionPane`. (issue
#761)
- ScrollPane: Styling ScrollPane border properties did not work if view
component is a Table.
- Table:
- Switching theme looses table grid and intercell spacing. (issues #733 and
#750)
- Fixed background of `boolean` columns when using alternating row colors.
(issue #780)
- Fixed border arc of components in complex table cell editors. (issue #786)
- TableHeader:
- No longer temporary replace header cell renderer while painting. This avoids
a `StackOverflowError` in case that custom renderer does this too. (see
[NetBeans issue #6835](https://github.com/apache/netbeans/issues/6835)) This
also improves compatibility with custom table header implementations.
- Header cell renderer background/foreground colors were not restored after
hover if renderer uses `null` for background/foreground. (PR #790)
- TabbedPane:
- Avoid unnecessary repainting whole tabbed pane content area when layouting
leading/trailing components.
- Avoid unnecessary repainting of selected tab on temporary changes.
- Fixed "endless" layouting and repainting when using nested tabbed panes (top
and bottom tab placement) and RSyntaxTextArea (with enabled line-wrapping)
as tab content. (see
[jadx issue #2030](https://github.com/skylot/jadx/issues/2030))
- Fixed broken rendering after resizing window to minimum size and then
increasing size again. (issue #767)
#### Incompatibilities
- Removed support for JetBrains custom decorations, which required
[JetBrains Runtime](https://github.com/JetBrains/JetBrainsRuntime/wiki) (JBR)
8 or 11. It did not work for JBR 17. System property
`flatlaf.useJetBrainsCustomDecorations` is now ignored. **Note**: FlatLaf
window decorations continue to work with JBR.
## 3.2.5
#### Fixed bugs
- Popup: Fixed NPE if popup invoker is `null` on Windows 10. (issue #753;
regression in 3.2.1 in fix for #626)
## 3.2.4
#### Fixed bugs
- Popup: Fixed NPE if popup invoker is `null` on Linux with Wayland and Java 21.
(issue #752; regression in 3.2.3)
## 3.2.3
#### Fixed bugs
- Popup: Popups that request focus were not shown on Linux with Wayland and Java 21.
(issue #752)
## 3.2.2
#### Fixed bugs
- Button: Fixed painting icon and text at wrong location when using HTML text,
left/right vertical alignment and running in Java 19+. (issue #746)
- CheckBox and RadioButton: Fixed cut off right side when border is removed and
horizontal alignment is set to `right`. (issue #734)
- TabbedPane: Fixed NPE when using focusable component as tab component and
switching theme. (issue #745)
## 3.2.1
#### Fixed bugs
@@ -16,7 +144,7 @@ FlatLaf Change Log
#718)
- TextField: Fixed placeholder text painting, which did not respect horizontal
alignment property of `JTextField`. (issue #721)
- Popop: Fixed drop shadow if popup overlaps a heavyweight component. (Windows
- Popup: Fixed drop shadow if popup overlaps a heavyweight component. (Windows
10 only; issue #626)
@@ -142,7 +270,6 @@ FlatLaf Change Log
- Windows DLLs are now digitally signed with FormDev Software GmbH
certificate.
#### Fixed bugs
- FlatLaf window decorations:

View File

@@ -62,7 +62,7 @@ build script:
artifactId: flatlaf
version: (see button below)
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)
@@ -141,7 +141,6 @@ details and downloads.
Buzz
----
- [What others say about FlatLaf on Twitter](https://twitter.com/search?f=live&q=flatlaf)
- [FlatLaf 3.1 (and 3.0) announcement on Reddit](https://www.reddit.com/r/java/comments/12xgrsu/flatlaf_31_and_30_swing_look_and_feel/)
- [FlatLaf 1.0 announcement on Reddit](https://www.reddit.com/r/java/comments/lsbcwe/flatlaf_10_swing_look_and_feel/)
- [FlatLaf announcement on Reddit](https://www.reddit.com/r/java/comments/dl0hu3/flatlaf_flat_look_and_feel/)

View File

@@ -18,6 +18,12 @@ import net.ltgt.gradle.errorprone.errorprone
version = property( if( hasProperty( "release" ) ) "flatlaf.releaseVersion" else "flatlaf.developmentVersion" ) as String
// for PR snapshots change version to 'PR-<pr_number>-SNAPSHOT'
val pullRequestNumber = findProperty( "github.event.pull_request.number" )
if( pullRequestNumber != null )
version = "PR-${pullRequestNumber}-SNAPSHOT"
allprojects {
version = rootProject.version

View File

@@ -45,7 +45,7 @@ public class ReorderJarEntries
// 1st pass: copy .properties files
copyFiles( zipOutStream, jarFile, name -> name.endsWith( ".properties" ) );
// 2st pass: copy other files
// 2nd pass: copy other files
copyFiles( zipOutStream, jarFile, name -> !name.endsWith( ".properties" ) );
}

View File

@@ -26,7 +26,7 @@ tasks {
// depend on :flatlaf-core:compileJava because it generates the JNI headers
dependsOn( ":flatlaf-core:compileJava" )
from( project( ":flatlaf-core" ).buildDir.resolve( "generated/jni-headers" ) )
from( project( ":flatlaf-core" ).layout.buildDirectory.dir( "generated/jni-headers" ) )
into( "src/main/headers" )
include( extension.headers )
filter<org.apache.tools.ant.filters.FixCrLfFilter>(

View File

@@ -124,7 +124,7 @@ tasks.withType<Sign>().configureEach {
}
// check whether parallel build is enabled
tasks.withType<PublishToMavenRepository>().configureEach {
tasks.withType<AbstractPublishToMaven>().configureEach {
doFirst {
if( System.getProperty( "org.gradle.parallel" ) == "true" )
throw RuntimeException( "Publishing does not work correctly with enabled parallel build. Disable parallel build with VM option '-Dorg.gradle.parallel=false'." )

View File

@@ -42,7 +42,7 @@ java {
tasks {
compileJava {
// generate JNI headers
options.headerOutputDirectory.set( buildDir.resolve( "generated/jni-headers" ) )
options.headerOutputDirectory.set( layout.buildDirectory.dir( "generated/jni-headers" ) )
}
jar {
@@ -61,6 +61,27 @@ tasks {
archiveBaseName.set( "flatlaf" )
}
register<Zip>( "jarNoNatives" ) {
group = "build"
dependsOn( "jar" )
archiveBaseName.set( "flatlaf" )
archiveClassifier.set( "no-natives" )
archiveExtension.set( "jar" )
destinationDirectory = layout.buildDirectory.dir( "libs" )
from( zipTree( jar.get().archiveFile.get().asFile ) )
exclude( "com/formdev/flatlaf/natives/**" )
}
withType<AbstractPublishToMaven>().configureEach {
dependsOn( "jarNoNatives" )
}
withType<Sign>().configureEach {
dependsOn( "jarNoNatives" )
}
check {
dependsOn( "sigtestCheck" )
}
@@ -127,9 +148,13 @@ flatlafPublish {
val natives = "src/main/resources/com/formdev/flatlaf/natives"
nativeArtifacts = listOf(
NativeArtifact( "${natives}/flatlaf-windows-x86.dll", "windows-x86", "dll" ),
NativeArtifact( "${natives}/flatlaf-windows-x86_64.dll", "windows-x86_64", "dll" ),
NativeArtifact( "${natives}/flatlaf-windows-arm64.dll", "windows-arm64", "dll" ),
NativeArtifact( "${natives}/libflatlaf-linux-x86_64.so", "linux-x86_64", "so" ),
NativeArtifact( tasks.getByName( "jarNoNatives" ).outputs.files.asPath, "no-natives", "jar" ),
NativeArtifact( "${natives}/flatlaf-windows-x86.dll", "windows-x86", "dll" ),
NativeArtifact( "${natives}/flatlaf-windows-x86_64.dll", "windows-x86_64", "dll" ),
NativeArtifact( "${natives}/flatlaf-windows-arm64.dll", "windows-arm64", "dll" ),
NativeArtifact( "${natives}/libflatlaf-macos-arm64.dylib", "macos-arm64", "dylib" ),
NativeArtifact( "${natives}/libflatlaf-macos-x86_64.dylib", "macos-x86_64", "dylib" ),
NativeArtifact( "${natives}/libflatlaf-linux-x86_64.so", "linux-x86_64", "so" ),
)
}

View File

@@ -1,5 +1,5 @@
#Signature file v4.1
#Version 3.2.1
#Version 3.4
CLSS public abstract interface com.formdev.flatlaf.FlatClientProperties
fld public final static java.lang.String BUTTON_TYPE = "JButton.buttonType"
@@ -12,7 +12,13 @@ fld public final static java.lang.String BUTTON_TYPE_TOOLBAR_BUTTON = "toolBarBu
fld public final static java.lang.String COMPONENT_FOCUS_OWNER = "JComponent.focusOwner"
fld public final static java.lang.String COMPONENT_ROUND_RECT = "JComponent.roundRect"
fld public final static java.lang.String COMPONENT_TITLE_BAR_CAPTION = "JComponent.titleBarCaption"
fld public final static java.lang.String FULL_WINDOW_CONTENT = "FlatLaf.fullWindowContent"
fld public final static java.lang.String FULL_WINDOW_CONTENT_BUTTONS_BOUNDS = "FlatLaf.fullWindowContent.buttonsBounds"
fld public final static java.lang.String FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER = "FlatLaf.fullWindowContent.buttonsPlaceholder"
fld public final static java.lang.String GLASS_PANE_FULL_HEIGHT = "JRootPane.glassPaneFullHeight"
fld public final static java.lang.String MACOS_WINDOW_BUTTONS_SPACING = "FlatLaf.macOS.windowButtonsSpacing"
fld public final static java.lang.String MACOS_WINDOW_BUTTONS_SPACING_LARGE = "large"
fld public final static java.lang.String MACOS_WINDOW_BUTTONS_SPACING_MEDIUM = "medium"
fld public final static java.lang.String MENU_BAR_EMBEDDED = "JRootPane.menuBarEmbedded"
fld public final static java.lang.String MINIMUM_HEIGHT = "JComponent.minimumHeight"
fld public final static java.lang.String MINIMUM_WIDTH = "JComponent.minimumWidth"
@@ -23,6 +29,7 @@ fld public final static java.lang.String PLACEHOLDER_TEXT = "JTextField.placehol
fld public final static java.lang.String POPUP_BORDER_CORNER_RADIUS = "Popup.borderCornerRadius"
fld public final static java.lang.String POPUP_DROP_SHADOW_PAINTED = "Popup.dropShadowPainted"
fld public final static java.lang.String POPUP_FORCE_HEAVY_WEIGHT = "Popup.forceHeavyWeight"
fld public final static java.lang.String POPUP_ROUNDED_BORDER_WIDTH = "Popup.roundedBorderWidth"
fld public final static java.lang.String PROGRESS_BAR_LARGE_HEIGHT = "JProgressBar.largeHeight"
fld public final static java.lang.String PROGRESS_BAR_SQUARE = "JProgressBar.square"
fld public final static java.lang.String SCROLL_BAR_SHOW_BUTTONS = "JScrollBar.showButtons"
@@ -67,6 +74,11 @@ fld public final static java.lang.String TABBED_PANE_TAB_CLOSE_TOOLTIPTEXT = "JT
fld public final static java.lang.String TABBED_PANE_TAB_HEIGHT = "JTabbedPane.tabHeight"
fld public final static java.lang.String TABBED_PANE_TAB_ICON_PLACEMENT = "JTabbedPane.tabIconPlacement"
fld public final static java.lang.String TABBED_PANE_TAB_INSETS = "JTabbedPane.tabInsets"
fld public final static java.lang.String TABBED_PANE_TAB_ROTATION = "JTabbedPane.tabRotation"
fld public final static java.lang.String TABBED_PANE_TAB_ROTATION_AUTO = "auto"
fld public final static java.lang.String TABBED_PANE_TAB_ROTATION_LEFT = "left"
fld public final static java.lang.String TABBED_PANE_TAB_ROTATION_NONE = "none"
fld public final static java.lang.String TABBED_PANE_TAB_ROTATION_RIGHT = "right"
fld public final static java.lang.String TABBED_PANE_TAB_TYPE = "JTabbedPane.tabType"
fld public final static java.lang.String TABBED_PANE_TAB_TYPE_CARD = "card"
fld public final static java.lang.String TABBED_PANE_TAB_TYPE_UNDERLINED = "underlined"
@@ -241,7 +253,7 @@ meth public void setExtraDefaults(java.util.Map<java.lang.String,java.lang.Strin
meth public void uninitialize()
meth public void unregisterUIDefaultsGetter(java.util.function.Function<java.lang.Object,java.lang.Object>)
supr javax.swing.plaf.basic.BasicLookAndFeel
hfds DESKTOPFONTHINTS,aquaLoaded,customDefaultsSources,desktopPropertyListener,desktopPropertyName,desktopPropertyName2,extraDefaults,getUIMethod,getUIMethodInitialized,globalExtraDefaults,mnemonicHandler,oldPopupFactory,postInitialization,preferredFontFamily,preferredLightFontFamily,preferredMonospacedFontFamily,preferredSemiboldFontFamily,subMenuUsabilityHelperInstalled,systemColorGetter,uiDefaultsGetters,updateUIPending
hfds DESKTOPFONTHINTS,aquaLoaded,customDefaultsSources,desktopPropertyListener,desktopPropertyName,desktopPropertyName2,extraDefaults,globalExtraDefaults,mnemonicHandler,oldPopupFactory,postInitialization,preferredFontFamily,preferredLightFontFamily,preferredMonospacedFontFamily,preferredSemiboldFontFamily,subMenuUsabilityHelperInstalled,systemColorGetter,uiDefaultsGetters,updateUIPending
hcls ActiveFont,FlatUIDefaults,ImageIconUIResource
CLSS public abstract interface static com.formdev.flatlaf.FlatLaf$DisabledIconProvider
@@ -282,6 +294,7 @@ fld public final static java.lang.String UI_SCALE_ALLOW_SCALE_DOWN = "flatlaf.ui
fld public final static java.lang.String UI_SCALE_ENABLED = "flatlaf.uiScale.enabled"
fld public final static java.lang.String UPDATE_UI_ON_SYSTEM_FONT_CHANGE = "flatlaf.updateUIOnSystemFontChange"
fld public final static java.lang.String USE_JETBRAINS_CUSTOM_DECORATIONS = "flatlaf.useJetBrainsCustomDecorations"
anno 0 java.lang.Deprecated()
fld public final static java.lang.String USE_NATIVE_LIBRARY = "flatlaf.useNativeLibrary"
fld public final static java.lang.String USE_TEXT_Y_CORRECTION = "flatlaf.useTextYCorrection"
fld public final static java.lang.String USE_UBUNTU_FONT = "flatlaf.useUbuntuFont"

View File

@@ -33,7 +33,7 @@ public interface FlatClientProperties
//---- JButton ------------------------------------------------------------
/**
* Specifies type of a button.
* Specifies type of button.
* <p>
* <strong>Components</strong> {@link javax.swing.JButton} and {@link javax.swing.JToggleButton}<br>
* <strong>Value type</strong> {@link java.lang.String}<br>
@@ -257,19 +257,116 @@ public interface FlatClientProperties
String COMPONENT_FOCUS_OWNER = "JComponent.focusOwner";
/**
* Specifies whether a component in an embedded menu bar should behave as caption
* Specifies whether a component shown in a window title bar area should behave as caption
* (left-click allows moving window, right-click shows window system menu).
* The component does not receive mouse pressed/released/clicked/dragged events,
* The caption component does not receive mouse pressed/released/clicked/dragged events,
* but it gets mouse entered/exited/moved events.
* <p>
* Since 3.4, this client property also supports using a function that can check
* whether a given location in the component should behave as caption.
* Useful for components that do not use mouse input on whole component bounds.
*
* <pre>{@code
* myComponent.putClientProperty( "JComponent.titleBarCaption",
* (Function<Point, Boolean>) pt -> {
* // parameter pt contains mouse location (in myComponent coordinates)
* // return true if the component is not interested in mouse input at the given location
* // return false if the component wants process mouse input at the given location
* // return null if the component children should be checked
* return ...; // check here
* } );
* }</pre>
* <b>Warning</b>:
* <ul>
* <li>This function is invoked often when mouse is moved over window title bar area
* and should therefore return quickly.
* <li>This function is invoked on 'AWT-Windows' thread (not 'AWT-EventQueue' thread)
* while processing Windows messages.
* It <b>must not</b> change any component property or layout because this could cause a dead lock.
* </ul>
* <p>
* <strong>Component</strong> {@link javax.swing.JComponent}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
* <strong>Value type</strong> {@link java.lang.Boolean} or {@link java.util.function.Function}&lt;Point, Boolean&gt;
*
* @since 2.5
*/
String COMPONENT_TITLE_BAR_CAPTION = "JComponent.titleBarCaption";
//---- Panel --------------------------------------------------------------
/**
* Marks the panel as placeholder for the iconfify/maximize/close buttons
* in fullWindowContent mode. See {@link #FULL_WINDOW_CONTENT}.
* <p>
* If fullWindowContent mode is enabled, the preferred size of the panel is equal
* to the size of the iconfify/maximize/close buttons. Otherwise is is {@code 0,0}.
* <p>
* You're responsible to layout that panel at the top-left or top-right corner,
* depending on platform, where the iconfify/maximize/close buttons are located.
* <p>
* Syntax of the value string is: {@code "win|mac [horizontal|vertical] [zeroInFullScreen] [leftToRight|rightToLeft]"}.
* <p>
* The string must start with {@code "win"} (for Windows or Linux) or
* with {@code "mac"} (for macOS) and specifies the platform where the placeholder
* should be used. On macOS, you need the placeholder in the top-left corner,
* but on Windows/Linux you need it in the top-right corner. So if your application supports
* fullWindowContent mode on both platforms, you can add two placeholders to your layout
* and FlatLaf automatically uses only one of them. The other gets size {@code 0,0}.
* <p>
* Optionally, you can append following options to the value string (separated by space characters):
* <ul>
* <li>{@code "horizontal"} - preferred height is zero
* <li>{@code "vertical"} - preferred width is zero
* <li>{@code "zeroInFullScreen"} - in full-screen mode on macOS, preferred size is {@code 0,0}
* <li>{@code "leftToRight"} - in right-to-left component orientation, preferred size is {@code 0,0}
* <li>{@code "rightToLeft"} - in left-to-right component orientation, preferred size is {@code 0,0}
* </ul>
*
* Example for adding placeholder to top-left corner on macOS:
* <pre>{@code
* JPanel placeholder = new JPanel();
* placeholder.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER, "mac" );
*
* JToolBar toolBar = new JToolBar();
* // add tool bar items
*
* JPanel toolBarPanel = new JPanel( new BorderLayout() );
* toolBarPanel.add( placeholder, BorderLayout.WEST );
* toolBarPanel.add( toolBar, BorderLayout.CENTER );
*
* frame.getContentPane().add( toolBarPanel, BorderLayout.NORTH );
* }</pre>
*
* Or add placeholder as first item to the tool bar:
* <pre>{@code
* JPanel placeholder = new JPanel();
* placeholder.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER, "mac" );
*
* JToolBar toolBar = new JToolBar();
* toolBar.add( placeholder );
* // add tool bar items
*
* frame.getContentPane().add( toolBar, BorderLayout.NORTH );
* }</pre>
*
* If a tabbed pane is located at the top, you can add the placeholder
* as leading component to that tabbed pane:
* <pre>{@code
* JPanel placeholder = new JPanel();
* placeholder.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER, "mac" );
*
* tabbedPane.putClientProperty( FlatClientProperties.TABBED_PANE_LEADING_COMPONENT, placeholder );
* }</pre>
* <p>
* <strong>Component</strong> {@link javax.swing.JPanel}<br>
* <strong>Value type</strong> {@link java.lang.String}
*
* @since 3.4
*/
String FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER = "FlatLaf.fullWindowContent.buttonsPlaceholder";
//---- Popup --------------------------------------------------------------
/**
@@ -278,12 +375,13 @@ public interface FlatClientProperties
* <p>
* Note that this is not available on all platforms since it requires special support.
* Supported platforms:
* <p>
* <strong>Windows 11</strong> (x86 or x86_64): Only two corner radiuses are supported
* by the OS: {@code DWMWCP_ROUND} is 8px and {@code DWMWCP_ROUNDSMALL} is 4px.
* If this value is {@code 1 - 4}, then {@code DWMWCP_ROUNDSMALL} is used.
* If it is {@code >= 5}, then {@code DWMWCP_ROUND} is used.
* <p>
* <ul>
* <li><strong>Windows 11</strong>: Only two corner radiuses are supported
* by the OS: {@code DWMWCP_ROUND} is 8px and {@code DWMWCP_ROUNDSMALL} is 4px.
* If this value is {@code 1 - 4}, then {@code DWMWCP_ROUNDSMALL} is used.
* If it is {@code >= 5}, then {@code DWMWCP_ROUND} is used.
* <li><strong>macOS</strong> (10.14 and later): Any corner radius is supported.
* </ul>
* <strong>Component</strong> {@link javax.swing.JComponent}<br>
* <strong>Value type</strong> {@link java.lang.Integer}<br>
*
@@ -291,6 +389,24 @@ public interface FlatClientProperties
*/
String POPUP_BORDER_CORNER_RADIUS = "Popup.borderCornerRadius";
/**
* Specifies the popup rounded border width if the component is shown in a popup
* or if the component is the owner of another component that is shown in a popup.
* <p>
* Only used if popup uses rounded border.
* <p>
* Note that this is not available on all platforms since it requires special support.
* Supported platforms:
* <ul>
* <li><strong>macOS</strong> (10.14 and later)
* </ul>
* <strong>Component</strong> {@link javax.swing.JComponent}<br>
* <strong>Value type</strong> {@link java.lang.Integer} or {@link java.lang.Float}<br>
*
* @since 3.3
*/
String POPUP_ROUNDED_BORDER_WIDTH = "Popup.roundedBorderWidth";
/**
* Specifies whether a drop shadow is painted if the component is shown in a popup
* or if the component is the owner of another component that is shown in a popup.
@@ -369,6 +485,46 @@ public interface FlatClientProperties
*/
String MENU_BAR_EMBEDDED = "JRootPane.menuBarEmbedded";
/**
* Specifies whether the content pane (and the glass pane) should be extended
* into the window title bar area
* (requires enabled window decorations). Default is {@code false}.
* <p>
* On macOS, use client property {@code apple.awt.fullWindowContent}
* (see <a href="https://www.formdev.com/flatlaf/macos/#full_window_content">macOS Full window content</a>).
* <p>
* Setting this enables/disables full window content
* for the {@code JFrame} or {@code JDialog} that contains the root pane.
* <p>
* If {@code true}, the content pane (and the glass pane) is extended into
* the title bar area. The window icon and title are hidden.
* Only the iconfify/maximize/close buttons stay visible in the upper right corner
* (and overlap the content pane).
* <p>
* The user can left-click-and-drag on the title bar area to move the window,
* except when clicking on a component that processes mouse events (e.g. buttons or menus).
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*
* @since 3.4
*/
String FULL_WINDOW_CONTENT = "FlatLaf.fullWindowContent";
/**
* Contains the current bounds of the iconfify/maximize/close buttons
* (in root pane coordinates) if fullWindowContent mode is enabled.
* Otherwise its value is {@code null}.
* <p>
* <b>Note</b>: Do not set this client property. It is set by FlatLaf.
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.awt.Rectangle}
*
* @since 3.4
*/
String FULL_WINDOW_CONTENT_BUTTONS_BOUNDS = "FlatLaf.fullWindowContent.buttonsBounds";
/**
* Specifies whether the window icon should be shown in the window title bar
* (requires enabled window decorations). Default is UI property {@code TitlePane.showIcon}.
@@ -402,10 +558,10 @@ public interface FlatClientProperties
String TITLE_BAR_SHOW_TITLE = "JRootPane.titleBarShowTitle";
/**
* Specifies whether the "iconfify" button should be shown in the window title bar
* Specifies whether the "iconify" button should be shown in the window title bar
* (requires enabled window decorations). Default is {@code true}.
* <p>
* Setting this shows/hides the "iconfify" button
* Setting this shows/hides the "iconify" button
* for the {@code JFrame} that contains the root pane.
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
@@ -487,7 +643,7 @@ public interface FlatClientProperties
* On macOS, Java supports this out of the box.
* <p>
* Note that this client property must be set before the window becomes displayable.
* Otherwise an {@link IllegalComponentStateException} is thrown.
* Otherwise, an {@link IllegalComponentStateException} is thrown.
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.lang.String}<br>
@@ -932,6 +1088,59 @@ public interface FlatClientProperties
*/
String TABBED_PANE_TAB_ICON_PLACEMENT = "JTabbedPane.tabIconPlacement";
/**
* Specifies the rotation of the tabs (title, icon, etc.).
* <p>
* <strong>Component</strong> {@link javax.swing.JTabbedPane}<br>
* <strong>Value type</strong> {@link java.lang.Integer} or {@link java.lang.String}<br>
* <strong>Allowed Values</strong>
* {@link SwingConstants#LEFT},
* {@link SwingConstants#RIGHT},
* {@link #TABBED_PANE_TAB_ROTATION_NONE}, (default)
* {@link #TABBED_PANE_TAB_ROTATION_AUTO},
* {@link #TABBED_PANE_TAB_ROTATION_LEFT} or
* {@link #TABBED_PANE_TAB_ROTATION_RIGHT}
*
* @since 3.3
*/
String TABBED_PANE_TAB_ROTATION = "JTabbedPane.tabRotation";
/**
* Tabs are not rotated.
*
* @see #TABBED_PANE_TAB_ROTATION
* @since 3.3
*/
String TABBED_PANE_TAB_ROTATION_NONE = "none";
/**
* Tabs are rotated depending on tab placement.
* <p>
* For top and bottom tab placement, the tabs are not rotated.<br>
* For left tab placement, the tabs are rotated counter-clockwise.<br>
* For right tab placement, the tabs are rotated clockwise.
*
* @see #TABBED_PANE_TAB_ROTATION
* @since 3.3
*/
String TABBED_PANE_TAB_ROTATION_AUTO = "auto";
/**
* Tabs are rotated counter-clockwise.
*
* @see #TABBED_PANE_TAB_ROTATION
* @since 3.3
*/
String TABBED_PANE_TAB_ROTATION_LEFT = "left";
/**
* Tabs are rotated clockwise.
*
* @see #TABBED_PANE_TAB_ROTATION
* @since 3.3
*/
String TABBED_PANE_TAB_ROTATION_RIGHT = "right";
/**
* Specifies a component that will be placed at the leading edge of the tabs area.
* <p>
@@ -1191,6 +1400,44 @@ public interface FlatClientProperties
String TREE_PAINT_SELECTION = "JTree.paintSelection";
//---- macOS --------------------------------------------------------------
/**
* Specifies the spacing around the macOS window close/minimize/zoom buttons.
* Useful if <a href="https://www.formdev.com/flatlaf/macos/#full_window_content">full window content</a>
* is enabled.
* <p>
* (requires macOS 10.14+ for "medium" spacing and macOS 11+ for "large" spacing, requires Java 17+)
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.lang.String}<br>
* <strong>Allowed Values</strong>
* {@link #MACOS_WINDOW_BUTTONS_SPACING_MEDIUM} or
* {@link #MACOS_WINDOW_BUTTONS_SPACING_LARGE} (requires macOS 11+)
*
* @since 3.4
*/
String MACOS_WINDOW_BUTTONS_SPACING = "FlatLaf.macOS.windowButtonsSpacing";
/**
* Add medium spacing around the macOS window close/minimize/zoom buttons.
*
* @see #MACOS_WINDOW_BUTTONS_SPACING
* @since 3.4
*/
String MACOS_WINDOW_BUTTONS_SPACING_MEDIUM = "medium";
/**
* Add large spacing around the macOS window close/minimize/zoom buttons.
* <p>
* (requires macOS 11+; "medium" is used on older systems)
*
* @see #MACOS_WINDOW_BUTTONS_SPACING
* @since 3.4
*/
String MACOS_WINDOW_BUTTONS_SPACING_LARGE = "large";
//---- helper methods -----------------------------------------------------
/**

View File

@@ -71,7 +71,7 @@ class FlatInputMaps
);
}
// join ltr and rtl bindings to fix up/down/etc keys in right-to-left component orientation
// join ltr and rtl bindings to fix up/down/etc. keys in right-to-left component orientation
Object[] bindings = (Object[]) defaults.get( "PopupMenu.selectedWindowInputMapBindings" );
Object[] rtlBindings = (Object[]) defaults.get( "PopupMenu.selectedWindowInputMapBindings.RightToLeft" );
if( bindings != null && rtlBindings != null ) {

View File

@@ -30,9 +30,6 @@ import java.awt.image.ImageProducer;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
@@ -78,6 +75,7 @@ import com.formdev.flatlaf.ui.FlatNativeWindowBorder;
import com.formdev.flatlaf.ui.FlatPopupFactory;
import com.formdev.flatlaf.ui.FlatRootPaneUI;
import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.ui.JavaCompatibility2;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.FontUtils;
import com.formdev.flatlaf.util.GrayFilter;
@@ -183,17 +181,11 @@ public abstract class FlatLaf
* This depends on the operating system and on the used Java runtime.
* <p>
* This method returns {@code true} on Windows 10/11 (see exception below)
* and on Linux, {@code false} otherwise.
* and on Linux, otherwise returns {@code false}.
* <p>
* Returns also {@code false} on Windows 10/11 if
* FlatLaf native window border support is available (requires Windows 10/11).
* <p>
* Returns also {@code false} on Windows 10/11 if:
* <ul>
* <li>FlatLaf native window border support is available (requires Windows 10/11)</li>
* <li>running in
* <a href="https://confluence.jetbrains.com/display/JBR/JetBrains+Runtime">JetBrains Runtime 11 (or later)</a>
* (<a href="https://github.com/JetBrains/JetBrainsRuntime">source code on github</a>)
* and JBR supports custom window decorations
* </li>
* </ul>
* In these cases, custom decorations are enabled by the root pane.
* Usage of {@link JFrame#setDefaultLookAndFeelDecorated(boolean)} or
* {@link JDialog#setDefaultLookAndFeelDecorated(boolean)} is not necessary.
@@ -1295,8 +1287,8 @@ public abstract class FlatLaf
* @since 2.5
*/
public static Map<String, Class<?>> getStyleableInfos( JComponent c ) {
StyleableUI ui = getStyleableUI( c );
return (ui != null) ? ui.getStyleableInfos( c ) : null;
ComponentUI ui = JavaCompatibility2.getUI( c );
return (ui instanceof StyleableUI) ? ((StyleableUI)ui).getStyleableInfos( c ) : null;
}
/**
@@ -1308,41 +1300,10 @@ public abstract class FlatLaf
*/
@SuppressWarnings( "unchecked" )
public static <T> T getStyleableValue( JComponent c, String key ) {
StyleableUI ui = getStyleableUI( c );
return (ui != null) ? (T) ui.getStyleableValue( c, key ) : null;
ComponentUI ui = JavaCompatibility2.getUI( c );
return (ui instanceof StyleableUI) ? (T) ((StyleableUI)ui).getStyleableValue( c, key ) : null;
}
private static StyleableUI getStyleableUI( JComponent c ) {
if( !getUIMethodInitialized ) {
getUIMethodInitialized = true;
if( SystemInfo.isJava_9_orLater ) {
try {
// JComponent.getUI() is available since Java 9
getUIMethod = MethodHandles.lookup().findVirtual( JComponent.class, "getUI",
MethodType.methodType( ComponentUI.class ) );
} catch( Exception ex ) {
// ignore
}
}
}
try {
Object ui;
if( getUIMethod != null )
ui = getUIMethod.invoke( c );
else
ui = c.getClass().getMethod( "getUI" ).invoke( c );
return (ui instanceof StyleableUI) ? (StyleableUI) ui : null;
} catch( Throwable ex ) {
// ignore
return null;
}
}
private static boolean getUIMethodInitialized;
private static MethodHandle getUIMethod;
/**
* Returns the preferred font family to be used for (nearly) all fonts; or {@code null}.
*

View File

@@ -103,7 +103,10 @@ public interface FlatSystemProperties
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> {@code false} (since v2; was {@code true} in v1)
*
* @deprecated No longer used since FlatLaf 3.3. Retained for API compatibility.
*/
@Deprecated
String USE_JETBRAINS_CUSTOM_DECORATIONS = "flatlaf.useJetBrainsCustomDecorations";
/**
@@ -169,19 +172,33 @@ public interface FlatSystemProperties
String USE_NATIVE_LIBRARY = "flatlaf.useNativeLibrary";
/**
* Specifies a directory in which the native FlatLaf libraries have been extracted.
* Specifies a directory in which the FlatLaf native libraries are searched for.
* The path can be absolute or relative to current application working directory.
* This can be used to avoid extraction of the native libraries to the temporary directory at runtime.
* <p>
* If the value is {@code "system"}, then {@link System#loadLibrary(String)} is
* used to load the native library.
* Searches for the native library in classloader of caller
* If the value is {@code "system"} (supported since FlatLaf 2.6),
* then {@link System#loadLibrary(String)} is used to load the native library.
* This searches for the native library in classloader of caller
* (using {@link ClassLoader#findLibrary(String)}) and in paths specified
* in system properties {@code sun.boot.library.path} and {@code java.library.path}.
* (supported since FlatLaf 2.6)
* <p>
* If the native library can not loaded from the given path (or via {@link System#loadLibrary(String)}),
* If the native library can not be loaded from the given path (or via {@link System#loadLibrary(String)}),
* then the embedded native library is extracted to the temporary directory and loaded from there.
* <p>
* The file names of the native libraries must be either:
* <ul>
* <li>the same as in flatlaf.jar in package 'com/formdev/flatlaf/natives' (required for "system") or
* <li>when downloaded from Maven central then as described here:
* <a href="https://www.formdev.com/flatlaf/native-libraries/">https://www.formdev.com/flatlaf/native-libraries/</a>
* (requires FlatLaf 3.4)
* </ul>
* <p>
* <strong>Note</strong>: Since FlatLaf 3.1 it is recommended to download the
* FlatLaf native libraries from Maven central and distribute them with your
* application in the same directory as flatlaf.jar.
* Then it is <strong>not necessary</strong> to set this system property.
* See <a href="https://www.formdev.com/flatlaf/native-libraries/">https://www.formdev.com/flatlaf/native-libraries/</a>
* for details.
*
* @since 2
*/

View File

@@ -203,7 +203,7 @@ class LinuxFontPolicy
* Gets the default font for KDE from KDE configuration files.
*
* The Swing fonts are not updated when the user changes system font size
* (System Settings > Fonts > Force Font DPI). A application restart is necessary.
* (System Settings > Fonts > Force Font DPI). An application restart is necessary.
* This is the same behavior as in native KDE applications.
*
* The "display scale factor" (kdeglobals: [KScreen] > ScaleFactor) is not used

View File

@@ -172,7 +172,7 @@ debug*/
targetTopY = popupLocation.y;
targetBottomY = popupLocation.y + popupSize.height;
// install own event queue to supress mouse events when mouse is moved within safe triangle
// install own event queue to suppress mouse events when mouse is moved within safe triangle
if( subMenuEventQueue == null )
subMenuEventQueue = new SubMenuEventQueue();

View File

@@ -90,7 +90,7 @@ public class FlatTabbedPaneCloseIcon
closeSize.width, closeSize.height, closeArc, closeArc );
}
// set cross color
// set color of cross
Color fg = FlatButtonUI.buttonStateColor( c, closeForeground, null, null, closeHoverForeground, closePressedForeground );
g.setColor( FlatUIUtils.deriveColor( fg, c.getForeground() ) );

View File

@@ -57,11 +57,11 @@ public class FlatTreeOpenIcon
double arc = 1.5;
double arc2 = 0.5;
path = FlatUIUtils.createPath( false,
// bottom-left of opend part
// bottom-left of opened part
2,13.5,
// top-left of opend part
// top-left of opened part
FlatUIUtils.ROUNDED, 4.5,7.5, arc,
// top-right of opend part
// top-right of opened part
FlatUIUtils.ROUNDED, 15.5,7.5, arc2,
// bottom-right

View File

@@ -71,7 +71,7 @@ public abstract class FlatWindowAbstractIcon
protected void paintBackground( Component c, Graphics2D g ) {
Color background = FlatButtonUI.buttonStateColor( c, null, null, null, hoverBackground, pressedBackground );
if( background != null ) {
// disable antialiasing for background rectangle painting to avoid blury edges when scaled (e.g. at 125% or 175%)
// disable antialiasing for background rectangle painting to avoid blurry edges when scaled (e.g. at 125% or 175%)
Object oldHint = g.getRenderingHint( RenderingHints.KEY_ANTIALIASING );
g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF );

View File

@@ -28,7 +28,6 @@ import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JViewport;
import javax.swing.UIManager;
import javax.swing.plaf.basic.BasicBorders;
import com.formdev.flatlaf.FlatClientProperties;
@@ -195,8 +194,7 @@ public class FlatBorder
protected boolean isEnabled( Component c ) {
if( c instanceof JScrollPane ) {
// check whether view component is disabled
JViewport viewport = ((JScrollPane)c).getViewport();
Component view = (viewport != null) ? viewport.getView() : null;
Component view = FlatScrollPaneUI.getView( (JScrollPane) c );
if( view != null && !isEnabled( view ) )
return false;
}

View File

@@ -53,6 +53,8 @@ import javax.swing.plaf.ToolBarUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicButtonListener;
import javax.swing.plaf.basic.BasicButtonUI;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.text.View;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.icons.FlatHelpButtonIcon;
@@ -551,9 +553,45 @@ public class FlatButtonUI
}
}
/**
* Similar to BasicButtonUI.paint(), but does not use zero insets for HTML text,
* which is done in BasicButtonUI.layout() since Java 19.
* See https://github.com/openjdk/jdk/pull/8407
* and https://github.com/openjdk/jdk/pull/8407#issuecomment-1761583430
*/
@Override
public void paint( Graphics g, JComponent c ) {
super.paint( FlatLabelUI.createGraphicsHTMLTextYCorrection( g, c ), c );
g = FlatLabelUI.createGraphicsHTMLTextYCorrection( g, c );
AbstractButton b = (AbstractButton) c;
// layout
String clippedText = layout( b, b.getFontMetrics( b.getFont() ), b.getWidth(), b.getHeight() );
// not used in FlatLaf, but invoked for compatibility with BasicButtonUI.paint()
clearTextShiftOffset();
// not used in FlatLaf, but invoked for compatibility with BasicButtonUI.paint()
ButtonModel model = b.getModel();
if( model.isArmed() && model.isPressed() )
paintButtonPressed( g, b );
// paint icon
if( b.getIcon() != null )
paintIcon( g, b, iconR );
// paint text
if( clippedText != null && !clippedText.isEmpty() ) {
View view = (View) b.getClientProperty( BasicHTML.propertyKey );
if( view != null )
view.paint( g, textR ); // HTML text
else
paintText( g, b, textR, clippedText );
}
// not used in FlatLaf, but invoked for compatibility with BasicButtonUI.paint()
if( b.isFocusPainted() && b.hasFocus() )
paintFocus( g, b, viewR, textR, iconR );
}
@Override
@@ -683,14 +721,15 @@ public class FlatButtonUI
}
protected Color getForeground( JComponent c ) {
Color fg = c.getForeground();
boolean toolBarButton = isToolBarButton( c ) || isBorderlessButton( c );
// selected state
if( ((AbstractButton)c).isSelected() ) {
return buttonStateColor( c,
toolBarButton
? (toolbarSelectedForeground != null ? toolbarSelectedForeground : c.getForeground())
: selectedForeground,
? (toolbarSelectedForeground != null ? toolbarSelectedForeground : fg)
: (isCustomForeground( fg ) ? fg : selectedForeground),
toolBarButton
? (toolbarDisabledSelectedForeground != null ? toolbarDisabledSelectedForeground : disabledText)
: (disabledSelectedForeground != null ? disabledSelectedForeground : disabledText),
@@ -702,7 +741,7 @@ public class FlatButtonUI
// toolbar button
if( toolBarButton ) {
return buttonStateColor( c,
c.getForeground(),
fg,
disabledText,
null,
toolbarHoverForeground,
@@ -713,7 +752,7 @@ public class FlatButtonUI
return buttonStateColor( c,
getForegroundBase( c, def ),
disabledText,
isCustomForeground( c.getForeground() ) ? null : (def ? defaultFocusedForeground : focusedForeground),
isCustomForeground( fg ) ? null : (def ? defaultFocusedForeground : focusedForeground),
def ? defaultHoverForeground : hoverForeground,
def ? defaultPressedForeground : pressedForeground );
}
@@ -786,6 +825,67 @@ public class FlatButtonUI
return margin instanceof UIResource && Objects.equals( margin, defaultMargin );
}
@Override
public int getBaseline( JComponent c, int width, int height ) {
return getBaselineImpl( c, width, height );
}
/**
* Similar to BasicButtonUI.getBaseline(), but does not use zero insets for HTML text,
* which is done in BasicButtonUI.layout() since Java 19.
* See https://github.com/openjdk/jdk/pull/8407
* and https://github.com/openjdk/jdk/pull/8407#issuecomment-1761583430
*/
static int getBaselineImpl( JComponent c, int width, int height ) {
if( width < 0 || height < 0 )
throw new IllegalArgumentException();
AbstractButton b = (AbstractButton) c;
String text = b.getText();
if( text == null || text.isEmpty() )
return -1;
FontMetrics fm = b.getFontMetrics( b.getFont() );
layout( b, fm, width, height );
View view = (View) b.getClientProperty( BasicHTML.propertyKey );
if( view != null ) {
// HTML text
int baseline = BasicHTML.getHTMLBaseline( view, textR.width, textR.height );
return (baseline >= 0) ? textR.y + baseline : baseline;
} else
return textR.y + fm.getAscent();
}
/**
* Similar to BasicButtonUI.layout(), but does not use zero insets for HTML text,
* which is done in BasicButtonUI.layout() since Java 19.
* See https://github.com/openjdk/jdk/pull/8407
* and https://github.com/openjdk/jdk/pull/8407#issuecomment-1761583430
*/
private static String layout( AbstractButton b, FontMetrics fm, int width, int height ) {
// compute view rectangle
Insets insets = b.getInsets();
viewR.setBounds( insets.left, insets.top,
width - insets.left - insets.right,
height - insets.top - insets.bottom );
// reset rectangles
textR.setBounds( 0, 0, 0, 0 );
iconR.setBounds( 0, 0, 0, 0 );
String text = b.getText();
return SwingUtilities.layoutCompoundLabel( b, fm, text, b.getIcon(),
b.getVerticalAlignment(), b.getHorizontalAlignment(),
b.getVerticalTextPosition(), b.getHorizontalTextPosition(),
viewR, iconR, textR,
(text != null) ? b.getIconTextGap() : 0 );
}
private static Rectangle viewR = new Rectangle();
private static Rectangle textR = new Rectangle();
private static Rectangle iconR = new Rectangle();
//---- class FlatButtonListener -------------------------------------------
protected class FlatButtonListener

View File

@@ -926,7 +926,7 @@ public class FlatComboBoxUI
protected void configurePopup() {
super.configurePopup();
// make opaque to avoid that background shines thru border (e.g. at 150% scaling)
// make opaque to avoid that background shines through border (e.g. at 150% scaling)
setOpaque( true );
// set popup border
@@ -944,7 +944,7 @@ public class FlatComboBoxUI
if( popupBackground != null )
list.setBackground( popupBackground );
// set popup background because it may shine thru when scaled (e.g. at 150%)
// set popup background because it may shine through when scaled (e.g. at 150%)
// use non-UIResource to avoid that it is overwritten when making
// popup visible (see JPopupMenu.setInvoker()) in theme editor preview
setBackground( FlatUIUtils.nonUIResource( list.getBackground() ) );
@@ -1090,7 +1090,7 @@ public class FlatComboBoxUI
}
// using synchronized to avoid problems with code that modifies combo box
// (model, selection, etc) not on AWT thread (which should be not done)
// (model, selection, etc.) not on AWT thread (which should be not done)
synchronized void install( Component c, int focusWidth ) {
if( !(c instanceof JComponent) )
return;
@@ -1242,7 +1242,7 @@ public class FlatComboBoxUI
* Key selection manager that delegates to the default manager.
* Shows the popup if Space key is pressed and "typed characters" buffer is empty.
* If items contain spaces (e.g. "a b") it is still possible to select them
* by pressing keys a, Space and b.
* by pressing keys 'a', 'Space' and 'b'.
*/
private class FlatKeySelectionManager
implements JComboBox.KeySelectionManager, UIResource

View File

@@ -370,7 +370,11 @@ public class FlatFileChooserUI
// get system icon
if( f != null ) {
icon = getFileChooser().getFileSystemView().getSystemIcon( f );
try {
icon = getFileChooser().getFileSystemView().getSystemIcon( f );
} catch( NullPointerException ex ) {
// Java 21 may throw a NPE for exe files that use default Windows exe icon
}
if( icon != null ) {
if( icon instanceof ImageIcon )
@@ -425,7 +429,7 @@ public class FlatFileChooserUI
iconFunction = (Function<File, Icon>) UIManager.get( "FileChooser.shortcuts.iconFunction" );
FileSystemView fsv = fc.getFileSystemView();
File[] files = getChooserShortcutPanelFiles( fsv );
File[] files = JavaCompatibility2.getChooserShortcutPanelFiles( fsv );
if( filesFunction != null )
files = filesFunction.apply( files );
@@ -494,32 +498,6 @@ public class FlatFileChooserUI
return button;
}
protected File[] getChooserShortcutPanelFiles( FileSystemView fsv ) {
try {
if( SystemInfo.isJava_12_orLater ) {
Method m = fsv.getClass().getMethod( "getChooserShortcutPanelFiles" );
File[] files = (File[]) m.invoke( fsv );
// on macOS and Linux, files consists only of the user home directory
if( files.length == 1 && files[0].equals( new File( System.getProperty( "user.home" ) ) ) )
files = new File[0];
return files;
} else if( SystemInfo.isWindows ) {
Class<?> cls = Class.forName( "sun.awt.shell.ShellFolder" );
Method m = cls.getMethod( "get", String.class );
return (File[]) m.invoke( null, "fileChooserShortcutPanelFolders" );
}
} catch( IllegalAccessException ex ) {
// do not log because access may be denied via VM option '--illegal-access=deny'
} catch( Exception ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
// fallback
return new File[0];
}
protected String getDisplayName( FileSystemView fsv, File file ) {
if( displayNameFunction != null ) {
String name = displayNameFunction.apply( file );
@@ -540,29 +518,36 @@ public class FlatFileChooserUI
if( doNotUseSystemIcons() )
return new FlatFileViewDirectoryIcon();
// Java 17+ supports getting larger system icons
try {
if( SystemInfo.isJava_17_orLater ) {
Method m = fsv.getClass().getMethod( "getSystemIcon", File.class, int.class, int.class );
return (Icon) m.invoke( fsv, file, iconSize.width, iconSize.height );
} else if( iconSize.width > 16 || iconSize.height > 16 ) {
Class<?> cls = Class.forName( "sun.awt.shell.ShellFolder" );
if( cls.isInstance( file ) ) {
Method m = file.getClass().getMethod( "getIcon", boolean.class );
m.setAccessible( true );
Image image = (Image) m.invoke( file, true );
if( image != null )
return new ImageIcon( image );
// Java 17+ supports getting larger system icons
try {
if( SystemInfo.isJava_17_orLater ) {
Method m = fsv.getClass().getMethod( "getSystemIcon", File.class, int.class, int.class );
return (Icon) m.invoke( fsv, file, iconSize.width, iconSize.height );
} else if( iconSize.width > 16 || iconSize.height > 16 ) {
Class<?> cls = Class.forName( "sun.awt.shell.ShellFolder" );
if( cls.isInstance( file ) ) {
Method m = file.getClass().getMethod( "getIcon", boolean.class );
m.setAccessible( true );
Image image = (Image) m.invoke( file, true );
if( image != null )
return new ImageIcon( image );
}
}
} catch( Exception ex ) {
// do not log InaccessibleObjectException because access
// may be denied via VM option '--illegal-access=deny' (default in Java 16)
// (not catching InaccessibleObjectException here because it is new in Java 9, but FlatLaf also runs on Java 8)
if( !"java.lang.reflect.InaccessibleObjectException".equals( ex.getClass().getName() ) )
LoggingFacade.INSTANCE.logSevere( null, ex );
}
} catch( IllegalAccessException ex ) {
// do not log because access may be denied via VM option '--illegal-access=deny'
} catch( Exception ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
// get system icon in default size 16x16
return fsv.getSystemIcon( file );
// get system icon in default size 16x16
return fsv.getSystemIcon( file );
} catch( NullPointerException ex ) {
// Java 21 may throw a NPE for exe files that use default Windows exe icon
return new FlatFileViewDirectoryIcon();
}
}
protected void directoryChanged( File file ) {

View File

@@ -22,6 +22,7 @@ import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import javax.swing.JComponent;
/**
* Line border for various components.
@@ -66,6 +67,9 @@ public class FlatLineBorder
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
if( c instanceof JComponent && ((JComponent)c).getClientProperty( FlatPopupFactory.KEY_POPUP_USES_NATIVE_BORDER ) != null )
return;
Graphics2D g2 = (Graphics2D) g.create();
try {
FlatUIUtils.setRenderingHints( g2 );

View File

@@ -411,7 +411,7 @@ public class FlatListUI
int leftIndex = locationToIndex( list, new Point( r.x - 1, r.y ) );
int rightIndex = locationToIndex( list, new Point( r.x + r.width, r.y ) );
// special handling for the case that last column contains less cells than the other columns
// special handling for the case that last column contains fewer cells than the other columns
boolean ltr = list.getComponentOrientation().isLeftToRight();
if( !ltr && leftIndex >= 0 && leftIndex != row && leftIndex == locationToIndex( list, new Point( r.x - 1, r.y - 1 ) ) )
leftIndex = -1;

View File

@@ -27,7 +27,6 @@ import java.awt.event.ActionEvent;
import java.beans.PropertyChangeListener;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.BoxLayout;
import javax.swing.JComponent;
@@ -39,7 +38,6 @@ import javax.swing.MenuElement;
import javax.swing.MenuSelectionManager;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.ActionMapUIResource;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicMenuBarUI;
@@ -144,12 +142,10 @@ public class FlatMenuBarUI
protected void installKeyboardActions() {
super.installKeyboardActions();
// get shared action map, used for all menu bars
ActionMap map = SwingUtilities.getUIActionMap( menuBar );
if( map == null ) {
map = new ActionMapUIResource();
SwingUtilities.replaceUIActionMap( menuBar, map );
}
map.put( "takeFocus", new TakeFocus() );
if( map != null && !(map.get( "takeFocus" ) instanceof TakeFocusAction) )
map.put( "takeFocus", new TakeFocusAction( "takeFocus" ) );
}
/** @since 2 */
@@ -365,16 +361,20 @@ public class FlatMenuBarUI
}
}
//---- class TakeFocus ----------------------------------------------------
//---- class TakeFocusAction ----------------------------------------------
/**
* Activates the menu bar and shows mnemonics.
* On Windows, the popup of the first menu is not shown.
* On other platforms, the popup of the first menu is shown.
*/
private static class TakeFocus
extends AbstractAction
private static class TakeFocusAction
extends FlatUIAction
{
TakeFocusAction( String name ) {
super( name );
}
@Override
public void actionPerformed( ActionEvent e ) {
JMenuBar menuBar = (JMenuBar) e.getSource();

View File

@@ -17,6 +17,7 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
@@ -271,7 +272,7 @@ public class FlatMenuUI
if( !isHover() )
selectionBackground = getStyleFromMenuBarUI( ui -> ui.selectionBackground, menuBarSelectionBackground, selectionBackground );
JMenuBar menuBar = (JMenuBar) menuItem.getParent();
Container menuBar = menuItem.getParent();
JRootPane rootPane = SwingUtilities.getRootPane( menuBar );
if( rootPane != null && rootPane.getParent() instanceof Window &&
rootPane.getJMenuBar() == menuBar &&
@@ -321,12 +322,17 @@ public class FlatMenuUI
}
private <T> T getStyleFromMenuBarUI( Function<FlatMenuBarUI, T> f, T defaultValue ) {
MenuBarUI ui = ((JMenuBar)menuItem.getParent()).getUI();
if( !(ui instanceof FlatMenuBarUI) )
return defaultValue;
T value = f.apply( (FlatMenuBarUI) ui );
return (value != null) ? value : defaultValue;
Container menuItemParent = menuItem.getParent();
if( menuItemParent instanceof JMenuBar ) {
MenuBarUI ui = ((JMenuBar) menuItemParent).getUI();
if( ui instanceof FlatMenuBarUI ) {
T value = f.apply( (FlatMenuBarUI) ui );
if( value != null ) {
return value;
}
}
}
return defaultValue;
}
}
}

View File

@@ -22,6 +22,7 @@ import java.security.CodeSource;
import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.NativeLibrary;
import com.formdev.flatlaf.util.StringUtils;
import com.formdev.flatlaf.util.SystemInfo;
/**
@@ -78,9 +79,15 @@ class FlatNativeLibrary
//
// To avoid this, flatlaf.dll is not linked to jawt.dll,
// which avoids loading jawt.dll when flatlaf.dll is loaded.
// Instead flatlaf.dll dynamically loads jawt.dll when first used,
// Instead, flatlaf.dll dynamically loads jawt.dll when first used,
// which is guaranteed after AWT initialization.
} else if( SystemInfo.isMacOS_10_14_Mojave_orLater && (SystemInfo.isAARCH64 || SystemInfo.isX86_64) ) {
// macOS: requires macOS 10.14 or later (arm64 or x86_64)
classifier = SystemInfo.isAARCH64 ? "macos-arm64" : "macos-x86_64";
ext = "dylib";
} else if( SystemInfo.isLinux && SystemInfo.isX86_64 ) {
// Linux: requires x86_64
@@ -111,13 +118,32 @@ class FlatNativeLibrary
if( library.isLoaded() )
return library;
LoggingFacade.INSTANCE.logSevere( "Did not find library " + libraryName + " in java.library.path, using extracted library instead", null );
LoggingFacade.INSTANCE.logSevere( "Did not find library '" + System.mapLibraryName( libraryName )
+ "' in java.library.path '" + System.getProperty( "java.library.path" )
+ "', using extracted library instead", null );
} else {
// try standard library naming scheme
// (same as in flatlaf.jar in package 'com/formdev/flatlaf/natives')
File libraryFile = new File( libraryPath, System.mapLibraryName( libraryName ) );
if( libraryFile.exists() )
return new NativeLibrary( libraryFile, true );
LoggingFacade.INSTANCE.logSevere( "Did not find external library " + libraryFile + ", using extracted library instead", null );
// try Maven naming scheme
// (see https://www.formdev.com/flatlaf/native-libraries/)
String libraryName2 = null;
File jarFile = getJarFile();
if( jarFile != null ) {
libraryName2 = buildLibraryName( jarFile, classifier, ext );
File libraryFile2 = new File( libraryPath, libraryName2 );
if( libraryFile2.exists() )
return new NativeLibrary( libraryFile2, true );
}
LoggingFacade.INSTANCE.logSevere( "Did not find library '"
+ libraryFile.getName()
+ (libraryName2 != null ? ("' or '" + libraryName2) : "")
+ "' in '" + libraryFile.getParentFile().getAbsolutePath()
+ "', using extracted library instead", null );
}
}
@@ -145,6 +171,51 @@ class FlatNativeLibrary
* flatlaf-3.1-linux-x86_64.so
*/
private static File findLibraryBesideJar( String classifier, String ext ) {
// get location of FlatLaf jar (or fat/uber application jar)
File jarFile = getJarFile();
if( jarFile == null )
return null;
// build library file
String libraryName = buildLibraryName( jarFile, classifier, ext );
File jarDir = jarFile.getParentFile();
// check whether native library exists in same directory as jar
File libraryFile = new File( jarDir, libraryName );
if( libraryFile.isFile() )
return libraryFile;
// if jar is in "lib" directory, then also check whether native library exists
// in "../bin" directory
if( jarDir.getName().equalsIgnoreCase( "lib" ) ) {
libraryFile = new File( jarDir.getParentFile(), "bin/" + libraryName );
if( libraryFile.isFile() )
return libraryFile;
}
// special case: support Gradle cache when running in development environment
// <user-home>/.gradle/caches/modules-2/files-2.1/com.formdev/flatlaf/<version>/<hash-1>/flatlaf-<version>.jar
// <user-home>/.gradle/caches/modules-2/files-2.1/com.formdev/flatlaf/<version>/<hash-2>/flatlaf-<version>-windows-x86_64.dll
String path = jarDir.getAbsolutePath().replace( '\\', '/' );
if( path.contains( "/.gradle/caches/" ) ) {
File versionDir = jarDir.getParentFile();
if( libraryName.contains( versionDir.getName() ) ) {
File[] dirs = versionDir.listFiles();
if( dirs != null ) {
for( File dir : dirs ) {
libraryFile = new File( dir, libraryName );
if( libraryFile.isFile() )
return libraryFile;
}
}
}
}
// native library not found
return null;
}
private static File getJarFile() {
try {
// get location of FlatLaf jar
CodeSource codeSource = FlatNativeLibrary.class.getProtectionDomain().getCodeSource();
@@ -162,31 +233,23 @@ class FlatNativeLibrary
if( !jarFile.isFile() )
return null;
// build library file
String jarName = jarFile.getName();
String jarBasename = jarName.substring( 0, jarName.lastIndexOf( '.' ) );
File parent = jarFile.getParentFile();
String libraryName = jarBasename
+ (jarBasename.contains( "flatlaf" ) ? "" : "-flatlaf")
+ '-' + classifier + '.' + ext;
// check whether native library exists in same directory as jar
File libraryFile = new File( parent, libraryName );
if( libraryFile.isFile() )
return libraryFile;
// if jar is in "lib" directory, then also check whether library exists
// in "../bin" directory
if( parent.getName().equalsIgnoreCase( "lib" ) ) {
libraryFile = new File( parent.getParentFile(), "bin/" + libraryName );
if( libraryFile.isFile() )
return libraryFile;
}
return jarFile;
} catch( Exception ex ) {
LoggingFacade.INSTANCE.logSevere( ex.getMessage(), ex );
return null;
}
}
return null;
private static String buildLibraryName( File jarFile, String classifier, String ext ) {
String jarName = jarFile.getName();
String jarBasename = jarName.substring( 0, jarName.lastIndexOf( '.' ) );
// remove classifier "no-natives" (if used)
jarBasename = StringUtils.removeTrailing( jarBasename, "-no-natives" );
return jarBasename
+ (jarBasename.contains( "flatlaf" ) ? "" : "-flatlaf")
+ '-' + classifier + '.' + ext;
}
private static void loadJAWT() {

View File

@@ -35,6 +35,12 @@ import com.formdev.flatlaf.util.SystemInfo;
*/
class FlatNativeLinuxLibrary
{
/**
* Checks whether native library is loaded/available.
* <p>
* <b>Note</b>: It is required to invoke this method before invoking any other
* method of this class. Otherwise, the native library may not be loaded.
*/
static boolean isLoaded() {
return SystemInfo.isLinux && FlatNativeLibrary.isLoaded();
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2023 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import java.awt.Rectangle;
import java.awt.Window;
import com.formdev.flatlaf.util.SystemInfo;
/**
* Native methods for macOS.
* <p>
* <b>Note</b>: This is private API. Do not use!
*
* <h2>Methods that use windows as parameter</h2>
*
* For all methods that accept a {@link java.awt.Window} as parameter,
* the underlying macOS window must be already created,
* otherwise the method fails. You can use following to ensure this:
* <pre>{@code
* if( !window.isDisplayable() )
* window.addNotify();
* }</pre>
* or invoke the method after packing the window. E.g.
* <pre>{@code
* window.pack();
* }</pre>
*
* @author Karl Tauber
* @since 3.3
*/
public class FlatNativeMacLibrary
{
/**
* Checks whether native library is loaded/available.
* <p>
* <b>Note</b>: It is required to invoke this method before invoking any other
* method of this class. Otherwise, the native library may not be loaded.
*/
public static boolean isLoaded() {
return SystemInfo.isMacOS && FlatNativeLibrary.isLoaded();
}
public native static boolean setWindowRoundedBorder( Window window, float radius, float borderWidth, int borderColor );
/** @since 3.4 */
public static final int
BUTTONS_SPACING_DEFAULT = 0,
BUTTONS_SPACING_MEDIUM = 1,
BUTTONS_SPACING_LARGE = 2;
/** @since 3.4 */ public native static boolean setWindowButtonsSpacing( Window window, int buttonsSpacing );
/** @since 3.4 */ public native static Rectangle getWindowButtonsBounds( Window window );
/** @since 3.4 */ public native static boolean isWindowFullScreen( Window window );
/** @since 3.4 */ public native static boolean toggleWindowFullScreen( Window window );
}

View File

@@ -17,20 +17,27 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.beans.PropertyChangeListener;
import java.util.List;
import java.util.function.Predicate;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JRootPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.BorderUIResource;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.ui.JBRCustomDecorations.JBRWindowTopBorder;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.SystemInfo;
/**
@@ -54,27 +61,15 @@ public class FlatNativeWindowBorder
!SystemInfo.isWinPE &&
FlatSystemProperties.getBoolean( FlatSystemProperties.USE_WINDOW_DECORATIONS, true );
// check this field before using class JBRCustomDecorations to avoid unnecessary loading of that class
private static final boolean canUseJBRCustomDecorations =
canUseWindowDecorations &&
SystemInfo.isJetBrainsJVM_11_orLater &&
FlatSystemProperties.getBoolean( FlatSystemProperties.USE_JETBRAINS_CUSTOM_DECORATIONS, false );
private static Boolean supported;
private static Provider nativeProvider;
public static boolean isSupported() {
if( canUseJBRCustomDecorations )
return JBRCustomDecorations.isSupported();
initialize();
return supported;
}
static Object install( JRootPane rootPane ) {
if( canUseJBRCustomDecorations )
return JBRCustomDecorations.install( rootPane );
if( !isSupported() )
return null;
@@ -163,11 +158,6 @@ public class FlatNativeWindowBorder
}
static void uninstall( JRootPane rootPane, Object data ) {
if( canUseJBRCustomDecorations ) {
JBRCustomDecorations.uninstall( rootPane, data );
return;
}
if( !isSupported() )
return;
@@ -215,9 +205,6 @@ public class FlatNativeWindowBorder
}
public static boolean hasCustomDecoration( Window window ) {
if( canUseJBRCustomDecorations )
return JBRCustomDecorations.hasCustomDecoration( window );
if( !isSupported() )
return false;
@@ -225,11 +212,6 @@ public class FlatNativeWindowBorder
}
public static void setHasCustomDecoration( Window window, boolean hasCustomDecoration ) {
if( canUseJBRCustomDecorations ) {
JBRCustomDecorations.setHasCustomDecoration( window, hasCustomDecoration );
return;
}
if( !isSupported() )
return;
@@ -237,23 +219,18 @@ public class FlatNativeWindowBorder
}
static void setTitleBarHeightAndHitTestSpots( Window window, int titleBarHeight,
List<Rectangle> hitTestSpots, Rectangle appIconBounds, Rectangle minimizeButtonBounds,
Predicate<Point> captionHitTestCallback, Rectangle appIconBounds, Rectangle minimizeButtonBounds,
Rectangle maximizeButtonBounds, Rectangle closeButtonBounds )
{
if( canUseJBRCustomDecorations ) {
JBRCustomDecorations.setTitleBarHeightAndHitTestSpots( window, titleBarHeight, hitTestSpots );
return;
}
if( !isSupported() )
return;
nativeProvider.updateTitleBarInfo( window, titleBarHeight, hitTestSpots,
nativeProvider.updateTitleBarInfo( window, titleBarHeight, captionHitTestCallback,
appIconBounds, minimizeButtonBounds, maximizeButtonBounds, closeButtonBounds );
}
static boolean showWindow( Window window, int cmd ) {
if( canUseJBRCustomDecorations || !isSupported() )
if( !isSupported() )
return false;
return nativeProvider.showWindow( window, cmd );
@@ -294,7 +271,7 @@ public class FlatNativeWindowBorder
{
boolean hasCustomDecoration( Window window );
void setHasCustomDecoration( Window window, boolean hasCustomDecoration );
void updateTitleBarInfo( Window window, int titleBarHeight, List<Rectangle> hitTestSpots,
void updateTitleBarInfo( Window window, int titleBarHeight, Predicate<Point> captionHitTestCallback,
Rectangle appIconBounds, Rectangle minimizeButtonBounds, Rectangle maximizeButtonBounds,
Rectangle closeButtonBounds );
@@ -320,20 +297,36 @@ public class FlatNativeWindowBorder
* No longer needed since Windows 11.
*/
static class WindowTopBorder
extends JBRCustomDecorations.JBRWindowTopBorder
extends BorderUIResource.EmptyBorderUIResource
{
private static WindowTopBorder instance;
static JBRWindowTopBorder getInstance() {
if( canUseJBRCustomDecorations )
return JBRWindowTopBorder.getInstance();
private final Color activeLightColor = new Color( 0x707070 );
private final Color activeDarkColor = new Color( 0x2D2E2F );
private final Color inactiveLightColor = new Color( 0xaaaaaa );
private final Color inactiveDarkColor = new Color( 0x494A4B );
private boolean colorizationAffectsBorders;
private Color activeColor;
static WindowTopBorder getInstance() {
if( instance == null )
instance = new WindowTopBorder();
return instance;
}
@Override
WindowTopBorder() {
super( 1, 0, 0, 0 );
update();
installListeners();
}
void update() {
colorizationAffectsBorders = isColorizationColorAffectsBorders();
activeColor = calculateActiveBorderColor();
}
void installListeners() {
nativeProvider.addChangeListener( e -> {
update();
@@ -346,19 +339,69 @@ public class FlatNativeWindowBorder
} );
}
@Override
boolean isColorizationColorAffectsBorders() {
return nativeProvider.isColorizationColorAffectsBorders();
}
@Override
Color getColorizationColor() {
return nativeProvider.getColorizationColor();
}
@Override
int getColorizationColorBalance() {
return nativeProvider.getColorizationColorBalance();
}
private Color calculateActiveBorderColor() {
if( !colorizationAffectsBorders )
return null;
Color colorizationColor = getColorizationColor();
if( colorizationColor != null ) {
int colorizationColorBalance = getColorizationColorBalance();
if( colorizationColorBalance < 0 || colorizationColorBalance > 100 )
colorizationColorBalance = 100;
if( colorizationColorBalance == 0 )
return new Color( 0xD9D9D9 );
if( colorizationColorBalance == 100 )
return colorizationColor;
float alpha = colorizationColorBalance / 100.0f;
float remainder = 1 - alpha;
int r = Math.round( colorizationColor.getRed() * alpha + 0xD9 * remainder );
int g = Math.round( colorizationColor.getGreen() * alpha + 0xD9 * remainder );
int b = Math.round( colorizationColor.getBlue() * alpha + 0xD9 * remainder );
// avoid potential IllegalArgumentException in Color constructor
r = Math.min( Math.max( r, 0 ), 255 );
g = Math.min( Math.max( g, 0 ), 255 );
b = Math.min( Math.max( b, 0 ), 255 );
return new Color( r, g, b );
}
Color activeBorderColor = (Color) Toolkit.getDefaultToolkit().getDesktopProperty( "win.frame.activeBorderColor" );
return (activeBorderColor != null) ? activeBorderColor : UIManager.getColor( "MenuBar.borderColor" );
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
Window window = SwingUtilities.windowForComponent( c );
boolean active = window != null && window.isActive();
boolean dark = FlatLaf.isLafDark();
g.setColor( active
? (activeColor != null ? activeColor : (dark ? activeDarkColor : activeLightColor))
: (dark ? inactiveDarkColor : inactiveLightColor) );
HiDPIUtils.paintAtScale1x( (Graphics2D) g, x, y, width, height, this::paintImpl );
}
private void paintImpl( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) {
g.fillRect( x, y, width, 1 );
}
void repaintBorder( Component c ) {
c.repaint( 0, 0, c.getWidth(), 1 );
}
}
}

View File

@@ -16,6 +16,7 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Window;
import com.formdev.flatlaf.util.SystemInfo;
@@ -31,6 +32,12 @@ public class FlatNativeWindowsLibrary
{
private static long osBuildNumber = Long.MIN_VALUE;
/**
* Checks whether native library is loaded/available.
* <p>
* <b>Note</b>: It is required to invoke this method before invoking any other
* method of this class. Otherwise, the native library may not be loaded.
*/
public static boolean isLoaded() {
return SystemInfo.isWindows && FlatNativeLibrary.isLoaded();
}
@@ -93,15 +100,60 @@ public class FlatNativeWindowsLibrary
public native static boolean setWindowCornerPreference( long hwnd, int cornerPreference );
/**
* Sets the color of the window border.
* The red/green/blue values must be in range {@code 0 - 255}.
* If red is {@code -1}, then the system default border color is used (useful to reset the border color).
* If red is {@code -2}, then no border is painted.
* <p>
* Invokes Win32 API method {@code DwmSetWindowAttribute(DWMWA_BORDER_COLOR)}.
* DWMWINDOWATTRIBUTE
* see https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
*
* @since 3.3
*/
public static final int
DWMWA_USE_IMMERSIVE_DARK_MODE = 20,
DWMWA_BORDER_COLOR = 34,
DWMWA_CAPTION_COLOR = 35,
DWMWA_TEXT_COLOR = 36;
/**
* Invokes Win32 API method {@code DwmSetWindowAttribute()} with a {@code BOOL} attribute value.
* See https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute
*
* @since 3.3
*/
public native static boolean dwmSetWindowAttributeBOOL( long hwnd, int attribute, boolean value );
/**
* Invokes Win32 API method {@code DwmSetWindowAttribute()} with a {@code DWORD} attribute value.
* See https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute
*
* @since 3.3
*/
public native static boolean dwmSetWindowAttributeDWORD( long hwnd, int attribute, int value );
/** @since 3.3 */
public static final int
// use this constant to reset any window part colors to the system default behavior
DWMWA_COLOR_DEFAULT = 0xFFFFFFFF,
// use this constant to specify that a window part should not be rendered
DWMWA_COLOR_NONE = 0xFFFFFFFE;
/** @since 3.3 */
public static final Color COLOR_NONE = new Color( 0, true );
/**
* Invokes Win32 API method {@code DwmSetWindowAttribute()} with a {@code COLORREF} attribute value.
* See https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute
* <p>
* Supported since Windows 11 Build 22000.
*
* @since 3.3
*/
public native static boolean setWindowBorderColor( long hwnd, int red, int green, int blue );
public static boolean dwmSetWindowAttributeCOLORREF( long hwnd, int attribute, Color color ) {
// convert color to Windows RGB value
int rgb = (color == COLOR_NONE)
? DWMWA_COLOR_NONE
: (color != null
? (color.getRed() | (color.getGreen() << 8) | (color.getBlue() << 16))
: DWMWA_COLOR_DEFAULT);
// DwmSetWindowAttribute() expects COLORREF as attribute value, which is defined as DWORD
return dwmSetWindowAttributeDWORD( hwnd, attribute, rgb );
}
}

View File

@@ -30,9 +30,7 @@ import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.plaf.basic.BasicOptionPaneUI;
import com.formdev.flatlaf.FlatClientProperties;
@@ -115,13 +113,6 @@ public class FlatOptionPaneUI
sameSizeButtons = FlatUIUtils.getUIBoolean( "OptionPane.sameSizeButtons", true );
}
@Override
protected void installComponents() {
super.installComponents();
updateChildPanels( optionPane );
}
@Override
protected PropertyChangeListener createPropertyChangeListener() {
PropertyChangeListener superListener = super.createPropertyChangeListener();
@@ -155,6 +146,13 @@ public class FlatOptionPaneUI
protected Container createMessageArea() {
Container messageArea = super.createMessageArea();
// use non-UIResource OptionPane.messageAreaBorder to avoid that it is replaced when switching LaF
// and make panel non-opaque for OptionPane.background
updateAreaPanel( messageArea );
// make known sub-panels non-opaque for OptionPane.background
updateKnownChildPanels( messageArea );
// set icon-message gap
if( iconMessageGap > 0 ) {
Component iconMessageSeparator = SwingUtils.getComponentByName( messageArea, "OptionPane.separator" );
@@ -169,6 +167,10 @@ public class FlatOptionPaneUI
protected Container createButtonArea() {
Container buttonArea = super.createButtonArea();
// use non-UIResource OptionPane.buttonAreaBorder to avoid that it is replaced when switching LaF
// and make panel non-opaque for OptionPane.background
updateAreaPanel( buttonArea );
// scale button padding and subtract focusWidth
if( buttonArea.getLayout() instanceof ButtonAreaLayout ) {
ButtonAreaLayout layout = (ButtonAreaLayout) buttonArea.getLayout();
@@ -218,22 +220,33 @@ public class FlatOptionPaneUI
super.addMessageComponents( container, cons, msg, maxll, internallyCreated );
}
private void updateChildPanels( Container c ) {
private void updateAreaPanel( Container area ) {
if( !(area instanceof JPanel) )
return;
// use non-UIResource border to avoid that it is replaced when switching LaF
// and make panel non-opaque for OptionPane.background
JPanel panel = (JPanel) area;
panel.setBorder( FlatUIUtils.nonUIResource( panel.getBorder() ) );
panel.setOpaque( false );
}
private void updateKnownChildPanels( Container c ) {
for( Component child : c.getComponents() ) {
if( child.getClass() == JPanel.class ) {
JPanel panel = (JPanel)child;
// make sub-panel non-opaque for OptionPane.background
panel.setOpaque( false );
// use non-UIResource borders to avoid that they are replaced when switching LaF
Border border = panel.getBorder();
if( border instanceof UIResource )
panel.setBorder( FlatUIUtils.nonUIResource( border ) );
if( child instanceof JPanel && child.getName() != null ) {
switch( child.getName() ) {
case "OptionPane.realBody":
case "OptionPane.body":
case "OptionPane.separator":
case "OptionPane.break":
// make known sub-panels non-opaque for OptionPane.background
((JPanel)child).setOpaque( false );
break;
}
}
if( child instanceof Container )
updateChildPanels( (Container) child );
updateKnownChildPanels( (Container) child );
}
}

View File

@@ -16,6 +16,7 @@
package com.formdev.flatlaf.ui;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.beans.PropertyChangeEvent;
@@ -23,6 +24,7 @@ import java.beans.PropertyChangeListener;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.LookAndFeel;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicPanelUI;
import com.formdev.flatlaf.FlatClientProperties;
@@ -69,6 +71,8 @@ public class FlatPanelUI
super.installUI( c );
c.addPropertyChangeListener( this );
if( c.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER ) != null )
FullWindowContentSupport.registerPlaceholder( c );
installStyle( (JPanel) c );
}
@@ -78,10 +82,20 @@ public class FlatPanelUI
super.uninstallUI( c );
c.removePropertyChangeListener( this );
if( c.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER ) != null )
FullWindowContentSupport.unregisterPlaceholder( c );
oldStyleValues = null;
}
@Override
protected void installDefaults( JPanel p ) {
super.installDefaults( p );
if( p.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER ) != null )
LookAndFeel.installProperty( p, "opaque", false );
}
/** @since 2.0.1 */
@Override
public void propertyChange( PropertyChangeEvent e ) {
@@ -98,6 +112,17 @@ public class FlatPanelUI
c.revalidate();
c.repaint();
break;
case FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER:
JPanel p = (JPanel) e.getSource();
if( e.getOldValue() != null )
FullWindowContentSupport.unregisterPlaceholder( p );
if( e.getNewValue() != null )
FullWindowContentSupport.registerPlaceholder( p );
// make panel non-opaque for placeholders
LookAndFeel.installProperty( p, "opaque", e.getNewValue() == null );
break;
}
}
@@ -162,4 +187,19 @@ public class FlatPanelUI
paint( g, c );
}
@Override
public Dimension getPreferredSize( JComponent c ) {
Object value = c.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER );
if( value != null )
return FullWindowContentSupport.getPlaceholderPreferredSize( c, (String) value );
return super.getPreferredSize( c );
}
@Override
public void paint( Graphics g, JComponent c ) {
if( c.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER ) != null )
FullWindowContentSupport.debugPaint( g, c );
}
}

View File

@@ -36,6 +36,7 @@ import java.awt.Window;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
import java.awt.event.WindowFocusListener;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
@@ -70,6 +71,8 @@ import com.formdev.flatlaf.util.UIScale;
public class FlatPopupFactory
extends PopupFactory
{
static final String KEY_POPUP_USES_NATIVE_BORDER = "FlatLaf.internal.FlatPopupFactory.popupUsesNativeBorder";
private MethodHandle java8getPopupMethod;
private MethodHandle java9getPopupMethod;
@@ -83,23 +86,28 @@ public class FlatPopupFactory
y = pt.y;
}
fixLinuxWaylandJava21focusIssue( owner );
boolean forceHeavyWeight = isOptionEnabled( owner, contents, FlatClientProperties.POPUP_FORCE_HEAVY_WEIGHT, "Popup.forceHeavyWeight" );
if( !isOptionEnabled( owner, contents, FlatClientProperties.POPUP_DROP_SHADOW_PAINTED, "Popup.dropShadowPainted" ) || SystemInfo.isProjector || SystemInfo.isWebswing )
return new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, forceHeavyWeight ), contents );
// macOS and Linux adds drop shadow to heavy weight popups
if( SystemInfo.isMacOS || SystemInfo.isLinux )
return new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, true ), contents );
if( SystemInfo.isMacOS || SystemInfo.isLinux ) {
NonFlashingPopup popup = new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, true ), contents );
if( popup.popupWindow != null && SystemInfo.isMacOS && FlatNativeMacLibrary.isLoaded() )
setupRoundedBorder( popup.popupWindow, owner, contents );
return popup;
}
// Windows 11 with FlatLaf native library can use rounded corners and shows drop shadow for heavy weight popups
int borderCornerRadius;
if( isWindows11BorderSupported() &&
(borderCornerRadius = getBorderCornerRadius( owner, contents )) > 0 )
getBorderCornerRadius( owner, contents ) > 0 )
{
NonFlashingPopup popup = new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, true ), contents );
if( popup.popupWindow != null )
setupWindows11Border( popup.popupWindow, contents, borderCornerRadius );
setupRoundedBorder( popup.popupWindow, owner, contents );
return popup;
}
@@ -159,67 +167,6 @@ public class FlatPopupFactory
}
}
/**
* Shows the given popup and, if necessary, fixes the location of a heavy weight popup window.
* <p>
* On a dual screen setup, where screens use different scale factors, it may happen
* that the window location changes when showing a heavy weight popup window.
* E.g. when opening a dialog on the secondary screen and making combobox popup visible.
* <p>
* This is a workaround for https://bugs.openjdk.java.net/browse/JDK-8224608
*/
private static void showPopupAndFixLocation( Popup popup, Window popupWindow ) {
if( popupWindow != null ) {
// remember location of heavy weight popup window
int x = popupWindow.getX();
int y = popupWindow.getY();
popup.show();
// restore popup window location if it has changed
// (probably scaled when screens use different scale factors)
if( popupWindow.getX() != x || popupWindow.getY() != y )
popupWindow.setLocation( x, y );
} else
popup.show();
}
private boolean isOptionEnabled( Component owner, Component contents, String clientKey, String uiKey ) {
Object value = getOption( owner, contents, clientKey, uiKey );
return (value instanceof Boolean) ? (Boolean) value : false;
}
private int getBorderCornerRadius( Component owner, Component contents ) {
String uiKey =
(contents instanceof BasicComboPopup) ? "ComboBox.borderCornerRadius" :
(contents instanceof JPopupMenu) ? "PopupMenu.borderCornerRadius" :
(contents instanceof JToolTip) ? "ToolTip.borderCornerRadius" :
"Popup.borderCornerRadius";
Object value = getOption( owner, contents, FlatClientProperties.POPUP_BORDER_CORNER_RADIUS, uiKey );
return (value instanceof Integer) ? (Integer) value : 0;
}
/**
* Get option from:
* <ol>
* <li>client property {@code clientKey} of {@code owner}
* <li>client property {@code clientKey} of {@code contents}
* <li>UI property {@code uiKey}
* </ol>
*/
private Object getOption( Component owner, Component contents, String clientKey, String uiKey ) {
for( Component c : new Component[] { owner, contents } ) {
if( c instanceof JComponent ) {
Object value = ((JComponent)c).getClientProperty( clientKey );
if( value != null )
return value;
}
}
return UIManager.get( uiKey );
}
/**
* There is no API in Java 8 to force creation of heavy weight popups,
* but it is possible with reflection. Java 9 provides a new method.
@@ -255,6 +202,33 @@ public class FlatPopupFactory
}
}
private static boolean isOptionEnabled( Component owner, Component contents, String clientKey, String uiKey ) {
Object value = getOption( owner, contents, clientKey, uiKey );
return (value instanceof Boolean) ? (Boolean) value : false;
}
/**
* Get option from:
* <ol>
* <li>client property {@code clientKey} of {@code owner}
* <li>client property {@code clientKey} of {@code contents}
* <li>UI property {@code uiKey}
* </ol>
*/
private static Object getOption( Component owner, Component contents, String clientKey, String uiKey ) {
for( Component c : new Component[] { owner, contents } ) {
if( c instanceof JComponent ) {
Object value = ((JComponent)c).getClientProperty( clientKey );
if( value != null )
return value;
}
}
return UIManager.get( uiKey );
}
//---- tooltips -----------------------------------------------------------
/**
* Usually ToolTipManager places a tooltip at (mouseLocation.x, mouseLocation.y + 20).
* In case that the tooltip would be partly outside of the screen,
@@ -339,48 +313,58 @@ public class FlatPopupFactory
((JComponent)owner).getToolTipLocation( me ) != null;
}
//---- native rounded border ----------------------------------------------
private static boolean isWindows11BorderSupported() {
return SystemInfo.isWindows_11_orLater && FlatNativeWindowsLibrary.isLoaded();
}
private static void setupWindows11Border( Window popupWindow, Component contents, int borderCornerRadius ) {
// make sure that the Windows 11 window is created
private static void setupRoundedBorder( Window popupWindow, Component owner, Component contents ) {
// make sure that the native window is created
if( !popupWindow.isDisplayable() )
popupWindow.addNotify();
// get window handle
long hwnd = FlatNativeWindowsLibrary.getHWND( popupWindow );
int borderCornerRadius = getBorderCornerRadius( owner, contents );
float borderWidth = getRoundedBorderWidth( owner, contents );
// set corner preference
int cornerPreference = (borderCornerRadius <= 4)
? FlatNativeWindowsLibrary.DWMWCP_ROUNDSMALL // 4px
: FlatNativeWindowsLibrary.DWMWCP_ROUND; // 8px
FlatNativeWindowsLibrary.setWindowCornerPreference( hwnd, cornerPreference );
// set border color
int red = -1; // use system default color
int green = 0;
int blue = 0;
// get Swing border color
Color borderColor = null; // use system default color
if( contents instanceof JComponent ) {
Border border = ((JComponent)contents).getBorder();
border = FlatUIUtils.unwrapNonUIResourceBorder( border );
// get color from border of contents (e.g. JPopupMenu or JToolTip)
Color borderColor = null;
if( border instanceof FlatLineBorder )
borderColor = ((FlatLineBorder)border).getLineColor();
else if( border instanceof LineBorder )
borderColor = ((LineBorder)border).getLineColor();
else if( border instanceof EmptyBorder )
red = -2; // do not paint border
borderColor = FlatNativeWindowsLibrary.COLOR_NONE; // do not paint border
if( borderColor != null ) {
red = borderColor.getRed();
green = borderColor.getGreen();
blue = borderColor.getBlue();
}
// avoid that FlatLineBorder paints the Swing border
((JComponent)contents).putClientProperty( KEY_POPUP_USES_NATIVE_BORDER, true );
}
if( SystemInfo.isWindows ) {
// get native window handle
long hwnd = FlatNativeWindowsLibrary.getHWND( popupWindow );
// set corner preference
int cornerPreference = (borderCornerRadius <= 4)
? FlatNativeWindowsLibrary.DWMWCP_ROUNDSMALL // 4px
: FlatNativeWindowsLibrary.DWMWCP_ROUND; // 8px
FlatNativeWindowsLibrary.setWindowCornerPreference( hwnd, cornerPreference );
// set border color
FlatNativeWindowsLibrary.dwmSetWindowAttributeCOLORREF( hwnd, FlatNativeWindowsLibrary.DWMWA_BORDER_COLOR, borderColor );
} else if( SystemInfo.isMacOS ) {
if( borderColor == null || borderColor == FlatNativeWindowsLibrary.COLOR_NONE )
borderWidth = 0;
// set corner radius, border width and color
FlatNativeMacLibrary.setWindowRoundedBorder( popupWindow, borderCornerRadius,
borderWidth, (borderColor != null) ? borderColor.getRGB() : 0 );
}
FlatNativeWindowsLibrary.setWindowBorderColor( hwnd, red, green, blue );
}
private static void resetWindows11Border( Window popupWindow ) {
@@ -393,7 +377,34 @@ public class FlatPopupFactory
FlatNativeWindowsLibrary.setWindowCornerPreference( hwnd, FlatNativeWindowsLibrary.DWMWCP_DONOTROUND );
}
private static int getBorderCornerRadius( Component owner, Component contents ) {
String uiKey =
(contents instanceof BasicComboPopup) ? "ComboBox.borderCornerRadius" :
(contents instanceof JPopupMenu) ? "PopupMenu.borderCornerRadius" :
(contents instanceof JToolTip) ? "ToolTip.borderCornerRadius" :
"Popup.borderCornerRadius";
Object value = getOption( owner, contents, FlatClientProperties.POPUP_BORDER_CORNER_RADIUS, uiKey );
return (value instanceof Integer) ? (Integer) value : 0;
}
private static float getRoundedBorderWidth( Component owner, Component contents ) {
String uiKey =
(contents instanceof BasicComboPopup) ? "ComboBox.roundedBorderWidth" :
(contents instanceof JPopupMenu) ? "PopupMenu.roundedBorderWidth" :
(contents instanceof JToolTip) ? "ToolTip.roundedBorderWidth" :
"Popup.roundedBorderWidth";
Object value = getOption( owner, contents, FlatClientProperties.POPUP_ROUNDED_BORDER_WIDTH, uiKey );
return (value instanceof Number) ? ((Number)value).floatValue() : 0;
}
//---- fixes --------------------------------------------------------------
private static boolean overlapsHeavyWeightComponent( Component owner, Component contents, int x, int y ) {
if( owner == null )
return false;
Window window = SwingUtilities.getWindowAncestor( owner );
if( window == null )
return false;
@@ -420,6 +431,63 @@ public class FlatPopupFactory
return false;
}
/**
* On Linux with Wayland, since Java 21, Swing adds a window focus listener to popup owner/invoker window,
* which hides the popup as soon as the owner/invoker window looses focus.
* This works fine for light-weight popups.
* It also works for heavy-weight popups if they do not request focus.
* Because FlatLaf always uses heavy-weight popups, all popups that request focus
* are broken since Java 21.
*
* This method removes the problematic window focus listener.
*
* https://bugs.openjdk.org/browse/JDK-8280993
* https://github.com/openjdk/jdk/pull/13830
*/
private static void fixLinuxWaylandJava21focusIssue( Component owner ) {
// only necessary on Linux when running in Java 21+
if( owner == null || !SystemInfo.isLinux || SystemInfo.javaVersion < SystemInfo.toVersion( 21, 0, 0, 0 ) )
return;
// get window
Window window = SwingUtilities.getWindowAncestor( owner );
if( window == null )
return;
// remove window focus listener, which was added from class sun.awt.UNIXToolkit since Java 21
for( WindowFocusListener l : window.getWindowFocusListeners() ) {
if( "sun.awt.UNIXToolkit$1".equals( l.getClass().getName() ) ) {
window.removeWindowFocusListener( l );
break;
}
}
}
/**
* Shows the given popup and, if necessary, fixes the location of a heavy weight popup window.
* <p>
* On a dual screen setup, where screens use different scale factors, it may happen
* that the window location changes when showing a heavy weight popup window.
* E.g. when opening a dialog on the secondary screen and making combobox popup visible.
* <p>
* This is a workaround for https://bugs.openjdk.java.net/browse/JDK-8224608
*/
private static void showPopupAndFixLocation( Popup popup, Window popupWindow ) {
if( popupWindow != null ) {
// remember location of heavy weight popup window
int x = popupWindow.getX();
int y = popupWindow.getY();
popup.show();
// restore popup window location if it has changed
// (probably scaled when screens use different scale factors)
if( popupWindow.getX() != x || popupWindow.getY() != y )
popupWindow.setLocation( x, y );
} else
popup.show();
}
//---- class NonFlashingPopup ---------------------------------------------
private static class NonFlashingPopup
@@ -473,6 +541,9 @@ public class FlatPopupFactory
@Override
public void hide() {
if( contents instanceof JComponent )
((JComponent)contents).putClientProperty( KEY_POPUP_USES_NATIVE_BORDER, null );
if( delegate != null ) {
delegate.hide();
delegate = null;

View File

@@ -35,6 +35,7 @@ import javax.swing.CellRendererPane;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.LookAndFeel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
@@ -262,7 +263,7 @@ public class FlatRadioButtonUI
@Override
public void paint( Graphics g, JComponent c ) {
// fill background even if not opaque if
// fill background even if not opaque and if:
// - contentAreaFilled is true and
// - if background color is different to default background color
// (this paints selection if using the component as cell renderer)
@@ -278,20 +279,27 @@ public class FlatRadioButtonUI
int focusWidth = getIconFocusWidth( c );
if( focusWidth > 0 ) {
boolean ltr = c.getComponentOrientation().isLeftToRight();
int halign = ((AbstractButton)c).getHorizontalAlignment();
if( halign == SwingConstants.LEADING )
halign = ltr ? SwingConstants.LEFT : SwingConstants.RIGHT;
else if( halign == SwingConstants.TRAILING )
halign = ltr ? SwingConstants.RIGHT : SwingConstants.LEFT;
Insets insets = c.getInsets( tempInsets );
int leftOrRightInset = ltr ? insets.left : insets.right;
if( focusWidth > leftOrRightInset ) {
if( (focusWidth > insets.left || focusWidth > insets.right) &&
(halign == SwingConstants.LEFT || halign == SwingConstants.RIGHT) )
{
// The left (or right) inset is smaller than the focus width, which may be
// the case if insets were explicitly reduced (e.g. with an EmptyBorder).
// In this case the width has been increased in getPreferredSize() and
// here it is necessary to fix icon and text painting location.
int offset = focusWidth - leftOrRightInset;
if( !ltr )
offset = -offset;
int offset = (halign == SwingConstants.LEFT)
? Math.max( focusWidth - insets.left, 0 )
: -Math.max( focusWidth - insets.right, 0 );
// move the graphics origin to the left (or right)
g.translate( offset, 0 );
super.paint( g, c );
super.paint( FlatLabelUI.createGraphicsHTMLTextYCorrection( g, c ), c );
g.translate( -offset, 0 );
return;
}
@@ -328,6 +336,11 @@ public class FlatRadioButtonUI
: 0;
}
@Override
public int getBaseline( JComponent c, int width, int height ) {
return FlatButtonUI.getBaselineImpl( c, width, height );
}
//---- class FlatRadioButtonListener --------------------------------------
/** @since 2 */

View File

@@ -28,8 +28,6 @@ import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.LayoutManager2;
import java.awt.Window;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
@@ -41,7 +39,6 @@ import javax.swing.JLayeredPane;
import javax.swing.JMenuBar;
import javax.swing.JRootPane;
import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.plaf.BorderUIResource;
@@ -88,8 +85,8 @@ public class FlatRootPaneUI
private Object nativeWindowBorderData;
private LayoutManager oldLayout;
private PropertyChangeListener ancestorListener;
private ComponentListener componentListener;
private ComponentListener macFullWindowContentListener;
private PropertyChangeListener macWindowBackgroundListener;
public static ComponentUI createUI( JComponent c ) {
return new FlatRootPaneUI();
@@ -107,6 +104,7 @@ public class FlatRootPaneUI
installBorder();
installNativeWindowBorder();
macInstallFullWindowContentSupport();
}
protected void installBorder() {
@@ -123,6 +121,7 @@ public class FlatRootPaneUI
uninstallNativeWindowBorder();
uninstallClientDecorations();
macUninstallFullWindowContentSupport();
rootPane = null;
}
@@ -157,9 +156,7 @@ public class FlatRootPaneUI
parent.setBackground( UIManager.getColor( "control" ) );
}
// enable dark window appearance on macOS when running in JetBrains Runtime
if( SystemInfo.isJetBrainsJVM && SystemInfo.isMacOS_10_14_Mojave_orLater )
c.putClientProperty( "jetbrains.awt.windowDarkAppearance", FlatLaf.isLafDark() );
macClearBackgroundForTranslucentWindow( c );
}
@Override
@@ -179,55 +176,20 @@ public class FlatRootPaneUI
protected void installListeners( JRootPane root ) {
super.installListeners( root );
if( SystemInfo.isJava_9_orLater ) {
// On HiDPI screens, where scaling is used, there may be white lines on the
// bottom and on the right side of the window when it is initially shown.
// This is very disturbing in dark themes, but hard to notice in light themes.
// Seems to be a rounding issue when Swing adds dirty region of window
// using RepaintManager.nativeAddDirtyRegion().
//
// Note: Not using a HierarchyListener here, which would be much easier,
// because this causes problems with mouse clicks in heavy-weight popups.
// Instead, add a listener to the root pane that waits until it is added
// to a window, then add a component listener to the window.
// See: https://github.com/JFormDesigner/FlatLaf/issues/371
ancestorListener = e -> {
Object oldValue = e.getOldValue();
Object newValue = e.getNewValue();
if( newValue instanceof Window ) {
if( componentListener == null ) {
componentListener = new ComponentAdapter() {
@Override
public void componentShown( ComponentEvent e ) {
// add whole root pane to dirty regions when window is initially shown
root.getParent().repaint( root.getX(), root.getY(), root.getWidth(), root.getHeight() );
}
};
}
((Window)newValue).addComponentListener( componentListener );
} else if( newValue == null && oldValue instanceof Window ) {
if( componentListener != null )
((Window)oldValue).removeComponentListener( componentListener );
}
};
root.addPropertyChangeListener( "ancestor", ancestorListener );
}
if( SystemInfo.isMacFullWindowContentSupported )
macFullWindowContentListener = FullWindowContentSupport.macInstallListeners( root );
macInstallWindowBackgroundListener( root );
}
@Override
protected void uninstallListeners( JRootPane root ) {
super.uninstallListeners( root );
if( SystemInfo.isJava_9_orLater ) {
if( componentListener != null ) {
Window window = SwingUtilities.windowForComponent( root );
if( window != null )
window.removeComponentListener( componentListener );
componentListener = null;
}
root.removePropertyChangeListener( "ancestor", ancestorListener );
ancestorListener = null;
if( SystemInfo.isMacFullWindowContentSupported ) {
FullWindowContentSupport.macUninstallListeners( root, macFullWindowContentListener );
macFullWindowContentListener = null;
}
macUninstallWindowBackgroundListener( root );
}
/** @since 1.1.2 */
@@ -307,19 +269,136 @@ public class FlatRootPaneUI
// layer title pane under frame content layer to allow placing menu bar over title pane
protected final static Integer TITLE_PANE_LAYER = JLayeredPane.FRAME_CONTENT_LAYER - 1;
private final static Integer TITLE_PANE_MOUSE_LAYER = JLayeredPane.FRAME_CONTENT_LAYER - 2;
// for fullWindowContent mode, layer title pane over frame content layer to allow placing title bar buttons over content
/** @since 3.4 */
protected final static Integer TITLE_PANE_FULL_WINDOW_CONTENT_LAYER = JLayeredPane.FRAME_CONTENT_LAYER + 1;
private Integer getLayerForTitlePane() {
return isFullWindowContent( rootPane ) ? TITLE_PANE_FULL_WINDOW_CONTENT_LAYER : TITLE_PANE_LAYER;
}
protected void setTitlePane( FlatTitlePane newTitlePane ) {
JLayeredPane layeredPane = rootPane.getLayeredPane();
if( titlePane != null )
if( titlePane != null ) {
layeredPane.remove( titlePane );
layeredPane.remove( titlePane.mouseLayer );
}
if( newTitlePane != null )
layeredPane.add( newTitlePane, TITLE_PANE_LAYER );
if( newTitlePane != null ) {
layeredPane.add( newTitlePane, getLayerForTitlePane() );
layeredPane.add( newTitlePane.mouseLayer, TITLE_PANE_MOUSE_LAYER );
}
titlePane = newTitlePane;
}
private void macInstallFullWindowContentSupport() {
if( !SystemInfo.isMacOS )
return;
// set window buttons spacing
if( isMacButtonsSpacingSupported() && rootPane.isDisplayable() ) {
int buttonsSpacing = FlatNativeMacLibrary.BUTTONS_SPACING_DEFAULT;
String value = (String) rootPane.getClientProperty( FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING );
if( value != null ) {
switch( value ) {
case FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING_MEDIUM:
buttonsSpacing = FlatNativeMacLibrary.BUTTONS_SPACING_MEDIUM;
break;
case FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING_LARGE:
buttonsSpacing = FlatNativeMacLibrary.BUTTONS_SPACING_LARGE;
break;
}
}
FlatNativeMacLibrary.setWindowButtonsSpacing( getParentWindow( rootPane ), buttonsSpacing );
}
// update buttons bounds client property
FullWindowContentSupport.macUpdateFullWindowContentButtonsBoundsProperty( rootPane );
}
private void macUninstallFullWindowContentSupport() {
if( !SystemInfo.isMacOS )
return;
// do not uninstall when switching to another FlatLaf theme
if( UIManager.getLookAndFeel() instanceof FlatLaf )
return;
// reset window buttons spacing
if( isMacButtonsSpacingSupported() )
FlatNativeMacLibrary.setWindowButtonsSpacing( getParentWindow( rootPane ), FlatNativeMacLibrary.BUTTONS_SPACING_DEFAULT );
// remove buttons bounds client property
FullWindowContentSupport.macUninstallFullWindowContentButtonsBoundsProperty( rootPane );
}
private boolean isMacButtonsSpacingSupported() {
return SystemInfo.isMacOS && SystemInfo.isJava_17_orLater && FlatNativeMacLibrary.isLoaded();
}
private void macInstallWindowBackgroundListener( JRootPane c ) {
if( !SystemInfo.isMacOS )
return;
Window window = getParentWindow( c );
if( window != null && macWindowBackgroundListener == null ) {
macWindowBackgroundListener = e -> macClearBackgroundForTranslucentWindow( c );
window.addPropertyChangeListener( "background", macWindowBackgroundListener );
}
}
private void macUninstallWindowBackgroundListener( JRootPane c ) {
if( !SystemInfo.isMacOS )
return;
Window window = getParentWindow( c );
if( window != null && macWindowBackgroundListener != null ) {
window.removePropertyChangeListener( "background", macWindowBackgroundListener );
macWindowBackgroundListener = null;
}
}
/**
* When setting window background to translucent color (alpha < 255),
* Swing paints that window translucent on Windows and Linux, but not on macOS.
* The reason for this is that FlatLaf sets the background color of the root pane,
* and Swing behaves a bit differently on macOS than on other platforms in that case.
* Other L&Fs do not set root pane background, which is {@code null} by default.
* <p>
* To fix this problem, set the root pane background to {@code null}
* if windows uses a translucent background.
*/
private void macClearBackgroundForTranslucentWindow( JRootPane c ) {
if( !SystemInfo.isMacOS )
return;
Window window = getParentWindow( c );
if( window != null ) {
Color windowBackground = window.getBackground();
if( windowBackground != null &&
windowBackground.getAlpha() < 255 &&
c.getBackground() instanceof UIResource )
{
// clear root pane background
c.setBackground( null );
}
}
}
private Window getParentWindow( JRootPane c ) {
// not using SwingUtilities.windowForComponent() or SwingUtilities.getWindowAncestor()
// here because root panes may be nested and used anywhere (e.g. in JInternalFrame)
// but we're only interested in the "root" root pane, which is a direct child of the window
Container parent = c.getParent();
return (parent instanceof Window) ? (Window) parent : null;
}
@Override
public void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e );
@@ -364,6 +443,21 @@ public class FlatRootPaneUI
titlePane.titleBarColorsChanged();
break;
case FlatClientProperties.FULL_WINDOW_CONTENT:
if( titlePane != null ) {
rootPane.getLayeredPane().setLayer( titlePane, getLayerForTitlePane() );
titlePane.updateIcon();
titlePane.updateVisibility();
titlePane.updateFullWindowContentButtonsBoundsProperty();
}
FullWindowContentSupport.revalidatePlaceholders( rootPane );
rootPane.revalidate();
break;
case FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS:
FullWindowContentSupport.revalidatePlaceholders( rootPane );
break;
case FlatClientProperties.GLASS_PANE_FULL_HEIGHT:
rootPane.revalidate();
break;
@@ -372,14 +466,43 @@ public class FlatRootPaneUI
if( rootPane.isDisplayable() )
throw new IllegalComponentStateException( "The client property 'Window.style' must be set before the window becomes displayable." );
break;
case "ancestor":
if( e.getNewValue() instanceof Window )
macClearBackgroundForTranslucentWindow( rootPane );
macUninstallWindowBackgroundListener( rootPane );
macInstallWindowBackgroundListener( rootPane );
// FlatNativeMacLibrary.setWindowButtonsSpacing() and
// FullWindowContentSupport.macUpdateFullWindowContentButtonsBoundsProperty()
// require a native window, but setting the client properties
// "apple.awt.fullWindowContent" or FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING
// is usually done before the native window is created
// --> try again when native window is created
if( e.getNewValue() instanceof Window )
macInstallFullWindowContentSupport();
break;
case FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING:
macInstallFullWindowContentSupport();
break;
case "apple.awt.fullWindowContent":
if( SystemInfo.isMacFullWindowContentSupported )
FullWindowContentSupport.macUpdateFullWindowContentButtonsBoundsProperty( rootPane );
break;
}
}
/** @since 3.4 */
protected static boolean isFullWindowContent( JRootPane rootPane ) {
return FlatClientProperties.clientPropertyBoolean( rootPane, FlatClientProperties.FULL_WINDOW_CONTENT, false );
}
protected static boolean isMenuBarEmbedded( JRootPane rootPane ) {
RootPaneUI ui = rootPane.getUI();
return ui instanceof FlatRootPaneUI &&
((FlatRootPaneUI)ui).titlePane != null &&
((FlatRootPaneUI)ui).titlePane.isMenuBarEmbedded();
FlatTitlePane titlePane = getTitlePane( rootPane );
return titlePane != null && titlePane.isMenuBarEmbedded();
}
/** @since 2.4 */
@@ -415,23 +538,21 @@ public class FlatRootPaneUI
private Dimension computeLayoutSize( Container parent, Function<Component, Dimension> getSizeFunc ) {
JRootPane rootPane = (JRootPane) parent;
Dimension titlePaneSize = (titlePane != null)
? getSizeFunc.apply( titlePane )
: new Dimension();
Dimension contentSize = (rootPane.getContentPane() != null)
? getSizeFunc.apply( rootPane.getContentPane() )
: rootPane.getSize();
: rootPane.getSize(); // same as in JRootPane.RootLayout.preferredLayoutSize()
int width = contentSize.width; // title pane width is not considered here
int height = titlePaneSize.height + contentSize.height;
int height = contentSize.height;
if( titlePane != null && !isFullWindowContent( rootPane ) )
height += getSizeFunc.apply( titlePane ).height;
if( titlePane == null || !titlePane.isMenuBarEmbedded() ) {
JMenuBar menuBar = rootPane.getJMenuBar();
Dimension menuBarSize = (menuBar != null && menuBar.isVisible())
? getSizeFunc.apply( menuBar )
: new Dimension();
width = Math.max( width, menuBarSize.width );
height += menuBarSize.height;
if( menuBar != null && menuBar.isVisible() ) {
Dimension menuBarSize = getSizeFunc.apply( menuBar );
width = Math.max( width, menuBarSize.width );
height += menuBarSize.height;
}
}
Insets insets = rootPane.getInsets();
@@ -456,12 +577,23 @@ public class FlatRootPaneUI
if( rootPane.getLayeredPane() != null )
rootPane.getLayeredPane().setBounds( x, y, width, height );
// title pane
// title pane (is a child of layered pane)
int nextY = 0;
if( titlePane != null ) {
int prefHeight = !isFullScreen ? titlePane.getPreferredSize().height : 0;
titlePane.setBounds( 0, 0, width, prefHeight );
nextY += prefHeight;
boolean isFullWindowContent = isFullWindowContent( rootPane );
if( isFullWindowContent && !UIManager.getBoolean( FlatTitlePane.KEY_DEBUG_SHOW_RECTANGLES ) ) {
// place title bar into top-right corner
int tw = Math.min( titlePane.getPreferredSize().width, width );
int tx = titlePane.getComponentOrientation().isLeftToRight() ? width - tw : 0;
titlePane.setBounds( tx, 0, tw, prefHeight );
} else
titlePane.setBounds( 0, 0, width, prefHeight );
titlePane.mouseLayer.setBounds( 0, 0, width, prefHeight );
if( !isFullWindowContent )
nextY += prefHeight;
}
// glass pane
@@ -472,7 +604,7 @@ public class FlatRootPaneUI
rootPane.getGlassPane().setBounds( x, y + offset, width, height - offset );
}
// menu bar
// menu bar (is a child of layered pane)
JMenuBar menuBar = rootPane.getJMenuBar();
if( menuBar != null && menuBar.isVisible() ) {
boolean embedded = !isFullScreen && titlePane != null && titlePane.isMenuBarEmbedded();
@@ -480,13 +612,23 @@ public class FlatRootPaneUI
titlePane.validate();
menuBar.setBounds( titlePane.getMenuBarBounds() );
} else {
int mx = 0;
int mw = width;
if( titlePane != null && isFullWindowContent( rootPane ) ) {
// make menu bar width smaller to avoid that it overlaps title bar buttons
int tw = Math.min( titlePane.getPreferredSize().width, width );
mw -= tw;
if( !titlePane.getComponentOrientation().isLeftToRight() )
mx = tw;
}
Dimension prefSize = menuBar.getPreferredSize();
menuBar.setBounds( 0, nextY, width, prefSize.height );
menuBar.setBounds( mx, nextY, mw, prefSize.height );
nextY += prefSize.height;
}
}
// content pane
// content pane (is a child of layered pane)
Container contentPane = rootPane.getContentPane();
if( contentPane != null )
contentPane.setBounds( 0, nextY, width, Math.max( height - nextY, 0 ) );
@@ -499,7 +641,7 @@ public class FlatRootPaneUI
@Override
public void invalidateLayout( Container parent ) {
if( titlePane != null )
titlePane.menuBarChanged();
titlePane.menuBarInvalidate();
}
@Override

View File

@@ -245,7 +245,7 @@ public class FlatScrollBarUI
// because scroll bars do not receive mouse exited event.
// The scroll pane, including its scroll bars, is not part
// of the component hierarchy and does not receive mouse events
// directly. Instead LWComponentPeer receives mouse events
// directly. Instead, LWComponentPeer receives mouse events
// and delegates them to peers, but entered/exited events
// are sent only for the whole scroll pane.
// Exited event is only sent when mouse leaves scroll pane.

View File

@@ -0,0 +1,118 @@
/*
* Copyright 2023 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import java.awt.Component;
import java.awt.Insets;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.text.JTextComponent;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.util.UIScale;
/**
* Border for {@link javax.swing.JScrollPane}.
*
* @uiDefault ScrollPane.arc int
* @uiDefault ScrollPane.List.arc int
* @uiDefault ScrollPane.Table.arc int
* @uiDefault ScrollPane.TextComponent.arc int
* @uiDefault ScrollPane.Tree.arc int
* @author Karl Tauber
* @since 3.3
*/
public class FlatScrollPaneBorder
extends FlatBorder
{
@Styleable protected int arc = UIManager.getInt( "ScrollPane.arc" );
private boolean isArcStyled;
private final int listArc = FlatUIUtils.getUIInt( "ScrollPane.List.arc", -1 );
private final int tableArc = FlatUIUtils.getUIInt( "ScrollPane.Table.arc", -1 );
private final int textComponentArc = FlatUIUtils.getUIInt( "ScrollPane.TextComponent.arc", -1 );
private final int treeArc = FlatUIUtils.getUIInt( "ScrollPane.Tree.arc", -1 );
@Override
public Object applyStyleProperty( String key, Object value ) {
Object oldValue = super.applyStyleProperty( key, value );
if( "arc".equals( key ) )
isArcStyled = true;
return oldValue;
}
@Override
public Insets getBorderInsets( Component c, Insets insets ) {
insets = super.getBorderInsets( c, insets );
// if view is rounded, increase left and right insets to avoid that the viewport
// is painted over the rounded border on the corners
int padding = getLeftRightPadding( c );
if( padding > 0 ) {
insets.left += padding;
insets.right += padding;
}
return insets;
}
@Override
protected int getArc( Component c ) {
if( isCellEditor( c ) )
return 0;
if( isArcStyled )
return arc;
if( c instanceof JScrollPane ) {
Component view = FlatScrollPaneUI.getView( (JScrollPane) c );
if( listArc >= 0 && view instanceof JList )
return listArc;
if( tableArc >= 0 && view instanceof JTable )
return tableArc;
if( textComponentArc >= 0&& view instanceof JTextComponent )
return textComponentArc;
if( treeArc >= 0 && view instanceof JTree )
return treeArc;
}
return arc;
}
/**
* Returns the scaled left/right padding used when arc is larger than zero.
* <p>
* This is the distance from the inside of the left border to the left side of the view component.
* On the right side, this is the distance between the right side of the view component and
* the vertical scrollbar. Or the inside of the right border if the scrollbar is hidden.
*/
public int getLeftRightPadding( Component c ) {
// Subtract lineWidth from radius because radius is given for the outside
// of the painted line, but insets from super already include lineWidth.
// Reduce padding by 10% to make padding slightly smaller because it is not recognizable
// when the view is minimally painted over the beginning of the border curve.
int arc = getArc( c );
return (arc > 0)
? Math.max( Math.round( UIScale.scale( ((arc / 2f) - getLineWidth( c )) * 0.9f ) ), 0 )
: 0;
}
}

View File

@@ -17,9 +17,12 @@
package com.formdev.flatlaf.ui;
import java.awt.Component;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener;
@@ -41,16 +44,19 @@ import javax.swing.JTree;
import javax.swing.JViewport;
import javax.swing.LookAndFeel;
import javax.swing.ScrollPaneConstants;
import javax.swing.ScrollPaneLayout;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicScrollPaneUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JScrollPane}.
@@ -97,7 +103,13 @@ public class FlatScrollPaneUI
super.installUI( c );
int focusWidth = UIManager.getInt( "Component.focusWidth" );
LookAndFeel.installProperty( c, "opaque", focusWidth == 0 );
int arc = UIManager.getInt( "ScrollPane.arc" );
LookAndFeel.installProperty( c, "opaque", focusWidth == 0 && arc == 0 );
// install layout manager
LayoutManager layout = c.getLayout();
if( layout != null && layout.getClass() == ScrollPaneLayout.UIResource.class )
c.setLayout( createScrollPaneLayout() );
installStyle();
@@ -108,6 +120,10 @@ public class FlatScrollPaneUI
public void uninstallUI( JComponent c ) {
MigLayoutVisualPadding.uninstall( scrollpane );
// uninstall layout manager
if( c.getLayout() instanceof FlatScrollPaneLayout )
c.setLayout( new ScrollPaneLayout.UIResource() );
super.uninstallUI( c );
oldStyleValues = null;
@@ -130,6 +146,13 @@ public class FlatScrollPaneUI
handler = null;
}
/**
* @since 3.3
*/
protected FlatScrollPaneLayout createScrollPaneLayout() {
return new FlatScrollPaneLayout();
}
@Override
protected MouseWheelListener createMouseWheelListener() {
MouseWheelListener superListener = super.createMouseWheelListener();
@@ -290,8 +313,7 @@ public class FlatScrollPaneUI
Object corner = e.getNewValue();
if( corner instanceof JButton &&
((JButton)corner).getBorder() instanceof FlatButtonBorder &&
scrollpane.getViewport() != null &&
scrollpane.getViewport().getView() instanceof JTable )
getView( scrollpane ) instanceof JTable )
{
((JButton)corner).setBorder( BorderFactory.createEmptyBorder() );
((JButton)corner).setFocusable( false );
@@ -308,6 +330,18 @@ public class FlatScrollPaneUI
scrollpane.revalidate();
scrollpane.repaint();
break;
case "border":
Object newBorder = e.getNewValue();
if( newBorder != null && newBorder == UIManager.getBorder( "Table.scrollPaneBorder" ) ) {
// JTable.configureEnclosingScrollPaneUI() replaces the scrollpane border
// with another one --> re-apply style on new border
borderShared = null;
installStyle();
scrollpane.revalidate();
scrollpane.repaint();
}
break;
}
};
}
@@ -334,9 +368,10 @@ public class FlatScrollPaneUI
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
if( key.equals( "focusWidth" ) ) {
if( key.equals( "focusWidth" ) || key.equals( "arc" ) ) {
int focusWidth = (value instanceof Integer) ? (int) value : UIManager.getInt( "Component.focusWidth" );
LookAndFeel.installProperty( scrollpane, "opaque", focusWidth == 0 );
int arc = (value instanceof Integer) ? (int) value : UIManager.getInt( "ScrollPane.arc" );
LookAndFeel.installProperty( scrollpane, "opaque", focusWidth == 0 && arc == 0 );
}
if( borderShared == null )
@@ -402,13 +437,46 @@ public class FlatScrollPaneUI
c.getHeight() - insets.top - insets.bottom );
}
// if view is rounded, paint rounded background with view background color
// to ensure that free areas at left and right have same color as view
Component view;
float arc = getBorderArc( scrollpane );
if( arc > 0 && (view = getView( scrollpane )) != null ) {
float focusWidth = FlatUIUtils.getBorderFocusWidth( c );
g.setColor( view.getBackground() );
Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g );
FlatUIUtils.paintComponentBackground( (Graphics2D) g, 0, 0, c.getWidth(), c.getHeight(), focusWidth, arc );
FlatUIUtils.resetRenderingHints( g, oldRenderingHints );
}
paint( g, c );
}
@Override
public void paint( Graphics g, JComponent c ) {
Border viewportBorder = scrollpane.getViewportBorder();
if( viewportBorder != null ) {
Rectangle r = scrollpane.getViewportBorderBounds();
int padding = getBorderLeftRightPadding( scrollpane );
JScrollBar vsb = scrollpane.getVerticalScrollBar();
if( padding > 0 &&
vsb != null && vsb.isVisible() &&
scrollpane.getLayout() instanceof FlatScrollPaneLayout &&
((FlatScrollPaneLayout)scrollpane.getLayout()).canIncreaseViewportWidth( scrollpane ) )
{
boolean ltr = scrollpane.getComponentOrientation().isLeftToRight();
int extraWidth = Math.min( padding, vsb.getWidth() );
viewportBorder.paintBorder( scrollpane, g, r.x - (ltr ? 0 : extraWidth), r.y, r.width + extraWidth, r.height );
} else
viewportBorder.paintBorder( scrollpane, g, r.x, r.y, r.width, r.height );
}
}
/** @since 1.3 */
public static boolean isPermanentFocusOwner( JScrollPane scrollPane ) {
JViewport viewport = scrollPane.getViewport();
Component view = (viewport != null) ? viewport.getView() : null;
Component view = getView( scrollPane );
if( view == null )
return false;
@@ -428,6 +496,25 @@ public class FlatScrollPaneUI
return false;
}
static Component getView( JScrollPane scrollPane ) {
JViewport viewport = scrollPane.getViewport();
return (viewport != null) ? viewport.getView() : null;
}
private static float getBorderArc( JScrollPane scrollPane ) {
Border border = scrollPane.getBorder();
return (border instanceof FlatScrollPaneBorder)
? UIScale.scale( (float) ((FlatScrollPaneBorder)border).getArc( scrollPane ) )
: 0;
}
private static int getBorderLeftRightPadding( JScrollPane scrollPane ) {
Border border = scrollPane.getBorder();
return (border instanceof FlatScrollPaneBorder)
? ((FlatScrollPaneBorder)border).getLeftRightPadding( scrollPane )
: 0;
}
//---- class Handler ------------------------------------------------------
/**
@@ -450,13 +537,71 @@ public class FlatScrollPaneUI
@Override
public void focusGained( FocusEvent e ) {
// necessary to update focus border
scrollpane.repaint();
if( scrollpane.getBorder() instanceof FlatBorder )
scrollpane.repaint();
}
@Override
public void focusLost( FocusEvent e ) {
// necessary to update focus border
scrollpane.repaint();
if( scrollpane.getBorder() instanceof FlatBorder )
scrollpane.repaint();
}
}
//---- class FlatScrollPaneLayout -----------------------------------------
/**
* @since 3.3
*/
protected static class FlatScrollPaneLayout
extends ScrollPaneLayout.UIResource
{
@Override
public void layoutContainer( Container parent ) {
super.layoutContainer( parent );
JScrollPane scrollPane = (JScrollPane) parent;
int padding = getBorderLeftRightPadding( scrollPane );
if( padding > 0 && vsb != null && vsb.isVisible() ) {
// move vertical scrollbar to trailing edge
Insets insets = scrollPane.getInsets();
Rectangle r = vsb.getBounds();
int y = Math.max( r.y, insets.top + padding );
int y2 = Math.min( r.y + r.height, scrollPane.getHeight() - insets.bottom - padding );
boolean ltr = scrollPane.getComponentOrientation().isLeftToRight();
vsb.setBounds( r.x + (ltr ? padding : -padding), y, r.width, y2 - y );
// increase width of viewport, column header and horizontal scrollbar
if( canIncreaseViewportWidth( scrollPane ) ) {
int extraWidth = Math.min( padding, vsb.getWidth() );
resizeViewport( viewport, extraWidth, ltr );
resizeViewport( colHead, extraWidth, ltr );
resizeViewport( hsb, extraWidth, ltr );
}
}
}
boolean canIncreaseViewportWidth( JScrollPane scrollPane ) {
return scrollPane.getComponentOrientation().isLeftToRight()
? !isCornerVisible( upperRight ) && !isCornerVisible( lowerRight )
: !isCornerVisible( upperLeft ) && !isCornerVisible( lowerLeft );
}
private static boolean isCornerVisible( Component corner ) {
return corner != null &&
corner.getWidth() > 0 &&
corner.getHeight() > 0 &&
corner.isVisible();
}
private static void resizeViewport( Component c, int extraWidth, boolean ltr ) {
if( c == null )
return;
Rectangle vr = c.getBounds();
c.setBounds( vr.x - (ltr ? 0 : extraWidth), vr.y, vr.width + extraWidth, vr.height );
}
}
}

View File

@@ -16,7 +16,9 @@
package com.formdev.flatlaf.ui;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Graphics;
@@ -67,6 +69,8 @@ import com.formdev.flatlaf.util.UIScale;
* <!-- FlatSplitPaneUI -->
*
* @uiDefault Component.arrowType String chevron (default) or triangle
* @uiDefault SplitPaneDivider.hoverColor Color optional
* @uiDefault SplitPaneDivider.pressedColor Color optional
* @uiDefault SplitPaneDivider.oneTouchArrowColor Color
* @uiDefault SplitPaneDivider.oneTouchHoverArrowColor Color
* @uiDefault SplitPaneDivider.oneTouchPressedArrowColor Color
@@ -80,9 +84,10 @@ import com.formdev.flatlaf.util.UIScale;
*/
public class FlatSplitPaneUI
extends BasicSplitPaneUI
implements StyleableUI
implements StyleableUI, FlatTitlePane.TitleBarCaptionHitTest
{
@Styleable protected String arrowType;
/** @since 3.3 */ @Styleable protected Color draggingColor;
@Styleable protected Color oneTouchArrowColor;
@Styleable protected Color oneTouchHoverArrowColor;
@Styleable protected Color oneTouchPressedArrowColor;
@@ -104,6 +109,8 @@ public class FlatSplitPaneUI
protected void installDefaults() {
arrowType = UIManager.getString( "Component.arrowType" );
draggingColor = UIManager.getColor( "SplitPaneDivider.draggingColor" );
// get one-touch colors before invoking super.installDefaults() because they are
// used in there on LaF switching
oneTouchArrowColor = UIManager.getColor( "SplitPaneDivider.oneTouchArrowColor" );
@@ -117,6 +124,8 @@ public class FlatSplitPaneUI
protected void uninstallDefaults() {
super.uninstallDefaults();
draggingColor = null;
oneTouchArrowColor = null;
oneTouchHoverArrowColor = null;
oneTouchPressedArrowColor = null;
@@ -183,12 +192,58 @@ public class FlatSplitPaneUI
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
protected Component createDefaultNonContinuousLayoutDivider() {
// only used for non-continuous layout if left or right component is heavy weight
return new Canvas() {
@Override
public void paint( Graphics g ) {
if( !isContinuousLayout() && getLastDragLocation() != -1 )
paintDragDivider( g, 0 );
}
};
}
@Override
public void finishedPaintingChildren( JSplitPane sp, Graphics g ) {
if( sp == splitPane && getLastDragLocation() != -1 && !isContinuousLayout() && !draggingHW )
paintDragDivider( g, getLastDragLocation() );
}
private void paintDragDivider( Graphics g, int dividerLocation ) {
// divider bounds
boolean horizontal = (getOrientation() == JSplitPane.HORIZONTAL_SPLIT);
int x = horizontal ? dividerLocation : 0;
int y = !horizontal ? dividerLocation : 0;
int width = horizontal ? dividerSize : splitPane.getWidth();
int height = !horizontal ? dividerSize : splitPane.getHeight();
// paint background
g.setColor( FlatUIUtils.deriveColor( draggingColor, splitPane.getBackground() ) );
g.fillRect( x, y, width, height );
// paint divider style (e.g. grip)
if( divider instanceof FlatSplitPaneDivider )
((FlatSplitPaneDivider)divider).paintStyle( g, x, y, width, height );
}
//---- interface FlatTitlePane.TitleBarCaptionHitTest ----
/** @since 3.4 */
@Override
public Boolean isTitleBarCaptionAt( int x, int y ) {
// necessary because BasicSplitPaneDivider adds some mouse listeners for dragging divider
return null; // check children
}
//---- class FlatSplitPaneDivider -----------------------------------------
protected class FlatSplitPaneDivider
extends BasicSplitPaneDivider
{
@Styleable protected String style = UIManager.getString( "SplitPaneDivider.style" );
/** @since 3.3 */ @Styleable protected Color hoverColor = UIManager.getColor( "SplitPaneDivider.hoverColor" );
/** @since 3.3 */ @Styleable protected Color pressedColor = UIManager.getColor( "SplitPaneDivider.pressedColor" );
@Styleable protected Color gripColor = UIManager.getColor( "SplitPaneDivider.gripColor" );
@Styleable protected int gripDotCount = FlatUIUtils.getUIInt( "SplitPaneDivider.gripDotCount", 3 );
@Styleable protected int gripDotSize = FlatUIUtils.getUIInt( "SplitPaneDivider.gripDotSize", 3 );
@@ -251,15 +306,31 @@ public class FlatSplitPaneUI
@Override
public void paint( Graphics g ) {
// paint hover or pressed background
Color hoverOrPressedColor = (isContinuousLayout() && dragger != null)
? pressedColor
: (isMouseOver() && dragger == null
? hoverColor
: null);
if( hoverOrPressedColor != null ) {
g.setColor( FlatUIUtils.deriveColor( hoverOrPressedColor, splitPane.getBackground() ) );
g.fillRect( 0, 0, getWidth(), getHeight() );
}
super.paint( g );
paintStyle( g, 0, 0, getWidth(), getHeight() );
}
/** @since 3.3 */
protected void paintStyle( Graphics g, int x, int y, int width, int height ) {
if( "plain".equals( style ) )
return;
Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g );
g.setColor( gripColor );
paintGrip( g, 0, 0, getWidth(), getHeight() );
paintGrip( g, x, y, width, height );
FlatUIUtils.resetRenderingHints( g, oldRenderingHints );
}
@@ -286,6 +357,29 @@ public class FlatSplitPaneUI
: location == (splitPane.getWidth() - getWidth() - insets.right);
}
@Override
protected void setMouseOver( boolean mouseOver ) {
super.setMouseOver( mouseOver );
repaintIfNecessary();
}
@Override
protected void prepareForDragging() {
super.prepareForDragging();
repaintIfNecessary();
}
@Override
protected void finishDraggingTo( int location ) {
super.finishDraggingTo( location );
repaintIfNecessary();
}
private void repaintIfNecessary() {
if( hoverColor != null || pressedColor != null )
repaint();
}
//---- class FlatOneTouchButton ---------------------------------------
protected class FlatOneTouchButton

View File

@@ -18,6 +18,7 @@ package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
@@ -28,16 +29,15 @@ import java.awt.event.MouseEvent;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeListener;
import java.util.Map;
import javax.swing.CellRendererPane;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.event.MouseInputListener;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTableHeaderUI;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
@@ -114,6 +114,11 @@ public class FlatTableHeaderUI
public void installUI( JComponent c ) {
super.installUI( c );
// replace cell renderer pane
header.remove( rendererPane );
rendererPane = new FlatTableHeaderCellRendererPane();
header.add( rendererPane );
installStyle();
}
@@ -265,16 +270,8 @@ public class FlatTableHeaderUI
}
}
// temporary use own default renderer
FlatTableCellHeaderRenderer tempRenderer = new FlatTableCellHeaderRenderer( header.getDefaultRenderer() );
header.setDefaultRenderer( tempRenderer );
// paint header
super.paint( g, c );
// restore default renderer
tempRenderer.reset();
header.setDefaultRenderer( tempRenderer.delegate );
}
private boolean isSystemDefaultRenderer( Object headerRenderer ) {
@@ -332,119 +329,129 @@ public class FlatTableHeaderUI
return false;
}
//---- class FlatTableCellHeaderRenderer ----------------------------------
//---- class FlatTableHeaderCellRendererPane ------------------------------
/**
* A delegating header renderer that is only used to paint hover and pressed
* background/foreground and to paint sort arrows at top, bottom or left position.
* Cell renderer pane that is used to paint hover and pressed background/foreground
* and to paint sort arrows at top, bottom or left position.
*/
private class FlatTableCellHeaderRenderer
implements TableCellRenderer, Border, UIResource
private class FlatTableHeaderCellRendererPane
extends CellRendererPane
{
private final TableCellRenderer delegate;
private final Icon ascendingSortIcon;
private final Icon descendingSortIcon;
private JLabel l;
private Color oldBackground;
private Color oldForeground;
private Boolean oldOpaque;
private int oldHorizontalTextPosition = -1;
private Border origBorder;
private Icon sortIcon;
FlatTableCellHeaderRenderer( TableCellRenderer delegate ) {
this.delegate = delegate;
public FlatTableHeaderCellRendererPane() {
ascendingSortIcon = UIManager.getIcon( "Table.ascendingSortIcon" );
descendingSortIcon = UIManager.getIcon( "Table.descendingSortIcon" );
}
@Override
public Component getTableCellRendererComponent( JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column )
{
Component c = delegate.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column );
if( !(c instanceof JLabel) )
return c;
public void paintComponent( Graphics g, Component c, Container p, int x, int y, int w, int h, boolean shouldValidate ) {
if( !(c instanceof JLabel) ) {
super.paintComponent( g, c, p, x, y, w, h, shouldValidate );
return;
}
l = (JLabel) c;
JLabel l = (JLabel) c;
Color oldBackground = null;
Color oldForeground = null;
boolean oldOpaque = false;
Icon oldIcon = null;
int oldHorizontalTextPosition = -1;
// hover and pressed background/foreground
TableColumn draggedColumn = header.getDraggedColumn();
Color background = null;
Color foreground = null;
if( draggedColumn != null && header.getTable().convertColumnIndexToView( draggedColumn.getModelIndex() ) == column ) {
if( draggedColumn != null &&
header.getTable().convertColumnIndexToView( draggedColumn.getModelIndex() )
== getColumn( x - header.getDraggedDistance(), w ) )
{
background = pressedBackground;
foreground = pressedForeground;
} else if( getRolloverColumn() == column ) {
} else if( getRolloverColumn() >= 0 && getRolloverColumn() == getColumn( x, w ) ) {
background = hoverBackground;
foreground = hoverForeground;
}
if( background != null ) {
if( oldBackground == null )
oldBackground = l.getBackground();
if( oldOpaque == null )
oldOpaque = l.isOpaque();
oldBackground = l.getBackground();
oldOpaque = l.isOpaque();
l.setBackground( FlatUIUtils.deriveColor( background, header.getBackground() ) );
l.setOpaque( true );
}
if( foreground != null ) {
if( oldForeground == null )
oldForeground = l.getForeground();
oldForeground = l.getForeground();
l.setForeground( FlatUIUtils.deriveColor( foreground, header.getForeground() ) );
}
// sort icon
if( sortIconPosition == SwingConstants.LEFT ) {
// left
if( oldHorizontalTextPosition < 0 )
// sort icon position
Icon icon = l.getIcon();
boolean isSortIcon = (icon != null && (icon == ascendingSortIcon || icon == descendingSortIcon));
if( isSortIcon ) {
if( sortIconPosition == SwingConstants.LEFT ) {
// left
oldHorizontalTextPosition = l.getHorizontalTextPosition();
l.setHorizontalTextPosition( SwingConstants.RIGHT );
} else if( sortIconPosition == SwingConstants.TOP || sortIconPosition == SwingConstants.BOTTOM ) {
// top or bottom
sortIcon = l.getIcon();
origBorder = l.getBorder();
l.setIcon( null );
l.setBorder( this );
l.setHorizontalTextPosition( SwingConstants.RIGHT );
} else if( sortIconPosition == SwingConstants.TOP || sortIconPosition == SwingConstants.BOTTOM ) {
// top or bottom
oldIcon = icon;
l.setIcon( null );
}
}
return l;
}
// paint renderer component
super.paintComponent( g, c, p, x, y, w, h, shouldValidate );
void reset() {
if( l == null )
return;
// paint top or bottom sort icon
if( isSortIcon && (sortIconPosition == SwingConstants.TOP || sortIconPosition == SwingConstants.BOTTOM) ) {
int xi = x + ((w - icon.getIconWidth()) / 2);
int yi = (sortIconPosition == SwingConstants.TOP)
? y + UIScale.scale( 1 )
: y + height - icon.getIconHeight()
- 1 // for gap
- (int) (1 * UIScale.getUserScaleFactor()); // for bottom border
icon.paintIcon( c, g, xi, yi );
}
if( oldBackground != null )
// restore modified renderer component properties
if( background != null ) {
l.setBackground( oldBackground );
if( oldForeground != null )
l.setForeground( oldForeground );
if( oldOpaque != null )
l.setOpaque( oldOpaque );
}
if( foreground != null )
l.setForeground( oldForeground );
if( oldIcon != null )
l.setIcon( oldIcon );
if( oldHorizontalTextPosition >= 0 )
l.setHorizontalTextPosition( oldHorizontalTextPosition );
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
if( origBorder != null )
origBorder.paintBorder( c, g, x, y, width, height );
/**
* Get column index for given coordinates.
*/
private int getColumn( int x, int width ) {
TableColumnModel columnModel = header.getColumnModel();
int columnCount = columnModel.getColumnCount();
boolean ltr = header.getComponentOrientation().isLeftToRight();
int cx = ltr ? 0 : getWidthInRightToLef();
if( sortIcon != null ) {
int xi = x + ((width - sortIcon.getIconWidth()) / 2);
int yi = (sortIconPosition == SwingConstants.TOP)
? y + UIScale.scale( 1 )
: y + height - sortIcon.getIconHeight()
- 1 // for gap
- (int) (1 * UIScale.getUserScaleFactor()); // for bottom border
sortIcon.paintIcon( c, g, xi, yi );
for( int i = 0; i < columnCount; i++ ) {
int cw = columnModel.getColumn( i ).getWidth();
if( x == cx - (ltr ? 0 : cw) && width == cw )
return i;
cx += ltr ? cw : -cw;
}
return -1;
}
@Override
public Insets getBorderInsets( Component c ) {
return (origBorder != null) ? origBorder.getBorderInsets( c ) : new Insets( 0, 0, 0, 0 );
}
@Override
public boolean isBorderOpaque() {
return (origBorder != null) ? origBorder.isBorderOpaque() : false;
// similar to JTableHeader.getWidthInRightToLeft()
private int getWidthInRightToLef() {
JTable table = header.getTable();
return (table != null && table.getAutoResizeMode() != JTable.AUTO_RESIZE_OFF)
? table.getWidth()
: header.getWidth();
}
}

View File

@@ -17,27 +17,39 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Map;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.JComponent;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.JViewport;
import javax.swing.LookAndFeel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTableUI;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.icons.FlatCheckBoxIcon;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.Graphics2DProxy;
@@ -81,6 +93,7 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault Table.selectionInactiveBackground Color
* @uiDefault Table.selectionInactiveForeground Color
* @uiDefault Table.paintOutsideAlternateRows boolean
* @uiDefault Table.editorSelectAllOnStartEditing boolean
*
* <!-- FlatTableCellBorder -->
*
@@ -116,6 +129,7 @@ public class FlatTableUI
private boolean oldShowHorizontalLines;
private boolean oldShowVerticalLines;
private Dimension oldIntercellSpacing;
private TableCellRenderer oldBooleanRenderer;
private PropertyChangeListener propertyChangeListener;
private Map<String, Object> oldStyleValues;
@@ -151,19 +165,35 @@ public class FlatTableUI
if( rowHeight > 0 )
LookAndFeel.installProperty( table, "rowHeight", UIScale.scale( rowHeight ) );
if( !showHorizontalLines ) {
FlatTablePropertyWatcher watcher = FlatTablePropertyWatcher.get( table );
if( watcher != null )
watcher.enabled = false;
if( !showHorizontalLines && (watcher == null || !watcher.showHorizontalLinesChanged) ) {
oldShowHorizontalLines = table.getShowHorizontalLines();
table.setShowHorizontalLines( false );
}
if( !showVerticalLines ) {
if( !showVerticalLines && (watcher == null || !watcher.showVerticalLinesChanged) ) {
oldShowVerticalLines = table.getShowVerticalLines();
table.setShowVerticalLines( false );
}
if( intercellSpacing != null ) {
if( intercellSpacing != null && (watcher == null || !watcher.intercellSpacingChanged) ) {
oldIntercellSpacing = table.getIntercellSpacing();
table.setIntercellSpacing( intercellSpacing );
}
if( watcher != null )
watcher.enabled = true;
else
table.addPropertyChangeListener( new FlatTablePropertyWatcher() );
// install boolean renderer
oldBooleanRenderer = table.getDefaultRenderer( Boolean.class );
if( oldBooleanRenderer instanceof UIResource )
table.setDefaultRenderer( Boolean.class, new FlatBooleanRenderer() );
else
oldBooleanRenderer = null;
}
@Override
@@ -177,15 +207,36 @@ public class FlatTableUI
oldStyleValues = null;
FlatTablePropertyWatcher watcher = FlatTablePropertyWatcher.get( table );
if( watcher != null )
watcher.enabled = false;
// restore old show horizontal/vertical lines (if not modified)
if( !showHorizontalLines && oldShowHorizontalLines && !table.getShowHorizontalLines() )
table.setShowHorizontalLines( true );
if( !showVerticalLines && oldShowVerticalLines && !table.getShowVerticalLines() )
table.setShowVerticalLines( true );
if( !showHorizontalLines && oldShowHorizontalLines && !table.getShowHorizontalLines() &&
(watcher == null || !watcher.showHorizontalLinesChanged) )
table.setShowHorizontalLines( true );
if( !showVerticalLines && oldShowVerticalLines && !table.getShowVerticalLines() &&
(watcher == null || !watcher.showVerticalLinesChanged) )
table.setShowVerticalLines( true );
// restore old intercell spacing (if not modified)
if( intercellSpacing != null && table.getIntercellSpacing().equals( intercellSpacing ) )
table.setIntercellSpacing( oldIntercellSpacing );
if( intercellSpacing != null && table.getIntercellSpacing().equals( intercellSpacing ) &&
(watcher == null || !watcher.intercellSpacingChanged) )
table.setIntercellSpacing( oldIntercellSpacing );
if( watcher != null )
watcher.enabled = true;
// uninstall boolean renderer
if( table.getDefaultRenderer( Boolean.class ) instanceof FlatBooleanRenderer ) {
if( oldBooleanRenderer instanceof Component ) {
// because the old renderer component was not attached to any component hierarchy,
// its UI was not yet updated, and it is necessary to do it here
SwingUtilities.updateComponentTreeUI( (Component) oldBooleanRenderer );
}
table.setDefaultRenderer( Boolean.class, oldBooleanRenderer );
}
oldBooleanRenderer = null;
}
@Override
@@ -238,6 +289,18 @@ public class FlatTableUI
};
}
@Override
protected void installKeyboardActions() {
super.installKeyboardActions();
if( UIManager.getBoolean( "Table.editorSelectAllOnStartEditing" ) ) {
// get shared action map, used for all tables
ActionMap map = SwingUtilities.getUIActionMap( table );
if( map != null )
StartEditingAction.install( map, "startEditing" );
}
}
/** @since 2 */
protected void installStyle() {
try {
@@ -467,4 +530,102 @@ public class FlatTableUI
}
}
}
//---- class FlatTablePropertyWatcher -------------------------------------
/**
* Listener that watches for change of some table properties from application code.
* This information is used in {@link FlatTableUI#installDefaults()} and
* {@link FlatTableUI#uninstallDefaults()} to decide whether FlatLaf modifies those properties.
* If they are modified in application code, FlatLaf no longer changes them.
*
* The listener is added once for each table, but never removed.
* So switching Laf/theme reuses existing listener.
*/
private static class FlatTablePropertyWatcher
implements PropertyChangeListener
{
boolean enabled = true;
boolean showHorizontalLinesChanged;
boolean showVerticalLinesChanged;
boolean intercellSpacingChanged;
static FlatTablePropertyWatcher get( JTable table ) {
for( PropertyChangeListener l : table.getPropertyChangeListeners() ) {
if( l instanceof FlatTablePropertyWatcher )
return (FlatTablePropertyWatcher) l;
}
return null;
}
//---- interface PropertyChangeListener ----
@Override
public void propertyChange( PropertyChangeEvent e ) {
if( !enabled )
return;
switch( e.getPropertyName() ) {
case "showHorizontalLines": showHorizontalLinesChanged = true; break;
case "showVerticalLines": showVerticalLinesChanged = true; break;
case "rowMargin": intercellSpacingChanged = true; break;
}
}
}
//---- class FlatBooleanRenderer ------------------------------------------
private static class FlatBooleanRenderer
extends DefaultTableCellRenderer
implements UIResource
{
private boolean selected;
FlatBooleanRenderer() {
setHorizontalAlignment( SwingConstants.CENTER );
setIcon( new FlatCheckBoxIcon() {
@Override
protected boolean isSelected( Component c ) {
return selected;
}
} );
}
@Override
protected void setValue( Object value ) {
selected = (value != null && (Boolean) value);
}
}
//---- class StartEditingAction -------------------------------------------
private static class StartEditingAction
extends FlatUIAction
{
static void install( ActionMap map, String key ) {
Action oldAction = map.get( key );
if( oldAction == null || oldAction instanceof StartEditingAction )
return; // not found or already installed
map.put( key, new StartEditingAction( oldAction ) );
}
private StartEditingAction( Action delegate ) {
super( delegate );
}
@Override
public void actionPerformed( ActionEvent e ) {
JTable table = (JTable) e.getSource();
Component oldEditorComp = table.getEditorComponent();
delegate.actionPerformed( e );
// select all text in editor if editing starts with F2 key
Component editorComp = table.getEditorComponent();
if( oldEditorComp == null && editorComp instanceof JTextField )
((JTextField)editorComp).selectAll();
}
}
}

View File

@@ -36,6 +36,7 @@ import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
@@ -46,9 +47,9 @@ import java.awt.event.WindowEvent;
import java.awt.geom.AffineTransform;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import javax.accessibility.AccessibleContext;
import javax.swing.BorderFactory;
import javax.swing.Box;
@@ -57,7 +58,6 @@ import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JMenuBar;
import javax.swing.JPanel;
@@ -66,6 +66,7 @@ import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.AbstractBorder;
import javax.swing.border.Border;
import javax.swing.plaf.ComponentUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.ui.FlatNativeWindowBorder.WindowTopBorder;
@@ -98,7 +99,6 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault TitlePane.showIconBesideTitle boolean
* @uiDefault TitlePane.menuBarTitleGap int
* @uiDefault TitlePane.menuBarTitleMinimumGap int
* @uiDefault TitlePane.menuBarResizeHeight int
* @uiDefault TitlePane.closeIcon Icon
* @uiDefault TitlePane.iconifyIcon Icon
* @uiDefault TitlePane.maximizeIcon Icon
@@ -109,7 +109,7 @@ import com.formdev.flatlaf.util.UIScale;
public class FlatTitlePane
extends JComponent
{
private static final String KEY_DEBUG_SHOW_RECTANGLES = "FlatLaf.debug.titlebar.showRectangles";
static final String KEY_DEBUG_SHOW_RECTANGLES = "FlatLaf.debug.titlebar.showRectangles";
/** @since 2.5 */ protected final Font titleFont;
protected final Color activeBackground;
@@ -131,7 +131,6 @@ public class FlatTitlePane
/** @since 2.4 */ protected final boolean showIconBesideTitle;
protected final int menuBarTitleGap;
/** @since 2.4 */ protected final int menuBarTitleMinimumGap;
/** @since 2.4 */ protected final int menuBarResizeHeight;
protected final JRootPane rootPane;
protected final String windowStyle;
@@ -150,6 +149,23 @@ public class FlatTitlePane
private final Handler handler;
/**
* This panel handles mouse events if FlatLaf window decorations are used
* without native window border. E.g. on Linux.
* <p>
* This panel usually has same bounds as the title pane,
* except if fullWindowContent mode is enabled.
* <p>
* This panel is not a child of the title pane.
* Instead it is added by FlatRootPaneUI to the layered pane at a layer
* under the title pane and under the frame content.
* The separation is necessary for fullWindowContent mode, where the title pane
* is layered over the frame content (for title pane buttons), but the mousePanel
* needs to be layered under the frame content so that components on content pane
* can receive mouse events when located in title area.
*/
final JPanel mouseLayer;
public FlatTitlePane( JRootPane rootPane ) {
this.rootPane = rootPane;
@@ -178,7 +194,6 @@ public class FlatTitlePane
showIconBesideTitle = FlatUIUtils.getSubUIBoolean( "TitlePane.showIconBesideTitle", windowStyle, false );
menuBarTitleGap = FlatUIUtils.getSubUIInt( "TitlePane.menuBarTitleGap", windowStyle, 40 );
menuBarTitleMinimumGap = FlatUIUtils.getSubUIInt( "TitlePane.menuBarTitleMinimumGap", windowStyle, 12 );
menuBarResizeHeight = FlatUIUtils.getSubUIInt( "TitlePane.menuBarResizeHeight", windowStyle, 4 );
handler = createHandler();
@@ -187,11 +202,10 @@ public class FlatTitlePane
addSubComponents();
activeChanged( true );
addMouseListener( handler );
addMouseMotionListener( handler );
// necessary for closing window with double-click on icon
iconLabel.addMouseListener( handler );
mouseLayer = new JPanel();
mouseLayer.setOpaque( false );
mouseLayer.addMouseListener( handler );
mouseLayer.addMouseMotionListener( handler );
applyComponentOrientation( rootPane.getComponentOrientation() );
}
@@ -234,6 +248,11 @@ public class FlatTitlePane
setLayout( new BorderLayout() {
@Override
public void layoutContainer( Container target ) {
if( isFullWindowContent() ) {
super.layoutContainer( target );
return;
}
// compute available bounds
Insets insets = target.getInsets();
int x = insets.left;
@@ -247,7 +266,7 @@ public class FlatTitlePane
int titleWidth = w - leftWidth - buttonsWidth;
int minTitleWidth = UIScale.scale( titleMinimumWidth );
// increase minimum width if icon is show besides the title
// increase minimum width if icon is shown besides the title
Icon icon = titleLabel.getIcon();
if( icon != null ) {
Insets iconInsets = iconLabel.getInsets();
@@ -295,6 +314,9 @@ public class FlatTitlePane
horizontalGlue.getWidth(), titleLabel.getHeight() );
}
}
// clear hit-test cache
lastCaptionHitTestTime = 0;
}
} );
@@ -338,6 +360,13 @@ public class FlatTitlePane
buttonPanel.add( restoreButton );
}
buttonPanel.add( closeButton );
ComponentListener l = new ComponentAdapter() {
@Override public void componentResized( ComponentEvent e ) { updateFullWindowContentButtonsBoundsProperty(); }
@Override public void componentMoved( ComponentEvent e ) { updateFullWindowContentButtonsBoundsProperty(); }
};
buttonPanel.addComponentListener( l );
addComponentListener( l );
}
protected JButton createButton( String iconKey, String accessibleName, ActionListener action ) {
@@ -417,7 +446,9 @@ public class FlatTitlePane
/** @since 3 */
protected void updateVisibility() {
titleLabel.setVisible( clientPropertyBoolean( rootPane, TITLE_BAR_SHOW_TITLE, true ) );
boolean isFullWindowContent = isFullWindowContent();
leftPanel.setVisible( !isFullWindowContent );
titleLabel.setVisible( clientPropertyBoolean( rootPane, TITLE_BAR_SHOW_TITLE, true ) && !isFullWindowContent );
closeButton.setVisible( clientPropertyBoolean( rootPane, TITLE_BAR_SHOW_CLOSE, true ) );
if( window instanceof Frame ) {
@@ -443,7 +474,7 @@ public class FlatTitlePane
// get window images
List<Image> images = null;
if( clientPropertyBoolean( rootPane, TITLE_BAR_SHOW_ICON, defaultShowIcon ) ) {
if( clientPropertyBoolean( rootPane, TITLE_BAR_SHOW_ICON, defaultShowIcon ) && !isFullWindowContent() ) {
images = window.getIconImages();
if( images.isEmpty() ) {
// search in owners
@@ -468,6 +499,13 @@ public class FlatTitlePane
updateNativeTitleBarHeightAndHitTestSpotsLater();
}
void updateFullWindowContentButtonsBoundsProperty() {
Rectangle bounds = isFullWindowContent()
? new Rectangle( SwingUtilities.convertPoint( buttonPanel, 0, 0, rootPane ), buttonPanel.getSize() )
: null;
rootPane.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS, bounds );
}
@Override
public void addNotify() {
super.addNotify();
@@ -522,6 +560,11 @@ public class FlatTitlePane
window.removeComponentListener( handler );
}
/** @since 3.4 */
protected boolean isFullWindowContent() {
return FlatRootPaneUI.isFullWindowContent( rootPane );
}
/**
* Returns whether this title pane currently has a visible and embedded menubar.
*/
@@ -533,6 +576,9 @@ public class FlatTitlePane
* Returns whether the menubar should be embedded into the title pane.
*/
protected boolean isMenuBarEmbedded() {
if( isFullWindowContent() )
return false;
// not storing value of "TitlePane.menuBarEmbedded" in class to allow changing at runtime
return FlatUIUtils.getBoolean( rootPane,
FlatSystemProperties.MENUBAR_EMBEDDED,
@@ -608,6 +654,10 @@ public class FlatTitlePane
doLayout();
}
void menuBarInvalidate() {
menuBarPlaceholder.invalidate();
}
@Override
public void paint( Graphics g ) {
super.paint( g );
@@ -616,21 +666,45 @@ public class FlatTitlePane
return;
if( debugTitleBarHeight > 0 ) {
// title bar height is measured from window top edge
int y = SwingUtilities.convertPoint( window, 0, debugTitleBarHeight, this ).y;
g.setColor( Color.green );
g.drawLine( 0, debugTitleBarHeight, getWidth(), debugTitleBarHeight );
g.drawLine( 0, y, getWidth(), y );
}
if( debugHitTestSpots != null ) {
for( Rectangle r : debugHitTestSpots )
paintRect( g, Color.red, r );
}
paintRect( g, Color.cyan, debugCloseButtonBounds );
paintRect( g, Color.blue, debugAppIconBounds );
paintRect( g, Color.blue, debugMinimizeButtonBounds );
paintRect( g, Color.magenta, debugMaximizeButtonBounds );
paintRect( g, Color.cyan, debugCloseButtonBounds );
g.setColor( Color.red );
debugPaintComponentWithMouseListener( g, Color.red, rootPane.getLayeredPane(), 0, 0 );
debugPaintRect( g, Color.blue, debugAppIconBounds );
debugPaintRect( g, Color.blue, debugMinimizeButtonBounds );
debugPaintRect( g, Color.magenta, debugMaximizeButtonBounds );
debugPaintRect( g, Color.cyan, debugCloseButtonBounds );
}
private void paintRect( Graphics g, Color color, Rectangle r ) {
private void debugPaintComponentWithMouseListener( Graphics g, Color color, Component c, int x, int y ) {
if( !c.isDisplayable() || !c.isVisible() || c == mouseLayer ||
c == iconifyButton || c == maximizeButton || c == restoreButton || c == closeButton )
return;
if( c.getMouseListeners().length > 0 ||
c.getMouseMotionListeners().length > 0 ||
c.getMouseWheelListeners().length > 0 )
{
g.drawRect( x, y, c.getWidth(), c.getHeight() );
return;
}
if( c instanceof Container ) {
Rectangle titlePaneBoundsOnWindow = SwingUtilities.convertRectangle( this, new Rectangle( getSize() ), window );
for( Component child : ((Container)c).getComponents() ) {
Rectangle compBoundsOnWindow = SwingUtilities.convertRectangle( c, new Rectangle( c.getSize() ), window );
if( compBoundsOnWindow.intersects( titlePaneBoundsOnWindow ) )
debugPaintComponentWithMouseListener( g, color, child, x + child.getX(), y + child.getY() );
}
}
}
private void debugPaintRect( Graphics g, Color color, Rectangle r ) {
if( r == null )
return;
@@ -641,6 +715,9 @@ public class FlatTitlePane
@Override
protected void paintComponent( Graphics g ) {
if( isFullWindowContent() )
return;
// not storing value of "TitlePane.unifiedBackground" in class to allow changing at runtime
g.setColor( (UIManager.getBoolean( "TitlePane.unifiedBackground" ) &&
clientPropertyColor( rootPane, TITLE_BAR_BACKGROUND, null ) == null)
@@ -835,10 +912,6 @@ public class FlatTitlePane
window.dispatchEvent( new WindowEvent( window, WindowEvent.WINDOW_CLOSING ) );
}
private boolean hasJBRCustomDecoration() {
return window != null && JBRCustomDecorations.hasCustomDecoration( window );
}
/**
* Returns whether windows uses native window border and has custom decorations enabled.
*/
@@ -866,11 +939,14 @@ public class FlatTitlePane
return;
int titleBarHeight = getHeight();
// title bar height must be measured from window top edge
// (when window is maximized, window y location is e.g. -11 and window top inset is 11)
for( Component c = this; c != window && c != null; c = c.getParent() )
titleBarHeight += c.getY();
// slightly reduce height so that component receives mouseExit events
if( titleBarHeight > 0 )
titleBarHeight--;
List<Rectangle> hitTestSpots = new ArrayList<>();
Rectangle appIconBounds = null;
if( !showIconBesideTitle && iconLabel.isVisible() ) {
@@ -896,10 +972,7 @@ public class FlatTitlePane
iconBounds.width += iconInsets.right;
}
if( hasJBRCustomDecoration() )
hitTestSpots.add( iconBounds );
else
appIconBounds = iconBounds;
appIconBounds = iconBounds;
} else if( showIconBesideTitle && titleLabel.getIcon() != null && titleLabel.getUI() instanceof FlatTitleLabelUI ) {
FlatTitleLabelUI ui = (FlatTitleLabelUI) titleLabel.getUI();
@@ -927,78 +1000,21 @@ public class FlatTitlePane
iconR.width += 2;
iconR.height += 2;
if( hasJBRCustomDecoration() )
hitTestSpots.add( iconR );
else
appIconBounds = iconR;
appIconBounds = iconR;
}
}
Rectangle r = getNativeHitTestSpot( buttonPanel );
if( r != null )
hitTestSpots.add( r );
JMenuBar menuBar = rootPane.getJMenuBar();
if( hasVisibleEmbeddedMenuBar( menuBar ) ) {
r = getNativeHitTestSpot( menuBar );
if( r != null ) {
// if frame is resizable and not maximized, make menu bar hit test spot smaller at top
// to have a small area above the menu bar to resize the window
if( window instanceof Frame && ((Frame)window).isResizable() && !isWindowMaximized() ) {
// limit to 8, because Windows does not use a larger height
int resizeHeight = UIScale.scale( Math.min( menuBarResizeHeight, 8 ) );
r.y += resizeHeight;
r.height -= resizeHeight;
}
int count = menuBar.getComponentCount();
for( int i = count - 1; i >= 0; i-- ) {
Component c = menuBar.getComponent( i );
if( c instanceof Box.Filler ||
(c instanceof JComponent && clientPropertyBoolean( (JComponent) c, COMPONENT_TITLE_BAR_CAPTION, false ) ) )
{
// If menu bar is embedded and contains a horizontal glue or caption component,
// then split the hit test spot so that
// the glue/caption component area can be used to move the window.
Point glueLocation = SwingUtilities.convertPoint( c, 0, 0, window );
int x2 = glueLocation.x + c.getWidth();
Rectangle r2;
if( getComponentOrientation().isLeftToRight() ) {
r2 = new Rectangle( x2, r.y, (r.x + r.width) - x2, r.height );
r.width = glueLocation.x - r.x;
} else {
r2 = new Rectangle( r.x, r.y, glueLocation.x - r.x, r.height );
r.width = (r.x + r.width) - x2;
r.x = x2;
}
if( r2.width > 0 )
hitTestSpots.add( r2 );
}
}
hitTestSpots.add( r );
}
}
// allow internal frames in layered pane to be moved/resized when placed over title bar
for( Component c : rootPane.getLayeredPane().getComponents() ) {
r = (c instanceof JInternalFrame) ? getNativeHitTestSpot( (JInternalFrame) c ) : null;
if( r != null )
hitTestSpots.add( r );
}
Rectangle minimizeButtonBounds = boundsInWindow( iconifyButton );
Rectangle maximizeButtonBounds = boundsInWindow( maximizeButton.isVisible() ? maximizeButton : restoreButton );
Rectangle closeButtonBounds = boundsInWindow( closeButton );
// clear hit-test cache
lastCaptionHitTestTime = 0;
FlatNativeWindowBorder.setTitleBarHeightAndHitTestSpots( window, titleBarHeight,
hitTestSpots, appIconBounds, minimizeButtonBounds, maximizeButtonBounds, closeButtonBounds );
this::captionHitTest, appIconBounds, minimizeButtonBounds, maximizeButtonBounds, closeButtonBounds );
debugTitleBarHeight = titleBarHeight;
debugHitTestSpots = hitTestSpots;
debugAppIconBounds = appIconBounds;
debugMinimizeButtonBounds = minimizeButtonBounds;
debugMaximizeButtonBounds = maximizeButtonBounds;
@@ -1013,18 +1029,101 @@ public class FlatTitlePane
: null;
}
protected Rectangle getNativeHitTestSpot( JComponent c ) {
Dimension size = c.getSize();
if( size.width <= 0 || size.height <= 0 )
return null;
/**
* Returns whether there is a component at the given location, that processes
* mouse events. E.g. buttons, menus, etc.
* <p>
* Note:
* <ul>
* <li>This method is invoked often when mouse is moved over title bar
* and should therefore return quickly.
* <li>This method is invoked on 'AWT-Windows' thread (not 'AWT-EventQueue' thread)
* while processing Windows messages.
* </ul>
*/
private boolean captionHitTest( Point pt ) {
// Windows invokes this method every ~200ms, even if the mouse has not moved
long time = System.currentTimeMillis();
if( pt.x == lastCaptionHitTestX && pt.y == lastCaptionHitTestY && time < lastCaptionHitTestTime + 300 ) {
lastCaptionHitTestTime = time;
return lastCaptionHitTestResult;
}
Point location = SwingUtilities.convertPoint( c, 0, 0, window );
Rectangle r = new Rectangle( location, size );
return r;
// convert pt from window coordinates to layeredPane coordinates
Component layeredPane = rootPane.getLayeredPane();
int x = pt.x;
int y = pt.y;
for( Component c = layeredPane; c != window && c != null; c = c.getParent() ) {
x -= c.getX();
y -= c.getY();
}
lastCaptionHitTestX = pt.x;
lastCaptionHitTestY = pt.y;
lastCaptionHitTestTime = time;
lastCaptionHitTestResult = isTitleBarCaptionAt( layeredPane, x, y );
return lastCaptionHitTestResult;
}
private boolean isTitleBarCaptionAt( Component c, int x, int y ) {
if( !c.isDisplayable() || !c.isVisible() || !c.contains( x, y ) || c == mouseLayer )
return true; // continue checking with next component
if( c.isEnabled() &&
(c.getMouseListeners().length > 0 ||
c.getMouseMotionListeners().length > 0) )
{
if( !(c instanceof JComponent) )
return false; // assume that this is not a caption because the component has mouse listeners
// check client property boolean value
Object caption = ((JComponent)c).getClientProperty( COMPONENT_TITLE_BAR_CAPTION );
if( caption instanceof Boolean )
return (boolean) caption;
// if component is not fully layouted, do not invoke function
// because it is too dangerous that the function tries to layout the component,
// which could cause a dead lock
if( !c.isValid() )
return false; // assume that this is not a caption because the component has mouse listeners
if( caption instanceof Function ) {
// check client property function value
@SuppressWarnings( "unchecked" )
Function<Point, Boolean> hitTest = (Function<Point, Boolean>) caption;
Boolean result = hitTest.apply( new Point( x, y ) );
if( result != null )
return result;
} else {
// check component UI
ComponentUI ui = JavaCompatibility2.getUI( (JComponent) c );
if( !(ui instanceof TitleBarCaptionHitTest) )
return false; // assume that this is not a caption because the component has mouse listeners
Boolean result = ((TitleBarCaptionHitTest)ui).isTitleBarCaptionAt( x, y );
if( result != null )
return result;
}
// else continue checking children
}
// check children
if( c instanceof Container ) {
for( Component child : ((Container)c).getComponents() ) {
if( !isTitleBarCaptionAt( child, x - child.getX(), y - child.getY() ) )
return false;
}
}
return true;
}
private int lastCaptionHitTestX;
private int lastCaptionHitTestY;
private long lastCaptionHitTestTime;
private boolean lastCaptionHitTestResult;
private int debugTitleBarHeight;
private List<Rectangle> debugHitTestSpots;
private Rectangle debugAppIconBounds;
private Rectangle debugMinimizeButtonBounds;
private Rectangle debugMaximizeButtonBounds;
@@ -1122,7 +1221,7 @@ public class FlatTitlePane
}
}
// compute icon width and gap (if icon is show besides the title)
// compute icon width and gap (if icon is shown besides the title)
int iconTextGap = 0;
int iconWidthAndGap = 0;
if( icon != null ) {
@@ -1131,7 +1230,7 @@ public class FlatTitlePane
iconWidthAndGap = icon.getIconWidth() + iconTextGap;
}
// layout title and icon (if show besides the title)
// layout title and icon (if shown besides the title)
String clippedText = SwingUtilities.layoutCompoundLabel( label, fontMetrics, text, icon,
label.getVerticalAlignment(), label.getHorizontalAlignment(),
label.getVerticalTextPosition(), label.getHorizontalTextPosition(),
@@ -1270,7 +1369,7 @@ debug*/
public void mouseClicked( MouseEvent e ) {
// on Linux, when using native library, the mouse clicked event
// is usually not sent and maximize/restore is done in mouse pressed event
// this check is here for the case that a mouse clicked event comes thru for some reason
// this check is here for the case that a mouse clicked event comes through for some reason
if( linuxNativeMove && SystemInfo.isLinux && FlatNativeLinuxLibrary.isWMUtilsSupported( window ) ) {
// see comment in mousePressed()
if( lastSingleClickWhen != 0 && (e.getWhen() - lastSingleClickWhen) <= getMultiClickInterval() ) {
@@ -1281,7 +1380,7 @@ debug*/
}
if( e.getClickCount() == 2 && SwingUtilities.isLeftMouseButton( e ) ) {
if( e.getSource() == iconLabel ) {
if( SwingUtilities.getDeepestComponentAt( FlatTitlePane.this, e.getX(), e.getY() ) == iconLabel ) {
// double-click on icon closes window
close();
} else if( !hasNativeCustomDecoration() ) {
@@ -1308,7 +1407,7 @@ debug*/
if( !SwingUtilities.isLeftMouseButton( e ) )
return;
dragOffset = SwingUtilities.convertPoint( FlatTitlePane.this, e.getPoint(), window );
dragOffset = SwingUtilities.convertPoint( mouseLayer, e.getPoint(), window );
linuxNativeMove = false;
// on Linux, move or maximize/restore window
@@ -1421,4 +1520,27 @@ debug*/
@Override public void componentMoved( ComponentEvent e ) {}
@Override public void componentHidden( ComponentEvent e ) {}
}
//---- interface TitleBarCaptionHitTest -----------------------------------
/**
* For custom components use {@link FlatClientProperties#COMPONENT_TITLE_BAR_CAPTION}
* instead of this interface.
*
* @since 3.4
*/
public interface TitleBarCaptionHitTest {
/**
* Invoked for a component that is enabled and has mouse listeners,
* to check whether it processes mouse input at the given x/y location.
* Useful for components that do not use mouse input on whole component bounds.
* E.g. a tabbed pane with a few tabs has some empty space beside the tabs
* that can be used to move the window.
*
* @return {@code true} if the component is not interested in mouse input at the given location
* {@code false} if the component wants process mouse input at the given location
* {@code null} if the component children should be checked
*/
Boolean isTitleBarCaptionAt( int x, int y );
}
}

View File

@@ -18,6 +18,7 @@ package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
@@ -173,6 +174,12 @@ public class FlatToolBarSeparatorUI
if( size != null )
return scale( size );
// get separator width
int separatorWidth = this.separatorWidth;
FlatToolBarUI toolBarUI = getToolBarUI( c );
if( toolBarUI != null && toolBarUI.separatorWidth != null )
separatorWidth = toolBarUI.separatorWidth;
// make sure that gap on left and right side of line have same size
int sepWidth = (scale( (separatorWidth - LINE_WIDTH) / 2 ) * 2) + scale( LINE_WIDTH );
@@ -196,6 +203,12 @@ public class FlatToolBarSeparatorUI
float lineWidth = scale( 1f );
float offset = scale( 2f );
// get separator color
Color separatorColor = this.separatorColor;
FlatToolBarUI toolBarUI = getToolBarUI( c );
if( toolBarUI != null && toolBarUI.separatorColor != null )
separatorColor = toolBarUI.separatorColor;
Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g );
g.setColor( separatorColor );
@@ -210,4 +223,11 @@ public class FlatToolBarSeparatorUI
private boolean isVertical( JComponent c ) {
return ((JToolBar.Separator)c).getOrientation() == SwingConstants.VERTICAL;
}
private FlatToolBarUI getToolBarUI( JComponent c ) {
Container parent = c.getParent();
return (parent instanceof JToolBar && ((JToolBar)parent).getUI() instanceof FlatToolBarUI)
? (FlatToolBarUI) ((JToolBar)parent).getUI()
: null;
}
}

View File

@@ -82,7 +82,7 @@ import com.formdev.flatlaf.util.UIScale;
*/
public class FlatToolBarUI
extends BasicToolBarUI
implements StyleableUI
implements StyleableUI, FlatTitlePane.TitleBarCaptionHitTest
{
/** @since 1.4 */ @Styleable protected boolean focusableButtons;
/** @since 2 */ @Styleable protected boolean arrowKeysOnlyNavigation;
@@ -93,6 +93,10 @@ public class FlatToolBarUI
@Styleable protected Insets borderMargins;
@Styleable protected Color gripColor;
// for FlatToolBarSeparatorUI
/** @since 3.3 */ @Styleable protected Integer separatorWidth;
/** @since 3.3 */ @Styleable protected Color separatorColor;
private FocusTraversalPolicy focusTraversalPolicy;
private Boolean oldFloatable;
private Map<String, Object> oldStyleValues;
@@ -449,6 +453,15 @@ public class FlatToolBarUI
: null;
}
//---- interface FlatTitlePane.TitleBarCaptionHitTest ----
/** @since 3.4 */
@Override
public Boolean isTitleBarCaptionAt( int x, int y ) {
// necessary because BasicToolBarUI adds some mouse listeners for dragging when toolbar is floatable
return null; // check children
}
//---- class FlatToolBarFocusTraversalPolicy ------------------------------
/**

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2024 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import java.beans.PropertyChangeListener;
import javax.swing.Action;
/**
* Base class for UI actions used in ActionMap.
* (similar to class sun.swing.UIAction)
*
* @author Karl Tauber
* @since 3.4
*/
public abstract class FlatUIAction
implements Action
{
protected final String name;
protected final Action delegate;
protected FlatUIAction( String name ) {
this.name = name;
this.delegate = null;
}
protected FlatUIAction( Action delegate ) {
this.name = null;
this.delegate = delegate;
}
@Override
public Object getValue( String key ) {
if( key == NAME && delegate == null )
return name;
return (delegate != null) ? delegate.getValue( key ) : null;
}
@Override
public boolean isEnabled() {
return (delegate != null) ? delegate.isEnabled() : true;
}
// do nothing in following methods because this class is immutable
@Override public void putValue( String key, Object value ) {}
@Override public void setEnabled( boolean b ) {}
@Override public void addPropertyChangeListener( PropertyChangeListener listener ) {}
@Override public void removePropertyChangeListener( PropertyChangeListener listener ) {}
}

View File

@@ -60,6 +60,7 @@ import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.tree.DefaultTreeCellEditor;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatIntelliJLaf;
import com.formdev.flatlaf.FlatLaf;
@@ -299,15 +300,14 @@ public class FlatUIUtils
if( c == null )
return false;
// check whether used in cell editor (check 3 levels up)
Component c2 = c;
for( int i = 0; i <= 2 && c2 != null; i++ ) {
Container parent = c2.getParent();
if( parent instanceof JTable && ((JTable)parent).getEditorComponent() == c2 )
return true;
// check whether used as table cell editor
Container parent = c.getParent();
if( parent instanceof JTable && ((JTable)parent).getEditorComponent() == c )
return true;
c2 = parent;
}
// check whether used as tree cell editor
if( parent instanceof DefaultTreeCellEditor.EditorContainer )
return true;
// check whether used as cell editor
// Table.editor is set in JTable.GenericEditor constructor
@@ -734,7 +734,7 @@ public class FlatUIUtils
}
/**
* Creates a (rounded) rectangle used to paint components (border, background, etc).
* Creates a (rounded) rectangle used to paint components (border, background, etc.).
* The given arc diameter is limited to min(width,height).
*/
public static Shape createComponentRectangle( float x, float y, float w, float h, float arc ) {

View File

@@ -18,7 +18,6 @@ package com.formdev.flatlaf.ui;
import java.awt.Component;
import java.awt.Graphics;
import java.lang.reflect.Method;
import javax.swing.JComponent;
import javax.swing.JViewport;
import javax.swing.plaf.ComponentUI;
@@ -48,14 +47,9 @@ public class FlatViewportUI
Component view = ((JViewport)c).getView();
if( view instanceof JComponent ) {
try {
Method m = view.getClass().getMethod( "getUI" );
Object ui = m.invoke( view );
if( ui instanceof ViewportPainter )
((ViewportPainter)ui).paintViewport( g, (JComponent) view, (JViewport) c );
} catch( Exception ex ) {
// ignore
}
ComponentUI ui = JavaCompatibility2.getUI( (JComponent) view );
if( ui instanceof ViewportPainter )
((ViewportPainter)ui).paintViewport( g, (JComponent) view, (JViewport) c );
}
}

View File

@@ -29,8 +29,8 @@ import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.Timer;
@@ -159,7 +159,7 @@ class FlatWindowsNativeWindowBorder
}
@Override
public void updateTitleBarInfo( Window window, int titleBarHeight, List<Rectangle> hitTestSpots,
public void updateTitleBarInfo( Window window, int titleBarHeight, Predicate<Point> captionHitTestCallback,
Rectangle appIconBounds, Rectangle minimizeButtonBounds, Rectangle maximizeButtonBounds,
Rectangle closeButtonBounds )
{
@@ -168,7 +168,7 @@ class FlatWindowsNativeWindowBorder
return;
wndProc.titleBarHeight = titleBarHeight;
wndProc.hitTestSpots = hitTestSpots.toArray( new Rectangle[hitTestSpots.size()] );
wndProc.captionHitTestCallback = captionHitTestCallback;
wndProc.appIconBounds = cloneRectange( appIconBounds );
wndProc.minimizeButtonBounds = cloneRectange( minimizeButtonBounds );
wndProc.maximizeButtonBounds = cloneRectange( maximizeButtonBounds );
@@ -288,8 +288,8 @@ class FlatWindowsNativeWindowBorder
private final long hwnd;
// Swing coordinates/values may be scaled on a HiDPI screen
private int titleBarHeight;
private Rectangle[] hitTestSpots;
private int titleBarHeight; // measured from window top edge, which may be out-of-screen if maximized
private Predicate<Point> captionHitTestCallback;
private Rectangle appIconBounds;
private Rectangle minimizeButtonBounds;
private Rectangle maximizeButtonBounds;
@@ -340,50 +340,61 @@ class FlatWindowsNativeWindowBorder
private int onNcHitTest( int x, int y, boolean isOnResizeBorder ) {
// scale-down mouse x/y because Swing coordinates/values may be scaled on a HiDPI screen
Point pt = scaleDown( x, y );
int sx = pt.x;
int sy = pt.y;
// return HTSYSMENU if mouse is over application icon
// - left-click on HTSYSMENU area shows system menu
// - double-left-click sends WM_CLOSE
if( contains( appIconBounds, sx, sy ) )
if( contains( appIconBounds, pt ) )
return HTSYSMENU;
// return HTMINBUTTON if mouse is over minimize button
// - hovering mouse over HTMINBUTTON area shows tooltip on Windows 10/11
if( contains( minimizeButtonBounds, sx, sy ) )
if( contains( minimizeButtonBounds, pt ) )
return HTMINBUTTON;
// return HTMAXBUTTON if mouse is over maximize/restore button
// - hovering mouse over HTMAXBUTTON area shows tooltip on Windows 10
// - hovering mouse over HTMAXBUTTON area shows snap layouts menu on Windows 11
// https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/apply-snap-layout-menu
if( contains( maximizeButtonBounds, sx, sy ) )
if( contains( maximizeButtonBounds, pt ) )
return HTMAXBUTTON;
// return HTCLOSE if mouse is over close button
// - hovering mouse over HTCLOSE area shows tooltip on Windows 10/11
if( contains( closeButtonBounds, sx, sy ) )
if( contains( closeButtonBounds, pt ) )
return HTCLOSE;
boolean isOnTitleBar = (sy < titleBarHeight);
// 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);
if( isOnTitleBar ) {
// use a second reference to the array to avoid that it can be changed
// in another thread while processing the array
Rectangle[] hitTestSpots2 = hitTestSpots;
for( Rectangle spot : hitTestSpots2 ) {
if( spot.contains( sx, sy ) )
// return HTCLIENT if mouse is over any Swing component in title bar
// that processes mouse events (e.g. buttons, menus, etc)
// - Windows ignores mouse events in this area
try {
if( captionHitTestCallback != null && !captionHitTestCallback.test( pt ) )
return HTCLIENT;
} catch( Throwable ex ) {
// ignore
}
return isOnResizeBorder ? HTTOP : HTCAPTION;
// return HTCAPTION if mouse is over title bar
// - right-click shows system menu
// - double-left-click maximizes/restores window size
return HTCAPTION;
}
return isOnResizeBorder ? HTTOP : HTCLIENT;
// return HTCLIENT
// - Windows ignores mouse events in this area
return HTCLIENT;
}
private boolean contains( Rectangle rect, int x, int y ) {
return (rect != null && rect.contains( x, y ) );
private boolean contains( Rectangle rect, Point pt ) {
return (rect != null && rect.contains( pt ) );
}
/**

View File

@@ -0,0 +1,219 @@
/*
* Copyright 2024 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import javax.swing.JComponent;
import javax.swing.JRootPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.util.SystemInfo;
/**
* @author Karl Tauber
*/
class FullWindowContentSupport
{
private static final String KEY_DEBUG_SHOW_PLACEHOLDERS = "FlatLaf.debug.panel.showPlaceholders";
private static ArrayList<WeakReference<JComponent>> placeholders = new ArrayList<>();
static Dimension getPlaceholderPreferredSize( JComponent c, String options ) {
JRootPane rootPane;
Rectangle bounds;
if( !options.startsWith( SystemInfo.isMacOS ? "mac" : "win" ) ||
!c.isDisplayable() ||
(rootPane = SwingUtilities.getRootPane( c )) == null ||
(bounds = (Rectangle) rootPane.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS )) == null )
return new Dimension( 0, 0 );
if( options.length() > 3 ) {
if( (options.contains( "leftToRight" ) && !c.getComponentOrientation().isLeftToRight()) ||
(options.contains( "rightToLeft" ) && c.getComponentOrientation().isLeftToRight()) )
return new Dimension( 0, 0 );
}
// On macOS, the client property is updated very late when toggling full screen,
// which results in "jumping" layout after full screen toggle finished.
// To avoid that, get up-to-date buttons bounds from macOS.
if( SystemInfo.isMacFullWindowContentSupported && FlatNativeMacLibrary.isLoaded() ) {
Rectangle r = FlatNativeMacLibrary.getWindowButtonsBounds( SwingUtilities.windowForComponent( c ) );
if( r != null )
bounds = r;
}
int width = bounds.width;
int height = bounds.height;
if( options.length() > 3 ) {
if( width == 0 && options.contains( "zeroInFullScreen" ) )
height = 0;
if( options.contains( "horizontal" ) )
height = 0;
if( options.contains( "vertical" ) )
width = 0;
}
return new Dimension( width, height );
}
static void registerPlaceholder( JComponent c ) {
synchronized( placeholders ) {
if( indexOfPlaceholder( c ) < 0 )
placeholders.add( new WeakReference<>( c ) );
}
}
static void unregisterPlaceholder( JComponent c ) {
synchronized( placeholders ) {
int index = indexOfPlaceholder( c );
if( index >= 0 )
placeholders.remove( index );
}
}
private static int indexOfPlaceholder( JComponent c ) {
int size = placeholders.size();
for( int i = 0; i < size; i++ ) {
if( placeholders.get( i ).get() == c )
return i;
}
return -1;
}
static void revalidatePlaceholders( Component container ) {
synchronized( placeholders ) {
if( placeholders.isEmpty() )
return;
for( Iterator<WeakReference<JComponent>> it = placeholders.iterator(); it.hasNext(); ) {
WeakReference<JComponent> ref = it.next();
JComponent c = ref.get();
// remove already released placeholder
if( c == null ) {
it.remove();
continue;
}
// revalidate placeholder if is in given container
if( SwingUtilities.isDescendingFrom( c, container ) )
c.revalidate();
}
}
}
static ComponentListener macInstallListeners( JRootPane rootPane ) {
ComponentListener l = new ComponentAdapter() {
boolean lastFullScreen;
@Override
public void componentResized( ComponentEvent e ) {
Window window = SwingUtilities.windowForComponent( rootPane );
if( window == null )
return;
boolean fullScreen = FlatNativeMacLibrary.isLoaded() && FlatNativeMacLibrary.isWindowFullScreen( window );
if( fullScreen == lastFullScreen )
return;
lastFullScreen = fullScreen;
macUpdateFullWindowContentButtonsBoundsProperty( rootPane );
}
};
rootPane.addComponentListener( l );
return l;
}
static void macUninstallListeners( JRootPane rootPane, ComponentListener l ) {
if( l != null )
rootPane.removeComponentListener( l );
}
static void macUpdateFullWindowContentButtonsBoundsProperty( JRootPane rootPane ) {
if( !SystemInfo.isMacFullWindowContentSupported || !rootPane.isDisplayable() )
return;
Rectangle bounds = null;
if( FlatClientProperties.clientPropertyBoolean( rootPane, "apple.awt.fullWindowContent", false ) ) {
bounds = FlatNativeMacLibrary.isLoaded()
? FlatNativeMacLibrary.getWindowButtonsBounds( SwingUtilities.windowForComponent( rootPane ) )
: new Rectangle( 68, 28 ); // default size
}
rootPane.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS, bounds );
}
static void macUninstallFullWindowContentButtonsBoundsProperty( JRootPane rootPane ) {
if( !SystemInfo.isMacFullWindowContentSupported )
return;
rootPane.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS, null );
}
static void debugPaint( Graphics g, JComponent c ) {
if( !UIManager.getBoolean( KEY_DEBUG_SHOW_PLACEHOLDERS ) )
return;
int width = c.getWidth();
int height = c.getHeight();
if( width <= 0 || height <= 0 )
return;
// draw red figure
g.setColor( Color.red );
debugPaintRect( g, new Rectangle( width, height ) );
// draw magenta figure if buttons bounds are not equal to placeholder bounds
JRootPane rootPane;
Rectangle bounds;
if( (rootPane = SwingUtilities.getRootPane( c )) != null &&
(bounds = (Rectangle) rootPane.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS )) != null &&
(bounds.width != width || bounds.height != height) )
{
g.setColor( Color.magenta );
debugPaintRect( g, SwingUtilities.convertRectangle( rootPane, bounds, c ) );
}
}
private static void debugPaintRect( Graphics g, Rectangle r ) {
// draw rectangle
g.drawRect( r.x, r.y, r.width - 1, r.height - 1 );
// draw diagonal cross
int x2 = r.x + r.width - 1;
int y2 = r.y + r.height - 1;
Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g );
g.drawLine( r.x, r.y, x2, y2 );
g.drawLine( r.x, y2, x2, r.y );
FlatUIUtils.resetRenderingHints( g, oldRenderingHints );
}
}

View File

@@ -1,306 +0,0 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.beans.PropertyChangeListener;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import javax.swing.JRootPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.BorderUIResource;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.SystemInfo;
/**
* Support for custom window decorations provided by JetBrains Runtime (based on OpenJDK).
* Requires that the application runs on Windows 10 in a JetBrains Runtime 11 or later.
* <ul>
* <li><a href="https://confluence.jetbrains.com/display/JBR/JetBrains+Runtime">https://confluence.jetbrains.com/display/JBR/JetBrains+Runtime</a></li>
* <li><a href="https://github.com/JetBrains/JetBrainsRuntime">https://github.com/JetBrains/JetBrainsRuntime</a></li>
* </ul>
*
* @author Karl Tauber
*/
public class JBRCustomDecorations
{
private static Boolean supported;
private static Method Window_hasCustomDecoration;
private static Method Window_setHasCustomDecoration;
private static Method WWindowPeer_setCustomDecorationTitleBarHeight;
private static Method WWindowPeer_setCustomDecorationHitTestSpots;
private static Method AWTAccessor_getComponentAccessor;
private static Method AWTAccessor_ComponentAccessor_getPeer;
public static boolean isSupported() {
initialize();
return supported;
}
static Object install( JRootPane rootPane ) {
if( !isSupported() )
return null;
// check whether root pane already has a parent, which is the case when switching LaF
Container parent = rootPane.getParent();
if( parent != null ) {
if( parent instanceof Window )
FlatNativeWindowBorder.install( (Window) parent );
return null;
}
// Use hierarchy listener to wait until the root pane is added to a window.
// Enabling JBR decorations must be done very early, probably before
// window becomes displayable (window.isDisplayable()). Tried also using
// "ancestor" property change event on root pane, but this is invoked too late.
HierarchyListener addListener = new HierarchyListener() {
@Override
public void hierarchyChanged( HierarchyEvent e ) {
if( e.getChanged() != rootPane || (e.getChangeFlags() & HierarchyEvent.PARENT_CHANGED) == 0 )
return;
Container parent = e.getChangedParent();
if( parent instanceof Window )
FlatNativeWindowBorder.install( (Window) parent );
// remove listener since it is actually not possible to uninstall JBR decorations
// use invokeLater to remove listener to avoid that listener
// is removed while listener queue is processed
EventQueue.invokeLater( () -> {
rootPane.removeHierarchyListener( this );
} );
}
};
rootPane.addHierarchyListener( addListener );
return addListener;
}
static void uninstall( JRootPane rootPane, Object data ) {
// remove listener (if not yet done)
if( data instanceof HierarchyListener )
rootPane.removeHierarchyListener( (HierarchyListener) data );
// since it is actually not possible to uninstall JBR decorations,
// simply reduce titleBarHeight so that it is still possible to resize window
// and remove hitTestSpots
Container parent = rootPane.getParent();
if( parent instanceof Window )
setHasCustomDecoration( (Window) parent, false );
}
static boolean hasCustomDecoration( Window window ) {
if( !isSupported() )
return false;
try {
return (Boolean) Window_hasCustomDecoration.invoke( window );
} catch( Exception ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
return false;
}
}
static void setHasCustomDecoration( Window window, boolean hasCustomDecoration ) {
if( !isSupported() )
return;
try {
if( hasCustomDecoration )
Window_setHasCustomDecoration.invoke( window );
else
setTitleBarHeightAndHitTestSpots( window, 4, Collections.emptyList() );
} catch( Exception ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
static void setTitleBarHeightAndHitTestSpots( Window window, int titleBarHeight, List<Rectangle> hitTestSpots ) {
if( !isSupported() )
return;
try {
Object compAccessor = AWTAccessor_getComponentAccessor.invoke( null );
Object peer = AWTAccessor_ComponentAccessor_getPeer.invoke( compAccessor, window );
WWindowPeer_setCustomDecorationTitleBarHeight.invoke( peer, titleBarHeight );
WWindowPeer_setCustomDecorationHitTestSpots.invoke( peer, hitTestSpots );
} catch( Exception ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
private static void initialize() {
if( supported != null )
return;
supported = false;
// requires JetBrains Runtime 11 and Windows 10
if( !SystemInfo.isJetBrainsJVM_11_orLater || !SystemInfo.isWindows_10_orLater )
return;
try {
Class<?> awtAcessorClass = Class.forName( "sun.awt.AWTAccessor" );
Class<?> compAccessorClass = Class.forName( "sun.awt.AWTAccessor$ComponentAccessor" );
AWTAccessor_getComponentAccessor = awtAcessorClass.getDeclaredMethod( "getComponentAccessor" );
AWTAccessor_ComponentAccessor_getPeer = compAccessorClass.getDeclaredMethod( "getPeer", Component.class );
Class<?> peerClass = Class.forName( "sun.awt.windows.WWindowPeer" );
WWindowPeer_setCustomDecorationTitleBarHeight = peerClass.getDeclaredMethod( "setCustomDecorationTitleBarHeight", int.class );
WWindowPeer_setCustomDecorationHitTestSpots = peerClass.getDeclaredMethod( "setCustomDecorationHitTestSpots", List.class );
WWindowPeer_setCustomDecorationTitleBarHeight.setAccessible( true );
WWindowPeer_setCustomDecorationHitTestSpots.setAccessible( true );
Window_hasCustomDecoration = Window.class.getDeclaredMethod( "hasCustomDecoration" );
Window_setHasCustomDecoration = Window.class.getDeclaredMethod( "setHasCustomDecoration" );
Window_hasCustomDecoration.setAccessible( true );
Window_setHasCustomDecoration.setAccessible( true );
supported = true;
} catch( Exception ex ) {
// ignore
}
}
//---- class JBRWindowTopBorder -------------------------------------------
static class JBRWindowTopBorder
extends BorderUIResource.EmptyBorderUIResource
{
private static JBRWindowTopBorder instance;
private final Color activeLightColor = new Color( 0x707070 );
private final Color activeDarkColor = new Color( 0x2D2E2F );
private final Color inactiveLightColor = new Color( 0xaaaaaa );
private final Color inactiveDarkColor = new Color( 0x494A4B );
private boolean colorizationAffectsBorders;
private Color activeColor;
static JBRWindowTopBorder getInstance() {
if( instance == null )
instance = new JBRWindowTopBorder();
return instance;
}
JBRWindowTopBorder() {
super( 1, 0, 0, 0 );
update();
installListeners();
}
void update() {
colorizationAffectsBorders = isColorizationColorAffectsBorders();
activeColor = calculateActiveBorderColor();
}
void installListeners() {
Toolkit toolkit = Toolkit.getDefaultToolkit();
toolkit.addPropertyChangeListener( "win.dwm.colorizationColor.affects.borders", e -> {
colorizationAffectsBorders = isColorizationColorAffectsBorders();
activeColor = calculateActiveBorderColor();
} );
PropertyChangeListener l = e -> {
activeColor = calculateActiveBorderColor();
};
toolkit.addPropertyChangeListener( "win.dwm.colorizationColor", l );
toolkit.addPropertyChangeListener( "win.dwm.colorizationColorBalance", l );
toolkit.addPropertyChangeListener( "win.frame.activeBorderColor", l );
}
boolean isColorizationColorAffectsBorders() {
Object value = Toolkit.getDefaultToolkit().getDesktopProperty( "win.dwm.colorizationColor.affects.borders" );
return (value instanceof Boolean) ? (Boolean) value : true;
}
Color getColorizationColor() {
return (Color) Toolkit.getDefaultToolkit().getDesktopProperty( "win.dwm.colorizationColor" );
}
int getColorizationColorBalance() {
Object value = Toolkit.getDefaultToolkit().getDesktopProperty( "win.dwm.colorizationColorBalance" );
return (value instanceof Integer) ? (Integer) value : -1;
}
private Color calculateActiveBorderColor() {
if( !colorizationAffectsBorders )
return null;
Color colorizationColor = getColorizationColor();
if( colorizationColor != null ) {
int colorizationColorBalance = getColorizationColorBalance();
if( colorizationColorBalance < 0 || colorizationColorBalance > 100 )
colorizationColorBalance = 100;
if( colorizationColorBalance == 0 )
return new Color( 0xD9D9D9 );
if( colorizationColorBalance == 100 )
return colorizationColor;
float alpha = colorizationColorBalance / 100.0f;
float remainder = 1 - alpha;
int r = Math.round( colorizationColor.getRed() * alpha + 0xD9 * remainder );
int g = Math.round( colorizationColor.getGreen() * alpha + 0xD9 * remainder );
int b = Math.round( colorizationColor.getBlue() * alpha + 0xD9 * remainder );
// avoid potential IllegalArgumentException in Color constructor
r = Math.min( Math.max( r, 0 ), 255 );
g = Math.min( Math.max( g, 0 ), 255 );
b = Math.min( Math.max( b, 0 ), 255 );
return new Color( r, g, b );
}
Color activeBorderColor = (Color) Toolkit.getDefaultToolkit().getDesktopProperty( "win.frame.activeBorderColor" );
return (activeBorderColor != null) ? activeBorderColor : UIManager.getColor( "MenuBar.borderColor" );
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
Window window = SwingUtilities.windowForComponent( c );
boolean active = window != null && window.isActive();
boolean dark = FlatLaf.isLafDark();
g.setColor( active
? (activeColor != null ? activeColor : (dark ? activeDarkColor : activeLightColor))
: (dark ? inactiveDarkColor : inactiveLightColor) );
HiDPIUtils.paintAtScale1x( (Graphics2D) g, x, y, width, height, this::paintImpl );
}
private void paintImpl( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) {
g.fillRect( x, y, width, 1 );
}
void repaintBorder( Component c ) {
c.repaint( 0, 0, c.getWidth(), 1 );
}
}
}

View File

@@ -0,0 +1,153 @@
/*
* Copyright 2024 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import java.io.File;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.filechooser.FileSystemView;
import javax.swing.plaf.ComponentUI;
import javax.swing.text.JTextComponent;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.SystemInfo;
/**
* Provides Java version compatibility methods.
* <p>
* WARNING: This is private API and may change.
*
* @author Karl Tauber
* @since 3.3
*/
public class JavaCompatibility2
{
private static boolean getUIMethodInitialized;
private static MethodHandle getUIMethod;
/**
* Java 8: getUI() method on various components (e.g. JButton, JList, etc)
* <br>
* Java 9: javax.swing.JComponent.getUI()
*/
public static ComponentUI getUI( JComponent c ) {
try {
// Java 9+
if( SystemInfo.isJava_9_orLater ) {
if( !getUIMethodInitialized ) {
getUIMethodInitialized = true;
try {
MethodType mt = MethodType.methodType( ComponentUI.class, new Class[0] );
getUIMethod = MethodHandles.publicLookup().findVirtual( JComponent.class, "getUI", mt );
} catch( Exception ex ) {
// ignore
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
if( getUIMethod != null )
return (ComponentUI) getUIMethod.invoke( c );
}
// components often used (e.g. as view in scroll panes)
if( c instanceof JPanel )
return ((JPanel)c).getUI();
if( c instanceof JList )
return ((JList<?>)c).getUI();
if( c instanceof JTable )
return ((JTable)c).getUI();
if( c instanceof JTree )
return ((JTree)c).getUI();
if( c instanceof JTextComponent )
return ((JTextComponent)c).getUI();
// Java 8 and fallback
Method m = c.getClass().getMethod( "getUI" );
return (ComponentUI) m.invoke( c );
} catch( Throwable ex ) {
// ignore
return null;
}
}
/**
* Java 8 - 11 on Windows: sun.awt.shell.ShellFolder.get( "fileChooserShortcutPanelFolders" )
* <br>
* Java 12: javax.swing.filechooser.FileSystemView.getChooserShortcutPanelFiles()
*
* @since 3.4
*/
public static File[] getChooserShortcutPanelFiles( FileSystemView fsv ) {
try {
if( SystemInfo.isJava_12_orLater ) {
Method m = fsv.getClass().getMethod( "getChooserShortcutPanelFiles" );
File[] files = (File[]) m.invoke( fsv );
// on macOS and Linux, files consists only of the user home directory
if( files.length == 1 && files[0].equals( new File( System.getProperty( "user.home" ) ) ) )
files = new File[0];
return files;
} else if( SystemInfo.isWindows ) {
Class<?> cls = Class.forName( "sun.awt.shell.ShellFolder" );
Method m = cls.getMethod( "get", String.class );
return (File[]) m.invoke( null, "fileChooserShortcutPanelFolders" );
}
} catch( IllegalAccessException ex ) {
// do not log because access may be denied via VM option '--illegal-access=deny'
} catch( Exception ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
// fallback
return new File[0];
}
/**
* Java 8: sun.awt.shell.ShellFolder.get( "fileChooserComboBoxFolders" )
* <br>
* Java 9: javax.swing.filechooser.FileSystemView.getChooserComboBoxFiles()
*
* @since 3.4
*/
public static File[] getChooserComboBoxFiles( FileSystemView fsv ) {
try {
if( SystemInfo.isJava_9_orLater ) {
Method m = fsv.getClass().getMethod( "getChooserComboBoxFiles" );
return (File[]) m.invoke( fsv );
} else {
Class<?> cls = Class.forName( "sun.awt.shell.ShellFolder" );
Method m = cls.getMethod( "get", String.class );
return (File[]) m.invoke( null, "fileChooserComboBoxFolders" );
}
} catch( IllegalAccessException ex ) {
// do not log because access may be denied via VM option '--illegal-access=deny'
} catch( Exception ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
// fallback
return new File[0];
}
}

View File

@@ -80,7 +80,7 @@ public class FontUtils
/**
* Loads a font family previously registered via {@link #registerFontFamilyLoader(String, Runnable)}.
* If the family is already loaded or no londer is registered for that family, nothing happens.
* If the family is already loaded or no loader is registered for that family, nothing happens.
*/
public static void loadFontFamily( String family ) {
if( !hasLoaders() )
@@ -109,7 +109,7 @@ public class FontUtils
}
/**
* Returns all font familiy names available in the graphics environment.
* Returns all font family names available in the graphics environment.
* This invokes {@link GraphicsEnvironment#getAvailableFontFamilyNames()} and
* appends families registered for lazy loading via {@link #registerFontFamilyLoader(String, Runnable)}
* to the result.

View File

@@ -192,7 +192,8 @@ public class HiDPIUtils
case "Inter":
case "Inter Light":
case "Inter Semi Bold":
case "Inter Semi Bold": // Inter v3
case "Inter SemiBold": // Inter v4
case "Roboto":
case "Roboto Light":
case "Roboto Medium":

View File

@@ -116,7 +116,11 @@ public class NativeLibrary
try {
// for development environment
if( "file".equals( libraryUrl.getProtocol() ) ) {
File libraryFile = new File( libraryUrl.getPath() );
String binPath = libraryUrl.getPath();
String srcPath = binPath.replace( "flatlaf-core/bin/main/", "flatlaf-core/src/main/resources/" );
File libraryFile = new File( srcPath ); // use from 'src' folder if available
if( !libraryFile.isFile() )
libraryFile = new File( binPath ); // use from 'bin' or 'output' folder if available
if( libraryFile.isFile() ) {
// load library without copying
System.load( libraryFile.getCanonicalPath() );

View File

@@ -204,7 +204,7 @@ public class UIScale
if( SystemInfo.isWindows ) {
// Special handling for Windows to be compatible with OS scaling,
// which distinguish between "screen scaling" and "text scaling".
// - Windows "screen scaling" scales everything (text, icon, gaps, etc)
// - Windows "screen scaling" scales everything (text, icon, gaps, etc.)
// and may have different scaling factors for each screen.
// - Windows "text scaling" increases only the font size, but on all screens.
//

View File

@@ -246,6 +246,7 @@ PasswordField.revealIconColor = @foreground
#---- Popup ----
[mac]Popup.roundedBorderWidth = 1
Popup.dropShadowColor = #000
Popup.dropShadowOpacity = 0.25

View File

@@ -289,6 +289,7 @@ ComboBox.popupInsets = 0,0,0,0
ComboBox.selectionInsets = 0,0,0,0
ComboBox.selectionArc = 0
ComboBox.borderCornerRadius = $Popup.borderCornerRadius
[mac]ComboBox.roundedBorderWidth = $Popup.roundedBorderWidth
#---- Component ----
@@ -505,6 +506,7 @@ PasswordField.revealIcon = com.formdev.flatlaf.icons.FlatRevealIcon
#---- Popup ----
Popup.borderCornerRadius = 4
[mac]Popup.roundedBorderWidth = 0
Popup.dropShadowPainted = true
Popup.dropShadowInsets = -4,-4,4,4
@@ -514,6 +516,7 @@ Popup.dropShadowInsets = -4,-4,4,4
PopupMenu.border = com.formdev.flatlaf.ui.FlatPopupMenuBorder
PopupMenu.borderInsets = 4,1,4,1
PopupMenu.borderCornerRadius = $Popup.borderCornerRadius
[mac]PopupMenu.roundedBorderWidth = $Popup.roundedBorderWidth
PopupMenu.background = @menuBackground
PopupMenu.scrollArrowColor = @buttonArrowColor
@@ -597,10 +600,15 @@ ScrollBar.allowsAbsolutePositioning = true
#---- ScrollPane ----
ScrollPane.border = com.formdev.flatlaf.ui.FlatBorder
ScrollPane.border = com.formdev.flatlaf.ui.FlatScrollPaneBorder
ScrollPane.background = $ScrollBar.track
ScrollPane.fillUpperCorner = true
ScrollPane.smoothScrolling = true
ScrollPane.arc = 0
#ScrollPane.List.arc = -1
#ScrollPane.Table.arc = -1
#ScrollPane.TextComponent.arc = -1
#ScrollPane.Tree.arc = -1
#---- SearchField ----
@@ -697,6 +705,8 @@ TabbedPane.tabAreaAlignment = leading
TabbedPane.tabAlignment = center
# allowed values: preferred, equal or compact
TabbedPane.tabWidthMode = preferred
# allowed values: none, auto, left or right
TabbedPane.tabRotation = none
# allowed values: underlined or card
TabbedPane.tabType = underlined
@@ -729,9 +739,11 @@ Table.rowHeight = 20
Table.showHorizontalLines = false
Table.showVerticalLines = false
Table.showTrailingVerticalLine = false
Table.paintOutsideAlternateRows = false
Table.editorSelectAllOnStartEditing = true
Table.consistentHomeEndKeyBehavior = true
Table.intercellSpacing = 0,0
Table.scrollPaneBorder = com.formdev.flatlaf.ui.FlatBorder
Table.scrollPaneBorder = com.formdev.flatlaf.ui.FlatScrollPaneBorder
Table.ascendingSortIcon = com.formdev.flatlaf.icons.FlatAscendingSortIcon
Table.descendingSortIcon = com.formdev.flatlaf.icons.FlatDescendingSortIcon
Table.sortIconColor = @icon
@@ -819,7 +831,6 @@ TitlePane.centerTitleIfMenuBarEmbedded = true
TitlePane.showIconBesideTitle = false
TitlePane.menuBarTitleGap = 40
TitlePane.menuBarTitleMinimumGap = 12
TitlePane.menuBarResizeHeight = 4
TitlePane.closeIcon = com.formdev.flatlaf.icons.FlatWindowCloseIcon
TitlePane.iconifyIcon = com.formdev.flatlaf.icons.FlatWindowIconifyIcon
TitlePane.maximizeIcon = com.formdev.flatlaf.icons.FlatWindowMaximizeIcon
@@ -902,6 +913,7 @@ ToolTipManager.enableToolTipMode = activeApplication
#---- ToolTip ----
ToolTip.borderCornerRadius = $Popup.borderCornerRadius
[mac]ToolTip.roundedBorderWidth = $Popup.roundedBorderWidth
#---- Tree ----

View File

@@ -295,3 +295,8 @@ ToggleButton.disabledBackground = $Button.disabledBackground
ToggleButton.selectedBackground = lighten($ToggleButton.background,20%,derived)
ToggleButton.toolbar.selectedBackground = #fff3
#---- ToolBar ----
ToolBar.hoverButtonGroupArc = 14

View File

@@ -291,3 +291,8 @@ TextPane.selectionForeground = @textSelectionForeground
#---- ToggleButton ----
ToggleButton.disabledBackground = $Button.disabledBackground
#---- ToolBar ----
ToolBar.hoverButtonGroupArc = 14

View File

@@ -601,7 +601,7 @@ public class TestFlatStyleableInfo
);
// border
flatBorder( expected );
flatScrollPaneBorder( expected );
assertMapEquals( expected, ui.getStyleableInfos( c ) );
}
@@ -689,6 +689,9 @@ public class TestFlatStyleableInfo
Map<String, Class<?>> expected = expectedMap(
"arrowType", String.class,
"draggingColor", Color.class,
"hoverColor", Color.class,
"pressedColor", Color.class,
"oneTouchArrowColor", Color.class,
"oneTouchHoverArrowColor", Color.class,
"oneTouchPressedArrowColor", Color.class,
@@ -752,6 +755,7 @@ public class TestFlatStyleableInfo
"tabAreaAlignment", String.class,
"tabAlignment", String.class,
"tabWidthMode", String.class,
"tabRotation", String.class,
"arrowType", String.class,
"buttonInsets", Insets.class,
@@ -925,7 +929,10 @@ public class TestFlatStyleableInfo
"hoverButtonGroupBackground", Color.class,
"borderMargins", Insets.class,
"gripColor", Color.class
"gripColor", Color.class,
"separatorWidth", Integer.class,
"separatorColor", Color.class
);
assertMapEquals( expected, ui.getStyleableInfos( c ) );
@@ -1005,17 +1012,23 @@ public class TestFlatStyleableInfo
expectedMap( expected,
"arc", int.class,
"roundRect", Boolean.class
);
}
private void flatScrollPaneBorder( Map<String, Class<?>> expected ) {
flatBorder( expected );
expectedMap( expected,
"arc", int.class
);
}
private void flatTextBorder( Map<String, Class<?>> expected ) {
flatBorder( expected );
expectedMap( expected,
"arc", int.class,
"roundRect", Boolean.class
);
}

View File

@@ -625,7 +625,7 @@ public class TestFlatStyleableValue
FlatScrollPaneUI ui = (FlatScrollPaneUI) c.getUI();
// border
flatBorder( c, ui );
flatScrollPaneBorder( c, ui );
testBoolean( c, ui, "showButtons", true );
}
@@ -699,6 +699,9 @@ public class TestFlatStyleableValue
FlatSplitPaneUI ui = (FlatSplitPaneUI) c.getUI();
testString( c, ui, "arrowType", "chevron" );
testColor( c, ui, "draggingColor", 0x123456 );
testColor( c, ui, "hoverColor", 0x123456 );
testColor( c, ui, "pressedColor", 0x123456 );
testColor( c, ui, "oneTouchArrowColor", 0x123456 );
testColor( c, ui, "oneTouchHoverArrowColor", 0x123456 );
testColor( c, ui, "oneTouchPressedArrowColor", 0x123456 );
@@ -758,6 +761,7 @@ public class TestFlatStyleableValue
testString( c, ui, "tabAreaAlignment", "leading" );
testString( c, ui, "tabAlignment", "center" );
testString( c, ui, "tabWidthMode", "preferred" );
testString( c, ui, "tabRotation", "none" );
testString( c, ui, "arrowType", "chevron" );
testInsets( c, ui, "buttonInsets", 1,2,3,4 );
@@ -902,6 +906,9 @@ public class TestFlatStyleableValue
testInsets( c, ui, "borderMargins", 1,2,3,4 );
testColor( c, ui, "gripColor", 0x123456 );
testInteger( c, ui, "separatorWidth", 123 );
testColor( c, ui, "separatorColor", 0x123456 );
}
@Test
@@ -965,15 +972,19 @@ public class TestFlatStyleableValue
flatBorder( c, ui );
testInteger( c, ui, "arc", 123 );
testBoolean( c, ui, "roundRect", true );
}
private void flatScrollPaneBorder( JComponent c, StyleableUI ui ) {
flatBorder( c, ui );
testInteger( c, ui, "arc", 123 );
}
private void flatTextBorder( JComponent c, StyleableUI ui ) {
flatBorder( c, ui );
testInteger( c, ui, "arc", 123 );
testBoolean( c, ui, "roundRect", true );
}
@@ -1034,6 +1045,17 @@ public class TestFlatStyleableValue
// FlatRoundBorder extends FlatBorder
flatBorder( border );
testValue( border, "arc", 6 );
testValue( border, "roundRect", true );
}
@Test
void flatScrollPaneBorder() {
FlatScrollPaneBorder border = new FlatScrollPaneBorder();
// FlatScrollPaneBorder extends FlatBorder
flatBorder( border );
testValue( border, "arc", 6 );
}
@@ -1045,6 +1067,7 @@ public class TestFlatStyleableValue
flatBorder( border );
testValue( border, "arc", 6 );
testValue( border, "roundRect", true );
}
@Test

View File

@@ -760,7 +760,7 @@ public class TestFlatStyling
FlatScrollPaneUI ui = (FlatScrollPaneUI) c.getUI();
// border
flatBorder( style -> ui.applyStyle( style ) );
flatScrollPaneBorder( style -> ui.applyStyle( style ) );
ui.applyStyle( "showButtons: true" );
@@ -870,6 +870,9 @@ public class TestFlatStyling
FlatSplitPaneUI ui = (FlatSplitPaneUI) c.getUI();
ui.applyStyle( "arrowType: chevron" );
ui.applyStyle( "draggingColor: #fff" );
ui.applyStyle( "hoverColor: #fff" );
ui.applyStyle( "pressedColor: #fff" );
ui.applyStyle( "oneTouchArrowColor: #fff" );
ui.applyStyle( "oneTouchHoverArrowColor: #fff" );
ui.applyStyle( "oneTouchPressedArrowColor: #fff" );
@@ -937,6 +940,7 @@ public class TestFlatStyling
ui.applyStyle( "tabAreaAlignment: leading" );
ui.applyStyle( "tabAlignment: center" );
ui.applyStyle( "tabWidthMode: preferred" );
ui.applyStyle( "tabRotation: none" );
ui.applyStyle( "arrowType: chevron" );
ui.applyStyle( "buttonInsets: 1,2,3,4" );
@@ -1146,6 +1150,9 @@ public class TestFlatStyling
ui.applyStyle( "borderMargins: 1,2,3,4" );
ui.applyStyle( "gripColor: #fff" );
ui.applyStyle( "separatorWidth: 6" );
ui.applyStyle( "separatorColor: #fff" );
// JComponent properties
ui.applyStyle( "background: #fff" );
ui.applyStyle( "foreground: #fff" );
@@ -1234,15 +1241,19 @@ public class TestFlatStyling
flatBorder( applyStyle );
applyStyle.accept( "arc: 6" );
applyStyle.accept( "roundRect: true" );
}
private void flatScrollPaneBorder( Consumer<String> applyStyle ) {
flatBorder( applyStyle );
applyStyle.accept( "arc: 6" );
}
private void flatTextBorder( Consumer<String> applyStyle ) {
flatBorder( applyStyle );
applyStyle.accept( "arc: 6" );
applyStyle.accept( "roundRect: true" );
}

View File

@@ -28,6 +28,8 @@ import javax.swing.plaf.metal.MetalLookAndFeel;
import javax.swing.plaf.nimbus.NimbusLookAndFeel;
import com.formdev.flatlaf.*;
import com.formdev.flatlaf.extras.FlatAnimatedLafChange;
import com.formdev.flatlaf.themes.FlatMacDarkLaf;
import com.formdev.flatlaf.themes.FlatMacLightLaf;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale;
@@ -66,6 +68,8 @@ class ControlBar
lafModel.addElement( new LookAndFeelInfo( "FlatLaf Dark (F2)", FlatDarkLaf.class.getName() ) );
lafModel.addElement( new LookAndFeelInfo( "FlatLaf IntelliJ (F3)", FlatIntelliJLaf.class.getName() ) );
lafModel.addElement( new LookAndFeelInfo( "FlatLaf Darcula (F4)", FlatDarculaLaf.class.getName() ) );
lafModel.addElement( new LookAndFeelInfo( "FlatLaf macOS Light (F5)", FlatMacLightLaf.class.getName() ) );
lafModel.addElement( new LookAndFeelInfo( "FlatLaf macOS Dark (F6)", FlatMacDarkLaf.class.getName() ) );
UIManager.LookAndFeelInfo[] lookAndFeels = UIManager.getInstalledLookAndFeels();
for( UIManager.LookAndFeelInfo lookAndFeel : lookAndFeels ) {
@@ -127,6 +131,8 @@ class ControlBar
registerSwitchToLookAndFeel( KeyEvent.VK_F2, FlatDarkLaf.class.getName() );
registerSwitchToLookAndFeel( KeyEvent.VK_F3, FlatIntelliJLaf.class.getName() );
registerSwitchToLookAndFeel( KeyEvent.VK_F4, FlatDarculaLaf.class.getName() );
registerSwitchToLookAndFeel( KeyEvent.VK_F5, FlatMacLightLaf.class.getName() );
registerSwitchToLookAndFeel( KeyEvent.VK_F6, FlatMacDarkLaf.class.getName() );
if( SystemInfo.isWindows )
registerSwitchToLookAndFeel( KeyEvent.VK_F9, "com.sun.java.swing.plaf.windows.WindowsLookAndFeel" );

View File

@@ -18,7 +18,6 @@ package com.formdev.flatlaf.demo;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
@@ -116,15 +115,16 @@ class DataComponentsPanel
table1.setGridColor( redGridColorCheckBox.isSelected() ? Color.red : UIManager.getColor( "Table.gridColor" ) );
}
@Override
public void updateUI() {
super.updateUI();
private void showHorizontalLinesPropertyChange() {
showHorizontalLinesCheckBox.setSelected( table1.getShowHorizontalLines() );
}
EventQueue.invokeLater( () -> {
showHorizontalLinesChanged();
showVerticalLinesChanged();
intercellSpacingChanged();
} );
private void showVerticalLinesPropertyChange() {
showVerticalLinesCheckBox.setSelected( table1.getShowVerticalLines() );
}
private void intercellSpacingPropertyChange() {
intercellSpacingCheckBox.setSelected( table1.getRowMargin() != 0 );
}
@SuppressWarnings( { "unchecked", "rawtypes" } )
@@ -333,10 +333,10 @@ class DataComponentsPanel
"Not editable", "Text", "Combo", "Combo Editable", "Integer", "Boolean"
}
) {
Class<?>[] columnTypes = new Class<?>[] {
Class<?>[] columnTypes = {
Object.class, Object.class, String.class, String.class, Integer.class, Boolean.class
};
boolean[] columnEditable = new boolean[] {
boolean[] columnEditable = {
false, true, true, true, true, true
};
@Override
@@ -383,6 +383,9 @@ class DataComponentsPanel
}
table1.setAutoCreateRowSorter(true);
table1.setComponentPopupMenu(popupMenu2);
table1.addPropertyChangeListener("showHorizontalLines", e -> showHorizontalLinesPropertyChange());
table1.addPropertyChangeListener("showVerticalLines", e -> showVerticalLinesPropertyChange());
table1.addPropertyChangeListener("rowMargin", e -> intercellSpacingPropertyChange());
scrollPane5.setViewportView(table1);
}
add(scrollPane5, "cell 1 3 3 1,width 300");

View File

@@ -1,4 +1,4 @@
JFDML JFormDesigner: "8.0.0.0.194" Java: "17.0.2" encoding: "UTF-8"
JFDML JFormDesigner: "8.2.0.0.331" Java: "21" encoding: "UTF-8"
new FormModel {
contentType: "form/swing"
@@ -333,6 +333,9 @@ new FormModel {
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
addEvent( new FormEvent( "java.beans.PropertyChangeListener", "propertyChange", "showHorizontalLinesPropertyChange", false, "showHorizontalLines" ) )
addEvent( new FormEvent( "java.beans.PropertyChangeListener", "propertyChange", "showVerticalLinesPropertyChange", false, "showVerticalLines" ) )
addEvent( new FormEvent( "java.beans.PropertyChangeListener", "propertyChange", "intercellSpacingPropertyChange", false, "rowMargin" ) )
} )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 3 3 1,width 300"

View File

@@ -46,7 +46,6 @@ import com.formdev.flatlaf.icons.FlatAbstractIcon;
import com.formdev.flatlaf.themes.FlatMacDarkLaf;
import com.formdev.flatlaf.themes.FlatMacLightLaf;
import com.formdev.flatlaf.extras.FlatSVGUtils;
import com.formdev.flatlaf.ui.JBRCustomDecorations;
import com.formdev.flatlaf.util.ColorFunctions;
import com.formdev.flatlaf.util.FontUtils;
import com.formdev.flatlaf.util.LoggingFacade;
@@ -74,6 +73,7 @@ class DemoFrame
initComponents();
updateFontMenuItems();
initAccentColors();
initFullWindowContent();
controlBar.initialize( this, tabbedPane );
setIconImages( FlatSVGUtils.createWindowIconImages( "/com/formdev/flatlaf/demo/FlatLaf.svg" ) );
@@ -90,24 +90,23 @@ class DemoFrame
// do not use HTML text in menu items because this is not supported in macOS screen menu
htmlMenuItem.setText( "some text" );
JRootPane rootPane = getRootPane();
if( SystemInfo.isMacFullWindowContentSupported ) {
// expand window content into window title bar and make title bar transparent
getRootPane().putClientProperty( "apple.awt.fullWindowContent", true );
getRootPane().putClientProperty( "apple.awt.transparentTitleBar", true );
rootPane.putClientProperty( "apple.awt.fullWindowContent", true );
rootPane.putClientProperty( "apple.awt.transparentTitleBar", true );
rootPane.putClientProperty( FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING, FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING_LARGE );
// hide window title
if( SystemInfo.isJava_17_orLater )
getRootPane().putClientProperty( "apple.awt.windowTitleVisible", false );
rootPane.putClientProperty( "apple.awt.windowTitleVisible", false );
else
setTitle( null );
// add gap to left side of toolbar
toolBar.add( Box.createHorizontalStrut( 70 ), 0 );
}
// enable full screen mode for this window (for Java 8 - 10; not necessary for Java 11+)
if( !SystemInfo.isJava_11_orLater )
getRootPane().putClientProperty( "apple.awt.fullscreenable", true );
rootPane.putClientProperty( "apple.awt.fullscreenable", true );
}
// integrate into macOS screen menu
@@ -462,9 +461,37 @@ class DemoFrame
accentColorButtons[i].setVisible( isAccentColorSupported );
}
private void initFullWindowContent() {
if( !supportsFlatLafWindowDecorations() )
return;
// create fullWindowContent mode toggle button
Icon expandIcon = new FlatSVGIcon( "com/formdev/flatlaf/demo/icons/expand.svg" );
Icon collapseIcon = new FlatSVGIcon( "com/formdev/flatlaf/demo/icons/collapse.svg" );
JToggleButton fullWindowContentButton = new JToggleButton( expandIcon );
fullWindowContentButton.setToolTipText( "Toggle full window content" );
fullWindowContentButton.addActionListener( e -> {
boolean fullWindowContent = fullWindowContentButton.isSelected();
fullWindowContentButton.setIcon( fullWindowContent ? collapseIcon : expandIcon );
menuBar.setVisible( !fullWindowContent );
toolBar.setVisible( !fullWindowContent );
getRootPane().putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT, fullWindowContent );
} );
// add fullWindowContent mode toggle button to tabbed pane
JToolBar trailingToolBar = new JToolBar();
trailingToolBar.add( Box.createGlue() );
trailingToolBar.add( fullWindowContentButton );
tabbedPane.putClientProperty( FlatClientProperties.TABBED_PANE_TRAILING_COMPONENT, trailingToolBar );
}
private boolean supportsFlatLafWindowDecorations() {
return FlatLaf.supportsNativeWindowDecorations() || (SystemInfo.isLinux && JFrame.isDefaultLookAndFeelDecorated());
}
private void initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
JMenuBar menuBar1 = new JMenuBar();
menuBar = new JMenuBar();
JMenu fileMenu = new JMenu();
JMenuItem newMenuItem = new JMenuItem();
JMenuItem openMenuItem = new JMenuItem();
@@ -510,6 +537,8 @@ class DemoFrame
JMenuItem showUIDefaultsInspectorMenuItem = new JMenuItem();
JMenu helpMenu = new JMenu();
aboutMenuItem = new JMenuItem();
JPanel toolBarPanel = new JPanel();
JPanel macFullWindowContentButtonsPlaceholder = new JPanel();
toolBar = new JToolBar();
JButton backButton = new JButton();
JButton forwardButton = new JButton();
@@ -525,8 +554,10 @@ class DemoFrame
DataComponentsPanel dataComponentsPanel = new DataComponentsPanel();
TabsPanel tabsPanel = new TabsPanel();
OptionPanePanel optionPanePanel = new OptionPanePanel();
ExtrasPanel extrasPanel1 = new ExtrasPanel();
ExtrasPanel extrasPanel = new ExtrasPanel();
controlBar = new ControlBar();
JPanel themesPanelPanel = new JPanel();
JPanel winFullWindowContentButtonsPlaceholder = new JPanel();
themesPanel = new IJThemesPanel();
//======== this ========
@@ -535,7 +566,7 @@ class DemoFrame
Container contentPane = getContentPane();
contentPane.setLayout(new BorderLayout());
//======== menuBar1 ========
//======== menuBar ========
{
//======== fileMenu ========
@@ -580,7 +611,7 @@ class DemoFrame
exitMenuItem.addActionListener(e -> exitActionPerformed());
fileMenu.add(exitMenuItem);
}
menuBar1.add(fileMenu);
menuBar.add(fileMenu);
//======== editMenu ========
{
@@ -633,7 +664,7 @@ class DemoFrame
deleteMenuItem.addActionListener(e -> menuItemActionPerformed(e));
editMenu.add(deleteMenuItem);
}
menuBar1.add(editMenu);
menuBar.add(editMenu);
//======== viewMenu ========
{
@@ -733,7 +764,7 @@ class DemoFrame
radioButtonMenuItem3.addActionListener(e -> menuItemActionPerformed(e));
viewMenu.add(radioButtonMenuItem3);
}
menuBar1.add(viewMenu);
menuBar.add(viewMenu);
//======== fontMenu ========
{
@@ -757,7 +788,7 @@ class DemoFrame
decrFontMenuItem.addActionListener(e -> decrFont());
fontMenu.add(decrFontMenuItem);
}
menuBar1.add(fontMenu);
menuBar.add(fontMenu);
//======== optionsMenu ========
{
@@ -809,7 +840,7 @@ class DemoFrame
showUIDefaultsInspectorMenuItem.addActionListener(e -> showUIDefaultsInspector());
optionsMenu.add(showUIDefaultsInspectorMenuItem);
}
menuBar1.add(optionsMenu);
menuBar.add(optionsMenu);
//======== helpMenu ========
{
@@ -822,54 +853,66 @@ class DemoFrame
aboutMenuItem.addActionListener(e -> aboutActionPerformed());
helpMenu.add(aboutMenuItem);
}
menuBar1.add(helpMenu);
menuBar.add(helpMenu);
}
setJMenuBar(menuBar1);
setJMenuBar(menuBar);
//======== toolBar ========
//======== toolBarPanel ========
{
toolBar.setMargin(new Insets(3, 3, 3, 3));
toolBarPanel.setLayout(new BorderLayout());
//---- backButton ----
backButton.setToolTipText("Back");
backButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/back.svg"));
toolBar.add(backButton);
//======== macFullWindowContentButtonsPlaceholder ========
{
macFullWindowContentButtonsPlaceholder.setLayout(new FlowLayout());
}
toolBarPanel.add(macFullWindowContentButtonsPlaceholder, BorderLayout.WEST);
//---- forwardButton ----
forwardButton.setToolTipText("Forward");
forwardButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/forward.svg"));
toolBar.add(forwardButton);
toolBar.addSeparator();
//======== toolBar ========
{
toolBar.setMargin(new Insets(3, 3, 3, 3));
//---- cutButton ----
cutButton.setToolTipText("Cut");
cutButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/menu-cut.svg"));
toolBar.add(cutButton);
//---- backButton ----
backButton.setToolTipText("Back");
backButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/back.svg"));
toolBar.add(backButton);
//---- copyButton ----
copyButton.setToolTipText("Copy");
copyButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/copy.svg"));
toolBar.add(copyButton);
//---- forwardButton ----
forwardButton.setToolTipText("Forward");
forwardButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/forward.svg"));
toolBar.add(forwardButton);
toolBar.addSeparator();
//---- pasteButton ----
pasteButton.setToolTipText("Paste");
pasteButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/menu-paste.svg"));
toolBar.add(pasteButton);
toolBar.addSeparator();
//---- cutButton ----
cutButton.setToolTipText("Cut");
cutButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/menu-cut.svg"));
toolBar.add(cutButton);
//---- refreshButton ----
refreshButton.setToolTipText("Refresh");
refreshButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/refresh.svg"));
toolBar.add(refreshButton);
toolBar.addSeparator();
//---- copyButton ----
copyButton.setToolTipText("Copy");
copyButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/copy.svg"));
toolBar.add(copyButton);
//---- showToggleButton ----
showToggleButton.setSelected(true);
showToggleButton.setToolTipText("Show Details");
showToggleButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/show.svg"));
toolBar.add(showToggleButton);
//---- pasteButton ----
pasteButton.setToolTipText("Paste");
pasteButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/menu-paste.svg"));
toolBar.add(pasteButton);
toolBar.addSeparator();
//---- refreshButton ----
refreshButton.setToolTipText("Refresh");
refreshButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/refresh.svg"));
toolBar.add(refreshButton);
toolBar.addSeparator();
//---- showToggleButton ----
showToggleButton.setSelected(true);
showToggleButton.setToolTipText("Show Details");
showToggleButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/show.svg"));
toolBar.add(showToggleButton);
}
toolBarPanel.add(toolBar, BorderLayout.CENTER);
}
contentPane.add(toolBar, BorderLayout.NORTH);
contentPane.add(toolBarPanel, BorderLayout.PAGE_START);
//======== contentPanel ========
{
@@ -889,13 +932,25 @@ class DemoFrame
tabbedPane.addTab("Data Components", dataComponentsPanel);
tabbedPane.addTab("Tabs", tabsPanel);
tabbedPane.addTab("Option Pane", optionPanePanel);
tabbedPane.addTab("Extras", extrasPanel1);
tabbedPane.addTab("Extras", extrasPanel);
}
contentPanel.add(tabbedPane, "cell 0 0");
}
contentPane.add(contentPanel, BorderLayout.CENTER);
contentPane.add(controlBar, BorderLayout.SOUTH);
contentPane.add(themesPanel, BorderLayout.EAST);
contentPane.add(controlBar, BorderLayout.PAGE_END);
//======== themesPanelPanel ========
{
themesPanelPanel.setLayout(new BorderLayout());
//======== winFullWindowContentButtonsPlaceholder ========
{
winFullWindowContentButtonsPlaceholder.setLayout(new FlowLayout());
}
themesPanelPanel.add(winFullWindowContentButtonsPlaceholder, BorderLayout.NORTH);
themesPanelPanel.add(themesPanel, BorderLayout.CENTER);
}
contentPane.add(themesPanelPanel, BorderLayout.LINE_END);
//---- buttonGroup1 ----
ButtonGroup buttonGroup1 = new ButtonGroup();
@@ -910,8 +965,8 @@ class DemoFrame
usersButton.setButtonType( ButtonType.toolBarButton );
usersButton.setFocusable( false );
usersButton.addActionListener( e -> JOptionPane.showMessageDialog( null, "Hello User! How are you?", "User", JOptionPane.INFORMATION_MESSAGE ) );
menuBar1.add( Box.createGlue() );
menuBar1.add( usersButton );
menuBar.add( Box.createGlue() );
menuBar.add( usersButton );
cutMenuItem.addActionListener( new DefaultEditorKit.CutAction() );
copyMenuItem.addActionListener( new DefaultEditorKit.CopyAction() );
@@ -923,7 +978,7 @@ class DemoFrame
for( int i = 1; i <= 100; i++ )
scrollingPopupMenu.add( "Item " + i );
if( FlatLaf.supportsNativeWindowDecorations() || (SystemInfo.isLinux && JFrame.isDefaultLookAndFeelDecorated()) ) {
if( supportsFlatLafWindowDecorations() ) {
if( SystemInfo.isLinux )
unsupported( windowDecorationsCheckBoxMenuItem );
else
@@ -931,12 +986,6 @@ class DemoFrame
menuBarEmbeddedCheckBoxMenuItem.setSelected( UIManager.getBoolean( "TitlePane.menuBarEmbedded" ) );
unifiedTitleBarMenuItem.setSelected( UIManager.getBoolean( "TitlePane.unifiedBackground" ) );
showTitleBarIconMenuItem.setSelected( UIManager.getBoolean( "TitlePane.showIcon" ) );
if( JBRCustomDecorations.isSupported() ) {
// If the JetBrains Runtime is used, it forces the use of it's own custom
// window decoration, which can not disabled.
windowDecorationsCheckBoxMenuItem.setEnabled( false );
}
} else {
unsupported( windowDecorationsCheckBoxMenuItem );
unsupported( menuBarEmbeddedCheckBoxMenuItem );
@@ -950,6 +999,17 @@ class DemoFrame
if( "false".equals( System.getProperty( "flatlaf.animatedLafChange" ) ) )
animatedLafChangeMenuItem.setSelected( false );
// on macOS, panel left to toolBar is a placeholder for title bar buttons in fullWindowContent mode
macFullWindowContentButtonsPlaceholder.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER, "mac zeroInFullScreen" );
// on Windows/Linux, panel above themesPanel is a placeholder for title bar buttons in fullWindowContent mode
winFullWindowContentButtonsPlaceholder.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER, "win" );
// uncomment this line to see title bar buttons placeholders in fullWindowContent mode
// UIManager.put( "FlatLaf.debug.panel.showPlaceholders", true );
// remove contentPanel bottom insets
MigLayout layout = (MigLayout) contentPanel.getLayout();
LC lc = ConstraintParser.parseLayoutConstraint( (String) layout.getLayoutConstraints() );
@@ -970,6 +1030,7 @@ class DemoFrame
}
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
private JMenuBar menuBar;
private JMenuItem exitMenuItem;
private JMenu scrollingPopupMenu;
private JMenuItem htmlMenuItem;

View File

@@ -1,4 +1,4 @@
JFDML JFormDesigner: "8.1.0.0.283" Java: "19.0.2" encoding: "UTF-8"
JFDML JFormDesigner: "8.2.1.0.348" Java: "21.0.1" encoding: "UTF-8"
new FormModel {
contentType: "form/swing"
@@ -12,59 +12,69 @@ new FormModel {
"defaultCloseOperation": 2
"$locationPolicy": 2
"$sizePolicy": 2
add( new FormContainer( "javax.swing.JToolBar", new FormLayoutManager( class javax.swing.JToolBar ) ) {
name: "toolBar"
"margin": new java.awt.Insets( 3, 3, 3, 3 )
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
add( new FormComponent( "javax.swing.JButton" ) {
name: "backButton"
"toolTipText": "Back"
"icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/back.svg" )
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class java.awt.BorderLayout ) ) {
name: "toolBarPanel"
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class java.awt.FlowLayout ) ) {
name: "macFullWindowContentButtonsPlaceholder"
}, new FormLayoutConstraints( class java.lang.String ) {
"value": "West"
} )
add( new FormComponent( "javax.swing.JButton" ) {
name: "forwardButton"
"toolTipText": "Forward"
"icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/forward.svg" )
} )
add( new FormComponent( "javax.swing.JToolBar$Separator" ) {
name: "separator5"
} )
add( new FormComponent( "javax.swing.JButton" ) {
name: "cutButton"
"toolTipText": "Cut"
"icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/menu-cut.svg" )
} )
add( new FormComponent( "javax.swing.JButton" ) {
name: "copyButton"
"toolTipText": "Copy"
"icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/copy.svg" )
} )
add( new FormComponent( "javax.swing.JButton" ) {
name: "pasteButton"
"toolTipText": "Paste"
"icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/menu-paste.svg" )
} )
add( new FormComponent( "javax.swing.JToolBar$Separator" ) {
name: "separator6"
} )
add( new FormComponent( "javax.swing.JButton" ) {
name: "refreshButton"
"toolTipText": "Refresh"
"icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/refresh.svg" )
} )
add( new FormComponent( "javax.swing.JToolBar$Separator" ) {
name: "separator7"
} )
add( new FormComponent( "javax.swing.JToggleButton" ) {
name: "showToggleButton"
"selected": true
"toolTipText": "Show Details"
"icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/show.svg" )
add( new FormContainer( "javax.swing.JToolBar", new FormLayoutManager( class javax.swing.JToolBar ) ) {
name: "toolBar"
"margin": new java.awt.Insets( 3, 3, 3, 3 )
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
add( new FormComponent( "javax.swing.JButton" ) {
name: "backButton"
"toolTipText": "Back"
"icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/back.svg" )
} )
add( new FormComponent( "javax.swing.JButton" ) {
name: "forwardButton"
"toolTipText": "Forward"
"icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/forward.svg" )
} )
add( new FormComponent( "javax.swing.JToolBar$Separator" ) {
name: "separator5"
} )
add( new FormComponent( "javax.swing.JButton" ) {
name: "cutButton"
"toolTipText": "Cut"
"icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/menu-cut.svg" )
} )
add( new FormComponent( "javax.swing.JButton" ) {
name: "copyButton"
"toolTipText": "Copy"
"icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/copy.svg" )
} )
add( new FormComponent( "javax.swing.JButton" ) {
name: "pasteButton"
"toolTipText": "Paste"
"icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/menu-paste.svg" )
} )
add( new FormComponent( "javax.swing.JToolBar$Separator" ) {
name: "separator6"
} )
add( new FormComponent( "javax.swing.JButton" ) {
name: "refreshButton"
"toolTipText": "Refresh"
"icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/refresh.svg" )
} )
add( new FormComponent( "javax.swing.JToolBar$Separator" ) {
name: "separator7"
} )
add( new FormComponent( "javax.swing.JToggleButton" ) {
name: "showToggleButton"
"selected": true
"toolTipText": "Show Details"
"icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/show.svg" )
} )
}, new FormLayoutConstraints( class java.lang.String ) {
"value": "Center"
} )
}, new FormLayoutConstraints( class java.lang.String ) {
"value": "North"
"value": "First"
} )
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "insets dialog,hidemode 3"
@@ -105,7 +115,7 @@ new FormModel {
"title": "Option Pane"
} )
add( new FormComponent( "com.formdev.flatlaf.demo.extras.ExtrasPanel" ) {
name: "extrasPanel1"
name: "extrasPanel"
}, new FormLayoutConstraints( null ) {
"title": "Extras"
} )
@@ -121,19 +131,32 @@ new FormModel {
"JavaCodeGenerator.variableLocal": false
}
}, new FormLayoutConstraints( class java.lang.String ) {
"value": "South"
"value": "Last"
} )
add( new FormComponent( "com.formdev.flatlaf.demo.intellijthemes.IJThemesPanel" ) {
name: "themesPanel"
auxiliary() {
"JavaCodeGenerator.variableLocal": false
"JavaCodeGenerator.variableModifiers": 0
}
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class java.awt.BorderLayout ) ) {
name: "themesPanelPanel"
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class java.awt.FlowLayout ) ) {
name: "winFullWindowContentButtonsPlaceholder"
}, new FormLayoutConstraints( class java.lang.String ) {
"value": "North"
} )
add( new FormComponent( "com.formdev.flatlaf.demo.intellijthemes.IJThemesPanel" ) {
name: "themesPanel"
auxiliary() {
"JavaCodeGenerator.variableLocal": false
"JavaCodeGenerator.variableModifiers": 0
}
}, new FormLayoutConstraints( class java.lang.String ) {
"value": "Center"
} )
}, new FormLayoutConstraints( class java.lang.String ) {
"value": "East"
"value": "After"
} )
menuBar: new FormContainer( "javax.swing.JMenuBar", new FormLayoutManager( class javax.swing.JMenuBar ) ) {
name: "menuBar1"
name: "menuBar"
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) {
name: "fileMenu"
"text": "File"

View File

@@ -34,7 +34,7 @@ public class ScrollablePanel
{
@Override
public Dimension getPreferredScrollableViewportSize() {
return UIScale.scale( new Dimension( 400, 400 ) );
return new Dimension( getPreferredSize().width, UIScale.scale( 400 ) );
}
@Override
@@ -49,7 +49,7 @@ public class ScrollablePanel
@Override
public boolean getScrollableTracksViewportWidth() {
return false;
return true;
}
@Override

View File

@@ -204,7 +204,7 @@ class TabsPanel
private void closeButtonStyleChanged() {
// WARNING:
// Do not use this trick to style individual tabbed panes in own code.
// Instead use one styling for all tabbed panes in your application.
// Instead, use one styling for all tabbed panes in your application.
if( circleCloseButton.isSelected() ) {
UIManager.put( "TabbedPane.closeArc", 999 );
UIManager.put( "TabbedPane.closeCrossFilledSize", 5.5f );
@@ -313,6 +313,14 @@ class TabsPanel
putTabbedPanesClientProperty( TABBED_PANE_SHOW_TAB_SEPARATORS, showTabSeparators );
}
private void tabRotationChanged() {
String tabRotation = rotationAutoButton.isSelected() ? TABBED_PANE_TAB_ROTATION_AUTO
: rotationLeftButton.isSelected() ? TABBED_PANE_TAB_ROTATION_LEFT
: rotationRightButton.isSelected() ? TABBED_PANE_TAB_ROTATION_RIGHT
: null;
putTabbedPanesClientProperty( TABBED_PANE_TAB_ROTATION, tabRotation );
}
private void putTabbedPanesClientProperty( String key, Object value ) {
updateTabbedPanesRecur( this, tabbedPane -> tabbedPane.putClientProperty( key, value ) );
}
@@ -331,6 +339,8 @@ class TabsPanel
private void initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
JScrollPane tabsScrollPane = new JScrollPane();
ScrollablePanel panel6 = new ScrollablePanel();
JPanel panel1 = new JPanel();
JLabel tabPlacementLabel = new JLabel();
tabPlacementToolBar = new JToolBar();
@@ -397,344 +407,369 @@ class TabsPanel
scrollAsNeededSingleButton = new JToggleButton();
scrollAsNeededButton = new JToggleButton();
scrollNeverButton = new JToggleButton();
scrollButtonsPlacementLabel = new JLabel();
scrollButtonsPlacementToolBar = new JToolBar();
scrollBothButton = new JToggleButton();
scrollTrailingButton = new JToggleButton();
showTabSeparatorsCheckBox = new JCheckBox();
tabsPopupPolicyLabel = new JLabel();
tabsPopupPolicyToolBar = new JToolBar();
popupAsNeededButton = new JToggleButton();
popupNeverButton = new JToggleButton();
showTabSeparatorsCheckBox = new JCheckBox();
scrollButtonsPlacementLabel = new JLabel();
scrollButtonsPlacementToolBar = new JToolBar();
scrollBothButton = new JToggleButton();
scrollTrailingButton = new JToggleButton();
tabTypeLabel = new JLabel();
tabTypeToolBar = new JToolBar();
underlinedTabTypeButton = new JToggleButton();
cardTabTypeButton = new JToggleButton();
tabRotationLabel = new JLabel();
tabRotationToolBar = new JToolBar();
rotationNoneButton = new JToggleButton();
rotationAutoButton = new JToggleButton();
rotationLeftButton = new JToggleButton();
rotationRightButton = new JToggleButton();
//======== this ========
setLayout(new MigLayout(
"insets dialog,hidemode 3",
"insets 0,hidemode 3",
// columns
"[grow,fill]para" +
"[fill]para" +
"[fill]",
"[grow,fill]",
// rows
"[grow,fill]para" +
"[]" +
"[grow,fill]0" +
"[]0" +
"[]"));
//======== panel1 ========
//======== tabsScrollPane ========
{
panel1.setLayout(new MigLayout(
"insets 0,hidemode 3",
// columns
"[grow,fill]",
// rows
"[]" +
"[fill]para" +
"[]0" +
"[]" +
"[]para" +
"[]" +
"[]para" +
"[]" +
"[]"));
tabsScrollPane.setBorder(BorderFactory.createEmptyBorder());
//---- tabPlacementLabel ----
tabPlacementLabel.setText("Tab placement");
tabPlacementLabel.putClientProperty("FlatLaf.styleClass", "h3");
panel1.add(tabPlacementLabel, "cell 0 0");
//======== tabPlacementToolBar ========
//======== panel6 ========
{
tabPlacementToolBar.setFloatable(false);
tabPlacementToolBar.setBorder(BorderFactory.createEmptyBorder());
//---- topPlacementButton ----
topPlacementButton.setText("top");
topPlacementButton.setSelected(true);
topPlacementButton.putClientProperty("FlatLaf.styleClass", "small");
topPlacementButton.addActionListener(e -> tabPlacementChanged());
tabPlacementToolBar.add(topPlacementButton);
//---- bottomPlacementButton ----
bottomPlacementButton.setText("bottom");
bottomPlacementButton.putClientProperty("FlatLaf.styleClass", "small");
bottomPlacementButton.addActionListener(e -> tabPlacementChanged());
tabPlacementToolBar.add(bottomPlacementButton);
//---- leftPlacementButton ----
leftPlacementButton.setText("left");
leftPlacementButton.putClientProperty("FlatLaf.styleClass", "small");
leftPlacementButton.addActionListener(e -> tabPlacementChanged());
tabPlacementToolBar.add(leftPlacementButton);
//---- rightPlacementButton ----
rightPlacementButton.setText("right");
rightPlacementButton.putClientProperty("FlatLaf.styleClass", "small");
rightPlacementButton.addActionListener(e -> tabPlacementChanged());
tabPlacementToolBar.add(rightPlacementButton);
tabPlacementToolBar.addSeparator();
//---- scrollButton ----
scrollButton.setText("scroll");
scrollButton.putClientProperty("FlatLaf.styleClass", "small");
scrollButton.addActionListener(e -> scrollChanged());
tabPlacementToolBar.add(scrollButton);
//---- borderButton ----
borderButton.setText("border");
borderButton.putClientProperty("FlatLaf.styleClass", "small");
borderButton.addActionListener(e -> borderChanged());
tabPlacementToolBar.add(borderButton);
}
panel1.add(tabPlacementToolBar, "cell 0 0,alignx right,growx 0");
panel1.add(tabPlacementTabbedPane, "cell 0 1,width 300:300,height 100:100");
//---- tabLayoutLabel ----
tabLayoutLabel.setText("Tab layout");
tabLayoutLabel.putClientProperty("FlatLaf.styleClass", "h3");
panel1.add(tabLayoutLabel, "cell 0 2");
//======== tabLayoutToolBar ========
{
tabLayoutToolBar.setFloatable(false);
tabLayoutToolBar.setBorder(BorderFactory.createEmptyBorder());
//---- scrollTabLayoutButton ----
scrollTabLayoutButton.setText("scroll");
scrollTabLayoutButton.setSelected(true);
scrollTabLayoutButton.putClientProperty("FlatLaf.styleClass", "small");
scrollTabLayoutButton.addActionListener(e -> tabLayoutChanged());
tabLayoutToolBar.add(scrollTabLayoutButton);
//---- wrapTabLayoutButton ----
wrapTabLayoutButton.setText("wrap");
wrapTabLayoutButton.putClientProperty("FlatLaf.styleClass", "small");
wrapTabLayoutButton.addActionListener(e -> tabLayoutChanged());
tabLayoutToolBar.add(wrapTabLayoutButton);
}
panel1.add(tabLayoutToolBar, "cell 0 2,alignx right,growx 0");
//---- scrollLayoutNoteLabel ----
scrollLayoutNoteLabel.setText("(use mouse wheel to scroll; arrow button shows hidden tabs)");
scrollLayoutNoteLabel.setEnabled(false);
scrollLayoutNoteLabel.putClientProperty("FlatLaf.styleClass", "small");
panel1.add(scrollLayoutNoteLabel, "cell 0 3");
//---- wrapLayoutNoteLabel ----
wrapLayoutNoteLabel.setText("(probably better to use scroll layout?)");
wrapLayoutNoteLabel.setEnabled(false);
wrapLayoutNoteLabel.putClientProperty("FlatLaf.styleClass", "small");
panel1.add(wrapLayoutNoteLabel, "cell 0 3");
panel1.add(scrollLayoutTabbedPane, "cell 0 4");
panel1.add(wrapLayoutTabbedPane, "cell 0 4,width 100:100,height pref*2px");
//---- closableTabsLabel ----
closableTabsLabel.setText("Closable tabs");
closableTabsLabel.putClientProperty("FlatLaf.styleClass", "h3");
panel1.add(closableTabsLabel, "cell 0 5");
//======== closableTabsToolBar ========
{
closableTabsToolBar.setFloatable(false);
closableTabsToolBar.setBorder(BorderFactory.createEmptyBorder());
//---- squareCloseButton ----
squareCloseButton.setText("square");
squareCloseButton.setSelected(true);
squareCloseButton.putClientProperty("FlatLaf.styleClass", "small");
squareCloseButton.addActionListener(e -> closeButtonStyleChanged());
closableTabsToolBar.add(squareCloseButton);
//---- circleCloseButton ----
circleCloseButton.setText("circle");
circleCloseButton.putClientProperty("FlatLaf.styleClass", "small");
circleCloseButton.addActionListener(e -> closeButtonStyleChanged());
closableTabsToolBar.add(circleCloseButton);
//---- redCrossCloseButton ----
redCrossCloseButton.setText("red cross");
redCrossCloseButton.putClientProperty("FlatLaf.styleClass", "small");
redCrossCloseButton.addActionListener(e -> closeButtonStyleChanged());
closableTabsToolBar.add(redCrossCloseButton);
}
panel1.add(closableTabsToolBar, "cell 0 5,alignx right,growx 0");
panel1.add(closableTabsTabbedPane, "cell 0 6");
//---- tabAreaComponentsLabel ----
tabAreaComponentsLabel.setText("Custom tab area components");
tabAreaComponentsLabel.putClientProperty("FlatLaf.styleClass", "h3");
panel1.add(tabAreaComponentsLabel, "cell 0 7");
//======== tabAreaComponentsToolBar ========
{
tabAreaComponentsToolBar.setFloatable(false);
tabAreaComponentsToolBar.setBorder(BorderFactory.createEmptyBorder());
//---- leadingComponentButton ----
leadingComponentButton.setText("leading");
leadingComponentButton.setSelected(true);
leadingComponentButton.putClientProperty("FlatLaf.styleClass", "small");
leadingComponentButton.addActionListener(e -> customComponentsChanged());
tabAreaComponentsToolBar.add(leadingComponentButton);
//---- trailingComponentButton ----
trailingComponentButton.setText("trailing");
trailingComponentButton.setSelected(true);
trailingComponentButton.putClientProperty("FlatLaf.styleClass", "small");
trailingComponentButton.addActionListener(e -> customComponentsChanged());
tabAreaComponentsToolBar.add(trailingComponentButton);
}
panel1.add(tabAreaComponentsToolBar, "cell 0 7,alignx right,growx 0");
panel1.add(customComponentsTabbedPane, "cell 0 8");
}
add(panel1, "cell 0 0");
//======== panel2 ========
{
panel2.setLayout(new MigLayout(
"insets 0,hidemode 3",
// columns
"[grow,fill]",
// rows
"[]0" +
"[]" +
"[fill]" +
"[center]" +
"[center]" +
"[center]para" +
"[center]0" +
"[]" +
"[center]" +
"[center]" +
"[center]" +
"[]"));
//---- tabIconPlacementLabel ----
tabIconPlacementLabel.setText("Tab icon placement");
tabIconPlacementLabel.putClientProperty("FlatLaf.styleClass", "h3");
panel2.add(tabIconPlacementLabel, "cell 0 0");
//---- tabIconPlacementNodeLabel ----
tabIconPlacementNodeLabel.setText("(top/bottom/leading/trailing)");
tabIconPlacementNodeLabel.setEnabled(false);
tabIconPlacementNodeLabel.putClientProperty("FlatLaf.styleClass", "small");
panel2.add(tabIconPlacementNodeLabel, "cell 0 1");
panel2.add(iconTopTabbedPane, "cell 0 2");
panel2.add(iconBottomTabbedPane, "cell 0 3");
panel2.add(iconLeadingTabbedPane, "cell 0 4");
panel2.add(iconTrailingTabbedPane, "cell 0 5");
//---- tabAreaAlignmentLabel ----
tabAreaAlignmentLabel.setText("Tab area alignment");
tabAreaAlignmentLabel.putClientProperty("FlatLaf.styleClass", "h3");
panel2.add(tabAreaAlignmentLabel, "cell 0 6");
//---- tabAreaAlignmentNoteLabel ----
tabAreaAlignmentNoteLabel.setText("(leading/center/trailing/fill)");
tabAreaAlignmentNoteLabel.setEnabled(false);
tabAreaAlignmentNoteLabel.putClientProperty("FlatLaf.styleClass", "small");
panel2.add(tabAreaAlignmentNoteLabel, "cell 0 7");
panel2.add(alignLeadingTabbedPane, "cell 0 8");
panel2.add(alignCenterTabbedPane, "cell 0 9");
panel2.add(alignTrailingTabbedPane, "cell 0 10");
panel2.add(alignFillTabbedPane, "cell 0 11");
}
add(panel2, "cell 1 0,growy");
//======== panel3 ========
{
panel3.setLayout(new MigLayout(
"insets 0,hidemode 3",
// columns
"[grow,fill]",
// rows
"[]0" +
"[]" +
"[]" +
"[]" +
"[]para" +
"[]" +
"[]" +
"[]para" +
"[]0" +
"[]"));
//---- tabWidthModeLabel ----
tabWidthModeLabel.setText("Tab width mode");
tabWidthModeLabel.putClientProperty("FlatLaf.styleClass", "h3");
panel3.add(tabWidthModeLabel, "cell 0 0");
//---- tabWidthModeNoteLabel ----
tabWidthModeNoteLabel.setText("(preferred/equal/compact)");
tabWidthModeNoteLabel.setEnabled(false);
tabWidthModeNoteLabel.putClientProperty("FlatLaf.styleClass", "small");
panel3.add(tabWidthModeNoteLabel, "cell 0 1");
panel3.add(widthPreferredTabbedPane, "cell 0 2");
panel3.add(widthEqualTabbedPane, "cell 0 3");
panel3.add(widthCompactTabbedPane, "cell 0 4");
//---- minMaxTabWidthLabel ----
minMaxTabWidthLabel.setText("Minimum/maximum tab width");
minMaxTabWidthLabel.putClientProperty("FlatLaf.styleClass", "h3");
panel3.add(minMaxTabWidthLabel, "cell 0 5");
panel3.add(minimumTabWidthTabbedPane, "cell 0 6");
panel3.add(maximumTabWidthTabbedPane, "cell 0 7");
//---- tabAlignmentLabel ----
tabAlignmentLabel.setText("Tab title alignment");
tabAlignmentLabel.putClientProperty("FlatLaf.styleClass", "h3");
panel3.add(tabAlignmentLabel, "cell 0 8");
//======== panel5 ========
{
panel5.setLayout(new MigLayout(
"insets 0,hidemode 3",
panel6.setLayout(new MigLayout(
"insets dialog,hidemode 3",
// columns
"[grow,fill]para" +
"[fill]para" +
"[fill]",
// rows
"[]" +
"[]" +
"[]" +
"[]"));
"[grow,fill]"));
//---- tabAlignmentNoteLabel ----
tabAlignmentNoteLabel.setText("(leading/center/trailing)");
tabAlignmentNoteLabel.setEnabled(false);
tabAlignmentNoteLabel.putClientProperty("FlatLaf.styleClass", "small");
panel5.add(tabAlignmentNoteLabel, "cell 0 0");
//---- tabAlignmentNoteLabel2 ----
tabAlignmentNoteLabel2.setText("(trailing)");
tabAlignmentNoteLabel2.setEnabled(false);
tabAlignmentNoteLabel2.putClientProperty("FlatLaf.styleClass", "small");
panel5.add(tabAlignmentNoteLabel2, "cell 1 0,alignx right,growx 0");
panel5.add(tabAlignLeadingTabbedPane, "cell 0 1");
//======== tabAlignVerticalTabbedPane ========
//======== panel1 ========
{
tabAlignVerticalTabbedPane.setTabPlacement(SwingConstants.LEFT);
panel1.setLayout(new MigLayout(
"insets 0,hidemode 3",
// columns
"[grow,fill]",
// rows
"[]" +
"[fill]para" +
"[]0" +
"[]" +
"[]para" +
"[]" +
"[]para" +
"[]" +
"[]"));
//---- tabPlacementLabel ----
tabPlacementLabel.setText("Tab placement");
tabPlacementLabel.putClientProperty("FlatLaf.styleClass", "h3");
panel1.add(tabPlacementLabel, "cell 0 0");
//======== tabPlacementToolBar ========
{
tabPlacementToolBar.setFloatable(false);
tabPlacementToolBar.setBorder(BorderFactory.createEmptyBorder());
//---- topPlacementButton ----
topPlacementButton.setText("top");
topPlacementButton.setSelected(true);
topPlacementButton.putClientProperty("FlatLaf.styleClass", "small");
topPlacementButton.addActionListener(e -> tabPlacementChanged());
tabPlacementToolBar.add(topPlacementButton);
//---- bottomPlacementButton ----
bottomPlacementButton.setText("bottom");
bottomPlacementButton.putClientProperty("FlatLaf.styleClass", "small");
bottomPlacementButton.addActionListener(e -> tabPlacementChanged());
tabPlacementToolBar.add(bottomPlacementButton);
//---- leftPlacementButton ----
leftPlacementButton.setText("left");
leftPlacementButton.putClientProperty("FlatLaf.styleClass", "small");
leftPlacementButton.addActionListener(e -> tabPlacementChanged());
tabPlacementToolBar.add(leftPlacementButton);
//---- rightPlacementButton ----
rightPlacementButton.setText("right");
rightPlacementButton.putClientProperty("FlatLaf.styleClass", "small");
rightPlacementButton.addActionListener(e -> tabPlacementChanged());
tabPlacementToolBar.add(rightPlacementButton);
tabPlacementToolBar.addSeparator();
//---- scrollButton ----
scrollButton.setText("scroll");
scrollButton.putClientProperty("FlatLaf.styleClass", "small");
scrollButton.addActionListener(e -> scrollChanged());
tabPlacementToolBar.add(scrollButton);
//---- borderButton ----
borderButton.setText("border");
borderButton.putClientProperty("FlatLaf.styleClass", "small");
borderButton.addActionListener(e -> borderChanged());
tabPlacementToolBar.add(borderButton);
}
panel1.add(tabPlacementToolBar, "cell 0 0,alignx right,growx 0");
panel1.add(tabPlacementTabbedPane, "cell 0 1,width 300:300,height 100:100");
//---- tabLayoutLabel ----
tabLayoutLabel.setText("Tab layout");
tabLayoutLabel.putClientProperty("FlatLaf.styleClass", "h3");
panel1.add(tabLayoutLabel, "cell 0 2");
//======== tabLayoutToolBar ========
{
tabLayoutToolBar.setFloatable(false);
tabLayoutToolBar.setBorder(BorderFactory.createEmptyBorder());
//---- scrollTabLayoutButton ----
scrollTabLayoutButton.setText("scroll");
scrollTabLayoutButton.setSelected(true);
scrollTabLayoutButton.putClientProperty("FlatLaf.styleClass", "small");
scrollTabLayoutButton.addActionListener(e -> tabLayoutChanged());
tabLayoutToolBar.add(scrollTabLayoutButton);
//---- wrapTabLayoutButton ----
wrapTabLayoutButton.setText("wrap");
wrapTabLayoutButton.putClientProperty("FlatLaf.styleClass", "small");
wrapTabLayoutButton.addActionListener(e -> tabLayoutChanged());
tabLayoutToolBar.add(wrapTabLayoutButton);
}
panel1.add(tabLayoutToolBar, "cell 0 2,alignx right,growx 0");
//---- scrollLayoutNoteLabel ----
scrollLayoutNoteLabel.setText("(use mouse wheel to scroll; arrow button shows hidden tabs)");
scrollLayoutNoteLabel.setEnabled(false);
scrollLayoutNoteLabel.putClientProperty("FlatLaf.styleClass", "small");
panel1.add(scrollLayoutNoteLabel, "cell 0 3");
//---- wrapLayoutNoteLabel ----
wrapLayoutNoteLabel.setText("(probably better to use scroll layout?)");
wrapLayoutNoteLabel.setEnabled(false);
wrapLayoutNoteLabel.putClientProperty("FlatLaf.styleClass", "small");
panel1.add(wrapLayoutNoteLabel, "cell 0 3");
panel1.add(scrollLayoutTabbedPane, "cell 0 4");
panel1.add(wrapLayoutTabbedPane, "cell 0 4,width 100:100,height pref*2px");
//---- closableTabsLabel ----
closableTabsLabel.setText("Closable tabs");
closableTabsLabel.putClientProperty("FlatLaf.styleClass", "h3");
panel1.add(closableTabsLabel, "cell 0 5");
//======== closableTabsToolBar ========
{
closableTabsToolBar.setFloatable(false);
closableTabsToolBar.setBorder(BorderFactory.createEmptyBorder());
//---- squareCloseButton ----
squareCloseButton.setText("square");
squareCloseButton.setSelected(true);
squareCloseButton.putClientProperty("FlatLaf.styleClass", "small");
squareCloseButton.addActionListener(e -> closeButtonStyleChanged());
closableTabsToolBar.add(squareCloseButton);
//---- circleCloseButton ----
circleCloseButton.setText("circle");
circleCloseButton.putClientProperty("FlatLaf.styleClass", "small");
circleCloseButton.addActionListener(e -> closeButtonStyleChanged());
closableTabsToolBar.add(circleCloseButton);
//---- redCrossCloseButton ----
redCrossCloseButton.setText("red cross");
redCrossCloseButton.putClientProperty("FlatLaf.styleClass", "small");
redCrossCloseButton.addActionListener(e -> closeButtonStyleChanged());
closableTabsToolBar.add(redCrossCloseButton);
}
panel1.add(closableTabsToolBar, "cell 0 5,alignx right,growx 0");
panel1.add(closableTabsTabbedPane, "cell 0 6");
//---- tabAreaComponentsLabel ----
tabAreaComponentsLabel.setText("Custom tab area components");
tabAreaComponentsLabel.putClientProperty("FlatLaf.styleClass", "h3");
panel1.add(tabAreaComponentsLabel, "cell 0 7");
//======== tabAreaComponentsToolBar ========
{
tabAreaComponentsToolBar.setFloatable(false);
tabAreaComponentsToolBar.setBorder(BorderFactory.createEmptyBorder());
//---- leadingComponentButton ----
leadingComponentButton.setText("leading");
leadingComponentButton.setSelected(true);
leadingComponentButton.putClientProperty("FlatLaf.styleClass", "small");
leadingComponentButton.addActionListener(e -> customComponentsChanged());
tabAreaComponentsToolBar.add(leadingComponentButton);
//---- trailingComponentButton ----
trailingComponentButton.setText("trailing");
trailingComponentButton.setSelected(true);
trailingComponentButton.putClientProperty("FlatLaf.styleClass", "small");
trailingComponentButton.addActionListener(e -> customComponentsChanged());
tabAreaComponentsToolBar.add(trailingComponentButton);
}
panel1.add(tabAreaComponentsToolBar, "cell 0 7,alignx right,growx 0");
panel1.add(customComponentsTabbedPane, "cell 0 8");
}
panel5.add(tabAlignVerticalTabbedPane, "cell 1 1 1 3,growy");
panel5.add(tabAlignCenterTabbedPane, "cell 0 2");
panel5.add(tabAlignTrailingTabbedPane, "cell 0 3");
panel6.add(panel1, "cell 0 0");
//======== panel2 ========
{
panel2.setLayout(new MigLayout(
"insets 0,hidemode 3",
// columns
"[grow,fill]",
// rows
"[]0" +
"[]" +
"[fill]" +
"[center]" +
"[center]" +
"[center]para" +
"[center]0" +
"[]" +
"[center]" +
"[center]" +
"[center]" +
"[]"));
//---- tabIconPlacementLabel ----
tabIconPlacementLabel.setText("Tab icon placement");
tabIconPlacementLabel.putClientProperty("FlatLaf.styleClass", "h3");
panel2.add(tabIconPlacementLabel, "cell 0 0");
//---- tabIconPlacementNodeLabel ----
tabIconPlacementNodeLabel.setText("(top/bottom/leading/trailing)");
tabIconPlacementNodeLabel.setEnabled(false);
tabIconPlacementNodeLabel.putClientProperty("FlatLaf.styleClass", "small");
panel2.add(tabIconPlacementNodeLabel, "cell 0 1");
panel2.add(iconTopTabbedPane, "cell 0 2");
panel2.add(iconBottomTabbedPane, "cell 0 3");
panel2.add(iconLeadingTabbedPane, "cell 0 4");
panel2.add(iconTrailingTabbedPane, "cell 0 5");
//---- tabAreaAlignmentLabel ----
tabAreaAlignmentLabel.setText("Tab area alignment");
tabAreaAlignmentLabel.putClientProperty("FlatLaf.styleClass", "h3");
panel2.add(tabAreaAlignmentLabel, "cell 0 6");
//---- tabAreaAlignmentNoteLabel ----
tabAreaAlignmentNoteLabel.setText("(leading/center/trailing/fill)");
tabAreaAlignmentNoteLabel.setEnabled(false);
tabAreaAlignmentNoteLabel.putClientProperty("FlatLaf.styleClass", "small");
panel2.add(tabAreaAlignmentNoteLabel, "cell 0 7");
panel2.add(alignLeadingTabbedPane, "cell 0 8");
panel2.add(alignCenterTabbedPane, "cell 0 9");
panel2.add(alignTrailingTabbedPane, "cell 0 10");
panel2.add(alignFillTabbedPane, "cell 0 11");
}
panel6.add(panel2, "cell 1 0,growy");
//======== panel3 ========
{
panel3.setLayout(new MigLayout(
"insets 0,hidemode 3",
// columns
"[grow,fill]",
// rows
"[]0" +
"[]" +
"[]" +
"[]" +
"[]para" +
"[]" +
"[]" +
"[]para" +
"[]0" +
"[]"));
//---- tabWidthModeLabel ----
tabWidthModeLabel.setText("Tab width mode");
tabWidthModeLabel.putClientProperty("FlatLaf.styleClass", "h3");
panel3.add(tabWidthModeLabel, "cell 0 0");
//---- tabWidthModeNoteLabel ----
tabWidthModeNoteLabel.setText("(preferred/equal/compact)");
tabWidthModeNoteLabel.setEnabled(false);
tabWidthModeNoteLabel.putClientProperty("FlatLaf.styleClass", "small");
panel3.add(tabWidthModeNoteLabel, "cell 0 1");
panel3.add(widthPreferredTabbedPane, "cell 0 2");
panel3.add(widthEqualTabbedPane, "cell 0 3");
panel3.add(widthCompactTabbedPane, "cell 0 4");
//---- minMaxTabWidthLabel ----
minMaxTabWidthLabel.setText("Minimum/maximum tab width");
minMaxTabWidthLabel.putClientProperty("FlatLaf.styleClass", "h3");
panel3.add(minMaxTabWidthLabel, "cell 0 5");
panel3.add(minimumTabWidthTabbedPane, "cell 0 6");
panel3.add(maximumTabWidthTabbedPane, "cell 0 7");
//---- tabAlignmentLabel ----
tabAlignmentLabel.setText("Tab title alignment");
tabAlignmentLabel.putClientProperty("FlatLaf.styleClass", "h3");
panel3.add(tabAlignmentLabel, "cell 0 8");
//======== panel5 ========
{
panel5.setLayout(new MigLayout(
"insets 0,hidemode 3",
// columns
"[grow,fill]para" +
"[fill]",
// rows
"[]" +
"[]" +
"[]" +
"[]" +
"[]"));
//---- tabAlignmentNoteLabel ----
tabAlignmentNoteLabel.setText("(leading/center/trailing)");
tabAlignmentNoteLabel.setEnabled(false);
tabAlignmentNoteLabel.putClientProperty("FlatLaf.styleClass", "small");
panel5.add(tabAlignmentNoteLabel, "cell 0 0");
//---- tabAlignmentNoteLabel2 ----
tabAlignmentNoteLabel2.setText("(trailing)");
tabAlignmentNoteLabel2.setEnabled(false);
tabAlignmentNoteLabel2.putClientProperty("FlatLaf.styleClass", "small");
panel5.add(tabAlignmentNoteLabel2, "cell 1 0,alignx right,growx 0");
panel5.add(tabAlignLeadingTabbedPane, "cell 0 1");
//======== tabAlignVerticalTabbedPane ========
{
tabAlignVerticalTabbedPane.setTabPlacement(SwingConstants.LEFT);
}
panel5.add(tabAlignVerticalTabbedPane, "cell 1 1 1 4,growy");
panel5.add(tabAlignCenterTabbedPane, "cell 0 2");
panel5.add(tabAlignTrailingTabbedPane, "cell 0 3");
}
panel3.add(panel5, "cell 0 9");
}
panel6.add(panel3, "cell 2 0");
}
panel3.add(panel5, "cell 0 9");
tabsScrollPane.setViewportView(panel6);
}
add(panel3, "cell 2 0");
add(separator2, "cell 0 1 3 1");
add(tabsScrollPane, "cell 0 0");
add(separator2, "cell 0 1");
//======== panel4 ========
{
panel4.setLayout(new MigLayout(
"insets 0,hidemode 3",
"insets panel,hidemode 3",
// columns
"[]" +
"[fill]para" +
"[fill]" +
"[fill]para" +
"[fill]" +
"[fill]",
// rows
"[]" +
@@ -770,38 +805,9 @@ class TabsPanel
}
panel4.add(scrollButtonsPolicyToolBar, "cell 1 0");
//---- scrollButtonsPlacementLabel ----
scrollButtonsPlacementLabel.setText("Scroll buttons placement:");
panel4.add(scrollButtonsPlacementLabel, "cell 2 0");
//======== scrollButtonsPlacementToolBar ========
{
scrollButtonsPlacementToolBar.setFloatable(false);
scrollButtonsPlacementToolBar.setBorder(BorderFactory.createEmptyBorder());
//---- scrollBothButton ----
scrollBothButton.setText("both");
scrollBothButton.setSelected(true);
scrollBothButton.putClientProperty("FlatLaf.styleClass", "small");
scrollBothButton.addActionListener(e -> scrollButtonsPlacementChanged());
scrollButtonsPlacementToolBar.add(scrollBothButton);
//---- scrollTrailingButton ----
scrollTrailingButton.setText("trailing");
scrollTrailingButton.putClientProperty("FlatLaf.styleClass", "small");
scrollTrailingButton.addActionListener(e -> scrollButtonsPlacementChanged());
scrollButtonsPlacementToolBar.add(scrollTrailingButton);
}
panel4.add(scrollButtonsPlacementToolBar, "cell 3 0");
//---- showTabSeparatorsCheckBox ----
showTabSeparatorsCheckBox.setText("Show tab separators");
showTabSeparatorsCheckBox.addActionListener(e -> showTabSeparatorsChanged());
panel4.add(showTabSeparatorsCheckBox, "cell 4 0");
//---- tabsPopupPolicyLabel ----
tabsPopupPolicyLabel.setText("Tabs popup policy:");
panel4.add(tabsPopupPolicyLabel, "cell 0 1");
panel4.add(tabsPopupPolicyLabel, "cell 2 0");
//======== tabsPopupPolicyToolBar ========
{
@@ -821,7 +827,36 @@ class TabsPanel
popupNeverButton.addActionListener(e -> tabsPopupPolicyChanged());
tabsPopupPolicyToolBar.add(popupNeverButton);
}
panel4.add(tabsPopupPolicyToolBar, "cell 1 1");
panel4.add(tabsPopupPolicyToolBar, "cell 3 0");
//---- showTabSeparatorsCheckBox ----
showTabSeparatorsCheckBox.setText("Show tab separators");
showTabSeparatorsCheckBox.addActionListener(e -> showTabSeparatorsChanged());
panel4.add(showTabSeparatorsCheckBox, "cell 4 0 2 1,alignx left,growx 0");
//---- scrollButtonsPlacementLabel ----
scrollButtonsPlacementLabel.setText("Scroll buttons placement:");
panel4.add(scrollButtonsPlacementLabel, "cell 0 1");
//======== scrollButtonsPlacementToolBar ========
{
scrollButtonsPlacementToolBar.setFloatable(false);
scrollButtonsPlacementToolBar.setBorder(BorderFactory.createEmptyBorder());
//---- scrollBothButton ----
scrollBothButton.setText("both");
scrollBothButton.setSelected(true);
scrollBothButton.putClientProperty("FlatLaf.styleClass", "small");
scrollBothButton.addActionListener(e -> scrollButtonsPlacementChanged());
scrollButtonsPlacementToolBar.add(scrollBothButton);
//---- scrollTrailingButton ----
scrollTrailingButton.setText("trailing");
scrollTrailingButton.putClientProperty("FlatLaf.styleClass", "small");
scrollTrailingButton.addActionListener(e -> scrollButtonsPlacementChanged());
scrollButtonsPlacementToolBar.add(scrollTrailingButton);
}
panel4.add(scrollButtonsPlacementToolBar, "cell 1 1");
//---- tabTypeLabel ----
tabTypeLabel.setText("Tab type:");
@@ -845,8 +880,44 @@ class TabsPanel
tabTypeToolBar.add(cardTabTypeButton);
}
panel4.add(tabTypeToolBar, "cell 3 1");
//---- tabRotationLabel ----
tabRotationLabel.setText("Tab rotation:");
panel4.add(tabRotationLabel, "cell 4 1");
//======== tabRotationToolBar ========
{
tabRotationToolBar.setFloatable(false);
tabRotationToolBar.setBorder(BorderFactory.createEmptyBorder());
//---- rotationNoneButton ----
rotationNoneButton.setText("none");
rotationNoneButton.setSelected(true);
rotationNoneButton.putClientProperty("FlatLaf.styleClass", "small");
rotationNoneButton.addActionListener(e -> tabRotationChanged());
tabRotationToolBar.add(rotationNoneButton);
//---- rotationAutoButton ----
rotationAutoButton.setText("auto");
rotationAutoButton.putClientProperty("FlatLaf.styleClass", "small");
rotationAutoButton.addActionListener(e -> tabRotationChanged());
tabRotationToolBar.add(rotationAutoButton);
//---- rotationLeftButton ----
rotationLeftButton.setText("left");
rotationLeftButton.putClientProperty("FlatLaf.styleClass", "small");
rotationLeftButton.addActionListener(e -> tabRotationChanged());
tabRotationToolBar.add(rotationLeftButton);
//---- rotationRightButton ----
rotationRightButton.setText("right");
rotationRightButton.putClientProperty("FlatLaf.styleClass", "small");
rotationRightButton.addActionListener(e -> tabRotationChanged());
tabRotationToolBar.add(rotationRightButton);
}
panel4.add(tabRotationToolBar, "cell 5 1");
}
add(panel4, "cell 0 2 3 1");
add(panel4, "cell 0 2");
//---- tabPlacementButtonGroup ----
ButtonGroup tabPlacementButtonGroup = new ButtonGroup();
@@ -872,20 +943,27 @@ class TabsPanel
scrollButtonsPolicyButtonGroup.add(scrollAsNeededButton);
scrollButtonsPolicyButtonGroup.add(scrollNeverButton);
//---- scrollButtonsPlacementButtonGroup ----
ButtonGroup scrollButtonsPlacementButtonGroup = new ButtonGroup();
scrollButtonsPlacementButtonGroup.add(scrollBothButton);
scrollButtonsPlacementButtonGroup.add(scrollTrailingButton);
//---- tabsPopupPolicyButtonGroup ----
ButtonGroup tabsPopupPolicyButtonGroup = new ButtonGroup();
tabsPopupPolicyButtonGroup.add(popupAsNeededButton);
tabsPopupPolicyButtonGroup.add(popupNeverButton);
//---- scrollButtonsPlacementButtonGroup ----
ButtonGroup scrollButtonsPlacementButtonGroup = new ButtonGroup();
scrollButtonsPlacementButtonGroup.add(scrollBothButton);
scrollButtonsPlacementButtonGroup.add(scrollTrailingButton);
//---- tabTypeButtonGroup ----
ButtonGroup tabTypeButtonGroup = new ButtonGroup();
tabTypeButtonGroup.add(underlinedTabTypeButton);
tabTypeButtonGroup.add(cardTabTypeButton);
//---- tabRotationButtonGroup ----
ButtonGroup tabRotationButtonGroup = new ButtonGroup();
tabRotationButtonGroup.add(rotationNoneButton);
tabRotationButtonGroup.add(rotationAutoButton);
tabRotationButtonGroup.add(rotationLeftButton);
tabRotationButtonGroup.add(rotationRightButton);
// JFormDesigner - End of component initialization //GEN-END:initComponents
if( FlatLafDemo.screenshotsMode ) {
@@ -961,18 +1039,24 @@ class TabsPanel
private JToggleButton scrollAsNeededSingleButton;
private JToggleButton scrollAsNeededButton;
private JToggleButton scrollNeverButton;
private JLabel scrollButtonsPlacementLabel;
private JToolBar scrollButtonsPlacementToolBar;
private JToggleButton scrollBothButton;
private JToggleButton scrollTrailingButton;
private JCheckBox showTabSeparatorsCheckBox;
private JLabel tabsPopupPolicyLabel;
private JToolBar tabsPopupPolicyToolBar;
private JToggleButton popupAsNeededButton;
private JToggleButton popupNeverButton;
private JCheckBox showTabSeparatorsCheckBox;
private JLabel scrollButtonsPlacementLabel;
private JToolBar scrollButtonsPlacementToolBar;
private JToggleButton scrollBothButton;
private JToggleButton scrollTrailingButton;
private JLabel tabTypeLabel;
private JToolBar tabTypeToolBar;
private JToggleButton underlinedTabTypeButton;
private JToggleButton cardTabTypeButton;
private JLabel tabRotationLabel;
private JToolBar tabRotationToolBar;
private JToggleButton rotationNoneButton;
private JToggleButton rotationAutoButton;
private JToggleButton rotationLeftButton;
private JToggleButton rotationRightButton;
// JFormDesigner - End of variables declaration //GEN-END:variables
}

View File

@@ -0,0 +1,4 @@
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.5 14.5L6 10M14.5 1.5L9.99998 6.00001M2.5 9.50001H6.5L6.5 13.5M13.5 6.5L9.5 6.50003V2.5" stroke="#6E6E6E" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 396 B

View File

@@ -0,0 +1,4 @@
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.5 9.5L2 14M9.50003 6.50004L14 2.00001M5.5 14.5H1.5V10.5M10.5 1.5L14.5 1.50002V5.5" stroke="#6E6E6E" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 391 B

View File

@@ -33,7 +33,7 @@ build script:
artifactId: flatlaf-extras
version: (see button below)
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)

View File

@@ -133,8 +133,8 @@ public class FlatAnimatedLafChange
}
/**
* Starts an animation that shows the snapshot (created by {@link #showSnapshot()}
* with an decreasing alpha. At the end, the snapshot is removed and the new UI is shown.
* Starts an animation that shows the snapshot (created by {@link #showSnapshot()})
* with a decreasing alpha. At the end, the snapshot is removed and the new UI is shown.
* Invoke after updating UI.
*/
public static void hideSnapshotWithAnimation() {

View File

@@ -119,7 +119,7 @@ public class FlatDesktop
(proxy, method, args) -> {
// Use invokeLater to release the listener firing for the case
// that the action listener shows a modal dialog.
// This (hopefully) prevents application hunging.
// This (hopefully) prevents application hanging.
EventQueue.invokeLater( () -> {
handler.run();
} );

View File

@@ -87,7 +87,7 @@ public class FlatSVGIcon
* in the tag {@code <svg>} are used as icon size.
* <p>
* If using Java modules, the package containing the icon must be opened in {@code module-info.java}.
* Otherwise use {@link #FlatSVGIcon(URL)}.
* Otherwise, use {@link #FlatSVGIcon(URL)}.
* <p>
* This is cheap operation because the icon is only loaded when used.
*
@@ -106,7 +106,7 @@ public class FlatSVGIcon
* in the tag {@code <svg>} are used as icon size.
* <p>
* If using Java modules, the package containing the icon must be opened in {@code module-info.java}.
* Otherwise use {@link #FlatSVGIcon(URL)}.
* Otherwise, use {@link #FlatSVGIcon(URL)}.
* <p>
* This is cheap operation because the icon is only loaded when used.
*
@@ -124,7 +124,7 @@ public class FlatSVGIcon
* The icon is scaled if the given size is different to the size specified in the SVG file.
* <p>
* If using Java modules, the package containing the icon must be opened in {@code module-info.java}.
* Otherwise use {@link #FlatSVGIcon(URL)}.
* Otherwise, use {@link #FlatSVGIcon(URL)}.
* <p>
* This is cheap operation because the icon is only loaded when used.
*
@@ -144,7 +144,7 @@ public class FlatSVGIcon
* The icon is scaled if the given size is different to the size specified in the SVG file.
* <p>
* If using Java modules, the package containing the icon must be opened in {@code module-info.java}.
* Otherwise use {@link #FlatSVGIcon(URL)}.
* Otherwise, use {@link #FlatSVGIcon(URL)}.
* <p>
* This is cheap operation because the icon is only loaded when used.
*
@@ -166,7 +166,7 @@ public class FlatSVGIcon
* by the given scale factor.
* <p>
* If using Java modules, the package containing the icon must be opened in {@code module-info.java}.
* Otherwise use {@link #FlatSVGIcon(URL)}.
* Otherwise, use {@link #FlatSVGIcon(URL)}.
* <p>
* This is cheap operation because the icon is only loaded when used.
*
@@ -187,7 +187,7 @@ public class FlatSVGIcon
* by the given scale factor.
* <p>
* If using Java modules, the package containing the icon must be opened in {@code module-info.java}.
* Otherwise use {@link #FlatSVGIcon(URL)}.
* Otherwise, use {@link #FlatSVGIcon(URL)}.
* <p>
* This is cheap operation because the icon is only loaded when used.
*
@@ -259,7 +259,7 @@ public class FlatSVGIcon
* <p>
* The input stream is loaded, parsed and closed immediately.
*
* @param in the input stream for reading a SVG resource
* @param in the input stream for reading an SVG resource
* @throws IOException if an I/O exception occurs
* @since 2
*/

View File

@@ -46,10 +46,10 @@ public class FlatSVGUtils
* then a single multi-resolution image is returned that creates images on demand
* for requested sizes from SVG.
* This has the advantage that only images for used sizes are created.
* Also if unusual sizes are requested (e.g. 18x18), then they are created from SVG.
* Also, if unusual sizes are requested (e.g. 18x18), then they are created from SVG.
* <p>
* If using Java modules, the package containing the SVG must be opened in {@code module-info.java}.
* Otherwise use {@link #createWindowIconImages(URL)}.
* Otherwise, use {@link #createWindowIconImages(URL)}.
*
* @param svgName the name of the SVG resource (a '/'-separated path)
* @return list of icon images with different sizes (16x16, 20x20, 24x24, 28x28, 32x32, 48x48 and 64x64)
@@ -69,7 +69,7 @@ public class FlatSVGUtils
* then a single multi-resolution image is returned that creates images on demand
* for requested sizes from SVG.
* This has the advantage that only images for used sizes are created.
* Also if unusual sizes are requested (e.g. 18x18), then they are created from SVG.
* Also, if unusual sizes are requested (e.g. 18x18), then they are created from SVG.
* <p>
* This method is useful if using Java modules and the package containing the SVG
* is not opened in {@code module-info.java}.
@@ -92,7 +92,7 @@ public class FlatSVGUtils
// any size is created on demand when
// MultiResolutionImage.getResolutionVariant(double destImageWidth, double destImageHeight)
// is invoked.
// This sizes are only used by MultiResolutionImage.getResolutionVariants().
// These sizes are only used by MultiResolutionImage.getResolutionVariants().
new Dimension( 16, 16 ), // 100%
new Dimension( 20, 20 ), // 125%
new Dimension( 24, 24 ), // 150%
@@ -120,7 +120,7 @@ public class FlatSVGUtils
* Creates a buffered image and renders the given SVG into it.
* <p>
* If using Java modules, the package containing the SVG must be opened in {@code module-info.java}.
* Otherwise use {@link #svg2image(URL, int, int)}.
* Otherwise, use {@link #svg2image(URL, int, int)}.
*
* @param svgName the name of the SVG resource (a '/'-separated path)
* @param width the width of the image
@@ -154,7 +154,7 @@ public class FlatSVGUtils
* Creates a buffered image and renders the given SVG into it.
* <p>
* If using Java modules, the package containing the SVG must be opened in {@code module-info.java}.
* Otherwise use {@link #svg2image(URL, float)}.
* Otherwise, use {@link #svg2image(URL, float)}.
*
* @param svgName the name of the SVG resource (a '/'-separated path)
* @param scaleFactor the amount by which the SVG size is scaled

View File

@@ -557,7 +557,7 @@ public class FlatUIDefaultsInspector
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
panel = new JPanel();
filterPanel = new JPanel();
flterLabel = new JLabel();
filterLabel = new JLabel();
filterField = new FlatTextField();
valueTypeLabel = new JLabel();
valueTypeField = new JComboBox<>();
@@ -580,11 +580,11 @@ public class FlatUIDefaultsInspector
((GridBagLayout)filterPanel.getLayout()).columnWeights = new double[] {0.0, 1.0, 0.0, 0.0, 1.0E-4};
((GridBagLayout)filterPanel.getLayout()).rowWeights = new double[] {0.0, 1.0E-4};
//---- flterLabel ----
flterLabel.setText("Filter:");
flterLabel.setLabelFor(filterField);
flterLabel.setDisplayedMnemonic('F');
filterPanel.add(flterLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
//---- filterLabel ----
filterLabel.setText("Filter:");
filterLabel.setLabelFor(filterField);
filterLabel.setDisplayedMnemonic('F');
filterPanel.add(filterLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
new Insets(0, 0, 0, 10), 0, 0));
@@ -668,7 +668,7 @@ public class FlatUIDefaultsInspector
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
private JPanel panel;
private JPanel filterPanel;
private JLabel flterLabel;
private JLabel filterLabel;
private FlatTextField filterField;
private JLabel valueTypeLabel;
private JComboBox<String> valueTypeField;

View File

@@ -15,7 +15,7 @@ new FormModel {
} ) {
name: "filterPanel"
add( new FormComponent( "javax.swing.JLabel" ) {
name: "flterLabel"
name: "filterLabel"
"text": "Filter:"
"labelFor": new FormReference( "filterField" )
"displayedMnemonic": 70

View File

@@ -33,14 +33,14 @@ public class FlatButton
public enum ButtonType { none, square, roundRect, tab, help, toolBarButton, borderless }
/**
* Returns type of a button.
* Returns type of button.
*/
public ButtonType getButtonType() {
return getClientPropertyEnumString( BUTTON_TYPE, ButtonType.class, null, ButtonType.none );
}
/**
* Specifies type of a button.
* Specifies type of button.
*/
public void setButtonType( ButtonType buttonType ) {
if( buttonType == ButtonType.none )

View File

@@ -106,7 +106,7 @@ public class FlatFormattedTextField
* The component should be not opaque because the text field border is painted
* slightly inside the usually visible border in some cases.
* E.g. when focused (in some themes) or when an outline color is specified
* (see {@link #setOutline(Object)}.
* (see {@link #setOutline(Object)}).
*
* @since 2
*/
@@ -135,7 +135,7 @@ public class FlatFormattedTextField
* The component should be not opaque because the text field border is painted
* slightly inside the usually visible border in some cases.
* E.g. when focused (in some themes) or when an outline color is specified
* (see {@link #setOutline(Object)}.
* (see {@link #setOutline(Object)}).
*
* @since 2
*/

View File

@@ -106,7 +106,7 @@ public class FlatPasswordField
* The component should be not opaque because the text field border is painted
* slightly inside the usually visible border in some cases.
* E.g. when focused (in some themes) or when an outline color is specified
* (see {@link #setOutline(Object)}.
* (see {@link #setOutline(Object)}).
*
* @since 2
*/
@@ -135,7 +135,7 @@ public class FlatPasswordField
* The component should be not opaque because the text field border is painted
* slightly inside the usually visible border in some cases.
* E.g. when focused (in some themes) or when an outline color is specified
* (see {@link #setOutline(Object)}.
* (see {@link #setOutline(Object)}).
*
* @since 2
*/

View File

@@ -502,6 +502,29 @@ public class FlatTabbedPane
}
// NOTE: enum names must be equal to allowed strings
/** @since 3.3 */ public enum TabRotation { none, auto, left, right }
/**
* Returns how the tabs should be rotated.
*
* @since 3.3
*/
public TabRotation getTabRotation() {
return getClientPropertyEnumString( TABBED_PANE_TAB_ROTATION, TabRotation.class,
"TabbedPane.tabRotation", TabRotation.none );
}
/**
* Specifies how the tabs should be rotated.
*
* @since 3.3
*/
public void setTabRotation( TabRotation tabRotation ) {
putClientPropertyEnumString( TABBED_PANE_TAB_ROTATION, tabRotation );
}
/**
* Returns the tab icon placement (relative to tab title).
*/

View File

@@ -105,7 +105,7 @@ public class FlatTextField
* The component should be not opaque because the text field border is painted
* slightly inside the usually visible border in some cases.
* E.g. when focused (in some themes) or when an outline color is specified
* (see {@link #setOutline(Object)}.
* (see {@link #setOutline(Object)}).
*
* @since 2
*/
@@ -134,7 +134,7 @@ public class FlatTextField
* The component should be not opaque because the text field border is painted
* slightly inside the usually visible border in some cases.
* E.g. when focused (in some themes) or when an outline color is specified
* (see {@link #setOutline(Object)}.
* (see {@link #setOutline(Object)}).
*
* @since 2
*/

View File

@@ -31,14 +31,14 @@ public class FlatToggleButton
implements FlatComponentExtension, FlatStyleableComponent
{
/**
* Returns type of a button.
* Returns type of button.
*/
public ButtonType getButtonType() {
return getClientPropertyEnumString( BUTTON_TYPE, ButtonType.class, null, ButtonType.none );
}
/**
* Specifies type of a button.
* Specifies type of button.
*/
public void setButtonType( ButtonType buttonType ) {
if( buttonType == ButtonType.none )

View File

@@ -30,8 +30,8 @@ import com.formdev.flatlaf.FlatLaf;
* <p>
* The initial state is {@link State#INDETERMINATE}.
* <p>
* By default the third state is allowed and clicking on the checkbox cycles thru all
* three states. If you want that the user can cycle only thru two states, disallow
* By default, the third state is allowed and clicking on the checkbox cycles through all
* three states. If you want that the user can cycle only through two states, disallow
* intermediate state using {@link #setAllowIndeterminate(boolean)}. Then you can still
* set the indeterminate state via API if necessary, but the user can not.
* <p>

View File

@@ -18,8 +18,8 @@ package com.formdev.flatlaf.extras.resources;
/**
* The only purpose of this file is to add a .class file to this package to make it non-empty.
* Otherwise the compiler outputs a warning because this package is opend in module-info.java.
* Also when using --patch-module (e.g. from an IDE), an error would occur for empty packages.
* Otherwise, the compiler outputs a warning because this package is opened in module-info.java.
* Also, when using --patch-module (e.g. from an IDE), an error would occur for empty packages.
*
* @author Karl Tauber
*/

View File

@@ -175,6 +175,9 @@ Spinner.buttonPressedArrowColor = Spinner.buttonArrowColor
#---- SplitPaneDivider ----
SplitPaneDivider.draggingColor = SplitPane.background
SplitPaneDivider.hoverColor = SplitPane.background
SplitPaneDivider.pressedColor = SplitPane.background
SplitPaneDivider.oneTouchHoverArrowColor = SplitPaneDivider.oneTouchArrowColor
SplitPaneDivider.oneTouchPressedArrowColor = SplitPaneDivider.oneTouchArrowColor

View File

@@ -100,6 +100,6 @@ build script:
artifactId: flatlaf-fonts-inter
version: (see button below)
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)

View File

@@ -18,7 +18,7 @@
// For maven compatibility, <font-version> should be in format <major>.<minor>[.<micro>].
// <build-number> is optional and should be incremented only if a new release is
// necessary, but the <font-version> has not changed.
version = "3.19"
version = "4.0"
if( !rootProject.hasProperty( "release" ) )
version = version.toString() + "-SNAPSHOT"
@@ -56,7 +56,7 @@ tasks {
testLogging.exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
}
withType<PublishToMavenRepository>().configureEach {
withType<AbstractPublishToMaven>().configureEach {
onlyIf { !rootProject.hasProperty( "skipFonts" ) }
}
}

Some files were not shown because too many files have changed in this diff Show More