Compare commits

...

221 Commits
3.5.4 ... main

Author SHA1 Message Date
Karl Tauber
84e95764fa macOS: hide popups when window is resized (issue #1082) 2026-01-30 19:01:26 +01:00
Karl Tauber
1a80a65411 Merge PR #1087: Fix exception when SVG icon name has no file extension 2026-01-30 13:17:56 +01:00
Karl Tauber
bf2227e1b8 Dialog: some client properties (e.g. JRootPane.titleBarShowTitle) did not work before the dialog was made visible (issue #1081) 2026-01-30 12:58:17 +01:00
poce1don
7fc26fe77e Fix exception when SVG icon name has no file extension 2026-01-24 20:44:50 -03:00
Karl Tauber
465254c0da FlatDesktop: avoid unnecessary logging if desktop is not supported (e.g. on NixOS with Plasma/KDE desktop) 2026-01-14 13:21:52 +01:00
Karl Tauber
aaca7cace1 README.md: added notes regarding uber/fat JARs and obfuscation/minimizing/shrinking tools (replaces PR #845) 2026-01-14 13:09:40 +01:00
Karl Tauber
c995b4cbdf Merge PR #1074: Recalculate geometry after styling in FlatSliderUI 2026-01-13 20:03:54 +01:00
Karl Tauber
b251ff76e8 Merge PR #1080: Fix NPE in FlatUIDefaultsInspector when color is null 2026-01-13 19:31:56 +01:00
Karl Tauber
ea2a985481 ToolBar: do not change floatable if it was changed from application code (issue #1075) 2026-01-13 12:35:49 +01:00
poce1don
3ca7ebbfee Fix NPE in FlatUIDefaultsInspector when color is null 2026-01-13 00:44:13 -03:00
daWoife
7e6dc269c5 Recalculate geometry after styling in FlatSliderUI 2025-12-31 11:28:41 +01:00
Karl Tauber
7680c3a817 ComboBox: added UI property ComboBox.buttonFocusedEditableBackground (issue #1068) 2025-12-30 17:36:06 +01:00
Karl Tauber
0e3cb95791 Popup: fixed scrolling popup painting issue on Windows 10 when a glass pane is visible and frame is maximized (issue #1071) 2025-12-30 13:36:09 +01:00
Karl Tauber
3f0b5d5a0e release 3.7 2025-12-04 12:13:56 +01:00
Karl Tauber
6e3633cca3 System File Chooser: save and restore size of (fallback) JFileChooser dialog (PR #988) 2025-12-04 11:36:02 +01:00
Karl Tauber
4f1e5cdb05 IntelliJ Themes: do not release JSON memory to allow re-using theme instance (issue #1020) 2025-12-03 19:13:07 +01:00
Karl Tauber
070cf9c40d Icons: scale checkbox and radiobutton icons when using text styles large, medium, small and mini (issue #1061)
https://www.formdev.com/flatlaf/typography/#text_styles

Theme Editor: added to preview
2025-12-03 14:19:47 +01:00
Karl Tauber
5b0f13110a Icons: support scaling Laf icons (checkbox, radiobutton, etc) (issue #1061) 2025-12-03 11:41:39 +01:00
Karl Tauber
d7a5c353fe System File Chooser: updated Linux native libraries (PR #988)
built by GitHub Actions:
https://github.com/JFormDesigner/FlatLaf/actions/runs/19859966311
2025-12-02 18:19:45 +01:00
Karl Tauber
9e8b8697d1 System File Chooser: Linux: show file dialog in dark if current FlatLaf theme is dark (PR #988) 2025-12-02 14:11:10 +01:00
Karl Tauber
58e073a05b System File Chooser: on Linux when JavaFX is used in application, then always use Swing file chooser because system file dialog does work (PR #988)
with Java 8, some GLib related messages are logged to console and system file dialog is not shown
e.g.: `GLib-GObject-WARNING **: cannot register existing type 'GdkDisplayManager'`

with Java 17, the system file dialog is shown, but then JavaFX no longer works
with Java 21, the application quits/crashes immediately when trying to show system file dialog

also fixed Error Prone warning in `getFiltersForDialog()`
2025-12-01 20:10:06 +01:00
Karl Tauber
1c6e8774cf System File Chooser: (PR #988)
- fixed missing filter in combobox if only `setFileFilter(myFilter)` is used, but not `addChoosableFileFilter(myFilter)`
- fallback Swing file chooser did ignore `isAcceptAllFileFilterUsed()`
- check for supported filter types in `setFileFilter()`
2025-11-30 19:16:51 +01:00
Karl Tauber
1e724029ae Merge PR #987: Enhance FlatUIUtils.getBorderArc to support FlatLineBorder Arc value 2025-11-30 00:44:22 +01:00
Karl Tauber
9fdaa827b5 Merge PR #1030: TabbedPaneUI: When the mouse wheel event is not in the viewport, propagate the event to the nearest parent 2025-11-29 23:25:50 +01:00
Karl Tauber
b9441050b2 TabbedPane:
- convert mouse wheel event before dispatching it to parent
- use `Component.dispatchEvent()`, instead of invoking mouse wheel listeners directly, which dispatches the event to an ancestor
2025-11-29 23:12:21 +01:00
Karl Tauber
8239cdd540 fixed errors reported by Error Prone in commit 855c41bf4a 2025-11-29 18:47:06 +01:00
Karl Tauber
08419d6135 TabbedPane:
- added icon-only tab mode, which shows tab icons but hides tab titles
- in "Show Hidden Tabs" popup menu, do not show text "x. Tab" if tab has icon but no title (issue #1062)
2025-11-29 18:11:31 +01:00
Karl Tauber
3a72232ae3 UI defaults dumps updated for commit 4945378dd3 2025-11-29 14:35:38 +01:00
Karl Tauber
e61499e7c6 use AtomicReference for method parameters that return values 2025-11-29 14:31:05 +01:00
Karl Tauber
d3e6c7af14 Styling: TestFlatStyleableValue: use random values for boolean, integer, float, color, insets and dimension to better test whether set and get style work correctly 2025-11-28 00:43:37 +01:00
Karl Tauber
ff11a11d28 Styling: TestFlatStyling: removed component, border and icon tests because they are also tested in TestFlatStyleableValue 2025-11-27 23:42:26 +01:00
Karl Tauber
855c41bf4a Styling: TestFlatStyleableValue:
- check whether all keys (returned by `getStyleableInfos()`) are tested
- added tests for missing keys
- cache `ui.applyStyle(...)` methods
2025-11-27 23:00:29 +01:00
Karl Tauber
2b587d4dba Styling: added missing unit tests 2025-11-27 15:53:25 +01:00
Karl Tauber
5fabfc7051 Styling: dump FlatLaf styleable infos to flatlaf-testing/dumps/styleable-infos.txt 2025-11-27 15:21:03 +01:00
Karl Tauber
db4173dd06 Styling: introduced interface StyleableObject
- has default implementations that use annotations
- replaces `StyleableBorder`
- used for styleable icons
2025-11-26 11:09:43 +01:00
Karl Tauber
03b7a1c29e Styling: added missing unit tests; added missing FlatCapsLockIcon.getStyleableInfos() 2025-11-25 18:51:40 +01:00
Karl Tauber
60968f77eb Styling: replaced all occurrences of
`return new UnknownStyleException( key )`
with
  `throw new UnknownStyleException( key )`
2025-11-25 18:48:15 +01:00
Karl Tauber
8bafa37b4a Merge PR #1060: Styling of wrong icon in FlatRadioButtonUI 2025-11-25 18:24:06 +01:00
Karl Tauber
04602ac227 CheckBox and RadioButton:
- fixed styling of custom icon
- fixed focus width (and preferred size) if using custom icon
- added unit tests
2025-11-25 18:18:22 +01:00
Karl Tauber
02636b260a Merge PR #1051: Zooming API 2025-11-25 12:30:09 +01:00
Karl Tauber
c8e2e78955 Gradle: use configuration cache 2025-11-25 11:51:41 +01:00
Karl Tauber
19c86cf1f7 update to Gradle 9.2.1 2025-11-25 11:36:07 +01:00
daWoife
7ebc1b27c1 Update FlatRadioButtonUI.java
The method applyStyleProperty of class FlatRadioButtonUI currently styles the variable icon, a field of the parent class BasicRadioButtonUI and the default icon which is set with UIManager.getIcon in method installDefaults. 
So the wrong icon is styled if someone subclasses FlatRadioBoxIcon or FlatCheckBoxIcon and sets this as the new icon for a JRadioButton or JCheckBox instance. (An example why someone would do so is shown in issue #413).
With this change styling takes account of the current icon of a JRadioButton or JCheckBox.
2025-11-15 11:37:18 +01:00
Karl Tauber
d4827b6ddf GitHub Actions: cache buildSrc/build to speed up Gradle builds by ~40 seconds 2025-11-01 11:15:46 +01:00
Karl Tauber
02f7cb8972 update to Gradle 9.2.0; always use Java 25 toolchain to generate javadoc
Notes:
- Gradle 9+ requires Java 17 to run
- using Java 21 on GitHub Actions
- not using Java 25 because Kotlin does not yet support it and output some warnings
2025-10-31 23:52:08 +01:00
Karl Tauber
10677d469f flatlaf-natives-windows: fixed link error on GitHub Actions
for some unknown reason (maybe newer Visual C++ version),
MSVC on GitHub Actions no longer inlines methods `wcscpy` and `wcslen`,
which results in linker error:
`error LNK2019: unresolved external symbol wcscpy/wcslen`
2025-10-31 12:01:20 +01:00
Karl Tauber
df8212b49e Gradle:
- default is now Java 8 toolchain (to fix Eclipse project import)
- `src/main/module-info` and `src/main/java9` are compiled with Java 11 toolchain (if global toolchain is Java 8)
- `src/main/module-info` and `src/main/java9` are no longer imported into Eclipse projects
- task `errorprone` now uses at least Java 11 toolchain
2025-10-30 18:59:21 +01:00
Karl Tauber
c583a21bf7 GitHub Actions: moved Error Prone checks to own workflow to reduce build time of CI workflow 2025-10-28 17:47:40 +01:00
Karl Tauber
5263125a04 GitHub Actions: build using various Java versions and publish snapshot in single job to reduce overhead 2025-10-28 17:15:36 +01:00
Karl Tauber
056da35758 Gradle:
- always use Java toolchains
- default is Java 11 (but source/targetCompatibility is still 1.8)
- use system property `toolchain` (e.g. `-Dtoolchain=25`) to compile with other Java versions

preparation for Gradle 9.x, which requires Java 17+ to run
2025-10-28 13:06:40 +01:00
Karl Tauber
3ccaacfb00 Native window decorations: updated com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc.h for commit 34b19f00e4 2025-10-28 12:27:50 +01:00
Karl Tauber
bdb7438672 Merge PR #988: System File Chooser 2025-10-27 19:16:50 +01:00
Karl Tauber
299250a710 System File Chooser: change @since 3.6 to @since 3.7 2025-10-27 18:06:41 +01:00
Karl Tauber
1e2a75a19c sigtest: fix line separators for file generated by sigtest (default is a mix of cr+lf and lf on Windows) 2025-10-26 19:44:58 +01:00
Karl Tauber
0fb4c811f6 Zooming API 2025-10-26 18:50:24 +01:00
Karl Tauber
0d4946230e JIDE: JideButton, JideToggleButton, JideSplitButton and JideToggleSplitButton: paint border in button style TOOLBAR_STYLE if in selected state (issue #1045) 2025-10-24 19:50:04 +02:00
Karl Tauber
960f9d86c1 TextField: fixed wrong leading/trailing icon placement if border is set to null (issue #1047) 2025-10-22 14:18:40 +02:00
Karl Tauber
015645e173 UI defaults inspector: exclude window from being blocked by modal dialogs (issue #1048) 2025-10-20 19:32:22 +02:00
Karl Tauber
36d5685f4c release 3.6.2 2025-10-10 09:13:22 +02:00
Karl Tauber
ddc8d6e29c README.md: new applications using FlatLaf:
- OpenRocket
- Warteschlangensimulator
- Nortantis
- QStudio
- Launch4j
2025-10-07 19:36:39 +02:00
Karl Tauber
119b4a922d fixed loading FlatLaf properties files in NetBeans (broken since commit again; issue #1026)
now using old implementation again (before commit 2ac7234c32), but if that does not find properties file, then fallback to new implementation from commit 2ac7234c32
2025-09-29 21:30:41 +02:00
Karl Tauber
5e4f00f0c8 GitHub Actions: build using Java 25 LTS 2025-09-23 16:15:08 +02:00
Karl Tauber
15cbf28a0d Popup: no longer reuse popup windows for menus to avoid immediately closing dialogs on ChromeOS (issue #1029)
added system property `flatlaf.reuseVisiblePopupWindow`
2025-09-20 12:51:50 +02:00
Karl Tauber
f8e53c9064 README.md:
- replaced maven badges with shields.io because maven-badges.herokuapp.com did not show latest version 3.6.1 (instead shows 3.6)
- also the link target search.maven.org does not show 3.6.1
- now link to central.sonatype.com, which seems to be the successor of search.maven.org
  https://central.sonatype.org/faq/what-happened-to-search-maven-org/
2025-09-10 14:48:27 +02:00
Karl Tauber
b3c9638e47 Popup: no longer use popup.show() for already visible popup window to avoid that inactive owner window becomes active (issue #1037) 2025-09-09 20:00:26 +02:00
Karl Tauber
d079741f94 Extras: FlatAnimatedLafChange: made transition smoother:
- use a single component in layered pane to paint new and old UI snapshots (previously used two components)
- the snapshot layer component is now opaque, which avoids that window component hierarchy is involved when painting snapshots
- snapshots are now painted immediately, which should result in a smoother transition
- changed animation resolution from 30ms to 16ms
2025-09-09 18:54:11 +02:00
Karl Tauber
c051ad5f72 macOS: fixed window "flashing" when switching from a light to a dark theme (or vice versa), especially when using animated theme changer 2025-09-09 17:30:43 +02:00
Karl Tauber
1ed7aeaa45 Tree: removed unused method parameter; reported by Error Prone in commit d388158de7 2025-09-08 14:11:16 +02:00
Karl Tauber
2ac7234c32 support loading FlatLaf properties files from named Java modules without the need to open that package in module-info.java (issue #1026) 2025-09-08 13:03:17 +02:00
Karl Tauber
6f63982054 load properties files using UTF-8 instead of ISO 8859-1 (issue #1031) 2025-09-06 12:45:40 +02:00
Karl Tauber
d388158de7 Tree and List: fixed painting of rounded drop backgrounds (issue #1023) 2025-09-06 00:15:11 +02:00
lkrieger-oashi
8cfe1ca597 changed: when the mouse wheel event is not in the viewport, propagate the event to the nearest parent
Scenario: A JTabbedPane inside a JScrollPane
Expectation: Moving the mouse wheel inside the TabbedPane's content should cause the outer ScrollPane to scroll
Actual: Event dispatcher targets TabbedPane, but the TabbedPane's mouse listener does nothing because the event is not in the viewport. The only other target is the ancestor (Window), which discards the event.
Fix: If the event does not occur in the viewport, delegate it up in the hierarchy to the nearest parent with a MouseWheelListener
2025-08-20 10:17:01 +02:00
Karl Tauber
e7a766bf8f added SOPTIM as Gold sponsor 2025-07-18 16:14:59 +02:00
Karl Tauber
97988e90b4 release 3.6.1 2025-07-12 16:44:32 +02:00
Karl Tauber
f71dbb2647 Linux: ensure that old LinuxPopupMenuCanceler window listener is removed before adding a new one (issue #962)
(did not yet happen...)
2025-07-12 16:28:49 +02:00
Karl Tauber
04ad21b5b6 Button: added unit tests for foreground and background colors (issue #1017) 2025-07-12 15:59:56 +02:00
Karl Tauber
ff722c0b34 Popup:
- macOS: Fixed popup flickering after theme change. (issue #1009)
- macOS with JetBrains Runtime: Fixed sometimes empty popups. (issue #1019)
2025-07-07 18:33:49 +02:00
Karl Tauber
34b19f00e4 Window decorations (Windows 10/11 only):
- improved diagonal window resizing on top-left and top-right window corners
- top window resize area now also covers iconify/maximize/close buttons
(issue #1015)
2025-07-03 20:08:40 +02:00
Karl Tauber
286ce15146 ToggleButton: styling selectedForeground did not work if foreground is also styled (issue #1017) 2025-07-02 20:08:55 +02:00
Karl Tauber
abfaf86cd5 change snapshot version from 3.7-SNAPSHOT to 3.6.1-SNAPSHOT 2025-07-02 19:50:51 +02:00
Karl Tauber
1eee35035d Merge main into system-file-chooser 2025-06-23 17:09:19 +02:00
Karl Tauber
bc4c7b25d3 snapshots: publish macOS .dylib native libraries to Maven Central Snapshots (disabled in commit 5575854e68) 2025-06-23 16:56:38 +02:00
Karl Tauber
0863e289a1 Table: add disabled icon for boolean renderer (issue #1008) 2025-06-20 19:56:24 +02:00
Karl Tauber
5c2d8ba555 System File Chooser: fix crash on macOS 15.x 2025-06-17 12:23:09 +02:00
Karl Tauber
0f27125107 GitHub Actions: natives.yml:
- fixed build issue on ubuntu arm64
- disabled signing on macOS (because it no longer works and I have no idea how to fix it)
2025-06-17 11:32:20 +02:00
Karl Tauber
f2882370de GitHub Actions: natives.yml:
- disabled cross-compile for arm64 architecture on x86_64 Linux
- use `apt-get` instead of `apt`
- use long command line options for `codesign`
2025-06-13 14:33:54 +02:00
Karl Tauber
6715886b24 Merge main into system-file-chooser 2025-06-12 11:29:17 +02:00
Karl Tauber
4945378dd3 macOS themes: fixed rendering of menu bar separator if unified background is disabled (issue #1003) 2025-06-12 10:45:57 +02:00
Karl Tauber
b178450e81 README.md: new applications using FlatLaf:
- SSPlot (issue #1002)
2025-06-12 10:28:24 +02:00
Karl Tauber
e3ffdd3b7c UIDefaultsLoader: improved error reporting and added more unit tests 2025-05-28 00:32:35 +02:00
Karl Tauber
5326971287 Linux: popups appeared in wrong position on multi-screen setup if primary display is located below or right to secondary display (issue https://github.com/apache/netbeans/issues/8532)) 2025-05-27 20:01:04 +02:00
Karl Tauber
cd34c08dc9 README.md: new applications using FlatLaf:
- Zettelkasten
- Convertigo
- EduMIPS64
- OpenPnP
- TrainControl
- Pixelitor
- Gephi
- StarPlan
- Lisheane ERP
- jose
2025-05-24 14:38:31 +02:00
Karl Tauber
edee73e0ea README.md: removed "new" badge from applications added in 2023 (and before) 2025-05-24 13:06:27 +02:00
Karl Tauber
54a53fb527 IntelliJ Themes: fixed logging false errors when loading 3rd party .theme.json files (issue #990) 2025-05-24 13:02:29 +02:00
Karl Tauber
62b96fbccd JideSplitButton: fixed updating popup when switching theme (issue #1000) 2025-05-23 19:31:42 +02:00
Karl Tauber
42cbb0666d README.md: added RedisFront (issue #989) 2025-05-23 01:17:14 +02:00
Karl Tauber
1465fbaabc Merge PR #992: Add Termora to Utilities list 2025-05-23 01:11:48 +02:00
Karl Tauber
5575854e68 migrate from legacy OSSRH to Central Portal
https://central.sonatype.org/faq/what-is-different-between-central-portal-and-legacy-ossrh/

using Portal OSSRH Staging API
https://central.sonatype.org/publish/publish-portal-ossrh-staging-api/
2025-05-23 00:59:32 +02:00
Karl Tauber
640f2ba9a2 update to Gradle 8.14.1 2025-05-23 00:49:02 +02:00
Karl Tauber
b221fd1894 Extras: Support JSVG 2.0.0. Minimum JSVG version is now 1.6.0. (issue #997) 2025-04-30 19:39:25 +02:00
hstyi
b64ab09b88 Add Termora to Utilities list 2025-04-14 15:39:25 +08:00
Karl Tauber
2870ee5c51 release 3.6 2025-04-11 12:00:51 +02:00
Karl Tauber
23f8ce867b Demo on Linux: support toggling FlatLaf window decorations (menu "Options > Window decorations") 2025-03-23 13:58:02 +01:00
Karl Tauber
835a1f155b ScrollBar: use rounded thumb also on Windows (as on macOS and Linux) and made thumb slightly darker/lighter (issue #918) 2025-03-23 13:01:15 +01:00
Karl Tauber
3925f198d9 Demo and Theme Editor: added Enable-Native-Access: ALL-UNNAMED to MANIFEST.MF to avoid warning when lauchned with Java 24 2025-03-22 18:13:10 +01:00
Karl Tauber
35e86ba772 System File Chooser: updated all native libraries
built and signed by GitHub Actions:
https://github.com/JFormDesigner/FlatLaf/actions/runs/14008769165
2025-03-22 14:24:34 +01:00
Karl Tauber
dade1cba5a System File Chooser:
- introduced state storage
- added "New Folder" to macOS select folder dialog
- Demo: added "Select Folder (System)" menu item
- javadoc fixes
2025-03-22 13:51:16 +01:00
Dar
dcb4c09387 fix: extended support for getBorderArc method
Fixes `FlatUIUtils.getBorderArc()` returning `0` for `FlatLineBorder`, causing background overflow outside rounded corners when a background color is set.
2025-03-21 09:12:24 +01:00
Karl Tauber
3e8b213367 System File Chooser: fixed font in message dialog on Windows 2025-03-20 19:12:10 +01:00
Karl Tauber
202a0d159b GitHub Actions: natives.yml: sign Windows and macOS native libraries 2025-03-18 19:57:38 +01:00
Karl Tauber
5d247f6269 GitHub Actions: natives.yml: include only the core natives that have been built in artefacts 2025-03-17 19:27:47 +01:00
Karl Tauber
d8e59f2cf3 TextComponents: selectAllOnFocusPolicy related changes:
- No longer select all text if selection (or caret position) was changed by
  application and `selectAllOnFocusPolicy` is `once` (the default). (issue #983)
- FormattedTextField and Spinner: `selectAllOnFocusPolicy = once` behaves now
  as `always` (was `never` before), which means that all text is selected when
  component gains focus. This is because of special behavior of
  `JFormattedTextField` that did not allow implementation of `once`.
- Client property `JTextField.selectAllOnFocusPolicy` now also works on
  (editable) `JComboBox` and on `JSpinner`.
- Added client property `JTextField.selectAllOnMouseClick` to override UI
  property `TextComponent.selectAllOnMouseClick`. (issue #961)
- For `selectAllOnMouseClick = true`, clicking with the mouse into the text
  field, to focus it, now always selects all text, even if
  `selectAllOnFocusPolicy` is `once`.
2025-03-16 11:24:01 +01:00
Karl Tauber
d81bcd5254 Merge main into system-file-chooser 2025-03-09 19:20:54 +01:00
Karl Tauber
666b99971d Merge PR #971: Linux: rounded iconify/maximize/close buttons if using FlatLaf window decorations 2025-03-09 16:41:51 +01:00
Karl Tauber
0ba7798cbd Fonts: updated Inter to v4.1 (issue #976)
`Inter-*.otf` files are from `Inter-4.1.zip`, folder `extras/otf`
2025-03-09 13:51:34 +01:00
Karl Tauber
c486f695f2 Testing: support interprocess Laf sync (enable/disable with Alt+Shift+S) 2025-03-09 13:31:38 +01:00
Karl Tauber
0bc2513c46 for PR #982:
- updated CHANGELOG.md
- updated styling unit tests
- updated UI defaults dumps
- Demo: error/warning/success hints in a single row
- Demo: reduced height of "Data comp" tab to make Demo window smaller
- IntelliJ themes: fixed missing/overwritten success colors
2025-03-09 00:15:00 +01:00
Karl Tauber
bc3504378b Merge PR #982: Add "success" outline for borders (issue #945) 2025-03-08 23:45:20 +01:00
Karl Tauber
94fc75dc78 Merge PR #981: Add Consulo to Software Development list 2025-03-08 19:51:22 +01:00
Karl Tauber
681c0cd4fe HTML: fixed rendering of <hr noshade> in dark themes (issue #932) 2025-03-08 19:47:37 +01:00
Karl Tauber
f7495a0a5b - support key prefixes for Linux desktop environments (issue #974)
- support custom key prefixes (issue #649)
- support multi-prefixed keys
- changed handling of prefixed keys
2025-03-08 18:11:38 +01:00
Valery Semenchuk
dd44d3ed2d Merge branch 'JFormDesigner:main' into issue945 2025-03-08 14:58:22 +04:00
Valery Semenchuk
86a4d0ab12 add "success" border color
fix #945
2025-03-08 14:57:06 +04:00
Valery Semenchuk
b43c3a9e00 Add Consulo to Software Development list 2025-03-07 23:01:03 +04:00
Karl Tauber
babc8aa55d SwingX: support JXTipOfTheDay component (issue #980) 2025-03-07 11:59:21 +01:00
Karl Tauber
5dc88a6210 SwingX: moved FlatMonthDownIcon and FlatMonthUpIcon from ui to icons package 2025-03-06 18:47:48 +01:00
Karl Tauber
d612b9f4b8 Linux: use X11 window manager events to resize window, if FlatLaf window decorations are enabled (issue #866)
also made drag border slightly wider (from 5 to 6 pixels) and changed drag corner width from 16 to 32 to match native windows
2025-03-06 17:47:55 +01:00
Karl Tauber
9b1ae5c74a IntelliJ Themes: support customizing through properties files (issue #824)
re-written how .theme.json values are applied:
- old: .theme.json values were applied as last step to a UIDefaults object (after processing all FlatLaf properties files)
- new: .theme.json values are applied to the properties map while loading all FlatLaf properties files
2025-03-06 13:26:38 +01:00
Karl Tauber
143f96360b IntelliJ Themes: removed Gruvbox Dark Medium and Gruvbox Dark Soft themes 2025-02-27 11:58:09 +01:00
Karl Tauber
f5e6b90e02 UIDefaultsDump: fixed property sort order and updated dumps
GTKLookAndFeel dump updated on Ubuntu 24.10
2025-02-25 20:04:29 +01:00
Karl Tauber
f36886aeb3 IntelliJ Themes: renamed classes in package com.formdev.flatlaf.intellijthemes.materialthemeuilite from Flat<theme> to FlatMT<theme> (preparation for #824) 2025-02-25 16:18:55 +01:00
Karl Tauber
d26eb2674f Demo/Testing: use IntelliJ Theme Laf classes (from package com.formdev.flatlaf.intellijthemes) in Demo and Testing apps instead of directly loading .theme.json (preparation for #824) 2025-02-25 15:50:30 +01:00
Karl Tauber
68b8769d0d IntelliJ Themes: theme prefix in IntelliJTheme$ThemeLaf.properties changed from [theme-name] to {theme-name}
preparation to support custom/general property prefixes for #649 and #974
2025-02-25 15:04:22 +01:00
Karl Tauber
7f37e884d3 Merge PR #972: Increase contrast of text for better readability 2025-02-25 11:06:28 +01:00
Karl Tauber
a4dc1b4151 UIDefaultsLoader: support lazy option for spin(), changeHue(), changeSaturation(), changeLightness(), changeAlpha(), mix(), tint() and shade() color functions; added unit tests for lazy color functions 2025-02-25 10:42:44 +01:00
Karl Tauber
022a67929a IntelliJ Themes: use "High Contrast" from intellij repo 2025-02-25 00:24:35 +01:00
Karl Tauber
f7c867fb97 increased contrast of separator lines in dark themes (issue #762) 2025-02-24 19:54:18 +01:00
Karl Tauber
f0685d179e Linux: support loading native libraries for unknown architectures (issue #966)
expected filename is same as for `x86_64` architecture but replace `x86_64` with value of system property `os.arch`
2025-02-20 13:56:50 +01:00
Karl Tauber
c8eaf5f587 increased contrast of text for better readability (issue #762) 2025-02-18 20:06:30 +01:00
Karl Tauber
ed69049c08 UIDefaultsDump: dump contrast ratios 2025-02-18 20:06:30 +01:00
Karl Tauber
ae4037ee82 Linux: rounded iconify/maximize/close buttons if using FlatLaf window decorations (issue #821) 2025-02-18 13:33:02 +01:00
Karl Tauber
411a2f6d29 fixed errors reported by Error Prone in commit 6c8f813e53 2025-02-16 19:59:36 +01:00
Karl Tauber
f24b3a6022 UI defaults: Linux dumps updated on Ubuntu 24.10 2025-02-16 19:21:53 +01:00
Karl Tauber
ebacad2d04 UIDefaultsLoader: support derived option for mix(), tint() and shade() color functions; added unit tests for color functions 2025-02-16 16:58:21 +01:00
Karl Tauber
76f436726f IntelliJ Themes: updated themes to newest versions (used IJThemesUpdater) 2025-02-13 18:55:08 +01:00
Karl Tauber
6c8f813e53 Testing: added FlatClassLoaderTest.java (used for previous commit) 2025-02-13 00:27:44 +01:00
Karl Tauber
5f6cc719ad fixed loading FlatLaf UI delegate classes when using FlatLaf in special application where multiple class loaders are involved; e.g. in Eclipse plugin or in LibreOffice extension (issues #955 and #851) 2025-02-12 14:35:35 +01:00
Karl Tauber
00858002de Eclipse: updated org.eclipse.jdt.core.prefs using latest Eclipse and buildship versions 2025-02-05 14:00:11 +01:00
Karl Tauber
072cc3c488 Styling: MigLayout visual padding was not updated after applying style to Button, ComboBox, Spinner, TextField (and subclasses) and ToggleButton (issue #965) 2025-02-05 12:55:12 +01:00
Karl Tauber
1f594b2ba8 Linux with FlatLaf window decorations: fixed occasional maximizing of window when single-clicking the window's title bar (issue #637) 2025-01-28 15:31:13 +01:00
Karl Tauber
03e5f8623e update to Gradle 8.12.1 2025-01-27 18:45:50 +01:00
Karl Tauber
c32c00a5eb Linux with FlatLaf window decorations:
- moved window resizer components from layered pane to rootpane so that border is included in area where user can resize window
- scale border thickness
2025-01-26 17:48:19 +01:00
Karl Tauber
3a8a55a545 Linux with FlatLaf window decorations: right and bottom window resize drag areas were 2px smaller than at left and top sides 2025-01-26 17:23:59 +01:00
Karl Tauber
6c49b8bc4d Linux: hide popups when window is moved, resized, maximized, restored, iconified or switched to another window (issue #962) 2025-01-25 09:48:52 +01:00
Karl Tauber
cca9707f6b Popup: on Windows 10, update drop shadow of heavy-weight popup if popup moved/resized (issue #942) 2025-01-25 07:45:57 +01:00
Karl Tauber
54d6959533 System File Chooser:
- always use some window as owner (similar to `JFileChooser`)
- Linux: use "Select" for approve button in directory selection
2025-01-23 19:52:39 +01:00
Karl Tauber
112116556d Merge main into system-file-chooser 2025-01-23 17:04:05 +01:00
Karl Tauber
f30dd876e4 Linux: updated libflatlaf-linux-arm64.so for Linux on ARM64; now built on ARM64 Linux (ubuntu-24.04-arm); previous was cross-compiled on X86_64 Linux (issue #899)
built by GitHub Actions: https://github.com/JFormDesigner/FlatLaf/actions/runs/12932258675
2025-01-23 16:38:16 +01:00
Karl Tauber
c6872d48b3 GitHub Actions:
- build Linux ARM64 native library on ubuntu-24.04-arm
- build on various Java versions only if build on Java 11 succeeded
- build Java version matrix only on main repo
2025-01-23 16:15:22 +01:00
Karl Tauber
3283cfe22f System File Chooser: macOS: disable screen menu bar when file dialog is shown
Testing: reduced duplicate code
2025-01-23 00:07:38 +01:00
Karl Tauber
aecb496142 System File Chooser: macOS: show file dialog in dark if current FlatLaf theme is dark 2025-01-21 14:36:01 +01:00
Karl Tauber
5e78b21df7 macOS native rounded borders: (PR #772)
- removed opacity from contentView.layer
- catch and log exceptions
2025-01-21 11:59:40 +01:00
Karl Tauber
1e3e4d7c61 System File Chooser: fixed (cross-)compile native library for ARM64 Linux 2025-01-21 11:28:57 +01:00
Karl Tauber
2ef87dc789 macOS: re-enabled rounded popup border (see PR #772) on macOS 14.4+ (was disabled in 3.5.x)
deferred rounded border setup, if popup window is not yet created, could make a difference (see commit 656d25b75e)
2025-01-20 19:42:55 +01:00
Karl Tauber
b808f6e803 System File Chooser: support platform specific features 2025-01-20 19:23:09 +01:00
Karl Tauber
f3ca3a001a System File Chooser: added "approve" callback to SystemFileChooser 2025-01-20 17:20:06 +01:00
Karl Tauber
d524536575 System File Chooser: Linux: cross-compile native library for ARM64 on x86_64 Linux 2025-01-20 16:09:16 +01:00
Karl Tauber
0a4c01cd40 Merge main into system-file-chooser 2025-01-18 17:53:04 +01:00
Karl Tauber
28904c34cc Linux: added libflatlaf-linux-arm64.so for Linux on ARM64 (issue #899)
built by GitHub Actions: https://github.com/JFormDesigner/FlatLaf/actions/runs/12845430366
2025-01-18 17:09:32 +01:00
Karl Tauber
91f19bf94c Native Libraries:
- Linux: added dumps
- macOS: replaced deprecated Gradle `exec`
2025-01-18 16:53:30 +01:00
Karl Tauber
0ea188f8db Linux: support cross-compile native library for ARM64 on x86_64 Linux (issue #899) 2025-01-18 14:50:42 +01:00
Karl Tauber
6c77b81277 Linux: support building native library for ARM64 when Gradle build runs on ARM64 Linux (issue #899) 2025-01-16 20:10:57 +01:00
Karl Tauber
d513ec497b System File Chooser: support system message dialog with custom buttons on Windows (not yet used in SystemFileChooser 2025-01-15 18:51:37 +01:00
Karl Tauber
07fc190b5f Native Libraries: moved code to JNIUtils.cpp and *MessageDialog.cpp 2025-01-14 16:50:13 +01:00
Karl Tauber
078e59a443 System File Chooser: support "approve" callback and system message dialog on macOS (not yet used in SystemFileChooser 2025-01-12 18:32:58 +01:00
Karl Tauber
d49282dfe8 System File Chooser: support "approve" callback and system message dialog on Windows and Linux (not yet used in SystemFileChooser 2025-01-12 01:40:12 +01:00
Karl Tauber
ddee4ef526 GitHub Actions: natives.yml: install libxt-dev to fix build error on ubuntu 24.04 (did work without this on ubuntu 22.04) 2025-01-09 15:57:46 +01:00
Karl Tauber
f11f68282b update to Gradle 8.12 2025-01-09 15:39:27 +01:00
Karl Tauber
ac0cceb09b added Exocharts as Gold sponsor 2025-01-09 15:19:47 +01:00
Karl Tauber
a238fd4505 Button: fixed background and foreground colors for borderless and toolBarButton style default buttons (JButton.isDefaultButton() is true) (issue #947) 2025-01-09 14:38:36 +01:00
Karl Tauber
8dc6242889 FlatLaf window decorations: minimize and maximize icons were not shown for custom scale factors less than 100% (e.g. `-Dflatlaf.uiScale=75%) (issue #951) 2025-01-09 14:19:44 +01:00
Karl Tauber
d17fffb82a PopupFactory: fixed NPE on Windows 10 when owner is null (issue #952) 2025-01-09 12:26:57 +01:00
Karl Tauber
0ad3180b10 FileChooser: improved performance when navigating to large directories with thousands of files (issue #953) 2025-01-09 12:04:25 +01:00
Karl Tauber
c73fd51704 System File Chooser: support filename extension filters 2025-01-08 18:41:14 +01:00
Karl Tauber
251198c66d Native Libraries:
- made C methods `static` (similar to `private` in Java) to avoid that they are added (exported) to shared library symbol table
- macOS and Linux: added `-fvisibility=hidden` to compiler options to mark C methods hidden by default
2025-01-08 12:59:38 +01:00
Karl Tauber
d7462bd424 System File Chooser: use Cocoa autolayout for "Format" label and combobox on macOS 2025-01-07 19:20:40 +01:00
Karl Tauber
9af7f95197 System File Chooser: added "Format" combobox on macOS (if using more than one filter) 2025-01-07 14:37:58 +01:00
Karl Tauber
2e16ded5d4 System File Chooser: support macOS in class SystemFileChooser 2025-01-06 19:22:15 +01:00
Karl Tauber
91e8d04a9f System File Chooser: introduced class SystemFileChooser as replacement for JFileChooser 2025-01-06 18:01:50 +01:00
Karl Tauber
9453d55abd System File Chooser: fixes for Windows 2025-01-04 12:33:18 +01:00
Karl Tauber
641fada6c4 System File Chooser: implemented modality for GtkFileChooserDialog on Linux 2025-01-04 12:22:14 +01:00
Karl Tauber
a303cd2dec System File Chooser: renamed Windows and macOS test apps 2025-01-03 17:56:02 +01:00
Karl Tauber
2b810addd8 System File Chooser: implemented native bindings for GtkFileChooserDialog on Linux 2025-01-03 16:38:10 +01:00
Karl Tauber
63272a03cf System File Chooser: macOS:
- use `optionsSet` and `optionsClear` (as on Windows)
- delete local reference after getting Java array item
- added "or null" to javadoc
2024-12-31 18:44:06 +01:00
Karl Tauber
49a0a83eca System File Chooser: implemented native bindings for IFileOpenDialog and IFileSaveDialog on Windows 2024-12-31 17:39:44 +01:00
Karl Tauber
516bd80702 System File Chooser: implemented native bindings for NSOpenPanel and NSSavePanel on macOS 2024-12-30 12:46:28 +01:00
Karl Tauber
80ba75fdeb List:
- fixed wrong x/width bounds of alternating rows for multi-column lists (PR #939)
- Demo: added "alternating rows" checkboxes to "Data Components" page
2024-12-18 12:22:04 +01:00
Karl Tauber
7027821c00 Merge PR #939: Support for alternate row color in JList 2024-12-18 00:30:01 +01:00
Karl Tauber
a3a49cef73 Merge PR #936: CheckBox: support styling indeterminate state of tri-state check boxes 2024-12-17 23:46:12 +01:00
Dar
abf77d5399 mod: code format 2024-12-16 15:55:40 +01:00
Dar
6404b8de2a fix: filter combobox by name 2024-12-16 15:49:19 +01:00
Dar
19055d5a18 mod: combobox does not support alternate row color 2024-12-13 09:21:14 +01:00
Dar
c12adf12e7 new: support for alternate row color in JList 2024-12-13 09:20:23 +01:00
Karl Tauber
b9c68fbe77 CheckBox: support styling indeterminate state of tri-state check boxes (issue #919) 2024-12-11 16:54:03 +01:00
Karl Tauber
7bdfd49921 Merge PR #935: Tree: support wide cell renderer 2024-12-11 13:33:03 +01:00
Karl Tauber
58fa2a5085 Demo, Testing, Theme Editor: re-generated UI code using JFormDesigner 8.3, which now supports class FlatClientProperties 2024-12-10 19:49:46 +01:00
Karl Tauber
a400799db5 Extras: use default activation keys if passing null to FlatInspector.install() or to FlatUIDefaultsInspector.install() 2024-12-09 23:55:02 +01:00
Karl Tauber
2a8e487c1f Tree: support wide cell renderer (issue #922) 2024-12-09 19:50:00 +01:00
Karl Tauber
145631fd43 FlatComponentsTest: added "transparent background" checkbox 2024-12-09 17:10:33 +01:00
Karl Tauber
6a774d8c70 GitHub Actions: use lftp directly to upload FlatLaf demo and theme editor
previously used github action `sebastianpopp/ftp-action`, which uses Docker, was very slow (about two minutes to upload one file)
2024-12-09 13:44:54 +01:00
Karl Tauber
bfd746f981 GitHub Actions: no longer upload theme editor snapshot; update gradle wrapper validation action 2024-12-09 01:23:55 +01:00
Karl Tauber
3af54b7215 Merge branch 'flatlaf-3.5.4' into main 2024-12-09 01:12:27 +01:00
Karl Tauber
304cb0d57b Merge PR #903: Support for alternate row color in JTree 2024-12-08 13:44:58 +01:00
Karl Tauber
41332de275 Merge PR #931: Fixing NPE when using HTML text on a component with null font 2024-12-08 13:37:02 +01:00
Karl Tauber
ef61ae504b FlatSVGIcon: color filters now can access painting component to implement component state based color mappings (PR #906) 2024-12-07 19:52:23 +01:00
Eduwardo Horibe
f96baf1bc2 Fixing NPE when using HTML text on a component with null font 2024-12-06 18:52:14 -03:00
Dar
a20cfa6db3 mod: style tester 2024-10-23 15:36:17 +02:00
Dar
6ac6698ecf mod: added 3.6 tag 2024-10-23 15:32:16 +02:00
Dar
8004d2761a fix: respect the selection arc 2024-10-23 15:30:06 +02:00
Dar
25c2bbc851 fix: alternate row color paint 2024-10-22 18:05:14 +02:00
Dar
33e37a7167 new: support for alternate row color in jtree
see: https://github.com/JFormDesigner/FlatLaf/issues/900
2024-10-22 16:46:30 +02:00
335 changed files with 35382 additions and 9202 deletions

View File

@@ -7,6 +7,11 @@ charset = latin1
indent_style = tab
indent_size = 4
[{*.yaml,*.yml}]
end_of_line = lf
indent_style = space
indent_size = 2
[*.java]
indent_style = tab
ij_continuation_indent_size = 4

32
.github/actions/cache-gradle/action.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
# https://docs.github.com/en/actions/tutorials/create-actions/create-a-composite-action#creating-a-composite-action-within-the-same-repository
# cache uses two files:
#
# - gradle-wrapper-<os>-<arch>-<hash>:
# contains the Gradle wrapper/distribution (~/.gradle/wrapper)
# and is updated when the Gradle version changed
# - gradle-caches-<os>-<arch>-<hash>:
# contains the Gradle caches (~/.gradle/caches), buildSrc/build and buildSrc/.gradle
# and is updated when Gradle related files were changed
# buildSrc/.gradle is needed so that buildSrc tasks are UP-TO-DATE
name: 'Cache Gradle'
runs:
using: "composite"
steps:
- name: Cache '.gradle/wrapper'
uses: actions/cache@v4
with:
key: gradle-wrapper-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles( 'gradle/wrapper/gradle-wrapper.properties' ) }}
path: |
~/.gradle/wrapper
- name: Cache '.gradle/caches' and 'buildSrc/build'
uses: actions/cache@v4
with:
key: gradle-caches-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles( '**/*.gradle*', 'gradle/**', 'gradle.properties', 'buildSrc/src/**' ) }}
path: |
~/.gradle/caches
buildSrc/build
buildSrc/.gradle

View File

@@ -14,6 +14,7 @@ on:
- '.*'
- '**/.settings/**'
- 'flatlaf-core/svg/**'
- 'flatlaf-natives/**'
- 'flatlaf-testing/dumps/**'
- 'flatlaf-testing/misc/**'
- 'images/**'
@@ -22,46 +23,50 @@ jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
steps:
- uses: actions/checkout@v4
- uses: gradle/actions/wrapper-validation@v4
- name: Setup Java 21
uses: actions/setup-java@v4
with:
java-version: 21
distribution: temurin # pre-installed on ubuntu-latest
- name: Cache Gradle
uses: ./.github/actions/cache-gradle
# test against
# - Java 8 (minimum requirement)
# - Java LTS versions (11, 17, ...)
# - latest Java version(s)
java:
- 8
- 11 # LTS
- 17 # LTS
- 21 # LTS
- 23 # latest
toolchain: [""]
# include:
# - java: 21
# toolchain: 22 # latest
steps:
- uses: actions/checkout@v4
- name: Build with Java 11 LTS
if: github.repository == 'JFormDesigner/FlatLaf'
run: ./gradlew build clean -Dtoolchain=11
- uses: gradle/wrapper-validation-action@v2
if: matrix.java == '8'
- name: Build with Java 17 LTS
if: github.repository == 'JFormDesigner/FlatLaf'
run: ./gradlew build clean -Dtoolchain=17
- name: Setup Java ${{ matrix.java }}
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java }}
distribution: temurin # Java 8, 11, 17 and 21 are pre-installed on ubuntu-latest
cache: gradle
- name: Build with Java 21 LTS
if: github.repository == 'JFormDesigner/FlatLaf'
run: ./gradlew build clean -Dtoolchain=21
- name: Check with Error Prone
if: matrix.java == '11'
run: ./gradlew errorprone clean -Dtoolchain=${{ matrix.toolchain }}
- name: Build with Java 25 LTS
if: github.repository == 'JFormDesigner/FlatLaf'
run: ./gradlew build clean -Dtoolchain=25
- name: Build with Gradle
run: ./gradlew build -Dtoolchain=${{ matrix.toolchain }}
- name: Upload artifacts
# build with Java 8 for snapshot
- name: Build with Java 8
run: ./gradlew build
- name: Upload artifacts to GitHub Actions
uses: actions/upload-artifact@v4
if: matrix.java == '11'
with:
name: FlatLaf-build-artifacts
path: |
@@ -71,40 +76,15 @@ jobs:
!**/*-sources.jar
snapshot:
runs-on: ubuntu-latest
needs: build
if: |
github.event_name == 'push' &&
(github.ref == 'refs/heads/main' || startsWith( github.ref, 'refs/heads/develop-' )) &&
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 snapshot to oss.sonatype.org
run: ./gradlew publish :flatlaf-theme-editor:build -PskipFonts -Dorg.gradle.internal.publish.checksums.insecure=true -Dorg.gradle.parallel=false
- name: Publish snapshot to Sonatype Central
if: |
github.repository == 'JFormDesigner/FlatLaf' &&
github.event_name == 'push' &&
(github.ref == 'refs/heads/main' || startsWith( github.ref, 'refs/heads/develop-' ))
run: ./gradlew publish -PskipFonts -Dorg.gradle.internal.publish.checksums.insecure=true -Dorg.gradle.parallel=false
env:
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
- name: Upload theme editor
uses: sebastianpopp/ftp-action@releases/v2
with:
host: ${{ secrets.FTP_SERVER }}
user: ${{ secrets.FTP_USERNAME }}
password: ${{ secrets.FTP_PASSWORD }}
forceSsl: true
localDir: "flatlaf-theme-editor/build/libs"
remoteDir: "snapshots"
options: "--only-newer --no-recursion --verbose=1"
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
release:
@@ -118,39 +98,29 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Setup Java 11
- name: Setup Java 21
uses: actions/setup-java@v4
with:
java-version: 11
java-version: 21
distribution: temurin # pre-installed on ubuntu-latest
cache: gradle
- name: Release a new stable version to Maven Central
run: ./gradlew publish :flatlaf-demo:build :flatlaf-theme-editor:build -PskipFonts -Prelease -Dorg.gradle.parallel=false
- name: Cache Gradle
uses: ./.github/actions/cache-gradle
- name: Release a new stable version to Maven Central and build demo and theme editor
run: ./gradlew publishToSonatype closeSonatypeStagingRepository :flatlaf-demo:build :flatlaf-theme-editor:build -PskipFonts -Prelease -Dorg.gradle.parallel=false
env:
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
- name: Upload demo
uses: sebastianpopp/ftp-action@releases/v2
with:
host: ${{ secrets.FTP_SERVER }}
user: ${{ secrets.FTP_USERNAME }}
password: ${{ secrets.FTP_PASSWORD }}
forceSsl: true
localDir: "flatlaf-demo/build/libs"
remoteDir: "."
options: "--only-newer --no-recursion --verbose=1"
- name: Install lftp
run: sudo apt-get -y install lftp
- name: Upload theme editor
uses: sebastianpopp/ftp-action@releases/v2
with:
host: ${{ secrets.FTP_SERVER }}
user: ${{ secrets.FTP_USERNAME }}
password: ${{ secrets.FTP_PASSWORD }}
forceSsl: true
localDir: "flatlaf-theme-editor/build/libs"
remoteDir: "."
options: "--only-newer --no-recursion --verbose=1"
- name: Upload demo and theme editor
run: >
lftp -c "set ftp:ssl-force true;
open -u ${{ secrets.FTP_USERNAME }},${{ secrets.FTP_PASSWORD }} ${{ secrets.FTP_SERVER }};
mput flatlaf-demo/build/libs/flatlaf-demo-*.jar;
mput flatlaf-theme-editor/build/libs/flatlaf-theme-editor-*.jar"

40
.github/workflows/error-prone.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions
# https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle
name: Error Prone
on:
push:
branches:
- '*'
tags:
- '[0-9]*'
paths-ignore:
- '**.md'
- '.*'
- '**/.settings/**'
- 'flatlaf-core/svg/**'
- 'flatlaf-natives/**'
- 'flatlaf-testing/dumps/**'
- 'flatlaf-testing/misc/**'
- 'images/**'
jobs:
error-prone:
runs-on: ubuntu-latest
if: github.repository == 'JFormDesigner/FlatLaf'
steps:
- uses: actions/checkout@v4
- name: Setup Java 21
uses: actions/setup-java@v4
with:
java-version: 21
distribution: temurin # pre-installed on ubuntu-latest
- name: Cache Gradle
uses: ./.github/actions/cache-gradle
- name: Check with Error Prone
run: ./gradlew errorprone

View File

@@ -34,29 +34,31 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Setup Java 11
- name: Setup Java 21
uses: actions/setup-java@v4
with:
java-version: 11
java-version: 21
distribution: temurin # pre-installed on ubuntu-latest
cache: gradle
- name: Cache Gradle
uses: ./.github/actions/cache-gradle
- name: Build with Gradle
run: ./gradlew :flatlaf-fonts-${{ matrix.font }}:build
if: startsWith( github.ref, format( 'refs/tags/fonts/{0}-', matrix.font ) ) != true
- name: Publish snapshot to oss.sonatype.org
- name: Publish snapshot to Sonatype Central
run: ./gradlew :flatlaf-fonts-${{ matrix.font }}:publish -Dorg.gradle.internal.publish.checksums.insecure=true
env:
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
if: github.ref == 'refs/heads/main' || startsWith( github.ref, 'refs/heads/develop-' )
- name: Release a new stable version to Maven Central
run: ./gradlew :flatlaf-fonts-${{ matrix.font }}:build :flatlaf-fonts-${{ matrix.font }}:publish -Prelease
env:
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
if: startsWith( github.ref, format( 'refs/tags/fonts/{0}-', matrix.font ) )

View File

@@ -21,33 +21,111 @@ jobs:
strategy:
matrix:
os:
- windows
- macos
- ubuntu
- windows-latest
- macos-latest
- ubuntu-latest
- ubuntu-24.04-arm
runs-on: ${{ matrix.os }}-latest
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: gradle/wrapper-validation-action@v2
- uses: gradle/actions/wrapper-validation@v4
- name: Setup Java 11
- name: apt update (Linux)
if: matrix.os == 'ubuntu-latest' || matrix.os == 'ubuntu-24.04-arm'
run: sudo apt-get update
- name: install libxt-dev and libgtk-3-dev (Linux)
if: matrix.os == 'ubuntu-latest' || matrix.os == 'ubuntu-24.04-arm'
run: sudo apt-get install libxt-dev libgtk-3-dev
# - name: Download libgtk-3.so for arm64 (Linux)
# if: matrix.os == 'ubuntu-latest'
# working-directory: flatlaf-natives/flatlaf-natives-linux/lib/aarch64
# run: |
# pwd
# ls -l /usr/lib/x86_64-linux-gnu/libgtk*
# wget --no-verbose https://ports.ubuntu.com/pool/main/g/gtk%2b3.0/libgtk-3-0_3.24.18-1ubuntu1_arm64.deb
# ls -l
# ar -x libgtk-3-0_3.24.18-1ubuntu1_arm64.deb data.tar.xz
# tar -xvf data.tar.xz --wildcards --to-stdout "./usr/lib/aarch64-linux-gnu/libgtk-3.so.0.*" > libgtk-3.so
# rm libgtk-3-0_3.24.18-1ubuntu1_arm64.deb data.tar.xz
# ls -l
# - name: install g++-aarch64-linux-gnu (Linux)
# if: matrix.os == 'ubuntu-latest'
# run: sudo apt-get install g++-aarch64-linux-gnu
- name: Setup Java 21
uses: actions/setup-java@v4
with:
java-version: 11
java-version: 21
distribution: temurin
cache: gradle
- name: Cache Gradle
uses: ./.github/actions/cache-gradle
- name: Build with Gradle
# --no-daemon is necessary on Windows otherwise caching Gradle would fail with:
# tar.exe: Couldn't open ~/.gradle/caches/modules-2/modules-2.lock: Permission denied
run: ./gradlew build-natives --no-daemon
- name: Sign Windows DLLs
if: matrix.os == 'windows-latest'
uses: skymatic/code-sign-action@v3
with:
certificate: '${{ secrets.CODE_SIGN_CERT_BASE64 }}'
password: '${{ secrets.CODE_SIGN_CERT_PASSWORD }}'
certificatesha1: '${{ secrets.CODE_SIGN_CERT_SHA1 }}'
folder: 'flatlaf-core/src/main/resources/com/formdev/flatlaf/natives'
- name: Sign macOS natives
if: matrix.os == 'DISABLED--macos-latest'
env:
CERT_BASE64: ${{ secrets.CODE_SIGN_CERT_BASE64 }}
CERT_PASSWORD: ${{ secrets.CODE_SIGN_CERT_PASSWORD }}
CERT_IDENTITY: ${{ secrets.CODE_SIGN_CERT_IDENTITY }}
run: |
# https://docs.github.com/en/actions/use-cases-and-examples/deploying/installing-an-apple-certificate-on-macos-runners-for-xcode-development
# create variables
CERTIFICATE_PATH=$RUNNER_TEMP/cert.p12
KEYCHAIN_PATH=$RUNNER_TEMP/signing.keychain-db
KEYCHAIN_PASSWORD=$CERT_PASSWORD
# decode certificate
printenv CERT_BASE64 | base64 --decode > $CERTIFICATE_PATH
# create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# import certificate to keychain
security import $CERTIFICATE_PATH -P "$CERT_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
# set partition list (required for codesign)
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# add keychain to keychain search list
security list-keychains -d user -s $KEYCHAIN_PATH
# sign code
codesign --sign "$CERT_IDENTITY" --force --verbose=4 --timestamp \
flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-macos-*.dylib
codesign --display --verbose=4 flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/libflatlaf-macos-*.dylib
# cleanup
security delete-keychain $KEYCHAIN_PATH
- name: Set artifacts pattern
shell: bash
run: |
case ${{ matrix.os }} in
windows-latest) echo "artifactPattern=flatlaf-windows-*.dll" >> $GITHUB_ENV ;;
macos-latest) echo "artifactPattern=libflatlaf-macos-*.dylib" >> $GITHUB_ENV ;;
ubuntu-latest) echo "artifactPattern=libflatlaf-linux-x86_64.so" >> $GITHUB_ENV ;;
ubuntu-24.04-arm) echo "artifactPattern=libflatlaf-linux-arm64.so" >> $GITHUB_ENV ;;
esac
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: FlatLaf-natives-build-artifacts-${{ matrix.os }}
path: |
flatlaf-core/src/main/resources/com/formdev/flatlaf/natives
flatlaf-core/src/main/resources/com/formdev/flatlaf/natives/${{ env.artifactPattern }}
flatlaf-natives/flatlaf-natives-*/build

View File

@@ -21,17 +21,19 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Setup Java 11
- name: Setup Java 21
uses: actions/setup-java@v4
with:
java-version: 11
java-version: 21
distribution: temurin # pre-installed on ubuntu-latest
cache: gradle
- name: Publish PR snapshot to oss.sonatype.org
- name: Cache Gradle
uses: ./.github/actions/cache-gradle
- name: Publish PR snapshot to Sonatype Central
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 }}
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}

1
.gitignore vendored
View File

@@ -5,6 +5,7 @@ build/
.project
.settings/
.idea/
.consulo/
out/
*.iml
*.ipr

View File

@@ -1,6 +1,225 @@
FlatLaf Change Log
==================
## 3.7.1-SNAPSHOT
- ComboBox: Added UI property `ComboBox.buttonFocusedEditableBackground`. (issue
#1068)
- Dialog: Some client properties (e.g. `JRootPane.titleBarShowTitle`) did not
work before the dialog was made visible. (issue #1081)
- Popup: Fixed scrolling popup painting issue on Windows 10 when a glass pane is
visible and frame is maximized. (issue #1071)
- Slider: Styling `thumbSize` or `focusWidth` did not update slider size/layout.
(PR #1074)
- ToolBar: Grip disappeared when switching between Look and Feels. (issue #1075)
- macOS: Popups (menus and combobox lists) were not always hidden when window is
resized. (issue #1082)
- Extras:
- UI defaults inspector: Fixed NPE if color of `FlatLineBorder` is null. Also
use `FlatLineBorder` line color as cell background color in "Value" column.
(PR #1080)
- `FlatDesktop`: Avoid unnecessary logging if desktop is not supported (e.g.
on NixOS with Plasma/KDE desktop).
## 3.7
#### New features and improvements
- System File Chooser allows using **operating system file dialogs** in Java
Swing applications. (PR #988)
- Zooming API. (PR #1051)
- Icons:
- Support scaling Laf icons (checkbox, radiobutton, etc). (issue #1061)
- Scale checkbox and radiobutton icons when using
[text styles](https://www.formdev.com/flatlaf/typography/#text_styles)
`large`, `medium`, `small` and `mini`.
- TabbedPane: Added icon-only tab mode, which shows tab icons but hides tab
titles. Tab titles are used in "Show Hidden Tabs" popup menu. (set client
property `JTabbedPane.tabWidthMode` to `"iconOnly"`)
- TabbedPane: In scroll tab layout, propagate mouse wheel events to ancestors.
This allows mouse wheel scrolling if JTabbedPane is inside a JScrollPane. (PR
#1030)
#### Fixed bugs
- CheckBox and RadioButton: Fixed styling of custom icon. Also fixed focus width
(and preferred size) if using custom icon. (PR #1060)
- TabbedPane: In "Show Hidden Tabs" popup menu, do not show text "x. Tab" if tab
has icon but no title. (issue #1062)
- TextField: Fixed wrong leading/trailing icon placement if border is set to
`null`. (issue #1047)
- Extras: UI defaults inspector: Exclude inspector window from being blocked by
modal dialogs. (issue #1048)
- JideButton, JideToggleButton, JideSplitButton and JideToggleSplitButton: Paint
border in button style `TOOLBAR_STYLE` if in selected state. (issue #1045)
- IntelliJ Themes: Fixed problem when using theme instance more than once when
switching to that theme. (issue #990)
## 3.6.2
#### New features and improvements
- If using `FlatLaf.registerCustomDefaultsSource( "com.myapp.themes" )` and
named Java modules, it is no longer necessary to add `opens com.myapp.themes;`
to `module-info.java`. (issue #1026)
- Extras: Made animated theme change (class `FlatAnimatedLafChange`) smoother.
#### Fixed bugs
- Tree and List: Fixed painting of rounded drop backgrounds. (issue #1023)
- Popup: Showing tooltip in inactive window brought that window to front (made
it active) and potentially hid the previously active window. (issue #1037)
- Popup: No longer reuse popup windows for menus to avoid immediately closing
dialogs on ChromeOS. (issue #1029)
- macOS: Fixed window "flashing" when switching from a light to a dark theme (or
vice versa). Especially when using animated theme changer (see
[FlatLaf Extras](flatlaf-extras)).
#### Incompatibilities
- FlatLaf properties files are now loaded using the UTF-8 character encoding
instead of ISO 8859-1. In usual properties files you will not notice any
difference because they use only ASCII characters, but if you've put localized
(non-English) texts (e.g. German umlauts) into your properties files, you need
to convert them to UTF-8. Properties files created with the FlatLaf Theme
Editor already use UTF-8, including in older versions. (issue #1031)
## 3.6.1
- Extras: Support JSVG 2.0.0. Minimum JSVG version is now 1.6.0. (issue #997)
- FlatLaf window decorations (Windows 10/11 only): Improved diagonal window
resizing on top-left and top-right window corners. Top window resize area now
also covers iconify/maximize/close buttons. (issue #1015)
- ToggleButton: Styling `selectedForeground` did not work if `foreground` is
also styled. (issue #1017)
- JideSplitButton: Fixed updating popup when switching theme. (issue #1000)
- IntelliJ Themes: Fixed logging false errors when loading 3rd party
`.theme.json` files. (issue #990)
- Linux: Popups appeared in wrong position on multi-screen setup if primary
display is located below or right to secondary display. (see
[NetBeans issue #8532](https://github.com/apache/netbeans/issues/8532))
- macOS: Fixed popup flickering after theme change. (issue #1009)
- macOS with JetBrains Runtime: Fixed sometimes empty popups. (issue #1019)
## 3.6
#### New features and improvements
- macOS: Re-enabled rounded popup border (see PR #772) on macOS 14.4+ (was
disabled in 3.5.x).
- Increased contrast of text for better readability: (PR #972; issue #762)
- In **FlatLaf Dark**, **FlatLaf Darcula** and many dark IntelliJ themes, made
all text colors brighter.
- In **FlatLaf Light**, **FlatLaf IntelliJ** and many light IntelliJ themes,
made disabled text colors slightly darker.
- In **FlatLaf macOS Light**, made disabled text colors darker.
- In **FlatLaf macOS Dark**, made text colors of "default" button and selected
ToggleButton lighter.
- CheckBox: Support styling indeterminate state of
[tri-state check boxes](https://www.javadoc.io/doc/com.formdev/flatlaf-extras/latest/com/formdev/flatlaf/extras/components/FlatTriStateCheckBox.html).
(PR #936; issue #919)
- List: Support for alternate row highlighting. (PR #939)
- Tree: Support for alternate row highlighting. (PR #903)
- Tree: Support wide cell renderer. (issue #922)
- ScrollBar: Use rounded thumb also on Windows (as on macOS and Linux) and made
thumb slightly darker/lighter. (issue #918)
- Extras: `FlatSVGIcon` color filters now can access painting component to
implement component state based color mappings. (issue #906)
- Linux:
- Rounded iconify/maximize/close buttons if using FlatLaf window decorations.
(PR #971)
- Added `libflatlaf-linux-arm64.so` for Linux on ARM64. (issue #899)
- Use X11 window manager events to resize window, if FlatLaf window
decorations are enabled. This gives FlatLaf windows a more "native" feeling.
(issue #866)
- IntelliJ Themes:
- Updated to latest versions and fixed various issues.
- Support customizing through properties files. (issue #824)
- SwingX: Support `JXTipOfTheDay` component. (issue #980)
- Support key prefixes for Linux desktop environments (e.g. `[gnome]`, `[kde]`
or `[xfce]`) in properties files. (issue #974)
- Support custom key prefixes (e.g. `[win10]` or `[test]`) in properties files.
(issue #649)
- Support multi-prefixed keys (e.g. `[dark][gnome]TitlePane.buttonBackground`).
The value is only used if all prefixes match current platform/theme.
- Support new component border color to indicate success state (set client
property `JComponent.outline` to `success`). (PR #982, issue #945)
- Fonts: Updated **Inter** to
[v4.1](https://github.com/rsms/inter/releases/tag/v4.1).
#### Fixed bugs
- Button: Fixed background and foreground colors for `borderless` and
`toolBarButton` style default buttons (`JButton.isDefaultButton()` is `true`).
(issue #947)
- FileChooser: Improved performance when navigating to large directories with
thousands of files. (issue #953)
- PopupFactory: Fixed NPE on Windows 10 when `owner` is `null`. (issue #952)
- Popup: On Windows 10, drop shadow of heavy-weight popup was not updated if
popup moved/resized. (issue #942)
- FlatLaf window decorations:
- Minimize and maximize icons were not shown for custom scale factors less
than 100% (e.g. `-Dflatlaf.uiScale=75%`). (issue #951)
- Linux: Fixed occasional maximizing of window when single-clicking the
window's title bar. (issue #637)
- Styling: MigLayout visual padding was not updated after applying style to
Button, ComboBox, Spinner, TextField (and subclasses) and ToggleButton. (issue
#965)
- Linux: Popups (menus and combobox lists) were not hidden when window is moved,
resized, maximized, restored, iconified or switched to another window. (issue
#962)
- Fixed loading FlatLaf UI delegate classes when using FlatLaf in special
application where multiple class loaders are involved. E.g. in Eclipse plugin
or in LibreOffice extension. (issues #955 and #851)
- HTML: Fixed rendering of `<hr noshade>` in dark themes. (issue #932)
- TextComponents: `selectAllOnFocusPolicy` related changes:
- No longer select all text if selection (or caret position) was changed by
application and `selectAllOnFocusPolicy` is `once` (the default). (issue
#983)
- FormattedTextField and Spinner: `selectAllOnFocusPolicy = once` behaves now
as `always` (was `never` before), which means that all text is selected when
component gains focus. This is because of special behavior of
`JFormattedTextField` that did not allow implementation of `once`.
- Client property `JTextField.selectAllOnFocusPolicy` now also works on
(editable) `JComboBox` and on `JSpinner`.
- Added client property `JTextField.selectAllOnMouseClick` to override UI
property `TextComponent.selectAllOnMouseClick`. (issue #961)
- For `selectAllOnMouseClick = true`, clicking with the mouse into the text
field, to focus it, now always selects all text, even if
`selectAllOnFocusPolicy` is `once`.
#### Incompatibilities
- IntelliJ Themes:
- Theme prefix in `IntelliJTheme$ThemeLaf.properties` changed from
`[theme-name]` to `{theme-name}`.
- Renamed classes in package
`com.formdev.flatlaf.intellijthemes.materialthemeuilite` from `Flat<theme>`
to `FlatMT<theme>`.
- Removed `Gruvbox Dark Medium` and `Gruvbox Dark Soft` themes.
- Prefixed keys in properties files (e.g. `[dark]Button.background` or
`[win]Button.arc`) are now handled earlier than before. In previous versions,
prefixed keys always had higher priority than unprefixed keys and did always
overwrite unprefixed keys. Now prefixed keys are handled in same order as
unprefixed keys, which means that if a key is prefixed and unprefixed (e.g.
`[win]Button.arc` and `Button.arc`), the one which is last specified in
properties file is used.\
Following worked in previous versions, but now `Button.arc` is always `6`:
~~~properties
[win]Button.arc = 12
Button.arc = 6
~~~
This works in new (and old) versions:
~~~properties
Button.arc = 6
[win]Button.arc = 12
~~~
## 3.5.4
#### Fixed bugs

133
README.md
View File

@@ -35,7 +35,11 @@ Sponsors
### Current Sponsors
[![None Sponsors](images/none-sponsors.png)](https://www.formdev.com/flatlaf/sponsor/)
<a href="https://www.soptim.de/"><img src="https://www.formdev.com/flatlaf/sponsor/soptim.svg" width="200" alt="SOPTIM" title="SOPTIM - your expert in software solutions for the energy industry"></a>
&nbsp; &nbsp; &nbsp; &nbsp;
<a href="https://exocharts.com/"><img src="https://www.formdev.com/flatlaf/sponsor/Exocharts.png" width="200" alt="Exocharts" title="Exocharts - Professional Grade OrderFlow"></a>
<!-- [![None Sponsors](images/none-sponsors.png)](https://www.formdev.com/flatlaf/sponsor/) -->
[Become a Sponsor](https://www.formdev.com/flatlaf/sponsor/)
@@ -70,21 +74,27 @@ build script:
Otherwise, download `flatlaf-<version>.jar` here:
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf/badge.svg?style=flat-square&color=007ec6)](https://maven-badges.herokuapp.com/maven-central/com.formdev/flatlaf)
[![Maven Central](https://img.shields.io/maven-central/v/com.formdev/flatlaf?style=flat-square)](https://central.sonatype.com/artifact/com.formdev/flatlaf)
See also
[Native Libraries distribution](https://www.formdev.com/flatlaf/native-libraries/)
for instructions on how to redistribute FlatLaf native libraries with your
application.
- See
[Native Libraries distribution](https://www.formdev.com/flatlaf/native-libraries/)
for instructions on how to redistribute FlatLaf native libraries with your
application.
- If repackaging FlatLaf (and other) JARs into a single fat/uber JAR:
- add `Multi-Release: true` to `META-INF/MANIFEST.MF`
- keep `META-INF/versions/` and `META-INF/services/` directories
- merge content of equally named files in `META-INF/services/`
- If using obfuscation/minimizing/shrinking tools (e.g. **ProGuard** or
**Shadow**), exclude package `com.formdev.flatlaf` and all sub-packages.
### Snapshots
FlatLaf snapshot binaries are available on
[Sonatype OSSRH](https://oss.sonatype.org/content/repositories/snapshots/com/formdev/flatlaf/).
[Sonatype Central](https://central.sonatype.com/service/rest/repository/browse/maven-snapshots/com/formdev/flatlaf/).
To access the latest snapshot, change the FlatLaf version in your dependencies
to `<version>-SNAPSHOT` (e.g. `0.27-SNAPSHOT`) and add the repository
`https://oss.sonatype.org/content/repositories/snapshots/` to your build (see
to `<version>-SNAPSHOT` (e.g. `3.7-SNAPSHOT`) and add the repository
`https://central.sonatype.com/repository/maven-snapshots/` to your build (see
[Maven](https://maven.apache.org/guides/mini/guide-multiple-repositories.html)
and
[Gradle](https://docs.gradle.org/current/userguide/declaring_repositories.html#sec:declaring_custom_repository)
@@ -182,11 +192,18 @@ Applications using FlatLaf
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
- [Constellation](https://www.constellation-app.com/) - Data Visualization and
Analytics (based on NetBeans platform)
- [Kafka Visualizer](https://github.com/kumait/kafkavisualizer) - Kafka GUI
client
- ![New](images/new.svg)
[RedisFront](https://github.com/dromara/RedisFront/blob/master/README_EN.md) -
Cross-platform redis GUI
- ![New](images/new.svg)
[Zettelkasten](https://github.com/Zettelkasten-Team/Zettelkasten) - knowledge
management tool
- ![New](images/new.svg) [QStudio](https://www.timestored.com/qstudio/) - free
SQL editor
### Security
@@ -195,11 +212,9 @@ Applications using FlatLaf
- ![Hot](images/hot.svg)
[Burp Suite Professional and Community Edition](https://portswigger.net/burp/pro)
(**commercial**) - the leading software for web security testing
- ![New](images/new.svg)
[Ghidra](https://github.com/NationalSecurityAgency/ghidra) - a software
- [Ghidra](https://github.com/NationalSecurityAgency/ghidra) - a software
reverse engineering (SRE) framework
- ![New](images/new.svg) [jadx](https://github.com/skylot/jadx) - Dex to Java
decompiler
- [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
@@ -211,19 +226,26 @@ Applications using FlatLaf
- [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
- [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
- [Guiffy](https://www.guiffy.com/) (**commercial**) - advanced cross-platform
Diff/Merge
- [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
- ![New](images/new.svg) [Consulo](https://github.com/consulo/consulo) - open
source cross-platform multi-language IDE (Java, .NET, JS, etc)
- [Convertigo](https://github.com/convertigo/convertigo) - low code & no code
mobile & web platform
- ![New](images/new.svg) [EduMIPS64](https://github.com/EduMIPS64/edumips64) -
visual MIPS64 CPU simulator
- ![New](images/new.svg) [Launch4j](https://launch4j.sourceforge.net/) -
cross-platform Java executable wrapper
### Electrical
@@ -231,6 +253,11 @@ Applications using FlatLaf
designing, simulating and explaining digital circuits
- [Logisim-evolution](https://github.com/logisim-evolution/logisim-evolution) -
Digital logic design tool and simulator
- ![New](images/new.svg) [OpenPnP](https://github.com/openpnp/openpnp) - SMT
Pick and Place Hardware and Software
- ![New](images/new.svg)
[TrainControl](https://github.com/bob123456678/TrainControl) - control Marklin
/ Trix / DCC digital model train layout
- [Makelangelo Software](https://github.com/MarginallyClever/Makelangelo-software) -
for plotters, especially the wall-hanging polargraph
- [GUIslice Builder](https://github.com/ImpulseAdventure/GUIslice-Builder) - GUI
@@ -245,8 +272,10 @@ Applications using FlatLaf
- ![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
- [MediathekView](https://mediathekview.de/) - search in media libraries of
various German broadcasters
- ![New](images/new.svg) [Pixelitor](https://github.com/lbalazscs/Pixelitor) -
image editor
- [Cinecred](https://loadingbyte.com/cinecred/) - create beautiful film credit
sequences
- [tinyMediaManager](https://www.tinymediamanager.org/) (**commercial**) - a
@@ -262,19 +291,31 @@ Applications using FlatLaf
from any webnovel and lightnovel site
- [lectureStudio](https://www.lecturestudio.org/) - digitize your lectures with
ease
- ![New](images/new.svg) [Nortantis](https://jandjheydorn.com/nortantis) -
fantasy map generator and editor
### Modelling
### Modelling / Planning
- ![New](images/new.svg) [Astah](https://astah.net/) (**commercial**) - create
UML, ER Diagram, Flowchart, Data Flow Diagram, Requirement Diagram, SysML
diagrams and more
- ![New](images/new.svg) [OpenRocket](https://github.com/openrocket/openrocket) -
model-rocketry aerodynamics and trajectory simulation software
- ![New](images/new.svg)
[Warteschlangensimulator](https://github.com/A-Herzog/Warteschlangensimulator) -
discrete-event stochastic simulator
- ![New](images/new.svg) [Gephi](https://github.com/gephi/gephi) - the Open
Graph Viz Platform
- [Astah](https://astah.net/) (**commercial**) - create UML, ER Diagram,
Flowchart, Data Flow Diagram, Requirement Diagram, SysML diagrams and more
- [IGMAS+](https://www.gfz-potsdam.de/igmas) - Interactive Gravity and Magnetic
Application System
- ![New](images/new.svg) [StarPlan](https://www.progotec.de/) (**commercial**) -
die Stundenplan Software für Bildungseinrichtungen
- ![New](images/new.svg) [SSPlot](https://github.com/babaissarkar/ssplot) -
plotting utility for plotting CSV data
### Documents
- ![New](images/new.svg) [Big Faceless (BFO) PDF Viewer](https://bfo.com/)
(**commercial**) - Swing PDF Viewer
- [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**)
@@ -292,6 +333,9 @@ Applications using FlatLaf
### Business / Legal
- ![New](images/new.svg) ![Sponsor](images/sponsor.svg)
[Lisheane ERP](https://www.lisheane.ch/) (**commercial**) - backoffice
applikation
- ![Sponsor](images/sponsor.svg)
[j-lawyer](https://github.com/jlawyerorg/j-lawyer-org) - Kanzleisoftware
- ![Sponsor](images/sponsor.svg) [Jeyla Studio](https://www.jeylastudio.com/) -
@@ -308,20 +352,20 @@ Applications using FlatLaf
### 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
- [Spark](https://github.com/igniterealtime/Spark) - cross-platform IM client
optimized for businesses and organizations
- [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
- ![Sponsor](images/sponsor.svg) [BGBlitz](https://www.bgblitz.com/)
(**commercial**) - professional Backgammon
- ![New](images/new.svg) [josé](https://peteschaefer.github.io/jose/) - a
graphical chess tool
- ![New](images/new.svg) [MCreator](https://github.com/MCreator/MCreator) - make
Minecraft Java Edition mods, Minecraft Bedrock Edition Add-Ons, and data packs
- [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
@@ -333,8 +377,7 @@ Applications using FlatLaf
- [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) -
- [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
@@ -343,6 +386,8 @@ Applications using FlatLaf
easy
- [Android Tool](https://github.com/fast-geek/Android-Tool) - makes popular adb
and fastboot commands easier to use
- ![New](images/new.svg) [Termora](https://github.com/TermoraDev/termora) -
Terminal emulator and SSH client
### Miscellaneous

View File

@@ -14,8 +14,13 @@
* limitations under the License.
*/
import io.github.gradlenexus.publishplugin.CloseNexusStagingRepository
import net.ltgt.gradle.errorprone.errorprone
import org.gradle.kotlin.dsl.withType
// initialize version
group = "com.formdev"
version = property( if( hasProperty( "release" ) ) "flatlaf.releaseVersion" else "flatlaf.developmentVersion" ) as String
// for PR snapshots change version to 'PR-<pr_number>-SNAPSHOT'
@@ -23,18 +28,16 @@ val pullRequestNumber = findProperty( "github.event.pull_request.number" )
if( pullRequestNumber != null )
version = "PR-${pullRequestNumber}-SNAPSHOT"
allprojects {
// apply version to all subprojects
subprojects {
version = rootProject.version
repositories {
mavenCentral()
}
}
// check required Java version
if( JavaVersion.current() < JavaVersion.VERSION_1_8 )
throw RuntimeException( "Java 8 or later required (running ${System.getProperty( "java.version" )})" )
// initialize toolchain version (default is Java 8)
val toolchainJavaVersion: String by extra {
System.getProperty( "toolchain", "8" )
}
// log version, Gradle and Java versions
println()
@@ -42,17 +45,20 @@ println( "----------------------------------------------------------------------
println( "FlatLaf Version: ${version}" )
println( "Gradle ${gradle.gradleVersion} at ${gradle.gradleHomeDir}" )
println( "Java ${System.getProperty( "java.version" )}" )
val toolchainJavaVersion = System.getProperty( "toolchain" )
if( !toolchainJavaVersion.isNullOrEmpty() )
println( "Java toolchain ${toolchainJavaVersion}" )
println( "Java toolchain ${toolchainJavaVersion}" )
println()
plugins {
alias( libs.plugins.gradle.nexus.publish.plugin )
alias( libs.plugins.errorprone ) apply false
}
allprojects {
repositories {
mavenCentral()
}
tasks {
withType<JavaCompile>().configureEach {
sourceCompatibility = "1.8"
@@ -76,6 +82,10 @@ allprojects {
}
}
withType<AbstractArchiveTask>().configureEach {
isPreserveFileTimestamps = true
}
withType<Javadoc>().configureEach {
options {
this as StandardJavadocDocletOptions
@@ -88,6 +98,23 @@ allprojects {
links( "https://docs.oracle.com/en/java/javase/11/docs/api/" )
}
isFailOnError = false
// use Java 25 to generate javadoc
val javaToolchains = (project as ExtensionAware).extensions.getByName("javaToolchains") as JavaToolchainService
javadocTool.set( javaToolchains.javadocToolFor {
languageVersion.set( JavaLanguageVersion.of( 25 ) )
} )
}
// mark some publishing related tasks as not compatible with configuration cache
withType<Sign>().configureEach {
notCompatibleWithConfigurationCache( "not compatible" )
}
withType<PublishToMavenRepository>().configureEach {
notCompatibleWithConfigurationCache( "not compatible" )
}
withType<CloseNexusStagingRepository>().configureEach {
notCompatibleWithConfigurationCache( "not compatible" )
}
}
@@ -139,7 +166,33 @@ allprojects {
)
}
}
// Error Prone requires at lease Java 11
val java = (project as ExtensionAware).extensions.getByName("java") as JavaPluginExtension
val javaToolchains = (project as ExtensionAware).extensions.getByName("javaToolchains") as JavaToolchainService
if( java.toolchain.languageVersion.get().asInt() < 11 ) {
javaCompiler.set( javaToolchains.compilerFor {
languageVersion.set( JavaLanguageVersion.of( 11 ) )
} )
}
}
}
}
}
nexusPublishing {
repositories {
sonatype {
// see https://central.sonatype.org/publish/publish-portal-ossrh-staging-api/
nexusUrl = uri( "https://ossrh-staging-api.central.sonatype.com/service/local/" )
snapshotRepositoryUrl = uri( "https://central.sonatype.com/repository/maven-snapshots/" )
// get from gradle.properties
val sonatypeUsername: String? by project
val sonatypePassword: String? by project
username = System.getenv( "SONATYPE_USERNAME" ) ?: sonatypeUsername
password = System.getenv( "SONATYPE_PASSWORD" ) ?: sonatypePassword
}
}
}

View File

@@ -14,6 +14,8 @@
* limitations under the License.
*/
import org.gradle.kotlin.dsl.support.serviceOf
plugins {
`cpp-library`
}
@@ -37,9 +39,14 @@ tasks {
doFirst {
println( "Used Tool Chain:" )
println( " - ${toolChain.get()}" )
println( "Available Tool Chains:" )
toolChains.forEach {
println( " - $it" )
}
if( !project.gradle.serviceOf<BuildFeatures>().configurationCache.active.get() ) {
doFirst {
println( "Available Tool Chains:" )
toolChains.forEach {
println( " - $it" )
}
}
}
}

View File

@@ -18,7 +18,14 @@ plugins {
java
}
if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) {
// for Eclipse IDE project import, exclude if:
// - plugin "eclipse" is applied; e.g. if running in Eclipse IDE with buildship plugin
// - no taskNames specified at command line; e.g. if buildship synchronizes projects
val exclude =
rootProject.plugins.hasPlugin( "eclipse" ) &&
gradle.startParameter.taskNames.isEmpty()
if( !exclude ) {
sourceSets {
create( "java9" ) {
java {
@@ -35,6 +42,13 @@ if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) {
named<JavaCompile>( "compileJava9Java" ) {
sourceCompatibility = "9"
targetCompatibility = "9"
// if global toolchain is Java 8, then use Java 11 to build
if( java.toolchain.languageVersion.get().asInt() < 9 ) {
javaCompiler.set( javaToolchains.compilerFor {
languageVersion.set( JavaLanguageVersion.of( 11 ) )
} )
}
}
jar {

View File

@@ -29,7 +29,14 @@ plugins {
java
}
if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) {
// for Eclipse IDE project import, exclude if:
// - plugin "eclipse" is applied; e.g. if running in Eclipse IDE with buildship plugin
// - no taskNames specified at command line; e.g. if buildship synchronizes projects
val exclude =
rootProject.plugins.hasPlugin( "eclipse" ) &&
gradle.startParameter.taskNames.isEmpty()
if( !exclude ) {
sourceSets {
create( "module-info" ) {
java {
@@ -38,10 +45,11 @@ if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) {
setSrcDirs( listOf( "src/main/module-info", "src/main/java", "src/main/java9" ) )
// exclude Java 8 source file if an equally named Java 9+ source file exists
val projectDir = projectDir // necessary for configuration cache
exclude {
if( it.isDirectory )
return@exclude false
val java9file = file( "${projectDir}/src/main/java9/${it.path}" )
val java9file = File( "${projectDir}/src/main/java9/${it.path}" )
java9file.exists() && java9file != it.file
}
}
@@ -58,6 +66,13 @@ if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) {
options.compilerArgs.add( "--module-path" )
options.compilerArgs.add( configurations.runtimeClasspath.get().asPath
+ File.pathSeparator + configurations.compileClasspath.get().asPath )
// if global toolchain is Java 8, then use Java 11 to build
if( java.toolchain.languageVersion.get().asInt() < 9 ) {
javaCompiler.set( javaToolchains.compilerFor {
languageVersion.set( JavaLanguageVersion.of( 11 ) )
} )
}
}
jar {

View File

@@ -15,7 +15,7 @@
*/
open class NativeArtifact( val fileName: String, val classifier: String, val type: String ) {}
open class NativeArtifact( val fileName: String, val classifier: String, val extension: String ) {}
open class PublishExtension {
var artifactId: String? = null
@@ -77,33 +77,14 @@ publishing {
afterEvaluate {
extension.nativeArtifacts?.forEach {
artifact( artifacts.add( "archives", file( it.fileName ) ) {
artifact( file( it.fileName ) ) {
classifier = it.classifier
type = it.type
} )
extension = it.extension
}
}
}
}
}
repositories {
maven {
name = "OSSRH"
val releasesRepoUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
val snapshotsRepoUrl = "https://oss.sonatype.org/content/repositories/snapshots/"
url = uri( if( rootProject.hasProperty( "release" ) ) releasesRepoUrl else snapshotsRepoUrl )
credentials {
// get from gradle.properties
val ossrhUsername: String? by project
val ossrhPassword: String? by project
username = System.getenv( "OSSRH_USERNAME" ) ?: ossrhUsername
password = System.getenv( "OSSRH_PASSWORD" ) ?: ossrhPassword
}
}
}
}
signing {
@@ -123,10 +104,22 @@ 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'." )
tasks {
// check whether parallel build is enabled
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'." )
}
}
register( "publishToSonatypeAndCloseStagingRepo" ) {
group = "publishing"
description = "Publish to Sonatype Maven Central and close staging repository"
dependsOn(
"publishToSonatype",
":closeSonatypeStagingRepository"
)
}
}

View File

@@ -18,9 +18,8 @@ plugins {
java
}
val toolchainJavaVersion = System.getProperty( "toolchain" )
if( !toolchainJavaVersion.isNullOrEmpty() ) {
java.toolchain {
languageVersion = JavaLanguageVersion.of( toolchainJavaVersion )
}
val toolchainJavaVersion: String by rootProject.extra
java.toolchain {
languageVersion = JavaLanguageVersion.of( toolchainJavaVersion )
}

View File

@@ -1,4 +1,7 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.source=1.8
org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false
org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false

View File

@@ -90,7 +90,7 @@ tasks {
useJUnitPlatform()
testLogging.exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
if( JavaVersion.current() >= JavaVersion.VERSION_1_9 )
if( java.toolchain.languageVersion.get().asInt() >= 9 )
jvmArgs( listOf( "--add-opens", "java.desktop/javax.swing.plaf.basic=ALL-UNNAMED" ) )
}
@@ -98,21 +98,31 @@ tasks {
group = "verification"
dependsOn( "jar" )
// necessary for configuration cache
val classpath = sigtest.asPath
val signatureFile = "${project.name}-sigtest.txt"
val jarPath = jar.get().outputs.files.asPath
val version = version
doLast {
ant.withGroovyBuilder {
"taskdef"(
"name" to "sigtest",
"classname" to "org.netbeans.apitest.Sigtest",
"classpath" to sigtest.asPath )
"classpath" to classpath )
"sigtest"(
"action" to "generate",
"fileName" to "${project.name}-sigtest.txt",
"classpath" to jar.get().outputs.files.asPath,
"fileName" to signatureFile,
"classpath" to jarPath,
"packages" to "com.formdev.flatlaf,com.formdev.flatlaf.themes,com.formdev.flatlaf.util",
"version" to version,
"release" to "1.8", // Java version
"failonerror" to "true" )
"fixcrlf"(
"file" to signatureFile,
"eol" to "lf" )
}
}
}
@@ -121,17 +131,23 @@ tasks {
group = "verification"
dependsOn( "jar" )
// necessary for configuration cache
val classpath = sigtest.asPath
val signatureFile = "${project.name}-sigtest.txt"
val jarPath = jar.get().outputs.files.asPath
val version = version
doLast {
ant.withGroovyBuilder {
"taskdef"(
"name" to "sigtest",
"classname" to "org.netbeans.apitest.Sigtest",
"classpath" to sigtest.asPath )
"classpath" to classpath )
"sigtest"(
"action" to "check",
"fileName" to "${project.name}-sigtest.txt",
"classpath" to jar.get().outputs.files.asPath,
"fileName" to signatureFile,
"classpath" to jarPath,
"packages" to "com.formdev.flatlaf,com.formdev.flatlaf.util",
"version" to version,
"release" to "1.8", // Java version
@@ -156,5 +172,6 @@ flatlafPublish {
NativeArtifact( "${natives}/libflatlaf-macos-arm64.dylib", "macos-arm64", "dylib" ),
NativeArtifact( "${natives}/libflatlaf-macos-x86_64.dylib", "macos-x86_64", "dylib" ),
NativeArtifact( "${natives}/libflatlaf-linux-x86_64.so", "linux-x86_64", "so" ),
NativeArtifact( "${natives}/libflatlaf-linux-arm64.so", "linux-arm64", "so" ),
)
}

View File

@@ -1,5 +1,5 @@
#Signature file v4.1
#Version 3.5.2
#Version 3.7
CLSS public abstract interface com.formdev.flatlaf.FlatClientProperties
fld public final static java.lang.String BUTTON_TYPE = "JButton.buttonType"
@@ -24,6 +24,7 @@ fld public final static java.lang.String MINIMUM_HEIGHT = "JComponent.minimumHei
fld public final static java.lang.String MINIMUM_WIDTH = "JComponent.minimumWidth"
fld public final static java.lang.String OUTLINE = "JComponent.outline"
fld public final static java.lang.String OUTLINE_ERROR = "error"
fld public final static java.lang.String OUTLINE_SUCCESS = "success"
fld public final static java.lang.String OUTLINE_WARNING = "warning"
fld public final static java.lang.String PLACEHOLDER_TEXT = "JTextField.placeholderText"
fld public final static java.lang.String POPUP_BORDER_CORNER_RADIUS = "Popup.borderCornerRadius"
@@ -40,6 +41,7 @@ fld public final static java.lang.String SELECT_ALL_ON_FOCUS_POLICY = "JTextFiel
fld public final static java.lang.String SELECT_ALL_ON_FOCUS_POLICY_ALWAYS = "always"
fld public final static java.lang.String SELECT_ALL_ON_FOCUS_POLICY_NEVER = "never"
fld public final static java.lang.String SELECT_ALL_ON_FOCUS_POLICY_ONCE = "once"
fld public final static java.lang.String SELECT_ALL_ON_MOUSE_CLICK = "JTextField.selectAllOnMouseClick"
fld public final static java.lang.String SPLIT_PANE_EXPANDABLE_SIDE = "JSplitPane.expandableSide"
fld public final static java.lang.String SPLIT_PANE_EXPANDABLE_SIDE_LEFT = "left"
fld public final static java.lang.String SPLIT_PANE_EXPANDABLE_SIDE_RIGHT = "right"
@@ -85,6 +87,7 @@ fld public final static java.lang.String TABBED_PANE_TAB_TYPE_UNDERLINED = "unde
fld public final static java.lang.String TABBED_PANE_TAB_WIDTH_MODE = "JTabbedPane.tabWidthMode"
fld public final static java.lang.String TABBED_PANE_TAB_WIDTH_MODE_COMPACT = "compact"
fld public final static java.lang.String TABBED_PANE_TAB_WIDTH_MODE_EQUAL = "equal"
fld public final static java.lang.String TABBED_PANE_TAB_WIDTH_MODE_ICON_ONLY = "iconOnly"
fld public final static java.lang.String TABBED_PANE_TAB_WIDTH_MODE_PREFERRED = "preferred"
fld public final static java.lang.String TABBED_PANE_TRAILING_COMPONENT = "JTabbedPane.trailingComponent"
fld public final static java.lang.String TAB_BUTTON_SELECTED_BACKGROUND = "JToggleButton.tab.selectedBackground"
@@ -107,6 +110,7 @@ fld public final static java.lang.String TITLE_BAR_SHOW_ICONIFFY = "JRootPane.ti
fld public final static java.lang.String TITLE_BAR_SHOW_MAXIMIZE = "JRootPane.titleBarShowMaximize"
fld public final static java.lang.String TITLE_BAR_SHOW_TITLE = "JRootPane.titleBarShowTitle"
fld public final static java.lang.String TREE_PAINT_SELECTION = "JTree.paintSelection"
fld public final static java.lang.String TREE_WIDE_CELL_RENDERER = "JTree.wideCellRenderer"
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"
@@ -220,8 +224,11 @@ meth public static java.lang.String getPreferredFontFamily()
meth public static java.lang.String getPreferredLightFontFamily()
meth public static java.lang.String getPreferredMonospacedFontFamily()
meth public static java.lang.String getPreferredSemiboldFontFamily()
meth public static java.lang.String getUIKeyLightOrDarkPrefix(boolean)
meth public static java.util.Map<java.lang.String,java.lang.Class<?>> getStyleableInfos(javax.swing.JComponent)
meth public static java.util.Map<java.lang.String,java.lang.String> getGlobalExtraDefaults()
meth public static java.util.Set<java.lang.String> getUIKeyPlatformPrefixes()
meth public static java.util.Set<java.lang.String> getUIKeySpecialPrefixes()
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()
@@ -255,7 +262,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,globalExtraDefaults,mnemonicHandler,oldPopupFactory,postInitialization,preferredFontFamily,preferredLightFontFamily,preferredMonospacedFontFamily,preferredSemiboldFontFamily,subMenuUsabilityHelperInstalled,systemColorGetter,uiDefaultsGetters,updateUIPending
hfds DESKTOPFONTHINTS,aquaLoaded,customDefaultsSources,desktopPropertyListener,desktopPropertyName,desktopPropertyName2,extraDefaults,globalExtraDefaults,linuxPopupMenuCanceler,mnemonicHandler,oldPopupFactory,postInitialization,preferredFontFamily,preferredLightFontFamily,preferredMonospacedFontFamily,preferredSemiboldFontFamily,subMenuUsabilityHelperInstalled,systemColorGetter,uiDefaultsGetters,uiKeyPlatformPrefixes,uiKeySpecialPrefixes,updateUIPending
hcls ActiveFont,FlatUIDefaults,ImageIconUIResource
CLSS public abstract interface static com.formdev.flatlaf.FlatLaf$DisabledIconProvider
@@ -291,6 +298,7 @@ CLSS public abstract interface com.formdev.flatlaf.FlatSystemProperties
fld public final static java.lang.String ANIMATION = "flatlaf.animation"
fld public final static java.lang.String MENUBAR_EMBEDDED = "flatlaf.menuBarEmbedded"
fld public final static java.lang.String NATIVE_LIBRARY_PATH = "flatlaf.nativeLibraryPath"
fld public final static java.lang.String REUSE_VISIBLE_POPUP_WINDOW = "flatlaf.reuseVisiblePopupWindow"
fld public final static java.lang.String UI_SCALE = "flatlaf.uiScale"
fld public final static java.lang.String UI_SCALE_ALLOW_SCALE_DOWN = "flatlaf.uiScale.allowScaleDown"
fld public final static java.lang.String UI_SCALE_ENABLED = "flatlaf.uiScale.enabled"
@@ -300,6 +308,7 @@ fld public final static java.lang.String USE_JETBRAINS_CUSTOM_DECORATIONS = "fla
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_SYSTEM_FILE_CHOOSER = "flatlaf.useSystemFileChooser"
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"
@@ -318,7 +327,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,uiKeyDoNotOverride,uiKeyExcludes,uiKeyInverseMapping,uiKeyMapping
hfds checkboxDuplicateColors,checkboxKeyMapping,jsonColors,jsonIcons,jsonUI,namedColors,uiKeyCopying,uiKeyDoNotOverride,uiKeyExcludesContains,uiKeyExcludesStartsWith,uiKeyInverseMapping,uiKeyMapping
CLSS public static com.formdev.flatlaf.IntelliJTheme$ThemeLaf
outer com.formdev.flatlaf.IntelliJTheme
@@ -413,6 +422,7 @@ innr public static Fade
innr public static HSLChange
innr public static HSLIncreaseDecrease
innr public static Mix
innr public static Mix2
meth public !varargs static java.awt.Color applyFunctions(java.awt.Color,com.formdev.flatlaf.util.ColorFunctions$ColorFunction[])
meth public static float clamp(float)
meth public static float luma(java.awt.Color)
@@ -474,6 +484,16 @@ meth public java.lang.String toString()
meth public void apply(float[])
supr java.lang.Object
CLSS public static com.formdev.flatlaf.util.ColorFunctions$Mix2
outer com.formdev.flatlaf.util.ColorFunctions
cons public init(java.awt.Color,float)
fld public final float weight
fld public final java.awt.Color color1
intf com.formdev.flatlaf.util.ColorFunctions$ColorFunction
meth public java.lang.String toString()
meth public void apply(float[])
supr java.lang.Object
CLSS public com.formdev.flatlaf.util.CubicBezierEasing
cons public init(float,float,float,float)
fld public final static com.formdev.flatlaf.util.CubicBezierEasing EASE
@@ -752,9 +772,116 @@ cons public init()
meth public static <%0 extends java.awt.Component> {%%0} getComponentByName(java.awt.Container,java.lang.String)
supr java.lang.Object
CLSS public com.formdev.flatlaf.util.SystemFileChooser
cons public init()
cons public init(java.io.File)
cons public init(java.lang.String)
fld public final static int APPROVE_OPTION = 0
fld public final static int CANCEL_OPTION = 1
fld public final static int DIRECTORIES_ONLY = 1
fld public final static int FILES_ONLY = 0
fld public final static int OPEN_DIALOG = 0
fld public final static int SAVE_DIALOG = 1
fld public final static java.lang.String LINUX_OPTIONS_CLEAR = "linux.optionsClear"
fld public final static java.lang.String LINUX_OPTIONS_SET = "linux.optionsSet"
fld public final static java.lang.String MAC_FILTER_FIELD_LABEL = "mac.filterFieldLabel"
fld public final static java.lang.String MAC_MESSAGE = "mac.message"
fld public final static java.lang.String MAC_NAME_FIELD_LABEL = "mac.nameFieldLabel"
fld public final static java.lang.String MAC_OPTIONS_CLEAR = "mac.optionsClear"
fld public final static java.lang.String MAC_OPTIONS_SET = "mac.optionsSet"
fld public final static java.lang.String MAC_TREATS_FILE_PACKAGES_AS_DIRECTORIES = "mac.treatsFilePackagesAsDirectories"
fld public final static java.lang.String WINDOWS_DEFAULT_EXTENSION = "windows.defaultExtension"
fld public final static java.lang.String WINDOWS_DEFAULT_FOLDER = "windows.defaultFolder"
fld public final static java.lang.String WINDOWS_FILE_NAME_LABEL = "windows.fileNameLabel"
fld public final static java.lang.String WINDOWS_OPTIONS_CLEAR = "windows.optionsClear"
fld public final static java.lang.String WINDOWS_OPTIONS_SET = "windows.optionsSet"
innr public abstract interface static ApproveCallback
innr public abstract interface static StateStore
innr public abstract static ApproveContext
innr public abstract static FileFilter
innr public final static FileNameExtensionFilter
meth public <%0 extends java.lang.Object> {%%0} getPlatformProperty(java.lang.String)
meth public boolean isAcceptAllFileFilterUsed()
meth public boolean isDirectorySelectionEnabled()
meth public boolean isFileHidingEnabled()
meth public boolean isFileSelectionEnabled()
meth public boolean isMultiSelectionEnabled()
meth public boolean removeChoosableFileFilter(com.formdev.flatlaf.util.SystemFileChooser$FileFilter)
meth public com.formdev.flatlaf.util.SystemFileChooser$ApproveCallback getApproveCallback()
meth public com.formdev.flatlaf.util.SystemFileChooser$FileFilter getAcceptAllFileFilter()
meth public com.formdev.flatlaf.util.SystemFileChooser$FileFilter getFileFilter()
meth public com.formdev.flatlaf.util.SystemFileChooser$FileFilter[] getChoosableFileFilters()
meth public int getApproveButtonMnemonic()
meth public int getDialogType()
meth public int getFileSelectionMode()
meth public int showDialog(java.awt.Component,java.lang.String)
meth public int showOpenDialog(java.awt.Component)
meth public int showSaveDialog(java.awt.Component)
meth public java.io.File getCurrentDirectory()
meth public java.io.File getSelectedFile()
meth public java.io.File[] getSelectedFiles()
meth public java.lang.String getApproveButtonText()
meth public java.lang.String getDialogTitle()
meth public java.lang.String getStateStoreID()
meth public static com.formdev.flatlaf.util.SystemFileChooser$StateStore getStateStore()
meth public static void setStateStore(com.formdev.flatlaf.util.SystemFileChooser$StateStore)
meth public void addChoosableFileFilter(com.formdev.flatlaf.util.SystemFileChooser$FileFilter)
meth public void putPlatformProperty(java.lang.String,java.lang.Object)
meth public void resetChoosableFileFilters()
meth public void setAcceptAllFileFilterUsed(boolean)
meth public void setApproveButtonMnemonic(char)
meth public void setApproveButtonMnemonic(int)
meth public void setApproveButtonText(java.lang.String)
meth public void setApproveCallback(com.formdev.flatlaf.util.SystemFileChooser$ApproveCallback)
meth public void setCurrentDirectory(java.io.File)
meth public void setDialogTitle(java.lang.String)
meth public void setDialogType(int)
meth public void setFileFilter(com.formdev.flatlaf.util.SystemFileChooser$FileFilter)
meth public void setFileHidingEnabled(boolean)
meth public void setFileSelectionMode(int)
meth public void setMultiSelectionEnabled(boolean)
meth public void setSelectedFile(java.io.File)
meth public void setSelectedFiles(java.io.File[])
meth public void setStateStoreID(java.lang.String)
supr java.lang.Object
hfds acceptAllFileFilter,approveButtonMnemonic,approveButtonText,approveCallback,approveResult,currentDirectory,dialogTitle,dialogType,fileFilter,fileSelectionMode,filters,inMemoryStateStore,keepAcceptAllAtEnd,multiSelection,platformProperties,selectedFile,selectedFiles,stateStore,stateStoreID,useAcceptAllFileFilter,useFileHiding
hcls AcceptAllFileFilter,FileChooserProvider,LinuxFileChooserProvider,MacFileChooserProvider,SwingFileChooserProvider,SystemFileChooserProvider,WindowsFileChooserProvider
CLSS public abstract interface static com.formdev.flatlaf.util.SystemFileChooser$ApproveCallback
outer com.formdev.flatlaf.util.SystemFileChooser
meth public abstract int approve(java.io.File[],com.formdev.flatlaf.util.SystemFileChooser$ApproveContext)
CLSS public abstract static com.formdev.flatlaf.util.SystemFileChooser$ApproveContext
outer com.formdev.flatlaf.util.SystemFileChooser
cons public init()
meth public abstract !varargs int showMessageDialog(int,java.lang.String,java.lang.String,int,java.lang.String[])
supr java.lang.Object
CLSS public abstract static com.formdev.flatlaf.util.SystemFileChooser$FileFilter
outer com.formdev.flatlaf.util.SystemFileChooser
cons public init()
meth public abstract java.lang.String getDescription()
supr java.lang.Object
CLSS public final static com.formdev.flatlaf.util.SystemFileChooser$FileNameExtensionFilter
outer com.formdev.flatlaf.util.SystemFileChooser
cons public !varargs init(java.lang.String,java.lang.String[])
meth public java.lang.String getDescription()
meth public java.lang.String toString()
meth public java.lang.String[] getExtensions()
supr com.formdev.flatlaf.util.SystemFileChooser$FileFilter
hfds description,extensions
CLSS public abstract interface static com.formdev.flatlaf.util.SystemFileChooser$StateStore
outer com.formdev.flatlaf.util.SystemFileChooser
fld public final static java.lang.String KEY_CURRENT_DIRECTORY = "currentDirectory"
meth public abstract java.lang.String get(java.lang.String,java.lang.String)
meth public abstract void put(java.lang.String,java.lang.String)
CLSS public com.formdev.flatlaf.util.SystemInfo
cons public init()
fld public final static boolean isAARCH64
fld public final static boolean isGNOME
fld public final static boolean isJava_11_orLater
fld public final static boolean isJava_12_orLater
fld public final static boolean isJava_15_orLater
@@ -771,6 +898,7 @@ fld public final static boolean isMacOS_10_11_ElCapitan_orLater
fld public final static boolean isMacOS_10_14_Mojave_orLater
fld public final static boolean isMacOS_10_15_Catalina_orLater
fld public final static boolean isProjector
fld public final static boolean isUnknownOS
fld public final static boolean isWebswing
fld public final static boolean isWinPE
fld public final static boolean isWindows
@@ -786,13 +914,21 @@ supr java.lang.Object
CLSS public com.formdev.flatlaf.util.UIScale
cons public init()
fld public final static java.lang.String PROP_USER_SCALE_FACTOR = "userScaleFactor"
fld public final static java.lang.String PROP_ZOOM_FACTOR = "zoomFactor"
meth public static boolean isSystemScalingEnabled()
meth public static boolean setZoomFactor(float)
meth public static boolean zoomIn()
meth public static boolean zoomOut()
meth public static boolean zoomReset()
meth public static double getSystemScaleFactor(java.awt.Graphics2D)
meth public static double getSystemScaleFactor(java.awt.GraphicsConfiguration)
meth public static float computeFontScaleFactor(java.awt.Font)
meth public static float getUserScaleFactor()
meth public static float getZoomFactor()
meth public static float scale(float)
meth public static float unscale(float)
meth public static float[] getSupportedZoomFactors()
meth public static int scale(int)
meth public static int scale2(int)
meth public static int unscale(int)
@@ -802,8 +938,9 @@ meth public static javax.swing.plaf.FontUIResource applyCustomScaleFactor(javax.
meth public static void addPropertyChangeListener(java.beans.PropertyChangeListener)
meth public static void removePropertyChangeListener(java.beans.PropertyChangeListener)
meth public static void scaleGraphics(java.awt.Graphics2D)
meth public static void setSupportedZoomFactors(float[])
supr java.lang.Object
hfds DEBUG,changeSupport,initialized,jreHiDPI,scaleFactor
hfds DEBUG,changeSupport,ignoreFontChange,inUnitTests,initialized,jreHiDPI,listenerInitialized,scaleFactor,supportedZoomFactors,unzoomedScaleFactor,zoomFactor
CLSS public java.awt.Color
cons public init(float,float,float)

View File

@@ -21,6 +21,8 @@ import java.awt.IllegalComponentStateException;
import java.awt.Window;
import java.util.Objects;
import javax.swing.JComponent;
import javax.swing.JFormattedTextField;
import javax.swing.JSpinner;
import javax.swing.SwingConstants;
/**
@@ -220,6 +222,7 @@ public interface FlatClientProperties
* <strong>Allowed Values</strong>
* {@link #OUTLINE_ERROR},
* {@link #OUTLINE_WARNING},
* {@link #OUTLINE_SUCCESS},
* any color (type {@link java.awt.Color}) or
* an array of two colors (type {@link java.awt.Color}[2]) where the first color
* is for focused state and the second for unfocused state
@@ -240,6 +243,14 @@ public interface FlatClientProperties
*/
String OUTLINE_WARNING = "warning";
/**
* Paint the component border in another color (usually greenish) to indicate a success.
*
* @see #OUTLINE
* @since 3.6
*/
String OUTLINE_SUCCESS = "success";
/**
* Specifies a callback that is invoked to check whether a component is permanent focus owner.
* Used to paint focus indicators.
@@ -1075,8 +1086,9 @@ public interface FlatClientProperties
* <strong>Value type</strong> {@link java.lang.String}<br>
* <strong>Allowed Values</strong>
* {@link #TABBED_PANE_TAB_WIDTH_MODE_PREFERRED} (default),
* {@link #TABBED_PANE_TAB_WIDTH_MODE_EQUAL} or
* {@link #TABBED_PANE_TAB_WIDTH_MODE_COMPACT}
* {@link #TABBED_PANE_TAB_WIDTH_MODE_EQUAL},
* {@link #TABBED_PANE_TAB_WIDTH_MODE_COMPACT} or
* {@link #TABBED_PANE_TAB_WIDTH_MODE_ICON_ONLY}
*/
String TABBED_PANE_TAB_WIDTH_MODE = "JTabbedPane.tabWidthMode";
@@ -1102,6 +1114,14 @@ public interface FlatClientProperties
*/
String TABBED_PANE_TAB_WIDTH_MODE_COMPACT = "compact";
/**
* All tabs are smaller because they show only the tab icon, but no tab title.
*
* @see #TABBED_PANE_TAB_WIDTH_MODE
* @since 3.7
*/
String TABBED_PANE_TAB_WIDTH_MODE_ICON_ONLY = "iconOnly";
/**
* Specifies the tab icon placement (relative to tab title).
* <p>
@@ -1200,12 +1220,15 @@ public interface FlatClientProperties
/**
* Specifies whether all text is selected when the text component gains focus.
* <p>
* <strong>Component</strong> {@link javax.swing.JTextField} (and subclasses)<br>
* <strong>Components</strong> {@link javax.swing.text.JTextComponent} (and subclasses),
* {@link javax.swing.JComboBox} (since 3.6) and {@link javax.swing.JSpinner} (since 3.6)<br>
* <strong>Value type</strong> {@link java.lang.String}<br>
* <strong>Allowed Values</strong>
* {@link #SELECT_ALL_ON_FOCUS_POLICY_NEVER},
* {@link #SELECT_ALL_ON_FOCUS_POLICY_ONCE} (default) or
* {@link #SELECT_ALL_ON_FOCUS_POLICY_ALWAYS}
*
* @see #SELECT_ALL_ON_MOUSE_CLICK
*/
String SELECT_ALL_ON_FOCUS_POLICY = "JTextField.selectAllOnFocusPolicy";
@@ -1220,6 +1243,12 @@ public interface FlatClientProperties
* Select all text when the text component gains focus for the first time
* and selection was not modified (is at end of text).
* This is the default.
* <p>
* <b>Limitations:</b>
* For {@link JFormattedTextField} and {@link JSpinner} this behaves
* as {@link #SELECT_ALL_ON_FOCUS_POLICY_ALWAYS}.
* This is because of special behavior of {@link JFormattedTextField}
* that did not allow implementation of {@code "once"}.
*
* @see #SELECT_ALL_ON_FOCUS_POLICY
*/
@@ -1232,6 +1261,19 @@ public interface FlatClientProperties
*/
String SELECT_ALL_ON_FOCUS_POLICY_ALWAYS = "always";
/**
* Specifies whether all text is selected when when clicking with the mouse
* into the text field (and if "select all on focus" policy is enabled).
* <p>
* <strong>Components</strong> {@link javax.swing.text.JTextComponent} (and subclasses),
* {@link javax.swing.JComboBox} and {@link javax.swing.JSpinner}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*
* @see #SELECT_ALL_ON_FOCUS_POLICY
* @since 3.6
*/
String SELECT_ALL_ON_MOUSE_CLICK = "JTextField.selectAllOnMouseClick";
/**
* Placeholder text that is only painted if the text field is empty.
* <p>
@@ -1410,13 +1452,23 @@ public interface FlatClientProperties
//---- JTree --------------------------------------------------------------
/**
* Override if a tree shows a wide selection. Default is {@code true}.
* Specifies whether tree shows a wide selection. Default is {@code true}.
* <p>
* <strong>Component</strong> {@link javax.swing.JTree}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*/
String TREE_WIDE_SELECTION = "JTree.wideSelection";
/**
* Specifies whether tree uses a wide cell renderer. Default is {@code false}.
* <p>
* <strong>Component</strong> {@link javax.swing.JTree}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*
* @since 3.6
*/
String TREE_WIDE_CELL_RENDERER = "JTree.wideCellRenderer";
/**
* Specifies whether tree item selection is painted. Default is {@code true}.
* If set to {@code false}, then the tree cell renderer is responsible for painting selection.

View File

@@ -37,6 +37,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -44,6 +45,7 @@ import java.util.MissingResourceException;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntUnaryOperator;
@@ -100,6 +102,8 @@ public abstract class FlatLaf
private static Map<String, String> globalExtraDefaults;
private Map<String, String> extraDefaults;
private static Function<String, Color> systemColorGetter;
private static Set<String> uiKeyPlatformPrefixes;
private static Set<String> uiKeySpecialPrefixes;
private String desktopPropertyName;
private String desktopPropertyName2;
@@ -111,6 +115,7 @@ public abstract class FlatLaf
private PopupFactory oldPopupFactory;
private MnemonicHandler mnemonicHandler;
private boolean subMenuUsabilityHelperInstalled;
private LinuxPopupMenuCanceler linuxPopupMenuCanceler;
private Consumer<UIDefaults> postInitialization;
private List<Function<Object, Object>> uiDefaultsGetters;
@@ -305,6 +310,10 @@ public abstract class FlatLaf
// install submenu usability helper
subMenuUsabilityHelperInstalled = SubMenuUsabilityHelper.install();
// install Linux/macOS popup menu canceler
if( SystemInfo.isLinux || SystemInfo.isMacOS )
linuxPopupMenuCanceler = new LinuxPopupMenuCanceler();
// listen to desktop property changes to update UI if system font or scaling changes
if( SystemInfo.isWindows ) {
// Windows 10 allows increasing font size independent of scaling:
@@ -359,6 +368,22 @@ public abstract class FlatLaf
String.format( "a, address { color: #%06x; }", linkColor.getRGB() & 0xffffff ) );
}
};
// Initialize UIScale user scale factor immediately after FlatLaf was activated,
// which is necessary to ensure that UIScale.setZoomFactor(float)
// scales FlatLaf defaultDont correctly even if UIScale.scale() was not yet used.
// In other words: Without this, UIScale.setZoomFactor(float) would
// not work correctly if invoked between FlatLaf.setup() and crating UI.
PropertyChangeListener listener = new PropertyChangeListener() {
@Override
public void propertyChange( PropertyChangeEvent e ) {
if( "lookAndFeel".equals( e.getPropertyName() ) ) {
UIManager.removePropertyChangeListener( this );
UIScale.getUserScaleFactor();
}
}
};
UIManager.addPropertyChangeListener( listener );
}
@Override
@@ -397,6 +422,12 @@ public abstract class FlatLaf
subMenuUsabilityHelperInstalled = false;
}
// uninstall Linux popup menu canceler
if( linuxPopupMenuCanceler != null ) {
linuxPopupMenuCanceler.uninstall();
linuxPopupMenuCanceler = null;
}
// restore default link color
new HTMLEditorKit().getStyleSheet().addRule( "a, address { color: blue; }" );
postInitialization = null;
@@ -510,10 +541,10 @@ public abstract class FlatLaf
// load defaults from properties
List<Class<?>> lafClassesForDefaultsLoading = getLafClassesForDefaultsLoading();
if( lafClassesForDefaultsLoading != null )
UIDefaultsLoader.loadDefaultsFromProperties( lafClassesForDefaultsLoading, addons, getAdditionalDefaults(), isDark(), defaults );
else
UIDefaultsLoader.loadDefaultsFromProperties( getClass(), addons, getAdditionalDefaults(), isDark(), defaults );
if( lafClassesForDefaultsLoading == null )
lafClassesForDefaultsLoading = UIDefaultsLoader.getLafClassesForDefaultsLoading( getClass() );
UIDefaultsLoader.loadDefaultsFromProperties( lafClassesForDefaultsLoading, addons,
this::applyAdditionalProperties, getAdditionalDefaults(), isDark(), defaults );
// setup default font after loading defaults from properties
// to allow defining "defaultFont" in properties
@@ -530,9 +561,6 @@ public abstract class FlatLaf
// initialize text antialiasing
putAATextInfo( defaults );
// apply additional defaults (e.g. from IntelliJ themes)
applyAdditionalDefaults( defaults );
// allow addons modifying UI defaults
for( FlatDefaultsAddon addon : addons )
addon.afterDefaultsLoading( this, defaults );
@@ -542,6 +570,9 @@ public abstract class FlatLaf
return UIScale.getUserScaleFactor();
} );
// add lazy UI delegate class loading (if necessary)
addLazyUIdelegateClassLoading( defaults );
if( postInitialization != null ) {
postInitialization.accept( defaults );
postInitialization = null;
@@ -550,7 +581,8 @@ public abstract class FlatLaf
return defaults;
}
void applyAdditionalDefaults( UIDefaults defaults ) {
// apply additional properties (e.g. from IntelliJ themes)
void applyAdditionalProperties( Properties properties ) {
}
protected List<Class<?>> getLafClassesForDefaultsLoading() {
@@ -691,11 +723,22 @@ public abstract class FlatLaf
uiFont = ((ActiveFont)defaultFont).derive( baseFont, fontSize -> {
return Math.round( fontSize * UIScale.computeFontScaleFactor( baseFont ) );
} );
}
} else if( defaultFont instanceof LazyValue )
uiFont = ActiveFont.toUIResource( (Font) ((LazyValue)defaultFont).createValue( defaults ) );
// increase font size if system property "flatlaf.uiScale" is set
uiFont = UIScale.applyCustomScaleFactor( uiFont );
// apply zoom factor to font size
float zoomFactor = UIScale.getZoomFactor();
if( zoomFactor != 1 ) {
// see also UIScale.setZoomFactor()
int unzoomedFontSize = uiFont.getSize();
defaults.put( "defaultFont.unzoomedSize", unzoomedFontSize );
int newFontSize = Math.max( Math.round( unzoomedFontSize * zoomFactor ), 1 );
uiFont = new FontUIResource( uiFont.deriveFont( (float) newFontSize ) );
}
// set default font
defaults.put( "defaultFont", uiFont );
}
@@ -739,6 +782,53 @@ public abstract class FlatLaf
}
}
/**
* Handle UI delegate classes if running in special application where multiple class loaders are involved.
* E.g. in Eclipse plugin or in LibreOffice extension.
* <p>
* Problem: Swing runs in Java's system classloader and FlatLaf is loaded in plugin classloader.
* When Swing tries to load UI delegate class in {@link UIDefaults#getUIClass(String, ClassLoader)},
* invoked from {@link UIDefaults#getUI(JComponent)}, it uses the component's classloader,
* which is Java's system classloader for core Swing components,
* and can not find FlatLaf UI delegates.
* <p>
* Solution: Add lazy values for UI delegate class names.
* Those lazy values use FlatLaf classloader to load UI delegate class.
* This is similar to what {@link UIDefaults#getUIClass(String, ClassLoader)} does.
* <p>
* Not using {@code defaults.put( "ClassLoader", FlatLaf.class.getClassLoader() )},
* which would work for FlatLaf UI delegates, but it would break custom
* UI delegates used in other classloaders.
*/
private static void addLazyUIdelegateClassLoading( UIDefaults defaults ) {
if( FlatLaf.class.getClassLoader() == ClassLoader.getSystemClassLoader() )
return; // not necessary
Map<String, LazyValue> map = new HashMap<>();
for( Map.Entry<Object, Object> e : defaults.entrySet() ) {
Object key = e.getKey();
Object value = e.getValue();
if( key instanceof String && ((String)key).endsWith( "UI" ) &&
value instanceof String && !defaults.containsKey( value ) )
{
String className = (String) value;
map.put( className, (LazyValue) t -> {
try {
Class<?> uiClass = FlatLaf.class.getClassLoader().loadClass( className );
if( ComponentUI.class.isAssignableFrom( uiClass ) )
return uiClass;
} catch( ClassNotFoundException ex ) {
// ignore
}
// let UIDefaults.getUIClass() try to load UI delegate class
return null;
} );
}
}
defaults.putAll( map );
}
private void putAATextInfo( UIDefaults defaults ) {
if ( SystemInfo.isMacOS && SystemInfo.isJetBrainsJVM ) {
// The awt.font.desktophints property suggests sub-pixel anti-aliasing
@@ -848,8 +938,7 @@ public abstract class FlatLaf
* <p>
* Invoke this method before setting the look and feel.
* <p>
* If using Java modules, the package must be opened in {@code module-info.java}.
* Otherwise, use {@link #registerCustomDefaultsSource(URL)}.
* If using Java modules, it is not necessary to open the package in {@code module-info.java}.
*
* @param packageName a package name (e.g. "com.myapp.resources")
*/
@@ -896,9 +985,9 @@ public abstract class FlatLaf
* <p>
* See {@link #registerCustomDefaultsSource(String)} for details.
* <p>
* This method is useful if using Java modules and the package containing the properties files
* is not opened in {@code module-info.java}.
* E.g. {@code FlatLaf.registerCustomDefaultsSource( MyApp.class.getResource( "/com/myapp/themes/" ) )}.
* <p>
* If using Java modules, it is not necessary to open the package in {@code module-info.java}.
*
* @param packageUrl a package URL
* @since 2
@@ -1063,6 +1152,92 @@ public abstract class FlatLaf
FlatLaf.systemColorGetter = systemColorGetter;
}
/**
* Returns UI key prefix, used in FlatLaf properties files, for light or dark themes.
* Return value is either {@code [light]} or {@code [dark]}.
*
* @since 3.6
*/
public static String getUIKeyLightOrDarkPrefix( boolean dark ) {
return dark ? "[dark]" : "[light]";
}
/**
* Returns set of UI key prefixes, used in FlatLaf properties files, for current platform.
* If UI keys in properties files start with a prefix (e.g. {@code [someprefix]Button.background}),
* then they are only used if that prefix is contained in this set
* (or is one of {@code [light]} or {@code [dark]} depending on current theme).
* <p>
* By default, the set contains one or more of following prefixes:
* <ul>
* <li>{@code [win]} on Windows
* <li>{@code [mac]} on macOS
* <li>{@code [linux]} on Linux
* <li>{@code [unknown]} on other platforms
* <li>{@code [gnome]} on Linux with GNOME desktop environment
* <li>{@code [kde]} on Linux with KDE desktop environment
* <li>on Linux, the value of the environment variable {@code XDG_CURRENT_DESKTOP},
* split at colons and converted to lower case (e.g. if value of {@code XDG_CURRENT_DESKTOP}
* is {@code ubuntu:GNOME}, then {@code [ubuntu]} and {@code [gnome]})
* </ul>
* <p>
* You can add own prefixes to the set.
* The prefixes must start with '[' and end with ']' characters, otherwise they will be ignored.
*
* @since 3.6
*/
public static Set<String> getUIKeyPlatformPrefixes() {
if( uiKeyPlatformPrefixes == null ) {
uiKeyPlatformPrefixes = new HashSet<>();
uiKeyPlatformPrefixes.add(
SystemInfo.isWindows ? "[win]" :
SystemInfo.isMacOS ? "[mac]" :
SystemInfo.isLinux ? "[linux]" : "[unknown]" );
// Linux
if( SystemInfo.isLinux ) {
if( SystemInfo.isGNOME )
uiKeyPlatformPrefixes.add( "[gnome]" );
else if( SystemInfo.isKDE )
uiKeyPlatformPrefixes.add( "[kde]" );
// add values from XDG_CURRENT_DESKTOP for other desktops
String desktop = System.getenv( "XDG_CURRENT_DESKTOP" );
if( desktop != null ) {
// XDG_CURRENT_DESKTOP is a colon-separated list of strings
// https://specifications.freedesktop.org/desktop-entry-spec/latest/recognized-keys.html#key-onlyshowin
// e.g. "ubuntu:GNOME" on Ubuntu 24.10 or "GNOME-Classic:GNOME" on CentOS 7
for( String desk : StringUtils.split( desktop.toLowerCase( Locale.ENGLISH ), ':', true, true ) )
uiKeyPlatformPrefixes.add( '[' + desk + ']' );
}
}
}
return uiKeyPlatformPrefixes;
}
/**
* Returns set of special UI key prefixes, used in FlatLaf properties files.
* Unlike other prefixes, properties with special prefixes are preserved.
* You can access them using `UIManager`. E.g. `UIManager.get( "[someSpecialPrefix]someKey" )`.
* <p>
* By default, the set contains following special prefixes:
* <ul>
* <li>{@code [style]}
* </ul>
* <p>
* You can add own prefixes to the set.
* The prefixes must start with '[' and end with ']' characters, otherwise they will be ignored.
*
* @since 3.6
*/
public static Set<String> getUIKeySpecialPrefixes() {
if( uiKeySpecialPrefixes == null ) {
uiKeySpecialPrefixes = new HashSet<>();
uiKeySpecialPrefixes.add( "[style]" );
}
return uiKeySpecialPrefixes;
}
private static void reSetLookAndFeel() {
EventQueue.invokeLater( () -> {
LookAndFeel lookAndFeel = UIManager.getLookAndFeel();
@@ -1620,7 +1795,7 @@ public abstract class FlatLaf
return toUIResource( baseFont );
}
private FontUIResource toUIResource( Font font ) {
private static FontUIResource toUIResource( Font font ) {
// make sure that font is a UIResource for LaF switching
return (font instanceof FontUIResource)
? (FontUIResource) font

View File

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

View File

@@ -16,7 +16,10 @@
package com.formdev.flatlaf;
import javax.swing.JFileChooser;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import com.formdev.flatlaf.util.SystemFileChooser;
import com.formdev.flatlaf.util.UIScale;
/**
@@ -141,12 +144,31 @@ public interface FlatSystemProperties
* (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}
* <strong>Default</strong> {@code true}; except in FlatLaf 3.5.x on macOS 14.4+ where it was {@code false}
*
* @since 3.5.2
*/
String USE_ROUNDED_POPUP_BORDER = "flatlaf.useRoundedPopupBorder";
/**
* Species whether popup windows may be reused without (temporary) hiding them.
* E.g. if "moving" a tooltip to follow the mouse pointer, normally it is necessary
* to hide the tooltip and show it again at the new location, which causes some
* flicker with heavy-weight popup windows that FlatLaf uses on all platforms.
* <p>
* If {@code true}, hiding popup window is deferred for an event cycle,
* which allows reusing still visible popup window and avoids flicker when "moving" the popup.
* <p>
* Note that {@link JPopupMenu} popup windows (menus and combobox lists) are newer reused.
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> {@code true}
*
* @since 3.6.2
*/
String REUSE_VISIBLE_POPUP_WINDOW = "flatlaf.reuseVisiblePopupWindow";
/**
* Specifies whether vertical text position is corrected when UI is scaled on HiDPI screens.
* <p>
@@ -226,6 +248,17 @@ public interface FlatSystemProperties
*/
String USE_SUB_MENU_SAFE_TRIANGLE = "flatlaf.useSubMenuSafeTriangle";
/**
* Specifies whether {@link SystemFileChooser} uses operating system file dialogs.
* If set to {@code false}, the {@link JFileChooser} is used instead.
* <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> {@code true}
*
* @since 3.7
*/
String USE_SYSTEM_FILE_CHOOSER = "flatlaf.useSystemFileChooser";
/**
* 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

@@ -16,7 +16,6 @@
package com.formdev.flatlaf;
import java.awt.Color;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -25,20 +24,16 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Map.Entry;
import javax.swing.UIDefaults;
import javax.swing.plaf.ColorUIResource;
import com.formdev.flatlaf.json.Json;
import com.formdev.flatlaf.json.ParseException;
import com.formdev.flatlaf.util.ColorFunctions;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.StringUtils;
import com.formdev.flatlaf.util.SystemInfo;
@@ -63,13 +58,11 @@ public class IntelliJTheme
public final boolean dark;
public final String author;
private final boolean isMaterialUILite;
private final Map<String, String> jsonColors;
private final Map<String, Object> jsonUI;
private final Map<String, Object> jsonIcons;
private Map<String, String> colors;
private Map<String, Object> ui;
private Map<String, Object> icons;
private Map<String, ColorUIResource> namedColors = Collections.emptyMap();
private Map<String, String> namedColors = Collections.emptyMap();
/**
* Loads a IntelliJ .theme.json file from the given input stream,
@@ -84,7 +77,7 @@ public class IntelliJTheme
try {
return FlatLaf.setup( createLaf( in ) );
} catch( Exception ex ) {
LoggingFacade.INSTANCE.logSevere( "FlatLaf: Failed to load IntelliJ theme", ex );
LoggingFacade.INSTANCE.logSevere( "FlatLaf: Failed to load IntelliJ theme", ex );
return false;
}
}
@@ -138,94 +131,90 @@ public class IntelliJTheme
dark = Boolean.parseBoolean( (String) json.get( "dark" ) );
author = (String) json.get( "author" );
isMaterialUILite = author.equals( "Mallowigi" );
colors = (Map<String, String>) json.get( "colors" );
ui = (Map<String, Object>) json.get( "ui" );
icons = (Map<String, Object>) json.get( "icons" );
jsonColors = (Map<String, String>) json.get( "colors" );
jsonUI = (Map<String, Object>) json.get( "ui" );
jsonIcons = (Map<String, Object>) json.get( "icons" );
}
private void applyProperties( UIDefaults defaults ) {
if( ui == null )
private void applyProperties( Properties properties ) {
if( jsonUI == null )
return;
defaults.put( "Component.isIntelliJTheme", true );
put( properties, "Component.isIntelliJTheme", "true" );
// enable button shadows
defaults.put( "Button.paintShadow", true );
defaults.put( "Button.shadowWidth", dark ? 2 : 1 );
put( properties, "Button.paintShadow", "true" );
put( properties, "Button.shadowWidth", dark ? "2" : "1" );
Map<Object, Object> themeSpecificDefaults = removeThemeSpecificDefaults( defaults );
Map<String, String> themeSpecificProps = removeThemeSpecificProps( properties );
Set<String> jsonUIKeys = new HashSet<>();
loadNamedColors( defaults );
// Json node "colors"
loadNamedColors( properties, jsonUIKeys );
// convert Json "ui" structure to UI defaults
ArrayList<Object> defaultsKeysCache = new ArrayList<>();
Set<String> uiKeys = new HashSet<>();
for( Map.Entry<String, Object> e : ui.entrySet() )
apply( e.getKey(), e.getValue(), defaults, defaultsKeysCache, uiKeys );
// convert Json "ui" structure to UI properties
for( Map.Entry<String, Object> e : jsonUI.entrySet() )
apply( e.getKey(), e.getValue(), properties, jsonUIKeys );
applyColorPalette( defaults );
applyCheckBoxColors( defaults );
// set FlatLaf variables
copyIfSetInJson( properties, jsonUIKeys, "@background", "Panel.background", "*.background" );
copyIfSetInJson( properties, jsonUIKeys, "@foreground", "CheckBox.foreground", "*.foreground" );
copyIfSetInJson( properties, jsonUIKeys, "@accentBaseColor",
"ColorPalette.accent", // Material UI Lite, Hiberbee
"ColorPalette.accentColor", // Dracula, One Dark
"ProgressBar.foreground",
"*.selectionBackground" );
copyIfSetInJson( properties, jsonUIKeys, "@accentUnderlineColor", "*.underlineColor", "TabbedPane.underlineColor" );
copyIfSetInJson( properties, jsonUIKeys, "@selectionBackground", "*.selectionBackground" );
copyIfSetInJson( properties, jsonUIKeys, "@selectionForeground", "*.selectionForeground" );
copyIfSetInJson( properties, jsonUIKeys, "@selectionInactiveBackground", "*.selectionInactiveBackground" );
copyIfSetInJson( properties, jsonUIKeys, "@selectionInactiveForeground", "*.selectionInactiveForeground" );
// Json node "icons/ColorPalette"
applyIconsColorPalette( properties );
// apply "CheckBox.icon." colors
applyCheckBoxColors( properties );
// copy values
for( Map.Entry<String, String> e : uiKeyCopying.entrySet() ) {
Object value = defaults.get( e.getValue() );
Object value = properties.get( e.getValue() );
if( value != null )
defaults.put( e.getKey(), value );
put( properties, e.getKey(), value );
}
// IDEA does not paint button background if disabled, but FlatLaf does
Object panelBackground = defaults.get( "Panel.background" );
defaults.put( "Button.disabledBackground", panelBackground );
defaults.put( "ToggleButton.disabledBackground", panelBackground );
put( properties, "Button.disabledBackground", "@disabledBackground" );
put( properties, "ToggleButton.disabledBackground", "@disabledBackground" );
// fix Button borders
copyIfNotSet( defaults, "Button.focusedBorderColor", "Component.focusedBorderColor", uiKeys );
defaults.put( "Button.hoverBorderColor", defaults.get( "Button.focusedBorderColor" ) );
defaults.put( "HelpButton.hoverBorderColor", defaults.get( "Button.focusedBorderColor" ) );
// IDEA uses an SVG icon for the help button, but paints the background with Button.startBackground and Button.endBackground
Object helpButtonBackground = defaults.get( "Button.startBackground" );
Object helpButtonBorderColor = defaults.get( "Button.startBorderColor" );
if( helpButtonBackground == null )
helpButtonBackground = defaults.get( "Button.background" );
if( helpButtonBorderColor == null )
helpButtonBorderColor = defaults.get( "Button.borderColor" );
defaults.put( "HelpButton.background", helpButtonBackground );
defaults.put( "HelpButton.borderColor", helpButtonBorderColor );
defaults.put( "HelpButton.disabledBackground", panelBackground );
defaults.put( "HelpButton.disabledBorderColor", defaults.get( "Button.disabledBorderColor" ) );
defaults.put( "HelpButton.focusedBorderColor", defaults.get( "Button.focusedBorderColor" ) );
defaults.put( "HelpButton.focusedBackground", defaults.get( "Button.focusedBackground" ) );
// fix Button
fixStartEnd( properties, jsonUIKeys, "Button.startBackground", "Button.endBackground", "Button.background" );
fixStartEnd( properties, jsonUIKeys, "Button.startBorderColor", "Button.endBorderColor", "Button.borderColor" );
fixStartEnd( properties, jsonUIKeys, "Button.default.startBackground", "Button.default.endBackground", "Button.default.background" );
fixStartEnd( properties, jsonUIKeys, "Button.default.startBorderColor", "Button.default.endBorderColor", "Button.default.borderColor" );
// IDEA uses TextField.background for editable ComboBox and Spinner
Object textFieldBackground = get( defaults, themeSpecificDefaults, "TextField.background" );
defaults.put( "ComboBox.editableBackground", textFieldBackground );
defaults.put( "Spinner.background", textFieldBackground );
// Spinner arrow button always has same colors as ComboBox arrow button
defaults.put( "Spinner.buttonBackground", defaults.get( "ComboBox.buttonEditableBackground" ) );
defaults.put( "Spinner.buttonArrowColor", defaults.get( "ComboBox.buttonArrowColor" ) );
defaults.put( "Spinner.buttonDisabledArrowColor", defaults.get( "ComboBox.buttonDisabledArrowColor" ) );
Object textFieldBackground = get( properties, themeSpecificProps, "TextField.background" );
put( properties, "ComboBox.editableBackground", textFieldBackground );
put( properties, "Spinner.background", textFieldBackground );
// some themes specify colors for TextField.background, but forget to specify it for other components
// (probably because those components are not used in IntelliJ IDEA)
putAll( defaults, textFieldBackground,
putAll( properties, textFieldBackground,
"EditorPane.background",
"FormattedTextField.background",
"PasswordField.background",
"TextArea.background",
"TextPane.background"
);
putAll( defaults, get( defaults, themeSpecificDefaults, "TextField.selectionBackground" ),
putAll( properties, get( properties, themeSpecificProps, "TextField.selectionBackground" ),
"EditorPane.selectionBackground",
"FormattedTextField.selectionBackground",
"PasswordField.selectionBackground",
"TextArea.selectionBackground",
"TextPane.selectionBackground"
);
putAll( defaults, get( defaults, themeSpecificDefaults, "TextField.selectionForeground" ),
putAll( properties, get( properties, themeSpecificProps, "TextField.selectionForeground" ),
"EditorPane.selectionForeground",
"FormattedTextField.selectionForeground",
"PasswordField.selectionForeground",
@@ -235,7 +224,7 @@ public class IntelliJTheme
// fix disabled and not-editable backgrounds for text components, combobox and spinner
// (IntelliJ IDEA does not use those colors; instead it used background color of parent)
putAll( defaults, panelBackground,
putAll( properties, "@disabledBackground",
"ComboBox.disabledBackground",
"EditorPane.disabledBackground", "EditorPane.inactiveBackground",
"FormattedTextField.disabledBackground", "FormattedTextField.inactiveBackground",
@@ -246,132 +235,143 @@ public class IntelliJTheme
"TextPane.disabledBackground", "TextPane.inactiveBackground"
);
// fix ToggleButton
if( !uiKeys.contains( "ToggleButton.startBackground" ) && !uiKeys.contains( "*.startBackground" ) )
defaults.put( "ToggleButton.startBackground", defaults.get( "Button.startBackground" ) );
if( !uiKeys.contains( "ToggleButton.endBackground" ) && !uiKeys.contains( "*.endBackground" ) )
defaults.put( "ToggleButton.endBackground", defaults.get( "Button.endBackground" ) );
if( !uiKeys.contains( "ToggleButton.foreground" ) && uiKeys.contains( "Button.foreground" ) )
defaults.put( "ToggleButton.foreground", defaults.get( "Button.foreground" ) );
// fix DesktopPane background (use Panel.background and make it 5% darker/lighter)
Color desktopBackgroundBase = defaults.getColor( "Panel.background" );
Color desktopBackground = ColorFunctions.applyFunctions( desktopBackgroundBase,
new ColorFunctions.HSLIncreaseDecrease( 2, dark, 5, false, true ) );
defaults.put( "Desktop.background", new ColorUIResource( desktopBackground ) );
// fix List and Table background colors in Material UI Lite themes
if( isMaterialUILite ) {
defaults.put( "List.background", defaults.get( "Tree.background" ) );
defaults.put( "Table.background", defaults.get( "Tree.background" ) );
}
put( properties, "Desktop.background", dark ? "lighten($Panel.background,5%)" : "darken($Panel.background,5%)" );
// limit tree row height
int rowHeight = defaults.getInt( "Tree.rowHeight" );
String rowHeightStr = (String) properties.get( "Tree.rowHeight" );
int rowHeight = (rowHeightStr != null) ? Integer.parseInt( rowHeightStr ) : 0;
if( rowHeight > 22 )
defaults.put( "Tree.rowHeight", 22 );
put( properties, "Tree.rowHeight", "22" );
// get (and remove) theme specific wildcard replacements, which override all other defaults that end with same suffix
HashMap<String, Object> wildcards = new HashMap<>();
Iterator<Entry<Object, Object>> it = themeSpecificDefaults.entrySet().iterator();
// get (and remove) theme specific wildcard replacements, which override all other properties that end with same suffix
HashMap<String, String> wildcardProps = new HashMap<>();
Iterator<Map.Entry<String, String>> it = themeSpecificProps.entrySet().iterator();
while( it.hasNext() ) {
Entry<Object, Object> e = it.next();
String key = (String) e.getKey();
Map.Entry<String, String> e = it.next();
String key = e.getKey();
if( key.startsWith( "*." ) ) {
wildcards.put( key.substring( "*.".length() ), e.getValue() );
wildcardProps.put( key, e.getValue() );
it.remove();
}
}
// override UI defaults with theme specific wildcard replacements
if( !wildcards.isEmpty() ) {
for( Object key : defaults.keySet().toArray() ) {
int dot;
if( !(key instanceof String) ||
(dot = ((String)key).lastIndexOf( '.' )) < 0 )
continue;
String wildcardKey = ((String)key).substring( dot + 1 );
Object wildcardValue = wildcards.get( wildcardKey );
if( wildcardValue != null )
defaults.put( key, wildcardValue );
}
// override properties with theme specific wildcard replacements
if( !wildcardProps.isEmpty() ) {
for( Map.Entry<String, String> e : wildcardProps.entrySet() )
applyWildcard( properties, e.getKey(), e.getValue() );
}
// apply theme specific UI defaults at the end to allow overwriting
for( Map.Entry<Object, Object> e : themeSpecificDefaults.entrySet() ) {
Object key = e.getKey();
Object value = e.getValue();
// apply theme specific properties at the end to allow overwriting
for( Map.Entry<String, String> e : themeSpecificProps.entrySet() ) {
String key = e.getKey();
String value = e.getValue();
// append styles to existing styles
if( key instanceof String && ((String)key).startsWith( "[style]" ) ) {
Object oldValue = defaults.get( key );
if( key.startsWith( "[style]" ) ) {
String oldValue = (String) properties.get( key );
if( oldValue != null )
value = oldValue + "; " + value;
}
defaults.put( key, value );
put( properties, key, value );
}
// let Java release memory
colors = null;
ui = null;
icons = null;
}
private Object get( UIDefaults defaults, Map<Object, Object> themeSpecificDefaults, String key ) {
return themeSpecificDefaults.getOrDefault( key, defaults.get( key ) );
private String get( Properties properties, Map<String, String> themeSpecificProps, String key ) {
return themeSpecificProps.getOrDefault( key, (String) properties.get( key ) );
}
private void putAll( UIDefaults defaults, Object value, String... keys ) {
private void put( Properties properties, Object key, Object value ) {
if( value != null )
properties.put( key, value );
else
properties.remove( key );
}
private void putAll( Properties properties, Object value, String... keys ) {
for( String key : keys )
defaults.put( key, value );
put( properties, key, value );
}
private Map<Object, Object> removeThemeSpecificDefaults( UIDefaults defaults ) {
// search for theme specific UI defaults keys
private void copyIfSetInJson( Properties properties, Set<String> jsonUIKeys, String destKey, String... srcKeys ) {
for( String srcKey : srcKeys ) {
if( jsonUIKeys.contains( srcKey ) ) {
Object value = properties.get( srcKey );
if( value != null ) {
put( properties, destKey, value );
break;
}
}
}
}
private void fixStartEnd( Properties properties, Set<String> jsonUIKeys, String startKey, String endKey, String key ) {
if( jsonUIKeys.contains( startKey ) && jsonUIKeys.contains( endKey ) )
put( properties, key, "$" + startKey );
}
private Map<String, String> removeThemeSpecificProps( Properties properties ) {
// search for theme specific properties keys
ArrayList<String> themeSpecificKeys = new ArrayList<>();
for( Object key : defaults.keySet() ) {
if( key instanceof String && ((String)key).startsWith( "[" ) && !((String)key).startsWith( "[style]" ) )
for( Object key : properties.keySet() ) {
if( ((String)key).startsWith( "{" ) )
themeSpecificKeys.add( (String) key );
}
// remove theme specific UI defaults and remember only those for current theme
Map<Object, Object> themeSpecificDefaults = new HashMap<>();
String currentThemePrefix = '[' + name.replace( ' ', '_' ) + ']';
String currentThemeAndAuthorPrefix = '[' + name.replace( ' ', '_' ) + "---" + author.replace( ' ', '_' ) + ']';
String currentAuthorPrefix = "[author-" + author.replace( ' ', '_' ) + ']';
String allThemesPrefix = "[*]";
String[] prefixes = { currentThemePrefix, currentThemeAndAuthorPrefix, currentAuthorPrefix, allThemesPrefix };
// special prefixes (priority from highest to lowest)
String currentThemePrefix = '{' + name.replace( ' ', '_' ) + '}';
String currentThemeAndAuthorPrefix = '{' + name.replace( ' ', '_' ) + "---" + author.replace( ' ', '_' ) + '}';
String currentAuthorPrefix = "{author-" + author.replace( ' ', '_' ) + '}';
String lightOrDarkPrefix = dark ? "{*-dark}" : "{*-light}";
String allThemesPrefix = "{*}";
String[] prefixes = { currentThemePrefix, currentThemeAndAuthorPrefix, currentAuthorPrefix, lightOrDarkPrefix, allThemesPrefix };
// collect values for special prefixes in its own maps
@SuppressWarnings( "unchecked" )
Map<String, String>[] maps = new Map[prefixes.length];
for( int i = 0; i < maps.length; i++ )
maps[i] = new HashMap<>();
// remove theme specific properties and remember only those for current theme
for( String key : themeSpecificKeys ) {
Object value = defaults.remove( key );
for( String prefix : prefixes ) {
String value = (String) properties.remove( key );
for( int i = 0; i < prefixes.length; i++ ) {
String prefix = prefixes[i];
if( key.startsWith( prefix ) ) {
themeSpecificDefaults.put( key.substring( prefix.length() ), value );
maps[i].put( key.substring( prefix.length() ), value );
break;
}
}
}
return themeSpecificDefaults;
// copy values into single map (from lowest to highest priority)
Map<String, String> themeSpecificProps = new HashMap<>();
for( int i = maps.length - 1; i >= 0; i-- )
themeSpecificProps.putAll( maps[i] );
return themeSpecificProps;
}
/**
* http://www.jetbrains.org/intellij/sdk/docs/reference_guide/ui_themes/themes_customize.html#defining-named-colors
*/
private void loadNamedColors( UIDefaults defaults ) {
if( colors == null )
private void loadNamedColors( Properties properties, Set<String> jsonUIKeys ) {
if( jsonColors == null )
return;
namedColors = new HashMap<>();
for( Map.Entry<String, String> e : colors.entrySet() ) {
for( Map.Entry<String, String> e : jsonColors.entrySet() ) {
String value = e.getValue();
ColorUIResource color = parseColor( value );
if( color != null ) {
if( canParseColor( value ) ) {
String key = e.getKey();
namedColors.put( key, color );
defaults.put( "ColorPalette." + key, color );
namedColors.put( key, value );
String uiKey = "ColorPalette." + key;
put( properties, uiKey, value );
// this is only necessary for copyIfSetInJson() (used for accent color)
jsonUIKeys.add( uiKey );
}
}
}
@@ -380,7 +380,7 @@ public class IntelliJTheme
* http://www.jetbrains.org/intellij/sdk/docs/reference_guide/ui_themes/themes_customize.html#custom-ui-control-colors
*/
@SuppressWarnings( "unchecked" )
private void apply( String key, Object value, UIDefaults defaults, ArrayList<Object> defaultsKeysCache, Set<String> uiKeys ) {
private void apply( String key, Object value, Properties properties, Set<String> jsonUIKeys ) {
if( value instanceof Map ) {
Map<String, Object> map = (Map<String, Object>)value;
if( map.containsKey( "os.default" ) || map.containsKey( "os.windows" ) || map.containsKey( "os.mac" ) || map.containsKey( "os.linux" ) ) {
@@ -388,12 +388,12 @@ public class IntelliJTheme
: SystemInfo.isMacOS ? "os.mac"
: SystemInfo.isLinux ? "os.linux" : null;
if( osKey != null && map.containsKey( osKey ) )
apply( key, map.get( osKey ), defaults, defaultsKeysCache, uiKeys );
apply( key, map.get( osKey ), properties, jsonUIKeys );
else if( map.containsKey( "os.default" ) )
apply( key, map.get( "os.default" ), defaults, defaultsKeysCache, uiKeys );
apply( key, map.get( "os.default" ), properties, jsonUIKeys );
} else {
for( Map.Entry<String, Object> e : map.entrySet() )
apply( key + '.' + e.getKey(), e.getValue(), defaults, defaultsKeysCache, uiKeys );
apply( key + '.' + e.getKey(), e.getValue(), properties, jsonUIKeys );
}
} else {
if( "".equals( value ) )
@@ -408,25 +408,40 @@ public class IntelliJTheme
key.equals( "Tree.rightChildIndent" ) )
return; // ignore
// ignore icons
if( key.endsWith( "Icon" ) )
return; // ignore
// map keys
key = uiKeyMapping.getOrDefault( key, key );
if( key.isEmpty() )
return; // ignore key
// exclude properties
// exclude properties (1st level)
int dot = key.indexOf( '.' );
if( dot > 0 && uiKeyExcludes.contains( key.substring( 0, dot + 1 ) ) )
if( dot > 0 && uiKeyExcludesStartsWith.contains( key.substring( 0, dot + 1 ) ) )
return;
if( uiKeyDoNotOverride.contains( key ) && uiKeys.contains( key ) )
// exclude properties (2st level)
int dot2 = (dot > 0) ? key.indexOf( '.', dot + 1 ) : -1;
if( dot2 > 0 && uiKeyExcludesStartsWith.contains( key.substring( 0, dot2 + 1 ) ) )
return;
uiKeys.add( key );
// exclude properties (contains)
for( String s : uiKeyExcludesContains ) {
if( key.contains( s ) )
return;
}
String valueStr = value.toString();
if( uiKeyDoNotOverride.contains( key ) && jsonUIKeys.contains( key ) )
return;
jsonUIKeys.add( key );
String valueStr = value.toString().trim();
// map named colors
Object uiValue = namedColors.get( valueStr );
String uiValue = namedColors.get( valueStr );
// parse value
if( uiValue == null ) {
@@ -445,47 +460,64 @@ public class IntelliJTheme
// parse value
try {
uiValue = UIDefaultsLoader.parseValue( key, valueStr, null );
UIDefaultsLoader.parseValue( key, valueStr, null );
uiValue = valueStr;
} catch( RuntimeException ex ) {
UIDefaultsLoader.logParseError( key, valueStr, ex, false );
UIDefaultsLoader.logParseError( key, valueStr, ex, true );
return; // ignore invalid value
}
}
if( key.startsWith( "*." ) ) {
// wildcard
String tail = key.substring( 1 );
// wildcards
if( applyWildcard( properties, key, uiValue ) )
return;
// because we can not iterate over the UI defaults keys while
// modifying UI defaults in the same loop, we have to copy the keys
if( defaultsKeysCache.size() != defaults.size() ) {
defaultsKeysCache.clear();
Enumeration<Object> e = defaults.keys();
while( e.hasMoreElements() )
defaultsKeysCache.add( e.nextElement() );
}
// replace all values in UI defaults that match the wildcard key
for( Object k : defaultsKeysCache ) {
if( k.equals( "Desktop.background" ) ||
k.equals( "DesktopIcon.background" ) ||
k.equals( "TabbedPane.focusColor" ) )
continue;
if( k instanceof String ) {
// support replacing of mapped keys
// (e.g. set ComboBox.buttonEditableBackground to *.background
// because it is mapped from ComboBox.ArrowButton.background)
String km = uiKeyInverseMapping.getOrDefault( k, (String) k );
if( km.endsWith( tail ) && !((String)k).startsWith( "CheckBox.icon." ) )
defaults.put( k, uiValue );
}
}
} else
defaults.put( key, uiValue );
put( properties, key, uiValue );
}
}
private boolean applyWildcard( Properties properties, String key, String value ) {
if( !key.startsWith( "*." ) )
return false;
String tail = key.substring( 1 );
// because we can not iterate over the properties keys while
// modifying properties in the same loop, we have to copy the keys
String[] keys = properties.keySet().toArray( new String[properties.size()] );
// replace all values in properties that match the wildcard key
for( String k : keys ) {
if( k.startsWith( "*" ) ||
k.startsWith( "@" ) ||
k.startsWith( "HelpButton." ) ||
k.startsWith( "JX" ) ||
k.startsWith( "Jide" ) ||
k.startsWith( "ProgressBar.selection" ) ||
k.startsWith( "TitlePane." ) ||
k.startsWith( "ToggleButton.tab." ) ||
k.equals( "Desktop.background" ) ||
k.equals( "DesktopIcon.background" ) ||
k.equals( "TabbedPane.focusColor" ) ||
k.endsWith( ".hoverBackground" ) ||
k.endsWith( ".pressedBackground" ) )
continue;
// support replacing of mapped keys
// (e.g. set ComboBox.buttonEditableBackground to *.background
// because it is mapped from ComboBox.ArrowButton.background)
String km = uiKeyInverseMapping.getOrDefault( k, k );
if( km.endsWith( tail ) && !k.startsWith( "CheckBox.icon." ) )
put( properties, k, value );
}
// Note: also add wildcards to properties and let UIDefaultsLoader
// process it on BasicLookAndFeel UI defaults
put( properties, key, value );
return true;
}
private String fixColorIfValid( String newColorStr, String colorStr ) {
try {
// check whether it is valid
@@ -497,11 +529,11 @@ public class IntelliJTheme
}
}
private void applyColorPalette( UIDefaults defaults ) {
if( icons == null )
private void applyIconsColorPalette( Properties properties ) {
if( jsonIcons == null )
return;
Object palette = icons.get( "ColorPalette" );
Object palette = jsonIcons.get( "ColorPalette" );
if( !(palette instanceof Map) )
return;
@@ -510,44 +542,48 @@ public class IntelliJTheme
for( Map.Entry<String, Object> e : colorPalette.entrySet() ) {
String key = e.getKey();
Object value = e.getValue();
if( key.startsWith( "Checkbox." ) || !(value instanceof String) )
if( key.startsWith( "Checkbox." ) || key.startsWith( "#" ) || !(value instanceof String) )
continue;
if( dark )
key = StringUtils.removeTrailing( key, ".Dark" );
ColorUIResource color = toColor( (String) value );
String color = toColor( (String) value );
if( color != null )
defaults.put( key, color );
put( properties, key, color );
}
}
private ColorUIResource toColor( String value ) {
private String toColor( String value ) {
if( value.startsWith( "##" ) )
value = fixColorIfValid( value.substring( 1 ), value );
// map named colors
ColorUIResource color = namedColors.get( value );
String color = namedColors.get( value );
// parse color
return (color != null) ? color : parseColor( value );
return (color != null) ? color : (canParseColor( value ) ? value : null);
}
private ColorUIResource parseColor( String value ) {
private boolean canParseColor( String value ) {
try {
return UIDefaultsLoader.parseColor( value );
return UIDefaultsLoader.parseColor( value ) != null;
} catch( IllegalArgumentException ex ) {
return null;
LoggingFacade.INSTANCE.logSevere( "FlatLaf: Failed to parse color: '" + value + '\'', ex );
return false;
}
}
/**
* Because IDEA uses SVGs for check boxes and radio buttons, the colors for
* these two components are specified in "icons > ColorPalette".
* FlatLaf uses vector icons and expects colors for the two components in UI defaults.
* FlatLaf uses vector icons and expects colors for the two components in properties.
*/
private void applyCheckBoxColors( UIDefaults defaults ) {
if( icons == null )
private void applyCheckBoxColors( Properties properties ) {
if( jsonIcons == null )
return;
Object palette = icons.get( "ColorPalette" );
Object palette = jsonIcons.get( "ColorPalette" );
if( !(palette instanceof Map) )
return;
@@ -569,9 +605,9 @@ public class IntelliJTheme
if( !dark && newKey.startsWith( checkBoxIconPrefix ) )
newKey = "CheckBox.icon[filled].".concat( newKey.substring( checkBoxIconPrefix.length() ) );
ColorUIResource color = toColor( (String) value );
String color = toColor( (String) value );
if( color != null ) {
defaults.put( newKey, color );
put( properties, newKey, color );
String key2 = checkboxDuplicateColors.get( key + ".Dark");
if( key2 != null ) {
@@ -592,7 +628,7 @@ public class IntelliJTheme
String newKey2 = checkboxKeyMapping.get( key2 );
if( newKey2 != null )
defaults.put( newKey2, color );
put( properties, newKey2, color );
}
}
@@ -603,13 +639,13 @@ public class IntelliJTheme
// update hover, pressed and focused colors
if( checkboxModified ) {
// for non-filled checkbox/radiobutton used in dark themes
defaults.remove( "CheckBox.icon.focusWidth" );
defaults.put( "CheckBox.icon.hoverBorderColor", defaults.get( "CheckBox.icon.focusedBorderColor" ) );
properties.remove( "CheckBox.icon.focusWidth" );
put( properties, "CheckBox.icon.hoverBorderColor", properties.get( "CheckBox.icon.focusedBorderColor" ) );
// for filled checkbox/radiobutton used in light themes
defaults.remove( "CheckBox.icon[filled].focusWidth" );
defaults.put( "CheckBox.icon[filled].hoverBorderColor", defaults.get( "CheckBox.icon[filled].focusedBorderColor" ) );
defaults.put( "CheckBox.icon[filled].focusedSelectedBackground", defaults.get( "CheckBox.icon[filled].selectedBackground" ) );
properties.remove( "CheckBox.icon[filled].focusWidth" );
put( properties, "CheckBox.icon[filled].hoverBorderColor", properties.get( "CheckBox.icon[filled].focusedBorderColor" ) );
put( properties, "CheckBox.icon[filled].focusedSelectedBackground", properties.get( "CheckBox.icon[filled].selectedBackground" ) );
if( dark ) {
// IDEA Darcula checkBoxFocused.svg, checkBoxSelectedFocused.svg,
@@ -623,22 +659,16 @@ public class IntelliJTheme
"CheckBox.icon[filled].focusedSelectedBorderColor",
};
for( String key : focusedBorderColorKeys ) {
Color color = defaults.getColor( key );
if( color != null ) {
defaults.put( key, new ColorUIResource( new Color(
(color.getRGB() & 0xffffff) | 0xa6000000, true ) ) );
}
Object color = properties.get( key );
if( color != null )
put( properties, key, "fade(" + color + ", 65%)" );
}
}
}
}
private void copyIfNotSet( UIDefaults defaults, String destKey, String srcKey, Set<String> uiKeys ) {
if( !uiKeys.contains( destKey ) )
defaults.put( destKey, defaults.get( srcKey ) );
}
private static final Set<String> uiKeyExcludes;
private static final Set<String> uiKeyExcludesStartsWith;
private static final String[] uiKeyExcludesContains;
private static final Set<String> uiKeyDoNotOverride;
/** Rename UI default keys (key --> value). */
private static final Map<String, String> uiKeyMapping = new HashMap<>();
@@ -650,29 +680,30 @@ public class IntelliJTheme
static {
// IntelliJ UI properties that are not used in FlatLaf
uiKeyExcludes = new HashSet<>( Arrays.asList(
uiKeyExcludesStartsWith = new HashSet<>( Arrays.asList(
"ActionButton.", "ActionToolbar.", "ActionsList.", "AppInspector.", "AssignedMnemonic.", "Autocomplete.",
"AvailableMnemonic.",
"BigSpinner.", "Bookmark.", "BookmarkIcon.", "BookmarkMnemonicAssigned.", "BookmarkMnemonicAvailable.",
"Badge.", "Banner.", "BigSpinner.", "Bookmark.", "BookmarkIcon.", "BookmarkMnemonicAssigned.", "BookmarkMnemonicAvailable.",
"BookmarkMnemonicCurrent.", "BookmarkMnemonicIcon.", "Borders.", "Breakpoint.",
"Canvas.", "CodeWithMe.", "ComboBoxButton.", "CompletionPopup.", "ComplexPopup.", "Content.",
"CurrentMnemonic.", "Counter.",
"Debugger.", "DebuggerPopup.", "DebuggerTabs.", "DefaultTabs.", "Dialog.", "DialogWrapper.", "DragAndDrop.",
"Canvas.", "CellEditor.", "Code.", "CodeWithMe.", "ColumnControlButton.", "CombinedDiff.", "ComboBoxButton.",
"CompilationCharts.", "CompletionPopup.", "ComplexPopup.", "Content.", "ContextHelp.", "CurrentMnemonic.", "Counter.",
"Debugger.", "DebuggerPopup.", "DebuggerTabs.", "DefaultTabs.", "Dialog.", "DialogWrapper.",
"DisclosureButton.", "DragAndDrop.",
"Editor.", "EditorGroupsTabs.", "EditorTabs.",
"FileColor.", "FlameGraph.", "Focus.",
"FileColor.", "FindPopup.", "FlameGraph.", "Focus.",
"Git.", "Github.", "GotItTooltip.", "Group.", "Gutter.", "GutterTooltip.",
"HeaderColor.", "HelpTooltip.", "Hg.",
"IconBadge.", "InformationHint.", "InplaceRefactoringPopup.",
"Lesson.", "Link.", "LiveIndicator.",
"MainMenu.", "MainToolbar.", "MemoryIndicator.", "MlModelBinding.", "MnemonicIcon.",
"IconBadge.", "InformationHint.", "InlineBanner.", "InplaceRefactoringPopup.",
"Lesson.", "LineProfiler.", "Link.", "LiveIndicator.",
"MainMenu.", "MainToolbar.", "MainWindow.", "MemoryIndicator.", "MlModelBinding.", "MnemonicIcon.",
"NavBar.", "NewClass.", "NewPSD.", "Notification.", "Notifications.", "NotificationsToolwindow.",
"OnePixelDivider.", "OptionButton.", "Outline.",
"ParameterInfo.", "Plugins.", "ProgressIcon.", "PsiViewer.",
"ReviewList.", "RunWidget.",
"ParameterInfo.", "PresentationAssistant.", "Plugins.", "Profiler.", "ProgressIcon.", "PsiViewer.",
"Resizable.", "Review.", "ReviewList.", "RunToolbar.", "RunWidget.",
"ScreenView.", "SearchEverywhere.", "SearchFieldWithExtension.", "SearchMatch.", "SearchOption.",
"SearchResults.", "SegmentedButton.", "Settings.", "SidePanel.", "Space.", "SpeedSearch.", "StateWidget.",
"StatusBar.",
"Tag.", "TipOfTheDay.", "ToolbarComboWidget.", "ToolWindow.",
"StatusBar.", "StripeToolbar.",
"Tag.", "TipOfTheDay.", "ToolbarComboWidget.", "ToolWindow.", "TrialWidget.",
"UIDesigner.", "UnattendedHostStatus.",
"ValidationTooltip.", "VersionControl.",
"WelcomeScreen.",
@@ -683,11 +714,24 @@ public class IntelliJTheme
// possible typos in .theme.json files
"Checkbox.", "Toolbar.", "Tooltip.", "UiDesigner.", "link."
) );
uiKeyExcludesContains = new String[] {
".darcula."
};
uiKeyDoNotOverride = new HashSet<>( Arrays.asList(
"TabbedPane.selectedForeground"
) );
// "*."
uiKeyMapping.put( "*.fontFace", "" ); // ignore (used in OnePauintxi themes)
uiKeyMapping.put( "*.fontSize", "" ); // ignore (used in OnePauintxi themes)
// Button
uiKeyMapping.put( "Button.minimumSize", "" ); // ignore (used in Material Theme UI Lite)
// CheckBox.iconSize
uiKeyMapping.put( "CheckBox.iconSize", "" ); // ignore (used in Rider themes)
// ComboBox
uiKeyMapping.put( "ComboBox.background", "" ); // ignore
uiKeyMapping.put( "ComboBox.buttonBackground", "" ); // ignore
@@ -696,14 +740,17 @@ public class IntelliJTheme
uiKeyMapping.put( "ComboBox.ArrowButton.disabledIconColor", "ComboBox.buttonDisabledArrowColor" );
uiKeyMapping.put( "ComboBox.ArrowButton.iconColor", "ComboBox.buttonArrowColor" );
uiKeyMapping.put( "ComboBox.ArrowButton.nonEditableBackground", "ComboBox.buttonBackground" );
uiKeyCopying.put( "ComboBox.buttonSeparatorColor", "Component.borderColor" );
uiKeyCopying.put( "ComboBox.buttonDisabledSeparatorColor", "Component.disabledBorderColor" );
// Component
uiKeyMapping.put( "Component.inactiveErrorFocusColor", "Component.error.borderColor" );
uiKeyMapping.put( "Component.errorFocusColor", "Component.error.focusedBorderColor" );
uiKeyMapping.put( "Component.inactiveWarningFocusColor", "Component.warning.borderColor" );
uiKeyMapping.put( "Component.warningFocusColor", "Component.warning.focusedBorderColor" );
uiKeyMapping.put( "Component.inactiveSuccessFocusColor", "Component.success.borderColor" );
uiKeyMapping.put( "Component.successFocusColor", "Component.success.focusedBorderColor" );
// Label
uiKeyMapping.put( "Label.disabledForegroundColor", "" ); // ignore (used in Material Theme UI Lite)
// Link
uiKeyMapping.put( "Link.activeForeground", "Component.linkColor" );
@@ -711,10 +758,7 @@ public class IntelliJTheme
// Menu
uiKeyMapping.put( "Menu.border", "Menu.margin" );
uiKeyMapping.put( "MenuItem.border", "MenuItem.margin" );
uiKeyCopying.put( "CheckBoxMenuItem.margin", "MenuItem.margin" );
uiKeyCopying.put( "RadioButtonMenuItem.margin", "MenuItem.margin" );
uiKeyMapping.put( "PopupMenu.border", "PopupMenu.borderInsets" );
uiKeyCopying.put( "MenuItem.underlineSelectionColor", "TabbedPane.underlineColor" );
// IDEA uses List.selectionBackground also for menu selection
uiKeyCopying.put( "Menu.selectionBackground", "List.selectionBackground" );
@@ -722,13 +766,14 @@ public class IntelliJTheme
uiKeyCopying.put( "CheckBoxMenuItem.selectionBackground", "List.selectionBackground" );
uiKeyCopying.put( "RadioButtonMenuItem.selectionBackground", "List.selectionBackground" );
// ProgressBar
// ProgressBar: IDEA uses ProgressBar.trackColor and ProgressBar.progressColor
uiKeyMapping.put( "ProgressBar.background", "" ); // ignore
uiKeyMapping.put( "ProgressBar.foreground", "" ); // ignore
uiKeyMapping.put( "ProgressBar.trackColor", "ProgressBar.background" );
uiKeyMapping.put( "ProgressBar.progressColor", "ProgressBar.foreground" );
uiKeyCopying.put( "ProgressBar.selectionForeground", "ProgressBar.background" );
uiKeyCopying.put( "ProgressBar.selectionBackground", "ProgressBar.foreground" );
// RadioButton
uiKeyMapping.put( "RadioButton.iconSize", "" ); // ignore (used in Rider themes)
// ScrollBar
uiKeyMapping.put( "ScrollBar.trackColor", "ScrollBar.track" );
@@ -738,34 +783,30 @@ public class IntelliJTheme
uiKeyMapping.put( "Separator.separatorColor", "Separator.foreground" );
// Slider
uiKeyMapping.put( "Slider.buttonColor", "Slider.thumbColor" );
uiKeyMapping.put( "Slider.buttonBorderColor", "" ); // ignore
uiKeyMapping.put( "Slider.thumb", "" ); // ignore (used in Material Theme UI Lite)
uiKeyMapping.put( "Slider.track", "" ); // ignore (used in Material Theme UI Lite)
uiKeyMapping.put( "Slider.trackDisabled", "" ); // ignore (used in Material Theme UI Lite)
uiKeyMapping.put( "Slider.trackWidth", "" ); // ignore (used in Material Theme UI Lite)
uiKeyCopying.put( "Slider.trackValueColor", "ProgressBar.foreground" );
uiKeyCopying.put( "Slider.thumbColor", "ProgressBar.foreground" );
uiKeyCopying.put( "Slider.trackColor", "ProgressBar.background" );
// Spinner
uiKeyCopying.put( "Spinner.buttonSeparatorColor", "Component.borderColor" );
uiKeyCopying.put( "Spinner.buttonDisabledSeparatorColor", "Component.disabledBorderColor" );
// TabbedPane
uiKeyMapping.put( "DefaultTabs.underlinedTabBackground", "TabbedPane.selectedBackground" );
uiKeyMapping.put( "DefaultTabs.underlinedTabForeground", "TabbedPane.selectedForeground" );
uiKeyMapping.put( "DefaultTabs.inactiveUnderlineColor", "TabbedPane.inactiveUnderlineColor" );
uiKeyMapping.put( "TabbedPane.tabAreaInsets", "" ); // ignore (used in Material Theme UI Lite)
// TableHeader
uiKeyMapping.put( "TableHeader.cellBorder", "" ); // ignore (used in Material Theme UI Lite)
uiKeyMapping.put( "TableHeader.height", "" ); // ignore (used in Material Theme UI Lite)
// TitlePane
uiKeyCopying.put( "TitlePane.inactiveBackground", "TitlePane.background" );
uiKeyMapping.put( "TitlePane.infoForeground", "TitlePane.foreground" );
uiKeyMapping.put( "TitlePane.inactiveInfoForeground", "TitlePane.inactiveForeground" );
for( Map.Entry<String, String> e : uiKeyMapping.entrySet() )
uiKeyInverseMapping.put( e.getValue(), e.getKey() );
uiKeyCopying.put( "ToggleButton.tab.underlineColor", "TabbedPane.underlineColor" );
uiKeyCopying.put( "ToggleButton.tab.disabledUnderlineColor", "TabbedPane.disabledUnderlineColor" );
uiKeyCopying.put( "ToggleButton.tab.selectedBackground", "TabbedPane.selectedBackground" );
uiKeyCopying.put( "ToggleButton.tab.hoverBackground", "TabbedPane.hoverColor" );
uiKeyCopying.put( "ToggleButton.tab.focusBackground", "TabbedPane.focusColor" );
checkboxKeyMapping.put( "Checkbox.Background.Default", "CheckBox.icon.background" );
checkboxKeyMapping.put( "Checkbox.Background.Disabled", "CheckBox.icon.disabledBackground" );
checkboxKeyMapping.put( "Checkbox.Border.Default", "CheckBox.icon.borderColor" );
@@ -818,17 +859,15 @@ public class IntelliJTheme
}
@Override
void applyAdditionalDefaults( UIDefaults defaults ) {
theme.applyProperties( defaults );
void applyAdditionalProperties( Properties properties ) {
theme.applyProperties( properties );
}
@Override
protected ArrayList<Class<?>> getLafClassesForDefaultsLoading() {
ArrayList<Class<?>> lafClasses = new ArrayList<>();
lafClasses.add( FlatLaf.class );
lafClasses.add( theme.dark ? FlatDarkLaf.class : FlatLightLaf.class );
lafClasses.add( theme.dark ? FlatDarculaLaf.class : FlatIntelliJLaf.class );
lafClasses.add( ThemeLaf.class );
ArrayList<Class<?>> lafClasses = UIDefaultsLoader.getLafClassesForDefaultsLoading( getClass() );
lafClasses.add( 1, theme.dark ? FlatDarkLaf.class : FlatLightLaf.class );
lafClasses.add( 2, theme.dark ? FlatDarculaLaf.class : FlatIntelliJLaf.class );
return lafClasses;
}
}

View File

@@ -0,0 +1,172 @@
/*
* Copyright 2025 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf;
import java.awt.Component;
import java.awt.Window;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JPopupMenu;
import javax.swing.MenuElement;
import javax.swing.MenuSelectionManager;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
/**
* Cancels (hides) popup menus on Linux and macOS.
* <p>
* On Linux, popups are not hidden under following conditions, which results in
* misplaced popups:
* <ul>
* <li>window moved or resized
* <li>window maximized or restored
* <li>window iconified
* <li>window deactivated (e.g. activated other application)
* </ul>
*
* On macOS, popups are usually automatically hidden, but not always.
* When resizing a window, then it depends where clicking to start resizing (and on the Java version).
* E.g. with Java 25, clicking at bottom-right corner inside of the window does not hide the popups.
* But clicking on same corner outside of the window, hides the popup.
*
* <p>
* On Windows, popups are automatically hidden.
* <p>
* The implementation is similar to what's done in
* {@code javax.swing.plaf.basic.BasicPopupMenuUI.MouseGrabber},
* but only hides popup in some conditions.
*
* @author Karl Tauber
*/
class LinuxPopupMenuCanceler
extends WindowAdapter
implements ChangeListener, ComponentListener
{
private MenuElement[] lastPathSelectedPath;
private Window window;
LinuxPopupMenuCanceler() {
MenuSelectionManager msm = MenuSelectionManager.defaultManager();
msm.addChangeListener( this );
lastPathSelectedPath = msm.getSelectedPath();
if( lastPathSelectedPath.length > 0 )
addWindowListeners( lastPathSelectedPath[0] );
}
void uninstall() {
MenuSelectionManager.defaultManager().removeChangeListener( this );
}
private void addWindowListeners( MenuElement selected ) {
removeWindowListeners();
// see BasicPopupMenuUI.MouseGrabber.grabWindow()
Component invoker = selected.getComponent();
if( invoker instanceof JPopupMenu )
invoker = ((JPopupMenu)invoker).getInvoker();
window = (invoker instanceof Window)
? (Window) invoker
: SwingUtilities.windowForComponent( invoker );
if( window != null ) {
window.addWindowListener( this );
window.addComponentListener( this );
}
}
private void removeWindowListeners() {
if( window != null ) {
window.removeWindowListener( this );
window.removeComponentListener( this );
window = null;
}
}
private void cancelPopupMenu() {
try {
MenuSelectionManager msm = MenuSelectionManager.defaultManager();
MenuElement[] selectedPath = msm.getSelectedPath();
for( MenuElement e : selectedPath ) {
if( e instanceof JPopupMenu )
((JPopupMenu)e).putClientProperty( "JPopupMenu.firePopupMenuCanceled", true );
}
msm.clearSelectedPath();
} catch( RuntimeException ex ) {
removeWindowListeners();
throw ex;
} catch( Error ex ) {
removeWindowListeners();
throw ex;
}
}
//---- ChangeListener ----
@Override
public void stateChanged( ChangeEvent e ) {
MenuElement[] selectedPath = MenuSelectionManager.defaultManager().getSelectedPath();
if( selectedPath.length == 0 )
removeWindowListeners();
else if( lastPathSelectedPath.length == 0 )
addWindowListeners( selectedPath[0] );
lastPathSelectedPath = selectedPath;
}
//---- WindowListener ----
@Override
public void windowIconified( WindowEvent e ) {
cancelPopupMenu();
}
@Override
public void windowDeactivated( WindowEvent e ) {
cancelPopupMenu();
}
@Override
public void windowClosing( WindowEvent e ) {
cancelPopupMenu();
}
//---- ComponentListener ----
@Override
public void componentResized( ComponentEvent e ) {
cancelPopupMenu();
}
@Override
public void componentMoved( ComponentEvent e ) {
cancelPopupMenu();
}
@Override
public void componentShown( ComponentEvent e ) {
}
@Override
public void componentHidden( ComponentEvent e ) {
cancelPopupMenu();
}
}

View File

@@ -25,12 +25,15 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -41,6 +44,9 @@ import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import javax.swing.Icon;
import javax.swing.UIDefaults;
@@ -61,7 +67,6 @@ import com.formdev.flatlaf.util.HSLColor;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.SoftCache;
import com.formdev.flatlaf.util.StringUtils;
import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale;
/**
@@ -85,15 +90,14 @@ class UIDefaultsLoader
private static final String WILDCARD_PREFIX = "*.";
static final String KEY_VARIABLES = "FlatLaf.internal.variables";
static final String KEY_PROPERTIES = "FlatLaf.internal.properties";
private static int parseColorDepth;
private static Map<String, ColorUIResource> systemColorCache;
private static final SoftCache<String, Object> fontCache = new SoftCache<>();
static void loadDefaultsFromProperties( Class<?> lookAndFeelClass, List<FlatDefaultsAddon> addons,
Properties additionalDefaults, boolean dark, UIDefaults defaults )
{
static ArrayList<Class<?>> getLafClassesForDefaultsLoading( Class<?> lookAndFeelClass ) {
// determine classes in class hierarchy in reverse order
ArrayList<Class<?>> lafClasses = new ArrayList<>();
for( Class<?> lafClass = lookAndFeelClass;
@@ -102,20 +106,62 @@ class UIDefaultsLoader
{
lafClasses.add( 0, lafClass );
}
return lafClasses;
}
loadDefaultsFromProperties( lafClasses, addons, additionalDefaults, dark, defaults );
static Properties newUIProperties( boolean dark ) {
// UI key prefixes
String lightOrDarkPrefix = FlatLaf.getUIKeyLightOrDarkPrefix( dark );
Set<String> platformPrefixes = FlatLaf.getUIKeyPlatformPrefixes();
Set<String> specialPrefixes = FlatLaf.getUIKeySpecialPrefixes();
return new Properties() {
@Override
public void load( InputStream in ) throws IOException {
// use UTF-8 to load properties file
try( Reader reader = new InputStreamReader( in, StandardCharsets.UTF_8 )) {
super.load( reader );
}
}
@Override
public synchronized Object put( Object k, Object value ) {
// process key prefixes (while loading properties files)
String key = (String) k;
while( key.startsWith( "[" ) ) {
int closeIndex = key.indexOf( ']' );
if( closeIndex < 0 )
return null; // ignore property with invalid prefix
String prefix = key.substring( 0, closeIndex + 1 );
if( specialPrefixes.contains( prefix ) )
break; // keep special prefix
if( !lightOrDarkPrefix.equals( prefix ) && !platformPrefixes.contains( prefix ) )
return null; // ignore property
// prefix is known and enabled --> remove prefix
key = key.substring( closeIndex + 1 );
}
return super.put( key, value );
}
};
}
static void loadDefaultsFromProperties( List<Class<?>> lafClasses, List<FlatDefaultsAddon> addons,
Properties additionalDefaults, boolean dark, UIDefaults defaults )
Consumer<Properties> intellijThemesHook, Properties additionalDefaults, boolean dark, UIDefaults defaults )
{
try {
// temporary cache system colors while loading defaults,
// which avoids that system color getter is invoked multiple times
systemColorCache = (FlatLaf.getSystemColorGetter() != null) ? new HashMap<>() : null;
// all properties files will be loaded into this map
Properties properties = newUIProperties( dark );
// load core properties files
Properties properties = new Properties();
for( Class<?> lafClass : lafClasses ) {
String propertiesName = '/' + lafClass.getName().replace( '.', '/' ) + ".properties";
try( InputStream in = lafClass.getResourceAsStream( propertiesName ) ) {
@@ -142,6 +188,10 @@ class UIDefaultsLoader
addonClassLoaders.add( addonClassLoader );
}
// apply IntelliJ themes properties
if( intellijThemesHook != null )
intellijThemesHook.accept( properties );
// load custom properties files (usually provided by applications)
List<Object> customDefaultsSources = FlatLaf.getCustomDefaultsSources();
int size = (customDefaultsSources != null) ? customDefaultsSources.size() : 0;
@@ -160,16 +210,46 @@ class UIDefaultsLoader
if( classLoader == null )
classLoader = FlatLaf.class.getClassLoader();
boolean found = false;
for( Class<?> lafClass : lafClasses ) {
String propertiesName = packageName + '/' + simpleClassName( lafClass ) + ".properties";
try( InputStream in = classLoader.getResourceAsStream( propertiesName ) ) {
if( in != null )
if( in != null ) {
properties.load( in );
found = true;
}
}
}
// fallback for named Java modules
if( !found ) {
// Get package URL using ClassLoader.getResource(...) because this works
// also in named Java modules, even without opening the package in module-info.java.
// This extra step is necessary because ClassLoader.getResource("<package>/<file>.properties")
// does not work for named Java modules.
URL url = classLoader.getResource( packageName );
if( url == null )
continue;
String packageUrl = url.toExternalForm();
if( !packageUrl.endsWith( "/" ) )
packageUrl = packageUrl.concat( "/" );
for( Class<?> lafClass : lafClasses ) {
URL propertiesUrl = new URL( packageUrl + simpleClassName( lafClass ) + ".properties" );
try( InputStream in = propertiesUrl.openStream() ) {
properties.load( in );
} catch( FileNotFoundException ex ) {
// ignore
}
}
}
} else if( source instanceof URL ) {
// load from package URL
URL packageUrl = (URL) source;
String packageUrl = ((URL)source).toExternalForm();
if( !packageUrl.endsWith( "/" ) )
packageUrl = packageUrl.concat( "/" );
for( Class<?> lafClass : lafClasses ) {
URL propertiesUrl = new URL( packageUrl + simpleClassName( lafClass ) + ".properties" );
@@ -198,41 +278,6 @@ class UIDefaultsLoader
if( additionalDefaults != null )
properties.putAll( additionalDefaults );
// collect all platform specific keys (but do not modify properties)
ArrayList<String> platformSpecificKeys = new ArrayList<>();
for( Object okey : properties.keySet() ) {
String key = (String) okey;
if( key.startsWith( "[" ) &&
(key.startsWith( "[win]" ) ||
key.startsWith( "[mac]" ) ||
key.startsWith( "[linux]" ) ||
key.startsWith( "[light]" ) ||
key.startsWith( "[dark]" )) )
platformSpecificKeys.add( key );
}
// remove platform specific properties and re-add only properties
// for current platform, but with platform prefix removed
if( !platformSpecificKeys.isEmpty() ) {
// handle light/dark specific properties
String lightOrDarkPrefix = dark ? "[dark]" : "[light]";
for( String key : platformSpecificKeys ) {
if( key.startsWith( lightOrDarkPrefix ) )
properties.put( key.substring( lightOrDarkPrefix.length() ), properties.remove( key ) );
}
// handle platform specific properties
String platformPrefix =
SystemInfo.isWindows ? "[win]" :
SystemInfo.isMacOS ? "[mac]" :
SystemInfo.isLinux ? "[linux]" : "[unknown]";
for( String key : platformSpecificKeys ) {
Object value = properties.remove( key );
if( key.startsWith( platformPrefix ) )
properties.put( key.substring( platformPrefix.length() ), value );
}
}
// get (and remove) wildcard replacements, which override all other defaults that end with same suffix
HashMap<String, String> wildcards = new HashMap<>();
Iterator<Entry<Object, Object>> it = properties.entrySet().iterator();
@@ -287,6 +332,15 @@ class UIDefaultsLoader
// remember variables in defaults to allow using them in styles
defaults.put( KEY_VARIABLES, variables );
// remember properties (for testing)
if( FlatSystemProperties.getBoolean( KEY_PROPERTIES, false ) ) {
Properties properties2 = new Properties();
properties2.putAll( properties );
for( Map.Entry<String, String> e : wildcards.entrySet() )
properties2.put( WILDCARD_PREFIX + e.getKey(), e.getValue() );
defaults.put( KEY_PROPERTIES, properties2 );
}
// clear/disable system color cache
systemColorCache = null;
} catch( IOException ex ) {
@@ -378,7 +432,7 @@ class UIDefaultsLoader
enum ValueType { UNKNOWN, STRING, BOOLEAN, CHARACTER, INTEGER, INTEGERORFLOAT, FLOAT, BORDER, ICON, INSETS, DIMENSION, COLOR, FONT,
SCALEDINTEGER, SCALEDFLOAT, SCALEDINSETS, SCALEDDIMENSION, INSTANCE, CLASS, GRAYFILTER, NULL, LAZY }
private static final ValueType[] tempResultValueType = new ValueType[1];
private static final AtomicReference<ValueType> tempResultValueType = new AtomicReference<>();
private static Map<Class<?>, ValueType> javaValueTypes;
private static Map<String, ValueType> knownValueTypes;
@@ -388,7 +442,7 @@ class UIDefaultsLoader
return parseValue( key, value, valueType, null, v -> v, Collections.emptyList() );
}
static Object parseValue( String key, String value, Class<?> javaValueType, ValueType[] resultValueType,
static Object parseValue( String key, String value, Class<?> javaValueType, AtomicReference<ValueType> resultValueType,
Function<String, String> resolver, List<ClassLoader> addonClassLoaders )
throws IllegalArgumentException
{
@@ -397,7 +451,7 @@ class UIDefaultsLoader
// do not parse styles here
if( key.startsWith( "[style]" ) ) {
resultValueType[0] = ValueType.STRING;
resultValueType.set( ValueType.STRING );
return value;
}
@@ -405,7 +459,7 @@ class UIDefaultsLoader
// null
if( value.equals( "null" ) || value.isEmpty() ) {
resultValueType[0] = ValueType.NULL;
resultValueType.set( ValueType.NULL );
return null;
}
@@ -461,14 +515,14 @@ class UIDefaultsLoader
} else {
// false, true
switch( value ) {
case "false": resultValueType[0] = ValueType.BOOLEAN; return false;
case "true": resultValueType[0] = ValueType.BOOLEAN; return true;
case "false": resultValueType.set( ValueType.BOOLEAN ); return false;
case "true": resultValueType.set( ValueType.BOOLEAN ); return true;
}
// check for function "lazy"
// Syntax: lazy(uiKey)
if( value.startsWith( "lazy(" ) && value.endsWith( ")" ) ) {
resultValueType[0] = ValueType.LAZY;
resultValueType.set( ValueType.LAZY );
String uiKey = StringUtils.substringTrimmed( value, 5, value.length() - 1 );
return (LazyValue) t -> {
return lazyUIManagerGet( uiKey );
@@ -549,7 +603,7 @@ class UIDefaultsLoader
}
}
resultValueType[0] = valueType;
resultValueType.set( valueType );
// parse value
switch( valueType ) {
@@ -576,14 +630,14 @@ class UIDefaultsLoader
default:
// string
if( value.startsWith( "\"" ) && value.endsWith( "\"" ) ) {
resultValueType[0] = ValueType.STRING;
resultValueType.set( ValueType.STRING );
return value.substring( 1, value.length() - 1 );
}
// colors
if( value.startsWith( "#" ) || value.endsWith( ")" ) ) {
Object color = parseColorOrFunction( value, resolver );
resultValueType[0] = (color != null) ? ValueType.COLOR : ValueType.NULL;
resultValueType.set( (color != null) ? ValueType.COLOR : ValueType.NULL );
return color;
}
@@ -595,7 +649,7 @@ class UIDefaultsLoader
// integer
try {
Integer integer = parseInteger( value );
resultValueType[0] = ValueType.INTEGER;
resultValueType.set( ValueType.INTEGER );
return integer;
} catch( NumberFormatException ex ) {
// ignore
@@ -604,7 +658,7 @@ class UIDefaultsLoader
// float
try {
Float f = parseFloat( value );
resultValueType[0] = ValueType.FLOAT;
resultValueType.set( ValueType.FLOAT );
return f;
} catch( NumberFormatException ex ) {
// ignore
@@ -612,7 +666,7 @@ class UIDefaultsLoader
}
// string
resultValueType[0] = ValueType.STRING;
resultValueType.set( ValueType.STRING );
return value;
}
}
@@ -637,22 +691,26 @@ class UIDefaultsLoader
if( value.indexOf( ',' ) >= 0 ) {
// Syntax: top,left,bottom,right[,lineColor[,lineThickness[,arc]]]
List<String> parts = splitFunctionParams( value, ',' );
Insets insets = parseInsets( value );
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) && !parts.get( 6 ).isEmpty()
? parseInteger( parts.get( 6 ) )
: -1;
try {
Insets insets = parseInsets( value );
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) && !parts.get( 6 ).isEmpty()
? parseInteger( parts.get( 6 ) )
: -1;
return (LazyValue) t -> {
return (lineColor != null || arc > 0)
? new FlatLineBorder( insets, lineColor, lineThickness, arc )
: new FlatEmptyBorder( insets );
};
return (LazyValue) t -> {
return (lineColor != null || arc > 0)
? new FlatLineBorder( insets, lineColor, lineThickness, arc )
: new FlatEmptyBorder( insets );
};
} catch( RuntimeException ex ) {
throw new IllegalArgumentException( "invalid border '" + value + "' (" + ex.getMessage() + ")" );
}
} else
return parseInstance( value, resolver, addonClassLoaders );
}
@@ -723,7 +781,7 @@ class UIDefaultsLoader
Integer.parseInt( numbers.get( 1 ) ),
Integer.parseInt( numbers.get( 2 ) ),
Integer.parseInt( numbers.get( 3 ) ) );
} catch( NumberFormatException ex ) {
} catch( NumberFormatException | IndexOutOfBoundsException ex ) {
throw new IllegalArgumentException( "invalid insets '" + value + "'" );
}
}
@@ -736,7 +794,7 @@ class UIDefaultsLoader
return new DimensionUIResource(
Integer.parseInt( numbers.get( 0 ) ),
Integer.parseInt( numbers.get( 1 ) ) );
} catch( NumberFormatException ex ) {
} catch( NumberFormatException | IndexOutOfBoundsException ex ) {
throw new IllegalArgumentException( "invalid size '" + value + "'" );
}
}
@@ -830,6 +888,7 @@ class UIDefaultsLoader
try {
switch( function ) {
case "if": return parseColorIf( value, params, resolver );
case "lazy": return parseColorLazy( value, params, resolver );
case "systemColor": return parseColorSystemColor( value, params, resolver );
case "rgb": return parseColorRgbOrRgba( false, params, resolver );
case "rgba": return parseColorRgbOrRgba( true, params, resolver );
@@ -877,6 +936,32 @@ class UIDefaultsLoader
return parseColorOrFunction( resolver.apply( ifValue ), resolver );
}
/**
* Syntax: lazy(uiKey)
* <p>
* This "lazy" function is only used if the "lazy" is passed as parameter to another
* color function. Otherwise, the general "lazy" function is used.
* <p>
* Note: The color is resolved immediately, not lazy, because it is passed as parameter to another color function.
* So e.g. {@code darken(lazy(List.background), 10%)} is the same as {@code darken($List.background, 10%)}.
* <p>
* Only useful if a property is defined as lazy and that property is used
* in another property's color function. E.g.
*
* <pre>{@code
* someProperty = lazy(List.background)
* anotherProperty = darken($someProperty, 10%)
* }</pre>
*/
private static Object parseColorLazy( String value, List<String> params, Function<String, String> resolver )
throws IllegalArgumentException
{
if( params.size() != 1 )
throw newMissingParametersException( value );
return parseColorOrFunction( resolver.apply( PROPERTY_PREFIX + params.get( 0 ) ), resolver );
}
/**
* Syntax: systemColor(name[,defaultValue])
* - name: system color name
@@ -974,7 +1059,7 @@ class UIDefaultsLoader
* fadein(color,amount[,options]) or fadeout(color,amount[,options])
* - color: a color (e.g. #f00) or a color function
* - amount: percentage 0-100%
* - options: [relative] [autoInverse] [noAutoInverse] [lazy] [derived]
* - options: [relative] [autoInverse] [noAutoInverse] [derived] [lazy]
*/
private static Object parseColorHSLIncreaseDecrease( int hslIndex, boolean increase,
List<String> params, Function<String, String> resolver )
@@ -984,15 +1069,15 @@ class UIDefaultsLoader
int amount = parsePercentage( params.get( 1 ) );
boolean relative = false;
boolean autoInverse = false;
boolean lazy = false;
boolean derived = false;
boolean lazy = false;
if( params.size() > 2 ) {
String options = params.get( 2 );
relative = options.contains( "relative" );
autoInverse = options.contains( "autoInverse" );
lazy = options.contains( "lazy" );
derived = options.contains( "derived" );
lazy = options.contains( "lazy" );
// use autoInverse by default for derived colors, except if noAutoInverse is set
if( derived && !options.contains( "noAutoInverse" ) )
@@ -1003,14 +1088,8 @@ class UIDefaultsLoader
ColorFunction function = new ColorFunctions.HSLIncreaseDecrease(
hslIndex, increase, amount, relative, autoInverse );
if( lazy ) {
return (LazyValue) t -> {
Object color = lazyUIManagerGet( colorStr );
return (color instanceof Color)
? new ColorUIResource( ColorFunctions.applyFunctions( (Color) color, function ) )
: null;
};
}
if( lazy )
return newLazyColorFunction( colorStr, function );
// parse base color, apply function and create derived color
return parseFunctionBaseColor( colorStr, function, derived, resolver );
@@ -1039,14 +1118,8 @@ class UIDefaultsLoader
// create function
ColorFunction function = new ColorFunctions.Fade( amount );
if( lazy ) {
return (LazyValue) t -> {
Object color = lazyUIManagerGet( colorStr );
return (color instanceof Color)
? new ColorUIResource( ColorFunctions.applyFunctions( (Color) color, function ) )
: null;
};
}
if( lazy )
return newLazyColorFunction( colorStr, function );
// parse base color, apply function and create derived color
return parseFunctionBaseColor( colorStr, function, derived, resolver );
@@ -1056,7 +1129,7 @@ class UIDefaultsLoader
* Syntax: spin(color,angle[,options])
* - color: a color (e.g. #f00) or a color function
* - angle: number of degrees to rotate
* - options: [derived]
* - options: [derived] [lazy]
*/
private static Object parseColorSpin( List<String> params, Function<String, String> resolver )
throws IllegalArgumentException
@@ -1064,15 +1137,20 @@ class UIDefaultsLoader
String colorStr = params.get( 0 );
int amount = parseInteger( params.get( 1 ) );
boolean derived = false;
boolean lazy = false;
if( params.size() > 2 ) {
String options = params.get( 2 );
derived = options.contains( "derived" );
lazy = options.contains( "lazy" );
}
// create function
ColorFunction function = new ColorFunctions.HSLIncreaseDecrease( 0, true, amount, false, false );
if( lazy )
return newLazyColorFunction( colorStr, function );
// parse base color, apply function and create derived color
return parseFunctionBaseColor( colorStr, function, derived, resolver );
}
@@ -1084,7 +1162,7 @@ class UIDefaultsLoader
* changeAlpha(color,value[,options])
* - color: a color (e.g. #f00) or a color function
* - value: for hue: number of degrees; otherwise: percentage 0-100%
* - options: [derived]
* - options: [derived] [lazy]
*/
private static Object parseColorChange( int hslIndex,
List<String> params, Function<String, String> resolver )
@@ -1095,27 +1173,33 @@ class UIDefaultsLoader
? parseInteger( params.get( 1 ) )
: parsePercentage( params.get( 1 ) );
boolean derived = false;
boolean lazy = false;
if( params.size() > 2 ) {
String options = params.get( 2 );
derived = options.contains( "derived" );
lazy = options.contains( "lazy" );
}
// create function
ColorFunction function = new ColorFunctions.HSLChange( hslIndex, value );
if( lazy )
return newLazyColorFunction( colorStr, function );
// parse base color, apply function and create derived color
return parseFunctionBaseColor( colorStr, function, derived, resolver );
}
/**
* Syntax: mix(color1,color2[,weight]) or
* tint(color[,weight]) or
* shade(color[,weight])
* Syntax: mix(color1,color2[,weight][,options]) or
* tint(color[,weight][,options]) or
* shade(color[,weight][,options])
* - color1: a color (e.g. #f00) or a color function
* - color2: a color (e.g. #f00) or a color function
* - weight: the weight (in range 0-100%) to mix the two colors
* larger weight uses more of first color, smaller weight more of second color
* - options: [derived] [lazy]
*/
private static Object parseColorMix( String color1Str, List<String> params, Function<String, String> resolver )
throws IllegalArgumentException
@@ -1124,18 +1208,36 @@ class UIDefaultsLoader
if( color1Str == null )
color1Str = params.get( i++ );
String color2Str = params.get( i++ );
int weight = (params.size() > i) ? parsePercentage( params.get( i ) ) : 50;
int weight = 50;
boolean derived = false;
boolean lazy = false;
if( params.size() > i ) {
String weightStr = params.get( i );
if( !weightStr.isEmpty() && Character.isDigit( weightStr.charAt( 0 ) ) ) {
weight = parsePercentage( weightStr );
i++;
}
}
if( params.size() > i ) {
String options = params.get( i );
derived = options.contains( "derived" );
lazy = options.contains( "lazy" );
}
// parse second color
ColorUIResource color2 = (ColorUIResource) parseColorOrFunction( resolver.apply( color2Str ), resolver );
if( color2 == null )
ColorUIResource color1 = (ColorUIResource) parseColorOrFunction( resolver.apply( color1Str ), resolver );
if( color1 == null )
return null;
// create function
ColorFunction function = new ColorFunctions.Mix( color2, weight );
ColorFunction function = new ColorFunctions.Mix2( color1, weight );
if( lazy )
return newLazyColorFunction( color2Str, function );
// parse first color, apply function and create mixed color
return parseFunctionBaseColor( color1Str, function, false, resolver );
return parseFunctionBaseColor( color2Str, function, derived, resolver );
}
/**
@@ -1229,6 +1331,15 @@ class UIDefaultsLoader
return new ColorUIResource( newColor );
}
private static LazyValue newLazyColorFunction( String uiKey, ColorFunction function ) {
return (LazyValue) t -> {
Object color = lazyUIManagerGet( uiKey );
return (color instanceof Color)
? new ColorUIResource( ColorFunctions.applyFunctions( (Color) color, function ) )
: null;
};
}
/**
* Syntax: [normal] [bold|+bold|-bold] [italic|+italic|-italic] [<size>|+<incr>|-<decr>|<percent>%] [family[, family]] [$baseFontKey]
*/
@@ -1314,17 +1425,17 @@ class UIDefaultsLoader
break;
}
}
} catch( IOException ex ) {
throw new IllegalArgumentException( ex );
} catch( RuntimeException | IOException ex ) {
throw new IllegalArgumentException( "invalid font '" + value + "' (" + ex.getMessage() + ")" );
}
if( style != -1 && styleChange != 0 )
throw new IllegalArgumentException( "can not mix absolute style (e.g. 'bold') with derived style (e.g. '+italic') in '" + value + "'" );
throw new IllegalArgumentException( "invalid font '" + value + "': can not mix absolute style (e.g. 'bold') with derived style (e.g. '+italic')" );
if( styleChange != 0 ) {
if( (styleChange & Font.BOLD) != 0 && (styleChange & (Font.BOLD << 16)) != 0 )
throw new IllegalArgumentException( "can not use '+bold' and '-bold' in '" + value + "'" );
throw new IllegalArgumentException( "invalid font '" + value + "': can not use '+bold' and '-bold'" );
if( (styleChange & Font.ITALIC) != 0 && (styleChange & (Font.ITALIC << 16)) != 0 )
throw new IllegalArgumentException( "can not use '+italic' and '-italic' in '" + value + "'" );
throw new IllegalArgumentException( "invalid font '" + value + "': can not use '+italic' and '-italic'" );
}
font = new FlatLaf.ActiveFont( baseFontKey, families, style, styleChange, absoluteSize, relativeSize, scaleSize );
@@ -1464,7 +1575,7 @@ class UIDefaultsLoader
return (LazyValue) t -> {
return new GrayFilter( brightness, contrast, alpha );
};
} catch( NumberFormatException ex ) {
} catch( NumberFormatException | IndexOutOfBoundsException ex ) {
throw new IllegalArgumentException( "invalid gray filter '" + value + "'" );
}
}

View File

@@ -16,7 +16,6 @@
package com.formdev.flatlaf.icons;
import static com.formdev.flatlaf.util.UIScale.*;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
@@ -29,7 +28,7 @@ import com.formdev.flatlaf.util.UIScale;
/**
* Base class for icons that scales width and height, creates and initializes
* a scaled graphics context for icon painting.
*
* <p>
* Subclasses do not need to scale icon painting.
*
* @author Karl Tauber
@@ -37,10 +36,15 @@ import com.formdev.flatlaf.util.UIScale;
public abstract class FlatAbstractIcon
implements Icon, UIResource
{
/** Unscaled icon width. */
protected final int width;
/** Unscaled icon height. */
protected final int height;
protected Color color;
/** Additional icon scale factor. */
private float scale = 1;
public FlatAbstractIcon( int width, int height, Color color ) {
this.width = width;
this.height = height;
@@ -61,6 +65,9 @@ public abstract class FlatAbstractIcon
g2.translate( x, y );
UIScale.scaleGraphics( g2 );
float scale = getScale();
if( scale != 1 )
g2.scale( scale, scale );
if( color != null )
g2.setColor( color );
@@ -71,19 +78,71 @@ public abstract class FlatAbstractIcon
}
}
/** @since 3.5.2 */
/**
* Paints icon background. Default implementation does nothing.
* Can be overridden to paint specific icon background.
* <p>
* The bounds of the area to be filled are:
* x, y, {@link #getIconWidth()}, {@link #getIconHeight()}.
* <p>
* In contrast to {@link #paintIcon(Component, Graphics2D)},
* the graphics context {@code g} is not translated and not scaled.
*
* @since 3.5.2
*/
protected void paintBackground( Component c, Graphics2D g, int x, int y ) {
}
/**
* Paints icon.
* <p>
* The graphics context is translated and scaled.
* This means that icon x,y coordinates are {@code 0,0}
* and it is not necessary to scale coordinates within this method.
* <p>
* The bounds to be used for icon painting are:
* 0, 0, {@link #width}, {@link #height}.
*/
protected abstract void paintIcon( Component c, Graphics2D g );
/**
* Returns the scaled icon width.
*/
@Override
public int getIconWidth() {
return scale( width );
return scale( UIScale.scale( width ) );
}
/**
* Returns the scaled icon height.
*/
@Override
public int getIconHeight() {
return scale( height );
return scale( UIScale.scale( height ) );
}
/** @since 3.7 */
public float getScale() {
return scale;
}
/** @since 3.7 */
public void setScale( float scale ) {
this.scale = scale;
}
/**
* Multiplies the given value by the icon scale factor {@link #getScale()} and rounds the result.
* <p>
* If you want scale a {@code float} or {@code double} value,
* simply use: {@code myFloatValue * }{@link #getScale()}.
* <p>
* Do not use this method when painting icon in {@link #paintIcon(Component, Graphics2D)}.
*
* @since 3.7
*/
protected int scale( int size ) {
float scale = getScale();
return (scale == 1) ? size : Math.round( size * scale );
}
}

View File

@@ -17,7 +17,6 @@
package com.formdev.flatlaf.icons;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
@@ -26,7 +25,8 @@ import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
@@ -36,8 +36,11 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
*
* @author Karl Tauber
*/
@StyleableField( cls=FlatAbstractIcon.class, key="capsLockIconScale", fieldName="scale" )
@StyleableField( cls=FlatAbstractIcon.class, key="capsLockIconColor", fieldName="color" )
public class FlatCapsLockIcon
extends FlatAbstractIcon
implements StyleableObject
{
private Path2D path;
@@ -45,23 +48,6 @@ public class FlatCapsLockIcon
super( 16, 16, UIManager.getColor( "PasswordField.capsLockIconColor" ) );
}
/** @since 2 */
public Object applyStyleProperty( String key, Object value ) {
Object oldValue;
switch( key ) {
case "capsLockIconColor": oldValue = color; color = (Color) value; return oldValue;
default: throw new UnknownStyleException( key );
}
}
/** @since 2.5 */
public Object getStyleableValue( String key ) {
switch( key ) {
case "capsLockIconColor": return color;
default: return null;
}
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
/*

View File

@@ -17,19 +17,20 @@
package com.formdev.flatlaf.icons;
import static com.formdev.flatlaf.FlatClientProperties.*;
import static com.formdev.flatlaf.ui.FlatUIUtils.stateColor;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.geom.Path2D;
import java.awt.geom.RoundRectangle2D;
import java.util.Map;
import javax.swing.AbstractButton;
import javax.swing.JComponent;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatButtonUI;
import com.formdev.flatlaf.ui.FlatStylingSupport;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
@@ -48,6 +49,8 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
* @uiDefault CheckBox.icon.borderWidth int or float optional; defaults to Component.borderWidth
* @uiDefault CheckBox.icon.selectedBorderWidth int or float optional; defaults to CheckBox.icon.borderWidth
* @uiDefault CheckBox.icon.disabledSelectedBorderWidth int or float optional; defaults to CheckBox.icon.selectedBorderWidth
* @uiDefault CheckBox.icon.indeterminateBorderWidth int or float optional; defaults to CheckBox.icon.selectedBorderWidth
* @uiDefault CheckBox.icon.disabledIndeterminateBorderWidth int or float optional; defaults to CheckBox.icon.disabledSelectedBorderWidth
* @uiDefault CheckBox.arc int
*
* @uiDefault CheckBox.icon.focusColor Color optional; defaults to Component.focusColor
@@ -56,35 +59,52 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
* @uiDefault CheckBox.icon.selectedBorderColor Color
* @uiDefault CheckBox.icon.selectedBackground Color
* @uiDefault CheckBox.icon.checkmarkColor Color
* @uiDefault CheckBox.icon.indeterminateBorderColor Color optional; defaults to CheckBox.icon.selectedBorderColor
* @uiDefault CheckBox.icon.indeterminateBackground Color optional; defaults to CheckBox.icon.selectedBackground
* @uiDefault CheckBox.icon.indeterminateCheckmarkColor Color optional; defaults to CheckBox.icon.checkmarkColor
*
* @uiDefault CheckBox.icon.disabledBorderColor Color
* @uiDefault CheckBox.icon.disabledBackground Color
* @uiDefault CheckBox.icon.disabledSelectedBorderColor Color optional; CheckBox.icon.disabledBorderColor is used if not specified
* @uiDefault CheckBox.icon.disabledSelectedBackground Color optional; CheckBox.icon.disabledBackground is used if not specified
* @uiDefault CheckBox.icon.disabledCheckmarkColor Color
* @uiDefault CheckBox.icon.disabledBorderColor Color
* @uiDefault CheckBox.icon.disabledBackground Color
* @uiDefault CheckBox.icon.disabledSelectedBorderColor Color optional; defaults to CheckBox.icon.disabledBorderColor
* @uiDefault CheckBox.icon.disabledSelectedBackground Color optional; defaults to CheckBox.icon.disabledBackground
* @uiDefault CheckBox.icon.disabledCheckmarkColor Color
* @uiDefault CheckBox.icon.disabledIndeterminateBorderColor Color optional; defaults to CheckBox.icon.disabledSelectedBorderColor
* @uiDefault CheckBox.icon.disabledIndeterminateBackground Color optional; defaults to CheckBox.icon.disabledSelectedBackground
* @uiDefault CheckBox.icon.disabledIndeterminateCheckmarkColor Color optional; defaults to CheckBox.icon.disabledCheckmarkColor
*
* @uiDefault CheckBox.icon.focusedBorderColor Color optional
* @uiDefault CheckBox.icon.focusedBackground Color optional
* @uiDefault CheckBox.icon.focusedSelectedBorderColor Color optional; CheckBox.icon.focusedBorderColor is used if not specified
* @uiDefault CheckBox.icon.focusedSelectedBackground Color optional; CheckBox.icon.focusedBackground is used if not specified
* @uiDefault CheckBox.icon.focusedCheckmarkColor Color optional; CheckBox.icon.checkmarkColor is used if not specified
* @uiDefault CheckBox.icon.focusedBorderColor Color optional
* @uiDefault CheckBox.icon.focusedBackground Color optional
* @uiDefault CheckBox.icon.focusedSelectedBorderColor Color optional; defaults to CheckBox.icon.focusedBorderColor
* @uiDefault CheckBox.icon.focusedSelectedBackground Color optional; defaults to CheckBox.icon.focusedBackground
* @uiDefault CheckBox.icon.focusedCheckmarkColor Color optional; defaults to CheckBox.icon.checkmarkColor
* @uiDefault CheckBox.icon.focusedIndeterminateBorderColor Color optional; defaults to CheckBox.icon.focusedSelectedBorderColor
* @uiDefault CheckBox.icon.focusedIndeterminateBackground Color optional; defaults to CheckBox.icon.focusedSelectedBackground
* @uiDefault CheckBox.icon.focusedIndeterminateCheckmarkColor Color optional; defaults to CheckBox.icon.focusedCheckmarkColor
*
* @uiDefault CheckBox.icon.hoverBorderColor Color optional
* @uiDefault CheckBox.icon.hoverBackground Color optional
* @uiDefault CheckBox.icon.hoverSelectedBorderColor Color optional; CheckBox.icon.hoverBorderColor is used if not specified
* @uiDefault CheckBox.icon.hoverSelectedBackground Color optional; CheckBox.icon.hoverBackground is used if not specified
* @uiDefault CheckBox.icon.hoverCheckmarkColor Color optional; CheckBox.icon.checkmarkColor is used if not specified
* @uiDefault CheckBox.icon.hoverBorderColor Color optional
* @uiDefault CheckBox.icon.hoverBackground Color optional
* @uiDefault CheckBox.icon.hoverSelectedBorderColor Color optional; defaults to CheckBox.icon.hoverBorderColor
* @uiDefault CheckBox.icon.hoverSelectedBackground Color optional; defaults to CheckBox.icon.hoverBackground
* @uiDefault CheckBox.icon.hoverCheckmarkColor Color optional; defaults to CheckBox.icon.checkmarkColor
* @uiDefault CheckBox.icon.hoverIndeterminateBorderColor Color optional; defaults to CheckBox.icon.hoverSelectedBorderColor
* @uiDefault CheckBox.icon.hoverIndeterminateBackground Color optional; defaults to CheckBox.icon.hoverSelectedBackground
* @uiDefault CheckBox.icon.hoverIndeterminateCheckmarkColor Color optional; defaults to CheckBox.icon.hoverCheckmarkColor
*
* @uiDefault CheckBox.icon.pressedBorderColor Color optional
* @uiDefault CheckBox.icon.pressedBackground Color optional
* @uiDefault CheckBox.icon.pressedSelectedBorderColor Color optional; CheckBox.icon.pressedBorderColor is used if not specified
* @uiDefault CheckBox.icon.pressedSelectedBackground Color optional; CheckBox.icon.pressedBackground is used if not specified
* @uiDefault CheckBox.icon.pressedCheckmarkColor Color optional; CheckBox.icon.checkmarkColor is used if not specified
* @uiDefault CheckBox.icon.pressedBorderColor Color optional
* @uiDefault CheckBox.icon.pressedBackground Color optional
* @uiDefault CheckBox.icon.pressedSelectedBorderColor Color optional; defaults to CheckBox.icon.pressedBorderColor
* @uiDefault CheckBox.icon.pressedSelectedBackground Color optional; defaults to CheckBox.icon.pressedBackground
* @uiDefault CheckBox.icon.pressedCheckmarkColor Color optional; defaults to CheckBox.icon.checkmarkColor
* @uiDefault CheckBox.icon.pressedIndeterminateBorderColor Color optional; defaults to CheckBox.icon.pressedSelectedBorderColor
* @uiDefault CheckBox.icon.pressedIndeterminateBackground Color optional; defaults to CheckBox.icon.pressedSelectedBackground
* @uiDefault CheckBox.icon.pressedIndeterminateCheckmarkColor Color optional; defaults to CheckBox.icon.pressedCheckmarkColor
*
* @author Karl Tauber
*/
@StyleableField( cls=FlatAbstractIcon.class, key="scale" )
public class FlatCheckBoxIcon
extends FlatAbstractIcon
implements StyleableObject
{
protected final String style = UIManager.getString( getPropertyPrefix() + "icon.style" );
@Styleable protected float focusWidth = getUIFloat( "CheckBox.icon.focusWidth", UIManager.getInt( "Component.focusWidth" ), style );
@@ -92,6 +112,8 @@ public class FlatCheckBoxIcon
/** @since 2 */ @Styleable protected float borderWidth = getUIFloat( "CheckBox.icon.borderWidth", FlatUIUtils.getUIFloat( "Component.borderWidth", 1 ), style );
/** @since 2 */ @Styleable protected float selectedBorderWidth = getUIFloat( "CheckBox.icon.selectedBorderWidth", Float.MIN_VALUE, style );
/** @since 2 */ @Styleable protected float disabledSelectedBorderWidth = getUIFloat( "CheckBox.icon.disabledSelectedBorderWidth", Float.MIN_VALUE, style );
/** @since 3.6 */ @Styleable protected float indeterminateBorderWidth = getUIFloat( "CheckBox.icon.indeterminateBorderWidth", Float.MIN_VALUE, style );
/** @since 3.6 */ @Styleable protected float disabledIndeterminateBorderWidth = getUIFloat( "CheckBox.icon.disabledIndeterminateBorderWidth", Float.MIN_VALUE, style );
@Styleable protected int arc = FlatUIUtils.getUIInt( "CheckBox.arc", 2 );
// enabled
@@ -100,6 +122,9 @@ public class FlatCheckBoxIcon
@Styleable protected Color selectedBorderColor = getUIColor( "CheckBox.icon.selectedBorderColor", style );
@Styleable protected Color selectedBackground = getUIColor( "CheckBox.icon.selectedBackground", style );
@Styleable protected Color checkmarkColor = getUIColor( "CheckBox.icon.checkmarkColor", style );
/** @since 3.6 */ @Styleable protected Color indeterminateBorderColor = getUIColor( "CheckBox.icon.indeterminateBorderColor", style );
/** @since 3.6 */ @Styleable protected Color indeterminateBackground = getUIColor( "CheckBox.icon.indeterminateBackground", style );
/** @since 3.6 */ @Styleable protected Color indeterminateCheckmarkColor = getUIColor( "CheckBox.icon.indeterminateCheckmarkColor", style );
// disabled
@Styleable protected Color disabledBorderColor = getUIColor( "CheckBox.icon.disabledBorderColor", style );
@@ -107,6 +132,9 @@ public class FlatCheckBoxIcon
/** @since 2 */ @Styleable protected Color disabledSelectedBorderColor = getUIColor( "CheckBox.icon.disabledSelectedBorderColor", style );
/** @since 2 */ @Styleable protected Color disabledSelectedBackground = getUIColor( "CheckBox.icon.disabledSelectedBackground", style );
@Styleable protected Color disabledCheckmarkColor = getUIColor( "CheckBox.icon.disabledCheckmarkColor", style );
/** @since 3.6 */ @Styleable protected Color disabledIndeterminateBorderColor = getUIColor( "CheckBox.icon.disabledIndeterminateBorderColor", style );
/** @since 3.6 */ @Styleable protected Color disabledIndeterminateBackground = getUIColor( "CheckBox.icon.disabledIndeterminateBackground", style );
/** @since 3.6 */ @Styleable protected Color disabledIndeterminateCheckmarkColor = getUIColor( "CheckBox.icon.disabledIndeterminateCheckmarkColor", style );
// focused
@Styleable protected Color focusedBorderColor = getUIColor( "CheckBox.icon.focusedBorderColor", style );
@@ -114,6 +142,9 @@ public class FlatCheckBoxIcon
/** @since 2 */ @Styleable protected Color focusedSelectedBorderColor = getUIColor( "CheckBox.icon.focusedSelectedBorderColor", style );
/** @since 2 */ @Styleable protected Color focusedSelectedBackground = getUIColor( "CheckBox.icon.focusedSelectedBackground", style );
/** @since 2 */ @Styleable protected Color focusedCheckmarkColor = getUIColor( "CheckBox.icon.focusedCheckmarkColor", style );
/** @since 3.6 */ @Styleable protected Color focusedIndeterminateBorderColor = getUIColor( "CheckBox.icon.focusedIndeterminateBorderColor", style );
/** @since 3.6 */ @Styleable protected Color focusedIndeterminateBackground = getUIColor( "CheckBox.icon.focusedIndeterminateBackground", style );
/** @since 3.6 */ @Styleable protected Color focusedIndeterminateCheckmarkColor = getUIColor( "CheckBox.icon.focusedIndeterminateCheckmarkColor", style );
// hover
@Styleable protected Color hoverBorderColor = getUIColor( "CheckBox.icon.hoverBorderColor", style );
@@ -121,6 +152,9 @@ public class FlatCheckBoxIcon
/** @since 2 */ @Styleable protected Color hoverSelectedBorderColor = getUIColor( "CheckBox.icon.hoverSelectedBorderColor", style );
/** @since 2 */ @Styleable protected Color hoverSelectedBackground = getUIColor( "CheckBox.icon.hoverSelectedBackground", style );
/** @since 2 */ @Styleable protected Color hoverCheckmarkColor = getUIColor( "CheckBox.icon.hoverCheckmarkColor", style );
/** @since 3.6 */ @Styleable protected Color hoverIndeterminateBorderColor = getUIColor( "CheckBox.icon.hoverIndeterminateBorderColor", style );
/** @since 3.6 */ @Styleable protected Color hoverIndeterminateBackground = getUIColor( "CheckBox.icon.hoverIndeterminateBackground", style );
/** @since 3.6 */ @Styleable protected Color hoverIndeterminateCheckmarkColor = getUIColor( "CheckBox.icon.hoverIndeterminateCheckmarkColor", style );
// pressed
/** @since 2 */ @Styleable protected Color pressedBorderColor = getUIColor( "CheckBox.icon.pressedBorderColor", style );
@@ -128,6 +162,9 @@ public class FlatCheckBoxIcon
/** @since 2 */ @Styleable protected Color pressedSelectedBorderColor = getUIColor( "CheckBox.icon.pressedSelectedBorderColor", style );
/** @since 2 */ @Styleable protected Color pressedSelectedBackground = getUIColor( "CheckBox.icon.pressedSelectedBackground", style );
/** @since 2 */ @Styleable protected Color pressedCheckmarkColor = getUIColor( "CheckBox.icon.pressedCheckmarkColor", style );
/** @since 3.6 */ @Styleable protected Color pressedIndeterminateBorderColor = getUIColor( "CheckBox.icon.pressedIndeterminateBorderColor", style );
/** @since 3.6 */ @Styleable protected Color pressedIndeterminateBackground = getUIColor( "CheckBox.icon.pressedIndeterminateBackground", style );
/** @since 3.6 */ @Styleable protected Color pressedIndeterminateCheckmarkColor = getUIColor( "CheckBox.icon.pressedIndeterminateCheckmarkColor", style );
protected String getPropertyPrefix() {
return "CheckBox.";
@@ -162,31 +199,22 @@ public class FlatCheckBoxIcon
super( ICON_SIZE, ICON_SIZE, null );
}
/** @since 2 */
public Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
}
/** @since 2 */
public Map<String, Class<?>> getStyleableInfos() {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
public Object getStyleableValue( String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
boolean indeterminate = isIndeterminate( c );
boolean selected = indeterminate || isSelected( c );
boolean isFocused = FlatUIUtils.isPermanentFocusOwner( c );
float bw = selected
? (disabledSelectedBorderWidth != Float.MIN_VALUE && !c.isEnabled()
? disabledSelectedBorderWidth
: (selectedBorderWidth != Float.MIN_VALUE ? selectedBorderWidth : borderWidth))
: borderWidth;
float bw = Float.MIN_VALUE;
if( !c.isEnabled() ) {
bw = (indeterminate && disabledIndeterminateBorderWidth != Float.MIN_VALUE)
? disabledIndeterminateBorderWidth
: (selected ? disabledSelectedBorderWidth : selectedBorderWidth);
}
if( bw == Float.MIN_VALUE ) {
bw = (indeterminate && indeterminateBorderWidth != Float.MIN_VALUE)
? indeterminateBorderWidth
: ((selected && selectedBorderWidth != Float.MIN_VALUE) ? selectedBorderWidth : borderWidth);
}
// paint focused border
if( isFocused && focusWidth > 0 && FlatButtonUI.isFocusPainted( c ) ) {
@@ -195,15 +223,15 @@ public class FlatCheckBoxIcon
}
// paint border
g.setColor( getBorderColor( c, selected ) );
g.setColor( getBorderColor( c, selected, indeterminate ) );
paintBorder( c, g, bw );
// paint background
Color bg = FlatUIUtils.deriveColor( getBackground( c, selected ),
selected ? selectedBackground : background );
Color baseBg = stateColor( indeterminate, indeterminateBackground, selected, selectedBackground, background );
Color bg = FlatUIUtils.deriveColor( getBackground( c, selected, indeterminate ), baseBg );
if( bg.getAlpha() < 255 ) {
// fill background with default color before filling with non-opaque background
g.setColor( selected ? selectedBackground : background );
g.setColor( baseBg );
paintBackground( c, g, bw );
}
g.setColor( bg );
@@ -211,7 +239,7 @@ public class FlatCheckBoxIcon
// paint checkmark
if( selected ) {
g.setColor( getCheckmarkColor( c ) );
g.setColor( getCheckmarkColor( c, indeterminate ) );
if( indeterminate )
paintIndeterminate( c, g );
else
@@ -265,37 +293,40 @@ public class FlatCheckBoxIcon
/** @since 2 */
public float getFocusWidth() {
return focusWidth;
return focusWidth * getScale();
}
protected Color getFocusColor( Component c ) {
return focusColor;
}
protected Color getBorderColor( Component c, boolean selected ) {
/** @since 3.6 */
protected Color getBorderColor( Component c, boolean selected, boolean indeterminate ) {
return FlatButtonUI.buttonStateColor( c,
selected ? selectedBorderColor : borderColor,
(selected && disabledSelectedBorderColor != null) ? disabledSelectedBorderColor : disabledBorderColor,
(selected && focusedSelectedBorderColor != null) ? focusedSelectedBorderColor : focusedBorderColor,
(selected && hoverSelectedBorderColor != null) ? hoverSelectedBorderColor : hoverBorderColor,
(selected && pressedSelectedBorderColor != null) ? pressedSelectedBorderColor : pressedBorderColor );
stateColor( indeterminate, indeterminateBorderColor, selected, selectedBorderColor, borderColor ),
stateColor( indeterminate, disabledIndeterminateBorderColor, selected, disabledSelectedBorderColor, disabledBorderColor ),
stateColor( indeterminate, focusedIndeterminateBorderColor, selected, focusedSelectedBorderColor, focusedBorderColor ),
stateColor( indeterminate, hoverIndeterminateBorderColor, selected, hoverSelectedBorderColor, hoverBorderColor ),
stateColor( indeterminate, pressedIndeterminateBorderColor, selected, pressedSelectedBorderColor, pressedBorderColor ) );
}
protected Color getBackground( Component c, boolean selected ) {
/** @since 3.6 */
protected Color getBackground( Component c, boolean selected, boolean indeterminate ) {
return FlatButtonUI.buttonStateColor( c,
selected ? selectedBackground : background,
(selected && disabledSelectedBackground != null) ? disabledSelectedBackground : disabledBackground,
(selected && focusedSelectedBackground != null) ? focusedSelectedBackground : focusedBackground,
(selected && hoverSelectedBackground != null) ? hoverSelectedBackground : hoverBackground,
(selected && pressedSelectedBackground != null) ? pressedSelectedBackground : pressedBackground );
stateColor( indeterminate, indeterminateBackground, selected, selectedBackground, background ),
stateColor( indeterminate, disabledIndeterminateBackground, selected, disabledSelectedBackground, disabledBackground ),
stateColor( indeterminate, focusedIndeterminateBackground, selected, focusedSelectedBackground, focusedBackground ),
stateColor( indeterminate, hoverIndeterminateBackground, selected, hoverSelectedBackground, hoverBackground ),
stateColor( indeterminate, pressedIndeterminateBackground, selected, pressedSelectedBackground, pressedBackground ) );
}
protected Color getCheckmarkColor( Component c ) {
/** @since 3.6 */
protected Color getCheckmarkColor( Component c, boolean indeterminate ) {
return FlatButtonUI.buttonStateColor( c,
checkmarkColor,
disabledCheckmarkColor,
focusedCheckmarkColor,
hoverCheckmarkColor,
pressedCheckmarkColor );
stateColor( indeterminate, indeterminateCheckmarkColor, checkmarkColor ),
stateColor( indeterminate, disabledIndeterminateCheckmarkColor, disabledCheckmarkColor ),
stateColor( indeterminate, focusedIndeterminateCheckmarkColor, focusedCheckmarkColor ),
stateColor( indeterminate, hoverIndeterminateCheckmarkColor, hoverCheckmarkColor ),
stateColor( indeterminate, pressedIndeterminateCheckmarkColor, pressedCheckmarkColor ) );
}
}

View File

@@ -21,12 +21,12 @@ import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.geom.Path2D;
import java.util.Map;
import javax.swing.AbstractButton;
import javax.swing.JMenuItem;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatStylingSupport;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
/**
* Icon for {@link javax.swing.JCheckBoxMenuItem}.
@@ -38,8 +38,10 @@ import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
*
* @author Karl Tauber
*/
@StyleableField( cls=FlatAbstractIcon.class, key="scale" )
public class FlatCheckBoxMenuItemIcon
extends FlatAbstractIcon
implements StyleableObject
{
@Styleable protected Color checkmarkColor = UIManager.getColor( "CheckBoxMenuItem.icon.checkmarkColor" );
@Styleable protected Color disabledCheckmarkColor = UIManager.getColor( "CheckBoxMenuItem.icon.disabledCheckmarkColor" );
@@ -49,21 +51,6 @@ public class FlatCheckBoxMenuItemIcon
super( 15, 15, null );
}
/** @since 2 */
public Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
}
/** @since 2 */
public Map<String, Class<?>> getStyleableInfos() {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
public Object getStyleableValue( String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
protected void paintIcon( Component c, Graphics2D g2 ) {
boolean selected = (c instanceof AbstractButton) && ((AbstractButton)c).isSelected();

View File

@@ -21,12 +21,12 @@ import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.util.Map;
import javax.swing.AbstractButton;
import javax.swing.ButtonModel;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatStylingSupport;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
@@ -39,8 +39,10 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
* @author Karl Tauber
* @since 1.5
*/
@StyleableField( cls=FlatAbstractIcon.class, key="clearIconScale", fieldName="scale" )
public class FlatClearIcon
extends FlatAbstractIcon
implements StyleableObject
{
@Styleable protected Color clearIconColor = UIManager.getColor( "SearchField.clearIconColor" );
@Styleable protected Color clearIconHoverColor = UIManager.getColor( "SearchField.clearIconHoverColor" );
@@ -58,21 +60,6 @@ public class FlatClearIcon
this.ignoreButtonState = ignoreButtonState;
}
/** @since 2 */
public Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
}
/** @since 2 */
public Map<String, Class<?>> getStyleableInfos() {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
public Object getStyleableValue( String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
if( !ignoreButtonState && c instanceof AbstractButton ) {

View File

@@ -16,7 +16,6 @@
package com.formdev.flatlaf.icons;
import static com.formdev.flatlaf.util.UIScale.*;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
@@ -24,11 +23,12 @@ import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.util.Map;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatButtonUI;
import com.formdev.flatlaf.ui.FlatStylingSupport;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
import com.formdev.flatlaf.util.UIScale;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
@@ -52,8 +52,10 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
*
* @author Karl Tauber
*/
@StyleableField( cls=FlatAbstractIcon.class, key="scale" )
public class FlatHelpButtonIcon
extends FlatAbstractIcon
implements StyleableObject
{
@Styleable protected int focusWidth = UIManager.getInt( "Component.focusWidth" );
@Styleable protected Color focusColor = UIManager.getColor( "Component.focusColor" );
@@ -76,21 +78,6 @@ public class FlatHelpButtonIcon
super( 0, 0, null );
}
/** @since 2 */
public Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
}
/** @since 2 */
public Map<String, Class<?>> getStyleableInfos() {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
public Object getStyleableValue( String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
protected void paintIcon( Component c, Graphics2D g2 ) {
/*
@@ -167,12 +154,12 @@ public class FlatHelpButtonIcon
@Override
public int getIconWidth() {
return scale( iconSize() );
return scale( UIScale.scale( iconSize() ) );
}
@Override
public int getIconHeight() {
return scale( iconSize() );
return scale( UIScale.scale( iconSize() ) );
}
private int iconSize() {

View File

@@ -21,12 +21,12 @@ import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.geom.Path2D;
import java.util.Map;
import javax.swing.JMenu;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatStylingSupport;
import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
/**
* "arrow" icon for {@link javax.swing.JMenu}.
@@ -39,8 +39,10 @@ import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
*
* @author Karl Tauber
*/
@StyleableField( cls=FlatAbstractIcon.class, key="scale" )
public class FlatMenuArrowIcon
extends FlatAbstractIcon
implements StyleableObject
{
@Styleable protected String arrowType = UIManager.getString( "Component.arrowType" );
@Styleable protected Color arrowColor = UIManager.getColor( "Menu.icon.arrowColor" );
@@ -51,21 +53,6 @@ public class FlatMenuArrowIcon
super( 6, 10, null );
}
/** @since 2 */
public Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
}
/** @since 2 */
public Map<String, Class<?>> getStyleableInfos() {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
public Object getStyleableValue( String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
if( c != null && !c.getComponentOrientation().isLeftToRight() )

View File

@@ -21,11 +21,11 @@ import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.util.Map;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatButtonUI;
import com.formdev.flatlaf.ui.FlatStylingSupport;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
@@ -38,8 +38,10 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
* @author Karl Tauber
* @since 1.5
*/
@StyleableField( cls=FlatAbstractIcon.class, key="searchIconScale", fieldName="scale" )
public class FlatSearchIcon
extends FlatAbstractIcon
implements StyleableObject
{
@Styleable protected Color searchIconColor = UIManager.getColor( "SearchField.searchIconColor" );
@Styleable protected Color searchIconHoverColor = UIManager.getColor( "SearchField.searchIconHoverColor" );
@@ -58,21 +60,6 @@ public class FlatSearchIcon
this.ignoreButtonState = ignoreButtonState;
}
/** @since 2 */
public Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
}
/** @since 2 */
public Map<String, Class<?>> getStyleableInfos() {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
public Object getStyleableValue( String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
/*

View File

@@ -22,11 +22,11 @@ import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.geom.Path2D;
import java.util.Map;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatButtonUI;
import com.formdev.flatlaf.ui.FlatStylingSupport;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableField;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
@@ -46,8 +46,10 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
*
* @author Karl Tauber
*/
@StyleableField( cls=FlatAbstractIcon.class, key="closeScale", fieldName="scale" )
public class FlatTabbedPaneCloseIcon
extends FlatAbstractIcon
implements StyleableObject
{
@Styleable protected Dimension closeSize = UIManager.getDimension( "TabbedPane.closeSize" );
@Styleable protected int closeArc = UIManager.getInt( "TabbedPane.closeArc" );
@@ -65,21 +67,6 @@ public class FlatTabbedPaneCloseIcon
super( 16, 16, null );
}
/** @since 2 */
public Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
}
/** @since 2 */
public Map<String, Class<?>> getStyleableInfos() {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
public Object getStyleableValue( String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
// paint background

View File

@@ -18,44 +18,95 @@ package com.formdev.flatlaf.icons;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Insets;
import java.awt.Window;
import javax.swing.SwingUtilities;
import com.formdev.flatlaf.ui.FlatButtonUI;
import com.formdev.flatlaf.ui.FlatTitlePane;
import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.util.DerivedColor;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.UIScale;
/**
* Base class for window icons.
*
* @uiDefault TitlePane.buttonSize Dimension
* @uiDefault TitlePane.buttonInsets Insets optional
* @uiDefault TitlePane.buttonArc int optional
* @uiDefault TitlePane.buttonSymbolHeight int
* @uiDefault TitlePane.buttonHoverBackground Color
* @uiDefault TitlePane.buttonPressedBackground Color
* @uiDefault TitlePane.buttonBackground Color optional
* @uiDefault TitlePane.buttonForeground Color optional
* @uiDefault TitlePane.buttonInactiveBackground Color optional
* @uiDefault TitlePane.buttonInactiveForeground Color optional
* @uiDefault TitlePane.buttonHoverBackground Color optional
* @uiDefault TitlePane.buttonHoverForeground Color optional
* @uiDefault TitlePane.buttonPressedBackground Color optional
* @uiDefault TitlePane.buttonPressedForeground Color optional
*
* @author Karl Tauber
*/
public abstract class FlatWindowAbstractIcon
extends FlatAbstractIcon
{
private final int symbolHeight;
private final Color hoverBackground;
private final Color pressedBackground;
/** @since 3.6 */ protected final Insets insets;
/** @since 3.6 */ protected final int arc;
/** @since 3.6 */ protected final int symbolHeight;
/** @since 3.6 */ protected final Color background;
/** @since 3.6 */ protected final Color foreground;
/** @since 3.6 */ protected final Color inactiveBackground;
/** @since 3.6 */ protected final Color inactiveForeground;
protected final Color hoverBackground;
/** @since 3.6 */ protected final Color hoverForeground;
protected final Color pressedBackground;
/** @since 3.6 */ protected final Color pressedForeground;
/** @since 3.2 */
protected FlatWindowAbstractIcon( String windowStyle ) {
this( FlatUIUtils.getSubUIDimension( "TitlePane.buttonSize", windowStyle ),
FlatUIUtils.getSubUIInt( "TitlePane.buttonSymbolHeight", windowStyle, 10 ),
FlatUIUtils.getSubUIColor( "TitlePane.buttonHoverBackground", windowStyle ),
FlatUIUtils.getSubUIColor( "TitlePane.buttonPressedBackground", windowStyle ) );
this( windowStyle, null, null, null, null, null, null, null, null );
}
/** @since 3.2 */
protected FlatWindowAbstractIcon( Dimension size, int symbolHeight, Color hoverBackground, Color pressedBackground ) {
/** @since 3.6 */
protected FlatWindowAbstractIcon( String windowStyle,
Color background, Color foreground, Color inactiveBackground, Color inactiveForeground,
Color hoverBackground, Color hoverForeground, Color pressedBackground, Color pressedForeground )
{
this( FlatUIUtils.getSubUIDimension( "TitlePane.buttonSize", windowStyle ),
FlatUIUtils.getSubUIInsets( "TitlePane.buttonInsets", windowStyle ),
FlatUIUtils.getSubUIInt( "TitlePane.buttonArc", windowStyle, 0 ),
FlatUIUtils.getSubUIInt( "TitlePane.buttonSymbolHeight", windowStyle, 10 ),
(background != null) ? background : FlatUIUtils.getSubUIColor( "TitlePane.buttonBackground", windowStyle ),
(foreground != null) ? foreground : FlatUIUtils.getSubUIColor( "TitlePane.buttonForeground", windowStyle ),
(inactiveBackground != null) ? inactiveBackground : FlatUIUtils.getSubUIColor( "TitlePane.buttonInactiveBackground", windowStyle ),
(inactiveForeground != null) ? inactiveForeground : FlatUIUtils.getSubUIColor( "TitlePane.buttonInactiveForeground", windowStyle ),
(hoverBackground != null) ? hoverBackground : FlatUIUtils.getSubUIColor( "TitlePane.buttonHoverBackground", windowStyle ),
(hoverForeground != null) ? hoverForeground : FlatUIUtils.getSubUIColor( "TitlePane.buttonHoverForeground", windowStyle ),
(pressedBackground != null) ? pressedBackground : FlatUIUtils.getSubUIColor( "TitlePane.buttonPressedBackground", windowStyle ),
(pressedForeground != null) ? pressedForeground : FlatUIUtils.getSubUIColor( "TitlePane.buttonPressedForeground", windowStyle ) );
}
/** @since 3.6 */
protected FlatWindowAbstractIcon( Dimension size, Insets insets, int arc, int symbolHeight,
Color background, Color foreground, Color inactiveBackground, Color inactiveForeground,
Color hoverBackground, Color hoverForeground, Color pressedBackground, Color pressedForeground )
{
super( size.width, size.height, null );
this.insets = (insets != null) ? insets : new Insets( 0, 0, 0, 0 );
this.arc = arc;
this.symbolHeight = symbolHeight;
this.background = background;
this.foreground = foreground;
this.inactiveBackground = inactiveBackground;
this.inactiveForeground = inactiveForeground;
this.hoverBackground = hoverBackground;
this.hoverForeground = hoverForeground;
this.pressedBackground = pressedBackground;
this.pressedForeground = pressedForeground;
}
@Override
@@ -69,26 +120,39 @@ public abstract class FlatWindowAbstractIcon
/** @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 );
Color bg = null;
if( background != null || inactiveBackground != null ) {
Window window = SwingUtilities.windowForComponent( c );
bg = (window == null || window.isActive()) ? background : inactiveBackground;
}
Color background = FlatButtonUI.buttonStateColor( c, bg, null, null, hoverBackground, pressedBackground );
if( background != null ) {
// 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 );
Insets insets = UIScale.scale( this.insets );
float arc = UIScale.scale( (float) this.arc );
// fill background of whole component
g.setColor( FlatUIUtils.deriveColor( background, c.getBackground() ) );
g.fillRect( 0, 0, c.getWidth(), c.getHeight() );
// derive color from title pane background
if( background instanceof DerivedColor ) {
Container titlePane = SwingUtilities.getAncestorOfClass( FlatTitlePane.class, c );
Component baseComp = (titlePane != null) ? titlePane : c;
background = FlatUIUtils.deriveColor( background, baseComp.getBackground() );
}
g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, oldHint );
g.setColor( background );
FlatUIUtils.paintComponentBackground( g, insets.left, insets.top,
c.getWidth() - insets.left - insets.right,
c.getHeight() - insets.top - insets.bottom,
0, arc );
}
}
protected Color getForeground( Component c ) {
return c.getForeground();
}
/** @since 3.2 */
protected int getSymbolHeight() {
return symbolHeight;
Color fg = null;
if( foreground != null || inactiveForeground != null ) {
Window window = SwingUtilities.windowForComponent( c );
fg = (window == null || window.isActive()) ? foreground : inactiveForeground;
}
return FlatButtonUI.buttonStateColor( c, (fg != null) ? fg : c.getForeground(),
null, null, hoverForeground, pressedForeground );
}
}

View File

@@ -17,53 +17,54 @@
package com.formdev.flatlaf.icons;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.geom.Path2D;
import com.formdev.flatlaf.ui.FlatButtonUI;
import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.util.SystemInfo;
/**
* "close" icon for windows (frames and dialogs).
*
* @uiDefault TitlePane.closeHoverBackground Color
* @uiDefault TitlePane.closePressedBackground Color
* @uiDefault TitlePane.closeHoverForeground Color
* @uiDefault TitlePane.closePressedForeground Color
* @uiDefault TitlePane.closeBackground Color optional
* @uiDefault TitlePane.closeForeground Color optional
* @uiDefault TitlePane.closeInactiveBackground Color optional
* @uiDefault TitlePane.closeInactiveForeground Color optional
* @uiDefault TitlePane.closeHoverBackground Color optional
* @uiDefault TitlePane.closeHoverForeground Color optional
* @uiDefault TitlePane.closePressedBackground Color optional
* @uiDefault TitlePane.closePressedForeground Color optional
*
* @author Karl Tauber
*/
public class FlatWindowCloseIcon
extends FlatWindowAbstractIcon
{
private final Color hoverForeground;
private final Color pressedForeground;
public FlatWindowCloseIcon() {
this( null );
}
/** @since 3.2 */
public FlatWindowCloseIcon( String windowStyle ) {
super( FlatUIUtils.getSubUIDimension( "TitlePane.buttonSize", windowStyle ),
FlatUIUtils.getSubUIInt( "TitlePane.buttonSymbolHeight", windowStyle, 10 ),
super( windowStyle,
FlatUIUtils.getSubUIColor( "TitlePane.closeBackground", windowStyle ),
FlatUIUtils.getSubUIColor( "TitlePane.closeForeground", windowStyle ),
FlatUIUtils.getSubUIColor( "TitlePane.closeInactiveBackground", windowStyle ),
FlatUIUtils.getSubUIColor( "TitlePane.closeInactiveForeground", windowStyle ),
FlatUIUtils.getSubUIColor( "TitlePane.closeHoverBackground", windowStyle ),
FlatUIUtils.getSubUIColor( "TitlePane.closePressedBackground", windowStyle ) );
hoverForeground = FlatUIUtils.getSubUIColor( "TitlePane.closeHoverForeground", windowStyle );
pressedForeground = FlatUIUtils.getSubUIColor( "TitlePane.closePressedForeground", windowStyle );
FlatUIUtils.getSubUIColor( "TitlePane.closeHoverForeground", windowStyle ),
FlatUIUtils.getSubUIColor( "TitlePane.closePressedBackground", windowStyle ),
FlatUIUtils.getSubUIColor( "TitlePane.closePressedForeground", windowStyle ) );
}
@Override
protected void paintIconAt1x( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) {
int iwh = (int) (getSymbolHeight() * scaleFactor);
int iwh = (int) (symbolHeight * scaleFactor);
int ix = x + ((width - iwh) / 2);
int iy = y + ((height - iwh) / 2);
int ix2 = ix + iwh - 1;
int iy2 = iy + iwh - 1;
float thickness = SystemInfo.isWindows_11_orLater ? (float) scaleFactor : (int) scaleFactor;
boolean isWindows10 = SystemInfo.isWindows_10_orLater && !SystemInfo.isWindows_11_orLater;
float thickness = Math.max( isWindows10 ? (int) scaleFactor : (float) scaleFactor, 1 );
Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD, 4 );
path.moveTo( ix, iy );
@@ -73,9 +74,4 @@ public class FlatWindowCloseIcon
g.setStroke( new BasicStroke( thickness ) );
g.draw( path );
}
@Override
protected Color getForeground( Component c ) {
return FlatButtonUI.buttonStateColor( c, c.getForeground(), null, null, hoverForeground, pressedForeground );
}
}

View File

@@ -37,8 +37,8 @@ public class FlatWindowIconifyIcon
@Override
protected void paintIconAt1x( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) {
int iw = (int) (getSymbolHeight() * scaleFactor);
int ih = (int) scaleFactor;
int iw = (int) (symbolHeight * scaleFactor);
int ih = Math.max( (int) scaleFactor, 1 );
int ix = x + ((width - iw) / 2);
int iy = y + ((height - ih) / 2);

View File

@@ -39,10 +39,11 @@ public class FlatWindowMaximizeIcon
@Override
protected void paintIconAt1x( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) {
int iwh = (int) (getSymbolHeight() * scaleFactor);
int iwh = (int) (symbolHeight * scaleFactor);
int ix = x + ((width - iwh) / 2);
int iy = y + ((height - iwh) / 2);
float thickness = SystemInfo.isWindows_11_orLater ? (float) scaleFactor : (int) scaleFactor;
boolean isWindows10 = SystemInfo.isWindows_10_orLater && !SystemInfo.isWindows_11_orLater;
float thickness = Math.max( isWindows10 ? (int) scaleFactor : (float) scaleFactor, 1 );
int arc = Math.max( (int) (1.5 * scaleFactor), 2 );
g.fill( SystemInfo.isWindows_11_orLater

View File

@@ -42,14 +42,15 @@ public class FlatWindowRestoreIcon
@Override
protected void paintIconAt1x( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) {
int iwh = (int) (getSymbolHeight() * scaleFactor);
int iwh = (int) (symbolHeight * scaleFactor);
int ix = x + ((width - iwh) / 2);
int iy = y + ((height - iwh) / 2);
float thickness = SystemInfo.isWindows_11_orLater ? (float) scaleFactor : (int) scaleFactor;
boolean isWindows10 = SystemInfo.isWindows_10_orLater && !SystemInfo.isWindows_11_orLater;
float thickness = Math.max( isWindows10 ? (int) scaleFactor : (float) scaleFactor, 1 );
int arc = Math.max( (int) (1.5 * scaleFactor), 2 );
int arcOuter = (int) (arc + (1.5 * scaleFactor));
int rwh = (int) ((getSymbolHeight() - 2) * scaleFactor);
int rwh = (int) ((symbolHeight - 2) * scaleFactor);
int ro2 = iwh - rwh;
// upper-right rectangle

View File

@@ -23,7 +23,6 @@ import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Paint;
import java.util.Map;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JScrollPane;
@@ -32,7 +31,7 @@ import javax.swing.UIManager;
import javax.swing.plaf.basic.BasicBorders;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableBorder;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
import com.formdev.flatlaf.util.DerivedColor;
/**
@@ -59,13 +58,15 @@ import com.formdev.flatlaf.util.DerivedColor;
* @uiDefault Component.error.focusedBorderColor Color
* @uiDefault Component.warning.borderColor Color
* @uiDefault Component.warning.focusedBorderColor Color
* @uiDefault Component.success.borderColor Color
* @uiDefault Component.success.focusedBorderColor Color
* @uiDefault Component.custom.borderColor Color
*
* @author Karl Tauber
*/
public class FlatBorder
extends BasicBorders.MarginBorder
implements StyleableBorder
implements StyleableObject
{
@Styleable protected int focusWidth = UIManager.getInt( "Component.focusWidth" );
@Styleable protected float innerFocusWidth = FlatUIUtils.getUIFloat( "Component.innerFocusWidth", 0 );
@@ -81,6 +82,8 @@ public class FlatBorder
@Styleable(dot=true) protected Color errorFocusedBorderColor = UIManager.getColor( "Component.error.focusedBorderColor" );
@Styleable(dot=true) protected Color warningBorderColor = UIManager.getColor( "Component.warning.borderColor" );
@Styleable(dot=true) protected Color warningFocusedBorderColor = UIManager.getColor( "Component.warning.focusedBorderColor" );
/** @since 3.6 */ @Styleable(dot=true) protected Color successBorderColor = UIManager.getColor( "Component.success.borderColor" );
/** @since 3.6 */ @Styleable(dot=true) protected Color successFocusedBorderColor = UIManager.getColor( "Component.success.focusedBorderColor" );
@Styleable(dot=true) protected Color customBorderColor = UIManager.getColor( "Component.custom.borderColor" );
// only used via styling (not in UI defaults, but has likewise client properties)
@@ -88,24 +91,6 @@ public class FlatBorder
/** @since 2 */ @Styleable protected Color outlineColor;
/** @since 2 */ @Styleable protected Color outlineFocusedColor;
/** @since 2 */
@Override
public Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos() {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
@Override
public Object getStyleableValue( String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
Graphics2D g2 = (Graphics2D) g.create();
@@ -168,6 +153,9 @@ public class FlatBorder
case FlatClientProperties.OUTLINE_WARNING:
return isFocused( c ) ? warningFocusedBorderColor : warningBorderColor;
case FlatClientProperties.OUTLINE_SUCCESS:
return isFocused( c ) ? successFocusedBorderColor : successBorderColor;
}
} else if( outline instanceof Color ) {
Color color = (Color) outline;

View File

@@ -58,8 +58,8 @@ 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.StyleableObject;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.HiDPIUtils;
@@ -280,8 +280,6 @@ public class FlatButtonUI
LookAndFeel.installProperty( b, "opaque", false );
LookAndFeel.installProperty( b, "iconTextGap", scale( iconTextGap ) );
MigLayoutVisualPadding.install( b );
}
@Override
@@ -291,10 +289,23 @@ public class FlatButtonUI
oldStyleValues = null;
borderShared = null;
MigLayoutVisualPadding.uninstall( b );
defaults_initialized = false;
}
@Override
protected void installListeners( AbstractButton b ) {
super.installListeners( b );
MigLayoutVisualPadding.install( b );
}
@Override
protected void uninstallListeners( AbstractButton b ) {
super.uninstallListeners( b );
MigLayoutVisualPadding.uninstall( b );
}
@Override
protected BasicButtonListener createButtonListener( AbstractButton b ) {
return new FlatButtonListener( b );
@@ -358,8 +369,8 @@ public class FlatButtonUI
/** @since 2 */
protected Object applyStyleProperty( AbstractButton b, String key, Object value ) {
if( key.startsWith( "help." ) ) {
if( !(helpButtonIcon instanceof FlatHelpButtonIcon) )
return new UnknownStyleException( key );
if( !(helpButtonIcon instanceof StyleableObject) )
throw new UnknownStyleException( key );
if( helpButtonIconShared ) {
helpButtonIcon = FlatStylingSupport.cloneIcon( helpButtonIcon );
@@ -367,7 +378,13 @@ public class FlatButtonUI
}
key = key.substring( "help.".length() );
return ((FlatHelpButtonIcon)helpButtonIcon).applyStyleProperty( key, value );
return ((StyleableObject)helpButtonIcon).applyStyleProperty( key, value );
}
// update internal values; otherwise isCustomBackground() and isCustomForeground() would return wrong results
switch( key ) {
case "background": background = (Color) value; break;
case "foreground": foreground = (Color) value; break;
}
if( "iconTextGap".equals( key ) && value instanceof Integer )
@@ -382,8 +399,8 @@ public class FlatButtonUI
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
Map<String, Class<?>> infos = FlatStylingSupport.getAnnotatedStyleableInfos( this, c.getBorder() );
if( helpButtonIcon instanceof FlatHelpButtonIcon )
FlatStylingSupport.putAllPrefixKey( infos, "help.", ((FlatHelpButtonIcon)helpButtonIcon).getStyleableInfos() );
if( helpButtonIcon instanceof StyleableObject )
FlatStylingSupport.putAllPrefixKey( infos, "help.", ((StyleableObject)helpButtonIcon).getStyleableInfos() );
return infos;
}
@@ -391,8 +408,8 @@ public class FlatButtonUI
@Override
public Object getStyleableValue( JComponent c, String key ) {
if( key.startsWith( "help." ) ) {
return (helpButtonIcon instanceof FlatHelpButtonIcon)
? ((FlatHelpButtonIcon)helpButtonIcon).getStyleableValue( key.substring( "help.".length() ) )
return (helpButtonIcon instanceof StyleableObject)
? ((StyleableObject)helpButtonIcon).getStyleableValue( key.substring( "help.".length() ) )
: null;
}
@@ -653,7 +670,8 @@ public class FlatButtonUI
}
protected Color getBackground( JComponent c ) {
boolean toolBarButton = isToolBarButton( c ) || isBorderlessButton( c );
boolean def = isDefaultButton( c );
boolean toolBarButton = !def && (isToolBarButton( c ) || isBorderlessButton( c ));
// selected state
if( ((AbstractButton)c).isSelected() ) {
@@ -681,7 +699,6 @@ public class FlatButtonUI
toolbarPressedBackground );
}
boolean def = isDefaultButton( c );
return buttonStateColor( c,
getBackgroundBase( c, def ),
disabledBackground,
@@ -733,7 +750,8 @@ public class FlatButtonUI
protected Color getForeground( JComponent c ) {
Color fg = c.getForeground();
boolean toolBarButton = isToolBarButton( c ) || isBorderlessButton( c );
boolean def = isDefaultButton( c );
boolean toolBarButton = !def && (isToolBarButton( c ) || isBorderlessButton( c ));
// selected state
if( ((AbstractButton)c).isSelected() ) {
@@ -759,7 +777,6 @@ public class FlatButtonUI
toolbarPressedForeground );
}
boolean def = isDefaultButton( c );
return buttonStateColor( c,
getForegroundBase( c, def ),
disabledText,

View File

@@ -17,6 +17,7 @@
package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.FlatClientProperties.*;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
@@ -24,7 +25,9 @@ import java.awt.event.FocusEvent;
import java.awt.event.MouseEvent;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.JComboBox;
import javax.swing.JFormattedTextField;
import javax.swing.JSpinner;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.UIResource;
@@ -33,6 +36,7 @@ import javax.swing.text.DefaultCaret;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import javax.swing.text.Position;
import javax.swing.text.Utilities;
/**
@@ -48,12 +52,15 @@ public class FlatCaret
{
private static final String KEY_CARET_INFO = "FlatLaf.internal.caretInfo";
// selectAllOnFocusPolicy
private static final int NEVER = 0, ONCE = 1, ALWAYS = 2;
private final String selectAllOnFocusPolicy;
private final boolean selectAllOnMouseClick;
private boolean inInstall;
private boolean wasFocused;
private boolean wasTemporaryLost;
private boolean wasFocusTemporaryLost;
private boolean isMousePressed;
private boolean isWordSelection;
private boolean isLineSelection;
@@ -94,6 +101,9 @@ public class FlatCaret
// restore selection
select( (int) ci[1], (int) ci[0] );
if( ci[4] != 0 )
wasFocused = true;
// if text component is focused, then caret and selection are visible,
// but when switching theme, the component does not yet have
// a highlighter and the selection is not painted
@@ -121,6 +131,7 @@ public class FlatCaret
getMark(),
getBlinkRate(),
System.currentTimeMillis(),
wasFocused ? 1 : 0,
} );
super.deinstall( c );
@@ -140,11 +151,36 @@ public class FlatCaret
super.adjustVisibility( nloc );
}
@Override
public void setDot( int dot ) {
super.setDot( dot );
// mark as focused if invoked from JTextComponent.setCaretPosition()
// to disable SELECT_ALL_ON_FOCUS_POLICY_ONCE if application explicitly changes selection
if( !wasFocused &&
getSelectAllOnFocusPolicy() == ONCE &&
StackUtils.wasInvokedFrom( JTextComponent.class.getName(), "setCaretPosition", 6 ) )
wasFocused = true;
}
@Override
public void moveDot( int dot ) {
super.moveDot( dot );
// mark as focused if invoked from JTextComponent.moveCaretPosition()
// to disable SELECT_ALL_ON_FOCUS_POLICY_ONCE if application explicitly changes selection
if( !wasFocused &&
getSelectAllOnFocusPolicy() == ONCE &&
StackUtils.wasInvokedFrom( JTextComponent.class.getName(), "moveCaretPosition", 6 ) )
wasFocused = true;
}
@Override
public void focusGained( FocusEvent e ) {
if( !inInstall && !wasTemporaryLost && (!isMousePressed || selectAllOnMouseClick) )
if( !inInstall && !wasFocusTemporaryLost && (!isMousePressed || isSelectAllOnMouseClick()) )
selectAllOnFocusGained();
wasTemporaryLost = false;
wasFocusTemporaryLost = false;
wasFocused = true;
super.focusGained( e );
@@ -152,7 +188,7 @@ public class FlatCaret
@Override
public void focusLost( FocusEvent e ) {
wasTemporaryLost = e.isTemporary();
wasFocusTemporaryLost = e.isTemporary();
super.focusLost( e );
}
@@ -232,24 +268,13 @@ public class FlatCaret
if( doc == null || !c.isEnabled() || !c.isEditable() || FlatUIUtils.isCellEditor( c ) )
return;
Object selectAllOnFocusPolicy = c.getClientProperty( SELECT_ALL_ON_FOCUS_POLICY );
if( selectAllOnFocusPolicy == null )
selectAllOnFocusPolicy = this.selectAllOnFocusPolicy;
if( selectAllOnFocusPolicy == null || SELECT_ALL_ON_FOCUS_POLICY_NEVER.equals( selectAllOnFocusPolicy ) )
int selectAllOnFocusPolicy = getSelectAllOnFocusPolicy();
if( selectAllOnFocusPolicy == NEVER )
return;
if( !SELECT_ALL_ON_FOCUS_POLICY_ALWAYS.equals( selectAllOnFocusPolicy ) ) {
// policy is "once" (or null or unknown)
if( selectAllOnFocusPolicy == ONCE && !isMousePressed ) {
// was already focused?
if( wasFocused )
return;
// check whether selection was modified before gaining focus
int dot = getDot();
int mark = getMark();
if( dot != mark || dot != doc.getLength() )
if( wasFocused && !(c instanceof JFormattedTextField) )
return;
}
@@ -265,16 +290,51 @@ public class FlatCaret
select( 0, c2.getDocument().getLength() );
} );
} else {
} else
select( 0, doc.getLength() );
}
}
private void select( int mark, int dot ) {
if( mark != getMark() )
setDot( mark );
setDot( mark, Position.Bias.Forward );
if( dot != getDot() )
moveDot( dot );
moveDot( dot, Position.Bias.Forward );
}
private int getSelectAllOnFocusPolicy() {
Object value = getClientProperty( SELECT_ALL_ON_FOCUS_POLICY );
// Note: using String.valueOf() because selectAllOnFocusPolicy may be null
switch( String.valueOf( value instanceof String ? value : selectAllOnFocusPolicy ) ) {
default:
case SELECT_ALL_ON_FOCUS_POLICY_NEVER: return NEVER;
case SELECT_ALL_ON_FOCUS_POLICY_ONCE: return ONCE;
case SELECT_ALL_ON_FOCUS_POLICY_ALWAYS: return ALWAYS;
}
}
private boolean isSelectAllOnMouseClick() {
Object value = getClientProperty( SELECT_ALL_ON_MOUSE_CLICK );
return (value instanceof Boolean) ? (boolean) value : selectAllOnMouseClick;
}
private Object getClientProperty( String key ) {
JTextComponent c = getComponent();
if( c == null )
return null;
Object value = c.getClientProperty( key );
if( value != null )
return value;
Container parent = c.getParent();
if( parent instanceof JComboBox )
return ((JComboBox<?>)parent).getClientProperty( key );
if( parent instanceof JSpinner.DefaultEditor ) {
parent = parent.getParent();
if( parent instanceof JSpinner )
return ((JSpinner)parent).getClientProperty( key );
}
return null;
}
/** @since 1.4 */

View File

@@ -113,6 +113,7 @@ import com.formdev.flatlaf.util.SystemInfo;
* @uiDefault ComboBox.buttonBackground Color optional
* @uiDefault ComboBox.buttonEditableBackground Color optional
* @uiDefault ComboBox.buttonFocusedBackground Color optional; defaults to ComboBox.focusedBackground
* @uiDefault ComboBox.buttonFocusedEditableBackground Color optional; defaults to ComboBox.buttonEditableBackground
* @uiDefault ComboBox.buttonSeparatorWidth int or float optional; defaults to Component.borderWidth
* @uiDefault ComboBox.buttonSeparatorColor Color optional
* @uiDefault ComboBox.buttonDisabledSeparatorColor Color optional
@@ -147,6 +148,7 @@ public class FlatComboBoxUI
@Styleable protected Color buttonBackground;
@Styleable protected Color buttonEditableBackground;
@Styleable protected Color buttonFocusedBackground;
/** @since 3.7.1 */ @Styleable protected Color buttonFocusedEditableBackground;
/** @since 2 */ @Styleable protected float buttonSeparatorWidth;
/** @since 2 */ @Styleable protected Color buttonSeparatorColor;
/** @since 2 */ @Styleable protected Color buttonDisabledSeparatorColor;
@@ -225,6 +227,8 @@ public class FlatComboBoxUI
}
};
comboBox.addMouseListener( hoverListener );
MigLayoutVisualPadding.install( comboBox );
}
@Override
@@ -233,6 +237,8 @@ public class FlatComboBoxUI
comboBox.removeMouseListener( hoverListener );
hoverListener = null;
MigLayoutVisualPadding.uninstall( comboBox );
}
@Override
@@ -254,6 +260,7 @@ public class FlatComboBoxUI
buttonBackground = UIManager.getColor( "ComboBox.buttonBackground" );
buttonFocusedBackground = UIManager.getColor( "ComboBox.buttonFocusedBackground" );
buttonFocusedEditableBackground = UIManager.getColor( "ComboBox.buttonFocusedEditableBackground" );
buttonEditableBackground = UIManager.getColor( "ComboBox.buttonEditableBackground" );
buttonSeparatorWidth = FlatUIUtils.getUIFloat( "ComboBox.buttonSeparatorWidth", FlatUIUtils.getUIFloat( "Component.borderWidth", 1 ) );
buttonSeparatorColor = UIManager.getColor( "ComboBox.buttonSeparatorColor" );
@@ -274,8 +281,6 @@ public class FlatComboBoxUI
comboBox.setMaximumRowCount( maximumRowCount );
paddingBorder = new CellPaddingBorder( padding );
MigLayoutVisualPadding.install( comboBox );
}
@Override
@@ -291,6 +296,7 @@ public class FlatComboBoxUI
buttonBackground = null;
buttonEditableBackground = null;
buttonFocusedBackground = null;
buttonFocusedEditableBackground = null;
buttonSeparatorColor = null;
buttonDisabledSeparatorColor = null;
buttonArrowColor = null;
@@ -304,8 +310,6 @@ public class FlatComboBoxUI
oldStyleValues = null;
borderShared = null;
MigLayoutVisualPadding.uninstall( comboBox );
}
@Override
@@ -587,7 +591,7 @@ public class FlatComboBoxUI
// paint arrow button background
if( enabled && !isCellRenderer && arrowButton.isVisible() ) {
Color buttonColor = paintButton
? buttonEditableBackground
? (buttonFocusedEditableBackground != null && isPermanentFocusOwner( comboBox ) ? buttonFocusedEditableBackground : buttonEditableBackground)
: (buttonFocusedBackground != null || focusedBackground != null) && isPermanentFocusOwner( comboBox )
? (buttonFocusedBackground != null ? buttonFocusedBackground : focusedBackground)
: buttonBackground;
@@ -882,7 +886,7 @@ public class FlatComboBoxUI
GraphicsConfiguration gc = comboBox.getGraphicsConfiguration();
if( gc != null ) {
Rectangle screenBounds = gc.getBounds();
Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets( gc );
Insets screenInsets = FlatUIUtils.getScreenInsets( gc );
displayWidth = Math.min( displayWidth, screenBounds.width - screenInsets.left - screenInsets.right );
} else {
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();

View File

@@ -24,9 +24,8 @@ import java.awt.Image;
import java.awt.Insets;
import java.awt.RadialGradientPaint;
import java.awt.image.BufferedImage;
import java.util.Map;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableBorder;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.UIScale;
@@ -43,7 +42,7 @@ import com.formdev.flatlaf.util.UIScale;
*/
public class FlatDropShadowBorder
extends FlatEmptyBorder
implements StyleableBorder
implements StyleableObject
{
@Styleable protected Color shadowColor;
@Styleable protected Insets shadowInsets;
@@ -74,7 +73,7 @@ public class FlatDropShadowBorder
this.shadowColor = shadowColor;
this.shadowInsets = shadowInsets;
this.shadowOpacity = shadowOpacity;
this.shadowOpacity = Math.min( Math.max( shadowOpacity, 0f ), 1f );
shadowSize = maxInset( shadowInsets );
}
@@ -93,7 +92,7 @@ public class FlatDropShadowBorder
/** @since 2 */
@Override
public Object applyStyleProperty( String key, Object value ) {
Object oldValue = FlatStylingSupport.applyToAnnotatedObject( this, key, value );
Object oldValue = StyleableObject.super.applyStyleProperty( key, value );
if( key.equals( "shadowInsets" ) ) {
applyStyleProperty( nonNegativeInsets( shadowInsets ) );
shadowSize = maxInset( shadowInsets );
@@ -101,18 +100,6 @@ public class FlatDropShadowBorder
return oldValue;
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos() {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
@Override
public Object getStyleableValue( String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
if( shadowSize <= 0 )

View File

@@ -376,31 +376,68 @@ public class FlatFileChooserUI
if( icon != null )
return icon;
// get system icon
if( f != null ) {
try {
icon = getFileChooser().getFileSystemView().getSystemIcon( f );
} catch( NullPointerException ex ) {
// Java 21 may throw a NPE for exe files that use default Windows exe icon
}
// new proxy icon
//
// Note: Since this is a super light weight icon object, we do not add it
// to the icon cache here. This keeps cache small in case of large directories
// with thousands of files when icons of all files are only needed to compute
// the layout of list/table, but never painted because located outside of visible area.
// When an icon needs to be painted, the proxy adds it to the icon cache
// and loads the real icon.
return new FlatFileViewIcon( f );
}
if( icon != null ) {
if( icon instanceof ImageIcon )
icon = new ScaledImageIcon( (ImageIcon) icon );
cacheIcon( f, icon );
return icon;
}
//---- class FlatFileViewIcon -----------------------------------------
/**
* A proxy icon that has a fixed (scaled) width/height (16x16) and
* gets/loads the real (system) icon only for painting.
* Avoids unnecessary getting/loading system icons.
*/
private class FlatFileViewIcon
implements Icon
{
private final File f;
private Icon realIcon;
FlatFileViewIcon( File f ) {
this.f = f;
}
// get default icon
icon = super.getIcon( f );
if( icon instanceof ImageIcon ) {
icon = new ScaledImageIcon( (ImageIcon) icon );
cacheIcon( f, icon );
@Override
public int getIconWidth() {
return UIScale.scale( 16 );
}
return icon;
@Override
public int getIconHeight() {
return UIScale.scale( 16 );
}
@Override
public void paintIcon( Component c, Graphics g, int x, int y ) {
// get icon on demand
if( realIcon == null ) {
// get system icon
try {
if( f != null )
realIcon = getFileChooser().getFileSystemView().getSystemIcon( f );
} catch( NullPointerException ex ) {
// Java 21 may throw a NPE for exe files that use default Windows exe icon
}
// get default icon
if( realIcon == null )
realIcon = FlatFileView.super.getIcon( f );
if( realIcon instanceof ImageIcon )
realIcon = new ScaledImageIcon( (ImageIcon) realIcon );
cacheIcon( f, this );
}
realIcon.paintIcon( c, g, x, y );
}
}
}

View File

@@ -35,7 +35,7 @@ import javax.swing.event.MouseInputAdapter;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicInternalFrameUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableBorder;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.LoggingFacade;
@@ -209,7 +209,7 @@ public class FlatInternalFrameUI
public static class FlatInternalFrameBorder
extends FlatEmptyBorder
implements StyleableBorder
implements StyleableObject
{
@Styleable protected Color activeBorderColor = UIManager.getColor( "InternalFrame.activeBorderColor" );
@Styleable protected Color inactiveBorderColor = UIManager.getColor( "InternalFrame.inactiveBorderColor" );

View File

@@ -57,6 +57,7 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault List.foreground Color
* @uiDefault List.selectionBackground Color
* @uiDefault List.selectionForeground Color
* @uiDefault List.alternateRowColor Color
* @uiDefault List.dropLineColor Color
* @uiDefault List.border Border
* @uiDefault List.cellRenderer ListCellRenderer
@@ -93,6 +94,7 @@ public class FlatListUI
@Styleable protected Color selectionForeground;
@Styleable protected Color selectionInactiveBackground;
@Styleable protected Color selectionInactiveForeground;
/** @since 3.6 */ @Styleable protected Color alternateRowColor;
/** @since 3 */ @Styleable protected Insets selectionInsets;
/** @since 3 */ @Styleable protected int selectionArc;
@@ -129,6 +131,7 @@ public class FlatListUI
selectionForeground = UIManager.getColor( "List.selectionForeground" );
selectionInactiveBackground = UIManager.getColor( "List.selectionInactiveBackground" );
selectionInactiveForeground = UIManager.getColor( "List.selectionInactiveForeground" );
alternateRowColor = UIManager.getColor( "List.alternateRowColor" );
selectionInsets = UIManager.getInsets( "List.selectionInsets" );
selectionArc = UIManager.getInt( "List.selectionArc" );
@@ -143,6 +146,7 @@ public class FlatListUI
selectionForeground = null;
selectionInactiveBackground = null;
selectionInactiveForeground = null;
alternateRowColor = null;
oldStyleValues = null;
}
@@ -298,6 +302,18 @@ public class FlatListUI
ListModel dataModel, ListSelectionModel selModel, int leadIndex )
{
boolean isSelected = selModel.isSelectedIndex( row );
boolean isDropRow = isDropRow( row );
// paint alternating rows
if( alternateRowColor != null && row % 2 != 0 &&
!"ComboBox.list".equals( list.getName() ) ) // combobox does not support alternate row color
{
g.setColor( alternateRowColor );
float arc = UIScale.scale( selectionArc / 2f );
FlatUIUtils.paintSelection( (Graphics2D) g, rowBounds.x, rowBounds.y, rowBounds.width, rowBounds.height,
UIScale.scale( selectionInsets ), arc, arc, arc, arc, 0 );
}
// get renderer component
@SuppressWarnings( "unchecked" )
@@ -305,7 +321,7 @@ public class FlatListUI
dataModel.getElementAt( row ), row, isSelected,
FlatUIUtils.isPermanentFocusOwner( list ) && (row == leadIndex) );
//
// use smaller cell width if list is used in JFileChooser
boolean isFileList = Boolean.TRUE.equals( list.getClientProperty( "List.isFileList" ) );
int cx, cw;
if( isFileList ) {
@@ -320,7 +336,7 @@ public class FlatListUI
}
// rounded selection or selection insets
if( isSelected &&
if( (isSelected || isDropRow) &&
!isFileList && // rounded selection is not supported for file list
(rendererComponent instanceof DefaultListCellRenderer ||
rendererComponent instanceof BasicComboBoxRenderer) &&
@@ -361,7 +377,22 @@ public class FlatListUI
this.getColor() == rendererComponent.getBackground() )
{
inPaintSelection = true;
paintCellSelection( this, row, x, y, width, height );
if( isDropRow ) {
// for rounded drop background, it is necessary to first
// paint selection background because may be not rounded on some corners
if( isSelected ) {
Color oldColor = getColor();
setColor( list.getSelectionBackground() );
paintCellSelection( this, row, x, y, width, height );
setColor( oldColor );
}
// paint drop background
float arc = UIScale.scale( selectionArc / 2f );
FlatUIUtils.paintSelection( this, x, y, width, height,
UIScale.scale( selectionInsets ), arc, arc, arc, arc, 0 );
} else
paintCellSelection( this, row, x, y, width, height );
inPaintSelection = false;
} else
super.fillRect( x, y, width, height );
@@ -460,4 +491,15 @@ public class FlatListUI
FlatListUI ui = (FlatListUI) list.getUI();
ui.paintCellSelection( g, row, x, y, width, height );
}
/**
* Checks whether dropping on a row.
* See DefaultListCellRenderer.getListCellRendererComponent().
*/
private boolean isDropRow( int row ) {
JList.DropLocation dropLocation = list.getDropLocation();
return dropLocation != null &&
!dropLocation.isInsert() &&
dropLocation.getIndex() == row;
}
}

View File

@@ -21,11 +21,10 @@ import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Insets;
import java.util.Map;
import javax.swing.JMenuBar;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableBorder;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
/**
* Border for {@link javax.swing.JMenuBar}.
@@ -36,27 +35,10 @@ import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableBorder;
*/
public class FlatMenuBarBorder
extends FlatMarginBorder
implements StyleableBorder
implements StyleableObject
{
@Styleable protected Color borderColor = UIManager.getColor( "MenuBar.borderColor" );
/** @since 2 */
@Override
public Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
}
@Override
public Map<String, Class<?>> getStyleableInfos() {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 2.5 */
@Override
public Object getStyleableValue( String key ) {
return FlatStylingSupport.getAnnotatedStyleableValue( this, key );
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
if( !showBottomSeparator( c ) )

View File

@@ -58,6 +58,7 @@ class FlatNativeLibrary
String classifier;
String ext;
boolean unknownArch = false;
if( SystemInfo.isWindows_10_orLater && (SystemInfo.isX86 || SystemInfo.isX86_64 || SystemInfo.isAARCH64) ) {
// Windows: requires Windows 10/11 (x86, x86_64 or aarch64)
@@ -90,11 +91,14 @@ class FlatNativeLibrary
classifier = SystemInfo.isAARCH64 ? "macos-arm64" : "macos-x86_64";
ext = "dylib";
} else if( SystemInfo.isLinux && SystemInfo.isX86_64 ) {
// Linux: requires x86_64
} else if( SystemInfo.isLinux ) {
// Linux: x86_64 or aarch64 (but also supports unknown architectures)
classifier = "linux-x86_64";
classifier = SystemInfo.isAARCH64 ? "linux-arm64"
: (SystemInfo.isX86_64 ? "linux-x86_64"
: "linux-" + sanitize( System.getProperty( "os.arch" ) ));
ext = "so";
unknownArch = !SystemInfo.isX86_64 && !SystemInfo.isAARCH64;
// Load libjawt.so (part of JRE) explicitly because it is not found
// in all Java versions/distributions.
@@ -106,7 +110,7 @@ class FlatNativeLibrary
return; // no native library available for current OS or CPU architecture
// load native library
NativeLibrary nativeLibrary = createNativeLibrary( classifier, ext );
NativeLibrary nativeLibrary = createNativeLibrary( classifier, ext, unknownArch );
if( !nativeLibrary.isLoaded() )
return;
@@ -128,7 +132,7 @@ class FlatNativeLibrary
FlatNativeLibrary.nativeLibrary = nativeLibrary;
}
private static NativeLibrary createNativeLibrary( String classifier, String ext ) {
private static NativeLibrary createNativeLibrary( String classifier, String ext, boolean unknownArch ) {
String libraryName = "flatlaf-" + classifier;
// load from "java.library.path" or from path specified in system property "flatlaf.nativeLibraryPath"
@@ -139,9 +143,11 @@ class FlatNativeLibrary
if( library.isLoaded() )
return library;
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 );
if( !unknownArch ) {
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')
@@ -160,11 +166,13 @@ class FlatNativeLibrary
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 );
if( !unknownArch ) {
LoggingFacade.INSTANCE.logSevere( "FlatLaf: Native library '"
+ libraryFile.getName()
+ (libraryName2 != null ? ("' or '" + libraryName2) : "")
+ "' not found in '" + libraryFile.getParentFile().getAbsolutePath()
+ "'. Using extracted native library instead.", null );
}
}
}
@@ -175,7 +183,7 @@ class FlatNativeLibrary
return new NativeLibrary( libraryFile, true );
// load from FlatLaf jar (extract native library to temp folder)
return new NativeLibrary( "com/formdev/flatlaf/natives/" + libraryName, null, true );
return new NativeLibrary( "com/formdev/flatlaf/natives/" + libraryName, null, !unknownArch );
}
/**
@@ -273,6 +281,13 @@ class FlatNativeLibrary
+ '-' + classifier + '.' + ext;
}
/**
* Allow only 'a'-'z', 'A'-'Z', '0'-'9', '_' and '-' in filenames.
*/
private static String sanitize( String s ) {
return s.replaceAll( "[^a-zA-Z0-9_-]", "_" );
}
private static void loadJAWT() {
try {
System.loadLibrary( "jawt" );

View File

@@ -24,6 +24,7 @@ import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import com.formdev.flatlaf.util.SystemInfo;
/**
@@ -34,9 +35,9 @@ import com.formdev.flatlaf.util.SystemInfo;
* @author Karl Tauber
* @since 2.5
*/
class FlatNativeLinuxLibrary
public class FlatNativeLinuxLibrary
{
private static int API_VERSION_LINUX = 3001;
private static int API_VERSION_LINUX = 3003;
/**
* Checks whether native library is loaded/available.
@@ -44,13 +45,25 @@ class FlatNativeLinuxLibrary
* <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() {
public static boolean isLoaded() {
return SystemInfo.isLinux && FlatNativeLibrary.isLoaded( API_VERSION_LINUX );
}
//---- X Window System ----------------------------------------------------
// direction for _NET_WM_MOVERESIZE message
// see https://specifications.freedesktop.org/wm-spec/wm-spec-latest.html
static final int MOVE = 8;
// see https://specifications.freedesktop.org/wm-spec/latest/ar01s04.html
static final int
SIZE_TOPLEFT = 0,
SIZE_TOP = 1,
SIZE_TOPRIGHT = 2,
SIZE_RIGHT = 3,
SIZE_BOTTOMRIGHT = 4,
SIZE_BOTTOM = 5,
SIZE_BOTTOMLEFT = 6,
SIZE_LEFT = 7,
MOVE = 8;
private static Boolean isXWindowSystem;
@@ -115,4 +128,110 @@ class FlatNativeLinuxLibrary
return (window instanceof JFrame && JFrame.isDefaultLookAndFeelDecorated() && ((JFrame)window).isUndecorated()) ||
(window instanceof JDialog && JDialog.isDefaultLookAndFeelDecorated() && ((JDialog)window).isUndecorated());
}
//---- GTK ----------------------------------------------------------------
private static Boolean isGtk3Available;
/**
* Checks whether GTK 3 is available.
* Use this before invoking any native method that uses GTK.
* Otherwise the app may terminate immediately if GTK is not installed.
* <p>
* This works because Java uses {@code dlopen(RTLD_LAZY)} to load JNI libraries,
* which only resolves symbols as the code that references them is executed.
*
* @since 3.7
*/
public static boolean isGtk3Available() {
if( isGtk3Available == null )
isGtk3Available = isLibAvailable( "libgtk-3.so.0" ) || isLibAvailable( "libgtk-3.so" );
return isGtk3Available;
}
private native static boolean isLibAvailable( String libname );
/**
* https://docs.gtk.org/gtk3/iface.FileChooser.html#properties
*
* @since 3.7
*/
public static final int
FC_select_folder = 1 << 0,
FC_select_multiple = 1 << 1,
FC_show_hidden = 1 << 2,
FC_local_only = 1 << 3, // default
FC_do_overwrite_confirmation = 1 << 4, // GTK 3 only; removed and always-on in GTK 4
FC_create_folders = 1 << 5; // default for Save
/**
* Shows the Linux/GTK system file dialog
* <a href="https://docs.gtk.org/gtk3/class.FileChooserDialog.html">GtkFileChooserDialog</a>.
* <p>
* Uses {@code GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER} if {@link #FC_select_folder} is set in parameter {@code optionsSet}.
* Otherwise uses {@code GTK_FILE_CHOOSER_ACTION_OPEN} if parameter {@code open} is {@code true},
* or {@code GTK_FILE_CHOOSER_ACTION_SAVE} if {@code false}.
* <p>
* <b>Note:</b> This method blocks the current thread until the user closes
* the file dialog. It is highly recommended to invoke it from a new thread
* to avoid blocking the AWT event dispatching thread.
*
* @param owner the owner of the file dialog; or {@code null}
* @param dark preferred appearance of the file dialog: {@code 1} = prefer dark, {@code 0} = prefer light, {@code -1} = default
* @param open if {@code true}, shows the open dialog; if {@code false}, shows the save dialog
* @param title text displayed in dialog title; or {@code null}
* @param okButtonLabel text displayed in default button; or {@code null}.
* Use '_' for mnemonics (e.g. "_Choose")
* Use '__' for '_' character (e.g. "Choose__and__Quit").
* @param currentName user-editable filename currently shown in the filename field in save dialog; or {@code null}
* @param currentFolder current directory shown in the dialog; or {@code null}
* @param optionsSet options to set; see {@code FOS_*} constants
* @param optionsClear options to clear; see {@code FOS_*} constants
* @param callback approve callback; or {@code null}
* @param fileTypeIndex the file type that appears as selected (zero-based)
* @param fileTypes file types that the dialog can open or save.
* Two or more strings and {@code null} are required for each filter.
* First string is the display name of the filter shown in the combobox (e.g. "Text Files").
* Subsequent strings are the filter patterns (e.g. "*.txt" or "*").
* {@code null} is required to mark end of filter.
* @return file path(s) that the user selected; an empty array if canceled;
* or {@code null} on failures (no dialog shown)
*
* @since 3.7
*/
public native static String[] showFileChooser( Window owner, int dark, boolean open,
String title, String okButtonLabel, String currentName, String currentFolder,
int optionsSet, int optionsClear, FileChooserCallback callback,
int fileTypeIndex, String... fileTypes );
/** @since 3.7 */
public interface FileChooserCallback {
boolean approve( String[] files, long hwndFileDialog );
}
/**
* Shows a GTK message box
* <a href="https://docs.gtk.org/gtk3/class.MessageDialog.html">GtkMessageDialog</a>.
* <p>
* For use in {@link FileChooserCallback} only.
*
* @param hwndParent the parent of the message box
* @param messageType type of message being displayed:
* {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#INFORMATION_MESSAGE},
* {@link JOptionPane#WARNING_MESSAGE}, {@link JOptionPane#QUESTION_MESSAGE} or
* {@link JOptionPane#PLAIN_MESSAGE}
* @param primaryText primary text; if the dialog has a secondary text,
* this will appear as title in a larger bold font
* @param secondaryText secondary text; shown below of primary text; or {@code null}
* @param defaultButton index of the default button, which can be pressed using ENTER key
* @param buttons texts of the buttons; if no buttons given the a default "OK" button is shown.
* Use '_' for mnemonics (e.g. "_Choose")
* Use '__' for '_' character (e.g. "Choose__and__Quit").
* @return index of pressed button; or -1 for ESC key
*
* @since 3.7
*/
public native static int showMessageDialog( long hwndParent, int messageType,
String primaryText, String secondaryText, int defaultButton, String... buttons );
}

View File

@@ -18,6 +18,7 @@ package com.formdev.flatlaf.ui;
import java.awt.Rectangle;
import java.awt.Window;
import javax.swing.JOptionPane;
import com.formdev.flatlaf.util.SystemInfo;
/**
@@ -44,7 +45,7 @@ import com.formdev.flatlaf.util.SystemInfo;
*/
public class FlatNativeMacLibrary
{
private static int API_VERSION_MACOS = 2001;
private static int API_VERSION_MACOS = 2002;
/**
* Checks whether native library is loaded/available.
@@ -68,4 +69,88 @@ public class FlatNativeMacLibrary
/** @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 );
/** @since 3.7 */
public static final int
// NSOpenPanel (extends NSSavePanel)
FC_canChooseFiles = 1 << 0, // default
FC_canChooseDirectories = 1 << 1,
FC_resolvesAliases = 1 << 2, // default
FC_allowsMultipleSelection = 1 << 3,
FC_accessoryViewDisclosed = 1 << 4,
// NSSavePanel
FC_showsTagField = 1 << 8, // default for Save
FC_canCreateDirectories = 1 << 9, // default for Save
FC_canSelectHiddenExtension = 1 << 10,
FC_showsHiddenFiles = 1 << 11,
FC_extensionHidden = 1 << 12,
FC_allowsOtherFileTypes = 1 << 13,
FC_treatsFilePackagesAsDirectories = 1 << 14,
// custom
FC_showSingleFilterField = 1 << 24;
/**
* Shows the macOS system file dialogs
* <a href="https://developer.apple.com/documentation/appkit/nsopenpanel?language=objc">NSOpenPanel</a> or
* <a href="https://developer.apple.com/documentation/appkit/nssavepanel?language=objc">NSSavePanel</a>.
* <p>
* <b>Note:</b> This method blocks the current thread until the user closes
* the file dialog. It is highly recommended to invoke it from a new thread
* to avoid blocking the AWT event dispatching thread.
*
* @param owner the owner of the file dialog; or {@code null}
* @param dark appearance of the file dialog: {@code 1} = dark, {@code 0} = light, {@code -1} = default
* @param open if {@code true}, shows the open dialog; if {@code false}, shows the save dialog
* @param title text displayed at top of save dialog (not used in open dialog); or {@code null}
* @param prompt text displayed in default button; or {@code null}
* @param message text displayed at top of open/save dialogs; or {@code null}
* @param filterFieldLabel text displayed in front of the filter combobox; or {@code null}
* @param nameFieldLabel text displayed in front of the filename text field in save dialog (not used in open dialog); or {@code null}
* @param nameFieldStringValue user-editable filename currently shown in the name field in save dialog (not used in open dialog); or {@code null}
* @param directoryURL current directory shown in the dialog; or {@code null}
* @param optionsSet options to set; see {@code FC_*} constants
* @param optionsClear options to clear; see {@code FC_*} constants
* @param fileTypeIndex the file type that appears as selected (zero-based)
* @param fileTypes file types that the dialog can open or save.
* Two or more strings and {@code null} are required for each filter.
* First string is the display name of the filter shown in the combobox (e.g. "Text Files").
* Subsequent strings are the filter patterns (e.g. "txt" or "*").
* {@code null} is required to mark end of filter.
* @return file path(s) that the user selected; an empty array if canceled;
* or {@code null} on failures (no dialog shown)
*
* @since 3.7
*/
public native static String[] showFileChooser( Window owner, int dark, boolean open,
String title, String prompt, String message, String filterFieldLabel,
String nameFieldLabel, String nameFieldStringValue, String directoryURL,
int optionsSet, int optionsClear, FileChooserCallback callback,
int fileTypeIndex, String... fileTypes );
/** @since 3.7 */
public interface FileChooserCallback {
boolean approve( String[] files, long hwndFileDialog );
}
/**
* Shows a macOS alert
* <a href="https://developer.apple.com/documentation/appkit/nsalert?language=objc">NSAlert</a>.
* <p>
* For use in {@link FileChooserCallback} only.
*
* @param hwndParent the parent of the message box
* @param alertStyle type of alert being displayed:
* {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#INFORMATION_MESSAGE} or
* {@link JOptionPane#WARNING_MESSAGE}
* @param messageText main message of the alert
* @param informativeText additional information about the alert; shown below of main message; or {@code null}
* @param defaultButton index of the default button, which can be pressed using ENTER key
* @param buttons texts of the buttons; if no buttons given the a default "OK" button is shown
* @return index of pressed button
*
* @since 3.7
*/
public native static int showMessageDialog( long hwndParent, int alertStyle,
String messageText, String informativeText, int defaultButton, String... buttons );
}

View File

@@ -18,6 +18,7 @@ package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Window;
import javax.swing.JOptionPane;
import com.formdev.flatlaf.util.SystemInfo;
/**
@@ -30,7 +31,7 @@ import com.formdev.flatlaf.util.SystemInfo;
*/
public class FlatNativeWindowsLibrary
{
private static int API_VERSION_WINDOWS = 1001;
private static int API_VERSION_WINDOWS = 1002;
private static long osBuildNumber = Long.MIN_VALUE;
@@ -158,4 +159,125 @@ public class FlatNativeWindowsLibrary
// DwmSetWindowAttribute() expects COLORREF as attribute value, which is defined as DWORD
return dwmSetWindowAttributeDWORD( hwnd, attribute, rgb );
}
/**
* FILEOPENDIALOGOPTIONS
* see https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/ne-shobjidl_core-_fileopendialogoptions
*
* @since 3.7
*/
public static final int
FOS_OVERWRITEPROMPT = 0x2, // default for Save
FOS_STRICTFILETYPES = 0x4,
FOS_NOCHANGEDIR = 0x8, // default
FOS_PICKFOLDERS = 0x20,
FOS_FORCEFILESYSTEM = 0x40,
FOS_ALLNONSTORAGEITEMS = 0x80,
FOS_NOVALIDATE = 0x100,
FOS_ALLOWMULTISELECT = 0x200,
FOS_PATHMUSTEXIST = 0x800, // default
FOS_FILEMUSTEXIST = 0x1000, // default for Open
FOS_CREATEPROMPT = 0x2000,
FOS_SHAREAWARE = 0x4000,
FOS_NOREADONLYRETURN = 0x8000, // default for Save
FOS_NOTESTFILECREATE = 0x10000,
FOS_HIDEMRUPLACES = 0x20000,
FOS_HIDEPINNEDPLACES = 0x40000,
FOS_NODEREFERENCELINKS = 0x100000,
FOS_OKBUTTONNEEDSINTERACTION = 0x200000,
FOS_DONTADDTORECENT = 0x2000000,
FOS_FORCESHOWHIDDEN = 0x10000000,
FOS_DEFAULTNOMINIMODE = 0x20000000,
FOS_FORCEPREVIEWPANEON = 0x40000000,
FOS_SUPPORTSTREAMABLEITEMS = 0x80000000;
/**
* Shows the Windows system
* <a href="https://learn.microsoft.com/en-us/windows/win32/shell/common-file-dialog">file dialogs</a>
* <a href="https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-ifileopendialog">IFileOpenDialog</a> or
* <a href="https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-ifilesavedialog">IFileSaveDialog</a>.
* <p>
* <b>Note:</b> This method blocks the current thread until the user closes
* the file dialog. It is highly recommended to invoke it from a new thread
* to avoid blocking the AWT event dispatching thread.
*
* @param owner the owner of the file dialog; or {@code null}
* @param open if {@code true}, shows the open dialog; if {@code false}, shows the save dialog
* @param title text displayed in dialog title; or {@code null}
* @param okButtonLabel text displayed in default button; or {@code null}.
* Use '&amp;' for mnemonics (e.g. "&amp;Choose").
* Use '&amp;&amp;' for '&amp;' character (e.g. "Choose &amp;&amp; Quit").
* @param fileNameLabel text displayed in front of the filename text field; or {@code null}
* @param fileName user-editable filename currently shown in the filename field; or {@code null}
* @param folder current directory shown in the dialog; or {@code null}
* @param saveAsItem file to be used as the initial entry in a Save As dialog; or {@code null}.
* File name is shown in filename text field, folder is selected in view.
* To be used for saving files that already exist. For new files use {@code fileName}.
* @param defaultFolder folder used as a default if there is not a recently used folder value available; or {@code null}.
* Windows somewhere stores default folder on a per-app basis.
* So this is probably used only once when the app opens a file dialog for first time.
* @param defaultExtension default extension to be added to file name in save dialog; or {@code null}
* @param optionsSet options to set; see {@code FOS_*} constants
* @param optionsClear options to clear; see {@code FOS_*} constants
* @param callback approve callback; or {@code null}
* @param fileTypeIndex the file type that appears as selected (zero-based)
* @param fileTypes file types that the dialog can open or save.
* Pairs of strings are required for each filter.
* First string is the display name of the filter shown in the combobox (e.g. "Text Files").
* Second string is the filter pattern (e.g. "*.txt", "*.exe;*.dll" or "*.*").
* @return file path(s) that the user selected; an empty array if canceled;
* or {@code null} on failures (no dialog shown)
*
* @since 3.7
*/
public native static String[] showFileChooser( Window owner, boolean open,
String title, String okButtonLabel, String fileNameLabel, String fileName,
String folder, String saveAsItem, String defaultFolder, String defaultExtension,
int optionsSet, int optionsClear, FileChooserCallback callback,
int fileTypeIndex, String... fileTypes );
/** @since 3.7 */
public interface FileChooserCallback {
boolean approve( String[] files, long hwndFileDialog );
}
/**
* Shows a modal Windows message dialog.
* <p>
* For use in {@link FileChooserCallback} only.
*
* @param hwndParent the parent of the message box
* @param messageType type of message being displayed:
* {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#INFORMATION_MESSAGE},
* {@link JOptionPane#WARNING_MESSAGE}, {@link JOptionPane#QUESTION_MESSAGE} or
* {@link JOptionPane#PLAIN_MESSAGE}
* @param title dialog box title; or {@code null} to use title from parent window
* @param text message to be displayed
* @param defaultButton index of the default button, which can be pressed using ENTER key
* @param buttons texts of the buttons.
* Use '&amp;' for mnemonics (e.g. "&amp;Choose").
* Use '&amp;&amp;' for '&amp;' character (e.g. "Choose &amp;&amp; Quit").
* @return index of pressed button; or -1 for ESC key
*
* @since 3.7
*/
public native static int showMessageDialog( long hwndParent, int messageType,
String title, String text, int defaultButton, String... buttons );
/**
* Shows a Windows message box
* <a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebox">MessageBox</a>.
* <p>
* For use in {@link FileChooserCallback} only.
*
* @param hwndParent the parent of the message box
* @param text message to be displayed
* @param caption dialog box title
* @param type see <a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebox#parameters">MessageBox parameter uType</a>
* @return see <a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebox#return-value">MessageBox Return value</a>
*
* @since 3.7
*/
public native static int showMessageBox( long hwndParent, String text, String caption, int type );
}

View File

@@ -16,7 +16,6 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Toolkit;
@@ -41,8 +40,8 @@ import javax.swing.text.JTextComponent;
import javax.swing.text.PasswordView;
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.ui.FlatStylingSupport.StyleableObject;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.UIScale;
@@ -215,12 +214,12 @@ public class FlatPasswordFieldUI
/** @since 2 */
@Override
protected Object applyStyleProperty( String key, Object value ) {
if( key.equals( "capsLockIconColor" ) && capsLockIcon instanceof FlatCapsLockIcon ) {
if( key.startsWith( "capsLockIcon" ) && capsLockIcon instanceof StyleableObject ) {
if( capsLockIconShared ) {
capsLockIcon = FlatStylingSupport.cloneIcon( capsLockIcon );
capsLockIconShared = false;
}
return ((FlatCapsLockIcon)capsLockIcon).applyStyleProperty( key, value );
return ((StyleableObject)capsLockIcon).applyStyleProperty( key, value );
}
return super.applyStyleProperty( key, value );
@@ -230,14 +229,15 @@ public class FlatPasswordFieldUI
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
Map<String, Class<?>> infos = super.getStyleableInfos( c );
infos.put( "capsLockIconColor", Color.class );
if( capsLockIcon instanceof StyleableObject )
infos.putAll( ((StyleableObject)capsLockIcon).getStyleableInfos() );
return infos;
}
@Override
public Object getStyleableValue( JComponent c, String key ) {
if( key.equals( "capsLockIconColor" ) && capsLockIcon instanceof FlatCapsLockIcon )
return ((FlatCapsLockIcon)capsLockIcon).getStyleableValue( key );
if( key.startsWith( "capsLockIcon" ) && capsLockIcon instanceof StyleableObject )
return ((StyleableObject)capsLockIcon).getStyleableValue( key );
return super.getStyleableValue( c, key );
}

View File

@@ -32,7 +32,6 @@ import java.awt.Panel;
import java.awt.Point;
import java.awt.PointerInfo;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
@@ -109,10 +108,8 @@ public class FlatPopupFactory
}
}
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 ), owner, contents );
return new NonFlashingPopup( getPopupForScreenOfOwner( owner, contents, x, y, isForceHeavyWeight( owner, contents, x, y ) ), owner, contents );
// macOS and Linux adds drop shadow to heavy weight popups
if( SystemInfo.isMacOS || SystemInfo.isLinux ) {
@@ -132,13 +129,9 @@ public class FlatPopupFactory
return popup;
}
// check whether popup overlaps a heavy weight component
if( !forceHeavyWeight && overlapsHeavyWeightComponent( owner, contents, x, y ) )
forceHeavyWeight = true;
// create drop shadow popup
Popup popupForScreenOfOwner = getPopupForScreenOfOwner( owner, contents, x, y, forceHeavyWeight );
GraphicsConfiguration gc = owner.getGraphicsConfiguration();
Popup popupForScreenOfOwner = getPopupForScreenOfOwner( owner, contents, x, y, isForceHeavyWeight( owner, contents, x, y ) );
GraphicsConfiguration gc = (owner != null) ? owner.getGraphicsConfiguration() : null;
return (gc != null && gc.isTranslucencyCapable())
? new DropShadowPopup( popupForScreenOfOwner, owner, contents )
: new NonFlashingPopup( popupForScreenOfOwner, owner, contents );
@@ -227,6 +220,11 @@ public class FlatPopupFactory
}
}
private static boolean isForceHeavyWeight( Component owner, Component contents, int x, int y ) {
boolean forceHeavyWeight = isOptionEnabled( owner, contents, FlatClientProperties.POPUP_FORCE_HEAVY_WEIGHT, "Popup.forceHeavyWeight" );
return forceHeavyWeight || hasVisibleGlassPane( owner ) || overlapsHeavyWeightComponent( owner, contents, x, y );
}
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;
@@ -306,13 +304,13 @@ public class FlatPopupFactory
break;
}
}
if( gc == null )
if( gc == null && owner != null )
gc = owner.getGraphicsConfiguration();
if( gc == null )
return null;
Rectangle screenBounds = gc.getBounds();
Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets( gc );
Insets screenInsets = FlatUIUtils.getScreenInsets( gc );
int screenTop = screenBounds.y + screenInsets.top;
// place tooltip above mouse location if there is enough space
@@ -365,14 +363,8 @@ public class FlatPopupFactory
}
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 ));
return SystemInfo.isMacOS &&
FlatSystemProperties.getBoolean( FlatSystemProperties.USE_ROUNDED_POPUP_BORDER, !isMacOS_14_4_orLater ) &&
FlatSystemProperties.getBoolean( FlatSystemProperties.USE_ROUNDED_POPUP_BORDER, true ) &&
FlatNativeMacLibrary.isLoaded();
}
@@ -476,6 +468,18 @@ public class FlatPopupFactory
//---- fixes --------------------------------------------------------------
private static boolean hasVisibleGlassPane( Component owner ) {
if( owner == null )
return false;
Window window = SwingUtilities.windowForComponent( owner );
if( !(window instanceof RootPaneContainer) )
return false;
Component glassPane = ((RootPaneContainer)window).getGlassPane();
return (glassPane != null && glassPane.isVisible());
}
private static boolean overlapsHeavyWeightComponent( Component owner, Component contents, int x, int y ) {
if( owner == null )
return false;
@@ -553,7 +557,15 @@ public class FlatPopupFactory
int x = popupWindow.getX();
int y = popupWindow.getY();
popup.show();
if( !popupWindow.isVisible() )
popup.show();
else {
// if the popup window is already visible (because it is reused),
// do not invoke Popup.show() because this would invoke Window.toFront(),
// which may have the side effect that an inactive owner window
// would be also moved to front and maybe hide previously active window
popupWindow.pack();
}
// restore popup window location if it has changed
// (probably scaled when screens use different scale factors)
@@ -625,7 +637,30 @@ public class FlatPopupFactory
void showImpl() {
if( delegate != null ) {
showPopupAndFixLocation( delegate, popupWindow );
// On macOS and Linux, the empty popup window is shown in popup.show()
// (in peer.setVisible(true) invoked from Component.show()),
// but the popup content is painted later via repaint manager.
// This may cause some flicker, especially during JVM warm-up or
// when running JVM in interpreter mode (option -Xint).
// To reduce flicker, immediately paint popup content as soon as popup window becomes visible.
// This also fixes a problem with JetBrainsRuntime JVM, where sometimes the popups were empty.
if( (SystemInfo.isMacOS || SystemInfo.isLinux) && popupWindow instanceof JWindow ) {
HierarchyListener l = e -> {
if( e.getID() == HierarchyEvent.HIERARCHY_CHANGED &&
(e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0 )
{
((JWindow)popupWindow).getRootPane().paintImmediately(
0, 0, popupWindow.getWidth(), popupWindow.getHeight() );
}
};
popupWindow.addHierarchyListener( l );
try {
showPopupAndFixLocation( delegate, popupWindow );
} finally {
popupWindow.removeHierarchyListener( l );
}
} else
showPopupAndFixLocation( delegate, popupWindow );
// increase tooltip size if necessary because it may be too small on HiDPI screens
// https://bugs.openjdk.java.net/browse/JDK-8213535
@@ -652,8 +687,11 @@ public class FlatPopupFactory
return;
disposed = true;
// immediately hide non-heavy weight popups or combobox popups
if( !(popupWindow instanceof JWindow) || contents instanceof BasicComboPopup ) {
// immediately hide non-heavy weight popups, popup menus and combobox popups
// of if system property is false
if( !(popupWindow instanceof JWindow) || contents instanceof JPopupMenu ||
!FlatSystemProperties.getBoolean( FlatSystemProperties.REUSE_VISIBLE_POPUP_WINDOW, true ) )
{
hideImpl();
return;
}
@@ -684,6 +722,21 @@ public class FlatPopupFactory
// restore background so that it can not affect other LaFs (when switching)
// because popup windows are cached and reused
popupWindow.setBackground( oldPopupWindowBackground );
// On macOS, popupWindow.setBackground(...) invoked from constructor,
// has no affect if the popup window peer (a NSWindow) is already created,
// which is the case when reusing a cached popup window
// (see class PopupFactory.HeavyWeightPopup).
// This may result in flicker when e.g. showing a popup in a light theme,
// then switching to a dark theme and again showing a popup,
// because the underling NSWindow still has a light background,
// which may be shown shortly before the actual dark popup content is shown.
// To fix this, dispose the popup window, which disposes the NSWindow.
// The AWT popup window stays in the popup cache, but when reusing it later,
// a new peer and a new NSWindow is created and gets the correct background.
if( SystemInfo.isMacOS )
popupWindow.dispose();
popupWindow = null;
}
}
@@ -715,6 +768,7 @@ public class FlatPopupFactory
private class DropShadowPopup
extends NonFlashingPopup
implements ComponentListener
{
// light weight
private JComponent lightComp;
@@ -774,7 +828,7 @@ public class FlatPopupFactory
}
// Windows 11: reset corner preference on reused heavy weight popups
if( isWindows11BorderSupported() ) {
if( SystemInfo.isWindows_11_orLater && FlatNativeWindowsLibrary.isLoaded() ) {
resetWindows11Border( popupWindow );
if( dropShadowWindow != null )
resetWindows11Border( dropShadowWindow );
@@ -844,10 +898,18 @@ public class FlatPopupFactory
if( insets.left != 0 || insets.top != 0 )
lightComp.setLocation( lightComp.getX() - insets.left, lightComp.getY() - insets.top );
}
if( popupWindow != null ) {
removeAllPopupWindowComponentListeners();
popupWindow.addComponentListener( this );
}
}
@Override
void hideImpl() {
if( popupWindow != null )
removeAllPopupWindowComponentListeners();
if( dropShadowDelegate != null ) {
dropShadowDelegate.hide();
dropShadowDelegate = null;
@@ -947,23 +1009,55 @@ public class FlatPopupFactory
@Override
void reset( Component contents, int ownerX, int ownerY ) {
if( popupWindow != null )
removeAllPopupWindowComponentListeners();
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();
updateDropShadowWindowBounds();
}
// update drop shadow popup window location
int x = popupWindow.getX() - insets.left;
int y = popupWindow.getY() - insets.top;
dropShadowWindow.setLocation( x, y );
private void updateDropShadowWindowBounds() {
if( dropShadowWindow == null )
return;
// calculate size of drop shadow window
Dimension size = popupWindow.getSize();
Insets insets = dropShadowPanel2.getInsets();
int w = size.width + insets.left + insets.right;
int h = size.height + insets.top + insets.bottom;
// update drop shadow popup window bounds
int x = popupWindow.getX() - insets.left;
int y = popupWindow.getY() - insets.top;
dropShadowWindow.setBounds( x, y, w, h );
dropShadowWindow.validate();
}
private void removeAllPopupWindowComponentListeners() {
// make sure that there is no old component listener
// necessary because this class is cloned if reusing popup windows
for( ComponentListener l : popupWindow.getComponentListeners() ) {
if( l instanceof DropShadowPopup )
popupWindow.removeComponentListener( l );
}
}
//---- interface ComponentListener ----
@Override
public void componentResized( ComponentEvent e ) {
if( e.getSource() == popupWindow )
updateDropShadowWindowBounds();
}
@Override
public void componentMoved( ComponentEvent e ) {
if( e.getSource() == popupWindow )
updateDropShadowWindowBounds();
}
@Override public void componentShown( ComponentEvent e ) {}
@Override public void componentHidden( ComponentEvent e ) {}
}
}

View File

@@ -23,7 +23,7 @@ import java.awt.Insets;
import java.util.Map;
import javax.swing.JScrollPane;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableBorder;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.UIScale;
@@ -37,7 +37,7 @@ import com.formdev.flatlaf.util.UIScale;
*/
public class FlatPopupMenuBorder
extends FlatLineBorder
implements StyleableBorder
implements StyleableObject
{
private Color borderColor;

View File

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

View File

@@ -45,6 +45,7 @@ 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.StyleableObject;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.HiDPIUtils;
@@ -203,16 +204,17 @@ public class FlatRadioButtonUI
protected Object applyStyleProperty( AbstractButton b, String key, Object value ) {
// style icon
if( key.startsWith( "icon." ) ) {
if( !(icon instanceof FlatCheckBoxIcon) )
return new UnknownStyleException( key );
Icon icon = getRealIcon( b );
if( !(icon instanceof StyleableObject) )
throw new UnknownStyleException( key );
if( iconShared ) {
icon = FlatStylingSupport.cloneIcon( icon );
if( icon == this.icon && iconShared ) {
this.icon = icon = FlatStylingSupport.cloneIcon( icon );
iconShared = false;
}
key = key.substring( "icon.".length() );
return ((FlatCheckBoxIcon)icon).applyStyleProperty( key, value );
return ((StyleableObject)icon).applyStyleProperty( key, value );
}
if( "iconTextGap".equals( key ) && value instanceof Integer )
@@ -225,10 +227,9 @@ public class FlatRadioButtonUI
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
Map<String, Class<?>> infos = FlatStylingSupport.getAnnotatedStyleableInfos( this );
if( icon instanceof FlatCheckBoxIcon ) {
for( Map.Entry<String, Class<?>> e : ((FlatCheckBoxIcon)icon).getStyleableInfos().entrySet() )
infos.put( "icon.".concat( e.getKey() ), e.getValue() );
}
Icon icon = getRealIcon( c );
if( icon instanceof StyleableObject )
FlatStylingSupport.putAllPrefixKey( infos, "icon.", ((StyleableObject)icon).getStyleableInfos() );
return infos;
}
@@ -237,8 +238,9 @@ public class FlatRadioButtonUI
public Object getStyleableValue( JComponent c, String key ) {
// style icon
if( key.startsWith( "icon." ) ) {
return (icon instanceof FlatCheckBoxIcon)
? ((FlatCheckBoxIcon)icon).getStyleableValue( key.substring( "icon.".length() ) )
Icon icon = getRealIcon( c );
return (icon instanceof StyleableObject)
? ((StyleableObject)icon).getStyleableValue( key.substring( "icon.".length() ) )
: null;
}
@@ -332,16 +334,18 @@ public class FlatRadioButtonUI
}
private int getIconFocusWidth( JComponent c ) {
AbstractButton b = (AbstractButton) c;
Icon icon = b.getIcon();
if( icon == null )
icon = getDefaultIcon();
Icon icon = getRealIcon( c );
return (icon instanceof FlatCheckBoxIcon)
? Math.round( UIScale.scale( ((FlatCheckBoxIcon)icon).getFocusWidth() ) )
: 0;
}
private Icon getRealIcon( JComponent c ) {
AbstractButton b = (AbstractButton) c;
Icon icon = b.getIcon();
return (icon != null) ? icon : getDefaultIcon();
}
@Override
public int getBaseline( JComponent c, int width, int height ) {
return FlatButtonUI.getBaselineImpl( c, width, height );

View File

@@ -20,6 +20,7 @@ import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
@@ -31,6 +32,7 @@ import java.awt.Window;
import java.awt.event.ComponentListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Objects;
import java.util.function.Function;
import javax.swing.JComponent;
import javax.swing.JDialog;
@@ -152,8 +154,28 @@ public class FlatRootPaneUI
Container parent = c.getParent();
if( parent instanceof JFrame || parent instanceof JDialog ) {
Color background = parent.getBackground();
if( background == null || background instanceof UIResource )
parent.setBackground( UIManager.getColor( "control" ) );
if( background == null || background instanceof UIResource ) {
if( SystemInfo.isMacOS ) {
// Setting window background on macOS immediately fills the whole window
// with that color, and slightly delayed, the Swing repaint manager
// repaints the actual window content. This results in some flashing
// when switching from a light to a dark theme (or vice versa).
// --> delay setting window background and immediately repaint window content
Runnable r = () -> {
parent.setBackground( UIManager.getColor( "control" ) );
c.paintImmediately( 0, 0, c.getWidth(), c.getHeight() );
};
// for class FlatAnimatedLafChange:
// if animated Laf change is in progress, set background color when
// animation has finished to avoid/reduce flashing
if( c.getClientProperty( "FlatLaf.internal.animatedLafChange" ) != null )
c.putClientProperty( "FlatLaf.internal.animatedLafChange.runWhenFinished", r );
else
EventQueue.invokeLater( r );
} else
parent.setBackground( UIManager.getColor( "control" ) );
}
}
macClearBackgroundForTranslucentWindow( c );
@@ -485,8 +507,12 @@ public class FlatRootPaneUI
break;
case "ancestor":
if( e.getNewValue() instanceof Window )
if( e.getNewValue() instanceof Window ) {
if( titlePane != null && !Objects.equals( titlePane.windowStyle, FlatTitlePane.getWindowStyle( rootPane ) ) )
setTitlePane( createTitlePane() );
macClearBackgroundForTranslucentWindow( rootPane );
}
macUninstallWindowBackgroundListener( rootPane );
macInstallWindowBackgroundListener( rootPane );
@@ -684,7 +710,7 @@ public class FlatRootPaneUI
* Window border used for non-native window decorations.
*/
public static class FlatWindowBorder
extends BorderUIResource.EmptyBorderUIResource
extends FlatEmptyBorder
{
protected final Color activeBorderColor = UIManager.getColor( "RootPane.activeBorderColor" );
protected final Color inactiveBorderColor = UIManager.getColor( "RootPane.inactiveBorderColor" );
@@ -717,7 +743,10 @@ public class FlatRootPaneUI
}
private void paintImpl( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) {
g.drawRect( x, y, width - 1, height - 1 );
Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g );
float lineWidth = (float) (UIScale.scale( 1f ) * scaleFactor);
g.fill( FlatUIUtils.createRectangle( x, y, width, height, lineWidth ) );
FlatUIUtils.resetRenderingHints( g, oldRenderingHints );
}
protected boolean isWindowMaximized( Component c ) {

View File

@@ -227,7 +227,13 @@ public class FlatSliderUI
/** @since 2 */
protected void applyStyle( Object style ) {
Dimension oldThumbSize = thumbSize;
int oldFocusWidth = focusWidth;
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
if( !thumbSize.equals( oldThumbSize ) || focusWidth != oldFocusWidth )
calculateGeometry();
}
/** @since 2 */

View File

@@ -140,8 +140,6 @@ public class FlatSpinnerUI
buttonHoverArrowColor = UIManager.getColor( "Spinner.buttonHoverArrowColor" );
buttonPressedArrowColor = UIManager.getColor( "Spinner.buttonPressedArrowColor" );
padding = UIManager.getInsets( "Spinner.padding" );
MigLayoutVisualPadding.install( spinner );
}
@Override
@@ -162,8 +160,6 @@ public class FlatSpinnerUI
oldStyleValues = null;
borderShared = null;
MigLayoutVisualPadding.uninstall( spinner );
}
@Override
@@ -173,6 +169,8 @@ public class FlatSpinnerUI
addEditorFocusListener( spinner.getEditor() );
spinner.addFocusListener( getHandler() );
spinner.addPropertyChangeListener( getHandler() );
MigLayoutVisualPadding.install( spinner );
}
@Override
@@ -184,6 +182,8 @@ public class FlatSpinnerUI
spinner.removePropertyChangeListener( getHandler() );
handler = null;
MigLayoutVisualPadding.uninstall( spinner );
}
private Handler getHandler() {

View File

@@ -42,7 +42,6 @@ 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;
/**
* Support for styling components in CSS syntax.
@@ -52,6 +51,9 @@ import com.formdev.flatlaf.util.SystemInfo;
*/
public class FlatStylingSupport
{
//---- annotations --------------------------------------------------------
/**
* Indicates that a field is intended to be used by FlatLaf styling support.
* <p>
@@ -99,17 +101,56 @@ public class FlatStylingSupport
}
//---- interfaces ---------------------------------------------------------
/** @since 2 */
public interface StyleableUI {
Map<String, Class<?>> getStyleableInfos( JComponent c ) throws IllegalArgumentException;
/** @since 2.5 */ Object getStyleableValue( JComponent c, String key ) throws IllegalArgumentException;
}
/** @since 2 */
public interface StyleableBorder {
Object applyStyleProperty( String key, Object value );
Map<String, Class<?>> getStyleableInfos() throws IllegalArgumentException;
/** @since 2.5 */ Object getStyleableValue( String key ) throws IllegalArgumentException;
/**
* An object that implements this interface is intended to support FlatLaf styling.
*
* @since 3.7
*/
public interface StyleableObject {
/**
* Applies the given value to this object.
* <p>
* The default implementation invokes {@link FlatStylingSupport#applyToAnnotatedObject(Object, String, Object)}.
*
* @param key the name of the property
* @param value the new value
* @return the old value of the property
*/
default Object applyStyleProperty( String key, Object value )
throws UnknownStyleException, IllegalArgumentException
{
return applyToAnnotatedObject( this, key, value );
}
/**
* Returns a map of all styleable properties.
* The key is the name of the property and the value the type of the property.
* <p>
* The default implementation invokes {@link FlatStylingSupport#getAnnotatedStyleableInfos(Object)}.
*/
default Map<String, Class<?>> getStyleableInfos() throws IllegalArgumentException {
return getAnnotatedStyleableInfos( this );
}
/**
* Returns the current value for the given property key.
* <p>
* The default implementation invokes {@link FlatStylingSupport#getAnnotatedStyleableValue(Object, String)}.
*
* @param key the name of the property
* @return the current value of the property
*/
default Object getStyleableValue( String key ) throws IllegalArgumentException {
return getAnnotatedStyleableValue( this, key );
}
}
/** @since 2.5 */
@@ -118,6 +159,8 @@ public class FlatStylingSupport
}
//---- methods ------------------------------------------------------------
/**
* Returns the style specified in client property {@link FlatClientProperties#STYLE}.
*/
@@ -325,22 +368,24 @@ public class FlatStylingSupport
return null;
Map<String, Object> oldValues = new HashMap<>();
outer:
for( Map.Entry<String, Object> e : style.entrySet() ) {
String key = e.getKey();
Object newValue = e.getValue();
// handle key prefix
if( key.startsWith( "[" ) ) {
if( (SystemInfo.isWindows && key.startsWith( "[win]" )) ||
(SystemInfo.isMacOS && key.startsWith( "[mac]" )) ||
(SystemInfo.isLinux && key.startsWith( "[linux]" )) ||
(key.startsWith( "[light]" ) && !FlatLaf.isLafDark()) ||
(key.startsWith( "[dark]" ) && FlatLaf.isLafDark()) )
{
// prefix is known and enabled --> remove prefix
key = key.substring( key.indexOf( ']' ) + 1 );
} else
continue;
while( key.startsWith( "[" ) ) {
int closeIndex = key.indexOf( ']' );
if( closeIndex < 0 )
continue outer;
String prefix = key.substring( 0, closeIndex + 1 );
String lightOrDarkPrefix = FlatLaf.getUIKeyLightOrDarkPrefix( FlatLaf.isLafDark() );
if( !lightOrDarkPrefix.equals( prefix ) && !FlatLaf.getUIKeyPlatformPrefixes().contains( prefix ) )
continue outer;
// prefix is known and enabled --> remove prefix
key = key.substring( closeIndex + 1 );
}
Object oldValue = applyProperty.apply( key, newValue );
@@ -674,7 +719,7 @@ public class FlatStylingSupport
} catch( UnknownStyleException ex ) {
// apply to border
Border border = c.getBorder();
if( border instanceof StyleableBorder ) {
if( border instanceof StyleableObject ) {
if( borderShared.get() ) {
border = cloneBorder( border );
c.setBorder( border );
@@ -682,7 +727,7 @@ public class FlatStylingSupport
}
try {
return ((StyleableBorder)border).applyStyleProperty( key, value );
return ((StyleableObject)border).applyStyleProperty( key, value );
} catch( UnknownStyleException ex2 ) {
// ignore
}
@@ -832,8 +877,8 @@ public class FlatStylingSupport
}
public static void collectStyleableInfos( Border border, Map<String, Class<?>> infos ) {
if( border instanceof StyleableBorder )
infos.putAll( ((StyleableBorder)border).getStyleableInfos() );
if( border instanceof StyleableObject )
infos.putAll( ((StyleableObject)border).getStyleableInfos() );
}
public static void putAllPrefixKey( Map<String, Class<?>> infos, String keyPrefix, Map<String, Class<?>> infos2 ) {
@@ -881,8 +926,8 @@ public class FlatStylingSupport
}
public static Object getAnnotatedStyleableValue( Object obj, Border border, String key ) {
if( border instanceof StyleableBorder ) {
Object value = ((StyleableBorder)border).getStyleableValue( key );
if( border instanceof StyleableObject ) {
Object value = ((StyleableObject)border).getStyleableValue( key );
if( value != null )
return value;
}

View File

@@ -92,8 +92,8 @@ import javax.swing.text.JTextComponent;
import javax.swing.text.View;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.icons.FlatTabbedPaneCloseIcon;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableObject;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.Animator;
@@ -205,6 +205,7 @@ public class FlatTabbedPaneUI
protected static final int WIDTH_MODE_PREFERRED = 0;
protected static final int WIDTH_MODE_EQUAL = 1;
protected static final int WIDTH_MODE_COMPACT = 2;
/** @since 3.7 */ protected static final int WIDTH_MODE_ICON_ONLY = 3;
// tab rotation
/** @since 3.3 */ protected static final int NONE = -1;
@@ -670,15 +671,15 @@ public class FlatTabbedPaneUI
protected Object applyStyleProperty( String key, Object value ) {
// close icon
if( key.startsWith( "close" ) ) {
if( !(closeIcon instanceof FlatTabbedPaneCloseIcon) )
return new UnknownStyleException( key );
if( !(closeIcon instanceof StyleableObject) )
throw new UnknownStyleException( key );
if( closeIconShared ) {
closeIcon = FlatStylingSupport.cloneIcon( closeIcon );
closeIconShared = false;
}
return ((FlatTabbedPaneCloseIcon)closeIcon).applyStyleProperty( key, value );
return ((StyleableObject)closeIcon).applyStyleProperty( key, value );
}
if( value instanceof String ) {
@@ -720,8 +721,8 @@ public class FlatTabbedPaneUI
infos.put( "tabAreaInsets", Insets.class );
infos.put( "textIconGap", int.class );
FlatStylingSupport.collectAnnotatedStyleableInfos( this, infos );
if( closeIcon instanceof FlatTabbedPaneCloseIcon )
infos.putAll( ((FlatTabbedPaneCloseIcon)closeIcon).getStyleableInfos() );
if( closeIcon instanceof StyleableObject )
infos.putAll( ((StyleableObject)closeIcon).getStyleableInfos() );
return infos;
}
@@ -730,8 +731,8 @@ public class FlatTabbedPaneUI
public Object getStyleableValue( JComponent c, String key ) {
// close icon
if( key.startsWith( "close" ) ) {
return (closeIcon instanceof FlatTabbedPaneCloseIcon)
? ((FlatTabbedPaneCloseIcon)closeIcon).getStyleableValue( key )
return (closeIcon instanceof StyleableObject)
? ((StyleableObject)closeIcon).getStyleableValue( key )
: null;
}
@@ -780,6 +781,7 @@ public class FlatTabbedPaneUI
case WIDTH_MODE_PREFERRED: return TABBED_PANE_TAB_WIDTH_MODE_PREFERRED;
case WIDTH_MODE_EQUAL: return TABBED_PANE_TAB_WIDTH_MODE_EQUAL;
case WIDTH_MODE_COMPACT: return TABBED_PANE_TAB_WIDTH_MODE_COMPACT;
case WIDTH_MODE_ICON_ONLY: return TABBED_PANE_TAB_WIDTH_MODE_ICON_ONLY;
}
case "tabRotation":
@@ -926,9 +928,10 @@ public class FlatTabbedPaneUI
int tabWidth;
Icon icon;
if( tabWidthMode == WIDTH_MODE_COMPACT &&
tabIndex != tabPane.getSelectedIndex() &&
isHorizontalOrRotated( tabPlacement ) &&
if( ((tabWidthMode == WIDTH_MODE_COMPACT &&
tabIndex != tabPane.getSelectedIndex() &&
isHorizontalOrRotated( tabPlacement )) ||
tabWidthMode == WIDTH_MODE_ICON_ONLY) &&
tabPane.getTabComponentAt( tabIndex ) == null &&
(icon = getIconForTab( tabIndex )) != null )
{
@@ -1247,7 +1250,8 @@ public class FlatTabbedPaneUI
Font font = tabPane.getFont();
FontMetrics metrics = tabPane.getFontMetrics( font );
boolean isCompact = (icon != null && !isSelected && getTabWidthMode() == WIDTH_MODE_COMPACT && isHorizontalOrRotated( tabPlacement ));
if( isCompact )
boolean isIconOnly = (icon != null && getTabWidthMode() == WIDTH_MODE_ICON_ONLY);
if( isCompact || isIconOnly )
title = null;
String clippedTitle = layoutAndClipLabel( tabPlacement, metrics, tabIndex, title, icon, tabRect, iconRect, textRect, isSelected );
@@ -1283,7 +1287,7 @@ debug*/
}
// paint title and icon
if( !isCompact )
if( !isCompact || isIconOnly )
paintText( g, tabPlacement, font, metrics, tabIndex, clippedTitle, textRect, isSelected );
paintIcon( g, tabPlacement, tabIndex, icon, iconRect, isSelected );
}
@@ -2160,6 +2164,7 @@ debug*/
case TABBED_PANE_TAB_WIDTH_MODE_PREFERRED: return WIDTH_MODE_PREFERRED;
case TABBED_PANE_TAB_WIDTH_MODE_EQUAL: return WIDTH_MODE_EQUAL;
case TABBED_PANE_TAB_WIDTH_MODE_COMPACT: return WIDTH_MODE_COMPACT;
case TABBED_PANE_TAB_WIDTH_MODE_ICON_ONLY: return WIDTH_MODE_ICON_ONLY;
}
}
@@ -2508,7 +2513,7 @@ debug*/
// 2. text of label or text component in custom tab component (including children)
// 3. accessible name of tab
// 4. accessible name of custom tab component (including children)
// 5. string "n. Tab"
// 5. string "n. Tab", if tab does not have an icon
String title = tabPane.getTitleAt( tabIndex );
if( StringUtils.isEmpty( title ) ) {
Component tabComp = tabPane.getTabComponentAt( tabIndex );
@@ -2518,7 +2523,7 @@ debug*/
title = tabPane.getAccessibleContext().getAccessibleChild( tabIndex ).getAccessibleContext().getAccessibleName();
if( StringUtils.isEmpty( title ) && tabComp instanceof Accessible )
title = findTabTitleInAccessible( (Accessible) tabComp );
if( StringUtils.isEmpty( title ) )
if( StringUtils.isEmpty( title ) && tabPane.getIconAt( tabIndex ) == null )
title = (tabIndex + 1) + ". Tab";
}
@@ -2539,6 +2544,12 @@ debug*/
if( !tabPane.isEnabled() || !tabPane.isEnabledAt( tabIndex ) )
menuItem.setEnabled( false );
// make menu item smaller if it contains icon only
if( StringUtils.isEmpty( title ) ) {
menuItem.putClientProperty( FlatClientProperties.STYLE,
"minimumWidth: 0; textNoAcceleratorGap: 0; acceleratorArrowGap: 0" );
}
menuItem.addActionListener( e -> selectTab( tabIndex ) );
return menuItem;
}
@@ -2707,8 +2718,13 @@ debug*/
// because this listener receives mouse events for the whole tabbed pane,
// we have to check whether the mouse is located over the viewport
if( !isInViewport( e.getX(), e.getY() ) )
if( !isInViewport( e.getX(), e.getY() ) ) {
// if it is not in the viewport, retarget the event to a parent container
// which might support scrolling (e.g. a surrounding ScrollPane)
Container parent = tabPane.getParent();
parent.dispatchEvent( SwingUtilities.convertMouseEvent( tabPane, e, parent ) );
return;
}
lastMouseX = e.getX();
lastMouseY = e.getY();

View File

@@ -34,11 +34,11 @@ 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.Icon;
import javax.swing.JComponent;
import javax.swing.JScrollPane;
import javax.swing.JTable;
@@ -65,6 +65,7 @@ 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.FlatUIUtils.FlatPropertyWatcher;
import com.formdev.flatlaf.util.Graphics2DProxy;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
@@ -188,29 +189,26 @@ public class FlatTableUI
if( rowHeight > 0 )
LookAndFeel.installProperty( table, "rowHeight", UIScale.scale( rowHeight ) );
FlatTablePropertyWatcher watcher = FlatTablePropertyWatcher.get( table );
if( watcher != null )
watcher.enabled = false;
if( !showHorizontalLines && (watcher == null || !watcher.showHorizontalLinesChanged) ) {
oldShowHorizontalLines = table.getShowHorizontalLines();
table.setShowHorizontalLines( false );
if( !showHorizontalLines ) {
FlatPropertyWatcher.runIfNotChanged( table, "showHorizontalLines", () -> {
oldShowHorizontalLines = table.getShowHorizontalLines();
table.setShowHorizontalLines( false );
} );
}
if( !showVerticalLines && (watcher == null || !watcher.showVerticalLinesChanged) ) {
oldShowVerticalLines = table.getShowVerticalLines();
table.setShowVerticalLines( false );
if( !showVerticalLines ) {
FlatPropertyWatcher.runIfNotChanged( table, "showVerticalLines", () -> {
oldShowVerticalLines = table.getShowVerticalLines();
table.setShowVerticalLines( false );
} );
}
if( intercellSpacing != null && (watcher == null || !watcher.intercellSpacingChanged) ) {
oldIntercellSpacing = table.getIntercellSpacing();
table.setIntercellSpacing( intercellSpacing );
if( intercellSpacing != null ) {
FlatPropertyWatcher.runIfNotChanged( table, "rowMargin", () -> {
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 )
@@ -230,25 +228,24 @@ 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() &&
(watcher == null || !watcher.showHorizontalLinesChanged) )
table.setShowHorizontalLines( true );
if( !showVerticalLines && oldShowVerticalLines && !table.getShowVerticalLines() &&
(watcher == null || !watcher.showVerticalLinesChanged) )
table.setShowVerticalLines( true );
if( !showHorizontalLines && oldShowHorizontalLines && !table.getShowHorizontalLines() ) {
FlatPropertyWatcher.runIfNotChanged( table, "showHorizontalLines", () -> {
table.setShowHorizontalLines( true );
} );
}
if( !showVerticalLines && oldShowVerticalLines && !table.getShowVerticalLines() ) {
FlatPropertyWatcher.runIfNotChanged( table, "showVerticalLines", () -> {
table.setShowVerticalLines( true );
} );
}
// restore old intercell spacing (if not modified)
if( intercellSpacing != null && table.getIntercellSpacing().equals( intercellSpacing ) &&
(watcher == null || !watcher.intercellSpacingChanged) )
table.setIntercellSpacing( oldIntercellSpacing );
if( watcher != null )
watcher.enabled = true;
if( intercellSpacing != null && table.getIntercellSpacing().equals( intercellSpacing ) ) {
FlatPropertyWatcher.runIfNotChanged( table, "rowMargin", () -> {
table.setIntercellSpacing( oldIntercellSpacing );
} );
}
// uninstall boolean renderer
if( table.getDefaultRenderer( Boolean.class ) instanceof FlatBooleanRenderer ) {
@@ -937,48 +934,6 @@ public class FlatTableUI
}
}
//---- class FlatTablePropertyWatcher -------------------------------------
/**
* Listener that watches for change of some table properties from application code.
* This information is used in {@link FlatTableUI#installDefaults()} and
* {@link FlatTableUI#uninstallDefaults()} to decide whether FlatLaf modifies those properties.
* If they are modified in application code, FlatLaf no longer changes them.
*
* The listener is added once for each table, but never removed.
* So switching Laf/theme reuses existing listener.
*/
private static class FlatTablePropertyWatcher
implements PropertyChangeListener
{
boolean enabled = true;
boolean showHorizontalLinesChanged;
boolean showVerticalLinesChanged;
boolean intercellSpacingChanged;
static FlatTablePropertyWatcher get( JTable table ) {
for( PropertyChangeListener l : table.getPropertyChangeListeners() ) {
if( l instanceof FlatTablePropertyWatcher )
return (FlatTablePropertyWatcher) l;
}
return null;
}
//---- interface PropertyChangeListener ----
@Override
public void propertyChange( PropertyChangeEvent e ) {
if( !enabled )
return;
switch( e.getPropertyName() ) {
case "showHorizontalLines": showHorizontalLinesChanged = true; break;
case "showVerticalLines": showVerticalLinesChanged = true; break;
case "rowMargin": intercellSpacingChanged = true; break;
}
}
}
//---- class FlatBooleanRenderer ------------------------------------------
private static class FlatBooleanRenderer
@@ -989,12 +944,14 @@ public class FlatTableUI
FlatBooleanRenderer() {
setHorizontalAlignment( SwingConstants.CENTER );
setIcon( new FlatCheckBoxIcon() {
Icon icon = new FlatCheckBoxIcon() {
@Override
protected boolean isSelected( Component c ) {
return selected;
}
} );
};
setIcon( icon );
setDisabledIcon( icon );
}
@Override

View File

@@ -174,8 +174,6 @@ public class FlatTextFieldUI
defaultMargin = UIManager.getInsets( prefix + ".margin" );
LookAndFeel.installProperty( getComponent(), "opaque", false );
MigLayoutVisualPadding.install( getComponent() );
}
@Override
@@ -193,8 +191,6 @@ public class FlatTextFieldUI
oldStyleValues = null;
borderShared = null;
MigLayoutVisualPadding.uninstall( getComponent() );
}
@Override
@@ -204,6 +200,8 @@ public class FlatTextFieldUI
// necessary to update focus border and background
focusListener = new FlatUIUtils.RepaintFocusListener( getComponent(), null );
getComponent().addFocusListener( focusListener );
MigLayoutVisualPadding.install( getComponent() );
}
@Override
@@ -217,6 +215,8 @@ public class FlatTextFieldUI
getComponent().getDocument().removeDocumentListener( documentListener );
documentListener = null;
}
MigLayoutVisualPadding.uninstall( getComponent() );
}
@Override
@@ -628,7 +628,7 @@ debug*/
/**
* Returns the rectangle used to paint leading and trailing icons.
* It invokes {@code super.getVisibleEditorRect()} and reduces left and/or
* right margin if the text field has leading or trailing icons or components.
* right insets if the text field has leading or trailing icons or components.
* Also, the preferred widths of leading and trailing components are removed.
*
* @since 2
@@ -660,24 +660,24 @@ debug*/
}
}
// if a leading/trailing icons (or components) are shown, then the left/right margins are reduced
// to the top margin, which places the icon nicely centered on left/right side
// if a leading/trailing icons (or components) are shown, then the left/right insets are reduced
// to the top inset, which places the icon nicely centered on left/right side
if( leftVisible || (ltr ? hasLeadingIcon() : hasTrailingIcon()) ) {
// reduce left margin
Insets margin = getComponent().getMargin();
int newLeftMargin = Math.min( margin.left, margin.top );
if( newLeftMargin < margin.left ) {
int diff = scale( margin.left - newLeftMargin );
// reduce left inset
Insets insets = getComponent().getInsets();
int newLeftInset = Math.min( insets.left, insets.top );
if( newLeftInset < insets.left ) {
int diff = insets.left - newLeftInset;
r.x -= diff;
r.width += diff;
}
}
if( rightVisible || (ltr ? hasTrailingIcon() : hasLeadingIcon()) ) {
// reduce right margin
Insets margin = getComponent().getMargin();
int newRightMargin = Math.min( margin.right, margin.top );
if( newRightMargin < margin.left )
r.width += scale( margin.right - newRightMargin );
// reduce right inset
Insets insets = getComponent().getInsets();
int newRightInset = Math.min( insets.right, insets.top );
if( newRightInset < insets.left )
r.width += insets.right - newRightInset;
}
// make sure that width and height are not negative

View File

@@ -33,7 +33,6 @@ import java.awt.Image;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
@@ -90,13 +89,16 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault TitlePane.iconSize Dimension
* @uiDefault TitlePane.iconMargins Insets
* @uiDefault TitlePane.titleMargins Insets
* @uiDefault TitlePane.menuBarEmbedded boolean
* @uiDefault TitlePane.titleMinimumWidth int
* @uiDefault TitlePane.buttonMinimumWidth int
* @uiDefault TitlePane.buttonMaximizedHeight int
* @uiDefault TitlePane.buttonsGap int
* @uiDefault TitlePane.buttonsMargins Insets
* @uiDefault TitlePane.buttonsFillVertically boolean
* @uiDefault TitlePane.centerTitle boolean
* @uiDefault TitlePane.centerTitleIfMenuBarEmbedded boolean
* @uiDefault TitlePane.showIconBesideTitle boolean
* @uiDefault TitlePane.menuBarEmbedded boolean
* @uiDefault TitlePane.menuBarTitleGap int
* @uiDefault TitlePane.menuBarTitleMinimumGap int
* @uiDefault TitlePane.closeIcon Icon
@@ -124,9 +126,14 @@ public class FlatTitlePane
/** @since 2.5 */ protected final boolean showIconInDialogs;
/** @since 2 */ protected final int noIconLeftGap;
protected final Dimension iconSize;
/** @since 3.6 */ protected final Insets iconMargins;
/** @since 3.6 */ protected final Insets titleMargins;
/** @since 2.4 */ protected final int titleMinimumWidth;
/** @since 2.4 */ protected final int buttonMinimumWidth;
protected final int buttonMaximizedHeight;
/** @since 3.6 */ protected final int buttonsGap;
/** @since 3.6 */ protected final Insets buttonsMargins;
/** @since 3.6 */ protected final boolean buttonsFillVertically;
protected final boolean centerTitle;
protected final boolean centerTitleIfMenuBarEmbedded;
/** @since 2.4 */ protected final boolean showIconBesideTitle;
@@ -146,6 +153,9 @@ public class FlatTitlePane
protected JButton restoreButton;
protected JButton closeButton;
private JComponent iconifyMaximizeGapComp;
private JComponent maximizeCloseGapComp;
protected Window window;
private final Handler handler;
@@ -180,9 +190,7 @@ public class FlatTitlePane
public FlatTitlePane( JRootPane rootPane ) {
this.rootPane = rootPane;
Window w = SwingUtilities.getWindowAncestor( rootPane );
String defaultWindowStyle = (w != null && w.getType() == Window.Type.UTILITY) ? WINDOW_STYLE_SMALL : null;
windowStyle = clientProperty( rootPane, WINDOW_STYLE, defaultWindowStyle, String.class );
windowStyle = getWindowStyle( rootPane );
titleFont = FlatUIUtils.getSubUIFont( "TitlePane.font", windowStyle );
activeBackground = FlatUIUtils.getSubUIColor( "TitlePane.background", windowStyle );
@@ -197,9 +205,14 @@ public class FlatTitlePane
showIconInDialogs = FlatUIUtils.getSubUIBoolean( "TitlePane.showIconInDialogs", windowStyle, true );
noIconLeftGap = FlatUIUtils.getSubUIInt( "TitlePane.noIconLeftGap", windowStyle, 8 );
iconSize = FlatUIUtils.getSubUIDimension( "TitlePane.iconSize", windowStyle );
iconMargins = FlatUIUtils.getSubUIInsets( "TitlePane.iconMargins", windowStyle );
titleMargins = FlatUIUtils.getSubUIInsets( "TitlePane.titleMargins", windowStyle );
titleMinimumWidth = FlatUIUtils.getSubUIInt( "TitlePane.titleMinimumWidth", windowStyle, 60 );
buttonMinimumWidth = FlatUIUtils.getSubUIInt( "TitlePane.buttonMinimumWidth", windowStyle, 30 );
buttonMaximizedHeight = FlatUIUtils.getSubUIInt( "TitlePane.buttonMaximizedHeight", windowStyle, 0 );
buttonsGap = FlatUIUtils.getSubUIInt( "TitlePane.buttonsGap", windowStyle, 0 );
buttonsMargins = FlatUIUtils.getSubUIInsets( "TitlePane.buttonsMargins", windowStyle );
buttonsFillVertically = FlatUIUtils.getSubUIBoolean( "TitlePane.buttonsFillVertically", windowStyle, true );
centerTitle = FlatUIUtils.getSubUIBoolean( "TitlePane.centerTitle", windowStyle, false );
centerTitleIfMenuBarEmbedded = FlatUIUtils.getSubUIBoolean( "TitlePane.centerTitleIfMenuBarEmbedded", windowStyle, true );
showIconBesideTitle = FlatUIUtils.getSubUIBoolean( "TitlePane.showIconBesideTitle", windowStyle, false );
@@ -229,6 +242,12 @@ public class FlatTitlePane
applyComponentOrientation( rootPane.getComponentOrientation() );
}
static String getWindowStyle( JRootPane rootPane ) {
Window w = SwingUtilities.getWindowAncestor( rootPane );
String defaultWindowStyle = (w != null && w.getType() == Window.Type.UTILITY) ? WINDOW_STYLE_SMALL : null;
return clientProperty( rootPane, WINDOW_STYLE, defaultWindowStyle, String.class );
}
protected FlatTitlePaneBorder createTitlePaneBorder() {
return new FlatTitlePaneBorder();
}
@@ -246,8 +265,8 @@ public class FlatTitlePane
setUI( new FlatTitleLabelUI() );
}
};
iconLabel.setBorder( new FlatEmptyBorder( FlatUIUtils.getSubUIInsets( "TitlePane.iconMargins", windowStyle ) ) );
titleLabel.setBorder( new FlatEmptyBorder( FlatUIUtils.getSubUIInsets( "TitlePane.titleMargins", windowStyle ) ) );
iconLabel.setBorder( new FlatEmptyBorder( iconMargins ) );
titleLabel.setBorder( new FlatEmptyBorder( titleMargins ) );
leftPanel.setLayout( new BoxLayout( leftPanel, BoxLayout.LINE_AXIS ) );
leftPanel.setOpaque( false );
@@ -350,10 +369,15 @@ public class FlatTitlePane
restoreButton = createButton( "TitlePane.restoreIcon", "Restore", e -> restore() );
closeButton = createButton( "TitlePane.closeIcon", "Close", e -> close() );
iconifyMaximizeGapComp = createButtonsGapComp();
maximizeCloseGapComp = createButtonsGapComp();
// initially hide buttons that are only supported in frames
iconifyButton.setVisible( false );
maximizeButton.setVisible( false );
restoreButton.setVisible( false );
iconifyMaximizeGapComp.setVisible( false );
maximizeCloseGapComp.setVisible( false );
buttonPanel = new JPanel() {
@Override
@@ -365,12 +389,13 @@ public class FlatTitlePane
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 ) ) );
size = new Dimension( size.width, Math.min( size.height, UIScale.scale( buttonMaximizedHeight + buttonsMargins.top + buttonsMargins.bottom ) ) );
}
return size;
}
};
buttonPanel.setOpaque( false );
buttonPanel.setBorder( FlatUIUtils.nonUIResource( new FlatEmptyBorder( buttonsMargins ) ) );
buttonPanel.setLayout( new BoxLayout( buttonPanel, BoxLayout.LINE_AXIS ) );
if( rootPane.getWindowDecorationStyle() == JRootPane.FRAME ) {
// JRootPane.FRAME works only for frames (and not for dialogs)
@@ -379,8 +404,10 @@ public class FlatTitlePane
// later in frameStateChanged(), which is invoked from addNotify()
buttonPanel.add( iconifyButton );
buttonPanel.add( iconifyMaximizeGapComp );
buttonPanel.add( maximizeButton );
buttonPanel.add( restoreButton );
buttonPanel.add( maximizeCloseGapComp );
}
buttonPanel.add( closeButton );
@@ -397,13 +424,17 @@ public class FlatTitlePane
@Override
public Dimension getMinimumSize() {
// allow the button to shrink if space is rare
return new Dimension( UIScale.scale( buttonMinimumWidth ), super.getMinimumSize().height );
return new Dimension(
Math.min( UIScale.scale( buttonMinimumWidth ), super.getPreferredSize().width ),
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 );
return buttonsFillVertically
? new Dimension( super.getMaximumSize().width, Short.MAX_VALUE )
: super.getMaximumSize();
}
};
button.setFocusable( false );
@@ -414,6 +445,14 @@ public class FlatTitlePane
return button;
}
private JComponent createButtonsGapComp() {
JComponent gapComp = new JPanel();
gapComp.setOpaque( false );
gapComp.setMinimumSize( new Dimension( 0, 0 ) );
gapComp.setPreferredSize( new Dimension( UIScale.scale( buttonsGap ), 0 ) );
return gapComp;
}
protected void activeChanged( boolean active ) {
Color background = clientPropertyColor( rootPane, TITLE_BAR_BACKGROUND, null );
Color foreground = clientPropertyColor( rootPane, TITLE_BAR_FOREGROUND, null );
@@ -435,6 +474,9 @@ public class FlatTitlePane
closeButton.setForeground( foreground );
// this is necessary because hover/pressed colors are derived from background color
// (since FlatWindowAbstractIcon now invokes FlatTitlePane.getBackground()
// to get base color, this is no longer necessary, but keep it for compatibility;
// e.g. for custom window icons)
iconifyButton.setBackground( background );
maximizeButton.setBackground( background );
restoreButton.setBackground( background );
@@ -442,7 +484,7 @@ public class FlatTitlePane
}
protected void frameStateChanged() {
if( window == null || rootPane.getWindowDecorationStyle() != JRootPane.FRAME )
if( window == null || rootPane.getWindowDecorationStyle() == JRootPane.NONE )
return;
updateVisibility();
@@ -494,6 +536,13 @@ public class FlatTitlePane
maximizeButton.setVisible( false );
restoreButton.setVisible( false );
}
boolean iconifyVisible = iconifyButton.isVisible();
boolean maximizeVisible = maximizeButton.isVisible();
boolean restoreVisible = restoreButton.isVisible();
boolean closeVisible = closeButton.isVisible();
iconifyMaximizeGapComp.setVisible( iconifyVisible && (maximizeVisible || restoreVisible || closeVisible) );
maximizeCloseGapComp.setVisible( closeVisible && (maximizeVisible || restoreVisible) );
}
protected void updateIcon() {
@@ -747,12 +796,17 @@ public class FlatTitlePane
if( isFullWindowContent() )
return;
g.setColor( getBackground() );
g.fillRect( 0, 0, getWidth(), getHeight() );
}
@Override
public Color getBackground() {
// not storing value of "TitlePane.unifiedBackground" in class to allow changing at runtime
g.setColor( (UIManager.getBoolean( "TitlePane.unifiedBackground" ) &&
return (UIManager.getBoolean( "TitlePane.unifiedBackground" ) &&
clientPropertyColor( rootPane, TITLE_BAR_BACKGROUND, null ) == null)
? FlatUIUtils.getParentBackground( this )
: getBackground() );
g.fillRect( 0, 0, getWidth(), getHeight() );
: super.getBackground();
}
/**
@@ -860,7 +914,7 @@ public class FlatTitlePane
// screen insets are in physical size, except for Java 15+
// (see https://bugs.openjdk.java.net/browse/JDK-8243925)
// and except for Java 8 on secondary screens where primary screen is scaled
Insets screenInsets = window.getToolkit().getScreenInsets( gc );
Insets screenInsets = FlatUIUtils.getScreenInsets( gc );
// maximized bounds are required in physical size, except for Java 15+
// (see https://bugs.openjdk.java.net/browse/JDK-8231564 and
@@ -1094,6 +1148,7 @@ public class FlatTitlePane
if( !c.isDisplayable() || !c.isVisible() || !contains( c, x, y ) || c == mouseLayer )
return true; // continue checking with next component
// check enabled component that has mouse listeners
if( c.isEnabled() &&
(c.getMouseListeners().length > 0 ||
c.getMouseMotionListeners().length > 0) )
@@ -1417,22 +1472,9 @@ debug*/
private Point dragOffset;
private boolean linuxNativeMove;
private long lastSingleClickWhen;
@Override
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 through for some reason
if( linuxNativeMove && SystemInfo.isLinux && FlatNativeLinuxLibrary.isWMUtilsSupported( window ) ) {
// see comment in mousePressed()
if( lastSingleClickWhen != 0 && (e.getWhen() - lastSingleClickWhen) <= getMultiClickInterval() ) {
lastSingleClickWhen = 0;
maximizeOrRestore();
}
return;
}
if( e.getClickCount() == 2 && SwingUtilities.isLeftMouseButton( e ) ) {
if( SwingUtilities.getDeepestComponentAt( FlatTitlePane.this, e.getX(), e.getY() ) == iconLabel ) {
// double-click on icon closes window
@@ -1463,42 +1505,6 @@ debug*/
dragOffset = SwingUtilities.convertPoint( mouseLayer, e.getPoint(), window );
linuxNativeMove = false;
// on Linux, move or maximize/restore window
if( SystemInfo.isLinux && FlatNativeLinuxLibrary.isWMUtilsSupported( window ) ) {
// The fired Java mouse events, when doing a double-click and the first click
// sends a _NET_WM_MOVERESIZE message, are different for various Linux distributions:
// CentOS 7 (GNOME 3.28.2, X11): PRESSED(clickCount=1) PRESSED(clickCount=2) RELEASED(clickCount=2)
// Ubuntu 20.04 (GNOME 3.36.1, X11): PRESSED(clickCount=1) PRESSED(clickCount=2) RELEASED(clickCount=2)
// Ubuntu 22.04 (GNOME 42.2, Wayland): PRESSED(clickCount=1) RELEASED(clickCount=1) CLICKED(clickCount=1)
// Kubuntu 22.04 (KDE 5.24.4, X11): PRESSED(clickCount=1) PRESSED(clickCount=1) RELEASED(clickCount=1)
// double-click is not always recognized in Java when using _NET_WM_MOVERESIZE message
int clickCount = e.getClickCount();
if( clickCount == 1 && lastSingleClickWhen != 0 && (e.getWhen() - lastSingleClickWhen) <= getMultiClickInterval() )
clickCount = 2;
switch( clickCount ) {
case 1:
// move window via _NET_WM_MOVERESIZE message
e.consume();
linuxNativeMove = FlatNativeLinuxLibrary.moveOrResizeWindow( window, e, FlatNativeLinuxLibrary.MOVE );
lastSingleClickWhen = e.getWhen();
break;
case 2:
// maximize/restore on double-click
// also done here because no mouse clicked event is sent when using _NET_WM_MOVERESIZE message
lastSingleClickWhen = 0;
maximizeOrRestore();
break;
}
}
}
private int getMultiClickInterval() {
Object value = Toolkit.getDefaultToolkit().getDesktopProperty( "awt.multiClickInterval" );
return (value instanceof Integer) ? (Integer) value : 500;
}
@Override public void mouseReleased( MouseEvent e ) {}
@@ -1521,6 +1527,13 @@ debug*/
if( hasNativeCustomDecoration() )
return; // do nothing if having native window border
// on Linux, move window using window manager
if( SystemInfo.isLinux && FlatNativeLinuxLibrary.isWMUtilsSupported( window ) ) {
linuxNativeMove = FlatNativeLinuxLibrary.moveOrResizeWindow( window, e, FlatNativeLinuxLibrary.MOVE );
if( linuxNativeMove )
return;
}
// restore window if it is maximized
if( window instanceof Frame ) {
Frame frame = (Frame) window;

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.ui.FlatUIUtils.FlatPropertyWatcher;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale;
@@ -144,11 +145,13 @@ public class FlatToolBarUI
hoverButtonGroupBackground = UIManager.getColor( "ToolBar.hoverButtonGroupBackground" );
// floatable
oldFloatable = null;
if( !UIManager.getBoolean( "ToolBar.floatable" ) ) {
oldFloatable = toolBar.isFloatable();
toolBar.setFloatable( false );
} else
oldFloatable = null;
FlatPropertyWatcher.runIfNotChanged( toolBar, "floatable", () -> {
oldFloatable = toolBar.isFloatable();
toolBar.setFloatable( false );
} );
}
}
@Override
@@ -158,7 +161,9 @@ public class FlatToolBarUI
hoverButtonGroupBackground = null;
if( oldFloatable != null ) {
toolBar.setFloatable( oldFloatable );
FlatPropertyWatcher.runIfNotChanged( toolBar, "floatable", () -> {
toolBar.setFloatable( oldFloatable );
} );
oldFloatable = null;
}
}

View File

@@ -102,9 +102,11 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault Tree.selectionForeground Color
* @uiDefault Tree.selectionInactiveBackground Color
* @uiDefault Tree.selectionInactiveForeground Color
* @uiDefault Tree.alternateRowColor Color
* @uiDefault Tree.selectionInsets Insets
* @uiDefault Tree.selectionArc int
* @uiDefault Tree.wideSelection boolean
* @uiDefault Tree.wideCellRenderer boolean
* @uiDefault Tree.showCellFocusIndicator boolean
* @uiDefault Tree.showDefaultIcons boolean
*
@@ -141,9 +143,11 @@ public class FlatTreeUI
@Styleable protected Color selectionInactiveBackground;
@Styleable protected Color selectionInactiveForeground;
@Styleable protected Color selectionBorderColor;
/** @since 3.6 */ @Styleable protected Color alternateRowColor;
/** @since 3 */ @Styleable protected Insets selectionInsets;
/** @since 3 */ @Styleable protected int selectionArc;
@Styleable protected boolean wideSelection;
/** @since 3.6 */ @Styleable protected boolean wideCellRenderer;
@Styleable protected boolean showCellFocusIndicator;
/** @since 3 */ protected boolean showDefaultIcons;
@@ -192,9 +196,11 @@ public class FlatTreeUI
selectionInactiveBackground = UIManager.getColor( "Tree.selectionInactiveBackground" );
selectionInactiveForeground = UIManager.getColor( "Tree.selectionInactiveForeground" );
selectionBorderColor = UIManager.getColor( "Tree.selectionBorderColor" );
alternateRowColor = UIManager.getColor( "Tree.alternateRowColor" );
selectionInsets = UIManager.getInsets( "Tree.selectionInsets" );
selectionArc = UIManager.getInt( "Tree.selectionArc" );
wideSelection = UIManager.getBoolean( "Tree.wideSelection" );
wideCellRenderer = UIManager.getBoolean( "Tree.wideCellRenderer" );
showCellFocusIndicator = UIManager.getBoolean( "Tree.showCellFocusIndicator" );
showDefaultIcons = UIManager.getBoolean( "Tree.showDefaultIcons" );
@@ -227,6 +233,7 @@ public class FlatTreeUI
selectionInactiveBackground = null;
selectionInactiveForeground = null;
selectionBorderColor = null;
alternateRowColor = null;
defaultLeafIcon = null;
defaultClosedIcon = null;
@@ -310,6 +317,7 @@ public class FlatTreeUI
if( e.getSource() == tree ) {
switch( e.getPropertyName() ) {
case TREE_WIDE_SELECTION:
case TREE_WIDE_CELL_RENDERER:
case TREE_PAINT_SELECTION:
HiDPIUtils.repaint( tree );
break;
@@ -569,7 +577,27 @@ public class FlatTreeUI
boolean isEditing = (editingComponent != null && editingRow == row);
boolean isSelected = tree.isRowSelected( row );
boolean isDropRow = isDropRow( row );
boolean needsSelectionPainting = (isSelected || isDropRow) && isPaintSelection();
// paint alternating rows
if( alternateRowColor != null && row % 2 != 0 ) {
g.setColor( alternateRowColor );
float arc = UIScale.scale( selectionArc / 2f );
FlatUIUtils.paintSelection( (Graphics2D) g, 0, bounds.y, tree.getWidth(), bounds.height,
UIScale.scale( selectionInsets ), arc, arc, arc, arc, 0 );
}
// update bounds for wide cell renderer
if( isWideSelection() && isWideCellRenderer() ) {
Rectangle wideBounds = new Rectangle( bounds );
if( tree.getComponentOrientation().isLeftToRight() )
wideBounds.width = tree.getWidth() - bounds.x - insets.right;
else {
wideBounds.x = insets.left;
wideBounds.width = bounds.x + bounds.width - insets.left;
}
bounds = wideBounds;
}
// do not paint row if editing
if( isEditing ) {
@@ -579,7 +607,7 @@ public class FlatTreeUI
if( isSelected && isWideSelection() ) {
Color oldColor = g.getColor();
g.setColor( selectionInactiveBackground );
paintWideSelection( g, bounds, row );
paintWideSelection( g, bounds, row, false );
g.setColor( oldColor );
}
return;
@@ -599,7 +627,7 @@ public class FlatTreeUI
// renderer background/foreground
Color oldBackgroundSelectionColor = null;
if( isSelected && !hasFocus && !isDropRow ) {
if( isSelected && !hasFocus ) {
// apply inactive selection background/foreground if tree is not focused
oldBackgroundSelectionColor = setRendererBackgroundSelectionColor( rendererComponent, selectionInactiveBackground );
setRendererForeground( rendererComponent, selectionInactiveForeground );
@@ -626,26 +654,12 @@ public class FlatTreeUI
}
// paint selection background
if( needsSelectionPainting ) {
// set selection color
Color oldColor = g.getColor();
g.setColor( isDropRow
? UIManager.getColor( "Tree.dropCellBackground" )
: (rendererComponent instanceof DefaultTreeCellRenderer
? ((DefaultTreeCellRenderer)rendererComponent).getBackgroundSelectionColor()
: (hasFocus ? selectionBackground : selectionInactiveBackground)) );
if( isSelected && isPaintSelection() ) {
Color selectionColor = rendererComponent instanceof DefaultTreeCellRenderer
? ((DefaultTreeCellRenderer)rendererComponent).getBackgroundSelectionColor()
: (hasFocus ? selectionBackground : selectionInactiveBackground);
if( isWideSelection() ) {
// wide selection
paintWideSelection( g, bounds, row );
} else {
// non-wide selection
paintCellBackground( g, rendererComponent, bounds, row, true );
}
// this is actually not necessary because renderer should always set color
// before painting, but doing anyway to avoid any side effect (in bad renderers)
g.setColor( oldColor );
paintRowSelection( g, selectionColor, rendererComponent, bounds, row, false );
} else {
// paint cell background if DefaultTreeCellRenderer.getBackgroundNonSelectionColor() is set
if( rendererComponent instanceof DefaultTreeCellRenderer ) {
@@ -654,12 +668,19 @@ public class FlatTreeUI
if( bg != null && !bg.equals( defaultCellNonSelectionBackground ) ) {
Color oldColor = g.getColor();
g.setColor( bg );
paintCellBackground( g, rendererComponent, bounds, row, false );
paintCellBackground( g, rendererComponent, bounds, row, false, false );
g.setColor( oldColor );
}
}
}
// paint drop background
// (this needs to be an extra step for rounded selection)
if( isDropRow && isPaintSelection() ) {
paintRowSelection( g, UIManager.getColor( "Tree.dropCellBackground" ),
rendererComponent, bounds, row, true );
}
// paint renderer
rendererPane.paintComponent( g, rendererComponent, tree, bounds.x, bounds.y, bounds.width, bounds.height, true );
@@ -670,6 +691,26 @@ public class FlatTreeUI
((DefaultTreeCellRenderer)rendererComponent).setBorderSelectionColor( oldBorderSelectionColor );
}
private void paintRowSelection( Graphics g, Color color, Component rendererComponent,
Rectangle bounds, int row, boolean paintDropSelection )
{
// set selection color
Color oldColor = g.getColor();
g.setColor( color );
if( isWideSelection() ) {
// wide selection
paintWideSelection( g, bounds, row, paintDropSelection );
} else {
// non-wide selection
paintCellBackground( g, rendererComponent, bounds, row, true, paintDropSelection );
}
// this is actually not necessary because renderer should always set color
// before painting, but doing anyway to avoid any side effect (in bad renderers)
g.setColor( oldColor );
}
private Color setRendererBackgroundSelectionColor( Component rendererComponent, Color color ) {
Color oldColor = null;
@@ -706,11 +747,11 @@ public class FlatTreeUI
return oldColor;
}
private void paintWideSelection( Graphics g, Rectangle bounds, int row ) {
private void paintWideSelection( Graphics g, Rectangle bounds, int row, boolean paintDropSelection ) {
float arcTop, arcBottom;
arcTop = arcBottom = UIScale.scale( selectionArc / 2f );
if( useUnitedRoundedSelection() ) {
if( useUnitedRoundedSelection() && !paintDropSelection ) {
if( row > 0 && tree.isRowSelected( row - 1 ) )
arcTop = 0;
if( row < tree.getRowCount() - 1 && tree.isRowSelected( row + 1 ) )
@@ -722,7 +763,7 @@ public class FlatTreeUI
}
private void paintCellBackground( Graphics g, Component rendererComponent, Rectangle bounds,
int row, boolean paintSelection )
int row, boolean paintSelection, boolean paintDropSelection )
{
int xOffset = 0;
int imageOffset = 0;
@@ -740,7 +781,7 @@ public class FlatTreeUI
float arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight;
arcTopLeft = arcTopRight = arcBottomLeft = arcBottomRight = UIScale.scale( selectionArc / 2f );
if( useUnitedRoundedSelection() ) {
if( useUnitedRoundedSelection() && !paintDropSelection ) {
if( row > 0 && tree.isRowSelected( row - 1 ) ) {
Rectangle r = getPathBounds( tree, tree.getPathForRow( row - 1 ) );
arcTopLeft = Math.min( arcTopLeft, r.x - bounds.x );
@@ -795,6 +836,11 @@ public class FlatTreeUI
return clientPropertyBoolean( tree, TREE_WIDE_SELECTION, wideSelection );
}
/** @since 3.6 */
protected boolean isWideCellRenderer() {
return clientPropertyBoolean( tree, TREE_WIDE_CELL_RENDERER, wideCellRenderer );
}
protected boolean isPaintSelection() {
return clientPropertyBoolean( tree, TREE_PAINT_SELECTION, paintSelection );
}

View File

@@ -26,6 +26,7 @@ import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.Paint;
@@ -34,6 +35,7 @@ import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.SystemColor;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
@@ -42,6 +44,8 @@ import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.IdentityHashMap;
import java.util.WeakHashMap;
import java.util.function.Predicate;
@@ -129,6 +133,20 @@ public class FlatUIUtils
return insets.top == 0 && insets.left == 0 && insets.bottom == 0 && insets.right == 0;
}
/** @since 3.6 */
public static Color stateColor( boolean state, Color stateColor, Color defaultColor ) {
return (state && stateColor != null) ? stateColor : defaultColor;
}
/** @since 3.6 */
public static Color stateColor( boolean state1, Color state1Color,
boolean state2, Color state2Color, Color defaultColor )
{
return (state1 && state1Color != null)
? state1Color
: ((state2 && state2Color != null) ? state2Color : defaultColor);
}
public static Color getUIColor( String key, int defaultColorRGB ) {
Color color = UIManager.getColor( key );
return (color != null) ? color : new Color( defaultColorRGB );
@@ -400,6 +418,17 @@ public class FlatUIUtils
return (fullScreenWindow != null && fullScreenWindow == SwingUtilities.windowForComponent( c ));
}
/** @since 3.6.1 */
public static Insets getScreenInsets( GraphicsConfiguration gc ) {
// on Linux, getScreenInsets() may report wrong values in multi-screen setups
// see https://github.com/apache/netbeans/issues/8532#issuecomment-2909687016
if( SystemInfo.isLinux &&
GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices().length > 1 )
return new Insets( 0, 0, 0, 0 );
return Toolkit.getDefaultToolkit().getScreenInsets( gc );
}
public static Boolean isRoundRect( Component c ) {
return (c instanceof JComponent)
? FlatClientProperties.clientPropertyBooleanStrict(
@@ -447,6 +476,9 @@ public class FlatUIUtils
* Returns the scaled arc diameter of the border for the given component.
*/
public static float getBorderArc( JComponent c ) {
if( c.getBorder() instanceof FlatLineBorder )
return UIScale.scale( ((FlatLineBorder)c.getBorder()).getArc() );
FlatBorder border = getOutsideFlatBorder( c );
return (border != null)
? UIScale.scale( (float) border.getArc( c ) )
@@ -1383,4 +1415,47 @@ debug*/
return delegate.isBorderOpaque();
}
}
//---- class FlatPropertyWatcher ------------------------------------------
/**
* Listener that watches for change of a property from application code.
* This information can be used to decide whether FlatLaf modifies the property.
* If it is modified in application code, FlatLaf no longer changes it.
* <p>
* The listener is added once for a property, but never removed.
* So switching Laf/theme reuses existing listener.
*/
static class FlatPropertyWatcher
implements PropertyChangeListener
{
private boolean changed;
static void runIfNotChanged( JComponent c, String propName, Runnable runnable ) {
FlatPropertyWatcher watcher = getOrInstall( c, propName );
if( watcher.changed )
return;
runnable.run();
watcher.changed = false;
}
private static FlatPropertyWatcher getOrInstall( JComponent c, String propName ) {
for( PropertyChangeListener l : c.getPropertyChangeListeners( propName ) ) {
if( l instanceof FlatPropertyWatcher )
return (FlatPropertyWatcher) l;
}
FlatPropertyWatcher watcher = new FlatPropertyWatcher();
c.addPropertyChangeListener( propName, watcher );
return watcher;
}
//---- interface PropertyChangeListener ----
@Override
public void propertyChange( PropertyChangeEvent e ) {
changed = true;
}
}
}

View File

@@ -41,7 +41,6 @@ import java.util.function.Supplier;
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;
@@ -60,8 +59,6 @@ import com.formdev.flatlaf.util.UIScale;
public abstract class FlatWindowResizer
implements PropertyChangeListener, ComponentListener
{
protected final static Integer WINDOW_RESIZER_LAYER = JLayeredPane.DRAG_LAYER + 1;
protected final JComponent resizeComp;
protected final int borderDragThickness = FlatUIUtils.getUIInt( "RootPane.borderDragThickness", 5 );
@@ -82,12 +79,12 @@ public abstract class FlatWindowResizer
leftDragComp = createDragBorderComponent( NW_RESIZE_CURSOR, W_RESIZE_CURSOR, SW_RESIZE_CURSOR );
rightDragComp = createDragBorderComponent( NE_RESIZE_CURSOR, E_RESIZE_CURSOR, SE_RESIZE_CURSOR );
Container cont = (resizeComp instanceof JRootPane) ? ((JRootPane)resizeComp).getLayeredPane() : resizeComp;
Object cons = (cont instanceof JLayeredPane) ? WINDOW_RESIZER_LAYER : null;
cont.add( topDragComp, cons, 0 );
cont.add( bottomDragComp, cons, 1 );
cont.add( leftDragComp, cons, 2 );
cont.add( rightDragComp, cons, 3 );
// for rootpanes, add after glasspane
int insertIndex = (resizeComp instanceof JRootPane) ? 1 : 0;
resizeComp.add( topDragComp, insertIndex++ );
resizeComp.add( bottomDragComp, insertIndex++ );
resizeComp.add( leftDragComp, insertIndex++ );
resizeComp.add( rightDragComp, insertIndex++ );
resizeComp.addComponentListener( this );
resizeComp.addPropertyChangeListener( "ancestor", this );
@@ -106,11 +103,10 @@ public abstract class FlatWindowResizer
resizeComp.removeComponentListener( this );
resizeComp.removePropertyChangeListener( "ancestor", this );
Container cont = topDragComp.getParent();
cont.remove( topDragComp );
cont.remove( bottomDragComp );
cont.remove( leftDragComp );
cont.remove( rightDragComp );
resizeComp.remove( topDragComp );
resizeComp.remove( bottomDragComp );
resizeComp.remove( leftDragComp );
resizeComp.remove( rightDragComp );
}
public void doLayout() {
@@ -121,7 +117,7 @@ public abstract class FlatWindowResizer
int y = 0;
int width = resizeComp.getWidth();
int height = resizeComp.getHeight();
if( width == 0 || height == 0 )
if( width <= 0 || height <= 0 )
return;
Insets resizeInsets = getResizeInsets();
@@ -192,7 +188,7 @@ public abstract class FlatWindowResizer
protected abstract Dimension getWindowMinimumSize();
protected abstract Dimension getWindowMaximumSize();
protected void beginResizing( int resizeDir ) {}
protected void beginResizing( int resizeDir, MouseEvent e ) {}
protected void endResizing() {}
//---- interface PropertyChangeListener ----
@@ -251,8 +247,7 @@ public abstract class FlatWindowResizer
centerComp = new JPanel();
centerComp.setOpaque( false );
centerComp.setVisible( false );
Container cont = rootPane.getLayeredPane();
cont.add( centerComp, WINDOW_RESIZER_LAYER, 4 );
rootPane.add( centerComp, 5 );
// On Linux, limit window resizing to screen bounds because otherwise
// there would be a strange effect when the mouse is moved over a sidebar
@@ -262,8 +257,7 @@ public abstract class FlatWindowResizer
@Override
public void uninstall() {
Container cont = topDragComp.getParent();
cont.remove( centerComp );
resizeComp.remove( centerComp );
super.uninstall();
}
@@ -337,7 +331,7 @@ public abstract class FlatWindowResizer
protected Rectangle getParentBounds() {
GraphicsConfiguration gc = window.getGraphicsConfiguration();
Rectangle bounds = gc.getBounds();
Insets insets = window.getToolkit().getScreenInsets( gc );
Insets insets = FlatUIUtils.getScreenInsets( gc );
return new Rectangle( bounds.x + insets.left, bounds.y + insets.top,
bounds.width - insets.left - insets.right,
bounds.height - insets.top - insets.bottom );
@@ -376,7 +370,25 @@ public abstract class FlatWindowResizer
}
@Override
protected void beginResizing( int resizeDir ) {
protected void beginResizing( int resizeDir, MouseEvent e ) {
// on Linux, resize window using window manager
if( SystemInfo.isLinux && window != null && FlatNativeLinuxLibrary.isWMUtilsSupported( window ) ) {
int direction = -1;
switch( resizeDir ) {
case N_RESIZE_CURSOR: direction = FlatNativeLinuxLibrary.SIZE_TOP; break;
case S_RESIZE_CURSOR: direction = FlatNativeLinuxLibrary.SIZE_BOTTOM; break;
case W_RESIZE_CURSOR: direction = FlatNativeLinuxLibrary.SIZE_LEFT; break;
case E_RESIZE_CURSOR: direction = FlatNativeLinuxLibrary.SIZE_RIGHT; break;
case NW_RESIZE_CURSOR: direction = FlatNativeLinuxLibrary.SIZE_TOPLEFT; break;
case NE_RESIZE_CURSOR: direction = FlatNativeLinuxLibrary.SIZE_TOPRIGHT; break;
case SW_RESIZE_CURSOR: direction = FlatNativeLinuxLibrary.SIZE_BOTTOMLEFT; break;
case SE_RESIZE_CURSOR: direction = FlatNativeLinuxLibrary.SIZE_BOTTOMRIGHT; break;
}
if( direction >= 0 && FlatNativeLinuxLibrary.moveOrResizeWindow( window, e, direction ) )
return;
}
centerComp.setBounds( 0, 0, resizeComp.getWidth(), resizeComp.getHeight() );
centerComp.setCursor( getPredefinedCursor( resizeDir ) );
centerComp.setVisible( true );
@@ -468,7 +480,7 @@ public abstract class FlatWindowResizer
}
@Override
protected void beginResizing( int resizeDir ) {
protected void beginResizing( int resizeDir, MouseEvent e ) {
int direction = 0;
switch( resizeDir ) {
case N_RESIZE_CURSOR: direction = NORTH; break;
@@ -587,7 +599,7 @@ debug*/
dragRightOffset = windowBounds.x + windowBounds.width - xOnScreen;
dragBottomOffset = windowBounds.y + windowBounds.height - yOnScreen;
beginResizing( resizeDir );
beginResizing( resizeDir, e );
}
@Override

View File

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

View File

@@ -21,7 +21,7 @@ import java.util.function.BiPredicate;
/**
* @author Karl Tauber
*/
class StackUtils
public class StackUtils
{
private static final StackUtils INSTANCE = new StackUtilsImpl();

View File

@@ -224,6 +224,9 @@ public class ColorFunctions
if( functions.length == 1 && functions[0] instanceof Mix ) {
Mix mixFunction = (Mix) functions[0];
return mix( color, mixFunction.color2, mixFunction.weight / 100 );
} else if( functions.length == 1 && functions[0] instanceof Mix2 ) {
Mix2 mixFunction = (Mix2) functions[0];
return mix( mixFunction.color1, color, mixFunction.weight / 100 );
}
// convert RGB to HSL
@@ -386,7 +389,11 @@ public class ColorFunctions
//---- class Mix ----------------------------------------------------------
/**
* Mix two colors.
* Mix two colors using {@link ColorFunctions#mix(Color, Color, float)}.
* First color is passed to {@link #apply(float[])}.
* Second color is {@link #color2}.
* <p>
* Use {@link Mix2} to tint or shade color.
*
* @since 1.6
*/
@@ -420,4 +427,44 @@ public class ColorFunctions
return String.format( "mix(#%08x,%.0f%%)", color2.getRGB(), weight );
}
}
//---- class Mix2 ---------------------------------------------------------
/**
* Mix two colors using {@link ColorFunctions#mix(Color, Color, float)}.
* First color is {@link #color1}.
* Second color is passed to {@link #apply(float[])}.
*
* @since 3.6
*/
public static class Mix2
implements ColorFunction
{
public final Color color1;
public final float weight;
public Mix2( Color color1, float weight ) {
this.color1 = color1;
this.weight = weight;
}
@Override
public void apply( float[] hsla ) {
// convert from HSL to RGB because color mixing is done on RGB values
Color color2 = HSLColor.toRGB( hsla[0], hsla[1], hsla[2], hsla[3] / 100 );
// mix
Color color = mix( color1, color2, weight / 100 );
// convert RGB to HSL
float[] hsl = HSLColor.fromRGB( color );
System.arraycopy( hsl, 0, hsla, 0, hsl.length );
hsla[3] = (color.getAlpha() / 255f) * 100;
}
@Override
public String toString() {
return String.format( "mix2(#%08x,%.0f%%)", color1.getRGB(), weight );
}
}
}

View File

@@ -16,6 +16,7 @@
package com.formdev.flatlaf.util;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.Graphics;
@@ -27,7 +28,9 @@ import java.awt.geom.Rectangle2D;
import java.text.AttributedCharacterIterator;
import javax.swing.JComponent;
import javax.swing.RepaintManager;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.ui.StackUtils;
/**
* @author Karl Tauber
@@ -323,6 +326,19 @@ public class HiDPIUtils
public void drawGlyphVector( GlyphVector g, float x, float y ) {
super.drawGlyphVector( g, x, y + yCorrection );
}
@Override
public void fillRect( int x, int y, int width, int height ) {
// fix hard coded black color in HRuleView.paint() of '<hr noshade>'
if( super.getColor() == Color.black &&
StackUtils.wasInvokedFrom( "javax.swing.text.html.HRuleView", "paint", 4 ) )
{
super.setColor( FlatLaf.isLafDark() ? Color.lightGray : Color.darkGray );
super.fillRect( x, y, width, height );
super.setColor( Color.black );
} else
super.fillRect( x, y, width, height );
}
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -31,6 +31,7 @@ public class SystemInfo
public static final boolean isWindows;
public static final boolean isMacOS;
public static final boolean isLinux;
/** @since 3.6 */ public static final boolean isUnknownOS;
// OS versions
public static final long osVersion;
@@ -59,6 +60,7 @@ public class SystemInfo
public static final boolean isJetBrainsJVM_11_orLater;
// UI toolkits
/** @since 3.6 */ public static final boolean isGNOME;
public static final boolean isKDE;
// other
@@ -75,6 +77,7 @@ public class SystemInfo
isWindows = osName.startsWith( "windows" );
isMacOS = osName.startsWith( "mac" );
isLinux = osName.startsWith( "linux" );
isUnknownOS = !isWindows && !isMacOS && !isLinux;
// OS versions
osVersion = scanVersion( System.getProperty( "os.version" ) );
@@ -104,7 +107,13 @@ public class SystemInfo
isJetBrainsJVM_11_orLater = isJetBrainsJVM && isJava_11_orLater;
// UI toolkits
isKDE = (isLinux && System.getenv( "KDE_FULL_SESSION" ) != null);
String desktop = isLinux ? System.getenv( "XDG_CURRENT_DESKTOP" ) : null;
isGNOME = (isLinux &&
(System.getenv( "GNOME_DESKTOP_SESSION_ID" ) != null ||
(desktop != null && desktop.contains( "GNOME" ))));
isKDE = (isLinux &&
(System.getenv( "KDE_FULL_SESSION" ) != null ||
(desktop != null && desktop.contains( "KDE" ))));
// other
isProjector = Boolean.getBoolean( "org.jetbrains.projector.server.enable" );

View File

@@ -27,7 +27,9 @@ import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.lang.reflect.Method;
import java.util.Arrays;
import javax.swing.LookAndFeel;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.plaf.DimensionUIResource;
import javax.swing.plaf.FontUIResource;
@@ -61,16 +63,28 @@ import com.formdev.flatlaf.FlatSystemProperties;
* or if the default font is changed.
* The user scale factor is computed based on the used font.
* The JRE does not scale anything.
* So we have to invoke {@link #scale(float)} where necessary.
* So we have to invoke {@link #scale(int)} where necessary.
* There is only one user scale factor for all displays.
* The user scale factor may change if the active LaF, "defaultFont" or "Label.font" has changed.
* If system scaling mode is available the user scale factor is usually 1,
* but may be larger on Linux or if the default font is changed.
*
* <h2>Zooming</h2>
*
* Zooming allows appliations to easily zoom their UI, if FlatLaf is active Laf.
* This is done by changing user scale factor and default font.
* There are methods to increase, decrease and reset zoom factor.
* <p>
* Note: Only standard Swing components are zoomed.
* Custom components need to use {@link #scale(int)} to zoom their UI.
*
* @author Karl Tauber
*/
public class UIScale
{
/** @since 3.7 */ public static final String PROP_USER_SCALE_FACTOR = "userScaleFactor";
/** @since 3.7 */ public static final String PROP_ZOOM_FACTOR = "zoomFactor";
private static final boolean DEBUG = false;
private static PropertyChangeSupport changeSupport;
@@ -87,7 +101,7 @@ public class UIScale
changeSupport.removePropertyChangeListener( listener );
}
//---- system scaling (Java 9) --------------------------------------------
//---- system scaling (Java 9+) -------------------------------------------
private static Boolean jreHiDPI;
@@ -135,10 +149,13 @@ public class UIScale
return (isSystemScalingEnabled() && gc != null) ? gc.getDefaultTransform().getScaleX() : 1;
}
//---- user scaling (Java 8) ----------------------------------------------
//---- user scaling (Java 8 / zooming) ------------------------------------
private static float unzoomedScaleFactor = 1;
private static float scaleFactor = 1;
private static boolean initialized;
private static boolean listenerInitialized; // use extra flag for unit tests
private static boolean ignoreFontChange;
private static void initialize() {
if( initialized )
@@ -148,33 +165,43 @@ public class UIScale
if( !isUserScalingEnabled() )
return;
initializeListener();
updateScaleFactor( true );
}
private static void initializeListener() {
if( listenerInitialized )
return;
listenerInitialized = true;
// listener to update scale factor if LaF changed, "defaultFont" or "Label.font" changed
PropertyChangeListener listener = new PropertyChangeListener() {
@Override
public void propertyChange( PropertyChangeEvent e ) {
switch( e.getPropertyName() ) {
case "lookAndFeel":
// it is not necessary (and possible) to remove listener of old LaF defaults
// it is not possible (and necessary) to remove listener of old LaF defaults
// because it is not possible to access the UIDefault object of the old LaF
if( e.getNewValue() instanceof LookAndFeel )
UIManager.getLookAndFeelDefaults().addPropertyChangeListener( this );
updateScaleFactor();
updateScaleFactor( true );
break;
case "defaultFont":
case "Label.font":
updateScaleFactor();
if( !ignoreFontChange )
updateScaleFactor( false );
break;
}
}
};
UIManager.addPropertyChangeListener( listener );
UIManager.getDefaults().addPropertyChangeListener( listener );
UIManager.getLookAndFeelDefaults().addPropertyChangeListener( listener );
updateScaleFactor();
UIManager.addPropertyChangeListener( listener );
}
private static void updateScaleFactor() {
private static void updateScaleFactor( boolean lafChanged ) {
if( !isUserScalingEnabled() )
return;
@@ -185,17 +212,20 @@ public class UIScale
return;
}
// use font size to calculate scale factor (instead of DPI)
// 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)
// get font that is used to calculate scale factor
Font font = null;
if( UIManager.getLookAndFeel() instanceof FlatLaf )
font = UIManager.getFont( "defaultFont" );
if( font == null )
font = UIManager.getFont( "Label.font" );
setUserScaleFactor( computeFontScaleFactor( font ), true );
float fontScaleFactor = computeFontScaleFactor( font );
if( lafChanged && UIManager.getLookAndFeel() instanceof FlatLaf ) {
// FlatLaf has applied zoom factor in FlatLaf.initDefaultFont() to defaultFont,
// so we need to take it into account to get correct user scale factor
fontScaleFactor /= zoomFactor;
}
setUserScaleFactor( fontScaleFactor, true );
}
/**
@@ -204,7 +234,7 @@ public class UIScale
* @since 2
*/
public static float computeFontScaleFactor( Font font ) {
if( SystemInfo.isWindows ) {
if( SystemInfo.isWindows && !inUnitTests ) {
// 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.)
@@ -335,7 +365,7 @@ public class UIScale
}
/**
* Returns the user scale factor.
* Returns the user scale factor (including zoom factor).
*/
public static float getUserScaleFactor() {
initialize();
@@ -345,27 +375,49 @@ public class UIScale
/**
* Sets the user scale factor.
*/
private static void setUserScaleFactor( float scaleFactor, boolean normalize ) {
if( normalize ) {
if( scaleFactor < 1f ) {
scaleFactor = FlatSystemProperties.getBoolean( FlatSystemProperties.UI_SCALE_ALLOW_SCALE_DOWN, false )
? Math.round( scaleFactor * 10f ) / 10f // round small scale factor to 1/10
: 1f;
} else if( scaleFactor > 1f ) // round scale factor to 1/4
scaleFactor = Math.round( scaleFactor * 4f ) / 4f;
}
private static void setUserScaleFactor( float unzoomedScaleFactor, boolean normalize ) {
if( normalize )
unzoomedScaleFactor = normalizeScaleFactor( unzoomedScaleFactor );
// minimum scale factor
scaleFactor = Math.max( scaleFactor, 0.1f );
unzoomedScaleFactor = Math.max( unzoomedScaleFactor, 0.1f );
if( unzoomedScaleFactor == UIScale.unzoomedScaleFactor )
return;
if( DEBUG )
System.out.println( "Unzoomed scale factor " + UIScale.unzoomedScaleFactor + " --> " + unzoomedScaleFactor );
UIScale.unzoomedScaleFactor = unzoomedScaleFactor;
setScaleFactor( unzoomedScaleFactor * zoomFactor );
}
private static void setScaleFactor( float scaleFactor ) {
// round scale factor to 1/100
scaleFactor = Math.round( scaleFactor * 100f ) / 100f;
if( scaleFactor == UIScale.scaleFactor )
return;
float oldScaleFactor = UIScale.scaleFactor;
UIScale.scaleFactor = scaleFactor;
if( DEBUG )
System.out.println( "HiDPI scale factor " + scaleFactor );
System.out.println( "Scale factor " + oldScaleFactor + " --> " + scaleFactor + " (unzoomed " + UIScale.unzoomedScaleFactor + ")" );
if( changeSupport != null )
changeSupport.firePropertyChange( "userScaleFactor", oldScaleFactor, scaleFactor );
changeSupport.firePropertyChange( PROP_USER_SCALE_FACTOR, oldScaleFactor, scaleFactor );
}
private static float normalizeScaleFactor( float scaleFactor ) {
if( scaleFactor < 1f ) {
return FlatSystemProperties.getBoolean( FlatSystemProperties.UI_SCALE_ALLOW_SCALE_DOWN, false )
? Math.round( scaleFactor * 10f ) / 10f // round small scale factor to 1/10
: 1f;
} else if( scaleFactor > 1f ) // round scale factor to 1/4
return Math.round( scaleFactor * 4f ) / 4f;
else
return scaleFactor;
}
/**
@@ -451,4 +503,185 @@ public class UIScale
? new InsetsUIResource( scale( insets.top ), scale( insets.left ), scale( insets.bottom ), scale( insets.right ) )
: new Insets ( scale( insets.top ), scale( insets.left ), scale( insets.bottom ), scale( insets.right ) ));
}
//---- zoom ---------------------------------------------------------------
private static float zoomFactor = 1;
private static float[] supportedZoomFactors = { 1f, 1.1f, 1.25f, 1.5f, 1.75f, 2f };
/**
* Returns the current zoom factor. Default is {@code 1}.
*
* @since 3.7
*/
public static float getZoomFactor() {
return zoomFactor;
}
/**
* Sets the zoom factor.
* Also updates user scale factor and default font (if FlatLaf is active Laf).
* <p>
* UI needs to be updated if zoom factor has changed. E.g.:
* <pre>{@code
* if( UIScale.setZoomFactor( newZoomFactor ) )
* FlatLaf.updateUI();
* }</pre>
*
* @param zoomFactor new zoom factor
* @return {@code true} if zoom factor has changed
* @since 3.7
*/
public static boolean setZoomFactor( float zoomFactor ) {
// minimum zoom factor
zoomFactor = Math.max( zoomFactor, 0.1f );
if( UIScale.zoomFactor == zoomFactor )
return false;
float oldZoomFactor = UIScale.zoomFactor;
UIScale.zoomFactor = zoomFactor;
if( DEBUG )
System.out.println( "Zoom factor " + oldZoomFactor + " --> " + zoomFactor );
setScaleFactor( UIScale.unzoomedScaleFactor * zoomFactor );
if( initialized && UIManager.getLookAndFeel() instanceof FlatLaf ) {
// see also FlatLaf.initDefaultFont()
UIDefaults defaults = UIManager.getLookAndFeelDefaults();
Font font = defaults.getFont( "defaultFont" );
int unzoomedSize = defaults.getInt( "defaultFont.unzoomedSize" );
if( unzoomedSize == 0 ) {
unzoomedSize = font.getSize();
defaults.put( "defaultFont.unzoomedSize", unzoomedSize );
}
// update "defaultFont"
ignoreFontChange = true;
try {
// get application default font before updating Laf default font
Font appFont = UIManager.getFont( "defaultFont" );
// update Laf default font
int newFontSize = Math.max( Math.round( unzoomedSize * zoomFactor ), 1 );
defaults.put( "defaultFont", new FontUIResource( font.deriveFont( (float) newFontSize ) ) );
if( DEBUG )
System.out.println( "Zoom Laf font " + font.getSize() + " --> " + newFontSize + " (unzoomed " + unzoomedSize + ")" );
// check whether application has changed default font
if( appFont != font ) {
// application has own default font --> also zoom it
int newAppFontSize = Math.max( Math.round( (appFont.getSize() / oldZoomFactor) * zoomFactor ), 1 );
UIManager.put( "defaultFont", appFont.deriveFont( (float) newAppFontSize ) );
if( DEBUG )
System.out.println( "Zoom app font " + appFont.getSize() + " --> " + newAppFontSize );
}
} finally {
ignoreFontChange = false;
}
}
if( changeSupport != null )
changeSupport.firePropertyChange( PROP_ZOOM_FACTOR, oldZoomFactor, zoomFactor );
return true;
}
/**
* Increases zoom factor using next greater factor in supported factors array.
* <p>
* UI needs to be updated if zoom factor has changed. E.g.:
* <pre>{@code
* if( UIScale.zoomIn() )
* FlatLaf.updateUI();
* }</pre>
*
* @return {@code true} if zoom factor has changed
* @see #getSupportedZoomFactors()
* @since 3.7
*/
public static boolean zoomIn() {
int i = Arrays.binarySearch( supportedZoomFactors, zoomFactor );
int next = (i >= 0) ? i + 1 : -i - 1;
if( next >= supportedZoomFactors.length )
return false;
return setZoomFactor( supportedZoomFactors[next] );
}
/**
* Decreases zoom factor using next smaller factor in supported factors array.
* <p>
* UI needs to be updated if zoom factor has changed. E.g.:
* <pre>{@code
* if( UIScale.zoomOut() )
* FlatLaf.updateUI();
* }</pre>
*
* @return {@code true} if zoom factor has changed
* @see #getSupportedZoomFactors()
* @since 3.7
*/
public static boolean zoomOut() {
int i = Arrays.binarySearch( supportedZoomFactors, zoomFactor );
int prev = (i >= 0) ? i - 1 : -i - 2;
if( prev < 0 )
return false;
return setZoomFactor( supportedZoomFactors[prev] );
}
/**
* Resets zoom factor to {@code 1}.
* <p>
* UI needs to be updated if zoom factor has changed. E.g.:
* <pre>{@code
* if( UIScale.zoomReset() )
* FlatLaf.updateUI();
* }</pre>
*
* @return {@code true} if zoom factor has changed
* @since 3.7
*/
public static boolean zoomReset() {
return setZoomFactor( 1 );
}
/**
* Returns the supported zoom factors used for {@link #zoomIn()} and {@link #zoomOut()}.
* <p>
* Default is {@code [ 1f, 1.1f, 1.25f, 1.5f, 1.75f, 2f ]}.
*
* @since 3.7
*/
public static float[] getSupportedZoomFactors() {
return supportedZoomFactors.clone();
}
/**
* Sets the supported zoom factors used for {@link #zoomIn()} and {@link #zoomOut()}.
*
* @since 3.7
*/
public static void setSupportedZoomFactors( float[] supportedZoomFactors ) {
UIScale.supportedZoomFactors = supportedZoomFactors.clone();
Arrays.sort( UIScale.supportedZoomFactors );
if( Arrays.binarySearch( UIScale.supportedZoomFactors, 1f ) < 0 )
throw new IllegalArgumentException( "supportedZoomFactors array must contain value 1f" );
}
//---- unit testing -------------------------------------------------------
static boolean inUnitTests;
static void tests_uninitialize() {
initialized = false;
unzoomedScaleFactor = 1;
scaleFactor = 1;
zoomFactor = 1;
}
}

View File

@@ -61,7 +61,6 @@ Component.arrowType = triangle
#---- ProgressBar ----
ProgressBar.foreground = darken(@foreground,10%)
ProgressBar.selectionForeground = @background
#---- RadioButton ----

View File

@@ -34,7 +34,7 @@
# general background and foreground (text color)
@background = #3c3f41
@foreground = #bbb
@foreground = #ddd
@disabledBackground = @background
@disabledForeground = shade(@foreground,25%)
@@ -45,7 +45,7 @@
# selection
@selectionBackground = @accentSelectionBackground
@selectionForeground = contrast(@selectionBackground, @background, @foreground, 25%)
@selectionForeground = contrast(@selectionBackground, shade(@background), tint(@foreground), 25%)
@selectionInactiveBackground = spin(saturate(shade(@selectionBackground,70%),20%),-15)
@selectionInactiveForeground = @foreground
@@ -187,6 +187,8 @@ Component.error.borderColor = desaturate($Component.error.focusedBorderColor,25%
Component.error.focusedBorderColor = #8b3c3c
Component.warning.borderColor = darken(desaturate($Component.warning.focusedBorderColor,20%),10%)
Component.warning.focusedBorderColor = #ac7920
Component.success.borderColor = desaturate($Component.success.focusedBorderColor,25%)
Component.success.focusedBorderColor = #648b3c
Component.custom.borderColor = desaturate(#f00,50%,relative derived noAutoInverse)
@@ -262,7 +264,7 @@ PopupMenu.hoverScrollArrowBackground = lighten(@background,5%)
ProgressBar.background = lighten(@background,8%)
ProgressBar.foreground = @accentSliderColor
ProgressBar.selectionBackground = @foreground
ProgressBar.selectionForeground = contrast($ProgressBar.foreground, @background, @foreground, 25%)
ProgressBar.selectionForeground = contrast($ProgressBar.foreground, shade(@background), tint(@foreground), 25%)
#---- RootPane ----
@@ -274,7 +276,7 @@ RootPane.inactiveBorderColor = lighten(@background,5%,derived)
#---- ScrollBar ----
ScrollBar.track = lighten(@background,1%,derived noAutoInverse)
ScrollBar.thumb = lighten($ScrollBar.track,10%,derived noAutoInverse)
ScrollBar.thumb = lighten($ScrollBar.track,15%,derived noAutoInverse)
ScrollBar.hoverTrackColor = lighten($ScrollBar.track,4%,derived noAutoInverse)
ScrollBar.hoverThumbColor = lighten($ScrollBar.thumb,10%,derived noAutoInverse)
ScrollBar.pressedThumbColor = lighten($ScrollBar.thumb,15%,derived noAutoInverse)
@@ -284,7 +286,7 @@ ScrollBar.pressedButtonBackground = lighten(@background,10%,derived noAutoInvers
#---- Separator ----
Separator.foreground = tint(@background,10%)
Separator.foreground = tint(@background,15%)
#---- Slider ----
@@ -341,8 +343,13 @@ TableHeader.bottomSeparatorColor = $TableHeader.separatorColor
#---- TitlePane ----
TitlePane.embeddedForeground = darken($TitlePane.foreground,15%)
TitlePane.buttonHoverBackground = lighten($TitlePane.background,15%,derived)
TitlePane.buttonPressedBackground = lighten($TitlePane.background,10%,derived)
TitlePane.buttonHoverBackground = lighten($TitlePane.background,10%,derived)
TitlePane.buttonPressedBackground = lighten($TitlePane.background,6%,derived)
# Linux
[linux]TitlePane.buttonBackground = lighten($TitlePane.background,5%,derived)
[linux]TitlePane.buttonHoverBackground = lighten($TitlePane.background,10%,derived)
[linux]TitlePane.buttonPressedBackground = lighten($TitlePane.background,15%,derived)
#---- ToggleButton ----

View File

@@ -50,6 +50,9 @@ mini.font = -3
#defaultFont = ...
# font weights
# fallback for unknown platform
light.font = +0
semibold.font = +0
# Windows
[win]light.font = "Segoe UI Light"
[win]semibold.font = "Segoe UI Semibold"
@@ -59,15 +62,12 @@ mini.font = -3
# Linux
[linux]light.font = "Lato Light", "Ubuntu Light", "Cantarell Light"
[linux]semibold.font = "Lato Semibold", "Ubuntu Medium", "Montserrat SemiBold"
# fallback for unknown platform
light.font = +0
semibold.font = +0
# monospaced
monospaced.font = Monospaced
[win]monospaced.font = Monospaced
[mac]monospaced.font = Menlo, Monospaced
[linux]monospaced.font = "Liberation Mono", "Ubuntu Mono", Monospaced
monospaced.font = Monospaced
# styles
[style].h00 = font: $h00.font
@@ -564,8 +564,8 @@ RadioButtonMenuItem.background = @menuBackground
#---- RootPane ----
RootPane.border = com.formdev.flatlaf.ui.FlatRootPaneUI$FlatWindowBorder
RootPane.borderDragThickness = 5
RootPane.cornerDragWidth = 16
RootPane.borderDragThickness = 6
RootPane.cornerDragWidth = 32
RootPane.honorFrameMinimumSizeOnResize = false
RootPane.honorDialogMinimumSizeOnResize = true
@@ -574,12 +574,12 @@ RootPane.honorDialogMinimumSizeOnResize = true
ScrollBar.width = 10
ScrollBar.minimumButtonSize = 12,12
ScrollBar.minimumThumbSize = 10,10
ScrollBar.minimumThumbSize = 18,18
ScrollBar.maximumThumbSize = 100000,100000
ScrollBar.trackInsets = 0,0,0,0
ScrollBar.thumbInsets = 0,0,0,0
ScrollBar.thumbInsets = 2,2,2,2
ScrollBar.trackArc = 0
ScrollBar.thumbArc = 0
ScrollBar.thumbArc = 999
ScrollBar.hoverThumbWithTrack = false
ScrollBar.pressedThumbWithTrack = false
ScrollBar.showButtons = false
@@ -588,15 +588,8 @@ ScrollBar.buttonArrowColor = @buttonArrowColor
ScrollBar.buttonDisabledArrowColor = @buttonDisabledArrowColor
ScrollBar.allowsAbsolutePositioning = true
[mac]ScrollBar.minimumThumbSize = 18,18
[mac]ScrollBar.thumbInsets = 2,2,2,2
[mac]ScrollBar.thumbArc = 999
[mac]ScrollBar.hoverThumbWithTrack = true
[linux]ScrollBar.minimumThumbSize = 18,18
[linux]ScrollBar.thumbInsets = 2,2,2,2
[linux]ScrollBar.thumbArc = 999
#---- ScrollPane ----
@@ -703,7 +696,7 @@ TabbedPane.hiddenTabsNavigation = moreTabsButton
TabbedPane.tabAreaAlignment = leading
# allowed values: leading, trailing or center
TabbedPane.tabAlignment = center
# allowed values: preferred, equal or compact
# allowed values: preferred, equal, compact or iconsOnly
TabbedPane.tabWidthMode = preferred
# allowed values: none, auto, left or right
TabbedPane.tabRotation = none
@@ -823,9 +816,14 @@ TitlePane.iconMargins = 3,8,3,8
TitlePane.titleMargins = 3,0,3,0
TitlePane.titleMinimumWidth = 60
TitlePane.buttonSize = 44,30
TitlePane.buttonInsets = 0,0,0,0
TitlePane.buttonArc = 0
TitlePane.buttonMinimumWidth = 30
TitlePane.buttonMaximizedHeight = 22
TitlePane.buttonSymbolHeight = 10
TitlePane.buttonsGap = 0
TitlePane.buttonsMargins = 0,0,0,0
TitlePane.buttonsFillVertically = true
TitlePane.centerTitle = false
TitlePane.centerTitleIfMenuBarEmbedded = true
TitlePane.showIconBesideTitle = false
@@ -856,6 +854,27 @@ TitlePane.small.iconifyIcon = com.formdev.flatlaf.icons.FlatWindowIconifyIcon, s
TitlePane.small.maximizeIcon = com.formdev.flatlaf.icons.FlatWindowMaximizeIcon, small
TitlePane.small.restoreIcon = com.formdev.flatlaf.icons.FlatWindowRestoreIcon, small
# Linux
[linux]TitlePane.buttonSize = 26,26
[linux]TitlePane.buttonInsets = 2,2,2,2
[linux]TitlePane.buttonArc = 999
[linux]TitlePane.buttonMaximizedHeight = -1
[linux]TitlePane.buttonSymbolHeight = 8
[linux]TitlePane.buttonsGap = 8
[linux]TitlePane.buttonsMargins = 4,4,4,4
[linux]TitlePane.buttonsFillVertically = false
[linux]TitlePane.small.buttonSize = 20,20
[linux]TitlePane.small.buttonInsets = 1,1,1,1
[linux]TitlePane.small.buttonSymbolHeight = 6
[linux]TitlePane.small.buttonsGap = 4
[linux]TitlePane.small.buttonsMargins = 2,2,2,2
[linux]TitlePane.closeBackground = $?TitlePane.buttonBackground
[linux]TitlePane.closeInactiveBackground = $?TitlePane.buttonInactiveBackground
[linux]TitlePane.closeHoverBackground = $?TitlePane.buttonHoverBackground
[linux]TitlePane.closePressedBackground = $?TitlePane.buttonPressedBackground
[linux]TitlePane.closeHoverForeground = $?TitlePane.foreground
[linux]TitlePane.closePressedForeground = $?TitlePane.foreground
#---- ToggleButton ----
@@ -934,6 +953,7 @@ Tree.rendererMargins = 1,2,1,2
Tree.selectionInsets = 0,0,0,0
Tree.selectionArc = 0
Tree.wideSelection = true
Tree.wideCellRenderer = false
Tree.repaintWholeRow = true
Tree.paintLines = false
Tree.showCellFocusIndicator = false
@@ -957,6 +977,24 @@ Tree.icon.openColor = @icon
#---- Styles ------------------------------------------------------------------
#---- scale icons ----
@largeScale = 1.125
@mediumScale = 0.875
@smallScale = 0.8125
@miniScale = 0.75
[style]CheckBox.large = icon.scale: @largeScale; iconTextGap: 5
[style]CheckBox.medium = icon.scale: @mediumScale
[style]CheckBox.small = icon.scale: @smallScale; iconTextGap: 3
[style]CheckBox.mini = icon.scale: @miniScale; iconTextGap: 3
[style]RadioButton.large = icon.scale: @largeScale; iconTextGap: 5
[style]RadioButton.medium = icon.scale: @mediumScale
[style]RadioButton.small = icon.scale: @smallScale; iconTextGap: 3
[style]RadioButton.mini = icon.scale: @miniScale; iconTextGap: 3
#---- inTextField ----
# for leading/trailing components in text fields

View File

@@ -36,7 +36,7 @@
@background = #f2f2f2
@foreground = #000
@disabledBackground = @background
@disabledForeground = tint(@foreground,55%)
@disabledForeground = tint(@foreground,50%)
# component background
@buttonBackground = lighten(@background,5%)
@@ -194,6 +194,8 @@ Component.error.borderColor = lighten(desaturate($Component.error.focusedBorderC
Component.error.focusedBorderColor = #e53e4d
Component.warning.borderColor = lighten(saturate($Component.warning.focusedBorderColor,25%),20%)
Component.warning.focusedBorderColor = #e2a53a
Component.success.borderColor = lighten(desaturate($Component.success.focusedBorderColor,20%),25%)
Component.success.focusedBorderColor = #14dc92
Component.custom.borderColor = lighten(desaturate(#f00,20%,derived noAutoInverse),25%,derived noAutoInverse)
@@ -280,7 +282,7 @@ RootPane.inactiveBorderColor = darken(@background,30%,derived)
#---- ScrollBar ----
ScrollBar.track = lighten(@background,1%,derived noAutoInverse)
ScrollBar.thumb = darken($ScrollBar.track,10%,derived noAutoInverse)
ScrollBar.thumb = darken($ScrollBar.track,15%,derived noAutoInverse)
ScrollBar.hoverTrackColor = darken($ScrollBar.track,3%,derived noAutoInverse)
ScrollBar.hoverThumbColor = darken($ScrollBar.thumb,10%,derived noAutoInverse)
ScrollBar.pressedThumbColor = darken($ScrollBar.thumb,20%,derived noAutoInverse)
@@ -347,8 +349,13 @@ TableHeader.bottomSeparatorColor = $TableHeader.separatorColor
#---- TitlePane ----
TitlePane.embeddedForeground = lighten($TitlePane.foreground,35%)
TitlePane.buttonHoverBackground = darken($TitlePane.background,10%,derived)
TitlePane.buttonPressedBackground = darken($TitlePane.background,8%,derived)
TitlePane.buttonHoverBackground = darken($TitlePane.background,5%,derived)
TitlePane.buttonPressedBackground = darken($TitlePane.background,3%,derived)
# Linux
[linux]TitlePane.buttonBackground = darken($TitlePane.background,5%,derived)
[linux]TitlePane.buttonHoverBackground = darken($TitlePane.background,10%,derived)
[linux]TitlePane.buttonPressedBackground = darken($TitlePane.background,15%,derived)
#---- ToggleButton ----

View File

@@ -21,27 +21,41 @@
# - https://www.formdev.com/flatlaf/properties-files/
# - https://www.formdev.com/flatlaf/how-to-customize/
#
# Properties in this file are applied in following order:
# 1. properties without '{...}' and without '[...]' prefix
# 2. properties specified in .theme.json file
# 3. properties starting with '{*}'
# 4. properties starting with '{*-light}' or '{*-dark}'
# 5. properties starting with '{author-<author>}',
# where '<author>' is replaced with "author" value from .theme.json file
# 6. properties starting with '{<name>---<author>}',
# where '<name>' and '<author>' are replaced with "name" and "author" values from .theme.json file
# 7. properties starting with '{<name>}',
# where '<name>' is replaced with "name" value from .theme.json file
# 8. properties with '[...]' prefix
#
#---- system colors ----
# fix (most) system colors because they are usually not set in .json files
desktop = lazy(TextField.background)
activeCaptionText = lazy(TextField.foreground)
inactiveCaptionText = lazy(TextField.foreground)
window = lazy(Panel.background)
windowBorder = lazy(TextField.foreground)
windowText = lazy(TextField.foreground)
menu = lazy(Menu.background)
menuText = lazy(Menu.foreground)
text = lazy(TextField.background)
textText = lazy(TextField.foreground)
textHighlight = lazy(TextField.selectionBackground)
textHighlightText = lazy(TextField.selectionForeground)
textInactiveText = lazy(TextField.inactiveForeground)
control = lazy(Panel.background)
controlText = lazy(TextField.foreground)
info = lazy(ToolTip.background)
infoText = lazy(ToolTip.foreground)
desktop = $TextField.background
activeCaptionText = $TextField.foreground
inactiveCaptionText = $TextField.foreground
window = $Panel.background
windowBorder = $TextField.foreground
windowText = $TextField.foreground
menu = $Menu.background
menuText = $Menu.foreground
text = $TextField.background
textText = $TextField.foreground
textHighlight = $TextField.selectionBackground
textHighlightText = $TextField.selectionForeground
textInactiveText = $TextField.inactiveForeground
control = $Panel.background
controlText = $TextField.foreground
info = $ToolTip.background
infoText = $ToolTip.foreground
#---- variables ----
@@ -49,26 +63,13 @@ infoText = lazy(ToolTip.foreground)
# make sure that accent color (set via FlatLaf.setSystemColorGetter()) is ignored
@accentColor = null
# use same accent color for checkmark, slider, tab underline, etc.
@accentBase2Color = @accentBaseColor
# use fixed color because it is used in borders
@cellFocusColor = #222
#---- Button ----
Button.startBackground = $Button.background
Button.endBackground = $Button.background
Button.startBorderColor = $Button.borderColor
Button.endBorderColor = $Button.borderColor
Button.default.startBackground = $Button.default.background
Button.default.endBackground = $Button.default.background
Button.default.startBorderColor = $Button.default.borderColor
Button.default.endBorderColor = $Button.default.borderColor
Button.hoverBorderColor = null
Button.default.hoverBorderColor = null
#---- CheckBoxMenuItem ----
# colors from intellij/checkmark.svg and darcula/checkmark.svg
@@ -76,34 +77,33 @@ Button.default.hoverBorderColor = null
[dark]CheckBoxMenuItem.icon.checkmarkColor=#fff9
#---- Component ----
Component.accentColor = lazy(ProgressBar.foreground)
#---- HelpButton ----
HelpButton.hoverBorderColor = null
#---- Slider ----
Slider.focusedColor = fade($Component.focusColor,40%,derived)
# this "reverses" definition in FlatLightLaf/FlatDarkLaf.properties
Slider.trackValueColor = $Slider.thumbColor
Slider.thumbColor = @accentSliderColor
#---- Spinner ----
# Spinner arrow button always has same colors as ComboBox arrow button
Spinner.buttonBackground = $ComboBox.buttonEditableBackground
Spinner.buttonArrowColor = $ComboBox.buttonArrowColor
Spinner.buttonDisabledArrowColor = $ComboBox.buttonDisabledArrowColor
#---- TabbedPane ----
# colors from JBUI.CurrentTheme.DefaultTabs.inactiveUnderlineColor()
[light]TabbedPane.inactiveUnderlineColor = #9ca7b8
[dark]TabbedPane.inactiveUnderlineColor = #747a80
{*-light}TabbedPane.inactiveUnderlineColor = #9ca7b8
{*-dark}TabbedPane.inactiveUnderlineColor = #747a80
#---- ToggleButton ----
ToggleButton.startBackground = $ToggleButton.background
ToggleButton.endBackground = $ToggleButton.background
[dark]ToggleButton.selectedBackground = lighten($ToggleButton.background,15%,derived)
[dark]ToggleButton.disabledSelectedBackground = lighten($ToggleButton.background,5%,derived)
{*}ToggleButton.background = $Button.background
{*-dark}ToggleButton.selectedBackground = lighten($ToggleButton.background,15%,derived)
{*-dark}ToggleButton.disabledSelectedBackground = lighten($ToggleButton.background,5%,derived)
#---- theme specific ----
@@ -112,357 +112,434 @@ ToggleButton.endBackground = $ToggleButton.background
@ijMenuCheckBackgroundL20 = lighten(@selectionBackground,20%,derived noAutoInverse)
@ijMenuCheckBackgroundD10 = darken(@selectionBackground,10%,derived noAutoInverse)
@ijTextBackgroundL3 = lighten(Panel.background,3%,lazy)
@ijTextBackgroundL4 = lighten(Panel.background,4%,lazy)
@ijSeparatorLight = shade(@background,15%)
@ijSeparatorDark = tint(@background,25%)
[Arc_Theme]CheckBoxMenuItem.foreground = lazy(MenuItem.foreground)
[Arc_Theme]PopupMenu.foreground = lazy(MenuItem.foreground)
[Arc_Theme]RadioButtonMenuItem.foreground = lazy(MenuItem.foreground)
[Arc_Theme]ProgressBar.selectionBackground = #000
[Arc_Theme]ProgressBar.selectionForeground = #fff
[Arc_Theme]List.selectionInactiveForeground = #fff
[Arc_Theme]Table.selectionInactiveForeground = #fff
[Arc_Theme]Tree.selectionInactiveForeground = #fff
@ijTextBackgroundL3 = lighten($Panel.background,3%)
@ijTextBackgroundL4 = lighten($Panel.background,4%)
[Arc_Theme_-_Orange]CheckBoxMenuItem.foreground = lazy(MenuItem.foreground)
[Arc_Theme_-_Orange]PopupMenu.foreground = lazy(MenuItem.foreground)
[Arc_Theme_-_Orange]RadioButtonMenuItem.foreground = lazy(MenuItem.foreground)
[Arc_Theme_-_Orange]ProgressBar.selectionBackground = #000
[Arc_Theme_-_Orange]ProgressBar.selectionForeground = #fff
[Arc_Theme_-_Orange]List.selectionInactiveForeground = #fff
[Arc_Theme_-_Orange]Table.selectionInactiveForeground = #fff
[Arc_Theme_-_Orange]Tree.selectionInactiveForeground = #fff
{Arc_Theme}@selectionInactiveForeground = @selectionForeground
{Arc_Theme}CheckBoxMenuItem.foreground = $MenuItem.foreground
{Arc_Theme}PopupMenu.foreground = $MenuItem.foreground
{Arc_Theme}RadioButtonMenuItem.foreground = $MenuItem.foreground
[Arc_Theme_Dark]CheckBoxMenuItem.foreground = lazy(MenuItem.foreground)
[Arc_Theme_Dark]PopupMenu.foreground = lazy(MenuItem.foreground)
[Arc_Theme_Dark]RadioButtonMenuItem.foreground = lazy(MenuItem.foreground)
[Arc_Theme_Dark]ProgressBar.selectionBackground = #ddd
[Arc_Theme_Dark]ProgressBar.selectionForeground = #ddd
[Arc_Theme_Dark]ToolBar.separatorColor = lazy(Separator.foreground)
{Arc_Theme_-_Orange}@selectionInactiveForeground = @selectionForeground
{Arc_Theme_-_Orange}CheckBoxMenuItem.foreground = $MenuItem.foreground
{Arc_Theme_-_Orange}PopupMenu.foreground = $MenuItem.foreground
{Arc_Theme_-_Orange}RadioButtonMenuItem.foreground = $MenuItem.foreground
[Arc_Theme_Dark_-_Orange]CheckBoxMenuItem.foreground = lazy(MenuItem.foreground)
[Arc_Theme_Dark_-_Orange]PopupMenu.foreground = lazy(MenuItem.foreground)
[Arc_Theme_Dark_-_Orange]RadioButtonMenuItem.foreground = lazy(MenuItem.foreground)
[Arc_Theme_Dark_-_Orange]ProgressBar.selectionBackground = #ddd
[Arc_Theme_Dark_-_Orange]ProgressBar.selectionForeground = #fff
[Arc_Theme_Dark_-_Orange]ToolBar.separatorColor = lazy(Separator.foreground)
{Arc_Theme_Dark}CheckBoxMenuItem.foreground = $MenuItem.foreground
{Arc_Theme_Dark}PopupMenu.foreground = $MenuItem.foreground
{Arc_Theme_Dark}RadioButtonMenuItem.foreground = $MenuItem.foreground
{Arc_Theme_Dark}ToolBar.background = @background
[Carbon]Table.selectionBackground = lazy(List.selectionBackground)
[Carbon]Table.selectionInactiveForeground = lazy(List.selectionInactiveForeground)
[Carbon]TextField.background = @ijTextBackgroundL4
{Arc_Theme_Dark_-_Orange}CheckBoxMenuItem.foreground = $MenuItem.foreground
{Arc_Theme_Dark_-_Orange}PopupMenu.foreground = $MenuItem.foreground
{Arc_Theme_Dark_-_Orange}ProgressBar.selectionForeground = #fff
{Arc_Theme_Dark_-_Orange}RadioButtonMenuItem.foreground = $MenuItem.foreground
{Arc_Theme_Dark_-_Orange}ToolBar.background = @background
[Cobalt_2]Component.accentColor = lazy(Component.focusColor)
[Cobalt_2]CheckBox.icon.background = #002946
[Cobalt_2]CheckBox.icon.checkmarkColor = #002946
[Cobalt_2]MenuItem.checkBackground = @ijMenuCheckBackgroundL10
[Cobalt_2]MenuItem.underlineSelectionCheckBackground = @ijMenuCheckBackgroundL10
[Cobalt_2]ComboBox.background = @ijTextBackgroundL3
[Cobalt_2]ComboBox.buttonBackground = @ijTextBackgroundL3
[Cobalt_2]TextField.background = @ijTextBackgroundL3
[Cobalt_2]Table.background = lazy(List.background)
[Cobalt_2]Tree.background = lazy(List.background)
{Carbon}Separator.foreground = @ijSeparatorDark
{Carbon}ToolBar.separatorColor = $Separator.foreground
{Carbon}Table.selectionBackground = $List.selectionBackground
{Carbon}TextField.background = @ijTextBackgroundL4
[Cyan_light]MenuItem.checkBackground = @ijMenuCheckBackgroundL20
[Cyan_light]MenuItem.underlineSelectionCheckBackground = @ijMenuCheckBackgroundL20
{Cobalt_2}@accentBaseColor = $ColorPalette.hue3
{Cobalt_2}CheckBox.icon.background = @background
{Cobalt_2}MenuItem.checkBackground = @ijMenuCheckBackgroundL10
{Cobalt_2}MenuItem.underlineSelectionCheckBackground = @ijMenuCheckBackgroundL10
{Cobalt_2}ComboBox.background = @ijTextBackgroundL3
{Cobalt_2}Slider.thumbColor = $ProgressBar.foreground
{Cobalt_2}Slider.disabledTrackColor = $Separator.foreground
{Cobalt_2}TextField.background = @ijTextBackgroundL3
{Cobalt_2}Table.background = $List.background
{Cobalt_2}Tree.background = $List.background
[Dark_Flat_Theme]*.inactiveForeground = #808080
[Dark_Flat_Theme]Component.accentColor = lazy(List.selectionBackground)
[Dark_Flat_Theme]TableHeader.background = #3B3B3B
[Dark_Flat_Theme]TextPane.foreground = lazy(TextField.foreground)
[Dark_Flat_Theme]CheckBoxMenuItem.selectionForeground = lazy(MenuItem.selectionForeground)
[Dark_Flat_Theme]List.selectionForeground = lazy(Tree.selectionForeground)
[Dark_Flat_Theme]RadioButtonMenuItem.selectionForeground = lazy(MenuItem.selectionForeground)
[Dark_Flat_Theme]Separator.foreground = lazy(ToolBar.separatorColor)
{Cyan_light}@disabledForeground = tint(@foreground,30%)
{Cyan_light}*.disabledText = @disabledForeground
{Cyan_light}*.disabledForeground = @disabledForeground
{Cyan_light}*.inactiveForeground = @disabledForeground
{Cyan_light}Button.background = @buttonBackground
{Cyan_light}MenuItem.checkBackground = @ijMenuCheckBackgroundL20
{Cyan_light}MenuItem.underlineSelectionCheckBackground = @ijMenuCheckBackgroundL20
[Dark_purple]Slider.focusedColor = fade($Component.focusColor,70%,derived)
{Dark_Flat_Theme}@accentBaseColor = $TabbedPane.underlineColor
{Dark_Flat_Theme}@disabledForeground = #808080
{Dark_Flat_Theme}*.disabledText = @disabledForeground
{Dark_Flat_Theme}*.disabledForeground = @disabledForeground
{Dark_Flat_Theme}*.inactiveForeground = @disabledForeground
{Dark_Flat_Theme}TableHeader.background = #3B3B3B
{Dark_Flat_Theme}CheckBoxMenuItem.selectionForeground = $MenuItem.selectionForeground
{Dark_Flat_Theme}ComboBox.background = $TextField.background
{Dark_Flat_Theme}ComboBox.buttonBackground = $ComboBox.background
{Dark_Flat_Theme}List.selectionForeground = $Tree.selectionForeground
{Dark_Flat_Theme}ProgressBar.selectionForeground = @foreground
{Dark_Flat_Theme}RadioButtonMenuItem.selectionForeground = $MenuItem.selectionForeground
{Dark_Flat_Theme}Separator.foreground = @ijSeparatorDark
{Dark_Flat_Theme}Slider.trackColor = $ProgressBar.background
{Dark_Flat_Theme}Slider.thumbColor = fade($ProgressBar.foreground,100%)
{Dark_Flat_Theme}TextPane.foreground = $TextField.foreground
{Dark_Flat_Theme}ToggleButton.foreground = $Button.foreground
[Dracula---Zihan_Ma]Component.accentColor = lazy(Component.focusColor)
[Dracula---Zihan_Ma]ComboBox.selectionBackground = lazy(List.selectionBackground)
[Dracula---Zihan_Ma]ProgressBar.selectionBackground = #fff
[Dracula---Zihan_Ma]ProgressBar.selectionForeground = #fff
{Dracula---Zihan_Ma}CheckBox.icon.background = @background
{Dracula---Zihan_Ma}ComboBox.selectionBackground = $List.selectionBackground
{Dracula---Zihan_Ma}ProgressBar.selectionBackground = #fff
{Dracula---Zihan_Ma}ProgressBar.selectionForeground = #fff
{Dracula---Zihan_Ma}Slider.trackColor = $?ColorPalette.selectionBackground
{Dracula---Zihan_Ma}ToggleButton.foreground = $Button.foreground
[Gradianto_Dark_Fuchsia]*.selectionBackground = #8452a7
[Gradianto_Dark_Fuchsia]*.selectionInactiveBackground = #562C6A
[Gradianto_Dark_Fuchsia]MenuItem.checkBackground = @ijMenuCheckBackgroundL10
[Gradianto_Dark_Fuchsia]MenuItem.underlineSelectionCheckBackground = @ijMenuCheckBackgroundL10
[Gradianto_Dark_Fuchsia]TextField.background = @ijTextBackgroundL4
[Gradianto_Dark_Fuchsia]Tree.background = lazy(List.background)
[Gradianto_Dark_Fuchsia]Separator.foreground = lazy(ScrollBar.track)
[Gradianto_Dark_Fuchsia]ToolBar.separatorColor = lazy(ScrollBar.track)
[Gradianto_Dark_Fuchsia]ProgressBar.background = lazy(ScrollBar.track)
[Gradianto_Dark_Fuchsia]Slider.trackColor = lazy(ScrollBar.track)
{Gradianto_Dark_Fuchsia}MenuItem.checkBackground = @ijMenuCheckBackgroundL10
{Gradianto_Dark_Fuchsia}MenuItem.underlineSelectionCheckBackground = @ijMenuCheckBackgroundL10
{Gradianto_Dark_Fuchsia}TextField.background = @ijTextBackgroundL4
{Gradianto_Dark_Fuchsia}Tree.background = $List.background
{Gradianto_Dark_Fuchsia}Tree.selectionInactiveBackground = $List.selectionInactiveBackground
{Gradianto_Dark_Fuchsia}Separator.foreground = @ijSeparatorDark
{Gradianto_Dark_Fuchsia}ToolBar.separatorColor = $Separator.foreground
{Gradianto_Dark_Fuchsia}ProgressBar.background = $ScrollBar.track
{Gradianto_Dark_Fuchsia}Slider.trackColor = $ScrollBar.track
[Gradianto_Deep_Ocean]TextField.background = @ijTextBackgroundL3
[Gradianto_Deep_Ocean]Tree.background = lazy(List.background)
{Gradianto_Deep_Ocean}Separator.foreground = @ijSeparatorDark
{Gradianto_Deep_Ocean}ToolBar.separatorColor = $Separator.foreground
{Gradianto_Deep_Ocean}TextField.background = @ijTextBackgroundL3
{Gradianto_Deep_Ocean}Tree.background = $List.background
[Gradianto_Midnight_Blue]ScrollBar.thumb = #533B6B
[Gradianto_Midnight_Blue]Table.selectionForeground = lazy(List.selectionForeground)
[Gradianto_Midnight_Blue]TextField.background = @ijTextBackgroundL4
[Gradianto_Midnight_Blue]Tree.background = lazy(List.background)
{Gradianto_Midnight_Blue}ScrollBar.thumb = #533B6B
{Gradianto_Midnight_Blue}Separator.foreground = @ijSeparatorDark
{Gradianto_Midnight_Blue}ToolBar.separatorColor = $Separator.foreground
{Gradianto_Midnight_Blue}Table.selectionForeground = $List.selectionForeground
{Gradianto_Midnight_Blue}TextField.background = @ijTextBackgroundL4
{Gradianto_Midnight_Blue}Tree.background = $List.background
[Gradianto_Nature_Green]Table.selectionForeground = lazy(List.selectionForeground)
[Gradianto_Nature_Green]TextField.background = @ijTextBackgroundL4
{Gradianto_Nature_Green}Separator.foreground = @ijSeparatorDark
{Gradianto_Nature_Green}ToolBar.separatorColor = $Separator.foreground
{Gradianto_Nature_Green}Table.selectionForeground = $List.selectionForeground
{Gradianto_Nature_Green}TextField.background = @ijTextBackgroundL4
[Gray]Separator.foreground = lazy(Slider.trackColor)
[Gray]ToolBar.separatorColor = lazy(Slider.trackColor)
{Gray}@disabledForeground = tint(@foreground,40%)
{Gray}*.disabledText = @disabledForeground
{Gray}*.disabledForeground = @disabledForeground
{Gray}*.inactiveForeground = @disabledForeground
{Gray}Button.background = @buttonBackground
{Gray}Separator.foreground = @ijSeparatorLight
{Gray}ToolBar.separatorColor = $Separator.foreground
[Gruvbox_Dark_Hard]Component.accentColor = lazy(TabbedPane.underlineColor)
[Gruvbox_Dark_Hard]ToggleButton.selectedBackground = $ToggleButton.selectedBackground
[Gruvbox_Dark_Hard]ToggleButton.toolbar.selectedBackground = $ToggleButton.toolbar.selectedBackground
[Gruvbox_Dark_Hard]ComboBox.background = @ijTextBackgroundL3
[Gruvbox_Dark_Hard]ComboBox.buttonBackground = @ijTextBackgroundL3
[Gruvbox_Dark_Hard]TextField.background = @ijTextBackgroundL3
{Gruvbox_Dark_Hard}@accentBaseColor = #4B6EAF
{Gruvbox_Dark_Hard}ComboBox.background = @ijTextBackgroundL3
{Gruvbox_Dark_Hard}ComboBox.buttonBackground = $ComboBox.background
{Gruvbox_Dark_Hard}TextField.background = @ijTextBackgroundL3
[Gruvbox_Dark_Medium]Component.accentColor = lazy(TabbedPane.underlineColor)
[Gruvbox_Dark_Medium]ToggleButton.selectedBackground = $ToggleButton.selectedBackground
[Gruvbox_Dark_Medium]ToggleButton.toolbar.selectedBackground = $ToggleButton.toolbar.selectedBackground
[Gruvbox_Dark_Medium]ComboBox.background = @ijTextBackgroundL3
[Gruvbox_Dark_Medium]ComboBox.buttonBackground = @ijTextBackgroundL3
[Gruvbox_Dark_Medium]TextField.background = @ijTextBackgroundL3
{Hiberbee_Dark}@disabledForeground = $ColorPalette.light3
{Hiberbee_Dark}*.disabledText = @disabledForeground
{Hiberbee_Dark}*.disabledForeground = @disabledForeground
{Hiberbee_Dark}*.inactiveForeground = @disabledForeground
{Hiberbee_Dark}List.selectionInactiveBackground = $Table.selectionInactiveBackground
{Hiberbee_Dark}ProgressBar.background = $Separator.foreground
{Hiberbee_Dark}RadioButtonMenuItem.selectionForeground = $MenuItem.selectionForeground
{Hiberbee_Dark}Slider.trackColor = $ColorPalette.light1
{Hiberbee_Dark}Slider.trackColor = $ColorPalette.dark10
{Hiberbee_Dark}Slider.thumbColor = @accentBaseColor
{Hiberbee_Dark}ToggleButton.foreground = $Button.foreground
{Hiberbee_Dark}ToolBar.background = @background
[Gruvbox_Dark_Soft]Component.accentColor = lazy(TabbedPane.underlineColor)
[Gruvbox_Dark_Soft]MenuItem.checkBackground = @ijMenuCheckBackgroundL10
[Gruvbox_Dark_Soft]MenuItem.underlineSelectionCheckBackground = @ijMenuCheckBackgroundL10
[Gruvbox_Dark_Soft]ToggleButton.selectedBackground = $ToggleButton.selectedBackground
[Gruvbox_Dark_Soft]ToggleButton.toolbar.selectedBackground = $ToggleButton.toolbar.selectedBackground
[Gruvbox_Dark_Soft]ComboBox.background = @ijTextBackgroundL3
[Gruvbox_Dark_Soft]ComboBox.buttonBackground = @ijTextBackgroundL3
[Gruvbox_Dark_Soft]TextField.background = @ijTextBackgroundL3
[Hiberbee_Dark]*.disabledForeground = #7F7E7D
[Hiberbee_Dark]*.disabledText = #7F7E7D
[Hiberbee_Dark]*.inactiveForeground = #7F7E7D
[Hiberbee_Dark]ProgressBar.background = lazy(Separator.foreground)
[Hiberbee_Dark]Slider.trackColor = lazy(Separator.foreground)
[Hiberbee_Dark]TabbedPane.focusColor = #5A5A5A
[Hiberbee_Dark]TabbedPane.selectedBackground = #434241
[Hiberbee_Dark]TabbedPane.selectedForeground = #70D7FF
[Hiberbee_Dark]ToggleButton.selectedBackground = $ToggleButton.selectedBackground
[Hiberbee_Dark]ToggleButton.toolbar.selectedBackground = $ToggleButton.toolbar.selectedBackground
[Hiberbee_Dark]Table.selectionInactiveBackground = lazy(List.selectionInactiveBackground)
[Hiberbee_Dark]Tree.selectionBackground = lazy(List.selectionBackground)
[Hiberbee_Dark]Tree.selectionInactiveBackground = lazy(List.selectionInactiveBackground)
[High_contrast]Component.accentColor = lazy(Component.focusColor)
[High_contrast]ToggleButton.selectedBackground = #fff
[High_contrast]ToggleButton.selectedForeground = #000
[High_contrast]ToggleButton.disabledSelectedBackground = #444
[High_contrast]ToggleButton.toolbar.selectedBackground = #fff
[High_contrast][style]Button.inTextField = \
{High_Contrast}@accentBaseColor = $TabbedPane.underlineColor
{High_Contrast}Slider.thumbBorderColor = $Slider.thumbColor
{High_Contrast}Slider.focusedThumbBorderColor = @background
{High_Contrast}Slider.focusedColor = $Component.focusColor
{High_Contrast}Slider.focusWidth = 2
{High_Contrast}ToggleButton.selectedBackground = @selectionBackground
{High_Contrast}ToggleButton.selectedForeground = @selectionForeground
{High_Contrast}ToggleButton.disabledSelectedBackground = shade(@selectionBackground,50%)
{High_Contrast}ToggleButton.toolbar.selectedBackground = @selectionBackground
{High_Contrast}[style]Button.inTextField = \
toolbar.hoverBackground: #444; \
toolbar.pressedBackground: #666; \
toolbar.selectedBackground: #fff
[High_contrast][style]ToggleButton.inTextField = $[High_contrast][style]Button.inTextField
toolbar.selectedBackground: @selectionBackground
[Light_Flat]*.disabledForeground = #8C8C8C
[Light_Flat]*.inactiveForeground = #8C8C8C
[Light_Flat]CheckBox.icon[filled].background = #fff
[Light_Flat]CheckBox.icon[filled].checkmarkColor = #fff
[Light_Flat]Component.accentColor = lazy(TabbedPane.underlineColor)
[Light_Flat]ComboBox.background = lazy(ComboBox.editableBackground)
[Light_Flat]ComboBox.buttonBackground = lazy(ComboBox.editableBackground)
[Light_Flat]Separator.foreground = lazy(ToolBar.separatorColor)
[Light_Flat]TableHeader.background = #E5E5E9
[Light_Flat]TextPane.foreground = lazy(TextField.foreground)
[Light_Flat]CheckBoxMenuItem.selectionForeground = lazy(MenuItem.selectionForeground)
[Light_Flat]RadioButtonMenuItem.selectionForeground = lazy(MenuItem.selectionForeground)
{Light_Flat}@accentBaseColor = $TabbedPane.underlineColor
{Light_Flat}@accentFocusColor = lighten(@accentBaseColor,15%)
{Light_Flat}@disabledForeground = #808080
{Light_Flat}*.disabledText = @disabledForeground
{Light_Flat}*.disabledForeground = @disabledForeground
{Light_Flat}*.inactiveForeground = @disabledForeground
{Light_Flat}CheckBox.icon[filled].background = #fff
{Light_Flat}CheckBox.icon[filled].checkmarkColor = #fff
{Light_Flat}ComboBox.background = $ComboBox.editableBackground
{Light_Flat}ComboBox.buttonBackground = $ComboBox.background
{Light_Flat}ProgressBar.selectionForeground = @foreground
{Light_Flat}Separator.foreground = @ijSeparatorLight
{Light_Flat}TableHeader.background = #E5E5E9
{Light_Flat}TextPane.foreground = $TextField.foreground
{Light_Flat}ToggleButton.foreground = $Button.foreground
{Light_Flat}CheckBoxMenuItem.selectionForeground = $MenuItem.selectionForeground
{Light_Flat}RadioButtonMenuItem.selectionForeground = $MenuItem.selectionForeground
[Monocai]Button.default.foreground = #2D2A2F
[Monocai]MenuItem.checkBackground = @ijMenuCheckBackgroundL10
[Monocai]MenuItem.underlineSelectionCheckBackground = @ijMenuCheckBackgroundL10
@Monocai.acceleratorForeground = lazy(MenuItem.disabledForeground)
@Monocai.acceleratorSelectionForeground = lighten(MenuItem.disabledForeground,10%,lazy)
[Monocai]CheckBoxMenuItem.acceleratorForeground = @Monocai.acceleratorForeground
[Monocai]CheckBoxMenuItem.acceleratorSelectionForeground = @Monocai.acceleratorSelectionForeground
[Monocai]Menu.acceleratorForeground = @Monocai.acceleratorForeground
[Monocai]Menu.acceleratorSelectionForeground = @Monocai.acceleratorSelectionForeground
[Monocai]MenuItem.acceleratorForeground = @Monocai.acceleratorForeground
[Monocai]MenuItem.acceleratorSelectionForeground = @Monocai.acceleratorSelectionForeground
[Monocai]RadioButtonMenuItem.acceleratorForeground = @Monocai.acceleratorForeground
[Monocai]RadioButtonMenuItem.acceleratorSelectionForeground = @Monocai.acceleratorSelectionForeground
[Monocai]TextField.background = @ijTextBackgroundL4
@Monocai.selectionBackground = lazy(TextField.selectionBackground)
[Monocai]ComboBox.selectionBackground = @Monocai.selectionBackground
[Monocai]List.selectionBackground = @Monocai.selectionBackground
[Monocai]Table.selectionBackground = @Monocai.selectionBackground
[Monocai]Tree.selectionBackground = @Monocai.selectionBackground
@Monocai.selectionInactiveBackground = lazy(MenuItem.selectionBackground)
[Monocai]List.selectionInactiveBackground = @Monocai.selectionInactiveBackground
[Monocai]Table.selectionInactiveBackground = @Monocai.selectionInactiveBackground
[Monocai]Tree.selectionInactiveBackground = @Monocai.selectionInactiveBackground
{Monocai}@accentUnderlineColor = @accentBaseColor
{Monocai}*.acceleratorForeground = @menuAcceleratorForeground
{Monocai}*.acceleratorSelectionForeground = @menuAcceleratorSelectionForeground
{Monocai}Button.default.foreground = @background
{Monocai}MenuItem.checkBackground = @ijMenuCheckBackgroundL10
{Monocai}TabbedPane.underlineColor = @accentUnderlineColor
{Monocai}TextField.background = @ijTextBackgroundL4
@Monocai.selectionBackground = $TextField.selectionBackground
{Monocai}ComboBox.selectionBackground = @Monocai.selectionBackground
{Monocai}List.selectionBackground = @Monocai.selectionBackground
{Monocai}Table.selectionBackground = @Monocai.selectionBackground
{Monocai}Tree.selectionBackground = @Monocai.selectionBackground
@Monocai.selectionInactiveBackground = $MenuItem.selectionBackground
{Monocai}List.selectionInactiveBackground = @Monocai.selectionInactiveBackground
{Monocai}Table.selectionInactiveBackground = @Monocai.selectionInactiveBackground
{Monocai}Tree.selectionInactiveBackground = @Monocai.selectionInactiveBackground
[Monokai_Pro---Subtheme]Table.selectionInactiveForeground = lazy(List.selectionInactiveForeground)
[Monokai_Pro---Subtheme]Tree.selectionBackground = lazy(List.selectionBackground)
[Monokai_Pro---Subtheme]Separator.foreground = lazy(Slider.trackColor)
[Monokai_Pro---Subtheme]ToolBar.separatorColor = lazy(Slider.trackColor)
{Monokai_Pro---Subtheme}@disabledForeground = shade(@foreground,40%)
{Monokai_Pro---Subtheme}*.disabledText = @disabledForeground
{Monokai_Pro---Subtheme}*.disabledForeground = @disabledForeground
{Monokai_Pro---Subtheme}*.inactiveForeground = @disabledForeground
{Monokai_Pro---Subtheme}ProgressBar.selectionBackground = #fff
{Monokai_Pro---Subtheme}Table.selectionInactiveForeground = $List.selectionInactiveForeground
{Monokai_Pro---Subtheme}Tree.selectionBackground = $List.selectionBackground
{Monokai_Pro---Subtheme}ToggleButton.foreground = $Button.foreground
{Monokai_Pro---Subtheme}Separator.foreground = @ijSeparatorDark
{Monokai_Pro---Subtheme}ToolBar.separatorColor = $Separator.foreground
{Monokai_Pro---Subtheme}ToolBar.background = @background
[Nord]*.inactiveForeground = #616E88
[Nord]MenuItem.checkBackground = @ijMenuCheckBackgroundL10
[Nord]MenuItem.underlineSelectionCheckBackground = @ijMenuCheckBackgroundL10
[Nord]List.selectionBackground = lazy(Tree.selectionBackground)
[Nord]List.selectionForeground = lazy(Tree.selectionForeground)
[Nord]Table.selectionBackground = lazy(Tree.selectionBackground)
[Nord]Table.selectionForeground = lazy(Tree.selectionForeground)
[Nord]TextField.selectionBackground = lazy(Tree.selectionBackground)
[Nord]TextField.selectionForeground = lazy(Tree.selectionForeground)
[Nord]Tree.selectionInactiveForeground = lazy(List.selectionInactiveForeground)
{Nord}@disabledForeground = #616E88
{Nord}*.disabledText = @disabledForeground
{Nord}*.disabledForeground = @disabledForeground
{Nord}*.inactiveForeground = @disabledForeground
{Nord}MenuItem.checkBackground = @ijMenuCheckBackgroundL10
{Nord}MenuItem.underlineSelectionCheckBackground = @ijMenuCheckBackgroundL10
{Nord}RadioButtonMenuItem.selectionBackground = $MenuItem.selectionBackground
{Nord}ProgressBar.selectionBackground = @foreground
{Nord}ProgressBar.selectionForeground = @background
{Nord}List.selectionBackground = $Tree.selectionBackground
{Nord}List.selectionForeground = $Tree.selectionForeground
{Nord}Table.selectionBackground = $Tree.selectionBackground
{Nord}Table.selectionForeground = $Tree.selectionForeground
{Nord}TextField.selectionBackground = $Tree.selectionBackground
{Nord}TextField.selectionForeground = $Tree.selectionForeground
{Nord}Tree.selectionInactiveForeground = $List.selectionInactiveForeground
[NotReallyMDTheme]*.selectionInactiveBackground = #21384E
[NotReallyMDTheme]ToolBar.separatorColor = lazy(Separator.foreground)
{NotReallyMDTheme}*.selectionInactiveBackground = #21384E
{NotReallyMDTheme}ToolBar.separatorColor = $Separator.foreground
[One_Dark]List.selectionInactiveForeground = lazy(Tree.selectionInactiveForeground)
[One_Dark]MenuItem.checkBackground = @ijMenuCheckBackgroundL10
[One_Dark]MenuItem.underlineSelectionCheckBackground = @ijMenuCheckBackgroundL10
[One_Dark]ProgressBar.background = lazy(Separator.foreground)
[One_Dark]Slider.trackColor = lazy(Separator.foreground)
[One_Dark]Slider.focusedColor = fade(#568af2,40%)
[One_Dark]Table.background = lazy(Tree.background)
[One_Dark]Table.selectionBackground = lazy(Tree.selectionBackground)
[One_Dark]TextField.selectionBackground = lazy(List.selectionBackground)
[One_Dark]Tree.selectionForeground = lazy(List.selectionForeground)
{One_Dark}MenuItem.checkBackground = @ijMenuCheckBackgroundL10
{One_Dark}MenuItem.underlineSelectionCheckBackground = @ijMenuCheckBackgroundL10
{One_Dark}ProgressBar.background = $Separator.foreground
{One_Dark}ProgressBar.selectionForeground = #fff
{One_Dark}Table.background = $Tree.background
{One_Dark}Table.selectionBackground = $Tree.selectionBackground
{One_Dark}TextField.selectionBackground = $List.selectionBackground
{One_Dark}Tree.selectionForeground = $List.selectionForeground
[Solarized_Dark---4lex4]*.inactiveForeground = #657B83
[Solarized_Dark---4lex4]Component.accentColor = lazy(TabbedPane.underlineColor)
[Solarized_Dark---4lex4]ComboBox.background = lazy(ComboBox.editableBackground)
[Solarized_Dark---4lex4]ComboBox.buttonBackground = lazy(ComboBox.editableBackground)
[Solarized_Dark---4lex4]Slider.focusedColor = fade($Component.focusColor,80%,derived)
[Solarized_Dark---4lex4]ToolBar.separatorColor = lazy(Separator.foreground)
{Solarized_Dark---4lex4}@accentBaseColor = $TabbedPane.underlineColor
{Solarized_Dark---4lex4}*.acceleratorForeground = @menuAcceleratorForeground
{Solarized_Dark---4lex4}ComboBox.background = $ComboBox.editableBackground
{Solarized_Dark---4lex4}ComboBox.buttonBackground = $ComboBox.editableBackground
{Solarized_Dark---4lex4}Slider.disabledTrackColor = $ColorPalette.colorSeparator
[Solarized_Light---4lex4]*.inactiveForeground = #839496
[Solarized_Light---4lex4]Button.default.hoverBackground = darken($Button.default.background,3%,derived)
[Solarized_Light---4lex4]Component.accentColor = lazy(TabbedPane.underlineColor)
{Solarized_Light---4lex4}@accentBaseColor = $TabbedPane.underlineColor
{Solarized_Light---4lex4}Slider.thumbColor = $ProgressBar.foreground
{Solarized_Light---4lex4}Slider.disabledTrackColor = $ColorPalette.colorSeparator
[Spacegray]ComboBox.background = @ijTextBackgroundL4
[Spacegray]ComboBox.buttonBackground = @ijTextBackgroundL4
[Spacegray]TextField.background = @ijTextBackgroundL4
[Spacegray]TextField.selectionBackground = lazy(Tree.selectionBackground)
[Spacegray]TextField.selectionForeground = lazy(Tree.selectionForeground)
{Spacegray}ComboBox.background = @ijTextBackgroundL4
{Spacegray}ComboBox.buttonBackground = $ComboBox.background
{Spacegray}TextField.background = @ijTextBackgroundL4
[vuesion-theme]*.disabledForeground = #8C8C8C
[vuesion-theme]*.disabledText = #8C8C8C
[vuesion-theme]*.inactiveForeground = #8C8C8C
[vuesion-theme]Component.accentColor = lazy(Button.default.endBackground)
[vuesion-theme]MenuItem.checkBackground = @ijMenuCheckBackgroundL10
[vuesion-theme]MenuItem.underlineSelectionCheckBackground = @ijMenuCheckBackgroundL10
[vuesion-theme]Slider.trackValueColor = #ececee
[vuesion-theme]Slider.trackColor = #303a45
[vuesion-theme]Slider.thumbColor = #ececee
[vuesion-theme]Slider.focusedColor = fade(#ececee,20%)
[vuesion-theme]ComboBox.background = @ijTextBackgroundL4
[vuesion-theme]ComboBox.buttonBackground = @ijTextBackgroundL4
[vuesion-theme]TextField.background = @ijTextBackgroundL4
[vuesion-theme]TextField.selectionBackground = lighten(#303A45,15%)
{vuesion-theme}@accentBaseColor = $TabbedPane.underlineColor
{vuesion-theme}@disabledForeground = #8C8C8C
{vuesion-theme}*.disabledText = @disabledForeground
{vuesion-theme}*.disabledForeground = @disabledForeground
{vuesion-theme}*.inactiveForeground = @disabledForeground
{vuesion-theme}MenuItem.checkBackground = @ijMenuCheckBackgroundL10
{vuesion-theme}MenuItem.underlineSelectionCheckBackground = @ijMenuCheckBackgroundL10
{vuesion-theme}ProgressBar.background = #303a45
{vuesion-theme}ProgressBar.foreground = #ececee
{vuesion-theme}Slider.thumbColor = #ececee
{vuesion-theme}Slider.focusedColor = fade($Slider.thumbColor,20%)
{vuesion-theme}ComboBox.background = @ijTextBackgroundL4
{vuesion-theme}ComboBox.buttonBackground = $ComboBox.background
{vuesion-theme}TextField.background = @ijTextBackgroundL4
{vuesion-theme}TextField.selectionBackground = lighten(#303A45,15%)
[Xcode-Dark]TextField.background = @ijTextBackgroundL4
{Xcode-Dark}@accentBaseColor = $List.selectionBackground
{Xcode-Dark}TextField.background = @ijTextBackgroundL4
# Material Theme UI Lite
[light][author-Mallowigi]MenuItem.checkBackground = @ijMenuCheckBackgroundD10
[light][author-Mallowigi]MenuItem.underlineSelectionCheckBackground = @ijMenuCheckBackgroundD10
[dark][author-Mallowigi]MenuItem.checkBackground = @ijMenuCheckBackgroundL20
[dark][author-Mallowigi]MenuItem.underlineSelectionCheckBackground = @ijMenuCheckBackgroundL20
{author-Mallowigi}[light]controlHighlight = lighten($controlShadow,8%)
{author-Mallowigi}[light]controlLtHighlight = lighten($controlShadow,15%)
{author-Mallowigi}[light]controlDkShadow = darken($controlShadow,15%)
{author-Mallowigi}[dark]controlHighlight = darken($controlShadow,10%)
{author-Mallowigi}[dark]controlLtHighlight = darken($controlShadow,15%)
{author-Mallowigi}[dark]controlDkShadow = lighten($controlShadow,10%)
[author-Mallowigi]Tree.selectionInactiveBackground = lazy(List.selectionInactiveBackground)
{author-Mallowigi}Button.hoverBorderColor = $Button.focusedBorderColor
{author-Mallowigi}HelpButton.hoverBorderColor = $Button.focusedBorderColor
[Arc_Dark]ComboBox.selectionBackground = lazy(List.selectionBackground)
[Arc_Dark]Table.selectionBackground = lazy(List.selectionBackground)
{author-Mallowigi}[light]ToggleButton.selectedForeground = #000
{author-Mallowigi}[dark]ToggleButton.selectedForeground = #fff
[Atom_One_Dark]Separator.foreground = lazy(Slider.trackColor)
[Atom_One_Dark]ToolBar.separatorColor = lazy(Slider.trackColor)
{author-Mallowigi}[light]MenuItem.checkBackground = @ijMenuCheckBackgroundD10
{author-Mallowigi}[light]MenuItem.underlineSelectionCheckBackground = @ijMenuCheckBackgroundD10
{author-Mallowigi}[dark]MenuItem.checkBackground = @ijMenuCheckBackgroundL20
{author-Mallowigi}[dark]MenuItem.underlineSelectionCheckBackground = @ijMenuCheckBackgroundL20
[Atom_One_Light]List.selectionBackground = lazy(Table.selectionBackground)
[Atom_One_Light]Tree.selectionBackground = lazy(Table.selectionBackground)
[Atom_One_Light]TabbedPane.contentAreaColor = lazy(Separator.foreground)
{author-Mallowigi}[light]Separator.foreground = @ijSeparatorLight
{author-Mallowigi}[dark]Separator.foreground = @ijSeparatorDark
{author-Mallowigi}ProgressBar.selectionBackground = @foreground
{author-Mallowigi}TabbedPane.selectedBackground = mix(@background,$ColorPalette.table,60%)
{author-Mallowigi}ToolBar.separatorColor = $Separator.foreground
{author-Mallowigi}Button.foreground = @foreground
{author-Mallowigi}Tree.foreground = @foreground
[Dracula---Mallowigi]*.selectionBackground = #44475A
[Dracula---Mallowigi]List.selectionInactiveForeground = lazy(Tree.selectionInactiveForeground)
[Dracula---Mallowigi]ProgressBar.selectionBackground = #fff
[Dracula---Mallowigi]ProgressBar.selectionForeground = #fff
[Dracula---Mallowigi]RadioButtonMenuItem.selectionForeground = lazy(CheckBoxMenuItem.selectionForeground)
[Dracula---Mallowigi]Table.selectionForeground = lazy(List.selectionForeground)
[Dracula---Mallowigi]Separator.foreground = lazy(Slider.trackColor)
[Dracula---Mallowigi]ToolBar.separatorColor = lazy(Slider.trackColor)
[GitHub]ProgressBar.selectionBackground = #222
[GitHub]ProgressBar.selectionForeground = #222
[GitHub]TextField.background = @ijTextBackgroundL3
[GitHub]List.selectionBackground = lazy(Table.selectionBackground)
[GitHub]Tree.selectionBackground = lazy(Table.selectionBackground)
{Arc_Dark}ComboBox.selectionBackground = $List.selectionBackground
{Arc_Dark}ProgressBar.selectionBackground = #fff
{Arc_Dark}ProgressBar.selectionForeground = #fff
{Arc_Dark}Table.selectionBackground = $List.selectionBackground
{Arc_Dark}Tree.selectionInactiveBackground = $List.selectionInactiveBackground
[GitHub_Dark]ComboBox.selectionBackground = lazy(Tree.selectionBackground)
[GitHub_Dark]Table.selectionBackground = lazy(Tree.selectionBackground)
[GitHub_Dark]Separator.foreground = lazy(Slider.trackColor)
[GitHub_Dark]ToolBar.separatorColor = lazy(Slider.trackColor)
{Atom_One_Dark}ProgressBar.selectionBackground = #fff
{Atom_One_Dark}ProgressBar.selectionForeground = #fff
{Atom_One_Dark}List.selectionBackground = $Table.selectionBackground
{Atom_One_Dark}Tree.selectionBackground = $Table.selectionBackground
{Atom_One_Dark}List.selectionInactiveBackground = $Tree.selectionInactiveBackground
{Atom_One_Dark}Table.selectionInactiveBackground = $Tree.selectionInactiveBackground
[Light_Owl]CheckBoxMenuItem.selectionForeground = lazy(CheckBoxMenuItem.foreground)
[Light_Owl]ComboBox.selectionForeground = lazy(ComboBox.foreground)
[Light_Owl]List.selectionInactiveForeground = lazy(List.foreground)
[Light_Owl]Menu.selectionForeground = lazy(Menu.foreground)
[Light_Owl]MenuBar.selectionForeground = lazy(MenuBar.foreground)
[Light_Owl]MenuItem.selectionForeground = lazy(MenuItem.foreground)
[Light_Owl]ProgressBar.selectionBackground = #111
[Light_Owl]ProgressBar.selectionForeground = #fff
[Light_Owl]Spinner.selectionForeground = lazy(Spinner.foreground)
[Light_Owl]Table.selectionForeground = lazy(Table.foreground)
[Light_Owl]TextField.selectionForeground = lazy(TextField.foreground)
[Light_Owl]TextField.background = @ijTextBackgroundL3
[Light_Owl]List.selectionBackground = lazy(Table.selectionBackground)
[Light_Owl]Tree.selectionBackground = lazy(Table.selectionBackground)
{Atom_One_Light}@disabledForeground = shade($ColorPalette.dis,20%)
{Atom_One_Light}*.disabledText = @disabledForeground
{Atom_One_Light}*.disabledForeground = @disabledForeground
{Atom_One_Light}*.inactiveForeground = @disabledForeground
{Atom_One_Light}TabbedPane.contentAreaColor = $Separator.foreground
[Material_Darker]*.selectionBackground = lighten(#2D2D2D,15%)
[Material_Darker]Separator.foreground = lazy(Slider.trackColor)
[Material_Darker]ToolBar.separatorColor = lazy(Slider.trackColor)
{Dracula---Mallowigi}ProgressBar.selectionBackground = #fff
{Dracula---Mallowigi}ProgressBar.selectionForeground = #fff
{Dracula---Mallowigi}List.selectionBackground = $Table.selectionBackground
{Dracula---Mallowigi}Tree.selectionBackground = $Table.selectionBackground
{Dracula---Mallowigi}List.selectionInactiveBackground = $Tree.selectionInactiveBackground
{Dracula---Mallowigi}Table.selectionInactiveBackground = $Tree.selectionInactiveBackground
[Material_Deep_Ocean]*.selectionBackground = lighten(#222533,15%)
[Material_Deep_Ocean]Separator.foreground = lazy(Slider.trackColor)
[Material_Deep_Ocean]ToolBar.separatorColor = lazy(Slider.trackColor)
{GitHub}ProgressBar.selectionBackground = #222
{GitHub}ProgressBar.selectionForeground = #222
{GitHub}TextField.background = @ijTextBackgroundL3
{GitHub}List.selectionBackground = $Table.selectionBackground
{GitHub}Tree.selectionBackground = $Table.selectionBackground
{GitHub}List.selectionInactiveBackground = $Tree.selectionInactiveBackground
{GitHub}Table.selectionInactiveBackground = $Tree.selectionInactiveBackground
[Material_Lighter]List.selectionInactiveForeground = lazy(Tree.selectionInactiveForeground)
[Material_Lighter]ProgressBar.selectionBackground = #222
[Material_Lighter]ProgressBar.selectionForeground = #fff
[Material_Lighter]ComboBox.selectionBackground = lazy(List.selectionBackground)
[Material_Lighter]Table.selectionBackground = lazy(List.selectionBackground)
[Material_Lighter]List.selectionForeground = lazy(Table.selectionForeground)
[Material_Lighter]RadioButtonMenuItem.selectionForeground = lazy(Table.selectionForeground)
[Material_Lighter]Tree.selectionForeground = lazy(Table.selectionForeground)
{GitHub_Dark}ComboBox.selectionBackground = $Tree.selectionBackground
{GitHub_Dark}ProgressBar.selectionForeground = #fff
{GitHub_Dark}Slider.trackColor = lighten(#2b3036,5%)
{GitHub_Dark}Table.selectionBackground = $Tree.selectionBackground
{GitHub_Dark}Tree.selectionInactiveBackground = $Table.selectionInactiveBackground
[Material_Oceanic]ProgressBar.selectionBackground = #ddd
[Material_Oceanic]ProgressBar.selectionForeground = #ddd
[Material_Oceanic]Separator.foreground = lazy(Slider.trackColor)
[Material_Oceanic]ToolBar.separatorColor = lazy(Slider.trackColor)
{Light_Owl}@disabledForeground = shade($ColorPalette.dis,10%)
{Light_Owl}*.disabledText = @disabledForeground
{Light_Owl}*.disabledForeground = @disabledForeground
{Light_Owl}*.inactiveForeground = @disabledForeground
{Light_Owl}CheckBoxMenuItem.selectionForeground = $CheckBoxMenuItem.foreground
{Light_Owl}ComboBox.selectionForeground = $ComboBox.foreground
{Light_Owl}List.selectionInactiveForeground = $Table.selectionInactiveForeground
{Light_Owl}Menu.selectionForeground = $Menu.foreground
{Light_Owl}MenuBar.selectionForeground = $MenuBar.foreground
{Light_Owl}MenuItem.selectionForeground = $MenuItem.foreground
{Light_Owl}Table.selectionForeground = $List.selectionForeground
{Light_Owl}TextField.selectionForeground = $TextField.foreground
{Light_Owl}TextField.background = @ijTextBackgroundL3
{Light_Owl}List.selectionBackground = $Table.selectionBackground
{Light_Owl}Tree.selectionBackground = $Table.selectionBackground
{Light_Owl}List.selectionInactiveBackground = $Tree.selectionInactiveBackground
{Light_Owl}Table.selectionInactiveBackground = $Tree.selectionInactiveBackground
[Material_Palenight]ProgressBar.selectionBackground = #ddd
[Material_Palenight]ProgressBar.selectionForeground = #ddd
[Material_Palenight]List.selectionBackground = lazy(Table.selectionBackground)
[Material_Palenight]Tree.selectionBackground = lazy(Table.selectionBackground)
[Material_Palenight]Separator.foreground = lazy(Slider.trackColor)
[Material_Palenight]ToolBar.separatorColor = lazy(Slider.trackColor)
{Material_Darker}@disabledForeground = tint($ColorPalette.dis,30%)
{Material_Darker}*.disabledText = @disabledForeground
{Material_Darker}*.disabledForeground = @disabledForeground
{Material_Darker}*.inactiveForeground = @disabledForeground
{Material_Darker}*.selectionBackground = lighten($ColorPalette.tree,15%)
{Material_Darker}ProgressBar.selectionForeground = #fff
{Material_Darker}Tree.selectionInactiveBackground = $List.selectionInactiveBackground
[Monokai_Pro---Mallowigi]List.selectionForeground = lazy(Table.selectionForeground)
[Monokai_Pro---Mallowigi]RadioButtonMenuItem.selectionForeground = lazy(Table.selectionForeground)
[Monokai_Pro---Mallowigi]Table.selectionInactiveForeground = lazy(List.selectionInactiveForeground)
[Monokai_Pro---Mallowigi]Tree.selectionForeground = lazy(Table.selectionForeground)
[Monokai_Pro---Mallowigi]Tree.selectionInactiveForeground = lazy(List.selectionInactiveForeground)
[Monokai_Pro---Mallowigi]Separator.foreground = lazy(Slider.trackColor)
[Monokai_Pro---Mallowigi]ToolBar.separatorColor = lazy(Slider.trackColor)
{Material_Deep_Ocean}@disabledForeground = tint($ColorPalette.dis,10%)
{Material_Deep_Ocean}*.disabledText = @disabledForeground
{Material_Deep_Ocean}*.disabledForeground = @disabledForeground
{Material_Deep_Ocean}*.inactiveForeground = @disabledForeground
{Material_Deep_Ocean}*.selectionBackground = lighten($ColorPalette.tree,15%)
{Material_Deep_Ocean}ProgressBar.selectionBackground = #fff
{Material_Deep_Ocean}Slider.trackColor = lighten(#1A1C25,5%)
{Material_Deep_Ocean}Tree.selectionInactiveBackground = $List.selectionInactiveBackground
[Moonlight]ComboBox.selectionBackground = lazy(List.selectionBackground)
[Moonlight]Table.selectionBackground = lazy(List.selectionBackground)
[Moonlight]Separator.foreground = lazy(Slider.trackColor)
[Moonlight]ToolBar.separatorColor = lazy(Slider.trackColor)
{Material_Lighter}@disabledForeground = shade($ColorPalette.dis,30%)
{Material_Lighter}*.disabledText = @disabledForeground
{Material_Lighter}*.disabledForeground = @disabledForeground
{Material_Lighter}*.inactiveForeground = @disabledForeground
{Material_Lighter}ComboBox.selectionBackground = $List.selectionBackground
{Material_Lighter}List.selectionForeground = $Table.selectionForeground
{Material_Lighter}List.selectionInactiveForeground = $Table.selectionInactiveForeground
{Material_Lighter}ProgressBar.selectionBackground = #222
{Material_Lighter}RadioButtonMenuItem.selectionForeground = $Table.selectionForeground
{Material_Lighter}Table.selectionBackground = $List.selectionBackground
{Material_Lighter}Tree.selectionForeground = $Table.selectionForeground
{Material_Lighter}Tree.selectionInactiveBackground = $List.selectionInactiveBackground
[Night_Owl]ProgressBar.selectionBackground = #ddd
[Night_Owl]ProgressBar.selectionForeground = #ddd
{Material_Oceanic}@disabledForeground = tint($ColorPalette.dis,30%)
{Material_Oceanic}*.disabledText = @disabledForeground
{Material_Oceanic}*.disabledForeground = @disabledForeground
{Material_Oceanic}*.inactiveForeground = @disabledForeground
{Material_Oceanic}ProgressBar.selectionBackground = #ddd
{Material_Oceanic}ProgressBar.selectionForeground = #ddd
{Material_Oceanic}List.selectionBackground = $Table.selectionBackground
{Material_Oceanic}Tree.selectionBackground = $Table.selectionBackground
{Material_Oceanic}List.selectionInactiveBackground = $Tree.selectionInactiveBackground
{Material_Oceanic}Table.selectionInactiveBackground = $Tree.selectionInactiveBackground
[Solarized_Dark---Mallowigi]ProgressBar.selectionBackground = #ccc
[Solarized_Dark---Mallowigi]ProgressBar.selectionForeground = #ccc
[Solarized_Dark---Mallowigi]Separator.foreground = lazy(Slider.trackColor)
[Solarized_Dark---Mallowigi]ToolBar.separatorColor = lazy(Slider.trackColor)
{Material_Palenight}@disabledForeground = tint($ColorPalette.dis,20%)
{Material_Palenight}*.disabledText = @disabledForeground
{Material_Palenight}*.disabledForeground = @disabledForeground
{Material_Palenight}*.inactiveForeground = @disabledForeground
{Material_Palenight}ProgressBar.selectionBackground = #ddd
{Material_Palenight}ProgressBar.selectionForeground = #ddd
{Material_Palenight}List.selectionBackground = $Table.selectionBackground
{Material_Palenight}Tree.selectionBackground = $Table.selectionBackground
{Material_Palenight}List.selectionInactiveBackground = $Tree.selectionInactiveBackground
{Material_Palenight}Table.selectionInactiveBackground = $Tree.selectionInactiveBackground
[Solarized_Light---Mallowigi]ProgressBar.selectionBackground = #222
[Solarized_Light---Mallowigi]ProgressBar.selectionForeground = #fff
[Solarized_Light---Mallowigi]ComboBox.selectionBackground = lazy(List.selectionBackground)
[Solarized_Light---Mallowigi]Table.selectionBackground = lazy(List.selectionBackground)
[Solarized_Light---Mallowigi]Separator.foreground = lazy(Slider.trackColor)
[Solarized_Light---Mallowigi]ToolBar.separatorColor = lazy(Slider.trackColor)
{Monokai_Pro---Mallowigi}@disabledForeground = tint($ColorPalette.dis,20%)
{Monokai_Pro---Mallowigi}*.disabledText = @disabledForeground
{Monokai_Pro---Mallowigi}*.disabledForeground = @disabledForeground
{Monokai_Pro---Mallowigi}*.inactiveForeground = @disabledForeground
{Monokai_Pro---Mallowigi}RadioButtonMenuItem.selectionForeground = $MenuItem.selectionForeground
{Monokai_Pro---Mallowigi}List.selectionForeground = $Table.selectionForeground
{Monokai_Pro---Mallowigi}Tree.selectionForeground = $Table.selectionForeground
{Monokai_Pro---Mallowigi}List.selectionInactiveForeground = $Table.selectionInactiveForeground
{Monokai_Pro---Mallowigi}List.selectionBackground = $Table.selectionBackground
{Monokai_Pro---Mallowigi}Tree.selectionBackground = $Table.selectionBackground
{Monokai_Pro---Mallowigi}List.selectionInactiveBackground = $Tree.selectionInactiveBackground
{Monokai_Pro---Mallowigi}Table.selectionInactiveBackground = $Tree.selectionInactiveBackground
{Moonlight}ComboBox.selectionBackground = $List.selectionBackground
{Moonlight}ProgressBar.selectionForeground = #000
{Moonlight}Table.selectionBackground = $List.selectionBackground
{Moonlight}Tree.selectionInactiveBackground = $List.selectionInactiveBackground
{Solarized_Dark---Mallowigi}@disabledForeground = tint($ColorPalette.dis,20%)
{Solarized_Dark---Mallowigi}*.disabledForeground = @disabledForeground
{Solarized_Dark---Mallowigi}*.inactiveForeground = @disabledForeground
{Solarized_Dark---Mallowigi}*.disabledText = @disabledForeground
{Solarized_Dark---Mallowigi}ProgressBar.selectionBackground = #ccc
{Solarized_Dark---Mallowigi}ProgressBar.selectionForeground = #ccc
{Solarized_Dark---Mallowigi}Slider.trackColor = lighten(@background,10%)
{Solarized_Dark---Mallowigi}Table.selectionBackground = $List.selectionBackground
{Solarized_Dark---Mallowigi}Tree.selectionInactiveBackground = $List.selectionInactiveBackground
{Solarized_Light---Mallowigi}@disabledForeground = tint(@foreground,30%)
{Solarized_Light---Mallowigi}*.disabledForeground = @disabledForeground
{Solarized_Light---Mallowigi}*.inactiveForeground = @disabledForeground
{Solarized_Light---Mallowigi}*.disabledText = @disabledForeground
{Solarized_Light---Mallowigi}ProgressBar.selectionBackground = #222
{Solarized_Light---Mallowigi}ComboBox.selectionBackground = $List.selectionBackground
{Solarized_Light---Mallowigi}Slider.disabledTrackColor = lighten($Slider.trackColor,5%)
{Solarized_Light---Mallowigi}Table.selectionBackground = $List.selectionBackground
{Solarized_Light---Mallowigi}Tree.selectionInactiveBackground = $List.selectionInactiveBackground
{Solarized_Light---Mallowigi}Button.toolbar.selectedBackground = darken($@background,15%)
{Solarized_Light---Mallowigi}ToggleButton.toolbar.selectedBackground = $Button.toolbar.selectedBackground

View File

@@ -112,6 +112,7 @@ Button.borderWidth = 0
Button.disabledBackground = darken($Button.background,10%)
Button.default.borderWidth = 0
Button.default.foreground = contrast($Button.default.background, @background, @selectionForeground, 25%)
Button.toolbar.hoverBackground = #fff1
Button.toolbar.pressedBackground = #fff2
@@ -183,6 +184,7 @@ MenuBar.selectionEmbeddedInsets = 3,0,3,0
MenuBar.selectionArc = 8
MenuBar.selectionBackground = lighten(@menuBackground,15%,derived)
MenuBar.selectionForeground = @foreground
MenuBar.borderColor = over($Separator.foreground,$MenuBar.background)
#---- MenuItem ----
@@ -293,6 +295,7 @@ TextPane.selectionForeground = @textSelectionForeground
ToggleButton.disabledBackground = $Button.disabledBackground
ToggleButton.selectedBackground = lighten($ToggleButton.background,20%,derived)
ToggleButton.selectedForeground = lighten($ToggleButton.foreground,20%)
ToggleButton.toolbar.selectedBackground = #fff3

View File

@@ -79,7 +79,7 @@
# general background and foreground (text color)
@background = #f6f6f6
@foreground = over(@nsControlTextColor,@background)
@disabledForeground = over(@nsTertiaryLabelColor,@background)
@disabledForeground = over(@nsSecondaryLabelColor,@background)
# component background
@buttonBackground = @nsControlColor
@@ -184,6 +184,7 @@ MenuBar.selectionEmbeddedInsets = 3,0,3,0
MenuBar.selectionArc = 8
MenuBar.selectionBackground = darken(@menuBackground,15%,derived)
MenuBar.selectionForeground = @foreground
MenuBar.borderColor = over($Separator.foreground,$MenuBar.background)
#---- MenuItem ----

View File

@@ -17,6 +17,9 @@
package com.formdev.flatlaf;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
@@ -27,8 +30,16 @@ import javax.swing.border.Border;
import javax.swing.UIDefaults.ActiveValue;
import javax.swing.UIDefaults.LazyValue;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import com.formdev.flatlaf.ui.FlatEmptyBorder;
import com.formdev.flatlaf.ui.FlatLineBorder;
import com.formdev.flatlaf.util.DerivedColor;
import com.formdev.flatlaf.util.ColorFunctions.ColorFunction;
import com.formdev.flatlaf.util.ColorFunctions.Fade;
import com.formdev.flatlaf.util.ColorFunctions.HSLChange;
import com.formdev.flatlaf.util.ColorFunctions.HSLIncreaseDecrease;
import com.formdev.flatlaf.util.ColorFunctions.Mix;
import com.formdev.flatlaf.util.ColorFunctions.Mix2;
/**
* @author Karl Tauber
@@ -180,6 +191,336 @@ public class TestUIDefaultsLoader
assertEquals( expected, ((LazyValue)UIDefaultsLoader.parseValue( "dummyIcon", value, null )).createValue( null ) );
}
@Test
void parseColorFunctions() {
// lighten
assertEquals( new Color( 0xff6666 ), parseColor( "lighten(#f00, 20%)" ) );
assertEquals( new Color( 0xff3333 ), parseColor( "lighten(#f00, 20%, relative)" ) );
assertEquals( new Color( 0xaaaaaa ), parseColor( "lighten(#ddd, 20%, autoInverse)" ) );
assertEquals( new Color( 0xb1b1b1 ), parseColor( "lighten(#ddd, 20%, relative autoInverse)" ) );
// darken
assertEquals( new Color( 0x990000 ), parseColor( "darken(#f00, 20%)" ) );
assertEquals( new Color( 0xcc0000 ), parseColor( "darken(#f00, 20%, relative)" ) );
assertEquals( new Color( 0x555555 ), parseColor( "darken(#222, 20%, autoInverse)" ) );
assertEquals( new Color( 0x292929 ), parseColor( "darken(#222, 20%, relative autoInverse)" ) );
// saturate
assertEquals( new Color( 0xf32e2e ), parseColor( "saturate(#d44, 20%)" ) );
assertEquals( new Color( 0xec3535 ), parseColor( "saturate(#d44, 20%, relative)" ) );
assertEquals( new Color( 0xc75a5a ), parseColor( "saturate(#d44, 20%, autoInverse)" ) );
assertEquals( new Color( 0xce5353 ), parseColor( "saturate(#d44, 20%, relative autoInverse)" ) );
// desaturate
assertEquals( new Color( 0x745858 ), parseColor( "desaturate(#844, 20%)" ) );
assertEquals( new Color( 0x814b4b ), parseColor( "desaturate(#844, 20%, relative)" ) );
assertEquals( new Color( 0x9c3030 ), parseColor( "desaturate(#844, 20%, autoInverse)" ) );
assertEquals( new Color( 0x8f3d3d ), parseColor( "desaturate(#844, 20%, relative autoInverse)" ) );
// fadein
assertEquals( new Color( 0xddff0000, true ), parseColor( "fadein(#f00a, 20%)" ) );
assertEquals( new Color( 0xccff0000, true ), parseColor( "fadein(#f00a, 20%, relative)" ) );
assertEquals( new Color( 0x77ff0000, true ), parseColor( "fadein(#f00a, 20%, autoInverse)" ) );
assertEquals( new Color( 0x88ff0000, true ), parseColor( "fadein(#f00a, 20%, relative autoInverse)" ) );
// fadeout
assertEquals( new Color( 0x11ff0000, true ), parseColor( "fadeout(#f004, 20%)" ) );
assertEquals( new Color( 0x36ff0000, true ), parseColor( "fadeout(#f004, 20%, relative)" ) );
assertEquals( new Color( 0x77ff0000, true ), parseColor( "fadeout(#f004, 20%, autoInverse)" ) );
assertEquals( new Color( 0x52ff0000, true ), parseColor( "fadeout(#f004, 20%, relative autoInverse)" ) );
// fade
assertEquals( new Color( 0x33ff0000, true ), parseColor( "fade(#f00, 20%)" ) );
assertEquals( new Color( 0xccff0000, true ), parseColor( "fade(#ff000010, 80%)" ) );
// spin
assertEquals( new Color( 0xffaa00 ), parseColor( "spin(#f00, 40)" ) );
assertEquals( new Color( 0xff00aa ), parseColor( "spin(#f00, -40)" ) );
// changeHue / changeSaturation / changeLightness / changeAlpha
assertEquals( new Color( 0xffaa00 ), parseColor( "changeHue(#f00, 40)" ) );
assertEquals( new Color( 0xb34d4d ), parseColor( "changeSaturation(#f00, 40%)" ) );
assertEquals( new Color( 0xcc0000 ), parseColor( "changeLightness(#f00, 40%)" ) );
assertEquals( new Color( 0x66ff0000, true ), parseColor( "changeAlpha(#f00, 40%)" ) );
// mix
assertEquals( new Color( 0x808000 ), parseColor( "mix(#f00, #0f0)" ) );
assertEquals( new Color( 0xbf4000 ), parseColor( "mix(#f00, #0f0, 75%)" ) );
// tint
assertEquals( new Color( 0xff80ff ), parseColor( "tint(#f0f)" ) );
assertEquals( new Color( 0xffbfff ), parseColor( "tint(#f0f, 75%)" ) );
// shade
assertEquals( new Color( 0x800080 ), parseColor( "shade(#f0f)" ) );
assertEquals( new Color( 0x400040 ), parseColor( "shade(#f0f, 75%)" ) );
// contrast
assertEquals( new Color( 0x0000ff ), parseColor( "contrast(#bbb, #00f, #0f0)" ) );
assertEquals( new Color( 0x00ff00 ), parseColor( "contrast(#444, #00f, #0f0)" ) );
assertEquals( new Color( 0x00ff00 ), parseColor( "contrast(#bbb, #00f, #0f0, 60%)" ) );
// rgb / rgba
assertEquals( new Color( 0x5a8120 ), parseColor( "rgb(90, 129, 32)" ) );
assertEquals( new Color( 0x5a8120 ), parseColor( "rgb(90, 129, 32)" ) );
assertEquals( new Color( 0x197fb2 ), parseColor( "rgb(10%,50%,70%)" ) );
assertEquals( new Color( 0x197f46 ), parseColor( "rgb(10%,50%,70)" ) );
assertEquals( new Color( 0x405a8120, true ), parseColor( "rgba(90, 129, 32, 64)" ) );
assertEquals( new Color( 0x335a8120, true ), parseColor( "rgba(90, 129, 32, 20%)" ) );
// hsl / hsla
assertEquals( new Color( 0x7fff00 ), parseColor( "hsl(90, 100%, 50%)" ) );
assertEquals( new Color( 0x337fff00, true ), parseColor( "hsla(90, 100%, 50%, 20%)" ) );
}
@Test
void parseLazyColorFunctions() {
// lighten
assertEquals( new Color( 0xff6666 ), parseColorLazy( "lighten(dummyColor, 20%, lazy)", new Color( 0xff0000 ) ) );
// darken
assertEquals( new Color( 0x990000 ), parseColorLazy( "darken(dummyColor, 20%, lazy)", new Color( 0xff0000 ) ) );
// saturate
assertEquals( new Color( 0xf32e2e ), parseColorLazy( "saturate(dummyColor, 20%, lazy)", new Color( 0xdd4444 ) ) );
// desaturate
assertEquals( new Color( 0x745858 ), parseColorLazy( "desaturate(dummyColor, 20%, lazy)", new Color( 0x884444 ) ) );
// fadein
assertEquals( new Color( 0xddff0000, true ), parseColorLazy( "fadein(dummyColor, 20%, lazy)", new Color( 0xaaff0000, true ) ) );
// fadeout
assertEquals( new Color( 0x11ff0000, true ), parseColorLazy( "fadeout(dummyColor, 20%, lazy)", new Color( 0x44ff0000, true ) ) );
// fade
assertEquals( new Color( 0x33ff0000, true ), parseColorLazy( "fade(dummyColor, 20%, lazy)", new Color( 0xff0000 ) ) );
assertEquals( new Color( 0xccff0000, true ), parseColorLazy( "fade(dummyColor, 80%, lazy)", new Color( 0x10ff0000, true ) ) );
// spin
assertEquals( new Color( 0xffaa00 ), parseColorLazy( "spin(dummyColor, 40, lazy)", new Color( 0xff0000 ) ) );
assertEquals( new Color( 0xff00aa ), parseColorLazy( "spin(dummyColor, -40, lazy)", new Color( 0xff0000 ) ) );
// changeHue / changeSaturation / changeLightness / changeAlpha
assertEquals( new Color( 0xffaa00 ), parseColorLazy( "changeHue(dummyColor, 40, lazy)", new Color( 0xff0000 ) ) );
assertEquals( new Color( 0xb34d4d ), parseColorLazy( "changeSaturation(dummyColor, 40%, lazy)", new Color( 0xff0000 ) ) );
assertEquals( new Color( 0xcc0000 ), parseColorLazy( "changeLightness(dummyColor, 40%, lazy)", new Color( 0xff0000 ) ) );
assertEquals( new Color( 0x66ff0000, true ), parseColorLazy( "changeAlpha(dummyColor, 40%, lazy)", new Color( 0xff0000 ) ) );
// mix
assertEquals( new Color( 0x808000 ), parseColorLazy( "mix(#f00, dummyColor, lazy)", new Color( 0x00ff00 ) ) );
assertEquals( new Color( 0xbf4000 ), parseColorLazy( "mix(#f00, dummyColor, 75%, lazy)", new Color( 0x00ff00 ) ) );
// tint
assertEquals( new Color( 0xff80ff ), parseColorLazy( "tint(dummyColor, lazy)", new Color( 0xff00ff ) ) );
assertEquals( new Color( 0xffbfff ), parseColorLazy( "tint(dummyColor, 75%, lazy)", new Color( 0xff00ff ) ) );
// shade
assertEquals( new Color( 0x800080 ), parseColorLazy( "shade(dummyColor, lazy)", new Color( 0xff00ff ) ) );
assertEquals( new Color( 0x400040 ), parseColorLazy( "shade(dummyColor, 75%, lazy)", new Color( 0xff00ff ) ) );
}
@Test
void parseDerivedColorFunctions() {
// mix
assertDerivedColorEquals( new Color( 0x808000 ), "mix(#f00, #0f0, derived)", new Mix2( Color.red, 50 ) );
assertDerivedColorEquals( new Color( 0xbf4000 ), "mix(#f00, #0f0, 75%, derived)", new Mix2( Color.red, 75 ) );
// tint
assertDerivedColorEquals( new Color( 0xff80ff ), "tint(#f0f, derived)", new Mix2( Color.white, 50 ) );
assertDerivedColorEquals( new Color( 0xffbfff ), "tint(#f0f, 75%, derived)", new Mix2( Color.white, 75 ) );
// shade
assertDerivedColorEquals( new Color( 0x800080 ), "shade(#f0f, derived)", new Mix2( Color.black, 50 ) );
assertDerivedColorEquals( new Color( 0x400040 ), "shade(#f0f, 75%, derived)", new Mix2( Color.black, 75 ) );
// lighten
assertDerivedColorEquals( new Color( 0xff6666 ), "lighten(#f00, 20%, derived)", new HSLIncreaseDecrease( 2, true, 20, false, true ) );
assertDerivedColorEquals( new Color( 0xff3333 ), "lighten(#f00, 20%, derived relative)", new HSLIncreaseDecrease( 2, true, 20, true, true ) );
assertDerivedColorEquals( new Color( 0xffffff ), "lighten(#ddd, 20%, derived noAutoInverse)", new HSLIncreaseDecrease( 2, true, 20, false, false ) );
assertDerivedColorEquals( new Color( 0xffffff ), "lighten(#ddd, 20%, derived relative noAutoInverse)", new HSLIncreaseDecrease( 2, true, 20, true, false ) );
// darken
assertDerivedColorEquals( new Color( 0x990000 ), "darken(#f00, 20%, derived)", new HSLIncreaseDecrease( 2, false, 20, false, true ) );
assertDerivedColorEquals( new Color( 0xcc0000 ), "darken(#f00, 20%, derived relative)", new HSLIncreaseDecrease( 2, false, 20, true, true ) );
assertDerivedColorEquals( new Color( 0x000000 ), "darken(#222, 20%, derived noAutoInverse)", new HSLIncreaseDecrease( 2, false, 20, false, false ) );
assertDerivedColorEquals( new Color( 0x1b1b1b ), "darken(#222, 20%, derived relative noAutoInverse)", new HSLIncreaseDecrease( 2, false, 20, true, false ) );
// saturate
assertDerivedColorEquals( new Color( 0xc75a5a ), "saturate(#d44, 20%, derived)", new HSLIncreaseDecrease( 1, true, 20, false, true ) );
assertDerivedColorEquals( new Color( 0xce5353 ), "saturate(#d44, 20%, derived relative)", new HSLIncreaseDecrease( 1, true, 20, true, true ) );
assertDerivedColorEquals( new Color( 0xf32e2e ), "saturate(#d44, 20%, derived noAutoInverse)", new HSLIncreaseDecrease( 1, true, 20, false, false ) );
assertDerivedColorEquals( new Color( 0xec3535 ), "saturate(#d44, 20%, derived relative noAutoInverse)", new HSLIncreaseDecrease( 1, true, 20, true, false ) );
// desaturate
assertDerivedColorEquals( new Color( 0x9c3030 ), "desaturate(#844, 20%, derived)", new HSLIncreaseDecrease( 1, false, 20, false, true ) );
assertDerivedColorEquals( new Color( 0x8f3d3d ), "desaturate(#844, 20%, derived relative)", new HSLIncreaseDecrease( 1, false, 20, true, true ) );
assertDerivedColorEquals( new Color( 0x745858 ), "desaturate(#844, 20%, derived noAutoInverse)", new HSLIncreaseDecrease( 1, false, 20, false, false ) );
assertDerivedColorEquals( new Color( 0x814b4b ), "desaturate(#844, 20%, derived relative noAutoInverse)", new HSLIncreaseDecrease( 1, false, 20, true, false ) );
// fadein
assertDerivedColorEquals( new Color( 0x77ff0000, true ), "fadein(#f00a, 20%, derived)", new HSLIncreaseDecrease( 3, true, 20, false, true ) );
assertDerivedColorEquals( new Color( 0x88ff0000, true ), "fadein(#f00a, 20%, derived relative)", new HSLIncreaseDecrease( 3, true, 20, true, true ) );
assertDerivedColorEquals( new Color( 0xddff0000, true ), "fadein(#f00a, 20%, derived noAutoInverse)", new HSLIncreaseDecrease( 3, true, 20, false, false ) );
assertDerivedColorEquals( new Color( 0xccff0000, true ), "fadein(#f00a, 20%, derived relative noAutoInverse)", new HSLIncreaseDecrease( 3, true, 20, true, false ) );
// fadeout
assertDerivedColorEquals( new Color( 0x77ff0000, true ), "fadeout(#f004, 20%, derived)", new HSLIncreaseDecrease( 3, false, 20, false, true ) );
assertDerivedColorEquals( new Color( 0x52ff0000, true ), "fadeout(#f004, 20%, derived relative)", new HSLIncreaseDecrease( 3, false, 20, true, true ) );
assertDerivedColorEquals( new Color( 0x11ff0000, true ), "fadeout(#f004, 20%, derived noAutoInverse)", new HSLIncreaseDecrease( 3, false, 20, false, false ) );
assertDerivedColorEquals( new Color( 0x36ff0000, true ), "fadeout(#f004, 20%, derived relative noAutoInverse)", new HSLIncreaseDecrease( 3, false, 20, true, false ) );
// fade
assertDerivedColorEquals( new Color( 0x33ff0000, true ), "fade(#f00, 20%, derived)", new Fade( 20 ) );
assertDerivedColorEquals( new Color( 0xccff0000, true ), "fade(#ff000010, 80%, derived)", new Fade( 80 ) );
// spin
assertDerivedColorEquals( new Color( 0xffaa00 ), "spin(#f00, 40, derived)", new HSLIncreaseDecrease( 0, true, 40, false, false ) );
assertDerivedColorEquals( new Color( 0xff00aa ), "spin(#f00, -40, derived)", new HSLIncreaseDecrease( 0, true, -40, false, false ) );
// changeHue / changeSaturation / changeLightness / changeAlpha
assertDerivedColorEquals( new Color( 0xffaa00 ), "changeHue(#f00, 40, derived)", new HSLChange( 0, 40 ) );
assertDerivedColorEquals( new Color( 0xb34d4d ), "changeSaturation(#f00, 40%, derived)", new HSLChange( 1, 40 ) );
assertDerivedColorEquals( new Color( 0xcc0000 ), "changeLightness(#f00, 40%, derived)", new HSLChange( 2, 40 ) );
assertDerivedColorEquals( new Color( 0x66ff0000, true ), "changeAlpha(#f00, 40%, derived)", new HSLChange( 3, 40 ) );
// mix
assertDerivedColorEquals( new Color( 0x808000 ), "mix(#f00, #0f0, derived)", new Mix2( new Color( 0xff0000 ), 50 ) );
assertDerivedColorEquals( new Color( 0xbf4000 ), "mix(#f00, #0f0, 75%, derived)", new Mix2( new Color( 0xff0000 ), 75 ) );
// tint
assertDerivedColorEquals( new Color( 0xff80ff ), "tint(#f0f, derived)", new Mix2( new Color( 0xffffff ), 50 ) );
assertDerivedColorEquals( new Color( 0xffbfff ), "tint(#f0f, 75%, derived)", new Mix2( new Color( 0xffffff ), 75 ) );
// shade
assertDerivedColorEquals( new Color( 0x800080 ), "shade(#f0f, derived)", new Mix2( new Color( 0x000000 ), 50 ) );
assertDerivedColorEquals( new Color( 0x400040 ), "shade(#f0f, 75%, derived)", new Mix2( new Color( 0x000000 ), 75 ) );
}
private void assertDerivedColorEquals( Color expectedColor, String actualStyle, ColorFunction... expectedFunctions ) {
Object actual = parseColor( actualStyle );
assertInstanceOf( DerivedColor.class, actual );
assertEquals( expectedColor, actual );
ColorFunction[] actualFunctions = ((DerivedColor)actual).getFunctions();
assertEquals( expectedFunctions.length, actualFunctions.length );
for( int i = 0; i < expectedFunctions.length; i++ )
assertColorFunctionEquals( expectedFunctions[i], actualFunctions[i] );
}
private void assertColorFunctionEquals( ColorFunction expected, ColorFunction actual ) {
assertEquals( expected.getClass(), actual.getClass() );
if( expected instanceof HSLIncreaseDecrease ) {
HSLIncreaseDecrease e = (HSLIncreaseDecrease) expected;
HSLIncreaseDecrease a = (HSLIncreaseDecrease) actual;
assertEquals( e.hslIndex, a.hslIndex );
assertEquals( e.increase, a.increase );
assertEquals( e.amount, a.amount );
assertEquals( e.relative, a.relative );
assertEquals( e.autoInverse, a.autoInverse );
} else if( expected instanceof HSLChange ) {
HSLChange e = (HSLChange) expected;
HSLChange a = (HSLChange) actual;
assertEquals( e.hslIndex, a.hslIndex );
assertEquals( e.value, a.value );
} else if( expected instanceof Fade ) {
Fade e = (Fade) expected;
Fade a = (Fade) actual;
assertEquals( e.amount, a.amount );
} else if( expected instanceof Mix ) {
Mix e = (Mix) expected;
Mix a = (Mix) actual;
assertEquals( e.color2, a.color2 );
assertEquals( e.weight, a.weight );
} else if( expected instanceof Mix2 ) {
Mix2 e = (Mix2) expected;
Mix2 a = (Mix2) actual;
assertEquals( e.color1, a.color1 );
assertEquals( e.weight, a.weight );
} else
assertTrue( false );
}
private Object parseColor( String value ) {
return UIDefaultsLoader.parseValue( "dummyColor", value, null );
}
private Object parseColorLazy( String value, Color actual ) {
UIManager.put( "dummyColor", actual );
Object v = UIDefaultsLoader.parseValue( "dummyColor", value, null );
assertInstanceOf( LazyValue.class, v );
return ((LazyValue)v).createValue( null );
}
//---- invalid values -----------------------------------------------------
@Test
void parseInvalidValue() {
assertThrows( new IllegalArgumentException( "invalid character 'abc'" ), () -> UIDefaultsLoader.parseValue( "dummyChar", "abc", null ) );
assertThrows( new NumberFormatException( "invalid integer or float '123abc'" ), () -> UIDefaultsLoader.parseValue( "dummyWidth", "123abc", null ) );
assertThrows( new NumberFormatException( "invalid integer or float '1.23abc'" ), () -> UIDefaultsLoader.parseValue( "dummyWidth", "1.23abc", null ) );
assertThrows( new IllegalArgumentException( "invalid insets '1,abc,3,4'" ), () -> UIDefaultsLoader.parseValue( "dummyInsets", "1,abc,3,4", null ) );
assertThrows( new IllegalArgumentException( "invalid insets '1,2,3'" ), () -> UIDefaultsLoader.parseValue( "dummyInsets", "1,2,3", null ) );
assertThrows( new IllegalArgumentException( "invalid size '1abc'" ), () -> UIDefaultsLoader.parseValue( "dummySize", "1abc", null ) );
assertThrows( new IllegalArgumentException( "invalid size '1'" ), () -> UIDefaultsLoader.parseValue( "dummySize", "1", null ) );
assertThrows( new IllegalArgumentException( "invalid color '#f0'" ), () -> UIDefaultsLoader.parseValue( "dummy", "#f0", null ) );
assertThrows( new IllegalArgumentException( "invalid color '#f0'" ), () -> UIDefaultsLoader.parseValue( "dummyColor", "#f0", null ) );
}
@Test
void parseInvalidValueWithJavaType() {
assertThrows( new IllegalArgumentException( "invalid boolean 'falseyy'" ), () -> UIDefaultsLoader.parseValue( "dummy", "falseyy", boolean.class ) );
assertThrows( new IllegalArgumentException( "invalid boolean 'falseyy'" ), () -> UIDefaultsLoader.parseValue( "dummy", "falseyy", Boolean.class ) );
assertThrows( new IllegalArgumentException( "invalid character 'abc'" ), () -> UIDefaultsLoader.parseValue( "dummyChar", "abc", char.class ) );
assertThrows( new IllegalArgumentException( "invalid character 'abc'" ), () -> UIDefaultsLoader.parseValue( "dummyChar", "abc", Character.class ) );
assertThrows( new NumberFormatException( "invalid integer '123abc'" ), () -> UIDefaultsLoader.parseValue( "dummyWidth", "123abc", int.class ) );
assertThrows( new NumberFormatException( "invalid integer '123abc'" ), () -> UIDefaultsLoader.parseValue( "dummyWidth", "123abc", Integer.class ) );
assertThrows( new NumberFormatException( "invalid float '1.23abc'" ), () -> UIDefaultsLoader.parseValue( "dummyWidth", "1.23abc", float.class ) );
assertThrows( new NumberFormatException( "invalid float '1.23abc'" ), () -> UIDefaultsLoader.parseValue( "dummyWidth", "1.23abc", Float.class ) );
assertThrows( new IllegalArgumentException( "invalid insets '1,abc,3'" ), () -> UIDefaultsLoader.parseValue( "dummyInsets", "1,abc,3", Insets.class ) );
assertThrows( new IllegalArgumentException( "invalid insets '1,2,3'" ), () -> UIDefaultsLoader.parseValue( "dummyInsets", "1,2,3", Insets.class ) );
assertThrows( new IllegalArgumentException( "invalid size '1abc'" ), () -> UIDefaultsLoader.parseValue( "dummySize", "1abc", Dimension.class ) );
assertThrows( new IllegalArgumentException( "invalid size '1'" ), () -> UIDefaultsLoader.parseValue( "dummySize", "1", Dimension.class ) );
assertThrows( new IllegalArgumentException( "invalid color '#f0'" ), () -> UIDefaultsLoader.parseValue( "dummy", "#f0", Color.class ) );
assertThrows( new IllegalArgumentException( "invalid color '#f0'" ), () -> UIDefaultsLoader.parseValue( "dummyColor", "#f0", Color.class ) );
}
@Test
void parseInvalidBorders() {
assertThrows( new IllegalArgumentException( "invalid border '1,abc,3,4' (invalid insets '1,abc,3,4')" ), () -> UIDefaultsLoader.parseValue( "dummyBorder", "1,abc,3,4", null ) );
assertThrows( new IllegalArgumentException( "invalid border '1,2,3' (invalid insets '1,2,3')" ), () -> UIDefaultsLoader.parseValue( "dummyBorder", "1,2,3", null ) );
assertThrows( new IllegalArgumentException( "invalid border '1,2,3,,,' (invalid insets '1,2,3,,,')" ), () -> UIDefaultsLoader.parseValue( "dummyBorder", "1,2,3,,,", null ) );
assertThrows( new IllegalArgumentException( "invalid border '1,2,3,4,#f0' (invalid color '#f0')" ), () -> UIDefaultsLoader.parseValue( "dummyBorder", "1,2,3,4,#f0", null ) );
assertThrows( new IllegalArgumentException( "invalid border '1,2,3,4,#f00,2.5abc' (invalid float '2.5abc')" ), () -> UIDefaultsLoader.parseValue( "dummyBorder", "1,2,3,4,#f00,2.5abc", null ) );
assertThrows( new IllegalArgumentException( "invalid border '1,2,3,4,#f00,2.5,6abc' (invalid integer '6abc')" ), () -> UIDefaultsLoader.parseValue( "dummyBorder", "1,2,3,4,#f00,2.5,6abc", null ) );
}
@Test
void parseInvalidFonts() {
// size
assertThrows( new IllegalArgumentException( "invalid font '12abc' (invalid integer '12abc')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "12abc", null ) );
assertThrows( new IllegalArgumentException( "invalid font '+12abc' (invalid integer '+12abc')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "+12abc", null ) );
assertThrows( new IllegalArgumentException( "invalid font '+3abc' (invalid integer '+3abc')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "+3abc", null ) );
assertThrows( new IllegalArgumentException( "invalid font '-4abc' (invalid integer '-4abc')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "-4abc", null ) );
assertThrows( new IllegalArgumentException( "invalid font '150abc%' (invalid integer '150abc')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "150abc%", null ) );
assertThrows( new IllegalArgumentException( "invalid font 'bold 13abc Monospaced' (invalid integer '13abc')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "bold 13abc Monospaced", null ) );
// invalid combinations of styles
assertThrows( new IllegalArgumentException( "invalid font 'bold +italic': can not mix absolute style (e.g. 'bold') with derived style (e.g. '+italic')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "bold +italic", null ) );
assertThrows( new IllegalArgumentException( "invalid font '+bold -bold': can not use '+bold' and '-bold'" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "+bold -bold", null ) );
assertThrows( new IllegalArgumentException( "invalid font '+italic -italic': can not use '+italic' and '-italic'" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "+italic -italic", null ) );
}
private void assertThrows( Throwable expected, Executable executable ) {
Throwable actual = assertThrowsExactly( expected.getClass(), executable );
assertEquals( expected.getMessage(), actual.getMessage() );
}
//---- class TestInstance -------------------------------------------------
@SuppressWarnings( "EqualsHashCode" ) // Error Prone

View File

@@ -0,0 +1,404 @@
/*
* Copyright 2025 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.awt.Color;
import java.util.HashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JRootPane;
import javax.swing.UIManager;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatLaf;
/**
* @author Karl Tauber
*/
public class TestFlatButton
{
@BeforeAll
static void setup() {
String[] defs = {
"Button.background", "#000001",
"Button.foreground", "#000002",
"Button.focusedBackground", "#000003",
"Button.focusedForeground", "#000004",
"Button.hoverBackground", "#000005",
"Button.hoverForeground", "#000006",
"Button.pressedBackground", "#000007",
"Button.pressedForeground", "#000008",
"Button.selectedBackground", "#000009",
"Button.selectedForeground", "#00000a",
"Button.disabledBackground", "#00000b",
"Button.disabledText", "#00000c",
"Button.disabledSelectedBackground", "#00000d",
"Button.disabledSelectedForeground", "#00000e",
"Button.default.background", "#000101",
"Button.default.foreground", "#000102",
"Button.default.focusedBackground", "#000103",
"Button.default.focusedForeground", "#000104",
"Button.default.hoverBackground", "#000105",
"Button.default.hoverForeground", "#000106",
"Button.default.pressedBackground", "#000107",
"Button.default.pressedForeground", "#000108",
"Button.toolbar.hoverBackground", "#000201",
"Button.toolbar.hoverForeground", "#000202",
"Button.toolbar.pressedBackground", "#000203",
"Button.toolbar.pressedForeground", "#000204",
"Button.toolbar.selectedBackground", "#000205",
"Button.toolbar.selectedForeground", "#000206",
"Button.toolbar.disabledSelectedBackground", "#000207",
"Button.toolbar.disabledSelectedForeground", "#000208",
};
HashMap<String, String> globalExtraDefaults = new HashMap<>();
for( int i = 0; i < defs.length; i += 2 )
globalExtraDefaults.put( defs[i], defs[i+1] );
FlatLaf.setGlobalExtraDefaults( globalExtraDefaults );
TestUtils.setup( false );
}
@AfterAll
static void cleanup() {
TestUtils.cleanup();
}
@Test
void background() {
JButton b = new JButton();
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
UIManager.getColor( "Button.background" ),
UIManager.getColor( "Button.disabledBackground" ),
UIManager.getColor( "Button.focusedBackground" ),
UIManager.getColor( "Button.hoverBackground" ),
UIManager.getColor( "Button.pressedBackground" ) );
// selected
b.setSelected( true );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
UIManager.getColor( "Button.selectedBackground" ),
UIManager.getColor( "Button.disabledSelectedBackground" ),
null,
null,
UIManager.getColor( "Button.pressedBackground" ) );
b.setSelected( false );
// default
JRootPane rootPane = new JRootPane();
rootPane.getContentPane().add( b );
rootPane.setDefaultButton( b );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
UIManager.getColor( "Button.default.background" ),
UIManager.getColor( "Button.disabledBackground" ),
UIManager.getColor( "Button.default.focusedBackground" ),
UIManager.getColor( "Button.default.hoverBackground" ),
UIManager.getColor( "Button.default.pressedBackground" ) );
rootPane.getContentPane().remove( b );
}
@Test
void foreground() {
JButton b = new JButton();
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
UIManager.getColor( "Button.foreground" ),
UIManager.getColor( "Button.disabledText" ),
UIManager.getColor( "Button.focusedForeground" ),
UIManager.getColor( "Button.hoverForeground" ),
UIManager.getColor( "Button.pressedForeground" ) );
// selected
b.setSelected( true );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
UIManager.getColor( "Button.selectedForeground" ),
FlatUIUtils.getUIColor( "Button.disabledSelectedForeground", "Button.disabledText" ),
null,
null,
UIManager.getColor( "Button.pressedForeground" ) );
b.setSelected( false );
// default
JRootPane rootPane = new JRootPane();
rootPane.getContentPane().add( b );
rootPane.setDefaultButton( b );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
UIManager.getColor( "Button.default.foreground" ),
UIManager.getColor( "Button.disabledText" ),
UIManager.getColor( "Button.default.focusedForeground" ),
UIManager.getColor( "Button.default.hoverForeground" ),
UIManager.getColor( "Button.default.pressedForeground" ) );
rootPane.getContentPane().remove( b );
}
@Test
void backgroundExplicit() {
JButton b = new JButton();
Color c = new Color( 0x020001 );
b.setBackground( c );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
c,
UIManager.getColor( "Button.disabledBackground" ),
null,
UIManager.getColor( "Button.hoverBackground" ),
UIManager.getColor( "Button.pressedBackground" ) );
// selected
b.setSelected( true );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
UIManager.getColor( "Button.selectedBackground" ),
UIManager.getColor( "Button.disabledSelectedBackground" ),
null,
null,
UIManager.getColor( "Button.pressedBackground" ) );
b.setSelected( false );
// default
JRootPane rootPane = new JRootPane();
rootPane.getContentPane().add( b );
rootPane.setDefaultButton( b );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
c,
UIManager.getColor( "Button.disabledBackground" ),
null,
UIManager.getColor( "Button.default.hoverBackground" ),
UIManager.getColor( "Button.default.pressedBackground" ) );
rootPane.getContentPane().remove( b );
}
@Test
void foregroundExplicit() {
JButton b = new JButton();
Color c = new Color( 0x020001 );
b.setForeground( c );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
c,
UIManager.getColor( "Button.disabledText" ),
null,
UIManager.getColor( "Button.hoverForeground" ),
UIManager.getColor( "Button.pressedForeground" ) );
// selected
b.setSelected( true );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
c,
FlatUIUtils.getUIColor( "Button.disabledSelectedForeground", "Button.disabledText" ),
null,
null,
UIManager.getColor( "Button.pressedForeground" ) );
b.setSelected( false );
// default
JRootPane rootPane = new JRootPane();
rootPane.getContentPane().add( b );
rootPane.setDefaultButton( b );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
c,
UIManager.getColor( "Button.disabledText" ),
null,
UIManager.getColor( "Button.default.hoverForeground" ),
UIManager.getColor( "Button.default.pressedForeground" ) );
rootPane.getContentPane().remove( b );
}
@Test
void backgroundStyled() {
JButton b = new JButton();
b.putClientProperty( FlatClientProperties.STYLE,
"background: #020001;" +
"disabledBackground: #020002;" +
"focusedBackground: #020003;" +
"hoverBackground: #020004;" +
"pressedBackground: #020005;" +
"selectedBackground: #020006;" +
"disabledSelectedBackground: #020007;" );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
new Color( 0x020001 ),
new Color( 0x020002 ),
new Color( 0x020003 ),
new Color( 0x020004 ),
new Color( 0x020005 ) );
// selected
b.setSelected( true );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
new Color( 0x020006 ),
new Color( 0x020007 ),
null,
null,
new Color( 0x020005 ) );
b.setSelected( false );
Color c = new Color( 0x0a0001 );
b.setBackground( c );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
c,
new Color( 0x020002 ),
c,
new Color( 0x020004 ),
new Color( 0x020005 ) );
b.setSelected( true );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
new Color( 0x020006 ),
new Color( 0x020007 ),
null,
null,
new Color( 0x020005 ) );
b.setSelected( false );
b = new JButton();
b.putClientProperty( FlatClientProperties.STYLE,
"default.background: #020101;" +
"disabledBackground: #020102;" +
"default.focusedBackground: #020103;" +
"default.hoverBackground: #020104;" +
"default.pressedBackground: #020105;" );
// default
JRootPane rootPane = new JRootPane();
rootPane.getContentPane().add( b );
rootPane.setDefaultButton( b );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getBackground( b2 ),
new Color( 0x020101 ),
new Color( 0x020102 ),
new Color( 0x020103 ),
new Color( 0x020104 ),
new Color( 0x020105 ) );
rootPane.getContentPane().remove( b );
}
@Test
void foregroundStyled() {
JButton b = new JButton();
b.putClientProperty( FlatClientProperties.STYLE,
"foreground: #020001;" +
"disabledText: #020002;" +
"focusedForeground: #020003;" +
"hoverForeground: #020004;" +
"pressedForeground: #020005;" +
"selectedForeground: #020006;" +
"disabledSelectedForeground: #020007;" );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
new Color( 0x020001 ),
new Color( 0x020002 ),
new Color( 0x020003 ),
new Color( 0x020004 ),
new Color( 0x020005 ) );
// selected
b.setSelected( true );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
new Color( 0x020006 ),
new Color( 0x020007 ),
null,
null,
new Color( 0x020005 ) );
b.setSelected( false );
Color c = new Color( 0x0a0001 );
b.setForeground( c );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
c,
new Color( 0x020002 ),
c,
new Color( 0x020004 ),
new Color( 0x020005 ) );
b.setSelected( true );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
c,
new Color( 0x020007 ),
null,
null,
new Color( 0x020005 ) );
b.setSelected( false );
b = new JButton();
b.putClientProperty( FlatClientProperties.STYLE,
"default.foreground: #020101;" +
"disabledText: #020102;" +
"default.focusedForeground: #020103;" +
"default.hoverForeground: #020104;" +
"default.pressedForeground: #020105;" );
// default
JRootPane rootPane = new JRootPane();
rootPane.getContentPane().add( b );
rootPane.setDefaultButton( b );
testButtonColors( b, b2 -> ((FlatButtonUI)b2.getUI()).getForeground( b2 ),
new Color( 0x020101 ),
new Color( 0x020102 ),
new Color( 0x020103 ),
new Color( 0x020104 ),
new Color( 0x020105 ) );
rootPane.getContentPane().remove( b );
}
private void testButtonColors( JButton b, Function<JButton, Color> f,
Color expectedEnabled, Color expectedDisabled, Color expectedFocused,
Color expectedHover, Color expectedPressed
)
{
assertEquals( expectedEnabled, f.apply( b ) );
// disabled
b.setEnabled( false );
assertEquals( expectedDisabled, f.apply( b ) );
b.setEnabled( true );
// focused
if( expectedFocused != null ) {
b.putClientProperty( FlatClientProperties.COMPONENT_FOCUS_OWNER, (Predicate<JComponent>) c -> true );
assertEquals( expectedFocused, f.apply( b ) );
b.putClientProperty( FlatClientProperties.COMPONENT_FOCUS_OWNER, null );
}
// hover
if( expectedHover != null ) {
b.getModel().setRollover( true );
assertEquals( expectedHover, f.apply( b ) );
b.getModel().setRollover( false );
}
// pressed
if( expectedPressed != null ) {
b.getModel().setPressed( true );
assertEquals( expectedPressed, f.apply( b ) );
b.getModel().setPressed( false );
}
}
}

View File

@@ -30,6 +30,10 @@ import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import com.formdev.flatlaf.icons.*;
import com.formdev.flatlaf.ui.FlatInternalFrameUI.FlatInternalFrameBorder;
import com.formdev.flatlaf.ui.TestFlatStyling.CustomCheckBoxIcon;
import com.formdev.flatlaf.ui.TestFlatStyling.CustomIcon;
import com.formdev.flatlaf.ui.TestFlatStyling.CustomRadioButtonIcon;
/**
* @author Karl Tauber
@@ -70,6 +74,8 @@ public class TestFlatStyleableInfo
//---- FlatHelpButtonIcon ----
expectedMap( expected,
"help.scale", float.class,
"help.focusWidth", int.class,
"help.focusColor", Color.class,
"help.innerFocusWidth", float.class,
@@ -144,7 +150,20 @@ public class TestFlatStyleableInfo
@Test
void checkBox() {
JCheckBox c = new JCheckBox();
checkBox( new JCheckBox() );
}
@Test
void checkBox2() {
checkBox( new JCheckBox( new CustomIcon() ) );
}
@Test
void checkBox3() {
checkBox( new JCheckBox( new CustomCheckBoxIcon() ) );
}
private void checkBox( JCheckBox c ) {
FlatCheckBoxUI ui = (FlatCheckBoxUI) c.getUI();
assertTrue( ui.getDefaultIcon() instanceof FlatCheckBoxIcon );
@@ -153,6 +172,11 @@ public class TestFlatStyleableInfo
Map<String, Class<?>> expected = new LinkedHashMap<>();
radioButton( expected );
// remove "icon." keys if check box has custom icon
Icon icon = c.getIcon();
if( icon != null && !(icon instanceof FlatCheckBoxIcon) )
expected.keySet().removeIf( key -> key.startsWith( "icon." ) );
assertMapEquals( expected, ui.getStyleableInfos( c ) );
}
@@ -175,8 +199,9 @@ public class TestFlatStyleableInfo
"disabledForeground", Color.class,
"buttonBackground", Color.class,
"buttonFocusedBackground", Color.class,
"buttonEditableBackground", Color.class,
"buttonFocusedBackground", Color.class,
"buttonFocusedEditableBackground", Color.class,
"buttonSeparatorWidth", float.class,
"buttonSeparatorColor", Color.class,
"buttonDisabledSeparatorColor", Color.class,
@@ -270,6 +295,7 @@ public class TestFlatStyleableInfo
"selectionForeground", Color.class,
"selectionInactiveBackground", Color.class,
"selectionInactiveForeground", Color.class,
"alternateRowColor", Color.class,
"selectionInsets", Insets.class,
"selectionArc", int.class,
@@ -390,6 +416,8 @@ public class TestFlatStyleableInfo
private void menuItem_checkIcon( Map<String, Class<?>> expected ) {
expectedMap( expected,
"icon.scale", float.class,
"icon.checkmarkColor", Color.class,
"icon.disabledCheckmarkColor", Color.class,
"selectionForeground", Color.class
@@ -398,6 +426,8 @@ public class TestFlatStyleableInfo
private void menuItem_arrowIcon( Map<String, Class<?>> expected ) {
expectedMap( expected,
"icon.scale", float.class,
"icon.arrowType", String.class,
"icon.arrowColor", Color.class,
"icon.disabledArrowColor", Color.class,
@@ -433,7 +463,8 @@ public class TestFlatStyleableInfo
expectedMap( expected,
// capsLockIcon
"capsLockIconColor", Color.class
"capsLockIconColor", Color.class,
"capsLockIconScale", float.class
);
// border
@@ -491,7 +522,20 @@ public class TestFlatStyleableInfo
@Test
void radioButton() {
JRadioButton c = new JRadioButton();
radioButton( new JRadioButton() );
}
@Test
void radioButton2() {
radioButton( new JRadioButton( new CustomIcon() ) );
}
@Test
void radioButton3() {
radioButton( new JRadioButton( new CustomRadioButtonIcon() ) );
}
private void radioButton( JRadioButton c ) {
FlatRadioButtonUI ui = (FlatRadioButtonUI) c.getUI();
assertTrue( ui.getDefaultIcon() instanceof FlatRadioButtonIcon );
@@ -503,6 +547,11 @@ public class TestFlatStyleableInfo
"icon.centerDiameter", float.class
);
// remove "icon." keys if radio button has custom icon
Icon icon = c.getIcon();
if( icon != null && !(icon instanceof FlatRadioButtonIcon) )
expected.keySet().removeIf( key -> key.startsWith( "icon." ) );
assertMapEquals( expected, ui.getStyleableInfos( c ) );
}
@@ -512,11 +561,15 @@ public class TestFlatStyleableInfo
//---- icon ----
"icon.scale", float.class,
"icon.focusWidth", float.class,
"icon.focusColor", Color.class,
"icon.borderWidth", float.class,
"icon.selectedBorderWidth", float.class,
"icon.disabledSelectedBorderWidth", float.class,
"icon.indeterminateBorderWidth", float.class,
"icon.disabledIndeterminateBorderWidth", float.class,
"icon.arc", int.class,
// enabled
@@ -525,6 +578,9 @@ public class TestFlatStyleableInfo
"icon.selectedBorderColor", Color.class,
"icon.selectedBackground", Color.class,
"icon.checkmarkColor", Color.class,
"icon.indeterminateBorderColor", Color.class,
"icon.indeterminateBackground", Color.class,
"icon.indeterminateCheckmarkColor", Color.class,
// disabled
"icon.disabledBorderColor", Color.class,
@@ -532,6 +588,9 @@ public class TestFlatStyleableInfo
"icon.disabledSelectedBorderColor", Color.class,
"icon.disabledSelectedBackground", Color.class,
"icon.disabledCheckmarkColor", Color.class,
"icon.disabledIndeterminateBorderColor", Color.class,
"icon.disabledIndeterminateBackground", Color.class,
"icon.disabledIndeterminateCheckmarkColor", Color.class,
// focused
"icon.focusedBorderColor", Color.class,
@@ -539,6 +598,9 @@ public class TestFlatStyleableInfo
"icon.focusedSelectedBorderColor", Color.class,
"icon.focusedSelectedBackground", Color.class,
"icon.focusedCheckmarkColor", Color.class,
"icon.focusedIndeterminateBorderColor", Color.class,
"icon.focusedIndeterminateBackground", Color.class,
"icon.focusedIndeterminateCheckmarkColor", Color.class,
// hover
"icon.hoverBorderColor", Color.class,
@@ -546,13 +608,19 @@ public class TestFlatStyleableInfo
"icon.hoverSelectedBorderColor", Color.class,
"icon.hoverSelectedBackground", Color.class,
"icon.hoverCheckmarkColor", Color.class,
"icon.hoverIndeterminateBorderColor", Color.class,
"icon.hoverIndeterminateBackground", Color.class,
"icon.hoverIndeterminateCheckmarkColor", Color.class,
// pressed
"icon.pressedBorderColor", Color.class,
"icon.pressedBackground", Color.class,
"icon.pressedSelectedBorderColor", Color.class,
"icon.pressedSelectedBackground", Color.class,
"icon.pressedCheckmarkColor", Color.class
"icon.pressedCheckmarkColor", Color.class,
"icon.pressedIndeterminateBorderColor", Color.class,
"icon.pressedIndeterminateBackground", Color.class,
"icon.pressedIndeterminateCheckmarkColor", Color.class
);
}
@@ -773,6 +841,7 @@ public class TestFlatStyleableInfo
"tabIconPlacement", int.class,
// FlatTabbedPaneCloseIcon
"closeScale", float.class,
"closeSize", Dimension.class,
"closeArc", int.class,
"closeCrossPlainSize", float.class,
@@ -965,9 +1034,11 @@ public class TestFlatStyleableInfo
"selectionInactiveBackground", Color.class,
"selectionInactiveForeground", Color.class,
"selectionBorderColor", Color.class,
"alternateRowColor", Color.class,
"selectionInsets", Insets.class,
"selectionArc", int.class,
"wideSelection", boolean.class,
"wideCellRenderer", boolean.class,
"showCellFocusIndicator", boolean.class,
"paintSelection", boolean.class,
@@ -1060,6 +1131,8 @@ public class TestFlatStyleableInfo
"error.focusedBorderColor", Color.class,
"warning.borderColor", Color.class,
"warning.focusedBorderColor", Color.class,
"success.borderColor", Color.class,
"success.focusedBorderColor", Color.class,
"custom.borderColor", Color.class,
"outline", String.class,
@@ -1090,6 +1163,16 @@ public class TestFlatStyleableInfo
assertMapEquals( expected, border.getStyleableInfos() );
}
@Test
void flatScrollPaneBorder() {
FlatScrollPaneBorder border = new FlatScrollPaneBorder();
Map<String, Class<?>> expected = new LinkedHashMap<>();
flatScrollPaneBorder( expected );
assertMapEquals( expected, border.getStyleableInfos() );
}
@Test
void flatTextBorder() {
FlatTextBorder border = new FlatTextBorder();
@@ -1110,6 +1193,64 @@ public class TestFlatStyleableInfo
assertMapEquals( expected, border.getStyleableInfos() );
}
@Test
void flatDropShadowBorder() {
FlatDropShadowBorder border = new FlatDropShadowBorder();
Map<String, Class<?>> expected = expectedMap(
"shadowColor", Color.class,
"shadowInsets", Insets.class,
"shadowOpacity", float.class
);
assertMapEquals( expected, border.getStyleableInfos() );
}
@Test
void flatMenuBarBorder() {
FlatMenuBarBorder border = new FlatMenuBarBorder();
Map<String, Class<?>> expected = expectedMap(
"borderColor", Color.class
);
assertMapEquals( expected, border.getStyleableInfos() );
}
@Test
void flatPopupMenuBorder() {
FlatPopupMenuBorder border = new FlatPopupMenuBorder();
Map<String, Class<?>> expected = expectedMap(
"borderInsets", Insets.class,
"borderColor", Color.class
);
assertMapEquals( expected, border.getStyleableInfos() );
}
@Test
void flatInternalFrameBorder() {
FlatInternalFrameBorder border = new FlatInternalFrameBorder();
Map<String, Class<?>> expected = expectedMap(
"activeBorderColor", Color.class,
"inactiveBorderColor", Color.class,
"borderLineWidth", int.class,
"dropShadowPainted", boolean.class,
"borderMargins", Insets.class,
"activeDropShadowColor", Color.class,
"activeDropShadowInsets", Insets.class,
"activeDropShadowOpacity", float.class,
"inactiveDropShadowColor", Color.class,
"inactiveDropShadowInsets", Insets.class,
"inactiveDropShadowOpacity", float.class
);
assertMapEquals( expected, border.getStyleableInfos() );
}
//---- icons --------------------------------------------------------------
@Test
@@ -1139,11 +1280,15 @@ public class TestFlatStyleableInfo
private void flatCheckBoxIcon( Map<String, Class<?>> expected ) {
expectedMap( expected,
"scale", float.class,
"focusWidth", float.class,
"focusColor", Color.class,
"borderWidth", float.class,
"selectedBorderWidth", float.class,
"disabledSelectedBorderWidth", float.class,
"indeterminateBorderWidth", float.class,
"disabledIndeterminateBorderWidth", float.class,
"arc", int.class,
// enabled
@@ -1152,6 +1297,9 @@ public class TestFlatStyleableInfo
"selectedBorderColor", Color.class,
"selectedBackground", Color.class,
"checkmarkColor", Color.class,
"indeterminateBorderColor", Color.class,
"indeterminateBackground", Color.class,
"indeterminateCheckmarkColor", Color.class,
// disabled
"disabledBorderColor", Color.class,
@@ -1159,6 +1307,9 @@ public class TestFlatStyleableInfo
"disabledSelectedBorderColor", Color.class,
"disabledSelectedBackground", Color.class,
"disabledCheckmarkColor", Color.class,
"disabledIndeterminateBorderColor", Color.class,
"disabledIndeterminateBackground", Color.class,
"disabledIndeterminateCheckmarkColor", Color.class,
// focused
"focusedBorderColor", Color.class,
@@ -1166,6 +1317,9 @@ public class TestFlatStyleableInfo
"focusedSelectedBorderColor", Color.class,
"focusedSelectedBackground", Color.class,
"focusedCheckmarkColor", Color.class,
"focusedIndeterminateBorderColor", Color.class,
"focusedIndeterminateBackground", Color.class,
"focusedIndeterminateCheckmarkColor", Color.class,
// hover
"hoverBorderColor", Color.class,
@@ -1173,13 +1327,19 @@ public class TestFlatStyleableInfo
"hoverSelectedBorderColor", Color.class,
"hoverSelectedBackground", Color.class,
"hoverCheckmarkColor", Color.class,
"hoverIndeterminateBorderColor", Color.class,
"hoverIndeterminateBackground", Color.class,
"hoverIndeterminateCheckmarkColor", Color.class,
// pressed
"pressedBorderColor", Color.class,
"pressedBackground", Color.class,
"pressedSelectedBorderColor", Color.class,
"pressedSelectedBackground", Color.class,
"pressedCheckmarkColor", Color.class
"pressedCheckmarkColor", Color.class,
"pressedIndeterminateBorderColor", Color.class,
"pressedIndeterminateBackground", Color.class,
"pressedIndeterminateCheckmarkColor", Color.class
);
}
@@ -1206,6 +1366,8 @@ public class TestFlatStyleableInfo
private void flatCheckBoxMenuItemIcon( Map<String, Class<?>> expected ) {
expectedMap( expected,
"scale", float.class,
"checkmarkColor", Color.class,
"disabledCheckmarkColor", Color.class,
"selectionForeground", Color.class
@@ -1224,6 +1386,8 @@ public class TestFlatStyleableInfo
private void flatMenuArrowIcon( Map<String, Class<?>> expected ) {
expectedMap( expected,
"scale", float.class,
"arrowType", String.class,
"arrowColor", Color.class,
"disabledArrowColor", Color.class,
@@ -1236,6 +1400,8 @@ public class TestFlatStyleableInfo
FlatHelpButtonIcon icon = new FlatHelpButtonIcon();
Map<String, Class<?>> expected = expectedMap(
"scale", float.class,
"focusWidth", int.class,
"focusColor", Color.class,
"innerFocusWidth", float.class,
@@ -1256,4 +1422,87 @@ public class TestFlatStyleableInfo
assertMapEquals( expected, icon.getStyleableInfos() );
}
@Test
void flatClearIcon() {
FlatClearIcon icon = new FlatClearIcon();
Map<String, Class<?>> expected = expectedMap(
"clearIconScale", float.class,
"clearIconColor", Color.class,
"clearIconHoverColor", Color.class,
"clearIconPressedColor", Color.class
);
assertMapEquals( expected, icon.getStyleableInfos() );
}
@Test
void flatSearchIcon() {
FlatSearchIcon icon = new FlatSearchIcon();
Map<String, Class<?>> expected = new LinkedHashMap<>();
flatSearchIcon( expected );
assertMapEquals( expected, icon.getStyleableInfos() );
}
@Test
void flatSearchWithHistoryIcon() {
FlatSearchWithHistoryIcon icon = new FlatSearchWithHistoryIcon();
Map<String, Class<?>> expected = new LinkedHashMap<>();
flatSearchIcon( expected );
assertMapEquals( expected, icon.getStyleableInfos() );
}
private void flatSearchIcon( Map<String, Class<?>> expected ) {
expectedMap( expected,
"searchIconScale", float.class,
"searchIconColor", Color.class,
"searchIconHoverColor", Color.class,
"searchIconPressedColor", Color.class
);
}
@Test
void flatCapsLockIcon() {
FlatCapsLockIcon icon = new FlatCapsLockIcon();
Map<String, Class<?>> expected = expectedMap(
"capsLockIconScale", float.class,
"capsLockIconColor", Color.class
);
assertMapEquals( expected, icon.getStyleableInfos() );
}
@Test
void flatTabbedPaneCloseIcon() {
FlatTabbedPaneCloseIcon icon = new FlatTabbedPaneCloseIcon();
Map<String, Class<?>> expected = expectedMap(
//TODO closeScale ?
// "scale", float.class,
"closeScale", float.class,
"closeSize", Dimension.class,
"closeArc", int.class,
"closeCrossPlainSize", float.class,
"closeCrossFilledSize", float.class,
"closeCrossLineWidth", float.class,
"closeBackground", Color.class,
"closeForeground", Color.class,
"closeHoverBackground", Color.class,
"closeHoverForeground", Color.class,
"closePressedBackground", Color.class,
"closePressedForeground", Color.class
);
assertMapEquals( expected, icon.getStyleableInfos() );
}
}

View File

@@ -17,9 +17,15 @@
package com.formdev.flatlaf.ui;
import java.awt.Font;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import javax.swing.UIManager;
import org.junit.jupiter.api.Test;
import org.opentest4j.AssertionFailedError;
import com.formdev.flatlaf.FlatIntelliJLaf;
import com.formdev.flatlaf.FlatLightLaf;
@@ -57,12 +63,57 @@ public class TestUtils
public static void assertMapEquals( Map<?, ?> expected, Map<?, ?> actual ) {
if( !Objects.equals( expected, actual ) ) {
String expectedStr = String.valueOf( expected ).replace( ", ", ",\n" );
String actualStr = String.valueOf( actual ).replace( ", ", ",\n" );
String expectedStr = String.valueOf( new TreeMap<>( expected ) ).replace( ", ", ",\n" );
String actualStr = String.valueOf( new TreeMap<>( actual ) ).replace( ", ", ",\n" );
String msg = String.format( "expected: <%s> but was: <%s>", expectedStr, actualStr );
// pass expected/actual strings to exception for nice diff in IDE
throw new AssertionFailedError( msg, expectedStr, actualStr );
}
}
public static void assertSetEquals( Set<?> expected, Set<?> actual, String message ) {
if( !Objects.equals( expected, actual ) ) {
String expectedStr = String.valueOf( new TreeSet<>( expected ) ).replace( ", ", ",\n" );
String actualStr = String.valueOf( new TreeSet<>( actual ) ).replace( ", ", ",\n" );
String msg = String.format( "expected: <%s> but was: <%s>", expectedStr, actualStr );
if( message != null )
msg = message + " ==> " + msg;
// pass expected/actual strings to exception for nice diff in IDE
throw new AssertionFailedError( msg, expectedStr, actualStr );
}
}
public static void checkImplementedTests( Set<String> excludes, Class<?> baseClass, Class<?>... classes ) {
Set<String> expected = getTestMethods( baseClass );
for( Class<?> cls : classes ) {
Set<String> actual = getTestMethods( cls );
for( String methodName : expected ) {
if( !actual.contains( methodName ) && !excludes.contains( methodName ) ) {
throw new AssertionFailedError( "missing " + cls.getSimpleName() + '.' + methodName
+ "() for " + baseClass.getSimpleName() + '.' + methodName + "()" );
}
}
for( String methodName : actual ) {
if( !expected.contains( methodName ) && !excludes.contains( methodName ) ) {
throw new AssertionFailedError( "missing " + baseClass.getSimpleName() + '.' + methodName
+ "() for " + cls.getSimpleName() + '.' + methodName + "()" );
}
}
}
}
private static Set<String> getTestMethods( Class<?> cls ) {
HashSet<String> tests = new HashSet<>();
Method[] methods = cls.getDeclaredMethods();
for( Method m : methods ) {
if( m.isAnnotationPresent( Test.class ) )
tests.add( m.getName() );
}
return tests;
}
}

View File

@@ -0,0 +1,343 @@
/*
* Copyright 2025 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import java.awt.Font;
import java.util.Collections;
import java.util.Map;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.plaf.metal.MetalLookAndFeel;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import com.formdev.flatlaf.FlatDarkLaf;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.FlatLightLaf;
import com.formdev.flatlaf.FlatSystemProperties;
/**
* @author Karl Tauber
*/
public class TestUIScale
{
private static Map<String, String> FONT_EXTRA_DEFAULTS_1x = Collections.singletonMap(
"defaultFont", "{instance}java.awt.Font,Dialog,0,12" );
private static Map<String, String> FONT_EXTRA_DEFAULTS_1_5x = Collections.singletonMap(
"defaultFont", "{instance}java.awt.Font,Dialog,0,18" );
@BeforeAll
static void setup() {
UIScale.inUnitTests = true;
// disable platform specific fonts
System.setProperty( "flatlaf.uiScale.fontSizeDivider", "12" );
FlatLaf.setGlobalExtraDefaults( FONT_EXTRA_DEFAULTS_1x );
}
@AfterAll
static void cleanup() throws UnsupportedLookAndFeelException {
System.clearProperty( "flatlaf.uiScale.fontSizeDivider" );
FlatLaf.setGlobalExtraDefaults( null );
UIScale.inUnitTests = false;
}
@AfterEach
void afterEach() throws UnsupportedLookAndFeelException {
UIManager.setLookAndFeel( new MetalLookAndFeel() );
UIManager.put( "defaultFont", null );
UIManager.put( "Label.font", null );
FlatLaf.setGlobalExtraDefaults( FONT_EXTRA_DEFAULTS_1x );
UIScale.tests_uninitialize();
}
@Test
void testCustomScaleFactor() {
System.setProperty( FlatSystemProperties.UI_SCALE, "1.25x" );
assertScaleFactor( 1.25f );
System.setProperty( FlatSystemProperties.UI_SCALE, "2x" );
UIScale.tests_uninitialize();
assertScaleFactor( 2f );
System.clearProperty( FlatSystemProperties.UI_SCALE );
}
@Test
void testLabelFontScaling() {
assertInstanceOf( MetalLookAndFeel.class, UIManager.getLookAndFeel() );
testLabelFont( 8, 1f );
testLabelFont( 9, 1f );
testLabelFont( 10, 1f );
testLabelFont( 11, 1f );
testLabelFont( 12, 1f );
testLabelFont( 13, 1f );
testLabelFont( 14, 1.25f );
testLabelFont( 15, 1.25f );
testLabelFont( 16, 1.25f );
testLabelFont( 17, 1.5f );
testLabelFont( 18, 1.5f );
testLabelFont( 19, 1.5f );
testLabelFont( 20, 1.75f );
testLabelFont( 21, 1.75f );
testLabelFont( 22, 1.75f );
testLabelFont( 23, 2f );
testLabelFont( 24, 2f );
testLabelFont( 25, 2f );
testLabelFont( 26, 2.25f );
}
private void testLabelFont( int fontSize, float expectedScaleFactor ) {
UIManager.put( "Label.font", new Font( Font.DIALOG, Font.PLAIN, fontSize ) );
assertScaleFactor( expectedScaleFactor );
}
@Test
void testDefaultFontScaling() {
FlatLightLaf.setup();
testDefaultFont( 8, 1f );
testDefaultFont( 9, 1f );
testDefaultFont( 10, 1f );
testDefaultFont( 11, 1f );
testDefaultFont( 12, 1f );
testDefaultFont( 13, 1f );
testDefaultFont( 14, 1.25f );
testDefaultFont( 15, 1.25f );
testDefaultFont( 16, 1.25f );
testDefaultFont( 17, 1.5f );
testDefaultFont( 18, 1.5f );
testDefaultFont( 19, 1.5f );
testDefaultFont( 20, 1.75f );
testDefaultFont( 21, 1.75f );
testDefaultFont( 22, 1.75f );
testDefaultFont( 23, 2f );
testDefaultFont( 24, 2f );
testDefaultFont( 25, 2f );
testDefaultFont( 26, 2.25f );
}
private void testDefaultFont( int fontSize, float expectedScaleFactor ) {
UIManager.put( "defaultFont", new Font( Font.DIALOG, Font.PLAIN, fontSize ) );
assertScaleFactor( expectedScaleFactor );
}
@Test
void testInitialScaleFactorAndFontSizes() {
FlatLightLaf.setup();
assertScaleFactorAndFontSizes( 1f, 12, -1 );
FlatLaf.setGlobalExtraDefaults( FONT_EXTRA_DEFAULTS_1_5x );
FlatDarkLaf.setup();
assertScaleFactorAndFontSizes( 1.5f, 18, -1 );
}
@Test
void zoom_Metal() {
UIScale.setZoomFactor( 1.1f );
assertScaleFactor( 1.1f );
UIScale.setZoomFactor( 1.3f );
assertScaleFactor( 1.3f );
UIScale.setZoomFactor( 2.3f );
assertScaleFactor( 2.3f );
}
@Test
void zoom_1x() {
FlatLightLaf.setup();
testZoom( 0.7f, 0.7f, 8, -1 );
testZoom( 0.75f, 0.75f, 9, -1 );
testZoom( 0.8f, 0.8f, 10, -1 );
testZoom( 0.9f, 0.9f, 11, -1 );
testZoom( 1f, 1f, 12, -1 );
testZoom( 1.1f, 1.1f, 13, -1 );
testZoom( 1.2f, 1.2f, 14, -1 );
testZoom( 1.25f, 1.25f, 15, -1 );
testZoom( 1.3f, 1.3f, 16, -1 );
testZoom( 1.4f, 1.4f, 17, -1 );
testZoom( 1.5f, 1.5f, 18, -1 );
testZoom( 1.6f, 1.6f, 19, -1 );
testZoom( 1.7f, 1.7f, 20, -1 );
testZoom( 1.75f, 1.75f, 21, -1 );
testZoom( 1.8f, 1.8f, 22, -1 );
testZoom( 1.9f, 1.9f, 23, -1 );
testZoom( 2f, 2f, 24, -1 );
testZoom( 2.25f, 2.25f, 27, -1 );
testZoom( 2.5f, 2.5f, 30, -1 );
testZoom( 2.75f, 2.75f, 33, -1 );
testZoom( 3f, 3f, 36, -1 );
testZoom( 4f, 4f, 48, -1 );
}
@Test
void zoom_1_5x() {
FlatLaf.setGlobalExtraDefaults( FONT_EXTRA_DEFAULTS_1_5x );
FlatLightLaf.setup();
testZoom( 0.7f, 1.05f, 13, -1 );
testZoom( 0.75f, 1.13f, 14, -1 );
testZoom( 0.8f, 1.2f, 14, -1 );
testZoom( 0.9f, 1.35f, 16, -1 );
testZoom( 1f, 1.5f, 18, -1 );
testZoom( 1.1f, 1.65f, 20, -1 );
testZoom( 1.2f, 1.8f, 22, -1 );
testZoom( 1.25f, 1.88f, 23, -1 );
testZoom( 1.3f, 1.95f, 23, -1 );
testZoom( 1.4f, 2.1f, 25, -1 );
testZoom( 1.5f, 2.25f, 27, -1 );
testZoom( 1.6f, 2.4f, 29, -1 );
testZoom( 1.7f, 2.55f, 31, -1 );
testZoom( 1.75f, 2.63f, 32, -1 );
testZoom( 1.8f, 2.7f, 32, -1 );
testZoom( 1.9f, 2.85f, 34, -1 );
testZoom( 2f, 3f, 36, -1 );
testZoom( 2.25f, 3.38f, 41, -1 );
testZoom( 2.5f, 3.75f, 45, -1 );
testZoom( 2.75f, 4.13f, 50, -1 );
testZoom( 3f, 4.5f, 54, -1 );
testZoom( 4f, 6f, 72, -1 );
}
@Test
void zoomAppFont_1x() {
FlatLightLaf.setup();
UIManager.put( "defaultFont", new Font( Font.DIALOG, Font.PLAIN, 14 ) );
testZoom( 1f, 1.25f, 12, 14 );
testZoom( 1.1f, 1.38f, 13, 15 );
testZoom( 1.25f, 1.56f, 15, 17 );
testZoom( 1.5f, 1.88f, 18, 20 );
testZoom( 1.75f, 2.19f, 21, 23 );
testZoom( 2f, 2.5f, 24, 26 );
testZoom( 1f, 1.25f, 12, 13 );
testZoom( 2f, 2.5f, 24, 26 );
}
@Test
void zoomWithLafChange() {
FlatLightLaf.setup();
assertScaleFactorAndFontSizes( 1f, 12, -1 );
testZoom( 1.1f, 1.1f, 13, -1 );
FlatDarkLaf.setup();
assertScaleFactorAndFontSizes( 1.1f, 13, -1 );
testZoom( 1.2f, 1.2f, 14, -1 );
FlatLightLaf.setup();
assertScaleFactorAndFontSizes( 1.2f, 14, -1 );
testZoom( 1.3f, 1.3f, 16, -1 );
FlatLaf.setGlobalExtraDefaults( FONT_EXTRA_DEFAULTS_1_5x );
FlatDarkLaf.setup();
assertScaleFactorAndFontSizes( 1.95f, 23, -1 );
testZoom( 1.4f, 2.1f, 25, -1 );
FlatLightLaf.setup();
assertScaleFactorAndFontSizes( 2.1f, 25, -1 );
testZoom( 1.5f, 2.25f, 27, -1 );
}
@Test
void zoomWithDefaultFontChange() {
FlatLightLaf.setup();
assertScaleFactorAndFontSizes( 1f, 12, -1 );
float zoom1 = 1.4f;
testZoom( zoom1, zoom1, 17, -1 );
testDefaultFont( 8, z( zoom1, 1f ) );
testDefaultFont( 9, z( zoom1, 1f ) );
testDefaultFont( 10, z( zoom1, 1f ) );
testDefaultFont( 11, z( zoom1, 1f ) );
testDefaultFont( 12, z( zoom1, 1f ) );
testDefaultFont( 13, z( zoom1, 1f ) );
testDefaultFont( 14, z( zoom1, 1.25f ) );
testDefaultFont( 15, z( zoom1, 1.25f ) );
testDefaultFont( 16, z( zoom1, 1.25f ) );
testDefaultFont( 17, z( zoom1, 1.5f ) );
testDefaultFont( 18, z( zoom1, 1.5f ) );
testDefaultFont( 19, z( zoom1, 1.5f ) );
testDefaultFont( 20, z( zoom1, 1.75f ) );
testDefaultFont( 21, z( zoom1, 1.75f ) );
testDefaultFont( 22, z( zoom1, 1.75f ) );
testDefaultFont( 23, z( zoom1, 2f ) );
testDefaultFont( 24, z( zoom1, 2f ) );
testDefaultFont( 25, z( zoom1, 2f ) );
testDefaultFont( 26, z( zoom1, 2.25f ) );
float zoom2 = 1.8f;
testZoom( zoom2, 4.05f, 22, 33 );
testDefaultFont( 8, z( zoom2, 1f ) );
testDefaultFont( 9, z( zoom2, 1f ) );
testDefaultFont( 10, z( zoom2, 1f ) );
testDefaultFont( 11, z( zoom2, 1f ) );
testDefaultFont( 12, z( zoom2, 1f ) );
testDefaultFont( 13, z( zoom2, 1f ) );
testDefaultFont( 14, z( zoom2, 1.25f ) );
testDefaultFont( 15, z( zoom2, 1.25f ) );
testDefaultFont( 16, z( zoom2, 1.25f ) );
testDefaultFont( 17, z( zoom2, 1.5f ) );
testDefaultFont( 18, z( zoom2, 1.5f ) );
testDefaultFont( 19, z( zoom2, 1.5f ) );
testDefaultFont( 20, z( zoom2, 1.75f ) );
testDefaultFont( 21, z( zoom2, 1.75f ) );
testDefaultFont( 22, z( zoom2, 1.75f ) );
testDefaultFont( 23, z( zoom2, 2f ) );
testDefaultFont( 24, z( zoom2, 2f ) );
testDefaultFont( 25, z( zoom2, 2f ) );
testDefaultFont( 26, z( zoom2, 2.25f ) );
}
private static float z( float zoom, float scale ) {
// round scale factor to 1/100
return Math.round( (zoom * scale) * 100f ) / 100f;
}
private static void testZoom( float zoomFactor, float expectedScaleFactor,
int expectedLafFontSize, int expectedAppFontSize )
{
UIScale.setZoomFactor( zoomFactor );
assertScaleFactorAndFontSizes( expectedScaleFactor, expectedLafFontSize, expectedAppFontSize );
}
private static void assertScaleFactorAndFontSizes( float expectedScaleFactor,
int expectedLafFontSize, int expectedAppFontSize )
{
assertScaleFactor( expectedScaleFactor );
Font lafFont = UIManager.getLookAndFeelDefaults().getFont( "defaultFont" );
Font appFont = UIManager.getFont( "defaultFont" );
assertEquals( expectedLafFontSize, lafFont.getSize() );
if( expectedAppFontSize > 0 ) {
assertNotEquals( lafFont, appFont );
assertEquals( expectedAppFontSize, appFont.getSize() );
} else
assertEquals( lafFont, appFont );
}
private static void assertScaleFactor( float expectedScaleFactor ) {
assertEquals( expectedScaleFactor, UIScale.getUserScaleFactor() );
}
}

View File

@@ -1,4 +1,7 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.source=1.8
org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false
org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false

View File

@@ -43,12 +43,13 @@ tasks {
dependsOn( ":flatlaf-intellij-themes:jar" )
// dependsOn( ":flatlaf-natives-jna:jar" )
manifest {
attributes( "Main-Class" to "com.formdev.flatlaf.demo.FlatLafDemo" )
manifest.attributes(
"Main-Class" to "com.formdev.flatlaf.demo.FlatLafDemo",
"Multi-Release" to "true",
if( JavaVersion.current() >= JavaVersion.VERSION_1_9 )
attributes( "Multi-Release" to "true" )
}
// allow loading FlatLaf native library in Java 24+ (see https://openjdk.org/jeps/472)
"Enable-Native-Access" to "ALL-UNNAMED",
)
exclude( "module-info.class" )
exclude( "META-INF/versions/*/module-info.class" )

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