Compare commits

..

226 Commits
3.2 ... 3.5.4

Author SHA1 Message Date
Karl Tauber
3ba9fc6c1c release 3.5.4 2024-12-09 00:44:57 +01:00
Karl Tauber
0a9ecd66a9 Linux: fixed NPE when using FlatLaf window decorations and switching theme (issue #933; regression in 3.5.3)
caused by fix for #907; commit d471f08b15
2024-12-09 00:43:44 +01:00
Karl Tauber
6991d6729e Merge PR #931: Fixing NPE when using HTML text on a component with null font
(cherry picked from commit 41332de275)
2024-12-08 23:01:00 +01:00
Karl Tauber
1462636e97 release 3.5.3
Some checks failed
CI / build (11, ) (push) Has been cancelled
CI / build (17, ) (push) Has been cancelled
CI / build (21, ) (push) Has been cancelled
CI / build (23, ) (push) Has been cancelled
CI / build (8, ) (push) Has been cancelled
CI / snapshot (push) Has been cancelled
CI / release (push) Has been cancelled
2024-12-06 12:42:15 +01:00
Karl Tauber
7e59a7f4af FlatPropertiesLaf: support macOS themes as base themes 2024-12-04 23:56:01 +01:00
Karl Tauber
e9a21848bc ComboBox: do not paint arrow button background if it is hidden (issue #915)
Some checks failed
CI / build (11, ) (push) Has been cancelled
CI / build (17, ) (push) Has been cancelled
CI / build (21, ) (push) Has been cancelled
CI / build (23, ) (push) Has been cancelled
CI / build (8, ) (push) Has been cancelled
CI / snapshot (push) Has been cancelled
CI / release (push) Has been cancelled
2024-12-04 19:24:10 +01:00
Karl Tauber
1dcb251ecb FlatLaf window decorations: fixed sometimes broken window moving with SplitPane in window title area in "full window content" mode (issue #926) 2024-12-04 18:21:06 +01:00
Karl Tauber
3f33543cee Linux: fixed slightly different font size (or letter width) used to paint HTML text when default font family is _Cantarell_ (e.g. on Fedora) (issue #912)
the removed use of floating point font size is similar to what is done in JDK for GTK Look and Feel:
- https://bugs.openjdk.org/browse/JDK-6979979
- 306f12db9e
2024-12-04 17:18:33 +01:00
Karl Tauber
84bd2088f2 FlatSystemProperties: javadoc fixes 2024-12-04 13:06:37 +01:00
Karl Tauber
4f4a3132c5 UIScale:
- do not use "defaultFont" if current Laf is not FlatLaf
- support custom font size divider to calculate user scale factor from font size

(for special use in JFormDesigner)
2024-12-04 13:06:11 +01:00
Karl Tauber
e064c934cb Windows: fixed detection of Windows 11 if custom exe launcher does not specify Windows 10+ compatibility in application manifest (issue #916)
Some checks failed
CI / build (11, ) (push) Has been cancelled
CI / build (17, ) (push) Has been cancelled
CI / build (21, ) (push) Has been cancelled
CI / build (23, ) (push) Has been cancelled
CI / build (8, ) (push) Has been cancelled
Native Libraries / Natives (macos) (push) Has been cancelled
Native Libraries / Natives (ubuntu) (push) Has been cancelled
Native Libraries / Natives (windows) (push) Has been cancelled
CI / snapshot (push) Has been cancelled
CI / release (push) Has been cancelled
Windows binaries built and signed locally in clean workspace
2024-11-28 14:12:56 +01:00
Karl Tauber
16fc3cabf2 Popup: fixed NPE if GraphicsConfiguration is null on Windows (issue #921)
Some checks failed
CI / build (11, ) (push) Waiting to run
CI / build (17, ) (push) Waiting to run
CI / build (21, ) (push) Waiting to run
CI / build (23, ) (push) Waiting to run
CI / build (8, ) (push) Waiting to run
CI / snapshot (push) Blocked by required conditions
CI / release (push) Blocked by required conditions
Native Libraries / Natives (macos) (push) Waiting to run
Native Libraries / Natives (ubuntu) (push) Waiting to run
Native Libraries / Natives (windows) (push) Waiting to run
Fonts / Fonts (inter) (push) Has been cancelled
Fonts / Fonts (jetbrains-mono) (push) Has been cancelled
Fonts / Fonts (roboto) (push) Has been cancelled
Fonts / Fonts (roboto-mono) (push) Has been cancelled
also check for null GraphicsConfiguration in other classes
2024-11-27 19:27:47 +01:00
Karl Tauber
7e002ff6c2 Theme Editor: fixed using color picker on secondary screen 2024-11-27 19:02:13 +01:00
Karl Tauber
323c0c62c3 update to Gradle 8.11.1 2024-11-20 20:11:34 +01:00
Karl Tauber
ff5bd301cc Popup: on Windows 10, fixed misplaced popup drop shadow (issue #911; regression in 3.5 since commit a311bac89b)
Some checks failed
CI / build (11, ) (push) Has been cancelled
CI / build (17, ) (push) Has been cancelled
CI / build (21, ) (push) Has been cancelled
CI / build (23, ) (push) Has been cancelled
CI / build (8, ) (push) Has been cancelled
CI / snapshot (push) Has been cancelled
CI / release (push) Has been cancelled
2024-11-19 23:25:50 +01:00
Karl Tauber
c37712b0f0 Windows: fixed wrong layout in maximized frame after changing screen scale factor (issue #904)
Some checks failed
CI / build (11, ) (push) Has been cancelled
CI / build (17, ) (push) Has been cancelled
CI / build (21, ) (push) Has been cancelled
CI / build (23, ) (push) Has been cancelled
CI / build (8, ) (push) Has been cancelled
Native Libraries / Natives (macos) (push) Has been cancelled
Native Libraries / Natives (ubuntu) (push) Has been cancelled
Native Libraries / Natives (windows) (push) Has been cancelled
CI / snapshot (push) Has been cancelled
CI / release (push) Has been cancelled
Windows binaries built and signed locally in clean workspace
2024-11-17 19:41:54 +01:00
Karl Tauber
ee9e238592 Windows: fixed possible deadlock with TabbedPane in window title area in "full window content" mode (issue #909)
Some checks failed
CI / build (11, ) (push) Has been cancelled
CI / build (17, ) (push) Has been cancelled
CI / build (21, ) (push) Has been cancelled
CI / build (23, ) (push) Has been cancelled
CI / build (8, ) (push) Has been cancelled
Fonts / Fonts (inter) (push) Has been cancelled
Fonts / Fonts (jetbrains-mono) (push) Has been cancelled
Fonts / Fonts (roboto) (push) Has been cancelled
Fonts / Fonts (roboto-mono) (push) Has been cancelled
Native Libraries / Natives (macos) (push) Has been cancelled
Native Libraries / Natives (ubuntu) (push) Has been cancelled
Native Libraries / Natives (windows) (push) Has been cancelled
CI / snapshot (push) Has been cancelled
CI / release (push) Has been cancelled
2024-11-14 19:34:31 +01:00
Karl Tauber
da5d6fa157 update to Gradle 8.11 2024-11-14 18:39:37 +01:00
Karl Tauber
d471f08b15 Linux: fixed continuous cursor toggling between resize and standard cursor when resizing window with FlatLaf window decorations (issue #907) 2024-11-14 18:34:55 +01:00
Karl Tauber
b97424f767 HTML: fixed wrong rendering if HTML text contains <style> tag with attributes (e.g. <style type='text/css'>) (issue #905; regression in 3.5)
Some checks failed
CI / build (11, ) (push) Has been cancelled
CI / build (17, ) (push) Has been cancelled
CI / build (21, ) (push) Has been cancelled
CI / build (23, ) (push) Has been cancelled
CI / build (8, ) (push) Has been cancelled
CI / snapshot (push) Has been cancelled
CI / release (push) Has been cancelled
2024-11-10 13:28:02 +01:00
Karl Tauber
c29a276188 release 3.5.2
Some checks failed
CI / build (11, ) (push) Has been cancelled
CI / build (17, ) (push) Has been cancelled
CI / build (21, ) (push) Has been cancelled
CI / build (23, ) (push) Has been cancelled
CI / build (8, ) (push) Has been cancelled
CI / snapshot (push) Has been cancelled
CI / release (push) Has been cancelled
2024-10-18 13:28:53 +02:00
Karl Tauber
d1694aa8bd FlatClientProperties and FlatSystemProperties: javadoc fixes 2024-10-18 13:28:07 +02:00
Karl Tauber
570cf6fc51 FlatLaf window decorations: added client property JRootPane.titleBarHeight to allow specifying a (larger) preferred height for the title bar (issue #897)
Some checks are pending
CI / build (11, ) (push) Waiting to run
CI / build (17, ) (push) Waiting to run
CI / build (21, ) (push) Waiting to run
CI / build (23, ) (push) Waiting to run
CI / build (8, ) (push) Waiting to run
CI / snapshot (push) Blocked by required conditions
CI / release (push) Blocked by required conditions
2024-10-17 19:58:58 +02:00
Karl Tauber
8eab86e489 FlatLaf window decorations: strech iconify/maximize/close buttons to always fill whole title bar height (issue #897) 2024-10-17 19:49:51 +02:00
Karl Tauber
566568f61a Windows: fixed repaint issues (ghosting) on some systems by setting sun.java2d.d3d.onscreen to false (issue #887) 2024-10-17 13:19:04 +02:00
Karl Tauber
56a73a4d17 Popup: added system property flatlaf.useRoundedPopupBorder to allow disabling native rounded popup borders (PRs #643 and #772) 2024-10-15 00:29:15 +02:00
Karl Tauber
656d25b75e Popup: setup rounded popup border after window was created (no longer create window ourself using addNotify()) to (hopefully) fix repaint issues on some Windows 11 systems after first showing a popup (issue #887, PR #643)
Some checks failed
CI / build (11, ) (push) Has been cancelled
CI / build (17, ) (push) Has been cancelled
CI / build (21, ) (push) Has been cancelled
CI / build (23, ) (push) Has been cancelled
CI / build (8, ) (push) Has been cancelled
CI / snapshot (push) Has been cancelled
CI / release (push) Has been cancelled
2024-10-12 23:25:59 +02:00
Karl Tauber
dcdc80ade3 Testing: FlatOptionPaneTest: test option pane with custom title bar icon (issue #886) 2024-10-12 00:28:19 +02:00
Karl Tauber
09f2d65d5e change snapshot version from 3.6-SNAPSHOT to 3.5.2-SNAPSHOT
Some checks failed
CI / build (11, ) (push) Waiting to run
CI / build (17, ) (push) Waiting to run
CI / build (21, ) (push) Waiting to run
CI / build (23, ) (push) Waiting to run
CI / build (8, ) (push) Waiting to run
CI / snapshot (push) Blocked by required conditions
CI / release (push) Blocked by required conditions
Fonts / Fonts (inter) (push) Has been cancelled
Fonts / Fonts (jetbrains-mono) (push) Has been cancelled
Fonts / Fonts (roboto) (push) Has been cancelled
Fonts / Fonts (roboto-mono) (push) Has been cancelled
Native Libraries / Natives (macos) (push) Has been cancelled
Native Libraries / Natives (ubuntu) (push) Has been cancelled
Native Libraries / Natives (windows) (push) Has been cancelled
2024-10-11 19:27:23 +02:00
Karl Tauber
b304d46f7e TextComponents: fixed too fast scrolling in multi-line text components when using touchpads (e.g. on macOS) (issue #892) 2024-10-11 19:18:00 +02:00
Karl Tauber
3391f971ec GitHub Actions: build using Java 23 2024-10-11 15:16:49 +02:00
Karl Tauber
778fed27a5 update to Gradle 8.10.2 2024-10-11 15:14:11 +02:00
Karl Tauber
1755dbc877 README.md updated 2024-10-11 15:11:56 +02:00
Karl Tauber
4e6f538519 ToolBar: fixed endless loop if button in Toolbar has focus and is made invisible (issue #884)
Some checks failed
CI / build (11, ) (push) Has been cancelled
CI / build (17, ) (push) Has been cancelled
CI / build (21, ) (push) Has been cancelled
CI / build (21, 22) (push) Has been cancelled
CI / build (8, ) (push) Has been cancelled
CI / snapshot (push) Has been cancelled
CI / release (push) Has been cancelled
2024-09-29 19:26:37 +02:00
Karl Tauber
a6ecb0ef85 FlatLaf window decorations on Windows: fixed possible application freeze when using custom component that overrides Component.contains(int x, int y) and invokes SwingUtilities.convertPoint() (or similar) from the overridden method (issue #878) 2024-09-04 00:48:42 +02:00
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
Karl Tauber
19f27a8d56 release 3.3 2024-01-11 18:14:17 +01:00
Karl Tauber
cf3fa17666 fixed typos and grammar 2024-01-11 18:11:09 +01:00
Karl Tauber
6fdc56f2d3 Merge PR #790: Table header background is never restored after hover when the renderer background is null 2024-01-10 19:26:22 +01:00
Karl Tauber
9f17a5b26d TableHeader: restore also renderer foreground after hover 2024-01-10 19:19:01 +01:00
Karl Tauber
fa53e90847 Merge PR #788: Support for hover/pressed on SplitPane divider 2024-01-10 16:43:28 +01:00
Karl Tauber
50c630f403 Table: fixed border arc of components in complex table cell editors (issue #786) 2024-01-10 16:37:32 +01:00
Dar
5630c161ea fix: restore the renderer background after hover
The table header background is never restored after hover when the label renderer is opaque
2024-01-09 16:58:21 +01:00
Karl Tauber
7d16ff9e79 SplitPane:
- removed `SplitPaneDivider.showHover` (hover is now enabled if hover color is specified)
- added `SplitPaneDivider.pressedColor` (for pressed/dragging in continuous layout)
- fixed painting of dragging divider in non-continuous layout (was 1px too small; added grip)
- updated styling unit tests
2024-01-08 22:39:01 +01:00
Karl Tauber
a9ea9daec3 FileChooser: catch NPE in Java 21 when getting icon for .exe files that use default Windows exe icon (see https://bugs.openjdk.org/browse/JDK-8320692) 2024-01-08 20:29:20 +01:00
Karl Tauber
45bdd40dce Viewport: use method handle, instead of reflection, to get view UI faster 2024-01-08 20:12:26 +01:00
Karl Tauber
a2bca88eec removed support for JetBrains custom decorations 2024-01-08 16:22:20 +01:00
Karl Tauber
c0dd02ee13 TabbedPane: paint rounded tab area background for rounded cards (issue #717) 2024-01-08 13:22:59 +01:00
Karl Tauber
97495a6093 TabbedPane: avoid "jumping" tab area when switching L&F or when mouse wheel scrolling to last tab (regression in commit 4ad45088c4) 2023-12-31 12:56:43 +01:00
Karl Tauber
4ad45088c4 TabbedPane: fixed "endless" layouting and repainting when using nested tabbed panes (top and bottom tab placement) and RSyntaxTextArea (with enabled line-wrapping) as tab content (see https://github.com/skylot/jadx/issues/2030)
instead of using `BasicTabbedPaneUI.TabbedPaneLayout.layoutContainer()`, now layouting all components ourself and avoid double moving/resizing of some components
2023-12-30 12:56:58 +01:00
Dar
d6d1d4b1b7 fix: missing properties in the expected map 2023-12-29 15:12:06 +01:00
Dar
6e453c170f new: support for hover on splitpane divider 2023-12-29 12:38:34 +01:00
Karl Tauber
beb2deee52 TabbedPane: further reduced duplicate code in nested classes FlatTabbedPaneLayout and FlatTabbedPaneScrollLayout 2023-12-23 14:06:47 +01:00
Karl Tauber
9c69043b2c TabbedPane: reduced duplicate code in nested classes FlatTabbedPaneLayout and FlatTabbedPaneScrollLayout 2023-12-23 00:28:44 +01:00
Karl Tauber
4df34b3f9d TableHeader: no longer temporary replace header cell renderer while painting (issue https://github.com/apache/netbeans/issues/6835) 2023-12-22 14:08:49 +01:00
Karl Tauber
ee01756188 TabbedPane:
- avoid unnecessary repainting whole tabbed pane content area when layouting leading/trailing components
- avoid unnecessary repainting of selected tab on temporary changes
2023-12-21 18:59:11 +01:00
Karl Tauber
0386aaa18b Merge PR #758: TabbedPane: support rotated/vertical tabs 2023-12-21 17:41:49 +01:00
Karl Tauber
92c4230cde Merge PR #713: Scrollpane rounded border 2023-12-21 17:40:00 +01:00
Karl Tauber
26165999e0 Table: fixed background of boolean columns when using alternating row colors (issue #780) 2023-12-21 16:58:49 +01:00
Karl Tauber
38b2641078 NativeLibrary: in development environment, load native library from 'src' folder 2023-12-14 15:29:02 +01:00
Karl Tauber
4e19169312 Merge PR #772: macOS: native rounded borders for popups 2023-12-14 15:19:01 +01:00
Karl Tauber
46de81c1c9 macOS native:
- removed `FlatNativeMacLibrary.getWindowPtr()` because it is too dangerous to use `windowPtr` (which is `NSWindow*`) in Java (using invalid window pointer would crash app)
- made `getNSWindow()` 20x faster
- catch exceptions in `getNSWindow()`
- digitally signed dylibs
2023-12-14 14:39:26 +01:00
Karl Tauber
9bf4da7acf Native macOS libraries updated for PR #772
built by GitHub Actions:
https://github.com/JFormDesigner/FlatLaf/actions/runs/7151969281
2023-12-09 17:04:03 +01:00
Karl Tauber
6f32236fb7 macOS: native rounded borders for popups (issue #715) 2023-12-09 16:12:35 +01:00
Karl Tauber
c25d857e78 UI defaults dumps updated on macOS for commits 9fef2f9d05 and ce527329a6 2023-12-08 19:07:32 +01:00
Karl Tauber
8cc2925fd0 FlatPopupFactory: reordered some methods (nothing else changed) 2023-12-08 17:53:51 +01:00
Karl Tauber
2b87d3c4db Native window libraries: updated Windows DLLs for commit 7f6f366744
built by GitHub Actions:
https://github.com/JFormDesigner/FlatLaf/actions/runs/7132082543

locally signed Windows DLLs with FormDev Software code signing certificate
2023-12-07 19:02:43 +01:00
Karl Tauber
7f6f366744 flatlaf-natives-windows: support DWM attributes DWMWA_USE_IMMERSIVE_DARK_MODE, DWMWA_CAPTION_COLOR and DWMWA_TEXT_COLOR (all unused in FlatLaf core) 2023-12-07 18:53:45 +01:00
Karl Tauber
b1fdbde5cd ScrollPane: allow specifying scroll pane border arc for multi-line text components, lists, tables and trees 2023-11-25 23:29:39 +01:00
Karl Tauber
417f0f5f1c fixed broken rendering after resizing window to minimum size and then increasing size again (issue #767) 2023-11-25 19:14:39 +01:00
Karl Tauber
ec7673790c Demo: added macOS themes to control bar combobox (F5 and F6) 2023-11-14 11:42:54 +01:00
Karl Tauber
7d0bdf3b9e OptionPane: fixed styling custom panel background in JOptionPane (issue #761) 2023-11-14 10:38:28 +01:00
Karl Tauber
2ef5270095 TabbedPane: support rotated/vertical tabs (issue #633) 2023-11-04 19:55:01 +01:00
Karl Tauber
61ba011c3b Testing: introduced FlatTestEnumSelector (a toolbar with a single button group) for easier testing 2023-11-01 14:53:06 +01:00
Karl Tauber
8d8b9f3e98 macOS themes: fixed ToolBar.hoverButtonGroupArc 2023-11-01 14:52:17 +01:00
Karl Tauber
69899ec29f ToolBar: added styling properties separatorWidth and separatorColor 2023-11-01 12:55:06 +01:00
Karl Tauber
5063621c95 junit updated to 5.10.0 2023-11-01 12:07:03 +01:00
Karl Tauber
030177f739 Theme Editore: updated rsyntaxtextarea and autocomplete to latest versions 2023-10-31 16:58:32 +01:00
Karl Tauber
808f5a6381 Button and ToggleButton: selected buttons did not use explicitly set foreground color (issue #756) 2023-10-31 16:14:48 +01:00
Karl Tauber
9602b191a9 jide-oss: updated to 3.7.14 2023-10-22 17:56:36 +02:00
Karl Tauber
34bd2d781c Table: switching theme looses table grid and intercell spacing (issues #733 and #750) 2023-10-22 17:55:10 +02:00
Karl Tauber
cf364c1264 release 3.2.5 2023-10-21 23:53:46 +02:00
Karl Tauber
a997820bb6 Merge PR #754: Fixing NPE when showing a popup without an invoker on Windows 10 2023-10-21 23:52:02 +02:00
Eduwardo Horibe
b8fabd59c0 Fixing NPE when showing a popup without an invoker on Windows 10 2023-10-21 15:26:55 -03:00
Karl Tauber
97d290795e release 3.2.4 2023-10-21 18:23:17 +02:00
Karl Tauber
2a237ff5fc Popup: fixed potential NPE in (unusual) case that the popup invoker is null (only on Linux with Wayland and Java 21; regression in 3.2.3) (issue #752) 2023-10-21 18:21:59 +02:00
Karl Tauber
13a418f74e release 3.2.3 2023-10-21 17:11:32 +02:00
Karl Tauber
5c56dbfed6 Popup: popups that request focus were not shown on Linux with Wayland and Java 21 (issue #752) 2023-10-21 17:04:22 +02:00
Karl Tauber
0d2f37e1da release 3.2.2 2023-10-15 18:12:11 +02:00
Karl Tauber
0494c2161c FileChooser: avoid unnecessary logging of InaccessibleObjectException when running in Java 16 (issue #741) 2023-10-15 18:00:33 +02:00
Karl Tauber
635a620439 jsvg: updated to 1.2.0 2023-10-15 16:42:15 +02:00
Karl Tauber
0a7c76ec72 GitHub Actions:
- build using Java 21 (use toolchain because Gradle 8.4 does not support running on Java 21)
- no longer build on Java 19 and 20
2023-10-15 16:38:28 +02:00
Karl Tauber
9ad8fb38e8 update to Gradle 8.4 2023-10-15 16:34:42 +02:00
Karl Tauber
1dbe968952 TabbedPane: fixed NPE when using focusable component as tab component and switching theme (issue #745) 2023-10-15 11:43:58 +02:00
Karl Tauber
460b6492cb Button: fixed painting icon and text at wrong location when using HTML text, left/right vertical alignment and running in Java 19+ (issue #746) 2023-10-14 19:16:23 +02:00
Karl Tauber
67b0faa9ae Merge PR #738: Fix typo 2023-09-28 16:25:11 +02:00
Karl Tauber
5553425a1a CheckBox and RadioButton: fixed cut off right side when border is removed and horizontal alignment is set to right (issue #734) 2023-09-28 16:22:52 +02:00
valerakostin
8ff516e43a Fix typo 2023-09-22 11:14:50 +02:00
Karl Tauber
b6207bafde release 3.2.1 2023-09-04 13:22:50 +02:00
Karl Tauber
b9f43fd560 jsvg: updated to 1.1.0 2023-09-04 13:13:14 +02:00
Karl Tauber
c617d9f569 Theme Editor: removed shapshot download link from README.md 2023-09-04 13:09:49 +02:00
Karl Tauber
9efb9761c6 MultiResolutionImageSupport optimizations:
- ProducerMultiResolutionImage: avoid creation of base image to get image width and height, because if screen is scaled then the base image would be never painted, but consumes memory and takes time to create it
- MappedMultiResolutionImage: delegate getting width/height/source/property to original image, to defer/avoid creation of mapped image
2023-09-04 13:08:21 +02:00
Karl Tauber
03f9115fbf MultiResolutionImageSupport: fixed memory leak in create(int,Dimension[],Function<Dimension,Image>) (issue #726) 2023-09-04 12:22:40 +02:00
Karl Tauber
a2859cedb5 Popop: fixed drop shadow if popup overlaps a heavyweight component (Windows 10 only; issue #626) 2023-09-02 12:48:46 +02:00
Karl Tauber
0c604b1023 ScrollPane: increase viewport width for rounded border to remove/reduce gap between view and vertical scrollbar 2023-08-27 18:14:03 +02:00
Karl Tauber
cdee0594f8 TextField: fixed placeholder text painting, which did not respect horizontal alignment property of JTextField (issue #721) 2023-08-27 16:30:16 +02:00
Karl Tauber
808833d749 UIDefaultsDump: dump action maps (ActionMap) 2023-08-25 14:02:22 +02:00
Karl Tauber
581c64b601 FileChooser: fixed occasional NPE in FlatShortcutsPanel on Windows (issue #718) 2023-08-23 19:40:59 +02:00
Karl Tauber
40418607e5 ScrollPane: fixed lost styling on ScrollPane border if using Table as view component 2023-08-13 23:23:03 +02:00
Karl Tauber
5436ea88d8 ScrollPane: improved/fixed calculation of left/right padding for rounded border 2023-08-13 17:01:56 +02:00
Karl Tauber
7bec5ec6dc ScrollPane: support rounded border 2023-08-13 14:01:09 +02:00
Karl Tauber
c953ff84d0 added explicit file encoding for Eclipse projects 2023-08-12 22:45:02 +02:00
Karl Tauber
96cd207df3 README.md: minor updates 2023-08-12 17:04:21 +02:00
Karl Tauber
7a75f62a6a README.md: new applications using FlatLaf:
- Constellation
- Ghidra
- jadx
- muCommander
- Guiffy
- HashGarten
- MediathekView
- Astah
- Big Faceless (BFO) PDF Viewer
- Chatty
- BGBlitz
- Linux Task Manager (LTM)
2023-08-12 16:35:00 +02:00
Karl Tauber
61e5fe58c2 README.md: organized applications using FlatLaf into categories 2023-08-12 15:57:32 +02:00
Karl Tauber
1a3baba702 README.md: removed versions and 'New' badge from applications using FlatLaf 2023-08-12 14:04:23 +02:00
Karl Tauber
58dc14bb46 Gradle: use System.getProperty( "org.gradle.parallel" ) instead of rootProject.property( "org.gradle.parallel" ) because this did not work with VM command-line option -Dorg.gradle.parallel=false 2023-08-12 13:38:42 +02:00
Karl Tauber
a5b7e04943 Gradle: check whether parallel build is enabled when running 'publish' task
https://stackoverflow.com/questions/72664149/gradle-maven-publish-sonatype-creates-multiple-repositories-that-cant-be-clos
2023-08-12 13:23:11 +02:00
Karl Tauber
22f2aa5475 README.md: introduced "Sponsor" badge icon
SVGO option `convertPathData` enabled
2023-08-11 13:21:56 +02:00
Karl Tauber
d4e9cb12be updated sigtest for FlatLaf 3.2
(generated in clean workspace with gradle task `sigtestGenerate`)
2023-08-10 23:31:26 +02:00
290 changed files with 21965 additions and 4052 deletions

View File

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

View File

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

View File

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

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

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

View File

@@ -1,6 +1,327 @@
FlatLaf Change Log
==================
## 3.5.4
#### Fixed bugs
- HTML: Fixed NPE when using HTML text on a component with `null` font. (issue
#930; PR #931; regression in 3.5)
- Linux: Fixed NPE when using FlatLaf window decorations and switching theme.
(issue #933; regression in 3.5.3)
## 3.5.3
#### Fixed bugs
- HTML: Fixed wrong rendering if HTML text contains `<style>` tag with
attributes (e.g. `<style type='text/css'>`). (issue #905; regression in 3.5.1)
- FlatLaf window decorations:
- Windows: Fixed possible deadlock with TabbedPane in window title area in
"full window content" mode. (issue #909)
- Windows: Fixed wrong layout in maximized frame after changing screen scale
factor. (issue #904)
- Linux: Fixed continuous cursor toggling between resize and standard cursor
when resizing window. (issue #907)
- Fixed sometimes broken window moving with SplitPane in window title area in
"full window content" mode. (issue #926)
- Popup: On Windows 10, fixed misplaced popup drop shadow. (issue #911;
regression in 3.5)
- Popup: Fixed NPE if `GraphicsConfiguration` is `null` on Windows. (issue #921)
- Theme Editor: Fixed using color picker on secondary screen.
- Fixed detection of Windows 11 if custom exe launcher does not specify Windows
10+ compatibility in application manifest. (issue #916)
- Linux: Fixed slightly different font size (or letter width) used to paint HTML
text when default font family is _Cantarell_ (e.g. on Fedora). (issue #912)
#### Other Changes
- Class `FlatPropertiesLaf` now supports FlatLaf macOS themes as base themes.
## 3.5.2
#### Fixed bugs
- Windows: Fixed repaint issues (ghosting) on some systems (probably depending
on graphics card/driver). This is done by setting Java system property
`sun.java2d.d3d.onscreen` to `false` (but only if `sun.java2d.d3d.onscreen`,
`sun.java2d.d3d` and `sun.java2d.noddraw` are not yet set), which disables
usage of Windows Direct3D (DirectX) onscreen surfaces. Component rendering
still uses Direct3D. (issue #887)
- FlatLaf window decorations:
- Iconify/maximize/close buttons did not fill whole title bar height, if some
custom component in menu bar increases title bar height. (issue #897)
- Windows: Fixed possible application freeze when using custom component that
overrides `Component.contains(int x, int y)` and invokes
`SwingUtilities.convertPoint()` (or similar) from the overridden method.
(issue #878)
- TextComponents: Fixed too fast scrolling in multi-line text components when
using touchpads (e.g. on macOS). (issue #892)
- ToolBar: Fixed endless loop if button in Toolbar has focus and is made
invisible. (issue #884)
#### Other Changes
- FlatLaf window decorations: Added client property `JRootPane.titleBarHeight`
to allow specifying a (larger) preferred height for the title bar. (issue
#897)
- Added system property `flatlaf.useRoundedPopupBorder` to allow disabling
native rounded popup borders on Windows 11 and macOS. On macOS 14.4+, where
rounded popup borders are disabled since FlatLaf 3.5 because of occasional
problems, you can use this to enable rounded popup borders (at your risk).
## 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
#### New features and improvements
- macOS (10.14+): Popups (`JPopupMenu`, `JComboBox`, `JToolTip`, etc.) now use
native macOS rounded borders. (PR #772; issue #715)
- Native libraries: Added `libflatlaf-macos-arm64.dylib` and
`libflatlaf-macos-x86_64.dylib`. See also
https://www.formdev.com/flatlaf/native-libraries/.
- ScrollPane: Support rounded border. (PR #713)
- SplitPane: Support divider hover and pressed background colors. (PR #788)
- TabbedPane: Support vertical tabs. (PR #758, issue #633)
- TabbedPane: Paint rounded tab area background for rounded cards. (issue #717)
- ToolBar: Added styling properties `separatorWidth` and `separatorColor`.
#### Fixed bugs
- Button and ToggleButton: Selected buttons did not use explicitly set
foreground color. (issue #756)
- FileChooser: Catch NPE in Java 21 when getting icon for `.exe` files that use
default Windows exe icon. (see
[JDK-8320692](https://bugs.openjdk.org/browse/JDK-8320692))
- OptionPane: Fixed styling custom panel background in `JOptionPane`. (issue
#761)
- ScrollPane: Styling ScrollPane border properties did not work if view
component is a Table.
- Table:
- Switching theme looses table grid and intercell spacing. (issues #733 and
#750)
- Fixed background of `boolean` columns when using alternating row colors.
(issue #780)
- Fixed border arc of components in complex table cell editors. (issue #786)
- TableHeader:
- No longer temporary replace header cell renderer while painting. This avoids
a `StackOverflowError` in case that custom renderer does this too. (see
[NetBeans issue #6835](https://github.com/apache/netbeans/issues/6835)) This
also improves compatibility with custom table header implementations.
- Header cell renderer background/foreground colors were not restored after
hover if renderer uses `null` for background/foreground. (PR #790)
- TabbedPane:
- Avoid unnecessary repainting whole tabbed pane content area when layouting
leading/trailing components.
- Avoid unnecessary repainting of selected tab on temporary changes.
- Fixed "endless" layouting and repainting when using nested tabbed panes (top
and bottom tab placement) and RSyntaxTextArea (with enabled line-wrapping)
as tab content. (see
[jadx issue #2030](https://github.com/skylot/jadx/issues/2030))
- Fixed broken rendering after resizing window to minimum size and then
increasing size again. (issue #767)
#### Incompatibilities
- Removed support for JetBrains custom decorations, which required
[JetBrains Runtime](https://github.com/JetBrains/JetBrainsRuntime/wiki) (JBR)
8 or 11. It did not work for JBR 17. System property
`flatlaf.useJetBrainsCustomDecorations` is now ignored. **Note**: FlatLaf
window decorations continue to work with JBR.
## 3.2.5
#### Fixed bugs
- Popup: Fixed NPE if popup invoker is `null` on Windows 10. (issue #753;
regression in 3.2.1 in fix for #626)
## 3.2.4
#### Fixed bugs
- Popup: Fixed NPE if popup invoker is `null` on Linux with Wayland and Java 21.
(issue #752; regression in 3.2.3)
## 3.2.3
#### Fixed bugs
- Popup: Popups that request focus were not shown on Linux with Wayland and Java 21.
(issue #752)
## 3.2.2
#### Fixed bugs
- Button: Fixed painting icon and text at wrong location when using HTML text,
left/right vertical alignment and running in Java 19+. (issue #746)
- CheckBox and RadioButton: Fixed cut off right side when border is removed and
horizontal alignment is set to `right`. (issue #734)
- TabbedPane: Fixed NPE when using focusable component as tab component and
switching theme. (issue #745)
## 3.2.1
#### Fixed bugs
- Fixed memory leak in
`MultiResolutionImageSupport.create(int,Dimension[],Function<Dimension,Image>)`,
which caches images created by the producer function. Used by
`FlatSVGIcon.getImage()` and `FlatSVGUtils.createWindowIconImages()`. If you
use one of these methods, it is **strongly recommended** to upgrade to this
version, because if the returned image is larger and painted very often it may
result in an out-of-memory situation. (issue #726)
- FileChooser: Fixed occasional NPE in `FlatShortcutsPanel` on Windows. (issue
#718)
- TextField: Fixed placeholder text painting, which did not respect horizontal
alignment property of `JTextField`. (issue #721)
- Popup: Fixed drop shadow if popup overlaps a heavyweight component. (Windows
10 only; issue #626)
## 3.2
#### New features and improvements
@@ -123,7 +444,6 @@ FlatLaf Change Log
- Windows DLLs are now digitally signed with FormDev Software GmbH
certificate.
#### Fixed bugs
- FlatLaf window decorations:
@@ -659,7 +979,7 @@ FlatLaf Change Log
- Native window decorations (Windows 10 only):
- Fixed occasional application crash in `flatlaf-windows.dll`. (issue #357)
- 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
space with the window background color (instead of black) before the layout
is updated.

293
README.md
View File

@@ -6,7 +6,7 @@ Swing desktop applications.
It looks almost flat (no shadows or gradients), clean, simple and elegant.
FlatLaf comes with **Light**, **Dark**, **IntelliJ** and **Darcula** themes,
scales on **HiDPI** displays and runs on Java 8 or newer.
scales on **HiDPI** displays and runs on Java 8 or newer (LTS and latest).
The look is heavily inspired by **Darcula** and **IntelliJ** themes from
IntelliJ IDEA 2019.2+ and uses almost the same colors and icons.
@@ -33,14 +33,20 @@ FlatLaf can use 3rd party themes created for IntelliJ Platform (see
Sponsors
--------
### Current Sponsors
[![None Sponsors](images/none-sponsors.png)](https://www.formdev.com/flatlaf/sponsor/)
[Become a Sponsor](https://www.formdev.com/flatlaf/sponsor/)
### Previous Sponsors
<a href="https://www.ej-technologies.com/"><img src="https://www.formdev.com/flatlaf/sponsor/ej-technologies.png" width="200" alt="ej-technologies" title="ej-technologies - Java APM, Java Profiler, Java Installer Builder"></a>
&nbsp; &nbsp; &nbsp; &nbsp;
<a href="https://www.dbvis.com/"><img src="https://www.formdev.com/flatlaf/sponsor/dbvisualizer.svg" width="200" alt="DbVisualizer" title="DbVisualizer - SQL Client and Editor"></a>
&nbsp; &nbsp; &nbsp; &nbsp;
<a href="https://www.dscsag.com/"><img src="https://www.formdev.com/flatlaf/sponsor/DSC.png" height="48" alt="DSC Software AG" title="DSC Software AG - Your Companion for Integrative PLM"></a>
[Become a Sponsor](https://www.formdev.com/flatlaf/sponsor/)
Demo
----
@@ -62,7 +68,7 @@ build script:
artifactId: flatlaf
version: (see button below)
Otherwise download `flatlaf-<version>.jar` here:
Otherwise, download `flatlaf-<version>.jar` here:
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf/badge.svg?style=flat-square&color=007ec6)](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf)
@@ -141,7 +147,7 @@ details and downloads.
Buzz
----
- [What others say about FlatLaf on Twitter](https://twitter.com/search?f=live&q=flatlaf)
- [FlatLaf 3.1 (and 3.0) announcement on Reddit](https://www.reddit.com/r/java/comments/12xgrsu/flatlaf_31_and_30_swing_look_and_feel/)
- [FlatLaf 1.0 announcement on Reddit](https://www.reddit.com/r/java/comments/lsbcwe/flatlaf_10_swing_look_and_feel/)
- [FlatLaf announcement on Reddit](https://www.reddit.com/r/java/comments/dl0hu3/flatlaf_flat_look_and_feel/)
@@ -149,125 +155,196 @@ Buzz
Applications using FlatLaf
--------------------------
- ![New](images/new.svg)
[Spark](https://github.com/igniterealtime/Spark) - cross-platform IM client optimized for businesses and organizations.
- ![New](images/new.svg)
[Kafka Visualizer](https://github.com/kumait/kafkavisualizer) Kafka GUI client
- ![New](images/new.svg)
### Featured
- ![Sponsor](images/sponsor.svg) [JFormDesigner](https://www.formdev.com/)
(**commercial**) - Java/Swing GUI Designer (from the FlatLaf creators)
- ![Sponsor](images/sponsor.svg)
[JProfiler](https://www.ej-technologies.com/products/jprofiler/overview.html)
12 (**commercial**) - the award-winning all-in-one Java profiler
- ![New](images/new.svg) [JFormDesigner](https://www.formdev.com/) 8
(**commercial**) - Java/Swing GUI Designer
- ![New](images/new.svg) [Jeyla Studio](https://www.jeylastudio.com/) - Salon
Software
- ![New](images/new.svg) [Fanurio](https://www.fanuriotimetracking.com/) 3.3.2
(**commercial**) - time tracking and billing for freelancers and teams
- ![New](images/new.svg) [Antares](https://www.antarescircuit.io/) - a free,
powerful platform for designing, simulating and explaining digital circuits
- ![New](images/new.svg)
[Logisim-evolution](https://github.com/logisim-evolution/logisim-evolution)
3.6 - Digital logic design tool and simulator
- ![New](images/new.svg) [Cinecred](https://loadingbyte.com/cinecred/) - create
beautiful film credit sequences
- ![New](images/new.svg) [tinyMediaManager](https://www.tinymediamanager.org/)
v4 (**commercial**) - a media management tool
- ![New](images/new.svg) [Weasis](https://nroduit.github.io/) - medical DICOM
viewer used in healthcare by hospitals, health networks, etc
- ![New](images/new.svg)
[Makelangelo Software](https://github.com/MarginallyClever/Makelangelo-software)
7.3.0 - for plotters, especially the wall-hanging polargraph
- ![Hot](images/hot.svg) [Ultorg](https://www.ultorg.com/) (**commercial**) - a
visual query system for relational databases
- [MooInfo](https://github.com/rememberber/MooInfo) - visual implementation of
OSHI, to view information about the system and hardware
- [Jailer](https://github.com/Wisser/Jailer) 11.2 - database subsetting and
relational data browsing tool
- ![Hot](images/hot.svg) [Apache NetBeans](https://netbeans.apache.org/) 11.3 -
IDE for Java, PHP, HTML and much more
- [jclasslib bytecode viewer](https://github.com/ingokegel/jclasslib) 5.5
- [KeyStore Explorer](https://keystore-explorer.org/) 5.4.3
- ![Hot](images/hot.svg)
(**commercial**) - the award-winning all-in-one Java profiler
- ![Sponsor](images/sponsor.svg)
[install4j](https://www.ej-technologies.com/products/install4j/overview.html)
9.0 (**commercial**) - the powerful multi-platform Java installer builder
- ![Hot](images/hot.svg) [DbVisualizer](https://www.dbvis.com/) 12.0
(**commercial**) - the powerful multi-platform Java installer builder
- ![Sponsor](images/sponsor.svg) [DbVisualizer](https://www.dbvis.com/)
(**commercial**) - the universal database tool for developers, analysts and
DBAs
- ![Hot](images/hot.svg) [MagicPlot](https://magicplot.com/) 3.0
(**commercial**) - Software for nonlinear fitting, plotting and data analysis
- ![Hot](images/hot.svg)
[Thermo-Calc](https://thermocalc.com/products/thermo-calc/) 2021a
(**commercial**) - Thermodynamics and Properties Software
- ![Hot](images/hot.svg) [ZAP](https://www.zaproxy.org/) 2.10 - the world's
most widely used web app scanner
- ![Hot](images/hot.svg) [Apache NetBeans](https://netbeans.apache.org/) - IDE
for Java, PHP, HTML and much more
- ![Sponsor](images/sponsor.svg)
[Thermo-Calc](https://thermocalc.com/products/thermo-calc/) (**commercial**) -
Thermodynamics and Properties Software
### Data
- ![Hot](images/hot.svg) [Ultorg](https://www.ultorg.com/) (**commercial**) - a
visual query system for relational databases
- [Jailer](https://github.com/Wisser/Jailer) - database subsetting and
relational data browsing tool
- ![Hot](images/hot.svg) [MagicPlot](https://magicplot.com/) (**commercial**) -
Software for nonlinear fitting, plotting and data analysis
- ![New](images/new.svg) [Constellation](https://www.constellation-app.com/) -
Data Visualization and Analytics (based on NetBeans platform)
- ![New](images/new.svg)
[Kafka Visualizer](https://github.com/kumait/kafkavisualizer) - Kafka GUI
client
### Security
- ![Hot](images/hot.svg) [ZAP](https://www.zaproxy.org/) - the world's most
widely used web app scanner
- ![Hot](images/hot.svg)
[Burp Suite Professional and Community Edition](https://portswigger.net/burp/pro)
2020.11.2 (**commercial**) - the leading software for web security testing
(**commercial**) - the leading software for web security testing
- ![New](images/new.svg)
[Ghidra](https://github.com/NationalSecurityAgency/ghidra) - a software
reverse engineering (SRE) framework
- ![New](images/new.svg) [jadx](https://github.com/skylot/jadx) - Dex to Java
decompiler
- [BurpCustomizer](https://github.com/CoreyD97/BurpCustomizer) - adds more
FlatLaf themes to Burp Suite
- [Total Validator](https://www.totalvalidator.com/) (**commercial**) - checks
your website
- [JPass](https://github.com/gaborbata/jpass) - password manager with strong
encryption
### Software Development
- [jclasslib bytecode viewer](https://github.com/ingokegel/jclasslib)
- [KeyStore Explorer](https://keystore-explorer.org/)
- ![New](images/new.svg)
[muCommander](https://github.com/mucommander/mucommander) - lightweight
cross-platform file manager
- ![New](images/new.svg) [Guiffy](https://www.guiffy.com/) (**commercial**) -
advanced cross-platform Diff/Merge
- ![New](images/new.svg) [HashGarten](https://github.com/jonelo/HashGarten) -
cross-platform Swing GUI for Jacksum
- [Pseudo Assembler IDE](https://github.com/tomasz-herman/PseudoAssemblerIDE) -
IDE for Pseudo-Assembler
- [Linotte](https://github.com/cpc6128/LangageLinotte) - French programming
language created to learn programming
- [lsfusion platform](https://github.com/lsfusion/platform) - information
systems development platform
### Electrical
- [Antares](https://www.antarescircuit.io/) - a free, powerful platform for
designing, simulating and explaining digital circuits
- [Logisim-evolution](https://github.com/logisim-evolution/logisim-evolution) -
Digital logic design tool and simulator
- [Makelangelo Software](https://github.com/MarginallyClever/Makelangelo-software) -
for plotters, especially the wall-hanging polargraph
- [GUIslice Builder](https://github.com/ImpulseAdventure/GUIslice-Builder) - GUI
builder for [GUIslice](https://github.com/ImpulseAdventure/GUIslice), a
lightweight GUI framework for embedded displays
- [ThunderFocus](https://github.com/marcocipriani01/ThunderFocus) -
Arduino-based telescope focuser
- [RemoteLight](https://github.com/Drumber/RemoteLight) - multifunctional LED
control software
### Media
- ![Hot](images/hot.svg) [jAlbum](https://jalbum.net/) (**commercial**) -
creates photo album websites
- ![New](images/new.svg) [MediathekView](https://mediathekview.de/) - search in
media libraries of various German broadcasters
- [Cinecred](https://loadingbyte.com/cinecred/) - create beautiful film credit
sequences
- [tinyMediaManager](https://www.tinymediamanager.org/) (**commercial**) - a
media management tool
- [Weasis](https://nroduit.github.io/) - medical DICOM viewer used in healthcare
by hospitals, health networks, etc
- [Shutter Encoder](https://www.shutterencoder.com/)
([source code](https://github.com/paulpacifico/shutter-encoder)) -
professional video converter and compression tool
- [Sound Analysis](https://github.com/tomasz-herman/SoundAnalysis) - analyze
sound files in time or frequency domain
- [Novel-Grabber](https://github.com/Flameish/Novel-Grabber) - download novels
from any webnovel and lightnovel site
- [lectureStudio](https://www.lecturestudio.org/) - digitize your lectures with
ease
### Modelling
- ![New](images/new.svg) [Astah](https://astah.net/) (**commercial**) - create
UML, ER Diagram, Flowchart, Data Flow Diagram, Requirement Diagram, SysML
diagrams and more
- [IGMAS+](https://www.gfz-potsdam.de/igmas) - Interactive Gravity and Magnetic
Application System
### Documents
- ![New](images/new.svg) [Big Faceless (BFO) PDF Viewer](https://bfo.com/)
(**commercial**) - Swing PDF Viewer
- [PDF Studio](https://www.qoppa.com/pdfstudio/) (**commercial**) - create,
review and edit PDF documents
- [XMLmind XML Editor](https://www.xmlmind.com/xmleditor/) (**commercial**)
### Geo
- ![Hot](images/hot.svg) [JOSM](https://josm.openstreetmap.de/) - an extensible
editor for [OpenStreetMap](https://www.openstreetmap.org/) (requires FlatLaf
JOSM plugin)
- ![Hot](images/hot.svg) [jAlbum](https://jalbum.net/) 21 (**commercial**) -
creates photo album websites
- [PDF Studio](https://www.qoppa.com/pdfstudio/) 2021 (**commercial**) - create,
review and edit PDF documents
- [XMLmind XML Editor](https://www.xmlmind.com/xmleditor/) 9.3 (**commercial**)
- [Total Validator](https://www.totalvalidator.com/) 15 (**commercial**) -
checks your website
- [j-lawyer](https://github.com/jlawyerorg/j-lawyer-org) - Kanzleisoftware
- [MegaMek](https://github.com/MegaMek/megamek),
[MegaMekLab](https://github.com/MegaMek/megameklab) and
[MekHQ](https://github.com/MegaMek/mekhq) v0.47.5+ - a sci-fi tabletop
BattleTech simulator suite handling battles, unit building, and campaigns
- [GUIslice Builder](https://github.com/ImpulseAdventure/GUIslice-Builder)
0.13.b024 - GUI builder for
[GUIslice](https://github.com/ImpulseAdventure/GUIslice), a lightweight GUI
framework for embedded displays
- [Rest Suite](https://github.com/supanadit/restsuite) - Rest API testing
- [ControllerBuddy](https://github.com/bwRavencl/ControllerBuddy) - advanced
gamepad mapping software
- [SpringRemote](https://github.com/HaleyWang/SpringRemote) - remote Linux SSH
connections manager
- [jEnTunnel](https://github.com/ggrandes/jentunnel) - manage SSH Tunnels made
easy
- [Mapton](https://mapton.org/)
([source code](https://github.com/trixon/mapton)) - some kind of map
application (based on NetBeans platform)
- [MeteoInfo](https://github.com/meteoinfo/MeteoInfo) - GIS and scientific
computation environment for meteorological community
### Business / Legal
- ![Sponsor](images/sponsor.svg)
[j-lawyer](https://github.com/jlawyerorg/j-lawyer-org) - Kanzleisoftware
- ![Sponsor](images/sponsor.svg) [Jeyla Studio](https://www.jeylastudio.com/) -
Salon Software
- [Fanurio](https://www.fanuriotimetracking.com/) (**commercial**) - time
tracking and billing for freelancers and teams
- [Jes](https://www.jes-eur.de) - Die Java-EÜR
- [mendelson AS2](https://sourceforge.net/projects/mec-as2/),
[AS4](https://sourceforge.net/projects/mendelson-as4/) and
[OFTP2](https://sourceforge.net/projects/mendelson-oftp2/) (open-source) and
[mendelson AS2](https://mendelson-e-c.com/as2/),
[AS4](https://mendelson-e-c.com/as4/) and
[OFTP2](https://mendelson-e-c.com/oftp2) (**commercial**)
- [IGMAS+](https://www.gfz-potsdam.de/igmas) - Interactive Gravity and Magnetic
Application System
- [MeteoInfo](https://github.com/meteoinfo/MeteoInfo) 2.2 - GIS and scientific
computation environment for meteorological community
- [lsfusion platform](https://github.com/lsfusion/platform) 4 - information
systems development platform
- [JPass](https://github.com/gaborbata/jpass) - password manager with strong
encryption
- [Jes - Die Java-EÜR](https://www.jes-eur.de)
- [Mapton](https://mapton.org/) 2.0
([source code](https://github.com/trixon/mapton)) - some kind of map
application (based on NetBeans platform)
- [Pseudo Assembler IDE](https://github.com/tomasz-herman/PseudoAssemblerIDE) -
IDE for Pseudo-Assembler
- [Linotte](https://github.com/cpc6128/LangageLinotte) 3.1 - French programming
language created to learn programming
- [MEKA](https://github.com/Waikato/meka) 1.9.3 - multi-label classifiers and
evaluation procedures using the Weka machine learning framework
- [Shutter Encoder](https://www.shutterencoder.com/) 14.2
([source code](https://github.com/paulpacifico/shutter-encoder)) -
professional video converter and compression tool (screenshots show **old**
look)
- [Sound Analysis](https://github.com/tomasz-herman/SoundAnalysis) - analyze
sound files in time or frequency domain
- [RemoteLight](https://github.com/Drumber/RemoteLight) - multifunctional LED
control software
- [ThunderFocus](https://github.com/marcocipriani01/ThunderFocus) -
Arduino-based telescope focuser
- [Novel-Grabber](https://github.com/Flameish/Novel-Grabber) - download novels
from any webnovel and lightnovel site
- [lectureStudio](https://www.lecturestudio.org/) 4.3.1060 - digitize your
lectures with ease
### Messaging
- ![New](images/new.svg) [Spark](https://github.com/igniterealtime/Spark) -
cross-platform IM client optimized for businesses and organizations
- ![New](images/new.svg) [Chatty](https://github.com/chatty/chatty) - Twitch
Chat Client
### Gaming
- ![New](images/new.svg) ![Sponsor](images/sponsor.svg)
[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
Tabletop for playing role-playing games
- [MegaMek](https://github.com/MegaMek/megamek),
[MegaMekLab](https://github.com/MegaMek/megameklab) and
[MekHQ](https://github.com/MegaMek/mekhq) - a sci-fi tabletop BattleTech
simulator suite handling battles, unit building, and campaigns
- [ControllerBuddy](https://github.com/bwRavencl/ControllerBuddy) - advanced
gamepad mapping software
### Utilities
- [MooInfo](https://github.com/rememberber/MooInfo) - visual implementation of
OSHI, to view information about the system and hardware
- ![New](images/new.svg)
[Linux Task Manager (LTM)](https://github.com/ajee10x/LTM-LinuxTaskManager) -
GUI for monitoring and managing various aspects of a Linux system
- [Rest Suite](https://github.com/supanadit/restsuite) - Rest API testing
- [SpringRemote](https://github.com/HaleyWang/SpringRemote) - remote Linux SSH
connections manager
- [jEnTunnel](https://github.com/ggrandes/jentunnel) - manage SSH Tunnels made
easy
- [Android Tool](https://github.com/fast-geek/Android-Tool) - makes popular adb
and fastboot commands easier to use
- and more...
### Miscellaneous
- [MEKA](https://github.com/Waikato/meka) - multi-label classifiers and
evaluation procedures using the Weka machine learning framework

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,2 @@
eclipse.preferences.version=1
encoding/<project>=ISO-8859-1

View File

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

View File

@@ -1,5 +1,5 @@
#Signature file v4.1
#Version 3.1
#Version 3.5.2
CLSS public abstract interface com.formdev.flatlaf.FlatClientProperties
fld public final static java.lang.String BUTTON_TYPE = "JButton.buttonType"
@@ -12,7 +12,13 @@ fld public final static java.lang.String BUTTON_TYPE_TOOLBAR_BUTTON = "toolBarBu
fld public final static java.lang.String COMPONENT_FOCUS_OWNER = "JComponent.focusOwner"
fld public final static java.lang.String COMPONENT_ROUND_RECT = "JComponent.roundRect"
fld public final static java.lang.String COMPONENT_TITLE_BAR_CAPTION = "JComponent.titleBarCaption"
fld public final static java.lang.String FULL_WINDOW_CONTENT = "FlatLaf.fullWindowContent"
fld public final static java.lang.String FULL_WINDOW_CONTENT_BUTTONS_BOUNDS = "FlatLaf.fullWindowContent.buttonsBounds"
fld public final static java.lang.String FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER = "FlatLaf.fullWindowContent.buttonsPlaceholder"
fld public final static java.lang.String GLASS_PANE_FULL_HEIGHT = "JRootPane.glassPaneFullHeight"
fld public final static java.lang.String MACOS_WINDOW_BUTTONS_SPACING = "FlatLaf.macOS.windowButtonsSpacing"
fld public final static java.lang.String MACOS_WINDOW_BUTTONS_SPACING_LARGE = "large"
fld public final static java.lang.String MACOS_WINDOW_BUTTONS_SPACING_MEDIUM = "medium"
fld public final static java.lang.String MENU_BAR_EMBEDDED = "JRootPane.menuBarEmbedded"
fld public final static java.lang.String MINIMUM_HEIGHT = "JComponent.minimumHeight"
fld public final static java.lang.String MINIMUM_WIDTH = "JComponent.minimumWidth"
@@ -23,6 +29,7 @@ fld public final static java.lang.String PLACEHOLDER_TEXT = "JTextField.placehol
fld public final static java.lang.String POPUP_BORDER_CORNER_RADIUS = "Popup.borderCornerRadius"
fld public final static java.lang.String POPUP_DROP_SHADOW_PAINTED = "Popup.dropShadowPainted"
fld public final static java.lang.String POPUP_FORCE_HEAVY_WEIGHT = "Popup.forceHeavyWeight"
fld public final static java.lang.String POPUP_ROUNDED_BORDER_WIDTH = "Popup.roundedBorderWidth"
fld public final static java.lang.String PROGRESS_BAR_LARGE_HEIGHT = "JProgressBar.largeHeight"
fld public final static java.lang.String PROGRESS_BAR_SQUARE = "JProgressBar.square"
fld public final static java.lang.String SCROLL_BAR_SHOW_BUTTONS = "JScrollBar.showButtons"
@@ -67,6 +74,11 @@ fld public final static java.lang.String TABBED_PANE_TAB_CLOSE_TOOLTIPTEXT = "JT
fld public final static java.lang.String TABBED_PANE_TAB_HEIGHT = "JTabbedPane.tabHeight"
fld public final static java.lang.String TABBED_PANE_TAB_ICON_PLACEMENT = "JTabbedPane.tabIconPlacement"
fld public final static java.lang.String TABBED_PANE_TAB_INSETS = "JTabbedPane.tabInsets"
fld public final static java.lang.String TABBED_PANE_TAB_ROTATION = "JTabbedPane.tabRotation"
fld public final static java.lang.String TABBED_PANE_TAB_ROTATION_AUTO = "auto"
fld public final static java.lang.String TABBED_PANE_TAB_ROTATION_LEFT = "left"
fld public final static java.lang.String TABBED_PANE_TAB_ROTATION_NONE = "none"
fld public final static java.lang.String TABBED_PANE_TAB_ROTATION_RIGHT = "right"
fld public final static java.lang.String TABBED_PANE_TAB_TYPE = "JTabbedPane.tabType"
fld public final static java.lang.String TABBED_PANE_TAB_TYPE_CARD = "card"
fld public final static java.lang.String TABBED_PANE_TAB_TYPE_UNDERLINED = "underlined"
@@ -88,6 +100,7 @@ fld public final static java.lang.String TEXT_FIELD_TRAILING_COMPONENT = "JTextF
fld public final static java.lang.String TEXT_FIELD_TRAILING_ICON = "JTextField.trailingIcon"
fld public final static java.lang.String TITLE_BAR_BACKGROUND = "JRootPane.titleBarBackground"
fld public final static java.lang.String TITLE_BAR_FOREGROUND = "JRootPane.titleBarForeground"
fld public final static java.lang.String TITLE_BAR_HEIGHT = "JRootPane.titleBarHeight"
fld public final static java.lang.String TITLE_BAR_SHOW_CLOSE = "JRootPane.titleBarShowClose"
fld public final static java.lang.String TITLE_BAR_SHOW_ICON = "JRootPane.titleBarShowIcon"
fld public final static java.lang.String TITLE_BAR_SHOW_ICONIFFY = "JRootPane.titleBarShowIconify"
@@ -96,6 +109,8 @@ fld public final static java.lang.String TITLE_BAR_SHOW_TITLE = "JRootPane.title
fld public final static java.lang.String TREE_PAINT_SELECTION = "JTree.paintSelection"
fld public final static java.lang.String TREE_WIDE_SELECTION = "JTree.wideSelection"
fld public final static java.lang.String USE_WINDOW_DECORATIONS = "JRootPane.useWindowDecorations"
fld public final static java.lang.String WINDOW_STYLE = "Window.style"
fld public final static java.lang.String WINDOW_STYLE_SMALL = "small"
meth public static <%0 extends java.lang.Object> {%%0} clientProperty(javax.swing.JComponent,java.lang.String,{%%0},java.lang.Class<{%%0}>)
meth public static boolean clientPropertyBoolean(javax.swing.JComponent,java.lang.String,boolean)
meth public static boolean clientPropertyEquals(javax.swing.JComponent,java.lang.String,java.lang.Object)
@@ -209,6 +224,7 @@ meth public static java.util.Map<java.lang.String,java.lang.Class<?>> getStyleab
meth public static java.util.Map<java.lang.String,java.lang.String> getGlobalExtraDefaults()
meth public static java.util.function.Function<java.lang.String,java.awt.Color> getSystemColorGetter()
meth public static javax.swing.UIDefaults$ActiveValue createActiveFontValue(float)
meth public static void disableWindowsD3Donscreen()
meth public static void hideMnemonics()
meth public static void initIconColors(javax.swing.UIDefaults,boolean)
meth public static void installLafInfo(java.lang.String,java.lang.Class<? extends javax.swing.LookAndFeel>)
@@ -239,7 +255,7 @@ meth public void setExtraDefaults(java.util.Map<java.lang.String,java.lang.Strin
meth public void uninitialize()
meth public void unregisterUIDefaultsGetter(java.util.function.Function<java.lang.Object,java.lang.Object>)
supr javax.swing.plaf.basic.BasicLookAndFeel
hfds DESKTOPFONTHINTS,aquaLoaded,customDefaultsSources,desktopPropertyListener,desktopPropertyName,desktopPropertyName2,extraDefaults,getUIMethod,getUIMethodInitialized,globalExtraDefaults,mnemonicHandler,oldPopupFactory,postInitialization,preferredFontFamily,preferredLightFontFamily,preferredMonospacedFontFamily,preferredSemiboldFontFamily,subMenuUsabilityHelperInstalled,systemColorGetter,uiDefaultsGetters,updateUIPending
hfds DESKTOPFONTHINTS,aquaLoaded,customDefaultsSources,desktopPropertyListener,desktopPropertyName,desktopPropertyName2,extraDefaults,globalExtraDefaults,mnemonicHandler,oldPopupFactory,postInitialization,preferredFontFamily,preferredLightFontFamily,preferredMonospacedFontFamily,preferredSemiboldFontFamily,subMenuUsabilityHelperInstalled,systemColorGetter,uiDefaultsGetters,updateUIPending
hcls ActiveFont,FlatUIDefaults,ImageIconUIResource
CLSS public abstract interface static com.formdev.flatlaf.FlatLaf$DisabledIconProvider
@@ -280,6 +296,10 @@ fld public final static java.lang.String UI_SCALE_ALLOW_SCALE_DOWN = "flatlaf.ui
fld public final static java.lang.String UI_SCALE_ENABLED = "flatlaf.uiScale.enabled"
fld public final static java.lang.String UPDATE_UI_ON_SYSTEM_FONT_CHANGE = "flatlaf.updateUIOnSystemFontChange"
fld public final static java.lang.String USE_JETBRAINS_CUSTOM_DECORATIONS = "flatlaf.useJetBrainsCustomDecorations"
anno 0 java.lang.Deprecated()
fld public final static java.lang.String USE_NATIVE_LIBRARY = "flatlaf.useNativeLibrary"
fld public final static java.lang.String USE_ROUNDED_POPUP_BORDER = "flatlaf.useRoundedPopupBorder"
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_UBUNTU_FONT = "flatlaf.useUbuntuFont"
fld public final static java.lang.String USE_WINDOW_DECORATIONS = "flatlaf.useWindowDecorations"
@@ -298,7 +318,7 @@ meth public static boolean setup(java.io.InputStream)
meth public static com.formdev.flatlaf.FlatLaf createLaf(com.formdev.flatlaf.IntelliJTheme)
meth public static com.formdev.flatlaf.FlatLaf createLaf(java.io.InputStream) throws java.io.IOException
supr java.lang.Object
hfds checkboxDuplicateColors,checkboxKeyMapping,colors,icons,isMaterialUILite,namedColors,ui,uiKeyCopying,uiKeyInverseMapping,uiKeyMapping
hfds checkboxDuplicateColors,checkboxKeyMapping,colors,icons,isMaterialUILite,namedColors,ui,uiKeyCopying,uiKeyDoNotOverride,uiKeyExcludes,uiKeyInverseMapping,uiKeyMapping
CLSS public static com.formdev.flatlaf.IntelliJTheme$ThemeLaf
outer com.formdev.flatlaf.IntelliJTheme
@@ -613,16 +633,33 @@ hfds alpha,hsl,rgb
CLSS public com.formdev.flatlaf.util.HiDPIUtils
cons public init()
innr public abstract interface static DirtyRegionCallback
innr public abstract interface static Painter
innr public static HiDPIRepaintManager
meth public static float computeTextYCorrection(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 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,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
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
outer com.formdev.flatlaf.util.HiDPIUtils
meth public abstract void paint(java.awt.Graphics2D,int,int,int,int,double)
@@ -1121,6 +1158,31 @@ meth public void provideErrorFeedback(java.awt.Component)
meth public void uninitialize()
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
cons public init()
intf java.io.Serializable

View File

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

View File

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

View File

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

View File

@@ -20,6 +20,7 @@ import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.Toolkit;
@@ -30,9 +31,6 @@ import java.awt.image.ImageProducer;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
@@ -78,6 +76,7 @@ import com.formdev.flatlaf.ui.FlatNativeWindowBorder;
import com.formdev.flatlaf.ui.FlatPopupFactory;
import com.formdev.flatlaf.ui.FlatRootPaneUI;
import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.ui.JavaCompatibility2;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.FontUtils;
import com.formdev.flatlaf.util.GrayFilter;
@@ -121,6 +120,46 @@ public abstract class FlatLaf
private static String preferredSemiboldFontFamily;
private static String preferredMonospacedFontFamily;
static {
// see disableWindowsD3Donscreen() for details
// https://github.com/JFormDesigner/FlatLaf/issues/887
if( SystemInfo.isWindows &&
System.getProperty( "sun.java2d.d3d.onscreen" ) == null &&
System.getProperty( "sun.java2d.d3d" ) == null &&
System.getProperty( "sun.java2d.noddraw" ) == null )
System.setProperty( "sun.java2d.d3d.onscreen", "false" );
}
/**
* Disable usage of Windows Direct3D (DirectX) onscreen surfaces because this may lead to
* repaint issues (ghosting) on some systems (probably depending on graphics card/driver).
* Problem occurs usually when a small heavy-weight popup window (menu, combobox, tooltip) is shown.
* <p>
* Sets system property {@code sun.java2d.d3d.onscreen} to {@code false},
* but only if {@code sun.java2d.d3d.onscreen}, {@code sun.java2d.d3d}
* and {@code sun.java2d.noddraw} are not yet set.
* <p>
* <strong>Note</strong>: Must be invoked very early before the graphics environment is created.
* <p>
* This method is automatically invoked when loading this class,
* which is usually before the graphics environment is created.
* E.g. when doing {@code FlatLightLaf.setup()} or
* {@code UIManager.setLookAndFeel( "com.formdev.flatlaf.FlatLightLaf" )}.
* <p>
* However, it may be invoked too late if you use some methods from {@link UIManager}
* of {@link GraphicsEnvironment} before setting look and feel.
* E.g. {@link UIManager#put(Object, Object)}.
* In that case invoke this method yourself very early.
* <p>
* <strong>Tip</strong>: How to find out when the graphics environment is created?
* Set a breakpoint at constructor of class {@link GraphicsEnvironment} and look at the stack.
*
* @since 3.5.2
*/
public static void disableWindowsD3Donscreen() {
// dummy method used to trigger invocation of "static {...}" block
}
/**
* Sets the application look and feel to the given LaF
* using {@link UIManager#setLookAndFeel(javax.swing.LookAndFeel)}.
@@ -183,17 +222,11 @@ public abstract class FlatLaf
* This depends on the operating system and on the used Java runtime.
* <p>
* This method returns {@code true} on Windows 10/11 (see exception below)
* and on Linux, {@code false} otherwise.
* and on Linux, otherwise returns {@code false}.
* <p>
* Returns also {@code false} on Windows 10/11 if
* FlatLaf native window border support is available (requires Windows 10/11).
* <p>
* Returns also {@code false} on Windows 10/11 if:
* <ul>
* <li>FlatLaf native window border support is available (requires Windows 10/11)</li>
* <li>running in
* <a href="https://confluence.jetbrains.com/display/JBR/JetBrains+Runtime">JetBrains Runtime 11 (or later)</a>
* (<a href="https://github.com/JetBrains/JetBrainsRuntime">source code on github</a>)
* and JBR supports custom window decorations
* </li>
* </ul>
* In these cases, custom decorations are enabled by the root pane.
* Usage of {@link JFrame#setDefaultLookAndFeelDecorated(boolean)} or
* {@link JDialog#setDefaultLookAndFeelDecorated(boolean)} is not necessary.
@@ -1295,8 +1328,8 @@ public abstract class FlatLaf
* @since 2.5
*/
public static Map<String, Class<?>> getStyleableInfos( JComponent c ) {
StyleableUI ui = getStyleableUI( c );
return (ui != null) ? ui.getStyleableInfos( c ) : null;
ComponentUI ui = JavaCompatibility2.getUI( c );
return (ui instanceof StyleableUI) ? ((StyleableUI)ui).getStyleableInfos( c ) : null;
}
/**
@@ -1308,41 +1341,10 @@ public abstract class FlatLaf
*/
@SuppressWarnings( "unchecked" )
public static <T> T getStyleableValue( JComponent c, String key ) {
StyleableUI ui = getStyleableUI( c );
return (ui != null) ? (T) ui.getStyleableValue( c, key ) : null;
ComponentUI ui = JavaCompatibility2.getUI( c );
return (ui instanceof StyleableUI) ? (T) ((StyleableUI)ui).getStyleableValue( c, key ) : null;
}
private static StyleableUI getStyleableUI( JComponent c ) {
if( !getUIMethodInitialized ) {
getUIMethodInitialized = true;
if( SystemInfo.isJava_9_orLater ) {
try {
// JComponent.getUI() is available since Java 9
getUIMethod = MethodHandles.lookup().findVirtual( JComponent.class, "getUI",
MethodType.methodType( ComponentUI.class ) );
} catch( Exception ex ) {
// ignore
}
}
}
try {
Object ui;
if( getUIMethod != null )
ui = getUIMethod.invoke( c );
else
ui = c.getClass().getMethod( "getUI" ).invoke( c );
return (ui instanceof StyleableUI) ? (StyleableUI) ui : null;
} catch( Throwable ex ) {
// ignore
return null;
}
}
private static boolean getUIMethodInitialized;
private static MethodHandle getUIMethod;
/**
* Returns the preferred font family to be used for (nearly) all fonts; or {@code null}.
*

View File

@@ -23,13 +23,15 @@ import java.io.InputStream;
import java.util.ArrayList;
import java.util.Locale;
import java.util.Properties;
import com.formdev.flatlaf.themes.FlatMacDarkLaf;
import com.formdev.flatlaf.themes.FlatMacLightLaf;
/**
* A Flat LaF that is able to load UI defaults from properties passed to the constructor.
* <p>
* Specify the base theme in the properties with {@code @baseTheme=<baseTheme>}.
* Allowed values for {@code <baseTheme>} are {@code light} (the default), {@code dark},
* {@code intellij} or {@code darcula}.
* {@code intellij}, {@code darcula}, {@code maclight} or {@code macdark}.
* <p>
* The properties are applied after loading the base theme and may overwrite base properties.
* All features of FlatLaf properties files are available.
@@ -71,7 +73,8 @@ public class FlatPropertiesLaf
this.properties = properties;
baseTheme = properties.getProperty( "@baseTheme", "light" );
dark = "dark".equalsIgnoreCase( baseTheme ) || "darcula".equalsIgnoreCase( baseTheme );
dark = "dark".equalsIgnoreCase( baseTheme ) || "darcula".equalsIgnoreCase( baseTheme ) ||
"macdark".equalsIgnoreCase( baseTheme );
}
@Override
@@ -116,6 +119,16 @@ public class FlatPropertiesLaf
lafClasses.add( FlatDarkLaf.class );
lafClasses.add( FlatDarculaLaf.class );
break;
case "maclight":
lafClasses.add( FlatLightLaf.class );
lafClasses.add( FlatMacLightLaf.class );
break;
case "macdark":
lafClasses.add( FlatDarkLaf.class );
lafClasses.add( FlatMacDarkLaf.class );
break;
}
return lafClasses;
}

View File

@@ -82,7 +82,7 @@ public interface FlatSystemProperties
* {@link FlatClientProperties#USE_WINDOW_DECORATIONS} and
* UI default {@code TitlePane.useWindowDecorations}.
* <p>
* (requires Window 10/11)
* (requires Windows 10/11)
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> none
@@ -99,11 +99,14 @@ public interface FlatSystemProperties
* Setting this to {@code false} disables using JetBrains Runtime custom window decorations.
* Then FlatLaf native window decorations are used.
* <p>
* (requires Window 10/11)
* (requires Windows 10/11)
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> {@code false} (since v2; was {@code true} in v1)
*
* @deprecated No longer used since FlatLaf 3.3. Retained for API compatibility.
*/
@Deprecated
String USE_JETBRAINS_CUSTOM_DECORATIONS = "flatlaf.useJetBrainsCustomDecorations";
/**
@@ -117,7 +120,7 @@ public interface FlatSystemProperties
* {@link FlatClientProperties#MENU_BAR_EMBEDDED} and
* UI default {@code TitlePane.menuBarEmbedded}.
* <p>
* (requires Window 10/11)
* (requires Windows 10/11)
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> none
@@ -132,6 +135,18 @@ public interface FlatSystemProperties
*/
String ANIMATION = "flatlaf.animation";
/**
* Specifies whether native rounded popup borders should be used (if supported by operating system).
* <p>
* (requires Windows 11 or macOS)
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> {@code true}; except on macOS 14.4+ where it is {@code false}
*
* @since 3.5.2
*/
String USE_ROUNDED_POPUP_BORDER = "flatlaf.useRoundedPopupBorder";
/**
* Specifies whether vertical text position is corrected when UI is scaled on HiDPI screens.
* <p>
@@ -169,24 +184,48 @@ public interface FlatSystemProperties
String USE_NATIVE_LIBRARY = "flatlaf.useNativeLibrary";
/**
* Specifies a directory in which the native FlatLaf libraries have been extracted.
* Specifies a directory in which the FlatLaf native libraries are searched for.
* The path can be absolute or relative to current application working directory.
* This can be used to avoid extraction of the native libraries to the temporary directory at runtime.
* <p>
* If the value is {@code "system"}, then {@link System#loadLibrary(String)} is
* used to load the native library.
* Searches for the native library in classloader of caller
* If the value is {@code "system"} (supported since FlatLaf 2.6),
* then {@link System#loadLibrary(String)} is used to load the native library.
* This searches for the native library in classloader of caller
* (using {@link ClassLoader#findLibrary(String)}) and in paths specified
* in system properties {@code sun.boot.library.path} and {@code java.library.path}.
* (supported since FlatLaf 2.6)
* <p>
* If the native library can not loaded from the given path (or via {@link System#loadLibrary(String)}),
* If the native library can not be loaded from the given path (or via {@link System#loadLibrary(String)}),
* then the embedded native library is extracted to the temporary directory and loaded from there.
* <p>
* The file names of the native libraries must be either:
* <ul>
* <li>the same as in flatlaf.jar in package 'com/formdev/flatlaf/natives' (required for "system") or
* <li>when downloaded from Maven central then as described here:
* <a href="https://www.formdev.com/flatlaf/native-libraries/">https://www.formdev.com/flatlaf/native-libraries/</a>
* (requires FlatLaf 3.4)
* </ul>
* <p>
* <strong>Note</strong>: Since FlatLaf 3.1 it is recommended to download the
* FlatLaf native libraries from Maven central and distribute them with your
* application in the same directory as flatlaf.jar.
* Then it is <strong>not necessary</strong> to set this system property.
* See <a href="https://www.formdev.com/flatlaf/native-libraries/">https://www.formdev.com/flatlaf/native-libraries/</a>
* for details.
*
* @since 2
*/
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
* is {@code "true"} (case-insensitive), otherwise it returns {@code false}.

View File

@@ -111,7 +111,7 @@ class LinuxFontPolicy
if( logicalFamily != null )
family = logicalFamily;
return createFontEx( family, style, size, dsize );
return createFontEx( family, style, size );
}
/**
@@ -121,9 +121,9 @@ class LinuxFontPolicy
* E.g. family 'URW Bookman Light' is not found, but 'URW Bookman' is found.
* If still not found, then font of family 'Dialog' is returned.
*/
private static Font createFontEx( String family, int style, int size, double dsize ) {
private static Font createFontEx( String family, int style, int size ) {
for(;;) {
Font font = createFont( family, style, size, dsize );
Font font = FlatLaf.createCompositeFont( family, style, size );
if( Font.DIALOG.equals( family ) )
return font;
@@ -135,7 +135,7 @@ class LinuxFontPolicy
// - character width is zero (e.g. font Cantarell; Fedora; Oracle Java 8)
FontMetrics fm = StyleContext.getDefaultStyleContext().getFontMetrics( font );
if( fm.getHeight() > size * 2 || fm.stringWidth( "a" ) == 0 )
return createFont( Font.DIALOG, style, size, dsize );
return FlatLaf.createCompositeFont( Font.DIALOG, style, size );
return font;
}
@@ -143,7 +143,7 @@ class LinuxFontPolicy
// find last word in family
int index = family.lastIndexOf( ' ' );
if( index < 0 )
return createFont( Font.DIALOG, style, size, dsize );
return FlatLaf.createCompositeFont( Font.DIALOG, style, size );
// check whether last work contains some font weight (e.g. Ultra-Bold or Heavy)
String lastWord = family.substring( index + 1 ).toLowerCase( Locale.ENGLISH );
@@ -155,15 +155,6 @@ class LinuxFontPolicy
}
}
private static Font createFont( String family, int style, int size, double dsize ) {
Font font = FlatLaf.createCompositeFont( family, style, size );
// set font size in floating points
font = font.deriveFont( style, (float) dsize );
return font;
}
private static double getGnomeFontScale() {
// do not scale font here if JRE scales
if( isSystemScaling() )
@@ -203,7 +194,7 @@ class LinuxFontPolicy
* Gets the default font for KDE from KDE configuration files.
*
* The Swing fonts are not updated when the user changes system font size
* (System Settings > Fonts > Force Font DPI). A application restart is necessary.
* (System Settings > Fonts > Force Font DPI). An application restart is necessary.
* This is the same behavior as in native KDE applications.
*
* The "display scale factor" (kdeglobals: [KScreen] > ScaleFactor) is not used
@@ -257,7 +248,7 @@ class LinuxFontPolicy
if( size < 1 )
size = 1;
return createFont( family, style, size, dsize );
return FlatLaf.createCompositeFont( family, style, size );
}
@SuppressWarnings( "MixedMutabilityReturnType" ) // Error Prone

View File

@@ -45,6 +45,7 @@ import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
/**
* Improves usability of submenus by using a
@@ -64,6 +65,7 @@ class SubMenuUsabilityHelper
// https://github.com/apache/netbeans/issues/4231#issuecomment-1179616607
private static SubMenuUsabilityHelper instance;
private boolean eventQueuePushNotSupported;
private SubMenuEventQueue subMenuEventQueue;
private SafeTrianglePainter safeTrianglePainter;
private boolean changePending;
@@ -83,6 +85,9 @@ class SubMenuUsabilityHelper
if( instance != null )
return false;
if( !FlatSystemProperties.getBoolean( FlatSystemProperties.USE_SUB_MENU_SAFE_TRIANGLE, true ) )
return false;
instance = new SubMenuUsabilityHelper();
MenuSelectionManager.defaultManager().addChangeListener( instance );
return true;
@@ -99,7 +104,7 @@ class SubMenuUsabilityHelper
@Override
public void stateChanged( ChangeEvent e ) {
if( !FlatUIUtils.getUIBoolean( KEY_USE_SAFE_TRIANGLE, true ))
if( eventQueuePushNotSupported || !FlatUIUtils.getUIBoolean( KEY_USE_SAFE_TRIANGLE, true ))
return;
// handle menu selection change later, but only once in case of temporary changes
@@ -172,9 +177,30 @@ debug*/
targetTopY = popupLocation.y;
targetBottomY = popupLocation.y + popupSize.height;
// install own event queue to supress mouse events when mouse is moved within safe triangle
if( subMenuEventQueue == null )
subMenuEventQueue = new SubMenuEventQueue();
// install own event queue to suppress mouse events when mouse is moved within safe triangle
if( subMenuEventQueue == null ) {
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
if( safeTrianglePainter == null && UIManager.getBoolean( KEY_SHOW_SAFE_TRIANGLE ) )
@@ -247,8 +273,6 @@ debug*/
}
} );
timeoutTimer.setRepeats( false );
Toolkit.getDefaultToolkit().getSystemEventQueue().push( this );
}
void uninstall() {

View File

@@ -161,7 +161,7 @@ class UIDefaultsLoader
classLoader = FlatLaf.class.getClassLoader();
for( Class<?> lafClass : lafClasses ) {
String propertiesName = packageName + '/' + lafClass.getSimpleName() + ".properties";
String propertiesName = packageName + '/' + simpleClassName( lafClass ) + ".properties";
try( InputStream in = classLoader.getResourceAsStream( propertiesName ) ) {
if( in != null )
properties.load( in );
@@ -171,7 +171,7 @@ class UIDefaultsLoader
// load from package URL
URL packageUrl = (URL) source;
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() ) {
properties.load( in );
@@ -183,7 +183,7 @@ class UIDefaultsLoader
// load from folder
File folder = (File) source;
for( Class<?> lafClass : lafClasses ) {
File propertiesFile = new File( folder, lafClass.getSimpleName() + ".properties" );
File propertiesFile = new File( folder, simpleClassName( lafClass ) + ".properties" );
if( !propertiesFile.isFile() )
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 ) {
String message = "FlatLaf: Failed to parse: '" + key + '=' + value + '\'';
if( severe )
@@ -630,14 +638,18 @@ class UIDefaultsLoader
// Syntax: top,left,bottom,right[,lineColor[,lineThickness[,arc]]]
List<String> parts = splitFunctionParams( 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 )
: null;
float lineThickness = (parts.size() >= 6 && !parts.get( 5 ).isEmpty()) ? parseFloat( parts.get( 5 ) ) : 1f;
int arc = (parts.size() >= 7) ? parseInteger( parts.get( 6 ) ) : 0;
float lineThickness = (parts.size() >= 6 && !parts.get( 5 ).isEmpty())
? parseFloat( parts.get( 5 ) )
: 1f;
int arc = (parts.size() >= 7) && !parts.get( 6 ).isEmpty()
? parseInteger( parts.get( 6 ) )
: -1;
return (LazyValue) t -> {
return (lineColor != null)
return (lineColor != null || arc > 0)
? new FlatLineBorder( insets, lineColor, lineThickness, arc )
: new FlatEmptyBorder( insets );
};

View File

@@ -57,6 +57,8 @@ public abstract class FlatAbstractIcon
// g2.setColor( Color.blue );
// g2.drawRect( x, y, getIconWidth() - 1, getIconHeight() - 1 );
paintBackground( c, g2, x, y );
g2.translate( x, y );
UIScale.scaleGraphics( g2 );
@@ -69,7 +71,11 @@ public abstract class FlatAbstractIcon
}
}
protected abstract void paintIcon( Component c, Graphics2D g2 );
/** @since 3.5.2 */
protected void paintBackground( Component c, Graphics2D g, int x, int y ) {
}
protected abstract void paintIcon( Component c, Graphics2D g );
@Override
public int getIconWidth() {

View File

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

View File

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

View File

@@ -60,23 +60,24 @@ public abstract class FlatWindowAbstractIcon
@Override
protected void paintIcon( Component c, Graphics2D g ) {
paintBackground( c, g );
g.setColor( getForeground( c ) );
HiDPIUtils.paintAtScale1x( g, 0, 0, width, height, this::paintIconAt1x );
}
protected abstract void paintIconAt1x( Graphics2D g, int x, int y, int width, int height, double scaleFactor );
protected void paintBackground( Component c, Graphics2D g ) {
/** @since 3.5.2 */
@Override
protected void paintBackground( Component c, Graphics2D g, int x, int y ) {
Color background = FlatButtonUI.buttonStateColor( c, null, null, null, hoverBackground, pressedBackground );
if( background != null ) {
// disable antialiasing for background rectangle painting to avoid blury edges when scaled (e.g. at 125% or 175%)
// disable antialiasing for background rectangle painting to avoid blurry edges when scaled (e.g. at 125% or 175%)
Object oldHint = g.getRenderingHint( RenderingHints.KEY_ANTIALIASING );
g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF );
// fill background of whole component
g.setColor( FlatUIUtils.deriveColor( background, c.getBackground() ) );
g.fillRect( 0, 0, width, height );
g.fillRect( 0, 0, c.getWidth(), c.getHeight() );
g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, oldHint );
}

View File

@@ -28,7 +28,6 @@ import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JViewport;
import javax.swing.UIManager;
import javax.swing.plaf.basic.BasicBorders;
import com.formdev.flatlaf.FlatClientProperties;
@@ -136,7 +135,7 @@ public class FlatBorder
Paint borderColor = (outlineColor != null) ? outlineColor : getBorderColor( c );
FlatUIUtils.paintOutlinedComponent( g2, x, y, width, height,
focusWidth, 1, focusInnerWidth, borderWidth, arc,
focusColor, borderColor, null );
focusColor, borderColor, null, c instanceof JScrollPane );
} finally {
g2.dispose();
}
@@ -195,8 +194,7 @@ public class FlatBorder
protected boolean isEnabled( Component c ) {
if( c instanceof JScrollPane ) {
// check whether view component is disabled
JViewport viewport = ((JScrollPane)c).getViewport();
Component view = (viewport != null) ? viewport.getView() : null;
Component view = FlatScrollPaneUI.getView( (JScrollPane) c );
if( view != null && !isEnabled( view ) )
return false;
}
@@ -279,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 ) {
return 0;

View File

@@ -42,6 +42,13 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault Button.disabledBorderColor Color
* @uiDefault Button.focusedBorderColor Color
* @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.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.focusedBorderColor Color
* @uiDefault Button.default.focusColor Color
* @uiDefault Button.default.pressedBorderColor Color optional
* @uiDefault Button.default.hoverBorderColor Color optional
*
* @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" );
@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 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 defaultFocusColor = UIManager.getColor( "Button.default.focusColor" );
@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 Color toolbarFocusColor = UIManager.getColor( "Button.toolbar.focusColor" );
@@ -139,12 +155,13 @@ public class FlatButtonBorder
@Override
protected Paint getBorderColor( Component c ) {
boolean def = FlatButtonUI.isDefaultButton( c );
boolean selected = (c instanceof AbstractButton && ((AbstractButton)c).isSelected());
Paint color = FlatButtonUI.buttonStateColor( c,
def ? defaultBorderColor : borderColor,
disabledBorderColor,
def ? defaultFocusedBorderColor : focusedBorderColor,
def ? defaultHoverBorderColor : hoverBorderColor,
null );
def ? defaultBorderColor : ((selected && selectedBorderColor != null) ? selectedBorderColor : borderColor),
(selected && disabledSelectedBorderColor != null) ? disabledSelectedBorderColor : disabledBorderColor,
def ? defaultFocusedBorderColor : ((selected && focusedSelectedBorderColor != null) ? focusedSelectedBorderColor : focusedBorderColor),
def ? defaultHoverBorderColor : ((selected && hoverSelectedBorderColor != null) ? hoverSelectedBorderColor : hoverBorderColor),
def ? defaultPressedBorderColor : ((selected && pressedSelectedBorderColor != null) ? pressedSelectedBorderColor : pressedBorderColor) );
// change to gradient paint if start/end colors are specified
Color startBg = def ? defaultBorderColor : borderColor;

View File

@@ -29,6 +29,7 @@ import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.FocusEvent;
import java.awt.geom.RoundRectangle2D;
import java.beans.PropertyChangeEvent;
import java.util.Map;
@@ -53,12 +54,15 @@ import javax.swing.plaf.ToolBarUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicButtonListener;
import javax.swing.plaf.basic.BasicButtonUI;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.text.View;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.icons.FlatHelpButtonIcon;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale;
@@ -298,6 +302,10 @@ public class FlatButtonUI
protected void propertyChange( AbstractButton b, PropertyChangeEvent e ) {
switch( e.getPropertyName() ) {
case BasicHTML.propertyKey:
FlatHTML.updateRendererCSSFontBaseSize( b );
break;
case SQUARE_SIZE:
case MINIMUM_WIDTH:
case MINIMUM_HEIGHT:
@@ -306,11 +314,11 @@ public class FlatButtonUI
case BUTTON_TYPE:
b.revalidate();
b.repaint();
HiDPIUtils.repaint( b );
break;
case OUTLINE:
b.repaint();
HiDPIUtils.repaint( b );
break;
case STYLE:
@@ -322,7 +330,7 @@ public class FlatButtonUI
} else
installStyle( b );
b.revalidate();
b.repaint();
HiDPIUtils.repaint( b );
break;
}
}
@@ -551,9 +559,52 @@ public class FlatButtonUI
}
}
/**
* Similar to BasicButtonUI.paint(), but does not use zero insets for HTML text,
* which is done in BasicButtonUI.layout() since Java 19.
* See https://github.com/openjdk/jdk/pull/8407
* and https://github.com/openjdk/jdk/pull/8407#issuecomment-1761583430
*/
@Override
public void paint( Graphics g, JComponent c ) {
super.paint( FlatLabelUI.createGraphicsHTMLTextYCorrection( g, c ), c );
g = FlatLabelUI.createGraphicsHTMLTextYCorrection( g, c );
AbstractButton b = (AbstractButton) c;
// layout
String clippedText = layout( b, b.getFontMetrics( b.getFont() ), b.getWidth(), b.getHeight() );
// not used in FlatLaf, but invoked for compatibility with BasicButtonUI.paint()
clearTextShiftOffset();
// not used in FlatLaf, but invoked for compatibility with BasicButtonUI.paint()
ButtonModel model = b.getModel();
if( model.isArmed() && model.isPressed() )
paintButtonPressed( g, b );
// paint icon
if( b.getIcon() != null )
paintIcon( g, b, iconR );
// paint text
if( clippedText != null && !clippedText.isEmpty() ) {
View view = (View) b.getClientProperty( BasicHTML.propertyKey );
if( view != null ) {
// 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
} else
paintText( g, b, textR, clippedText );
}
// not used in FlatLaf, but invoked for compatibility with BasicButtonUI.paint()
if( b.isFocusPainted() && b.hasFocus() )
paintFocus( g, b, viewR, textR, iconR );
}
@Override
@@ -593,8 +644,6 @@ public class FlatButtonUI
}
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() );
int mnemonicIndex = FlatLaf.isShowMnemonics() ? b.getDisplayedMnemonicIndex() : -1;
@@ -683,14 +732,15 @@ public class FlatButtonUI
}
protected Color getForeground( JComponent c ) {
Color fg = c.getForeground();
boolean toolBarButton = isToolBarButton( c ) || isBorderlessButton( c );
// selected state
if( ((AbstractButton)c).isSelected() ) {
return buttonStateColor( c,
toolBarButton
? (toolbarSelectedForeground != null ? toolbarSelectedForeground : c.getForeground())
: selectedForeground,
? (toolbarSelectedForeground != null ? toolbarSelectedForeground : fg)
: (isCustomForeground( fg ) ? fg : selectedForeground),
toolBarButton
? (toolbarDisabledSelectedForeground != null ? toolbarDisabledSelectedForeground : disabledText)
: (disabledSelectedForeground != null ? disabledSelectedForeground : disabledText),
@@ -702,7 +752,7 @@ public class FlatButtonUI
// toolbar button
if( toolBarButton ) {
return buttonStateColor( c,
c.getForeground(),
fg,
disabledText,
null,
toolbarHoverForeground,
@@ -713,7 +763,7 @@ public class FlatButtonUI
return buttonStateColor( c,
getForegroundBase( c, def ),
disabledText,
isCustomForeground( c.getForeground() ) ? null : (def ? defaultFocusedForeground : focusedForeground),
isCustomForeground( fg ) ? null : (def ? defaultFocusedForeground : focusedForeground),
def ? defaultHoverForeground : hoverForeground,
def ? defaultPressedForeground : pressedForeground );
}
@@ -786,6 +836,67 @@ public class FlatButtonUI
return margin instanceof UIResource && Objects.equals( margin, defaultMargin );
}
@Override
public int getBaseline( JComponent c, int width, int height ) {
return getBaselineImpl( c, width, height );
}
/**
* Similar to BasicButtonUI.getBaseline(), but does not use zero insets for HTML text,
* which is done in BasicButtonUI.layout() since Java 19.
* See https://github.com/openjdk/jdk/pull/8407
* and https://github.com/openjdk/jdk/pull/8407#issuecomment-1761583430
*/
static int getBaselineImpl( JComponent c, int width, int height ) {
if( width < 0 || height < 0 )
throw new IllegalArgumentException();
AbstractButton b = (AbstractButton) c;
String text = b.getText();
if( text == null || text.isEmpty() )
return -1;
FontMetrics fm = b.getFontMetrics( b.getFont() );
layout( b, fm, width, height );
View view = (View) b.getClientProperty( BasicHTML.propertyKey );
if( view != null ) {
// HTML text
int baseline = BasicHTML.getHTMLBaseline( view, textR.width, textR.height );
return (baseline >= 0) ? textR.y + baseline : baseline;
} else
return textR.y + fm.getAscent();
}
/**
* Similar to BasicButtonUI.layout(), but does not use zero insets for HTML text,
* which is done in BasicButtonUI.layout() since Java 19.
* See https://github.com/openjdk/jdk/pull/8407
* and https://github.com/openjdk/jdk/pull/8407#issuecomment-1761583430
*/
private static String layout( AbstractButton b, FontMetrics fm, int width, int height ) {
// compute view rectangle
Insets insets = b.getInsets();
viewR.setBounds( insets.left, insets.top,
width - insets.left - insets.right,
height - insets.top - insets.bottom );
// reset rectangles
textR.setBounds( 0, 0, 0, 0 );
iconR.setBounds( 0, 0, 0, 0 );
String text = b.getText();
return SwingUtilities.layoutCompoundLabel( b, fm, text, b.getIcon(),
b.getVerticalAlignment(), b.getHorizontalAlignment(),
b.getVerticalTextPosition(), b.getHorizontalTextPosition(),
viewR, iconR, textR,
(text != null) ? b.getIconTextGap() : 0 );
}
private static Rectangle viewR = new Rectangle();
private static Rectangle textR = new Rectangle();
private static Rectangle iconR = new Rectangle();
//---- class FlatButtonListener -------------------------------------------
protected class FlatButtonListener
@@ -806,7 +917,7 @@ public class FlatButtonUI
@Override
public void stateChanged( ChangeEvent e ) {
super.stateChanged( e );
HiDPIUtils.repaint( b );
// if button is in toolbar, repaint button groups
AbstractButton b = (AbstractButton) e.getSource();
@@ -818,5 +929,17 @@ public class FlatButtonUI
((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 javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.LookAndFeel;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicCheckBoxMenuItemUI;
@@ -102,13 +103,23 @@ public class FlatCheckBoxMenuItemUI
oldStyleValues = null;
}
@Override
protected void installComponents( JMenuItem menuItem ) {
super.installComponents( menuItem );
// update HTML renderer if necessary
FlatHTML.updateRendererCSSFontBaseSize( menuItem );
}
protected FlatMenuItemRenderer createRenderer() {
return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter );
}
@Override
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 */

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.StyleableLookupProvider;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.SystemInfo;
@@ -220,7 +221,7 @@ public class FlatComboBoxUI
private void repaintArrowButton() {
if( arrowButton != null && !comboBox.isEditable() )
arrowButton.repaint();
HiDPIUtils.repaint( arrowButton );
}
};
comboBox.addMouseListener( hoverListener );
@@ -351,15 +352,15 @@ public class FlatComboBoxUI
@Override
public void focusGained( FocusEvent e ) {
super.focusGained( e );
if( comboBox != null && comboBox.isEditable() )
comboBox.repaint();
if( comboBox != null )
HiDPIUtils.repaint( comboBox );
}
@Override
public void focusLost( FocusEvent e ) {
super.focusLost( e );
if( comboBox != null && comboBox.isEditable() )
comboBox.repaint();
if( comboBox != null )
HiDPIUtils.repaint( comboBox );
}
};
}
@@ -386,12 +387,12 @@ public class FlatComboBoxUI
switch( propertyName ) {
case PLACEHOLDER_TEXT:
if( editor != null )
editor.repaint();
HiDPIUtils.repaint( editor );
break;
case COMPONENT_ROUND_RECT:
case OUTLINE:
comboBox.repaint();
HiDPIUtils.repaint( comboBox );
break;
case MINIMUM_WIDTH:
@@ -402,7 +403,7 @@ public class FlatComboBoxUI
case STYLE_CLASS:
installStyle();
comboBox.revalidate();
comboBox.repaint();
HiDPIUtils.repaint( comboBox );
break;
}
}
@@ -584,7 +585,7 @@ public class FlatComboBoxUI
FlatUIUtils.paintComponentBackground( g2, 0, 0, width, height, focusWidth, arc );
// paint arrow button background
if( enabled && !isCellRenderer ) {
if( enabled && !isCellRenderer && arrowButton.isVisible() ) {
Color buttonColor = paintButton
? buttonEditableBackground
: (buttonFocusedBackground != null || focusedBackground != null) && isPermanentFocusOwner( comboBox )
@@ -611,7 +612,7 @@ public class FlatComboBoxUI
}
// paint vertical line between value and arrow button
if( paintButton ) {
if( paintButton && arrowButton.isVisible() ) {
Color separatorColor = enabled ? buttonSeparatorColor : buttonDisabledSeparatorColor;
if( separatorColor != null && buttonSeparatorWidth > 0 ) {
g2.setColor( separatorColor );
@@ -926,7 +927,7 @@ public class FlatComboBoxUI
protected void configurePopup() {
super.configurePopup();
// make opaque to avoid that background shines thru border (e.g. at 150% scaling)
// make opaque to avoid that background shines through border (e.g. at 150% scaling)
setOpaque( true );
// set popup border
@@ -944,7 +945,7 @@ public class FlatComboBoxUI
if( popupBackground != null )
list.setBackground( popupBackground );
// set popup background because it may shine thru when scaled (e.g. at 150%)
// set popup background because it may shine through when scaled (e.g. at 150%)
// use non-UIResource to avoid that it is overwritten when making
// popup visible (see JPopupMenu.setInvoker()) in theme editor preview
setBackground( FlatUIUtils.nonUIResource( list.getBackground() ) );
@@ -1090,7 +1091,7 @@ public class FlatComboBoxUI
}
// using synchronized to avoid problems with code that modifies combo box
// (model, selection, etc) not on AWT thread (which should be not done)
// (model, selection, etc.) not on AWT thread (which should be not done)
synchronized void install( Component c, int focusWidth ) {
if( !(c instanceof JComponent) )
return;
@@ -1242,7 +1243,7 @@ public class FlatComboBoxUI
* Key selection manager that delegates to the default manager.
* Shows the popup if Space key is pressed and "typed characters" buffer is empty.
* If items contain spaces (e.g. "a b") it is still possible to select them
* by pressing keys a, Space and b.
* by pressing keys 'a', 'Space' and 'b'.
*/
private class FlatKeySelectionManager
implements JComboBox.KeySelectionManager, UIResource

View File

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

View File

@@ -24,13 +24,16 @@ import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.function.Function;
import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
@@ -45,6 +48,7 @@ import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.filechooser.FileSystemView;
@@ -163,6 +167,7 @@ public class FlatFileChooserUI
{
private final FlatFileView fileView = new FlatFileView();
private FlatShortcutsPanel shortcutsPanel;
private JScrollPane shortcutsScrollPane;
public static ComponentUI createUI( JComponent c ) {
return new FlatFileChooserUI( (JFileChooser) c );
@@ -182,7 +187,10 @@ public class FlatFileChooserUI
FlatShortcutsPanel panel = createShortcutsPanel( fc );
if( panel.getComponentCount() > 0 ) {
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 );
}
}
@@ -195,6 +203,7 @@ public class FlatFileChooserUI
if( shortcutsPanel != null ) {
fc.removePropertyChangeListener( shortcutsPanel );
shortcutsPanel = null;
shortcutsScrollPane = null;
}
}
@@ -323,7 +332,7 @@ public class FlatFileChooserUI
public Dimension getPreferredSize( JComponent c ) {
Dimension prefSize = super.getPreferredSize( c );
Dimension minSize = getMinimumSize( c );
int shortcutsPanelWidth = (shortcutsPanel != null) ? shortcutsPanel.getPreferredSize().width : 0;
int shortcutsPanelWidth = (shortcutsScrollPane != null) ? shortcutsScrollPane.getPreferredSize().width : 0;
return new Dimension(
Math.max( prefSize.width, minSize.width + shortcutsPanelWidth ),
Math.max( prefSize.height, minSize.height ) );
@@ -369,7 +378,11 @@ public class FlatFileChooserUI
// get system icon
if( f != null ) {
icon = getFileChooser().getFileSystemView().getSystemIcon( f );
try {
icon = getFileChooser().getFileSystemView().getSystemIcon( f );
} catch( NullPointerException ex ) {
// Java 21 may throw a NPE for exe files that use default Windows exe icon
}
if( icon != null ) {
if( icon instanceof ImageIcon )
@@ -396,7 +409,7 @@ public class FlatFileChooserUI
/** @since 2.3 */
public static class FlatShortcutsPanel
extends JToolBar
implements PropertyChangeListener
implements PropertyChangeListener, Scrollable
{
private final JFileChooser fc;
@@ -408,13 +421,14 @@ public class FlatFileChooserUI
protected final File[] files;
protected final JToggleButton[] buttons;
protected final ButtonGroup buttonGroup;
protected final ButtonGroup buttonGroup = new ButtonGroup();
@SuppressWarnings( "unchecked" )
public FlatShortcutsPanel( JFileChooser fc ) {
super( JToolBar.VERTICAL );
this.fc = fc;
setFloatable( false );
putClientProperty( FlatClientProperties.STYLE, "hoverButtonGroupBackground: null" );
buttonSize = UIScale.scale( getUIDimension( "FileChooser.shortcuts.buttonSize", 84, 64 ) );
iconSize = getUIDimension( "FileChooser.shortcuts.iconSize", 32, 32 );
@@ -424,22 +438,25 @@ public class FlatFileChooserUI
iconFunction = (Function<File, Icon>) UIManager.get( "FileChooser.shortcuts.iconFunction" );
FileSystemView fsv = fc.getFileSystemView();
File[] files = getChooserShortcutPanelFiles( fsv );
File[] files = JavaCompatibility2.getChooserShortcutPanelFiles( fsv );
if( filesFunction != null )
files = filesFunction.apply( files );
this.files = files;
// create toolbar buttons
buttons = new JToggleButton[files.length];
buttonGroup = new ButtonGroup();
for( int i = 0; i < files.length; i++ ) {
// wrap drive path
if( fsv.isFileSystemRoot( files[i] ) )
files[i] = fsv.createFileObject( files[i].getAbsolutePath() );
ArrayList<File> filesList = new ArrayList<>();
ArrayList<JToggleButton> buttonsList = new ArrayList<>();
for( File file : files ) {
if( file == null )
continue;
// wrap drive path
if( fsv.isFileSystemRoot( file ) )
file = fsv.createFileObject( file.getAbsolutePath() );
File file = files[i];
String name = getDisplayName( fsv, file );
Icon icon = getIcon( fsv, file );
if( name == null )
continue;
// remove path from name
int lastSepIndex = name.lastIndexOf( File.separatorChar );
@@ -453,16 +470,22 @@ public class FlatFileChooserUI
icon = new ShortcutIcon( icon, iconSize.width, iconSize.height );
// create button
JToggleButton button = createButton( name, icon );
JToggleButton button = createButton( name, icon, file.toString() );
File f = file;
button.addActionListener( e -> {
fc.setCurrentDirectory( file );
fc.setCurrentDirectory( f );
} );
add( button );
buttonGroup.add( button );
buttons[i] = button;
filesList.add( file );
buttonsList.add( button );
}
this.files = filesList.toArray( new File[filesList.size()] );
this.buttons = buttonsList.toArray( new JToggleButton[buttonsList.size()] );
directoryChanged( fc.getCurrentDirectory() );
}
@@ -473,8 +496,10 @@ public class FlatFileChooserUI
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 );
button.setToolTipText( toolTip );
button.setVerticalTextPosition( SwingConstants.BOTTOM );
button.setHorizontalTextPosition( SwingConstants.CENTER );
button.setAlignmentX( Component.CENTER_ALIGNMENT );
@@ -484,32 +509,6 @@ public class FlatFileChooserUI
return button;
}
protected File[] getChooserShortcutPanelFiles( FileSystemView fsv ) {
try {
if( SystemInfo.isJava_12_orLater ) {
Method m = fsv.getClass().getMethod( "getChooserShortcutPanelFiles" );
File[] files = (File[]) m.invoke( fsv );
// on macOS and Linux, files consists only of the user home directory
if( files.length == 1 && files[0].equals( new File( System.getProperty( "user.home" ) ) ) )
files = new File[0];
return files;
} else if( SystemInfo.isWindows ) {
Class<?> cls = Class.forName( "sun.awt.shell.ShellFolder" );
Method m = cls.getMethod( "get", String.class );
return (File[]) m.invoke( null, "fileChooserShortcutPanelFolders" );
}
} catch( IllegalAccessException ex ) {
// do not log because access may be denied via VM option '--illegal-access=deny'
} catch( Exception ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
// fallback
return new File[0];
}
protected String getDisplayName( FileSystemView fsv, File file ) {
if( displayNameFunction != null ) {
String name = displayNameFunction.apply( file );
@@ -530,29 +529,36 @@ public class FlatFileChooserUI
if( doNotUseSystemIcons() )
return new FlatFileViewDirectoryIcon();
// Java 17+ supports getting larger system icons
try {
if( SystemInfo.isJava_17_orLater ) {
Method m = fsv.getClass().getMethod( "getSystemIcon", File.class, int.class, int.class );
return (Icon) m.invoke( fsv, file, iconSize.width, iconSize.height );
} else if( iconSize.width > 16 || iconSize.height > 16 ) {
Class<?> cls = Class.forName( "sun.awt.shell.ShellFolder" );
if( cls.isInstance( file ) ) {
Method m = file.getClass().getMethod( "getIcon", boolean.class );
m.setAccessible( true );
Image image = (Image) m.invoke( file, true );
if( image != null )
return new ImageIcon( image );
// Java 17+ supports getting larger system icons
try {
if( SystemInfo.isJava_17_orLater ) {
Method m = fsv.getClass().getMethod( "getSystemIcon", File.class, int.class, int.class );
return (Icon) m.invoke( fsv, file, iconSize.width, iconSize.height );
} else if( iconSize.width > 16 || iconSize.height > 16 ) {
Class<?> cls = Class.forName( "sun.awt.shell.ShellFolder" );
if( cls.isInstance( file ) ) {
Method m = file.getClass().getMethod( "getIcon", boolean.class );
m.setAccessible( true );
Image image = (Image) m.invoke( file, true );
if( image != null )
return new ImageIcon( image );
}
}
} catch( Exception ex ) {
// do not log InaccessibleObjectException because access
// may be denied via VM option '--illegal-access=deny' (default in Java 16)
// (not catching InaccessibleObjectException here because it is new in Java 9, but FlatLaf also runs on Java 8)
if( !"java.lang.reflect.InaccessibleObjectException".equals( ex.getClass().getName() ) )
LoggingFacade.INSTANCE.logSevere( null, ex );
}
} catch( IllegalAccessException ex ) {
// do not log because access may be denied via VM option '--illegal-access=deny'
} catch( Exception ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
// get system icon in default size 16x16
return fsv.getSystemIcon( file );
// get system icon in default size 16x16
return fsv.getSystemIcon( file );
} catch( NullPointerException ex ) {
// Java 21 may throw a NPE for exe files that use default Windows exe icon
return new FlatFileViewDirectoryIcon();
}
}
protected void directoryChanged( File file ) {
@@ -571,6 +577,8 @@ public class FlatFileChooserUI
buttonGroup.clearSelection();
}
//---- interface PropertyChangeListener ----
@Override
public void propertyChange( PropertyChangeEvent e ) {
switch( e.getPropertyName() ) {
@@ -579,6 +587,41 @@ public class FlatFileChooserUI
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 -------------------------------------------------

View File

@@ -0,0 +1,290 @@
/*
* 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.Font;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.function.BiConsumer;
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*/
Font font = c.getFont();
if( styleSheet.getPointSize( 7 ) != 36f ||
font == null || styleSheet.getPointSize( 4 ) == font.getSize() )
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 || !BasicHTML.isHTMLString( text ) )
return;
// BASE_SIZE rule is parsed in javax.swing.text.html.StyleSheet.addRule()
String style = "<style>BASE_SIZE " + font.getSize() + "</style>";
String openTag = "";
String closeTag = "";
int headIndex;
int styleIndex;
int insertIndex;
if( (headIndex = indexOfTag( text, "head", true )) >= 0 ) {
// there is a <head> tag --> insert after <head> tag
insertIndex = headIndex;
} else if( (styleIndex = indexOfTag( text, "style", false )) >= 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 );
// for unit tests
if( testUpdateRenderer != null )
testUpdateRenderer.accept( c, newText );
}
// for unit tests
static BiConsumer<JComponent, String> testUpdateRenderer;
/**
* Returns start or end index of a HTML tag.
* Checks only for leading '<' character and (case-ignore) tag name.
*/
private static int indexOfTag( String html, String tag, boolean endIndex ) {
int tagLength = tag.length();
int maxLength = html.length() - tagLength - 2;
char lastTagChar = tag.charAt( tagLength - 1 );
for( int i = "<html>".length(); i < maxLength; i++ ) {
// check for leading '<' and last tag name character
if( html.charAt( i ) == '<' && Character.toLowerCase( html.charAt( i + tagLength ) ) == lastTagChar ) {
// compare tag characters from last to first
for( int j = tagLength - 2; j >= 0; j-- ) {
if( Character.toLowerCase( html.charAt( i + 1 + j ) ) != tag.charAt( j ) )
break; // not equal
if( j == 0 ) {
// tag found
return endIndex ? html.indexOf( '>', i + tagLength ) + 1 : i;
}
}
}
}
return -1;
}
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.Rectangle;
import java.beans.PropertyChangeEvent;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JLabel;
@@ -64,6 +60,9 @@ public class FlatLabelUI
{
@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 boolean defaults_initialized = false;
private Map<String, Object> oldStyleValues;
@@ -110,16 +109,13 @@ public class FlatLabelUI
super.installComponents( c );
// update HTML renderer if necessary
updateHTMLRenderer( c, c.getText(), false );
FlatHTML.updateRendererCSSFontBaseSize( c );
}
@Override
public void propertyChange( PropertyChangeEvent e ) {
String name = e.getPropertyName();
if( name == "text" || name == "font" || name == "foreground" ) {
JLabel label = (JLabel) e.getSource();
updateHTMLRenderer( label, label.getText(), true );
} else if( name.equals( FlatClientProperties.STYLE ) || name.equals( FlatClientProperties.STYLE_CLASS ) ) {
if( name.equals( FlatClientProperties.STYLE ) || name.equals( FlatClientProperties.STYLE_CLASS ) ) {
JLabel label = (JLabel) e.getSource();
if( shared && FlatStylingSupport.hasStyleProperty( label ) ) {
// unshare component UI if necessary
@@ -128,9 +124,11 @@ public class FlatLabelUI
} else
installStyle( label );
label.revalidate();
label.repaint();
} else
super.propertyChange( e );
HiDPIUtils.repaint( label );
}
super.propertyChange( e );
FlatHTML.propertyChange( e );
}
/** @since 2 */
@@ -165,83 +163,10 @@ public class FlatLabelUI
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
/**
* Checks whether text contains HTML tags that use "absolute-size" keywords
* (e.g. "x-large") for font-size in default style sheet
* (see javax/swing/text/html/default.css).
* 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;
@Override
public void update( Graphics g, JComponent c ) {
FlatPanelUI.fillRoundedBackground( g, c, arc );
paint( g, c );
}
static Graphics createGraphicsHTMLTextYCorrection( Graphics g, JComponent c ) {

View File

@@ -22,13 +22,21 @@ import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.plaf.ComponentUI;
/**
* Line border for various components.
*
* <p>
* Paints a scaled (usually 1px thick) line around the component.
* The line thickness is not added to the border insets.
* 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
*/
@@ -40,7 +48,7 @@ public class FlatLineBorder
/** @since 2 */ private final int arc;
public FlatLineBorder( Insets insets, Color lineColor ) {
this( insets, lineColor, 1f, 0 );
this( insets, lineColor, 1f, -1 );
}
/** @since 2 */
@@ -51,26 +59,62 @@ public class FlatLineBorder
this.arc = arc;
}
/** @since 3.5 */
public FlatLineBorder( Insets insets, int arc ) {
this( insets, null, 0, arc );
}
public Color getLineColor() {
return lineColor;
}
/**
* Returns the (unscaled) line thickness used to paint the border.
* The line thickness does not affect the border insets.
*/
public float getLineThickness() {
return lineThickness;
}
/** @since 2 */
/**
* Returns the (unscaled) arc diameter of the border corners.
*
* @since 2
*/
public int getArc() {
return arc;
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
if( c instanceof JComponent && ((JComponent)c).getClientProperty( FlatPopupFactory.KEY_POPUP_USES_NATIVE_BORDER ) != null )
return;
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();
try {
FlatUIUtils.setRenderingHints( g2 );
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 {
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.StyleableUI;
import com.formdev.flatlaf.util.Graphics2DProxy;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale;
@@ -182,7 +183,7 @@ public class FlatListUI
case FlatClientProperties.STYLE_CLASS:
installStyle();
list.revalidate();
list.repaint();
HiDPIUtils.repaint( list );
break;
}
};
@@ -205,7 +206,7 @@ public class FlatListUI
Rectangle r = getCellBounds( list, firstIndex, lastIndex );
if( r != null ) {
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 BasicComboBoxRenderer) &&
(selectionArc > 0 ||
(selectionInsets != null &&
(selectionInsets.top != 0 || selectionInsets.left != 0 || selectionInsets.bottom != 0 || selectionInsets.right != 0))) )
(selectionInsets != null && !FlatUIUtils.isInsetsEmpty( selectionInsets ))) )
{
// Because selection painting is done in the cell renderer, it would be
// 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 );
}
/** @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 ) {
float arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight;
arcTopLeft = arcTopRight = arcBottomLeft = arcBottomRight = UIScale.scale( selectionArc / 2f );
@@ -411,7 +419,7 @@ public class FlatListUI
int leftIndex = locationToIndex( list, new Point( r.x - 1, r.y ) );
int rightIndex = locationToIndex( list, new Point( r.x + r.width, r.y ) );
// special handling for the case that last column contains less cells than the other columns
// special handling for the case that last column contains fewer cells than the other columns
boolean ltr = list.getComponentOrientation().isLeftToRight();
if( !ltr && leftIndex >= 0 && leftIndex != row && leftIndex == locationToIndex( list, new Point( r.x - 1, r.y - 1 ) ) )
leftIndex = -1;
@@ -440,7 +448,8 @@ public class FlatListUI
* 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.
* This method is intended for use in custom cell renderers
* to support {@link #selectionArc} and {@link #selectionInsets}.
*
* @since 3
*/

View File

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

View File

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

View File

@@ -103,13 +103,23 @@ public class FlatMenuItemUI
oldStyleValues = null;
}
@Override
protected void installComponents( JMenuItem menuItem ) {
super.installComponents( menuItem );
// update HTML renderer if necessary
FlatHTML.updateRendererCSSFontBaseSize( menuItem );
}
protected FlatMenuItemRenderer createRenderer() {
return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter );
}
@Override
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 */

View File

@@ -17,6 +17,7 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
@@ -46,6 +47,7 @@ import javax.swing.plaf.basic.BasicMenuUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableLookupProvider;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
/**
@@ -135,6 +137,14 @@ public class FlatMenuUI
oldStyleValues = null;
}
@Override
protected void installComponents( JMenuItem menuItem ) {
super.installComponents( menuItem );
// update HTML renderer if necessary
FlatHTML.updateRendererCSSFontBaseSize( menuItem );
}
protected FlatMenuItemRenderer createRenderer() {
return new FlatMenuRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter );
}
@@ -158,7 +168,7 @@ public class FlatMenuUI
JMenu menu = (JMenu) e.getSource();
if( menu.isTopLevelMenu() && menu.isRolloverEnabled() ) {
menu.getModel().setRollover( rollover );
menu.repaint();
HiDPIUtils.repaint( menu );
}
}
};
@@ -166,7 +176,9 @@ public class FlatMenuUI
@Override
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 */
@@ -271,7 +283,7 @@ public class FlatMenuUI
if( !isHover() )
selectionBackground = getStyleFromMenuBarUI( ui -> ui.selectionBackground, menuBarSelectionBackground, selectionBackground );
JMenuBar menuBar = (JMenuBar) menuItem.getParent();
Container menuBar = menuItem.getParent();
JRootPane rootPane = SwingUtilities.getRootPane( menuBar );
if( rootPane != null && rootPane.getParent() instanceof Window &&
rootPane.getJMenuBar() == menuBar &&
@@ -321,12 +333,17 @@ public class FlatMenuUI
}
private <T> T getStyleFromMenuBarUI( Function<FlatMenuBarUI, T> f, T defaultValue ) {
MenuBarUI ui = ((JMenuBar)menuItem.getParent()).getUI();
if( !(ui instanceof FlatMenuBarUI) )
return defaultValue;
T value = f.apply( (FlatMenuBarUI) ui );
return (value != null) ? value : defaultValue;
Container menuItemParent = menuItem.getParent();
if( menuItemParent instanceof JMenuBar ) {
MenuBarUI ui = ((JMenuBar) menuItemParent).getUI();
if( ui instanceof FlatMenuBarUI ) {
T value = f.apply( (FlatMenuBarUI) ui );
if( value != null ) {
return value;
}
}
}
return defaultValue;
}
}
}

View File

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

View File

@@ -16,6 +16,7 @@
package com.formdev.flatlaf.ui;
import java.awt.GraphicsConfiguration;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.Window;
@@ -35,8 +36,16 @@ import com.formdev.flatlaf.util.SystemInfo;
*/
class FlatNativeLinuxLibrary
{
private static int API_VERSION_LINUX = 3001;
/**
* Checks whether native library is loaded/available.
* <p>
* <b>Note</b>: It is required to invoke this method before invoking any other
* method of this class. Otherwise, the native library may not be loaded.
*/
static boolean isLoaded() {
return SystemInfo.isLinux && FlatNativeLibrary.isLoaded();
return SystemInfo.isLinux && FlatNativeLibrary.isLoaded( API_VERSION_LINUX );
}
// direction for _NET_WM_MOVERESIZE message
@@ -88,7 +97,11 @@ class FlatNativeLinuxLibrary
}
private static Point scale( Window window, Point pt ) {
AffineTransform transform = window.getGraphicsConfiguration().getDefaultTransform();
GraphicsConfiguration gc = window.getGraphicsConfiguration();
if( gc == null )
return pt;
AffineTransform transform = gc.getDefaultTransform();
int x = (int) Math.round( pt.x * transform.getScaleX() );
int y = (int) Math.round( pt.y * transform.getScaleY() );
return new Point( x, y );

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,6 +16,7 @@
package com.formdev.flatlaf.ui;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.beans.PropertyChangeEvent;
@@ -23,11 +24,14 @@ import java.beans.PropertyChangeListener;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.LookAndFeel;
import javax.swing.border.Border;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicPanelUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale;
@@ -69,6 +73,8 @@ public class FlatPanelUI
super.installUI( c );
c.addPropertyChangeListener( this );
if( c.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER ) != null )
FullWindowContentSupport.registerPlaceholder( c );
installStyle( (JPanel) c );
}
@@ -78,10 +84,20 @@ public class FlatPanelUI
super.uninstallUI( c );
c.removePropertyChangeListener( this );
if( c.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER ) != null )
FullWindowContentSupport.unregisterPlaceholder( c );
oldStyleValues = null;
}
@Override
protected void installDefaults( JPanel p ) {
super.installDefaults( p );
if( p.getClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_PLACEHOLDER ) != null )
LookAndFeel.installProperty( p, "opaque", false );
}
/** @since 2.0.1 */
@Override
public void propertyChange( PropertyChangeEvent e ) {
@@ -96,7 +112,18 @@ public class FlatPanelUI
} else
installStyle( c );
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;
}
}
@@ -135,31 +162,52 @@ public class FlatPanelUI
@Override
public void update( Graphics g, JComponent c ) {
// fill background
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 );
}
fillRoundedBackground( g, c, arc );
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.icons.FlatCapsLockIcon;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.UIScale;
/**
@@ -163,7 +164,7 @@ public class FlatPasswordFieldUI
}
private void repaint( KeyEvent e ) {
if( e.getKeyCode() == KeyEvent.VK_CAPS_LOCK ) {
e.getComponent().repaint();
HiDPIUtils.repaint( e.getComponent() );
scrollCaretToVisible();
}
}
@@ -326,7 +327,7 @@ public class FlatPasswordFieldUI
if( visible != revealButton.isVisible() ) {
revealButton.setVisible( visible );
c.revalidate();
c.repaint();
HiDPIUtils.repaint( c );
if( !visible ) {
revealButton.setSelected( false );

View File

@@ -17,6 +17,7 @@
package com.formdev.flatlaf.ui;
import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
@@ -35,11 +36,16 @@ import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.awt.event.MouseEvent;
import java.awt.event.WindowFocusListener;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicReference;
import javax.swing.JComponent;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
@@ -57,6 +63,7 @@ import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.plaf.basic.BasicComboPopup;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale;
@@ -70,9 +77,13 @@ import com.formdev.flatlaf.util.UIScale;
public class FlatPopupFactory
extends PopupFactory
{
static final String KEY_POPUP_USES_NATIVE_BORDER = "FlatLaf.internal.FlatPopupFactory.popupUsesNativeBorder";
private MethodHandle java8getPopupMethod;
private MethodHandle java9getPopupMethod;
private final ArrayList<NonFlashingPopup> stillShownHeavyWeightPopups = new ArrayList<>();
@Override
public Popup getPopup( Component owner, Component contents, int x, int y )
throws IllegalArgumentException
@@ -83,28 +94,54 @@ public class FlatPopupFactory
y = pt.y;
}
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" );
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
if( SystemInfo.isMacOS || SystemInfo.isLinux )
return new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, true ), contents );
// Windows 11 with FlatLaf native library can use rounded corners and shows drop shadow for heavy weight popups
int borderCornerRadius;
if( isWindows11BorderSupported() &&
(borderCornerRadius = getBorderCornerRadius( owner, contents )) > 0 )
{
NonFlashingPopup popup = new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, true ), contents );
if( popup.popupWindow != null )
setupWindows11Border( popup.popupWindow, contents, borderCornerRadius );
if( SystemInfo.isMacOS || SystemInfo.isLinux ) {
NonFlashingPopup popup = new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, true ), owner, contents );
if( popup.popupWindow != null && isMacOSBorderSupported() )
setupRoundedBorder( popup.popupWindow, owner, contents );
return popup;
}
// Windows 11 with FlatLaf native library can use rounded corners and shows drop shadow for heavy weight popups
if( isWindows11BorderSupported() &&
getBorderCornerRadius( owner, contents ) > 0 )
{
NonFlashingPopup popup = new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, true ), owner, contents );
if( popup.popupWindow != null )
setupRoundedBorder( popup.popupWindow, owner, contents );
return popup;
}
// check whether popup overlaps a heavy weight component
if( !forceHeavyWeight && overlapsHeavyWeightComponent( owner, contents, x, y ) )
forceHeavyWeight = true;
// create drop shadow popup
return new DropShadowPopup( getPopupForScreenOfOwner( owner, contents, x, y, forceHeavyWeight ), owner, contents );
Popup popupForScreenOfOwner = getPopupForScreenOfOwner( owner, contents, x, y, forceHeavyWeight );
GraphicsConfiguration gc = owner.getGraphicsConfiguration();
return (gc != null && gc.isTranslucencyCapable())
? new DropShadowPopup( popupForScreenOfOwner, owner, contents )
: new NonFlashingPopup( popupForScreenOfOwner, owner, contents );
}
/**
@@ -155,67 +192,6 @@ public class FlatPopupFactory
}
}
/**
* Shows the given popup and, if necessary, fixes the location of a heavy weight popup window.
* <p>
* On a dual screen setup, where screens use different scale factors, it may happen
* that the window location changes when showing a heavy weight popup window.
* E.g. when opening a dialog on the secondary screen and making combobox popup visible.
* <p>
* This is a workaround for https://bugs.openjdk.java.net/browse/JDK-8224608
*/
private static void showPopupAndFixLocation( Popup popup, Window popupWindow ) {
if( popupWindow != null ) {
// remember location of heavy weight popup window
int x = popupWindow.getX();
int y = popupWindow.getY();
popup.show();
// restore popup window location if it has changed
// (probably scaled when screens use different scale factors)
if( popupWindow.getX() != x || popupWindow.getY() != y )
popupWindow.setLocation( x, y );
} else
popup.show();
}
private boolean isOptionEnabled( Component owner, Component contents, String clientKey, String uiKey ) {
Object value = getOption( owner, contents, clientKey, uiKey );
return (value instanceof Boolean) ? (Boolean) value : false;
}
private int getBorderCornerRadius( Component owner, Component contents ) {
String uiKey =
(contents instanceof BasicComboPopup) ? "ComboBox.borderCornerRadius" :
(contents instanceof JPopupMenu) ? "PopupMenu.borderCornerRadius" :
(contents instanceof JToolTip) ? "ToolTip.borderCornerRadius" :
"Popup.borderCornerRadius";
Object value = getOption( owner, contents, FlatClientProperties.POPUP_BORDER_CORNER_RADIUS, uiKey );
return (value instanceof Integer) ? (Integer) value : 0;
}
/**
* Get option from:
* <ol>
* <li>client property {@code clientKey} of {@code owner}
* <li>client property {@code clientKey} of {@code contents}
* <li>UI property {@code uiKey}
* </ol>
*/
private Object getOption( Component owner, Component contents, String clientKey, String uiKey ) {
for( Component c : new Component[] { owner, contents } ) {
if( c instanceof JComponent ) {
Object value = ((JComponent)c).getClientProperty( clientKey );
if( value != null )
return value;
}
}
return UIManager.get( uiKey );
}
/**
* There is no API in Java 8 to force creation of heavy weight popups,
* but it is possible with reflection. Java 9 provides a new method.
@@ -251,6 +227,51 @@ public class FlatPopupFactory
}
}
private static boolean isOptionEnabled( Component owner, Component contents, String clientKey, String uiKey ) {
Object value = getOption( owner, contents, clientKey, uiKey );
return (value instanceof Boolean) ? (Boolean) value : false;
}
/**
* Get option from:
* <ol>
* <li>client property {@code clientKey} of {@code owner}
* <li>client property {@code clientKey} of {@code contents}
* <li>UI property {@code uiKey}
* </ol>
*/
private static Object getOption( Component owner, Component contents, String clientKey, String uiKey ) {
for( Component c : new Component[] { owner, contents } ) {
if( c instanceof JComponent ) {
Object value = ((JComponent)c).getClientProperty( clientKey );
if( value != null )
return value;
}
}
return UIManager.get( uiKey );
}
/**
* 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 -----------------------------------------------------------
/**
* Usually ToolTipManager places a tooltip at (mouseLocation.x, mouseLocation.y + 20).
* In case that the tooltip would be partly outside of the screen,
@@ -335,48 +356,90 @@ public class FlatPopupFactory
((JComponent)owner).getToolTipLocation( me ) != null;
}
//---- native rounded border ----------------------------------------------
private static boolean isWindows11BorderSupported() {
return SystemInfo.isWindows_11_orLater && FlatNativeWindowsLibrary.isLoaded();
return SystemInfo.isWindows_11_orLater &&
FlatSystemProperties.getBoolean( FlatSystemProperties.USE_ROUNDED_POPUP_BORDER, true ) &&
FlatNativeWindowsLibrary.isLoaded();
}
private static void setupWindows11Border( Window popupWindow, Component contents, int borderCornerRadius ) {
// make sure that the Windows 11 window is created
if( !popupWindow.isDisplayable() )
popupWindow.addNotify();
private static boolean isMacOSBorderSupported() {
// 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
boolean isMacOS_14_4_orLater = (SystemInfo.osVersion >= SystemInfo.toVersion( 14, 4, 0, 0 ));
// get window handle
long hwnd = FlatNativeWindowsLibrary.getHWND( popupWindow );
return SystemInfo.isMacOS &&
FlatSystemProperties.getBoolean( FlatSystemProperties.USE_ROUNDED_POPUP_BORDER, !isMacOS_14_4_orLater ) &&
FlatNativeMacLibrary.isLoaded();
}
// set corner preference
int cornerPreference = (borderCornerRadius <= 4)
? FlatNativeWindowsLibrary.DWMWCP_ROUNDSMALL // 4px
: FlatNativeWindowsLibrary.DWMWCP_ROUND; // 8px
FlatNativeWindowsLibrary.setWindowCornerPreference( hwnd, cornerPreference );
private static void setupRoundedBorder( Window popupWindow, Component owner, Component contents ) {
int borderCornerRadius = getBorderCornerRadius( owner, contents );
float borderWidth = getRoundedBorderWidth( owner, contents );
// set border color
int red = -1; // use system default color
int green = 0;
int blue = 0;
// get Swing border color
Color borderColor;
if( contents instanceof JComponent ) {
Border border = ((JComponent)contents).getBorder();
border = FlatUIUtils.unwrapNonUIResourceBorder( border );
// get color from border of contents (e.g. JPopupMenu or JToolTip)
Color borderColor = null;
if( border instanceof FlatLineBorder )
borderColor = ((FlatLineBorder)border).getLineColor();
else if( border instanceof LineBorder )
borderColor = ((LineBorder)border).getLineColor();
else if( border instanceof EmptyBorder )
red = -2; // do not paint border
borderColor = FlatNativeWindowsLibrary.COLOR_NONE; // do not paint border
else
borderColor = null; // use system default color
if( borderColor != null ) {
red = borderColor.getRed();
green = borderColor.getGreen();
blue = borderColor.getBlue();
}
// avoid that FlatLineBorder paints the Swing border
((JComponent)contents).putClientProperty( KEY_POPUP_USES_NATIVE_BORDER, true );
} else
borderColor = null; // use system default color
if( popupWindow.isDisplayable() ) {
// native window already created
setupRoundedBorderImpl( popupWindow, borderCornerRadius, borderWidth, borderColor );
} else {
// native window not yet created --> add listener to set native border after window creation
AtomicReference<HierarchyListener> l = new AtomicReference<>();
l.set( e -> {
if( e.getID() == HierarchyEvent.HIERARCHY_CHANGED &&
(e.getChangeFlags() & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0 )
{
setupRoundedBorderImpl( popupWindow, borderCornerRadius, borderWidth, borderColor );
popupWindow.removeHierarchyListener( l.get() );
}
} );
popupWindow.addHierarchyListener( l.get() );
}
}
private static void setupRoundedBorderImpl( Window popupWindow, int borderCornerRadius, float borderWidth, Color borderColor ) {
if( SystemInfo.isWindows ) {
// get native window handle
long hwnd = FlatNativeWindowsLibrary.getHWND( popupWindow );
// set corner preference
int cornerPreference = (borderCornerRadius <= 4)
? FlatNativeWindowsLibrary.DWMWCP_ROUNDSMALL // 4px
: FlatNativeWindowsLibrary.DWMWCP_ROUND; // 8px
FlatNativeWindowsLibrary.setWindowCornerPreference( hwnd, cornerPreference );
// set border color
FlatNativeWindowsLibrary.dwmSetWindowAttributeCOLORREF( hwnd, FlatNativeWindowsLibrary.DWMWA_BORDER_COLOR, borderColor );
} else if( SystemInfo.isMacOS ) {
if( borderColor == null || borderColor == FlatNativeWindowsLibrary.COLOR_NONE )
borderWidth = 0;
// set corner radius, border width and color
FlatNativeMacLibrary.setWindowRoundedBorder( popupWindow, borderCornerRadius,
borderWidth, (borderColor != null) ? borderColor.getRGB() : 0 );
}
FlatNativeWindowsLibrary.setWindowBorderColor( hwnd, red, green, blue );
}
private static void resetWindows11Border( Window popupWindow ) {
@@ -389,20 +452,144 @@ public class FlatPopupFactory
FlatNativeWindowsLibrary.setWindowCornerPreference( hwnd, FlatNativeWindowsLibrary.DWMWCP_DONOTROUND );
}
private static int getBorderCornerRadius( Component owner, Component contents ) {
String uiKey =
(contents instanceof BasicComboPopup) ? "ComboBox.borderCornerRadius" :
(contents instanceof JPopupMenu) ? "PopupMenu.borderCornerRadius" :
(contents instanceof JToolTip) ? "ToolTip.borderCornerRadius" :
"Popup.borderCornerRadius";
Object value = getOption( owner, contents, FlatClientProperties.POPUP_BORDER_CORNER_RADIUS, uiKey );
return (value instanceof Integer) ? (Integer) value : 0;
}
private static float getRoundedBorderWidth( Component owner, Component contents ) {
String uiKey =
(contents instanceof BasicComboPopup) ? "ComboBox.roundedBorderWidth" :
(contents instanceof JPopupMenu) ? "PopupMenu.roundedBorderWidth" :
(contents instanceof JToolTip) ? "ToolTip.roundedBorderWidth" :
"Popup.roundedBorderWidth";
Object value = getOption( owner, contents, FlatClientProperties.POPUP_ROUNDED_BORDER_WIDTH, uiKey );
return (value instanceof Number) ? ((Number)value).floatValue() : 0;
}
//---- fixes --------------------------------------------------------------
private static boolean overlapsHeavyWeightComponent( Component owner, Component contents, int x, int y ) {
if( owner == null )
return false;
Window window = SwingUtilities.getWindowAncestor( owner );
if( window == null )
return false;
Rectangle r = new Rectangle( new Point( x, y ), contents.getPreferredSize() );
return overlapsHeavyWeightComponent( window, r );
}
private static boolean overlapsHeavyWeightComponent( Component parent, Rectangle r ) {
if( !parent.isVisible() || !r.intersects( parent.getBounds() ) )
return false;
if( !parent.isLightweight() && !(parent instanceof Window) )
return true;
if( parent instanceof Container ) {
Rectangle r2 = new Rectangle( r.x - parent.getX(), r.y - parent.getY(), r.width, r.height );
for( Component c : ((Container)parent).getComponents() ) {
if( overlapsHeavyWeightComponent( c, r2 ) )
return true;
}
}
return false;
}
/**
* On Linux with Wayland, since Java 21, Swing adds a window focus listener to popup owner/invoker window,
* which hides the popup as soon as the owner/invoker window looses focus.
* This works fine for light-weight popups.
* It also works for heavy-weight popups if they do not request focus.
* Because FlatLaf always uses heavy-weight popups, all popups that request focus
* are broken since Java 21.
*
* This method removes the problematic window focus listener.
*
* https://bugs.openjdk.org/browse/JDK-8280993
* https://github.com/openjdk/jdk/pull/13830
*/
private static void fixLinuxWaylandJava21focusIssue( Component owner ) {
// only necessary on Linux when running in Java 21+
if( owner == null || !SystemInfo.isLinux || SystemInfo.javaVersion < SystemInfo.toVersion( 21, 0, 0, 0 ) )
return;
// get window
Window window = SwingUtilities.getWindowAncestor( owner );
if( window == null )
return;
// remove window focus listener, which was added from class sun.awt.UNIXToolkit since Java 21
for( WindowFocusListener l : window.getWindowFocusListeners() ) {
if( "sun.awt.UNIXToolkit$1".equals( l.getClass().getName() ) ) {
window.removeWindowFocusListener( l );
break;
}
}
}
/**
* Shows the given popup and, if necessary, fixes the location of a heavy weight popup window.
* <p>
* On a dual screen setup, where screens use different scale factors, it may happen
* that the window location changes when showing a heavy weight popup window.
* E.g. when opening a dialog on the secondary screen and making combobox popup visible.
* <p>
* This is a workaround for https://bugs.openjdk.java.net/browse/JDK-8224608
*/
private static void showPopupAndFixLocation( Popup popup, Window popupWindow ) {
if( popupWindow != null ) {
// remember location of heavy weight popup window
int x = popupWindow.getX();
int y = popupWindow.getY();
popup.show();
// restore popup window location if it has changed
// (probably scaled when screens use different scale factors)
if( popupWindow.getX() != x || popupWindow.getY() != y )
popupWindow.setLocation( x, y );
} else
popup.show();
}
//---- class NonFlashingPopup ---------------------------------------------
private static class NonFlashingPopup
/**
* 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
{
private Popup delegate;
Component owner;
private Component contents;
// heavy weight
protected Window popupWindow;
Window popupWindow;
private Color oldPopupWindowBackground;
NonFlashingPopup( Popup delegate, Component contents ) {
private boolean disposed;
NonFlashingPopup( Popup delegate, Component owner, Component contents ) {
this.delegate = delegate;
this.owner = owner;
this.contents = contents;
popupWindow = SwingUtilities.windowForComponent( contents );
@@ -416,8 +603,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
public void show() {
public final void show() {
if( disposed )
return;
showImpl();
}
void showImpl() {
if( delegate != null ) {
showPopupAndFixLocation( delegate, popupWindow );
@@ -441,10 +647,36 @@ public class FlatPopupFactory
}
@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 )
((JComponent)contents).putClientProperty( KEY_POPUP_USES_NATIVE_BORDER, null );
if( delegate != null ) {
delegate.hide();
delegate = null;
owner = null;
contents = null;
}
@@ -455,6 +687,28 @@ public class FlatPopupFactory
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.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 ----------------------------------------------
@@ -462,8 +716,6 @@ public class FlatPopupFactory
private class DropShadowPopup
extends NonFlashingPopup
{
private final Component owner;
// light weight
private JComponent lightComp;
private Border oldBorder;
@@ -478,11 +730,11 @@ public class FlatPopupFactory
// heavy weight
private Popup dropShadowDelegate;
private Window dropShadowWindow;
private JPanel dropShadowPanel2;
private Color oldDropShadowWindowBackground;
DropShadowPopup( Popup delegate, Component owner, Component contents ) {
super( delegate, contents );
this.owner = owner;
super( delegate, owner, contents );
Dimension size = contents.getPreferredSize();
if( size.width <= 0 || size.height <= 0 )
@@ -498,24 +750,24 @@ public class FlatPopupFactory
// the drop shadow and is positioned behind the popup window.
// create panel that paints the drop shadow
JPanel dropShadowPanel = new JPanel();
dropShadowPanel.setBorder( createDropShadowBorder() );
dropShadowPanel.setOpaque( false );
dropShadowPanel2 = new JPanel();
dropShadowPanel2.setBorder( createDropShadowBorder() );
dropShadowPanel2.setOpaque( false );
// set preferred size of drop shadow panel
Dimension prefSize = popupWindow.getPreferredSize();
Insets insets = dropShadowPanel.getInsets();
dropShadowPanel.setPreferredSize( new Dimension(
Insets insets = dropShadowPanel2.getInsets();
dropShadowPanel2.setPreferredSize( new Dimension(
prefSize.width + insets.left + insets.right,
prefSize.height + insets.top + insets.bottom ) );
// create heavy weight popup for drop shadow
int x = popupWindow.getX() - insets.left;
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
dropShadowWindow = SwingUtilities.windowForComponent( dropShadowPanel );
dropShadowWindow = SwingUtilities.windowForComponent( dropShadowPanel2 );
if( dropShadowWindow != null ) {
oldDropShadowWindowBackground = dropShadowWindow.getBackground();
dropShadowWindow.setBackground( new Color( 0, true ) );
@@ -552,6 +804,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() {
return new FlatDropShadowBorder(
UIManager.getColor( "Popup.dropShadowColor" ),
@@ -560,14 +829,14 @@ public class FlatPopupFactory
}
@Override
public void show() {
void showImpl() {
if( dropShadowDelegate != null )
showPopupAndFixLocation( dropShadowDelegate, dropShadowWindow );
if( mediumWeightPanel != null )
showMediumWeightDropShadow();
super.show();
super.showImpl();
// fix location of light weight popup in case it has left or top drop shadow
if( lightComp != null ) {
@@ -578,10 +847,11 @@ public class FlatPopupFactory
}
@Override
public void hide() {
void hideImpl() {
if( dropShadowDelegate != null ) {
dropShadowDelegate.hide();
dropShadowDelegate = null;
dropShadowPanel2 = null;
}
if( mediumWeightPanel != null ) {
@@ -590,7 +860,7 @@ public class FlatPopupFactory
mediumWeightPanel = null;
}
super.hide();
super.hideImpl();
if( dropShadowWindow != null ) {
dropShadowWindow.setBackground( oldDropShadowWindowBackground );
@@ -674,5 +944,26 @@ public class FlatPopupFactory
if( dropShadowPanel != null && mediumWeightPanel != null )
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 ) );
dropShadowPanel2.invalidate();
dropShadowWindow.pack();
// update drop shadow popup window location
int x = popupWindow.getX() - insets.left;
int y = popupWindow.getY() - insets.top;
dropShadowWindow.setLocation( x, y );
}
}
}
}

View File

@@ -239,11 +239,13 @@ public class FlatPopupMenuUI
if( gc == null && popupMenu.getInvoker() != null )
gc = popupMenu.getInvoker().getGraphicsConfiguration();
// compute screen height
if( gc == null )
return new Rectangle( Toolkit.getDefaultToolkit().getScreenSize() );
// compute screen bounds
// (always subtract screen insets because there is no API to detect whether
// the popup can overlap the taskbar; see JPopupMenu.canPopupOverlapTaskBar())
Toolkit toolkit = Toolkit.getDefaultToolkit();
Rectangle screenBounds = (gc != null) ? gc.getBounds() : new Rectangle( toolkit.getScreenSize() );
Rectangle screenBounds = gc.getBounds();
Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets( gc );
return FlatUIUtils.subtractInsets( screenBounds, screenInsets );
}

View File

@@ -18,6 +18,7 @@ package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.FlatClientProperties.*;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
@@ -86,6 +87,17 @@ public class FlatProgressBarUI
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
protected void installDefaults() {
super.installDefaults();
@@ -110,17 +122,25 @@ public class FlatProgressBarUI
propertyChangeListener = e -> {
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_SQUARE:
progressBar.revalidate();
progressBar.repaint();
HiDPIUtils.repaint( progressBar );
break;
case STYLE:
case STYLE_CLASS:
installStyle();
progressBar.revalidate();
progressBar.repaint();
HiDPIUtils.repaint( progressBar );
break;
}
};
@@ -274,6 +294,6 @@ public class FlatProgressBarUI
// Only solution is to repaint whole progress bar.
double systemScaleFactor = UIScale.getSystemScaleFactor( progressBar.getGraphicsConfiguration() );
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 javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.LookAndFeel;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicMenuItemUI;
@@ -102,13 +103,23 @@ public class FlatRadioButtonMenuItemUI
oldStyleValues = null;
}
@Override
protected void installComponents( JMenuItem menuItem ) {
super.installComponents( menuItem );
// update HTML renderer if necessary
FlatHTML.updateRendererCSSFontBaseSize( menuItem );
}
protected FlatMenuItemRenderer createRenderer() {
return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter );
}
@Override
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 */

View File

@@ -35,16 +35,19 @@ import javax.swing.CellRendererPane;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.LookAndFeel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicButtonListener;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.plaf.basic.BasicRadioButtonUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.icons.FlatCheckBoxIcon;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale;
@@ -158,6 +161,10 @@ public class FlatRadioButtonUI
/** @since 2 */
protected void propertyChange( AbstractButton b, PropertyChangeEvent e ) {
switch( e.getPropertyName() ) {
case BasicHTML.propertyKey:
FlatHTML.updateRendererCSSFontBaseSize( b );
break;
case FlatClientProperties.STYLE:
case FlatClientProperties.STYLE_CLASS:
if( shared && FlatStylingSupport.hasStyleProperty( b ) ) {
@@ -167,7 +174,7 @@ public class FlatRadioButtonUI
} else
installStyle( b );
b.revalidate();
b.repaint();
HiDPIUtils.repaint( b );
break;
}
}
@@ -262,7 +269,7 @@ public class FlatRadioButtonUI
@Override
public void paint( Graphics g, JComponent c ) {
// fill background even if not opaque if
// fill background even if not opaque and if:
// - contentAreaFilled is true and
// - if background color is different to default background color
// (this paints selection if using the component as cell renderer)
@@ -278,20 +285,27 @@ public class FlatRadioButtonUI
int focusWidth = getIconFocusWidth( c );
if( focusWidth > 0 ) {
boolean ltr = c.getComponentOrientation().isLeftToRight();
int halign = ((AbstractButton)c).getHorizontalAlignment();
if( halign == SwingConstants.LEADING )
halign = ltr ? SwingConstants.LEFT : SwingConstants.RIGHT;
else if( halign == SwingConstants.TRAILING )
halign = ltr ? SwingConstants.RIGHT : SwingConstants.LEFT;
Insets insets = c.getInsets( tempInsets );
int leftOrRightInset = ltr ? insets.left : insets.right;
if( focusWidth > leftOrRightInset ) {
if( (focusWidth > insets.left || focusWidth > insets.right) &&
(halign == SwingConstants.LEFT || halign == SwingConstants.RIGHT) )
{
// The left (or right) inset is smaller than the focus width, which may be
// the case if insets were explicitly reduced (e.g. with an EmptyBorder).
// In this case the width has been increased in getPreferredSize() and
// here it is necessary to fix icon and text painting location.
int offset = focusWidth - leftOrRightInset;
if( !ltr )
offset = -offset;
int offset = (halign == SwingConstants.LEFT)
? Math.max( focusWidth - insets.left, 0 )
: -Math.max( focusWidth - insets.right, 0 );
// move the graphics origin to the left (or right)
g.translate( offset, 0 );
super.paint( g, c );
super.paint( FlatLabelUI.createGraphicsHTMLTextYCorrection( g, c ), c );
g.translate( -offset, 0 );
return;
}
@@ -328,6 +342,11 @@ public class FlatRadioButtonUI
: 0;
}
@Override
public int getBaseline( JComponent c, int width, int height ) {
return FlatButtonUI.getBaselineImpl( c, width, height );
}
//---- class FlatRadioButtonListener --------------------------------------
/** @since 2 */

View File

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

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.StyleableLookupProvider;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale;
@@ -212,14 +213,14 @@ public class FlatScrollBarUI
switch( e.getPropertyName() ) {
case FlatClientProperties.SCROLL_BAR_SHOW_BUTTONS:
scrollbar.revalidate();
scrollbar.repaint();
HiDPIUtils.repaint( scrollbar );
break;
case FlatClientProperties.STYLE:
case FlatClientProperties.STYLE_CLASS:
installStyle();
scrollbar.revalidate();
scrollbar.repaint();
HiDPIUtils.repaint( scrollbar );
break;
case "componentOrientation":
@@ -245,7 +246,7 @@ public class FlatScrollBarUI
// because scroll bars do not receive mouse exited event.
// The scroll pane, including its scroll bars, is not part
// of the component hierarchy and does not receive mouse events
// directly. Instead LWComponentPeer receives mouse events
// directly. Instead, LWComponentPeer receives mouse events
// and delegates them to peers, but entered/exited events
// are sent only for the whole scroll pane.
// Exited event is only sent when mouse leaves scroll pane.
@@ -492,7 +493,7 @@ public class FlatScrollBarUI
private void repaint() {
if( scrollbar.isEnabled() )
scrollbar.repaint();
HiDPIUtils.repaint( scrollbar );
}
}

View File

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

View File

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

View File

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

View File

@@ -25,6 +25,8 @@ import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
@@ -191,6 +193,23 @@ public class FlatSliderUI
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
protected PropertyChangeListener createPropertyChangeListener( JSlider slider ) {
return FlatStylingSupport.createPropertyChangeListener( slider, this::installStyle,
@@ -422,7 +441,7 @@ debug*/
Color thumbColor, Color thumbBorderColor, Color focusedColor, float thumbBorderWidth, int focusWidth )
{
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%
HiDPIUtils.paintAtScale1x( (Graphics2D) g, thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height,
(g2d, x2, y2, width2, height2, scaleFactor) -> {
@@ -579,15 +598,15 @@ debug*/
@Override
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() ) {
// the needle of the directional thumb is painted outside of thumbRect
// --> 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
int extra = (int) Math.ceil( UIScale.scale( focusWidth ) * 0.4142f );
if( slider.getOrientation() == JSlider.HORIZONTAL )
@@ -597,10 +616,9 @@ debug*/
if( !slider.getComponentOrientation().isLeftToRight() )
r.x -= extra;
}
}
slider.repaint( r );
} else
super.setThumbLocation( x, y );
HiDPIUtils.repaint( slider, r );
}
//---- class FlatTrackListener --------------------------------------------
@@ -688,21 +706,21 @@ debug*/
!UIManager.getBoolean( "Slider.snapToTicksOnReleased" ) )
{
calculateThumbLocation();
slider.repaint();
HiDPIUtils.repaint( slider );
}
}
protected void setThumbHover( boolean hover ) {
if( hover != thumbHover ) {
thumbHover = hover;
slider.repaint( thumbRect );
HiDPIUtils.repaint( slider, thumbRect );
}
}
protected void setThumbPressed( boolean pressed ) {
if( pressed != thumbPressed ) {
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.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
/**
@@ -586,7 +587,7 @@ public class FlatSpinnerUI
@Override
public void focusGained( FocusEvent e ) {
// necessary to update focus border
spinner.repaint();
HiDPIUtils.repaint( spinner );
// if spinner gained focus, transfer it to the editor text field
if( e.getComponent() == spinner ) {
@@ -599,7 +600,7 @@ public class FlatSpinnerUI
@Override
public void focusLost( FocusEvent e ) {
// necessary to update focus border
spinner.repaint();
HiDPIUtils.repaint( spinner );
}
//---- interface PropertyChangeListener ----
@@ -614,7 +615,7 @@ public class FlatSpinnerUI
case FlatClientProperties.COMPONENT_ROUND_RECT:
case FlatClientProperties.OUTLINE:
spinner.repaint();
HiDPIUtils.repaint( spinner );
break;
case FlatClientProperties.MINIMUM_WIDTH:
@@ -625,7 +626,7 @@ public class FlatSpinnerUI
case FlatClientProperties.STYLE_CLASS:
installStyle();
spinner.revalidate();
spinner.repaint();
HiDPIUtils.repaint( spinner );
break;
}
}

View File

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

View File

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

View File

@@ -65,8 +65,28 @@ public class FlatTableCellBorder
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
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;
super.paintBorder( c, g, x, y, width, height );
this.c = null;

View File

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

View File

@@ -17,30 +17,56 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.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.FocusListener;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Map;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.JComponent;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.JViewport;
import javax.swing.ListSelectionModel;
import javax.swing.LookAndFeel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
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.UIResource;
import javax.swing.plaf.basic.BasicTableUI;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumnModel;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.icons.FlatCheckBoxIcon;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.Graphics2DProxy;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale;
@@ -80,7 +106,10 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault Table.intercellSpacing Dimension
* @uiDefault Table.selectionInactiveBackground Color
* @uiDefault Table.selectionInactiveForeground Color
* @uiDefault Table.selectionInsets Insets
* @uiDefault Table.selectionArc int
* @uiDefault Table.paintOutsideAlternateRows boolean
* @uiDefault Table.editorSelectAllOnStartEditing boolean
*
* <!-- FlatTableCellBorder -->
*
@@ -107,6 +136,8 @@ public class FlatTableUI
@Styleable protected Color selectionForeground;
@Styleable protected Color selectionInactiveBackground;
@Styleable protected Color selectionInactiveForeground;
/** @since 3.5 */ @Styleable protected Insets selectionInsets;
/** @since 3.5 */ @Styleable protected int selectionArc;
// for FlatTableCellBorder
/** @since 2 */ @Styleable protected Insets cellMargins;
@@ -116,8 +147,12 @@ public class FlatTableUI
private boolean oldShowHorizontalLines;
private boolean oldShowVerticalLines;
private Dimension oldIntercellSpacing;
private TableCellRenderer oldBooleanRenderer;
private PropertyChangeListener propertyChangeListener;
private ComponentListener outsideAlternateRowsListener;
private ListSelectionListener rowSelectionListener;
private TableColumnModelListener columnSelectionListener;
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
@@ -144,6 +179,8 @@ public class FlatTableUI
selectionForeground = UIManager.getColor( "Table.selectionForeground" );
selectionInactiveBackground = UIManager.getColor( "Table.selectionInactiveBackground" );
selectionInactiveForeground = UIManager.getColor( "Table.selectionInactiveForeground" );
selectionInsets = UIManager.getInsets( "Table.selectionInsets" );
selectionArc = UIManager.getInt( "Table.selectionArc" );
toggleSelectionColors();
@@ -151,19 +188,35 @@ public class FlatTableUI
if( rowHeight > 0 )
LookAndFeel.installProperty( table, "rowHeight", UIScale.scale( rowHeight ) );
if( !showHorizontalLines ) {
FlatTablePropertyWatcher watcher = FlatTablePropertyWatcher.get( table );
if( watcher != null )
watcher.enabled = false;
if( !showHorizontalLines && (watcher == null || !watcher.showHorizontalLinesChanged) ) {
oldShowHorizontalLines = table.getShowHorizontalLines();
table.setShowHorizontalLines( false );
}
if( !showVerticalLines ) {
if( !showVerticalLines && (watcher == null || !watcher.showVerticalLinesChanged) ) {
oldShowVerticalLines = table.getShowVerticalLines();
table.setShowVerticalLines( false );
}
if( intercellSpacing != null ) {
if( intercellSpacing != null && (watcher == null || !watcher.intercellSpacingChanged) ) {
oldIntercellSpacing = table.getIntercellSpacing();
table.setIntercellSpacing( intercellSpacing );
}
if( watcher != null )
watcher.enabled = true;
else
table.addPropertyChangeListener( new FlatTablePropertyWatcher() );
// install boolean renderer
oldBooleanRenderer = table.getDefaultRenderer( Boolean.class );
if( oldBooleanRenderer instanceof UIResource )
table.setDefaultRenderer( Boolean.class, new FlatBooleanRenderer() );
else
oldBooleanRenderer = null;
}
@Override
@@ -177,15 +230,36 @@ public class FlatTableUI
oldStyleValues = null;
FlatTablePropertyWatcher watcher = FlatTablePropertyWatcher.get( table );
if( watcher != null )
watcher.enabled = false;
// restore old show horizontal/vertical lines (if not modified)
if( !showHorizontalLines && oldShowHorizontalLines && !table.getShowHorizontalLines() )
table.setShowHorizontalLines( true );
if( !showVerticalLines && oldShowVerticalLines && !table.getShowVerticalLines() )
table.setShowVerticalLines( true );
if( !showHorizontalLines && oldShowHorizontalLines && !table.getShowHorizontalLines() &&
(watcher == null || !watcher.showHorizontalLinesChanged) )
table.setShowHorizontalLines( true );
if( !showVerticalLines && oldShowVerticalLines && !table.getShowVerticalLines() &&
(watcher == null || !watcher.showVerticalLinesChanged) )
table.setShowVerticalLines( true );
// restore old intercell spacing (if not modified)
if( intercellSpacing != null && table.getIntercellSpacing().equals( intercellSpacing ) )
table.setIntercellSpacing( oldIntercellSpacing );
if( intercellSpacing != null && table.getIntercellSpacing().equals( intercellSpacing ) &&
(watcher == null || !watcher.intercellSpacingChanged) )
table.setIntercellSpacing( oldIntercellSpacing );
if( watcher != null )
watcher.enabled = true;
// uninstall boolean renderer
if( table.getDefaultRenderer( Boolean.class ) instanceof FlatBooleanRenderer ) {
if( oldBooleanRenderer instanceof Component ) {
// because the old renderer component was not attached to any component hierarchy,
// its UI was not yet updated, and it is necessary to do it here
SwingUtilities.updateComponentTreeUI( (Component) oldBooleanRenderer );
}
table.setDefaultRenderer( Boolean.class, oldBooleanRenderer );
}
oldBooleanRenderer = null;
}
@Override
@@ -194,6 +268,28 @@ public class FlatTableUI
propertyChangeListener = e -> {
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:
toggleSelectionColors();
break;
@@ -202,11 +298,14 @@ public class FlatTableUI
case FlatClientProperties.STYLE_CLASS:
installStyle();
table.revalidate();
table.repaint();
HiDPIUtils.repaint( table );
break;
}
};
table.addPropertyChangeListener( propertyChangeListener );
if( selectionArc > 0 )
installRepaintRoundedSelectionListeners();
}
@Override
@@ -215,6 +314,19 @@ public class FlatTableUI
table.removePropertyChangeListener( propertyChangeListener );
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
@@ -238,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 */
protected void installStyle() {
try {
@@ -279,6 +403,8 @@ public class FlatTableUI
protected Object applyStyleProperty( String key, Object value ) {
if( "rowHeight".equals( key ) && value instanceof Integer )
value = UIScale.scale( (Integer) value );
else if( "selectionArc".equals( key ) && value instanceof Integer && (Integer) value > 0 )
installRepaintRoundedSelectionListeners();
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, table, key, value );
}
@@ -341,6 +467,7 @@ public class FlatTableUI
double systemScaleFactor = UIScale.getSystemScaleFactor( (Graphics2D) g );
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 9+ uses fillRect() to paint grid lines (except for dragged column)
@@ -383,11 +510,11 @@ public class FlatTableUI
// reduce line thickness to avoid unstable painted line thickness
if( lineThickness != 1 ) {
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;
}
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;
}
}
@@ -405,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 );
}
@@ -450,8 +581,6 @@ public class FlatTableUI
boolean paintOutside = UIManager.getBoolean( "Table.paintOutsideAlternateRows" );
Color alternateColor;
if( paintOutside && (alternateColor = UIManager.getColor( "Table.alternateRowColor" )) != null ) {
g.setColor( alternateColor );
int rowCount = table.getRowCount();
// paint alternating empty rows below the table
@@ -460,11 +589,449 @@ public class FlatTableUI
int tableWidth = table.getWidth();
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++ ) {
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 );
}
}
}
//---- class FlatTablePropertyWatcher -------------------------------------
/**
* Listener that watches for change of some table properties from application code.
* This information is used in {@link FlatTableUI#installDefaults()} and
* {@link FlatTableUI#uninstallDefaults()} to decide whether FlatLaf modifies those properties.
* If they are modified in application code, FlatLaf no longer changes them.
*
* The listener is added once for each table, but never removed.
* So switching Laf/theme reuses existing listener.
*/
private static class FlatTablePropertyWatcher
implements PropertyChangeListener
{
boolean enabled = true;
boolean showHorizontalLinesChanged;
boolean showVerticalLinesChanged;
boolean intercellSpacingChanged;
static FlatTablePropertyWatcher get( JTable table ) {
for( PropertyChangeListener l : table.getPropertyChangeListeners() ) {
if( l instanceof FlatTablePropertyWatcher )
return (FlatTablePropertyWatcher) l;
}
return null;
}
//---- interface PropertyChangeListener ----
@Override
public void propertyChange( PropertyChangeEvent e ) {
if( !enabled )
return;
switch( e.getPropertyName() ) {
case "showHorizontalLines": showHorizontalLinesChanged = true; break;
case "showVerticalLines": showVerticalLinesChanged = true; break;
case "rowMargin": intercellSpacingChanged = true; break;
}
}
}
//---- class FlatBooleanRenderer ------------------------------------------
private static class FlatBooleanRenderer
extends DefaultTableCellRenderer
implements UIResource
{
private boolean selected;
FlatBooleanRenderer() {
setHorizontalAlignment( SwingConstants.CENTER );
setIcon( new FlatCheckBoxIcon() {
@Override
protected boolean isSelected( Component c ) {
return selected;
}
} );
}
@Override
protected void setValue( Object value ) {
selected = (value != null && (Boolean) value);
}
}
//---- class StartEditingAction -------------------------------------------
private static class StartEditingAction
extends FlatUIAction
{
static void install( ActionMap map, String key ) {
Action oldAction = map.get( key );
if( oldAction == null || oldAction instanceof StartEditingAction )
return; // not found or already installed
map.put( key, new StartEditingAction( oldAction ) );
}
private StartEditingAction( Action delegate ) {
super( delegate );
}
@Override
public void actionPerformed( ActionEvent e ) {
JTable table = (JTable) e.getSource();
Component oldEditorComp = table.getEditorComponent();
delegate.actionPerformed( e );
// select all text in editor if editing starts with F2 key
Component editorComp = table.getEditorComponent();
if( oldEditorComp == null && editorComp instanceof JTextField )
((JTextField)editorComp).selectAll();
}
}
}

View File

@@ -45,6 +45,7 @@ import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.LookAndFeel;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
@@ -238,7 +239,7 @@ public class FlatTextFieldUI
case COMPONENT_ROUND_RECT:
case OUTLINE:
case TEXT_FIELD_PADDING:
c.repaint();
HiDPIUtils.repaint( c );
break;
case MINIMUM_WIDTH:
@@ -249,38 +250,38 @@ public class FlatTextFieldUI
case STYLE_CLASS:
installStyle();
c.revalidate();
c.repaint();
HiDPIUtils.repaint( c );
break;
case TEXT_FIELD_LEADING_ICON:
leadingIcon = (e.getNewValue() instanceof Icon) ? (Icon) e.getNewValue() : null;
c.repaint();
HiDPIUtils.repaint( c );
break;
case TEXT_FIELD_TRAILING_ICON:
trailingIcon = (e.getNewValue() instanceof Icon) ? (Icon) e.getNewValue() : null;
c.repaint();
HiDPIUtils.repaint( c );
break;
case TEXT_FIELD_LEADING_COMPONENT:
uninstallLeadingComponent();
installLeadingComponent();
c.revalidate();
c.repaint();
HiDPIUtils.repaint( c );
break;
case TEXT_FIELD_TRAILING_COMPONENT:
uninstallTrailingComponent();
installTrailingComponent();
c.revalidate();
c.repaint();
HiDPIUtils.repaint( c );
break;
case TEXT_FIELD_SHOW_CLEAR_BUTTON:
uninstallClearButton();
installClearButton();
c.revalidate();
c.repaint();
HiDPIUtils.repaint( c );
break;
case "enabled":
@@ -480,10 +481,22 @@ debug*/
// compute placeholder location
Rectangle r = getVisibleEditorRect();
FontMetrics fm = c.getFontMetrics( c.getFont() );
String clippedPlaceholder = JavaCompatibility.getClippedString( c, fm, placeholder, r.width );
int x = r.x + (isLeftToRight() ? 0 : r.width - fm.stringWidth( clippedPlaceholder ));
int x = r.x;
int y = r.y + fm.getAscent() + ((r.height - fm.getHeight()) / 2);
// apply horizontal alignment to x location
String clippedPlaceholder = JavaCompatibility.getClippedString( c, fm, placeholder, r.width );
int stringWidth = fm.stringWidth( clippedPlaceholder );
int halign = (c instanceof JTextField) ? ((JTextField)c).getHorizontalAlignment() : SwingConstants.LEADING;
if( halign == SwingConstants.LEADING )
halign = isLeftToRight() ? SwingConstants.LEFT : SwingConstants.RIGHT;
else if( halign == SwingConstants.TRAILING )
halign = isLeftToRight() ? SwingConstants.RIGHT : SwingConstants.LEFT;
if( halign == SwingConstants.RIGHT )
x += r.width - stringWidth;
else if( halign == SwingConstants.CENTER )
x = Math.max( 0, x + (r.width / 2) - (stringWidth / 2) );
// paint placeholder
g.setColor( placeholderForeground );
FlatUIUtils.drawString( c, g, clippedPlaceholder, x, y );
@@ -802,7 +815,7 @@ debug*/
if( visible != clearButton.isVisible() ) {
clearButton.setVisible( visible );
c.revalidate();
c.repaint();
HiDPIUtils.repaint( c );
}
}

View File

@@ -36,6 +36,7 @@ import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
@@ -46,9 +47,9 @@ import java.awt.event.WindowEvent;
import java.awt.geom.AffineTransform;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import javax.accessibility.AccessibleContext;
import javax.swing.BorderFactory;
import javax.swing.Box;
@@ -57,7 +58,6 @@ import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JMenuBar;
import javax.swing.JPanel;
@@ -66,6 +66,7 @@ import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.AbstractBorder;
import javax.swing.border.Border;
import javax.swing.plaf.ComponentUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.ui.FlatNativeWindowBorder.WindowTopBorder;
@@ -98,7 +99,6 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault TitlePane.showIconBesideTitle boolean
* @uiDefault TitlePane.menuBarTitleGap int
* @uiDefault TitlePane.menuBarTitleMinimumGap int
* @uiDefault TitlePane.menuBarResizeHeight int
* @uiDefault TitlePane.closeIcon Icon
* @uiDefault TitlePane.iconifyIcon Icon
* @uiDefault TitlePane.maximizeIcon Icon
@@ -109,7 +109,8 @@ import com.formdev.flatlaf.util.UIScale;
public class FlatTitlePane
extends JComponent
{
private static final String KEY_DEBUG_SHOW_RECTANGLES = "FlatLaf.debug.titlebar.showRectangles";
static final String KEY_DEBUG_SHOW_RECTANGLES = "FlatLaf.debug.titlebar.showRectangles";
private static final boolean isWindows_10 = SystemInfo.isWindows_10_orLater && !SystemInfo.isWindows_11_orLater;
/** @since 2.5 */ protected final Font titleFont;
protected final Color activeBackground;
@@ -131,7 +132,6 @@ public class FlatTitlePane
/** @since 2.4 */ protected final boolean showIconBesideTitle;
protected final int menuBarTitleGap;
/** @since 2.4 */ protected final int menuBarTitleMinimumGap;
/** @since 2.4 */ protected final int menuBarResizeHeight;
protected final JRootPane rootPane;
protected final String windowStyle;
@@ -150,6 +150,33 @@ public class FlatTitlePane
private final Handler handler;
/**
* This panel handles mouse events if FlatLaf window decorations are used
* without native window border. E.g. on Linux.
* <p>
* This panel usually has same bounds as the title pane,
* except if fullWindowContent mode is enabled.
* <p>
* This panel is not a child of the title pane.
* Instead it is added by FlatRootPaneUI to the layered pane at a layer
* under the title pane and under the frame content.
* The separation is necessary for fullWindowContent mode, where the title pane
* is layered over the frame content (for title pane buttons), but the mousePanel
* needs to be layered under the frame content so that components on content pane
* can receive mouse events when located in title area.
*/
final JPanel mouseLayer;
/**
* 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 ) {
this.rootPane = rootPane;
@@ -178,7 +205,6 @@ public class FlatTitlePane
showIconBesideTitle = FlatUIUtils.getSubUIBoolean( "TitlePane.showIconBesideTitle", windowStyle, false );
menuBarTitleGap = FlatUIUtils.getSubUIInt( "TitlePane.menuBarTitleGap", windowStyle, 40 );
menuBarTitleMinimumGap = FlatUIUtils.getSubUIInt( "TitlePane.menuBarTitleMinimumGap", windowStyle, 12 );
menuBarResizeHeight = FlatUIUtils.getSubUIInt( "TitlePane.menuBarResizeHeight", windowStyle, 4 );
handler = createHandler();
@@ -187,11 +213,18 @@ public class FlatTitlePane
addSubComponents();
activeChanged( true );
addMouseListener( handler );
addMouseMotionListener( handler );
mouseLayer = new JPanel();
mouseLayer.setOpaque( false );
mouseLayer.addMouseListener( handler );
mouseLayer.addMouseMotionListener( handler );
// necessary for closing window with double-click on icon
iconLabel.addMouseListener( handler );
if( isWindows_10 && FlatNativeWindowBorder.isSupported() ) {
windowTopBorderLayer = new JPanel();
windowTopBorderLayer.setVisible( false );
windowTopBorderLayer.setOpaque( false );
windowTopBorderLayer.setBorder( FlatUIUtils.nonUIResource( WindowTopBorder.getInstance() ) );
} else
windowTopBorderLayer = null;
applyComponentOrientation( rootPane.getComponentOrientation() );
}
@@ -234,6 +267,11 @@ public class FlatTitlePane
setLayout( new BorderLayout() {
@Override
public void layoutContainer( Container target ) {
if( isFullWindowContent() ) {
super.layoutContainer( target );
return;
}
// compute available bounds
Insets insets = target.getInsets();
int x = insets.left;
@@ -247,7 +285,7 @@ public class FlatTitlePane
int titleWidth = w - leftWidth - buttonsWidth;
int minTitleWidth = UIScale.scale( titleMinimumWidth );
// increase minimum width if icon is show besides the title
// increase minimum width if icon is shown besides the title
Icon icon = titleLabel.getIcon();
if( icon != null ) {
Insets iconInsets = iconLabel.getInsets();
@@ -295,6 +333,9 @@ public class FlatTitlePane
horizontalGlue.getWidth(), titleLabel.getHeight() );
}
}
// clear hit-test cache
lastCaptionHitTestTime = 0;
}
} );
@@ -318,6 +359,10 @@ public class FlatTitlePane
@Override
public Dimension getPreferredSize() {
Dimension size = super.getPreferredSize();
int titleBarHeight = clientPropertyInt( rootPane, TITLE_BAR_HEIGHT, -1 );
if( titleBarHeight >= 0 )
return new Dimension( size.width, UIScale.scale( titleBarHeight ) );
if( buttonMaximizedHeight > 0 && isWindowMaximized() && !hasVisibleEmbeddedMenuBar( rootPane.getJMenuBar() ) ) {
// make title pane height smaller when frame is maximized
size = new Dimension( size.width, Math.min( size.height, UIScale.scale( buttonMaximizedHeight ) ) );
@@ -338,6 +383,13 @@ public class FlatTitlePane
buttonPanel.add( restoreButton );
}
buttonPanel.add( closeButton );
ComponentListener l = new ComponentAdapter() {
@Override public void componentResized( ComponentEvent e ) { updateFullWindowContentButtonsBoundsProperty(); }
@Override public void componentMoved( ComponentEvent e ) { updateFullWindowContentButtonsBoundsProperty(); }
};
buttonPanel.addComponentListener( l );
addComponentListener( l );
}
protected JButton createButton( String iconKey, String accessibleName, ActionListener action ) {
@@ -347,6 +399,12 @@ public class FlatTitlePane
// allow the button to shrink if space is rare
return new Dimension( UIScale.scale( buttonMinimumWidth ), super.getMinimumSize().height );
}
@Override
public Dimension getMaximumSize() {
// allow the button to fill whole button area height
// see also BasicMenuUI.getMaximumSize()
return new Dimension( super.getMaximumSize().width, Short.MAX_VALUE );
}
};
button.setFocusable( false );
button.setContentAreaFilled( false );
@@ -417,7 +475,9 @@ public class FlatTitlePane
/** @since 3 */
protected void updateVisibility() {
titleLabel.setVisible( clientPropertyBoolean( rootPane, TITLE_BAR_SHOW_TITLE, true ) );
boolean isFullWindowContent = isFullWindowContent();
leftPanel.setVisible( !isFullWindowContent );
titleLabel.setVisible( clientPropertyBoolean( rootPane, TITLE_BAR_SHOW_TITLE, true ) && !isFullWindowContent );
closeButton.setVisible( clientPropertyBoolean( rootPane, TITLE_BAR_SHOW_CLOSE, true ) );
if( window instanceof Frame ) {
@@ -443,7 +503,7 @@ public class FlatTitlePane
// get window images
List<Image> images = null;
if( clientPropertyBoolean( rootPane, TITLE_BAR_SHOW_ICON, defaultShowIcon ) ) {
if( clientPropertyBoolean( rootPane, TITLE_BAR_SHOW_ICON, defaultShowIcon ) && !isFullWindowContent() ) {
images = window.getIconImages();
if( images.isEmpty() ) {
// search in owners
@@ -468,6 +528,13 @@ public class FlatTitlePane
updateNativeTitleBarHeightAndHitTestSpotsLater();
}
void updateFullWindowContentButtonsBoundsProperty() {
Rectangle bounds = isFullWindowContent()
? new Rectangle( SwingUtilities.convertPoint( buttonPanel, 0, 0, rootPane ), buttonPanel.getSize() )
: null;
rootPane.putClientProperty( FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS, bounds );
}
@Override
public void addNotify() {
super.addNotify();
@@ -522,6 +589,11 @@ public class FlatTitlePane
window.removeComponentListener( handler );
}
/** @since 3.4 */
protected boolean isFullWindowContent() {
return FlatRootPaneUI.isFullWindowContent( rootPane );
}
/**
* Returns whether this title pane currently has a visible and embedded menubar.
*/
@@ -533,6 +605,9 @@ public class FlatTitlePane
* Returns whether the menubar should be embedded into the title pane.
*/
protected boolean isMenuBarEmbedded() {
if( isFullWindowContent() )
return false;
// not storing value of "TitlePane.menuBarEmbedded" in class to allow changing at runtime
return FlatUIUtils.getBoolean( rootPane,
FlatSystemProperties.MENUBAR_EMBEDDED,
@@ -608,6 +683,10 @@ public class FlatTitlePane
doLayout();
}
void menuBarInvalidate() {
menuBarPlaceholder.invalidate();
}
@Override
public void paint( Graphics g ) {
super.paint( g );
@@ -616,21 +695,45 @@ public class FlatTitlePane
return;
if( debugTitleBarHeight > 0 ) {
// title bar height is measured from window top edge
int y = SwingUtilities.convertPoint( window, 0, debugTitleBarHeight, this ).y;
g.setColor( Color.green );
g.drawLine( 0, debugTitleBarHeight, getWidth(), debugTitleBarHeight );
g.drawLine( 0, y, getWidth(), y );
}
if( debugHitTestSpots != null ) {
for( Rectangle r : debugHitTestSpots )
paintRect( g, Color.red, r );
}
paintRect( g, Color.cyan, debugCloseButtonBounds );
paintRect( g, Color.blue, debugAppIconBounds );
paintRect( g, Color.blue, debugMinimizeButtonBounds );
paintRect( g, Color.magenta, debugMaximizeButtonBounds );
paintRect( g, Color.cyan, debugCloseButtonBounds );
g.setColor( Color.red );
debugPaintComponentWithMouseListener( g, Color.red, rootPane.getLayeredPane(), 0, 0 );
debugPaintRect( g, Color.blue, debugAppIconBounds );
debugPaintRect( g, Color.blue, debugMinimizeButtonBounds );
debugPaintRect( g, Color.magenta, debugMaximizeButtonBounds );
debugPaintRect( g, Color.cyan, debugCloseButtonBounds );
}
private void paintRect( Graphics g, Color color, Rectangle r ) {
private void debugPaintComponentWithMouseListener( Graphics g, Color color, Component c, int x, int y ) {
if( !c.isDisplayable() || !c.isVisible() || c == mouseLayer ||
c == iconifyButton || c == maximizeButton || c == restoreButton || c == closeButton )
return;
if( c.getMouseListeners().length > 0 ||
c.getMouseMotionListeners().length > 0 ||
c.getMouseWheelListeners().length > 0 )
{
g.drawRect( x, y, c.getWidth(), c.getHeight() );
return;
}
if( c instanceof Container ) {
Rectangle titlePaneBoundsOnWindow = SwingUtilities.convertRectangle( this, new Rectangle( getSize() ), window );
for( Component child : ((Container)c).getComponents() ) {
Rectangle compBoundsOnWindow = SwingUtilities.convertRectangle( c, new Rectangle( c.getSize() ), window );
if( compBoundsOnWindow.intersects( titlePaneBoundsOnWindow ) )
debugPaintComponentWithMouseListener( g, color, child, x + child.getX(), y + child.getY() );
}
}
}
private void debugPaintRect( Graphics g, Color color, Rectangle r ) {
if( r == null )
return;
@@ -641,6 +744,9 @@ public class FlatTitlePane
@Override
protected void paintComponent( Graphics g ) {
if( isFullWindowContent() )
return;
// not storing value of "TitlePane.unifiedBackground" in class to allow changing at runtime
g.setColor( (UIManager.getBoolean( "TitlePane.unifiedBackground" ) &&
clientPropertyColor( rootPane, TITLE_BAR_BACKGROUND, null ) == null)
@@ -649,16 +755,6 @@ public class FlatTitlePane
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.
*/
@@ -728,7 +824,8 @@ public class FlatTitlePane
Rectangle oldMaximizedBounds = frame.getMaximizedBounds();
if( !hasNativeCustomDecoration() &&
(oldMaximizedBounds == null ||
Objects.equals( oldMaximizedBounds, rootPane.getClientProperty( "_flatlaf.maximizedBounds" ) )) )
Objects.equals( oldMaximizedBounds, rootPane.getClientProperty( "_flatlaf.maximizedBounds" ) )) &&
window.getGraphicsConfiguration() != null )
{
GraphicsConfiguration gc = window.getGraphicsConfiguration();
@@ -835,10 +932,6 @@ public class FlatTitlePane
window.dispatchEvent( new WindowEvent( window, WindowEvent.WINDOW_CLOSING ) );
}
private boolean hasJBRCustomDecoration() {
return window != null && JBRCustomDecorations.hasCustomDecoration( window );
}
/**
* Returns whether windows uses native window border and has custom decorations enabled.
*/
@@ -846,6 +939,10 @@ public class FlatTitlePane
return window != null && FlatNativeWindowBorder.hasCustomDecoration( window );
}
boolean isWindowTopBorderNeeded() {
return isWindows_10 && hasNativeCustomDecoration();
}
// used to invoke updateNativeTitleBarHeightAndHitTestSpots() only once from latest invokeLater()
private int laterCounter;
@@ -866,11 +963,14 @@ public class FlatTitlePane
return;
int titleBarHeight = getHeight();
// title bar height must be measured from window top edge
// (when window is maximized, window y location is e.g. -11 and window top inset is 11)
for( Component c = this; c != window && c != null; c = c.getParent() )
titleBarHeight += c.getY();
// slightly reduce height so that component receives mouseExit events
if( titleBarHeight > 0 )
titleBarHeight--;
List<Rectangle> hitTestSpots = new ArrayList<>();
Rectangle appIconBounds = null;
if( !showIconBesideTitle && iconLabel.isVisible() ) {
@@ -896,10 +996,7 @@ public class FlatTitlePane
iconBounds.width += iconInsets.right;
}
if( hasJBRCustomDecoration() )
hitTestSpots.add( iconBounds );
else
appIconBounds = iconBounds;
appIconBounds = iconBounds;
} else if( showIconBesideTitle && titleLabel.getIcon() != null && titleLabel.getUI() instanceof FlatTitleLabelUI ) {
FlatTitleLabelUI ui = (FlatTitleLabelUI) titleLabel.getUI();
@@ -927,78 +1024,21 @@ public class FlatTitlePane
iconR.width += 2;
iconR.height += 2;
if( hasJBRCustomDecoration() )
hitTestSpots.add( iconR );
else
appIconBounds = iconR;
appIconBounds = iconR;
}
}
Rectangle r = getNativeHitTestSpot( buttonPanel );
if( r != null )
hitTestSpots.add( r );
JMenuBar menuBar = rootPane.getJMenuBar();
if( hasVisibleEmbeddedMenuBar( menuBar ) ) {
r = getNativeHitTestSpot( menuBar );
if( r != null ) {
// if frame is resizable and not maximized, make menu bar hit test spot smaller at top
// to have a small area above the menu bar to resize the window
if( window instanceof Frame && ((Frame)window).isResizable() && !isWindowMaximized() ) {
// limit to 8, because Windows does not use a larger height
int resizeHeight = UIScale.scale( Math.min( menuBarResizeHeight, 8 ) );
r.y += resizeHeight;
r.height -= resizeHeight;
}
int count = menuBar.getComponentCount();
for( int i = count - 1; i >= 0; i-- ) {
Component c = menuBar.getComponent( i );
if( c instanceof Box.Filler ||
(c instanceof JComponent && clientPropertyBoolean( (JComponent) c, COMPONENT_TITLE_BAR_CAPTION, false ) ) )
{
// If menu bar is embedded and contains a horizontal glue or caption component,
// then split the hit test spot so that
// the glue/caption component area can be used to move the window.
Point glueLocation = SwingUtilities.convertPoint( c, 0, 0, window );
int x2 = glueLocation.x + c.getWidth();
Rectangle r2;
if( getComponentOrientation().isLeftToRight() ) {
r2 = new Rectangle( x2, r.y, (r.x + r.width) - x2, r.height );
r.width = glueLocation.x - r.x;
} else {
r2 = new Rectangle( r.x, r.y, glueLocation.x - r.x, r.height );
r.width = (r.x + r.width) - x2;
r.x = x2;
}
if( r2.width > 0 )
hitTestSpots.add( r2 );
}
}
hitTestSpots.add( r );
}
}
// allow internal frames in layered pane to be moved/resized when placed over title bar
for( Component c : rootPane.getLayeredPane().getComponents() ) {
r = (c instanceof JInternalFrame) ? getNativeHitTestSpot( (JInternalFrame) c ) : null;
if( r != null )
hitTestSpots.add( r );
}
Rectangle minimizeButtonBounds = boundsInWindow( iconifyButton );
Rectangle maximizeButtonBounds = boundsInWindow( maximizeButton.isVisible() ? maximizeButton : restoreButton );
Rectangle closeButtonBounds = boundsInWindow( closeButton );
// clear hit-test cache
lastCaptionHitTestTime = 0;
FlatNativeWindowBorder.setTitleBarHeightAndHitTestSpots( window, titleBarHeight,
hitTestSpots, appIconBounds, minimizeButtonBounds, maximizeButtonBounds, closeButtonBounds );
this::captionHitTest, appIconBounds, minimizeButtonBounds, maximizeButtonBounds, closeButtonBounds );
debugTitleBarHeight = titleBarHeight;
debugHitTestSpots = hitTestSpots;
debugAppIconBounds = appIconBounds;
debugMinimizeButtonBounds = minimizeButtonBounds;
debugMaximizeButtonBounds = maximizeButtonBounds;
@@ -1013,18 +1053,122 @@ public class FlatTitlePane
: null;
}
protected Rectangle getNativeHitTestSpot( JComponent c ) {
Dimension size = c.getSize();
if( size.width <= 0 || size.height <= 0 )
return null;
/**
* Returns whether there is a component at the given location, that processes
* mouse events. E.g. buttons, menus, etc.
* <p>
* Note:
* <ul>
* <li>This method is invoked often when mouse is moved over window title bar area
* and should therefore return quickly.
* <li>This method 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>
*/
private boolean captionHitTest( Point pt ) {
// Windows invokes this method every ~200ms, even if the mouse has not moved
long time = System.currentTimeMillis();
if( pt.x == lastCaptionHitTestX && pt.y == lastCaptionHitTestY && time < lastCaptionHitTestTime + 300 ) {
lastCaptionHitTestTime = time;
return lastCaptionHitTestResult;
}
Point location = SwingUtilities.convertPoint( c, 0, 0, window );
Rectangle r = new Rectangle( location, size );
return r;
// convert pt from window coordinates to layeredPane coordinates
Component layeredPane = rootPane.getLayeredPane();
int x = pt.x;
int y = pt.y;
for( Component c = layeredPane; c != window && c != null; c = c.getParent() ) {
x -= c.getX();
y -= c.getY();
}
lastCaptionHitTestX = pt.x;
lastCaptionHitTestY = pt.y;
lastCaptionHitTestTime = time;
lastCaptionHitTestResult = isTitleBarCaptionAt( layeredPane, x, y );
return lastCaptionHitTestResult;
}
private boolean isTitleBarCaptionAt( Component c, int x, int y ) {
if( !c.isDisplayable() || !c.isVisible() || !contains( c, 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() ) {
// revalidate if necessary so that it is valid when invoked again later
EventQueue.invokeLater( () -> {
Window w = SwingUtilities.windowForComponent( c );
if( w != null )
w.revalidate();
else
c.revalidate();
} );
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;
}
/**
* Same as {@link Component#contains(int, int)}, but not using that method
* because it may be overridden by custom components and invoke code that
* tries to request AWT tree lock on 'AWT-Windows' thread.
* This could freeze the application if AWT tree is already locked on 'AWT-EventQueue' thread.
*/
private boolean contains( Component c, int x, int y ) {
return x >= 0 && y >= 0 && x < c.getWidth() && y < c.getHeight();
}
private int lastCaptionHitTestX;
private int lastCaptionHitTestY;
private long lastCaptionHitTestTime;
private boolean lastCaptionHitTestResult;
private int debugTitleBarHeight;
private List<Rectangle> debugHitTestSpots;
private Rectangle debugAppIconBounds;
private Rectangle debugMinimizeButtonBounds;
private Rectangle debugMaximizeButtonBounds;
@@ -1047,7 +1191,7 @@ public class FlatTitlePane
} else if( borderColor != null && (rootPane.getJMenuBar() == null || !rootPane.getJMenuBar().isVisible()) )
insets.bottom += UIScale.scale( 1 );
if( !SystemInfo.isWindows_11_orLater && hasNativeCustomDecoration() && !isWindowMaximized() )
if( isWindowTopBorderNeeded() && !isWindowMaximized() )
insets = FlatUIUtils.addInsets( insets, WindowTopBorder.getInstance().getBorderInsets() );
return insets;
@@ -1066,7 +1210,7 @@ public class FlatTitlePane
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 );
}
@@ -1122,7 +1266,7 @@ public class FlatTitlePane
}
}
// compute icon width and gap (if icon is show besides the title)
// compute icon width and gap (if icon is shown besides the title)
int iconTextGap = 0;
int iconWidthAndGap = 0;
if( icon != null ) {
@@ -1131,7 +1275,7 @@ public class FlatTitlePane
iconWidthAndGap = icon.getIconWidth() + iconTextGap;
}
// layout title and icon (if show besides the title)
// layout title and icon (if shown besides the title)
String clippedText = SwingUtilities.layoutCompoundLabel( label, fontMetrics, text, icon,
label.getVerticalAlignment(), label.getHorizontalAlignment(),
label.getVerticalTextPosition(), label.getHorizontalTextPosition(),
@@ -1230,10 +1374,7 @@ public class FlatTitlePane
activeChanged( true );
updateNativeTitleBarHeightAndHitTestSpots();
if( !SystemInfo.isWindows_11_orLater && hasNativeCustomDecoration() )
WindowTopBorder.getInstance().repaintBorder( FlatTitlePane.this );
repaintWindowBorder();
repaintBorder();
}
@Override
@@ -1241,10 +1382,22 @@ public class FlatTitlePane
activeChanged( false );
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 );
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
@@ -1270,7 +1423,7 @@ debug*/
public void mouseClicked( MouseEvent e ) {
// on Linux, when using native library, the mouse clicked event
// is usually not sent and maximize/restore is done in mouse pressed event
// this check is here for the case that a mouse clicked event comes thru for some reason
// this check is here for the case that a mouse clicked event comes through for some reason
if( linuxNativeMove && SystemInfo.isLinux && FlatNativeLinuxLibrary.isWMUtilsSupported( window ) ) {
// see comment in mousePressed()
if( lastSingleClickWhen != 0 && (e.getWhen() - lastSingleClickWhen) <= getMultiClickInterval() ) {
@@ -1281,7 +1434,7 @@ debug*/
}
if( e.getClickCount() == 2 && SwingUtilities.isLeftMouseButton( e ) ) {
if( e.getSource() == iconLabel ) {
if( SwingUtilities.getDeepestComponentAt( FlatTitlePane.this, e.getX(), e.getY() ) == iconLabel ) {
// double-click on icon closes window
close();
} else if( !hasNativeCustomDecoration() ) {
@@ -1308,7 +1461,7 @@ debug*/
if( !SwingUtilities.isLeftMouseButton( e ) )
return;
dragOffset = SwingUtilities.convertPoint( FlatTitlePane.this, e.getPoint(), window );
dragOffset = SwingUtilities.convertPoint( mouseLayer, e.getPoint(), window );
linuxNativeMove = false;
// on Linux, move or maximize/restore window
@@ -1421,4 +1574,36 @@ debug*/
@Override public void componentMoved( ComponentEvent e ) {}
@Override public void componentHidden( ComponentEvent e ) {}
}
//---- interface TitleBarCaptionHitTest -----------------------------------
/**
* For custom components use {@link FlatClientProperties#COMPONENT_TITLE_BAR_CAPTION}
* instead of this interface.
*
* @since 3.4
*/
public interface TitleBarCaptionHitTest {
/**
* Invoked for a component that is enabled and has mouse listeners,
* to check whether it processes mouse input at the given x/y location.
* Useful for components that do not use mouse input on whole component bounds.
* E.g. a tabbed pane with a few tabs has some empty space beside the tabs
* that can be used to move the window.
* <p>
* Note:
* <ul>
* <li>This method is invoked often when mouse is moved over window title bar area
* and should therefore return quickly.
* <li>This method 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>
*
* @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 com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.UIScale;
/**
@@ -159,14 +160,14 @@ public class FlatToggleButtonUI
b.revalidate();
}
b.repaint();
HiDPIUtils.repaint( b );
break;
case TAB_BUTTON_UNDERLINE_PLACEMENT:
case TAB_BUTTON_UNDERLINE_HEIGHT:
case TAB_BUTTON_UNDERLINE_COLOR:
case TAB_BUTTON_SELECTED_BACKGROUND:
b.repaint();
HiDPIUtils.repaint( b );
break;
}
}

View File

@@ -18,6 +18,7 @@ package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
@@ -35,6 +36,7 @@ import javax.swing.plaf.basic.BasicToolBarSeparatorUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
/**
@@ -130,7 +132,7 @@ public class FlatToolBarSeparatorUI
} else
installStyle( s );
s.revalidate();
s.repaint();
HiDPIUtils.repaint( s );
break;
}
}
@@ -173,6 +175,12 @@ public class FlatToolBarSeparatorUI
if( size != null )
return scale( size );
// get separator width
int separatorWidth = this.separatorWidth;
FlatToolBarUI toolBarUI = getToolBarUI( c );
if( toolBarUI != null && toolBarUI.separatorWidth != null )
separatorWidth = toolBarUI.separatorWidth;
// make sure that gap on left and right side of line have same size
int sepWidth = (scale( (separatorWidth - LINE_WIDTH) / 2 ) * 2) + scale( LINE_WIDTH );
@@ -196,6 +204,12 @@ public class FlatToolBarSeparatorUI
float lineWidth = scale( 1f );
float offset = scale( 2f );
// get separator color
Color separatorColor = this.separatorColor;
FlatToolBarUI toolBarUI = getToolBarUI( c );
if( toolBarUI != null && toolBarUI.separatorColor != null )
separatorColor = toolBarUI.separatorColor;
Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g );
g.setColor( separatorColor );
@@ -210,4 +224,11 @@ public class FlatToolBarSeparatorUI
private boolean isVertical( JComponent c ) {
return ((JToolBar.Separator)c).getOrientation() == SwingConstants.VERTICAL;
}
private FlatToolBarUI getToolBarUI( JComponent c ) {
Container parent = c.getParent();
return (parent instanceof JToolBar && ((JToolBar)parent).getUI() instanceof FlatToolBarUI)
? (FlatToolBarUI) ((JToolBar)parent).getUI()
: null;
}
}

View File

@@ -47,6 +47,7 @@ import javax.swing.plaf.basic.BasicToolBarUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale;
@@ -82,7 +83,7 @@ import com.formdev.flatlaf.util.UIScale;
*/
public class FlatToolBarUI
extends BasicToolBarUI
implements StyleableUI
implements StyleableUI, FlatTitlePane.TitleBarCaptionHitTest
{
/** @since 1.4 */ @Styleable protected boolean focusableButtons;
/** @since 2 */ @Styleable protected boolean arrowKeysOnlyNavigation;
@@ -93,6 +94,10 @@ public class FlatToolBarUI
@Styleable protected Insets borderMargins;
@Styleable protected Color gripColor;
// for FlatToolBarSeparatorUI
/** @since 3.3 */ @Styleable protected Integer separatorWidth;
/** @since 3.3 */ @Styleable protected Color separatorColor;
private FocusTraversalPolicy focusTraversalPolicy;
private Boolean oldFloatable;
private Map<String, Object> oldStyleValues;
@@ -439,7 +444,7 @@ public class FlatToolBarUI
// repaint button group
if( gr != null )
toolBar.repaint( gr );
HiDPIUtils.repaint(toolBar, gr );
}
private ButtonGroup getButtonGroup( AbstractButton b ) {
@@ -449,6 +454,15 @@ public class FlatToolBarUI
: null;
}
//---- interface FlatTitlePane.TitleBarCaptionHitTest ----
/** @since 3.4 */
@Override
public Boolean isTitleBarCaptionAt( int x, int y ) {
// necessary because BasicToolBarUI adds some mouse listeners for dragging when toolbar is floatable
return null; // check children
}
//---- class FlatToolBarFocusTraversalPolicy ------------------------------
/**
@@ -517,8 +531,11 @@ public class FlatToolBarUI
private Component getRecentComponent( Container aContainer, boolean first ) {
// if moving focus into the toolbar, focus recently focused toolbar button
if( focusedCompIndex >= 0 && focusedCompIndex < toolBar.getComponentCount() )
return toolBar.getComponent( focusedCompIndex );
if( focusedCompIndex >= 0 && focusedCompIndex < toolBar.getComponentCount() ) {
Component c = toolBar.getComponent( focusedCompIndex );
if( accept( c ) )
return c;
}
return first
? super.getFirstComponent( aContainer )

View File

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

View File

@@ -47,6 +47,7 @@ import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreePath;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale;
@@ -310,7 +311,7 @@ public class FlatTreeUI
switch( e.getPropertyName() ) {
case TREE_WIDE_SELECTION:
case TREE_PAINT_SELECTION:
tree.repaint();
HiDPIUtils.repaint( tree );
break;
case "dropLocation":
@@ -325,7 +326,7 @@ public class FlatTreeUI
case STYLE_CLASS:
installStyle();
tree.revalidate();
tree.repaint();
HiDPIUtils.repaint( tree );
break;
case "enabled":
@@ -353,7 +354,7 @@ public class FlatTreeUI
Rectangle r = tree.getPathBounds( loc.getPath() );
if( r != null )
tree.repaint( 0, r.y, tree.getWidth(), r.height );
HiDPIUtils.repaint( tree, 0, r.y, tree.getWidth(), r.height );
}
@Override
@@ -370,14 +371,14 @@ public class FlatTreeUI
{
if( changedPaths.length > 4 ) {
// same is done in BasicTreeUI.Handler.valueChanged()
tree.repaint();
HiDPIUtils.repaint( tree );
} else {
int arc = (int) Math.ceil( UIScale.scale( selectionArc / 2f ) );
for( TreePath path : changedPaths ) {
Rectangle r = getPathBounds( tree, path );
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.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.tree.DefaultTreeCellEditor;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatIntelliJLaf;
import com.formdev.flatlaf.FlatLaf;
@@ -123,6 +124,11 @@ public class FlatUIUtils
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 ) {
Color color = UIManager.getColor( key );
return (color != null) ? color : new Color( defaultColorRGB );
@@ -299,15 +305,14 @@ public class FlatUIUtils
if( c == null )
return false;
// check whether used in cell editor (check 3 levels up)
Component c2 = c;
for( int i = 0; i <= 2 && c2 != null; i++ ) {
Container parent = c2.getParent();
if( parent instanceof JTable && ((JTable)parent).getEditorComponent() == c2 )
return true;
// check whether used as table cell editor
Container parent = c.getParent();
if( parent instanceof JTable && ((JTable)parent).getEditorComponent() == c )
return true;
c2 = parent;
}
// check whether used as tree cell editor
if( parent instanceof DefaultTreeCellEditor.EditorContainer )
return true;
// check whether used as cell editor
// Table.editor is set in JTable.GenericEditor constructor
@@ -601,28 +606,55 @@ public class FlatUIUtils
public 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 )
{
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 );
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%
HiDPIUtils.paintAtScale1x( g, x, y, width, height,
(g2d, x2, y2, width2, height2, scaleFactor) -> {
paintOutlinedComponentImpl( g2d, x2, y2, width2, height2,
(float) (focusWidth * scaleFactor), focusWidthFraction, (float) (focusInnerWidth * scaleFactor),
(float) (borderWidth * scaleFactor), (float) (arc * scaleFactor),
focusColor, borderColor, background );
focusColor, borderColor, background, scrollPane, scaleFactor );
} );
return;
}
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,
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
float x1 = x + focusWidth;
float y1 = y + focusWidth;
@@ -734,7 +766,7 @@ public class FlatUIUtils
}
/**
* Creates a (rounded) rectangle used to paint components (border, background, etc).
* Creates a (rounded) rectangle used to paint components (border, background, etc.).
* The given arc diameter is limited to min(width,height).
*/
public static Shape createComponentRectangle( float x, float y, float w, float h, float arc ) {
@@ -780,7 +812,7 @@ public class FlatUIUtils
if( arcTopLeft > 0 || arcTopRight > 0 || arcBottomLeft > 0 || arcBottomRight > 0 ) {
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%
HiDPIUtils.paintAtScale1x( g, x, y, width, height,
(g2d, x2, y2, width2, height2, scaleFactor) -> {
@@ -1315,13 +1347,13 @@ debug*/
@Override
public void focusGained( FocusEvent e ) {
if( repaintCondition == null || repaintCondition.test( repaintComponent ) )
repaintComponent.repaint();
HiDPIUtils.repaint( repaintComponent );
}
@Override
public void focusLost( FocusEvent e ) {
if( repaintCondition == null || repaintCondition.test( repaintComponent ) )
repaintComponent.repaint();
HiDPIUtils.repaint( repaintComponent );
}
}

View File

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

View File

@@ -42,6 +42,7 @@ import javax.swing.DesktopManager;
import javax.swing.JComponent;
import javax.swing.JInternalFrame;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
@@ -191,7 +192,7 @@ public abstract class FlatWindowResizer
protected abstract Dimension getWindowMinimumSize();
protected abstract Dimension getWindowMaximumSize();
protected void beginResizing( int direction ) {}
protected void beginResizing( int resizeDir ) {}
protected void endResizing() {}
//---- interface PropertyChangeListener ----
@@ -234,17 +235,47 @@ public abstract class FlatWindowResizer
{
protected Window window;
private final JComponent centerComp;
private final boolean limitResizeToScreenBounds;
public WindowResizer( JRootPane rootPane ) {
super( rootPane );
// Transparent "center" component that is made visible only while resizing window.
// It uses same cursor as the area where resize dragging started.
// This ensures that the cursor shape stays stable while dragging mouse
// into the window to make window smaller. Otherwise it would toggling between
// resize and standard cursor because the component layout is not updated
// fast enough and the mouse cursor is always updated from the component
// at the mouse location.
centerComp = new JPanel();
centerComp.setOpaque( false );
centerComp.setVisible( false );
Container cont = rootPane.getLayeredPane();
cont.add( centerComp, WINDOW_RESIZER_LAYER, 4 );
// On Linux, limit window resizing to screen bounds because otherwise
// there would be a strange effect when the mouse is moved over a sidebar
// while resizing and the opposite window side is also resized.
limitResizeToScreenBounds = SystemInfo.isLinux;
}
@Override
public void uninstall() {
Container cont = topDragComp.getParent();
cont.remove( centerComp );
super.uninstall();
}
@Override
public void doLayout() {
super.doLayout();
if( centerComp != null && centerComp.isVisible() )
centerComp.setBounds( 0, 0, resizeComp.getWidth(), resizeComp.getHeight() );
}
@Override
protected void addNotify() {
Container parent = resizeComp.getParent();
@@ -299,20 +330,17 @@ public abstract class FlatWindowResizer
@Override
protected boolean limitToParentBounds() {
return limitResizeToScreenBounds && window != null;
return limitResizeToScreenBounds && window != null && window.getGraphicsConfiguration() != null;
}
@Override
protected Rectangle getParentBounds() {
if( limitResizeToScreenBounds && window != null ) {
GraphicsConfiguration gc = window.getGraphicsConfiguration();
Rectangle bounds = gc.getBounds();
Insets insets = window.getToolkit().getScreenInsets( gc );
return new Rectangle( bounds.x + insets.left, bounds.y + insets.top,
bounds.width - insets.left - insets.right,
bounds.height - insets.top - insets.bottom );
}
return null;
GraphicsConfiguration gc = window.getGraphicsConfiguration();
Rectangle bounds = gc.getBounds();
Insets insets = window.getToolkit().getScreenInsets( gc );
return new Rectangle( bounds.x + insets.left, bounds.y + insets.top,
bounds.width - insets.left - insets.right,
bounds.height - insets.top - insets.bottom );
}
@Override
@@ -346,6 +374,19 @@ public abstract class FlatWindowResizer
public void windowStateChanged( WindowEvent e ) {
updateVisibility();
}
@Override
protected void beginResizing( int resizeDir ) {
centerComp.setBounds( 0, 0, resizeComp.getWidth(), resizeComp.getHeight() );
centerComp.setCursor( getPredefinedCursor( resizeDir ) );
centerComp.setVisible( true );
}
@Override
protected void endResizing() {
centerComp.setVisible( false );
centerComp.setCursor( null );
}
}
//---- class InternalFrameResizer -----------------------------------------
@@ -427,7 +468,18 @@ public abstract class FlatWindowResizer
}
@Override
protected void beginResizing( int direction ) {
protected void beginResizing( int resizeDir ) {
int direction = 0;
switch( resizeDir ) {
case N_RESIZE_CURSOR: direction = NORTH; break;
case S_RESIZE_CURSOR: direction = SOUTH; break;
case W_RESIZE_CURSOR: direction = WEST; break;
case E_RESIZE_CURSOR: direction = EAST; break;
case NW_RESIZE_CURSOR: direction = NORTH_WEST; break;
case NE_RESIZE_CURSOR: direction = NORTH_EAST; break;
case SW_RESIZE_CURSOR: direction = SOUTH_WEST; break;
case SE_RESIZE_CURSOR: direction = SOUTH_EAST; break;
}
desktopManager.get().beginResizingFrame( getFrame(), direction );
}
@@ -535,18 +587,7 @@ debug*/
dragRightOffset = windowBounds.x + windowBounds.width - xOnScreen;
dragBottomOffset = windowBounds.y + windowBounds.height - yOnScreen;
int direction = 0;
switch( resizeDir ) {
case N_RESIZE_CURSOR: direction = NORTH; break;
case S_RESIZE_CURSOR: direction = SOUTH; break;
case W_RESIZE_CURSOR: direction = WEST; break;
case E_RESIZE_CURSOR: direction = EAST; break;
case NW_RESIZE_CURSOR: direction = NORTH_WEST; break;
case NE_RESIZE_CURSOR: direction = NORTH_EAST; break;
case SW_RESIZE_CURSOR: direction = SOUTH_WEST; break;
case SE_RESIZE_CURSOR: direction = SOUTH_EAST; break;
}
beginResizing( direction );
beginResizing( resizeDir );
}
@Override

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,14 +16,17 @@
package com.formdev.flatlaf.util;
import java.awt.Component;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.text.AttributedCharacterIterator;
import javax.swing.JComponent;
import javax.swing.RepaintManager;
import com.formdev.flatlaf.FlatSystemProperties;
/**
@@ -192,7 +195,8 @@ public class HiDPIUtils
case "Inter":
case "Inter Light":
case "Inter Semi Bold":
case "Inter Semi Bold": // Inter v3
case "Inter SemiBold": // Inter v4
case "Roboto":
case "Roboto Light":
case "Roboto Medium":
@@ -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

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

View File

@@ -33,6 +33,7 @@ import javax.swing.plaf.DimensionUIResource;
import javax.swing.plaf.FontUIResource;
import javax.swing.plaf.InsetsUIResource;
import javax.swing.plaf.UIResource;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.FlatSystemProperties;
/**
@@ -188,7 +189,9 @@ public class UIScale
// because even if we are on a HiDPI display it is not sure
// that a larger font size is set by the current LaF
// (e.g. can avoid large icons with small text)
Font font = UIManager.getFont( "defaultFont" );
Font font = null;
if( UIManager.getLookAndFeel() instanceof FlatLaf )
font = UIManager.getFont( "defaultFont" );
if( font == null )
font = UIManager.getFont( "Label.font" );
@@ -204,7 +207,7 @@ public class UIScale
if( SystemInfo.isWindows ) {
// Special handling for Windows to be compatible with OS scaling,
// which distinguish between "screen scaling" and "text scaling".
// - Windows "screen scaling" scales everything (text, icon, gaps, etc)
// - Windows "screen scaling" scales everything (text, icon, gaps, etc.)
// and may have different scaling factors for each screen.
// - Windows "text scaling" increases only the font size, but on all screens.
//
@@ -244,6 +247,16 @@ public class UIScale
}
private static float computeScaleFactor( Font font ) {
String customFontSizeDivider = System.getProperty( "flatlaf.uiScale.fontSizeDivider" );
if( customFontSizeDivider != null ) {
try {
float fontSizeDivider = Math.max( Integer.parseInt( customFontSizeDivider ), 10 );
return font.getSize() / fontSizeDivider;
} catch( NumberFormatException ex ) {
// ignore
}
}
// default font size
float fontSizeDivider = 12f;

View File

@@ -20,9 +20,12 @@ import java.awt.Dimension;
import java.awt.Image;
import java.awt.image.AbstractMultiResolutionImage;
import java.awt.image.BaseMultiResolutionImage;
import java.awt.image.ImageObserver;
import java.awt.image.ImageProducer;
import java.awt.image.MultiResolutionImage;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.function.Function;
@@ -116,6 +119,26 @@ public class MultiResolutionImageSupport
return mapAndCacheImage( mrImage );
}
@Override
public int getWidth( ImageObserver observer ) {
return mrImage.getWidth( observer );
}
@Override
public int getHeight( ImageObserver observer ) {
return mrImage.getHeight( observer );
}
@Override
public ImageProducer getSource() {
return mrImage.getSource();
}
@Override
public Object getProperty( String name, ImageObserver observer ) {
return mrImage.getProperty( name, observer );
}
private Image mapAndCacheImage( Image image ) {
return cache.computeIfAbsent( image, img -> {
// using ImageIcon here makes sure that the image is loaded
@@ -134,7 +157,7 @@ public class MultiResolutionImageSupport
{
private final Dimension[] dimensions;
private final Function<Dimension, Image> producer;
private final IdentityHashMap<Dimension, Image> cache = new IdentityHashMap<>();
private final HashMap<Dimension, Image> cache = new HashMap<>();
ProducerMultiResolutionImage( Dimension[] dimensions, Function<Dimension, Image> producer ) {
this.dimensions = dimensions;
@@ -159,6 +182,16 @@ public class MultiResolutionImageSupport
return produceAndCacheImage( dimensions[0] );
}
@Override
public int getWidth( ImageObserver observer ) {
return dimensions[0].width;
}
@Override
public int getHeight( ImageObserver observer ) {
return dimensions[0].height;
}
private Image produceAndCacheImage( Dimension size ) {
return cache.computeIfAbsent( size, size2 -> {
// using ImageIcon here makes sure that the image is loaded

View File

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

View File

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

View File

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

View File

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

View File

@@ -84,10 +84,12 @@ public class TestUIDefaultsLoader
void parseBorders() {
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 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, 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 ) {

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