Compare commits

...

107 Commits
3.3 ... 3.5.1

Author SHA1 Message Date
Karl Tauber
438ec6ac5c release 3.5.1 2024-08-05 18:17:34 +02:00
Karl Tauber
8089e66642 SubMenuUsabilityHelper: added system property flatlaf.useSubMenuSafeTriangle to allow disabling submenu safe triangle for SWTSwing (issue #870)
also check whether EventQueue.push() succeeded; if not, disable submenu safe triangle
2024-08-05 13:56:43 +02:00
Karl Tauber
d27e0561f2 HiDPI: fixed occasional wrong repaint areas when using HiDPIUtils.installHiDPIRepaintManager() (see PR #864) 2024-08-04 15:14:46 +02:00
Karl Tauber
97b21bfa8b HTML: fixed occasional cutoff wrapped text when using multi-line text in HTML tags <h1>...<h6>, <code>, <kbd>, <big>, <small> or <samp> (issue #873; regression in 3.5) 2024-08-04 14:45:17 +02:00
Karl Tauber
ec4343ed30 TabbedPane: fixed ArrayIndexOutOfBoundsException in case of using "card" tab type and using a custom tab selection model that returns -1 for selected tab (issue #875) 2024-08-03 00:00:00 +02:00
Karl Tauber
948decb3b5 Popup: fixed UnsupportedOperationException: PERPIXEL_TRANSLUCENT translucency is not supported exception on Haiku OS when showing popup (partly) outside of window (issue #869) 2024-08-02 23:47:29 +02:00
Karl Tauber
d510fee7f6 CHANGELOG.md: moved note regarding disabled rounded popup border on macOS from 3.4.1 to 3.5 (wrong in commit 32b0f1ba10) 2024-07-17 00:23:41 +02:00
Karl Tauber
70b7a3d662 release 3.5 2024-07-16 22:17:04 +02:00
Karl Tauber
b142a6f31e FlatInspector: added FlatLaf style to tooltip 2024-07-16 13:58:53 +02:00
Karl Tauber
14705a9b30 Menu: show arrow icon for top-level JMenu if used in vertical JMenuBar (issue #867) 2024-07-16 13:48:47 +02:00
Karl Tauber
32b0f1ba10 macOS: (temporary) disabled rounded popup border (see PR #772) on macOS 14.4+ because it may freeze the application and crash the macOS WindowServer process 2024-07-16 01:25:10 +02:00
Karl Tauber
cbffdf4900 HiDPIUtils: fixed javadoc error 2024-07-15 19:01:41 +02:00
Karl Tauber
1238da5e54 Merge PR #864: HiDPI: fix incomplete component repainting at 125% or 175% scaling on Windows 2024-07-15 18:43:46 +02:00
Karl Tauber
cba203be09 Merge PR #856: Table: rounded selection 2024-07-15 18:39:12 +02:00
Karl Tauber
d89c6156b9 HiDPI: introduced (optional) repaint manager that fixes incomplete component paintings at 125% or 175% scaling on Windows (issues #860 and #582) 2024-07-10 23:02:19 +02:00
Karl Tauber
e06475b3b7 HiDPIUtils: javadoc fixes for previous commit 2024-07-05 23:29:38 +02:00
Karl Tauber
5ff99bd45e HiDPI: fixed incomplete component paintings at 125% or 175% scaling on Windows (issues #860 and #582) 2024-07-05 22:18:27 +02:00
Karl Tauber
127dd6ac41 Table: fixed repainting of rounded selection when selection changes 2024-06-27 23:48:14 +02:00
Karl Tauber
9e05384513 Table: fixed location of painted grid lines when scaled with fractional scale factors (e.g. 125%-175%) 2024-06-24 19:22:03 +02:00
Karl Tauber
9ffda72ae3 Table: support rounded selection (issue #844) 2024-06-24 19:20:55 +02:00
Karl Tauber
72a4c00e72 Window decorations: fixed black line sometimes painted on top of (native) window border on Windows 11 (issue #852)
Windows binaries built and signed locally in clean workspace
2024-06-21 19:58:36 +02:00
Karl Tauber
c95e95ef67 FileChooser: wrap shortcuts in scroll pane (issue #828)
added tooltips to shortcuts
disabled group hover effect on shortcuts
2024-06-21 19:10:39 +02:00
Karl Tauber
0c0d4bffbf ScrollPane: fixed/improved border painting at 125% - 175% scaling to avoid different border thicknesses (issue #743) 2024-06-14 19:19:22 +02:00
Karl Tauber
2a494b1d60 Testing: easier testing of various system scale factors using Alt+Shift+F1...F12 2024-06-13 12:20:32 +02:00
Karl Tauber
1463723e52 Table: Fixed painting of alternating rows below table if auto-resize mode is JTable.AUTO_RESIZE_OFF and table is smaller than scroll pane. Below alternating rows were not updated when table width changed and were painted on wrong side in right-to-left component orientation 2024-06-08 11:10:46 +02:00
Karl Tauber
9ade48d078 FlatHTML: javadoc fixes 2024-06-04 13:10:52 +02:00
Karl Tauber
7ba8274fd4 FlatLineBorder: use arc from Label or Panel, if not specified in border (issue #842) 2024-06-01 14:53:44 +02:00
Karl Tauber
238443074c Theme Editor: updated rsyntaxtextarea to latest version 2024-05-31 14:06:57 +02:00
Karl Tauber
0decbec595 Theme Editor: on macOS, use larger window title bar (PR #779) 2024-05-31 13:57:01 +02:00
Karl Tauber
0eb77c7f72 Theme Editor: fixed occasional empty window on startup on macOS 2024-05-31 13:54:50 +02:00
Karl Tauber
f05df0db0a Button and ToggleButton: added more missing border colors for selected states (issue #848) 2024-05-31 10:41:33 +02:00
Karl Tauber
13fbaf1f74 fixed errors reported by Error Prone in commit 261d2b1fe8 2024-05-30 19:33:18 +02:00
Karl Tauber
969d2642de Button and ToggleButton: added missing border colors for pressed and selected states (issue #848) 2024-05-30 19:22:25 +02:00
Karl Tauber
17ce6d39b4 Button and ToggleButton: UI properties [Toggle]Button.selectedForeground and [Toggle]Button.pressedForeground did not work for HTML text (issue #848) 2024-05-30 17:45:28 +02:00
Karl Tauber
261d2b1fe8 HTML: Fixed font sizes for HTML tags <h1>...<h6>, <code>, <kbd>, <big>, <small> and <samp> in HTML text for components Button, CheckBox, RadioButton, MenuItem (and subclasses), JideLabel, JideButton, JXBusyLabel and JXHyperlink. Also fixed for Label and ToolTip if using Java 11+. 2024-05-30 16:39:23 +02:00
Karl Tauber
a54aeb3838 FlatTestFrame: automatically add scroll pane if content is very large (or screen is small) 2024-05-29 11:14:52 +02:00
Karl Tauber
cc4f9a9db5 Window decorations: window top border on Windows 10 in "full window content" mode was not fully repainted when activating or deactivating window (issue #809) 2024-05-28 16:16:03 +02:00
Karl Tauber
a311bac89b Popup: fixed flicker of popups (e.g. tooltips) while they are moving (e.g. following mouse pointer) (issues #832 and #672) 2024-05-22 14:02:40 +02:00
Karl Tauber
029f273dd9 Label: support painting background with rounded corners (issue #842)
Demo: added rounded panels and labels to "More Components" tab
2024-05-21 13:37:11 +02:00
Karl Tauber
bbbdd7e4d3 Panel: rounded background of panel with rounded corners is now painted even if panel is not opaque (issue #840) 2024-05-20 18:57:19 +02:00
Karl Tauber
3f3ef6b24f ProgressBar: log warning (including stack trace) when uninstalling indeterminate progress bar UI or using JProgressBar.setIndeterminate(false) not on AWT thread, because this may throw NPE in FlatProgressBarUI.paint() (issues #841 and #830) 2024-05-09 11:35:49 +02:00
Karl Tauber
8c3dfd4a36 Merge PR #834: Update flatlaf-intellij-themes/README.md 2024-04-23 13:04:13 +02:00
Hirun Chamara
af57599df9 Update README.md
install(); is deprecated
2024-04-23 11:22:13 +05:30
Karl Tauber
bde25f6ac8 release 3.4.1 2024-03-29 13:28:58 +01:00
Karl Tauber
c989b97ffa fixed custom properties file names for nested classes (e.g. IntelliJTheme$ThemeLaf.properties instead of ThemeLaf.properties)
(see issue #824)
2024-03-29 13:14:29 +01:00
Karl Tauber
5f5c225300 macOS: fixed crash when running in WebSwing (issue #826; regression in 3.4) 2024-03-26 13:24:05 +01:00
Karl Tauber
36e4071b7f FlatSVGIcon: use log level CONFIG instead of SEVERE and allow disabling logging (issue #823) 2024-03-24 17:29:57 +01:00
Karl Tauber
1068884bce change snapshot version from 3.5-SNAPSHOT to 3.4.1-SNAPSHOT 2024-03-24 17:10:43 +01:00
Karl Tauber
32d102dbc9 Native Libraries: added API version to:
- test whether native library matches the JAR (bad builds could e.g. ship a newer JAR with an older incompatible native library)
- invoke a method (to get API version) to check whether native library works correctly

if API version do not match, or method could not invoked correctly, disable usage of FlatLaf native library

Windows and macOS binaries built and signed locally in clean workspace
Linux binary built by GitHub Actions
2024-03-24 16:37:11 +01:00
Karl Tauber
4e1f092b98 FlatClientProperties: javadoc fixes 2024-03-24 12:45:48 +01:00
Karl Tauber
bd60a18ff4 SplitPane:
- update divider when client property `JSplitPane.expandableSide` changed
- Extras: added support for `JSplitPane.expandableSide` client property to `FlatSplitPane`
2024-03-24 12:42:39 +01:00
Karl Tauber
3b3d7d76eb Testing: added tab with random background color to FlatContainerTest and FlatJideOssContainerTest 2024-03-23 16:33:27 +01:00
Karl Tauber
ec76448e9f jsvg: updated to 1.4.0
jide-oss: updated to 3.7.15
2024-03-23 15:31:45 +01:00
Karl Tauber
872c84974c Merge PR #822: Request to add MCreator to list of apps using FlatLAF 2024-03-23 15:08:20 +01:00
Karl Tauber
5dd2008969 GitHub Actions: build using Java 22 (use toolchain because Gradle 8.7 does not support running on Java 22) 2024-03-23 14:13:43 +01:00
Karl Tauber
55ddac2bc7 Gradle: use simpler Kotlin DSL property assignment
https://blog.gradle.org/simpler-kotlin-dsl-property-assignment
2024-03-23 13:56:47 +01:00
Karl Tauber
a62dd22f83 Gradle: fixed warning regarding using deprecation Gradle features, making it incompatible with Gradle 9.0 2024-03-23 13:46:51 +01:00
Karl Tauber
a503879858 update to Gradle 8.7 2024-03-23 13:10:58 +01:00
Klemen
14f19dd735 Update README.md 2024-03-18 09:07:56 +01:00
Karl Tauber
9a727f68ce Window decorations: fixed missing window top border on Windows 10 in "full window content" mode (issue #809) 2024-03-17 18:09:19 +01:00
Karl Tauber
d26819d268 TabbedPane:
- updated comment regarding unsupported scroll buttons in right-to-left component orientation (see issue #815)
- removed unused on wrong code that tries to layout scroll buttons for right-to-left
2024-03-13 00:03:05 +01:00
Karl Tauber
44752cc9aa TabbedPane: fixed swapped back and forward scroll buttons when using TabbedPane.scrollButtonsPlacement = trailing (regression in FlatLaf 3.3 since commit 97495a6093) 2024-03-12 18:41:47 +01:00
Karl Tauber
11e0757387 FlatSVGIcon:
- setColorFilter() now returns `this`
- added method to enable/disable and clear SVGDocument cache
- do not filter red color in `paintSvgError()`
2024-03-12 13:36:56 +01:00
Karl Tauber
1f1ebc3c44 FlatSVGIcon: some additions to PR #817:
- create new `LinearGradientPaint` only if color has changed
- preserve old `LinearGradientPaint.colorSpace` and `transform`
2024-03-12 12:20:31 +01:00
Karl Tauber
4be97b6ea6 Merge PR #817: Add LinearGradient paint in svg color filter 2024-03-11 19:04:47 +01:00
laing raven
3facca5499 add LinearGradient paint in svg color filter 2024-03-10 19:12:29 +07:00
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
186 changed files with 8180 additions and 1634 deletions

View File

@@ -9,6 +9,14 @@ on:
- '*' - '*'
tags: tags:
- '[0-9]*' - '[0-9]*'
paths-ignore:
- '**.md'
- '.*'
- '**/.settings/**'
- 'flatlaf-core/svg/**'
- 'flatlaf-testing/dumps/**'
- 'flatlaf-testing/misc/**'
- 'images/**'
jobs: jobs:
build: build:
@@ -24,22 +32,23 @@ jobs:
- 8 - 8
- 11 # LTS - 11 # LTS
- 17 # LTS - 17 # LTS
- 21 # LTS
toolchain: [""] toolchain: [""]
include: include:
- java: 17 - java: 21
toolchain: 21 # latest toolchain: 22 # latest
steps: 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' if: matrix.java == '8'
- name: Setup Java ${{ matrix.java }} - name: Setup Java ${{ matrix.java }}
uses: actions/setup-java@v3 uses: actions/setup-java@v4
with: with:
java-version: ${{ matrix.java }} java-version: ${{ matrix.java }}
distribution: temurin # Java 8, 11 and 17 are pre-installed on ubuntu-latest distribution: temurin # Java 8, 11, 17 and 21 are pre-installed on ubuntu-latest
cache: gradle cache: gradle
- name: Check with Error Prone - name: Check with Error Prone
@@ -50,7 +59,7 @@ jobs:
run: ./gradlew build -Dtoolchain=${{ matrix.toolchain }} run: ./gradlew build -Dtoolchain=${{ matrix.toolchain }}
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
if: matrix.java == '11' if: matrix.java == '11'
with: with:
name: FlatLaf-build-artifacts name: FlatLaf-build-artifacts
@@ -70,10 +79,10 @@ jobs:
github.repository == 'JFormDesigner/FlatLaf' github.repository == 'JFormDesigner/FlatLaf'
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Setup Java 11 - name: Setup Java 11
uses: actions/setup-java@v3 uses: actions/setup-java@v4
with: with:
java-version: 11 java-version: 11
distribution: temurin # pre-installed on ubuntu-latest distribution: temurin # pre-installed on ubuntu-latest
@@ -106,10 +115,10 @@ jobs:
github.repository == 'JFormDesigner/FlatLaf' github.repository == 'JFormDesigner/FlatLaf'
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Setup Java 11 - name: Setup Java 11
uses: actions/setup-java@v3 uses: actions/setup-java@v4
with: with:
java-version: 11 java-version: 11
distribution: temurin # pre-installed on ubuntu-latest distribution: temurin # pre-installed on ubuntu-latest

View File

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

View File

@@ -13,6 +13,8 @@ on:
- 'flatlaf-natives/**' - 'flatlaf-natives/**'
- '.github/workflows/natives.yml' - '.github/workflows/natives.yml'
- 'gradle/wrapper/gradle-wrapper.properties' - 'gradle/wrapper/gradle-wrapper.properties'
- '!**.md'
- '!**/.settings/**'
jobs: jobs:
Natives: Natives:
@@ -26,12 +28,12 @@ jobs:
runs-on: ${{ matrix.os }}-latest runs-on: ${{ matrix.os }}-latest
steps: 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 - name: Setup Java 11
uses: actions/setup-java@v3 uses: actions/setup-java@v4
with: with:
java-version: 11 java-version: 11
distribution: temurin distribution: temurin
@@ -43,7 +45,7 @@ jobs:
run: ./gradlew build-natives --no-daemon run: ./gradlew build-natives --no-daemon
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: FlatLaf-natives-build-artifacts-${{ matrix.os }} name: FlatLaf-natives-build-artifacts-${{ matrix.os }}
path: | 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,141 @@
FlatLaf Change Log FlatLaf Change Log
================== ==================
## 3.5.1
#### Fixed bugs
- HTML: Fixed occasional cutoff wrapped text when using multi-line text in HTML
tags `<h1>`...`<h6>`, `<code>`, `<kbd>`, `<big>`, `<small>` or `<samp>`.
(issue #873; regression in 3.5)
- Popup: Fixed `UnsupportedOperationException: PERPIXEL_TRANSLUCENT translucency
is not supported` exception on Haiku OS when showing popup (partly) outside of
window. (issue #869)
- HiDPI: Fixed occasional wrong repaint areas when using
`HiDPIUtils.installHiDPIRepaintManager()`. (see PR #864)
- Added system property `flatlaf.useSubMenuSafeTriangle` to allow disabling
submenu safe triangle (PR #490) for
[SWTSwing](https://github.com/Chrriis/SWTSwing). (issue #870)
## 3.5
#### New features and improvements
- Table: Support rounded selection. (PR #856)
- Button and ToggleButton: Added border colors for pressed and selected states.
(issue #848)
- Label: Support painting background with rounded corners. (issue #842)
- Popup: Fixed flicker of popups (e.g. tooltips) while they are moving (e.g.
following mouse pointer). (issues #832 and #672)
- FileChooser: Wrap shortcuts in scroll pane. (issue #828)
- Theme Editor: On macOS, use larger window title bar. (PR #779)
#### Fixed bugs
- macOS: Disabled rounded popup border (see PR #772) on macOS 14.4+ because it
may freeze the application and crash the macOS WindowServer process (reports
vary from Finder restarts to OS restarts). This is a temporary change until a
solution is found. See NetBeans issues
[apache/netbeans#7560](https://github.com/apache/netbeans/issues/7560#issuecomment-2226439215)
and
[apache/netbeans#6647](https://github.com/apache/netbeans/issues/6647#issuecomment-2070124442).
- FlatLaf window decorations: Window top border on Windows 10 in "full window
content" mode was not fully repainted when activating or deactivating window.
(issue #809)
- Button and ToggleButton: UI properties `[Toggle]Button.selectedForeground` and
`[Toggle]Button.pressedForeground` did not work for HTML text. (issue #848)
- HTML: Fixed font sizes for HTML tags `<h1>`...`<h6>`, `<code>`, `<kbd>`,
`<big>`, `<small>` and `<samp>` in HTML text for components Button, CheckBox,
RadioButton, MenuItem (and subclasses), JideLabel, JideButton, JXBusyLabel and
JXHyperlink. Also fixed for Label and ToolTip if using Java 11+.
- ScrollPane: Fixed/improved border painting at 125% - 175% scaling to avoid
different border thicknesses. (issue #743)
- Table: Fixed painting of alternating rows below table if auto-resize mode is
`JTable.AUTO_RESIZE_OFF` and table width is smaller than scroll pane (was not
updated when table width changed and was painted on wrong side in
right-to-left component orientation).
- Theme Editor: Fixed occasional empty window on startup on macOS.
- FlatLaf window decorations: Fixed black line sometimes painted on top of
(native) window border on Windows 11. (issue #852)
- HiDPI: Fixed incomplete component paintings at 125% or 175% scaling on Windows
where sometimes a 1px wide area at the right or bottom component edge is not
repainted. E.g. ScrollPane focus indicator border. (issues #860 and #582)
#### Incompatibilities
- ProgressBar: Log warning (including stack trace) when uninstalling
indeterminate progress bar UI or using `JProgressBar.setIndeterminate(false)`
not on AWT thread, because this may throw NPE in `FlatProgressBarUI.paint()`.
(issues #841 and #830)
- Panel: Rounded background of panel with rounded corners is now painted even if
panel is not opaque. (issue #840)
## 3.4.1
#### Fixed bugs
- SplitPane: Update divider when client property `JSplitPane.expandableSide`
changed.
- TabbedPane: Fixed swapped back and forward scroll buttons when using
`TabbedPane.scrollButtonsPlacement = trailing` (regression in FlatLaf 3.3).
- Fixed missing window top border on Windows 10 in "full window content" mode.
(issue #809)
- Extras:
- `FlatSVGIcon` color filters now support linear gradients. (PR #817)
- `FlatSVGIcon`: Use log level `CONFIG` instead of `SEVERE` and allow
disabling logging. (issue #823)
- Added support for `JSplitPane.expandableSide` client property to
`FlatSplitPane`.
- Native libraries: Added API version check to test whether native library
matches the JAR (bad builds could e.g. ship a newer JAR with an older
incompatible native library) and to test whether native methods can be invoked
(some security software allows loading native library but blocks method
invocation).
- macOS: Fixed crash when running in WebSwing. (issue #826; regression in 3.4)
#### Incompatibilities
- File names of custom properties files for nested Laf classes now must include
name of enclosing class name. E.g. nested Laf class `IntelliJTheme.ThemeLaf`
used `ThemeLaf.properties` in previous versions, but now needs to be named
`IntelliJTheme$ThemeLaf.properties`.
## 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 on
Windows or Linux. (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 ## 3.3
#### New features and improvements #### New features and improvements
@@ -19,7 +154,7 @@ FlatLaf Change Log
#### Fixed bugs #### Fixed bugs
- Button and ToggleButton: Selected buttons did not use explicitly set - Button and ToggleButton: Selected buttons did not use explicitly set
foreground color. (issue 756) foreground color. (issue #756)
- FileChooser: Catch NPE in Java 21 when getting icon for `.exe` files that use - FileChooser: Catch NPE in Java 21 when getting icon for `.exe` files that use
default Windows exe icon. (see default Windows exe icon. (see
[JDK-8320692](https://bugs.openjdk.org/browse/JDK-8320692)) [JDK-8320692](https://bugs.openjdk.org/browse/JDK-8320692))
@@ -772,7 +907,7 @@ FlatLaf Change Log
- Native window decorations (Windows 10 only): - Native window decorations (Windows 10 only):
- Fixed occasional application crash in `flatlaf-windows.dll`. (issue #357) - Fixed occasional application crash in `flatlaf-windows.dll`. (issue #357)
- When window is initially shown, fill background with window background color - When window is initially shown, fill background with window background color
(instead of white), which avoids flickering in dark themes. (issue 339) (instead of white), which avoids flickering in dark themes. (issue #339)
- When resizing a window at the right/bottom edge, then first fill the new - When resizing a window at the right/bottom edge, then first fill the new
space with the window background color (instead of black) before the layout space with the window background color (instead of black) before the layout
is updated. is updated.

View File

@@ -311,6 +311,9 @@ Applications using FlatLaf
- ![New](images/new.svg) ![Sponsor](images/sponsor.svg) - ![New](images/new.svg) ![Sponsor](images/sponsor.svg)
[BGBlitz](https://www.bgblitz.com/) (**commercial**) - professional Backgammon [BGBlitz](https://www.bgblitz.com/) (**commercial**) - professional Backgammon
- ![New](images/new.svg) [MCreator](https://github.com/MCreator/MCreator) -
software used to make Minecraft Java Edition mods, Minecraft Bedrock Edition Add-Ons,
and data packs without programming knowledge
- ![New](images/new.svg) [MapTool](https://github.com/RPTools/maptool) - virtual - ![New](images/new.svg) [MapTool](https://github.com/RPTools/maptool) - virtual
Tabletop for playing role-playing games Tabletop for playing role-playing games
- [MegaMek](https://github.com/MegaMek/megamek), - [MegaMek](https://github.com/MegaMek/megamek),

View File

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

View File

@@ -27,7 +27,7 @@ library {
} }
with( linkTask.get() ) { with( linkTask.get() ) {
if( name.contains( "Release" ) ) if( name.contains( "Release" ) )
debuggable.set( false ) debuggable = false
} }
} }
} }

View File

@@ -44,34 +44,34 @@ publishing {
pom { pom {
afterEvaluate { afterEvaluate {
this@pom.name.set( extension.name ) this@pom.name = extension.name
this@pom.description.set( extension.description ) this@pom.description = extension.description
} }
url.set( "https://github.com/JFormDesigner/FlatLaf" ) url = "https://github.com/JFormDesigner/FlatLaf"
licenses { licenses {
license { license {
name.set( "The Apache License, Version 2.0" ) name = "The Apache License, Version 2.0"
url.set( "https://www.apache.org/licenses/LICENSE-2.0.txt" ) url = "https://www.apache.org/licenses/LICENSE-2.0.txt"
} }
} }
developers { developers {
developer { developer {
name.set( "Karl Tauber" ) name = "Karl Tauber"
organization.set( "FormDev Software GmbH" ) organization = "FormDev Software GmbH"
organizationUrl.set( "https://www.formdev.com/" ) organizationUrl = "https://www.formdev.com/"
} }
} }
scm { scm {
connection.set( "scm:git:git://github.com/JFormDesigner/FlatLaf.git" ) connection = "scm:git:git://github.com/JFormDesigner/FlatLaf.git"
url.set( "https://github.com/JFormDesigner/FlatLaf" ) url = "https://github.com/JFormDesigner/FlatLaf"
} }
issueManagement { issueManagement {
system.set( "GitHub" ) system = "GitHub"
url.set( "https://github.com/JFormDesigner/FlatLaf/issues" ) url = "https://github.com/JFormDesigner/FlatLaf/issues"
} }
} }
@@ -124,7 +124,7 @@ tasks.withType<Sign>().configureEach {
} }
// check whether parallel build is enabled // check whether parallel build is enabled
tasks.withType<PublishToMavenRepository>().configureEach { tasks.withType<AbstractPublishToMaven>().configureEach {
doFirst { doFirst {
if( System.getProperty( "org.gradle.parallel" ) == "true" ) 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'." ) throw RuntimeException( "Publishing does not work correctly with enabled parallel build. Disable parallel build with VM option '-Dorg.gradle.parallel=false'." )

View File

@@ -21,6 +21,6 @@ plugins {
val toolchainJavaVersion = System.getProperty( "toolchain" ) val toolchainJavaVersion = System.getProperty( "toolchain" )
if( !toolchainJavaVersion.isNullOrEmpty() ) { if( !toolchainJavaVersion.isNullOrEmpty() ) {
java.toolchain { java.toolchain {
languageVersion.set( JavaLanguageVersion.of( toolchainJavaVersion ) ) languageVersion = JavaLanguageVersion.of( toolchainJavaVersion )
} }
} }

View File

@@ -27,8 +27,8 @@ plugins {
val sigtest = configurations.create( "sigtest" ) val sigtest = configurations.create( "sigtest" )
dependencies { dependencies {
testImplementation( libs.bundles.junit ) testImplementation( libs.junit )
testRuntimeOnly( libs.junit.engine ) testRuntimeOnly( libs.junit.launcher )
// https://github.com/jtulach/netbeans-apitest // https://github.com/jtulach/netbeans-apitest
sigtest( libs.sigtest ) sigtest( libs.sigtest )
@@ -42,11 +42,11 @@ java {
tasks { tasks {
compileJava { compileJava {
// generate JNI headers // generate JNI headers
options.headerOutputDirectory.set( layout.buildDirectory.dir( "generated/jni-headers" ) ) options.headerOutputDirectory = layout.buildDirectory.dir( "generated/jni-headers" )
} }
jar { jar {
archiveBaseName.set( "flatlaf" ) archiveBaseName = "flatlaf"
doLast { doLast {
ReorderJarEntries.reorderJarEntries( outputs.files.singleFile ); ReorderJarEntries.reorderJarEntries( outputs.files.singleFile );
@@ -54,11 +54,32 @@ tasks {
} }
named<Jar>( "sourcesJar" ) { named<Jar>( "sourcesJar" ) {
archiveBaseName.set( "flatlaf" ) archiveBaseName = "flatlaf"
} }
named<Jar>( "javadocJar" ) { named<Jar>( "javadocJar" ) {
archiveBaseName.set( "flatlaf" ) archiveBaseName = "flatlaf"
}
register<Zip>( "jarNoNatives" ) {
group = "build"
dependsOn( "jar" )
archiveBaseName = "flatlaf"
archiveClassifier = "no-natives"
archiveExtension = "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 { check {
@@ -127,6 +148,8 @@ flatlafPublish {
val natives = "src/main/resources/com/formdev/flatlaf/natives" val natives = "src/main/resources/com/formdev/flatlaf/natives"
nativeArtifacts = listOf( nativeArtifacts = listOf(
NativeArtifact( tasks.getByName( "jarNoNatives" ).outputs.files.asPath, "no-natives", "jar" ),
NativeArtifact( "${natives}/flatlaf-windows-x86.dll", "windows-x86", "dll" ), NativeArtifact( "${natives}/flatlaf-windows-x86.dll", "windows-x86", "dll" ),
NativeArtifact( "${natives}/flatlaf-windows-x86_64.dll", "windows-x86_64", "dll" ), NativeArtifact( "${natives}/flatlaf-windows-x86_64.dll", "windows-x86_64", "dll" ),
NativeArtifact( "${natives}/flatlaf-windows-arm64.dll", "windows-arm64", "dll" ), NativeArtifact( "${natives}/flatlaf-windows-arm64.dll", "windows-arm64", "dll" ),

View File

@@ -1,5 +1,5 @@
#Signature file v4.1 #Signature file v4.1
#Version 3.3 #Version 3.5.1
CLSS public abstract interface com.formdev.flatlaf.FlatClientProperties CLSS public abstract interface com.formdev.flatlaf.FlatClientProperties
fld public final static java.lang.String BUTTON_TYPE = "JButton.buttonType" fld public final static java.lang.String BUTTON_TYPE = "JButton.buttonType"
@@ -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_FOCUS_OWNER = "JComponent.focusOwner"
fld public final static java.lang.String COMPONENT_ROUND_RECT = "JComponent.roundRect" 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 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 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 MENU_BAR_EMBEDDED = "JRootPane.menuBarEmbedded"
fld public final static java.lang.String MINIMUM_HEIGHT = "JComponent.minimumHeight" fld public final static java.lang.String MINIMUM_HEIGHT = "JComponent.minimumHeight"
fld public final static java.lang.String MINIMUM_WIDTH = "JComponent.minimumWidth" fld public final static java.lang.String MINIMUM_WIDTH = "JComponent.minimumWidth"
@@ -290,6 +296,7 @@ fld public final static java.lang.String UPDATE_UI_ON_SYSTEM_FONT_CHANGE = "flat
fld public final static java.lang.String USE_JETBRAINS_CUSTOM_DECORATIONS = "flatlaf.useJetBrainsCustomDecorations" fld public final static java.lang.String USE_JETBRAINS_CUSTOM_DECORATIONS = "flatlaf.useJetBrainsCustomDecorations"
anno 0 java.lang.Deprecated() 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_NATIVE_LIBRARY = "flatlaf.useNativeLibrary"
fld public final static java.lang.String USE_SUB_MENU_SAFE_TRIANGLE = "flatlaf.useSubMenuSafeTriangle"
fld public final static java.lang.String USE_TEXT_Y_CORRECTION = "flatlaf.useTextYCorrection" 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" fld public final static java.lang.String USE_UBUNTU_FONT = "flatlaf.useUbuntuFont"
fld public final static java.lang.String USE_WINDOW_DECORATIONS = "flatlaf.useWindowDecorations" fld public final static java.lang.String USE_WINDOW_DECORATIONS = "flatlaf.useWindowDecorations"
@@ -623,16 +630,33 @@ hfds alpha,hsl,rgb
CLSS public com.formdev.flatlaf.util.HiDPIUtils CLSS public com.formdev.flatlaf.util.HiDPIUtils
cons public init() cons public init()
innr public abstract interface static DirtyRegionCallback
innr public abstract interface static Painter innr public abstract interface static Painter
innr public static HiDPIRepaintManager
meth public static float computeTextYCorrection(java.awt.Graphics2D) meth public static float computeTextYCorrection(java.awt.Graphics2D)
meth public static java.awt.Graphics2D createGraphicsTextYCorrection(java.awt.Graphics2D) meth public static java.awt.Graphics2D createGraphicsTextYCorrection(java.awt.Graphics2D)
meth public static void addDirtyRegion(javax.swing.JComponent,int,int,int,int,com.formdev.flatlaf.util.HiDPIUtils$DirtyRegionCallback)
meth public static void drawStringUnderlineCharAtWithYCorrection(javax.swing.JComponent,java.awt.Graphics2D,java.lang.String,int,int,int) meth public static void drawStringUnderlineCharAtWithYCorrection(javax.swing.JComponent,java.awt.Graphics2D,java.lang.String,int,int,int)
meth public static void drawStringWithYCorrection(javax.swing.JComponent,java.awt.Graphics2D,java.lang.String,int,int) meth public static void drawStringWithYCorrection(javax.swing.JComponent,java.awt.Graphics2D,java.lang.String,int,int)
meth public static void installHiDPIRepaintManager()
meth public static void paintAtScale1x(java.awt.Graphics2D,int,int,int,int,com.formdev.flatlaf.util.HiDPIUtils$Painter) meth public static void paintAtScale1x(java.awt.Graphics2D,int,int,int,int,com.formdev.flatlaf.util.HiDPIUtils$Painter)
meth public static void paintAtScale1x(java.awt.Graphics2D,javax.swing.JComponent,com.formdev.flatlaf.util.HiDPIUtils$Painter) meth public static void paintAtScale1x(java.awt.Graphics2D,javax.swing.JComponent,com.formdev.flatlaf.util.HiDPIUtils$Painter)
meth public static void repaint(java.awt.Component)
meth public static void repaint(java.awt.Component,int,int,int,int)
meth public static void repaint(java.awt.Component,java.awt.Rectangle)
supr java.lang.Object supr java.lang.Object
hfds CORRECTION_INTER,CORRECTION_OPEN_SANS,CORRECTION_SEGOE_UI,CORRECTION_TAHOMA,SCALE_FACTORS,useDebugScaleFactor,useTextYCorrection hfds CORRECTION_INTER,CORRECTION_OPEN_SANS,CORRECTION_SEGOE_UI,CORRECTION_TAHOMA,SCALE_FACTORS,useDebugScaleFactor,useTextYCorrection
CLSS public abstract interface static com.formdev.flatlaf.util.HiDPIUtils$DirtyRegionCallback
outer com.formdev.flatlaf.util.HiDPIUtils
meth public abstract void addDirtyRegion(javax.swing.JComponent,int,int,int,int)
CLSS public static com.formdev.flatlaf.util.HiDPIUtils$HiDPIRepaintManager
outer com.formdev.flatlaf.util.HiDPIUtils
cons public init()
meth public void addDirtyRegion(javax.swing.JComponent,int,int,int,int)
supr javax.swing.RepaintManager
CLSS public abstract interface static com.formdev.flatlaf.util.HiDPIUtils$Painter CLSS public abstract interface static com.formdev.flatlaf.util.HiDPIUtils$Painter
outer com.formdev.flatlaf.util.HiDPIUtils outer com.formdev.flatlaf.util.HiDPIUtils
meth public abstract void paint(java.awt.Graphics2D,int,int,int,int,double) meth public abstract void paint(java.awt.Graphics2D,int,int,int,int,double)
@@ -1131,6 +1155,31 @@ meth public void provideErrorFeedback(java.awt.Component)
meth public void uninitialize() meth public void uninitialize()
supr java.lang.Object supr java.lang.Object
CLSS public javax.swing.RepaintManager
cons public init()
meth public boolean isCompletelyDirty(javax.swing.JComponent)
meth public boolean isDoubleBufferingEnabled()
meth public java.awt.Dimension getDoubleBufferMaximumSize()
meth public java.awt.Image getOffscreenBuffer(java.awt.Component,int,int)
meth public java.awt.Image getVolatileOffscreenBuffer(java.awt.Component,int,int)
meth public java.awt.Rectangle getDirtyRegion(javax.swing.JComponent)
meth public java.lang.String toString()
meth public static javax.swing.RepaintManager currentManager(java.awt.Component)
meth public static javax.swing.RepaintManager currentManager(javax.swing.JComponent)
meth public static void setCurrentManager(javax.swing.RepaintManager)
meth public void addDirtyRegion(java.applet.Applet,int,int,int,int)
meth public void addDirtyRegion(java.awt.Window,int,int,int,int)
meth public void addDirtyRegion(javax.swing.JComponent,int,int,int,int)
meth public void addInvalidComponent(javax.swing.JComponent)
meth public void markCompletelyClean(javax.swing.JComponent)
meth public void markCompletelyDirty(javax.swing.JComponent)
meth public void paintDirtyRegions()
meth public void removeInvalidComponent(javax.swing.JComponent)
meth public void setDoubleBufferMaximumSize(java.awt.Dimension)
meth public void setDoubleBufferingEnabled(boolean)
meth public void validateInvalidComponents()
supr java.lang.Object
CLSS public abstract javax.swing.border.AbstractBorder CLSS public abstract javax.swing.border.AbstractBorder
cons public init() cons public init()
intf java.io.Serializable intf java.io.Serializable

View File

@@ -102,6 +102,17 @@ public interface FlatClientProperties
*/ */
String BUTTON_TYPE_BORDERLESS = "borderless"; String BUTTON_TYPE_BORDERLESS = "borderless";
/**
* Specifies whether the button preferred size will be made square (quadratically).
* <p>
* <strong>Components</strong> {@link javax.swing.JButton} and {@link javax.swing.JToggleButton}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*/
String SQUARE_SIZE = "JButton.squareSize";
//---- JCheckBox ----------------------------------------------------------
/** /**
* Specifies selected state of a checkbox. * Specifies selected state of a checkbox.
* <p> * <p>
@@ -118,14 +129,6 @@ public interface FlatClientProperties
*/ */
String SELECTED_STATE_INDETERMINATE = "indeterminate"; String SELECTED_STATE_INDETERMINATE = "indeterminate";
/**
* Specifies whether the button preferred size will be made square (quadratically).
* <p>
* <strong>Components</strong> {@link javax.swing.JButton} and {@link javax.swing.JToggleButton}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*/
String SQUARE_SIZE = "JButton.squareSize";
//---- JComponent --------------------------------------------------------- //---- JComponent ---------------------------------------------------------
@@ -257,19 +260,116 @@ public interface FlatClientProperties
String COMPONENT_FOCUS_OWNER = "JComponent.focusOwner"; 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). * (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. * but it gets mouse entered/exited/moved events.
* <p> * <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>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 * @since 2.5
*/ */
String COMPONENT_TITLE_BAR_CAPTION = "JComponent.titleBarCaption"; 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 -------------------------------------------------------------- //---- Popup --------------------------------------------------------------
/** /**
@@ -388,6 +488,46 @@ public interface FlatClientProperties
*/ */
String MENU_BAR_EMBEDDED = "JRootPane.menuBarEmbedded"; 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 * Specifies whether the window icon should be shown in the window title bar
* (requires enabled window decorations). Default is UI property {@code TitlePane.showIcon}. * (requires enabled window decorations). Default is UI property {@code TitlePane.showIcon}.
@@ -847,7 +987,7 @@ public interface FlatClientProperties
* <strong>Component</strong> {@link javax.swing.JTabbedPane}<br> * <strong>Component</strong> {@link javax.swing.JTabbedPane}<br>
* <strong>Value type</strong> {@link java.lang.Integer} or {@link java.lang.String}<br> * <strong>Value type</strong> {@link java.lang.Integer} or {@link java.lang.String}<br>
* <strong>Allowed Values</strong> * <strong>Allowed Values</strong>
* {@link SwingConstants#LEADING} (default) * {@link SwingConstants#LEADING} (default),
* {@link SwingConstants#TRAILING}, * {@link SwingConstants#TRAILING},
* {@link SwingConstants#CENTER}, * {@link SwingConstants#CENTER},
* {@link #TABBED_PANE_ALIGN_LEADING} (default), * {@link #TABBED_PANE_ALIGN_LEADING} (default),
@@ -959,7 +1099,7 @@ public interface FlatClientProperties
* <strong>Allowed Values</strong> * <strong>Allowed Values</strong>
* {@link SwingConstants#LEFT}, * {@link SwingConstants#LEFT},
* {@link SwingConstants#RIGHT}, * {@link SwingConstants#RIGHT},
* {@link #TABBED_PANE_TAB_ROTATION_NONE}, (default) * {@link #TABBED_PANE_TAB_ROTATION_NONE} (default),
* {@link #TABBED_PANE_TAB_ROTATION_AUTO}, * {@link #TABBED_PANE_TAB_ROTATION_AUTO},
* {@link #TABBED_PANE_TAB_ROTATION_LEFT} or * {@link #TABBED_PANE_TAB_ROTATION_LEFT} or
* {@link #TABBED_PANE_TAB_ROTATION_RIGHT} * {@link #TABBED_PANE_TAB_ROTATION_RIGHT}
@@ -1208,8 +1348,8 @@ public interface FlatClientProperties
* <p> * <p>
* <strong>Component</strong> {@link javax.swing.JToggleButton}<br> * <strong>Component</strong> {@link javax.swing.JToggleButton}<br>
* <strong>Value type</strong> {@link java.lang.Integer}<br> * <strong>Value type</strong> {@link java.lang.Integer}<br>
* <strong>SupportedValues:</strong> * <strong>Allowed Values</strong>
* {@link SwingConstants#BOTTOM} (default) * {@link SwingConstants#BOTTOM} (default),
* {@link SwingConstants#TOP}, * {@link SwingConstants#TOP},
* {@link SwingConstants#LEFT} or * {@link SwingConstants#LEFT} or
* {@link SwingConstants#RIGHT} * {@link SwingConstants#RIGHT}
@@ -1263,6 +1403,44 @@ public interface FlatClientProperties
String TREE_PAINT_SELECTION = "JTree.paintSelection"; 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 ----------------------------------------------------- //---- helper methods -----------------------------------------------------
/** /**

View File

@@ -48,7 +48,7 @@ public abstract class FlatDefaultsAddon
public InputStream getDefaults( Class<?> lafClass ) { public InputStream getDefaults( Class<?> lafClass ) {
Class<?> addonClass = this.getClass(); Class<?> addonClass = this.getClass();
String propertiesName = '/' + addonClass.getPackage().getName().replace( '.', '/' ) String propertiesName = '/' + addonClass.getPackage().getName().replace( '.', '/' )
+ '/' + lafClass.getSimpleName() + ".properties"; + '/' + UIDefaultsLoader.simpleClassName( lafClass ) + ".properties";
return addonClass.getResourceAsStream( propertiesName ); return addonClass.getResourceAsStream( propertiesName );
} }

View File

@@ -172,24 +172,47 @@ public interface FlatSystemProperties
String USE_NATIVE_LIBRARY = "flatlaf.useNativeLibrary"; 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. * 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. * This can be used to avoid extraction of the native libraries to the temporary directory at runtime.
* <p> * <p>
* If the value is {@code "system"}, then {@link System#loadLibrary(String)} is * If the value is {@code "system"} (supported since FlatLaf 2.6),
* used to load the native library. * then {@link System#loadLibrary(String)} is used to load the native library.
* Searches for the native library in classloader of caller * This searches for the native library in classloader of caller
* (using {@link ClassLoader#findLibrary(String)}) and in paths specified * (using {@link ClassLoader#findLibrary(String)}) and in paths specified
* in system properties {@code sun.boot.library.path} and {@code java.library.path}. * in system properties {@code sun.boot.library.path} and {@code java.library.path}.
* (supported since FlatLaf 2.6)
* <p> * <p>
* If the native library can not be 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. * 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 * @since 2
*/ */
String NATIVE_LIBRARY_PATH = "flatlaf.nativeLibraryPath"; String NATIVE_LIBRARY_PATH = "flatlaf.nativeLibraryPath";
/**
* Specifies whether safe triangle is used to improve usability of submenus.
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> {@code true}
* @since 3.5.1
*/
String USE_SUB_MENU_SAFE_TRIANGLE = "flatlaf.useSubMenuSafeTriangle";
/** /**
* Checks whether a system property is set and returns {@code true} if its value * Checks whether a system property is set and returns {@code true} if its value
* is {@code "true"} (case-insensitive), otherwise it returns {@code false}. * is {@code "true"} (case-insensitive), otherwise it returns {@code false}.

View File

@@ -45,6 +45,7 @@ import javax.swing.UIManager;
import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener; import javax.swing.event.ChangeListener;
import com.formdev.flatlaf.ui.FlatUIUtils; import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
/** /**
* Improves usability of submenus by using a * Improves usability of submenus by using a
@@ -64,6 +65,7 @@ class SubMenuUsabilityHelper
// https://github.com/apache/netbeans/issues/4231#issuecomment-1179616607 // https://github.com/apache/netbeans/issues/4231#issuecomment-1179616607
private static SubMenuUsabilityHelper instance; private static SubMenuUsabilityHelper instance;
private boolean eventQueuePushNotSupported;
private SubMenuEventQueue subMenuEventQueue; private SubMenuEventQueue subMenuEventQueue;
private SafeTrianglePainter safeTrianglePainter; private SafeTrianglePainter safeTrianglePainter;
private boolean changePending; private boolean changePending;
@@ -83,6 +85,9 @@ class SubMenuUsabilityHelper
if( instance != null ) if( instance != null )
return false; return false;
if( !FlatSystemProperties.getBoolean( FlatSystemProperties.USE_SUB_MENU_SAFE_TRIANGLE, true ) )
return false;
instance = new SubMenuUsabilityHelper(); instance = new SubMenuUsabilityHelper();
MenuSelectionManager.defaultManager().addChangeListener( instance ); MenuSelectionManager.defaultManager().addChangeListener( instance );
return true; return true;
@@ -99,7 +104,7 @@ class SubMenuUsabilityHelper
@Override @Override
public void stateChanged( ChangeEvent e ) { public void stateChanged( ChangeEvent e ) {
if( !FlatUIUtils.getUIBoolean( KEY_USE_SAFE_TRIANGLE, true )) if( eventQueuePushNotSupported || !FlatUIUtils.getUIBoolean( KEY_USE_SAFE_TRIANGLE, true ))
return; return;
// handle menu selection change later, but only once in case of temporary changes // handle menu selection change later, but only once in case of temporary changes
@@ -173,8 +178,29 @@ debug*/
targetBottomY = popupLocation.y + popupSize.height; targetBottomY = popupLocation.y + popupSize.height;
// install own event queue to suppress 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 ) if( subMenuEventQueue == null ) {
subMenuEventQueue = new SubMenuEventQueue(); SubMenuEventQueue queue = new SubMenuEventQueue();
try {
Toolkit toolkit = Toolkit.getDefaultToolkit();
toolkit.getSystemEventQueue().push( queue );
// check whether push() worked
// (e.g. SWTSwing uses own event queue that does not support push())
if( toolkit.getSystemEventQueue() != queue ) {
eventQueuePushNotSupported = true;
LoggingFacade.INSTANCE.logSevere( "FlatLaf: Failed to push submenu event queue. Disabling submenu safe triangle.", null );
return;
}
subMenuEventQueue = queue;
} catch( RuntimeException ex ) {
// catch runtime exception from EventQueue.push()
eventQueuePushNotSupported = true;
LoggingFacade.INSTANCE.logSevere( "FlatLaf: Failed to push submenu event queue. Disabling submenu safe triangle.", ex );
return;
}
}
// create safe triangle painter // create safe triangle painter
if( safeTrianglePainter == null && UIManager.getBoolean( KEY_SHOW_SAFE_TRIANGLE ) ) if( safeTrianglePainter == null && UIManager.getBoolean( KEY_SHOW_SAFE_TRIANGLE ) )
@@ -247,8 +273,6 @@ debug*/
} }
} ); } );
timeoutTimer.setRepeats( false ); timeoutTimer.setRepeats( false );
Toolkit.getDefaultToolkit().getSystemEventQueue().push( this );
} }
void uninstall() { void uninstall() {

View File

@@ -161,7 +161,7 @@ class UIDefaultsLoader
classLoader = FlatLaf.class.getClassLoader(); classLoader = FlatLaf.class.getClassLoader();
for( Class<?> lafClass : lafClasses ) { for( Class<?> lafClass : lafClasses ) {
String propertiesName = packageName + '/' + lafClass.getSimpleName() + ".properties"; String propertiesName = packageName + '/' + simpleClassName( lafClass ) + ".properties";
try( InputStream in = classLoader.getResourceAsStream( propertiesName ) ) { try( InputStream in = classLoader.getResourceAsStream( propertiesName ) ) {
if( in != null ) if( in != null )
properties.load( in ); properties.load( in );
@@ -171,7 +171,7 @@ class UIDefaultsLoader
// load from package URL // load from package URL
URL packageUrl = (URL) source; URL packageUrl = (URL) source;
for( Class<?> lafClass : lafClasses ) { for( Class<?> lafClass : lafClasses ) {
URL propertiesUrl = new URL( packageUrl + lafClass.getSimpleName() + ".properties" ); URL propertiesUrl = new URL( packageUrl + simpleClassName( lafClass ) + ".properties" );
try( InputStream in = propertiesUrl.openStream() ) { try( InputStream in = propertiesUrl.openStream() ) {
properties.load( in ); properties.load( in );
@@ -183,7 +183,7 @@ class UIDefaultsLoader
// load from folder // load from folder
File folder = (File) source; File folder = (File) source;
for( Class<?> lafClass : lafClasses ) { for( Class<?> lafClass : lafClasses ) {
File propertiesFile = new File( folder, lafClass.getSimpleName() + ".properties" ); File propertiesFile = new File( folder, simpleClassName( lafClass ) + ".properties" );
if( !propertiesFile.isFile() ) if( !propertiesFile.isFile() )
continue; continue;
@@ -294,6 +294,14 @@ class UIDefaultsLoader
} }
} }
/**
* Similar to Class.getSimpleName(), but includes enclosing class for nested classes.
*/
static String simpleClassName( Class<?> cls ) {
String className = cls.getName();
return className.substring( className.lastIndexOf( '.' ) + 1 );
}
static void logParseError( String key, String value, RuntimeException ex, boolean severe ) { static void logParseError( String key, String value, RuntimeException ex, boolean severe ) {
String message = "FlatLaf: Failed to parse: '" + key + '=' + value + '\''; String message = "FlatLaf: Failed to parse: '" + key + '=' + value + '\'';
if( severe ) if( severe )
@@ -630,14 +638,18 @@ class UIDefaultsLoader
// Syntax: top,left,bottom,right[,lineColor[,lineThickness[,arc]]] // Syntax: top,left,bottom,right[,lineColor[,lineThickness[,arc]]]
List<String> parts = splitFunctionParams( value, ',' ); List<String> parts = splitFunctionParams( value, ',' );
Insets insets = parseInsets( value ); Insets insets = parseInsets( value );
ColorUIResource lineColor = (parts.size() >= 5) ColorUIResource lineColor = (parts.size() >= 5 && !parts.get( 4 ).isEmpty())
? (ColorUIResource) parseColorOrFunction( resolver.apply( parts.get( 4 ) ), resolver ) ? (ColorUIResource) parseColorOrFunction( resolver.apply( parts.get( 4 ) ), resolver )
: null; : null;
float lineThickness = (parts.size() >= 6 && !parts.get( 5 ).isEmpty()) ? parseFloat( parts.get( 5 ) ) : 1f; float lineThickness = (parts.size() >= 6 && !parts.get( 5 ).isEmpty())
int arc = (parts.size() >= 7) ? parseInteger( parts.get( 6 ) ) : 0; ? parseFloat( parts.get( 5 ) )
: 1f;
int arc = (parts.size() >= 7) && !parts.get( 6 ).isEmpty()
? parseInteger( parts.get( 6 ) )
: -1;
return (LazyValue) t -> { return (LazyValue) t -> {
return (lineColor != null) return (lineColor != null || arc > 0)
? new FlatLineBorder( insets, lineColor, lineThickness, arc ) ? new FlatLineBorder( insets, lineColor, lineThickness, arc )
: new FlatEmptyBorder( insets ); : new FlatEmptyBorder( insets );
}; };

View File

@@ -135,7 +135,7 @@ public class FlatBorder
Paint borderColor = (outlineColor != null) ? outlineColor : getBorderColor( c ); Paint borderColor = (outlineColor != null) ? outlineColor : getBorderColor( c );
FlatUIUtils.paintOutlinedComponent( g2, x, y, width, height, FlatUIUtils.paintOutlinedComponent( g2, x, y, width, height,
focusWidth, 1, focusInnerWidth, borderWidth, arc, focusWidth, 1, focusInnerWidth, borderWidth, arc,
focusColor, borderColor, null ); focusColor, borderColor, null, c instanceof JScrollPane );
} finally { } finally {
g2.dispose(); g2.dispose();
} }
@@ -277,7 +277,7 @@ public class FlatBorder
} }
/** /**
* Returns the (unscaled) arc diameter of the border. * Returns the (unscaled) arc diameter of the border corners.
*/ */
protected int getArc( Component c ) { protected int getArc( Component c ) {
return 0; return 0;

View File

@@ -42,6 +42,13 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault Button.disabledBorderColor Color * @uiDefault Button.disabledBorderColor Color
* @uiDefault Button.focusedBorderColor Color * @uiDefault Button.focusedBorderColor Color
* @uiDefault Button.hoverBorderColor Color optional * @uiDefault Button.hoverBorderColor Color optional
* @uiDefault Button.pressedBorderColor Color optional
*
* @uiDefault Button.selectedBorderColor Color optional
* @uiDefault Button.disabledSelectedBorderColor Color optional
* @uiDefault Button.focusedSelectedBorderColor Color optional
* @uiDefault Button.hoverSelectedBorderColor Color optional
* @uiDefault Button.pressedSelectedBorderColor Color optional
* *
* @uiDefault Button.default.borderWidth int or float * @uiDefault Button.default.borderWidth int or float
* @uiDefault Button.default.borderColor Color * @uiDefault Button.default.borderColor Color
@@ -49,6 +56,7 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault Button.default.endBorderColor Color optional; if set, a gradient paint is used * @uiDefault Button.default.endBorderColor Color optional; if set, a gradient paint is used
* @uiDefault Button.default.focusedBorderColor Color * @uiDefault Button.default.focusedBorderColor Color
* @uiDefault Button.default.focusColor Color * @uiDefault Button.default.focusColor Color
* @uiDefault Button.default.pressedBorderColor Color optional
* @uiDefault Button.default.hoverBorderColor Color optional * @uiDefault Button.default.hoverBorderColor Color optional
* *
* @uiDefault Button.toolbar.focusWidth int or float optional; default is 1.5 * @uiDefault Button.toolbar.focusWidth int or float optional; default is 1.5
@@ -65,6 +73,13 @@ public class FlatButtonBorder
protected Color endBorderColor = UIManager.getColor( "Button.endBorderColor" ); protected Color endBorderColor = UIManager.getColor( "Button.endBorderColor" );
@Styleable protected Color hoverBorderColor = UIManager.getColor( "Button.hoverBorderColor" ); @Styleable protected Color hoverBorderColor = UIManager.getColor( "Button.hoverBorderColor" );
/** @since 3.5 */ @Styleable protected Color pressedBorderColor = UIManager.getColor( "Button.pressedBorderColor" );
/** @since 3.5 */ @Styleable protected Color selectedBorderColor = UIManager.getColor( "Button.selectedBorderColor" );
/** @since 3.5 */ @Styleable protected Color disabledSelectedBorderColor = UIManager.getColor( "Button.disabledSelectedBorderColor" );
/** @since 3.5 */ @Styleable protected Color focusedSelectedBorderColor = UIManager.getColor( "Button.focusedSelectedBorderColor" );
/** @since 3.5 */ @Styleable protected Color hoverSelectedBorderColor = UIManager.getColor( "Button.hoverSelectedBorderColor" );
/** @since 3.5 */ @Styleable protected Color pressedSelectedBorderColor = UIManager.getColor( "Button.pressedSelectedBorderColor" );
@Styleable(dot=true) protected float defaultBorderWidth = FlatUIUtils.getUIFloat( "Button.default.borderWidth", 1 ); @Styleable(dot=true) protected float defaultBorderWidth = FlatUIUtils.getUIFloat( "Button.default.borderWidth", 1 );
@Styleable(dot=true) protected Color defaultBorderColor = FlatUIUtils.getUIColor( "Button.default.startBorderColor", "Button.default.borderColor" ); @Styleable(dot=true) protected Color defaultBorderColor = FlatUIUtils.getUIColor( "Button.default.startBorderColor", "Button.default.borderColor" );
@@ -72,6 +87,7 @@ public class FlatButtonBorder
@Styleable(dot=true) protected Color defaultFocusedBorderColor = UIManager.getColor( "Button.default.focusedBorderColor" ); @Styleable(dot=true) protected Color defaultFocusedBorderColor = UIManager.getColor( "Button.default.focusedBorderColor" );
@Styleable(dot=true) protected Color defaultFocusColor = UIManager.getColor( "Button.default.focusColor" ); @Styleable(dot=true) protected Color defaultFocusColor = UIManager.getColor( "Button.default.focusColor" );
@Styleable(dot=true) protected Color defaultHoverBorderColor = UIManager.getColor( "Button.default.hoverBorderColor" ); @Styleable(dot=true) protected Color defaultHoverBorderColor = UIManager.getColor( "Button.default.hoverBorderColor" );
/** @since 3.5 */ @Styleable(dot=true) protected Color defaultPressedBorderColor = UIManager.getColor( "Button.default.pressedBorderColor" );
/** @since 1.4 */ @Styleable(dot=true) protected float toolbarFocusWidth = FlatUIUtils.getUIFloat( "Button.toolbar.focusWidth", 1.5f ); /** @since 1.4 */ @Styleable(dot=true) protected float toolbarFocusWidth = FlatUIUtils.getUIFloat( "Button.toolbar.focusWidth", 1.5f );
/** @since 1.4 */ @Styleable(dot=true) protected Color toolbarFocusColor = UIManager.getColor( "Button.toolbar.focusColor" ); /** @since 1.4 */ @Styleable(dot=true) protected Color toolbarFocusColor = UIManager.getColor( "Button.toolbar.focusColor" );
@@ -139,12 +155,13 @@ public class FlatButtonBorder
@Override @Override
protected Paint getBorderColor( Component c ) { protected Paint getBorderColor( Component c ) {
boolean def = FlatButtonUI.isDefaultButton( c ); boolean def = FlatButtonUI.isDefaultButton( c );
boolean selected = (c instanceof AbstractButton && ((AbstractButton)c).isSelected());
Paint color = FlatButtonUI.buttonStateColor( c, Paint color = FlatButtonUI.buttonStateColor( c,
def ? defaultBorderColor : borderColor, def ? defaultBorderColor : ((selected && selectedBorderColor != null) ? selectedBorderColor : borderColor),
disabledBorderColor, (selected && disabledSelectedBorderColor != null) ? disabledSelectedBorderColor : disabledBorderColor,
def ? defaultFocusedBorderColor : focusedBorderColor, def ? defaultFocusedBorderColor : ((selected && focusedSelectedBorderColor != null) ? focusedSelectedBorderColor : focusedBorderColor),
def ? defaultHoverBorderColor : hoverBorderColor, def ? defaultHoverBorderColor : ((selected && hoverSelectedBorderColor != null) ? hoverSelectedBorderColor : hoverBorderColor),
null ); def ? defaultPressedBorderColor : ((selected && pressedSelectedBorderColor != null) ? pressedSelectedBorderColor : pressedBorderColor) );
// change to gradient paint if start/end colors are specified // change to gradient paint if start/end colors are specified
Color startBg = def ? defaultBorderColor : borderColor; Color startBg = def ? defaultBorderColor : borderColor;

View File

@@ -29,6 +29,7 @@ import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Insets; import java.awt.Insets;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.event.FocusEvent;
import java.awt.geom.RoundRectangle2D; import java.awt.geom.RoundRectangle2D;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.util.Map; import java.util.Map;
@@ -61,6 +62,7 @@ import com.formdev.flatlaf.icons.FlatHelpButtonIcon;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException; import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
@@ -300,6 +302,10 @@ public class FlatButtonUI
protected void propertyChange( AbstractButton b, PropertyChangeEvent e ) { protected void propertyChange( AbstractButton b, PropertyChangeEvent e ) {
switch( e.getPropertyName() ) { switch( e.getPropertyName() ) {
case BasicHTML.propertyKey:
FlatHTML.updateRendererCSSFontBaseSize( b );
break;
case SQUARE_SIZE: case SQUARE_SIZE:
case MINIMUM_WIDTH: case MINIMUM_WIDTH:
case MINIMUM_HEIGHT: case MINIMUM_HEIGHT:
@@ -308,11 +314,11 @@ public class FlatButtonUI
case BUTTON_TYPE: case BUTTON_TYPE:
b.revalidate(); b.revalidate();
b.repaint(); HiDPIUtils.repaint( b );
break; break;
case OUTLINE: case OUTLINE:
b.repaint(); HiDPIUtils.repaint( b );
break; break;
case STYLE: case STYLE:
@@ -324,7 +330,7 @@ public class FlatButtonUI
} else } else
installStyle( b ); installStyle( b );
b.revalidate(); b.revalidate();
b.repaint(); HiDPIUtils.repaint( b );
break; break;
} }
} }
@@ -583,9 +589,16 @@ public class FlatButtonUI
// paint text // paint text
if( clippedText != null && !clippedText.isEmpty() ) { if( clippedText != null && !clippedText.isEmpty() ) {
View view = (View) b.getClientProperty( BasicHTML.propertyKey ); View view = (View) b.getClientProperty( BasicHTML.propertyKey );
if( view != null ) if( view != null ) {
// update foreground color in HTML view, which is necessary
// for selected and pressed states
// (only for enabled buttons, because UIManager.getColor("textInactiveText")
// is used for disabled components; see: javax.swing.text.GlyphView.paint())
if( b.isEnabled() )
FlatHTML.updateRendererCSSForeground( view, getForeground( b ) );
view.paint( g, textR ); // HTML text view.paint( g, textR ); // HTML text
else } else
paintText( g, b, textR, clippedText ); paintText( g, b, textR, clippedText );
} }
@@ -631,8 +644,6 @@ public class FlatButtonUI
} }
public static void paintText( Graphics g, AbstractButton b, Rectangle textRect, String text, Color foreground ) { public static void paintText( Graphics g, AbstractButton b, Rectangle textRect, String text, Color foreground ) {
if(foreground == null)
foreground=Color.red;
FontMetrics fm = b.getFontMetrics( b.getFont() ); FontMetrics fm = b.getFontMetrics( b.getFont() );
int mnemonicIndex = FlatLaf.isShowMnemonics() ? b.getDisplayedMnemonicIndex() : -1; int mnemonicIndex = FlatLaf.isShowMnemonics() ? b.getDisplayedMnemonicIndex() : -1;
@@ -906,7 +917,7 @@ public class FlatButtonUI
@Override @Override
public void stateChanged( ChangeEvent e ) { public void stateChanged( ChangeEvent e ) {
super.stateChanged( e ); HiDPIUtils.repaint( b );
// if button is in toolbar, repaint button groups // if button is in toolbar, repaint button groups
AbstractButton b = (AbstractButton) e.getSource(); AbstractButton b = (AbstractButton) e.getSource();
@@ -918,5 +929,17 @@ public class FlatButtonUI
((FlatToolBarUI)ui).repaintButtonGroup( b ); ((FlatToolBarUI)ui).repaintButtonGroup( b );
} }
} }
@Override
public void focusGained( FocusEvent e ) {
super.focusGained( e );
HiDPIUtils.repaint( b );
}
@Override
public void focusLost( FocusEvent e ) {
super.focusLost( e );
HiDPIUtils.repaint( b );
}
} }
} }

View File

@@ -23,6 +23,7 @@ import java.lang.invoke.MethodHandles;
import java.util.Map; import java.util.Map;
import javax.swing.Icon; import javax.swing.Icon;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicCheckBoxMenuItemUI; import javax.swing.plaf.basic.BasicCheckBoxMenuItemUI;
@@ -102,13 +103,23 @@ public class FlatCheckBoxMenuItemUI
oldStyleValues = null; oldStyleValues = null;
} }
@Override
protected void installComponents( JMenuItem menuItem ) {
super.installComponents( menuItem );
// update HTML renderer if necessary
FlatHTML.updateRendererCSSFontBaseSize( menuItem );
}
protected FlatMenuItemRenderer createRenderer() { protected FlatMenuItemRenderer createRenderer() {
return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter ); return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter );
} }
@Override @Override
protected PropertyChangeListener createPropertyChangeListener( JComponent c ) { protected PropertyChangeListener createPropertyChangeListener( JComponent c ) {
return FlatStylingSupport.createPropertyChangeListener( c, this::installStyle, super.createPropertyChangeListener( c ) ); return FlatHTML.createPropertyChangeListener(
FlatStylingSupport.createPropertyChangeListener( c, this::installStyle,
super.createPropertyChangeListener( c ) ) );
} }
/** @since 2 */ /** @since 2 */

View File

@@ -78,6 +78,7 @@ import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.SystemInfo;
@@ -220,7 +221,7 @@ public class FlatComboBoxUI
private void repaintArrowButton() { private void repaintArrowButton() {
if( arrowButton != null && !comboBox.isEditable() ) if( arrowButton != null && !comboBox.isEditable() )
arrowButton.repaint(); HiDPIUtils.repaint( arrowButton );
} }
}; };
comboBox.addMouseListener( hoverListener ); comboBox.addMouseListener( hoverListener );
@@ -351,15 +352,15 @@ public class FlatComboBoxUI
@Override @Override
public void focusGained( FocusEvent e ) { public void focusGained( FocusEvent e ) {
super.focusGained( e ); super.focusGained( e );
if( comboBox != null && comboBox.isEditable() ) if( comboBox != null )
comboBox.repaint(); HiDPIUtils.repaint( comboBox );
} }
@Override @Override
public void focusLost( FocusEvent e ) { public void focusLost( FocusEvent e ) {
super.focusLost( e ); super.focusLost( e );
if( comboBox != null && comboBox.isEditable() ) if( comboBox != null )
comboBox.repaint(); HiDPIUtils.repaint( comboBox );
} }
}; };
} }
@@ -386,12 +387,12 @@ public class FlatComboBoxUI
switch( propertyName ) { switch( propertyName ) {
case PLACEHOLDER_TEXT: case PLACEHOLDER_TEXT:
if( editor != null ) if( editor != null )
editor.repaint(); HiDPIUtils.repaint( editor );
break; break;
case COMPONENT_ROUND_RECT: case COMPONENT_ROUND_RECT:
case OUTLINE: case OUTLINE:
comboBox.repaint(); HiDPIUtils.repaint( comboBox );
break; break;
case MINIMUM_WIDTH: case MINIMUM_WIDTH:
@@ -402,7 +403,7 @@ public class FlatComboBoxUI
case STYLE_CLASS: case STYLE_CLASS:
installStyle(); installStyle();
comboBox.revalidate(); comboBox.revalidate();
comboBox.repaint(); HiDPIUtils.repaint( comboBox );
break; break;
} }
} }

View File

@@ -171,7 +171,7 @@ public class FlatEditorPaneUI
case FlatClientProperties.STYLE_CLASS: case FlatClientProperties.STYLE_CLASS:
installStyle.run(); installStyle.run();
c.revalidate(); c.revalidate();
c.repaint(); HiDPIUtils.repaint( c );
break; break;
} }
} }

View File

@@ -24,6 +24,7 @@ import java.awt.Graphics2D;
import java.awt.Image; import java.awt.Image;
import java.awt.Insets; import java.awt.Insets;
import java.awt.LayoutManager; import java.awt.LayoutManager;
import java.awt.Rectangle;
import java.awt.RenderingHints; import java.awt.RenderingHints;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
@@ -32,6 +33,7 @@ import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.function.Function; import java.util.function.Function;
import javax.swing.AbstractButton; import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.Box; import javax.swing.Box;
import javax.swing.BoxLayout; import javax.swing.BoxLayout;
import javax.swing.ButtonGroup; import javax.swing.ButtonGroup;
@@ -46,6 +48,7 @@ import javax.swing.JScrollPane;
import javax.swing.JTable; import javax.swing.JTable;
import javax.swing.JToggleButton; import javax.swing.JToggleButton;
import javax.swing.JToolBar; import javax.swing.JToolBar;
import javax.swing.Scrollable;
import javax.swing.SwingConstants; import javax.swing.SwingConstants;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.filechooser.FileSystemView; import javax.swing.filechooser.FileSystemView;
@@ -164,6 +167,7 @@ public class FlatFileChooserUI
{ {
private final FlatFileView fileView = new FlatFileView(); private final FlatFileView fileView = new FlatFileView();
private FlatShortcutsPanel shortcutsPanel; private FlatShortcutsPanel shortcutsPanel;
private JScrollPane shortcutsScrollPane;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
return new FlatFileChooserUI( (JFileChooser) c ); return new FlatFileChooserUI( (JFileChooser) c );
@@ -183,7 +187,10 @@ public class FlatFileChooserUI
FlatShortcutsPanel panel = createShortcutsPanel( fc ); FlatShortcutsPanel panel = createShortcutsPanel( fc );
if( panel.getComponentCount() > 0 ) { if( panel.getComponentCount() > 0 ) {
shortcutsPanel = panel; shortcutsPanel = panel;
fc.add( shortcutsPanel, BorderLayout.LINE_START ); shortcutsScrollPane = new JScrollPane( shortcutsPanel,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER );
shortcutsScrollPane.setBorder( BorderFactory.createEmptyBorder() );
fc.add( shortcutsScrollPane, BorderLayout.LINE_START );
fc.addPropertyChangeListener( shortcutsPanel ); fc.addPropertyChangeListener( shortcutsPanel );
} }
} }
@@ -196,6 +203,7 @@ public class FlatFileChooserUI
if( shortcutsPanel != null ) { if( shortcutsPanel != null ) {
fc.removePropertyChangeListener( shortcutsPanel ); fc.removePropertyChangeListener( shortcutsPanel );
shortcutsPanel = null; shortcutsPanel = null;
shortcutsScrollPane = null;
} }
} }
@@ -324,7 +332,7 @@ public class FlatFileChooserUI
public Dimension getPreferredSize( JComponent c ) { public Dimension getPreferredSize( JComponent c ) {
Dimension prefSize = super.getPreferredSize( c ); Dimension prefSize = super.getPreferredSize( c );
Dimension minSize = getMinimumSize( c ); Dimension minSize = getMinimumSize( c );
int shortcutsPanelWidth = (shortcutsPanel != null) ? shortcutsPanel.getPreferredSize().width : 0; int shortcutsPanelWidth = (shortcutsScrollPane != null) ? shortcutsScrollPane.getPreferredSize().width : 0;
return new Dimension( return new Dimension(
Math.max( prefSize.width, minSize.width + shortcutsPanelWidth ), Math.max( prefSize.width, minSize.width + shortcutsPanelWidth ),
Math.max( prefSize.height, minSize.height ) ); Math.max( prefSize.height, minSize.height ) );
@@ -401,7 +409,7 @@ public class FlatFileChooserUI
/** @since 2.3 */ /** @since 2.3 */
public static class FlatShortcutsPanel public static class FlatShortcutsPanel
extends JToolBar extends JToolBar
implements PropertyChangeListener implements PropertyChangeListener, Scrollable
{ {
private final JFileChooser fc; private final JFileChooser fc;
@@ -420,6 +428,7 @@ public class FlatFileChooserUI
super( JToolBar.VERTICAL ); super( JToolBar.VERTICAL );
this.fc = fc; this.fc = fc;
setFloatable( false ); setFloatable( false );
putClientProperty( FlatClientProperties.STYLE, "hoverButtonGroupBackground: null" );
buttonSize = UIScale.scale( getUIDimension( "FileChooser.shortcuts.buttonSize", 84, 64 ) ); buttonSize = UIScale.scale( getUIDimension( "FileChooser.shortcuts.buttonSize", 84, 64 ) );
iconSize = getUIDimension( "FileChooser.shortcuts.iconSize", 32, 32 ); iconSize = getUIDimension( "FileChooser.shortcuts.iconSize", 32, 32 );
@@ -429,7 +438,7 @@ public class FlatFileChooserUI
iconFunction = (Function<File, Icon>) UIManager.get( "FileChooser.shortcuts.iconFunction" ); iconFunction = (Function<File, Icon>) UIManager.get( "FileChooser.shortcuts.iconFunction" );
FileSystemView fsv = fc.getFileSystemView(); FileSystemView fsv = fc.getFileSystemView();
File[] files = getChooserShortcutPanelFiles( fsv ); File[] files = JavaCompatibility2.getChooserShortcutPanelFiles( fsv );
if( filesFunction != null ) if( filesFunction != null )
files = filesFunction.apply( files ); files = filesFunction.apply( files );
@@ -461,7 +470,7 @@ public class FlatFileChooserUI
icon = new ShortcutIcon( icon, iconSize.width, iconSize.height ); icon = new ShortcutIcon( icon, iconSize.width, iconSize.height );
// create button // create button
JToggleButton button = createButton( name, icon ); JToggleButton button = createButton( name, icon, file.toString() );
File f = file; File f = file;
button.addActionListener( e -> { button.addActionListener( e -> {
fc.setCurrentDirectory( f ); fc.setCurrentDirectory( f );
@@ -487,8 +496,10 @@ public class FlatFileChooserUI
return size; return size;
} }
protected JToggleButton createButton( String name, Icon icon ) { /** @since 3.5 */
protected JToggleButton createButton( String name, Icon icon, String toolTip ) {
JToggleButton button = new JToggleButton( name, icon ); JToggleButton button = new JToggleButton( name, icon );
button.setToolTipText( toolTip );
button.setVerticalTextPosition( SwingConstants.BOTTOM ); button.setVerticalTextPosition( SwingConstants.BOTTOM );
button.setHorizontalTextPosition( SwingConstants.CENTER ); button.setHorizontalTextPosition( SwingConstants.CENTER );
button.setAlignmentX( Component.CENTER_ALIGNMENT ); button.setAlignmentX( Component.CENTER_ALIGNMENT );
@@ -498,32 +509,6 @@ public class FlatFileChooserUI
return button; 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 ) { protected String getDisplayName( FileSystemView fsv, File file ) {
if( displayNameFunction != null ) { if( displayNameFunction != null ) {
String name = displayNameFunction.apply( file ); String name = displayNameFunction.apply( file );
@@ -592,6 +577,8 @@ public class FlatFileChooserUI
buttonGroup.clearSelection(); buttonGroup.clearSelection();
} }
//---- interface PropertyChangeListener ----
@Override @Override
public void propertyChange( PropertyChangeEvent e ) { public void propertyChange( PropertyChangeEvent e ) {
switch( e.getPropertyName() ) { switch( e.getPropertyName() ) {
@@ -600,6 +587,41 @@ public class FlatFileChooserUI
break; break;
} }
} }
//---- interface Scrollable ----
@Override
public Dimension getPreferredScrollableViewportSize() {
if( getComponentCount() > 0 ) {
Insets insets = getInsets();
int height = (getComponent( 0 ).getPreferredSize().height * 5) + insets.top + insets.bottom;
return new Dimension( getPreferredSize().width, height );
}
return getPreferredSize();
}
@Override
public int getScrollableUnitIncrement( Rectangle visibleRect, int orientation, int direction ) {
if( orientation == SwingConstants.VERTICAL && getComponentCount() > 0 )
return getComponent( 0 ).getPreferredSize().height;
return getScrollableBlockIncrement( visibleRect, orientation, direction ) / 10;
}
@Override
public int getScrollableBlockIncrement( Rectangle visibleRect, int orientation, int direction ) {
return (orientation == SwingConstants.VERTICAL) ? visibleRect.height : visibleRect.width;
}
@Override
public boolean getScrollableTracksViewportWidth() {
return true;
}
@Override
public boolean getScrollableTracksViewportHeight() {
return false;
}
} }
//---- class ShortcutIcon ------------------------------------------------- //---- class ShortcutIcon -------------------------------------------------

View File

@@ -0,0 +1,255 @@
/*
* 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.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import javax.swing.AbstractButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JToolTip;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.text.AttributeSet;
import javax.swing.text.Document;
import javax.swing.text.LabelView;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.View;
import javax.swing.text.html.CSS;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.StyleSheet;
/**
* @author Karl Tauber
* @since 3.5
*/
public class FlatHTML
{
private FlatHTML() {}
/**
* Adds CSS rule BASE_SIZE to the style sheet of the HTML view,
* which re-calculates font sizes based on current component font size.
* This is necessary for "absolute-size" keywords (e.g. "x-large")
* for "font-size" attributes in default style sheet (see javax/swing/text/html/default.css).
* See also <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/font-size#values">CSS font-size</a>.
* <p>
* This method should be invoked after {@link BasicHTML#updateRenderer(JComponent, String)}.
*/
public static void updateRendererCSSFontBaseSize( JComponent c ) {
View view = (View) c.getClientProperty( BasicHTML.propertyKey );
if( view == null )
return;
// dumpViews( view, 0 );
Document doc = view.getDocument();
if( !(doc instanceof HTMLDocument) )
return;
// add BASE_SIZE rule if necessary
// - if point size at index 7 is not 36, then probably HTML text contains BASE_SIZE rule
// - if point size at index 4 is equal to given font size, then it is not necessary to add BASE_SIZE rule
StyleSheet styleSheet = ((HTMLDocument)doc).getStyleSheet();
/*debug
for( int i = 1; i <= 7; i++ )
System.out.println( i+": "+ styleSheet.getPointSize( i ) );
debug*/
int fontBaseSize = c.getFont().getSize();
if( styleSheet.getPointSize( 7 ) != 36f ||
styleSheet.getPointSize( 4 ) == fontBaseSize )
return;
// check whether view uses "absolute-size" keywords (e.g. "x-large") for font-size
if( !usesAbsoluteSizeKeywordForFontSize( view ) )
return;
// get HTML text from component
String text;
if( c instanceof JLabel )
text = ((JLabel)c).getText();
else if( c instanceof AbstractButton )
text = ((AbstractButton)c).getText();
else if( c instanceof JToolTip )
text = ((JToolTip)c).getTipText();
else
return;
if( text == null )
return;
// BASE_SIZE rule is parsed in javax.swing.text.html.StyleSheet.addRule()
String style = "<style>BASE_SIZE " + c.getFont().getSize() + "</style>";
String openTag = "";
String closeTag = "";
String lowerText = text.toLowerCase( Locale.ENGLISH );
int headIndex;
int styleIndex;
int insertIndex;
if( (headIndex = lowerText.indexOf( "<head>" )) >= 0 ) {
// there is a <head> tag --> insert after <head> tag
insertIndex = headIndex + "<head>".length();
} else if( (styleIndex = lowerText.indexOf( "<style>" )) >= 0 ) {
// there is a <style> tag --> insert before <style> tag
insertIndex = styleIndex;
} else {
// no <head> or <style> tag --> insert <head> tag after <html> tag
insertIndex = "<html>".length();
openTag = "<head>";
closeTag = "</head>";
}
String newText = text.substring( 0, insertIndex )
+ openTag + style + closeTag
+ text.substring( insertIndex );
BasicHTML.updateRenderer( c, newText );
}
private static final Set<String> absoluteSizeKeywordsSet = new HashSet<>( Arrays.asList(
"xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large" ) );
/**
* Checks whether view uses "absolute-size" keywords (e.g. "x-large") for font-size
* (see javax/swing/text/html/default.css).
*/
private static boolean usesAbsoluteSizeKeywordForFontSize( View view ) {
AttributeSet attributes = view.getAttributes();
if( attributes != null ) {
Object fontSize = attributes.getAttribute( CSS.Attribute.FONT_SIZE );
if( fontSize != null ) {
if( absoluteSizeKeywordsSet.contains( fontSize.toString() ) )
return true;
}
}
int viewCount = view.getViewCount();
for( int i = 0; i < viewCount; i++ ) {
if( usesAbsoluteSizeKeywordForFontSize( view.getView( i ) ) )
return true;
}
return false;
}
/**
* Updates foreground in style sheet of the HTML view.
* Adds "body { color: #&lt;foreground-hex&gt;; }"
*/
public static void updateRendererCSSForeground( View view, Color foreground ) {
Document doc = view.getDocument();
if( !(doc instanceof HTMLDocument) || foreground == null )
return;
// add foreground rule if necessary
// - use tag 'body' because BasicHTML.createHTMLView() also uses this tag
// to set font and color styles to component font/color
// see: SwingUtilities2.displayPropertiesToCSS()
// - this color is not used if component is disabled;
// JTextComponent.getDisabledTextColor() is used for disabled text components;
// UIManager.getColor("textInactiveText") is used for other disabled components
// see: javax.swing.text.GlyphView.paint()
Style bodyStyle = ((HTMLDocument)doc).getStyle( "body" );
if( bodyStyle == null ) {
StyleSheet styleSheet = ((HTMLDocument)doc).getStyleSheet();
styleSheet.addRule( String.format( "body { color: #%06x; }", foreground.getRGB() & 0xffffff ) );
clearViewCaches( view );
} else if( !foreground.equals( bodyStyle.getAttribute( StyleConstants.Foreground ) ) ) {
bodyStyle.addAttribute( StyleConstants.Foreground, foreground );
clearViewCaches( view );
}
}
/**
* Clears cached values in view so that CSS changes take effect.
*/
private static void clearViewCaches( View view ) {
if( view instanceof LabelView )
((LabelView)view).changedUpdate( null, null, null );
int viewCount = view.getViewCount();
for( int i = 0; i < viewCount; i++ )
clearViewCaches( view.getView( i ) );
}
public static PropertyChangeListener createPropertyChangeListener( PropertyChangeListener superListener ) {
return e -> {
if( superListener != null )
superListener.propertyChange( e );
propertyChange( e );
};
}
/**
* Invokes {@link #updateRendererCSSFontBaseSize(JComponent)}
* for {@link BasicHTML#propertyKey} property change events,
* which are fired when {@link BasicHTML#updateRenderer(JComponent, String)}
* updates the HTML view.
*/
public static void propertyChange( PropertyChangeEvent e ) {
if( BasicHTML.propertyKey.equals( e.getPropertyName() ) && e.getNewValue() instanceof View )
updateRendererCSSFontBaseSize( (JComponent) e.getSource() );
}
/*debug
public static void dumpView( JComponent c ) {
View view = (View) c.getClientProperty( BasicHTML.propertyKey );
if( view != null )
dumpViews( view, 0 );
}
public static void dumpViews( View view, int indent ) {
for( int i = 0; i < indent; i++ )
System.out.print( " " );
System.out.printf( "%s @%-8x %3d,%2d",
view.getClass().isAnonymousClass() ? view.getClass().getName() : view.getClass().getSimpleName(),
System.identityHashCode( view ),
(int) view.getPreferredSpan( View.X_AXIS ),
(int) view.getPreferredSpan( View.Y_AXIS ) );
AttributeSet attrs = view.getAttributes();
if( attrs != null ) {
Object fontSize = attrs.getAttribute( CSS.Attribute.FONT_SIZE );
System.out.printf( " %-8s", fontSize );
}
if( view instanceof javax.swing.text.GlyphView ) {
javax.swing.text.GlyphView gview = ((javax.swing.text.GlyphView)view);
java.awt.Font font = gview.getFont();
System.out.printf( " %3d-%-3d %s %2d (@%x) #%06x '%s'",
gview.getStartOffset(), gview.getEndOffset() - 1,
font.getName(), font.getSize(), System.identityHashCode( font ),
gview.getForeground().getRGB() & 0xffffff,
gview.getText( gview.getStartOffset(), gview.getEndOffset() ) );
}
System.out.println();
int viewCount = view.getViewCount();
for( int i = 0; i < viewCount; i++ ) {
View child = view.getView( i );
dumpViews( child, indent + 1 );
}
}
debug*/
}

View File

@@ -22,11 +22,7 @@ import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set;
import javax.swing.Icon; import javax.swing.Icon;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JLabel; import javax.swing.JLabel;
@@ -64,6 +60,9 @@ public class FlatLabelUI
{ {
@Styleable protected Color disabledForeground; @Styleable protected Color disabledForeground;
// only used via styling (not in UI defaults)
/** @since 3.5 */ @Styleable protected int arc = -1;
private final boolean shared; private final boolean shared;
private boolean defaults_initialized = false; private boolean defaults_initialized = false;
private Map<String, Object> oldStyleValues; private Map<String, Object> oldStyleValues;
@@ -110,16 +109,13 @@ public class FlatLabelUI
super.installComponents( c ); super.installComponents( c );
// update HTML renderer if necessary // update HTML renderer if necessary
updateHTMLRenderer( c, c.getText(), false ); FlatHTML.updateRendererCSSFontBaseSize( c );
} }
@Override @Override
public void propertyChange( PropertyChangeEvent e ) { public void propertyChange( PropertyChangeEvent e ) {
String name = e.getPropertyName(); String name = e.getPropertyName();
if( name == "text" || name == "font" || name == "foreground" ) { if( name.equals( FlatClientProperties.STYLE ) || name.equals( FlatClientProperties.STYLE_CLASS ) ) {
JLabel label = (JLabel) e.getSource();
updateHTMLRenderer( label, label.getText(), true );
} else if( name.equals( FlatClientProperties.STYLE ) || name.equals( FlatClientProperties.STYLE_CLASS ) ) {
JLabel label = (JLabel) e.getSource(); JLabel label = (JLabel) e.getSource();
if( shared && FlatStylingSupport.hasStyleProperty( label ) ) { if( shared && FlatStylingSupport.hasStyleProperty( label ) ) {
// unshare component UI if necessary // unshare component UI if necessary
@@ -128,9 +124,11 @@ public class FlatLabelUI
} else } else
installStyle( label ); installStyle( label );
label.revalidate(); label.revalidate();
label.repaint(); HiDPIUtils.repaint( label );
} else }
super.propertyChange( e );
super.propertyChange( e );
FlatHTML.propertyChange( e );
} }
/** @since 2 */ /** @since 2 */
@@ -165,83 +163,10 @@ public class FlatLabelUI
return FlatStylingSupport.getAnnotatedStyleableValue( this, key ); return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
} }
/** @Override
* Checks whether text contains HTML tags that use "absolute-size" keywords public void update( Graphics g, JComponent c ) {
* (e.g. "x-large") for font-size in default style sheet FlatPanelUI.fillRoundedBackground( g, c, arc );
* (see javax/swing/text/html/default.css). paint( g, c );
* If yes, adds a special CSS rule (BASE_SIZE) to the HTML text, which
* re-calculates font sizes based on current component font size.
*/
static void updateHTMLRenderer( JComponent c, String text, boolean always ) {
if( BasicHTML.isHTMLString( text ) &&
c.getClientProperty( "html.disable" ) != Boolean.TRUE &&
needsFontBaseSize( text ) )
{
// BASE_SIZE rule is parsed in javax.swing.text.html.StyleSheet.addRule()
String style = "<style>BASE_SIZE " + c.getFont().getSize() + "</style>";
String lowerText = text.toLowerCase( Locale.ENGLISH );
int headIndex;
int styleIndex;
int insertIndex;
if( (headIndex = lowerText.indexOf( "<head>" )) >= 0 ) {
// there is a <head> tag --> insert after <head> tag
insertIndex = headIndex + "<head>".length();
} else if( (styleIndex = lowerText.indexOf( "<style>" )) >= 0 ) {
// there is a <style> tag --> insert before <style> tag
insertIndex = styleIndex;
} else {
// no <head> or <style> tag --> insert <head> tag after <html> tag
style = "<head>" + style + "</head>";
insertIndex = "<html>".length();
}
text = text.substring( 0, insertIndex )
+ style
+ text.substring( insertIndex );
} else if( !always )
return; // not necessary to invoke BasicHTML.updateRenderer()
BasicHTML.updateRenderer( c, text );
}
private static Set<String> tagsUseFontSizeSet;
private static boolean needsFontBaseSize( String text ) {
if( tagsUseFontSizeSet == null ) {
// tags that use font-size in javax/swing/text/html/default.css
tagsUseFontSizeSet = new HashSet<>( Arrays.asList(
"h1", "h2", "h3", "h4", "h5", "h6", "code", "kbd", "big", "small", "samp" ) );
}
// search for tags in HTML text
int textLength = text.length();
for( int i = 6; i < textLength - 1; i++ ) {
if( text.charAt( i ) == '<' ) {
switch( text.charAt( i + 1 ) ) {
// first letters of tags in tagsUseFontSizeSet
case 'b': case 'B':
case 'c': case 'C':
case 'h': case 'H':
case 'k': case 'K':
case 's': case 'S':
int tagBegin = i + 1;
for( i += 2; i < textLength; i++ ) {
if( !Character.isLetterOrDigit( text.charAt( i ) ) ) {
String tag = text.substring( tagBegin, i ).toLowerCase( Locale.ENGLISH );
if( tagsUseFontSizeSet.contains( tag ) )
return true;
break;
}
}
break;
}
}
}
return false;
} }
static Graphics createGraphicsHTMLTextYCorrection( Graphics g, JComponent c ) { static Graphics createGraphicsHTMLTextYCorrection( Graphics g, JComponent c ) {

View File

@@ -23,13 +23,20 @@ import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Insets; import java.awt.Insets;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.plaf.ComponentUI;
/** /**
* Line border for various components. * Line border for various components.
* * <p>
* Paints a scaled (usually 1px thick) line around the component. * Paints a scaled (usually 1px thick) line around the component.
* The line thickness is not added to the border insets. * The line thickness is not added to the border insets.
* The insets should be at least have line thickness (usually 1,1,1,1). * The insets should be at least have line thickness (usually 1,1,1,1).
* <p>
* For {@link javax.swing.JPanel} and {@link javax.swing.JLabel}, this border
* can be used paint rounded background (if line color is {@code null}) or
* paint rounded line border with rounded background.
* *
* @author Karl Tauber * @author Karl Tauber
*/ */
@@ -41,7 +48,7 @@ public class FlatLineBorder
/** @since 2 */ private final int arc; /** @since 2 */ private final int arc;
public FlatLineBorder( Insets insets, Color lineColor ) { public FlatLineBorder( Insets insets, Color lineColor ) {
this( insets, lineColor, 1f, 0 ); this( insets, lineColor, 1f, -1 );
} }
/** @since 2 */ /** @since 2 */
@@ -52,15 +59,28 @@ public class FlatLineBorder
this.arc = arc; this.arc = arc;
} }
/** @since 3.5 */
public FlatLineBorder( Insets insets, int arc ) {
this( insets, null, 0, arc );
}
public Color getLineColor() { public Color getLineColor() {
return lineColor; return lineColor;
} }
/**
* Returns the (unscaled) line thickness used to paint the border.
* The line thickness does not affect the border insets.
*/
public float getLineThickness() { public float getLineThickness() {
return lineThickness; return lineThickness;
} }
/** @since 2 */ /**
* Returns the (unscaled) arc diameter of the border corners.
*
* @since 2
*/
public int getArc() { public int getArc() {
return arc; return arc;
} }
@@ -70,11 +90,31 @@ public class FlatLineBorder
if( c instanceof JComponent && ((JComponent)c).getClientProperty( FlatPopupFactory.KEY_POPUP_USES_NATIVE_BORDER ) != null ) if( c instanceof JComponent && ((JComponent)c).getClientProperty( FlatPopupFactory.KEY_POPUP_USES_NATIVE_BORDER ) != null )
return; return;
Color lineColor = getLineColor();
float lineThickness = getLineThickness();
if( lineColor == null || lineThickness <= 0 )
return;
int arc = getArc();
if( arc < 0 ) {
// get arc from label or panel
ComponentUI ui = (c instanceof JLabel)
? ((JLabel)c).getUI()
: (c instanceof JPanel ? ((JPanel)c).getUI() : null);
if( ui instanceof FlatLabelUI )
arc = ((FlatLabelUI)ui).arc;
else if( ui instanceof FlatPanelUI )
arc = ((FlatPanelUI)ui).arc;
if( arc < 0 )
arc = 0;
}
Graphics2D g2 = (Graphics2D) g.create(); Graphics2D g2 = (Graphics2D) g.create();
try { try {
FlatUIUtils.setRenderingHints( g2 ); FlatUIUtils.setRenderingHints( g2 );
FlatUIUtils.paintOutlinedComponent( g2, x, y, width, height, FlatUIUtils.paintOutlinedComponent( g2, x, y, width, height,
0, 0, 0, scale( getLineThickness() ), scale( getArc() ), null, getLineColor(), null ); 0, 0, 0, scale( lineThickness ), scale( arc ), null, lineColor, null );
} finally { } finally {
g2.dispose(); g2.dispose();
} }

View File

@@ -43,6 +43,7 @@ import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.Graphics2DProxy; import com.formdev.flatlaf.util.Graphics2DProxy;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
@@ -182,7 +183,7 @@ public class FlatListUI
case FlatClientProperties.STYLE_CLASS: case FlatClientProperties.STYLE_CLASS:
installStyle(); installStyle();
list.revalidate(); list.revalidate();
list.repaint(); HiDPIUtils.repaint( list );
break; break;
} }
}; };
@@ -205,7 +206,7 @@ public class FlatListUI
Rectangle r = getCellBounds( list, firstIndex, lastIndex ); Rectangle r = getCellBounds( list, firstIndex, lastIndex );
if( r != null ) { if( r != null ) {
int arc = (int) Math.ceil( UIScale.scale( selectionArc / 2f ) ); int arc = (int) Math.ceil( UIScale.scale( selectionArc / 2f ) );
list.repaint( r.x - arc, r.y - arc, r.width + (arc * 2), r.height + (arc * 2) ); HiDPIUtils.repaint( list, r.x - arc, r.y - arc, r.width + (arc * 2), r.height + (arc * 2) );
} }
} }
}; };
@@ -324,8 +325,7 @@ public class FlatListUI
(rendererComponent instanceof DefaultListCellRenderer || (rendererComponent instanceof DefaultListCellRenderer ||
rendererComponent instanceof BasicComboBoxRenderer) && rendererComponent instanceof BasicComboBoxRenderer) &&
(selectionArc > 0 || (selectionArc > 0 ||
(selectionInsets != null && (selectionInsets != null && !FlatUIUtils.isInsetsEmpty( selectionInsets ))) )
(selectionInsets.top != 0 || selectionInsets.left != 0 || selectionInsets.bottom != 0 || selectionInsets.right != 0))) )
{ {
// Because selection painting is done in the cell renderer, it would be // Because selection painting is done in the cell renderer, it would be
// necessary to require a FlatLaf specific renderer to implement rounded selection. // necessary to require a FlatLaf specific renderer to implement rounded selection.
@@ -374,7 +374,15 @@ public class FlatListUI
rendererPane.paintComponent( g, rendererComponent, list, cx, rowBounds.y, cw, rowBounds.height, true ); rendererPane.paintComponent( g, rendererComponent, list, cx, rowBounds.y, cw, rowBounds.height, true );
} }
/** @since 3 */ /**
* Paints (rounded) cell selection.
* Supports {@link #selectionArc} and {@link #selectionInsets}.
* <p>
* <b>Note:</b> This method is only invoked if either selection arc
* is greater than zero or if selection insets are not empty.
*
* @since 3
*/
protected void paintCellSelection( Graphics g, int row, int x, int y, int width, int height ) { protected void paintCellSelection( Graphics g, int row, int x, int y, int width, int height ) {
float arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight; float arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight;
arcTopLeft = arcTopRight = arcBottomLeft = arcBottomRight = UIScale.scale( selectionArc / 2f ); arcTopLeft = arcTopRight = arcBottomLeft = arcBottomRight = UIScale.scale( selectionArc / 2f );
@@ -440,7 +448,8 @@ public class FlatListUI
* Paints a cell selection at the given coordinates. * Paints a cell selection at the given coordinates.
* The selection color must be set on the graphics context. * The selection color must be set on the graphics context.
* <p> * <p>
* This method is intended for use in custom cell renderers. * This method is intended for use in custom cell renderers
* to support {@link #selectionArc} and {@link #selectionInsets}.
* *
* @since 3 * @since 3
*/ */

View File

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

View File

@@ -25,7 +25,9 @@ import java.awt.Font;
import java.awt.FontMetrics; import java.awt.FontMetrics;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Insets; import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Paint; import java.awt.Paint;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.event.InputEvent; import java.awt.event.InputEvent;
@@ -35,6 +37,7 @@ import java.util.Map;
import javax.swing.Icon; import javax.swing.Icon;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JMenu; import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem; import javax.swing.JMenuItem;
import javax.swing.KeyStroke; import javax.swing.KeyStroke;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
@@ -222,7 +225,7 @@ public class FlatMenuItemRenderer
} }
// arrow size // arrow size
if( !isTopLevelMenu && arrowIcon != null ) { if( arrowIcon != null && (!isTopLevelMenu || isInVerticalMenuBar( menuItem )) ) {
// gap between text and arrow // gap between text and arrow
if( accelText == null ) if( accelText == null )
width += scale( textNoAcceleratorGap ); width += scale( textNoAcceleratorGap );
@@ -254,7 +257,8 @@ public class FlatMenuItemRenderer
boolean isTopLevelMenu = isTopLevelMenu( menuItem ); boolean isTopLevelMenu = isTopLevelMenu( menuItem );
// layout arrow // layout arrow
if( !isTopLevelMenu && arrowIcon != null ) { boolean showArrowIcon = (arrowIcon != null && (!isTopLevelMenu || isInVerticalMenuBar( menuItem )));
if( showArrowIcon ) {
arrowRect.width = arrowIcon.getIconWidth(); arrowRect.width = arrowIcon.getIconWidth();
arrowRect.height = arrowIcon.getIconHeight(); arrowRect.height = arrowIcon.getIconHeight();
} else } else
@@ -288,7 +292,7 @@ public class FlatMenuItemRenderer
int accelArrowWidth = accelRect.width + arrowRect.width; int accelArrowWidth = accelRect.width + arrowRect.width;
if( accelText != null ) if( accelText != null )
accelArrowWidth += scale( !isTopLevelMenu ? textAcceleratorGap : menuItem.getIconTextGap() ); accelArrowWidth += scale( !isTopLevelMenu ? textAcceleratorGap : menuItem.getIconTextGap() );
if( !isTopLevelMenu && arrowIcon != null ) { if( showArrowIcon ) {
if( accelText == null ) if( accelText == null )
accelArrowWidth += scale( textNoAcceleratorGap ); accelArrowWidth += scale( textNoAcceleratorGap );
accelArrowWidth += scale( acceleratorArrowGap ); accelArrowWidth += scale( acceleratorArrowGap );
@@ -355,7 +359,7 @@ debug*/
paintIcon( g, iconRect, getIconForPainting(), underlineSelection ? underlineSelectionCheckBackground : checkBackground, selectionBackground ); paintIcon( g, iconRect, getIconForPainting(), underlineSelection ? underlineSelectionCheckBackground : checkBackground, selectionBackground );
paintText( g, textRect, menuItem.getText(), selectionForeground, disabledForeground ); paintText( g, textRect, menuItem.getText(), selectionForeground, disabledForeground );
paintAccelerator( g, accelRect, getAcceleratorText(), acceleratorForeground, acceleratorSelectionForeground, disabledForeground ); paintAccelerator( g, accelRect, getAcceleratorText(), acceleratorForeground, acceleratorSelectionForeground, disabledForeground );
if( !isTopLevelMenu( menuItem ) ) if( arrowIcon != null && (!isTopLevelMenu( menuItem ) || isInVerticalMenuBar( menuItem )) )
paintArrowIcon( g, arrowRect, arrowIcon ); paintArrowIcon( g, arrowRect, arrowIcon );
} }
@@ -520,6 +524,15 @@ debug*/
return menuItem instanceof JMenu && ((JMenu)menuItem).isTopLevelMenu(); return menuItem instanceof JMenu && ((JMenu)menuItem).isTopLevelMenu();
} }
/** @since 3.5 */
public static boolean isInVerticalMenuBar( JMenuItem menuItem ) {
if( !(menuItem instanceof JMenu) || !(menuItem.getParent() instanceof JMenuBar) )
return false;
LayoutManager layout = menuItem.getParent().getLayout();
return layout instanceof GridLayout && ((GridLayout)layout).getRows() != 1;
}
protected boolean isUnderlineSelection() { protected boolean isUnderlineSelection() {
return "underline".equals( UIManager.getString( "MenuItem.selectionType" ) ); return "underline".equals( UIManager.getString( "MenuItem.selectionType" ) );
} }

View File

@@ -103,13 +103,23 @@ public class FlatMenuItemUI
oldStyleValues = null; oldStyleValues = null;
} }
@Override
protected void installComponents( JMenuItem menuItem ) {
super.installComponents( menuItem );
// update HTML renderer if necessary
FlatHTML.updateRendererCSSFontBaseSize( menuItem );
}
protected FlatMenuItemRenderer createRenderer() { protected FlatMenuItemRenderer createRenderer() {
return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter ); return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter );
} }
@Override @Override
protected PropertyChangeListener createPropertyChangeListener( JComponent c ) { protected PropertyChangeListener createPropertyChangeListener( JComponent c ) {
return FlatStylingSupport.createPropertyChangeListener( c, this::installStyle, super.createPropertyChangeListener( c ) ); return FlatHTML.createPropertyChangeListener(
FlatStylingSupport.createPropertyChangeListener( c, this::installStyle,
super.createPropertyChangeListener( c ) ) );
} }
/** @since 2 */ /** @since 2 */

View File

@@ -17,6 +17,7 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import java.awt.Color; import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Font; import java.awt.Font;
import java.awt.Graphics; import java.awt.Graphics;
@@ -46,6 +47,7 @@ import javax.swing.plaf.basic.BasicMenuUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
/** /**
@@ -135,6 +137,14 @@ public class FlatMenuUI
oldStyleValues = null; oldStyleValues = null;
} }
@Override
protected void installComponents( JMenuItem menuItem ) {
super.installComponents( menuItem );
// update HTML renderer if necessary
FlatHTML.updateRendererCSSFontBaseSize( menuItem );
}
protected FlatMenuItemRenderer createRenderer() { protected FlatMenuItemRenderer createRenderer() {
return new FlatMenuRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter ); return new FlatMenuRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter );
} }
@@ -158,7 +168,7 @@ public class FlatMenuUI
JMenu menu = (JMenu) e.getSource(); JMenu menu = (JMenu) e.getSource();
if( menu.isTopLevelMenu() && menu.isRolloverEnabled() ) { if( menu.isTopLevelMenu() && menu.isRolloverEnabled() ) {
menu.getModel().setRollover( rollover ); menu.getModel().setRollover( rollover );
menu.repaint(); HiDPIUtils.repaint( menu );
} }
} }
}; };
@@ -166,7 +176,9 @@ public class FlatMenuUI
@Override @Override
protected PropertyChangeListener createPropertyChangeListener( JComponent c ) { protected PropertyChangeListener createPropertyChangeListener( JComponent c ) {
return FlatStylingSupport.createPropertyChangeListener( c, this::installStyle, super.createPropertyChangeListener( c ) ); return FlatHTML.createPropertyChangeListener(
FlatStylingSupport.createPropertyChangeListener( c, this::installStyle,
super.createPropertyChangeListener( c ) ) );
} }
/** @since 2 */ /** @since 2 */
@@ -271,7 +283,7 @@ public class FlatMenuUI
if( !isHover() ) if( !isHover() )
selectionBackground = getStyleFromMenuBarUI( ui -> ui.selectionBackground, menuBarSelectionBackground, selectionBackground ); selectionBackground = getStyleFromMenuBarUI( ui -> ui.selectionBackground, menuBarSelectionBackground, selectionBackground );
JMenuBar menuBar = (JMenuBar) menuItem.getParent(); Container menuBar = menuItem.getParent();
JRootPane rootPane = SwingUtilities.getRootPane( menuBar ); JRootPane rootPane = SwingUtilities.getRootPane( menuBar );
if( rootPane != null && rootPane.getParent() instanceof Window && if( rootPane != null && rootPane.getParent() instanceof Window &&
rootPane.getJMenuBar() == menuBar && rootPane.getJMenuBar() == menuBar &&
@@ -321,12 +333,17 @@ public class FlatMenuUI
} }
private <T> T getStyleFromMenuBarUI( Function<FlatMenuBarUI, T> f, T defaultValue ) { private <T> T getStyleFromMenuBarUI( Function<FlatMenuBarUI, T> f, T defaultValue ) {
MenuBarUI ui = ((JMenuBar)menuItem.getParent()).getUI(); Container menuItemParent = menuItem.getParent();
if( !(ui instanceof FlatMenuBarUI) ) if( menuItemParent instanceof JMenuBar ) {
return defaultValue; MenuBarUI ui = ((JMenuBar) menuItemParent).getUI();
if( ui instanceof FlatMenuBarUI ) {
T value = f.apply( (FlatMenuBarUI) ui ); T value = f.apply( (FlatMenuBarUI) ui );
return (value != null) ? value : defaultValue; 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.FlatSystemProperties;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.NativeLibrary; import com.formdev.flatlaf.util.NativeLibrary;
import com.formdev.flatlaf.util.StringUtils;
import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.SystemInfo;
/** /**
@@ -36,16 +37,18 @@ class FlatNativeLibrary
private static boolean initialized; private static boolean initialized;
private static NativeLibrary nativeLibrary; private static NativeLibrary nativeLibrary;
private native static int getApiVersion();
/** /**
* Loads native library (if available) and returns whether loaded successfully. * Loads native library (if available) and returns whether loaded successfully.
* Returns {@code false} if no native library is available. * Returns {@code false} if no native library is available.
*/ */
static synchronized boolean isLoaded() { static synchronized boolean isLoaded( int apiVersion ) {
initialize(); initialize( apiVersion );
return (nativeLibrary != null) ? nativeLibrary.isLoaded() : false; return (nativeLibrary != null) ? nativeLibrary.isLoaded() : false;
} }
private static void initialize() { private static void initialize( int apiVersion ) {
if( initialized ) if( initialized )
return; return;
initialized = true; initialized = true;
@@ -103,7 +106,26 @@ class FlatNativeLibrary
return; // no native library available for current OS or CPU architecture return; // no native library available for current OS or CPU architecture
// load native library // load native library
nativeLibrary = createNativeLibrary( classifier, ext ); NativeLibrary nativeLibrary = createNativeLibrary( classifier, ext );
if( !nativeLibrary.isLoaded() )
return;
// check API version (and check whether library works)
try {
int actualApiVersion = getApiVersion();
if( actualApiVersion != apiVersion ) {
LoggingFacade.INSTANCE.logSevere( "FlatLaf: Wrong API version in native library (expected "
+ apiVersion + ", actual " + actualApiVersion + "). Ignoring native library.", null );
return;
}
} catch( Throwable ex ) {
// could be a UnsatisfiedLinkError in case that loading native library
// from temp directory was blocked by some OS security mechanism
LoggingFacade.INSTANCE.logSevere( "FlatLaf: Failed to get API version of native library. Ignoring native library.", ex );
return;
}
FlatNativeLibrary.nativeLibrary = nativeLibrary;
} }
private static NativeLibrary createNativeLibrary( String classifier, String ext ) { private static NativeLibrary createNativeLibrary( String classifier, String ext ) {
@@ -117,13 +139,32 @@ class FlatNativeLibrary
if( library.isLoaded() ) if( library.isLoaded() )
return library; return library;
LoggingFacade.INSTANCE.logSevere( "Did not find library " + libraryName + " in java.library.path, using extracted library instead", null ); LoggingFacade.INSTANCE.logSevere( "FlatLaf: Native library '" + System.mapLibraryName( libraryName )
+ "' not found in java.library.path '" + System.getProperty( "java.library.path" )
+ "'. Using extracted native library instead.", null );
} else { } 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 ) ); File libraryFile = new File( libraryPath, System.mapLibraryName( libraryName ) );
if( libraryFile.exists() ) if( libraryFile.exists() )
return new NativeLibrary( libraryFile, true ); 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( "FlatLaf: Native library '"
+ libraryFile.getName()
+ (libraryName2 != null ? ("' or '" + libraryName2) : "")
+ "' not found in '" + libraryFile.getParentFile().getAbsolutePath()
+ "'. Using extracted native library instead.", null );
} }
} }
@@ -151,6 +192,51 @@ class FlatNativeLibrary
* flatlaf-3.1-linux-x86_64.so * flatlaf-3.1-linux-x86_64.so
*/ */
private static File findLibraryBesideJar( String classifier, String ext ) { 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 { try {
// get location of FlatLaf jar // get location of FlatLaf jar
CodeSource codeSource = FlatNativeLibrary.class.getProtectionDomain().getCodeSource(); CodeSource codeSource = FlatNativeLibrary.class.getProtectionDomain().getCodeSource();
@@ -168,31 +254,23 @@ class FlatNativeLibrary
if( !jarFile.isFile() ) if( !jarFile.isFile() )
return null; return null;
// build library file return jarFile;
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;
}
} catch( Exception ex ) { } catch( Exception ex ) {
LoggingFacade.INSTANCE.logSevere( ex.getMessage(), 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() { private static void loadJAWT() {

View File

@@ -35,6 +35,8 @@ import com.formdev.flatlaf.util.SystemInfo;
*/ */
class FlatNativeLinuxLibrary class FlatNativeLinuxLibrary
{ {
private static int API_VERSION_LINUX = 3001;
/** /**
* Checks whether native library is loaded/available. * Checks whether native library is loaded/available.
* <p> * <p>
@@ -42,7 +44,7 @@ class FlatNativeLinuxLibrary
* method of this class. Otherwise, the native library may not be loaded. * method of this class. Otherwise, the native library may not be loaded.
*/ */
static boolean isLoaded() { static boolean isLoaded() {
return SystemInfo.isLinux && FlatNativeLibrary.isLoaded(); return SystemInfo.isLinux && FlatNativeLibrary.isLoaded( API_VERSION_LINUX );
} }
// direction for _NET_WM_MOVERESIZE message // direction for _NET_WM_MOVERESIZE message

View File

@@ -16,7 +16,9 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import java.awt.Rectangle;
import java.awt.Window; import java.awt.Window;
import com.formdev.flatlaf.util.SystemInfo;
/** /**
* Native methods for macOS. * Native methods for macOS.
@@ -42,6 +44,8 @@ import java.awt.Window;
*/ */
public class FlatNativeMacLibrary public class FlatNativeMacLibrary
{ {
private static int API_VERSION_MACOS = 2001;
/** /**
* Checks whether native library is loaded/available. * Checks whether native library is loaded/available.
* <p> * <p>
@@ -49,8 +53,19 @@ public class FlatNativeMacLibrary
* method of this class. Otherwise, the native library may not be loaded. * method of this class. Otherwise, the native library may not be loaded.
*/ */
public static boolean isLoaded() { public static boolean isLoaded() {
return FlatNativeLibrary.isLoaded(); return SystemInfo.isMacOS && FlatNativeLibrary.isLoaded( API_VERSION_MACOS );
} }
public native static boolean setWindowRoundedBorder( Window window, float radius, float borderWidth, int borderColor ); 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

@@ -21,11 +21,12 @@ import java.awt.Component;
import java.awt.Container; import java.awt.Container;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.Toolkit; import java.awt.Toolkit;
import java.awt.Window; import java.awt.Window;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.util.List; import java.util.function.Predicate;
import javax.swing.JDialog; import javax.swing.JDialog;
import javax.swing.JFrame; import javax.swing.JFrame;
import javax.swing.JRootPane; import javax.swing.JRootPane;
@@ -218,13 +219,13 @@ public class FlatNativeWindowBorder
} }
static void setTitleBarHeightAndHitTestSpots( Window window, int titleBarHeight, 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 ) Rectangle maximizeButtonBounds, Rectangle closeButtonBounds )
{ {
if( !isSupported() ) if( !isSupported() )
return; return;
nativeProvider.updateTitleBarInfo( window, titleBarHeight, hitTestSpots, nativeProvider.updateTitleBarInfo( window, titleBarHeight, captionHitTestCallback,
appIconBounds, minimizeButtonBounds, maximizeButtonBounds, closeButtonBounds ); appIconBounds, minimizeButtonBounds, maximizeButtonBounds, closeButtonBounds );
} }
@@ -270,7 +271,7 @@ public class FlatNativeWindowBorder
{ {
boolean hasCustomDecoration( Window window ); boolean hasCustomDecoration( Window window );
void setHasCustomDecoration( Window window, boolean hasCustomDecoration ); 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 appIconBounds, Rectangle minimizeButtonBounds, Rectangle maximizeButtonBounds,
Rectangle closeButtonBounds ); Rectangle closeButtonBounds );

View File

@@ -30,6 +30,8 @@ import com.formdev.flatlaf.util.SystemInfo;
*/ */
public class FlatNativeWindowsLibrary public class FlatNativeWindowsLibrary
{ {
private static int API_VERSION_WINDOWS = 1001;
private static long osBuildNumber = Long.MIN_VALUE; private static long osBuildNumber = Long.MIN_VALUE;
/** /**
@@ -39,7 +41,7 @@ public class FlatNativeWindowsLibrary
* method of this class. Otherwise, the native library may not be loaded. * method of this class. Otherwise, the native library may not be loaded.
*/ */
public static boolean isLoaded() { public static boolean isLoaded() {
return SystemInfo.isWindows && FlatNativeLibrary.isLoaded(); return SystemInfo.isWindows && FlatNativeLibrary.isLoaded( API_VERSION_WINDOWS );
} }
/** /**

View File

@@ -16,6 +16,7 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import java.awt.Dimension;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
@@ -23,11 +24,14 @@ import java.beans.PropertyChangeListener;
import java.util.Map; import java.util.Map;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.LookAndFeel;
import javax.swing.border.Border;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicPanelUI; import javax.swing.plaf.basic.BasicPanelUI;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
@@ -69,6 +73,8 @@ public class FlatPanelUI
super.installUI( c ); super.installUI( c );
c.addPropertyChangeListener( this ); c.addPropertyChangeListener( this );
if( c.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER ) != null )
FullWindowContentSupport.registerPlaceholder( c );
installStyle( (JPanel) c ); installStyle( (JPanel) c );
} }
@@ -78,10 +84,20 @@ public class FlatPanelUI
super.uninstallUI( c ); super.uninstallUI( c );
c.removePropertyChangeListener( this ); c.removePropertyChangeListener( this );
if( c.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER ) != null )
FullWindowContentSupport.unregisterPlaceholder( c );
oldStyleValues = null; 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 */ /** @since 2.0.1 */
@Override @Override
public void propertyChange( PropertyChangeEvent e ) { public void propertyChange( PropertyChangeEvent e ) {
@@ -96,7 +112,18 @@ public class FlatPanelUI
} else } else
installStyle( c ); installStyle( c );
c.revalidate(); c.revalidate();
c.repaint(); HiDPIUtils.repaint( c );
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; break;
} }
} }
@@ -135,31 +162,52 @@ public class FlatPanelUI
@Override @Override
public void update( Graphics g, JComponent c ) { public void update( Graphics g, JComponent c ) {
// fill background fillRoundedBackground( g, c, arc );
if( c.isOpaque() ) {
int width = c.getWidth();
int height = c.getHeight();
int arc = (this.arc >= 0)
? this.arc
: ((c.getBorder() instanceof FlatLineBorder)
? ((FlatLineBorder)c.getBorder()).getArc()
: 0);
// fill background with parent color to avoid garbage in rounded corners
if( arc > 0 )
FlatUIUtils.paintParentBackground( g, c );
g.setColor( c.getBackground() );
if( arc > 0 ) {
// fill rounded rectangle if having rounded corners
Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g );
FlatUIUtils.paintComponentBackground( (Graphics2D) g, 0, 0, width, height,
0, UIScale.scale( arc ) );
FlatUIUtils.resetRenderingHints( g, oldRenderingHints );
} else
g.fillRect( 0, 0, width, height );
}
paint( g, c ); paint( g, c );
} }
/** @since 3.5 */
public static void fillRoundedBackground( Graphics g, JComponent c, int arc ) {
if( arc < 0 ) {
Border border = c.getBorder();
arc = ((border instanceof FlatLineBorder)
? ((FlatLineBorder)border).getArc()
: 0);
}
// fill background
if( c.isOpaque() ) {
if( arc > 0 ) {
// fill background with parent color to avoid garbage in rounded corners
FlatUIUtils.paintParentBackground( g, c );
} else {
g.setColor( c.getBackground() );
g.fillRect( 0, 0, c.getWidth(), c.getHeight() );
}
}
// fill rounded rectangle if having rounded corners
if( arc > 0 ) {
g.setColor( c.getBackground() );
Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g );
FlatUIUtils.paintComponentBackground( (Graphics2D) g, 0, 0, c.getWidth(), c.getHeight(),
0, UIScale.scale( arc ) );
FlatUIUtils.resetRenderingHints( g, oldRenderingHints );
}
}
@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

@@ -43,6 +43,7 @@ import javax.swing.text.View;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.icons.FlatCapsLockIcon; import com.formdev.flatlaf.icons.FlatCapsLockIcon;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
/** /**
@@ -163,7 +164,7 @@ public class FlatPasswordFieldUI
} }
private void repaint( KeyEvent e ) { private void repaint( KeyEvent e ) {
if( e.getKeyCode() == KeyEvent.VK_CAPS_LOCK ) { if( e.getKeyCode() == KeyEvent.VK_CAPS_LOCK ) {
e.getComponent().repaint(); HiDPIUtils.repaint( e.getComponent() );
scrollCaretToVisible(); scrollCaretToVisible();
} }
} }
@@ -326,7 +327,7 @@ public class FlatPasswordFieldUI
if( visible != revealButton.isVisible() ) { if( visible != revealButton.isVisible() ) {
revealButton.setVisible( visible ); revealButton.setVisible( visible );
c.revalidate(); c.revalidate();
c.repaint(); HiDPIUtils.repaint( c );
if( !visible ) { if( !visible ) {
revealButton.setSelected( false ); revealButton.setSelected( false );

View File

@@ -17,6 +17,7 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import java.awt.AWTEvent; import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Color; import java.awt.Color;
import java.awt.Component; import java.awt.Component;
import java.awt.Container; import java.awt.Container;
@@ -41,6 +42,7 @@ import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType; import java.lang.invoke.MethodType;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JLayeredPane; import javax.swing.JLayeredPane;
import javax.swing.JPanel; import javax.swing.JPanel;
@@ -76,6 +78,8 @@ public class FlatPopupFactory
private MethodHandle java8getPopupMethod; private MethodHandle java8getPopupMethod;
private MethodHandle java9getPopupMethod; private MethodHandle java9getPopupMethod;
private final ArrayList<NonFlashingPopup> stillShownHeavyWeightPopups = new ArrayList<>();
@Override @Override
public Popup getPopup( Component owner, Component contents, int x, int y ) public Popup getPopup( Component owner, Component contents, int x, int y )
throws IllegalArgumentException throws IllegalArgumentException
@@ -88,15 +92,34 @@ public class FlatPopupFactory
fixLinuxWaylandJava21focusIssue( owner ); fixLinuxWaylandJava21focusIssue( owner );
// reuse a heavy weight popup window, which is still shown on screen,
// to avoid flicker when popup (e.g. tooltip) is moving while mouse is moved
for( NonFlashingPopup popup : stillShownHeavyWeightPopups ) {
if( popup.delegate != null &&
popup.owner == owner &&
(popup.contents == contents ||
(popup.contents instanceof JToolTip && contents instanceof JToolTip)) )
{
stillShownHeavyWeightPopups.remove( popup );
return reuseStillShownHeavyWeightPopups( popup, contents, x, y );
}
}
boolean forceHeavyWeight = isOptionEnabled( owner, contents, FlatClientProperties.POPUP_FORCE_HEAVY_WEIGHT, "Popup.forceHeavyWeight" ); 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 ) 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 ); return new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, forceHeavyWeight ), owner, contents );
// macOS and Linux adds drop shadow to heavy weight popups // macOS and Linux adds drop shadow to heavy weight popups
if( SystemInfo.isMacOS || SystemInfo.isLinux ) { if( SystemInfo.isMacOS || SystemInfo.isLinux ) {
NonFlashingPopup popup = new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, true ), contents ); NonFlashingPopup popup = new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, true ), owner, contents );
if( popup.popupWindow != null && SystemInfo.isMacOS && FlatNativeMacLibrary.isLoaded() ) if( popup.popupWindow != null && SystemInfo.isMacOS &&
// do not use rounded border on macOS 14.4+ because it may freeze the application
// and crash the macOS WindowServer process (reports vary from Finder restarts to OS restarts)
// https://github.com/apache/netbeans/issues/7560#issuecomment-2226439215
// https://github.com/apache/netbeans/issues/6647#issuecomment-2070124442
SystemInfo.osVersion < SystemInfo.toVersion( 14, 4, 0, 0 ) &&
FlatNativeMacLibrary.isLoaded() )
setupRoundedBorder( popup.popupWindow, owner, contents ); setupRoundedBorder( popup.popupWindow, owner, contents );
return popup; return popup;
} }
@@ -105,7 +128,7 @@ public class FlatPopupFactory
if( isWindows11BorderSupported() && if( isWindows11BorderSupported() &&
getBorderCornerRadius( owner, contents ) > 0 ) getBorderCornerRadius( owner, contents ) > 0 )
{ {
NonFlashingPopup popup = new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, true ), contents ); NonFlashingPopup popup = new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, true ), owner, contents );
if( popup.popupWindow != null ) if( popup.popupWindow != null )
setupRoundedBorder( popup.popupWindow, owner, contents ); setupRoundedBorder( popup.popupWindow, owner, contents );
return popup; return popup;
@@ -116,7 +139,10 @@ public class FlatPopupFactory
forceHeavyWeight = true; forceHeavyWeight = true;
// create drop shadow popup // create drop shadow popup
return new DropShadowPopup( getPopupForScreenOfOwner( owner, contents, x, y, forceHeavyWeight ), owner, contents ); Popup popupForScreenOfOwner = getPopupForScreenOfOwner( owner, contents, x, y, forceHeavyWeight );
return owner.getGraphicsConfiguration().isTranslucencyCapable()
? new DropShadowPopup( popupForScreenOfOwner, owner, contents )
: new NonFlashingPopup( popupForScreenOfOwner, owner, contents );
} }
/** /**
@@ -227,6 +253,24 @@ public class FlatPopupFactory
return UIManager.get( uiKey ); return UIManager.get( uiKey );
} }
/**
* Reuse a heavy weight popup window, which is still shown on screen,
* by updating window location and contents.
* This avoid flicker when popup (e.g. a tooltip) is moving while mouse is moved.
* E.g. overridden JComponent.getToolTipLocation(MouseEvent).
* See ToolTipManager.checkForTipChange(MouseEvent).
*/
private static NonFlashingPopup reuseStillShownHeavyWeightPopups(
NonFlashingPopup reusePopup, Component contents, int ownerX, int ownerY )
{
// clone popup because PopupFactory.getPopup() should not return old instance
NonFlashingPopup popup = reusePopup.cloneForReuse();
// update popup location, size and contents
popup.reset( contents, ownerX, ownerY );
return popup;
}
//---- tooltips ----------------------------------------------------------- //---- tooltips -----------------------------------------------------------
/** /**
@@ -490,18 +534,31 @@ public class FlatPopupFactory
//---- class NonFlashingPopup --------------------------------------------- //---- class NonFlashingPopup ---------------------------------------------
private static class NonFlashingPopup /**
* Fixes popup background flashing effect when using dark theme on light platform theme,
* where the light popup background is shown for a fraction of a second before
* the dark popup content is shown.
* This is fixed by setting popup background to content background.
* <p>
* Defers hiding of heavy weight popup window for an event cycle,
* which allows reusing popup window to avoid flicker when "moving" popup.
*/
private class NonFlashingPopup
extends Popup extends Popup
{ {
private Popup delegate; private Popup delegate;
Component owner;
private Component contents; private Component contents;
// heavy weight // heavy weight
protected Window popupWindow; Window popupWindow;
private Color oldPopupWindowBackground; private Color oldPopupWindowBackground;
NonFlashingPopup( Popup delegate, Component contents ) { private boolean disposed;
NonFlashingPopup( Popup delegate, Component owner, Component contents ) {
this.delegate = delegate; this.delegate = delegate;
this.owner = owner;
this.contents = contents; this.contents = contents;
popupWindow = SwingUtilities.windowForComponent( contents ); popupWindow = SwingUtilities.windowForComponent( contents );
@@ -515,8 +572,27 @@ public class FlatPopupFactory
} }
} }
private NonFlashingPopup( NonFlashingPopup reusePopup ) {
delegate = reusePopup.delegate;
owner = reusePopup.owner;
contents = reusePopup.contents;
popupWindow = reusePopup.popupWindow;
oldPopupWindowBackground = reusePopup.oldPopupWindowBackground;
}
NonFlashingPopup cloneForReuse() {
return new NonFlashingPopup( this );
}
@Override @Override
public void show() { public final void show() {
if( disposed )
return;
showImpl();
}
void showImpl() {
if( delegate != null ) { if( delegate != null ) {
showPopupAndFixLocation( delegate, popupWindow ); showPopupAndFixLocation( delegate, popupWindow );
@@ -540,13 +616,36 @@ public class FlatPopupFactory
} }
@Override @Override
public void hide() { public final void hide() {
if( disposed )
return;
disposed = true;
// immediately hide non-heavy weight popups or combobox popups
if( !(popupWindow instanceof JWindow) || contents instanceof BasicComboPopup ) {
hideImpl();
return;
}
// defer hiding of heavy weight popup window for an event cycle,
// which allows reusing popup window to avoid flicker when "moving" popup
((JWindow)popupWindow).getContentPane().removeAll();
stillShownHeavyWeightPopups.add( this );
EventQueue.invokeLater( () -> {
// hide popup if it was not reused
if( stillShownHeavyWeightPopups.remove( this ) )
hideImpl();
} );
}
void hideImpl() {
if( contents instanceof JComponent ) if( contents instanceof JComponent )
((JComponent)contents).putClientProperty( KEY_POPUP_USES_NATIVE_BORDER, null ); ((JComponent)contents).putClientProperty( KEY_POPUP_USES_NATIVE_BORDER, null );
if( delegate != null ) { if( delegate != null ) {
delegate.hide(); delegate.hide();
delegate = null; delegate = null;
owner = null;
contents = null; contents = null;
} }
@@ -557,6 +656,30 @@ public class FlatPopupFactory
popupWindow = null; popupWindow = null;
} }
} }
void reset( Component contents, int ownerX, int ownerY ) {
// update popup window location
popupWindow.setLocation( ownerX, ownerY );
// replace component in content pane
Container contentPane = ((JWindow)popupWindow).getContentPane();
contentPane.removeAll();
contentPane.add( contents, BorderLayout.CENTER );
popupWindow.invalidate();
popupWindow.validate();
popupWindow.pack();
// update client property on contents
if( this.contents != contents ) {
Object old = (this.contents instanceof JComponent)
? ((JComponent)this.contents).getClientProperty( KEY_POPUP_USES_NATIVE_BORDER )
: null;
if( contents instanceof JComponent )
((JComponent)contents).putClientProperty( KEY_POPUP_USES_NATIVE_BORDER, old );
this.contents = contents;
}
}
} }
//---- class DropShadowPopup ---------------------------------------------- //---- class DropShadowPopup ----------------------------------------------
@@ -564,8 +687,6 @@ public class FlatPopupFactory
private class DropShadowPopup private class DropShadowPopup
extends NonFlashingPopup extends NonFlashingPopup
{ {
private final Component owner;
// light weight // light weight
private JComponent lightComp; private JComponent lightComp;
private Border oldBorder; private Border oldBorder;
@@ -580,11 +701,11 @@ public class FlatPopupFactory
// heavy weight // heavy weight
private Popup dropShadowDelegate; private Popup dropShadowDelegate;
private Window dropShadowWindow; private Window dropShadowWindow;
private JPanel dropShadowPanel2;
private Color oldDropShadowWindowBackground; private Color oldDropShadowWindowBackground;
DropShadowPopup( Popup delegate, Component owner, Component contents ) { DropShadowPopup( Popup delegate, Component owner, Component contents ) {
super( delegate, contents ); super( delegate, owner, contents );
this.owner = owner;
Dimension size = contents.getPreferredSize(); Dimension size = contents.getPreferredSize();
if( size.width <= 0 || size.height <= 0 ) if( size.width <= 0 || size.height <= 0 )
@@ -600,24 +721,24 @@ public class FlatPopupFactory
// the drop shadow and is positioned behind the popup window. // the drop shadow and is positioned behind the popup window.
// create panel that paints the drop shadow // create panel that paints the drop shadow
JPanel dropShadowPanel = new JPanel(); dropShadowPanel2 = new JPanel();
dropShadowPanel.setBorder( createDropShadowBorder() ); dropShadowPanel2.setBorder( createDropShadowBorder() );
dropShadowPanel.setOpaque( false ); dropShadowPanel2.setOpaque( false );
// set preferred size of drop shadow panel // set preferred size of drop shadow panel
Dimension prefSize = popupWindow.getPreferredSize(); Dimension prefSize = popupWindow.getPreferredSize();
Insets insets = dropShadowPanel.getInsets(); Insets insets = dropShadowPanel2.getInsets();
dropShadowPanel.setPreferredSize( new Dimension( dropShadowPanel2.setPreferredSize( new Dimension(
prefSize.width + insets.left + insets.right, prefSize.width + insets.left + insets.right,
prefSize.height + insets.top + insets.bottom ) ); prefSize.height + insets.top + insets.bottom ) );
// create heavy weight popup for drop shadow // create heavy weight popup for drop shadow
int x = popupWindow.getX() - insets.left; int x = popupWindow.getX() - insets.left;
int y = popupWindow.getY() - insets.top; int y = popupWindow.getY() - insets.top;
dropShadowDelegate = getPopupForScreenOfOwner( owner, dropShadowPanel, x, y, true ); dropShadowDelegate = getPopupForScreenOfOwner( owner, dropShadowPanel2, x, y, true );
// make drop shadow popup window translucent // make drop shadow popup window translucent
dropShadowWindow = SwingUtilities.windowForComponent( dropShadowPanel ); dropShadowWindow = SwingUtilities.windowForComponent( dropShadowPanel2 );
if( dropShadowWindow != null ) { if( dropShadowWindow != null ) {
oldDropShadowWindowBackground = dropShadowWindow.getBackground(); oldDropShadowWindowBackground = dropShadowWindow.getBackground();
dropShadowWindow.setBackground( new Color( 0, true ) ); dropShadowWindow.setBackground( new Color( 0, true ) );
@@ -654,6 +775,23 @@ public class FlatPopupFactory
} }
} }
private DropShadowPopup( DropShadowPopup reusePopup ) {
super( reusePopup );
// not necessary to clone fields used for light/medium weight popups
// heavy weight
dropShadowDelegate = reusePopup.dropShadowDelegate;
dropShadowWindow = reusePopup.dropShadowWindow;
dropShadowPanel2 = reusePopup.dropShadowPanel2;
oldDropShadowWindowBackground = reusePopup.oldDropShadowWindowBackground;
}
@Override
NonFlashingPopup cloneForReuse() {
return new DropShadowPopup( this );
}
private Border createDropShadowBorder() { private Border createDropShadowBorder() {
return new FlatDropShadowBorder( return new FlatDropShadowBorder(
UIManager.getColor( "Popup.dropShadowColor" ), UIManager.getColor( "Popup.dropShadowColor" ),
@@ -662,14 +800,14 @@ public class FlatPopupFactory
} }
@Override @Override
public void show() { void showImpl() {
if( dropShadowDelegate != null ) if( dropShadowDelegate != null )
showPopupAndFixLocation( dropShadowDelegate, dropShadowWindow ); showPopupAndFixLocation( dropShadowDelegate, dropShadowWindow );
if( mediumWeightPanel != null ) if( mediumWeightPanel != null )
showMediumWeightDropShadow(); showMediumWeightDropShadow();
super.show(); super.showImpl();
// fix location of light weight popup in case it has left or top drop shadow // fix location of light weight popup in case it has left or top drop shadow
if( lightComp != null ) { if( lightComp != null ) {
@@ -680,10 +818,11 @@ public class FlatPopupFactory
} }
@Override @Override
public void hide() { void hideImpl() {
if( dropShadowDelegate != null ) { if( dropShadowDelegate != null ) {
dropShadowDelegate.hide(); dropShadowDelegate.hide();
dropShadowDelegate = null; dropShadowDelegate = null;
dropShadowPanel2 = null;
} }
if( mediumWeightPanel != null ) { if( mediumWeightPanel != null ) {
@@ -692,7 +831,7 @@ public class FlatPopupFactory
mediumWeightPanel = null; mediumWeightPanel = null;
} }
super.hide(); super.hideImpl();
if( dropShadowWindow != null ) { if( dropShadowWindow != null ) {
dropShadowWindow.setBackground( oldDropShadowWindowBackground ); dropShadowWindow.setBackground( oldDropShadowWindowBackground );
@@ -776,5 +915,25 @@ public class FlatPopupFactory
if( dropShadowPanel != null && mediumWeightPanel != null ) if( dropShadowPanel != null && mediumWeightPanel != null )
dropShadowPanel.setSize( FlatUIUtils.addInsets( mediumWeightPanel.getSize(), dropShadowPanel.getInsets() ) ); dropShadowPanel.setSize( FlatUIUtils.addInsets( mediumWeightPanel.getSize(), dropShadowPanel.getInsets() ) );
} }
@Override
void reset( Component contents, int ownerX, int ownerY ) {
super.reset( contents, ownerX, ownerY );
if( dropShadowWindow != null ) {
// set preferred size of drop shadow panel
Dimension prefSize = popupWindow.getPreferredSize();
Insets insets = dropShadowPanel2.getInsets();
int w = prefSize.width + insets.left + insets.right;
int h = prefSize.height + insets.top + insets.bottom;
dropShadowPanel2.setPreferredSize( new Dimension( w, h ) );
// update drop shadow popup window location and size
int x = popupWindow.getX() - insets.left;
int y = popupWindow.getY() - insets.top;
dropShadowWindow.setBounds( x, y, w, h );
dropShadowWindow.pack();
}
}
} }
} }

View File

@@ -18,6 +18,7 @@ package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.FlatClientProperties.*; import static com.formdev.flatlaf.FlatClientProperties.*;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics; import java.awt.FontMetrics;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
@@ -86,6 +87,17 @@ public class FlatProgressBarUI
installStyle(); installStyle();
} }
@Override
public void uninstallUI( JComponent c ) {
if( !EventQueue.isDispatchThread() && progressBar.isIndeterminate() ) {
LoggingFacade.INSTANCE.logSevere(
"FlatLaf: Uninstalling indeterminate progress bar UI not on AWT thread may throw NPE in FlatProgressBarUI.paint(). Use SwingUtilities.invokeLater().",
new IllegalStateException() );
}
super.uninstallUI( c );
}
@Override @Override
protected void installDefaults() { protected void installDefaults() {
super.installDefaults(); super.installDefaults();
@@ -110,17 +122,25 @@ public class FlatProgressBarUI
propertyChangeListener = e -> { propertyChangeListener = e -> {
switch( e.getPropertyName() ) { switch( e.getPropertyName() ) {
case "indeterminate":
if( !EventQueue.isDispatchThread() && !progressBar.isIndeterminate() ) {
LoggingFacade.INSTANCE.logSevere(
"FlatLaf: Using JProgressBar.setIndeterminate(false) not on AWT thread may throw NPE in FlatProgressBarUI.paint(). Use SwingUtilities.invokeLater().",
new IllegalStateException() );
}
break;
case PROGRESS_BAR_LARGE_HEIGHT: case PROGRESS_BAR_LARGE_HEIGHT:
case PROGRESS_BAR_SQUARE: case PROGRESS_BAR_SQUARE:
progressBar.revalidate(); progressBar.revalidate();
progressBar.repaint(); HiDPIUtils.repaint( progressBar );
break; break;
case STYLE: case STYLE:
case STYLE_CLASS: case STYLE_CLASS:
installStyle(); installStyle();
progressBar.revalidate(); progressBar.revalidate();
progressBar.repaint(); HiDPIUtils.repaint( progressBar );
break; break;
} }
}; };
@@ -274,6 +294,6 @@ public class FlatProgressBarUI
// Only solution is to repaint whole progress bar. // Only solution is to repaint whole progress bar.
double systemScaleFactor = UIScale.getSystemScaleFactor( progressBar.getGraphicsConfiguration() ); double systemScaleFactor = UIScale.getSystemScaleFactor( progressBar.getGraphicsConfiguration() );
if( (int) systemScaleFactor != systemScaleFactor ) if( (int) systemScaleFactor != systemScaleFactor )
progressBar.repaint(); HiDPIUtils.repaint( progressBar );
} }
} }

View File

@@ -23,6 +23,7 @@ import java.lang.invoke.MethodHandles;
import java.util.Map; import java.util.Map;
import javax.swing.Icon; import javax.swing.Icon;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicMenuItemUI; import javax.swing.plaf.basic.BasicMenuItemUI;
@@ -102,13 +103,23 @@ public class FlatRadioButtonMenuItemUI
oldStyleValues = null; oldStyleValues = null;
} }
@Override
protected void installComponents( JMenuItem menuItem ) {
super.installComponents( menuItem );
// update HTML renderer if necessary
FlatHTML.updateRendererCSSFontBaseSize( menuItem );
}
protected FlatMenuItemRenderer createRenderer() { protected FlatMenuItemRenderer createRenderer() {
return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter ); return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter );
} }
@Override @Override
protected PropertyChangeListener createPropertyChangeListener( JComponent c ) { protected PropertyChangeListener createPropertyChangeListener( JComponent c ) {
return FlatStylingSupport.createPropertyChangeListener( c, this::installStyle, super.createPropertyChangeListener( c ) ); return FlatHTML.createPropertyChangeListener(
FlatStylingSupport.createPropertyChangeListener( c, this::installStyle,
super.createPropertyChangeListener( c ) ) );
} }
/** @since 2 */ /** @since 2 */

View File

@@ -40,12 +40,14 @@ import javax.swing.SwingUtilities;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicButtonListener; import javax.swing.plaf.basic.BasicButtonListener;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.plaf.basic.BasicRadioButtonUI; import javax.swing.plaf.basic.BasicRadioButtonUI;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.icons.FlatCheckBoxIcon; import com.formdev.flatlaf.icons.FlatCheckBoxIcon;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException; import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
@@ -159,6 +161,10 @@ public class FlatRadioButtonUI
/** @since 2 */ /** @since 2 */
protected void propertyChange( AbstractButton b, PropertyChangeEvent e ) { protected void propertyChange( AbstractButton b, PropertyChangeEvent e ) {
switch( e.getPropertyName() ) { switch( e.getPropertyName() ) {
case BasicHTML.propertyKey:
FlatHTML.updateRendererCSSFontBaseSize( b );
break;
case FlatClientProperties.STYLE: case FlatClientProperties.STYLE:
case FlatClientProperties.STYLE_CLASS: case FlatClientProperties.STYLE_CLASS:
if( shared && FlatStylingSupport.hasStyleProperty( b ) ) { if( shared && FlatStylingSupport.hasStyleProperty( b ) ) {
@@ -168,7 +174,7 @@ public class FlatRadioButtonUI
} else } else
installStyle( b ); installStyle( b );
b.revalidate(); b.revalidate();
b.repaint(); HiDPIUtils.repaint( b );
break; break;
} }
} }

View File

@@ -28,8 +28,6 @@ import java.awt.Insets;
import java.awt.LayoutManager; import java.awt.LayoutManager;
import java.awt.LayoutManager2; import java.awt.LayoutManager2;
import java.awt.Window; import java.awt.Window;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener; import java.awt.event.ComponentListener;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
@@ -41,7 +39,6 @@ import javax.swing.JLayeredPane;
import javax.swing.JMenuBar; import javax.swing.JMenuBar;
import javax.swing.JRootPane; import javax.swing.JRootPane;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.border.Border; import javax.swing.border.Border;
import javax.swing.plaf.BorderUIResource; import javax.swing.plaf.BorderUIResource;
@@ -50,6 +47,7 @@ import javax.swing.plaf.RootPaneUI;
import javax.swing.plaf.UIResource; import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicRootPaneUI; import javax.swing.plaf.basic.BasicRootPaneUI;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.util.HiDPIUtils; import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
@@ -87,8 +85,8 @@ public class FlatRootPaneUI
private Object nativeWindowBorderData; private Object nativeWindowBorderData;
private LayoutManager oldLayout; private LayoutManager oldLayout;
private PropertyChangeListener ancestorListener; private ComponentListener macFullWindowContentListener;
private ComponentListener componentListener; private PropertyChangeListener macWindowBackgroundListener;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
return new FlatRootPaneUI(); return new FlatRootPaneUI();
@@ -106,6 +104,7 @@ public class FlatRootPaneUI
installBorder(); installBorder();
installNativeWindowBorder(); installNativeWindowBorder();
macInstallFullWindowContentSupport();
} }
protected void installBorder() { protected void installBorder() {
@@ -122,6 +121,7 @@ public class FlatRootPaneUI
uninstallNativeWindowBorder(); uninstallNativeWindowBorder();
uninstallClientDecorations(); uninstallClientDecorations();
macUninstallFullWindowContentSupport();
rootPane = null; rootPane = null;
} }
@@ -155,6 +155,8 @@ public class FlatRootPaneUI
if( background == null || background instanceof UIResource ) if( background == null || background instanceof UIResource )
parent.setBackground( UIManager.getColor( "control" ) ); parent.setBackground( UIManager.getColor( "control" ) );
} }
macClearBackgroundForTranslucentWindow( c );
} }
@Override @Override
@@ -174,55 +176,20 @@ public class FlatRootPaneUI
protected void installListeners( JRootPane root ) { protected void installListeners( JRootPane root ) {
super.installListeners( root ); super.installListeners( root );
if( SystemInfo.isJava_9_orLater ) { if( SystemInfo.isMacFullWindowContentSupported )
// On HiDPI screens, where scaling is used, there may be white lines on the macFullWindowContentListener = FullWindowContentSupport.macInstallListeners( root );
// bottom and on the right side of the window when it is initially shown. macInstallWindowBackgroundListener( root );
// 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 );
}
} }
@Override @Override
protected void uninstallListeners( JRootPane root ) { protected void uninstallListeners( JRootPane root ) {
super.uninstallListeners( root ); super.uninstallListeners( root );
if( SystemInfo.isJava_9_orLater ) { if( SystemInfo.isMacFullWindowContentSupported ) {
if( componentListener != null ) { FullWindowContentSupport.macUninstallListeners( root, macFullWindowContentListener );
Window window = SwingUtilities.windowForComponent( root ); macFullWindowContentListener = null;
if( window != null )
window.removeComponentListener( componentListener );
componentListener = null;
}
root.removePropertyChangeListener( "ancestor", ancestorListener );
ancestorListener = null;
} }
macUninstallWindowBackgroundListener( root );
} }
/** @since 1.1.2 */ /** @since 1.1.2 */
@@ -302,19 +269,141 @@ public class FlatRootPaneUI
// layer title pane under frame content layer to allow placing menu bar over title pane // 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; 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;
private final static Integer WINDOW_TOP_BORDER_LAYER = Integer.MAX_VALUE;
// 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 ) { protected void setTitlePane( FlatTitlePane newTitlePane ) {
JLayeredPane layeredPane = rootPane.getLayeredPane(); JLayeredPane layeredPane = rootPane.getLayeredPane();
if( titlePane != null ) if( titlePane != null ) {
layeredPane.remove( titlePane ); layeredPane.remove( titlePane );
layeredPane.remove( titlePane.mouseLayer );
if( titlePane.windowTopBorderLayer != null )
layeredPane.remove( titlePane.windowTopBorderLayer );
}
if( newTitlePane != null ) if( newTitlePane != null ) {
layeredPane.add( newTitlePane, TITLE_PANE_LAYER ); layeredPane.add( newTitlePane, getLayerForTitlePane() );
layeredPane.add( newTitlePane.mouseLayer, TITLE_PANE_MOUSE_LAYER );
if( newTitlePane.windowTopBorderLayer != null && newTitlePane.isWindowTopBorderNeeded() && isFullWindowContent( rootPane ) )
layeredPane.add( newTitlePane.windowTopBorderLayer, WINDOW_TOP_BORDER_LAYER );
}
titlePane = newTitlePane; 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 @Override
public void propertyChange( PropertyChangeEvent e ) { public void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e ); super.propertyChange( e );
@@ -359,6 +448,28 @@ public class FlatRootPaneUI
titlePane.titleBarColorsChanged(); titlePane.titleBarColorsChanged();
break; break;
case FlatClientProperties.FULL_WINDOW_CONTENT:
if( titlePane != null ) {
rootPane.getLayeredPane().setLayer( titlePane, getLayerForTitlePane() );
if( titlePane.windowTopBorderLayer != null ) {
JLayeredPane layeredPane = rootPane.getLayeredPane();
if( titlePane.isWindowTopBorderNeeded() && isFullWindowContent( rootPane ) )
layeredPane.add( titlePane.windowTopBorderLayer, WINDOW_TOP_BORDER_LAYER );
else
layeredPane.remove( titlePane.windowTopBorderLayer );
}
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: case FlatClientProperties.GLASS_PANE_FULL_HEIGHT:
rootPane.revalidate(); rootPane.revalidate();
break; break;
@@ -367,14 +478,43 @@ public class FlatRootPaneUI
if( rootPane.isDisplayable() ) if( rootPane.isDisplayable() )
throw new IllegalComponentStateException( "The client property 'Window.style' must be set before the window becomes displayable." ); throw new IllegalComponentStateException( "The client property 'Window.style' must be set before the window becomes displayable." );
break; 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 ) { protected static boolean isMenuBarEmbedded( JRootPane rootPane ) {
RootPaneUI ui = rootPane.getUI(); FlatTitlePane titlePane = getTitlePane( rootPane );
return ui instanceof FlatRootPaneUI && return titlePane != null && titlePane.isMenuBarEmbedded();
((FlatRootPaneUI)ui).titlePane != null &&
((FlatRootPaneUI)ui).titlePane.isMenuBarEmbedded();
} }
/** @since 2.4 */ /** @since 2.4 */
@@ -410,23 +550,21 @@ public class FlatRootPaneUI
private Dimension computeLayoutSize( Container parent, Function<Component, Dimension> getSizeFunc ) { private Dimension computeLayoutSize( Container parent, Function<Component, Dimension> getSizeFunc ) {
JRootPane rootPane = (JRootPane) parent; JRootPane rootPane = (JRootPane) parent;
Dimension titlePaneSize = (titlePane != null)
? getSizeFunc.apply( titlePane )
: new Dimension();
Dimension contentSize = (rootPane.getContentPane() != null) Dimension contentSize = (rootPane.getContentPane() != null)
? getSizeFunc.apply( rootPane.getContentPane() ) ? 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 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() ) { if( titlePane == null || !titlePane.isMenuBarEmbedded() ) {
JMenuBar menuBar = rootPane.getJMenuBar(); JMenuBar menuBar = rootPane.getJMenuBar();
Dimension menuBarSize = (menuBar != null && menuBar.isVisible()) if( menuBar != null && menuBar.isVisible() ) {
? getSizeFunc.apply( menuBar ) Dimension menuBarSize = getSizeFunc.apply( menuBar );
: new Dimension(); width = Math.max( width, menuBarSize.width );
height += menuBarSize.height;
width = Math.max( width, menuBarSize.width ); }
height += menuBarSize.height;
} }
Insets insets = rootPane.getInsets(); Insets insets = rootPane.getInsets();
@@ -451,12 +589,29 @@ public class FlatRootPaneUI
if( rootPane.getLayeredPane() != null ) if( rootPane.getLayeredPane() != null )
rootPane.getLayeredPane().setBounds( x, y, width, height ); rootPane.getLayeredPane().setBounds( x, y, width, height );
// title pane // title pane (is a child of layered pane)
int nextY = 0; int nextY = 0;
if( titlePane != null ) { if( titlePane != null ) {
int prefHeight = !isFullScreen ? titlePane.getPreferredSize().height : 0; int prefHeight = !isFullScreen ? titlePane.getPreferredSize().height : 0;
titlePane.setBounds( 0, 0, width, prefHeight ); boolean isFullWindowContent = isFullWindowContent( rootPane );
nextY += prefHeight; 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( titlePane.windowTopBorderLayer != null ) {
boolean show = isFullWindowContent && !titlePane.isWindowMaximized() && !isFullScreen;
if( show )
titlePane.windowTopBorderLayer.setBounds( 0, 0, width, 1 );
titlePane.windowTopBorderLayer.setVisible( show );
}
if( !isFullWindowContent )
nextY += prefHeight;
} }
// glass pane // glass pane
@@ -467,7 +622,7 @@ public class FlatRootPaneUI
rootPane.getGlassPane().setBounds( x, y + offset, width, height - offset ); rootPane.getGlassPane().setBounds( x, y + offset, width, height - offset );
} }
// menu bar // menu bar (is a child of layered pane)
JMenuBar menuBar = rootPane.getJMenuBar(); JMenuBar menuBar = rootPane.getJMenuBar();
if( menuBar != null && menuBar.isVisible() ) { if( menuBar != null && menuBar.isVisible() ) {
boolean embedded = !isFullScreen && titlePane != null && titlePane.isMenuBarEmbedded(); boolean embedded = !isFullScreen && titlePane != null && titlePane.isMenuBarEmbedded();
@@ -475,13 +630,23 @@ public class FlatRootPaneUI
titlePane.validate(); titlePane.validate();
menuBar.setBounds( titlePane.getMenuBarBounds() ); menuBar.setBounds( titlePane.getMenuBarBounds() );
} else { } 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(); Dimension prefSize = menuBar.getPreferredSize();
menuBar.setBounds( 0, nextY, width, prefSize.height ); menuBar.setBounds( mx, nextY, mw, prefSize.height );
nextY += prefSize.height; nextY += prefSize.height;
} }
} }
// content pane // content pane (is a child of layered pane)
Container contentPane = rootPane.getContentPane(); Container contentPane = rootPane.getContentPane();
if( contentPane != null ) if( contentPane != null )
contentPane.setBounds( 0, nextY, width, Math.max( height - nextY, 0 ) ); contentPane.setBounds( 0, nextY, width, Math.max( height - nextY, 0 ) );

View File

@@ -43,6 +43,7 @@ import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
@@ -212,14 +213,14 @@ public class FlatScrollBarUI
switch( e.getPropertyName() ) { switch( e.getPropertyName() ) {
case FlatClientProperties.SCROLL_BAR_SHOW_BUTTONS: case FlatClientProperties.SCROLL_BAR_SHOW_BUTTONS:
scrollbar.revalidate(); scrollbar.revalidate();
scrollbar.repaint(); HiDPIUtils.repaint( scrollbar );
break; break;
case FlatClientProperties.STYLE: case FlatClientProperties.STYLE:
case FlatClientProperties.STYLE_CLASS: case FlatClientProperties.STYLE_CLASS:
installStyle(); installStyle();
scrollbar.revalidate(); scrollbar.revalidate();
scrollbar.repaint(); HiDPIUtils.repaint( scrollbar );
break; break;
case "componentOrientation": case "componentOrientation":
@@ -492,7 +493,7 @@ public class FlatScrollBarUI
private void repaint() { private void repaint() {
if( scrollbar.isEnabled() ) if( scrollbar.isEnabled() )
scrollbar.repaint(); HiDPIUtils.repaint( scrollbar );
} }
} }

View File

@@ -55,6 +55,7 @@ import javax.swing.plaf.basic.BasicScrollPaneUI;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
@@ -297,11 +298,11 @@ public class FlatScrollPaneUI
JScrollBar hsb = scrollpane.getHorizontalScrollBar(); JScrollBar hsb = scrollpane.getHorizontalScrollBar();
if( vsb != null ) { if( vsb != null ) {
vsb.revalidate(); vsb.revalidate();
vsb.repaint(); HiDPIUtils.repaint( vsb );
} }
if( hsb != null ) { if( hsb != null ) {
hsb.revalidate(); hsb.revalidate();
hsb.repaint(); HiDPIUtils.repaint( hsb );
} }
break; break;
@@ -321,14 +322,14 @@ public class FlatScrollPaneUI
break; break;
case FlatClientProperties.OUTLINE: case FlatClientProperties.OUTLINE:
scrollpane.repaint(); HiDPIUtils.repaint( scrollpane );
break; break;
case FlatClientProperties.STYLE: case FlatClientProperties.STYLE:
case FlatClientProperties.STYLE_CLASS: case FlatClientProperties.STYLE_CLASS:
installStyle(); installStyle();
scrollpane.revalidate(); scrollpane.revalidate();
scrollpane.repaint(); HiDPIUtils.repaint( scrollpane );
break; break;
case "border": case "border":
@@ -339,7 +340,7 @@ public class FlatScrollPaneUI
borderShared = null; borderShared = null;
installStyle(); installStyle();
scrollpane.revalidate(); scrollpane.revalidate();
scrollpane.repaint(); HiDPIUtils.repaint( scrollpane );
} }
break; break;
} }
@@ -538,14 +539,14 @@ public class FlatScrollPaneUI
public void focusGained( FocusEvent e ) { public void focusGained( FocusEvent e ) {
// necessary to update focus border // necessary to update focus border
if( scrollpane.getBorder() instanceof FlatBorder ) if( scrollpane.getBorder() instanceof FlatBorder )
scrollpane.repaint(); HiDPIUtils.repaint( scrollpane );
} }
@Override @Override
public void focusLost( FocusEvent e ) { public void focusLost( FocusEvent e ) {
// necessary to update focus border // necessary to update focus border
if( scrollpane.getBorder() instanceof FlatBorder ) if( scrollpane.getBorder() instanceof FlatBorder )
scrollpane.repaint(); HiDPIUtils.repaint( scrollpane );
} }
} }

View File

@@ -32,6 +32,7 @@ import javax.swing.plaf.basic.BasicSeparatorUI;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
/** /**
@@ -134,7 +135,7 @@ public class FlatSeparatorUI
} else } else
installStyle( s ); installStyle( s );
s.revalidate(); s.revalidate();
s.repaint(); HiDPIUtils.repaint( s );
break; break;
} }
} }

View File

@@ -25,6 +25,8 @@ import java.awt.Graphics2D;
import java.awt.Insets; import java.awt.Insets;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.Shape; import java.awt.Shape;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D; import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D; import java.awt.geom.Path2D;
@@ -191,6 +193,23 @@ public class FlatSliderUI
return new FlatTrackListener(); return new FlatTrackListener();
} }
@Override
protected FocusListener createFocusListener( JSlider slider ) {
return new BasicSliderUI.FocusHandler() {
@Override
public void focusGained( FocusEvent e ) {
super.focusGained( e );
HiDPIUtils.repaint( slider );
}
@Override
public void focusLost( FocusEvent e ) {
super.focusLost( e );
HiDPIUtils.repaint( slider );
}
};
}
@Override @Override
protected PropertyChangeListener createPropertyChangeListener( JSlider slider ) { protected PropertyChangeListener createPropertyChangeListener( JSlider slider ) {
return FlatStylingSupport.createPropertyChangeListener( slider, this::installStyle, return FlatStylingSupport.createPropertyChangeListener( slider, this::installStyle,
@@ -422,7 +441,7 @@ debug*/
Color thumbColor, Color thumbBorderColor, Color focusedColor, float thumbBorderWidth, int focusWidth ) Color thumbColor, Color thumbBorderColor, Color focusedColor, float thumbBorderWidth, int focusWidth )
{ {
double systemScaleFactor = UIScale.getSystemScaleFactor( (Graphics2D) g ); double systemScaleFactor = UIScale.getSystemScaleFactor( (Graphics2D) g );
if( systemScaleFactor != 1 && systemScaleFactor != 2 ) { if( systemScaleFactor != (int) systemScaleFactor ) {
// paint at scale 1x to avoid clipping on right and bottom edges at 125%, 150% or 175% // paint at scale 1x to avoid clipping on right and bottom edges at 125%, 150% or 175%
HiDPIUtils.paintAtScale1x( (Graphics2D) g, thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height, HiDPIUtils.paintAtScale1x( (Graphics2D) g, thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height,
(g2d, x2, y2, width2, height2, scaleFactor) -> { (g2d, x2, y2, width2, height2, scaleFactor) -> {
@@ -579,15 +598,15 @@ debug*/
@Override @Override
public void setThumbLocation( int x, int y ) { public void setThumbLocation( int x, int y ) {
// set new thumb location and compute union of old and new thumb bounds
Rectangle r = new Rectangle( thumbRect );
thumbRect.setLocation( x, y );
SwingUtilities.computeUnion( thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height, r );
if( !isRoundThumb() ) { if( !isRoundThumb() ) {
// the needle of the directional thumb is painted outside of thumbRect // the needle of the directional thumb is painted outside of thumbRect
// --> must increase repaint rectangle // --> must increase repaint rectangle
// set new thumb location and compute union of old and new thumb bounds
Rectangle r = new Rectangle( thumbRect );
thumbRect.setLocation( x, y );
SwingUtilities.computeUnion( thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height, r );
// increase union rectangle for repaint // increase union rectangle for repaint
int extra = (int) Math.ceil( UIScale.scale( focusWidth ) * 0.4142f ); int extra = (int) Math.ceil( UIScale.scale( focusWidth ) * 0.4142f );
if( slider.getOrientation() == JSlider.HORIZONTAL ) if( slider.getOrientation() == JSlider.HORIZONTAL )
@@ -597,10 +616,9 @@ debug*/
if( !slider.getComponentOrientation().isLeftToRight() ) if( !slider.getComponentOrientation().isLeftToRight() )
r.x -= extra; r.x -= extra;
} }
}
slider.repaint( r ); HiDPIUtils.repaint( slider, r );
} else
super.setThumbLocation( x, y );
} }
//---- class FlatTrackListener -------------------------------------------- //---- class FlatTrackListener --------------------------------------------
@@ -688,21 +706,21 @@ debug*/
!UIManager.getBoolean( "Slider.snapToTicksOnReleased" ) ) !UIManager.getBoolean( "Slider.snapToTicksOnReleased" ) )
{ {
calculateThumbLocation(); calculateThumbLocation();
slider.repaint(); HiDPIUtils.repaint( slider );
} }
} }
protected void setThumbHover( boolean hover ) { protected void setThumbHover( boolean hover ) {
if( hover != thumbHover ) { if( hover != thumbHover ) {
thumbHover = hover; thumbHover = hover;
slider.repaint( thumbRect ); HiDPIUtils.repaint( slider, thumbRect );
} }
} }
protected void setThumbPressed( boolean pressed ) { protected void setThumbPressed( boolean pressed ) {
if( pressed != thumbPressed ) { if( pressed != thumbPressed ) {
thumbPressed = pressed; thumbPressed = pressed;
slider.repaint( thumbRect ); HiDPIUtils.repaint( slider, thumbRect );
} }
} }

View File

@@ -47,6 +47,7 @@ import javax.swing.plaf.basic.BasicSpinnerUI;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
/** /**
@@ -586,7 +587,7 @@ public class FlatSpinnerUI
@Override @Override
public void focusGained( FocusEvent e ) { public void focusGained( FocusEvent e ) {
// necessary to update focus border // necessary to update focus border
spinner.repaint(); HiDPIUtils.repaint( spinner );
// if spinner gained focus, transfer it to the editor text field // if spinner gained focus, transfer it to the editor text field
if( e.getComponent() == spinner ) { if( e.getComponent() == spinner ) {
@@ -599,7 +600,7 @@ public class FlatSpinnerUI
@Override @Override
public void focusLost( FocusEvent e ) { public void focusLost( FocusEvent e ) {
// necessary to update focus border // necessary to update focus border
spinner.repaint(); HiDPIUtils.repaint( spinner );
} }
//---- interface PropertyChangeListener ---- //---- interface PropertyChangeListener ----
@@ -614,7 +615,7 @@ public class FlatSpinnerUI
case FlatClientProperties.COMPONENT_ROUND_RECT: case FlatClientProperties.COMPONENT_ROUND_RECT:
case FlatClientProperties.OUTLINE: case FlatClientProperties.OUTLINE:
spinner.repaint(); HiDPIUtils.repaint( spinner );
break; break;
case FlatClientProperties.MINIMUM_WIDTH: case FlatClientProperties.MINIMUM_WIDTH:
@@ -625,7 +626,7 @@ public class FlatSpinnerUI
case FlatClientProperties.STYLE_CLASS: case FlatClientProperties.STYLE_CLASS:
installStyle(); installStyle();
spinner.revalidate(); spinner.revalidate();
spinner.repaint(); HiDPIUtils.repaint( spinner );
break; break;
} }
} }

View File

@@ -84,7 +84,7 @@ import com.formdev.flatlaf.util.UIScale;
*/ */
public class FlatSplitPaneUI public class FlatSplitPaneUI
extends BasicSplitPaneUI extends BasicSplitPaneUI
implements StyleableUI implements StyleableUI, FlatTitlePane.TitleBarCaptionHitTest
{ {
@Styleable protected String arrowType; @Styleable protected String arrowType;
/** @since 3.3 */ @Styleable protected Color draggingColor; /** @since 3.3 */ @Styleable protected Color draggingColor;
@@ -227,6 +227,15 @@ public class FlatSplitPaneUI
((FlatSplitPaneDivider)divider).paintStyle( g, x, y, width, height ); ((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 ----------------------------------------- //---- class FlatSplitPaneDivider -----------------------------------------
protected class FlatSplitPaneDivider protected class FlatSplitPaneDivider
@@ -292,6 +301,10 @@ public class FlatSplitPaneUI
// necessary to show/hide one-touch buttons on expand/collapse // necessary to show/hide one-touch buttons on expand/collapse
doLayout(); doLayout();
break; break;
case FlatClientProperties.SPLIT_PANE_EXPANDABLE_SIDE:
revalidate();
break;
} }
} }

View File

@@ -40,6 +40,7 @@ import javax.swing.UIManager;
import javax.swing.border.Border; import javax.swing.border.Border;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.StringUtils; import com.formdev.flatlaf.util.StringUtils;
import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.SystemInfo;
@@ -709,7 +710,7 @@ public class FlatStylingSupport
case FlatClientProperties.STYLE_CLASS: case FlatClientProperties.STYLE_CLASS:
installStyle.run(); installStyle.run();
c.revalidate(); c.revalidate();
c.repaint(); HiDPIUtils.repaint( c );
break; break;
} }
}; };

View File

@@ -98,6 +98,7 @@ import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException; import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.Animator; import com.formdev.flatlaf.util.Animator;
import com.formdev.flatlaf.util.CubicBezierEasing; import com.formdev.flatlaf.util.CubicBezierEasing;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.JavaCompatibility; import com.formdev.flatlaf.util.JavaCompatibility;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.StringUtils; import com.formdev.flatlaf.util.StringUtils;
@@ -182,7 +183,7 @@ import com.formdev.flatlaf.util.UIScale;
*/ */
public class FlatTabbedPaneUI public class FlatTabbedPaneUI
extends BasicTabbedPaneUI extends BasicTabbedPaneUI
implements StyleableUI implements StyleableUI, FlatTitlePane.TitleBarCaptionHitTest
{ {
// tab type // tab type
/** @since 2 */ protected static final int TAB_TYPE_UNDERLINED = 0; /** @since 2 */ protected static final int TAB_TYPE_UNDERLINED = 0;
@@ -895,7 +896,7 @@ public class FlatTabbedPaneUI
} }
} }
tabPane.repaint( r ); HiDPIUtils.repaint( tabPane, r );
} }
private boolean inCalculateEqual; private boolean inCalculateEqual;
@@ -1684,7 +1685,7 @@ debug*/
w - (ci.left / 100f) - (ci.right / 100f), h - (ci.top / 100f) - (ci.bottom / 100f) ), false ); w - (ci.left / 100f) - (ci.right / 100f), h - (ci.top / 100f) - (ci.bottom / 100f) ), false );
// add gap for selected tab to path // add gap for selected tab to path
if( getTabType() == TAB_TYPE_CARD ) { if( getTabType() == TAB_TYPE_CARD && selectedIndex >= 0 ) {
float csh = scale( (float) contentSeparatorHeight ); float csh = scale( (float) contentSeparatorHeight );
Rectangle tabRect = getTabBounds( tabPane, selectedIndex ); Rectangle tabRect = getTabBounds( tabPane, selectedIndex );
@@ -2300,6 +2301,17 @@ debug*/
return (rects[last].y + rects[last].height) - rects[0].y; return (rects[last].y + rects[last].height) - rects[0].y;
} }
//---- interface FlatTitlePane.TitleBarCaptionHitTest ----
/** @since 3.4 */
@Override
public Boolean isTitleBarCaptionAt( int x, int y ) {
if( tabForCoordinate( tabPane, x, y ) >= 0 )
return false;
return null; // check children
}
//---- class TabCloseButton ----------------------------------------------- //---- class TabCloseButton -----------------------------------------------
private static class TabCloseButton private static class TabCloseButton
@@ -2570,19 +2582,19 @@ debug*/
@Override @Override
public void popupMenuWillBecomeVisible( PopupMenuEvent e ) { public void popupMenuWillBecomeVisible( PopupMenuEvent e ) {
popupVisible = true; popupVisible = true;
repaint(); HiDPIUtils.repaint( this );
} }
@Override @Override
public void popupMenuWillBecomeInvisible( PopupMenuEvent e ) { public void popupMenuWillBecomeInvisible( PopupMenuEvent e ) {
popupVisible = false; popupVisible = false;
repaint(); HiDPIUtils.repaint( this );
} }
@Override @Override
public void popupMenuCanceled( PopupMenuEvent e ) { public void popupMenuCanceled( PopupMenuEvent e ) {
popupVisible = false; popupVisible = false;
repaint(); HiDPIUtils.repaint( this );
} }
} }
@@ -3091,7 +3103,7 @@ debug*/
case TABBED_PANE_SHOW_TAB_SEPARATORS: case TABBED_PANE_SHOW_TAB_SEPARATORS:
case TABBED_PANE_TAB_TYPE: case TABBED_PANE_TAB_TYPE:
tabPane.repaint(); HiDPIUtils.repaint( tabPane );
break; break;
case TABBED_PANE_SHOW_CONTENT_SEPARATOR: case TABBED_PANE_SHOW_CONTENT_SEPARATOR:
@@ -3114,14 +3126,14 @@ debug*/
case TABBED_PANE_TAB_ICON_PLACEMENT: case TABBED_PANE_TAB_ICON_PLACEMENT:
case TABBED_PANE_TAB_CLOSABLE: case TABBED_PANE_TAB_CLOSABLE:
tabPane.revalidate(); tabPane.revalidate();
tabPane.repaint(); HiDPIUtils.repaint( tabPane );
break; break;
case TABBED_PANE_LEADING_COMPONENT: case TABBED_PANE_LEADING_COMPONENT:
uninstallLeadingComponent(); uninstallLeadingComponent();
installLeadingComponent(); installLeadingComponent();
tabPane.revalidate(); tabPane.revalidate();
tabPane.repaint(); HiDPIUtils.repaint( tabPane );
ensureSelectedTabIsVisibleLater(); ensureSelectedTabIsVisibleLater();
break; break;
@@ -3129,7 +3141,7 @@ debug*/
uninstallTrailingComponent(); uninstallTrailingComponent();
installTrailingComponent(); installTrailingComponent();
tabPane.revalidate(); tabPane.revalidate();
tabPane.repaint(); HiDPIUtils.repaint( tabPane );
ensureSelectedTabIsVisibleLater(); ensureSelectedTabIsVisibleLater();
break; break;
@@ -3137,7 +3149,7 @@ debug*/
case STYLE_CLASS: case STYLE_CLASS:
installStyle(); installStyle();
tabPane.revalidate(); tabPane.revalidate();
tabPane.repaint(); HiDPIUtils.repaint( tabPane );
break; break;
} }
} }
@@ -3161,7 +3173,7 @@ debug*/
case TABBED_PANE_TAB_ALIGNMENT: case TABBED_PANE_TAB_ALIGNMENT:
case TABBED_PANE_TAB_CLOSABLE: case TABBED_PANE_TAB_CLOSABLE:
tabPane.revalidate(); tabPane.revalidate();
tabPane.repaint(); HiDPIUtils.repaint( tabPane );
break; break;
} }
} }
@@ -3756,9 +3768,20 @@ debug*/
boolean hideDisabledScrollButtons = (scrollButtonsPolicy == AS_NEEDED_SINGLE && scrollButtonsPlacement == BOTH); boolean hideDisabledScrollButtons = (scrollButtonsPolicy == AS_NEEDED_SINGLE && scrollButtonsPlacement == BOTH);
boolean trailingScrollButtons = (scrollButtonsPlacement == TRAILING); boolean trailingScrollButtons = (scrollButtonsPlacement == TRAILING);
// for right-to-left always use "more tabs" button for horizontal scrolling // For right-to-left, always use "more tabs" button for horizontal scrolling
// because methods scrollForward() and scrollBackward() in class // because methods scrollForward() and scrollBackward() in class
// BasicTabbedPaneUI.ScrollableTabSupport do not work for right-to-left // BasicTabbedPaneUI.ScrollableTabSupport do not work for right-to-left.
//
// One problem is that BasicTabbedPaneUI.getClosestTab(), which is used
// to compute leadingTabIndex, does not work for right-to-left because is uses "binary" search
// on rects[] to find tab, but rects[] is ordered in reverse order for right-to-left.
// So leadingTabIndex is either zero or tabCount.
// Therefore increasing/decreasing leadingTabIndex in scrollForward()
// and scrollBackward() does not work as expected.
// Also backward/forward scroll buttons are not correctly enabled/disabled.
//
// Fixing this would require replacing nearly whole functionality of class
// BasicTabbedPaneUI.ScrollableTabSupport, which is not possible because it is private.
boolean leftToRight = isLeftToRight(); boolean leftToRight = isLeftToRight();
if( !leftToRight && isHorizontalTabPlacement( tabPane.getTabPlacement() ) ) { if( !leftToRight && isHorizontalTabPlacement( tabPane.getTabPlacement() ) ) {
useMoreTabsButton = true; useMoreTabsButton = true;
@@ -3840,6 +3863,8 @@ debug*/
w -= buttonWidth; w -= buttonWidth;
moreTabsButtonVisible = true; moreTabsButtonVisible = true;
} }
// layout scroll buttons
if( useScrollButtons ) { if( useScrollButtons ) {
// the tabViewport view size is set in // the tabViewport view size is set in
// BasicTabbedPaneUI.TabbedPaneScrollLayout.calculateTabRects(), // BasicTabbedPaneUI.TabbedPaneScrollLayout.calculateTabRects(),
@@ -3847,30 +3872,28 @@ debug*/
Point viewPosition = tabViewport.getViewPosition(); Point viewPosition = tabViewport.getViewPosition();
Dimension viewSize = tabViewport.getViewSize(); Dimension viewSize = tabViewport.getViewSize();
// layout forward button on trailing side
if( !hideDisabledScrollButtons || viewSize.width - viewPosition.x > w ) {
int buttonWidth = forwardButton.getPreferredSize().width;
forwardButton.setBounds( x + w - buttonWidth, y, buttonWidth, h );
w -= buttonWidth;
forwardButtonVisible = true;
}
// layout backward button // layout backward button
if( !hideDisabledScrollButtons || viewPosition.x > 0 ) { if( !hideDisabledScrollButtons || viewPosition.x > 0 ) {
int buttonWidth = backwardButton.getPreferredSize().width; int buttonWidth = backwardButton.getPreferredSize().width;
if( trailingScrollButtons ) { if( trailingScrollButtons ) {
// on trailing side // on trailing side
backwardButton.setBounds( leftToRight ? (x + w - buttonWidth) : x, y, buttonWidth, h ); backwardButton.setBounds( x + w - buttonWidth, y, buttonWidth, h );
x += leftToRight ? 0 : buttonWidth;
} else { } else {
// on leading side // on leading side
backwardButton.setBounds( leftToRight ? x : (x + w - buttonWidth), y, buttonWidth, h ); backwardButton.setBounds( x, y, buttonWidth, h );
x += leftToRight ? buttonWidth : 0; x += buttonWidth;
} }
w -= buttonWidth; w -= buttonWidth;
backwardButtonVisible = true; backwardButtonVisible = true;
} }
// layout forward button on trailing side
if( !hideDisabledScrollButtons || viewSize.width - viewPosition.x > w ) {
int buttonWidth = forwardButton.getPreferredSize().width;
forwardButton.setBounds( leftToRight ? (x + w - buttonWidth) : x, y, buttonWidth, h );
x += leftToRight ? 0 : buttonWidth;
w -= buttonWidth;
forwardButtonVisible = true;
}
} }
} }
@@ -3916,6 +3939,8 @@ debug*/
h -= buttonHeight; h -= buttonHeight;
moreTabsButtonVisible = true; moreTabsButtonVisible = true;
} }
// layout scroll buttons
if( useScrollButtons ) { if( useScrollButtons ) {
// the tabViewport view size is set in // the tabViewport view size is set in
// BasicTabbedPaneUI.TabbedPaneScrollLayout.calculateTabRects(), // BasicTabbedPaneUI.TabbedPaneScrollLayout.calculateTabRects(),
@@ -3923,6 +3948,14 @@ debug*/
Point viewPosition = tabViewport.getViewPosition(); Point viewPosition = tabViewport.getViewPosition();
Dimension viewSize = tabViewport.getViewSize(); Dimension viewSize = tabViewport.getViewSize();
// layout forward button on bottom side
if( !hideDisabledScrollButtons || viewSize.height - viewPosition.y > h ) {
int buttonHeight = forwardButton.getPreferredSize().height;
forwardButton.setBounds( x, y + h - buttonHeight, w, buttonHeight );
h -= buttonHeight;
forwardButtonVisible = true;
}
// layout backward button // layout backward button
if( !hideDisabledScrollButtons || viewPosition.y > 0 ) { if( !hideDisabledScrollButtons || viewPosition.y > 0 ) {
int buttonHeight = backwardButton.getPreferredSize().height; int buttonHeight = backwardButton.getPreferredSize().height;
@@ -3937,14 +3970,6 @@ debug*/
h -= buttonHeight; h -= buttonHeight;
backwardButtonVisible = true; backwardButtonVisible = true;
} }
// layout forward button on bottom side
if( !hideDisabledScrollButtons || viewSize.height - viewPosition.y > h ) {
int buttonHeight = forwardButton.getPreferredSize().height;
forwardButton.setBounds( x, y + h - buttonHeight, w, buttonHeight );
h -= buttonHeight;
forwardButtonVisible = true;
}
} }
} }
@@ -3985,10 +4010,8 @@ debug*/
//---- class RunWithOriginalLayoutManagerDelegateAction ------------------- //---- class RunWithOriginalLayoutManagerDelegateAction -------------------
private static class RunWithOriginalLayoutManagerDelegateAction private static class RunWithOriginalLayoutManagerDelegateAction
implements Action extends FlatUIAction
{ {
private final Action delegate;
static void install( ActionMap map, String key ) { static void install( ActionMap map, String key ) {
Action oldAction = map.get( key ); Action oldAction = map.get( key );
if( oldAction == null || oldAction instanceof RunWithOriginalLayoutManagerDelegateAction ) if( oldAction == null || oldAction instanceof RunWithOriginalLayoutManagerDelegateAction )
@@ -3998,24 +4021,9 @@ debug*/
} }
private RunWithOriginalLayoutManagerDelegateAction( Action delegate ) { private RunWithOriginalLayoutManagerDelegateAction( Action delegate ) {
this.delegate = delegate; super( delegate );
} }
@Override
public Object getValue( String key ) {
return delegate.getValue( key );
}
@Override
public boolean isEnabled() {
return delegate.isEnabled();
}
@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 ) {}
@Override @Override
public void actionPerformed( ActionEvent e ) { public void actionPerformed( ActionEvent e ) {
JTabbedPane tabbedPane = (JTabbedPane) e.getSource(); JTabbedPane tabbedPane = (JTabbedPane) e.getSource();

View File

@@ -65,8 +65,28 @@ public class FlatTableCellBorder
return super.getLineColor(); return super.getLineColor();
} }
@Override
public int getArc() {
if( c != null ) {
Integer selectionArc = getStyleFromTableUI( c, ui -> ui.selectionArc );
if( selectionArc != null )
return selectionArc;
}
return super.getArc();
}
@Override @Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) { public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
if( c != null ) {
Insets selectionInsets = getStyleFromTableUI( c, ui -> ui.selectionInsets );
if( selectionInsets != null ) {
x += selectionInsets.left;
y += selectionInsets.top;
width -= selectionInsets.left + selectionInsets.right;
height -= selectionInsets.top + selectionInsets.bottom;
}
}
this.c = c; this.c = c;
super.paintBorder( c, g, x, y, width, height ); super.paintBorder( c, g, x, y, width, height );
this.c = null; this.c = null;

View File

@@ -45,6 +45,7 @@ import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel; import javax.swing.table.TableColumnModel;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
@@ -234,8 +235,8 @@ public class FlatTableHeaderUI
@Override @Override
protected void rolloverColumnUpdated( int oldColumn, int newColumn ) { protected void rolloverColumnUpdated( int oldColumn, int newColumn ) {
header.repaint( header.getHeaderRect( oldColumn ) ); HiDPIUtils.repaint( header, header.getHeaderRect( oldColumn ) );
header.repaint( header.getHeaderRect( newColumn ) ); HiDPIUtils.repaint( header, header.getHeaderRect( newColumn ) );
} }
@Override @Override

View File

@@ -24,31 +24,49 @@ import java.awt.EventQueue;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Insets; import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.FocusEvent; import java.awt.event.FocusEvent;
import java.awt.event.FocusListener; import java.awt.event.FocusListener;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.util.Map; import java.util.Map;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JScrollPane; import javax.swing.JScrollPane;
import javax.swing.JTable; import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.JViewport; import javax.swing.JViewport;
import javax.swing.ListSelectionModel;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
import javax.swing.SwingConstants; import javax.swing.SwingConstants;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource; import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTableUI; import javax.swing.plaf.basic.BasicTableUI;
import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.JTableHeader; import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer; import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumnModel;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.icons.FlatCheckBoxIcon; import com.formdev.flatlaf.icons.FlatCheckBoxIcon;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.Graphics2DProxy; import com.formdev.flatlaf.util.Graphics2DProxy;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
@@ -88,7 +106,10 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault Table.intercellSpacing Dimension * @uiDefault Table.intercellSpacing Dimension
* @uiDefault Table.selectionInactiveBackground Color * @uiDefault Table.selectionInactiveBackground Color
* @uiDefault Table.selectionInactiveForeground Color * @uiDefault Table.selectionInactiveForeground Color
* @uiDefault Table.selectionInsets Insets
* @uiDefault Table.selectionArc int
* @uiDefault Table.paintOutsideAlternateRows boolean * @uiDefault Table.paintOutsideAlternateRows boolean
* @uiDefault Table.editorSelectAllOnStartEditing boolean
* *
* <!-- FlatTableCellBorder --> * <!-- FlatTableCellBorder -->
* *
@@ -115,6 +136,8 @@ public class FlatTableUI
@Styleable protected Color selectionForeground; @Styleable protected Color selectionForeground;
@Styleable protected Color selectionInactiveBackground; @Styleable protected Color selectionInactiveBackground;
@Styleable protected Color selectionInactiveForeground; @Styleable protected Color selectionInactiveForeground;
/** @since 3.5 */ @Styleable protected Insets selectionInsets;
/** @since 3.5 */ @Styleable protected int selectionArc;
// for FlatTableCellBorder // for FlatTableCellBorder
/** @since 2 */ @Styleable protected Insets cellMargins; /** @since 2 */ @Styleable protected Insets cellMargins;
@@ -127,6 +150,9 @@ public class FlatTableUI
private TableCellRenderer oldBooleanRenderer; private TableCellRenderer oldBooleanRenderer;
private PropertyChangeListener propertyChangeListener; private PropertyChangeListener propertyChangeListener;
private ComponentListener outsideAlternateRowsListener;
private ListSelectionListener rowSelectionListener;
private TableColumnModelListener columnSelectionListener;
private Map<String, Object> oldStyleValues; private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
@@ -153,6 +179,8 @@ public class FlatTableUI
selectionForeground = UIManager.getColor( "Table.selectionForeground" ); selectionForeground = UIManager.getColor( "Table.selectionForeground" );
selectionInactiveBackground = UIManager.getColor( "Table.selectionInactiveBackground" ); selectionInactiveBackground = UIManager.getColor( "Table.selectionInactiveBackground" );
selectionInactiveForeground = UIManager.getColor( "Table.selectionInactiveForeground" ); selectionInactiveForeground = UIManager.getColor( "Table.selectionInactiveForeground" );
selectionInsets = UIManager.getInsets( "Table.selectionInsets" );
selectionArc = UIManager.getInt( "Table.selectionArc" );
toggleSelectionColors(); toggleSelectionColors();
@@ -240,6 +268,28 @@ public class FlatTableUI
propertyChangeListener = e -> { propertyChangeListener = e -> {
switch( e.getPropertyName() ) { switch( e.getPropertyName() ) {
case "selectionModel":
if( rowSelectionListener != null ) {
Object oldModel = e.getOldValue();
Object newModel = e.getNewValue();
if( oldModel != null )
((ListSelectionModel)oldModel).removeListSelectionListener( rowSelectionListener );
if( newModel != null )
((ListSelectionModel)newModel).addListSelectionListener( rowSelectionListener );
}
break;
case "columnModel":
if( columnSelectionListener != null ) {
Object oldModel = e.getOldValue();
Object newModel = e.getNewValue();
if( oldModel != null )
((TableColumnModel)oldModel).removeColumnModelListener( columnSelectionListener );
if( newModel != null )
((TableColumnModel)newModel).addColumnModelListener( columnSelectionListener );
}
break;
case FlatClientProperties.COMPONENT_FOCUS_OWNER: case FlatClientProperties.COMPONENT_FOCUS_OWNER:
toggleSelectionColors(); toggleSelectionColors();
break; break;
@@ -248,11 +298,14 @@ public class FlatTableUI
case FlatClientProperties.STYLE_CLASS: case FlatClientProperties.STYLE_CLASS:
installStyle(); installStyle();
table.revalidate(); table.revalidate();
table.repaint(); HiDPIUtils.repaint( table );
break; break;
} }
}; };
table.addPropertyChangeListener( propertyChangeListener ); table.addPropertyChangeListener( propertyChangeListener );
if( selectionArc > 0 )
installRepaintRoundedSelectionListeners();
} }
@Override @Override
@@ -261,6 +314,19 @@ public class FlatTableUI
table.removePropertyChangeListener( propertyChangeListener ); table.removePropertyChangeListener( propertyChangeListener );
propertyChangeListener = null; propertyChangeListener = null;
if( outsideAlternateRowsListener != null ) {
table.removeComponentListener( outsideAlternateRowsListener );
outsideAlternateRowsListener = null;
}
if( rowSelectionListener != null ) {
table.getSelectionModel().removeListSelectionListener( rowSelectionListener );
rowSelectionListener = null;
}
if( columnSelectionListener != null ) {
table.getColumnModel().removeColumnModelListener( columnSelectionListener );
columnSelectionListener = null;
}
} }
@Override @Override
@@ -284,6 +350,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 */ /** @since 2 */
protected void installStyle() { protected void installStyle() {
try { try {
@@ -325,6 +403,8 @@ public class FlatTableUI
protected Object applyStyleProperty( String key, Object value ) { protected Object applyStyleProperty( String key, Object value ) {
if( "rowHeight".equals( key ) && value instanceof Integer ) if( "rowHeight".equals( key ) && value instanceof Integer )
value = UIScale.scale( (Integer) value ); value = UIScale.scale( (Integer) value );
else if( "selectionArc".equals( key ) && value instanceof Integer && (Integer) value > 0 )
installRepaintRoundedSelectionListeners();
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, table, key, value ); return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, table, key, value );
} }
@@ -387,6 +467,7 @@ public class FlatTableUI
double systemScaleFactor = UIScale.getSystemScaleFactor( (Graphics2D) g ); double systemScaleFactor = UIScale.getSystemScaleFactor( (Graphics2D) g );
double lineThickness = (1. / systemScaleFactor) * (int) systemScaleFactor; double lineThickness = (1. / systemScaleFactor) * (int) systemScaleFactor;
double lineOffset = (1. - lineThickness) + 0.05; // adding 0.05 to fix line location in some cases
// Java 8 uses drawLine() to paint grid lines // Java 8 uses drawLine() to paint grid lines
// Java 9+ uses fillRect() to paint grid lines (except for dragged column) // Java 9+ uses fillRect() to paint grid lines (except for dragged column)
@@ -429,11 +510,11 @@ public class FlatTableUI
// reduce line thickness to avoid unstable painted line thickness // reduce line thickness to avoid unstable painted line thickness
if( lineThickness != 1 ) { if( lineThickness != 1 ) {
if( horizontalLines && height == 1 && wasInvokedFromPaintGrid() ) { if( horizontalLines && height == 1 && wasInvokedFromPaintGrid() ) {
super.fill( new Rectangle2D.Double( x, y, width, lineThickness ) ); super.fill( new Rectangle2D.Double( x, y + lineOffset, width, lineThickness ) );
return; return;
} }
if( verticalLines && width == 1 && y == 0 && wasInvokedFromPaintGrid() ) { if( verticalLines && width == 1 && y == 0 && wasInvokedFromPaintGrid() ) {
super.fill( new Rectangle2D.Double( x, y, lineThickness, height ) ); super.fill( new Rectangle2D.Double( x + lineOffset, y, lineThickness, height ) );
return; return;
} }
} }
@@ -451,6 +532,10 @@ public class FlatTableUI
}; };
} }
// rounded selection or selection insets
if( selectionArc > 0 || (selectionInsets != null && !FlatUIUtils.isInsetsEmpty( selectionInsets )) )
g = new RoundedSelectionGraphics( g, UIManager.getColor( "Table.alternateRowColor" ) );
super.paint( g, c ); super.paint( g, c );
} }
@@ -496,8 +581,6 @@ public class FlatTableUI
boolean paintOutside = UIManager.getBoolean( "Table.paintOutsideAlternateRows" ); boolean paintOutside = UIManager.getBoolean( "Table.paintOutsideAlternateRows" );
Color alternateColor; Color alternateColor;
if( paintOutside && (alternateColor = UIManager.getColor( "Table.alternateRowColor" )) != null ) { if( paintOutside && (alternateColor = UIManager.getColor( "Table.alternateRowColor" )) != null ) {
g.setColor( alternateColor );
int rowCount = table.getRowCount(); int rowCount = table.getRowCount();
// paint alternating empty rows below the table // paint alternating empty rows below the table
@@ -506,10 +589,350 @@ public class FlatTableUI
int tableWidth = table.getWidth(); int tableWidth = table.getWidth();
int rowHeight = table.getRowHeight(); int rowHeight = table.getRowHeight();
g.setColor( alternateColor );
int x = viewport.getComponentOrientation().isLeftToRight() ? 0 : viewportWidth - tableWidth;
for( int y = tableHeight, row = rowCount; y < viewportHeight; y += rowHeight, row++ ) { for( int y = tableHeight, row = rowCount; y < viewportHeight; y += rowHeight, row++ ) {
if( row % 2 != 0 ) if( row % 2 != 0 )
g.fillRect( 0, y, tableWidth, rowHeight ); paintAlternateRowBackground( g, -1, -1, x, y, tableWidth, rowHeight );
} }
// add listener on demand
if( outsideAlternateRowsListener == null && table.getAutoResizeMode() == JTable.AUTO_RESIZE_OFF ) {
outsideAlternateRowsListener = new FlatOutsideAlternateRowsListener();
table.addComponentListener( outsideAlternateRowsListener );
}
}
}
}
/**
* Paints (rounded) alternate row background.
* Supports {@link #selectionArc} and {@link #selectionInsets}.
* <p>
* <b>Note:</b> This method is only invoked if either selection arc
* is greater than zero or if selection insets are not empty.
*
* @since 3.5
*/
protected void paintAlternateRowBackground( Graphics g, int row, int column, int x, int y, int width, int height ) {
Insets insets = (selectionInsets != null) ? (Insets) selectionInsets.clone() : null;
float arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight;
arcTopLeft = arcTopRight = arcBottomLeft = arcBottomRight = UIScale.scale( selectionArc / 2f );
if( column >= 0 ) {
// selection insets
// selection arc
if( column > 0 ) {
if( insets != null )
insets.left = 0;
if( table.getComponentOrientation().isLeftToRight() )
arcTopLeft = arcBottomLeft = 0;
else
arcTopRight = arcBottomRight = 0;
}
if( column < table.getColumnCount() - 1 ) {
if( insets != null )
insets.right = 0;
if( table.getComponentOrientation().isLeftToRight() )
arcTopRight = arcBottomRight = 0;
else
arcTopLeft = arcBottomLeft = 0;
}
}
FlatUIUtils.paintSelection( (Graphics2D) g, x, y, width, height,
UIScale.scale( insets ), arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight, 0 );
}
/**
* Paints (rounded) cell selection.
* Supports {@link #selectionArc} and {@link #selectionInsets}.
* <p>
* <b>Note:</b> This method is only invoked if either selection arc
* is greater than zero or if selection insets are not empty.
*
* @since 3.5
*/
protected void paintCellSelection( Graphics g, int row, int column, int x, int y, int width, int height ) {
boolean rowSelAllowed = table.getRowSelectionAllowed();
boolean colSelAllowed = table.getColumnSelectionAllowed();
boolean rowSelOnly = rowSelAllowed && !colSelAllowed;
boolean colSelOnly = colSelAllowed && !rowSelAllowed;
boolean cellOnlySel = rowSelAllowed && colSelAllowed;
// get selection state of surrounding cells
boolean leftSelected = (column > 0 && (rowSelOnly || table.isCellSelected( row, column - 1 )));
boolean topSelected = (row > 0 && (colSelOnly || table.isCellSelected( row - 1, column )));
boolean rightSelected = (column < table.getColumnCount() - 1 && (rowSelOnly || table.isCellSelected( row, column + 1 )));
boolean bottomSelected = (row < table.getRowCount() - 1 && (colSelOnly || table.isCellSelected( row + 1, column )));
if( !table.getComponentOrientation().isLeftToRight() ) {
boolean temp = leftSelected;
leftSelected = rightSelected;
rightSelected = temp;
}
// selection insets
// (insets are applied to whole row if row-only selection is used,
// or to whole column if column-only selection is used,
// or to cell if cell selection is used)
Insets insets = (selectionInsets != null) ? (Insets) selectionInsets.clone() : null;
if( insets != null ) {
if( rowSelOnly && leftSelected )
insets.left = 0;
if( rowSelOnly && rightSelected )
insets.right = 0;
if( colSelOnly && topSelected )
insets.top = 0;
if( colSelOnly && bottomSelected )
insets.bottom = 0;
}
// selection arc
float arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight;
arcTopLeft = arcTopRight = arcBottomLeft = arcBottomRight = UIScale.scale( selectionArc / 2f );
if( selectionArc > 0 ) {
// note that intercellSpacing is not considered as a gap because
// grid lines are usually painted to intercell space
boolean hasRowGap = (rowSelOnly || cellOnlySel) && insets != null && (insets.top != 0 || insets.bottom != 0);
boolean hasColGap = (colSelOnly || cellOnlySel) && insets != null && (insets.left != 0 || insets.right != 0);
if( leftSelected && !hasColGap )
arcTopLeft = arcBottomLeft = 0;
if( rightSelected && !hasColGap )
arcTopRight = arcBottomRight = 0;
if( topSelected && !hasRowGap )
arcTopLeft = arcTopRight = 0;
if( bottomSelected && !hasRowGap )
arcBottomLeft = arcBottomRight = 0;
}
FlatUIUtils.paintSelection( (Graphics2D) g, x, y, width, height,
UIScale.scale( insets ), arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight, 0 );
}
/**
* Paints a cell selection at the given coordinates.
* The selection color must be set on the graphics context.
* <p>
* This method is intended for use in custom cell renderers to support
* {@link #selectionArc} and {@link #selectionInsets}.
*
* @since 3.5
*/
public static void paintCellSelection( JTable table, Graphics g, int row, int column, int x, int y, int width, int height ) {
if( !(table.getUI() instanceof FlatTableUI) )
return;
FlatTableUI ui = (FlatTableUI) table.getUI();
ui.paintCellSelection( g, row, column, x, y, width, height );
}
private void installRepaintRoundedSelectionListeners() {
if( rowSelectionListener == null ) {
rowSelectionListener = this::repaintRoundedRowSelection;
table.getSelectionModel().addListSelectionListener( rowSelectionListener );
}
if( columnSelectionListener == null ) {
columnSelectionListener = new TableColumnModelListener() {
@Override
public void columnSelectionChanged( ListSelectionEvent e ) {
repaintRoundedColumnSelection( e );
}
@Override public void columnRemoved( TableColumnModelEvent e ) {}
@Override public void columnMoved( TableColumnModelEvent e ) {}
@Override public void columnMarginChanged( ChangeEvent e ) {}
@Override public void columnAdded( TableColumnModelEvent e ) {}
};
table.getColumnModel().addColumnModelListener( columnSelectionListener );
}
}
private void repaintRoundedRowSelection( ListSelectionEvent e ) {
if( selectionArc <= 0 || !table.getRowSelectionAllowed() )
return;
int rowCount = table.getRowCount();
int columnCount = table.getColumnCount();
if( rowCount <= 0 || columnCount <= 0 )
return;
// repaint including rows before and after changed selection
int firstRow = Math.max( 0, Math.min( e.getFirstIndex() - 1, rowCount - 1 ) );
int lastRow = Math.max( 0, Math.min( e.getLastIndex() + 1, rowCount - 1 ) );
Rectangle firstRect = table.getCellRect( firstRow, 0, false );
Rectangle lastRect = table.getCellRect( lastRow, columnCount - 1, false );
table.repaint( firstRect.union( lastRect ) );
}
private void repaintRoundedColumnSelection( ListSelectionEvent e ) {
if( selectionArc <= 0 || !table.getColumnSelectionAllowed() )
return;
int rowCount = table.getRowCount();
int columnCount = table.getColumnCount();
if( rowCount <= 0 || columnCount <= 0 )
return;
// limit to selected rows for cell selection
int firstRow = 0;
int lastRow = rowCount - 1;
if( table.getRowSelectionAllowed() ) {
firstRow = table.getSelectionModel().getMinSelectionIndex();
lastRow = table.getSelectionModel().getMaxSelectionIndex();
}
// repaint including columns before and after changed selection
int firstColumn = Math.max( 0, Math.min( e.getFirstIndex() - 1, columnCount - 1 ) );
int lastColumn = Math.max( 0, Math.min( e.getLastIndex() + 1, columnCount - 1 ) );
Rectangle firstRect = table.getCellRect( firstRow, firstColumn, false );
Rectangle lastRect = table.getCellRect( lastRow, lastColumn, false );
table.repaint( firstRect.union( lastRect ) );
}
//---- class RoundedSelectionGraphics -------------------------------------
/**
* Because selection painting is done in the cell renderer, it would be
* necessary to require a FlatLaf specific renderer to implement rounded selection.
* Using a LaF specific renderer was avoided because often a custom renderer is
* already used in applications. Then either the rounded selection is not used,
* or the application has to be changed to extend a FlatLaf renderer.
* <p>
* To solve this, a graphics proxy is used that paints rounded selection
* if row/column/cell is selected and the renderer wants to fill the background.
*/
private class RoundedSelectionGraphics
extends Graphics2DProxy
{
private final Color alternateRowColor;
// used to avoid endless loop in case that paintCellSelection() invokes
// g.fillRect() with full bounds (selectionInsets is 0,0,0,0)
private boolean inPaintSelection;
RoundedSelectionGraphics( Graphics delegate, Color alternateRowColor ) {
super( (Graphics2D) delegate );
this.alternateRowColor = alternateRowColor;
}
@Override
public Graphics create() {
return new RoundedSelectionGraphics( super.create(), alternateRowColor );
}
@Override
public Graphics create( int x, int y, int width, int height ) {
return new RoundedSelectionGraphics( super.create( x, y, width, height ), alternateRowColor );
}
@Override
public void fillRect( int x, int y, int width, int height ) {
if( fillCellSelection( x, y, width, height ) )
return;
super.fillRect( x, y, width, height );
}
@Override
public void fill( Shape shape ) {
if( shape instanceof Rectangle2D ) {
Rectangle2D r = (Rectangle2D) shape;
double x = r.getX();
double y = r.getY();
double width = r.getWidth();
double height = r.getHeight();
if( x == (int) x && y == (int) y && width == (int) width && height == (int) height ) {
if( fillCellSelection( (int) x, (int) y, (int) width, (int) height ) )
return;
}
}
super.fill( shape );
}
private boolean fillCellSelection( int x, int y, int width, int height ) {
if( inPaintSelection )
return false;
Color color;
Component rendererComponent;
if( x == 0 && y == 0 &&
((color = getColor()) == table.getSelectionBackground() ||
(alternateRowColor != null && color == alternateRowColor)) &&
(rendererComponent = findActiveRendererComponent()) != null &&
width == rendererComponent.getWidth() &&
height == rendererComponent.getHeight() )
{
Point location = rendererComponent.getLocation();
int row = table.rowAtPoint( location );
int column = table.columnAtPoint( location );
if( row >= 0 && column >= 0 ) {
inPaintSelection = true;
if( color == table.getSelectionBackground() )
paintCellSelection( this, row, column, x, y, width, height );
else
paintAlternateRowBackground( this, row, column, x, y, width, height );
inPaintSelection = false;
return true;
}
}
return false;
}
/**
* A CellRendererPane may contain multiple components, if multiple renderers
* are used. Inactive renderer components have size {@code 0x0}.
*/
private Component findActiveRendererComponent() {
int count = rendererPane.getComponentCount();
for( int i = 0; i < count; i++ ) {
Component c = rendererPane.getComponent( i );
if( c.getWidth() > 0 && c.getHeight() > 0 )
return c;
}
return null;
}
}
//---- class OutsideAlternateRowsListener ---------------------------------
/**
* Used if table auto-resize-mode is off to repaint outside alternate rows
* when table width changed (column resized) or component orientation changed.
*/
private class FlatOutsideAlternateRowsListener
extends ComponentAdapter
{
@Override
public void componentHidden( ComponentEvent e ) {
Container viewport = SwingUtilities.getUnwrappedParent( table );
if( viewport instanceof JViewport )
HiDPIUtils.repaint( viewport );
}
@Override
public void componentMoved( ComponentEvent e ) {
repaintAreaBelowTable();
}
@Override
public void componentResized( ComponentEvent e ) {
repaintAreaBelowTable();
}
private void repaintAreaBelowTable() {
Container viewport = SwingUtilities.getUnwrappedParent( table );
if( viewport instanceof JViewport ) {
int viewportHeight = viewport.getHeight();
int tableHeight = table.getHeight();
if( tableHeight < viewportHeight )
HiDPIUtils.repaint( viewport, 0, tableHeight, viewport.getWidth(), viewportHeight - tableHeight );
} }
} }
} }
@@ -579,4 +1002,36 @@ public class FlatTableUI
selected = (value != null && (Boolean) 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

@@ -239,7 +239,7 @@ public class FlatTextFieldUI
case COMPONENT_ROUND_RECT: case COMPONENT_ROUND_RECT:
case OUTLINE: case OUTLINE:
case TEXT_FIELD_PADDING: case TEXT_FIELD_PADDING:
c.repaint(); HiDPIUtils.repaint( c );
break; break;
case MINIMUM_WIDTH: case MINIMUM_WIDTH:
@@ -250,38 +250,38 @@ public class FlatTextFieldUI
case STYLE_CLASS: case STYLE_CLASS:
installStyle(); installStyle();
c.revalidate(); c.revalidate();
c.repaint(); HiDPIUtils.repaint( c );
break; break;
case TEXT_FIELD_LEADING_ICON: case TEXT_FIELD_LEADING_ICON:
leadingIcon = (e.getNewValue() instanceof Icon) ? (Icon) e.getNewValue() : null; leadingIcon = (e.getNewValue() instanceof Icon) ? (Icon) e.getNewValue() : null;
c.repaint(); HiDPIUtils.repaint( c );
break; break;
case TEXT_FIELD_TRAILING_ICON: case TEXT_FIELD_TRAILING_ICON:
trailingIcon = (e.getNewValue() instanceof Icon) ? (Icon) e.getNewValue() : null; trailingIcon = (e.getNewValue() instanceof Icon) ? (Icon) e.getNewValue() : null;
c.repaint(); HiDPIUtils.repaint( c );
break; break;
case TEXT_FIELD_LEADING_COMPONENT: case TEXT_FIELD_LEADING_COMPONENT:
uninstallLeadingComponent(); uninstallLeadingComponent();
installLeadingComponent(); installLeadingComponent();
c.revalidate(); c.revalidate();
c.repaint(); HiDPIUtils.repaint( c );
break; break;
case TEXT_FIELD_TRAILING_COMPONENT: case TEXT_FIELD_TRAILING_COMPONENT:
uninstallTrailingComponent(); uninstallTrailingComponent();
installTrailingComponent(); installTrailingComponent();
c.revalidate(); c.revalidate();
c.repaint(); HiDPIUtils.repaint( c );
break; break;
case TEXT_FIELD_SHOW_CLEAR_BUTTON: case TEXT_FIELD_SHOW_CLEAR_BUTTON:
uninstallClearButton(); uninstallClearButton();
installClearButton(); installClearButton();
c.revalidate(); c.revalidate();
c.repaint(); HiDPIUtils.repaint( c );
break; break;
case "enabled": case "enabled":
@@ -815,7 +815,7 @@ debug*/
if( visible != clearButton.isVisible() ) { if( visible != clearButton.isVisible() ) {
clearButton.setVisible( visible ); clearButton.setVisible( visible );
c.revalidate(); c.revalidate();
c.repaint(); HiDPIUtils.repaint( c );
} }
} }

View File

@@ -36,6 +36,7 @@ import java.awt.Rectangle;
import java.awt.Toolkit; import java.awt.Toolkit;
import java.awt.Window; import java.awt.Window;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent; import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener; import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
@@ -46,9 +47,9 @@ import java.awt.event.WindowEvent;
import java.awt.geom.AffineTransform; import java.awt.geom.AffineTransform;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.function.Function;
import javax.accessibility.AccessibleContext; import javax.accessibility.AccessibleContext;
import javax.swing.BorderFactory; import javax.swing.BorderFactory;
import javax.swing.Box; import javax.swing.Box;
@@ -57,7 +58,6 @@ import javax.swing.Icon;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JDialog; import javax.swing.JDialog;
import javax.swing.JInternalFrame;
import javax.swing.JLabel; import javax.swing.JLabel;
import javax.swing.JMenuBar; import javax.swing.JMenuBar;
import javax.swing.JPanel; import javax.swing.JPanel;
@@ -66,6 +66,7 @@ import javax.swing.SwingUtilities;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.border.AbstractBorder; import javax.swing.border.AbstractBorder;
import javax.swing.border.Border; import javax.swing.border.Border;
import javax.swing.plaf.ComponentUI;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatSystemProperties; import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.ui.FlatNativeWindowBorder.WindowTopBorder; import com.formdev.flatlaf.ui.FlatNativeWindowBorder.WindowTopBorder;
@@ -98,7 +99,6 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault TitlePane.showIconBesideTitle boolean * @uiDefault TitlePane.showIconBesideTitle boolean
* @uiDefault TitlePane.menuBarTitleGap int * @uiDefault TitlePane.menuBarTitleGap int
* @uiDefault TitlePane.menuBarTitleMinimumGap int * @uiDefault TitlePane.menuBarTitleMinimumGap int
* @uiDefault TitlePane.menuBarResizeHeight int
* @uiDefault TitlePane.closeIcon Icon * @uiDefault TitlePane.closeIcon Icon
* @uiDefault TitlePane.iconifyIcon Icon * @uiDefault TitlePane.iconifyIcon Icon
* @uiDefault TitlePane.maximizeIcon Icon * @uiDefault TitlePane.maximizeIcon Icon
@@ -109,7 +109,8 @@ import com.formdev.flatlaf.util.UIScale;
public class FlatTitlePane public class FlatTitlePane
extends JComponent 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";
private static final boolean isWindows_10 = SystemInfo.isWindows_10_orLater && !SystemInfo.isWindows_11_orLater;
/** @since 2.5 */ protected final Font titleFont; /** @since 2.5 */ protected final Font titleFont;
protected final Color activeBackground; protected final Color activeBackground;
@@ -131,7 +132,6 @@ public class FlatTitlePane
/** @since 2.4 */ protected final boolean showIconBesideTitle; /** @since 2.4 */ protected final boolean showIconBesideTitle;
protected final int menuBarTitleGap; protected final int menuBarTitleGap;
/** @since 2.4 */ protected final int menuBarTitleMinimumGap; /** @since 2.4 */ protected final int menuBarTitleMinimumGap;
/** @since 2.4 */ protected final int menuBarResizeHeight;
protected final JRootPane rootPane; protected final JRootPane rootPane;
protected final String windowStyle; protected final String windowStyle;
@@ -150,6 +150,33 @@ public class FlatTitlePane
private final Handler handler; 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;
/**
* This panel paint a border at the top of the window in fullWindowContent mode,
* if FlatLaf window decorations are enabled.
* Only used on Windows 10.
* <p>
* This panel is not a child of the title pane.
* Instead it is added by FlatRootPaneUI to the layered pane at a layer over all other layers.
*/
final JPanel windowTopBorderLayer;
public FlatTitlePane( JRootPane rootPane ) { public FlatTitlePane( JRootPane rootPane ) {
this.rootPane = rootPane; this.rootPane = rootPane;
@@ -178,7 +205,6 @@ public class FlatTitlePane
showIconBesideTitle = FlatUIUtils.getSubUIBoolean( "TitlePane.showIconBesideTitle", windowStyle, false ); showIconBesideTitle = FlatUIUtils.getSubUIBoolean( "TitlePane.showIconBesideTitle", windowStyle, false );
menuBarTitleGap = FlatUIUtils.getSubUIInt( "TitlePane.menuBarTitleGap", windowStyle, 40 ); menuBarTitleGap = FlatUIUtils.getSubUIInt( "TitlePane.menuBarTitleGap", windowStyle, 40 );
menuBarTitleMinimumGap = FlatUIUtils.getSubUIInt( "TitlePane.menuBarTitleMinimumGap", windowStyle, 12 ); menuBarTitleMinimumGap = FlatUIUtils.getSubUIInt( "TitlePane.menuBarTitleMinimumGap", windowStyle, 12 );
menuBarResizeHeight = FlatUIUtils.getSubUIInt( "TitlePane.menuBarResizeHeight", windowStyle, 4 );
handler = createHandler(); handler = createHandler();
@@ -187,11 +213,18 @@ public class FlatTitlePane
addSubComponents(); addSubComponents();
activeChanged( true ); activeChanged( true );
addMouseListener( handler ); mouseLayer = new JPanel();
addMouseMotionListener( handler ); mouseLayer.setOpaque( false );
mouseLayer.addMouseListener( handler );
mouseLayer.addMouseMotionListener( handler );
// necessary for closing window with double-click on icon if( isWindows_10 && FlatNativeWindowBorder.isSupported() ) {
iconLabel.addMouseListener( handler ); windowTopBorderLayer = new JPanel();
windowTopBorderLayer.setVisible( false );
windowTopBorderLayer.setOpaque( false );
windowTopBorderLayer.setBorder( FlatUIUtils.nonUIResource( WindowTopBorder.getInstance() ) );
} else
windowTopBorderLayer = null;
applyComponentOrientation( rootPane.getComponentOrientation() ); applyComponentOrientation( rootPane.getComponentOrientation() );
} }
@@ -234,6 +267,11 @@ public class FlatTitlePane
setLayout( new BorderLayout() { setLayout( new BorderLayout() {
@Override @Override
public void layoutContainer( Container target ) { public void layoutContainer( Container target ) {
if( isFullWindowContent() ) {
super.layoutContainer( target );
return;
}
// compute available bounds // compute available bounds
Insets insets = target.getInsets(); Insets insets = target.getInsets();
int x = insets.left; int x = insets.left;
@@ -247,7 +285,7 @@ public class FlatTitlePane
int titleWidth = w - leftWidth - buttonsWidth; int titleWidth = w - leftWidth - buttonsWidth;
int minTitleWidth = UIScale.scale( titleMinimumWidth ); 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(); Icon icon = titleLabel.getIcon();
if( icon != null ) { if( icon != null ) {
Insets iconInsets = iconLabel.getInsets(); Insets iconInsets = iconLabel.getInsets();
@@ -295,6 +333,9 @@ public class FlatTitlePane
horizontalGlue.getWidth(), titleLabel.getHeight() ); horizontalGlue.getWidth(), titleLabel.getHeight() );
} }
} }
// clear hit-test cache
lastCaptionHitTestTime = 0;
} }
} ); } );
@@ -338,6 +379,13 @@ public class FlatTitlePane
buttonPanel.add( restoreButton ); buttonPanel.add( restoreButton );
} }
buttonPanel.add( closeButton ); 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 ) { protected JButton createButton( String iconKey, String accessibleName, ActionListener action ) {
@@ -417,7 +465,9 @@ public class FlatTitlePane
/** @since 3 */ /** @since 3 */
protected void updateVisibility() { 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 ) ); closeButton.setVisible( clientPropertyBoolean( rootPane, TITLE_BAR_SHOW_CLOSE, true ) );
if( window instanceof Frame ) { if( window instanceof Frame ) {
@@ -443,7 +493,7 @@ public class FlatTitlePane
// get window images // get window images
List<Image> images = null; List<Image> images = null;
if( clientPropertyBoolean( rootPane, TITLE_BAR_SHOW_ICON, defaultShowIcon ) ) { if( clientPropertyBoolean( rootPane, TITLE_BAR_SHOW_ICON, defaultShowIcon ) && !isFullWindowContent() ) {
images = window.getIconImages(); images = window.getIconImages();
if( images.isEmpty() ) { if( images.isEmpty() ) {
// search in owners // search in owners
@@ -468,6 +518,13 @@ public class FlatTitlePane
updateNativeTitleBarHeightAndHitTestSpotsLater(); 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 @Override
public void addNotify() { public void addNotify() {
super.addNotify(); super.addNotify();
@@ -522,6 +579,11 @@ public class FlatTitlePane
window.removeComponentListener( handler ); 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. * Returns whether this title pane currently has a visible and embedded menubar.
*/ */
@@ -533,6 +595,9 @@ public class FlatTitlePane
* Returns whether the menubar should be embedded into the title pane. * Returns whether the menubar should be embedded into the title pane.
*/ */
protected boolean isMenuBarEmbedded() { protected boolean isMenuBarEmbedded() {
if( isFullWindowContent() )
return false;
// not storing value of "TitlePane.menuBarEmbedded" in class to allow changing at runtime // not storing value of "TitlePane.menuBarEmbedded" in class to allow changing at runtime
return FlatUIUtils.getBoolean( rootPane, return FlatUIUtils.getBoolean( rootPane,
FlatSystemProperties.MENUBAR_EMBEDDED, FlatSystemProperties.MENUBAR_EMBEDDED,
@@ -620,21 +685,45 @@ public class FlatTitlePane
return; return;
if( debugTitleBarHeight > 0 ) { 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.setColor( Color.green );
g.drawLine( 0, debugTitleBarHeight, getWidth(), debugTitleBarHeight ); g.drawLine( 0, y, getWidth(), y );
} }
if( debugHitTestSpots != null ) {
for( Rectangle r : debugHitTestSpots ) g.setColor( Color.red );
paintRect( g, Color.red, r ); debugPaintComponentWithMouseListener( g, Color.red, rootPane.getLayeredPane(), 0, 0 );
}
paintRect( g, Color.cyan, debugCloseButtonBounds ); debugPaintRect( g, Color.blue, debugAppIconBounds );
paintRect( g, Color.blue, debugAppIconBounds ); debugPaintRect( g, Color.blue, debugMinimizeButtonBounds );
paintRect( g, Color.blue, debugMinimizeButtonBounds ); debugPaintRect( g, Color.magenta, debugMaximizeButtonBounds );
paintRect( g, Color.magenta, debugMaximizeButtonBounds ); debugPaintRect( g, Color.cyan, debugCloseButtonBounds );
paintRect( 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 ) if( r == null )
return; return;
@@ -645,6 +734,9 @@ public class FlatTitlePane
@Override @Override
protected void paintComponent( Graphics g ) { protected void paintComponent( Graphics g ) {
if( isFullWindowContent() )
return;
// not storing value of "TitlePane.unifiedBackground" in class to allow changing at runtime // not storing value of "TitlePane.unifiedBackground" in class to allow changing at runtime
g.setColor( (UIManager.getBoolean( "TitlePane.unifiedBackground" ) && g.setColor( (UIManager.getBoolean( "TitlePane.unifiedBackground" ) &&
clientPropertyColor( rootPane, TITLE_BAR_BACKGROUND, null ) == null) clientPropertyColor( rootPane, TITLE_BAR_BACKGROUND, null ) == null)
@@ -653,16 +745,6 @@ public class FlatTitlePane
g.fillRect( 0, 0, getWidth(), getHeight() ); g.fillRect( 0, 0, getWidth(), getHeight() );
} }
protected void repaintWindowBorder() {
int width = rootPane.getWidth();
int height = rootPane.getHeight();
Insets insets = rootPane.getInsets();
rootPane.repaint( 0, 0, width, insets.top ); // top
rootPane.repaint( 0, 0, insets.left, height ); // left
rootPane.repaint( 0, height - insets.bottom, width, insets.bottom ); // bottom
rootPane.repaint( width - insets.right, 0, insets.right, height ); // right
}
/** /**
* Iconifies the window. * Iconifies the window.
*/ */
@@ -846,6 +928,10 @@ public class FlatTitlePane
return window != null && FlatNativeWindowBorder.hasCustomDecoration( window ); return window != null && FlatNativeWindowBorder.hasCustomDecoration( window );
} }
boolean isWindowTopBorderNeeded() {
return isWindows_10 && hasNativeCustomDecoration();
}
// used to invoke updateNativeTitleBarHeightAndHitTestSpots() only once from latest invokeLater() // used to invoke updateNativeTitleBarHeightAndHitTestSpots() only once from latest invokeLater()
private int laterCounter; private int laterCounter;
@@ -866,11 +952,14 @@ public class FlatTitlePane
return; return;
int titleBarHeight = getHeight(); 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 // slightly reduce height so that component receives mouseExit events
if( titleBarHeight > 0 ) if( titleBarHeight > 0 )
titleBarHeight--; titleBarHeight--;
List<Rectangle> hitTestSpots = new ArrayList<>();
Rectangle appIconBounds = null; Rectangle appIconBounds = null;
if( !showIconBesideTitle && iconLabel.isVisible() ) { if( !showIconBesideTitle && iconLabel.isVisible() ) {
@@ -928,71 +1017,17 @@ public class FlatTitlePane
} }
} }
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 minimizeButtonBounds = boundsInWindow( iconifyButton );
Rectangle maximizeButtonBounds = boundsInWindow( maximizeButton.isVisible() ? maximizeButton : restoreButton ); Rectangle maximizeButtonBounds = boundsInWindow( maximizeButton.isVisible() ? maximizeButton : restoreButton );
Rectangle closeButtonBounds = boundsInWindow( closeButton ); Rectangle closeButtonBounds = boundsInWindow( closeButton );
// clear hit-test cache
lastCaptionHitTestTime = 0;
FlatNativeWindowBorder.setTitleBarHeightAndHitTestSpots( window, titleBarHeight, FlatNativeWindowBorder.setTitleBarHeightAndHitTestSpots( window, titleBarHeight,
hitTestSpots, appIconBounds, minimizeButtonBounds, maximizeButtonBounds, closeButtonBounds ); this::captionHitTest, appIconBounds, minimizeButtonBounds, maximizeButtonBounds, closeButtonBounds );
debugTitleBarHeight = titleBarHeight; debugTitleBarHeight = titleBarHeight;
debugHitTestSpots = hitTestSpots;
debugAppIconBounds = appIconBounds; debugAppIconBounds = appIconBounds;
debugMinimizeButtonBounds = minimizeButtonBounds; debugMinimizeButtonBounds = minimizeButtonBounds;
debugMaximizeButtonBounds = maximizeButtonBounds; debugMaximizeButtonBounds = maximizeButtonBounds;
@@ -1007,18 +1042,101 @@ public class FlatTitlePane
: null; : null;
} }
protected Rectangle getNativeHitTestSpot( JComponent c ) { /**
Dimension size = c.getSize(); * Returns whether there is a component at the given location, that processes
if( size.width <= 0 || size.height <= 0 ) * mouse events. E.g. buttons, menus, etc.
return null; * <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 ); // convert pt from window coordinates to layeredPane coordinates
Rectangle r = new Rectangle( location, size ); Component layeredPane = rootPane.getLayeredPane();
return r; 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 int debugTitleBarHeight;
private List<Rectangle> debugHitTestSpots;
private Rectangle debugAppIconBounds; private Rectangle debugAppIconBounds;
private Rectangle debugMinimizeButtonBounds; private Rectangle debugMinimizeButtonBounds;
private Rectangle debugMaximizeButtonBounds; private Rectangle debugMaximizeButtonBounds;
@@ -1041,7 +1159,7 @@ public class FlatTitlePane
} else if( borderColor != null && (rootPane.getJMenuBar() == null || !rootPane.getJMenuBar().isVisible()) ) } else if( borderColor != null && (rootPane.getJMenuBar() == null || !rootPane.getJMenuBar().isVisible()) )
insets.bottom += UIScale.scale( 1 ); insets.bottom += UIScale.scale( 1 );
if( !SystemInfo.isWindows_11_orLater && hasNativeCustomDecoration() && !isWindowMaximized() ) if( isWindowTopBorderNeeded() && !isWindowMaximized() )
insets = FlatUIUtils.addInsets( insets, WindowTopBorder.getInstance().getBorderInsets() ); insets = FlatUIUtils.addInsets( insets, WindowTopBorder.getInstance().getBorderInsets() );
return insets; return insets;
@@ -1060,7 +1178,7 @@ public class FlatTitlePane
FlatUIUtils.paintFilledRectangle( g, borderColor, x, y + height - lineHeight, width, lineHeight ); FlatUIUtils.paintFilledRectangle( g, borderColor, x, y + height - lineHeight, width, lineHeight );
} }
if( !SystemInfo.isWindows_11_orLater && hasNativeCustomDecoration() && !isWindowMaximized() ) if( isWindowTopBorderNeeded() && !isWindowMaximized() && !isFullWindowContent() )
WindowTopBorder.getInstance().paintBorder( c, g, x, y, width, height ); WindowTopBorder.getInstance().paintBorder( c, g, x, y, width, height );
} }
@@ -1116,7 +1234,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 iconTextGap = 0;
int iconWidthAndGap = 0; int iconWidthAndGap = 0;
if( icon != null ) { if( icon != null ) {
@@ -1125,7 +1243,7 @@ public class FlatTitlePane
iconWidthAndGap = icon.getIconWidth() + iconTextGap; 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, String clippedText = SwingUtilities.layoutCompoundLabel( label, fontMetrics, text, icon,
label.getVerticalAlignment(), label.getHorizontalAlignment(), label.getVerticalAlignment(), label.getHorizontalAlignment(),
label.getVerticalTextPosition(), label.getHorizontalTextPosition(), label.getVerticalTextPosition(), label.getHorizontalTextPosition(),
@@ -1224,10 +1342,7 @@ public class FlatTitlePane
activeChanged( true ); activeChanged( true );
updateNativeTitleBarHeightAndHitTestSpots(); updateNativeTitleBarHeightAndHitTestSpots();
if( !SystemInfo.isWindows_11_orLater && hasNativeCustomDecoration() ) repaintBorder();
WindowTopBorder.getInstance().repaintBorder( FlatTitlePane.this );
repaintWindowBorder();
} }
@Override @Override
@@ -1235,10 +1350,22 @@ public class FlatTitlePane
activeChanged( false ); activeChanged( false );
updateNativeTitleBarHeightAndHitTestSpots(); updateNativeTitleBarHeightAndHitTestSpots();
if( !SystemInfo.isWindows_11_orLater && hasNativeCustomDecoration() ) repaintBorder();
}
private void repaintBorder() {
// Windows 10 top border
if( windowTopBorderLayer != null && windowTopBorderLayer.isShowing())
WindowTopBorder.getInstance().repaintBorder( windowTopBorderLayer );
else if( isWindowTopBorderNeeded() && !isWindowMaximized() && !isFullWindowContent() )
WindowTopBorder.getInstance().repaintBorder( FlatTitlePane.this ); WindowTopBorder.getInstance().repaintBorder( FlatTitlePane.this );
repaintWindowBorder(); // Window border used for non-native window decorations
if( rootPane.getBorder() instanceof FlatRootPaneUI.FlatWindowBorder ) {
// not repainting four areas on the four sides because RepaintManager
// unions dirty regions, which also results in repaint of whole rootpane
rootPane.repaint();
}
} }
@Override @Override
@@ -1275,7 +1402,7 @@ debug*/
} }
if( e.getClickCount() == 2 && SwingUtilities.isLeftMouseButton( e ) ) { 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 // double-click on icon closes window
close(); close();
} else if( !hasNativeCustomDecoration() ) { } else if( !hasNativeCustomDecoration() ) {
@@ -1302,7 +1429,7 @@ debug*/
if( !SwingUtilities.isLeftMouseButton( e ) ) if( !SwingUtilities.isLeftMouseButton( e ) )
return; return;
dragOffset = SwingUtilities.convertPoint( FlatTitlePane.this, e.getPoint(), window ); dragOffset = SwingUtilities.convertPoint( mouseLayer, e.getPoint(), window );
linuxNativeMove = false; linuxNativeMove = false;
// on Linux, move or maximize/restore window // on Linux, move or maximize/restore window
@@ -1415,4 +1542,27 @@ debug*/
@Override public void componentMoved( ComponentEvent e ) {} @Override public void componentMoved( ComponentEvent e ) {}
@Override public void componentHidden( 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

@@ -26,6 +26,7 @@ import javax.swing.*;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException; import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
/** /**
@@ -159,14 +160,14 @@ public class FlatToggleButtonUI
b.revalidate(); b.revalidate();
} }
b.repaint(); HiDPIUtils.repaint( b );
break; break;
case TAB_BUTTON_UNDERLINE_PLACEMENT: case TAB_BUTTON_UNDERLINE_PLACEMENT:
case TAB_BUTTON_UNDERLINE_HEIGHT: case TAB_BUTTON_UNDERLINE_HEIGHT:
case TAB_BUTTON_UNDERLINE_COLOR: case TAB_BUTTON_UNDERLINE_COLOR:
case TAB_BUTTON_SELECTED_BACKGROUND: case TAB_BUTTON_SELECTED_BACKGROUND:
b.repaint(); HiDPIUtils.repaint( b );
break; break;
} }
} }

View File

@@ -36,6 +36,7 @@ import javax.swing.plaf.basic.BasicToolBarSeparatorUI;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
/** /**
@@ -131,7 +132,7 @@ public class FlatToolBarSeparatorUI
} else } else
installStyle( s ); installStyle( s );
s.revalidate(); s.revalidate();
s.repaint(); HiDPIUtils.repaint( s );
break; break;
} }
} }

View File

@@ -47,6 +47,7 @@ import javax.swing.plaf.basic.BasicToolBarUI;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
@@ -82,7 +83,7 @@ import com.formdev.flatlaf.util.UIScale;
*/ */
public class FlatToolBarUI public class FlatToolBarUI
extends BasicToolBarUI extends BasicToolBarUI
implements StyleableUI implements StyleableUI, FlatTitlePane.TitleBarCaptionHitTest
{ {
/** @since 1.4 */ @Styleable protected boolean focusableButtons; /** @since 1.4 */ @Styleable protected boolean focusableButtons;
/** @since 2 */ @Styleable protected boolean arrowKeysOnlyNavigation; /** @since 2 */ @Styleable protected boolean arrowKeysOnlyNavigation;
@@ -443,7 +444,7 @@ public class FlatToolBarUI
// repaint button group // repaint button group
if( gr != null ) if( gr != null )
toolBar.repaint( gr ); HiDPIUtils.repaint(toolBar, gr );
} }
private ButtonGroup getButtonGroup( AbstractButton b ) { private ButtonGroup getButtonGroup( AbstractButton b ) {
@@ -453,6 +454,15 @@ public class FlatToolBarUI
: null; : 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 ------------------------------ //---- class FlatToolBarFocusTraversalPolicy ------------------------------
/** /**

View File

@@ -61,7 +61,7 @@ public class FlatToolTipUI
super.installUI( c ); super.installUI( c );
// update HTML renderer if necessary // update HTML renderer if necessary
FlatLabelUI.updateHTMLRenderer( c, ((JToolTip)c).getTipText(), false ); FlatHTML.updateRendererCSSFontBaseSize( c );
} }
@Override @Override
@@ -81,11 +81,7 @@ public class FlatToolTipUI
/** @since 2.0.1 */ /** @since 2.0.1 */
@Override @Override
public void propertyChange( PropertyChangeEvent e ) { public void propertyChange( PropertyChangeEvent e ) {
String name = e.getPropertyName(); FlatHTML.propertyChange( e );
if( name == "tiptext" || name == "font" || name == "foreground" ) {
JToolTip toolTip = (JToolTip) e.getSource();
FlatLabelUI.updateHTMLRenderer( toolTip, toolTip.getTipText(), false );
}
} }
@Override @Override

View File

@@ -47,6 +47,7 @@ import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreePath; import javax.swing.tree.TreePath;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
@@ -310,7 +311,7 @@ public class FlatTreeUI
switch( e.getPropertyName() ) { switch( e.getPropertyName() ) {
case TREE_WIDE_SELECTION: case TREE_WIDE_SELECTION:
case TREE_PAINT_SELECTION: case TREE_PAINT_SELECTION:
tree.repaint(); HiDPIUtils.repaint( tree );
break; break;
case "dropLocation": case "dropLocation":
@@ -325,7 +326,7 @@ public class FlatTreeUI
case STYLE_CLASS: case STYLE_CLASS:
installStyle(); installStyle();
tree.revalidate(); tree.revalidate();
tree.repaint(); HiDPIUtils.repaint( tree );
break; break;
case "enabled": case "enabled":
@@ -353,7 +354,7 @@ public class FlatTreeUI
Rectangle r = tree.getPathBounds( loc.getPath() ); Rectangle r = tree.getPathBounds( loc.getPath() );
if( r != null ) if( r != null )
tree.repaint( 0, r.y, tree.getWidth(), r.height ); HiDPIUtils.repaint( tree, 0, r.y, tree.getWidth(), r.height );
} }
@Override @Override
@@ -370,14 +371,14 @@ public class FlatTreeUI
{ {
if( changedPaths.length > 4 ) { if( changedPaths.length > 4 ) {
// same is done in BasicTreeUI.Handler.valueChanged() // same is done in BasicTreeUI.Handler.valueChanged()
tree.repaint(); HiDPIUtils.repaint( tree );
} else { } else {
int arc = (int) Math.ceil( UIScale.scale( selectionArc / 2f ) ); int arc = (int) Math.ceil( UIScale.scale( selectionArc / 2f ) );
for( TreePath path : changedPaths ) { for( TreePath path : changedPaths ) {
Rectangle r = getPathBounds( tree, path ); Rectangle r = getPathBounds( tree, path );
if( r != null ) if( r != null )
tree.repaint( r.x, r.y - arc, r.width, r.height + (arc * 2) ); HiDPIUtils.repaint( tree, r.x, r.y - arc, r.width, r.height + (arc * 2) );
} }
} }
} }

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.border.CompoundBorder;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource; import javax.swing.plaf.UIResource;
import javax.swing.tree.DefaultTreeCellEditor;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatIntelliJLaf; import com.formdev.flatlaf.FlatIntelliJLaf;
import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.FlatLaf;
@@ -123,6 +124,11 @@ public class FlatUIUtils
dest.right = src.right; dest.right = src.right;
} }
/** @since 3.5 */
public static boolean isInsetsEmpty( Insets insets ) {
return insets.top == 0 && insets.left == 0 && insets.bottom == 0 && insets.right == 0;
}
public static Color getUIColor( String key, int defaultColorRGB ) { public static Color getUIColor( String key, int defaultColorRGB ) {
Color color = UIManager.getColor( key ); Color color = UIManager.getColor( key );
return (color != null) ? color : new Color( defaultColorRGB ); return (color != null) ? color : new Color( defaultColorRGB );
@@ -304,6 +310,10 @@ public class FlatUIUtils
if( parent instanceof JTable && ((JTable)parent).getEditorComponent() == c ) if( parent instanceof JTable && ((JTable)parent).getEditorComponent() == c )
return true; return true;
// check whether used as tree cell editor
if( parent instanceof DefaultTreeCellEditor.EditorContainer )
return true;
// check whether used as cell editor // check whether used as cell editor
// Table.editor is set in JTable.GenericEditor constructor // Table.editor is set in JTable.GenericEditor constructor
// Tree.cellEditor is set in sun.swing.FilePane.editFileName() // Tree.cellEditor is set in sun.swing.FilePane.editFileName()
@@ -596,28 +606,55 @@ public class FlatUIUtils
public static void paintOutlinedComponent( Graphics2D g, int x, int y, int width, int height, public static void paintOutlinedComponent( Graphics2D g, int x, int y, int width, int height,
float focusWidth, float focusWidthFraction, float focusInnerWidth, float borderWidth, float arc, float focusWidth, float focusWidthFraction, float focusInnerWidth, float borderWidth, float arc,
Paint focusColor, Paint borderColor, Paint background ) Paint focusColor, Paint borderColor, Paint background )
{
paintOutlinedComponent( g, x, y, width, height, focusWidth, focusWidthFraction, focusInnerWidth,
borderWidth, arc, focusColor, borderColor, background, false );
}
static void paintOutlinedComponent( Graphics2D g, int x, int y, int width, int height,
float focusWidth, float focusWidthFraction, float focusInnerWidth, float borderWidth, float arc,
Paint focusColor, Paint borderColor, Paint background, boolean scrollPane )
{ {
double systemScaleFactor = UIScale.getSystemScaleFactor( g ); double systemScaleFactor = UIScale.getSystemScaleFactor( g );
if( systemScaleFactor != 1 && systemScaleFactor != 2 ) { if( (int) systemScaleFactor != systemScaleFactor ) {
// paint at scale 1x to avoid clipping on right and bottom edges at 125%, 150% or 175% // paint at scale 1x to avoid clipping on right and bottom edges at 125%, 150% or 175%
HiDPIUtils.paintAtScale1x( g, x, y, width, height, HiDPIUtils.paintAtScale1x( g, x, y, width, height,
(g2d, x2, y2, width2, height2, scaleFactor) -> { (g2d, x2, y2, width2, height2, scaleFactor) -> {
paintOutlinedComponentImpl( g2d, x2, y2, width2, height2, paintOutlinedComponentImpl( g2d, x2, y2, width2, height2,
(float) (focusWidth * scaleFactor), focusWidthFraction, (float) (focusInnerWidth * scaleFactor), (float) (focusWidth * scaleFactor), focusWidthFraction, (float) (focusInnerWidth * scaleFactor),
(float) (borderWidth * scaleFactor), (float) (arc * scaleFactor), (float) (borderWidth * scaleFactor), (float) (arc * scaleFactor),
focusColor, borderColor, background ); focusColor, borderColor, background, scrollPane, scaleFactor );
} ); } );
return; return;
} }
paintOutlinedComponentImpl( g, x, y, width, height, focusWidth, focusWidthFraction, focusInnerWidth, paintOutlinedComponentImpl( g, x, y, width, height, focusWidth, focusWidthFraction, focusInnerWidth,
borderWidth, arc, focusColor, borderColor, background ); borderWidth, arc, focusColor, borderColor, background, scrollPane, systemScaleFactor );
} }
@SuppressWarnings( "SelfAssignment" ) // Error Prone
private static void paintOutlinedComponentImpl( Graphics2D g, int x, int y, int width, int height, private static void paintOutlinedComponentImpl( Graphics2D g, int x, int y, int width, int height,
float focusWidth, float focusWidthFraction, float focusInnerWidth, float borderWidth, float arc, float focusWidth, float focusWidthFraction, float focusInnerWidth, float borderWidth, float arc,
Paint focusColor, Paint borderColor, Paint background ) Paint focusColor, Paint borderColor, Paint background, boolean scrollPane, double scaleFactor )
{ {
// Special handling for scrollpane and fractional scale factors (e.g. 1.25 - 1.75),
// where Swing scales one "logical" pixel (border insets) to either one or two physical pixels.
// Antialiasing is used to paint the border, which usually needs two physical pixels
// at small scale factors. 1px for the solid border and another 1px for antialiasing.
// But scrollpane view is painted over the border, which results in a painted border
// that is 1px thick at some sides and 2px thick at other sides.
if( scrollPane && scaleFactor != (int) scaleFactor ) {
if( focusWidth > 0 ) {
// reduce outer border thickness (focusWidth) so that inner side of
// component border (focusWidth + borderWidth) is at a full pixel
int totalWidth = (int) (focusWidth + borderWidth);
focusWidth = totalWidth - borderWidth;
} else {// if( scaleFactor > 1 && scaleFactor < 2 ) {
// reduce component border thickness (borderWidth) to full pixels
borderWidth = (int) borderWidth;
}
}
// outside bounds of the border and the background // outside bounds of the border and the background
float x1 = x + focusWidth; float x1 = x + focusWidth;
float y1 = y + focusWidth; float y1 = y + focusWidth;
@@ -775,7 +812,7 @@ public class FlatUIUtils
if( arcTopLeft > 0 || arcTopRight > 0 || arcBottomLeft > 0 || arcBottomRight > 0 ) { if( arcTopLeft > 0 || arcTopRight > 0 || arcBottomLeft > 0 || arcBottomRight > 0 ) {
double systemScaleFactor = UIScale.getSystemScaleFactor( g ); double systemScaleFactor = UIScale.getSystemScaleFactor( g );
if( systemScaleFactor != 1 && systemScaleFactor != 2 ) { if( systemScaleFactor != (int) systemScaleFactor ) {
// paint at scale 1x to avoid clipping on right and bottom edges at 125%, 150% or 175% // paint at scale 1x to avoid clipping on right and bottom edges at 125%, 150% or 175%
HiDPIUtils.paintAtScale1x( g, x, y, width, height, HiDPIUtils.paintAtScale1x( g, x, y, width, height,
(g2d, x2, y2, width2, height2, scaleFactor) -> { (g2d, x2, y2, width2, height2, scaleFactor) -> {
@@ -1310,13 +1347,13 @@ debug*/
@Override @Override
public void focusGained( FocusEvent e ) { public void focusGained( FocusEvent e ) {
if( repaintCondition == null || repaintCondition.test( repaintComponent ) ) if( repaintCondition == null || repaintCondition.test( repaintComponent ) )
repaintComponent.repaint(); HiDPIUtils.repaint( repaintComponent );
} }
@Override @Override
public void focusLost( FocusEvent e ) { public void focusLost( FocusEvent e ) {
if( repaintCondition == null || repaintCondition.test( repaintComponent ) ) if( repaintCondition == null || repaintCondition.test( repaintComponent ) )
repaintComponent.repaint(); HiDPIUtils.repaint( repaintComponent );
} }
} }

View File

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

@@ -16,6 +16,7 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import java.io.File;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType; import java.lang.invoke.MethodType;
@@ -25,6 +26,7 @@ import javax.swing.JList;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.JTable; import javax.swing.JTable;
import javax.swing.JTree; import javax.swing.JTree;
import javax.swing.filechooser.FileSystemView;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.text.JTextComponent; import javax.swing.text.JTextComponent;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
@@ -88,4 +90,64 @@ public class JavaCompatibility2
return null; 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

@@ -16,14 +16,17 @@
package com.formdev.flatlaf.util; package com.formdev.flatlaf.util;
import java.awt.Component;
import java.awt.Font; import java.awt.Font;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.font.GlyphVector; import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform; import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.text.AttributedCharacterIterator; import java.text.AttributedCharacterIterator;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.RepaintManager;
import com.formdev.flatlaf.FlatSystemProperties; import com.formdev.flatlaf.FlatSystemProperties;
/** /**
@@ -192,7 +195,8 @@ public class HiDPIUtils
case "Inter": case "Inter":
case "Inter Light": case "Inter Light":
case "Inter Semi Bold": case "Inter Semi Bold": // Inter v3
case "Inter SemiBold": // Inter v4
case "Roboto": case "Roboto":
case "Roboto Light": case "Roboto Light":
case "Roboto Medium": case "Roboto Medium":
@@ -321,4 +325,243 @@ public class HiDPIUtils
} }
}; };
} }
/**
* Repaints the given component.
* <p>
* See {@link #repaint(Component, int, int, int, int)} for more details.
*
* @since 3.5
*/
public static void repaint( Component c ) {
repaint( c, 0, 0, c.getWidth(), c.getHeight() );
}
/**
* Repaints the given component area.
* <p>
* See {@link #repaint(Component, int, int, int, int)} for more details.
*
* @since 3.5
*/
public static void repaint( Component c, Rectangle r ) {
repaint( c, r.x, r.y, r.width, r.height );
}
/**
* Repaints the given component area.
* <p>
* Invokes {@link Component#repaint(int, int, int, int)} on the given component,
* <p>
* Use this method, instead of {@code Component.repaint(...)},
* to fix a problem in Swing when using scale factors that end on .25 or .75
* (e.g. 1.25, 1.75, 2.25, etc) and repainting single components, which may not
* repaint right and/or bottom 1px edge of component.
* <p>
* The problem may occur under following conditions:
* <ul>
* <li>using Java 9 or later
* <li>system scale factor is 125%, 175%, 225%, ...
* (Windows only; Java on macOS and Linux does not support fractional scale factors)
* <li>repaint whole component or right/bottom area of component
* <li>component is opaque; or component is contained in a opaque container
* that has same right/bottom bounds as component
* <li>component has bounds that Java/Swing scales different when repainting components
* </ul>
*
* @since 3.5
*/
public static void repaint( Component c, int x, int y, int width, int height ) {
// repaint given component area
// Always invoke repaint() on given component, even if also invoked (below)
// on one of its ancestors, for the case that component overrides that method.
// Also RepaintManager "merges" the two repaints into one.
c.repaint( x, y, width, height );
if( RepaintManager.currentManager( c ) instanceof HiDPIRepaintManager )
return;
// if necessary, also repaint given area in first ancestor that is larger than component
// to avoid clipping issue (see needsSpecialRepaint())
if( needsSpecialRepaint( c, x, y, width, height ) ) {
int x2 = x + c.getX();
int y2 = y + c.getY();
for( Component p = c.getParent(); p != null; p = p.getParent() ) {
x2 += p.getX();
y2 += p.getY();
if( x2 + width < p.getWidth() && y2 + height < p.getHeight() ) {
p.repaint( x2, y2, width, height );
break;
}
}
}
}
/**
* There is a problem in Swing, when using scale factors that end on .25 or .75
* (e.g. 1.25, 1.75, 2.25, etc) and repainting single components, which may not
* repaint right and/or bottom 1px edge of component.
* <p>
* The component is first painted to an in-memory image,
* and then that image is copied to the screen.
* See {@code javax.swing.RepaintManager.PaintManager#paintDoubleBufferedFPScales()}.
* <p>
* There are two clipping rectangles involved when copying the image to the screen:
* {@code sun.java2d.SunGraphics2D#devClip} and
* {@code sun.java2d.SunGraphics2D#usrClip}.
* <p>
* {@code devClip} is the device clipping in physical pixels.
* It gets the bounds of the painting component, which is either the passed component,
* or if it is non-opaque, then the first opaque ancestor of the passed component.
* It is calculated in {@code sun.java2d.SunGraphics2D#constrain()} while
* getting a graphics context via {@link JComponent#getGraphics()}.
* <p>
* {@code usrClip} is the user clipping, which is set via {@link Graphics} clipping methods.
* This is done in {@code javax.swing.RepaintManager.PaintManager#paintDoubleBufferedFPScales()}.
* <p>
* The intersection of {@code devClip} and {@code usrClip}
* (computed in {@code sun.java2d.SunGraphics2D#validateCompClip()})
* is used to copy the image to the screen.
* <p>
* Unfortunately different scaling/rounding strategies are used to calculate
* the two clipping rectangles, which is the reason of the issue.
* <p>
* {@code devClip} (see {@code sun.java2d.SunGraphics2D#constrain()}):
* <pre>{@code
* int devX = (int) (x * scale);
* int devWidth = Math.round( width * scale )
* }</pre>
* {@code usrClip} (see {@code javax.swing.RepaintManager.PaintManager#paintDoubleBufferedFPScales()}):
* <pre>{@code
* int usrX = (int) Math.ceil( (x * scale) - 0.5 );
* int usrWidth = ((int) Math.ceil( ((x + width) * scale) - 0.5 )) - usrX;
* }</pre>
* X/Y coordinates are always rounded down for {@code devClip}, but rounded up for {@code usrClip}.
* Width/height calculation is also different.
*/
private static boolean needsSpecialRepaint( Component c, int x, int y, int width, int height ) {
// no special repaint necessary for Java 8 or for macOS and Linux
// (Java on those platforms does not support fractional scale factors)
if( !SystemInfo.isJava_9_orLater || !SystemInfo.isWindows )
return false;
// check whether repaint area is empty or no component given
// (same checks as in javax.swing.RepaintManager.addDirtyRegion0())
if( width <= 0 || height <= 0 || c == null )
return false;
// check whether component has zero size
// (same checks as in javax.swing.RepaintManager.addDirtyRegion0())
int compWidth = c.getWidth();
int compHeight = c.getHeight();
if( compWidth <= 0 || compHeight <= 0 )
return false;
// check whether repaint area does span to right or bottom component edges
// (in this case, {@code devClip} is always larger than {@code usrClip})
if( x + width < compWidth && y + height < compHeight )
return false;
// if component is not opaque, Swing uses the first opaque ancestor for painting
if( !c.isOpaque() ) {
int x2 = x;
int y2 = y;
for( Component p = c.getParent(); p != null; p = p.getParent() ) {
x2 += p.getX();
y2 += p.getY();
if( p.isOpaque() ) {
// check whether repaint area does span to right or bottom edges
// of the opaque ancestor component
// (in this case, {@code devClip} is always larger than {@code usrClip})
if( x2 + width < p.getWidth() && y2 + height < p.getHeight() )
return false;
break;
}
}
}
// check whether Special repaint is necessary for current scale factor
// (doing this check late because it temporary allocates some memory)
double scaleFactor = UIScale.getSystemScaleFactor( c.getGraphicsConfiguration() );
double fraction = scaleFactor - (int) scaleFactor;
if( fraction == 0 || fraction == 0.5 )
return false;
return true;
}
/**
* Installs a {@link HiDPIRepaintManager} on Windows when running in Java 9+,
* but only if default repaint manager is currently installed.
* <p>
* Invoke once on application startup.
* Compatible with all/other LaFs.
*
* @since 3.5
*/
public static void installHiDPIRepaintManager() {
if( !SystemInfo.isJava_9_orLater || !SystemInfo.isWindows )
return;
RepaintManager manager = RepaintManager.currentManager( (Component) null );
if( manager.getClass() == RepaintManager.class )
RepaintManager.setCurrentManager( new HiDPIRepaintManager() );
}
/**
* Similar to {@link #repaint(Component, int, int, int, int)},
* but invokes callback instead of invoking {@link Component#repaint(int, int, int, int)}.
* <p>
* For use in custom repaint managers.
*
* @since 3.5
*/
public static void addDirtyRegion( JComponent c, int x, int y, int width, int height, DirtyRegionCallback callback ) {
if( needsSpecialRepaint( c, x, y, width, height ) ) {
int x2 = x + c.getX();
int y2 = y + c.getY();
for( Component p = c.getParent(); p != null; p = p.getParent() ) {
if( x2 + width < p.getWidth() && y2 + height < p.getHeight() && p instanceof JComponent ) {
callback.addDirtyRegion( (JComponent) p, x2, y2, width, height );
return;
}
x2 += p.getX();
y2 += p.getY();
}
}
callback.addDirtyRegion( c, x, y, width, height );
}
//---- interface DirtyRegionCallback --------------------------------------
/**
* For {@link HiDPIUtils#addDirtyRegion(JComponent, int, int, int, int, DirtyRegionCallback)}.
*
* @since 3.5
*/
public interface DirtyRegionCallback {
void addDirtyRegion( JComponent c, int x, int y, int w, int h );
}
//---- class HiDPIRepaintManager ------------------------------------------
/**
* A repaint manager that fixes a problem in Swing when repainting components
* at some scale factors (e.g. 125%, 175%, etc) on Windows.
* <p>
* Use {@link HiDPIUtils#installHiDPIRepaintManager()} to install it.
* <p>
* See {@link HiDPIUtils#repaint(Component, int, int, int, int)} for details.
*
* @since 3.5
*/
public static class HiDPIRepaintManager
extends RepaintManager
{
@Override
public void addDirtyRegion( JComponent c, int x, int y, int w, int h ) {
HiDPIUtils.addDirtyRegion( c, x, y, w, h, super::addDirtyRegion );
}
}
} }

View File

@@ -739,6 +739,8 @@ Table.rowHeight = 20
Table.showHorizontalLines = false Table.showHorizontalLines = false
Table.showVerticalLines = false Table.showVerticalLines = false
Table.showTrailingVerticalLine = false Table.showTrailingVerticalLine = false
Table.paintOutsideAlternateRows = false
Table.editorSelectAllOnStartEditing = true
Table.consistentHomeEndKeyBehavior = true Table.consistentHomeEndKeyBehavior = true
Table.intercellSpacing = 0,0 Table.intercellSpacing = 0,0
Table.scrollPaneBorder = com.formdev.flatlaf.ui.FlatScrollPaneBorder Table.scrollPaneBorder = com.formdev.flatlaf.ui.FlatScrollPaneBorder
@@ -829,7 +831,6 @@ TitlePane.centerTitleIfMenuBarEmbedded = true
TitlePane.showIconBesideTitle = false TitlePane.showIconBesideTitle = false
TitlePane.menuBarTitleGap = 40 TitlePane.menuBarTitleGap = 40
TitlePane.menuBarTitleMinimumGap = 12 TitlePane.menuBarTitleMinimumGap = 12
TitlePane.menuBarResizeHeight = 4
TitlePane.closeIcon = com.formdev.flatlaf.icons.FlatWindowCloseIcon TitlePane.closeIcon = com.formdev.flatlaf.icons.FlatWindowCloseIcon
TitlePane.iconifyIcon = com.formdev.flatlaf.icons.FlatWindowIconifyIcon TitlePane.iconifyIcon = com.formdev.flatlaf.icons.FlatWindowIconifyIcon
TitlePane.maximizeIcon = com.formdev.flatlaf.icons.FlatWindowMaximizeIcon TitlePane.maximizeIcon = com.formdev.flatlaf.icons.FlatWindowMaximizeIcon

View File

@@ -84,10 +84,12 @@ public class TestUIDefaultsLoader
void parseBorders() { void parseBorders() {
Insets insets = new Insets( 1,2,3,4 ); Insets insets = new Insets( 1,2,3,4 );
assertBorderEquals( new FlatEmptyBorder( insets ), "1,2,3,4" ); assertBorderEquals( new FlatEmptyBorder( insets ), "1,2,3,4" );
assertBorderEquals( new FlatEmptyBorder( insets ), "1,2,3,4,,," );
assertBorderEquals( new FlatLineBorder( insets, Color.red ), "1,2,3,4,#f00" ); assertBorderEquals( new FlatLineBorder( insets, Color.red ), "1,2,3,4,#f00" );
assertBorderEquals( new FlatLineBorder( insets, Color.red, 2.5f, 0 ), "1,2,3,4,#f00,2.5" ); assertBorderEquals( new FlatLineBorder( insets, Color.red, 2.5f, -1 ), "1,2,3,4,#f00,2.5" );
assertBorderEquals( new FlatLineBorder( insets, Color.red, 2.5f, 6 ), "1,2,3,4,#f00,2.5,6" ); assertBorderEquals( new FlatLineBorder( insets, Color.red, 2.5f, 6 ), "1,2,3,4,#f00,2.5,6" );
assertBorderEquals( new FlatLineBorder( insets, Color.red, 1, 6 ), "1,2,3,4,#f00,,6" ); assertBorderEquals( new FlatLineBorder( insets, Color.red, 1, 6 ), "1,2,3,4,#f00,,6" );
assertBorderEquals( new FlatLineBorder( insets, null, 1, 6 ), "1,2,3,4,,,6" );
} }
private void assertBorderEquals( Border expected, String actualStyle ) { private void assertBorderEquals( Border expected, String actualStyle ) {

View File

@@ -253,7 +253,8 @@ public class TestFlatStyleableInfo
FlatLabelUI ui = (FlatLabelUI) c.getUI(); FlatLabelUI ui = (FlatLabelUI) c.getUI();
Map<String, Class<?>> expected = expectedMap( Map<String, Class<?>> expected = expectedMap(
"disabledForeground", Color.class "disabledForeground", Color.class,
"arc", int.class
); );
assertMapEquals( expected, ui.getStyleableInfos( c ) ); assertMapEquals( expected, ui.getStyleableInfos( c ) );
@@ -799,6 +800,8 @@ public class TestFlatStyleableInfo
"selectionForeground", Color.class, "selectionForeground", Color.class,
"selectionInactiveBackground", Color.class, "selectionInactiveBackground", Color.class,
"selectionInactiveForeground", Color.class, "selectionInactiveForeground", Color.class,
"selectionInsets", Insets.class,
"selectionArc", int.class,
// FlatTableCellBorder // FlatTableCellBorder
"cellMargins", Insets.class, "cellMargins", Insets.class,
@@ -993,12 +996,20 @@ public class TestFlatStyleableInfo
"disabledBorderColor", Color.class, "disabledBorderColor", Color.class,
"focusedBorderColor", Color.class, "focusedBorderColor", Color.class,
"hoverBorderColor", Color.class, "hoverBorderColor", Color.class,
"pressedBorderColor", Color.class,
"selectedBorderColor", Color.class,
"disabledSelectedBorderColor", Color.class,
"focusedSelectedBorderColor", Color.class,
"hoverSelectedBorderColor", Color.class,
"pressedSelectedBorderColor", Color.class,
"default.borderWidth", float.class, "default.borderWidth", float.class,
"default.borderColor", Color.class, "default.borderColor", Color.class,
"default.focusedBorderColor", Color.class, "default.focusedBorderColor", Color.class,
"default.focusColor", Color.class, "default.focusColor", Color.class,
"default.hoverBorderColor", Color.class, "default.hoverBorderColor", Color.class,
"default.pressedBorderColor", Color.class,
"toolbar.focusWidth", float.class, "toolbar.focusWidth", float.class,
"toolbar.focusColor", Color.class, "toolbar.focusColor", Color.class,

View File

@@ -305,6 +305,9 @@ public class TestFlatStyleableValue
testColor( c, ui, "buttonPressedArrowColor", 0x123456 ); testColor( c, ui, "buttonPressedArrowColor", 0x123456 );
testColor( c, ui, "popupBackground", 0x123456 ); testColor( c, ui, "popupBackground", 0x123456 );
testInsets( c, ui, "popupInsets", 1,2,3,4 );
testInsets( c, ui, "selectionInsets", 1,2,3,4 );
testInteger( c, ui, "selectionArc", 123 );
// border // border
flatRoundBorder( c, ui ); flatRoundBorder( c, ui );
@@ -355,6 +358,7 @@ public class TestFlatStyleableValue
FlatLabelUI ui = (FlatLabelUI) c.getUI(); FlatLabelUI ui = (FlatLabelUI) c.getUI();
testColor( c, ui, "disabledForeground", 0x123456 ); testColor( c, ui, "disabledForeground", 0x123456 );
testInteger( c, ui, "arc", 123 );
} }
@Test @Test
@@ -366,6 +370,8 @@ public class TestFlatStyleableValue
testColor( c, ui, "selectionForeground", 0x123456 ); testColor( c, ui, "selectionForeground", 0x123456 );
testColor( c, ui, "selectionInactiveBackground", 0x123456 ); testColor( c, ui, "selectionInactiveBackground", 0x123456 );
testColor( c, ui, "selectionInactiveForeground", 0x123456 ); testColor( c, ui, "selectionInactiveForeground", 0x123456 );
testInsets( c, ui, "selectionInsets", 1,2,3,4 );
testInteger( c, ui, "selectionArc", 123 );
// FlatListCellBorder // FlatListCellBorder
testInsets( c, ui, "cellMargins", 1,2,3,4 ); testInsets( c, ui, "cellMargins", 1,2,3,4 );
@@ -801,6 +807,8 @@ public class TestFlatStyleableValue
testColor( c, ui, "selectionForeground", 0x123456 ); testColor( c, ui, "selectionForeground", 0x123456 );
testColor( c, ui, "selectionInactiveBackground", 0x123456 ); testColor( c, ui, "selectionInactiveBackground", 0x123456 );
testColor( c, ui, "selectionInactiveForeground", 0x901324 ); testColor( c, ui, "selectionInactiveForeground", 0x901324 );
testInsets( c, ui, "selectionInsets", 1,2,3,4 );
testInteger( c, ui, "selectionArc", 123 );
// FlatTableCellBorder // FlatTableCellBorder
testInsets( c, ui, "cellMargins", 1,2,3,4 ); testInsets( c, ui, "cellMargins", 1,2,3,4 );
@@ -930,6 +938,8 @@ public class TestFlatStyleableValue
testColor( c, ui, "selectionInactiveBackground", 0x123456 ); testColor( c, ui, "selectionInactiveBackground", 0x123456 );
testColor( c, ui, "selectionInactiveForeground", 0x123456 ); testColor( c, ui, "selectionInactiveForeground", 0x123456 );
testColor( c, ui, "selectionBorderColor", 0x123456 ); testColor( c, ui, "selectionBorderColor", 0x123456 );
testInsets( c, ui, "selectionInsets", 1,2,3,4 );
testInteger( c, ui, "selectionArc", 123 );
testBoolean( c, ui, "wideSelection", true ); testBoolean( c, ui, "wideSelection", true );
testBoolean( c, ui, "showCellFocusIndicator", true ); testBoolean( c, ui, "showCellFocusIndicator", true );
@@ -955,12 +965,20 @@ public class TestFlatStyleableValue
testColor( c, ui, "disabledBorderColor", 0x123456 ); testColor( c, ui, "disabledBorderColor", 0x123456 );
testColor( c, ui, "focusedBorderColor", 0x123456 ); testColor( c, ui, "focusedBorderColor", 0x123456 );
testColor( c, ui, "hoverBorderColor", 0x123456 ); testColor( c, ui, "hoverBorderColor", 0x123456 );
testColor( c, ui, "pressedBorderColor", 0x123456 );
testColor( c, ui, "selectedBorderColor", 0x123456 );
testColor( c, ui, "disabledSelectedBorderColor", 0x123456 );
testColor( c, ui, "focusedSelectedBorderColor", 0x123456 );
testColor( c, ui, "hoverSelectedBorderColor", 0x123456 );
testColor( c, ui, "pressedSelectedBorderColor", 0x123456 );
testFloat( c, ui, "default.borderWidth", 1.23f ); testFloat( c, ui, "default.borderWidth", 1.23f );
testColor( c, ui, "default.borderColor", 0x123456 ); testColor( c, ui, "default.borderColor", 0x123456 );
testColor( c, ui, "default.focusedBorderColor", 0x123456 ); testColor( c, ui, "default.focusedBorderColor", 0x123456 );
testColor( c, ui, "default.focusColor", 0x123456 ); testColor( c, ui, "default.focusColor", 0x123456 );
testColor( c, ui, "default.hoverBorderColor", 0x123456 ); testColor( c, ui, "default.hoverBorderColor", 0x123456 );
testColor( c, ui, "default.pressedBorderColor", 0x123456 );
testFloat( c, ui, "toolbar.focusWidth", 1.23f ); testFloat( c, ui, "toolbar.focusWidth", 1.23f );
testColor( c, ui, "toolbar.focusColor", 0x123456 ); testColor( c, ui, "toolbar.focusColor", 0x123456 );
@@ -1025,12 +1043,20 @@ public class TestFlatStyleableValue
testValue( border, "disabledBorderColor", Color.WHITE ); testValue( border, "disabledBorderColor", Color.WHITE );
testValue( border, "focusedBorderColor", Color.WHITE ); testValue( border, "focusedBorderColor", Color.WHITE );
testValue( border, "hoverBorderColor", Color.WHITE ); testValue( border, "hoverBorderColor", Color.WHITE );
testValue( border, "pressedBorderColor", Color.WHITE );
testValue( border, "selectedBorderColor", Color.WHITE );
testValue( border, "disabledSelectedBorderColor", Color.WHITE );
testValue( border, "focusedSelectedBorderColor", Color.WHITE );
testValue( border, "hoverSelectedBorderColor", Color.WHITE );
testValue( border, "pressedSelectedBorderColor", Color.WHITE );
testValue( border, "default.borderWidth", 2f ); testValue( border, "default.borderWidth", 2f );
testValue( border, "default.borderColor", Color.WHITE ); testValue( border, "default.borderColor", Color.WHITE );
testValue( border, "default.focusedBorderColor", Color.WHITE ); testValue( border, "default.focusedBorderColor", Color.WHITE );
testValue( border, "default.focusColor", Color.WHITE ); testValue( border, "default.focusColor", Color.WHITE );
testValue( border, "default.hoverBorderColor", Color.WHITE ); testValue( border, "default.hoverBorderColor", Color.WHITE );
testValue( border, "default.pressedBorderColor", Color.WHITE );
testValue( border, "toolbar.focusWidth", 1.5f ); testValue( border, "toolbar.focusWidth", 1.5f );
testValue( border, "toolbar.focusColor", Color.WHITE ); testValue( border, "toolbar.focusColor", Color.WHITE );

View File

@@ -412,6 +412,7 @@ public class TestFlatStyling
FlatLabelUI ui = (FlatLabelUI) c.getUI(); FlatLabelUI ui = (FlatLabelUI) c.getUI();
ui.applyStyle( c, "disabledForeground: #fff" ); ui.applyStyle( c, "disabledForeground: #fff" );
ui.applyStyle( c, "arc: 8" );
// JComponent properties // JComponent properties
ui.applyStyle( c, "background: #fff" ); ui.applyStyle( c, "background: #fff" );
@@ -986,6 +987,8 @@ public class TestFlatStyling
ui.applyStyle( "selectionForeground: #fff" ); ui.applyStyle( "selectionForeground: #fff" );
ui.applyStyle( "selectionInactiveBackground: #fff" ); ui.applyStyle( "selectionInactiveBackground: #fff" );
ui.applyStyle( "selectionInactiveForeground: #fff" ); ui.applyStyle( "selectionInactiveForeground: #fff" );
ui.applyStyle( "selectionInsets: 1,2,3,4" );
ui.applyStyle( "selectionArc: 8" );
// FlatTableCellBorder // FlatTableCellBorder
ui.applyStyle( "cellMargins: 1,2,3,4" ); ui.applyStyle( "cellMargins: 1,2,3,4" );
@@ -1294,12 +1297,20 @@ public class TestFlatStyling
border.applyStyleProperty( "disabledBorderColor", Color.WHITE ); border.applyStyleProperty( "disabledBorderColor", Color.WHITE );
border.applyStyleProperty( "focusedBorderColor", Color.WHITE ); border.applyStyleProperty( "focusedBorderColor", Color.WHITE );
border.applyStyleProperty( "hoverBorderColor", Color.WHITE ); border.applyStyleProperty( "hoverBorderColor", Color.WHITE );
border.applyStyleProperty( "pressedBorderColor", Color.WHITE );
border.applyStyleProperty( "selectedBorderColor", Color.WHITE );
border.applyStyleProperty( "disabledSelectedBorderColor", Color.WHITE );
border.applyStyleProperty( "focusedSelectedBorderColor", Color.WHITE );
border.applyStyleProperty( "hoverSelectedBorderColor", Color.WHITE );
border.applyStyleProperty( "pressedSelectedBorderColor", Color.WHITE );
border.applyStyleProperty( "default.borderWidth", 2 ); border.applyStyleProperty( "default.borderWidth", 2 );
border.applyStyleProperty( "default.borderColor", Color.WHITE ); border.applyStyleProperty( "default.borderColor", Color.WHITE );
border.applyStyleProperty( "default.focusedBorderColor", Color.WHITE ); border.applyStyleProperty( "default.focusedBorderColor", Color.WHITE );
border.applyStyleProperty( "default.focusColor", Color.WHITE ); border.applyStyleProperty( "default.focusColor", Color.WHITE );
border.applyStyleProperty( "default.hoverBorderColor", Color.WHITE ); border.applyStyleProperty( "default.hoverBorderColor", Color.WHITE );
border.applyStyleProperty( "default.pressedBorderColor", Color.WHITE );
border.applyStyleProperty( "toolbar.focusWidth", 1.5f ); border.applyStyleProperty( "toolbar.focusWidth", 1.5f );
border.applyStyleProperty( "toolbar.focusColor", Color.WHITE ); border.applyStyleProperty( "toolbar.focusColor", Color.WHITE );

View File

@@ -140,8 +140,8 @@ class ControlBar
registerSwitchToLookAndFeel( KeyEvent.VK_F9, "com.apple.laf.AquaLookAndFeel" ); registerSwitchToLookAndFeel( KeyEvent.VK_F9, "com.apple.laf.AquaLookAndFeel" );
else if( SystemInfo.isLinux ) else if( SystemInfo.isLinux )
registerSwitchToLookAndFeel( KeyEvent.VK_F9, "com.sun.java.swing.plaf.gtk.GTKLookAndFeel" ); registerSwitchToLookAndFeel( KeyEvent.VK_F9, "com.sun.java.swing.plaf.gtk.GTKLookAndFeel" );
registerSwitchToLookAndFeel( KeyEvent.VK_F12, MetalLookAndFeel.class.getName() );
registerSwitchToLookAndFeel( KeyEvent.VK_F11, NimbusLookAndFeel.class.getName() ); registerSwitchToLookAndFeel( KeyEvent.VK_F11, NimbusLookAndFeel.class.getName() );
registerSwitchToLookAndFeel( KeyEvent.VK_F12, MetalLookAndFeel.class.getName() );
// register Alt+UP and Alt+DOWN to switch to previous/next theme // register Alt+UP and Alt+DOWN to switch to previous/next theme
((JComponent)frame.getContentPane()).registerKeyboardAction( ((JComponent)frame.getContentPane()).registerKeyboardAction(
@@ -153,6 +153,9 @@ class ControlBar
KeyStroke.getKeyStroke( KeyEvent.VK_DOWN, KeyEvent.ALT_DOWN_MASK ), KeyStroke.getKeyStroke( KeyEvent.VK_DOWN, KeyEvent.ALT_DOWN_MASK ),
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ); JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT );
// register Alt+Shift+F1, F2, ... keys to change system scale factor
DemoPrefs.registerSystemScaleFactors( frame );
// register ESC key to close frame // register ESC key to close frame
((JComponent)frame.getContentPane()).registerKeyboardAction( ((JComponent)frame.getContentPane()).registerKeyboardAction(
e -> { e -> {

View File

@@ -25,6 +25,8 @@ import javax.swing.*;
import javax.swing.table.*; import javax.swing.table.*;
import javax.swing.tree.*; import javax.swing.tree.*;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.util.ColorFunctions;
import net.miginfocom.swing.*; import net.miginfocom.swing.*;
/** /**
@@ -93,10 +95,12 @@ class DataComponentsPanel
private void rowSelectionChanged() { private void rowSelectionChanged() {
table1.setRowSelectionAllowed( rowSelectionCheckBox.isSelected() ); table1.setRowSelectionAllowed( rowSelectionCheckBox.isSelected() );
roundedSelectionChanged();
} }
private void columnSelectionChanged() { private void columnSelectionChanged() {
table1.setColumnSelectionAllowed( columnSelectionCheckBox.isSelected() ); table1.setColumnSelectionAllowed( columnSelectionCheckBox.isSelected() );
roundedSelectionChanged();
} }
private void showHorizontalLinesChanged() { private void showHorizontalLinesChanged() {
@@ -127,6 +131,28 @@ class DataComponentsPanel
intercellSpacingCheckBox.setSelected( table1.getRowMargin() != 0 ); intercellSpacingCheckBox.setSelected( table1.getRowMargin() != 0 );
} }
private void roundedSelectionChanged() {
String style = null;
if( roundedSelectionCheckBox.isSelected() ) {
style = rowSelectionCheckBox.isSelected()
? "selectionArc: 6; selectionInsets: 0,1,0,1"
: "selectionArc: 6";
}
table1.putClientProperty( FlatClientProperties.STYLE, style );
}
private void alternatingRowsChanged() {
Color alternateRowColor = null;
if( alternatingRowsCheckBox.isSelected() ) {
Color background = table1.getBackground();
alternateRowColor = FlatLaf.isLafDark()
? ColorFunctions.lighten( background, 0.05f )
: ColorFunctions.darken( background, 0.05f );
}
UIManager.put( "Table.alternateRowColor", alternateRowColor );
table1.repaint();
}
@SuppressWarnings( { "unchecked", "rawtypes" } ) @SuppressWarnings( { "unchecked", "rawtypes" } )
private void initComponents() { private void initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
@@ -151,12 +177,14 @@ class DataComponentsPanel
JScrollPane scrollPane5 = new JScrollPane(); JScrollPane scrollPane5 = new JScrollPane();
table1 = new JTable(); table1 = new JTable();
JPanel tableOptionsPanel = new JPanel(); JPanel tableOptionsPanel = new JPanel();
roundedSelectionCheckBox = new JCheckBox();
showHorizontalLinesCheckBox = new JCheckBox(); showHorizontalLinesCheckBox = new JCheckBox();
showVerticalLinesCheckBox = new JCheckBox(); showVerticalLinesCheckBox = new JCheckBox();
intercellSpacingCheckBox = new JCheckBox(); intercellSpacingCheckBox = new JCheckBox();
redGridColorCheckBox = new JCheckBox(); redGridColorCheckBox = new JCheckBox();
rowSelectionCheckBox = new JCheckBox(); rowSelectionCheckBox = new JCheckBox();
columnSelectionCheckBox = new JCheckBox(); columnSelectionCheckBox = new JCheckBox();
alternatingRowsCheckBox = new JCheckBox();
dndCheckBox = new JCheckBox(); dndCheckBox = new JCheckBox();
JPopupMenu popupMenu2 = new JPopupMenu(); JPopupMenu popupMenu2 = new JPopupMenu();
JMenuItem menuItem3 = new JMenuItem(); JMenuItem menuItem3 = new JMenuItem();
@@ -403,44 +431,56 @@ class DataComponentsPanel
"[]0" + "[]0" +
"[]0" + "[]0" +
"[]0" + "[]0" +
"[]0" +
"[]0" +
"[]0")); "[]0"));
//---- roundedSelectionCheckBox ----
roundedSelectionCheckBox.setText("rounded selection");
roundedSelectionCheckBox.addActionListener(e -> roundedSelectionChanged());
tableOptionsPanel.add(roundedSelectionCheckBox, "cell 0 0");
//---- showHorizontalLinesCheckBox ---- //---- showHorizontalLinesCheckBox ----
showHorizontalLinesCheckBox.setText("show horizontal lines"); showHorizontalLinesCheckBox.setText("show horizontal lines");
showHorizontalLinesCheckBox.addActionListener(e -> showHorizontalLinesChanged()); showHorizontalLinesCheckBox.addActionListener(e -> showHorizontalLinesChanged());
tableOptionsPanel.add(showHorizontalLinesCheckBox, "cell 0 0"); tableOptionsPanel.add(showHorizontalLinesCheckBox, "cell 0 1");
//---- showVerticalLinesCheckBox ---- //---- showVerticalLinesCheckBox ----
showVerticalLinesCheckBox.setText("show vertical lines"); showVerticalLinesCheckBox.setText("show vertical lines");
showVerticalLinesCheckBox.addActionListener(e -> showVerticalLinesChanged()); showVerticalLinesCheckBox.addActionListener(e -> showVerticalLinesChanged());
tableOptionsPanel.add(showVerticalLinesCheckBox, "cell 0 1"); tableOptionsPanel.add(showVerticalLinesCheckBox, "cell 0 2");
//---- intercellSpacingCheckBox ---- //---- intercellSpacingCheckBox ----
intercellSpacingCheckBox.setText("intercell spacing"); intercellSpacingCheckBox.setText("intercell spacing");
intercellSpacingCheckBox.addActionListener(e -> intercellSpacingChanged()); intercellSpacingCheckBox.addActionListener(e -> intercellSpacingChanged());
tableOptionsPanel.add(intercellSpacingCheckBox, "cell 0 2"); tableOptionsPanel.add(intercellSpacingCheckBox, "cell 0 3");
//---- redGridColorCheckBox ---- //---- redGridColorCheckBox ----
redGridColorCheckBox.setText("red grid color"); redGridColorCheckBox.setText("red grid color");
redGridColorCheckBox.addActionListener(e -> redGridColorChanged()); redGridColorCheckBox.addActionListener(e -> redGridColorChanged());
tableOptionsPanel.add(redGridColorCheckBox, "cell 0 3"); tableOptionsPanel.add(redGridColorCheckBox, "cell 0 4");
//---- rowSelectionCheckBox ---- //---- rowSelectionCheckBox ----
rowSelectionCheckBox.setText("row selection"); rowSelectionCheckBox.setText("row selection");
rowSelectionCheckBox.setSelected(true); rowSelectionCheckBox.setSelected(true);
rowSelectionCheckBox.addActionListener(e -> rowSelectionChanged()); rowSelectionCheckBox.addActionListener(e -> rowSelectionChanged());
tableOptionsPanel.add(rowSelectionCheckBox, "cell 0 4"); tableOptionsPanel.add(rowSelectionCheckBox, "cell 0 5");
//---- columnSelectionCheckBox ---- //---- columnSelectionCheckBox ----
columnSelectionCheckBox.setText("column selection"); columnSelectionCheckBox.setText("column selection");
columnSelectionCheckBox.addActionListener(e -> columnSelectionChanged()); columnSelectionCheckBox.addActionListener(e -> columnSelectionChanged());
tableOptionsPanel.add(columnSelectionCheckBox, "cell 0 5"); tableOptionsPanel.add(columnSelectionCheckBox, "cell 0 6");
//---- alternatingRowsCheckBox ----
alternatingRowsCheckBox.setText("alternating rows");
alternatingRowsCheckBox.addActionListener(e -> alternatingRowsChanged());
tableOptionsPanel.add(alternatingRowsCheckBox, "cell 0 7");
//---- dndCheckBox ---- //---- dndCheckBox ----
dndCheckBox.setText("enable drag and drop"); dndCheckBox.setText("enable drag and drop");
dndCheckBox.setMnemonic('D'); dndCheckBox.setMnemonic('D');
dndCheckBox.addActionListener(e -> dndChanged()); dndCheckBox.addActionListener(e -> dndChanged());
tableOptionsPanel.add(dndCheckBox, "cell 0 6"); tableOptionsPanel.add(dndCheckBox, "cell 0 8");
} }
add(tableOptionsPanel, "cell 4 3"); add(tableOptionsPanel, "cell 4 3");
@@ -477,12 +517,14 @@ class DataComponentsPanel
private JTree tree3; private JTree tree3;
private JTree tree2; private JTree tree2;
private JTable table1; private JTable table1;
private JCheckBox roundedSelectionCheckBox;
private JCheckBox showHorizontalLinesCheckBox; private JCheckBox showHorizontalLinesCheckBox;
private JCheckBox showVerticalLinesCheckBox; private JCheckBox showVerticalLinesCheckBox;
private JCheckBox intercellSpacingCheckBox; private JCheckBox intercellSpacingCheckBox;
private JCheckBox redGridColorCheckBox; private JCheckBox redGridColorCheckBox;
private JCheckBox rowSelectionCheckBox; private JCheckBox rowSelectionCheckBox;
private JCheckBox columnSelectionCheckBox; private JCheckBox columnSelectionCheckBox;
private JCheckBox alternatingRowsCheckBox;
private JCheckBox dndCheckBox; private JCheckBox dndCheckBox;
// JFormDesigner - End of variables declaration //GEN-END:variables // JFormDesigner - End of variables declaration //GEN-END:variables

View File

@@ -343,9 +343,19 @@ new FormModel {
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "insets 0,hidemode 3" "$layoutConstraints": "insets 0,hidemode 3"
"$columnConstraints": "[]" "$columnConstraints": "[]"
"$rowConstraints": "[]0[]0[]0[]0[]0[]0[]0" "$rowConstraints": "[]0[]0[]0[]0[]0[]0[]0[]0[]0"
} ) { } ) {
name: "tableOptionsPanel" name: "tableOptionsPanel"
add( new FormComponent( "javax.swing.JCheckBox" ) {
name: "roundedSelectionCheckBox"
"text": "rounded selection"
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "roundedSelectionChanged", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 0"
} )
add( new FormComponent( "javax.swing.JCheckBox" ) { add( new FormComponent( "javax.swing.JCheckBox" ) {
name: "showHorizontalLinesCheckBox" name: "showHorizontalLinesCheckBox"
"text": "show horizontal lines" "text": "show horizontal lines"
@@ -354,7 +364,7 @@ new FormModel {
} }
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "showHorizontalLinesChanged", false ) ) addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "showHorizontalLinesChanged", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 0" "value": "cell 0 1"
} ) } )
add( new FormComponent( "javax.swing.JCheckBox" ) { add( new FormComponent( "javax.swing.JCheckBox" ) {
name: "showVerticalLinesCheckBox" name: "showVerticalLinesCheckBox"
@@ -364,7 +374,7 @@ new FormModel {
} }
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "showVerticalLinesChanged", false ) ) addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "showVerticalLinesChanged", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 1" "value": "cell 0 2"
} ) } )
add( new FormComponent( "javax.swing.JCheckBox" ) { add( new FormComponent( "javax.swing.JCheckBox" ) {
name: "intercellSpacingCheckBox" name: "intercellSpacingCheckBox"
@@ -374,7 +384,7 @@ new FormModel {
} }
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "intercellSpacingChanged", false ) ) addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "intercellSpacingChanged", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 2" "value": "cell 0 3"
} ) } )
add( new FormComponent( "javax.swing.JCheckBox" ) { add( new FormComponent( "javax.swing.JCheckBox" ) {
name: "redGridColorCheckBox" name: "redGridColorCheckBox"
@@ -384,7 +394,7 @@ new FormModel {
} }
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "redGridColorChanged", false ) ) addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "redGridColorChanged", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 3" "value": "cell 0 4"
} ) } )
add( new FormComponent( "javax.swing.JCheckBox" ) { add( new FormComponent( "javax.swing.JCheckBox" ) {
name: "rowSelectionCheckBox" name: "rowSelectionCheckBox"
@@ -395,7 +405,7 @@ new FormModel {
} }
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "rowSelectionChanged", false ) ) addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "rowSelectionChanged", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 4" "value": "cell 0 5"
} ) } )
add( new FormComponent( "javax.swing.JCheckBox" ) { add( new FormComponent( "javax.swing.JCheckBox" ) {
name: "columnSelectionCheckBox" name: "columnSelectionCheckBox"
@@ -405,7 +415,17 @@ new FormModel {
} }
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "columnSelectionChanged", false ) ) addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "columnSelectionChanged", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 5" "value": "cell 0 6"
} )
add( new FormComponent( "javax.swing.JCheckBox" ) {
name: "alternatingRowsCheckBox"
"text": "alternating rows"
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "alternatingRowsChanged", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 7"
} ) } )
add( new FormComponent( "javax.swing.JCheckBox" ) { add( new FormComponent( "javax.swing.JCheckBox" ) {
name: "dndCheckBox" name: "dndCheckBox"
@@ -416,7 +436,7 @@ new FormModel {
} }
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "dndChanged", false ) ) addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "dndChanged", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 6" "value": "cell 0 8"
} ) } )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 4 3" "value": "cell 4 3"

View File

@@ -73,6 +73,7 @@ class DemoFrame
initComponents(); initComponents();
updateFontMenuItems(); updateFontMenuItems();
initAccentColors(); initAccentColors();
initFullWindowContent();
controlBar.initialize( this, tabbedPane ); controlBar.initialize( this, tabbedPane );
setIconImages( FlatSVGUtils.createWindowIconImages( "/com/formdev/flatlaf/demo/FlatLaf.svg" ) ); setIconImages( FlatSVGUtils.createWindowIconImages( "/com/formdev/flatlaf/demo/FlatLaf.svg" ) );
@@ -89,24 +90,23 @@ class DemoFrame
// do not use HTML text in menu items because this is not supported in macOS screen menu // do not use HTML text in menu items because this is not supported in macOS screen menu
htmlMenuItem.setText( "some text" ); htmlMenuItem.setText( "some text" );
JRootPane rootPane = getRootPane();
if( SystemInfo.isMacFullWindowContentSupported ) { if( SystemInfo.isMacFullWindowContentSupported ) {
// expand window content into window title bar and make title bar transparent // expand window content into window title bar and make title bar transparent
getRootPane().putClientProperty( "apple.awt.fullWindowContent", true ); rootPane.putClientProperty( "apple.awt.fullWindowContent", true );
getRootPane().putClientProperty( "apple.awt.transparentTitleBar", true ); rootPane.putClientProperty( "apple.awt.transparentTitleBar", true );
rootPane.putClientProperty( FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING, FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING_LARGE );
// hide window title // hide window title
if( SystemInfo.isJava_17_orLater ) if( SystemInfo.isJava_17_orLater )
getRootPane().putClientProperty( "apple.awt.windowTitleVisible", false ); rootPane.putClientProperty( "apple.awt.windowTitleVisible", false );
else else
setTitle( null ); 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+) // enable full screen mode for this window (for Java 8 - 10; not necessary for Java 11+)
if( !SystemInfo.isJava_11_orLater ) if( !SystemInfo.isJava_11_orLater )
getRootPane().putClientProperty( "apple.awt.fullscreenable", true ); rootPane.putClientProperty( "apple.awt.fullscreenable", true );
} }
// integrate into macOS screen menu // integrate into macOS screen menu
@@ -461,9 +461,37 @@ class DemoFrame
accentColorButtons[i].setVisible( isAccentColorSupported ); 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() { private void initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
JMenuBar menuBar1 = new JMenuBar(); menuBar = new JMenuBar();
JMenu fileMenu = new JMenu(); JMenu fileMenu = new JMenu();
JMenuItem newMenuItem = new JMenuItem(); JMenuItem newMenuItem = new JMenuItem();
JMenuItem openMenuItem = new JMenuItem(); JMenuItem openMenuItem = new JMenuItem();
@@ -509,6 +537,8 @@ class DemoFrame
JMenuItem showUIDefaultsInspectorMenuItem = new JMenuItem(); JMenuItem showUIDefaultsInspectorMenuItem = new JMenuItem();
JMenu helpMenu = new JMenu(); JMenu helpMenu = new JMenu();
aboutMenuItem = new JMenuItem(); aboutMenuItem = new JMenuItem();
JPanel toolBarPanel = new JPanel();
JPanel macFullWindowContentButtonsPlaceholder = new JPanel();
toolBar = new JToolBar(); toolBar = new JToolBar();
JButton backButton = new JButton(); JButton backButton = new JButton();
JButton forwardButton = new JButton(); JButton forwardButton = new JButton();
@@ -524,8 +554,10 @@ class DemoFrame
DataComponentsPanel dataComponentsPanel = new DataComponentsPanel(); DataComponentsPanel dataComponentsPanel = new DataComponentsPanel();
TabsPanel tabsPanel = new TabsPanel(); TabsPanel tabsPanel = new TabsPanel();
OptionPanePanel optionPanePanel = new OptionPanePanel(); OptionPanePanel optionPanePanel = new OptionPanePanel();
ExtrasPanel extrasPanel1 = new ExtrasPanel(); ExtrasPanel extrasPanel = new ExtrasPanel();
controlBar = new ControlBar(); controlBar = new ControlBar();
JPanel themesPanelPanel = new JPanel();
JPanel winFullWindowContentButtonsPlaceholder = new JPanel();
themesPanel = new IJThemesPanel(); themesPanel = new IJThemesPanel();
//======== this ======== //======== this ========
@@ -534,7 +566,7 @@ class DemoFrame
Container contentPane = getContentPane(); Container contentPane = getContentPane();
contentPane.setLayout(new BorderLayout()); contentPane.setLayout(new BorderLayout());
//======== menuBar1 ======== //======== menuBar ========
{ {
//======== fileMenu ======== //======== fileMenu ========
@@ -579,7 +611,7 @@ class DemoFrame
exitMenuItem.addActionListener(e -> exitActionPerformed()); exitMenuItem.addActionListener(e -> exitActionPerformed());
fileMenu.add(exitMenuItem); fileMenu.add(exitMenuItem);
} }
menuBar1.add(fileMenu); menuBar.add(fileMenu);
//======== editMenu ======== //======== editMenu ========
{ {
@@ -632,7 +664,7 @@ class DemoFrame
deleteMenuItem.addActionListener(e -> menuItemActionPerformed(e)); deleteMenuItem.addActionListener(e -> menuItemActionPerformed(e));
editMenu.add(deleteMenuItem); editMenu.add(deleteMenuItem);
} }
menuBar1.add(editMenu); menuBar.add(editMenu);
//======== viewMenu ======== //======== viewMenu ========
{ {
@@ -732,7 +764,7 @@ class DemoFrame
radioButtonMenuItem3.addActionListener(e -> menuItemActionPerformed(e)); radioButtonMenuItem3.addActionListener(e -> menuItemActionPerformed(e));
viewMenu.add(radioButtonMenuItem3); viewMenu.add(radioButtonMenuItem3);
} }
menuBar1.add(viewMenu); menuBar.add(viewMenu);
//======== fontMenu ======== //======== fontMenu ========
{ {
@@ -756,7 +788,7 @@ class DemoFrame
decrFontMenuItem.addActionListener(e -> decrFont()); decrFontMenuItem.addActionListener(e -> decrFont());
fontMenu.add(decrFontMenuItem); fontMenu.add(decrFontMenuItem);
} }
menuBar1.add(fontMenu); menuBar.add(fontMenu);
//======== optionsMenu ======== //======== optionsMenu ========
{ {
@@ -808,7 +840,7 @@ class DemoFrame
showUIDefaultsInspectorMenuItem.addActionListener(e -> showUIDefaultsInspector()); showUIDefaultsInspectorMenuItem.addActionListener(e -> showUIDefaultsInspector());
optionsMenu.add(showUIDefaultsInspectorMenuItem); optionsMenu.add(showUIDefaultsInspectorMenuItem);
} }
menuBar1.add(optionsMenu); menuBar.add(optionsMenu);
//======== helpMenu ======== //======== helpMenu ========
{ {
@@ -821,54 +853,66 @@ class DemoFrame
aboutMenuItem.addActionListener(e -> aboutActionPerformed()); aboutMenuItem.addActionListener(e -> aboutActionPerformed());
helpMenu.add(aboutMenuItem); 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 ---- //======== macFullWindowContentButtonsPlaceholder ========
backButton.setToolTipText("Back"); {
backButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/back.svg")); macFullWindowContentButtonsPlaceholder.setLayout(new FlowLayout());
toolBar.add(backButton); }
toolBarPanel.add(macFullWindowContentButtonsPlaceholder, BorderLayout.WEST);
//---- forwardButton ---- //======== toolBar ========
forwardButton.setToolTipText("Forward"); {
forwardButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/forward.svg")); toolBar.setMargin(new Insets(3, 3, 3, 3));
toolBar.add(forwardButton);
toolBar.addSeparator();
//---- cutButton ---- //---- backButton ----
cutButton.setToolTipText("Cut"); backButton.setToolTipText("Back");
cutButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/menu-cut.svg")); backButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/back.svg"));
toolBar.add(cutButton); toolBar.add(backButton);
//---- copyButton ---- //---- forwardButton ----
copyButton.setToolTipText("Copy"); forwardButton.setToolTipText("Forward");
copyButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/copy.svg")); forwardButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/forward.svg"));
toolBar.add(copyButton); toolBar.add(forwardButton);
toolBar.addSeparator();
//---- pasteButton ---- //---- cutButton ----
pasteButton.setToolTipText("Paste"); cutButton.setToolTipText("Cut");
pasteButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/menu-paste.svg")); cutButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/menu-cut.svg"));
toolBar.add(pasteButton); toolBar.add(cutButton);
toolBar.addSeparator();
//---- refreshButton ---- //---- copyButton ----
refreshButton.setToolTipText("Refresh"); copyButton.setToolTipText("Copy");
refreshButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/refresh.svg")); copyButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/copy.svg"));
toolBar.add(refreshButton); toolBar.add(copyButton);
toolBar.addSeparator();
//---- showToggleButton ---- //---- pasteButton ----
showToggleButton.setSelected(true); pasteButton.setToolTipText("Paste");
showToggleButton.setToolTipText("Show Details"); pasteButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/menu-paste.svg"));
showToggleButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/show.svg")); toolBar.add(pasteButton);
toolBar.add(showToggleButton); 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 ======== //======== contentPanel ========
{ {
@@ -888,13 +932,25 @@ class DemoFrame
tabbedPane.addTab("Data Components", dataComponentsPanel); tabbedPane.addTab("Data Components", dataComponentsPanel);
tabbedPane.addTab("Tabs", tabsPanel); tabbedPane.addTab("Tabs", tabsPanel);
tabbedPane.addTab("Option Pane", optionPanePanel); tabbedPane.addTab("Option Pane", optionPanePanel);
tabbedPane.addTab("Extras", extrasPanel1); tabbedPane.addTab("Extras", extrasPanel);
} }
contentPanel.add(tabbedPane, "cell 0 0"); contentPanel.add(tabbedPane, "cell 0 0");
} }
contentPane.add(contentPanel, BorderLayout.CENTER); contentPane.add(contentPanel, BorderLayout.CENTER);
contentPane.add(controlBar, BorderLayout.SOUTH); contentPane.add(controlBar, BorderLayout.PAGE_END);
contentPane.add(themesPanel, BorderLayout.EAST);
//======== 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 ---- //---- buttonGroup1 ----
ButtonGroup buttonGroup1 = new ButtonGroup(); ButtonGroup buttonGroup1 = new ButtonGroup();
@@ -909,8 +965,8 @@ class DemoFrame
usersButton.setButtonType( ButtonType.toolBarButton ); usersButton.setButtonType( ButtonType.toolBarButton );
usersButton.setFocusable( false ); usersButton.setFocusable( false );
usersButton.addActionListener( e -> JOptionPane.showMessageDialog( null, "Hello User! How are you?", "User", JOptionPane.INFORMATION_MESSAGE ) ); usersButton.addActionListener( e -> JOptionPane.showMessageDialog( null, "Hello User! How are you?", "User", JOptionPane.INFORMATION_MESSAGE ) );
menuBar1.add( Box.createGlue() ); menuBar.add( Box.createGlue() );
menuBar1.add( usersButton ); menuBar.add( usersButton );
cutMenuItem.addActionListener( new DefaultEditorKit.CutAction() ); cutMenuItem.addActionListener( new DefaultEditorKit.CutAction() );
copyMenuItem.addActionListener( new DefaultEditorKit.CopyAction() ); copyMenuItem.addActionListener( new DefaultEditorKit.CopyAction() );
@@ -922,7 +978,7 @@ class DemoFrame
for( int i = 1; i <= 100; i++ ) for( int i = 1; i <= 100; i++ )
scrollingPopupMenu.add( "Item " + i ); scrollingPopupMenu.add( "Item " + i );
if( FlatLaf.supportsNativeWindowDecorations() || (SystemInfo.isLinux && JFrame.isDefaultLookAndFeelDecorated()) ) { if( supportsFlatLafWindowDecorations() ) {
if( SystemInfo.isLinux ) if( SystemInfo.isLinux )
unsupported( windowDecorationsCheckBoxMenuItem ); unsupported( windowDecorationsCheckBoxMenuItem );
else else
@@ -943,6 +999,17 @@ class DemoFrame
if( "false".equals( System.getProperty( "flatlaf.animatedLafChange" ) ) ) if( "false".equals( System.getProperty( "flatlaf.animatedLafChange" ) ) )
animatedLafChangeMenuItem.setSelected( false ); 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 // remove contentPanel bottom insets
MigLayout layout = (MigLayout) contentPanel.getLayout(); MigLayout layout = (MigLayout) contentPanel.getLayout();
LC lc = ConstraintParser.parseLayoutConstraint( (String) layout.getLayoutConstraints() ); LC lc = ConstraintParser.parseLayoutConstraint( (String) layout.getLayoutConstraints() );
@@ -963,6 +1030,7 @@ class DemoFrame
} }
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
private JMenuBar menuBar;
private JMenuItem exitMenuItem; private JMenuItem exitMenuItem;
private JMenu scrollingPopupMenu; private JMenu scrollingPopupMenu;
private JMenuItem htmlMenuItem; 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 { new FormModel {
contentType: "form/swing" contentType: "form/swing"
@@ -12,59 +12,69 @@ new FormModel {
"defaultCloseOperation": 2 "defaultCloseOperation": 2
"$locationPolicy": 2 "$locationPolicy": 2
"$sizePolicy": 2 "$sizePolicy": 2
add( new FormContainer( "javax.swing.JToolBar", new FormLayoutManager( class javax.swing.JToolBar ) ) { add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class java.awt.BorderLayout ) ) {
name: "toolBar" name: "toolBarPanel"
"margin": new java.awt.Insets( 3, 3, 3, 3 ) add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class java.awt.FlowLayout ) ) {
auxiliary() { name: "macFullWindowContentButtonsPlaceholder"
"JavaCodeGenerator.variableLocal": false }, new FormLayoutConstraints( class java.lang.String ) {
} "value": "West"
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" ) { add( new FormContainer( "javax.swing.JToolBar", new FormLayoutManager( class javax.swing.JToolBar ) ) {
name: "forwardButton" name: "toolBar"
"toolTipText": "Forward" "margin": new java.awt.Insets( 3, 3, 3, 3 )
"icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/forward.svg" ) auxiliary() {
} ) "JavaCodeGenerator.variableLocal": false
add( new FormComponent( "javax.swing.JToolBar$Separator" ) { }
name: "separator5" add( new FormComponent( "javax.swing.JButton" ) {
} ) name: "backButton"
add( new FormComponent( "javax.swing.JButton" ) { "toolTipText": "Back"
name: "cutButton" "icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/back.svg" )
"toolTipText": "Cut" } )
"icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/menu-cut.svg" ) add( new FormComponent( "javax.swing.JButton" ) {
} ) name: "forwardButton"
add( new FormComponent( "javax.swing.JButton" ) { "toolTipText": "Forward"
name: "copyButton" "icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/forward.svg" )
"toolTipText": "Copy" } )
"icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/copy.svg" ) add( new FormComponent( "javax.swing.JToolBar$Separator" ) {
} ) name: "separator5"
add( new FormComponent( "javax.swing.JButton" ) { } )
name: "pasteButton" add( new FormComponent( "javax.swing.JButton" ) {
"toolTipText": "Paste" name: "cutButton"
"icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/menu-paste.svg" ) "toolTipText": "Cut"
} ) "icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/menu-cut.svg" )
add( new FormComponent( "javax.swing.JToolBar$Separator" ) { } )
name: "separator6" add( new FormComponent( "javax.swing.JButton" ) {
} ) name: "copyButton"
add( new FormComponent( "javax.swing.JButton" ) { "toolTipText": "Copy"
name: "refreshButton" "icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/copy.svg" )
"toolTipText": "Refresh" } )
"icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/refresh.svg" ) add( new FormComponent( "javax.swing.JButton" ) {
} ) name: "pasteButton"
add( new FormComponent( "javax.swing.JToolBar$Separator" ) { "toolTipText": "Paste"
name: "separator7" "icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/menu-paste.svg" )
} ) } )
add( new FormComponent( "javax.swing.JToggleButton" ) { add( new FormComponent( "javax.swing.JToolBar$Separator" ) {
name: "showToggleButton" name: "separator6"
"selected": true } )
"toolTipText": "Show Details" add( new FormComponent( "javax.swing.JButton" ) {
"icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/show.svg" ) 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 ) { }, new FormLayoutConstraints( class java.lang.String ) {
"value": "North" "value": "First"
} ) } )
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "insets dialog,hidemode 3" "$layoutConstraints": "insets dialog,hidemode 3"
@@ -105,7 +115,7 @@ new FormModel {
"title": "Option Pane" "title": "Option Pane"
} ) } )
add( new FormComponent( "com.formdev.flatlaf.demo.extras.ExtrasPanel" ) { add( new FormComponent( "com.formdev.flatlaf.demo.extras.ExtrasPanel" ) {
name: "extrasPanel1" name: "extrasPanel"
}, new FormLayoutConstraints( null ) { }, new FormLayoutConstraints( null ) {
"title": "Extras" "title": "Extras"
} ) } )
@@ -121,19 +131,32 @@ new FormModel {
"JavaCodeGenerator.variableLocal": false "JavaCodeGenerator.variableLocal": false
} }
}, new FormLayoutConstraints( class java.lang.String ) { }, new FormLayoutConstraints( class java.lang.String ) {
"value": "South" "value": "Last"
} ) } )
add( new FormComponent( "com.formdev.flatlaf.demo.intellijthemes.IJThemesPanel" ) { add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class java.awt.BorderLayout ) ) {
name: "themesPanel" name: "themesPanelPanel"
auxiliary() { add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class java.awt.FlowLayout ) ) {
"JavaCodeGenerator.variableLocal": false name: "winFullWindowContentButtonsPlaceholder"
"JavaCodeGenerator.variableModifiers": 0 }, 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 ) { }, new FormLayoutConstraints( class java.lang.String ) {
"value": "East" "value": "After"
} ) } )
menuBar: new FormContainer( "javax.swing.JMenuBar", new FormLayoutManager( class javax.swing.JMenuBar ) ) { 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 ) ) { add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) {
name: "fileMenu" name: "fileMenu"
"text": "File" "text": "File"

View File

@@ -19,6 +19,10 @@ package com.formdev.flatlaf.demo;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.util.prefs.Preferences; import java.util.prefs.Preferences;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.KeyStroke;
import javax.swing.UIManager; import javax.swing.UIManager;
import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.FlatLightLaf; import com.formdev.flatlaf.FlatLightLaf;
@@ -27,6 +31,7 @@ import com.formdev.flatlaf.IntelliJTheme;
import com.formdev.flatlaf.demo.intellijthemes.IJThemesPanel; import com.formdev.flatlaf.demo.intellijthemes.IJThemesPanel;
import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.StringUtils; import com.formdev.flatlaf.util.StringUtils;
import com.formdev.flatlaf.util.SystemInfo;
/** /**
* @author Karl Tauber * @author Karl Tauber
@@ -35,6 +40,7 @@ public class DemoPrefs
{ {
public static final String KEY_LAF = "laf"; public static final String KEY_LAF = "laf";
public static final String KEY_LAF_THEME = "lafTheme"; public static final String KEY_LAF_THEME = "lafTheme";
public static final String KEY_SYSTEM_SCALE_FACTOR = "systemScaleFactor";
public static final String RESOURCE_PREFIX = "res:"; public static final String RESOURCE_PREFIX = "res:";
public static final String FILE_PREFIX = "file:"; public static final String FILE_PREFIX = "file:";
@@ -96,4 +102,66 @@ public class DemoPrefs
state.put( KEY_LAF, UIManager.getLookAndFeel().getClass().getName() ); state.put( KEY_LAF, UIManager.getLookAndFeel().getClass().getName() );
} ); } );
} }
public static void initSystemScale() {
if( System.getProperty( "sun.java2d.uiScale" ) == null ) {
String scaleFactor = getState().get( KEY_SYSTEM_SCALE_FACTOR, null );
if( scaleFactor != null )
System.setProperty( "sun.java2d.uiScale", scaleFactor );
}
}
/**
* register Alt+Shift+F1, F2, ... F12 keys to change system scale factor
*/
public static void registerSystemScaleFactors( JFrame frame ) {
registerSystemScaleFactor( frame, "alt shift F1", null );
registerSystemScaleFactor( frame, "alt shift F2", "1" );
if( SystemInfo.isWindows ) {
registerSystemScaleFactor( frame, "alt shift F3", "1.25" );
registerSystemScaleFactor( frame, "alt shift F4", "1.5" );
registerSystemScaleFactor( frame, "alt shift F5", "1.75" );
registerSystemScaleFactor( frame, "alt shift F6", "2" );
registerSystemScaleFactor( frame, "alt shift F7", "2.25" );
registerSystemScaleFactor( frame, "alt shift F8", "2.5" );
registerSystemScaleFactor( frame, "alt shift F9", "2.75" );
registerSystemScaleFactor( frame, "alt shift F10", "3" );
registerSystemScaleFactor( frame, "alt shift F11", "3.5" );
registerSystemScaleFactor( frame, "alt shift F12", "4" );
} else {
// Java on macOS and Linux supports only integer scale factors
registerSystemScaleFactor( frame, "alt shift F3", "2" );
registerSystemScaleFactor( frame, "alt shift F4", "3" );
registerSystemScaleFactor( frame, "alt shift F5", "4" );
}
}
private static void registerSystemScaleFactor( JFrame frame, String keyStrokeStr, String scaleFactor ) {
KeyStroke keyStroke = KeyStroke.getKeyStroke( keyStrokeStr );
if( keyStroke == null )
throw new IllegalArgumentException( "Invalid key stroke '" + keyStrokeStr + "'" );
((JComponent)frame.getContentPane()).registerKeyboardAction(
e -> applySystemScaleFactor( frame, scaleFactor ),
keyStroke,
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT );
}
private static void applySystemScaleFactor( JFrame frame, String scaleFactor ) {
if( JOptionPane.showConfirmDialog( frame,
"Change system scale factor to "
+ (scaleFactor != null ? scaleFactor : "default")
+ " and exit?",
frame.getTitle(), JOptionPane.YES_NO_OPTION ) != JOptionPane.YES_OPTION )
return;
if( scaleFactor != null )
DemoPrefs.getState().put( KEY_SYSTEM_SCALE_FACTOR, scaleFactor );
else
DemoPrefs.getState().remove( KEY_SYSTEM_SCALE_FACTOR );
System.exit( 0 );
}
} }

View File

@@ -70,9 +70,10 @@ public class FlatLafDemo
if( FlatLafDemo.screenshotsMode && !SystemInfo.isJava_9_orLater && System.getProperty( "flatlaf.uiScale" ) == null ) if( FlatLafDemo.screenshotsMode && !SystemInfo.isJava_9_orLater && System.getProperty( "flatlaf.uiScale" ) == null )
System.setProperty( "flatlaf.uiScale", "2x" ); System.setProperty( "flatlaf.uiScale", "2x" );
SwingUtilities.invokeLater( () -> { DemoPrefs.init( PREFS_ROOT_PATH );
DemoPrefs.init( PREFS_ROOT_PATH ); DemoPrefs.initSystemScale();
SwingUtilities.invokeLater( () -> {
// install fonts for lazy loading // install fonts for lazy loading
FlatInterFont.installLazy(); FlatInterFont.installLazy();
FlatJetBrainsMonoFont.installLazy(); FlatJetBrainsMonoFont.installLazy();
@@ -95,6 +96,9 @@ public class FlatLafDemo
// use Roboto Mono font // use Roboto Mono font
// FlatLaf.setPreferredMonospacedFontFamily( FlatRobotoMonoFont.FAMILY ); // FlatLaf.setPreferredMonospacedFontFamily( FlatRobotoMonoFont.FAMILY );
// install own repaint manager to fix repaint issues at 125%, 175%, 225%, ... on Windows
// HiDPIUtils.installHiDPIRepaintManager();
// application specific UI defaults // application specific UI defaults
FlatLaf.registerCustomDefaultsSource( "com.formdev.flatlaf.demo" ); FlatLaf.registerCustomDefaultsSource( "com.formdev.flatlaf.demo" );

View File

@@ -118,6 +118,14 @@ class MoreComponentsPanel
JLabel label5 = new JLabel(); JLabel label5 = new JLabel();
JPanel panel13 = new JPanel(); JPanel panel13 = new JPanel();
JLabel label6 = new JLabel(); JLabel label6 = new JLabel();
JLabel panelLabel = new JLabel();
JPanel panel5 = new JPanel();
JLabel label9 = new JLabel();
JPanel panel4 = new JPanel();
JLabel label8 = new JLabel();
JLabel labelLabel = new JLabel();
JLabel label13 = new JLabel();
JLabel label10 = new JLabel();
//======== this ======== //======== this ========
setLayout(new MigLayout( setLayout(new MigLayout(
@@ -140,7 +148,9 @@ class MoreComponentsPanel
"[]" + "[]" +
"[]" + "[]" +
"[]" + "[]" +
"[100,top]")); "[100,top]" +
"[50,top]" +
"[]"));
//---- scrollPaneLabel ---- //---- scrollPaneLabel ----
scrollPaneLabel.setText("JScrollPane:"); scrollPaneLabel.setText("JScrollPane:");
@@ -441,7 +451,7 @@ class MoreComponentsPanel
//======== panel10 ======== //======== panel10 ========
{ {
panel10.setBackground(new Color(217, 163, 67)); panel10.setBackground(new Color(0xd9a343));
panel10.setLayout(new BorderLayout()); panel10.setLayout(new BorderLayout());
//---- label1 ---- //---- label1 ----
@@ -454,7 +464,7 @@ class MoreComponentsPanel
//======== panel11 ======== //======== panel11 ========
{ {
panel11.setBackground(new Color(98, 181, 67)); panel11.setBackground(new Color(0x62b543));
panel11.setLayout(new BorderLayout()); panel11.setLayout(new BorderLayout());
//---- label2 ---- //---- label2 ----
@@ -474,7 +484,7 @@ class MoreComponentsPanel
//======== panel12 ======== //======== panel12 ========
{ {
panel12.setBackground(new Color(242, 101, 34)); panel12.setBackground(new Color(0xf26522));
panel12.setLayout(new BorderLayout()); panel12.setLayout(new BorderLayout());
//---- label5 ---- //---- label5 ----
@@ -487,7 +497,7 @@ class MoreComponentsPanel
//======== panel13 ======== //======== panel13 ========
{ {
panel13.setBackground(new Color(64, 182, 224)); panel13.setBackground(new Color(0x40b6e0));
panel13.setLayout(new BorderLayout()); panel13.setLayout(new BorderLayout());
//---- label6 ---- //---- label6 ----
@@ -502,6 +512,52 @@ class MoreComponentsPanel
} }
add(splitPane3, "cell 1 11 4 1,grow"); add(splitPane3, "cell 1 11 4 1,grow");
//---- panelLabel ----
panelLabel.setText("JPanel:");
add(panelLabel, "cell 0 12");
//======== panel5 ========
{
panel5.putClientProperty("FlatLaf.style", "arc: 16; background: darken($Panel.background,5%)");
panel5.setLayout(new BorderLayout());
//---- label9 ----
label9.setText("rounded background");
label9.setHorizontalAlignment(SwingConstants.CENTER);
panel5.add(label9, BorderLayout.CENTER);
}
add(panel5, "cell 1 12 4 1,growy,width 150");
//======== panel4 ========
{
panel4.putClientProperty("FlatLaf.style", "border: 1,1,1,1,@disabledForeground,1,16; background: darken($Panel.background,5%)");
panel4.setLayout(new BorderLayout());
//---- label8 ----
label8.setText("rounded border");
label8.setHorizontalAlignment(SwingConstants.CENTER);
panel4.add(label8, BorderLayout.CENTER);
}
add(panel4, "cell 1 12 4 1,growy,width 150");
//---- labelLabel ----
labelLabel.setText("JLabel:");
add(labelLabel, "cell 0 13");
//---- label13 ----
label13.setText("rounded background");
label13.putClientProperty("FlatLaf.style", "arc: 999; border: 2,10,2,10");
label13.setBackground(new Color(0xb8e4f3));
label13.setForeground(new Color(0x135b76));
add(label13, "cell 1 13 4 1");
//---- label10 ----
label10.setText("rounded border");
label10.putClientProperty("FlatLaf.style", "arc: 999; border: 2,10,2,10,#135b76");
label10.setBackground(new Color(0xb8e4f3));
label10.setForeground(new Color(0x135b76));
add(label10, "cell 1 13 4 1");
//---- buttonGroup1 ---- //---- buttonGroup1 ----
ButtonGroup buttonGroup1 = new ButtonGroup(); ButtonGroup buttonGroup1 = new ButtonGroup();
buttonGroup1.add(toggleButton1); buttonGroup1.add(toggleButton1);

View File

@@ -1,4 +1,4 @@
JFDML JFormDesigner: "7.0.5.0.404" Java: "17.0.2" encoding: "UTF-8" JFDML JFormDesigner: "8.2.2.0.9999" Java: "21.0.1" encoding: "UTF-8"
new FormModel { new FormModel {
contentType: "form/swing" contentType: "form/swing"
@@ -9,7 +9,7 @@ new FormModel {
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "insets dialog,hidemode 3" "$layoutConstraints": "insets dialog,hidemode 3"
"$columnConstraints": "[][][][][]" "$columnConstraints": "[][][][][]"
"$rowConstraints": "[][][][][][][][][][][][100,top]" "$rowConstraints": "[][][][][][][][][][][][100,top][50,top][]"
} ) { } ) {
name: "this" name: "this"
add( new FormComponent( "javax.swing.JLabel" ) { add( new FormComponent( "javax.swing.JLabel" ) {
@@ -467,6 +467,62 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 11 4 1,grow" "value": "cell 1 11 4 1,grow"
} ) } )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "panelLabel"
"text": "JPanel:"
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 12"
} )
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class java.awt.BorderLayout ) ) {
name: "panel5"
"$client.FlatLaf.style": "arc: 16; background: darken($Panel.background,5%)"
add( new FormComponent( "javax.swing.JLabel" ) {
name: "label9"
"text": "rounded background"
"horizontalAlignment": 0
}, new FormLayoutConstraints( class java.lang.String ) {
"value": "Center"
} )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 12 4 1,growy,width 150"
} )
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class java.awt.BorderLayout ) ) {
name: "panel4"
"$client.FlatLaf.style": "border: 1,1,1,1,@disabledForeground,1,16; background: darken($Panel.background,5%)"
add( new FormComponent( "javax.swing.JLabel" ) {
name: "label8"
"text": "rounded border"
"horizontalAlignment": 0
}, new FormLayoutConstraints( class java.lang.String ) {
"value": "Center"
} )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 12 4 1,growy,width 150"
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "labelLabel"
"text": "JLabel:"
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 13"
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "label13"
"text": "rounded background"
"$client.FlatLaf.style": "arc: 999; border: 2,10,2,10"
"background": new java.awt.Color( 184, 228, 243, 255 )
"foreground": new java.awt.Color( 19, 91, 118, 255 )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 13 4 1"
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "label10"
"text": "rounded border"
"$client.FlatLaf.style": "arc: 999; border: 2,10,2,10,#135b76"
"background": new java.awt.Color( 184, 228, 243, 255 )
"foreground": new java.awt.Color( 19, 91, 118, 255 )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 13 4 1"
} )
}, new FormLayoutConstraints( null ) { }, new FormLayoutConstraints( null ) {
"location": new java.awt.Point( 0, 0 ) "location": new java.awt.Point( 0, 0 )
"size": new java.awt.Dimension( 700, 550 ) "size": new java.awt.Dimension( 700, 550 )
@@ -474,7 +530,7 @@ new FormModel {
add( new FormNonVisual( "javax.swing.ButtonGroup" ) { add( new FormNonVisual( "javax.swing.ButtonGroup" ) {
name: "buttonGroup1" name: "buttonGroup1"
}, new FormLayoutConstraints( null ) { }, new FormLayoutConstraints( null ) {
"location": new java.awt.Point( 0, 560 ) "location": new java.awt.Point( 0, 600 )
} ) } )
} }
} }

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

@@ -547,6 +547,12 @@ public class FlatInspector
appendRow( buf, "Left-to-right", String.valueOf( c.getComponentOrientation().isLeftToRight() ) ); appendRow( buf, "Left-to-right", String.valueOf( c.getComponentOrientation().isLeftToRight() ) );
appendRow( buf, "Parent", (c.getParent() != null ? toString( c.getParent().getClass(), classHierarchy ) : "null") ); appendRow( buf, "Parent", (c.getParent() != null ? toString( c.getParent().getClass(), classHierarchy ) : "null") );
if( c instanceof JComponent ) {
Object style = ((JComponent)c).getClientProperty( FlatClientProperties.STYLE );
if( style != null )
appendRow( buf, "FlatLaf Style", style.toString() );
}
// append parent level // append parent level
buf.append( "<tr><td colspan=\"2\">" ); buf.append( "<tr><td colspan=\"2\">" );
if( parentLevel > 0 ) if( parentLevel > 0 )

View File

@@ -25,6 +25,7 @@ import java.awt.Image;
import java.awt.Paint; import java.awt.Paint;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.RenderingHints; import java.awt.RenderingHints;
import java.awt.LinearGradientPaint;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.awt.image.RGBImageFilter; import java.awt.image.RGBImageFilter;
import java.io.File; import java.io.File;
@@ -62,6 +63,8 @@ public class FlatSVGIcon
extends ImageIcon extends ImageIcon
implements DisabledIconProvider implements DisabledIconProvider
{ {
private static boolean loggingEnabled = true;
private static boolean svgCacheEnabled = true;
// cache that uses soft references for values, which allows freeing SVG documents if no longer used // cache that uses soft references for values, which allows freeing SVG documents if no longer used
private static final SoftCache<String, SVGDocument> svgCache = new SoftCache<>(); private static final SoftCache<String, SVGDocument> svgCache = new SoftCache<>();
private static final SVGLoader svgLoader = new SVGLoader(); private static final SVGLoader svgLoader = new SVGLoader();
@@ -271,7 +274,8 @@ public class FlatSVGIcon
if( document == null ) { if( document == null ) {
loadFailed = true; loadFailed = true;
LoggingFacade.INSTANCE.logSevere( "FlatSVGIcon: failed to load SVG icon from input stream", null ); if( loggingEnabled )
LoggingFacade.INSTANCE.logConfig( "FlatSVGIcon: failed to load SVG icon from input stream", null );
} }
} }
} }
@@ -449,8 +453,9 @@ public class FlatSVGIcon
* @param colorFilter The color filter * @param colorFilter The color filter
* @since 1.2 * @since 1.2
*/ */
public void setColorFilter( ColorFilter colorFilter ) { public FlatSVGIcon setColorFilter( ColorFilter colorFilter ) {
this.colorFilter = colorFilter; this.colorFilter = colorFilter;
return this;
} }
private void update() { private void update() {
@@ -474,7 +479,8 @@ public class FlatSVGIcon
if( url == null ) { if( url == null ) {
loadFailed = true; loadFailed = true;
LoggingFacade.INSTANCE.logConfig( "FlatSVGIcon: resource '" + name + "' not found (if using Java modules, check whether icon package is opened in module-info.java)", null ); if( loggingEnabled )
LoggingFacade.INSTANCE.logConfig( "FlatSVGIcon: resource '" + name + "' not found (if using Java modules, check whether icon package is opened in module-info.java)", null );
return; return;
} }
} }
@@ -484,6 +490,9 @@ public class FlatSVGIcon
} }
static synchronized SVGDocument loadSVG( URL url ) { static synchronized SVGDocument loadSVG( URL url ) {
if( !svgCacheEnabled )
return loadSVGUncached( url );
// get from our cache // get from our cache
String cacheKey = url.toString(); String cacheKey = url.toString();
SVGDocument document = svgCache.get( cacheKey ); SVGDocument document = svgCache.get( cacheKey );
@@ -491,18 +500,25 @@ public class FlatSVGIcon
return document; return document;
// load SVG document // load SVG document
document = svgLoader.load( url ); document = loadSVGUncached( url );
if( document == null ) {
LoggingFacade.INSTANCE.logSevere( "FlatSVGIcon: failed to load '" + url + "'", null );
return null;
}
svgCache.put( cacheKey, document ); svgCache.put( cacheKey, document );
return document; return document;
} }
private static SVGDocument loadSVGUncached( URL url ) {
SVGDocument document = svgLoader.load( url );
if( document == null ) {
if( loggingEnabled )
LoggingFacade.INSTANCE.logConfig( "FlatSVGIcon: failed to load '" + url + "'", null );
return null;
}
return document;
}
private URL getIconURL( String name, boolean dark ) { private URL getIconURL( String name, boolean dark ) {
if( dark ) { if( dark ) {
int dotIndex = name.lastIndexOf( '.' ); int dotIndex = name.lastIndexOf( '.' );
@@ -611,7 +627,10 @@ public class FlatSVGIcon
} }
private void paintSvgError( Graphics2D g, int x, int y ) { private void paintSvgError( Graphics2D g, int x, int y ) {
g.setColor( Color.red ); if( g instanceof GraphicsFilter )
((GraphicsFilter)g).setColorUnfiltered( Color.red );
else
g.setColor( Color.red );
g.fillRect( x, y, getIconWidth(), getIconHeight() ); g.fillRect( x, y, getIconWidth(), getIconHeight() );
} }
@@ -692,6 +711,34 @@ public class FlatSVGIcon
darkLaf = FlatLaf.isLafDark(); darkLaf = FlatLaf.isLafDark();
} }
/** @since 3.4.1 */
public static boolean isLoggingEnabled() {
return loggingEnabled;
}
/** @since 3.4.1 */
public static void setLoggingEnabled( boolean loggingEnabled ) {
FlatSVGIcon.loggingEnabled = loggingEnabled;
}
/** @since 3.4.1 */
public static boolean isSVGDocumentEnabled() {
return svgCacheEnabled;
}
/** @since 3.4.1 */
public static void setSVGDocumentEnabled( boolean svgCacheEnabled ) {
FlatSVGIcon.svgCacheEnabled = svgCacheEnabled;
if( !svgCacheEnabled )
clearSVGDocumentCache();
}
/** @since 3.4.1 */
public static void clearSVGDocumentCache() {
svgCache.clear();
}
//---- class ColorFilter -------------------------------------------------- //---- class ColorFilter --------------------------------------------------
/** /**
@@ -978,10 +1025,23 @@ public class FlatSVGIcon
super.setColor( filterColor( c ) ); super.setColor( filterColor( c ) );
} }
void setColorUnfiltered( Color c ) {
super.setColor( c );
}
@Override @Override
public void setPaint( Paint paint ) { public void setPaint( Paint paint ) {
if( paint instanceof Color ) if( paint instanceof Color )
paint = filterColor( (Color) paint ); paint = filterColor( (Color) paint );
else if( paint instanceof LinearGradientPaint ) {
LinearGradientPaint oldPaint = (LinearGradientPaint) paint;
Color[] newColors = filterColors( oldPaint.getColors() );
if( newColors != null ) {
paint = new LinearGradientPaint( oldPaint.getStartPoint(), oldPaint.getEndPoint(),
oldPaint.getFractions(), newColors, oldPaint.getCycleMethod(),
oldPaint.getColorSpace(), oldPaint.getTransform() );
}
}
super.setPaint( paint ); super.setPaint( paint );
} }
@@ -1001,5 +1061,15 @@ public class FlatSVGIcon
} }
return color; return color;
} }
private Color[] filterColors( Color[] colors ) {
Color[] newColors = new Color[colors.length];
boolean changed = false;
for( int i = 0; i < colors.length; i++ ) {
newColors[i] = filterColor( colors[i] );
changed = (changed || newColors[i] != colors[i]);
}
return changed ? newColors : null;
}
} }
} }

View File

@@ -16,6 +16,7 @@
package com.formdev.flatlaf.extras.components; package com.formdev.flatlaf.extras.components;
import static com.formdev.flatlaf.FlatClientProperties.*;
import javax.swing.JSplitPane; import javax.swing.JSplitPane;
/** /**
@@ -26,6 +27,29 @@ import javax.swing.JSplitPane;
*/ */
public class FlatSplitPane public class FlatSplitPane
extends JSplitPane extends JSplitPane
implements FlatStyleableComponent implements FlatComponentExtension, FlatStyleableComponent
{ {
// NOTE: enum names must be equal to allowed strings
/** @since 3.4.1 */ public enum ExpandableSide { both, left, right }
/**
* Returns what side of the spilt pane is allowed to expand
* via one-touch expanding arrow buttons.
*
* @since 3.4.1
*/
public ExpandableSide getExpandableSide() {
return getClientPropertyEnumString( SPLIT_PANE_EXPANDABLE_SIDE, ExpandableSide.class,
null, ExpandableSide.both );
}
/**
* Specifies what side of the spilt pane is allowed to expand
* via one-touch expanding arrow buttons.
*
* @since 3.4.1
*/
public void setExpandableSide( ExpandableSide expandableSide ) {
putClientPropertyEnumString( SPLIT_PANE_EXPANDABLE_SIDE, expandableSide );
}
} }

View File

@@ -18,7 +18,7 @@
// For maven compatibility, <font-version> should be in format <major>.<minor>[.<micro>]. // 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 // <build-number> is optional and should be incremented only if a new release is
// necessary, but the <font-version> has not changed. // necessary, but the <font-version> has not changed.
version = "3.19" version = "4.0"
if( !rootProject.hasProperty( "release" ) ) if( !rootProject.hasProperty( "release" ) )
version = version.toString() + "-SNAPSHOT" version = version.toString() + "-SNAPSHOT"
@@ -33,8 +33,8 @@ plugins {
dependencies { dependencies {
implementation( project( ":flatlaf-core" ) ) implementation( project( ":flatlaf-core" ) )
testImplementation( libs.bundles.junit ) testImplementation( libs.junit )
testRuntimeOnly( libs.junit.engine ) testRuntimeOnly( libs.junit.launcher )
} }
flatlafModuleInfo { flatlafModuleInfo {
@@ -56,7 +56,7 @@ tasks {
testLogging.exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL testLogging.exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
} }
withType<PublishToMavenRepository>().configureEach { withType<AbstractPublishToMaven>().configureEach {
onlyIf { !rootProject.hasProperty( "skipFonts" ) } onlyIf { !rootProject.hasProperty( "skipFonts" ) }
} }
} }
@@ -73,8 +73,8 @@ publishing {
pom { pom {
licenses { licenses {
license { license {
name.set( "SIL OPEN FONT LICENSE Version 1.1" ) name = "SIL OPEN FONT LICENSE Version 1.1"
url.set( "https://choosealicense.com/licenses/ofl-1.1/" ) url = "https://choosealicense.com/licenses/ofl-1.1/"
} }
} }
} }

View File

@@ -117,7 +117,7 @@ public class FlatInterFont
* new Font( FlatInterFont.FAMILY_SEMIBOLD, Font.ITALIC, 12 ); * new Font( FlatInterFont.FAMILY_SEMIBOLD, Font.ITALIC, 12 );
* }</pre> * }</pre>
*/ */
public static final String FAMILY_SEMIBOLD = "Inter Semi Bold"; public static final String FAMILY_SEMIBOLD = "Inter SemiBold";
/** /**
* Use for {@link #installStyle(String)} to install single font style. * Use for {@link #installStyle(String)} to install single font style.

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