Compare commits

...

283 Commits

Author SHA1 Message Date
Karl Tauber
efd8cf8236 release 2.0-rc1 2021-12-18 14:09:39 +01:00
Karl Tauber
8dbfc6d5d6 README.md: dark screenshots re-updated for v2 (using black background to avoid that light background shines through window border) 2021-12-16 14:53:29 +01:00
Karl Tauber
ef343397d4 README.md: screenshots updated for v2 2021-12-16 11:42:25 +01:00
Karl Tauber
cae02d31db IntelliJ Themes: updated themes to newest versions (used IJThemesUpdater) 2021-12-16 00:51:03 +01:00
Karl Tauber
96c78cbc16 ComboBox: fixed wrong popup border when using in theme editor preview 2021-12-15 23:39:16 +01:00
Karl Tauber
f8c769644d SwingX: fixed NullPointerException in FlatCaret when using org.jdesktop.swingx.prompt.PromptSupport.setPrompt() on a text field and then switching theme 2021-12-15 11:37:35 +01:00
Karl Tauber
0bd1e413b0 Native window decorations: fixed UnsatisfiedLinkError on Windows 11 for ARM processors (issue #443) 2021-12-15 00:49:08 +01:00
Karl Tauber
07c9ad484a MenuBar: do not fill background if non-opaque and having custom background color (issue #409) 2021-12-15 00:29:37 +01:00
Karl Tauber
5fd5b1206e InternalFrame: fill background to avoid that parent may shine through internal frame if it contains non-opaque components (better fix for issue #274) 2021-12-15 00:27:20 +01:00
Karl Tauber
8e107647bd Merge PR #442: TextField clear button and PasswordField reveal password button 2021-12-14 11:35:44 +01:00
Karl Tauber
12b7389376 TextField: added "clear" callback 2021-12-14 11:28:03 +01:00
Karl Tauber
45332c8126 TextField: added "clear" button to theme editor find/replace and to UI defaults inspector 2021-12-14 10:42:07 +01:00
Karl Tauber
02a9d4e31d PasswordField: support "reveal" button to show password (issue #173) 2021-12-14 01:09:21 +01:00
Karl Tauber
a4377e81cb TextField: support "clear" (or "cancel") button to empty text field 2021-12-13 22:10:21 +01:00
Karl Tauber
8d2ed3faf6 Merge PR #386: TextField: leading and trailing components 2021-12-13 17:50:38 +01:00
Karl Tauber
e7dacb8fef Unit tests: (temporary) disable testing scaled UI until it is clear why it fails now on GitHub Actions (but worked yesterday) 2021-12-13 17:45:46 +01:00
Karl Tauber
60e2ffac5f Demo: added text field leading/trailing components example 2021-12-13 17:27:36 +01:00
Karl Tauber
73c37b2018 Search/clear icons: option to ignore button state (hover/pressed) for usage where button background changes color on hover/pressed 2021-12-13 00:47:23 +01:00
Karl Tauber
1b3cc223da TextField: added styles for buttons and toolbars in leading and trailing components (smaller margins/insets, better hover/pressed/selected colors) 2021-12-12 18:43:29 +01:00
Karl Tauber
51be7ad832 TextField: require JComponent for leading and trailing components (to allow setting a style class client property) 2021-12-12 00:50:56 +01:00
Karl Tauber
f93d035e4e TextField: support leading and trailing components 2021-12-11 14:29:56 +01:00
Karl Tauber
a3885d7a48 Theme Editor: Preview: minor tweeks 2021-12-11 14:13:56 +01:00
Karl Tauber
bbf2331766 ComboBox and Spinner: made buttonBackground optional 2021-12-11 12:19:10 +01:00
Karl Tauber
2164bd363b Styling: MenuItem: support styling acceleratorFont
Theme Editor:
- support font keys in auto-completion
- ignore Menu.acceleratorFont, CheckBoxMenuItem.acceleratorFont and RadioButtonMenuItem.acceleratorFont because they are never used (keep UI values for backward compatibility)
2021-12-11 11:39:31 +01:00
Karl Tauber
6205e18c45 Styling: FlatMenuItemArrowIcon no longer extends FlatMenuArrowIcon because it does not paint anything and therefore should not inherit styling properties from FlatMenuArrowIcon 2021-12-11 01:06:23 +01:00
Karl Tauber
959b3e46fa Styling: fixes and added missing @since tags 2021-12-11 00:50:05 +01:00
Karl Tauber
09d8d09aad Theme Editor: Preview: support usage of variables in styles 2021-12-10 23:14:19 +01:00
Karl Tauber
70336e31c7 Theme Editor: update RSyntaxTextArea from 3.1.3 to 3.1.4 2021-12-10 22:44:24 +01:00
Karl Tauber
600e0f3d67 Panel: support painting background with rounded corners (issue #367)
FlatLineBorder: support rounded corners
2021-12-10 22:40:17 +01:00
Karl Tauber
023e356057 MenuItem: vertically align text if icons have different widths (issue #437) 2021-12-08 11:45:17 +01:00
Karl Tauber
27786ec00a UI defaults dumps updated for commits 269eb0ba29, c9a38f0a13 and dd3ffc64b9 2021-12-08 00:53:21 +01:00
Karl Tauber
e52e72c5a8 Merge PR #415: MenuItem: paint the selected icon when the item is selected 2021-12-07 22:52:25 +01:00
Karl Tauber
802dd08ce7 MenuItem: use isArmedOrSelected() instead of MenuSelectionManager to detect selected item 2021-12-07 22:46:39 +01:00
Karl Tauber
568ec5a1a2 added Jailer version number that first uses FlatLaf; reformatting 2021-12-07 19:46:03 +01:00
Karl Tauber
035d196392 Merge PR #440: Added Jailer to Applications using FlatLaf 2021-12-07 19:45:16 +01:00
Karl Tauber
dd3ffc64b9 SwingX: improved dark/light colors "column control" icon for JXTable (issue #434) 2021-12-07 18:10:09 +01:00
Karl Tauber
c9a38f0a13 SwingX: new "column control" icon for JXTable that scales and uses antialiasing (issue #434) 2021-12-07 17:45:25 +01:00
Wisser
78461a9d5a added Jailer to Applications using FlatLaf 2021-12-07 17:44:00 +01:00
Wisser
79b8fb910a added Jailer to Applications using FlatLaf 2021-12-07 17:39:52 +01:00
Karl Tauber
405e3df1f0 Merge remote-tracking branch 'origin/develop-1.x' into main
# Conflicts:
#	CHANGELOG.md
2021-12-07 16:05:31 +01:00
Karl Tauber
f7126d154f Window decorations: left indent was lost when icon is hidden and switching Laf (regression in commit 005c9f471e6bc3ea5d708a08e8fb0b087b2c3382; PR #429) 2021-12-07 16:00:38 +01:00
Karl Tauber
d8df8c9631 release 1.6.5 2021-12-07 15:05:13 +01:00
Karl Tauber
37b35f9063 updated svgSalamander to version 1.1.3 2021-12-07 14:34:39 +01:00
Karl Tauber
f61a7288eb fixed updating (embedded) menu bar layout when window is narrow and changing TitlePane.menuBarEmbedded 2021-12-07 11:52:33 +01:00
Karl Tauber
47a1122f04 Window decorations: do not exit application with UnsatisfiedLinkError in case that FlatLaf DLL cannot be executed because of restrictions on temporary directory (issue #436) 2021-12-07 00:39:28 +01:00
Karl Tauber
e1bfabbce5 macOS: fixed NullPointerException when using AWT component java.awt.Choice (issue #439) 2021-12-06 22:45:24 +01:00
Karl Tauber
9708fec0e0 GitHub Actions: produce snapshots only on develop-* branches; change version to 1.6.5-SNAPSHOT 2021-12-06 17:27:49 +01:00
Karl Tauber
7f4efaf0a3 ComboBox: fixed occasional StackOverflowError when modifying combo box not on AWT thread (issue #432) 2021-12-06 17:15:37 +01:00
Karl Tauber
269eb0ba29 MenuItem: changed accelerator delimiter from - to + (Windows and Linux) 2021-12-04 12:15:53 +01:00
Karl Tauber
428c6b7813 Styling: comment fixes 2021-11-30 18:37:03 +01:00
Karl Tauber
db2452a4ec Styling: support Panel 2021-11-30 18:36:48 +01:00
Karl Tauber
7dac3825d7 Linux: Fixed font problems when running on Oracle Java 8 (OpenJDK 8 is not affected):
- oversized text if system font is "Inter" (issue #427)
- missing text if system font is "Cantarell" (on Fedora)
2021-11-24 10:47:58 +01:00
Karl Tauber
7c99872278 Typography:
- fixed semibold font on Ubuntu
- use font "Montserrat SemiBold" on Fedora

(PR #396)
2021-11-24 10:37:48 +01:00
Karl Tauber
64c7318cfc README.md: added PDF Studio to applications using FlatLaf (#422, #430) 2021-11-23 17:07:19 +01:00
Karl Tauber
a9d6483829 removed accidentally committed log 2021-11-23 16:46:53 +01:00
Karl Tauber
13a6b92e47 Merge PR #429: Window title bar improvements (Windows 10/11 only) 2021-11-19 18:01:34 +01:00
Karl Tauber
9ba008002b Merge PR #396: Typography 2021-11-19 14:57:36 +01:00
Karl Tauber
8914cf78a1 Typography: Theme Editor: added h1.regular, h2.regular and h3.regular to preview 2021-11-19 11:37:46 +01:00
Karl Tauber
d360375b4f Typography:
- use semibold for `h1`, `h2` and `h3`
- added `h1.regular`, `h2.regular` and `h3.regular`
2021-11-19 11:13:32 +01:00
Karl Tauber
1caab194af TabbedPane:
- added styling support for properties added in PR #343
- updated change log
2021-11-18 18:01:07 +01:00
Karl Tauber
31754eba5d Merge PR #343: New tabbed pane active tab border painting style 2021-11-18 17:27:23 +01:00
Karl Tauber
3cfa16b8b7 TabbedPane: completed review of PR #343 (active tab border painting style)
- replaced `activeTabBorder` with `tabType`
- added `TabbedPane.cardTabSelectionHeight`
2021-11-18 16:47:21 +01:00
Karl Tauber
f80d2bacf4 Typography: use light and semibold in FlatTypographyTest 2021-11-17 19:39:51 +01:00
Karl Tauber
5df3717d94 Typography: added "Material Design 3" to FlatTypographyTest 2021-11-17 00:25:38 +01:00
Karl Tauber
68897f04a2 Typography: removed thin font/style because
- there is no thin font available on Windows
- previously used "Segoe UI Light" for `thin.font` and "Segoe UI Semilight" for `light.font`, but "Segoe UI Semilight" is too close to regular font so that it is better to use "Segoe UI Light" for `light.font` and drop `thin.font`
- the usefulness of having thin font in addition to light font is low

on macOS use "HelveticaNeue-Thin" for `light.font` (instead of "HelveticaNeue-Light")
2021-11-17 00:23:54 +01:00
Karl Tauber
4cb6aeae36 OptionPane: hide window icon by default; can be shown via UI default OptionPane.showIcon = true (issue #416) 2021-11-16 21:20:01 +01:00
Karl Tauber
0a765a35bf Merge remote-tracking branch 'origin/release-1.6.4' into main
# Conflicts:
#	CHANGELOG.md
2021-11-16 15:52:15 +01:00
Karl Tauber
6c0b122fbc release 1.6.4 2021-11-16 15:37:19 +01:00
Karl Tauber
4da2bd90cb ComboBox: fixed regression in FlatLaf 1.6.3 that makes selected item invisible in popup list if DefaultListCellRenderer is used as renderer (issue #426) 2021-11-16 15:36:14 +01:00
Karl Tauber
f0275192c6 Demo: added "Options > Show window title bar icon" to menu 2021-11-16 11:08:04 +01:00
Karl Tauber
df905a1d73 Demo: made hint popups look nicer (balloon with arrow and shadow) 2021-11-16 10:49:53 +01:00
Karl Tauber
ad8ad06f44 Window decorations: FlatTitlePane: fixed size of hit test spot on right side of menu bar if it contains a glue and a stretching component (e.g. progress bar)
removed HIT_TEST_SPOT_GROW because it is no longer necessary since commit 54e6cefa67
2021-11-15 20:43:25 +01:00
Karl Tauber
d6b9e2df62 RootPane: give the root pane useful background, foreground and font
(fixes wrong background in title bar and menu bar when switching from Nimbus to FlatLaf)
2021-11-15 16:58:44 +01:00
Karl Tauber
5c9b36556f Window decorations: FlatMenuBarBorder: use same conditions for bottom separator painting as for menu bar background painting (fixes/improves previous commit) 2021-11-15 01:00:10 +01:00
Karl Tauber
80a8348a99 Window decorations:
- enabled `TitlePane.unifiedBackground` by default (because seems to be standard on Windows 11)
- no longer paint a bottom separator for the menu bar (if unified background is enabled)
2021-11-15 01:00:10 +01:00
Karl Tauber
005c9f471e Window decorations:
- option to hide window icon (via client property or UI default)
- no longer show the Java "duke/cup" icon if no window icon image is set (issue #416)
2021-11-15 01:00:10 +01:00
Karl Tauber
b40532a830 UI defaults inspector: fixed color value renderer for some 3rd party Lafs 2021-11-15 00:59:13 +01:00
Karl Tauber
fc7a4408e9 FlatWindowDecorationsTest: update decoration style radio buttons from window 2021-11-15 00:35:19 +01:00
Karl Tauber
93b5f0081d Window decorations: reduced number of FlatTitlePane.updateNativeTitleBarHeightAndHitTestSpots() invokations 2021-11-15 00:24:56 +01:00
Karl Tauber
ce049ea3ee Window decorations: fixed exception in SwingUtilities.convertPoint() when doing new JDialog( (Window) null )
regression since commits 54e6cefa67 and
fb37be5734
2021-11-15 00:20:04 +01:00
Karl Tauber
fcc39b2db5 Merge branch 'release-1.6.3' into main
# Conflicts:
#	CHANGELOG.md
#	flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTreeUI.java
2021-11-14 23:23:29 +01:00
Karl Tauber
a42c413705 Testing: added modular app 2021-11-12 17:28:04 +01:00
Karl Tauber
d59d38dc7c added method FlatLaf.registerCustomDefaultsSource(URL packageUrl) for JPMS (issue #325) 2021-11-12 17:00:26 +01:00
Karl Tauber
77582be7fd Demo: fixed exception if hint is shown and switching to a non-FlatLaf L&F 2021-11-12 11:39:33 +01:00
Karl Tauber
0cb50355b7 added hint to FlatLaf.registerCustomDefaultsSource() javadoc that the package must be opened in module-info.java (issue #325) 2021-11-12 11:16:53 +01:00
Karl Tauber
a2d66e91ff Extras: FlatSVGIcon and FlatSVGUtils: use soft cache for SVG diagrams to allow freeing memory if no longer used 2021-11-12 10:49:02 +01:00
Karl Tauber
ccdb981917 refactored private class UIDefaultsLoader.Cache to public class SoftCache and implement the Map interface 2021-11-12 10:12:34 +01:00
Karl Tauber
d80b581ace Extras: FlatSVGUtils: support loading SVG from URL (for JPMS) (issue #325) 2021-11-12 09:28:24 +01:00
Karl Tauber
53efb6711d Extras: FlatSVGIcon: support loading SVG from URL (for JPMS), URI, File or InputStream (issues #419 and #325)
also improved error handling and javadoc
2021-11-12 09:27:04 +01:00
Karl Tauber
1de6e875f9 Merge branch 'release-1.6.2' into main
# Conflicts:
#	CHANGELOG.md
2021-11-11 13:05:33 +01:00
Karl Tauber
d3355eda65 Native window decorations: fixed layout loop (issue #420) 2021-11-11 11:49:39 +01:00
Karl Tauber
fbf10e553d Native window decorations: updated DLLs (issues #397 and #407)
built by GitHub Actions:
https://github.com/JFormDesigner/FlatLaf/actions/runs/1440605430
2021-11-09 18:35:55 +01:00
Karl Tauber
fb37be5734 Native window decorations: show (system) tooltips for minimize/maximize/close buttons on Windows 10 and for minimize/close buttons on Windows 11 (issues #397 and #407) 2021-11-09 18:21:17 +01:00
Karl Tauber
54e6cefa67 Native window decorations: show Windows 11 snap layouts menu when hovering the mouse over the maximize button (issues #397 and #407) 2021-11-09 15:36:21 +01:00
Karl Tauber
33b25c1129 FileChooser: updated CHANGELOG.md for previous commit (issue #403) 2021-11-06 00:34:06 +01:00
Karl Tauber
44d8545c09 FileChooser: workaround for crash on Windows with Java 17 32bit (issue #403) 2021-11-06 00:03:17 +01:00
Karl Tauber
7a2808243c Typography: Demo: make typography/fonts visible in "screen shot" mode; hide password fields and labels 2021-11-04 14:54:01 +01:00
Karl Tauber
1be84de26b Typography: added thin/light/semibold fonts to demo 2021-11-04 12:48:46 +01:00
Karl Tauber
78e37f7ab4 Typography: since there is no thin font available on Windows, use "Segoe UI Light" (which looks thin IMHO) for thin.font and "Segoe UI Semilight" for light.font 2021-11-04 12:16:11 +01:00
Karl Tauber
e49459fd8b UIDefaultsLoader: do not add properties with empty values to UI defaults (value was an empty string) 2021-11-04 00:21:57 +01:00
Karl Tauber
52f6e7fc32 Theme Editor: Switches" preview:
- zoom 2x, 3x and 4x icons via toolbar
- hide indeterminate state for checkboxes via toolbar
2021-11-04 00:17:24 +01:00
Karl Tauber
c8ea61dc79 Merge PR #414: CheckBox and RadioButton improvements 2021-11-04 00:08:07 +01:00
Karl Tauber
16a769ea61 Linux: fixed NPE when using java.awt.TrayIcon (issue #405) 2021-11-03 23:37:37 +01:00
Emmanuel Bourg
6b880af447 MenuItem: paint the selected icon when the item is selected 2021-11-03 18:10:13 +01:00
Karl Tauber
475781db91 Extras: FlatDesktop: fixed javadoc issues 2021-11-03 15:22:41 +01:00
Karl Tauber
4dae082cd5 fixed unit tests, which fail now on GitHub Actions, but did work a few days ago... 2021-11-03 15:21:52 +01:00
Karl Tauber
57405b2f56 CheckBox and RadioButton: derive CheckBox.icon.disabledCheckmarkColor from CheckBox.icon.checkmarkColor in light themes (as in dark themes) 2021-11-03 14:29:56 +01:00
Karl Tauber
88576f68fd CheckBox and RadioButton: added RadioButton.icon.style (similar to CheckBox.icon.style) to support different styles for checkbox and radiobutton
(e.g. Material design uses filled checkboxes, but outlined radiobuttons)
2021-11-03 11:27:18 +01:00
Karl Tauber
aa4e013097 CheckBox and RadioButton: made focused icon better recognizable in **FlatLaf Light** and **Dark** themes by painting a 1px focus border; **Darcula** and **IntelliJ** themes are not changed 2021-11-03 11:15:17 +01:00
Karl Tauber
d67cfc911b CheckBox and RadioButton:
- added `CheckBox.icon.selectedBorderWidth`
- added `CheckBox.icon.disabledSelectedBorderWidth`
- added `CheckBox.icon.disabledSelectedBorderColor`
- added `CheckBox.icon.disabledSelectedBackground`
- changed `CheckBox.icon.focusWidth` from `int` `float`
2021-11-03 10:59:39 +01:00
Karl Tauber
00a3ad738f CheckBox and RadioButton: made selected icon better recognizable in **FlatLaf Light** (use blue border), **Dark** and **Darcula** (use lighter border) themes; **IntelliJ** theme is not changed 2021-10-31 18:26:31 +01:00
Karl Tauber
1d39d34d7c CheckBox and RadioButton:
- added `CheckBox.icon.hoverCheckmarkColor`
- added `CheckBox.icon.selectedHoverBorderColor`
- added `CheckBox.icon.pressedBorderColor`
- added `CheckBox.icon.selectedPressedBorderColor`
- added `CheckBox.icon.pressedCheckmarkColor`
- renamed `CheckBox.icon.selectedFocusedBorderColor` to `CheckBox.icon.focusedSelectedBorderColor`
- renamed `CheckBox.icon.selectedFocusedBackground` to `CheckBox.icon.focusedSelectedBackground`
- renamed `CheckBox.icon.selectedFocusedCheckmarkColor` to `CheckBox.icon.focusedCheckmarkColor`
- renamed `CheckBox.icon.selectedHoverBackground` to `CheckBox.icon.hoverSelectedBackground`
- renamed `CheckBox.icon.selectedPressedBackground` to `CheckBox.icon.pressedSelectedBackground`
- renamed `CheckBox.icon[filled].selectedFocusedBorderColor` to `CheckBox.icon[filled].focusedSelectedBorderColor`
- renamed `CheckBox.icon[filled].selectedFocusedBackground` to `CheckBox.icon[filled].focusedSelectedBackground`
- renamed `CheckBox.icon[filled].selectedFocusedCheckmarkColor` to `CheckBox.icon[filled].focusedCheckmarkColor`
- renamed `CheckBox.icon[filled].selectedHoverBackground` to `CheckBox.icon[filled].hoverSelectedBackground`
- renamed `CheckBox.icon[filled].selectedPressedBackground` to `CheckBox.icon[filled].pressedSelectedBackground`

(Note: this are incompatible changes!)
2021-10-31 17:39:48 +01:00
Karl Tauber
5f6013edd4 Theme Editor: Switches" preview:
- show indeterminate state for checkboxes
- removed "text" from checkboxes and radio buttons
2021-10-31 11:17:37 +01:00
Karl Tauber
3e198ecd28 JIDE: RangeSlider: support specifying width of thumb borders (fixes compile error in commit dd806144) 2021-10-30 20:37:12 +02:00
Karl Tauber
d48b98f582 ComboBox: fix NPE in CellPaddingBorder.install() (issue #408) 2021-10-30 20:25:57 +02:00
Karl Tauber
9b839231f7 ToggleButton: use UI values from Button 2021-10-30 14:04:31 +02:00
Karl Tauber
dd80614465 ComboBox, Spinner, TextField and subclasses, CheckBox, RadioButton and Slider: support specifying width of borders 2021-10-30 14:02:49 +02:00
Karl Tauber
8c2be1b406 FlatButtonBorder: removed fields that override fields from FlatBorder 2021-10-29 14:09:58 +02:00
Karl Tauber
8152b7dad6 UIDefaultsLoader: further reduced need for value type prefixes in properties files and in CSS styles 2021-10-29 13:55:28 +02:00
Karl Tauber
fb4fe175d9 UIDefaultsLoader: reduced need for {float} in properties files and in CSS styles 2021-10-29 13:33:55 +02:00
Karl Tauber
5e03eb9b51 UIDefaultsDump: removed Java 8 patch version from dump file names 2021-10-28 21:01:16 +02:00
Karl Tauber
ef25575f85 ignore internal UI keys in dumps and in UI defaults inspector 2021-10-28 20:47:47 +02:00
Karl Tauber
b77b338c7a Styling: support using variables (defined in properties files) in CSS styles 2021-10-28 19:03:13 +02:00
Karl Tauber
0e4fe4e9bb Theme Editor: support platform and light/dark specific properties in preview 2021-10-26 18:46:08 +02:00
Karl Tauber
f742f83834 Typography: added thin font/style 2021-10-26 13:16:52 +02:00
Karl Tauber
e6e4e53a73 Typography: added light and semibold font/style 2021-10-25 13:27:40 +02:00
Karl Tauber
7c594ba7a9 Typography: UIDefaultsDump: dumps updated on macOS Big Sur 2021-10-25 13:06:34 +02:00
Karl Tauber
0156a9a9d5 Merge PR #401: Text components: double/triple-click-and-drag selection improvements 2021-10-24 20:06:06 +02:00
Karl Tauber
3facbc0900 macOS: improved macOS support of Demo and Theme Editor:
- set application name that is used in screen menu bar
- enable dark window title bars if macOS is in dark mode
2021-10-24 17:05:50 +02:00
Karl Tauber
78cef1b3c7 Theme Editor:
- use class `FlatDesktop`
- hide "File > Exit" and "Help > About" on macOS
- enable macOS screen menu bar
2021-10-24 11:49:48 +02:00
Karl Tauber
d907c469ed Theme Editor: renamed class FlatThemeEditor to FlatLafThemeEditor because this name is shown in macOS screen menu bar (and to be consistent with FlatLafDemo) 2021-10-24 11:43:28 +02:00
Karl Tauber
cc238d3e34 Extras: added class FlatDesktop for easy integration into macOS screen menu (About, Preferences and Quit) when using Java 8
Demo:
- use class `FlatDesktop`
- hide "File > Exit" and "Help > About" on macOS
2021-10-24 09:44:34 +02:00
Karl Tauber
0f9b38895e FlatComponents2Test: allow enabling tree editing 2021-10-23 09:20:53 +02:00
Karl Tauber
8fa1eae352 TextComponents: triple-click-and-drag now extends selection by whole lines
triple-click-and-drag does not work in theme editor because drag is enabled, anyway a triple-click now selects the whole line before dragging starts
2021-10-22 13:14:01 +02:00
Karl Tauber
e13fb25f14 TextComponents: keep selection when switching theme 2021-10-21 22:57:28 +02:00
Karl Tauber
e36f942129 TextComponents: double-click-and-drag now extends selection by whole words 2021-10-21 13:24:07 +02:00
Karl Tauber
d34619824c Typography: support deriving font from any other font (was always from defaultFont) (issue #384) 2021-10-17 16:54:05 +02:00
Karl Tauber
80297f113f UIDefaultsLoader: detect string values, that start and end with '"', after determining value type from key to allow font values like "Roboto Mono" 2021-10-17 14:59:18 +02:00
Karl Tauber
80f51bfe1e Theme Editor: fixed StackOverflowError when setting "defaultFont" to non-font value (e.g. defaultFont = #fff) 2021-10-17 14:43:12 +02:00
Karl Tauber
f8b9f4c1fa Table: do not select text in cell editor when it gets focus (when JTable.surrendersFocusOnKeystroke is true) and TextComponent.selectAllOnFocusPolicy is once (the default) or always (issue #395) 2021-10-16 23:32:08 +02:00
Karl Tauber
587f431ef4 Typography: added FlatFontsTest to quickly view all available fonts on the current system 2021-10-16 18:56:53 +02:00
Karl Tauber
65a4f66d2c Merge branch 'release-1.6.1' into main
# Conflicts:
#	CHANGELOG.md
2021-10-14 22:50:44 +02:00
Karl Tauber
e560f9cbd6 Typography: added typography/fonts preview to theme editor 2021-10-13 15:47:55 +02:00
Karl Tauber
80235d53f4 Typography: added FlatTypographyTest, which was used to compare various typography systems 2021-10-13 14:29:57 +02:00
Karl Tauber
892b9a732e Typography: added typography/fonts to demo 2021-10-13 14:17:40 +02:00
Karl Tauber
d8a0a015e4 Typography: Extras: add labelType property to FlatLabel 2021-10-13 13:48:03 +02:00
Karl Tauber
e60e3b9fae Typography: added monospaced font/style 2021-10-13 13:46:43 +02:00
Karl Tauber
6715f01b8c Typography: use typography styles in demo, theme editor, etc 2021-10-13 11:48:28 +02:00
Karl Tauber
465af9bc41 Typography: added fonts/styles for headings and various text sizes 2021-10-13 10:53:37 +02:00
Karl Tauber
d10bcfc72f Theme Editor: fixed StackOverflowError when adding "defaultFont" key to properties file 2021-10-12 23:50:45 +02:00
Karl Tauber
942e5b9cd1 IntelliJ Themes: do not ignore UI keys prefixed with [style] to allow using styles 2021-10-12 22:29:38 +02:00
Karl Tauber
51a90d32f8 support defining "defaultFont" in FlatLaf properties files (issue #384) 2021-10-12 11:13:40 +02:00
Karl Tauber
ac46632e73 UIDefaultsLoader: do not detect string values that start and end with '"', but also contain ", as string (e.g. font value "Roboto Mono", "Ubuntu Mono") 2021-10-10 17:59:00 +02:00
Karl Tauber
1192bef1ae Styling: use cache for parsed fonts, which is mostly used for fonts in CSS styles (e.g. font: bold +4) (issue 384) 2021-10-08 22:08:37 +02:00
Karl Tauber
b9ec382589 support defining fonts in FlatLaf properties files (issue #384) 2021-10-08 20:23:54 +02:00
Karl Tauber
5ecf19ef4f Styling: added styling properties that are likewise to client properties
(e.g. `buttonType: help` on `JButton` does the same as setting client property `JButton.buttonType` to `help`)
2021-10-07 14:22:47 +02:00
Karl Tauber
9636809b4d CHANGELOG.md: added PR #388 (style classes) 2021-10-06 12:25:17 +02:00
Karl Tauber
ba1c1ed952 ToolBar: added arrowKeysOnlyNavigation to unit tests (issue #346) 2021-10-06 00:04:49 +02:00
Karl Tauber
7452390614 ToolBar: support non-button components in arrow-keys-only navigation (issue #346) 2021-10-05 23:11:53 +02:00
Karl Tauber
69042e42b7 ToolBar: support arrow-keys-only navigation within focusable buttons of toolbar:
- arrow keys move focus within toolbar (provided by `BasicToolBarUI`)
- tab-key moves focus out of toolbar
- if moving focus into the toolbar, focus recently focused toolbar button

(issue #346)
2021-10-05 16:36:50 +02:00
Karl Tauber
1e93deab2a ToolBar: fixed focus moving to next button when switching Laf and focusable buttons are enabled via CSS style focusableButtons: true
also avoid unnecessary invokation of `c.setFocusable()`
2021-10-05 15:34:23 +02:00
Karl Tauber
16ea809bb3 ToolBar: skip components with empty input map (e.g. JLabel) when using arrow keys to navigate in focusable buttons (related to issue #346) 2021-10-05 12:15:51 +02:00
Karl Tauber
78aa4343b7 ColorFunctions: added lighten(), darken(), saturate(), desaturate() and spin() 2021-10-04 22:56:10 +02:00
Karl Tauber
6815109e15 ColorFunctions: moved methods applyFunctions() and clamp() down to nested classes 2021-10-04 22:41:32 +02:00
Karl Tauber
e34fbcec58 ToolBar: foolbars are no longer floatable by default 2021-10-04 15:31:55 +02:00
Karl Tauber
bb2a21270b Theme Editor: added "Pick Color from Screen" action to "Edit" menu that allows picking a color from anywhere on screen and insert/change it at caret position 2021-10-04 12:44:03 +02:00
Karl Tauber
49b9ec9025 Theming improvements: updated CHANGELOG.md 2021-10-04 12:00:29 +02:00
Karl Tauber
a2e896e102 Merge PR #390: Theming improvements 2021-10-04 11:55:18 +02:00
Karl Tauber
2e1ef647a9 Theming improvements:
- renamed `MenuItemCheckBox.icon.checkmarkColor` to `CheckBoxMenuItem.icon.checkmarkColor`
- renamed `MenuItemCheckBox.icon.disabledCheckmarkColor` to `CheckBoxMenuItem.icon.disabledCheckmarkColor`

(Note: this are incompatible changes!)
2021-10-03 23:28:53 +02:00
Karl Tauber
f0c314df80 Theming improvements:
- renamed `@disabledText` to `@disabledForeground`
- renamed `@textComponentBackground` to `@componentBackground`
- renamed `@textComponentMargin` to `@componentMargin`
- added `@disabledBackground`

(Note: this are incompatible changes!)
2021-10-03 23:15:51 +02:00
Karl Tauber
4db39828ef Theming improvements: reordered variables and added comments 2021-10-02 00:53:54 +02:00
Karl Tauber
b2d40825ac Theming improvements:
- reworked core themes
- reduced explicit colors by using color functions
- made it easier to create new themes
- avoid using colors from one component type for another component type
  (except when useful; e.g. `Separator.foreground` is also used for other separators)
- HelpButton: use colors from Button (instead of from CheckBox.icon)
- ToolBar: improved "docking" and "floating" colors
2021-10-01 23:48:46 +02:00
Karl Tauber
6df5d3334e Styling: test if() and contrast() functions in unit tests 2021-10-01 15:08:17 +02:00
Karl Tauber
0e982df90c Styling:
- check for duplicate keys in StyleableInfos to find "overlapping" fields/properties (e.g. `borderColor` in `FlatBorder` and in `FlatComboBoxUI`)
- Button and ToggleButton: fixed styling `toolbar.spacingInsets`
2021-10-01 13:49:38 +02:00
Karl Tauber
3834d93c9d ComboBox and Spinner:
- added `buttonSeparatorColor` and `buttonDisabledSeparatorColor`
- fixed styling of `borderColor` and `disabledBorderColor`
2021-10-01 13:43:24 +02:00
Karl Tauber
16920a5b82 Styling: added slider properties to tests 2021-09-30 11:39:02 +02:00
Karl Tauber
d66e35fdde Styling: support enums 2021-09-30 10:40:43 +02:00
Karl Tauber
d93dde0a03 StringUtils:
- use new StringUtils.split(..., boolean trim, boolean excludeEmpty) where possible
- added substringTrimmed() and isTrimmedEmpty() to avoid temporary string  allocation
2021-09-30 01:14:45 +02:00
Karl Tauber
2d232124dd Merge PR #388: Styling: basic support for "classes" (similar to CSS classes) 2021-09-30 00:06:34 +02:00
Karl Tauber
ac6702fcf7 Styling: Extras: add styleClass getter and setter to component classes 2021-09-29 23:56:41 +02:00
Karl Tauber
c4b016c9c8 Styling: support specifying multiple style classes in single string 2021-09-29 23:49:40 +02:00
Karl Tauber
6baa583a28 ColorFunctions: improved performance of mix(), tint() and shade() color functions
(used UIDefaultsDump to verify whether results are the same as before)
2021-09-28 19:48:54 +02:00
Karl Tauber
82df2ecfa9 ComboBox: paint focus border if combobox component itself is focused (instead of internal text field) or if client property JComponent.focusOwner is set
Theme Editor:
- do not set client property `JComponent.focusOwner` on internal components of combobox and spinner
- repaint preview on window activation (necessary because if something changed in editor and switching to another app, the editor is saved and the preview is updated while the editor window is not-active, which hides all focus indicators)
2021-09-28 19:34:53 +02:00
Karl Tauber
06b3de720a Merge PR #375: Accent colors
# Conflicts:
#	flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java
#	flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java
2021-09-28 15:13:25 +02:00
Karl Tauber
b0edd5659f Accent color: made accent focus border colors lighter/darker in IntelliJ/Darcula themes
(issue #233)
2021-09-28 14:18:34 +02:00
Karl Tauber
bb5c2eea10 Accent color:
- added `Component.accentColor`
- dark themes: changed threshold for contrast() from 39% down to 25% for better readability of text
- Demo: added "Default" accent color and changed "Blue" to lighter color

(issue #233)
2021-09-28 11:12:17 +02:00
Karl Tauber
e31e4dfe3a Accent color: avoid that @accentXYZ variables depend on other @accentXYZ variables to allow independent modification
(issue #233)
2021-09-27 12:31:49 +02:00
Karl Tauber
caf2cd8487 Accent color: fixed text colors if using light accent color
(issue #233)
2021-09-27 12:24:45 +02:00
Karl Tauber
15c6f11a5e Accent color:
- introduced @accentBaseColor variable that is now used as base for accent colors in Light/Dark/IntelliJ/Darcula themes, which use variations of the accent color
- @accentColor is now `null` by default, but if set to a color, then it is used unmodified for all accents

(issue #233)
2021-09-26 23:56:56 +02:00
Karl Tauber
a4ea88f4be UIDefaultsLoader: added if() function (inspired by Less CSS) 2021-09-26 23:54:06 +02:00
Karl Tauber
36d5747fbf Accent color: changed javadoc since version from 1.6 to 2 2021-09-25 23:53:57 +02:00
Karl Tauber
3d8c535ffa Styling: catch runtime exceptions while applying styles (and log them) to avoid that wrong/invalid styles could result in "damaged" UI 2021-09-25 19:34:46 +02:00
Karl Tauber
1c067d0284 behaviour --> behavior 2021-09-25 18:32:11 +02:00
Karl Tauber
b6be0462a5 Styling: basic support for "classes" (similar to CSS classes) using client property FlatLaf.styleClass 2021-09-25 14:34:21 +02:00
Karl Tauber
cce91ea16d changed multi-line javadoc since tags to single-line 2021-09-25 13:27:26 +02:00
Karl Tauber
d756041b06 Styling: fixed "Illegal reflective access" warning on stdout for BasicMenuItemUI fields when running on Java 9+ 2021-09-22 23:40:10 +02:00
Karl Tauber
2d0eb0a05b Styling: fixed build error on GitHub Actions 2021-09-22 23:15:00 +02:00
Karl Tauber
02f3239669 ComboBox (not editable): fixed background painted outside of border if round edges are enabled (similar to issue #382; regression since fixing #330 in FlatLaf 1.4) 2021-09-16 22:55:05 +02:00
Karl Tauber
14a9240c45 FlatUIUtils: joined the 3 component painting methods (for focus border, border and background) into a single method paintOutlinedComponent()
- this allows optimized painting if focus color and border color are equal
- avoids duplicate code
- support focusWidthFraction for future animations
2021-09-16 18:09:32 +02:00
Karl Tauber
c659638fb4 Styling: support styling for recently merged PR #378 2021-09-15 23:43:41 +02:00
Karl Tauber
fd15b63044 Merge PR #378: TextField: leading and trailing icons
# Conflicts:
#	flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPasswordFieldUI.java
#	flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextFieldUI.java
2021-09-15 23:32:57 +02:00
Karl Tauber
263e6c34b5 Merge PR #341: Styling individual components 2021-09-15 20:00:06 +02:00
Karl Tauber
eb62a3dc17 UI defaults inspector: avoid NPE if DerivedColorKeys.properties missing 2021-09-15 19:44:09 +02:00
Karl Tauber
161ee090a8 Tree: Fixed editing cell issue with custom cell renderer and cell editor that use same component for rendering and editing (fixes #385) 2021-09-15 19:39:44 +02:00
Karl Tauber
560ec437b9 Styling: avoid duplicate applying styles to buttons, labels and separators (which use shared UI delegates) 2021-09-15 10:57:52 +02:00
Karl Tauber
ccd0597b35 Styling: support styling for recently merged changes 2021-09-14 22:43:44 +02:00
Karl Tauber
c5c0a3768a Merge remote-tracking branch 'origin/main' into styling 2021-09-14 19:02:21 +02:00
Karl Tauber
5aa2d24d58 added sigtest to flatlaf-core subproject to check for incompatible API changes in packages com.formdev.flatlaf and com.formdev.flatlaf.util
added FlatLaf 1.6 API signature (generated in clean workspace with gradle task `sigtestGenerate`)
2021-09-14 18:14:21 +02:00
Karl Tauber
e0dddfceba Styling: Menu: support top-level underline selection 2021-09-08 14:55:41 +02:00
Karl Tauber
08ca2aa266 Styling:
- support references in color functions
- added test for using color functions in styling
2021-09-06 22:53:04 +02:00
Karl Tauber
fe15758e59 Styling: updated "since" javadoc tags 2021-09-06 15:39:19 +02:00
Karl Tauber
674efae184 Styling: Extras: add style getters and setters to component classes 2021-09-06 15:23:15 +02:00
Karl Tauber
4a65bc88d5 Theme Editor: highlight selected editor tab 2021-09-05 23:25:16 +02:00
Karl Tauber
a8f3d59729 Merge remote-tracking branch 'origin/main' into styling
# Conflicts:
#	flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatListUI.java
#	flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableUI.java
2021-09-05 23:12:38 +02:00
Karl Tauber
a2c0df5891 TextField: consider widths of leading and trailing icons for minimum/preferred text field size 2021-09-03 11:01:44 +02:00
Karl Tauber
dc33c26960 TextField: support leading and trailing icons (issue #368) 2021-09-02 17:45:33 +02:00
Karl Tauber
cdbdccf1ad Styling: support styling any component property that has public getter and setter methods 2021-09-01 13:32:31 +02:00
Karl Tauber
397c369114 Styling: renamed class FlatStyleSupport to FlatStylingSupport 2021-09-01 00:21:47 +02:00
Karl Tauber
6f9bbb184a Styling: support specifying explicit value type for parsing CSS values (for future use) 2021-08-31 23:53:12 +02:00
Karl Tauber
b12c818862 Styling: support styling for recently merged changes 2021-08-31 16:12:03 +02:00
Karl Tauber
9118dcf925 Merge remote-tracking branch 'origin/main' into styling
# Conflicts:
#	flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatButtonUI.java
#	flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatInternalFrameUI.java
#	flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextFieldUI.java
2021-08-31 15:41:46 +02:00
Karl Tauber
d333d0c9e4 Accent color:
- Demo: added accent color switching to toolbar
- added `FlatLaf.setGlobalExtraDefaults()` for easy setting accent color at runtime

(issue #233)
2021-08-31 14:06:41 +02:00
Karl Tauber
7f9cf6f45c UIDefaultsLoader: added contrast() color function (inspired by Less CSS), which is useful to choose a foreground color that is readable, based on the luma (perceptual brightness) of a background color 2021-08-30 23:58:37 +02:00
Karl Tauber
9b465cb550 Accent color: added FlatLaf.setExtraDefaults() for easy setting accent color at runtime (issue #233) 2021-08-30 23:06:06 +02:00
Karl Tauber
9144b7206e Accent color:
- added @accentXYZ variables that define all blueish accent colors
- all blueish accent colors are calculated based on @accentColor

(issue #233)
2021-08-30 23:06:06 +02:00
Karl Tauber
dd14843f2e Accent color: reduced number of individual blueish accent colors with color functions (issue #233) 2021-08-30 23:06:06 +02:00
Karl Tauber
299bd67151 Styling: support PopupMenu 2021-07-23 08:54:50 +02:00
Karl Tauber
4d4bb3fd7f Styling: added StyleableUI.getStyleableInfos() for tooling (e.g. GUI builder) 2021-07-21 11:51:19 +02:00
Karl Tauber
7fd64a1b73 Styling: support InternalFrame 2021-07-20 08:57:21 +02:00
Karl Tauber
e3e8765b91 Styling: support MenuBar 2021-07-19 14:39:57 +02:00
Karl Tauber
435cf05f9f TabbedPane: reviewed PR #343 (active tab border painting style)
- moved focus listener to class `Handler`
- instead of repainting content border in `repaintContentBorder()`, increase size of repaint region in `repaintTab()` to include part of content border
- simplified `paintContentBorder()` by using `getTabBounds()` and `Rectangle2D.intersect()` to compute gap rectangle
- slightly make code smaller
- minor formatting, renaming, ...
2021-07-12 13:37:24 +02:00
Karl Tauber
943dfe0619 Styling: support styling for recently merged changes 2021-07-11 01:41:52 +02:00
Karl Tauber
be7114d3e6 Merge remote-tracking branch 'origin/main' into styling
# Conflicts:
#	flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatButtonBorder.java
#	flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatToolBarUI.java
2021-07-11 01:26:11 +02:00
Karl Tauber
713a01bfa9 Styling: set "shared" flag to true when shared icon is assigned 2021-07-05 23:12:45 +02:00
Karl Tauber
ac291b688d Styling: fixed detection of value type if key prefix ix given (e.g. [light]padding) 2021-07-05 22:40:55 +02:00
Karl Tauber
84f7e244f2 Styling:
- support ComboBox.padding
- fixed updating of Spinner.padding
2021-07-05 22:36:57 +02:00
Karl Tauber
4a8207f367 Styling: use TestUtils.setup() in unit tests; 2021-07-05 21:58:48 +02:00
Karl Tauber
9cfd4d27e9 Styling: renamed unit tests (so that all unit test classes start with Test) 2021-07-05 21:48:41 +02:00
Karl Tauber
1b23cfd747 Merge remote-tracking branch 'origin/main' into styling
# Conflicts:
#	flatlaf-core/build.gradle.kts
#	flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatComboBoxUI.java
#	flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPasswordFieldUI.java
2021-07-05 21:38:07 +02:00
Karl Tauber
5801bf3bdf Styling: use FlatLightLaf in unit tests and get UI delegates from components 2021-07-04 10:43:08 +02:00
Karl Tauber
2b1c55ee67 Styling: support TabbedPane 2021-06-27 17:12:46 +02:00
Karl Tauber
925ddaa63a Styling: update MigLayout visual padding 2021-06-25 21:40:19 +02:00
Karl Tauber
2b60b18d47 Styling: support ScrollPane 2021-06-25 21:39:52 +02:00
Karl Tauber
d502406fa2 Styling: support borders of ComboBox and Spinner
reduced code size of Button, TextField and PasswordField
2021-06-25 21:38:39 +02:00
Karl Tauber
afdbf711f7 Styling: support Help button 2021-06-25 14:27:44 +02:00
Karl Tauber
b4f7b1d71d Styling: support ToolBar and ToolBar.Separator 2021-06-24 20:06:00 +02:00
Karl Tauber
69061cd41c Styling: support TableHeader 2021-06-24 17:57:12 +02:00
Karl Tauber
8ba7f7f961 Styling: reduce duplicate code in list and table cell borders 2021-06-24 17:53:19 +02:00
Karl Tauber
5e5aa17e14 Styling: add property change listener only for FlatLaf.style (where possible) 2021-06-24 14:05:24 +02:00
Karl Tauber
551f5fc929 Styling: support Label 2021-06-24 14:02:32 +02:00
Karl Tauber
4e7b0d11d0 Styling: support Tree icons 2021-06-24 13:43:19 +02:00
Karl Tauber
06bc53692a Styling: support cell borders of List and Table 2021-06-24 10:45:48 +02:00
Karl Tauber
007ee38cb4 Styling: support List, Table and Tree 2021-06-22 17:59:55 +02:00
Karl Tauber
82192bef91 Styling: clear field oldStyleValues on UI delegate uninstall 2021-06-21 21:06:39 +02:00
Karl Tauber
0c51dfe19c Styling: support ComboBox 2021-06-21 20:58:04 +02:00
Karl Tauber
24a9fa1ccc Styling: renamed "update" methods 2021-06-21 20:49:31 +02:00
Karl Tauber
14b06507cb Styling: support Spinner 2021-06-21 19:45:42 +02:00
Karl Tauber
b46233087b Styling: use FlatStyleSupport.createPropertyChangeListener() where possible/useful 2021-06-21 17:27:46 +02:00
Karl Tauber
28fb2e2a08 Styling: support Menu, MenuItem, CheckBoxMenuItem and RadioButtonMenuItem 2021-06-21 17:24:45 +02:00
DUDSS
943e211cf1 TabbedPane: ActiveTabBorder: Added handling for thick content separators and 0 height selection indicator. Fixed scaling issues (presumably). 2021-06-20 14:01:01 +02:00
DUDSS
ad0a13004e TabbedPane: Changed name in demo and added separator repaint on focus gained. 2021-06-20 02:56:19 +02:00
DUDSS
04bb6a5275 TabbedPane: Adjustements 2021-06-20 02:14:31 +02:00
DUDSS
d3c917eac1 TabbedPane: Added the new style option to the flatlaf demo. 2021-06-20 02:14:27 +02:00
DUDSS
4c13271a5b TabbedPane: Added activeTabBorder option to tabbed panes. Changes the active tab design to more closely resemble classic tabbed pane designs. 2021-06-20 02:13:54 +02:00
Karl Tauber
20027c2db7 Styling: support platform and light/dark theme specific styling with key prefixes [win], [mac], [linux], [light] and [dark]
e.g. `mySlider.putClientProperty( "FlatLaf.style", "[light]trackColor: #00f; [dark]trackColor: #f00" );`
2021-06-19 23:24:04 +02:00
Karl Tauber
6affc70a66 Styling: support Button and ToggleButton (including border) 2021-06-19 22:31:21 +02:00
Karl Tauber
ab4c9bdeda Styling: renamed client property JComponent.style to FlatLaf.style 2021-06-19 11:16:14 +02:00
Karl Tauber
b4a9c9b7f5 Styling: support styling disabledBackground and inactiveBackground of text components 2021-06-19 11:11:57 +02:00
Karl Tauber
5e20d50abf Styling: support TextArea, TextPane and EditorPane 2021-06-18 14:21:17 +02:00
Karl Tauber
53abbbbe56 Styling: support TextField, FormattedTextField and PasswordField 2021-06-18 13:22:19 +02:00
Karl Tauber
1938cb586d Styling: support SplitPane 2021-06-17 20:59:09 +02:00
Karl Tauber
50490ece84 Styling: support Separator and PopupMenu.Separator 2021-06-17 15:21:19 +02:00
Karl Tauber
f291cc2bd3 Styling: support ProgressBar 2021-06-17 14:57:10 +02:00
Karl Tauber
2542c8bd53 Styling: support ScrollBar 2021-06-17 13:56:38 +02:00
Karl Tauber
b457fd634e Styling: (try to) fix errors on GitHub Actions 2021-06-16 22:59:00 +02:00
Karl Tauber
041fd0e0cd Styling: fixed javadoc error 2021-06-16 22:57:56 +02:00
Karl Tauber
a983edde1e Styling: support CheckBox and RadioButton icons 2021-06-16 22:31:56 +02:00
Karl Tauber
7eb642dd13 Styling: added simple unit tests 2021-06-16 21:28:51 +02:00
Karl Tauber
e0bc93371e Styling: use annotation on fields to apply style properties (to avoid boilerplate code) 2021-06-16 21:21:33 +02:00
Karl Tauber
db56486506 Styling: support CheckBox and RadioButton (without icons) 2021-06-16 12:07:13 +02:00
Karl Tauber
c99be13697 Styling: support using java.util.Map as style
e.g. `mySlider.putClientProperty( "JComponent.style", Collections.singletonMap( "thumbSize", new Dimension( 8, 24 ) ) );`
2021-06-15 23:23:11 +02:00
Karl Tauber
0830c78728 Styling: support using simple references to UI defaults
e.g. `mySlider.putClientProperty( "JComponent.style", "thumbColor: $TextField.background; thumbBorderColor: $Component.borderColor" );`
2021-06-15 23:20:33 +02:00
Karl Tauber
edade93054 Styling: basic implementation of styling support using client property JComponent.style and CSS syntax
only for JSlider (at the moment)

e.g. `mySlider.putClientProperty( "JComponent.style", "trackValueColor: #00f; trackColor: #f00; thumbColor: #0f0; trackWidth: 6; thumbSize: 40,20; focusWidth: 20" );`

(issues #117 and #340)
2021-06-15 14:35:26 +02:00
335 changed files with 52414 additions and 27278 deletions

View File

@@ -73,7 +73,7 @@ jobs:
needs: build
if: |
github.event_name == 'push' &&
github.ref == 'refs/heads/main' &&
(github.ref == 'refs/heads/main' || startsWith( github.ref, 'refs/heads/develop-' )) &&
github.repository == 'JFormDesigner/FlatLaf'
steps:

View File

@@ -1,6 +1,136 @@
FlatLaf Change Log
==================
## 2.0-rc1
#### New features and improvements
- Styling:
- Styling individual components using string in CSS syntax or `java.util.Map`.
(PR #341)\
E.g.: `mySlider.putClientProperty( "FlatLaf.style", "trackWidth: 2" );`
- Style classes allow defining style rules at a single place (in UI defaults)
and use them in any component. (PR #388)\
E.g.: `mySlider.putClientProperty( "FlatLaf.styleClass", "myclass" );`
- Typography defines several font styles for headers and various text sizes,
which makes it easy to use consistent font styles across the application. (PR
#396)
- Native window decorations (Windows 10/11 only):
- Unified backgrounds for window title bar is now enabled by default (window
title bar has now same background color as window content). Bottom separator
for menu bars is no longer painted (if unified background is enabled).
- Show Windows 11 snap layouts menu when hovering the mouse over the maximize
button. (issues #397 and #407)
- Possibility to hide window title bar icon (for single window set client
property `JRootPane.titleBarShowIcon` to `false`; for all windows set UI
value `TitlePane.showIcon` to `false`).
- OptionPane: Hide window title bar icon by default. Can be be made visibly by
setting UI default `OptionPane.showIcon` to `true`. (issue #416)
- No longer show the Java "duke/cup" icon if no window icon image is set.
(issue #416)
- TextField, FormattedTextField and PasswordField:
- Support leading and trailing icons (set client property
`JTextField.leadingIcon` or `JTextField.trailingIcon` to a
`javax.swing.Icon`). (PR #378; issue #368)
- Support leading and trailing components (set client property
`JTextField.leadingComponent` or `JTextField.trailingComponent` to a
`java.awt.Component`). (PR #386)
- Support "clear" (or "cancel") button to empty text field. Only shown if text
field is not empty, editable and enabled. (set client property
`JTextField.showClearButton` to `true`). (PR #442)
- PasswordField: Support reveal (or "eye") button to show password. (see UI
value `PasswordField.showRevealButton`) (PR #442; issue #173)
- TextComponents: Double/triple-click-and-drag now extends selection by whole
words/lines.
- Theming improvements: Reworks core themes to make it easier to create new
themes (e.g. reduced explicit colors by using color functions). **Note**:
There are minor incompatible changes in FlatLaf properties files. (PR #390)
- ToolBar:
- Toolbars are no longer floatable by default (dots on left side of toolbar
that allows dragging toolbar). Use `UIManager.put( "ToolBar.floatable", true
)` if you want the old behavior.
- Skip components with empty input map (e.g. `JLabel`) when using arrow keys
to navigate in focusable buttons (if UI value `ToolBar.focusableButtons` is
`true`).
- Support arrow-keys-only navigation within focusable buttons of toolbar (if
UI value `ToolBar.focusableButtons` is `true`):
- arrow keys move focus within toolbar
- tab-key moves focus out of toolbar
- if moving focus into the toolbar, focus recently focused toolbar button
- ComboBox, Spinner, TextField and subclasses: Support specifying width of
border (see UI value `Component.borderWidth`).
- CheckBox and RadioButton:
- Made selected icon better recognizable in **FlatLaf Light** (use blue
border), **Dark** and **Darcula** (use lighter border) themes. **IntelliJ**
theme is not changed.
- Support specifying width of icon border (see UI value
`CheckBox.icon.borderWidth`).
- Reworked icon UI defaults and added missing ones. **Note**: There are minor
incompatible changes in FlatLaf properties files.
- Slider: Support specifying width of thumb border (see UI value
`Slider.thumbBorderWidth`).
- TabbedPane: Optionally paint selected tab as card. (PR #343)
- MenuItem:
- Paint the selected icon when the item is selected. (PR #415)
- Vertically align text if icons have different widths. (issue #437)
- Panel: Support painting background with rounded corners. (issue #367)
- Added more color functions to class `ColorFunctions` for easy use in
applications: `lighten()`, `darken()`, `saturate()`, `desaturate()`, `spin()`,
`tint()`, `shade()` and `luma()`.
- Support defining fonts in FlatLaf properties files. (issue #384)
- Added method `FlatLaf.registerCustomDefaultsSource(URL packageUrl)` for JPMS.
(issue #325)
- Extras:
- Added class `FlatDesktop` for easy integration into macOS screen menu
(About, Preferences and Quit) when using Java 8.
- `FlatSVGIcon`: Support loading SVG from `URL` (for JPMS), `URI`, `File` or
`InputStream`. (issues #419 and #325)
- `FlatSVGUtils`: Support loading SVG from `URL` (for JPMS). (issue #325)
- SwingX:
- New "column control" icon for `JXTable` that scales and uses antialiasing.
(issue #434)
#### Fixed bugs
- Native window decorations: Fixed `UnsatisfiedLinkError` on Windows 11 for ARM
processors. (issue #443)
- MenuBar: Do not fill background if non-opaque and having custom background
color. (issue #409)
- InternalFrame: Fill background to avoid that parent may shine through internal
frame if it contains non-opaque components. (better fix for issue #274)
- SwingX: Fixed `NullPointerException` in `FlatCaret` when using
`org.jdesktop.swingx.prompt.PromptSupport.setPrompt()` on a text field and
then switching theme.
## 1.6.5
#### Fixed bugs
- Linux: Fixed font problems when running on Oracle Java (OpenJDK is not
affected):
- oversized text if system font is "Inter" (issue #427)
- missing text if system font is "Cantarell" (on Fedora)
- MenuItem: Changed accelerator delimiter from `-` to `+`. (Windows and Linux).
- ComboBox: Fixed occasional `StackOverflowError` when modifying combo box not
on AWT thread. (issue #432)
- macOS: Fixed `NullPointerException` when using AWT component
`java.awt.Choice`. (issue #439)
- Native window decorations: Do not exit application with `UnsatisfiedLinkError`
in case that FlatLaf DLL cannot be executed because of restrictions on
temporary directory. Instead, continue with default window decorations. (issue
#436)
## 1.6.4
#### Fixed bugs
- ComboBox: Fixed regression in FlatLaf 1.6.3 that makes selected item invisible
in popup list if `DefaultListCellRenderer` is used as renderer. If using
default renderer, it works. (issue #426)
## 1.6.3
#### Fixed bugs
@@ -108,8 +238,8 @@ FlatLaf Change Log
#### New features and improvements
- TextField, FormattedTextField and PasswordField: Support adding extra padding.
(set client property `JTextField.padding` to `Insets`).
- TextField, FormattedTextField and PasswordField: Support adding extra padding
(set client property `JTextField.padding` to an `Insets`).
- PasswordField: UI delegate `FlatPasswordFieldUI` now extends `FlatTextFieldUI`
(instead of `BasicPasswordFieldUI`) to avoid duplicate code and for easier
extensibility.

View File

@@ -114,6 +114,8 @@ Buzz
Applications using FlatLaf
--------------------------
- ![New](images/new.svg) [Jailer](https://github.com/Wisser/Jailer) 11.2 -
database subsetting and relational data browsing tool
- [Apache NetBeans](https://netbeans.apache.org/) 11.3 - IDE for Java, PHP, HTML
and much more
- [jclasslib bytecode viewer](https://github.com/ingokegel/jclasslib) 5.5
@@ -141,6 +143,8 @@ Applications using FlatLaf
[OpenStreetMap](https://www.openstreetmap.org/) (requires FlatLaf JOSM plugin)
- [jAlbum](https://jalbum.net/) 21 (**commercial**) - creates photo album
websites
- ![New](images/new.svg) [PDF Studio](https://www.qoppa.com/pdfstudio/) 2021
(**commercial**) - create, review and edit PDF documents
- [XMLmind XML Editor](https://www.xmlmind.com/xmleditor/) 9.3 (**commercial**)
- [Total Validator](https://www.totalvalidator.com/) 15 (**commercial**) -
checks your website
@@ -173,7 +177,7 @@ Applications using FlatLaf
systems development platform
- [JPass](https://github.com/gaborbata/jpass) - password manager with strong
encryption
- [Jes - Die Java-E<EFBFBD>R](https://www.jes-eur.de)
- [Jes - Die Java-EÜR](https://www.jes-eur.de)
- [Mapton](https://mapton.org/) 2.0
([source code](https://github.com/trixon/mapton)) - some kind of map
application (based on NetBeans platform)

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
val releaseVersion = "1.6.3"
val releaseVersion = "2.0-rc1"
val developmentVersion = "2.0-SNAPSHOT"
version = if( java.lang.Boolean.getBoolean( "release" ) ) releaseVersion else developmentVersion

View File

@@ -21,10 +21,15 @@ plugins {
`flatlaf-publish`
}
val sigtest = configurations.create( "sigtest" )
dependencies {
testImplementation( "org.junit.jupiter:junit-jupiter-api:5.7.2" )
testImplementation( "org.junit.jupiter:junit-jupiter-params" )
testRuntimeOnly( "org.junit.jupiter:junit-jupiter-engine" )
// https://github.com/jtulach/netbeans-apitest
sigtest( "org.netbeans.tools:sigtest-maven-plugin:1.4" )
}
java {
@@ -59,10 +64,60 @@ tasks {
archiveBaseName.set( "flatlaf" )
}
check {
dependsOn( "sigtestCheck" )
}
test {
useJUnitPlatform()
testLogging.exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
}
register( "sigtestGenerate" ) {
group = "verification"
dependsOn( "jar" )
doLast {
ant.withGroovyBuilder {
"taskdef"(
"name" to "sigtest",
"classname" to "org.netbeans.apitest.Sigtest",
"classpath" to sigtest.asPath )
"sigtest"(
"action" to "generate",
"fileName" to "${project.name}-sigtest.txt",
"classpath" to jar.get().outputs.files.asPath,
"packages" to "com.formdev.flatlaf,com.formdev.flatlaf.util",
"version" to version,
"release" to "1.8", // Java version
"failonerror" to "true" )
}
}
}
register( "sigtestCheck" ) {
group = "verification"
dependsOn( "jar" )
doLast {
ant.withGroovyBuilder {
"taskdef"(
"name" to "sigtest",
"classname" to "org.netbeans.apitest.Sigtest",
"classpath" to sigtest.asPath )
"sigtest"(
"action" to "check",
"fileName" to "${project.name}-sigtest.txt",
"classpath" to jar.get().outputs.files.asPath,
"packages" to "com.formdev.flatlaf,com.formdev.flatlaf.util",
"version" to version,
"release" to "1.8", // Java version
"failonerror" to "true" )
}
}
}
}
flatlafPublish {

File diff suppressed because it is too large Load Diff

View File

@@ -126,6 +126,57 @@ public interface FlatClientProperties
//---- JComponent ---------------------------------------------------------
/**
* Specifies the style of a component as String in CSS syntax ("key1: value1; key2: value2; ...")
* or as {@link java.util.Map}&lt;String, Object&gt; with binary values.
* <p>
* The keys are the same as used in UI defaults, but without component type prefix.
* E.g. for UI default {@code Slider.thumbSize} use key {@code thumbSize}.
* <p>
* The syntax of the CSS values is the same as used in FlatLaf properties files
* (<a href="https://www.formdev.com/flatlaf/properties-files/">https://www.formdev.com/flatlaf/properties-files/</a>),
* but some features are not supported (e.g. variables).
* When using a map, the values are not parsed from a string. They must be binary.
* <p>
* <strong>Components</strong> {@link javax.swing.JComponent}<br>
* <strong>Value type</strong> {@link java.lang.String} or {@link java.util.Map}&lt;String, Object&gt;<br>
*
* @since 2
*/
String STYLE = "FlatLaf.style";
/**
* Specifies the style class(es) of a component as String (single class or multiple classes separated by space characters)
* or as {@code String[]} or {@link java.util.List}&lt;String&gt; (multiple classes).
* <p>
* The style rules must be defined in UI defaults either as strings (in CSS syntax)
* or as {@link java.util.Map}&lt;String, Object&gt; (with binary values).
* The key must be in syntax: {@code [style]type.styleClass}, where the type is optional.
* E.g. in FlatLaf properties file:
* <pre>{@code
* [style]Button.primary = borderColor: #08f; background: #08f; foreground: #fff
* [style].secondary = borderColor: #0f8; background: #0f8
* }</pre>
* or in Java code:
* <pre>{@code
* UIManager.put( "[style]Button.primary", "borderColor: #08f; background: #08f; foreground: #fff" );
* UIManager.put( "[style].secondary", "borderColor: #0f8; background: #0f8" );
* }</pre>
* The rule "Button.primary" can be applied to buttons only.
* The rule ".secondary" can be applied to any component.
* <p>
* To have similar behavior as in CSS, first the rule without type is applied,
* then the rule with type.
* E.g. setting style class to "foo" on a {@code JButton} uses rules
* from UI default keys "[style].foo" and "[style]Button.foo".
* <p>
* <strong>Components</strong> {@link javax.swing.JComponent}<br>
* <strong>Value type</strong> {@link java.lang.String}, {@code String[]} or {@link java.util.List}&lt;String&gt;<br>
*
* @since 2
*/
String STYLE_CLASS = "FlatLaf.styleClass";
/**
* Specifies minimum width of a component.
* <p>
@@ -280,6 +331,24 @@ public interface FlatClientProperties
*/
String MENU_BAR_EMBEDDED = "JRootPane.menuBarEmbedded";
/**
* Specifies whether the window icon should be shown in the window title bar
* (requires enabled window decorations).
* <p>
* Setting this shows/hides the windows icon
* for the {@code JFrame} or {@code JDialog} that contains the root pane.
* <p>
* This client property has higher priority than UI default {@code TitlePane.showIcon}.
* <p>
* (requires Window 10)
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*
* @since 2
*/
String TITLE_BAR_SHOW_ICON = "JRootPane.titleBarShowIcon";
/**
* Background color of window title bar (requires enabled window decorations).
* <p>
@@ -324,6 +393,35 @@ public interface FlatClientProperties
//---- JTabbedPane --------------------------------------------------------
/**
* Specifies type of the selected tab.
* <p>
* <strong>Component</strong> {@link javax.swing.JTabbedPane}<br>
* <strong>Value type</strong> {@link java.lang.String}<br>
* <strong>Allowed Values</strong>
* {@link #TABBED_PANE_TAB_TYPE_UNDERLINED} or
* {@link #TABBED_PANE_TAB_TYPE_CARD}
*
* @since 2
*/
String TABBED_PANE_TAB_TYPE = "JTabbedPane.tabType";
/**
* Paint the selected tab underlined.
*
* @see #TABBED_PANE_TAB_TYPE
* @since 2
*/
String TABBED_PANE_TAB_TYPE_UNDERLINED = "underlined";
/**
* Paint the selected tab as card.
*
* @see #TABBED_PANE_TAB_TYPE
* @since 2
*/
String TABBED_PANE_TAB_TYPE_CARD = "card";
/**
* Specifies whether separators are shown between tabs.
* <p>
@@ -665,9 +763,9 @@ public interface FlatClientProperties
/**
* Specifies a component that will be placed at the leading edge of the tabs area.
* <p>
* For top and bottom tab placement, the layed out component size will be
* For top and bottom tab placement, the laid out component size will be
* the preferred component width and the tab area height.<br>
* For left and right tab placement, the layed out component size will be
* For left and right tab placement, the laid out component size will be
* the tab area width and the preferred component height.
* <p>
* <strong>Component</strong> {@link javax.swing.JTabbedPane}<br>
@@ -678,9 +776,9 @@ public interface FlatClientProperties
/**
* Specifies a component that will be placed at the trailing edge of the tabs area.
* <p>
* For top and bottom tab placement, the layed out component size will be
* For top and bottom tab placement, the laid out component size will be
* the available horizontal space (minimum is preferred component width) and the tab area height.<br>
* For left and right tab placement, the layed out component size will be
* For left and right tab placement, the laid out component size will be
* the tab area width and the available vertical space (minimum is preferred component height).
* <p>
* <strong>Component</strong> {@link javax.swing.JTabbedPane}<br>
@@ -745,6 +843,107 @@ public interface FlatClientProperties
*/
String TEXT_FIELD_PADDING = "JTextField.padding";
/**
* Specifies an icon that will be placed at the leading edge of the text field.
* <p>
* <strong>Component</strong> {@link javax.swing.JTextField} (and subclasses)<br>
* <strong>Value type</strong> {@link javax.swing.Icon}
*
* @since 2
*/
String TEXT_FIELD_LEADING_ICON = "JTextField.leadingIcon";
/**
* Specifies an icon that will be placed at the trailing edge of the text field.
* <p>
* <strong>Component</strong> {@link javax.swing.JTextField} (and subclasses)<br>
* <strong>Value type</strong> {@link javax.swing.Icon}
*
* @since 2
*/
String TEXT_FIELD_TRAILING_ICON = "JTextField.trailingIcon";
/**
* Specifies a component that will be placed at the leading edge of the text field.
* <p>
* The component will be positioned inside and aligned to the visible text field border.
* There is no gap between the visible border and the component.
* The laid out component size will be the preferred component width
* and the inner text field height.
* <p>
* The component should be not opaque because the text field border is painted
* slightly inside the usually visible border in some cases.
* E.g. when focused (in some themes) or when an outline color is specified
* (see {@link #OUTLINE}.
* <p>
* <strong>Component</strong> {@link javax.swing.JTextField} (and subclasses)<br>
* <strong>Value type</strong> {@link javax.swing.JComponent}
*
* @since 2
*/
String TEXT_FIELD_LEADING_COMPONENT = "JTextField.leadingComponent";
/**
* Specifies a component that will be placed at the trailing edge of the text field.
* <p>
* The component will be positioned inside and aligned to the visible text field border.
* There is no gap between the visible border and the component.
* The laid out component size will be the preferred component width
* and the inner text field height.
* <p>
* The component should be not opaque because the text field border is painted
* slightly inside the usually visible border in some cases.
* E.g. when focused (in some themes) or when an outline color is specified
* (see {@link #OUTLINE}.
* <p>
* <strong>Component</strong> {@link javax.swing.JTextField} (and subclasses)<br>
* <strong>Value type</strong> {@link javax.swing.JComponent}
*
* @since 2
*/
String TEXT_FIELD_TRAILING_COMPONENT = "JTextField.trailingComponent";
/**
* Specifies whether a "clear" (or "cancel") button is shown on the trailing side
* if the text field is not empty, editable and enabled. Default is {@code false}.
* <p>
* <strong>Component</strong> {@link javax.swing.JTextField} (and subclasses)<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*
* @since 2
*/
String TEXT_FIELD_SHOW_CLEAR_BUTTON = "JTextField.showClearButton";
/**
* Specifies the callback that is invoked when a "clear" (or "cancel") button is clicked.
* If a callback is specified than it is responsible for clearing the text field.
* Without callback, the text field clears itself.
* <p>
* Either use a {@link java.lang.Runnable}:
* <pre>{@code
* myTextField.putClientProperty( "JTextField.clearCallback",
* (Runnable) () -> {
* // clear field here or cancel search
* } );
* }</pre>
* Or use a {@link java.util.function.Consumer}&lt;javax.swing.text.JTextComponent&gt;
* that receives the text field as parameter:
* <pre>{@code
* myTextField.putClientProperty( "JTextField.clearCallback",
* (Consumer<JTextComponent>) textField -> {
* // clear field here or cancel search
* } );
* }</pre>
* <p>
* <strong>Component</strong> {@link javax.swing.JTextField} (and subclasses)<br>
* <strong>Value type</strong> {@link java.lang.Runnable}
* or {@link java.util.function.Consumer}&lt;javax.swing.text.JTextComponent&gt;
*
* @see #TEXT_FIELD_SHOW_CLEAR_BUTTON
* @since 2
*/
String TEXT_FIELD_CLEAR_CALLBACK = "JTextField.clearCallback";
//---- JToggleButton ------------------------------------------------------
/**
@@ -813,8 +1012,7 @@ public interface FlatClientProperties
* If the client property is not set, or not a {@link Boolean}, defaultValue is returned.
*/
static Boolean clientPropertyBooleanStrict( JComponent c, String key, Boolean defaultValue ) {
Object value = c.getClientProperty( key );
return (value instanceof Boolean) ? (Boolean) value : defaultValue;
return clientProperty( c, key, defaultValue, Boolean.class );
}
/**
@@ -831,7 +1029,18 @@ public interface FlatClientProperties
* If the client property is not set, or not a color, defaultValue is returned.
*/
static Color clientPropertyColor( JComponent c, String key, Color defaultValue ) {
return clientProperty( c, key, defaultValue, Color.class );
}
/**
* Returns the value of the specified client property if it is an instance of
* the specified type. Otherwise, defaultValue is returned.
*
* @since 2
*/
@SuppressWarnings( "unchecked" )
static <T> T clientProperty( JComponent c, String key, T defaultValue, Class<T> type ) {
Object value = c.getClientProperty( key );
return (value instanceof Color) ? (Color) value : defaultValue;
return type.isInstance( value ) ? (T) value : defaultValue;
}
}

View File

@@ -31,7 +31,9 @@ import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
@@ -43,18 +45,21 @@ import java.util.ResourceBundle;
import java.util.ServiceLoader;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntUnaryOperator;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JMenuBar;
import javax.swing.LookAndFeel;
import javax.swing.PopupFactory;
import javax.swing.RootPaneContainer;
import javax.swing.SwingUtilities;
import javax.swing.UIDefaults;
import javax.swing.UIDefaults.ActiveValue;
import javax.swing.UIDefaults.LazyValue;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.plaf.ColorUIResource;
@@ -86,6 +91,8 @@ public abstract class FlatLaf
private static final String DESKTOPFONTHINTS = "awt.font.desktophints";
private static List<Object> customDefaultsSources;
private static Map<String, String> globalExtraDefaults;
private Map<String, String> extraDefaults;
private String desktopPropertyName;
private String desktopPropertyName2;
@@ -387,6 +394,7 @@ public abstract class FlatLaf
"EditorPane.inactiveBackground",
"FormattedTextField.disabledBackground",
"PasswordField.disabledBackground",
"RootPane.background",
"Spinner.disabledBackground",
"TextArea.disabledBackground",
"TextArea.inactiveBackground",
@@ -405,7 +413,8 @@ public abstract class FlatLaf
"Spinner.disabledForeground",
"ToggleButton.disabledText" );
putDefaults( defaults, defaults.getColor( "textText" ),
"DesktopIcon.foreground" );
"DesktopIcon.foreground",
"RootPane.foreground" );
initFonts( defaults );
initIconColors( defaults, isDark() );
@@ -415,7 +424,7 @@ public abstract class FlatLaf
// (using defaults.remove() to avoid that lazy value is resolved and icon loaded here)
Object icon = defaults.remove( "InternalFrame.icon" );
defaults.put( "InternalFrame.icon", icon );
defaults.put( "TitlePane.icon", icon );
defaults.put( "TitlePane.icon", icon ); // no longer used, but keep for compatibility
// get addons and sort them by priority
ServiceLoader<FlatDefaultsAddon> addonLoader = ServiceLoader.load( FlatDefaultsAddon.class );
@@ -431,6 +440,10 @@ public abstract class FlatLaf
else
UIDefaultsLoader.loadDefaultsFromProperties( getClass(), addons, getAdditionalDefaults(), isDark(), defaults );
// setup default font after loading defaults from properties
// to allow defining "defaultFont" in properties
initDefaultFont( defaults );
// use Aqua MenuBarUI if Mac screen menubar is enabled
if( SystemInfo.isMacOS && Boolean.getBoolean( "apple.laf.useScreenMenuBar" ) ) {
defaults.put( "MenuBarUI", "com.apple.laf.AquaMenuBarUI" );
@@ -470,7 +483,15 @@ public abstract class FlatLaf
}
protected Properties getAdditionalDefaults() {
return null;
if( globalExtraDefaults == null && extraDefaults == null )
return null;
Properties properties = new Properties();
if( globalExtraDefaults != null )
properties.putAll( globalExtraDefaults );
if( extraDefaults != null )
properties.putAll( extraDefaults );
return properties;
}
private void initResourceBundle( UIDefaults defaults, String bundleName ) {
@@ -513,8 +534,25 @@ public abstract class FlatLaf
}
private void initFonts( UIDefaults defaults ) {
// use active value for all fonts to allow changing fonts in all components with:
// UIManager.put( "defaultFont", myFont );
// (this is similar as in Nimbus L&F)
Object activeFont = new ActiveFont( null, null, -1, 0, 0, 0, 0 );
// override fonts
for( Object key : defaults.keySet() ) {
if( key instanceof String && (((String)key).endsWith( ".font" ) || ((String)key).endsWith( "Font" )) )
defaults.put( key, activeFont );
}
// add fonts that are not set in BasicLookAndFeel
defaults.put( "RootPane.font", activeFont );
}
private void initDefaultFont( UIDefaults defaults ) {
FontUIResource uiFont = null;
// determine UI font based on operating system
if( SystemInfo.isWindows ) {
Font winFont = (Font) Toolkit.getDefaultToolkit().getDesktopProperty( "win.messagebox.font" );
if( winFont != null ) {
@@ -557,23 +595,21 @@ public abstract class FlatLaf
if( uiFont == null )
uiFont = createCompositeFont( Font.SANS_SERIF, Font.PLAIN, 12 );
// get/remove "defaultFont" from defaults if set in properties files
// (use remove() to avoid that ActiveFont.createValue() gets invoked)
Object defaultFont = defaults.remove( "defaultFont" );
// use font from OS as base font and derive the UI font from it
if( defaultFont instanceof ActiveFont ) {
Font baseFont = uiFont;
uiFont = ((ActiveFont)defaultFont).derive( baseFont, fontSize -> {
return Math.round( fontSize * UIScale.computeFontScaleFactor( baseFont ) );
} );
};
// increase font size if system property "flatlaf.uiScale" is set
uiFont = UIScale.applyCustomScaleFactor( uiFont );
// use active value for all fonts to allow changing fonts in all components
// (similar as in Nimbus L&F) with:
// UIManager.put( "defaultFont", myFont );
Object activeFont = new ActiveFont( 1 );
// override fonts
for( Object key : defaults.keySet() ) {
if( key instanceof String && (((String)key).endsWith( ".font" ) || ((String)key).endsWith( "Font" )) )
defaults.put( key, activeFont );
}
// use smaller font for progress bar
defaults.put( "ProgressBar.font", new ActiveFont( 0.85f ) );
// set default font
defaults.put( "defaultFont", uiFont );
}
@@ -586,11 +622,9 @@ public abstract class FlatLaf
return (font instanceof FontUIResource) ? (FontUIResource) font : new FontUIResource( font );
}
/**
* @since 1.1
*/
/** @since 1.1 */
public static ActiveValue createActiveFontValue( float scaleFactor ) {
return new ActiveFont( scaleFactor );
return new ActiveFont( null, null, -1, 0, 0, 0, scaleFactor );
}
/**
@@ -724,6 +758,9 @@ public abstract class FlatLaf
* and can therefore override all UI defaults.
* <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)}.
*
* @param packageName a package name (e.g. "com.myapp.resources")
*/
@@ -765,6 +802,32 @@ public abstract class FlatLaf
}
}
/**
* Registers a package where FlatLaf searches for properties files with custom UI defaults.
* <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/" ) )}.
*
* @param packageUrl a package URL
* @since 2
*/
public static void registerCustomDefaultsSource( URL packageUrl ) {
if( customDefaultsSources == null )
customDefaultsSources = new ArrayList<>();
customDefaultsSources.add( packageUrl );
}
/** @since 2 */
public static void unregisterCustomDefaultsSource( URL packageUrl ) {
if( customDefaultsSources == null )
return;
customDefaultsSources.remove( packageUrl );
}
/**
* Registers a folder where FlatLaf searches for properties files with custom UI defaults.
* <p>
@@ -785,6 +848,102 @@ public abstract class FlatLaf
customDefaultsSources.remove( folder );
}
/**
* Gets global extra UI defaults; or {@code null}.
*
* @since 2
*/
public static Map<String, String> getGlobalExtraDefaults() {
return globalExtraDefaults;
}
/**
* Sets global extra UI defaults, which are only used when setting up the application look and feel.
* E.g. using {@link UIManager#setLookAndFeel(LookAndFeel)} or {@link #setup(LookAndFeel)}.
* <p>
* The global extra defaults are useful for smaller additional defaults that may change.
* E.g. accent color. Otherwise FlatLaf properties files should be used.
* See {@link #registerCustomDefaultsSource(String)}.
* <p>
* The keys and values are strings in same format as in FlatLaf properties files.
* <p>
* Sample that setups "FlatLaf Light" theme with red accent color:
* <pre>{@code
* FlatLaf.setGlobalExtraDefaults( Collections.singletonMap( "@accentColor", "#f00" ) );
* FlatLightLaf.setup();
* }</pre>
*
* @see #setExtraDefaults(Map)
* @since 2
*/
public static void setGlobalExtraDefaults( Map<String, String> globalExtraDefaults ) {
FlatLaf.globalExtraDefaults = globalExtraDefaults;
}
/**
* Gets extra UI defaults; or {@code null}.
*
* @since 2
*/
public Map<String, String> getExtraDefaults() {
return extraDefaults;
}
/**
* Sets extra UI defaults, which are only used when setting up the application look and feel.
* E.g. using {@link UIManager#setLookAndFeel(LookAndFeel)} or {@link #setup(LookAndFeel)}.
* <p>
* The extra defaults are useful for smaller additional defaults that may change.
* E.g. accent color. Otherwise FlatLaf properties files should be used.
* See {@link #registerCustomDefaultsSource(String)}.
* <p>
* The keys and values are strings in same format as in FlatLaf properties files.
* <p>
* Sample that setups "FlatLaf Light" theme with red accent color:
* <pre>{@code
* FlatLaf laf = new FlatLightLaf();
* laf.setExtraDefaults( Collections.singletonMap( "@accentColor", "#f00" ) );
* FlatLaf.setup( laf );
* }</pre>
*
* @see #setGlobalExtraDefaults(Map)
* @since 2
*/
public void setExtraDefaults( Map<String, String> extraDefaults ) {
this.extraDefaults = extraDefaults;
}
/**
* Parses a UI defaults value string and converts it into a binary object.
* <p>
* See: <a href="https://www.formdev.com/flatlaf/properties-files/">https://www.formdev.com/flatlaf/properties-files/</a>
*
* @param key the key, which is used to determine the value type if parameter {@code valueType} is {@code null}
* @param value the value string
* @param valueType the expected value type, or {@code null}
* @return the binary value
* @throws IllegalArgumentException on syntax errors
* @since 2
*/
public static Object parseDefaultsValue( String key, String value, Class<?> valueType )
throws IllegalArgumentException
{
// resolve variables
value = UIDefaultsLoader.resolveValueFromUIManager( value );
// parse value
Object val = UIDefaultsLoader.parseValue( key, value, valueType, null,
v -> UIDefaultsLoader.resolveValueFromUIManager( v ), Collections.emptyList() );
// create actual value if lazy or active
if( val instanceof LazyValue )
val = ((LazyValue)val).createValue( null );
else if( val instanceof ActiveValue )
val = ((ActiveValue)val).createValue( null );
return val;
}
private static void reSetLookAndFeel() {
EventQueue.invokeLater( () -> {
LookAndFeel lookAndFeel = UIManager.getLookAndFeel();
@@ -878,12 +1037,23 @@ public abstract class FlatLaf
/**
* Revalidate and repaint all displayable frames and dialogs.
* <p>
* Useful to update UI after changing {@code TitlePane.menuBarEmbedded}.
*
* @since 1.1.2
*/
public static void revalidateAndRepaintAllFramesAndDialogs() {
for( Window w : Window.getWindows() ) {
if( isDisplayableFrameOrDialog( w ) ) {
// revalidate menu bar
JMenuBar menuBar = (w instanceof JFrame)
? ((JFrame)w).getJMenuBar()
: (w instanceof JDialog
? ((JDialog)w).getJMenuBar()
: null);
if( menuBar != null )
menuBar.revalidate();
w.revalidate();
w.repaint();
}
@@ -892,6 +1062,9 @@ public abstract class FlatLaf
/**
* Repaint all displayable frames and dialogs.
* <p>
* Useful to update UI after changing {@code TitlePane.unifiedBackground},
* {@code MenuItem.selectionType} or {@code Component.hideMnemonics}.
*
* @since 1.1.2
*/
@@ -1065,44 +1238,133 @@ public abstract class FlatLaf
//---- class ActiveFont ---------------------------------------------------
private static class ActiveFont
static class ActiveFont
implements ActiveValue
{
private final float scaleFactor;
private final String baseFontKey;
private final List<String> families;
private final int style;
private final int styleChange;
private final int absoluteSize;
private final int relativeSize;
private final float scaleSize;
// cache (scaled) font
private Font font;
private Font lastDefaultFont;
// cache (scaled/derived) font
private FontUIResource font;
private Font lastBaseFont;
ActiveFont( float scaleFactor ) {
this.scaleFactor = scaleFactor;
private boolean inCreateValue;
/**
* @param families list of font families, or {@code null}
* @param style new style of font, or {@code -1}
* @param styleChange derive style of base font; or {@code 0}
* (the lower 16 bits are added; the upper 16 bits are removed)
* @param absoluteSize new size of font, or {@code 0}
* @param relativeSize added to size of base font, or {@code 0}
* @param scaleSize multiply size of base font, or {@code 0}
*/
ActiveFont( String baseFontKey, List<String> families, int style, int styleChange,
int absoluteSize, int relativeSize, float scaleSize )
{
this.baseFontKey = baseFontKey;
this.families = families;
this.style = style;
this.styleChange = styleChange;
this.absoluteSize = absoluteSize;
this.relativeSize = relativeSize;
this.scaleSize = scaleSize;
}
@Override
public Object createValue( UIDefaults table ) {
Font defaultFont = UIManager.getFont( "defaultFont" );
if( inCreateValue )
throw new IllegalStateException( "FlatLaf: endless recursion in font" );
// fallback (to avoid NPE in case that this is used in another Laf)
if( defaultFont == null )
defaultFont = UIManager.getFont( "Label.font" );
Font baseFont = null;
if( lastDefaultFont != defaultFont ) {
lastDefaultFont = defaultFont;
inCreateValue = true;
try {
if( baseFontKey != null )
baseFont = (Font) UIDefaultsLoader.lazyUIManagerGet( baseFontKey );
if( scaleFactor != 1 ) {
// scale font
int newFontSize = Math.round( defaultFont.getSize() * scaleFactor );
font = new FontUIResource( defaultFont.deriveFont( (float) newFontSize ) );
} else {
// make sure that font is a UIResource for LaF switching
font = (defaultFont instanceof UIResource)
? defaultFont
: new FontUIResource( defaultFont );
}
if( baseFont == null )
baseFont = UIManager.getFont( "defaultFont" );
// fallback (to avoid NPE in case that this is used in another Laf)
if( baseFont == null )
baseFont = UIManager.getFont( "Label.font" );
} finally {
inCreateValue = false;
}
if( lastBaseFont != baseFont ) {
lastBaseFont = baseFont;
font = derive( baseFont, fontSize -> UIScale.scale( fontSize ) );
}
return font;
}
FontUIResource derive( Font baseFont, IntUnaryOperator scale ) {
int baseStyle = baseFont.getStyle();
int baseSize = baseFont.getSize();
// new style
int newStyle = (style != -1)
? style
: (styleChange != 0)
? baseStyle & ~((styleChange >> 16) & 0xffff) | (styleChange & 0xffff)
: baseStyle;
// new size
int newSize = (absoluteSize > 0)
? scale.applyAsInt( absoluteSize )
: (relativeSize != 0)
? (baseSize + scale.applyAsInt( relativeSize ))
: (scaleSize > 0)
? Math.round( baseSize * scaleSize )
: baseSize;
if( newSize <= 0 )
newSize = 1;
// create font for family
if( families != null && !families.isEmpty() ) {
for( String family : families ) {
Font font = createCompositeFont( family, newStyle, newSize );
if( !isFallbackFont( font ) || family.equalsIgnoreCase( Font.DIALOG ) )
return toUIResource( font );
}
}
// derive font
if( newStyle != baseStyle || newSize != baseSize ) {
// hack for font "Ubuntu Medium" on Linux, which curiously belongs
// to family "Ubuntu Light" and using deriveFont() would create a light font
if( "Ubuntu Medium".equalsIgnoreCase( baseFont.getName() ) &&
"Ubuntu Light".equalsIgnoreCase( baseFont.getFamily() ) )
{
Font font = createCompositeFont( "Ubuntu Medium", newStyle, newSize );
if( !isFallbackFont( font ) )
return toUIResource( font );
}
return toUIResource( baseFont.deriveFont( newStyle, newSize ) );
} else
return toUIResource( baseFont );
}
private FontUIResource toUIResource( Font font ) {
// make sure that font is a UIResource for LaF switching
return (font instanceof FontUIResource)
? (FontUIResource) font
: new FontUIResource( font );
}
private boolean isFallbackFont( Font font ) {
return Font.DIALOG.equalsIgnoreCase( font.getFamily() );
}
}
//---- class ImageIconUIResource ------------------------------------------

View File

@@ -249,7 +249,7 @@ public class IntelliJTheme
// search for theme specific UI defaults keys
ArrayList<String> themeSpecificKeys = new ArrayList<>();
for( Object key : defaults.keySet() ) {
if( key instanceof String && ((String)key).startsWith( "[" ) )
if( key instanceof String && ((String)key).startsWith( "[" ) && !((String)key).startsWith( "[style]" ) )
themeSpecificKeys.add( (String) key );
}
@@ -338,7 +338,7 @@ public class IntelliJTheme
// parse value
try {
uiValue = UIDefaultsLoader.parseValue( key, valueStr );
uiValue = UIDefaultsLoader.parseValue( key, valueStr, null );
} catch( RuntimeException ex ) {
UIDefaultsLoader.logParseError( key, valueStr, ex, false );
return; // ignore invalid value
@@ -504,7 +504,7 @@ public class IntelliJTheme
// 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].selectedFocusedBackground", defaults.get( "CheckBox.icon[filled].selectedBackground" ) );
defaults.put( "CheckBox.icon[filled].focusedSelectedBackground", defaults.get( "CheckBox.icon[filled].selectedBackground" ) );
if( dark ) {
// IDEA Darcula checkBoxFocused.svg, checkBoxSelectedFocused.svg,
@@ -513,9 +513,9 @@ public class IntelliJTheme
// --> add alpha to focused border colors
String[] focusedBorderColorKeys = new String[] {
"CheckBox.icon.focusedBorderColor",
"CheckBox.icon.selectedFocusedBorderColor",
"CheckBox.icon.focusedSelectedBorderColor",
"CheckBox.icon[filled].focusedBorderColor",
"CheckBox.icon[filled].selectedFocusedBorderColor",
"CheckBox.icon[filled].focusedSelectedBorderColor",
};
for( String key : focusedBorderColorKeys ) {
Color color = defaults.getColor( key );
@@ -549,6 +549,8 @@ 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" );
@@ -594,6 +596,10 @@ public class IntelliJTheme
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" );
// TitlePane
uiKeyCopying.put( "TitlePane.inactiveBackground", "TitlePane.background" );
uiKeyMapping.put( "TitlePane.infoForeground", "TitlePane.foreground" );
@@ -618,7 +624,7 @@ public class IntelliJTheme
checkboxKeyMapping.put( "Checkbox.Background.Selected", "CheckBox.icon.selectedBackground" );
checkboxKeyMapping.put( "Checkbox.Border.Selected", "CheckBox.icon.selectedBorderColor" );
checkboxKeyMapping.put( "Checkbox.Foreground.Selected", "CheckBox.icon.checkmarkColor" );
checkboxKeyMapping.put( "Checkbox.Focus.Thin.Selected", "CheckBox.icon.selectedFocusedBorderColor" );
checkboxKeyMapping.put( "Checkbox.Focus.Thin.Selected", "CheckBox.icon.focusedSelectedBorderColor" );
checkboxDuplicateColors.put( "Checkbox.Background.Default.Dark", "Checkbox.Background.Selected.Dark" );
checkboxDuplicateColors.put( "Checkbox.Border.Default.Dark", "Checkbox.Border.Selected.Dark" );

View File

@@ -17,6 +17,7 @@
package com.formdev.flatlaf;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.Toolkit;
@@ -28,7 +29,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.StringTokenizer;
import javax.swing.text.StyleContext;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.StringUtils;
import com.formdev.flatlaf.util.SystemInfo;
@@ -121,14 +122,25 @@ class LinuxFontPolicy
for(;;) {
Font font = createFont( family, style, size, dsize );
// if the font family does not match any font on the system, "Dialog" family is returned
if( !"Dialog".equals( font.getFamily() ) || "Dialog".equals( family ) )
if( Font.DIALOG.equals( family ) )
return font;
// if the font family does not match any font on the system, "Dialog" family is returned
if( !Font.DIALOG.equals( font.getFamily() ) ) {
// check for font problems
// - font height much larger than expected (e.g. font Inter; Oracle Java 8)
// - character width is zero (e.g. font Cantarell; Fedora; Oracle Java 8)
FontMetrics fm = StyleContext.getDefaultStyleContext().getFontMetrics( font );
if( fm.getHeight() > size * 2 || fm.stringWidth( "a" ) == 0 )
return createFont( Font.DIALOG, style, size, dsize );
return font;
}
// find last word in family
int index = family.lastIndexOf( ' ' );
if( index < 0 )
return createFont( "Dialog", style, size, dsize );
return createFont( Font.DIALOG, style, size, dsize );
// check whether last work contains some font weight (e.g. Ultra-Bold or Heavy)
String lastWord = family.substring( index + 1 ).toLowerCase();

View File

@@ -18,11 +18,16 @@ package com.formdev.flatlaf;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Insets;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -33,8 +38,10 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.function.Function;
import javax.swing.Icon;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.UIDefaults.ActiveValue;
import javax.swing.UIDefaults.LazyValue;
import javax.swing.plaf.ColorUIResource;
@@ -48,6 +55,7 @@ import com.formdev.flatlaf.util.DerivedColor;
import com.formdev.flatlaf.util.GrayFilter;
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;
@@ -72,8 +80,12 @@ class UIDefaultsLoader
private static final String OPTIONAL_PREFIX = "?";
private static final String WILDCARD_PREFIX = "*.";
static final String KEY_VARIABLES = "FlatLaf.internal.variables";
private static int parseColorDepth;
private static final SoftCache<String, Object> fontCache = new SoftCache<>();
static void loadDefaultsFromProperties( Class<?> lookAndFeelClass, List<FlatDefaultsAddon> addons,
Properties additionalDefaults, boolean dark, UIDefaults defaults )
{
@@ -146,6 +158,18 @@ class UIDefaultsLoader
properties.load( in );
}
}
} else if( source instanceof URL ) {
// load from package URL
URL packageUrl = (URL) source;
for( Class<?> lafClass : lafClasses ) {
URL propertiesUrl = new URL( packageUrl + lafClass.getSimpleName() + ".properties" );
try( InputStream in = propertiesUrl.openStream() ) {
properties.load( in );
} catch( FileNotFoundException ex ) {
// ignore
}
}
} else if( source instanceof File ) {
// load from folder
File folder = (File) source;
@@ -234,18 +258,24 @@ class UIDefaultsLoader
};
// parse and add properties to UI defaults
Map<String, String> variables = new HashMap<>( 50 );
for( Map.Entry<Object, Object> e : properties.entrySet() ) {
String key = (String) e.getKey();
if( key.startsWith( VARIABLE_PREFIX ) )
if( key.startsWith( VARIABLE_PREFIX ) ) {
variables.put( key, (String) e.getValue() );
continue;
}
String value = resolveValue( (String) e.getValue(), propertiesGetter );
try {
defaults.put( key, parseValue( key, value, null, resolver, addonClassLoaders ) );
defaults.put( key, parseValue( key, value, null, null, resolver, addonClassLoaders ) );
} catch( RuntimeException ex ) {
logParseError( key, value, ex, true );
}
}
// remember variables in defaults to allow using them in styles
defaults.put( KEY_VARIABLES, variables );
} catch( IOException ex ) {
LoggingFacade.INSTANCE.logSevere( "FlatLaf: Failed to load properties files.", ex );
}
@@ -288,87 +318,200 @@ class UIDefaultsLoader
return resolveValue( newValue, propertiesGetter );
}
enum ValueType { UNKNOWN, STRING, BOOLEAN, CHARACTER, INTEGER, FLOAT, BORDER, ICON, INSETS, DIMENSION, COLOR,
static String resolveValueFromUIManager( String value ) {
if( value.startsWith( VARIABLE_PREFIX ) ) {
@SuppressWarnings( "unchecked" )
Map<String, String> variables = (Map<String, String>) UIManager.get( KEY_VARIABLES );
String newValue = (variables != null) ? variables.get( value ) : null;
if( newValue == null )
throw new IllegalArgumentException( "variable '" + value + "' not found" );
return newValue;
}
if( !value.startsWith( PROPERTY_PREFIX ) )
return value;
String key = value.substring( PROPERTY_PREFIX.length() );
Object newValue = UIManager.get( key );
if( newValue == null )
throw new IllegalArgumentException( "property '" + key + "' not found" );
// convert binary color to string
if( newValue instanceof Color ) {
Color color = (Color) newValue;
int alpha = color.getAlpha();
return String.format( (alpha != 255) ? "#%06x%02x" : "#%06x", color.getRGB() & 0xffffff, alpha );
}
throw new IllegalArgumentException( "property value type '" + newValue.getClass().getName() + "' not supported in references" );
}
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 ValueType[] tempResultValueType = new ValueType[1];
private static Map<Class<?>, ValueType> javaValueTypes;
private static Map<String, ValueType> knownValueTypes;
static Object parseValue( String key, String value ) {
return parseValue( key, value, null, v -> v, Collections.emptyList() );
static Object parseValue( String key, String value, Class<?> valueType ) {
return parseValue( key, value, valueType, null, v -> v, Collections.emptyList() );
}
static Object parseValue( String key, String value, ValueType[] resultValueType,
static Object parseValue( String key, String value, Class<?> javaValueType, ValueType[] resultValueType,
Function<String, String> resolver, List<ClassLoader> addonClassLoaders )
{
if( resultValueType == null )
resultValueType = tempResultValueType;
value = value.trim();
// null, false, true
switch( value ) {
case "null": resultValueType[0] = ValueType.NULL; return null;
case "false": resultValueType[0] = ValueType.BOOLEAN; return false;
case "true": resultValueType[0] = ValueType.BOOLEAN; return true;
// do not parse styles here
if( key.startsWith( "[style]" ) ) {
resultValueType[0] = ValueType.STRING;
return value;
}
// check for function "lazy"
// Syntax: lazy(uiKey)
if( value.startsWith( "lazy(" ) && value.endsWith( ")" ) ) {
resultValueType[0] = ValueType.LAZY;
String uiKey = value.substring( 5, value.length() - 1 ).trim();
return (LazyValue) t -> {
return lazyUIManagerGet( uiKey );
};
value = value.trim();
// null
if( value.equals( "null" ) || value.isEmpty() ) {
resultValueType[0] = ValueType.NULL;
return null;
}
// check for function "if"
// Syntax: if(condition,trueValue,falseValue)
// - condition: evaluates to true if:
// - is not "null"
// - is not "false"
// - is not an integer with zero value
// - trueValue: used if condition is true
// - falseValue: used if condition is false
if( value.startsWith( "if(" ) && value.endsWith( ")" ) ) {
List<String> params = splitFunctionParams( value.substring( 3, value.length() - 1 ), ',' );
if( params.size() != 3 )
throwMissingParametersException( value );
boolean ifCondition = parseCondition( params.get( 0 ), resolver, addonClassLoaders );
String ifValue = params.get( ifCondition ? 1 : 2 );
return parseValue( key, resolver.apply( ifValue ), javaValueType, resultValueType, resolver, addonClassLoaders );
}
ValueType valueType = ValueType.UNKNOWN;
// check whether value type is specified in the value
if( value.startsWith( "#" ) )
valueType = ValueType.COLOR;
else if( value.startsWith( "\"" ) && value.endsWith( "\"" ) ) {
valueType = ValueType.STRING;
value = value.substring( 1, value.length() - 1 );
} else if( value.startsWith( TYPE_PREFIX ) ) {
int end = value.indexOf( TYPE_PREFIX_END );
if( end != -1 ) {
try {
String typeStr = value.substring( TYPE_PREFIX.length(), end );
valueType = ValueType.valueOf( typeStr.toUpperCase( Locale.ENGLISH ) );
if( javaValueType != null ) {
if( javaValueTypes == null ) {
// create lazy
javaValueTypes = new HashMap<>();
javaValueTypes.put( String.class, ValueType.STRING );
javaValueTypes.put( boolean.class, ValueType.BOOLEAN );
javaValueTypes.put( Boolean.class, ValueType.BOOLEAN );
javaValueTypes.put( char.class, ValueType.CHARACTER );
javaValueTypes.put( Character.class, ValueType.CHARACTER );
javaValueTypes.put( int.class, ValueType.INTEGER );
javaValueTypes.put( Integer.class, ValueType.INTEGER );
javaValueTypes.put( float.class, ValueType.FLOAT );
javaValueTypes.put( Float.class, ValueType.FLOAT );
javaValueTypes.put( Border.class, ValueType.BORDER );
javaValueTypes.put( Icon.class, ValueType.ICON );
javaValueTypes.put( Insets.class, ValueType.INSETS );
javaValueTypes.put( Dimension.class, ValueType.DIMENSION );
javaValueTypes.put( Color.class, ValueType.COLOR );
javaValueTypes.put( Font.class, ValueType.FONT );
}
// remove type from value
value = value.substring( end + TYPE_PREFIX_END.length() );
} catch( IllegalArgumentException ex ) {
// ignore
// map java value type to parser value type
valueType = javaValueTypes.get( javaValueType );
if( valueType == null )
throw new IllegalArgumentException( "unsupported value type '" + javaValueType.getName() + "'" );
// remove '"' from strings
if( valueType == ValueType.STRING && value.startsWith( "\"" ) && value.endsWith( "\"" ) )
value = value.substring( 1, value.length() - 1 );
} else {
// false, true
switch( value ) {
case "false": resultValueType[0] = ValueType.BOOLEAN; return false;
case "true": resultValueType[0] = ValueType.BOOLEAN; return true;
}
// check for function "lazy"
// Syntax: lazy(uiKey)
if( value.startsWith( "lazy(" ) && value.endsWith( ")" ) ) {
resultValueType[0] = ValueType.LAZY;
String uiKey = StringUtils.substringTrimmed( value, 5, value.length() - 1 );
return (LazyValue) t -> {
return lazyUIManagerGet( uiKey );
};
}
// check whether value type is specified in the value
if( value.startsWith( "#" ) )
valueType = ValueType.COLOR;
else if( value.startsWith( TYPE_PREFIX ) ) {
int end = value.indexOf( TYPE_PREFIX_END );
if( end != -1 ) {
try {
String typeStr = value.substring( TYPE_PREFIX.length(), end );
valueType = ValueType.valueOf( typeStr.toUpperCase( Locale.ENGLISH ) );
// remove type from value
value = value.substring( end + TYPE_PREFIX_END.length() );
} catch( IllegalArgumentException ex ) {
// ignore
}
}
}
}
// determine value type from key
if( valueType == ValueType.UNKNOWN ) {
if( key.endsWith( "UI" ) )
valueType = ValueType.STRING;
else if( key.endsWith( "Color" ) ||
(key.endsWith( "ground" ) &&
(key.endsWith( ".background" ) || key.endsWith( "Background" ) ||
key.endsWith( ".foreground" ) || key.endsWith( "Foreground" ))) )
valueType = ValueType.COLOR;
else if( key.endsWith( ".border" ) || key.endsWith( "Border" ) )
valueType = ValueType.BORDER;
else if( key.endsWith( ".icon" ) || key.endsWith( "Icon" ) )
valueType = ValueType.ICON;
else if( key.endsWith( ".margin" ) || key.endsWith( ".padding" ) ||
key.endsWith( "Margins" ) || key.endsWith( "Insets" ) )
valueType = ValueType.INSETS;
else if( key.endsWith( "Size" ) )
valueType = ValueType.DIMENSION;
else if( key.endsWith( "Width" ) || key.endsWith( "Height" ) )
valueType = ValueType.INTEGER;
else if( key.endsWith( "Char" ) )
valueType = ValueType.CHARACTER;
else if( key.endsWith( "grayFilter" ) )
valueType = ValueType.GRAYFILTER;
if( valueType == ValueType.UNKNOWN ) {
if( knownValueTypes == null ) {
// create lazy
knownValueTypes = new HashMap<>();
// SplitPane
knownValueTypes.put( "SplitPane.dividerSize", ValueType.INTEGER );
knownValueTypes.put( "SplitPaneDivider.gripDotSize", ValueType.INTEGER );
knownValueTypes.put( "dividerSize", ValueType.INTEGER );
knownValueTypes.put( "gripDotSize", ValueType.INTEGER );
// TabbedPane
knownValueTypes.put( "TabbedPane.closeCrossPlainSize", ValueType.FLOAT );
knownValueTypes.put( "TabbedPane.closeCrossFilledSize", ValueType.FLOAT );
knownValueTypes.put( "closeCrossPlainSize", ValueType.FLOAT );
knownValueTypes.put( "closeCrossFilledSize", ValueType.FLOAT );
// Table
knownValueTypes.put( "Table.intercellSpacing", ValueType.DIMENSION );
knownValueTypes.put( "intercellSpacing", ValueType.DIMENSION );
}
valueType = knownValueTypes.getOrDefault( key, ValueType.UNKNOWN );
}
// determine value type from key
if( valueType == ValueType.UNKNOWN ) {
if( key.endsWith( "UI" ) )
valueType = ValueType.STRING;
else if( key.endsWith( "Color" ) ||
(key.endsWith( "ground" ) &&
(key.endsWith( ".background" ) || key.endsWith( "Background" ) || key.equals( "background" ) ||
key.endsWith( ".foreground" ) || key.endsWith( "Foreground" ) || key.equals( "foreground" ))) )
valueType = ValueType.COLOR;
else if( key.endsWith( ".font" ) || key.endsWith( "Font" ) || key.equals( "font" ) )
valueType = ValueType.FONT;
else if( key.endsWith( ".border" ) || key.endsWith( "Border" ) || key.equals( "border" ) )
valueType = ValueType.BORDER;
else if( key.endsWith( ".icon" ) || key.endsWith( "Icon" ) || key.equals( "icon" ) )
valueType = ValueType.ICON;
else if( key.endsWith( ".margin" ) || key.equals( "margin" ) ||
key.endsWith( ".padding" ) || key.equals( "padding" ) ||
key.endsWith( "Margins" ) || key.endsWith( "Insets" ) )
valueType = ValueType.INSETS;
else if( key.endsWith( "Size" ) )
valueType = ValueType.DIMENSION;
else if( key.endsWith( "Width" ) || key.endsWith( "Height" ) )
valueType = ValueType.INTEGERORFLOAT;
else if( key.endsWith( "Char" ) )
valueType = ValueType.CHARACTER;
else if( key.endsWith( "grayFilter" ) )
valueType = ValueType.GRAYFILTER;
}
}
resultValueType[0] = valueType;
@@ -376,14 +519,17 @@ class UIDefaultsLoader
// parse value
switch( valueType ) {
case STRING: return value;
case BOOLEAN: return parseBoolean( value );
case CHARACTER: return parseCharacter( value );
case INTEGER: return parseInteger( value, true );
case INTEGERORFLOAT:return parseIntegerOrFloat( value, true );
case FLOAT: return parseFloat( value, true );
case BORDER: return parseBorder( value, resolver, addonClassLoaders );
case ICON: return parseInstance( value, addonClassLoaders );
case INSETS: return parseInsets( value );
case DIMENSION: return parseDimension( value );
case COLOR: return parseColorOrFunction( value, resolver, true );
case FONT: return parseFont( value );
case SCALEDINTEGER: return parseScaledInteger( value );
case SCALEDFLOAT: return parseScaledFloat( value );
case SCALEDINSETS: return parseScaledInsets( value );
@@ -393,6 +539,12 @@ class UIDefaultsLoader
case GRAYFILTER: return parseGrayFilter( value );
case UNKNOWN:
default:
// string
if( value.startsWith( "\"" ) && value.endsWith( "\"" ) ) {
resultValueType[0] = ValueType.STRING;
return value.substring( 1, value.length() - 1 );
}
// colors
Object color = parseColorOrFunction( value, resolver, false );
if( color != null ) {
@@ -420,19 +572,34 @@ class UIDefaultsLoader
}
}
private static boolean parseCondition( String condition,
Function<String, String> resolver, List<ClassLoader> addonClassLoaders )
{
try {
Object conditionValue = parseValue( "", resolver.apply( condition ), null, null, resolver, addonClassLoaders );
return (conditionValue != null &&
!conditionValue.equals( false ) &&
!conditionValue.equals( 0 ) );
} catch( IllegalArgumentException ex ) {
// ignore errors (e.g. variable or property not found) and evaluate to false
return false;
}
}
private static Object parseBorder( String value, Function<String, String> resolver, List<ClassLoader> addonClassLoaders ) {
if( value.indexOf( ',' ) >= 0 ) {
// top,left,bottom,right[,lineColor[,lineThickness]]
List<String> parts = split( value, ',' );
// top,left,bottom,right[,lineColor[,lineThickness[,arc]]]
List<String> parts = splitFunctionParams( value, ',' );
Insets insets = parseInsets( value );
ColorUIResource lineColor = (parts.size() >= 5)
? (ColorUIResource) parseColorOrFunction( resolver.apply( parts.get( 4 ) ), resolver, true )
: null;
float lineThickness = (parts.size() >= 6) ? parseFloat( parts.get( 5 ), true ) : 1f;
float lineThickness = (parts.size() >= 6 && !parts.get( 5 ).isEmpty()) ? parseFloat( parts.get( 5 ), true ) : 1f;
int arc = (parts.size() >= 7) ? parseInteger( parts.get( 6 ), true ) : 0;
return (LazyValue) t -> {
return (lineColor != null)
? new FlatLineBorder( insets, lineColor, lineThickness )
? new FlatLineBorder( insets, lineColor, lineThickness, arc )
: new FlatEmptyBorder( insets );
};
} else
@@ -480,7 +647,7 @@ class UIDefaultsLoader
}
private static Insets parseInsets( String value ) {
List<String> numbers = split( value, ',' );
List<String> numbers = StringUtils.split( value, ',', true, false );
try {
return new InsetsUIResource(
Integer.parseInt( numbers.get( 0 ) ),
@@ -493,7 +660,7 @@ class UIDefaultsLoader
}
private static Dimension parseDimension( String value ) {
List<String> numbers = split( value, ',' );
List<String> numbers = StringUtils.split( value, ',', true, false );
try {
return new DimensionUIResource(
Integer.parseInt( numbers.get( 0 ) ),
@@ -581,10 +748,10 @@ class UIDefaultsLoader
return null;
}
String function = value.substring( 0, paramsStart ).trim();
String function = StringUtils.substringTrimmed( value, 0, paramsStart );
List<String> params = splitFunctionParams( value.substring( paramsStart + 1, value.length() - 1 ), ',' );
if( params.isEmpty() )
throw new IllegalArgumentException( "missing parameters in function '" + value + "'" );
throwMissingParametersException( value );
if( parseColorDepth > 100 )
throw new IllegalArgumentException( "endless recursion in color function '" + value + "'" );
@@ -592,6 +759,7 @@ class UIDefaultsLoader
parseColorDepth++;
try {
switch( function ) {
case "if": return parseColorIf( value, params, resolver, reportError );
case "rgb": return parseColorRgbOrRgba( false, params, resolver, reportError );
case "rgba": return parseColorRgbOrRgba( true, params, resolver, reportError );
case "hsl": return parseColorHslOrHsla( false, params );
@@ -611,6 +779,7 @@ class UIDefaultsLoader
case "mix": return parseColorMix( null, params, resolver, reportError );
case "tint": return parseColorMix( "#fff", params, resolver, reportError );
case "shade": return parseColorMix( "#000", params, resolver, reportError );
case "contrast": return parseColorContrast( params, resolver, reportError );
}
} finally {
parseColorDepth--;
@@ -619,6 +788,21 @@ class UIDefaultsLoader
throw new IllegalArgumentException( "unknown color function '" + value + "'" );
}
/**
* Syntax: if(condition,trueValue,falseValue)
* <p>
* This "if" function is only used if the "if" is passed as parameter to another
* color function. Otherwise the general "if" function is used.
*/
private static Object parseColorIf( String value, List<String> params, Function<String, String> resolver, boolean reportError ) {
if( params.size() != 3 )
throwMissingParametersException( value );
boolean ifCondition = parseCondition( params.get( 0 ), resolver, Collections.emptyList() );
String ifValue = params.get( ifCondition ? 1 : 2 );
return parseColorOrFunction( resolver.apply( ifValue ), resolver, reportError );
}
/**
* Syntax: rgb(red,green,blue) or rgba(red,green,blue,alpha)
* - red: an integer 0-255 or a percentage 0-100%
@@ -804,14 +988,10 @@ class UIDefaultsLoader
if( color1Str == null )
color1Str = params.get( i++ );
String color2Str = params.get( i++ );
int weight = 50;
if( params.size() > i )
weight = parsePercentage( params.get( i++ ) );
int weight = (params.size() > i) ? parsePercentage( params.get( i ) ) : 50;
// parse second color
String resolvedColor2Str = resolver.apply( color2Str );
ColorUIResource color2 = (ColorUIResource) parseColorOrFunction( resolvedColor2Str, resolver, reportError );
ColorUIResource color2 = (ColorUIResource) parseColorOrFunction( resolver.apply( color2Str ), resolver, reportError );
if( color2 == null )
return null;
@@ -822,6 +1002,34 @@ class UIDefaultsLoader
return parseFunctionBaseColor( color1Str, function, false, resolver, reportError );
}
/**
* Syntax: contrast(color,dark,light[,threshold])
* - color: a color to compare against
* - dark: a designated dark color (e.g. #000) or a color function
* - light: a designated light color (e.g. #fff) or a color function
* - threshold: the threshold (in range 0-100%) to specify where the transition
* from "dark" to "light" is (default is 43%)
*/
private static Object parseColorContrast( List<String> params, Function<String, String> resolver, boolean reportError ) {
String colorStr = params.get( 0 );
String darkStr = params.get( 1 );
String lightStr = params.get( 2 );
int threshold = (params.size() > 3) ? parsePercentage( params.get( 3 ) ) : 43;
// parse color to compare against
ColorUIResource color = (ColorUIResource) parseColorOrFunction( resolver.apply( colorStr ), resolver, reportError );
if( color == null )
return null;
// check luma and determine whether to use dark or light color
String darkOrLightColor = (ColorFunctions.luma( color ) * 100 < threshold)
? lightStr
: darkStr;
// parse dark or light color
return parseColorOrFunction( resolver.apply( darkOrLightColor ), resolver, reportError );
}
private static Object parseFunctionBaseColor( String colorStr, ColorFunction function,
boolean derived, Function<String, String> resolver, boolean reportError )
{
@@ -852,6 +1060,107 @@ class UIDefaultsLoader
return new ColorUIResource( newColor );
}
/**
* Syntax: [normal] [bold|+bold|-bold] [italic|+italic|-italic] [<size>|+<incr>|-<decr>|<percent>%] [family[, family]] [$baseFontKey]
*/
private static Object parseFont( String value ) {
Object font = fontCache.get( value );
if( font != null )
return font;
int style = -1;
int styleChange = 0;
int absoluteSize = 0;
int relativeSize = 0;
float scaleSize = 0;
List<String> families = null;
String baseFontKey = null;
// use StreamTokenizer to split string because it supports quoted strings
StreamTokenizer st = new StreamTokenizer( new StringReader( value ) );
st.resetSyntax();
st.wordChars( ' ' + 1, 255 );
st.whitespaceChars( 0, ' ' );
st.whitespaceChars( ',', ',' ); // ignore ','
st.quoteChar( '"' );
st.quoteChar( '\'' );
try {
while( st.nextToken() != StreamTokenizer.TT_EOF ) {
String param = st.sval;
switch( param ) {
// font style
case "normal":
style = 0;
break;
case "bold":
if( style == -1 )
style = 0;
style |= Font.BOLD;
break;
case "italic":
if( style == -1 )
style = 0;
style |= Font.ITALIC;
break;
case "+bold": styleChange |= Font.BOLD; break;
case "-bold": styleChange |= Font.BOLD << 16; break;
case "+italic": styleChange |= Font.ITALIC; break;
case "-italic": styleChange |= Font.ITALIC << 16; break;
default:
char firstChar = param.charAt( 0 );
if( Character.isDigit( firstChar ) || firstChar == '+' || firstChar == '-' ) {
// font size
if( absoluteSize != 0 || relativeSize != 0 || scaleSize != 0 )
throw new IllegalArgumentException( "size specified more than once in '" + value + "'" );
if( firstChar == '+' || firstChar == '-' )
relativeSize = parseInteger( param, true );
else if( param.endsWith( "%" ) )
scaleSize = parseInteger( param.substring( 0, param.length() - 1 ), true ) / 100f;
else
absoluteSize = parseInteger( param, true );
} else if( firstChar == '$' ) {
// reference to base font
if( baseFontKey != null )
throw new IllegalArgumentException( "baseFontKey specified more than once in '" + value + "'" );
baseFontKey = param.substring( 1 );
} else {
// font family
if( families == null )
families = Collections.singletonList( param );
else {
if( families.size() == 1 )
families = new ArrayList<>( families );
families.add( param );
}
}
break;
}
}
} catch( IOException ex ) {
throw new IllegalArgumentException( ex );
}
if( style != -1 && styleChange != 0 )
throw new IllegalArgumentException( "can not mix absolute style (e.g. 'bold') with derived style (e.g. '+italic') in '" + value + "'" );
if( styleChange != 0 ) {
if( (styleChange & Font.BOLD) != 0 && (styleChange & (Font.BOLD << 16)) != 0 )
throw new IllegalArgumentException( "can not use '+bold' and '-bold' in '" + value + "'" );
if( (styleChange & Font.ITALIC) != 0 && (styleChange & (Font.ITALIC << 16)) != 0 )
throw new IllegalArgumentException( "can not use '+italic' and '-italic' in '" + value + "'" );
}
font = new FlatLaf.ActiveFont( baseFontKey, families, style, styleChange, absoluteSize, relativeSize, scaleSize );
fontCache.put( value, font );
return font;
}
private static int parsePercentage( String value ) {
if( !value.endsWith( "%" ) )
throw new NumberFormatException( "invalid percentage '" + value + "'" );
@@ -868,6 +1177,14 @@ class UIDefaultsLoader
return val;
}
private static Boolean parseBoolean( String value ) {
switch( value ) {
case "false": return false;
case "true": return true;
}
throw new IllegalArgumentException( "invalid boolean '" + value + "'" );
}
private static Character parseCharacter( String value ) {
if( value.length() != 1 )
throw new IllegalArgumentException( "invalid character '" + value + "'" );
@@ -896,6 +1213,20 @@ class UIDefaultsLoader
return null;
}
private static Number parseIntegerOrFloat( String value, boolean reportError ) {
try {
return Integer.parseInt( value );
} catch( NumberFormatException ex ) {
try {
return Float.parseFloat( value );
} catch( NumberFormatException ex2 ) {
if( reportError )
throw new NumberFormatException( "invalid integer or float '" + value + "'" );
}
}
return null;
}
private static Float parseFloat( String value, boolean reportError ) {
try {
return Float.parseFloat( value );
@@ -935,7 +1266,7 @@ class UIDefaultsLoader
}
private static Object parseGrayFilter( String value ) {
List<String> numbers = split( value, ',' );
List<String> numbers = StringUtils.split( value, ',', true, false );
try {
int brightness = Integer.parseInt( numbers.get( 0 ) );
int contrast = Integer.parseInt( numbers.get( 1 ) );
@@ -949,20 +1280,6 @@ class UIDefaultsLoader
}
}
/**
* Split string and trim parts.
*/
private static List<String> split( String str, char delim ) {
List<String> result = StringUtils.split( str, delim );
// trim strings
int size = result.size();
for( int i = 0; i < size; i++ )
result.set( i, result.get( i ).trim() );
return result;
}
/**
* Splits function parameters and allows using functions as parameters.
* In other words: Delimiters surrounded by '(' and ')' are ignored.
@@ -979,11 +1296,11 @@ class UIDefaultsLoader
else if( ch == ')' )
nestLevel--;
else if( nestLevel == 0 && ch == delim ) {
strs.add( str.substring( start, i ).trim() );
strs.add( StringUtils.substringTrimmed( str, start, i ) );
start = i + 1;
}
}
strs.add( str.substring( start ).trim() );
strs.add( StringUtils.substringTrimmed( str, start ) );
return strs;
}
@@ -992,7 +1309,7 @@ class UIDefaultsLoader
* For use in LazyValue to get value for given key from UIManager and report error
* if not found. If key is prefixed by '?', then no error is reported.
*/
private static Object lazyUIManagerGet( String uiKey ) {
static Object lazyUIManagerGet( String uiKey ) {
boolean optional = false;
if( uiKey.startsWith( OPTIONAL_PREFIX ) ) {
uiKey = uiKey.substring( OPTIONAL_PREFIX.length() );
@@ -1004,4 +1321,8 @@ class UIDefaultsLoader
LoggingFacade.INSTANCE.logSevere( "FlatLaf: '" + uiKey + "' not found in UI defaults.", null );
return value;
}
private static void throwMissingParametersException( String value ) {
throw new IllegalArgumentException( "missing parameters in function '" + value + "'" );
}
}

View File

@@ -39,7 +39,7 @@ public abstract class FlatAbstractIcon
{
protected final int width;
protected final int height;
protected final Color color;
protected Color color;
public FlatAbstractIcon( int width, int height, Color color ) {
this.width = width;

View File

@@ -21,7 +21,11 @@ import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.geom.Path2D;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.TableHeaderUI;
import javax.swing.table.JTableHeader;
import com.formdev.flatlaf.ui.FlatTableHeaderUI;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
@@ -35,8 +39,8 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
public class FlatAscendingSortIcon
extends FlatAbstractIcon
{
protected final boolean chevron = FlatUIUtils.isChevron( UIManager.getString( "Component.arrowType" ) );
protected final Color sortIconColor = UIManager.getColor( "Table.sortIconColor" );
protected boolean chevron = FlatUIUtils.isChevron( UIManager.getString( "Component.arrowType" ) );
protected Color sortIconColor = UIManager.getColor( "Table.sortIconColor" );
public FlatAscendingSortIcon() {
super( 10, 5, null );
@@ -44,7 +48,28 @@ public class FlatAscendingSortIcon
@Override
protected void paintIcon( Component c, Graphics2D g ) {
boolean chevron = this.chevron;
Color sortIconColor = this.sortIconColor;
// Because this icons are always shared for all table headers,
// get icon specific style from FlatTableHeaderUI.
JTableHeader tableHeader = (JTableHeader) SwingUtilities.getAncestorOfClass( JTableHeader.class, c );
if( tableHeader != null ) {
TableHeaderUI ui = tableHeader.getUI();
if( ui instanceof FlatTableHeaderUI ) {
FlatTableHeaderUI fui = (FlatTableHeaderUI) ui;
if( fui.arrowType != null )
chevron = FlatUIUtils.isChevron( fui.arrowType );
if( fui.sortIconColor != null )
sortIconColor = fui.sortIconColor;
}
}
g.setColor( sortIconColor );
paintArrow( c, g, chevron );
}
protected void paintArrow( Component c, Graphics2D g, boolean chevron ) {
if( chevron ) {
// chevron arrow
Path2D path = FlatUIUtils.createPath( false, 1,4, 5,0, 9,4 );

View File

@@ -16,12 +16,14 @@
package com.formdev.flatlaf.icons;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics2D;
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.FlatUIUtils;
/**
@@ -38,6 +40,15 @@ 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 );
}
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
/*

View File

@@ -23,83 +23,115 @@ 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.FlatUIUtils;
/**
* Icon for {@link javax.swing.JCheckBox}.
*
* Note: If Component.focusWidth is greater than zero, then the outline focus border
* <p>
* <strong>Note</strong>:
* If Component.focusWidth is greater than zero, then the outer focus border
* is painted outside of the icon bounds. Make sure that the checkbox
* has margins, which are equal or greater than focusWidth.
*
* @uiDefault CheckBox.icon.style String optional; "outline"/null (default) or "filled"
* @uiDefault CheckBox.icon.style String optional; "outlined"/null (default) or "filled"
* @uiDefault Component.focusWidth int
* @uiDefault Component.borderWidth int
* @uiDefault Component.focusColor Color
* @uiDefault CheckBox.icon.focusWidth int optional; defaults to Component.focusWidth
* @uiDefault CheckBox.icon.focusWidth int or float optional; defaults to Component.focusWidth
* @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.arc int
*
* @uiDefault CheckBox.icon.focusColor Color optional; defaults to Component.focusColor
* @uiDefault CheckBox.icon.borderColor Color
* @uiDefault CheckBox.icon.background Color
* @uiDefault CheckBox.icon.selectedBorderColor Color
* @uiDefault CheckBox.icon.selectedBackground Color
* @uiDefault CheckBox.icon.checkmarkColor Color
*
* @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.focusedBorderColor Color optional
* @uiDefault CheckBox.icon.focusedBackground Color optional
* @uiDefault CheckBox.icon.selectedFocusedBorderColor Color optional; CheckBox.icon.focusedBorderColor is used if not specified
* @uiDefault CheckBox.icon.selectedFocusedBackground Color optional; CheckBox.icon.focusedBackground is used if not specified
* @uiDefault CheckBox.icon.selectedFocusedCheckmarkColor Color optional; CheckBox.icon.checkmarkColor is used if not specified
* @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.hoverBorderColor Color optional
* @uiDefault CheckBox.icon.hoverBackground Color optional
* @uiDefault CheckBox.icon.selectedHoverBackground Color optional; CheckBox.icon.hoverBackground is used if not specified
* @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.pressedBorderColor Color optional
* @uiDefault CheckBox.icon.pressedBackground Color optional
* @uiDefault CheckBox.icon.selectedPressedBackground Color optional; CheckBox.icon.pressedBackground is used if not specified
* @uiDefault CheckBox.arc int
* @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
*
* @author Karl Tauber
*/
public class FlatCheckBoxIcon
extends FlatAbstractIcon
{
protected final String style = UIManager.getString( "CheckBox.icon.style" );
public final int focusWidth = getUIInt( "CheckBox.icon.focusWidth",
UIManager.getInt( "Component.focusWidth" ), style );
protected final Color focusColor = FlatUIUtils.getUIColor( "CheckBox.icon.focusColor",
UIManager.getColor( "Component.focusColor" ) );
protected final int arc = FlatUIUtils.getUIInt( "CheckBox.arc", 2 );
protected final String style = UIManager.getString( getPropertyPrefix() + "icon.style" );
@Styleable protected float focusWidth = getUIFloat( "CheckBox.icon.focusWidth", UIManager.getInt( "Component.focusWidth" ), style );
@Styleable protected Color focusColor = FlatUIUtils.getUIColor( "CheckBox.icon.focusColor", UIManager.getColor( "Component.focusColor" ) );
/** @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 );
@Styleable protected int arc = FlatUIUtils.getUIInt( "CheckBox.arc", 2 );
// enabled
protected final Color borderColor = getUIColor( "CheckBox.icon.borderColor", style );
protected final Color background = getUIColor( "CheckBox.icon.background", style );
protected final Color selectedBorderColor = getUIColor( "CheckBox.icon.selectedBorderColor", style );
protected final Color selectedBackground = getUIColor( "CheckBox.icon.selectedBackground", style );
protected final Color checkmarkColor = getUIColor( "CheckBox.icon.checkmarkColor", style );
@Styleable protected Color borderColor = getUIColor( "CheckBox.icon.borderColor", style );
@Styleable protected Color background = getUIColor( "CheckBox.icon.background", style );
@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 );
// disabled
protected final Color disabledBorderColor = getUIColor( "CheckBox.icon.disabledBorderColor", style );
protected final Color disabledBackground = getUIColor( "CheckBox.icon.disabledBackground", style );
protected final Color disabledCheckmarkColor = getUIColor( "CheckBox.icon.disabledCheckmarkColor", style );
@Styleable protected Color disabledBorderColor = getUIColor( "CheckBox.icon.disabledBorderColor", style );
@Styleable protected Color disabledBackground = getUIColor( "CheckBox.icon.disabledBackground", style );
/** @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 );
// focused
protected final Color focusedBorderColor = getUIColor( "CheckBox.icon.focusedBorderColor", style );
protected final Color focusedBackground = getUIColor( "CheckBox.icon.focusedBackground", style );
protected final Color selectedFocusedBorderColor = getUIColor( "CheckBox.icon.selectedFocusedBorderColor", style );
protected final Color selectedFocusedBackground = getUIColor( "CheckBox.icon.selectedFocusedBackground", style );
protected final Color selectedFocusedCheckmarkColor = getUIColor( "CheckBox.icon.selectedFocusedCheckmarkColor", style );
@Styleable protected Color focusedBorderColor = getUIColor( "CheckBox.icon.focusedBorderColor", style );
@Styleable protected Color focusedBackground = getUIColor( "CheckBox.icon.focusedBackground", style );
/** @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 );
// hover
protected final Color hoverBorderColor = getUIColor( "CheckBox.icon.hoverBorderColor", style );
protected final Color hoverBackground = getUIColor( "CheckBox.icon.hoverBackground", style );
protected final Color selectedHoverBackground = getUIColor( "CheckBox.icon.selectedHoverBackground", style );
@Styleable protected Color hoverBorderColor = getUIColor( "CheckBox.icon.hoverBorderColor", style );
@Styleable protected Color hoverBackground = getUIColor( "CheckBox.icon.hoverBackground", style );
/** @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 );
// pressed
protected final Color pressedBackground = getUIColor( "CheckBox.icon.pressedBackground", style );
protected final Color selectedPressedBackground = getUIColor( "CheckBox.icon.selectedPressedBackground", style );
/** @since 2 */ @Styleable protected Color pressedBorderColor = getUIColor( "CheckBox.icon.pressedBorderColor", style );
@Styleable protected Color pressedBackground = getUIColor( "CheckBox.icon.pressedBackground", style );
/** @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 );
protected String getPropertyPrefix() {
return "CheckBox.";
}
protected static Color getUIColor( String key, String style ) {
if( style != null ) {
@@ -110,13 +142,14 @@ public class FlatCheckBoxIcon
return UIManager.getColor( key );
}
protected static int getUIInt( String key, int defaultValue, String style ) {
/** @since 2 */
protected static float getUIFloat( String key, float defaultValue, String style ) {
if( style != null ) {
Object value = UIManager.get( styleKey( key, style ) );
if( value instanceof Integer )
return (Integer) value;
float value = FlatUIUtils.getUIFloat( styleKey( key, style ), Float.MIN_VALUE );
if( value != Float.MIN_VALUE )
return value;
}
return FlatUIUtils.getUIInt( key, defaultValue );
return FlatUIUtils.getUIFloat( key, defaultValue );
}
private static String styleKey( String key, String style ) {
@@ -129,11 +162,26 @@ 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 );
}
@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;
// paint focused border
if( isFocused && focusWidth > 0 && FlatButtonUI.isFocusPainted( c ) ) {
@@ -143,7 +191,7 @@ public class FlatCheckBoxIcon
// paint border
g.setColor( getBorderColor( c, selected ) );
paintBorder( c, g );
paintBorder( c, g, bw );
// paint background
Color bg = FlatUIUtils.deriveColor( getBackground( c, selected ),
@@ -151,14 +199,14 @@ public class FlatCheckBoxIcon
if( bg.getAlpha() < 255 ) {
// fill background with default color before filling with non-opaque background
g.setColor( selected ? selectedBackground : background );
paintBackground( c, g );
paintBackground( c, g, bw );
}
g.setColor( bg );
paintBackground( c, g );
paintBackground( c, g, bw );
// paint checkmark
if( selected || indeterminate ) {
g.setColor( getCheckmarkColor( c, selected, isFocused ) );
if( selected ) {
g.setColor( getCheckmarkColor( c ) );
if( indeterminate )
paintIndeterminate( c, g );
else
@@ -167,20 +215,25 @@ public class FlatCheckBoxIcon
}
protected void paintFocusBorder( Component c, Graphics2D g ) {
// the outline focus border is painted outside of the icon
int wh = ICON_SIZE - 1 + (focusWidth * 2);
int arcwh = arc + (focusWidth * 2);
g.fillRoundRect( -focusWidth + 1, -focusWidth, wh, wh, arcwh, arcwh );
// the outer focus border is painted outside of the icon
float wh = ICON_SIZE - 1 + (focusWidth * 2);
float arcwh = arc + (focusWidth * 2);
g.fill( new RoundRectangle2D.Float( -focusWidth + 1, -focusWidth, wh, wh, arcwh, arcwh ) );
}
protected void paintBorder( Component c, Graphics2D g ) {
protected void paintBorder( Component c, Graphics2D g, float borderWidth ) {
if( borderWidth == 0 )
return;
int arcwh = arc;
g.fillRoundRect( 1, 0, 14, 14, arcwh, arcwh );
}
protected void paintBackground( Component c, Graphics2D g ) {
int arcwh = arc - 1;
g.fillRoundRect( 2, 1, 12, 12, arcwh, arcwh );
protected void paintBackground( Component c, Graphics2D g, float borderWidth ) {
float xy = borderWidth;
float wh = 14 - (borderWidth * 2);
float arcwh = arc - borderWidth;
g.fill( new RoundRectangle2D.Float( 1 + xy, xy, wh, wh, arcwh, arcwh ) );
}
protected void paintCheckmark( Component c, Graphics2D g ) {
@@ -205,6 +258,11 @@ public class FlatCheckBoxIcon
return c instanceof AbstractButton && ((AbstractButton)c).isSelected();
}
/** @since 2 */
public float getFocusWidth() {
return focusWidth;
}
protected Color getFocusColor( Component c ) {
return focusColor;
}
@@ -212,26 +270,27 @@ public class FlatCheckBoxIcon
protected Color getBorderColor( Component c, boolean selected ) {
return FlatButtonUI.buttonStateColor( c,
selected ? selectedBorderColor : borderColor,
disabledBorderColor,
selected && selectedFocusedBorderColor != null ? selectedFocusedBorderColor : focusedBorderColor,
hoverBorderColor,
null );
(selected && disabledSelectedBorderColor != null) ? disabledSelectedBorderColor : disabledBorderColor,
(selected && focusedSelectedBorderColor != null) ? focusedSelectedBorderColor : focusedBorderColor,
(selected && hoverSelectedBorderColor != null) ? hoverSelectedBorderColor : hoverBorderColor,
(selected && pressedSelectedBorderColor != null) ? pressedSelectedBorderColor : pressedBorderColor );
}
protected Color getBackground( Component c, boolean selected ) {
return FlatButtonUI.buttonStateColor( c,
selected ? selectedBackground : background,
disabledBackground,
(selected && selectedFocusedBackground != null) ? selectedFocusedBackground : focusedBackground,
(selected && selectedHoverBackground != null) ? selectedHoverBackground : hoverBackground,
(selected && selectedPressedBackground != null) ? selectedPressedBackground : pressedBackground );
(selected && disabledSelectedBackground != null) ? disabledSelectedBackground : disabledBackground,
(selected && focusedSelectedBackground != null) ? focusedSelectedBackground : focusedBackground,
(selected && hoverSelectedBackground != null) ? hoverSelectedBackground : hoverBackground,
(selected && pressedSelectedBackground != null) ? pressedSelectedBackground : pressedBackground );
}
protected Color getCheckmarkColor( Component c, boolean selected, boolean isFocused ) {
return c.isEnabled()
? ((selected && isFocused && selectedFocusedCheckmarkColor != null)
? selectedFocusedCheckmarkColor
: checkmarkColor)
: disabledCheckmarkColor;
protected Color getCheckmarkColor( Component c ) {
return FlatButtonUI.buttonStateColor( c,
checkmarkColor,
disabledCheckmarkColor,
focusedCheckmarkColor,
hoverCheckmarkColor,
pressedCheckmarkColor );
}
}

View File

@@ -21,15 +21,18 @@ 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;
/**
* Icon for {@link javax.swing.JCheckBoxMenuItem}.
*
* @uiDefault MenuItemCheckBox.icon.checkmarkColor Color
* @uiDefault MenuItemCheckBox.icon.disabledCheckmarkColor Color
* @uiDefault CheckBoxMenuItem.icon.checkmarkColor Color
* @uiDefault CheckBoxMenuItem.icon.disabledCheckmarkColor Color
* @uiDefault MenuItem.selectionForeground Color
* @uiDefault MenuItem.selectionType String
*
@@ -38,14 +41,24 @@ import javax.swing.UIManager;
public class FlatCheckBoxMenuItemIcon
extends FlatAbstractIcon
{
protected final Color checkmarkColor = UIManager.getColor( "MenuItemCheckBox.icon.checkmarkColor" );
protected final Color disabledCheckmarkColor = UIManager.getColor( "MenuItemCheckBox.icon.disabledCheckmarkColor" );
protected final Color selectionForeground = UIManager.getColor( "MenuItem.selectionForeground" );
@Styleable protected Color checkmarkColor = UIManager.getColor( "CheckBoxMenuItem.icon.checkmarkColor" );
@Styleable protected Color disabledCheckmarkColor = UIManager.getColor( "CheckBoxMenuItem.icon.disabledCheckmarkColor" );
@Styleable protected Color selectionForeground = UIManager.getColor( "MenuItem.selectionForeground" );
public 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 );
}
@Override
protected void paintIcon( Component c, Graphics2D g2 ) {
boolean selected = (c instanceof AbstractButton) && ((AbstractButton)c).isSelected();

View File

@@ -22,9 +22,12 @@ import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
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.FlatUIUtils;
/**
@@ -40,17 +43,35 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
public class FlatClearIcon
extends FlatAbstractIcon
{
protected Color clearIconColor = UIManager.getColor( "SearchField.clearIconColor" );
protected Color clearIconHoverColor = UIManager.getColor( "SearchField.clearIconHoverColor" );
protected Color clearIconPressedColor = UIManager.getColor( "SearchField.clearIconPressedColor" );
@Styleable protected Color clearIconColor = UIManager.getColor( "SearchField.clearIconColor" );
@Styleable protected Color clearIconHoverColor = UIManager.getColor( "SearchField.clearIconHoverColor" );
@Styleable protected Color clearIconPressedColor = UIManager.getColor( "SearchField.clearIconPressedColor" );
private final boolean ignoreButtonState;
public FlatClearIcon() {
this( false );
}
/** @since 2 */
public FlatClearIcon( boolean ignoreButtonState ) {
super( 16, 16, null );
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 );
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
if( c instanceof AbstractButton ) {
if( !ignoreButtonState && c instanceof AbstractButton ) {
ButtonModel model = ((AbstractButton)c).getModel();
if( model.isPressed() || model.isRollover() ) {
/*

View File

@@ -17,11 +17,9 @@
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 javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
@@ -33,18 +31,14 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
* @author Karl Tauber
*/
public class FlatDescendingSortIcon
extends FlatAbstractIcon
extends FlatAscendingSortIcon
{
protected final boolean chevron = FlatUIUtils.isChevron( UIManager.getString( "Component.arrowType" ) );
protected final Color sortIconColor = UIManager.getColor( "Table.sortIconColor" );
public FlatDescendingSortIcon() {
super( 10, 5, null );
super();
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
g.setColor( sortIconColor );
protected void paintArrow( Component c, Graphics2D g, boolean chevron ) {
if( chevron ) {
// chevron arrow
Path2D path = FlatUIUtils.createPath( false, 1,0, 5,4, 9,0 );

View File

@@ -22,8 +22,11 @@ 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.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.FlatUIUtils;
/**
@@ -50,29 +53,37 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
public class FlatHelpButtonIcon
extends FlatAbstractIcon
{
protected final int focusWidth = UIManager.getInt( "Component.focusWidth" );
protected final Color focusColor = UIManager.getColor( "Component.focusColor" );
protected final float innerFocusWidth = FlatUIUtils.getUIFloat( "HelpButton.innerFocusWidth", FlatUIUtils.getUIFloat( "Component.innerFocusWidth", 0 ) );
protected final int borderWidth = FlatUIUtils.getUIInt( "HelpButton.borderWidth", 1 );
@Styleable protected int focusWidth = UIManager.getInt( "Component.focusWidth" );
@Styleable protected Color focusColor = UIManager.getColor( "Component.focusColor" );
@Styleable protected float innerFocusWidth = FlatUIUtils.getUIFloat( "HelpButton.innerFocusWidth", FlatUIUtils.getUIFloat( "Component.innerFocusWidth", 0 ) );
@Styleable protected int borderWidth = FlatUIUtils.getUIInt( "HelpButton.borderWidth", 1 );
protected final Color borderColor = UIManager.getColor( "HelpButton.borderColor" );
protected final Color disabledBorderColor = UIManager.getColor( "HelpButton.disabledBorderColor" );
protected final Color focusedBorderColor = UIManager.getColor( "HelpButton.focusedBorderColor" );
protected final Color hoverBorderColor = UIManager.getColor( "HelpButton.hoverBorderColor" );
protected final Color background = UIManager.getColor( "HelpButton.background" );
protected final Color disabledBackground = UIManager.getColor( "HelpButton.disabledBackground" );
protected final Color focusedBackground = UIManager.getColor( "HelpButton.focusedBackground" );
protected final Color hoverBackground = UIManager.getColor( "HelpButton.hoverBackground" );
protected final Color pressedBackground = UIManager.getColor( "HelpButton.pressedBackground" );
protected final Color questionMarkColor = UIManager.getColor( "HelpButton.questionMarkColor" );
protected final Color disabledQuestionMarkColor = UIManager.getColor( "HelpButton.disabledQuestionMarkColor" );
protected final int iconSize = 22 + (focusWidth * 2);
@Styleable protected Color borderColor = UIManager.getColor( "HelpButton.borderColor" );
@Styleable protected Color disabledBorderColor = UIManager.getColor( "HelpButton.disabledBorderColor" );
@Styleable protected Color focusedBorderColor = UIManager.getColor( "HelpButton.focusedBorderColor" );
@Styleable protected Color hoverBorderColor = UIManager.getColor( "HelpButton.hoverBorderColor" );
@Styleable protected Color background = UIManager.getColor( "HelpButton.background" );
@Styleable protected Color disabledBackground = UIManager.getColor( "HelpButton.disabledBackground" );
@Styleable protected Color focusedBackground = UIManager.getColor( "HelpButton.focusedBackground" );
@Styleable protected Color hoverBackground = UIManager.getColor( "HelpButton.hoverBackground" );
@Styleable protected Color pressedBackground = UIManager.getColor( "HelpButton.pressedBackground" );
@Styleable protected Color questionMarkColor = UIManager.getColor( "HelpButton.questionMarkColor" );
@Styleable protected Color disabledQuestionMarkColor = UIManager.getColor( "HelpButton.disabledQuestionMarkColor" );
public 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 );
}
@Override
protected void paintIcon( Component c, Graphics2D g2 ) {
/*
@@ -89,7 +100,7 @@ public class FlatHelpButtonIcon
boolean focused = FlatUIUtils.isPermanentFocusOwner( c );
float xy = 0.5f;
float wh = iconSize - 1;
float wh = iconSize() - 1;
// paint outer focus border
if( focused && FlatButtonUI.isFocusPainted( c ) ) {
@@ -151,11 +162,15 @@ public class FlatHelpButtonIcon
@Override
public int getIconWidth() {
return scale( iconSize );
return scale( iconSize() );
}
@Override
public int getIconHeight() {
return scale( iconSize );
return scale( iconSize() );
}
private int iconSize() {
return 22 + (focusWidth * 2);
}
}

View File

@@ -21,9 +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;
/**
* "arrow" icon for {@link javax.swing.JMenu}.
@@ -39,22 +42,32 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
public class FlatMenuArrowIcon
extends FlatAbstractIcon
{
protected final boolean chevron = FlatUIUtils.isChevron( UIManager.getString( "Component.arrowType" ) );
protected final Color arrowColor = UIManager.getColor( "Menu.icon.arrowColor" );
protected final Color disabledArrowColor = UIManager.getColor( "Menu.icon.disabledArrowColor" );
protected final Color selectionForeground = UIManager.getColor( "Menu.selectionForeground" );
@Styleable protected String arrowType = UIManager.getString( "Component.arrowType" );
@Styleable protected Color arrowColor = UIManager.getColor( "Menu.icon.arrowColor" );
@Styleable protected Color disabledArrowColor = UIManager.getColor( "Menu.icon.disabledArrowColor" );
@Styleable protected Color selectionForeground = UIManager.getColor( "Menu.selectionForeground" );
public 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 );
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
if( !c.getComponentOrientation().isLeftToRight() )
g.rotate( Math.toRadians( 180 ), width / 2., height / 2. );
g.setColor( getArrowColor( c ) );
if( chevron ) {
if( FlatUIUtils.isChevron( arrowType ) ) {
// chevron arrow
Path2D path = FlatUIUtils.createPath( false, 1,1, 5,5, 1,9 );
g.setStroke( new BasicStroke( 1f ) );

View File

@@ -21,14 +21,16 @@ import java.awt.Graphics;
import java.awt.Graphics2D;
/**
* "arrow" icon for {@link javax.swing.JMenuItem}.
* "arrow" icon for {@link javax.swing.JMenuItem}, {@link javax.swing.JCheckBoxMenuItem}
* and {@link javax.swing.JRadioButtonMenuItem}.
*
* @author Karl Tauber
*/
public class FlatMenuItemArrowIcon
extends FlatMenuArrowIcon
extends FlatAbstractIcon
{
public FlatMenuItemArrowIcon() {
super( 6, 10, null );
}
@Override

View File

@@ -19,38 +19,51 @@ package com.formdev.flatlaf.icons;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
/**
* Icon for {@link javax.swing.JRadioButton}.
*
* Note: If Component.focusWidth is greater than zero, then the outline focus border
* <p>
* <strong>Note</strong>:
* If Component.focusWidth is greater than zero, then the outer focus border
* is painted outside of the icon bounds. Make sure that the radiobutton
* has margins, which are equal or greater than focusWidth.
*
* @uiDefault RadioButton.icon.centerDiameter int
* @uiDefault RadioButton.icon.style String optional; "outlined"/null (default) or "filled"
* @uiDefault RadioButton.icon.centerDiameter int or float
*
* @author Karl Tauber
*/
public class FlatRadioButtonIcon
extends FlatCheckBoxIcon
{
protected final int centerDiameter = getUIInt( "RadioButton.icon.centerDiameter", 8, style );
@Styleable protected float centerDiameter = getUIFloat( "RadioButton.icon.centerDiameter", 8, style );
@Override
protected void paintFocusBorder( Component c, Graphics2D g ) {
// the outline focus border is painted outside of the icon
int wh = ICON_SIZE + (focusWidth * 2);
g.fillOval( -focusWidth, -focusWidth, wh, wh );
protected String getPropertyPrefix() {
return "RadioButton.";
}
@Override
protected void paintBorder( Component c, Graphics2D g ) {
protected void paintFocusBorder( Component c, Graphics2D g ) {
// the outer focus border is painted outside of the icon
float wh = ICON_SIZE + (focusWidth * 2);
g.fill( new Ellipse2D.Float( -focusWidth, -focusWidth, wh, wh ) );
}
@Override
protected void paintBorder( Component c, Graphics2D g, float borderWidth ) {
if( borderWidth == 0 )
return;
g.fillOval( 0, 0, 15, 15 );
}
@Override
protected void paintBackground( Component c, Graphics2D g ) {
g.fillOval( 1, 1, 13, 13 );
protected void paintBackground( Component c, Graphics2D g, float borderWidth ) {
float xy = borderWidth;
float wh = 15 - (borderWidth * 2);
g.fill( new Ellipse2D.Float( xy, xy, wh, wh ) );
}
@Override

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2021 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.icons;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import javax.swing.UIManager;
/**
* "eye" icon for {@link javax.swing.JPasswordField}.
*
* @uiDefault PasswordField.revealIconColor Color
*
* @author Karl Tauber
* @since 2
*/
public class FlatRevealIcon
extends FlatAbstractIcon
{
public FlatRevealIcon() {
super( 16, 16, UIManager.getColor( "PasswordField.revealIconColor" ) );
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD );
path.append( new Ellipse2D.Float( 5.15f, 6.15f, 5.7f, 5.7f ), false );
path.append( new Ellipse2D.Float( 6, 7, 4, 4 ), false );
g.fill( path );
Path2D path2 = new Path2D.Float( Path2D.WIND_EVEN_ODD );
path2.append( new Ellipse2D.Float( 2.15f, 4.15f, 11.7f, 11.7f ), false );
path2.append( new Ellipse2D.Float( 3, 5, 10, 10 ), false );
Area area = new Area( path2 );
area.subtract( new Area( new Rectangle2D.Float( 0, 9.5f, 16, 16 ) ) );
g.fill( area );
}
}

View File

@@ -21,8 +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.FlatUIUtils;
/**
@@ -38,12 +41,30 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
public class FlatSearchIcon
extends FlatAbstractIcon
{
protected Color searchIconColor = UIManager.getColor( "SearchField.searchIconColor" );
protected Color searchIconHoverColor = UIManager.getColor( "SearchField.searchIconHoverColor" );
protected Color searchIconPressedColor = UIManager.getColor( "SearchField.searchIconPressedColor" );
@Styleable protected Color searchIconColor = UIManager.getColor( "SearchField.searchIconColor" );
@Styleable protected Color searchIconHoverColor = UIManager.getColor( "SearchField.searchIconHoverColor" );
@Styleable protected Color searchIconPressedColor = UIManager.getColor( "SearchField.searchIconPressedColor" );
private final boolean ignoreButtonState;
public FlatSearchIcon() {
this( false );
}
/** @since 2 */
public FlatSearchIcon( boolean ignoreButtonState ) {
super( 16, 16, null );
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 );
}
@Override
@@ -57,8 +78,10 @@ public class FlatSearchIcon
</svg>
*/
g.setColor( FlatButtonUI.buttonStateColor( c, searchIconColor, searchIconColor,
null, searchIconHoverColor, searchIconPressedColor ) );
g.setColor( ignoreButtonState
? searchIconColor
: FlatButtonUI.buttonStateColor( c, searchIconColor, searchIconColor,
null, searchIconHoverColor, searchIconPressedColor ) );
// paint magnifier
Area area = new Area( new Ellipse2D.Float( 2, 2, 10, 10 ) );

View File

@@ -30,6 +30,12 @@ public class FlatSearchWithHistoryIcon
extends FlatSearchIcon
{
public FlatSearchWithHistoryIcon() {
this( false );
}
/** @since 2 */
public FlatSearchWithHistoryIcon( boolean ignoreButtonState ) {
super( ignoreButtonState );
}
@Override

View File

@@ -23,8 +23,11 @@ import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.geom.Line2D;
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.FlatUIUtils;
/**
@@ -47,39 +50,49 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
public class FlatTabbedPaneCloseIcon
extends FlatAbstractIcon
{
protected final Dimension size = UIManager.getDimension( "TabbedPane.closeSize" );
protected final int arc = UIManager.getInt( "TabbedPane.closeArc" );
protected final float crossPlainSize = FlatUIUtils.getUIFloat( "TabbedPane.closeCrossPlainSize", 7.5f );
protected final float crossFilledSize = FlatUIUtils.getUIFloat( "TabbedPane.closeCrossFilledSize", crossPlainSize );
protected final float closeCrossLineWidth = FlatUIUtils.getUIFloat( "TabbedPane.closeCrossLineWidth", 1f );
protected final Color background = UIManager.getColor( "TabbedPane.closeBackground" );
protected final Color foreground = UIManager.getColor( "TabbedPane.closeForeground" );
protected final Color hoverBackground = UIManager.getColor( "TabbedPane.closeHoverBackground" );
protected final Color hoverForeground = UIManager.getColor( "TabbedPane.closeHoverForeground" );
protected final Color pressedBackground = UIManager.getColor( "TabbedPane.closePressedBackground" );
protected final Color pressedForeground = UIManager.getColor( "TabbedPane.closePressedForeground" );
@Styleable protected Dimension closeSize = UIManager.getDimension( "TabbedPane.closeSize" );
@Styleable protected int closeArc = UIManager.getInt( "TabbedPane.closeArc" );
@Styleable protected float closeCrossPlainSize = FlatUIUtils.getUIFloat( "TabbedPane.closeCrossPlainSize", 7.5f );
@Styleable protected float closeCrossFilledSize = FlatUIUtils.getUIFloat( "TabbedPane.closeCrossFilledSize", closeCrossPlainSize );
@Styleable protected float closeCrossLineWidth = FlatUIUtils.getUIFloat( "TabbedPane.closeCrossLineWidth", 1f );
@Styleable protected Color closeBackground = UIManager.getColor( "TabbedPane.closeBackground" );
@Styleable protected Color closeForeground = UIManager.getColor( "TabbedPane.closeForeground" );
@Styleable protected Color closeHoverBackground = UIManager.getColor( "TabbedPane.closeHoverBackground" );
@Styleable protected Color closeHoverForeground = UIManager.getColor( "TabbedPane.closeHoverForeground" );
@Styleable protected Color closePressedBackground = UIManager.getColor( "TabbedPane.closePressedBackground" );
@Styleable protected Color closePressedForeground = UIManager.getColor( "TabbedPane.closePressedForeground" );
public 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 );
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
// paint background
Color bg = FlatButtonUI.buttonStateColor( c, background, null, null, hoverBackground, pressedBackground );
Color bg = FlatButtonUI.buttonStateColor( c, closeBackground, null, null, closeHoverBackground, closePressedBackground );
if( bg != null ) {
g.setColor( FlatUIUtils.deriveColor( bg, c.getBackground() ) );
g.fillRoundRect( (width - size.width) / 2, (height - size.height) / 2,
size.width, size.height, arc, arc );
g.fillRoundRect( (width - closeSize.width) / 2, (height - closeSize.height) / 2,
closeSize.width, closeSize.height, closeArc, closeArc );
}
// set cross color
Color fg = FlatButtonUI.buttonStateColor( c, foreground, null, null, hoverForeground, pressedForeground );
Color fg = FlatButtonUI.buttonStateColor( c, closeForeground, null, null, closeHoverForeground, closePressedForeground );
g.setColor( FlatUIUtils.deriveColor( fg, c.getForeground() ) );
float mx = width / 2;
float my = height / 2;
float r = ((bg != null) ? crossFilledSize : crossPlainSize) / 2;
float r = ((bg != null) ? closeCrossFilledSize : closeCrossPlainSize) / 2;
// paint cross
Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD );

View File

@@ -37,6 +37,8 @@ public class FlatTreeClosedIcon
@Override
protected void paintIcon( Component c, Graphics2D g ) {
FlatTreeCollapsedIcon.setStyleColorFromTreeUI( c, g, ui -> ui.iconClosedColor );
/*
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<polygon fill="#6E6E6E" fill-rule="evenodd" points="1 2 6 2 8 4 15 4 15 13 1 13"/>

View File

@@ -19,7 +19,12 @@ package com.formdev.flatlaf.icons;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics2D;
import java.util.function.Function;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.TreeUI;
import com.formdev.flatlaf.ui.FlatTreeUI;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
@@ -46,8 +51,12 @@ public class FlatTreeCollapsedIcon
@Override
protected void paintIcon( Component c, Graphics2D g ) {
setStyleColorFromTreeUI( c, g );
rotate( c, g );
String arrowType = getStyleFromTreeUI( c, ui -> ui.iconArrowType );
boolean chevron = (arrowType != null) ? FlatUIUtils.isChevron( arrowType ) : this.chevron;
if( chevron ) {
// chevron arrow
g.fill( FlatUIUtils.createPath( 3,1, 3,2.5, 6,5.5, 3,8.5, 3,10, 4.5,10, 9,5.5, 4.5,1 ) );
@@ -57,8 +66,34 @@ public class FlatTreeCollapsedIcon
}
}
void setStyleColorFromTreeUI( Component c, Graphics2D g ) {
setStyleColorFromTreeUI( c, g, ui -> ui.iconCollapsedColor );
}
void rotate( Component c, Graphics2D g ) {
if( !c.getComponentOrientation().isLeftToRight() )
g.rotate( Math.toRadians( 180 ), width / 2., height / 2. );
}
/**
* Because this icons are always shared for all trees,
* get icon specific style from FlatTreeUI.
*/
static <T> T getStyleFromTreeUI( Component c, Function<FlatTreeUI, T> f ) {
JTree tree = (c instanceof JTree)
? (JTree) c
: (JTree) SwingUtilities.getAncestorOfClass( JTree.class, c );
if( tree != null ) {
TreeUI ui = tree.getUI();
if( ui instanceof FlatTreeUI )
return f.apply( (FlatTreeUI) ui );
}
return null;
}
static void setStyleColorFromTreeUI( Component c, Graphics2D g, Function<FlatTreeUI, Color> f ) {
Color color = getStyleFromTreeUI( c, f );
if( color != null )
g.setColor( color );
}
}

View File

@@ -34,6 +34,11 @@ public class FlatTreeExpandedIcon
super( UIManager.getColor( "Tree.icon.expandedColor" ) );
}
@Override
void setStyleColorFromTreeUI( Component c, Graphics2D g ) {
setStyleColorFromTreeUI( c, g, ui -> ui.iconExpandedColor );
}
@Override
void rotate( Component c, Graphics2D g ) {
g.rotate( Math.toRadians( 90 ), width / 2., height / 2. );

View File

@@ -37,6 +37,8 @@ public class FlatTreeLeafIcon
@Override
protected void paintIcon( Component c, Graphics2D g ) {
FlatTreeCollapsedIcon.setStyleColorFromTreeUI( c, g, ui -> ui.iconLeafColor );
/*
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<g fill="none" fill-rule="evenodd">

View File

@@ -37,6 +37,8 @@ public class FlatTreeOpenIcon
@Override
protected void paintIcon( Component c, Graphics2D g ) {
FlatTreeCollapsedIcon.setStyleColorFromTreeUI( c, g, ui -> ui.iconOpenColor );
/*
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<g fill="none" fill-rule="evenodd">

View File

@@ -39,13 +39,13 @@ public class FlatArrowButton
{
public static final int DEFAULT_ARROW_WIDTH = 8;
protected final boolean chevron;
protected final Color foreground;
protected final Color disabledForeground;
protected final Color hoverForeground;
protected final Color hoverBackground;
protected final Color pressedForeground;
protected final Color pressedBackground;
protected boolean chevron;
protected Color foreground;
protected Color disabledForeground;
protected Color hoverForeground;
protected Color hoverBackground;
protected Color pressedForeground;
protected Color pressedBackground;
private int arrowWidth = DEFAULT_ARROW_WIDTH;
private float xOffset = 0;
@@ -58,14 +58,8 @@ public class FlatArrowButton
Color hoverForeground, Color hoverBackground, Color pressedForeground, Color pressedBackground )
{
super( direction, Color.WHITE, Color.WHITE, Color.WHITE, Color.WHITE );
this.chevron = FlatUIUtils.isChevron( type );
this.foreground = foreground;
this.disabledForeground = disabledForeground;
this.hoverForeground = hoverForeground;
this.hoverBackground = hoverBackground;
this.pressedForeground = pressedForeground;
this.pressedBackground = pressedBackground;
updateStyle( type, foreground, disabledForeground, hoverForeground, hoverBackground,
pressedForeground, pressedBackground );
setOpaque( false );
setBorder( null );
@@ -101,6 +95,19 @@ public class FlatArrowButton
}
}
/** @since 2 */
public void updateStyle( String type, Color foreground, Color disabledForeground,
Color hoverForeground, Color hoverBackground, Color pressedForeground, Color pressedBackground )
{
this.chevron = FlatUIUtils.isChevron( type );
this.foreground = foreground;
this.disabledForeground = disabledForeground;
this.hoverForeground = hoverForeground;
this.hoverBackground = hoverBackground;
this.pressedForeground = pressedForeground;
this.pressedBackground = pressedBackground;
}
public int getArrowWidth() {
return arrowWidth;
}

View File

@@ -23,6 +23,7 @@ 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;
@@ -31,21 +32,25 @@ import javax.swing.JViewport;
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.util.DerivedColor;
/**
* Border for various components (e.g. {@link javax.swing.JTextField}).
*
* <p>
* There is empty space around the component border, if Component.focusWidth is greater than zero,
* which is used to paint outer focus border.
*
* <p>
* Because there is empty space (if outer focus border is not painted),
* UI delegates that use this border (or subclasses) must invoke
* {@link FlatUIUtils#paintParentBackground} to paint the empty space correctly.
* {@link FlatUIUtils#paintParentBackground} to fill the empty space correctly.
*
* @uiDefault Component.focusWidth int
* @uiDefault Component.innerFocusWidth int or float
* @uiDefault Component.innerOutlineWidth int or float
* @uiDefault Component.borderWidth int or float
*
* @uiDefault Component.focusColor Color
* @uiDefault Component.borderColor Color
* @uiDefault Component.disabledBorderColor Color
@@ -61,20 +66,40 @@ import com.formdev.flatlaf.util.DerivedColor;
*/
public class FlatBorder
extends BasicBorders.MarginBorder
implements StyleableBorder
{
protected final int focusWidth = UIManager.getInt( "Component.focusWidth" );
protected final float innerFocusWidth = FlatUIUtils.getUIFloat( "Component.innerFocusWidth", 0 );
protected final float innerOutlineWidth = FlatUIUtils.getUIFloat( "Component.innerOutlineWidth", 0 );
protected final Color focusColor = UIManager.getColor( "Component.focusColor" );
protected final Color borderColor = UIManager.getColor( "Component.borderColor" );
protected final Color disabledBorderColor = UIManager.getColor( "Component.disabledBorderColor" );
protected final Color focusedBorderColor = UIManager.getColor( "Component.focusedBorderColor" );
@Styleable protected int focusWidth = UIManager.getInt( "Component.focusWidth" );
@Styleable protected float innerFocusWidth = FlatUIUtils.getUIFloat( "Component.innerFocusWidth", 0 );
@Styleable protected float innerOutlineWidth = FlatUIUtils.getUIFloat( "Component.innerOutlineWidth", 0 );
/** @since 2 */ @Styleable protected float borderWidth = FlatUIUtils.getUIFloat( "Component.borderWidth", 1 );
protected final Color errorBorderColor = UIManager.getColor( "Component.error.borderColor" );
protected final Color errorFocusedBorderColor = UIManager.getColor( "Component.error.focusedBorderColor" );
protected final Color warningBorderColor = UIManager.getColor( "Component.warning.borderColor" );
protected final Color warningFocusedBorderColor = UIManager.getColor( "Component.warning.focusedBorderColor" );
protected final Color customBorderColor = UIManager.getColor( "Component.custom.borderColor" );
@Styleable protected Color focusColor = UIManager.getColor( "Component.focusColor" );
@Styleable protected Color borderColor = UIManager.getColor( "Component.borderColor" );
@Styleable protected Color disabledBorderColor = UIManager.getColor( "Component.disabledBorderColor" );
@Styleable protected Color focusedBorderColor = UIManager.getColor( "Component.focusedBorderColor" );
@Styleable(dot=true) protected Color errorBorderColor = UIManager.getColor( "Component.error.borderColor" );
@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" );
@Styleable(dot=true) protected Color customBorderColor = UIManager.getColor( "Component.custom.borderColor" );
// only used via styling (not in UI defaults, but has likewise client properties)
/** @since 2 */ @Styleable protected String outline;
/** @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 );
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
@@ -83,9 +108,11 @@ public class FlatBorder
FlatUIUtils.setRenderingHints( g2 );
float focusWidth = scale( (float) getFocusWidth( c ) );
float borderWidth = scale( (float) getBorderWidth( c ) );
float focusInnerWidth = 0;
float borderWidth = scale( getBorderWidth( c ) );
float arc = scale( (float) getArc( c ) );
Color outlineColor = getOutlineColor( c );
Color focusColor = null;
// paint outer border
if( outlineColor != null || isFocused( c ) ) {
@@ -94,15 +121,16 @@ public class FlatBorder
: 0;
if( focusWidth > 0 || innerWidth > 0 ) {
g2.setColor( (outlineColor != null) ? outlineColor : getFocusColor( c ) );
FlatUIUtils.paintComponentOuterBorder( g2, x, y, width, height,
focusWidth, borderWidth + scale( innerWidth ), arc );
focusColor = (outlineColor != null) ? outlineColor : getFocusColor( c );
focusInnerWidth = borderWidth + scale( innerWidth );
}
}
// paint border
g2.setPaint( (outlineColor != null) ? outlineColor : getBorderColor( c ) );
FlatUIUtils.paintComponentBorder( g2, x, y, width, height, focusWidth, borderWidth, arc );
Paint borderColor = (outlineColor != null) ? outlineColor : getBorderColor( c );
FlatUIUtils.paintOutlinedComponent( g2, x, y, width, height,
focusWidth, 1, focusInnerWidth, borderWidth, arc,
focusColor, borderColor, null );
} finally {
g2.dispose();
}
@@ -117,6 +145,17 @@ public class FlatBorder
return null;
Object outline = ((JComponent)c).getClientProperty( FlatClientProperties.OUTLINE );
if( outline == null )
outline = this.outline;
if( outline == null ) {
if( outlineColor != null && outlineFocusedColor != null )
outline = new Color[] { outlineFocusedColor, outlineColor };
else if( outlineColor != null )
outline = outlineColor;
else if( outlineFocusedColor != null )
outline = outlineFocusedColor;
}
if( outline instanceof String ) {
switch( (String) outline ) {
case FlatClientProperties.OUTLINE_ERROR:
@@ -229,8 +268,8 @@ public class FlatBorder
* Returns the (unscaled) line thickness used to paint the border.
* This may be different to {@link #getLineWidth}.
*/
protected int getBorderWidth( Component c ) {
return getLineWidth( c );
protected float getBorderWidth( Component c ) {
return borderWidth;
}
/**

View File

@@ -26,57 +26,66 @@ import java.awt.Paint;
import javax.swing.AbstractButton;
import javax.swing.UIManager;
import javax.swing.plaf.UIResource;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.util.UIScale;
/**
* Border for {@link javax.swing.JButton}.
*
* @uiDefault Button.arc int
* @uiDefault Button.innerFocusWidth int or float optional; defaults to Component.innerFocusWidth
* @uiDefault Button.borderWidth int or float optional; defaults to Component.borderWidth
*
* @uiDefault Button.borderColor Color
* @uiDefault Button.startBorderColor Color optional; if set, a gradient paint is used and Button.borderColor is ignored
* @uiDefault Button.endBorderColor Color optional; if set, a gradient paint is used
* @uiDefault Button.disabledBorderColor Color
* @uiDefault Button.focusedBorderColor Color
* @uiDefault Button.hoverBorderColor Color optional
*
* @uiDefault Button.default.borderWidth int or float
* @uiDefault Button.default.borderColor Color
* @uiDefault Button.default.startBorderColor Color optional; if set, a gradient paint is used and Button.default.borderColor is ignored
* @uiDefault Button.default.endBorderColor Color optional; if set, a gradient paint is used
* @uiDefault Button.default.hoverBorderColor Color optional
* @uiDefault Button.default.focusedBorderColor Color
* @uiDefault Button.default.focusColor Color
* @uiDefault Button.default.hoverBorderColor Color optional
*
* @uiDefault Button.toolbar.focusWidth int or float optional; default is 1.5
* @uiDefault Button.toolbar.focusColor Color optional; defaults to Component.focusColor
* @uiDefault Button.borderWidth int
* @uiDefault Button.default.borderWidth int
* @uiDefault Button.innerFocusWidth int or float optional; defaults to Component.innerFocusWidth
* @uiDefault Button.toolbar.margin Insets
* @uiDefault Button.toolbar.spacingInsets Insets
* @uiDefault Button.toolbar.focusWidth int or float optional; default is 1
* @uiDefault Button.arc int
*
* @author Karl Tauber
*/
public class FlatButtonBorder
extends FlatBorder
{
protected final Color borderColor = FlatUIUtils.getUIColor( "Button.startBorderColor", "Button.borderColor" );
protected final Color endBorderColor = UIManager.getColor( "Button.endBorderColor" );
protected final Color disabledBorderColor = UIManager.getColor( "Button.disabledBorderColor" );
protected final Color focusedBorderColor = UIManager.getColor( "Button.focusedBorderColor" );
protected final Color hoverBorderColor = UIManager.getColor( "Button.hoverBorderColor" );
protected final Color defaultBorderColor = FlatUIUtils.getUIColor( "Button.default.startBorderColor", "Button.default.borderColor" );
protected final Color defaultEndBorderColor = UIManager.getColor( "Button.default.endBorderColor" );
protected final Color defaultHoverBorderColor = UIManager.getColor( "Button.default.hoverBorderColor" );
protected final Color defaultFocusedBorderColor = UIManager.getColor( "Button.default.focusedBorderColor" );
protected final Color defaultFocusColor = UIManager.getColor( "Button.default.focusColor" );
/** @since 1.4 */
protected final Color toolbarFocusColor = UIManager.getColor( "Button.toolbar.focusColor" );
protected final int borderWidth = UIManager.getInt( "Button.borderWidth" );
protected final int defaultBorderWidth = UIManager.getInt( "Button.default.borderWidth" );
protected final float buttonInnerFocusWidth = FlatUIUtils.getUIFloat( "Button.innerFocusWidth", innerFocusWidth );
protected final Insets toolbarMargin = UIManager.getInsets( "Button.toolbar.margin" );
protected final Insets toolbarSpacingInsets = UIManager.getInsets( "Button.toolbar.spacingInsets" );
/** @since 1.4 */
protected final float toolbarFocusWidth = FlatUIUtils.getUIFloat( "Button.toolbar.focusWidth", 1.5f );
protected final int arc = UIManager.getInt( "Button.arc" );
@Styleable protected int arc = UIManager.getInt( "Button.arc" );
protected Color endBorderColor = UIManager.getColor( "Button.endBorderColor" );
@Styleable protected Color hoverBorderColor = UIManager.getColor( "Button.hoverBorderColor" );
@Styleable(dot=true) protected float defaultBorderWidth = FlatUIUtils.getUIFloat( "Button.default.borderWidth", 1 );
@Styleable(dot=true) protected Color defaultBorderColor = FlatUIUtils.getUIColor( "Button.default.startBorderColor", "Button.default.borderColor" );
protected Color defaultEndBorderColor = UIManager.getColor( "Button.default.endBorderColor" );
@Styleable(dot=true) protected Color defaultFocusedBorderColor = UIManager.getColor( "Button.default.focusedBorderColor" );
@Styleable(dot=true) protected Color defaultFocusColor = UIManager.getColor( "Button.default.focusColor" );
@Styleable(dot=true) protected Color defaultHoverBorderColor = UIManager.getColor( "Button.default.hoverBorderColor" );
/** @since 1.4 */ @Styleable(dot=true) protected float toolbarFocusWidth = FlatUIUtils.getUIFloat( "Button.toolbar.focusWidth", 1.5f );
/** @since 1.4 */ @Styleable(dot=true) protected Color toolbarFocusColor = UIManager.getColor( "Button.toolbar.focusColor" );
@Styleable(dot=true) protected Insets toolbarMargin = UIManager.getInsets( "Button.toolbar.margin" );
@Styleable(dot=true) protected Insets toolbarSpacingInsets = UIManager.getInsets( "Button.toolbar.spacingInsets" );
public FlatButtonBorder() {
innerFocusWidth = FlatUIUtils.getUIFloat( "Button.innerFocusWidth", innerFocusWidth );
borderWidth = FlatUIUtils.getUIFloat( "Button.borderWidth", borderWidth );
borderColor = FlatUIUtils.getUIColor( "Button.startBorderColor", "Button.borderColor" );
disabledBorderColor = UIManager.getColor( "Button.disabledBorderColor" );
focusedBorderColor = UIManager.getColor( "Button.focusedBorderColor" );
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
@@ -90,9 +99,7 @@ public class FlatButtonBorder
paintToolBarFocus( c, g, x, y, width, height );
}
/**
* @since 1.4
*/
/** @since 1.4 */
protected void paintToolBarFocus( Component c, Graphics g, int x, int y, int width, int height ) {
Graphics2D g2 = (Graphics2D) g.create();
try {
@@ -108,9 +115,10 @@ public class FlatButtonBorder
width -= spacing.left + spacing.right;
height -= spacing.top + spacing.bottom;
g2.setColor( (outlineColor != null) ? outlineColor : getFocusColor( c ) );
// not using paintComponentOuterBorder() here because its round edges look too "thick"
FlatUIUtils.paintComponentBorder( g2, x, y, width, height, 0, focusWidth, arc );
Color color = (outlineColor != null) ? outlineColor : getFocusColor( c );
// not using focus border painting of paintOutlinedComponent() here
// because its round edges look too "thick"
FlatUIUtils.paintOutlinedComponent( g2, x, y, width, height, 0, 0, 0, focusWidth, arc, null, color, null );
} finally {
g2.dispose();
}
@@ -175,12 +183,7 @@ public class FlatButtonBorder
}
@Override
protected float getInnerFocusWidth( Component c ) {
return buttonInnerFocusWidth;
}
@Override
protected int getBorderWidth( Component c ) {
protected float getBorderWidth( Component c ) {
return FlatButtonUI.isDefaultButton( c ) ? defaultBorderWidth : borderWidth;
}

View File

@@ -30,7 +30,9 @@ import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.geom.RoundRectangle2D;
import java.beans.PropertyChangeEvent;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.AbstractButton;
import javax.swing.ButtonModel;
import javax.swing.Icon;
@@ -40,11 +42,17 @@ import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.LookAndFeel;
import javax.swing.UIManager;
import javax.swing.plaf.ButtonUI;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicButtonListener;
import javax.swing.plaf.basic.BasicButtonUI;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.icons.FlatHelpButtonIcon;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale;
/**
@@ -94,8 +102,9 @@ import com.formdev.flatlaf.util.UIScale;
*/
public class FlatButtonUI
extends BasicButtonUI
implements StyleableUI
{
protected int minimumWidth;
@Styleable protected int minimumWidth;
protected int iconTextGap;
protected Color background;
@@ -103,39 +112,62 @@ public class FlatButtonUI
protected Color startBackground;
protected Color endBackground;
protected Color focusedBackground;
protected Color hoverBackground;
protected Color pressedBackground;
protected Color selectedBackground;
protected Color selectedForeground;
protected Color disabledBackground;
protected Color disabledText;
protected Color disabledSelectedBackground;
@Styleable protected Color focusedBackground;
@Styleable protected Color hoverBackground;
@Styleable protected Color pressedBackground;
@Styleable protected Color selectedBackground;
@Styleable protected Color selectedForeground;
@Styleable protected Color disabledBackground;
@Styleable protected Color disabledText;
@Styleable protected Color disabledSelectedBackground;
protected Color defaultBackground;
@Styleable(dot=true) protected Color defaultBackground;
protected Color defaultEndBackground;
protected Color defaultForeground;
protected Color defaultFocusedBackground;
protected Color defaultHoverBackground;
protected Color defaultPressedBackground;
protected boolean defaultBoldText;
@Styleable(dot=true) protected Color defaultForeground;
@Styleable(dot=true) protected Color defaultFocusedBackground;
@Styleable(dot=true) protected Color defaultHoverBackground;
@Styleable(dot=true) protected Color defaultPressedBackground;
@Styleable(dot=true) protected boolean defaultBoldText;
protected int shadowWidth;
protected Color shadowColor;
protected Color defaultShadowColor;
@Styleable protected boolean paintShadow;
@Styleable protected int shadowWidth;
@Styleable protected Color shadowColor;
@Styleable(dot=true) protected Color defaultShadowColor;
protected Insets toolbarSpacingInsets;
protected Color toolbarHoverBackground;
protected Color toolbarPressedBackground;
protected Color toolbarSelectedBackground;
@Styleable(dot=true) protected Color toolbarHoverBackground;
@Styleable(dot=true) protected Color toolbarPressedBackground;
@Styleable(dot=true) protected Color toolbarSelectedBackground;
// only used via styling (not in UI defaults, but has likewise client properties)
/** @since 2 */ @Styleable protected String buttonType;
/** @since 2 */ @Styleable protected boolean squareSize;
/** @since 2 */ @Styleable protected int minimumHeight;
private Icon helpButtonIcon;
private Insets defaultMargin;
private final boolean shared;
private boolean helpButtonIconShared = true;
private boolean defaults_initialized = false;
private Map<String, Object> oldStyleValues;
private AtomicBoolean borderShared;
public static ComponentUI createUI( JComponent c ) {
return FlatUIUtils.createSharedUI( FlatButtonUI.class, FlatButtonUI::new );
return FlatUIUtils.canUseSharedUI( c )
? FlatUIUtils.createSharedUI( FlatButtonUI.class, () -> new FlatButtonUI( true ) )
: new FlatButtonUI( false );
}
/** @since 2 */
protected FlatButtonUI( boolean shared ) {
this.shared = shared;
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installStyle( (AbstractButton) c );
}
@Override
@@ -162,16 +194,6 @@ public class FlatButtonUI
disabledText = UIManager.getColor( prefix + "disabledText" );
disabledSelectedBackground = UIManager.getColor( prefix + "disabledSelectedBackground" );
if( UIManager.getBoolean( "Button.paintShadow" ) ) {
shadowWidth = FlatUIUtils.getUIInt( "Button.shadowWidth", 2 );
shadowColor = UIManager.getColor( "Button.shadowColor" );
defaultShadowColor = UIManager.getColor( "Button.default.shadowColor" );
} else {
shadowWidth = 0;
shadowColor = null;
defaultShadowColor = null;
}
defaultBackground = FlatUIUtils.getUIColor( "Button.default.startBackground", "Button.default.background" );
defaultEndBackground = UIManager.getColor( "Button.default.endBackground" );
defaultForeground = UIManager.getColor( "Button.default.foreground" );
@@ -180,7 +202,11 @@ public class FlatButtonUI
defaultPressedBackground = UIManager.getColor( "Button.default.pressedBackground" );
defaultBoldText = UIManager.getBoolean( "Button.default.boldText" );
toolbarSpacingInsets = UIManager.getInsets( "Button.toolbar.spacingInsets" );
paintShadow = UIManager.getBoolean( "Button.paintShadow" );
shadowWidth = FlatUIUtils.getUIInt( "Button.shadowWidth", 2 );
shadowColor = UIManager.getColor( "Button.shadowColor" );
defaultShadowColor = UIManager.getColor( "Button.default.shadowColor" );
toolbarHoverBackground = UIManager.getColor( prefix + "toolbar.hoverBackground" );
toolbarPressedBackground = UIManager.getColor( prefix + "toolbar.pressedBackground" );
toolbarSelectedBackground = UIManager.getColor( prefix + "toolbar.selectedBackground" );
@@ -188,6 +214,7 @@ public class FlatButtonUI
helpButtonIcon = UIManager.getIcon( "HelpButton.icon" );
defaultMargin = UIManager.getInsets( prefix + "margin" );
helpButtonIconShared = true;
defaults_initialized = true;
}
@@ -207,6 +234,9 @@ public class FlatButtonUI
protected void uninstallDefaults( AbstractButton b ) {
super.uninstallDefaults( b );
oldStyleValues = null;
borderShared = null;
MigLayoutVisualPadding.uninstall( b );
defaults_initialized = false;
}
@@ -228,9 +258,70 @@ public class FlatButtonUI
b.revalidate();
b.repaint();
break;
case STYLE:
case STYLE_CLASS:
if( shared && FlatStylingSupport.hasStyleProperty( b ) ) {
// unshare component UI if necessary
// updateUI() invokes installStyle() from installUI()
b.updateUI();
} else
installStyle( b );
b.revalidate();
b.repaint();
break;
}
}
/** @since 2 */
protected void installStyle( AbstractButton b ) {
try {
applyStyle( b, FlatStylingSupport.getResolvedStyle( b, getStyleType() ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
String getStyleType() {
return "Button";
}
/** @since 2 */
protected void applyStyle( AbstractButton b, Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style,
(key, value) -> applyStyleProperty( b, key, value ) );
}
/** @since 2 */
protected Object applyStyleProperty( AbstractButton b, String key, Object value ) {
if( key.startsWith( "help." ) ) {
if( !(helpButtonIcon instanceof FlatHelpButtonIcon) )
return new UnknownStyleException( key );
if( helpButtonIconShared ) {
helpButtonIcon = FlatStylingSupport.cloneIcon( helpButtonIcon );
helpButtonIconShared = false;
}
key = key.substring( "help.".length() );
return ((FlatHelpButtonIcon)helpButtonIcon).applyStyleProperty( key, value );
}
if( borderShared == null )
borderShared = new AtomicBoolean( true );
return FlatStylingSupport.applyToAnnotatedObjectOrBorder( this, key, value, b, borderShared );
}
/** @since 2 */
@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() );
return infos;
}
static boolean isContentAreaFilled( Component c ) {
return !(c instanceof AbstractButton) || ((AbstractButton)c).isContentAreaFilled();
}
@@ -268,11 +359,11 @@ public class FlatButtonUI
if( !(c instanceof AbstractButton) )
return TYPE_OTHER;
Object value = ((AbstractButton)c).getClientProperty( BUTTON_TYPE );
if( !(value instanceof String) )
String value = getButtonTypeStr( (AbstractButton) c );
if( value == null )
return TYPE_OTHER;
switch( (String) value ) {
switch( value ) {
case BUTTON_TYPE_SQUARE: return TYPE_SQUARE;
case BUTTON_TYPE_ROUND_RECT: return TYPE_ROUND_RECT;
default: return TYPE_OTHER;
@@ -280,16 +371,27 @@ public class FlatButtonUI
}
static boolean isHelpButton( Component c ) {
return c instanceof JButton && clientPropertyEquals( (JButton) c, BUTTON_TYPE, BUTTON_TYPE_HELP );
return c instanceof JButton && BUTTON_TYPE_HELP.equals( getButtonTypeStr( (JButton) c ) );
}
static boolean isToolBarButton( Component c ) {
return c.getParent() instanceof JToolBar ||
(c instanceof AbstractButton && clientPropertyEquals( (AbstractButton) c, BUTTON_TYPE, BUTTON_TYPE_TOOLBAR_BUTTON ));
(c instanceof AbstractButton && BUTTON_TYPE_TOOLBAR_BUTTON.equals( getButtonTypeStr( (AbstractButton) c ) ));
}
static boolean isBorderlessButton( Component c ) {
return c instanceof AbstractButton && clientPropertyEquals( (AbstractButton) c, BUTTON_TYPE, BUTTON_TYPE_BORDERLESS );
return c instanceof AbstractButton && BUTTON_TYPE_BORDERLESS.equals( getButtonTypeStr( (AbstractButton) c ) );
}
static String getButtonTypeStr( AbstractButton c ) {
// get from client property
Object value = c.getClientProperty( BUTTON_TYPE );
if( value instanceof String )
return (String) value;
// get from styling property
ButtonUI ui = c.getUI();
return (ui instanceof FlatButtonUI) ? ((FlatButtonUI)ui).buttonType : null;
}
@Override
@@ -329,8 +431,8 @@ public class FlatButtonUI
int width = c.getWidth();
int height = c.getHeight();
if( isToolBarButton ) {
Insets spacing = UIScale.scale( toolbarSpacingInsets );
if( isToolBarButton && c.getBorder() instanceof FlatButtonBorder ) {
Insets spacing = UIScale.scale( ((FlatButtonBorder)c.getBorder()).toolbarSpacingInsets );
x += spacing.left;
y += spacing.top;
width -= spacing.left + spacing.right;
@@ -339,7 +441,8 @@ public class FlatButtonUI
// paint shadow
Color shadowColor = def ? defaultShadowColor : this.shadowColor;
if( shadowColor != null && shadowWidth > 0 && focusWidth > 0 && c.isEnabled() &&
if( paintShadow &&
shadowColor != null && shadowWidth > 0 && focusWidth > 0 && c.isEnabled() &&
!isToolBarButton && !isBorderlessButton( c ) &&
!(isFocusPainted( c ) && FlatUIUtils.isPermanentFocusOwner( c )) )
{
@@ -496,7 +599,7 @@ public class FlatButtonUI
// make square or apply minimum width/height
boolean isIconOnlyOrSingleCharacter = isIconOnlyOrSingleCharacterButton( c );
if( clientPropertyBoolean( c, SQUARE_SIZE, false ) ) {
if( clientPropertyBoolean( c, SQUARE_SIZE, squareSize ) ) {
// make button square (increase width or height so that they are equal)
prefSize.width = prefSize.height = Math.max( prefSize.width, prefSize.height );
} else if( isIconOnlyOrSingleCharacter && ((AbstractButton)c).getIcon() == null ) {
@@ -508,7 +611,7 @@ public class FlatButtonUI
// apply minimum width/height
int fw = Math.round( FlatUIUtils.getBorderFocusWidth( c ) * 2 );
prefSize.width = Math.max( prefSize.width, scale( FlatUIUtils.minimumWidth( c, minimumWidth ) ) + fw );
prefSize.height = Math.max( prefSize.height, scale( FlatUIUtils.minimumHeight( c, 0 ) ) + fw );
prefSize.height = Math.max( prefSize.height, scale( FlatUIUtils.minimumHeight( c, minimumHeight ) ) + fw );
}
return prefSize;

View File

@@ -18,19 +18,27 @@ package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.FlatClientProperties.*;
import java.awt.EventQueue;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.FocusEvent;
import java.awt.event.MouseEvent;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.JFormattedTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.UIResource;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultCaret;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import javax.swing.text.Utilities;
/**
* Caret that can select all text on focus gained.
* Also fixes Swing's double-click-and-drag behavior so that dragging after
* a double-click extends selection by whole words.
*
* @author Karl Tauber
*/
@@ -38,12 +46,19 @@ public class FlatCaret
extends DefaultCaret
implements UIResource
{
private static final String KEY_CARET_INFO = "FlatLaf.internal.caretInfo";
private final String selectAllOnFocusPolicy;
private final boolean selectAllOnMouseClick;
private boolean inInstall;
private boolean wasFocused;
private boolean wasTemporaryLost;
private boolean isMousePressed;
private boolean isWordSelection;
private boolean isLineSelection;
private int dragSelectionStart;
private int dragSelectionEnd;
public FlatCaret( String selectAllOnFocusPolicy, boolean selectAllOnMouseClick ) {
this.selectAllOnFocusPolicy = selectAllOnFocusPolicy;
@@ -52,34 +67,82 @@ public class FlatCaret
@Override
public void install( JTextComponent c ) {
super.install( c );
// get caret info if switched theme
long[] ci = (long[]) c.getClientProperty( KEY_CARET_INFO );
if( ci != null ) {
c.putClientProperty( KEY_CARET_INFO, null );
// the dot and mark are lost when switching LaF
// --> move dot to end of text so that all text may be selected when it gains focus
Document doc = c.getDocument();
if( doc != null && getDot() == 0 && getMark() == 0 ) {
int length = doc.getLength();
if( length > 0 )
setDot( length );
// if caret info is too old assume that switched from FlatLaf
// to another Laf and back to FlatLaf
if( System.currentTimeMillis() - 500 > ci[3] )
ci = null;
}
if( ci != null ) {
// when switching theme, it is necessary to set blink rate before
// invoking super.install() otherwise the caret does not blink
setBlinkRate( (int) ci[2] );
}
inInstall = true;
try {
super.install( c );
} finally {
inInstall = false;
}
if( ci != null ) {
// restore selection
select( (int) ci[1], (int) ci[0] );
// if text component is focused, then caret and selection are visible,
// but when switching theme, the component does not yet have
// an highlighter and the selection is not painted
// --> make selection temporary invisible later, then the caret
// adds selection highlights to the text component highlighter
if( isSelectionVisible() ) {
EventQueue.invokeLater( () -> {
if( getComponent() == null )
return; // was deinstalled
if( isSelectionVisible() ) {
setSelectionVisible( false );
setSelectionVisible( true );
}
} );
}
}
}
@Override
public void deinstall( JTextComponent c ) {
// remember dot and mark (the selection) when switching theme
c.putClientProperty( KEY_CARET_INFO, new long[] {
getDot(),
getMark(),
getBlinkRate(),
System.currentTimeMillis(),
} );
super.deinstall( c );
}
@Override
protected void adjustVisibility( Rectangle nloc ) {
JTextComponent c = getComponent();
if( c != null && c.getUI() instanceof FlatTextFieldUI ) {
Insets padding = ((FlatTextFieldUI)c.getUI()).getPadding();
if( padding != null ) {
nloc.x -= padding.left;
nloc.y -= padding.top;
}
// need to fix x location because JTextField.scrollRectToVisible() uses insets.left
// (as BasicTextUI.getVisibleEditorRect() does),
// but FlatTextFieldUI.getVisibleEditorRect() may add some padding
Rectangle r = ((FlatTextFieldUI)c.getUI()).getVisibleEditorRect();
if( r != null )
nloc.x -= r.x - c.getInsets().left;
}
super.adjustVisibility( nloc );
}
@Override
public void focusGained( FocusEvent e ) {
if( !wasTemporaryLost && (!isMousePressed || selectAllOnMouseClick) )
if( !inInstall && !wasTemporaryLost && (!isMousePressed || selectAllOnMouseClick) )
selectAllOnFocusGained();
wasTemporaryLost = false;
wasFocused = true;
@@ -97,14 +160,72 @@ public class FlatCaret
public void mousePressed( MouseEvent e ) {
isMousePressed = true;
super.mousePressed( e );
JTextComponent c = getComponent();
// left double-click starts word selection
isWordSelection = e.getClickCount() == 2 && SwingUtilities.isLeftMouseButton( e ) && !e.isConsumed();
// left triple-click starts line selection
isLineSelection = e.getClickCount() == 3 && SwingUtilities.isLeftMouseButton( e ) && (!e.isConsumed() || c.getDragEnabled());
// select line
// (this is also done in DefaultCaret.mouseClicked(), but this event is
// sent when the mouse is released, which is too late for triple-click-and-drag)
if( isLineSelection ) {
ActionMap actionMap = c.getActionMap();
Action selectLineAction = (actionMap != null)
? actionMap.get( DefaultEditorKit.selectLineAction )
: null;
if( selectLineAction != null ) {
selectLineAction.actionPerformed( new ActionEvent( c,
ActionEvent.ACTION_PERFORMED, null, e.getWhen(), e.getModifiers() ) );
}
}
// remember selection where word/line selection starts to keep it always selected while dragging
if( isWordSelection || isLineSelection ) {
int mark = getMark();
int dot = getDot();
dragSelectionStart = Math.min( dot, mark );
dragSelectionEnd = Math.max( dot, mark );
}
}
@Override
public void mouseReleased( MouseEvent e ) {
isMousePressed = false;
isWordSelection = false;
isLineSelection = false;
super.mouseReleased( e );
}
@Override
public void mouseDragged( MouseEvent e ) {
if( (isWordSelection || isLineSelection) &&
!e.isConsumed() && SwingUtilities.isLeftMouseButton( e ) )
{
// fix Swing's double/triple-click-and-drag behavior so that dragging after
// a double/triple-click extends selection by whole words/lines
JTextComponent c = getComponent();
int pos = c.viewToModel( e.getPoint() );
if( pos < 0 )
return;
try {
if( pos > dragSelectionEnd )
select( dragSelectionStart, isWordSelection ? Utilities.getWordEnd( c, pos ) : Utilities.getRowEnd( c, pos ) );
else if( pos < dragSelectionStart )
select( dragSelectionEnd, isWordSelection ? Utilities.getWordStart( c, pos ) : Utilities.getRowStart( c, pos ) );
else
select( dragSelectionStart, dragSelectionEnd );
} catch( BadLocationException ex ) {
UIManager.getLookAndFeel().provideErrorFeedback( c );
}
} else
super.mouseDragged( e );
}
protected void selectAllOnFocusGained() {
JTextComponent c = getComponent();
Document doc = c.getDocument();
@@ -135,18 +256,24 @@ public class FlatCaret
// select all
if( c instanceof JFormattedTextField ) {
EventQueue.invokeLater( () -> {
setDot( 0 );
moveDot( doc.getLength() );
if( getComponent() == null )
return; // was deinstalled
select( 0, doc.getLength() );
} );
} else {
setDot( 0 );
moveDot( doc.getLength() );
select( 0, doc.getLength() );
}
}
/**
* @since 1.4
*/
private void select( int mark, int dot ) {
if( mark != getMark() )
setDot( mark );
if( dot != getDot() )
moveDot( dot );
}
/** @since 1.4 */
public void scrollCaretToVisible() {
JTextComponent c = getComponent();
if( c == null || c.getUI() == null )

View File

@@ -16,13 +16,19 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.beans.PropertyChangeListener;
import java.util.Map;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.LookAndFeel;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicCheckBoxMenuItemUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.LoggingFacade;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JCheckBoxMenuItem}.
@@ -54,13 +60,22 @@ import javax.swing.plaf.basic.BasicCheckBoxMenuItemUI;
*/
public class FlatCheckBoxMenuItemUI
extends BasicCheckBoxMenuItemUI
implements StyleableUI
{
private FlatMenuItemRenderer renderer;
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
return new FlatCheckBoxMenuItemUI();
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installStyle();
}
@Override
protected void installDefaults() {
super.installDefaults();
@@ -74,13 +89,61 @@ public class FlatCheckBoxMenuItemUI
protected void uninstallDefaults() {
super.uninstallDefaults();
FlatMenuItemRenderer.clearClientProperties( menuItem.getParent() );
renderer = null;
oldStyleValues = null;
}
protected FlatMenuItemRenderer createRenderer() {
return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter );
}
@Override
protected PropertyChangeListener createPropertyChangeListener( JComponent c ) {
return FlatStylingSupport.createPropertyChangeListener( c, this::installStyle, super.createPropertyChangeListener( c ) );
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( menuItem, "CheckBoxMenuItem" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
try {
return renderer.applyStyleProperty( key, value );
} catch ( UnknownStyleException ex ) {
// ignore
}
Object oldValue;
switch( key ) {
// BasicMenuItemUI
case "selectionBackground": oldValue = selectionBackground; selectionBackground = (Color) value; return oldValue;
case "selectionForeground": oldValue = selectionForeground; selectionForeground = (Color) value; return oldValue;
case "disabledForeground": oldValue = disabledForeground; disabledForeground = (Color) value; return oldValue;
case "acceleratorForeground": oldValue = acceleratorForeground; acceleratorForeground = (Color) value; return oldValue;
case "acceleratorSelectionForeground": oldValue = acceleratorSelectionForeground; acceleratorSelectionForeground = (Color) value; return oldValue;
}
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, menuItem, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatMenuItemUI.getStyleableInfos( renderer );
}
@Override
protected Dimension getPreferredMenuItemSize( JComponent c, Icon checkIcon, Icon arrowIcon, int defaultTextIconGap ) {
return renderer.getPreferredMenuItemSize();

View File

@@ -43,11 +43,24 @@ public class FlatCheckBoxUI
extends FlatRadioButtonUI
{
public static ComponentUI createUI( JComponent c ) {
return FlatUIUtils.createSharedUI( FlatCheckBoxUI.class, FlatCheckBoxUI::new );
return FlatUIUtils.canUseSharedUI( c )
? FlatUIUtils.createSharedUI( FlatCheckBoxUI.class, () -> new FlatCheckBoxUI( true ) )
: new FlatCheckBoxUI( false );
}
/** @since 2 */
protected FlatCheckBoxUI( boolean shared ) {
super( shared );
}
@Override
public String getPropertyPrefix() {
return "CheckBox.";
}
/** @since 2 */
@Override
String getStyleType() {
return "CheckBox";
}
}

View File

@@ -16,6 +16,7 @@
package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.FlatClientProperties.*;
import static com.formdev.flatlaf.util.UIScale.scale;
import static com.formdev.flatlaf.util.UIScale.unscale;
import java.awt.Color;
@@ -41,6 +42,8 @@ import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeListener;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.CellRendererPane;
@@ -66,7 +69,9 @@ import javax.swing.plaf.basic.BasicComboBoxUI;
import javax.swing.plaf.basic.BasicComboPopup;
import javax.swing.plaf.basic.ComboPopup;
import javax.swing.text.JTextComponent;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.SystemInfo;
/**
@@ -89,15 +94,16 @@ import com.formdev.flatlaf.util.SystemInfo;
* @uiDefault ComboBox.buttonStyle String auto (default), button or none
* @uiDefault Component.arrowType String chevron (default) or triangle
* @uiDefault Component.isIntelliJTheme boolean
* @uiDefault Component.borderColor Color
* @uiDefault Component.disabledBorderColor Color
* @uiDefault ComboBox.editableBackground Color optional; defaults to ComboBox.background
* @uiDefault ComboBox.focusedBackground Color optional
* @uiDefault ComboBox.disabledBackground Color
* @uiDefault ComboBox.disabledForeground Color
* @uiDefault ComboBox.buttonBackground Color
* @uiDefault ComboBox.buttonEditableBackground Color
* @uiDefault ComboBox.buttonBackground Color optional
* @uiDefault ComboBox.buttonEditableBackground Color optional
* @uiDefault ComboBox.buttonFocusedBackground Color optional; defaults to ComboBox.focusedBackground
* @uiDefault ComboBox.buttonSeparatorWidth int or float optional; defaults to Component.borderWidth
* @uiDefault ComboBox.buttonSeparatorColor Color optional
* @uiDefault ComboBox.buttonDisabledSeparatorColor Color optional
* @uiDefault ComboBox.buttonArrowColor Color
* @uiDefault ComboBox.buttonDisabledArrowColor Color
* @uiDefault ComboBox.buttonHoverArrowColor Color
@@ -108,29 +114,31 @@ import com.formdev.flatlaf.util.SystemInfo;
*/
public class FlatComboBoxUI
extends BasicComboBoxUI
implements StyleableUI
{
protected int minimumWidth;
protected int editorColumns;
protected String buttonStyle;
protected String arrowType;
@Styleable protected int minimumWidth;
@Styleable protected int editorColumns;
@Styleable protected String buttonStyle;
@Styleable protected String arrowType;
protected boolean isIntelliJTheme;
protected Color borderColor;
protected Color disabledBorderColor;
protected Color editableBackground;
protected Color focusedBackground;
protected Color disabledBackground;
protected Color disabledForeground;
@Styleable protected Color editableBackground;
@Styleable protected Color focusedBackground;
@Styleable protected Color disabledBackground;
@Styleable protected Color disabledForeground;
protected Color buttonBackground;
protected Color buttonEditableBackground;
protected Color buttonFocusedBackground;
protected Color buttonArrowColor;
protected Color buttonDisabledArrowColor;
protected Color buttonHoverArrowColor;
protected Color buttonPressedArrowColor;
@Styleable protected Color buttonBackground;
@Styleable protected Color buttonEditableBackground;
@Styleable protected Color buttonFocusedBackground;
/** @since 2 */ @Styleable protected float buttonSeparatorWidth;
/** @since 2 */ @Styleable protected Color buttonSeparatorColor;
/** @since 2 */ @Styleable protected Color buttonDisabledSeparatorColor;
@Styleable protected Color buttonArrowColor;
@Styleable protected Color buttonDisabledArrowColor;
@Styleable protected Color buttonHoverArrowColor;
@Styleable protected Color buttonPressedArrowColor;
protected Color popupBackground;
@Styleable protected Color popupBackground;
private MouseListener hoverListener;
protected boolean hover;
@@ -138,10 +146,20 @@ public class FlatComboBoxUI
private CellPaddingBorder paddingBorder;
private Map<String, Object> oldStyleValues;
private AtomicBoolean borderShared;
public static ComponentUI createUI( JComponent c ) {
return new FlatComboBoxUI();
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installStyle();
}
@Override
protected void installListeners() {
super.installListeners();
@@ -198,8 +216,6 @@ public class FlatComboBoxUI
buttonStyle = UIManager.getString( "ComboBox.buttonStyle" );
arrowType = UIManager.getString( "Component.arrowType" );
isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" );
borderColor = UIManager.getColor( "Component.borderColor" );
disabledBorderColor = UIManager.getColor( "Component.disabledBorderColor" );
editableBackground = UIManager.getColor( "ComboBox.editableBackground" );
focusedBackground = UIManager.getColor( "ComboBox.focusedBackground" );
@@ -209,6 +225,9 @@ public class FlatComboBoxUI
buttonBackground = UIManager.getColor( "ComboBox.buttonBackground" );
buttonFocusedBackground = UIManager.getColor( "ComboBox.buttonFocusedBackground" );
buttonEditableBackground = UIManager.getColor( "ComboBox.buttonEditableBackground" );
buttonSeparatorWidth = FlatUIUtils.getUIFloat( "ComboBox.buttonSeparatorWidth", FlatUIUtils.getUIFloat( "Component.borderWidth", 1 ) );
buttonSeparatorColor = UIManager.getColor( "ComboBox.buttonSeparatorColor" );
buttonDisabledSeparatorColor = UIManager.getColor( "ComboBox.buttonDisabledSeparatorColor" );
buttonArrowColor = UIManager.getColor( "ComboBox.buttonArrowColor" );
buttonDisabledArrowColor = UIManager.getColor( "ComboBox.buttonDisabledArrowColor" );
buttonHoverArrowColor = UIManager.getColor( "ComboBox.buttonHoverArrowColor" );
@@ -230,9 +249,6 @@ public class FlatComboBoxUI
protected void uninstallDefaults() {
super.uninstallDefaults();
borderColor = null;
disabledBorderColor = null;
editableBackground = null;
focusedBackground = null;
disabledBackground = null;
@@ -241,6 +257,8 @@ public class FlatComboBoxUI
buttonBackground = null;
buttonEditableBackground = null;
buttonFocusedBackground = null;
buttonSeparatorColor = null;
buttonDisabledSeparatorColor = null;
buttonArrowColor = null;
buttonDisabledArrowColor = null;
buttonHoverArrowColor = null;
@@ -250,6 +268,9 @@ public class FlatComboBoxUI
paddingBorder.uninstall();
oldStyleValues = null;
borderShared = null;
MigLayoutVisualPadding.uninstall( comboBox );
}
@@ -260,7 +281,10 @@ public class FlatComboBoxUI
public void layoutContainer( Container parent ) {
super.layoutContainer( parent );
if( arrowButton != null ) {
// on macOS, a Swing combo box is used for AWT component java.awt.Choice
// and the font may be (temporary) null
if( arrowButton != null && comboBox.getFont() != null ) {
// limit button width to height of a raw combobox (without insets)
FontMetrics fm = comboBox.getFontMetrics( comboBox.getFont() );
int maxButtonWidth = fm.getHeight() + scale( padding.top ) + scale( padding.bottom );
@@ -322,12 +346,29 @@ public class FlatComboBoxUI
} else if( editor != null && source == comboBox && propertyName == "componentOrientation" ) {
ComponentOrientation o = (ComponentOrientation) e.getNewValue();
editor.applyComponentOrientation( o );
} else if( editor != null && FlatClientProperties.PLACEHOLDER_TEXT.equals( propertyName ) )
editor.repaint();
else if( FlatClientProperties.COMPONENT_ROUND_RECT.equals( propertyName ) )
comboBox.repaint();
else if( FlatClientProperties.MINIMUM_WIDTH.equals( propertyName ) )
comboBox.revalidate();
} else {
switch( propertyName ) {
case PLACEHOLDER_TEXT:
if( editor != null )
editor.repaint();
break;
case COMPONENT_ROUND_RECT:
comboBox.repaint();
break;
case MINIMUM_WIDTH:
comboBox.revalidate();
break;
case STYLE:
case STYLE_CLASS:
installStyle();
comboBox.revalidate();
comboBox.repaint();
break;
}
}
};
}
@@ -395,7 +436,7 @@ public class FlatComboBoxUI
unscale( Math.max( scale( padding.right ) - insets.right, 0 ) )
);
}
textField.putClientProperty( FlatClientProperties.TEXT_FIELD_PADDING, pad );
textField.putClientProperty( TEXT_FIELD_PADDING, pad );
}
private void updateEditorColors() {
@@ -414,6 +455,58 @@ public class FlatComboBoxUI
return new FlatComboBoxButton();
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( comboBox, "ComboBox" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
Insets oldPadding = padding;
int oldEditorColumns = editorColumns;
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
if( !padding.equals( oldPadding ) ) {
paddingBorder.padding = padding;
updateEditorPadding();
}
if( arrowButton instanceof FlatComboBoxButton )
((FlatComboBoxButton)arrowButton).updateStyle();
if( popup instanceof FlatComboPopup )
((FlatComboPopup)popup).updateStyle();
if( editorColumns != oldEditorColumns && editor instanceof JTextField )
((JTextField)editor).setColumns( editorColumns );
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
// BasicComboBoxUI
if( key.equals( "padding" ) ) {
Object oldValue = padding;
padding = (Insets) value;
return oldValue;
}
if( borderShared == null )
borderShared = new AtomicBoolean( true );
return FlatStylingSupport.applyToAnnotatedObjectOrBorder( this, key, value, comboBox, borderShared );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
Map<String, Class<?>> infos = new FlatStylingSupport.StyleableInfosMap<>();
infos.put( "padding", Insets.class );
FlatStylingSupport.collectAnnotatedStyleableInfos( this, infos );
FlatStylingSupport.collectStyleableInfos( comboBox.getBorder(), infos );
return infos;
}
@Override
public void update( Graphics g, JComponent c ) {
float focusWidth = FlatUIUtils.getBorderFocusWidth( c );
@@ -450,26 +543,32 @@ public class FlatComboBoxUI
// paint arrow button background
if( enabled && !isCellRenderer ) {
g2.setColor( paintButton
Color buttonColor = paintButton
? buttonEditableBackground
: (buttonFocusedBackground != null || focusedBackground != null) && isPermanentFocusOwner( comboBox )
? (buttonFocusedBackground != null ? buttonFocusedBackground : focusedBackground)
: buttonBackground );
Shape oldClip = g2.getClip();
if( isLeftToRight )
g2.clipRect( arrowX, 0, width - arrowX, height );
else
g2.clipRect( 0, 0, arrowX + arrowWidth, height );
FlatUIUtils.paintComponentBackground( g2, 0, 0, width, height, focusWidth, arc );
g2.setClip( oldClip );
: buttonBackground;
if( buttonColor != null ) {
g2.setColor( buttonColor );
Shape oldClip = g2.getClip();
if( isLeftToRight )
g2.clipRect( arrowX, 0, width - arrowX, height );
else
g2.clipRect( 0, 0, arrowX + arrowWidth, height );
FlatUIUtils.paintComponentBackground( g2, 0, 0, width, height, focusWidth, arc );
g2.setClip( oldClip );
}
}
// paint vertical line between value and arrow button
if( paintButton ) {
g2.setColor( enabled ? borderColor : disabledBorderColor );
float lw = scale( 1f );
float lx = isLeftToRight ? arrowX : arrowX + arrowWidth - lw;
g2.fill( new Rectangle2D.Float( lx, focusWidth, lw, height - 1 - (focusWidth * 2)) );
Color separatorColor = enabled ? buttonSeparatorColor : buttonDisabledSeparatorColor;
if( separatorColor != null && buttonSeparatorWidth > 0 ) {
g2.setColor( separatorColor );
float lw = scale( buttonSeparatorWidth );
float lx = isLeftToRight ? arrowX : arrowX + arrowWidth - lw;
g2.fill( new Rectangle2D.Float( lx, focusWidth, lw, height - 1 - (focusWidth * 2)) );
}
}
}
@@ -498,11 +597,8 @@ public class FlatComboBoxUI
// make renderer component temporary non-opaque to avoid that renderer paints
// background outside of border if combobox uses larger arc for edges
// (e.g. FlatClientProperties.COMPONENT_ROUND_RECT is true)
boolean oldOpaque = true;
if( c instanceof JComponent ) {
oldOpaque = ((JComponent)c).isOpaque();
if( c instanceof JComponent )
((JComponent)c).setOpaque( false );
}
boolean shouldValidate = (c instanceof JPanel);
@@ -511,7 +607,7 @@ public class FlatComboBoxUI
paddingBorder.uninstall();
if( c instanceof JComponent )
((JComponent)c).setOpaque( oldOpaque );
((JComponent)c).setOpaque( true );
}
@Override
@@ -596,11 +692,12 @@ public class FlatComboBoxUI
return parentParent != null && !comboBox.getBackground().equals( parentParent.getBackground() );
}
/**
* @since 1.3
*/
/** @since 1.3 */
public static boolean isPermanentFocusOwner( JComboBox<?> comboBox ) {
if( comboBox.isEditable() ) {
if( FlatUIUtils.isPermanentFocusOwner( comboBox ) )
return true;
Component editorComponent = comboBox.getEditor().getEditorComponent();
return (editorComponent != null) ? FlatUIUtils.isPermanentFocusOwner( editorComponent ) : false;
} else
@@ -624,6 +721,11 @@ public class FlatComboBoxUI
hoverForeground, hoverBackground, pressedForeground, pressedBackground );
}
protected void updateStyle() {
updateStyle( arrowType, buttonArrowColor, buttonDisabledArrowColor,
buttonHoverArrowColor, null, buttonPressedArrowColor, null );
}
@Override
protected boolean isHover() {
return super.isHover() || (!comboBox.isEditable() ? hover : false);
@@ -710,9 +812,12 @@ public class FlatComboBoxUI
// make opaque to avoid that background shines thru border (e.g. at 150% scaling)
setOpaque( true );
// set popup border
// use non-UIResource to avoid that it is overwritten when making
// popup visible (see JPopupMenu.setInvoker()) in theme editor preview
Border border = UIManager.getBorder( "PopupMenu.border" );
if( border != null )
setBorder( border );
setBorder( FlatUIUtils.nonUIResource( border ) );
}
@Override
@@ -720,8 +825,17 @@ public class FlatComboBoxUI
super.configureList();
list.setCellRenderer( new PopupListCellRenderer() );
updateStyle();
}
void updateStyle() {
if( popupBackground != null )
list.setBackground( popupBackground );
// set popup background because it may shine thru when scaled (e.g. at 150%)
// use non-UIResource to avoid that it is overwritten when making
// popup visible (see JPopupMenu.setInvoker()) in theme editor preview
setBackground( FlatUIUtils.nonUIResource( list.getBackground() ) );
}
@Override
@@ -800,7 +914,7 @@ public class FlatComboBoxUI
private static class CellPaddingBorder
extends AbstractBorder
{
private final Insets padding;
private Insets padding;
private JComponent rendererComponent;
private Border rendererBorder;
@@ -808,7 +922,9 @@ public class FlatComboBoxUI
this.padding = padding;
}
void install( Component c ) {
// using synchronized to avoid problems with code that modifies combo box
// (model, selection, etc) not on AWT thread (which should be not done)
synchronized void install( Component c ) {
if( !(c instanceof JComponent) )
return;
@@ -840,7 +956,7 @@ public class FlatComboBoxUI
* there is no single place to uninstall it.
* This is the reason why this method is called from various places.
*/
void uninstall() {
synchronized void uninstall() {
if( rendererComponent == null )
return;
@@ -851,9 +967,9 @@ public class FlatComboBoxUI
}
@Override
public Insets getBorderInsets( Component c, Insets insets ) {
synchronized public Insets getBorderInsets( Component c, Insets insets ) {
Insets padding = scale( this.padding );
if( rendererBorder != null ) {
if( rendererBorder != null && !(rendererBorder instanceof CellPaddingBorder) ) {
Insets insideInsets = rendererBorder.getBorderInsets( c );
insets.top = Math.max( padding.top, insideInsets.top );
insets.left = Math.max( padding.left, insideInsets.left );

View File

@@ -24,6 +24,9 @@ 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.util.HiDPIUtils;
import com.formdev.flatlaf.util.UIScale;
@@ -40,14 +43,17 @@ import com.formdev.flatlaf.util.UIScale;
*/
public class FlatDropShadowBorder
extends FlatEmptyBorder
implements StyleableBorder
{
private final Color shadowColor;
private final Insets shadowInsets;
private final float shadowOpacity;
@Styleable protected Color shadowColor;
@Styleable protected Insets shadowInsets;
@Styleable protected float shadowOpacity;
private final int shadowSize;
private int shadowSize;
private Image shadowImage;
private Color lastShadowColor;
private float lastShadowOpacity;
private int lastShadowSize;
private double lastSystemScaleFactor;
private float lastUserScaleFactor;
@@ -64,17 +70,43 @@ public class FlatDropShadowBorder
}
public FlatDropShadowBorder( Color shadowColor, Insets shadowInsets, float shadowOpacity ) {
super( Math.max( shadowInsets.top, 0 ), Math.max( shadowInsets.left, 0 ),
Math.max( shadowInsets.bottom, 0 ), Math.max( shadowInsets.right, 0 ) );
super( nonNegativeInsets( shadowInsets ) );
this.shadowColor = shadowColor;
this.shadowInsets = shadowInsets;
this.shadowOpacity = shadowOpacity;
shadowSize = Math.max(
shadowSize = maxInset( shadowInsets );
}
private static Insets nonNegativeInsets( Insets shadowInsets ) {
return new Insets( Math.max( shadowInsets.top, 0 ), Math.max( shadowInsets.left, 0 ),
Math.max( shadowInsets.bottom, 0 ), Math.max( shadowInsets.right, 0 ) );
}
private int maxInset( Insets shadowInsets ) {
return Math.max(
Math.max( shadowInsets.left, shadowInsets.right ),
Math.max( shadowInsets.top, shadowInsets.bottom ) );
}
/** @since 2 */
@Override
public Object applyStyleProperty( String key, Object value ) {
Object oldValue = FlatStylingSupport.applyToAnnotatedObject( this, key, value );
if( key.equals( "shadowInsets" ) ) {
applyStyleProperty( nonNegativeInsets( shadowInsets ) );
shadowSize = maxInset( shadowInsets );
}
return oldValue;
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos() {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
if( shadowSize <= 0 )
@@ -91,12 +123,16 @@ public class FlatDropShadowBorder
float userScaleFactor = UIScale.getUserScaleFactor();
if( shadowImage == null ||
!shadowColor.equals( lastShadowColor ) ||
lastShadowOpacity != shadowOpacity ||
lastShadowSize != shadowSize ||
lastSystemScaleFactor != scaleFactor ||
lastUserScaleFactor != userScaleFactor )
{
shadowImage = createShadowImage( shadowColor, shadowSize, shadowOpacity,
(float) (scaleFactor * userScaleFactor) );
lastShadowColor = shadowColor;
lastShadowOpacity = shadowOpacity;
lastShadowSize = shadowSize;
lastSystemScaleFactor = scaleFactor;
lastUserScaleFactor = userScaleFactor;
}

View File

@@ -24,14 +24,19 @@ import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.event.FocusListener;
import java.beans.PropertyChangeEvent;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicEditorPaneUI;
import javax.swing.text.Caret;
import javax.swing.text.JTextComponent;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JEditorPane}.
@@ -39,8 +44,8 @@ import com.formdev.flatlaf.util.HiDPIUtils;
* <!-- BasicEditorPaneUI -->
*
* @uiDefault EditorPane.font Font
* @uiDefault EditorPane.background Color also used if not editable
* @uiDefault EditorPane.foreground Color
* @uiDefault EditorPane.background Color
* @uiDefault EditorPane.foreground Color also used if not editable
* @uiDefault EditorPane.caretForeground Color
* @uiDefault EditorPane.selectionBackground Color
* @uiDefault EditorPane.selectionForeground Color
@@ -61,20 +66,35 @@ import com.formdev.flatlaf.util.HiDPIUtils;
*/
public class FlatEditorPaneUI
extends BasicEditorPaneUI
implements StyleableUI
{
protected int minimumWidth;
@Styleable protected int minimumWidth;
protected boolean isIntelliJTheme;
protected Color focusedBackground;
private Color background;
@Styleable protected Color disabledBackground;
@Styleable protected Color inactiveBackground;
@Styleable protected Color focusedBackground;
private Color oldDisabledBackground;
private Color oldInactiveBackground;
private Insets defaultMargin;
private Object oldHonorDisplayProperties;
private FocusListener focusListener;
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
return new FlatEditorPaneUI();
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installStyle();
}
@Override
protected void installDefaults() {
super.installDefaults();
@@ -82,6 +102,9 @@ public class FlatEditorPaneUI
String prefix = getPropertyPrefix();
minimumWidth = UIManager.getInt( "Component.minimumWidth" );
isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" );
background = UIManager.getColor( prefix + ".background" );
disabledBackground = UIManager.getColor( prefix + ".disabledBackground" );
inactiveBackground = UIManager.getColor( prefix + ".inactiveBackground" );
focusedBackground = UIManager.getColor( prefix + ".focusedBackground" );
defaultMargin = UIManager.getInsets( prefix + ".margin" );
@@ -95,8 +118,16 @@ public class FlatEditorPaneUI
protected void uninstallDefaults() {
super.uninstallDefaults();
background = null;
disabledBackground = null;
inactiveBackground = null;
focusedBackground = null;
oldDisabledBackground = null;
oldInactiveBackground = null;
oldStyleValues = null;
getComponent().putClientProperty( JEditorPane.HONOR_DISPLAY_PROPERTIES, oldHonorDisplayProperties );
}
@@ -118,19 +149,72 @@ public class FlatEditorPaneUI
}
@Override
protected void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e );
propertyChange( getComponent(), e );
protected Caret createCaret() {
return new FlatCaret( null, false );
}
static void propertyChange( JTextComponent c, PropertyChangeEvent e ) {
@Override
protected void propertyChange( PropertyChangeEvent e ) {
// invoke updateBackground() before super.propertyChange()
String propertyName = e.getPropertyName();
if( "editable".equals( propertyName ) || "enabled".equals( propertyName ) )
updateBackground();
super.propertyChange( e );
propertyChange( getComponent(), e, this::installStyle );
}
static void propertyChange( JTextComponent c, PropertyChangeEvent e, Runnable installStyle ) {
switch( e.getPropertyName() ) {
case FlatClientProperties.MINIMUM_WIDTH:
c.revalidate();
break;
case FlatClientProperties.STYLE:
case FlatClientProperties.STYLE_CLASS:
installStyle.run();
c.revalidate();
c.repaint();
break;
}
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( getComponent(), "EditorPane" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
oldDisabledBackground = disabledBackground;
oldInactiveBackground = inactiveBackground;
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
updateBackground();
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, getComponent(), key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
private void updateBackground() {
FlatTextFieldUI.updateBackground( getComponent(), background,
disabledBackground, inactiveBackground,
oldDisabledBackground, oldInactiveBackground );
}
@Override
public Dimension getPreferredSize( JComponent c ) {
return applyMinimumWidth( c, super.getPreferredSize( c ), minimumWidth, defaultMargin );

View File

@@ -50,6 +50,12 @@ public class FlatEmptyBorder
@Override
public Insets getBorderInsets( Component c, Insets insets ) {
return scaleInsets( c, insets, top, left, bottom, right );
}
protected static Insets scaleInsets( Component c, Insets insets,
int top, int left, int bottom, int right )
{
boolean leftToRight = left == right || c.getComponentOrientation().isLeftToRight();
insets.left = scale( leftToRight ? left : right );
insets.top = scale( top );
@@ -61,4 +67,13 @@ public class FlatEmptyBorder
public Insets getUnscaledBorderInsets() {
return super.getBorderInsets();
}
public Object applyStyleProperty( Insets insets ) {
Insets oldInsets = getUnscaledBorderInsets();
top = insets.top;
left = insets.left;
bottom = insets.bottom;
right = insets.right;
return oldInsets;
}
}

View File

@@ -43,6 +43,7 @@ import javax.swing.plaf.ComponentUI;
* @uiDefault Component.isIntelliJTheme boolean
* @uiDefault FormattedTextField.placeholderForeground Color
* @uiDefault FormattedTextField.focusedBackground Color optional
* @uiDefault FormattedTextField.iconTextGap int optional, default is 4
* @uiDefault TextComponent.selectAllOnFocusPolicy String never, once (default) or always
* @uiDefault TextComponent.selectAllOnMouseClick boolean
*
@@ -59,4 +60,10 @@ public class FlatFormattedTextFieldUI
protected String getPropertyPrefix() {
return "FormattedTextField";
}
/** @since 2 */
@Override
String getStyleType() {
return "FormattedTextField";
}
}

View File

@@ -213,6 +213,13 @@ public class FlatInternalFrameTitlePane
case "componentOrientation":
applyComponentOrientation( frame.getComponentOrientation() );
break;
case "opaque":
// Do not invoke super.propertyChange() here because it always
// invokes repaint(), which would cause endless repainting.
// The opaque flag is temporary changed in FlatUIUtils.hasOpaqueBeenExplicitlySet(),
// invoked from FlatInternalFrameUI.update().
return;
}
super.propertyChange( e );

View File

@@ -24,6 +24,9 @@ import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeListener;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.JComponent;
import javax.swing.JInternalFrame;
import javax.swing.LookAndFeel;
@@ -31,6 +34,10 @@ import javax.swing.UIManager;
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.StyleableUI;
import com.formdev.flatlaf.util.LoggingFacade;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JInternalFrame}.
@@ -86,9 +93,13 @@ import javax.swing.plaf.basic.BasicInternalFrameUI;
*/
public class FlatInternalFrameUI
extends BasicInternalFrameUI
implements StyleableUI
{
protected FlatWindowResizer windowResizer;
private Map<String, Object> oldStyleValues;
private AtomicBoolean borderShared;
public static ComponentUI createUI( JComponent c ) {
return new FlatInternalFrameUI( (JInternalFrame) c );
}
@@ -104,6 +115,8 @@ public class FlatInternalFrameUI
LookAndFeel.installProperty( frame, "opaque", false );
windowResizer = createWindowResizer();
installStyle();
}
@Override
@@ -114,6 +127,9 @@ public class FlatInternalFrameUI
windowResizer.uninstall();
windowResizer = null;
}
oldStyleValues = null;
borderShared = null;
}
@Override
@@ -130,15 +146,69 @@ public class FlatInternalFrameUI
return new FlatBorderListener();
}
@Override
protected PropertyChangeListener createPropertyChangeListener() {
return FlatStylingSupport.createPropertyChangeListener( frame, this::installStyle,
super.createPropertyChangeListener() );
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( frame, "InternalFrame" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
if( borderShared == null )
borderShared = new AtomicBoolean( true );
return FlatStylingSupport.applyToAnnotatedObjectOrBorder( this, key, value, frame, borderShared );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatStylingSupport.getAnnotatedStyleableInfos( this, frame.getBorder() );
}
@Override
public void update( Graphics g, JComponent c ) {
// The internal frame actually should be opaque and fill its background,
// but it must be non-opaque to allow translucent resize handles (outside of visual bounds).
// To avoid that parent may shine through internal frame (e.g. if menu bar is non-opaque),
// fill background excluding insets (translucent resize handles),
// but only if opaque was not set explicitly by application to false.
// If applications has set internal frame opacity to false, do not fill background (for compatibility).
if( !c.isOpaque() && !FlatUIUtils.hasOpaqueBeenExplicitlySet( c ) ) {
Insets insets = c.getInsets();
g.setColor( c.getBackground() );
g.fillRect( insets.left, insets.top,
c.getWidth() - insets.left - insets.right,
c.getHeight() - insets.top - insets.bottom );
}
super.update( g, c );
}
//---- class FlatInternalFrameBorder --------------------------------------
public static class FlatInternalFrameBorder
extends FlatEmptyBorder
implements StyleableBorder
{
private final Color activeBorderColor = UIManager.getColor( "InternalFrame.activeBorderColor" );
private final Color inactiveBorderColor = UIManager.getColor( "InternalFrame.inactiveBorderColor" );
private final int borderLineWidth = FlatUIUtils.getUIInt( "InternalFrame.borderLineWidth", 1 );
private final boolean dropShadowPainted = UIManager.getBoolean( "InternalFrame.dropShadowPainted" );
@Styleable protected Color activeBorderColor = UIManager.getColor( "InternalFrame.activeBorderColor" );
@Styleable protected Color inactiveBorderColor = UIManager.getColor( "InternalFrame.inactiveBorderColor" );
@Styleable protected int borderLineWidth = FlatUIUtils.getUIInt( "InternalFrame.borderLineWidth", 1 );
@Styleable protected boolean dropShadowPainted = UIManager.getBoolean( "InternalFrame.dropShadowPainted" );
private final FlatDropShadowBorder activeDropShadowBorder = new FlatDropShadowBorder(
UIManager.getColor( "InternalFrame.activeDropShadowColor" ),
@@ -153,6 +223,36 @@ public class FlatInternalFrameUI
super( UIManager.getInsets( "InternalFrame.borderMargins" ) );
}
@Override
public Object applyStyleProperty( String key, Object value ) {
switch( key ) {
case "borderMargins": return applyStyleProperty( (Insets) value );
case "activeDropShadowColor": return activeDropShadowBorder.applyStyleProperty( "shadowColor", value );
case "activeDropShadowInsets": return activeDropShadowBorder.applyStyleProperty( "shadowInsets", value );
case "activeDropShadowOpacity": return activeDropShadowBorder.applyStyleProperty( "shadowOpacity", value );
case "inactiveDropShadowColor": return inactiveDropShadowBorder.applyStyleProperty( "shadowColor", value );
case "inactiveDropShadowInsets": return inactiveDropShadowBorder.applyStyleProperty( "shadowInsets", value );
case "inactiveDropShadowOpacity": return inactiveDropShadowBorder.applyStyleProperty( "shadowOpacity", value );
}
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
}
@Override
public Map<String, Class<?>> getStyleableInfos() {
Map<String, Class<?>> infos = new FlatStylingSupport.StyleableInfosMap<>();
FlatStylingSupport.collectAnnotatedStyleableInfos( this, infos );
infos.put( "borderMargins", Insets.class );
infos.put( "activeDropShadowColor", Color.class );
infos.put( "activeDropShadowInsets", Insets.class );
infos.put( "activeDropShadowOpacity", float.class );
infos.put( "inactiveDropShadowColor", Color.class );
infos.put( "inactiveDropShadowInsets", Insets.class );
infos.put( "inactiveDropShadowOpacity", float.class );
return infos;
}
@Override
public Insets getBorderInsets( Component c, Insets insets ) {
if( c instanceof JInternalFrame && ((JInternalFrame)c).isMaximum() ) {
@@ -206,9 +306,7 @@ public class FlatInternalFrameUI
//---- class FlatBorderListener -------------------------------------------
/**
* @since 1.6
*/
/** @since 1.6 */
protected class FlatBorderListener
extends BorderListener
{

View File

@@ -24,6 +24,7 @@ import java.awt.Rectangle;
import java.beans.PropertyChangeEvent;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.swing.Icon;
import javax.swing.JComponent;
@@ -33,8 +34,12 @@ import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.plaf.basic.BasicLabelUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale;
/**
@@ -54,13 +59,30 @@ import com.formdev.flatlaf.util.UIScale;
*/
public class FlatLabelUI
extends BasicLabelUI
implements StyleableUI
{
private Color disabledForeground;
@Styleable protected Color disabledForeground;
private final boolean shared;
private boolean defaults_initialized = false;
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
return FlatUIUtils.createSharedUI( FlatLabelUI.class, FlatLabelUI::new );
return FlatUIUtils.canUseSharedUI( c )
? FlatUIUtils.createSharedUI( FlatLabelUI.class, () -> new FlatLabelUI( true ) )
: new FlatLabelUI( false );
}
/** @since 2 */
protected FlatLabelUI( boolean shared ) {
this.shared = shared;
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installStyle( (JLabel) c );
}
@Override
@@ -77,7 +99,9 @@ public class FlatLabelUI
@Override
protected void uninstallDefaults( JLabel c ) {
super.uninstallDefaults( c );
defaults_initialized = false;
oldStyleValues = null;
}
@Override
@@ -94,10 +118,46 @@ public class FlatLabelUI
if( name == "text" || name == "font" || name == "foreground" ) {
JLabel label = (JLabel) e.getSource();
updateHTMLRenderer( label, label.getText(), true );
} else if( name.equals( FlatClientProperties.STYLE ) || name.equals( FlatClientProperties.STYLE_CLASS ) ) {
JLabel label = (JLabel) e.getSource();
if( shared && FlatStylingSupport.hasStyleProperty( label ) ) {
// unshare component UI if necessary
// updateUI() invokes installStyle() from installUI()
label.updateUI();
} else
installStyle( label );
label.revalidate();
label.repaint();
} else
super.propertyChange( e );
}
/** @since 2 */
protected void installStyle( JLabel c ) {
try {
applyStyle( c, FlatStylingSupport.getResolvedStyle( c, "Label" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( JLabel c, Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style,
(key, value) -> applyStyleProperty( c, key, value ) );
}
/** @since 2 */
protected Object applyStyleProperty( JLabel c, String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, c, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/**
* Checks whether text contains HTML tags that use "absolute-size" keywords
* (e.g. "x-large") for font-size in default style sheet

View File

@@ -37,15 +37,18 @@ public class FlatLineBorder
{
private final Color lineColor;
private final float lineThickness;
/** @since 2 */ private final int arc;
public FlatLineBorder( Insets insets, Color lineColor ) {
this( insets, lineColor, 1f );
this( insets, lineColor, 1f, 0 );
}
public FlatLineBorder( Insets insets, Color lineColor, float lineThickness ) {
/** @since 2 */
public FlatLineBorder( Insets insets, Color lineColor, float lineThickness, int arc ) {
super( insets );
this.lineColor = lineColor;
this.lineThickness = lineThickness;
this.arc = arc;
}
public Color getLineColor() {
@@ -56,13 +59,18 @@ public class FlatLineBorder
return lineThickness;
}
/** @since 2 */
public int getArc() {
return arc;
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
Graphics2D g2 = (Graphics2D) g.create();
try {
FlatUIUtils.setRenderingHints( g2 );
g2.setColor( lineColor );
FlatUIUtils.paintComponentBorder( g2, x, y, width, height, 0f, scale( lineThickness ), 0f );
FlatUIUtils.paintOutlinedComponent( g2, x, y, width, height,
0, 0, 0, scale( getLineThickness() ), scale( getArc() ), null, getLineColor(), null );
} finally {
g2.dispose();
}

View File

@@ -16,11 +16,15 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Insets;
import java.util.function.Function;
import javax.swing.JList;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.ListUI;
/**
* Cell border for {@link javax.swing.DefaultListCellRenderer}
@@ -33,12 +37,54 @@ import javax.swing.UIManager;
public class FlatListCellBorder
extends FlatLineBorder
{
final boolean showCellFocusIndicator = UIManager.getBoolean( "List.showCellFocusIndicator" );
/** @since 2 */ protected boolean showCellFocusIndicator = UIManager.getBoolean( "List.showCellFocusIndicator" );
private Component c;
protected FlatListCellBorder() {
super( UIManager.getInsets( "List.cellMargins" ), UIManager.getColor( "List.cellFocusColor" ) );
}
@Override
public Insets getBorderInsets( Component c, Insets insets ) {
Insets m = getStyleFromListUI( c, ui -> ui.cellMargins );
if( m != null )
return scaleInsets( c, insets, m.top, m.left, m.bottom, m.right );
return super.getBorderInsets( c, insets );
}
@Override
public Color getLineColor() {
if( c != null ) {
Color color = getStyleFromListUI( c, ui -> ui.cellFocusColor );
if( color != null )
return color;
}
return super.getLineColor();
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
this.c = c;
super.paintBorder( c, g, x, y, width, height );
this.c = null;
}
/**
* Because this borders are always shared for all lists,
* get border specific style from FlatListUI.
*/
static <T> T getStyleFromListUI( Component c, Function<FlatListUI, T> f ) {
JList<?> list = (JList<?>) SwingUtilities.getAncestorOfClass( JList.class, c );
if( list != null ) {
ListUI ui = list.getUI();
if( ui instanceof FlatListUI )
return f.apply( (FlatListUI) ui );
}
return null;
}
//---- class Default ------------------------------------------------------
/**
@@ -67,17 +113,19 @@ public class FlatListCellBorder
/**
* Border for selected cell that uses margins and paints focus indicator border
* if enabled (List.showCellFocusIndicator=true) and exactly one item is selected.
* if enabled (List.showCellFocusIndicator=true) and multiple items are selected.
*/
public static class Selected
extends FlatListCellBorder
{
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
Boolean b = getStyleFromListUI( c, ui -> ui.showCellFocusIndicator );
boolean showCellFocusIndicator = (b != null) ? b : this.showCellFocusIndicator;
if( !showCellFocusIndicator )
return;
// paint focus indicator border only if exactly one item is selected
// paint focus indicator border only if multiple items are selected
JList<?> list = (JList<?>) SwingUtilities.getAncestorOfClass( JList.class, c );
if( list != null && list.getMinSelectionIndex() == list.getMaxSelectionIndex() )
return;

View File

@@ -18,14 +18,19 @@ package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Insets;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.beans.PropertyChangeListener;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicListUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.LoggingFacade;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JList}.
@@ -65,16 +70,31 @@ import com.formdev.flatlaf.FlatClientProperties;
*/
public class FlatListUI
extends BasicListUI
implements StyleableUI
{
protected Color selectionBackground;
protected Color selectionForeground;
protected Color selectionInactiveBackground;
protected Color selectionInactiveForeground;
@Styleable protected Color selectionBackground;
@Styleable protected Color selectionForeground;
@Styleable protected Color selectionInactiveBackground;
@Styleable protected Color selectionInactiveForeground;
// for FlatListCellBorder
/** @since 2 */ @Styleable protected Insets cellMargins;
/** @since 2 */ @Styleable protected Color cellFocusColor;
/** @since 2 */ @Styleable protected Boolean showCellFocusIndicator;
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
return new FlatListUI();
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installStyle();
}
@Override
protected void installDefaults() {
super.installDefaults();
@@ -95,17 +115,8 @@ public class FlatListUI
selectionForeground = null;
selectionInactiveBackground = null;
selectionInactiveForeground = null;
}
@Override
protected PropertyChangeListener createPropertyChangeListener() {
PropertyChangeListener superListener = super.createPropertyChangeListener();
return e -> {
superListener.propertyChange( e );
if( FlatClientProperties.COMPONENT_FOCUS_OWNER.equals( e.getPropertyName() ) )
toggleSelectionColors();
};
oldStyleValues = null;
}
@Override
@@ -129,6 +140,75 @@ public class FlatListUI
};
}
@Override
protected PropertyChangeListener createPropertyChangeListener() {
PropertyChangeListener superListener = super.createPropertyChangeListener();
return e -> {
superListener.propertyChange( e );
switch( e.getPropertyName() ) {
case FlatClientProperties.COMPONENT_FOCUS_OWNER:
toggleSelectionColors();
break;
case FlatClientProperties.STYLE:
case FlatClientProperties.STYLE_CLASS:
installStyle();
list.revalidate();
list.repaint();
break;
}
};
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( list, "List" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
Color oldSelectionBackground = selectionBackground;
Color oldSelectionForeground = selectionForeground;
Color oldSelectionInactiveBackground = selectionInactiveBackground;
Color oldSelectionInactiveForeground = selectionInactiveForeground;
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
// update selection background
if( selectionBackground != oldSelectionBackground ) {
Color selBg = list.getSelectionBackground();
if( selBg == oldSelectionBackground )
list.setSelectionBackground( selectionBackground );
else if( selBg == oldSelectionInactiveBackground )
list.setSelectionBackground( selectionInactiveBackground );
}
// update selection foreground
if( selectionForeground != oldSelectionForeground ) {
Color selFg = list.getSelectionForeground();
if( selFg == oldSelectionForeground )
list.setSelectionForeground( selectionForeground );
else if( selFg == oldSelectionInactiveForeground )
list.setSelectionForeground( selectionInactiveForeground );
}
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, list, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/**
* Toggle selection colors from focused to inactive and vice versa.
*

View File

@@ -29,7 +29,7 @@ import javax.swing.plaf.basic.BasicBorders;
public class FlatMarginBorder
extends BasicBorders.MarginBorder
{
private final int left, right, top, bottom;
protected int left, right, top, bottom;
public FlatMarginBorder() {
left = right = top = bottom = 0;

View File

@@ -21,8 +21,11 @@ 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;
/**
* Border for {@link javax.swing.JMenuBar}.
@@ -33,11 +36,26 @@ import javax.swing.UIManager;
*/
public class FlatMenuBarBorder
extends FlatMarginBorder
implements StyleableBorder
{
private final Color borderColor = UIManager.getColor( "MenuBar.borderColor" );
@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 );
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
if( !showBottomSeparator( c ) )
return;
float lineHeight = scale( (float) 1 );
FlatUIUtils.paintFilledRectangle( g, borderColor, x, y + height - lineHeight, width, lineHeight );
}
@@ -53,4 +71,9 @@ public class FlatMenuBarBorder
insets.right = scale( margin.right );
return insets;
}
/** @since 2 */
protected boolean showBottomSeparator( Component c ) {
return !FlatMenuBarUI.useUnifiedBackground( c );
}
}

View File

@@ -17,9 +17,14 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeListener;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.JComponent;
@@ -36,6 +41,9 @@ import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicMenuBarUI;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.SystemInfo;
/**
@@ -47,13 +55,30 @@ import com.formdev.flatlaf.util.SystemInfo;
* @uiDefault MenuBar.background Color
* @uiDefault MenuBar.foreground Color
* @uiDefault MenuBar.border Border
*
* <!-- FlatMenuBarUI -->
*
* @uiDefault TitlePane.unifiedBackground boolean
*
* @author Karl Tauber
*/
public class FlatMenuBarUI
extends BasicMenuBarUI
implements StyleableUI
{
// used in FlatMenuItemBorder
/** @since 2 */ @Styleable protected Insets itemMargins;
// used in FlatMenuUI
/** @since 2 */ @Styleable protected Color hoverBackground;
/** @since 2 */ @Styleable protected Color underlineSelectionBackground;
/** @since 2 */ @Styleable protected Color underlineSelectionColor;
/** @since 2 */ @Styleable protected int underlineSelectionHeight = -1;
private PropertyChangeListener propertyChangeListener;
private Map<String, Object> oldStyleValues;
private AtomicBoolean borderShared;
public static ComponentUI createUI( JComponent c ) {
return new FlatMenuBarUI();
}
@@ -63,6 +88,13 @@ public class FlatMenuBarUI
* Do not add any functionality here.
*/
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installStyle();
}
@Override
protected void installDefaults() {
super.installDefaults();
@@ -70,6 +102,30 @@ public class FlatMenuBarUI
LookAndFeel.installProperty( menuBar, "opaque", false );
}
@Override
protected void uninstallDefaults() {
super.uninstallDefaults();
oldStyleValues = null;
borderShared = null;
}
@Override
protected void installListeners() {
super.installListeners();
propertyChangeListener = FlatStylingSupport.createPropertyChangeListener( menuBar, this::installStyle, null );
menuBar.addPropertyChangeListener( propertyChangeListener );
}
@Override
protected void uninstallListeners() {
super.uninstallListeners();
menuBar.removePropertyChangeListener( propertyChangeListener );
propertyChangeListener = null;
}
@Override
protected void installKeyboardActions() {
super.installKeyboardActions();
@@ -82,6 +138,33 @@ public class FlatMenuBarUI
map.put( "takeFocus", new TakeFocus() );
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( menuBar, "MenuBar" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
if( borderShared == null )
borderShared = new AtomicBoolean( true );
return FlatStylingSupport.applyToAnnotatedObjectOrBorder( this, key, value, menuBar, borderShared );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatStylingSupport.getAnnotatedStyleableInfos( this, menuBar.getBorder() );
}
@Override
public void update( Graphics g, JComponent c ) {
// paint background
@@ -97,20 +180,22 @@ public class FlatMenuBarUI
protected Color getBackground( JComponent c ) {
Color background = c.getBackground();
// paint background if opaque or if having custom background color
if( c.isOpaque() || !(background instanceof UIResource) )
// paint background if opaque
if( c.isOpaque() )
return background;
// paint background if menu bar is not the "main" menu bar
// do not paint background if non-opaque and having custom background color
if( !(background instanceof UIResource) )
return null;
// paint background if menu bar is not the "main" menu bar (e.g. in internal frame)
JRootPane rootPane = SwingUtilities.getRootPane( c );
if( rootPane == null || !(rootPane.getParent() instanceof Window) || rootPane.getJMenuBar() != c )
return background;
// use parent background for unified title pane
// (not storing value of "TitlePane.unifiedBackground" in class to allow changing at runtime)
if( UIManager.getBoolean( "TitlePane.unifiedBackground" ) &&
FlatNativeWindowBorder.hasCustomDecoration( (Window) rootPane.getParent() ) )
background = FlatUIUtils.getParentBackground( c );
if( useUnifiedBackground( c ) )
background = FlatUIUtils.getParentBackground( c );
// paint background in full screen mode
if( FlatUIUtils.isFullScreen( rootPane ) )
@@ -120,6 +205,22 @@ public class FlatMenuBarUI
return FlatRootPaneUI.isMenuBarEmbedded( rootPane ) ? null : background;
}
/**@since 2 */
static boolean useUnifiedBackground( Component c ) {
// check whether:
// - TitlePane.unifiedBackground is true and
// - menu bar is the "main" menu bar and
// - window has custom decorations enabled
JRootPane rootPane;
// (not storing value of "TitlePane.unifiedBackground" in class to allow changing at runtime)
return UIManager.getBoolean( "TitlePane.unifiedBackground" ) &&
(rootPane = SwingUtilities.getRootPane( c )) != null &&
rootPane.getParent() instanceof Window &&
rootPane.getJMenuBar() == c &&
FlatNativeWindowBorder.hasCustomDecoration( (Window) rootPane.getParent() );
}
//---- class TakeFocus ----------------------------------------------------
/**

View File

@@ -18,9 +18,11 @@ package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Component;
import java.awt.Container;
import java.awt.Insets;
import javax.swing.JMenuBar;
import javax.swing.UIManager;
import javax.swing.plaf.MenuBarUI;
/**
* Border for {@link javax.swing.JMenu}, {@link javax.swing.JMenuItem},
@@ -33,15 +35,22 @@ import javax.swing.UIManager;
public class FlatMenuItemBorder
extends FlatMarginBorder
{
// only used if parent menubar is not a instance of FlatMenuBarUI
private final Insets menuBarItemMargins = UIManager.getInsets( "MenuBar.itemMargins" );
@Override
public Insets getBorderInsets( Component c, Insets insets ) {
if( c.getParent() instanceof JMenuBar ) {
insets.top = scale( menuBarItemMargins.top );
insets.left = scale( menuBarItemMargins.left );
insets.bottom = scale( menuBarItemMargins.bottom );
insets.right = scale( menuBarItemMargins.right );
Container parent = c.getParent();
if( parent instanceof JMenuBar ) {
// get margins from FlatMenuBarUI to allow styling
MenuBarUI ui = ((JMenuBar)parent).getUI();
Insets margins = (ui instanceof FlatMenuBarUI && ((FlatMenuBarUI)ui).itemMargins != null)
? ((FlatMenuBarUI)ui).itemMargins
: this.menuBarItemMargins;
insets.top = scale( margins.top );
insets.left = scale( margins.left );
insets.bottom = scale( margins.bottom );
insets.right = scale( margins.right );
return insets;
} else
return super.getBorderInsets( c, insets );

View File

@@ -19,6 +19,7 @@ package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
@@ -30,7 +31,9 @@ import java.awt.Rectangle;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.text.AttributedCharacterIterator;
import java.util.Map;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.KeyStroke;
@@ -38,7 +41,12 @@ import javax.swing.SwingUtilities;
import javax.swing.UIManager;
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.FlatCheckBoxMenuItemIcon;
import com.formdev.flatlaf.icons.FlatMenuArrowIcon;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.DerivedColor;
import com.formdev.flatlaf.util.Graphics2DProxy;
import com.formdev.flatlaf.util.HiDPIUtils;
@@ -47,43 +55,48 @@ import com.formdev.flatlaf.util.SystemInfo;
/**
* Renderer for menu items.
*
* @uiDefault MenuItem.verticallyAlignText boolean
* @uiDefault MenuItem.minimumWidth int
* @uiDefault MenuItem.minimumIconSize Dimension
* @uiDefault MenuItem.textAcceleratorGap int
* @uiDefault MenuItem.textNoAcceleratorGap int
* @uiDefault MenuItem.acceleratorArrowGap int
* @uiDefault MenuItem.checkBackground Color
* @uiDefault MenuItem.checkMargins Insets
* @uiDefault MenuItem.selectionType String null (default) or underline
* @uiDefault MenuItem.underlineSelectionBackground Color
* @uiDefault MenuItem.underlineSelectionCheckBackground Color
* @uiDefault MenuItem.underlineSelectionColor Color
* @uiDefault MenuItem.underlineSelectionHeight int
* @uiDefault MenuItem.selectionBackground Color
*
* @author Karl Tauber
*/
public class FlatMenuItemRenderer
{
private static final String KEY_MAX_ICONS_WIDTH = "FlatLaf.internal.FlatMenuItemRenderer.maxIconWidth";
protected final JMenuItem menuItem;
protected final Icon checkIcon;
protected final Icon arrowIcon;
protected final Font acceleratorFont;
protected Icon checkIcon;
protected Icon arrowIcon;
@Styleable protected Font acceleratorFont;
protected final String acceleratorDelimiter;
protected final int minimumWidth = UIManager.getInt( "MenuItem.minimumWidth" );
protected final Dimension minimumIconSize;
protected final int textAcceleratorGap = FlatUIUtils.getUIInt( "MenuItem.textAcceleratorGap", 28 );
protected final int textNoAcceleratorGap = FlatUIUtils.getUIInt( "MenuItem.textNoAcceleratorGap", 6 );
protected final int acceleratorArrowGap = FlatUIUtils.getUIInt( "MenuItem.acceleratorArrowGap", 2 );
/** @since 2 */ @Styleable protected boolean verticallyAlignText = FlatUIUtils.getUIBoolean( "MenuItem.verticallyAlignText", true );
@Styleable protected int minimumWidth = UIManager.getInt( "MenuItem.minimumWidth" );
@Styleable protected Dimension minimumIconSize;
@Styleable protected int textAcceleratorGap = FlatUIUtils.getUIInt( "MenuItem.textAcceleratorGap", 28 );
@Styleable protected int textNoAcceleratorGap = FlatUIUtils.getUIInt( "MenuItem.textNoAcceleratorGap", 6 );
@Styleable protected int acceleratorArrowGap = FlatUIUtils.getUIInt( "MenuItem.acceleratorArrowGap", 2 );
protected final Color checkBackground = UIManager.getColor( "MenuItem.checkBackground" );
protected final Insets checkMargins = UIManager.getInsets( "MenuItem.checkMargins" );
@Styleable protected Color checkBackground = UIManager.getColor( "MenuItem.checkBackground" );
@Styleable protected Insets checkMargins = UIManager.getInsets( "MenuItem.checkMargins" );
protected final Color underlineSelectionBackground = UIManager.getColor( "MenuItem.underlineSelectionBackground" );
protected final Color underlineSelectionCheckBackground = UIManager.getColor( "MenuItem.underlineSelectionCheckBackground" );
protected final Color underlineSelectionColor = UIManager.getColor( "MenuItem.underlineSelectionColor" );
protected final int underlineSelectionHeight = UIManager.getInt( "MenuItem.underlineSelectionHeight" );
@Styleable protected Color underlineSelectionBackground = UIManager.getColor( "MenuItem.underlineSelectionBackground" );
@Styleable protected Color underlineSelectionCheckBackground = UIManager.getColor( "MenuItem.underlineSelectionCheckBackground" );
@Styleable protected Color underlineSelectionColor = UIManager.getColor( "MenuItem.underlineSelectionColor" );
@Styleable protected int underlineSelectionHeight = UIManager.getInt( "MenuItem.underlineSelectionHeight" );
protected final Color selectionBackground = UIManager.getColor( "MenuItem.selectionBackground" );
private boolean iconsShared = true;
protected FlatMenuItemRenderer( JMenuItem menuItem, Icon checkIcon, Icon arrowIcon,
Font acceleratorFont, String acceleratorDelimiter )
@@ -98,6 +111,64 @@ public class FlatMenuItemRenderer
this.minimumIconSize = (minimumIconSize != null) ? minimumIconSize : new Dimension( 16, 16 );
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
// style icon
if( key.startsWith( "icon." ) || key.equals( "selectionForeground" ) ) {
if( iconsShared ) {
if( checkIcon instanceof FlatCheckBoxMenuItemIcon )
checkIcon = FlatStylingSupport.cloneIcon( checkIcon );
if( arrowIcon instanceof FlatMenuArrowIcon )
arrowIcon = FlatStylingSupport.cloneIcon( arrowIcon );
iconsShared = false;
}
if( key.startsWith( "icon." ) ) {
String key2 = key.substring( "icon.".length() );
try {
if( checkIcon instanceof FlatCheckBoxMenuItemIcon )
return ((FlatCheckBoxMenuItemIcon)checkIcon).applyStyleProperty( key2, value );
} catch ( UnknownStyleException ex ) {
// ignore
}
try {
if( arrowIcon instanceof FlatMenuArrowIcon )
return ((FlatMenuArrowIcon)arrowIcon).applyStyleProperty( key2, value );
} catch ( UnknownStyleException ex ) {
// ignore
}
// keys with prefix "icon." are only for icons
throw new UnknownStyleException( key );
} else if( key.equals( "selectionForeground" ) ) {
// special case: same key is used in icons and in menuitem
if( checkIcon instanceof FlatCheckBoxMenuItemIcon )
((FlatCheckBoxMenuItemIcon)checkIcon).applyStyleProperty( key, value );
if( arrowIcon instanceof FlatMenuArrowIcon )
((FlatMenuArrowIcon)arrowIcon).applyStyleProperty( key, value );
// throw exception because the caller should also apply this key
throw new UnknownStyleException( key );
}
}
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
}
/** @since 2 */
public Map<String, Class<?>> getStyleableInfos() {
Map<String, Class<?>> infos = FlatStylingSupport.getAnnotatedStyleableInfos( this );
if( checkIcon instanceof FlatCheckBoxMenuItemIcon )
FlatStylingSupport.putAllPrefixKey( infos, "icon.", ((FlatCheckBoxMenuItemIcon)checkIcon).getStyleableInfos() );
infos.remove( "icon.selectionForeground" );
if( arrowIcon instanceof FlatMenuArrowIcon )
FlatStylingSupport.putAllPrefixKey( infos, "icon.", ((FlatMenuArrowIcon)arrowIcon).getStyleableInfos() );
infos.remove( "icon.selectionForeground" );
return infos;
}
protected Dimension getPreferredMenuItemSize() {
int width = 0;
int height = 0;
@@ -254,7 +325,7 @@ debug*/
paintBackground( g, underlineSelection ? underlineSelectionBackground : selectionBackground );
if( underlineSelection && isArmedOrSelected( menuItem ) )
paintUnderlineSelection( g, underlineSelectionColor, underlineSelectionHeight );
paintIcon( g, iconRect, getIconForPainting(), underlineSelection ? underlineSelectionCheckBackground : checkBackground );
paintIcon( g, iconRect, getIconForPainting(), underlineSelection ? underlineSelectionCheckBackground : checkBackground, selectionBackground );
paintText( g, textRect, menuItem.getText(), selectionForeground, disabledForeground );
paintAccelerator( g, accelRect, getAcceleratorText(), acceleratorForeground, acceleratorSelectionForeground, disabledForeground );
if( !isTopLevelMenu( menuItem ) )
@@ -301,7 +372,7 @@ debug*/
return FlatUIUtils.deriveColor( background, baseColor );
}
protected void paintIcon( Graphics g, Rectangle iconRect, Icon icon, Color checkBackground ) {
protected void paintIcon( Graphics g, Rectangle iconRect, Icon icon, Color checkBackground, Color selectionBackground ) {
// if checkbox/radiobutton menu item is selected and also has a custom icon,
// then use filled icon background to indicate selection (instead of using checkIcon)
if( menuItem.isSelected() && checkIcon != null && icon != checkIcon ) {
@@ -343,11 +414,10 @@ debug*/
return;
// center because the real icon may be smaller than dimension in iconRect
int x = iconRect.x + centerOffset( iconRect.width, icon.getIconWidth() );
int y = iconRect.y + centerOffset( iconRect.height, icon.getIconHeight() );
// paint
icon.paintIcon( menuItem, g, x, y );
icon.paintIcon( menuItem, g, iconRect.x, y );
}
protected static void paintText( Graphics g, JMenuItem menuItem,
@@ -382,6 +452,10 @@ debug*/
htmlView.paint( HiDPIUtils.createGraphicsTextYCorrection( (Graphics2D) g ), textRect );
}
/**
* Returns {@code true} if either the menu item is armed (mouse over item)
* or it is a {@code JMenu} and selected (shows submenu).
*/
protected static boolean isArmedOrSelected( JMenuItem menuItem ) {
return menuItem.isArmed() || (menuItem instanceof JMenu && menuItem.isSelected());
}
@@ -412,6 +486,12 @@ debug*/
return pressedIcon;
}
if( isArmedOrSelected( menuItem ) ) {
Icon selectedIcon = menuItem.getSelectedIcon();
if( selectedIcon != null )
return selectedIcon;
}
return icon;
}
@@ -498,6 +578,44 @@ debug*/
shiftGlyph = 0x21E7,
commandGlyph = 0x2318;
/**
* Calculates the maximum width of all menu item icons in the popup.
*/
private int getMaxIconsWidth() {
if( !verticallyAlignText )
return 0;
Container parent = menuItem.getParent();
if( !(parent instanceof JComponent) )
return 0;
int maxWidth = FlatClientProperties.clientPropertyInt( (JComponent) parent, KEY_MAX_ICONS_WIDTH, -1 );
if( maxWidth >= 0 )
return maxWidth;
maxWidth = 0;
for( Component c : parent.getComponents() ) {
if( !(c instanceof JMenuItem) )
continue;
Icon icon = ((JMenuItem)c).getIcon();
if( icon != null )
maxWidth = Math.max( maxWidth, icon.getIconWidth() );
}
((JComponent)parent).putClientProperty( KEY_MAX_ICONS_WIDTH, maxWidth );
return maxWidth;
}
static void clearClientProperties( Component c ) {
if( !(c instanceof JComponent) )
return;
JComponent jc = (JComponent) c;
jc.putClientProperty( FlatMenuItemRenderer.KEY_MAX_ICONS_WIDTH, null );
}
//---- class MinSizeIcon --------------------------------------------------
private class MinSizeIcon
@@ -512,6 +630,7 @@ debug*/
@Override
public int getIconWidth() {
int iconWidth = (delegate != null) ? delegate.getIconWidth() : 0;
iconWidth = Math.max( iconWidth, getMaxIconsWidth() );
return Math.max( iconWidth, scale( minimumIconSize.width ) );
}

View File

@@ -16,13 +16,19 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.beans.PropertyChangeListener;
import java.util.Map;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.LookAndFeel;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicMenuItemUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.LoggingFacade;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JMenuItem}.
@@ -54,13 +60,22 @@ import javax.swing.plaf.basic.BasicMenuItemUI;
*/
public class FlatMenuItemUI
extends BasicMenuItemUI
implements StyleableUI
{
private FlatMenuItemRenderer renderer;
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
return new FlatMenuItemUI();
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installStyle();
}
@Override
protected void installDefaults() {
super.installDefaults();
@@ -74,13 +89,72 @@ public class FlatMenuItemUI
protected void uninstallDefaults() {
super.uninstallDefaults();
FlatMenuItemRenderer.clearClientProperties( menuItem.getParent() );
renderer = null;
oldStyleValues = null;
}
protected FlatMenuItemRenderer createRenderer() {
return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter );
}
@Override
protected PropertyChangeListener createPropertyChangeListener( JComponent c ) {
return FlatStylingSupport.createPropertyChangeListener( c, this::installStyle, super.createPropertyChangeListener( c ) );
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( menuItem, "MenuItem" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
try {
return renderer.applyStyleProperty( key, value );
} catch ( UnknownStyleException ex ) {
// ignore
}
Object oldValue;
switch( key ) {
// BasicMenuItemUI
case "selectionBackground": oldValue = selectionBackground; selectionBackground = (Color) value; return oldValue;
case "selectionForeground": oldValue = selectionForeground; selectionForeground = (Color) value; return oldValue;
case "disabledForeground": oldValue = disabledForeground; disabledForeground = (Color) value; return oldValue;
case "acceleratorForeground": oldValue = acceleratorForeground; acceleratorForeground = (Color) value; return oldValue;
case "acceleratorSelectionForeground": oldValue = acceleratorSelectionForeground; acceleratorSelectionForeground = (Color) value; return oldValue;
}
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, menuItem, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return getStyleableInfos( renderer );
}
static Map<String, Class<?>> getStyleableInfos( FlatMenuItemRenderer renderer ) {
Map<String, Class<?>> infos = new FlatStylingSupport.StyleableInfosMap<>();
infos.put( "selectionBackground", Color.class );
infos.put( "selectionForeground", Color.class );
infos.put( "disabledForeground", Color.class );
infos.put( "acceleratorForeground", Color.class );
infos.put( "acceleratorSelectionForeground", Color.class );
infos.putAll( renderer.getStyleableInfos() );
return infos;
}
@Override
protected Dimension getPreferredMenuItemSize( JComponent c, Icon checkIcon, Icon arrowIcon, int defaultTextIconGap ) {
return renderer.getPreferredMenuItemSize();

View File

@@ -21,16 +21,24 @@ import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeListener;
import java.util.Map;
import java.util.function.Function;
import javax.swing.ButtonModel;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.LookAndFeel;
import javax.swing.UIManager;
import javax.swing.event.MouseInputListener;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.MenuBarUI;
import javax.swing.plaf.basic.BasicMenuUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.LoggingFacade;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JMenu}.
@@ -60,10 +68,10 @@ import javax.swing.plaf.basic.BasicMenuUI;
* <!-- FlatMenuUI -->
*
* @uiDefault MenuItem.iconTextGap int
* @uiDefault MenuBar.hoverBackground Color
*
* <!-- FlatMenuRenderer -->
*
* @uiDefault MenuBar.hoverBackground Color
* @uiDefault MenuBar.underlineSelectionBackground Color
* @uiDefault MenuBar.underlineSelectionColor Color
* @uiDefault MenuBar.underlineSelectionHeight int
@@ -72,14 +80,22 @@ import javax.swing.plaf.basic.BasicMenuUI;
*/
public class FlatMenuUI
extends BasicMenuUI
implements StyleableUI
{
private Color hoverBackground;
private FlatMenuItemRenderer renderer;
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
return new FlatMenuUI();
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installStyle();
}
@Override
protected void installDefaults() {
super.installDefaults();
@@ -88,7 +104,6 @@ public class FlatMenuUI
menuItem.setRolloverEnabled( true );
hoverBackground = UIManager.getColor( "MenuBar.hoverBackground" );
renderer = createRenderer();
}
@@ -96,8 +111,9 @@ public class FlatMenuUI
protected void uninstallDefaults() {
super.uninstallDefaults();
hoverBackground = null;
FlatMenuItemRenderer.clearClientProperties( menuItem.getParent() );
renderer = null;
oldStyleValues = null;
}
protected FlatMenuItemRenderer createRenderer() {
@@ -129,6 +145,52 @@ public class FlatMenuUI
};
}
@Override
protected PropertyChangeListener createPropertyChangeListener( JComponent c ) {
return FlatStylingSupport.createPropertyChangeListener( c, this::installStyle, super.createPropertyChangeListener( c ) );
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( menuItem, "Menu" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
try {
return renderer.applyStyleProperty( key, value );
} catch ( UnknownStyleException ex ) {
// ignore
}
Object oldValue;
switch( key ) {
// BasicMenuItemUI
case "selectionBackground": oldValue = selectionBackground; selectionBackground = (Color) value; return oldValue;
case "selectionForeground": oldValue = selectionForeground; selectionForeground = (Color) value; return oldValue;
case "disabledForeground": oldValue = disabledForeground; disabledForeground = (Color) value; return oldValue;
case "acceleratorForeground": oldValue = acceleratorForeground; acceleratorForeground = (Color) value; return oldValue;
case "acceleratorSelectionForeground": oldValue = acceleratorSelectionForeground; acceleratorSelectionForeground = (Color) value; return oldValue;
}
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, menuItem, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatMenuItemUI.getStyleableInfos( renderer );
}
@Override
public Dimension getMinimumSize( JComponent c ) {
// avoid that top-level menus (in menu bar) are made smaller if horizontal space is rare
@@ -153,9 +215,10 @@ public class FlatMenuUI
protected class FlatMenuRenderer
extends FlatMenuItemRenderer
{
protected final Color menuBarUnderlineSelectionBackground = FlatUIUtils.getUIColor( "MenuBar.underlineSelectionBackground", underlineSelectionBackground );
protected final Color menuBarUnderlineSelectionColor = FlatUIUtils.getUIColor( "MenuBar.underlineSelectionColor", underlineSelectionColor );
protected final int menuBarUnderlineSelectionHeight = FlatUIUtils.getUIInt( "MenuBar.underlineSelectionHeight", underlineSelectionHeight );
protected Color hoverBackground = UIManager.getColor( "MenuBar.hoverBackground" );
protected Color menuBarUnderlineSelectionBackground = FlatUIUtils.getUIColor( "MenuBar.underlineSelectionBackground", underlineSelectionBackground );
protected Color menuBarUnderlineSelectionColor = FlatUIUtils.getUIColor( "MenuBar.underlineSelectionColor", underlineSelectionColor );
protected int menuBarUnderlineSelectionHeight = FlatUIUtils.getUIInt( "MenuBar.underlineSelectionHeight", underlineSelectionHeight );
protected FlatMenuRenderer( JMenuItem menuItem, Icon checkIcon, Icon arrowIcon,
Font acceleratorFont, String acceleratorDelimiter )
@@ -165,27 +228,39 @@ public class FlatMenuUI
@Override
protected void paintBackground( Graphics g, Color selectionBackground ) {
if( isUnderlineSelection() && ((JMenu)menuItem).isTopLevelMenu() )
selectionBackground = menuBarUnderlineSelectionBackground;
if( ((JMenu)menuItem).isTopLevelMenu() ) {
if( isUnderlineSelection() )
selectionBackground = getStyleFromMenuBarUI( ui -> ui.underlineSelectionBackground, menuBarUnderlineSelectionBackground );
ButtonModel model = menuItem.getModel();
if( model.isRollover() && !model.isArmed() && !model.isSelected() &&
model.isEnabled() && ((JMenu)menuItem).isTopLevelMenu() )
{
g.setColor( deriveBackground( hoverBackground ) );
g.fillRect( 0, 0, menuItem.getWidth(), menuItem.getHeight() );
} else
super.paintBackground( g, selectionBackground );
ButtonModel model = menuItem.getModel();
if( model.isRollover() && !model.isArmed() && !model.isSelected() && model.isEnabled() ) {
g.setColor( deriveBackground( getStyleFromMenuBarUI( ui -> ui.hoverBackground, hoverBackground ) ) );
g.fillRect( 0, 0, menuItem.getWidth(), menuItem.getHeight() );
return;
}
}
super.paintBackground( g, selectionBackground );
}
@Override
protected void paintUnderlineSelection( Graphics g, Color underlineSelectionColor, int underlineSelectionHeight ) {
if( ((JMenu)menuItem).isTopLevelMenu() ) {
underlineSelectionColor = menuBarUnderlineSelectionColor;
underlineSelectionHeight = menuBarUnderlineSelectionHeight;
underlineSelectionColor = getStyleFromMenuBarUI( ui -> ui.underlineSelectionColor, menuBarUnderlineSelectionColor );
underlineSelectionHeight = getStyleFromMenuBarUI( ui -> (ui.underlineSelectionHeight != -1)
? ui.underlineSelectionHeight : null, menuBarUnderlineSelectionHeight );
}
super.paintUnderlineSelection( g, underlineSelectionColor, underlineSelectionHeight );
}
private <T> T getStyleFromMenuBarUI( Function<FlatMenuBarUI, T> f, T defaultValue ) {
MenuBarUI ui = ((JMenuBar)menuItem.getParent()).getUI();
if( !(ui instanceof FlatMenuBarUI) )
return defaultValue;
T value = f.apply( (FlatMenuBarUI) ui );
return (value != null) ? value : defaultValue;
}
}
}

View File

@@ -235,7 +235,8 @@ public class FlatNativeWindowBorder
}
static void setTitleBarHeightAndHitTestSpots( Window window, int titleBarHeight,
List<Rectangle> hitTestSpots, Rectangle appIconBounds )
List<Rectangle> hitTestSpots, Rectangle appIconBounds, Rectangle minimizeButtonBounds,
Rectangle maximizeButtonBounds, Rectangle closeButtonBounds )
{
if( canUseJBRCustomDecorations ) {
JBRCustomDecorations.setTitleBarHeightAndHitTestSpots( window, titleBarHeight, hitTestSpots );
@@ -245,9 +246,8 @@ public class FlatNativeWindowBorder
if( !isSupported() )
return;
nativeProvider.setTitleBarHeight( window, titleBarHeight );
nativeProvider.setTitleBarHitTestSpots( window, hitTestSpots );
nativeProvider.setTitleBarAppIconBounds( window, appIconBounds );
nativeProvider.updateTitleBarInfo( window, titleBarHeight, hitTestSpots,
appIconBounds, minimizeButtonBounds, maximizeButtonBounds, closeButtonBounds );
}
static boolean showWindow( Window window, int cmd ) {
@@ -268,7 +268,7 @@ public class FlatNativeWindowBorder
try {
/*
Class<?> cls = Class.forName( "com.formdev.flatlaf.natives.jna.windows.FlatWindowsNativeWindowBorder" );
Method m = cls.getMethod( "getInstance" );
java.lang.reflect.Method m = cls.getMethod( "getInstance" );
setNativeProvider( (Provider) m.invoke( null ) );
*/
setNativeProvider( FlatWindowsNativeWindowBorder.getInstance() );
@@ -277,9 +277,7 @@ public class FlatNativeWindowBorder
}
}
/**
* @since 1.1.1
*/
/** @since 1.1.1 */
public static void setNativeProvider( Provider provider ) {
if( nativeProvider != null )
throw new IllegalStateException();
@@ -294,9 +292,9 @@ public class FlatNativeWindowBorder
{
boolean hasCustomDecoration( Window window );
void setHasCustomDecoration( Window window, boolean hasCustomDecoration );
void setTitleBarHeight( Window window, int titleBarHeight );
void setTitleBarHitTestSpots( Window window, List<Rectangle> hitTestSpots );
void setTitleBarAppIconBounds( Window window, Rectangle appIconBounds );
void updateTitleBarInfo( Window window, int titleBarHeight, List<Rectangle> hitTestSpots,
Rectangle appIconBounds, Rectangle minimizeButtonBounds, Rectangle maximizeButtonBounds,
Rectangle closeButtonBounds );
// commands for showWindow(); values must match Win32 API
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow

View File

@@ -19,21 +19,23 @@ package com.formdev.flatlaf.ui;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.Insets;
import java.beans.PropertyChangeListener;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.plaf.basic.BasicOptionPaneUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.util.UIScale;
/**
@@ -79,6 +81,7 @@ import com.formdev.flatlaf.util.UIScale;
*
* <!-- FlatOptionPaneUI -->
*
* @uiDefault OptionPane.showIcon boolean
* @uiDefault OptionPane.iconMessageGap int
* @uiDefault OptionPane.messagePadding int
* @uiDefault OptionPane.maxCharactersPerLine int
@@ -88,6 +91,7 @@ import com.formdev.flatlaf.util.UIScale;
public class FlatOptionPaneUI
extends BasicOptionPaneUI
{
/** @since 2 */ protected boolean showIcon;
protected int iconMessageGap;
protected int messagePadding;
protected int maxCharactersPerLine;
@@ -102,6 +106,7 @@ public class FlatOptionPaneUI
protected void installDefaults() {
super.installDefaults();
showIcon = UIManager.getBoolean( "OptionPane.showIcon" );
iconMessageGap = UIManager.getInt( "OptionPane.iconMessageGap" );
messagePadding = UIManager.getInt( "OptionPane.messagePadding" );
maxCharactersPerLine = UIManager.getInt( "OptionPane.maxCharactersPerLine" );
@@ -116,6 +121,24 @@ public class FlatOptionPaneUI
updateChildPanels( optionPane );
}
@Override
protected PropertyChangeListener createPropertyChangeListener() {
PropertyChangeListener superListener = super.createPropertyChangeListener();
return e -> {
superListener.propertyChange( e );
// hide window title bar icon
// (only if showIcon is false, otherwise the default behavior is used)
if( !showIcon && "ancestor".equals( e.getPropertyName() ) && e.getNewValue() != null ) {
JRootPane rootPane = SwingUtilities.getRootPane( optionPane );
if( rootPane != null &&
rootPane.getContentPane().getComponentCount() > 0 &&
rootPane.getContentPane().getComponent( 0 ) == optionPane )
rootPane.putClientProperty( FlatClientProperties.TITLE_BAR_SHOW_ICON, false );
}
};
}
@Override
public Dimension getMinimumOptionPaneSize() {
return UIScale.scale( super.getMinimumOptionPaneSize() );
@@ -205,7 +228,7 @@ public class FlatOptionPaneUI
// use non-UIResource borders to avoid that they are replaced when switching LaF
Border border = panel.getBorder();
if( border instanceof UIResource )
panel.setBorder( new NonUIResourceBorder( border ) );
panel.setBorder( FlatUIUtils.nonUIResource( border ) );
}
if( child instanceof Container )
@@ -231,31 +254,4 @@ public class FlatOptionPaneUI
protected boolean getSizeButtonsToSameWidth() {
return sameSizeButtons;
}
//---- class NonUIResourceBorder ------------------------------------------
private static class NonUIResourceBorder
implements Border
{
private final Border delegate;
NonUIResourceBorder( Border delegate ) {
this.delegate = delegate;
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
delegate.paintBorder( c, g, x, y, width, height );
}
@Override
public Insets getBorderInsets( Component c ) {
return delegate.getBorderInsets( c );
}
@Override
public boolean isBorderOpaque() {
return delegate.isBorderOpaque();
}
}
}

View File

@@ -16,9 +16,18 @@
package com.formdev.flatlaf.ui;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.beans.PropertyChangeListener;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicPanelUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JPanel}.
@@ -27,15 +36,118 @@ import javax.swing.plaf.basic.BasicPanelUI;
*
* @uiDefault Panel.font Font unused
* @uiDefault Panel.background Color only used if opaque
* @uiDefault Panel.foreground Color
* @uiDefault Panel.foreground Color unused
* @uiDefault Panel.border Border
*
* @author Karl Tauber
*/
public class FlatPanelUI
extends BasicPanelUI
implements StyleableUI
{
// only used via styling (not in UI defaults)
/** @since 2 */ @Styleable protected int arc = -1;
private final boolean shared;
private PropertyChangeListener propertyChangeListener;
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
return FlatUIUtils.createSharedUI( FlatPanelUI.class, FlatPanelUI::new );
return FlatUIUtils.canUseSharedUI( c )
? FlatUIUtils.createSharedUI( FlatPanelUI.class, () -> new FlatPanelUI( true ) )
: new FlatPanelUI( false );
}
/** @since 2 */
protected FlatPanelUI( boolean shared ) {
this.shared = shared;
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
propertyChangeListener = FlatStylingSupport.createPropertyChangeListener(
c, () -> stylePropertyChange( (JPanel) c ), null );
c.addPropertyChangeListener( propertyChangeListener );
installStyle( (JPanel) c );
}
@Override
public void uninstallUI( JComponent c ) {
super.uninstallUI( c );
c.removePropertyChangeListener( propertyChangeListener );
propertyChangeListener = null;
oldStyleValues = null;
}
private void stylePropertyChange( JPanel c ) {
if( shared && FlatStylingSupport.hasStyleProperty( c ) ) {
// unshare component UI if necessary
// updateUI() invokes installStyle() from installUI()
c.updateUI();
} else
installStyle( c );
c.revalidate();
c.repaint();
}
/** @since 2 */
protected void installStyle( JPanel c ) {
try {
applyStyle( c, FlatStylingSupport.getResolvedStyle( c, "Panel" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( JPanel c, Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style,
(key, value) -> applyStyleProperty( c, key, value ) );
}
/** @since 2 */
protected Object applyStyleProperty( JPanel c, String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, c, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
@Override
public void update( Graphics g, JComponent c ) {
// fill background
if( c.isOpaque() ) {
int width = c.getWidth();
int height = c.getHeight();
int arc = (this.arc >= 0)
? this.arc
: ((c.getBorder() instanceof FlatLineBorder)
? ((FlatLineBorder)c.getBorder()).getArc()
: 0);
// fill background with parent color to avoid garbage in rounded corners
if( arc > 0 )
FlatUIUtils.paintParentBackground( g, c );
g.setColor( c.getBackground() );
if( arc > 0 ) {
// fill rounded rectangle if having rounded corners
Object[] oldRenderingHints = FlatUIUtils.setRenderingHints( g );
FlatUIUtils.paintComponentBackground( (Graphics2D) g, 0, 0, width, height,
0, UIScale.scale( arc ) );
FlatUIUtils.resetRenderingHints( g, oldRenderingHints );
} else
g.fillRect( 0, 0, width, height );
}
paint( g, c );
}
}

View File

@@ -16,17 +16,19 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Shape;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Map;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JToggleButton;
import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
@@ -36,6 +38,9 @@ import javax.swing.text.Element;
import javax.swing.text.JTextComponent;
import javax.swing.text.PasswordView;
import javax.swing.text.View;
import com.formdev.flatlaf.icons.FlatCapsLockIcon;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.util.UIScale;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JPasswordField}.
@@ -61,6 +66,7 @@ import javax.swing.text.View;
* @uiDefault Component.isIntelliJTheme boolean
* @uiDefault PasswordField.placeholderForeground Color
* @uiDefault PasswordField.focusedBackground Color optional
* @uiDefault PasswordField.iconTextGap int optional, default is 4
* @uiDefault TextComponent.selectAllOnFocusPolicy String never, once (default) or always
* @uiDefault TextComponent.selectAllOnMouseClick boolean
*
@@ -68,17 +74,25 @@ import javax.swing.text.View;
*
* @uiDefault PasswordField.echoChar character
* @uiDefault PasswordField.showCapsLock boolean
* @uiDefault PasswordField.showRevealButton boolean
* @uiDefault PasswordField.capsLockIcon Icon
* @uiDefault PasswordField.revealIcon Icon
*
* @author Karl Tauber
*/
public class FlatPasswordFieldUI
extends FlatTextFieldUI
{
protected boolean showCapsLock;
private Character echoChar;
@Styleable protected boolean showCapsLock;
/** @since 2 */ @Styleable protected boolean showRevealButton;
protected Icon capsLockIcon;
/** @since 2 */ protected Icon revealIcon;
private KeyListener capsLockListener;
private boolean capsLockIconShared = true;
private JToggleButton revealButton;
public static ComponentUI createUI( JComponent c ) {
return new FlatPasswordFieldUI();
@@ -89,17 +103,34 @@ public class FlatPasswordFieldUI
return "PasswordField";
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installRevealButton();
}
@Override
public void uninstallUI( JComponent c ) {
uninstallRevealButton();
super.uninstallUI( c );
}
@Override
protected void installDefaults() {
super.installDefaults();
String prefix = getPropertyPrefix();
Character echoChar = (Character) UIManager.get( prefix + ".echoChar" );
echoChar = (Character) UIManager.get( prefix + ".echoChar" );
if( echoChar != null )
LookAndFeel.installProperty( getComponent(), "echoChar", echoChar );
showCapsLock = UIManager.getBoolean( "PasswordField.showCapsLock" );
showRevealButton = UIManager.getBoolean( "PasswordField.showRevealButton" );
capsLockIcon = UIManager.getIcon( "PasswordField.capsLockIcon" );
revealIcon = UIManager.getIcon( "PasswordField.revealIcon" );
capsLockIconShared = true;
}
@Override
@@ -107,6 +138,7 @@ public class FlatPasswordFieldUI
super.uninstallDefaults();
capsLockIcon = null;
revealIcon = null;
}
@Override
@@ -155,37 +187,84 @@ public class FlatPasswordFieldUI
}
}
/** @since 2 */
@Override
String getStyleType() {
return "PasswordField";
}
@Override
protected void applyStyle( Object style ) {
boolean oldShowRevealButton = showRevealButton;
super.applyStyle( style );
if( showRevealButton != oldShowRevealButton ) {
uninstallRevealButton();
installRevealButton();
}
}
/** @since 2 */
@Override
protected Object applyStyleProperty( String key, Object value ) {
if( key.equals( "capsLockIconColor" ) && capsLockIcon instanceof FlatCapsLockIcon ) {
if( capsLockIconShared ) {
capsLockIcon = FlatStylingSupport.cloneIcon( capsLockIcon );
capsLockIconShared = false;
}
return ((FlatCapsLockIcon)capsLockIcon).applyStyleProperty( key, value );
}
return super.applyStyleProperty( key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
Map<String, Class<?>> infos = super.getStyleableInfos( c );
infos.put( "capsLockIconColor", Color.class );
return infos;
}
@Override
public View create( Element elem ) {
return new PasswordView( elem );
}
/** @since 2 */
@Override
protected void paintSafely( Graphics g ) {
// safe and restore clipping area because super.paintSafely() modifies it
// and the caps lock icon would be truncated
Shape oldClip = g.getClip();
super.paintSafely( g );
g.setClip( oldClip );
protected void paintIcons( Graphics g, Rectangle r ) {
super.paintIcons( g, r );
paintCapsLock( g );
if( isCapsLockVisible() )
paintCapsLock( g, r );
}
protected void paintCapsLock( Graphics g ) {
if( !isCapsLockVisible() )
return;
/** @since 2 */
protected void paintCapsLock( Graphics g, Rectangle r ) {
JTextComponent c = getComponent();
int y = (c.getHeight() - capsLockIcon.getIconHeight()) / 2;
int x = c.getComponentOrientation().isLeftToRight()
? c.getWidth() - capsLockIcon.getIconWidth() - y
: y;
? r.x + r.width - capsLockIcon.getIconWidth()
: r.x;
int y = r.y + Math.round( (r.height - capsLockIcon.getIconHeight()) / 2f );
capsLockIcon.paintIcon( c, g, x, y );
}
/**
* @since 1.4
*/
/** @since 2 */
@Override
protected boolean hasTrailingIcon() {
return super.hasTrailingIcon() || isCapsLockVisible();
}
/** @since 2 */
@Override
protected int getTrailingIconWidth() {
return super.getTrailingIconWidth()
+ (isCapsLockVisible() ? capsLockIcon.getIconWidth() + UIScale.scale( iconTextGap ) : 0);
}
/** @since 1.4 */
protected boolean isCapsLockVisible() {
if( !showCapsLock )
return false;
@@ -195,17 +274,38 @@ public class FlatPasswordFieldUI
Toolkit.getDefaultToolkit().getLockingKeyState( KeyEvent.VK_CAPS_LOCK );
}
/**
* @since 1.4
*/
@Override
protected Insets getPadding() {
Insets padding = super.getPadding();
if( !isCapsLockVisible() )
return padding;
/** @since 2 */
protected void installRevealButton() {
JTextComponent c = getComponent();
if( showRevealButton ) {
revealButton = createRevealButton();
installLayout();
c.add( revealButton );
}
}
boolean ltr = getComponent().getComponentOrientation().isLeftToRight();
int iconWidth = capsLockIcon.getIconWidth();
return FlatUIUtils.addInsets( padding, new Insets( 0, ltr ? 0 : iconWidth, 0, ltr ? iconWidth : 0 ) );
/** @since 2 */
protected JToggleButton createRevealButton() {
JToggleButton button = new JToggleButton( revealIcon );
prepareLeadingOrTrailingComponent( button );
button.addActionListener( e -> {
LookAndFeel.installProperty( getComponent(), "echoChar", button.isSelected()
? '\0'
: (echoChar != null ? echoChar : '*'));
} );
return button;
}
/** @since 2 */
protected void uninstallRevealButton() {
if( revealButton != null ) {
getComponent().remove( revealButton );
revealButton = null;
}
}
@Override
protected JComponent[] getTrailingComponents() {
return new JComponent[] { trailingComponent, revealButton, clearButton };
}
}

View File

@@ -121,6 +121,10 @@ public class FlatPopupFactory
popupWindow.getGraphicsConfiguration() == owner.getGraphicsConfiguration() )
return popup;
// avoid endless loop (should newer happen; PopupFactory cache size is 5)
if( ++count > 10 )
return popup;
// remove contents component from popup window
if( popupWindow instanceof JWindow )
((JWindow)popupWindow).getContentPane().removeAll();
@@ -128,10 +132,6 @@ public class FlatPopupFactory
// dispose unused popup
// (do not invoke popup.hide() because this would cache the popup window)
popupWindow.dispose();
// avoid endless loop (should newer happen; PopupFactory cache size is 5)
if( ++count > 10 )
return popup;
}
}

View File

@@ -16,11 +16,15 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
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.UnknownStyleException;
import com.formdev.flatlaf.util.UIScale;
/**
@@ -33,12 +37,40 @@ import com.formdev.flatlaf.util.UIScale;
*/
public class FlatPopupMenuBorder
extends FlatLineBorder
implements StyleableBorder
{
private Color borderColor;
public FlatPopupMenuBorder() {
super( UIManager.getInsets( "PopupMenu.borderInsets" ),
UIManager.getColor( "PopupMenu.borderColor" ) );
}
/** @since 2 */
@Override
public Object applyStyleProperty( String key, Object value ) {
Object oldValue;
switch( key ) {
case "borderInsets": return applyStyleProperty( (Insets) value );
case "borderColor": oldValue = getLineColor(); borderColor = (Color) value; return oldValue;
}
throw new UnknownStyleException( key );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos() {
Map<String, Class<?>> infos = new FlatStylingSupport.StyleableInfosMap<>();
infos.put( "borderInsets", Insets.class );
infos.put( "borderColor", Color.class );
return infos;
}
@Override
public Color getLineColor() {
return (borderColor != null) ? borderColor : super.getLineColor();
}
@Override
public Insets getBorderInsets( Component c, Insets insets ) {
if( c instanceof Container &&

View File

@@ -39,11 +39,24 @@ public class FlatPopupMenuSeparatorUI
extends FlatSeparatorUI
{
public static ComponentUI createUI( JComponent c ) {
return FlatUIUtils.createSharedUI( FlatPopupMenuSeparatorUI.class, FlatPopupMenuSeparatorUI::new );
return FlatUIUtils.canUseSharedUI( c )
? FlatUIUtils.createSharedUI( FlatPopupMenuSeparatorUI.class, () -> new FlatPopupMenuSeparatorUI( true ) )
: new FlatPopupMenuSeparatorUI( false );
}
/** @since 2 */
protected FlatPopupMenuSeparatorUI( boolean shared ) {
super( shared );
}
@Override
protected String getPropertyPrefix() {
return "PopupMenuSeparator";
}
/** @since 2 */
@Override
String getStyleType() {
return "PopupMenuSeparator";
}
}

View File

@@ -16,9 +16,20 @@
package com.formdev.flatlaf.ui;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.LayoutManager;
import java.beans.PropertyChangeListener;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicPopupMenuUI;
import javax.swing.plaf.basic.DefaultMenuLayout;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.LoggingFacade;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JPopupMenu}.
@@ -34,8 +45,97 @@ import javax.swing.plaf.basic.BasicPopupMenuUI;
*/
public class FlatPopupMenuUI
extends BasicPopupMenuUI
implements StyleableUI
{
private PropertyChangeListener propertyChangeListener;
private Map<String, Object> oldStyleValues;
private AtomicBoolean borderShared;
public static ComponentUI createUI( JComponent c ) {
return new FlatPopupMenuUI();
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installStyle();
}
@Override
public void uninstallUI( JComponent c ) {
super.uninstallUI( c );
oldStyleValues = null;
borderShared = null;
}
@Override
public void installDefaults() {
super.installDefaults();
LayoutManager layout = popupMenu.getLayout();
if( layout == null || layout instanceof UIResource )
popupMenu.setLayout( new FlatMenuLayout( popupMenu, BoxLayout.Y_AXIS ) );
}
@Override
protected void installListeners() {
super.installListeners();
propertyChangeListener = FlatStylingSupport.createPropertyChangeListener( popupMenu, this::installStyle, null );
popupMenu.addPropertyChangeListener( propertyChangeListener );
}
@Override
protected void uninstallListeners() {
super.uninstallListeners();
popupMenu.removePropertyChangeListener( propertyChangeListener );
propertyChangeListener = null;
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( popupMenu, "PopupMenu" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
if( borderShared == null )
borderShared = new AtomicBoolean( true );
return FlatStylingSupport.applyToAnnotatedObjectOrBorder( this, key, value, popupMenu, borderShared );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatStylingSupport.getAnnotatedStyleableInfos( this, popupMenu.getBorder() );
}
//---- class FlatMenuLayout -----------------------------------------------
protected static class FlatMenuLayout
extends DefaultMenuLayout
{
public FlatMenuLayout( Container target, int axis ) {
super( target, axis );
}
@Override
public Dimension preferredLayoutSize( Container target ) {
FlatMenuItemRenderer.clearClientProperties( target );
return super.preferredLayoutSize( target );
}
}
}

View File

@@ -25,13 +25,17 @@ import java.awt.Insets;
import java.awt.geom.Area;
import java.awt.geom.RoundRectangle2D;
import java.beans.PropertyChangeListener;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.JProgressBar;
import javax.swing.LookAndFeel;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicProgressBarUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale;
/**
@@ -58,17 +62,30 @@ import com.formdev.flatlaf.util.UIScale;
*/
public class FlatProgressBarUI
extends BasicProgressBarUI
implements StyleableUI
{
protected int arc;
protected Dimension horizontalSize;
protected Dimension verticalSize;
@Styleable protected int arc;
@Styleable protected Dimension horizontalSize;
@Styleable protected Dimension verticalSize;
// only used via styling (not in UI defaults, but has likewise client properties)
/** @since 2 */ @Styleable protected boolean largeHeight;
/** @since 2 */ @Styleable protected boolean square;
private PropertyChangeListener propertyChangeListener;
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
return new FlatProgressBarUI();
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installStyle();
}
@Override
protected void installDefaults() {
super.installDefaults();
@@ -80,6 +97,13 @@ public class FlatProgressBarUI
verticalSize = UIManager.getDimension( "ProgressBar.verticalSize" );
}
@Override
protected void uninstallDefaults() {
super.uninstallDefaults();
oldStyleValues = null;
}
@Override
protected void installListeners() {
super.installListeners();
@@ -91,6 +115,13 @@ public class FlatProgressBarUI
progressBar.revalidate();
progressBar.repaint();
break;
case STYLE:
case STYLE_CLASS:
installStyle();
progressBar.revalidate();
progressBar.repaint();
break;
}
};
progressBar.addPropertyChangeListener( propertyChangeListener );
@@ -104,11 +135,36 @@ public class FlatProgressBarUI
propertyChangeListener = null;
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( progressBar, "ProgressBar" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, progressBar, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
@Override
public Dimension getPreferredSize( JComponent c ) {
Dimension size = super.getPreferredSize( c );
if( progressBar.isStringPainted() || clientPropertyBoolean( c, PROGRESS_BAR_LARGE_HEIGHT, false ) ) {
if( progressBar.isStringPainted() || clientPropertyBoolean( c, PROGRESS_BAR_LARGE_HEIGHT, largeHeight ) ) {
// recalculate progress height/width to make it smaller
Insets insets = progressBar.getInsets();
FontMetrics fm = progressBar.getFontMetrics( progressBar.getFont() );
@@ -151,7 +207,7 @@ public class FlatProgressBarUI
return;
boolean horizontal = (progressBar.getOrientation() == JProgressBar.HORIZONTAL);
int arc = clientPropertyBoolean( c, PROGRESS_BAR_SQUARE, false )
int arc = clientPropertyBoolean( c, PROGRESS_BAR_SQUARE, square )
? 0
: Math.min( UIScale.scale( this.arc ), horizontal ? height : width );

View File

@@ -16,13 +16,19 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.beans.PropertyChangeListener;
import java.util.Map;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.LookAndFeel;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicRadioButtonMenuItemUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.LoggingFacade;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JRadioButtonMenuItem}.
@@ -54,13 +60,22 @@ import javax.swing.plaf.basic.BasicRadioButtonMenuItemUI;
*/
public class FlatRadioButtonMenuItemUI
extends BasicRadioButtonMenuItemUI
implements StyleableUI
{
private FlatMenuItemRenderer renderer;
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
return new FlatRadioButtonMenuItemUI();
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installStyle();
}
@Override
protected void installDefaults() {
super.installDefaults();
@@ -74,13 +89,61 @@ public class FlatRadioButtonMenuItemUI
protected void uninstallDefaults() {
super.uninstallDefaults();
FlatMenuItemRenderer.clearClientProperties( menuItem.getParent() );
renderer = null;
oldStyleValues = null;
}
protected FlatMenuItemRenderer createRenderer() {
return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter );
}
@Override
protected PropertyChangeListener createPropertyChangeListener( JComponent c ) {
return FlatStylingSupport.createPropertyChangeListener( c, this::installStyle, super.createPropertyChangeListener( c ) );
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( menuItem, "RadioButtonMenuItem" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
try {
return renderer.applyStyleProperty( key, value );
} catch ( UnknownStyleException ex ) {
// ignore
}
Object oldValue;
switch( key ) {
// BasicMenuItemUI
case "selectionBackground": oldValue = selectionBackground; selectionBackground = (Color) value; return oldValue;
case "selectionForeground": oldValue = selectionForeground; selectionForeground = (Color) value; return oldValue;
case "disabledForeground": oldValue = disabledForeground; disabledForeground = (Color) value; return oldValue;
case "acceleratorForeground": oldValue = acceleratorForeground; acceleratorForeground = (Color) value; return oldValue;
case "acceleratorSelectionForeground": oldValue = acceleratorSelectionForeground; acceleratorSelectionForeground = (Color) value; return oldValue;
}
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, menuItem, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatMenuItemUI.getStyleableInfos( renderer );
}
@Override
protected Dimension getPreferredMenuItemSize( JComponent c, Icon checkIcon, Icon arrowIcon, int defaultTextIconGap ) {
return renderer.getPreferredMenuItemSize();

View File

@@ -23,15 +23,24 @@ import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
import java.beans.PropertyChangeEvent;
import java.util.Map;
import java.util.Objects;
import javax.swing.AbstractButton;
import javax.swing.CellRendererPane;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.LookAndFeel;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicButtonListener;
import javax.swing.plaf.basic.BasicRadioButtonUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.icons.FlatCheckBoxIcon;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale;
/**
@@ -56,16 +65,34 @@ import com.formdev.flatlaf.util.UIScale;
*/
public class FlatRadioButtonUI
extends BasicRadioButtonUI
implements StyleableUI
{
protected int iconTextGap;
protected Color disabledText;
@Styleable protected Color disabledText;
private Color defaultBackground;
private final boolean shared;
private boolean iconShared = true;
private boolean defaults_initialized = false;
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
return FlatUIUtils.createSharedUI( FlatRadioButtonUI.class, FlatRadioButtonUI::new );
return FlatUIUtils.canUseSharedUI( c )
? FlatUIUtils.createSharedUI( FlatRadioButtonUI.class, () -> new FlatRadioButtonUI( true ) )
: new FlatRadioButtonUI( false );
}
/** @since 2 */
protected FlatRadioButtonUI( boolean shared ) {
this.shared = shared;
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installStyle( (AbstractButton) c );
}
@Override
@@ -80,6 +107,7 @@ public class FlatRadioButtonUI
defaultBackground = UIManager.getColor( prefix + "background" );
iconShared = true;
defaults_initialized = true;
}
@@ -93,10 +121,84 @@ public class FlatRadioButtonUI
protected void uninstallDefaults( AbstractButton b ) {
super.uninstallDefaults( b );
oldStyleValues = null;
MigLayoutVisualPadding.uninstall( b );
defaults_initialized = false;
}
@Override
protected BasicButtonListener createButtonListener( AbstractButton b ) {
return new FlatRadioButtonListener( b );
}
/** @since 2 */
protected void propertyChange( AbstractButton b, PropertyChangeEvent e ) {
switch( e.getPropertyName() ) {
case FlatClientProperties.STYLE:
case FlatClientProperties.STYLE_CLASS:
if( shared && FlatStylingSupport.hasStyleProperty( b ) ) {
// unshare component UI if necessary
// updateUI() invokes installStyle() from installUI()
b.updateUI();
} else
installStyle( b );
b.revalidate();
b.repaint();
break;
}
}
/** @since 2 */
protected void installStyle( AbstractButton b ) {
try {
applyStyle( b, FlatStylingSupport.getResolvedStyle( b, getStyleType() ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
String getStyleType() {
return "RadioButton";
}
/** @since 2 */
protected void applyStyle( AbstractButton b, Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style,
(key, value) -> applyStyleProperty( b, key, value ) );
}
/** @since 2 */
protected Object applyStyleProperty( AbstractButton b, String key, Object value ) {
// style icon
if( key.startsWith( "icon." ) ) {
if( !(icon instanceof FlatCheckBoxIcon) )
return new UnknownStyleException( key );
if( iconShared ) {
icon = FlatStylingSupport.cloneIcon( icon );
iconShared = false;
}
key = key.substring( "icon.".length() );
return ((FlatCheckBoxIcon)icon).applyStyleProperty( key, value );
}
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, b, key, value );
}
/** @since 2 */
@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() );
}
return infos;
}
private static Insets tempInsets = new Insets( 0, 0, 0, 0 );
@Override
@@ -178,8 +280,32 @@ public class FlatRadioButtonUI
private int getIconFocusWidth( JComponent c ) {
AbstractButton b = (AbstractButton) c;
return (b.getIcon() == null && getDefaultIcon() instanceof FlatCheckBoxIcon)
? UIScale.scale( ((FlatCheckBoxIcon)getDefaultIcon()).focusWidth )
Icon icon = b.getIcon();
if( icon == null )
icon = getDefaultIcon();
return (icon instanceof FlatCheckBoxIcon)
? Math.round( UIScale.scale( ((FlatCheckBoxIcon)icon).getFocusWidth() ) )
: 0;
}
//---- class FlatRadioButtonListener --------------------------------------
/** @since 2 */
protected class FlatRadioButtonListener
extends BasicButtonListener
{
private final AbstractButton b;
protected FlatRadioButtonListener( AbstractButton b ) {
super( b );
this.b = b;
}
@Override
public void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e );
FlatRadioButtonUI.this.propertyChange( b, e );
}
}
}

View File

@@ -66,6 +66,9 @@ import com.formdev.flatlaf.util.UIScale;
*
* <!-- FlatWindowResizer -->
*
* @uiDefault RootPane.font Font unused
* @uiDefault RootPane.background Color
* @uiDefault RootPane.foreground Color unused
* @uiDefault RootPane.borderDragThickness int
* @uiDefault RootPane.cornerDragWidth int
* @uiDefault RootPane.honorFrameMinimumSizeOnResize boolean
@@ -126,8 +129,23 @@ public class FlatRootPaneUI
protected void installDefaults( JRootPane c ) {
super.installDefaults( c );
// Give the root pane useful background, foreground and font.
// Background is used for title bar and menu bar if native window decorations
// and unified background are enabled.
// Foreground and font are usually not used, but set for completeness.
// Not using LookAndFeel.installColorsAndFont() here because it will not work
// because the properties are null by default but inherit non-null values from parent.
if( !c.isBackgroundSet() || c.getBackground() instanceof UIResource )
c.setBackground( UIManager.getColor( "RootPane.background" ) );
if( !c.isForegroundSet() || c.getForeground() instanceof UIResource )
c.setForeground( UIManager.getColor( "RootPane.foreground" ) );
if( !c.isFontSet() || c.getFont() instanceof UIResource )
c.setFont( UIManager.getFont( "RootPane.font" ) );
// Update background color of JFrame or JDialog parent to avoid bad border
// on HiDPI screens when switching from light to dark Laf.
// Window background color is also used in native window decorations
// to fill background when window is initially shown or when resizing window.
// The background of JFrame is initialized in JFrame.frameInit() and
// the background of JDialog in JDialog.dialogInit(),
// but it was not updated when switching Laf.
@@ -198,24 +216,18 @@ public class FlatRootPaneUI
}
}
/**
* @since 1.1.2
*/
/** @since 1.1.2 */
protected void installNativeWindowBorder() {
nativeWindowBorderData = FlatNativeWindowBorder.install( rootPane );
}
/**
* @since 1.1.2
*/
/** @since 1.1.2 */
protected void uninstallNativeWindowBorder() {
FlatNativeWindowBorder.uninstall( rootPane, nativeWindowBorderData );
nativeWindowBorderData = null;
}
/**
* @since 1.1.2
*/
/** @since 1.1.2 */
public static void updateNativeWindowBorder( JRootPane rootPane ) {
RootPaneUI rui = rootPane.getUI();
if( !(rui instanceof FlatRootPaneUI) )
@@ -319,6 +331,11 @@ public class FlatRootPaneUI
}
break;
case FlatClientProperties.TITLE_BAR_SHOW_ICON:
if( titlePane != null )
titlePane.updateIcon();
break;
case FlatClientProperties.TITLE_BAR_BACKGROUND:
case FlatClientProperties.TITLE_BAR_FOREGROUND:
if( titlePane != null )

View File

@@ -18,6 +18,7 @@ package com.formdev.flatlaf.ui;
import java.awt.Component;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
/**
* Border for various components (e.g. {@link javax.swing.JComboBox}).
@@ -29,7 +30,10 @@ import javax.swing.UIManager;
public class FlatRoundBorder
extends FlatBorder
{
protected final int arc = UIManager.getInt( "Component.arc" );
@Styleable protected int arc = UIManager.getInt( "Component.arc" );
// only used via styling (not in UI defaults, but has likewise client properties)
/** @since 2 */ @Styleable protected Boolean roundRect;
@Override
protected int getArc( Component c ) {
@@ -37,6 +41,8 @@ public class FlatRoundBorder
return 0;
Boolean roundRect = FlatUIUtils.isRoundRect( c );
if( roundRect == null )
roundRect = this.roundRect;
return roundRect != null ? (roundRect ? Short.MAX_VALUE : 0) : arc;
}
}

View File

@@ -24,6 +24,7 @@ import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeListener;
import java.util.Map;
import java.util.Objects;
import javax.swing.InputMap;
import javax.swing.JButton;
@@ -35,6 +36,9 @@ import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicScrollBarUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale;
/**
@@ -43,7 +47,7 @@ import com.formdev.flatlaf.util.UIScale;
* <!-- BasicScrollBarUI -->
*
* @uiDefault ScrollBar.background Color
* @uiDefault ScrollBar.foreground Color
* @uiDefault ScrollBar.foreground Color unused
* @uiDefault ScrollBar.track Color
* @uiDefault ScrollBar.thumb Color
* @uiDefault ScrollBar.width int
@@ -74,33 +78,46 @@ import com.formdev.flatlaf.util.UIScale;
*/
public class FlatScrollBarUI
extends BasicScrollBarUI
implements StyleableUI
{
protected Insets trackInsets;
protected Insets thumbInsets;
protected int trackArc;
protected int thumbArc;
protected Color hoverTrackColor;
protected Color hoverThumbColor;
protected boolean hoverThumbWithTrack;
protected Color pressedTrackColor;
protected Color pressedThumbColor;
protected boolean pressedThumbWithTrack;
// overrides BasicScrollBarUI.supportsAbsolutePositioning (which is private)
@Styleable protected boolean allowsAbsolutePositioning;
protected boolean showButtons;
protected String arrowType;
protected Color buttonArrowColor;
protected Color buttonDisabledArrowColor;
protected Color hoverButtonBackground;
protected Color pressedButtonBackground;
@Styleable protected Insets trackInsets;
@Styleable protected Insets thumbInsets;
@Styleable protected int trackArc;
@Styleable protected int thumbArc;
@Styleable protected Color hoverTrackColor;
@Styleable protected Color hoverThumbColor;
@Styleable protected boolean hoverThumbWithTrack;
@Styleable protected Color pressedTrackColor;
@Styleable protected Color pressedThumbColor;
@Styleable protected boolean pressedThumbWithTrack;
@Styleable protected boolean showButtons;
@Styleable protected String arrowType;
@Styleable protected Color buttonArrowColor;
@Styleable protected Color buttonDisabledArrowColor;
@Styleable protected Color hoverButtonBackground;
@Styleable protected Color pressedButtonBackground;
private MouseAdapter hoverListener;
protected boolean hoverTrack;
protected boolean hoverThumb;
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
return new FlatScrollBarUI();
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installStyle();
}
@Override
protected void installListeners() {
super.installListeners();
@@ -123,6 +140,8 @@ public class FlatScrollBarUI
protected void installDefaults() {
super.installDefaults();
allowsAbsolutePositioning = super.getSupportsAbsolutePositioning();
trackInsets = UIManager.getInsets( "ScrollBar.trackInsets" );
thumbInsets = UIManager.getInsets( "ScrollBar.thumbInsets" );
trackArc = UIManager.getInt( "ScrollBar.trackArc" );
@@ -163,6 +182,8 @@ public class FlatScrollBarUI
buttonDisabledArrowColor = null;
hoverButtonBackground = null;
pressedButtonBackground = null;
oldStyleValues = null;
}
@Override
@@ -177,6 +198,13 @@ public class FlatScrollBarUI
scrollbar.repaint();
break;
case FlatClientProperties.STYLE:
case FlatClientProperties.STYLE_CLASS:
installStyle();
scrollbar.revalidate();
scrollbar.repaint();
break;
case "componentOrientation":
// this is missing in BasicScrollBarUI.Handler.propertyChange()
InputMap inputMap = (InputMap) UIManager.get( "ScrollBar.ancestorInputMap" );
@@ -193,6 +221,53 @@ public class FlatScrollBarUI
};
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( scrollbar, "ScrollBar" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
if( incrButton instanceof FlatScrollBarButton )
((FlatScrollBarButton)incrButton).updateStyle();
if( decrButton instanceof FlatScrollBarButton )
((FlatScrollBarButton)decrButton).updateStyle();
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
Object oldValue;
switch( key ) {
// BasicScrollBarUI
case "track": oldValue = trackColor; trackColor = (Color) value; return oldValue;
case "thumb": oldValue = thumbColor; thumbColor = (Color) value; return oldValue;
case "width": oldValue = scrollBarWidth; scrollBarWidth = (int) value; return oldValue;
case "minimumThumbSize": oldValue = minimumThumbSize; minimumThumbSize = (Dimension) value; return oldValue;
case "maximumThumbSize": oldValue = maximumThumbSize; maximumThumbSize = (Dimension) value; return oldValue;
}
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, scrollbar, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
Map<String, Class<?>> infos = new FlatStylingSupport.StyleableInfosMap<>();
infos.put( "track", Color.class );
infos.put( "thumb", Color.class );
infos.put( "width", int.class );
infos.put( "minimumThumbSize", Dimension.class );
infos.put( "maximumThumbSize", Dimension.class );
FlatStylingSupport.collectAnnotatedStyleableInfos( this, infos );
return infos;
}
@Override
public Dimension getPreferredSize( JComponent c ) {
return UIScale.scale( super.getPreferredSize( c ) );
@@ -209,9 +284,17 @@ public class FlatScrollBarUI
}
protected boolean isShowButtons() {
// check client property on scroll bar
Object showButtons = scrollbar.getClientProperty( FlatClientProperties.SCROLL_BAR_SHOW_BUTTONS );
if( showButtons == null && scrollbar.getParent() instanceof JScrollPane )
showButtons = ((JScrollPane)scrollbar.getParent()).getClientProperty( FlatClientProperties.SCROLL_BAR_SHOW_BUTTONS );
if( showButtons == null && scrollbar.getParent() instanceof JScrollPane ) {
JScrollPane scrollPane = (JScrollPane) scrollbar.getParent();
// check client property on scroll pane
showButtons = scrollPane.getClientProperty( FlatClientProperties.SCROLL_BAR_SHOW_BUTTONS );
if( showButtons == null && scrollPane.getUI() instanceof FlatScrollPaneUI ) {
// check styling property on scroll pane
showButtons = ((FlatScrollPaneUI)scrollPane.getUI()).showButtons;
}
}
return (showButtons != null) ? Objects.equals( showButtons, true ) : this.showButtons;
}
@@ -295,6 +378,11 @@ public class FlatScrollBarUI
return UIScale.scale( FlatUIUtils.addInsets( super.getMaximumThumbSize(), thumbInsets ) );
}
@Override
public boolean getSupportsAbsolutePositioning() {
return allowsAbsolutePositioning;
}
//---- class ScrollBarHoverListener ---------------------------------------
// using static field to disabling hover for other scroll bars
@@ -368,6 +456,11 @@ public class FlatScrollBarUI
setRequestFocusEnabled( false );
}
protected void updateStyle() {
updateStyle( arrowType, buttonArrowColor, buttonDisabledArrowColor,
null, hoverButtonBackground, null, pressedButtonBackground );
}
@Override
protected Color deriveBackground( Color background ) {
return FlatUIUtils.deriveColor( background, scrollbar.getBackground() );

View File

@@ -29,6 +29,8 @@ import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
@@ -46,6 +48,9 @@ import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicScrollPaneUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.LoggingFacade;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JScrollPane}.
@@ -66,9 +71,16 @@ import com.formdev.flatlaf.FlatClientProperties;
*/
public class FlatScrollPaneUI
extends BasicScrollPaneUI
implements StyleableUI
{
// only used via styling (not in UI defaults, but has likewise client properties)
/** @since 2 */ @Styleable protected Boolean showButtons;
private Handler handler;
private Map<String, Object> oldStyleValues;
private AtomicBoolean borderShared;
public static ComponentUI createUI( JComponent c ) {
return new FlatScrollPaneUI();
}
@@ -80,6 +92,8 @@ public class FlatScrollPaneUI
int focusWidth = UIManager.getInt( "Component.focusWidth" );
LookAndFeel.installProperty( c, "opaque", focusWidth == 0 );
installStyle();
MigLayoutVisualPadding.install( scrollpane );
}
@@ -88,6 +102,9 @@ public class FlatScrollPaneUI
MigLayoutVisualPadding.uninstall( scrollpane );
super.uninstallUI( c );
oldStyleValues = null;
borderShared = null;
}
@Override
@@ -272,7 +289,14 @@ public class FlatScrollPaneUI
((JButton)corner).setBorder( BorderFactory.createEmptyBorder() );
((JButton)corner).setFocusable( false );
}
break;
break;
case FlatClientProperties.STYLE:
case FlatClientProperties.STYLE_CLASS:
installStyle();
scrollpane.revalidate();
scrollpane.repaint();
break;
}
};
}
@@ -283,6 +307,38 @@ public class FlatScrollPaneUI
return handler;
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( scrollpane, "ScrollPane" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
if( key.equals( "focusWidth" ) ) {
int focusWidth = (value instanceof Integer) ? (int) value : UIManager.getInt( "Component.focusWidth" );
LookAndFeel.installProperty( scrollpane, "opaque", focusWidth == 0 );
}
if( borderShared == null )
borderShared = new AtomicBoolean( true );
return FlatStylingSupport.applyToAnnotatedObjectOrBorder( this, key, value, scrollpane, borderShared );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatStylingSupport.getAnnotatedStyleableInfos( this, scrollpane.getBorder() );
}
@Override
protected void updateViewport( PropertyChangeEvent e ) {
super.updateViewport( e );
@@ -332,9 +388,7 @@ public class FlatScrollPaneUI
paint( g, c );
}
/**
* @since 1.3
*/
/** @since 1.3 */
public static boolean isPermanentFocusOwner( JScrollPane scrollPane ) {
JViewport viewport = scrollPane.getViewport();
Component view = (viewport != null) ? viewport.getView() : null;

View File

@@ -21,11 +21,16 @@ import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeListener;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.JSeparator;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicSeparatorUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.LoggingFacade;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JSeparator}.
@@ -45,15 +50,37 @@ import javax.swing.plaf.basic.BasicSeparatorUI;
*/
public class FlatSeparatorUI
extends BasicSeparatorUI
implements StyleableUI
{
protected int height;
protected int stripeWidth;
protected int stripeIndent;
@Styleable protected int height;
@Styleable protected int stripeWidth;
@Styleable protected int stripeIndent;
private final boolean shared;
private boolean defaults_initialized = false;
private PropertyChangeListener propertyChangeListener;
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
return FlatUIUtils.createSharedUI( FlatSeparatorUI.class, FlatSeparatorUI::new );
return FlatUIUtils.canUseSharedUI( c )
? FlatUIUtils.createSharedUI( FlatSeparatorUI.class, () -> new FlatSeparatorUI( true ) )
: new FlatSeparatorUI( false );
}
/** @since 2 */
protected FlatSeparatorUI( boolean shared ) {
this.shared = shared;
}
protected String getPropertyPrefix() {
return "Separator";
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installStyle( (JSeparator) c );
}
@Override
@@ -73,13 +100,70 @@ public class FlatSeparatorUI
@Override
protected void uninstallDefaults( JSeparator s ) {
super.uninstallDefaults( s );
defaults_initialized = false;
oldStyleValues = null;
}
protected String getPropertyPrefix() {
@Override
protected void installListeners( JSeparator s ) {
super.installListeners( s );
propertyChangeListener = FlatStylingSupport.createPropertyChangeListener(
s, () -> stylePropertyChange( s ), null );
s.addPropertyChangeListener( propertyChangeListener );
}
@Override
protected void uninstallListeners( JSeparator s ) {
super.uninstallListeners( s );
s.removePropertyChangeListener( propertyChangeListener );
propertyChangeListener = null;
}
private void stylePropertyChange( JSeparator s ) {
if( shared && FlatStylingSupport.hasStyleProperty( s ) ) {
// unshare component UI if necessary
// updateUI() invokes installStyle() from installUI()
s.updateUI();
} else
installStyle( s );
s.revalidate();
s.repaint();
}
/** @since 2 */
protected void installStyle( JSeparator s ) {
try {
applyStyle( s, FlatStylingSupport.getResolvedStyle( s, getStyleType() ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
String getStyleType() {
return "Separator";
}
/** @since 2 */
protected void applyStyle( JSeparator s, Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style,
(key, value) -> applyStyleProperty( s, key, value ) );
}
/** @since 2 */
protected Object applyStyleProperty( JSeparator s, String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, s, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
@Override
public void paint( Graphics g, JComponent c ) {
Graphics2D g2 = (Graphics2D) g.create();

View File

@@ -29,6 +29,8 @@ import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.awt.geom.RoundRectangle2D;
import java.beans.PropertyChangeListener;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.JSlider;
import javax.swing.LookAndFeel;
@@ -36,7 +38,11 @@ import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicSliderUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.Graphics2DProxy;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale;
/**
@@ -59,6 +65,8 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault Slider.trackWidth int
* @uiDefault Slider.thumbSize Dimension
* @uiDefault Slider.focusWidth int
* @uiDefault Slider.thumbBorderWidth int or float
*
* @uiDefault Slider.trackValueColor Color optional; defaults to Slider.thumbColor
* @uiDefault Slider.trackColor Color
* @uiDefault Slider.thumbColor Color
@@ -75,23 +83,26 @@ import com.formdev.flatlaf.util.UIScale;
*/
public class FlatSliderUI
extends BasicSliderUI
implements StyleableUI
{
protected int trackWidth;
protected Dimension thumbSize;
protected int focusWidth;
@Styleable protected int trackWidth;
@Styleable protected Dimension thumbSize;
@Styleable protected int focusWidth;
/** @since 2 */ @Styleable protected float thumbBorderWidth;
protected Color trackValueColor;
protected Color trackColor;
protected Color thumbColor;
protected Color thumbBorderColor;
@Styleable protected Color trackValueColor;
@Styleable protected Color trackColor;
@Styleable protected Color thumbColor;
@Styleable protected Color thumbBorderColor;
protected Color focusBaseColor;
protected Color focusedColor;
protected Color focusedThumbBorderColor;
protected Color hoverThumbColor;
protected Color pressedThumbColor;
protected Color disabledTrackColor;
protected Color disabledThumbColor;
protected Color disabledThumbBorderColor;
@Styleable protected Color focusedColor;
@Styleable protected Color focusedThumbBorderColor;
@Styleable protected Color hoverThumbColor;
@Styleable protected Color pressedThumbColor;
@Styleable protected Color disabledTrackColor;
@Styleable protected Color disabledThumbColor;
@Styleable protected Color disabledThumbBorderColor;
@Styleable protected Color tickColor;
private Color defaultBackground;
private Color defaultForeground;
@@ -100,6 +111,7 @@ public class FlatSliderUI
protected boolean thumbPressed;
private Object[] oldRenderingHints;
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
return new FlatSliderUI();
@@ -109,6 +121,13 @@ public class FlatSliderUI
super( null );
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installStyle();
}
@Override
protected void installDefaults( JSlider slider ) {
super.installDefaults( slider );
@@ -123,6 +142,7 @@ public class FlatSliderUI
thumbSize = new Dimension( thumbWidth, thumbWidth );
}
focusWidth = FlatUIUtils.getUIInt( "Slider.focusWidth", 4 );
thumbBorderWidth = FlatUIUtils.getUIFloat( "Slider.thumbBorderWidth", 1 );
trackValueColor = FlatUIUtils.getUIColor( "Slider.trackValueColor", "Slider.thumbColor" );
trackColor = UIManager.getColor( "Slider.trackColor" );
@@ -136,6 +156,7 @@ public class FlatSliderUI
disabledTrackColor = UIManager.getColor( "Slider.disabledTrackColor" );
disabledThumbColor = UIManager.getColor( "Slider.disabledThumbColor" );
disabledThumbBorderColor = FlatUIUtils.getUIColor( "Slider.disabledThumbBorderColor", "Component.disabledBorderColor" );
tickColor = FlatUIUtils.getUIColor( "Slider.tickColor", Color.BLACK ); // see BasicSliderUI.paintTicks()
defaultBackground = UIManager.getColor( "Slider.background" );
defaultForeground = UIManager.getColor( "Slider.foreground" );
@@ -157,9 +178,12 @@ public class FlatSliderUI
disabledTrackColor = null;
disabledThumbColor = null;
disabledThumbBorderColor = null;
tickColor = null;
defaultBackground = null;
defaultForeground = null;
oldStyleValues = null;
}
@Override
@@ -167,6 +191,37 @@ public class FlatSliderUI
return new FlatTrackListener();
}
@Override
protected PropertyChangeListener createPropertyChangeListener( JSlider slider ) {
return FlatStylingSupport.createPropertyChangeListener( slider, this::installStyle,
super.createPropertyChangeListener( slider ) );
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( slider, "Slider" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, slider, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
@Override
public int getBaseline( JComponent c, int width, int height ) {
if( c == null )
@@ -326,6 +381,19 @@ debug*/
((Graphics2D)g).fill( track );
}
@Override
public void paintTicks( Graphics g ) {
// because BasicSliderUI.paintTicks() always uses
// g.setColor( UIManager.getColor("Slider.tickColor") )
// we override this method and use our tickColor field to allow styling
super.paintTicks( new Graphics2DProxy( (Graphics2D) g ) {
@Override
public void setColor( Color c ) {
super.setColor( tickColor );
}
} );
}
@Override
public void paintThumb( Graphics g ) {
Color thumbColor = getThumbColor();
@@ -341,11 +409,11 @@ debug*/
Color focusedColor = FlatUIUtils.deriveColor( this.focusedColor,
(foreground != defaultForeground) ? foreground : focusBaseColor );
paintThumb( g, slider, thumbRect, isRoundThumb(), color, borderColor, focusedColor, focusWidth );
paintThumb( g, slider, thumbRect, isRoundThumb(), color, borderColor, focusedColor, thumbBorderWidth, focusWidth );
}
public static void paintThumb( Graphics g, JSlider slider, Rectangle thumbRect, boolean roundThumb,
Color thumbColor, Color thumbBorderColor, Color focusedColor, int focusWidth )
Color thumbColor, Color thumbBorderColor, Color focusedColor, float thumbBorderWidth, int focusWidth )
{
double systemScaleFactor = UIScale.getSystemScaleFactor( (Graphics2D) g );
if( systemScaleFactor != 1 && systemScaleFactor != 2 ) {
@@ -354,18 +422,20 @@ debug*/
(g2d, x2, y2, width2, height2, scaleFactor) -> {
paintThumbImpl( g, slider, x2, y2, width2, height2,
roundThumb, thumbColor, thumbBorderColor, focusedColor,
(float) (thumbBorderWidth * scaleFactor),
(float) (focusWidth * scaleFactor) );
} );
return;
}
paintThumbImpl( g, slider, thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height,
roundThumb, thumbColor, thumbBorderColor, focusedColor, focusWidth );
roundThumb, thumbColor, thumbBorderColor, focusedColor, thumbBorderWidth, focusWidth );
}
private static void paintThumbImpl( Graphics g, JSlider slider, int x, int y, int width, int height,
boolean roundThumb, Color thumbColor, Color thumbBorderColor, Color focusedColor, float focusWidth )
boolean roundThumb, Color thumbColor, Color thumbBorderColor, Color focusedColor,
float thumbBorderWidth, float focusWidth )
{
int fw = Math.round( UIScale.scale( focusWidth ) );
int tx = x + fw;
@@ -387,7 +457,7 @@ debug*/
((Graphics2D)g).fill( createRoundThumbShape( tx, ty, tw, th ) );
// paint thumb background
float lw = UIScale.scale( 1f );
float lw = UIScale.scale( thumbBorderWidth );
g.setColor( thumbColor );
((Graphics2D)g).fill( createRoundThumbShape( tx + lw, ty + lw,
tw - lw - lw, th - lw - lw ) );
@@ -428,7 +498,7 @@ debug*/
g2.fill( createDirectionalThumbShape( fw, fw, tw, th, 0 ) );
// paint thumb background
float lw = UIScale.scale( 1f );
float lw = UIScale.scale( thumbBorderWidth );
g2.setColor( thumbColor );
g2.fill( createDirectionalThumbShape( fw + lw, fw + lw,
tw - lw - lw, th - lw - lw - (lw * 0.4142f), 0 ) );

View File

@@ -33,6 +33,8 @@ import java.awt.event.FocusListener;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.JComponent;
import javax.swing.JSpinner;
import javax.swing.JTextField;
@@ -43,6 +45,9 @@ import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicSpinnerUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.LoggingFacade;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JSpinner}.
@@ -63,12 +68,13 @@ import com.formdev.flatlaf.FlatClientProperties;
* @uiDefault Spinner.buttonStyle String button (default) or none
* @uiDefault Component.arrowType String chevron (default) or triangle
* @uiDefault Component.isIntelliJTheme boolean
* @uiDefault Component.borderColor Color
* @uiDefault Component.disabledBorderColor Color
* @uiDefault Spinner.disabledBackground Color
* @uiDefault Spinner.disabledForeground Color
* @uiDefault Spinner.focusedBackground Color optional
* @uiDefault Spinner.buttonBackground Color
* @uiDefault Spinner.buttonBackground Color optional
* @uiDefault Spinner.buttonSeparatorWidth int or float optional; defaults to Component.borderWidth
* @uiDefault Spinner.buttonSeparatorColor Color optional
* @uiDefault Spinner.buttonDisabledSeparatorColor Color optional
* @uiDefault Spinner.buttonArrowColor Color
* @uiDefault Spinner.buttonDisabledArrowColor Color
* @uiDefault Spinner.buttonHoverArrowColor Color
@@ -79,29 +85,41 @@ import com.formdev.flatlaf.FlatClientProperties;
*/
public class FlatSpinnerUI
extends BasicSpinnerUI
implements StyleableUI
{
private Handler handler;
protected int minimumWidth;
protected String buttonStyle;
protected String arrowType;
@Styleable protected int minimumWidth;
@Styleable protected String buttonStyle;
@Styleable protected String arrowType;
protected boolean isIntelliJTheme;
protected Color borderColor;
protected Color disabledBorderColor;
protected Color disabledBackground;
protected Color disabledForeground;
protected Color focusedBackground;
protected Color buttonBackground;
protected Color buttonArrowColor;
protected Color buttonDisabledArrowColor;
protected Color buttonHoverArrowColor;
protected Color buttonPressedArrowColor;
protected Insets padding;
@Styleable protected Color disabledBackground;
@Styleable protected Color disabledForeground;
@Styleable protected Color focusedBackground;
@Styleable protected Color buttonBackground;
/** @since 2 */ @Styleable protected float buttonSeparatorWidth;
/** @since 2 */ @Styleable protected Color buttonSeparatorColor;
/** @since 2 */ @Styleable protected Color buttonDisabledSeparatorColor;
@Styleable protected Color buttonArrowColor;
@Styleable protected Color buttonDisabledArrowColor;
@Styleable protected Color buttonHoverArrowColor;
@Styleable protected Color buttonPressedArrowColor;
@Styleable protected Insets padding;
private Map<String, Object> oldStyleValues;
private AtomicBoolean borderShared;
public static ComponentUI createUI( JComponent c ) {
return new FlatSpinnerUI();
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installStyle();
}
@Override
protected void installDefaults() {
super.installDefaults();
@@ -112,12 +130,13 @@ public class FlatSpinnerUI
buttonStyle = UIManager.getString( "Spinner.buttonStyle" );
arrowType = UIManager.getString( "Component.arrowType" );
isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" );
borderColor = UIManager.getColor( "Component.borderColor" );
disabledBorderColor = UIManager.getColor( "Component.disabledBorderColor" );
disabledBackground = UIManager.getColor( "Spinner.disabledBackground" );
disabledForeground = UIManager.getColor( "Spinner.disabledForeground" );
focusedBackground = UIManager.getColor( "Spinner.focusedBackground" );
buttonBackground = UIManager.getColor( "Spinner.buttonBackground" );
buttonSeparatorWidth = FlatUIUtils.getUIFloat( "Spinner.buttonSeparatorWidth", FlatUIUtils.getUIFloat( "Component.borderWidth", 1 ) );
buttonSeparatorColor = UIManager.getColor( "Spinner.buttonSeparatorColor" );
buttonDisabledSeparatorColor = UIManager.getColor( "Spinner.buttonDisabledSeparatorColor" );
buttonArrowColor = UIManager.getColor( "Spinner.buttonArrowColor" );
buttonDisabledArrowColor = UIManager.getColor( "Spinner.buttonDisabledArrowColor" );
buttonHoverArrowColor = UIManager.getColor( "Spinner.buttonHoverArrowColor" );
@@ -131,18 +150,21 @@ public class FlatSpinnerUI
protected void uninstallDefaults() {
super.uninstallDefaults();
borderColor = null;
disabledBorderColor = null;
disabledBackground = null;
disabledForeground = null;
focusedBackground = null;
buttonBackground = null;
buttonSeparatorColor = null;
buttonDisabledSeparatorColor = null;
buttonArrowColor = null;
buttonDisabledArrowColor = null;
buttonHoverArrowColor = null;
buttonPressedArrowColor = null;
padding = null;
oldStyleValues = null;
borderShared = null;
MigLayoutVisualPadding.uninstall( spinner );
}
@@ -172,6 +194,35 @@ public class FlatSpinnerUI
return handler;
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( spinner, "Spinner" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
updateEditorPadding();
updateArrowButtonsStyle();
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
if( borderShared == null )
borderShared = new AtomicBoolean( true );
return FlatStylingSupport.applyToAnnotatedObjectOrBorder( this, key, value, spinner, borderShared );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatStylingSupport.getAnnotatedStyleableInfos( this, spinner.getBorder() );
}
@Override
protected JComponent createEditor() {
JComponent editor = super.createEditor();
@@ -236,9 +287,7 @@ public class FlatSpinnerUI
: null;
}
/**
* @since 1.3
*/
/** @since 1.3 */
public static boolean isPermanentFocusOwner( JSpinner spinner ) {
if( FlatUIUtils.isPermanentFocusOwner( spinner ) )
return true;
@@ -297,6 +346,15 @@ public class FlatSpinnerUI
return button;
}
private void updateArrowButtonsStyle() {
for( Component c : spinner.getComponents() ) {
if( c instanceof FlatArrowButton ) {
((FlatArrowButton)c).updateStyle( arrowType, buttonArrowColor,
buttonDisabledArrowColor, buttonHoverArrowColor, null, buttonPressedArrowColor, null );
}
}
}
@Override
public void update( Graphics g, JComponent c ) {
float focusWidth = FlatUIUtils.getBorderFocusWidth( c );
@@ -327,7 +385,7 @@ public class FlatSpinnerUI
boolean isLeftToRight = spinner.getComponentOrientation().isLeftToRight();
// paint arrow buttons background
if( enabled ) {
if( enabled && buttonBackground != null ) {
g2.setColor( buttonBackground );
Shape oldClip = g2.getClip();
if( isLeftToRight )
@@ -339,10 +397,13 @@ public class FlatSpinnerUI
}
// paint vertical line between value and arrow buttons
g2.setColor( enabled ? borderColor : disabledBorderColor );
float lw = scale( 1f );
float lx = isLeftToRight ? arrowX : arrowX + arrowWidth - lw;
g2.fill( new Rectangle2D.Float( lx, focusWidth, lw, height - 1 - (focusWidth * 2) ) );
Color separatorColor = enabled ? buttonSeparatorColor : buttonDisabledSeparatorColor;
if( separatorColor != null && buttonSeparatorWidth > 0 ) {
g2.setColor( separatorColor );
float lw = scale( buttonSeparatorWidth );
float lx = isLeftToRight ? arrowX : arrowX + arrowWidth - lw;
g2.fill( new Rectangle2D.Float( lx, focusWidth, lw, height - 1 - (focusWidth * 2) ) );
}
}
paint( g, c );
@@ -483,6 +544,13 @@ public class FlatSpinnerUI
case FlatClientProperties.MINIMUM_WIDTH:
spinner.revalidate();
break;
case FlatClientProperties.STYLE:
case FlatClientProperties.STYLE_CLASS:
installStyle();
spinner.revalidate();
spinner.repaint();
break;
}
}
}

View File

@@ -23,6 +23,8 @@ import java.awt.Graphics;
import java.awt.Insets;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Map;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JSplitPane;
@@ -32,6 +34,10 @@ import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicSplitPaneDivider;
import javax.swing.plaf.basic.BasicSplitPaneUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale;
/**
@@ -66,16 +72,27 @@ import com.formdev.flatlaf.util.UIScale;
*/
public class FlatSplitPaneUI
extends BasicSplitPaneUI
implements StyleableUI
{
protected String arrowType;
protected Color oneTouchArrowColor;
protected Color oneTouchHoverArrowColor;
protected Color oneTouchPressedArrowColor;
@Styleable protected String arrowType;
@Styleable protected Color oneTouchArrowColor;
@Styleable protected Color oneTouchHoverArrowColor;
@Styleable protected Color oneTouchPressedArrowColor;
private PropertyChangeListener propertyChangeListener;
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
return new FlatSplitPaneUI();
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installStyle();
}
@Override
protected void installDefaults() {
arrowType = UIManager.getString( "Component.arrowType" );
@@ -96,6 +113,24 @@ public class FlatSplitPaneUI
oneTouchArrowColor = null;
oneTouchHoverArrowColor = null;
oneTouchPressedArrowColor = null;
oldStyleValues = null;
}
@Override
protected void installListeners() {
super.installListeners();
propertyChangeListener = FlatStylingSupport.createPropertyChangeListener( splitPane, this::installStyle, null );
splitPane.addPropertyChangeListener( propertyChangeListener );
}
@Override
protected void uninstallListeners() {
super.uninstallListeners();
splitPane.removePropertyChangeListener( propertyChangeListener );
propertyChangeListener = null;
}
@Override
@@ -103,16 +138,53 @@ public class FlatSplitPaneUI
return new FlatSplitPaneDivider( this );
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( splitPane, "SplitPane" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
if( divider instanceof FlatSplitPaneDivider )
((FlatSplitPaneDivider)divider).updateStyle();
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
try {
if( divider instanceof FlatSplitPaneDivider )
return ((FlatSplitPaneDivider)divider).applyStyleProperty( key, value );
} catch( UnknownStyleException ex ) {
// ignore
}
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, splitPane, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
Map<String, Class<?>> infos = FlatStylingSupport.getAnnotatedStyleableInfos( this );
if( divider instanceof FlatSplitPaneDivider )
infos.putAll( ((FlatSplitPaneDivider)divider).getStyleableInfos() );
return infos;
}
//---- class FlatSplitPaneDivider -----------------------------------------
protected class FlatSplitPaneDivider
extends BasicSplitPaneDivider
{
protected final String style = UIManager.getString( "SplitPaneDivider.style" );
protected final Color gripColor = UIManager.getColor( "SplitPaneDivider.gripColor" );
protected final int gripDotCount = FlatUIUtils.getUIInt( "SplitPaneDivider.gripDotCount", 3 );
protected final int gripDotSize = FlatUIUtils.getUIInt( "SplitPaneDivider.gripDotSize", 3 );
protected final int gripGap = FlatUIUtils.getUIInt( "SplitPaneDivider.gripGap", 2 );
@Styleable protected String style = UIManager.getString( "SplitPaneDivider.style" );
@Styleable protected Color gripColor = UIManager.getColor( "SplitPaneDivider.gripColor" );
@Styleable protected int gripDotCount = FlatUIUtils.getUIInt( "SplitPaneDivider.gripDotCount", 3 );
@Styleable protected int gripDotSize = FlatUIUtils.getUIInt( "SplitPaneDivider.gripDotSize", 3 );
@Styleable protected int gripGap = FlatUIUtils.getUIInt( "SplitPaneDivider.gripGap", 2 );
protected FlatSplitPaneDivider( BasicSplitPaneUI ui ) {
super( ui );
@@ -120,6 +192,27 @@ public class FlatSplitPaneUI
setLayout( new FlatDividerLayout() );
}
/**
* @since 2
*/
protected Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
}
/**
* @since 2
*/
public Map<String, Class<?>> getStyleableInfos() {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
void updateStyle() {
if( leftButton instanceof FlatOneTouchButton )
((FlatOneTouchButton)leftButton).updateStyle();
if( rightButton instanceof FlatOneTouchButton )
((FlatOneTouchButton)rightButton).updateStyle();
}
@Override
public void setDividerSize( int newSize ) {
super.setDividerSize( UIScale.scale( newSize ) );
@@ -200,6 +293,11 @@ public class FlatSplitPaneUI
this.left = left;
}
protected void updateStyle() {
updateStyle( arrowType, oneTouchArrowColor, null,
oneTouchHoverArrowColor, null, oneTouchPressedArrowColor, null );
}
@Override
public int getDirection() {
return (orientation == JSplitPane.VERTICAL_SPLIT)

View File

@@ -0,0 +1,723 @@
/*
* Copyright 2021 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import java.beans.PropertyChangeListener;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiFunction;
import java.util.function.Predicate;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.UIManager;
import javax.swing.border.Border;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.util.StringUtils;
import com.formdev.flatlaf.util.SystemInfo;
/**
* Support for styling components in CSS syntax.
*
* @author Karl Tauber
* @since 2
*/
public class FlatStylingSupport
{
/**
* Indicates that a field is intended to be used by FlatLaf styling support.
* <p>
* <strong>Do not rename fields annotated with this annotation.</strong>
*
* @since 2
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Styleable {
boolean dot() default false;
Class<?> type() default Void.class;
}
/** @since 2 */
public interface StyleableUI {
Map<String, Class<?>> getStyleableInfos( JComponent c );
}
/** @since 2 */
public interface StyleableBorder {
Object applyStyleProperty( String key, Object value );
Map<String, Class<?>> getStyleableInfos();
}
/**
* Returns the style specified in client property {@link FlatClientProperties#STYLE}.
*/
public static Object getStyle( JComponent c ) {
return c.getClientProperty( FlatClientProperties.STYLE );
}
/**
* Returns the style class(es) specified in client property {@link FlatClientProperties#STYLE_CLASS}.
*/
public static Object getStyleClass( JComponent c ) {
return c.getClientProperty( FlatClientProperties.STYLE_CLASS );
}
static boolean hasStyleProperty( JComponent c ) {
return getStyle( c ) != null || getStyleClass( c ) != null;
}
public static Object getResolvedStyle( JComponent c, String type ) {
Object style = getStyle( c );
Object styleClass = getStyleClass( c );
Object styleForClasses = getStyleForClasses( styleClass, type );
return joinStyles( styleForClasses, style );
}
/**
* Returns the styles for the given style class(es) and the given type.
* <p>
* The style rules must be defined in UI defaults either as strings (in CSS syntax)
* or as {@link java.util.Map}&lt;String, Object&gt; (with binary values).
* The key must be in syntax: {@code [style]type.styleClass}, where the type is optional.
* E.g. in FlatLaf properties file:
* <pre>{@code
* [style]Button.primary = borderColor: #08f; background: #08f; foreground: #fff
* [style].secondary = borderColor: #0f8; background: #0f8
* }</pre>
* or in Java code:
* <pre>{@code
* UIManager.put( "[style]Button.primary", "borderColor: #08f; background: #08f; foreground: #fff" );
* UIManager.put( "[style].secondary", "borderColor: #0f8; background: #0f8" );
* }</pre>
* The rule "Button.primary" can be applied to buttons only.
* The rule ".secondary" can be applied to any component.
* <p>
* To have similar behavior as in CSS, this method first gets the rule without type,
* then the rule with type and concatenates both rules.
* E.g. invoking this method with parameters styleClass="foo" and type="Button" does following:
* <pre>{@code
* return joinStyles(
* UIManager.get( "[style].foo" ),
* UIManager.get( "[style]Button.foo" ) );
* }</pre>
*
* @param styleClass the style class(es) either as string (single class or multiple classes separated by space characters)
* or as {@code String[]} or {@link java.util.List}&lt;String&gt; (multiple classes)
* @param type the type of the component
* @return the styles
*/
public static Object getStyleForClasses( Object styleClass, String type ) {
if( styleClass == null )
return null;
if( styleClass instanceof String && ((String)styleClass).indexOf( ' ' ) >= 0 )
styleClass = StringUtils.split( (String) styleClass, ' ', true, true );
if( styleClass instanceof String )
return getStyleForClass( ((String)styleClass).trim(), type );
else if( styleClass instanceof String[] ) {
Object style = null;
for( String cls : (String[]) styleClass )
style = joinStyles( style, getStyleForClass( cls, type ) );
return style;
} else if( styleClass instanceof List<?> ) {
Object style = null;
for( Object cls : (List<?>) styleClass )
style = joinStyles( style, getStyleForClass( (String) cls, type ) );
return style;
} else
return null;
}
private static Object getStyleForClass( String styleClass, String type ) {
return joinStyles(
UIManager.get( "[style]." + styleClass ),
UIManager.get( "[style]" + type + '.' + styleClass ) );
}
/**
* Joins two styles. They can be either strings (in CSS syntax)
* or {@link java.util.Map}&lt;String, Object&gt; (with binary values).
* <p>
* If both styles are strings, then a joined string is returned.
* If both styles are maps, then a joined map is returned.
* If one style is a map and the other style a string, then the string
* is parsed (using {@link #parse(String)}) to a map and a joined map is returned.
*
* @param style1 first style as string or map, or {@code null}
* @param style2 second style as string or map, or {@code null}
* @return new joined style
*/
@SuppressWarnings( "unchecked" )
public static Object joinStyles( Object style1, Object style2 ) {
if( style1 == null )
return style2;
if( style2 == null )
return style1;
// join two strings
if( style1 instanceof String && style2 instanceof String )
return style1 + "; " + style2;
// convert first style to map
Map<String, Object> map1 = (style1 instanceof String)
? parse( (String) style1 )
: (Map<String, Object>) style1;
if( map1 == null )
return style2;
// convert second style to map
Map<String, Object> map2 = (style2 instanceof String)
? parse( (String) style2 )
: (Map<String, Object>) style2;
if( map2 == null )
return style1;
// join two maps
Map<String, Object> map = new HashMap<>( map1 );
map.putAll( map2 );
return map;
}
/**
* Concatenates two styles in CSS syntax.
*
* @param style1 first style, or {@code null}
* @param style2 second style, or {@code null}
* @return concatenation of the two styles separated by a semicolon
*/
public static String concatStyles( String style1, String style2 ) {
if( style1 == null )
return style2;
if( style2 == null )
return style1;
return style1 + "; " + style2;
}
/**
* Parses styles in CSS syntax ("key1: value1; key2: value2; ..."),
* converts the value strings into binary and invokes the given function
* to apply the properties.
*
* @param oldStyleValues map of old values modified by the previous invocation, or {@code null}
* @param style the style in CSS syntax as string, or a Map, or {@code null}
* @param applyProperty function that is invoked to apply the properties;
* first parameter is the key, second the binary value;
* the function must return the old value
* @return map of old values modified by the given style, or {@code null}
* @throws UnknownStyleException on unknown style keys
* @throws IllegalArgumentException on syntax errors
* @throws ClassCastException if value type does not fit to expected type
*/
public static Map<String, Object> parseAndApply( Map<String, Object> oldStyleValues,
Object style, BiFunction<String, Object, Object> applyProperty )
throws UnknownStyleException, IllegalArgumentException
{
// restore previous values
if( oldStyleValues != null ) {
for( Map.Entry<String, Object> e : oldStyleValues.entrySet() )
applyProperty.apply( e.getKey(), e.getValue() );
}
// ignore empty style
if( style == null )
return null;
if( style instanceof String ) {
// handle style in CSS syntax
String str = (String) style;
if( StringUtils.isTrimmedEmpty( str ) )
return null;
return applyStyle( parse( str ), applyProperty );
} else if( style instanceof Map ) {
// handle style of type Map
@SuppressWarnings( "unchecked" )
Map<String, Object> map = (Map<String, Object>) style;
return applyStyle( map, applyProperty );
} else
return null;
}
private static Map<String, Object> applyStyle( Map<String, Object> style,
BiFunction<String, Object, Object> applyProperty )
{
if( style.isEmpty() )
return null;
Map<String, Object> oldValues = new HashMap<>();
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;
}
Object oldValue = applyProperty.apply( key, newValue );
oldValues.put( key, oldValue );
}
return oldValues;
}
/**
* Parses styles in CSS syntax ("key1: value1; key2: value2; ..."),
* converts the value strings into binary and returns all key/value pairs as map.
*
* @param style the style in CSS syntax, or {@code null}
* @return map of parsed styles, or {@code null}
* @throws IllegalArgumentException on syntax errors
*/
public static Map<String, Object> parse( String style )
throws IllegalArgumentException
{
if( style == null || StringUtils.isTrimmedEmpty( style ) )
return null;
Map<String, Object> map = null;
// split style into parts and process them
for( String part : StringUtils.split( style, ';', true, true ) ) {
// find separator colon
int sepIndex = part.indexOf( ':' );
if( sepIndex < 0 )
throw new IllegalArgumentException( "missing colon in '" + part + "'" );
// split into key and value
String key = StringUtils.substringTrimmed( part, 0, sepIndex );
String value = StringUtils.substringTrimmed( part, sepIndex + 1 );
if( key.isEmpty() )
throw new IllegalArgumentException( "missing key in '" + part + "'" );
if( value.isEmpty() )
throw new IllegalArgumentException( "missing value in '" + part + "'" );
// parse value string and convert it into binary value
if( map == null )
map = new LinkedHashMap<>();
map.put( key, parseValue( key, value ) );
}
return map;
}
private static Object parseValue( String key, String value ) {
// simple reference
if( value.startsWith( "$" ) )
return UIManager.get( value.substring( 1 ) );
// remove key prefix for correct value type detection
// (e.g. "[light]padding" would not parse to Insets)
if( key.startsWith( "[" ) )
key = key.substring( key.indexOf( ']' ) + 1 );
// parse string
return FlatLaf.parseDefaultsValue( key, value, null );
}
/**
* Applies the given value to an annotated field of the given object.
* The field must be annotated with {@link Styleable}.
*
* @param obj the object
* @param key the name of the field
* @param value the new value
* @return the old value of the field
* @throws UnknownStyleException if object does not have a annotated field with given name
* @throws IllegalArgumentException if value type does not fit to expected type
*/
public static Object applyToAnnotatedObject( Object obj, String key, Object value )
throws UnknownStyleException, IllegalArgumentException
{
String fieldName = key;
int dotIndex = key.indexOf( '.' );
if( dotIndex >= 0 ) {
// remove first dot in key and change subsequent character to uppercase
fieldName = key.substring( 0, dotIndex )
+ Character.toUpperCase( key.charAt( dotIndex + 1 ) )
+ key.substring( dotIndex + 2 );
}
return applyToField( obj, fieldName, key, value, field -> {
Styleable styleable = field.getAnnotation( Styleable.class );
return styleable != null && styleable.dot() == (dotIndex >= 0);
} );
}
/**
* Applies the given value to a field of the given object.
*
* @param obj the object
* @param fieldName the name of the field
* @param key the key (only used for error reporting)
* @param value the new value
* @return the old value of the field
* @throws UnknownStyleException if object does not have a field with given name
* @throws IllegalArgumentException if value type does not fit to expected type
*/
static Object applyToField( Object obj, String fieldName, String key, Object value )
throws UnknownStyleException, IllegalArgumentException
{
return applyToField( obj, fieldName, key, value, null );
}
private static Object applyToField( Object obj, String fieldName, String key, Object value, Predicate<Field> predicate )
throws UnknownStyleException, IllegalArgumentException
{
Class<?> cls = obj.getClass();
for(;;) {
try {
Field f = cls.getDeclaredField( fieldName );
if( predicate == null || predicate.test( f ) ) {
if( !isValidField( f ) )
throw new IllegalArgumentException( "field '" + cls.getName() + "." + fieldName + "' is final or static" );
try {
// necessary to access protected fields in other packages
f.setAccessible( true );
// get old value and set new value
Object oldValue = f.get( obj );
f.set( obj, convertToEnum( value, f.getType() ) );
return oldValue;
} catch( IllegalAccessException ex ) {
throw new IllegalArgumentException( "failed to access field '" + cls.getName() + "." + fieldName + "'", ex );
}
}
} catch( NoSuchFieldException ex ) {
// field not found in class --> try superclass
}
cls = cls.getSuperclass();
if( cls == null )
throw new UnknownStyleException( key );
if( predicate != null ) {
String superclassName = cls.getName();
if( superclassName.startsWith( "java." ) || superclassName.startsWith( "javax." ) )
throw new UnknownStyleException( key );
}
}
}
private static boolean isValidField( Field f ) {
int modifiers = f.getModifiers();
return (modifiers & (Modifier.FINAL|Modifier.STATIC)) == 0 && !f.isSynthetic();
}
/**
* Applies the given value to a property of the given object.
* Works only for properties that have public getter and setter methods.
* First the property getter is invoked to get the old value,
* then the property setter is invoked to set the new value.
*
* @param obj the object
* @param name the name of the property
* @param value the new value
* @return the old value of the property
* @throws UnknownStyleException if object does not have a property with given name
* @throws IllegalArgumentException if value type does not fit to expected type
*/
private static Object applyToProperty( Object obj, String name, Object value )
throws UnknownStyleException, IllegalArgumentException
{
Class<?> cls = obj.getClass();
String getterName = buildMethodName( "get", name );
String setterName = buildMethodName( "set", name );
try {
Method getter;
try {
getter = cls.getMethod( getterName );
} catch( NoSuchMethodException ex ) {
getter = cls.getMethod( buildMethodName( "is", name ) );
}
Method setter = cls.getMethod( setterName, getter.getReturnType() );
Object oldValue = getter.invoke( obj );
setter.invoke( obj, convertToEnum( value, getter.getReturnType() ) );
return oldValue;
} catch( NoSuchMethodException ex ) {
throw new UnknownStyleException( name );
} catch( Exception ex ) {
throw new IllegalArgumentException( "failed to invoke property methods '" + cls.getName() + "."
+ getterName + "()' or '" + setterName + "(...)'", ex );
}
}
private static String buildMethodName( String prefix, String name ) {
int prefixLength = prefix.length();
int nameLength = name.length();
char[] chars = new char[prefixLength + nameLength];
prefix.getChars( 0, prefixLength, chars, 0 );
name.getChars( 0, nameLength, chars, prefixLength );
chars[prefixLength] = Character.toUpperCase( chars[prefixLength] );
return new String( chars );
}
@SuppressWarnings( { "unchecked", "rawtypes" } )
private static Object convertToEnum( Object value, Class<?> type )
throws IllegalArgumentException
{
// if type is an enum, convert string to enum value
if( Enum.class.isAssignableFrom( type ) && value instanceof String ) {
try {
value = Enum.valueOf( (Class<? extends Enum>) type, (String) value );
} catch( IllegalArgumentException ex ) {
throw new IllegalArgumentException( "unknown enum value '" + value + "' in enum '" + type.getName() + "'", ex );
}
}
return value;
}
/**
* Applies the given value to an annotated field of the given object
* or to a property of the given component.
* The field must be annotated with {@link Styleable}.
* The component property must have public getter and setter methods.
*
* @param obj the object
* @param comp the component, or {@code null}
* @param key the name of the field
* @param value the new value
* @return the old value of the field
* @throws UnknownStyleException if object does not have a annotated field with given name
* @throws IllegalArgumentException if value type does not fit to expected type
*/
public static Object applyToAnnotatedObjectOrComponent( Object obj, Object comp, String key, Object value )
throws UnknownStyleException, IllegalArgumentException
{
try {
return applyToAnnotatedObject( obj, key, value );
} catch( UnknownStyleException ex ) {
try {
if( comp != null )
return applyToProperty( comp, key, value );
} catch( UnknownStyleException ex2 ) {
// ignore
}
throw ex;
}
}
static Object applyToAnnotatedObjectOrBorder( Object obj, String key, Object value,
JComponent c, AtomicBoolean borderShared )
{
try {
return applyToAnnotatedObject( obj, key, value );
} catch( UnknownStyleException ex ) {
// apply to border
Border border = c.getBorder();
if( border instanceof StyleableBorder ) {
if( borderShared.get() ) {
border = cloneBorder( border );
c.setBorder( border );
borderShared.set( false );
}
try {
return ((StyleableBorder)border).applyStyleProperty( key, value );
} catch( UnknownStyleException ex2 ) {
// ignore
}
}
// apply to component property
try {
return applyToProperty( c, key, value );
} catch( UnknownStyleException ex2 ) {
// ignore
}
throw ex;
}
}
static PropertyChangeListener createPropertyChangeListener( JComponent c,
Runnable installStyle, PropertyChangeListener superListener )
{
return e -> {
if( superListener != null )
superListener.propertyChange( e );
switch( e.getPropertyName() ) {
case FlatClientProperties.STYLE:
case FlatClientProperties.STYLE_CLASS:
installStyle.run();
c.revalidate();
c.repaint();
break;
}
};
}
static Border cloneBorder( Border border ) {
Class<? extends Border> borderClass = border.getClass();
try {
return borderClass.getDeclaredConstructor().newInstance();
} catch( Exception ex ) {
throw new IllegalArgumentException( "failed to clone border '" + borderClass.getName() + "'", ex );
}
}
static Icon cloneIcon( Icon icon ) {
Class<? extends Icon> iconClass = icon.getClass();
try {
return iconClass.getDeclaredConstructor().newInstance();
} catch( Exception ex ) {
throw new IllegalArgumentException( "failed to clone icon '" + iconClass.getName() + "'", ex );
}
}
/**
* Returns a map of all fields annotated with {@link Styleable}.
* The key is the name of the field and the value the type of the field.
*/
public static Map<String, Class<?>> getAnnotatedStyleableInfos( Object obj ) {
return getAnnotatedStyleableInfos( obj, null );
}
public static Map<String, Class<?>> getAnnotatedStyleableInfos( Object obj, Border border ) {
Map<String, Class<?>> infos = new StyleableInfosMap<>();
collectAnnotatedStyleableInfos( obj, infos );
collectStyleableInfos( border, infos );
return infos;
}
/**
* Search for all fields annotated with {@link Styleable} and add them to the given map.
* The key is the name of the field and the value the type of the field.
*/
public static void collectAnnotatedStyleableInfos( Object obj, Map<String, Class<?>> infos ) {
HashSet<String> processedFields = new HashSet<>();
Class<?> cls = obj.getClass();
for(;;) {
for( Field f : cls.getDeclaredFields() ) {
if( !isValidField( f ) )
continue;
Styleable styleable = f.getAnnotation( Styleable.class );
if( styleable == null )
continue;
String name = f.getName();
Class<?> type = f.getType();
// for the case that the same field name is used in a class and in
// one of its superclasses (e.g. field 'borderColor' in FlatButtonBorder
// and in FlatBorder), do not process field in superclass
if( processedFields.contains( name ) )
continue;
processedFields.add( name );
// handle "dot" keys (e.g. change field name "iconArrowType" to style key "icon.arrowType")
if( styleable.dot() ) {
int len = name.length();
for( int i = 0; i < len; i++ ) {
if( Character.isUpperCase( name.charAt( i ) ) ) {
name = name.substring( 0, i ) + '.'
+ Character.toLowerCase( name.charAt( i ) )
+ name.substring( i + 1 );
break;
}
}
}
// field has a different type
if( styleable.type() != Void.class )
type = styleable.type();
infos.put( name, type );
}
cls = cls.getSuperclass();
if( cls == null )
return;
String superclassName = cls.getName();
if( superclassName.startsWith( "java." ) || superclassName.startsWith( "javax." ) )
return;
}
}
public static void collectStyleableInfos( Border border, Map<String, Class<?>> infos ) {
if( border instanceof StyleableBorder )
infos.putAll( ((StyleableBorder)border).getStyleableInfos() );
}
public static void putAllPrefixKey( Map<String, Class<?>> infos, String keyPrefix, Map<String, Class<?>> infos2 ) {
for( Map.Entry<String, Class<?>> e : infos2.entrySet() )
infos.put( keyPrefix.concat( e.getKey() ), e.getValue() );
}
//---- class UnknownStyleException ----------------------------------------
public static class UnknownStyleException
extends IllegalArgumentException
{
public UnknownStyleException( String key ) {
super( key );
}
@Override
public String getMessage() {
return "unknown style '" + super.getMessage() + "'";
}
}
//---- class StyleableInfosMap --------------------------------------------
static class StyleableInfosMap<K,V>
extends LinkedHashMap<K,V>
{
@Override
public V put( K key, V value ) {
V oldValue = super.put( key, value );
if( oldValue != null )
throw new IllegalArgumentException( "duplicate key '" + key + "'" );
return oldValue;
}
@Override
public void putAll( Map<? extends K, ? extends V> m ) {
for( Map.Entry<? extends K, ? extends V> e : m.entrySet() )
put( e.getKey(), e.getValue() );
}
}
}

View File

@@ -40,6 +40,8 @@ import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
@@ -53,6 +55,7 @@ import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.IntConsumer;
@@ -84,9 +87,14 @@ import javax.swing.plaf.basic.BasicTabbedPaneUI;
import javax.swing.text.JTextComponent;
import javax.swing.text.View;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.icons.FlatTabbedPaneCloseIcon;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.Animator;
import com.formdev.flatlaf.util.CubicBezierEasing;
import com.formdev.flatlaf.util.JavaCompatibility;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.StringUtils;
import com.formdev.flatlaf.util.UIScale;
@@ -101,7 +109,7 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault TabbedPane.font Font
* @uiDefault TabbedPane.background Color
* @uiDefault TabbedPane.foreground Color
* @uiDefault TabbedPane.shadow Color used for scroll arrows and cropped line
* @uiDefault TabbedPane.shadow Color used for cropped line
* @uiDefault TabbedPane.textIconGap int
* @uiDefault TabbedPane.tabInsets Insets
* @uiDefault TabbedPane.selectedTabPadInsets Insets unused
@@ -128,12 +136,15 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault TabbedPane.maximumTabWidth int optional
* @uiDefault TabbedPane.tabHeight int
* @uiDefault TabbedPane.tabSelectionHeight int
* @uiDefault TabbedPane.cardTabSelectionHeight int
* @uiDefault TabbedPane.contentSeparatorHeight int
* @uiDefault TabbedPane.showTabSeparators boolean
* @uiDefault TabbedPane.tabSeparatorsFullHeight boolean
* @uiDefault TabbedPane.hasFullBorder boolean
* @uiDefault TabbedPane.activeTabBorder boolean
*
* @uiDefault TabbedPane.tabLayoutPolicy String wrap (default) or scroll
* @uiDefault TabbedPane.tabType String underlined (default) or card
* @uiDefault TabbedPane.tabsPopupPolicy String never or asNeeded (default)
* @uiDefault TabbedPane.scrollButtonsPolicy String never, asNeeded or asNeededSingle (default)
* @uiDefault TabbedPane.scrollButtonsPlacement String both (default) or trailing
@@ -151,12 +162,18 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault TabbedPane.buttonPressedBackground Color
*
* @uiDefault TabbedPane.moreTabsButtonToolTipText String
* @uiDefault TabbedPane.tabCloseToolTipText String
*
* @author Karl Tauber
*/
public class FlatTabbedPaneUI
extends BasicTabbedPaneUI
implements StyleableUI
{
// tab type
/** @since 2 */ protected static final int TAB_TYPE_UNDERLINED = 0;
/** @since 2 */ protected static final int TAB_TYPE_CARD = 1;
// tabs popup policy / scroll arrows policy
protected static final int NEVER = 0;
// protected static final int ALWAYS = 1;
@@ -177,43 +194,52 @@ public class FlatTabbedPaneUI
private static Set<KeyStroke> focusBackwardTraversalKeys;
protected Color foreground;
protected Color disabledForeground;
protected Color selectedBackground;
protected Color selectedForeground;
protected Color underlineColor;
protected Color disabledUnderlineColor;
protected Color hoverColor;
protected Color focusColor;
protected Color tabSeparatorColor;
protected Color contentAreaColor;
@Styleable protected Color disabledForeground;
@Styleable protected Color selectedBackground;
@Styleable protected Color selectedForeground;
@Styleable protected Color underlineColor;
@Styleable protected Color disabledUnderlineColor;
@Styleable protected Color hoverColor;
@Styleable protected Color focusColor;
@Styleable protected Color tabSeparatorColor;
@Styleable protected Color contentAreaColor;
private int textIconGapUnscaled;
protected int minimumTabWidth;
protected int maximumTabWidth;
protected int tabHeight;
protected int tabSelectionHeight;
protected int contentSeparatorHeight;
protected boolean showTabSeparators;
protected boolean tabSeparatorsFullHeight;
protected boolean hasFullBorder;
protected boolean tabsOpaque = true;
@Styleable protected int minimumTabWidth;
@Styleable protected int maximumTabWidth;
@Styleable protected int tabHeight;
@Styleable protected int tabSelectionHeight;
/** @since 2 */ @Styleable protected int cardTabSelectionHeight;
@Styleable protected int contentSeparatorHeight;
@Styleable protected boolean showTabSeparators;
@Styleable protected boolean tabSeparatorsFullHeight;
@Styleable protected boolean hasFullBorder;
@Styleable protected boolean tabsOpaque = true;
private int tabsPopupPolicy;
private int scrollButtonsPolicy;
private int scrollButtonsPlacement;
@Styleable(type=String.class) private int tabType;
@Styleable(type=String.class) private int tabsPopupPolicy;
@Styleable(type=String.class) private int scrollButtonsPolicy;
@Styleable(type=String.class) private int scrollButtonsPlacement;
private int tabAreaAlignment;
private int tabAlignment;
private int tabWidthMode;
@Styleable(type=String.class) private int tabAreaAlignment;
@Styleable(type=String.class) private int tabAlignment;
@Styleable(type=String.class) private int tabWidthMode;
protected Icon closeIcon;
protected String arrowType;
protected Insets buttonInsets;
protected int buttonArc;
protected Color buttonHoverBackground;
protected Color buttonPressedBackground;
@Styleable protected String arrowType;
@Styleable protected Insets buttonInsets;
@Styleable protected int buttonArc;
@Styleable protected Color buttonHoverBackground;
@Styleable protected Color buttonPressedBackground;
protected String moreTabsButtonToolTipText;
@Styleable protected String moreTabsButtonToolTipText;
/** @since 2 */ @Styleable protected String tabCloseToolTipText;
// only used via styling (not in UI defaults, but has likewise client properties)
/** @since 2 */ @Styleable protected boolean showContentSeparator = true;
/** @since 2 */ @Styleable protected boolean hideTabAreaWithOneTab;
/** @since 2 */ @Styleable protected boolean tabClosable;
/** @since 2 */ @Styleable protected int tabIconPlacement = LEADING;
protected JViewport tabViewport;
protected FlatWheelTabScroller wheelTabScroller;
@@ -231,6 +257,8 @@ public class FlatTabbedPaneUI
private boolean pressedTabClose;
private Object[] oldRenderingHints;
private Map<String, Object> oldStyleValues;
private boolean closeIconShared = true;
public static ComponentUI createUI( JComponent c ) {
return new FlatTabbedPaneUI();
@@ -259,6 +287,8 @@ public class FlatTabbedPaneUI
buttonPressedBackground = UIManager.getColor( "TabbedPane.buttonPressedBackground" );
super.installUI( c );
installStyle();
}
@Override
@@ -299,12 +329,14 @@ public class FlatTabbedPaneUI
maximumTabWidth = UIManager.getInt( "TabbedPane.maximumTabWidth" );
tabHeight = UIManager.getInt( "TabbedPane.tabHeight" );
tabSelectionHeight = UIManager.getInt( "TabbedPane.tabSelectionHeight" );
cardTabSelectionHeight = UIManager.getInt( "TabbedPane.cardTabSelectionHeight" );
contentSeparatorHeight = UIManager.getInt( "TabbedPane.contentSeparatorHeight" );
showTabSeparators = UIManager.getBoolean( "TabbedPane.showTabSeparators" );
tabSeparatorsFullHeight = UIManager.getBoolean( "TabbedPane.tabSeparatorsFullHeight" );
hasFullBorder = UIManager.getBoolean( "TabbedPane.hasFullBorder" );
tabsOpaque = UIManager.getBoolean( "TabbedPane.tabsOpaque" );
tabType = parseTabType( UIManager.getString( "TabbedPane.tabType" ) );
tabsPopupPolicy = parseTabsPopupPolicy( UIManager.getString( "TabbedPane.tabsPopupPolicy" ) );
scrollButtonsPolicy = parseScrollButtonsPolicy( UIManager.getString( "TabbedPane.scrollButtonsPolicy" ) );
scrollButtonsPlacement = parseScrollButtonsPlacement( UIManager.getString( "TabbedPane.scrollButtonsPlacement" ) );
@@ -313,12 +345,14 @@ public class FlatTabbedPaneUI
tabAlignment = parseAlignment( UIManager.getString( "TabbedPane.tabAlignment" ), CENTER );
tabWidthMode = parseTabWidthMode( UIManager.getString( "TabbedPane.tabWidthMode" ) );
closeIcon = UIManager.getIcon( "TabbedPane.closeIcon" );
closeIconShared = true;
buttonInsets = UIManager.getInsets( "TabbedPane.buttonInsets" );
buttonArc = UIManager.getInt( "TabbedPane.buttonArc" );
Locale l = tabPane.getLocale();
moreTabsButtonToolTipText = UIManager.getString( "TabbedPane.moreTabsButtonToolTipText", l );
tabCloseToolTipText = UIManager.getString( "TabbedPane.tabCloseToolTipText", l );
// scale
textIconGap = scale( textIconGapUnscaled );
@@ -361,6 +395,8 @@ public class FlatTabbedPaneUI
buttonHoverBackground = null;
buttonPressedBackground = null;
oldStyleValues = null;
MigLayoutVisualPadding.uninstall( tabPane );
}
@@ -537,6 +573,13 @@ public class FlatTabbedPaneUI
return handler;
}
@Override
protected FocusListener createFocusListener() {
Handler handler = getHandler();
handler.focusDelegate = super.createFocusListener();
return handler;
}
@Override
protected LayoutManager createLayoutManager() {
if( tabPane.getTabLayoutPolicy() == JTabbedPane.WRAP_TAB_LAYOUT )
@@ -558,6 +601,84 @@ public class FlatTabbedPaneUI
return new FlatScrollableTabButton( direction );
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( tabPane, "TabbedPane" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
// update buttons
for( Component c : tabPane.getComponents() ) {
if( c instanceof FlatTabAreaButton )
((FlatTabAreaButton)c).updateStyle();
}
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
// close icon
if( key.startsWith( "close" ) ) {
if( !(closeIcon instanceof FlatTabbedPaneCloseIcon) )
return new UnknownStyleException( key );
if( closeIconShared ) {
closeIcon = FlatStylingSupport.cloneIcon( closeIcon );
closeIconShared = false;
}
return ((FlatTabbedPaneCloseIcon)closeIcon).applyStyleProperty( key, value );
}
if( value instanceof String ) {
switch( key ) {
case "tabType": value = parseTabType( (String) value ); break;
case "tabsPopupPolicy": value = parseTabsPopupPolicy( (String) value ); break;
case "scrollButtonsPolicy": value = parseScrollButtonsPolicy( (String) value ); break;
case "scrollButtonsPlacement": value = parseScrollButtonsPlacement( (String) value ); break;
case "tabAreaAlignment": value = parseAlignment( (String) value, LEADING ); break;
case "tabAlignment": value = parseAlignment( (String) value, CENTER ); break;
case "tabWidthMode": value = parseTabWidthMode( (String) value ); break;
case "tabIconPlacement": value = parseTabIconPlacement( (String) value ); break;
}
} else {
Object oldValue = null;
switch( key ) {
// BasicTabbedPaneUI
case "tabInsets": oldValue = tabInsets; tabInsets = (Insets) value; return oldValue;
case "tabAreaInsets": oldValue = tabAreaInsets; tabAreaInsets = (Insets) value; return oldValue;
case "textIconGap":
oldValue = textIconGapUnscaled;
textIconGapUnscaled = (int) value;
textIconGap = scale( textIconGapUnscaled );
return oldValue;
}
}
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, tabPane, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
Map<String, Class<?>> infos = new FlatStylingSupport.StyleableInfosMap<>();
infos.put( "tabInsets", Insets.class );
infos.put( "tabAreaInsets", Insets.class );
infos.put( "textIconGap", int.class );
FlatStylingSupport.collectAnnotatedStyleableInfos( this, infos );
if( closeIcon instanceof FlatTabbedPaneCloseIcon )
infos.putAll( ((FlatTabbedPaneCloseIcon)closeIcon).getStyleableInfos() );
return infos;
}
protected void setRolloverTab( int x, int y ) {
setRolloverTab( tabForCoordinate( tabPane, x, y ) );
}
@@ -607,8 +728,25 @@ public class FlatTabbedPaneUI
return;
Rectangle r = getTabBounds( tabPane, tabIndex );
if( r != null )
tabPane.repaint( r );
if( r == null )
return;
// increase size of repaint region to include part of content border
if( contentSeparatorHeight > 0 &&
getTabType() == TAB_TYPE_CARD &&
clientPropertyBoolean( tabPane, TABBED_PANE_SHOW_CONTENT_SEPARATOR, true ) )
{
int sh = scale( contentSeparatorHeight );
switch( tabPane.getTabPlacement() ) {
default:
case TOP: r.height += sh; break;
case BOTTOM: r.height += sh; r.y -= sh; break;
case LEFT: r.width += sh; break;
case RIGHT: r.width += sh; r.x -= sh; break;
}
}
tabPane.repaint( r );
}
private boolean inCalculateEqual;
@@ -639,7 +777,7 @@ public class FlatTabbedPaneUI
Insets tabInsets = getTabInsets( tabPlacement, tabIndex );
tabWidth = icon.getIconWidth() + tabInsets.left + tabInsets.right;
} else {
int iconPlacement = clientPropertyInt( tabPane, TABBED_PANE_TAB_ICON_PLACEMENT, LEADING );
int iconPlacement = clientPropertyInt( tabPane, TABBED_PANE_TAB_ICON_PLACEMENT, tabIconPlacement );
if( (iconPlacement == TOP || iconPlacement == BOTTOM) &&
tabPane.getTabComponentAt( tabIndex ) == null &&
(icon = getIconForTab( tabIndex )) != null )
@@ -682,7 +820,7 @@ public class FlatTabbedPaneUI
int tabHeight;
Icon icon;
int iconPlacement = clientPropertyInt( tabPane, TABBED_PANE_TAB_ICON_PLACEMENT, LEADING );
int iconPlacement = clientPropertyInt( tabPane, TABBED_PANE_TAB_ICON_PLACEMENT, tabIconPlacement );
if( (iconPlacement == TOP || iconPlacement == BOTTOM) &&
tabPane.getTabComponentAt( tabIndex ) == null &&
(icon = getIconForTab( tabIndex )) != null )
@@ -790,7 +928,7 @@ public class FlatTabbedPaneUI
*/
@Override
protected Insets getContentBorderInsets( int tabPlacement ) {
if( hideTabArea() || contentSeparatorHeight == 0 || !clientPropertyBoolean( tabPane, TABBED_PANE_SHOW_CONTENT_SEPARATOR, true ) )
if( hideTabArea() || contentSeparatorHeight == 0 || !clientPropertyBoolean( tabPane, TABBED_PANE_SHOW_CONTENT_SEPARATOR, showContentSeparator ) )
return new Insets( 0, 0, 0, 0 );
boolean hasFullBorder = clientPropertyBoolean( tabPane, TABBED_PANE_HAS_FULL_BORDER, this.hasFullBorder );
@@ -945,16 +1083,21 @@ public class FlatTabbedPaneUI
int x, int y, int w, int h, boolean isSelected )
{
// paint tab background
Color background = getTabBackground( tabPlacement, tabIndex, isSelected );
g.setColor( FlatUIUtils.deriveColor( background, tabPane.getBackground() ) );
g.fillRect( x, y, w, h );
}
/** @since 2 */
protected Color getTabBackground( int tabPlacement, int tabIndex, boolean isSelected ) {
boolean enabled = tabPane.isEnabled();
Color background = enabled && tabPane.isEnabledAt( tabIndex ) && getRolloverTab() == tabIndex
return enabled && tabPane.isEnabledAt( tabIndex ) && getRolloverTab() == tabIndex
? hoverColor
: (enabled && isSelected && FlatUIUtils.isPermanentFocusOwner( tabPane )
? focusColor
: (selectedBackground != null && enabled && isSelected
? selectedBackground
: tabPane.getBackgroundAt( tabIndex )));
g.setColor( FlatUIUtils.deriveColor( background, tabPane.getBackground() ) );
g.fillRect( x, y, w, h );
}
@Override
@@ -964,7 +1107,62 @@ public class FlatTabbedPaneUI
// paint tab separators
if( clientPropertyBoolean( tabPane, TABBED_PANE_SHOW_TAB_SEPARATORS, showTabSeparators ) &&
!isLastInRun( tabIndex ) )
paintTabSeparator( g, tabPlacement, x, y, w, h );
{
if( getTabType() == TAB_TYPE_CARD ) {
// some separators need to be omitted if selected tab is painted as card
int selectedIndex = tabPane.getSelectedIndex();
if( tabIndex != selectedIndex - 1 && tabIndex != selectedIndex )
paintTabSeparator( g, tabPlacement, x, y, w, h );
} else
paintTabSeparator( g, tabPlacement, x, y, w, h );
}
// paint active tab border
if( isSelected && getTabType() == TAB_TYPE_CARD )
paintCardTabBorder( g, tabPlacement, tabIndex, x, y, w, h );
}
/** @since 2 */
protected void paintCardTabBorder( Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h ) {
Graphics2D g2 = (Graphics2D) g;
float borderWidth = scale( (float) contentSeparatorHeight );
g.setColor( (tabSeparatorColor != null) ? tabSeparatorColor : contentAreaColor );
switch( tabPlacement ) {
default:
case TOP:
case BOTTOM:
// paint left and right tab border
g2.fill( new Rectangle2D.Float( x, y, borderWidth, h ) );
g2.fill( new Rectangle2D.Float( x + w - borderWidth, y, borderWidth, h ) );
break;
case LEFT:
case RIGHT:
// paint top and bottom tab border
g2.fill( new Rectangle2D.Float( x, y, w, borderWidth ) );
g2.fill( new Rectangle2D.Float( x, y + h - borderWidth, w, borderWidth ) );
break;
}
if( cardTabSelectionHeight <= 0 ) {
// if there is no tab selection indicator, paint a top border as well
switch( tabPlacement ) {
default:
case TOP:
g2.fill( new Rectangle2D.Float( x, y, w, borderWidth ) );
break;
case BOTTOM:
g2.fill( new Rectangle2D.Float( x, y + h - borderWidth, w, borderWidth ) );
break;
case LEFT:
g2.fill( new Rectangle2D.Float( x, y, borderWidth, h ) );
break;
case RIGHT:
g2.fill( new Rectangle2D.Float( x + w - borderWidth, y, borderWidth, h ) );
break;
}
}
}
protected void paintTabCloseButton( Graphics g, int tabIndex, int x, int y, int w, int h ) {
@@ -1010,26 +1208,30 @@ public class FlatTabbedPaneUI
g.setColor( tabPane.isEnabled() ? underlineColor : disabledUnderlineColor );
// paint underline selection
boolean atBottom = (getTabType() != TAB_TYPE_CARD);
Insets contentInsets = getContentBorderInsets( tabPlacement );
int tabSelectionHeight = scale( this.tabSelectionHeight );
int tabSelectionHeight = scale( atBottom ? this.tabSelectionHeight : cardTabSelectionHeight );
int sx, sy;
switch( tabPlacement ) {
case TOP:
default:
int sy = y + h + contentInsets.top - tabSelectionHeight;
sy = atBottom ? (y + h + contentInsets.top - tabSelectionHeight) : y;
g.fillRect( x, sy, w, tabSelectionHeight );
break;
case BOTTOM:
g.fillRect( x, y - contentInsets.bottom, w, tabSelectionHeight );
sy = atBottom ? (y - contentInsets.bottom) : (y + h - tabSelectionHeight);
g.fillRect( x, sy, w, tabSelectionHeight );
break;
case LEFT:
int sx = x + w + contentInsets.left - tabSelectionHeight;
sx = atBottom ? (x + w + contentInsets.left - tabSelectionHeight) : x;
g.fillRect( sx, y, tabSelectionHeight, h );
break;
case RIGHT:
g.fillRect( x - contentInsets.right, y, tabSelectionHeight, h );
sx = atBottom ? (x - contentInsets.right) : (x + w - tabSelectionHeight);
g.fillRect( sx, y, tabSelectionHeight, h );
break;
}
}
@@ -1041,12 +1243,13 @@ public class FlatTabbedPaneUI
* - paint full border (if enabled)
* - not invoking paintContentBorder*Edge() methods
* - repaint selection
* - painting active tab border style
*/
@Override
protected void paintContentBorder( Graphics g, int tabPlacement, int selectedIndex ) {
if( tabPane.getTabCount() <= 0 ||
contentSeparatorHeight == 0 ||
!clientPropertyBoolean( tabPane, TABBED_PANE_SHOW_CONTENT_SEPARATOR, true ) )
!clientPropertyBoolean( tabPane, TABBED_PANE_SHOW_CONTENT_SEPARATOR, showContentSeparator ) )
return;
Insets insets = tabPane.getInsets();
@@ -1089,12 +1292,49 @@ public class FlatTabbedPaneUI
Insets ci = new Insets( 0, 0, 0, 0 );
rotateInsets( hasFullBorder ? new Insets( sh, sh, sh, sh ) : new Insets( sh, 0, 0, 0 ), ci, tabPlacement );
// paint content separator or full border
g.setColor( contentAreaColor );
// create path for content separator or full border
Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD );
path.append( new Rectangle2D.Float( x, y, w, h ), false );
path.append( new Rectangle2D.Float( x + (ci.left / 100f), y + (ci.top / 100f),
w - (ci.left / 100f) - (ci.right / 100f), h - (ci.top / 100f) - (ci.bottom / 100f) ), false );
// add gap for selected tab to path
if( getTabType() == TAB_TYPE_CARD ) {
float csh = scale( (float) contentSeparatorHeight );
Rectangle tabRect = getTabBounds( tabPane, selectedIndex );
Rectangle2D.Float innerTabRect = new Rectangle2D.Float( tabRect.x + csh, tabRect.y + csh,
tabRect.width - (csh * 2), tabRect.height - (csh * 2) );
// Ensure that the separator outside the tabViewport is present (doesn't get cutoff by the active tab)
// If left unsolved the active tab is "visible" in the separator (the gap) even when outside the viewport
if( tabViewport != null )
Rectangle2D.intersect( tabViewport.getBounds(), innerTabRect, innerTabRect );
Rectangle2D.Float gap = null;
if( isHorizontalTabPlacement() ) {
if( innerTabRect.width > 0 ) {
float y2 = (tabPlacement == TOP) ? y : y + h - csh;
gap = new Rectangle2D.Float( innerTabRect.x, y2, innerTabRect.width, csh );
}
} else {
if( innerTabRect.height > 0 ) {
float x2 = (tabPlacement == LEFT) ? x : x + w - csh;
gap = new Rectangle2D.Float( x2, innerTabRect.y, csh, innerTabRect.height );
}
}
if( gap != null ) {
path.append( gap, false );
// fill gap in case that the tab is colored (e.g. focused or hover)
g.setColor( getTabBackground( tabPlacement, selectedIndex, true ) );
((Graphics2D)g).fill( gap );
}
}
// paint content separator or full border
g.setColor( contentAreaColor );
((Graphics2D)g).fill( path );
// repaint selection in scroll-tab-layout because it may be painted before
@@ -1137,7 +1377,7 @@ public class FlatTabbedPaneUI
// icon placement
int verticalTextPosition;
int horizontalTextPosition;
switch( clientPropertyInt( tabPane, TABBED_PANE_TAB_ICON_PLACEMENT, LEADING ) ) {
switch( clientPropertyInt( tabPane, TABBED_PANE_TAB_ICON_PLACEMENT, tabIconPlacement ) ) {
default:
case LEADING: verticalTextPosition = CENTER; horizontalTextPosition = TRAILING; break;
case TRAILING: verticalTextPosition = CENTER; horizontalTextPosition = LEADING; break;
@@ -1218,8 +1458,11 @@ public class FlatTabbedPaneUI
}
protected boolean isTabClosable( int tabIndex ) {
if( tabIndex < 0 )
return false;
Object value = getTabClientProperty( tabIndex, TABBED_PANE_TAB_CLOSABLE );
return (value instanceof Boolean) ? (boolean) value : false;
return (value instanceof Boolean) ? (boolean) value : tabClosable;
}
@SuppressWarnings( { "unchecked" } )
@@ -1293,7 +1536,16 @@ public class FlatTabbedPaneUI
return tabPane.getTabCount() == 1 &&
leadingComponent == null &&
trailingComponent == null &&
clientPropertyBoolean( tabPane, TABBED_PANE_HIDE_TAB_AREA_WITH_ONE_TAB, false );
clientPropertyBoolean( tabPane, TABBED_PANE_HIDE_TAB_AREA_WITH_ONE_TAB, hideTabAreaWithOneTab );
}
/** @since 2 */
protected int getTabType() {
Object value = tabPane.getClientProperty( TABBED_PANE_TAB_TYPE );
return (value instanceof String)
? parseTabType( (String) value )
: tabType;
}
protected int getTabsPopupPolicy() {
@@ -1348,6 +1600,18 @@ public class FlatTabbedPaneUI
: tabWidthMode;
}
/** @since 2 */
protected static int parseTabType( String str ) {
if( str == null )
return TAB_TYPE_UNDERLINED;
switch( str ) {
default:
case TABBED_PANE_TAB_TYPE_UNDERLINED: return TAB_TYPE_UNDERLINED;
case TABBED_PANE_TAB_TYPE_CARD: return TAB_TYPE_CARD;
}
}
protected static int parseTabsPopupPolicy( String str ) {
if( str == null )
return AS_NEEDED;
@@ -1407,6 +1671,19 @@ public class FlatTabbedPaneUI
}
}
protected static int parseTabIconPlacement( String str ) {
if( str == null )
return LEADING;
switch( str ) {
default:
case "leading": return LEADING;
case "trailing": return TRAILING;
case "top": return TOP;
case "bottom": return BOTTOM;
}
}
private void runWithOriginalLayoutManager( Runnable runnable ) {
LayoutManager layout = tabPane.getLayout();
if( layout instanceof FlatTabbedPaneScrollLayout ) {
@@ -1567,6 +1844,12 @@ public class FlatTabbedPaneUI
setArrowWidth( 10 );
}
protected void updateStyle() {
updateStyle( arrowType,
FlatTabbedPaneUI.this.foreground, FlatTabbedPaneUI.this.disabledForeground,
null, buttonHoverBackground, null, buttonPressedBackground );
}
@Override
protected Color deriveBackground( Color background ) {
return FlatUIUtils.deriveColor( background, tabPane.getBackground() );
@@ -2142,11 +2425,12 @@ public class FlatTabbedPaneUI
private class Handler
implements MouseListener, MouseMotionListener, PropertyChangeListener,
ChangeListener, ComponentListener, ContainerListener
ChangeListener, ComponentListener, ContainerListener, FocusListener
{
MouseListener mouseDelegate;
PropertyChangeListener propertyChangeDelegate;
ChangeListener changeDelegate;
FocusListener focusDelegate;
private final PropertyChangeListener contentListener = this::contentPropertyChange;
@@ -2252,6 +2536,8 @@ public class FlatTabbedPaneUI
// update tooltip
if( tabIndex >= 0 && hitClose ) {
Object closeTip = getTabClientProperty( tabIndex, TABBED_PANE_TAB_CLOSE_TOOLTIPTEXT );
if( closeTip == null )
closeTip = tabCloseToolTipText;
if( closeTip instanceof String )
setCloseToolTip( tabIndex, (String) closeTip );
else
@@ -2314,6 +2600,10 @@ public class FlatTabbedPaneUI
break;
case TABBED_PANE_SHOW_TAB_SEPARATORS:
case TABBED_PANE_TAB_TYPE:
tabPane.repaint();
break;
case TABBED_PANE_SHOW_CONTENT_SEPARATOR:
case TABBED_PANE_HAS_FULL_BORDER:
case TABBED_PANE_HIDE_TAB_AREA_WITH_ONE_TAB:
@@ -2351,6 +2641,13 @@ public class FlatTabbedPaneUI
tabPane.repaint();
ensureSelectedTabIsVisibleLater();
break;
case STYLE:
case STYLE_CLASS:
installStyle();
tabPane.revalidate();
tabPane.repaint();
break;
}
}
@@ -2405,6 +2702,20 @@ public class FlatTabbedPaneUI
if( !(c instanceof UIResource) )
c.removePropertyChangeListener( contentListener );
}
//---- interface FocusListener ----
@Override
public void focusGained( FocusEvent e ) {
focusDelegate.focusGained( e );
repaintTab( tabPane.getSelectedIndex() );
}
@Override
public void focusLost( FocusEvent e ) {
focusDelegate.focusLost( e );
repaintTab( tabPane.getSelectedIndex() );
}
}
//---- class FlatTabbedPaneLayout -----------------------------------------

View File

@@ -16,11 +16,15 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Insets;
import java.util.function.Function;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.TableUI;
/**
* Cell border for {@link javax.swing.table.DefaultTableCellRenderer}
@@ -33,12 +37,54 @@ import javax.swing.UIManager;
public class FlatTableCellBorder
extends FlatLineBorder
{
final boolean showCellFocusIndicator = UIManager.getBoolean( "Table.showCellFocusIndicator" );
/** @since 2 */ protected boolean showCellFocusIndicator = UIManager.getBoolean( "Table.showCellFocusIndicator" );
private Component c;
protected FlatTableCellBorder() {
super( UIManager.getInsets( "Table.cellMargins" ), UIManager.getColor( "Table.cellFocusColor" ) );
}
@Override
public Insets getBorderInsets( Component c, Insets insets ) {
Insets m = getStyleFromTableUI( c, ui -> ui.cellMargins );
if( m != null )
return scaleInsets( c, insets, m.top, m.left, m.bottom, m.right );
return super.getBorderInsets( c, insets );
}
@Override
public Color getLineColor() {
if( c != null ) {
Color color = getStyleFromTableUI( c, ui -> ui.cellFocusColor );
if( color != null )
return color;
}
return super.getLineColor();
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
this.c = c;
super.paintBorder( c, g, x, y, width, height );
this.c = null;
}
/**
* Because this borders are always shared for all tables,
* get border specific style from FlatTableUI.
*/
static <T> T getStyleFromTableUI( Component c, Function<FlatTableUI, T> f ) {
JTable table = (JTable) SwingUtilities.getAncestorOfClass( JTable.class, c );
if( table != null ) {
TableUI ui = table.getUI();
if( ui instanceof FlatTableUI )
return f.apply( (FlatTableUI) ui );
}
return null;
}
//---- class Default ------------------------------------------------------
/**
@@ -74,6 +120,9 @@ public class FlatTableCellBorder
{
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
Boolean b = getStyleFromTableUI( c, ui -> ui.showCellFocusIndicator );
boolean showCellFocusIndicator = (b != null) ? b : this.showCellFocusIndicator;
if( !showCellFocusIndicator ) {
JTable table = (JTable) SwingUtilities.getAncestorOfClass( JTable.class, c );
if( table != null && !isSelectionEditable( table ) )

View File

@@ -21,6 +21,7 @@ import java.awt.Component;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.geom.Rectangle2D;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
@@ -51,12 +52,30 @@ public class FlatTableHeaderBorder
super( UIManager.getInsets( "TableHeader.cellMargins" ) );
}
@Override
public Insets getBorderInsets( Component c, Insets insets ) {
JTableHeader header = (JTableHeader) SwingUtilities.getAncestorOfClass( JTableHeader.class, c );
if( header != null ) {
if( header.getUI() instanceof FlatTableHeaderUI ) {
FlatTableHeaderUI ui = (FlatTableHeaderUI) header.getUI();
if( ui.cellMargins != null ) {
Insets m = ui.cellMargins;
return scaleInsets( c, insets, m.top, m.left, m.bottom, m.right );
}
}
}
return super.getBorderInsets( c, insets );
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
JTableHeader header = (JTableHeader) SwingUtilities.getAncestorOfClass( JTableHeader.class, c );
boolean leftToRight = (header != null ? header : c).getComponentOrientation().isLeftToRight();
boolean paintLeft = !leftToRight;
boolean paintRight = leftToRight;
Color separatorColor = this.separatorColor;
Color bottomSeparatorColor = this.bottomSeparatorColor;
if( header != null ) {
int hx = SwingUtilities.convertPoint( c, x, y, header ).x;
@@ -68,6 +87,16 @@ public class FlatTableHeaderBorder
if( hx + width >= header.getWidth() && leftToRight && hideTrailingVerticalLine( header ) )
paintRight = false;
}
// Because this border is always shared for all table headers,
// get border specific style from FlatTableHeaderUI.
if( header.getUI() instanceof FlatTableHeaderUI ) {
FlatTableHeaderUI ui = (FlatTableHeaderUI) header.getUI();
if( ui.separatorColor != null )
separatorColor = ui.separatorColor;
if( ui.bottomSeparatorColor != null )
bottomSeparatorColor = ui.bottomSeparatorColor;
}
}
float lineWidth = UIScale.scale( 1f );
@@ -110,6 +139,12 @@ public class FlatTableHeaderBorder
}
protected boolean hideTrailingVerticalLine( JTableHeader header ) {
if( header.getUI() instanceof FlatTableHeaderUI ) {
FlatTableHeaderUI ui = (FlatTableHeaderUI) header.getUI();
if( ui.showTrailingVerticalLine != null )
return !ui.showTrailingVerticalLine;
}
if( showTrailingVerticalLine )
return false;

View File

@@ -26,7 +26,8 @@ import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.geom.Rectangle2D;
import java.util.Objects;
import java.beans.PropertyChangeListener;
import java.util.Map;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JLabel;
@@ -40,6 +41,9 @@ import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTableHeaderUI;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumnModel;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale;
/**
@@ -64,32 +68,52 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault TableHeader.bottomSeparatorColor Color
* @uiDefault TableHeader.showTrailingVerticalLine boolean
*
* <!-- FlatAscendingSortIcon and FlatDescendingSortIcon -->
*
* @uiDefault Component.arrowType String chevron (default) or triangle
* @uiDefault Table.sortIconColor Color
*
* @author Karl Tauber
*/
public class FlatTableHeaderUI
extends BasicTableHeaderUI
implements StyleableUI
{
protected Color bottomSeparatorColor;
protected int height;
protected int sortIconPosition;
@Styleable protected Color bottomSeparatorColor;
@Styleable protected int height;
@Styleable(type=String.class) protected int sortIconPosition;
// for FlatTableHeaderBorder
/** @since 2 */ @Styleable protected Insets cellMargins;
/** @since 2 */ @Styleable protected Color separatorColor;
/** @since 2 */ @Styleable protected Boolean showTrailingVerticalLine;
// for FlatAscendingSortIcon and FlatDescendingSortIcon
// (needs to be public because icon classes are in another package)
/** @since 2 */ @Styleable public String arrowType;
/** @since 2 */ @Styleable public Color sortIconColor;
private PropertyChangeListener propertyChangeListener;
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
return new FlatTableHeaderUI();
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installStyle();
}
@Override
protected void installDefaults() {
super.installDefaults();
bottomSeparatorColor = UIManager.getColor( "TableHeader.bottomSeparatorColor" );
height = UIManager.getInt( "TableHeader.height" );
switch( Objects.toString( UIManager.getString( "TableHeader.sortIconPosition" ), "right" ) ) {
default:
case "right": sortIconPosition = SwingConstants.RIGHT; break;
case "left": sortIconPosition = SwingConstants.LEFT; break;
case "top": sortIconPosition = SwingConstants.TOP; break;
case "bottom": sortIconPosition = SwingConstants.BOTTOM; break;
}
sortIconPosition = parseSortIconPosition( UIManager.getString( "TableHeader.sortIconPosition" ) );
}
@Override
@@ -97,6 +121,65 @@ public class FlatTableHeaderUI
super.uninstallDefaults();
bottomSeparatorColor = null;
oldStyleValues = null;
}
@Override
protected void installListeners() {
super.installListeners();
propertyChangeListener = FlatStylingSupport.createPropertyChangeListener( header, this::installStyle, null );
header.addPropertyChangeListener( propertyChangeListener );
}
@Override
protected void uninstallListeners() {
super.uninstallListeners();
header.removePropertyChangeListener( propertyChangeListener );
propertyChangeListener = null;
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( header, "TableHeader" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
if( key.equals( "sortIconPosition" ) && value instanceof String )
value = parseSortIconPosition( (String) value );
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, header, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
private static int parseSortIconPosition( String str ) {
if( str == null )
str = "";
switch( str ) {
default:
case "right": return SwingConstants.RIGHT;
case "left": return SwingConstants.LEFT;
case "top": return SwingConstants.TOP;
case "bottom": return SwingConstants.BOTTOM;
}
}
@Override
@@ -265,9 +348,7 @@ public class FlatTableHeaderUI
//---- class FlatMouseInputHandler ----------------------------------------
/**
* @since 1.6
*/
/** @since 1.6 */
protected class FlatMouseInputHandler
extends MouseInputHandler
{

View File

@@ -22,10 +22,12 @@ import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeListener;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
@@ -36,7 +38,10 @@ import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicTableUI;
import javax.swing.table.JTableHeader;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.Graphics2DProxy;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale;
@@ -90,27 +95,41 @@ import com.formdev.flatlaf.util.UIScale;
*/
public class FlatTableUI
extends BasicTableUI
implements StyleableUI
{
protected boolean showHorizontalLines;
protected boolean showVerticalLines;
/** @since 1.6 */ protected boolean showTrailingVerticalLine;
/** @since 1.6 */ @Styleable protected boolean showTrailingVerticalLine;
protected Dimension intercellSpacing;
protected Color selectionBackground;
protected Color selectionForeground;
protected Color selectionInactiveBackground;
protected Color selectionInactiveForeground;
@Styleable protected Color selectionBackground;
@Styleable protected Color selectionForeground;
@Styleable protected Color selectionInactiveBackground;
@Styleable protected Color selectionInactiveForeground;
// for FlatTableCellBorder
/** @since 2 */ @Styleable protected Insets cellMargins;
/** @since 2 */ @Styleable protected Color cellFocusColor;
/** @since 2 */ @Styleable protected Boolean showCellFocusIndicator;
private boolean oldShowHorizontalLines;
private boolean oldShowVerticalLines;
private Dimension oldIntercellSpacing;
private PropertyChangeListener propertyChangeListener;
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
return new FlatTableUI();
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installStyle();
}
@Override
protected void installDefaults() {
super.installDefaults();
@@ -155,6 +174,8 @@ public class FlatTableUI
selectionInactiveBackground = null;
selectionInactiveForeground = null;
oldStyleValues = null;
// restore old show horizontal/vertical lines (if not modified)
if( !showHorizontalLines && oldShowHorizontalLines && !table.getShowHorizontalLines() )
table.setShowHorizontalLines( true );
@@ -171,8 +192,18 @@ public class FlatTableUI
super.installListeners();
propertyChangeListener = e -> {
if( FlatClientProperties.COMPONENT_FOCUS_OWNER.equals( e.getPropertyName() ) )
toggleSelectionColors();
switch( e.getPropertyName() ) {
case FlatClientProperties.COMPONENT_FOCUS_OWNER:
toggleSelectionColors();
break;
case FlatClientProperties.STYLE:
case FlatClientProperties.STYLE_CLASS:
installStyle();
table.revalidate();
table.repaint();
break;
}
};
table.addPropertyChangeListener( propertyChangeListener );
}
@@ -206,6 +237,54 @@ public class FlatTableUI
};
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( table, "Table" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
Color oldSelectionBackground = selectionBackground;
Color oldSelectionForeground = selectionForeground;
Color oldSelectionInactiveBackground = selectionInactiveBackground;
Color oldSelectionInactiveForeground = selectionInactiveForeground;
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
// update selection background
if( selectionBackground != oldSelectionBackground ) {
Color selBg = table.getSelectionBackground();
if( selBg == oldSelectionBackground )
table.setSelectionBackground( selectionBackground );
else if( selBg == oldSelectionInactiveBackground )
table.setSelectionBackground( selectionInactiveBackground );
}
// update selection foreground
if( selectionForeground != oldSelectionForeground ) {
Color selFg = table.getSelectionForeground();
if( selFg == oldSelectionForeground )
table.setSelectionForeground( selectionForeground );
else if( selFg == oldSelectionInactiveForeground )
table.setSelectionForeground( selectionInactiveForeground );
}
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, table, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/**
* Toggle selection colors from focused to inactive and vice versa.
*

View File

@@ -23,14 +23,17 @@ import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.event.FocusListener;
import java.beans.PropertyChangeEvent;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.JTextArea;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTextAreaUI;
import javax.swing.text.JTextComponent;
import javax.swing.text.Caret;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JTextArea}.
@@ -60,17 +63,22 @@ import com.formdev.flatlaf.util.HiDPIUtils;
*/
public class FlatTextAreaUI
extends BasicTextAreaUI
implements StyleableUI
{
protected int minimumWidth;
@Styleable protected int minimumWidth;
protected boolean isIntelliJTheme;
protected Color background;
protected Color disabledBackground;
protected Color inactiveBackground;
protected Color focusedBackground;
private Color background;
@Styleable protected Color disabledBackground;
@Styleable protected Color inactiveBackground;
@Styleable protected Color focusedBackground;
private Color oldDisabledBackground;
private Color oldInactiveBackground;
private Insets defaultMargin;
private FocusListener focusListener;
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
return new FlatTextAreaUI();
@@ -80,7 +88,7 @@ public class FlatTextAreaUI
public void installUI( JComponent c ) {
super.installUI( c );
updateBackground();
installStyle();
}
@Override
@@ -105,6 +113,11 @@ public class FlatTextAreaUI
disabledBackground = null;
inactiveBackground = null;
focusedBackground = null;
oldDisabledBackground = null;
oldInactiveBackground = null;
oldStyleValues = null;
}
@Override
@@ -125,39 +138,55 @@ public class FlatTextAreaUI
}
@Override
protected void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e );
FlatEditorPaneUI.propertyChange( getComponent(), e );
protected Caret createCaret() {
return new FlatCaret( null, false );
}
switch( e.getPropertyName() ) {
case "editable":
case "enabled":
updateBackground();
break;
@Override
protected void propertyChange( PropertyChangeEvent e ) {
// invoke updateBackground() before super.propertyChange()
String propertyName = e.getPropertyName();
if( "editable".equals( propertyName ) || "enabled".equals( propertyName ) )
updateBackground();
super.propertyChange( e );
FlatEditorPaneUI.propertyChange( getComponent(), e, this::installStyle );
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( getComponent(), "TextArea" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
oldDisabledBackground = disabledBackground;
oldInactiveBackground = inactiveBackground;
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
updateBackground();
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, getComponent(), key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
private void updateBackground() {
JTextComponent c = getComponent();
Color background = c.getBackground();
if( !(background instanceof UIResource) )
return;
// do not update background if it currently has a unknown color (assigned from outside)
if( background != this.background &&
background != disabledBackground &&
background != inactiveBackground )
return;
Color newBackground = !c.isEnabled()
? disabledBackground
: (!c.isEditable()
? inactiveBackground
: this.background);
if( newBackground != background )
c.setBackground( newBackground );
FlatTextFieldUI.updateBackground( getComponent(), background,
disabledBackground, inactiveBackground,
oldDisabledBackground, oldInactiveBackground );
}
@Override

View File

@@ -18,6 +18,7 @@ package com.formdev.flatlaf.ui;
import java.awt.Component;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
/**
* Border for various text components (e.g. {@link javax.swing.JTextField}).
@@ -29,7 +30,10 @@ import javax.swing.UIManager;
public class FlatTextBorder
extends FlatBorder
{
protected final int arc = UIManager.getInt( "TextComponent.arc" );
@Styleable protected int arc = UIManager.getInt( "TextComponent.arc" );
// only used via styling (not in UI defaults, but has likewise client properties)
/** @since 2 */ @Styleable protected Boolean roundRect;
@Override
protected int getArc( Component c ) {
@@ -37,6 +41,8 @@ public class FlatTextBorder
return 0;
Boolean roundRect = FlatUIUtils.isRoundRect( c );
if( roundRect == null )
roundRect = this.roundRect;
return roundRect != null ? (roundRect ? Short.MAX_VALUE : 0) : arc;
}
}

View File

@@ -16,33 +16,49 @@
package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.FlatClientProperties.*;
import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.LayoutManager2;
import java.awt.Rectangle;
import java.awt.event.FocusListener;
import java.beans.PropertyChangeEvent;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.LookAndFeel;
import javax.swing.UIManager;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTextFieldUI;
import javax.swing.text.Caret;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.JavaCompatibility;
import com.formdev.flatlaf.util.UIScale;
import com.formdev.flatlaf.util.LoggingFacade;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JTextField}.
@@ -68,6 +84,7 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault Component.isIntelliJTheme boolean
* @uiDefault TextField.placeholderForeground Color
* @uiDefault TextField.focusedBackground Color optional
* @uiDefault TextField.iconTextGap int optional, default is 4
* @uiDefault TextComponent.selectAllOnFocusPolicy String never, once (default) or always
* @uiDefault TextComponent.selectAllOnMouseClick boolean
*
@@ -75,20 +92,66 @@ import com.formdev.flatlaf.util.UIScale;
*/
public class FlatTextFieldUI
extends BasicTextFieldUI
implements StyleableUI
{
protected int minimumWidth;
@Styleable protected int minimumWidth;
protected boolean isIntelliJTheme;
protected Color placeholderForeground;
protected Color focusedBackground;
private Color background;
@Styleable protected Color disabledBackground;
@Styleable protected Color inactiveBackground;
@Styleable protected Color placeholderForeground;
@Styleable protected Color focusedBackground;
/** @since 2 */ @Styleable protected int iconTextGap;
/** @since 2 */ @Styleable protected Icon leadingIcon;
/** @since 2 */ @Styleable protected Icon trailingIcon;
/** @since 2 */ protected JComponent leadingComponent;
/** @since 2 */ protected JComponent trailingComponent;
/** @since 2 */ protected JComponent clearButton;
// only used via styling (not in UI defaults, but has likewise client properties)
/** @since 2 */ @Styleable protected boolean showClearButton;
private Color oldDisabledBackground;
private Color oldInactiveBackground;
private Insets defaultMargin;
private FocusListener focusListener;
private DocumentListener documentListener;
private Map<String, Object> oldStyleValues;
private AtomicBoolean borderShared;
public static ComponentUI createUI( JComponent c ) {
return new FlatTextFieldUI();
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
leadingIcon = clientProperty( c, TEXT_FIELD_LEADING_ICON, null, Icon.class );
trailingIcon = clientProperty( c, TEXT_FIELD_TRAILING_ICON, null, Icon.class );
installLeadingComponent();
installTrailingComponent();
installClearButton();
installStyle();
}
@Override
public void uninstallUI( JComponent c ) {
uninstallLeadingComponent();
uninstallTrailingComponent();
uninstallClearButton();
super.uninstallUI( c );
leadingIcon = null;
trailingIcon = null;
}
@Override
protected void installDefaults() {
super.installDefaults();
@@ -96,8 +159,12 @@ public class FlatTextFieldUI
String prefix = getPropertyPrefix();
minimumWidth = UIManager.getInt( "Component.minimumWidth" );
isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" );
background = UIManager.getColor( prefix + ".background" );
disabledBackground = UIManager.getColor( prefix + ".disabledBackground" );
inactiveBackground = UIManager.getColor( prefix + ".inactiveBackground" );
placeholderForeground = UIManager.getColor( prefix + ".placeholderForeground" );
focusedBackground = UIManager.getColor( prefix + ".focusedBackground" );
iconTextGap = FlatUIUtils.getUIInt( prefix + ".iconTextGap", 4 );
defaultMargin = UIManager.getInsets( prefix + ".margin" );
@@ -110,9 +177,18 @@ public class FlatTextFieldUI
protected void uninstallDefaults() {
super.uninstallDefaults();
background = null;
disabledBackground = null;
inactiveBackground = null;
placeholderForeground = null;
focusedBackground = null;
oldDisabledBackground = null;
oldInactiveBackground = null;
oldStyleValues = null;
borderShared = null;
MigLayoutVisualPadding.uninstall( getComponent() );
}
@@ -131,6 +207,11 @@ public class FlatTextFieldUI
getComponent().removeFocusListener( focusListener );
focusListener = null;
if( documentListener != null ) {
getComponent().getDocument().removeDocumentListener( documentListener );
documentListener = null;
}
}
@Override
@@ -141,29 +222,184 @@ public class FlatTextFieldUI
@Override
protected void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e );
propertyChange( getComponent(), e );
}
String propertyName = e.getPropertyName();
if( "editable".equals( propertyName ) || "enabled".equals( propertyName ) )
updateBackground();
else
super.propertyChange( e );
static void propertyChange( JTextComponent c, PropertyChangeEvent e ) {
JTextComponent c = getComponent();
switch( e.getPropertyName() ) {
case FlatClientProperties.PLACEHOLDER_TEXT:
case FlatClientProperties.COMPONENT_ROUND_RECT:
case FlatClientProperties.TEXT_FIELD_PADDING:
case PLACEHOLDER_TEXT:
case COMPONENT_ROUND_RECT:
case TEXT_FIELD_PADDING:
c.repaint();
break;
case FlatClientProperties.MINIMUM_WIDTH:
case MINIMUM_WIDTH:
c.revalidate();
break;
case STYLE:
case STYLE_CLASS:
installStyle();
c.revalidate();
c.repaint();
break;
case TEXT_FIELD_LEADING_ICON:
leadingIcon = (e.getNewValue() instanceof Icon) ? (Icon) e.getNewValue() : null;
c.repaint();
break;
case TEXT_FIELD_TRAILING_ICON:
trailingIcon = (e.getNewValue() instanceof Icon) ? (Icon) e.getNewValue() : null;
c.repaint();
break;
case TEXT_FIELD_LEADING_COMPONENT:
uninstallLeadingComponent();
installLeadingComponent();
c.revalidate();
c.repaint();
break;
case TEXT_FIELD_TRAILING_COMPONENT:
uninstallTrailingComponent();
installTrailingComponent();
c.revalidate();
c.repaint();
break;
case TEXT_FIELD_SHOW_CLEAR_BUTTON:
uninstallClearButton();
installClearButton();
c.revalidate();
c.repaint();
break;
case "enabled":
case "editable":
updateClearButton();
break;
case "document":
if( documentListener != null ) {
if( e.getOldValue() instanceof Document )
((Document)e.getOldValue()).removeDocumentListener( documentListener );
if( e.getNewValue() instanceof Document )
((Document)e.getNewValue()).addDocumentListener( documentListener );
updateClearButton();
}
break;
}
}
/** @since 2 */
protected void installDocumentListener() {
if( documentListener != null )
return;
documentListener = new FlatDocumentListener();
getComponent().getDocument().addDocumentListener( documentListener );
}
/** @since 2 */
protected void documentChanged( DocumentEvent e ) {
if( clearButton != null )
updateClearButton();
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( getComponent(), getStyleType() ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
String getStyleType() {
return "TextField";
}
/** @since 2 */
protected void applyStyle( Object style ) {
oldDisabledBackground = disabledBackground;
oldInactiveBackground = inactiveBackground;
boolean oldShowClearButton = showClearButton;
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
updateBackground();
if( showClearButton != oldShowClearButton ) {
uninstallClearButton();
installClearButton();
}
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
if( borderShared == null )
borderShared = new AtomicBoolean( true );
return FlatStylingSupport.applyToAnnotatedObjectOrBorder( this, key, value, getComponent(), borderShared );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatStylingSupport.getAnnotatedStyleableInfos( this, getComponent().getBorder() );
}
private void updateBackground() {
updateBackground( getComponent(), background,
disabledBackground, inactiveBackground,
oldDisabledBackground, oldInactiveBackground );
}
// same functionality as BasicTextUI.updateBackground()
static void updateBackground( JTextComponent c, Color background,
Color disabledBackground, Color inactiveBackground,
Color oldDisabledBackground, Color oldInactiveBackground )
{
Color oldBackground = c.getBackground();
if( !(oldBackground instanceof UIResource) )
return;
// do not update background if it currently has a unknown color (assigned from outside)
if( oldBackground != background &&
oldBackground != disabledBackground &&
oldBackground != inactiveBackground &&
oldBackground != oldDisabledBackground &&
oldBackground != oldInactiveBackground )
return;
Color newBackground = !c.isEnabled()
? disabledBackground
: (!c.isEditable()
? inactiveBackground
: background);
if( newBackground != oldBackground )
c.setBackground( newBackground );
}
@Override
protected void paintSafely( Graphics g ) {
paintBackground( g, getComponent(), isIntelliJTheme, focusedBackground );
paintPlaceholder( g );
if( hasLeadingIcon() || hasTrailingIcon() )
paintIcons( g, new Rectangle( getIconsRect() ) );
/*debug
Rectangle r = getVisibleEditorRect();
g.setColor( Color.red );
g.drawRect( r.x, r.y, r.width - 1, r.height - 1 );
debug*/
super.paintSafely( HiDPIUtils.createGraphicsTextYCorrection( (Graphics2D) g ) );
}
@@ -177,7 +413,7 @@ public class FlatTextFieldUI
// - not opaque and
// - border is not a flat border and
// - opaque was explicitly set (to false)
// (same behaviour as in AquaTextFieldUI)
// (same behavior as in AquaTextFieldUI)
if( !c.isOpaque() && FlatUIUtils.getOutsideFlatBorder( c ) == null && FlatUIUtils.hasOpaqueBeenExplicitlySet( c ) )
return;
@@ -230,15 +466,15 @@ public class FlatTextFieldUI
JComponent jc = (parent instanceof JComboBox) ? (JComboBox<?>) parent : c;
// get placeholder text
Object placeholder = jc.getClientProperty( FlatClientProperties.PLACEHOLDER_TEXT );
if( !(placeholder instanceof String) )
String placeholder = clientProperty( jc, PLACEHOLDER_TEXT, null, String.class );
if( placeholder == null )
return;
// compute placeholder location
Rectangle r = getVisibleEditorRect();
FontMetrics fm = c.getFontMetrics( c.getFont() );
String clippedPlaceholder = JavaCompatibility.getClippedString( c, fm, (String) placeholder, r.width );
int x = r.x + (c.getComponentOrientation().isLeftToRight() ? 0 : r.width - fm.stringWidth( clippedPlaceholder ));
String clippedPlaceholder = JavaCompatibility.getClippedString( c, fm, placeholder, r.width );
int x = r.x + (isLeftToRight() ? 0 : r.width - fm.stringWidth( clippedPlaceholder ));
int y = r.y + fm.getAscent() + ((r.height - fm.getHeight()) / 2);
// paint placeholder
@@ -246,14 +482,67 @@ public class FlatTextFieldUI
FlatUIUtils.drawString( c, g, clippedPlaceholder, x, y );
}
/**
* Paints the leading and trailing icons in the given rectangle.
* The rectangle is updated by this method so that subclasses can use it
* without painting over leading or trailing icons.
*
* @since 2
*/
protected void paintIcons( Graphics g, Rectangle r ) {
boolean ltr = isLeftToRight();
Icon leftIcon = ltr ? leadingIcon : trailingIcon;
Icon rightIcon = ltr ? trailingIcon : leadingIcon;
// paint left icon
if( leftIcon != null ) {
int x = r.x;
int y = r.y + Math.round( (r.height - leftIcon.getIconHeight()) / 2f );
leftIcon.paintIcon( getComponent(), g, x, y );
// update rectangle so that subclasses can use it
int w = leftIcon.getIconWidth() + scale( iconTextGap );
r.x += w;
r.width -= w;
}
// paint right icon
if( rightIcon != null ) {
int iconWidth = rightIcon.getIconWidth();
int x = r.x + r.width - iconWidth;
int y = r.y + Math.round( (r.height - rightIcon.getIconHeight()) / 2f );
rightIcon.paintIcon( getComponent(), g, x, y );
// update rectangle so that subclasses can use it
r.width -= iconWidth + scale( iconTextGap );
}
}
@Override
public Dimension getPreferredSize( JComponent c ) {
return applyMinimumWidth( c, super.getPreferredSize( c ), minimumWidth );
return applyMinimumWidth( c, applyExtraSize( super.getPreferredSize( c ) ), minimumWidth );
}
@Override
public Dimension getMinimumSize( JComponent c ) {
return applyMinimumWidth( c, super.getMinimumSize( c ), minimumWidth );
return applyMinimumWidth( c, applyExtraSize( super.getMinimumSize( c ) ), minimumWidth );
}
private Dimension applyExtraSize( Dimension size ) {
// add width of leading and trailing icons
size.width += getLeadingIconWidth() + getTrailingIconWidth();
// add width of leading and trailing components
for( JComponent comp : getLeadingComponents() ) {
if( comp != null && comp.isVisible() )
size.width += comp.getPreferredSize().width;
}
for( JComponent comp : getTrailingComponents() ) {
if( comp != null && comp.isVisible() )
size.width += comp.getPreferredSize().width;
}
return size;
}
private Dimension applyMinimumWidth( JComponent c, Dimension size, int minimumWidth ) {
@@ -283,35 +572,389 @@ public class FlatTextFieldUI
return margin instanceof UIResource && Objects.equals( margin, defaultMargin );
}
/**
* Returns the rectangle used for the root view of the text.
* This method is used to place the text.
*/
@Override
protected Rectangle getVisibleEditorRect() {
Rectangle r = super.getVisibleEditorRect();
if( r != null ) {
// remove padding
Insets padding = getPadding();
if( padding != null ) {
r = FlatUIUtils.subtractInsets( r, padding );
r.width = Math.max( r.width, 0 );
r.height = Math.max( r.height, 0 );
}
Rectangle r = getIconsRect();
if( r == null )
return null;
// remove space needed for leading and trailing icons
int leading = getLeadingIconWidth();
int trailing = getTrailingIconWidth();
if( leading != 0 || trailing != 0 ) {
boolean ltr = isLeftToRight();
int left = ltr ? leading : trailing;
int right = ltr ? trailing : leading;
r.x += left;
r.width -= left + right;
}
// remove padding
Insets padding = getPadding();
if( padding != null )
r = FlatUIUtils.subtractInsets( r, padding );
// make sure that width and height are not negative
r.width = Math.max( r.width, 0 );
r.height = Math.max( r.height, 0 );
return r;
}
/**
* @since 1.4
* 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.
* Also the preferred widths of leading and trailing components are removed.
*
* @since 2
*/
protected Insets getPadding() {
Object padding = getComponent().getClientProperty( FlatClientProperties.TEXT_FIELD_PADDING );
return (padding instanceof Insets) ? UIScale.scale( (Insets) padding ) : null;
protected Rectangle getIconsRect() {
Rectangle r = super.getVisibleEditorRect();
if( r == null )
return null;
boolean ltr = isLeftToRight();
// remove width of leading/trailing components
JComponent[] leftComponents = ltr ? getLeadingComponents() : getTrailingComponents();
JComponent[] rightComponents = ltr ? getTrailingComponents() : getLeadingComponents();
boolean leftVisible = false;
boolean rightVisible = false;
for( JComponent leftComponent : leftComponents ) {
if( leftComponent != null && leftComponent.isVisible() ) {
int w = leftComponent.getPreferredSize().width;
r.x += w;
r.width -= w;
leftVisible = true;
}
}
for( JComponent rightComponent : rightComponents ) {
if( rightComponent != null && rightComponent.isVisible() ) {
r.width -= rightComponent.getPreferredSize().width;
rightVisible = true;
}
}
// 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( 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 );
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 );
}
// make sure that width and height are not negative
r.width = Math.max( r.width, 0 );
r.height = Math.max( r.height, 0 );
return r;
}
/**
* @since 1.4
*/
/** @since 2 */
protected boolean hasLeadingIcon() {
return leadingIcon != null;
}
/** @since 2 */
protected boolean hasTrailingIcon() {
return trailingIcon != null;
}
/** @since 2 */
protected int getLeadingIconWidth() {
return (leadingIcon != null) ? leadingIcon.getIconWidth() + scale( iconTextGap ) : 0;
}
/** @since 2 */
protected int getTrailingIconWidth() {
return (trailingIcon != null) ? trailingIcon.getIconWidth() + scale( iconTextGap ) : 0;
}
boolean isLeftToRight() {
return getComponent().getComponentOrientation().isLeftToRight();
}
/** @since 1.4 */
protected Insets getPadding() {
return scale( clientProperty( getComponent(), TEXT_FIELD_PADDING, null, Insets.class ) );
}
/** @since 1.4 */
protected void scrollCaretToVisible() {
Caret caret = getComponent().getCaret();
if( caret instanceof FlatCaret )
((FlatCaret)caret).scrollCaretToVisible();
}
/** @since 2 */
protected void installLeadingComponent() {
JTextComponent c = getComponent();
leadingComponent = clientProperty( c, TEXT_FIELD_LEADING_COMPONENT, null, JComponent.class );
if( leadingComponent != null ) {
prepareLeadingOrTrailingComponent( leadingComponent );
installLayout();
c.add( leadingComponent );
}
}
/** @since 2 */
protected void installTrailingComponent() {
JTextComponent c = getComponent();
trailingComponent = clientProperty( c, TEXT_FIELD_TRAILING_COMPONENT, null, JComponent.class );
if( trailingComponent != null ) {
prepareLeadingOrTrailingComponent( trailingComponent );
installLayout();
c.add( trailingComponent );
}
}
/** @since 2 */
protected void uninstallLeadingComponent() {
if( leadingComponent != null ) {
getComponent().remove( leadingComponent );
leadingComponent = null;
}
}
/** @since 2 */
protected void uninstallTrailingComponent() {
if( trailingComponent != null ) {
getComponent().remove( trailingComponent );
trailingComponent = null;
}
}
/** @since 2 */
protected void installClearButton() {
JTextComponent c = getComponent();
if( clientPropertyBoolean( c, TEXT_FIELD_SHOW_CLEAR_BUTTON, showClearButton ) ) {
clearButton = createClearButton();
updateClearButton();
installDocumentListener();
installLayout();
c.add( clearButton );
}
}
/** @since 2 */
protected void uninstallClearButton() {
if( clearButton != null ) {
getComponent().remove( clearButton );
clearButton = null;
}
}
/** @since 2 */
protected JComponent createClearButton() {
JButton button = new JButton();
button.putClientProperty( STYLE_CLASS, "clearButton" );
button.putClientProperty( BUTTON_TYPE, BUTTON_TYPE_TOOLBAR_BUTTON );
button.setCursor( Cursor.getDefaultCursor() );
button.addActionListener( e -> clearButtonClicked() );
return button;
}
/** @since 2 */
@SuppressWarnings( "unchecked" )
protected void clearButtonClicked() {
JTextComponent c = getComponent();
Object callback = c.getClientProperty( TEXT_FIELD_CLEAR_CALLBACK );
if( callback instanceof Runnable )
((Runnable)callback).run();
else if( callback instanceof Consumer )
((Consumer<JTextComponent>)callback).accept( c );
else
c.setText( "" );
}
/** @since 2 */
protected void updateClearButton() {
if( clearButton == null )
return;
JTextComponent c = getComponent();
boolean visible = c.isEnabled() && c.isEditable() && c.getDocument().getLength() > 0;
if( visible != clearButton.isVisible() ) {
clearButton.setVisible( visible );
c.revalidate();
c.repaint();
}
}
/**
* Returns components placed at the leading side of the text field.
* The returned array may contain {@code null}.
* The default implementation returns {@link #leadingComponent}.
*
* @since 2
*/
protected JComponent[] getLeadingComponents() {
return new JComponent[] { leadingComponent };
}
/**
* Returns components placed at the trailing side of the text field.
* The returned array may contain {@code null}.
* The default implementation returns {@link #trailingComponent} and {@link #clearButton}.
* <p>
* <strong>Note</strong>: The components in the array must be in reverse (visual) order.
*
* @since 2
*/
protected JComponent[] getTrailingComponents() {
return new JComponent[] { trailingComponent, clearButton };
}
/** @since 2 */
protected void prepareLeadingOrTrailingComponent( JComponent c ) {
c.putClientProperty( STYLE_CLASS, "inTextField" );
c.setCursor( Cursor.getDefaultCursor() );
if( c instanceof JButton || c instanceof JToggleButton )
c.putClientProperty( BUTTON_TYPE, BUTTON_TYPE_TOOLBAR_BUTTON );
else if( c instanceof JToolBar ) {
for( Component child : c.getComponents() ) {
if( child instanceof JComponent )
((JComponent)child).putClientProperty( STYLE_CLASS, "inTextField" );
}
}
}
/** @since 2 */
protected void installLayout() {
JTextComponent c = getComponent();
LayoutManager oldLayout = c.getLayout();
if( !(oldLayout instanceof FlatTextFieldLayout) )
c.setLayout( new FlatTextFieldLayout( oldLayout ) );
}
//---- class FlatTextFieldLayout ------------------------------------------
private class FlatTextFieldLayout
implements LayoutManager2, UIResource
{
private final LayoutManager delegate;
FlatTextFieldLayout( LayoutManager delegate ) {
this.delegate = delegate;
}
@Override
public void addLayoutComponent( String name, Component comp ) {
if( delegate != null )
delegate.addLayoutComponent( name, comp );
}
@Override
public void removeLayoutComponent( Component comp ) {
if( delegate != null )
delegate.removeLayoutComponent( comp );
}
@Override
public Dimension preferredLayoutSize( Container parent ) {
return (delegate != null) ? delegate.preferredLayoutSize( parent ) : null;
}
@Override
public Dimension minimumLayoutSize( Container parent ) {
return (delegate != null) ? delegate.minimumLayoutSize( parent ) : null;
}
@Override
public void layoutContainer( Container parent ) {
if( delegate != null )
delegate.layoutContainer( parent );
int ow = FlatUIUtils.getBorderFocusAndLineWidth( getComponent() );
int h = parent.getHeight() - ow - ow;
boolean ltr = isLeftToRight();
JComponent[] leftComponents = ltr ? getLeadingComponents() : getTrailingComponents();
JComponent[] rightComponents = ltr ? getTrailingComponents() : getLeadingComponents();
// layout left components
int x = ow;
for( JComponent leftComponent : leftComponents ) {
if( leftComponent != null && leftComponent.isVisible() ) {
int cw = leftComponent.getPreferredSize().width;
leftComponent.setBounds( x, ow, cw, h );
x += cw;
}
}
// layout right components
x = parent.getWidth() - ow;
for( JComponent rightComponent : rightComponents ) {
if( rightComponent != null && rightComponent.isVisible() ) {
int cw = rightComponent.getPreferredSize().width;
x -= cw;
rightComponent.setBounds( x, ow, cw, h );
}
}
}
@Override
public void addLayoutComponent( Component comp, Object constraints ) {
if( delegate instanceof LayoutManager2 )
((LayoutManager2)delegate).addLayoutComponent( comp, constraints );
}
@Override
public Dimension maximumLayoutSize( Container target ) {
return (delegate instanceof LayoutManager2) ? ((LayoutManager2)delegate).maximumLayoutSize( target ) : null;
}
@Override
public float getLayoutAlignmentX( Container target ) {
return (delegate instanceof LayoutManager2) ? ((LayoutManager2)delegate).getLayoutAlignmentX( target ) : 0.5f;
}
@Override
public float getLayoutAlignmentY( Container target ) {
return (delegate instanceof LayoutManager2) ? ((LayoutManager2)delegate).getLayoutAlignmentY( target ) : 0.5f;
}
@Override
public void invalidateLayout( Container target ) {
if( delegate instanceof LayoutManager2 )
((LayoutManager2)delegate).invalidateLayout( target );
}
}
//---- class FlatDocumentListener -----------------------------------------
private class FlatDocumentListener
implements DocumentListener
{
@Override
public void insertUpdate( DocumentEvent e ) {
documentChanged( e );
}
@Override
public void removeUpdate( DocumentEvent e ) {
documentChanged( e );
}
@Override
public void changedUpdate( DocumentEvent e ) {
documentChanged( e );
}
}
}

View File

@@ -23,12 +23,17 @@ import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.event.FocusListener;
import java.beans.PropertyChangeEvent;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicTextPaneUI;
import javax.swing.text.Caret;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JTextPane}.
@@ -58,20 +63,35 @@ import com.formdev.flatlaf.util.HiDPIUtils;
*/
public class FlatTextPaneUI
extends BasicTextPaneUI
implements StyleableUI
{
protected int minimumWidth;
@Styleable protected int minimumWidth;
protected boolean isIntelliJTheme;
protected Color focusedBackground;
private Color background;
@Styleable protected Color disabledBackground;
@Styleable protected Color inactiveBackground;
@Styleable protected Color focusedBackground;
private Color oldDisabledBackground;
private Color oldInactiveBackground;
private Insets defaultMargin;
private Object oldHonorDisplayProperties;
private FocusListener focusListener;
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
return new FlatTextPaneUI();
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installStyle();
}
@Override
protected void installDefaults() {
super.installDefaults();
@@ -79,6 +99,9 @@ public class FlatTextPaneUI
String prefix = getPropertyPrefix();
minimumWidth = UIManager.getInt( "Component.minimumWidth" );
isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" );
background = UIManager.getColor( prefix + ".background" );
disabledBackground = UIManager.getColor( prefix + ".disabledBackground" );
inactiveBackground = UIManager.getColor( prefix + ".inactiveBackground" );
focusedBackground = UIManager.getColor( prefix + ".focusedBackground" );
defaultMargin = UIManager.getInsets( prefix + ".margin" );
@@ -92,8 +115,16 @@ public class FlatTextPaneUI
protected void uninstallDefaults() {
super.uninstallDefaults();
background = null;
disabledBackground = null;
inactiveBackground = null;
focusedBackground = null;
oldDisabledBackground = null;
oldInactiveBackground = null;
oldStyleValues = null;
getComponent().putClientProperty( JEditorPane.HONOR_DISPLAY_PROPERTIES, oldHonorDisplayProperties );
}
@@ -114,10 +145,56 @@ public class FlatTextPaneUI
focusListener = null;
}
@Override
protected Caret createCaret() {
return new FlatCaret( null, false );
}
@Override
protected void propertyChange( PropertyChangeEvent e ) {
// invoke updateBackground() before super.propertyChange()
String propertyName = e.getPropertyName();
if( "editable".equals( propertyName ) || "enabled".equals( propertyName ) )
updateBackground();
super.propertyChange( e );
FlatEditorPaneUI.propertyChange( getComponent(), e );
FlatEditorPaneUI.propertyChange( getComponent(), e, this::installStyle );
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( getComponent(), "TextPane" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
oldDisabledBackground = disabledBackground;
oldInactiveBackground = inactiveBackground;
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
updateBackground();
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, getComponent(), key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
private void updateBackground() {
FlatTextFieldUI.updateBackground( getComponent(), background,
disabledBackground, inactiveBackground,
oldDisabledBackground, oldInactiveBackground );
}
@Override

View File

@@ -50,8 +50,6 @@ import javax.accessibility.AccessibleContext;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
@@ -66,7 +64,6 @@ import javax.swing.border.Border;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.ui.FlatNativeWindowBorder.WindowTopBorder;
import com.formdev.flatlaf.util.ScaledImageIcon;
import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale;
@@ -80,6 +77,8 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault TitlePane.embeddedForeground Color
* @uiDefault TitlePane.borderColor Color optional
* @uiDefault TitlePane.unifiedBackground boolean
* @uiDefault TitlePane.showIcon boolean
* @uiDefault TitlePane.noIconLeftGap int
* @uiDefault TitlePane.iconSize Dimension
* @uiDefault TitlePane.iconMargins Insets
* @uiDefault TitlePane.titleMargins Insets
@@ -88,7 +87,6 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault TitlePane.centerTitle boolean
* @uiDefault TitlePane.centerTitleIfMenuBarEmbedded boolean
* @uiDefault TitlePane.menuBarTitleGap int
* @uiDefault TitlePane.icon Icon
* @uiDefault TitlePane.closeIcon Icon
* @uiDefault TitlePane.iconifyIcon Icon
* @uiDefault TitlePane.maximizeIcon Icon
@@ -106,6 +104,8 @@ public class FlatTitlePane
protected final Color embeddedForeground = UIManager.getColor( "TitlePane.embeddedForeground" );
protected final Color borderColor = UIManager.getColor( "TitlePane.borderColor" );
/** @since 2 */ protected final boolean showIcon = FlatUIUtils.getUIBoolean( "TitlePane.showIcon", true );
/** @since 2 */ protected final int noIconLeftGap = FlatUIUtils.getUIInt( "TitlePane.noIconLeftGap", 8 );
protected final Dimension iconSize = UIManager.getDimension( "TitlePane.iconSize" );
protected final int buttonMaximizedHeight = UIManager.getInt( "TitlePane.buttonMaximizedHeight" );
protected final boolean centerTitle = UIManager.getBoolean( "TitlePane.centerTitle" );
@@ -224,6 +224,11 @@ public class FlatTitlePane
restoreButton = createButton( "TitlePane.restoreIcon", "Restore", e -> restore() );
closeButton = createButton( "TitlePane.closeIcon", "Close", e -> close() );
// initially hide buttons that are only supported in frames
iconifyButton.setVisible( false );
maximizeButton.setVisible( false );
restoreButton.setVisible( false );
buttonPanel = new JPanel() {
@Override
public Dimension getPreferredSize() {
@@ -243,11 +248,9 @@ public class FlatTitlePane
if( rootPane.getWindowDecorationStyle() == JRootPane.FRAME ) {
// JRootPane.FRAME works only for frames (and not for dialogs)
// but at this time the owner window type is unknown (not yet added)
// so we add the iconify/maximize/restore buttons and they are hidden
// so we add the iconify/maximize/restore buttons and they are shown
// later in frameStateChanged(), which is invoked from addNotify()
restoreButton.setVisible( false );
buttonPanel.add( iconifyButton );
buttonPanel.add( maximizeButton );
buttonPanel.add( restoreButton );
@@ -337,36 +340,27 @@ public class FlatTitlePane
protected void updateIcon() {
// get window images
List<Image> images = window.getIconImages();
if( images.isEmpty() ) {
// search in owners
for( Window owner = window.getOwner(); owner != null; owner = owner.getOwner() ) {
images = owner.getIconImages();
if( !images.isEmpty() )
break;
List<Image> images = null;
if( clientPropertyBoolean( rootPane, TITLE_BAR_SHOW_ICON, showIcon ) ) {
images = window.getIconImages();
if( images.isEmpty() ) {
// search in owners
for( Window owner = window.getOwner(); owner != null; owner = owner.getOwner() ) {
images = owner.getIconImages();
if( !images.isEmpty() )
break;
}
}
}
boolean hasIcon = true;
boolean hasIcon = (images != null && !images.isEmpty());
// set icon
if( !images.isEmpty() )
iconLabel.setIcon( new FlatTitlePaneIcon( images, iconSize ) );
else {
// no icon set on window --> use default icon
Icon defaultIcon = UIManager.getIcon( "TitlePane.icon" );
if( defaultIcon != null && (defaultIcon.getIconWidth() == 0 || defaultIcon.getIconHeight() == 0) )
defaultIcon = null;
if( defaultIcon != null ) {
if( defaultIcon instanceof ImageIcon )
defaultIcon = new ScaledImageIcon( (ImageIcon) defaultIcon, iconSize.width, iconSize.height );
iconLabel.setIcon( defaultIcon );
} else
hasIcon = false;
}
iconLabel.setIcon( hasIcon ? new FlatTitlePaneIcon( images, iconSize ) : null );
// show/hide icon
iconLabel.setVisible( hasIcon );
leftPanel.setBorder( hasIcon ? null : FlatUIUtils.nonUIResource( new FlatEmptyBorder( 0, noIconLeftGap, 0, 0 ) ) );
updateNativeTitleBarHeightAndHitTestSpotsLater();
}
@@ -521,17 +515,23 @@ public class FlatTitlePane
g.drawLine( 0, debugTitleBarHeight, getWidth(), debugTitleBarHeight );
}
if( debugHitTestSpots != null ) {
g.setColor( Color.red );
Point offset = SwingUtilities.convertPoint( this, 0, 0, window );
for( Rectangle r : debugHitTestSpots )
g.drawRect( r.x - offset.x, r.y - offset.y, r.width - 1, r.height - 1 );
}
if( debugAppIconBounds != null ) {
g.setColor( Color.blue);
Point offset = SwingUtilities.convertPoint( this, 0, 0, window );
Rectangle r = debugAppIconBounds;
g.drawRect( r.x - offset.x, r.y - offset.y, r.width - 1, r.height - 1 );
paintRect( g, Color.red, r );
}
paintRect( g, Color.cyan, debugCloseButtonBounds );
paintRect( g, Color.blue, debugAppIconBounds );
paintRect( g, Color.blue, debugMinimizeButtonBounds );
paintRect( g, Color.magenta, debugMaximizeButtonBounds );
paintRect( g, Color.cyan, debugCloseButtonBounds );
}
private void paintRect( Graphics g, Color color, Rectangle r ) {
if( r == null )
return;
g.setColor( color );
Point offset = SwingUtilities.convertPoint( this, 0, 0, window );
g.drawRect( r.x - offset.x, r.y - offset.y, r.width - 1, r.height - 1 );
}
debug*/
@@ -702,9 +702,15 @@ debug*/
return window != null && FlatNativeWindowBorder.hasCustomDecoration( window );
}
// used to invoke updateNativeTitleBarHeightAndHitTestSpots() only once from latest invokeLater()
private int laterCounter;
protected void updateNativeTitleBarHeightAndHitTestSpotsLater() {
laterCounter++;
EventQueue.invokeLater( () -> {
updateNativeTitleBarHeightAndHitTestSpots();
laterCounter--;
if( laterCounter == 0 )
updateNativeTitleBarHeightAndHitTestSpots();
} );
}
@@ -722,8 +728,9 @@ debug*/
List<Rectangle> hitTestSpots = new ArrayList<>();
Rectangle appIconBounds = null;
if( iconLabel.isVisible() ) {
// compute real icon size (without insets; 1px wider for easier hitting)
// compute real icon size (without insets; 1px larger for easier hitting)
Point location = SwingUtilities.convertPoint( iconLabel, 0, 0, window );
Insets iconInsets = iconLabel.getInsets();
Rectangle iconBounds = new Rectangle(
@@ -759,7 +766,7 @@ debug*/
JMenuBar menuBar = rootPane.getJMenuBar();
if( hasVisibleEmbeddedMenuBar( menuBar ) ) {
r = getNativeHitTestSpot( menuBarPlaceholder );
r = getNativeHitTestSpot( menuBar );
if( r != null ) {
Component horizontalGlue = findHorizontalGlue( menuBar );
if( horizontalGlue != null ) {
@@ -768,18 +775,18 @@ debug*/
// the glue component area can used to move the window.
Point glueLocation = SwingUtilities.convertPoint( horizontalGlue, 0, 0, window );
int x2 = glueLocation.x + horizontalGlue.getWidth();
Rectangle r2;
if( getComponentOrientation().isLeftToRight() ) {
int trailingWidth = (r.x + r.width - HIT_TEST_SPOT_GROW) - glueLocation.x;
r.width -= trailingWidth;
r2 = new Rectangle( glueLocation.x + horizontalGlue.getWidth(), r.y, trailingWidth, r.height );
r2 = new Rectangle( x2, r.y, (r.x + r.width) - x2, r.height );
r.width = glueLocation.x - r.x;
} else {
int leadingWidth = (glueLocation.x + horizontalGlue.getWidth()) - (r.x + HIT_TEST_SPOT_GROW);
r.x += leadingWidth;
r.width -= leadingWidth;
r2 = new Rectangle( glueLocation.x -leadingWidth, r.y, leadingWidth, r.height );
r2 = new Rectangle( r.x, r.y, glueLocation.x - r.x, r.height );
r.width = (r.x + r.width) - x2;
r.x = x2;
}
r2.grow( HIT_TEST_SPOT_GROW, HIT_TEST_SPOT_GROW );
hitTestSpots.add( r2 );
}
@@ -787,16 +794,30 @@ debug*/
}
}
FlatNativeWindowBorder.setTitleBarHeightAndHitTestSpots( window, titleBarHeight, hitTestSpots, appIconBounds );
Rectangle minimizeButtonBounds = boundsInWindow( iconifyButton );
Rectangle maximizeButtonBounds = boundsInWindow( maximizeButton.isVisible() ? maximizeButton : restoreButton );
Rectangle closeButtonBounds = boundsInWindow( closeButton );
FlatNativeWindowBorder.setTitleBarHeightAndHitTestSpots( window, titleBarHeight,
hitTestSpots, appIconBounds, minimizeButtonBounds, maximizeButtonBounds, closeButtonBounds );
/*debug
debugTitleBarHeight = titleBarHeight;
debugHitTestSpots = hitTestSpots;
debugAppIconBounds = appIconBounds;
debugMinimizeButtonBounds = minimizeButtonBounds;
debugMaximizeButtonBounds = maximizeButtonBounds;
debugCloseButtonBounds = closeButtonBounds;
repaint();
debug*/
}
private Rectangle boundsInWindow( JComponent c ) {
return c.isShowing()
? SwingUtilities.convertRectangle( c.getParent(), c.getBounds(), window )
: null;
}
protected Rectangle getNativeHitTestSpot( JComponent c ) {
Dimension size = c.getSize();
if( size.width <= 0 || size.height <= 0 )
@@ -804,17 +825,16 @@ debug*/
Point location = SwingUtilities.convertPoint( c, 0, 0, window );
Rectangle r = new Rectangle( location, size );
// slightly increase rectangle so that component receives mouseExit events
r.grow( HIT_TEST_SPOT_GROW, HIT_TEST_SPOT_GROW );
return r;
}
private static final int HIT_TEST_SPOT_GROW = 2;
/*debug
private int debugTitleBarHeight;
private List<Rectangle> debugHitTestSpots;
private Rectangle debugAppIconBounds;
private Rectangle debugMinimizeButtonBounds;
private Rectangle debugMaximizeButtonBounds;
private Rectangle debugCloseButtonBounds;
debug*/
//---- class FlatTitlePaneBorder ------------------------------------------
@@ -846,7 +866,7 @@ debug*/
Border menuBarBorder = getMenuBarBorder();
if( menuBarBorder != null ) {
// if menu bar is embedded, paint menu bar border
menuBarBorder.paintBorder( c, g, x, y, width, height );
menuBarBorder.paintBorder( rootPane.getJMenuBar(), g, x, y, width, height );
} else if( borderColor != null && (rootPane.getJMenuBar() == null || !rootPane.getJMenuBar().isVisible()) ) {
// paint border between title pane and content if border color is specified
float lineHeight = UIScale.scale( (float) 1 );
@@ -871,12 +891,14 @@ debug*/
//---- class FlatTitleLabelUI ---------------------------------------------
/**
* @since 1.1
*/
/** @since 1.1 */
protected class FlatTitleLabelUI
extends FlatLabelUI
{
protected FlatTitleLabelUI() {
super( false );
}
@Override
protected void paintEnabledText( JLabel l, Graphics g, String s, int textX, int textY ) {
boolean hasEmbeddedMenuBar = hasVisibleEmbeddedMenuBar( rootPane.getJMenuBar() );

View File

@@ -31,9 +31,7 @@ public class FlatTitlePaneIcon
{
private final List<Image> images;
/**
* @since 1.2
*/
/** @since 1.2 */
public FlatTitlePaneIcon( List<Image> images, Dimension size ) {
super( null, size.width, size.height );
this.images = images;

View File

@@ -21,11 +21,15 @@ import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.beans.PropertyChangeEvent;
import java.util.Iterator;
import java.util.Map;
import javax.swing.AbstractButton;
import javax.swing.JComponent;
import javax.swing.JToggleButton;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.UnknownStyleException;
import com.formdev.flatlaf.util.UIScale;
/**
@@ -73,17 +77,28 @@ import com.formdev.flatlaf.util.UIScale;
public class FlatToggleButtonUI
extends FlatButtonUI
{
protected int tabUnderlineHeight;
protected Color tabUnderlineColor;
protected Color tabDisabledUnderlineColor;
protected Color tabSelectedBackground;
protected Color tabHoverBackground;
protected Color tabFocusBackground;
@Styleable(dot=true) protected int tabUnderlineHeight;
@Styleable(dot=true) protected Color tabUnderlineColor;
@Styleable(dot=true) protected Color tabDisabledUnderlineColor;
@Styleable(dot=true) protected Color tabSelectedBackground;
@Styleable(dot=true) protected Color tabHoverBackground;
@Styleable(dot=true) protected Color tabFocusBackground;
private boolean defaults_initialized = false;
public static ComponentUI createUI( JComponent c ) {
return FlatUIUtils.createSharedUI( FlatToggleButtonUI.class, FlatToggleButtonUI::new );
return FlatUIUtils.canUseSharedUI( c )
? FlatUIUtils.createSharedUI( FlatToggleButtonUI.class, () -> new FlatToggleButtonUI( true ) )
: new FlatToggleButtonUI( false );
}
protected FlatToggleButtonUI( boolean shared ) {
super( shared );
}
@Override
String getStyleType() {
return "ToggleButton";
}
@Override
@@ -136,8 +151,29 @@ public class FlatToggleButtonUI
}
}
/** @since 2 */
@Override
protected Object applyStyleProperty( AbstractButton b, String key, Object value ) {
if( key.startsWith( "help." ) )
throw new UnknownStyleException( key );
return super.applyStyleProperty( b, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
Map<String, Class<?>> infos = super.getStyleableInfos( c );
Iterator<String> it = infos.keySet().iterator();
while( it.hasNext() ) {
if( it.next().startsWith( "help." ) )
it.remove();
}
return infos;
}
static boolean isTabButton( Component c ) {
return c instanceof JToggleButton && clientPropertyEquals( (JToggleButton) c, BUTTON_TYPE, BUTTON_TYPE_TAB );
return c instanceof JToggleButton && BUTTON_TYPE_TAB.equals( getButtonTypeStr( (JToggleButton) c ) );
}
@Override

View File

@@ -22,9 +22,11 @@ import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.util.function.Function;
import javax.swing.JToolBar;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.plaf.ToolBarUI;
import com.formdev.flatlaf.util.UIScale;
/**
@@ -42,7 +44,7 @@ public class FlatToolBarBorder
private static final int DOT_SIZE = 2;
private static final int GRIP_SIZE = DOT_SIZE * 3;
protected final Color gripColor = UIManager.getColor( "ToolBar.gripColor" );
protected Color gripColor = UIManager.getColor( "ToolBar.gripColor" );
public FlatToolBarBorder() {
super( UIManager.getInsets( "ToolBar.borderMargins" ) );
@@ -56,7 +58,8 @@ public class FlatToolBarBorder
try {
FlatUIUtils.setRenderingHints( g2 );
g2.setColor( gripColor );
Color color = getStyleFromToolBarUI( c, ui -> ui.gripColor );
g2.setColor( (color != null) ? color : gripColor );
paintGrip( c, g2, x, y, width, height );
} finally {
g2.dispose();
@@ -90,7 +93,14 @@ public class FlatToolBarBorder
@Override
public Insets getBorderInsets( Component c, Insets insets ) {
insets = super.getBorderInsets( c, insets );
Insets m = getStyleFromToolBarUI( c, ui -> ui.borderMargins );
if( m != null ) {
int t = top, l = left, b = bottom, r = right;
top = m.top; left = m.left; bottom = m.bottom; right = m.right;
insets = super.getBorderInsets( c, insets );
top = t; left = l; bottom = b; right = r;
} else
insets = super.getBorderInsets( c, insets );
// add grip inset if floatable
if( c instanceof JToolBar && ((JToolBar)c).isFloatable() ) {
@@ -106,4 +116,17 @@ public class FlatToolBarBorder
return insets;
}
/**
* Because this border is shared for all toolbars,
* get border specific style from FlatToolBarUI.
*/
static <T> T getStyleFromToolBarUI( Component c, Function<FlatToolBarUI, T> f ) {
if( c instanceof JToolBar ) {
ToolBarUI ui = ((JToolBar)c).getUI();
if( ui instanceof FlatToolBarUI )
return f.apply( (FlatToolBarUI) ui );
}
return null;
}
}

View File

@@ -22,6 +22,8 @@ import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeListener;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.JSeparator;
import javax.swing.JToolBar;
@@ -29,6 +31,9 @@ import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicToolBarSeparatorUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.LoggingFacade;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JToolBar.Separator}.
@@ -42,16 +47,34 @@ import javax.swing.plaf.basic.BasicToolBarSeparatorUI;
*/
public class FlatToolBarSeparatorUI
extends BasicToolBarSeparatorUI
implements StyleableUI
{
private static final int LINE_WIDTH = 1;
protected int separatorWidth;
protected Color separatorColor;
@Styleable protected int separatorWidth;
@Styleable protected Color separatorColor;
private final boolean shared;
private boolean defaults_initialized = false;
private PropertyChangeListener propertyChangeListener;
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
return FlatUIUtils.createSharedUI( FlatToolBarSeparatorUI.class, FlatToolBarSeparatorUI::new );
return FlatUIUtils.canUseSharedUI( c )
? FlatUIUtils.createSharedUI( FlatToolBarSeparatorUI.class, () -> new FlatToolBarSeparatorUI( true ) )
: new FlatToolBarSeparatorUI( false );
}
/** @since 2 */
protected FlatToolBarSeparatorUI( boolean shared ) {
this.shared = shared;
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installStyle( (JSeparator) c );
}
@Override
@@ -73,7 +96,62 @@ public class FlatToolBarSeparatorUI
@Override
protected void uninstallDefaults( JSeparator s ) {
super.uninstallDefaults( s );
defaults_initialized = false;
oldStyleValues = null;
}
@Override
protected void installListeners( JSeparator s ) {
super.installListeners( s );
propertyChangeListener = FlatStylingSupport.createPropertyChangeListener(
s, () -> stylePropertyChange( s ), null );
s.addPropertyChangeListener( propertyChangeListener );
}
@Override
protected void uninstallListeners( JSeparator s ) {
super.uninstallListeners( s );
s.removePropertyChangeListener( propertyChangeListener );
propertyChangeListener = null;
}
private void stylePropertyChange( JSeparator s ) {
if( shared && FlatStylingSupport.hasStyleProperty( s ) ) {
// unshare component UI if necessary
// updateUI() invokes installStyle() from installUI()
s.updateUI();
} else
installStyle( s );
s.revalidate();
s.repaint();
}
/** @since 2 */
protected void installStyle( JSeparator s ) {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( s, "ToolBarSeparator" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObject( this, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
@Override

View File

@@ -16,16 +16,27 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.FocusTraversalPolicy;
import java.awt.Insets;
import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener;
import java.beans.PropertyChangeListener;
import java.util.Map;
import javax.swing.AbstractButton;
import javax.swing.InputMap;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.LayoutFocusTraversalPolicy;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicToolBarUI;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.LoggingFacade;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JToolBar}.
@@ -45,14 +56,30 @@ import javax.swing.plaf.basic.BasicToolBarUI;
* <!-- FlatToolBarUI -->
*
* @uiDefault ToolBar.focusableButtons boolean
* @uiDefault ToolBar.arrowKeysOnlyNavigation boolean
* @uiDefault ToolBar.floatable boolean
*
* <!-- FlatToolBarBorder -->
*
* @uiDefault ToolBar.borderMargins Insets
* @uiDefault ToolBar.gripColor Color
*
* @author Karl Tauber
*/
public class FlatToolBarUI
extends BasicToolBarUI
implements StyleableUI
{
/** @since 1.4 */
protected boolean focusableButtons;
/** @since 1.4 */ @Styleable protected boolean focusableButtons;
/** @since 2 */ @Styleable protected boolean arrowKeysOnlyNavigation;
// for FlatToolBarBorder
@Styleable protected Insets borderMargins;
@Styleable protected Color gripColor;
private FocusTraversalPolicy focusTraversalPolicy;
private Boolean oldFloatable;
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
return new FlatToolBarUI();
@@ -62,7 +89,13 @@ public class FlatToolBarUI
public void installUI( JComponent c ) {
super.installUI( c );
installFocusTraversalPolicy();
installStyle();
// disable focusable state of buttons (when switching from another Laf)
// do this after applying style to avoid disabling (here) and re-enabling
// (in applyStyle()), which would transfer focus to next button
if( !focusableButtons )
setButtonsFocusable( false );
}
@@ -74,6 +107,10 @@ public class FlatToolBarUI
// re-enable focusable state of buttons (when switching to another Laf)
if( !focusableButtons )
setButtonsFocusable( true );
uninstallFocusTraversalPolicy();
oldStyleValues = null;
}
@Override
@@ -81,6 +118,24 @@ public class FlatToolBarUI
super.installDefaults();
focusableButtons = UIManager.getBoolean( "ToolBar.focusableButtons" );
arrowKeysOnlyNavigation = UIManager.getBoolean( "ToolBar.arrowKeysOnlyNavigation" );
// floatable
if( !UIManager.getBoolean( "ToolBar.floatable" ) ) {
oldFloatable = toolBar.isFloatable();
toolBar.setFloatable( false );
} else
oldFloatable = null;
}
@Override
protected void uninstallDefaults() {
super.uninstallDefaults();
if( oldFloatable != null ) {
toolBar.setFloatable( oldFloatable );
oldFloatable = null;
}
}
@Override
@@ -90,36 +145,157 @@ public class FlatToolBarUI
public void componentAdded( ContainerEvent e ) {
super.componentAdded( e );
if( !focusableButtons ) {
Component c = e.getChild();
if( c instanceof AbstractButton )
c.setFocusable( false );
}
if( !focusableButtons )
setButtonFocusable( e.getChild(), false );
}
@Override
public void componentRemoved( ContainerEvent e ) {
super.componentRemoved( e );
if( !focusableButtons ) {
Component c = e.getChild();
if( c instanceof AbstractButton )
c.setFocusable( true );
}
if( !focusableButtons )
setButtonFocusable( e.getChild(), true );
}
};
}
/**
* @since 1.4
*/
protected void setButtonsFocusable( boolean focusable ) {
for( Component c : toolBar.getComponents() ) {
if( c instanceof AbstractButton )
c.setFocusable( focusable );
@Override
protected PropertyChangeListener createPropertyListener() {
return FlatStylingSupport.createPropertyChangeListener( toolBar, this::installStyle, super.createPropertyListener() );
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( toolBar, "ToolBar" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
boolean oldFocusableButtons = focusableButtons;
boolean oldArrowKeysOnlyNavigation = arrowKeysOnlyNavigation;
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
if( focusableButtons != oldFocusableButtons )
setButtonsFocusable( focusableButtons );
if( arrowKeysOnlyNavigation != oldArrowKeysOnlyNavigation || focusableButtons != oldFocusableButtons ) {
if( arrowKeysOnlyNavigation )
installFocusTraversalPolicy();
else
uninstallFocusTraversalPolicy();
}
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, toolBar, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/** @since 1.4 */
protected void setButtonsFocusable( boolean focusable ) {
for( Component c : toolBar.getComponents() )
setButtonFocusable( c, focusable );
}
private void setButtonFocusable( Component c, boolean focusable ) {
if( c instanceof AbstractButton && focusable != c.isFocusable() )
c.setFocusable( focusable );
}
/** @since 2 */
protected void installFocusTraversalPolicy() {
if( !arrowKeysOnlyNavigation || !focusableButtons || toolBar.getFocusTraversalPolicy() != null )
return;
focusTraversalPolicy = createFocusTraversalPolicy();
if( focusTraversalPolicy != null ) {
toolBar.setFocusTraversalPolicy( focusTraversalPolicy );
toolBar.setFocusTraversalPolicyProvider( true );
}
}
/** @since 2 */
protected void uninstallFocusTraversalPolicy() {
if( focusTraversalPolicy != null && toolBar.getFocusTraversalPolicy() == focusTraversalPolicy ) {
toolBar.setFocusTraversalPolicy( null );
toolBar.setFocusTraversalPolicyProvider( false );
}
focusTraversalPolicy = null;
}
/** @since 2 */
protected FocusTraversalPolicy createFocusTraversalPolicy() {
return new FlatToolBarFocusTraversalPolicy();
}
/**
* Does the same as super.navigateFocusedComp() with the exception that components
* with empty input map (e.g. JLabel) are skipped.
*/
@Override
protected void navigateFocusedComp( int direction ) {
int count = toolBar.getComponentCount();
if( focusedCompIndex < 0 || focusedCompIndex >= count )
return;
int add;
switch( direction ) {
case EAST: case SOUTH: add = 1; break;
case WEST: case NORTH: add = -1; break;
default: return;
}
for( int i = focusedCompIndex + add; i != focusedCompIndex; i += add ) {
if( i < 0 )
i = count - 1;
else if( i >= count )
i = 0;
Component c = toolBar.getComponentAtIndex( i );
if( canBeFocusOwner( c ) ) {
c.requestFocus();
return;
}
}
}
private static boolean canBeFocusOwner( Component c ) {
// see Component.canBeFocusOwner()
if( c == null || !c.isEnabled() || !c.isVisible() || !c.isDisplayable() || !c.isFocusable() )
return false;
// special handling for combo box
// see LayoutFocusTraversalPolicy.accept()
if( c instanceof JComboBox ) {
JComboBox<?> comboBox = (JComboBox<?>) c;
return comboBox.getUI().isFocusTraversable( comboBox );
}
// check whether component has a empty input map to skip components that
// are focusable, but do nothing when focused (e.g. JLabel)
// see LayoutFocusTraversalPolicy.accept()
if( c instanceof JComponent ) {
InputMap inputMap = ((JComponent)c).getInputMap( JComponent.WHEN_FOCUSED );
while( inputMap != null && inputMap.size() == 0 )
inputMap = inputMap.getParent();
if( inputMap == null )
return false;
}
return true;
}
// disable rollover border
@Override protected void setBorderToRollover( Component c ) {}
@Override protected void setBorderToNonRollover( Component c ) {}
@@ -142,4 +318,81 @@ public class FlatToolBarUI
super.setOrientation( orientation );
}
//---- class FlatToolBarFocusTraversalPolicy ------------------------------
/**
* Focus traversal policy used for toolbar to modify traversal behaviour:
* <ul>
* <li>Tab-key moves focus out of toolbar.</li>
* <li>If moving focus into the toolbar, focus recently focused toolbar button.</li>
* </ul>
* If the toolbar contains non-button components (e.g. combobox), then the behavior
* is slightly different. Non-button component are always included in Tab-key traversal.
*
* @since 2
*/
protected class FlatToolBarFocusTraversalPolicy
extends LayoutFocusTraversalPolicy
{
@Override
public Component getComponentAfter( Container aContainer, Component aComponent ) {
// if currently focused component is not a button,
// then move focus to next component/button in toolbar
if( !(aComponent instanceof AbstractButton) )
return super.getComponentAfter( aContainer, aComponent );
// if currently focused component is a button,
// then either move focus to next non-button component in toolbar (and skip buttons)
// or move it out of toolbar
Component c = aComponent;
while( (c = super.getComponentAfter( aContainer, c )) != null ) {
if( !(c instanceof AbstractButton) )
return c;
}
// move focus out of toolbar
return null;
}
@Override
public Component getComponentBefore( Container aContainer, Component aComponent ) {
// if currently focused component is not a button,
// then move focus to previous component/button in toolbar
if( !(aComponent instanceof AbstractButton) )
return super.getComponentBefore( aContainer, aComponent );
// if currently focused component is a button,
// then either move focus to previous non-button component in toolbar (and skip buttons)
// or move it out of toolbar
Component c = aComponent;
while( (c = super.getComponentBefore( aContainer, c )) != null ) {
if( !(c instanceof AbstractButton) )
return c;
}
// move focus out of toolbar
return null;
}
@Override
public Component getFirstComponent( Container aContainer ) {
return getRecentComponent( aContainer, true );
}
@Override
public Component getLastComponent( Container aContainer ) {
return getRecentComponent( aContainer, false );
}
private Component getRecentComponent( Container aContainer, boolean first ) {
// if moving focus into the toolbar, focus recently focused toolbar button
if( focusedCompIndex >= 0 && focusedCompIndex < toolBar.getComponentCount() )
return toolBar.getComponent( focusedCompIndex );
return first
? super.getFirstComponent( aContainer )
: super.getLastComponent( aContainer );
}
}
}

View File

@@ -26,6 +26,7 @@ import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.beans.PropertyChangeListener;
import java.util.Map;
import javax.swing.CellRendererPane;
import javax.swing.Icon;
import javax.swing.JComponent;
@@ -39,6 +40,9 @@ import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicTreeUI;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreePath;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale;
/**
@@ -48,6 +52,7 @@ import com.formdev.flatlaf.util.UIScale;
*
* @uiDefault Tree.font Font
* @uiDefault Tree.background Color
* @uiDefault Tree.foreground Color unused
* @uiDefault Tree.hash Color
* @uiDefault Tree.dropLineColor Color
* @uiDefault Tree.expandedIcon Icon
@@ -94,25 +99,71 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault Tree.wideSelection boolean
* @uiDefault Tree.showCellFocusIndicator boolean
*
* <!-- FlatTreeExpandedIcon -->
*
* @uiDefault Component.arrowType String chevron (default) or triangle
* @uiDefault Tree.icon.expandedColor Color
*
* <!-- FlatTreeCollapsedIcon -->
*
* @uiDefault Component.arrowType String chevron (default) or triangle
* @uiDefault Tree.icon.collapsedColor Color
*
* <!-- FlatTreeLeafIcon -->
*
* @uiDefault Tree.icon.leafColor Color
*
* <!-- FlatTreeClosedIcon -->
*
* @uiDefault Tree.icon.closedColor Color
*
* <!-- FlatTreeOpenIcon -->
*
* @uiDefault Tree.icon.openColor Color
*
* @author Karl Tauber
*/
public class FlatTreeUI
extends BasicTreeUI
implements StyleableUI
{
protected Color selectionBackground;
protected Color selectionForeground;
protected Color selectionInactiveBackground;
protected Color selectionInactiveForeground;
protected Color selectionBorderColor;
protected boolean wideSelection;
protected boolean showCellFocusIndicator;
@Styleable protected Color selectionBackground;
@Styleable protected Color selectionForeground;
@Styleable protected Color selectionInactiveBackground;
@Styleable protected Color selectionInactiveForeground;
@Styleable protected Color selectionBorderColor;
@Styleable protected boolean wideSelection;
@Styleable protected boolean showCellFocusIndicator;
// for icons
// (needs to be public because icon classes are in another package)
/** @since 2 */ @Styleable(dot=true) public String iconArrowType;
/** @since 2 */ @Styleable(dot=true) public Color iconExpandedColor;
/** @since 2 */ @Styleable(dot=true) public Color iconCollapsedColor;
/** @since 2 */ @Styleable(dot=true) public Color iconLeafColor;
/** @since 2 */ @Styleable(dot=true) public Color iconClosedColor;
/** @since 2 */ @Styleable(dot=true) public Color iconOpenColor;
// only used via styling (not in UI defaults, but has likewise client properties)
/** @since 2 */ @Styleable protected boolean paintSelection = true;
private Color defaultCellNonSelectionBackground;
private Color defaultSelectionBackground;
private Color defaultSelectionForeground;
private Color defaultSelectionBorderColor;
private Map<String, Object> oldStyleValues;
public static ComponentUI createUI( JComponent c ) {
return new FlatTreeUI();
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installStyle();
}
@Override
protected void installDefaults() {
super.installDefaults();
@@ -128,6 +179,9 @@ public class FlatTreeUI
showCellFocusIndicator = UIManager.getBoolean( "Tree.showCellFocusIndicator" );
defaultCellNonSelectionBackground = UIManager.getColor( "Tree.textBackground" );
defaultSelectionBackground = selectionBackground;
defaultSelectionForeground = selectionForeground;
defaultSelectionBorderColor = selectionBorderColor;
// scale
int rowHeight = FlatUIUtils.getUIInt( "Tree.rowHeight", 16 );
@@ -150,6 +204,10 @@ public class FlatTreeUI
selectionBorderColor = null;
defaultCellNonSelectionBackground = null;
defaultSelectionBackground = null;
defaultSelectionForeground = null;
defaultSelectionBorderColor = null;
oldStyleValues = null;
}
@Override
@@ -216,6 +274,13 @@ public class FlatTreeUI
repaintWideDropLocation( tree.getDropLocation() );
}
break;
case STYLE:
case STYLE_CLASS:
installStyle();
tree.revalidate();
tree.repaint();
break;
}
}
};
@@ -250,6 +315,31 @@ public class FlatTreeUI
return bounds;
}
/** @since 2 */
protected void installStyle() {
try {
applyStyle( FlatStylingSupport.getResolvedStyle( tree, "Tree" ) );
} catch( RuntimeException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
/** @since 2 */
protected void applyStyle( Object style ) {
oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty );
}
/** @since 2 */
protected Object applyStyleProperty( String key, Object value ) {
return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, tree, key, value );
}
/** @since 2 */
@Override
public Map<String, Class<?>> getStyleableInfos( JComponent c ) {
return FlatStylingSupport.getAnnotatedStyleableInfos( this );
}
/**
* Same as super.paintRow(), but supports wide selection and uses
* inactive selection background/foreground if tree is not focused.
@@ -289,35 +379,32 @@ public class FlatTreeUI
Component rendererComponent = currentCellRenderer.getTreeCellRendererComponent( tree,
path.getLastPathComponent(), isSelected, isExpanded, isLeaf, row, cellHasFocus );
// apply inactive selection background/foreground if tree is not focused
// renderer background/foreground
Color oldBackgroundSelectionColor = null;
if( isSelected && !hasFocus && !isDropRow ) {
if( rendererComponent instanceof DefaultTreeCellRenderer ) {
DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer) rendererComponent;
if( renderer.getBackgroundSelectionColor() == selectionBackground ) {
oldBackgroundSelectionColor = renderer.getBackgroundSelectionColor();
renderer.setBackgroundSelectionColor( selectionInactiveBackground );
}
} else {
if( rendererComponent.getBackground() == selectionBackground )
rendererComponent.setBackground( selectionInactiveBackground );
}
// apply inactive selection background/foreground if tree is not focused
oldBackgroundSelectionColor = setRendererBackgroundSelectionColor( rendererComponent, selectionInactiveBackground );
setRendererForeground( rendererComponent, selectionInactiveForeground );
if( rendererComponent.getForeground() == selectionForeground )
rendererComponent.setForeground( selectionInactiveForeground );
} else if( isSelected ) {
// update background/foreground if set via style
if( selectionBackground != defaultSelectionBackground )
oldBackgroundSelectionColor = setRendererBackgroundSelectionColor( rendererComponent, selectionBackground );
if( selectionForeground != defaultSelectionForeground )
setRendererForeground( rendererComponent, selectionForeground );
}
// remove focus selection border if exactly one item is selected
// update focus selection border
Color oldBorderSelectionColor = null;
if( isSelected && hasFocus &&
(!showCellFocusIndicator || tree.getMinSelectionRow() == tree.getMaxSelectionRow()) &&
rendererComponent instanceof DefaultTreeCellRenderer )
(!showCellFocusIndicator || tree.getMinSelectionRow() == tree.getMaxSelectionRow()) )
{
DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer) rendererComponent;
if( renderer.getBorderSelectionColor() == selectionBorderColor ) {
oldBorderSelectionColor = renderer.getBorderSelectionColor();
renderer.setBorderSelectionColor( null );
}
// remove focus selection border if exactly one item is selected or if showCellFocusIndicator is false
oldBorderSelectionColor = setRendererBorderSelectionColor( rendererComponent, null );
} else if( hasFocus && selectionBorderColor != defaultSelectionBorderColor ) {
// update focus selection border if set via style
oldBorderSelectionColor = setRendererBorderSelectionColor( rendererComponent, selectionBorderColor );
}
// paint selection background
@@ -365,6 +452,42 @@ public class FlatTreeUI
((DefaultTreeCellRenderer)rendererComponent).setBorderSelectionColor( oldBorderSelectionColor );
}
private Color setRendererBackgroundSelectionColor( Component rendererComponent, Color color ) {
Color oldColor = null;
if( rendererComponent instanceof DefaultTreeCellRenderer ) {
DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer) rendererComponent;
if( renderer.getBackgroundSelectionColor() == defaultSelectionBackground ) {
oldColor = renderer.getBackgroundSelectionColor();
renderer.setBackgroundSelectionColor( color );
}
} else {
if( rendererComponent.getBackground() == defaultSelectionBackground )
rendererComponent.setBackground( color );
}
return oldColor;
}
private void setRendererForeground( Component rendererComponent, Color color ) {
if( rendererComponent.getForeground() == defaultSelectionForeground )
rendererComponent.setForeground( color );
}
private Color setRendererBorderSelectionColor( Component rendererComponent, Color color ) {
Color oldColor = null;
if( rendererComponent instanceof DefaultTreeCellRenderer ) {
DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer) rendererComponent;
if( renderer.getBorderSelectionColor() == defaultSelectionBorderColor ) {
oldColor = renderer.getBorderSelectionColor();
renderer.setBorderSelectionColor( color );
}
}
return oldColor;
}
private void paintWideSelection( Graphics g, Rectangle clipBounds, Insets insets, Rectangle bounds,
TreePath path, int row, boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf )
{
@@ -416,6 +539,6 @@ public class FlatTreeUI
}
protected boolean isPaintSelection() {
return clientPropertyBoolean( tree, TREE_PAINT_SELECTION, true );
return clientPropertyBoolean( tree, TREE_PAINT_SELECTION, paintSelection );
}
}

View File

@@ -28,6 +28,7 @@ import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
@@ -129,9 +130,7 @@ public class FlatUIUtils
return (color != null) ? color : UIManager.getColor( defaultKey );
}
/**
* @since 1.1
*/
/** @since 1.1 */
public static boolean getUIBoolean( String key, boolean defaultValue ) {
Object value = UIManager.get( key );
return (value instanceof Boolean) ? (Boolean) value : defaultValue;
@@ -147,9 +146,20 @@ public class FlatUIUtils
return (value instanceof Number) ? ((Number)value).floatValue() : defaultValue;
}
/**
* @since 1.1.2
*/
/** @since 2 */
public static <T extends Enum<T>> T getUIEnum( String key, Class<T> enumType, T defaultValue ) {
Object value = UIManager.get( key );
if( value instanceof String ) {
try {
return Enum.valueOf( enumType, (String) value );
} catch( IllegalArgumentException ex ) {
// ignore
}
}
return defaultValue;
}
/** @since 1.1.2 */
public static boolean getBoolean( JComponent c, String systemPropertyKey,
String clientPropertyKey, String uiKey, boolean defaultValue )
{
@@ -178,6 +188,11 @@ public class FlatUIUtils
return (font instanceof UIResource) ? font.deriveFont( font.getStyle() ) : font;
}
/** @since 2 */
public static Border nonUIResource( Border border ) {
return (border instanceof UIResource) ? new NonUIResourceBorder( border ) : border;
}
public static int minimumWidth( JComponent c, int minimumWidth ) {
return FlatClientProperties.clientPropertyInt( c, FlatClientProperties.MINIMUM_WIDTH, minimumWidth );
}
@@ -263,6 +278,32 @@ public class FlatUIUtils
: 0;
}
/**
* Returns the scaled line thickness used to compute the border insets.
*
* @since 2
*/
public static float getBorderLineWidth( JComponent c ) {
FlatBorder border = getOutsideFlatBorder( c );
return (border != null)
? UIScale.scale( (float) border.getLineWidth( c ) )
: 0;
}
/**
* Returns the scaled thickness of the border.
* This includes the outer focus border and the actual component border.
*
* @since 2
*/
public static int getBorderFocusAndLineWidth( JComponent c ) {
FlatBorder border = getOutsideFlatBorder( c );
return (border != null)
? Math.round( UIScale.scale( (float) border.getFocusWidth( c ) )
+ UIScale.scale( (float) border.getLineWidth( c ) ) )
: 0;
}
/**
* Returns the scaled arc diameter of the border for the given component.
*/
@@ -351,104 +392,6 @@ public class FlatUIUtils
: color;
}
/**
* Paints an outer border, which is usually a focus border.
* <p>
* The outside bounds of the painted border are {@code x,y,width,height}.
* The line thickness of the painted border is {@code focusWidth + lineWidth}.
* The given arc diameter refers to the inner rectangle ({@code x,y,width,height} minus {@code focusWidth}).
*
* @see #paintComponentBorder
* @see #paintComponentBackground
*/
public static void paintComponentOuterBorder( Graphics2D g, int x, int y, int width, int height,
float focusWidth, float lineWidth, float arc )
{
if( focusWidth + lineWidth == 0 )
return; // nothing to paint
double systemScaleFactor = UIScale.getSystemScaleFactor( g );
if( systemScaleFactor != 1 && systemScaleFactor != 2 ) {
// paint at scale 1x to avoid clipping on right and bottom edges at 125%, 150% or 175%
HiDPIUtils.paintAtScale1x( g, x, y, width, height,
(g2d, x2, y2, width2, height2, scaleFactor) -> {
paintComponentOuterBorderImpl( g2d, x2, y2, width2, height2,
(float) (focusWidth * scaleFactor), (float) (lineWidth * scaleFactor), (float) (arc * scaleFactor) );
} );
return;
}
paintComponentOuterBorderImpl( g, x, y, width, height, focusWidth, lineWidth, arc );
}
private static void paintComponentOuterBorderImpl( Graphics2D g, int x, int y, int width, int height,
float focusWidth, float lineWidth, float arc )
{
float ow = focusWidth + lineWidth;
float outerArc = arc + (focusWidth * 2);
float innerArc = arc - (lineWidth * 2);
// reduce outer arc slightly for small arcs to make the curve slightly wider
if( focusWidth > 0 && arc > 0 && arc < UIScale.scale( 10 ) )
outerArc -= UIScale.scale( 2f );
Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD );
path.append( createComponentRectangle( x, y, width, height, outerArc ), false );
path.append( createComponentRectangle( x + ow, y + ow, width - (ow * 2), height - (ow * 2), innerArc ), false );
g.fill( path );
}
/**
* Draws the border of a component as round rectangle.
* <p>
* The outside bounds of the painted border are
* {@code x + focusWidth, y + focusWidth, width - (focusWidth * 2), height - (focusWidth * 2)}.
* The line thickness of the painted border is {@code lineWidth}.
* The given arc diameter refers to the painted rectangle (and not to {@code x,y,width,height}).
*
* @see #paintComponentOuterBorder
* @see #paintComponentBackground
*/
public static void paintComponentBorder( Graphics2D g, int x, int y, int width, int height,
float focusWidth, float lineWidth, float arc )
{
if( lineWidth == 0 )
return; // nothing to paint
double systemScaleFactor = UIScale.getSystemScaleFactor( g );
if( systemScaleFactor != 1 && systemScaleFactor != 2 ) {
// paint at scale 1x to avoid clipping on right and bottom edges at 125%, 150% or 175%
HiDPIUtils.paintAtScale1x( g, x, y, width, height,
(g2d, x2, y2, width2, height2, scaleFactor) -> {
paintComponentBorderImpl( g2d, x2, y2, width2, height2,
(float) (focusWidth * scaleFactor), (float) (lineWidth * scaleFactor), (float) (arc * scaleFactor) );
} );
return;
}
paintComponentBorderImpl( g, x, y, width, height, focusWidth, lineWidth, arc );
}
private static void paintComponentBorderImpl( Graphics2D g, int x, int y, int width, int height,
float focusWidth, float lineWidth, float arc )
{
float x1 = x + focusWidth;
float y1 = y + focusWidth;
float width1 = width - focusWidth * 2;
float height1 = height - focusWidth * 2;
float arc2 = arc - (lineWidth * 2);
Shape r1 = createComponentRectangle( x1, y1, width1, height1, arc );
Shape r2 = createComponentRectangle(
x1 + lineWidth, y1 + lineWidth,
width1 - lineWidth * 2, height1 - lineWidth * 2, arc2 );
Path2D border = new Path2D.Float( Path2D.WIND_EVEN_ODD );
border.append( r1, false );
border.append( r2, false );
g.fill( border );
}
/**
* Fills the background of a component with a round rectangle.
* <p>
@@ -456,32 +399,203 @@ public class FlatUIUtils
* {@code x + focusWidth, y + focusWidth, width - (focusWidth * 2), height - (focusWidth * 2)}.
* The given arc diameter refers to the painted rectangle (and not to {@code x,y,width,height}).
*
* @see #paintComponentOuterBorder
* @see #paintComponentBorder
* @see #paintOutlinedComponent
*/
public static void paintComponentBackground( Graphics2D g, int x, int y, int width, int height,
float focusWidth, float arc )
{
paintOutlinedComponent( g, x, y, width, height, focusWidth, 0, 0, 0, arc, null, null, g.getPaint() );
}
/**
* Paints an outlined component with rounded corners, consisting of following parts:
* <ul>
* <li>an (optional) outer border, which is usually a focus indicator
* <li>an (optional) component border
* <li>the (optional) component background
* </ul>
* <p>
*
* Each part is painted only if the corresponding part color is not {@code null}.
* The parts are painted in this order:
* <ol>
* <li>background
* <li>focus border
* <li>border
* </ol>
* <p>
*
* <strong>Background</strong>:
* The bounds of the filled round rectangle are
* {@code [x + focusWidth, y + focusWidth, width - (focusWidth * 2), height - (focusWidth * 2)]}.
* The focus border and the border may paint over the background.
* <p>
*
* <strong>Focus border</strong>:
* The outside bounds of the painted focus border are {@code [x, y, width, height]}.
* The thickness of the painted focus border is {@code (focusWidth * focusWidthFraction) + focusInnerWidth}.
* The border may paint over the focus border if {@code focusInnerWidth > 0}.
* <p>
*
* <strong>Border</strong>:
* The outside bounds of the painted border are
* {@code [x + focusWidth, y + focusWidth, width - (focusWidth * 2), height - (focusWidth * 2)]}.
* The thickness of the painted border is {@code borderWidth}.
*
* @param g the graphics context used for painting
* @param x the x coordinate of the component
* @param y the y coordinate of the component
* @param width the width of the component
* @param height the height of the component
* @param focusWidth the width of the focus border, or {@code 0}
* @param focusWidthFraction specified how much of the focus border is painted (in range 0 - 1);
* can be used for animation;
* the painted thickness of the focus border is {@code (focusWidth * focusWidthFraction) + focusInnerWidth}
* @param focusInnerWidth the inner width of the focus border, or {@code 0};
* if a border is painted then {@code focusInnerWidth} needs to be larger
* than {@code borderWidth} to be not hidden by the border
* @param borderWidth the width of the border, or {@code 0}
* @param arc the arc diameter used for the outside shape of the component border;
* the other needed arc diameters are computed from this arc diameter
* @param focusColor the color of the focus border, or {@code null}
* @param borderColor the color of the border, or {@code null}
* @param background the background color of the component, or {@code null}
*
* @since 2
*/
public static void paintOutlinedComponent( Graphics2D g, int x, int y, int width, int height,
float focusWidth, float focusWidthFraction, float focusInnerWidth, float borderWidth, float arc,
Paint focusColor, Paint borderColor, Paint background )
{
double systemScaleFactor = UIScale.getSystemScaleFactor( g );
if( systemScaleFactor != 1 && systemScaleFactor != 2 ) {
// paint at scale 1x to avoid clipping on right and bottom edges at 125%, 150% or 175%
HiDPIUtils.paintAtScale1x( g, x, y, width, height,
(g2d, x2, y2, width2, height2, scaleFactor) -> {
paintComponentBackgroundImpl( g2d, x2, y2, width2, height2,
(float) (focusWidth * scaleFactor), (float) (arc * scaleFactor) );
paintOutlinedComponentImpl( g2d, x2, y2, width2, height2,
(float) (focusWidth * scaleFactor), focusWidthFraction, (float) (focusInnerWidth * scaleFactor),
(float) (borderWidth * scaleFactor), (float) (arc * scaleFactor),
focusColor, borderColor, background );
} );
return;
}
paintComponentBackgroundImpl( g, x, y, width, height, focusWidth, arc );
paintOutlinedComponentImpl( g, x, y, width, height, focusWidth, focusWidthFraction, focusInnerWidth,
borderWidth, arc, focusColor, borderColor, background );
}
private static void paintComponentBackgroundImpl( Graphics2D g, int x, int y, int width, int height,
float focusWidth, float arc )
private static void paintOutlinedComponentImpl( Graphics2D g, int x, int y, int width, int height,
float focusWidth, float focusWidthFraction, float focusInnerWidth, float borderWidth, float arc,
Paint focusColor, Paint borderColor, Paint background )
{
g.fill( createComponentRectangle(
x + focusWidth, y + focusWidth,
width - focusWidth * 2, height - focusWidth * 2, arc ) );
// outside bounds of the border and the background
float x1 = x + focusWidth;
float y1 = y + focusWidth;
float w1 = width - focusWidth * 2;
float h1 = height - focusWidth * 2;
// fill background
// bounds: x + focusWidth, y + focusWidth, width - (focusWidth * 2), height - (focusWidth * 2)
// arc diameter: arc
if( background != null ) {
g.setPaint( background );
g.fill( createComponentRectangle( x1, y1, w1, h1, arc ) );
}
// optimization: paint focus border and border in single operation if colors are equal
if( borderColor != null && borderColor.equals( focusColor ) ) {
borderColor = null;
focusInnerWidth = Math.max( focusInnerWidth, borderWidth );
}
// paint focus border
// outer bounds: x, y, width, height
// thickness: focusWidth + focusInnerWidth
// outer arc diameter: arc + (focusWidth * 2)
// inner arc diameter: arc - (focusInnerWidth * 2)
float paintedFocusWidth = (focusWidth * focusWidthFraction) + focusInnerWidth;
if( focusColor != null && paintedFocusWidth != 0 ) {
// outside bounds of the focus border
float inset = focusWidth - (focusWidth * focusWidthFraction);
float x2 = x + inset;
float y2 = y + inset;
float w2 = width - (inset * 2);
float h2 = height - (inset * 2);
float outerArc = arc + (focusWidth * 2);
float innerArc = arc - (focusInnerWidth * 2);
// reduce outer arc slightly for small arcs to make the curve slightly wider
if( focusWidth > 0 && arc > 0 && arc < UIScale.scale( 10 ) )
outerArc -= UIScale.scale( 2f );
// consider focus width fraction
if( focusWidthFraction != 1 )
outerArc = arc + ((outerArc - arc) * focusWidthFraction);
g.setPaint( focusColor );
paintOutline( g, x2, y2, w2, h2, paintedFocusWidth, outerArc, innerArc );
}
// paint border
// outer bounds: x + focusWidth, y + focusWidth, width - (focusWidth * 2), height - (focusWidth * 2)
// thickness: borderWidth
// outer arc diameter: arc
// inner arc diameter: arc - (borderWidth * 2)
if( borderColor != null && borderWidth != 0 ) {
g.setPaint( borderColor );
paintOutline( g, x1, y1, w1, h1, borderWidth, arc );
}
}
/**
* Paints an outline at the given bounds using the given line width.
* Depending on the given arc, a rectangle, rounded rectangle or circle (if w == h) is painted.
*
* @param g the graphics context used for painting
* @param x the x coordinate of the outline
* @param y the y coordinate of the outline
* @param w the width of the outline
* @param h the height of the outline
* @param lineWidth the width of the outline
* @param arc the arc diameter used for the outside shape of the outline
*
* @since 2
*/
public static void paintOutline( Graphics2D g, float x, float y, float w, float h,
float lineWidth, float arc )
{
paintOutline( g, x, y, w, h, lineWidth, arc, arc - (lineWidth * 2) );
}
/**
* Paints an outline at the given bounds using the given line width.
* Depending on the given arc, a rectangle, rounded rectangle or circle (if w == h) is painted.
*
* @param g the graphics context used for painting
* @param x the x coordinate of the outline
* @param y the y coordinate of the outline
* @param w the width of the outline
* @param h the height of the outline
* @param lineWidth the width of the outline
* @param arc the arc diameter used for the outside shape of the outline
* @param innerArc the arc diameter used for the inside shape of the outline
*
* @since 2
*/
public static void paintOutline( Graphics2D g, float x, float y, float w, float h,
float lineWidth, float arc, float innerArc )
{
if( lineWidth == 0 || w <= 0 || h <= 0 )
return;
float t = lineWidth;
float t2x = t * 2;
Path2D border = new Path2D.Float( Path2D.WIND_EVEN_ODD );
border.append( createComponentRectangle( x, y, w, h, arc ), false );
border.append( createComponentRectangle( x + t, y + t, w - t2x, h - t2x, innerArc ), false );
g.fill( border );
}
/**
@@ -492,6 +606,9 @@ public class FlatUIUtils
if( arc <= 0 )
return new Rectangle2D.Float( x, y, w, h );
if( w == h && arc >= w )
return new Ellipse2D.Float( x, y, w, h );
arc = Math.min( arc, Math.min( w, h ) );
return new RoundRectangle2D.Float( x, y, w, h, arc, arc );
}
@@ -863,6 +980,14 @@ debug*/
.computeIfAbsent( key, k -> newInstanceSupplier.get() );
}
/**
* Returns whether the component UI for the given component can be shared
* with other components. This is only possible if it does not have styles.
*/
public static boolean canUseSharedUI( JComponent c ) {
return !FlatStylingSupport.hasStyleProperty( c );
}
//---- class RepaintFocusListener -----------------------------------------
public static class RepaintFocusListener
@@ -888,4 +1013,31 @@ debug*/
repaintComponent.repaint();
}
}
//---- class NonUIResourceBorder ------------------------------------------
private static class NonUIResourceBorder
implements Border
{
private final Border delegate;
NonUIResourceBorder( Border delegate ) {
this.delegate = delegate;
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
delegate.paintBorder( c, g, x, y, width, height );
}
@Override
public Insets getBorderInsets( Component c ) {
return delegate.getBorderInsets( c );
}
@Override
public boolean isBorderOpaque() {
return delegate.isBorderOpaque();
}
}
}

View File

@@ -54,6 +54,10 @@ import com.formdev.flatlaf.util.SystemInfo;
// https://github.com/oberth/custom-chrome
// https://github.com/rossy/borderless-window
//
// Windows 11
// https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/apply-snap-layout-menu
// https://github.com/dotnet/wpf/issues/4825#issuecomment-930442736
//
/**
* Native window border support for Windows 10 when using custom decorations.
@@ -86,6 +90,10 @@ class FlatWindowsNativeWindowBorder
if( !SystemInfo.isWindows_10_orLater )
return null;
// requires x86 architecture
if( !SystemInfo.isX86 && !SystemInfo.isX86_64 )
return null;
// load native library
if( nativeLibrary == null ) {
if( !SystemInfo.isJava_9_orLater ) {
@@ -163,11 +171,18 @@ class FlatWindowsNativeWindowBorder
return;
// install
WndProc wndProc = new WndProc( window );
if( wndProc.hwnd == 0 )
return;
try {
WndProc wndProc = new WndProc( window );
if( wndProc.hwnd == 0 )
return;
windowsMap.put( window, wndProc );
windowsMap.put( window, wndProc );
} catch( UnsatisfiedLinkError ex ) {
// catch for the case that the operating system prevents execution of DLL
// (e.g. if DLLs in temp folder are restricted)
// --> continue application without custom decorations
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
private void uninstall( Window window ) {
@@ -177,30 +192,24 @@ class FlatWindowsNativeWindowBorder
}
@Override
public void setTitleBarHeight( Window window, int titleBarHeight ) {
public void updateTitleBarInfo( Window window, int titleBarHeight, List<Rectangle> hitTestSpots,
Rectangle appIconBounds, Rectangle minimizeButtonBounds, Rectangle maximizeButtonBounds,
Rectangle closeButtonBounds )
{
WndProc wndProc = windowsMap.get( window );
if( wndProc == null )
return;
wndProc.titleBarHeight = titleBarHeight;
}
@Override
public void setTitleBarHitTestSpots( Window window, List<Rectangle> hitTestSpots ) {
WndProc wndProc = windowsMap.get( window );
if( wndProc == null )
return;
wndProc.hitTestSpots = hitTestSpots.toArray( new Rectangle[hitTestSpots.size()] );
wndProc.appIconBounds = cloneRectange( appIconBounds );
wndProc.minimizeButtonBounds = cloneRectange( minimizeButtonBounds );
wndProc.maximizeButtonBounds = cloneRectange( maximizeButtonBounds );
wndProc.closeButtonBounds = cloneRectange( closeButtonBounds );
}
@Override
public void setTitleBarAppIconBounds( Window window, Rectangle appIconBounds ) {
WndProc wndProc = windowsMap.get( window );
if( wndProc == null )
return;
wndProc.appIconBounds = (appIconBounds != null) ? new Rectangle( appIconBounds ) : null;
private static Rectangle cloneRectange( Rectangle rect ) {
return (rect != null) ? new Rectangle( rect ) : null;
}
@Override
@@ -303,14 +312,21 @@ class FlatWindowsNativeWindowBorder
HTCLIENT = 1,
HTCAPTION = 2,
HTSYSMENU = 3,
HTTOP = 12;
HTMINBUTTON = 8,
HTMAXBUTTON = 9,
HTTOP = 12,
HTCLOSE = 20;
private Window window;
private final long hwnd;
// Swing coordinates/values may be scaled on a HiDPI screen
private int titleBarHeight;
private Rectangle[] hitTestSpots;
private Rectangle appIconBounds;
private Rectangle minimizeButtonBounds;
private Rectangle maximizeButtonBounds;
private Rectangle closeButtonBounds;
WndProc( Window window ) {
this.window = window;
@@ -355,7 +371,7 @@ class FlatWindowsNativeWindowBorder
// invoked from native code
private int onNcHitTest( int x, int y, boolean isOnResizeBorder ) {
// scale-down mouse x/y
// scale-down mouse x/y because Swing coordinates/values may be scaled on a HiDPI screen
Point pt = scaleDown( x, y );
int sx = pt.x;
int sy = pt.y;
@@ -363,9 +379,26 @@ class FlatWindowsNativeWindowBorder
// return HTSYSMENU if mouse is over application icon
// - left-click on HTSYSMENU area shows system menu
// - double-left-click sends WM_CLOSE
if( appIconBounds != null && appIconBounds.contains( sx, sy ) )
if( contains( appIconBounds, sx, sy ) )
return HTSYSMENU;
// return HTMINBUTTON if mouse is over minimize button
// - hovering mouse over HTMINBUTTON area shows tooltip on Windows 10/11
if( contains( minimizeButtonBounds, sx, sy ) )
return HTMINBUTTON;
// return HTMAXBUTTON if mouse is over maximize/restore button
// - hovering mouse over HTMAXBUTTON area shows tooltip on Windows 10
// - hovering mouse over HTMAXBUTTON area shows snap layouts menu on Windows 11
// https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/apply-snap-layout-menu
if( contains( maximizeButtonBounds, sx, sy ) )
return HTMAXBUTTON;
// return HTCLOSE if mouse is over close button
// - hovering mouse over HTCLOSE area shows tooltip on Windows 10/11
if( contains( closeButtonBounds, sx, sy ) )
return HTCLOSE;
boolean isOnTitleBar = (sy < titleBarHeight);
if( isOnTitleBar ) {
@@ -382,6 +415,10 @@ class FlatWindowsNativeWindowBorder
return isOnResizeBorder ? HTTOP : HTCLIENT;
}
private boolean contains( Rectangle rect, int x, int y ) {
return (rect != null && rect.contains( x, y ) );
}
/**
* Scales down in the same way as AWT.
* See AwtWin32GraphicsDevice::ScaleDownX() and ::ScaleDownY()

View File

@@ -21,6 +21,7 @@ import java.awt.Insets;
import java.beans.PropertyChangeListener;
import java.util.function.Function;
import javax.swing.JComponent;
import com.formdev.flatlaf.FlatClientProperties;
/**
* Support for MigLayout visual paddings.
@@ -80,7 +81,7 @@ public class MigLayoutVisualPadding
return new Insets( focusWidth, focusWidth, focusWidth, focusWidth );
} else
return null;
}, "border" );
}, "border", FlatClientProperties.STYLE, FlatClientProperties.STYLE_CLASS );
}
/**
@@ -99,7 +100,7 @@ public class MigLayoutVisualPadding
c.addPropertyChangeListener( (FlatMigListener) e -> {
String propertyName = e.getPropertyName();
for( String name : propertyNames ) {
if( name == propertyName ) {
if( name.equals( propertyName ) ) {
setVisualPadding( c, getPaddingFunction.apply( c ) );
break;
}

View File

@@ -25,30 +25,91 @@ import java.awt.Color;
*/
public class ColorFunctions
{
public static Color applyFunctions( Color color, ColorFunction... functions ) {
/**
* Increase the lightness of a color in HSL color space by an absolute amount.
* <p>
* Consider using {@link #tint(Color, float)} as alternative.
*
* @param color base color
* @param amount the amount (in range 0-1) that is added to the lightness
* @return new color
* @since 2
*/
public static Color lighten( Color color, float amount ) {
return hslIncreaseDecrease( color, amount, 2, true );
}
/**
* Decrease the lightness of a color in HSL color space by an absolute amount.
* <p>
* Consider using {@link #shade(Color, float)} as alternative.
*
* @param color base color
* @param amount the amount (in range 0-1) that is subtracted from the lightness
* @return new color
* @since 2
*/
public static Color darken( Color color, float amount ) {
return hslIncreaseDecrease( color, amount, 2, false );
}
/**
* Increase the saturation of a color in HSL color space by an absolute amount.
*
* @param color base color
* @param amount the amount (in range 0-1) that is added to the saturation
* @return new color
* @since 2
*/
public static Color saturate( Color color, float amount ) {
return hslIncreaseDecrease( color, amount, 1, true );
}
/**
* Decrease the saturation of a color in HSL color space by an absolute amount.
*
* @param color base color
* @param amount the amount (in range 0-1) that is subtracted from the saturation
* @return new color
* @since 2
*/
public static Color desaturate( Color color, float amount ) {
return hslIncreaseDecrease( color, amount, 1, false );
}
/**
* Rotate the hue angle (0-360) of a color in HSL color space in either direction.
*
* @param color base color
* @param angle the number of degrees to rotate (in range -360 - 360)
* @return new color
* @since 2
*/
public static Color spin( Color color, float angle ) {
return hslIncreaseDecrease( color, angle, 0, true );
}
private static Color hslIncreaseDecrease( Color color, float amount, int hslIndex, boolean increase ) {
// convert RGB to HSL
float[] hsl = HSLColor.fromRGB( color );
float alpha = color.getAlpha() / 255f;
float[] hsla = { hsl[0], hsl[1], hsl[2], alpha * 100 };
// apply color functions
for( ColorFunction function : functions )
function.apply( hsla );
// apply HSL color change
float amount2 = increase ? amount : -amount;
if( hslIndex == 0 )
hsl[0] = (hsl[0] + amount2) % 360;
else
hsl[hslIndex] = clamp( hsl[hslIndex] + (amount2 * 100) );
// convert HSL to RGB
return HSLColor.toRGB( hsla[0], hsla[1], hsla[2], hsla[3] / 100 );
}
public static float clamp( float value ) {
return (value < 0)
? 0
: ((value > 100)
? 100
: value);
return HSLColor.toRGB( hsl[0], hsl[1], hsl[2], alpha );
}
/**
* Returns a color that is a mixture of two colors.
* <p>
* This can be used to animate a color change from {@code color1} to {@code color2}
* by invoking this method multiple times with growing {@code weight} (from 0 to 1).
*
* @param color1 first color
* @param color2 second color
@@ -79,6 +140,95 @@ public class ColorFunctions
Math.round( a2 + ((a1 - a2) * weight) ) );
}
/**
* Mix color with white, which makes the color brighter.
* This is the same as {@link #mix}{@code (Color.white, color, weight)}.
*
* @param color second color
* @param weight the weight (in range 0-1) to mix the two colors.
* Larger weight uses more of first color, smaller weight more of second color.
* @return mixture of colors
* @since 2
*/
public static Color tint( Color color, float weight ) {
return mix( Color.white, color, weight );
}
/**
* Mix color with black, which makes the color darker.
* This is the same as {@link #mix}{@code (Color.black, color, weight)}.
*
* @param color second color
* @param weight the weight (in range 0-1) to mix the two colors.
* Larger weight uses more of first color, smaller weight more of second color.
* @return mixture of colors
* @since 2
*/
public static Color shade( Color color, float weight ) {
return mix( Color.black, color, weight );
}
/**
* Calculates the luma (perceptual brightness) of the given color.
* <p>
* Uses SMPTE C / Rec. 709 coefficients, as recommended in
* <a href="https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef">WCAG 2.0</a>.
*
* @param color a color
* @return the luma (in range 0-1)
*
* @see <a href="https://en.wikipedia.org/wiki/Luma_(video)">https://en.wikipedia.org/wiki/Luma_(video)</a>
* @since 2
*/
public static float luma( Color color ) {
// see https://en.wikipedia.org/wiki/Luma_(video)
// see https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
// see https://github.com/less/less.js/blob/master/packages/less/src/less/tree/color.js
float r = gammaCorrection( color.getRed() / 255f );
float g = gammaCorrection( color.getGreen() / 255f );
float b = gammaCorrection( color.getBlue() / 255f );
return (0.2126f * r) + (0.7152f * g) + (0.0722f * b);
}
private static float gammaCorrection( float value ) {
return (value <= 0.03928f)
? value / 12.92f
: (float) Math.pow( (value + 0.055) / 1.055, 2.4 );
}
public static Color applyFunctions( Color color, ColorFunction... functions ) {
// if having only a single function of type Mix, then avoid four unnecessary conversions:
// 1. RGB to HSL in this method
// 2. HSL to RGB in Mix.apply()
// mix
// 3. RGB to HSL in Mix.apply()
// 4. HSL to RGB in this method
if( functions.length == 1 && functions[0] instanceof Mix ) {
Mix mixFunction = (Mix) functions[0];
return mix( color, mixFunction.color2, mixFunction.weight / 100 );
}
// convert RGB to HSL
float[] hsl = HSLColor.fromRGB( color );
float alpha = color.getAlpha() / 255f;
float[] hsla = { hsl[0], hsl[1], hsl[2], alpha * 100 };
// apply color functions
for( ColorFunction function : functions )
function.apply( hsla );
// convert HSL to RGB
return HSLColor.toRGB( hsla[0], hsla[1], hsla[2], hsla[3] / 100 );
}
public static float clamp( float value ) {
return (value < 0)
? 0
: ((value > 100)
? 100
: value);
}
//---- interface ColorFunction --------------------------------------------
public interface ColorFunction {

View File

@@ -0,0 +1,168 @@
/*
* Copyright 2021 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 java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
/**
* A simple cache (map) that uses soft references for the values.
*
* @author Karl Tauber
* @since 2
*/
public class SoftCache<K,V>
implements Map<K, V>
{
private final Map<K, CacheReference<K,V>> map;
private final ReferenceQueue<V> queue = new ReferenceQueue<>();
public SoftCache() {
map = new HashMap<>();
}
public SoftCache( int initialCapacity ) {
map = new HashMap<>( initialCapacity );
}
@Override
public int size() {
expungeStaleEntries();
return map.size();
}
@Override
public boolean isEmpty() {
expungeStaleEntries();
return map.isEmpty();
}
@Override
public boolean containsKey( Object key ) {
expungeStaleEntries();
return map.containsKey( key );
}
/**
* Not supported. Throws {@link UnsupportedOperationException}.
*/
@Override
public boolean containsValue( Object value ) {
throw new UnsupportedOperationException();
}
@Override
public V get( Object key ) {
expungeStaleEntries();
return getRef( map.get( key ) );
}
@Override
public V put( K key, V value ) {
expungeStaleEntries();
return getRef( map.put( key, new CacheReference<>( key, value, queue ) ) );
}
@Override
public V remove( Object key ) {
expungeStaleEntries();
return getRef( map.remove( key ) );
}
private V getRef( CacheReference<K,V> ref ) {
return (ref != null) ? ref.get() : null;
}
@Override
public void putAll( Map<? extends K, ? extends V> m ) {
expungeStaleEntries();
for( Entry<? extends K, ? extends V> e : m.entrySet() )
put( e.getKey(), e.getValue() );
}
@Override
public void clear() {
map.clear();
expungeStaleEntries();
}
@Override
public Set<K> keySet() {
expungeStaleEntries();
return map.keySet();
}
/**
* Not supported. Throws {@link UnsupportedOperationException}.
*/
@Override
public Collection<V> values() {
throw new UnsupportedOperationException();
}
/**
* Not supported. Throws {@link UnsupportedOperationException}.
*/
@Override
public Set<Entry<K, V>> entrySet() {
throw new UnsupportedOperationException();
}
/**
* Not supported. Throws {@link UnsupportedOperationException}.
*/
@Override
public void forEach( BiConsumer<? super K, ? super V> action ) {
throw new UnsupportedOperationException();
}
/**
* Not supported. Throws {@link UnsupportedOperationException}.
*/
@Override
public void replaceAll( BiFunction<? super K, ? super V, ? extends V> function ) {
throw new UnsupportedOperationException();
}
@SuppressWarnings( "unchecked" )
private void expungeStaleEntries() {
Reference<? extends V> reference;
while( (reference = queue.poll()) != null )
map.remove( ((CacheReference<K,V>)reference).key );
}
//---- class CacheReference ----
private static class CacheReference<K,V>
extends SoftReference<V>
{
// needed to remove reference from map in expungeStaleEntries()
final K key;
CacheReference( K key, V value, ReferenceQueue<? super V> queue ) {
super( value, queue );
this.key = key;
}
}
}

View File

@@ -17,6 +17,7 @@
package com.formdev.flatlaf.util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
@@ -46,16 +47,98 @@ public class StringUtils
}
public static List<String> split( String str, char delim ) {
ArrayList<String> strs = new ArrayList<>();
return split( str, delim, false, false );
}
/**
* Splits a string at the specified delimiter.
* If trimming is enabled, then leading and trailing whitespace characters are removed.
* If excludeEmpty is {@code true}, then only non-empty strings are returned.
*
* @since 2
*/
public static List<String> split( String str, char delim, boolean trim, boolean excludeEmpty ) {
int delimIndex = str.indexOf( delim );
if( delimIndex < 0 ) {
if( trim )
str = str.trim();
return !excludeEmpty || !str.isEmpty()
? Collections.singletonList( str )
: Collections.emptyList();
}
ArrayList<String> strs = new ArrayList<>();
int index = 0;
while( delimIndex >= 0 ) {
strs.add( str.substring( index, delimIndex ) );
add( strs, str, index, delimIndex, trim, excludeEmpty );
index = delimIndex + 1;
delimIndex = str.indexOf( delim, index );
}
strs.add( str.substring( index ) );
add( strs, str, index, str.length(), trim, excludeEmpty );
return strs;
}
private static void add( List<String> strs, String str, int beginIndex, int endIndex,
boolean trim, boolean excludeEmpty )
{
if( trim ) {
beginIndex = trimBegin( str, beginIndex, endIndex );
endIndex = trimEnd( str, beginIndex, endIndex );
}
if( !excludeEmpty || endIndex > beginIndex )
strs.add( str.substring( beginIndex, endIndex ) );
}
/**
* This is equal to {@code str.substring( beginIndex, endIndex ).trim()},
* but avoids temporary untrimmed substring allocation.
* If the trimmed string is empty, a shared empty string is returned.
*
* @since 2
*/
public static String substringTrimmed( String str, int beginIndex ) {
return substringTrimmed( str, beginIndex, str.length() );
}
/**
* This is equal to {@code str.substring( beginIndex ).trim()},
* but avoids temporary untrimmed substring allocation.
* If the trimmed string is empty, a shared empty string is returned.
*
* @since 2
*/
public static String substringTrimmed( String str, int beginIndex, int endIndex ) {
beginIndex = trimBegin( str, beginIndex, endIndex );
endIndex = trimEnd( str, beginIndex, endIndex );
return (endIndex > beginIndex) ? str.substring( beginIndex, endIndex ) : "";
}
/**
* This is equal to {@code str.trim().isEmpty()},
* but avoids temporary trimmed substring allocation.
*
* @since 2
*/
public static boolean isTrimmedEmpty( String str ) {
int length = str.length();
int beginIndex = trimBegin( str, 0, length );
int endIndex = trimEnd( str, beginIndex, length );
return beginIndex >= endIndex;
}
private static int trimBegin( String str, int beginIndex, int endIndex ) {
// skip leading whitespace
while( beginIndex < endIndex && str.charAt( beginIndex ) <= ' ' )
beginIndex++;
return beginIndex;
}
private static int trimEnd( String str, int beginIndex, int endIndex ) {
// skip trailing whitespace
while( beginIndex < endIndex && str.charAt( endIndex - 1 ) <= ' ' )
endIndex--;
return endIndex;
}
}

View File

@@ -39,7 +39,9 @@ public class SystemInfo
public static final boolean isMacOS_10_15_Catalina_orLater;
// OS architecture
/** @since 2 */ public static final boolean isX86;
/** @since 1.1 */ public static final boolean isX86_64;
/** @since 2 */ public static final boolean isAARCH64;
// Java versions
public static final long javaVersion;
@@ -76,7 +78,9 @@ public class SystemInfo
// OS architecture
String osArch = System.getProperty( "os.arch" );
isX86 = osArch.equals( "x86" );
isX86_64 = osArch.equals( "amd64" ) || osArch.equals( "x86_64" );
isAARCH64 = osArch.equals( "aarch64" );
// Java versions
javaVersion = scanVersion( System.getProperty( "java.version" ) );

View File

@@ -192,7 +192,15 @@ public class UIScale
if( font == null )
font = UIManager.getFont( "Label.font" );
float newScaleFactor;
setUserScaleFactor( computeFontScaleFactor( font ), true );
}
/**
* For internal use only.
*
* @since 2
*/
public static float computeFontScaleFactor( Font font ) {
if( SystemInfo.isWindows ) {
// Special handling for Windows to be compatible with OS scaling,
// which distinguish between "screen scaling" and "text scaling".
@@ -204,33 +212,35 @@ public class UIScale
// - Settings > Display > Scale and layout
// - Settings > Ease of Access > Display > Make text bigger (100% - 225%)
if( font instanceof UIResource ) {
if( isSystemScalingEnabled() ) {
// Do not apply own scaling if the JRE scales using Windows screen scale factor.
// If user increases font size in Windows 10 settings, desktop property
// "win.messagebox.font" is changed and FlatLaf uses the larger font.
newScaleFactor = 1;
} else {
// If the JRE does not scale (Java 8), the size of the UI font
// (usually from desktop property "win.messagebox.font")
// combines the Windows screen and text scale factors.
// But the font in desktop property "win.defaultGUI.font" is only
// scaled with the Windows screen scale factor. So use it to compute
// our scale factor that is equal to Windows screen scale factor.
Font winFont = (Font) Toolkit.getDefaultToolkit().getDesktopProperty( "win.defaultGUI.font" );
newScaleFactor = computeScaleFactor( (winFont != null) ? winFont : font );
Font uiFont = (Font) Toolkit.getDefaultToolkit().getDesktopProperty( "win.messagebox.font" );
if( uiFont == null || uiFont.getSize() == font.getSize() ) {
if( isSystemScalingEnabled() ) {
// Do not apply own scaling if the JRE scales using Windows screen scale factor.
// If user increases font size in Windows 10 settings, desktop property
// "win.messagebox.font" is changed and FlatLaf uses the larger font.
return 1;
} else {
// If the JRE does not scale (Java 8), the size of the UI font
// (usually from desktop property "win.messagebox.font")
// combines the Windows screen and text scale factors.
// But the font in desktop property "win.defaultGUI.font" is only
// scaled with the Windows screen scale factor. So use it to compute
// our scale factor that is equal to Windows screen scale factor.
Font winFont = (Font) Toolkit.getDefaultToolkit().getDesktopProperty( "win.defaultGUI.font" );
return computeScaleFactor( (winFont != null) ? winFont : font );
}
}
} else {
// If font was explicitly set from outside (is not a UIResource)
// use it to compute scale factor. This allows applications to
// use custom fonts (e.g. that the user can change in UI) and
// get scaling if a larger font size is used.
// E.g. FlatLaf Demo supports increasing font size in "Font" menu and UI scales.
newScaleFactor = computeScaleFactor( font );
}
} else
newScaleFactor = computeScaleFactor( font );
setUserScaleFactor( newScaleFactor, true );
// If font was explicitly set from outside (is not a UIResource),
// or was set in FlatLaf properties files (is a UIResource),
// use it to compute scale factor. This allows applications to
// use custom fonts (e.g. that the user can change in UI) and
// get scaling if a larger font size is used.
// E.g. FlatLaf Demo supports increasing font size in "Font" menu and UI scales.
}
return computeScaleFactor( font );
}
private static float computeScaleFactor( Font font ) {

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