Compare commits

...

220 Commits
0.10 ... 0.24

Author SHA1 Message Date
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
Karl Tauber
6ee5234351 release 0.20
(skipping 0.19)
2019-11-29 16:36:21 +01:00
Karl Tauber
398a041ddf screenshots updated 2019-11-29 16:35:28 +01:00
Karl Tauber
02bb73c335 FileChooser: scale file chooser size (issue #5) 2019-11-29 10:29:28 +01:00
Karl Tauber
f37c4cdc4d IntelliJ Themes: added "Save" and "GitHub" buttons to themes list toolbar in demo (and testing apps) to allow users quickly save .theme.json files (including license) or visit theme source code repository 2019-11-29 10:24:04 +01:00
Karl Tauber
d5c9bcddc8 fixed "cannot find symbol" error in NetBeans editor, when source/binary format is set to JDK 9 (or later) in NetBeans project (issue #13)
flatlaf.jar is no longer a multi-release jar
2019-11-28 19:04:42 +01:00
Karl Tauber
f3f8c81518 SwingX: JXTitledPanel support (issue #22) 2019-11-28 18:29:47 +01:00
Karl Tauber
a99ffd4821 fixed color of links in HTML text 2019-11-28 17:06:45 +01:00
Karl Tauber
7b5a9d9949 Demo: added square buttons 2019-11-28 11:29:09 +01:00
Karl Tauber
feb8d0591e Button: make button square if button text is "..." or a single character 2019-11-28 10:55:10 +01:00
Karl Tauber
2197808631 ComboBox: fixed issues with NetBeans org.openide.awt.ColorComboBox component 2019-11-28 10:33:26 +01:00
Karl Tauber
36b3ccc34f IntelliJ Themes: fixes for ScrollBar 2019-11-27 23:51:27 +01:00
Karl Tauber
a5b69d712c IntelliJ Themes Demo: updated themes (used IJThemesUpdater) 2019-11-26 16:26:03 +01:00
Karl Tauber
8edcca9745 IntelliJ Themes Demo: IJThemesUpdater tool added to update themes from source code repositories 2019-11-26 13:31:44 +01:00
Karl Tauber
f3b1f4b608 IntelliJ Themes Demo: replaced themes.properties with themes.json 2019-11-26 11:39:29 +01:00
Karl Tauber
879a8aaa6d renamed FlatLaf.initColorPalette() to initIconColors() 2019-11-26 11:06:13 +01:00
Karl Tauber
900d005d89 IntelliJ Themes: fixes for ComboBox, Spinner, CheckBox and RadioButton 2019-11-26 10:41:11 +01:00
Karl Tauber
c8225171a3 IntelliJ Themes: avoid double setting LaFs 2019-11-26 00:41:47 +01:00
Karl Tauber
c513c052fc SwingX: fixed too wide border when using date picker as table cell editor (issue #24) 2019-11-26 00:05:38 +01:00
Karl Tauber
711c4dd2b5 hex color values in .properties files now must start with a # character 2019-11-25 19:10:51 +01:00
Karl Tauber
81f1ebd1db added readme files to sub-projects 2019-11-24 16:59:13 +01:00
Karl Tauber
c722934510 look and feel identifier returned by FlatLaf.getID() now always starts with "FlatLaf" 2019-11-24 14:08:38 +01:00
Karl Tauber
3ffe8e225d IntelliJ Themes: fixes for Slider:
- use Component.focusColor if Slider.focusedColor is not set (which is the case in all IntelliJ themes)
- compute hover color based on thumb color
- ignore Slider.trackWidth (used in Material Theme UI Lite)
2019-11-24 12:58:38 +01:00
Karl Tauber
6d86cf8f9c IntelliJ Themes: added more popular open-source IntelliJ themes to demo
(downloaded latest .theme.json from source repos;
see themes.properties for source repo URLs)
2019-11-21 13:17:06 +01:00
Karl Tauber
e6e19110b2 HSLColor: fixed javadoc errors 2019-11-20 17:21:49 +01:00
Karl Tauber
b5c13bd1b3 IntelliJ Themes: fixes for ProgressBar 2019-11-20 17:09:49 +01:00
Karl Tauber
b129fe437c JIDE: fixed JidePopup border 2019-11-20 17:01:23 +01:00
Karl Tauber
645be4bfa3 IntelliJ Themes: Tree: fixed renderer background 2019-11-18 19:16:48 +01:00
Karl Tauber
321c49ee32 OptionPane: fixed button order on Mac 2019-11-18 17:31:24 +01:00
Karl Tauber
84db2f9b65 OptionPane:
- fixed background, which was wrong when OptionPane.background is different to Panel.background (e.g. in Hiberbee or Solarized themes)
- use non-UIResource borders in sub-panels to avoid that they are replaced when switching LaF
2019-11-18 13:12:27 +01:00
Karl Tauber
6ba1a419bc FlatSVGIcon:
- use SVGDiagram directly instead of using SVGIcon
- check cliping bounds before painting
- paint red box on errors
2019-11-17 13:04:46 +01:00
Karl Tauber
3de329a332 moved ScaledSVGIcon.java from demo to flatlaf-extras and renamed to FlatSVGIcon.java 2019-11-16 23:23:02 +01:00
Karl Tauber
f175c36736 IntelliJ Themes: OptionPane: use colors for error/information/question/warning icons from default color palette, which enables modification of icon colors by theme 2019-11-16 21:55:28 +01:00
Karl Tauber
f1de65b471 UI inspector: support anonymous classes 2019-11-16 20:13:37 +01:00
Karl Tauber
5069013e6e FileChooser implemented (issue #5) 2019-11-16 17:21:28 +01:00
Karl Tauber
c0642ed620 IntelliJ Themes: apply action/object icons color palette from .theme.json to FlatLaf default color palette in UI defaults 2019-11-16 13:36:19 +01:00
Karl Tauber
a145673dd1 use chevron arrows for table header ascending/descending sort icons (#7) 2019-11-15 23:41:26 +01:00
Karl Tauber
223af48c09 added default color palette for action icons and object icons (based on IntelliJ Platform colors)
and use color palette for file chooser icons (issue #5)
2019-11-15 23:40:52 +01:00
Karl Tauber
d72cfc37d6 fixed jittery submenu rendering on Mac (issue #10) 2019-11-15 18:24:22 +01:00
Karl Tauber
3ba8133890 Table: fixed selection background of checkbox in table cell 2019-11-14 12:33:04 +01:00
Karl Tauber
a907cd7f46 IntelliJ Themes: fixes for text component disabled backgrounds, button backgrounds, combobox disabled backgrounds and spinner disabled backgrounds 2019-11-14 11:28:38 +01:00
Karl Tauber
3b740cb494 IntelliJ Themes: fixes for ComboBox, text components and Spinner 2019-11-13 19:17:34 +01:00
Karl Tauber
0f0f21a7b1 IntelliJ Themes Demo: refresh themes list (from current working directory) on window activation 2019-11-13 13:54:17 +01:00
Karl Tauber
537f6703c0 IntelliJ Themes Demo: search for .theme.json files in current working directory and show them in themes list 2019-11-13 13:53:01 +01:00
Karl Tauber
da0c562ac2 FlatTestFrame: fixed exception when changing scale factor (when running in Java 8) 2019-11-13 10:48:20 +01:00
Karl Tauber
0bef71907c IntelliJ Themes: added core themes to list of themes 2019-11-13 10:44:39 +01:00
Karl Tauber
0ebd43bc5f IntelliJ Themes:
- added themes list to demo (and testing) apps
- added some popular open-source IntelliJ themes to demo
  (downloaded latest .theme.json from source repos;
  see themes.properties for source repo URLs)
2019-11-12 19:23:20 +01:00
Karl Tauber
3092fced3c Demo: moved Laf combobox related code to new class LookAndFeelsComboBox 2019-11-12 16:30:41 +01:00
Karl Tauber
a02784fcba IntelliJ Themes: fixes for Button, CheckBox, RadioButton, ComboBox and Spinner 2019-11-12 12:40:20 +01:00
Karl Tauber
924abde89e StringUtils added 2019-11-12 10:19:56 +01:00
Karl Tauber
f011468819 IntelliJ Themes: use single Laf class for light and dark IntelliJ themes and added IntelliJTheme$ThemeLaf.properties to allow (re-)setting UI defaults before .theme.json is applied 2019-11-12 10:11:26 +01:00
Karl Tauber
11f459d5b0 IntelliJ Themes:
- support theming check boxes and radio buttons
- fixed button background and border
2019-11-11 22:55:40 +01:00
Karl Tauber
974f7d5d68 IntelliJ Themes: reworked applying values so that it works and behaves the same way as in IntelliJ Platform 2019-11-11 18:35:31 +01:00
Karl Tauber
e60db4ff90 IntelliJ Themes: basic support for loading and applying IntelliJ Platform themes in .theme.json format 2019-11-11 16:25:37 +01:00
Karl Tauber
ebe1cd3367 IntelliJ Themes: added internal json parser for parsing IntelliJ .theme.json files
this is a partly copy of https://github.com/ralfstx/minimal-json (license is MIT)
2019-11-11 13:46:36 +01:00
Karl Tauber
30db9d13a5 UIDefaultsLoader:
- detect colors and UIs
- fixed parsing of colors with transparency
- allow '#' prefix colors
2019-11-11 13:39:11 +01:00
Karl Tauber
368611359c documented used UI defaults in most UI delegates 2019-11-10 18:18:57 +01:00
Karl Tauber
746918c054 README.md: fixed link to "JIDE Common Layer" addon 2019-11-10 10:26:34 +01:00
Karl Tauber
6572198178 release 0.18 2019-11-10 10:06:59 +01:00
Karl Tauber
f69b3f56dd ToolTip: use anti-aliasing to render multi-line tooltips 2019-11-10 00:09:51 +01:00
Karl Tauber
c379f2f3b0 JIDE: added missing dependency to flatlaf-testing project 2019-11-09 18:51:52 +01:00
Karl Tauber
e7194e43b4 JIDE: README.md added and publishing added to build.gradle.kts 2019-11-09 18:44:31 +01:00
Karl Tauber
883b282cd8 JIDE: JideTabbedPane: hover tab event if mouse is over close button
unfortunately it is not possible to replace JIDEs arrow and close buttons with own implementations
2019-11-09 18:21:25 +01:00
Karl Tauber
7c2b2d7f26 JIDE: basic JideTabbedPane implementation 2019-11-09 17:23:55 +01:00
Karl Tauber
08f525de5f TabbedPane: content pane is no longer opaque and use antialiasing for painting separator and content border 2019-11-09 15:53:02 +01:00
Karl Tauber
433659a5df TabbedPane: no longer modify BasicTabbedPaneUI.contentBorderInsets in getContentBorderInsets() because this is useless and confusing 2019-11-09 13:57:39 +01:00
Karl Tauber
7f50a30b29 TabbedPane: reworked painting in scroll-tab-layout, so that the separator line now spans the whole width and is no longer interrupted by the scroll buttons 2019-11-09 10:58:31 +01:00
Karl Tauber
d5944779e8 TabbedPane: use FlatClientProperties for JTabbedPane.hasFullBorder client property 2019-11-08 23:01:33 +01:00
Karl Tauber
fdaea31475 JIDE: flatlaf-jide-oss subproject created 2019-11-08 15:51:28 +01:00
Karl Tauber
a66ebd29b4 update to Gradle 5.6.4
./gradlew wrapper --gradle-version=5.6.4
2019-11-07 18:24:33 +01:00
Karl Tauber
f3006467e9 TextField and TextArea: do not apply minimum width if columns property > 0 2019-10-30 15:14:23 +01:00
Karl Tauber
aa52af4c8f added FlatLaf.isDark() 2019-10-29 11:01:48 +01:00
Karl Tauber
2e0fde464d release 0.17 2019-10-27 12:29:12 +01:00
Karl Tauber
9bf0124950 FlatBorder: replaced Paint with Color 2019-10-27 12:27:31 +01:00
Karl Tauber
eaa6db1d19 Table: fixed missing upper right corner (e.g. in SwingX JXTable with column control visible) 2019-10-27 12:25:39 +01:00
Karl Tauber
2ec142f000 Button: hover and pressed background colors are now derived from actual button background color (issue #21) 2019-10-27 11:03:40 +01:00
Karl Tauber
ec572436a9 extracted properties file parsing to new class UIDefaultsLoader 2019-10-25 23:07:44 +02:00
Karl Tauber
6e5e548c9d Testing: fixed content panel insets and removed 5,5 gaps 2019-10-25 10:44:24 +02:00
Karl Tauber
61c3bbad60 ComboBox and Spinner:
- make child components explicitly non-opaque
- paint parent background only if necessary
2019-10-25 10:28:24 +02:00
Karl Tauber
bc10c4e871 Made JComboBox, JProgressBar, JSpinner and JXDatePicker non-opaque.
`JPasswordField`, `JScrollPane` and `JTextField` are non-opaque if they have
an outside focus border (e.g. IntelliJ and Darcula themes).
(issues #20 and #17)
2019-10-25 10:28:18 +02:00
Karl Tauber
8b8d84c2a3 TextField and PasswordField: reduced duplicate code 2019-10-24 20:47:31 +02:00
Karl Tauber
5743b5d59f CheckBox: removed accidentally checked in debug output 2019-10-24 18:07:22 +02:00
Karl Tauber
9450ba5e46 Extras: fixed link in README.md 2019-10-24 15:31:13 +02:00
Karl Tauber
cfcbf3e61c CheckBox:
- compute focus border arc based on Component.focusWidth
- allow specifying border arc in UI defaults (CheckBox.arc)
2019-10-24 14:28:50 +02:00
Karl Tauber
136481c110 Testing: added "opaque" checkbox to test apps 2019-10-24 12:44:16 +02:00
Karl Tauber
7f43b3003c TriStateCheckBox component added 2019-10-24 12:36:40 +02:00
Karl Tauber
1b0c2687c8 Testing: added "background" checkbox to test apps 2019-10-24 10:52:53 +02:00
Karl Tauber
aeb80f862b build.gradle.kts: depend task "assemble" on "sourcesJar" and "javadocJar" so that they are built on Travic CI to file problems early (previously those tasks were build only just before publishing) 2019-10-23 17:06:25 +02:00
Karl Tauber
1de367e19e moved testing applications from src/test to new project flatlaf-testing (part 2) 2019-10-23 16:44:39 +02:00
Karl Tauber
62895a602f moved testing applications from src/test to new project flatlaf-testing (part 1) 2019-10-23 16:44:19 +02:00
Karl Tauber
6438e890bb release 0.16 2019-10-23 10:46:46 +02:00
Karl Tauber
7d72b13ac9 made JButton, JCheckBox, JRadioButton, JToggleButton and JSlider non-opaque (#20) 2019-10-23 10:36:33 +02:00
Karl Tauber
a2e21cb07b fixed Java 9 module descriptor (broken since 0.14) 2019-10-23 09:55:55 +02:00
Karl Tauber
06766cb4db Demo: missing SwingUtilities.invokeLater() added 2019-10-23 09:03:08 +02:00
Karl Tauber
72e8ab70a3 Demo: tooltips added to toolbar buttons 2019-10-22 12:09:04 +02:00
Karl Tauber
0f38af5922 ComboBox: right-to-left fixes (#18) 2019-10-22 12:07:23 +02:00
Karl Tauber
4181759008 right-to-left fixes:
-Slider: colored track (if ticks and labels are hidden) was on the left side of the thumb
- ToolTip: multi-line text was not aligned to the right

(issue #18)
2019-10-21 22:12:51 +02:00
Karl Tauber
fff0e5e946 fixed FlatTestLaf (broken by commit 342b932f9e) 2019-10-21 20:11:35 +02:00
Karl Tauber
be88eeb343 release 0.15 2019-10-21 18:29:40 +02:00
Karl Tauber
342b932f9e ToolTip:
- Improved styling of dark tooltips (darker background, no border).
- increased top and bottom margins
- use brighter color in light theme
- Fixed colors in tooltips of disabled components. (issue #15)
2019-10-21 18:07:43 +02:00
Karl Tauber
964dc14a8a ComboBox: fixed NPE in combobox with custom renderer after switching to FlatLaf (#16; regression in 0.14) 2019-10-21 17:04:23 +02:00
Karl Tauber
b56f462626 SwingX: added screenshots 2019-10-21 13:42:22 +02:00
Karl Tauber
4477b4c44e release 0.14 2019-10-21 10:52:33 +02:00
Karl Tauber
714c6e2920 TextField and PasswordField: fixed minimum width if focusWidth > 2 and not having a FlatBorder 2019-10-20 22:17:15 +02:00
Karl Tauber
0853a1aa2e SwingX: fixed preferred width of JXDatePicker, which was too large (#8) 2019-10-20 22:16:13 +02:00
Karl Tauber
f9d2312b3a ComboBox: fixed StackOverflowError when switching LaF (#14) 2019-10-20 20:04:10 +02:00
Karl Tauber
f53f205f52 SwingX: fixed JXDatePicker.TodayPanel colors (#8) 2019-10-20 18:18:06 +02:00
Karl Tauber
41ecbccc76 EditorPane and TextPane: fixed font and text color when using HTML content (#9) 2019-10-20 18:17:47 +02:00
Karl Tauber
5a952c187c SwingX: JXMonthView support (#8) 2019-10-20 10:47:53 +02:00
Karl Tauber
0a86d00c1e FlatLaf: allow specifying value type in value for cases where auto-detecting value type from key or value does not work 2019-10-19 13:47:53 +02:00
Karl Tauber
b3e9d82537 SwingX: added SwingX LaF addon (#8) 2019-10-19 09:36:43 +02:00
Karl Tauber
0970dceee2 SwingX: JXDatePicker support (#8) 2019-10-19 09:35:01 +02:00
Karl Tauber
ffef71d6db OptionPane: fixed rendering of longer HTML text (#12) 2019-10-18 18:41:14 +02:00
Karl Tauber
0ede8cd5b9 SwingX: build.gradle.kts: added maven publishing and bintray upload 2019-10-18 14:36:04 +02:00
Karl Tauber
c1a9f48e6b SwingX: JXBusyLabel support (#8) 2019-10-18 13:26:30 +02:00
Karl Tauber
3f7215c602 update to Gradle 5.6.3
./gradlew wrapper --gradle-version=5.6.3
2019-10-18 10:34:39 +02:00
Karl Tauber
8b5e3e344a SwingX: JXHeader support (#8) 2019-10-18 10:33:05 +02:00
Karl Tauber
212ff012d6 SwingX: JXTaskPaneContainer and JXTaskPane support (#8) 2019-10-18 10:31:24 +02:00
Karl Tauber
7c77b857f6 SwingX: added test app 2019-10-17 12:09:22 +02:00
Karl Tauber
423c01074a SwingX: flatlaf-swingx subproject created; JXHyperlink support (#8) 2019-10-17 11:21:23 +02:00
Karl Tauber
2dbd584e28 use KeyEventPostProcessor instead of AWTEventListener for listening for Alt key pressed (similar to WindowLookAndFeel) (#4) 2019-10-16 19:42:55 +02:00
Karl Tauber
250f435ceb build.gradle.kts: moved jcenter to root script 2019-10-16 19:39:41 +02:00
Karl Tauber
fa4e409555 ToolBar: disable focusability of buttons in toolbar 2019-10-15 19:00:51 +02:00
Karl Tauber
dfc3b7c796 README.md: intro updated 2019-10-15 11:40:19 +02:00
Karl Tauber
41df9859ad ComboBox: use small border if used as table editor 2019-10-15 10:41:28 +02:00
Karl Tauber
a8b8cbdf8c FlatTestFrame: reduced duplicate code 2019-10-14 17:55:38 +02:00
Karl Tauber
fe2909c56a Demo: build.gradle.kts: added bintray upload 2019-10-14 10:59:45 +02:00
Karl Tauber
3a2fe06c34 README.md: added Maven Central coordinates 2019-10-13 22:54:41 +02:00
Karl Tauber
873e8604ce added developer information to Maven POM for Maven Central publishing 2019-10-13 21:37:49 +02:00
Karl Tauber
2cfcd0facf release 0.12 2019-10-13 17:53:36 +02:00
Karl Tauber
b49eff348d Linux: support KDE (#2)
The Swing fonts are not updated when the user changes system font size (System Settings > Fonts > Force Font DPI). A application restart is necessary. This is the same behavior as in native KDE applications.

tested on Kubuntu 19.04
2019-10-13 17:48:46 +02:00
Karl Tauber
42d801f123 Linux:
- allow user scaling mode on Linux even if running in Java 9+
- update fonts (and scaling) when user changes text size or scaling in Gnome settings

(#1)
2019-10-13 14:15:37 +02:00
Karl Tauber
5e33182de3 Windows: update fonts (and scaling) when user changes Windows text size 2019-10-12 20:14:18 +02:00
Karl Tauber
012d86066d UI inspector: fixed NPE and use anti-aliasing for painting line border on Java 9+ for better results when scaled 2019-10-12 13:43:43 +02:00
Karl Tauber
ff510095a3 UIScale: renamed some methods (to avoid confusion) 2019-10-12 13:42:05 +02:00
Karl Tauber
10283d022f Linux: initialize font (#2) 2019-10-12 10:44:11 +02:00
Karl Tauber
d55abd1b00 README.md: use SVG button for demo download 2019-10-10 11:15:19 +02:00
Karl Tauber
87845fc6f5 added Flat*Laf.install() methods 2019-10-10 10:34:40 +02:00
Karl Tauber
340c0d647c README.md: temporary removed jcenter coordinates because they currently do not work 2019-10-09 11:56:29 +02:00
Karl Tauber
49a574a8d7 macOS: use native screen menu bar if system property apple.laf.useScreenMenuBar is true 2019-10-08 23:41:51 +02:00
Karl Tauber
9216997bba screenshots updated 2019-10-08 23:32:26 +02:00
Karl Tauber
267567d0a0 release 0.11 2019-10-08 13:31:08 +02:00
Karl Tauber
230bc28ddd changed Maven groupId to com.formdev and artifactId to flatlaf 2019-10-08 13:25:59 +02:00
Karl Tauber
57c9ffc2ba Demo: use com.formdev:svgSalamander:1.1.2.1 2019-10-08 10:11:11 +02:00
282 changed files with 49446 additions and 2323 deletions

View File

@@ -1,6 +1,206 @@
FlatLaf Change Log
==================
## 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
- Support using IntelliJ platform themes (.theme.json files).
- Support `JFileChooser`. (issue #5)
- Look and feel identifier returned by `FlatLaf.getID()` now always starts with
"FlatLaf". Use `UIManager.getLookAndFeel().getID().startsWith( "FlatLaf" )` to
check whether the current look and feel is FlatLaf.
- Fixed selection background of checkbox in table cell.
- Fixed color of links in HTML text.
- Fixed jittery submenu rendering on Mac. (issue #10)
- Fixed "cannot find symbol" error in NetBeans editor, when source/binary format
is set to JDK 9 (or later) in NetBeans project. (issue #13)
- Button: Make button square if button text is "..." or a single character.
- ComboBox: Fixed issues with NetBeans `org.openide.awt.ColorComboBox`
component.
- Hex color values in `.properties` files now must start with a `#` character.
- SwingX: Support `JXTitledPanel`. (issue #22)
- SwingX: Fixed too wide border when using date picker as table cell editor.
(issue #24)
- JIDE Common Layer: Fixed `JidePopup` border.
## 0.18
- TextField and TextArea: Do not apply minimum width if `columns` property is
greater than zero.
- TabbedPane: In scroll-tab-layout, the separator line now spans the whole width
and is no longer interrupted by the scroll buttons.
- TabbedPane: Content pane is no longer opaque. Use antialiasing for painting
separator and content border.
- ToolTip: Use anti-aliasing to render multi-line tooltips.
- JIDE Common Layer: Support `JideTabbedPane`.
## 0.17
- CheckBox: Support painting a third state (set client property
"JButton.selectedState" to "indeterminate").
- `TriStateCheckBox` component added (see [FlatLaf Extras](flatlaf-extras)).
- Made `JComboBox`, `JProgressBar`, `JSpinner` and `JXDatePicker` non-opaque.
`JPasswordField`, `JScrollPane` and `JTextField` are non-opaque if they have
an outside focus border (e.g. IntelliJ and Darcula themes). (issues #20 and
#17)
- Button: Hover and pressed background colors are now derived from actual button
background color. (issue #21)
- Table: Fixed missing upper right corner (e.g. in SwingX JXTable with column
control visible).
## 0.16
- Made some fixes for right-to-left support in ComboBox, Slider and ToolTip.
(issue #18)
- Fixed Java 9 module descriptor (broken since 0.14).
- Made `JButton`, `JCheckBox`, `JRadioButton`, `JToggleButton` and `JSlider`
non-opaque. (issue #20)
## 0.15
- ToolTip: Improved styling of dark tooltips (darker background, no border).
- ToolTip: Fixed colors in tooltips of disabled components. (issue #15)
- ComboBox: Fixed NPE in combobox with custom renderer after switching to
FlatLaf. (issue #16; regression in 0.14)
## 0.14
- ComboBox: Use small border if used as table editor.
- ToolBar: Disable focusability of buttons in toolbar.
- OptionPane: Fixed rendering of longer HTML text. (issue #12)
- EditorPane and TextPane: Fixed font and text color when using HTML content.
(issue #9)
- ComboBox: Fixed `StackOverflowError` when switching LaF. (issue #14)
- SwingX: Support `JXBusyLabel`, `JXDatePicker`, `JXHeader`, `JXHyperlink`,
`JXMonthView`, `JXTaskPaneContainer` and `JXTaskPane`. (issue #8)
## 0.13
- Added developer information to Maven POM for Maven Central publishing.
## 0.12
- Support Linux. (issue #2)
- Added `Flat*Laf.install()` methods.
- macOS: Use native screen menu bar if system property
`apple.laf.useScreenMenuBar` is `true`.
- Windows: Update fonts (and scaling) when user changes Windows text size
(Settings > Ease of Access > Display > Make text bigger).
## 0.11
- Changed Maven groupId to `com.formdev` and artifactId to `flatlaf`.
## 0.10
- Use new chevron arrows in "Flat Light" and "Flat Dark" themes, but keep

View File

@@ -1,44 +1,66 @@
FlatLaf - Flat Look and Feel
============================
**FlatLaf** is a modern open-source cross-platform Look and Feel for Java
desktop applications.
**FlatLaf** is a modern **open-source** cross-platform Look and Feel for Java
Swing desktop applications.
It looks mostly "flat" (no shadows or gradients), clean, simple and elegant.
It looks almost flat (no shadows or gradients), clean, simple and elegant.
FlatLaf comes with **Light**, **Dark**, **IntelliJ** and **Darcula** themes,
scales on **HiDPI** displays and runs on Java 8 or newer.
The look is heavily inspired by **Darcula** and **IntelliJ** themes from
IntelliJ IDEA 2019.2+ and uses mostly the same colors and icons.
IntelliJ IDEA 2019.2+ and uses almost the same colors and icons.
![Flat Light Demo](images/FlatLightDemo.png)
![Flat Dark Demo](images/FlatDarkDemo.png)
IntelliJ Platform Themes
------------------------
FlatLaf can use 3rd party themes created for IntelliJ Platform:
![Cyan Light Demo](images/CyanLightDemo.png)
![Dark Purple Demo](images/DarkPurpleDemo.png)
Demo
----
Download
[flatlaf-demo-0.10.jar](https://download.formdev.com/flatlaf/flatlaf-demo-0.10.jar)
and run it with `java -jar flatlaf-demo-0.10.jar` (or double-click it). Requires
Java 8 or newer.
[![Download Demo](https://download.formdev.com/flatlaf/images/download-demo.svg)](https://download.formdev.com/flatlaf/flatlaf-demo-latest.jar)
Run demo with `java -jar flatlaf-demo-<version>.jar` (or double-click it).
Requires Java 8 or newer.
Download
--------
[ ![Download](https://api.bintray.com/packages/jformdesigner/flatlaf/flatlaf-core/images/download.svg) ](https://bintray.com/jformdesigner/flatlaf/flatlaf-core/_latestVersion)
FlatLaf binaries are available on **JCenter** and **Maven Central**.
For Maven or Gradle use:
If you use Maven or Gradle, add a dependency with following coordinates to your
build script:
Repository: jcenter (https://jcenter.bintray.com/)
Group Id: com.formdev.flatlaf
Artifact Id: flatlaf-core
Version: 0.10
groupId: com.formdev
artifactId: flatlaf
version: 0.24
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)
License
-------
Addons
------
FlatLaf is open source licensed under the [Apache 2.0 License](LICENSE)
- [SwingX](flatlaf-swingx)
- [JIDE Common Layer](flatlaf-jide-oss)
Documentation
-------------
For more information and documentation visit
[FlatLaf Home](https://www.formdev.com/flatlaf/)

View File

@@ -14,7 +14,13 @@
* limitations under the License.
*/
version = "0.10"
version = "0.24"
allprojects {
repositories {
jcenter()
}
}
// check required Java version
if( JavaVersion.current() < JavaVersion.VERSION_1_8 )

4
flatlaf-core/README.md Normal file
View File

@@ -0,0 +1,4 @@
FlatLaf Core
============
This sub-project contains the FlatLaf core source code.

View File

@@ -22,10 +22,6 @@ plugins {
id( "com.jfrog.bintray" ) version "1.8.4"
}
repositories {
jcenter()
}
if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) {
sourceSets {
create( "module-info" ) {
@@ -38,17 +34,19 @@ if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) {
}
}
dependencies {
testImplementation( "com.miglayout:miglayout-swing:5.2" )
testImplementation( "com.jgoodies:jgoodies-forms:1.9.0" )
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
tasks {
assemble {
dependsOn(
"sourcesJar",
"javadocJar"
)
}
if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) {
named<JavaCompile>( "compileModuleInfoJava" ) {
sourceCompatibility = "9"
@@ -60,12 +58,7 @@ tasks {
archiveBaseName.set( "flatlaf" )
if( JavaVersion.current() >= JavaVersion.VERSION_1_9 ) {
manifest.attributes(
"Multi-Release" to "true"
)
into( "META-INF/versions/9" ) {
from( sourceSets["module-info"].output )
from( sourceSets["module-info"].output ) {
include( "module-info.class" )
}
}
@@ -97,8 +90,8 @@ tasks {
publishing {
publications {
create<MavenPublication>( "maven" ) {
artifactId = "flatlaf-core"
groupId = "com.formdev.flatlaf"
artifactId = "flatlaf"
groupId = "com.formdev"
from( components["java"] )
@@ -117,6 +110,14 @@ publishing {
}
}
developers {
developer {
name.set( "Karl Tauber" )
organization.set( "FormDev Software GmbH" )
organizationUrl.set( "https://www.formdev.com/" )
}
}
scm {
url.set( "https://github.com/JFormDesigner/FlatLaf" )
}
@@ -133,7 +134,7 @@ bintray {
with( pkg ) {
repo = "flatlaf"
name = "flatlaf-core"
name = "flatlaf"
setLicenses( "Apache-2.0" )
vcsUrl = "https://github.com/JFormDesigner/FlatLaf"

View File

@@ -24,13 +24,111 @@ import javax.swing.JComponent;
*/
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";
/**
* 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 underline style.
* <p>
* <strong>Components</strong> {@link javax.swing.JToggleButton}
*
* @see #TOGGLE_BUTTON_TYPE
*/
String BUTTON_TYPE_UNDERLINE = "underline";
/**
* Paint a help button (circle with question mark).
* <p>
* <strong>Components</strong> {@link javax.swing.JButton}
*
* @see #BUTTON_TYPE
*/
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";
/**
* Paint an indeterminate state on a checkbox.
*
* @see #SELECTED_STATE
*/
String SELECTED_STATE_INDETERMINATE = "indeterminate";
/**
* Specifies minimum width of a component.
* <p>
* <strong>Component</strong> {@link javax.swing.JButton} and {@link javax.swing.text.JTextComponent}<br>
* <strong>Value type</strong> {@link java.lang.Integer}<br>
*/
String MINIMUM_WIDTH = "JComponent.minimumWidth";
/**
* 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";
/**
* Placeholder text that is only painted if the text field is empty.
* <p>
* <strong>Component</strong> {@link javax.swing.JTextField} or {@link javax.swing.JComboBox}<br>
* <strong>Value type</strong> {@link java.lang.String}
*/
String PLACEHOLDER_TEXT = "JTextField.placeholderText";
/**
* Checks whether a client property of a component has the given value.
*/
static boolean clientPropertyEquals( JComponent c, String key, Object 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;
}
}

View File

@@ -19,11 +19,17 @@ package com.formdev.flatlaf;
/**
* A Flat LaF that has a dark color scheme and looks like Darcula LaF.
*
* The UI defaults are loaded from FlatDarculaLaf.properties, FlatDarkLaf.properties and FlatLaf.properties
*
* @author Karl Tauber
*/
public class FlatDarculaLaf
extends FlatDarkLaf
{
public static boolean install( ) {
return install( new FlatDarculaLaf() );
}
@Override
public String getName() {
return "Flat Darcula";

View File

@@ -19,11 +19,17 @@ package com.formdev.flatlaf;
/**
* A Flat LaF that has a dark color scheme.
*
* The UI defaults are loaded from FlatDarkLaf.properties and FlatLaf.properties
*
* @author Karl Tauber
*/
public class FlatDarkLaf
extends FlatLaf
{
public static boolean install( ) {
return install( new FlatDarkLaf() );
}
@Override
public String getName() {
return "Flat Dark";
@@ -33,4 +39,9 @@ public class FlatDarkLaf
public String getDescription() {
return "Flat Dark Look and Feel";
}
@Override
public boolean isDark() {
return true;
}
}

View File

@@ -0,0 +1,62 @@
/*
* 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;
import java.io.InputStream;
/**
* Addon for FlatLaf UI defaults.
*
* Allows loading of additional .properties files 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
* {@code META-INF/services/com.formdev.flatlaf.FlatDefaultsAddon}
* to the addon JAR. The file must contain a single line with the class name.
* <p>
* See 'flatlaf-swingx' addon for an example
*
* @author Karl Tauber
*/
public abstract class FlatDefaultsAddon
{
/**
* Finds an addon .properties file for the given LaF class and returns
* 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 InputStream getDefaults( Class<?> lafClass ) {
Class<?> addonClass = this.getClass();
String propertiesName = '/' + addonClass.getPackage().getName().replace( '.', '/' )
+ '/' + lafClass.getSimpleName() + ".properties";
return addonClass.getResourceAsStream( propertiesName );
}
/**
* 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

@@ -19,11 +19,17 @@ package com.formdev.flatlaf;
/**
* A Flat LaF that has a light color scheme and looks like IntelliJ LaF.
*
* The UI defaults are loaded from FlatIntelliJLaf.properties, FlatLightLaf.properties and FlatLaf.properties
*
* @author Karl Tauber
*/
public class FlatIntelliJLaf
extends FlatLightLaf
{
public static boolean install( ) {
return install( new FlatIntelliJLaf() );
}
@Override
public String getName() {
return "Flat IntelliJ";

View File

@@ -16,42 +16,36 @@
package com.formdev.flatlaf;
import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Insets;
import java.awt.KeyEventPostProcessor;
import java.awt.KeyboardFocusManager;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.AWTEventListener;
import java.awt.event.KeyEvent;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.function.Function;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.AbstractButton;
import javax.swing.JLabel;
import javax.swing.JTabbedPane;
import javax.swing.LookAndFeel;
import javax.swing.PopupFactory;
import javax.swing.SwingUtilities;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.UIDefaults.LazyValue;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.plaf.ColorUIResource;
import javax.swing.plaf.DimensionUIResource;
import javax.swing.plaf.FontUIResource;
import javax.swing.plaf.InsetsUIResource;
import javax.swing.plaf.basic.BasicLookAndFeel;
import javax.swing.plaf.metal.MetalLookAndFeel;
import com.formdev.flatlaf.ui.FlatEmptyBorder;
import com.formdev.flatlaf.ui.FlatLineBorder;
import com.formdev.flatlaf.util.ScaledNumber;
import javax.swing.text.html.HTMLEditorKit;
import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale;
@@ -63,24 +57,46 @@ import com.formdev.flatlaf.util.UIScale;
public abstract class FlatLaf
extends BasicLookAndFeel
{
private static final String VARIABLE_PREFIX = "@";
private static final String REF_PREFIX = VARIABLE_PREFIX + "@";
private static final String OPTIONAL_PREFIX = "?";
private static final String GLOBAL_PREFIX = "*.";
static final Logger LOG = Logger.getLogger( FlatLaf.class.getName() );
private BasicLookAndFeel base;
private AWTEventListener mnemonicListener;
private static boolean altKeyPressed;
private String desktopPropertyName;
private PropertyChangeListener desktopPropertyListener;
private KeyEventPostProcessor mnemonicListener;
private static boolean showMnemonics;
private Consumer<UIDefaults> postInitialization;
public static boolean install( LookAndFeel newLookAndFeel ) {
try {
UIManager.setLookAndFeel( newLookAndFeel );
return true;
} catch( Exception ex ) {
LOG.log( Level.SEVERE, "FlatLaf: Failed to initialize look and feel '" + newLookAndFeel.getClass().getName() + "'.", ex );
return false;
}
}
/**
* Returns the look and feel identifier.
* <p>
* Syntax: "FlatLaf - ${theme-name}"
* <p>
* Use {@code UIManager.getLookAndFeel().getID().startsWith( "FlatLaf" )}
* to check whether the current look and feel is FlatLaf.
*/
@Override
public String getID() {
return getName();
return "FlatLaf - " + getName();
}
public abstract boolean isDark();
@Override
public boolean isNativeLookAndFeel() {
return true;
return false;
}
@Override
@@ -94,22 +110,68 @@ public abstract class FlatLaf
super.initialize();
// make sure that a plain popup factory is used (otherwise sub-menu rendering
// is "jittery" on Mac, where AquaLookAndFeel installs its own popup factory)
if( PopupFactory.getSharedInstance().getClass() != PopupFactory.class )
PopupFactory.setSharedInstance( new PopupFactory() );
// add mnemonic listener
mnemonicListener = e -> {
if( e instanceof KeyEvent && ((KeyEvent)e).getKeyCode() == KeyEvent.VK_ALT )
altKeyChanged( e.getID() == KeyEvent.KEY_PRESSED );
checkShowMnemonics( e );
return false;
};
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventPostProcessor( mnemonicListener );
// listen to desktop property changes to update UI if system font or scaling changes
if( SystemInfo.IS_WINDOWS ) {
// Windows 10 allows increasing font size independent of scaling:
// Settings > Ease of Access > Display > Make text bigger (100% - 225%)
desktopPropertyName = "win.messagebox.font";
} else if( SystemInfo.IS_LINUX ) {
// Linux/Gnome allows extra scaling and larger text:
// Settings > Devices > Displays > Scale (100% or 200%)
// Settings > Universal access > Large Text (off or on, 125%)
desktopPropertyName = "gnome.Xft/DPI";
}
if( desktopPropertyName != null ) {
desktopPropertyListener = e -> {
reSetLookAndFeel();
};
Toolkit.getDefaultToolkit().addPropertyChangeListener( desktopPropertyName, 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 ) );
}
};
Toolkit.getDefaultToolkit().addAWTEventListener( mnemonicListener, AWTEvent.KEY_EVENT_MASK );
}
@Override
public void uninitialize() {
// remove desktop property listener
if( desktopPropertyListener != null ) {
Toolkit.getDefaultToolkit().removePropertyChangeListener( desktopPropertyName, desktopPropertyListener );
desktopPropertyName = null;
desktopPropertyListener = null;
}
// remove mnemonic listener
if( mnemonicListener != null ) {
Toolkit.getDefaultToolkit().removeAWTEventListener( mnemonicListener );
KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventPostProcessor( mnemonicListener );
mnemonicListener = null;
}
// restore default link color
new HTMLEditorKit().getStyleSheet().addRule( "a { color: blue; }" );
postInitialization = null;
if( base != null )
base.uninitialize();
@@ -124,10 +186,11 @@ public abstract class FlatLaf
if( base == null ) {
if( SystemInfo.IS_MAC ) {
// use Mac Aqua LaF as base
String aquaLafClassName = "com.apple.laf.AquaLookAndFeel";
try {
base = (BasicLookAndFeel) Class.forName( "com.apple.laf.AquaLookAndFeel" ).newInstance();
base = (BasicLookAndFeel) Class.forName( aquaLafClassName ).newInstance();
} catch( Exception ex ) {
ex.printStackTrace();
LOG.log( Level.SEVERE, "FlatLaf: Failed to initialize base look and feel '" + aquaLafClassName + "'.", ex );
throw new IllegalStateException();
}
} else
@@ -140,6 +203,9 @@ public abstract class FlatLaf
public UIDefaults getDefaults() {
UIDefaults defaults = getBase().getDefaults();
// add Metal resource bundle, which is required for FlatFileChooserUI
defaults.addResourceBundle( "com.sun.swing.internal.plaf.metal.resources.metal" );
// initialize some defaults (for overriding) that are used in basic UI delegates,
// but are not set in MetalLookAndFeel or BasicLookAndFeel
Color control = defaults.getColor( "control" );
@@ -157,12 +223,40 @@ public abstract class FlatLaf
defaults.put( "Spinner.disabledBackground", control );
defaults.put( "Spinner.disabledForeground", control );
// remember MenuBarUI from Mac Aqua LaF if Mac screen menubar is enabled
boolean useScreenMenuBar = SystemInfo.IS_MAC && "true".equals( System.getProperty( "apple.laf.useScreenMenuBar" ) );
Object aquaMenuBarUI = useScreenMenuBar ? defaults.get( "MenuBarUI" ) : null;
initFonts( defaults );
loadDefaultsFromProperties( defaults );
initIconColors( defaults, isDark() );
// load defaults from properties
List<Class<?>> lafClassesForDefaultsLoading = getLafClassesForDefaultsLoading();
if( lafClassesForDefaultsLoading != null )
UIDefaultsLoader.loadDefaultsFromProperties( lafClassesForDefaultsLoading, defaults );
else
UIDefaultsLoader.loadDefaultsFromProperties( getClass(), defaults );
// use Aqua MenuBarUI if Mac screen menubar is enabled
if( useScreenMenuBar )
defaults.put( "MenuBarUI", aquaMenuBarUI );
invokePostInitialization( defaults );
return defaults;
}
void invokePostInitialization( UIDefaults defaults ) {
if( postInitialization != null ) {
postInitialization.accept( defaults );
postInitialization = null;
}
}
List<Class<?>> getLafClassesForDefaultsLoading() {
return null;
}
private void initFonts( UIDefaults defaults ) {
FontUIResource uiFont = null;
@@ -182,7 +276,8 @@ public abstract class FlatLaf
uiFont = (font instanceof FontUIResource) ? (FontUIResource) font : new FontUIResource( font );
} else if( SystemInfo.IS_LINUX ) {
System.err.println( "WARNING: FlatLaf is not yet tested on Linux!" );
Font font = LinuxFontPolicy.getFont();
uiFont = (font instanceof FontUIResource) ? (FontUIResource) font : new FontUIResource( font );
}
if( uiFont == null )
@@ -196,277 +291,101 @@ public abstract class FlatLaf
defaults.put( key, uiFont );
}
defaults.put( "MenuItem.acceleratorFont", uiFont );
// use smaller font for progress bar
defaults.put( "ProgressBar.font", UIScale.scaleFont( uiFont, 0.85f ) );
}
/**
* Load properties associated to Flat LaF classes and add to UI defaults.
*
* Each class that extend this class may have its own .properties file
* in the same package as the class. Properties from superclasses are loaded
* first to give subclasses a chance to override defaults.
* E.g. if running FlatDarkLaf, then the FlatLaf.properties is loaded first
* and FlatDarkLaf.properties loaded second.
* Adds the default color palette for action icons and object icons to the given UIDefaults.
* <p>
* This method is public and static to allow using the color palette with
* other LaFs (e.g. Windows LaF). To do so invoke:
* {@code FlatLaf.initIconColors( UIManager.getLookAndFeelDefaults(), false );}
* after
* {@code UIManager.setLookAndFeel( ... );}.
* <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>
*/
private void loadDefaultsFromProperties( UIDefaults defaults ) {
// determine classes in class hierarchy in reverse order
ArrayList<Class<?>> lafClasses = new ArrayList<>();
for( Class<?> lafClass = getClass();
FlatLaf.class.isAssignableFrom( lafClass );
lafClass = lafClass.getSuperclass() )
{
lafClasses.add( 0, lafClass );
}
public static void initIconColors( UIDefaults defaults, boolean dark ) {
// colors for action icons
// see https://jetbrains.design/intellij/principles/icons/#action-icons
defaults.put( "Actions.Red", new ColorUIResource( !dark ? 0xDB5860 : 0xC75450 ) );
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 ) );
try {
// load properties files
Properties properties = new Properties();
for( Class<?> lafClass : lafClasses ) {
String propertiesName = "/" + lafClass.getName().replace( '.', '/' ) + ".properties";
try( InputStream in = lafClass.getResourceAsStream( propertiesName ) ) {
if( in != null )
properties.load( in );
}
}
Function<String, String> resolver = value -> {
return resolveValue( properties, value );
};
// get globals, which override all other defaults that end with same suffix
HashMap<String, Object> globals = new HashMap<>();
for( Map.Entry<Object, Object> e : properties.entrySet() ) {
String key = (String) e.getKey();
if( !key.startsWith( GLOBAL_PREFIX ) )
continue;
String value = resolveValue( properties, (String) e.getValue() );
globals.put( key.substring( GLOBAL_PREFIX.length() ), parseValue( key, value, resolver ) );
}
// override UI defaults with globals
for( Object key : defaults.keySet() ) {
if( key instanceof String && ((String)key).contains( "." ) ) {
String skey = (String) key;
String globalKey = skey.substring( skey.lastIndexOf( '.' ) + 1 );
Object globalValue = globals.get( globalKey );
if( globalValue != null )
defaults.put( key, globalValue );
}
}
// add non-global properties to UI defaults
for( Map.Entry<Object, Object> e : properties.entrySet() ) {
String key = (String) e.getKey();
if( key.startsWith( VARIABLE_PREFIX ) || key.startsWith( GLOBAL_PREFIX ) )
continue;
String value = resolveValue( properties, (String) e.getValue() );
defaults.put( key, parseValue( key, value, resolver ) );
}
} catch( IOException ex ) {
ex.printStackTrace();
}
// colors for object icons
// see https://jetbrains.design/intellij/principles/icons/#noun-icons
defaults.put( "Objects.Grey", new ColorUIResource( 0x9AA7B0 ) );
defaults.put( "Objects.Blue", new ColorUIResource( 0x40B6E0 ) );
defaults.put( "Objects.Green", new ColorUIResource( 0x62B543 ) );
defaults.put( "Objects.Yellow", new ColorUIResource( 0xF4AF3D ) );
defaults.put( "Objects.YellowDark", new ColorUIResource( 0xD9A343 ) );
defaults.put( "Objects.Purple", new ColorUIResource( 0xB99BF8 ) );
defaults.put( "Objects.Pink", new ColorUIResource( 0xF98B9E ) );
defaults.put( "Objects.Red", new ColorUIResource( 0xF26522 ) );
defaults.put( "Objects.RedStatus", new ColorUIResource( 0xE05555 ) );
defaults.put( "Objects.GreenAndroid", new ColorUIResource( 0xA4C639 ) );
defaults.put( "Objects.BlackText", new ColorUIResource( 0x231F20 ) );
}
private String resolveValue( Properties properties, String value ) {
if( !value.startsWith( VARIABLE_PREFIX ) )
return value;
if( value.startsWith( REF_PREFIX ) )
value = value.substring( REF_PREFIX.length() );
boolean optional = false;
if( value.startsWith( OPTIONAL_PREFIX ) ) {
value = value.substring( OPTIONAL_PREFIX.length() );
optional = true;
}
String newValue = properties.getProperty( value );
if( newValue == null ) {
if( optional )
return "null";
System.err.println( "variable or reference '" + value + "' not found" );
throw new IllegalArgumentException( value );
}
return resolveValue( properties, newValue );
}
private Object parseValue( String key, String value, Function<String, String> resolver ) {
value = value.trim();
// null, false, true
switch( value ) {
case "null": return null;
case "false": return false;
case "true": return true;
}
// borders
if( key.endsWith( ".border" ) || key.endsWith( "Border" ) )
return parseBorder( value, resolver );
// icons
if( key.endsWith( ".icon" ) || key.endsWith( "Icon" ) )
return parseInstance( value );
// insets
if( key.endsWith( ".margin" ) || key.endsWith( ".padding" ) ||
key.endsWith( "Margins" ) || key.endsWith( "Insets" ) )
return parseInsets( value );
// scaled number
ScaledNumber scaledNumber = parseScaledNumber( key, value );
if( scaledNumber != null )
return scaledNumber;
// size
if( key.endsWith( "Size" ) && !key.equals( "SplitPane.dividerSize" ))
return parseSize( value );
// width, height
if( key.endsWith( "Width" ) || key.endsWith( "Height" ) )
return parseInteger( value, true );
// colors
ColorUIResource color = parseColor( value, false );
if( color != null )
return color;
// integer
Integer integer = parseInteger( value, false );
if( integer != null )
return integer;
// string
return value;
}
private Object parseBorder( String value, Function<String, String> resolver ) {
if( value.indexOf( ',' ) >= 0 ) {
// top,left,bottom,right[,lineColor]
List<String> parts = split( value, ',' );
Insets insets = parseInsets( value );
ColorUIResource lineColor = (parts.size() == 5)
? parseColor( resolver.apply( parts.get( 4 ) ), true )
: null;
return (LazyValue) t -> {
return (lineColor != null)
? new FlatLineBorder( insets, lineColor )
: new FlatEmptyBorder( insets );
};
} else
return parseInstance( value );
}
private Object parseInstance( String value ) {
return (LazyValue) t -> {
private static void reSetLookAndFeel() {
EventQueue.invokeLater( () -> {
LookAndFeel lookAndFeel = UIManager.getLookAndFeel();
try {
return Class.forName( value ).newInstance();
} catch( InstantiationException | IllegalAccessException | ClassNotFoundException ex ) {
ex.printStackTrace();
return null;
// re-set current LaF
UIManager.setLookAndFeel( lookAndFeel );
// must fire property change events ourself because old and new LaF are the same
PropertyChangeEvent e = new PropertyChangeEvent( UIManager.class, "lookAndFeel", lookAndFeel, lookAndFeel );
for( PropertyChangeListener l : UIManager.getPropertyChangeListeners() )
l.propertyChange( e );
// update UI
updateUI();
} catch( UnsupportedLookAndFeelException ex ) {
LOG.log( Level.SEVERE, "FlatLaf: Failed to reinitialize look and feel '" + lookAndFeel.getClass().getName() + "'.", ex );
}
};
} );
}
private Insets parseInsets( String value ) {
List<String> numbers = split( value, ',' );
try {
return new InsetsUIResource(
Integer.parseInt( numbers.get( 0 ) ),
Integer.parseInt( numbers.get( 1 ) ),
Integer.parseInt( numbers.get( 2 ) ),
Integer.parseInt( numbers.get( 3 ) ) );
} catch( NumberFormatException ex ) {
System.err.println( "invalid insets '" + value + "'" );
throw ex;
}
}
private Dimension parseSize( String value ) {
List<String> numbers = split( value, ',' );
try {
return new DimensionUIResource(
Integer.parseInt( numbers.get( 0 ) ),
Integer.parseInt( numbers.get( 1 ) ) );
} catch( NumberFormatException ex ) {
System.err.println( "invalid size '" + value + "'" );
throw ex;
}
}
private ColorUIResource parseColor( String value, boolean reportError ) {
try {
int rgb = Integer.parseInt( value, 16 );
if( value.length() == 6 )
return new ColorUIResource( rgb );
if( value.length() == 8 )
return new ColorUIResource( new Color( rgb, true ) );
if( reportError )
throw new NumberFormatException( value );
} catch( NumberFormatException ex ) {
if( reportError ) {
System.err.println( "invalid color '" + value + "'" );
throw ex;
}
// not a color --> ignore
}
return null;
}
private Integer parseInteger( String value, boolean reportError ) {
try {
return Integer.parseInt( value );
} catch( NumberFormatException ex ) {
if( reportError ) {
System.err.println( "invalid integer '" + value + "'" );
throw ex;
}
}
return null;
}
private ScaledNumber parseScaledNumber( String key, String value ) {
if( !key.equals( "OptionPane.buttonMinimumWidth" ) &&
!key.equals( "SplitPane.oneTouchButtonSize" ) &&
!key.equals( "SplitPane.oneTouchButtonOffset" ) )
return null; // not supported
try {
return new ScaledNumber( Integer.parseInt( value ) );
} catch( NumberFormatException ex ) {
System.err.println( "invalid integer '" + value + "'" );
throw ex;
}
}
public static List<String> split( String str, char delim ) {
ArrayList<String> strs = new ArrayList<>();
int delimIndex = str.indexOf( delim );
int index = 0;
while( delimIndex >= 0 ) {
strs.add( str.substring( index, delimIndex ) );
index = delimIndex + 1;
delimIndex = str.indexOf( delim, index );
}
strs.add( str.substring( index ) );
return strs;
/**
* Update UI of all application windows.
* Invoke after changing LaF.
*/
public static void updateUI() {
for( Window w : Window.getWindows() )
SwingUtilities.updateComponentTreeUI( w );
}
public static boolean isShowMnemonics() {
return altKeyPressed || !UIManager.getBoolean( "Component.hideMnemonics" );
return showMnemonics || !UIManager.getBoolean( "Component.hideMnemonics" );
}
private static void altKeyChanged( boolean pressed ) {
if( pressed == altKeyPressed )
private static void checkShowMnemonics( 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( e.getID() == KeyEvent.KEY_PRESSED && e.isControlDown() && e.isAltDown() );
} else {
// Alt key must be pressed on Windows and Linux
if( keyCode == KeyEvent.VK_ALT )
showMnemonics( e.getID() == KeyEvent.KEY_PRESSED );
}
}
private static void showMnemonics( boolean show ) {
if( show == showMnemonics )
return;
altKeyPressed = pressed;
showMnemonics = show;
// check whether it is necessary to repaint
if( !UIManager.getBoolean( "Component.hideMnemonics" ) )

View File

@@ -19,11 +19,17 @@ package com.formdev.flatlaf;
/**
* A Flat LaF that has a light color scheme.
*
* The UI defaults are loaded from FlatLightLaf.properties and FlatLaf.properties
*
* @author Karl Tauber
*/
public class FlatLightLaf
extends FlatLaf
{
public static boolean install( ) {
return install( new FlatLightLaf() );
}
@Override
public String getName() {
return "Flat Light";
@@ -33,4 +39,9 @@ public class FlatLightLaf
public String getDescription() {
return "Flat Light Look and Feel";
}
@Override
public boolean isDark() {
return false;
}
}

View File

@@ -0,0 +1,537 @@
/*
* 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;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import javax.swing.UIDefaults;
import javax.swing.plaf.ColorUIResource;
import com.formdev.flatlaf.json.Json;
import com.formdev.flatlaf.json.ParseException;
import com.formdev.flatlaf.util.StringUtils;
/**
* This class supports loading IntelliJ .theme.json files and using them as a Laf.
*
* .theme.json files are used by Theme plugins for IntelliJ IDEA and other
* JetBrains IDEs that are based on IntelliJ platform.
*
* Here you can find IntelliJ Theme plugins:
* https://plugins.jetbrains.com/search?tags=Theme
*
* The IntelliJ .theme.json file are documented here:
* http://www.jetbrains.org/intellij/sdk/docs/reference_guide/ui_themes/themes_customize.html
*
* @author Karl Tauber
*/
public class IntelliJTheme
{
public final String name;
public final boolean dark;
public final String author;
private final Map<String, String> colors;
private final Map<String, Object> ui;
private final Map<String, Object> icons;
private Map<String, ColorUIResource> namedColors = Collections.emptyMap();
/**
* Loads a IntelliJ .theme.json file from the given input stream,
* creates a Laf instance for it and installs it.
*
* The input stream is automatically closed.
* Using a buffered input stream is not necessary.
*/
public static boolean install( InputStream in ) {
try {
return FlatLaf.install( createLaf( in ) );
} catch( Exception ex ) {
FlatLaf.LOG.log( Level.SEVERE, "FlatLaf: Failed to load IntelliJ theme", ex );
return false;
}
}
/**
* Loads a IntelliJ .theme.json file from the given input stream and
* creates a Laf instance for it.
*
* The input stream is automatically closed.
* Using a buffered input stream is not necessary.
*/
public static FlatLaf createLaf( InputStream in )
throws IOException, ParseException
{
return createLaf( new IntelliJTheme( in ) );
}
/**
* Creates a Laf instance for the given IntelliJ theme.
*/
public static FlatLaf createLaf( IntelliJTheme theme ) {
return new ThemeLaf( theme );
}
/**
* Loads a IntelliJ .theme.json file from the given input stream.
*
* The input stream is automatically closed.
* Using a buffered input stream is not necessary.
*/
@SuppressWarnings( "unchecked" )
public IntelliJTheme( InputStream in )
throws IOException, ParseException
{
Map<String, Object> json;
try( Reader reader = new InputStreamReader( in, StandardCharsets.UTF_8 ) ) {
json = (Map<String, Object>) Json.parse( reader );
}
name = (String) json.get( "name" );
dark = Boolean.parseBoolean( (String) json.get( "dark" ) );
author = (String) json.get( "author" );
colors = (Map<String, String>) json.get( "colors" );
ui = (Map<String, Object>) json.get( "ui" );
icons = (Map<String, Object>) json.get( "icons" );
}
private void applyProperties( UIDefaults defaults ) {
if( ui == null )
return;
defaults.put( "Component.isIntelliJTheme", true );
// enable button shadows
defaults.put( "Button.paintShadow", true );
defaults.put( "Button.shadowWidth", dark ? 2 : 1 );
loadNamedColors( defaults );
// convert Json "ui" structure to UI defaults
ArrayList<Object> defaultsKeysCache = new ArrayList<>();
Set<String> uiKeys = new HashSet<>();
for( Map.Entry<String, Object> e : ui.entrySet() )
apply( e.getKey(), e.getValue(), defaults, defaultsKeysCache, uiKeys );
applyColorPalette( 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
defaults.put( "ComboBox.editableBackground", defaults.get( "TextField.background" ) );
defaults.put( "Spinner.background", defaults.get( "TextField.background" ) );
// Spinner arrow button always has same colors as ComboBox arrow button
defaults.put( "Spinner.buttonBackground", defaults.get( "ComboBox.buttonEditableBackground" ) );
defaults.put( "Spinner.buttonArrowColor", defaults.get( "ComboBox.buttonArrowColor" ) );
defaults.put( "Spinner.buttonDisabledArrowColor", defaults.get( "ComboBox.buttonDisabledArrowColor" ) );
// some themes specify colors for TextField.background, but forget to specify it for other components
// (probably because those components are not used in IntelliJ)
if( uiKeys.contains( "TextField.background" ) ) {
Object textFieldBackground = defaults.get( "TextField.background" );
if( !uiKeys.contains( "FormattedTextField.background" ) )
defaults.put( "FormattedTextField.background", textFieldBackground );
if( !uiKeys.contains( "PasswordField.background" ) )
defaults.put( "PasswordField.background", textFieldBackground );
if( !uiKeys.contains( "EditorPane.background" ) )
defaults.put( "EditorPane.background", textFieldBackground );
if( !uiKeys.contains( "TextArea.background" ) )
defaults.put( "TextArea.background", textFieldBackground );
if( !uiKeys.contains( "TextPane.background" ) )
defaults.put( "TextPane.background", textFieldBackground );
if( !uiKeys.contains( "Spinner.background" ) )
defaults.put( "Spinner.background", textFieldBackground );
}
}
/**
* http://www.jetbrains.org/intellij/sdk/docs/reference_guide/ui_themes/themes_customize.html#defining-named-colors
*/
private void loadNamedColors( UIDefaults defaults ) {
if( colors == null )
return;
namedColors = new HashMap<>();
for( Map.Entry<String, String> e : colors.entrySet() ) {
String value = e.getValue();
ColorUIResource color = UIDefaultsLoader.parseColor( value );
if( color != null ) {
String key = e.getKey();
namedColors.put( key, color );
defaults.put( "ColorPalette." + e.getKey(), color );
}
}
}
/**
* http://www.jetbrains.org/intellij/sdk/docs/reference_guide/ui_themes/themes_customize.html#custom-ui-control-colors
*/
@SuppressWarnings( "unchecked" )
private void apply( String key, Object value, UIDefaults defaults, ArrayList<Object> defaultsKeysCache, Set<String> uiKeys ) {
if( value instanceof Map ) {
for( Map.Entry<String, Object> e : ((Map<String, Object>)value).entrySet() )
apply( key + '.' + e.getKey(), e.getValue(), defaults, defaultsKeysCache, uiKeys );
} else {
uiKeys.add( key );
// map keys
key = uiKeyMapping.getOrDefault( key, key );
if( key.isEmpty() )
return; // ignore key
String valueStr = value.toString();
// map named colors
Object uiValue = namedColors.get( valueStr );
// parse value
if( uiValue == null ) {
// fix errors (missing '#' for colors)
if( !valueStr.startsWith( "#" ) && (key.endsWith( "ground" ) || key.endsWith( "Color" )) )
valueStr = fixColorIfValid( "#" + valueStr, valueStr );
else if( valueStr.startsWith( "##" ) )
valueStr = fixColorIfValid( valueStr.substring( 1 ), valueStr );
else if( key.endsWith( ".border" ) || key.endsWith( "Border" ) ) {
List<String> parts = StringUtils.split( valueStr, ',' );
if( parts.size() == 5 && !parts.get( 4 ).startsWith( "#" ) ) {
parts.set( 4, "#" + parts.get( 4 ) );
valueStr = String.join( ",", parts );
}
}
// parse value
try {
uiValue = UIDefaultsLoader.parseValue( key, valueStr );
} catch( RuntimeException ex ) {
UIDefaultsLoader.logParseError( Level.CONFIG, key, valueStr, ex );
return; // ignore invalid value
}
}
if( key.startsWith( "*." ) ) {
// wildcard
String tail = key.substring( 1 );
// because we can not iterate over the UI defaults keys while
// modifying UI defaults in the same loop, we have to copy the keys
if( defaultsKeysCache.size() != defaults.size() ) {
defaultsKeysCache.clear();
Enumeration<Object> e = defaults.keys();
while( e.hasMoreElements() )
defaultsKeysCache.add( e.nextElement() );
}
// replace all values in UI defaults that match the wildcard key
for( Object k : defaultsKeysCache ) {
if( k instanceof String ) {
// support replacing of mapped keys
// (e.g. set ComboBox.buttonEditableBackground to *.background
// because it is mapped from ComboBox.ArrowButton.background)
String km = uiKeyInverseMapping.getOrDefault( k, (String) k );
if( km.endsWith( tail ) && !noWildcardReplace.contains( k ) && !((String)k).startsWith( "CheckBox.icon." ) )
defaults.put( k, uiValue );
}
}
} else
defaults.put( key, uiValue );
}
}
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 ) {
if( icons == null )
return;
Object palette = icons.get( "ColorPalette" );
if( !(palette instanceof Map) )
return;
@SuppressWarnings( "unchecked" )
Map<String, Object> colorPalette = (Map<String, Object>) palette;
for( Map.Entry<String, Object> e : colorPalette.entrySet() ) {
String key = e.getKey();
Object value = e.getValue();
if( key.startsWith( "Checkbox." ) || !(value instanceof String) )
continue;
if( dark )
key = StringUtils.removeTrailing( key, ".Dark" );
ColorUIResource color = toColor( (String) value );
if( color != null )
defaults.put( key, color );
}
}
private ColorUIResource toColor( String value ) {
// map named colors
ColorUIResource color = namedColors.get( value );
// parse color
return (color != null) ? color : UIDefaultsLoader.parseColor( value );
}
/**
* Because IDEA uses SVGs for check boxes and radio buttons the colors for
* this two components are specified in "icons > ColorPalette".
* FlatLaf uses vector icons and expects colors for the two components in UI defaults.
*/
private void applyCheckBoxColors( UIDefaults defaults ) {
if( icons == null )
return;
Object palette = icons.get( "ColorPalette" );
if( !(palette instanceof Map) )
return;
boolean checkboxModified = false;
@SuppressWarnings( "unchecked" )
Map<String, Object> colorPalette = (Map<String, Object>) palette;
for( Map.Entry<String, Object> e : colorPalette.entrySet() ) {
String key = e.getKey();
Object value = e.getValue();
if( !key.startsWith( "Checkbox." ) || !(value instanceof String) )
continue;
if( key.equals( "Checkbox.Background.Default" ) ||
key.equals( "Checkbox.Foreground.Selected" ) )
{
// This two keys do not work correctly in IDEA because they
// map SVG color "#ffffff" to another color, but checkBox.svg and
// radio.svg (in package com.intellij.ide.ui.laf.icons.intellij)
// use "#fff". So use white to get same appearance as in IDEA.
value = "#ffffff";
}
String key2 = checkboxDuplicateColors.get( key );
if( dark )
key = StringUtils.removeTrailing( key, ".Dark" );
String newKey = checkboxKeyMapping.get( key );
if( newKey != null ) {
ColorUIResource color = toColor( (String) value );
if( color != null ) {
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;
}
}
// remove hover and pressed colors
if( checkboxModified ) {
defaults.remove( "CheckBox.icon.hoverBorderColor" );
defaults.remove( "CheckBox.icon.focusedBackground" );
defaults.remove( "CheckBox.icon.hoverBackground" );
defaults.remove( "CheckBox.icon.pressedBackground" );
defaults.remove( "CheckBox.icon.selectedHoverBackground" );
defaults.remove( "CheckBox.icon.selectedPressedBackground" );
}
// copy values
for( Map.Entry<String, String> e : uiKeyCopying.entrySet() )
defaults.put( e.getKey(), defaults.get( e.getValue() ) );
}
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> checkboxKeyMapping = new HashMap<>();
private static Map<String, String> checkboxDuplicateColors = new HashMap<>();
private static Set<String> noWildcardReplace = new HashSet<>();
static {
// ComboBox
uiKeyMapping.put( "ComboBox.background", "" ); // ignore
uiKeyMapping.put( "ComboBox.nonEditableBackground", "ComboBox.background" );
uiKeyMapping.put( "ComboBox.ArrowButton.background", "ComboBox.buttonEditableBackground" );
uiKeyMapping.put( "ComboBox.ArrowButton.disabledIconColor", "ComboBox.buttonDisabledArrowColor" );
uiKeyMapping.put( "ComboBox.ArrowButton.iconColor", "ComboBox.buttonArrowColor" );
uiKeyMapping.put( "ComboBox.ArrowButton.nonEditableBackground", "ComboBox.buttonBackground" );
// Link
uiKeyMapping.put( "Link.activeForeground", "Component.linkColor" );
// ProgressBar
uiKeyMapping.put( "ProgressBar.background", "" ); // ignore
uiKeyMapping.put( "ProgressBar.foreground", "" ); // ignore
uiKeyMapping.put( "ProgressBar.trackColor", "ProgressBar.background" );
uiKeyMapping.put( "ProgressBar.progressColor", "ProgressBar.foreground" );
// ScrollBar
uiKeyMapping.put( "ScrollBar.trackColor", "ScrollBar.track" );
uiKeyMapping.put( "ScrollBar.thumbColor", "ScrollBar.thumb" );
// Separator
uiKeyMapping.put( "Separator.separatorColor", "Separator.foreground" );
// Slider
uiKeyMapping.put( "Slider.trackWidth", "" ); // ignore (used in Material Theme UI Lite)
for( Map.Entry<String, String> e : uiKeyMapping.entrySet() )
uiKeyInverseMapping.put( e.getValue(), e.getKey() );
uiKeyCopying.put( "ToggleButton.underline.underlineColor", "TabbedPane.underlineColor" );
uiKeyCopying.put( "ToggleButton.underline.disabledUnderlineColor", "TabbedPane.disabledUnderlineColor" );
uiKeyCopying.put( "ToggleButton.underline.selectedBackground", "TabbedPane.selectedBackground" );
uiKeyCopying.put( "ToggleButton.underline.hoverBackground", "TabbedPane.hoverColor" );
uiKeyCopying.put( "ToggleButton.underline.focusBackground", "TabbedPane.focusColor" );
checkboxKeyMapping.put( "Checkbox.Background.Default", "CheckBox.icon.background" );
checkboxKeyMapping.put( "Checkbox.Background.Disabled", "CheckBox.icon.disabledBackground" );
checkboxKeyMapping.put( "Checkbox.Border.Default", "CheckBox.icon.borderColor" );
checkboxKeyMapping.put( "Checkbox.Border.Disabled", "CheckBox.icon.disabledBorderColor" );
checkboxKeyMapping.put( "Checkbox.Focus.Thin.Default", "CheckBox.icon.focusedBorderColor" );
checkboxKeyMapping.put( "Checkbox.Focus.Wide", "CheckBox.icon.focusedColor" );
checkboxKeyMapping.put( "Checkbox.Foreground.Disabled", "CheckBox.icon.disabledCheckmarkColor" );
checkboxKeyMapping.put( "Checkbox.Background.Selected", "CheckBox.icon.selectedBackground" );
checkboxKeyMapping.put( "Checkbox.Border.Selected", "CheckBox.icon.selectedBorderColor" );
checkboxKeyMapping.put( "Checkbox.Foreground.Selected", "CheckBox.icon.checkmarkColor" );
checkboxKeyMapping.put( "Checkbox.Focus.Thin.Selected", "CheckBox.icon.selectedFocusedBorderColor" );
checkboxDuplicateColors.put( "Checkbox.Background.Default.Dark", "Checkbox.Background.Selected.Dark" );
checkboxDuplicateColors.put( "Checkbox.Border.Default.Dark", "Checkbox.Border.Selected.Dark" );
checkboxDuplicateColors.put( "Checkbox.Focus.Thin.Default.Dark", "Checkbox.Focus.Thin.Selected.Dark" );
@SuppressWarnings( "unchecked" )
Map.Entry<String, String>[] entries = checkboxDuplicateColors.entrySet().toArray( new Map.Entry[checkboxDuplicateColors.size()] );
for( Map.Entry<String, String> e : entries )
checkboxDuplicateColors.put( e.getValue(), e.getKey() );
// because FlatLaf uses Button.background and Button.borderColor,
// but IDEA uses Button.startBackground and Button.startBorderColor,
// our default button background and border colors may be replaced by
// wildcard *.background and *.borderColor colors
noWildcardReplace.add( "Button.background" );
noWildcardReplace.add( "Button.borderColor" );
noWildcardReplace.add( "Button.default.background" );
noWildcardReplace.add( "Button.default.borderColor" );
noWildcardReplace.add( "ToggleButton.background" );
}
//---- class ThemeLaf -----------------------------------------------------
public static class ThemeLaf
extends FlatLaf
{
private final IntelliJTheme theme;
public ThemeLaf( IntelliJTheme theme ) {
this.theme = theme;
}
@Override
public String getName() {
return theme.name;
}
@Override
public String getDescription() {
return theme.name;
}
@Override
public boolean isDark() {
return theme.dark;
}
public IntelliJTheme getTheme() {
return theme;
}
@Override
public UIDefaults getDefaults() {
UIDefaults defaults = super.getDefaults();
theme.applyProperties( defaults );
super.invokePostInitialization( defaults );
return defaults;
}
@Override
void invokePostInitialization( UIDefaults defaults ) {
}
@Override
ArrayList<Class<?>> getLafClassesForDefaultsLoading() {
ArrayList<Class<?>> lafClasses = new ArrayList<>();
lafClasses.add( FlatLaf.class );
lafClasses.add( theme.dark ? FlatDarkLaf.class : FlatLightLaf.class );
lafClasses.add( theme.dark ? FlatDarculaLaf.class : FlatIntelliJLaf.class );
lafClasses.add( ThemeLaf.class );
return lafClasses;
}
}
}

View File

@@ -0,0 +1,250 @@
/*
* 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;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.awt.Toolkit;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.StringTokenizer;
import java.util.logging.Level;
import javax.swing.text.StyleContext;
import com.formdev.flatlaf.util.StringUtils;
import com.formdev.flatlaf.util.SystemInfo;
/**
* @author Karl Tauber
*/
class LinuxFontPolicy
{
static Font getFont() {
return SystemInfo.IS_KDE ? getKDEFont() : getGnomeFont();
}
/**
* Gets the default font for Gnome.
*/
private static Font getGnomeFont() {
// see class com.sun.java.swing.plaf.gtk.PangoFonts background information
Object fontName = Toolkit.getDefaultToolkit().getDesktopProperty( "gnome.Gtk/FontName" );
if( !(fontName instanceof String) )
fontName = "sans 10";
String family = "";
int style = Font.PLAIN;
int size = 10;
StringTokenizer st = new StringTokenizer( (String) fontName );
while( st.hasMoreTokens() ) {
String word = st.nextToken();
if( word.equalsIgnoreCase( "italic" ) )
style |= Font.ITALIC;
else if( word.equalsIgnoreCase( "bold" ) )
style |= Font.BOLD;
else if( Character.isDigit( word.charAt( 0 ) ) ) {
try {
size = Integer.parseInt( word );
} catch( NumberFormatException ex ) {
// ignore
}
} else
family = family.isEmpty() ? word : (family + ' ' + word);
}
// scale font size
double dsize = size * getGnomeFontScale();
size = (int) (dsize + 0.5);
if( size < 1 )
size = 1;
// handle logical font names
String logicalFamily = mapFcName( family.toLowerCase() );
if( logicalFamily != null )
family = logicalFamily;
return createFont( family, style, size, dsize );
}
private static Font createFont( String family, int style, int size, double dsize ) {
// using StyleContext.getFont() here because it uses
// sun.font.FontUtilities.getCompositeFontUIResource()
Font font = new StyleContext().getFont( family, style, size );
// set font size in floating points
font = font.deriveFont( style, (float) dsize );
return font;
}
private static double getGnomeFontScale() {
// see class com.sun.java.swing.plaf.gtk.PangoFonts background information
Object value = Toolkit.getDefaultToolkit().getDesktopProperty( "gnome.Xft/DPI" );
if( value instanceof Integer ) {
int dpi = ((Integer)value).intValue() / 1024;
if( dpi == -1 )
dpi = 96;
if( dpi < 50 )
dpi = 50;
return dpi / 72.0;
} else {
return GraphicsEnvironment.getLocalGraphicsEnvironment()
.getDefaultScreenDevice().getDefaultConfiguration()
.getNormalizingTransform().getScaleY();
}
}
/**
* map GTK/fontconfig names to equivalent JDK logical font name
*/
private static String mapFcName( String name ) {
switch( name ) {
case "sans": return "sansserif";
case "sans-serif": return "sansserif";
case "serif": return "serif";
case "monospace": return "monospaced";
}
return null;
}
/**
* Gets the default font for KDE for KDE configuration files.
*
* The Swing fonts are not updated when the user changes system font size
* (System Settings > Fonts > Force Font DPI). A application restart is necessary.
* This is the same behavior as in native KDE applications.
*
* The "display scale factor" (kdeglobals: [KScreen] > ScaleFactor) is not used
* KDE also does not use it to calculate font size. Only forceFontDPI is used by KDE.
* If user changes "display scale factor" (System Settings > Display and Monitors >
* Displays > Scale Display), the forceFontDPI is also changed to reflect the scale factor.
*/
private static Font getKDEFont() {
List<String> kdeglobals = readConfig( "kdeglobals" );
List<String> kcmfonts = readConfig( "kcmfonts" );
String generalFont = getConfigEntry( kdeglobals, "General", "font" );
String forceFontDPI = getConfigEntry( kcmfonts, "General", "forceFontDPI" );
String family = "sansserif";
int style = Font.PLAIN;
int size = 10;
if( generalFont != null ) {
List<String> strs = StringUtils.split( generalFont, ',' );
try {
family = strs.get( 0 );
size = Integer.parseInt( strs.get( 1 ) );
if( "75".equals( strs.get( 4 ) ) )
style |= Font.BOLD;
if( "1".equals( strs.get( 5 ) ) )
style |= Font.ITALIC;
} catch( RuntimeException ex ) {
FlatLaf.LOG.log( Level.CONFIG, "FlatLaf: Failed to parse 'font=" + generalFont + "'.", ex );
}
}
// font dpi
int dpi = 96;
if( forceFontDPI != null ) {
try {
dpi = Integer.parseInt( forceFontDPI );
if( dpi <= 0 )
dpi = 96;
if( dpi < 50 )
dpi = 50;
} catch( NumberFormatException ex ) {
FlatLaf.LOG.log( Level.CONFIG, "FlatLaf: Failed to parse 'forceFontDPI=" + forceFontDPI + "'.", ex );
}
}
// scale font size
double fontScale = dpi / 72.0;
double dsize = size * fontScale;
size = (int) (dsize + 0.5);
if( size < 1 )
size = 1;
return createFont( family, style, size, dsize );
}
private static List<String> readConfig( String filename ) {
File userHome = new File( System.getProperty( "user.home" ) );
// search for config file
String[] configDirs = {
".config", // KDE 5
".kde4/share/config", // KDE 4
".kde/share/config"// KDE 3
};
File file = null;
for( String configDir : configDirs ) {
file = new File( userHome, configDir + "/" + filename );
if( file.isFile() )
break;
}
if( !file.isFile() )
return Collections.emptyList();
// read config file
ArrayList<String> lines = new ArrayList<>( 200 );
try( BufferedReader reader = new BufferedReader( new FileReader( file ) ) ) {
String line = null;
while( (line = reader.readLine()) != null )
lines.add( line );
} catch( IOException ex ) {
FlatLaf.LOG.log( Level.CONFIG, "FlatLaf: Failed to read '" + filename + "'.", ex );
}
return lines;
}
private static String getConfigEntry( List<String> config, String group, String key ) {
int groupLength = group.length();
int keyLength = key.length();
boolean inGroup = false;
for( String line : config ) {
if( !inGroup ) {
if( line.length() >= groupLength + 2 &&
line.charAt( 0 ) == '[' &&
line.charAt( groupLength + 1 ) == ']' &&
line.indexOf( group ) == 1 )
{
inGroup = true;
}
} else {
if( line.startsWith( "[" ) )
return null;
if( line.length() >= keyLength + 2 &&
line.charAt( keyLength ) == '=' &&
line.startsWith( key ) )
{
return line.substring( keyLength + 1 );
}
}
}
return null;
}
}

View File

@@ -0,0 +1,687 @@
/*
* 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;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Insets;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.ServiceLoader;
import java.util.function.Function;
import java.util.logging.Level;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.UIDefaults.ActiveValue;
import javax.swing.UIDefaults.LazyValue;
import javax.swing.plaf.ColorUIResource;
import javax.swing.plaf.DimensionUIResource;
import javax.swing.plaf.InsetsUIResource;
import com.formdev.flatlaf.ui.FlatEmptyBorder;
import com.formdev.flatlaf.ui.FlatLineBorder;
import com.formdev.flatlaf.util.ColorFunctions;
import com.formdev.flatlaf.util.DerivedColor;
import com.formdev.flatlaf.util.HSLColor;
import com.formdev.flatlaf.util.StringUtils;
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.
*
* Each class that extend the LaF class may have its own .properties file
* in the same package as the class. Properties from superclasses are loaded
* first to give subclasses a chance to override defaults.
* E.g. if running FlatDarkLaf, then the FlatLaf.properties is loaded first
* and FlatDarkLaf.properties loaded second.
*
* @author Karl Tauber
*/
class UIDefaultsLoader
{
private static final String TYPE_PREFIX = "{";
private static final String TYPE_PREFIX_END = "}";
private static final String VARIABLE_PREFIX = "@";
@Deprecated
private static final String REF_PREFIX = VARIABLE_PREFIX + "@";
private static final String PROPERTY_PREFIX = "$";
private static final String OPTIONAL_PREFIX = "?";
private static final String GLOBAL_PREFIX = "*.";
static void loadDefaultsFromProperties( Class<?> lookAndFeelClass, UIDefaults defaults ) {
// determine classes in class hierarchy in reverse order
ArrayList<Class<?>> lafClasses = new ArrayList<>();
for( Class<?> lafClass = lookAndFeelClass;
FlatLaf.class.isAssignableFrom( lafClass );
lafClass = lafClass.getSuperclass() )
{
lafClasses.add( 0, lafClass );
}
loadDefaultsFromProperties( lafClasses, defaults );
}
static void loadDefaultsFromProperties( List<Class<?>> lafClasses, UIDefaults defaults ) {
try {
// load core properties files
Properties properties = new Properties();
for( Class<?> lafClass : lafClasses ) {
String propertiesName = '/' + lafClass.getName().replace( '.', '/' ) + ".properties";
try( InputStream in = lafClass.getResourceAsStream( propertiesName ) ) {
if( in != null )
properties.load( in );
}
}
// get addons and sort them by priority
ServiceLoader<FlatDefaultsAddon> addonLoader = ServiceLoader.load( FlatDefaultsAddon.class );
List<FlatDefaultsAddon> addonList = new ArrayList<>();
for( FlatDefaultsAddon addon : addonLoader )
addonList.add( addon );
addonList.sort( (addon1, addon2) -> addon1.getPriority() - addon2.getPriority() );
// load properties from addons
for( FlatDefaultsAddon addon : addonList ) {
for( Class<?> lafClass : lafClasses ) {
try( InputStream in = addon.getDefaults( lafClass ) ) {
if( in != null )
properties.load( in );
}
}
}
// collect addon class loaders
List<ClassLoader> addonClassLoaders = new ArrayList<>();
for( FlatDefaultsAddon addon : addonList ) {
ClassLoader addonClassLoader = addon.getClass().getClassLoader();
if( !addonClassLoaders.contains( addonClassLoader ) )
addonClassLoaders.add( addonClassLoader );
}
// collect all platform specific keys (but do not modify properties)
ArrayList<String> platformSpecificKeys = new ArrayList<>();
for( Object key : properties.keySet() ) {
if( ((String)key).startsWith( "[" ) )
platformSpecificKeys.add( (String) key );
}
// remove platform specific properties and re-add only properties
// for current platform, but with platform prefix removed
if( !platformSpecificKeys.isEmpty() ) {
String platformPrefix =
SystemInfo.IS_WINDOWS ? "[win]" :
SystemInfo.IS_MAC ? "[mac]" :
SystemInfo.IS_LINUX ? "[linux]" : "[unknown]";
for( String key : platformSpecificKeys ) {
Object value = properties.remove( key );
if( key.startsWith( platformPrefix ) )
properties.put( key.substring( platformPrefix.length() ), value );
}
}
Function<String, String> resolver = value -> {
return resolveValue( properties, value );
};
// get globals, which override all other defaults that end with same suffix
HashMap<String, Object> globals = new HashMap<>();
for( Map.Entry<Object, Object> e : properties.entrySet() ) {
String key = (String) e.getKey();
if( !key.startsWith( GLOBAL_PREFIX ) )
continue;
String value = resolveValue( properties, (String) e.getValue() );
try {
globals.put( key.substring( GLOBAL_PREFIX.length() ), parseValue( key, value, resolver, addonClassLoaders ) );
} catch( RuntimeException ex ) {
logParseError( Level.SEVERE, key, value, ex );
}
}
// override UI defaults with globals
for( Object key : defaults.keySet() ) {
if( key instanceof String && ((String)key).contains( "." ) ) {
String skey = (String) key;
String globalKey = skey.substring( skey.lastIndexOf( '.' ) + 1 );
Object globalValue = globals.get( globalKey );
if( globalValue != null )
defaults.put( key, globalValue );
}
}
// add non-global properties to UI defaults
for( Map.Entry<Object, Object> e : properties.entrySet() ) {
String key = (String) e.getKey();
if( key.startsWith( VARIABLE_PREFIX ) || key.startsWith( GLOBAL_PREFIX ) )
continue;
String value = resolveValue( properties, (String) e.getValue() );
try {
defaults.put( key, parseValue( key, value, resolver, addonClassLoaders ) );
} catch( RuntimeException ex ) {
logParseError( Level.SEVERE, key, value, ex );
}
}
} catch( IOException ex ) {
FlatLaf.LOG.log( Level.SEVERE, "FlatLaf: Failed to load properties files.", ex );
}
}
static void logParseError( Level level, String key, String value, RuntimeException ex ) {
FlatLaf.LOG.log( level, "FlatLaf: Failed to parse: '" + key + '=' + value + '\'', ex );
}
private static String resolveValue( Properties properties, String value ) {
if( value.startsWith( PROPERTY_PREFIX ) )
value = value.substring( PROPERTY_PREFIX.length() );
else if( !value.startsWith( VARIABLE_PREFIX ) )
return value;
// for compatibility
if( value.startsWith( REF_PREFIX ) ) {
FlatLaf.LOG.log( Level.WARNING, "FlatLaf: Usage of '@@' in .properties files is deprecated. Use '$' instead." );
value = value.substring( REF_PREFIX.length() );
}
boolean optional = false;
if( value.startsWith( OPTIONAL_PREFIX ) ) {
value = value.substring( OPTIONAL_PREFIX.length() );
optional = true;
}
String newValue = properties.getProperty( value );
if( newValue == null ) {
if( optional )
return "null";
throw new IllegalArgumentException( "variable or property '" + value + "' not found" );
}
return resolveValue( properties, newValue );
}
private enum ValueType { UNKNOWN, STRING, INTEGER, FLOAT, BORDER, ICON, INSETS, DIMENSION, COLOR, SCALEDINTEGER, INSTANCE, CLASS }
static Object parseValue( String key, String value ) {
return parseValue( key, value, v -> v, Collections.emptyList() );
}
private static Object parseValue( String key, String value, Function<String, String> resolver, List<ClassLoader> addonClassLoaders ) {
value = value.trim();
// null, false, true
switch( value ) {
case "null": return null;
case "false": return false;
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;
// check whether value type is specified in the value
if( value.startsWith( "#" ) )
valueType = ValueType.COLOR;
else if( value.startsWith( "\"" ) && value.endsWith( "\"" ) ) {
valueType = ValueType.STRING;
value = value.substring( 1, value.length() - 1 );
} else if( value.startsWith( TYPE_PREFIX ) ) {
int end = value.indexOf( TYPE_PREFIX_END );
if( end != -1 ) {
try {
String typeStr = value.substring( TYPE_PREFIX.length(), end );
valueType = ValueType.valueOf( typeStr.toUpperCase( Locale.ENGLISH ) );
// remove type from value
value = value.substring( end + TYPE_PREFIX_END.length() );
} catch( IllegalArgumentException ex ) {
// ignore
}
}
}
// determine value type from key
if( valueType == ValueType.UNKNOWN ) {
if( key.endsWith( "ground" ) || key.endsWith( "Color" ) )
valueType = ValueType.COLOR;
else if( key.endsWith( ".border" ) || key.endsWith( "Border" ) )
valueType = ValueType.BORDER;
else if( key.endsWith( ".icon" ) || key.endsWith( "Icon" ) )
valueType = ValueType.ICON;
else if( key.endsWith( ".margin" ) || key.endsWith( ".padding" ) ||
key.endsWith( "Margins" ) || key.endsWith( "Insets" ) )
valueType = ValueType.INSETS;
else if( key.endsWith( "Size" ) )
valueType = ValueType.DIMENSION;
else if( key.endsWith( "Width" ) || key.endsWith( "Height" ) )
valueType = ValueType.INTEGER;
else if( key.endsWith( "UI" ) )
valueType = ValueType.STRING;
}
// parse value
switch( valueType ) {
case STRING: return value;
case INTEGER: return parseInteger( value, true );
case FLOAT: return parseFloat( value, true );
case BORDER: return parseBorder( value, resolver, addonClassLoaders );
case ICON: return parseInstance( value, addonClassLoaders );
case INSETS: return parseInsets( value );
case DIMENSION: return parseDimension( value );
case COLOR: return parseColorOrFunction( value, resolver, true );
case SCALEDINTEGER: return parseScaledInteger( value );
case INSTANCE: return parseInstance( value, addonClassLoaders );
case CLASS: return parseClass( value, addonClassLoaders );
case UNKNOWN:
default:
// colors
Object color = parseColorOrFunction( value, resolver, false );
if( color != null )
return color;
// integer
Integer integer = parseInteger( value, false );
if( integer != null )
return integer;
// float
Float f = parseFloat( value, false );
if( f != null )
return f;
// string
return value;
}
}
private static Object parseBorder( String value, Function<String, String> resolver, List<ClassLoader> addonClassLoaders ) {
if( value.indexOf( ',' ) >= 0 ) {
// top,left,bottom,right[,lineColor]
List<String> parts = split( value, ',' );
Insets insets = parseInsets( value );
ColorUIResource lineColor = (parts.size() == 5)
? (ColorUIResource) parseColorOrFunction( resolver.apply( parts.get( 4 ) ), resolver, true )
: null;
return (LazyValue) t -> {
return (lineColor != null)
? new FlatLineBorder( insets, lineColor )
: new FlatEmptyBorder( insets );
};
} else
return parseInstance( value, addonClassLoaders );
}
private static Object parseInstance( String value, List<ClassLoader> addonClassLoaders ) {
return (LazyValue) t -> {
try {
return findClass( value, addonClassLoaders ).newInstance();
} catch( InstantiationException | IllegalAccessException | ClassNotFoundException ex ) {
FlatLaf.LOG.log( Level.SEVERE, "FlatLaf: Failed to instantiate '" + value + "'.", ex );
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 ) {
List<String> numbers = split( value, ',' );
try {
return new InsetsUIResource(
Integer.parseInt( numbers.get( 0 ) ),
Integer.parseInt( numbers.get( 1 ) ),
Integer.parseInt( numbers.get( 2 ) ),
Integer.parseInt( numbers.get( 3 ) ) );
} catch( NumberFormatException ex ) {
throw new IllegalArgumentException( "invalid insets '" + value + "'" );
}
}
private static Dimension parseDimension( String value ) {
List<String> numbers = split( value, ',' );
try {
return new DimensionUIResource(
Integer.parseInt( numbers.get( 0 ) ),
Integer.parseInt( numbers.get( 1 ) ) );
} catch( NumberFormatException ex ) {
throw new IllegalArgumentException( "invalid size '" + value + "'" );
}
}
private static Object parseColorOrFunction( String value, Function<String, String> resolver, boolean reportError ) {
if( value.endsWith( ")" ) )
return parseColorFunctions( value, resolver, reportError );
return parseColor( value, reportError );
}
static ColorUIResource parseColor( String value ) {
return parseColor( value, false );
}
private static ColorUIResource parseColor( String value, boolean reportError ) {
try {
int rgba = parseColorRGBA( value );
return ((rgba & 0xff000000) == 0xff000000)
? new ColorUIResource( rgba )
: new ColorUIResource( new Color( rgba, true ) );
} catch( IllegalArgumentException ex ) {
if( reportError )
throw new IllegalArgumentException( "invalid color '" + value + "'" );
// not a color --> ignore
}
return null;
}
/**
* Parses a hex color in {@code #RGB}, {@code #RGBA}, {@code #RRGGBB} or {@code #RRGGBBAA}
* format and returns it as {@code rgba} integer suitable for {@link java.awt.Color},
* which includes alpha component in bits 24-31.
*
* @throws IllegalArgumentException
*/
static int parseColorRGBA( String value ) {
int len = value.length();
if( (len != 4 && len != 5 && len != 7 && len != 9) || value.charAt( 0 ) != '#' )
throw new IllegalArgumentException();
// parse hex
int n = 0;
for( int i = 1; i < len; i++ ) {
char ch = value.charAt( i );
int digit;
if( ch >= '0' && ch <= '9' )
digit = ch - '0';
else if( ch >= 'a' && ch <= 'f' )
digit = ch - 'a' + 10;
else if( ch >= 'A' && ch <= 'F' )
digit = ch - 'A' + 10;
else
throw new IllegalArgumentException();
n = (n << 4) | digit;
}
if( len <= 5 ) {
// double nibbles
int n1 = n & 0xf000;
int n2 = n & 0xf00;
int n3 = n & 0xf0;
int n4 = n & 0xf;
n = (n1 << 16) | (n1 << 12) | (n2 << 12) | (n2 << 8) | (n3 << 8) | (n3 << 4) | (n4 << 4) | n4;
}
return (len == 4 || len == 7)
? (0xff000000 | n) // set alpha to 255
: (((n >> 8) & 0xffffff) | ((n & 0xff) << 24)); // move alpha from lowest to highest byte
}
private static Object parseColorFunctions( String value, Function<String, String> resolver, boolean reportError ) {
int paramsStart = value.indexOf( '(' );
if( paramsStart < 0 ) {
if( reportError )
throw new IllegalArgumentException( "missing opening parenthesis in function '" + value + "'" );
return null;
}
String function = value.substring( 0, paramsStart ).trim();
List<String> params = splitFunctionParams( value.substring( paramsStart + 1, value.length() - 1 ), ',' );
if( params.isEmpty() )
throw new IllegalArgumentException( "missing parameters in function '" + value + "'" );
switch( function ) {
case "rgb": return parseColorRgbOrRgba( false, params );
case "rgba": return parseColorRgbOrRgba( true, params );
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 + "'" );
}
/**
* Syntax: rgb(red,green,blue) or rgba(red,green,blue,alpha)
* - red: an integer 0-255
* - green: an integer 0-255
* - blue: an integer 0-255
* - alpha: an integer 0-255
*/
private static ColorUIResource parseColorRgbOrRgba( boolean hasAlpha, List<String> params ) {
int red = parseInteger( params.get( 0 ), 0, 255 );
int green = parseInteger( params.get( 1 ), 0, 255 );
int blue = parseInteger( params.get( 2 ), 0, 255 );
int alpha = hasAlpha ? parseInteger( params.get( 3 ), 0, 255 ) : 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 );
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]
*/
private static Object parseColorLightenOrDarken( boolean lighten, List<String> params,
Function<String, String> resolver, boolean reportError )
{
boolean isDerived = params.get( 0 ).endsWith( "%" );
String colorStr = isDerived ? null : params.get( 0 );
int nextParam = isDerived ? 0 : 1;
int amount = parsePercentage( params.get( nextParam++ ) );
boolean relative = false;
boolean autoInverse = false;
boolean lazy = false;
if( params.size() > nextParam ) {
String options = params.get( nextParam++ );
relative = options.contains( "relative" );
autoInverse = options.contains( "autoInverse" );
lazy = options.contains( "lazy" );
}
ColorFunctions.ColorFunction function = lighten
? new ColorFunctions.Lighten( amount, relative, autoInverse )
: new ColorFunctions.Darken( amount, relative, autoInverse );
if( isDerived )
return new DerivedColor( 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 ) {
if( !value.endsWith( "%" ) )
throw new NumberFormatException( "invalid percentage '" + value + "'" );
int val;
try {
val = Integer.parseInt( value.substring( 0, value.length() - 1 ) );
} catch( NumberFormatException ex ) {
throw new NumberFormatException( "invalid percentage '" + value + "'" );
}
if( val < 0 || val > 100 )
throw new IllegalArgumentException( "percentage out of range (0-100%) '" + value + "'" );
return val;
}
private static Integer parseInteger( String value, int min, int max ) {
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 ) {
try {
return Integer.parseInt( value );
} catch( NumberFormatException ex ) {
if( reportError )
throw new NumberFormatException( "invalid integer '" + value + "'" );
}
return null;
}
private static Float parseFloat( String value, boolean reportError ) {
try {
return Float.parseFloat( value );
} catch( NumberFormatException ex ) {
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 );
};
}
/**
* 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

@@ -16,15 +16,18 @@
package com.formdev.flatlaf.icons;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.geom.Path2D;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
* "ascendingSort" icon for {@link javax.swing.table.JTableHeader}.
*
* @uiDefault Component.arrowType String triangle (default) or chevron
* @uiDefault Table.sortIconColor Color
*
* @author Karl Tauber
@@ -32,6 +35,7 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
public class FlatAscendingSortIcon
extends FlatAbstractIcon
{
protected final boolean chevron = "chevron".equals( UIManager.getString( "Component.arrowType" ) );
protected final Color sortIconColor = UIManager.getColor( "Table.sortIconColor" );
public FlatAscendingSortIcon() {
@@ -41,6 +45,14 @@ public class FlatAscendingSortIcon
@Override
protected void paintIcon( Component c, Graphics2D g ) {
g.setColor( sortIconColor );
g.fill( FlatUIUtils.createPath( 0.5,5, 9.5,5, 5,0 ) );
if( chevron ) {
// chevron arrow
Path2D path = FlatUIUtils.createPath( false, 1,5, 5,1, 9,5 );
g.setStroke( new BasicStroke( 1f ) );
g.draw( path );
} else {
// triangle arrow
g.fill( FlatUIUtils.createPath( 0.5,5, 5,0, 9.5,5 ) );
}
}
}

View File

@@ -16,14 +16,18 @@
package com.formdev.flatlaf.icons;
import static com.formdev.flatlaf.FlatClientProperties.*;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.geom.Path2D;
import java.awt.geom.RoundRectangle2D;
import javax.swing.AbstractButton;
import javax.swing.JComponent;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatButtonUI;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
* Icon for {@link javax.swing.JCheckBox}.
@@ -34,6 +38,7 @@ import com.formdev.flatlaf.ui.FlatButtonUI;
*
* @uiDefault Component.focusWidth int
* @uiDefault Component.focusColor Color
* @uiDefault CheckBox.icon.focusedColor Color optional; defaults to Component.focusColor
* @uiDefault CheckBox.icon.borderColor Color
* @uiDefault CheckBox.icon.disabledBorderColor Color
* @uiDefault CheckBox.icon.selectedBorderColor Color
@@ -50,14 +55,17 @@ import com.formdev.flatlaf.ui.FlatButtonUI;
* @uiDefault CheckBox.icon.selectedPressedBackground Color optional
* @uiDefault CheckBox.icon.checkmarkColor Color
* @uiDefault CheckBox.icon.disabledCheckmarkColor Color
* @uiDefault CheckBox.arc int
*
* @author Karl Tauber
*/
public class FlatCheckBoxIcon
extends FlatAbstractIcon
{
protected final int focusWidth = UIManager.getInt( "Component.focusWidth" );
protected final Color focusColor = UIManager.getColor( "Component.focusColor" );
public final int focusWidth = UIManager.getInt( "Component.focusWidth" );
protected final Color focusColor = FlatUIUtils.getUIColor( "CheckBox.icon.focusedColor",
UIManager.getColor( "Component.focusColor" ) );
protected final int arc = FlatUIUtils.getUIInt( "CheckBox.arc", 2 );
protected final Color borderColor = UIManager.getColor( "CheckBox.icon.borderColor" );
protected final Color disabledBorderColor = UIManager.getColor( "CheckBox.icon.disabledBorderColor" );
@@ -84,7 +92,8 @@ public class FlatCheckBoxIcon
@Override
protected void paintIcon( Component c, Graphics2D g2 ) {
boolean selected = (c instanceof AbstractButton) ? ((AbstractButton)c).isSelected() : false;
boolean indeterminate = c instanceof JComponent && clientPropertyEquals( (JComponent) c, SELECTED_STATE, SELECTED_STATE_INDETERMINATE );
boolean selected = indeterminate || (c instanceof AbstractButton && ((AbstractButton)c).isSelected());
// paint focused border
if( c.hasFocus() && focusWidth > 0 ) {
@@ -102,33 +111,40 @@ public class FlatCheckBoxIcon
paintBorder( g2 );
// paint background
g2.setColor( FlatButtonUI.buttonStateColor( c,
FlatUIUtils.setColor( g2, FlatButtonUI.buttonStateColor( c,
selected ? selectedBackground : background,
disabledBackground,
focusedBackground,
selected && selectedHoverBackground != null ? selectedHoverBackground : hoverBackground,
selected && selectedPressedBackground != null ? selectedPressedBackground : pressedBackground ) );
selected && selectedPressedBackground != null ? selectedPressedBackground : pressedBackground ),
background );
paintBackground( g2 );
// paint checkmark
if( selected ) {
if( selected || indeterminate ) {
g2.setColor( c.isEnabled() ? checkmarkColor : disabledCheckmarkColor );
paintCheckmark( g2 );
if( indeterminate )
paintIndeterminate( g2 );
else
paintCheckmark( g2 );
}
}
protected void paintFocusBorder( Graphics2D g2 ) {
// the outline focus border is painted outside of the icon
int wh = ICON_SIZE - 1 + (focusWidth * 2);
g2.fillRoundRect( -focusWidth + 1, -focusWidth, wh, wh, 8, 8 );
int arcwh = arc + (focusWidth * 2);
g2.fillRoundRect( -focusWidth + 1, -focusWidth, wh, wh, arcwh, arcwh );
}
protected void paintBorder( Graphics2D g2 ) {
g2.fillRoundRect( 1, 0, 14, 14, 4, 4 );
int arcwh = arc;
g2.fillRoundRect( 1, 0, 14, 14, arcwh, arcwh );
}
protected void paintBackground( Graphics2D g2 ) {
g2.fillRoundRect( 2, 1, 12, 12, 3, 3 );
int arcwh = arc - 1;
g2.fillRoundRect( 2, 1, 12, 12, arcwh, arcwh );
}
protected void paintCheckmark( Graphics2D g2 ) {
@@ -140,4 +156,8 @@ public class FlatCheckBoxIcon
g2.setStroke( new BasicStroke( 1.9f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND ) );
g2.draw( path );
}
protected void paintIndeterminate( Graphics2D g2 ) {
g2.fill( new RoundRectangle2D.Float( 3.75f, 5.75f, 8.5f, 2.5f, 2f, 2f ) );
}
}

View File

@@ -16,15 +16,18 @@
package com.formdev.flatlaf.icons;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.geom.Path2D;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
* "descendingSort" icon for {@link javax.swing.table.JTableHeader}.
*
* @uiDefault Component.arrowType String triangle (default) or chevron
* @uiDefault Table.sortIconColor Color
*
* @author Karl Tauber
@@ -32,6 +35,7 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
public class FlatDescendingSortIcon
extends FlatAbstractIcon
{
protected final boolean chevron = "chevron".equals( UIManager.getString( "Component.arrowType" ) );
protected final Color sortIconColor = UIManager.getColor( "Table.sortIconColor" );
public FlatDescendingSortIcon() {
@@ -41,6 +45,14 @@ public class FlatDescendingSortIcon
@Override
protected void paintIcon( Component c, Graphics2D g ) {
g.setColor( sortIconColor );
g.fill( FlatUIUtils.createPath( 0.5,0, 9.5,0, 5,5 ) );
if( chevron ) {
// chevron arrow
Path2D path = FlatUIUtils.createPath( false, 1,1, 5,5, 9,1 );
g.setStroke( new BasicStroke( 1f ) );
g.draw( path );
} else {
// triangle arrow
g.fill( FlatUIUtils.createPath( 0.5,0, 5,5, 9.5,0 ) );
}
}
}

View File

@@ -23,7 +23,7 @@ import javax.swing.UIManager;
/**
* "details view" icon for {@link javax.swing.JFileChooser}.
*
* @uiDefault FileChooser.icon.detailsViewColor Color
* @uiDefault Actions.Grey Color
*
* @author Karl Tauber
*/
@@ -31,7 +31,7 @@ public class FlatFileChooserDetailsViewIcon
extends FlatAbstractIcon
{
public FlatFileChooserDetailsViewIcon() {
super( 16, 16, UIManager.getColor( "FileChooser.icon.detailsViewColor" ) );
super( 16, 16, UIManager.getColor( "Actions.Grey" ) );
}
@Override

View File

@@ -24,7 +24,7 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
/**
* "home folder" icon for {@link javax.swing.JFileChooser}.
*
* @uiDefault FileChooser.icon.homeFolderColor Color
* @uiDefault Actions.Grey Color
*
* @author Karl Tauber
*/
@@ -32,7 +32,7 @@ public class FlatFileChooserHomeFolderIcon
extends FlatAbstractIcon
{
public FlatFileChooserHomeFolderIcon() {
super( 16, 16, UIManager.getColor( "FileChooser.icon.homeFolderColor" ) );
super( 16, 16, UIManager.getColor( "Actions.Grey" ) );
}
@Override

View File

@@ -23,7 +23,7 @@ import javax.swing.UIManager;
/**
* "list view" icon for {@link javax.swing.JFileChooser}.
*
* @uiDefault FileChooser.icon.listViewColor Color
* @uiDefault Actions.Grey Color
*
* @author Karl Tauber
*/
@@ -31,7 +31,7 @@ public class FlatFileChooserListViewIcon
extends FlatAbstractIcon
{
public FlatFileChooserListViewIcon() {
super( 16, 16, UIManager.getColor( "FileChooser.icon.listViewColor" ) );
super( 16, 16, UIManager.getColor( "Actions.Grey" ) );
}
@Override

View File

@@ -24,7 +24,7 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
/**
* "new folder" icon for {@link javax.swing.JFileChooser}.
*
* @uiDefault FileChooser.icon.newFolderColor Color
* @uiDefault Actions.Grey Color
*
* @author Karl Tauber
*/
@@ -32,7 +32,7 @@ public class FlatFileChooserNewFolderIcon
extends FlatAbstractIcon
{
public FlatFileChooserNewFolderIcon() {
super( 16, 16, UIManager.getColor( "FileChooser.icon.newFolderColor" ) );
super( 16, 16, UIManager.getColor( "Actions.Grey" ) );
}
@Override

View File

@@ -16,6 +16,7 @@
package com.formdev.flatlaf.icons;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics2D;
import javax.swing.UIManager;
@@ -24,15 +25,18 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
/**
* "up folder" icon for {@link javax.swing.JFileChooser}.
*
* @uiDefault FileChooser.icon.upFolderColor Color
* @uiDefault Actions.Grey Color
* @uiDefault Actions.Blue Color
*
* @author Karl Tauber
*/
public class FlatFileChooserUpFolderIcon
extends FlatAbstractIcon
{
private final Color blueColor = UIManager.getColor( "Actions.Blue" );
public FlatFileChooserUpFolderIcon() {
super( 16, 16, UIManager.getColor( "FileChooser.icon.upFolderColor" ) );
super( 16, 16, UIManager.getColor( "Actions.Grey" ) );
}
@Override
@@ -47,6 +51,8 @@ public class FlatFileChooserUpFolderIcon
*/
g.fill( FlatUIUtils.createPath( 2,3, 5.5,3, 7,5, 9,5, 9,9, 13,9, 13,5, 14,5, 14,13, 2,13 ) );
g.setColor( blueColor );
g.fill( FlatUIUtils.createPath( 12,4, 12,8, 10,8, 10,4, 8,4, 11,1, 14,4, 12,4 ) );
}
}

View File

@@ -25,7 +25,7 @@ import javax.swing.UIManager;
/**
* "computer" icon for {@link javax.swing.JFileChooser}.
*
* @uiDefault FileView.icon.computerColor Color
* @uiDefault Objects.Grey Color
*
* @author Karl Tauber
*/
@@ -33,7 +33,7 @@ public class FlatFileViewComputerIcon
extends FlatAbstractIcon
{
public FlatFileViewComputerIcon() {
super( 16, 16, UIManager.getColor( "FileView.icon.computerColor" ) );
super( 16, 16, UIManager.getColor( "Objects.Grey" ) );
}
@Override

View File

@@ -24,7 +24,7 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
/**
* "directory" icon for {@link javax.swing.JFileChooser}.
*
* @uiDefault FileView.icon.directoryColor Color
* @uiDefault Objects.Grey Color
*
* @author Karl Tauber
*/
@@ -32,7 +32,7 @@ public class FlatFileViewDirectoryIcon
extends FlatAbstractIcon
{
public FlatFileViewDirectoryIcon() {
super( 16, 16, UIManager.getColor( "FileView.icon.directoryColor" ) );
super( 16, 16, UIManager.getColor( "Objects.Grey" ) );
}
@Override

View File

@@ -24,7 +24,7 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
/**
* "file" icon for {@link javax.swing.JFileChooser}.
*
* @uiDefault FileView.icon.fileColor Color
* @uiDefault Objects.Grey Color
*
* @author Karl Tauber
*/
@@ -32,7 +32,7 @@ public class FlatFileViewFileIcon
extends FlatAbstractIcon
{
public FlatFileViewFileIcon() {
super( 16, 16, UIManager.getColor( "FileView.icon.fileColor" ) );
super( 16, 16, UIManager.getColor( "Objects.Grey" ) );
}
@Override

View File

@@ -25,7 +25,7 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
/**
* "floppy drive" icon for {@link javax.swing.JFileChooser}.
*
* @uiDefault FileView.icon.floppyDriveColor Color
* @uiDefault Objects.Grey Color
*
* @author Karl Tauber
*/
@@ -33,7 +33,7 @@ public class FlatFileViewFloppyDriveIcon
extends FlatAbstractIcon
{
public FlatFileViewFloppyDriveIcon() {
super( 16, 16, UIManager.getColor( "FileView.icon.floppyDriveColor" ) );
super( 16, 16, UIManager.getColor( "Objects.Grey" ) );
}
@Override

View File

@@ -25,7 +25,7 @@ import javax.swing.UIManager;
/**
* "hard drive" icon for {@link javax.swing.JFileChooser}.
*
* @uiDefault FileView.icon.hardDriveColor Color
* @uiDefault Objects.Grey Color
*
* @author Karl Tauber
*/
@@ -33,7 +33,7 @@ public class FlatFileViewHardDriveIcon
extends FlatAbstractIcon
{
public FlatFileViewHardDriveIcon() {
super( 16, 16, UIManager.getColor( "FileView.icon.hardDriveColor" ) );
super( 16, 16, UIManager.getColor( "Objects.Grey" ) );
}
@Override

View File

@@ -24,6 +24,7 @@ import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatButtonUI;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
* Help button icon for {@link javax.swing.JButton}.
@@ -99,12 +100,12 @@ public class FlatHelpButtonIcon
g2.fill( new Ellipse2D.Float( focusWidth + 0.5f, focusWidth + 0.5f, 21, 21 ) );
// paint background
g2.setColor( FlatButtonUI.buttonStateColor( c,
FlatUIUtils.setColor( g2, FlatButtonUI.buttonStateColor( c,
background,
disabledBackground,
focusedBackground,
hoverBackground,
pressedBackground ) );
pressedBackground ), background );
g2.fill( new Ellipse2D.Float( focusWidth + 1.5f, focusWidth + 1.5f, 19, 19 ) );
// paint question mark

View File

@@ -0,0 +1,52 @@
/*
* 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.Component;
import java.awt.Graphics2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import javax.swing.UIManager;
/**
* "close" icon for {@link javax.swing.JInternalFrame}.
*
* @uiDefault InternalFrame.iconColor Color
*
* @author Karl Tauber
*/
public class FlatInternalFrameCloseIcon
extends FlatAbstractIcon
{
public FlatInternalFrameCloseIcon() {
super( 16, 16, UIManager.getColor( "InternalFrame.iconColor" ) );
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
float mx = 8;
float my = 8;
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,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 javax.swing.UIManager;
/**
* "iconify" icon for {@link javax.swing.JInternalFrame}.
*
* @uiDefault InternalFrame.iconColor Color
*
* @author Karl Tauber
*/
public class FlatInternalFrameIconifyIcon
extends FlatAbstractIcon
{
public FlatInternalFrameIconifyIcon() {
super( 16, 16, UIManager.getColor( "InternalFrame.iconColor" ) );
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
g.fillRect( 3, 8, 10, 1 );
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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 javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
* "maximize" icon for {@link javax.swing.JInternalFrame}.
*
* @uiDefault InternalFrame.iconColor Color
*
* @author Karl Tauber
*/
public class FlatInternalFrameMaximizeIcon
extends FlatAbstractIcon
{
public FlatInternalFrameMaximizeIcon() {
super( 16, 16, UIManager.getColor( "InternalFrame.iconColor" ) );
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
g.fill( FlatUIUtils.createRectangle( 3, 3, 10, 10, 1 ) );
}
}

View File

@@ -0,0 +1,52 @@
/*
* 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 javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
* "minimize" (actually "restore") icon for {@link javax.swing.JInternalFrame}.
*
* @uiDefault InternalFrame.iconColor Color
*
* @author Karl Tauber
*/
public class FlatInternalFrameMinimizeIcon
extends FlatAbstractIcon
{
public FlatInternalFrameMinimizeIcon() {
super( 16, 16, UIManager.getColor( "InternalFrame.iconColor" ) );
}
@Override
protected void paintIcon( Component c, Graphics2D g ) {
Path2D r1 = FlatUIUtils.createRectangle( 5, 3, 8, 8, 1 );
Path2D r2 = FlatUIUtils.createRectangle( 3, 5, 8, 8, 1 );
Area area = new Area( r1 );
area.subtract( new Area( new Rectangle2D.Float( 3, 5, 8, 8 ) ) );
g.fill( area );
g.fill( r2 );
}
}

View File

@@ -60,7 +60,7 @@ public class FlatMenuArrowIcon
g.draw( path );
} else {
// triangle arrow
g.fill( FlatUIUtils.createPath( 0,0.5, 0,9.5, 5,5 ) );
g.fill( FlatUIUtils.createPath( 0,0.5, 5,5, 0,9.5 ) );
}
}

View File

@@ -22,11 +22,12 @@ import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.Path2D;
import javax.swing.UIManager;
import com.formdev.flatlaf.ui.FlatUIUtils;
/**
* Base class for icons for {@link javax.swing.JOptionPane}.
*
* @uiDefault OptionPane.icon.foreground Color
* @uiDefault OptionPane.icon.foreground Color default is transparent
*
* @author Karl Tauber
*/
@@ -35,8 +36,8 @@ public abstract class FlatOptionPaneAbstractIcon
{
protected final Color foreground = UIManager.getColor( "OptionPane.icon.foreground" );
protected FlatOptionPaneAbstractIcon( String colorKey ) {
super( 32, 32, UIManager.getColor( colorKey ) );
protected FlatOptionPaneAbstractIcon( String colorKey, String defaultColorKey ) {
super( 32, 32, FlatUIUtils.getUIColor( colorKey, defaultColorKey ) );
}
@Override

View File

@@ -24,7 +24,8 @@ import java.awt.geom.Rectangle2D;
/**
* "Error" icon for {@link javax.swing.JOptionPane}.
*
* @uiDefault OptionPane.icon.errorColor Color
* @uiDefault OptionPane.icon.errorColor Color optional; defaults to Actions.Red
* @uiDefault Actions.Red Color
*
* @author Karl Tauber
*/
@@ -32,7 +33,7 @@ public class FlatOptionPaneErrorIcon
extends FlatOptionPaneAbstractIcon
{
public FlatOptionPaneErrorIcon() {
super( "OptionPane.icon.errorColor" );
super( "OptionPane.icon.errorColor", "Actions.Red" );
}
/*

View File

@@ -24,7 +24,8 @@ import java.awt.geom.Rectangle2D;
/**
* "Information" icon for {@link javax.swing.JOptionPane}.
*
* @uiDefault OptionPane.icon.informationColor Color
* @uiDefault OptionPane.icon.informationColor Color optional; defaults to Actions.Blue
* @uiDefault Actions.Blue Color
*
* @author Karl Tauber
*/
@@ -32,7 +33,7 @@ public class FlatOptionPaneInformationIcon
extends FlatOptionPaneAbstractIcon
{
public FlatOptionPaneInformationIcon() {
super( "OptionPane.icon.informationColor" );
super( "OptionPane.icon.informationColor", "Actions.Blue" );
}
/*

View File

@@ -24,7 +24,8 @@ import java.awt.geom.Rectangle2D;
/**
* "Question" icon for {@link javax.swing.JOptionPane}.
*
* @uiDefault OptionPane.icon.questionColor Color
* @uiDefault OptionPane.icon.questionColor Color optional; defaults to Actions.Blue
* @uiDefault Actions.Blue Color
*
* @author Karl Tauber
*/
@@ -32,7 +33,7 @@ public class FlatOptionPaneQuestionIcon
extends FlatOptionPaneAbstractIcon
{
public FlatOptionPaneQuestionIcon() {
super( "OptionPane.icon.questionColor" );
super( "OptionPane.icon.questionColor", "Actions.Blue" );
}
/*

View File

@@ -24,7 +24,8 @@ import com.formdev.flatlaf.ui.FlatUIUtils;
/**
* "Warning" icon for {@link javax.swing.JOptionPane}.
*
* @uiDefault OptionPane.icon.warningColor Color
* @uiDefault OptionPane.icon.warningColor Color optional; defaults to Actions.Yellow
* @uiDefault Actions.Yellow Color
*
* @author Karl Tauber
*/
@@ -32,7 +33,7 @@ public class FlatOptionPaneWarningIcon
extends FlatOptionPaneAbstractIcon
{
public FlatOptionPaneWarningIcon() {
super( "OptionPane.icon.warningColor" );
super( "OptionPane.icon.warningColor", "Actions.Yellow" );
}
/*

View File

@@ -0,0 +1,100 @@
/*
* 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.json;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @author Karl Tauber
*/
public class Json
{
public static Object parse( Reader reader )
throws IOException, ParseException
{
DefaultHandler handler = new DefaultHandler();
new JsonParser( handler ).parse( reader );
return handler.getValue();
}
//---- class DefaultHandler -----------------------------------------------
static class DefaultHandler
extends JsonHandler<List<Object>, Map<String, Object>>
{
private Object value;
@Override
public List<Object> startArray() {
return new ArrayList<>();
}
@Override
public Map<String, Object> startObject() {
return new LinkedHashMap<>();
}
@Override
public void endNull() {
value = "null";
}
@Override
public void endBoolean( boolean bool ) {
value = bool ? "true" : "false";
}
@Override
public void endString( String string ) {
value = string;
}
@Override
public void endNumber( String string ) {
value = string;
}
@Override
public void endArray( List<Object> array ) {
value = array;
}
@Override
public void endObject( Map<String, Object> object ) {
value = object;
}
@Override
public void endArrayValue( List<Object> array ) {
array.add( value );
}
@Override
public void endObjectValue( Map<String, Object> object, String name ) {
object.put( name, value );
}
Object getValue() {
return value;
}
}
}

View File

@@ -0,0 +1,266 @@
/*******************************************************************************
* Copyright (c) 2016 EclipseSource.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
******************************************************************************/
// from https://github.com/ralfstx/minimal-json
package com.formdev.flatlaf.json;
/**
* A handler for parser events. Instances of this class can be given to a {@link JsonParser}. The
* parser will then call the methods of the given handler while reading the input.
* <p>
* The default implementations of these methods do nothing. Subclasses may override only those
* methods they are interested in. They can use <code>getLocation()</code> to access the current
* character position of the parser at any point. The <code>start*</code> methods will be called
* while the location points to the first character of the parsed element. The <code>end*</code>
* methods will be called while the location points to the character position that directly follows
* the last character of the parsed element. Example:
* </p>
*
* <pre>
* ["lorem ipsum"]
* ^ ^
* startString endString
* </pre>
* <p>
* Subclasses that build an object representation of the parsed JSON can return arbitrary handler
* objects for JSON arrays and JSON objects in {@link #startArray()} and {@link #startObject()}.
* These handler objects will then be provided in all subsequent parser events for this particular
* array or object. They can be used to keep track the elements of a JSON array or object.
* </p>
*
* @param <A>
* The type of handlers used for JSON arrays
* @param <O>
* The type of handlers used for JSON objects
* @see JsonParser
*/
abstract class JsonHandler<A, O> {
JsonParser parser;
/**
* Returns the current parser location.
*
* @return the current parser location
*/
protected Location getLocation() {
return parser.getLocation();
}
/**
* Indicates the beginning of a <code>null</code> literal in the JSON input. This method will be
* called when reading the first character of the literal.
*/
public void startNull() {
}
/**
* Indicates the end of a <code>null</code> literal in the JSON input. This method will be called
* after reading the last character of the literal.
*/
public void endNull() {
}
/**
* Indicates the beginning of a boolean literal (<code>true</code> or <code>false</code>) in the
* JSON input. This method will be called when reading the first character of the literal.
*/
public void startBoolean() {
}
/**
* Indicates the end of a boolean literal (<code>true</code> or <code>false</code>) in the JSON
* input. This method will be called after reading the last character of the literal.
*
* @param value
* the parsed boolean value
*/
public void endBoolean(boolean value) {
}
/**
* Indicates the beginning of a string in the JSON input. This method will be called when reading
* the opening double quote character (<code>'&quot;'</code>).
*/
public void startString() {
}
/**
* Indicates the end of a string in the JSON input. This method will be called after reading the
* closing double quote character (<code>'&quot;'</code>).
*
* @param string
* the parsed string
*/
public void endString(String string) {
}
/**
* Indicates the beginning of a number in the JSON input. This method will be called when reading
* the first character of the number.
*/
public void startNumber() {
}
/**
* Indicates the end of a number in the JSON input. This method will be called after reading the
* last character of the number.
*
* @param string
* the parsed number string
*/
public void endNumber(String string) {
}
/**
* Indicates the beginning of an array in the JSON input. This method will be called when reading
* the opening square bracket character (<code>'['</code>).
* <p>
* This method may return an object to handle subsequent parser events for this array. This array
* handler will then be provided in all calls to {@link #startArrayValue(Object)
* startArrayValue()}, {@link #endArrayValue(Object) endArrayValue()}, and
* {@link #endArray(Object) endArray()} for this array.
* </p>
*
* @return a handler for this array, or <code>null</code> if not needed
*/
public A startArray() {
return null;
}
/**
* Indicates the end of an array in the JSON input. This method will be called after reading the
* closing square bracket character (<code>']'</code>).
*
* @param array
* the array handler returned from {@link #startArray()}, or <code>null</code> if not
* provided
*/
public void endArray(A array) {
}
/**
* Indicates the beginning of an array element in the JSON input. This method will be called when
* reading the first character of the element, just before the call to the <code>start</code>
* method for the specific element type ({@link #startString()}, {@link #startNumber()}, etc.).
*
* @param array
* the array handler returned from {@link #startArray()}, or <code>null</code> if not
* provided
*/
public void startArrayValue(A array) {
}
/**
* Indicates the end of an array element in the JSON input. This method will be called after
* reading the last character of the element value, just after the <code>end</code> method for the
* specific element type (like {@link #endString(String) endString()}, {@link #endNumber(String)
* endNumber()}, etc.).
*
* @param array
* the array handler returned from {@link #startArray()}, or <code>null</code> if not
* provided
*/
public void endArrayValue(A array) {
}
/**
* Indicates the beginning of an object in the JSON input. This method will be called when reading
* the opening curly bracket character (<code>'{'</code>).
* <p>
* This method may return an object to handle subsequent parser events for this object. This
* object handler will be provided in all calls to {@link #startObjectName(Object)
* startObjectName()}, {@link #endObjectName(Object, String) endObjectName()},
* {@link #startObjectValue(Object, String) startObjectValue()},
* {@link #endObjectValue(Object, String) endObjectValue()}, and {@link #endObject(Object)
* endObject()} for this object.
* </p>
*
* @return a handler for this object, or <code>null</code> if not needed
*/
public O startObject() {
return null;
}
/**
* Indicates the end of an object in the JSON input. This method will be called after reading the
* closing curly bracket character (<code>'}'</code>).
*
* @param object
* the object handler returned from {@link #startObject()}, or null if not provided
*/
public void endObject(O object) {
}
/**
* Indicates the beginning of the name of an object member in the JSON input. This method will be
* called when reading the opening quote character ('&quot;') of the member name.
*
* @param object
* the object handler returned from {@link #startObject()}, or <code>null</code> if not
* provided
*/
public void startObjectName(O object) {
}
/**
* Indicates the end of an object member name in the JSON input. This method will be called after
* reading the closing quote character (<code>'"'</code>) of the member name.
*
* @param object
* the object handler returned from {@link #startObject()}, or null if not provided
* @param name
* the parsed member name
*/
public void endObjectName(O object, String name) {
}
/**
* Indicates the beginning of the name of an object member in the JSON input. This method will be
* called when reading the opening quote character ('&quot;') of the member name.
*
* @param object
* the object handler returned from {@link #startObject()}, or <code>null</code> if not
* provided
* @param name
* the member name
*/
public void startObjectValue(O object, String name) {
}
/**
* Indicates the end of an object member value in the JSON input. This method will be called after
* reading the last character of the member value, just after the <code>end</code> method for the
* specific member type (like {@link #endString(String) endString()}, {@link #endNumber(String)
* endNumber()}, etc.).
*
* @param object
* the object handler returned from {@link #startObject()}, or null if not provided
* @param name
* the parsed member name
*/
public void endObjectValue(O object, String name) {
}
}

View File

@@ -0,0 +1,514 @@
/*******************************************************************************
* Copyright (c) 2013, 2016 EclipseSource.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
******************************************************************************/
// from https://github.com/ralfstx/minimal-json
package com.formdev.flatlaf.json;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
/**
* A streaming parser for JSON text. The parser reports all events to a given handler.
*/
class JsonParser {
private static final int MAX_NESTING_LEVEL = 1000;
private static final int MIN_BUFFER_SIZE = 10;
private static final int DEFAULT_BUFFER_SIZE = 1024;
private final JsonHandler<Object, Object> handler;
private Reader reader;
private char[] buffer;
private int bufferOffset;
private int index;
private int fill;
private int line;
private int lineOffset;
private int current;
private StringBuilder captureBuffer;
private int captureStart;
private int nestingLevel;
/*
* | bufferOffset
* v
* [a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t] < input
* [l|m|n|o|p|q|r|s|t|?|?] < buffer
* ^ ^
* | index fill
*/
/**
* Creates a new JsonParser with the given handler. The parser will report all parser events to
* this handler.
*
* @param handler
* the handler to process parser events
*/
@SuppressWarnings("unchecked")
public JsonParser(JsonHandler<?, ?> handler) {
if (handler == null) {
throw new NullPointerException("handler is null");
}
this.handler = (JsonHandler<Object, Object>)handler;
handler.parser = this;
}
/**
* Parses the given input string. The input must contain a valid JSON value, optionally padded
* with whitespace.
*
* @param string
* the input string, must be valid JSON
* @throws ParseException
* if the input is not valid JSON
*/
public void parse(String string) {
if (string == null) {
throw new NullPointerException("string is null");
}
int bufferSize = Math.max(MIN_BUFFER_SIZE, Math.min(DEFAULT_BUFFER_SIZE, string.length()));
try {
parse(new StringReader(string), bufferSize);
} catch (IOException exception) {
// StringReader does not throw IOException
throw new RuntimeException(exception);
}
}
/**
* Reads the entire input from the given reader and parses it as JSON. The input must contain a
* valid JSON value, optionally padded with whitespace.
* <p>
* Characters are read in chunks into a default-sized input buffer. Hence, wrapping a reader in an
* additional <code>BufferedReader</code> likely won't improve reading performance.
* </p>
*
* @param reader
* the reader to read the input from
* @throws IOException
* if an I/O error occurs in the reader
* @throws ParseException
* if the input is not valid JSON
*/
public void parse(Reader reader) throws IOException {
parse(reader, DEFAULT_BUFFER_SIZE);
}
/**
* Reads the entire input from the given reader and parses it as JSON. The input must contain a
* valid JSON value, optionally padded with whitespace.
* <p>
* Characters are read in chunks into an input buffer of the given size. Hence, wrapping a reader
* in an additional <code>BufferedReader</code> likely won't improve reading performance.
* </p>
*
* @param reader
* the reader to read the input from
* @param buffersize
* the size of the input buffer in chars
* @throws IOException
* if an I/O error occurs in the reader
* @throws ParseException
* if the input is not valid JSON
*/
public void parse(Reader reader, int buffersize) throws IOException {
if (reader == null) {
throw new NullPointerException("reader is null");
}
if (buffersize <= 0) {
throw new IllegalArgumentException("buffersize is zero or negative");
}
this.reader = reader;
buffer = new char[buffersize];
bufferOffset = 0;
index = 0;
fill = 0;
line = 1;
lineOffset = 0;
current = 0;
captureStart = -1;
read();
skipWhiteSpace();
readValue();
skipWhiteSpace();
if (!isEndOfText()) {
throw error("Unexpected character");
}
}
private void readValue() throws IOException {
switch (current) {
case 'n':
readNull();
break;
case 't':
readTrue();
break;
case 'f':
readFalse();
break;
case '"':
readString();
break;
case '[':
readArray();
break;
case '{':
readObject();
break;
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
readNumber();
break;
default:
throw expected("value");
}
}
private void readArray() throws IOException {
Object array = handler.startArray();
read();
if (++nestingLevel > MAX_NESTING_LEVEL) {
throw error("Nesting too deep");
}
skipWhiteSpace();
if (readChar(']')) {
nestingLevel--;
handler.endArray(array);
return;
}
do {
skipWhiteSpace();
handler.startArrayValue(array);
readValue();
handler.endArrayValue(array);
skipWhiteSpace();
} while (readChar(','));
if (!readChar(']')) {
throw expected("',' or ']'");
}
nestingLevel--;
handler.endArray(array);
}
private void readObject() throws IOException {
Object object = handler.startObject();
read();
if (++nestingLevel > MAX_NESTING_LEVEL) {
throw error("Nesting too deep");
}
skipWhiteSpace();
if (readChar('}')) {
nestingLevel--;
handler.endObject(object);
return;
}
do {
skipWhiteSpace();
handler.startObjectName(object);
String name = readName();
handler.endObjectName(object, name);
skipWhiteSpace();
if (!readChar(':')) {
throw expected("':'");
}
skipWhiteSpace();
handler.startObjectValue(object, name);
readValue();
handler.endObjectValue(object, name);
skipWhiteSpace();
} while (readChar(','));
if (!readChar('}')) {
throw expected("',' or '}'");
}
nestingLevel--;
handler.endObject(object);
}
private String readName() throws IOException {
if (current != '"') {
throw expected("name");
}
return readStringInternal();
}
private void readNull() throws IOException {
handler.startNull();
read();
readRequiredChar('u');
readRequiredChar('l');
readRequiredChar('l');
handler.endNull();
}
private void readTrue() throws IOException {
handler.startBoolean();
read();
readRequiredChar('r');
readRequiredChar('u');
readRequiredChar('e');
handler.endBoolean(true);
}
private void readFalse() throws IOException {
handler.startBoolean();
read();
readRequiredChar('a');
readRequiredChar('l');
readRequiredChar('s');
readRequiredChar('e');
handler.endBoolean(false);
}
private void readRequiredChar(char ch) throws IOException {
if (!readChar(ch)) {
throw expected("'" + ch + "'");
}
}
private void readString() throws IOException {
handler.startString();
handler.endString(readStringInternal());
}
private String readStringInternal() throws IOException {
read();
startCapture();
while (current != '"') {
if (current == '\\') {
pauseCapture();
readEscape();
startCapture();
} else if (current < 0x20) {
throw expected("valid string character");
} else {
read();
}
}
String string = endCapture();
read();
return string;
}
private void readEscape() throws IOException {
read();
switch (current) {
case '"':
case '/':
case '\\':
captureBuffer.append((char)current);
break;
case 'b':
captureBuffer.append('\b');
break;
case 'f':
captureBuffer.append('\f');
break;
case 'n':
captureBuffer.append('\n');
break;
case 'r':
captureBuffer.append('\r');
break;
case 't':
captureBuffer.append('\t');
break;
case 'u':
char[] hexChars = new char[4];
for (int i = 0; i < 4; i++) {
read();
if (!isHexDigit()) {
throw expected("hexadecimal digit");
}
hexChars[i] = (char)current;
}
captureBuffer.append((char)Integer.parseInt(new String(hexChars), 16));
break;
default:
throw expected("valid escape sequence");
}
read();
}
private void readNumber() throws IOException {
handler.startNumber();
startCapture();
readChar('-');
int firstDigit = current;
if (!readDigit()) {
throw expected("digit");
}
if (firstDigit != '0') {
while (readDigit()) {
}
}
readFraction();
readExponent();
handler.endNumber(endCapture());
}
private boolean readFraction() throws IOException {
if (!readChar('.')) {
return false;
}
if (!readDigit()) {
throw expected("digit");
}
while (readDigit()) {
}
return true;
}
private boolean readExponent() throws IOException {
if (!readChar('e') && !readChar('E')) {
return false;
}
if (!readChar('+')) {
readChar('-');
}
if (!readDigit()) {
throw expected("digit");
}
while (readDigit()) {
}
return true;
}
private boolean readChar(char ch) throws IOException {
if (current != ch) {
return false;
}
read();
return true;
}
private boolean readDigit() throws IOException {
if (!isDigit()) {
return false;
}
read();
return true;
}
private void skipWhiteSpace() throws IOException {
while (isWhiteSpace()) {
read();
}
}
private void read() throws IOException {
if (index == fill) {
if (captureStart != -1) {
captureBuffer.append(buffer, captureStart, fill - captureStart);
captureStart = 0;
}
bufferOffset += fill;
fill = reader.read(buffer, 0, buffer.length);
index = 0;
if (fill == -1) {
current = -1;
index++;
return;
}
}
if (current == '\n') {
line++;
lineOffset = bufferOffset + index;
}
current = buffer[index++];
}
private void startCapture() {
if (captureBuffer == null) {
captureBuffer = new StringBuilder();
}
captureStart = index - 1;
}
private void pauseCapture() {
int end = current == -1 ? index : index - 1;
captureBuffer.append(buffer, captureStart, end - captureStart);
captureStart = -1;
}
private String endCapture() {
int start = captureStart;
int end = index - 1;
captureStart = -1;
if (captureBuffer.length() > 0) {
captureBuffer.append(buffer, start, end - start);
String captured = captureBuffer.toString();
captureBuffer.setLength(0);
return captured;
}
return new String(buffer, start, end - start);
}
Location getLocation() {
int offset = bufferOffset + index - 1;
int column = offset - lineOffset + 1;
return new Location(offset, line, column);
}
private ParseException expected(String expected) {
if (isEndOfText()) {
return error("Unexpected end of input");
}
return error("Expected " + expected);
}
private ParseException error(String message) {
return new ParseException(message, getLocation());
}
private boolean isWhiteSpace() {
return current == ' ' || current == '\t' || current == '\n' || current == '\r';
}
private boolean isDigit() {
return current >= '0' && current <= '9';
}
private boolean isHexDigit() {
return current >= '0' && current <= '9'
|| current >= 'a' && current <= 'f'
|| current >= 'A' && current <= 'F';
}
private boolean isEndOfText() {
return current == -1;
}
}

View File

@@ -0,0 +1,79 @@
/*******************************************************************************
* Copyright (c) 2016 EclipseSource.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
******************************************************************************/
// from https://github.com/ralfstx/minimal-json
package com.formdev.flatlaf.json;
/**
* An immutable object that represents a location in the parsed text.
*/
public class Location {
/**
* The absolute character index, starting at 0.
*/
public final int offset;
/**
* The line number, starting at 1.
*/
public final int line;
/**
* The column number, starting at 1.
*/
public final int column;
Location(int offset, int line, int column) {
this.offset = offset;
this.column = column;
this.line = line;
}
@Override
public String toString() {
return line + ":" + column;
}
@Override
public int hashCode() {
return offset;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Location other = (Location)obj;
return offset == other.offset && column == other.column && line == other.line;
}
}

View File

@@ -0,0 +1,83 @@
/*******************************************************************************
* Copyright (c) 2013, 2016 EclipseSource.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
******************************************************************************/
// from https://github.com/ralfstx/minimal-json
package com.formdev.flatlaf.json;
/**
* An unchecked exception to indicate that an input does not qualify as valid JSON.
*/
public class ParseException extends RuntimeException {
private final Location location;
ParseException(String message, Location location) {
super(message + " at " + location);
this.location = location;
}
/**
* Returns the location at which the error occurred.
*
* @return the error location
*/
public Location getLocation() {
return location;
}
/**
* Returns the absolute character index at which the error occurred. The offset of the first
* character of a document is 0.
*
* @return the character offset at which the error occurred, will be &gt;= 0
* @deprecated Use {@link #getLocation()} instead
*/
@Deprecated
public int getOffset() {
return location.offset;
}
/**
* Returns the line number in which the error occurred. The number of the first line is 1.
*
* @return the line in which the error occurred, will be &gt;= 1
* @deprecated Use {@link #getLocation()} instead
*/
@Deprecated
public int getLine() {
return location.line;
}
/**
* Returns the column number at which the error occurred, i.e. the number of the character in its
* line. The number of the first character of a line is 1.
*
* @return the column in which the error occurred, will be &gt;= 1
* @deprecated Use {@link #getLocation()} instead
*/
@Deprecated
public int getColumn() {
return location.column;
}
}

View File

@@ -38,12 +38,15 @@ public class FlatArrowButton
extends BasicArrowButton
implements UIResource
{
public static final int DEFAULT_ARROW_WIDTH = 8;
private final boolean chevron;
private final Color foreground;
private final Color disabledForeground;
private final Color hoverForeground;
private final Color hoverBackground;
private int arrowWidth = DEFAULT_ARROW_WIDTH;
private int xOffset = 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() {
return hover;
}
@@ -128,8 +139,8 @@ public class FlatArrowButton
int direction = getDirection();
boolean vert = (direction == NORTH || direction == SOUTH);
int w = scale( chevron ? 8 : 9 );
int h = scale( chevron ? 4 : 5 );
int w = scale( arrowWidth + (chevron ? 0 : 1) );
int h = scale( (arrowWidth / 2) + (chevron ? 0 : 1) );
int rw = vert ? w : h;
int rh = vert ? h : w;
int x = Math.round( (width - rw) / 2f + scale( (float) xOffset ) );
@@ -157,7 +168,7 @@ public class FlatArrowButton
g.translate( -x, -y );
}
public static Shape createArrowShape( int direction, boolean chevron, int w, int h ) {
public static Shape createArrowShape( int direction, boolean chevron, float w, float h ) {
switch( direction ) {
case NORTH: return FlatUIUtils.createPath( !chevron, 0,h, (w / 2f),0, w,h );
case SOUTH: return FlatUIUtils.createPath( !chevron, 0,0, (w / 2f),h, w,0 );

View File

@@ -48,7 +48,7 @@ import javax.swing.text.JTextComponent;
* {@link FlatUIUtils#paintParentBackground} to paint the empty space correctly.
*
* @uiDefault Component.focusWidth int
* @uiDefault Component.innerFocusWidth int
* @uiDefault Component.innerFocusWidth int or float
* @uiDefault Component.focusColor Color
* @uiDefault Component.borderColor Color
* @uiDefault Component.disabledBorderColor Color
@@ -60,7 +60,7 @@ public class FlatBorder
extends BasicBorders.MarginBorder
{
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 borderColor = UIManager.getColor( "Component.borderColor" );
protected final Color disabledBorderColor = UIManager.getColor( "Component.disabledBorderColor" );
@@ -72,18 +72,19 @@ public class FlatBorder
try {
FlatUIUtils.setRenderingHints( g2 );
float focusWidth = getFocusWidth();
boolean isCellEditor = isTableCellEditor( c );
float focusWidth = isCellEditor ? 0 : getFocusWidth();
float borderWidth = getBorderWidth( c );
float arc = getArc();
float arc = isCellEditor ? 0 : getArc( c );
if( isFocused( c ) ) {
g2.setColor( getFocusColor( c ) );
FlatUIUtils.paintOutlineBorder( g2, x, y, width, height, focusWidth,
getLineWidth() + scale( (float) innerFocusWidth ), arc );
FlatUIUtils.paintComponentOuterBorder( g2, x, y, width, height, focusWidth,
getLineWidth() + scale( innerFocusWidth ), arc );
}
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 {
g2.dispose();
}
@@ -94,12 +95,23 @@ public class FlatBorder
}
protected Paint getBorderColor( Component c ) {
boolean enabled = c.isEnabled() && (!(c instanceof JTextComponent) || ((JTextComponent)c).isEditable());
return enabled
return isEnabled( c )
? (isFocused( c ) ? focusedBorderColor : borderColor)
: 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 ) {
if( c instanceof JScrollPane ) {
JViewport viewport = ((JScrollPane)c).getViewport();
@@ -132,9 +144,14 @@ public class FlatBorder
return c.hasFocus();
}
protected boolean isTableCellEditor( Component c ) {
return FlatUIUtils.isTableCellEditor( c );
}
@Override
public Insets getBorderInsets( Component c, Insets insets ) {
float ow = getFocusWidth() + getLineWidth();
boolean isCellEditor = isTableCellEditor( c );
float ow = (isCellEditor ? 0 : getFocusWidth()) + getLineWidth();
insets = super.getBorderInsets( c, insets );
insets.top = Math.round( scale( (float) insets.top ) + ow );
@@ -156,7 +173,7 @@ public class FlatBorder
return getLineWidth();
}
protected float getArc() {
protected float getArc( Component c ) {
return 0;
}
}

View File

@@ -19,6 +19,7 @@ package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Color;
import java.awt.Component;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Paint;
@@ -30,10 +31,14 @@ import javax.swing.plaf.UIResource;
* Border for {@link javax.swing.JButton}.
*
* @uiDefault Button.borderColor Color
* @uiDefault Button.startBorderColor Color optional; if set, a gradient paint is used and Button.borderColor is ignored
* @uiDefault Button.endBorderColor Color optional; if set, a gradient paint is used
* @uiDefault Button.disabledBorderColor Color
* @uiDefault Button.focusedBorderColor Color
* @uiDefault Button.hoverBorderColor Color optional
* @uiDefault Button.default.borderColor Color
* @uiDefault Button.default.startBorderColor Color optional; if set, a gradient paint is used and Button.default.borderColor is ignored
* @uiDefault Button.default.endBorderColor Color optional; if set, a gradient paint is used
* @uiDefault Button.default.hoverBorderColor Color optional
* @uiDefault Button.default.focusedBorderColor Color
* @uiDefault Button.default.focusColor Color
@@ -45,11 +50,13 @@ import javax.swing.plaf.UIResource;
public class FlatButtonBorder
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 focusedBorderColor = UIManager.getColor( "Button.focusedBorderColor" );
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 defaultFocusedBorderColor = UIManager.getColor( "Button.default.focusedBorderColor" );
protected final Color defaultFocusColor = UIManager.getColor( "Button.default.focusColor" );
@@ -58,7 +65,7 @@ public class FlatButtonBorder
@Override
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 ) && !FlatButtonUI.isHelpButton( c ) && !FlatToggleButtonUI.isUnderlineButton( c ) )
super.paintBorder( c, g, x, y, width, height );
}
@@ -70,12 +77,20 @@ public class FlatButtonBorder
@Override
protected Paint getBorderColor( Component c ) {
boolean def = FlatButtonUI.isDefaultButton( c );
return FlatButtonUI.buttonStateColor( c,
Paint color = FlatButtonUI.buttonStateColor( c,
def ? defaultBorderColor : borderColor,
disabledBorderColor,
def ? defaultFocusedBorderColor : focusedBorderColor,
def ? defaultHoverBorderColor : hoverBorderColor,
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
@@ -95,7 +110,7 @@ public class FlatButtonBorder
}
@Override
protected float getArc() {
return scale( (float) arc );
protected float getArc( Component c ) {
return FlatButtonUI.isSquareButton( c ) ? 0 : scale( (float) arc );
}
}

View File

@@ -23,9 +23,11 @@ import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.RoundRectangle2D;
import javax.swing.AbstractButton;
import javax.swing.ButtonModel;
import javax.swing.Icon;
@@ -39,26 +41,44 @@ import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicButtonUI;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.util.UIScale;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JButton}.
*
* TODO document used UI defaults of superclass
* <!-- BasicButtonUI -->
*
* @uiDefault Button.font Font
* @uiDefault Button.background Color
* @uiDefault Button.foreground Color
* @uiDefault Button.border Border
* @uiDefault Button.margin Insets
* @uiDefault Button.rollover boolean
*
* <!-- FlatButtonUI -->
*
* @uiDefault Component.focusWidth int
* @uiDefault Button.arc int
* @uiDefault Button.minimumWidth 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.hoverBackground Color optional
* @uiDefault Button.pressedBackground Color optional
* @uiDefault Button.disabledText 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.focusedBackground Color optional
* @uiDefault Button.default.hoverBackground Color optional
* @uiDefault Button.default.pressedBackground Color optional
* @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.hoverBackground Color
* @uiDefault Button.toolbar.pressedBackground Color
*
@@ -72,18 +92,25 @@ public class FlatButtonUI
protected int minimumWidth;
protected int iconTextGap;
protected Color startBackground;
protected Color endBackground;
protected Color focusedBackground;
protected Color hoverBackground;
protected Color pressedBackground;
protected Color disabledText;
protected Color defaultBackground;
protected Color defaultEndBackground;
protected Color defaultForeground;
protected Color defaultFocusedBackground;
protected Color defaultHoverBackground;
protected Color defaultPressedBackground;
protected boolean defaultBoldText;
protected int shadowWidth;
protected Color shadowColor;
protected Color defaultShadowColor;
protected Color toolbarHoverBackground;
protected Color toolbarPressedBackground;
@@ -107,16 +134,29 @@ public class FlatButtonUI
String prefix = getPropertyPrefix();
focusWidth = UIManager.getInt( "Component.focusWidth" );
arc = UIManager.getInt( prefix + "arc" );
arc = UIManager.getInt( "Button.arc" );
minimumWidth = UIManager.getInt( prefix + "minimumWidth" );
iconTextGap = FlatUIUtils.getUIInt( prefix + "iconTextGap", 4 );
startBackground = UIManager.getColor( prefix + "startBackground" );
endBackground = UIManager.getColor( prefix + "endBackground" );
focusedBackground = UIManager.getColor( prefix + "focusedBackground" );
hoverBackground = UIManager.getColor( prefix + "hoverBackground" );
pressedBackground = UIManager.getColor( prefix + "pressedBackground" );
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" );
defaultFocusedBackground = UIManager.getColor( "Button.default.focusedBackground" );
defaultHoverBackground = UIManager.getColor( "Button.default.hoverBackground" );
@@ -131,6 +171,13 @@ public class FlatButtonUI
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, "iconTextGap", scale( iconTextGap ) );
MigLayoutVisualPadding.install( b, focusWidth );
@@ -153,10 +200,17 @@ public class FlatButtonUI
}
static boolean isIconOnlyButton( Component c ) {
String text;
return c instanceof JButton &&
((JButton)c).getIcon() != null &&
((text = ((JButton)c).getText()) == null || text.isEmpty());
if( !(c instanceof JButton) )
return false;
Icon icon = ((JButton)c).getIcon();
String text = ((JButton)c).getText();
return (icon != null && (text == null || text.isEmpty())) ||
(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 ) {
@@ -169,36 +223,56 @@ public class FlatButtonUI
@Override
public void update( Graphics g, JComponent c ) {
if( isHelpButton( c ) ) {
// fill background if opaque to avoid garbage if user sets opaque to true
if( c.isOpaque() )
FlatUIUtils.paintParentBackground( g, c );
if( isHelpButton( c ) ) {
helpButtonIcon.paintIcon( c, g, 0, 0 );
return;
}
if( c.isOpaque() && isContentAreaFilled( c ) ) {
FlatUIUtils.paintParentBackground( g, c );
Color background = getBackground( 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;
g2.setColor( background );
FlatUIUtils.fillRoundRectangle( g2, 0, 0, c.getWidth(), c.getHeight(), focusWidth, arc );
} finally {
g2.dispose();
}
}
}
if( isContentAreaFilled( c ) )
paintBackground( 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();
float focusWidth = (border instanceof FlatBorder) ? scale( (float) this.focusWidth ) : 0;
float arc = ((border instanceof FlatButtonBorder && !isSquareButton( c )) || isToolBarButton( c ))
? scale( (float) this.arc ) : 0;
boolean def = isDefaultButton( c );
// paint shadow
Color shadowColor = def ? defaultShadowColor : this.shadowColor;
if( shadowColor != null && shadowWidth > 0 && focusWidth > 0 && !c.hasFocus() && c.isEnabled() ) {
g2.setColor( shadowColor );
g2.fill( new RoundRectangle2D.Float( focusWidth, focusWidth + UIScale.scale( (float) shadowWidth ),
c.getWidth() - focusWidth * 2, c.getHeight() - 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, c.getHeight(), endBg ) );
else
FlatUIUtils.setColor( g2, background, def ? defaultBackground : c.getBackground() );
FlatUIUtils.paintComponentBackground( g2, 0, 0, c.getWidth(), c.getHeight(), focusWidth, arc );
} finally {
g2.dispose();
}
}
}
@Override
protected void paintText( Graphics g, AbstractButton b, Rectangle textRect, String text ) {
if( isHelpButton( b ) )
@@ -218,7 +292,7 @@ public class FlatButtonUI
paintText( g, b, textRect, text, b.isEnabled() ? getForeground( b ) : disabledText );
}
static void paintText( Graphics g, AbstractButton b, Rectangle textRect, String text, Color foreground ) {
public static void paintText( Graphics g, AbstractButton b, Rectangle textRect, String text, Color foreground ) {
FontMetrics fm = b.getFontMetrics( b.getFont() );
int mnemonicIndex = FlatLaf.isShowMnemonics() ? b.getDisplayedMnemonicIndex() : -1;
@@ -284,9 +358,12 @@ public class FlatButtonUI
Dimension prefSize = super.getPreferredSize( c );
// apply minimum width, if not in toolbar and not a icon-only button
if( !isToolBarButton( c ) && !isIconOnlyButton( c ) )
prefSize.width = Math.max( prefSize.width, scale( minimumWidth + (focusWidth * 2) ) );
// make button square if it is a icon-only button
// or apply minimum width, if not in toolbar and not a icon-only button
if( isIconOnlyButton( c ) )
prefSize.width = Math.max( prefSize.width, prefSize.height );
else if( !isToolBarButton( c ) )
prefSize.width = Math.max( prefSize.width, scale( FlatUIUtils.minimumWidth( c, minimumWidth ) + (focusWidth * 2) ) );
return prefSize;
}

View File

@@ -25,6 +25,26 @@ import javax.swing.plaf.basic.BasicCheckBoxMenuItemUI;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JCheckBoxMenuItem}.
*
* <!-- BasicCheckBoxMenuItemUI -->
*
* @uiDefault CheckBoxMenuItem.font Font
* @uiDefault CheckBoxMenuItem.background Color
* @uiDefault CheckBoxMenuItem.foreground Color
* @uiDefault CheckBoxMenuItem.disabledForeground Color
* @uiDefault CheckBoxMenuItem.selectionBackground Color
* @uiDefault CheckBoxMenuItem.selectionForeground Color
* @uiDefault CheckBoxMenuItem.acceleratorForeground Color
* @uiDefault CheckBoxMenuItem.acceleratorSelectionForeground Color
* @uiDefault MenuItem.acceleratorFont Font defaults to MenuItem.font
* @uiDefault MenuItem.acceleratorDelimiter String
* @uiDefault CheckBoxMenuItem.border Border
* @uiDefault CheckBoxMenuItem.borderPainted boolean
* @uiDefault CheckBoxMenuItem.margin Insets
* @uiDefault CheckBoxMenuItem.arrowIcon Icon
* @uiDefault CheckBoxMenuItem.checkIcon Icon
* @uiDefault CheckBoxMenuItem.opaque boolean
* @uiDefault CheckBoxMenuItem.evenHeight boolean
*
* @author Karl Tauber
*/
public class FlatCheckBoxMenuItemUI

View File

@@ -22,6 +22,21 @@ import javax.swing.plaf.ComponentUI;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JCheckBox}.
*
* <!-- BasicRadioButtonUI -->
*
* @uiDefault CheckBox.font Font
* @uiDefault CheckBox.background Color
* @uiDefault CheckBox.foreground Color
* @uiDefault CheckBox.border Border
* @uiDefault CheckBox.margin Insets
* @uiDefault CheckBox.rollover boolean
* @uiDefault CheckBox.icon Icon
*
* <!-- FlatRadioButtonUI -->
*
* @uiDefault CheckBox.iconTextGap int
* @uiDefault CheckBox.disabledText Color
*
* @author Karl Tauber
*/
public class FlatCheckBoxUI

View File

@@ -26,6 +26,11 @@ import com.formdev.flatlaf.util.UIScale;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JColorChooser}.
*
* <!-- BasicColorChooserUI -->
*
* @uiDefault ColorChooser.font Font
* @uiDefault ColorChooser.background Color
* @uiDefault ColorChooser.foreground Color
* @uiDefault ColorChooser.showPreviewPanelText boolean
* @uiDefault ColorChooser.swatchesSwatchSize Dimension
* @uiDefault ColorChooser.swatchesRecentSwatchSize Dimension

View File

@@ -19,6 +19,7 @@ package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Color;
import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
@@ -33,13 +34,16 @@ import java.awt.event.MouseListener;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.ref.WeakReference;
import javax.swing.BorderFactory;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.ListCellRenderer;
import javax.swing.LookAndFeel;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.border.AbstractBorder;
@@ -49,18 +53,30 @@ import javax.swing.plaf.basic.BasicComboBoxUI;
import javax.swing.plaf.basic.BasicComboPopup;
import javax.swing.plaf.basic.ComboPopup;
import javax.swing.text.JTextComponent;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.util.UIScale;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JComboBox}.
*
* TODO document used UI defaults of superclass
* <!-- BasicComboBoxUI -->
*
* @uiDefault ComboBox.font Font
* @uiDefault ComboBox.background Color
* @uiDefault ComboBox.foreground Color
* @uiDefault ComboBox.border Border
* @uiDefault ComboBox.padding Insets
* @uiDefault ComboBox.squareButton boolean default is true
*
* <!-- FlatComboBoxUI -->
*
* @uiDefault Component.focusWidth int
* @uiDefault Component.arc int
* @uiDefault Component.arrowType String triangle (default) or chevron
* @uiDefault Component.isIntelliJTheme boolean
* @uiDefault Component.borderColor Color
* @uiDefault Component.disabledBorderColor Color
* @uiDefault ComboBox.editableBackground Color optional; defaults to ComboBox.background
* @uiDefault ComboBox.disabledBackground Color
* @uiDefault ComboBox.disabledForeground Color
* @uiDefault ComboBox.buttonBackground Color
@@ -77,9 +93,11 @@ public class FlatComboBoxUI
protected int focusWidth;
protected int arc;
protected String arrowType;
protected boolean isIntelliJTheme;
protected Color borderColor;
protected Color disabledBorderColor;
protected Color editableBackground;
protected Color disabledBackground;
protected Color disabledForeground;
@@ -92,6 +110,8 @@ public class FlatComboBoxUI
private MouseListener hoverListener;
private boolean hover;
private WeakReference<Component> lastRendererComponent;
public static ComponentUI createUI( JComponent c ) {
return new FlatComboBoxUI();
}
@@ -122,12 +142,16 @@ public class FlatComboBoxUI
protected void installDefaults() {
super.installDefaults();
LookAndFeel.installProperty( comboBox, "opaque", false );
focusWidth = UIManager.getInt( "Component.focusWidth" );
arc = UIManager.getInt( "Component.arc" );
arrowType = UIManager.getString( "Component.arrowType" );
isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" );
borderColor = UIManager.getColor( "Component.borderColor" );
disabledBorderColor = UIManager.getColor( "Component.disabledBorderColor" );
editableBackground = UIManager.getColor( "ComboBox.editableBackground" );
disabledBackground = UIManager.getColor( "ComboBox.disabledBackground" );
disabledForeground = UIManager.getColor( "ComboBox.disabledForeground" );
@@ -150,6 +174,7 @@ public class FlatComboBoxUI
borderColor = null;
disabledBorderColor = null;
editableBackground = null;
disabledBackground = null;
disabledForeground = null;
@@ -208,12 +233,16 @@ public class FlatComboBoxUI
String propertyName = e.getPropertyName();
if( editor != null &&
((source == comboBox && (propertyName == "background" || propertyName == "foreground")) ||
((source == comboBox && propertyName == "foreground") ||
(source == editor && propertyName == "enabled")) )
{
// fix editor component colors
updateEditorColors();
}
} else if( editor != null && source == comboBox && propertyName == "componentOrientation" ) {
ComponentOrientation o = (ComponentOrientation) e.getNewValue();
editor.applyComponentOrientation( o );
} else if( editor != null && FlatClientProperties.PLACEHOLDER_TEXT.equals( propertyName ) )
editor.repaint();
}
};
}
@@ -235,6 +264,12 @@ public class FlatComboBoxUI
if( editor instanceof JTextComponent )
((JTextComponent)editor).setBorder( BorderFactory.createEmptyBorder() );
// explicitly make non-opaque
if( editor instanceof JComponent )
((JComponent)editor).setOpaque( false );
editor.applyComponentOrientation( comboBox.getComponentOrientation() );
updateEditorColors();
}
@@ -243,7 +278,6 @@ public class FlatComboBoxUI
// is used, then the editor is updated after the combobox and the
// colors are again replaced with default colors
boolean enabled = editor.isEnabled();
editor.setBackground( FlatUIUtils.nonUIResource( enabled ? comboBox.getBackground() : disabledBackground ) );
editor.setForeground( FlatUIUtils.nonUIResource( (enabled || editor instanceof JTextComponent)
? comboBox.getForeground()
: disabledForeground ) );
@@ -265,44 +299,46 @@ public class FlatComboBoxUI
@Override
public void update( Graphics g, JComponent c ) {
if( c.isOpaque() ) {
// fill background if opaque to avoid garbage if user sets opaque to true
if( c.isOpaque() && (focusWidth > 0 || arc != 0) )
FlatUIUtils.paintParentBackground( g, c );
Graphics2D g2 = (Graphics2D) g;
FlatUIUtils.setRenderingHints( g2 );
Graphics2D g2 = (Graphics2D) g;
FlatUIUtils.setRenderingHints( g2 );
int width = c.getWidth();
int height = c.getHeight();
float focusWidth = (c.getBorder() instanceof FlatBorder) ? scale( (float) this.focusWidth ) : 0;
float arc = (c.getBorder() instanceof FlatRoundBorder) ? scale( (float) this.arc ) : 0;
int arrowX = arrowButton.getX();
int arrowWidth = arrowButton.getWidth();
boolean enabled = comboBox.isEnabled();
boolean isLeftToRight = comboBox.getComponentOrientation().isLeftToRight();
int width = c.getWidth();
int height = c.getHeight();
float focusWidth = (c.getBorder() instanceof FlatBorder) ? scale( (float) this.focusWidth ) : 0;
float arc = (c.getBorder() instanceof FlatRoundBorder) ? scale( (float) this.arc ) : 0;
int arrowX = arrowButton.getX();
int arrowWidth = arrowButton.getWidth();
boolean enabled = comboBox.isEnabled();
boolean isLeftToRight = comboBox.getComponentOrientation().isLeftToRight();
// paint background
g2.setColor( enabled ? c.getBackground() : disabledBackground );
FlatUIUtils.fillRoundRectangle( g2, 0, 0, width, height, focusWidth, arc );
// paint background
g2.setColor( enabled
? (editableBackground != null && comboBox.isEditable() ? editableBackground : c.getBackground())
: getDisabledBackground( comboBox ) );
FlatUIUtils.paintComponentBackground( g2, 0, 0, width, height, focusWidth, arc );
// paint arrow button background
if( enabled ) {
g2.setColor( comboBox.isEditable() ? buttonEditableBackground : buttonBackground );
Shape oldClip = g2.getClip();
if( isLeftToRight )
g2.clipRect( arrowX, 0, width - arrowX, height );
else
g2.clipRect( 0, 0, arrowX + arrowWidth, height );
FlatUIUtils.fillRoundRectangle( g2, 0, 0, width, height, focusWidth, arc );
g2.setClip( oldClip );
}
// paint arrow button background
if( enabled ) {
g2.setColor( comboBox.isEditable() ? buttonEditableBackground : buttonBackground );
Shape oldClip = g2.getClip();
if( isLeftToRight )
g2.clipRect( arrowX, 0, width - arrowX, height );
else
g2.clipRect( 0, 0, arrowX + arrowWidth, height );
FlatUIUtils.paintComponentBackground( g2, 0, 0, width, height, focusWidth, arc );
g2.setClip( oldClip );
}
if( comboBox.isEditable() ) {
// paint vertical line between value and arrow button
g2.setColor( enabled ? borderColor : disabledBorderColor );
float lw = scale( 1f );
float lx = isLeftToRight ? arrowX : arrowX + arrowWidth - lw;
g2.fill( new Rectangle2D.Float( lx, focusWidth, lw, height - (focusWidth * 2) ) );
}
// paint vertical line between value and arrow button
if( comboBox.isEditable() ) {
g2.setColor( enabled ? borderColor : disabledBorderColor );
float lw = scale( 1f );
float lx = isLeftToRight ? arrowX : arrowX + arrowWidth - lw;
g2.fill( new Rectangle2D.Float( lx, focusWidth, lw, height - 1 - (focusWidth * 2)) );
}
paint( g, c );
@@ -312,14 +348,17 @@ public class FlatComboBoxUI
@SuppressWarnings( "unchecked" )
public void paintCurrentValue( Graphics g, Rectangle bounds, boolean hasFocus ) {
ListCellRenderer<Object> renderer = comboBox.getRenderer();
CellPaddingBorder.uninstall( renderer );
uninstallCellPaddingBorder( renderer );
if( renderer == null )
renderer = new DefaultListCellRenderer();
Component c = renderer.getListCellRendererComponent( listBox, comboBox.getSelectedItem(), -1, false, false );
c.setFont( comboBox.getFont() );
CellPaddingBorder.uninstall( c );
c.applyComponentOrientation( comboBox.getComponentOrientation() );
uninstallCellPaddingBorder( c );
boolean enabled = comboBox.isEnabled();
c.setForeground( enabled ? comboBox.getForeground() : disabledForeground );
c.setBackground( enabled ? comboBox.getBackground() : disabledBackground );
c.setBackground( enabled ? comboBox.getBackground() : getDisabledBackground( comboBox ) );
boolean shouldValidate = (c instanceof JPanel);
if( padding != null )
@@ -336,10 +375,38 @@ public class FlatComboBoxUI
@Override
public void paintCurrentValueBackground( Graphics g, Rectangle bounds, boolean hasFocus ) {
g.setColor( comboBox.isEnabled() ? comboBox.getBackground() : disabledBackground );
g.setColor( comboBox.isEnabled() ? comboBox.getBackground() : getDisabledBackground( comboBox ) );
g.fillRect( bounds.x, bounds.y, bounds.width, bounds.height );
}
private Color getDisabledBackground( JComponent c ) {
return isIntelliJTheme ? FlatUIUtils.getParentBackground( c ) : disabledBackground;
}
@Override
protected Dimension getDefaultSize() {
@SuppressWarnings( "unchecked" )
ListCellRenderer<Object> renderer = comboBox.getRenderer();
uninstallCellPaddingBorder( renderer );
Dimension size = super.getDefaultSize();
uninstallCellPaddingBorder( renderer );
return size;
}
@Override
protected Dimension getDisplaySize() {
@SuppressWarnings( "unchecked" )
ListCellRenderer<Object> renderer = comboBox.getRenderer();
uninstallCellPaddingBorder( renderer );
Dimension displaySize = super.getDisplaySize();
uninstallCellPaddingBorder( renderer );
return displaySize;
}
@Override
protected Dimension getSizeForComponent( Component comp ) {
Dimension size = super.getSizeForComponent( comp );
@@ -363,17 +430,33 @@ public class FlatComboBoxUI
return null;
}
private void uninstallCellPaddingBorder( Object o ) {
CellPaddingBorder.uninstall( o );
if( lastRendererComponent != null ) {
CellPaddingBorder.uninstall( lastRendererComponent );
lastRendererComponent = null;
}
}
//---- class FlatComboPopup -----------------------------------------------
@SuppressWarnings( { "rawtypes", "unchecked" } )
private class FlatComboPopup
extends BasicComboPopup
implements ListCellRenderer
{
private CellPaddingBorder paddingBorder;
FlatComboPopup( JComboBox combo ) {
super( combo );
// BasicComboPopup listens to JComboBox.componentOrientation and updates
// the component orientation of the list, scroller and popup, but when
// switching the LaF and a new combo popup is created, the component
// orientation is not applied.
ComponentOrientation o = comboBox.getComponentOrientation();
list.setComponentOrientation( o );
scroller.setComponentOrientation( o );
setComponentOrientation( o );
}
@Override
@@ -387,7 +470,13 @@ public class FlatComboBoxUI
comboBox.setPrototypeDisplayValue( prototype );
// make popup wider if necessary
pw = Math.max( pw, displaySize.width );
if( displaySize.width > pw ) {
int diff = displaySize.width - pw;
pw = displaySize.width;
if( !comboBox.getComponentOrientation().isLeftToRight() )
px -= diff;
}
return super.computePopupBounds( px, py, pw, ph );
}
@@ -405,7 +494,7 @@ public class FlatComboBoxUI
protected void configureList() {
super.configureList();
list.setCellRenderer( this );
list.setCellRenderer( new PopupListCellRenderer() );
}
@Override
@@ -416,27 +505,39 @@ public class FlatComboBoxUI
super.propertyChange( e );
if( e.getPropertyName() == "renderer" )
list.setCellRenderer( FlatComboPopup.this );
list.setCellRenderer( new PopupListCellRenderer() );
}
};
}
@Override
public Component getListCellRendererComponent( JList list, Object value,
int index, boolean isSelected, boolean cellHasFocus )
//---- class PopupListCellRenderer -----
private class PopupListCellRenderer
implements ListCellRenderer
{
ListCellRenderer renderer = comboBox.getRenderer();
CellPaddingBorder.uninstall( renderer );
@Override
public Component getListCellRendererComponent( JList list, Object value,
int index, boolean isSelected, boolean cellHasFocus )
{
ListCellRenderer renderer = comboBox.getRenderer();
CellPaddingBorder.uninstall( renderer );
CellPaddingBorder.uninstall( lastRendererComponent );
Component c = renderer.getListCellRendererComponent( list, value, index, isSelected, cellHasFocus );
if( renderer == null )
renderer = new DefaultListCellRenderer();
Component c = renderer.getListCellRendererComponent( list, value, index, isSelected, cellHasFocus );
c.applyComponentOrientation( comboBox.getComponentOrientation() );
if( c instanceof JComponent ) {
if( paddingBorder == null )
paddingBorder = new CellPaddingBorder( padding );
paddingBorder.install( (JComponent) c );
if( c instanceof JComponent ) {
if( paddingBorder == null )
paddingBorder = new CellPaddingBorder( padding );
paddingBorder.install( (JComponent) c );
}
lastRendererComponent = (c != renderer) ? new WeakReference<>( c ) : null;
return c;
}
return c;
}
}
@@ -469,6 +570,9 @@ public class FlatComboBoxUI
}
static void uninstall( Object o ) {
if( o instanceof WeakReference )
o = ((WeakReference<?>)o).get();
if( !(o instanceof JComponent) )
return;

View File

@@ -18,17 +18,37 @@ package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicEditorPaneUI;
import javax.swing.text.JTextComponent;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JEditorPane}.
*
* TODO document used UI defaults of superclass
* <!-- BasicEditorPaneUI -->
*
* @uiDefault EditorPane.font Font
* @uiDefault EditorPane.background Color also used if not editable
* @uiDefault EditorPane.foreground Color
* @uiDefault EditorPane.caretForeground Color
* @uiDefault EditorPane.selectionBackground Color
* @uiDefault EditorPane.selectionForeground Color
* @uiDefault EditorPane.disabledBackground Color used if not enabled
* @uiDefault EditorPane.inactiveBackground Color used if not editable
* @uiDefault EditorPane.inactiveForeground Color used if not enabled (yes, this is confusing; this should be named disabledForeground)
* @uiDefault EditorPane.border Border
* @uiDefault EditorPane.margin Insets
* @uiDefault EditorPane.caretBlinkRate int default is 500 milliseconds
*
* <!-- FlatEditorPaneUI -->
*
* @uiDefault Component.minimumWidth int
* @uiDefault Component.isIntelliJTheme boolean
*
* @author Karl Tauber
*/
@@ -36,6 +56,9 @@ public class FlatEditorPaneUI
extends BasicEditorPaneUI
{
protected int minimumWidth;
protected boolean isIntelliJTheme;
private Object oldHonorDisplayProperties;
public static ComponentUI createUI( JComponent c ) {
return new FlatEditorPaneUI();
@@ -46,6 +69,18 @@ public class FlatEditorPaneUI
super.installDefaults();
minimumWidth = UIManager.getInt( "Component.minimumWidth" );
isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" );
// use component font and foreground for HTML text
oldHonorDisplayProperties = getComponent().getClientProperty( JEditorPane.HONOR_DISPLAY_PROPERTIES );
getComponent().putClientProperty( JEditorPane.HONOR_DISPLAY_PROPERTIES, true );
}
@Override
protected void uninstallDefaults() {
super.uninstallDefaults();
getComponent().putClientProperty( JEditorPane.HONOR_DISPLAY_PROPERTIES, oldHonorDisplayProperties );
}
@Override
@@ -63,7 +98,21 @@ public class FlatEditorPaneUI
// and subtract 1px border line width.
// 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.
int minimumWidth = FlatUIUtils.minimumWidth( getComponent(), this.minimumWidth );
size.width = Math.max( size.width, scale( minimumWidth ) - (scale( 1 ) * 2) );
return size;
}
@Override
protected void paintBackground( Graphics g ) {
JTextComponent c = getComponent();
// for compatibility with IntelliJ themes
if( isIntelliJTheme && (!c.isEnabled() || !c.isEditable()) && (c.getBackground() instanceof UIResource) ) {
FlatUIUtils.paintParentBackground( g, c );
return;
}
super.paintBackground( g );
}
}

View File

@@ -0,0 +1,122 @@
/*
* 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.Dimension;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.metal.MetalFileChooserUI;
import com.formdev.flatlaf.util.UIScale;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JFileChooser}.
*
* <!-- 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
*/
public class FlatFileChooserUI
extends MetalFileChooserUI
{
public static ComponentUI createUI( JComponent c ) {
return new FlatFileChooserUI( (JFileChooser) c );
}
public FlatFileChooserUI( JFileChooser filechooser ) {
super( filechooser );
}
@Override
public Dimension getPreferredSize( JComponent c ) {
return UIScale.scale( super.getPreferredSize( c ) );
}
@Override
public Dimension getMinimumSize( JComponent c ) {
return UIScale.scale( super.getMinimumSize( c ) );
}
}

View File

@@ -22,6 +22,28 @@ import javax.swing.plaf.ComponentUI;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JFormattedTextField}.
*
* <!-- BasicTextFieldUI -->
*
* @uiDefault FormattedTextField.font Font
* @uiDefault FormattedTextField.background Color
* @uiDefault FormattedTextField.foreground Color also used if not editable
* @uiDefault FormattedTextField.caretForeground Color
* @uiDefault FormattedTextField.selectionBackground Color
* @uiDefault FormattedTextField.selectionForeground Color
* @uiDefault FormattedTextField.disabledBackground Color used if not enabled
* @uiDefault FormattedTextField.inactiveBackground Color used if not editable
* @uiDefault FormattedTextField.inactiveForeground Color used if not enabled (yes, this is confusing; this should be named disabledForeground)
* @uiDefault FormattedTextField.border Border
* @uiDefault FormattedTextField.margin Insets
* @uiDefault FormattedTextField.caretBlinkRate int default is 500 milliseconds
*
* <!-- FlatTextFieldUI -->
*
* @uiDefault Component.focusWidth int
* @uiDefault Component.minimumWidth int
* @uiDefault Component.isIntelliJTheme boolean
* @uiDefault FormattedTextField.placeholderForeground Color
*
* @author Karl Tauber
*/
public class FlatFormattedTextFieldUI

View File

@@ -20,12 +20,14 @@ import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.beans.PropertyChangeEvent;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.plaf.basic.BasicLabelUI;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.util.UIScale;
@@ -33,10 +35,15 @@ import com.formdev.flatlaf.util.UIScale;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JLabel}.
*
* <!-- BasicLabelUI -->
*
* @uiDefault Label.font Font
* @uiDefault Label.background Color only used if opaque
* @uiDefault Label.foreground Color
*
* <!-- FlatLabelUI -->
*
* @uiDefault Label.disabledForeground Color
* @uiDefault Label.font Font
*
* @author Karl Tauber
*/
@@ -72,6 +79,51 @@ public class FlatLabelUI
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
protected void paintEnabledText( JLabel l, Graphics g, String s, int textX, int textY ) {
int mnemIndex = FlatLaf.isShowMnemonics() ? l.getDisplayedMnemonicIndex() : -1;

View File

@@ -48,7 +48,7 @@ public class FlatLineBorder
try {
FlatUIUtils.setRenderingHints( g2 );
g2.setColor( lineColor );
FlatUIUtils.drawRoundRectangle( g2, x, y, width, height, 0f, scale( 1f ), 0f );
FlatUIUtils.paintComponentBorder( g2, x, y, width, height, 0f, scale( 1f ), 0f );
} finally {
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

@@ -27,11 +27,37 @@ import javax.swing.plaf.basic.BasicListUI;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JList}.
*
* TODO document used UI defaults of superclass
* <!-- BasicListUI -->
*
* @uiDefault List.font Font
* @uiDefault List.background Color
* @uiDefault List.foreground Color
* @uiDefault List.selectionBackground Color
* @uiDefault List.selectionForeground Color
* @uiDefault List.dropLineColor Color
* @uiDefault List.border Border
* @uiDefault List.cellRenderer ListCellRenderer
* @uiDefault FileChooser.listFont Font used if client property List.isFileList is true
*
* <!-- DefaultListCellRenderer -->
*
* @uiDefault List.cellNoFocusBorder Border
* @uiDefault List.focusCellHighlightBorder Border
* @uiDefault List.focusSelectedCellHighlightBorder Border
* @uiDefault List.dropCellBackground Color
* @uiDefault List.dropCellForeground Color
*
* <!-- FlatListUI -->
*
* @uiDefault List.selectionInactiveBackground Color
* @uiDefault List.selectionInactiveForeground Color
*
* <!-- FlatListCellBorder -->
*
* @uiDefault List.cellMargins Insets
* @uiDefault List.cellFocusColor Color
* @uiDefault List.showCellFocusIndicator boolean
*
* @author Karl Tauber
*/
public class FlatListUI

View File

@@ -23,6 +23,13 @@ import javax.swing.plaf.basic.BasicMenuBarUI;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JMenuBar}.
*
* <!-- BasicMenuBarUI -->
*
* @uiDefault MenuBar.font Font
* @uiDefault MenuBar.background Color
* @uiDefault MenuBar.foreground Color
* @uiDefault MenuBar.border Border
*
* @author Karl Tauber
*/
public class FlatMenuBarUI

View File

@@ -25,6 +25,26 @@ import javax.swing.plaf.basic.BasicMenuItemUI;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JMenuItem}.
*
* <!-- BasicMenuItemUI -->
*
* @uiDefault MenuItem.font Font
* @uiDefault MenuItem.background Color
* @uiDefault MenuItem.foreground Color
* @uiDefault MenuItem.disabledForeground Color
* @uiDefault MenuItem.selectionBackground Color
* @uiDefault MenuItem.selectionForeground Color
* @uiDefault MenuItem.acceleratorForeground Color
* @uiDefault MenuItem.acceleratorSelectionForeground Color
* @uiDefault MenuItem.acceleratorFont Font defaults to MenuItem.font
* @uiDefault MenuItem.acceleratorDelimiter String
* @uiDefault MenuItem.border Border
* @uiDefault MenuItem.borderPainted boolean
* @uiDefault MenuItem.margin Insets
* @uiDefault MenuItem.arrowIcon Icon
* @uiDefault MenuItem.checkIcon Icon
* @uiDefault MenuItem.opaque boolean
* @uiDefault MenuItem.evenHeight boolean
*
* @author Karl Tauber
*/
public class FlatMenuItemUI

View File

@@ -25,6 +25,29 @@ import javax.swing.plaf.basic.BasicMenuUI;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JMenu}.
*
* <!-- BasicMenuUI -->
*
* @uiDefault Menu.font Font
* @uiDefault Menu.background Color
* @uiDefault Menu.foreground Color
* @uiDefault Menu.disabledForeground Color
* @uiDefault Menu.selectionBackground Color
* @uiDefault Menu.selectionForeground Color
* @uiDefault Menu.acceleratorForeground Color
* @uiDefault Menu.acceleratorSelectionForeground Color
* @uiDefault MenuItem.acceleratorFont Font defaults to MenuItem.font
* @uiDefault MenuItem.acceleratorDelimiter String
* @uiDefault Menu.border Border
* @uiDefault Menu.borderPainted boolean
* @uiDefault Menu.margin Insets
* @uiDefault Menu.arrowIcon Icon
* @uiDefault Menu.checkIcon Icon
* @uiDefault Menu.opaque boolean
* @uiDefault Menu.evenHeight boolean
* @uiDefault Menu.crossMenuMnemonic boolean default is false
* @uiDefault Menu.useMenuBarBackgroundForTopLevel boolean default is false
* @uiDefault MenuBar.background Color used if Menu.useMenuBarBackgroundForTopLevel is true
*
* @author Karl Tauber
*/
public class FlatMenuUI

View File

@@ -19,27 +19,35 @@ package com.formdev.flatlaf.ui;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.Insets;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.plaf.basic.BasicOptionPaneUI;
import com.formdev.flatlaf.util.UIScale;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JOptionPane}.
*
* <!-- BasicOptionPaneUI -->
*
* @uiDefault OptionPane.font Font unused
* @uiDefault OptionPane.background Color
* @uiDefault OptionPane.foreground Color unused
* @uiDefault OptionPane.border Border
* @uiDefault OptionPane.messageAreaBorder Border
* @uiDefault OptionPane.buttonAreaBorder Border
* @uiDefault OptionPane.messageForeground Color
* @uiDefault OptionPane.messageFont Font
* @uiDefault OptionPane.buttonFont Font
* @uiDefault OptionPane.messageForeground Color optional; defaults to Label.foreground
* @uiDefault OptionPane.messageFont Font optional; defaults to Label.font
* @uiDefault OptionPane.buttonFont Font optional; defaults to Button.font
*
* @uiDefault OptionPane.minimumSize Dimension
* @uiDefault OptionPane.maxCharactersPerLine int
* @uiDefault OptionPane.iconMessageGap int
* @uiDefault OptionPane.messagePadding int
* @uiDefault OptionPane.buttonPadding int
* @uiDefault OptionPane.buttonMinimumWidth int -1=disabled
* @uiDefault OptionPane.sameSizeButtons boolean if true, gives all buttons same size
@@ -52,6 +60,25 @@ import com.formdev.flatlaf.util.UIScale;
* @uiDefault OptionPane.questionIcon 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
*/
public class FlatOptionPaneUI
@@ -76,6 +103,13 @@ public class FlatOptionPaneUI
focusWidth = UIManager.getInt( "Component.focusWidth" );
}
@Override
protected void installComponents() {
super.installComponents();
updateChildPanels( optionPane );
}
@Override
public Dimension getMinimumOptionPaneSize() {
return UIScale.scale( super.getMinimumOptionPaneSize() );
@@ -122,9 +156,33 @@ public class FlatOptionPaneUI
if( messagePadding > 0 )
cons.insets.bottom = UIScale.scale( messagePadding );
// disable line wrapping for HTML
if( msg instanceof String && BasicHTML.isHTMLString( (String) msg ) )
maxll = Integer.MAX_VALUE;
super.addMessageComponents( container, cons, msg, maxll, internallyCreated );
}
private void updateChildPanels( Container c ) {
for( Component child : c.getComponents() ) {
if( child instanceof JPanel ) {
JPanel panel = (JPanel)child;
// make sub-panel non-opaque for OptionPane.background
panel.setOpaque( false );
// use non-UIResource borders to avoid that they are replaced when switching LaF
Border border = panel.getBorder();
if( border instanceof UIResource )
panel.setBorder( new NonUIResourceBorder( border ) );
}
if( child instanceof Container ) {
updateChildPanels( (Container) child );
}
}
}
private Component findByName( Container c, String name ) {
for( Component child : c.getComponents() ) {
if( name.equals( child.getName() ) )
@@ -138,4 +196,31 @@ public class FlatOptionPaneUI
}
return null;
}
//---- class NonUIResourceBorder ------------------------------------------
private static class NonUIResourceBorder
implements Border
{
private final Border delegate;
NonUIResourceBorder( Border delegate ) {
this.delegate = delegate;
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
delegate.paintBorder( c, g, x, y, width, height );
}
@Override
public Insets getBorderInsets( Component c ) {
return delegate.getBorderInsets( c );
}
@Override
public boolean isBorderOpaque() {
return delegate.isBorderOpaque();
}
}
}

View File

@@ -17,26 +17,44 @@
package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.beans.PropertyChangeEvent;
import javax.swing.JComponent;
import javax.swing.LookAndFeel;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicPasswordFieldUI;
import javax.swing.text.JTextComponent;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.util.SystemInfo;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JPasswordField}.
*
* TODO document used UI defaults of superclass
* <!-- BasicPasswordFieldUI -->
*
* @uiDefault Component.focusWidth int
* @uiDefault Component.minimumWidth int
* @uiDefault PasswordField.font Font
* @uiDefault PasswordField.background Color
* @uiDefault PasswordField.foreground Color also used if not editable
* @uiDefault PasswordField.caretForeground Color
* @uiDefault PasswordField.selectionBackground Color
* @uiDefault PasswordField.selectionForeground Color
* @uiDefault PasswordField.disabledBackground Color used if not enabled
* @uiDefault PasswordField.inactiveBackground Color used if not editable
* @uiDefault PasswordField.inactiveForeground Color used if not enabled (yes, this is confusing; this should be named disabledForeground)
* @uiDefault PasswordField.border Border
* @uiDefault PasswordField.margin Insets
* @uiDefault PasswordField.echoChar character
* @uiDefault PasswordField.caretBlinkRate int default is 500 milliseconds
*
* <!-- FlatPasswordFieldUI -->
*
* @uiDefault Component.focusWidth int
* @uiDefault Component.minimumWidth int
* @uiDefault Component.isIntelliJTheme boolean
* @uiDefault PasswordField.placeholderForeground Color
*
* @author Karl Tauber
*/
@@ -45,8 +63,10 @@ public class FlatPasswordFieldUI
{
protected int focusWidth;
protected int minimumWidth;
protected boolean isIntelliJTheme;
protected Color placeholderForeground;
private Handler handler;
private FocusListener focusListener;
public static ComponentUI createUI( JComponent c ) {
return new FlatPasswordFieldUI();
@@ -60,8 +80,13 @@ public class FlatPasswordFieldUI
if( SystemInfo.IS_MAC )
LookAndFeel.installProperty( getComponent(), "echoChar", '\u2022' );
String prefix = getPropertyPrefix();
focusWidth = UIManager.getInt( "Component.focusWidth" );
minimumWidth = UIManager.getInt( "Component.minimumWidth" );
isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" );
placeholderForeground = UIManager.getColor( prefix + ".placeholderForeground" );
LookAndFeel.installProperty( getComponent(), "opaque", focusWidth == 0 );
MigLayoutVisualPadding.install( getComponent(), focusWidth );
}
@@ -70,6 +95,8 @@ public class FlatPasswordFieldUI
protected void uninstallDefaults() {
super.uninstallDefaults();
placeholderForeground = null;
MigLayoutVisualPadding.uninstall( getComponent() );
}
@@ -77,71 +104,52 @@ public class FlatPasswordFieldUI
protected void installListeners() {
super.installListeners();
getComponent().addFocusListener( getHandler() );
focusListener = new FlatUIUtils.RepaintFocusListener( getComponent() );
getComponent().addFocusListener( focusListener );
}
@Override
protected void uninstallListeners() {
super.uninstallListeners();
getComponent().removeFocusListener( getHandler() );
handler = null;
getComponent().removeFocusListener( focusListener );
focusListener = null;
}
public Handler getHandler() {
if( handler == null )
handler = new Handler();
return handler;
@Override
protected void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e );
if( FlatClientProperties.PLACEHOLDER_TEXT.equals( e.getPropertyName() ) )
getComponent().repaint();
}
@Override
protected void paintSafely( Graphics g ) {
FlatTextFieldUI.paintBackground( g, getComponent(), focusWidth, isIntelliJTheme );
FlatTextFieldUI.paintPlaceholder( g, getComponent(), placeholderForeground );
super.paintSafely( g );
}
@Override
protected void paintBackground( Graphics g ) {
JTextComponent c = getComponent();
FlatUIUtils.paintParentBackground( g, c );
Graphics2D g2 = (Graphics2D) g.create();
try {
FlatUIUtils.setRenderingHints( g2 );
float focusWidth = (c.getBorder() instanceof FlatBorder) ? scale( (float) this.focusWidth ) : 0;
g2.setColor( c.getBackground() );
FlatUIUtils.fillRoundRectangle( g2, 0, 0, c.getWidth(), c.getHeight(), focusWidth, 0 );
} finally {
g2.dispose();
}
// background is painted elsewhere
}
@Override
public Dimension getPreferredSize( JComponent c ) {
return applyMinimumWidth( super.getPreferredSize( c ) );
return applyMinimumWidth( super.getPreferredSize( c ), c );
}
@Override
public Dimension getMinimumSize( JComponent c ) {
return applyMinimumWidth( super.getMinimumSize( c ) );
return applyMinimumWidth( super.getMinimumSize( c ), c );
}
private Dimension applyMinimumWidth( Dimension size ) {
private Dimension applyMinimumWidth( Dimension size, JComponent c ) {
int minimumWidth = FlatUIUtils.minimumWidth( getComponent(), this.minimumWidth );
int focusWidth = (c.getBorder() instanceof FlatBorder) ? this.focusWidth : 0;
size.width = Math.max( size.width, scale( minimumWidth + (focusWidth * 2) ) );
return size;
}
//---- class Handler ------------------------------------------------------
private class Handler
implements FocusListener
{
@Override
public void focusGained( FocusEvent e ) {
getComponent().repaint();
}
@Override
public void focusLost( FocusEvent e ) {
getComponent().repaint();
}
}
}

View File

@@ -22,6 +22,17 @@ import javax.swing.plaf.ComponentUI;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JPopupMenu.Separator}.
*
* <!-- BasicSeparatorUI -->
*
* @uiDefault PopupMenuSeparator.background Color unused
* @uiDefault PopupMenuSeparator.foreground Color
*
* <!-- FlatSeparatorUI -->
*
* @uiDefault PopupMenuSeparator.height int height (or width) of the component; may be larger than stripe
* @uiDefault PopupMenuSeparator.stripeWidth int width of the stripe
* @uiDefault PopupMenuSeparator.stripeIndent int indent of stripe from top (or left); allows positioning of stripe within component
*
* @author Karl Tauber
*/
public class FlatPopupMenuSeparatorUI

View File

@@ -23,6 +23,13 @@ import javax.swing.plaf.basic.BasicPopupMenuUI;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JPopupMenu}.
*
* <!-- BasicPopupMenuUI -->
*
* @uiDefault PopupMenu.font Font
* @uiDefault PopupMenu.background Color
* @uiDefault PopupMenu.foreground Color
* @uiDefault PopupMenu.border Border
*
* @author Karl Tauber
*/
public class FlatPopupMenuUI

View File

@@ -17,12 +17,16 @@
package com.formdev.flatlaf.ui;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.geom.Area;
import java.awt.geom.RoundRectangle2D;
import javax.swing.JComponent;
import javax.swing.JProgressBar;
import javax.swing.LookAndFeel;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicProgressBarUI;
import com.formdev.flatlaf.util.UIScale;
@@ -30,23 +34,72 @@ import com.formdev.flatlaf.util.UIScale;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JProgressBar}.
*
* <!-- BasicProgressBarUI -->
*
* @uiDefault ProgressBar.font Font
* @uiDefault ProgressBar.background Color
* @uiDefault ProgressBar.foreground Color
* @uiDefault ProgressBar.selectionBackground Color
* @uiDefault ProgressBar.selectionForeground Color
* @uiDefault ProgressBar.border Border
* @uiDefault ProgressBar.horizontalSize Dimension default is 146,12
* @uiDefault ProgressBar.verticalSize Dimension default is 12,146
* @uiDefault ProgressBar.repaintInterval int default is 50 milliseconds
* @uiDefault ProgressBar.cycleTime int default is 3000 milliseconds
*
* <!-- FlatProgressBarUI -->
*
* @uiDefault ProgressBar.arc int
*
* @author Karl Tauber
*/
public class FlatProgressBarUI
extends BasicProgressBarUI
{
protected int arc;
protected Dimension horizontalSize;
protected Dimension verticalSize;
public static ComponentUI createUI( JComponent c ) {
return new FlatProgressBarUI();
}
@Override
protected void installDefaults() {
super.installDefaults();
LookAndFeel.installProperty( progressBar, "opaque", false );
arc = UIManager.getInt( "ProgressBar.arc" );
horizontalSize = UIManager.getDimension( "ProgressBar.horizontalSize" );
verticalSize = UIManager.getDimension( "ProgressBar.verticalSize" );
}
@Override
public Dimension getPreferredSize( JComponent c ) {
Dimension size = super.getPreferredSize( c );
if( progressBar.isStringPainted() ) {
// 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
protected Dimension getPreferredInnerHorizontal() {
return UIScale.scale( super.getPreferredInnerHorizontal() );
return UIScale.scale( horizontalSize );
}
@Override
protected Dimension getPreferredInnerVertical() {
return UIScale.scale( super.getPreferredInnerVertical() );
return UIScale.scale( verticalSize );
}
@Override
@@ -69,13 +122,14 @@ public class FlatProgressBarUI
return;
boolean horizontal = (progressBar.getOrientation() == JProgressBar.HORIZONTAL);
int arc = horizontal ? height : width;
int arc = Math.min( UIScale.scale( this.arc ), horizontal ? height : width );
FlatUIUtils.setRenderingHints( (Graphics2D) g );
// paint track
RoundRectangle2D.Float trackShape = new RoundRectangle2D.Float( x, y, width, height, arc, arc );
g.setColor( progressBar.getBackground() );
((Graphics2D)g).fill( new RoundRectangle2D.Float( x, y, width, height, arc, arc ) );
((Graphics2D)g).fill( trackShape );
// paint progress
if( progressBar.isIndeterminate() ) {
@@ -91,11 +145,19 @@ public class FlatProgressBarUI
} else {
int amountFull = getAmountFull( insets, width, height );
g.setColor( progressBar.getForeground() );
((Graphics2D)g).fill( horizontal
RoundRectangle2D.Float progressShape = horizontal
? new RoundRectangle2D.Float( c.getComponentOrientation().isLeftToRight() ? x : x + (width - amountFull),
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() )
paintString( g, x, y, width, height, amountFull, insets );

View File

@@ -25,6 +25,26 @@ import javax.swing.plaf.basic.BasicRadioButtonMenuItemUI;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JRadioButtonMenuItem}.
*
* <!-- BasicRadioButtonMenuItemUI -->
*
* @uiDefault RadioButtonMenuItem.font Font
* @uiDefault RadioButtonMenuItem.background Color
* @uiDefault RadioButtonMenuItem.foreground Color
* @uiDefault RadioButtonMenuItem.disabledForeground Color
* @uiDefault RadioButtonMenuItem.selectionBackground Color
* @uiDefault RadioButtonMenuItem.selectionForeground Color
* @uiDefault RadioButtonMenuItem.acceleratorForeground Color
* @uiDefault RadioButtonMenuItem.acceleratorSelectionForeground Color
* @uiDefault MenuItem.acceleratorFont Font defaults to MenuItem.font
* @uiDefault MenuItem.acceleratorDelimiter String
* @uiDefault RadioButtonMenuItem.border Border
* @uiDefault RadioButtonMenuItem.borderPainted boolean
* @uiDefault RadioButtonMenuItem.margin Insets
* @uiDefault RadioButtonMenuItem.arrowIcon Icon
* @uiDefault RadioButtonMenuItem.checkIcon Icon
* @uiDefault RadioButtonMenuItem.opaque boolean
* @uiDefault RadioButtonMenuItem.evenHeight boolean
*
* @author Karl Tauber
*/
public class FlatRadioButtonMenuItemUI

View File

@@ -18,22 +18,38 @@ package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
import javax.swing.AbstractButton;
import javax.swing.CellRendererPane;
import javax.swing.JComponent;
import javax.swing.LookAndFeel;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
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}.
*
* TODO document used UI defaults of superclass
* <!-- BasicRadioButtonUI -->
*
* @uiDefault Button.iconTextGap int
* @uiDefault Button.disabledText Color
* @uiDefault RadioButton.font Font
* @uiDefault RadioButton.background Color
* @uiDefault RadioButton.foreground Color
* @uiDefault RadioButton.border Border
* @uiDefault RadioButton.margin Insets
* @uiDefault RadioButton.rollover boolean
* @uiDefault RadioButton.icon Icon
*
* <!-- FlatRadioButtonUI -->
*
* @uiDefault RadioButton.iconTextGap int
* @uiDefault RadioButton.disabledText Color
*
* @author Karl Tauber
*/
@@ -66,6 +82,7 @@ public class FlatRadioButtonUI
defaults_initialized = true;
}
LookAndFeel.installProperty( b, "opaque", false );
LookAndFeel.installProperty( b, "iconTextGap", scale( iconTextGap ) );
MigLayoutVisualPadding.install( b, null );
@@ -79,8 +96,73 @@ public class FlatRadioButtonUI
defaults_initialized = false;
}
private static Insets tempInsets = new Insets( 0, 0, 0, 0 );
@Override
public Dimension getPreferredSize( JComponent c ) {
Dimension size = super.getPreferredSize( c );
// 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
public void paint( Graphics g, JComponent c ) {
// fill background even if opaque if
// - used as cell renderer (because of selection background)
// - if background was explicitly set to a non-UIResource color
if( !c.isOpaque() &&
(c.getParent() instanceof CellRendererPane || !(c.getBackground() instanceof UIResource)) )
{
g.setColor( c.getBackground() );
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 );
}
@Override
protected void paintText( Graphics g, AbstractButton b, Rectangle textRect, String text ) {
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

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

View File

@@ -22,27 +22,41 @@ import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Objects;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JScrollPane;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicScrollBarUI;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.util.UIScale;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JScrollBar}.
*
* <!-- BasicScrollBarUI -->
*
* @uiDefault ScrollBar.background Color
* @uiDefault ScrollBar.foreground Color
* @uiDefault ScrollBar.track Color
* @uiDefault ScrollBar.thumb Color
* @uiDefault ScrollBar.hoverTrackColor Color
* @uiDefault ScrollBar.hoverThumbColor Color
* @uiDefault ScrollBar.width int
* @uiDefault ScrollBar.minimumThumbSize Insets
* @uiDefault ScrollBar.maximumThumbSize Insets
* @uiDefault ScrollBar.minimumThumbSize Dimension
* @uiDefault ScrollBar.maximumThumbSize Dimension
* @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
*/
public class FlatScrollBarUI
@@ -51,6 +65,11 @@ public class FlatScrollBarUI
protected Color hoverTrackColor;
protected Color hoverThumbColor;
protected boolean showButtons;
protected String arrowType;
protected Color buttonArrowColor;
protected Color buttonDisabledArrowColor;
private MouseAdapter hoverListener;
private boolean hoverTrack;
private boolean hoverThumb;
@@ -83,6 +102,11 @@ public class FlatScrollBarUI
hoverTrackColor = UIManager.getColor( "ScrollBar.hoverTrackColor" );
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
@@ -91,6 +115,24 @@ public class FlatScrollBarUI
hoverTrackColor = null;
hoverThumbColor = null;
buttonArrowColor = null;
buttonDisabledArrowColor = null;
}
@Override
protected PropertyChangeListener createPropertyChangeListener() {
return new BasicScrollBarUI.PropertyChangeHandler() {
@Override
public void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e );
if( FlatClientProperties.SCROLL_BAR_SHOW_BUTTONS.equals( e.getPropertyName() ) ) {
scrollbar.revalidate();
scrollbar.repaint();
}
}
};
}
@Override
@@ -100,24 +142,50 @@ public class FlatScrollBarUI
@Override
protected JButton createDecreaseButton( int orientation ) {
return createInvisibleButton();
return createArrowButton( orientation );
}
@Override
protected JButton createIncreaseButton( int orientation ) {
return createInvisibleButton();
return createArrowButton( orientation );
}
private JButton createInvisibleButton() {
JButton button = new JButton();
button.setMinimumSize( new Dimension() );
button.setMaximumSize( new Dimension() );
button.setPreferredSize( new Dimension() );
private JButton createArrowButton( int orientation ) {
FlatArrowButton button = new FlatArrowButton( orientation,
arrowType, buttonArrowColor, buttonDisabledArrowColor, null, hoverTrackColor )
{
@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.setRequestFocusEnabled( false );
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
protected void paintDecreaseHighlight( Graphics g ) {
// do not paint

View File

@@ -14,10 +14,15 @@
* 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;
import java.awt.Component;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
@@ -25,19 +30,41 @@ import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JViewport;
import javax.swing.ScrollPaneLayout;
import javax.swing.LookAndFeel;
import javax.swing.ScrollPaneConstants;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicScrollPaneUI;
import com.formdev.flatlaf.FlatClientProperties;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JScrollPane}.
*
* <!-- BasicScrollPaneUI -->
*
* @uiDefault ScrollPane.font Font unused
* @uiDefault ScrollPane.background Color
* @uiDefault ScrollPane.foreground Color unused
* @uiDefault ScrollPane.border Border
* @uiDefault ScrollPane.viewportBorder Border
*
* <!-- FlatScrollPaneUI -->
*
* @uiDefault ScrollPane.smoothScrolling boolean
*
* @author Karl Tauber
*/
public class FlatScrollPaneUI
@@ -53,17 +80,14 @@ public class FlatScrollPaneUI
public void installUI( JComponent c ) {
super.installUI( c );
if( scrollpane.getLayout() instanceof UIResource )
scrollpane.setLayout( new FlatScrollPaneLayout() );
int focusWidth = UIManager.getInt( "Component.focusWidth" );
LookAndFeel.installProperty( c, "opaque", focusWidth == 0 );
MigLayoutVisualPadding.install( scrollpane, UIManager.getInt( "Component.focusWidth" ) );
MigLayoutVisualPadding.install( scrollpane, focusWidth );
}
@Override
public void uninstallUI( JComponent c ) {
if( scrollpane.getLayout() instanceof FlatScrollPaneLayout )
scrollpane.setLayout( new ScrollPaneLayout.UIResource() );
MigLayoutVisualPadding.uninstall( scrollpane );
super.uninstallUI( c );
@@ -85,7 +109,172 @@ public class FlatScrollPaneUI
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 )
handler = new Handler();
return handler;
@@ -169,27 +358,4 @@ public class FlatScrollPaneUI
scrollpane.repaint();
}
}
//---- class FlatScrollPaneLayout -----------------------------------------
private static class FlatScrollPaneLayout
extends ScrollPaneLayout
{
@Override
public void layoutContainer( Container parent ) {
super.layoutContainer( parent );
// increase height of vertical scroll bar so that it also fills the upper right corner
if( colHead != null && vsb != null && colHead.isVisible() && vsb.isVisible() ) {
Rectangle colHeadBounds = colHead.getBounds();
Rectangle vsbBounds = vsb.getBounds();
if( vsbBounds.y > colHeadBounds.y ) {
vsbBounds.height += (vsbBounds.y - colHeadBounds.y);
vsbBounds.y = colHeadBounds.y;
vsb.setBounds( vsbBounds );
}
}
}
}
}

View File

@@ -30,8 +30,13 @@ import javax.swing.plaf.basic.BasicSeparatorUI;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JSeparator}.
*
* <!-- BasicSeparatorUI -->
*
* @uiDefault Separator.background Color unused
* @uiDefault Separator.foreground Color
*
* <!-- FlatSeparatorUI -->
*
* @uiDefault Separator.height int height (or width) of the component; may be larger than stripe
* @uiDefault Separator.stripeWidth int width of the stripe
* @uiDefault Separator.stripeIndent int indent of stripe from top (or left); allows positioning of stripe within component

View File

@@ -25,6 +25,7 @@ import java.awt.geom.Path2D;
import java.awt.geom.RoundRectangle2D;
import javax.swing.JComponent;
import javax.swing.JSlider;
import javax.swing.LookAndFeel;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicSliderUI;
@@ -33,21 +34,27 @@ import com.formdev.flatlaf.util.UIScale;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JSlider}.
*
* <!-- BasicSliderUI -->
*
* @uiDefault Slider.font Font
* @uiDefault Slider.background Color
* @uiDefault Slider.foreground Color unused
* @uiDefault Slider.disabledForeground Color used for track and thumb if disabled
* @uiDefault Slider.trackColor Color
* @uiDefault Slider.thumbColor Color
* @uiDefault Slider.tickColor Color
* @uiDefault Slider.focusedColor Color
* @uiDefault Slider.hoverColor Color optional; defaults to Slider.focusedColor
* @uiDefault Slider.trackWidth int
* @uiDefault Slider.thumbWidth int
* @uiDefault Slider.horizontalSize Dimension preferred horizontal size; height is ignored; computed slider height is used
* @uiDefault Slider.verticalSize Dimension preferred vertical size; width is ignored; computed slider width is used
* @uiDefault Slider.minimumHorizontalSize Dimension height is ignored; computed slider height is used
* @uiDefault Slider.minimumVerticalSize Dimension width is ignored; computed slider width is used
* @uiDefault Slider.border Border
*
* <!-- FlatSliderUI -->
*
* @uiDefault Slider.trackWidth int
* @uiDefault Slider.thumbWidth int
* @uiDefault Slider.trackColor Color
* @uiDefault Slider.thumbColor Color
* @uiDefault Slider.focusedColor Color optional; defaults to Component.focusColor
* @uiDefault Slider.hoverColor Color optional; defaults to Slider.focusedColor
* @uiDefault Slider.disabledForeground Color used for track and thumb is disabled
*
* @author Karl Tauber
*/
@@ -96,12 +103,14 @@ public class FlatSliderUI
protected void installDefaults( JSlider slider ) {
super.installDefaults( slider );
LookAndFeel.installProperty( slider, "opaque", false );
trackWidth = UIManager.getInt( "Slider.trackWidth" );
thumbWidth = UIManager.getInt( "Slider.thumbWidth" );
trackColor = UIManager.getColor( "Slider.trackColor" );
thumbColor = UIManager.getColor( "Slider.thumbColor" );
focusColor = UIManager.getColor( "Slider.focusedColor" );
focusColor = FlatUIUtils.getUIColor( "Slider.focusedColor", "Component.focusColor" );
hoverColor = FlatUIUtils.getUIColor( "Slider.hoverColor", focusColor );
disabledForeground = UIManager.getColor( "Slider.disabledForeground" );
}
@@ -170,9 +179,15 @@ public class FlatSliderUI
if( slider.getOrientation() == JSlider.HORIZONTAL ) {
float y = trackRect.y + (trackRect.height - tw) / 2f;
if( enabled && isRoundThumb() ) {
int cw = thumbRect.x + (thumbRect.width / 2) - trackRect.x;
coloredTrack = new RoundRectangle2D.Float( trackRect.x, y, cw, tw, arc, arc );
track = new RoundRectangle2D.Float( trackRect.x + cw, y, trackRect.width - cw, tw, arc, arc );
if( slider.getComponentOrientation().isLeftToRight() ) {
int cw = thumbRect.x + (thumbRect.width / 2) - trackRect.x;
coloredTrack = new RoundRectangle2D.Float( trackRect.x, y, cw, tw, arc, arc );
track = new RoundRectangle2D.Float( trackRect.x + cw, y, trackRect.width - cw, tw, arc, arc );
} else {
int cw = trackRect.x + trackRect.width - thumbRect.x - (thumbRect.width / 2);
coloredTrack = new RoundRectangle2D.Float( trackRect.x + trackRect.width - cw, y, cw, tw, arc, arc );
track = new RoundRectangle2D.Float( trackRect.x, y, trackRect.width - cw, tw, arc, arc );
}
} else
track = new RoundRectangle2D.Float( trackRect.x, y, trackRect.width, tw, arc, arc );
} else {
@@ -186,7 +201,7 @@ public class FlatSliderUI
}
if( coloredTrack != null ) {
g.setColor( slider.hasFocus() ? focusColor : (hover ? hoverColor : thumbColor) );
FlatUIUtils.setColor( g, slider.hasFocus() ? focusColor : (hover ? hoverColor : thumbColor), thumbColor );
((Graphics2D)g).fill( coloredTrack );
}
@@ -196,9 +211,10 @@ public class FlatSliderUI
@Override
public void paintThumb( Graphics g ) {
g.setColor( slider.isEnabled()
FlatUIUtils.setColor( g, slider.isEnabled()
? (slider.hasFocus() ? focusColor : (hover ? hoverColor : thumbColor))
: disabledForeground );
: disabledForeground,
thumbColor );
if( isRoundThumb() )
g.fillOval( thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height );

View File

@@ -35,6 +35,7 @@ import java.beans.PropertyChangeListener;
import javax.swing.JComponent;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.LookAndFeel;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
@@ -43,12 +44,23 @@ import javax.swing.plaf.basic.BasicSpinnerUI;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JSpinner}.
*
* TODO document used UI defaults of superclass
* <!-- BasicSpinnerUI -->
*
* @uiDefault Spinner.font Font
* @uiDefault Spinner.background Color
* @uiDefault Spinner.foreground Color
* @uiDefault Spinner.border Border
* @uiDefault Spinner.disableOnBoundaryValues boolean default is false
* @uiDefault Spinner.editorAlignment int 0=center, 2=left, 4=right, 10=leading, 11=trailing
* @uiDefault Spinner.editorBorderPainted boolean paint inner editor border; defaults to false
*
* <!-- FlatSpinnerUI -->
*
* @uiDefault Component.focusWidth int
* @uiDefault Component.arc int
* @uiDefault Component.minimumWidth int
* @uiDefault Component.arrowType String triangle (default) or chevron
* @uiDefault Component.isIntelliJTheme boolean
* @uiDefault Component.borderColor Color
* @uiDefault Component.disabledBorderColor Color
* @uiDefault Spinner.disabledBackground Color
@@ -70,6 +82,7 @@ public class FlatSpinnerUI
protected int arc;
protected int minimumWidth;
protected String arrowType;
protected boolean isIntelliJTheme;
protected Color borderColor;
protected Color disabledBorderColor;
protected Color disabledBackground;
@@ -88,10 +101,13 @@ public class FlatSpinnerUI
protected void installDefaults() {
super.installDefaults();
LookAndFeel.installProperty( spinner, "opaque", false );
focusWidth = UIManager.getInt( "Component.focusWidth" );
arc = UIManager.getInt( "Component.arc" );
minimumWidth = UIManager.getInt( "Component.minimumWidth" );
arrowType = UIManager.getString( "Component.arrowType" );
isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" );
borderColor = UIManager.getColor( "Component.borderColor" );
disabledBorderColor = UIManager.getColor( "Component.disabledBorderColor" );
disabledBackground = UIManager.getColor( "Spinner.disabledBackground" );
@@ -143,7 +159,7 @@ public class FlatSpinnerUI
handler = null;
}
public Handler getHandler() {
private Handler getHandler() {
if( handler == null )
handler = new Handler();
return handler;
@@ -152,6 +168,13 @@ public class FlatSpinnerUI
@Override
protected JComponent createEditor() {
JComponent editor = super.createEditor();
// explicitly make non-opaque
editor.setOpaque( false );
JTextField textField = getEditorTextField( editor );
if( textField != null )
textField.setOpaque( false );
updateEditorColors();
return editor;
}
@@ -183,8 +206,6 @@ public class FlatSpinnerUI
// use non-UIResource colors because when SwingUtilities.updateComponentTreeUI()
// is used, then the text field is updated after the spinner and the
// colors are again replaced with default colors
textField.setBackground( FlatUIUtils.nonUIResource( spinner.isEnabled()
? spinner.getBackground() : disabledBackground ) );
textField.setForeground( FlatUIUtils.nonUIResource( spinner.getForeground() ) );
textField.setDisabledTextColor( FlatUIUtils.nonUIResource( disabledForeground ) );
}
@@ -225,45 +246,47 @@ public class FlatSpinnerUI
@Override
public void update( Graphics g, JComponent c ) {
if( c.isOpaque() ) {
// fill background if opaque to avoid garbage if user sets opaque to true
if( c.isOpaque() && (focusWidth > 0 || arc != 0) )
FlatUIUtils.paintParentBackground( g, c );
Graphics2D g2 = (Graphics2D) g;
FlatUIUtils.setRenderingHints( g2 );
Graphics2D g2 = (Graphics2D) g;
FlatUIUtils.setRenderingHints( g2 );
int width = c.getWidth();
int height = c.getHeight();
float focusWidth = (c.getBorder() instanceof FlatBorder) ? scale( (float) this.focusWidth ) : 0;
float arc = (c.getBorder() instanceof FlatRoundBorder) ? scale( (float) this.arc ) : 0;
Component nextButton = getHandler().nextButton;
int arrowX = nextButton.getX();
int arrowWidth = nextButton.getWidth();
boolean enabled = spinner.isEnabled();
boolean isLeftToRight = spinner.getComponentOrientation().isLeftToRight();
int width = c.getWidth();
int height = c.getHeight();
float focusWidth = (c.getBorder() instanceof FlatBorder) ? scale( (float) this.focusWidth ) : 0;
float arc = (c.getBorder() instanceof FlatRoundBorder) ? scale( (float) this.arc ) : 0;
Component nextButton = getHandler().nextButton;
int arrowX = nextButton.getX();
int arrowWidth = nextButton.getWidth();
boolean enabled = spinner.isEnabled();
boolean isLeftToRight = spinner.getComponentOrientation().isLeftToRight();
// paint background
g2.setColor( enabled ? c.getBackground() : disabledBackground );
FlatUIUtils.fillRoundRectangle( g2, 0, 0, width, height, focusWidth, arc );
// paint background
g2.setColor( enabled
? c.getBackground()
: (isIntelliJTheme ? FlatUIUtils.getParentBackground( c ) : disabledBackground) );
FlatUIUtils.paintComponentBackground( g2, 0, 0, width, height, focusWidth, arc );
// paint arrow buttons background
if( enabled ) {
g2.setColor( buttonBackground );
Shape oldClip = g2.getClip();
if( isLeftToRight )
g2.clipRect( arrowX, 0, width - arrowX, height );
else
g2.clipRect( 0, 0, arrowX + arrowWidth, height );
FlatUIUtils.fillRoundRectangle( g2, 0, 0, width, height, focusWidth, arc );
g2.setClip( oldClip );
}
// paint vertical line between value and arrow buttons
g2.setColor( enabled ? borderColor : disabledBorderColor );
float lw = scale( 1f );
float lx = isLeftToRight ? arrowX : arrowX + arrowWidth - lw;
g2.fill( new Rectangle2D.Float( lx, focusWidth, lw, height - (focusWidth * 2) ) );
// paint arrow buttons background
if( enabled ) {
g2.setColor( buttonBackground );
Shape oldClip = g2.getClip();
if( isLeftToRight )
g2.clipRect( arrowX, 0, width - arrowX, height );
else
g2.clipRect( 0, 0, arrowX + arrowWidth, height );
FlatUIUtils.paintComponentBackground( g2, 0, 0, width, height, focusWidth, arc );
g2.setClip( oldClip );
}
// paint vertical line between value and arrow buttons
g2.setColor( enabled ? borderColor : disabledBorderColor );
float lw = scale( 1f );
float lx = isLeftToRight ? arrowX : arrowX + arrowWidth - lw;
g2.fill( new Rectangle2D.Float( lx, focusWidth, lw, height - 1 - (focusWidth * 2) ) );
paint( g, c );
}
@@ -303,6 +326,7 @@ public class FlatSpinnerUI
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
int minimumWidth = FlatUIUtils.minimumWidth( spinner, FlatSpinnerUI.this.minimumWidth );
int innerHeight = editorSize.height + padding.top + padding.bottom;
return new Dimension(
Math.max( insets.left + insets.right + editorSize.width + padding.left + padding.right + innerHeight, scale( minimumWidth + (focusWidth * 2) ) ),
@@ -370,7 +394,6 @@ public class FlatSpinnerUI
@Override
public void propertyChange( PropertyChangeEvent e ) {
switch( e.getPropertyName() ) {
case "background":
case "foreground":
case "enabled":
updateEditorColors();

View File

@@ -31,14 +31,19 @@ import com.formdev.flatlaf.util.UIScale;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JSplitPane}.
*
* @uiDefault Component.arrowType String triangle (default) or chevron
* <!-- BasicSplitPaneUI -->
*
* @uiDefault SplitPane.background Color
* @uiDefault SplitPane.foreground Color unused
* @uiDefault SplitPane.dividerSize int
* @uiDefault SplitPane.continuousLayout boolean
* @uiDefault SplitPane.border Border
* @uiDefault SplitPaneDivider.border Border
* @uiDefault SplitPaneDivider.draggingColor Color only used if continuousLayout is false
*
* <!-- FlatSplitPaneUI -->
*
* @uiDefault Component.arrowType String triangle (default) or chevron
* @uiDefault SplitPane.continuousLayout boolean
* @uiDefault SplitPaneDivider.oneTouchArrowColor Color
* @uiDefault SplitPaneDivider.oneTouchHoverArrowColor Color
*

View File

@@ -17,12 +17,19 @@
package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale;
import static com.formdev.flatlaf.FlatClientProperties.*;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JButton;
@@ -34,30 +41,47 @@ import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTabbedPaneUI;
import javax.swing.text.View;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.util.UIScale;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JTabbedPane}.
*
* @clientProperty JTabbedPane.showTabSeparators boolean
* @clientProperty JTabbedPane.hasFullBorder boolean
*
* @uiDefault Component.arrowType String triangle (default) or chevron
* <!-- BasicTabbedPaneUI -->
*
* @uiDefault TabbedPane.font Font
* @uiDefault TabbedPane.background Color
* @uiDefault TabbedPane.foreground Color
* @uiDefault TabbedPane.shadow Color used for scroll arrows and cropped line
* @uiDefault TabbedPane.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.selectedBackground Color optional
* @uiDefault TabbedPane.selectedForeground Color
* @uiDefault TabbedPane.underlineColor Color
* @uiDefault TabbedPane.disabledUnderlineColor Color
* @uiDefault TabbedPane.hoverColor Color
* @uiDefault TabbedPane.focusColor Color
* @uiDefault TabbedPane.tabSeparatorColor Color optional; defaults to TabbedPane.contentAreaColor
* @uiDefault TabbedPane.contentAreaColor Color
* @uiDefault TabbedPane.textIconGap int
* @uiDefault TabbedPane.tabInsets Insets
* @uiDefault TabbedPane.tabAreaInsets Insets
* @uiDefault TabbedPane.tabHeight int
* @uiDefault TabbedPane.tabSelectionHeight int
* @uiDefault TabbedPane.contentSeparatorHeight int
* @uiDefault TabbedPane.showTabSeparators boolean
* @uiDefault TabbedPane.hasFullBorder boolean
*
* @author Karl Tauber
@@ -66,16 +90,19 @@ public class FlatTabbedPaneUI
extends BasicTabbedPaneUI
{
protected Color disabledForeground;
protected Color selectedBackground;
protected Color selectedForeground;
protected Color underlineColor;
protected Color disabledUnderlineColor;
protected Color hoverColor;
protected Color focusColor;
protected Color tabSeparatorColor;
protected Color contentAreaColor;
protected int tabHeight;
protected int tabSelectionHeight;
protected int contentSeparatorHeight;
protected boolean showTabSeparators;
protected boolean hasFullBorder;
protected boolean tabsOverlapBorder;
@@ -88,16 +115,19 @@ public class FlatTabbedPaneUI
super.installDefaults();
disabledForeground = UIManager.getColor( "TabbedPane.disabledForeground" );
selectedBackground = UIManager.getColor( "TabbedPane.selectedBackground" );
selectedForeground = UIManager.getColor( "TabbedPane.selectedForeground" );
underlineColor = UIManager.getColor( "TabbedPane.underlineColor" );
disabledUnderlineColor = UIManager.getColor( "TabbedPane.disabledUnderlineColor" );
hoverColor = UIManager.getColor( "TabbedPane.hoverColor" );
focusColor = UIManager.getColor( "TabbedPane.focusColor" );
tabSeparatorColor = UIManager.getColor( "TabbedPane.tabSeparatorColor" );
contentAreaColor = UIManager.getColor( "TabbedPane.contentAreaColor" );
tabHeight = UIManager.getInt( "TabbedPane.tabHeight" );
tabSelectionHeight = UIManager.getInt( "TabbedPane.tabSelectionHeight" );
contentSeparatorHeight = UIManager.getInt( "TabbedPane.contentSeparatorHeight" );
showTabSeparators = UIManager.getBoolean( "TabbedPane.showTabSeparators" );
hasFullBorder = UIManager.getBoolean( "TabbedPane.hasFullBorder" );
tabsOverlapBorder = UIManager.getBoolean( "TabbedPane.tabsOverlapBorder" );
@@ -108,7 +138,6 @@ public class FlatTabbedPaneUI
tabAreaInsets = scale( tabAreaInsets );
tabHeight = scale( tabHeight );
tabSelectionHeight = scale( tabSelectionHeight );
contentSeparatorHeight = scale( contentSeparatorHeight );
MigLayoutVisualPadding.install( tabPane, null );
}
@@ -118,11 +147,13 @@ public class FlatTabbedPaneUI
super.uninstallDefaults();
disabledForeground = null;
selectedBackground = null;
selectedForeground = null;
underlineColor = null;
disabledUnderlineColor = null;
hoverColor = null;
focusColor = null;
tabSeparatorColor = null;
contentAreaColor = null;
MigLayoutVisualPadding.uninstall( tabPane );
@@ -135,9 +166,12 @@ public class FlatTabbedPaneUI
public void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e );
if( "JTabbedPane.hasFullBorder".equals( e.getPropertyName() ) ) {
tabPane.revalidate();
tabPane.repaint();
switch( e.getPropertyName() ) {
case TABBED_PANE_SHOW_TAB_SEPARATORS:
case TABBED_PANE_HAS_FULL_BORDER:
tabPane.revalidate();
tabPane.repaint();
break;
}
}
};
@@ -176,70 +210,45 @@ public class FlatTabbedPaneUI
@Override
protected int calculateTabWidth( int tabPlacement, int tabIndex, FontMetrics metrics ) {
return super.calculateTabWidth( tabPlacement, tabIndex, metrics ) - 3 // was added by superclass
+ (!isTopOrBottom( tabPlacement ) && isScrollTabLayout() ? contentSeparatorHeight : 0);
return super.calculateTabWidth( tabPlacement, tabIndex, metrics ) - 3 /* was added by superclass */;
}
@Override
protected int calculateTabHeight( int tabPlacement, int tabIndex, int fontHeight ) {
return Math.max( tabHeight, super.calculateTabHeight( tabPlacement, tabIndex, fontHeight ) - 2 /* was added by superclass */ )
+ (isTopOrBottom( tabPlacement ) && isScrollTabLayout() ? contentSeparatorHeight : 0);
return Math.max( tabHeight, super.calculateTabHeight( tabPlacement, tabIndex, fontHeight ) - 2 /* was added by superclass */ );
}
/**
* The content border insets are used to create a separator between tabs and content.
* Except in scroll tab policy, where the separator is painted in paintTabArea().
* If client property JTabbedPane.hasFullBorder is true, then the content border insets
* are also used for the border.
*/
@Override
protected Insets getContentBorderInsets( int tabPlacement ) {
boolean hasFullBorder = this.hasFullBorder || (tabPane.getClientProperty( "JTabbedPane.hasFullBorder" ) == Boolean.TRUE);
int sh = contentSeparatorHeight;
boolean hasFullBorder = clientPropertyBoolean( tabPane, TABBED_PANE_HAS_FULL_BORDER, this.hasFullBorder );
int sh = scale( contentSeparatorHeight );
Insets insets = hasFullBorder ? new Insets( sh, sh, sh, sh ) : new Insets( sh, 0, 0, 0 );
if( isScrollTabLayout() )
insets.top = 0;
Insets contentBorderInsets = new Insets( 0, 0, 0, 0 );
rotateInsets( insets, contentBorderInsets, tabPlacement );
return contentBorderInsets;
}
@Override
protected int getTabLabelShiftX( int tabPlacement, int tabIndex, boolean isSelected ) {
if( isScrollTabLayout() && !isTopOrBottom( tabPlacement ) ) {
float shift = contentSeparatorHeight / 2f;
return Math.round( tabPlacement == LEFT ? -shift : shift );
} else
return 0;
return 0;
}
@Override
protected int getTabLabelShiftY( int tabPlacement, int tabIndex, boolean isSelected ) {
if( isScrollTabLayout() && isTopOrBottom( tabPlacement ) ) {
float shift = contentSeparatorHeight / 2f;
return Math.round( tabPlacement == TOP ? -shift : shift );
} else
return 0;
return 0;
}
@Override
protected void paintTabArea( Graphics g, int tabPlacement, int selectedIndex ) {
if( isScrollTabLayout() ) {
// paint separator between tabs and content
Rectangle bounds = g.getClipBounds();
g.setColor( contentAreaColor );
public void update( Graphics g, JComponent c ) {
FlatUIUtils.setRenderingHints( (Graphics2D) g );
if( tabPlacement == TOP || tabPlacement == BOTTOM ) {
int y = (tabPlacement == TOP) ? bounds.y + bounds.height - contentSeparatorHeight : bounds.y;
g.fillRect( bounds.x, y, bounds.x + bounds.width, contentSeparatorHeight );
} else {
int x = (tabPlacement == LEFT) ? bounds.x + bounds.width - contentSeparatorHeight : bounds.x;
g.fillRect( x, bounds.y, contentSeparatorHeight, bounds.y + bounds.height );
}
}
super.paintTabArea( g, tabPlacement, selectedIndex );
super.update( g, c );
}
@Override
@@ -275,26 +284,15 @@ public class FlatTabbedPaneUI
protected void paintTabBackground( Graphics g, int tabPlacement, int tabIndex,
int x, int y, int w, int h, boolean isSelected )
{
if( isScrollTabLayout() ) {
// make tab bounds smaller for separator between tabs and content
if( tabPlacement == TOP || tabPlacement == BOTTOM ) {
if( tabPlacement == BOTTOM )
y += contentSeparatorHeight;
h -= contentSeparatorHeight;
} else {
if( tabPlacement == RIGHT )
x += contentSeparatorHeight;
w -= contentSeparatorHeight;
}
}
// paint tab background
boolean enabled = tabPane.isEnabled();
g.setColor( enabled && tabPane.isEnabledAt( tabIndex ) && getRolloverTab() == tabIndex
? hoverColor
: (enabled && isSelected && tabPane.hasFocus()
? focusColor
: tabPane.getBackgroundAt( tabIndex )) );
: (selectedBackground != null && enabled && isSelected
? selectedBackground
: tabPane.getBackgroundAt( tabIndex ))) );
g.fillRect( x, y, w, h );
}
@@ -302,8 +300,58 @@ public class FlatTabbedPaneUI
protected void paintTabBorder( Graphics g, int tabPlacement, int tabIndex,
int x, int y, int w, int h, boolean isSelected )
{
if( !isSelected )
return;
// paint tab separators
if( clientPropertyBoolean( tabPane, TABBED_PANE_SHOW_TAB_SEPARATORS, showTabSeparators ) &&
!isLastInRun( tabIndex ) )
{
float sepWidth = UIScale.scale( 1f );
float offset = 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 )
paintTabSelection( g, tabPlacement, x, y, w, h );
}
protected void paintTabSelection( Graphics g, int tabPlacement, int x, int y, int w, int h ) {
// increase clip bounds in scroll-tab-layout to paint over the separator line
Rectangle clipBounds = isScrollTabLayout() ? g.getClipBounds() : null;
if( clipBounds != null ) {
Rectangle newClipBounds = new Rectangle( clipBounds );
int contentSeparatorHeight = scale( this.contentSeparatorHeight );
switch( tabPlacement ) {
case TOP:
default:
newClipBounds.height += contentSeparatorHeight;
break;
case BOTTOM:
newClipBounds.y -= contentSeparatorHeight;
newClipBounds.height += contentSeparatorHeight;
break;
case LEFT:
newClipBounds.width += contentSeparatorHeight;
break;
case RIGHT:
newClipBounds.x -= contentSeparatorHeight;
newClipBounds.width += contentSeparatorHeight;
break;
}
g.setClip( newClipBounds );
}
g.setColor( tabPane.isEnabled() ? underlineColor : disabledUnderlineColor );
@@ -330,13 +378,16 @@ public class FlatTabbedPaneUI
g.fillRect( x - contentInsets.right, y, tabSelectionHeight, h );
break;
}
if( clipBounds != null )
g.setClip( clipBounds );
}
/**
* Actually does the nearly the same as super.paintContentBorder() but
* - content pane is always opaque
* Actually does nearly the same as super.paintContentBorder() but
* - not using UIManager.getColor("TabbedPane.contentAreaColor") to be GUI builder friendly
* - not invoking paintContentBorder*Edge() methods
* - repaint selection
*/
@Override
protected void paintContentBorder( Graphics g, int tabPlacement, int selectedIndex ) {
@@ -377,9 +428,34 @@ public class FlatTabbedPaneUI
h -= (y - insets.top);
}
// compute insets for separator or full border
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
Insets ci = new Insets( 0, 0, 0, 0 );
rotateInsets( hasFullBorder ? new Insets( sh, sh, sh, sh ) : new Insets( sh, 0, 0, 0 ), ci, tabPlacement );
// paint content area
g.setColor( contentAreaColor );
g.fillRect( x, y, w, h );
Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD );
path.append( new Rectangle2D.Float( x, y, w, h ), false );
path.append( new Rectangle2D.Float( x + (ci.left / 100f), y + (ci.top / 100f),
w - (ci.left / 100f) - (ci.right / 100f), h - (ci.top / 100f) - (ci.bottom / 100f) ), false );
((Graphics2D)g).fill( path );
// repaint selection in scroll-tab-layout because it may be painted before
// the content border was painted (from BasicTabbedPaneUI$ScrollableTabPanel)
if( isScrollTabLayout() && selectedIndex >= 0 ) {
Component scrollableTabViewport = findComponentByClassName( tabPane,
BasicTabbedPaneUI.class.getName() + "$ScrollableTabViewport" );
if( scrollableTabViewport != null ) {
Rectangle tabRect = getTabBounds( tabPane, selectedIndex );
Shape oldClip = g.getClip();
g.setClip( scrollableTabViewport.getBounds() );
paintTabSelection( g, tabPlacement, tabRect.x, tabRect.y, tabRect.width, tabRect.height );
g.setClip( oldClip );
}
}
}
@Override
@@ -388,11 +464,26 @@ public class FlatTabbedPaneUI
{
}
private boolean isLastInRun( int tabIndex ) {
int run = getRunForTab( tabPane.getTabCount(), tabIndex );
return lastTabInRun( tabPane.getTabCount(), run ) == tabIndex;
}
private boolean isScrollTabLayout() {
return tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT;
}
private boolean isTopOrBottom( int tabPlacement ) {
return tabPlacement == TOP || tabPlacement == BOTTOM;
private Component findComponentByClassName( Container c, String className ) {
for( Component child : c.getComponents() ) {
if( className.equals( child.getClass().getName() ) )
return child;
if( child instanceof Container ) {
Component c2 = findComponentByClassName( (Container) child, className );
if( c2 != null )
return c2;
}
}
return null;
}
}

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,6 +17,7 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
@@ -29,6 +30,7 @@ import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicTableHeaderUI;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import com.formdev.flatlaf.util.UIScale;
@@ -36,7 +38,13 @@ import com.formdev.flatlaf.util.UIScale;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.table.JTableHeader}.
*
* TODO document used UI defaults of superclass
* <!-- BasicTableHeaderUI -->
*
* @uiDefault TableHeader.font Font
* @uiDefault TableHeader.background Color
* @uiDefault TableHeader.foreground Color
*
* <!-- FlatTableHeaderUI -->
*
* @uiDefault TableHeader.separatorColor Color
* @uiDefault TableHeader.bottomSeparatorColor Color
@@ -75,8 +83,14 @@ public class FlatTableHeaderUI
@Override
public void paint( Graphics g, JComponent c ) {
// do not paint borders if JTableHeader.setDefaultRenderer() was used
boolean paintBorders = header.getDefaultRenderer().getClass().getName().equals(
"sun.swing.table.DefaultTableCellHeaderRenderer" );
TableCellRenderer defaultRenderer = header.getDefaultRenderer();
boolean paintBorders = isSystemDefaultRenderer( defaultRenderer );
if( !paintBorders && header.getColumnModel().getColumnCount() > 0 ) {
// 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 )
paintColumnBorders( g, c );
@@ -87,6 +101,12 @@ public class FlatTableHeaderUI
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 ) {
int width = c.getWidth();
int height = c.getHeight();

View File

@@ -17,6 +17,7 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import javax.swing.JComponent;
@@ -29,30 +30,84 @@ import com.formdev.flatlaf.util.UIScale;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JTable}.
*
* TODO document used UI defaults of superclass
* <!-- BasicTableUI -->
*
* @uiDefault Table.rowHeight int
* @uiDefault Table.selectionInactiveBackground Color
* @uiDefault Table.selectionInactiveForeground Color
* @uiDefault Table.font Font
* @uiDefault Table.background Color
* @uiDefault Table.foreground Color
* @uiDefault Table.selectionBackground Color
* @uiDefault Table.selectionForeground Color
* @uiDefault Table.gridColor Color
* @uiDefault Table.scrollPaneBorder Border
* @uiDefault Table.dropLineColor Color
* @uiDefault Table.dropLineShortColor Color
*
* <!-- DefaultTableCellRenderer -->
*
* @uiDefault Table.cellNoFocusBorder Border
* @uiDefault Table.focusCellHighlightBorder Border
* @uiDefault Table.focusSelectedCellHighlightBorder Border
* @uiDefault Table.dropCellBackground Color
* @uiDefault Table.dropCellForeground Color
* @uiDefault Table.alternateRowColor Color
* @uiDefault Table.focusCellBackground Color
* @uiDefault Table.focusCellForeground Color
*
* <!-- FlatTableUI -->
*
* @uiDefault Table.rowHeight int
* @uiDefault Table.showHorizontalLines boolean
* @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
*/
public class FlatTableUI
extends BasicTableUI
{
protected boolean showHorizontalLines;
protected boolean showVerticalLines;
protected Dimension intercellSpacing;
protected Color selectionBackground;
protected Color selectionForeground;
protected Color selectionInactiveBackground;
protected Color selectionInactiveForeground;
private boolean oldShowHorizontalLines;
private boolean oldShowVerticalLines;
private Dimension oldIntercellSpacing;
public static ComponentUI createUI( JComponent c ) {
return new FlatTableUI();
}
@Override
public void installUI( JComponent c ) {
super.installUI( c );
}
@Override
public void uninstallUI( JComponent c ) {
super.uninstallUI( c );
}
@Override
protected void installDefaults() {
super.installDefaults();
showHorizontalLines = UIManager.getBoolean( "Table.showHorizontalLines" );
showVerticalLines = UIManager.getBoolean( "Table.showVerticalLines" );
intercellSpacing = UIManager.getDimension( "Table.intercellSpacing" );
selectionBackground = UIManager.getColor( "Table.selectionBackground" );
selectionForeground = UIManager.getColor( "Table.selectionForeground" );
selectionInactiveBackground = UIManager.getColor( "Table.selectionInactiveBackground" );
@@ -63,6 +118,20 @@ public class FlatTableUI
int rowHeight = FlatUIUtils.getUIInt( "Table.rowHeight", 16 );
if( rowHeight > 0 )
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
@@ -73,6 +142,16 @@ public class FlatTableUI
selectionForeground = null;
selectionInactiveBackground = 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

View File

@@ -21,6 +21,7 @@ import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JComponent;
import javax.swing.JTextArea;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
@@ -30,11 +31,25 @@ import javax.swing.text.JTextComponent;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JTextArea}.
*
* TODO document used UI defaults of superclass
* <!-- BasicTextAreaUI -->
*
* @uiDefault TextArea.font Font
* @uiDefault TextArea.background Color
* @uiDefault TextArea.foreground Color also used if not editable
* @uiDefault TextArea.caretForeground Color
* @uiDefault TextArea.selectionBackground Color
* @uiDefault TextArea.selectionForeground Color
* @uiDefault TextArea.inactiveForeground Color used if not enabled (yes, this is confusing; this should be named disabledForeground)
* @uiDefault TextArea.border Border
* @uiDefault TextArea.margin Insets
* @uiDefault TextArea.caretBlinkRate int default is 500 milliseconds
*
* <!-- FlatTextAreaUI -->
*
* @uiDefault Component.minimumWidth int
* @uiDefault TextArea.disabledBackground Color
* @uiDefault TextArea.inactiveBackground Color
* @uiDefault Component.isIntelliJTheme boolean
* @uiDefault TextArea.disabledBackground Color used if not enabled
* @uiDefault TextArea.inactiveBackground Color used if not editable
*
* @author Karl Tauber
*/
@@ -42,6 +57,7 @@ public class FlatTextAreaUI
extends BasicTextAreaUI
{
protected int minimumWidth;
protected boolean isIntelliJTheme;
protected Color disabledBackground;
protected Color inactiveBackground;
@@ -54,6 +70,7 @@ public class FlatTextAreaUI
super.installDefaults();
minimumWidth = UIManager.getInt( "Component.minimumWidth" );
isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" );
disabledBackground = UIManager.getColor( "TextArea.disabledBackground" );
inactiveBackground = UIManager.getColor( "TextArea.inactiveBackground" );
}
@@ -73,27 +90,34 @@ public class FlatTextAreaUI
Color background = c.getBackground();
g.setColor( !(background instanceof UIResource)
? background
: (!c.isEnabled()
? disabledBackground
: (!c.isEditable() ? inactiveBackground : background)) );
: (isIntelliJTheme && (!c.isEnabled() || !c.isEditable())
? FlatUIUtils.getParentBackground( c )
: (!c.isEnabled()
? disabledBackground
: (!c.isEditable() ? inactiveBackground : background))) );
g.fillRect( 0, 0, c.getWidth(), c.getHeight() );
}
@Override
public Dimension getPreferredSize( JComponent c ) {
return applyMinimumWidth( super.getPreferredSize( c ) );
return applyMinimumWidth( super.getPreferredSize( c ), c );
}
@Override
public Dimension getMinimumSize( JComponent c ) {
return applyMinimumWidth( super.getMinimumSize( c ) );
return applyMinimumWidth( super.getMinimumSize( c ), c );
}
private Dimension applyMinimumWidth( Dimension size ) {
private Dimension applyMinimumWidth( Dimension size, JComponent c ) {
// do not apply minimum width if JTextArea.columns is set
if( c instanceof JTextArea && ((JTextArea)c).getColumns() > 0 )
return size;
// Assume that text area is in a scroll pane (that displays the border)
// and subtract 1px border line width.
// 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.
int minimumWidth = FlatUIUtils.minimumWidth( getComponent(), this.minimumWidth );
size.width = Math.max( size.width, scale( minimumWidth ) - (scale( 1 ) * 2) );
return size;
}

View File

@@ -17,27 +17,51 @@
package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.FocusEvent;
import java.awt.Insets;
import java.awt.event.FocusListener;
import java.beans.PropertyChangeEvent;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.LookAndFeel;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTextFieldUI;
import javax.swing.text.JTextComponent;
import com.formdev.flatlaf.FlatClientProperties;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JTextField}.
*
* TODO document used UI defaults of superclass
* <!-- BasicTextFieldUI -->
*
* @uiDefault TextField.font Font
* @uiDefault TextField.background Color
* @uiDefault TextField.foreground Color also used if not editable
* @uiDefault TextField.caretForeground Color
* @uiDefault TextField.selectionBackground Color
* @uiDefault TextField.selectionForeground Color
* @uiDefault TextField.disabledBackground Color used if not enabled
* @uiDefault TextField.inactiveBackground Color used if not editable
* @uiDefault TextField.inactiveForeground Color used if not enabled (yes, this is confusing; this should be named disabledForeground)
* @uiDefault TextField.border Border
* @uiDefault TextField.margin Insets
* @uiDefault TextField.caretBlinkRate int default is 500 milliseconds
*
* <!-- FlatTextFieldUI -->
*
* @uiDefault Component.focusWidth int
* @uiDefault Component.minimumWidth int
* @uiDefault Component.isIntelliJTheme boolean
* @uiDefault TextField.placeholderForeground Color
*
* @author Karl Tauber
*/
@@ -46,8 +70,10 @@ public class FlatTextFieldUI
{
protected int focusWidth;
protected int minimumWidth;
protected boolean isIntelliJTheme;
protected Color placeholderForeground;
private Handler handler;
private FocusListener focusListener;
public static ComponentUI createUI( JComponent c ) {
return new FlatTextFieldUI();
@@ -57,8 +83,13 @@ public class FlatTextFieldUI
protected void installDefaults() {
super.installDefaults();
String prefix = getPropertyPrefix();
focusWidth = UIManager.getInt( "Component.focusWidth" );
minimumWidth = UIManager.getInt( "Component.minimumWidth" );
isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" );
placeholderForeground = UIManager.getColor( prefix + ".placeholderForeground" );
LookAndFeel.installProperty( getComponent(), "opaque", focusWidth == 0 );
MigLayoutVisualPadding.install( getComponent(), focusWidth );
}
@@ -67,6 +98,8 @@ public class FlatTextFieldUI
protected void uninstallDefaults() {
super.uninstallDefaults();
placeholderForeground = null;
MigLayoutVisualPadding.uninstall( getComponent() );
}
@@ -74,43 +107,95 @@ public class FlatTextFieldUI
protected void installListeners() {
super.installListeners();
getComponent().addFocusListener( getHandler() );
focusListener = new FlatUIUtils.RepaintFocusListener( getComponent() );
getComponent().addFocusListener( focusListener );
}
@Override
protected void uninstallListeners() {
super.uninstallListeners();
getComponent().removeFocusListener( getHandler() );
handler = null;
getComponent().removeFocusListener( focusListener );
focusListener = null;
}
public Handler getHandler() {
if( handler == null )
handler = new Handler();
return handler;
@Override
protected void propertyChange( PropertyChangeEvent e ) {
super.propertyChange( e );
if( FlatClientProperties.PLACEHOLDER_TEXT.equals( e.getPropertyName() ) )
getComponent().repaint();
}
@Override
protected void paintSafely( Graphics g ) {
paintBackground( g, getComponent(), focusWidth, isIntelliJTheme );
paintPlaceholder( g, getComponent(), placeholderForeground );
super.paintSafely( g );
}
@Override
protected void paintBackground( Graphics g ) {
JTextComponent c = getComponent();
// background is painted elsewhere
}
FlatUIUtils.paintParentBackground( g, c );
static void paintBackground( Graphics g, JTextComponent c, int focusWidth, boolean isIntelliJTheme ) {
// do not paint background if:
// - not opaque and
// - border is not a flat border and
// - opaque was explicitly set (to false)
// (same behaviour as in AquaTextFieldUI)
if( !c.isOpaque() && !(c.getBorder() instanceof FlatBorder) && FlatUIUtils.hasOpaqueBeenExplicitlySet( c ) )
return;
// fill background if opaque to avoid garbage if user sets opaque to true
if( c.isOpaque() && focusWidth > 0 )
FlatUIUtils.paintParentBackground( g, c );
// paint background
Graphics2D g2 = (Graphics2D) g.create();
try {
FlatUIUtils.setRenderingHints( g2 );
float focusWidth = (c.getBorder() instanceof FlatBorder) ? scale( (float) this.focusWidth ) : 0;
float fFocusWidth = (c.getBorder() instanceof FlatBorder) ? scale( (float) focusWidth ) : 0;
g2.setColor( c.getBackground() );
FlatUIUtils.fillRoundRectangle( g2, 0, 0, c.getWidth(), c.getHeight(), focusWidth, 0 );
Color background = c.getBackground();
g2.setColor( !(background instanceof UIResource)
? background
: (isIntelliJTheme && (!c.isEnabled() || !c.isEditable())
? FlatUIUtils.getParentBackground( c )
: background) );
FlatUIUtils.paintComponentBackground( g2, 0, 0, c.getWidth(), c.getHeight(), fFocusWidth, 0 );
} finally {
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
public Dimension getPreferredSize( JComponent c ) {
return applyMinimumWidth( super.getPreferredSize( c ), c );
@@ -122,29 +207,19 @@ public class FlatTextFieldUI
}
private Dimension applyMinimumWidth( Dimension size, JComponent c ) {
// do not apply minimum width if JTextField.columns is set
if( c instanceof JTextField && ((JTextField)c).getColumns() > 0 )
return size;
Container parent = c.getParent();
if( parent instanceof JComboBox ||
parent instanceof JSpinner ||
(parent != null && parent.getParent() instanceof JSpinner) )
return size;
int minimumWidth = FlatUIUtils.minimumWidth( getComponent(), this.minimumWidth );
int focusWidth = (c.getBorder() instanceof FlatBorder) ? this.focusWidth : 0;
size.width = Math.max( size.width, scale( minimumWidth + (focusWidth * 2) ) );
return size;
}
//---- class Handler ------------------------------------------------------
private class Handler
implements FocusListener
{
@Override
public void focusGained( FocusEvent e ) {
getComponent().repaint();
}
@Override
public void focusLost( FocusEvent e ) {
getComponent().repaint();
}
}
}

View File

@@ -18,17 +18,37 @@ package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTextPaneUI;
import javax.swing.text.JTextComponent;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JTextPane}.
*
* TODO document used UI defaults of superclass
* <!-- BasicTextPaneUI -->
*
* @uiDefault TextPane.font Font
* @uiDefault TextPane.background Color
* @uiDefault TextPane.foreground Color also used if not editable
* @uiDefault TextPane.caretForeground Color
* @uiDefault TextPane.selectionBackground Color
* @uiDefault TextPane.selectionForeground Color
* @uiDefault TextPane.disabledBackground Color used if not enabled
* @uiDefault TextPane.inactiveBackground Color used if not editable
* @uiDefault TextPane.inactiveForeground Color used if not enabled (yes, this is confusing; this should be named disabledForeground)
* @uiDefault TextPane.border Border
* @uiDefault TextPane.margin Insets
* @uiDefault TextPane.caretBlinkRate int default is 500 milliseconds
*
* <!-- FlatTextPaneUI -->
*
* @uiDefault Component.minimumWidth int
* @uiDefault Component.isIntelliJTheme boolean
*
* @author Karl Tauber
*/
@@ -36,6 +56,9 @@ public class FlatTextPaneUI
extends BasicTextPaneUI
{
protected int minimumWidth;
protected boolean isIntelliJTheme;
private Object oldHonorDisplayProperties;
public static ComponentUI createUI( JComponent c ) {
return new FlatTextPaneUI();
@@ -46,6 +69,18 @@ public class FlatTextPaneUI
super.installDefaults();
minimumWidth = UIManager.getInt( "Component.minimumWidth" );
isIntelliJTheme = UIManager.getBoolean( "Component.isIntelliJTheme" );
// use component font and foreground for HTML text
oldHonorDisplayProperties = getComponent().getClientProperty( JEditorPane.HONOR_DISPLAY_PROPERTIES );
getComponent().putClientProperty( JEditorPane.HONOR_DISPLAY_PROPERTIES, true );
}
@Override
protected void uninstallDefaults() {
super.uninstallDefaults();
getComponent().putClientProperty( JEditorPane.HONOR_DISPLAY_PROPERTIES, oldHonorDisplayProperties );
}
@Override
@@ -63,7 +98,21 @@ public class FlatTextPaneUI
// and subtract 1px border line width.
// 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.
int minimumWidth = FlatUIUtils.minimumWidth( getComponent(), this.minimumWidth );
size.width = Math.max( size.width, scale( minimumWidth ) - (scale( 1 ) * 2) );
return size;
}
@Override
protected void paintBackground( Graphics g ) {
JTextComponent c = getComponent();
// for compatibility with IntelliJ themes
if( isIntelliJTheme && (!c.isEnabled() || !c.isEditable()) && (c.getBackground() instanceof UIResource) ) {
FlatUIUtils.paintParentBackground( g, c );
return;
}
super.paintBackground( g );
}
}

View File

@@ -16,29 +16,57 @@
package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.FlatClientProperties.*;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import javax.swing.AbstractButton;
import javax.swing.ButtonModel;
import javax.swing.JComponent;
import javax.swing.JToggleButton;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import com.formdev.flatlaf.util.UIScale;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JToggleButton}.
*
* TODO document used UI defaults of superclass
* <!-- BasicButtonUI -->
*
* @uiDefault ToggleButton.font Font
* @uiDefault ToggleButton.background Color
* @uiDefault ToggleButton.foreground Color
* @uiDefault ToggleButton.border Border
* @uiDefault ToggleButton.margin Insets
* @uiDefault ToggleButton.rollover boolean
*
* <!-- FlatButtonUI -->
*
* @uiDefault Component.focusWidth int
* @uiDefault ToggleButton.arc int
* @uiDefault Button.arc int
* @uiDefault ToggleButton.minimumWidth 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.disabledText Color
* @uiDefault ToggleButton.toolbar.hoverBackground Color
* @uiDefault ToggleButton.toolbar.pressedBackground Color
*
* <!-- FlatToggleButtonUI -->
*
* @uiDefault ToggleButton.selectedBackground Color
* @uiDefault ToggleButton.selectedForeground Color
* @uiDefault ToggleButton.disabledSelectedBackground Color
* @uiDefault ToggleButton.toolbar.hoverBackground Color
* @uiDefault ToggleButton.toolbar.pressedBackground Color
* @uiDefault ToggleButton.toolbar.selectedBackground Color
*
* @uiDefault ToggleButton.underline.underlineHeight int
* @uiDefault ToggleButton.underline.underlineColor Color
* @uiDefault ToggleButton.underline.disabledUnderlineColor Color
* @uiDefault ToggleButton.underline.selectedBackground Color optional
* @uiDefault ToggleButton.underline.hoverBackground Color
* @uiDefault ToggleButton.underline.focusBackground Color
*
*
* @author Karl Tauber
*/
@@ -51,6 +79,13 @@ public class FlatToggleButtonUI
protected Color toolbarSelectedBackground;
protected int underlineHeight;
protected Color underlineColor;
protected Color disabledUnderlineColor;
protected Color underlineSelectedBackground;
protected Color underlineHoverBackground;
protected Color underlineFocusBackground;
private boolean defaults_initialized = false;
private static ComponentUI instance;
@@ -77,6 +112,13 @@ public class FlatToggleButtonUI
toolbarSelectedBackground = UIManager.getColor( "ToggleButton.toolbar.selectedBackground" );
underlineHeight = UIManager.getInt( "ToggleButton.underline.underlineHeight" );
underlineColor = UIManager.getColor( "ToggleButton.underline.underlineColor" );
disabledUnderlineColor = UIManager.getColor( "ToggleButton.underline.disabledUnderlineColor" );
underlineSelectedBackground = UIManager.getColor( "ToggleButton.underline.selectedBackground" );
underlineHoverBackground = UIManager.getColor( "ToggleButton.underline.hoverBackground" );
underlineFocusBackground = UIManager.getColor( "ToggleButton.underline.focusBackground" );
defaults_initialized = true;
}
}
@@ -87,6 +129,36 @@ public class FlatToggleButtonUI
defaults_initialized = false;
}
static boolean isUnderlineButton( Component c ) {
return c instanceof JToggleButton && clientPropertyEquals( (JToggleButton) c, BUTTON_TYPE, BUTTON_TYPE_UNDERLINE );
}
@Override
protected void paintBackground( Graphics g, JComponent c ) {
if( isUnderlineButton( c ) ) {
int height = c.getHeight();
int width = c.getWidth();
boolean selected = ((AbstractButton)c).isSelected();
// paint background
Color background = buttonStateColor( c,
selected ? underlineSelectedBackground : null,
null, underlineFocusBackground, underlineHoverBackground, null );
if( background != null ) {
g.setColor( background );
g.fillRect( 0, 0, width, height );
}
// paint underline if selected
if( selected ) {
int underlineHeight = UIScale.scale( this.underlineHeight );
g.setColor( c.isEnabled() ? underlineColor : disabledUnderlineColor );
g.fillRect( 0, height - underlineHeight, width, underlineHeight );
}
} else
super.paintBackground( g, c );
}
@Override
protected Color getBackground( JComponent c ) {
ButtonModel model = ((AbstractButton)c).getModel();

View File

@@ -33,6 +33,8 @@ import javax.swing.plaf.basic.BasicToolBarSeparatorUI;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JToolBar.Separator}.
*
* <!-- FlatToolBarSeparatorUI -->
*
* @uiDefault ToolBar.separatorWidth int
* @uiDefault ToolBar.separatorColor Color
*

View File

@@ -19,6 +19,8 @@ package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.util.UIScale.scale;
import java.awt.Component;
import java.awt.Insets;
import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener;
import javax.swing.AbstractButton;
import javax.swing.JComponent;
import javax.swing.UIManager;
@@ -31,7 +33,19 @@ import javax.swing.plaf.basic.BasicToolBarUI;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JToolBar}.
*
* TODO document used UI defaults of superclass
* <!-- BasicToolBarUI -->
*
* @uiDefault ToolBar.font Font
* @uiDefault ToolBar.background Color
* @uiDefault ToolBar.foreground Color
* @uiDefault ToolBar.border Border
* @uiDefault ToolBar.dockingBackground Color
* @uiDefault ToolBar.dockingForeground Color
* @uiDefault ToolBar.floatingBackground Color
* @uiDefault ToolBar.floatingForeground Color
* @uiDefault ToolBar.isRollover boolean
*
* <!-- FlatToolBarUI -->
*
* @uiDefault ToolBar.buttonMargins Insets
*
@@ -53,6 +67,29 @@ public class FlatToolBarUI
rolloverBorder = null;
}
@Override
protected ContainerListener createToolBarContListener() {
return new ToolBarContListener() {
@Override
public void componentAdded( ContainerEvent e ) {
super.componentAdded( e );
Component c = e.getChild();
if( c instanceof AbstractButton )
c.setFocusable( false );
}
@Override
public void componentRemoved( ContainerEvent e ) {
super.componentRemoved( e );
Component c = e.getChild();
if( c instanceof AbstractButton )
c.setFocusable( true );
}
};
}
@Override
protected Border createRolloverBorder() {
return getRolloverBorder();

View File

@@ -21,22 +21,35 @@ import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.beans.PropertyChangeListener;
import java.util.List;
import javax.swing.JComponent;
import javax.swing.JToolTip;
import javax.swing.SwingUtilities;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicToolTipUI;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.util.StringUtils;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JToolTip}.
*
* <!-- BasicToolTipUI -->
*
* @uiDefault ToolTip.font Font
* @uiDefault ToolTip.background Color
* @uiDefault ToolTip.foreground Color
* @uiDefault ToolTip.backgroundInactive Color
* @uiDefault ToolTip.foregroundInactive Color
* @uiDefault ToolTip.border Border
* @uiDefault ToolTip.borderInactive Border
*
* @author Karl Tauber
*/
public class FlatToolTipUI
extends BasicToolTipUI
{
private static PropertyChangeListener sharedPropertyChangedListener;
private static ComponentUI instance;
public static ComponentUI createUI( JComponent c ) {
@@ -45,13 +58,45 @@ public class FlatToolTipUI
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
public Dimension getPreferredSize( JComponent c ) {
if( isMultiLine( c ) ) {
FontMetrics fm = c.getFontMetrics( c.getFont() );
Insets insets = c.getInsets();
List<String> lines = FlatLaf.split( ((JToolTip)c).getTipText(), '\n' );
List<String> lines = StringUtils.split( ((JToolTip)c).getTipText(), '\n' );
int width = 0;
int height = fm.getHeight() * Math.max( lines.size(), 1 );
for( String line : lines )
@@ -71,14 +116,17 @@ public class FlatToolTipUI
FlatUIUtils.setRenderingHints( (Graphics2D) g );
g.setColor( c.getForeground() );
List<String> lines = FlatLaf.split( ((JToolTip)c).getTipText(), '\n' );
List<String> lines = StringUtils.split( ((JToolTip)c).getTipText(), '\n' );
int x = insets.left;
int x2 = c.getWidth() - insets.right;
int y = insets.top - fm.getDescent();
int lineHeight = fm.getHeight();
JComponent comp = ((JToolTip)c).getComponent();
boolean leftToRight = (comp != null ? comp : c).getComponentOrientation().isLeftToRight();
for( String line : lines ) {
y += lineHeight;
g.drawString( line, x, y );
FlatUIUtils.drawString( c, g, line, leftToRight ? x : x2 - SwingUtilities.computeStringWidth( fm, line ), y );
}
} else
super.paint( g, c );

View File

@@ -21,9 +21,16 @@ import java.awt.Component;
import java.awt.Graphics;
import java.awt.Insets;
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.JTree;
import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.JTree.DropLocation;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicTreeUI;
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}.
*
* 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.selectionBackground Color
* @uiDefault Tree.selectionForeground Color
* @uiDefault Tree.selectionInactiveBackground Color
* @uiDefault Tree.selectionInactiveForeground Color
* @uiDefault Tree.wideSelection boolean
* @uiDefault Tree.showCellFocusIndicator boolean
*
* @author Karl Tauber
*/
@@ -50,6 +94,9 @@ public class FlatTreeUI
protected Color selectionForeground;
protected Color selectionInactiveBackground;
protected Color selectionInactiveForeground;
protected Color selectionBorderColor;
protected boolean wideSelection;
protected boolean showCellFocusIndicator;
public static ComponentUI createUI( JComponent c ) {
return new FlatTreeUI();
@@ -65,6 +112,9 @@ public class FlatTreeUI
selectionForeground = UIManager.getColor( "Tree.selectionForeground" );
selectionInactiveBackground = UIManager.getColor( "Tree.selectionInactiveBackground" );
selectionInactiveForeground = UIManager.getColor( "Tree.selectionInactiveForeground" );
selectionBorderColor = UIManager.getColor( "Tree.selectionBorderColor" );
wideSelection = UIManager.getBoolean( "Tree.wideSelection" );
showCellFocusIndicator = UIManager.getBoolean( "Tree.showCellFocusIndicator" );
// scale
int rowHeight = FlatUIUtils.getUIInt( "Tree.rowHeight", 16 );
@@ -84,21 +134,115 @@ public class FlatTreeUI
selectionForeground = null;
selectionInactiveBackground = 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
protected void paintRow( Graphics g, Rectangle clipBounds, Insets insets, Rectangle bounds, TreePath path, int row,
boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf )
protected void paintRow( Graphics g, Rectangle clipBounds, Insets insets, Rectangle bounds,
TreePath path, int row, boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf )
{
if( editingComponent != null && editingRow == row )
return;
boolean isEditing = (editingComponent != null && editingRow == row);
boolean hasFocus = tree.hasFocus();
boolean cellHasFocus = hasFocus && (row == getLeadSelectionRow());
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
Component rendererComponent = currentCellRenderer.getTreeCellRendererComponent( tree,
@@ -106,7 +250,7 @@ public class FlatTreeUI
// apply inactive selection background/foreground if tree is not focused
Color oldBackgroundSelectionColor = null;
if( isSelected && !hasFocus ) {
if( isSelected && !hasFocus && !isDropRow ) {
if( rendererComponent instanceof DefaultTreeCellRenderer ) {
DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer) rendererComponent;
if( renderer.getBackgroundSelectionColor() == selectionBackground ) {
@@ -122,11 +266,43 @@ public class FlatTreeUI
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
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 )
((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

@@ -17,13 +17,17 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Path2D;
@@ -31,8 +35,12 @@ import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.util.function.Consumer;
import javax.swing.JComponent;
import javax.swing.LookAndFeel;
import javax.swing.UIManager;
import javax.swing.plaf.ColorUIResource;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.util.DerivedColor;
import com.formdev.flatlaf.util.HiDPIUtils;
import com.formdev.flatlaf.util.JavaCompatibility;
import com.formdev.flatlaf.util.UIScale;
@@ -61,6 +69,20 @@ public class FlatUIUtils
r.height - insets.top - insets.bottom );
}
public static Dimension addInsets( Dimension dim, Insets insets ) {
return new Dimension(
dim.width + insets.left + insets.right,
dim.height + insets.top + insets.bottom );
}
public static Insets addInsets( Insets insets1, Insets insets2 ) {
return new Insets(
insets1.top + insets2.top,
insets1.left + insets2.left,
insets1.bottom + insets2.bottom,
insets1.right + insets2.right );
}
public static Color getUIColor( String key, int defaultColorRGB ) {
Color color = UIManager.getColor( key );
return (color != null) ? color : new Color( defaultColorRGB );
@@ -71,15 +93,34 @@ public class FlatUIUtils
return (color != null) ? color : defaultColor;
}
public static Color getUIColor( String key, String defaultKey ) {
Color color = UIManager.getColor( key );
return (color != null) ? color : UIManager.getColor( defaultKey );
}
public static int getUIInt( String key, int defaultValue ) {
Object value = UIManager.get( key );
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 ) {
return (c instanceof ColorUIResource) ? new Color( c.getRGB(), true ) : c;
}
public static int minimumWidth( JComponent c, int minimumWidth ) {
Object p = c.getClientProperty( FlatClientProperties.MINIMUM_WIDTH );
return (p instanceof Integer) ? ((Integer)p).intValue() : minimumWidth;
}
public static boolean isTableCellEditor( Component c ) {
return c instanceof JComponent && Boolean.TRUE.equals( ((JComponent)c).getClientProperty( "JComboBox.isTableCellEditor" ) );
}
/**
* Sets rendering hints used for painting.
*/
@@ -89,10 +130,80 @@ public class FlatUIUtils
MAC_USE_QUARTZ ? RenderingHints.VALUE_STROKE_PURE : RenderingHints.VALUE_STROKE_NORMALIZE );
}
public static void setColor( Graphics g, Color color, Color baseColor ) {
if( color instanceof DerivedColor )
color = ((DerivedColor)color).derive( baseColor );
g.setColor( color );
}
/**
* 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 )
{
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, systemScaleFactor,
(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 outerRadius = (arc > 0) ? arc + focusWidth - UIScale.scale( 2f ) : focusWidth;
float ow = focusWidth + lineWidth;
float innerRadius = outerRadius - ow;
Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD );
path.append( createRoundRectanglePath( x, y, width, height, outerRadius, outerRadius, outerRadius, outerRadius ), false );
path.append( createRoundRectanglePath( x + ow, y + ow, width - (ow * 2), height - (ow * 2), innerRadius, innerRadius, innerRadius, innerRadius ), 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, systemScaleFactor,
(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 ? arc - lineWidth : 0f;
@@ -111,9 +222,33 @@ 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 )
{
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, systemScaleFactor,
(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 )
{
g.fill( new RoundRectangle2D.Float(
@@ -133,6 +268,16 @@ public class FlatUIUtils
}
}
/**
* Gets the background color of the first opaque parent.
*/
public static Color getParentBackground( JComponent c ) {
Container parent = findOpaqueParent( c );
return (parent != null)
? parent.getBackground()
: UIManager.getColor( "Panel.background" ); // fallback, probably never used
}
/**
* Find the first parent that is opaque.
*/
@@ -145,45 +290,74 @@ 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,
float focusWidth, float lineWidth, float arc )
{
float outerArc = (arc > 0) ? arc + focusWidth - UIScale.scale( 2f ) : focusWidth;
float ow = focusWidth + lineWidth;
public static Path2D createRectangle( float x, float y, float width, float height, float lineWidth ) {
Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD );
path.append( createOutlinePath( x, y, width, height, outerArc ), false );
path.append( createOutlinePath( x + ow, y + ow, width - (ow * 2), height - (ow * 2), outerArc - ow ), false );
g.fill( path );
path.append( new Rectangle2D.Float( x, y, width, height ), false );
path.append( new Rectangle2D.Float( x + lineWidth, y + lineWidth,
width - (lineWidth * 2), height - (lineWidth * 2) ), false );
return path;
}
private static Shape createOutlinePath( float x, float y, float width, float height, float arc ) {
if( arc <= 0 )
/**
* Creates a not-filled rounded rectangle shape and allows specifying the line width and the radius or each corner.
*/
public static Path2D createRoundRectangle( float x, float y, float width, float height,
float lineWidth, float arcTopLeft, float arcTopRight, float arcBottomLeft, float arcBottomRight )
{
Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD );
path.append( createRoundRectanglePath( x, y, width, height, arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight ), false );
path.append( createRoundRectanglePath( x + lineWidth, y + lineWidth, width - (lineWidth * 2), height - (lineWidth * 2),
arcTopLeft - lineWidth, arcTopRight - lineWidth, arcBottomLeft - lineWidth, arcBottomRight - lineWidth ), false );
return path;
}
/**
* 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,
float arcTopLeft, float arcTopRight, float arcBottomLeft, float arcBottomRight )
{
if( arcTopLeft <= 0 && arcTopRight <= 0 && arcBottomLeft <= 0 && arcBottomRight <= 0 )
return new Rectangle2D.Float( x, y, width, height );
if( arcTopLeft < 0 )
arcTopLeft = 0;
if( arcTopRight < 0 )
arcTopRight = 0;
if( arcBottomLeft < 0 )
arcBottomLeft = 0;
if( arcBottomRight < 0 )
arcBottomRight = 0;
float x2 = x + width;
float y2 = y + height;
Path2D rect = new Path2D.Float();
rect.moveTo( x2 - arc, y );
rect.quadTo( x2, y, x2, y + arc );
rect.lineTo( x2, y2 - arc );
rect.quadTo( x2, y2, x2 - arc, y2 );
rect.lineTo( x + arc, y2 );
rect.quadTo( x, y2, x, y2 - arc );
rect.lineTo( x, y + arc );
rect.quadTo( x, y, x + arc, y );
rect.moveTo( x2 - arcTopRight, y );
rect.quadTo( x2, y, x2, y + arcTopRight );
rect.lineTo( x2, y2 - arcBottomRight );
rect.quadTo( x2, y2, x2 - arcBottomRight, y2 );
rect.lineTo( x + arcBottomLeft, y2 );
rect.quadTo( x, y2, x, y2 - arcBottomLeft );
rect.lineTo( x, y + arcTopLeft );
rect.quadTo( x, y, x + arcTopLeft, y );
rect.closePath();
return rect;
}
/**
* Creates a closed path for the given points.
*/
public static Path2D createPath( double... points ) {
return createPath( true, points );
}
/**
* Creates a open or closed path for the given points.
*/
public static Path2D createPath( boolean close, double... points ) {
Path2D path = new Path2D.Float();
path.moveTo( points[0], points[1] );
@@ -195,6 +369,22 @@ public class FlatUIUtils
}
/**
* Draws the given string at the specified location using text properties
* and anti-aliasing hints from the provided component.
*
* Use this method instead of Graphics.drawString() for correct anti-aliasing.
*
* Replacement for SwingUtilities2.drawString()
*/
public static void drawString( JComponent c, Graphics g, String text, int x, int y ) {
JavaCompatibility.drawStringUnderlineCharAt( c, g, text, -1, x, y );
}
/**
* Draws the given string at the specified location underlining the specified
* character. The provided component is used to query text properties and
* anti-aliasing hints.
*
* Replacement for SwingUtilities2.drawStringUnderlineCharAt()
*/
public static void drawStringUnderlineCharAt( JComponent c, Graphics g,
@@ -203,15 +393,23 @@ public class FlatUIUtils
JavaCompatibility.drawStringUnderlineCharAt( c, g, text, underlinedIndex, x, y );
}
public static boolean hasOpaqueBeenExplicitlySet( JComponent c ) {
boolean oldOpaque = c.isOpaque();
LookAndFeel.installProperty( c, "opaque", !oldOpaque );
boolean explicitlySet = c.isOpaque() == oldOpaque;
LookAndFeel.installProperty( c, "opaque", oldOpaque );
return explicitlySet;
}
//---- class HoverListener ------------------------------------------------
public static class HoverListener
extends MouseAdapter
{
private final JComponent repaintComponent;
private final Component repaintComponent;
private final Consumer<Boolean> hoverChanged;
public HoverListener( JComponent repaintComponent, Consumer<Boolean> hoverChanged ) {
public HoverListener( Component repaintComponent, Consumer<Boolean> hoverChanged ) {
this.repaintComponent = repaintComponent;
this.hoverChanged = hoverChanged;
}
@@ -233,4 +431,26 @@ public class FlatUIUtils
repaintComponent.repaint();
}
}
//---- class RepaintFocusListener -----------------------------------------
public static class RepaintFocusListener
implements FocusListener
{
private final Component repaintComponent;
public RepaintFocusListener( Component repaintComponent ) {
this.repaintComponent = repaintComponent;
}
@Override
public void focusGained( FocusEvent e ) {
repaintComponent.repaint();
}
@Override
public void focusLost( FocusEvent e ) {
repaintComponent.repaint();
}
}
}

View File

@@ -27,6 +27,12 @@ import javax.swing.plaf.basic.BasicViewportUI;
/**
* Provides the Flat LaF UI delegate for {@link javax.swing.JViewport}.
*
* <!-- BasicViewportUI -->
*
* @uiDefault Viewport.font Font unused
* @uiDefault Viewport.background Color
* @uiDefault Viewport.foreground Color unused
*
* @author Karl Tauber
*/
public class FlatViewportUI

View File

@@ -0,0 +1,102 @@
/*
* 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.Color;
/**
* Functions that modify colors.
*
* @author Karl Tauber
*/
public class ColorFunctions
{
public static Color applyFunctions( Color color, ColorFunction... functions ) {
float[] hsl = HSLColor.fromRGB( color );
float alpha = color.getAlpha() / 255f;
for( ColorFunction function : functions )
function.apply( hsl );
return HSLColor.toRGB( hsl, alpha );
}
private static float clamp( float value ) {
return (value < 0)
? 0
: ((value > 100)
? 100
: value);
}
//---- interface ColorFunction --------------------------------------------
public interface ColorFunction {
void apply( float[] hsl );
}
//---- class Lighten ------------------------------------------------------
/**
* Increase the lightness of a color in the HSL color space by an absolute
* or relative amount.
*/
public static class Lighten
implements ColorFunction
{
private final float amount;
private final boolean relative;
private final boolean autoInverse;
public Lighten( float amount, boolean relative, boolean autoInverse ) {
this.amount = amount;
this.relative = relative;
this.autoInverse = autoInverse;
}
@Override
public void apply( float[] hsl ) {
float amount2 = autoInverse && shouldInverse( hsl ) ? -amount : amount;
hsl[2] = clamp( relative
? (hsl[2] * ((100 + amount2) / 100))
: (hsl[2] + amount2) );
}
protected boolean shouldInverse( float[] hsl ) {
return hsl[2] >= 50;
}
}
//---- class Darken -------------------------------------------------------
/**
* Decrease the lightness of a color in the HSL color space by an absolute
* or relative amount.
*/
public static class Darken
extends Lighten
{
public Darken( float amount, boolean relative, boolean autoInverse ) {
super( -amount, relative, autoInverse );
}
@Override
protected boolean shouldInverse( float[] hsl ) {
return hsl[2] < 50;
}
}
}

View File

@@ -0,0 +1,43 @@
/*
* 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.Color;
import javax.swing.plaf.ColorUIResource;
import com.formdev.flatlaf.util.ColorFunctions.ColorFunction;
/**
* A (red) color that acts as a placeholder in UI defaults.
* The actual color is derived from another color,
* which is modified by the given color functions.
*
* @author Karl Tauber
*/
public class DerivedColor
extends ColorUIResource
{
private final ColorFunction[] functions;
public DerivedColor( ColorFunction... functions ) {
super( Color.red );
this.functions = functions;
}
public Color derive( Color baseColor ) {
return ColorFunctions.applyFunctions( baseColor, functions );
}
}

View File

@@ -0,0 +1,432 @@
/*
* From http://tips4java.wordpress.com/2009/07/05/hsl-color/
*
* License note on http://tips4java.wordpress.com/about/
* "You are free to use and/or modify any or all code posted on the
* Java Tips Weblog without restriction. A credit in the code comments
* would be nice, but not in any way mandatory."
*/
package com.formdev.flatlaf.util;
import java.awt.Color;
/**
* The HSLColor class provides methods to manipulate HSL (Hue, Saturation
* Luminance) values to create a corresponding Color object using the RGB
* ColorSpace.
*
* The HUE is the color, the Saturation is the purity of the color (with
* respect to grey) and Luminance is the brightness of the color (with respect
* to black and white)
*
* The Hue is specified as an angel between 0 - 360 degrees where red is 0,
* green is 120 and blue is 240. In between you have the colors of the rainbow.
* Saturation is specified as a percentage between 0 - 100 where 100 is fully
* saturated and 0 approaches gray. Luminance is specified as a percentage
* between 0 - 100 where 0 is black and 100 is white.
*
* In particular the HSL color space makes it easier change the Tone or Shade
* of a color by adjusting the luminance value.
*/
public class HSLColor
{
private final Color rgb;
private final float[] hsl;
private final float alpha;
/**
* Create a HSLColor object using an RGB Color object.
*
* @param rgb the RGB Color object
*/
public HSLColor(Color rgb)
{
this.rgb = rgb;
hsl = fromRGB( rgb );
alpha = rgb.getAlpha() / 255.0f;
}
/**
* Create a HSLColor object using individual HSL values and a default
* alpha value of 1.0.
*
* @param h is the Hue value in degrees between 0 - 360
* @param s is the Saturation percentage between 0 - 100
* @param l is the Lumanance percentage between 0 - 100
*/
public HSLColor(float h, float s, float l)
{
this(h, s, l, 1.0f);
}
/**
* Create a HSLColor object using individual HSL values.
*
* @param h the Hue value in degrees between 0 - 360
* @param s the Saturation percentage between 0 - 100
* @param l the Lumanance percentage between 0 - 100
* @param alpha the alpha value between 0 - 1
*/
public HSLColor(float h, float s, float l, float alpha)
{
hsl = new float[] {h, s, l};
this.alpha = alpha;
rgb = toRGB(hsl, alpha);
}
/**
* Create a HSLColor object using an an array containing the
* individual HSL values and with a default alpha value of 1.
*
* @param hsl array containing HSL values
*/
public HSLColor(float[] hsl)
{
this(hsl, 1.0f);
}
/**
* Create a HSLColor object using an an array containing the
* individual HSL values.
*
* @param hsl array containing HSL values
* @param alpha the alpha value between 0 - 1
*/
public HSLColor(float[] hsl, float alpha)
{
this.hsl = hsl;
this.alpha = alpha;
rgb = toRGB(hsl, alpha);
}
/**
* Create a RGB Color object based on this HSLColor with a different
* Hue value. The degrees specified is an absolute value.
*
* @param degrees - the Hue value between 0 - 360
* @return the RGB Color object
*/
public Color adjustHue(float degrees)
{
return toRGB(degrees, hsl[1], hsl[2], alpha);
}
/**
* Create a RGB Color object based on this HSLColor with a different
* Luminance value. The percent specified is an absolute value.
*
* @param percent - the Luminance value between 0 - 100
* @return the RGB Color object
*/
public Color adjustLuminance(float percent)
{
return toRGB(hsl[0], hsl[1], percent, alpha);
}
/**
* Create a RGB Color object based on this HSLColor with a different
* Saturation value. The percent specified is an absolute value.
*
* @param percent - the Saturation value between 0 - 100
* @return the RGB Color object
*/
public Color adjustSaturation(float percent)
{
return toRGB(hsl[0], percent, hsl[2], alpha);
}
/**
* Create a RGB Color object based on this HSLColor with a different
* Shade. Changing the shade will return a darker color. The percent
* specified is a relative value.
*
* @param percent - the value between 0 - 100
* @return the RGB Color object
*/
public Color adjustShade(float percent)
{
float multiplier = (100.0f - percent) / 100.0f;
float l = Math.max(0.0f, hsl[2] * multiplier);
return toRGB(hsl[0], hsl[1], l, alpha);
}
/**
* Create a RGB Color object based on this HSLColor with a different
* Tone. Changing the tone will return a lighter color. The percent
* specified is a relative value.
*
* @param percent - the value between 0 - 100
* @return the RGB Color object
*/
public Color adjustTone(float percent)
{
float multiplier = (100.0f + percent) / 100.0f;
float l = Math.min(100.0f, hsl[2] * multiplier);
return toRGB(hsl[0], hsl[1], l, alpha);
}
/**
* Get the Alpha value.
*
* @return the Alpha value.
*/
public float getAlpha()
{
return alpha;
}
/**
* Create a RGB Color object that is the complementary color of this
* HSLColor. This is a convenience method. The complementary color is
* determined by adding 180 degrees to the Hue value.
* @return the RGB Color object
*/
public Color getComplementary()
{
float hue = (hsl[0] + 180.0f) % 360.0f;
return toRGB(hue, hsl[1], hsl[2]);
}
/**
* Get the Hue value.
*
* @return the Hue value.
*/
public float getHue()
{
return hsl[0];
}
/**
* Get the HSL values.
*
* @return the HSL values.
*/
public float[] getHSL()
{
return hsl;
}
/**
* Get the Luminance value.
*
* @return the Luminance value.
*/
public float getLuminance()
{
return hsl[2];
}
/**
* Get the RGB Color object represented by this HDLColor.
*
* @return the RGB Color object.
*/
public Color getRGB()
{
return rgb;
}
/**
* Get the Saturation value.
*
* @return the Saturation value.
*/
public float getSaturation()
{
return hsl[1];
}
@Override
public String toString()
{
String toString =
"HSLColor[h=" + hsl[0] +
",s=" + hsl[1] +
",l=" + hsl[2] +
",alpha=" + alpha + "]";
return toString;
}
/**
* Convert a RGB Color to it corresponding HSL values.
*
* @return an array containing the 3 HSL values.
*/
public static float[] fromRGB(Color color)
{
// Get RGB values in the range 0 - 1
float[] rgb = color.getRGBColorComponents( null );
float r = rgb[0];
float g = rgb[1];
float b = rgb[2];
// Minimum and Maximum RGB values are used in the HSL calculations
float min = Math.min(r, Math.min(g, b));
float max = Math.max(r, Math.max(g, b));
// Calculate the Hue
float h = 0;
if (max == min)
h = 0;
else if (max == r)
h = ((60 * (g - b) / (max - min)) + 360) % 360;
else if (max == g)
h = (60 * (b - r) / (max - min)) + 120;
else if (max == b)
h = (60 * (r - g) / (max - min)) + 240;
// Calculate the Luminance
float l = (max + min) / 2;
// System.out.println(max + " : " + min + " : " + l);
// Calculate the Saturation
float s = 0;
if (max == min)
s = 0;
else if (l <= .5f)
s = (max - min) / (max + min);
else
s = (max - min) / (2 - max - min);
// System.out.println(new HSLColor( new float[] {h, s * 100, l * 100} ));
return new float[] {h, s * 100, l * 100};
}
/**
* Convert HSL values to a RGB Color with a default alpha value of 1.
* H (Hue) is specified as degrees in the range 0 - 360.
* S (Saturation) is specified as a percentage in the range 1 - 100.
* L (Lumanance) is specified as a percentage in the range 1 - 100.
*
* @param hsl an array containing the 3 HSL values
*
* @return the RGB Color object
*/
public static Color toRGB(float[] hsl)
{
return toRGB(hsl, 1.0f);
}
/**
* Convert HSL values to a RGB Color.
* H (Hue) is specified as degrees in the range 0 - 360.
* S (Saturation) is specified as a percentage in the range 1 - 100.
* L (Lumanance) is specified as a percentage in the range 1 - 100.
*
* @param hsl an array containing the 3 HSL values
* @param alpha the alpha value between 0 - 1
*
* @return the RGB Color object
*/
public static Color toRGB(float[] hsl, float alpha)
{
return toRGB(hsl[0], hsl[1], hsl[2], alpha);
}
/**
* Convert HSL values to a RGB Color with a default alpha value of 1.
*
* @param h Hue is specified as degrees in the range 0 - 360.
* @param s Saturation is specified as a percentage in the range 1 - 100.
* @param l Lumanance is specified as a percentage in the range 1 - 100.
*
* @return the RGB Color object
*/
public static Color toRGB(float h, float s, float l)
{
return toRGB(h, s, l, 1.0f);
}
/**
* Convert HSL values to a RGB Color.
*
* @param h Hue is specified as degrees in the range 0 - 360.
* @param s Saturation is specified as a percentage in the range 1 - 100.
* @param l Lumanance is specified as a percentage in the range 1 - 100.
* @param alpha the alpha value between 0 - 1
*
* @return the RGB Color object
*/
public static Color toRGB(float h, float s, float l, float alpha)
{
if (s <0.0f || s > 100.0f)
{
String message = "Color parameter outside of expected range - Saturation";
throw new IllegalArgumentException( message );
}
if (l <0.0f || l > 100.0f)
{
String message = "Color parameter outside of expected range - Luminance";
throw new IllegalArgumentException( message );
}
if (alpha <0.0f || alpha > 1.0f)
{
String message = "Color parameter outside of expected range - Alpha";
throw new IllegalArgumentException( message );
}
// Formula needs all values between 0 - 1.
h = h % 360.0f;
h /= 360f;
s /= 100f;
l /= 100f;
float q = 0;
if (l < 0.5)
q = l * (1 + s);
else
q = (l + s) - (s * l);
float p = 2 * l - q;
float r = Math.max(0, HueToRGB(p, q, h + (1.0f / 3.0f)));
float g = Math.max(0, HueToRGB(p, q, h));
float b = Math.max(0, HueToRGB(p, q, h - (1.0f / 3.0f)));
r = Math.min(r, 1.0f);
g = Math.min(g, 1.0f);
b = Math.min(b, 1.0f);
return new Color(r, g, b, alpha);
}
private static float HueToRGB(float p, float q, float h)
{
if (h < 0) h += 1;
if (h > 1 ) h -= 1;
if (6 * h < 1)
{
return p + ((q - p) * 6 * h);
}
if (2 * h < 1 )
{
return q;
}
if (3 * h < 2)
{
return p + ( (q - p) * 6 * ((2.0f / 3.0f) - h) );
}
return p;
}
}

View File

@@ -0,0 +1,111 @@
/*
* 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 );
}
public static void paintAtScale1x( Graphics2D g, int x, int y, int width, int height, Painter painter ) {
paintAtScale1x( g, x, y, width, height, UIScale.getSystemScaleFactor( g ), 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,
double scaleFactor, Painter painter )
{
if( scaleFactor == 1 ) {
painter.paint( g, x, y, width, height, 1 );
return;
}
// save original transform
AffineTransform transform = g.getTransform();
// scale rectangle
Rectangle2D.Double scaledRect = scale( transform, x, y, width, height );
try {
// unscale to factor 1.0
double scale = 1.0 / scaleFactor;
g.scale( scale, scale );
// compute origin delta x/y
double dx = Math.floor( scaledRect.x ) - transform.getTranslateX();
double dy = Math.floor( scaledRect.y ) - transform.getTranslateY();
// move origin to make sure that origin x/y are at whole numbers
if( dx != 0 || dy != 0 )
g.translate( dx, dy );
int swidth = (int) scaledRect.width;
int sheight = (int) scaledRect.height;
// paint
painter.paint( g, 0, 0, swidth, sheight, scaleFactor );
} 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.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JComponent;
import com.formdev.flatlaf.FlatLaf;
/**
* 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, Graphics.class, String.class, int.class, int.class, int.class } );
} catch( Exception ex ) {
ex.printStackTrace();
Logger.getLogger( FlatLaf.class.getName() ).log( Level.SEVERE, null, ex );
throw new RuntimeException( ex );
}
}
@@ -64,7 +67,7 @@ public class JavaCompatibility
else
drawStringUnderlineCharAtMethod.invoke( null, c, g, text, underlinedIndex, x, y );
} catch( IllegalAccessException | IllegalArgumentException | InvocationTargetException ex ) {
ex.printStackTrace();
Logger.getLogger( FlatLaf.class.getName() ).log( Level.SEVERE, null, ex );
throw new RuntimeException( ex );
}
}

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

@@ -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.util;
import java.util.ArrayList;
import java.util.List;
/**
* Utility methods for strings.
*
* @author Karl Tauber
*/
public class StringUtils
{
public static String removeLeading( String string, String leading ) {
return string.startsWith( leading )
? string.substring( leading.length() )
: string;
}
public static String removeTrailing( String string, String trailing ) {
return string.endsWith( trailing )
? string.substring( 0, string.length() - trailing.length() )
: string;
}
public static List<String> split( String str, char delim ) {
ArrayList<String> strs = new ArrayList<>();
int delimIndex = str.indexOf( delim );
int index = 0;
while( delimIndex >= 0 ) {
strs.add( str.substring( index, delimIndex ) );
index = delimIndex + 1;
delimIndex = str.indexOf( delim, index );
}
strs.add( str.substring( index ) );
return strs;
}
}

View File

@@ -40,6 +40,9 @@ public class SystemInfo
// Java VMs
public static final boolean IS_JETBRAINS_JVM;
// UI toolkits
public static final boolean IS_KDE;
static {
// platforms
String osName = System.getProperty( "os.name" ).toLowerCase( Locale.ENGLISH );
@@ -58,6 +61,9 @@ public class SystemInfo
// Java VMs
IS_JETBRAINS_JVM = System.getProperty( "java.vm.vendor", "Unknown" )
.toLowerCase( Locale.ENGLISH ).contains( "jetbrains" );
// UI toolkits
IS_KDE = (IS_LINUX && System.getenv( "KDE_FULL_SESSION" ) != null);
}
private static long scanVersion( String version ) {

View File

@@ -48,8 +48,7 @@ import javax.swing.plaf.UIResource;
*
* 2) user scaling mode
*
* This mode is for Java 8 compatibility and can be removed when changing minimum
* required Java version to 9.
* This mode is mainly for Java 8 compatibility, but is also used on Linux.
* The user scale factor is computed based on the used font.
* The JRE does not scale anything.
* So we have to invoke {@link #scale(float)} where necessary.
@@ -66,7 +65,7 @@ public class UIScale
private static Boolean jreHiDPI;
public static boolean isJreHiDPIEnabled() {
public static boolean isSystemScalingEnabled() {
if( jreHiDPI != null )
return jreHiDPI;
@@ -94,11 +93,11 @@ public class UIScale
}
public static double getSystemScaleFactor( Graphics2D g ) {
return isJreHiDPIEnabled() ? g.getDeviceConfiguration().getDefaultTransform().getScaleX() : 1;
return isSystemScalingEnabled() ? g.getDeviceConfiguration().getDefaultTransform().getScaleX() : 1;
}
public static double getSystemScaleFactor( GraphicsConfiguration gc ) {
return isJreHiDPIEnabled() ? gc.getDefaultTransform().getScaleX() : 1;
return isSystemScalingEnabled() ? gc.getDefaultTransform().getScaleX() : 1;
}
//---- user scaling (Java 8) ----------------------------------------------
@@ -111,7 +110,7 @@ public class UIScale
return;
initialized = true;
if( isEnabled() ) {
if( isUserScalingEnabled() ) {
// listener to update scale factor if LaF changed or if Label.font changed
// (e.g. option "Override default fonts" in IntelliJ IDEA)
PropertyChangeListener listener = new PropertyChangeListener() {
@@ -135,7 +134,7 @@ public class UIScale
}
private static void updateScaleFactor() {
if( !isEnabled() )
if( !isUserScalingEnabled() )
return;
// use font size to calculate scale factor (instead of DPI)
@@ -158,15 +157,15 @@ public class UIScale
if( "Tahoma".equals( font.getFamily() ) )
fontSizeDivider = 11f;
} else if( SystemInfo.IS_LINUX ) {
// default font size for Unity and Gnome is 15
fontSizeDivider = 15f;
// default font size for Unity and Gnome is 15 and for KDE it is 13
fontSizeDivider = SystemInfo.IS_KDE ? 13f : 15f;
}
return font.getSize() / fontSizeDivider;
}
private static boolean isEnabled() {
if( isJreHiDPIEnabled() )
private static boolean isUserScalingEnabled() {
if( isSystemScalingEnabled() && !SystemInfo.IS_LINUX )
return false; // disable user scaling if JRE scales
// same as in IntelliJ IDEA
@@ -179,7 +178,7 @@ public class UIScale
* or "sun.java2d.uiScale" to the given font.
*/
public static FontUIResource applyCustomScaleFactor( FontUIResource font ) {
if( UIScale.isJreHiDPIEnabled() )
if( UIScale.isSystemScalingEnabled() )
return font;
String uiScale = System.getProperty( "flatlaf.uiScale" );
@@ -198,6 +197,14 @@ public class UIScale
return new FontUIResource( font.getFamily(), font.getStyle(), newFontSize );
}
/**
* Scales the given font.
*/
public static FontUIResource scaleFont( FontUIResource font, float scaleFactor ) {
int newFontSize = Math.round( font.getSize() * scaleFactor );
return new FontUIResource( font.getFamily(), font.getStyle(), newFontSize );
}
/**
* Similar to sun.java2d.SunGraphicsEnvironment.getScaleFactor(String)
*/

View File

@@ -19,9 +19,12 @@
*/
module com.formdev.flatlaf {
requires java.desktop;
requires java.logging;
exports com.formdev.flatlaf;
exports com.formdev.flatlaf.icons;
exports com.formdev.flatlaf.ui;
exports com.formdev.flatlaf.util;
uses com.formdev.flatlaf.FlatDefaultsAddon;
}

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