Compare commits

...

319 Commits
0.20 ... 0.34

Author SHA1 Message Date
Karl Tauber
8e423b4552 release 0.34 2020-05-08 14:53:16 +02:00
Karl Tauber
0e288c955c Extras: added publishing tasks to build.gradle.kts 2020-05-08 14:44:41 +02:00
Karl Tauber
7e5c599cc0 added user scale factor to UI defaults to allow layout managers (e.g. MigLayout) to use it
(see https://github.com/mikaelgrev/miglayout/pull/76)
2020-05-07 23:28:57 +02:00
Karl Tauber
a961001a4b reorder entries in JAR file to fix issues #13 and #93 2020-05-07 14:45:22 +02:00
Karl Tauber
27a347db34 PopupMenu on macOS: enabled drop shadows for popup menus and combobox popups (issue #94) 2020-05-05 19:20:48 +02:00
Karl Tauber
b228dbb2df Demo on macOS: enabled screen menu bar by default 2020-05-05 19:10:57 +02:00
Karl Tauber
09cffc4340 UIDefaultsDump: avoid locale specific decimal separators in dumps 2020-05-05 18:53:31 +02:00
Karl Tauber
e79880d305 ToolTip: made border darker (to make it better and no longer paint disabled tips (issue #94) 2020-05-05 18:44:54 +02:00
Karl Tauber
34266761d1 UIDefaultsDump: dump FlatLineBorder parameters because they may be specified in properties files 2020-05-05 16:34:30 +02:00
Karl Tauber
77f17eaa3e FlatPropertiesLaf class added that allows creating FlatLaf theme from properties (issue #97) 2020-05-05 15:13:21 +02:00
Karl Tauber
ac70342cb3 Menus: made check background margin smaller (issue #96) 2020-05-05 13:56:41 +02:00
Karl Tauber
d2f16dcaf3 Menus:
- added 1px to menu item top and bottom margin
- changed gap between menu item icon and text from 4 to 6
- improved colors of checked menu items that have a icon

(issue #96)
2020-05-05 12:31:33 +02:00
Karl Tauber
abcce2bf68 Table: fixed inconsistent table selection / move shortcuts (issue #95) 2020-05-04 13:30:42 +02:00
Karl Tauber
514487074b Menus: after Alt+Tab to other window and back, activating menu with Alt key did not always work (issue #43) 2020-05-04 12:08:47 +02:00
Karl Tauber
f014e2473f Menus: on Windows, releasing Alt key now activates the menu bar (issue #43) 2020-05-04 10:57:10 +02:00
Karl Tauber
80981f7027 Demo: added "Extras" tab 2020-05-03 19:34:21 +02:00
Karl Tauber
8e6e971b51 IntelliJ Themes Demo: theme save and github buttons were not enabled when starting demo with active IntelliJ theme 2020-05-03 18:23:16 +02:00
Karl Tauber
4bd3b889dc FlatSVGIcon: support color filtering 2020-05-03 18:21:00 +02:00
Karl Tauber
464787dc1e FlatSVGIcon: use grayFilter and graphics proxy to paint disabled icons without bitmaps 2020-05-02 23:48:46 +02:00
Karl Tauber
a2541a9659 Menus: added gap between accelerator and arrow in menu items (issue #91) 2020-05-02 19:16:33 +02:00
Karl Tauber
099dd87241 UIDefaultsLoader: removed support for deprecated variable prefix '@@' 2020-05-02 16:20:17 +02:00
Karl Tauber
38eb914420 Mnemonics: scale underline; added mnemonic test app
FlatTestFrame: Metal Laf is now at F12 so that F10 is unused because F10 is a standard key to move focus to menu bar
2020-05-02 14:38:54 +02:00
Karl Tauber
162215b1cf UIDefaultsLoader:
- support percentage in rgb() and rgba() functions
- support rgba(color,alpha) to add alpha to any color
2020-05-02 11:52:53 +02:00
Karl Tauber
c6883f7a92 ToolTip: use BasicHTML.propertyKey to check whether tooltip contains HTML 2020-05-02 00:41:11 +02:00
Karl Tauber
584286b794 Demo: wrap OptionPanePanel in JPanel to avoid that JFormDesigner tries to convert it to a container when opening DemoFrame.jfd 2020-05-02 00:33:47 +02:00
Karl Tauber
a48713b7ca no longer always show mnemonics when a menu bar is active or a popup menu is visible (issue #43) 2020-05-01 00:22:04 +02:00
Karl Tauber
8f10c2d8bf Menus: removed now unused *.evenHeight from list of UI defaults 2020-04-30 22:33:42 +02:00
Karl Tauber
5c0de9aa1c macOS: Fixed NPE if using JMenuBar in JInternalFrame and macOS screen menu bar is enabled (issue #90) 2020-04-30 13:38:23 +02:00
Karl Tauber
5553fd6538 CHANGELOG.md: added changes made in 'menu-layout' branch 2020-04-30 00:54:36 +02:00
Karl Tauber
e3ed47b37c show mnemonics always when a menu bar is active or a popup menu is visible 2020-04-29 23:56:15 +02:00
Karl Tauber
976353d770 Menus: on Windows, pressing F10 now activates the menu bar without showing a menu popup 2020-04-29 23:29:34 +02:00
Karl Tauber
6fc216dff5 Menus: fixed text color of selected menu items that use HTML (issue #87) 2020-04-29 19:22:09 +02:00
Karl Tauber
3f3961d255 fixed broken FlatTestLaf.properties 2020-04-29 19:14:18 +02:00
Karl Tauber
875637bc6d Menus: support switching "underline" menu selection type at any time without updating UI (issue #49) 2020-04-29 14:46:33 +02:00
Karl Tauber
395333cb3d Merge branch 'origin/menu-layout' into master 2020-04-29 13:39:51 +02:00
Karl Tauber
870d039541 hide mnemonics if window is deactivated (e.g. Alt+Tab to another window) (issue #43) 2020-04-29 12:06:00 +02:00
Karl Tauber
e8c8bece3f Menus: support "underline" menu selection type (suggested in issue #49) 2020-04-29 00:26:25 +02:00
Karl Tauber
bd2f5dd6fe Menus: 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) (issue #3) 2020-04-28 18:00:01 +02:00
Karl Tauber
73f78d47ae refactored mnemonic code into own class 2020-04-28 12:02:10 +02:00
Karl Tauber
8f60755f02 release 0.33 2020-04-27 18:39:02 +02:00
Karl Tauber
44c455419b IntelliJ Themes: added Java 9 module descriptor to flatlaf-intellij-themes-<version>.jar 2020-04-27 18:25:49 +02:00
Karl Tauber
129bc9b3ae IntelliJ Themes Pack: use absolute resource location for loading themes (PR #88, issue #89) 2020-04-27 16:28:38 +02:00
Karl T
08ba7dd065 Merge pull request #88 from matt-pan/patch-1
IntelliJ Themes: Fix relative resource location for Material UI Lite Themes
2020-04-27 16:14:50 +02:00
Karl Tauber
dd2cf50a39 Menus: use simpler method to compute center offset (same as in SwingUtilities.layoutCompoundLabel()), which gives same results and avoids floats 2020-04-27 13:14:11 +02:00
Karl Tauber
06eeced5b2 Menus: made accelerator text in dark themes brighter; updated UI defaults dumps (issue #3) 2020-04-27 12:07:06 +02:00
Karl Tauber
be23e5709d Menus: support alignment and text position properties (issue #3) 2020-04-27 11:52:11 +02:00
Karl Tauber
2735185eb9 Menus: fixed icon in top-level JMenu (issue #3) 2020-04-26 14:20:09 +02:00
Karl Tauber
41dd0acfa3 Menus: use disabled and pressed icons (issue #3) 2020-04-26 11:27:59 +02:00
Karl Tauber
115a2df2b0 Menus: support HTML in new layout (issue #3) 2020-04-26 10:35:23 +02:00
Karl Tauber
fcbb3aeed1 Menus: new menu item layout and renderer
- stable left margin (always space for one icon)
- right aligned accelerators
- larger gap between text and accelerator

current limitations:
- no HTML text support
- text not vertically aligned with other menu items if icons have different sizes
- vertical/horizontal alignment/textPosition properties are ignored

(issues #3 and #54)
2020-04-26 10:13:52 +02:00
matt-pan
e9cb85127a Fix relative resource location
Using flatlaf intellijthemes as a dependency does not work for all themes from the subfolder 'material-theme-ui-lite' because of the usage of ".." to  to load the resource.
2020-04-25 17:46:57 +02:00
Karl Tauber
c9c703fe98 support multi-resolution images in disabled icons on Java 9+ (e.g. @2x icons on macOS) (issue #70) 2020-04-24 17:07:30 +02:00
Karl Tauber
0141dfbea2 CHANGELOG.md: added IntelliJ Theme fixes 2020-04-24 00:55:49 +02:00
Karl Tauber
fb7dafbc39 Merge branch 'disabled-icons' into master 2020-04-24 00:54:05 +02:00
Karl Tauber
0660f9a511 improved creation of disabled grayscale icons (issue #70) 2020-04-24 00:46:16 +02:00
Karl Tauber
a39ae5a8c5 FlatDisabledIconsTest: support palette icons 2020-04-24 00:16:44 +02:00
Karl Tauber
03e22e3dbf Demo: exclude module-info.class from JAR 2020-04-23 23:54:54 +02:00
Karl Tauber
d5e9fd0e5c IntelliJ Themes:
- fixed ComboBox size and Spinner border in all Material UI Lite themes
- limit tree row height in all Material UI Lite themes and some other themes
2020-04-23 23:46:14 +02:00
Karl Tauber
141138ebea IntelliJ Themes Pack: added readme 2020-04-23 18:16:57 +02:00
Karl Tauber
9026efeb26 release 0.32 2020-04-23 16:04:31 +02:00
Karl Tauber
2ab023beb0 UIDefaultsDump: used FlatAllIJThemes instead of IJThemesManager to get list of IJ themes 2020-04-23 14:02:25 +02:00
Karl Tauber
8e471fd720 IntelliJ Themes: generated Java classes for all themes (used IJThemesClassGenerator) 2020-04-23 13:59:59 +02:00
Karl Tauber
13cbbd8bc1 IntelliJ Themes: moved themes into own sub-project and build a JAR that contains all themes 2020-04-23 11:06:12 +02:00
Karl Tauber
b08ccc9767 UIDefaultsLoader: no longer support/use derived colors without base colors 2020-04-22 11:49:44 +02:00
Karl Tauber
801a7023a4 IntelliJ Themes: fixed toggle button selected backgrounds (issue #86) 2020-04-22 10:30:14 +02:00
Karl Tauber
dd06b554da ToggleButton: compute selected background color based on current component background (issue #32) 2020-04-22 09:48:58 +02:00
Karl Tauber
23f0504b30 IntelliJ Themes: fixed toggle button unselected background in most themes and foreground in Darcula and One Dark themes (issue #86) 2020-04-21 14:39:11 +02:00
Karl Tauber
262d172cde IntelliJ Themes: removed code that is obsolete since supporting gradient button background/border colors in commit de82dac8
make sure that gradient colors are predefined for improved compatibility
this fixes button background in Arc themes and toggle button background in Dark Flat and Light Flat themes
2020-04-21 10:02:11 +02:00
Karl Tauber
be81cb7876 IntelliJ Themes Demo: removed IntelliJ Light Preview theme because Flat light already uses colors from this theme since commit 78d5e03a 2020-04-21 09:41:53 +02:00
Karl Tauber
aaf9bd33cb UIDefaultsDump: support dumping IntelliJ themes (disabled)
can be used to check changes to UI defaults when modifying the IntelliJ theme converter
2020-04-21 09:37:24 +02:00
Karl Tauber
7381e2141f IntelliJ Themes Demo: updated Dracula, Gruvbox and Hiberbee themes (used IJThemesUpdater) 2020-04-21 00:10:18 +02:00
Karl Tauber
3923d941c1 IntelliJ Themes Demo: updated Material UI Lite themes (used IJThemesUpdater)
List.selectionInactiveBackground and Tree.selectionInactiveBackground now have better visible colors; other changes seem to be not used in FlatLaf
2020-04-20 23:28:39 +02:00
Karl Tauber
d134c33499 release 0.31 2020-04-20 11:44:00 +02:00
Karl Tauber
a2b615d4a7 focus indication border (or background) no longer hidden when temporary loosing focus (e.g. showing a popup menu) 2020-04-20 11:27:29 +02:00
Karl Tauber
37ecd9bd4f FlatDisabledIconsTest: renamed @2x_dark.png icons to _dark@2x.png so that they are automatically loaded on macOS Retina displays 2020-04-19 10:23:40 +02:00
Karl Tauber
2e1acb7871 List, Table and Tree: item selection color of focused components no longer change from blue to gray when temporary loosing focus (e.g. showing a popup menu) 2020-04-17 19:14:48 +02:00
Karl Tauber
2250185487 Testing: FlatDisabledIconsTest: use intellij dark icons in dark themes 2020-04-14 12:41:14 +02:00
Karl Tauber
97a1bf90a4 README.md: added Total Validator and GUIslice Builder to list of projects that use FlatLaf 2020-04-14 10:16:37 +02:00
Karl Tauber
73cb63c9f9 Testing: added FlatDisabledIconsTest to compare different methods to create disabled icons 2020-04-13 15:48:07 +02:00
Karl Tauber
f61f6b6006 Merge pull request #72 from basix86:disabledIcon into branch disabled-icons
Improve disabled button rendering #70
2020-04-11 13:57:05 +02:00
Karl Tauber
7d3ffbc45a README.md: added MegaMek and MekHQ to list of projects that use FlatLaf 2020-04-09 17:26:08 +02:00
Karl Tauber
93ac6fa88a README.md: added XMLmind XML Editor, MeteoInfo and lsfusion platform to list of projects that use FlatLaf 2020-04-09 14:43:14 +02:00
Karl Tauber
09d19a13b7 release 0.30 2020-04-09 13:51:11 +02:00
Karl Tauber
9eaee8d2c4 Windows: fixed rendering of Unicode characters (issue #81) 2020-04-09 13:39:37 +02:00
Karl Tauber
4da0c342f8 Theme Editor: paint real colors and HSL values in overlay on right side of editor (instead of behind editor text; previous commit) 2020-04-06 15:42:58 +02:00
Karl Tauber
70fed22737 Theme Editor: use real colors as background of color strings 2020-04-05 23:18:39 +02:00
Karl Tauber
acb62e347a Theme Editor: mark invalid color values with a curly underline 2020-04-05 18:04:28 +02:00
Karl Tauber
78d06d82d6 Theme Editor: fixed mark occurrences for property references (starting with '$') and property references in function parameters 2020-04-05 17:16:09 +02:00
Karl Tauber
4777bdd250 Theme Editor: do not mark token at caret if it does not occur elsewhere 2020-04-05 15:23:40 +02:00
Karl Tauber
266e9d92d5 Theme Editor: enabled mark occurrences 2020-04-05 14:37:25 +02:00
Karl Tauber
54c14d0dc8 Theme Editor: added editor theme 2020-04-05 11:56:28 +02:00
Karl Tauber
d59d353c2e Theme Editor: added token maker (based on .properties token maker) 2020-04-04 23:32:48 +02:00
Karl Tauber
204da2175b Theme Editor: initial commit 2020-04-04 14:13:20 +02:00
basix86
a8f659f2ac Pull Request #72: Improve disabled button rendering #70 2020-04-02 21:25:41 +02:00
basix86
a878ebc368 Merge branch 'master' into disabledIcon 2020-04-02 21:25:41 +02:00
Karl Tauber
152f235ca1 release 0.29 2020-04-01 23:26:47 +02:00
Karl Tauber
d094709dc8 ComboBox: no longer ignore JComboBox.prototypeDisplayValue when computing popup width (issue #80) 2020-03-31 18:53:55 +02:00
Karl Tauber
97d5792341 ComboBox: made class FlatComboPopup protected to allow subclassing (issue #80) 2020-03-31 17:29:12 +02:00
Karl Tauber
9429ba7d48 support specifying custom scale factor in system property flatlaf.uiScale also for Java 9 and later 2020-03-31 12:17:05 +02:00
Karl Tauber
af89dd13c1 support changing default font used for all components with automatic scaling UI if using larger font 2020-03-31 12:15:51 +02:00
Karl Tauber
60c6c5b37a FlatLineBorder: support specifying painted line thickness 2020-03-29 23:30:04 +02:00
Karl Tauber
5ed40cab1d Demo: support using own FlatLaf themes (.properties files) that are located in working directory of Demo application 2020-03-29 18:02:35 +02:00
Karl Tauber
1bebfe9cf2 IntelliJ Themes Demo: updated Arc, Arc Orange and Hiberbee themes (used IJThemesUpdater) 2020-03-28 09:41:51 +01:00
Karl Tauber
e2618c37a2 Testing: added "size variant" combobox to control bar if Aqua or Nimbus LaF are active 2020-03-28 09:41:03 +01:00
Karl Tauber
f2ab848c46 FlatOptionPaneTest: scroll pane added 2020-03-27 23:49:25 +01:00
Karl Tauber
93b82c0e97 FlatDefaultsAddon: added afterDefaultsLoading() method to allow modification of UI defaults by Addons 2020-03-27 23:21:55 +01:00
Karl Tauber
4ac5ad06f2 IntelliJ Themes: simplified applying theme properties to UI defaults 2020-03-27 18:54:30 +01:00
Karl Tauber
a3788038bb Tree: fixed repainting wide selection on focus gained/lost 2020-03-27 10:51:20 +01:00
Karl Tauber
12af2de99e no longer use system property sun.java2d.uiScale (Java 8 only) 2020-03-27 10:44:43 +01:00
Karl Tauber
225b722b1b Linux: fixed wrong font size if GDK_SCALE environment variable is set or if running on JetBrains Runtime (issue #69) 2020-03-26 17:20:09 +01:00
Karl Tauber
1d9c8ca65e Linux: fixed scaling if GDK_SCALE environment variable is set or if running on JetBrains Runtime (issue #69) 2020-03-26 13:06:12 +01:00
Karl Tauber
e51ffe2a1c release 0.28 2020-03-16 22:47:44 +01:00
Karl Tauber
c706a79f74 UIScale: fixed NPE in getSystemScaleFactor() (occurred in progress bar on startup in NB) 2020-03-16 22:46:33 +01:00
Karl Tauber
2608061d48 reviewed (and tested) all key bindings on macOS 2020-03-16 15:20:17 +01:00
Karl Tauber
df1634de3d FlatTestFrame: add JGoodies Windows LaF only when running on Windows 2020-03-15 10:21:28 +01:00
Karl Tauber
4aeabea3fe UI defaults: updated FlatLightLaf_InputMap_1.8.0_202-mac.txt on Mac 2020-03-15 10:16:28 +01:00
Karl Tauber
eb30f9d5bf copy all font attributes in FlatUIUtils.nonUIResource() and when scaling fonts (issue #75) 2020-03-12 11:22:43 +01:00
Karl Tauber
de718f847c README.md: added KeyStore Explorer and OWASP Zed Attack Proxy (ZAP) to list of projects that use FlatLaf 2020-03-12 11:15:10 +01:00
mmatessi
8ee6588d46 fix review #70 2020-03-05 13:08:49 +01:00
mmatessi
7c25f087fb NPE getDisabledIcon Fix 2020-03-05 13:07:15 +01:00
mmatessi
d0b0f098d9 disabledIcon 2020-03-05 13:07:15 +01:00
Karl Tauber
8835e20bfc .editorconfig: added Java code style for IntelliJ IDEA (#71) 2020-02-28 15:00:04 +01:00
Karl Tauber
92258f3ba3 ScrollBar: improved colors
Table: use color functions
2020-02-27 12:49:43 +01:00
Karl Tauber
1bda7595dd UI defaults: support dumping IntelliJ and Darcula themes (disabled) 2020-02-27 11:52:42 +01:00
Karl Tauber
60557fc8c8 reviewed (and tested) all key bindings on Windows 2020-02-26 23:47:38 +01:00
Karl Tauber
1a4a7831f6 UI defaults: support dumping 3rd party LaFs 2020-02-26 23:34:49 +01:00
Karl Tauber
d1415a8c53 TabbedPane: support Ctrl+TAB/Ctrl+Shift+TAB to switch to next/previous tab if a child of tabbedpane has focus 2020-02-26 00:11:10 +01:00
Karl Tauber
0d4f33ac6e ScrollBar: fixed left/right keys in right-to-left component orientation 2020-02-25 16:59:30 +01:00
Karl Tauber
87100bef7b re-worked Aqua LaF initialization of macOS 2020-02-24 22:51:36 +01:00
Karl Tauber
958dfa8ae9 UI defaults: get rid of Aqua InputMaps on macOS 2020-02-24 20:44:14 +01:00
Karl Tauber
d3752573e7 FlatInputMaps: use static import for DefaultEditorKit and changed order of keys
(nothing else changed)
2020-02-24 13:16:24 +01:00
Karl Tauber
cfd07cbcc8 PasswordField: warn about enabled Caps Lock 2020-02-23 13:15:12 +01:00
Karl Tauber
33e6ce1673 UI defaults: get rid of unused Aqua UI defaults on macOS
UI defaults on macOS are now (nearly) equal to other platforms.
There are only minor platform specific differences.
InputMaps are still used from Aqua LaF.
2020-02-23 10:57:37 +01:00
Karl Tauber
00ccda83f9 UIScale: default font size on macOS is 13 2020-02-22 14:39:43 +01:00
Karl Tauber
8d66cce6eb README.md: fixed Twitter link 2020-02-22 14:35:41 +01:00
Karl Tauber
ba35fb7525 Panel: added UI delegate 2020-02-21 22:28:58 +01:00
Karl Tauber
4fd2b24b10 README.md: fixed Twitter link 2020-02-21 19:10:27 +01:00
Karl Tauber
5f7a33b085 README.md: Buzz section added 2020-02-21 18:56:32 +01:00
Karl Tauber
65bc5b1f31 README.md: added mendelson AS2, AS4 and OFTP2 to list of projects that use FlatLaf 2020-02-21 18:42:44 +01:00
Karl Tauber
dd155e9f89 CheckBox and RadioButton: fixed NPE when button has children (similar to PR #68) 2020-02-21 16:52:38 +01:00
Karl T
5c4ef3b0f5 Merge pull request #68 from basix86/FlatButtonUi
FlatButtonUI getPreferredSize nullPointer fix
2020-02-21 16:33:56 +01:00
mmatessi
2129a48cc8 FlatButtonUI.getPreferredSize fix nullPointer when button.getComponentCount()>0 2020-02-21 10:34:57 +01:00
mmatessi
c0f2784599 FlatButtonUI.getPreferredSize fix nullPointer when button.getComponentCount()>0 2020-02-21 10:32:25 +01:00
Karl Tauber
fee00b2acb UI defaults: replaced "base" Metal LaF with BasicLookAndFeel (on Windows and Linux)
(UI defaults dumps did not change, so UI defaults are equal to Metal defaults)
2020-02-19 16:47:02 +01:00
Karl Tauber
ae8093313e UI defaults: add text antialiasing hint
(preparation for replacing "base" Metal LaF with BasicLookAndFeel)
2020-02-19 15:35:29 +01:00
Karl Tauber
5a4e321f78 UI defaults: added/modified InputMaps (on Windows and Linux) so that they are equal to Metal LaF InputMaps when using Basic LaF as base
(preparation for replacing "base" Metal LaF with BasicLookAndFeel)
2020-02-19 09:15:20 +01:00
Karl Tauber
9d1ed241b9 UI defaults: removed optional "pressed " from InputMap dumps (to make it easier to read) 2020-02-18 22:41:04 +01:00
Karl Tauber
b63cd241d2 refactored InputMap code into own class 2020-02-18 18:09:49 +01:00
Karl Tauber
1e4f2d85a2 UI defaults: removed FlatDarkLaf InputMap dumps because they are equal to the Light versions 2020-02-18 18:08:14 +01:00
Karl Tauber
5cb7be4a64 UI defaults: added Java 8 - 13 dumps of NimbusLookAndFeel
(only checked in dumps that are not equal to version predecessor)
2020-02-18 17:45:27 +01:00
Karl Tauber
0cd9068c0e UI defaults: split InputMap dumps into own files
(only checked in dumps that are not equal to version predecessor)
2020-02-18 17:40:21 +01:00
Karl Tauber
9d6afe3bde UI defaults: removed dumps that are equal to version predecessor 2020-02-18 17:04:24 +01:00
Karl Tauber
52702b5267 ColorChooser: use scaled dimension instead of temporary modifying UI defaults 2020-02-18 14:32:15 +01:00
Karl Tauber
cd144ff067 UI defaults: added UI values that are defined in MetalLookAndFeel but not in BasicLookAndFeel
also added UI values that have different values in Metal and Basic LaF

(preparation for replacing "base" Metal LaF with BasicLookAndFeel)
2020-02-18 13:49:19 +01:00
Karl Tauber
dd9784b3f2 UI defaults: added UI values that are defined in MetalLookAndFeel but not in BasicLookAndFeel; also added UI values that have different values in Metal and Basic LaF
(preparation for replacing "base" Metal LaF with BasicLookAndFeel)
2020-02-18 13:36:07 +01:00
Karl Tauber
d781b3d4a7 FlatLaf.getDefaults() simplified 2020-02-18 13:24:34 +01:00
Karl Tauber
790f490674 replaced MetalRootPaneUI on Windows and Linux (issue #47)
(preparation for replacing "base" Metal LaF with BasicLookAndFeel)
2020-02-18 12:52:53 +01:00
Karl Tauber
004a5cb765 UI defaults: added some missing colors an set unused colors to useful values 2020-02-18 12:11:58 +01:00
Karl Tauber
408b2d8376 Merge branch 'uidefaults-review' into master
# Conflicts:
#	flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/uidefaults/FlatDarkLaf_1.8.0_202.txt
#	flatlaf-testing/src/main/resources/com/formdev/flatlaf/testing/uidefaults/FlatLightLaf_1.8.0_202.txt
2020-02-18 00:43:17 +01:00
Karl Tauber
3aa53ff3be CHANGELOG.md: Gradianto themes 2020-02-17 23:36:22 +01:00
Karl Tauber
73b642799d UI defaults dumps updated 2020-02-17 23:35:38 +01:00
Karl Tauber
b522500379 TextField, FormattedTextField and PasswordField: support round border (issue #65) 2020-02-17 22:39:16 +01:00
Karl Tauber
f736ed401f - tuned/fixed component border painting
- HiDPIUtils.paintAtScale1x() now also works if the Graphics is scaled (as in FlatPaintingTest)
- FlatPaintingTest added
2020-02-17 22:19:36 +01:00
Karl Tauber
c02f824d74 FlatInspector: added layout manager and insets UI resource 2020-02-17 11:28:56 +01:00
Karl Tauber
6f9a61de56 IntelliJ Themes Demo: updated Arc, Arc Orange and Nord themes (used IJThemesUpdater) 2020-02-17 11:22:07 +01:00
Karl Tauber
4cebeda37a IntelliJ Themes: added popular Gradianto IntelliJ themes to demo 2020-02-17 11:16:27 +01:00
Karl Tauber
4275005a64 FlatTestFrame: avoid initializing LaF classes in isClassAvailable() because this invokes static class initialization, which logs a reflection warning on Java 13 for WebLaf 2020-02-17 10:13:48 +01:00
Karl Tauber
5f40ab130e build.gradle.kts:
- added version info to manifest
- added META-INF/LICENSE
- Java source/target compatibility now defined in single location
- defined source file encoding for java compiler
2020-02-16 16:58:01 +01:00
Karl Tauber
7489526eb7 build.gradle.kts:
- plugin versions now defined in single location (settings.gradle.kts)
- going back to Gradle 6.1.1
- using "extra" properties for bintray user and key
- allow easy enabling/disabling bintray upload dryMode and publishing
2020-02-16 12:18:08 +01:00
Karl Tauber
e439d91763 build.gradle.kts: fix bintray upload (broken since adding snapshot publishing) 2020-02-15 00:03:57 +01:00
Karl Tauber
793969e39b downgrade from Gradle 6.1.1 to 6.1 to check whether this fixes the broken bintray upload (worked in 0.26 with 6.1) 2020-02-14 16:30:46 +01:00
Karl Tauber
9f7ffe8d77 travis: changed condition for release stage again (because it did not start) 2020-02-14 16:04:53 +01:00
Karl Tauber
eecb867227 travis: changed condition for release stage (because it did not start) 2020-02-14 16:00:04 +01:00
Karl Tauber
bff9f135e6 release 0.27 2020-02-14 15:41:18 +01:00
Karl Tauber
03627281d7 ToolBar: added empty space around toolbar (issue #56) 2020-02-14 13:59:14 +01:00
Karl Tauber
c83b4093f0 ToolBar: added empty space around buttons in toolbar (issue #56) 2020-02-14 12:53:30 +01:00
Karl Tauber
7f9f22df3e merged PR #61 into master (with minor modifications)
Bug #60 Illegal reflective access operation on mac
2020-02-13 17:13:07 +01:00
Karl Tauber
fd48582a9f ToolBar: no longer use special rollover border for buttons in toolbar (issue #36) 2020-02-13 15:53:54 +01:00
Karl Tauber
e5761128f9 ToggleButton: make toggle button square if it has an icon but no text or text is "..." or a single character 2020-02-13 14:51:36 +01:00
Karl Tauber
87dd5a9ebb PasswordField: get echoChar from .properties files and no longer hard code it on macOS 2020-02-13 11:17:29 +01:00
Karl Tauber
11950f8b4d UI defaults: removed unused UI defaults from "base" Metal LaF 2020-02-13 10:52:19 +01:00
smile atom
f2ddfadc9d fix: #60 should compile on JDK 8 2020-02-12 19:01:35 -08:00
smile atom
b1a7983f18 fix: #60 Illegal reflective access operation on mac 2020-02-12 18:46:39 -08:00
Karl Tauber
b319cb278b UI defaults: updated dumps on macOS 2020-02-12 18:47:22 +01:00
Karl Tauber
78e3d781fc UI defaults: changed dump format of characters and updated dumps on Windows 2020-02-12 18:43:35 +01:00
Karl Tauber
34834917b0 UI defaults:
- added macOS Java 8 - 13 dumps of AquaLookAndFeel
- added macOS Java 8 dumps of FlatLightLaf and FlatDarkLaf

used macOS Mojave 10.14.
2020-02-12 14:23:56 +01:00
Karl Tauber
9446c287e9 UI defaults:
- class UIDefaultsDump implemented to dump UI defaults to text files
- added Java 8 - 13 dumps of BasicLookAndFeel, MetalLookAndFeel, WindowsLookAndFeel
- added Java 8 dumps of FlatLightLaf and FlatDarkLaf
2020-02-12 10:42:07 +01:00
Bill Culp
31b0cf396e fix #60 fix typo 2020-02-11 15:10:58 -08:00
Bill Culp
00bb13c230 fix #60 just use getDeclaredConstructor() 2020-02-11 15:05:22 -08:00
Bill Culp
3bf09ee731 fix #60 Illegal reflective access operation on mac 2020-02-11 15:00:28 -08:00
Bill Culp
5b07941c4c Merge branch 'master' of https://github.com/JFormDesigner/FlatLaf into bug-60 2020-02-11 14:57:51 -08:00
Karl Tauber
23c30ec46d FlatComponentsTest: add checkbox to change contentAreaFilled of all buttons (for issue #58) 2020-02-11 18:44:35 +01:00
Karl Tauber
22c06300f1 merged PR #63 (for issue #58) into master (with modifications)
bug: AbstractButton's ContentAreaFilled=false not honored when parent is a CellRendererPane
2020-02-11 18:34:44 +01:00
Karl Tauber
37cca1b106 merged PR #62 into master (with minor modifications)
Feat 59 Option to allow tabbed pane separator to take full height
2020-02-11 16:48:27 +01:00
Karl Tauber
f0a49c806e DesktopPane support implemented (issues #39 and #11) 2020-02-11 15:38:32 +01:00
Bill Culp
a1d5f65588 bug: AbstractButton's ContentAreaFilled=false not honored when parent is a CellRendererPane
docs: AbstractButton:setContentAreaFilled

Sets the contentAreaFilled property. If true the button will paint the content area. If you wish to have a transparent button, such as an icon only button, for example, then you should set this to false. Do not call setOpaque(false). The default value for the the contentAreaFilled property is true.
This function may cause the component's opaque property to change.

The exact behavior of calling this function varies on a component-by-component and L&F-by-L&F basis.

Parameters:
b - if true, the content should be filled; if false the content area is not filled
2020-02-10 23:37:27 -08:00
Bill Culp
b6789e14a4 Option to allow tabbed pane separator to take full height 2020-02-10 22:32:11 -08:00
Bill Culp
c72ee30a25 fix: Illegal reflective access operation on mac 2020-02-10 18:17:20 -08:00
Karl Tauber
686d667c4f Table: optimized position of column sort arrow (issue #34) 2020-02-08 10:38:48 +01:00
Karl Tauber
26d603db5d UIDefaultsLoader: support scaling float, insets and dimension 2020-02-07 17:25:14 +01:00
Karl Tauber
409840aef9 README.md: added snapshots 2020-02-05 18:23:27 +01:00
Karl Tauber
1f3c264afe travis: moved JDKs back to top-level and execute "test" stage first (replaces "build" stage) 2020-02-05 16:12:56 +01:00
Karl Tauber
cd69d9a1a7 travis: moved JDKs to build job 2020-02-05 15:54:29 +01:00
Karl Tauber
a000c8fd99 travis: use stages and added snapshot upload 2020-02-05 15:29:00 +01:00
Karl Tauber
84d05603ef build.gradle.kts: separate versions for release and development (snapshot) 2020-02-05 15:21:01 +01:00
Karl Tauber
9d046ecd1d build.gradle.kts: added snapshot publishing to oss.jfrog.org 2020-02-05 12:34:07 +01:00
Karl Tauber
030e1809f3 Table: support positioning the column sort arrow in header right, left, top or bottom (issue #34) 2020-02-03 21:27:08 +01:00
Karl Tauber
5853bd4a96 InternalFrame: made buttons larger and square (issue #39) 2020-02-02 17:12:34 +01:00
Karl Tauber
10695ff51b InternalFrame: fixed exception on macOS when minimizing internal frame (#39) 2020-01-27 15:52:04 +01:00
Karl Tauber
f421659fea update to Gradle 6.1.1
./gradlew wrapper --gradle-version=6.1.1
2020-01-27 15:33:30 +01:00
Karl Tauber
df4f51eff3 InternalFrame: basic implementation (issues #39 and #11) 2020-01-27 15:23:03 +01:00
Karl Tauber
7e61d6a850 README.md: added some projects that use FlatLaf 2020-01-26 17:31:48 +01:00
Karl Tauber
0910bd23c4 ProgressBar: fixed visual artifacts in indeterminate mode, on HiDPI screens at 125%, 150% and 175% scaling, when the progress moves around 2020-01-23 10:33:01 +01:00
Karl Tauber
5a29753912 release 0.26 2020-01-22 15:07:56 +01:00
Karl Tauber
a467356437 build.gradle.kts: disable javadoc warnings for missing @param or @return 2020-01-22 14:54:49 +01:00
Karl Tauber
094967f52a ProgressBar: made progress bar paint smooth in indeterminate mode 2020-01-22 14:21:38 +01:00
Karl Tauber
757b0812ba Menu: highlight items in menu bar on mouse hover (issue #49) 2020-01-20 23:35:50 +01:00
Karl Tauber
8f4f5d8c92 FlatClientProperties: fixed javadoc error 2020-01-20 20:17:58 +01:00
Karl Tauber
4e266483ba Menus: menu items now have larger left and right margins 2020-01-20 20:13:37 +01:00
Karl Tauber
7433dc9cf3 Menus: changed menu bar and popup menu background colors (made brighter in light themes and darker in dark themes)
made `JMenu`, `JMenuItem`, `JCheckBoxMenuItem` and `JRadioButtonMenuItem` non-opaque
2020-01-20 20:09:32 +01:00
Karl Tauber
409a773e36 IntelliJ Themes Demo: updated Arc, Arc Orange, and Material UI Lite themes (used IJThemesUpdater) 2020-01-20 14:58:39 +01:00
Karl Tauber
48bdd5c3df TextField, FormattedTextField and PasswordField: select all text when a text field gains focus for the first time and selection was not set explicitly 2020-01-19 18:05:12 +01:00
smileatom
5796057a75 Merge pull request #1 from JFormDesigner/master
merge latest changes
2020-01-18 14:43:29 -08:00
Karl Tauber
c8248e91ca release 0.25.1 2020-01-18 10:35:18 +01:00
Karl Tauber
7317ce44e7 update to Gradle 6.1
./gradlew wrapper --gradle-version=6.1
2020-01-18 10:25:55 +01:00
Karl Tauber
10e2a5b1eb release 0.25 2020-01-17 13:18:29 +01:00
Karl Tauber
e675d1b7e2 show mnemonics if the active window does not have a focused component; ignore invisible components (issue #43) 2020-01-17 11:03:48 +01:00
Karl Tauber
499c4dadd5 FlatDarkLaf.properties: use slightly brighter color for popup menu border 2020-01-17 10:53:46 +01:00
Karl Tauber
f550f84acd Menu: fixed vertical alignment of sub-menus (issue #42) 2020-01-17 01:01:30 +01:00
Karl Tauber
8021f1a7fc ComboBox on macOS: fixed keyboard navigation and show/hide popup 2020-01-16 23:43:05 +01:00
Karl Tauber
d50fe606ee Tree on macOS: fixed Left and Right keys to collapse or expand nodes 2020-01-16 21:33:23 +01:00
Karl Tauber
281f014aa0 FlatTestFrame: support testing 3rd party lafs 2020-01-15 19:10:42 +01:00
Karl Tauber
2f6da3e84a FlatInspector: improved inspecting parent levels: Ctrl adds 1 level, Shift adds 2 levels and Alt adds 4 levels; no longer limit inspecting to content pane 2020-01-15 18:13:43 +01:00
Karl Tauber
f9accc2a7a ProgressBar: support square painting and larger height even if no string is painted 2020-01-15 17:13:39 +01:00
Karl Tauber
fe15078bbd TabbedPane: support per component tab height 2020-01-15 12:52:39 +01:00
Karl Tauber
27d4b5eba7 ToggleButton: Support per component styling for tab-style toggle buttons with client properties JToggleButton.tab.underlineHeight (integer), JToggleButton.tab.underlineColor (Color) and JToggleButton.tab.selectedBackground (Color) (issue #45) 2020-01-15 11:05:16 +01:00
Karl Tauber
e378576632 ToggleButton renamed toggle button type "underline" to "tab" (value of client property JButton.buttonType is now tab) 2020-01-14 23:59:56 +01:00
Karl Tauber
74909da110 Button and ToggleButton:
- support per component minimum height (issue #44)
- do not apply minimum width if button border was changed (is no longer an instance of `FlatButtonBorder`)
- ToggleButton: no longer use focus width for underline style toggle buttons to compute component size, which reduces/fixes component size in "Flat IntelliJ" and "Flat Darcula" themes
- revalidate/repaint client properties minimum width/height or buttonType change
2020-01-14 18:42:06 +01:00
Karl Tauber
655bf112ac ScrollPane: fixed UI artifact at bottom right corner of scroll pane if both scroll bars are visible, which was caused by Component.innerFocusWidth > 0 (issue #35) 2020-01-14 15:03:07 +01:00
Karl Tauber
5c3638a5a4 Menu: hide mnemonics by default and show them only when Alt key is pressed (issue #43) 2020-01-14 12:09:31 +01:00
Karl Tauber
2459a3654b TabbedPane: hide cropped line in scroll-tab-layout (issue #40) 2020-01-14 10:51:07 +01:00
Karl Tauber
e9a3456cf5 Tree: Tree.textBackground now has a valid color and is no longer null; instead set Tree.rendererFillBackground to false to always get correct cell backgrounds (in IntelliJ themes or if tree.setBackground(...) was used)
undone commit 645be4bfa3
2020-01-14 10:44:00 +01:00
Karl Tauber
2bcdf774ff release 0.24 2020-01-10 10:02:54 +01:00
Karl Tauber
ef01f23384 improved Swing system colors controlHighlight, controlLtHighlight, controlShadow and controlDkShadow 2020-01-10 09:47:13 +01:00
Karl Tauber
ab7bbb6593 ProgressBar: now uses blueish color for the progress part in "Flat Dark" theme 2020-01-10 00:28:26 +01:00
Karl Tauber
f2dad88875 ToggleButton: support underline toggle button colors in IntelliJ themes 2020-01-10 00:13:39 +01:00
Karl Tauber
c474565ff5 UI inspector: support nested classes 2020-01-09 23:54:45 +01:00
Karl Tauber
fd9dbbd7e6 support smooth scrolling with touchpads and high precision mouse wheels (issue #27) 2020-01-09 23:43:58 +01:00
Karl Tauber
43ab095e0f Table: replaced Table.showGrid with Table.showHorizontalLines and Table.showVerticalLines (issue #38) 2020-01-09 20:55:55 +01:00
Karl Tauber
41e2888bf1 ScrollPane with Table: The border of buttons that are added to one of the four scroll pane corners are now removed if the center component is a table. Also, these corner buttons are made not focusable. 2020-01-08 23:25:57 +01:00
Karl Tauber
e7d5e22960 IntelliJ Themes Demo: fixed last invalid colors in Material UI Lite themes (issue #26) 2020-01-08 15:27:31 +01:00
Karl Tauber
3f3884193d Button and TextComponent: support per component minimum width 2020-01-08 14:47:40 +01:00
Karl Tauber
dfccabc2b9 ToggleButton: support underline toggle button style 2020-01-08 14:18:17 +01:00
Karl Tauber
af7c181596 Button and ToggleButton: support square button style 2020-01-08 13:59:39 +01:00
Karl Tauber
8e84112837 Label and ToolTip: fixed font sizes for HTML headings 2020-01-08 10:18:30 +01:00
Karl Tauber
822cd16daa IntelliJ Themes Demo: updated Dracula, Hiberbee and Material UI Lite themes (used IJThemesUpdater) (issue #26) 2020-01-07 23:40:24 +01:00
Karl Tauber
33ea84004d UIDefaultsLoader: changed .properties file loading order: now all core .properties files are loaded before loading addon .properties files, which makes it easier to overwrite core values in addons; also, addon loading order can be specified 2020-01-07 12:55:37 +01:00
Karl Tauber
8dbbe20840 TableHeader: paint column borders also if renderer has changed, but delegates to the system default renderer
(e.g. done in NetBeans class ETableHeader)
2020-01-06 16:36:23 +01:00
Karl Tauber
d990ccc4ab release 0.23.1 2020-01-02 22:39:34 +01:00
Karl Tauber
9f16249898 IntelliJ Themes: fixed checkbox colors in Material UI Lite dark themes 2020-01-02 21:09:11 +01:00
Karl Tauber
62fc3139cf ComboBox: fixed NPE in Oracle SQL Developer settings 2020-01-02 18:45:32 +01:00
Karl Tauber
452452dcc9 Tree: fixed wide selection if scrolled horizontally 2019-12-31 09:49:56 +01:00
Karl Tauber
b6fb06bc65 CHANGELOG.md: added some missing changes form 0.23 2019-12-30 18:10:33 +01:00
Karl Tauber
aac6bd1b7c release 0.23 2019-12-30 17:49:16 +01:00
Karl Tauber
d260001cbd IntelliJ Themes Demo: fixed progress bar in Hiberbee theme 2019-12-30 16:55:35 +01:00
Karl Tauber
9c470d77cb List and Tree: Hide cell focus indicator (black rectangle) by default. Can be enabled with List.showCellFocusIndicator=true / Tree.showCellFocusIndicator=true, but then the cell focus indicator is shown only if more than one item is selected.
Table: Hide cell focus indicator (black rectangle) by default if none of the selected cells is editable. Can be show always with `Table.showCellFocusIndicator=true`.
2019-12-30 16:31:51 +01:00
Karl Tauber
269075657d FlatDefaultsAddon: added default implementation to getDefaults() because most subclasses use the same implementation 2019-12-30 11:02:40 +01:00
Karl Tauber
d0029beb22 use 0.5 pixel "inner" focus border for "Flat Light" and "Flat Dark" themes 2019-12-30 10:48:15 +01:00
Karl Tauber
4b4837e3a1 IntelliJ Themes Demo: updated Hiberbee theme (used IJThemesUpdater) (issue #26) 2019-12-30 10:04:48 +01:00
Karl Tauber
d0160b8b6d added required module java.logging to module-info.java 2019-12-23 15:39:58 +01:00
Karl Tauber
ea351935b2 IntelliJ Themes: use CONFIG log level for reporting invalid colors so that they do not show up in the console by default (issue #26) 2019-12-23 15:30:57 +01:00
Karl Tauber
20ccc2951e IntelliJ Themes Demo: updated "Material Theme UI Lite" theme to version 7.0, downloaded from IntelliJ plugin repo (https://plugins.jetbrains.com/plugin/12124-material-theme-ui-lite/versions) because theme github repo is not up-to-date 2019-12-23 13:55:33 +01:00
Karl Tauber
6f9bad1bdf Tree: fixed painting wide selection while drag-and-drop 2019-12-23 13:43:50 +01:00
Karl Tauber
39a0d514a8 List, Table and Tree: added colors for drag-and-drop
- added "enable drag and drop" checkbox to Demo on "Data Components" tab
- support copying UI default values lazy
2019-12-23 13:10:50 +01:00
Karl Tauber
ad82c591cc use logging instead of printing errors to System.err 2019-12-23 11:29:17 +01:00
Karl Tauber
32ceb168d5 replaced prefix @@ with $ in .properties files 2019-12-23 00:18:27 +01:00
Karl Tauber
16146f4c88 support basic color functions in .properties files 2019-12-22 17:40:13 +01:00
Karl Tauber
60febbf3f8 Table: hide grid and changed intercell spacing to zero 2019-12-22 11:38:32 +01:00
Karl Tauber
4960b30cfb Tree: support wide selection (enabled by default) 2019-12-22 00:07:08 +01:00
Karl Tauber
56c161fea0 List and Tree: paint cell focus indicator (black rectangle) only if more than one item is selected 2019-12-21 21:46:36 +01:00
Karl Tauber
27c439d728 documented used UI defaults in UI delegates 2019-12-21 17:30:36 +01:00
Karl Tauber
78d5e03a66 updated colors in "Flat Light" and "Flat IntelliJ" themes with colors from "IntelliJ Light Theme", which provides blue coloring that better match platform colors 2019-12-21 11:13:11 +01:00
Karl Tauber
f25e647b6a fixed separator color in IntelliJ platform themes 2019-12-21 10:55:17 +01:00
Karl Tauber
e44212fc65 fixed link color (in HTML text) in IntelliJ platform themes 2019-12-20 18:36:25 +01:00
Karl Tauber
a483403774 Button: reset shadow variables on LaF switch 2019-12-20 14:43:08 +01:00
Karl Tauber
69750dba19 IntelliJ Themes Demo: updated themes (used IJThemesUpdater) 2019-12-19 18:26:43 +01:00
Karl Tauber
af962b99be InternalFrame: icons implemented (issue #11) 2019-12-19 17:37:36 +01:00
Karl Tauber
2399e54a4b release 0.22 2019-12-18 12:28:08 +01:00
Karl Tauber
aea5e8eb16 Demo: bottom horizontal slider bound to the progress bars 2019-12-18 12:21:38 +01:00
Karl Tauber
a3a60c1c4b ProgressBar: reduced thickness from 6 to 4 (as in IntelliJ and Windows 10) 2019-12-18 11:44:34 +01:00
Karl Tauber
c141cb6c6c CheckBox and RadioButton: fixed cut off outer focus border if checkbox/radiobutton border was explicitly set to a EmptyBorder 2019-12-17 23:24:38 +01:00
Karl Tauber
62765ab6ca TextComponent: support placeholder text that is displayed if text field is empty (set client property "JTextField.placeholderText" to a string) 2019-12-17 18:08:45 +01:00
Karl Tauber
e4f7fed523 TextComponent: scale caret width on HiDPI screens when running on Java 8 2019-12-17 17:34:54 +01:00
Karl Tauber
bf8cc268cc on Mac show mnemonics only when Ctrl and Alt keys are pressed (issue #4) 2019-12-17 11:35:16 +01:00
Karl Tauber
8450e74832 TabbedPane: support separators between tabs (TabbedPane.showTabSeparators) 2019-12-16 20:40:29 +01:00
Karl Tauber
475b258e4a Button: enabled Button.defaultButtonFollowsFocus on Windows, which allows pressing focused button with <kbd>Enter</kbd> key (as in Windows LaF) 2019-12-16 18:11:48 +01:00
Karl Tauber
f20803ae57 ProgressBar: If progress text is visible:
- use smaller font
  - reduced height
  - changed style to rounded rectangle
  - fixed painting issues on low values

Support configure of arc with `ProgressBar.arc`
2019-12-16 17:39:46 +01:00
Karl Tauber
3fcb17931a fixed clipped borders at 125%, 150% and 175% scaling when outer focus width is zero (default in "Flat Light" and "Flat Dark" themes) 2019-12-15 11:36:24 +01:00
Karl Tauber
736c7b8377 CheckBox: changed CheckBox.arc from radius to diameter to be consistent with Button.arc and Component.arc 2019-12-14 23:36:22 +01:00
Karl Tauber
05743e2d8b FlatUIUtils: renamed and documented component painting methods 2019-12-14 23:17:11 +01:00
Karl Tauber
6cd2c7f26d InternalFrame: test application implemented (issue #11) 2019-12-14 11:57:07 +01:00
Karl Tauber
469e5bd179 ToggleButton: removed "ToggleButton.arc" because it was only used for the background, but not for the border, which is painted in FlatButtonBorder 2019-12-14 00:05:47 +01:00
Karl Tauber
dbeb3f04e7 UI inspector:
- fixed wrong detection of components under mouse location if window contains a menubar
- fixed positioning of tooltip in bottom and right window area to avoid that the tooltip overlaps the inspected component
2019-12-13 23:49:49 +01:00
Karl Tauber
e9b17ac24a UI inspector: support using it in any application 2019-12-13 23:24:10 +01:00
Karl Tauber
65fbcedaa4 TabbedPane: support background color for selected tabs 2019-12-13 23:14:41 +01:00
Karl Tauber
c4183ada11 ScrollPane and FlatSpinner: made getHandler() methods private 2019-12-11 21:58:39 +01:00
Karl Tauber
27f9614633 release 0.21 2019-12-08 12:38:45 +01:00
Karl Tauber
2211cc5596 fixed Swing system colors in dark themes 2019-12-08 10:21:07 +01:00
Karl Tauber
46f0393648 UIDefaultsLoader:
- support `{instance}com.myapp.MyClass` to instantiate any public class with public no-arg constructor
- support `{class}com.myapp.MyClass`
- support loading addon classes from different classloaders (e.g. in NetBeans)
2019-12-07 17:53:59 +01:00
Karl Tauber
b4c1a97687 IntelliJ Themes:
- accept colors starting with two `#` as valid colors because IntelliJ IDEA does it too
- fixed wrong error message when a color reference is missing

(issue #26)
2019-12-06 11:13:50 +01:00
Karl Tauber
adcef385b0 FlatClientProperties: added javadoc comments 2019-12-03 11:33:12 +01:00
Karl Tauber
48e38b2855 ScrollBar: show decrease/increase arrow buttons if client property "JScrollBar.showButtons" is set to true on JScrollPane or JScrollBar (issue #25) 2019-12-03 10:53:39 +01:00
Karl Tauber
2cc8327a08 ScrollPane: paint disabled border if view component (e.g. JTextPane) is disabled 2019-12-01 18:23:30 +01:00
Karl Tauber
404e80082c Button: fixed help button styling in IntelliJ platform themes 2019-12-01 17:53:10 +01:00
Karl Tauber
3fbc21347a Demo: restore last used theme on startup 2019-12-01 13:10:38 +01:00
Karl Tauber
d76f0e2241 moved code that fixes color of links in HTML text from FlatLaf.getDefaults() to FlatLaf.initialize() and invoke it only once (for the case that getDefaults() is invoked from 3rd party code) 2019-11-30 23:23:34 +01:00
Karl Tauber
e5fcc59805 Button: optionally support shadows for improved compatibility with IntelliJ platform themes (e.g. for Material Design Dark theme) 2019-11-30 19:14:37 +01:00
Karl Tauber
de82dac873 Button: optionally support gradient border and gradient background for improved compatibility with IntelliJ platform themes (e.g. Vuesion and Spacegray themes) 2019-11-30 17:58:40 +01:00
Karl Tauber
a14ef72177 FlatLaf.isNativeLookAndFeel() now returns false 2019-11-30 15:41:27 +01:00
421 changed files with 61668 additions and 8680 deletions

View File

@@ -6,3 +6,231 @@ insert_final_newline = true
charset = latin1 charset = latin1
indent_style = tab indent_style = tab
indent_size = 4 indent_size = 4
[*.java]
indent_style = tab
ij_continuation_indent_size = 4
ij_java_align_consecutive_assignments = false
ij_java_align_consecutive_variable_declarations = false
ij_java_align_group_field_declarations = false
ij_java_align_multiline_annotation_parameters = false
ij_java_align_multiline_array_initializer_expression = false
ij_java_align_multiline_assignment = false
ij_java_align_multiline_binary_operation = false
ij_java_align_multiline_chained_methods = false
ij_java_align_multiline_extends_list = false
ij_java_align_multiline_for = true
ij_java_align_multiline_method_parentheses = false
ij_java_align_multiline_parameters = false
ij_java_align_multiline_parameters_in_calls = false
ij_java_align_multiline_parenthesized_expression = false
ij_java_align_multiline_resources = false
ij_java_align_multiline_ternary_operation = false
ij_java_align_multiline_text_blocks = false
ij_java_align_multiline_throws_list = false
ij_java_align_subsequent_simple_methods = false
ij_java_align_throws_keyword = false
ij_java_annotation_parameter_wrap = off
ij_java_array_initializer_new_line_after_left_brace = false
ij_java_array_initializer_right_brace_on_new_line = false
ij_java_array_initializer_wrap = normal
ij_java_assert_statement_colon_on_next_line = false
ij_java_assert_statement_wrap = off
ij_java_assignment_wrap = off
ij_java_binary_operation_sign_on_next_line = false
ij_java_binary_operation_wrap = off
ij_java_blank_lines_after_anonymous_class_header = 0
ij_java_blank_lines_after_class_header = 0
ij_java_blank_lines_after_imports = 1
ij_java_blank_lines_after_package = 1
ij_java_blank_lines_around_class = 1
ij_java_blank_lines_around_field = 0
ij_java_blank_lines_around_field_in_interface = 0
ij_java_blank_lines_around_initializer = 1
ij_java_blank_lines_around_method = 1
ij_java_blank_lines_around_method_in_interface = 1
ij_java_blank_lines_before_class_end = 0
ij_java_blank_lines_before_imports = 1
ij_java_blank_lines_before_method_body = 0
ij_java_blank_lines_before_package = 0
ij_java_block_brace_style = next_line_if_wrapped
ij_java_block_comment_at_first_column = true
ij_java_call_parameters_new_line_after_left_paren = false
ij_java_call_parameters_right_paren_on_new_line = false
ij_java_call_parameters_wrap = normal
ij_java_case_statement_on_separate_line = true
ij_java_catch_on_new_line = false
ij_java_class_annotation_wrap = split_into_lines
ij_java_class_brace_style = next_line
ij_java_class_count_to_use_import_on_demand = 99
ij_java_class_names_in_javadoc = 1
ij_java_do_not_indent_top_level_class_members = false
ij_java_do_not_wrap_after_single_annotation = false
ij_java_do_while_brace_force = never
ij_java_doc_add_blank_line_after_description = true
ij_java_doc_add_blank_line_after_param_comments = false
ij_java_doc_add_blank_line_after_return = false
ij_java_doc_add_p_tag_on_empty_lines = true
ij_java_doc_align_exception_comments = true
ij_java_doc_align_param_comments = true
ij_java_doc_do_not_wrap_if_one_line = false
ij_java_doc_enable_formatting = true
ij_java_doc_enable_leading_asterisks = true
ij_java_doc_indent_on_continuation = false
ij_java_doc_keep_empty_lines = true
ij_java_doc_keep_empty_parameter_tag = true
ij_java_doc_keep_empty_return_tag = true
ij_java_doc_keep_empty_throws_tag = true
ij_java_doc_keep_invalid_tags = true
ij_java_doc_param_description_on_new_line = false
ij_java_doc_preserve_line_breaks = false
ij_java_doc_use_throws_not_exception_tag = true
ij_java_else_on_new_line = false
ij_java_enum_constants_wrap = off
ij_java_extends_keyword_wrap = split_into_lines
ij_java_extends_list_wrap = normal
ij_java_field_annotation_wrap = split_into_lines
ij_java_finally_on_new_line = false
ij_java_for_brace_force = never
ij_java_for_statement_new_line_after_left_paren = false
ij_java_for_statement_right_paren_on_new_line = false
ij_java_for_statement_wrap = off
ij_java_generate_final_locals = false
ij_java_generate_final_parameters = false
ij_java_if_brace_force = never
ij_java_imports_layout = java.**,javax.**,*,$*
ij_java_indent_case_from_switch = true
ij_java_insert_inner_class_imports = false
ij_java_insert_override_annotation = true
ij_java_keep_blank_lines_before_right_brace = 1
ij_java_keep_blank_lines_between_package_declaration_and_header = 1
ij_java_keep_blank_lines_in_code = 1
ij_java_keep_blank_lines_in_declarations = 1
ij_java_keep_control_statement_in_one_line = false
ij_java_keep_first_column_comment = false
ij_java_keep_indents_on_empty_lines = false
ij_java_keep_line_breaks = false
ij_java_keep_multiple_expressions_in_one_line = false
ij_java_keep_simple_blocks_in_one_line = false
ij_java_keep_simple_classes_in_one_line = false
ij_java_keep_simple_lambdas_in_one_line = false
ij_java_keep_simple_methods_in_one_line = false
ij_java_label_indent_absolute = false
ij_java_label_indent_size = 0
ij_java_lambda_brace_style = end_of_line
ij_java_layout_static_imports_separately = true
ij_java_line_comment_add_space = false
ij_java_line_comment_at_first_column = true
ij_java_method_annotation_wrap = split_into_lines
ij_java_method_brace_style = next_line_if_wrapped
ij_java_method_call_chain_wrap = normal
ij_java_method_parameters_new_line_after_left_paren = false
ij_java_method_parameters_right_paren_on_new_line = false
ij_java_method_parameters_wrap = normal
ij_java_modifier_list_wrap = false
ij_java_names_count_to_use_import_on_demand = 3
ij_java_parameter_annotation_wrap = off
ij_java_parentheses_expression_new_line_after_left_paren = false
ij_java_parentheses_expression_right_paren_on_new_line = false
ij_java_place_assignment_sign_on_next_line = false
ij_java_prefer_longer_names = true
ij_java_prefer_parameters_wrap = false
ij_java_repeat_synchronized = true
ij_java_replace_instanceof_and_cast = false
ij_java_replace_null_check = true
ij_java_replace_sum_lambda_with_method_ref = true
ij_java_resource_list_new_line_after_left_paren = false
ij_java_resource_list_right_paren_on_new_line = false
ij_java_resource_list_wrap = normal
ij_java_space_after_closing_angle_bracket_in_type_argument = false
ij_java_space_after_colon = true
ij_java_space_after_comma = true
ij_java_space_after_comma_in_type_arguments = true
ij_java_space_after_for_semicolon = true
ij_java_space_after_quest = true
ij_java_space_after_type_cast = true
ij_java_space_before_annotation_array_initializer_left_brace = true
ij_java_space_before_annotation_parameter_list = false
ij_java_space_before_array_initializer_left_brace = true
ij_java_space_before_catch_keyword = true
ij_java_space_before_catch_left_brace = true
ij_java_space_before_catch_parentheses = false
ij_java_space_before_class_left_brace = true
ij_java_space_before_colon = true
ij_java_space_before_colon_in_foreach = true
ij_java_space_before_comma = false
ij_java_space_before_do_left_brace = true
ij_java_space_before_else_keyword = true
ij_java_space_before_else_left_brace = true
ij_java_space_before_finally_keyword = true
ij_java_space_before_finally_left_brace = true
ij_java_space_before_for_left_brace = true
ij_java_space_before_for_parentheses = false
ij_java_space_before_for_semicolon = false
ij_java_space_before_if_left_brace = true
ij_java_space_before_if_parentheses = false
ij_java_space_before_method_call_parentheses = false
ij_java_space_before_method_left_brace = true
ij_java_space_before_method_parentheses = false
ij_java_space_before_opening_angle_bracket_in_type_parameter = false
ij_java_space_before_quest = true
ij_java_space_before_switch_left_brace = true
ij_java_space_before_switch_parentheses = false
ij_java_space_before_synchronized_left_brace = true
ij_java_space_before_synchronized_parentheses = false
ij_java_space_before_try_left_brace = true
ij_java_space_before_try_parentheses = false
ij_java_space_before_type_parameter_list = false
ij_java_space_before_while_keyword = true
ij_java_space_before_while_left_brace = true
ij_java_space_before_while_parentheses = false
ij_java_space_inside_one_line_enum_braces = false
ij_java_space_within_empty_array_initializer_braces = false
ij_java_space_within_empty_method_call_parentheses = false
ij_java_space_within_empty_method_parentheses = false
ij_java_spaces_around_additive_operators = true
ij_java_spaces_around_assignment_operators = true
ij_java_spaces_around_bitwise_operators = true
ij_java_spaces_around_equality_operators = true
ij_java_spaces_around_lambda_arrow = true
ij_java_spaces_around_logical_operators = true
ij_java_spaces_around_method_ref_dbl_colon = false
ij_java_spaces_around_multiplicative_operators = true
ij_java_spaces_around_relational_operators = true
ij_java_spaces_around_shift_operators = true
ij_java_spaces_around_type_bounds_in_type_parameters = true
ij_java_spaces_around_unary_operator = false
ij_java_spaces_within_angle_brackets = false
ij_java_spaces_within_annotation_parentheses = true
ij_java_spaces_within_array_initializer_braces = true
ij_java_spaces_within_braces = false
ij_java_spaces_within_brackets = false
ij_java_spaces_within_cast_parentheses = false
ij_java_spaces_within_catch_parentheses = true
ij_java_spaces_within_for_parentheses = true
ij_java_spaces_within_if_parentheses = true
ij_java_spaces_within_method_call_parentheses = true
ij_java_spaces_within_method_parentheses = true
ij_java_spaces_within_parentheses = false
ij_java_spaces_within_switch_parentheses = true
ij_java_spaces_within_synchronized_parentheses = true
ij_java_spaces_within_try_parentheses = true
ij_java_spaces_within_while_parentheses = true
ij_java_special_else_if_treatment = true
ij_java_subclass_name_suffix = Impl
ij_java_ternary_operation_signs_on_next_line = true
ij_java_ternary_operation_wrap = on_every_item
ij_java_test_name_suffix = Test
ij_java_throws_keyword_wrap = normal
ij_java_throws_list_wrap = normal
ij_java_use_external_annotations = false
ij_java_use_fq_class_names = false
ij_java_use_relative_indents = false
ij_java_use_single_class_imports = true
ij_java_variable_annotation_wrap = off
ij_java_visibility = public
ij_java_while_brace_force = never
ij_java_while_on_new_line = false
ij_java_wrap_comments = false
ij_java_wrap_first_method_in_call_chain = false
ij_java_wrap_long_lines = false

View File

@@ -19,11 +19,21 @@ before_install:
- ./gradlew --version - ./gradlew --version
- java -version - java -version
deploy: stages:
provider: script - name: test
script: ./gradlew bintrayUpload - name: snapshot
skip_cleanup: true # to upload artifacts created during the build if: branch = master AND type IN (push) AND tag IS blank
on: - name: release
branch: master if: type IN (push) AND tag IS present
jdk: openjdk11
tags: true jobs:
include:
# publish snapshot to oss.jfrog.org
- stage: snapshot
jdk: openjdk11
script: ./gradlew artifactoryPublish
# release a new stable version to bintray
- stage: release
jdk: openjdk11
script: ./gradlew bintrayUpload -Drelease=true

View File

@@ -1,6 +1,281 @@
FlatLaf Change Log FlatLaf Change Log
================== ==================
## 0.34
- Menus: New menu item renderer brings stable left margins, right aligned
accelerators and larger gap between text and accelerator. This makes menus
look more modern and more similar to native platform menus.
- New underline menu selection style that displays selected menu items similar
to tabs (to enable use `UIManager.put( "MenuItem.selectionType", "underline"
);`).
- Menus: Fixed text color of selected menu items that use HTML. (issue #87)
- Menus: On Windows, pressing <kbd>F10</kbd> now activates the menu bar without
showing a menu popup (as usual on Windows platform). On other platforms the
first menu popup is shown.
- Menus: On Windows, releasing <kbd>Alt</kbd> key now activates the menu bar (as
usual on Windows platform). (issue #43)
- Menus: Fixed inconsistent left padding in menu items. (issue #3)
- Menus: Fixed: Setting `iconTextGap` property on a menu item did increase left
and right margins. (issue #54)
- Hide mnemonics if window is deactivated (e.g. <kbd>Alt+Tab</kbd> to another
window). (issue #43)
- macOS: Enabled drop shadows for popup menus and combobox popups. (issue #94)
- macOS: Fixed NPE if using `JMenuBar` in `JInternalFrame` and macOS screen menu
bar is enabled (with `-Dapple.laf.useScreenMenuBar=true`). (issue #90)
## 0.33
- Improved creation of disabled grayscale icons used in disabled buttons, labels
and tabs. They now have more contrast and are lighter in light themes and
darker in dark themes. (issue #70)
- IntelliJ Themes: Fixed ComboBox size and Spinner border in all Material UI
Lite themes and limit tree row height in all Material UI Lite themes and some
other themes.
- IntelliJ Themes: Material UI Lite themes did not work when using
[IntelliJ Themes Pack](flatlaf-intellij-themes) addon. (PR #88, issue #89)
- IntelliJ Themes: Added Java 9 module descriptor to
`flatlaf-intellij-themes-<version>.jar`.
## 0.32
- New [IntelliJ Themes Pack](flatlaf-intellij-themes) addon bundles many popular
open-source 3rd party themes from JetBrains Plugins Repository into a JAR and
provides Java classes to use them.
- IntelliJ Themes: Fixed button and toggle button colors. (issue #86)
- Updated IntelliJ Themes in demo to the latest versions.
- ToggleButton: Compute selected background color based on current component
background. (issue #32)
## 0.31
- Focus indication border (or background) no longer hidden when temporary
loosing focus (e.g. showing a popup menu).
- List, Table and Tree: Item selection color of focused components no longer
change from blue to gray when temporary loosing focus (e.g. showing a popup
menu).
## 0.30
- Windows: Fixed rendering of Unicode characters. Previously not all Unicode
characters were rendered on Windows. (issue #81)
## 0.29
- Linux: Fixed scaling if `GDK_SCALE` environment variable is set or if running
on JetBrains Runtime. (issue #69)
- Tree: Fixed repainting wide selection on focus gained/lost.
- ComboBox: No longer ignore `JComboBox.prototypeDisplayValue` when computing
popup width. (issue #80)
- Support changing default font used for all components with automatic scaling
UI if using larger font. Use `UIManager.put( "defaultFont", myFont );`
- No longer use system property `sun.java2d.uiScale`. (Java 8 only)
- Support specifying custom scale factor in system property `flatlaf.uiScale`
also for Java 9 and later.
- Demo: Support using own FlatLaf themes (`.properties` files) that are located
in working directory of Demo application. Shown in the "Themes" list under
category "Current Directory".
## 0.28
- PasswordField: Warn about enabled Caps Lock.
- TabbedPane: Support <kbd>Ctrl+TAB</kbd> / <kbd>Ctrl+Shift+TAB</kbd> to switch
to next / previous tab.
- TextField, FormattedTextField and PasswordField: Support round borders (see UI
default value `TextComponent.arc`). (issue #65)
- IntelliJ Themes: Added Gradianto themes to demo.
- Button, CheckBox and RadioButton: Fixed NPE when button has children. (PR #68)
- ScrollBar: Improved colors.
- Reviewed (and tested) all key bindings on Windows and macOS. Linux key
bindings are equal to Windows key bindings. macOS key bindings are slightly
different for platform specific behavior.
- UI default values are no longer based on Metal/Aqua UI defaults.
## 0.27
- Support `JInternalFrame` and `JDesktopPane`. (issues #39 and #11)
- Table: Support positioning the column sort arrow in header right, left, top or
bottom. (issue #34)
- ProgressBar: Fixed visual artifacts in indeterminate mode, on HiDPI screens at
125%, 150% and 175% scaling, when the progress moves around.
- TabbedPane: New option to allow tab separators to take full height (to enable
use `UIManager.put( "TabbedPane.tabSeparatorsFullHeight", true );`). (issue
#59, PR #62)
- CheckBox and RadioButton: Do not fill background if `contentAreaFilled` is
`false`. (issue #58, PR #63)
- ToggleButton: Make toggle button square if it has an icon but no text or text
is "..." or a single character.
- ToolBar: No longer use special rollover border for buttons in toolbar. (issue
#36)
- ToolBar: Added empty space around buttons in toolbar and toolbar itself (see
UI default values `Button.toolbar.spacingInsets` and `ToolBar.borderMargins`).
(issue #56)
- Fixed "illegal reflective access operation" warning on macOS when using Java
12 or later. (issue #60, PR #61)
## 0.26
- Menus:
- Changed menu bar and popup menu background colors (made brighter in light
themes and darker in dark themes).
- Highlight items in menu bar on mouse hover. (issue #49)
- Popup menus now have empty space at the top and bottom.
- Menu items now have larger left and right margins.
- Made `JMenu`, `JMenuItem`, `JCheckBoxMenuItem` and `JRadioButtonMenuItem`
non-opaque.
- TextField, FormattedTextField and PasswordField: Select all text when a text
field gains focus for the first time and selection was not set explicitly.
This can be configured to newer or always select all text on focus gain (see
UI default value `TextComponent.selectAllOnFocusPolicy`).
- ProgressBar: Made progress bar paint smooth in indeterminate mode.
## 0.25.1
Re-release of 0.25 because of problems with Maven Central.
## 0.25
- Hide menu mnemonics by default and show them only when <kbd>Alt</kbd> key is
pressed. (issue #43)
- Menu: Fixed vertical alignment of sub-menus. (issue #42)
- TabbedPane: In scroll-tab-layout, the cropped line is now hidden. (issue #40)
- Tree: UI default value `Tree.textBackground` now has a valid color and is no
longer `null`.
- Tree on macOS: Fixed <kbd>Left</kbd> and <kbd>Right</kbd> keys to collapse or
expand nodes.
- ComboBox on macOS: Fixed keyboard navigation and show/hide popup.
- Button and ToggleButton: Support per component minimum height (set client
property `JComponent.minimumHeight` to an integer). (issue #44)
- Button and ToggleButton: Do not apply minimum width if button border was
changed (is no longer an instance of `FlatButtonBorder`).
- ToggleButton: Renamed toggle button type "underline" to "tab" (value of client
property `JButton.buttonType` is now `tab`).
- ToggleButton: Support per component styling for tab-style toggle buttons with
client properties `JToggleButton.tab.underlineHeight` (integer),
`JToggleButton.tab.underlineColor` (Color) and
`JToggleButton.tab.selectedBackground` (Color). (issue #45)
- ToggleButton: No longer use focus width for tab-style toggle buttons to
compute component size, which reduces/fixes component size in "Flat IntelliJ"
and "Flat Darcula" themes.
- TabbedPane: Support per component tab height (set client property
`JTabbedPane.tabHeight` to an integer).
- ProgressBar: Support square painting (set client property
`JProgressBar.square` to `true`) and larger height even if no string is
painted (set client property `JProgressBar.largeHeight` to `true`).
## 0.24
- Support smooth scrolling with touchpads and high precision mouse wheels.
(issue #27)
- Changed `.properties` file loading order: Now all core `.properties` files are
loaded before loading addon `.properties` files. This makes it easier to
overwrite core values in addons. Also, addon loading order can be specified.
- TableHeader: Paint column borders if renderer has changed, but delegates to
the system default renderer (e.g. done in NetBeans).
- Label and ToolTip: Fixed font sizes for HTML headings.
- Button and ToggleButton: Support square button style (set client property
`JButton.buttonType` to `square`).
- ToggleButton: Support underline toggle button style (set client property
`JButton.buttonType` to `underline`).
- Button and TextComponent: Support per component minimum width (set client
property `JComponent.minimumWidth` to an integer).
- ScrollPane with Table: The border of buttons that are added to one of the four
scroll pane corners are now removed if the center component is a table. Also,
these corner buttons are made not focusable.
- Table: Replaced `Table.showGrid` with `Table.showHorizontalLines` and
`Table.showVerticalLines`. (issue #38)
- ProgressBar: Now uses blueish color for the progress part in "Flat Dark"
theme. In the "Flat Darcula" theme, it remains light gray.
- Improved Swing system colors `controlHighlight`, `controlLtHighlight`,
`controlShadow` and `controlDkShadow`.
## 0.23.1
- Tree: Fixed wide selection if scrolled horizontally.
- ComboBox: Fixed NPE in Oracle SQL Developer settings.
- IntelliJ Themes: Fixed checkbox colors in Material UI Lite dark themes.
## 0.23
- Updated colors in "Flat Light" and "Flat IntelliJ" themes with colors from
"IntelliJ Light Theme", which provides blue coloring that better matches
platform colors.
- Tree: Support wide selection (enabled by default).
- Table: Hide grid and changed intercell spacing to zero.
- List, Table and Tree: Added colors for drag-and-drop. Added "enable drag and
drop" checkbox to Demo on "Data Components" tab.
- List and Tree: Hide cell focus indicator (black rectangle) by default. Can be
enabled with `List.showCellFocusIndicator=true` /
`Tree.showCellFocusIndicator=true`, but then the cell focus indicator is shown
only if more than one item is selected.
- Table: Hide cell focus indicator (black rectangle) by default if none of the
selected cells is editable. Can be show always with
`Table.showCellFocusIndicator=true`.
- Support basic color functions in `.properties` files: `rgb(red,green,blue)`,
`rgba(red,green,blue,alpha)`, `hsl(hue,saturation,lightness)`,
`hsla(hue,saturation,lightness,alpha)`, `lighten(color,amount[,options])` and
`darken(color,amount[,options])`.
- Replaced prefix `@@` with `$` in `.properties` files.
- Fixed link color (in HTML text) and separator color in IntelliJ platform
themes.
- Use logging instead of printing errors to `System.err`.
- Updated IntelliJ Themes in demo to the latest versions.
- IntelliJ Themes: Fixed link and separator colors.
## 0.22
- TextComponent: Support placeholder text that is displayed if text field is
empty (set client property "JTextField.placeholderText" to a string).
- TextComponent: Scale caret width on HiDPI screens when running on Java 8.
- ProgressBar: If progress text is visible:
- use smaller font
- reduced height
- changed style to rounded rectangle
- fixed painting issues on low values
- ProgressBar: Support configure of arc with `ProgressBar.arc`.
- ProgressBar: Reduced thickness from 6 to 4.
- TabbedPane: Support background color for selected tabs
(`TabbedPane.selectedBackground`) and separators between tabs
(`TabbedPane.showTabSeparators`).
- CheckBox: changed `CheckBox.arc` from radius to diameter to be consistent with
`Button.arc` and `Component.arc`
- Button: Enabled `Button.defaultButtonFollowsFocus` on Windows, which allows
pressing focused button with <kbd>Enter</kbd> key (as in Windows LaF).
- Fixed clipped borders at 125%, 150% and 175% scaling when outer focus width is
zero (default in "Flat Light" and "Flat Dark" themes).
- On Mac show mnemonics only when <kbd>Ctrl</kbd> and <kbd>Alt</kbd> keys are
pressed. (issue #4)
## 0.21
- ScrollBar: Show decrease/increase arrow buttons if client property
"JScrollBar.showButtons" is set to `true` on `JScrollPane` or `JScrollBar`.
(issue #25)
- `FlatLaf.isNativeLookAndFeel()` now returns `false`.
- Button: Optionally support gradient borders, gradient backgrounds and shadows
for improved compatibility with IntelliJ platform themes (e.g. for Vuesion,
Spacegray and Material Design Dark themes).
- Button: Fixed help button styling in IntelliJ platform themes.
- ScrollPane: Paint disabled border if view component (e.g. JTextPane) is
disabled.
- Fixed Swing system colors in dark themes.
## 0.20 ## 0.20
- Support using IntelliJ platform themes (.theme.json files). - Support using IntelliJ platform themes (.theme.json files).

View File

@@ -19,7 +19,8 @@ IntelliJ IDEA 2019.2+ and uses almost the same colors and icons.
IntelliJ Platform Themes IntelliJ Platform Themes
------------------------ ------------------------
FlatLaf can use 3rd party themes created for IntelliJ Platform: FlatLaf can use 3rd party themes created for IntelliJ Platform (see
[IntelliJ Themes Pack](flatlaf-intellij-themes)):
![Cyan Light Demo](images/CyanLightDemo.png) ![Cyan Light Demo](images/CyanLightDemo.png)
@@ -45,18 +46,67 @@ build script:
groupId: com.formdev groupId: com.formdev
artifactId: flatlaf artifactId: flatlaf
version: 0.20 version: (see button below)
Otherwise download `flatlaf-<version>.jar` here: Otherwise download `flatlaf-<version>.jar` here:
[![Download](https://api.bintray.com/packages/jformdesigner/flatlaf/flatlaf/images/download.svg)](https://bintray.com/jformdesigner/flatlaf/flatlaf/_latestVersion) [![Download](https://api.bintray.com/packages/jformdesigner/flatlaf/flatlaf/images/download.svg)](https://bintray.com/jformdesigner/flatlaf/flatlaf/_latestVersion)
### Snapshots
FlatLaf snapshot binaries are available in
[JFrog Artifactory](https://oss.jfrog.org/artifactory/oss-snapshot-local/com/formdev/).
To access the latest snapshot, change the FlatLaf version(s) in the dependencies
to `<version>-SNAPSHOT` (e.g. `0.27-SNAPSHOT`) and add the repository
`https://oss.jfrog.org/artifactory/oss-snapshot-local` to your build (see
[Maven](https://maven.apache.org/guides/mini/guide-multiple-repositories.html)
and
[Gradle](https://docs.gradle.org/current/userguide/declaring_repositories.html#sec:declaring_custom_repository)
docs).
Addons Addons
------ ------
- [SwingX](flatlaf-swingx) - [SwingX](flatlaf-swingx)
- [JIDE Common Layer](flatlaf-jide-oss) - [JIDE Common Layer](flatlaf-jide-oss)
- [IntelliJ Themes Pack](flatlaf-intellij-themes)
Projects using FlatLaf
----------------------
- [NetBeans](https://netbeans.apache.org/) 11.3
- [jclasslib bytecode viewer](https://github.com/ingokegel/jclasslib) 5.5
- [KeyStore Explorer](https://keystore-explorer.org/) 5.4.3
- [OWASP Zed Attack Proxy (ZAP)](https://www.zaproxy.org/) (in weekly releases)
- [XMLmind XML Editor](https://www.xmlmind.com/xmleditor/) 9.3 (commercial)
- [Total Validator](https://www.totalvalidator.com/) 15 (commercial)
- [j-lawyer](https://github.com/jlawyerorg/j-lawyer-org)
- [MegaMek](https://github.com/MegaMek/megamek) v0.47.4 and
[MekHQ](https://github.com/MegaMek/mekhq) v0.47.5
- [GUIslice Builder](https://github.com/ImpulseAdventure/GUIslice-Builder)
0.13.b024
- [Rest Suite](https://github.com/supanadit/restsuite)
- [ControllerBuddy](https://github.com/bwRavencl/ControllerBuddy)
- [SpringRemote](https://github.com/HaleyWang/SpringRemote)
- [mendelson AS2](https://sourceforge.net/projects/mec-as2/),
[AS4](https://sourceforge.net/projects/mendelson-as4/) and
[OFTP2](https://sourceforge.net/projects/mendelson-oftp2/) (open-source) and
[mendelson AS2](https://mendelson-e-c.com/as2/),
[AS4](https://mendelson-e-c.com/as4/) and
[OFTP2](https://mendelson-e-c.com/oftp2) (commercial)
- [MeteoInfo](https://github.com/meteoinfo/MeteoInfo) 2.1.6
- [lsfusion platform](https://github.com/lsfusion/platform)
- and more...
Buzz
----
- [What others say about FlatLaf on Twitter](https://twitter.com/search?f=live&q=flatlaf)
- [FlatLaf announcement on Reddit](https://www.reddit.com/r/java/comments/dl0hu3/flatlaf_flat_look_and_feel/)
Documentation Documentation

View File

@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,9 +14,14 @@
* limitations under the License. * limitations under the License.
*/ */
version = "0.20" val releaseVersion = "0.34"
val developmentVersion = "0.35-SNAPSHOT"
version = if( java.lang.Boolean.getBoolean( "release" ) ) releaseVersion else developmentVersion
allprojects { allprojects {
version = rootProject.version
repositories { repositories {
jcenter() jcenter()
} }
@@ -33,3 +38,39 @@ println( "FlatLaf Version: ${version}" )
println( "Gradle ${gradle.gradleVersion} at ${gradle.gradleHomeDir}" ) println( "Gradle ${gradle.gradleVersion} at ${gradle.gradleHomeDir}" )
println( "Java ${System.getProperty( "java.version" )}" ) println( "Java ${System.getProperty( "java.version" )}" )
println() println()
extra["bintray.user"] = System.getenv( "BINTRAY_USER" ) ?: System.getProperty( "bintray.user" )
extra["bintray.key"] = System.getenv( "BINTRAY_KEY" ) ?: System.getProperty( "bintray.key" )
// if true, do not upload to bintray
extra["bintray.dryRun"] = false
// if true, uploaded artifacts are visible to all
// if false, only visible to owner when logged into bintray
extra["bintray.publish"] = true
allprojects {
tasks {
withType<JavaCompile>().configureEach {
sourceCompatibility = "1.8"
targetCompatibility = "1.8"
options.encoding = "ISO-8859-1"
}
withType<Jar>().configureEach {
// manifest for all created JARs
manifest.attributes(mapOf(
"Implementation-Vendor" to "FormDev Software GmbH",
"Implementation-Copyright" to "Copyright (C) ${java.time.LocalDate.now().year} FormDev Software GmbH. All rights reserved.",
"Implementation-Version" to project.version))
// add META-INF/LICENSE to all created JARs
from("${rootDir}/LICENSE") {
into("META-INF")
}
}
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.util.function.Predicate;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
/**
* Reorders entries in a JAR file so that .properties files are placed before .class files,
* which is necessary to workaround an issue in NetBeans 11.3 (and older).
* See issues #13 and #93.
*
* @author Karl Tauber
*/
public class ReorderJarEntries
{
public static void reorderJarEntries( File jarFile )
throws IOException
{
ByteArrayOutputStream outStream = new ByteArrayOutputStream( (int) jarFile.length() + 1000 );
try( ZipOutputStream zipOutStream = new ZipOutputStream( outStream ) ) {
// 1st pass: copy .properties files
copyFiles( zipOutStream, jarFile, name -> name.endsWith( ".properties" ) );
// 2st pass: copy other files
copyFiles( zipOutStream, jarFile, name -> !name.endsWith( ".properties" ) );
}
// replace JAR
Files.write( jarFile.toPath(), outStream.toByteArray(),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING );
}
private static void copyFiles( ZipOutputStream dest, File jarFile, Predicate<String> filter )
throws IOException
{
try( ZipInputStream zipInputStream = new ZipInputStream( new FileInputStream( jarFile ) ) ) {
ZipEntry entry;
while( (entry = zipInputStream.getNextEntry()) != null ) {
if( filter.test( entry.getName() ) ) {
dest.putNextEntry( entry );
copyFile( zipInputStream, dest );
}
}
}
}
private static void copyFile( InputStream src, OutputStream dest )
throws IOException
{
byte[] buf = new byte[8*1024];
int len;
while( (len = src.read( buf )) > 0 )
dest.write( buf, 0, len );
dest.flush();
}
}

View File

@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,16 +14,20 @@
* limitations under the License. * limitations under the License.
*/ */
version = rootProject.version
plugins { plugins {
`java-library` `java-library`
`maven-publish` `maven-publish`
id( "com.jfrog.bintray" ) version "1.8.4" id( "com.jfrog.bintray" )
id( "com.jfrog.artifactory" )
} }
if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) { if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) {
sourceSets { sourceSets {
create( "java9" ) {
java {
setSrcDirs( listOf( "src/main/java9" ) )
}
}
create( "module-info" ) { create( "module-info" ) {
java { java {
// include "src/main/java" here to get compile errors if classes are // include "src/main/java" here to get compile errors if classes are
@@ -34,11 +38,6 @@ if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) {
} }
} }
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
tasks { tasks {
assemble { assemble {
dependsOn( dependsOn(
@@ -53,21 +52,32 @@ tasks {
targetCompatibility = "9" targetCompatibility = "9"
} }
} }
jar { jar {
archiveBaseName.set( "flatlaf" ) archiveBaseName.set( "flatlaf" )
if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) { if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) {
manifest.attributes( "Multi-Release" to "true" )
into( "META-INF/versions/9" ) {
from( sourceSets["java9"].output )
}
from( sourceSets["module-info"].output ) { from( sourceSets["module-info"].output ) {
include( "module-info.class" ) include( "module-info.class" )
} }
} }
doLast {
ReorderJarEntries.reorderJarEntries( outputs.files.singleFile );
}
} }
javadoc { javadoc {
options { options {
this as StandardJavadocDocletOptions this as StandardJavadocDocletOptions
tags = listOf( "uiDefault", "clientProperty" ) tags = listOf( "uiDefault", "clientProperty" )
addStringOption( "Xdoclint:all,-missing", "-Xdoclint:all,-missing" )
} }
isFailOnError = false isFailOnError = false
} }
@@ -106,7 +116,7 @@ publishing {
licenses { licenses {
license { license {
name.set( "The Apache License, Version 2.0" ) name.set( "The Apache License, Version 2.0" )
url.set( "http://www.apache.org/licenses/LICENSE-2.0.txt" ) url.set( "https://www.apache.org/licenses/LICENSE-2.0.txt" )
} }
} }
@@ -127,8 +137,8 @@ publishing {
} }
bintray { bintray {
user = System.getenv( "BINTRAY_USER" ) ?: System.getProperty( "bintray.user" ) user = rootProject.extra["bintray.user"] as String?
key = System.getenv( "BINTRAY_KEY" ) ?: System.getProperty( "bintray.key" ) key = rootProject.extra["bintray.key"] as String?
setPublications( "maven" ) setPublications( "maven" )
@@ -142,6 +152,29 @@ bintray {
name = project.version.toString() name = project.version.toString()
} }
publish = true publish = rootProject.extra["bintray.publish"] as Boolean
dryRun = rootProject.extra["bintray.dryRun"] as Boolean
} }
} }
artifactory {
setContextUrl( "https://oss.jfrog.org" )
publish( closureOf<org.jfrog.gradle.plugin.artifactory.dsl.PublisherConfig> {
repository( delegateClosureOf<groovy.lang.GroovyObject> {
setProperty( "repoKey", "oss-snapshot-local" )
setProperty( "username", rootProject.extra["bintray.user"] as String? )
setProperty( "password", rootProject.extra["bintray.key"] as String? )
} )
defaults( delegateClosureOf<groovy.lang.GroovyObject> {
invokeMethod( "publications", "maven" )
setProperty( "publishArtifacts", true )
setProperty( "publishPom", true )
} )
} )
resolve( delegateClosureOf<org.jfrog.gradle.plugin.artifactory.dsl.ResolverConfig> {
setProperty( "repoKey", "jcenter" )
} )
}

View File

@@ -16,6 +16,7 @@
package com.formdev.flatlaf; package com.formdev.flatlaf;
import java.awt.Color;
import java.util.Objects; import java.util.Objects;
import javax.swing.JComponent; import javax.swing.JComponent;
@@ -24,18 +25,219 @@ import javax.swing.JComponent;
*/ */
public interface FlatClientProperties public interface FlatClientProperties
{ {
/**
* Specifies type of a button.
* <p>
* <strong>Components</strong> {@link javax.swing.JButton} and {@link javax.swing.JToggleButton}<br>
* <strong>Value type</strong> {@link java.lang.String}<br>
* <strong>Allowed Values</strong> {@link #BUTTON_TYPE_SQUARE} and {@link #BUTTON_TYPE_HELP}
*/
String BUTTON_TYPE = "JButton.buttonType"; String BUTTON_TYPE = "JButton.buttonType";
/**
* Paint the button with square edges.
* <p>
* <strong>Components</strong> {@link javax.swing.JButton} and {@link javax.swing.JToggleButton}
*
* @see #BUTTON_TYPE
*/
String BUTTON_TYPE_SQUARE = "square";
/**
* Paint the toggle button in tab style.
* <p>
* <strong>Components</strong> {@link javax.swing.JToggleButton}
*
* @see #BUTTON_TYPE
*/
String BUTTON_TYPE_TAB = "tab";
/**
* Paint a help button (circle with question mark).
* <p>
* <strong>Components</strong> {@link javax.swing.JButton}
*
* @see #BUTTON_TYPE
*/
String BUTTON_TYPE_HELP = "help"; String BUTTON_TYPE_HELP = "help";
/**
* Specifies selected state of a checkbox.
* <p>
* <strong>Component</strong> {@link javax.swing.JCheckBox}<br>
* <strong>Value type</strong> {@link java.lang.String}<br>
* <strong>Allowed Values</strong> {@link #SELECTED_STATE_INDETERMINATE}
*/
String SELECTED_STATE = "JButton.selectedState"; String SELECTED_STATE = "JButton.selectedState";
/**
* Paint an indeterminate state on a checkbox.
*
* @see #SELECTED_STATE
*/
String SELECTED_STATE_INDETERMINATE = "indeterminate"; String SELECTED_STATE_INDETERMINATE = "indeterminate";
/**
* Specifies minimum width of a component.
* <p>
* <strong>Component</strong> {@link javax.swing.JButton}, {@link javax.swing.JToggleButton} and {@link javax.swing.text.JTextComponent}<br>
* <strong>Value type</strong> {@link java.lang.Integer}<br>
*/
String MINIMUM_WIDTH = "JComponent.minimumWidth";
/**
* Specifies minimum height of a component.
* <p>
* <strong>Component</strong> {@link javax.swing.JButton} and {@link javax.swing.JToggleButton}<br>
* <strong>Value type</strong> {@link java.lang.Integer}<br>
*/
String MINIMUM_HEIGHT = "JComponent.minimumHeight";
/**
* Specifies whether the progress bar has always the larger height even if no string is painted.
* <p>
* <strong>Component</strong> {@link javax.swing.JProgressBar}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*/
String PROGRESS_BAR_LARGE_HEIGHT = "JProgressBar.largeHeight";
/**
* Specifies whether the progress bar is paint with square edges.
* <p>
* <strong>Component</strong> {@link javax.swing.JProgressBar}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*/
String PROGRESS_BAR_SQUARE = "JProgressBar.square";
/**
* Specifies whether the decrease/increase arrow buttons of a scrollbar are shown.
* <p>
* <strong>Component</strong> {@link javax.swing.JScrollBar} or {@link javax.swing.JScrollPane}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*/
String SCROLL_BAR_SHOW_BUTTONS = "JScrollBar.showButtons";
/**
* Specifies whether separators are shown between tabs.
* <p>
* <strong>Component</strong> {@link javax.swing.JTabbedPane}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*/
String TABBED_PANE_SHOW_TAB_SEPARATORS = "JTabbedPane.showTabSeparators";
/**
* Specifies whether a full border is painted around a tabbed pane.
* <p>
* <strong>Component</strong> {@link javax.swing.JTabbedPane}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*/
String TABBED_PANE_HAS_FULL_BORDER = "JTabbedPane.hasFullBorder"; String TABBED_PANE_HAS_FULL_BORDER = "JTabbedPane.hasFullBorder";
/**
* Specifies the height of a tab.
* <p>
* <strong>Component</strong> {@link javax.swing.JTabbedPane}<br>
* <strong>Value type</strong> {@link java.lang.Integer}
*/
String TABBED_PANE_TAB_HEIGHT = "JTabbedPane.tabHeight";
/**
* Specifies whether all text is selected when the text component gains focus.
* <p>
* <strong>Component</strong> {@link javax.swing.JTextField} (and subclasses)<br>
* <strong>Value type</strong> {@link java.lang.String}<br>
* <strong>Allowed Values</strong> {@link #SELECT_ALL_ON_FOCUS_POLICY_NEVER},
* {@link #SELECT_ALL_ON_FOCUS_POLICY_ONCE} (default) or
* {@link #SELECT_ALL_ON_FOCUS_POLICY_ALWAYS}
*/
String SELECT_ALL_ON_FOCUS_POLICY = "JTextField.selectAllOnFocusPolicy";
/**
* Never select all text when the text component gains focus.
*
* @see #SELECT_ALL_ON_FOCUS_POLICY
*/
String SELECT_ALL_ON_FOCUS_POLICY_NEVER = "never";
/**
* Select all text when the text component gains focus for the first time
* and selection was not modified (is at end of text).
* This is the default.
*
* @see #SELECT_ALL_ON_FOCUS_POLICY
*/
String SELECT_ALL_ON_FOCUS_POLICY_ONCE = "once";
/**
* Always select all text when the text component gains focus.
*
* @see #SELECT_ALL_ON_FOCUS_POLICY
*/
String SELECT_ALL_ON_FOCUS_POLICY_ALWAYS = "always";
/**
* Placeholder text that is only painted if the text field is empty.
* <p>
* <strong>Component</strong> {@link javax.swing.JTextField} (and subclasses) or {@link javax.swing.JComboBox}<br>
* <strong>Value type</strong> {@link java.lang.String}
*/
String PLACEHOLDER_TEXT = "JTextField.placeholderText";
/**
* Height of underline if toggle button type is {@link #BUTTON_TYPE_TAB}.
* <p>
* <strong>Component</strong> {@link javax.swing.JToggleButton}<br>
* <strong>Value type</strong> {@link java.lang.Integer}
*/
String TAB_BUTTON_UNDERLINE_HEIGHT = "JToggleButton.tab.underlineHeight";
/**
* Color of underline if toggle button type is {@link #BUTTON_TYPE_TAB}.
* <p>
* <strong>Component</strong> {@link javax.swing.JToggleButton}<br>
* <strong>Value type</strong> {@link java.awt.Color}
*/
String TAB_BUTTON_UNDERLINE_COLOR = "JToggleButton.tab.underlineColor";
/**
* Background color if selected and toggle button type is {@link #BUTTON_TYPE_TAB}.
* <p>
* <strong>Component</strong> {@link javax.swing.JToggleButton}<br>
* <strong>Value type</strong> {@link java.awt.Color}
*/
String TAB_BUTTON_SELECTED_BACKGROUND = "JToggleButton.tab.selectedBackground";
/** /**
* Checks whether a client property of a component has the given value. * Checks whether a client property of a component has the given value.
*/ */
static boolean clientPropertyEquals( JComponent c, String key, Object value ) { static boolean clientPropertyEquals( JComponent c, String key, Object value ) {
return Objects.equals( c.getClientProperty( key ), value ); return Objects.equals( c.getClientProperty( key ), value );
} }
/**
* Checks whether a client property of a component is a boolean and returns its value.
* If the client property is not set, or not a boolean, defaultValue is returned.
*/
static boolean clientPropertyBoolean( JComponent c, String key, boolean defaultValue ) {
Object value = c.getClientProperty( key );
return (value instanceof Boolean) ? (boolean) value : defaultValue;
}
/**
* Checks whether a client property of a component is an integer and returns its value.
* If the client property is not set, or not an integer, defaultValue is returned.
*/
static int clientPropertyInt( JComponent c, String key, int defaultValue ) {
Object value = c.getClientProperty( key );
return (value instanceof Integer) ? (int) value : defaultValue;
}
/**
* Checks whether a client property of a component is a color and returns its value.
* If the client property is not set, or not a color, defaultValue is returned.
*/
static Color clientPropertyColor( JComponent c, String key, Color defaultValue ) {
Object value = c.getClientProperty( key );
return (value instanceof Color) ? (Color) value : defaultValue;
}
} }

View File

@@ -17,17 +17,19 @@
package com.formdev.flatlaf; package com.formdev.flatlaf;
import java.io.InputStream; import java.io.InputStream;
import javax.swing.LookAndFeel;
import javax.swing.UIDefaults;
/** /**
* Addon for FlatLaf UI defaults. * Addon for FlatLaf UI defaults.
* *
* Allows loading of additional .properties files from addon JARs. * Allows loading of additional .properties files from addon JARs.
* {@link java.util.ServiceLoader} is used to load extensions of this class from addon JARs. * {@link java.util.ServiceLoader} is used to load extensions of this class from addon JARs.
* * <p>
* If you extend this class in a addon JAR, you also have to add a text file named * If you extend this class in a addon JAR, you also have to add a text file named
* {@code META-INF/services/com.formdev.flatlaf.FlatDefaultsAddon} * {@code META-INF/services/com.formdev.flatlaf.FlatDefaultsAddon}
* to the addon JAR. The file must contain a single line with the class name. * to the addon JAR. The file must contain a single line with the class name.
* * <p>
* See 'flatlaf-swingx' addon for an example * See 'flatlaf-swingx' addon for an example
* *
* @author Karl Tauber * @author Karl Tauber
@@ -37,6 +39,33 @@ public abstract class FlatDefaultsAddon
/** /**
* Finds an addon .properties file for the given LaF class and returns * Finds an addon .properties file for the given LaF class and returns
* it as input stream. Or {@code null} if not found. * it as input stream. Or {@code null} if not found.
* <p>
* This default implementation finds addon .properties file for the given LaF class
* in the same package as the subclass.
* <p>
* Override this method to load addon .properties files from other locations.
*/ */
public abstract InputStream getDefaults( Class<?> lafClass ); public InputStream getDefaults( Class<?> lafClass ) {
Class<?> addonClass = this.getClass();
String propertiesName = '/' + addonClass.getPackage().getName().replace( '.', '/' )
+ '/' + lafClass.getSimpleName() + ".properties";
return addonClass.getResourceAsStream( propertiesName );
}
/**
* Allows modifying UI defaults after loading UI defaults.
* The default implementation does nothing.
*/
public void afterDefaultsLoading( LookAndFeel laf, UIDefaults defaults ) {
}
/**
* Returns the priority used to sort addon loading.
* The order is only important if you want overwrite UI defaults of other addons.
* Lower numbers mean higher priority.
* Returns 10000 by default.
*/
public int getPriority() {
return 10000;
}
} }

View File

@@ -0,0 +1,89 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf;
/**
* Default color palette for action icons and object icons.
* <p>
* The idea is to use only this well defined set of colors in SVG icons and
* then they are replaced at runtime to dark variants or to other theme colors.
* Then a single SVG icon (light variant) can be used for dark themes too.
* IntelliJ Platform uses this mechanism to allow themes to change IntelliJ Platform icons.
* <p>
* Use the {@code *_DARK} colors only in {@code *_dark.svg} files.
* <p>
* The colors are based on IntelliJ Platform
* <a href="https://jetbrains.design/intellij/principles/icons/#action-icons">Action icons</a>
* and
* <a href="https://jetbrains.design/intellij/principles/icons/#noun-icons">Noun icons</a>
* <p>
* These colors may be changed by IntelliJ Platform themes.
* <p>
* You may use these colors also in your application (outside of SVG icons), but do
* not use the RGB values defined in this enum.<br>
* Instead use {@code UIManager.getColor( FlatIconColors.ACTIONS_GREY.key )}.
*
* @author Karl Tauber
*/
public enum FlatIconColors
{
// colors for action icons
// see https://jetbrains.design/intellij/principles/icons/#action-icons
ACTIONS_RED ( 0xDB5860, "Actions.Red", true, false ),
ACTIONS_RED_DARK ( 0xC75450, "Actions.Red", false, true ),
ACTIONS_YELLOW ( 0xEDA200, "Actions.Yellow", true, false ),
ACTIONS_YELLOW_DARK ( 0xF0A732, "Actions.Yellow", false, true ),
ACTIONS_GREEN ( 0x59A869, "Actions.Green", true, false ),
ACTIONS_GREEN_DARK ( 0x499C54, "Actions.Green", false, true ),
ACTIONS_BLUE ( 0x389FD6, "Actions.Blue", true, false ),
ACTIONS_BLUE_DARK ( 0x3592C4, "Actions.Blue", false, true ),
ACTIONS_GREY ( 0x6E6E6E, "Actions.Grey", true, false ),
ACTIONS_GREY_DARK ( 0xAFB1B3, "Actions.Grey", false, true ),
ACTIONS_GREYINLINE ( 0x7F8B91, "Actions.GreyInline", true, false ),
ACTIONS_GREYINLINE_DARK ( 0x7F8B91, "Actions.GreyInline", false, true ),
// colors for object icons
// see https://jetbrains.design/intellij/principles/icons/#noun-icons
OBJECTS_GREY ( 0x9AA7B0, "Objects.Grey" ),
OBJECTS_BLUE ( 0x40B6E0, "Objects.Blue" ),
OBJECTS_GREEN ( 0x62B543, "Objects.Green" ),
OBJECTS_YELLOW ( 0xF4AF3D, "Objects.Yellow" ),
OBJECTS_YELLOW_DARK ( 0xD9A343, "Objects.YellowDark" ),
OBJECTS_PURPLE ( 0xB99BF8, "Objects.Purple" ),
OBJECTS_PINK ( 0xF98B9E, "Objects.Pink" ),
OBJECTS_RED ( 0xF26522, "Objects.Red" ),
OBJECTS_RED_STATUS ( 0xE05555, "Objects.RedStatus" ),
OBJECTS_GREEN_ANDROID ( 0xA4C639, "Objects.GreenAndroid" ),
OBJECTS_BLACK_TEXT ( 0x231F20, "Objects.BlackText" );
public final int rgb;
public final String key;
public final boolean light;
public final boolean dark;
FlatIconColors( int rgb, String key ) {
this( rgb, key, true, true );
}
FlatIconColors( int rgb, String key, boolean light, boolean dark ) {
this.rgb = rgb;
this.key = key;
this.light = light;
this.dark = dark;
}
}

View File

@@ -0,0 +1,644 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf;
import javax.swing.InputMap;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.LookAndFeel;
import javax.swing.UIDefaults;
import javax.swing.UIDefaults.LazyValue;
import javax.swing.plaf.InputMapUIResource;
import com.formdev.flatlaf.util.SystemInfo;
import static javax.swing.text.DefaultEditorKit.*;
/**
* @author Karl Tauber
*/
class FlatInputMaps
{
static void initInputMaps( UIDefaults defaults ) {
initBasicInputMaps( defaults );
initTextComponentInputMaps( defaults );
if( SystemInfo.IS_MAC )
initMacInputMaps( defaults );
}
private static void initBasicInputMaps( UIDefaults defaults ) {
defaults.put( "Button.focusInputMap", new UIDefaults.LazyInputMap( new Object[] {
"SPACE", "pressed",
"released SPACE", "released"
} ) );
modifyInputMap( defaults, "ComboBox.ancestorInputMap",
"SPACE", "spacePopup",
"UP", mac( "selectPrevious2", "selectPrevious" ),
"DOWN", mac( "selectNext2", "selectNext" ),
"KP_UP", mac( "selectPrevious2", "selectPrevious" ),
"KP_DOWN", mac( "selectNext2", "selectNext" ),
mac( "alt UP", null ), "togglePopup",
mac( "alt DOWN", null ), "togglePopup",
mac( "alt KP_UP", null ), "togglePopup",
mac( "alt KP_DOWN", null ), "togglePopup"
);
if( !SystemInfo.IS_MAC ) {
modifyInputMap( defaults, "FileChooser.ancestorInputMap",
"F2", "editFileName",
"BACK_SPACE", "Go Up"
);
}
// join ltr and rtl bindings to fix up/down/etc keys in right-to-left component orientation
Object[] bindings = (Object[]) defaults.get( "PopupMenu.selectedWindowInputMapBindings" );
Object[] rtlBindings = (Object[]) defaults.get( "PopupMenu.selectedWindowInputMapBindings.RightToLeft" );
if( bindings != null && rtlBindings != null ) {
Object[] newBindings = new Object[bindings.length + rtlBindings.length];
System.arraycopy( bindings, 0, newBindings, 0, bindings.length );
System.arraycopy( rtlBindings, 0, newBindings, bindings.length, rtlBindings.length );
defaults.put( "PopupMenu.selectedWindowInputMapBindings.RightToLeft", newBindings );
}
modifyInputMap( defaults, "TabbedPane.ancestorInputMap",
"ctrl TAB", "navigateNext",
"shift ctrl TAB", "navigatePrevious"
);
modifyInputMap( defaults, "Table.ancestorInputMap",
// swap to make it consistent with List and Tree
"HOME", "selectFirstRow",
"END", "selectLastRow",
"shift HOME", "selectFirstRowExtendSelection",
"shift END", "selectLastRowExtendSelection",
mac( "ctrl HOME", null ), "selectFirstColumn",
mac( "ctrl END", null ), "selectLastColumn",
mac( "shift ctrl HOME", null ), "selectFirstColumnExtendSelection",
mac( "shift ctrl END", null ), "selectLastColumnExtendSelection"
);
if( !SystemInfo.IS_MAC ) {
modifyInputMap( defaults, "Tree.focusInputMap",
"ADD", "expand",
"SUBTRACT", "collapse"
);
}
}
private static void initTextComponentInputMaps( UIDefaults defaults ) {
Object[] commonTextComponentBindings = {
// move caret one character (without selecting text)
"LEFT", backwardAction,
"RIGHT", forwardAction,
"KP_LEFT", backwardAction,
"KP_RIGHT", forwardAction,
// move caret one character and select text
"shift LEFT", selectionBackwardAction,
"shift RIGHT", selectionForwardAction,
"shift KP_LEFT", selectionBackwardAction,
"shift KP_RIGHT", selectionForwardAction,
// move caret to word (without selecting text)
mac( "ctrl LEFT", "alt LEFT" ), previousWordAction,
mac( "ctrl RIGHT", "alt RIGHT" ), nextWordAction,
mac( "ctrl KP_LEFT", "alt KP_LEFT" ), previousWordAction,
mac( "ctrl KP_RIGHT", "alt KP_RIGHT" ), nextWordAction,
// move caret to word and select text
mac( "ctrl shift LEFT", "shift alt LEFT" ), selectionPreviousWordAction,
mac( "ctrl shift RIGHT", "shift alt RIGHT" ), selectionNextWordAction,
mac( "ctrl shift KP_LEFT", "shift alt KP_LEFT" ), selectionPreviousWordAction,
mac( "ctrl shift KP_RIGHT", "shift alt KP_RIGHT" ), selectionNextWordAction,
// move caret to line begin/end (without selecting text)
mac( "HOME", "meta LEFT" ), beginLineAction,
mac( "END", "meta RIGHT" ), endLineAction,
// move caret to line begin/end and select text
mac( "shift HOME", "shift meta LEFT" ), selectionBeginLineAction,
mac( "shift END", "shift meta RIGHT" ), selectionEndLineAction,
// select all/none
mac( "ctrl A", "meta A" ), selectAllAction,
mac( "ctrl BACK_SLASH", "meta BACK_SLASH" ), "unselect", // DefaultEditorKit.unselectAction
// delete previous/next character
"BACK_SPACE", deletePrevCharAction,
"shift BACK_SPACE", deletePrevCharAction,
"ctrl H", deletePrevCharAction,
"DELETE", deleteNextCharAction,
// delete previous/next word
mac( "ctrl BACK_SPACE", "alt BACK_SPACE" ), deletePrevWordAction,
mac( "ctrl DELETE", "alt DELETE" ), deleteNextWordAction,
// clipboard
mac( "ctrl X", "meta X" ), cutAction,
mac( "ctrl C", "meta C" ), copyAction,
mac( "ctrl V", "meta V" ), pasteAction,
"CUT", cutAction,
"COPY", copyAction,
"PASTE", pasteAction,
mac( "shift DELETE", null ), cutAction,
mac( "control INSERT", null ), copyAction,
mac( "shift INSERT", null ), pasteAction,
// misc
"control shift O", "toggle-componentOrientation", // DefaultEditorKit.toggleComponentOrientation
};
Object[] macCommonTextComponentBindings = SystemInfo.IS_MAC ? new Object[] {
// move caret one character (without selecting text)
"ctrl B", backwardAction,
"ctrl F", forwardAction,
// move caret to document begin/end (without selecting text)
"HOME", beginAction,
"END", endAction,
"meta UP", beginAction,
"meta DOWN", endAction,
"meta KP_UP", beginAction,
"meta KP_DOWN", endAction,
"ctrl P", beginAction,
"ctrl N", endAction,
"ctrl V", endAction,
// move caret to line begin/end (without selecting text)
"meta KP_LEFT", beginLineAction,
"meta KP_RIGHT", endLineAction,
"ctrl A", beginLineAction,
"ctrl E", endLineAction,
// move caret to document begin/end and select text
"shift meta UP", selectionBeginAction,
"shift meta DOWN", selectionEndAction,
"shift meta KP_UP", selectionBeginAction,
"shift meta KP_DOWN", selectionEndAction,
"shift HOME", selectionBeginAction,
"shift END", selectionEndAction,
// move caret to line begin/end and select text
"shift meta KP_LEFT", selectionBeginLineAction,
"shift meta KP_RIGHT", selectionEndLineAction,
"shift UP", selectionBeginLineAction,
"shift DOWN", selectionEndLineAction,
"shift KP_UP", selectionBeginLineAction,
"shift KP_DOWN", selectionEndLineAction,
// delete previous/next word
"ctrl W", deletePrevWordAction,
"ctrl D", deleteNextCharAction,
} : null;
Object[] singleLineTextComponentBindings = {
"ENTER", JTextField.notifyAction,
};
Object[] macSingleLineTextComponentBindings = SystemInfo.IS_MAC ? new Object[] {
// move caret to line begin/end (without selecting text)
"UP", beginLineAction,
"DOWN", endLineAction,
"KP_UP", beginLineAction,
"KP_DOWN", endLineAction,
} : null;
Object[] formattedTextComponentBindings = {
// reset
"ESCAPE", "reset-field-edit",
// increment/decrement
"UP", "increment",
"DOWN", "decrement",
"KP_UP", "increment",
"KP_DOWN", "decrement",
};
Object[] passwordTextComponentBindings = {
// move caret to line begin/end (without selecting text)
mac( "ctrl LEFT", "alt LEFT" ), beginLineAction,
mac( "ctrl RIGHT", "alt RIGHT" ), endLineAction,
mac( "ctrl KP_LEFT", "alt KP_LEFT" ), beginLineAction,
mac( "ctrl KP_RIGHT", "alt KP_RIGHT" ), endLineAction,
// move caret to line begin/end and select text
mac( "ctrl shift LEFT", "shift alt LEFT" ), selectionBeginLineAction,
mac( "ctrl shift RIGHT", "shift alt RIGHT" ), selectionEndLineAction,
mac( "ctrl shift KP_LEFT", "shift alt KP_LEFT" ), selectionBeginLineAction,
mac( "ctrl shift KP_RIGHT", "shift alt KP_RIGHT" ), selectionEndLineAction,
// delete previous/next word
mac( "ctrl BACK_SPACE", "alt BACK_SPACE" ), null,
mac( "ctrl DELETE", "alt DELETE" ), null,
};
Object[] multiLineTextComponentBindings = {
// move caret one line (without selecting text)
"UP", upAction,
"DOWN", downAction,
"KP_UP", upAction,
"KP_DOWN", downAction,
// move caret one line and select text
"shift UP", selectionUpAction,
"shift DOWN", selectionDownAction,
"shift KP_UP", selectionUpAction,
"shift KP_DOWN", selectionDownAction,
// move caret one page (without selecting text)
"PAGE_UP", pageUpAction,
"PAGE_DOWN", pageDownAction,
// move caret one page and select text
"shift PAGE_UP", "selection-page-up", // DefaultEditorKit.selectionPageUpAction
"shift PAGE_DOWN", "selection-page-down", // DefaultEditorKit.selectionPageDownAction
mac( "ctrl shift PAGE_UP", "shift meta PAGE_UP" ), "selection-page-left", // DefaultEditorKit.selectionPageLeftAction
mac( "ctrl shift PAGE_DOWN", "shift meta PAGE_DOWN" ), "selection-page-right", // DefaultEditorKit.selectionPageRightAction
// move caret to document begin/end (without selecting text)
mac( "ctrl HOME", "meta UP" ), beginAction,
mac( "ctrl END", "meta DOWN" ), endAction,
// move caret to document begin/end and select text
mac( "ctrl shift HOME", "shift meta UP" ), selectionBeginAction,
mac( "ctrl shift END", "shift meta DOWN" ), selectionEndAction,
// misc
"ENTER", insertBreakAction,
"TAB", insertTabAction,
// links
mac( "ctrl T", "meta T" ), "next-link-action",
mac( "ctrl shift T", "shift meta T" ), "previous-link-action",
mac( "ctrl SPACE", "meta SPACE" ), "activate-link-action",
};
Object[] macMultiLineTextComponentBindings = SystemInfo.IS_MAC ? new Object[] {
// move caret one line (without selecting text)
"ctrl N", downAction,
"ctrl P", upAction,
// move caret to beginning/end of paragraph and select text
"shift alt UP", selectionBeginParagraphAction,
"shift alt DOWN", selectionEndParagraphAction,
"shift alt KP_UP", selectionBeginParagraphAction,
"shift alt KP_DOWN", selectionEndParagraphAction,
// move caret one page (without selecting text)
"ctrl V", pageDownAction,
} : null;
defaults.put( "TextField.focusInputMap", new LazyInputMapEx(
commonTextComponentBindings,
macCommonTextComponentBindings,
singleLineTextComponentBindings,
macSingleLineTextComponentBindings
) );
defaults.put( "FormattedTextField.focusInputMap", new LazyInputMapEx(
commonTextComponentBindings,
macCommonTextComponentBindings,
singleLineTextComponentBindings,
macSingleLineTextComponentBindings,
formattedTextComponentBindings
) );
defaults.put( "PasswordField.focusInputMap", new LazyInputMapEx(
commonTextComponentBindings,
macCommonTextComponentBindings,
singleLineTextComponentBindings,
macSingleLineTextComponentBindings,
passwordTextComponentBindings
) );
Object multiLineInputMap = new LazyInputMapEx(
commonTextComponentBindings,
macCommonTextComponentBindings,
multiLineTextComponentBindings,
macMultiLineTextComponentBindings
);
defaults.put( "TextArea.focusInputMap", multiLineInputMap );
defaults.put( "TextPane.focusInputMap", multiLineInputMap );
defaults.put( "EditorPane.focusInputMap", multiLineInputMap );
}
private static void initMacInputMaps( UIDefaults defaults ) {
// list
modifyInputMap( defaults, "List.focusInputMap",
"meta A", "selectAll",
"meta C", "copy",
"meta V", "paste",
"meta X", "cut",
// let parent scroll pane do the macOS typical scrolling without changing selection
"HOME", null,
"END", null,
"PAGE_UP", null,
"PAGE_DOWN", null,
"ctrl A", null,
"ctrl BACK_SLASH", null,
"ctrl C", null,
"ctrl DOWN", null,
"ctrl END", null,
"ctrl HOME", null,
"ctrl INSERT", null,
"ctrl KP_DOWN", null,
"ctrl KP_LEFT", null,
"ctrl KP_RIGHT", null,
"ctrl KP_UP", null,
"ctrl LEFT", null,
"ctrl PAGE_DOWN", null,
"ctrl PAGE_UP", null,
"ctrl RIGHT", null,
"ctrl SLASH", null,
"ctrl SPACE", null,
"ctrl UP", null,
"ctrl V", null,
"ctrl X", null,
"SPACE", null,
"shift ctrl DOWN", null,
"shift ctrl END", null,
"shift ctrl HOME", null,
"shift ctrl KP_DOWN", null,
"shift ctrl KP_LEFT", null,
"shift ctrl KP_RIGHT", null,
"shift ctrl KP_UP", null,
"shift ctrl LEFT", null,
"shift ctrl PAGE_DOWN", null,
"shift ctrl PAGE_UP", null,
"shift ctrl RIGHT", null,
"shift ctrl SPACE", null,
"shift ctrl UP", null,
"shift DELETE", null,
"shift INSERT", null,
"shift SPACE", null
);
modifyInputMap( defaults, "List.focusInputMap.RightToLeft",
"ctrl KP_LEFT", null,
"ctrl KP_RIGHT", null,
"ctrl LEFT", null,
"ctrl RIGHT", null,
"shift ctrl KP_LEFT", null,
"shift ctrl KP_RIGHT", null,
"shift ctrl LEFT", null,
"shift ctrl RIGHT", null
);
// scrollpane
modifyInputMap( defaults, "ScrollPane.ancestorInputMap",
"END", "scrollEnd",
"HOME", "scrollHome",
"ctrl END", null,
"ctrl HOME", null,
"ctrl PAGE_DOWN", null,
"ctrl PAGE_UP", null
);
modifyInputMap( defaults, "ScrollPane.ancestorInputMap.RightToLeft",
"ctrl PAGE_DOWN", null,
"ctrl PAGE_UP", null
);
// tabbedpane
modifyInputMap( defaults, "TabbedPane.ancestorInputMap",
"ctrl UP", null,
"ctrl KP_UP", null
);
modifyInputMap( defaults, "TabbedPane.focusInputMap",
"ctrl DOWN", null,
"ctrl KP_DOWN", null
);
// table
modifyInputMap( defaults, "Table.ancestorInputMap",
"alt TAB", "focusHeader",
"shift alt TAB", "focusHeader",
"meta A", "selectAll",
"meta C", "copy",
"meta V", "paste",
"meta X", "cut",
// let parent scroll pane do the macOS typical scrolling without changing selection
"HOME", null,
"END", null,
"PAGE_UP", null,
"PAGE_DOWN", null,
"ctrl A", null,
"ctrl BACK_SLASH", null,
"ctrl C", null,
"ctrl DOWN", null,
"ctrl END", null,
"ctrl HOME", null,
"ctrl INSERT", null,
"ctrl KP_DOWN", null,
"ctrl KP_LEFT", null,
"ctrl KP_RIGHT", null,
"ctrl KP_UP", null,
"ctrl LEFT", null,
"ctrl PAGE_DOWN", null,
"ctrl PAGE_UP", null,
"ctrl RIGHT", null,
"ctrl SLASH", null,
"ctrl SPACE", null,
"ctrl UP", null,
"ctrl V", null,
"ctrl X", null,
"F2", null,
"F8", null,
"SPACE", null,
"shift ctrl DOWN", null,
"shift ctrl END", null,
"shift ctrl HOME", null,
"shift ctrl KP_DOWN", null,
"shift ctrl KP_LEFT", null,
"shift ctrl KP_RIGHT", null,
"shift ctrl KP_UP", null,
"shift ctrl LEFT", null,
"shift ctrl PAGE_DOWN", null,
"shift ctrl PAGE_UP", null,
"shift ctrl RIGHT", null,
"shift ctrl SPACE", null,
"shift ctrl UP", null,
"shift DELETE", null,
"shift INSERT", null,
"shift SPACE", null
);
modifyInputMap( defaults, "Table.ancestorInputMap.RightToLeft",
"ctrl KP_LEFT", null,
"ctrl KP_RIGHT", null,
"ctrl LEFT", null,
"ctrl RIGHT", null,
"shift ctrl KP_LEFT", null,
"shift ctrl KP_RIGHT", null,
"shift ctrl LEFT", null,
"shift ctrl RIGHT", null
);
// tree node expanding/collapsing
modifyInputMap( defaults, "Tree.focusInputMap",
"LEFT", "selectParent",
"RIGHT", "selectChild",
"KP_LEFT", "selectParent",
"KP_RIGHT", "selectChild",
"shift LEFT", "selectParent",
"shift RIGHT", "selectChild",
"shift KP_LEFT", "selectParent",
"shift KP_RIGHT", "selectChild",
"alt LEFT", "selectParent",
"alt RIGHT", "selectChild",
"alt KP_LEFT", "selectParent",
"alt KP_RIGHT", "selectChild",
"shift HOME", "selectFirstExtendSelection",
"shift END", "selectLastExtendSelection",
"meta A", "selectAll",
"meta C", "copy",
"meta V", "paste",
"meta X", "cut",
// let parent scroll pane do the macOS typical scrolling without changing selection
"HOME", null,
"END", null,
"PAGE_UP", null,
"PAGE_DOWN", null,
"ctrl LEFT", null,
"ctrl RIGHT", null,
"ctrl KP_LEFT", null,
"ctrl KP_RIGHT", null,
"ctrl A", null,
"ctrl BACK_SLASH", null,
"ctrl C", null,
"ctrl DOWN", null,
"ctrl END", null,
"ctrl HOME", null,
"ctrl INSERT", null,
"ctrl KP_DOWN", null,
"ctrl KP_UP", null,
"ctrl PAGE_DOWN", null,
"ctrl PAGE_UP", null,
"ctrl SLASH", null,
"ctrl SPACE", null,
"ctrl UP", null,
"ctrl V", null,
"ctrl X", null,
"F2", null,
"SPACE", null,
"shift ctrl DOWN", null,
"shift ctrl END", null,
"shift ctrl HOME", null,
"shift ctrl KP_DOWN", null,
"shift ctrl KP_UP", null,
"shift ctrl PAGE_DOWN", null,
"shift ctrl PAGE_UP", null,
"shift ctrl SPACE", null,
"shift ctrl UP", null,
"shift DELETE", null,
"shift INSERT", null,
"shift PAGE_DOWN", null,
"shift PAGE_UP", null,
"shift SPACE", null
);
defaults.put( "Tree.focusInputMap.RightToLeft", new UIDefaults.LazyInputMap( new Object[] {
"LEFT", "selectChild",
"RIGHT", "selectParent",
"KP_LEFT", "selectChild",
"KP_RIGHT", "selectParent",
"shift LEFT", "selectChild",
"shift RIGHT", "selectParent",
"shift KP_LEFT", "selectChild",
"shift KP_RIGHT", "selectParent",
"alt LEFT", "selectChild",
"alt RIGHT", "selectParent",
"alt KP_LEFT", "selectChild",
"alt KP_RIGHT", "selectParent"
} ) );
}
private static void modifyInputMap( UIDefaults defaults, String key, Object... bindings ) {
// Note: not using `defaults.get(key)` here because this would resolve the lazy value
defaults.put( key, new LazyModifyInputMap( defaults.remove( key ), bindings ) );
}
private static <T> T mac( T value, T macValue ) {
return SystemInfo.IS_MAC ? macValue : value;
}
//---- class LazyInputMapEx -----------------------------------------------
/**
* Lazily creates a input map.
* Similar to {@link UIDefaults.LazyInputMap}, but can use multiple bindings arrays.
*/
private static class LazyInputMapEx
implements LazyValue
{
private final Object[][] bindingsArray;
LazyInputMapEx( Object[]... bindingsArray ) {
this.bindingsArray = bindingsArray;
}
@Override
public Object createValue( UIDefaults table ) {
InputMap inputMap = new InputMapUIResource();
for( Object[] bindings : bindingsArray )
LookAndFeel.loadKeyBindings( inputMap, bindings );
return inputMap;
}
}
//---- class LazyModifyInputMap -------------------------------------------
/**
* Takes a (lazy) base input map and lazily applies modifications to it specified in bindings.
*/
private static class LazyModifyInputMap
implements LazyValue
{
private final Object baseInputMap;
private final Object[] bindings;
LazyModifyInputMap( Object baseInputMap, Object[] bindings ) {
this.baseInputMap = baseInputMap;
this.bindings = bindings;
}
@Override
public Object createValue( UIDefaults table ) {
// get base input map
InputMap inputMap = (baseInputMap instanceof LazyValue)
? (InputMap) ((LazyValue)baseInputMap).createValue( table )
: (InputMap) baseInputMap;
// modify input map (replace or remove)
for( int i = 0; i < bindings.length; i += 2 ) {
KeyStroke keyStroke = KeyStroke.getKeyStroke( (String) bindings[i] );
if( bindings[i + 1] != null )
inputMap.put( keyStroke, bindings[i + 1] );
else
inputMap.remove( keyStroke );
}
return inputMap;
}
}
}

View File

@@ -18,31 +18,46 @@ package com.formdev.flatlaf;
import java.awt.Color; import java.awt.Color;
import java.awt.Component; import java.awt.Component;
import java.awt.Container;
import java.awt.EventQueue; import java.awt.EventQueue;
import java.awt.Font; import java.awt.Font;
import java.awt.KeyEventPostProcessor; import java.awt.Image;
import java.awt.KeyboardFocusManager; import java.awt.RenderingHints;
import java.awt.Toolkit; import java.awt.Toolkit;
import java.awt.Window; import java.awt.Window;
import java.awt.event.KeyEvent; import java.awt.image.FilteredImageSource;
import java.awt.image.ImageFilter;
import java.awt.image.ImageProducer;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.swing.AbstractButton; import java.util.Map;
import javax.swing.JLabel; import java.util.Properties;
import javax.swing.JTabbedPane; import java.util.ServiceLoader;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
import javax.swing.PopupFactory; import javax.swing.PopupFactory;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.UIDefaults; import javax.swing.UIDefaults;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException; import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.UIDefaults.ActiveValue;
import javax.swing.plaf.ColorUIResource; import javax.swing.plaf.ColorUIResource;
import javax.swing.plaf.FontUIResource; import javax.swing.plaf.FontUIResource;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicLookAndFeel; import javax.swing.plaf.basic.BasicLookAndFeel;
import javax.swing.plaf.metal.MetalLookAndFeel; import javax.swing.text.StyleContext;
import javax.swing.text.html.HTMLEditorKit; import javax.swing.text.html.HTMLEditorKit;
import com.formdev.flatlaf.util.GrayFilter;
import com.formdev.flatlaf.util.MultiResolutionImageSupport;
import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
@@ -54,21 +69,26 @@ import com.formdev.flatlaf.util.UIScale;
public abstract class FlatLaf public abstract class FlatLaf
extends BasicLookAndFeel extends BasicLookAndFeel
{ {
private BasicLookAndFeel base; static final Logger LOG = Logger.getLogger( FlatLaf.class.getName() );
private static final String DESKTOPFONTHINTS = "awt.font.desktophints";
private String desktopPropertyName; private String desktopPropertyName;
private PropertyChangeListener desktopPropertyListener; private PropertyChangeListener desktopPropertyListener;
private KeyEventPostProcessor mnemonicListener; private static boolean aquaLoaded;
private static boolean altKeyPressed; private static boolean updateUIPending;
private MnemonicHandler mnemonicHandler;
private Consumer<UIDefaults> postInitialization;
public static boolean install( LookAndFeel newLookAndFeel ) { public static boolean install( LookAndFeel newLookAndFeel ) {
try { try {
UIManager.setLookAndFeel( newLookAndFeel ); UIManager.setLookAndFeel( newLookAndFeel );
return true; return true;
} catch( Exception ex ) { } catch( Exception ex ) {
System.err.println( "Failed to initialize look and feel " + newLookAndFeel.getClass().getName() ); LOG.log( Level.SEVERE, "FlatLaf: Failed to initialize look and feel '" + newLookAndFeel.getClass().getName() + "'.", ex );
return false; return false;
} }
} }
@@ -89,7 +109,7 @@ public abstract class FlatLaf
@Override @Override
public boolean isNativeLookAndFeel() { public boolean isNativeLookAndFeel() {
return true; return false;
} }
@Override @Override
@@ -97,24 +117,36 @@ public abstract class FlatLaf
return true; return true;
} }
@Override
public Icon getDisabledIcon( JComponent component, Icon icon ) {
if( icon instanceof ImageIcon ) {
Object grayFilter = UIManager.get( "Component.grayFilter" );
ImageFilter filter = (grayFilter instanceof ImageFilter)
? (ImageFilter) grayFilter
: GrayFilter.createDisabledIconFilter( isDark() ); // fallback
Function<Image, Image> mapper = img -> {
ImageProducer producer = new FilteredImageSource( img.getSource(), filter );
return Toolkit.getDefaultToolkit().createImage( producer );
};
Image image = ((ImageIcon)icon).getImage();
return new ImageIconUIResource( MultiResolutionImageSupport.map( image, mapper ) );
}
return null;
}
@Override @Override
public void initialize() { public void initialize() {
getBase().initialize(); if( SystemInfo.IS_MAC )
initializeAqua();
super.initialize(); super.initialize();
// make sure that a plain popup factory is used (otherwise sub-menu rendering // install mnemonic handler
// is "jittery" on Mac, where AquaLookAndFeel installs its own popup factory) mnemonicHandler = new MnemonicHandler();
if( PopupFactory.getSharedInstance().getClass() != PopupFactory.class ) mnemonicHandler.install();
PopupFactory.setSharedInstance( new PopupFactory() );
// add mnemonic listener
mnemonicListener = e -> {
if( e.getKeyCode() == KeyEvent.VK_ALT )
altKeyChanged( e.getID() == KeyEvent.KEY_PRESSED );
return false;
};
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventPostProcessor( mnemonicListener );
// listen to desktop property changes to update UI if system font or scaling changes // listen to desktop property changes to update UI if system font or scaling changes
if( SystemInfo.IS_WINDOWS ) { if( SystemInfo.IS_WINDOWS ) {
@@ -129,109 +161,187 @@ public abstract class FlatLaf
} }
if( desktopPropertyName != null ) { if( desktopPropertyName != null ) {
desktopPropertyListener = e -> { desktopPropertyListener = e -> {
reSetLookAndFeel(); String propertyName = e.getPropertyName();
if( desktopPropertyName.equals( propertyName ) )
reSetLookAndFeel();
else if( DESKTOPFONTHINTS.equals( propertyName ) ) {
if( UIManager.getLookAndFeel() instanceof FlatLaf ) {
putAATextInfo( UIManager.getLookAndFeelDefaults() );
updateUILater();
}
}
}; };
Toolkit.getDefaultToolkit().addPropertyChangeListener( desktopPropertyName, desktopPropertyListener ); Toolkit toolkit = Toolkit.getDefaultToolkit();
toolkit.addPropertyChangeListener( desktopPropertyName, desktopPropertyListener );
toolkit.addPropertyChangeListener( DESKTOPFONTHINTS, desktopPropertyListener );
} }
// Following code should be ideally in initialize(), but needs color from UI defaults.
// Do not move this code to getDefaults() to avoid side effects in the case that
// getDefaults() is directly invoked from 3rd party code. E.g. `new FlatLightLaf().getDefaults()`.
postInitialization = defaults -> {
// update link color in HTML text
Color linkColor = defaults.getColor( "Component.linkColor" );
if( linkColor != null ) {
new HTMLEditorKit().getStyleSheet().addRule(
String.format( "a { color: #%06x; }", linkColor.getRGB() & 0xffffff ) );
}
};
} }
@Override @Override
public void uninitialize() { public void uninitialize() {
// remove desktop property listener // remove desktop property listener
if( desktopPropertyListener != null ) { if( desktopPropertyListener != null ) {
Toolkit.getDefaultToolkit().removePropertyChangeListener( desktopPropertyName, desktopPropertyListener ); Toolkit toolkit = Toolkit.getDefaultToolkit();
toolkit.removePropertyChangeListener( desktopPropertyName, desktopPropertyListener );
toolkit.removePropertyChangeListener( DESKTOPFONTHINTS, desktopPropertyListener );
desktopPropertyName = null; desktopPropertyName = null;
desktopPropertyListener = null; desktopPropertyListener = null;
} }
// remove mnemonic listener // uninstall mnemonic handler
if( mnemonicListener != null ) { if( mnemonicHandler != null ) {
KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventPostProcessor( mnemonicListener ); mnemonicHandler.uninstall();
mnemonicListener = null; mnemonicHandler = null;
} }
// restore default link color // restore default link color
new HTMLEditorKit().getStyleSheet().addRule( "a { color: blue; }" ); new HTMLEditorKit().getStyleSheet().addRule( "a { color: blue; }" );
postInitialization = null;
if( base != null )
base.uninitialize();
super.uninitialize(); super.uninitialize();
} }
/** /**
* Get/create base LaF. This is used to grab base UI defaults from different LaFs. * Initialize Aqua LaF on macOS, which is required for using Mac screen menubar.
* E.g. on Mac from system dependent LaF, otherwise from Metal LaF. * (at least on Java 8, since 9 it seems to work without it)
* <p>
* This loads the native library "osxui" and initializes JRSUI.
* Because both are not unloaded/uninitialized, Aqua LaF is initialized only once.
*/ */
private BasicLookAndFeel getBase() { private void initializeAqua() {
if( base == null ) { if( aquaLoaded )
if( SystemInfo.IS_MAC ) { return;
// use Mac Aqua LaF as base
try { aquaLoaded = true;
base = (BasicLookAndFeel) Class.forName( "com.apple.laf.AquaLookAndFeel" ).newInstance();
} catch( Exception ex ) { // create macOS Aqua LaF
ex.printStackTrace(); String aquaLafClassName = "com.apple.laf.AquaLookAndFeel";
throw new IllegalStateException(); BasicLookAndFeel aquaLaf;
} try {
if( SystemInfo.IS_JAVA_9_OR_LATER ) {
Method m = UIManager.class.getMethod( "createLookAndFeel", String.class );
aquaLaf = (BasicLookAndFeel) m.invoke( null, "Mac OS X" );
} else } else
base = new MetalLookAndFeel(); aquaLaf = (BasicLookAndFeel) Class.forName( aquaLafClassName ).newInstance();
} catch( Exception ex ) {
LOG.log( Level.SEVERE, "FlatLaf: Failed to initialize Aqua look and feel '" + aquaLafClassName + "'.", ex );
throw new IllegalStateException();
} }
return base;
// remember popup factory because aquaLaf.initialize() installs its own
// factory, which makes sub-menu rendering "jittery"
PopupFactory oldPopupFactory = PopupFactory.getSharedInstance();
// initialize Aqua LaF
aquaLaf.initialize();
aquaLaf.uninitialize();
// restore popup factory
PopupFactory.setSharedInstance( oldPopupFactory );
} }
@Override @Override
public UIDefaults getDefaults() { public UIDefaults getDefaults() {
UIDefaults defaults = getBase().getDefaults(); UIDefaults defaults = super.getDefaults();
// add Metal resource bundle, which is required for FlatFileChooserUI // add Metal resource bundle, which is required for FlatFileChooserUI
defaults.addResourceBundle( "com.sun.swing.internal.plaf.metal.resources.metal" ); defaults.addResourceBundle( "com.sun.swing.internal.plaf.metal.resources.metal" );
// initialize some defaults (for overriding) that are used in basic UI delegates, // initialize some defaults (for overriding) that are used in UI delegates,
// but are not set in MetalLookAndFeel or BasicLookAndFeel // but are not set in BasicLookAndFeel
Color control = defaults.getColor( "control" ); putDefaults( defaults, defaults.getColor( "control" ),
defaults.put( "EditorPane.disabledBackground", control ); "EditorPane.disabledBackground",
defaults.put( "EditorPane.inactiveBackground", control ); "EditorPane.inactiveBackground",
defaults.put( "FormattedTextField.disabledBackground", control ); "FormattedTextField.disabledBackground",
defaults.put( "PasswordField.disabledBackground", control ); "PasswordField.disabledBackground",
defaults.put( "TextArea.disabledBackground", control ); "Spinner.disabledBackground",
defaults.put( "TextArea.inactiveBackground", control ); "TextArea.disabledBackground",
defaults.put( "TextField.disabledBackground", control ); "TextArea.inactiveBackground",
defaults.put( "TextPane.disabledBackground", control ); "TextField.disabledBackground",
defaults.put( "TextPane.inactiveBackground", control ); "TextPane.disabledBackground",
"TextPane.inactiveBackground" );
// initialize some own defaults (for overriding) putDefaults( defaults, defaults.getColor( "textInactiveText" ),
defaults.put( "Spinner.disabledBackground", control ); "Button.disabledText",
defaults.put( "Spinner.disabledForeground", control ); "CheckBox.disabledText",
"CheckBoxMenuItem.disabledForeground",
// remember MenuBarUI from Mac Aqua LaF if Mac screen menubar is enabled "Menu.disabledForeground",
boolean useScreenMenuBar = SystemInfo.IS_MAC && "true".equals( System.getProperty( "apple.laf.useScreenMenuBar" ) ); "MenuItem.disabledForeground",
Object aquaMenuBarUI = useScreenMenuBar ? defaults.get( "MenuBarUI" ) : null; "RadioButton.disabledText",
"RadioButtonMenuItem.disabledForeground",
"Spinner.disabledForeground",
"ToggleButton.disabledText" );
putDefaults( defaults, defaults.getColor( "textText" ),
"DesktopIcon.foreground" );
initFonts( defaults ); initFonts( defaults );
initIconColors( defaults, isDark() ); initIconColors( defaults, isDark() );
FlatInputMaps.initInputMaps( defaults );
// get addons and sort them by priority
ServiceLoader<FlatDefaultsAddon> addonLoader = ServiceLoader.load( FlatDefaultsAddon.class );
List<FlatDefaultsAddon> addons = new ArrayList<>();
for( FlatDefaultsAddon addon : addonLoader )
addons.add( addon );
addons.sort( (addon1, addon2) -> addon1.getPriority() - addon2.getPriority() );
// load defaults from properties // load defaults from properties
List<Class<?>> lafClassesForDefaultsLoading = getLafClassesForDefaultsLoading(); List<Class<?>> lafClassesForDefaultsLoading = getLafClassesForDefaultsLoading();
if( lafClassesForDefaultsLoading != null ) if( lafClassesForDefaultsLoading != null )
UIDefaultsLoader.loadDefaultsFromProperties( lafClassesForDefaultsLoading, defaults ); UIDefaultsLoader.loadDefaultsFromProperties( lafClassesForDefaultsLoading, addons, getAdditionalDefaults(), isDark(), defaults );
else else
UIDefaultsLoader.loadDefaultsFromProperties( getClass(), defaults ); UIDefaultsLoader.loadDefaultsFromProperties( getClass(), addons, getAdditionalDefaults(), isDark(), defaults );
// use Aqua MenuBarUI if Mac screen menubar is enabled // use Aqua MenuBarUI if Mac screen menubar is enabled
if( useScreenMenuBar ) if( SystemInfo.IS_MAC && Boolean.getBoolean( "apple.laf.useScreenMenuBar" ) ) {
defaults.put( "MenuBarUI", aquaMenuBarUI ); defaults.put( "MenuBarUI", "com.apple.laf.AquaMenuBarUI" );
// update link color in HTML text // add defaults necessary for AquaMenuBarUI
Color linkColor = defaults.getColor( "Component.linkColor" ); defaults.put( "MenuBar.backgroundPainter", BorderFactory.createEmptyBorder() );
if( linkColor != null ) { }
new HTMLEditorKit().getStyleSheet().addRule(
String.format( "a { color: #%06x; }", linkColor.getRGB() & 0xffffff ) ); // initialize text antialiasing
putAATextInfo( defaults );
// apply additional defaults (e.g. from IntelliJ themes)
applyAdditionalDefaults( defaults );
// allow addons modifying UI defaults
for( FlatDefaultsAddon addon : addons )
addon.afterDefaultsLoading( this, defaults );
// add user scale factor to allow layout managers (e.g. MigLayout) to use it
defaults.put( "laf.scaleFactor", (ActiveValue) t -> {
return UIScale.getUserScaleFactor();
} );
if( postInitialization != null ) {
postInitialization.accept( defaults );
postInitialization = null;
} }
return defaults; return defaults;
} }
List<Class<?>> getLafClassesForDefaultsLoading() { void applyAdditionalDefaults( UIDefaults defaults ) {
}
protected List<Class<?>> getLafClassesForDefaultsLoading() {
return null;
}
protected Properties getAdditionalDefaults() {
return null; return null;
} }
@@ -241,17 +351,19 @@ public abstract class FlatLaf
if( SystemInfo.IS_WINDOWS ) { if( SystemInfo.IS_WINDOWS ) {
Font winFont = (Font) Toolkit.getDefaultToolkit().getDesktopProperty( "win.messagebox.font" ); Font winFont = (Font) Toolkit.getDefaultToolkit().getDesktopProperty( "win.messagebox.font" );
if( winFont != null ) if( winFont != null )
uiFont = new FontUIResource( winFont ); uiFont = createCompositeFont( winFont.getFamily(), winFont.getStyle(), winFont.getSize() );
} else if( SystemInfo.IS_MAC ) { } else if( SystemInfo.IS_MAC ) {
Font font = defaults.getFont( "Label.font" ); String fontName;
if( SystemInfo.IS_MAC_OS_10_11_EL_CAPITAN_OR_LATER ) { if( SystemInfo.IS_MAC_OS_10_11_EL_CAPITAN_OR_LATER ) {
// use San Francisco Text font // use San Francisco Text font
font = new FontUIResource( ".SF NS Text", font.getStyle(), font.getSize() ); fontName = ".SF NS Text";
} else {
// default font on older systems (see com.apple.laf.AquaFonts)
fontName = "Lucida Grande";
} }
uiFont = (font instanceof FontUIResource) ? (FontUIResource) font : new FontUIResource( font ); uiFont = createCompositeFont( fontName, Font.PLAIN, 13 );
} else if( SystemInfo.IS_LINUX ) { } else if( SystemInfo.IS_LINUX ) {
Font font = LinuxFontPolicy.getFont(); Font font = LinuxFontPolicy.getFont();
@@ -259,16 +371,34 @@ public abstract class FlatLaf
} }
if( uiFont == null ) if( uiFont == null )
return; uiFont = createCompositeFont( Font.SANS_SERIF, Font.PLAIN, 12 );
uiFont = UIScale.applyCustomScaleFactor( uiFont ); 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 // override fonts
for( Object key : defaults.keySet() ) { for( Object key : defaults.keySet() ) {
if( key instanceof String && ((String)key).endsWith( ".font" ) ) if( key instanceof String && (((String)key).endsWith( ".font" ) || ((String)key).endsWith( "Font" )) )
defaults.put( key, uiFont ); defaults.put( key, activeFont );
} }
defaults.put( "MenuItem.acceleratorFont", uiFont );
// use smaller font for progress bar
defaults.put( "ProgressBar.font", new ActiveFont( 0.85f ) );
// set default font
defaults.put( "defaultFont", uiFont );
}
static FontUIResource createCompositeFont( String family, int style, int size ) {
// using StyleContext.getFont() here because it uses
// sun.font.FontUtilities.getCompositeFontUIResource()
// and creates a composite font that is able to display all Unicode characters
Font font = new StyleContext().getFont( family, style, size );
return (font instanceof FontUIResource) ? (FontUIResource) font : new FontUIResource( font );
} }
/** /**
@@ -284,37 +414,59 @@ public abstract class FlatLaf
* <a href="https://jetbrains.design/intellij/principles/icons/#action-icons">Action icons</a> * <a href="https://jetbrains.design/intellij/principles/icons/#action-icons">Action icons</a>
* and * and
* <a href="https://jetbrains.design/intellij/principles/icons/#noun-icons">Noun icons</a> * <a href="https://jetbrains.design/intellij/principles/icons/#noun-icons">Noun icons</a>
* <p>
* These colors may be changed by IntelliJ Platform themes.
*/ */
public static void initIconColors( UIDefaults defaults, boolean dark ) { public static void initIconColors( UIDefaults defaults, boolean dark ) {
// colors for action icons for( FlatIconColors c : FlatIconColors.values() ) {
// see https://jetbrains.design/intellij/principles/icons/#action-icons if( c.light == !dark || c.dark == dark )
defaults.put( "Actions.Red", new ColorUIResource( !dark ? 0xDB5860 : 0xC75450 ) ); defaults.put( c.key, new ColorUIResource( c.rgb ) );
defaults.put( "Actions.Yellow", new ColorUIResource( !dark ? 0xEDA200 : 0xF0A732 ) ); }
defaults.put( "Actions.Green", new ColorUIResource( !dark ? 0x59A869 : 0x499C54 ) ); }
defaults.put( "Actions.Blue", new ColorUIResource( !dark ? 0x389FD6 : 0x3592C4 ) );
defaults.put( "Actions.Grey", new ColorUIResource( !dark ? 0x6E6E6E : 0xAFB1B3 ) );
defaults.put( "Actions.GreyInline", new ColorUIResource( !dark ? 0x7F8B91 : 0x7F8B91 ) );
// colors for object icons private void putAATextInfo( UIDefaults defaults ) {
// see https://jetbrains.design/intellij/principles/icons/#noun-icons if( SystemInfo.IS_JAVA_9_OR_LATER ) {
defaults.put( "Objects.Grey", new ColorUIResource( 0x9AA7B0 ) ); Object desktopHints = Toolkit.getDefaultToolkit().getDesktopProperty( DESKTOPFONTHINTS );
defaults.put( "Objects.Blue", new ColorUIResource( 0x40B6E0 ) ); if( desktopHints instanceof Map ) {
defaults.put( "Objects.Green", new ColorUIResource( 0x62B543 ) ); @SuppressWarnings( "unchecked" )
defaults.put( "Objects.Yellow", new ColorUIResource( 0xF4AF3D ) ); Map<Object, Object> hints = (Map<Object, Object>) desktopHints;
defaults.put( "Objects.YellowDark", new ColorUIResource( 0xD9A343 ) ); Object aaHint = hints.get( RenderingHints.KEY_TEXT_ANTIALIASING );
defaults.put( "Objects.Purple", new ColorUIResource( 0xB99BF8 ) ); if( aaHint != null &&
defaults.put( "Objects.Pink", new ColorUIResource( 0xF98B9E ) ); aaHint != RenderingHints.VALUE_TEXT_ANTIALIAS_OFF &&
defaults.put( "Objects.Red", new ColorUIResource( 0xF26522 ) ); aaHint != RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT )
defaults.put( "Objects.RedStatus", new ColorUIResource( 0xE05555 ) ); {
defaults.put( "Objects.GreenAndroid", new ColorUIResource( 0xA4C639 ) ); defaults.put( RenderingHints.KEY_TEXT_ANTIALIASING, aaHint );
defaults.put( "Objects.BlackText", new ColorUIResource( 0x231F20 ) ); defaults.put( RenderingHints.KEY_TEXT_LCD_CONTRAST,
hints.get( RenderingHints.KEY_TEXT_LCD_CONTRAST ) );
}
}
} else {
// Java 8
try {
Object key = Class.forName( "sun.swing.SwingUtilities2" )
.getField( "AA_TEXT_PROPERTY_KEY" )
.get( null );
Object value = Class.forName( "sun.swing.SwingUtilities2$AATextInfo" )
.getMethod( "getAATextInfo", boolean.class )
.invoke( null, true );
defaults.put( key, value );
} catch( Exception ex ) {
Logger.getLogger( FlatLaf.class.getName() ).log( Level.SEVERE, null, ex );
throw new RuntimeException( ex );
}
}
}
private void putDefaults( UIDefaults defaults, Object value, String... keys ) {
for( String key : keys )
defaults.put( key, value );
} }
private static void reSetLookAndFeel() { private static void reSetLookAndFeel() {
EventQueue.invokeLater( () -> { EventQueue.invokeLater( () -> {
LookAndFeel lookAndFeel = UIManager.getLookAndFeel();
try { try {
// re-set current LaF // re-set current LaF
LookAndFeel lookAndFeel = UIManager.getLookAndFeel();
UIManager.setLookAndFeel( lookAndFeel ); UIManager.setLookAndFeel( lookAndFeel );
// must fire property change events ourself because old and new LaF are the same // must fire property change events ourself because old and new LaF are the same
@@ -325,13 +477,13 @@ public abstract class FlatLaf
// update UI // update UI
updateUI(); updateUI();
} catch( UnsupportedLookAndFeelException ex ) { } catch( UnsupportedLookAndFeelException ex ) {
ex.printStackTrace(); LOG.log( Level.SEVERE, "FlatLaf: Failed to reinitialize look and feel '" + lookAndFeel.getClass().getName() + "'.", ex );
} }
} ); } );
} }
/** /**
* Update UI of all application windows. * Update UI of all application windows immediately.
* Invoke after changing LaF. * Invoke after changing LaF.
*/ */
public static void updateUI() { public static void updateUI() {
@@ -339,60 +491,83 @@ public abstract class FlatLaf
SwingUtilities.updateComponentTreeUI( w ); SwingUtilities.updateComponentTreeUI( w );
} }
public static boolean isShowMnemonics() { /**
return altKeyPressed || !UIManager.getBoolean( "Component.hideMnemonics" ); * Update UI of all application windows later.
} */
public static void updateUILater() {
synchronized( FlatLaf.class ) {
if( updateUIPending )
return;
private static void altKeyChanged( boolean pressed ) { updateUIPending = true;
if( pressed == altKeyPressed )
return;
altKeyPressed = pressed;
// check whether it is necessary to repaint
if( !UIManager.getBoolean( "Component.hideMnemonics" ) )
return;
// get focus owner
Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
if( focusOwner == null )
return;
// get focused window
Window window = SwingUtilities.windowForComponent( focusOwner );
if( window == null )
return;
// repaint components with mnemonics in focused window
repaintMnemonics( window );
}
private static void repaintMnemonics( Container container ) {
for( Component c : container.getComponents() ) {
if( hasMnemonic( c ) )
c.repaint();
if( c instanceof Container )
repaintMnemonics( (Container) c );
} }
}
private static boolean hasMnemonic( Component c ) { EventQueue.invokeLater( () -> {
if( c instanceof JLabel && ((JLabel)c).getDisplayedMnemonicIndex() >= 0 ) updateUI();
return true; synchronized( FlatLaf.class ) {
updateUIPending = false;
if( c instanceof AbstractButton && ((AbstractButton)c).getDisplayedMnemonicIndex() >= 0 )
return true;
if( c instanceof JTabbedPane ) {
JTabbedPane tabPane = (JTabbedPane) c;
int tabCount = tabPane.getTabCount();
for( int i = 0; i < tabCount; i++ ) {
if( tabPane.getDisplayedMnemonicIndexAt( i ) >= 0 )
return true;
} }
} );
}
public static boolean isShowMnemonics() {
return MnemonicHandler.isShowMnemonics();
}
public static void showMnemonics( Component c ) {
MnemonicHandler.showMnemonics( true, c );
}
public static void hideMnemonics() {
MnemonicHandler.showMnemonics( false, null );
}
//---- class ActiveFont ---------------------------------------------------
private static class ActiveFont
implements ActiveValue
{
private final float scaleFactor;
// cache (scaled) font
private Font font;
private Font lastDefaultFont;
ActiveFont( float scaleFactor ) {
this.scaleFactor = scaleFactor;
} }
return false; @Override
public Object createValue( UIDefaults table ) {
Font defaultFont = UIManager.getFont( "defaultFont" );
if( lastDefaultFont != defaultFont ) {
lastDefaultFont = defaultFont;
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 );
}
}
return font;
}
}
//---- class ImageIconUIResource ------------------------------------------
private static class ImageIconUIResource
extends ImageIcon
implements UIResource
{
ImageIconUIResource( Image image ) {
super( image );
}
} }
} }

View File

@@ -0,0 +1,122 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Properties;
/**
* A Flat LaF that is able to load UI defaults from properties passed to the constructor.
* <p>
* Specify the base theme in the properties with {@code @baseTheme=<baseTheme>}.
* Allowed values for {@code <baseTheme>} are {@code light} (the default), {@code dark},
* {@code intellij} or {@code darcula}.
* <p>
* The properties are applied after loading the base theme and may overwrite base properties.
* All features of FlatLaf properties files are available.
*
* @author Karl Tauber
*/
public class FlatPropertiesLaf
extends FlatLaf
{
private final String name;
private final String baseTheme;
private final boolean dark;
private final Properties properties;
public FlatPropertiesLaf( String name, File propertiesFile )
throws IOException
{
this( name, new FileInputStream( propertiesFile ) );
}
public FlatPropertiesLaf( String name, InputStream in )
throws IOException
{
this( name, loadProperties( in ) );
}
private static Properties loadProperties( InputStream in )
throws IOException
{
Properties properties = new Properties();
try( InputStream in2 = in ) {
properties.load( in2 );
}
return properties;
}
public FlatPropertiesLaf( String name, Properties properties ) {
this.name = name;
this.properties = properties;
baseTheme = properties.getProperty( "@baseTheme", "light" );
dark = "dark".equalsIgnoreCase( baseTheme ) || "darcula".equalsIgnoreCase( baseTheme );
}
@Override
public String getName() {
return name;
}
@Override
public String getDescription() {
return name;
}
@Override
public boolean isDark() {
return dark;
}
@Override
protected ArrayList<Class<?>> getLafClassesForDefaultsLoading() {
ArrayList<Class<?>> lafClasses = new ArrayList<>();
lafClasses.add( FlatLaf.class );
switch( baseTheme.toLowerCase() ) {
default:
case "light":
lafClasses.add( FlatLightLaf.class );
break;
case "dark":
lafClasses.add( FlatDarkLaf.class );
break;
case "intellij":
lafClasses.add( FlatLightLaf.class );
lafClasses.add( FlatIntelliJLaf.class );
break;
case "darcula":
lafClasses.add( FlatDarkLaf.class );
lafClasses.add( FlatDarculaLaf.class );
break;
}
return lafClasses;
}
@Override
protected Properties getAdditionalDefaults() {
return properties;
}
}

View File

@@ -29,6 +29,7 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.logging.Level;
import javax.swing.UIDefaults; import javax.swing.UIDefaults;
import javax.swing.plaf.ColorUIResource; import javax.swing.plaf.ColorUIResource;
import com.formdev.flatlaf.json.Json; import com.formdev.flatlaf.json.Json;
@@ -72,8 +73,7 @@ public class IntelliJTheme
try { try {
return FlatLaf.install( createLaf( in ) ); return FlatLaf.install( createLaf( in ) );
} catch( Exception ex ) { } catch( Exception ex ) {
System.err.println( "Failed to load IntelliJ theme" ); FlatLaf.LOG.log( Level.SEVERE, "FlatLaf: Failed to load IntelliJ theme", ex );
ex.printStackTrace();
return false; return false;
} }
} }
@@ -86,7 +86,7 @@ public class IntelliJTheme
* Using a buffered input stream is not necessary. * Using a buffered input stream is not necessary.
*/ */
public static FlatLaf createLaf( InputStream in ) public static FlatLaf createLaf( InputStream in )
throws IOException, ParseException throws IOException
{ {
return createLaf( new IntelliJTheme( in ) ); return createLaf( new IntelliJTheme( in ) );
} }
@@ -106,11 +106,13 @@ public class IntelliJTheme
*/ */
@SuppressWarnings( "unchecked" ) @SuppressWarnings( "unchecked" )
public IntelliJTheme( InputStream in ) public IntelliJTheme( InputStream in )
throws IOException, ParseException throws IOException
{ {
Map<String, Object> json; Map<String, Object> json;
try( Reader reader = new InputStreamReader( in, StandardCharsets.UTF_8 ) ) { try( Reader reader = new InputStreamReader( in, StandardCharsets.UTF_8 ) ) {
json = (Map<String, Object>) Json.parse( reader ); json = (Map<String, Object>) Json.parse( reader );
} catch( ParseException ex ) {
throw new IOException( ex.getMessage(), ex );
} }
name = (String) json.get( "name" ); name = (String) json.get( "name" );
@@ -128,6 +130,12 @@ public class IntelliJTheme
defaults.put( "Component.isIntelliJTheme", true ); defaults.put( "Component.isIntelliJTheme", true );
// enable button shadows
defaults.put( "Button.paintShadow", true );
defaults.put( "Button.shadowWidth", dark ? 2 : 1 );
Map<Object, Object> themeSpecificDefaults = removeThemeSpecificDefaults( defaults );
loadNamedColors( defaults ); loadNamedColors( defaults );
// convert Json "ui" structure to UI defaults // convert Json "ui" structure to UI defaults
@@ -139,6 +147,20 @@ public class IntelliJTheme
applyColorPalette( defaults ); applyColorPalette( defaults );
applyCheckBoxColors( defaults ); applyCheckBoxColors( defaults );
// IDEA uses a SVG icon for the help button, but paints the background with Button.startBackground and Button.endBackground
Object helpButtonBackground = defaults.get( "Button.startBackground" );
Object helpButtonBorderColor = defaults.get( "Button.startBorderColor" );
if( helpButtonBackground == null )
helpButtonBackground = defaults.get( "Button.background" );
if( helpButtonBorderColor == null )
helpButtonBorderColor = defaults.get( "Button.borderColor" );
defaults.put( "HelpButton.background", helpButtonBackground );
defaults.put( "HelpButton.borderColor", helpButtonBorderColor );
defaults.put( "HelpButton.disabledBackground", defaults.get( "Panel.background" ) );
defaults.put( "HelpButton.disabledBorderColor", defaults.get( "Button.disabledBorderColor" ) );
defaults.put( "HelpButton.focusedBorderColor", defaults.get( "Button.focusedBorderColor" ) );
defaults.put( "HelpButton.focusedBackground", defaults.get( "Button.focusedBackground" ) );
// IDEA uses TextField.background for editable ComboBox and Spinner // IDEA uses TextField.background for editable ComboBox and Spinner
defaults.put( "ComboBox.editableBackground", defaults.get( "TextField.background" ) ); defaults.put( "ComboBox.editableBackground", defaults.get( "TextField.background" ) );
defaults.put( "Spinner.background", defaults.get( "TextField.background" ) ); defaults.put( "Spinner.background", defaults.get( "TextField.background" ) );
@@ -165,6 +187,42 @@ public class IntelliJTheme
if( !uiKeys.contains( "Spinner.background" ) ) if( !uiKeys.contains( "Spinner.background" ) )
defaults.put( "Spinner.background", textFieldBackground ); defaults.put( "Spinner.background", textFieldBackground );
} }
// fix ToggleButton
if( !uiKeys.contains( "ToggleButton.startBackground" ) && !uiKeys.contains( "*.startBackground" ) )
defaults.put( "ToggleButton.startBackground", defaults.get( "Button.startBackground" ) );
if( !uiKeys.contains( "ToggleButton.endBackground" ) && !uiKeys.contains( "*.endBackground" ) )
defaults.put( "ToggleButton.endBackground", defaults.get( "Button.endBackground" ) );
if( !uiKeys.contains( "ToggleButton.foreground" ) && uiKeys.contains( "Button.foreground" ) )
defaults.put( "ToggleButton.foreground", defaults.get( "Button.foreground" ) );
// limit tree row height
int rowHeight = defaults.getInt( "Tree.rowHeight" );
if( rowHeight > 22 )
defaults.put( "Tree.rowHeight", 22 );
// apply theme specific UI defaults at the end to allow overwriting
defaults.putAll( themeSpecificDefaults );
}
private Map<Object, Object> removeThemeSpecificDefaults( UIDefaults defaults ) {
// search for theme specific UI defaults keys
ArrayList<String> themeSpecificKeys = new ArrayList<>();
for( Object key : defaults.keySet() ) {
if( key instanceof String && ((String)key).startsWith( "[" ) )
themeSpecificKeys.add( (String) key );
}
// remove theme specific UI defaults and remember only those for current theme
Map<Object, Object> themeSpecificDefaults = new HashMap<>();
String currentThemePrefix = '[' + name.replace( ' ', '_' ) + ']';
for( String key : themeSpecificKeys ) {
Object value = defaults.remove( key );
if( key.startsWith( currentThemePrefix ) )
themeSpecificDefaults.put( key.substring( currentThemePrefix.length() ), value );
}
return themeSpecificDefaults;
} }
/** /**
@@ -182,7 +240,7 @@ public class IntelliJTheme
if( color != null ) { if( color != null ) {
String key = e.getKey(); String key = e.getKey();
namedColors.put( key, color ); namedColors.put( key, color );
defaults.put( "ColorPalette." + e.getKey(), color ); defaults.put( "ColorPalette." + key, color );
} }
} }
} }
@@ -198,6 +256,11 @@ public class IntelliJTheme
} else { } else {
uiKeys.add( key ); uiKeys.add( key );
// fix ComboBox size and Spinner border in all Material UI Lite themes
boolean isMaterialUILite = author.equals( "Mallowigi" );
if( isMaterialUILite && (key.equals( "ComboBox.padding" ) || key.equals( "Spinner.border" )) )
return; // ignore
// map keys // map keys
key = uiKeyMapping.getOrDefault( key, key ); key = uiKeyMapping.getOrDefault( key, key );
if( key.isEmpty() ) if( key.isEmpty() )
@@ -212,7 +275,9 @@ public class IntelliJTheme
if( uiValue == null ) { if( uiValue == null ) {
// fix errors (missing '#' for colors) // fix errors (missing '#' for colors)
if( !valueStr.startsWith( "#" ) && (key.endsWith( "ground" ) || key.endsWith( "Color" )) ) if( !valueStr.startsWith( "#" ) && (key.endsWith( "ground" ) || key.endsWith( "Color" )) )
valueStr = "#" + valueStr; valueStr = fixColorIfValid( "#" + valueStr, valueStr );
else if( valueStr.startsWith( "##" ) )
valueStr = fixColorIfValid( valueStr.substring( 1 ), valueStr );
else if( key.endsWith( ".border" ) || key.endsWith( "Border" ) ) { else if( key.endsWith( ".border" ) || key.endsWith( "Border" ) ) {
List<String> parts = StringUtils.split( valueStr, ',' ); List<String> parts = StringUtils.split( valueStr, ',' );
if( parts.size() == 5 && !parts.get( 4 ).startsWith( "#" ) ) { if( parts.size() == 5 && !parts.get( 4 ).startsWith( "#" ) ) {
@@ -225,7 +290,7 @@ public class IntelliJTheme
try { try {
uiValue = UIDefaultsLoader.parseValue( key, valueStr ); uiValue = UIDefaultsLoader.parseValue( key, valueStr );
} catch( RuntimeException ex ) { } catch( RuntimeException ex ) {
UIDefaultsLoader.logParseError( key, valueStr, ex ); UIDefaultsLoader.logParseError( Level.CONFIG, key, valueStr, ex );
return; // ignore invalid value return; // ignore invalid value
} }
} }
@@ -250,7 +315,7 @@ public class IntelliJTheme
// (e.g. set ComboBox.buttonEditableBackground to *.background // (e.g. set ComboBox.buttonEditableBackground to *.background
// because it is mapped from ComboBox.ArrowButton.background) // because it is mapped from ComboBox.ArrowButton.background)
String km = uiKeyInverseMapping.getOrDefault( k, (String) k ); String km = uiKeyInverseMapping.getOrDefault( k, (String) k );
if( km.endsWith( tail ) && !noWildcardReplace.contains( k ) && !((String)k).startsWith( "CheckBox.icon." ) ) if( km.endsWith( tail ) && !((String)k).startsWith( "CheckBox.icon." ) )
defaults.put( k, uiValue ); defaults.put( k, uiValue );
} }
} }
@@ -259,6 +324,17 @@ public class IntelliJTheme
} }
} }
private String fixColorIfValid( String newColorStr, String colorStr ) {
try {
// check whether it is valid
UIDefaultsLoader.parseColorRGBA( newColorStr );
return newColorStr;
} catch( IllegalArgumentException ex ) {
return colorStr;
}
}
private void applyColorPalette( UIDefaults defaults ) { private void applyColorPalette( UIDefaults defaults ) {
if( icons == null ) if( icons == null )
return; return;
@@ -324,33 +400,43 @@ public class IntelliJTheme
value = "#ffffff"; value = "#ffffff";
} }
String key2 = checkboxDuplicateColors.get( key );
if( dark ) if( dark )
key = StringUtils.removeTrailing( key, ".Dark" ); key = StringUtils.removeTrailing( key, ".Dark" );
String newKey = checkboxKeyMapping.get( key ); String newKey = checkboxKeyMapping.get( key );
if( newKey != null ) { if( newKey != null ) {
ColorUIResource color = toColor( (String) value ); ColorUIResource color = toColor( (String) value );
if( color != null ) if( color != null ) {
defaults.put( newKey, color ); defaults.put( newKey, color );
if( key2 != null ) {
// When IDEA replaces colors in SVGs it uses color values and not the keys
// from com.intellij.ide.ui.UITheme.colorPalette, but there are some keys that
// have same color value:
// - Checkbox.Background.Default.Dark has same color as Checkbox.Background.Selected.Dark
// - Checkbox.Border.Default.Dark has same color as Checkbox.Border.Selected.Dark
// - Checkbox.Focus.Thin.Default.Dark has same color as Checkbox.Focus.Thin.Selected.Dark
//
// So if only e.g. Checkbox.Background.Default.Dark is specified in .theme.json,
// then this color is also used for Checkbox.Background.Selected.Dark.
//
// If Checkbox.Background.Default.Dark and Checkbox.Background.Selected.Dark
// are specified in .theme.json, then the later specified is used for both.
if( dark )
key2 = StringUtils.removeTrailing( key2, ".Dark" );
String newKey2 = checkboxKeyMapping.get( key2 );
if( newKey2 != null )
defaults.put( newKey2, color );
}
}
checkboxModified = true; checkboxModified = true;
} }
} }
// When IDEA replaces colors in SVGs it uses color values and not the keys
// from com.intellij.ide.ui.UITheme.colorPalette, but there are some keys that
// have same color value:
// - Checkbox.Background.Default.Dark has same color as Checkbox.Background.Selected.Dark
// - Checkbox.Border.Default.Dark has same color as Checkbox.Border.Selected.Dark
// - Checkbox.Focus.Thin.Default.Dark has same color as Checkbox.Focus.Thin.Selected.Dark
//
// So if only e.g. Checkbox.Background.Default.Dark is specified in .theme.json,
// then this color is also used for Checkbox.Background.Selected.Dark.
// Occurs e.g. in "Dark purple" theme.
fixCheckBoxColor( defaults, colorPalette, "Checkbox.Background.Default.Dark", "Checkbox.Background.Selected.Dark" );
fixCheckBoxColor( defaults, colorPalette, "Checkbox.Border.Default.Dark", "Checkbox.Border.Selected.Dark" );
fixCheckBoxColor( defaults, colorPalette, "Checkbox.Focus.Thin.Default.Dark", "Checkbox.Focus.Thin.Selected.Dark" );
// remove hover and pressed colors // remove hover and pressed colors
if( checkboxModified ) { if( checkboxModified ) {
defaults.remove( "CheckBox.icon.hoverBorderColor" ); defaults.remove( "CheckBox.icon.hoverBorderColor" );
@@ -360,37 +446,19 @@ public class IntelliJTheme
defaults.remove( "CheckBox.icon.selectedHoverBackground" ); defaults.remove( "CheckBox.icon.selectedHoverBackground" );
defaults.remove( "CheckBox.icon.selectedPressedBackground" ); defaults.remove( "CheckBox.icon.selectedPressedBackground" );
} }
}
private void fixCheckBoxColor( UIDefaults defaults, Map<String, Object> colorPalette, String key1, String key2 ) { // copy values
if( colorPalette.containsKey( key1 ) == colorPalette.containsKey( key2 ) ) for( Map.Entry<String, String> e : uiKeyCopying.entrySet() )
return; defaults.put( e.getKey(), defaults.get( e.getValue() ) );
String newKey1 = checkboxKeyMapping.get( StringUtils.removeTrailing( key1, ".Dark" ) );
String newKey2 = checkboxKeyMapping.get( StringUtils.removeTrailing( key2, ".Dark" ) );
if( colorPalette.containsKey( key1 ) )
defaults.put( newKey2, defaults.get( newKey1 ) );
else
defaults.put( newKey1, defaults.get( newKey2 ) );
} }
private static Map<String, String> uiKeyMapping = new HashMap<>(); private static Map<String, String> uiKeyMapping = new HashMap<>();
private static Map<String, String> uiKeyCopying = new HashMap<>();
private static Map<String, String> uiKeyInverseMapping = new HashMap<>(); private static Map<String, String> uiKeyInverseMapping = new HashMap<>();
private static Map<String, String> checkboxKeyMapping = new HashMap<>(); private static Map<String, String> checkboxKeyMapping = new HashMap<>();
private static Set<String> noWildcardReplace = new HashSet<>(); private static Map<String, String> checkboxDuplicateColors = new HashMap<>();
static { static {
// Button
// IDEA buttons support gradient for background and border, but FlatLaf does not
uiKeyMapping.put( "Button.startBackground", "Button.background" );
uiKeyMapping.put( "Button.startBorderColor", "Button.borderColor" );
uiKeyMapping.put( "Button.default.startBackground", "Button.default.background" );
uiKeyMapping.put( "Button.default.startBorderColor", "Button.default.borderColor" );
uiKeyMapping.put( "Button.endBackground", "" ); // ignore
uiKeyMapping.put( "Button.endBorderColor", "" ); // ignore
uiKeyMapping.put( "Button.default.endBackground", "" ); // ignore
uiKeyMapping.put( "Button.default.endBorderColor", "" ); // ignore
// ComboBox // ComboBox
uiKeyMapping.put( "ComboBox.background", "" ); // ignore uiKeyMapping.put( "ComboBox.background", "" ); // ignore
uiKeyMapping.put( "ComboBox.nonEditableBackground", "ComboBox.background" ); uiKeyMapping.put( "ComboBox.nonEditableBackground", "ComboBox.background" );
@@ -399,6 +467,9 @@ public class IntelliJTheme
uiKeyMapping.put( "ComboBox.ArrowButton.iconColor", "ComboBox.buttonArrowColor" ); uiKeyMapping.put( "ComboBox.ArrowButton.iconColor", "ComboBox.buttonArrowColor" );
uiKeyMapping.put( "ComboBox.ArrowButton.nonEditableBackground", "ComboBox.buttonBackground" ); uiKeyMapping.put( "ComboBox.ArrowButton.nonEditableBackground", "ComboBox.buttonBackground" );
// Link
uiKeyMapping.put( "Link.activeForeground", "Component.linkColor" );
// ProgressBar // ProgressBar
uiKeyMapping.put( "ProgressBar.background", "" ); // ignore uiKeyMapping.put( "ProgressBar.background", "" ); // ignore
uiKeyMapping.put( "ProgressBar.foreground", "" ); // ignore uiKeyMapping.put( "ProgressBar.foreground", "" ); // ignore
@@ -409,12 +480,21 @@ public class IntelliJTheme
uiKeyMapping.put( "ScrollBar.trackColor", "ScrollBar.track" ); uiKeyMapping.put( "ScrollBar.trackColor", "ScrollBar.track" );
uiKeyMapping.put( "ScrollBar.thumbColor", "ScrollBar.thumb" ); uiKeyMapping.put( "ScrollBar.thumbColor", "ScrollBar.thumb" );
// Separator
uiKeyMapping.put( "Separator.separatorColor", "Separator.foreground" );
// Slider // Slider
uiKeyMapping.put( "Slider.trackWidth", "" ); // ignore (used in Material Theme UI Lite) uiKeyMapping.put( "Slider.trackWidth", "" ); // ignore (used in Material Theme UI Lite)
for( Map.Entry<String, String> e : uiKeyMapping.entrySet() ) for( Map.Entry<String, String> e : uiKeyMapping.entrySet() )
uiKeyInverseMapping.put( e.getValue(), e.getKey() ); uiKeyInverseMapping.put( e.getValue(), e.getKey() );
uiKeyCopying.put( "ToggleButton.tab.underlineColor", "TabbedPane.underlineColor" );
uiKeyCopying.put( "ToggleButton.tab.disabledUnderlineColor", "TabbedPane.disabledUnderlineColor" );
uiKeyCopying.put( "ToggleButton.tab.selectedBackground", "TabbedPane.selectedBackground" );
uiKeyCopying.put( "ToggleButton.tab.hoverBackground", "TabbedPane.hoverColor" );
uiKeyCopying.put( "ToggleButton.tab.focusBackground", "TabbedPane.focusColor" );
checkboxKeyMapping.put( "Checkbox.Background.Default", "CheckBox.icon.background" ); checkboxKeyMapping.put( "Checkbox.Background.Default", "CheckBox.icon.background" );
checkboxKeyMapping.put( "Checkbox.Background.Disabled", "CheckBox.icon.disabledBackground" ); checkboxKeyMapping.put( "Checkbox.Background.Disabled", "CheckBox.icon.disabledBackground" );
checkboxKeyMapping.put( "Checkbox.Border.Default", "CheckBox.icon.borderColor" ); checkboxKeyMapping.put( "Checkbox.Border.Default", "CheckBox.icon.borderColor" );
@@ -427,15 +507,13 @@ public class IntelliJTheme
checkboxKeyMapping.put( "Checkbox.Foreground.Selected", "CheckBox.icon.checkmarkColor" ); 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.selectedFocusedBorderColor" );
// because FlatLaf uses Button.background and Button.borderColor, checkboxDuplicateColors.put( "Checkbox.Background.Default.Dark", "Checkbox.Background.Selected.Dark" );
// but IDEA uses Button.startBackground and Button.startBorderColor, checkboxDuplicateColors.put( "Checkbox.Border.Default.Dark", "Checkbox.Border.Selected.Dark" );
// our default button background and border colors may be replaced by checkboxDuplicateColors.put( "Checkbox.Focus.Thin.Default.Dark", "Checkbox.Focus.Thin.Selected.Dark" );
// wildcard *.background and *.borderColor colors @SuppressWarnings( "unchecked" )
noWildcardReplace.add( "Button.background" ); Map.Entry<String, String>[] entries = checkboxDuplicateColors.entrySet().toArray( new Map.Entry[checkboxDuplicateColors.size()] );
noWildcardReplace.add( "Button.borderColor" ); for( Map.Entry<String, String> e : entries )
noWildcardReplace.add( "Button.default.background" ); checkboxDuplicateColors.put( e.getValue(), e.getKey() );
noWildcardReplace.add( "Button.default.borderColor" );
noWildcardReplace.add( "ToggleButton.background" );
} }
//---- class ThemeLaf ----------------------------------------------------- //---- class ThemeLaf -----------------------------------------------------
@@ -469,14 +547,12 @@ public class IntelliJTheme
} }
@Override @Override
public UIDefaults getDefaults() { void applyAdditionalDefaults( UIDefaults defaults ) {
UIDefaults defaults = super.getDefaults();
theme.applyProperties( defaults ); theme.applyProperties( defaults );
return defaults;
} }
@Override @Override
ArrayList<Class<?>> getLafClassesForDefaultsLoading() { protected ArrayList<Class<?>> getLafClassesForDefaultsLoading() {
ArrayList<Class<?>> lafClasses = new ArrayList<>(); ArrayList<Class<?>> lafClasses = new ArrayList<>();
lafClasses.add( FlatLaf.class ); lafClasses.add( FlatLaf.class );
lafClasses.add( theme.dark ? FlatDarkLaf.class : FlatLightLaf.class ); lafClasses.add( theme.dark ? FlatDarkLaf.class : FlatLightLaf.class );

View File

@@ -17,6 +17,7 @@
package com.formdev.flatlaf; package com.formdev.flatlaf;
import java.awt.Font; import java.awt.Font;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment; import java.awt.GraphicsEnvironment;
import java.awt.Toolkit; import java.awt.Toolkit;
import java.io.BufferedReader; import java.io.BufferedReader;
@@ -27,9 +28,10 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import javax.swing.text.StyleContext; import java.util.logging.Level;
import com.formdev.flatlaf.util.StringUtils; import com.formdev.flatlaf.util.StringUtils;
import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale;
/** /**
* @author Karl Tauber * @author Karl Tauber
@@ -87,9 +89,7 @@ class LinuxFontPolicy
} }
private static Font createFont( String family, int style, int size, double dsize ) { private static Font createFont( String family, int style, int size, double dsize ) {
// using StyleContext.getFont() here because it uses Font font = FlatLaf.createCompositeFont( family, style, size );
// sun.font.FontUtilities.getCompositeFontUIResource()
Font font = new StyleContext().getFont( family, style, size );
// set font size in floating points // set font size in floating points
font = font.deriveFont( style, (float) dsize ); font = font.deriveFont( style, (float) dsize );
@@ -98,6 +98,10 @@ class LinuxFontPolicy
} }
private static double getGnomeFontScale() { private static double getGnomeFontScale() {
// do not scale font here if JRE scales
if( isSystemScaling() )
return 96. / 72.;
// see class com.sun.java.swing.plaf.gtk.PangoFonts background information // see class com.sun.java.swing.plaf.gtk.PangoFonts background information
Object value = Toolkit.getDefaultToolkit().getDesktopProperty( "gnome.Xft/DPI" ); Object value = Toolkit.getDefaultToolkit().getDesktopProperty( "gnome.Xft/DPI" );
@@ -161,13 +165,13 @@ class LinuxFontPolicy
if( "1".equals( strs.get( 5 ) ) ) if( "1".equals( strs.get( 5 ) ) )
style |= Font.ITALIC; style |= Font.ITALIC;
} catch( RuntimeException ex ) { } catch( RuntimeException ex ) {
ex.printStackTrace(); FlatLaf.LOG.log( Level.CONFIG, "FlatLaf: Failed to parse 'font=" + generalFont + "'.", ex );
} }
} }
// font dpi // font dpi
int dpi = 96; int dpi = 96;
if( forceFontDPI != null ) { if( forceFontDPI != null && !isSystemScaling() ) {
try { try {
dpi = Integer.parseInt( forceFontDPI ); dpi = Integer.parseInt( forceFontDPI );
if( dpi <= 0 ) if( dpi <= 0 )
@@ -175,7 +179,7 @@ class LinuxFontPolicy
if( dpi < 50 ) if( dpi < 50 )
dpi = 50; dpi = 50;
} catch( NumberFormatException ex ) { } catch( NumberFormatException ex ) {
ex.printStackTrace(); FlatLaf.LOG.log( Level.CONFIG, "FlatLaf: Failed to parse 'forceFontDPI=" + forceFontDPI + "'.", ex );
} }
} }
@@ -214,7 +218,7 @@ class LinuxFontPolicy
while( (line = reader.readLine()) != null ) while( (line = reader.readLine()) != null )
lines.add( line ); lines.add( line );
} catch( IOException ex ) { } catch( IOException ex ) {
ex.printStackTrace(); FlatLaf.LOG.log( Level.CONFIG, "FlatLaf: Failed to read '" + filename + "'.", ex );
} }
return lines; return lines;
} }
@@ -246,4 +250,15 @@ class LinuxFontPolicy
} }
return null; return null;
} }
/**
* Returns true if the JRE scales, which is the case if:
* - environment variable GDK_SCALE is set and running on Java 9 or later
* - running on JetBrains Runtime 11 or later and scaling is enabled in system Settings
*/
private static boolean isSystemScaling() {
GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment()
.getDefaultScreenDevice().getDefaultConfiguration();
return UIScale.getSystemScaleFactor( gc ) > 1;
}
} }

View File

@@ -0,0 +1,259 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf;
import java.awt.Component;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.KeyEventPostProcessor;
import java.awt.KeyboardFocusManager;
import java.awt.Window;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.lang.ref.WeakReference;
import javax.swing.AbstractButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JRootPane;
import javax.swing.JTabbedPane;
import javax.swing.MenuElement;
import javax.swing.MenuSelectionManager;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import com.formdev.flatlaf.util.SystemInfo;
/**
* Show/hide mnemonics.
*
* @author Karl Tauber
*/
class MnemonicHandler
implements KeyEventPostProcessor, ChangeListener
{
private static boolean showMnemonics;
private static WeakReference<Window> lastShowMnemonicWindow;
private static WindowListener windowListener;
static boolean isShowMnemonics() {
return showMnemonics || !UIManager.getBoolean( "Component.hideMnemonics" );
}
void install() {
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventPostProcessor( this );
MenuSelectionManager.defaultManager().addChangeListener( this );
}
void uninstall() {
KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventPostProcessor( this );
MenuSelectionManager.defaultManager().removeChangeListener( this );
}
@Override
public boolean postProcessKeyEvent( KeyEvent e ) {
int keyCode = e.getKeyCode();
if( SystemInfo.IS_MAC ) {
// Ctrl+Alt keys must be pressed on Mac
if( keyCode == KeyEvent.VK_CONTROL || keyCode == KeyEvent.VK_ALT )
showMnemonics( shouldShowMnemonics( e ) && e.isControlDown() && e.isAltDown(), e.getComponent() );
} else {
// Alt key must be pressed on Windows and Linux
if( SystemInfo.IS_WINDOWS )
return processKeyEventOnWindows( e );
if( keyCode == KeyEvent.VK_ALT )
showMnemonics( shouldShowMnemonics( e ), e.getComponent() );
}
return false;
}
private boolean shouldShowMnemonics( KeyEvent e ) {
return e.getID() == KeyEvent.KEY_PRESSED ||
MenuSelectionManager.defaultManager().getSelectedPath().length > 0;
}
private static int altPressedEventCount;
private static boolean selectMenuOnAltReleased;
/**
* Special Alt key behavior on Windows.
*
* Press-and-release Alt key selects first menu (if available) and moves focus
* temporary to menu bar. If menu bar has focus (some menu is selected),
* pressing Alt key unselects menu and moves focus back to permanent focus owner.
*/
private boolean processKeyEventOnWindows( KeyEvent e ) {
if( e.getKeyCode() != KeyEvent.VK_ALT ) {
selectMenuOnAltReleased = false;
return false;
}
if( e.getID() == KeyEvent.KEY_PRESSED ) {
altPressedEventCount++;
if( altPressedEventCount == 1 && !e.isConsumed() ) {
MenuSelectionManager menuSelectionManager = MenuSelectionManager.defaultManager();
selectMenuOnAltReleased = (menuSelectionManager.getSelectedPath().length == 0);
// if menu is selected when Alt key is pressed then clear menu selection
if( !selectMenuOnAltReleased )
menuSelectionManager.clearSelectedPath();
}
// show mnemonics
showMnemonics( shouldShowMnemonics( e ), e.getComponent() );
// avoid that the system menu of the window gets focus
e.consume();
return true;
} else if( e.getID() == KeyEvent.KEY_RELEASED ) {
altPressedEventCount = 0;
boolean mnemonicsShown = false;
if( selectMenuOnAltReleased && !e.isConsumed() ) {
MenuSelectionManager menuSelectionManager = MenuSelectionManager.defaultManager();
if( menuSelectionManager.getSelectedPath().length == 0 ) {
// get menu bar and first menu
Component c = e.getComponent();
JRootPane rootPane = SwingUtilities.getRootPane( c );
Window window = (rootPane != null) ? SwingUtilities.getWindowAncestor( rootPane ) : null;
JMenuBar menuBar = (rootPane != null) ? rootPane.getJMenuBar() : null;
if( menuBar == null && window instanceof JFrame )
menuBar = ((JFrame)window).getJMenuBar();
JMenu firstMenu = (menuBar != null) ? menuBar.getMenu( 0 ) : null;
// select first menu and show mnemonics
if( firstMenu != null ) {
menuSelectionManager.setSelectedPath( new MenuElement[] { menuBar, firstMenu } );
showMnemonics( true, c );
mnemonicsShown = true;
}
}
}
selectMenuOnAltReleased = false;
// hide mnemonics
if( !mnemonicsShown )
showMnemonics( shouldShowMnemonics( e ), e.getComponent() );
}
return false;
}
@Override
public void stateChanged( ChangeEvent e ) {
MenuElement[] selectedPath = MenuSelectionManager.defaultManager().getSelectedPath();
if( selectedPath.length == 0 && altPressedEventCount == 0 ) {
// hide mnemonics when menu selection was canceled
showMnemonics( false, null );
}
}
static void showMnemonics( boolean show, Component c ) {
if( show == showMnemonics )
return;
showMnemonics = show;
// check whether it is necessary to repaint
if( !UIManager.getBoolean( "Component.hideMnemonics" ) )
return;
if( show ) {
// get root pane
JRootPane rootPane = SwingUtilities.getRootPane( c );
if( rootPane == null )
return;
// get window
Window window = SwingUtilities.getWindowAncestor( rootPane );
if( window == null )
return;
// repaint components with mnemonics in focused window
repaintMnemonics( window );
// hide mnemonics if window is deactivated (e.g. Alt+Tab to another window)
windowListener = new WindowAdapter() {
@Override
public void windowDeactivated( WindowEvent e ) {
altPressedEventCount = 0;
selectMenuOnAltReleased = false;
// use invokeLater() to avoid that the listener is removed
// while the listener queue is iterated to fire this event
EventQueue.invokeLater( () -> {
showMnemonics( false, null );
} );
}
};
window.addWindowListener( windowListener );
lastShowMnemonicWindow = new WeakReference<>( window );
} else if( lastShowMnemonicWindow != null ) {
Window window = lastShowMnemonicWindow.get();
if( window != null ) {
repaintMnemonics( window );
if( windowListener != null ) {
window.removeWindowListener( windowListener );
windowListener = null;
}
}
lastShowMnemonicWindow = null;
}
}
private static void repaintMnemonics( Container container ) {
for( Component c : container.getComponents() ) {
if( !c.isVisible() )
continue;
if( hasMnemonic( c ) )
c.repaint();
if( c instanceof Container )
repaintMnemonics( (Container) c );
}
}
private static boolean hasMnemonic( Component c ) {
if( c instanceof JLabel && ((JLabel)c).getDisplayedMnemonicIndex() >= 0 )
return true;
if( c instanceof AbstractButton && ((AbstractButton)c).getDisplayedMnemonicIndex() >= 0 )
return true;
if( c instanceof JTabbedPane ) {
JTabbedPane tabPane = (JTabbedPane) c;
int tabCount = tabPane.getTabCount();
for( int i = 0; i < tabCount; i++ ) {
if( tabPane.getDisplayedMnemonicIndexAt( i ) >= 0 )
return true;
}
}
return false;
}
}

View File

@@ -22,14 +22,17 @@ import java.awt.Insets;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.ServiceLoader;
import java.util.function.Function; import java.util.function.Function;
import java.util.logging.Level;
import javax.swing.UIDefaults; import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.UIDefaults.ActiveValue;
import javax.swing.UIDefaults.LazyValue; import javax.swing.UIDefaults.LazyValue;
import javax.swing.plaf.ColorUIResource; import javax.swing.plaf.ColorUIResource;
import javax.swing.plaf.DimensionUIResource; import javax.swing.plaf.DimensionUIResource;
@@ -38,9 +41,11 @@ import com.formdev.flatlaf.ui.FlatEmptyBorder;
import com.formdev.flatlaf.ui.FlatLineBorder; import com.formdev.flatlaf.ui.FlatLineBorder;
import com.formdev.flatlaf.util.ColorFunctions; import com.formdev.flatlaf.util.ColorFunctions;
import com.formdev.flatlaf.util.DerivedColor; import com.formdev.flatlaf.util.DerivedColor;
import com.formdev.flatlaf.util.ScaledNumber; import com.formdev.flatlaf.util.GrayFilter;
import com.formdev.flatlaf.util.HSLColor;
import com.formdev.flatlaf.util.StringUtils; import com.formdev.flatlaf.util.StringUtils;
import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale;
/** /**
* Load UI defaults from properties files associated to Flat LaF classes and add to UI defaults. * Load UI defaults from properties files associated to Flat LaF classes and add to UI defaults.
@@ -58,11 +63,13 @@ class UIDefaultsLoader
private static final String TYPE_PREFIX = "{"; private static final String TYPE_PREFIX = "{";
private static final String TYPE_PREFIX_END = "}"; private static final String TYPE_PREFIX_END = "}";
private static final String VARIABLE_PREFIX = "@"; private static final String VARIABLE_PREFIX = "@";
private static final String REF_PREFIX = VARIABLE_PREFIX + "@"; private static final String PROPERTY_PREFIX = "$";
private static final String OPTIONAL_PREFIX = "?"; private static final String OPTIONAL_PREFIX = "?";
private static final String GLOBAL_PREFIX = "*."; private static final String GLOBAL_PREFIX = "*.";
static void loadDefaultsFromProperties( Class<?> lookAndFeelClass, UIDefaults defaults ) { static void loadDefaultsFromProperties( Class<?> lookAndFeelClass, List<FlatDefaultsAddon> addons,
Properties additionalDefaults, boolean dark, UIDefaults defaults )
{
// determine classes in class hierarchy in reverse order // determine classes in class hierarchy in reverse order
ArrayList<Class<?>> lafClasses = new ArrayList<>(); ArrayList<Class<?>> lafClasses = new ArrayList<>();
for( Class<?> lafClass = lookAndFeelClass; for( Class<?> lafClass = lookAndFeelClass;
@@ -72,24 +79,26 @@ class UIDefaultsLoader
lafClasses.add( 0, lafClass ); lafClasses.add( 0, lafClass );
} }
loadDefaultsFromProperties( lafClasses, defaults ); loadDefaultsFromProperties( lafClasses, addons, additionalDefaults, dark, defaults );
} }
static void loadDefaultsFromProperties( List<Class<?>> lafClasses, UIDefaults defaults ) { static void loadDefaultsFromProperties( List<Class<?>> lafClasses, List<FlatDefaultsAddon> addons,
Properties additionalDefaults, boolean dark, UIDefaults defaults )
{
try { try {
// load properties files // load core properties files
Properties properties = new Properties(); Properties properties = new Properties();
ServiceLoader<FlatDefaultsAddon> addonLoader = ServiceLoader.load( FlatDefaultsAddon.class );
for( Class<?> lafClass : lafClasses ) { for( Class<?> lafClass : lafClasses ) {
// load core properties String propertiesName = '/' + lafClass.getName().replace( '.', '/' ) + ".properties";
String propertiesName = "/" + lafClass.getName().replace( '.', '/' ) + ".properties";
try( InputStream in = lafClass.getResourceAsStream( propertiesName ) ) { try( InputStream in = lafClass.getResourceAsStream( propertiesName ) ) {
if( in != null ) if( in != null )
properties.load( in ); properties.load( in );
} }
}
// load properties from addons // load properties from addons
for( FlatDefaultsAddon addon : addonLoader ) { for( FlatDefaultsAddon addon : addons ) {
for( Class<?> lafClass : lafClasses ) {
try( InputStream in = addon.getDefaults( lafClass ) ) { try( InputStream in = addon.getDefaults( lafClass ) ) {
if( in != null ) if( in != null )
properties.load( in ); properties.load( in );
@@ -97,16 +106,42 @@ class UIDefaultsLoader
} }
} }
// collect addon class loaders
List<ClassLoader> addonClassLoaders = new ArrayList<>();
for( FlatDefaultsAddon addon : addons ) {
ClassLoader addonClassLoader = addon.getClass().getClassLoader();
if( !addonClassLoaders.contains( addonClassLoader ) )
addonClassLoaders.add( addonClassLoader );
}
// add additional defaults
if( additionalDefaults != null )
properties.putAll( additionalDefaults );
// collect all platform specific keys (but do not modify properties) // collect all platform specific keys (but do not modify properties)
ArrayList<String> platformSpecificKeys = new ArrayList<>(); ArrayList<String> platformSpecificKeys = new ArrayList<>();
for( Object key : properties.keySet() ) { for( Object okey : properties.keySet() ) {
if( ((String)key).startsWith( "[" ) ) String key = (String) okey;
platformSpecificKeys.add( (String) key ); if( key.startsWith( "[" ) &&
(key.startsWith( "[win]" ) ||
key.startsWith( "[mac]" ) ||
key.startsWith( "[linux]" ) ||
key.startsWith( "[light]" ) ||
key.startsWith( "[dark]" )) )
platformSpecificKeys.add( key );
} }
// remove platform specific properties and re-add only properties // remove platform specific properties and re-add only properties
// for current platform, but with platform prefix removed // for current platform, but with platform prefix removed
if( !platformSpecificKeys.isEmpty() ) { if( !platformSpecificKeys.isEmpty() ) {
// handle light/dark specific properties
String lightOrDarkPrefix = dark ? "[dark]" : "[light]";
for( String key : platformSpecificKeys ) {
if( key.startsWith( lightOrDarkPrefix ) )
properties.put( key.substring( lightOrDarkPrefix.length() ), properties.remove( key ) );
}
// handle platform specific properties
String platformPrefix = String platformPrefix =
SystemInfo.IS_WINDOWS ? "[win]" : SystemInfo.IS_WINDOWS ? "[win]" :
SystemInfo.IS_MAC ? "[mac]" : SystemInfo.IS_MAC ? "[mac]" :
@@ -131,9 +166,9 @@ class UIDefaultsLoader
String value = resolveValue( properties, (String) e.getValue() ); String value = resolveValue( properties, (String) e.getValue() );
try { try {
globals.put( key.substring( GLOBAL_PREFIX.length() ), parseValue( key, value, resolver ) ); globals.put( key.substring( GLOBAL_PREFIX.length() ), parseValue( key, value, resolver, addonClassLoaders ) );
} catch( RuntimeException ex ) { } catch( RuntimeException ex ) {
logParseError( key, value, ex ); logParseError( Level.SEVERE, key, value, ex );
} }
} }
@@ -156,28 +191,26 @@ class UIDefaultsLoader
String value = resolveValue( properties, (String) e.getValue() ); String value = resolveValue( properties, (String) e.getValue() );
try { try {
defaults.put( key, parseValue( key, value, resolver ) ); defaults.put( key, parseValue( key, value, resolver, addonClassLoaders ) );
} catch( RuntimeException ex ) { } catch( RuntimeException ex ) {
logParseError( key, value, ex ); logParseError( Level.SEVERE, key, value, ex );
} }
} }
} catch( IOException ex ) { } catch( IOException ex ) {
ex.printStackTrace(); FlatLaf.LOG.log( Level.SEVERE, "FlatLaf: Failed to load properties files.", ex );
} }
} }
static void logParseError( String key, String value, RuntimeException ex ) { static void logParseError( Level level, String key, String value, RuntimeException ex ) {
System.err.println( "Failed to parse: '" + key + '=' + value + '\'' ); FlatLaf.LOG.log( level, "FlatLaf: Failed to parse: '" + key + '=' + value + '\'', ex );
System.err.println( " " + ex.getMessage() );
} }
private static String resolveValue( Properties properties, String value ) { private static String resolveValue( Properties properties, String value ) {
if( !value.startsWith( VARIABLE_PREFIX ) ) if( value.startsWith( PROPERTY_PREFIX ) )
value = value.substring( PROPERTY_PREFIX.length() );
else if( !value.startsWith( VARIABLE_PREFIX ) )
return value; return value;
if( value.startsWith( REF_PREFIX ) )
value = value.substring( REF_PREFIX.length() );
boolean optional = false; boolean optional = false;
if( value.startsWith( OPTIONAL_PREFIX ) ) { if( value.startsWith( OPTIONAL_PREFIX ) ) {
value = value.substring( OPTIONAL_PREFIX.length() ); value = value.substring( OPTIONAL_PREFIX.length() );
@@ -189,19 +222,20 @@ class UIDefaultsLoader
if( optional ) if( optional )
return "null"; return "null";
throw new IllegalArgumentException( "variable or reference '" + value + "' not found" ); throw new IllegalArgumentException( "variable or property '" + value + "' not found" );
} }
return resolveValue( properties, newValue ); return resolveValue( properties, newValue );
} }
private enum ValueType { UNKNOWN, STRING, INTEGER, BORDER, ICON, INSETS, SIZE, COLOR, SCALEDNUMBER } private enum ValueType { UNKNOWN, STRING, CHARACTER, INTEGER, FLOAT, BORDER, ICON, INSETS, DIMENSION, COLOR,
SCALEDINTEGER, SCALEDFLOAT, SCALEDINSETS, SCALEDDIMENSION, INSTANCE, CLASS, GRAYFILTER }
static Object parseValue( String key, String value ) { static Object parseValue( String key, String value ) {
return parseValue( key, value, v -> v ); return parseValue( key, value, v -> v, Collections.emptyList() );
} }
private static Object parseValue( String key, String value, Function<String, String> resolver ) { private static Object parseValue( String key, String value, Function<String, String> resolver, List<ClassLoader> addonClassLoaders ) {
value = value.trim(); value = value.trim();
// null, false, true // null, false, true
@@ -211,12 +245,24 @@ class UIDefaultsLoader
case "true": return true; case "true": return true;
} }
// check for function "lazy"
// Syntax: lazy(uiKey)
if( value.startsWith( "lazy(" ) && value.endsWith( ")" ) ) {
String uiKey = value.substring( 5, value.length() - 1 ).trim();
return (LazyValue) t -> {
return lazyUIManagerGet( uiKey );
};
}
ValueType valueType = ValueType.UNKNOWN; ValueType valueType = ValueType.UNKNOWN;
// check whether value type is specified in the value // check whether value type is specified in the value
if( value.startsWith( "#" ) ) if( value.startsWith( "#" ) )
valueType = ValueType.COLOR; valueType = ValueType.COLOR;
else if( value.startsWith( TYPE_PREFIX ) ) { 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 ); int end = value.indexOf( TYPE_PREFIX_END );
if( end != -1 ) { if( end != -1 ) {
try { try {
@@ -243,27 +289,39 @@ class UIDefaultsLoader
key.endsWith( "Margins" ) || key.endsWith( "Insets" ) ) key.endsWith( "Margins" ) || key.endsWith( "Insets" ) )
valueType = ValueType.INSETS; valueType = ValueType.INSETS;
else if( key.endsWith( "Size" ) ) else if( key.endsWith( "Size" ) )
valueType = ValueType.SIZE; valueType = ValueType.DIMENSION;
else if( key.endsWith( "Width" ) || key.endsWith( "Height" ) ) else if( key.endsWith( "Width" ) || key.endsWith( "Height" ) )
valueType = ValueType.INTEGER; valueType = ValueType.INTEGER;
else if( key.endsWith( "Char" ) )
valueType = ValueType.CHARACTER;
else if( key.endsWith( "UI" ) ) else if( key.endsWith( "UI" ) )
valueType = ValueType.STRING; valueType = ValueType.STRING;
else if( key.endsWith( "grayFilter" ) )
valueType = ValueType.GRAYFILTER;
} }
// parse value // parse value
switch( valueType ) { switch( valueType ) {
case STRING: return value; case STRING: return value;
case CHARACTER: return parseCharacter( value );
case INTEGER: return parseInteger( value, true ); case INTEGER: return parseInteger( value, true );
case BORDER: return parseBorder( value, resolver ); case FLOAT: return parseFloat( value, true );
case ICON: return parseInstance( value ); case BORDER: return parseBorder( value, resolver, addonClassLoaders );
case ICON: return parseInstance( value, addonClassLoaders );
case INSETS: return parseInsets( value ); case INSETS: return parseInsets( value );
case SIZE: return parseSize( value ); case DIMENSION: return parseDimension( value );
case COLOR: return parseColorOrFunction( value, true ); case COLOR: return parseColorOrFunction( value, resolver, true );
case SCALEDNUMBER: return parseScaledNumber( value ); case SCALEDINTEGER: return parseScaledInteger( value );
case SCALEDFLOAT: return parseScaledFloat( value );
case SCALEDINSETS: return parseScaledInsets( value );
case SCALEDDIMENSION:return parseScaledDimension( value );
case INSTANCE: return parseInstance( value, addonClassLoaders );
case CLASS: return parseClass( value, addonClassLoaders );
case GRAYFILTER: return parseGrayFilter( value );
case UNKNOWN: case UNKNOWN:
default: default:
// colors // colors
ColorUIResource color = parseColorOrFunction( value, false ); Object color = parseColorOrFunction( value, resolver, false );
if( color != null ) if( color != null )
return color; return color;
@@ -272,42 +330,77 @@ class UIDefaultsLoader
if( integer != null ) if( integer != null )
return integer; return integer;
// float
Float f = parseFloat( value, false );
if( f != null )
return f;
// string // string
return value; return value;
} }
} }
private static Object parseBorder( String value, Function<String, String> resolver ) { private static Object parseBorder( String value, Function<String, String> resolver, List<ClassLoader> addonClassLoaders ) {
if( value.indexOf( ',' ) >= 0 ) { if( value.indexOf( ',' ) >= 0 ) {
// top,left,bottom,right[,lineColor] // top,left,bottom,right[,lineColor[,lineThickness]]
List<String> parts = StringUtils.split( value, ',' ); List<String> parts = split( value, ',' );
Insets insets = parseInsets( value ); Insets insets = parseInsets( value );
ColorUIResource lineColor = (parts.size() == 5) ColorUIResource lineColor = (parts.size() >= 5)
? parseColorOrFunction( resolver.apply( parts.get( 4 ) ), true ) ? (ColorUIResource) parseColorOrFunction( resolver.apply( parts.get( 4 ) ), resolver, true )
: null; : null;
float lineThickness = (parts.size() >= 6) ? parseFloat( parts.get( 5 ), true ) : 1f;
return (LazyValue) t -> { return (LazyValue) t -> {
return (lineColor != null) return (lineColor != null)
? new FlatLineBorder( insets, lineColor ) ? new FlatLineBorder( insets, lineColor, lineThickness )
: new FlatEmptyBorder( insets ); : new FlatEmptyBorder( insets );
}; };
} else } else
return parseInstance( value ); return parseInstance( value, addonClassLoaders );
} }
private static Object parseInstance( String value ) { private static Object parseInstance( String value, List<ClassLoader> addonClassLoaders ) {
return (LazyValue) t -> { return (LazyValue) t -> {
try { try {
return Class.forName( value ).newInstance(); return findClass( value, addonClassLoaders ).newInstance();
} catch( InstantiationException | IllegalAccessException | ClassNotFoundException ex ) { } catch( InstantiationException | IllegalAccessException | ClassNotFoundException ex ) {
ex.printStackTrace(); FlatLaf.LOG.log( Level.SEVERE, "FlatLaf: Failed to instantiate '" + value + "'.", ex );
return null; return null;
} }
}; };
} }
private static Object parseClass( String value, List<ClassLoader> addonClassLoaders ) {
return (LazyValue) t -> {
try {
return findClass( value, addonClassLoaders );
} catch( ClassNotFoundException ex ) {
FlatLaf.LOG.log( Level.SEVERE, "FlatLaf: Failed to find class '" + value + "'.", ex );
return null;
}
};
}
private static Class<?> findClass( String className, List<ClassLoader> addonClassLoaders )
throws ClassNotFoundException
{
try {
return Class.forName( className );
} catch( ClassNotFoundException ex ) {
// search in addons class loaders
for( ClassLoader addonClassLoader : addonClassLoaders ) {
try {
return addonClassLoader.loadClass( className );
} catch( ClassNotFoundException ex2 ) {
// ignore
}
}
throw ex;
}
}
private static Insets parseInsets( String value ) { private static Insets parseInsets( String value ) {
List<String> numbers = StringUtils.split( value, ',' ); List<String> numbers = split( value, ',' );
try { try {
return new InsetsUIResource( return new InsetsUIResource(
Integer.parseInt( numbers.get( 0 ) ), Integer.parseInt( numbers.get( 0 ) ),
@@ -319,8 +412,8 @@ class UIDefaultsLoader
} }
} }
private static Dimension parseSize( String value ) { private static Dimension parseDimension( String value ) {
List<String> numbers = StringUtils.split( value, ',' ); List<String> numbers = split( value, ',' );
try { try {
return new DimensionUIResource( return new DimensionUIResource(
Integer.parseInt( numbers.get( 0 ) ), Integer.parseInt( numbers.get( 0 ) ),
@@ -330,9 +423,9 @@ class UIDefaultsLoader
} }
} }
private static ColorUIResource parseColorOrFunction( String value, boolean reportError ) { private static Object parseColorOrFunction( String value, Function<String, String> resolver, boolean reportError ) {
if( value.endsWith( ")" ) ) if( value.endsWith( ")" ) )
return parseColorFunctions( value, reportError ); return parseColorFunctions( value, resolver, reportError );
return parseColor( value, reportError ); return parseColor( value, reportError );
} }
@@ -400,7 +493,7 @@ class UIDefaultsLoader
: (((n >> 8) & 0xffffff) | ((n & 0xff) << 24)); // move alpha from lowest to highest byte : (((n >> 8) & 0xffffff) | ((n & 0xff) << 24)); // move alpha from lowest to highest byte
} }
private static ColorUIResource parseColorFunctions( String value, boolean reportError ) { private static Object parseColorFunctions( String value, Function<String, String> resolver, boolean reportError ) {
int paramsStart = value.indexOf( '(' ); int paramsStart = value.indexOf( '(' );
if( paramsStart < 0 ) { if( paramsStart < 0 ) {
if( reportError ) if( reportError )
@@ -409,37 +502,112 @@ class UIDefaultsLoader
} }
String function = value.substring( 0, paramsStart ).trim(); String function = value.substring( 0, paramsStart ).trim();
List<String> params = StringUtils.split( value.substring( paramsStart + 1, value.length() - 1 ), ',' ); List<String> params = splitFunctionParams( value.substring( paramsStart + 1, value.length() - 1 ), ',' );
if( params.isEmpty() ) if( params.isEmpty() )
throw new IllegalArgumentException( "missing parameters in function '" + value + "'" ); throw new IllegalArgumentException( "missing parameters in function '" + value + "'" );
switch( function ) { switch( function ) {
case "lighten": return parseColorLightenOrDarken( true, params, reportError ); case "rgb": return parseColorRgbOrRgba( false, params, resolver, reportError );
case "darken": return parseColorLightenOrDarken( false, params, reportError ); case "rgba": return parseColorRgbOrRgba( true, params, resolver, reportError );
case "hsl": return parseColorHslOrHsla( false, params );
case "hsla": return parseColorHslOrHsla( true, params );
case "lighten": return parseColorLightenOrDarken( true, params, resolver, reportError );
case "darken": return parseColorLightenOrDarken( false, params, resolver, reportError );
} }
throw new IllegalArgumentException( "unknown color function '" + value + "'" ); throw new IllegalArgumentException( "unknown color function '" + value + "'" );
} }
/** /**
* Syntax: lighten(amount[,options]) or darken(amount[,options]) * Syntax: rgb(red,green,blue) or rgba(red,green,blue,alpha) or rgba(color,alpha)
* - amount: percentage 0-100% * - red: an integer 0-255 or a percentage 0-100%
* - options: [relative] [autoInverse] * - green: an integer 0-255 or a percentage 0-100%
* - blue: an integer 0-255 or a percentage 0-100%
* - alpha: an integer 0-255 or a percentage 0-100%
*/ */
private static ColorUIResource parseColorLightenOrDarken( boolean lighten, List<String> params, boolean reportError ) { private static ColorUIResource parseColorRgbOrRgba( boolean hasAlpha, List<String> params,
int amount = parsePercentage( params.get( 0 ) ); Function<String, String> resolver, boolean reportError )
boolean relative = false; {
boolean autoInverse = false; if( hasAlpha && params.size() == 2 ) {
// syntax rgba(color,alpha), which allows adding alpha to any color
String colorStr = params.get( 0 );
int alpha = parseInteger( params.get( 1 ), 0, 255, true );
if( params.size() >= 2 ) { ColorUIResource color = (ColorUIResource) parseColorOrFunction( resolver.apply( colorStr ), resolver, reportError );
String options = params.get( 1 ); return new ColorUIResource( new Color( ((alpha & 0xff) << 24) | (color.getRGB() & 0xffffff), true ) );
relative = options.contains( "relative" );
autoInverse = options.contains( "autoInverse" );
} }
return new DerivedColor( lighten int red = parseInteger( params.get( 0 ), 0, 255, true );
int green = parseInteger( params.get( 1 ), 0, 255, true );
int blue = parseInteger( params.get( 2 ), 0, 255, true );
int alpha = hasAlpha ? parseInteger( params.get( 3 ), 0, 255, true ) : 255;
return hasAlpha
? new ColorUIResource( new Color( red, green, blue, alpha ) )
: new ColorUIResource( red, green, blue );
}
/**
* Syntax: hsl(hue,saturation,lightness) or hsla(hue,saturation,lightness,alpha)
* - hue: an integer 0-360 representing degrees
* - saturation: a percentage 0-100%
* - lightness: a percentage 0-100%
* - alpha: a percentage 0-100%
*/
private static ColorUIResource parseColorHslOrHsla( boolean hasAlpha, List<String> params ) {
int hue = parseInteger( params.get( 0 ), 0, 360, false );
int saturation = parsePercentage( params.get( 1 ) );
int lightness = parsePercentage( params.get( 2 ) );
int alpha = hasAlpha ? parsePercentage( params.get( 3 ) ) : 100;
float[] hsl = new float[] { hue, saturation, lightness };
return new ColorUIResource( HSLColor.toRGB( hsl, alpha / 100f ) );
}
/**
* Syntax: lighten(color,amount[,options]) or darken(color,amount[,options])
* - color: a color (e.g. #f00) or a color function
* - amount: percentage 0-100%
* - options: [relative] [autoInverse] [lazy] [derived]
*/
private static Object parseColorLightenOrDarken( boolean lighten, List<String> params,
Function<String, String> resolver, boolean reportError )
{
String colorStr = params.get( 0 );
int amount = parsePercentage( params.get( 1 ) );
boolean relative = false;
boolean autoInverse = false;
boolean lazy = false;
boolean derived = false;
if( params.size() > 2 ) {
String options = params.get( 2 );
relative = options.contains( "relative" );
autoInverse = options.contains( "autoInverse" );
lazy = options.contains( "lazy" );
derived = options.contains( "derived" );
}
ColorFunctions.ColorFunction function = lighten
? new ColorFunctions.Lighten( amount, relative, autoInverse ) ? new ColorFunctions.Lighten( amount, relative, autoInverse )
: new ColorFunctions.Darken( amount, relative, autoInverse ) ); : new ColorFunctions.Darken( amount, relative, autoInverse );
if( derived ) {
ColorUIResource color = (ColorUIResource) parseColorOrFunction( resolver.apply( colorStr ), resolver, reportError );
return new DerivedColor( ColorFunctions.applyFunctions( color, function ), function );
}
if( lazy ) {
return (LazyValue) t -> {
Object color = lazyUIManagerGet( colorStr );
return (color instanceof Color)
? new ColorUIResource( ColorFunctions.applyFunctions( (Color) color, function ) )
: null;
};
}
ColorUIResource color = (ColorUIResource) parseColorOrFunction( resolver.apply( colorStr ), resolver, reportError );
return new ColorUIResource( ColorFunctions.applyFunctions( color, function ) );
} }
private static int parsePercentage( String value ) { private static int parsePercentage( String value ) {
@@ -458,6 +626,24 @@ class UIDefaultsLoader
return val; return val;
} }
private static Character parseCharacter( String value ) {
if( value.length() != 1 )
throw new IllegalArgumentException( "invalid character '" + value + "'" );
return value.charAt( 0 );
}
private static Integer parseInteger( String value, int min, int max, boolean allowPercentage ) {
if( allowPercentage && value.endsWith( "%" ) ) {
int percent = parsePercentage( value );
return (max * percent) / 100;
}
Integer integer = parseInteger( value, true );
if( integer.intValue() < min || integer.intValue() > max )
throw new NumberFormatException( "integer '" + value + "' out of range (" + min + '-' + max + ')' );
return integer;
}
private static Integer parseInteger( String value, boolean reportError ) { private static Integer parseInteger( String value, boolean reportError ) {
try { try {
return Integer.parseInt( value ); return Integer.parseInt( value );
@@ -468,11 +654,112 @@ class UIDefaultsLoader
return null; return null;
} }
private static ScaledNumber parseScaledNumber( String value ) { private static Float parseFloat( String value, boolean reportError ) {
try { try {
return new ScaledNumber( Integer.parseInt( value ) ); return Float.parseFloat( value );
} catch( NumberFormatException ex ) { } catch( NumberFormatException ex ) {
throw new NumberFormatException( "invalid integer '" + value + "'" ); if( reportError )
throw new NumberFormatException( "invalid float '" + value + "'" );
}
return null;
}
private static ActiveValue parseScaledInteger( String value ) {
int val = parseInteger( value, true );
return (ActiveValue) t -> {
return UIScale.scale( val );
};
}
private static ActiveValue parseScaledFloat( String value ) {
float val = parseFloat( value, true );
return (ActiveValue) t -> {
return UIScale.scale( val );
};
}
private static ActiveValue parseScaledInsets( String value ) {
Insets insets = parseInsets( value );
return (ActiveValue) t -> {
return UIScale.scale( insets );
};
}
private static ActiveValue parseScaledDimension( String value ) {
Dimension dimension = parseDimension( value );
return (ActiveValue) t -> {
return UIScale.scale( dimension );
};
}
private static Object parseGrayFilter( String value ) {
List<String> numbers = split( value, ',' );
try {
int brightness = Integer.parseInt( numbers.get( 0 ) );
int contrast = Integer.parseInt( numbers.get( 1 ) );
int alpha = Integer.parseInt( numbers.get( 2 ) );
return (LazyValue) t -> {
return new GrayFilter( brightness, contrast, alpha );
};
} catch( NumberFormatException ex ) {
throw new IllegalArgumentException( "invalid gray filter '" + value + "'" );
} }
} }
/**
* 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.
*/
private static List<String> splitFunctionParams( String str, char delim ) {
ArrayList<String> strs = new ArrayList<>();
int nestLevel = 0;
int start = 0;
int strlen = str.length();
for( int i = 0; i < strlen; i++ ) {
char ch = str.charAt( i );
if( ch == '(' )
nestLevel++;
else if( ch == ')' )
nestLevel--;
else if( nestLevel == 0 && ch == delim ) {
strs.add( str.substring( start, i ).trim() );
start = i + 1;
}
}
strs.add( str.substring( start ).trim() );
return strs;
}
/**
* 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 ) {
boolean optional = false;
if( uiKey.startsWith( OPTIONAL_PREFIX ) ) {
uiKey = uiKey.substring( OPTIONAL_PREFIX.length() );
optional = true;
}
Object value = UIManager.get( uiKey );
if( value == null && !optional )
FlatLaf.LOG.log( Level.SEVERE, "FlatLaf: '" + uiKey + "' not found in UI defaults." );
return value;
}
} }

View File

@@ -47,7 +47,7 @@ public class FlatAscendingSortIcon
g.setColor( sortIconColor ); g.setColor( sortIconColor );
if( chevron ) { if( chevron ) {
// chevron arrow // chevron arrow
Path2D path = FlatUIUtils.createPath( false, 1,5, 5,1, 9,5 ); Path2D path = FlatUIUtils.createPath( false, 1,4, 5,0, 9,4 );
g.setStroke( new BasicStroke( 1f ) ); g.setStroke( new BasicStroke( 1f ) );
g.draw( path ); g.draw( path );
} else { } else {

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.icons;
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.FlatUIUtils;
/**
* "caps lock" icon for {@link javax.swing.JPasswordField}.
*
* @uiDefault PasswordField.capsLockIconColor Color
*
* @author Karl Tauber
*/
public class FlatCapsLockIcon
extends FlatAbstractIcon
{
public FlatCapsLockIcon() {
super( 16, 16, UIManager.getColor( "PasswordField.capsLockIconColor" ) );
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
/*
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<g fill="none" fill-rule="evenodd">
<rect width="16" height="16" fill="#6E6E6E" rx="3"/>
<rect width="6" height="2" x="5" y="12" fill="#FFF"/>
<path fill="#FFF" d="M2,8 L8,2 L14,8 L11,8 L11,10 L5,10 L5,8 L2,8 Z"/>
</g>
</svg>
*/
Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD );
path.append( new RoundRectangle2D.Float( 0, 0, 16, 16, 6, 6 ), false );
path.append( new Rectangle2D.Float( 5, 12, 6, 2 ), false );
path.append( FlatUIUtils.createPath( 2,8, 8,2, 14,8, 11,8, 11,10, 5,10, 5,8 ), false );
g.fill( path );
}
}

View File

@@ -62,7 +62,7 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
public class FlatCheckBoxIcon public class FlatCheckBoxIcon
extends FlatAbstractIcon extends FlatAbstractIcon
{ {
protected final int focusWidth = UIManager.getInt( "Component.focusWidth" ); public final int focusWidth = UIManager.getInt( "Component.focusWidth" );
protected final Color focusColor = FlatUIUtils.getUIColor( "CheckBox.icon.focusedColor", protected final Color focusColor = FlatUIUtils.getUIColor( "CheckBox.icon.focusedColor",
UIManager.getColor( "Component.focusColor" ) ); UIManager.getColor( "Component.focusColor" ) );
protected final int arc = FlatUIUtils.getUIInt( "CheckBox.arc", 2 ); protected final int arc = FlatUIUtils.getUIInt( "CheckBox.arc", 2 );
@@ -96,7 +96,7 @@ public class FlatCheckBoxIcon
boolean selected = indeterminate || (c instanceof AbstractButton && ((AbstractButton)c).isSelected()); boolean selected = indeterminate || (c instanceof AbstractButton && ((AbstractButton)c).isSelected());
// paint focused border // paint focused border
if( c.hasFocus() && focusWidth > 0 ) { if( FlatUIUtils.isPermanentFocusOwner( c ) && focusWidth > 0 ) {
g2.setColor( focusColor ); g2.setColor( focusColor );
paintFocusBorder( g2 ); paintFocusBorder( g2 );
} }
@@ -133,17 +133,17 @@ public class FlatCheckBoxIcon
protected void paintFocusBorder( Graphics2D g2 ) { protected void paintFocusBorder( Graphics2D g2 ) {
// the outline focus border is painted outside of the icon // the outline focus border is painted outside of the icon
int wh = ICON_SIZE - 1 + (focusWidth * 2); int wh = ICON_SIZE - 1 + (focusWidth * 2);
int arcwh = (arc + focusWidth) * 2; int arcwh = arc + (focusWidth * 2);
g2.fillRoundRect( -focusWidth + 1, -focusWidth, wh, wh, arcwh, arcwh ); g2.fillRoundRect( -focusWidth + 1, -focusWidth, wh, wh, arcwh, arcwh );
} }
protected void paintBorder( Graphics2D g2 ) { protected void paintBorder( Graphics2D g2 ) {
int arcwh = arc * 2; int arcwh = arc;
g2.fillRoundRect( 1, 0, 14, 14, arcwh, arcwh ); g2.fillRoundRect( 1, 0, 14, 14, arcwh, arcwh );
} }
protected void paintBackground( Graphics2D g2 ) { protected void paintBackground( Graphics2D g2 ) {
int arcwh = (arc * 2) - 1; int arcwh = arc - 1;
g2.fillRoundRect( 2, 1, 12, 12, arcwh, arcwh ); g2.fillRoundRect( 2, 1, 12, 12, arcwh, arcwh );
} }

View File

@@ -30,7 +30,8 @@ import javax.swing.UIManager;
* *
* @uiDefault MenuItemCheckBox.icon.checkmarkColor Color * @uiDefault MenuItemCheckBox.icon.checkmarkColor Color
* @uiDefault MenuItemCheckBox.icon.disabledCheckmarkColor Color * @uiDefault MenuItemCheckBox.icon.disabledCheckmarkColor Color
* @uiDefault Menu.selectionForeground Color * @uiDefault MenuItem.selectionForeground Color
* @uiDefault MenuItem.selectionType String
* *
* @author Karl Tauber * @author Karl Tauber
*/ */
@@ -39,7 +40,7 @@ public class FlatCheckBoxMenuItemIcon
{ {
protected final Color checkmarkColor = UIManager.getColor( "MenuItemCheckBox.icon.checkmarkColor" ); protected final Color checkmarkColor = UIManager.getColor( "MenuItemCheckBox.icon.checkmarkColor" );
protected final Color disabledCheckmarkColor = UIManager.getColor( "MenuItemCheckBox.icon.disabledCheckmarkColor" ); protected final Color disabledCheckmarkColor = UIManager.getColor( "MenuItemCheckBox.icon.disabledCheckmarkColor" );
protected final Color selectionForeground = UIManager.getColor( "Menu.selectionForeground" ); protected final Color selectionForeground = UIManager.getColor( "MenuItem.selectionForeground" );
public FlatCheckBoxMenuItemIcon() { public FlatCheckBoxMenuItemIcon() {
super( 15, 15, null ); super( 15, 15, null );
@@ -67,9 +68,14 @@ public class FlatCheckBoxMenuItemIcon
} }
private Color getCheckmarkColor( Component c ) { private Color getCheckmarkColor( Component c ) {
if( c instanceof JMenuItem && ((JMenuItem)c).isArmed() ) if( c instanceof JMenuItem && ((JMenuItem)c).isArmed() && !isUnderlineSelection() )
return selectionForeground; return selectionForeground;
return c.isEnabled() ? checkmarkColor : disabledCheckmarkColor; return c.isEnabled() ? checkmarkColor : disabledCheckmarkColor;
} }
private boolean isUnderlineSelection() {
// not storing value of "MenuItem.selectionType" in class to allow changing at runtime
return "underline".equals( UIManager.getString( "MenuItem.selectionType" ) );
}
} }

View File

@@ -47,7 +47,7 @@ public class FlatDescendingSortIcon
g.setColor( sortIconColor ); g.setColor( sortIconColor );
if( chevron ) { if( chevron ) {
// chevron arrow // chevron arrow
Path2D path = FlatUIUtils.createPath( false, 1,1, 5,5, 9,1 ); Path2D path = FlatUIUtils.createPath( false, 1,0, 5,4, 9,0 );
g.setStroke( new BasicStroke( 1f ) ); g.setStroke( new BasicStroke( 1f ) );
g.draw( path ); g.draw( path );
} else { } else {

View File

@@ -82,7 +82,7 @@ public class FlatHelpButtonIcon
*/ */
boolean enabled = c.isEnabled(); boolean enabled = c.isEnabled();
boolean focused = c.hasFocus(); boolean focused = FlatUIUtils.isPermanentFocusOwner( c );
// paint focused border // paint focused border
if( focused ) { if( focused ) {

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2019 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.icons;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics2D;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatButtonUI;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
* Base class for internal frame icons.
*
* @uiDefault InternalFrame.buttonHoverBackground Color
* @uiDefault InternalFrame.buttonPressedBackground Color
*
* @author Karl Tauber
*/
public abstract class FlatInternalFrameAbstractIcon
extends FlatAbstractIcon
{
private final Color hoverBackground;
private final Color pressedBackground;
public FlatInternalFrameAbstractIcon() {
this( UIManager.getDimension( "InternalFrame.buttonSize" ),
UIManager.getColor( "InternalFrame.buttonHoverBackground" ),
UIManager.getColor( "InternalFrame.buttonPressedBackground" ) );
}
public FlatInternalFrameAbstractIcon( Dimension size, Color hoverBackground, Color pressedBackground ) {
super( size.width, size.height, null );
this.hoverBackground = hoverBackground;
this.pressedBackground = pressedBackground;
}
protected void paintBackground( Component c, Graphics2D g ) {
Color background = FlatButtonUI.buttonStateColor( c, null, null, null, hoverBackground, pressedBackground );
if( background != null ) {
FlatUIUtils.setColor( g, background, c.getBackground() );
g.fillRect( 0, 0, width, height );
}
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2019 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.icons;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatButtonUI;
/**
* "close" icon for {@link javax.swing.JInternalFrame}.
*
* @uiDefault InternalFrame.buttonHoverBackground Color
* @uiDefault InternalFrame.buttonPressedBackground Color
*
* @author Karl Tauber
*/
public class FlatInternalFrameCloseIcon
extends FlatInternalFrameAbstractIcon
{
private final Color hoverForeground = UIManager.getColor( "InternalFrame.closeHoverForeground" );
private final Color pressedForeground = UIManager.getColor( "InternalFrame.closePressedForeground" );
public FlatInternalFrameCloseIcon() {
super( UIManager.getDimension( "InternalFrame.buttonSize" ),
UIManager.getColor( "InternalFrame.closeHoverBackground" ),
UIManager.getColor( "InternalFrame.closePressedBackground" ) );
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
paintBackground( c, g );
g.setColor( FlatButtonUI.buttonStateColor( c, c.getForeground(), null, null, hoverForeground, pressedForeground ) );
float mx = width / 2;
float my = height / 2;
float r = 3.25f;
Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD );
path.append( new Line2D.Float( mx - r, my - r, mx + r, my + r ), false );
path.append( new Line2D.Float( mx - r, my + r, mx + r, my - r ), false );
g.setStroke( new BasicStroke( 1f ) );
g.draw( path );
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2019 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.icons;
import java.awt.Component;
import java.awt.Graphics2D;
/**
* "iconify" icon for {@link javax.swing.JInternalFrame}.
*
* @author Karl Tauber
*/
public class FlatInternalFrameIconifyIcon
extends FlatInternalFrameAbstractIcon
{
public FlatInternalFrameIconifyIcon() {
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
paintBackground( c, g );
g.setColor( c.getForeground() );
g.fillRect( (width / 2) - 4, height / 2, 8, 1 );
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2019 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.icons;
import java.awt.Component;
import java.awt.Graphics2D;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
* "maximize" icon for {@link javax.swing.JInternalFrame}.
*
* @author Karl Tauber
*/
public class FlatInternalFrameMaximizeIcon
extends FlatInternalFrameAbstractIcon
{
public FlatInternalFrameMaximizeIcon() {
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
paintBackground( c, g );
g.setColor( c.getForeground() );
g.fill( FlatUIUtils.createRectangle( (width / 2) - 4, (height / 2) - 4, 8, 8, 1 ) );
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2019 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.icons;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.geom.Area;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
* "minimize" (actually "restore") icon for {@link javax.swing.JInternalFrame}.
*
* @author Karl Tauber
*/
public class FlatInternalFrameMinimizeIcon
extends FlatInternalFrameAbstractIcon
{
public FlatInternalFrameMinimizeIcon() {
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
paintBackground( c, g );
g.setColor( c.getForeground() );
int x = (width / 2) - 4;
int y = (height / 2) - 4;
Path2D r1 = FlatUIUtils.createRectangle( x + 1, y - 1, 8, 8, 1 );
Path2D r2 = FlatUIUtils.createRectangle( x - 1, y + 1, 8, 8, 1 );
Area area = new Area( r1 );
area.subtract( new Area( new Rectangle2D.Float( x - 1, y + 1, 8, 8 ) ) );
g.fill( area );
g.fill( r2 );
}
}

View File

@@ -32,6 +32,7 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
* @uiDefault Menu.icon.arrowColor Color * @uiDefault Menu.icon.arrowColor Color
* @uiDefault Menu.icon.disabledArrowColor Color * @uiDefault Menu.icon.disabledArrowColor Color
* @uiDefault Menu.selectionForeground Color * @uiDefault Menu.selectionForeground Color
* @uiDefault MenuItem.selectionType String
* *
* @author Karl Tauber * @author Karl Tauber
*/ */
@@ -65,9 +66,14 @@ public class FlatMenuArrowIcon
} }
private Color getArrowColor( Component c ) { private Color getArrowColor( Component c ) {
if( c instanceof JMenu && ((JMenu)c).isSelected() ) if( c instanceof JMenu && ((JMenu)c).isSelected() && !isUnderlineSelection() )
return selectionForeground; return selectionForeground;
return c.isEnabled() ? arrowColor : disabledArrowColor; return c.isEnabled() ? arrowColor : disabledArrowColor;
} }
private boolean isUnderlineSelection() {
// not storing value of "MenuItem.selectionType" in class to allow changing at runtime
return "underline".equals( UIManager.getString( "MenuItem.selectionType" ) );
}
} }

View File

@@ -38,12 +38,15 @@ public class FlatArrowButton
extends BasicArrowButton extends BasicArrowButton
implements UIResource implements UIResource
{ {
public static final int DEFAULT_ARROW_WIDTH = 8;
private final boolean chevron; private final boolean chevron;
private final Color foreground; private final Color foreground;
private final Color disabledForeground; private final Color disabledForeground;
private final Color hoverForeground; private final Color hoverForeground;
private final Color hoverBackground; private final Color hoverBackground;
private int arrowWidth = DEFAULT_ARROW_WIDTH;
private int xOffset = 0; private int xOffset = 0;
private int yOffset = 0; private int yOffset = 0;
@@ -80,6 +83,14 @@ public class FlatArrowButton
} }
} }
public int getArrowWidth() {
return arrowWidth;
}
public void setArrowWidth( int arrowWidth ) {
this.arrowWidth = arrowWidth;
}
protected boolean isHover() { protected boolean isHover() {
return hover; return hover;
} }
@@ -128,8 +139,8 @@ public class FlatArrowButton
int direction = getDirection(); int direction = getDirection();
boolean vert = (direction == NORTH || direction == SOUTH); boolean vert = (direction == NORTH || direction == SOUTH);
int w = scale( chevron ? 8 : 9 ); int w = scale( arrowWidth + (chevron ? 0 : 1) );
int h = scale( chevron ? 4 : 5 ); int h = scale( (arrowWidth / 2) + (chevron ? 0 : 1) );
int rw = vert ? w : h; int rw = vert ? w : h;
int rh = vert ? h : w; int rh = vert ? h : w;
int x = Math.round( (width - rw) / 2f + scale( (float) xOffset ) ); int x = Math.round( (width - rw) / 2f + scale( (float) xOffset ) );

View File

@@ -23,6 +23,7 @@ import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Insets; import java.awt.Insets;
import java.awt.KeyboardFocusManager; import java.awt.KeyboardFocusManager;
import java.awt.Paint;
import javax.swing.JComboBox; import javax.swing.JComboBox;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JScrollPane; import javax.swing.JScrollPane;
@@ -47,7 +48,7 @@ import javax.swing.text.JTextComponent;
* {@link FlatUIUtils#paintParentBackground} to paint the empty space correctly. * {@link FlatUIUtils#paintParentBackground} to paint the empty space correctly.
* *
* @uiDefault Component.focusWidth int * @uiDefault Component.focusWidth int
* @uiDefault Component.innerFocusWidth int * @uiDefault Component.innerFocusWidth int or float
* @uiDefault Component.focusColor Color * @uiDefault Component.focusColor Color
* @uiDefault Component.borderColor Color * @uiDefault Component.borderColor Color
* @uiDefault Component.disabledBorderColor Color * @uiDefault Component.disabledBorderColor Color
@@ -59,7 +60,7 @@ public class FlatBorder
extends BasicBorders.MarginBorder extends BasicBorders.MarginBorder
{ {
protected final int focusWidth = UIManager.getInt( "Component.focusWidth" ); protected final int focusWidth = UIManager.getInt( "Component.focusWidth" );
protected final int innerFocusWidth = UIManager.getInt( "Component.innerFocusWidth" ); protected final float innerFocusWidth = FlatUIUtils.getUIFloat( "Component.innerFocusWidth", 0 );
protected final Color focusColor = UIManager.getColor( "Component.focusColor" ); protected final Color focusColor = UIManager.getColor( "Component.focusColor" );
protected final Color borderColor = UIManager.getColor( "Component.borderColor" ); protected final Color borderColor = UIManager.getColor( "Component.borderColor" );
protected final Color disabledBorderColor = UIManager.getColor( "Component.disabledBorderColor" ); protected final Color disabledBorderColor = UIManager.getColor( "Component.disabledBorderColor" );
@@ -72,18 +73,20 @@ public class FlatBorder
FlatUIUtils.setRenderingHints( g2 ); FlatUIUtils.setRenderingHints( g2 );
boolean isCellEditor = isTableCellEditor( c ); boolean isCellEditor = isTableCellEditor( c );
float focusWidth = isCellEditor ? 0 : getFocusWidth(); float focusWidth = isCellEditor ? 0 : getFocusWidth( c );
float borderWidth = getBorderWidth( c ); float borderWidth = getBorderWidth( c );
float arc = isCellEditor ? 0 : getArc(); float arc = isCellEditor ? 0 : getArc( c );
if( isFocused( c ) ) { if( isFocused( c ) ) {
float innerFocusWidth = !(c instanceof JScrollPane) ? this.innerFocusWidth : 0;
g2.setColor( getFocusColor( c ) ); g2.setColor( getFocusColor( c ) );
FlatUIUtils.paintOutlineBorder( g2, x, y, width, height, focusWidth, FlatUIUtils.paintComponentOuterBorder( g2, x, y, width, height, focusWidth,
getLineWidth() + scale( (float) innerFocusWidth ), arc ); getLineWidth( c ) + scale( innerFocusWidth ), arc );
} }
g2.setColor( getBorderColor( c ) ); g2.setPaint( getBorderColor( c ) );
FlatUIUtils.drawRoundRectangle( g2, x, y, width, height, focusWidth, borderWidth, arc ); FlatUIUtils.paintComponentBorder( g2, x, y, width, height, focusWidth, borderWidth, arc );
} finally { } finally {
g2.dispose(); g2.dispose();
} }
@@ -93,19 +96,30 @@ public class FlatBorder
return focusColor; return focusColor;
} }
protected Color getBorderColor( Component c ) { protected Paint getBorderColor( Component c ) {
boolean enabled = c.isEnabled() && (!(c instanceof JTextComponent) || ((JTextComponent)c).isEditable()); return isEnabled( c )
return enabled
? (isFocused( c ) ? focusedBorderColor : borderColor) ? (isFocused( c ) ? focusedBorderColor : borderColor)
: disabledBorderColor; : disabledBorderColor;
} }
protected boolean isEnabled( Component c ) {
if( c instanceof JScrollPane ) {
// check whether view component is disabled
JViewport viewport = ((JScrollPane)c).getViewport();
Component view = (viewport != null) ? viewport.getView() : null;
if( view != null && !isEnabled( view ) )
return false;
}
return c.isEnabled() && (!(c instanceof JTextComponent) || ((JTextComponent)c).isEditable());
}
protected boolean isFocused( Component c ) { protected boolean isFocused( Component c ) {
if( c instanceof JScrollPane ) { if( c instanceof JScrollPane ) {
JViewport viewport = ((JScrollPane)c).getViewport(); JViewport viewport = ((JScrollPane)c).getViewport();
Component view = (viewport != null) ? viewport.getView() : null; Component view = (viewport != null) ? viewport.getView() : null;
if( view != null ) { if( view != null ) {
if( view.hasFocus() ) if( FlatUIUtils.isPermanentFocusOwner( view ) )
return true; return true;
if( (view instanceof JTable && ((JTable)view).isEditing()) || if( (view instanceof JTable && ((JTable)view).isEditing()) ||
@@ -119,17 +133,17 @@ public class FlatBorder
return false; return false;
} else if( c instanceof JComboBox && ((JComboBox<?>)c).isEditable() ) { } else if( c instanceof JComboBox && ((JComboBox<?>)c).isEditable() ) {
Component editorComponent = ((JComboBox<?>)c).getEditor().getEditorComponent(); Component editorComponent = ((JComboBox<?>)c).getEditor().getEditorComponent();
return (editorComponent != null) ? editorComponent.hasFocus() : false; return (editorComponent != null) ? FlatUIUtils.isPermanentFocusOwner( editorComponent ) : false;
} else if( c instanceof JSpinner ) { } else if( c instanceof JSpinner ) {
JComponent editor = ((JSpinner)c).getEditor(); JComponent editor = ((JSpinner)c).getEditor();
if( editor instanceof JSpinner.DefaultEditor ) { if( editor instanceof JSpinner.DefaultEditor ) {
JTextField textField = ((JSpinner.DefaultEditor)editor).getTextField(); JTextField textField = ((JSpinner.DefaultEditor)editor).getTextField();
if( textField != null ) if( textField != null )
return textField.hasFocus(); return FlatUIUtils.isPermanentFocusOwner( textField );
} }
return false; return false;
} else } else
return c.hasFocus(); return FlatUIUtils.isPermanentFocusOwner( c );
} }
protected boolean isTableCellEditor( Component c ) { protected boolean isTableCellEditor( Component c ) {
@@ -139,7 +153,7 @@ public class FlatBorder
@Override @Override
public Insets getBorderInsets( Component c, Insets insets ) { public Insets getBorderInsets( Component c, Insets insets ) {
boolean isCellEditor = isTableCellEditor( c ); boolean isCellEditor = isTableCellEditor( c );
float ow = (isCellEditor ? 0 : getFocusWidth()) + getLineWidth(); float ow = (isCellEditor ? 0 : getFocusWidth( c )) + getLineWidth( c );
insets = super.getBorderInsets( c, insets ); insets = super.getBorderInsets( c, insets );
insets.top = Math.round( scale( (float) insets.top ) + ow ); insets.top = Math.round( scale( (float) insets.top ) + ow );
@@ -149,19 +163,19 @@ public class FlatBorder
return insets; return insets;
} }
protected float getFocusWidth() { protected float getFocusWidth( Component c ) {
return scale( (float) focusWidth ); return scale( (float) focusWidth );
} }
protected float getLineWidth() { protected float getLineWidth( Component c ) {
return scale( 1f ); return scale( 1f );
} }
protected float getBorderWidth( Component c ) { protected float getBorderWidth( Component c ) {
return getLineWidth(); return getLineWidth( c );
} }
protected float getArc() { protected float getArc( Component c ) {
return 0; return 0;
} }
} }

View File

@@ -19,24 +19,33 @@ package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale; import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Color; import java.awt.Color;
import java.awt.Component; import java.awt.Component;
import java.awt.GradientPaint;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Insets; import java.awt.Insets;
import javax.swing.JButton; import java.awt.Paint;
import javax.swing.AbstractButton;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.plaf.UIResource; import javax.swing.plaf.UIResource;
import com.formdev.flatlaf.util.UIScale;
/** /**
* Border for {@link javax.swing.JButton}. * Border for {@link javax.swing.JButton}.
* *
* @uiDefault Button.borderColor Color * @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.disabledBorderColor Color
* @uiDefault Button.focusedBorderColor Color * @uiDefault Button.focusedBorderColor Color
* @uiDefault Button.hoverBorderColor Color optional * @uiDefault Button.hoverBorderColor Color optional
* @uiDefault Button.default.borderColor Color * @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.hoverBorderColor Color optional
* @uiDefault Button.default.focusedBorderColor Color * @uiDefault Button.default.focusedBorderColor Color
* @uiDefault Button.default.focusColor Color * @uiDefault Button.default.focusColor Color
* @uiDefault Button.default.borderWidth int * @uiDefault Button.default.borderWidth int
* @uiDefault Button.toolbar.margin Insets
* @uiDefault Button.toolbar.spacingInsets Insets
* @uiDefault Button.arc int * @uiDefault Button.arc int
* *
* @author Karl Tauber * @author Karl Tauber
@@ -44,21 +53,28 @@ import javax.swing.plaf.UIResource;
public class FlatButtonBorder public class FlatButtonBorder
extends FlatBorder extends FlatBorder
{ {
protected final Color borderColor = UIManager.getColor( "Button.borderColor" ); 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 disabledBorderColor = UIManager.getColor( "Button.disabledBorderColor" );
protected final Color focusedBorderColor = UIManager.getColor( "Button.focusedBorderColor" ); protected final Color focusedBorderColor = UIManager.getColor( "Button.focusedBorderColor" );
protected final Color hoverBorderColor = UIManager.getColor( "Button.hoverBorderColor" ); protected final Color hoverBorderColor = UIManager.getColor( "Button.hoverBorderColor" );
protected final Color defaultBorderColor = UIManager.getColor( "Button.default.borderColor" ); 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 defaultHoverBorderColor = UIManager.getColor( "Button.default.hoverBorderColor" );
protected final Color defaultFocusedBorderColor = UIManager.getColor( "Button.default.focusedBorderColor" ); protected final Color defaultFocusedBorderColor = UIManager.getColor( "Button.default.focusedBorderColor" );
protected final Color defaultFocusColor = UIManager.getColor( "Button.default.focusColor" ); protected final Color defaultFocusColor = UIManager.getColor( "Button.default.focusColor" );
protected final int defaultBorderWidth = UIManager.getInt( "Button.default.borderWidth" ); protected final int defaultBorderWidth = UIManager.getInt( "Button.default.borderWidth" );
protected final Insets toolbarMargin = UIManager.getInsets( "Button.toolbar.margin" );
protected final Insets toolbarSpacingInsets = UIManager.getInsets( "Button.toolbar.spacingInsets" );
protected final int arc = UIManager.getInt( "Button.arc" ); protected final int arc = UIManager.getInt( "Button.arc" );
@Override @Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) { public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
if( FlatButtonUI.isContentAreaFilled( c ) && !FlatButtonUI.isHelpButton( c ) ) if( FlatButtonUI.isContentAreaFilled( c ) &&
super.paintBorder( c, g, x, y, width, height ); !FlatButtonUI.isToolBarButton( c ) &&
!FlatButtonUI.isHelpButton( c ) &&
!FlatToggleButtonUI.isTabButton( c ) )
super.paintBorder( c, g, x, y, width, height );
} }
@Override @Override
@@ -67,34 +83,58 @@ public class FlatButtonBorder
} }
@Override @Override
protected Color getBorderColor( Component c ) { protected Paint getBorderColor( Component c ) {
boolean def = FlatButtonUI.isDefaultButton( c ); boolean def = FlatButtonUI.isDefaultButton( c );
return FlatButtonUI.buttonStateColor( c, Paint color = FlatButtonUI.buttonStateColor( c,
def ? defaultBorderColor : borderColor, def ? defaultBorderColor : borderColor,
disabledBorderColor, disabledBorderColor,
def ? defaultFocusedBorderColor : focusedBorderColor, def ? defaultFocusedBorderColor : focusedBorderColor,
def ? defaultHoverBorderColor : hoverBorderColor, def ? defaultHoverBorderColor : hoverBorderColor,
null ); null );
// change to gradient paint if start/end colors are specified
Color startBg = def ? defaultBorderColor : borderColor;
Color endBg = def ? defaultEndBorderColor : endBorderColor;
if( color == startBg && endBg != null && !startBg.equals( endBg ) )
color = new GradientPaint( 0, 0, startBg, 0, c.getHeight(), endBg );
return color;
} }
@Override @Override
public Insets getBorderInsets( Component c, Insets insets ) { public Insets getBorderInsets( Component c, Insets insets ) {
insets = super.getBorderInsets( c, insets ); if( FlatButtonUI.isToolBarButton( c ) ) {
// In toolbars, use button margin only if explicitly set.
// Otherwise use toolbar margin specified in UI defaults.
Insets margin = (c instanceof AbstractButton)
? ((AbstractButton)c).getMargin()
: null;
// use smaller left and right insets for icon-only buttons (so that they are square) FlatUIUtils.setInsets( insets, UIScale.scale( FlatUIUtils.addInsets( toolbarSpacingInsets,
if( FlatButtonUI.isIconOnlyButton( c ) && ((JButton)c).getMargin() instanceof UIResource ) (margin != null && !(margin instanceof UIResource)) ? margin : toolbarMargin ) ) );
insets.left = insets.right = Math.min( insets.top, insets.bottom ); } else {
insets = super.getBorderInsets( c, insets );
// use smaller left and right insets for icon-only buttons (so that they are square)
if( FlatButtonUI.isIconOnlyButton( c ) && ((AbstractButton)c).getMargin() instanceof UIResource )
insets.left = insets.right = Math.min( insets.top, insets.bottom );
}
return insets; return insets;
} }
@Override
protected float getFocusWidth( Component c ) {
return FlatToggleButtonUI.isTabButton( c ) ? 0 : super.getFocusWidth(c );
}
@Override @Override
protected float getBorderWidth( Component c ) { protected float getBorderWidth( Component c ) {
return FlatButtonUI.isDefaultButton( c ) ? scale( (float) defaultBorderWidth ) : super.getBorderWidth( c ); return FlatButtonUI.isDefaultButton( c ) ? scale( (float) defaultBorderWidth ) : super.getBorderWidth( c );
} }
@Override @Override
protected float getArc() { protected float getArc( Component c ) {
return scale( (float) arc ); return FlatButtonUI.isSquareButton( c ) ? 0 : scale( (float) arc );
} }
} }

View File

@@ -23,22 +23,29 @@ import java.awt.Component;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Font; import java.awt.Font;
import java.awt.FontMetrics; import java.awt.FontMetrics;
import java.awt.GradientPaint;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.geom.RoundRectangle2D;
import java.beans.PropertyChangeEvent;
import javax.swing.AbstractButton; import javax.swing.AbstractButton;
import javax.swing.ButtonModel; import javax.swing.ButtonModel;
import javax.swing.Icon; import javax.swing.Icon;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JToggleButton;
import javax.swing.JToolBar; import javax.swing.JToolBar;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.border.Border; import javax.swing.border.Border;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource; import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicButtonListener;
import javax.swing.plaf.basic.BasicButtonUI; import javax.swing.plaf.basic.BasicButtonUI;
import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.util.UIScale;
/** /**
* Provides the Flat LaF UI delegate for {@link javax.swing.JButton}. * Provides the Flat LaF UI delegate for {@link javax.swing.JButton}.
@@ -58,16 +65,25 @@ import com.formdev.flatlaf.FlatLaf;
* @uiDefault Button.arc int * @uiDefault Button.arc int
* @uiDefault Button.minimumWidth int * @uiDefault Button.minimumWidth int
* @uiDefault Button.iconTextGap int * @uiDefault Button.iconTextGap int
* @uiDefault Button.startBackground Color optional; if set, a gradient paint is used and Button.background is ignored
* @uiDefault Button.endBackground Color optional; if set, a gradient paint is used
* @uiDefault Button.focusedBackground Color optional * @uiDefault Button.focusedBackground Color optional
* @uiDefault Button.hoverBackground Color optional * @uiDefault Button.hoverBackground Color optional
* @uiDefault Button.pressedBackground Color optional * @uiDefault Button.pressedBackground Color optional
* @uiDefault Button.disabledText Color * @uiDefault Button.disabledText Color
* @uiDefault Button.default.background Color * @uiDefault Button.default.background Color
* @uiDefault Button.default.startBackground Color optional; if set, a gradient paint is used and Button.default.background is ignored
* @uiDefault Button.default.endBackground Color optional; if set, a gradient paint is used
* @uiDefault Button.default.foreground Color * @uiDefault Button.default.foreground Color
* @uiDefault Button.default.focusedBackground Color optional * @uiDefault Button.default.focusedBackground Color optional
* @uiDefault Button.default.hoverBackground Color optional * @uiDefault Button.default.hoverBackground Color optional
* @uiDefault Button.default.pressedBackground Color optional * @uiDefault Button.default.pressedBackground Color optional
* @uiDefault Button.default.boldText boolean * @uiDefault Button.default.boldText boolean
* @uiDefault Button.paintShadow boolean default is false
* @uiDefault Button.shadowWidth int default is 2
* @uiDefault Button.shadowColor Color optional
* @uiDefault Button.default.shadowColor Color optional
* @uiDefault Button.toolbar.spacingInsets Insets
* @uiDefault Button.toolbar.hoverBackground Color * @uiDefault Button.toolbar.hoverBackground Color
* @uiDefault Button.toolbar.pressedBackground Color * @uiDefault Button.toolbar.pressedBackground Color
* *
@@ -81,18 +97,26 @@ public class FlatButtonUI
protected int minimumWidth; protected int minimumWidth;
protected int iconTextGap; protected int iconTextGap;
protected Color startBackground;
protected Color endBackground;
protected Color focusedBackground; protected Color focusedBackground;
protected Color hoverBackground; protected Color hoverBackground;
protected Color pressedBackground; protected Color pressedBackground;
protected Color disabledText; protected Color disabledText;
protected Color defaultBackground; protected Color defaultBackground;
protected Color defaultEndBackground;
protected Color defaultForeground; protected Color defaultForeground;
protected Color defaultFocusedBackground; protected Color defaultFocusedBackground;
protected Color defaultHoverBackground; protected Color defaultHoverBackground;
protected Color defaultPressedBackground; protected Color defaultPressedBackground;
protected boolean defaultBoldText; protected boolean defaultBoldText;
protected int shadowWidth;
protected Color shadowColor;
protected Color defaultShadowColor;
protected Insets toolbarSpacingInsets;
protected Color toolbarHoverBackground; protected Color toolbarHoverBackground;
protected Color toolbarPressedBackground; protected Color toolbarPressedBackground;
@@ -116,22 +140,36 @@ public class FlatButtonUI
String prefix = getPropertyPrefix(); String prefix = getPropertyPrefix();
focusWidth = UIManager.getInt( "Component.focusWidth" ); focusWidth = UIManager.getInt( "Component.focusWidth" );
arc = UIManager.getInt( prefix + "arc" ); arc = UIManager.getInt( "Button.arc" );
minimumWidth = UIManager.getInt( prefix + "minimumWidth" ); minimumWidth = UIManager.getInt( prefix + "minimumWidth" );
iconTextGap = FlatUIUtils.getUIInt( prefix + "iconTextGap", 4 ); iconTextGap = FlatUIUtils.getUIInt( prefix + "iconTextGap", 4 );
startBackground = UIManager.getColor( prefix + "startBackground" );
endBackground = UIManager.getColor( prefix + "endBackground" );
focusedBackground = UIManager.getColor( prefix + "focusedBackground" ); focusedBackground = UIManager.getColor( prefix + "focusedBackground" );
hoverBackground = UIManager.getColor( prefix + "hoverBackground" ); hoverBackground = UIManager.getColor( prefix + "hoverBackground" );
pressedBackground = UIManager.getColor( prefix + "pressedBackground" ); pressedBackground = UIManager.getColor( prefix + "pressedBackground" );
disabledText = UIManager.getColor( prefix + "disabledText" ); disabledText = UIManager.getColor( prefix + "disabledText" );
defaultBackground = UIManager.getColor( "Button.default.background" ); 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" ); defaultForeground = UIManager.getColor( "Button.default.foreground" );
defaultFocusedBackground = UIManager.getColor( "Button.default.focusedBackground" ); defaultFocusedBackground = UIManager.getColor( "Button.default.focusedBackground" );
defaultHoverBackground = UIManager.getColor( "Button.default.hoverBackground" ); defaultHoverBackground = UIManager.getColor( "Button.default.hoverBackground" );
defaultPressedBackground = UIManager.getColor( "Button.default.pressedBackground" ); defaultPressedBackground = UIManager.getColor( "Button.default.pressedBackground" );
defaultBoldText = UIManager.getBoolean( "Button.default.boldText" ); defaultBoldText = UIManager.getBoolean( "Button.default.boldText" );
toolbarSpacingInsets = UIManager.getInsets( "Button.toolbar.spacingInsets" );
toolbarHoverBackground = UIManager.getColor( prefix + "toolbar.hoverBackground" ); toolbarHoverBackground = UIManager.getColor( prefix + "toolbar.hoverBackground" );
toolbarPressedBackground = UIManager.getColor( prefix + "toolbar.pressedBackground" ); toolbarPressedBackground = UIManager.getColor( prefix + "toolbar.pressedBackground" );
@@ -140,10 +178,16 @@ public class FlatButtonUI
defaults_initialized = true; defaults_initialized = true;
} }
if( startBackground != null ) {
Color bg = b.getBackground();
if( bg == null || bg instanceof UIResource )
b.setBackground( startBackground );
}
LookAndFeel.installProperty( b, "opaque", false ); LookAndFeel.installProperty( b, "opaque", false );
LookAndFeel.installProperty( b, "iconTextGap", scale( iconTextGap ) ); LookAndFeel.installProperty( b, "iconTextGap", scale( iconTextGap ) );
MigLayoutVisualPadding.install( b, focusWidth ); MigLayoutVisualPadding.install( b, getFocusWidth( b ) );
} }
@Override @Override
@@ -154,6 +198,26 @@ public class FlatButtonUI
defaults_initialized = false; defaults_initialized = false;
} }
@Override
protected BasicButtonListener createButtonListener( AbstractButton b ) {
return new BasicButtonListener( b ) {
@Override
public void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e );
FlatButtonUI.this.propertyChange( b, e );
}
};
}
protected void propertyChange( AbstractButton b, PropertyChangeEvent e ) {
switch( e.getPropertyName() ) {
case MINIMUM_WIDTH:
case MINIMUM_HEIGHT:
b.revalidate();
break;
}
}
static boolean isContentAreaFilled( Component c ) { static boolean isContentAreaFilled( Component c ) {
return !(c instanceof AbstractButton) || ((AbstractButton)c).isContentAreaFilled(); return !(c instanceof AbstractButton) || ((AbstractButton)c).isContentAreaFilled();
} }
@@ -163,20 +227,24 @@ public class FlatButtonUI
} }
static boolean isIconOnlyButton( Component c ) { static boolean isIconOnlyButton( Component c ) {
if( !(c instanceof JButton) ) if( !(c instanceof JButton) && !(c instanceof JToggleButton) )
return false; return false;
Icon icon = ((JButton)c).getIcon(); Icon icon = ((AbstractButton)c).getIcon();
String text = ((JButton)c).getText(); String text = ((AbstractButton)c).getText();
return (icon != null && (text == null || text.isEmpty())) || return (icon != null && (text == null || text.isEmpty())) ||
(icon == null && text != null && ("...".equals( text ) || text.length() == 1)); (icon == null && text != null && ("...".equals( text ) || text.length() == 1));
} }
static boolean isSquareButton( Component c ) {
return c instanceof AbstractButton && clientPropertyEquals( (AbstractButton) c, BUTTON_TYPE, BUTTON_TYPE_SQUARE );
}
static boolean isHelpButton( Component c ) { static boolean isHelpButton( Component c ) {
return c instanceof JButton && clientPropertyEquals( (JButton) c, BUTTON_TYPE, BUTTON_TYPE_HELP ); return c instanceof JButton && clientPropertyEquals( (JButton) c, BUTTON_TYPE, BUTTON_TYPE_HELP );
} }
static boolean isToolBarButton( JComponent c ) { static boolean isToolBarButton( Component c ) {
return c.getParent() instanceof JToolBar; return c.getParent() instanceof JToolBar;
} }
@@ -191,28 +259,64 @@ public class FlatButtonUI
return; return;
} }
if( isContentAreaFilled( c ) ) { if( isContentAreaFilled( c ) )
Color background = getBackground( c ); paintBackground( g, c );
if( background != null ) {
Graphics2D g2 = (Graphics2D) g.create();
try {
FlatUIUtils.setRenderingHints( g2 );
Border border = c.getBorder();
float focusWidth = (border instanceof FlatBorder) ? scale( (float) this.focusWidth ) : 0;
float arc = (border instanceof FlatButtonBorder || isToolBarButton( c )) ? scale( (float) this.arc ) : 0;
FlatUIUtils.setColor( g2, background, isDefaultButton(c) ? defaultBackground : c.getBackground() );
FlatUIUtils.fillRoundRectangle( g2, 0, 0, c.getWidth(), c.getHeight(), focusWidth, arc );
} finally {
g2.dispose();
}
}
}
paint( g, c ); paint( g, c );
} }
protected void paintBackground( Graphics g, JComponent c ) {
Color background = getBackground( c );
if( background != null ) {
Graphics2D g2 = (Graphics2D) g.create();
try {
FlatUIUtils.setRenderingHints( g2 );
Border border = c.getBorder();
boolean isToolBarButton = isToolBarButton( c );
float focusWidth = (border instanceof FlatBorder && !isToolBarButton) ? scale( (float) getFocusWidth( c ) ) : 0;
float arc = ((border instanceof FlatButtonBorder && !isSquareButton( c )) || isToolBarButton)
? scale( (float) this.arc ) : 0;
boolean def = isDefaultButton( c );
int x = 0;
int y = 0;
int width = c.getWidth();
int height = c.getHeight();
if( isToolBarButton ) {
Insets spacing = UIScale.scale( toolbarSpacingInsets );
x += spacing.left;
y += spacing.top;
width -= spacing.left + spacing.right;
height -= spacing.top + spacing.bottom;
}
// paint shadow
Color shadowColor = def ? defaultShadowColor : this.shadowColor;
if( !isToolBarButton && shadowColor != null && shadowWidth > 0 && focusWidth > 0 &&
!FlatUIUtils.isPermanentFocusOwner( c ) && c.isEnabled() )
{
g2.setColor( shadowColor );
g2.fill( new RoundRectangle2D.Float( focusWidth, focusWidth + UIScale.scale( (float) shadowWidth ),
width - focusWidth * 2, height - focusWidth * 2, arc, arc ) );
}
// paint background
Color startBg = def ? defaultBackground : startBackground;
Color endBg = def ? defaultEndBackground : endBackground;
if( background == startBg && endBg != null && !startBg.equals( endBg ) )
g2.setPaint( new GradientPaint( 0, 0, startBg, 0, height, endBg ) );
else
FlatUIUtils.setColor( g2, background, def ? defaultBackground : c.getBackground() );
FlatUIUtils.paintComponentBackground( g2, x, y, width, height, focusWidth, arc );
} finally {
g2.dispose();
}
}
}
@Override @Override
protected void paintText( Graphics g, AbstractButton b, Rectangle textRect, String text ) { protected void paintText( Graphics g, AbstractButton b, Rectangle textRect, String text ) {
if( isHelpButton( b ) ) if( isHelpButton( b ) )
@@ -280,7 +384,7 @@ public class FlatButtonUI
if( hoverColor != null && b != null && b.getModel().isRollover() ) if( hoverColor != null && b != null && b.getModel().isRollover() )
return hoverColor; return hoverColor;
if( focusedColor != null && c.hasFocus() ) if( focusedColor != null && FlatUIUtils.isPermanentFocusOwner( c ) )
return focusedColor; return focusedColor;
return enabledColor; return enabledColor;
@@ -297,14 +401,23 @@ public class FlatButtonUI
return new Dimension( helpButtonIcon.getIconWidth(), helpButtonIcon.getIconHeight() ); return new Dimension( helpButtonIcon.getIconWidth(), helpButtonIcon.getIconHeight() );
Dimension prefSize = super.getPreferredSize( c ); Dimension prefSize = super.getPreferredSize( c );
if ( prefSize == null )
return null;
// make button square if it is a icon-only button // make button square if it is a icon-only button
// or apply minimum width, if not in toolbar and not a icon-only button // or apply minimum width, if not in toolbar and not a icon-only button
if( isIconOnlyButton( c ) ) if( isIconOnlyButton( c ) )
prefSize.width = Math.max( prefSize.width, prefSize.height ); prefSize.width = Math.max( prefSize.width, prefSize.height );
else if( !isToolBarButton( c ) ) else if( !isToolBarButton( c ) && c.getBorder() instanceof FlatButtonBorder ) {
prefSize.width = Math.max( prefSize.width, scale( minimumWidth + (focusWidth * 2) ) ); int focusWidth = getFocusWidth( c );
prefSize.width = Math.max( prefSize.width, scale( FlatUIUtils.minimumWidth( c, minimumWidth ) + (focusWidth * 2) ) );
prefSize.height = Math.max( prefSize.height, scale( FlatUIUtils.minimumHeight( c, 0 ) + (focusWidth * 2) ) );
}
return prefSize; return prefSize;
} }
protected int getFocusWidth( JComponent c ) {
return focusWidth;
}
} }

View File

@@ -0,0 +1,128 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.FlatClientProperties.*;
import java.awt.EventQueue;
import java.awt.event.FocusEvent;
import java.awt.event.MouseEvent;
import javax.swing.JFormattedTextField;
import javax.swing.plaf.UIResource;
import javax.swing.text.DefaultCaret;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
/**
* Caret that can select all text on focus gained.
*
* @author Karl Tauber
*/
class FlatCaret
extends DefaultCaret
implements UIResource
{
private final String selectAllOnFocusPolicy;
private boolean wasFocused;
private boolean wasTemporaryLost;
private boolean isMousePressed;
FlatCaret( String selectAllOnFocusPolicy ) {
this.selectAllOnFocusPolicy = selectAllOnFocusPolicy;
}
@Override
public void install( JTextComponent c ) {
super.install( c );
// 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 );
}
}
@Override
public void focusGained( FocusEvent e ) {
if( !wasTemporaryLost && !isMousePressed )
selectAllOnFocusGained();
wasTemporaryLost = false;
wasFocused = true;
super.focusGained( e );
}
@Override
public void focusLost( FocusEvent e ) {
wasTemporaryLost = e.isTemporary();
super.focusLost( e );
}
@Override
public void mousePressed( MouseEvent e ) {
isMousePressed = true;
super.mousePressed( e );
}
@Override
public void mouseReleased( MouseEvent e ) {
isMousePressed = false;
super.mouseReleased( e );
}
private void selectAllOnFocusGained() {
JTextComponent c = getComponent();
Document doc = c.getDocument();
if( doc == null || !c.isEnabled() || !c.isEditable() )
return;
Object selectAllOnFocusPolicy = c.getClientProperty( SELECT_ALL_ON_FOCUS_POLICY );
if( selectAllOnFocusPolicy == null )
selectAllOnFocusPolicy = this.selectAllOnFocusPolicy;
if( SELECT_ALL_ON_FOCUS_POLICY_NEVER.equals( selectAllOnFocusPolicy ) )
return;
if( !SELECT_ALL_ON_FOCUS_POLICY_ALWAYS.equals( selectAllOnFocusPolicy ) ) {
// policy is "once" (or null or unknown)
// was already focused?
if( wasFocused )
return;
// check whether selection was modified before gaining focus
int dot = getDot();
int mark = getMark();
if( dot != mark || dot != doc.getLength() )
return;
}
// select all
if( c instanceof JFormattedTextField ) {
EventQueue.invokeLater( () -> {
setDot( 0 );
moveDot( doc.getLength() );
} );
} else {
setDot( 0 );
moveDot( doc.getLength() );
}
}
}

View File

@@ -16,9 +16,11 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale; import java.awt.Dimension;
import java.beans.PropertyChangeListener; import java.awt.Graphics;
import javax.swing.Icon;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.LookAndFeel;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicCheckBoxMenuItemUI; import javax.swing.plaf.basic.BasicCheckBoxMenuItemUI;
@@ -27,29 +29,34 @@ import javax.swing.plaf.basic.BasicCheckBoxMenuItemUI;
* *
* <!-- BasicCheckBoxMenuItemUI --> * <!-- BasicCheckBoxMenuItemUI -->
* *
* @uiDefault CheckBoxMenuItem.font Font * @uiDefault CheckBoxMenuItem.font Font
* @uiDefault CheckBoxMenuItem.background Color * @uiDefault CheckBoxMenuItem.background Color
* @uiDefault CheckBoxMenuItem.foreground Color * @uiDefault CheckBoxMenuItem.foreground Color
* @uiDefault CheckBoxMenuItem.disabledForeground Color * @uiDefault CheckBoxMenuItem.disabledForeground Color
* @uiDefault CheckBoxMenuItem.selectionBackground Color * @uiDefault CheckBoxMenuItem.selectionBackground Color
* @uiDefault CheckBoxMenuItem.selectionForeground Color * @uiDefault CheckBoxMenuItem.selectionForeground Color
* @uiDefault CheckBoxMenuItem.acceleratorForeground Color * @uiDefault CheckBoxMenuItem.acceleratorForeground Color
* @uiDefault CheckBoxMenuItem.acceleratorSelectionForeground Color * @uiDefault CheckBoxMenuItem.acceleratorSelectionForeground Color
* @uiDefault MenuItem.acceleratorFont Font defaults to MenuItem.font * @uiDefault MenuItem.acceleratorFont Font defaults to MenuItem.font
* @uiDefault MenuItem.acceleratorDelimiter String * @uiDefault MenuItem.acceleratorDelimiter String
* @uiDefault CheckBoxMenuItem.border Border * @uiDefault CheckBoxMenuItem.border Border
* @uiDefault CheckBoxMenuItem.borderPainted boolean * @uiDefault CheckBoxMenuItem.borderPainted boolean
* @uiDefault CheckBoxMenuItem.margin Insets * @uiDefault CheckBoxMenuItem.margin Insets
* @uiDefault CheckBoxMenuItem.arrowIcon Icon * @uiDefault CheckBoxMenuItem.arrowIcon Icon
* @uiDefault CheckBoxMenuItem.checkIcon Icon * @uiDefault CheckBoxMenuItem.checkIcon Icon
* @uiDefault CheckBoxMenuItem.opaque boolean * @uiDefault CheckBoxMenuItem.opaque boolean
* @uiDefault CheckBoxMenuItem.evenHeight boolean *
* <!-- FlatCheckBoxMenuItemUI -->
*
* @uiDefault MenuItem.iconTextGap int
* *
* @author Karl Tauber * @author Karl Tauber
*/ */
public class FlatCheckBoxMenuItemUI public class FlatCheckBoxMenuItemUI
extends BasicCheckBoxMenuItemUI extends BasicCheckBoxMenuItemUI
{ {
private FlatMenuItemRenderer renderer;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
return new FlatCheckBoxMenuItemUI(); return new FlatCheckBoxMenuItemUI();
} }
@@ -58,20 +65,30 @@ public class FlatCheckBoxMenuItemUI
protected void installDefaults() { protected void installDefaults() {
super.installDefaults(); super.installDefaults();
// scale LookAndFeel.installProperty( menuItem, "iconTextGap", FlatUIUtils.getUIInt( "MenuItem.iconTextGap", 4 ) );
defaultTextIconGap = scale( defaultTextIconGap );
renderer = createRenderer();
} }
/**
* Scale defaultTextIconGap again if iconTextGap property has changed.
*/
@Override @Override
protected PropertyChangeListener createPropertyChangeListener( JComponent c ) { protected void uninstallDefaults() {
PropertyChangeListener superListener = super.createPropertyChangeListener( c ); super.uninstallDefaults();
return e -> {
superListener.propertyChange( e ); renderer = null;
if( e.getPropertyName() == "iconTextGap" ) }
defaultTextIconGap = scale( defaultTextIconGap );
}; protected FlatMenuItemRenderer createRenderer() {
return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter );
}
@Override
protected Dimension getPreferredMenuItemSize( JComponent c, Icon checkIcon, Icon arrowIcon, int defaultTextIconGap ) {
return renderer.getPreferredMenuItemSize();
}
@Override
public void paint( Graphics g, JComponent c ) {
renderer.paintMenuItem( g, selectionBackground, selectionForeground, disabledForeground,
acceleratorForeground, acceleratorSelectionForeground );
} }
} }

View File

@@ -16,12 +16,9 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import java.awt.Dimension;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicColorChooserUI; import javax.swing.plaf.basic.BasicColorChooserUI;
import com.formdev.flatlaf.util.UIScale;
/** /**
* Provides the Flat LaF UI delegate for {@link javax.swing.JColorChooser}. * Provides the Flat LaF UI delegate for {@link javax.swing.JColorChooser}.
@@ -43,21 +40,4 @@ public class FlatColorChooserUI
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
return new FlatColorChooserUI(); return new FlatColorChooserUI();
} }
@Override
public void installUI( JComponent c ) {
if( UIScale.getUserScaleFactor() != 1f ) {
// temporary scale swatch sizes
Dimension swatchSize = UIManager.getDimension( "ColorChooser.swatchesSwatchSize" );
Dimension swatchSize2 = UIManager.getDimension( "ColorChooser.swatchesRecentSwatchSize" );
UIManager.put( "ColorChooser.swatchesSwatchSize", UIScale.scale( swatchSize ) );
UIManager.put( "ColorChooser.swatchesRecentSwatchSize", UIScale.scale( swatchSize2 ) );
super.installUI( c );
UIManager.put( "ColorChooser.swatchesSwatchSize", null );
UIManager.put( "ColorChooser.swatchesRecentSwatchSize", null );
} else
super.installUI( c );
}
} }

View File

@@ -28,6 +28,8 @@ import java.awt.Insets;
import java.awt.LayoutManager; import java.awt.LayoutManager;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.Shape; import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent; import java.awt.event.FocusEvent;
import java.awt.event.FocusListener; import java.awt.event.FocusListener;
import java.awt.event.MouseListener; import java.awt.event.MouseListener;
@@ -35,12 +37,16 @@ import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory; import javax.swing.BorderFactory;
import javax.swing.DefaultListCellRenderer;
import javax.swing.InputMap;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JComboBox; import javax.swing.JComboBox;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JList; import javax.swing.JList;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.ListCellRenderer; import javax.swing.ListCellRenderer;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
import javax.swing.SwingConstants; import javax.swing.SwingConstants;
@@ -52,6 +58,8 @@ import javax.swing.plaf.basic.BasicComboBoxUI;
import javax.swing.plaf.basic.BasicComboPopup; import javax.swing.plaf.basic.BasicComboPopup;
import javax.swing.plaf.basic.ComboPopup; import javax.swing.plaf.basic.ComboPopup;
import javax.swing.text.JTextComponent; import javax.swing.text.JTextComponent;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
/** /**
@@ -239,7 +247,8 @@ public class FlatComboBoxUI
} else if( editor != null && source == comboBox && propertyName == "componentOrientation" ) { } else if( editor != null && source == comboBox && propertyName == "componentOrientation" ) {
ComponentOrientation o = (ComponentOrientation) e.getNewValue(); ComponentOrientation o = (ComponentOrientation) e.getNewValue();
editor.applyComponentOrientation( o ); editor.applyComponentOrientation( o );
} } else if( editor != null && FlatClientProperties.PLACEHOLDER_TEXT.equals( propertyName ) )
editor.repaint();
} }
}; };
} }
@@ -268,6 +277,19 @@ public class FlatComboBoxUI
editor.applyComponentOrientation( comboBox.getComponentOrientation() ); editor.applyComponentOrientation( comboBox.getComponentOrientation() );
updateEditorColors(); updateEditorColors();
// macOS
if( SystemInfo.IS_MAC && editor instanceof JTextComponent ) {
// delegate actions from editor text field to combobox, which is necessary
// because text field on macOS already handle those keys
InputMap inputMap = ((JTextComponent)editor).getInputMap();
new EditorDelegateAction( inputMap, KeyStroke.getKeyStroke( "UP" ) );
new EditorDelegateAction( inputMap, KeyStroke.getKeyStroke( "KP_UP" ) );
new EditorDelegateAction( inputMap, KeyStroke.getKeyStroke( "DOWN" ) );
new EditorDelegateAction( inputMap, KeyStroke.getKeyStroke( "KP_DOWN" ) );
new EditorDelegateAction( inputMap, KeyStroke.getKeyStroke( "HOME" ) );
new EditorDelegateAction( inputMap, KeyStroke.getKeyStroke( "END" ) );
}
} }
private void updateEditorColors() { private void updateEditorColors() {
@@ -316,7 +338,7 @@ public class FlatComboBoxUI
g2.setColor( enabled g2.setColor( enabled
? (editableBackground != null && comboBox.isEditable() ? editableBackground : c.getBackground()) ? (editableBackground != null && comboBox.isEditable() ? editableBackground : c.getBackground())
: getDisabledBackground( comboBox ) ); : getDisabledBackground( comboBox ) );
FlatUIUtils.fillRoundRectangle( g2, 0, 0, width, height, focusWidth, arc ); FlatUIUtils.paintComponentBackground( g2, 0, 0, width, height, focusWidth, arc );
// paint arrow button background // paint arrow button background
if( enabled ) { if( enabled ) {
@@ -326,7 +348,7 @@ public class FlatComboBoxUI
g2.clipRect( arrowX, 0, width - arrowX, height ); g2.clipRect( arrowX, 0, width - arrowX, height );
else else
g2.clipRect( 0, 0, arrowX + arrowWidth, height ); g2.clipRect( 0, 0, arrowX + arrowWidth, height );
FlatUIUtils.fillRoundRectangle( g2, 0, 0, width, height, focusWidth, arc ); FlatUIUtils.paintComponentBackground( g2, 0, 0, width, height, focusWidth, arc );
g2.setClip( oldClip ); g2.setClip( oldClip );
} }
@@ -335,7 +357,7 @@ public class FlatComboBoxUI
g2.setColor( enabled ? borderColor : disabledBorderColor ); g2.setColor( enabled ? borderColor : disabledBorderColor );
float lw = scale( 1f ); float lw = scale( 1f );
float lx = isLeftToRight ? arrowX : arrowX + arrowWidth - lw; float lx = isLeftToRight ? arrowX : arrowX + arrowWidth - lw;
g2.fill( new Rectangle2D.Float( lx, focusWidth, lw, height - (focusWidth * 2) ) ); g2.fill( new Rectangle2D.Float( lx, focusWidth, lw, height - 1 - (focusWidth * 2)) );
} }
paint( g, c ); paint( g, c );
@@ -346,6 +368,8 @@ public class FlatComboBoxUI
public void paintCurrentValue( Graphics g, Rectangle bounds, boolean hasFocus ) { public void paintCurrentValue( Graphics g, Rectangle bounds, boolean hasFocus ) {
ListCellRenderer<Object> renderer = comboBox.getRenderer(); ListCellRenderer<Object> renderer = comboBox.getRenderer();
uninstallCellPaddingBorder( renderer ); uninstallCellPaddingBorder( renderer );
if( renderer == null )
renderer = new DefaultListCellRenderer();
Component c = renderer.getListCellRendererComponent( listBox, comboBox.getSelectedItem(), -1, false, false ); Component c = renderer.getListCellRendererComponent( listBox, comboBox.getSelectedItem(), -1, false, false );
c.setFont( comboBox.getFont() ); c.setFont( comboBox.getFont() );
c.applyComponentOrientation( comboBox.getComponentOrientation() ); c.applyComponentOrientation( comboBox.getComponentOrientation() );
@@ -436,12 +460,12 @@ public class FlatComboBoxUI
//---- class FlatComboPopup ----------------------------------------------- //---- class FlatComboPopup -----------------------------------------------
@SuppressWarnings( { "rawtypes", "unchecked" } ) @SuppressWarnings( { "rawtypes", "unchecked" } )
private class FlatComboPopup protected class FlatComboPopup
extends BasicComboPopup extends BasicComboPopup
{ {
private CellPaddingBorder paddingBorder; private CellPaddingBorder paddingBorder;
FlatComboPopup( JComboBox combo ) { protected FlatComboPopup( JComboBox combo ) {
super( combo ); super( combo );
// BasicComboPopup listens to JComboBox.componentOrientation and updates // BasicComboPopup listens to JComboBox.componentOrientation and updates
@@ -456,13 +480,8 @@ public class FlatComboBoxUI
@Override @Override
protected Rectangle computePopupBounds( int px, int py, int pw, int ph ) { protected Rectangle computePopupBounds( int px, int py, int pw, int ph ) {
// get maximum display size of all items, ignoring prototype value // get maximum display size of all items
Object prototype = comboBox.getPrototypeDisplayValue();
if( prototype != null )
comboBox.setPrototypeDisplayValue( null );
Dimension displaySize = getDisplaySize(); Dimension displaySize = getDisplaySize();
if( prototype != null )
comboBox.setPrototypeDisplayValue( prototype );
// make popup wider if necessary // make popup wider if necessary
if( displaySize.width > pw ) { if( displaySize.width > pw ) {
@@ -518,6 +537,8 @@ public class FlatComboBoxUI
CellPaddingBorder.uninstall( renderer ); CellPaddingBorder.uninstall( renderer );
CellPaddingBorder.uninstall( lastRendererComponent ); CellPaddingBorder.uninstall( lastRendererComponent );
if( renderer == null )
renderer = new DefaultListCellRenderer();
Component c = renderer.getListCellRendererComponent( list, value, index, isSelected, cellHasFocus ); Component c = renderer.getListCellRendererComponent( list, value, index, isSelected, cellHasFocus );
c.applyComponentOrientation( comboBox.getComponentOrientation() ); c.applyComponentOrientation( comboBox.getComponentOrientation() );
@@ -601,4 +622,31 @@ public class FlatComboBoxUI
rendererBorder.paintBorder( c, g, x, y, width, height ); rendererBorder.paintBorder( c, g, x, y, width, height );
} }
} }
//---- class EditorDelegateAction -----------------------------------------
/**
* Delegates actions from editor text field to combobox.
*/
private class EditorDelegateAction
extends AbstractAction
{
private final KeyStroke keyStroke;
EditorDelegateAction( InputMap inputMap, KeyStroke keyStroke ) {
this.keyStroke = keyStroke;
// add to input map
inputMap.put( keyStroke, this );
}
@Override
public void actionPerformed( ActionEvent e ) {
ActionListener action = comboBox.getActionForKeyStroke( keyStroke );
if( action != null ) {
action.actionPerformed( new ActionEvent( comboBox, e.getID(),
e.getActionCommand(), e.getWhen(), e.getModifiers() ) );
}
}
}
} }

View File

@@ -0,0 +1,308 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.beans.PropertyVetoException;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.event.MouseInputAdapter;
import javax.swing.event.MouseInputListener;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JRootPane;
import javax.swing.JToolTip;
import javax.swing.LookAndFeel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicDesktopIconUI;
import com.formdev.flatlaf.util.UIScale;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JInternalFrame.JDesktopIcon}.
*
* <!-- BasicDesktopIconUI -->
*
* @uiDefault DesktopIcon.border Border
*
* <!-- FlatDesktopIconUI -->
*
* @uiDefault DesktopIcon.background Color
* @uiDefault DesktopIcon.foreground Color
* @uiDefault DesktopIcon.iconSize Dimension
* @uiDefault DesktopIcon.closeSize Dimension
* @uiDefault DesktopIcon.closeIcon Icon
*
* @author Karl Tauber
*/
public class FlatDesktopIconUI
extends BasicDesktopIconUI
{
private Dimension iconSize;
private Dimension closeSize;
private JLabel dockIcon;
private JButton closeButton;
private JToolTip titleTip;
private ActionListener closeListener;
private MouseInputListener mouseInputListener;
public static ComponentUI createUI( JComponent c ) {
return new FlatDesktopIconUI();
}
@Override
public void uninstallUI( JComponent c ) {
super.uninstallUI( c );
dockIcon = null;
closeButton = null;
}
@Override
protected void installComponents() {
dockIcon = new JLabel();
dockIcon.setHorizontalAlignment( SwingConstants.CENTER );
closeButton = new JButton();
closeButton.setIcon( UIManager.getIcon( "DesktopIcon.closeIcon" ) );
closeButton.setFocusable( false );
closeButton.setBorder( BorderFactory.createEmptyBorder() );
closeButton.setOpaque( true );
closeButton.setBackground( FlatUIUtils.nonUIResource( desktopIcon.getBackground() ) );
closeButton.setForeground( FlatUIUtils.nonUIResource( desktopIcon.getForeground() ) );
closeButton.setVisible( false );
desktopIcon.setLayout( new FlatDesktopIconLayout() );
desktopIcon.add( closeButton );
desktopIcon.add( dockIcon );
}
@Override
protected void uninstallComponents() {
hideTitleTip();
desktopIcon.remove( dockIcon );
desktopIcon.remove( closeButton );
desktopIcon.setLayout( null );
}
@Override
protected void installDefaults() {
super.installDefaults();
LookAndFeel.installColors( desktopIcon, "DesktopIcon.background", "DesktopIcon.foreground" );
iconSize = UIManager.getDimension( "DesktopIcon.iconSize" );
closeSize = UIManager.getDimension( "DesktopIcon.closeSize" );
}
@Override
protected void installListeners() {
super.installListeners();
closeListener = e -> {
if( frame.isClosable() )
frame.doDefaultCloseAction();
};
closeButton.addActionListener( closeListener );
closeButton.addMouseListener( mouseInputListener );
}
@Override
protected void uninstallListeners() {
super.uninstallListeners();
closeButton.removeActionListener( closeListener );
closeButton.removeMouseListener( mouseInputListener );
closeListener = null;
mouseInputListener = null;
}
@Override
protected MouseInputListener createMouseInputListener() {
mouseInputListener = new MouseInputAdapter() {
@Override
public void mouseReleased( MouseEvent e ) {
if( frame.isIcon() && desktopIcon.contains( e.getX(), e.getY() ) ) {
hideTitleTip();
closeButton.setVisible( false );
try {
frame.setIcon( false );
} catch( PropertyVetoException ex ) {
// ignore
}
}
}
@Override
public void mouseEntered( MouseEvent e ) {
showTitleTip();
if( frame.isClosable() )
closeButton.setVisible( true );
}
@Override
public void mouseExited( MouseEvent e ) {
hideTitleTip();
closeButton.setVisible( false );
}
};
return mouseInputListener;
}
private void showTitleTip() {
JRootPane rootPane = SwingUtilities.getRootPane( desktopIcon );
if( rootPane == null )
return;
if( titleTip == null ) {
titleTip = new JToolTip();
rootPane.getLayeredPane().add( titleTip, JLayeredPane.POPUP_LAYER );
}
titleTip.setTipText( frame.getTitle() );
titleTip.setSize( titleTip.getPreferredSize() );
int tx = (desktopIcon.getWidth() - titleTip.getWidth()) / 2;
int ty = -(titleTip.getHeight() + UIScale.scale( 4 ));
Point pt = SwingUtilities.convertPoint( desktopIcon, tx, ty, titleTip.getParent() );
if( pt.x + titleTip.getWidth() > rootPane.getWidth() )
pt.x = rootPane.getWidth() - titleTip.getWidth();
if( pt.x < 0 )
pt.x = 0;
titleTip.setLocation( pt );
titleTip.repaint();
}
private void hideTitleTip() {
if( titleTip == null )
return;
titleTip.setVisible( false );
titleTip.getParent().remove( titleTip );
titleTip = null;
}
@Override
public Dimension getPreferredSize( JComponent c ) {
return UIScale.scale( iconSize );
}
@Override
public Dimension getMinimumSize( JComponent c ) {
return getPreferredSize( c );
}
@Override
public Dimension getMaximumSize( JComponent c ) {
return getPreferredSize( c );
}
void updateDockIcon() {
// use invoke later to make sure that components are updated when switching LaF
EventQueue.invokeLater( () -> {
if( dockIcon != null )
updateDockIconLater();
} );
}
private void updateDockIconLater() {
// make sure that frame is not selected
if( frame.isSelected() ) {
try {
frame.setSelected( false );
} catch( PropertyVetoException ex ) {
// ignore
}
}
// paint internal frame to buffered image
int frameWidth = Math.max( frame.getWidth(), 1 );
int frameHeight = Math.max( frame.getHeight(), 1 );
BufferedImage frameImage = new BufferedImage( frameWidth, frameHeight, BufferedImage.TYPE_INT_ARGB );
Graphics2D g = frameImage.createGraphics();
try {
//TODO fix missing internal frame header when switching LaF
frame.paint( g );
} finally {
g.dispose();
}
// compute preview size (keep ratio; also works with non-square preview)
Insets insets = desktopIcon.getInsets();
int previewWidth = UIScale.scale( iconSize.width ) - insets.left - insets.right;
int previewHeight = UIScale.scale( iconSize.height ) - insets.top - insets.bottom;
float frameRatio = ((float) frameHeight / (float) frameWidth);
if( ((float) previewWidth / (float) frameWidth) > ((float) previewHeight / (float) frameHeight) )
previewWidth = Math.round( previewHeight / frameRatio );
else
previewHeight = Math.round( previewWidth * frameRatio );
// scale preview
Image previewImage = frameImage.getScaledInstance( previewWidth, previewHeight, Image.SCALE_SMOOTH );
dockIcon.setIcon( new ImageIcon( previewImage ) );
}
//---- class DockIcon -----------------------------------------------------
private class FlatDesktopIconLayout
implements LayoutManager
{
@Override public void addLayoutComponent( String name, Component comp ) {}
@Override public void removeLayoutComponent( Component comp ) {}
@Override
public Dimension preferredLayoutSize( Container parent ) {
return dockIcon.getPreferredSize();
}
@Override
public Dimension minimumLayoutSize( Container parent ) {
return dockIcon.getMinimumSize();
}
@Override
public void layoutContainer( Container parent ) {
Insets insets = parent.getInsets();
// dock icon
dockIcon.setBounds( insets.left, insets.top,
parent.getWidth() - insets.left - insets.right,
parent.getHeight() - insets.top - insets.bottom );
// close button in upper right corner
Dimension cSize = UIScale.scale( closeSize );
closeButton.setBounds( parent.getWidth() - cSize.width, 0, cSize.width, cSize.height );
}
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import javax.swing.DefaultDesktopManager;
import javax.swing.JComponent;
import javax.swing.JInternalFrame;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicDesktopPaneUI;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JDesktopPane}.
*
* <!-- BasicDesktopPaneUI -->
*
* @uiDefault Desktop.background Color
* @uiDefault Desktop.minOnScreenInsets Insets
*
* @author Karl Tauber
*/
public class FlatDesktopPaneUI
extends BasicDesktopPaneUI
{
public static ComponentUI createUI( JComponent c ) {
return new FlatDesktopPaneUI();
}
@Override
protected void installDesktopManager() {
desktopManager = desktop.getDesktopManager();
if( desktopManager == null ) {
desktopManager = new FlatDesktopManager();
desktop.setDesktopManager( desktopManager );
}
}
//---- class FlatDesktopManager -------------------------------------------
private class FlatDesktopManager
extends DefaultDesktopManager
implements UIResource
{
@Override
public void iconifyFrame( JInternalFrame f ) {
super.iconifyFrame( f );
((FlatDesktopIconUI)f.getDesktopIcon().getUI()).updateDockIcon();
}
}
}

View File

@@ -98,6 +98,7 @@ public class FlatEditorPaneUI
// and subtract 1px border line width. // and subtract 1px border line width.
// Using "(scale( 1 ) * 2)" instead of "scale( 2 )" to deal with rounding // Using "(scale( 1 ) * 2)" instead of "scale( 2 )" to deal with rounding
// issues. E.g. at scale factor 1.5 the first returns 4, but the second 3. // issues. E.g. at scale factor 1.5 the first returns 4, but the second 3.
int minimumWidth = FlatUIUtils.minimumWidth( getComponent(), this.minimumWidth );
size.width = Math.max( size.width, scale( minimumWidth ) - (scale( 1 ) * 2) ); size.width = Math.max( size.width, scale( minimumWidth ) - (scale( 1 ) * 2) );
return size; return size;
} }

View File

@@ -50,9 +50,10 @@ public class FlatEmptyBorder
@Override @Override
public Insets getBorderInsets( Component c, Insets insets ) { public Insets getBorderInsets( Component c, Insets insets ) {
insets.left = scale( left ); boolean leftToRight = left == right || c.getComponentOrientation().isLeftToRight();
insets.left = scale( leftToRight ? left : right );
insets.top = scale( top ); insets.top = scale( top );
insets.right = scale( right ); insets.right = scale( leftToRight ? right : left );
insets.bottom = scale( bottom ); insets.bottom = scale( bottom );
return insets; return insets;
} }

View File

@@ -26,7 +26,76 @@ import com.formdev.flatlaf.util.UIScale;
/** /**
* Provides the Flat LaF UI delegate for {@link javax.swing.JFileChooser}. * Provides the Flat LaF UI delegate for {@link javax.swing.JFileChooser}.
* *
* TODO document used UI defaults of superclass * <!-- BasicFileChooserUI -->
*
* @uiDefault FileView.directoryIcon Icon
* @uiDefault FileView.fileIcon Icon
* @uiDefault FileView.computerIcon Icon
* @uiDefault FileView.hardDriveIcon Icon
* @uiDefault FileView.floppyDriveIcon Icon
*
* @uiDefault FileChooser.newFolderIcon Icon
* @uiDefault FileChooser.upFolderIcon Icon
* @uiDefault FileChooser.homeFolderIcon Icon
* @uiDefault FileChooser.detailsViewIcon Icon
* @uiDefault FileChooser.listViewIcon Icon
* @uiDefault FileChooser.viewMenuIcon Icon
*
* @uiDefault FileChooser.usesSingleFilePane boolean
* @uiDefault FileChooser.readOnly boolean if true, "New Folder" is disabled
*
* @uiDefault FileChooser.newFolderErrorText String
* @uiDefault FileChooser.newFolderErrorSeparator String
* @uiDefault FileChooser.newFolderParentDoesntExistTitleText String
* @uiDefault FileChooser.newFolderParentDoesntExistText String
* @uiDefault FileChooser.fileDescriptionText String
* @uiDefault FileChooser.directoryDescriptionText String
* @uiDefault FileChooser.saveButtonText String
* @uiDefault FileChooser.openButtonText String
* @uiDefault FileChooser.saveDialogTitleText String
* @uiDefault FileChooser.openDialogTitleText String
* @uiDefault FileChooser.cancelButtonText String
* @uiDefault FileChooser.updateButtonText String
* @uiDefault FileChooser.helpButtonText String
* @uiDefault FileChooser.directoryOpenButtonText String
*
* @uiDefault FileChooser.saveButtonMnemonic String
* @uiDefault FileChooser.openButtonMnemonic String
* @uiDefault FileChooser.cancelButtonMnemonic String
* @uiDefault FileChooser.updateButtonMnemonic String
* @uiDefault FileChooser.helpButtonMnemonic String
* @uiDefault FileChooser.directoryOpenButtonMnemonic String
*
* @uiDefault FileChooser.saveButtonToolTipText String
* @uiDefault FileChooser.openButtonToolTipText String
* @uiDefault FileChooser.cancelButtonToolTipText String
* @uiDefault FileChooser.updateButtonToolTipText String
* @uiDefault FileChooser.helpButtonToolTipText String
* @uiDefault FileChooser.directoryOpenButtonToolTipText String
*
* @uiDefault FileChooser.acceptAllFileFilterText String
*
* <!-- MetalFileChooserUI -->
*
* @uiDefault FileChooser.lookInLabelMnemonic String
* @uiDefault FileChooser.lookInLabelText String
* @uiDefault FileChooser.saveInLabelText String
* @uiDefault FileChooser.fileNameLabelMnemonic String
* @uiDefault FileChooser.fileNameLabelText String
* @uiDefault FileChooser.folderNameLabelMnemonic String
* @uiDefault FileChooser.folderNameLabelText String
* @uiDefault FileChooser.filesOfTypeLabelMnemonic String
* @uiDefault FileChooser.filesOfTypeLabelText String
* @uiDefault FileChooser.upFolderToolTipText String
* @uiDefault FileChooser.upFolderAccessibleName String
* @uiDefault FileChooser.homeFolderToolTipText String
* @uiDefault FileChooser.homeFolderAccessibleName String
* @uiDefault FileChooser.newFolderToolTipText String
* @uiDefault FileChooser.newFolderAccessibleName String
* @uiDefault FileChooser.listViewButtonToolTipText String
* @uiDefault FileChooser.listViewButtonAccessibleName String
* @uiDefault FileChooser.detailsViewButtonToolTipText String
* @uiDefault FileChooser.detailsViewButtonAccessibleName String
* *
* @author Karl Tauber * @author Karl Tauber
*/ */

View File

@@ -39,8 +39,12 @@ import javax.swing.plaf.ComponentUI;
* *
* <!-- FlatTextFieldUI --> * <!-- FlatTextFieldUI -->
* *
* @uiDefault Component.focusWidth int * @uiDefault TextComponent.arc int
* @uiDefault Component.minimumWidth int * @uiDefault Component.focusWidth int
* @uiDefault Component.minimumWidth int
* @uiDefault Component.isIntelliJTheme boolean
* @uiDefault FormattedTextField.placeholderForeground Color
* @uiDefault TextComponent.selectAllOnFocusPolicy String never, once (default) or always
* *
* @author Karl Tauber * @author Karl Tauber
*/ */

View File

@@ -0,0 +1,190 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.LayoutManager;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.LookAndFeel;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.plaf.basic.BasicInternalFrameTitlePane;
import com.formdev.flatlaf.util.UIScale;
/**
* Provides the Flat LaF internal frame title bar.
*
* @author Karl Tauber
*/
public class FlatInternalFrameTitlePane
extends BasicInternalFrameTitlePane
{
private JLabel titleLabel;
private JPanel buttonPanel;
public FlatInternalFrameTitlePane( JInternalFrame f ) {
super( f );
}
@Override
protected void installDefaults() {
super.installDefaults();
LookAndFeel.installBorder( this, "InternalFrameTitlePane.border" );
}
@Override
protected PropertyChangeListener createPropertyChangeListener() {
return new FlatPropertyChangeHandler();
}
@Override
protected LayoutManager createLayout() {
return new BorderLayout( UIScale.scale( 4 ), 0 );
}
@Override
protected void createButtons() {
super.createButtons();
iconButton.setContentAreaFilled( false );
maxButton.setContentAreaFilled( false );
closeButton.setContentAreaFilled( false );
Border emptyBorder = BorderFactory.createEmptyBorder();
iconButton.setBorder( emptyBorder );
maxButton.setBorder( emptyBorder );
closeButton.setBorder( emptyBorder );
updateButtonsVisibility();
}
@Override
protected void addSubComponents() {
titleLabel = new JLabel( frame.getTitle() );
titleLabel.setFont( FlatUIUtils.nonUIResource( getFont() ) );
titleLabel.setMinimumSize( new Dimension( UIScale.scale( 32 ), 1 ) );
updateFrameIcon();
updateColors();
buttonPanel = new JPanel();
buttonPanel.setLayout( new BoxLayout( buttonPanel, BoxLayout.LINE_AXIS ) );
buttonPanel.setOpaque( false );
buttonPanel.add( iconButton );
buttonPanel.add( maxButton );
buttonPanel.add( closeButton );
add( titleLabel, BorderLayout.CENTER );
add( buttonPanel, BorderLayout.LINE_END );
}
private void updateFrameIcon() {
Icon frameIcon = frame.getFrameIcon();
if( frameIcon == UIManager.getIcon( "InternalFrame.icon" ) )
frameIcon = null;
titleLabel.setIcon( frameIcon );
}
private void updateColors() {
Color background = FlatUIUtils.nonUIResource( frame.isSelected() ? selectedTitleColor : notSelectedTitleColor );
Color foreground = FlatUIUtils.nonUIResource( frame.isSelected() ? selectedTextColor : notSelectedTextColor );
titleLabel.setForeground( foreground );
iconButton.setBackground( background );
iconButton.setForeground( foreground );
maxButton.setBackground( background );
maxButton.setForeground( foreground );
closeButton.setBackground( background );
closeButton.setForeground( foreground );
}
private void updateButtonsVisibility() {
iconButton.setVisible( frame.isIconifiable() );
maxButton.setVisible( frame.isMaximizable() );
closeButton.setVisible( frame.isClosable() );
}
/**
* Does nothing because FlatLaf internal frames do not have system menus.
*/
@Override
protected void assembleSystemMenu() {
}
/**
* Does nothing because FlatLaf internal frames do not have system menus.
*/
@Override
protected void showSystemMenu() {
}
@Override
public void paintComponent( Graphics g ) {
paintTitleBackground( g );
}
//---- class FlatPropertyChangeHandler ------------------------------------
private class FlatPropertyChangeHandler
extends PropertyChangeHandler
{
@Override
public void propertyChange( PropertyChangeEvent e ) {
switch( e.getPropertyName() ) {
case JInternalFrame.TITLE_PROPERTY:
titleLabel.setText( frame.getTitle() );
break;
case JInternalFrame.FRAME_ICON_PROPERTY:
updateFrameIcon();
break;
case JInternalFrame.IS_SELECTED_PROPERTY:
updateColors();
break;
case "iconable":
case "maximizable":
case "closable":
updateButtonsVisibility();
enableActions();
revalidate();
repaint();
// do not invoke super.propertyChange() because this adds/removes the buttons
return;
case "componentOrientation":
applyComponentOrientation( frame.getComponentOrientation() );
break;
}
super.propertyChange( e );
}
}
}

View File

@@ -0,0 +1,155 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import javax.swing.JComponent;
import javax.swing.JInternalFrame;
import javax.swing.LookAndFeel;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicInternalFrameUI;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JInternalFrame}.
*
* <!-- BasicInternalFrameUI -->
*
* @uiDefault control Color
* @uiDefault InternalFrame.icon Icon
* @uiDefault InternalFrame.border Border
* @uiDefault InternalFrame.layoutTitlePaneAtOrigin boolean
*
* <!-- BasicInternalFrameTitlePane -->
*
* @uiDefault InternalFrame.titleFont Font
* @uiDefault InternalFrame.icon Icon
* @uiDefault InternalFrame.maximizeIcon Icon
* @uiDefault InternalFrame.minimizeIcon Icon
* @uiDefault InternalFrame.iconifyIcon Icon
* @uiDefault InternalFrame.closeIcon Icon
* @uiDefault InternalFrame.activeTitleBackground Color
* @uiDefault InternalFrame.activeTitleForeground Color
* @uiDefault InternalFrame.inactiveTitleBackground Color
* @uiDefault InternalFrame.inactiveTitleForeground Color
* @uiDefault InternalFrame.closeButtonToolTip String
* @uiDefault InternalFrame.iconButtonToolTip String
* @uiDefault InternalFrame.restoreButtonToolTip String
* @uiDefault InternalFrame.maxButtonToolTip String
* @uiDefault InternalFrameTitlePane.closeButtonText String
* @uiDefault InternalFrameTitlePane.minimizeButtonText String
* @uiDefault InternalFrameTitlePane.restoreButtonText String
* @uiDefault InternalFrameTitlePane.maximizeButtonText String
* @uiDefault InternalFrameTitlePane.moveButtonText String
* @uiDefault InternalFrameTitlePane.sizeButtonText String
* @uiDefault InternalFrameTitlePane.closeButton.mnemonic Integer
* @uiDefault InternalFrameTitlePane.minimizeButton.mnemonic Integer
* @uiDefault InternalFrameTitlePane.restoreButton.mnemonic Integer
* @uiDefault InternalFrameTitlePane.maximizeButton.mnemonic Integer
* @uiDefault InternalFrameTitlePane.moveButton.mnemonic Integer
* @uiDefault InternalFrameTitlePane.sizeButton.mnemonic Integer
*
* <!-- FlatInternalFrameUI -->
*
* @uiDefault InternalFrame.activeBorderColor Color
* @uiDefault InternalFrame.inactiveBorderColor Color
* @uiDefault InternalFrame.borderLineWidth int
* @uiDefault InternalFrame.borderMargins Insets
*
* <!-- FlatInternalFrameTitlePane -->
*
* @uiDefault InternalFrameTitlePane.border Border
*
* @author Karl Tauber
*/
public class FlatInternalFrameUI
extends BasicInternalFrameUI
{
public static ComponentUI createUI( JComponent c ) {
return new FlatInternalFrameUI( (JInternalFrame) c );
}
public FlatInternalFrameUI( JInternalFrame b ) {
super( b );
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
LookAndFeel.installProperty( frame, "opaque", false );
}
@Override
protected JComponent createNorthPane( JInternalFrame w ) {
return new FlatInternalFrameTitlePane( w );
}
//---- class FlatInternalFrameBorder --------------------------------------
public static class FlatInternalFrameBorder
extends FlatEmptyBorder
{
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 );
public FlatInternalFrameBorder() {
super( UIManager.getInsets( "InternalFrame.borderMargins" ) );
}
@Override
public Insets getBorderInsets( Component c, Insets insets ) {
if( c instanceof JInternalFrame && ((JInternalFrame)c).isMaximum() ) {
insets.left = scale( Math.min( borderLineWidth, left ) );
insets.top = scale( Math.min( borderLineWidth, top ) );
insets.right = scale( Math.min( borderLineWidth, right ) );
insets.bottom = scale( Math.min( borderLineWidth, bottom ) );
return insets;
}
return super.getBorderInsets( c, insets );
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
JInternalFrame f = (JInternalFrame) c;
Insets insets = getBorderInsets( c );
float lineWidth = scale( (float) borderLineWidth );
Graphics2D g2 = (Graphics2D) g.create();
try {
FlatUIUtils.setRenderingHints( g2 );
g2.setColor( f.isSelected() ? activeBorderColor : inactiveBorderColor );
g2.fill( FlatUIUtils.createRectangle(
x + insets.left - lineWidth,
y + insets.top - lineWidth,
width - insets.left - insets.right + (lineWidth * 2),
height - insets.top - insets.bottom + (lineWidth * 2),
lineWidth ) );
} finally {
g2.dispose();
}
}
}
}

View File

@@ -20,12 +20,14 @@ import java.awt.Color;
import java.awt.FontMetrics; import java.awt.FontMetrics;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.beans.PropertyChangeEvent;
import javax.swing.Icon; import javax.swing.Icon;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JLabel; import javax.swing.JLabel;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.plaf.basic.BasicLabelUI; import javax.swing.plaf.basic.BasicLabelUI;
import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
@@ -77,6 +79,51 @@ public class FlatLabelUI
defaults_initialized = false; defaults_initialized = false;
} }
@Override
protected void installComponents( JLabel c ) {
super.installComponents( c );
// update HTML renderer if necessary
updateHTMLRenderer( c, c.getText(), false );
}
@Override
public void propertyChange( PropertyChangeEvent e ) {
String name = e.getPropertyName();
if( name == "text" || name == "font" || name == "foreground" ) {
JLabel label = (JLabel) e.getSource();
updateHTMLRenderer( label, label.getText(), true );
} else
super.propertyChange( e );
}
/**
* Checks whether text contains HTML headings and adds a special CSS rule to
* re-calculate heading font sizes based on current component font size.
*/
static void updateHTMLRenderer( JComponent c, String text, boolean always ) {
if( BasicHTML.isHTMLString( text ) &&
c.getClientProperty( "html.disable" ) != Boolean.TRUE &&
text.contains( "<h" ) &&
(text.contains( "<h1" ) || text.contains( "<h2" ) || text.contains( "<h3" ) ||
text.contains( "<h4" ) || text.contains( "<h5" ) || text.contains( "<h6" )) )
{
int headIndex = text.indexOf( "<head>" );
String style = "<style>BASE_SIZE " + c.getFont().getSize() + "</style>";
if( headIndex < 0 )
style = "<head>" + style + "</head>";
int insertIndex = headIndex >= 0 ? (headIndex + "<head>".length()) : "<html>".length();
text = text.substring( 0, insertIndex )
+ style
+ text.substring( insertIndex );
} else if( !always )
return; // not necessary to invoke BasicHTML.updateRenderer()
BasicHTML.updateRenderer( c, text );
}
@Override @Override
protected void paintEnabledText( JLabel l, Graphics g, String s, int textX, int textY ) { protected void paintEnabledText( JLabel l, Graphics g, String s, int textX, int textY ) {
int mnemIndex = FlatLaf.isShowMnemonics() ? l.getDisplayedMnemonicIndex() : -1; int mnemIndex = FlatLaf.isShowMnemonics() ? l.getDisplayedMnemonicIndex() : -1;

View File

@@ -26,9 +26,9 @@ import java.awt.Insets;
/** /**
* Line border for various components. * Line border for various components.
* *
* Paints a scaled 1px thick line around the component. * Paints a scaled (usually 1px thick) line around the component.
* The line thickness is not included in the border insets. * The line thickness is not added to the border insets.
* The insets should be at least 1,1,1,1. * The insets should be at least have line thickness (usually 1,1,1,1).
* *
* @author Karl Tauber * @author Karl Tauber
*/ */
@@ -36,10 +36,24 @@ public class FlatLineBorder
extends FlatEmptyBorder extends FlatEmptyBorder
{ {
private final Color lineColor; private final Color lineColor;
private final float lineThickness;
public FlatLineBorder( Insets insets, Color lineColor ) { public FlatLineBorder( Insets insets, Color lineColor ) {
this( insets, lineColor, 1f );
}
public FlatLineBorder( Insets insets, Color lineColor, float lineThickness ) {
super( insets ); super( insets );
this.lineColor = lineColor; this.lineColor = lineColor;
this.lineThickness = lineThickness;
}
public Color getLineColor() {
return lineColor;
}
public float getLineThickness() {
return lineThickness;
} }
@Override @Override
@@ -48,7 +62,7 @@ public class FlatLineBorder
try { try {
FlatUIUtils.setRenderingHints( g2 ); FlatUIUtils.setRenderingHints( g2 );
g2.setColor( lineColor ); g2.setColor( lineColor );
FlatUIUtils.drawRoundRectangle( g2, x, y, width, height, 0f, scale( 1f ), 0f ); FlatUIUtils.paintComponentBorder( g2, x, y, width, height, 0f, scale( lineThickness ), 0f );
} finally { } finally {
g2.dispose(); g2.dispose();
} }

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2019 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import java.awt.Component;
import java.awt.Graphics;
import javax.swing.JList;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
/**
* Cell border for {@link javax.swing.DefaultListCellRenderer}
* (used by {@link javax.swing.JList}).
* <p>
* Uses separate cell margins from UI defaults to allow easy customizing.
*
* @author Karl Tauber
*/
public class FlatListCellBorder
extends FlatLineBorder
{
final boolean showCellFocusIndicator = UIManager.getBoolean( "List.showCellFocusIndicator" );
protected FlatListCellBorder() {
super( UIManager.getInsets( "List.cellMargins" ), UIManager.getColor( "List.cellFocusColor" ) );
}
//---- class Default ------------------------------------------------------
/**
* Border for unselected cell that uses margins, but does not paint focus indicator border.
*/
public static class Default
extends FlatListCellBorder
{
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
// do not paint focus indicator border
}
}
//---- class Focused ------------------------------------------------------
/**
* Border for focused unselected cell that uses margins and paints focus indicator border.
*/
public static class Focused
extends FlatListCellBorder
{
}
//---- class Selected -----------------------------------------------------
/**
* Border for selected cell that uses margins and paints focus indicator border
* if enabled (List.showCellFocusIndicator=true) and exactly one item is selected.
*/
public static class Selected
extends FlatListCellBorder
{
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
if( !showCellFocusIndicator )
return;
// paint focus indicator border only if exactly one item is selected
JList<?> list = (JList<?>) SwingUtilities.getAncestorOfClass( JList.class, c );
if( list != null && list.getMinSelectionIndex() == list.getMaxSelectionIndex() )
return;
super.paintBorder( c, g, x, y, width, height );
}
}
}

View File

@@ -52,6 +52,12 @@ import javax.swing.plaf.basic.BasicListUI;
* @uiDefault List.selectionInactiveBackground Color * @uiDefault List.selectionInactiveBackground Color
* @uiDefault List.selectionInactiveForeground Color * @uiDefault List.selectionInactiveForeground Color
* *
* <!-- FlatListCellBorder -->
*
* @uiDefault List.cellMargins Insets
* @uiDefault List.cellFocusColor Color
* @uiDefault List.showCellFocusIndicator boolean
*
* @author Karl Tauber * @author Karl Tauber
*/ */
public class FlatListUI public class FlatListUI
@@ -75,7 +81,7 @@ public class FlatListUI
selectionInactiveBackground = UIManager.getColor( "List.selectionInactiveBackground" ); selectionInactiveBackground = UIManager.getColor( "List.selectionInactiveBackground" );
selectionInactiveForeground = UIManager.getColor( "List.selectionInactiveForeground" ); selectionInactiveForeground = UIManager.getColor( "List.selectionInactiveForeground" );
toggleSelectionColors( list.hasFocus() ); toggleSelectionColors();
} }
@Override @Override
@@ -94,13 +100,13 @@ public class FlatListUI
@Override @Override
public void focusGained( FocusEvent e ) { public void focusGained( FocusEvent e ) {
super.focusGained( e ); super.focusGained( e );
toggleSelectionColors( true ); toggleSelectionColors();
} }
@Override @Override
public void focusLost( FocusEvent e ) { public void focusLost( FocusEvent e ) {
super.focusLost( e ); super.focusLost( e );
toggleSelectionColors( false ); toggleSelectionColors();
} }
}; };
} }
@@ -114,8 +120,8 @@ public class FlatListUI
* already used in applications. Then either the inactive colors are not used, * already used in applications. Then either the inactive colors are not used,
* or the application has to be changed to extend a FlatLaf renderer. * or the application has to be changed to extend a FlatLaf renderer.
*/ */
private void toggleSelectionColors( boolean focused ) { private void toggleSelectionColors() {
if( focused ) { if( FlatUIUtils.isPermanentFocusOwner( list ) ) {
if( list.getSelectionBackground() == selectionInactiveBackground ) if( list.getSelectionBackground() == selectionInactiveBackground )
list.setSelectionBackground( selectionBackground ); list.setSelectionBackground( selectionBackground );
if( list.getSelectionForeground() == selectionInactiveForeground ) if( list.getSelectionForeground() == selectionInactiveForeground )

View File

@@ -29,13 +29,26 @@ import javax.swing.plaf.basic.BasicBorders;
public class FlatMarginBorder public class FlatMarginBorder
extends BasicBorders.MarginBorder extends BasicBorders.MarginBorder
{ {
private final int left, right, top, bottom;
public FlatMarginBorder() {
left = right = top = bottom = 0;
}
public FlatMarginBorder( Insets insets ) {
left = insets.left;
top = insets.top;
right = insets.right;
bottom = insets.bottom;
}
@Override @Override
public Insets getBorderInsets( Component c, Insets insets ) { public Insets getBorderInsets( Component c, Insets insets ) {
insets = super.getBorderInsets( c, insets ); insets = super.getBorderInsets( c, insets );
insets.top = scale( insets.top ); insets.top = scale( insets.top + top );
insets.left = scale( insets.left ); insets.left = scale( insets.left + left );
insets.bottom = scale( insets.bottom ); insets.bottom = scale( insets.bottom + bottom );
insets.right = scale( insets.right ); insets.right = scale( insets.right + right );
return insets; return insets;
} }
} }

View File

@@ -16,9 +16,20 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.MenuElement;
import javax.swing.MenuSelectionManager;
import javax.swing.SwingUtilities;
import javax.swing.plaf.ActionMapUIResource;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicMenuBarUI; import javax.swing.plaf.basic.BasicMenuBarUI;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.util.SystemInfo;
/** /**
* Provides the Flat LaF UI delegate for {@link javax.swing.JMenuBar}. * Provides the Flat LaF UI delegate for {@link javax.swing.JMenuBar}.
@@ -38,4 +49,45 @@ public class FlatMenuBarUI
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
return new FlatMenuBarUI(); return new FlatMenuBarUI();
} }
/*
* WARNING: This class is not used on macOS if screen menu bar is enabled.
* Do not add any functionality here.
*/
@Override
protected void installKeyboardActions() {
super.installKeyboardActions();
ActionMap map = SwingUtilities.getUIActionMap( menuBar );
if( map == null ) {
map = new ActionMapUIResource();
SwingUtilities.replaceUIActionMap( menuBar, map );
}
map.put( "takeFocus", new TakeFocus() );
}
//---- class TakeFocus ----------------------------------------------------
/**
* Activates the menu bar and shows mnemonics.
* On Windows, the popup of the first menu is not shown.
* On other platforms, the popup of the first menu is shown.
*/
private static class TakeFocus
extends AbstractAction
{
@Override
public void actionPerformed( ActionEvent e ) {
JMenuBar menuBar = (JMenuBar) e.getSource();
JMenu menu = menuBar.getMenu( 0 );
if( menu != null ) {
MenuSelectionManager.defaultManager().setSelectedPath( SystemInfo.IS_WINDOWS
? new MenuElement[] { menuBar, menu }
: new MenuElement[] { menuBar, menu, menu.getPopupMenu() } );
FlatLaf.showMnemonics( menuBar );
}
}
}
} }

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Component;
import java.awt.Insets;
import javax.swing.JMenuBar;
import javax.swing.UIManager;
/**
* Border for {@link javax.swing.JMenu}, {@link javax.swing.JMenuItem},
* {@link javax.swing.JCheckBoxMenuItem} and {@link javax.swing.JRadioButtonMenuItem}.
*
* @uiDefault MenuBar.itemMargins Insets
*
* @author Karl Tauber
*/
public class FlatMenuItemBorder
extends FlatMarginBorder
{
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 + 1 );
insets.right = scale( menuBarItemMargins.right );
return insets;
} else
return super.getBorderInsets( c, insets );
}
}

View File

@@ -0,0 +1,516 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.text.AttributedCharacterIterator;
import javax.swing.Icon;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.text.View;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.util.Graphics2DProxy;
/**
* Renderer for menu items.
*
* @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.underlineSelectionBackground Color
* @uiDefault MenuItem.underlineSelectionCheckBackground Color
* @uiDefault MenuItem.underlineSelectionColor Color
* @uiDefault MenuItem.underlineSelectionHeight Color
*
* @author Karl Tauber
*/
public class FlatMenuItemRenderer
{
protected final JMenuItem menuItem;
protected final Icon checkIcon;
protected final Icon arrowIcon;
protected final 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 );
protected final Color checkBackground = UIManager.getColor( "MenuItem.checkBackground" );
protected final 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" );
protected FlatMenuItemRenderer( JMenuItem menuItem, Icon checkIcon, Icon arrowIcon,
Font acceleratorFont, String acceleratorDelimiter )
{
this.menuItem = menuItem;
this.checkIcon = checkIcon;
this.arrowIcon = arrowIcon;
this.acceleratorFont = acceleratorFont;
this.acceleratorDelimiter = acceleratorDelimiter;
Dimension minimumIconSize = UIManager.getDimension( "MenuItem.minimumIconSize" );
this.minimumIconSize = (minimumIconSize != null) ? minimumIconSize : new Dimension( 16, 16 );
}
protected Dimension getPreferredMenuItemSize() {
int width = 0;
int height = 0;
boolean isTopLevelMenu = isTopLevelMenu( menuItem );
Rectangle viewRect = new Rectangle( 0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE );
Rectangle iconRect = new Rectangle();
Rectangle textRect = new Rectangle();
// layout icon and text
SwingUtilities.layoutCompoundLabel( menuItem,
menuItem.getFontMetrics( menuItem.getFont() ), menuItem.getText(), getIconForLayout(),
menuItem.getVerticalAlignment(), menuItem.getHorizontalAlignment(),
menuItem.getVerticalTextPosition(), menuItem.getHorizontalTextPosition(),
viewRect, iconRect, textRect, scale( menuItem.getIconTextGap() ) );
// union icon and text rectangles
Rectangle labelRect = iconRect.union( textRect );
width += labelRect.width;
height = Math.max( labelRect.height, height );
// accelerator size
String accelText = getAcceleratorText();
if( accelText != null ) {
// gap between text and accelerator
width += scale( !isTopLevelMenu ? textAcceleratorGap : menuItem.getIconTextGap() );
FontMetrics accelFm = menuItem.getFontMetrics( acceleratorFont );
width += SwingUtilities.computeStringWidth( accelFm, accelText );
height = Math.max( accelFm.getHeight(), height );
}
// arrow size
if( !isTopLevelMenu && arrowIcon != null ) {
// gap between text and arrow
if( accelText == null )
width += scale( textNoAcceleratorGap );
// gap between accelerator and arrow
width += scale( acceleratorArrowGap );
width += arrowIcon.getIconWidth();
height = Math.max( arrowIcon.getIconHeight(), height );
}
// add insets
Insets insets = menuItem.getInsets();
width += insets.left + insets.right;
height += insets.top + insets.bottom;
// minimum width
if( !isTopLevelMenu ) {
int minimumWidth = FlatUIUtils.minimumWidth( menuItem, this.minimumWidth );
width = Math.max( width, scale( minimumWidth ) );
}
return new Dimension( width, height );
}
private void layout( Rectangle viewRect, Rectangle iconRect, Rectangle textRect,
Rectangle accelRect, Rectangle arrowRect, Rectangle labelRect )
{
boolean isTopLevelMenu = isTopLevelMenu( menuItem );
// layout arrow
if( !isTopLevelMenu && arrowIcon != null ) {
arrowRect.width = arrowIcon.getIconWidth();
arrowRect.height = arrowIcon.getIconHeight();
} else
arrowRect.setSize( 0, 0 );
arrowRect.y = viewRect.y + centerOffset( viewRect.height, arrowRect.height );
// layout accelerator
String accelText = getAcceleratorText();
if( accelText != null ) {
FontMetrics accelFm = menuItem.getFontMetrics( acceleratorFont );
accelRect.width = SwingUtilities.computeStringWidth( accelFm, accelText );
accelRect.height = accelFm.getHeight();
accelRect.y = viewRect.y + centerOffset( viewRect.height, accelRect.height );
} else
accelRect.setBounds( 0, 0, 0, 0 );
// compute horizontal positions of accelerator and arrow
int accelArrowGap = !isTopLevelMenu ? scale( acceleratorArrowGap ) : 0;
if( menuItem.getComponentOrientation().isLeftToRight() ) {
// left-to-right
arrowRect.x = viewRect.x + viewRect.width - arrowRect.width;
accelRect.x = arrowRect.x - accelArrowGap - accelRect.width;
} else {
// right-to-left
arrowRect.x = viewRect.x;
accelRect.x = arrowRect.x + accelArrowGap + arrowRect.width;
}
// width of accelerator, arrow and gap
int accelArrowWidth = accelRect.width + arrowRect.width;
if( accelText != null )
accelArrowWidth += scale( !isTopLevelMenu ? textAcceleratorGap : menuItem.getIconTextGap() );
if( !isTopLevelMenu && arrowIcon != null ) {
if( accelText == null )
accelArrowWidth += scale( textNoAcceleratorGap );
accelArrowWidth += scale( acceleratorArrowGap );
}
// label rectangle is view rectangle subtracted by accelerator, arrow and gap
labelRect.setBounds( viewRect );
labelRect.width -= accelArrowWidth;
if( !menuItem.getComponentOrientation().isLeftToRight() )
labelRect.x += accelArrowWidth;
// layout icon and text
SwingUtilities.layoutCompoundLabel( menuItem,
menuItem.getFontMetrics( menuItem.getFont() ), menuItem.getText(), getIconForLayout(),
menuItem.getVerticalAlignment(), menuItem.getHorizontalAlignment(),
menuItem.getVerticalTextPosition(), menuItem.getHorizontalTextPosition(),
labelRect, iconRect, textRect, scale( menuItem.getIconTextGap() ) );
}
private static int centerOffset( int wh1, int wh2 ) {
return (wh1 / 2) - (wh2 / 2);
}
protected void paintMenuItem( Graphics g, Color selectionBackground, Color selectionForeground,
Color disabledForeground, Color acceleratorForeground, Color acceleratorSelectionForeground )
{
Rectangle viewRect = new Rectangle( menuItem.getWidth(), menuItem.getHeight() );
// subtract insets
Insets insets = menuItem.getInsets();
viewRect.x += insets.left;
viewRect.y += insets.top;
viewRect.width -= (insets.left + insets.right);
viewRect.height -= (insets.top + insets.bottom);
Rectangle iconRect = new Rectangle();
Rectangle textRect = new Rectangle();
Rectangle accelRect = new Rectangle();
Rectangle arrowRect = new Rectangle();
Rectangle labelRect = new Rectangle();
layout( viewRect, iconRect, textRect, accelRect, arrowRect, labelRect );
/*debug
g.setColor( Color.green ); g.drawRect( viewRect.x, viewRect.y, viewRect.width - 1, viewRect.height - 1 );
g.setColor( Color.red ); g.drawRect( labelRect.x, labelRect.y, labelRect.width - 1, labelRect.height - 1 );
g.setColor( Color.blue ); g.drawRect( iconRect.x, iconRect.y, iconRect.width - 1, iconRect.height - 1 );
g.setColor( Color.cyan ); g.drawRect( textRect.x, textRect.y, textRect.width - 1, textRect.height - 1 );
g.setColor( Color.magenta ); g.drawRect( accelRect.x, accelRect.y, accelRect.width - 1, accelRect.height - 1 );
g.setColor( Color.orange ); g.drawRect( arrowRect.x, arrowRect.y, arrowRect.width - 1, arrowRect.height - 1 );
debug*/
paintBackground( g, selectionBackground );
paintIcon( g, iconRect, getIconForPainting() );
paintText( g, textRect, menuItem.getText(), selectionForeground, disabledForeground );
paintAccelerator( g, accelRect, getAcceleratorText(), acceleratorForeground, acceleratorSelectionForeground, disabledForeground );
if( !isTopLevelMenu( menuItem ) )
paintArrowIcon( g, arrowRect, arrowIcon );
}
protected void paintBackground( Graphics g, Color selectionBackground ) {
boolean armedOrSelected = isArmedOrSelected( menuItem );
if( menuItem.isOpaque() || armedOrSelected ) {
int width = menuItem.getWidth();
int height = menuItem.getHeight();
// paint background
g.setColor( armedOrSelected
? (isUnderlineSelection() ? underlineSelectionBackground : selectionBackground)
: menuItem.getBackground() );
g.fillRect( 0, 0, width, height );
// paint underline
if( armedOrSelected && isUnderlineSelection() ) {
int underlineHeight = scale( underlineSelectionHeight );
g.setColor( underlineSelectionColor );
if( isTopLevelMenu( menuItem ) ) {
// paint underline at bottom
g.fillRect( 0, height - underlineHeight, width, underlineHeight );
} else if( menuItem.getComponentOrientation().isLeftToRight() ) {
// paint underline at left side
g.fillRect( 0, 0, underlineHeight, height );
} else {
// paint underline at right side
g.fillRect( width - underlineHeight, 0, underlineHeight, height );
}
}
}
}
protected void paintIcon( Graphics g, Rectangle iconRect, Icon icon ) {
// 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 ) {
Rectangle r = FlatUIUtils.addInsets( iconRect, scale( checkMargins ) );
g.setColor( isUnderlineSelection() ? underlineSelectionCheckBackground : checkBackground );
g.fillRect( r.x, r.y, r.width, r.height );
}
paintIcon( g, menuItem, icon, iconRect );
}
protected void paintText( Graphics g, Rectangle textRect, String text, Color selectionForeground, Color disabledForeground ) {
View htmlView = (View) menuItem.getClientProperty( BasicHTML.propertyKey );
if( htmlView != null ) {
paintHTMLText( g, menuItem, textRect, htmlView, isUnderlineSelection() ? null : selectionForeground );
return;
}
int mnemonicIndex = FlatLaf.isShowMnemonics() ? menuItem.getDisplayedMnemonicIndex() : -1;
Color foreground = menuItem.getForeground();
paintText( g, menuItem, textRect, text, mnemonicIndex, menuItem.getFont(),
foreground, isUnderlineSelection() ? foreground : selectionForeground, disabledForeground );
}
protected void paintAccelerator( Graphics g, Rectangle accelRect, String accelText,
Color foreground, Color selectionForeground, Color disabledForeground )
{
paintText( g, menuItem, accelRect, accelText, -1, acceleratorFont,
foreground, isUnderlineSelection() ? foreground : selectionForeground, disabledForeground );
}
protected void paintArrowIcon( Graphics g, Rectangle arrowRect, Icon arrowIcon ) {
paintIcon( g, menuItem, arrowIcon, arrowRect );
}
protected static void paintIcon( Graphics g, JMenuItem menuItem, Icon icon, Rectangle iconRect ) {
if( icon == null )
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 );
}
protected static void paintText( Graphics g, JMenuItem menuItem,
Rectangle textRect, String text, int mnemonicIndex, Font font,
Color foreground, Color selectionForeground, Color disabledForeground )
{
if( text == null || text.isEmpty() )
return;
FontMetrics fm = menuItem.getFontMetrics( font );
Font oldFont = g.getFont();
g.setFont( font );
g.setColor( !menuItem.isEnabled()
? disabledForeground
: (isArmedOrSelected( menuItem )
? selectionForeground
: foreground) );
FlatUIUtils.drawStringUnderlineCharAt( menuItem, g, text, mnemonicIndex,
textRect.x, textRect.y + fm.getAscent() );
g.setFont( oldFont );
}
protected static void paintHTMLText( Graphics g, JMenuItem menuItem,
Rectangle textRect, View htmlView, Color selectionForeground )
{
if( isArmedOrSelected( menuItem ) && selectionForeground != null )
g = new GraphicsProxyWithTextColor( (Graphics2D) g, selectionForeground );
htmlView.paint( g, textRect );
}
protected static boolean isArmedOrSelected( JMenuItem menuItem ) {
return menuItem.isArmed() || (menuItem instanceof JMenu && menuItem.isSelected());
}
protected static boolean isTopLevelMenu( JMenuItem menuItem ) {
return menuItem instanceof JMenu && ((JMenu)menuItem).isTopLevelMenu();
}
private boolean isUnderlineSelection() {
return "underline".equals( UIManager.getString( "MenuItem.selectionType" ) );
}
private Icon getIconForPainting() {
Icon icon = menuItem.getIcon();
if( icon == null && checkIcon != null && !isTopLevelMenu( menuItem ) )
return checkIcon;
if( icon == null )
return null;
if( !menuItem.isEnabled() )
return menuItem.getDisabledIcon();
if( menuItem.getModel().isPressed() && menuItem.isArmed() ) {
Icon pressedIcon = menuItem.getPressedIcon();
if( pressedIcon != null )
return pressedIcon;
}
return icon;
}
private Icon getIconForLayout() {
Icon icon = menuItem.getIcon();
if( isTopLevelMenu( menuItem ) )
return (icon != null) ? new MinSizeIcon( icon ) : null;
return new MinSizeIcon( (icon != null) ? icon : checkIcon );
}
private KeyStroke cachedAccelerator;
private String cachedAcceleratorText;
private String getAcceleratorText() {
KeyStroke accelerator = menuItem.getAccelerator();
if( accelerator == null )
return null;
if( accelerator == cachedAccelerator )
return cachedAcceleratorText;
StringBuilder buf = new StringBuilder();
int modifiers = accelerator.getModifiers();
if( modifiers != 0 )
buf.append( InputEvent.getModifiersExText( modifiers ) ).append( acceleratorDelimiter );
int keyCode = accelerator.getKeyCode();
if( keyCode != 0 )
buf.append( KeyEvent.getKeyText( keyCode ) );
else
buf.append( accelerator.getKeyChar() );
cachedAccelerator = accelerator;
cachedAcceleratorText = buf.toString();
return cachedAcceleratorText;
}
//---- class MinSizeIcon --------------------------------------------------
private class MinSizeIcon
implements Icon
{
private final Icon delegate;
MinSizeIcon( Icon delegate ) {
this.delegate = delegate;
}
@Override
public int getIconWidth() {
int iconWidth = (delegate != null) ? delegate.getIconWidth() : 0;
return Math.max( iconWidth, scale( minimumIconSize.width ) );
}
@Override
public int getIconHeight() {
int iconHeight = (delegate != null) ? delegate.getIconHeight() : 0;
return Math.max( iconHeight, scale( minimumIconSize.height ) );
}
@Override
public void paintIcon( Component c, Graphics g, int x, int y ) {
}
}
//---- class GraphicsProxyWithTextColor -----------------------------------
private static class GraphicsProxyWithTextColor
extends Graphics2DProxy
{
private final Color textColor;
GraphicsProxyWithTextColor( Graphics2D delegate, Color textColor ) {
super( delegate );
this.textColor = textColor;
}
@Override
public void drawString( String str, int x, int y ) {
Paint oldPaint = getPaint();
setPaint( textColor );
super.drawString( str, x, y );
setPaint( oldPaint );
}
@Override
public void drawString( String str, float x, float y ) {
Paint oldPaint = getPaint();
setPaint( textColor );
super.drawString( str, x, y );
setPaint( oldPaint );
}
@Override
public void drawString( AttributedCharacterIterator iterator, int x, int y ) {
Paint oldPaint = getPaint();
setPaint( textColor );
super.drawString( iterator, x, y );
setPaint( oldPaint );
}
@Override
public void drawString( AttributedCharacterIterator iterator, float x, float y ) {
Paint oldPaint = getPaint();
setPaint( textColor );
super.drawString( iterator, x, y );
setPaint( oldPaint );
}
@Override
public void drawChars( char[] data, int offset, int length, int x, int y ) {
Paint oldPaint = getPaint();
setPaint( textColor );
super.drawChars( data, offset, length, x, y );
setPaint( oldPaint );
}
}
}

View File

@@ -16,9 +16,11 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale; import java.awt.Dimension;
import java.beans.PropertyChangeListener; import java.awt.Graphics;
import javax.swing.Icon;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.LookAndFeel;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicMenuItemUI; import javax.swing.plaf.basic.BasicMenuItemUI;
@@ -27,29 +29,34 @@ import javax.swing.plaf.basic.BasicMenuItemUI;
* *
* <!-- BasicMenuItemUI --> * <!-- BasicMenuItemUI -->
* *
* @uiDefault MenuItem.font Font * @uiDefault MenuItem.font Font
* @uiDefault MenuItem.background Color * @uiDefault MenuItem.background Color
* @uiDefault MenuItem.foreground Color * @uiDefault MenuItem.foreground Color
* @uiDefault MenuItem.disabledForeground Color * @uiDefault MenuItem.disabledForeground Color
* @uiDefault MenuItem.selectionBackground Color * @uiDefault MenuItem.selectionBackground Color
* @uiDefault MenuItem.selectionForeground Color * @uiDefault MenuItem.selectionForeground Color
* @uiDefault MenuItem.acceleratorForeground Color * @uiDefault MenuItem.acceleratorForeground Color
* @uiDefault MenuItem.acceleratorSelectionForeground Color * @uiDefault MenuItem.acceleratorSelectionForeground Color
* @uiDefault MenuItem.acceleratorFont Font defaults to MenuItem.font * @uiDefault MenuItem.acceleratorFont Font defaults to MenuItem.font
* @uiDefault MenuItem.acceleratorDelimiter String * @uiDefault MenuItem.acceleratorDelimiter String
* @uiDefault MenuItem.border Border * @uiDefault MenuItem.border Border
* @uiDefault MenuItem.borderPainted boolean * @uiDefault MenuItem.borderPainted boolean
* @uiDefault MenuItem.margin Insets * @uiDefault MenuItem.margin Insets
* @uiDefault MenuItem.arrowIcon Icon * @uiDefault MenuItem.arrowIcon Icon
* @uiDefault MenuItem.checkIcon Icon * @uiDefault MenuItem.checkIcon Icon
* @uiDefault MenuItem.opaque boolean * @uiDefault MenuItem.opaque boolean
* @uiDefault MenuItem.evenHeight boolean *
* <!-- FlatMenuItemUI -->
*
* @uiDefault MenuItem.iconTextGap int
* *
* @author Karl Tauber * @author Karl Tauber
*/ */
public class FlatMenuItemUI public class FlatMenuItemUI
extends BasicMenuItemUI extends BasicMenuItemUI
{ {
private FlatMenuItemRenderer renderer;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
return new FlatMenuItemUI(); return new FlatMenuItemUI();
} }
@@ -58,20 +65,30 @@ public class FlatMenuItemUI
protected void installDefaults() { protected void installDefaults() {
super.installDefaults(); super.installDefaults();
// scale LookAndFeel.installProperty( menuItem, "iconTextGap", FlatUIUtils.getUIInt( "MenuItem.iconTextGap", 4 ) );
defaultTextIconGap = scale( defaultTextIconGap );
renderer = createRenderer();
} }
/**
* Scale defaultTextIconGap again if iconTextGap property has changed.
*/
@Override @Override
protected PropertyChangeListener createPropertyChangeListener( JComponent c ) { protected void uninstallDefaults() {
PropertyChangeListener superListener = super.createPropertyChangeListener( c ); super.uninstallDefaults();
return e -> {
superListener.propertyChange( e ); renderer = null;
if( e.getPropertyName() == "iconTextGap" ) }
defaultTextIconGap = scale( defaultTextIconGap );
}; protected FlatMenuItemRenderer createRenderer() {
return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter );
}
@Override
protected Dimension getPreferredMenuItemSize( JComponent c, Icon checkIcon, Icon arrowIcon, int defaultTextIconGap ) {
return renderer.getPreferredMenuItemSize();
}
@Override
public void paint( Graphics g, JComponent c ) {
renderer.paintMenuItem( g, selectionBackground, selectionForeground, disabledForeground,
acceleratorForeground, acceleratorSelectionForeground );
} }
} }

View File

@@ -16,9 +16,19 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale; import java.awt.Color;
import java.beans.PropertyChangeListener; import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import javax.swing.ButtonModel;
import javax.swing.Icon;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JMenu;
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.ComponentUI;
import javax.swing.plaf.basic.BasicMenuUI; import javax.swing.plaf.basic.BasicMenuUI;
@@ -27,32 +37,39 @@ import javax.swing.plaf.basic.BasicMenuUI;
* *
* <!-- BasicMenuUI --> * <!-- BasicMenuUI -->
* *
* @uiDefault Menu.font Font * @uiDefault Menu.font Font
* @uiDefault Menu.background Color * @uiDefault Menu.background Color
* @uiDefault Menu.foreground Color * @uiDefault Menu.foreground Color
* @uiDefault Menu.disabledForeground Color * @uiDefault Menu.disabledForeground Color
* @uiDefault Menu.selectionBackground Color * @uiDefault Menu.selectionBackground Color
* @uiDefault Menu.selectionForeground Color * @uiDefault Menu.selectionForeground Color
* @uiDefault Menu.acceleratorForeground Color * @uiDefault Menu.acceleratorForeground Color
* @uiDefault Menu.acceleratorSelectionForeground Color * @uiDefault Menu.acceleratorSelectionForeground Color
* @uiDefault MenuItem.acceleratorFont Font defaults to MenuItem.font * @uiDefault MenuItem.acceleratorFont Font defaults to MenuItem.font
* @uiDefault MenuItem.acceleratorDelimiter String * @uiDefault MenuItem.acceleratorDelimiter String
* @uiDefault Menu.border Border * @uiDefault Menu.border Border
* @uiDefault Menu.borderPainted boolean * @uiDefault Menu.borderPainted boolean
* @uiDefault Menu.margin Insets * @uiDefault Menu.margin Insets
* @uiDefault Menu.arrowIcon Icon * @uiDefault Menu.arrowIcon Icon
* @uiDefault Menu.checkIcon Icon * @uiDefault Menu.checkIcon Icon
* @uiDefault Menu.opaque boolean * @uiDefault Menu.opaque boolean
* @uiDefault Menu.evenHeight boolean * @uiDefault Menu.crossMenuMnemonic boolean default is false
* @uiDefault Menu.crossMenuMnemonic boolean default is false * @uiDefault Menu.useMenuBarBackgroundForTopLevel boolean default is false
* @uiDefault Menu.useMenuBarBackgroundForTopLevel boolean default is false * @uiDefault MenuBar.background Color used if Menu.useMenuBarBackgroundForTopLevel is true
* @uiDefault MenuBar.background Color used if Menu.useMenuBarBackgroundForTopLevel is true *
* <!-- FlatMenuUI -->
*
* @uiDefault MenuItem.iconTextGap int
* @uiDefault MenuBar.hoverBackground Color
* *
* @author Karl Tauber * @author Karl Tauber
*/ */
public class FlatMenuUI public class FlatMenuUI
extends BasicMenuUI extends BasicMenuUI
{ {
private Color hoverBackground;
private FlatMenuItemRenderer renderer;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
return new FlatMenuUI(); return new FlatMenuUI();
} }
@@ -61,20 +78,83 @@ public class FlatMenuUI
protected void installDefaults() { protected void installDefaults() {
super.installDefaults(); super.installDefaults();
// scale LookAndFeel.installProperty( menuItem, "iconTextGap", FlatUIUtils.getUIInt( "MenuItem.iconTextGap", 4 ) );
defaultTextIconGap = scale( defaultTextIconGap );
menuItem.setRolloverEnabled( true );
hoverBackground = UIManager.getColor( "MenuBar.hoverBackground" );
renderer = createRenderer();
} }
/**
* Scale defaultTextIconGap again if iconTextGap property has changed.
*/
@Override @Override
protected PropertyChangeListener createPropertyChangeListener( JComponent c ) { protected void uninstallDefaults() {
PropertyChangeListener superListener = super.createPropertyChangeListener( c ); super.uninstallDefaults();
return e -> {
superListener.propertyChange( e ); hoverBackground = null;
if( e.getPropertyName() == "iconTextGap" ) renderer = null;
defaultTextIconGap = scale( defaultTextIconGap ); }
protected FlatMenuItemRenderer createRenderer() {
return new FlatMenuRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter );
}
@Override
protected MouseInputListener createMouseInputListener( JComponent c ) {
return new BasicMenuUI.MouseInputHandler() {
@Override
public void mouseEntered( MouseEvent e ) {
super.mouseEntered( e );
rollover( e, true );
}
@Override
public void mouseExited( MouseEvent e ) {
super.mouseExited( e );
rollover( e, false );
}
private void rollover( MouseEvent e, boolean rollover ) {
JMenu menu = (JMenu) e.getSource();
if( menu.isTopLevelMenu() && menu.isRolloverEnabled() ) {
menu.getModel().setRollover( rollover );
menu.repaint();
}
}
}; };
} }
@Override
protected Dimension getPreferredMenuItemSize( JComponent c, Icon checkIcon, Icon arrowIcon, int defaultTextIconGap ) {
return renderer.getPreferredMenuItemSize();
}
@Override
public void paint( Graphics g, JComponent c ) {
renderer.paintMenuItem( g, selectionBackground, selectionForeground, disabledForeground,
acceleratorForeground, acceleratorSelectionForeground );
}
//---- class FlatMenuRenderer ---------------------------------------------
protected class FlatMenuRenderer
extends FlatMenuItemRenderer
{
protected FlatMenuRenderer( JMenuItem menuItem, Icon checkIcon, Icon arrowIcon,
Font acceleratorFont, String acceleratorDelimiter )
{
super( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter );
}
@Override
protected void paintBackground( Graphics g, Color selectionBackground ) {
ButtonModel model = menuItem.getModel();
if( model.isRollover() && !model.isArmed() && !model.isSelected() &&
model.isEnabled() && ((JMenu)menuItem).isTopLevelMenu() )
{
FlatUIUtils.setColor( g, hoverBackground, menuItem.getBackground() );
g.fillRect( 0, 0, menuItem.getWidth(), menuItem.getHeight() );
} else
super.paintBackground( g, selectionBackground );
}
}
} }

View File

@@ -35,20 +35,19 @@ import com.formdev.flatlaf.util.UIScale;
/** /**
* Provides the Flat LaF UI delegate for {@link javax.swing.JOptionPane}. * Provides the Flat LaF UI delegate for {@link javax.swing.JOptionPane}.
* *
* <!-- BasicOptionPaneUI -->
*
* @uiDefault OptionPane.font Font unused * @uiDefault OptionPane.font Font unused
* @uiDefault OptionPane.background Color * @uiDefault OptionPane.background Color
* @uiDefault OptionPane.foreground Color unused * @uiDefault OptionPane.foreground Color unused
* @uiDefault OptionPane.border Border * @uiDefault OptionPane.border Border
* @uiDefault OptionPane.messageAreaBorder Border * @uiDefault OptionPane.messageAreaBorder Border
* @uiDefault OptionPane.buttonAreaBorder Border * @uiDefault OptionPane.buttonAreaBorder Border
* @uiDefault OptionPane.messageForeground Color * @uiDefault OptionPane.messageForeground Color optional; defaults to Label.foreground
* @uiDefault OptionPane.messageFont Font * @uiDefault OptionPane.messageFont Font optional; defaults to Label.font
* @uiDefault OptionPane.buttonFont Font * @uiDefault OptionPane.buttonFont Font optional; defaults to Button.font
* *
* @uiDefault OptionPane.minimumSize Dimension * @uiDefault OptionPane.minimumSize Dimension
* @uiDefault OptionPane.maxCharactersPerLine int
* @uiDefault OptionPane.iconMessageGap int
* @uiDefault OptionPane.messagePadding int
* @uiDefault OptionPane.buttonPadding int * @uiDefault OptionPane.buttonPadding int
* @uiDefault OptionPane.buttonMinimumWidth int -1=disabled * @uiDefault OptionPane.buttonMinimumWidth int -1=disabled
* @uiDefault OptionPane.sameSizeButtons boolean if true, gives all buttons same size * @uiDefault OptionPane.sameSizeButtons boolean if true, gives all buttons same size
@@ -61,6 +60,25 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault OptionPane.questionIcon Icon * @uiDefault OptionPane.questionIcon Icon
* @uiDefault OptionPane.warningIcon Icon * @uiDefault OptionPane.warningIcon Icon
* *
* @uiDefault OptionPane.okButtonText String
* @uiDefault OptionPane.okButtonMnemonic String
* @uiDefault OptionPane.okIcon Icon
* @uiDefault OptionPane.cancelButtonText String
* @uiDefault OptionPane.cancelButtonMnemonic String
* @uiDefault OptionPane.cancelIcon Icon
* @uiDefault OptionPane.yesButtonText String
* @uiDefault OptionPane.yesButtonMnemonic String
* @uiDefault OptionPane.yesIcon Icon
* @uiDefault OptionPane.noButtonText String
* @uiDefault OptionPane.noButtonMnemonic String
* @uiDefault OptionPane.noIcon Icon
*
* <!-- FlatOptionPaneUI -->
*
* @uiDefault OptionPane.iconMessageGap int
* @uiDefault OptionPane.messagePadding int
* @uiDefault OptionPane.maxCharactersPerLine int
*
* @author Karl Tauber * @author Karl Tauber
*/ */
public class FlatOptionPaneUI public class FlatOptionPaneUI

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import javax.swing.JComponent;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicPanelUI;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JPanel}.
*
* <!-- BasicPanelUI -->
*
* @uiDefault Panel.font Font unused
* @uiDefault Panel.background Color only used if opaque
* @uiDefault Panel.foreground Color
* @uiDefault Panel.border Border
*
* @author Karl Tauber
*/
public class FlatPanelUI
extends BasicPanelUI
{
private static ComponentUI instance;
public static ComponentUI createUI( JComponent c ) {
if( instance == null )
instance = new FlatPanelUI();
return instance;
}
}

View File

@@ -17,15 +17,24 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale; import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Color;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.event.FocusListener; import java.awt.event.FocusListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.beans.PropertyChangeEvent;
import javax.swing.Icon;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicPasswordFieldUI; import javax.swing.plaf.basic.BasicPasswordFieldUI;
import com.formdev.flatlaf.util.SystemInfo; import javax.swing.text.Caret;
import javax.swing.text.JTextComponent;
import com.formdev.flatlaf.FlatClientProperties;
/** /**
* Provides the Flat LaF UI delegate for {@link javax.swing.JPasswordField}. * Provides the Flat LaF UI delegate for {@link javax.swing.JPasswordField}.
@@ -48,20 +57,28 @@ import com.formdev.flatlaf.util.SystemInfo;
* *
* <!-- FlatPasswordFieldUI --> * <!-- FlatPasswordFieldUI -->
* *
* @uiDefault TextComponent.arc int
* @uiDefault Component.focusWidth int * @uiDefault Component.focusWidth int
* @uiDefault Component.minimumWidth int * @uiDefault Component.minimumWidth int
* @uiDefault Component.isIntelliJTheme boolean * @uiDefault Component.isIntelliJTheme boolean
* @uiDefault PasswordField.placeholderForeground Color
* @uiDefault PasswordField.capsLockIcon Icon
* @uiDefault TextComponent.selectAllOnFocusPolicy String never, once (default) or always
* *
* @author Karl Tauber * @author Karl Tauber
*/ */
public class FlatPasswordFieldUI public class FlatPasswordFieldUI
extends BasicPasswordFieldUI extends BasicPasswordFieldUI
{ {
protected int arc;
protected int focusWidth; protected int focusWidth;
protected int minimumWidth; protected int minimumWidth;
protected boolean isIntelliJTheme; protected boolean isIntelliJTheme;
protected Color placeholderForeground;
protected Icon capsLockIcon;
private FocusListener focusListener; private FocusListener focusListener;
private KeyListener capsLockListener;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
return new FlatPasswordFieldUI(); return new FlatPasswordFieldUI();
@@ -71,13 +88,13 @@ public class FlatPasswordFieldUI
protected void installDefaults() { protected void installDefaults() {
super.installDefaults(); super.installDefaults();
// use other echoChar on Mac because the default is too large in SF font String prefix = getPropertyPrefix();
if( SystemInfo.IS_MAC ) arc = UIManager.getInt( "TextComponent.arc" );
LookAndFeel.installProperty( getComponent(), "echoChar", '\u2022' );
focusWidth = UIManager.getInt( "Component.focusWidth" ); focusWidth = UIManager.getInt( "Component.focusWidth" );
minimumWidth = UIManager.getInt( "Component.minimumWidth" ); minimumWidth = UIManager.getInt( "Component.minimumWidth" );
isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" ); isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" );
placeholderForeground = UIManager.getColor( prefix + ".placeholderForeground" );
capsLockIcon = UIManager.getIcon( "PasswordField.capsLockIcon" );
LookAndFeel.installProperty( getComponent(), "opaque", focusWidth == 0 ); LookAndFeel.installProperty( getComponent(), "opaque", focusWidth == 0 );
@@ -88,6 +105,9 @@ public class FlatPasswordFieldUI
protected void uninstallDefaults() { protected void uninstallDefaults() {
super.uninstallDefaults(); super.uninstallDefaults();
placeholderForeground = null;
capsLockIcon = null;
MigLayoutVisualPadding.uninstall( getComponent() ); MigLayoutVisualPadding.uninstall( getComponent() );
} }
@@ -96,7 +116,23 @@ public class FlatPasswordFieldUI
super.installListeners(); super.installListeners();
focusListener = new FlatUIUtils.RepaintFocusListener( getComponent() ); focusListener = new FlatUIUtils.RepaintFocusListener( getComponent() );
capsLockListener = new KeyAdapter() {
@Override
public void keyPressed( KeyEvent e ) {
repaint( e );
}
@Override
public void keyReleased( KeyEvent e ) {
repaint( e );
}
private void repaint( KeyEvent e ) {
if( e.getKeyCode() == KeyEvent.VK_CAPS_LOCK )
e.getComponent().repaint();
}
};
getComponent().addFocusListener( focusListener ); getComponent().addFocusListener( focusListener );
getComponent().addKeyListener( capsLockListener );
} }
@Override @Override
@@ -104,15 +140,43 @@ public class FlatPasswordFieldUI
super.uninstallListeners(); super.uninstallListeners();
getComponent().removeFocusListener( focusListener ); getComponent().removeFocusListener( focusListener );
getComponent().removeKeyListener( capsLockListener );
focusListener = null; focusListener = null;
capsLockListener = null;
}
@Override
protected Caret createCaret() {
return new FlatCaret( UIManager.getString( "TextComponent.selectAllOnFocusPolicy" ) );
}
@Override
protected void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e );
if( FlatClientProperties.PLACEHOLDER_TEXT.equals( e.getPropertyName() ) )
getComponent().repaint();
} }
@Override @Override
protected void paintSafely( Graphics g ) { protected void paintSafely( Graphics g ) {
FlatTextFieldUI.paintBackground( g, getComponent(), focusWidth, isIntelliJTheme ); FlatTextFieldUI.paintBackground( g, getComponent(), focusWidth, arc, isIntelliJTheme );
FlatTextFieldUI.paintPlaceholder( g, getComponent(), placeholderForeground );
paintCapsLock( g );
super.paintSafely( g ); super.paintSafely( g );
} }
protected void paintCapsLock( Graphics g ) {
JTextComponent c = getComponent();
if( !FlatUIUtils.isPermanentFocusOwner( c ) ||
!Toolkit.getDefaultToolkit().getLockingKeyState( KeyEvent.VK_CAPS_LOCK ) )
return;
int y = (c.getHeight() - capsLockIcon.getIconHeight()) / 2;
int x = c.getWidth() - capsLockIcon.getIconWidth() - y;
capsLockIcon.paintIcon( c, g, x, y );
}
@Override @Override
protected void paintBackground( Graphics g ) { protected void paintBackground( Graphics g ) {
// background is painted elsewhere // background is painted elsewhere
@@ -129,6 +193,7 @@ public class FlatPasswordFieldUI
} }
private Dimension applyMinimumWidth( Dimension size, JComponent c ) { private Dimension applyMinimumWidth( Dimension size, JComponent c ) {
int minimumWidth = FlatUIUtils.minimumWidth( getComponent(), this.minimumWidth );
int focusWidth = (c.getBorder() instanceof FlatBorder) ? this.focusWidth : 0; int focusWidth = (c.getBorder() instanceof FlatBorder) ? this.focusWidth : 0;
size.width = Math.max( size.width, scale( minimumWidth + (focusWidth * 2) ) ); size.width = Math.max( size.width, scale( minimumWidth + (focusWidth * 2) ) );
return size; return size;

View File

@@ -16,7 +16,12 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import java.awt.Component;
import java.awt.Container;
import java.awt.Insets;
import javax.swing.JScrollPane;
import javax.swing.UIManager; import javax.swing.UIManager;
import com.formdev.flatlaf.util.UIScale;
/** /**
* Border for {@link javax.swing.JPopupMenu}. * Border for {@link javax.swing.JPopupMenu}.
@@ -33,4 +38,18 @@ public class FlatPopupMenuBorder
super( UIManager.getInsets( "PopupMenu.borderInsets" ), super( UIManager.getInsets( "PopupMenu.borderInsets" ),
UIManager.getColor( "PopupMenu.borderColor" ) ); UIManager.getColor( "PopupMenu.borderColor" ) );
} }
@Override
public Insets getBorderInsets( Component c, Insets insets ) {
if( c instanceof Container &&
((Container)c).getComponentCount() > 0 &&
((Container)c).getComponent( 0 ) instanceof JScrollPane )
{
// e.g. for combobox popups
insets.left = insets.top = insets.right = insets.bottom = UIScale.scale( 1 );
return insets;
}
return super.getBorderInsets( c, insets );
}
} }

View File

@@ -19,6 +19,7 @@ package com.formdev.flatlaf.ui;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicPopupMenuUI; import javax.swing.plaf.basic.BasicPopupMenuUI;
import com.formdev.flatlaf.util.SystemInfo;
/** /**
* Provides the Flat LaF UI delegate for {@link javax.swing.JPopupMenu}. * Provides the Flat LaF UI delegate for {@link javax.swing.JPopupMenu}.
@@ -35,7 +36,28 @@ import javax.swing.plaf.basic.BasicPopupMenuUI;
public class FlatPopupMenuUI public class FlatPopupMenuUI
extends BasicPopupMenuUI extends BasicPopupMenuUI
{ {
private boolean oldLightWeightPopupEnabled;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
return new FlatPopupMenuUI(); return new FlatPopupMenuUI();
} }
@Override
public void installDefaults() {
super.installDefaults();
// use heavy-weight popups on macOS to get nice drop shadow from OS
if( SystemInfo.IS_MAC ) {
oldLightWeightPopupEnabled = popupMenu.isLightWeightPopupEnabled();
popupMenu.setLightWeightPopupEnabled( false );
}
}
@Override
protected void uninstallDefaults() {
super.uninstallDefaults();
if( SystemInfo.IS_MAC )
popupMenu.setLightWeightPopupEnabled( oldLightWeightPopupEnabled );
}
} }

View File

@@ -16,14 +16,19 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.FlatClientProperties.*;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Insets; import java.awt.Insets;
import java.awt.geom.Area;
import java.awt.geom.RoundRectangle2D; import java.awt.geom.RoundRectangle2D;
import java.beans.PropertyChangeListener;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JProgressBar; import javax.swing.JProgressBar;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicProgressBarUI; import javax.swing.plaf.basic.BasicProgressBarUI;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
@@ -44,11 +49,21 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault ProgressBar.repaintInterval int default is 50 milliseconds * @uiDefault ProgressBar.repaintInterval int default is 50 milliseconds
* @uiDefault ProgressBar.cycleTime int default is 3000 milliseconds * @uiDefault ProgressBar.cycleTime int default is 3000 milliseconds
* *
* <!-- FlatProgressBarUI -->
*
* @uiDefault ProgressBar.arc int
*
* @author Karl Tauber * @author Karl Tauber
*/ */
public class FlatProgressBarUI public class FlatProgressBarUI
extends BasicProgressBarUI extends BasicProgressBarUI
{ {
protected int arc;
protected Dimension horizontalSize;
protected Dimension verticalSize;
private PropertyChangeListener propertyChangeListener;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
return new FlatProgressBarUI(); return new FlatProgressBarUI();
} }
@@ -58,16 +73,61 @@ public class FlatProgressBarUI
super.installDefaults(); super.installDefaults();
LookAndFeel.installProperty( progressBar, "opaque", false ); LookAndFeel.installProperty( progressBar, "opaque", false );
arc = UIManager.getInt( "ProgressBar.arc" );
horizontalSize = UIManager.getDimension( "ProgressBar.horizontalSize" );
verticalSize = UIManager.getDimension( "ProgressBar.verticalSize" );
}
@Override
protected void installListeners() {
super.installListeners();
propertyChangeListener = e -> {
switch( e.getPropertyName() ) {
case PROGRESS_BAR_LARGE_HEIGHT:
case PROGRESS_BAR_SQUARE:
progressBar.revalidate();
progressBar.repaint();
break;
}
};
progressBar.addPropertyChangeListener( propertyChangeListener );
}
@Override
protected void uninstallListeners() {
super.uninstallListeners();
progressBar.removePropertyChangeListener( propertyChangeListener );
propertyChangeListener = null;
}
@Override
public Dimension getPreferredSize( JComponent c ) {
Dimension size = super.getPreferredSize( c );
if( progressBar.isStringPainted() || clientPropertyBoolean( c, PROGRESS_BAR_LARGE_HEIGHT, false ) ) {
// recalculate progress height/width to make it smaller
Insets insets = progressBar.getInsets();
FontMetrics fm = progressBar.getFontMetrics( progressBar.getFont() );
if( progressBar.getOrientation() == JProgressBar.HORIZONTAL )
size.height = Math.max( fm.getHeight() + insets.top + insets.bottom, getPreferredInnerHorizontal().height );
else
size.width = Math.max( fm.getHeight() + insets.left + insets.right, getPreferredInnerVertical().width );
}
return size;
} }
@Override @Override
protected Dimension getPreferredInnerHorizontal() { protected Dimension getPreferredInnerHorizontal() {
return UIScale.scale( super.getPreferredInnerHorizontal() ); return UIScale.scale( horizontalSize );
} }
@Override @Override
protected Dimension getPreferredInnerVertical() { protected Dimension getPreferredInnerVertical() {
return UIScale.scale( super.getPreferredInnerVertical() ); return UIScale.scale( verticalSize );
} }
@Override @Override
@@ -90,13 +150,16 @@ public class FlatProgressBarUI
return; return;
boolean horizontal = (progressBar.getOrientation() == JProgressBar.HORIZONTAL); boolean horizontal = (progressBar.getOrientation() == JProgressBar.HORIZONTAL);
int arc = horizontal ? height : width; int arc = clientPropertyBoolean( c, PROGRESS_BAR_SQUARE, false )
? 0
: Math.min( UIScale.scale( this.arc ), horizontal ? height : width );
FlatUIUtils.setRenderingHints( (Graphics2D) g ); FlatUIUtils.setRenderingHints( (Graphics2D) g );
// paint track // paint track
RoundRectangle2D.Float trackShape = new RoundRectangle2D.Float( x, y, width, height, arc, arc );
g.setColor( progressBar.getBackground() ); g.setColor( progressBar.getBackground() );
((Graphics2D)g).fill( new RoundRectangle2D.Float( x, y, width, height, arc, arc ) ); ((Graphics2D)g).fill( trackShape );
// paint progress // paint progress
if( progressBar.isIndeterminate() ) { if( progressBar.isIndeterminate() ) {
@@ -112,14 +175,37 @@ public class FlatProgressBarUI
} else { } else {
int amountFull = getAmountFull( insets, width, height ); int amountFull = getAmountFull( insets, width, height );
g.setColor( progressBar.getForeground() ); RoundRectangle2D.Float progressShape = horizontal
((Graphics2D)g).fill( horizontal
? new RoundRectangle2D.Float( c.getComponentOrientation().isLeftToRight() ? x : x + (width - amountFull), ? new RoundRectangle2D.Float( c.getComponentOrientation().isLeftToRight() ? x : x + (width - amountFull),
y, amountFull, height, arc, arc ) y, amountFull, height, arc, arc )
: new RoundRectangle2D.Float( x, y + (height - amountFull), width, amountFull, arc, arc ) ); : new RoundRectangle2D.Float( x, y + (height - amountFull), width, amountFull, arc, arc );
g.setColor( progressBar.getForeground() );
if( amountFull < (horizontal ? height : width) ) {
// special painting for low amounts to avoid painting outside of track
Area area = new Area( trackShape );
area.intersect( new Area( progressShape ) );
((Graphics2D)g).fill( area );
} else
((Graphics2D)g).fill( progressShape );
if( progressBar.isStringPainted() ) if( progressBar.isStringPainted() )
paintString( g, x, y, width, height, amountFull, insets ); paintString( g, x, y, width, height, amountFull, insets );
} }
} }
@Override
protected void setAnimationIndex( int newValue ) {
super.setAnimationIndex( newValue );
// On HiDPI screens at 125%, 150% and 175% scaling, it occurs that antialiased painting
// may paint one pixel outside of the clipping area. This results in visual artifacts
// in indeterminate mode when the progress moves around.
// Unfortunately it is not safe to invoke getBox() from here (may throw NPE),
// which makes it impractical to get progress box and repaint increased box.
// Only solution is to repaint whole progress bar.
double systemScaleFactor = UIScale.getSystemScaleFactor( progressBar.getGraphicsConfiguration() );
if( (int) systemScaleFactor != systemScaleFactor )
progressBar.repaint();
}
} }

View File

@@ -16,9 +16,11 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale; import java.awt.Dimension;
import java.beans.PropertyChangeListener; import java.awt.Graphics;
import javax.swing.Icon;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.LookAndFeel;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicRadioButtonMenuItemUI; import javax.swing.plaf.basic.BasicRadioButtonMenuItemUI;
@@ -43,13 +45,18 @@ import javax.swing.plaf.basic.BasicRadioButtonMenuItemUI;
* @uiDefault RadioButtonMenuItem.arrowIcon Icon * @uiDefault RadioButtonMenuItem.arrowIcon Icon
* @uiDefault RadioButtonMenuItem.checkIcon Icon * @uiDefault RadioButtonMenuItem.checkIcon Icon
* @uiDefault RadioButtonMenuItem.opaque boolean * @uiDefault RadioButtonMenuItem.opaque boolean
* @uiDefault RadioButtonMenuItem.evenHeight boolean *
* <!-- FlatRadioButtonMenuItemUI -->
*
* @uiDefault MenuItem.iconTextGap int
* *
* @author Karl Tauber * @author Karl Tauber
*/ */
public class FlatRadioButtonMenuItemUI public class FlatRadioButtonMenuItemUI
extends BasicRadioButtonMenuItemUI extends BasicRadioButtonMenuItemUI
{ {
private FlatMenuItemRenderer renderer;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
return new FlatRadioButtonMenuItemUI(); return new FlatRadioButtonMenuItemUI();
} }
@@ -58,20 +65,30 @@ public class FlatRadioButtonMenuItemUI
protected void installDefaults() { protected void installDefaults() {
super.installDefaults(); super.installDefaults();
// scale LookAndFeel.installProperty( menuItem, "iconTextGap", FlatUIUtils.getUIInt( "MenuItem.iconTextGap", 4 ) );
defaultTextIconGap = scale( defaultTextIconGap );
renderer = createRenderer();
} }
/**
* Scale defaultTextIconGap again if iconTextGap property has changed.
*/
@Override @Override
protected PropertyChangeListener createPropertyChangeListener( JComponent c ) { protected void uninstallDefaults() {
PropertyChangeListener superListener = super.createPropertyChangeListener( c ); super.uninstallDefaults();
return e -> {
superListener.propertyChange( e ); renderer = null;
if( e.getPropertyName() == "iconTextGap" ) }
defaultTextIconGap = scale( defaultTextIconGap );
}; protected FlatMenuItemRenderer createRenderer() {
return new FlatMenuItemRenderer( menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter );
}
@Override
protected Dimension getPreferredMenuItemSize( JComponent c, Icon checkIcon, Icon arrowIcon, int defaultTextIconGap ) {
return renderer.getPreferredMenuItemSize();
}
@Override
public void paint( Graphics g, JComponent c ) {
renderer.paintMenuItem( g, selectionBackground, selectionForeground, disabledForeground,
acceleratorForeground, acceleratorSelectionForeground );
} }
} }

View File

@@ -18,7 +18,9 @@ package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale; import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Color; import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle; import java.awt.Rectangle;
import javax.swing.AbstractButton; import javax.swing.AbstractButton;
import javax.swing.CellRendererPane; import javax.swing.CellRendererPane;
@@ -28,6 +30,8 @@ import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource; import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicRadioButtonUI; import javax.swing.plaf.basic.BasicRadioButtonUI;
import com.formdev.flatlaf.icons.FlatCheckBoxIcon;
import com.formdev.flatlaf.util.UIScale;
/** /**
* Provides the Flat LaF UI delegate for {@link javax.swing.JRadioButton}. * Provides the Flat LaF UI delegate for {@link javax.swing.JRadioButton}.
@@ -92,18 +96,65 @@ public class FlatRadioButtonUI
defaults_initialized = false; defaults_initialized = false;
} }
private static Insets tempInsets = new Insets( 0, 0, 0, 0 );
@Override
public Dimension getPreferredSize( JComponent c ) {
Dimension size = super.getPreferredSize( c );
if( size == null )
return null;
// small insets fix
int focusWidth = getIconFocusWidth( c );
if( focusWidth > 0 ) {
// Increase preferred width and height if insets were explicitly reduced (e.g. with
// an EmptyBorder) and icon has a focus width, which is not included in icon size.
// Otherwise the component may be too small and outer focus border may be cut off.
Insets insets = c.getInsets( tempInsets );
size.width += Math.max( focusWidth - insets.left, 0 ) + Math.max( focusWidth - insets.right, 0 );
size.height += Math.max( focusWidth - insets.top, 0 ) + Math.max( focusWidth - insets.bottom, 0 );
}
return size;
}
@Override @Override
public void paint( Graphics g, JComponent c ) { public void paint( Graphics g, JComponent c ) {
// fill background even if opaque if // fill background even if not opaque if
// - used as cell renderer (because of selection background) // - contentAreaFilled is true and
// - if background was explicitly set to a non-UIResource color // - used as cell renderer (because of selection background)
// - or if background was explicitly set to a non-UIResource color
if( !c.isOpaque() && if( !c.isOpaque() &&
(c.getParent() instanceof CellRendererPane || !(c.getBackground() instanceof UIResource)) ) ((AbstractButton)c).isContentAreaFilled() &&
(c.getParent() instanceof CellRendererPane || !(c.getBackground() instanceof UIResource)))
{ {
g.setColor( c.getBackground() ); g.setColor( c.getBackground() );
g.fillRect( 0, 0, c.getWidth(), c.getHeight() ); g.fillRect( 0, 0, c.getWidth(), c.getHeight() );
} }
// small insets fix
int focusWidth = getIconFocusWidth( c );
if( focusWidth > 0 ) {
boolean ltr = c.getComponentOrientation().isLeftToRight();
Insets insets = c.getInsets( tempInsets );
int leftOrRightInset = ltr ? insets.left : insets.right;
if( focusWidth > leftOrRightInset ) {
// The left (or right) inset is smaller than the focus width, which may be
// the case if insets were explicitly reduced (e.g. with an EmptyBorder).
// In this case the width has been increased in getPreferredSize() and
// here it is necessary to fix icon and text painting location.
int offset = focusWidth - leftOrRightInset;
if( !ltr )
offset = -offset;
// move the graphics origin to the left (or right)
g.translate( offset, 0 );
super.paint( g, c );
g.translate( -offset, 0 );
return;
}
}
super.paint( g, c ); super.paint( g, c );
} }
@@ -111,4 +162,11 @@ public class FlatRadioButtonUI
protected void paintText( Graphics g, AbstractButton b, Rectangle textRect, String text ) { protected void paintText( Graphics g, AbstractButton b, Rectangle textRect, String text ) {
FlatButtonUI.paintText( g, b, textRect, text, b.isEnabled() ? b.getForeground() : disabledText ); FlatButtonUI.paintText( g, b, textRect, text, b.isEnabled() ? b.getForeground() : disabledText );
} }
private int getIconFocusWidth( JComponent c ) {
AbstractButton b = (AbstractButton) c;
return (b.getIcon() == null && getDefaultIcon() instanceof FlatCheckBoxIcon)
? UIScale.scale( ((FlatCheckBoxIcon)getDefaultIcon()).focusWidth )
: 0;
}
} }

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import javax.swing.JComponent;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicRootPaneUI;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JRootPane}.
*
* @author Karl Tauber
*/
public class FlatRootPaneUI
extends BasicRootPaneUI
{
private static ComponentUI instance;
public static ComponentUI createUI( JComponent c ) {
if( instance == null )
instance = new FlatRootPaneUI();
return instance;
}
}

View File

@@ -17,6 +17,7 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale; import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Component;
import javax.swing.UIManager; import javax.swing.UIManager;
/** /**
@@ -32,7 +33,7 @@ public class FlatRoundBorder
protected final int arc = UIManager.getInt( "Component.arc" ); protected final int arc = UIManager.getInt( "Component.arc" );
@Override @Override
protected float getArc() { protected float getArc( Component c ) {
return scale( (float) arc ); return scale( (float) arc );
} }
} }

View File

@@ -22,11 +22,18 @@ import java.awt.Graphics;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.event.MouseAdapter; import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Objects;
import javax.swing.InputMap;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicScrollBarUI; import javax.swing.plaf.basic.BasicScrollBarUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
/** /**
@@ -38,13 +45,20 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault ScrollBar.foreground Color * @uiDefault ScrollBar.foreground Color
* @uiDefault ScrollBar.track Color * @uiDefault ScrollBar.track Color
* @uiDefault ScrollBar.thumb Color * @uiDefault ScrollBar.thumb Color
* @uiDefault ScrollBar.hoverTrackColor Color
* @uiDefault ScrollBar.hoverThumbColor Color
* @uiDefault ScrollBar.width int * @uiDefault ScrollBar.width int
* @uiDefault ScrollBar.minimumThumbSize Dimension * @uiDefault ScrollBar.minimumThumbSize Dimension
* @uiDefault ScrollBar.maximumThumbSize Dimension * @uiDefault ScrollBar.maximumThumbSize Dimension
* @uiDefault ScrollBar.allowsAbsolutePositioning boolean * @uiDefault ScrollBar.allowsAbsolutePositioning boolean
* *
* <!-- FlatScrollBarUI -->
*
* @uiDefault ScrollBar.hoverTrackColor Color
* @uiDefault ScrollBar.hoverThumbColor Color
* @uiDefault Component.arrowType String triangle (default) or chevron
* @uiDefault ScrollBar.showButtons boolean
* @uiDefault ScrollBar.buttonArrowColor Color
* @uiDefault ScrollBar.buttonDisabledArrowColor Color
*
* @author Karl Tauber * @author Karl Tauber
*/ */
public class FlatScrollBarUI public class FlatScrollBarUI
@@ -53,6 +67,11 @@ public class FlatScrollBarUI
protected Color hoverTrackColor; protected Color hoverTrackColor;
protected Color hoverThumbColor; protected Color hoverThumbColor;
protected boolean showButtons;
protected String arrowType;
protected Color buttonArrowColor;
protected Color buttonDisabledArrowColor;
private MouseAdapter hoverListener; private MouseAdapter hoverListener;
private boolean hoverTrack; private boolean hoverTrack;
private boolean hoverThumb; private boolean hoverThumb;
@@ -85,6 +104,11 @@ public class FlatScrollBarUI
hoverTrackColor = UIManager.getColor( "ScrollBar.hoverTrackColor" ); hoverTrackColor = UIManager.getColor( "ScrollBar.hoverTrackColor" );
hoverThumbColor = UIManager.getColor( "ScrollBar.hoverThumbColor" ); hoverThumbColor = UIManager.getColor( "ScrollBar.hoverThumbColor" );
showButtons = UIManager.getBoolean( "ScrollBar.showButtons" );
arrowType = UIManager.getString( "Component.arrowType" );
buttonArrowColor = UIManager.getColor( "ScrollBar.buttonArrowColor" );
buttonDisabledArrowColor = UIManager.getColor( "ScrollBar.buttonDisabledArrowColor" );
} }
@Override @Override
@@ -93,6 +117,39 @@ public class FlatScrollBarUI
hoverTrackColor = null; hoverTrackColor = null;
hoverThumbColor = null; hoverThumbColor = null;
buttonArrowColor = null;
buttonDisabledArrowColor = null;
}
@Override
protected PropertyChangeListener createPropertyChangeListener() {
return new BasicScrollBarUI.PropertyChangeHandler() {
@Override
public void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e );
switch( e.getPropertyName() ) {
case FlatClientProperties.SCROLL_BAR_SHOW_BUTTONS:
scrollbar.revalidate();
scrollbar.repaint();
break;
case "componentOrientation":
// this is missing in BasicScrollBarUI.Handler.propertyChange()
InputMap inputMap = (InputMap) UIManager.get( "ScrollBar.ancestorInputMap" );
if( !scrollbar.getComponentOrientation().isLeftToRight() ) {
InputMap rtlInputMap = (InputMap) UIManager.get( "ScrollBar.ancestorInputMap.RightToLeft" );
if( rtlInputMap != null ) {
rtlInputMap.setParent( inputMap );
inputMap = rtlInputMap;
}
}
SwingUtilities.replaceUIInputMap( scrollbar, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, inputMap );
break;
}
}
};
} }
@Override @Override
@@ -102,24 +159,50 @@ public class FlatScrollBarUI
@Override @Override
protected JButton createDecreaseButton( int orientation ) { protected JButton createDecreaseButton( int orientation ) {
return createInvisibleButton(); return createArrowButton( orientation );
} }
@Override @Override
protected JButton createIncreaseButton( int orientation ) { protected JButton createIncreaseButton( int orientation ) {
return createInvisibleButton(); return createArrowButton( orientation );
} }
private JButton createInvisibleButton() { private JButton createArrowButton( int orientation ) {
JButton button = new JButton(); FlatArrowButton button = new FlatArrowButton( orientation,
button.setMinimumSize( new Dimension() ); arrowType, buttonArrowColor, buttonDisabledArrowColor, null, hoverTrackColor )
button.setMaximumSize( new Dimension() ); {
button.setPreferredSize( new Dimension() ); @Override
public Dimension getPreferredSize() {
if( isShowButtons() ) {
int w = UIScale.scale( scrollBarWidth );
return new Dimension( w, w );
} else
return new Dimension();
}
@Override
public Dimension getMinimumSize() {
return isShowButtons() ? super.getMinimumSize() : new Dimension();
}
@Override
public Dimension getMaximumSize() {
return isShowButtons() ? super.getMaximumSize() : new Dimension();
}
};
button.setArrowWidth( FlatArrowButton.DEFAULT_ARROW_WIDTH - 2 );
button.setFocusable( false ); button.setFocusable( false );
button.setRequestFocusEnabled( false ); button.setRequestFocusEnabled( false );
return button; return button;
} }
private boolean isShowButtons() {
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 );
return (showButtons != null) ? Objects.equals( showButtons, true ) : this.showButtons;
}
@Override @Override
protected void paintDecreaseHighlight( Graphics g ) { protected void paintDecreaseHighlight( Graphics g ) {
// do not paint // do not paint

View File

@@ -14,23 +14,41 @@
* limitations under the License. * limitations under the License.
*/ */
/*
* Smooth scrolling code partly based on code from IntelliJ IDEA Community Edition,
* which is licensed under the Apache 2.0 license. Copyright 2000-2016 JetBrains s.r.o.
* See: https://github.com/JetBrains/intellij-community/blob/31e1b5a8e43219b9571951bab6457cfb3012e3ef/platform/platform-api/src/com/intellij/ui/components/SmoothScrollPane.java#L141-L185
*
*/
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import java.awt.Component; import java.awt.Component;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Insets; import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ContainerEvent; import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener; import java.awt.event.ContainerListener;
import java.awt.event.FocusEvent; import java.awt.event.FocusEvent;
import java.awt.event.FocusListener; import java.awt.event.FocusListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane; import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JViewport; import javax.swing.JViewport;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
import javax.swing.ScrollPaneConstants;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicScrollPaneUI; import javax.swing.plaf.basic.BasicScrollPaneUI;
import com.formdev.flatlaf.FlatClientProperties;
/** /**
* Provides the Flat LaF UI delegate for {@link javax.swing.JScrollPane}. * Provides the Flat LaF UI delegate for {@link javax.swing.JScrollPane}.
@@ -43,6 +61,10 @@ import javax.swing.plaf.basic.BasicScrollPaneUI;
* @uiDefault ScrollPane.border Border * @uiDefault ScrollPane.border Border
* @uiDefault ScrollPane.viewportBorder Border * @uiDefault ScrollPane.viewportBorder Border
* *
* <!-- FlatScrollPaneUI -->
*
* @uiDefault ScrollPane.smoothScrolling boolean
*
* @author Karl Tauber * @author Karl Tauber
*/ */
public class FlatScrollPaneUI public class FlatScrollPaneUI
@@ -87,7 +109,172 @@ public class FlatScrollPaneUI
handler = null; handler = null;
} }
public Handler getHandler() { @Override
protected MouseWheelListener createMouseWheelListener() {
return new BasicScrollPaneUI.MouseWheelHandler() {
@Override
public void mouseWheelMoved( MouseWheelEvent e ) {
// Note: Getting UI value "ScrollPane.smoothScrolling" here to allow
// applications to turn smooth scrolling on or off at any time
// (e.g. in application options dialog).
if( UIManager.getBoolean( "ScrollPane.smoothScrolling" ) &&
scrollpane.isWheelScrollingEnabled() &&
e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL &&
e.getPreciseWheelRotation() != 0 &&
e.getPreciseWheelRotation() != e.getWheelRotation() )
{
mouseWheelMovedSmooth( e );
} else
super.mouseWheelMoved( e );
}
};
}
private static final double EPSILON = 1e-5d;
private void mouseWheelMovedSmooth( MouseWheelEvent e ) {
// return if there is no viewport
JViewport viewport = scrollpane.getViewport();
if( viewport == null )
return;
// find scrollbar to scroll
JScrollBar scrollbar = scrollpane.getVerticalScrollBar();
if( scrollbar == null || !scrollbar.isVisible() || e.isShiftDown() ) {
scrollbar = scrollpane.getHorizontalScrollBar();
if( scrollbar == null || !scrollbar.isVisible() )
return;
}
// consume event
e.consume();
// get precise wheel rotation
double rotation = e.getPreciseWheelRotation();
// get unit and block increment
int unitIncrement;
int blockIncrement;
int orientation = scrollbar.getOrientation();
Component view = viewport.getView();
if( view instanceof Scrollable ) {
Scrollable scrollable = (Scrollable) view;
// Use (0, 0) view position to obtain constant unit increment of first item
// (which might otherwise be variable on smaller-than-unit scrolling).
Rectangle visibleRect = new Rectangle( viewport.getViewSize() );
unitIncrement = scrollable.getScrollableUnitIncrement( visibleRect, orientation, 1 );
blockIncrement = scrollable.getScrollableBlockIncrement( visibleRect, orientation, 1 );
if( unitIncrement > 0 ) {
// For the case that the first item (e.g. in a list) is larger
// than the other items, get the unit increment of the second item
// and use the smaller one.
if( orientation == SwingConstants.VERTICAL ) {
visibleRect.y += unitIncrement;
visibleRect.height -= unitIncrement;
} else {
visibleRect.x += unitIncrement;
visibleRect.width -= unitIncrement;
}
int unitIncrement2 = scrollable.getScrollableUnitIncrement( visibleRect, orientation, 1 );
if( unitIncrement2 > 0 )
unitIncrement = Math.min( unitIncrement, unitIncrement2 );
}
} else {
int direction = rotation < 0 ? -1 : 1;
unitIncrement = scrollbar.getUnitIncrement( direction );
blockIncrement = scrollbar.getBlockIncrement( direction );
}
// limit scroll amount (number of units to scroll) for small viewports
// (e.g. vertical scrolling in file chooser)
int scrollAmount = e.getScrollAmount();
int viewportWH = (orientation == SwingConstants.VERTICAL)
? viewport.getHeight()
: viewport.getWidth();
if( unitIncrement * scrollAmount > viewportWH )
scrollAmount = Math.max( viewportWH / unitIncrement, 1 );
// compute relative delta
double delta = rotation * scrollAmount * unitIncrement;
boolean adjustDelta = Math.abs( rotation ) < (1.0 + EPSILON);
double adjustedDelta = adjustDelta
? Math.max( -blockIncrement, Math.min( delta, blockIncrement ) )
: delta;
// compute new value
int value = scrollbar.getValue();
double minDelta = scrollbar.getMinimum() - value;
double maxDelta = scrollbar.getMaximum() - scrollbar.getModel().getExtent() - value;
double boundedDelta = Math.max( minDelta, Math.min( adjustedDelta, maxDelta ) );
int newValue = value + (int) Math.round( boundedDelta );
// set new value
if( newValue != value )
scrollbar.setValue( newValue );
/*debug
System.out.println( String.format( "%4d %9f / %4d %4d / %12f %5s %12f / %4d %4d %4d / %12f %12f %12f / %4d",
e.getWheelRotation(),
e.getPreciseWheelRotation(),
unitIncrement,
blockIncrement,
delta,
adjustDelta,
adjustedDelta,
value,
scrollbar.getMinimum(),
scrollbar.getMaximum(),
minDelta,
maxDelta,
boundedDelta,
newValue ) );
*/
}
@Override
protected PropertyChangeListener createPropertyChangeListener() {
return new BasicScrollPaneUI.PropertyChangeHandler() {
@Override
public void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e );
switch( e.getPropertyName() ) {
case FlatClientProperties.SCROLL_BAR_SHOW_BUTTONS:
JScrollBar vsb = scrollpane.getVerticalScrollBar();
JScrollBar hsb = scrollpane.getHorizontalScrollBar();
if( vsb != null ) {
vsb.revalidate();
vsb.repaint();
}
if( hsb != null ) {
hsb.revalidate();
hsb.repaint();
}
break;
case ScrollPaneConstants.LOWER_LEFT_CORNER:
case ScrollPaneConstants.LOWER_RIGHT_CORNER:
case ScrollPaneConstants.UPPER_LEFT_CORNER:
case ScrollPaneConstants.UPPER_RIGHT_CORNER:
// remove border from buttons added to corners
Object corner = e.getNewValue();
if( corner instanceof JButton &&
((JButton)corner).getBorder() instanceof FlatButtonBorder &&
scrollpane.getViewport() != null &&
scrollpane.getViewport().getView() instanceof JTable )
{
((JButton)corner).setBorder( BorderFactory.createEmptyBorder() );
((JButton)corner).setFocusable( false );
}
break;
}
}
};
}
private Handler getHandler() {
if( handler == null ) if( handler == null )
handler = new Handler(); handler = new Handler();
return handler; return handler;

View File

@@ -201,7 +201,7 @@ public class FlatSliderUI
} }
if( coloredTrack != null ) { if( coloredTrack != null ) {
FlatUIUtils.setColor( g, slider.hasFocus() ? focusColor : (hover ? hoverColor : thumbColor), thumbColor ); FlatUIUtils.setColor( g, FlatUIUtils.isPermanentFocusOwner( slider ) ? focusColor : (hover ? hoverColor : thumbColor), thumbColor );
((Graphics2D)g).fill( coloredTrack ); ((Graphics2D)g).fill( coloredTrack );
} }
@@ -212,7 +212,7 @@ public class FlatSliderUI
@Override @Override
public void paintThumb( Graphics g ) { public void paintThumb( Graphics g ) {
FlatUIUtils.setColor( g, slider.isEnabled() FlatUIUtils.setColor( g, slider.isEnabled()
? (slider.hasFocus() ? focusColor : (hover ? hoverColor : thumbColor)) ? (FlatUIUtils.isPermanentFocusOwner( slider ) ? focusColor : (hover ? hoverColor : thumbColor))
: disabledForeground, : disabledForeground,
thumbColor ); thumbColor );

View File

@@ -159,7 +159,7 @@ public class FlatSpinnerUI
handler = null; handler = null;
} }
public Handler getHandler() { private Handler getHandler() {
if( handler == null ) if( handler == null )
handler = new Handler(); handler = new Handler();
return handler; return handler;
@@ -267,7 +267,7 @@ public class FlatSpinnerUI
g2.setColor( enabled g2.setColor( enabled
? c.getBackground() ? c.getBackground()
: (isIntelliJTheme ? FlatUIUtils.getParentBackground( c ) : disabledBackground) ); : (isIntelliJTheme ? FlatUIUtils.getParentBackground( c ) : disabledBackground) );
FlatUIUtils.fillRoundRectangle( g2, 0, 0, width, height, focusWidth, arc ); FlatUIUtils.paintComponentBackground( g2, 0, 0, width, height, focusWidth, arc );
// paint arrow buttons background // paint arrow buttons background
if( enabled ) { if( enabled ) {
@@ -277,7 +277,7 @@ public class FlatSpinnerUI
g2.clipRect( arrowX, 0, width - arrowX, height ); g2.clipRect( arrowX, 0, width - arrowX, height );
else else
g2.clipRect( 0, 0, arrowX + arrowWidth, height ); g2.clipRect( 0, 0, arrowX + arrowWidth, height );
FlatUIUtils.fillRoundRectangle( g2, 0, 0, width, height, focusWidth, arc ); FlatUIUtils.paintComponentBackground( g2, 0, 0, width, height, focusWidth, arc );
g2.setClip( oldClip ); g2.setClip( oldClip );
} }
@@ -285,7 +285,7 @@ public class FlatSpinnerUI
g2.setColor( enabled ? borderColor : disabledBorderColor ); g2.setColor( enabled ? borderColor : disabledBorderColor );
float lw = scale( 1f ); float lw = scale( 1f );
float lx = isLeftToRight ? arrowX : arrowX + arrowWidth - lw; float lx = isLeftToRight ? arrowX : arrowX + arrowWidth - lw;
g2.fill( new Rectangle2D.Float( lx, focusWidth, lw, height - (focusWidth * 2) ) ); g2.fill( new Rectangle2D.Float( lx, focusWidth, lw, height - 1 - (focusWidth * 2) ) );
paint( g, c ); paint( g, c );
} }
@@ -326,6 +326,7 @@ public class FlatSpinnerUI
Dimension editorSize = (editor != null) ? editor.getPreferredSize() : new Dimension( 0, 0 ); Dimension editorSize = (editor != null) ? editor.getPreferredSize() : new Dimension( 0, 0 );
// the arrows width is the same as the inner height so that the arrows area is square // the arrows width is the same as the inner height so that the arrows area is square
int minimumWidth = FlatUIUtils.minimumWidth( spinner, FlatSpinnerUI.this.minimumWidth );
int innerHeight = editorSize.height + padding.top + padding.bottom; int innerHeight = editorSize.height + padding.top + padding.bottom;
return new Dimension( return new Dimension(
Math.max( insets.left + insets.right + editorSize.width + padding.left + padding.right + innerHeight, scale( minimumWidth + (focusWidth * 2) ) ), Math.max( insets.left + insets.right + editorSize.width + padding.left + padding.right + innerHeight, scale( minimumWidth + (focusWidth * 2) ) ),

View File

@@ -26,45 +26,69 @@ import java.awt.FontMetrics;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Insets; import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.Shape; import java.awt.Shape;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.geom.Path2D; import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.util.Collections;
import java.util.Set;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JTabbedPane; import javax.swing.JTabbedPane;
import javax.swing.KeyStroke;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource; import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTabbedPaneUI; import javax.swing.plaf.basic.BasicTabbedPaneUI;
import javax.swing.text.View; import javax.swing.text.View;
import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.util.UIScale;
/** /**
* Provides the Flat LaF UI delegate for {@link javax.swing.JTabbedPane}. * Provides the Flat LaF UI delegate for {@link javax.swing.JTabbedPane}.
* *
* @clientProperty JTabbedPane.showTabSeparators boolean
* @clientProperty JTabbedPane.hasFullBorder boolean * @clientProperty JTabbedPane.hasFullBorder boolean
* *
* @uiDefault Component.arrowType String triangle (default) or chevron * <!-- BasicTabbedPaneUI -->
*
* @uiDefault TabbedPane.font Font * @uiDefault TabbedPane.font Font
* @uiDefault TabbedPane.background Color * @uiDefault TabbedPane.background Color
* @uiDefault TabbedPane.foreground Color * @uiDefault TabbedPane.foreground Color
* @uiDefault TabbedPane.shadow Color used for scroll arrows and cropped line * @uiDefault TabbedPane.shadow Color used for scroll arrows and cropped line
* @uiDefault TabbedPane.textIconGap int
* @uiDefault TabbedPane.tabInsets Insets
* @uiDefault TabbedPane.selectedTabPadInsets Insets
* @uiDefault TabbedPane.tabAreaInsets Insets
* @uiDefault TabbedPane.tabsOverlapBorder boolean
* @uiDefault TabbedPane.tabRunOverlay int
* @uiDefault TabbedPane.tabsOpaque boolean
* @uiDefault TabbedPane.contentOpaque boolean unused
* @uiDefault TabbedPane.opaque boolean
* @uiDefault TabbedPane.selectionFollowsFocus boolean default is true
*
* <!-- FlatTabbedPaneUI -->
*
* @uiDefault Component.arrowType String triangle (default) or chevron
* @uiDefault TabbedPane.disabledForeground Color * @uiDefault TabbedPane.disabledForeground Color
* @uiDefault TabbedPane.selectedBackground Color optional
* @uiDefault TabbedPane.selectedForeground Color * @uiDefault TabbedPane.selectedForeground Color
* @uiDefault TabbedPane.underlineColor Color * @uiDefault TabbedPane.underlineColor Color
* @uiDefault TabbedPane.disabledUnderlineColor Color * @uiDefault TabbedPane.disabledUnderlineColor Color
* @uiDefault TabbedPane.hoverColor Color * @uiDefault TabbedPane.hoverColor Color
* @uiDefault TabbedPane.focusColor Color * @uiDefault TabbedPane.focusColor Color
* @uiDefault TabbedPane.tabSeparatorColor Color optional; defaults to TabbedPane.contentAreaColor
* @uiDefault TabbedPane.contentAreaColor Color * @uiDefault TabbedPane.contentAreaColor Color
* @uiDefault TabbedPane.textIconGap int
* @uiDefault TabbedPane.tabInsets Insets
* @uiDefault TabbedPane.tabAreaInsets Insets
* @uiDefault TabbedPane.tabHeight int * @uiDefault TabbedPane.tabHeight int
* @uiDefault TabbedPane.tabSelectionHeight int * @uiDefault TabbedPane.tabSelectionHeight int
* @uiDefault TabbedPane.contentSeparatorHeight int * @uiDefault TabbedPane.contentSeparatorHeight int
* @uiDefault TabbedPane.showTabSeparators boolean
* @uiDefault TabbedPane.tabSeparatorsFullHeight boolean
* @uiDefault TabbedPane.hasFullBorder boolean * @uiDefault TabbedPane.hasFullBorder boolean
* *
* @author Karl Tauber * @author Karl Tauber
@@ -72,17 +96,24 @@ import com.formdev.flatlaf.FlatLaf;
public class FlatTabbedPaneUI public class FlatTabbedPaneUI
extends BasicTabbedPaneUI extends BasicTabbedPaneUI
{ {
private static Set<KeyStroke> focusForwardTraversalKeys;
private static Set<KeyStroke> focusBackwardTraversalKeys;
protected Color disabledForeground; protected Color disabledForeground;
protected Color selectedBackground;
protected Color selectedForeground; protected Color selectedForeground;
protected Color underlineColor; protected Color underlineColor;
protected Color disabledUnderlineColor; protected Color disabledUnderlineColor;
protected Color hoverColor; protected Color hoverColor;
protected Color focusColor; protected Color focusColor;
protected Color tabSeparatorColor;
protected Color contentAreaColor; protected Color contentAreaColor;
protected int tabHeight; protected int tabHeight;
protected int tabSelectionHeight; protected int tabSelectionHeight;
protected int contentSeparatorHeight; protected int contentSeparatorHeight;
protected boolean showTabSeparators;
protected boolean tabSeparatorsFullHeight;
protected boolean hasFullBorder; protected boolean hasFullBorder;
protected boolean tabsOverlapBorder; protected boolean tabsOverlapBorder;
@@ -95,16 +126,20 @@ public class FlatTabbedPaneUI
super.installDefaults(); super.installDefaults();
disabledForeground = UIManager.getColor( "TabbedPane.disabledForeground" ); disabledForeground = UIManager.getColor( "TabbedPane.disabledForeground" );
selectedBackground = UIManager.getColor( "TabbedPane.selectedBackground" );
selectedForeground = UIManager.getColor( "TabbedPane.selectedForeground" ); selectedForeground = UIManager.getColor( "TabbedPane.selectedForeground" );
underlineColor = UIManager.getColor( "TabbedPane.underlineColor" ); underlineColor = UIManager.getColor( "TabbedPane.underlineColor" );
disabledUnderlineColor = UIManager.getColor( "TabbedPane.disabledUnderlineColor" ); disabledUnderlineColor = UIManager.getColor( "TabbedPane.disabledUnderlineColor" );
hoverColor = UIManager.getColor( "TabbedPane.hoverColor" ); hoverColor = UIManager.getColor( "TabbedPane.hoverColor" );
focusColor = UIManager.getColor( "TabbedPane.focusColor" ); focusColor = UIManager.getColor( "TabbedPane.focusColor" );
tabSeparatorColor = UIManager.getColor( "TabbedPane.tabSeparatorColor" );
contentAreaColor = UIManager.getColor( "TabbedPane.contentAreaColor" ); contentAreaColor = UIManager.getColor( "TabbedPane.contentAreaColor" );
tabHeight = UIManager.getInt( "TabbedPane.tabHeight" ); tabHeight = UIManager.getInt( "TabbedPane.tabHeight" );
tabSelectionHeight = UIManager.getInt( "TabbedPane.tabSelectionHeight" ); tabSelectionHeight = UIManager.getInt( "TabbedPane.tabSelectionHeight" );
contentSeparatorHeight = UIManager.getInt( "TabbedPane.contentSeparatorHeight" ); contentSeparatorHeight = UIManager.getInt( "TabbedPane.contentSeparatorHeight" );
showTabSeparators = UIManager.getBoolean( "TabbedPane.showTabSeparators" );
tabSeparatorsFullHeight = UIManager.getBoolean( "TabbedPane.tabSeparatorsFullHeight" );
hasFullBorder = UIManager.getBoolean( "TabbedPane.hasFullBorder" ); hasFullBorder = UIManager.getBoolean( "TabbedPane.hasFullBorder" );
tabsOverlapBorder = UIManager.getBoolean( "TabbedPane.tabsOverlapBorder" ); tabsOverlapBorder = UIManager.getBoolean( "TabbedPane.tabsOverlapBorder" );
@@ -116,19 +151,37 @@ public class FlatTabbedPaneUI
tabHeight = scale( tabHeight ); tabHeight = scale( tabHeight );
tabSelectionHeight = scale( tabSelectionHeight ); tabSelectionHeight = scale( tabSelectionHeight );
// replace focus forward/backward traversal keys with TAB/Shift+TAB because
// the default also includes Ctrl+TAB/Ctrl+Shift+TAB, which we need to switch tabs
if( focusForwardTraversalKeys == null ) {
focusForwardTraversalKeys = Collections.singleton( KeyStroke.getKeyStroke( KeyEvent.VK_TAB, 0 ) );
focusBackwardTraversalKeys = Collections.singleton( KeyStroke.getKeyStroke( KeyEvent.VK_TAB, InputEvent.SHIFT_MASK ) );
}
// Ideally we should use `LookAndFeel.installProperty( tabPane, "focusTraversalKeysForward", keys )` here
// instead of `tabPane.setFocusTraversalKeys()`, but WindowsTabbedPaneUI also uses later method
// and switching from Windows LaF to FlatLaf would not replace the keys and Ctrl+TAB would not work.
tabPane.setFocusTraversalKeys( KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, focusForwardTraversalKeys );
tabPane.setFocusTraversalKeys( KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, focusBackwardTraversalKeys );
MigLayoutVisualPadding.install( tabPane, null ); MigLayoutVisualPadding.install( tabPane, null );
} }
@Override @Override
protected void uninstallDefaults() { protected void uninstallDefaults() {
// restore focus forward/backward traversal keys
tabPane.setFocusTraversalKeys( KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, null );
tabPane.setFocusTraversalKeys( KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, null );
super.uninstallDefaults(); super.uninstallDefaults();
disabledForeground = null; disabledForeground = null;
selectedBackground = null;
selectedForeground = null; selectedForeground = null;
underlineColor = null; underlineColor = null;
disabledUnderlineColor = null; disabledUnderlineColor = null;
hoverColor = null; hoverColor = null;
focusColor = null; focusColor = null;
tabSeparatorColor = null;
contentAreaColor = null; contentAreaColor = null;
MigLayoutVisualPadding.uninstall( tabPane ); MigLayoutVisualPadding.uninstall( tabPane );
@@ -141,9 +194,13 @@ public class FlatTabbedPaneUI
public void propertyChange( PropertyChangeEvent e ) { public void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e ); super.propertyChange( e );
if( TABBED_PANE_HAS_FULL_BORDER.equals( e.getPropertyName() ) ) { switch( e.getPropertyName() ) {
tabPane.revalidate(); case TABBED_PANE_SHOW_TAB_SEPARATORS:
tabPane.repaint(); case TABBED_PANE_HAS_FULL_BORDER:
case TABBED_PANE_TAB_HEIGHT:
tabPane.revalidate();
tabPane.repaint();
break;
} }
} }
}; };
@@ -153,7 +210,7 @@ public class FlatTabbedPaneUI
protected JButton createScrollButton( int direction ) { protected JButton createScrollButton( int direction ) {
// this method is invoked before installDefaults(), so we can not use color fields here // this method is invoked before installDefaults(), so we can not use color fields here
return new FlatArrowButton( direction, UIManager.getString( "Component.arrowType" ), return new FlatArrowButton( direction, UIManager.getString( "Component.arrowType" ),
UIManager.getColor( "TabbedPane.shadow" ), UIManager.getColor( "TabbedPane.foreground" ),
UIManager.getColor( "TabbedPane.disabledForeground" ), null, UIManager.getColor( "TabbedPane.disabledForeground" ), null,
UIManager.getColor( "TabbedPane.hoverColor" ) ); UIManager.getColor( "TabbedPane.hoverColor" ) );
} }
@@ -187,6 +244,7 @@ public class FlatTabbedPaneUI
@Override @Override
protected int calculateTabHeight( int tabPlacement, int tabIndex, int fontHeight ) { protected int calculateTabHeight( int tabPlacement, int tabIndex, int fontHeight ) {
int tabHeight = clientPropertyInt( tabPane, TABBED_PANE_TAB_HEIGHT, this.tabHeight );
return Math.max( tabHeight, super.calculateTabHeight( tabPlacement, tabIndex, fontHeight ) - 2 /* was added by superclass */ ); return Math.max( tabHeight, super.calculateTabHeight( tabPlacement, tabIndex, fontHeight ) - 2 /* was added by superclass */ );
} }
@@ -197,7 +255,7 @@ public class FlatTabbedPaneUI
*/ */
@Override @Override
protected Insets getContentBorderInsets( int tabPlacement ) { protected Insets getContentBorderInsets( int tabPlacement ) {
boolean hasFullBorder = this.hasFullBorder || clientPropertyEquals( tabPane, TABBED_PANE_HAS_FULL_BORDER, true ); boolean hasFullBorder = clientPropertyBoolean( tabPane, TABBED_PANE_HAS_FULL_BORDER, this.hasFullBorder );
int sh = scale( contentSeparatorHeight ); int sh = scale( contentSeparatorHeight );
Insets insets = hasFullBorder ? new Insets( sh, sh, sh, sh ) : new Insets( sh, 0, 0, 0 ); Insets insets = hasFullBorder ? new Insets( sh, sh, sh, sh ) : new Insets( sh, 0, 0, 0 );
@@ -260,9 +318,11 @@ public class FlatTabbedPaneUI
boolean enabled = tabPane.isEnabled(); boolean enabled = tabPane.isEnabled();
g.setColor( enabled && tabPane.isEnabledAt( tabIndex ) && getRolloverTab() == tabIndex g.setColor( enabled && tabPane.isEnabledAt( tabIndex ) && getRolloverTab() == tabIndex
? hoverColor ? hoverColor
: (enabled && isSelected && tabPane.hasFocus() : (enabled && isSelected && FlatUIUtils.isPermanentFocusOwner( tabPane )
? focusColor ? focusColor
: tabPane.getBackgroundAt( tabIndex )) ); : (selectedBackground != null && enabled && isSelected
? selectedBackground
: tabPane.getBackgroundAt( tabIndex ))) );
g.fillRect( x, y, w, h ); g.fillRect( x, y, w, h );
} }
@@ -270,6 +330,26 @@ public class FlatTabbedPaneUI
protected void paintTabBorder( Graphics g, int tabPlacement, int tabIndex, protected void paintTabBorder( Graphics g, int tabPlacement, int tabIndex,
int x, int y, int w, int h, boolean isSelected ) int x, int y, int w, int h, boolean isSelected )
{ {
// paint tab separators
if( clientPropertyBoolean( tabPane, TABBED_PANE_SHOW_TAB_SEPARATORS, showTabSeparators ) &&
!isLastInRun( tabIndex ) )
{
float sepWidth = UIScale.scale( 1f );
float offset = tabSeparatorsFullHeight ? 0 : UIScale.scale( 5f );
g.setColor( (tabSeparatorColor != null) ? tabSeparatorColor : contentAreaColor );
if( tabPlacement == LEFT || tabPlacement == RIGHT ) {
// paint tab separator at bottom side
((Graphics2D)g).fill( new Rectangle2D.Float( x + offset, y + h - sepWidth, w - (offset * 2), sepWidth ) );
} else if( tabPane.getComponentOrientation().isLeftToRight() ) {
// paint tab separator at right side
((Graphics2D)g).fill( new Rectangle2D.Float( x + w - sepWidth, y + offset, sepWidth, h - (offset * 2) ) );
} else {
// paint tab separator at left side
((Graphics2D)g).fill( new Rectangle2D.Float( x, y + offset, sepWidth, h - (offset * 2) ) );
}
}
if( isSelected ) if( isSelected )
paintTabSelection( g, tabPlacement, x, y, w, h ); paintTabSelection( g, tabPlacement, x, y, w, h );
} }
@@ -334,7 +414,7 @@ public class FlatTabbedPaneUI
} }
/** /**
* Actually does the nearly the same as super.paintContentBorder() but * Actually does nearly the same as super.paintContentBorder() but
* - not using UIManager.getColor("TabbedPane.contentAreaColor") to be GUI builder friendly * - not using UIManager.getColor("TabbedPane.contentAreaColor") to be GUI builder friendly
* - not invoking paintContentBorder*Edge() methods * - not invoking paintContentBorder*Edge() methods
* - repaint selection * - repaint selection
@@ -379,7 +459,7 @@ public class FlatTabbedPaneUI
} }
// compute insets for separator or full border // compute insets for separator or full border
boolean hasFullBorder = this.hasFullBorder || clientPropertyEquals( tabPane, TABBED_PANE_HAS_FULL_BORDER, true ); boolean hasFullBorder = clientPropertyBoolean( tabPane, TABBED_PANE_HAS_FULL_BORDER, this.hasFullBorder );
int sh = scale( contentSeparatorHeight * 100 ); // multiply by 100 because rotateInsets() does not use floats int sh = scale( contentSeparatorHeight * 100 ); // multiply by 100 because rotateInsets() does not use floats
Insets ci = new Insets( 0, 0, 0, 0 ); Insets ci = new Insets( 0, 0, 0, 0 );
rotateInsets( hasFullBorder ? new Insets( sh, sh, sh, sh ) : new Insets( sh, 0, 0, 0 ), ci, tabPlacement ); rotateInsets( hasFullBorder ? new Insets( sh, sh, sh, sh ) : new Insets( sh, 0, 0, 0 ), ci, tabPlacement );
@@ -414,6 +494,11 @@ public class FlatTabbedPaneUI
{ {
} }
private boolean isLastInRun( int tabIndex ) {
int run = getRunForTab( tabPane.getTabCount(), tabIndex );
return lastTabInRun( tabPane.getTabCount(), run ) == tabIndex;
}
private boolean isScrollTabLayout() { private boolean isScrollTabLayout() {
return tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT; return tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT;
} }

View File

@@ -0,0 +1,115 @@
/*
* Copyright 2019 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import java.awt.Component;
import java.awt.Graphics;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
/**
* Cell border for {@link javax.swing.table.DefaultTableCellRenderer}
* (used by {@link javax.swing.JTable}).
* <p>
* Uses separate cell margins from UI defaults to allow easy customizing.
*
* @author Karl Tauber
*/
public class FlatTableCellBorder
extends FlatLineBorder
{
final boolean showCellFocusIndicator = UIManager.getBoolean( "Table.showCellFocusIndicator" );
protected FlatTableCellBorder() {
super( UIManager.getInsets( "Table.cellMargins" ), UIManager.getColor( "Table.cellFocusColor" ) );
}
//---- class Default ------------------------------------------------------
/**
* Border for unselected cell that uses margins, but does not paint focus indicator border.
*/
public static class Default
extends FlatTableCellBorder
{
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
// do not paint focus indicator border
}
}
//---- class Focused ------------------------------------------------------
/**
* Border for focused unselected cell that uses margins and paints focus indicator border.
*/
public static class Focused
extends FlatTableCellBorder
{
}
//---- class Selected -----------------------------------------------------
/**
* Border for selected cell that uses margins and paints focus indicator border
* if enabled (Table.showCellFocusIndicator=true) or at least one selected cell is editable.
*/
public static class Selected
extends FlatTableCellBorder
{
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
if( !showCellFocusIndicator ) {
JTable table = (JTable) SwingUtilities.getAncestorOfClass( JTable.class, c );
if( table != null && !isSelectionEditable( table ) )
return;
}
super.paintBorder( c, g, x, y, width, height );
}
/**
* Checks whether at least one selected cell is editable.
*/
private boolean isSelectionEditable( JTable table ) {
if( table.getRowSelectionAllowed() ) {
int columnCount = table.getColumnCount();
int[] selectedRows = table.getSelectedRows();
for( int selectedRow : selectedRows ) {
for( int column = 0; column < columnCount; column++ ) {
if( table.isCellEditable( selectedRow, column ) )
return true;
}
}
}
if( table.getColumnSelectionAllowed() ) {
int rowCount = table.getRowCount();
int[] selectedColumns = table.getSelectedColumns();
for( int selectedColumn : selectedColumns ) {
for( int row = 0; row < rowCount; row++ ) {
if( table.isCellEditable( row, selectedColumn ) )
return true;
}
}
}
return false;
}
}
}

View File

@@ -17,18 +17,27 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import java.awt.Color; import java.awt.Color;
import java.awt.Component;
import java.awt.Container; import java.awt.Container;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.util.Objects;
import javax.swing.Icon;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JScrollPane; import javax.swing.JScrollPane;
import javax.swing.JTable; import javax.swing.JTable;
import javax.swing.SwingConstants;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTableHeaderUI; import javax.swing.plaf.basic.BasicTableHeaderUI;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn; import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel; import javax.swing.table.TableColumnModel;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
@@ -47,6 +56,7 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault TableHeader.separatorColor Color * @uiDefault TableHeader.separatorColor Color
* @uiDefault TableHeader.bottomSeparatorColor Color * @uiDefault TableHeader.bottomSeparatorColor Color
* @uiDefault TableHeader.height int * @uiDefault TableHeader.height int
* @uiDefault TableHeader.sortIconPosition String right (default), left, top or bottom
* *
* @author Karl Tauber * @author Karl Tauber
*/ */
@@ -56,6 +66,7 @@ public class FlatTableHeaderUI
protected Color separatorColor; protected Color separatorColor;
protected Color bottomSeparatorColor; protected Color bottomSeparatorColor;
protected int height; protected int height;
protected int sortIconPosition;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
return new FlatTableHeaderUI(); return new FlatTableHeaderUI();
@@ -68,12 +79,33 @@ public class FlatTableHeaderUI
separatorColor = UIManager.getColor( "TableHeader.separatorColor" ); separatorColor = UIManager.getColor( "TableHeader.separatorColor" );
bottomSeparatorColor = UIManager.getColor( "TableHeader.bottomSeparatorColor" ); bottomSeparatorColor = UIManager.getColor( "TableHeader.bottomSeparatorColor" );
height = UIManager.getInt( "TableHeader.height" ); 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;
}
// use own renderer if necessary
if( sortIconPosition != SwingConstants.RIGHT ) {
TableCellRenderer defaultRenderer = header.getDefaultRenderer();
if( defaultRenderer instanceof UIResource )
header.setDefaultRenderer( new FlatTableCellHeaderRenderer( defaultRenderer ) );
}
} }
@Override @Override
protected void uninstallDefaults() { protected void uninstallDefaults() {
super.uninstallDefaults(); super.uninstallDefaults();
// restore default renderer
TableCellRenderer defaultRenderer = header.getDefaultRenderer();
if( defaultRenderer instanceof FlatTableCellHeaderRenderer ) {
((FlatTableCellHeaderRenderer)defaultRenderer).reset();
header.setDefaultRenderer( ((FlatTableCellHeaderRenderer)defaultRenderer).delegate );
}
separatorColor = null; separatorColor = null;
bottomSeparatorColor = null; bottomSeparatorColor = null;
} }
@@ -81,10 +113,14 @@ public class FlatTableHeaderUI
@Override @Override
public void paint( Graphics g, JComponent c ) { public void paint( Graphics g, JComponent c ) {
// do not paint borders if JTableHeader.setDefaultRenderer() was used // do not paint borders if JTableHeader.setDefaultRenderer() was used
String rendererClassName = header.getDefaultRenderer().getClass().getName(); TableCellRenderer defaultRenderer = header.getDefaultRenderer();
boolean paintBorders = boolean paintBorders = isSystemDefaultRenderer( defaultRenderer );
rendererClassName.equals( "sun.swing.table.DefaultTableCellHeaderRenderer" ) || if( !paintBorders && header.getColumnModel().getColumnCount() > 0 ) {
rendererClassName.equals( "sun.swing.FilePane$AlignableTableHeaderRenderer" ); // check whether the renderer delegates to the system default renderer
Component rendererComponent = defaultRenderer.getTableCellRendererComponent(
header.getTable(), "", false, false, -1, 0 );
paintBorders = isSystemDefaultRenderer( rendererComponent );
}
if( paintBorders ) if( paintBorders )
paintColumnBorders( g, c ); paintColumnBorders( g, c );
@@ -95,6 +131,12 @@ public class FlatTableHeaderUI
paintDraggedColumnBorders( g, c ); paintDraggedColumnBorders( g, c );
} }
private boolean isSystemDefaultRenderer( Object headerRenderer ) {
String rendererClassName = headerRenderer.getClass().getName();
return rendererClassName.equals( "sun.swing.table.DefaultTableCellHeaderRenderer" ) ||
rendererClassName.equals( "sun.swing.FilePane$AlignableTableHeaderRenderer" );
}
private void paintColumnBorders( Graphics g, JComponent c ) { private void paintColumnBorders( Graphics g, JComponent c ) {
int width = c.getWidth(); int width = c.getWidth();
int height = c.getHeight(); int height = c.getHeight();
@@ -203,4 +245,83 @@ public class FlatTableHeaderUI
parent = parent.getParent(); parent = parent.getParent();
return (parent instanceof JScrollPane) ? (JScrollPane) parent : null; return (parent instanceof JScrollPane) ? (JScrollPane) parent : null;
} }
//---- class FlatTableCellHeaderRenderer ----------------------------------
/**
* A delegating header renderer that is only used to paint sort arrows at
* top, bottom or left position.
*/
private class FlatTableCellHeaderRenderer
implements TableCellRenderer, Border, UIResource
{
private final TableCellRenderer delegate;
private int oldHorizontalTextPosition = -1;
private Border origBorder;
private Icon sortIcon;
FlatTableCellHeaderRenderer( TableCellRenderer delegate ) {
this.delegate = delegate;
}
@Override
public Component getTableCellRendererComponent( JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column )
{
Component c = delegate.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column );
if( !(c instanceof JLabel) )
return c;
JLabel l = (JLabel) c;
if( sortIconPosition == SwingConstants.LEFT ) {
if( oldHorizontalTextPosition < 0 )
oldHorizontalTextPosition = l.getHorizontalTextPosition();
l.setHorizontalTextPosition( SwingConstants.RIGHT );
} else {
// top or bottom
sortIcon = l.getIcon();
origBorder = l.getBorder();
l.setIcon( null );
l.setBorder( this );
}
return l;
}
void reset() {
if( sortIconPosition == SwingConstants.LEFT && oldHorizontalTextPosition >= 0 ) {
Component c = getTableCellRendererComponent( header.getTable(), "", false, false, -1, 0 );
if( c instanceof JLabel && ((JLabel)c).getHorizontalTextPosition() == SwingConstants.RIGHT )
((JLabel)c).setHorizontalTextPosition( oldHorizontalTextPosition );
}
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
if( origBorder != null )
origBorder.paintBorder( c, g, x, y, width, height );
if( sortIcon != null ) {
int xi = x + ((width - sortIcon.getIconWidth()) / 2);
int yi = (sortIconPosition == SwingConstants.TOP)
? y + UIScale.scale( 1 )
: y + height - sortIcon.getIconHeight()
- 1 // for gap
- (int) (1 * UIScale.getUserScaleFactor()); // for bottom border
sortIcon.paintIcon( c, g, xi, yi );
}
}
@Override
public Insets getBorderInsets( Component c ) {
return (origBorder != null) ? origBorder.getBorderInsets( c ) : new Insets( 0, 0, 0, 0 );
}
@Override
public boolean isBorderOpaque() {
return (origBorder != null) ? origBorder.isBorderOpaque() : false;
}
}
} }

View File

@@ -17,6 +17,7 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import java.awt.Color; import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.FocusEvent; import java.awt.event.FocusEvent;
import java.awt.event.FocusListener; import java.awt.event.FocusListener;
import javax.swing.JComponent; import javax.swing.JComponent;
@@ -54,38 +55,83 @@ import com.formdev.flatlaf.util.UIScale;
* *
* <!-- FlatTableUI --> * <!-- FlatTableUI -->
* *
* @uiDefault Table.rowHeight int * @uiDefault Table.rowHeight int
* @uiDefault Table.selectionInactiveBackground Color * @uiDefault Table.showHorizontalLines boolean
* @uiDefault Table.selectionInactiveForeground Color * @uiDefault Table.showVerticalLines boolean
* @uiDefault Table.intercellSpacing Dimension
* @uiDefault Table.selectionInactiveBackground Color
* @uiDefault Table.selectionInactiveForeground Color
*
* <!-- FlatTableCellBorder -->
*
* @uiDefault Table.cellMargins Insets
* @uiDefault Table.cellFocusColor Color
* @uiDefault Table.showCellFocusIndicator boolean
* *
* @author Karl Tauber * @author Karl Tauber
*/ */
public class FlatTableUI public class FlatTableUI
extends BasicTableUI extends BasicTableUI
{ {
protected boolean showHorizontalLines;
protected boolean showVerticalLines;
protected Dimension intercellSpacing;
protected Color selectionBackground; protected Color selectionBackground;
protected Color selectionForeground; protected Color selectionForeground;
protected Color selectionInactiveBackground; protected Color selectionInactiveBackground;
protected Color selectionInactiveForeground; protected Color selectionInactiveForeground;
private boolean oldShowHorizontalLines;
private boolean oldShowVerticalLines;
private Dimension oldIntercellSpacing;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
return new FlatTableUI(); return new FlatTableUI();
} }
@Override
public void installUI( JComponent c ) {
super.installUI( c );
}
@Override
public void uninstallUI( JComponent c ) {
super.uninstallUI( c );
}
@Override @Override
protected void installDefaults() { protected void installDefaults() {
super.installDefaults(); super.installDefaults();
showHorizontalLines = UIManager.getBoolean( "Table.showHorizontalLines" );
showVerticalLines = UIManager.getBoolean( "Table.showVerticalLines" );
intercellSpacing = UIManager.getDimension( "Table.intercellSpacing" );
selectionBackground = UIManager.getColor( "Table.selectionBackground" ); selectionBackground = UIManager.getColor( "Table.selectionBackground" );
selectionForeground = UIManager.getColor( "Table.selectionForeground" ); selectionForeground = UIManager.getColor( "Table.selectionForeground" );
selectionInactiveBackground = UIManager.getColor( "Table.selectionInactiveBackground" ); selectionInactiveBackground = UIManager.getColor( "Table.selectionInactiveBackground" );
selectionInactiveForeground = UIManager.getColor( "Table.selectionInactiveForeground" ); selectionInactiveForeground = UIManager.getColor( "Table.selectionInactiveForeground" );
toggleSelectionColors( table.hasFocus() ); toggleSelectionColors();
int rowHeight = FlatUIUtils.getUIInt( "Table.rowHeight", 16 ); int rowHeight = FlatUIUtils.getUIInt( "Table.rowHeight", 16 );
if( rowHeight > 0 ) if( rowHeight > 0 )
LookAndFeel.installProperty( table, "rowHeight", UIScale.scale( rowHeight ) ); LookAndFeel.installProperty( table, "rowHeight", UIScale.scale( rowHeight ) );
if( !showHorizontalLines ) {
oldShowHorizontalLines = table.getShowHorizontalLines();
table.setShowHorizontalLines( false );
}
if( !showVerticalLines ) {
oldShowVerticalLines = table.getShowVerticalLines();
table.setShowVerticalLines( false );
}
if( intercellSpacing != null ) {
oldIntercellSpacing = table.getIntercellSpacing();
table.setIntercellSpacing( intercellSpacing );
}
} }
@Override @Override
@@ -96,6 +142,16 @@ public class FlatTableUI
selectionForeground = null; selectionForeground = null;
selectionInactiveBackground = null; selectionInactiveBackground = null;
selectionInactiveForeground = null; selectionInactiveForeground = null;
// restore old show horizontal/vertical lines (if not modified)
if( !showHorizontalLines && oldShowHorizontalLines && !table.getShowHorizontalLines() )
table.setShowHorizontalLines( true );
if( !showVerticalLines && oldShowVerticalLines && !table.getShowVerticalLines() )
table.setShowVerticalLines( true );
// restore old intercell spacing (if not modified)
if( intercellSpacing != null && table.getIntercellSpacing().equals( intercellSpacing ) )
table.setIntercellSpacing( oldIntercellSpacing );
} }
@Override @Override
@@ -104,13 +160,13 @@ public class FlatTableUI
@Override @Override
public void focusGained( FocusEvent e ) { public void focusGained( FocusEvent e ) {
super.focusGained( e ); super.focusGained( e );
toggleSelectionColors( true ); toggleSelectionColors();
} }
@Override @Override
public void focusLost( FocusEvent e ) { public void focusLost( FocusEvent e ) {
super.focusLost( e ); super.focusLost( e );
toggleSelectionColors( false ); toggleSelectionColors();
} }
}; };
} }
@@ -124,8 +180,8 @@ public class FlatTableUI
* already used in applications. Then either the inactive colors are not used, * already used in applications. Then either the inactive colors are not used,
* or the application has to be changed to extend a FlatLaf renderer. * or the application has to be changed to extend a FlatLaf renderer.
*/ */
private void toggleSelectionColors( boolean focused ) { private void toggleSelectionColors() {
if( focused ) { if( FlatUIUtils.isPermanentFocusOwner( table ) ) {
if( table.getSelectionBackground() == selectionInactiveBackground ) if( table.getSelectionBackground() == selectionInactiveBackground )
table.setSelectionBackground( selectionBackground ); table.setSelectionBackground( selectionBackground );
if( table.getSelectionForeground() == selectionInactiveForeground ) if( table.getSelectionForeground() == selectionInactiveForeground )

View File

@@ -117,6 +117,7 @@ public class FlatTextAreaUI
// and subtract 1px border line width. // and subtract 1px border line width.
// Using "(scale( 1 ) * 2)" instead of "scale( 2 )" to deal with rounding // Using "(scale( 1 ) * 2)" instead of "scale( 2 )" to deal with rounding
// issues. E.g. at scale factor 1.5 the first returns 4, but the second 3. // issues. E.g. at scale factor 1.5 the first returns 4, but the second 3.
int minimumWidth = FlatUIUtils.minimumWidth( getComponent(), this.minimumWidth );
size.width = Math.max( size.width, scale( minimumWidth ) - (scale( 1 ) * 2) ); size.width = Math.max( size.width, scale( minimumWidth ) - (scale( 1 ) * 2) );
return size; return size;
} }

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2019 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Component;
import javax.swing.UIManager;
/**
* Border for various text components (e.g. {@link javax.swing.JTextField}).
*
* @uiDefault Component.arc int
*
* @author Karl Tauber
*/
public class FlatTextBorder
extends FlatBorder
{
protected final int arc = UIManager.getInt( "TextComponent.arc" );
@Override
protected float getArc( Component c ) {
return scale( (float) arc );
}
}

View File

@@ -20,19 +20,25 @@ import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Color; import java.awt.Color;
import java.awt.Container; import java.awt.Container;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.event.FocusListener; import java.awt.event.FocusListener;
import java.beans.PropertyChangeEvent;
import javax.swing.JComboBox; import javax.swing.JComboBox;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JSpinner; import javax.swing.JSpinner;
import javax.swing.JTextField; import javax.swing.JTextField;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource; import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTextFieldUI; import javax.swing.plaf.basic.BasicTextFieldUI;
import javax.swing.text.Caret;
import javax.swing.text.JTextComponent; import javax.swing.text.JTextComponent;
import com.formdev.flatlaf.FlatClientProperties;
/** /**
* Provides the Flat LaF UI delegate for {@link javax.swing.JTextField}. * Provides the Flat LaF UI delegate for {@link javax.swing.JTextField}.
@@ -54,18 +60,23 @@ import javax.swing.text.JTextComponent;
* *
* <!-- FlatTextFieldUI --> * <!-- FlatTextFieldUI -->
* *
* @uiDefault TextComponent.arc int
* @uiDefault Component.focusWidth int * @uiDefault Component.focusWidth int
* @uiDefault Component.minimumWidth int * @uiDefault Component.minimumWidth int
* @uiDefault Component.isIntelliJTheme boolean * @uiDefault Component.isIntelliJTheme boolean
* @uiDefault TextField.placeholderForeground Color
* @uiDefault TextComponent.selectAllOnFocusPolicy String never, once (default) or always
* *
* @author Karl Tauber * @author Karl Tauber
*/ */
public class FlatTextFieldUI public class FlatTextFieldUI
extends BasicTextFieldUI extends BasicTextFieldUI
{ {
protected int arc;
protected int focusWidth; protected int focusWidth;
protected int minimumWidth; protected int minimumWidth;
protected boolean isIntelliJTheme; protected boolean isIntelliJTheme;
protected Color placeholderForeground;
private FocusListener focusListener; private FocusListener focusListener;
@@ -77,9 +88,12 @@ public class FlatTextFieldUI
protected void installDefaults() { protected void installDefaults() {
super.installDefaults(); super.installDefaults();
String prefix = getPropertyPrefix();
arc = UIManager.getInt( "TextComponent.arc" );
focusWidth = UIManager.getInt( "Component.focusWidth" ); focusWidth = UIManager.getInt( "Component.focusWidth" );
minimumWidth = UIManager.getInt( "Component.minimumWidth" ); minimumWidth = UIManager.getInt( "Component.minimumWidth" );
isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" ); isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" );
placeholderForeground = UIManager.getColor( prefix + ".placeholderForeground" );
LookAndFeel.installProperty( getComponent(), "opaque", focusWidth == 0 ); LookAndFeel.installProperty( getComponent(), "opaque", focusWidth == 0 );
@@ -90,6 +104,8 @@ public class FlatTextFieldUI
protected void uninstallDefaults() { protected void uninstallDefaults() {
super.uninstallDefaults(); super.uninstallDefaults();
placeholderForeground = null;
MigLayoutVisualPadding.uninstall( getComponent() ); MigLayoutVisualPadding.uninstall( getComponent() );
} }
@@ -109,9 +125,23 @@ public class FlatTextFieldUI
focusListener = null; focusListener = null;
} }
@Override
protected Caret createCaret() {
return new FlatCaret( UIManager.getString( "TextComponent.selectAllOnFocusPolicy" ) );
}
@Override
protected void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e );
if( FlatClientProperties.PLACEHOLDER_TEXT.equals( e.getPropertyName() ) )
getComponent().repaint();
}
@Override @Override
protected void paintSafely( Graphics g ) { protected void paintSafely( Graphics g ) {
paintBackground( g, getComponent(), focusWidth, isIntelliJTheme ); paintBackground( g, getComponent(), focusWidth, arc, isIntelliJTheme );
paintPlaceholder( g, getComponent(), placeholderForeground );
super.paintSafely( g ); super.paintSafely( g );
} }
@@ -120,13 +150,15 @@ public class FlatTextFieldUI
// background is painted elsewhere // background is painted elsewhere
} }
static void paintBackground( Graphics g, JTextComponent c, int focusWidth, boolean isIntelliJTheme ) { static void paintBackground( Graphics g, JTextComponent c, int focusWidth, int arc, boolean isIntelliJTheme ) {
Border border = c.getBorder();
// do not paint background if: // do not paint background if:
// - not opaque and // - not opaque and
// - border is not a flat border and // - border is not a flat border and
// - opaque was explicitly set (to false) // - opaque was explicitly set (to false)
// (same behaviour as in AquaTextFieldUI) // (same behaviour as in AquaTextFieldUI)
if( !c.isOpaque() && !(c.getBorder() instanceof FlatBorder) && FlatUIUtils.hasOpaqueBeenExplicitlySet( c ) ) if( !c.isOpaque() && !(border instanceof FlatBorder) && FlatUIUtils.hasOpaqueBeenExplicitlySet( c ) )
return; return;
// fill background if opaque to avoid garbage if user sets opaque to true // fill background if opaque to avoid garbage if user sets opaque to true
@@ -138,7 +170,8 @@ public class FlatTextFieldUI
try { try {
FlatUIUtils.setRenderingHints( g2 ); FlatUIUtils.setRenderingHints( g2 );
float fFocusWidth = (c.getBorder() instanceof FlatBorder) ? scale( (float) focusWidth ) : 0; float fFocusWidth = (border instanceof FlatBorder) ? scale( (float) focusWidth ) : 0;
float fArc = (border instanceof FlatTextBorder) ? scale( (float) arc ) : 0;
Color background = c.getBackground(); Color background = c.getBackground();
g2.setColor( !(background instanceof UIResource) g2.setColor( !(background instanceof UIResource)
@@ -146,12 +179,37 @@ public class FlatTextFieldUI
: (isIntelliJTheme && (!c.isEnabled() || !c.isEditable()) : (isIntelliJTheme && (!c.isEnabled() || !c.isEditable())
? FlatUIUtils.getParentBackground( c ) ? FlatUIUtils.getParentBackground( c )
: background) ); : background) );
FlatUIUtils.fillRoundRectangle( g2, 0, 0, c.getWidth(), c.getHeight(), fFocusWidth, 0 ); FlatUIUtils.paintComponentBackground( g2, 0, 0, c.getWidth(), c.getHeight(), fFocusWidth, fArc );
} finally { } finally {
g2.dispose(); g2.dispose();
} }
} }
static void paintPlaceholder( Graphics g, JTextComponent c, Color placeholderForeground ) {
// check whether text component is empty
if( c.getDocument().getLength() > 0 )
return;
// check for JComboBox
Container parent = c.getParent();
JComponent jc = (parent instanceof JComboBox) ? (JComboBox<?>) parent : c;
// get placeholder text
Object placeholder = jc.getClientProperty( FlatClientProperties.PLACEHOLDER_TEXT );
if( !(placeholder instanceof String) )
return;
// compute placeholder location
Insets insets = c.getInsets();
FontMetrics fm = c.getFontMetrics( c.getFont() );
int x = insets.left;
int y = insets.top + fm.getAscent() + ((c.getHeight() - insets.top - insets.bottom - fm.getHeight()) / 2);
// paint placeholder
g.setColor( placeholderForeground );
FlatUIUtils.drawString( c, g, (String) placeholder, x, y );
}
@Override @Override
public Dimension getPreferredSize( JComponent c ) { public Dimension getPreferredSize( JComponent c ) {
return applyMinimumWidth( super.getPreferredSize( c ), c ); return applyMinimumWidth( super.getPreferredSize( c ), c );
@@ -173,6 +231,7 @@ public class FlatTextFieldUI
(parent != null && parent.getParent() instanceof JSpinner) ) (parent != null && parent.getParent() instanceof JSpinner) )
return size; return size;
int minimumWidth = FlatUIUtils.minimumWidth( getComponent(), this.minimumWidth );
int focusWidth = (c.getBorder() instanceof FlatBorder) ? this.focusWidth : 0; int focusWidth = (c.getBorder() instanceof FlatBorder) ? this.focusWidth : 0;
size.width = Math.max( size.width, scale( minimumWidth + (focusWidth * 2) ) ); size.width = Math.max( size.width, scale( minimumWidth + (focusWidth * 2) ) );
return size; return size;

View File

@@ -98,6 +98,7 @@ public class FlatTextPaneUI
// and subtract 1px border line width. // and subtract 1px border line width.
// Using "(scale( 1 ) * 2)" instead of "scale( 2 )" to deal with rounding // Using "(scale( 1 ) * 2)" instead of "scale( 2 )" to deal with rounding
// issues. E.g. at scale factor 1.5 the first returns 4, but the second 3. // issues. E.g. at scale factor 1.5 the first returns 4, but the second 3.
int minimumWidth = FlatUIUtils.minimumWidth( getComponent(), this.minimumWidth );
size.width = Math.max( size.width, scale( minimumWidth ) - (scale( 1 ) * 2) ); size.width = Math.max( size.width, scale( minimumWidth ) - (scale( 1 ) * 2) );
return size; return size;
} }

View File

@@ -16,12 +16,18 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.FlatClientProperties.*;
import java.awt.Color; import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.beans.PropertyChangeEvent;
import javax.swing.AbstractButton; import javax.swing.AbstractButton;
import javax.swing.ButtonModel; import javax.swing.ButtonModel;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JToggleButton;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import com.formdev.flatlaf.util.UIScale;
/** /**
* Provides the Flat LaF UI delegate for {@link javax.swing.JToggleButton}. * Provides the Flat LaF UI delegate for {@link javax.swing.JToggleButton}.
@@ -38,9 +44,11 @@ import javax.swing.plaf.ComponentUI;
* <!-- FlatButtonUI --> * <!-- FlatButtonUI -->
* *
* @uiDefault Component.focusWidth int * @uiDefault Component.focusWidth int
* @uiDefault ToggleButton.arc int * @uiDefault Button.arc int
* @uiDefault ToggleButton.minimumWidth int * @uiDefault ToggleButton.minimumWidth int
* @uiDefault ToggleButton.iconTextGap int * @uiDefault ToggleButton.iconTextGap int
* @uiDefault ToggleButton.startBackground Color optional; if set, a gradient paint is used and ToggleButton.background is ignored
* @uiDefault ToggleButton.endBackground Color optional; if set, a gradient paint is used
* @uiDefault ToggleButton.pressedBackground Color * @uiDefault ToggleButton.pressedBackground Color
* @uiDefault ToggleButton.disabledText Color * @uiDefault ToggleButton.disabledText Color
* @uiDefault ToggleButton.toolbar.hoverBackground Color * @uiDefault ToggleButton.toolbar.hoverBackground Color
@@ -53,6 +61,13 @@ import javax.swing.plaf.ComponentUI;
* @uiDefault ToggleButton.disabledSelectedBackground Color * @uiDefault ToggleButton.disabledSelectedBackground Color
* @uiDefault ToggleButton.toolbar.selectedBackground Color * @uiDefault ToggleButton.toolbar.selectedBackground Color
* *
* @uiDefault ToggleButton.tab.underlineHeight int
* @uiDefault ToggleButton.tab.underlineColor Color
* @uiDefault ToggleButton.tab.disabledUnderlineColor Color
* @uiDefault ToggleButton.tab.selectedBackground Color optional
* @uiDefault ToggleButton.tab.hoverBackground Color
* @uiDefault ToggleButton.tab.focusBackground Color
*
* *
* @author Karl Tauber * @author Karl Tauber
*/ */
@@ -65,6 +80,13 @@ public class FlatToggleButtonUI
protected Color toolbarSelectedBackground; protected Color toolbarSelectedBackground;
protected int tabUnderlineHeight;
protected Color tabUnderlineColor;
protected Color tabDisabledUnderlineColor;
protected Color tabSelectedBackground;
protected Color tabHoverBackground;
protected Color tabFocusBackground;
private boolean defaults_initialized = false; private boolean defaults_initialized = false;
private static ComponentUI instance; private static ComponentUI instance;
@@ -91,6 +113,13 @@ public class FlatToggleButtonUI
toolbarSelectedBackground = UIManager.getColor( "ToggleButton.toolbar.selectedBackground" ); toolbarSelectedBackground = UIManager.getColor( "ToggleButton.toolbar.selectedBackground" );
tabUnderlineHeight = UIManager.getInt( "ToggleButton.tab.underlineHeight" );
tabUnderlineColor = UIManager.getColor( "ToggleButton.tab.underlineColor" );
tabDisabledUnderlineColor = UIManager.getColor( "ToggleButton.tab.disabledUnderlineColor" );
tabSelectedBackground = UIManager.getColor( "ToggleButton.tab.selectedBackground" );
tabHoverBackground = UIManager.getColor( "ToggleButton.tab.hoverBackground" );
tabFocusBackground = UIManager.getColor( "ToggleButton.tab.focusBackground" );
defaults_initialized = true; defaults_initialized = true;
} }
} }
@@ -101,6 +130,61 @@ public class FlatToggleButtonUI
defaults_initialized = false; defaults_initialized = false;
} }
@Override
protected void propertyChange( AbstractButton b, PropertyChangeEvent e ) {
super.propertyChange( b, e );
switch( e.getPropertyName() ) {
case BUTTON_TYPE:
if( BUTTON_TYPE_TAB.equals( e.getOldValue() ) || BUTTON_TYPE_TAB.equals( e.getNewValue() ) ) {
MigLayoutVisualPadding.uninstall( b );
MigLayoutVisualPadding.install( b, getFocusWidth( b ) );
b.revalidate();
}
b.repaint();
break;
case TAB_BUTTON_UNDERLINE_HEIGHT:
case TAB_BUTTON_UNDERLINE_COLOR:
case TAB_BUTTON_SELECTED_BACKGROUND:
b.repaint();
break;
}
}
static boolean isTabButton( Component c ) {
return c instanceof JToggleButton && clientPropertyEquals( (JToggleButton) c, BUTTON_TYPE, BUTTON_TYPE_TAB );
}
@Override
protected void paintBackground( Graphics g, JComponent c ) {
if( isTabButton( c ) ) {
int height = c.getHeight();
int width = c.getWidth();
boolean selected = ((AbstractButton)c).isSelected();
// paint background
Color background = buttonStateColor( c,
selected ? clientPropertyColor( c, TAB_BUTTON_SELECTED_BACKGROUND, tabSelectedBackground ) : null,
null, tabFocusBackground, tabHoverBackground, null );
if( background != null ) {
g.setColor( background );
g.fillRect( 0, 0, width, height );
}
// paint underline if selected
if( selected ) {
int underlineHeight = UIScale.scale( clientPropertyInt( c, TAB_BUTTON_UNDERLINE_HEIGHT, tabUnderlineHeight ) );
g.setColor( c.isEnabled()
? clientPropertyColor( c, TAB_BUTTON_UNDERLINE_COLOR, tabUnderlineColor )
: tabDisabledUnderlineColor );
g.fillRect( 0, height - underlineHeight, width, underlineHeight );
}
} else
super.paintBackground( g, c );
}
@Override @Override
protected Color getBackground( JComponent c ) { protected Color getBackground( JComponent c ) {
ButtonModel model = ((AbstractButton)c).getModel(); ButtonModel model = ((AbstractButton)c).getModel();
@@ -128,4 +212,9 @@ public class FlatToggleButtonUI
return super.getForeground( c ); return super.getForeground( c );
} }
@Override
protected int getFocusWidth( JComponent c ) {
return isTabButton( c ) ? 0 : super.getFocusWidth( c );
}
} }

View File

@@ -29,6 +29,7 @@ import javax.swing.UIManager;
/** /**
* Border for {@link javax.swing.JToolBar}. * Border for {@link javax.swing.JToolBar}.
* *
* @uiDefault ToolBar.borderMargins Insets
* @uiDefault ToolBar.gripColor Color * @uiDefault ToolBar.gripColor Color
* *
* @author Karl Tauber * @author Karl Tauber
@@ -42,6 +43,10 @@ public class FlatToolBarBorder
protected final Color gripColor = UIManager.getColor( "ToolBar.gripColor" ); protected final Color gripColor = UIManager.getColor( "ToolBar.gripColor" );
public FlatToolBarBorder() {
super( UIManager.getInsets( "ToolBar.borderMargins" ) );
}
@Override @Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) { public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
// paint grip // paint grip

View File

@@ -16,18 +16,14 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Component; import java.awt.Component;
import java.awt.Insets; import java.awt.Insets;
import java.awt.event.ContainerEvent; import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener; import java.awt.event.ContainerListener;
import javax.swing.AbstractButton; import javax.swing.AbstractButton;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.UIManager;
import javax.swing.border.Border; import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicToolBarUI; import javax.swing.plaf.basic.BasicToolBarUI;
/** /**
@@ -45,28 +41,15 @@ import javax.swing.plaf.basic.BasicToolBarUI;
* @uiDefault ToolBar.floatingForeground Color * @uiDefault ToolBar.floatingForeground Color
* @uiDefault ToolBar.isRollover boolean * @uiDefault ToolBar.isRollover boolean
* *
* <!-- FlatToolBarUI -->
*
* @uiDefault ToolBar.buttonMargins Insets
*
* @author Karl Tauber * @author Karl Tauber
*/ */
public class FlatToolBarUI public class FlatToolBarUI
extends BasicToolBarUI extends BasicToolBarUI
{ {
private Border rolloverBorder;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
return new FlatToolBarUI(); return new FlatToolBarUI();
} }
@Override
public void uninstallUI( JComponent c ) {
super.uninstallUI( c );
rolloverBorder = null;
}
@Override @Override
protected ContainerListener createToolBarContListener() { protected ContainerListener createToolBarContListener() {
return new ToolBarContListener() { return new ToolBarContListener() {
@@ -90,26 +73,15 @@ public class FlatToolBarUI
}; };
} }
@Override // disable rollover border
protected Border createRolloverBorder() { @Override protected void setBorderToRollover( Component c ) {}
return getRolloverBorder(); @Override protected void setBorderToNonRollover( Component c ) {}
} @Override protected void setBorderToNormal( Component c ) {}
@Override protected void installRolloverBorders( JComponent c ) {}
@Override @Override protected void installNonRolloverBorders( JComponent c ) {}
protected Border createNonRolloverBorder() { @Override protected void installNormalBorders( JComponent c ) {}
return getRolloverBorder(); @Override protected Border createRolloverBorder() { return null; }
} @Override protected Border createNonRolloverBorder() { return null; }
@Override
protected Border getNonRolloverBorder( AbstractButton b ) {
return getRolloverBorder();
}
private Border getRolloverBorder() {
if( rolloverBorder == null )
rolloverBorder = new FlatRolloverMarginBorder();
return rolloverBorder;
}
@Override @Override
public void setOrientation( int orientation ) { public void setOrientation( int orientation ) {
@@ -123,46 +95,4 @@ public class FlatToolBarUI
super.setOrientation( orientation ); super.setOrientation( orientation );
} }
//---- class FlatRolloverMarginBorder -------------------------------------
/**
* Uses button margin only if explicitly set.
* Otherwise uses insets specified in constructor.
*/
private static class FlatRolloverMarginBorder
extends EmptyBorder
{
public FlatRolloverMarginBorder() {
super( UIManager.getInsets( "ToolBar.buttonMargins" ) );
}
@Override
public Insets getBorderInsets( Component c, Insets insets ) {
Insets margin = (c instanceof AbstractButton)
? ((AbstractButton) c).getMargin()
: null;
if( margin == null || margin instanceof UIResource ) {
insets.top = top;
insets.left = left;
insets.bottom = bottom;
insets.right = right;
} else {
// margin explicitly set
insets.top = margin.top;
insets.left = margin.left;
insets.bottom = margin.bottom;
insets.right = margin.right;
}
// scale
insets.top = scale( insets.top );
insets.left = scale( insets.left );
insets.bottom = scale( insets.bottom );
insets.right = scale( insets.right );
return insets;
}
}
} }

View File

@@ -21,11 +21,13 @@ import java.awt.FontMetrics;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Insets; import java.awt.Insets;
import java.beans.PropertyChangeListener;
import java.util.List; import java.util.List;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JToolTip; import javax.swing.JToolTip;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.plaf.basic.BasicToolTipUI; import javax.swing.plaf.basic.BasicToolTipUI;
import com.formdev.flatlaf.util.StringUtils; import com.formdev.flatlaf.util.StringUtils;
@@ -47,6 +49,8 @@ import com.formdev.flatlaf.util.StringUtils;
public class FlatToolTipUI public class FlatToolTipUI
extends BasicToolTipUI extends BasicToolTipUI
{ {
private static PropertyChangeListener sharedPropertyChangedListener;
private static ComponentUI instance; private static ComponentUI instance;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
@@ -55,6 +59,38 @@ public class FlatToolTipUI
return instance; return instance;
} }
@Override
public void installUI( JComponent c ) {
super.installUI( c );
// update HTML renderer if necessary
FlatLabelUI.updateHTMLRenderer( c, ((JToolTip)c).getTipText(), false );
}
@Override
protected void installListeners( JComponent c ) {
super.installListeners( c );
if( sharedPropertyChangedListener == null ) {
sharedPropertyChangedListener = e -> {
String name = e.getPropertyName();
if( name == "text" || name == "font" || name == "foreground" ) {
JToolTip toolTip = (JToolTip) e.getSource();
FlatLabelUI.updateHTMLRenderer( toolTip, toolTip.getTipText(), false );
}
};
}
c.addPropertyChangeListener( sharedPropertyChangedListener );
}
@Override
protected void uninstallListeners( JComponent c ) {
super.uninstallListeners( c );
c.removePropertyChangeListener( sharedPropertyChangedListener );
}
@Override @Override
public Dimension getPreferredSize( JComponent c ) { public Dimension getPreferredSize( JComponent c ) {
if( isMultiLine( c ) ) { if( isMultiLine( c ) ) {
@@ -99,6 +135,6 @@ public class FlatToolTipUI
private boolean isMultiLine( JComponent c ) { private boolean isMultiLine( JComponent c ) {
String text = ((JToolTip)c).getTipText(); String text = ((JToolTip)c).getTipText();
return c.getClientProperty( "html" ) == null && text != null && text.indexOf( '\n' ) >= 0; return c.getClientProperty( BasicHTML.propertyKey ) == null && text != null && text.indexOf( '\n' ) >= 0;
} }
} }

View File

@@ -21,9 +21,16 @@ import java.awt.Component;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Insets; import java.awt.Insets;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JTree;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.JTree.DropLocation;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicTreeUI; import javax.swing.plaf.basic.BasicTreeUI;
import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.DefaultTreeCellRenderer;
@@ -33,13 +40,50 @@ import com.formdev.flatlaf.util.UIScale;
/** /**
* Provides the Flat LaF UI delegate for {@link javax.swing.JTree}. * Provides the Flat LaF UI delegate for {@link javax.swing.JTree}.
* *
* TODO document used UI defaults of superclass * <!-- BasicTreeUI -->
*
* @uiDefault Tree.font Font
* @uiDefault Tree.background Color
* @uiDefault Tree.hash Color
* @uiDefault Tree.dropLineColor Color
* @uiDefault Tree.expandedIcon Icon
* @uiDefault Tree.collapsedIcon Icon
* @uiDefault Tree.leftChildIndent int
* @uiDefault Tree.rightChildIndent int
* @uiDefault Tree.rowHeight int
* @uiDefault Tree.scrollsOnExpand boolean
* @uiDefault Tree.scrollsHorizontallyAndVertically boolean
* @uiDefault Tree.paintLines boolean
* @uiDefault Tree.lineTypeDashed boolean
* @uiDefault Tree.showsRootHandles boolean
* @uiDefault Tree.repaintWholeRow boolean
*
* <!-- DefaultTreeCellRenderer -->
*
* @uiDefault Tree.leafIcon Icon
* @uiDefault Tree.closedIcon Icon
* @uiDefault Tree.openIcon Icon
* @uiDefault Tree.textBackground Color
* @uiDefault Tree.textForeground Color
* @uiDefault Tree.selectionBackground Color
* @uiDefault Tree.selectionForeground Color
* @uiDefault Tree.selectionBorderColor Color focus indicator border color
* @uiDefault Tree.drawsFocusBorderAroundIcon boolean
* @uiDefault Tree.drawDashedFocusIndicator boolean
* @uiDefault Tree.rendererFillBackground boolean default is true
* @uiDefault Tree.rendererMargins Insets
* @uiDefault Tree.dropCellBackground Color
* @uiDefault Tree.dropCellForeground Color
*
* <!-- FlatTreeUI -->
* *
* @uiDefault Tree.border Border * @uiDefault Tree.border Border
* @uiDefault Tree.selectionBackground Color * @uiDefault Tree.selectionBackground Color
* @uiDefault Tree.selectionForeground Color * @uiDefault Tree.selectionForeground Color
* @uiDefault Tree.selectionInactiveBackground Color * @uiDefault Tree.selectionInactiveBackground Color
* @uiDefault Tree.selectionInactiveForeground Color * @uiDefault Tree.selectionInactiveForeground Color
* @uiDefault Tree.wideSelection boolean
* @uiDefault Tree.showCellFocusIndicator boolean
* *
* @author Karl Tauber * @author Karl Tauber
*/ */
@@ -50,6 +94,9 @@ public class FlatTreeUI
protected Color selectionForeground; protected Color selectionForeground;
protected Color selectionInactiveBackground; protected Color selectionInactiveBackground;
protected Color selectionInactiveForeground; protected Color selectionInactiveForeground;
protected Color selectionBorderColor;
protected boolean wideSelection;
protected boolean showCellFocusIndicator;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
return new FlatTreeUI(); return new FlatTreeUI();
@@ -65,6 +112,9 @@ public class FlatTreeUI
selectionForeground = UIManager.getColor( "Tree.selectionForeground" ); selectionForeground = UIManager.getColor( "Tree.selectionForeground" );
selectionInactiveBackground = UIManager.getColor( "Tree.selectionInactiveBackground" ); selectionInactiveBackground = UIManager.getColor( "Tree.selectionInactiveBackground" );
selectionInactiveForeground = UIManager.getColor( "Tree.selectionInactiveForeground" ); selectionInactiveForeground = UIManager.getColor( "Tree.selectionInactiveForeground" );
selectionBorderColor = UIManager.getColor( "Tree.selectionBorderColor" );
wideSelection = UIManager.getBoolean( "Tree.wideSelection" );
showCellFocusIndicator = UIManager.getBoolean( "Tree.showCellFocusIndicator" );
// scale // scale
int rowHeight = FlatUIUtils.getUIInt( "Tree.rowHeight", 16 ); int rowHeight = FlatUIUtils.getUIInt( "Tree.rowHeight", 16 );
@@ -84,21 +134,115 @@ public class FlatTreeUI
selectionForeground = null; selectionForeground = null;
selectionInactiveBackground = null; selectionInactiveBackground = null;
selectionInactiveForeground = null; selectionInactiveForeground = null;
selectionBorderColor = null;
}
@Override
protected MouseListener createMouseListener() {
if( !wideSelection )
return super.createMouseListener();
return new BasicTreeUI.MouseHandler() {
@Override
public void mousePressed( MouseEvent e ) {
super.mousePressed( handleWideMouseEvent( e ) );
}
@Override
public void mouseReleased( MouseEvent e ) {
super.mouseReleased( handleWideMouseEvent( e ) );
}
@Override
public void mouseDragged( MouseEvent e ) {
super.mouseDragged( handleWideMouseEvent( e ) );
}
private MouseEvent handleWideMouseEvent( MouseEvent e ) {
if( !tree.isEnabled() || !SwingUtilities.isLeftMouseButton( e ) || e.isConsumed() )
return e;
int x = e.getX();
int y = e.getY();
TreePath path = getClosestPathForLocation( tree, x, y );
if( path == null || isLocationInExpandControl( path, x, y ) )
return e;
Rectangle bounds = getPathBounds( tree, path );
if( bounds == null || y < bounds.y || y >= (bounds.y + bounds.height) )
return e;
int newX = Math.max( bounds.x, Math.min( x, bounds.x + bounds.width - 1 ) );
if( newX == x )
return e;
// clone mouse event, but with new X coordinate
return new MouseEvent( e.getComponent(), e.getID(), e.getWhen(),
e.getModifiers() | e.getModifiersEx(), newX, e.getY(),
e.getClickCount(), e.isPopupTrigger(), e.getButton() );
}
};
}
@Override
protected PropertyChangeListener createPropertyChangeListener() {
if( !wideSelection )
return super.createPropertyChangeListener();
return new BasicTreeUI.PropertyChangeHandler() {
@Override
public void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e );
if( e.getSource() == tree && e.getPropertyName() == "dropLocation" ) {
JTree.DropLocation oldValue = (JTree.DropLocation) e.getOldValue();
repaintWideDropLocation( oldValue );
repaintWideDropLocation( tree.getDropLocation() );
}
}
private void repaintWideDropLocation(JTree.DropLocation loc) {
if( loc == null || isDropLine( loc ) )
return;
Rectangle r = tree.getPathBounds( loc.getPath() );
if( r != null )
tree.repaint( 0, r.y, tree.getWidth(), r.height );
}
};
} }
/** /**
* Same as super.paintRow(), but uses inactive selection background/foreground if tree is not focused. * Same as super.paintRow(), but supports wide selection and uses
* inactive selection background/foreground if tree is not focused.
*/ */
@Override @Override
protected void paintRow( Graphics g, Rectangle clipBounds, Insets insets, Rectangle bounds, TreePath path, int row, protected void paintRow( Graphics g, Rectangle clipBounds, Insets insets, Rectangle bounds,
boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf ) TreePath path, int row, boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf )
{ {
if( editingComponent != null && editingRow == row ) boolean isEditing = (editingComponent != null && editingRow == row);
return; boolean hasFocus = FlatUIUtils.isPermanentFocusOwner( tree );
boolean hasFocus = tree.hasFocus();
boolean cellHasFocus = hasFocus && (row == getLeadSelectionRow()); boolean cellHasFocus = hasFocus && (row == getLeadSelectionRow());
boolean isSelected = tree.isRowSelected( row ); boolean isSelected = tree.isRowSelected( row );
boolean isDropRow = isDropRow( row );
// wide selection background
if( wideSelection && (isSelected || isDropRow) ) {
// fill background
g.setColor( isDropRow
? UIManager.getColor( "Tree.dropCellBackground" )
: (hasFocus ? selectionBackground : selectionInactiveBackground) );
g.fillRect( 0, bounds.y, tree.getWidth(), bounds.height );
// paint expand/collapse icon
if( shouldPaintExpandControl( path, row, isExpanded, hasBeenExpanded, isLeaf ) ) {
paintExpandControl( g, clipBounds, insets, bounds,
path, row, isExpanded, hasBeenExpanded, isLeaf );
}
}
if( isEditing )
return;
// get renderer component // get renderer component
Component rendererComponent = currentCellRenderer.getTreeCellRendererComponent( tree, Component rendererComponent = currentCellRenderer.getTreeCellRendererComponent( tree,
@@ -106,7 +250,7 @@ public class FlatTreeUI
// apply inactive selection background/foreground if tree is not focused // apply inactive selection background/foreground if tree is not focused
Color oldBackgroundSelectionColor = null; Color oldBackgroundSelectionColor = null;
if( isSelected && !hasFocus ) { if( isSelected && !hasFocus && !isDropRow ) {
if( rendererComponent instanceof DefaultTreeCellRenderer ) { if( rendererComponent instanceof DefaultTreeCellRenderer ) {
DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer) rendererComponent; DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer) rendererComponent;
if( renderer.getBackgroundSelectionColor() == selectionBackground ) { if( renderer.getBackgroundSelectionColor() == selectionBackground ) {
@@ -122,11 +266,43 @@ public class FlatTreeUI
rendererComponent.setForeground( selectionInactiveForeground ); rendererComponent.setForeground( selectionInactiveForeground );
} }
// remove focus selection border if exactly one item is selected
Color oldBorderSelectionColor = null;
if( isSelected && hasFocus &&
(!showCellFocusIndicator || tree.getMinSelectionRow() == tree.getMaxSelectionRow()) &&
rendererComponent instanceof DefaultTreeCellRenderer )
{
DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer) rendererComponent;
if( renderer.getBorderSelectionColor() == selectionBorderColor ) {
oldBorderSelectionColor = renderer.getBorderSelectionColor();
renderer.setBorderSelectionColor( null );
}
}
// paint renderer // paint renderer
rendererPane.paintComponent( g, rendererComponent, tree, bounds.x, bounds.y, bounds.width, bounds.height, true ); rendererPane.paintComponent( g, rendererComponent, tree, bounds.x, bounds.y, bounds.width, bounds.height, true );
// restore background selection color // restore background selection color and border selection color
if( oldBackgroundSelectionColor != null ) if( oldBackgroundSelectionColor != null )
((DefaultTreeCellRenderer)rendererComponent).setBackgroundSelectionColor( oldBackgroundSelectionColor ); ((DefaultTreeCellRenderer)rendererComponent).setBackgroundSelectionColor( oldBackgroundSelectionColor );
if( oldBorderSelectionColor != null )
((DefaultTreeCellRenderer)rendererComponent).setBorderSelectionColor( oldBorderSelectionColor );
}
/**
* Checks whether dropping on a row.
* See DefaultTreeCellRenderer.getTreeCellRendererComponent().
*/
private boolean isDropRow( int row ) {
JTree.DropLocation dropLocation = tree.getDropLocation();
return dropLocation != null &&
dropLocation.getChildIndex() == -1 &&
tree.getRowForPath( dropLocation.getPath() ) == row;
}
@Override
protected Rectangle getDropLineRect( DropLocation loc ) {
Rectangle r = super.getDropLineRect( loc );
return wideSelection ? new Rectangle( 0, r.y, tree.getWidth(), r.height ) : r;
} }
} }

View File

@@ -20,9 +20,11 @@ import java.awt.Color;
import java.awt.Component; import java.awt.Component;
import java.awt.Container; import java.awt.Container;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Insets; import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.RenderingHints; import java.awt.RenderingHints;
import java.awt.Shape; import java.awt.Shape;
@@ -37,8 +39,11 @@ import java.util.function.Consumer;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.plaf.ColorUIResource; import javax.swing.plaf.UIResource;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.util.DerivedColor; import com.formdev.flatlaf.util.DerivedColor;
import com.formdev.flatlaf.util.Graphics2DProxy;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.JavaCompatibility; import com.formdev.flatlaf.util.JavaCompatibility;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
@@ -81,6 +86,13 @@ public class FlatUIUtils
insets1.right + insets2.right ); insets1.right + insets2.right );
} }
public static void setInsets( Insets dest, Insets src ) {
dest.top = src.top;
dest.left = src.left;
dest.bottom = src.bottom;
dest.right = src.right;
}
public static Color getUIColor( String key, int defaultColorRGB ) { public static Color getUIColor( String key, int defaultColorRGB ) {
Color color = UIManager.getColor( key ); Color color = UIManager.getColor( key );
return (color != null) ? color : new Color( defaultColorRGB ); return (color != null) ? color : new Color( defaultColorRGB );
@@ -101,14 +113,35 @@ public class FlatUIUtils
return (value instanceof Integer) ? (Integer) value : defaultValue; return (value instanceof Integer) ? (Integer) value : defaultValue;
} }
public static float getUIFloat( String key, float defaultValue ) {
Object value = UIManager.get( key );
return (value instanceof Number) ? ((Number)value).floatValue() : defaultValue;
}
public static Color nonUIResource( Color c ) { public static Color nonUIResource( Color c ) {
return (c instanceof ColorUIResource) ? new Color( c.getRGB(), true ) : c; return (c instanceof UIResource) ? new Color( c.getRGB(), true ) : c;
}
public static Font nonUIResource( Font font ) {
return (font instanceof UIResource) ? font.deriveFont( font.getStyle() ) : font;
}
public static int minimumWidth( JComponent c, int minimumWidth ) {
return FlatClientProperties.clientPropertyInt( c, FlatClientProperties.MINIMUM_WIDTH, minimumWidth );
}
public static int minimumHeight( JComponent c, int minimumHeight ) {
return FlatClientProperties.clientPropertyInt( c, FlatClientProperties.MINIMUM_HEIGHT, minimumHeight );
} }
public static boolean isTableCellEditor( Component c ) { public static boolean isTableCellEditor( Component c ) {
return c instanceof JComponent && Boolean.TRUE.equals( ((JComponent)c).getClientProperty( "JComboBox.isTableCellEditor" ) ); return c instanceof JComponent && Boolean.TRUE.equals( ((JComponent)c).getClientProperty( "JComboBox.isTableCellEditor" ) );
} }
public static boolean isPermanentFocusOwner( Component c ) {
return (KeyboardFocusManager.getCurrentKeyboardFocusManager().getPermanentFocusOwner() == c);
}
/** /**
* Sets rendering hints used for painting. * Sets rendering hints used for painting.
*/ */
@@ -125,12 +158,90 @@ public class FlatUIUtils
} }
/** /**
* Draws a round rectangle. * 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 width 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 drawRoundRectangle( Graphics2D g, int x, int y, int width, int height, public static void paintComponentOuterBorder( Graphics2D g, int x, int y, int width, int height,
float focusWidth, float lineWidth, float arc ) float focusWidth, float lineWidth, float arc )
{ {
float arc2 = arc > lineWidth ? arc - lineWidth : 0f; 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( arc > 0 && arc < UIScale.scale( 10 ) )
outerArc -= UIScale.scale( 2f );
if( outerArc < 0 )
outerArc = 0;
if( innerArc < 0 )
innerArc = 0;
Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD );
path.append( new RoundRectangle2D.Float( x, y, width, height, outerArc, outerArc ), false );
path.append( new RoundRectangle2D.Float( x + ow, y + ow, width - (ow * 2), height - (ow * 2), innerArc, 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 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 )
{
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 arc2 = arc - (lineWidth * 2);
if( arc < 0 )
arc = 0;
if( arc2 < 0 )
arc2 = 0;
RoundRectangle2D.Float r1 = new RoundRectangle2D.Float( RoundRectangle2D.Float r1 = new RoundRectangle2D.Float(
x + focusWidth, y + focusWidth, x + focusWidth, y + focusWidth,
@@ -146,11 +257,38 @@ public class FlatUIUtils
} }
/** /**
* Fills a round rectangle. * Fills the background of a component with a round rectangle.
* <p>
* The bounds of the painted round rectangle are
* {@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
*/ */
public static void fillRoundRectangle( Graphics2D g, int x, int y, int width, int height, public static void paintComponentBackground( Graphics2D g, int x, int y, int width, int height,
float focusWidth, float arc ) float focusWidth, float arc )
{ {
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) );
} );
return;
}
paintComponentBackgroundImpl( g, x, y, width, height, focusWidth, arc );
}
private static void paintComponentBackgroundImpl( Graphics2D g, int x, int y, int width, int height,
float focusWidth, float arc )
{
if( arc < 0 )
arc = 0;
g.fill( new RoundRectangle2D.Float( g.fill( new RoundRectangle2D.Float(
x + focusWidth, y + focusWidth, x + focusWidth, y + focusWidth,
width - focusWidth * 2, height - focusWidth * 2, arc, arc ) ); width - focusWidth * 2, height - focusWidth * 2, arc, arc ) );
@@ -190,22 +328,14 @@ public class FlatUIUtils
} }
/** /**
* Paints an outline border. * Creates a not-filled rectangle shape with the given line width.
*/ */
public static void paintOutlineBorder( Graphics2D g, int x, int y, int width, int height, public static Path2D createRectangle( float x, float y, float width, float height, float lineWidth ) {
float focusWidth, float lineWidth, float arc )
{
float outerArc = (arc > 0) ? arc + focusWidth - UIScale.scale( 2f ) : focusWidth;
float ow = focusWidth + lineWidth;
Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD ); Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD );
path.append( createOutlinePath( x, y, width, height, outerArc ), false ); path.append( new Rectangle2D.Float( x, y, width, height ), false );
path.append( createOutlinePath( x + ow, y + ow, width - (ow * 2), height - (ow * 2), outerArc - ow ), false ); path.append( new Rectangle2D.Float( x + lineWidth, y + lineWidth,
g.fill( path ); width - (lineWidth * 2), height - (lineWidth * 2) ), false );
} return path;
private static Shape createOutlinePath( float x, float y, float width, float height, float arc ) {
return createRoundRectanglePath( x, y, width, height, arc, arc, arc, arc );
} }
/** /**
@@ -222,7 +352,7 @@ public class FlatUIUtils
} }
/** /**
* Creates a filled rounded rectangle shape and allows specifying the radius or each corner. * Creates a filled rounded rectangle shape and allows specifying the radius of each corner.
*/ */
public static Shape createRoundRectanglePath( float x, float y, float width, float height, public static Shape createRoundRectanglePath( float x, float y, float width, float height,
float arcTopLeft, float arcTopRight, float arcBottomLeft, float arcBottomRight ) float arcTopLeft, float arcTopRight, float arcBottomLeft, float arcBottomRight )
@@ -256,10 +386,16 @@ public class FlatUIUtils
return rect; return rect;
} }
/**
* Creates a closed path for the given points.
*/
public static Path2D createPath( double... points ) { public static Path2D createPath( double... points ) {
return createPath( true, points ); return createPath( true, points );
} }
/**
* Creates a open or closed path for the given points.
*/
public static Path2D createPath( boolean close, double... points ) { public static Path2D createPath( boolean close, double... points ) {
Path2D path = new Path2D.Float(); Path2D path = new Path2D.Float();
path.moveTo( points[0], points[1] ); path.moveTo( points[0], points[1] );
@@ -292,6 +428,23 @@ public class FlatUIUtils
public static void drawStringUnderlineCharAt( JComponent c, Graphics g, public static void drawStringUnderlineCharAt( JComponent c, Graphics g,
String text, int underlinedIndex, int x, int y ) String text, int underlinedIndex, int x, int y )
{ {
// scale underline height if necessary
if( underlinedIndex >= 0 && UIScale.getUserScaleFactor() > 1 ) {
g = new Graphics2DProxy( (Graphics2D) g ) {
@Override
public void fillRect( int x, int y, int width, int height ) {
if( height == 1 ) {
// scale height and correct y position
// (using 0.9f so that underline height is 1 at scale factor 1.5x)
height = Math.round( UIScale.scale( 0.9f ) );
y += height - 1;
}
super.fillRect( x, y, width, height );
}
};
}
JavaCompatibility.drawStringUnderlineCharAt( c, g, text, underlinedIndex, x, y ); JavaCompatibility.drawStringUnderlineCharAt( c, g, text, underlinedIndex, x, y );
} }

View File

@@ -25,7 +25,7 @@ import java.awt.Color;
*/ */
public class ColorFunctions public class ColorFunctions
{ {
public static Color applyFunctions( Color color, ColorFunction[] functions ) { public static Color applyFunctions( Color color, ColorFunction... functions ) {
float[] hsl = HSLColor.fromRGB( color ); float[] hsl = HSLColor.fromRGB( color );
float alpha = color.getAlpha() / 255f; float alpha = color.getAlpha() / 255f;

View File

@@ -32,8 +32,8 @@ public class DerivedColor
{ {
private final ColorFunction[] functions; private final ColorFunction[] functions;
public DerivedColor( ColorFunction... functions ) { public DerivedColor( Color defaultColor, ColorFunction... functions ) {
super( Color.red ); super( (defaultColor != null) ? defaultColor : Color.red );
this.functions = functions; this.functions = functions;
} }

View File

@@ -0,0 +1,479 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.util;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.Image;
import java.awt.Paint;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.RenderingHints.Key;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ImageObserver;
import java.awt.image.RenderedImage;
import java.awt.image.renderable.RenderableImage;
import java.text.AttributedCharacterIterator;
import java.util.Map;
/**
* A proxy for {@link Graphics2D}.
*
* @author Karl Tauber
*/
public class Graphics2DProxy
extends Graphics2D
{
private final Graphics2D delegate;
public Graphics2DProxy( Graphics2D delegate ) {
this.delegate = delegate;
}
@Override
public Graphics create() {
return delegate.create();
}
@Override
public Graphics create( int x, int y, int width, int height ) {
return delegate.create( x, y, width, height );
}
@Override
public Color getColor() {
return delegate.getColor();
}
@Override
public void setColor( Color c ) {
delegate.setColor( c );
}
@Override
public void setPaintMode() {
delegate.setPaintMode();
}
@Override
public void setXORMode( Color c1 ) {
delegate.setXORMode( c1 );
}
@Override
public Font getFont() {
return delegate.getFont();
}
@Override
public void setFont( Font font ) {
delegate.setFont( font );
}
@Override
public FontMetrics getFontMetrics() {
return delegate.getFontMetrics();
}
@Override
public FontMetrics getFontMetrics( Font f ) {
return delegate.getFontMetrics( f );
}
@Override
public Rectangle getClipBounds() {
return delegate.getClipBounds();
}
@Override
public void clipRect( int x, int y, int width, int height ) {
delegate.clipRect( x, y, width, height );
}
@Override
public void setClip( int x, int y, int width, int height ) {
delegate.setClip( x, y, width, height );
}
@Override
public Shape getClip() {
return delegate.getClip();
}
@Override
public void setClip( Shape clip ) {
delegate.setClip( clip );
}
@Override
public void copyArea( int x, int y, int width, int height, int dx, int dy ) {
delegate.copyArea( x, y, width, height, dx, dy );
}
@Override
public void drawLine( int x1, int y1, int x2, int y2 ) {
delegate.drawLine( x1, y1, x2, y2 );
}
@Override
public void fillRect( int x, int y, int width, int height ) {
delegate.fillRect( x, y, width, height );
}
@Override
public void drawRect( int x, int y, int width, int height ) {
delegate.drawRect( x, y, width, height );
}
@Override
public void clearRect( int x, int y, int width, int height ) {
delegate.clearRect( x, y, width, height );
}
@Override
public void drawRoundRect( int x, int y, int width, int height, int arcWidth, int arcHeight ) {
delegate.drawRoundRect( x, y, width, height, arcWidth, arcHeight );
}
@Override
public void fillRoundRect( int x, int y, int width, int height, int arcWidth, int arcHeight ) {
delegate.fillRoundRect( x, y, width, height, arcWidth, arcHeight );
}
@Override
public void drawOval( int x, int y, int width, int height ) {
delegate.drawOval( x, y, width, height );
}
@Override
public void fillOval( int x, int y, int width, int height ) {
delegate.fillOval( x, y, width, height );
}
@Override
public void drawArc( int x, int y, int width, int height, int startAngle, int arcAngle ) {
delegate.drawArc( x, y, width, height, startAngle, arcAngle );
}
@Override
public void fillArc( int x, int y, int width, int height, int startAngle, int arcAngle ) {
delegate.fillArc( x, y, width, height, startAngle, arcAngle );
}
@Override
public void drawPolyline( int[] xPoints, int[] yPoints, int nPoints ) {
delegate.drawPolyline( xPoints, yPoints, nPoints );
}
@Override
public void drawPolygon( int[] xPoints, int[] yPoints, int nPoints ) {
delegate.drawPolygon( xPoints, yPoints, nPoints );
}
@Override
public void drawPolygon( Polygon p ) {
delegate.drawPolygon( p );
}
@Override
public void fillPolygon( int[] xPoints, int[] yPoints, int nPoints ) {
delegate.fillPolygon( xPoints, yPoints, nPoints );
}
@Override
public void fillPolygon( Polygon p ) {
delegate.fillPolygon( p );
}
@Override
public void drawChars( char[] data, int offset, int length, int x, int y ) {
delegate.drawChars( data, offset, length, x, y );
}
@Override
public void drawBytes( byte[] data, int offset, int length, int x, int y ) {
delegate.drawBytes( data, offset, length, x, y );
}
@Override
public boolean drawImage( Image img, int x, int y, ImageObserver observer ) {
return delegate.drawImage( img, x, y, observer );
}
@Override
public boolean drawImage( Image img, int x, int y, int width, int height, ImageObserver observer ) {
return delegate.drawImage( img, x, y, width, height, observer );
}
@Override
public boolean drawImage( Image img, int x, int y, Color bgcolor, ImageObserver observer ) {
return delegate.drawImage( img, x, y, bgcolor, observer );
}
@Override
public boolean drawImage( Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer ) {
return delegate.drawImage( img, x, y, width, height, bgcolor, observer );
}
@Override
public boolean drawImage( Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer ) {
return delegate.drawImage( img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, observer );
}
@Override
public boolean drawImage( Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgcolor, ImageObserver observer ) {
return delegate.drawImage( img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, bgcolor, observer );
}
@Override
public void dispose() {
delegate.dispose();
}
@Override
public void finalize() {
delegate.finalize();
}
@Override
public String toString() {
return delegate.toString();
}
@SuppressWarnings( "deprecation" )
@Override
public Rectangle getClipRect() {
return delegate.getClipRect();
}
@Override
public boolean hitClip( int x, int y, int width, int height ) {
return delegate.hitClip( x, y, width, height );
}
@Override
public Rectangle getClipBounds( Rectangle r ) {
return delegate.getClipBounds( r );
}
@Override
public void draw3DRect( int x, int y, int width, int height, boolean raised ) {
delegate.draw3DRect( x, y, width, height, raised );
}
@Override
public void fill3DRect( int x, int y, int width, int height, boolean raised ) {
delegate.fill3DRect( x, y, width, height, raised );
}
@Override
public void draw( Shape s ) {
delegate.draw( s );
}
@Override
public boolean drawImage( Image img, AffineTransform xform, ImageObserver obs ) {
return delegate.drawImage( img, xform, obs );
}
@Override
public void drawImage( BufferedImage img, BufferedImageOp op, int x, int y ) {
delegate.drawImage( img, op, x, y );
}
@Override
public void drawRenderedImage( RenderedImage img, AffineTransform xform ) {
delegate.drawRenderedImage( img, xform );
}
@Override
public void drawRenderableImage( RenderableImage img, AffineTransform xform ) {
delegate.drawRenderableImage( img, xform );
}
@Override
public void drawString( String str, int x, int y ) {
delegate.drawString( str, x, y );
}
@Override
public void drawString( String str, float x, float y ) {
delegate.drawString( str, x, y );
}
@Override
public void drawString( AttributedCharacterIterator iterator, int x, int y ) {
delegate.drawString( iterator, x, y );
}
@Override
public void drawString( AttributedCharacterIterator iterator, float x, float y ) {
delegate.drawString( iterator, x, y );
}
@Override
public void drawGlyphVector( GlyphVector g, float x, float y ) {
delegate.drawGlyphVector( g, x, y );
}
@Override
public void fill( Shape s ) {
delegate.fill( s );
}
@Override
public boolean hit( Rectangle rect, Shape s, boolean onStroke ) {
return delegate.hit( rect, s, onStroke );
}
@Override
public GraphicsConfiguration getDeviceConfiguration() {
return delegate.getDeviceConfiguration();
}
@Override
public void setComposite( Composite comp ) {
delegate.setComposite( comp );
}
@Override
public void setPaint( Paint paint ) {
delegate.setPaint( paint );
}
@Override
public void setStroke( Stroke s ) {
delegate.setStroke( s );
}
@Override
public void setRenderingHint( Key hintKey, Object hintValue ) {
delegate.setRenderingHint( hintKey, hintValue );
}
@Override
public Object getRenderingHint( Key hintKey ) {
return delegate.getRenderingHint( hintKey );
}
@Override
public void setRenderingHints( Map<?, ?> hints ) {
delegate.setRenderingHints( hints );
}
@Override
public void addRenderingHints( Map<?, ?> hints ) {
delegate.addRenderingHints( hints );
}
@Override
public RenderingHints getRenderingHints() {
return delegate.getRenderingHints();
}
@Override
public void translate( int x, int y ) {
delegate.translate( x, y );
}
@Override
public void translate( double tx, double ty ) {
delegate.translate( tx, ty );
}
@Override
public void rotate( double theta ) {
delegate.rotate( theta );
}
@Override
public void rotate( double theta, double x, double y ) {
delegate.rotate( theta, x, y );
}
@Override
public void scale( double sx, double sy ) {
delegate.scale( sx, sy );
}
@Override
public void shear( double shx, double shy ) {
delegate.shear( shx, shy );
}
@Override
public void transform( AffineTransform Tx ) {
delegate.transform( Tx );
}
@Override
public void setTransform( AffineTransform Tx ) {
delegate.setTransform( Tx );
}
@Override
public AffineTransform getTransform() {
return delegate.getTransform();
}
@Override
public Paint getPaint() {
return delegate.getPaint();
}
@Override
public Composite getComposite() {
return delegate.getComposite();
}
@Override
public void setBackground( Color color ) {
delegate.setBackground( color );
}
@Override
public Color getBackground() {
return delegate.getBackground();
}
@Override
public Stroke getStroke() {
return delegate.getStroke();
}
@Override
public void clip( Shape s ) {
delegate.clip( s );
}
@Override
public FontRenderContext getFontRenderContext() {
return delegate.getFontRenderContext();
}
}

View File

@@ -0,0 +1,87 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.formdev.flatlaf.util;
import java.awt.image.RGBImageFilter;
// based on https://github.com/JetBrains/intellij-community/blob/3840eab54746f5c4f301bb3ac78f00a980b5fd6e/platform/util/ui/src/com/intellij/util/ui/UIUtil.java#L253-L347
/**
* An image filter that turns an image into a grayscale image.
* Used for icons in disabled buttons and labels.
*/
public class GrayFilter
extends RGBImageFilter
{
private final float brightness;
private final float contrast;
private final int alpha;
private final int origContrast;
private final int origBrightness;
public static GrayFilter createDisabledIconFilter( boolean dark ) {
return dark
? new GrayFilter( -20, -70, 100 )
: new GrayFilter( 25, -25, 100 );
}
/**
* @param brightness in range [-100..100] where 0 has no effect
* @param contrast in range [-100..100] where 0 has no effect
* @param alpha in range [0..100] where 0 is transparent, 100 has no effect
*/
public GrayFilter( int brightness, int contrast, int alpha ) {
this.origBrightness = Math.max( -100, Math.min( 100, brightness ) );
this.origContrast = Math.max( -100, Math.min( 100, contrast ) );
this.alpha = Math.max( 0, Math.min( 100, alpha ) );
this.brightness = (float) (Math.pow( origBrightness, 3 ) / (100f * 100f)); // cubic in [0..100]
this.contrast = origContrast / 100f;
canFilterIndexColorModel = true;
}
public GrayFilter() {
this( 0, 0, 100 );
}
public int getBrightness() {
return origBrightness;
}
public int getContrast() {
return origContrast;
}
public int getAlpha() {
return alpha;
}
@Override
public int filterRGB( int x, int y, int rgb ) {
// use NTSC conversion formula
int gray = (int)(
0.30 * (rgb >> 16 & 0xff) +
0.59 * (rgb >> 8 & 0xff) +
0.11 * (rgb & 0xff));
if( brightness >= 0 )
gray = (int) ((gray + brightness * 255) / (1 + brightness));
else
gray = (int) (gray / (1 - brightness));
if( contrast >= 0 ) {
if( gray >= 127 )
gray = (int) (gray + (255 - gray) * contrast);
else
gray = (int) (gray - gray * contrast);
} else
gray = (int) (127 + (gray - 127) * (contrast + 1));
int a = (alpha != 100)
? (((rgb >> 24) & 0xff) * alpha / 100) << 24
: (rgb & 0xff000000);
return a | (gray << 16) | (gray << 8) | gray;
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright 2019 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.util;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import javax.swing.JComponent;
/**
* @author Karl Tauber
*/
public class HiDPIUtils
{
public interface Painter {
public void paint( Graphics2D g, int x, int y, int width, int height, double scaleFactor );
}
public static void paintAtScale1x( Graphics2D g, JComponent c, Painter painter ) {
paintAtScale1x( g, 0, 0, c.getWidth(), c.getHeight(), painter );
}
/**
* Paint at system scale factor 1x to avoid rounding issues at 125%, 150% and 175% scaling.
* <p>
* Scales the given Graphics2D down to 100% and invokes the
* given painter passing scaled x, y, width and height.
* <p>
* Uses the same scaling calculation as the JRE uses.
*/
public static void paintAtScale1x( Graphics2D g, int x, int y, int width, int height, Painter painter ) {
// save original transform
AffineTransform transform = g.getTransform();
// check whether scaled
if( transform.getScaleX() == 1 && transform.getScaleY() == 1 ) {
painter.paint( g, x, y, width, height, 1 );
return;
}
// scale rectangle
Rectangle2D.Double scaledRect = scale( transform, x, y, width, height );
try {
// unscale to factor 1.0 and move origin (to whole numbers)
g.setTransform( new AffineTransform( 1, 0, 0, 1,
Math.floor( scaledRect.x ), Math.floor( scaledRect.y ) ) );
int swidth = (int) scaledRect.width;
int sheight = (int) scaledRect.height;
// paint
painter.paint( g, 0, 0, swidth, sheight, transform.getScaleX() );
} finally {
// restore original transform
g.setTransform( transform );
}
}
/**
* Scales a rectangle in the same way as the JRE does in
* sun.java2d.pipe.PixelToParallelogramConverter.fillRectangle(),
* which is used by Graphics.fillRect().
*/
private static Rectangle2D.Double scale( AffineTransform transform, int x, int y, int width, int height ) {
double dx1 = transform.getScaleX();
double dy2 = transform.getScaleY();
double px = x * dx1 + transform.getTranslateX();
double py = y * dy2 + transform.getTranslateY();
dx1 *= width;
dy2 *= height;
double newx = normalize( px );
double newy = normalize( py );
dx1 = normalize( px + dx1 ) - newx;
dy2 = normalize( py + dy2 ) - newy;
return new Rectangle2D.Double( newx, newy, dx1, dy2 );
}
private static double normalize( double value ) {
return Math.floor( value + 0.25 ) + 0.25;
}
}

View File

@@ -20,7 +20,10 @@ import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JComponent; import javax.swing.JComponent;
import com.formdev.flatlaf.FlatLaf;
/** /**
* Provides Java version compatibility methods. * Provides Java version compatibility methods.
@@ -52,7 +55,7 @@ public class JavaCompatibility
? new Class[] { JComponent.class, Graphics2D.class, String.class, int.class, float.class, float.class } ? new Class[] { JComponent.class, Graphics2D.class, String.class, int.class, float.class, float.class }
: new Class[] { JComponent.class, Graphics.class, String.class, int.class, int.class, int.class } ); : new Class[] { JComponent.class, Graphics.class, String.class, int.class, int.class, int.class } );
} catch( Exception ex ) { } catch( Exception ex ) {
ex.printStackTrace(); Logger.getLogger( FlatLaf.class.getName() ).log( Level.SEVERE, null, ex );
throw new RuntimeException( ex ); throw new RuntimeException( ex );
} }
} }
@@ -64,7 +67,7 @@ public class JavaCompatibility
else else
drawStringUnderlineCharAtMethod.invoke( null, c, g, text, underlinedIndex, x, y ); drawStringUnderlineCharAtMethod.invoke( null, c, g, text, underlinedIndex, x, y );
} catch( IllegalAccessException | IllegalArgumentException | InvocationTargetException ex ) { } catch( IllegalAccessException | IllegalArgumentException | InvocationTargetException ex ) {
ex.printStackTrace(); Logger.getLogger( FlatLaf.class.getName() ).log( Level.SEVERE, null, ex );
throw new RuntimeException( ex ); throw new RuntimeException( ex );
} }
} }

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.util;
import java.awt.Image;
import java.util.function.Function;
/**
* Support for multi-resolution images available since Java 9.
*
* @author Karl Tauber
*/
public class MultiResolutionImageSupport
{
public static boolean isAvailable() {
return false;
}
public static boolean isMultiResolutionImage( Image image ) {
return false;
}
public static Image create( int baseImageIndex, Image... resolutionVariants ) {
return resolutionVariants[baseImageIndex];
}
public static Image map( Image image, Function<Image, Image> mapper ) {
return mapper.apply( image );
}
}

View File

@@ -1,77 +0,0 @@
/*
* Copyright 2019 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.util;
import static com.formdev.flatlaf.util.UIScale.scale;
/**
* A number that scales its value.
*
* NOTE:
* Using ScaledNumber in UI defaults works only if the value is get with
* sun.swing.DefaultLookup.getInt(), which is used by some basic UI delegates,
* because this method uses "instanceof Number".
* UIManager.getInt() on the other hand uses "instanceof Integer" and does not work.
*
* @author Karl Tauber
*/
public class ScaledNumber
extends Number
{
private final int value;
public ScaledNumber( int value ) {
this.value = value;
}
@Override
public int intValue() {
return scale( value );
}
@Override
public long longValue() {
return scale( value );
}
@Override
public float floatValue() {
return scale( (float) value );
}
@Override
public double doubleValue() {
return scale( (float) value );
}
@Override
public int hashCode() {
return Integer.hashCode( value );
}
@Override
public boolean equals( Object obj ) {
return (obj instanceof ScaledNumber)
? (value == ((ScaledNumber)obj).value)
: false;
}
@Override
public String toString() {
return Integer.toString( value );
}
}

View File

@@ -24,6 +24,7 @@ import java.awt.GraphicsEnvironment;
import java.awt.Insets; import java.awt.Insets;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
import javax.swing.UIManager; import javax.swing.UIManager;
@@ -48,12 +49,15 @@ import javax.swing.plaf.UIResource;
* *
* 2) user scaling mode * 2) user scaling mode
* *
* This mode is mainly for Java 8 compatibility, but is also used on Linux. * This mode is mainly for Java 8 compatibility, but is also used on Linux
* or if the default font is changed.
* The user scale factor is computed based on the used font. * The user scale factor is computed based on the used font.
* The JRE does not scale anything. * The JRE does not scale anything.
* So we have to invoke {@link #scale(float)} where necessary. * So we have to invoke {@link #scale(float)} where necessary.
* There is only one user scale factor for all displays. * There is only one user scale factor for all displays.
* The user scale factor may change if the active LaF or "Label.font" has changed. * The user scale factor may change if the active LaF, "defaultFont" or "Label.font" has changed.
* If system scaling mode is available the user scale factor is usually 1,
* but may be larger on Linux or if the default font is changed.
* *
* @author Karl Tauber * @author Karl Tauber
*/ */
@@ -61,6 +65,20 @@ public class UIScale
{ {
private static final boolean DEBUG = false; private static final boolean DEBUG = false;
private static PropertyChangeSupport changeSupport;
public static void addPropertyChangeListener( PropertyChangeListener listener ) {
if( changeSupport == null )
changeSupport = new PropertyChangeSupport( UIScale.class );
changeSupport.addPropertyChangeListener( listener );
}
public static void removePropertyChangeListener( PropertyChangeListener listener ) {
if( changeSupport == null )
return;
changeSupport.removePropertyChangeListener( listener );
}
//---- system scaling (Java 9) -------------------------------------------- //---- system scaling (Java 9) --------------------------------------------
private static Boolean jreHiDPI; private static Boolean jreHiDPI;
@@ -97,7 +115,7 @@ public class UIScale
} }
public static double getSystemScaleFactor( GraphicsConfiguration gc ) { public static double getSystemScaleFactor( GraphicsConfiguration gc ) {
return isSystemScalingEnabled() ? gc.getDefaultTransform().getScaleX() : 1; return (isSystemScalingEnabled() && gc != null) ? gc.getDefaultTransform().getScaleX() : 1;
} }
//---- user scaling (Java 8) ---------------------------------------------- //---- user scaling (Java 8) ----------------------------------------------
@@ -110,27 +128,33 @@ public class UIScale
return; return;
initialized = true; initialized = true;
if( isUserScalingEnabled() ) { if( !isUserScalingEnabled() )
// listener to update scale factor if LaF changed or if Label.font changed return;
// (e.g. option "Override default fonts" in IntelliJ IDEA)
PropertyChangeListener listener = new PropertyChangeListener() { // listener to update scale factor if LaF changed, "defaultFont" or "Label.font" changed
@Override PropertyChangeListener listener = new PropertyChangeListener() {
public void propertyChange( PropertyChangeEvent e ) { @Override
String propName = e.getPropertyName(); public void propertyChange( PropertyChangeEvent e ) {
if( "lookAndFeel".equals( propName ) ) { switch( e.getPropertyName() ) {
case "lookAndFeel":
// it is not necessary (and possible) to remove listener of old LaF defaults // it is not necessary (and possible) to remove listener of old LaF defaults
if( e.getNewValue() instanceof LookAndFeel ) if( e.getNewValue() instanceof LookAndFeel )
UIManager.getLookAndFeelDefaults().addPropertyChangeListener( this ); UIManager.getLookAndFeelDefaults().addPropertyChangeListener( this );
updateScaleFactor(); updateScaleFactor();
} else if( "Label.font".equals( propName ) ) break;
updateScaleFactor();
}
};
UIManager.addPropertyChangeListener( listener );
UIManager.getLookAndFeelDefaults().addPropertyChangeListener( listener );
updateScaleFactor(); case "defaultFont":
} case "Label.font":
updateScaleFactor();
break;
}
}
};
UIManager.addPropertyChangeListener( listener );
UIManager.getDefaults().addPropertyChangeListener( listener );
UIManager.getLookAndFeelDefaults().addPropertyChangeListener( listener );
updateScaleFactor();
} }
private static void updateScaleFactor() { private static void updateScaleFactor() {
@@ -141,7 +165,9 @@ public class UIScale
// because even if we are on a HiDPI display it is not sure // because even if we are on a HiDPI display it is not sure
// that a larger font size is set by the current LaF // that a larger font size is set by the current LaF
// (e.g. can avoid large icons with small text) // (e.g. can avoid large icons with small text)
Font font = UIManager.getFont( "Label.font" ); Font font = UIManager.getFont( "defaultFont" );
if( font == null )
font = UIManager.getFont( "Label.font" );
setUserScaleFactor( computeScaleFactor( font ) ); setUserScaleFactor( computeScaleFactor( font ) );
} }
@@ -156,6 +182,9 @@ public class UIScale
// Tahoma 11 is used at 100% // Tahoma 11 is used at 100%
if( "Tahoma".equals( font.getFamily() ) ) if( "Tahoma".equals( font.getFamily() ) )
fontSizeDivider = 11f; fontSizeDivider = 11f;
} else if( SystemInfo.IS_MAC ) {
// default font size on macOS is 13
fontSizeDivider = 13f;
} else if( SystemInfo.IS_LINUX ) { } else if( SystemInfo.IS_LINUX ) {
// default font size for Unity and Gnome is 15 and for KDE it is 13 // default font size for Unity and Gnome is 15 and for KDE it is 13
fontSizeDivider = SystemInfo.IS_KDE ? 13f : 15f; fontSizeDivider = SystemInfo.IS_KDE ? 13f : 15f;
@@ -165,26 +194,17 @@ public class UIScale
} }
private static boolean isUserScalingEnabled() { private static boolean isUserScalingEnabled() {
if( isSystemScalingEnabled() && !SystemInfo.IS_LINUX )
return false; // disable user scaling if JRE scales
// same as in IntelliJ IDEA // same as in IntelliJ IDEA
String hidpi = System.getProperty( "hidpi" ); String hidpi = System.getProperty( "hidpi" );
return (hidpi != null) ? Boolean.parseBoolean( hidpi ) : true; return (hidpi != null) ? Boolean.parseBoolean( hidpi ) : true;
} }
/** /**
* Applies a custom scale factor given in system properties "flatlaf.uiScale" * Applies a custom scale factor given in system property "flatlaf.uiScale"
* or "sun.java2d.uiScale" to the given font. * to the given font.
*/ */
public static FontUIResource applyCustomScaleFactor( FontUIResource font ) { public static FontUIResource applyCustomScaleFactor( FontUIResource font ) {
if( UIScale.isSystemScalingEnabled() )
return font;
String uiScale = System.getProperty( "flatlaf.uiScale" ); String uiScale = System.getProperty( "flatlaf.uiScale" );
if( uiScale == null )
uiScale = System.getProperty( "sun.java2d.uiScale" );
float scaleFactor = parseScaleFactor( uiScale ); float scaleFactor = parseScaleFactor( uiScale );
if( scaleFactor <= 0 ) if( scaleFactor <= 0 )
return font; return font;
@@ -194,7 +214,7 @@ public class UIScale
return font; return font;
int newFontSize = Math.round( (font.getSize() / fontScaleFactor) * scaleFactor ); int newFontSize = Math.round( (font.getSize() / fontScaleFactor) * scaleFactor );
return new FontUIResource( font.getFamily(), font.getStyle(), newFontSize ); return new FontUIResource( font.deriveFont( (float) newFontSize ) );
} }
/** /**
@@ -234,10 +254,14 @@ public class UIScale
else // round scale factor to 1/4 else // round scale factor to 1/4
scaleFactor = Math.round( scaleFactor * 4f ) / 4f; scaleFactor = Math.round( scaleFactor * 4f ) / 4f;
float oldScaleFactor = UIScale.scaleFactor;
UIScale.scaleFactor = scaleFactor; UIScale.scaleFactor = scaleFactor;
if( DEBUG ) if( DEBUG )
System.out.println( "HiDPI scale factor " + scaleFactor ); System.out.println( "HiDPI scale factor " + scaleFactor );
if( changeSupport != null )
changeSupport.firePropertyChange( "userScaleFactor", oldScaleFactor, scaleFactor );
} }
public static float scale( float value ) { public static float scale( float value ) {

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2020 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.util;
import java.awt.Image;
import java.awt.image.AbstractMultiResolutionImage;
import java.awt.image.BaseMultiResolutionImage;
import java.awt.image.MultiResolutionImage;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.function.Function;
import javax.swing.ImageIcon;
/**
* Support for multi-resolution images available since Java 9.
*
* @author Karl Tauber
*/
public class MultiResolutionImageSupport
{
public static boolean isAvailable() {
return true;
}
public static boolean isMultiResolutionImage( Image image ) {
return image instanceof MultiResolutionImage;
}
public static Image create( int baseImageIndex, Image... resolutionVariants ) {
return new BaseMultiResolutionImage( baseImageIndex, resolutionVariants );
}
public static Image map( Image image, Function<Image, Image> mapper ) {
return image instanceof MultiResolutionImage
? new MappedMultiResolutionImage( image, mapper )
: mapper.apply( image );
}
//---- class MappedMultiResolutionImage -----------------------------------
private static class MappedMultiResolutionImage
extends AbstractMultiResolutionImage
{
private final Image mrImage;
private final Function<Image, Image> mapper;
private final IdentityHashMap<Image, Image> cache = new IdentityHashMap<>();
MappedMultiResolutionImage( Image mrImage, Function<Image, Image> mapper ) {
assert mrImage instanceof MultiResolutionImage;
this.mrImage = mrImage;
this.mapper = mapper;
}
@Override
public Image getResolutionVariant( double destImageWidth, double destImageHeight ) {
Image variant = ((MultiResolutionImage)mrImage).getResolutionVariant( destImageWidth, destImageHeight );
return mapAndCacheImage( variant );
}
@Override
public List<Image> getResolutionVariants() {
List<Image> variants = ((MultiResolutionImage)mrImage).getResolutionVariants();
List<Image> mappedVariants = new ArrayList<>();
for( Image image : variants )
mappedVariants.add( mapAndCacheImage( image ) );
return mappedVariants;
}
@Override
protected Image getBaseImage() {
return mapAndCacheImage( mrImage );
}
private Image mapAndCacheImage( Image image ) {
return cache.computeIfAbsent( image, img -> {
return new ImageIcon( mapper.apply( img ) ).getImage();
} );
}
}
}

View File

@@ -19,6 +19,7 @@
*/ */
module com.formdev.flatlaf { module com.formdev.flatlaf {
requires java.desktop; requires java.desktop;
requires java.logging;
exports com.formdev.flatlaf; exports com.formdev.flatlaf;
exports com.formdev.flatlaf.icons; exports com.formdev.flatlaf.icons;

View File

@@ -30,6 +30,12 @@ Component.innerFocusWidth=0
Component.arrowType=triangle Component.arrowType=triangle
#---- ProgressBar ----
ProgressBar.foreground=#a0a0a0
ProgressBar.selectionForeground=@background
#---- RadioButton ---- #---- RadioButton ----
RadioButton.icon.centerDiameter=5 RadioButton.icon.centerDiameter=5

View File

@@ -28,12 +28,18 @@
@selectionInactiveForeground=@foreground @selectionInactiveForeground=@foreground
@disabledText=#777777 @disabledText=#777777
@textComponentBackground=#45494A @textComponentBackground=#45494A
@menuBackground=darken(@background,5%)
@menuHoverBackground=lighten(@menuBackground,10%)
@menuCheckBackground=lighten(@menuBackground,10%)
@menuCheckHoverBackground=lighten(@menuBackground,20%)
@cellFocusColor=#000000 @cellFocusColor=#000000
@icon=#adadad @icon=#adadad
# Button # Drop (use lazy colors for IntelliJ platform themes, which usually do not specify these colors)
@buttonHoverBackground=lighten(3%,autoInverse) @dropCellBackground=darken(List.selectionBackground,10%,lazy)
@buttonPressedBackground=lighten(6%,autoInverse) @dropCellForeground=lazy(List.selectionForeground)
@dropLineColor=lighten(List.selectionBackground,10%,lazy)
@dropLineShortColor=lighten(List.selectionBackground,30%,lazy)
#---- globals ---- #---- globals ----
@@ -50,43 +56,42 @@
*.disabledBackground=@background *.disabledBackground=@background
*.disabledForeground=@disabledText *.disabledForeground=@disabledText
*.disabledText=@disabledText *.disabledText=@disabledText
*.acceleratorForeground=#bbbbbb *.acceleratorForeground=darken(@foreground,15%)
*.acceleratorSelectionForeground=@selectionForeground *.acceleratorSelectionForeground=@selectionForeground
#---- system ---- #---- system colors ----
control=@background activeCaption=#434E60
controlText=@foreground inactiveCaption=#393C3D
infoText=@foreground controlHighlight=darken($controlShadow,20%)
text=@foreground controlLtHighlight=darken($controlShadow,25%)
textText=@foreground controlDkShadow=lighten($controlShadow,10%)
window=@background
#---- Button ---- #---- Button ----
Button.background=#4c5052 Button.background=#4c5052
Button.hoverBackground=@buttonHoverBackground Button.hoverBackground=lighten($Button.background,3%,derived autoInverse)
Button.pressedBackground=@buttonPressedBackground Button.pressedBackground=lighten($Button.background,6%,derived autoInverse)
Button.borderColor=#5e6060 Button.borderColor=#5e6060
Button.disabledBorderColor=#5e6060 Button.disabledBorderColor=#5e6060
Button.focusedBorderColor=#466d94 Button.focusedBorderColor=#466d94
Button.hoverBorderColor=@@Button.focusedBorderColor Button.hoverBorderColor=$Button.focusedBorderColor
Button.default.background=#365880 Button.default.background=#365880
Button.default.foreground=#bbbbbb Button.default.foreground=#bbbbbb
Button.default.hoverBackground=@buttonHoverBackground Button.default.hoverBackground=lighten($Button.default.background,3%,derived autoInverse)
Button.default.pressedBackground=@buttonPressedBackground Button.default.pressedBackground=lighten($Button.default.background,6%,derived autoInverse)
Button.default.borderColor=#4c708c Button.default.borderColor=#4c708c
Button.default.hoverBorderColor=#537699 Button.default.hoverBorderColor=#537699
Button.default.focusedBorderColor=#537699 Button.default.focusedBorderColor=#537699
Button.default.focusColor=#43688c Button.default.focusColor=#43688c
Button.default.boldText=true Button.default.boldText=true
Button.toolbar.hoverBackground=#4c5052 Button.toolbar.hoverBackground=lighten($Button.background,1%,derived autoInverse)
Button.toolbar.pressedBackground=#555a5d Button.toolbar.pressedBackground=lighten($Button.background,4%,derived autoInverse)
#---- CheckBox ---- #---- CheckBox ----
@@ -95,12 +100,12 @@ CheckBox.icon.borderColor=#6B6B6B
CheckBox.icon.disabledBorderColor=#545556 CheckBox.icon.disabledBorderColor=#545556
CheckBox.icon.selectedBorderColor=#6B6B6B CheckBox.icon.selectedBorderColor=#6B6B6B
CheckBox.icon.focusedBorderColor=#466D94 CheckBox.icon.focusedBorderColor=#466D94
CheckBox.icon.hoverBorderColor=@@CheckBox.icon.focusedBorderColor CheckBox.icon.hoverBorderColor=$CheckBox.icon.focusedBorderColor
CheckBox.icon.selectedFocusedBorderColor=#466D94 CheckBox.icon.selectedFocusedBorderColor=#466D94
CheckBox.icon.background=#43494A CheckBox.icon.background=#43494A
CheckBox.icon.disabledBackground=@background CheckBox.icon.disabledBackground=@background
CheckBox.icon.hoverBackground=@buttonHoverBackground CheckBox.icon.hoverBackground=lighten($CheckBox.icon.background,3%,derived autoInverse)
CheckBox.icon.pressedBackground=@buttonPressedBackground CheckBox.icon.pressedBackground=lighten($CheckBox.icon.background,6%,derived autoInverse)
CheckBox.icon.selectedBackground=#43494A CheckBox.icon.selectedBackground=#43494A
CheckBox.icon.checkmarkColor=#A7A7A7 CheckBox.icon.checkmarkColor=#A7A7A7
CheckBox.icon.disabledCheckmarkColor=#606060 CheckBox.icon.disabledCheckmarkColor=#606060
@@ -123,6 +128,35 @@ Component.disabledBorderColor=#646464
Component.focusedBorderColor=#466d94 Component.focusedBorderColor=#466d94
Component.focusColor=#3d6185 Component.focusColor=#3d6185
Component.linkColor=#589df6 Component.linkColor=#589df6
Component.grayFilter=-20,-70,100
#---- Desktop ----
Desktop.background=#3E434C
#---- DesktopIcon ----
DesktopIcon.background=lighten($Desktop.background,10%)
#---- InternalFrame ----
InternalFrame.activeTitleBackground=darken(@background,10%)
InternalFrame.activeTitleForeground=@foreground
InternalFrame.inactiveTitleBackground=darken(@background,5%)
InternalFrame.inactiveTitleForeground=@disabledText
InternalFrame.activeBorderColor=lighten($Component.borderColor,10%)
InternalFrame.inactiveBorderColor=$Component.borderColor
InternalFrame.buttonHoverBackground=lighten($InternalFrame.activeTitleBackground,10%,derived autoInverse)
InternalFrame.buttonPressedBackground=lighten($InternalFrame.activeTitleBackground,20%,derived autoInverse)
InternalFrame.closeHoverBackground=lazy(Actions.Red)
InternalFrame.closePressedBackground=darken(Actions.Red,10%,lazy)
InternalFrame.closeHoverForeground=#fff
InternalFrame.closePressedForeground=#fff
#---- List ---- #---- List ----
@@ -139,6 +173,7 @@ Menu.icon.disabledArrowColor=#606060
#---- MenuBar ---- #---- MenuBar ----
MenuBar.borderColor=#515151 MenuBar.borderColor=#515151
MenuBar.hoverBackground=@menuHoverBackground
#---- MenuItemCheckBox ---- #---- MenuItemCheckBox ----
@@ -147,25 +182,30 @@ MenuItemCheckBox.icon.checkmarkColor=#A7A7A7
MenuItemCheckBox.icon.disabledCheckmarkColor=#606060 MenuItemCheckBox.icon.disabledCheckmarkColor=#606060
#---- PasswordField ----
PasswordField.capsLockIconColor=#ffffff64
#---- PopupMenu ---- #---- PopupMenu ----
PopupMenu.borderColor=#515151 PopupMenu.borderColor=#5e5e5e
#---- ProgressBar ---- #---- ProgressBar ----
ProgressBar.background=#555555 ProgressBar.background=#555555
ProgressBar.foreground=#a0a0a0 ProgressBar.foreground=#4A88C7
ProgressBar.selectionForeground=@background ProgressBar.selectionForeground=@foreground
ProgressBar.selectionBackground=@foreground ProgressBar.selectionBackground=@foreground
#---- ScrollBar ---- #---- ScrollBar ----
ScrollBar.track=#3F4244 ScrollBar.track=#3F4244
ScrollBar.thumb=#5B5E5F ScrollBar.thumb=lighten($ScrollBar.track,10%)
ScrollBar.hoverTrackColor=#434647 ScrollBar.hoverTrackColor=lighten($ScrollBar.track,4%)
ScrollBar.hoverThumbColor=#666868 ScrollBar.hoverThumbColor=lighten($ScrollBar.thumb,10%)
#---- Separator ---- #---- Separator ----
@@ -178,7 +218,7 @@ Separator.foreground=#515151
Slider.trackColor=#646464 Slider.trackColor=#646464
Slider.thumbColor=#A6A6A6 Slider.thumbColor=#A6A6A6
Slider.tickColor=#888888 Slider.tickColor=#888888
Slider.hoverColor=darken(15%,autoInverse) Slider.hoverColor=darken($Slider.thumbColor,15%,derived autoInverse)
Slider.disabledForeground=#4c5052 Slider.disabledForeground=#4c5052
@@ -190,7 +230,7 @@ SplitPaneDivider.oneTouchHoverArrowColor=#7A7D81
#---- TabbedPane ---- #---- TabbedPane ----
TabbedPane.disabledForeground=#777777 TabbedPane.disabledForeground=@disabledText
TabbedPane.underlineColor=#4A88C7 TabbedPane.underlineColor=#4A88C7
TabbedPane.disabledUnderlineColor=#7a7a7a TabbedPane.disabledUnderlineColor=#7a7a7a
TabbedPane.hoverColor=#2e3133 TabbedPane.hoverColor=#2e3133
@@ -201,23 +241,23 @@ TabbedPane.contentAreaColor=#323232
#---- Table ---- #---- Table ----
Table.background=@textComponentBackground Table.background=@textComponentBackground
Table.gridColor=#4F5152 Table.gridColor=lighten($Table.background,3%)
#---- TableHeader ---- #---- TableHeader ----
TableHeader.background=#45494A TableHeader.background=@textComponentBackground
TableHeader.separatorColor=#585858 TableHeader.separatorColor=lighten($TableHeader.background,10%)
TableHeader.bottomSeparatorColor=#585858 TableHeader.bottomSeparatorColor=$TableHeader.separatorColor
#---- ToggleButton ---- #---- ToggleButton ----
ToggleButton.selectedBackground=#64696C ToggleButton.selectedBackground=lighten($ToggleButton.background,10%,derived autoInverse)
ToggleButton.selectedForeground=@foreground ToggleButton.selectedForeground=@foreground
ToggleButton.disabledSelectedBackground=#525658 ToggleButton.disabledSelectedBackground=lighten($ToggleButton.background,3%,derived autoInverse)
ToggleButton.toolbar.selectedBackground=#5c6164 ToggleButton.toolbar.selectedBackground=lighten($ToggleButton.background,7%,derived autoInverse)
#---- ToolTip ---- #---- ToolTip ----

View File

@@ -22,23 +22,22 @@
Button.focusedBackground=null Button.focusedBackground=null
Button.default.background=#4A86C7 Button.default.background=#4D8AC9
Button.default.foreground=#f0f0f0 Button.default.foreground=#FFFFFF
Button.default.focusedBackground=null Button.default.focusedBackground=null
Button.default.hoverBackground=#5B91CC Button.default.borderColor=#3D75B2
Button.default.pressedBackground=#6E9ED2 Button.default.hoverBorderColor=#A9C9F5
Button.default.borderColor=#3167ad Button.default.focusedBorderColor=#A9C9F5
Button.default.hoverBorderColor=#a8cef6
Button.default.focusedBorderColor=#a8cef6
Button.default.focusColor=#97c3f3 Button.default.focusColor=#97c3f3
Button.default.boldText=true Button.default.boldText=true
Button.default.borderWidth=1
#---- CheckBox ---- #---- CheckBox ----
CheckBox.icon.selectedBorderColor=#4982CC CheckBox.icon.selectedBorderColor=#4B97D9
CheckBox.icon.selectedFocusedBorderColor=#ACCFF7 CheckBox.icon.selectedFocusedBorderColor=#ACCFF7
CheckBox.icon.selectedBackground=#4D89C9 CheckBox.icon.selectedBackground=#4F9EE3
CheckBox.icon.checkmarkColor=#FFFFFF CheckBox.icon.checkmarkColor=#FFFFFF
CheckBox.icon.selectedHoverBackground=#5E94CE CheckBox.icon.selectedHoverBackground=#5E94CE

View File

@@ -21,21 +21,26 @@ CheckBoxUI=com.formdev.flatlaf.ui.FlatCheckBoxUI
CheckBoxMenuItemUI=com.formdev.flatlaf.ui.FlatCheckBoxMenuItemUI CheckBoxMenuItemUI=com.formdev.flatlaf.ui.FlatCheckBoxMenuItemUI
ColorChooserUI=com.formdev.flatlaf.ui.FlatColorChooserUI ColorChooserUI=com.formdev.flatlaf.ui.FlatColorChooserUI
ComboBoxUI=com.formdev.flatlaf.ui.FlatComboBoxUI ComboBoxUI=com.formdev.flatlaf.ui.FlatComboBoxUI
DesktopIconUI=com.formdev.flatlaf.ui.FlatDesktopIconUI
DesktopPaneUI=com.formdev.flatlaf.ui.FlatDesktopPaneUI
EditorPaneUI=com.formdev.flatlaf.ui.FlatEditorPaneUI EditorPaneUI=com.formdev.flatlaf.ui.FlatEditorPaneUI
FileChooserUI=com.formdev.flatlaf.ui.FlatFileChooserUI FileChooserUI=com.formdev.flatlaf.ui.FlatFileChooserUI
FormattedTextFieldUI=com.formdev.flatlaf.ui.FlatFormattedTextFieldUI FormattedTextFieldUI=com.formdev.flatlaf.ui.FlatFormattedTextFieldUI
InternalFrameUI=com.formdev.flatlaf.ui.FlatInternalFrameUI
LabelUI=com.formdev.flatlaf.ui.FlatLabelUI LabelUI=com.formdev.flatlaf.ui.FlatLabelUI
ListUI=com.formdev.flatlaf.ui.FlatListUI ListUI=com.formdev.flatlaf.ui.FlatListUI
MenuUI=com.formdev.flatlaf.ui.FlatMenuUI MenuUI=com.formdev.flatlaf.ui.FlatMenuUI
MenuBarUI=com.formdev.flatlaf.ui.FlatMenuBarUI MenuBarUI=com.formdev.flatlaf.ui.FlatMenuBarUI
MenuItemUI=com.formdev.flatlaf.ui.FlatMenuItemUI MenuItemUI=com.formdev.flatlaf.ui.FlatMenuItemUI
OptionPaneUI=com.formdev.flatlaf.ui.FlatOptionPaneUI OptionPaneUI=com.formdev.flatlaf.ui.FlatOptionPaneUI
PanelUI=com.formdev.flatlaf.ui.FlatPanelUI
PasswordFieldUI=com.formdev.flatlaf.ui.FlatPasswordFieldUI PasswordFieldUI=com.formdev.flatlaf.ui.FlatPasswordFieldUI
PopupMenuUI=com.formdev.flatlaf.ui.FlatPopupMenuUI PopupMenuUI=com.formdev.flatlaf.ui.FlatPopupMenuUI
PopupMenuSeparatorUI=com.formdev.flatlaf.ui.FlatPopupMenuSeparatorUI PopupMenuSeparatorUI=com.formdev.flatlaf.ui.FlatPopupMenuSeparatorUI
ProgressBarUI=com.formdev.flatlaf.ui.FlatProgressBarUI ProgressBarUI=com.formdev.flatlaf.ui.FlatProgressBarUI
RadioButtonUI=com.formdev.flatlaf.ui.FlatRadioButtonUI RadioButtonUI=com.formdev.flatlaf.ui.FlatRadioButtonUI
RadioButtonMenuItemUI=com.formdev.flatlaf.ui.FlatRadioButtonMenuItemUI RadioButtonMenuItemUI=com.formdev.flatlaf.ui.FlatRadioButtonMenuItemUI
RootPaneUI=com.formdev.flatlaf.ui.FlatRootPaneUI
ScrollBarUI=com.formdev.flatlaf.ui.FlatScrollBarUI ScrollBarUI=com.formdev.flatlaf.ui.FlatScrollBarUI
ScrollPaneUI=com.formdev.flatlaf.ui.FlatScrollPaneUI ScrollPaneUI=com.formdev.flatlaf.ui.FlatScrollPaneUI
SeparatorUI=com.formdev.flatlaf.ui.FlatSeparatorUI SeparatorUI=com.formdev.flatlaf.ui.FlatSeparatorUI
@@ -59,6 +64,64 @@ ViewportUI=com.formdev.flatlaf.ui.FlatViewportUI
#---- variables ---- #---- variables ----
@textComponentMargin=2,6,2,6 @textComponentMargin=2,6,2,6
@menuItemMargin=3,6,3,6
#---- system colors ----
desktop=@textComponentBackground
activeCaptionText=@foreground
activeCaptionBorder=$activeCaption
inactiveCaptionText=@foreground
inactiveCaptionBorder=$inactiveCaption
window=@background
windowBorder=@foreground
windowText=@foreground
menu=@background
menuText=@foreground
text=@textComponentBackground
textText=@foreground
textHighlight=@selectionBackground
textHighlightText=@selectionForeground
textInactiveText=@disabledText
control=@background
controlText=@foreground
controlShadow=$Component.borderColor
scrollbar=$ScrollBar.track
info=$ToolTip.background
infoText=@foreground
#---- unused colors ----
# Colors that are defined in BasicLookAndFeel but are not used in FlatLaf.
# Keep them for compatibility (if used in 3rd party app) and give them useful values.
*.shadow=$controlShadow
*.darkShadow=$controlDkShadow
*.light=$controlHighlight
*.highlight=$controlLtHighlight
ComboBox.buttonShadow=$controlShadow
ComboBox.buttonDarkShadow=$controlDkShadow
ComboBox.buttonHighlight=$controlLtHighlight
InternalFrame.borderColor=$control
InternalFrame.borderShadow=$controlShadow
InternalFrame.borderDarkShadow=$controlDkShadow
InternalFrame.borderHighlight=$controlLtHighlight
InternalFrame.borderLight=$controlHighlight
Label.disabledShadow=$controlShadow
ScrollBar.trackHighlight=$controlDkShadow
ScrollBar.thumbHighlight=$controlLtHighlight
ScrollBar.thumbDarkShadow=$controlDkShadow
ScrollBar.thumbShadow=$controlShadow
Slider.focus=$controlDkShadow
TabbedPane.focus=$controlText
#---- Button ---- #---- Button ----
@@ -70,15 +133,24 @@ Button.margin=2,14,2,14
Button.iconTextGap=4 Button.iconTextGap=4
Button.rollover=true Button.rollover=true
Button.defaultButtonFollowsFocus=false Button.defaultButtonFollowsFocus=false
[win]Button.defaultButtonFollowsFocus=true
Button.default.borderWidth=1 Button.default.borderWidth=1
Button.toolbar.margin=3,3,3,3
Button.toolbar.spacingInsets=1,2,1,2
#---- Caret ----
Caret.width={scaledInteger}1
#---- CheckBox ---- #---- CheckBox ----
CheckBox.border=com.formdev.flatlaf.ui.FlatMarginBorder CheckBox.border=com.formdev.flatlaf.ui.FlatMarginBorder
CheckBox.icon=com.formdev.flatlaf.icons.FlatCheckBoxIcon CheckBox.icon=com.formdev.flatlaf.icons.FlatCheckBoxIcon
CheckBox.arc=2 CheckBox.arc=4
CheckBox.margin=2,2,2,2 CheckBox.margin=2,2,2,2
CheckBox.iconTextGap=4 CheckBox.iconTextGap=4
CheckBox.rollover=true CheckBox.rollover=true
@@ -86,39 +158,52 @@ CheckBox.rollover=true
#---- CheckBoxMenuItem ---- #---- CheckBoxMenuItem ----
CheckBoxMenuItem.border=com.formdev.flatlaf.ui.FlatMarginBorder CheckBoxMenuItem.border=com.formdev.flatlaf.ui.FlatMenuItemBorder
CheckBoxMenuItem.checkIcon=com.formdev.flatlaf.icons.FlatCheckBoxMenuItemIcon CheckBoxMenuItem.checkIcon=com.formdev.flatlaf.icons.FlatCheckBoxMenuItemIcon
CheckBoxMenuItem.arrowIcon=com.formdev.flatlaf.icons.FlatMenuItemArrowIcon CheckBoxMenuItem.arrowIcon=com.formdev.flatlaf.icons.FlatMenuItemArrowIcon
CheckBoxMenuItem.margin=2,2,2,2 CheckBoxMenuItem.margin=@menuItemMargin
CheckBoxMenuItem.opaque=false
CheckBoxMenuItem.borderPainted=true
CheckBoxMenuItem.background=@menuBackground
#---- ColorChooser ---- #---- ColorChooser ----
ColorChooser.swatchesSwatchSize=16,16 ColorChooser.swatchesSwatchSize={scaledDimension}16,16
ColorChooser.swatchesRecentSwatchSize=16,16 ColorChooser.swatchesRecentSwatchSize={scaledDimension}16,16
ColorChooser.swatchesDefaultRecentColor=$control
#---- ComboBox ---- #---- ComboBox ----
ComboBox.border=com.formdev.flatlaf.ui.FlatRoundBorder ComboBox.border=com.formdev.flatlaf.ui.FlatRoundBorder
ComboBox.padding=2,6,2,6 ComboBox.padding=2,6,2,6
[mac]ComboBox.showPopupOnNavigation=true
#---- Component ---- #---- Component ----
Component.focusWidth=0 Component.focusWidth=0
Component.innerFocusWidth=0 Component.innerFocusWidth={float}0.5
Component.arc=5 Component.arc=5
Component.minimumWidth=64 Component.minimumWidth=64
Component.arrowType=chevron Component.arrowType=chevron
Component.hideMnemonics=true Component.hideMnemonics=true
#---- DesktopIcon ----
DesktopIcon.border=4,4,4,4
DesktopIcon.iconSize=64,64
DesktopIcon.closeSize=20,20
DesktopIcon.closeIcon=com.formdev.flatlaf.icons.FlatInternalFrameCloseIcon
#---- EditorPane ---- #---- EditorPane ----
EditorPane.border=com.formdev.flatlaf.ui.FlatMarginBorder EditorPane.border=com.formdev.flatlaf.ui.FlatMarginBorder
EditorPane.background=@textComponentBackground
EditorPane.margin=@textComponentMargin EditorPane.margin=@textComponentMargin
EditorPane.background=@textComponentBackground
#---- FileChooser ---- #---- FileChooser ----
@@ -128,6 +213,7 @@ FileChooser.upFolderIcon=com.formdev.flatlaf.icons.FlatFileChooserUpFolderIcon
FileChooser.homeFolderIcon=com.formdev.flatlaf.icons.FlatFileChooserHomeFolderIcon FileChooser.homeFolderIcon=com.formdev.flatlaf.icons.FlatFileChooserHomeFolderIcon
FileChooser.detailsViewIcon=com.formdev.flatlaf.icons.FlatFileChooserDetailsViewIcon FileChooser.detailsViewIcon=com.formdev.flatlaf.icons.FlatFileChooserDetailsViewIcon
FileChooser.listViewIcon=com.formdev.flatlaf.icons.FlatFileChooserListViewIcon FileChooser.listViewIcon=com.formdev.flatlaf.icons.FlatFileChooserListViewIcon
FileChooser.usesSingleFilePane=true
#---- FileView ---- #---- FileView ----
@@ -141,54 +227,106 @@ FileView.floppyDriveIcon=com.formdev.flatlaf.icons.FlatFileViewFloppyDriveIcon
#---- FormattedTextField ---- #---- FormattedTextField ----
FormattedTextField.border=com.formdev.flatlaf.ui.FlatBorder FormattedTextField.border=com.formdev.flatlaf.ui.FlatTextBorder
FormattedTextField.background=@textComponentBackground
FormattedTextField.margin=@textComponentMargin FormattedTextField.margin=@textComponentMargin
FormattedTextField.background=@textComponentBackground
FormattedTextField.placeholderForeground=@disabledText
#---- HelpButton ---- #---- HelpButton ----
HelpButton.icon=com.formdev.flatlaf.icons.FlatHelpButtonIcon HelpButton.icon=com.formdev.flatlaf.icons.FlatHelpButtonIcon
HelpButton.borderColor=@@CheckBox.icon.borderColor HelpButton.borderColor=$CheckBox.icon.borderColor
HelpButton.disabledBorderColor=@@CheckBox.icon.disabledBorderColor HelpButton.disabledBorderColor=$CheckBox.icon.disabledBorderColor
HelpButton.focusedBorderColor=@@CheckBox.icon.focusedBorderColor HelpButton.focusedBorderColor=$CheckBox.icon.focusedBorderColor
HelpButton.hoverBorderColor=@@?CheckBox.icon.hoverBorderColor HelpButton.hoverBorderColor=$?CheckBox.icon.hoverBorderColor
HelpButton.background=@@CheckBox.icon.background HelpButton.background=$CheckBox.icon.background
HelpButton.disabledBackground=@@CheckBox.icon.disabledBackground HelpButton.disabledBackground=$CheckBox.icon.disabledBackground
HelpButton.focusedBackground=@@?CheckBox.icon.focusedBackground HelpButton.focusedBackground=$?CheckBox.icon.focusedBackground
HelpButton.hoverBackground=@@?CheckBox.icon.hoverBackground HelpButton.hoverBackground=$?CheckBox.icon.hoverBackground
HelpButton.pressedBackground=@@?CheckBox.icon.pressedBackground HelpButton.pressedBackground=$?CheckBox.icon.pressedBackground
HelpButton.questionMarkColor=@@CheckBox.icon.checkmarkColor HelpButton.questionMarkColor=$CheckBox.icon.checkmarkColor
HelpButton.disabledQuestionMarkColor=@@CheckBox.icon.disabledCheckmarkColor HelpButton.disabledQuestionMarkColor=$CheckBox.icon.disabledCheckmarkColor
#---- InternalFrame ----
InternalFrame.border=com.formdev.flatlaf.ui.FlatInternalFrameUI$FlatInternalFrameBorder
InternalFrame.borderLineWidth=1
InternalFrame.borderMargins=6,6,6,6
InternalFrame.buttonSize=24,24
InternalFrame.closeIcon=com.formdev.flatlaf.icons.FlatInternalFrameCloseIcon
InternalFrame.iconifyIcon=com.formdev.flatlaf.icons.FlatInternalFrameIconifyIcon
InternalFrame.maximizeIcon=com.formdev.flatlaf.icons.FlatInternalFrameMaximizeIcon
InternalFrame.minimizeIcon=com.formdev.flatlaf.icons.FlatInternalFrameMinimizeIcon
InternalFrame.windowBindings=null
#---- InternalFrameTitlePane ----
InternalFrameTitlePane.border=0,8,0,0
#---- List ---- #---- List ----
List.border=1,0,1,0 List.border=0,0,0,0
List.cellNoFocusBorder=1,6,1,6 List.cellMargins=1,6,1,6
List.focusCellHighlightBorder=1,6,1,6,@cellFocusColor List.cellFocusColor=@cellFocusColor
List.focusSelectedCellHighlightBorder=1,6,1,6,@cellFocusColor List.cellNoFocusBorder=com.formdev.flatlaf.ui.FlatListCellBorder$Default
List.focusCellHighlightBorder=com.formdev.flatlaf.ui.FlatListCellBorder$Focused
List.focusSelectedCellHighlightBorder=com.formdev.flatlaf.ui.FlatListCellBorder$Selected
List.selectionInactiveBackground=@selectionInactiveBackground List.selectionInactiveBackground=@selectionInactiveBackground
List.selectionInactiveForeground=@selectionInactiveForeground List.selectionInactiveForeground=@selectionInactiveForeground
List.dropCellBackground=@dropCellBackground
List.dropCellForeground=@dropCellForeground
List.dropLineColor=@dropLineColor
#---- Menu ---- #---- Menu ----
Menu.border=com.formdev.flatlaf.ui.FlatMarginBorder Menu.border=com.formdev.flatlaf.ui.FlatMenuItemBorder
Menu.arrowIcon=com.formdev.flatlaf.icons.FlatMenuArrowIcon Menu.arrowIcon=com.formdev.flatlaf.icons.FlatMenuArrowIcon
Menu.margin=2,2,2,2 Menu.checkIcon=null
Menu.margin=@menuItemMargin
Menu.submenuPopupOffsetX={scaledInteger}-4
Menu.submenuPopupOffsetY={scaledInteger}-4
Menu.opaque=false
Menu.borderPainted=true
Menu.background=@menuBackground
#---- MenuBar ---- #---- MenuBar ----
MenuBar.border=com.formdev.flatlaf.ui.FlatMenuBarBorder MenuBar.border=com.formdev.flatlaf.ui.FlatMenuBarBorder
MenuBar.background=@menuBackground
MenuBar.itemMargins=3,8,3,8
#---- MenuItem ---- #---- MenuItem ----
MenuItem.border=com.formdev.flatlaf.ui.FlatMarginBorder MenuItem.border=com.formdev.flatlaf.ui.FlatMenuItemBorder
MenuItem.arrowIcon=com.formdev.flatlaf.icons.FlatMenuItemArrowIcon MenuItem.arrowIcon=com.formdev.flatlaf.icons.FlatMenuItemArrowIcon
MenuItem.margin=2,2,2,2 MenuItem.checkIcon=null
MenuItem.margin=@menuItemMargin
MenuItem.opaque=false
MenuItem.borderPainted=true
MenuItem.background=@menuBackground
MenuItem.checkBackground=@menuCheckBackground
MenuItem.checkMargins=2,2,2,2
MenuItem.minimumWidth=72
MenuItem.minimumIconSize=16,16
MenuItem.iconTextGap=6
MenuItem.textAcceleratorGap=24
MenuItem.textNoAcceleratorGap=6
MenuItem.acceleratorArrowGap=2
MenuItem.acceleratorDelimiter=-
[mac]MenuItem.acceleratorDelimiter=
# for MenuItem.selectionType=underline
MenuItem.underlineSelectionBackground=@menuHoverBackground
MenuItem.underlineSelectionCheckBackground=@menuCheckHoverBackground
MenuItem.underlineSelectionColor=$TabbedPane.underlineColor
MenuItem.underlineSelectionHeight=3
#---- OptionPane ---- #---- OptionPane ----
@@ -202,7 +340,7 @@ OptionPane.maxCharactersPerLine=80
OptionPane.iconMessageGap=16 OptionPane.iconMessageGap=16
OptionPane.messagePadding=3 OptionPane.messagePadding=3
OptionPane.buttonPadding=8 OptionPane.buttonPadding=8
OptionPane.buttonMinimumWidth={scaledNumber}72 OptionPane.buttonMinimumWidth={scaledInteger}72
OptionPane.sameSizeButtons=true OptionPane.sameSizeButtons=true
OptionPane.setButtonMargin=false OptionPane.setButtonMargin=false
OptionPane.buttonOrientation=4 OptionPane.buttonOrientation=4
@@ -216,15 +354,19 @@ OptionPane.warningIcon=com.formdev.flatlaf.icons.FlatOptionPaneWarningIcon
#---- PasswordField ---- #---- PasswordField ----
PasswordField.border=com.formdev.flatlaf.ui.FlatBorder PasswordField.border=com.formdev.flatlaf.ui.FlatTextBorder
PasswordField.background=@textComponentBackground
PasswordField.margin=@textComponentMargin PasswordField.margin=@textComponentMargin
PasswordField.background=@textComponentBackground
PasswordField.placeholderForeground=@disabledText
PasswordField.echoChar=\u2022
PasswordField.capsLockIcon=com.formdev.flatlaf.icons.FlatCapsLockIcon
#---- PopupMenu ---- #---- PopupMenu ----
PopupMenu.border=com.formdev.flatlaf.ui.FlatPopupMenuBorder PopupMenu.border=com.formdev.flatlaf.ui.FlatPopupMenuBorder
PopupMenu.borderInsets=1,1,1,1 PopupMenu.borderInsets=4,1,4,1
PopupMenu.background=@menuBackground
#---- PopupMenuSeparator ---- #---- PopupMenuSeparator ----
@@ -237,8 +379,11 @@ PopupMenuSeparator.stripeIndent=4
#---- ProgressBar ---- #---- ProgressBar ----
ProgressBar.border=com.formdev.flatlaf.ui.FlatEmptyBorder ProgressBar.border=com.formdev.flatlaf.ui.FlatEmptyBorder
ProgressBar.horizontalSize=146,6 ProgressBar.arc=4
ProgressBar.verticalSize=6,146 ProgressBar.horizontalSize=146,4
ProgressBar.verticalSize=4,146
ProgressBar.cycleTime=4000
ProgressBar.repaintInterval=15
#---- RadioButton ---- #---- RadioButton ----
@@ -253,22 +398,31 @@ RadioButton.rollover=true
#---- RadioButtonMenuItem ---- #---- RadioButtonMenuItem ----
RadioButtonMenuItem.border=com.formdev.flatlaf.ui.FlatMarginBorder RadioButtonMenuItem.border=com.formdev.flatlaf.ui.FlatMenuItemBorder
RadioButtonMenuItem.checkIcon=com.formdev.flatlaf.icons.FlatRadioButtonMenuItemIcon RadioButtonMenuItem.checkIcon=com.formdev.flatlaf.icons.FlatRadioButtonMenuItemIcon
RadioButtonMenuItem.arrowIcon=com.formdev.flatlaf.icons.FlatMenuItemArrowIcon RadioButtonMenuItem.arrowIcon=com.formdev.flatlaf.icons.FlatMenuItemArrowIcon
RadioButtonMenuItem.margin=2,2,2,2 RadioButtonMenuItem.margin=@menuItemMargin
RadioButtonMenuItem.opaque=false
RadioButtonMenuItem.borderPainted=true
RadioButtonMenuItem.background=@menuBackground
#---- ScrollBar ---- #---- ScrollBar ----
ScrollBar.width=10 ScrollBar.width=10
ScrollBar.showButtons=false
ScrollBar.squareButtons=false
ScrollBar.buttonArrowColor=$ComboBox.buttonArrowColor
ScrollBar.buttonDisabledArrowColor=$ComboBox.buttonDisabledArrowColor
ScrollBar.allowsAbsolutePositioning=true
#---- ScrollPane ---- #---- ScrollPane ----
ScrollPane.border=com.formdev.flatlaf.ui.FlatBorder ScrollPane.border=com.formdev.flatlaf.ui.FlatBorder
ScrollPane.background=@@ScrollBar.track ScrollPane.background=$ScrollBar.track
ScrollPane.fillUpperCorner=true ScrollPane.fillUpperCorner=true
ScrollPane.smoothScrolling=true
#---- Separator ---- #---- Separator ----
@@ -289,10 +443,10 @@ Slider.thumbWidth=11
Spinner.border=com.formdev.flatlaf.ui.FlatRoundBorder Spinner.border=com.formdev.flatlaf.ui.FlatRoundBorder
Spinner.background=@textComponentBackground Spinner.background=@textComponentBackground
Spinner.buttonBackground=@@ComboBox.buttonEditableBackground Spinner.buttonBackground=$ComboBox.buttonEditableBackground
Spinner.buttonArrowColor=@@ComboBox.buttonArrowColor Spinner.buttonArrowColor=$ComboBox.buttonArrowColor
Spinner.buttonDisabledArrowColor=@@ComboBox.buttonDisabledArrowColor Spinner.buttonDisabledArrowColor=$ComboBox.buttonDisabledArrowColor
Spinner.buttonHoverArrowColor=@@ComboBox.buttonHoverArrowColor Spinner.buttonHoverArrowColor=$ComboBox.buttonHoverArrowColor
Spinner.padding=@textComponentMargin Spinner.padding=@textComponentMargin
Spinner.editorBorderPainted=false Spinner.editorBorderPainted=false
@@ -303,11 +457,11 @@ SplitPane.dividerSize={integer}5
SplitPane.continuousLayout=true SplitPane.continuousLayout=true
SplitPane.border=null SplitPane.border=null
SplitPane.centerOneTouchButtons=true SplitPane.centerOneTouchButtons=true
SplitPane.oneTouchButtonSize={scaledNumber}6 SplitPane.oneTouchButtonSize={scaledInteger}6
SplitPane.oneTouchButtonOffset={scaledNumber}2 SplitPane.oneTouchButtonOffset={scaledInteger}2
SplitPaneDivider.border=null SplitPaneDivider.border=null
SplitPaneDivider.oneTouchArrowColor=@@ComboBox.buttonArrowColor SplitPaneDivider.oneTouchArrowColor=$ComboBox.buttonArrowColor
#---- TabbedPane ---- #---- TabbedPane ----
@@ -321,90 +475,120 @@ TabbedPane.tabAreaInsets=0,0,0,0
TabbedPane.selectedTabPadInsets=0,0,0,0 TabbedPane.selectedTabPadInsets=0,0,0,0
TabbedPane.tabRunOverlay=0 TabbedPane.tabRunOverlay=0
TabbedPane.tabsOverlapBorder=true TabbedPane.tabsOverlapBorder=true
TabbedPane.shadow=@@ComboBox.buttonArrowColor TabbedPane.shadow=@background
TabbedPane.contentBorderInsets=null
#---- Table ---- #---- Table ----
Table.rowHeight=20 Table.rowHeight=20
Table.showHorizontalLines=false
Table.showVerticalLines=false
Table.intercellSpacing={dimension}0,0
Table.scrollPaneBorder=com.formdev.flatlaf.ui.FlatBorder Table.scrollPaneBorder=com.formdev.flatlaf.ui.FlatBorder
Table.ascendingSortIcon=com.formdev.flatlaf.icons.FlatAscendingSortIcon Table.ascendingSortIcon=com.formdev.flatlaf.icons.FlatAscendingSortIcon
Table.descendingSortIcon=com.formdev.flatlaf.icons.FlatDescendingSortIcon Table.descendingSortIcon=com.formdev.flatlaf.icons.FlatDescendingSortIcon
Table.sortIconColor=@icon Table.sortIconColor=@icon
Table.cellNoFocusBorder=2,3,2,3 Table.cellMargins=2,3,2,3
Table.focusSelectedCellHighlightBorder=2,3,2,3,@cellFocusColor Table.cellFocusColor=@cellFocusColor
Table.cellNoFocusBorder=com.formdev.flatlaf.ui.FlatTableCellBorder$Default
Table.focusCellHighlightBorder=com.formdev.flatlaf.ui.FlatTableCellBorder$Focused
Table.focusSelectedCellHighlightBorder=com.formdev.flatlaf.ui.FlatTableCellBorder$Selected
Table.focusCellBackground=@textComponentBackground
Table.focusCellForeground=@foreground
Table.selectionInactiveBackground=@selectionInactiveBackground Table.selectionInactiveBackground=@selectionInactiveBackground
Table.selectionInactiveForeground=@selectionInactiveForeground Table.selectionInactiveForeground=@selectionInactiveForeground
Table.dropCellBackground=@dropCellBackground
Table.dropCellForeground=@dropCellForeground
Table.dropLineColor=@dropLineColor
Table.dropLineShortColor=@dropLineShortColor
#---- TableHeader ---- #---- TableHeader ----
TableHeader.height=25 TableHeader.height=25
TableHeader.cellBorder=2,3,2,3 TableHeader.cellBorder=2,3,2,3
TableHeader.focusCellBackground=$TableHeader.background
#---- TextArea ---- #---- TextArea ----
TextArea.border=com.formdev.flatlaf.ui.FlatMarginBorder TextArea.border=com.formdev.flatlaf.ui.FlatMarginBorder
TextArea.background=@textComponentBackground
TextArea.margin=@textComponentMargin TextArea.margin=@textComponentMargin
TextArea.background=@textComponentBackground
#---- TextComponent ----
# allowed values: "never", "once" (default) or "always"
TextComponent.selectAllOnFocusPolicy=once
TextComponent.arc=0
#---- TextField ---- #---- TextField ----
TextField.border=com.formdev.flatlaf.ui.FlatBorder TextField.border=com.formdev.flatlaf.ui.FlatTextBorder
TextField.background=@textComponentBackground
TextField.margin=@textComponentMargin TextField.margin=@textComponentMargin
TextField.background=@textComponentBackground
TextField.placeholderForeground=@disabledText
#---- TextPane ---- #---- TextPane ----
TextPane.border=com.formdev.flatlaf.ui.FlatMarginBorder TextPane.border=com.formdev.flatlaf.ui.FlatMarginBorder
TextPane.background=@textComponentBackground
TextPane.margin=@textComponentMargin TextPane.margin=@textComponentMargin
TextPane.background=@textComponentBackground
#---- TitledBorder ---- #---- TitledBorder ----
TitledBorder.titleColor=@foreground TitledBorder.titleColor=@foreground
TitledBorder.border=1,1,1,1,@@Separator.foreground TitledBorder.border=1,1,1,1,$Separator.foreground
#---- ToggleButton ---- #---- ToggleButton ----
ToggleButton.border=com.formdev.flatlaf.ui.FlatButtonBorder ToggleButton.border=com.formdev.flatlaf.ui.FlatButtonBorder
ToggleButton.arc=6
ToggleButton.margin=2,14,2,14 ToggleButton.margin=2,14,2,14
ToggleButton.iconTextGap=4 ToggleButton.iconTextGap=4
ToggleButton.rollover=true ToggleButton.rollover=true
ToggleButton.background=@@Button.background ToggleButton.background=$Button.background
ToggleButton.pressedBackground=@@Button.pressedBackground ToggleButton.pressedBackground=$Button.pressedBackground
ToggleButton.toolbar.hoverBackground=@@Button.toolbar.hoverBackground ToggleButton.toolbar.hoverBackground=$Button.toolbar.hoverBackground
ToggleButton.toolbar.pressedBackground=@@Button.toolbar.pressedBackground ToggleButton.toolbar.pressedBackground=$Button.toolbar.pressedBackground
# button type "tab"
ToggleButton.tab.underlineHeight=2
ToggleButton.tab.underlineColor=$TabbedPane.underlineColor
ToggleButton.tab.disabledUnderlineColor=$TabbedPane.disabledUnderlineColor
ToggleButton.tab.selectedBackground=$?TabbedPane.selectedBackground
ToggleButton.tab.hoverBackground=$TabbedPane.hoverColor
ToggleButton.tab.focusBackground=$TabbedPane.focusColor
#---- ToolBar ---- #---- ToolBar ----
ToolBar.border=com.formdev.flatlaf.ui.FlatToolBarBorder ToolBar.border=com.formdev.flatlaf.ui.FlatToolBarBorder
ToolBar.borderMargins=2,2,2,2
ToolBar.isRollover=true ToolBar.isRollover=true
ToolBar.buttonMargins=3,3,3,3
ToolBar.gripColor=@icon ToolBar.gripColor=@icon
ToolBar.dockingBackground=@background ToolBar.dockingBackground=@background
ToolBar.dockingForeground=@foreground
ToolBar.floatingBackground=@background ToolBar.floatingBackground=@background
ToolBar.floatingForeground=@disabledText
ToolBar.separatorSize=null ToolBar.separatorSize=null
ToolBar.separatorWidth=7 ToolBar.separatorWidth=7
ToolBar.separatorColor=@@Separator.foreground ToolBar.separatorColor=$Separator.foreground
ToolBar.spacingBorder=$Button.toolbar.spacingInsets
#---- ToolTip ---- #---- ToolTipManager ----
ToolTip.border=4,6,4,6,@@Component.borderColor ToolTipManager.enableToolTipMode=activeApplication
ToolTip.borderInactive=null
ToolTip.backgroundInactive=@@ToolTip.background
ToolTip.foregroundInactive=@disabledText
#---- Tree ---- #---- Tree ----
@@ -412,9 +596,15 @@ ToolTip.foregroundInactive=@disabledText
Tree.border=1,1,1,1 Tree.border=1,1,1,1
Tree.selectionInactiveBackground=@selectionInactiveBackground Tree.selectionInactiveBackground=@selectionInactiveBackground
Tree.selectionInactiveForeground=@selectionInactiveForeground Tree.selectionInactiveForeground=@selectionInactiveForeground
Tree.textBackground=null Tree.textBackground=$Tree.background
Tree.selectionBorderColor=@cellFocusColor Tree.selectionBorderColor=@cellFocusColor
Tree.dropCellBackground=@dropCellBackground
Tree.dropCellForeground=@dropCellForeground
Tree.dropLineColor=@dropLineColor
Tree.rendererFillBackground=false
Tree.rendererMargins=1,2,1,2 Tree.rendererMargins=1,2,1,2
Tree.wideSelection=true
Tree.repaintWholeRow=true
Tree.paintLines=false Tree.paintLines=false
Tree.leftChildIndent=7 Tree.leftChildIndent=7
Tree.rightChildIndent=11 Tree.rightChildIndent=11

View File

@@ -22,18 +22,24 @@
@background=#f2f2f2 @background=#f2f2f2
@foreground=#000000 @foreground=#000000
@selectionBackground=#4A6EB7 @selectionBackground=#2675BF
@selectionForeground=#ffffff @selectionForeground=#ffffff
@selectionInactiveBackground=#d4d4d4 @selectionInactiveBackground=#d4d4d4
@selectionInactiveForeground=@foreground @selectionInactiveForeground=@foreground
@disabledText=#999999 @disabledText=#8C8C8C
@textComponentBackground=#ffffff @textComponentBackground=#ffffff
@menuBackground=#fff
@menuHoverBackground=darken(@menuBackground,10%)
@menuCheckBackground=darken(@menuBackground,10%)
@menuCheckHoverBackground=darken(@menuBackground,20%)
@cellFocusColor=#000000 @cellFocusColor=#000000
@icon=#afafaf @icon=#afafaf
# Button # Drop (use lazy colors for IntelliJ platform themes, which usually do not specify these colors)
@buttonHoverBackground=darken(3%,autoInverse) @dropCellBackground=lighten(List.selectionBackground,10%,lazy)
@buttonPressedBackground=darken(10%,autoInverse) @dropCellForeground=lazy(List.selectionForeground)
@dropLineColor=lighten(List.selectionBackground,20%,lazy)
@dropLineShortColor=darken(List.selectionBackground,20%,lazy)
#---- globals ---- #---- globals ----
@@ -44,67 +50,66 @@
*.textForeground=@foreground *.textForeground=@foreground
*.caretForeground=@foreground *.caretForeground=@foreground
*.inactiveBackground=@background *.inactiveBackground=@background
*.inactiveForeground=#777777 *.inactiveForeground=@disabledText
*.selectionBackground=@selectionBackground *.selectionBackground=@selectionBackground
*.selectionForeground=@selectionForeground *.selectionForeground=@selectionForeground
*.disabledBackground=@background *.disabledBackground=@background
*.disabledForeground=@disabledText *.disabledForeground=@disabledText
*.disabledText=@disabledText *.disabledText=@disabledText
*.acceleratorForeground=#505050 *.acceleratorForeground=lighten(@foreground,30%)
*.acceleratorSelectionForeground=@selectionForeground *.acceleratorSelectionForeground=@selectionForeground
#---- system ---- #---- system colors ----
control=#e0e0e0 activeCaption=#99b4d1
controlText=@foreground inactiveCaption=#bfcddb
infoText=@foreground controlHighlight=#e3e3e3
text=@foreground controlLtHighlight=#fff
textText=@foreground controlDkShadow=darken($controlShadow,15%)
window=@background
#---- Button ---- #---- Button ----
Button.background=#ffffff Button.background=#ffffff
Button.focusedBackground=#e3f1fa Button.focusedBackground=#e3f1fa
Button.hoverBackground=@buttonHoverBackground Button.hoverBackground=darken($Button.background,3%,derived autoInverse)
Button.pressedBackground=@buttonPressedBackground Button.pressedBackground=darken($Button.background,10%,derived autoInverse)
Button.borderColor=#bfbfbf Button.borderColor=$Component.borderColor
Button.disabledBorderColor=#cfcfcf Button.disabledBorderColor=$Component.disabledBorderColor
Button.focusedBorderColor=#87afda Button.focusedBorderColor=$Component.focusedBorderColor
Button.hoverBorderColor=@@Button.focusedBorderColor Button.hoverBorderColor=$Button.focusedBorderColor
Button.default.background=@@Button.background Button.default.background=$Button.background
Button.default.foreground=@foreground Button.default.foreground=@foreground
Button.default.focusedBackground=@@Button.focusedBackground Button.default.focusedBackground=$Button.focusedBackground
Button.default.hoverBackground=@buttonHoverBackground Button.default.hoverBackground=$Button.hoverBackground
Button.default.pressedBackground=@buttonPressedBackground Button.default.pressedBackground=$Button.pressedBackground
Button.default.borderColor=#4D89C9 Button.default.borderColor=#4F9EE3
Button.default.hoverBorderColor=@@Button.hoverBorderColor Button.default.hoverBorderColor=$Button.hoverBorderColor
Button.default.focusedBorderColor=@@Button.focusedBorderColor Button.default.focusedBorderColor=$Button.focusedBorderColor
Button.default.focusColor=@@Component.focusColor Button.default.focusColor=$Component.focusColor
Button.default.borderWidth=2 Button.default.borderWidth=2
Button.toolbar.hoverBackground=#dfdfdf Button.toolbar.hoverBackground=darken($Button.background,12%,derived autoInverse)
Button.toolbar.pressedBackground=#d8d8d8 Button.toolbar.pressedBackground=darken($Button.background,15%,derived autoInverse)
#---- CheckBox ---- #---- CheckBox ----
CheckBox.icon.borderColor=#878787 CheckBox.icon.borderColor=#b0b0b0
CheckBox.icon.disabledBorderColor=#BDBDBD CheckBox.icon.disabledBorderColor=#BDBDBD
CheckBox.icon.selectedBorderColor=#878787 CheckBox.icon.selectedBorderColor=$CheckBox.icon.borderColor
CheckBox.icon.focusedBorderColor=#7B9FC7 CheckBox.icon.focusedBorderColor=#7B9FC7
CheckBox.icon.hoverBorderColor=@@CheckBox.icon.focusedBorderColor CheckBox.icon.hoverBorderColor=$CheckBox.icon.focusedBorderColor
CheckBox.icon.background=#FFFFFF CheckBox.icon.background=#FFFFFF
CheckBox.icon.disabledBackground=@background CheckBox.icon.disabledBackground=@background
CheckBox.icon.focusedBackground=@@Button.focusedBackground CheckBox.icon.focusedBackground=$Button.focusedBackground
CheckBox.icon.hoverBackground=@buttonHoverBackground CheckBox.icon.hoverBackground=$Button.hoverBackground
CheckBox.icon.pressedBackground=@buttonPressedBackground CheckBox.icon.pressedBackground=$Button.pressedBackground
CheckBox.icon.selectedBackground=#FFFFFF CheckBox.icon.selectedBackground=#FFFFFF
CheckBox.icon.checkmarkColor=#4D89C9 CheckBox.icon.checkmarkColor=#4F9EE3
CheckBox.icon.disabledCheckmarkColor=#ABABAB CheckBox.icon.disabledCheckmarkColor=#ABABAB
@@ -124,12 +129,41 @@ Component.borderColor=#c4c4c4
Component.disabledBorderColor=#cfcfcf Component.disabledBorderColor=#cfcfcf
Component.focusedBorderColor=#87afda Component.focusedBorderColor=#87afda
Component.focusColor=#97c3f3 Component.focusColor=#97c3f3
Component.linkColor=#4a78c2 Component.linkColor=#2470B3
Component.grayFilter=25,-25,100
#---- Desktop ----
Desktop.background=#E6EBF0
#---- DesktopIcon ----
DesktopIcon.background=darken($Desktop.background,10%)
#---- HelpButton ---- #---- HelpButton ----
HelpButton.questionMarkColor=#4D89C9 HelpButton.questionMarkColor=#4F9EE3
#---- InternalFrame ----
InternalFrame.activeTitleBackground=#fff
InternalFrame.activeTitleForeground=@foreground
InternalFrame.inactiveTitleBackground=#fafafa
InternalFrame.inactiveTitleForeground=@disabledText
InternalFrame.activeBorderColor=darken($Component.borderColor,20%)
InternalFrame.inactiveBorderColor=$Component.borderColor
InternalFrame.buttonHoverBackground=darken($InternalFrame.activeTitleBackground,10%,derived autoInverse)
InternalFrame.buttonPressedBackground=darken($InternalFrame.activeTitleBackground,20%,derived autoInverse)
InternalFrame.closeHoverBackground=lazy(Actions.Red)
InternalFrame.closePressedBackground=darken(Actions.Red,10%,lazy)
InternalFrame.closeHoverForeground=#fff
InternalFrame.closePressedForeground=#fff
#---- List ---- #---- List ----
@@ -146,23 +180,29 @@ Menu.icon.disabledArrowColor=#ABABAB
#---- MenuBar ---- #---- MenuBar ----
MenuBar.borderColor=#cdcdcd MenuBar.borderColor=#cdcdcd
MenuBar.hoverBackground=@menuHoverBackground
#---- MenuItemCheckBox ---- #---- MenuItemCheckBox ----
MenuItemCheckBox.icon.checkmarkColor=#4D89C9 MenuItemCheckBox.icon.checkmarkColor=#4F9EE3
MenuItemCheckBox.icon.disabledCheckmarkColor=#ABABAB MenuItemCheckBox.icon.disabledCheckmarkColor=#ABABAB
#---- PasswordField ----
PasswordField.capsLockIconColor=#00000064
#---- PopupMenu ---- #---- PopupMenu ----
PopupMenu.borderColor=#cdcdcd PopupMenu.borderColor=#adadad
#---- ProgressBar ---- #---- ProgressBar ----
ProgressBar.background=#c4c4c4 ProgressBar.background=#D1D1D1
ProgressBar.foreground=#808080 ProgressBar.foreground=#1E82E6
ProgressBar.selectionForeground=@textComponentBackground ProgressBar.selectionForeground=@textComponentBackground
ProgressBar.selectionBackground=@foreground ProgressBar.selectionBackground=@foreground
@@ -170,14 +210,14 @@ ProgressBar.selectionBackground=@foreground
#---- ScrollBar ---- #---- ScrollBar ----
ScrollBar.track=#F5F5F5 ScrollBar.track=#F5F5F5
ScrollBar.thumb=#DBDBDB ScrollBar.thumb=darken($ScrollBar.track,10%)
ScrollBar.hoverTrackColor=#e6e6e6 ScrollBar.hoverTrackColor=darken($ScrollBar.track,3%)
ScrollBar.hoverThumbColor=#c6c6c6 ScrollBar.hoverThumbColor=darken($ScrollBar.thumb,10%)
#---- Separator ---- #---- Separator ----
Separator.foreground=#cdcdcd Separator.foreground=#d1d1d1
#---- Slider ---- #---- Slider ----
@@ -185,7 +225,7 @@ Separator.foreground=#cdcdcd
Slider.trackColor=#c4c4c4 Slider.trackColor=#c4c4c4
Slider.thumbColor=#6e6e6e Slider.thumbColor=#6e6e6e
Slider.tickColor=#888888 Slider.tickColor=#888888
Slider.hoverColor=lighten(15%,autoInverse) Slider.hoverColor=lighten($Slider.thumbColor,15%,derived autoInverse)
Slider.disabledForeground=#c0c0c0 Slider.disabledForeground=#c0c0c0
@@ -197,7 +237,7 @@ SplitPaneDivider.oneTouchHoverArrowColor=#333333
#---- TabbedPane ---- #---- TabbedPane ----
TabbedPane.disabledForeground=#999999 TabbedPane.disabledForeground=@disabledText
TabbedPane.underlineColor=#4083C9 TabbedPane.underlineColor=#4083C9
TabbedPane.disabledUnderlineColor=#ababab TabbedPane.disabledUnderlineColor=#ababab
TabbedPane.hoverColor=#d9d9d9 TabbedPane.hoverColor=#d9d9d9
@@ -208,27 +248,28 @@ TabbedPane.contentAreaColor=#bfbfbf
#---- Table ---- #---- Table ----
Table.background=@textComponentBackground Table.background=@textComponentBackground
Table.gridColor=#F7F7F7 Table.gridColor=darken($Table.background,3%)
#---- TableHeader ---- #---- TableHeader ----
TableHeader.background=#ffffff TableHeader.background=@textComponentBackground
TableHeader.separatorColor=#e5e5e5 TableHeader.separatorColor=darken($TableHeader.background,10%)
TableHeader.bottomSeparatorColor=#e5e5e5 TableHeader.bottomSeparatorColor=$TableHeader.separatorColor
#---- ToggleButton ---- #---- ToggleButton ----
ToggleButton.selectedBackground=#cfcfcf ToggleButton.selectedBackground=darken($ToggleButton.background,20%,derived autoInverse)
ToggleButton.selectedForeground=@foreground ToggleButton.selectedForeground=@foreground
ToggleButton.disabledSelectedBackground=#dfdfdf ToggleButton.disabledSelectedBackground=darken($ToggleButton.background,13%,derived autoInverse)
ToggleButton.toolbar.selectedBackground=#cfcfcf ToggleButton.toolbar.selectedBackground=$ToggleButton.selectedBackground
#---- ToolTip ---- #---- ToolTip ----
ToolTip.border=4,6,4,6,$InternalFrame.activeBorderColor
ToolTip.background=#fafafa ToolTip.background=#fafafa

View File

@@ -16,8 +16,49 @@
#---- Button ---- #---- Button ----
Button.startBackground=$Button.background
Button.endBackground=$Button.background
Button.startBorderColor=$Button.borderColor
Button.endBorderColor=$Button.borderColor
Button.default.startBackground=$Button.default.background
Button.default.endBackground=$Button.default.background
Button.default.startBorderColor=$Button.default.borderColor
Button.default.endBorderColor=$Button.default.borderColor
Button.hoverBorderColor=null Button.hoverBorderColor=null
Button.default.hoverBorderColor=null Button.default.hoverBorderColor=null
Button.default.hoverBackground=@buttonHoverBackground
Button.default.pressedBackground=@buttonPressedBackground #---- HelpButton ----
HelpButton.hoverBorderColor=null
#---- ToggleButton ----
ToggleButton.startBackground=$ToggleButton.background
ToggleButton.endBackground=$ToggleButton.background
[dark]ToggleButton.selectedBackground=lighten($ToggleButton.background,15%,derived autoInverse)
[dark]ToggleButton.disabledSelectedBackground=lighten($ToggleButton.background,5%,derived autoInverse)
#---- theme specific ----
[Gruvbox_Dark_Hard]ToggleButton.selectedBackground=$ToggleButton.selectedBackground
[Gruvbox_Dark_Hard]ToggleButton.toolbar.selectedBackground=$ToggleButton.toolbar.selectedBackground
[Gruvbox_Dark_Medium]ToggleButton.selectedBackground=$ToggleButton.selectedBackground
[Gruvbox_Dark_Medium]ToggleButton.toolbar.selectedBackground=$ToggleButton.toolbar.selectedBackground
[Gruvbox_Dark_Soft]ToggleButton.selectedBackground=$ToggleButton.selectedBackground
[Gruvbox_Dark_Soft]ToggleButton.toolbar.selectedBackground=$ToggleButton.toolbar.selectedBackground
[Hiberbee_Dark]ToggleButton.selectedBackground=$ToggleButton.selectedBackground
[Hiberbee_Dark]ToggleButton.selectedBackground=$ToggleButton.selectedBackground
[Hiberbee_Dark]ToggleButton.toolbar.selectedBackground=$ToggleButton.toolbar.selectedBackground
[High_contrast]ToggleButton.selectedBackground=#fff
[High_contrast]ToggleButton.selectedForeground=#000
[High_contrast]ToggleButton.disabledSelectedBackground=#444
[High_contrast]ToggleButton.toolbar.selectedBackground=#fff

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